开发者

How can I determine if a given git hash exists on a given branch?

开发者 https://www.devze.com 2022-12-22 18:13 出处:网络
Background: I use an automated build system which takes a git hash as input, as well 开发者_运维百科as the name of the branch on which that hash exists, and builds it.However, the build system uses th

Background: I use an automated build system which takes a git hash as input, as well 开发者_运维百科as the name of the branch on which that hash exists, and builds it. However, the build system uses the hash alone to check out the code and build it -- it simply stores the branch name, as given, in the build DB metadata.

I'm worried about developers accidentally providing the wrong branch name when they kick off a build, causing confusion when people are looking through the build history.

So how can I confirm, before passing along the hash and branch name to the build system, that the given hash does in fact come from the given branch?


You can ask git branch directly which branches contain the commit in question:

% git branch -a --contains 4f08c85ad
* master
  remotes/origin/bug_872
  remotes/origin/bug_898
  remotes/origin/master


Hmm, this use of a branch name seems fishy. As in Dustin's answer, there may be multiple branches that contain a commit. Why is any one of those branch names better than another for this purpose?


If you only care about one specific branch, you could compute the “merge base” between the branch and the commit.

In the following diagrams, the commit to build is C, the tip of the claimed branch is T, and the merge base is labeled with M above it.

  • If M equals C equals T, then the the commit to build is the tip of the claimed branch.

                   M
                   ↓
    o--o--o--o--o--x  branch  # x is both C and T
    
  • If M equals C, then the tip of the claimed branch is a descendent of the commit to build.

          M
          ↓
    o--o--C--o--o--T  branch
    
  • If M equals T, then the commit to build is a descendent of the tip of the claimed branch.

          M
          ↓
    o--o--T           branch
           \
            o--o--C
    
  • If M equals something else, then C is a descendent of some ancestor of T.

          M
          ↓
    o--o--o--o--o--T  branch
           \
            o--o--C
    
  • If there is no M, then the commit to build is not related to the claimed branch.

    o--o--o--o--o--T branch
    
    o--o--o--o--C
    

You can make this check like this:

#!/bin/sh

# Usage: is-ancestor-of <branch> <commit>

if test $# -ne 2; then
    echo "$0"': invalid arguments'
    exit 128
fi
claimed_branch="$1"
commit="$2"

merge_base="$(git merge-base "$commit" "$claimed_branch")" &&
  test -n "$merge_base" &&
  test "$merge_base" = "$(git rev-parse --verify "$commit")" &&
  exit 0

echo "$commit is not an ancestor of $claimed_branch" 1>&2
exit 1

The above script does not actually require or check that the ‘branch’ argument is a branch (it could be any commit-ish). To check that something is actually a branch, you might use something like this:

#!/bin/sh

# Usage: is-branch <branch>

if test $# -ne 1; then
    echo "$0"': invalid arguments'
    exit 128
fi
branch="$1"

# check various branch hierarchies, adjust as needed
git show-ref --verify refs/heads/"$branch" ||
git show-ref --verify refs/remotes/"$branch" || {
    echo "not a branch name: $branch" 1>&2
    exit 1
}

So you could use them together to verify that something is a branch and that a certain commit is in that branch:

is-branch "$claimed_branch" && is-ancestor-of "$claimed_branch" "$commit_to_build"


One possible non-solution would be to parse the result of:

$ git reflog show myBranch

and see if your hash is in it.

C:\Prog\Git\tests\rep\main>git reflog show patches
786a190 patches@{0}: rebase finished: refs/heads/patches onto 74630b983db476c323b1d3f6771e57484551240e
8448d0f patches@{1}: master~1: updating HEAD
74630b9 patches@{2}: commit: test2
1e73e36 patches@{3}: branch: Created from master

I keep this as a Community Wiki answer to remember Igor Zevaka and Chris Johnsen's comments:

Would that work after a clone?
Pretty sure your reflog starts after you clone a remote. So if a branch had a commit before the clone, it wouldn't show up in the reflog.

There are also problems with pushes, rebases, fetches (for ‘remote tracking’ branches), and pulls (either merge or rebase type) where only the new tip commit ends up the reflog.
Also reflogs are disabled by default in bare repositories (the most common destination for pushes).
Also, if “cheating” is a problem, it is just a matter of editing a text file to add or delete reflog entries, but it is a bigger ordeal to alter the history graph itself.


Also, do not forget git reflog exists which can check if a ref has a reflog.

With Git 2.36 (Q2 2022), "git reflog"(man) command now uses parse-options API to parse its command line options.

See commit 840344d (28 Mar 2022) by SZEDER Gábor (szeder).
See commit fbc15b1, commit e3c3675, commit a34393f, commit cbe4852, commit 1e91d3f, commit d3ab1a5, commit 5f9b64a, commit 03df6cb (17 Mar 2022) by Ævar Arnfjörð Bjarmason (avar).
(Merged by Junio C Hamano -- gitster -- in commit 3ff8cbf, 04 Apr 2022)

reflog exists: use parse_options() API

Signed-off-by: Ævar Arnfjörð Bjarmason

Change the "reflog exists" command added in afcb2e7 ("git-reflog: add exists command", 2015-07-21, Git v2.6.0-rc0 -- merge listed in batch #1) to use parse_options() instead of its own custom command-line parser.
This continues work started in 33d7bdd (builtin/reflog.c: use parse-options api for expire, 2022-01-06, Git v2.36.0 -- merge listed in batch #1) (builtin/reflog.c: use parse-options api for expire, delete subcommands, 2022-01-06).

As a result we'll understand the --end-of-options synonym for --, so let's test for that.

0

精彩评论

暂无评论...
验证码 换一张
取 消