5

I am looping through commits in LibGit2Sharp:

Repository repo = new Repository("Z:/www/gg");

foreach (LibGit2Sharp.Commit commit in repo.Commits)
{
    ...
}

I can retrieve properties like Author and Message, but I do not see anything about what branch it belongs to? Ideally I would like to have a pointer to the branch object, but even a name would be fine in this scenario.

This is what the debugger shows up:

enter image description here

This is what I am looking for:

enter image description here

TortoiseGit's behavior of showing the most relevant branch name:

enter image description here

Example repository: https://docs.google.com/open?id=0B-3-X85VysdNcmZIaGVTSDZSenVGbTJxYlI2SUlsZw

Tower
  • 98,741
  • 129
  • 357
  • 507

3 Answers3

7

There is currently no built-in way to mimic git branch --contains <commit>.

However, you might work around this limit by explicitly walking each branch and comparing each popped commit against the searched one.

Following test demonstrates this

[Test]
public void CanSearchBranchesContainingASpecificCommit()
{
    using (var repo = new Repository(StandardTestRepoPath))
    {
        const string commitSha = "5b5b025afb0b4c913b4c338a42934a3863bf3644";
        IEnumerable<Branch> branches = ListBranchesContaininingCommit(repo, commitSha);

        branches.Count().ShouldEqual(6);
    }
}

private IEnumerable<Branch> ListBranchesContaininingCommit(Repository repo, string commitSha)
{
    foreach (var branch in repo.Branches)
    {
        var commits = repo.Commits.QueryBy(new CommitFilter { Since = branch }).Where(c => c.Sha == commitSha);

        if (!commits.Any())
        {
            continue;
        }

        yield return branch;
    }
}

Note: This code has been successfully tested against the current tip of the development branch of LibGit2Sharp.

UPDATE:

Following the discussion in the comments, here's a little update which I hope will fulfill your request.

The code below will return all the branches containing the searched commit. If the commit happens to be the tip of at least one branch, those branches will be returned instead.

[Test]
public void CanSearchBranchesContainingASpecificCommit()
{
    using (var repo = new Repository(StandardTestRepoPath))
    {
        const string commitSha = "5b5b025afb0b4c913b4c338a42934a3863bf3644";
        IEnumerable<Branch> branches = ListBranchesContaininingCommit(repo, commitSha);

        branches.Count().ShouldEqual(6);

        const string otherCommitSha = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045";
        branches = ListBranchesContaininingCommit(repo, otherCommitSha);

        branches.Count().ShouldEqual(1); // origin/packed-test
    }
}

private IEnumerable<Branch> ListBranchesContaininingCommit(Repository repo, string commitSha)
{
    bool directBranchHasBeenFound = false;
    foreach (var branch in repo.Branches)
    {
        if (branch.Tip.Sha != commitSha)
        {
            continue;
        }

        directBranchHasBeenFound = true;
        yield return branch;
    }

    if (directBranchHasBeenFound)
    {
        yield break;
    }

    foreach (var branch in repo.Branches)
    {
        var commits = repo.Commits.QueryBy(new CommitFilter { Since = branch }).Where(c => c.Sha == commitSha);

        if (!commits.Any())
        {
            continue;
        }

        yield return branch;
    }
}
Jan Willem B
  • 3,787
  • 1
  • 25
  • 39
