find一个Git分支的父分支

比方说,我有这样的提交树下面的本地存储库:

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

master是我的这是最新的稳定版本代码develop是我的这个是'下一个'的发布代码 ,而这个feature一个正在为develop做准备的新特性

我希望能够使用钩子在我的远程回购上做的,是推动feature被拒绝,除非承诺fdevelop HEAD的直接后代。 即提交树看起来像这样,因为function已经在dgit rebase

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

那么有可能:

  • 确定function的父分支?
  • 识别父分支中的哪个f是后代的提交?

从那里我会检查父分支的HEAD,看看f前辈是否与父分支HEAD匹配,以确定是否需要重新绑定function。

假设远程仓库有一个开发分支的拷贝(你的初始描述在本地仓库中描述了它,但它听起来像它也存在于远程仓库中),你应该能够实现我认为你想要的,但是方法与你所设想的有些不同。

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 。 尽pipe如此,祖先并没有改变,只有标签发生了变化。

但是,如果我们要求“提交6是提交6的后代?”(假设36是完整的SHA-1提交名称),那么答案是“yes”, barquux标签是否存在或不。

所以,你可以提出一些问题,比如“推送提交是开发分支的当前技巧的后代?”,但是你不能可靠地问“推送提交的父分支是什么?”。

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

对于所有被推送的提交者的祖先(不包括当前的发展尖端及其祖先)

  • 至less有一个这样的提交存在?
  • 都是这样的提交单亲提交?

这可以实现为:

 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 (它们涉及合并,但是它们并不合并开发的提示)。

也要拒绝LP ,你可以改变问题并问

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

  • 有两个父母提交吗?
  • 如果没有,至less有一个这样的承诺有发展其(只)父母的当前技巧?
 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 

一个重述

另外一个问题的表述是:“当前分支以外的分支是什么,最近的分支是哪个分支?

一个办法

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

 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. 有时分支名称将包含一个〜#或^#来表示参考提交和分支提示之间有多less提交。 我们不在乎。 别理他们。

和结果

运行上面的代码

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

如果你从H运行它,并且如果从I运行它,则会给你develop

代码可以作为一个要点

我有一个解决scheme来解决你的整体问题(确定feature是否是从develop ),但它不能用你所概述的方法。

你可以使用git branch --contains来列出所有从develop顶端下来的分支,然后使用grep来确定其中的feature

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

如果是其中之一,则将标准输出打印" feature" ,返回代码为0.否则,将不打印任何数据,返回码为1。

你也可以尝试:

 git log --graph --decorate 

这对我工作很好。

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

来自@droidbot和@Jistanidiot的答案

请记住,正如“Git:查找一个提交来自哪个分支”所描述的那样,你不能容易的find提交的分支(分支可以被重命名,移动,删除…),即使git branch --contains <commit>是一个开始。

  • 你可以从提交回去提交,直到git branch --contains <commit>没有列出feature分支和列表develop分支,
  • 比较那个提交SHA1到/refs/heads/develop

如果这两个提交id匹配,你就可以走了(这意味着feature分支的起源在develop )。

由于没有上述的答案在我们的仓库工作,我想分享我自己的方式,使用最新的合并在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 (并且经常快速转发,所以没有太多的merge commit),这个答案将不会很好,所以我写了一个脚本来提前提交提交(正常和合并)和后面的提交(在父分支中不应该有任何后面的合并)在所有分支比较当前分支。 只要运行这个脚本,让我知道如果适合你的作品

 #!/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 

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

 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命令中完成与所有五个单独的命令filter相同的事情:

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

这样的分解:

 -F'[]^~[]' 

将行分割为]^~[字符。

 /\*/ 

查找包含星号的行

 && !/'"$current_branch"'/ 

…但不是当前的分支名称

 { print $2; 

当你find这样一行时,打印它的第二个字段(也就是字段分隔符的第一个和第二个字符之间的部分)。 对于简单的分支名称,这只是括号内的内容; 对于有相对跳转的裁判来说,这只是没有修饰符的名字。 所以我们的字段分隔符处理sed命令的意图。

  exit } 

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

@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` 

使用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}" /> 
 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/[\^~].*//' 

达到马克·里德的答案相同的目的,但使用一个更安全的方法,不会在许多情况下行事不端:

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

以下是Mark Reed解决scheme的PowerShell实现:

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

现在任何人都希望这样做 – Atlassian的SourceTree应用程序向你展示了你的分支之间的相互关系,也就是说他们开始的地方,以及他们当前在提交顺序中的位置(例如HEAD或4落后等等) 。

如果您使用源代码树查看您的提交详细信息>家长>那么你会看到下划线提交数字(链接)