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

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


当前回答

对于您的整体问题,我有一个解决方案(确定feature是否源自develop的提示),但使用您概述的方法它不起作用。

你可以使用git branch——contains列出所有来自develop提示的分支,然后使用grep确保feature在其中。

git branch --contains develop | grep "^ *feature$"

如果是其中之一,则将“feature”打印到标准输出,并返回代码为0。否则,它将不打印任何东西,并有一个返回代码1。

其他回答

当我做了像develop→release-v1.0.0→feature-foo这样的事情时,这对我来说并不管用。它会一直往回发展。请注意,其中涉及到一个基数调整,我不确定这是否加剧了我的问题……

下面给出了正确的提交哈希值:

git log --decorate \
  | grep 'commit' \
  | grep 'origin/' \
  | head -n 2 \
  | tail -n 1 \
  | awk '{ print $2 }' \
  | tr -d "\n"

转到父级

您可以直接执行命令

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。

就是这样!

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来输出。

我不喜欢解析半结构化文本输出时涉及的不安全假设,所以我想要一个更健壮的解决方案,假设更少:

# Search backwards in history for the first commit that is in a branch other than $1
# and output that branch's name.
parent_branch() {
    local result rev child_branch=$1
    rev=$(git rev-parse --revs-only $child_branch)
    while [[ -n $rev ]]; do
        result=$(git branch --contains $rev | grep -v " $child_branch$")
        if [[ -n $result ]]; then
            echo $result
            return 0
        fi
        rev=$(git rev-parse --revs-only $rev^)
    done
    return 1
}

注意:由于这是在历史上进行迭代,查看每个提交以找到第一个在不同分支中而不是$1的提交,因此分支越长,它的开销就越高。但是,由于通常情况下分支应该是相对短暂的,所以这不应该是一个太大的问题。

还要注意,我使用的是git branch——contains,所以这也会找到共享公共基础但已经超越它的分支。要只找到确切指向公共基的分支,请使用git branch——points-at。

一个解决方案

基于git show-branch的解决方案不太适合我(见下文),所以我把它与基于git log的解决方案结合起来,最终得到了这个:

git log --decorate --simplify-by-decoration --oneline \ # selects only commits with a branch or tag
      | grep -v "(HEAD" \                               # removes current head (and branch)
      | head -n1 \                                      # selects only the closest decoration
      | sed 's/.* (\(.*\)) .*/\1/' \                    # filters out everything but decorations
      | sed 's/\(.*\), .*/\1/' \                        # picks only the first decoration
      | sed 's/origin\///'                              # strips "origin/" from the decoration

限制和注意事项

可以分离HEAD(许多CI工具这样做是为了确保它们在给定的分支中构建正确的提交),但是起源分支和本地分支都必须与当前HEAD相同或“高于”当前HEAD。 不能有标签挡道(我想;我还没有测试在子分支和父分支之间使用标签提交的脚本) 脚本依赖于“HEAD”总是被log命令列为第一个装饰的事实 在master上运行脚本并开发结果(大部分)在<SHA>初始提交

结果

 A---B---D---E---F <-origin/master, master
      \      \
       \      \
        \      G---H---I <- origin/hotfix, hotfix
         \
          \
           J---K---L <-origin/develop, develop
                \
                 \
                  M---N---O <-origin/feature/a, feature/a
                       \   \
                        \   \
                         \   P---Q---R <-origin/feature/b, feature/b
                          \
                           \
                            S---T---U <-origin/feature/c, feature/c

尽管存在本地分支(例如,只有origin/topic存在,因为提交O是由它的SHA直接签出的),脚本应该打印如下:

对于提交G, H, I(分支热修复)→master 对于提交M, N, O(分支特征/a)→开发 对于提交S, T, U(分支功能/c)→开发 对于提交P, Q, R(分支特征/b)→特征/a 对于提交J, K, L(分支开发)→<sha>初次提交* 对于提交B, D, E, F (master分支)→<sha>初次提交

* -或者master,如果develop的提交在master的HEAD的顶部(~ master将是快速前进的开发)


为什么show-branch对我没用

基于git show-branch的解决方案在以下情况下被证明是不可靠的:

分离头-包括分离头情况意味着替换grep '\*' \为' grep '!——而这只是所有麻烦的开始 在master和develop上运行脚本的结果分别为develop和' ' 主分支(hotfix/ Branches)上的分支最终以develop作为父分支,因为它们最近的主分支父分支被标记为!而不是*是有原因的。