找到Git分支的父分支

Pet*_*mer 387 git branch

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

master --> a
            \
             \
      develop c --> d
               \
                \
         feature f --> g --> h
Run Code Online (Sandbox Code Playgroud)

master是我的这是最新的稳定版本代码,develop是我的这个'下一个'版本代码,feature一个正在准备的新功能develop.

我希望能够使用钩子在我的远程仓库上做什么,feature除非提交fdevelopHEAD 的直接后代,否则推送被拒绝.即提交树看起来像这样,因为功能已经git rebase打开d.

master --> a
            \
             \
      develop c --> d
                     \
                      \
               feature f --> g --> h
Run Code Online (Sandbox Code Playgroud)

那么有可能:

  • 确定父分支feature
  • 识别父分支中的提交,它f是后代的?

从那里我将检查父分支的HEAD是什么,并查看f前任是否与父分支HEAD匹配,以确定该特征是否需要重新定位.

Chr*_*sen 331

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

Git的历史基于提交的DAG.分支(和"refs"一般)只是瞬态标签,指向不断增长的提交DAG中的特定提交.因此,分支之间的关系可以随时间变化,但提交之间的关系不会.

    ---o---1                foo
            \
             2---3---o      bar
                  \
                   4
                    \
                     5---6  baz
Run Code Online (Sandbox Code Playgroud)

它看起来像是baz基于(旧版本)bar?但是如果我们删除bar怎么办?

    ---o---1                foo
            \
             2---3
                  \
                   4
                    \
                     5---6  baz
Run Code Online (Sandbox Code Playgroud)

现在它看起来像是baz基于foo.但是祖先baz并没有改变,我们只是删除了一个标签(以及由此产生的悬空提交).如果我们添加新标签4怎么办?

    ---o---1                foo
            \
             2---3
                  \
                   4        quux
                    \
                     5---6  baz
Run Code Online (Sandbox Code Playgroud)

现在它看起来像是baz基于quux.尽管如此,祖先并没有改变,只有标签改变了.

但是,如果我们问:"是犯6犯的后代3?"(假设36充满SHA-1提交的名称),那么答案是"是",是否barquux标签存在与否.

那么,您可以提出诸如"被推送的提交是开发分支当前提示的后代吗?"之类的问题,但是您无法可靠地询问"推送提交的父分支是什么?".

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

对于所有被推送的提交的祖先(不包括当前的开发提示及其祖先),其目前的开发方式是作为父级:

  • 至少存在一个这样的提交吗?
  • 所有这些提交单父提交?

哪个可以实现为:

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
Run Code Online (Sandbox Code Playgroud)

这将涵盖您想要限制的一些内容,但可能不是所有内容.

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

    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
Run Code Online (Sandbox Code Playgroud)

上面的代码可以用来拒绝HS同时接受H',J,K,或N,但它也接受LP(它们涉及合并,但他们不合并的尖端发展).

要拒绝LP,您可以更改问题并询问

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

  • 有两个父母的任何承诺吗?
  • 如果没有,至少有一个这样的提交是否有当前的发展其(唯一)父母的提示?
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
Run Code Online (Sandbox Code Playgroud)

  • 我欣赏霸王龙举着旗帜(或切肉刀)的历史图 (40认同)
  • @GrahamRussell:几年前的一条评论中它被称为迅猛龙,但该评论现在似乎已被删除一段时间了。 (4认同)

Joe*_*ler 222

一个rephrasal

另一种表达问题的方法是"当前分支以外的分支上最近的提交是什么,哪个分支是那个?"

一个办法

您可以通过一点命令​​行魔术找到它

git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/.*\[\(.*\)\].*/\1/' \
| sed 's/[\^~].*//'
Run Code Online (Sandbox Code Playgroud)

以下是它的工作原理:

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

结果

运行上面的代码

 A---B---D <-master
      \
       \
        C---E---I <-develop
             \
              \
               F---G---H <-topic
