Commits whose tree changed `rel_path` (file or directory), newest first. `rel_path` is trace-relative; resolves to repo-relative internally so single-repo and per-trace layouts both work.
(root: &Path, rel_path: &str)
| 181 | /// `rel_path` is trace-relative; resolves to repo-relative internally so |
| 182 | /// single-repo and per-trace layouts both work. |
| 183 | pub fn commits_under(root: &Path, rel_path: &str) -> Result<Vec<CommitInfo>> { |
| 184 | let repo = open(root)?; |
| 185 | let translated = translate(&repo, root, rel_path); |
| 186 | let mut walk = repo.revwalk()?; |
| 187 | walk.push_head().context("revwalk push HEAD")?; |
| 188 | walk.set_sorting(git2::Sort::TIME)?; |
| 189 | |
| 190 | let target_path = Path::new(&translated); |
| 191 | let mut out = Vec::new(); |
| 192 | for oid in walk { |
| 193 | let oid = oid?; |
| 194 | let commit = repo.find_commit(oid)?; |
| 195 | let commit_tree = commit.tree()?; |
| 196 | let touched = if commit.parent_count() == 0 { |
| 197 | tree_contains_path(&commit_tree, target_path)? |
| 198 | } else { |
| 199 | let mut found = false; |
| 200 | for i in 0..commit.parent_count() { |
| 201 | let parent = commit.parent(i)?; |
| 202 | let parent_tree = parent.tree()?; |
| 203 | let diff = repo.diff_tree_to_tree(Some(&parent_tree), Some(&commit_tree), None)?; |
| 204 | for delta in diff.deltas() { |
| 205 | if path_matches(delta.new_file().path(), target_path) |
| 206 | || path_matches(delta.old_file().path(), target_path) |
| 207 | { |
| 208 | found = true; |
| 209 | break; |
| 210 | } |
| 211 | } |
| 212 | if found { |
| 213 | break; |
| 214 | } |
| 215 | } |
| 216 | found |
| 217 | }; |
| 218 | if touched { |
| 219 | let time = commit.time(); |
| 220 | let at = Utc.timestamp_opt(time.seconds(), 0).single().unwrap_or_else(Utc::now); |
| 221 | out.push(CommitInfo { |
| 222 | sha: oid.to_string(), |
| 223 | at, |
| 224 | message: commit.message().unwrap_or("").trim_end().to_string(), |
| 225 | }); |
| 226 | } |
| 227 | } |
| 228 | Ok(out) |
| 229 | } |
| 230 | |
| 231 | fn path_matches(p: Option<&Path>, target: &Path) -> bool { |
| 232 | let Some(p) = p else { return false }; |
searching dependent graphs…