代码之家  ›  专栏  ›  技术社区  ›  Peter Farmer

查找Git分支的父分支

git
  •  317
  • Peter Farmer  · 技术社区  · 14 年前

    假设我有如下本地存储库,其中有一个这样的提交树:

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

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

    我想用钩子在远程回购上做的是推动 特征 除非承诺否则被拒绝 f 是的直接后代 发展 头。也就是说,提交树看起来像这样,因为功能 git rebase d .

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

    那么是否有可能:

    • 标识的父分支 特征 ?
    • 在父分支中标识提交 f 是的后代吗?

    从那里我会检查什么是父分支机构的负责人,看看 f 前置任务与父分支头匹配,以确定是否需要重新调整功能。

    18 回复  |  直到 5 年前
        1
  •  296
  •   Sled bayer    6 年前

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

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

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

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

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

    现在看起来像 巴兹 基于 foo . 但是他们的祖先 巴兹 没有改变,我们只是删除了一个标签(以及由此产生的悬而未决的承诺)。如果我们在 4 ?

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

    现在看起来像 巴兹 基于 quux . 但是,祖先没有改变,只有标签改变了。

    但是,如果我们问__是否承诺 6 承诺的后代 3 ?_(假设 是完整的sha-1提交名称),那么答案将是__是__,是否 酒吧 夸克 标签是否存在。

    所以,您可以问一些问题,比如推送提交是 发展 分支机构?_157;,但您不能可靠地问_156;什么是推送提交的父分支?艾斯。

    一个最可靠的问题是:

    对于所有推送commit的祖先(不包括 发展 以及它的祖先),他们现在拥有 发展 作为家长:

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

    可实施为:

    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
    

    上述代码可用于拒绝 H S 接受时 H' , J , K N 但是它也会接受 L P (它们涉及合并,但不合并 发展 )

    也拒绝 L ,您可以更改问题并提问

    对于所有推送commit的祖先(不包括 发展 及其祖先):

    • 有没有对两个父母的承诺?
    • 如果没有,那么至少有一个这样的提交具有 发展 它的(唯一的)父母?
    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
    
        2
  •  180
  •   Joe Chrysler    7 年前

    遣词造句

    另一种表达问题的方法是“驻留在当前分支之外的分支上最近的提交是什么,它是哪个分支?”

    解决方案

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

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

    它的工作原理如下:

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

    以及结果

    在上运行上述代码

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

    会给你 develop 如果你从H和 master 如果你从我这里跑出来。

    The code is available as a gist

        3
  •  78
  •   Peter Mortensen TravisEz13    7 年前

    您也可以尝试:

    git log --graph --decorate
    
        4
  •  50
  •   Daniel Stutzbach Edward Leno    14 年前

    我有一个解决你整个问题的办法(决定 feature 是从 develop ,但使用您概述的方法不起作用。

    你可以使用 git branch --contains 列出从 发展 然后使用 grep 确定 特征 就是其中之一。

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

    如果在其中,它会打印出来 " feature" 标准输出,返回代码为0。否则,它将不打印任何内容,返回代码为1。

        5
  •  33
  •   NIKHIL C M    6 年前

    GIT亲本

    你可以运行命令

    git parent

    若要查找分支的父级,请添加 @乔克莱斯勒 作为一个答案 git别名 . 它将简化使用。

    使用任何文本编辑器打开位于“~/.gitconfig”的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亲本

    就是这样!

        6
  •  30
  •   Murali Mopuru    7 年前

    这对我来说很管用。

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

    礼貌回答:@droidbot和@jistanidiot

        7
  •  11
  •   saeedgnu satish chennupati    8 年前

    由于上面的所有答案都不适用于我们的存储库,所以我希望使用中的最新合并来共享我自己的方法。 git log :

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

    把它放在一个名为 git-last-merges ,它还接受一个分支名称作为参数(而不是当前分支)以及其他 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
    
        8
  •  8
  •   Community basarat    7 年前

    请记住,如中所述 "Git: Finding what branch a commit came from" ,您无法轻松地定位已提交的分支(分支可以重命名、移动、删除…),即使 git branch --contains <commit> 是一个开始。

    • 你可以从提交到提交 git branch——包含<commit> 没有列出 feature 分支和列表 develop 分支,
    • 把承诺与 /refs/heads/develop

    如果两个提交的ID匹配,您就可以离开(这意味着 特征 分支的起源在 发展 )

        9
  •  5
  •   Mark Reed    10 年前

    乔·克莱斯勒的命令行魔法可以简化。以下是所写的逻辑:

    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
    

    我们可以在相对简单的 awk 命令:

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

    就这样崩溃了:

    -F'[]^~[]' 
    

    将行拆分为字段 ] , ^ , ~ [ 字符。

    /\*/                      
    

    查找包含星号的行

    && !/'"$current_branch"'/
    

    …但不是当前分支名称

    { print $2;               
    

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

      exit }
    

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

        10
  •  4
  •   Peter Mortensen TravisEz13    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('[\^~].*','') }
    
        11
  •  3
  •   Community basarat    9 年前

    @MarkReed:您应该补充一下,提交行不仅应该包含一个星号,而且应该以一个星号开始!否则,包含星号的提交消息也包含在匹配的行中。所以应该是:

    git show-branch -a | awk -F'[]^~[]' '/^\*/ && !/'"$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`
    
        12
  •  3
  •   ENargit    9 年前

    使用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}" />
    
        13
  •  3
  •   user1529413    6 年前

    我不是说这是解决这个问题的一个好方法,但是这对我来说确实有效。

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

        14
  •  2
  •   Haoyang Feng    8 年前
    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的答案相同的目的,但使用了一种更安全的方法,在许多情况下都不会出现错误行为:

    1. 父分支的最后一次提交是合并,使列显示 - *
    2. 提交消息包含分支名称
    3. 提交消息包含 *
        15
  •  1
  •   jake    7 年前

    这些天任何想这样做的人——Atlassian的SourceTree应用程序向您展示了您的分支如何相互关联的一个很好的可视化表示,即它们从何处开始,以及它们当前处于提交顺序中的位置(例如head或4 commit s behind等)。

        16
  •  1
  •   Alexander Woods    5 年前

    另一种选择: git rev-list master | grep "$(git rev-list HEAD)" | head -1

    最后一次承诺这是我的分支 master (或您要指定的任何分支)

        17
  •  1
  •   Matt Stuvysant    5 年前

    解决方案

    解决方案 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
    

    局限和警告

    • 可以分离头(许多CI工具这样做是为了确保它们在给定的分支中构建正确的提交),但是 始发地分行和本地分行 必须两者兼而有之 平价或“高于” 当前头部。
    • 一定有 无标签 以这种方式(我假定;我还没有在提交时测试脚本,在子分支和父分支之间使用标记)
    • 剧本依赖于事实 “头” 总是 列为第一装修 log 命令
    • 运行 剧本 master develop 结果(大部分)在 <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
    

    尽管存在当地分支机构(例如仅 origin/topic 自提交后出现 O 由其sha直接签出),脚本应打印如下:

    • 对于提交 G , H , I (分支) hotfix ) 主人
    • 对于提交 M , N , o (分支) feature/a ) 发展
    • 对于提交 S , T , U (分支) feature/c ) 发展
    • 对于提交 P , Q , R (分支) feature/b ) 特征/ A
    • 对于提交 J , K , L (分支) 发展 ) <sha> Initial commit *
    • 对于提交 B , D , E , F (分支) 主人 ) <sha>初始提交

    *-或 主人 如果 发展 师父的承诺是在他头上的(~师父很快就会得到发展)


    为什么不给我展示分支工作?

    解决方案 基于 GIT显示分支 在以下情况下证明我不可靠:

    • 分离头 _“包括分离的头部外壳意味着更换 grep '\*' \ 为了“GRIP”!\“这只是所有麻烦的开始”
    • 运行 剧本 主人 发展 结果在 发展 和``分别
    • 枝上 主人 分公司(分支) hotfix/ 分支)以 发展 作为最亲密的父母 主人 分支父级标记为 ! 而不是 * 这是有原因的。
        18
  •  0
  •   Mike6679    7 年前

    如果使用源树查看提交详细信息>父级>,则会看到带下划线的提交编号(链接)