Run Code Online (Sandbox Code Playgroud)

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

该代码可作为要点提供

  • 对不起,那是错的.这是对我有用的正确的:`git show-branch | grep'*'| grep -v"$(git rev-parse --abbrev-ref HEAD)"| 头-n1 | sed的/.*\[\(.*\)\].*/\1 /'| sed's/[\ ^〜].*//'` (53认同)
  • 删除了导致错误的尾部反引号.在运行此命令时,我收到大量警告,抱怨每个分支说"无法处理超过25个参考" (23认同)
  • @droidbot很好,但是当grep -v catch提交消息或你的分支名称是另一个分支名称的一部分时,需要管道重新排序以避免删除引用.`git show-branch | sed"s /].*//"| grep"\*"| grep -v"$(git rev-parse --abbrev-ref HEAD)"| 头-n1 | sed"s /^.*"["" (14认同)
  • @OlegAbrazhaev我不知道你是否回答过你的问题.使用git别名:`parent ="!git show-branch | grep'*'| grep -v \"$(git rev-parse --abbrev-ref HEAD)\"| head -n1 | sed's/.*\\ [\\(.*\\)\\].*/\\ 1 /'| sed's/[\\ ^〜].*//'#"`对我有用 (3认同)

rcd*_*de0 99

你也可以尝试:

git log --graph --decorate
Run Code Online (Sandbox Code Playgroud)

  • `git log --graph --decorate --simplify-by-decoration --oneline` (6认同)
  • git log --graph --decorate --simplify-by-decoration`,其中`--graph`是可选的。 (5认同)

NIK*_*C M 92

git parent

您只需运行该命令即可

git parent

找到分支的父级,如果你把@Joe Chrysler的答案添加为git别名.它将简化使用.

使用任何文本编辑器打开位于"〜/ .gitconfig"的gitconfig文件.

vim  ~/.gitconfig
Run Code Online (Sandbox Code Playgroud)

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

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

保存并退出编辑器.

运行命令 "~/.gitconfig"

而已!

  • 获取“不能处理超过25个引用”异常。 (7认同)
  • git log --first-parent 这有效 https://github.community/t/is-there-a-way-to-find-the-parent-branch-from-which-branch-head-is-detached-for -分离头/825/4 (7认同)
  • 这是一个很好的解决方案.添加一些样本输出以确保预期结果将是有帮助的.当我运行它时,我在最后一行之前收到了一些警告,我认为它是父分支的名称. (5认同)
  • 奇迹般有效!对于Windows用户,.gitconfig通常位于c:\ users\your-user\.gitconfig (4认同)
  • @shajin 和其他人:如果你想静音警告,你可以将 git show-branch 的 stderr 通过管道传输到 stdout,如下所示:`git show-branch 2&gt;&amp;1 | ...`。以下 sed/grep 将以这种方式过滤掉警告。或者,您也可以通过“2&gt;/dev/null”将错误传递到/dev/null。 (3认同)
  • 这不是_答案_,而是_应用_(别人的)答案的方式。所以它应该是各自(或基本上任何)答案下的评论,而不是单独的答案 (2认同)

Dan*_*ach 52

我有一个解决你的整体问题的方法(确定是否feature来自尖端develop),但它不能使用你概述的方法.

您可以使用git branch --contains列出从尖端下降的所有分支develop,然后使用grep以确保feature它们之中.

git branch --contains develop | grep "^ *feature$"
Run Code Online (Sandbox Code Playgroud)

如果它是其中之一,它将打印" feature"到标准输出并返回代码为0.否则,它将不打印任何内容并返回代码为1.


Mur*_*uru 43

这对我来说很好.

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

礼貌答案来自:@droidbot和@Jistanidiot

  • 我收到很多“无法处理超过 26 个裁判”的信息。 (9认同)
  • 是的,但有时它会让你从 grep 中得到“断管”。 (2认同)
  • `*` 不是传递给 grep 的正确正则表达式。应该使用 `grep -F '*'` 或 `grep '\*'` 代替。否则很好的解决方案。 (2认同)
  • 我没有任何输出。 (2认同)

