【问题标题】:How to find the nearest parent of a Git branch如何找到最近的 Git 分支的父级
【发布时间】:2011-03-10 19:49:08
【问题描述】:

假设我有以下本地存储库,其提交树如下:

master --> a
            \
             \
      develop c --> d
               \
                \
         feature f --> g --> h

master 是我的这是最新的稳定版本代码develop 是我的这是“下一个”版本代码feature 是 正在为develop准备一个新功能。

使用钩子,我希望能够拒绝将feature 推送到我的远程存储库,除非提交fdevelop HEAD 的直接后代。即,提交树看起来像这样,因为功能已经在 d 上为 git rebase

master --> a
            \
             \
      develop c --> d
                     \
                      \
               feature f --> g --> h

那么有没有可能:

  • 标识feature的父分支?
  • 确定父分支中f 是其后代的提交?

从那里我会检查父分支的 HEAD 是什么,并查看 f 前任是否与父分支 HEAD 匹配,以确定是否需要重新设置该功能。

【问题讨论】:

  • 这个问题应该改写为找到父母的父母。
  • 一般情况下,我使用git log --first-parent,它会显示当前分支的所有提交,之后会显示父分支及其提交

标签: git branch


【解决方案1】:

假设远程存储库有一个 develop 分支的副本(您最初的描述是在本地存储库中描述它,但听起来它也存在于远程存储库中),您应该能够实现我认为你想要的,但方法与你预想的有点不同。

Git 的历史基于DAG 的提交。分支(和一般的“引用”)只是指向不断增长的提交 DAG 中特定提交的临时标签。因此,分支之间的关系会随着时间而变化,但提交之间的关系不会。

    ---o---1                foo
            \
             2---3---o      bar
                  \
                   4
                    \
                     5---6  baz

看起来baz 是基于bar 的(旧版本)?但是如果我们删除bar呢?

    ---o---1                foo
            \
             2---3
                  \
                   4
                    \
                     5---6  baz

现在看起来baz 是基于foo。但baz的血统并没有改变。我们刚刚删除了一个标签(以及由此产生的悬空提交)。如果我们在4 添加一个新标签呢?

    ---o---1                foo
            \
             2---3
                  \
                   4        quux
                    \
                     5---6  baz

现在看起来baz 是基于quux。不过,血统没有变,只是标签变了。

但是,如果我们问的是“commit 6 是 commit 3 的后代吗?” (假设36 是完整的SHA-1 提交名称),那么无论barquux 标签是否存在,答案都是“是”。

所以,你可以问诸如“推送的提交是 develop 分支的当前提示的后代吗?”,但你不能可靠地问“推送的父分支是什么?提交?”。

一个似乎接近你想要的最可靠的问题是:

对于所有推送的提交的祖先(不包括 develop 的当前提示及其祖先),以 develop 的当前提示作为父项:

  • 至少存在一个这样的提交吗?
  • 所有此类提交都是单亲提交吗?

可以这样实现:

pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_children_of_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
    ,)     echo "must descend from tip of '$basename'"
           exit 1 ;;
    ,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
           exit 1 ;;
    ,*)    exit 0 ;;
esac

这将涵盖您想要限制的一些内容,但可能不是全部。

作为参考,这里是一个扩展的示例历史:

    A                                   master
     \
      \                    o-----J
       \                  /       \
        \                | o---K---L
         \               |/
          C--------------D              develop
           \             |\
            F---G---H    | F'--G'--H'
                    |    |\
                    |    | o---o---o---N
                     \   \      \       \
                      \   \      o---o---P
                       \   \
                        R---S

上述代码可用于拒绝HS,同时接受H'JKN,但它也可以接受LP(它们涉及合并,但它们不合并 develop 的提示)。

要同时拒绝LP,你可以换个问题问

对于所有推送的提交的祖先(不包括 develop 的当前提示及其祖先):

  • 是否有两个父母的任何提交?
  • 如果没有,是否至少有一个这样的提交具有 develop 其(唯一)父级的当前提示?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
    echo "'$basename' is missing, call for help!"
    exit 1
fi
parents_of_commits_beyond_base="$(
  git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
  grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
    *\ *)          echo "must not push merge commits (rebase instead)"
                   exit 1 ;;
    *"$baserev"*)  exit 0 ;;
    *)             echo "must descend from tip of '$basename'"
                   exit 1 ;;
esac

【讨论】:

  • 我明白了:git : fatal: ambiguous argument '...': 修订版和文件名。三点的意图是什么?
  • @Schneider 我很确定 '...' 在此示例中用作占位符:如果您将其替换为您尝试执行此检查的提交的 SHA (例如,您当前所在分支的 HEAD),一切正常。
  • 感谢您的详尽回答!它非常有用。我想做一个类似的钩子,但我不想硬编码开发分支的名称。意思是我想要一个钩子来防止变基到父分支以外的分支。如果我很好地理解了您的答案(我是 bash 和其他东西的新手),那么您的答案中没有涵盖,对吧?有没有办法做到这一点?
  • 我很欣赏拿着旗帜(或切肉刀)的霸王龙历史图表
  • @GrahamRussell:它在几年前的较早评论中被称为 velociraptor,但该评论似乎已经被删除了一段时间。
【解决方案2】:

改写

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

解决方案

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

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 )}'

