发布于 2026-01-05 8 阅读
0

保持 Git 历史记录的整洁——101

保持 Git 历史记录的整洁——101

封面照片由JESHOOTS.COM提供,来自Unsplash

我使用 Git 已经好几年了,说实话,保持提交历史的整洁对我来说并非一直那么重要。正如我们在《回到未来》中看到的那样,改写历史可能会带来一些糟糕的后果。我只能猜测,这就是为什么它如此令人恐惧的原因。在这篇文章中,我想分享一些基本命令,它们将帮助你保持 Git 提交历史的整洁,并消除你对《回到未来》的恐惧 ;-)

首先,

本文中的所有命令都会生成新的提交哈希值,因此会使你的分支与远程分支产生分歧。这意味着你必须使用 `git push`git push --force或 ` git push` 强制推送来覆盖远程git push -f
分支的历史记录。这反过来意味着:永远不要更改共享分支上的 Git 历史记录。
有一个更安全的选项,即使强制推送也会在远程分支包含比本地分支更新的提交时被拒绝:
git push --force-with-lease

场景 1:在上次提交中添加一些内容

每个人都遇到过这种情况。你把修改添加到暂存区,提交,然后等待构建成功。不幸的是,构建失败了。当然,你忘了添加文件 x。接下来会发生什么?把文件 x 添加到暂存区,提交并附上类似“我真笨,忘了添加文件 x”这样的信息……等等!让我来介绍一下我们的第一个命令:
git commit --amend
这个命令会将你在暂存区的修改添加到最后一次提交中,并允许你修改最后一次提交的信息。你也可以用一个命令完成所有操作:
git commit --amend -m "new message"
如果你不想修改最后一次提交的信息,只需添加`--no-edit`参数:
git commit --amend --no-edit
这样就不会再出现“我真笨,忘了添加文件 x”之类的错误提交了!

Git 交互式变基

