如何从大量提交的拉取请求中删除某些文件?
How to remove certain files from a pull request with allot of commits?
概括地说,假设我有一个包含以下目录的项目。在推送并执行拉取请求后,我将如何最终删除 file2.txt?
app/someFolder
- file1.txt
- file2.txt
- file3.txt
假设我的提交是这些
Commit 1
file1.txt
Hello World
file2.txt
Cool, Superb
file3.txt
December 2
git 添加 .
git提交-m“提交1”
git push --set upstream origin someBranchOnRemote
Commit 2
file1.txt
Hello World
Boss Bass
git 添加 .
git提交-m“提交2”
git推
Commit 3
file3.txt
December 3
git 添加 .
git提交-m“提交3”
git推
所以如果我要执行拉取请求,文件将如下所示
file1.txt
Hello World
Boss Bass
file2.txt
Cool, Superb
file3.txt
December 3
现在我该如何更新拉取请求,这样我就可以不包含 file2.txt?假设散列是 hash1、hash2 和 hash3。我在拉取请求中想要的最终输出是
file1.txt
Hello World
Boss Bass
file3.txt
December 3
TL;DR:您需要 git rebase -i
后跟 git push --force
或 git push --force-with-lease
。但请阅读以下内容。
首先,旁注:Git 本身没有“拉取请求”;这些是某些托管网站的功能,例如 GitHub 和 Bitbucket。他们倾向于在每个托管站点上以类似的方式工作,但是每个站点在这里都有自己的怪癖和行为。您可能需要针对您使用的任何托管站点调整此答案。
除此之外,PR 是您向某人提出的请求,要求他们合并(或“获取并合并”=“拉取”)您所做的一些提交。在 Git 中,你并没有真正合并一个 分支: 你实际上合并了 提交 。当您 运行 git merge
时,您将合并的提交是来自某个提交链的提交,以该链中的 last 提交结束。
即:提交表单链。每个提交 in 一条链都会记住其前一个提交的原始哈希 ID。我们说提交 指向 它的父提交,我们可以这样画:
... <-E <-F <-G <-H
A 分支名称 然后简单地提供链中 last 提交的原始哈希 ID,Git将找到所有以前的提交:
...--E--F--G--H <-- branch
当您去 提出 拉取请求时,您:
- 首先分叉 and/or 克隆一些存储库,这样您就可以获得其他人拥有的所有 提交;
- 创建一个新的分支名称,以便您有一个指向 last 提交的名称,该提交也是 their 提交之一;
- 进行新的提交,以便您的分支名称前进。
例如,假设他们的提交通过(然后停止)我在上面绘制为 E
的提交。 (顺便说一下,我只是因为懒惰才停止在提交之间绘制箭头:提交总是指向后方,所以任何时候你看到一条连接的“线”,它实际上是一个 backwards-pointing“箭头”。)
也就是说,他们在他们的存储库中有一些提交序列:
...--D--E <-- somebranch
您现在在您的存储库中拥有:
...--D--E <-- origin/somebranch
你创建了一个新分支名称指向提交E
:
...--D--E <-- my-fancy-new-feature, origin/somebranch
现在您在这个新分支“上”进行新提交:
...--D--E <-- origin/somebranch
\
F <-- my-fancy-new-feature (HEAD)
这是影响三个文件的“哈希 1”或“提交 1”。提交 F
中有 所有文件 ,因为所有提交总是有每个文件的完整快照,但是文件 in 提交F
与提交 E
中的所有文件都相同,除了您更改的三个文件。 (Git 巧妙地 de-duplicates 相同的 文件,因此这也不会占用太多 space。)
现在提交 F
存在,您进行另一个新提交 G
:
...--D--E <-- origin/somebranch
\
F--G <-- my-fancy-new-feature (HEAD)
这是您的“提交 2”,它仅更改文件 file1.txt
。提交 G
仍然有每个文件,只是它的副本 file2.txt
与提交 F
的副本匹配;它的 file3.txt
副本与提交 F
的副本相匹配;及其所有其他文件与提交 F
和 E
.
的文件相匹配
最后,您添加提交 H
:
...--D--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature (HEAD)
在提交 H
中,您已将 file3.txt
替换为修改后的文件; file1.txt
和 file2.txt
匹配提交 G
中的副本,依此类推。
这让我们又回到了你的问题:
... how would I update the pull request so I can have file2.txt
not be included?
Git 基于 commits,而不是文件,你的 PR 说 please merge commit H
.要更改此设置,您必须:
- 以某种方式更改提交
H
,或
- 更改 PR,使其列出一些其他提交哈希 ID,而不是
H
。
关于任何提交,几乎不可能改变任何,所以第一个想法是正确的。
是否可以更改 PR 以使其列出其他提交,这取决于托管站点。如果托管站点特别令人讨厌,您可能必须 关闭 此 PR,稍后再打开一个新的。但是 GitHub 至少可以让你很简单地更新 PR。
不过,您的首要任务是提出新提交。您不想更改 file2.txt
,但它在提交 F
中有所不同(与提交 E
相比),因此提交 F
本身在某些方面是不好的。这意味着您需要一个新的替代提交 F
。让我们称它为 F'
来表示它与 很像 F
,但它将具有不同的原始哈希 ID。
为了获得提交 F'
,我们想要“复制”提交 F
而不是完全提交。我们将从检查提交 E
开始。我们可以创建另一个新的分支名称,但我们也可以使用 Git 的“分离 HEAD”模式,如下所示:
...--E <-- HEAD, origin/somebranch
\
F--G--H <-- my-fancy-new-feature
现在我们 运行, 说git cherry-pick -n
并给出 Git 提交 F
的哈希 ID,或类似的东西:例如 my-fancy-new-feature~2
。 Git 将复制 F
的效果但尚未提交任何内容——我们将有一些正在进行的工作可以提交——现在我们有机会 撤消 更改为 file2
,例如 git restore
:
git restore -SW --source=origin/somebranch file2.txt
快速 git status
和 git diff --cached
将显示我们现在保留了 file1.txt
和 file3.txt
的更新版本,但又回到了原来的 file2.txt
来自提交 E
由名称 origin/somebranch
.
找到
我们现在可以运行git commit
来制作F'
:
F' <-- HEAD
/
...--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature
提交G
只影响file1.txt
,所以我们可以直接复制它,用git cherry-pick
,这不仅会弄清楚它改变了什么并应用它,而且还会使一个新的提交,re-using 原始提交的消息:
F'-G' <-- HEAD
/
...--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature
您可能想知道为什么我们 将 G
复制到 G'
,而不是仅使用 G
本身。答案很简单:关于提交G
的任何事情都不会改变。从 G
出来的箭头指向 F
,是 G
的一部分。改变不了!提交 G
将永远指向提交 F
,永远不会提交 F'
。所以我们不得不复制G
.
此外,提交 G
中包含错误的 file2.txt
副本,当然,这也会迫使我们复制它——但是 anything这迫使我们复制提交,迫使整个事情发生。请注意,当我们使用 cherry-pick 进行“复制”G
时,Git 会将 in G
中的快照与 [=38] 中的快照进行比较=] 看看 改变了什么 。由于file2.txt
在这个pair-of-commits中没有改变,Git不会改变file2.txt
在 G'
与 F'
中。因此 G'
将具有与 F'
相同的 file2.txt
,而 F'
将具有与 E
相同的 file2.txt
。
现在,出于同样的原因,我们需要复制 H
,我们可以再使用一个 git cherry-pick
命令来完成。结果是:
F'-G'-H' <-- HEAD
/
...--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature
现在我们有了 commits,所有(所有?!)我们要做的就是获得 name my-fancy-new-feature
指向 H'
而不是 H
。我们可以通过多种方式做到这一点,例如 git checkout -B my-fancy-new-feature
或 git switch -C my-fancy-new-feature
。这里的最终结果将是:
F'-G'-H' <-- my-fancy-new-feature (HEAD)
/
...--E <-- origin/somebranch
\
F--G--H ???
F-G-H
链发生了什么变化,Git 过去常常通过查看名称 my-fancy-new-feature
来查找?答案是:没有发生。它仍然在那里。只是现在,它闲置了。 These aren't the droids commits you're looking for,所以我们只是确保这些不是我们 找到 .
的提交
我们现在在 this 存储库中有正确的本地提交。现在我们必须将它们带到托管站点,并让托管站点更新拉取请求。要在 GitHub 上执行此操作,我们只需将新提交推送到 GitHub,告诉 Git 在 GitHub 上 replace F-G-H
使用我们的新 F'-G'-H'
链在其存储库中提交。
Git 通常是贪婪的提交,所以如果我们只是 运行 一个普通的 git push origin my-fancy-new-feature
,他们——Git 在 GitHub 上,在您的存储库上操作 — 将 拒绝我们这样做的尝试 。实际上,他们会说 不!如果我这样做,我将失去 F-G-H 链!(与我们自己的存储库一样,提交不会 消失 ,它们只是不会' name my-fancy-new-feature
再也找不到了。但这足以让他们拒绝请求。)你可能会得到一个建议,你 pull
(即,获取和合并)来自 GitHub 的提交:他们没有意识到他们首先从你那里得到了它们,而你告诉他们 这些是新的和改进的替换 所以你应该放弃旧的,转而使用这些 new-and-improved 的 .
要让他们意识到,你需要某种forced-push(不是星球大战风格的“力量”,而是常规的English-language意思). Git 有几种,你可以在这里使用它们中的任何一种,但是 --force-with-lease
有一个安全功能(在这里应该无关紧要:如果是这样,有些东西已经消失了 not-according-to-plan,并且安全功能检测到)并且通常是要走的路。
让这一切变得简单(大概)
上面的序列中有很多 Git 命令,其中很多都很棘手(出于多种原因我没有显示完整的命令)。我们可以使用 git rebase -i
将其减少到较少数量的 much-less-tricky 命令。不过还是有点棘手。
运行:
git switch my-fancy-new-feature
git rebase -i origin/somebranch
我们就是这样开始的。 rebase 在 当前分支 上运行,因此我们首先检查 my-fancy-new-feature
(您可以在此处使用 git checkout
或 git switch
,如果你已经在上面了)。
rebase 做的是:
- 列出要复制的提交(哈希 ID);
- 使用Git的detached HEAD模式开始复制;和
- 开始cherry-picking。
完成后,它通过将分支名称移动到最后的 copied 提交(H'
在我们的例子中)。这样就可以自动完成很多艰苦的工作。
一般来说,当我们有一些我们最喜欢喜欢的提交时,我们会使用变基,但是有一些关于那些提交我们不喜欢。由于任何现有提交都不能 更改 ,因此变基通过 复制 提交来工作。新副本 可以 在我们提交之前进行更改。
interactive rebase 特别为我们提供了更多改变的机会。一个普通的 rebase
只是复制所有东西而不给我们修复东西的机会,这对于 移动 提交很有用——对于像这样的链:
A--B--C <-- topic
/
...--o--o--o--o <-- mainline
并将其复制到:
A--B--C ???
/
...--o--o--o--o <-- mainline
\
A'-B'-C' <-- topic
以便现在提交出现在主线的 end,而不是从较早的点发芽。这不是我们想要的:我们想要更改其中一个提交中的一些文件。
因此,交互式变基不是仅仅计划所有 cherry-pick 然后启动它们,而是写出 指令 sheet。这条指令 sheet 列出了 个 cherry-pick,每一个都使用单词 pick
:
pick hash1 subject
pick hash2 subject
pick hash3 subject
然后,一旦编写了指令 sheet,git rebase -i
将在指令 sheet 上打开一个编辑器,这样我们就可以 更改命令.
在我们的例子中,我们不想只选择提交 #1 as-is。我们希望有机会 改变 它。所以我们将pick
改为edit
。我们确实想按原样选择#2 和#3,所以我们将保留它们。然后我们写出指令sheet并退出编辑器,1到return到cherry-pick动作
将第一个 pick
更改为 edit
,Git 将 cherry-pick 第一次提交,然后停下来让我们修复它。这里有一件事特别棘手:Git 实际上 做了 此时的临时提交,所以当我们修复它时,我们必须 运行 git commit --amend
.2 我们现在可以像我之前描述的那样做 git restore
,然后 运行 git commit --amend
:
git restore -SW --source origin/somebranch file2.txt
git commit --amend
(注意:--source=origin/somebranch
和 --source origin/somebranch
在这里的工作方式相同,因此您可以使用任何一个)。
一旦我们完成修复 edit-able 提交,我们告诉 Git 恢复变基:
git rebase --continue
这将完成所有剩余的 cherry-picks,然后 re-arrange 分支名称和 re-attach 我们的 HEAD
到我们的分支,现在我们有了我们通缉:
F'-G'-H' <-- my-fancy-new-feature (HEAD)
/
...--E <-- origin/somebranch
\
F--G--H ???
我们现在准备 运行:
git push --force-with-lease origin my-fancy-new-feature
如果我们正在谈论 GitHub,“不关闭更新 PR 并且 re-opening”现在已经完成。
我们总共使用了五六个 Git 命令,大约是我们之前需要的一半,除了交互式变基“编辑”步骤之外,我们不需要做任何棘手的事情。这里的其他一切都非常简单。
1有些编辑器不会 退出: 你早点启动它们,然后它们就会永远闲逛。示例包括 Emacs、Sublime 和 Atom 的许多案例。如果您正在使用这些编辑器之一,则必须使它们与 Git 良好交互;这是特定编辑器的问题,但现在大多数编辑器都有一个 --wait
标志,可以安排所有这些工作正常进行。
2--amend
标志似乎改变了一个提交,这与上面我们不能改变任何提交的说法相矛盾。这里的肮脏秘密是 --amend
不会 更改提交。相反,它只是让 又一次 提交。因此,当我们使用 --edit
时,我们会生成额外的、毫无意义的“垃圾”提交。但是 Git 中的提交是如此之小和廉价,以至于这样做比避免它更好。 Git 最终会自行清理,尽管这通常需要一个多月的时间。 Git 做的清理/清洁工作有点慢,但在 5 分钟内清扫一个月的垃圾,而不是立即清理所有垃圾,实际上是一个非常实用的权衡。
根据您的要求,我会删除该文件,从中重新提交并推送到您的分支。这样 PR 中将有第 4 次提交删除文件,最终结果将如您所说。
概括地说,假设我有一个包含以下目录的项目。在推送并执行拉取请求后,我将如何最终删除 file2.txt?
app/someFolder
- file1.txt
- file2.txt
- file3.txt
假设我的提交是这些
Commit 1
file1.txt
Hello World
file2.txt
Cool, Superb
file3.txt
December 2
git 添加 .
git提交-m“提交1”
git push --set upstream origin someBranchOnRemote
Commit 2
file1.txt
Hello World
Boss Bass
git 添加 .
git提交-m“提交2”
git推
Commit 3
file3.txt
December 3
git 添加 .
git提交-m“提交3”
git推
所以如果我要执行拉取请求,文件将如下所示
file1.txt
Hello World
Boss Bass
file2.txt
Cool, Superb
file3.txt
December 3
现在我该如何更新拉取请求,这样我就可以不包含 file2.txt?假设散列是 hash1、hash2 和 hash3。我在拉取请求中想要的最终输出是
file1.txt
Hello World
Boss Bass
file3.txt
December 3
TL;DR:您需要 git rebase -i
后跟 git push --force
或 git push --force-with-lease
。但请阅读以下内容。
首先,旁注:Git 本身没有“拉取请求”;这些是某些托管网站的功能,例如 GitHub 和 Bitbucket。他们倾向于在每个托管站点上以类似的方式工作,但是每个站点在这里都有自己的怪癖和行为。您可能需要针对您使用的任何托管站点调整此答案。
除此之外,PR 是您向某人提出的请求,要求他们合并(或“获取并合并”=“拉取”)您所做的一些提交。在 Git 中,你并没有真正合并一个 分支: 你实际上合并了 提交 。当您 运行 git merge
时,您将合并的提交是来自某个提交链的提交,以该链中的 last 提交结束。
即:提交表单链。每个提交 in 一条链都会记住其前一个提交的原始哈希 ID。我们说提交 指向 它的父提交,我们可以这样画:
... <-E <-F <-G <-H
A 分支名称 然后简单地提供链中 last 提交的原始哈希 ID,Git将找到所有以前的提交:
...--E--F--G--H <-- branch
当您去 提出 拉取请求时,您:
- 首先分叉 and/or 克隆一些存储库,这样您就可以获得其他人拥有的所有 提交;
- 创建一个新的分支名称,以便您有一个指向 last 提交的名称,该提交也是 their 提交之一;
- 进行新的提交,以便您的分支名称前进。
例如,假设他们的提交通过(然后停止)我在上面绘制为 E
的提交。 (顺便说一下,我只是因为懒惰才停止在提交之间绘制箭头:提交总是指向后方,所以任何时候你看到一条连接的“线”,它实际上是一个 backwards-pointing“箭头”。)
也就是说,他们在他们的存储库中有一些提交序列:
...--D--E <-- somebranch
您现在在您的存储库中拥有:
...--D--E <-- origin/somebranch
你创建了一个新分支名称指向提交E
:
...--D--E <-- my-fancy-new-feature, origin/somebranch
现在您在这个新分支“上”进行新提交:
...--D--E <-- origin/somebranch
\
F <-- my-fancy-new-feature (HEAD)
这是影响三个文件的“哈希 1”或“提交 1”。提交 F
中有 所有文件 ,因为所有提交总是有每个文件的完整快照,但是文件 in 提交F
与提交 E
中的所有文件都相同,除了您更改的三个文件。 (Git 巧妙地 de-duplicates 相同的 文件,因此这也不会占用太多 space。)
现在提交 F
存在,您进行另一个新提交 G
:
...--D--E <-- origin/somebranch
\
F--G <-- my-fancy-new-feature (HEAD)
这是您的“提交 2”,它仅更改文件 file1.txt
。提交 G
仍然有每个文件,只是它的副本 file2.txt
与提交 F
的副本匹配;它的 file3.txt
副本与提交 F
的副本相匹配;及其所有其他文件与提交 F
和 E
.
最后,您添加提交 H
:
...--D--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature (HEAD)
在提交 H
中,您已将 file3.txt
替换为修改后的文件; file1.txt
和 file2.txt
匹配提交 G
中的副本,依此类推。
这让我们又回到了你的问题:
... how would I update the pull request so I can have
file2.txt
not be included?
Git 基于 commits,而不是文件,你的 PR 说 please merge commit H
.要更改此设置,您必须:
- 以某种方式更改提交
H
,或 - 更改 PR,使其列出一些其他提交哈希 ID,而不是
H
。
关于任何提交,几乎不可能改变任何,所以第一个想法是正确的。
是否可以更改 PR 以使其列出其他提交,这取决于托管站点。如果托管站点特别令人讨厌,您可能必须 关闭 此 PR,稍后再打开一个新的。但是 GitHub 至少可以让你很简单地更新 PR。
不过,您的首要任务是提出新提交。您不想更改 file2.txt
,但它在提交 F
中有所不同(与提交 E
相比),因此提交 F
本身在某些方面是不好的。这意味着您需要一个新的替代提交 F
。让我们称它为 F'
来表示它与 很像 F
,但它将具有不同的原始哈希 ID。
为了获得提交 F'
,我们想要“复制”提交 F
而不是完全提交。我们将从检查提交 E
开始。我们可以创建另一个新的分支名称,但我们也可以使用 Git 的“分离 HEAD”模式,如下所示:
...--E <-- HEAD, origin/somebranch
\
F--G--H <-- my-fancy-new-feature
现在我们 运行, 说git cherry-pick -n
并给出 Git 提交 F
的哈希 ID,或类似的东西:例如 my-fancy-new-feature~2
。 Git 将复制 F
的效果但尚未提交任何内容——我们将有一些正在进行的工作可以提交——现在我们有机会 撤消 更改为 file2
,例如 git restore
:
git restore -SW --source=origin/somebranch file2.txt
快速 git status
和 git diff --cached
将显示我们现在保留了 file1.txt
和 file3.txt
的更新版本,但又回到了原来的 file2.txt
来自提交 E
由名称 origin/somebranch
.
我们现在可以运行git commit
来制作F'
:
F' <-- HEAD
/
...--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature
提交G
只影响file1.txt
,所以我们可以直接复制它,用git cherry-pick
,这不仅会弄清楚它改变了什么并应用它,而且还会使一个新的提交,re-using 原始提交的消息:
F'-G' <-- HEAD
/
...--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature
您可能想知道为什么我们 将 G
复制到 G'
,而不是仅使用 G
本身。答案很简单:关于提交G
的任何事情都不会改变。从 G
出来的箭头指向 F
,是 G
的一部分。改变不了!提交 G
将永远指向提交 F
,永远不会提交 F'
。所以我们不得不复制G
.
此外,提交 G
中包含错误的 file2.txt
副本,当然,这也会迫使我们复制它——但是 anything这迫使我们复制提交,迫使整个事情发生。请注意,当我们使用 cherry-pick 进行“复制”G
时,Git 会将 in G
中的快照与 [=38] 中的快照进行比较=] 看看 改变了什么 。由于file2.txt
在这个pair-of-commits中没有改变,Git不会改变file2.txt
在 G'
与 F'
中。因此 G'
将具有与 F'
相同的 file2.txt
,而 F'
将具有与 E
相同的 file2.txt
。
现在,出于同样的原因,我们需要复制 H
,我们可以再使用一个 git cherry-pick
命令来完成。结果是:
F'-G'-H' <-- HEAD
/
...--E <-- origin/somebranch
\
F--G--H <-- my-fancy-new-feature
现在我们有了 commits,所有(所有?!)我们要做的就是获得 name my-fancy-new-feature
指向 H'
而不是 H
。我们可以通过多种方式做到这一点,例如 git checkout -B my-fancy-new-feature
或 git switch -C my-fancy-new-feature
。这里的最终结果将是:
F'-G'-H' <-- my-fancy-new-feature (HEAD)
/
...--E <-- origin/somebranch
\
F--G--H ???
F-G-H
链发生了什么变化,Git 过去常常通过查看名称 my-fancy-new-feature
来查找?答案是:没有发生。它仍然在那里。只是现在,它闲置了。 These aren't the droids commits you're looking for,所以我们只是确保这些不是我们 找到 .
我们现在在 this 存储库中有正确的本地提交。现在我们必须将它们带到托管站点,并让托管站点更新拉取请求。要在 GitHub 上执行此操作,我们只需将新提交推送到 GitHub,告诉 Git 在 GitHub 上 replace F-G-H
使用我们的新 F'-G'-H'
链在其存储库中提交。
Git 通常是贪婪的提交,所以如果我们只是 运行 一个普通的 git push origin my-fancy-new-feature
,他们——Git 在 GitHub 上,在您的存储库上操作 — 将 拒绝我们这样做的尝试 。实际上,他们会说 不!如果我这样做,我将失去 F-G-H 链!(与我们自己的存储库一样,提交不会 消失 ,它们只是不会' name my-fancy-new-feature
再也找不到了。但这足以让他们拒绝请求。)你可能会得到一个建议,你 pull
(即,获取和合并)来自 GitHub 的提交:他们没有意识到他们首先从你那里得到了它们,而你告诉他们 这些是新的和改进的替换 所以你应该放弃旧的,转而使用这些 new-and-improved 的 .
要让他们意识到,你需要某种forced-push(不是星球大战风格的“力量”,而是常规的English-language意思). Git 有几种,你可以在这里使用它们中的任何一种,但是 --force-with-lease
有一个安全功能(在这里应该无关紧要:如果是这样,有些东西已经消失了 not-according-to-plan,并且安全功能检测到)并且通常是要走的路。
让这一切变得简单(大概)
上面的序列中有很多 Git 命令,其中很多都很棘手(出于多种原因我没有显示完整的命令)。我们可以使用 git rebase -i
将其减少到较少数量的 much-less-tricky 命令。不过还是有点棘手。
运行:
git switch my-fancy-new-feature
git rebase -i origin/somebranch
我们就是这样开始的。 rebase 在 当前分支 上运行,因此我们首先检查 my-fancy-new-feature
(您可以在此处使用 git checkout
或 git switch
,如果你已经在上面了)。
rebase 做的是:
- 列出要复制的提交(哈希 ID);
- 使用Git的detached HEAD模式开始复制;和
- 开始cherry-picking。
完成后,它通过将分支名称移动到最后的 copied 提交(H'
在我们的例子中)。这样就可以自动完成很多艰苦的工作。
一般来说,当我们有一些我们最喜欢喜欢的提交时,我们会使用变基,但是有一些关于那些提交我们不喜欢。由于任何现有提交都不能 更改 ,因此变基通过 复制 提交来工作。新副本 可以 在我们提交之前进行更改。
interactive rebase 特别为我们提供了更多改变的机会。一个普通的 rebase
只是复制所有东西而不给我们修复东西的机会,这对于 移动 提交很有用——对于像这样的链:
A--B--C <-- topic
/
...--o--o--o--o <-- mainline
并将其复制到:
A--B--C ???
/
...--o--o--o--o <-- mainline
\
A'-B'-C' <-- topic
以便现在提交出现在主线的 end,而不是从较早的点发芽。这不是我们想要的:我们想要更改其中一个提交中的一些文件。
因此,交互式变基不是仅仅计划所有 cherry-pick 然后启动它们,而是写出 指令 sheet。这条指令 sheet 列出了 个 cherry-pick,每一个都使用单词 pick
:
pick hash1 subject
pick hash2 subject
pick hash3 subject
然后,一旦编写了指令 sheet,git rebase -i
将在指令 sheet 上打开一个编辑器,这样我们就可以 更改命令.
在我们的例子中,我们不想只选择提交 #1 as-is。我们希望有机会 改变 它。所以我们将pick
改为edit
。我们确实想按原样选择#2 和#3,所以我们将保留它们。然后我们写出指令sheet并退出编辑器,1到return到cherry-pick动作
将第一个 pick
更改为 edit
,Git 将 cherry-pick 第一次提交,然后停下来让我们修复它。这里有一件事特别棘手:Git 实际上 做了 此时的临时提交,所以当我们修复它时,我们必须 运行 git commit --amend
.2 我们现在可以像我之前描述的那样做 git restore
,然后 运行 git commit --amend
:
git restore -SW --source origin/somebranch file2.txt
git commit --amend
(注意:--source=origin/somebranch
和 --source origin/somebranch
在这里的工作方式相同,因此您可以使用任何一个)。
一旦我们完成修复 edit-able 提交,我们告诉 Git 恢复变基:
git rebase --continue
这将完成所有剩余的 cherry-picks,然后 re-arrange 分支名称和 re-attach 我们的 HEAD
到我们的分支,现在我们有了我们通缉:
F'-G'-H' <-- my-fancy-new-feature (HEAD)
/
...--E <-- origin/somebranch
\
F--G--H ???
我们现在准备 运行:
git push --force-with-lease origin my-fancy-new-feature
如果我们正在谈论 GitHub,“不关闭更新 PR 并且 re-opening”现在已经完成。
我们总共使用了五六个 Git 命令,大约是我们之前需要的一半,除了交互式变基“编辑”步骤之外,我们不需要做任何棘手的事情。这里的其他一切都非常简单。
1有些编辑器不会 退出: 你早点启动它们,然后它们就会永远闲逛。示例包括 Emacs、Sublime 和 Atom 的许多案例。如果您正在使用这些编辑器之一,则必须使它们与 Git 良好交互;这是特定编辑器的问题,但现在大多数编辑器都有一个 --wait
标志,可以安排所有这些工作正常进行。
2--amend
标志似乎改变了一个提交,这与上面我们不能改变任何提交的说法相矛盾。这里的肮脏秘密是 --amend
不会 更改提交。相反,它只是让 又一次 提交。因此,当我们使用 --edit
时,我们会生成额外的、毫无意义的“垃圾”提交。但是 Git 中的提交是如此之小和廉价,以至于这样做比避免它更好。 Git 最终会自行清理,尽管这通常需要一个多月的时间。 Git 做的清理/清洁工作有点慢,但在 5 分钟内清扫一个月的垃圾,而不是立即清理所有垃圾,实际上是一个非常实用的权衡。
根据您的要求,我会删除该文件,从中重新提交并推送到您的分支。这样 PR 中将有第 4 次提交删除文件,最终结果将如您所说。