nulltoken
  • 64,429
  • 20
  • 138
  • 130
  • This works fine, but one more thing: can I somehow know which of these multiple branches is the "most recent one" it belongs to? I.e. it makes little sense in my situation to know if it belonged to a branch one year ago if it was just merged to `master` one week ago. I doubt I can just rely on it being the first in the result set... – Tower Feb 26 '12 at 11:10
  • Could you give me some examples or the equivalent git command? I'd gladly try to answer you but I'm not sure to clearly understand what would be the "most recent" branch. – nulltoken Feb 26 '12 at 11:40
  • If you branch off from `master` and create the branch `foo`. If you now commit on `foo`, the commit is part of 2 branches (`foo` and `master`). What I want to have, is just `foo`, because that's the "most recent". If `foo` were an orphan branch (`git checkout -b foo --orphan`) then your solution would only return `foo` and I could use it, but in many cases a commit is part of one or more branches and I'm only interested in the one it is most recently connected to. – Tower Feb 26 '12 at 12:40
  • The command `git log --all --decorate` shows this properly. It shows that the commit is part of e.g. `(HEAD, foo)` and not `(HEAD, foo, master)`, although it is part of `master`. – Tower Feb 26 '12 at 12:44
  • In order to be sure I clearly get what you're after, can this be rephrased this way: "The branch with the most recent tip (ie. commit) which contains the searched commit"? – nulltoken Feb 26 '12 at 15:41
  • Not quite. :) I added a picture to the question that should clarify. – Tower Feb 26 '12 at 16:08
  • For example if you look at TortoiseHg you can see it doing this what I want: http://tortoisehg.bitbucket.org/img/vt_history.png (it shows the most relevant branch for the commit using the grid column `Branch`). – Tower Feb 26 '12 at 16:13
  • I'm far from being a Mercurial expert, but IIRC a ChangeSet contains the name of the branch it's been committed onto (which might help TortoiseHg a bit :) ). In git parlance, a branch is nothing more than a pointer to a commit. The commit knows nothing about the branch(es) it belongs to. The branch might even has been discarded after the commit has been merged. If you had a tortoiseGit related example or a sample Git repo (with branches!) you could point me to, that would be awesome. – nulltoken Feb 26 '12 at 17:04
  • I have added a screenshot of what I want and that is what TortoiseGit does. I also linked a small example repository which is nothing spectacular (just two branches demonstrating this). I'd also like to add that `git log --decorate --all --graph` shows this the way I want. Basically your code is fine in finding the right branches, but I still need to choose the one right branch that is the most relevant branch. Can I just pick the first element `branches.ElementAt(0)`? – Tower Feb 26 '12 at 17:19
  • Look at TortoiseGit screenshot. The commit 3572... says `foo` and your code gives me both `master` and `foo`. I need to figure out which one is more relevant (technically the commit is indeed in both). – Tower Feb 26 '12 at 17:23
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/8237/discussion-between-nulltoken-and-rfactor) – nulltoken Feb 26 '12 at 17:47
  • @nulltoken this works very slow for me (ListBranchesContaininingCommit first version) or it required libgit2 modification? – KindDragon Mar 16 '13 at 00:45
  • @KindDragon Unfortunately, there's no better alternative in C#. It's only a workaround and leverages a revwalk per branch (which is very time consuming). A change in the libgit2 C revwalk implementation would be required to benefit from a speed bump. – nulltoken Mar 16 '13 at 19:57
  • You can use `FindCommonAncestor` instead of `Commits.QueryBy` in libgit2sharp v0.9 and upper. – KindDragon Jan 28 '14 at 16:02
2

Git does not store branch information with commits. You'd have to walk the history DAG and see if the commit is reachable from the refs.

On the commandline with normal git you would run git branch --contains $SHA1

knittl
  • 246,190
  • 53
  • 318
  • 364
  • 3
    That command line call is exactly what I want. Now how do I do this with LibGit2Sharp? – Tower Feb 26 '12 at 10:52
1

As knittl said, git doesn't store that information. A commit is a fixed state of the repository with some metadata. Since a commit is immutable and what branches it belongs to can change, branch information can't be stored directly in the commit.

So, to find out whether a certain commit actually belongs to some branch, you need to walk through the commits of that branch and compare them with the one commit.

To make this faster, you can store all the commits for each branch in a HashSet<T> like this:

var branchCommits =
    repo.Branches.Select(
        b => new
             {
                 b.Name,
                 Commits = new HashSet<Commit>(b.Commits)
             })
        .ToArray();

foreach (Commit commit in branch.Commits)
{
    var commitBranches = branchCommits.Where(b => b.Commits.Contains(commit));

    …
}
svick
  • 236,525
  • 50
  • 385
  • 514
  • 2
    Beware that this eagerly loads all commits of every branch in memory (which basically parses the whole repository). In order to be a little bit more "resource-friendly", one could rely on the `Commits.QueryBy()` method which leverages a walker. – nulltoken Feb 26 '12 at 11:15