Ric*_*tze 42

为什么家里没有家长?

下面会发生什么

要点:

为什么有人想阅读这篇长文章?因为虽然之前的答案清楚地理解了原始问题的问题,但它们缺乏正确/有意义的结果;或准确地解决不同的问题。

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

这将向您展示一种从 git 中提取正确且有意义的结果的方法(您可能不喜欢它们),并演示一种将您对约定的知识应用到这些结果中以提取您真正想要的内容的方法。

以下各节涵盖:

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

问题的问题

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

A---B---C---D    <- master branch
     \
      E---F      <- work branch
Run Code Online (Sandbox Code Playgroud)

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

      E---F   <- jack
     /
A---B
     \
      C---D   <- jill
Run Code Online (Sandbox Code Playgroud)

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

master第一张图中的父项是work惯例问题。

所以

  • 简单的工具将产生忽略偏差的响应
  • 更复杂的工具包含约定(偏差)。

一个公正的问题

首先,我必须首先感谢乔·克莱斯勒的回应、这里的其他回应以及周围的许多评论/建议;他们启发了我,为我指明了道路!

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

“当前分支之外的分支上最近的提交是什么?那是哪个分支?”

或者,换句话说:

Q1

给定一个分支:考虑最接近 (可能是)由其他分支共享的B提交:除了 之外,还有哪些分支在其提交历史记录中?CB'HEADCB'HEADBC

公正的解决方案

预先道歉;人们似乎更喜欢俏皮话。请随意提出(可读/可维护的)改进建议!

#!/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 "${@}"
Run Code Online (Sandbox Code Playgroud)

怎么运行的

考虑输出: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
Run Code Online (Sandbox Code Playgroud)

几点:

  • 原始命令在命令行上列出了 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:length N+1:第一个N元素1-1与BRANCHES,最后一个元素对应于BASE_BRANCH
  11. 迭代BRANCHES(这就是我们想要的,而且更短)并检查MAP: output BRANCH[$idx]if MAP[$idx]is中的相应元素+

示例图和结果

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

  • 将使用有偏见的名称,因为它们有助于(我)权衡和考虑结果。
  • 假设合并存在并且被忽略。
  • 该图通常试图突出显示分支本身(分叉),而不在视觉上暗示偏好/层次结构;具有讽刺意味的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
Run Code Online (Sandbox Code Playgroud)

示例图的无偏结果

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

gitr <baseBranch>
Run Code Online (Sandbox Code Playgroud)

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

给定B 共享提交 C 历史上有 C 的分支 !B?
特征/一个 H 特征/b
特征/b H 特征/一个
特征/c 特征/d
特征/d 特征/c
特征/e 特征/c,特征/d
特征/f C 功能/a、功能/b、功能/g、修补程序、主版本、版本/2、版本/3
特征/克 C 功能/a、功能/b、功能/f、修补程序、主版本、版本/2、版本/3
修补程序 D 功能/a、功能/b、主控
掌握 D 功能/a、功能/b、修补程序
发布/2 C 功能/a、功能/b、功能/f、功能/g、修补程序、主版本、发行版/3
发布/3 C 功能/a、功能/b、功能/f、功能/g、修补程序、主版本、发行版/2
发布/4 L 功能/c、功​​能/d、功能/e、支持/1
支持/1 L 功能/c、功​​能/d、功能/e、发布/4

批处理分支

[在此阶段呈现,因为它最适合此时的最终脚本。 本节不是必需的,请随意跳到后面。]

git show-branch其分支机构数量仅限于 29 个。这对某些人来说可能是一个障碍(没有判断力,只是说!)。

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

  • BASE_BRANCH 必须与每个分支一起提交。
  • 如果存储库中有大量分支,则其本身的价值可能有限。
  • 如果您找到其他方法来限制分支(将被批量处理),可能会提供更多价值。
  • 上一点适合我的用例,所以请继续前进!

