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

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,以确定该特性是否需要重基。


当前回答

下面是Mark Reed解决方案的PowerShell实现:

git show-branch -a | where-object { $_.Contains('*') -eq $true} | Where-object {$_.Contains($branchName) -ne $true } | select -first 1 | % {$_ -replace('.*\[(.*)\].*','$1')} | % { $_ -replace('[\^~].*','') }

其他回答

转到父级

您可以直接执行命令

git parent

如果您添加Joe Chrysler的答案作为Git别名,就可以找到分支的父分支。它将简化使用。

打开位于“~/”的gitconfig文件。使用任何文本编辑器(适用于Linux)。而对于Windows的“。gitconfig路径通常位于C:\users\your-user\.gitconfig。

vim  ~/.gitconfig

在文件中添加如下alias命令:

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

保存并退出编辑器。

执行命令git parent。

就是这样!

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

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

这是我的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'}
}

你也可以试试:

git log --graph --decorate

Joe Chrysler的命令行魔法可以简化。下面是Joe的逻辑——为了简洁起见,我在两个版本中都引入了一个名为cur_branch的参数来代替命令替换' git rev-parse——abbrev-ref HEAD ';可以像这样初始化:

cur_branch=$(git rev-parse --abbrev-ref HEAD)

然后,这是Joe的管道:

git show-branch -a           |
  grep '\*'                  | # we want only lines that contain an asterisk
  grep -v "$cur_branch"      | # but also don't contain the current branch
  head -n1                   | # and only the first such line
  sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between []
  sed 's/[\^~].*//'            # and with any relative refs (^, ~n) removed

我们可以在一个相对简单的awk命令中完成与所有这五个单独的命令过滤器相同的事情:

git show-branch -a |
  awk -F'[]^~[]' '/\*/ && !/'"$cur_branch"'/ {print $2;exit}'

具体情况是这样的:

-F'[]^~[]'

将行分割为以]、^、~和[为字符的字段。

/\*/

找出包含星号的行

&& !/'"$cur_branch"'/

...但不是当前的分支名称

{ print $2;

当您找到这样的一行时,打印它的第二个字段(即字段分隔符第一次和第二次出现之间的部分)。对于简单的分支名称,这将是括号之间的内容;对于具有相对跳转的引用,它将只是没有修饰符的名称。因此,我们的字段分隔符集处理了两个sed命令的意图。

  exit }

然后立即退出。这意味着它只处理第一个匹配的行,所以我们不需要通过head -n 1来输出。