您无法更改现有的提交,但您可以进行新的提交,其父提交与现有的“提交过多”提交相同。
在你开始之前,确保你有一个干净的工作树(“没有什么可提交的”)。这样就没有git reset 或任何可能失去任何东西的东西。如有必要,您可以进行新的提交,以便您可能需要zorg~2 而不是zorg~1(参见下图)。稍后您将能够从此提交中检索您保存的项目。
画出你现在拥有的东西
与 Git 一样,首先绘制(至少部分)提交图。您现在在某个分支上,这意味着您的分支 name 指向最尖端的提交,并且该提交指向某个父提交,依此类推:
...--A--B--C--D <-- zorg
zorg 是您当前的分支,D 可能是这个太大的提交,C 是它之前的提交,没有任何一组更改。 (如果您必须进行更多提交,那么提交D 可能会退后一步;如果是,请调整以下数字。)
提示:使用git log --graph --oneline --decorate(也可以使用--all)让Git 为您绘制图形(尽管它是垂直绘制的,顶部是最近的东西,而不是水平方向的新东西)右边)。
画出你喜欢的东西
您无法更改D,但可以进行新的提交E 和F,您可以这样安排:
...--A--B--C--D <-- ... we'll fill this in later ...
\
E--F <-- ... likewise this ...
或者这样:
F <-- ...
/
...--A--B--C--D <-- ...
\
E <-- ...
提交 D 将继续是您的“太大”提交,而 E 可能只有 HTML 更改,F 可能只有 JS 更改。 (如果F 是基于E 构建的,那么它确实有两个变化并且实际上在内容方面匹配提交D。如果F 是基于C 构建的,那么它只有 JS 发生变化。由您决定如何安排这些。)
每个...都要填写一个分支名称。您可以不理会现有的分支名称,并发明一两个新的分支名称,这就是我将首先展示的内容。
手动操作
假设您想要两个新的分支名称,E 和 F 每个都将 C 作为它们的父级(所以,不是 C--E--F)。 Git 是 Git,有很多方法可以做到这一点,但一种简单的方法是使用 git checkout -b 创建它们,它会创建新的分支名称并打开它们(这样git status 就表示您在新分支上)。这个git checkout -b 命令还带有一个可选的提交说明符,它是创建新分支后在索引和工作树中的提交。我们希望E 和F 都从C 中跳出来,所以我们要创建新的分支“at”提交C:
git checkout -b zorg-html zorg~1
名称zorg 标识提交D。添加~ 后缀意味着“从这个提交,后退到第一父链接,无论我在数字中说了多少次”。由于数字是 1(一),我们将退回一位父级,这会将我们从 D 带到 C。这意味着名称 zorg-html 当前将指向提交 C,我们将在这个新分支上。
现在我们在zorg-html(在提交C)我们只想替换所有的HTML文件。这些文件的正确版本在提交D 中,正如名称zorg 所指出的那样。获取这些文件的简单但困难的方法是:
git checkout zorg -- first_file second_file third_file ...
这-这有点疯狂git checkout-这次根本不更改分支,而是只提取特定的命名文件(@987654370 之后的文件名列表@part) 来自指定的提交(zorg,即提交D)。
如果文件都以.html结尾并且没有.html文件实际上不是HTML文件,这种简单方法的简单版本是:
git checkout zorg -- '*.html' '**/*.html'
也就是说,从顶级目录中获取每个名为 whatever.html 的文件,以及在任意数量的子目录中名为 whatever.html 的每个文件,从 zorg 提交(再次提交 D) .
这种git checkout将更新后的文件同时写入索引和工作树,所以此时你可以简单地git commit得到结果。
现在,要创建提交 F,我们重复整个过程:
git checkout -b zorg-js zorg~1 # new zorg-js branch starting at C
git checkout zorg -- '*.js' '**/*.js'
git commit
(假设和之前的 HTML 文件一样,每个 JS 文件都被命名为 .js 并且没有一个名为 .js 的文件是 other 而不是 JS 文件)。现在我们有了:
F <-- zorg-js
/
...--A--B--C--D <-- zorg
\
E <-- zorg-html
显然,您可以为所有这些分支选择更好的名称。
如果您希望在提交E 之后提交F,只需省略将创建一个新分支的git checkout -b 并切换回提交C。当您提取所有.js 文件并提交F 时,这将使您留在分支zorg-html 上提交E,因此F 的父级将是E,您将拥有:
...--A--B--C--D <-- zorg
\
E--F <-- zorg-html # zorg-html is clearly a bad name
如果你想要的只是一些简单的食谱,你可以在这里停下来。如果您想了解许多处理此问题和其他问题的方法,请继续阅读。
如果你想在zorg 上使用E--F 怎么办?
没问题。 Git 是 Git,有多种方法可以做到这一点。例如,您可以在开始之前重命名zorg:
git branch -m zorg gary-oldman
现在你有了这个:
A--B--C--D <-- gary-oldman
您可以安全地创建一个新的zorg。
当然,任何上游设置都使用重命名的分支。没什么大不了的,您可以使用git branch --set-upstream-to 为每个分支设置新的上游。
当然,Git 就是 Git,还有另一种方法可以做到!您可以创建一个新的分支名称现在,指向提交D,只要您需要它就记住它——您将需要它用于两个git checkout 命令。然后你可以git reset分支名zorg让它指向提交C:
git checkout zorg # make sure zorg is the current branch
git branch temp # save its tip commit under a new name
git reset --hard zorg~1 # and move zorg back to commit C
现在,当您进行新提交时,他们会将名称 zorg 向前移动,但名称 temp 仍会为您记住提交 D:
A--B--C--D <-- temp
\
E <-- zorg
现在要访问提交D,您将使用名称temp,并重新找到提交C,您将使用temp~1。
请注意,如果您有“过去”D 的额外提交(例如在 HTML 和 JS 更改后保存所做的工作):
A--B--C--D--H--I--J <-- temp, or zorg, or whatever
你仍然可以做到这一切。只是现在,要命名提交 C,您将需要它的 SHA-1 哈希“真实名称”(它永远不会改变,但很难正确输入——鼠标剪切和粘贴在这里很有帮助),或从小费倒数。这里temp 可能命名为commit J,而temp~1 是commit I,而temp~2 是H;那么temp~3 是D 和temp~4 是C。完成拆分提交后,您可以挑选剩余的提交。
使用git rebase -i
Git 是 Git,还有另一种方法可以做到这一点,如果在 D 之后有提交,则特别有用,即要拆分的提交。这个特殊的方法需要对 Git 有一定的了解,但最终是最短和最快的方法。我们从git rebase -i 开始,将提交D(以及任何以后的提交)重新定位到C,它已经在(或它们在)那里;但我们将D 的pick 行更改为edit。
Git 现在让我们进入 rebase 会话,并提交 D。现在我们想要git reset HEAD~1(或git reset --mixed HEAD~1;--mixed 只是默认值)返回提交C。这将设置当前提交——我们处于分离 HEAD 模式,所以这只是将HEAD 本身调整为C 并重置索引以匹配C,但保留为D 设置的工作树。现在我们只是有选择地git add 我们想要的文件:所有.html 的文件。使用您喜欢的任何方法(例如find ... | xargs git add 或git add '*.html' '**/*.html')添加这些,然后git commit 结果。然后git add 剩余文件和git commit 再次,然后git rebase --continue 复制剩余提交并将分支标签移动到最尖端的结果提交。