假设我有以下本地存储库,其中包含如下提交树:
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
master
是我这是最新的稳定版本代码,develop
是我的这是'下一个'版本代码,{ {1}} 是为feature
准备的新功能。
除了提交develop
是feature
HEAD的直接后代之外,我希望能够使用钩子对我的远程仓库执行操作,以便推送到f
。即提交树看起来像这样,因为develop
上的功能已git rebase
。
d
所以有可能:
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
?feature
是[从那里我将检查父分支的HEAD是什么,并查看f
前任是否与父分支HEAD匹配,以确定该特征是否需要重新定位。
答案 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
?”(假设3
和6
是完整的SHA-1提交名称) ,那么答案就是“是”,是否存在bar
和quux
标签。
所以,你可以问一些诸如“推送提交是 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
以上代码可用于在接受H
,S
,H'
或J
时拒绝K
和N
,但它也会接受L
和P
(它们涉及合并,但它们不会合并 develop 的提示)。
要拒绝L
和P
,您也可以更改问题并询问
对于所有被推送的提交的祖先(不包括 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)
另一种表达问题的方法是“除当前分支之外的分支上最近的提交是什么,以及哪个分支是什么?”
你可以通过一点点命令行魔术来找到它
git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/.*\[\(.*\)\].*/\1/' \
| sed 's/[\^~].*//'
在
上运行上述代码 A---B---D <-master
\
\
C---E---I <-develop
\
\
F---G---H <-topic
如果您从H运行它,将会给您develop
,如果您从I运行它,则会master
。
答案 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
查找分支的父级(如果您添加 @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
分支,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
log
命令始终将“ HEAD” 列为第一个装饰。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
)→ master
M
,N
,O
(分支feature/a
)→ develop
S
,T
,U
(分支feature/c
)→ develop
P
,Q
,R
(分支feature/b
)→ feature/a
J
,K
,L
(分支develop
)→<sha> Initial commit
* B
,D
,E
,F
(分支master
)→<sha> Initial commit
*-或master
,如果develop
的提交位于master的HEAD之上(〜master将是可快速转发的)
在以下情况下,解决方案based on git show-branch
对我来说是不可靠的:
grep '\*' \
替换为'grep'!' \ –这只是所有麻烦的开始master
和develop
上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的答案相同的目的,但使用了一种更安全的方法,在许多场景中都没有行为不端:
-
不是*
*
答案 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/a
和 important/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提交等等。)。