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

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


当前回答

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

其他回答

在多个分支中寻找第一次提交的shell函数:

# Get the first commit hash of a given branch.
# Uses `git branch --contains` to backward (starts from HEAD) check each commits
# and output that branch's name.
first_commit_of_branch() {
    if [ $# -ne 1 ] || [ -z "${1}" ] ; then
        (>&2 echo "Error: Missing or empty branch name.")
        (>&2 echo "Usage: $0 branch_to_test")
        return 2
    fi
    local branch_to_test="${1}"; shift
    local commit_index_to_test
    local maximum_number_of_commit_to_test
    local branch_count_having_tested_commit

    git rev-parse --verify --quiet "${branch_to_test}" 2>&1 > /dev/null || {
        (>&2 echo "Error: Branch \"${branch_to_test}\" does not exists.")
        return 2
    }

    commit_index_to_test=0
    maximum_number_of_commit_to_test=$(git rev-list --count "${branch_to_test}")

    while [ ${commit_index_to_test} -le ${maximum_number_of_commit_to_test} ] ; do
        # Testing commit $branch_to_test~$commit_index_to_test…

        # If it fails, it means we tested all commits from the most recent of
        # branch $branch_to_test to the very first of the git DAG. So it must be it.
        git rev-parse --verify --quiet ${branch_to_test}~${commit_index_to_test} 2>&1 > /dev/null || {
            git rev-list --max-parents=0 "${branch_to_test}"
            return 0
        }

        branch_count_having_tested_commit="$( \
            git --no-pager branch --no-abbrev --verbose \
                --contains ${branch_to_test}~${commit_index_to_test} \
            | cut -c 3- \
            | cut -d ' ' -f 2 \
            | wc -l \
        )"

        # Tested commit found in more than one branch
        if [ ${branch_count_having_tested_commit} -gt 1 ] ; then
            if [ ${commit_index_to_test} -eq 0 ]; then
                (>&2 echo "Error: The most recent commit of branch \"${branch_to_test}\" (${branch_to_test}~${commit_index_to_test}) is already in more than one branch. This is likely a new branch without any commit (yet). Cannot continue.")
                return 1
            else
                # Commit $branch_to_test~$commit_index_to_test is in more than
                # one branch, stopping there…
                git rev-parse ${branch_to_test}~$((commit_index_to_test-1))
                return 0
            fi
        fi
        # else: Commit $branch_to_test~$commit_index_to_test is still only in
        #       branch ${branch_to_test} continuing…"
        commit_index_to_test=$((commit_index_to_test+1))
    done
}

警告:当在一个有子分支的分支上执行并且没有新的提交时,它会失败。

A---B---C---D      <- "main" branch
 \   \
  \   E---F        <- "work1" branch
   \       \
    \       G---H  <- "work1-b" branch
     \
      I---J        <- "work2" branch
first_commit_of_branch main # C
first_commit_of_branch work1 # (Fails)
first_commit_of_branch work1-b # G
first_commit_of_branch work2 # I

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

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

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

当我做了像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日志中的最新归并:

#!/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