Git 核心的附加价值之一就是编辑历史记录的能力。与将历史记录视为神圣的记录的版本控制系统不同,在 Git 中,我们可以修改历史记录以适应我们的需要。这为我们提供了很多强大的工具,让我们可以像使用重构来维护良好的软件设计实践一样,编织良好的提交历史。这些工具对于新手甚至是有经验的 Git 用户来说可能会有些令人生畏,但本指南将帮助我们揭开强大的 git-rebase 的神秘面纱。
值得注意的是:一般建议不要修改公共分支、共享分支或稳定分支的历史记录。编辑特性分支和个人分支的历史记录是可以的,编辑还没有推送的提交也是可以的。在编辑完提交后,可以使用
git push -f
来强制推送你的修改到个人分支或特性分支。
尽管有这么可怕的警告,但值得一提的是,本指南中提到的一切都是非破坏性操作。实际上,在 Git 中永久丢失数据是相当困难的。本指南结尾介绍了在犯错误时进行纠正的方法。
设置沙盒
我们不想破坏你的任何实际的版本库,所以在整个指南中,我们将使用一个沙盒版本库。运行这些命令来开始工作。 1
git init /tmp/rebase-sandbox
cd /tmp/rebase-sandbox
git commit --allow-empty -m"Initial commit"
如果你遇到麻烦,只需运行 rm -rf /tmp/rebase-sandbox
,并重新运行这些步骤即可重新开始。本指南的每一步都可以在新的沙箱上运行,所以没有必要重做每个任务。
修正最近的提交
让我们从简单的事情开始:修复你最近的提交。让我们向沙盒中添加一个文件,并犯个错误。
echo "Hello wrold!" >greeting.txt
git add greeting.txt
git commit -m"Add greeting.txt"
修复这个错误是非常容易的。我们只需要编辑文件,然后用 --amend
提交就可以了,就像这样:
echo "Hello world!" >greeting.txt
git commit -a --amend
指定 -a
会自动将所有 Git 已经知道的文件进行暂存(例如 Git 添加的),而 --amend
会将更改的内容压扁到最近的提交中。保存并退出你的编辑器(如果需要,你现在可以修改提交信息)。你可以通过运行 git show
看到修复的提交。
commit f5f19fbf6d35b2db37dcac3a55289ff9602e4d00 (HEAD -> master)
Author: Drew DeVault
Date: Sun Apr 28 11:09:47 2019 -0400
Add greeting.txt
diff --git a/greeting.txt b/greeting.txt
new file mode 100644
index 0000000..cd08755
a/main.c
+++ b/main.c
@@ -1,3 +1,14 @@
+#include <stdio.h>
+
+const char *get_name() {
+ static char buf[128];
+ scanf("%s", buf);
+ return buf;
+}
+
int main(int argc, char *argv[]) {
+ printf("What's your name? ");
+ const char *name = get_name();
+ printf("Hello, %s!n", name);
return 0;
}
Stage this hunk [y,n,q,a,d,s,e,?]?
Git 仅向你提供了一个“大块”(即单个更改)以进行提交。不过,这太大了,让我们使用 s
命令将这个“大块”拆分成较小的部分。
Split into 2 hunks.
@@ -1 +1,9 @@
+#include <stdio.h>
+
+const char *get_name() {
+ static char buf[128];
+ scanf("%s", buf);
+ return buf;
+}
+
int main(int argc, char *argv[]) {
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?
提示:如果你对其他选项感到好奇,请按
?
汇总显示。
这个大块看起来更好:单一、独立的更改。让我们按 y
来回答问题(并暂存那个“大块”),然后按 q
以“退出”交互式会话并继续进行提交。会弹出编辑器,要求输入合适的提交消息。
Add get_name function to C program
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto c785f47
# Last commands done (2 commands done):
# pick 237b246 Add C program skeleton
# edit b3f188b Flesh out C program
# No commands remaining.
# You are currently splitting a commit while rebasing branch 'master' on 'c785f47'.
#
# Changes to be committed:
# modified: main.c
#
# Changes not staged for commit:
# modified: main.c
#
保存并关闭编辑器,然后我们进行第二次提交。我们可以执行另一次交互式提交,但是由于我们只想在此提交中包括其余更改,因此我们将执行以下操作:
git commit -a -m"Prompt user for their name"
git rebase --continue
最后一条命令告诉 Git 我们已经完成了此提交的编辑,并继续执行下一个变基命令。这样就行了!运行 git log
来查看你的劳动成果:
$ git log -3 --oneline
fe19cc3 (HEAD -> master) Prompt user for their name
659a489 Add get_name function to C program
237b246 Add C program skeleton
重新排序提交
这很简单。让我们从设置沙箱开始:
echo "Goodbye now!" >farewell.txt
git add farewell.txt
git commit -m"Add farewell.txt"
echo "Hello there!" >greeting.txt
git add greeting.txt
git commit -m"Add greeting.txt"
echo "How're you doing?" >inquiry.txt
git add inquiry.txt
git commit -m"Add inquiry.txt"
现在 git log
看起来应如下所示:
f03baa5 (HEAD -> master) Add inquiry.txt
a4cebf7 Add greeting.txt
90bb015 Add farewell.txt
显然,这都是乱序。让我们对过去的 3 个提交进行交互式变基来解决此问题。运行 git rebase -i HEAD~3
,这个变基规划将出现:
pick 90bb015 Add farewell.txt
pick a4cebf7 Add greeting.txt
pick f03baa5 Add inquiry.txt
# Rebase fe19cc3..f03baa5 onto fe19cc3 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
#
# These lines can be re-ordered; they are executed from top to bottom.
现在,解决方法很简单:只需按照你希望提交出现的顺序重新排列这些行。应该看起来像这样:
pick a4cebf7 Add greeting.txt
pick f03baa5 Add inquiry.txt
pick 90bb015 Add farewell.txt
保存并关闭你的编辑器,而 Git 将为你完成其余工作。请注意,在实践中这样做可能会导致冲突,参看下面章节以获取解决冲突的帮助。
git pull –rebase
如果你一直在由上游更新的分支 <branch>
(比如说原始远程)上做一些提交,通常 git pull
会创建一个合并提交。在这方面,git pull
的默认行为等同于:
git fetch origin <branch>
git merge origin/<branch>
假设本地分支 <branch>
配置为从原始远程跟踪 <branch>
分支,即:
$ git config branch.<branch>.remote
origin
$ git config branch.<branch>.merge
refs/heads/<branch>
还有另一种选择,它通常更有用,并且会让历史记录更清晰:git pull --rebase
。与合并方式不同,这基本上 4 等效于以下内容:
git fetch origin
git rebase origin/<branch>
合并方式更简单易懂,但是如果你了解如何使用 git rebase
,那么变基方式几乎可以做到你想要做的任何事情。如果愿意,可以将其设置为默认行为,如下所示:
git config --global pull.rebase true
当你执行此操作时,从技术上讲,你在应用我们在下一节中讨论的过程……因此,让我们也解释一下故意执行此操作的含义。
使用 git rebase 来变基
具有讽刺意味的是,我最少使用的 Git 变基功能是它以之命名的功能:变基分支。假设你有以下分支:
A--B--C--D--> master
--E--F--> feature-1
--G--> feature-2
事实证明,feature-2
不依赖于 feature-1
的任何更改,它依赖于提交 E,因此你可以将其作为基础脱离 master
。因此,解决方法是:
git rebase --onto master feature-1 feature-2
非交互式变基对所有牵连的提交都执行默认操作(pick
) 5 ,它只是简单地将不在 feature-1
中的 feature-2
中提交重放到 master
上。你的历史记录现在看起来像这样:
A--B--C--D--> master
| --G--> feature-2
--E--F--> feature-1
解决冲突
解决合并冲突的详细信息不在本指南的范围内,将来请你注意另一篇指南。假设你熟悉通常的解决冲突的方法,那么这里是专门适用于变基的部分。
有时,在进行变基时会遇到合并冲突,你可以像处理其他任何合并冲突一样处理该冲突。Git 将在受影响的文件中设置冲突标记,git status
将显示你需要解决的问题,并且你可以使用 git add
或 git rm
将文件标记为已解决。但是,在 git rebase
的上下文中,你应该注意两个选项。
首先是如何完成冲突解决。解决由于 git merge
引起的冲突时,与其使用 git commit
那样的命令,更适当的变基命令是 git rebase --continue
。但是,还有一个可用的选项:git rebase --skip
。 这将跳过你正在处理的提交,它不会包含在变基中。这在执行非交互性变基时最常见,这时 Git 不会意识到它从“其他”分支中提取的提交是与“我们”分支上冲突的提交的更新版本。
帮帮我! 我把它弄坏了!
毫无疑问,变基有时会很难。如果你犯了一个错误,并因此而丢失了所需的提交,那么可以使用 git reflog
来节省下一天的时间。运行此命令将向你显示更改一个引用(即分支和标记)的每个操作。每行显示你的旧引用所指向的内容,你可对你认为丢失的 Git 提交执行 git cherry-pick
、git checkout
、git show
或任何其他操作。
作者:git-rebase 选题:lujun9972 译者:wxy 校对:wxy
发表回复