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

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

一个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运行它会给你掌握。

代码可以作为要点提供。

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

# 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。

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