简体   繁体   中英

How to find the common ancestor of two branches before a merge commit in git?

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:

  1. Branch from master to a bug fix branch, eg bugfix/abc
  2. Make all the commits to fix the bug on bugfix/abc
  3. Merge 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:

  1. Create a new branch from bugfix/abc , eg bugfix/def
  2. Then manually find the commit on master that was before the merge commit, eg f4d399238f
  3. Now I use rebase: $ 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

  1. git merge-base bugfix/abc master
    • Since the merge has already been done this just returns the commit at the head of bugfix/abc
  2. Combining the result of #1 to get the child of this commit using git log

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

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

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 , ie a merge base . Note that there can be more than one merge base for a pair of commits.

Manually find it from the log

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM