让我们假设我有以下本地存储库和一个像这样的提交树:

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

Master是我的,这是最新的稳定发布代码,develop是我的,这是“下一个”发布代码,feature是一个正在准备开发的新功能。

使用钩子,我希望能够拒绝推送功能到我的远程存储库,除非commit f是develop HEAD的直接后代。也就是说,提交树看起来是这样的,因为feature已经基于d。

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

那么是否有可能:

识别特征的父分支? 确定父分支中的提交f是哪个分支的后代?

从那里,我将检查父分支的HEAD是什么,并查看f前任是否匹配父分支HEAD,以确定该特性是否需要重基。


当前回答

假设远程存储库有一个开发分支的副本(您最初的描述在本地存储库中描述了它,但听起来它也存在于远程存储库中),您应该能够实现我认为您想要的东西,但是方法与您预想的有点不同。

Git的历史记录基于提交的DAG。分支(和一般的“引用”)只是在不断增长的提交DAG中指向特定提交的临时标签。因此,分支之间的关系可以随时间变化,但提交之间的关系不会。

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

看起来baz是基于(旧版的)bar?但是如果我们删除bar呢?

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

现在看起来baz是基于foo的。但是巴兹的祖先并没有改变。我们只是删除了一个标签(以及由此产生的悬空提交)。如果我们在4处添加一个新标签呢?

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

现在看来baz是基于quux的。尽管如此,祖先并没有改变,只是标签变了。

然而,如果我们问“commit 6是commit 3的后代吗?”(假设3和6是完整的SHA-1提交名称),那么无论bar和quux标签是否存在,答案都是“yes”。

因此,您可以问这样的问题:“推送的提交是开发分支当前尖端的后代吗?”,但你不能可靠地问“推送的提交的父分支是什么?”。

一个似乎接近你想要的最可靠的问题是:

对于所有被推送的提交的祖先(不包括develop的当前尖端及其祖先),它们以develop的当前尖端为父: 是否至少存在一个这样的提交? 所有这样的提交都是单亲提交吗?

可以实现为:

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

这将涵盖您想要限制的一些内容,但可能不是所有内容。

作为参考,这里是一个扩展的例子历史:

    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

上面的代码可以在接受H'、J、K或N的同时拒绝手牌S,但它也可以接受L和P(它们涉及合并,但它们不合并develop的尖端)。

要拒绝L和P,你可以改变问题,然后问

对于所有被推送的提交的祖先(不包括develop的当前尖端及其祖先): 有双亲的提交吗? 如果不是,是否至少有一个这样的提交有开发其父(唯一)的当前提示?

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

其他回答

这对我来说很有效:

git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'

来自droidbot和@ jstanidiot的礼貌评论和回答。

这是我的PowerShell版本:

function Get-GHAParentBranch {
    [CmdletBinding()]
    param(
        $Name = (git branch --show-current)
    )
    git show-branch |
      Select-String '^[^\[]*\*' |
      Select-String -NotMatch -Pattern "\[$([Regex]::Escape($Name)).*?\]" |
      Select-Object -First 1 |
      Foreach-Object {$PSItem -replace '^.+?\[(.+)\].+$','$1'}
}

现在任何人都想这样做——Atlassian的Sourcetree应用程序向你展示了你的分支如何相互关联的一个很好的可视化表示,即它们开始的位置和它们目前在提交顺序中的位置(例如,HEAD或4个提交后等)。

我并不是说这是解决问题的好方法,但这似乎对我来说确实有效:

git branch --contains $(cat .git/ORIG_HEAD)

问题是,隐藏一个文件是窥探Git的内部工作,所以这并不一定是向前兼容(或向后兼容)。

Use:

vbc=$(git rev-parse --abbrev-ref HEAD)
vbc_col=$(( $(git show-branch | grep '^[^\[]*\*' | head -1 | cut -d* -f1 | wc -c) - 1 )) 
swimming_lane_start_row=$(( $(git show-branch | grep -n "^[\-]*$" | cut -d: -f1) + 1 )) 
git show-branch | tail -n +$swimming_lane_start_row | grep -v "^[^\[]*\[$vbc" | grep "^.\{$vbc_col\}[^ ]" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'

它达到了与Mark Reed的答案相同的目的,但它使用了一种更安全的方法,在许多情况下都不会表现不当:

父分支的最后一次提交是一个merge,使列显示为-,而不是* 提交消息包含一个分支名称 提交消息包含*