39

I've used git-blame to find a particular commit. Now I want to find the branch that it originally came from. (From there, I'll use the branch name to find the particular ticket)

Let's define "original branch" as "the branch to which the commit was made before the branch was merged into any other branch".

Craig Walker
  • 44,465
  • 49
  • 146
  • 204

9 Answers9

41

Like the others said, if the branch you are looking for isn't local to the repository on which you are blaming this commit (e.g. a branch only in the personal repo of a distant developer), you are screwed.

But assuming that sought-after branch is something you can see, and that of course you have the commit's hash, say d590f2..., a partial answer is that you can do :

$ git branch --contains d590f2
  tests
* master

Then, just to confirm you have the culprit:

$ git rev-list tests | grep d590f2

Of course, if d590f2 has been merged in more than one branch, you will have to be more subtle than this.

Francois G
  • 11,485
  • 50
  • 57
  • 3
    If after 3 months , a commit now belong to multiple branches , then how will your strategy give me original branch name ? Note that there can be fast forward / non fast forward merges . – Number945 Oct 10 '18 at 08:11
10

That isn't really applicable in git. Branches are local concepts to each repository: one person's "local-stuff" branch can be quite separate from another person's "local-stuff" branch. If you do something like looking at your main integration branch, and your query commit, and removing all the merge bases between the two, you should be able to get a subtree of the commit history that may shed some illumination... or may not. e.g. if you trace up the link from the query commit towards "master" you should hopefully find merge commits with useful comments saying where the merge came from... but this information is just informational, not recorded in some way intended to be automatically retrieved.

e.g. gitk some-commit...master (which is almost short for gitk some-commit master --not $(git merge-base some-commit master))

araqnid
  • 108,587
  • 20
  • 147
  • 127
8

A Git branch is nothing else than a "named pointer to a commit" (that's a fundamental different concept than in other well-known VCS).

This situation is clear, commit A is on branch-1, commit B on branch-2:

  o A [branch-1]
  |
o | B [branch-2]
| |

After merging is becomes unclear whether A (or B) originally was on branch-1 or branch-2:

o [branch-1] [branch-2]
|
o merged 
|\
| o A
| |
o | B
| |

Maybe you can guess on what Git branch the commit A was if you have tagged parent commits of A, e.g. release-1 and you know that this tag only was given for commits in branch-1.

o [branch-1] [branch-2]
|
o merged 
|\
| o A
| |
o | B
| |
| o <release-1]
| |
Mot
  • 24,166
  • 22
  • 78
  • 117
  • Strictly speaking, only impossible if the merge is fast forwarded. If all merges are done with `--no-ff` then git preserves branch history as well. Just do `git log --graph $commit_id .. HEAD` – slebetman Dec 27 '10 at 00:11
  • 6
    Generally speaking, in Git, commits aren't on branches, branches are on commits. – Jörg W Mittag Dec 27 '10 at 03:47
6

I give a try, please comment since not totally sure, but I believe it does the job.

The following will work only if the branches still point at the tip of before being merged into master, which is the case if the branches were on the same repo:

o [master]
|
o merged branch "great-feature" into master
|\
| o A [great-feature]
| |
o | B
| |

If it it not the case (for example if you pulled from another repo) you can still recreate them by hand.

First get the branches where your commit are:

$ git branch -a --contains=<sha-of-B>
*master
great-feature

then for each branch get the number of commits that separate their head to the commit: this is the number of lines that output git log for the specified range:

$ git log --pretty=oneline <sha-of-B>..great-feature | wc -l
1
$ git log --pretty=oneline <sha-of-B>..master | wc -l
4

So B is nearest to great-feature, which means it was created in it.

