Reputation: 6134
Background
I'm trying to write a script that will simplify my backport process using git.
The general process for fixing a bug in the current version goes something like this:
master
to a bug fix branch, e.g. bugfix/abc
bugfix/abc
bugfix/abc
to master
(without fast-forward merges)Now when I want to backport the fix to version 1 for example (branch v1
) I do:
bugfix/abc
, e.g. bugfix/def
master
that was before the merge commit, e.g. f4d399238f
$ git rebase --onto v1 f4d399238f bugfix/def
This works great (after working out that I had to use the commit before the merge for the upstream).
Question
How do I find the common ancestor of two branches before a merge commit? (for step 2 of the backport process).
Tried
git merge-base bugfix/abc master
bugfix/abc
git log
--reverse
, --ancestry-path
and --children
but I never got what I expected.Update
The key difference between this question and Find common ancestor of two branches is that the two branches have already been merged. As I mentioned the best commit is returned as the head of the branch that was merged. I need the common ancestor before the merge commit.
Assume that the branches after the bug has been fixed and merged into master look like:
A---B v1
\
C---D---F---H master
\ /
E---G bugfix/abc
Running $ git merge-base master bugfix/abc
will return G but I need to get D (or even F would do for the purpose of using rebase --onto
).
Once I get D I would then run:
$ git branch bugfix/def bugfix/abc
$ git rebase --onto v1 D bugfix/def
$ git checkout v1
$ git merge bugfix/def
To end up with the desired result of:
E'---G' bugfix/def
/ \
A---B--------I v1
\
C---D---F---H master
\ /
E---G bugfix/abc
Upvotes: 3
Views: 3284
Reputation: 6134
For the case that I have shown I found a reasonable solution:
git rev-list --first-parent master | gawk -v bugfix=bugfix/abc '{
commit = $1
branchCmd = "git branch --contains \"" commit "\" \"" bugfix "\""
if (branchCmd | getline) {
print commit
exit
}
}'
This will print D
.
It turns out that this fails if master
was ever merged into bugfix/abc
as shown here:
E'---G'---h' bugfix/def
/ \
A---B-------------J v1
\
C---D---F-------I master
\ \ /
E---G---H bugfix/abc
In this case it will print F
but that is still not what we want.
If only there was some option for the branch --contains
to limit the parents, such as --first-parent
like there is for rev-list
...
A workaround was to modify the script to the following:
git rev-list --first-parent master | gawk -v bugfix=bugfix/abc '{
commit = $1
branchCmd = "git branch --contains \"" commit "\" \"" bugfix "\""
if (branchCmd | getline) {
timestampCmd = "git show -s --format=%ct \"" commit "\""
if (timestampCmd | getline timestamp) {
revlistCmd = "git rev-list --first-parent --max-age=" timestamp " \"" bugfix "\""
while (revlistCmd | getline line) {
if (commit == line) {
print commit
exit
}
}
}
}
}'
The idea here is that we take each possible result and check whether that is reachable from bugfix/abc
but only following the first parent. The --max-age
means we only go back as far as necessary. If it is not reachable then it must have been a merge from master
to bugfix/abc
. If it is then we print the commit hash and exit.
Upvotes: 0
Reputation: 142114
How do I find the common ancestor of two branches before a merge commit? (for step 2 of the backport process).
There are several options to use:
git merge-base
is the command you are looking for
git merge-base finds best common ancestor(s) between two commits to use in a three-way merge. One common ancestor is better than another common ancestor if the latter is an ancestor of the former. A common ancestor that does not have any better common ancestor is a best common ancestor, i.e. a merge base. Note that there can be more than one merge base for a pair of commits.
git log --decorate --graph --oneline --all
This will display the fork points in the log so you can track the commit id of the branching.
Upvotes: 4