它的工作原理如下:

  1. 显示所有提交的文本历史记录,包括远程分支。
  2. 当前提交的祖先用星号表示。过滤掉其他所有内容。
  3. 忽略当前分支中的所有提交。
  4. 第一个结果将是最近的祖先分支。忽略其他结果。
  5. 分支名称显示在[括号中]。忽略括号和括号之外的所有内容。
  6. 有时分支名称将包含~#^# 以指示在引用的提交和分支提示之间有多少提交。我们不在乎。忽略它们。

以及结果

运行上面的代码

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

如果你从 H 运行它会给你develop,如果你从 I 运行它会给你master

The code is available as a gist.

【讨论】:

  • 删除了导致错误的尾随反引号。但是,在运行此命令时,我收到大量警告,抱怨每个分支都说 cannot handle more than 25 refs
  • @JoeChrysler 你认为你可以让它成为一行而不是 2 行,并且可能让它在 Mac 上工作,因为ack 在 Mac 上不可用(有人建议用 grep 替换 ack )
  • 抱歉,打错了。这是对我有用的正确方法:git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'
  • @droidbot 不错,但需要重新排序以避免在 grep -v 捕获提交消息或您的分支名称是另一个分支名称的一部分时删除引用。 git show-branch | sed "s/].*//" | grep "\*" | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed "s/^.*\[//"
  • @OlegAbrazhaev 我不知道您是否回答了您的问题。使用 git 别名:parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\\[\\(.*\\)\\].*/\\1/' | sed 's/[\\^~].*//' #" 为我工作
【解决方案3】:

Git 父级

你可以直接运行命令

git parent

如果您将Joe Chrysler's answer 添加为Git 别名,则查找分支的父级。这将简化使用。

使用任何文本编辑器(适用于 Linux)打开位于 "~/.gitconfig"gitconfig 文件。对于 Windows,“.gitconfig”路径通常位于 C:\users\your-user\.gitconfig

vim  ~/.gitconfig

在文件中添加以下别名命令:

[alias]
    parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\\[\\(.*\\)\\].*/\\1/' | sed 's/[\\^~].*//' #"

保存并退出编辑器。

运行命令git parent

就是这样!

【讨论】:

  • 这是一个很好的解决方案。添加一些示例输出以确保获得预期结果会很有帮助。当我运行它时,我在最后一行之前收到了一些警告,我认为这是父分支的名称。
  • 像魅力一样工作!对于 Windows 用户,.gitconfig 通常位于 c:\users\your-user\.gitconfig
  • 遇到cannot handle more than 25 refs 异常。
  • 有人可以编辑这个来处理警告吗? @temple,你可以吗?
  • @NIKHILCM 像冠军一样工作。但是我在这里有一个问题,父级是否指示分支创建的位置或其他?
【解决方案4】:

你也可以试试:

git log --graph --decorate

【讨论】:

  • git log --graph --decorate --simplify-by-decoration 其中--graph 是可选的。
  • git log --graph --decorate --simplify-by-decoration --oneline
  • git log --decorate=full --simplify-by-decoration --oneline --format="%D"
【解决方案5】:

我对您的整体问题有一个解决方案(确定feature 是否从develop 的尖端继承),但使用您概述的方法不起作用。

您可以使用git branch --contains 列出从develop 的尖端派生的所有分支,然后使用grep 确保feature 在其中。

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

如果在其中,则将" feature"打印到标准输出,返回码为0。否则,什么也不打印,返回码为1。

【讨论】:

  • 这行得通,但应该注意的是,在具有大量 refs 的存储库上可能需要很长时间。这使得它不太适合运行,例如预接收钩子。
  • 我正在寻找分支,我们将其称为&lt;branch&gt;,我在哪里表演:git checkout -b &lt;branch-2&gt; 来自...这就是答案!真的不需要grep。 git branch --contains &lt;branch&gt;
  • 这是寻找孩子的答案
【解决方案6】:

这对我来说很好用:

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

礼貌的评论和回答from droidbot 和@Jistanidiot。

【讨论】:

  • 是的,但有时它会让你从 grep 中“断掉管道”。
  • * 不是传递给 grep 的正确正则表达式。应该改用grep -F '*'grep '\*'。否则很好的解决方案。
  • 我没有输出。
  • 没有用户“Jistanidiot”的内容痕迹。它指的是什么?
  • 我收到了很多cannot handle more than 26 refs
【解决方案7】:

解决方案

based on git show-branch 的解决方案对我来说不是很有效(见下文),所以我将它与based on 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 工具这样做是为了确保它们在给定分支中构建正确的提交),但 origin 分支和本地分支 必须同时 在 par 或 "above 以上" 当前的 HEAD。
  • 必须有无标签(我想;我没有在子分支和父分支之间带有标签的提交测试脚本)
  • 脚本依赖于 "HEAD" 始终被log 命令列为第一个装饰这一事实
  • 运行脚本masterdevelop 上结果(大部分)在&lt;SHA&gt; Initial commit

结果

 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(分支hotfix)→ master
  • 对于提交 M, N, O(分支feature/a)→ develop
  • 对于提交 S, T, U(分支feature/c)→ develop
  • 对于提交 P, Q, R(分支feature/b)→ feature/a
  • 对于提交JKL(分支develop)→&lt;sha&gt; Initial commit*
  • 对于提交BDEF(分支master)→&lt;sha&gt; Initial commit

* - 或master 如果develop 的提交位于master 的HEAD 之上(~master 可以快速开发)


为什么 show-branch 不适合我

