Answers:
我在寻找相同的东西,但我发现了这个问题。谢谢您的询问!
但是,我发现我在这里看到的答案似乎并不能完全满足您所要求的(或我一直在寻找的)答案,它们似乎是在G
提交而不是A
提交。
因此,我创建了以下树(按时间顺序分配的字母),以便进行测试:
A - B - D - F - G <- "master" branch (at G)
\ \ /
C - E --' <- "topic" branch (still at E)
这看起来与您的有所不同,因为我想确保我(参考此图,不是您的图)是B,但不是A(也不是D或E)。这是附加在SHA前缀和提交消息上的字母(可以从这里克隆我的repo ,如果任何人都感兴趣的话):
G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master
所以,目标:二分找到B。经过一番修补,我发现了以下三种方法:
您应该在视觉上看到像这样的树(从主视图看):
或此处(从主题查看):
在这两种情况下,我都选择了B
图形中的提交。单击它之后,它的完整SHA就会显示在图形下方的文本输入字段中。
git log --graph --oneline --all
(编辑/旁注:添加--decorate
也可能很有趣;它添加了分支名称,标记等的指示。由于下面的输出不能反映其用法,因此不将其添加到上面的命令行中。)
显示(假设git config --global color.ui auto
):
或者,以纯文本形式:
* a9546a2从主题合并回主目录 | \ | * 648ca35将母版合并到主题中 | | \ | * | 132ee2a在主题分支上的第一次提交 * | | 将master合并到主题后,e7c863d在master上提交 | | / | / | * | 在主节点上的37ad159分支后提交 | / * 6aafd7f在分支之前对主机进行第二次提交 * 4112403主控上的初始提交
无论哪种情况,我们都将6aafd7f提交视为最低的公共点,即B
在我的图形中还是A
在您的图形中。
您无需在问题中指定是要上述内容,还是要通过一个命令获取一个修订版本的命令,而没有其他要求。好吧,这是后者:
diff -u <(git rev-list --first-parent topic) \
<(git rev-list --first-parent master) | \
sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d
您也可以将其放入〜/ .gitconfig中(请注意:尾部的破折号很重要;感谢Brian引起注意):
[alias]
oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -
这可以通过以下命令行(用引号引起来)完成:
git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'
注:zsh
可以很容易地一直bash
,但sh
不会不工作-在<()
语法不存在香草sh
。(再次感谢@conny,在此页面上对另一个答案的评论中让我意识到了这一点!)
由于liori用于指出在比较相同分支当上述可以落下,并想出替代的diff形式其去除从所述混合物中的sed形式,并且使这种“安全”的(即,它返回一个结果(即,最新提交),即使您将master与master进行比较):
作为.git-config行:
[alias]
oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -
从外壳:
git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'
因此,在我的测试树中(暂时无法使用,很抱歉;它又回来了),现在可以同时在master和topic上使用(分别提供提交G和B)。liori,再次感谢您提供其他表格。
因此,这就是我[和liori]提出的。它似乎为我工作。它还允许使用其他可能方便使用的别名:
git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'
快乐的混蛋!
diff
'if-then-else'模式更安全,并且从输出中删除更改/删除的行,而不是依靠足够大的上下文diff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1
。
git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~
也能发挥作用,我认为
git merge-base --fork-point ...
提供提交B(commit 6aafd7f
)?当您发布该消息时,我正忙于其他内容,听起来不错,但我终于尝试了一下,但没有使它正常工作……我得到的是最新的消息,或者只是无声的失败(没有错误)消息,但退出状态1),试图论证一样git co master; git merge-base --fork-point topic
,git co topic; git merge-base --fork-point master
,git merge-base --fork-point topic master
(对于结帐)等有什么我做错了或丢失?
--fork-point
基于reflog,因此仅当您在本地进行更改时,它才起作用。即使这样,reflog条目也可能已过期。这很有用,但一点都不可靠。
您可能正在寻找git merge-base
:
git merge-base在两次提交之间找到最佳的共同祖先,以用于三向合并。如果另一个共同祖先是前一个共同祖先,则它比另一个共同祖先更好。没有更好的共同祖先的共同祖先就是最好的共同祖先,即合并基础。请注意,一对提交可以有多个合并基础。
--all
“ git merge-base
” 的选项
git merge-base
在发布答案大约五个小时后添加该笔记(可能是针对我的回答)。不过,我将保留此答案,因为它对于通过搜索找到此问题的其他人可能仍然有用。
我已经习惯git rev-list
了这种事情。例如,(请注意3个点)
$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-
将吐出分支点。现在,它并不完美;由于您已将母版合并到分支A中两次,因此将拆分出几个可能的分支点(基本上是原始分支点,然后是母版合并到分支A中的每个点)。但是,它至少应该缩小可能性。
我已经将该命令添加为别名~/.gitconfig
:
[alias]
diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'
所以我可以这样称呼它:
$ git diverges branch-a master
G
而不是给出A
。)我有一个答案,该答案A
将在当前发布。
如果您喜欢简洁的命令,
git rev-list $(git rev-list --first-parent ^ branch_name master | tail -n1)^^!
这是一个解释。
以下命令为您提供了创建branch_name之后在master中进行的所有提交的列表
git rev-list --first-parent ^ branch_name主
由于您只关心最早的那些提交,因此需要输出的最后一行:
git rev-list ^ branch_name --first-parent主| 尾-n1
根据定义,最早提交而不是“ branch_name”的祖先的父级在 “ branch_name”中,并且在“ master”中,因为它是“ master”中某个事物的祖先。因此,您在两个分支中都拥有最早的提交。
命令
git rev-list提交^^!
只是显示父提交参考的一种方法。你可以用
git log -1提交^
管他呢。
PS:我不同意祖先顺序无关紧要的说法。这取决于您想要什么。例如,在这种情况下
_C1___C2_________主 \ \ _XXXXX_分支A(X表示主节点和A之间的任意交叉) \ _____ / B支
最好将C2作为“分支”提交输出。这是开发人员从“ master”分支出来的时候。当他分支时,分支“ B”甚至没有合并到他的分支中!这就是本文中提供的解决方案。
如果您想要的是最后一个提交C,使得从原点到分支“ A”上的最后一个提交的所有路径都经过C,那么您想忽略祖先顺序。这纯粹是拓扑结构,可让您有了一个想法,因为当您同时有两个版本的代码时。那时您将使用基于合并的方法,并且在我的示例中它将返回C1。
git rev-list commit^^!
可以简化为git rev-parse commit^
git rev-list --first-parent ^branch_name master
,git rev-list --first-parent branch_name ^master
因为如果master分支为0,则比另一个分支(可快速转发到它)提前提交,则不会创建任何输出。对于我的解决方案,如果master严格位于前面(即分支已完全合并),则不会创建任何输出,这就是我想要的。
git rev-list --first-parent ^topic master
只会将您带回到最后一次合并master
到topic
(如果存在)的第一次提交。
鉴于该线程中的许多答案都没有给出问题所要求的答案,因此这里是每个解决方案结果的摘要,以及我用来复制问题中给定存储库的脚本。
创建具有给定结构的存储库,我们得到以下内容的git日志:
$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| * 3bd4054 (master) F - Merge branch_A into branch master
| |\
| |/
|/|
* | a06711b I - Merge master into branch_A
|\ \
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/
| * 413851d C - Merge branch_A into branch master
| |\
| |/
|/|
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master
我唯一的补充是标记,它使我们可以清楚地了解创建分支的点以及因此希望查找的提交。
唯一有效的解决方案是lindes提供的解决方案正确返回A
:
$ diff -u <(git rev-list --first-parent branch_A) \
<(git rev-list --first-parent master) | \
sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5
正如Charles Bailey指出的那样,这种解决方案非常脆弱。
如果你branch_A
成master
,然后合并master
成branch_A
中间没有提交,然后lindes'解决方案只为您提供最新的第一divergance。
这意味着对于我的工作流程,我认为我将不得不坚持对长期运行的分支的分支点进行标记,因为我无法保证以后可以可靠地找到它们。
这实际上归结为git
缺乏hg
所谓的分支机构。博客jhw 在他的文章《为什么我比Git更喜欢Mercurial》和他的后续文章More On Mercurial vs. Git(带有Graphs!)中将这些世系与家庭联系起来。我建议人们阅读它们,看看为什么一些信奉宗教的信徒错过了命名的分支机构在git
。
mipadi提供的解决方案返回两个答案,I
并且C
:
$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc
Greg Hewgill提供的解决方案返回I
$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
Karl提供的解决方案返回X
:
$ diff -u <(git log --pretty=oneline branch_A) \
<(git log --pretty=oneline master) | \
tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5
mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"
我怀疑git版本对此有什么影响,但是:
$ git --version
git version 1.7.1
感谢Charles Bailey向我展示了一种更紧凑的方式编写示例存储库脚本。
diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
。感谢您提供创建存储库的说明:)
通常,这是不可能的。在分支历史中,分支和合并在分支命名分支之前发生,并且两个命名分支的中间分支看起来相同。
在git中,分支只是历史部分提示的当前名称。他们确实没有很强的身份。
这通常不是什么大问题,因为两个提交的merge-base(请参阅Greg Hewgill的答案)通常更有用,给出两个分支共享的最新提交。
在分支机构的历史记录中某个时刻已完全集成分支机构的情况下,依靠提交的父级顺序的解决方案显然行不通。
git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"
如果已经进行了合并合并而其父级相反,则该技术也会失败(例如,使用临时分支将测试合并到master中,然后快速转发到feature分支中以进一步构建)。
git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"
git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch
git checkout master
git commit --allow-empty -m "More work on master"
git
具有hg
与命名分支相同的名称,它将使管理长期维护的分支变得如此容易。
怎么样
git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2
git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^
diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'
无临时文件)
G
而不是A
。)不过,我认为我已经找到了答案,该答案将在当前发布。
diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
当然,我缺少一些东西,但是IMO,上述所有问题都是由于我们一直试图找到历史上的分支点而引起的,并且由于可用的合并组合而导致了各种各样的问题。
相反,我采用了不同的方法,因为两个分支共享很多历史记录,而分支之前的所有历史记录都完全相同,因此100%相同,因此,我的提议不是从前开始,而是从第一个开始提交),寻找两个分支中的第一个区别。分支点将简单地是找到的第一个差异的父级。
在实践中:
#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
<( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1
它解决了我所有的常见情况。当然有边境的不包括在内,但是... ciao :-)
comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1
我最近也需要解决这个问题,并最终为此编写了一个Ruby脚本:https : //github.com/vaneyckt/git-find-branching-point
经过大量的研究和讨论,很明显没有万能的子弹可以在所有情况下都起作用,至少在当前版本的Git中不行。
这就是为什么我写了几个补丁来添加tail
分支概念的原因。每次创建分支时,也会创建指向原始点的指针,tail
ref。每当分支重新建立基础时,此引用都会更新。
要找出devel分支的分支点,您所要做的就是使用 devel@{tail}
,就是这样。
这是我先前的回答先前的回答的改进版本。它依靠来自合并的提交消息来查找分支的首次创建位置。
它适用于此处提到的所有存储库,我什至还解决了邮件列表中产生的一些棘手问题。我也为此编写了测试。
find_merge ()
{
local selection extra
test "$2" && extra=" into $2"
git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}
branch_point ()
{
local first_merge second_merge merge
first_merge=$(find_merge $1 "" "$1 $2")
second_merge=$(find_merge $2 $1 $first_merge)
merge=${second_merge:-$first_merge}
if [ "$merge" ]; then
git merge-base $merge^1 $merge^2
else
git merge-base $1 $2
fi
}
要从分支点查找提交,可以使用它。
git log --ancestry-path master..topicbranch
有时这实际上是不可能的(某些情况下您可能会幸运地拥有更多数据),并且此处的解决方案将行不通。
Git不保留引用历史记录(包括分支)。它仅存储每个分支(头)的当前位置。这意味着随着时间的流逝,您可能会丢失git中的某些分支历史记录。例如,无论何时分支,它都会立即丢失哪个分支是原始分支。分支机构所做的全部是:
git checkout branch1 # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1
您可能假设第一个提交到的是分支。情况往往如此,但并非总是如此。完成上述操作后,没有什么可以阻止您首先提交任何一个分支。另外,不能保证git时间戳是可靠的。直到您承诺两者都真正在结构上成为分支。
虽然在图中,我们倾向于在概念上对提交进行编号,但是当提交树分支时,git没有真正稳定的序列概念。在这种情况下,您可以假设数字(指示顺序)是由时间戳确定的(将所有时间戳设置为相同时,看看git UI如何处理事物很有趣)。
这是人类在概念上的期望:
After branch:
C1 (B1)
/
-
\
C1 (B2)
After first commit:
C1 (B1)
/
-
\
C1 - C2 (B2)
这是您实际得到的:
After branch:
- C1 (B1) (B2)
After first commit (human):
- C1 (B1)
\
C2 (B2)
After first commit (real):
- C1 (B1) - C2 (B2)
您将假设B1是原始分支,但实际上它实际上可能是一个死分支(有人签出-b但从未提交)。直到您都承诺在git中获得合法的分支结构后,您才能:
Either:
/ - C2 (B1)
-- C1
\ - C3 (B2)
Or:
/ - C3 (B1)
-- C1
\ - C2 (B2)
您始终知道C1早于C2和C3,但您永远无法可靠地知道C2早于C3还是C3早于C2(因为您可以将工作站上的时间设置为任何时间)。B1和B2也会产生误导,因为您不知道哪个分支先出现。在许多情况下,您都可以做出非常好且通常准确的猜测。这有点像比赛跑道。一般情况下,所有事物都与赛车相同,那么您可以假设一辆落后一圈的赛车开始了落后一圈的比赛。我们也有非常可靠的约定,例如master几乎总是代表寿命最长的分支,尽管可悲的是,我看到的情况甚至不是这样。
此处给出的示例是历史保存示例:
Human:
- X - A - B - C - D - F (B1)
\ / \ /
G - H ----- I - J (B2)
Real:
B ----- C - D - F (B1)
/ / \ /
- X - A / \ /
\ / \ /
G - H ----- I - J (B2)
这里的真实也令人误解,因为我们人类从左到右阅读,从根到叶(参考)。Git不会那样做。git在(A <-B或B-> A)中执行(A-> B)。它从ref读取到根。引用可以在任何地方,但往往是叶子,至少对于活动分支而言。ref指向一个提交,并且提交仅包含其父母(而不是其孩子)的赞。当提交是合并提交时,它将有多个父级。第一父级始终是合并到其中的原始提交。其他父项始终是已合并到原始提交中的提交。
Paths:
F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
这不是一个非常有效的表示,而是git可以从每个ref(B1和B2)获取的所有路径的表达。
Git的内部存储看起来更像这样(不是作为父级的A出现两次):
F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A
如果您转储原始的git commit,您将看到零个或多个父字段。如果为零,则表示没有父级,并且提交是一个根(实际上可以有多个根)。如果存在,则表示没有合并,而且不是根提交。如果有多个,则表示该提交是合并的结果,而第一个之后的所有父级都是合并提交。
Paths simplified:
F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
F->D->C->B->A | J->I->->G->A | A->X
Topological:
- X - A - B - C - D - F (B1)
\
G - H - I - J (B2)
当两者都命中A时,它们的链将是相同的,然后它们的链将完全不同。第一个提交与另外两个提交的共同点是共同祖先,并且从那里分开。在这里,术语“提交”,“分支”和“引用”之间可能会有一些混淆。实际上,您可以合并一个提交。这就是合并的真正作用。ref只是指向提交,分支仅是文件夹.git / refs / heads中的ref,文件夹的位置决定了ref是分支而不是诸如标签之类的东西。
您失去历史记录的地方是合并将根据情况执行以下两项操作之一。
考虑:
/ - B (B1)
- A
\ - C (B2)
在这种情况下,在任一方向上的合并都将创建一个新提交,其中第一个父级为当前签出分支所指向的提交,第二个父级为合并到当前分支中分支的尖端处的提交。由于两个分支的共同祖先必须合并,因此它必须创建一个新的提交。
/ - B - D (B1)
- A /
\ --- C (B2)
此时,D(B1)现在具有来自两个分支(本身和B2)的两组更改。但是,第二个分支没有对B1的更改。如果将更改从B1合并到B2,以便它们被同步化,那么您可能会期望像这样(可以使用--no-ff强制git merge这样做):
Expected:
/ - B - D (B1)
- A / \
\ --- C - E (B2)
Reality:
/ - B - D (B1) (B2)
- A /
\ --- C
即使B1包含其他提交,您也将得到它。只要B2中没有B1没有的更改,两个分支就会合并。它执行快速前进,就像重新设置一样(重新设置也会消耗或线性化历史记录),除了重新设置不同,因为只有一个分支具有更改集,所以不必将一个分支的更改集应用到另一个分支上。
From:
/ - B - D - E (B1)
- A /
\ --- C (B2)
To:
/ - B - D - E (B1) (B2)
- A /
\ --- C
如果您停止在B1上的工作,那么从长远来看,可以很好地保留历史。通常只有B1(可能是主节点)会前进,因此B2在B2历史记录中的位置成功代表了将其合并到B1中的点。这就是git希望您做的,从A分支到B,然后您可以根据需要将A合并到B中,只要累积更改即可,但是将B合并回A时,不希望您继续使用B并进一步。如果您在将分支快速合并到分支中之后继续进行分支工作,则每次都删除B的先前历史记录。在每次快进提交到源然后提交到分支之后,您实际上每次都在创建一个新分支。
0 1 2 3 4 (B1)
/-\ /-\ /-\ /-\ /
---- - - - -
\-/ \-/ \-/ \-/ \
5 6 7 8 9 (B2)
1到3和5到8是结构分支,如果您遵循4或9的历史记录,则会显示出来。git中没有办法知道这个未命名和未引用的结构分支中,哪个属于命名分支和引用分支。结构的末端。您可能会根据此图假定0到4属于B1,4到9属于B2,但是除了4和9之外,我们不知道哪个分支属于哪个分支,我以一种给出的幻想。0可能属于B2,而5可能属于B1。在这种情况下,有16个不同的可能性,每个结构分支都可以属于每个命名分支。
有很多git策略可以解决此问题。您可以强制git merge永远不要快进,并始终创建一个merge分支。保留分支历史记录的一种可怕方法是根据您选择的约定使用标签和/或分支(确实建议使用标签)。我真的不建议在要合并的分支中使用虚拟的空提交。一个非常常见的约定是,除非您想真正关闭分支,否则不要合并到集成分支。人们应该尝试坚持这种做法,否则您将在拥有分支机构这一点上工作。但是,在现实世界中,理想并不总是可行的,这意味着在每种情况下正确地做事都不可行。如果你是
man git-reflog
以及有关日期的部分:“ master@{one.week.ago}的意思是“ master以前在此本地存储库中指向一周前的位置”。或在讨论<refname>@{<date>}
在man gitrevisions
。而core.reflogExpire
在man git-config
。
这个问题不是一个解决方案,但是我认为值得一提的是当我拥有一个长期存在的分支机构时所使用的方法:
在创建分支的同时,还创建了一个具有相同名称但带有-init
后缀的标记,例如feature-branch
和feature-branch-init
。
(奇怪的是,这是一个很难回答的问题!)
一种简单的方法,可以轻松查看分支点,git log --graph
即使用选项--first-parent
。
$ git log --all --oneline --decorate --graph
* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\
| * 648ca35 (origin/topic) merging master onto topic
| |\
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/
|/|
* | 37ad159 post-branch commit on master
|/
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master
现在添加 --first-parent
:
$ git log --all --oneline --decorate --graph --first-parent
* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) merging master onto topic
| * 132ee2a first commit on topic branch
* | e7c863d commit on master after master was merged to topic
* | 37ad159 post-branch commit on master
|/
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master
这样更容易!
请注意,如果仓库中有很多分支,您将要指定要比较的2个分支,而不是使用--all
:
$ git log --decorate --oneline --graph --first-parent master origin/topic
问题似乎是要在一侧的两个分支之间找到最新的单提交剪切,而在另一侧找到最早的共同祖先(可能是回购的初始提交)。这符合我对“分支”点的直觉。
记住,用普通的git shell命令计算起来根本不容易,因为git rev-list
-我们最强大的工具-无法让我们限制到达提交的路径。我们最接近的是git rev-list --boundary
,它可以为我们提供所有“阻塞方式”的提交。(注意:git rev-list --ancestry-path
很有趣,但在这里我不做介绍。)
这是脚本:https : //gist.github.com/abortz/d464c88923c520b79e3d。它相对简单,但是由于循环的原因,它的复杂程度足以保证其要旨。
请注意,这里提出的大多数其他解决方案可能无法在所有情况下都基于一个简单的原因:在线性git rev-list --first-parent
化历史记录中不可靠,因为可以使用任何一种顺序进行合并。
git rev-list --topo-order
另一方面,对于按地形顺序进行提交非常有用-但进行差异比较脆弱:给定图有多种可能的地形排序,因此您要取决于排序的某种稳定性。就是说,strongk7的解决方案在大多数情况下可能效果很好。但是,由于不得不遍历整个回购记录的历史,所以我的速度较慢...两次。:-)
以下实现了svn log --stop-on-copy的 git等效项,还可以用于查找分支源。
方法
就像所有河流都流向大海一样,所有分支机构都需要掌握,因此我们发现了看似无关的分支机构之间的合并基础。当我们从分支头经过祖先返回时,由于理论上它应该是该分支的起点,因此可以在第一个潜在的合并基础处停止。
笔记
您可以使用以下命令在branch_a中返回最早的提交,而从master无法访问该提交:
git rev-list branch_a ^master | tail -1
也许有一个额外的完整性检查是的父提交是从主竟可达...
我相信我已经找到一种处理这里提到的所有极端情况的方法:
branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2
查尔斯·贝利(Charles Bailey)完全正确,即基于祖先顺序的解决方案仅具有有限的价值。最终,您需要某种“此提交来自分支X”的记录,但该记录已经存在;默认情况下,“ git merge”将使用诸如“将分支'branch_A'合并到master”之类的提交消息,这告诉您第二个父级(commit ^ 2)的所有提交均来自'branch_A',并已合并到第一个父级(commit ^ 1),即“主”。
有了这些信息,您可以找到'branch_A'的第一个合并(这是在'branch_A'真正存在的时候),并找到合并的基础,这将是分支点:)
我已经尝试过Mark Booth和Charles Bailey的存储库,并且该解决方案有效。怎么可能 唯一不起作用的方法是,如果您手动更改了用于合并的默认提交消息,则分支信息确实会丢失。
为了有用:
[alias]
branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'
那你就可以git branch-point branch_A
'。
请享用 ;)
git merge -m
说什么我已经合并了,而不是potentionally短暂分支的名称(如“合并主线变成XYZ功能重构”)。假设我-m
在我的示例中对我的帮助较少?这个问题不能完全解决,因为我可以使用一个或两个临时分支进行相同的历史记录,并且无法分辨出差异。