聊聊 git 中 detached HEAD、amend、rebase 和 reset

4,782 阅读6分钟

聊聊 git 中 detached HEAD、amend、rebase 和 reset

20190609235612.png

⭐️ 更多前端技术和知识点,搜索订阅号 JS 菌 订阅

分离头导致 commit 丢失

分离头是指 checkout 历史版本后,做了修改并提交 commit,这时切回别的分支,之前提交的 commit 就会被抛弃。如果想要保留需要手动创建一个新的分支。

查看提交记录

git log --oneline

可以看到有两个提交记录

7c53c63 (HEAD -> master) 创建文件
c034a61 init

这时 checkout 到历史版本

Note: checking out 'c034a61'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b <new-branch-name>

HEAD is now at c034a61 init

现在就出现了分离头,在这个时候修改文件内容

diff --git a/fdsa b/fdsa
index e69de29..2d7a928 100644
--- a/fdsa
+++ b/fdsa
@@ -0,0 +1 @@
+change file content
(END)

查看 status

HEAD detached at c034a61
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   fdsa

no changes added to commit (use "git add" and/or "git commit -a")

提交 commit 就会提示 detached HEAD 修改

git commit -am 'modify'

[detached HEAD 9a78be9] modify
 1 file changed, 1 insertion(+)

如果此时 checkout 分支,这些提交的内容就会在以后的某个时间点被 git 抛弃。

git checkout master

Warning: you are leaving 1 commit behind, not connected to
any of your branches:

  9a78be9 modify

If you want to keep it by creating a new branch, this may be a good time
to do so with:

 git branch <new-branch-name> 9a78be9

Switched to branch 'master'

按照提示,使用命令 git branch xxx 9a78be9 就可以创建分支,保留所有这些 commit

git branch -a

  another
* master
(END)

HEAD 版本比较两种操作符的区别

diff commit 的时候经常需要查看当前 commit 和上一个版本或上上个版本的差异,^~ 的操作符两个用法是不一样的

git diff HEAD HEAD^

这个指的是 HEAD 和 HEAD 上一个版本的比较等同于 git diff HEAD HEAD^1 也等同于 git diff HEAD HEAD~1

git diff HEAD HEAD^^

这个指的是 HEAD 和 HEAD 的上上个版本的比较,等同于 git diff HEAD HEAD^1^1 也等同于 git diff HEAD HEAD~2

所以你以为有 git diff HEAD HEAD^2 这样的使用方法吗?那就错了,并没有 HEAD^2 你必须写成 HEAD~2 或者 HEAD^1^1 🤕

fatal: ambiguous argument 'HEAD^2': unknown revision or path not in the working tree.

amend 并不能修改历史提交信息

通常我们使用 git commit --amend 来修改最近一次提交的 message,那么修改历史提交的 commit message,怎么操作呢。并没有什么 git commit --amend^ 之类的东西,正确的做法是使用 rebase

2842585 (HEAD -> master) add app.js
7c53c63 创建文件
c034a61 init

假设需要修改第二次的提交信息,将 创建文件 改成 add main.css,那么使用 rebase 的交互式命令:

git rebase -i c034a61

注意 hash 值是要修改的 commit 的上一个 commit

pick 7c53c63 创建文件
pick 2842585 add app.js

# Rebase c034a61..2842585 onto c034a61 (2 commands)
#

出现上述提示,我们需要使用 reword 修改 commit 提交信息,修改第一个 pick 为 reword:

reword 7c53c63 创建文件
pick 2842585 add app.js

保存,接着就会弹出新的窗口,这个时候就可以修改 commit message 了,保存即可:

[detached HEAD 9ccb083] add main.css
 Date: Fri Jun 7 12:54:21 2019 +0800
 1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

提示修改成功

c0bf3b1 (HEAD -> master) add app.js
9ccb083 add main.css
c034a61 init

另外 rebase 还有很多其他用途,比如合并历史 commit:

reword 9ccb083 add main.css
squash c0bf3b1 add app.js

将两个 commit 合并,并修改最新的提交信息

[detached HEAD b46e9cc] init website
 Date: Fri Jun 7 12:54:21 2019 +0800
 1 file changed, 1 insertion(+)
[detached HEAD 209f417] init website
 Date: Fri Jun 7 12:54:21 2019 +0800
 2 files changed, 1 insertion(+)
 create mode 100644 app.js
Successfully rebased and updated refs/heads/master.

合并后效果如下:

209f417 (HEAD -> master) init website
c034a61 init

rebase 可以合并多个非相邻的 commit

如果我们想要合并多个 commit 但这些 commit 并不是紧挨着的,而是分散开来的。那么使用 rebase 仍然还是可以合并的:

1421dc2 (HEAD -> master) init server app
209f417 init website
c034a61 init

假设有三个 commit 我需要将第一个和最新的 commit 合并,那么使用 rebase:

git rebase -i c034a61

弹出编辑器:

pick 209f417 init website
pick 1421dc2 init server app

因为第一个没有显示,那么需要手动添加

pick 209f417 init website
pick 1421dc2 init server app
pick c034a61

然后调整顺序:

pick 1421dc2 init server app
squash c034a61
pick 209f417 init website

这样 c034a61 就会和 init server app 合并

如果有冲突那么解决冲突然后 rebase --continue 或放弃 rebase --abort

checkout、clean 和 reset 的回退

checkout 和 reset 都是用于恢复文件的,但两者的区别是 checkout 是恢复工作区的,reset 则是恢复暂存区到工作区的。

假设目前是这种情况:

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   read.me

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   app.js

Untracked files:
  (use "git add <file>..." to include in what will be committed)

	readme.md

看到提示我们将暂存区的 read.me 恢复则需要使用 reset;如果需要将修改后的工作区的文件 app.js 恢复则使用 checkout;另外还有未追踪的文件 readme.md,这个文件的恢复则需要使用到 clean

我们挨个来恢复:

git clean -f

首先使用 clean 命令清空工作区中未追踪的文件

Removing readme.md

然后使用

git checkout .

清空工作区修改的文件

Updated 1 path from the index

最后再恢复暂存区中的文件:

git reset HEAD .

恢复完成后,暂存区的文件会变为工作区的文件,这个时候还需要再次 checkout 或 clean 一下,这取决于你是新增的文件还是修改已有的文件

JS 菌公众账号

请关注我的订阅号,不定期推送有关 JS 的技术文章,只谈技术不谈八卦 😊