我的开发团队最近开始使用 Gitlab。我曾建议,在合并请求中我们需要压缩提交。我遭到很多反对,认为这不安全。
典型的功能开发周期包括功能分支上的每日提交和推送。它还包括每天一次 git 从 master 拉取,以与其他更改保持同步。有时这涉及解决合并冲突。
他们认为所有这些从 master 合并,然后向 master 执行合并请求并压缩提交可能会导致可能未检测到的合并问题。
有没有道理呢?我的假设是,无论如何,压缩提交都应该是安全的。
tor*_*rek 13
冒着重复bk2204 的答案(部分甚至全部)内容的风险,我将对此进行尝试。
\n\n(请注意,任何答案都存在问题,因为您使用了“安全”这个词。这不是一个具有清晰明显的技术定义的术语。在一种情况下完全安全的东西在另一种情况下可能不太安全。类比总是如果用力过猛,就会崩溃,但请考虑一下,例如,在没有穿戴手部、膝盖和肘部保护的情况下走在街上,与穿着溜冰鞋\xe2\x80\x94 在街道上疾驰\xe2\x80\x94 或者我应该说“直排轮滑鞋”吗?\xe2 \x80\x94 没有上述保护。)
\n\n我总是喜欢说 Git 与文件无关,而是与提交(然后保存文件)有关。这在本例中也成立:
\n\ngit merge --squash
所做的提交不同。git merge
在这种情况下,全部差异在于最终提交的父链接。从表面上看,这似乎是一个微小的差异……但是 Git 中的提交链接方式已经成为历史。没有文件历史记录这样的东西:只有提交历史记录。因此,这个微小的差异实际上是巨大的\xe2\x80\x94,特别是当你考虑挤压合并通常会如何影响人们的未来行为时。
\n\n要了解其在实践中的工作原理,有必要了解提交图。这让我们了解了我在上面第一个要点中提到的元数据。
\n\n我发现有时查看真实提交的实际具体内容很有帮助。这是 Git 本身在 Git 存储库中的提交:
\n\n$ git rev-parse HEAD\n08da6496b61341ec45eac36afcc8f94242763468\n$ git cat-file -p 08da6496b61341ec45eac36afcc8f94242763468 | sed \'s/@/ /\'\ntree 27fee9c50528ef1b0960c2c342ff139e36ce2076\nparent 07f25ad8b235c2682a36ee76d59734ec3f08c413\nauthor Junio C Hamano <gitster pobox.com> 1570770961 +0900\ncommitter Junio C Hamano <gitster pobox.com> 1570771489 +0900\n\nEighth batch\n\nSigned-off-by: Junio C Hamano <gitster pobox.com>\n
Run Code Online (Sandbox Code Playgroud)\n\n(我用@
空格替换了 可能只是为了减少发送到 pobox.com 的垃圾邮件数量。请注意,您实际上也可以git cat-file -p HEAD
:我在这里使用原始哈希 ID 来帮助巩固这样的想法:是重要的哈希 ID。)
这实际上是提交的完整文本。哈希 ID08da6496b61341ec45eac36afcc8f94242763468
是该内容的加密校验和。1 Git 间接将快照本身存储为树对象,2它有自己的哈希 ID。文本的其余部分是元数据的其余部分:作者和提交者(姓名、电子邮件地址以及日期和时间戳);父提交,按哈希 ID 列出;和日志消息。
因此,每个提交都有自己唯一的哈希 ID。该哈希 ID 指的是该提交;其他提交不能具有相同的哈希 ID。3 此外,每个提交通过哈希 ID 引用其直接父提交。这些父哈希 ID 必须是在此提交之前存在的某些提交的 ID。对于普通(非合并)提交,(单个)父级是您在提交时签出的任何提交。
\n\n当我们查看一个只有 3 个提交的新 Git 存储库时,我们可以通过使用单个大写字母代表实际的哈希 ID 来得出这一点:
\n\nA <-B <-C\n
Run Code Online (Sandbox Code Playgroud)\n\n在这里,commitA
是我们所做的第一个提交。它没有父项,因为父项是在 commit 之前存在的提交A
,并且没有父项。这种特殊情况就是 Git 所说的根提交。4 但是一旦我们提交了A
,我们就进行了提交B
,所以B
\ 的父级是A
。B
类似地,我们在进行 commit 时使用了 commit C
,因此C
\ 的父级是B
。
当某个东西保存了提交的哈希 ID 时,我们说该东西指向该提交。所以 commitC
指向B
,它又指向A
,这是一个根提交,但那里没有任何东西指向。
在这些图中,很容易看出链的终点在哪里,但在真实的 Git 存储库中,具有真实的\xe2\x80\x94 和随机的\xe2\x80\x94 哈希 ID,这很难。此时,我们需要一种快速简便的方法来找到commit 。因此,Git 为我们提供了分支名称C
,部分是为了安慰我们这些无法记住原始哈希 ID 的人。
分支名称仅保存一次提交的哈希 ID。在我们的例子中,我们将使用该名称master
来保存我们正在调用的提交的实际哈希 IDC
。我们可以这样画:
A--B--C <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n该名称master
指向链中的最后一次C
提交,即. 我已经停止绘制内部箭头,因为它们很快就会变得更难绘制。请记住,它们总是指向后面。它们是parent
实际提交中的行的结果,因此它们永远不能更改 xe2x80x94 ,这与分支名称箭头不同改变。
让我们通过执行然后更新文件、-ing 和-ingD
向我们的链添加一个新的提交。新提交将指向现有提交作为\ 的父级:git checkout master
git add
git commit
D
C
D
A--B--C <-- master\n \\\n D\n
Run Code Online (Sandbox Code Playgroud)\n\n然后,由于D
这是链的最后一次提交,Git 将更新我们当前的分支名称 ,master
以指向D
而不是C
。我们还可以D
在图中向上移动一条线。结果是:
A--B--C--D <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n要添加分支,我们只需创建一个新名称,指向当前提交。让我们创建新分支dev
并让它指向D
:
A--B--C--D <-- dev, master\n
Run Code Online (Sandbox Code Playgroud)\n\n现在我们需要一点额外的符号:我们必须记住我们签出了哪个分支名称。我们已经提交D
了,但是当我们添加新的提交时E
,只有两个分支名称之一应该移动。为此,我们将(HEAD)
在其中一个分支名称后添加。这就是我们的名字;git commit
将移动该名称。因此git checkout dev
,如果我们继续提交D
\xe2\x80\x94 及其所有文件 \xe2\x80\x94,然后当我们进行新的提交时,我们会得到:
A--B--C--D <-- master\n \\\n E <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n1从技术上讲,它是单词的 SHA-1 校验和,commit
后跟一个空格,后跟以十进制表示的字节大小,即280
,后跟 ASCII NUL 字符,后跟内容的字节:
$ (printf \'commit 280\\0\'; git cat-file -p HEAD) | shasum\n08da6496b61341ec45eac36afcc8f94242763468 -\n
Run Code Online (Sandbox Code Playgroud)\n\n该技术适用于 Git 的所有四种对象类型:打印类型名称、空格、十进制 ASCII 格式的对象大小(以字节为单位)、ASCII NUL,然后打印内容。对于提交,内容是提交的文本,顺序为:tree
和散列,每个父级按顺序,author
行,committer
行,任何其他标题行,空行,主题和正文。对于带注释的标签,请查看任何带注释的标签。Blob 对象具有明显的文本。棘手的是树对象,它是二元的,并且有几个半任意规则应用于强制排序。树对象将成为转换为 SHA-256 的最大障碍。
2这允许共享树对象:如果两个不同的提交保存相同的快照,则它们不需要重新保存树。这种共享递归地应用于子树,当然也应用于单个 blob 对象。毕竟,一旦写入一个对象,它的任何部分都无法更改\xe2\x80\x94,哈希 ID 校验和将会改变,结果将是一个不同的对象\xe2\x80\x94,所以如果你有相同的内容,它会减少到相同的哈希 ID。对象数据库是一个简单的键值存储,它检测到键已经存在,因此不会再次存储数据。
\n\n3请注意,如果您使用相同的树、相同的作者、相同的日志消息和\xe2\x80\x94(至关重要的是\xe2\x80\x94)进行相同的提交,并且具有相同的时间戳和parents,那么您将获得相同的提交。如果其中任何一个不同,包括父哈希或时间戳,您将获得一个新的不同提交。因此,如果您重新提交相同的快照,则会在不同的时间并使用不同的父提交来执行此操作:因此,新提交与旧提交不同。因此,新的提交会获得新的哈希值。即使您使用GIT_AUTHOR_DATE
和GIT_COMMITTER_DATE
强制新提交共享旧提交的时间戳,父级哈希也会不同:新提交引用回某个其他现有提交,与原始提交的父级不同( s),同样,重新提交相同的快照会给您一个新的、不同的提交。
让 Git 重新使用提交哈希的唯一方法是使用相同的父项同时提交相同的快照。在这种情况下,可以重新使用原始提交,因为新提交就是原始提交。它们确实是相同的。没有人能区分它们,所以它们可能是同一个提交。
\n\n4您可以生成具有多个根提交的提交图,但许多存储库只有一个原始根。需要使用几个稍微奇怪的技巧之一来获取额外的根提交。
\n\n在 Git 中,合并提交是指至少有两个父项(通常只有两个父项)的任何提交。在图中出现分叉之前,我们无法进行合并提交。例如,假设该图在提交之前是线性的H
,之后它会像这样发散:
I--J <-- master (HEAD)\n /\n...--G--H\n \\\n K--L <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n我们已经完成了git checkout master
,这意味着我们J
现在已经提交了签出。该名称dev
标识 commit L
。
我们现在跑git merge dev
。Git 使用该图来查找两个分支最后聚集在一起的点,在本例中显然是 commit H
。Git 将其称为合并基础提交。另外两个有趣的提交是我们当前的提交,J
以及名称dev
指向的提交,L
提交。
Git 现在实际上将:
\n\nH
比较合并基础提交与当前提交的内容J
:这是我们自基础提交以来更改的内容;H
与其他提交L
:这是他们自基础以来所做的更改;组合的更改将应用于 commit 的内容H
。这样,我们就可以得到我们的更改以及他们的更改。组合过程的精确细节可能会变得非常棘手,尤其是在面临冲突时,但在这里,我们对结果更感兴趣。我们假设没有冲突。
由于我们运行了git merge
(不是git merge --squash
),Git 现在将提交结果。新的提交推进了我们当前的分支,就像任何其他提交一样,但这次新的提交指向两个分支提示提交。因此我们可以画出这样的结果:
I--J\n / \\\n...--G--H M <-- master (HEAD)\n \\ /\n K--L <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\nCommitM
有两个父母,J
并且L
.
当然,如果我们愿意,我们可以添加更多的日常提交:
\n\n I--J\n / \\\n...--G--H M--N <-- master (HEAD)\n \\ /\n K--L <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n像往常一样,新提交N
有M
其父提交。当我们让 Git 向我们显示可从 访问的 提交时master
,Git 将从 开始N
,然后后退一步M
并向我们显示提交M
。现在 Git 有一个选择:是向后退一步到J
,还是退一步到L
?
一般来说,Git 实际上同时完成了这两件事。5 父级中的任何一个都不比另一个“更好”或“更重要”,因此 Git 将跟踪 \xe2\x80\x94 合并的两条腿\xe2\x80\x94 并访问提交J
和I
,但也L
访问 和K
。这些轨迹在 重新组合H
,因此一旦 Git 到达,它就可以像以前一样H
移动到G
等。F
请注意,合并提交M
有自己的快照,就像任何其他提交一样。这里唯一特别的M
是它有两个父母。历史,从M
向后推演,沿着两条腿走下去,然后重新汇合。
这如此重要的原因之一是未来合并的合并基础取决于当前合并。让我们画一个稍微不同的变体,其中dev
被合并master
不止一次。我们将从一次合并M
开始master
如下所示:
...--H---L--M--P <-- master\n \\ /\n I--J--N--O <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n(是的,没有K
,我把它遗漏了以使合并得到字母M
。:-))
现在我们将git checkout master
和git merge dev
进行新的合并q
。Git 必须找到 commitP
和 commit之间的合并基础O
。为此,它从 向后工作P
,转到M
,然后再转到L
和J
。它也可以向后运行 from O
、going toN
和 then J
。
提交J
可以从两个分支访问,并且是正确的合并基础。它是合并基础,因为 M
链接回L
和 J
。因此,从M
到 的反向链接J
在这里很重要。如果我们没有它,Git 就必须继续前进,从L
到H
,从J
到I
到H
。
换句话说,如果没有 merge commit 提供的链接M
,下一次合并将不得不查看 to (我们的更改)和 from to (他们的更改)的差异H
,P
其中H
包括O
已合并的更改。 通过链接,Git 会查看 from J
to P
\xe2\x80\x94 的差异,这实际上是我们在L
and P
\xe2\x80\x94 中所做的事情,以及 from J
to O
,这实际上是他们在N
和 中所做的事情O
。因此后续的合并更容易(当然,这不是微不足道的,但更容易)。
5对于git log
其他一些 Git 命令,您可以用来--first-parent
告诉它忽略第二个父级以及任何其他父级(如果存在),以实现该命令的目的。请注意,合并的第一个父级是在进行合并时选择的。以后无法更改。它始终是您或任何人进行合并时正在进行的提交。由观看者决定第一个父母是否比任何其他父母“更重要”,但这里唯一的内置标志是--first-parent
:没有--second-parent
,所以要么所有父母都是平等的,要么第一个- 父母更重要。
假设我们不是用 进行合并M
,git merge dev
而是用git merge --squash dev
。Git 仍然会:
H
;H
与L
看看我们改变了什么;H
与J
看看它们改变了什么;和H
。命令行git merge --squash
标志也设置该--no-commit
标志,以便 Git 强制我们运行git commit
以进行新的提交M
。Web 界面上的大多数点击按钮都会继续并自行提交,并且命令行 Git 没有充分的理由像这样停止,除非向后兼容性需要它。无论如何,当我们进行合并\xe2\x80\x94或一些点击按钮\xe2\x80\x94时,我们得到:
...--H---L--M <-- master\n \\\n I--J <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n其他一切都完全相同,但 commitM
没有指向dev
.
如果我们现在像以前一样继续并有:
\n\n...--H---L--M--P <-- master\n \\\n I--J--N--O <-- dev\n
Run Code Online (Sandbox Code Playgroud)\n\n我们运行的下一次合并,无论有没有,--squash
都会将提交与提交和进行比较。如果我们在创建时必须对合并冲突采取一些巧妙的措施,那么我们可能不得不再次这样做。dev
master
H
P
O
M
这在实践中意味着,在 后git merge --squash
,您几乎总是应该删除刚刚合并的分支。也就是说,合并 commit 后J
,您应该删除name dev
:
...--H---L--M <-- master\n \\\n I--J [abandoned]\n
Run Code Online (Sandbox Code Playgroud)\n\n提交I
并J
在您自己的存储库中保留一段时间\xe2\x80\x94具体要多长时间有点复杂\xe2\x80\x94但是没有名称来查找它们,您必须记住它们的哈希ID,或者至少是提交的哈希 ID J
。
如果有人将其他名称指向提交I
或或我们可以从中找到或J
的任何提交,那么这里的情况会更加复杂。例如,如果我们有: J
I
...--H---L--M <-- master\n \\\n I--J <-- dev\n \\\n K <-- d2\n
Run Code Online (Sandbox Code Playgroud)\n\n然后我们删除 name dev
,这样就J
无法找到提交,除非你记住它的哈希 ID,我们最终会得到如下所示的结果:
...--H---L--M <-- master\n \\\n I--K <-- d2\n
Run Code Online (Sandbox Code Playgroud)\n\n现在看来,提交I
本身是我们在工作时所做的重要事情,而不是在开始工作并进行提交之后d2
开始工作的副作用。d2
dev
I
这是关于 Git 的一般规则,而不是特定于压缩合并的规则:重要的是提交,而不是分支名称。分支名称只是我们用来查找提交的起始\xe2\x80\x94或结束\xe2\x80\x94点。因此,当您移动或删除分支名称时,如果仍然可以从其他分支名称找到提交,那么它们仍然在那里。即使找不到它们,它们也可能仍在您的存储库中的某个位置。在确定它们是不需要的垃圾之后,您的 Git 最终会将它们扔掉。
\n\n我们通过从分支名称开始并向后工作来查找提交。当我们以这种方式发现某些提交时,我们说它在分支上,但更准确的说法可能是它包含在分支内。任何一项提交都可能包含在许多分支中。当我们添加、删除或移动分支名称时,包含提交的分支集会发生更改。
\n\n所以我们必须记住的规则是:除非添加提交或无法找到提交,否则提交图永远不会改变。大多数时候,分支名称一直在变化\xe2\x80\x94,这样我们就可以找到新的提交并不断找到旧的提交。
\n\n我将跳过添加多个 Git 存储库时出现的大部分复杂情况。Agit push
只是将提交发送到其他一些 Git 存储库,要求它们将其中一个分支名称设置为指向这些新提交中的最后一个。Agit pull
首先运行git fetch
,它从其他 Git 存储库获取任何新的提交,然后更新您自己的 Git\ 的远程跟踪名称 \xe2\x80\x94origin/master
等 \xe2\x80\x94 以记住其对应的最后一次提交分支。有时,您可能会在进行真正的合并(如上所述)之后执行新的合并提交。有时它会执行快进操作,这相当于检查他们的提示提交并将您自己的分支名称向前拖动以包含由此引入的所有新提交。当然,只是意味着运行,然后运行第二个 Git 命令,通常是。git merge
git fetch
git pull
git fetch
git merge
复杂性很重要,但实际的合并(如果有的话)总是发生在我们自己的存储库中。6 我们还git fetch
根据需要其他人的提交。我们最终在本地保存所有提交,并且可以可视化 Git\xe2\x80\x94 的提交,即使它是从其他 Git\xe2\x80\x94 获取的,就像我们的存储库中存在的那样。
\n\n\n典型的功能开发周期包括功能分支上的每日提交和推送。它还包括每天一次 git 从 master 拉取,以与其他更改保持同步。有时这涉及解决合并冲突。
\n
经过几天的工作,我们最终得到了一个如下所示的图表:
\n\n...--H--K--L--N--O--R <-- master\n \\ \\ \\\n I--J--M--P--Q--S <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n其中M
和Q
是真正的合并提交,其中一些可能涉及解决合并冲突。
如果我们相信现在feature
已经准备好了,我们可以:
git checkout master\ngit merge feature\n
Run Code Online (Sandbox Code Playgroud)\n\n这将产生:
\n\n...--H--K--L--N--O--R---T <-- master\n \\ \\ \\ /\n I--J--M--P--Q--S <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n的两个父母T
是R
(第一父母)和S
(第二父母)。我们现在可以feature
完全删除该名称:
...--H--K--L--N--O--R---T <-- master\n \\ \\ \\ /\n I--J--M--P--Q--S\n
Run Code Online (Sandbox Code Playgroud)\n\nS
通过返回到其第二个父级,提交仍然是可访问的T
。合并本身是通过将提交中的合并基础快照与提交中的快照进行比较并合并O
它们来完成的。R
S
或者,我们可以git merge --squash
,之后我们真的应该feature
完全删除。删除前的结果feature
如下:
...--H--K--L--N--O--R--T <-- master\n \\ \\ \\\n I--J--M--P--Q--S <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n在这两种情况下, commit 的内容T
都是相同的;只是T
这次没有第二个父级(所以根据定义它不是合并提交)。
假设我们不尝试通过哈希 ID 来记住提交,而只是忘记所有无法访问的提交,那么删除名称时的结果feature
如下所示:
...--H--K--L--N--O--R--T <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n6通过点击 Web UI 按钮,合并发生在他们的Git 存储库中,之后我们必须git fetch
合并它们,然后更新我们自己的分支名称以合并新的合并提交。一般来说,托管服务不允许存在冲突的合并,因为托管服务不提供解决冲突的方法。随着时间的推移,一些托管服务可能会添加此类功能,但我更愿意自己在本地进行冲突合并:通过这种方式,您可以轻松获得更多数据。
您的问题询问了安全性,但如果没有安全的定义,就无法真正回答。I
我们可以说的是,我们在单独提交、J
、P
、中所做的工作S
不再可找到。相反,当我们查看仍然可以找到的快照时,看起来好像有人feature
在一夜之间编写了所有代码/更改。
这有好的方面,也有坏的方面。对于某些功能,好处多于坏处:没有分散注意力的合并提交的第二父级T
来查看单独的提交,例如I
、J
、P
和S
,并且无需担心该路径中的任何合并提交。但是,如果一体式更改中出现错误怎么办T
? 也许这个 bug 在 commitJ
或 commit中很容易找到P
,而在T
. 因此,也许保留个人承诺会更好。
如果您确定您永远不想恢复原始提交,则压缩是可行的方法。如果您不确定,则可能不是。
\n\n有一种妥协的立场,从某些方面来说,这可能是最好的一种。您可以将分支重新设置为dev
,而不是挤压合并feature
。rebase 的作用是复制提交。不过,变基也有其自身的缺点。
这是合并之前的内容:
\n\n...--H--K--L--N--O--R <-- master\n \\ \\ \\\n I--J--M--P--Q--S <-- feature\n
Run Code Online (Sandbox Code Playgroud)\n\n有 6 个提交可通过名称访问feature
,但无法通过名称访问master
:I
、J
、M
、P
、Q
和S
。
这样做git checkout feature; git rebase master
将指示 Git 找到这六个提交,从列表\xe2\x80\x94中丢弃任何合并提交,留下I-J-P-S
\xe2\x80\x94 ,然后一次一个复制R
这些提交,以便新副本出现在 commit 之后。每个副本都是由 完成的git cherry-pick
。7 不幸的是,每个选择都可能存在冲突,如果是这样,您必须解决它们。它们往往与您在现在省略的合并中可能已解决的冲突相同,并且往往需要相同的解决方案。8 复制完成后,Git 移动分支名称,最终结果如下所示:
I\'-J\'-P\'-S\' <-- feature\n /\n...--H--K--L--N--O--R <-- master\n \\ \\ \\\n I--J--M--P--Q--S [abandoned, but the name ORIG_HEAD works to find S]\n
Run Code Online (Sandbox Code Playgroud)\n\n名称I\'
、J\'
等意味着即使新提交具有新的且不同的哈希 ID,它也是原始提交的副本。
您可以将副本中的快照S\'
与使用中的快照进行比较S
:
git diff ORIG_HEAD HEAD\n
Run Code Online (Sandbox Code Playgroud)\n\n如果它们不匹配,您可能在重新解决冲突时犯了错误。9
\n\n现在您已经将所有内容整齐地重新设置了基础,您可以执行快进合并:
\n\n I\'-J\'-P\'-S\' <-- feature, master\n /\n...--H--K--L--N--O--R\n
Run Code Online (Sandbox Code Playgroud)\n\n或真正的合并:
\n\n I\'-J\'-P\'-S\' <-- feature\n / \\\n...--H--K--L--N--O--R------------T <-- master\n
Run Code Online (Sandbox Code Playgroud)\n\n然后在任何一种情况下,feature
只要应该删除该名称,就将其删除。
变基的一大缺点是每个使用过的人都feature
必须转而使用新的变基feature
提交副本。如果该功能已准备就绪,并且将删除其名称,那么通常很容易。但这绝对符合我喜欢使用的关于 Git 的另一条规则: 只有当使用这些提交的每个人都同意迁移到新的和改进的副本时,才用新的和改进的副本替换提交。 该协议通常应提前达成。
7 Rebase 有一组令人眼花缭乱的选项,包括-i
、-k
、-m
和-s
,其中一些选项导致它实际使用git cherry-pick
。其他方法在内部使用git format-patch
and git am
,但旨在产生相同的结果。
8还有另一个 Git 功能和命令,git rerere
带有启用选项,允许 Git 为您执行此操作,但请始终记住,Git 并不智能,它只是应用简单的文本替换规则。如何rerere
工作以及可能出错的地方相当棘手,我不会在这里详细介绍。
9请注意,如果ORIG_HEAD
后续操作写入,您还可以使用feature@{1}
find S
。如果您遇到需要知道 的哈希 ID 的复杂情况S
,请将其复制粘贴到某处,或者创建一个临时分支或标记名称来保存它。
在实践中,当我重新构建一个复杂的功能时,我喜欢做的是:
\n\ngit checkout feature\ngit branch feature.0 # or .1, .2, etc if I already have .0, etc\ngit rebase master\n
Run Code Online (Sandbox Code Playgroud)\n\nfeature.0
现在我有了记住原始分支提示提交的名称。
没有理由害怕...很多人都用git rebase -i
. 我个人遵循一条不会留下任何东西并且不涉及变基的替代路径:
git checkout my-feature-branch
git pull # merge changes from upstream, do _not_ rebase.
# correct conflicts if they show up and finish merge
# after merge is finished/committed the only differences between your branch and upstream are related to _your_ feature and so....
git reset --soft the-upstream-branch # set branch pointer to upstream branch, all differences are set on index ready to be committed
git commit -m "the feature in a single revision"
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
5154 次 |
最近记录: |