这种机制并不完美,因为结果大小接近最大值 (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
Run Code Online (Sandbox Code Playgroud)

怎么运行的

重复直到结果稳定

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

它如何会失败

如果聚合分支的数量超过最大值SIZE 并且进一步的批处理/处理无法减少该数量,则:

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

考虑替代方案

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

我从事后的角度提出这一点。这可能确实是正确的方法。我正在前进;也许在当前主题之外还有价值。

一个有偏见的问题

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

Q2

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

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

建模简单偏差/约定

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

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

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

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

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

合并之前的代码和下面的新代码可作为要点: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 "$@"
Run Code Online (Sandbox Code Playgroud)

示例图的有偏差结果

让我们考虑一下有序集

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

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

gitr <baseBranch> '^release/.*$' '^support/.*$' '^master$'
Run Code Online (Sandbox Code Playgroud)

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

给定B 共享提交 C 历史上有 P 和 C 的分支(按顺序)
特征/一个 D 掌握
特征/b D 掌握
特征/c L 发布/4,支持/1
特征/d L 发布/4,支持/1
特征/e L 发布/4,支持/1
特征/f C 发布/2、发布/3、主控
特征/克 C 发布/2、发布/3、主控
修补程序 D 掌握
掌握 C 发布/2、发布/3
发布/2 C 发布/3,主控
发布/3 C 发布/2,主控
发布/4 L 支持/1
支持/1 L 发布/4

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

偏差由BASE_NAMEP

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

惯例

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

  • 支持分支应从 master 分支出来。
    • 不会有两个支持分支共享一个共同的提交。
  • 修补程序分支应从支持分支或主分支分支出来。
  • 发布分支应从支持分支或主分支分支出来。
    • 可能有多个发布分支共享一个共同的提交;即同时从 master 分支出来。
  • 错误修复分支应从发布分支分支出来。
  • 功能分支可以从功能、版本、支持或主分支中分支出来:
    • 出于“父级”的目的,不能将一个功能分支建立为另一个功能分支的父级(请参阅最初的讨论)。
    • 因此:跳过功能分支并在发布、支持和/或主分支中查找“父”分支。
  • 任何其他被视为工作分支的分支名称,与功能分支具有相同的约定。

让我们看看我们能git做到什么程度:

基础分支模式 父分支,已订购 评论)
^大师$ 不适用 没有父母
^支持/.*$ ^大师$
^修补程序/.*$ ^支持/.*$ ^大师$ 优先支持支持分支而不是主分支(排序)
^发布/.*$ ^支持/.*$ ^大师$ 优先支持支持分支而不是主分支(排序)
^错误修复/.*$ ^发布/.*$
^功能/.*$ ^发布/.*$ ^支持/.*$ ^master$
^.*$ ^发布/.*$ ^支持/.*$ ^master$ 冗余,但将设计问题分开

脚本

合并的先前代码和下面的新代码可作为要点: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"
Run Code Online (Sandbox Code Playgroud)
示例图的有偏差结果

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

gitr <baseBranch>
Run Code Online (Sandbox Code Playgroud)

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

给定B 共享提交 C 历史上有 P 和 C 的分支(按顺序)
特征/一个 D 掌握
特征/b D 掌握
特征/c L 发布/4,支持/1
特征/d
  • 被严重低估的答案 (3认同)

小智 14

一个办法

该解决方案基于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
Run Code Online (Sandbox Code Playgroud)

限制和注意事项

  • HEAD 可以分离(许多 CI 工具这样做是为了确保它们在给定分支中构建正确的提交),但原始分支和本地分支必须与当前 HEAD相同或“高于”
  • 不能有任何标签挡在路上(我猜想;我没有在子分支和父分支之间使用标签测试提交时的脚本)
  • 脚本依赖于以下事实“HEAD”总是被列为第一个装饰用的log命令
  • 运行该脚本masterdevelop结果(大部分)在<SHA> 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
