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

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


当前回答

在多个分支中寻找第一次提交的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

其他回答

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,使列显示为-,而不是* 提交消息包含一个分支名称 提交消息包含*

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

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

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

git log -2 --pretty=format:'%d' --abbrev-commit | tail -n 1 | sed 's/\s(//g; s/,/\n/g';

(来源/母体名,母体名)

git log -2 --pretty=format:'%d' --abbrev-commit | tail -n 1 | sed 's/\s(//g; s/,/\n/g';

起源- parent-name

git log -2 --pretty=format:'%d' --abbrev-commit | tail -n 1 | sed 's/(.*,//g; s/)//';

母体名

在多个分支中寻找第一次提交的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

请记住,正如“Git:查找一个提交来自哪个分支”中所描述的那样,你不能轻易地确定提交是在哪个分支进行的(分支可以重命名、移动、删除……),即使Git branch——contains <commit>是一个开始。

你可以从commit返回到commit,直到git分支——contains <commit>没有列出feature分支和development分支, 比较提交SHA1到/refs/heads/develop

如果两个提交id匹配,就可以继续了(这意味着特性分支的起源在develop的HEAD)。