git2-rs icon indicating copy to clipboard operation
git2-rs copied to clipboard

[Documentation] Improve revwalk examples. Finding last change to file?

Open kallisti5 opened this issue 5 years ago • 6 comments

The revwalk documentation is unclear about how to find the last commit changing a file (a pretty basic usage).

Example:

    fn recipe_modified(path: PathBuf) -> Result<i32, Box<dyn Error>> {
        let repo = git2::Repository::discover(path)?;
        let mut revwalk = repo.revwalk()?;
        revwalk.push_head()?;
        revwalk.set_sorting(git2::Sort::TIME)?;
        for rev in revwalk {
            let commit = repo.find_commit(rev?)?;
            let message = commit.summary_bytes().unwrap_or_else(|| commit.message_bytes());
            println!("{}\t{}", commit.id(), String::from_utf8_lossy(message));
        }
        Ok(0)
    }

Here, provided a git repo, we pull all of the commits for it.. but I don't see a clear way to:

  • Check the files included in a commit.
  • Revwalk only on commits impacting a file.

This seems like a really common usage scenario, but little documentation on it,

kallisti5 avatar Mar 29 '20 02:03 kallisti5

same problem, I try to get files changes

phodal avatar Jan 25 '21 13:01 phodal

I have the same issue. Does anyone solved it already?

Ronaldho80 avatar Mar 24 '21 14:03 Ronaldho80

git2-rs is a basic wrapper around libgit2, functionality you miss in here that are not rust specific are better adressed in upstream: https://github.com/libgit2/libgit2

pretty sure this question came up there already

extrawurst avatar Mar 24 '21 16:03 extrawurst

for each revision you walk over you can use diff_tree_to_tree to find the files the commit touched. gitui uses something like that to show a diff of a commit: https://github.com/extrawurst/gitui/blob/master/asyncgit/src/sync/commit_files.rs#L42

extrawurst avatar Mar 24 '21 16:03 extrawurst

Thank you for this suggestion.

I am actually doing this. I just figured, that my code had different issues making it buggy.

One thing is bugging me though: The git2::Delta has no "Merged" status. So, if the last commit message was done before a merge, it shows me the merge message of the commit. But I want to have the commit message of the last modification. Do you have a hint on how to get that?

Ronaldho80 avatar Mar 25 '21 13:03 Ronaldho80

Old thread, but first google result. Here's some further pointers that I've used:

  • Sort order should be set to Sort::TOPOLOGICAL, not Sort::Time. Times on commits can be arbitrary, and are not reliable to sort on. TOPOLOGICAL sorts according to the real git history, matching the behaviour of e.g. git log.
  • Use revwalk.simplify_first_parent() to ignore the history of merged in branches. This will still include the merge commits themselves, but it won't walk the individual commits of the branch1. This is useful, because with this option enabled successive commits in the revwalk are always actual successive commits in the history (i.e. child then parent). Without this option revwalk will visit all branches of the history, and as a result successive revs in the iterable do not necessarily have a child-parent relation, and thus we can't directly compare them to find in which commit files have changed.
  • With the above 2, revwalk will give us a linear history, and we can compare successive commits in the revwalk to find out what has changed where. We can either use diff_tree_to_tree (as @extrawurst suggested) to get the full changes between commits, or if you're only interested in a single file, you can compare the file_id2 of its path between successive commits to see where the file has changed.

  1. I suppose that if you want to walk through the history of the merged-in branches then you could start a new revwalk that only includes the commits for that specific branch. This way, we're still only ever dealing with linear histories. I haven't tried this myself though.
  2. To get the file id:
    let commit = repo.find_commit(rev)?;
    let tree = repo.find_tree(commit.tree_id())?;
    let file_id = tree.get_path(Path::new(file_path_str)).ok();
    

TiddoLangerak avatar Jul 19 '24 11:07 TiddoLangerak