找到Git分支的父分支

时间:2010-07-01 19:52:14

标签: git

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

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

master是我这是最新的稳定版本代码develop是我的这是'下一个'版本代码,{ {1}} 是为feature 准备的新功能。

除了提交developfeature HEAD的直接后代之外,我希望能够使用钩子对我的远程仓库执行操作,以便推送到f。即提交树看起来像这样,因为develop上的功能已git rebase

d

所以有可能:

  • 确定master --> a \ \ develop c --> d \ \ feature f --> g --> h
  • 的父分支
  • 确定feature是[
  • >的后代的父分支中的提交

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

23 个答案:

答案 0 :(得分:304)

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

Git的历史记录基于DAG提交。分支(和“refs”一般)只是瞬态标签,指向不断增长的提交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。尽管如此,祖先并没有改变,只有标签发生了变化。

但是,如果我们要求“提交6后代提交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

以上代码可用于在接受HSH'J时拒绝KN,但它也会接受LP(它们涉及合并,但它们不会合并 develop 的提示)。

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

  

对于所有被推送的提交的祖先(不包括 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

答案 1 :(得分:190)

一个rephrasal

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

解决方案

你可以通过一点点命令行魔术来找到它

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. 有时分支名称将包含〜#或^#,以指示引用的提交和分支提示之间有多少提交。我们不在乎。忽略它们。
  7. 和结果

    上运行上述代码
     A---B---D <-master
          \
           \
            C---E---I <-develop
                 \
                  \
                   F---G---H <-topic
    

    如果您从H运行它,将会给您develop,如果您从I运行它,则会master

    The code is available as a gist

答案 2 :(得分:80)

您也可以尝试:

git log --graph --decorate

答案 3 :(得分:50)

我有一个解决您的整体问题的方法(确定feature是否来自develop的提示),但使用您概述的方法无效。

您可以使用git branch --contains列出develop提示下的所有分支,然后使用grep确保其中feature

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

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

答案 4 :(得分:48)

git parent

您只需运行命令

git parent

查找分支的父级(如果您添加 @Joe Chrysler 的答案作为 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 parent

就是这样!

答案 5 :(得分:32)

这对我来说很好。

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

来自@droidbot和@Jistanidiot

的礼貌答案

答案 6 :(得分:10)

由于上述答案都没有在我们的存储库中运行,我希望使用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

答案 7 :(得分:8)

请记住,正如"Git: Finding what branch a commit came from"中所述,即使git branch --contains <commit>git branch --contains <commit>,您也无法轻松查明已进行提交的分支(分支可以重命名,移动,删除...)启动。

  • 您可以从提交返回提交,直到feature未列出develop分支并列出/refs/heads/develop分支,
  • 将提交SHA1与feature
  • 进行比较

如果两个提交id匹配,那么你就可以了(这意味着develop分支的起源位于{{1}}的HEAD。

答案 8 :(得分: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

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

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

这样崩溃了:

-F'[]^~[]' 

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

/\*/                      

查找包含星号的行

&& !/'"$current_branch"'/

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

{ print $2;               

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

  exit }

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

答案 9 :(得分:3)

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

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

答案 10 :(得分:3)

以下是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)

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

答案 12 :(得分:2)

解决方案

解决方案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工具这样做是为了确保它们在给定的分支中建立正确的提交),但是原始分支和本地分支必须都等于或大于当前的HEAD。
  • 必须没有没有标记(我想;我尚未在子分支和父分支之间带有标记的提交中测试脚本)
  • 脚本依赖于事实,log命令始终将“ HEAD” 列为第一个装饰
  • 运行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

尽管存在本地分支(例如,仅存在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 *
  • 对于提交BDEF(分支master)→<sha> Initial commit

*-或master,如果develop的提交位于master的HEAD之上(〜master将是可快速转发的)


为什么没有为我做分支工作

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

  • 分离的头 –包括分离的头套意味着将grep '\*' \替换为'grep'!' \ –这只是所有麻烦的开始
  • masterdevelop
  • 运行脚本分别导致develop和``
  • master 分支(hotfix/分支)上的
  • 分支以develop作为父级结束,因为标记了与其最近的master分支父级原因是使用!而不是*

答案 13 :(得分:2)

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. 提交消息包含*

答案 14 :(得分:2)

@Mark Reed:你应该补充一点,提交行不应该只包含一个星号,而是以星号开头!否则,提交包含星号的消息也包含在匹配的行中。所以它应该是:

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`

答案 15 :(得分:1)

基于 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/*"); 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/*"):使用某种模式 ("important/*") 打印所有分支的列表。

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

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

3.) $d1:打印每个分支的名称,然后是距离以便稍后对其进行排序(先是 echo "${b} ${d1} ${d2}";,然后是 $d1)。< /p>

4.) $d2:对之前的结果进行排序,因此我们得到所有分支的排序(按距离)列表,然后是它们的距离(两者)。

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

6.) |head -n1:我们只关心分支名称,而不关心距离,因此提取第一个字段,即父名称。这里是! :)

答案 16 :(得分:1)

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

答案 17 :(得分:1)

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

答案 18 :(得分:0)

当我做过develop > release-v1.0.0 > feature-foo之类的事情时,这对我不起作用,它会一直发展下去,请注意其中涉及到重新设置,不确定是否使我的问题更加复杂... < / p>

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

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

答案 19 :(得分:0)

替代方法:git rev-list master | grep "$(git rev-list HEAD)" | head -1

获取最后一个提交,它既是我的分支又是master(或您要指定的任何分支)

答案 20 :(得分:0)

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

来源/父母姓名

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

父母姓名

答案 21 :(得分:0)

如果您使用源树,请查看您的提交详细信息&gt;父母&gt;然后你会看到下划线的提交号码(链接)

答案 22 :(得分:0)

这些天想要这样做的人--Atlassian的SourceTree应用程序向您展示了您的分支机构如何相互关联的绝佳视觉表示,即它们的开始位置以及它们当前在提交顺序中的位置(例如HEAD或4提交等等。)。