如何找到 Git 分支的最近父级?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3161204/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to find the nearest parent of a Git branch?
提问by Peter Farmer
Let's say I have the following local repository with a commit tree like this:
假设我有以下本地存储库,其中包含这样的提交树:
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
master
is my this is the latest stable release code, develop
is my this is the 'next' release code, and feature
is a new feature being prepared for develop
.
master
是我的这是最新的稳定版本代码,develop
是我的这是“下一个”版本代码,feature
是正在准备的新功能develop
。
What I want to be able to do on my remote repo using hooks, is for pushes to feature
to be refused unless commit f
is a direct descendant of develop
HEAD. i.e. the commit tree looks like this because feature has been git rebase
on d
.
我希望能够使用钩子在我的远程仓库上做的feature
是拒绝推送,除非提交f
是develop
HEAD的直接后代。即提交树看起来像这样,因为功能已经git rebase
开启d
。
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
So is it possible to:
那么是否有可能:
- Identify the parent branch of
feature
? - Identify the commit in parent branch which
f
is a descendant of?
- 确定
feature
? - 确定父分支中的提交哪个
f
是其后代?
From there I would check what HEAD of parent branch is, and see if f
predecessor matches the parent branch HEAD, to determine if the feature needs to be rebased.
从那里我将检查父分支的 HEAD 是什么,并查看f
前任是否与父分支 HEAD 匹配,以确定该功能是否需要重新定位。
采纳答案by Chris Johnsen
Assuming that the remote repository has a copy of the developbranch (your initial description describes it in a local repository, but it sounds like it also exists in the remote), you should be able to achieve what I think you want, but the approach is a bit different from what you have envisioned.
假设远程存储库有一个开发分支的副本(您的初始描述在本地存储库中描述了它,但听起来它也存在于远程存储库中),您应该能够实现我认为您想要的,但是方法和你想象的有点不一样。
Git's history is based on a DAGof commits. Branches (and “refs” in general) are just transient labels that point to specific commits in the continually growing commit DAG. As such, the relationship between branches can vary over time, but the relationship between commits does not.
Git 的历史基于提交的DAG。分支(和一般的“refs”)只是临时标签,指向不断增长的提交 DAG 中的特定提交。因此,分支之间的关系会随着时间而变化,但提交之间的关系不会。
---o---1 foo
\
2---3---o bar
\
4
\
5---6 baz
It looks like baz
is based on (an old version of) bar
? But what if we delete bar
?
看起来baz
是基于(旧版本)bar
?但是如果我们删除bar
呢?
---o---1 foo
\
2---3
\
4
\
5---6 baz
Now it looks like baz
is based on foo
. But the ancestry of baz
did not change, we just removed a label (and the resulting dangling commit). And what if we add a new label at 4
?
现在看起来baz
是基于foo
. 但是祖先baz
没有改变,我们只是删除了一个标签(以及由此产生的悬空提交)。如果我们在 处添加一个新标签4
呢?
---o---1 foo
\
2---3
\
4 quux
\
5---6 baz
Now it looks like baz
is based on quux
. Still, the ancestry did not change, only the labels changed.
现在看起来baz
是基于quux
. 尽管如此,血统没有改变,只是标签改变了。
If, however, we were asking “is commit 6
a descendent of commit 3
?” (assuming 3
and 6
are full SHA-1 commit names), then the answer would be “yes”, whether the bar
and quux
labels are present or not.
但是,如果我们问“ commit 是 commit6
的后代3
吗?” (假设3
和6
是完整的 SHA-1 提交名称),那么无论bar
和quux
标签是否存在,答案都是“是” 。
So, you could ask questions like “is the pushed commit a descendent of the current tip of the developbranch?”, but you can not reliably ask “what is the parent branch of the pushed commit?”.
因此,您可以问诸如“推送的提交是否是开发分支当前提示的后代?”之类的问题,但您不能可靠地问“推送提交的父分支是什么?”。
A mostly reliable question that seems to get close to what you want is:
一个似乎接近你想要的最可靠的问题是:
For all the pushed commit's ancestors (excluding the current tip of developand its ancestors), that have the current tip of developas a parent:
- does at least one such commit exist?
- are all such commits single-parent commits?
对于所有推送提交的祖先(不包括当前的develop提示及其祖先),将当前的develop提示作为父项:
- 至少存在一个这样的提交吗?
- 所有这些提交都是单亲提交吗?
Which could be implemented as:
可以实现为:
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
This will cover some of what you want restricted, but maybe not everything.
这将涵盖您想要限制的一些内容,但可能不是所有内容。
For reference, here is an extended example history:
作为参考,这是一个扩展的示例历史:
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
The above code could be used to reject H
and S
while accepting H'
, J
, K
, or N
, but it would also accept L
and P
(they involve merges, but they do not merge the tip of develop).
上面的代码可以用来拒绝H
and S
while 接受H'
, J
, K
, or N
,但它也会接受L
and P
(它们涉及合并,但它们不合并develop的提示)。
To also reject L
and P
, you can change the question and ask
要也拒绝L
和P
,您可以更改问题并询问
For all the pushed commit's ancestors (excluding the current tip of developand its ancestors):
- are there any commits with two parents?
- if not, does at least one such commit have the current tip of developits (only) parent?
对于所有推送提交的祖先(不包括开发的当前提示及其祖先):
- 有两个父母有任何承诺吗?
- 如果没有,是否至少有一个这样的提交具有开发其(唯一)父级的当前提示?
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
回答by Joe Chrysler
A rephrasal
改写
Another way to phrase the question is "What is the nearest commit that resides on a branch other than the current branch, and which branch is that?"
表达这个问题的另一种方式是“驻留在当前分支以外的分支上最近的提交是什么,那是哪个分支?”
A solution
一个办法
You can find it with a little bit of command line magic
你可以用一点命令行魔法找到它
git show-branch \
| sed "s/].*//" \
| grep "\*" \
| grep -v "$(git rev-parse --abbrev-ref HEAD)" \
| head -n1 \
| sed "s/^.*\[//"
With awk:
使用awk:
git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/[^\[]*//' \
| awk 'match( A---B---D <-master
\
\
C---E---I <-develop
\
\
F---G---H <-topic
, /\[[a-zA-Z0-9\/-]+\]/) { print substr( git log --graph --decorate
, RSTART+1, RLENGTH-2 )}'
Here's how it works:
这是它的工作原理:
- Display a textual history of all commits, including remote branches.
- Ancestors of the current commit are indicated by a star. Filter out everything else.
- Ignore all the commits in the current branch.
- The first result will be the nearest ancestor branch. Ignore the other results.
- Branch names are displayed [in brackets]. Ignore everything outside the brackets, and the brackets.
- Sometimes the branch name will include a ~# or ^# to indicate how many commits are between the referenced commit and the branch tip. We don't care. Ignore them.
- 显示所有提交的文本历史,包括远程分支。
- 当前提交的祖先用星号表示。过滤掉其他一切。
- 忽略当前分支中的所有提交。
- 第一个结果将是最近的祖先分支。忽略其他结果。
- 分支名称显示在 [括号中]。忽略括号外的所有内容和括号。
- 有时,分支名称会包含 ~# 或 ^# 以指示引用的提交和分支提示之间有多少提交。我们不在乎。别理他们。
And the Result
结果
Running the above code on
运行上面的代码
vim ~/.gitconfig
Will give you develop
if you run it from H and master
if you run it from I.
develop
如果你从 H 运行它,master
如果你从 I 运行它,会给你。
回答by rcde0
You can also try:
你也可以试试:
[alias]
parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//' #"
回答by NIKHIL C M
git parent
git 父母
You can just run the command
你可以只运行命令
git parent
git parent
to find the parent of the branch, if you add the @Joe Chrysler's answer as a git alias. It will simplify the usage.
找到分支的父级,如果您将@Joe Chrysler的答案添加为git alias。它将简化使用。
Open gitconfig file located at "~/.gitconfig"
by using any text editor. ( For linux). And for Windows the ".gitconfig" path is generally located at c:\users\your-user\.gitconfig
"~/.gitconfig"
使用任何文本编辑器打开位于 的 gitconfig 文件。(对于Linux)。对于 Windows,“.gitconfig”路径通常位于c:\users\your-user\.gitconfig
git branch --contains develop | grep "^ *feature$"
Add the following alias command in the file:
在文件中添加以下别名命令:
git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*//' | sed 's/[\^~].*//'
Save and exit the editor.
保存并退出编辑器。
Run the command git parent
运行命令 git parent
That's it!
就是这样!
回答by Daniel Stutzbach
I have a solution to your overall problem (determine if feature
is descended from the tip of develop
), but it doesn't work using the method you outlined.
我有一个解决您的整体问题的解决方案(确定是否feature
从 的尖端下降develop
),但使用您概述的方法不起作用。
You can use git branch --contains
to list all the branches descended from the tip of develop
, then use grep
to make sure feature
is among them.
您可以使用git branch --contains
列出从 的尖端下降的所有分支develop
,然后使用grep
来确保feature
在其中。
#!/bin/bash
git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10
If it is among them, it will print " feature"
to standard output and have a return code of 0. Otherwise, it will print nothing and have a return code of 1.
如果在其中,则打印" feature"
到标准输出,返回码为 0。否则,不打印任何内容,返回码为 1。
回答by Murali Mopuru
This working fine for me.
这对我来说很好用。
#!/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
Courtesy answers from: @droidbot and @Jistanidiot
礼貌回答:@droidbot 和@Jistanidiot
回答by saeedgnu
Since none of the answers above worked on our repository, I want to share my own way, using latest merges in git log
:
由于上述答案都不适用于我们的存储库,我想分享我自己的方式,使用最新的合并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/.* (\(.*\)) .*//' \ # filters out everything but decorations
| sed 's/\(.*\), .*//' \ # picks only the first decoration
| sed 's/origin\///' # strips "origin/" from the decoration
Put it in a script named git-last-merges
, which also accepts a branch name as argument (instead of current branch) as well as other git log
arguments
把它放在一个名为 的脚本中git-last-merges
,它也接受一个分支名称作为参数(而不是当前分支)以及其他git log
参数
From the output, we can manually detect the parent branch(es) based on own branching conventions and number of merges from each branch.
从输出中,我们可以根据自己的分支约定和每个分支的合并数量手动检测父分支。
EDIT:If you use git rebase
on child branches often (and merges are fast-forwarded often so there aren't too many merge commits), this answer won't work well, so I wrote a script to count ahead commits (normal and merge), and behind commits (there shouldn't be any behind merge in parent branch) on all branches comparing to the current branch. Just run this script and let me know if works for you or not
编辑:如果您git rebase
经常在子分支上使用(并且合并经常快进,所以没有太多合并提交),这个答案不会很好用,所以我写了一个脚本来计算提交(正常和合并) , 以及与当前分支相比的所有分支上的落后提交(父分支中不应有任何落后合并)。只需运行此脚本,让我知道是否适合您
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
回答by Matt Stuvysant
A solution
一个办法
The solution based on git show-branch
did not quite work for me (see below), so I've combined it with the one based on git log
and ended up with this:
该解决方案基于git show-branch
并没有为我(见下文)相当的工作,所以我用一个组合是基于git log
并结束了与此:
cur_branch=$(git rev-parse --abbrev-ref HEAD)
Limitations and Caveats
限制和注意事项
- HEAD can be detached (many CI tools do so to ensure they build correct commit in a given branch), but origin branch and local branchhave to be both at par or "above"the current HEAD.
- There must be no tagsin the way (I presume; I have not tested the script on commits with a tag between child and parent branch)
- the script relies on the fact "HEAD"is always listed as the first decorationby the
log
command - runningthe script on
master
anddevelop
results (mostly) in<SHA> Initial commit
- HEAD 可以分离(许多 CI 工具这样做是为了确保它们在给定分支中构建正确的提交),但源分支和本地分支必须与当前 HEAD相同或“高于”当前 HEAD。
- 不得有任何标记(我猜想;我还没有在子分支和父分支之间带有标记的提交中测试脚本)
- 脚本依赖于以下事实“HEAD”总是被列为第一个装饰用的
log
命令 - 运行该脚本上
master
和develop
结果(大部分)在<SHA> Initial commit
The results
结果
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/.*\[\(.*\)\].*//' | # really, just the part of the line between []
sed 's/[\^~].*//' # and with any relative refs (^, ~n) removed
Despite local branch existence (e.g. only origin/topic
is present since the commit O
was checked-out by directly by its SHA), the script should print as follows:
尽管存在本地分支(例如,仅origin/topic
在提交O
由其 SHA 直接检出后才存在),脚本应打印如下:
- For commits
G
,H
,I
(branchhotfix
) →master
- For commits
M
,N
,O
(branchfeature/a
) →develop
- For commits
S
,T
,U
(branchfeature/c
) →develop
- For commits
P
,Q
,R
(branchfeature/b
) →feature/a
- For commits
J
,K
,L
(branchdevelop
) →<sha> Initial commit
* - For commits
B
,D
,E
,F
(branchmaster
) →<sha> Initial commit
- 对于提交
G
,H
,I
(branchhotfix
) →master
- 对于提交
M
,N
,O
(branchfeature/a
) →develop
- 对于提交
S
,T
,U
(branchfeature/c
) →develop
- 对于提交
P
,Q
,R
(branchfeature/b
) →feature/a
- 对于提交
J
,K
,L
(branchdevelop
) →<sha> Initial commit
* - 对于提交
B
,D
,E
,F
(branchmaster
) →<sha> Initial commit
* - or master
if develop
's commits were on top of master's HEAD (~ the master would be fast-forwardable to develop)
* - 或者master
如果develop
's commits 在 master 的 HEAD 之上(~ master 可以快速开发)
Why did not show-branch work for me
为什么 show-branch 对我不起作用
The solution based on git show-branch
proved unreliable for me in the following situations:
在以下情况下,基于git show-branch
的解决方案对我来说是不可靠的:
- detached HEAD– including detached head case means replacing
grep '\*' \
for `grep '!' \ – and that is just the beginning of all the troubles - runningthe script on
master
anddevelop
results indevelop
and `` respectively - branches on
master
branch (hotfix/
branches) end up with thedevelop
as a parent since their closestmaster
branch parent was marked with!
instead of*
for a reason.
- 分离的 HEAD- 包括分离的头盒意味着替换
grep '\*' \
“grep”!\ – 而这只是所有麻烦的开始 - 运行该脚本上
master
,并develop
导致develop
和``分别 master
分支(hotfix/
分支)上的分支最终develop
以 为父级,因为它们最近的master
分支父级被标记为 ,!
而不是*
出于某种原因。
回答by VonC
Remember that, as described in "Git: Finding what branch a commit came from", you cannot easily pinpoint the branch where that commit has been made (branches can be renamed, moved, deleted...), even though git branch --contains <commit>
is a start.
请记住,如“Git:查找提交来自哪个分支”中所述,即使git branch --contains <commit>
是一个开始,您也无法轻松确定进行该提交的分支(可以重命名、移动、删除分支...)。
- You can go back from commit to commit until
git branch --contains <commit>
doesn't list thefeature
branch and listdevelop
branch, - compare that commit SHA1 to
/refs/heads/develop
- 您可以从提交返回到提交,直到
git branch --contains <commit>
不列出feature
分支并列出develop
分支, - 将提交 SHA1 与
/refs/heads/develop
If the two commits id match, you are good to go (that would mean the feature
branch has its origin at the HEAD of develop
).
如果两个提交 id 匹配,您就可以开始了(这意味着feature
分支的起点位于 的 HEAD develop
)。
回答by Mark Reed
JoeChrysler's command-line magic can be simplified. Here's Joe's logic - for brevity I've introduced a parameter named cur_branch
in place of the command substitution `git rev-parse --abbrev-ref HEAD`
into both versions; that can be initialized like so:
JoeChrysler 的命令行魔法可以被简化。这是 Joe 的逻辑——为简洁起见,我在两个版本中都引入了一个参数cur_branch
来代替命令替换`git rev-parse --abbrev-ref HEAD`
;可以像这样初始化:
git show-branch -a |
awk -F'[]^~[]' '/\*/ && !/'"$cur_branch"'/ {print ;exit}'
Then, here's Joe's pipeline:
然后,这是乔的管道:
-F'[]^~[]'
We can accomplish the same thing as all five of those individual command filters in a relatively simple awk
command:
我们可以在一个相对简单的awk
命令中完成与所有五个单独的命令过滤器相同的事情:
/\*/
That breaks down like this:
分解如下:
&& !/'"$cur_branch"'/
split the line into fields at ]
, ^
, ~
, and [
characters.
分割线成字段]
,^
,~
,和[
字符。
{ print ;
Find lines that contain an asterisk
查找包含星号的行
exit }
...but not the current branch name
...但不是当前的分支名称
##代码##When you find such a line, print its second field (that is, the part between the first and second occurrences of our field separator characters). For simple branch names, that will be just what's between the brackets; for refs with relative jumps, it will be just the name without the modifier. So our set of field separators handles the intent of both sed
commands.
当你找到这样的一行时,打印它的第二个字段(即我们的字段分隔符第一次和第二次出现之间的部分)。对于简单的分支名称,这就是括号之间的内容;对于具有相对跳转的引用,它将只是没有修饰符的名称。所以我们的字段分隔符集处理两个sed
命令的意图。
Then exit immediately. This means it only ever processes the first matching line, so we don't need to pipe the output through head -n 1
.
然后立即退出。这意味着它只处理第一条匹配的行,所以我们不需要通过head -n 1
.