This could be made into a nice script, feel free to add it to the answer (I'm not good at this)

CharlesB
  • 75,315
  • 26
  • 174
  • 199
5

First ensure you fetched changes from remotes

$ git fetch --all

And,

$ git branch -a --contains d590f2

Without -a option you can't find commits existing only on remote branches

Ochko
  • 111
  • 1
  • 5
3

When you on the branch the "original branch" was merged to. You may run:

git log <SHA>..HEAD --ancestry-path --merges

This command will show all merge commits between <SHA>..HEAD. You need last one.

For example for c0118fa commit (last but one) the "original branch" is redesign_interactions

* ccfd449 (HEAD -> develop) Require to return undef if no digits found
*   93dd5ff Merge pull request #4 from KES777/clean_api
|\  
| * 39d82d1 Fix tc0118faests for debugging debugger internals
| * ed67179 Move &push_frame out of core
| * 2fd84b5 Do not lose info about call point
| * 3ab09a2 Improve debugger output: Show info about emitted events
| *   a435005 Merge branch 'redesign_interactions' into clean_api
| |\  
| | * a06cc29 Code comments
| | * d5d6266 Remove copy/paste code
| | * c0118fa Allow command to choose how continue interaction
| | * 19cb534 Emit &interact event

You should run:

git log c0118fa..HEAD --ancestry-path --merges

And scroll down to find last commit. Which is:

commit a435005445a6752dfe788b8d994e155b3cd9778f
Merge: 0953cac a06cc29
Author: Eugen Konkov
Date:   Sat Oct 1 00:54:18 2016 +0300

    Merge branch 'redesign_interactions' into clean_api
Eugen Konkov
  • 15,716
  • 7
  • 69
  • 107
2

I found a simpler way to do it: it's in the message of the last commit of git log <sha>..HEAD --merges!

This command shows the merges that have happened between master and the commit; the last commit output by this command is the first merge commit that included it. It usually contains the branch name, so even if the branch was deleted you can find its name.

To get only the name of the branch just type git log <sha>..HEAD --merges --oneline |tail -1

meager
  • 209,754
  • 38
  • 307
  • 315
CharlesB
  • 75,315
  • 26
  • 174
  • 199
  • 3
    This is only applicable in cases where there wasn't a fast-forward merge. – meager Jul 14 '11 at 19:51
  • @meagar agree, on a side note merging a feature into master with fast-forward is not a good practice, for this precise reason: it's nicer to see in the history that a feature has been merged – CharlesB Jul 15 '11 at 08:53
  • Thank you CharlesB, you just saved my bacon. I had accidentally deleted a remote branch and had no local reflog because my project was on another computer I don't have access to. So I need to reconstruct the last commit before the merge so that I can resurrect the branch. – Zack Morris Apr 12 '15 at 18:20
  • @CharlesB , below I present an idea. I am not assuming that the line of work containing got merged into master. A general (may be partial ) solution can be : Let B ={all branch ref that contain }. Filter B such that discard all branch ref whose creation date is later that creation date of . Now, using ref log track the movement of the remaining branch ref and find the first branch ref which touched/created . Of course ref log is needed here. Any comments ? – Number945 Oct 10 '18 at 08:57
1

It seems like this isn't a question that's possible to answer with 100% precision via git.

git branch --contains --merge <sha1>

returns a list of all branches to which the commit was merge and the original branch. --no-merged returns all subsequent branches that include the commit because they branched after the merge point.

So, you can get a list of each merge but not original branch and any branch deleted prior to command execution is lost (or your looking at reflogs)

Results

git branch --contains <sha1 for "added feature/inital 1">
* develop
  feature/inital
  feature/subsequent1

git branch --contains <sha1 for "added feature/inital 1"> --merged
* develop
  feature/inital

git branch --contains <sha1 for "added feature/inital 1"> --no-merged
  feature/inital

Test Script

function mkbranch {
  git checkout -b $1
  git push --set-upstream origin $1
}

# Develop
mkbranch develop
for f in 1 2 3; do date > file${f}.txt;  git add file${f}.txt; git commit -m "added develop $f"; done
git push

# Initial Feature Branch
mkbranch feature/inital
for f in 1 3; do date > file${f}.txt;  git add file${f}.txt; git commit -m "modified feature/inital $f"; done
git push

# Merge
git checkout -b develop
git merge feature/inital
git push


# Next Feature Branch
mkbranch feature/subsequent1
for f in 1 3; do date > file${f}.txt;  git add file${f}.txt; git commit -m "modified feature/subsequent1 $f"; done
git push
Peter Kahn
  • 10,918
  • 14
  • 64
  • 108
-1

This worked well enough for me to get the branch name from a detached head in a Jenkins workspace:

git show -s --pretty=%d

ed209
  • 218
  • 1
  • 7