解决方案based on git show-branch 在以下情况下对我来说是不可靠的:

  • 分离的 HEAD – 包括分离的头部外壳意味着将 grep '\*' \ 替换为 `grep '!' \——这只是所有麻烦的开始
  • 运行脚本masterdevelop上分别产生develop和``
  • master 分支上的分支(hotfix/ 分支)最终以 develop 作为父级,因为它们最近的 master 分支父级被标记为 ! 而不是 *原因。

【讨论】:

  • 唯一有效的答案 - 作为 git 别名:"!git log --decorate --simplify-by-decoration --oneline | grep -v '(HEAD' | head -n1 | sed 's/.* (\\(.*\\)) .*/\\1/' | sed 's/\\(.*\\), .*/\\1/' | sed 's/origin\\///'"
  • 这个命令对我有用 git log --decorate --simplify-by-decoration --oneline | grep -v "(HEAD" | head -n1 | sed 's/.* ((.*)) .*/\1/' | sed 's/(.*), .*/\1/' | sed 's/原点\///'
  • 这应该是公认的答案:/
【解决方案8】:

由于以前的答案都不适用于我们的存储库,我想分享我自己的方式,使用 git log 中的最新合并:

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

将其放入名为 git-last-merges 的脚本中,该脚本还接受分支名称作为参数(而不是当前分支)以及其他 git log 参数。

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

如果您经常在子分支上使用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

【讨论】:

  • 谢谢。尽管如果您经常使用rebase(并且合并经常使用fast-forwarded),这可能效果不佳。如果我找到更好的解决方案,我会编辑我的答案。
  • 唯一的?到目前为止,在当前分支是主分支的情况下,答案对我来说是明智的。大多数其他解决方案在没有实际父分支的这种公认的边缘情况下给出了随机(并且明显不正确)的结果。
  • 这是唯一对我有用的答案。要获取第一个父母而不是前 10 个的列表,您可以使用:git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 1 | cut -d ' ' -f 8
【解决方案9】:

接下来会发生什么

要点:

为什么会有人想阅读这篇长文? 因为虽然以前的答案很清楚 理解原始问题的问题, 他们没有得到正确/有意义的结果; 或准确地解决一个不同的问题。

请随意查看第一部分; 它解决了“找东西”的问题, 应该突出问题的范围。 对于某些人来说,这可能就足够了。

这将向您展示一种方法 从 git 中提取正确且有意义的结果 (你可能不喜欢它们), 并展示一种申请方式 你对惯例的了解 对那些结果 提取您真正需要的内容。

封面以下部分:

  • 一个公正的问题和解决方案
    • 最近的git分支使用git show-branch
    • 预期结果应该是什么样子
  • 示例图表和结果
  • 批处理分支:解决git show-branch 的限制
  • 一个有偏见的问题和解决方案: 引入(命名)约定以改进结果

问题的问题

如前所述,git 不跟踪分支之间的关系; 分支只是引用提交的名称。 在官方 git 文档和其他来源中,我们经常会遇到一些误导性的图表,例如:

A---B---C---D    <- master branch
     \
      E---F      <- work branch

让我们更改图表的形式和分层提示名称以显示等效图表:

      E---F   <- jack
     /
A---B
     \
      C---D   <- jill

图表(因此是 git)绝对没有告诉我们哪个分支是首先创建的(因此,哪个分支是从另一个分支出来的)。

第一个图中masterwork 的父级是约定俗成的问题。

因此

  • 简单的工具会产生忽略偏差的响应
  • 更复杂的工具包含惯例(偏差)。

一个公正的问题

首先,我必须首先承认 Joe Chrysler 的回应、这里的其他回应以及周围的许多 cmets/建议; 他们启发了我并为我指明了方向!

请允许我重新表述 Joe 的表述,考虑到与最近提交相关的多个分支(它发生了!):

"驻留在除 当前分支,那是哪个分支?”

或者,换句话说:

第一季度

给定一个分支B: 考虑最接近B'HEAD 的提交CC 可以是 B'HEAD) 由其他分支共享: 除了B,还有哪些分支的提交历史中有C

公正的解决方案

先道歉;似乎人们更喜欢单线。随意提出(可读/可维护)改进建议!

#!/usr/local/bin/bash

# git show-branch supports 29 branches; reserve 1 for current branch
GIT_SHOW_BRANCH_MAX=28

CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
if (( $? != 0 )); then
    echo "Failed to determine git branch; is this a git repo?" >&2
    exit 1
fi


##
# Given Params:
#   EXCEPT : $1
#   VALUES : $2..N
#
# Return all values except EXCEPT, in order.
#
function valuesExcept() {
    local except=$1 ; shift
    for value in "$@"; do
        if [[ "$value" != "$except" ]]; then
            echo $value
        fi
    done
}


##
# Given Params:
#   BASE_BRANCH : $1           : base branch; default is current branch
#   BRANCHES    : [ $2 .. $N ] : list of unique branch names (no duplicates);
#                                perhaps possible parents.
#                                Default is all branches except base branch.
#
# For the most recent commit in the commit history for BASE_BRANCH that is
# also in the commit history of at least one branch in BRANCHES: output all
# BRANCHES that share that commit in their commit history.
#
function nearestCommonBranches() {
    local BASE_BRANCH
    if [[ -z "${1+x}" || "$1" == '.' ]]; then
        BASE_BRANCH="$CURRENT_BRANCH"
    else
        BASE_BRANCH="$1"
    fi

    shift
    local -a CANDIDATES
    if [[ -z "${1+x}" ]]; then
        CANDIDATES=( $(git rev-parse --symbolic --branches) )
    else
        CANDIDATES=("$@")
    fi
    local BRANCHES=( $(valuesExcept "$BASE_BRANCH" "${CANDIDATES[@]}") )

    local BRANCH_COUNT=${#BRANCHES[@]}
    if (( $BRANCH_COUNT > $GIT_SHOW_BRANCH_MAX )); then
        echo "Too many branches: limit $GIT_SHOW_BRANCH_MAX" >&2
        exit 1
    fi

    local MAP=( $(git show-branch --topo-order "${BRANCHES[@]}" "$BASE_BRANCH" \
                    | tail -n +$(($BRANCH_COUNT+3)) \
                    | sed "s/ \[.*$//" \
                    | sed "s/ /_/g" \
                    | sed "s/*/+/g" \
                    | egrep '^_*[^_].*[^_]$' \
                    | head -n1 \
                    | sed 's/\(.\)/\1\n/g'
          ) )

    for idx in "${!BRANCHES[@]}"; do
        ## to include "merge", symbolized by '-', use
        ## ALT: if [[ "${MAP[$idx]}" != "_" ]]
        if [[ "${MAP[$idx]}" == "+" ]]; then
            echo "${BRANCHES[$idx]}"
        fi
    done
}

# Usage: gitr [ baseBranch [branchToConsider]* ]
#   baseBranch: '.' (no quotes needed) corresponds to default current branch
#   branchToConsider* : list of unique branch names (no duplicates);
#                        perhaps possible (bias?) parents.
#                        Default is all branches except base branch.
nearestCommonBranches "${@}"

工作原理

考虑输出:git show-branch

对于git show-branch --topo-order feature/g hotfix master release/2 release/3 feature/d,输出类似于:

! [feature/g] TEAM-12345: create X
 * [hotfix] TEAM-12345: create G
  ! [master] TEAM-12345: create E
   ! [release/2] TEAM-12345: create C
    ! [release/3] TEAM-12345: create C
     ! [feature/d] TEAM-12345: create S
------
+      [feature/g] TEAM-12345: create X
+      [feature/g^] TEAM-12345: create W
     + [feature/d] TEAM-12345: create S
     + [feature/d^] TEAM-12345: create R
     + [feature/d~2] TEAM-12345: create Q
        ...
  +    [master] TEAM-12345: create E
 *     [hotfix] TEAM-12345: create G
 *     [hotfix^] TEAM-12345: create F
 *+    [master^] TEAM-12345: create D
+*+++  [release/2] TEAM-12345: create C
+*++++ [feature/d~8] TEAM-12345: create B

几点:

  • 原始命令在命令行上列出了 N (6) 个分支名称
  • 这些分支名称按顺序显示为输出的前 N ​​行
  • 标题后面的行代表提交
  • 提交行的前 N ​​列(作为一个整体)表示“分支/提交矩阵”,其中X 列中的单个字符表示之间的关系(或缺少)一个分支(标题行 X)和当前提交。

主要步骤

  1. 给定一个BASE_BRANCH
  2. 给定一个有序集(唯一)BRANCHES,不包括BASE_BRANCH
  3. 为简洁起见,让NBRANCH_COUNT, 这是BRANCHES的大小; 它不包括BASE_BRANCH
  4. git show-branch --topo-order $BRANCHES $BASE_BRANCH:
    • 由于BRANCHES 仅包含唯一名称(假定有效) 名称将映射 1-1 与输出的标题行, 并对应于分支/提交矩阵的前 N ​​列。
    • 由于BASE_BRANCH 不在BRANCHES 中 这将是标题行的最后一行, 并且对应于最后一列分支/提交矩阵。
  5. tail:以N+3行开头;丢弃第一个N+2 行:N 个分支 + 基本分支 + 分隔行 ---..
  6. sed:这些可以合二为一……但为了清楚起见分开
    • 删除分支/提交矩阵之后的所有内容
    • 用下划线'_'替换空格; 我的主要原因是避免潜在的 IFS 解析麻烦 以及用于调试/可读性。
    • *替换为+;基础分支总是在最后一列, 这就足够了。此外,如果不理会它会通过bash 路径名扩展,* 总是很有趣
  7. egrep:grep 用于映射到至少一个分支 ([^_]) 和 BASE_BRANCH ([^_]$) 的提交。也许那个基本分支模式应该是\+$
  8. head -n1: 进行剩余的第一个提交
  9. sed:将分支/提交矩阵的每个字符分隔为单独的行。
  10. 捕获数组MAP中的行,此时我们有两个数组:
    • BRANCHES:长度N
    • MAP:长度N+1:第一个N元素1-1和BRANCHES,最后一个元素对应BASE_BRANCH
  11. 遍历BRANCHES(这就是我们想要的,而且它更短)并检查MAP中的对应元素:如果MAP[$idx]+,则输出BRANCH[$idx]

示例图表和结果

考虑以下有点做作的示例图:

  • 将使用有偏见的名称,因为它们有助于(我)权衡和考虑结果。
  • 假定合并存在并且被忽略。
  • 该图通常会尝试突出显示分支(分叉), 没有在视觉上暗示偏好/层次结构; 讽刺的是,master 在我做完这件事后脱颖而出。
                         J                   <- feature/b
                        /
                       H
                      / \ 
                     /   I                   <- feature/a
                    /
                   D---E                     <- master
                  / \ 
                 /   F---G                   <- hotfix
                /
       A---B---C                             <- feature/f, release/2, release/3
            \   \ 
             \   W--X                        <- feature/g
              \ 
               \       M                     <- support/1
                \     /
                 K---L                       <- release/4
                      \ 
                       \       T---U---V     <- feature/e
                        \     /
                         N---O
                              \ 
                               P             <- feature/c
                                \ 
                                 Q---R---S   <- feature/d

示例图的无偏结果

假设脚本在可执行文件gitr中,则运行:

gitr <baseBranch>

对于不同的分支B我们得到如下结果:

GIVEN B Shared Commit C Branches !B with C in their history?
feature/a H feature/b
feature/b H feature/a
feature/c P feature/d
feature/d P feature/c
feature/e O feature/c, feature/d
feature/f C feature/a, feature/b, feature/g, hotfix, master, release/2, release/3
feature/g C feature/a, feature/b, feature/f, hotfix, master, release/2, release/3
hotfix D feature/a, feature/b, master
master D feature/a, feature/b, hotfix
release/2 C feature/a, feature/b, feature/f, feature/g, hotfix, master, release/3
release/3 C feature/a, feature/b, feature/f, feature/g, hotfix, master, release/2
release/4 L feature/c, feature/d, feature/e, support/1
support/1 L feature/c, feature/d, feature/e, release/4

批处理分支

[现阶段呈现 因为此时它最适合最终脚本。 此部分不是必需的,请随意跳过。]

git show-branch 将自身限制为 29 个分支。 这对某些人来说可能是一个障碍(没有判断力,只是说!)。

在某些情况下,我们可以改善结果, 通过将分支分组为批次。

  • BASE_BRANCH 必须与每个分支一起提交。
  • 如果repo中有大量分支 这本身的价值可能有限。
  • 如果您找到其他方法,可能会提供更多价值 限制分支(将被批处理)。
  • 上一点适合我的用例, 冲锋陷阵!

这个机制并不完美, 随着结果大小接近最大值(29), 预计它会失败。详情如下

批量解决方案

#
# Remove/comment-out the function call at the end of script,
# and append this to the end.
##

##
# Given:
#   BASE_BRANCH : $1           : first param on every batch
#   BRANCHES    : [ $2 .. $N ] : list of unique branch names (no duplicates);
#                                perhaps possible parents
#                                Default is all branches except base branch.
#
# Output all BRANCHES that share that commit in their commit history.
#
function repeatBatchingUntilStableResults() {
    local BASE_BRANCH="$1"

    shift
    local -a CANDIDATES
    if [[ -z "${1+x}" ]]; then
        CANDIDATES=( $(git rev-parse --symbolic --branches) )
    else
        CANDIDATES=("$@")
    fi
    local BRANCHES=( $(valuesExcept "$BASE_BRANCH" "${CANDIDATES[@]}") )

    local SIZE=$GIT_SHOW_BRANCH_MAX
    local COUNT=${#BRANCHES[@]}
    local LAST_COUNT=$(( $COUNT + 1 ))

    local NOT_DONE=1
    while (( $NOT_DONE && $COUNT < $LAST_COUNT )); do
        NOT_DONE=$(( $SIZE < $COUNT ))
        LAST_COUNT=$COUNT

        local -a BRANCHES_TO_BATCH=( "${BRANCHES[@]}" )
        local -a AGGREGATE=()
        while (( ${#BRANCHES_TO_BATCH[@]} > 0 )); do
            local -a BATCH=( "${BRANCHES_TO_BATCH[@]:0:$SIZE}" )
            AGGREGATE+=( $(nearestCommonBranches "$BASE_BRANCH" "${BATCH[@]}") )
            BRANCHES_TO_BATCH=( "${BRANCHES_TO_BATCH[@]:$SIZE}" )
        done
        BRANCHES=( "${AGGREGATE[@]}" )
        COUNT=${#BRANCHES[@]}
    done
    if (( ${#BRANCHES[@]} > $SIZE )); then
        echo "Unable to reduce candidate branches below MAX for git-show-branch" >&2
        echo "  Base Branch : $BASE_BRANCH" >&2
        echo "  MAX Branches: $SIZE" >&2
        echo "  Candidates  : ${BRANCHES[@]}" >&2
        exit 1
    fi
    echo "${BRANCHES[@]}"
}

repeatBatchingUntilStableResults "$@"
exit 0

工作原理

重复直到结果稳定

  1. BRANCHES拆分成批次 GIT_SHOW_BRANCH_MAX(又名SIZE)元素
  2. 致电nearestCommonBranches BASE_BRANCH BATCH
  3. 将结果聚合到一组新的(更小的?)分支中

怎么会失败

如果聚合分支数超过最大值SIZE 并且进一步的批处理/处理不能减少这个数字 然后要么:

  • 聚合的分支是解决方案, 但git show-branch 无法验证,或者
  • 每批不减; 可能一批中的一个分支将有助于减少另一个 (差异合并基础);当前算法承认失败并失败。

考虑替代方案

单独将一个基础分支与每个其他感兴趣的分支配对,为每一对确定一个提交节点(合并基础);按提交历史顺序对合并基集进行排序,获取最近的节点,确定与该节点关联的所有分支。

我是从后见之明的立场提出的。 这可能真的是正确的方法。 我在前进; 也许在当前主题之外还有其他价值。

一个有偏见的问题

你可能已经注意到核心函数nearestCommonBranches 在较早的脚本中,答案比 Q1 提出的问题更多。 事实上,该函数回答了一个更普遍的问题:

第二季度

给定一个分支B 和 一个有序集合(无重复)P 的分支(B 不在P 中): 考虑最接近B'HEAD 的提交CC 可能是B'HEAD) 由P 中的分支共享: 按照 P 的顺序,P 中的哪些分支的提交历史中有 C?

选择P 会提供偏见,或描述(有限的)约定。 为了匹配您的偏见/惯例的所有特征,可能需要额外的工具,这超出了本次讨论的范围。

为简单的偏差/惯例建模

偏见因不同的组织和实践而异, 以下内容可能不适合您的组织。 如果不出意外,也许这里的一些想法可能会有所帮助 您可以找到满足您需求的解决方案。

有偏见的解决方案;分支命名约定的偏差

也许偏差可以映射到,并从中提取, 使用的命名约定。

P(其他分支名称)的偏见

下一步我们将需要这个, 所以让我们看看我们可以通过正则表达式过滤分支名称来做什么。

合并以前的代码和下面的新代码可用作gist: gitr

#
# Remove/comment-out the function call at the end of script,
# and append this to the end.
##

##
# Given Params:
#   BASE_BRANCH : $1           : base branch
#   REGEXs      : $2 [ .. $N ] : regex(s)
#
# Output:
#   - git branches matching at least one of the regex params
#   - base branch is excluded from result
#   - order: branches matching the Nth regex will appear before
#            branches matching the (N+1)th regex.
#   - no duplicates in output
#
function expandUniqGitBranches() {
    local -A BSET[$1]=1
    shift

    local ALL_BRANCHES=$(git rev-parse --symbolic --branches)
    for regex in "$@"; do
        for branch in $ALL_BRANCHES; do
            ## RE: -z ${BSET[$branch]+x ...  ; presumes ENV 'x' is not defined
            if [[ $branch =~ $regex && -z "${BSET[$branch]+x}" ]]; then
                echo "$branch"
                BSET[$branch]=1
            fi
        done
    done
}


##
# Params:
#   BASE_BRANCH: $1    : "." equates to the current branch;
#   REGEXS     : $2..N : regex(es) corresponding to other to include
#
function findBranchesSharingFirstCommonCommit() {
    if [[ -z "$1" ]]; then
        echo "Usage: findBranchesSharingFirstCommonCommit ( . | baseBranch ) [ regex [ ... ] ]" >&2
        exit 1
    fi

    local BASE_BRANCH
    if [[ -z "${1+x}" || "$1" == '.' ]]; then
        BASE_BRANCH="$CURRENT_BRANCH"
    else
        BASE_BRANCH="$1"
    fi

    shift
    local REGEXS
    if [[ -z "$1" ]]; then
        REGEXS=(".*")
    else
        REGEXS=("$@")
    fi

    local BRANCHES=( $(expandUniqGitBranches "$BASE_BRANCH" "${REGEXS[@]}") )

## nearestCommonBranches  can also be used here, if batching not used.
    repeatBatchingUntilStableResults "$BASE_BRANCH" "${BRANCHES[@]}"
}

findBranchesSharingFirstCommonCommit "$@"

示例图的偏差结果

让我们考虑有序集

P = { ^release/.*$ ^support/.*$ ^master$ }

假设脚本(所有部分)在可执行文件gitr中,然后运行:

gitr <baseBranch> '^release/.*$' '^support/.*$' '^master$'

对于不同的分支B我们得到如下结果:

GIVEN B Shared Commit C Branches P with C in their history (in order)
feature/a D master
feature/b D master
feature/c L release/4, support/1
feature/d L release/4, support/1
feature/e L release/4, support/1
feature/f C release/2, release/3, master
feature/g C release/2, release/3, master
hotfix D master
master C release/2, release/3
release/2 C release/3, master
release/3 C release/2, master
release/4 L support/1
support/1 L release/4

这越来越接近一个明确的答案;发布分支的响应并不理想。让我们更进一步。

BASE_NAMEP 的偏差

采取这个的一个方向可能是使用不同的P 基本名称。让我们为此制定一个设计。

约定

免责声明:我不是 git flow 纯粹主义者,请原谅我

  • 支持分支应从主分支分支。
    • 不会有两个支持分支共享一个共同的提交。
  • 修补程序分支应从支持分支或主分支分支出来。
  • 发布分支应从支持分支或主分支分支出来。
    • 可能有多个发布分支共享一个共同的提交; 即同时从master分支出来。
  • 一个错误修复分支应该从一个发布分支分支出来。
  • 功能分支可以分支功能、发布、支持或主控:
    • 出于“父母”的目的, 一个特性分支不能被建立为 父母高于其他人(见初步讨论)。
    • 因此:跳过功能分支和 在发布、支持和/或主分支中寻找“父”。
  • 任何其他被视为工作分支的分支名称, 具有与功能分支相同的约定。

让我们看看我们git 能做到多远:

Base Branch Pattern Parent Branches, Ordered Comment(s)
^master$ n/a no parent
^support/.*$ ^master$
^hotfix/.*$ ^support/.*$ ^master$ give preference to a support branch over master (ordering)
^release/.*$ ^support/.*$ ^master$ give preference to a support branch over master (ordering)
^bugfix/.*$ ^release/.*$
^feature/.*$ ^release/.*$ ^support/.*$ ^master$
^.*$ ^release/.*$ ^support/.*$ ^master$ Redundant, but keep design concerns separate

脚本

合并以前的代码和下面的新代码可用作gist: gitp

#
# Remove/comment-out the function call at the end of script,
# and append this to the end.
##

# bash associative arrays maintain key/entry order.
# So, use two maps, values correlated by index:
declare -a MAP_BASE_BRANCH_REGEX=( "^master$" \
                                       "^support/.*$" \
                                       "^hotfix/.*$" \
                                       "^release/.*$" \
                                       "^bugfix/.*$" \
                                       "^feature/.*$" \
                                       "^.*$" )

declare -a MAP_BRANCHES_REGEXS=("" \
                                    "^master$" \
                                    "^support/.*$ ^master$" \
                                    "^support/.*$ ^master$" \
                                    "^release/.*$" \
                                    "^release/.*$ ^support/.*$ ^master$" \
                                    "^release/.*$ ^support/.*$ ^master$" )

function findBranchesByBaseBranch() {
    local BASE_BRANCH
    if [[ -z "${1+x}" || "$1" == '.' ]]; then
        BASE_BRANCH="$CURRENT_BRANCH"
    else
        BASE_BRANCH="$1"
    fi

    for idx in "${!MAP_BASE_BRANCH_REGEX[@]}"; do
        local BASE_BRANCH_REGEX=${MAP_BASE_BRANCH_REGEX[$idx]}
        if [[ "$BASE_BRANCH" =~ $BASE_BRANCH_REGEX ]]; then
            local BRANCHES_REGEXS=( ${MAP_BRANCHES_REGEXS[$idx]} )
            if (( ${#BRANCHES_REGEXS[@]} > 0 )); then
                findBranchesSharingFirstCommonCommit $BASE_BRANCH "${BRANCHES_REGEXS[@]}"
            fi
            break
        fi
    done
}

findBranchesByBaseBranch "$1"
示例图的偏差结果

假设脚本(所有部分)在可执行文件gitr中,然后运行:

gitr <baseBranch>

对于不同的分支B我们得到如下结果:

GIVEN B Shared Commit C Branches P with C in their history (in order)
feature/a D master
feature/b D master
feature/c L release/4, support/1
feature/d L release/4, support/1
feature/e L release/4, support/1
feature/f C release/2, release/3, master
feature/g C release/2, release/3, master
hotfix D master
master (blank, no value)
release/2 C master
release/3 C master
release/4 L support/1
support/1 L master
为胜利重构!

机会!

在最后一个示例中,发布分支共享一个公共提交 与其他多个:发布、支持或主分支。

让我们“重构”或重新评估所使用的约定,并把它们收紧一点。

考虑一下git 的使用约定:

创建新的发布分支时: 立即创建一个新的提交;也许更新一个版本,或者 README 文件。 这确保了功能/工作分支 用于发布(从发布分支) 将与发布分支共享提交 在底层的提交之前(并且不被共享) 支持或主分支。

例如:

        G---H   <- feature/z
       /
      E         <- release/1
     /
A---B---C---D   <- master
     \
      F         <- release/2

从 release/1 分支的一个特性不能有一个共同的提交 其中包括 release/1(它的父级)和 master 或 release/2。

这为每个分支提供了一个结果,即父级, 使用这些约定。

完成!有了工具和约定,我就可以生活在一个 OCD 友好的结构化 git 世界中。

您的里程可能会有所不同!

离别的想法

  1. 要点
  1. 最重要的是:我得出的结论是, 除了这里介绍的, 在某些时候,人们可能需要接受可能有多个 分支确实处理。

    • 也许可以对所有潜在的分支进行验证; “至少一个”或“全部”还是??可能会应用规则。
  2. 像这样的几周,我真的认为该是我学习 Python 的时候了。

【讨论】:

    【解决方案10】:

    请记住,正如"Git: Finding what branch a commit came from" 中所述,即使git branch --contains &lt;commit&gt; 是一个开始,您也无法轻松确定提交的分支(可以重命名、移动、删除分支...)。

    • 您可以从提交返回到提交,直到 git branch --contains &lt;commit&gt; 未列出 feature 分支并列出 develop 分支,
    • 比较提交 SHA1 到 /refs/heads/develop

    如果两个提交 id 匹配,您就可以开始了(这意味着 feature 分支的起源位于 develop 的 HEAD)。

    【讨论】:

      【解决方案11】:
      git log -2 --pretty=format:'%d' --abbrev-commit |尾-n 1 | sed 's/\s(//g; s/,/\n/g';

      (来源/父名,父名)

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

      原产地/父名

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

      父名

      【讨论】:

      • 这只是为我返回远程分支名称。
      【解决方案12】:

      Joe Chrysler's command-line magic 可以简化。这是 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 管道输出。

      【讨论】:

      • 请注意,由于引用过多,某些分支可能会从输出中丢失。它们在 stderr 上显示为警告。
      • 还有cannot handle more than 26 refs
      【解决方案13】:

      这是Mark Reed's solution 的 PowerShell 实现:

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

      【讨论】:

        【解决方案14】:

        我并不是说这是解决这个问题的好方法,但这似乎对我有用:

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

        问题在于对文件进行分类是在窥探 Git 的内部工作,因此这不一定是向前兼容(或向后兼容)的。

        【讨论】:

          【解决方案15】:

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

          【讨论】:

          • 您好!虽然这段代码可以解决问题,including an explanation 解决问题的方式和原因确实有助于提高帖子的质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。请edit您的回答添加解释并说明适用的限制和假设。
          【解决方案16】:

          Mark Reed's solution 基本上是正确的。但是,请注意,提交行不仅应包含星号,还应以星号开头!否则,包含星号的提交消息也包含在匹配的行中。所以应该是:

          git show-branch -a | awk -F'[]^~[]' '/^\*/ &amp;&amp; !/'"$current_branch"'/ {print $2;exit}'

          或长版:

          git show-branch -a           |
            awk '^\*'                  | # we want only lines that contain an asterisk
            awk -v "$current_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`
          

          【讨论】:

          • 在长版本中,您的意思是 s/awk/grep/ 吗?
          【解决方案17】:

          使用 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}" />
          

          【讨论】:

          • 解释一下。例如,想法/要点是什么?
          【解决方案18】:

          用途:

          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's answer 相同的目的,但它使用了一种更安全的方法,在许多情况下都不会出现错误行为:

          1. 父分支的最后一次提交是合并,使列显示-,而不是*
          2. 提交消息包含分支名称
          3. 提交消息包含*

          【讨论】:

            【解决方案19】:

            Git 带有几个 GUI 客户端,可帮助您将其可视化。打开 GitGUI 并转到菜单 RepositoryVisualize All Branch History

            【讨论】:

            • “GitGUI”究竟是如何打开的?什么平台(操作系统等)?您能否为您的答案添加一些参考(但没有“编辑:”、“更新:”或类似的 - 答案应该看起来好像是今天写的)?
            【解决方案20】:

            基于git show-branch -a 加上一些过滤器的解决方案有一个缺点:Git 可能会考虑一个短期分支的分支名称。

            如果你有几个你关心的可能父母,你可以问自己这个类似的问题(可能是 OP 想知道的问题):

            来自所有分支的特定子集,哪个是 git 分支最近的父级?

            为简化起见,我将考虑“一个 git 分支”来指代 HEAD(即当前分支)。

            假设我们有以下分支:

            HEAD
            important/a
            important/b
            spam/a
            spam/b
            

            基于git show-branch -a + 过滤器的解决方案可能会给出HEAD 的最近父级是spam/a,但我们不在乎。

            如果我们想知道important/aimportant/b 中的哪一个是HEAD 的最近父级,我们可以运行以下命令:

            for b in $(git branch -a -l "important/*" | sed -E -e "s/\*//"); do
                d1=$(git rev-list --first-parent ^${b} HEAD | wc -l);
                d2=$(git rev-list --first-parent ^HEAD ${b} | wc -l);
                echo "${b} ${d1} ${d2}";
            done \
            | sort -n -k2 -k3 \
            | head -n1 \
            | awk '{print $1}';
            

            它的作用:

            1.) $(git branch -a -l "important/*" | sed -E -e "s/\*//"):以某种模式打印所有分支的列表 ("important/*")。 (如果您碰巧在 important/* 分支之一上,git branch 将包含一个 * 来表示您当前的分支。命令替换 $() 然后会将其扩展为您当前目录的内容。sedgit branch 的输出中删除*。)

            2.) d=$(git rev-list --first-parent ^${b} HEAD | wc -l);:对于每个分支 ($b),计算从 HEAD$b 中最近提交的距离 ($d1) 的提交次数(类似于 when您计算从点到线的距离)。您可能想在这里以不同的方式考虑距离:您可能不想使用--first-parent,或者可能想要从尖端到分支尖端的距离("${b}"...HEAD),...

            2.2) d2=$(git rev-list --first-parent ^HEAD ${b} | wc -l);:对于每个分支 ($b),计算从分支尖端到 HEAD 中最近提交的距离 ($d2)。我们将使用此距离在距离$d1 相等的两个分支之间进行选择。

            3.) echo "${b} ${d1} ${d2}";:打印每个分支的名称,然后是距离以便以后能够对它们进行排序(首先是$d1,然后是$d2)。

            4.) | sort -n -k2 -k3:对之前的结果进行排序,所以我们得到了所有分支的排序(按距离)列表,然后是它们的距离(两者)。

            5.) | head -n1:上一步的第一个结果将是距离较小的分支,即最近的父分支。所以只需丢弃所有其他分支。

            6.) | awk '{print $1}';:我们只关心分支名称,而不关心距离,所以提取第一个字段,即父项的名称。这里是! :)

            【讨论】:

              【解决方案21】:

              现在任何人都想这样做 - Atlassian 的 Sourcetree 应用程序向您展示了您的分支如何相互关联的出色可视化表示,即它们开始的位置以及它们当前在提交顺序中所处的位置(例如,HEAD 或 4提交后面等)。

              【讨论】:

              • 可能需要在脚本中使用父分支,为此您需要提取父分支的名称。
              【解决方案22】:

              如果您使用 Sourcetree,请查看您的提交详细信息 → Parents。然后你会看到下划线的提交号(链接)。

              【讨论】:

                【解决方案23】:

                另一种选择:

                git rev-list master | grep "$(git rev-list HEAD)" | head -1
                

                获取我的分支和master(或您要指定的任何分支)的最后一次提交。

                【讨论】:

                • 我收到以下错误:bash: /usr/bin/grep: Argument list too long
                【解决方案24】:

                当我完成类似 developrelease-v1.0.0feature-foo 之类的操作时,这对我不起作用。它会一路回去发展。请注意,涉及到一个变基,我不确定这是否会加剧我的问题......

                以下确实为我提供了正确的提交哈希:

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

                【讨论】:

                • 到目前为止,这似乎是唯一适合我的答案。小提示:使用 ripgrep 而不是 grep 使其几乎是即时的。
                • 我们实际上通过将确切答案保存到 repo 中的 &lt;branch&gt;.json 文件使这变得容易多了。增加了一些混乱,但它给了我们极大的灵活性来定义额外的变量来存储信息和控制 CI 中的过程。我们编写了一个脚本来创建一个分支,创建这个文件,然后提交和推送,同时设置源。
                猜你喜欢
                • 2014-03-16
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2023-01-04
                • 2018-09-12
                • 2016-02-22
                相关资源
                最近更新 更多