Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find the nearest parent of a Git branch

Tags:

git

branch

Let's say I have the following local repository with a commit tree like this:

master --> a             \              \       develop c --> d                \                 \          feature f --> g --> h 

master is my this is the latest stable release code, develop is my this is the 'next' release code, and feature is a new feature being prepared for develop.

Using hooks, I want to be able to refuse pushes to feature to my remote repository, unless commit f is a direct descendant of develop HEAD. I.e., the commit tree looks like this, because feature has been git rebase on d.

master --> a             \              \       develop c --> d                      \                       \                feature f --> g --> h 

So is it possible to:

  • Identify the parent branch of feature?
  • Identify the commit in parent branch which f is a descendant of?

From there I would check what HEAD of the parent branch is, and see if f predecessor matches the parent branch HEAD, to determine if the feature needs to be rebased.

like image 305
Peter Farmer Avatar asked Jul 01 '10 19:07

Peter Farmer


People also ask

What is a parent branch?

Parent Branch means the main branch from where the Additional Profile is trading.

What is parent commit in git?

When you use the git merge command to merge a branch/commit into your current branch, it joins their development histories together in a new commit. This new commit records both as its parents; the branch/commit you're merging in and the branch you are on when the merge occurs.


1 Answers

Assuming that the remote repository has a copy of the develop branch (your initial description describes it in a local repository, but it sounds like it also exists in the remote), you should be able to achieve what I think you want, but the approach is a bit different from what you have envisioned.

Git’s history is based on a DAG of commits. Branches (and “refs” in general) are just transient labels that point to specific commits in the continually growing commit DAG. As such, the relationship between branches can vary over time, but the relationship between commits does not.

    ---o---1                foo             \              2---3---o      bar                   \                    4                     \                      5---6  baz 

It looks like baz is based on (an old version of) bar? But what if we delete bar?

    ---o---1                foo             \              2---3                   \                    4                     \                      5---6  baz 

Now it looks like baz is based on foo. But the ancestry of baz did not change. We just removed a label (and the resulting dangling commit). And what if we add a new label at 4?

    ---o---1                foo             \              2---3                   \                    4        quux                     \                      5---6  baz 

Now it looks like baz is based on quux. Still, the ancestry did not change, only the labels changed.

If, however, we were asking “is commit 6 a descendent of commit 3?” (assuming 3 and 6 are full SHA-1 commit names), then the answer would be “yes”, whether the bar and quux labels are present or not.

So, you could ask questions like “is the pushed commit a descendent of the current tip of the develop branch?”, but you can not reliably ask “what is the parent branch of the pushed commit?”.

A mostly reliable question that seems to get close to what you want is:

For all the pushed commit’s ancestors (excluding the current tip of develop and its ancestors), that have the current tip of develop as a parent:

  • does at least one such commit exist?
  • are all such commits single-parent commits?

Which could be implemented as:

pushedrev=... basename=develop if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then     echo "'$basename' is missing, call for help!"     exit 1 fi parents_of_children_of_base="$(   git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |   grep -F "$baserev" )" case ",$parents_of_children_of_base" in     ,)     echo "must descend from tip of '$basename'"            exit 1 ;;     ,*\ *) echo "must not merge tip of '$basename' (rebase instead)"            exit 1 ;;     ,*)    exit 0 ;; esac 

This will cover some of what you want restricted, but maybe not everything.

For reference, here is an extended example history:

    A                                   master      \       \                    o-----J        \                  /       \         \                | o---K---L          \               |/           C--------------D              develop            \             |\             F---G---H    | F'--G'--H'                     |    |\                     |    | o---o---o---N                      \   \      \       \                       \   \      o---o---P                        \   \                         R---S 

The above code could be used to reject Hand S while accepting H', J, K, or N, but it would also accept L and P (they involve merges, but they do not merge the tip of develop).

To also reject L and P, you can change the question and ask

For all the pushed commit’s ancestors (excluding the current tip of develop and its ancestors):

  • are there any commits with two parents?
  • if not, does at least one such commit have the current tip of develop its (only) parent?
pushedrev=... basename=develop if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then     echo "'$basename' is missing, call for help!"     exit 1 fi parents_of_commits_beyond_base="$(   git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |   grep -v '^commit ' )" case "$parents_of_commits_beyond_base" in     *\ *)          echo "must not push merge commits (rebase instead)"                    exit 1 ;;     *"$baserev"*)  exit 0 ;;     *)             echo "must descend from tip of '$basename'"                    exit 1 ;; esac 
like image 199
Chris Johnsen Avatar answered Oct 17 '22 01:10

Chris Johnsen