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

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日志中的最新归并:

#!/bin/bash
git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10

把它放在一个名为git-last-merge的脚本中,该脚本也接受一个分支名称作为参数(而不是当前分支)以及其他git日志参数。

从输出中,我们可以根据自己的分支约定和每个分支的合并数量手动检测父分支。

如果你经常在子分支上使用git rebase(合并通常是快进的,所以没有太多的合并提交),这个答案不会很好地工作,所以我写了一个脚本来计算所有分支与当前分支相比的前提交(正常和合并)和后提交(在父分支中不应该有任何后合并)。

#!/bin/bash
HEAD="`git rev-parse --abbrev-ref HEAD`"
echo "Comparing to $HEAD"
printf "%12s  %12s   %10s     %s\n" "Behind" "BehindMerge" "Ahead" "Branch"
git branch | grep -v '^*' | sed 's/^\* //g' | while read branch ; do
    ahead_merge_count=`git log --oneline --merges $branch ^$HEAD | wc -l`
    if [[ $ahead_merge_count != 0 ]] ; then
        continue
    fi
    ahead_count=`git log --oneline --no-merges $branch ^$HEAD | wc -l`
    behind_count=`git log --oneline --no-merges ^$branch $HEAD | wc -l`
    behind_merge_count=`git log --oneline --merges ^$branch $HEAD | wc -l`
    behind="-$behind_count"
    behind_merge="-M$behind_merge_count"
    ahead="+$ahead_count"
    printf "%12s  %12s   %10s     %s\n" "$behind" "$behind_merge" "$ahead" "$branch"
done | sort -n

其他回答

使用Ant进行跨平台实现

    <exec executable="git" outputproperty="currentBranch">
        <arg value="rev-parse" />  
        <arg value="--abbrev-ref" />  
        <arg value="HEAD" />  
    </exec>

    <exec executable="git" outputproperty="showBranchOutput">
        <arg value="show-branch" />  
        <arg value="-a" />  
    </exec>

    <loadresource property="baseBranch">
      <propertyresource name="showBranchOutput"/>
          <filterchain>
            <linecontains>
              <contains value="*"/>
            </linecontains>
            <linecontains negate="true">
              <contains value="${currentBranch}"/>
            </linecontains>
            <headfilter lines="1"/>
            <tokenfilter>
                <replaceregex pattern=".*\[(.*)\].*" replace="\1"/>
                <replaceregex pattern="[\^~].*" replace=""/>
            </tokenfilter>
          </filterchain>
    </loadresource>

    <echo message="${currentBranch} ${baseBranch}" />

Git附带了几个GUI客户端,可以帮助您可视化这些内容。打开GitGUI,进入菜单Repository→可视化所有分支历史。

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

一个rephrasal

这个问题的另一种表达方式是“驻留在当前分支以外的分支上的最近的提交是什么?是哪个分支?”

一个解决方案

您可以使用一点命令行魔法找到它

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

AWK:

git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/[^\[]*//' \
| awk 'match($0, /\[[a-zA-Z0-9\/.-]+\]/) { print substr( $0, RSTART+1, RLENGTH-2 )}'

下面是它的工作原理:

Display a textual history of all commits, including remote branches. Ancestors of the current commit are indicated by a star. Filter out everything else. Ignore all the commits in the current branch. The first result will be the nearest ancestor branch. Ignore the other results. Branch names are displayed [in brackets]. Ignore everything outside the brackets, and the brackets. Sometimes the branch name will include a ~# or ^# to indicate how many commits are between the referenced commit and the branch tip. We don't care. Ignore them.

结果是

运行上面的代码

 A---B---D <-master
      \
       \
        C---E---I <-develop
             \
              \
               F---G---H <-topic

如果你从H运行它会给你发展,如果你从I运行它会给你掌握。

代码可以作为要点提供。

一个解决方案

基于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作为父分支,因为它们最近的主分支父分支被标记为!而不是*是有原因的。