1

I realize that this question looks a lot like 1419623, but it's different.

Imagine the following tree:

master
|
N
M
H-J-K-L-O  -- branch2
E  
C-D-F-G-I  -- branch1
B
A

I need a method to tell that commits C, D, F, G, and I all belong to branch1. For commits D,F,G,I git branch --contains works just great. However, for commit C it will list master, branch1, and branch2 because they all really do include it. Is there any method to only list the branches that have the given commit at or after the branch creation?

That is, I need some command that would behave like this:

$ git some-command B
master
$ git some-command C
branch1
master
$ git some-command F
branch1
$ git some-command H
branch2
master
$ git some-command L
branch2
$ git some-command M
master

Is there any?

Alexander Amelkin
  • 759
  • 1
  • 9
  • 15
  • Regarding `C` as belonging "on" `branch1` but not "on" `master` is ... weird. `C` is certainly in `master`'s history, suppose you have only commits `ABCDFGI` in the above graph, with `master` pointing at `C`, does `C` still not belong on the branch that points right at it? Why does adding a new commit on a branch make previous commits no longer belong? – jthill Aug 31 '18 at 06:00
  • If you have got the answer which helps you solve the problem, you cam mark the answer. – Marina Liu Sep 01 '18 at 05:07

3 Answers3

2

There is no way to do this. git does not keep track of branch creation times. At a physical level of what's stored, there is no difference in how C relates to the three branches.

Mark Adelsberger
  • 42,148
  • 4
  • 35
  • 52
1

In Git, branches do not store any metadata about when or how they were created, or about who created them. They are just pointers to a commit.

And it actually wouldn't make sense to store that information. As an example, here's a typical workflow:

git checkout master
// Make changes and commit A (on master)
git checkout -b my-branch
// Make changes and commit B (on my-branch)
git checkout master
// Make changes and commit C (on master)

...which generates a commit graph like:

* C  master
|
| * B  my-branch
|/
* A

...but I can't tell you how many times I've accidentally committed on master instead of a branch. A good way of fixing that is to checkout a new branch where you are, and then reset master back to where it was before this commit. Something like:

git checkout master
// Make changes and commit A (on master)
// Make changes and commit B (on master)
// Oops... I meant to commit B on a branch
git checkout -b my-branch
git checkout master
git reset master^
// Make changes and commit C (on master)

Both of these generate the same commit graph, but would have different interpretations of which commits "belong" to each branch (specifically, my-branch would not contain either A or B, since the branch was created after each of those commits).

Thomas Kelley
  • 10,187
  • 1
  • 36
  • 43
0

You have a couple of good answers so far, but I think it's worth pointing out that given your question formulation, the command would have to print multiple branch names for commits B and C for instance.

If you are willing to live within the limitations of Git reflogs (essentially, 90 days by default), there is an alternative forumlation that I think will do what you want.

Let me re-draw your graph the way I prefer, which is left (earlier commits) to right (later commits) with names at the right hand edges:

        D--F--G--I   <-- branch1
       /
A--B--C--E--H--M--N   <-- master
             \
              J--K--L--O   <-- branch2

When you created branch1, you probably did so like this:

$ git checkout master   # which resolves to commit `C`, because ...

... because at this point the graph looks like this:

A--B--C   <-- master (HEAD)

Now you run:

$ git checkout -b branch1

which makes the graph look like this:

A--B--C   <-- master, branch1 (HEAD)

That is, now two branch-names exist, both of which identify commit C. The name HEAD is currently attached to branch1, due to the use of git checkout -b.

At this point you:

... do some work ...
$ git commit -m message   # which creates commit `D`

giving:

        D   <-- branch1 (HEAD)
       /
A--B--C   <-- master

As you make more commits, they are added wherever HEAD is attached, so this causes branch1 to accumulate commits.

Your question includes this phrasing:

Is there any method to only list the branches that have the given commit at or after the branch creation?

which falls afoul of two problems: branches don't really have a "creation", and commits have—or contain, really—every commit that is reachable from their tip backwards.