Run Code Online (Sandbox Code Playgroud)

尽管存在本地分支(例如,仅origin/topic在提交O由其 SHA 直接检出后才存在),脚本应打印如下:

  • 对于提交GHI(分支hotfix)?master
  • 对于提交MNO(分支feature/a)?develop
  • 对于提交STU(分支feature/c)?develop
  • 对于提交PQR(分支feature/b)?feature/a
  • 对于提交JKL(分支develop)?<sha> Initial commit*
  • 对于提交B, D, E, F(branch master) ?<sha> Initial commit

* - 或者master如果develop's commits 在 master 的 HEAD 之上(~ master 可以快速开发)


为什么 show-branch 对我不起作用

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

  • 分离的 HEAD - 包括分离的头盒意味着替换grep '\*' \“grep”!\ – 而这只是所有麻烦的开始
  • 运行该脚本master,并develop导致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\\///'" ` (2认同)
  • 这应该是公认的答案:/ (2认同)

sae*_*gnu 11

由于上述答案都没有在我们的存储库中运行,我想以自己的方式分享,使用最新的合并git log:

#!/bin/bash
git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10
Run Code Online (Sandbox Code Playgroud)

将它放在一个名为的脚本中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
Run Code Online (Sandbox Code Playgroud)

  • 这是唯一对我有用的答案。要获取第一个父级而不是前 10 个父级的列表,您可以使用以下命令: `git log --oneline --merges "$@" | grep 进入 | sed 's/.* 进入 //g' | 唯一性——计数| 头 -n 1 | 切 -d ' ' -f 8` (2认同)

Von*_*onC 8

请记住,正如"Git:查找提交来自哪个分支"中所述,您无法轻松查明已提交提交的分支(分支可以重命名,移动,删除...),即使它git branch --contains <commit>是一个开始.

  • 您可以从提交返回提交,直到git branch --contains <commit>不列出feature分支和列表develop分支,
  • 比较提交SHA1 /refs/heads/develop

如果两个提交id匹配,那么你就好了(这意味着feature分支的起源是HEAD develop).


小智 8

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/)//';

父母名字


chr*_*ett 7

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

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


Mar*_*eed 5

JoeChrysler的命令行魔法可以简化.这是写的逻辑:

git show-branch -a           |
  ack '\*'                   | # we want only lines that contain an asterisk
  ack -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
Run Code Online (Sandbox Code Playgroud)

我们可以在一个相对简单的awk命令中完成与所有五个单独命令过滤器相同的操作:

git show-branch -a | awk -F'[]^~[]' '/\*/ && !/'"$current_branch"'/ {print $2;exit}'  
Run Code Online (Sandbox Code Playgroud)

这样就崩溃了:

-F'[]^~[]' 
Run Code Online (Sandbox Code Playgroud)

分割线成字段],^,~,和[字符.

/\*/                      
Run Code Online (Sandbox Code Playgroud)

查找包含星号的行

&& !/'"$current_branch"'/
Run Code Online (Sandbox Code Playgroud)

...但不是当前的分支名称

{ print $2;               
Run Code Online (Sandbox Code Playgroud)

当您找到这样的行时,打印其第二个字段(即,我们的字段分隔符字符的第一次和第二次出现之间的部分).对于简单的分支名称,这将是括号之间的内容; 对于具有相对跳转的refs,它将只是没有修饰符的名称.所以我们的字段分隔符集处理两个sed命令的意图.

  exit }
Run Code Online (Sandbox Code Playgroud)

然后立即退出.这意味着它只处理第一个匹配行,因此我们不需要通过管道输出head -n 1.

  • 请注意,由于引用过多,输出中可能会缺少一些分支。它们在stderr上显示为警告。 (2认同)

use*_*413 5

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

git branch --contains $(cat .git/ORIG_HEAD) 问题在于,正在接收文件的文件正在窥探git的内部工作,因此这不一定是向前兼容(或向后兼容)的。