Git Handling errors

此篇介绍Git处理一些提交错误的大全。涉及revert,reset,rebase等。

Start

修改非最新commit

演示示例

实际上有了交互式rebase,随便哪一个commit出错了都可以直接修改,只要在本地。

commit --amend 可以修复最新 commit 的错误,但如果是倒数第二个 commit 写错了,怎么办?

在``git-exercise : sami`分支上进行操作。这条分支还是很干净的。提交三次作为实验。

1

rebase回退两个。定位到1实际上。

2
1
2
3
4
5
6
7
8
9
10
➜  git-exercise git:(sami) git rebase -i HEAD^^
Stopped at 229cf94... rebase i 22
You can amend the commit now, with

git commit --amend

Once you are satisfied with your changes, run

git rebase --continue
➜ git-exercise git:(229cf94)

可以看出停在了22这一次提交,就可以在22基础上就行改正。方式就是当作最新的一次commit就行修改即可。

3
1
2
3
4
5
➜  git-exercise git:(229cf94) git add .
➜ git-exercise git:(229cf94) ✗ git commit --amend
[detached HEAD 8fec5f4] rebase i 22 修改了一下,并直接使用add .
1 file changed, 2 insertions(+)
➜ git-exercise git:(8fec5f4)
4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
➜  git-exercise git:(8fec5f4) git rebase --continue
Auto-merging sami1.txt
CONFLICT (content): Merge conflict in sami1.txt
error: could not apply 45d3a51... rebase i 333
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 45d3a51... rebase i 333
# 解决文件冲突
➜ git-exercise git:(8fec5f4) ✗ git add sami1.txt
➜ git-exercise git:(8fec5f4) ✗ git rebase --continue
[detached HEAD d480273] rebase i 333
1 file changed, 2 insertions(+)
Successfully rebased and updated refs/heads/sami.

5

交互式rebase

交互式 rebase,它可以在 rebase 开始之前指定一些额外操作。交互式 rebase 最常用的场景是修改写错的 commit,但也可以用作其他用途。它的大致用法:

  1. 使用方式是 git rebase -i 目标commit
  2. 在编辑界面中指定需要操作的 commits 以及操作类型;
  3. 操作完成之后用 git rebase --continue 来继续 rebase 过程。

如果不是最新的 commit 写错,就不能用 commit --amend 来修复了,而是要用 rebase。不过需要给 rebase 也加一个参数:-i

rebase -irebase --interactive 的缩写形式,意为「交互式 rebase」。

所谓「交互式 rebase」,就是在 rebase 的操作执行之前,你可以指定要 rebasecommit 链中的每一个 commit 是否需要进一步修改。那么你就可以利用这个特点,进行一次「原地 rebase」。

用法

1
2
3
4
5
6
git rebase -i HEAD^^ # 定位到HEAD之前2个的commit上
# 把当前 commit ( HEAD 所指向的 commit) rebase 到 HEAD 之前 2 个的 commit 上
#把要修改的commit 前面的pick 改成edit 然后使用之前那一套
git add 处理要修改的文件
git commit --amend
git rebase --continue

说明:在 Git 中,有两个「偏移符号」: ^~

^ 的用法:在 commit 的后面加一个或多个 ^ 号,可以把 commit 往回偏移,偏移的数量是 ^ 的数量。例如:master^ 表示 master 指向的 commit 之前的那个 commitHEAD^^ 表示 HEAD 所指向的 commit 往前数两个 commit

~ 的用法:在 commit 的后面加上 ~ 号和一个数,可以把 commit 往回偏移,偏移的数量是 ~ 号后面的数。例如:HEAD~5 表示 HEAD 指向的 commit往前数 5 个 commit

丢弃最新提交

1
2
3
git reset --hard HEAD^
➜ git-exercise git:(main) git reset --hard HEAD^
HEAD is now at 5ce1e26 rebase分支提交

6

丢弃非最新提交

不是最新的提交,就不能用 reset --hard 来撤销了。这种情况的撤销,就要用之前介绍过的一个指令:交互式 rebase ——rebase -i

「撤销过往的提交」。方法有两种:

  1. git rebase -i 在编辑界面中删除想撤销的 commits
  2. git rebase --onto 在 rebase 命令中直接剔除想撤销的 commits

方法有两种,理念是一样的:在 rebase 的过程中去掉想撤销的 commit,让他它消失在历史中。

演示示例丢弃rebase 1

7

把第一条删除

8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
➜  git-exercise git:(sami) git rebase -i sami~3
Auto-merging sami1.txt
CONFLICT (content): Merge conflict in sami1.txt
error: could not apply 8fec5f4... rebase i 22 修改了一下,并直接使用add .
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 8fec5f4... rebase i 22 修改了一下,并直接使用add .
➜ git-exercise git:(4b3689d) ✗ git add .
➜ git-exercise git:(4b3689d) ✗ git rebase --continue
[detached HEAD f15d6cf] rebase i 22 修改了一下,并直接使用add .
1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/sami.

从sami最新rebase到之前3个commits,HEAD到了4b3689,产生冲突。

9

1
2
3
4
5
6
7
Sami de branch
xiugai
<<<<<<< HEAD
=======
Rebase交互式修改不是最新的commits 1
Rebase交互式修改不是最新的commits 22 修改倒数第二个22222
>>>>>>> 8fec5f4 (rebase i 22 修改了一下,并直接使用add .)

10

演示合并无效提交

15

16

17

1
2
3
4
5
6
➜  git-exercise-b git:(sami) git log       # 找到要rebase的位置
➜ git-exercise-b git:(sami) git rebase -i 4b36
[detached HEAD 66d9175] yaosuo一次就够
1 file changed, 5 insertions(+)
create mode 100644 sami-yasuo.txt
Successfully rebased and updated refs/heads/sami.

错误commit已经push

讲当错误的 commit 已经被 push 上去时的解决方案。具体的方案有两类:

  1. 如果出错内容在私有 branch:在本地把内容修正后,强制 push (push -f)一次就可以解决;
  2. 如果出错内容在 master:不要强制 push,而要用 revert 把写错的 commit 撤销。

11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
➜  git-exercise git:(main) git revert HEAD~1
CONFLICT (modify/delete): revert.txt deleted in parent of 534e060 (test-revert) and modified in HEAD. Version HEAD of revert.txt left in tree.
error: could not revert 534e060... test-revert
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
➜ git-exercise git:(main) ✗ git revert HEAD
error: Reverting is not possible because you have unmerged files.
hint: Fix them up in the work tree, and then use 'git add/rm <file>'
hint: as appropriate to mark resolution and make a commit.
fatal: revert failed
➜ git-exercise git:(main) ✗ git status
On branch main
Your branch is ahead of 'origin/main' by 6 commits.
(use "git push" to publish your local commits)

You are currently reverting commit 534e060.
(fix conflicts and run "git revert --continue")
(use "git revert --skip" to skip this patch)
(use "git revert --abort" to cancel the revert operation)

Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add/rm <file>..." as appropriate to mark resolution)
deleted by them: revert.txt

no changes added to commit (use "git add" and/or "git commit -a")
➜ git-exercise git:(main) ✗ git add .
➜ git-exercise git:(main) git status
On branch main
Your branch is ahead of 'origin/main' by 6 commits.
(use "git push" to publish your local commits)

You are currently reverting commit 534e060.
(all conflicts fixed: run "git revert --continue")
(use "git revert --skip" to skip this patch)
(use "git revert --abort" to cancel the revert operation)

nothing to commit, working tree clean
➜ git-exercise git:(main) git revert --continue
On branch main
Your branch is ahead of 'origin/main' by 6 commits.
(use "git push" to publish your local commits)

You are currently reverting commit 534e060.
(all conflicts fixed: run "git revert --continue")
(use "git revert --skip" to skip this patch)
(use "git revert --abort" to cancel the revert operation)

nothing to commit, working tree clean
➜ git-exercise git:(main) git revert --abort
➜ git-exercise git:(main) git revert HEAD
[main 6db3b2e] Revert "test-revert-1"
1 file changed, 1 deletion(-)

reset 的本质——不止可以撤销提交

reset 指令的本质:重置 HEAD 以及它所指向的 branch 的位置。同时,介绍了 reset 的三种参数:

  1. --hard:重置位置的同时,清空工作目录的所有改动;
  2. --soft:重置位置的同时,保留工作目录和暂存区的内容,并把重置 HEAD 的位置所导致的新的文件差异放进暂存区。
  3. --mixed(默认):重置位置的同时,保留工作目录的内容,并清空暂存区。

reset --hard 不仅可以撤销提交,还可以用来把 HEADbranch 移动到其他的任何地方。

reset 指令可以重置 HEADbranch 的位置,不过在重置它们的同时,对工作目录可以选择不同的操作,而对工作目录的操作的不同,就是通过 reset 后面跟的参数来确定的。

reset–hard

reset --hard 会在重置 HEADbranch 的同时,重置工作目录里的内容。当你在 reset 后面加了 --hard 参数时,你的工作目录里的内容会被完全重置为和 HEAD 的新位置相同的内容。换句话说,就是你的未提交的修改会被全部擦掉。

你的 HEAD 和当前 branch 切到上一条 commit 的同时,你工作目录里的新改动也一起全都消失了,不管它们是否被放进暂存区

reset–soft

reset --soft 会在重置 HEADbranch 时,保留工作目录和暂存区中的内容,并把重置 HEAD 所带来的新的差异放进暂存区。--hard 会清空工作目录的改动,而 --soft 则会保留工作目录的内容,并把因为保留工作目录内容所带来的新的文件差异放进暂存区。还没有放入暂存区的文件仍然存在,实际上所有的工作目录文件都在。

reset不加参数

reset 如果不加参数,那么默认使用 --mixed 参数。它的行为是:保留工作目录,并且清空暂存区。由 reset 所导致的新提交的文件,直接变成未追踪文件。原来工作区的暂存没有暂存的都变成未暂存。

Git stash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
➜  git-exercise git:(main) git add gitstash.txt 
➜ git-exercise git:(main) ✗ git status
On branch main
Your branch is ahead of 'origin/main' by 7 commits.
(use "git push" to publish your local commits)

Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: gitstash.txt

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

➜ git-exercise git:(main) ✗ git stash
Saved working directory and index state WIP on main: 6db3b2e Revert "test-revert-1"
➜ git-exercise git:(main) ✗ git checkout sami
Switched to branch 'sami'
➜ git-exercise git:(sami) ✗ git status
On branch sami
Untracked files:
(use "git add <file>..." to include in what will be committed)
gitstas1.txt

nothing added to commit but untracked files present (use "git add" to track)
➜ git-exercise git:(sami) ✗ git checkout main
Switched to branch 'main'
Your branch is ahead of 'origin/main' by 7 commits.
(use "git push" to publish your local commits)
➜ git-exercise git:(main) ✗ git status
On branch main
Your branch is ahead of 'origin/main' by 7 commits.
(use "git push" to publish your local commits)

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

nothing added to commit but untracked files present (use "git add" to track)
➜ git-exercise git:(main) ✗ git stash pop
On branch main
Your branch is ahead of 'origin/main' by 7 commits.
(use "git push" to publish your local commits)

Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: gitstash.txt

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

Dropped refs/stash@{0} (1b5ba513b08ccf60326d068ae516cb35dc87c254)

目前尚未暂存的也不会丢失。但为了保险起见:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
git stash -u #include 没有被 track 的文件
git checkout other
# after operate
git checkout old
git stash pop
#查看list
git stash list
-------------------
git stash //把本地的改动暂存起来
git stash save "message" 执行存储时,添加备注,方便查找。
git stash pop // 应用最近一次暂存的修改,并删除暂存的记录
git stash apply // 应用某个存储,但不会把存储从存储列表中删除,默认使用第一个存储,即 stash@{0},如果要使用其他个,git stash apply stash@{$num} 。
git stash list // 查看 stash 有哪些存储
git stash clear // 删除所有缓存的 stash

找回已经删除的branch

reflog 是 “reference log” 的缩写,使用它可以查看 Git 仓库中的引用的移动记录。如果不指定引用,它会显示 HEAD 的移动记录。假如你误删了 branch1 这个 branch,那么你可以查看一下 HEAD 的移动历史:

git fetch

12

14
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
➜  git-exercise-b git:(main) git fetch
➜ git-exercise-b git:(main) git fetch origin main
From github.com:cutegentle/git-exercise
* branch main -> FETCH_HEAD
➜ git-exercise-b git:(main) git pull
Updating 942f1d3..877ca87
Fast-forward
branch-a-1.txt | 3 +++
1 file changed, 3 insertions(+)
create mode 100644 branch-a-1.txt
➜ git-exercise-b git:(main) git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

回滚总结

主流:checkout

在sami分支,chekout回退至sami分支第一次提交进行一些debug。

16

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
➜  git-exercise-b git:(sami) git checkout 08c7
# 会提示你head进入游离状态,也就是仅仅指向commit。
# 不必理会,可以发现文件已经退回到当时状态,可以随便操作
# 操作完直接切回原来分支就可以
Note: switching to '08c7'.

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 switching back to a branch.

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

git switch -c <new-branch-name>

Or undo this operation with:

git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 08c7221 sami分支第一次提交
➜ git-exercise-b git:(08c7221) git add .
➜ git-exercise-b git:(08c7221) ✗ git commit -m "回退08c7进行一些更改"
[detached HEAD 0cd559b] 回退08c7进行一些更改
1 file changed, 1 insertion(+)
➜ git-exercise-b git:(0cd559b) git log
➜ git-exercise-b git:(0cd559b) git checkout sami
Warning: you are leaving 1 commit behind, not connected to
any of your branches:

0cd559b 回退08c7进行一些更改

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> 0cd559b

Switched to branch 'sami'
# 不加下面这句就不会产生新分支,权当测试了,毫无影响。
# 如果觉得在上面修改的一些东西是有效的,那么就可以用如下命令,新起一条分支,到时候可以合并进去
➜ git-exercise-b git:(sami) git branch testcheckoutrollback 0cd559b

reset

  • 「当你用 reset 回滚到了某个版本后,那么在下一次 git 提交时,之前该版本后面的版本会被作为垃圾删掉。」
  • 「当我们回退到一个旧版本后,此时再用 git log 查看提交记录,会发现之前的新版本记录没有了。如果第二天,你又想恢复到新版本怎么办?找不到新版本的 commit_id 怎么办?」

「我们可以用 git reflog 查看历史命令,这样就可以看到之前新版本的 commit_id ,然后 git reset --hard commit_id 就可以回到之前的新版本代码」

  • 虽然可以用 git reflog 查看本地历史,然后回复到之前的新版本代码,但是在别的电脑上是无法获取你的历史命令的,所以这种方法不安全。万一你的电脑突然坏了,这时候就无法回到未来的版本。

这种方式回退是比较粗暴的,可能会丢失信息。

revert

「千万不要用 git reset 回退已经被推送到公共仓库上的 提交,它只适用于回退本地修改(从未提交到公共仓库中)。如果你需要修复一个公共提交,最好使用 git revert」。revert适合发现bug在一个commits中。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!