What we could instead ask is: For each commit, which branch reflogs have entries that refer directly to that commit? Assuming no reflog value has expired and been removed, we will find that, at this point—where branch1 exists and points to commit D—commit D is in only branch1's reflog, commit C is in both branch1 and master's reflog, commit B is only in master's reflog, and commit A is only in master's reflog.

Later, when we have commits A through O inclusive, we will find that H is like C: it's in two different reflogs, for master and branch2. The remaining commits are in only one reflog.

Hence we would start by asking the reflog-based question. Then, for commits that appear in multiple reflogs, we would declare that such a commit is "more in tune with" whichever reflog was created later. That's because the reflog entry that has C as a commit on master was created at the time commit C itself was created, so that reflog entry has an earlier date; but when branch1 was created, pointing to commit C, that, in effect, "takes" commit C into branch1.

Note that this formulation also has a flaw—besides, of course, the one that occurs when the reflog entry expires. Suppose we now run:

git checkout -b branch3 <hash-of-C>

and make a new commit on branch3? Our graph is now:

       ,--P   <-- branch3 (HEAD)
      /
      | D--F--G--I   <-- branch1
      |/
A--B--C--E--H--M--N   <-- master
             \
              J--K--L--O   <-- branch2

Commit C is now in three reflogs: one for master, one for branch1, and one for branch3. (The full reflog for branch3 consists of "commit P, then commit C".) The latest is branch3: by attaching C as the base of branch3 we've effectively "stolen" commit C for branch3.

If you dislike this, the obvious alternative is to assign commit C to the earliest reflog that contains it, i.e., to master. But now:

git who-owns <hash-of-C>

says master, not branch1, which is not what you asked for. It's easy enough to do either one though; here's the untested pseudocode framework to get you started:

#! /bin/sh
# git-who-owns: pick a branch to "own" a commit
. git-sh-setup
case $# in
0) die "usage: git who-owns <commit> ...";;
esac

who-owns() {
    local b hash

    hash=$(git rev-parse $1^{commit}) || return 1
    for b in $(git for-each-ref --format='%(refname:short) refs/heads); do
        git reflog --no-abbrev $b | awk '$1 == "$hash"'
    done
}

status=0
for arg do who-owns "$arg" || status=1; done
exit $status

If this script is in your $PATH as git-who-owns, running git who-owns <commit-specifier> will print out all the matching reflogs.

There's another bigger flaw: if a commit could belong to one or more branches, but was brought in via an en-masse operations like git fetch followed by git merge, its hash ID will not be in any reflog. You can hybridize the above along with --contains operations to assign such "in-between" commits to branches.

(The idea in general is not terribly workable, though. I would not spend a lot of time on this. Instead, drop a marker, such as a tag, on a boundary commit when you want to generate bounded lists of commits. Then git rev-list branch-name ^marker will get you the commit IDs you want. This is more conventionally spelled git rev-list marker..branch-name, e.g., git log orgin/master..master.)

torek
  • 448,244
  • 59
  • 642
  • 775
  • TL;DR. All in all, the answer is "there is no built in way". Saying that `branches don't really have a "creation"` is just nitpicking actually. Because they do. From human point of view the commit at which the branch entry was first created, that is the first commit that belongs to 'master' if you traverse the branch from the tip backwards, is the creation point. Basically, that's the criterion: if a commit is reachable from a branch tip and its child is both on the branch and on the default branch (master), then discard the branch from `who-owns` results. Just need to find an optimal way. – Alexander Amelkin Sep 03 '18 at 15:19
  • Using tags in any way is not an option as they are already used with `git describe` to generate software version. Adding more tags will ruin that functionality. – Alexander Amelkin Sep 03 '18 at 15:22
  • @AlexanderAmelkin: `git describe` will only use *lightweight* tags if you use `--tags` or `--all`. But if you dislike cluttering your tag name space, you can invent your own name space under `refs/`. You *can* use the algorithm you're suggesting (i.e., assign commits to branches based on some kind of "branch priority", with `master` being the lowest priority) but I would advise against it. – torek Sep 03 '18 at 17:35