接下来的几个场景中,我们将使用交互式 Git 变基。这个工具可以帮助我们修改更早的历史记录。只需使用以下命令启动交互式变基:其中 ` --rebase - background
git rebase -i <base>
...<base>

git rebase -i HEAD~3

git rebase -i 63a2356

pick bcfd87e add sql scripts for database
pick 9fb0b9c prevent users from changing their email
pick e0b46b9 cleanup web config

# Rebase dfef724..e0b46b9 onto dfef724 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Enter fullscreen mode Exit fullscreen mode

让我们直接进入第一个变基场景。

场景 2:向历史记录中的任意提交添加内容

就像场景 1 中那样,你忘记向提交中添加一些内容。但你不想将其添加到最近的提交中,而是想将该更改修改到历史记录中更早的提交中。对于这种情况,交互式变基提供了编辑选项。
我们首先使用交互式变基命令(见上文),然后将编辑命令设置为要更改的提交:

pick bcfd87e add sql scripts for database
edit 9fb0b9c prevent users from changing their email
pick e0b46b9 cleanup web config
Enter fullscreen mode Exit fullscreen mode

交互式变基现在会停止于此提交,您可以添加您的更改:

Stopped at 9fb0b9c...  prevent users from changing their email
You can amend the commit now, with

  git commit --amend 

Once you are satisfied with your changes, run

  git rebase --continue
Enter fullscreen mode Exit fullscreen mode

完成后,只需将更改添加到暂存区,然后使用 amend 命令提交即可:
git commit --amend
快完成了!现在我们需要继续执行交互式变基:
git rebase --continue

场景 3:合并提交

我尽量频繁地提交代码。这意味着我在特性分支中的 Git 历史记录通常看起来像这样:

9edf77a more review findings
67b5e01 review findings
940778d enable users to change their name
dc6b0db enable users to change their name
dfdd77d wip
Enter fullscreen mode Exit fullscreen mode

这在功能开发过程中可能很有用,但对于整个 Git 仓库的历史记录来说并不适用。因此,我希望将所有提交合并为一个。有两种方法可以做到这一点:squash 和 fixup。这两个命令都会将一个提交合并到前一个提交中。唯一的区别在于,squash 允许你添加新的提交消息,而 fixup 会丢弃该消息。
那么,让我们来合并一些提交。我们像之前一样启动交互式变基,并设置相应的选项:

pick dfdd77d wip
squash dc6b0db enable users to change their name
squash 940778d enable users to change their name
squash 67b5e01 review findings
squash 9edf77a more review findings

# Rebase 63a2356..9edf77a onto 63a2356 (5 commands)
Enter fullscreen mode Exit fullscreen mode

Git 现在提供包含所有消息的概览:

# This is a combination of 5 commits.
# This is the 1st commit message:

wip

# This is the commit message #2:

enable users to change their name

# This is the commit message #3:

enable users to change their name

# This is the commit message #4:

review findings

# This is the commit message #5:

more review findings

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
Enter fullscreen mode Exit fullscreen mode

我们删除所有多余的行,只保留最终想要表达的信息:

enable users to change their name

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
Enter fullscreen mode Exit fullscreen mode

瞧,我们已经将所有提交合并成一个了:

cb88cf6 enable users to change their name
Enter fullscreen mode Exit fullscreen mode

我们也可以使用 fixup 命令执行相同的操作。在下面的场景中,我对提交信息很满意,我想将我的提交合并到该提交信息中:

pick dc6b0db enable users to change their name
fixup dfdd77d wip
fixup 940778d enable users to change their name
fixup 67b5e01 review findings
fixup 9edf77a more review findings

# Rebase 63a2356..9edf77a onto 63a2356 (5 commands)
Enter fullscreen mode Exit fullscreen mode

结果与上面壁球的例子相同:

b8e76d1 enable users to change their name
Enter fullscreen mode Exit fullscreen mode

场景 4:拆分提交

我们有时会将两个或多个不同的主题合并成一个主题:

7080968 add sql scripts for database
172db2b prevent user from changing their email and cleanup web config
Enter fullscreen mode Exit fullscreen mode

在这种情况下,将 web.config 的清理工作单独进行会更清晰,不是吗?此时我们可以再次使用 edit 命令。让我们启动交互式 rebase 会话并执行此操作:

pick 7080968 add sql scripts for database
edit 172db2b prevent user from changing their email and cleanup web config

# Rebase dfef724..172db2b onto dfef724 (2 commands)
Enter fullscreen mode Exit fullscreen mode

正如你所料,Git 会在我们的提交处停止。现在我们可以将更改恢复到工作区:
git reset HEAD~
剩下的步骤很简单,只需为我们的更改创建两个提交:
git add [files]
git commit -m "prevent user from changing their email"
git add [files]
git commit -m "cleanup web config"
别忘了继续执行变基操作:
git rebase --continue
让我们看看结果:

9fb0b9c prevent user from changing their email
dfef724 cleanup web config
7080968 add sql scripts for database
Enter fullscreen mode Exit fullscreen mode

整洁的!

场景 5:重新排序提交

如果要合并两个或多个提交,但它们的顺序不对,该怎么办?

09f43c9 validate user inputs
62490ed import user data
c531f57 validate user inputs
Enter fullscreen mode Exit fullscreen mode

您只需在变基编辑器中重新排列提交顺序即可。之后,您可以使用 fixup 或 squash 选项来合并它们。

pick 62490ed import user data
pick c531f57 validate user inputs
fixup 09f43c9 validate user inputs

# Rebase 6c70ff2..09f43c9 onto 6c70ff2 (3 commands)
Enter fullscreen mode Exit fullscreen mode

场景 6:更改提交消息

对某个提交信息不满意?借助 reword 命令,您可以随时更改它:

reword c531f57 validate user inputs
pick 62490ed import user data

# Rebase 6c70ff2..09f43c9 onto 6c70ff2 (3 commands)
Enter fullscreen mode Exit fullscreen mode

Git 将在此提交处停止,您可以选择更改此消息:

validate user inputs

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
Enter fullscreen mode Exit fullscreen mode

场景 7:删除提交

需要完全撤销某个特定的提交吗?对于这种情况,我们可以使用 drop 命令:

pick bcfd87e add sql scripts for database
drop 9fb0b9c prevent users from changing their email
pick e0b46b9 cleanup web config

# Rebase dfef724..e0b46b9 onto dfef724 (3 commands)
Enter fullscreen mode Exit fullscreen mode

至少在这里,你就能明白为什么保持 Git 历史记录的整洁如此重要了。想象一下,你把不同的功能放在一个提交里,或者把一个功能拆分成多个(毫无意义的)提交。那么,回滚操作就会变得复杂得多。

当事情出错时

重写 Git 历史记录时,不必担心会破坏任何东西。
首先,你可以随时使用 `git rebase` 命令中止交互式变基会话。git rebase --abort
该命令会停止变基会话并撤销所有已做的更改。
其次,即使你已经完成了变基操作,但不知何故搞砸了,你也可以撤销它。幸运的是,Git 会跟踪你执行的所有命令。你可以使用 `git log` 命令打开此日志。git reflog

e0b46b9 (HEAD -> feature/my2, feature/my3) HEAD@{34}: rebase -i (finish): returning to refs/heads/feature/my2
e0b46b9 (HEAD -> feature/my2, feature/my3) HEAD@{35}: commit: cleanup web config
9fb0b9c HEAD@{36}: commit: prevent users from changing their email
bcfd87e HEAD@{37}: reset: moving to HEAD~
3892bc7 HEAD@{38}: rebase -i: fast-forward
bcfd87e HEAD@{39}: rebase -i (start): checkout master
3892bc7 HEAD@{40}: rebase -i (finish): returning to refs/heads/feature/my2
3892bc7 HEAD@{41}: commit (amend): prevent user from changing their email and cleanup web config
39262b5 HEAD@{42}: rebase -i: fast-forward
bcfd87e HEAD@{43}: rebase -i (start): checkout master
39262b5 HEAD@{44}: rebase -i (abort): updating HEAD
39262b5 HEAD@{45}: rebase -i (abort): updating HEAD
bcfd87e HEAD@{46}: reset: moving to HEAD~
39262b5 HEAD@{47}: rebase -i: fast-forward
bcfd87e HEAD@{48}: rebase -i (start): checkout master
39262b5 HEAD@{49}: rebase -i (finish): returning to refs/heads/feature/my2
39262b5 HEAD@{50}: rebase -i (pick): prevent user from changing their email and cleanup web config
bcfd87e HEAD@{51}: commit (amend): add sql scripts for database
7080968 HEAD@{52}: rebase -i: fast-forward
dfef724 (master) HEAD@{53}: rebase -i (start): checkout master
172db2b (feature/my) HEAD@{54}: checkout: moving from feature/my to feature/my2
172db2b (feature/my) HEAD@{55}: commit: prevent user from changing their email and cleanup web config
7080968 HEAD@{56}: commit: add sql scripts for database
dfef724 (master) HEAD@{57}: checkout: moving from master to feature/my
dfef724 (master) HEAD@{58}: commit (initial): init
Enter fullscreen mode Exit fullscreen mode

现在您可以搜索在开始交互式变基会话之前的最后一个操作,并恢复到该状态。例如:
git reset --hard HEAD@{18}

所以,让我们克服恐惧,保持 Git 历史记录的整洁吧!:-)

文章来源:https://dev.to/manuelsidler/keep-your-git-history-clean-101-k7