git 各種撤銷
因?yàn)間it有三個(gè)區(qū):工作區(qū)勾拉,索引區(qū)和版本區(qū)。所以git的撤銷有很多種盗温,比如:
- 撤銷工作區(qū): 剛寫了幾行代碼藕赞,不想要了,想撤銷卖局。
- 撤銷版本區(qū): 剛提交了一次代碼斧蜕,但由于疏忽,漏掉了幾個(gè)文件或者備注信息寫錯(cuò)了砚偶,想撤銷后重新提交批销。
撤銷工作區(qū)
一個(gè)已經(jīng)提交的文件中新加了一行,現(xiàn)在想撤銷染坯,怎么辦均芽?
因?yàn)樾录恿艘恍校髠?cè)出現(xiàn)** > **符號(hào)单鹿,表示有修改掀宋。這個(gè)修改只發(fā)生在“工作區(qū)”,尚未進(jìn)入“索引區(qū)”。
$ git status
On branch master
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: src/main/java/io/downgoon/hello/boot/HelloWorld.java
如何撤銷布朦?
- 命令行方式
$ git checkout -- src/main/java/io/downgoon/hello/boot/HelloWorld.java
符號(hào) ** -- ** 可以理解為“索引區(qū)”囤萤。
- GUI 方式
注意: 菜單在 Team 的下面。Eclipse里面說(shuō)的 Replace With 對(duì)應(yīng)的就是 git checkout
命令是趴,解答了許多人問(wèn) “為什么Eclipse Git 沒(méi)有checkout菜單”涛舍。
撤銷版本區(qū)(重新編輯最后一次提交)
剛提交了一次代碼,但由于疏忽唆途,漏掉了幾個(gè)文件或者備注信息寫錯(cuò)了富雅,想撤銷后重新提交。怎么辦肛搬?
git的設(shè)計(jì)者也考慮到人容易犯錯(cuò)誤没佑,特地為這種場(chǎng)景設(shè)計(jì)了一個(gè)“改過(guò)自新(amend)”的機(jī)會(huì)。這種情況都不需用更具一般性的 git reset
命令温赔,然后再git commit
蛤奢,而是直接git commit --amend
。
如下代碼創(chuàng)建了c.txt和d.txt兩個(gè)文件陶贼,本打算兩個(gè)文件一起提交的啤贩,但一時(shí)筆誤,只把c.txt提交了拜秧,d.txt沒(méi)有提交痹屹。
? GitTutorial git:(master) echo "ccc" > c.txt
? GitTutorial git:(master) ? echo "ddd" > d.txt
? GitTutorial git:(master) ? git add *
? GitTutorial git:(master) ? git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: c.txt
new file: d.txt
? GitTutorial git:(master) ? git commit c.txt -m 'add c.txt and d.txt'
[master 2ef4359] add c.txt and d.txt
1 file changed, 1 insertion(+)
create mode 100644 c.txt
? GitTutorial git:(master) ?
如何把d.txt也提交? 直接 git commit --amend c.txt d.txt
進(jìn)入vi編輯區(qū)枉氮,重新編輯提交注釋信息(注意:新增加的d.txt在命令行上已經(jīng)攜帶d.txt文件了)志衍,保存退出(:wq
)。
add c.txt and d.txt
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Wed Nov 30 20:36:05 2016 +0800
#
# On branch master
# Changes to be committed:
# new file: c.txt
# new file: d.txt
#
不妨假設(shè)新的注釋內(nèi)容修改為:
add c.txt and d.txt (don't forget d.txt)
按 :wq
保存退出后聊替,顯示
2 files changed, 2 insertions(+)
? GitTutorial git:(master) git commit --amend c.txt d.txt
[master 0935027] add c.txt and d.txt (don't forget d.txt)
Date: Wed Nov 30 20:36:05 2016 +0800
2 files changed, 2 insertions(+)
create mode 100644 c.txt
create mode 100644 d.txt
? GitTutorial git:(master) git status
On branch master
nothing to commit, working directory clean
? GitTutorial git:(master)
如果你愿意楼肪,你還可以繼續(xù)后悔最后一次提交,比如我們還需要追加e.txt文件佃牛,這次我們用GUI圖形界面演示(演示前淹辞,先自行增加e.txt,并加入索引區(qū)):
上圖所示的步驟:
- 右擊Project -> Team -> Commit ...
- 選擇右上角的 **Amend (Edit Previous Commit) 圖標(biāo)
- 勾選需要追加的 e.txt
- 修改注釋內(nèi)容俘侠,并提交象缀。
事后可以查看日志,并對(duì)比 git log
和 git reflog
有什么不同 爷速? 自己做實(shí)驗(yàn)觀察央星。
amend失靈的時(shí)候
但是如果我們不是追加,而是想刪除一些呢惫东? 比如從之前的提交了c.txt莉给,d.txt和e.txt毙石,要amend成只提交d.txt呢? 嘗試的結(jié)果居然不可以(命令
git commit --amend d.txt -m 'only d.txt, remove c.txt and e.txt'
)颓遏。
需要真的撤銷徐矩,再提交。
撤銷版本區(qū)(真的撤銷再提交)
撤銷實(shí)操
所謂的
回滾
其實(shí)就是將分支游標(biāo)
master指向之前的提交叁幢,重置命令git reset
上場(chǎng):git reset --hard commit-ID
即可滤灯。
$ git reset --hard <tag/branch/commit id>
接著需要強(qiáng)制推送到遠(yuǎn)程:
$ git push <reponame> --force
但是強(qiáng)制
推送遠(yuǎn)程,這個(gè)操作很危險(xiǎn)曼玩,通常權(quán)限會(huì)被禁止:
remote: GitLab: You are not allowed to force push code to a
protected branch
on this project.
To http://gitlab.com/blueocean/boxstore.git
! [remote rejected] master -> master (pre-receive hook declined)
在gitlab
中鳞骤,項(xiàng)目的master
與owner
都很可能沒(méi)有這個(gè)權(quán)限,只有gitlab
的管理員才有這個(gè)權(quán)限黍判。
撤銷圖解
章節(jié)開(kāi)頭提到:
所謂的
回滾
其實(shí)就是將分支游標(biāo)
master指向之前的提交豫尽,重置命令git reset
上場(chǎng):git reset --hard commit-ID
即可。
- 一個(gè)分支提交序列
- 回退到
HEAD
當(dāng)執(zhí)行git reset HEAD
時(shí)顷帖,不會(huì)做任何事情美旧。因?yàn)楫?dāng)前分支本身就是指向HEAD
的。
- 回退1步驟
$ git reset HEAD~1
其中符號(hào)HEAD~1
表示HEAD
回退1步(即:HEAD的父親節(jié)點(diǎn))窟她。
HEAD~1
is shorthand case for “the commit right before HEAD”, or put differently “HEAD’s parent”陈症。
回退完后,HEAD
指針:
- 回退2步
$ git reset HEAD~2
指針指向:
復(fù)雜的參數(shù)
git reset
概念很簡(jiǎn)單震糖,就是回退到某個(gè)提交點(diǎn)。但是它的參數(shù)蠻復(fù)雜趴腋,如下三條命令的區(qū)別是什么吊说?
git reset --hard <commit-id>
git reset --soft <commit-id>
git reset --mixed <commit-id> (默認(rèn)情況)
我們知道git
有:工作區(qū),索引區(qū)和版本庫(kù)的概念优炬。這三個(gè)選項(xiàng)參數(shù)就是影響對(duì)三個(gè)區(qū)的不同處理颁井。簡(jiǎn)單說(shuō),選項(xiàng)參數(shù)就是reset
的程度:
-
soft
: 程度最輕蠢护,僅僅是把版本庫(kù)的重置雅宾,索引區(qū)和工作區(qū)沒(méi)做任何修改。 -
mixed
: 程度中葵硕,也是默認(rèn)操作眉抬,它把版本區(qū)和索引區(qū)都重置了,但是工作區(qū)沒(méi)有重置懈凹。 -
hard
: 程度最強(qiáng)蜀变,三個(gè)區(qū)全部重置了。
人們習(xí)慣
--hard
介评,為什么呢库北?因?yàn)橹刂煤笈澜ⅲ藗兂3P枰萌庋廴ズ藢?duì),核對(duì)的方式往往是打開(kāi)某個(gè)目錄看看或文件看看寒瓦,而這些都是在工作區(qū)
的狀態(tài)情屹。
三選項(xiàng)對(duì)比
以下圖例:綠色的表示
reset
的了;紅色表示沒(méi)有reset
杂腰。
soft
版本區(qū)被重置了垃你,但是索引區(qū)和工作區(qū)都沒(méi)有被重置。
mixed
版本區(qū)和索引區(qū)都被重置了颈墅,但是工作區(qū)都沒(méi)有被重置蜡镶。
hard
版本區(qū)、索引區(qū)和工作區(qū)都被重置了恤筛。
快速實(shí)驗(yàn)
快速做個(gè)實(shí)驗(yàn)驗(yàn)證一下官还,分別創(chuàng)建c1.txt
,c2.txt
和c3.txt
毒坛,三個(gè)文件分別提交三次望伦,然后回退到中間那次提交:
- 連續(xù)3次提交
echo "c1" > c1.txt
git add c1.txt && git commit -m 'c1'
echo "c2" > c2.txt
git add c2.txt && git commit -m 'c2'
echo "c3" > c3.txt
git add c3.txt && git commit -m 'c3'
- 回退到中間
git reset HEAD~1
我們選擇的是默認(rèn)的--mixed
模式。
- 查看狀態(tài)
$ git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
c3.txt
nothing added to commit but untracked files present (use "git add" to track)
$ ls
c1.txt c2.txt c3.txt
的確從文件系統(tǒng)上看煎殷,文件c3.txt
并沒(méi)有消失屯伞,但是從索引區(qū)看,它消失了(就是回退到中間了)豪直。
查看日志
查看日志時(shí)候劣摇,現(xiàn)在只能看到最前面的兩條commit:
$ git log
c4b06a0 c2
81ee32e c1
但是這并不是表示git
就放棄對(duì)reset
的追蹤了,實(shí)際上弓乙,我們還可以對(duì)剛才的reset
進(jìn)行回滾末融,我們查看更詳細(xì)的日志:
$ git reflog
c4b06a0 HEAD@{0}: reset: moving to HEAD~1
c51558f HEAD@{1}: commit: c3
c4b06a0 HEAD@{2}: commit: c2
81ee32e HEAD@{3}: commit (initial): c1
如果我們回到之前的c3,也完全可以暇韧。
git revert 與 git reset 的區(qū)別
剛才勾习,git reset
后,可能因?yàn)闄?quán)限的問(wèn)題無(wú)法強(qiáng)行git push --force
懈玻。但是難道如果程序員誤操作提交了一次錯(cuò)誤的東西到master
就沒(méi)法回滾了(指不需要gitlab
管理員來(lái)回滾)巧婶?
可以用git revert HEAD
,它跟git reset
的不同主要有兩點(diǎn):
git reset
是指HEAD
指針涂乌,重新指向某個(gè)commit-id的位置艺栈。并且它后續(xù)的commit-id會(huì)被刪除。git revert
會(huì)產(chǎn)生一條新的commit骂倘,原有的commit-log并不會(huì)發(fā)生任何變化眼滤。git reset
只是改變HEAD的指向。而git reset
是真的 回收历涝,它不僅可以回收最后一次的诅需,還可以回收中間的漾唉,它回收中間的,中間之后的并不回收堰塌。比如赵刑,連續(xù)三次提交了三個(gè)文件,分別是c1.txt, c2.txt和c3.txt场刑,如果回收第二個(gè)般此,那么會(huì)剩2個(gè)文件,分別是c1.txt和c3.txt牵现,而不會(huì)只有c1.txt铐懊。連續(xù)三次提交3個(gè)文件
mkdir revertlab && cd revertlab && git init
echo "c1" > c1.txt
git add c1.txt && git commit -m 'c1'
echo "c2" > c2.txt
git add c2.txt && git commit -m 'c2'
echo "c3" > c3.txt
git add c3.txt && git commit -m 'c3'
然后撤銷中間那次:
$ git revert HEAD~1
會(huì)自動(dòng)編寫 commit
Revert "c2"
This reverts commit c86b27c706d6a88082958818643e6b26db8b7300.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Changes to be committed:
# deleted: c2.txt
#
查看日志,發(fā)現(xiàn)的確是產(chǎn)生一條新的commit:
$ git log --oneline
c94de9d Revert "c2"
a6681f2 c3
c86b27c c2
402e6df c1
查看本地文件:
$ ls
c1.txt c3.txt
很驚訝的是瞎疼,它是精準(zhǔn)的撤銷中間那次操作c2科乎,c3.txt文件依然保留了。
總結(jié)
- 撤銷工作區(qū):
git checkout -- <files>
- 撤銷版本區(qū)(重新編輯最后一次提交):
git commit --amend <files>
- 撤銷版本區(qū)(真的撤銷再提交):
git reset --soft HEAD^
接著git commit <files> -m ''
-
git reset
與git revert
的區(qū)別贼急。