Git的反悔操作(一)

概述

這次主要來講講Git的反悔操作羽杰,自己平時在寫代碼的過程中經(jīng)常會出現(xiàn)想要棄用所有的改動或回滾到上一次commit的情況。Git上的反悔操作有resetrebase泽腮、revert等像吻,每個操作各有區(qū)別和對應的使用場景峻汉,這里做下總結(jié)。

Git的反悔操作有兩大類:

  • 撤銷改動 ( Undoing Change )
  • 重寫歷史 ( Rewriting History )

文章大部分翻譯于 Undoing ChangesRewriting history亡脑,并結(jié)合了自己的一些理解和補充喷斋。

撤銷改動(Undoing Change)

git checkout

git checkout有三個不同的功能:切換分支锅风、回滾至某個commit边器、回滾一個文件至某個commit训枢。切換分支是git checkout最常見的功能,不做介紹忘巧,這里主要介紹下它在撤銷文件改動上的應用恒界。

回滾至某個commit

git chekcout <commit>

上面的命令是回滾到工作目錄中指定的 commit 上,這是一個 只讀 操作砚嘴,不會影響到當前工作區(qū)的狀態(tài)十酣,它在你查看舊版本的文件時不會損壞你的代碼倉庫。通常际长,HEAD指向master分支或其他本地分支耸采,當使用git checkout回滾到以前的 commit 時,HEAD就不再指向某個分支了工育,而是直接指向一個commit虾宇,這時就叫做detached HEAD狀態(tài)。

切換到detached HEAD狀態(tài)時如绸,會有一個警告嘱朽。

警告

這個警告是告訴你,你現(xiàn)在做的所有事情與你開發(fā)項目的其余工作區(qū)是分離的竭沫,即所有的改動與本地倉庫的任一分支都無關(guān)燥翅,不會影響到其他的分支的狀態(tài)。如果你準備在detached HEAD狀態(tài)下開發(fā)新的feature蜕提,那將會沒有分支允許你回退這里森书,當你不可避免地切換到其他分支時,將沒有任何辦法引用到這個feature谎势。你可以把detached HEAD狀態(tài)看作是正在一個未命名的分支上凛膏。

HEADdetached HEAD 的區(qū)別可以參考 How can I reconcile detached HEAD with master/origin?

將英文翻譯為中文經(jīng)常會詞不達意,很難把握脏榆,建議還是看英文原文:)猖毫。

示例

假設(shè)你正在進行一次瘋狂的重構(gòu),但現(xiàn)在你不確定是否要繼續(xù)下去须喂。這時你想要看一下開始這次重構(gòu)之前項目原來的樣子吁断,首先你需要找到你想要查看的版本的ID趁蕊。

git log --oneline

假設(shè)你的項目歷史看起來像下面這樣:

b7119f2 Continue doing crazy things
872fa7e Try something crazy
a1e8fb5 Make some important changes to hello.py
435b61d Create hello.py
9773e52 Initial import

你可以使用git checkout查看Make some important changes to hello.py這次commit,如下:

git checkout a1e8fb5

這讓你的工作區(qū)切換到了a1e8fb5comimit的狀態(tài)仔役。你可以查看文件掷伙、編譯項目、運行測試用例又兵,甚至編輯文件任柜,完全不用擔心丟失項目“當前”的狀態(tài),你在這里做的所有修改都不會被保存到項目中沛厨。當你想要繼續(xù)那次瘋狂的重構(gòu)時宙地,你需要回到項目的“當前”狀態(tài)。

git checkout master

回滾一個文件至某個commit

git checkout <commit> <file>

回滾一個文件到以前的一個版本逆皮,這個操作會 影響 當前工作區(qū)的狀態(tài)宅粥。

你可以在一個新的快照中重新提交這個舊版本,當然也包含其他任何文件电谣。實際上粹胯,checkout的這個用法和revert類似,只不過是僅針對一個文件辰企。

示例

如果你只對單個文件感興趣,你可以使用 git checkout 獲取到該文件的舊版本况鸣。比如牢贸,如果你只想要看看 某次commit下的hello.py文件,可以使用下面的命令:

git checkout a1e8fb5 hello.py

記住镐捧,不像切換commit潜索,這會影響當前項目的狀態(tài)。這個舊版本的文件的狀態(tài)會變?yōu)?Change to be committed懂酱,給你一個機會將該文件恢復到先前的版本竹习。

如果你決定不需要保留這個舊版本了,你可以切換到最近的版本列牺,如下:

git checkout HEAD hello.py

git revert

git revert 可以撤銷一個已提交的快照(snapshot)整陌,但它解決的是如何撤銷已提交的被引入的改動,并生成內(nèi)容來追加一個新的提交瞎领,而不是從項目的歷史中移除這個提交泌辫,這避免了丟失歷史記錄,這對于項目的每一次修改的歷史記錄的完整性來說非常重要九默,并這是服務于可靠的多人協(xié)作開發(fā)的震放。

git revert <commit>

這句命令會撤銷這次<commit>所有被引入的改動,生成一個新的commit驼修,并應用在當前分支上殿遂。

當你想從你的項目歷史中移除一個完整的commit時诈铛,就應該使用git revert。比如墨礁,你正在追蹤一個Bug并發(fā)現(xiàn)它是在一次單一的commit中被引入的幢竹,你可以手動進行修改,刪除有Bug的代碼來修復它饵溅,然后提交一個新的快照妨退,但這樣很麻煩,效率也很低蜕企,你更應該做的是咬荷,使用git revert自動完成,撤銷這次commit所有被引入的改動轻掩。

Reverting vs. Resetting

很重要的一點幸乒,revert是對一次單一的commit的撤銷,并不是真正意義上的回滾唇牧。它不是通過移除項目中一次commit后面的所有提交來“回滾”之前的狀態(tài)罕扎,實際上那樣的操作在Git上被叫做reset,而不是revert丐重。

比起reset腔召,revert有兩個重要的好處:

  • revert不會改變項目的歷史。如果那些commits已經(jīng)推到了共享的代碼倉庫扮惦,它會是一個“安全”的操作臀蛛。為什么改變共享代碼倉庫的歷史是危險的,請看后面的git reset的介紹崖蜜。

  • revert可以作用于歷史中 任意 的單一的commit節(jié)點浊仆,然而reset只能做到從當前 最新 的commit開始回滾。比如說豫领,如果你想要只撤銷一次舊的指定的commit抡柿,使用git reset,你則必須移除該commit和該commit之后出現(xiàn)的所有commits等恐,然后再把那些隨后的commit重新提交洲劣。毫無疑問,這種撤銷的方式一點都不優(yōu)雅鼠锈。

示例1

下面的例子是git revert的一個簡單示例闪檬,提交了一個快照,然后立即使用revert撤銷了它购笆。

# Edit some tracked files

# Commit a snapshot
git commit -m "Make some changes that will be undone"

# Revert the commit we just created
git revert HEAD

注意:在revert后粗悯,第4次commit仍然被保留在項目歷史中,git revert新增了一個新的commit來撤銷它的改動同欠,而不是刪除它样傍。結(jié)果就是横缔,第3次和第5次commit的代碼是完全一樣的,第4次commit依然保留在歷史中衫哥,以防我們想要重新回滾到這里茎刚。

示例2

假設(shè)你發(fā)現(xiàn)在某次commit中引入了一個bug,你想使用 git revert來回滾撤逢。查看歷史:

git log --oneline

項目歷史如下:

417e4a9 commit 4
427d76b commit 3
1642475 introduced a bug
71d3ef7 commit 1
bf4f6f6 git initial

使用 revert 回滾到 1642475

git revert 1642475

但你會發(fā)現(xiàn)沒有想象中那么簡單膛锭,而是發(fā)生沖突了,報錯如下:

error: could not revert 1642475... introduced a bug
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'

revert 僅僅是撤銷introduced a bug這一commit的改動蚊荣,默認會生成一個新的commit提交初狰,但在它之后還有commit 3commit 4,它們的改動不會被影響互例,依然保留在工作區(qū)中奢入,因此產(chǎn)生了沖突。你可以手動解決沖突后commit媳叨,但這卻是個麻煩且不優(yōu)雅的方式腥光。因為1642475427d76b417e4a9這幾個commit的改動被一起合并在暫存區(qū)中糊秆,如果你修改的不止一個文件武福,那手動解決沖突將會非常麻煩。解決方式是痘番,默認 生成新的commit艘儒,并按順序回滾。

先強制結(jié)束revert

git revert --abort

按順序回滾

git revert 417e4a9 --no-commit
git revert 427d76b --no-commit
git revert 1642475 --no-commit  
git revert --continue

git revert --continue夫偶,會生成帶默認message的commit。

更多參數(shù)說明詳見:git-revert-document

git reset

如果git revert是以一個"安全""的方式來撤銷改動觉增,那你可以認為git reset是一種 危險 的方式兵拢。當你使用git reset后,將沒有辦法恢復原樣逾礁,它是一個永恒的撤銷,因為那些commits不再被任何refreflog引用说铃。在使用這個工具時請務必謹慎,因為它是git命令中唯一一個潛在的使你的努力付諸東流的命令嘹履。

git reset是一個功能豐富的命令腻扇,它可以用于移除已提交的快照,但它更多的是用來撤銷暫存區(qū)和工作區(qū)的改動砾嫉,另一種情況是幼苛,它應該只用于撤銷本地的改動(不應該reset那些已經(jīng)與其他開發(fā)者共享了的快照)。

用法

git reset <file>

從暫存區(qū)中移除指定的文件焕刮,但保留工作區(qū)不變舶沿。它unstage一個 文件且沒有覆蓋任何改動墙杯。

把文件加入暫存區(qū)叫做stage,文件修改過但還未使用git add加入暫存區(qū)叫做unstage

git reset

重置暫存區(qū)匹配至最近的一次commit括荡,但保留工作區(qū)不變高镐。它unstage所有 文件且沒有覆蓋任何改動,讓你有機會從頭開始重建暫存快照畸冲。

git reset --hard

重置暫存區(qū)和工作區(qū)匹配至最近的一次commit嫉髓。除了unstage所有文件外,-- hard還告訴Git也一并覆蓋工作區(qū)的所有改動邑闲,也就是說算行,這個操作撤銷了所有未提交的改動,所以在使用它前监憎,請確定你是真的想丟棄本地的開發(fā)纱意。

git reset <commit>

將當前分支的HEAD移動至<commit>,重置暫存區(qū)匹配至<commit>鲸阔,但不包括工作區(qū)池户。從<commit>開始的所有改動會被駐留在工作區(qū)炎辨,這讓你可以使用更干凈、更原子性的快照來重新提交項目歷史。

git reset --hard <commit>

將當前分支的HEAD移動至<commit>以及重置暫存區(qū)和工作區(qū)匹配至<commit>缀匕。它不僅撤銷了未提交的改動,還撤銷了<commit>之后的所有commits奕枢。

討論

正如上面提及到的夭问,git reset是用來從一個代碼倉庫中移除改動的。沒有-- hard標記時晃痴,git reset通過unstage改動或撤銷(uncommit)一系列已提交的快照來清理干凈代碼倉庫残吩,然后重頭開始重建它們。當一個試驗已經(jīng)往可怕的方向發(fā)展時倘核,-- hard標記就派上用場了泣侮,你需要一個干凈的工作空間。

reset是被設(shè)計來撤銷 本地 的改動的紧唱,而revert是被設(shè)計來安全地撤銷 公有 的commit的活尊。出于完全不同的目的,這兩個命令的執(zhí)行結(jié)果也不同:reset是完全地移除有改動的地方漏益,而revert則是維持原來的改動蛹锰,使用一個新的commit來達到撤銷的目的。

不要重置公有的歷史

<commit后面的任一快照被推送到公有倉庫時绰疤,你就不應該使用git reset <commit>铜犬,推送一個commit到公有倉庫后,就必須假設(shè)其他開發(fā)者是依賴于它的。刪除一個其他團隊成員在此基礎(chǔ)上持續(xù)開發(fā)的commit會引發(fā)團隊協(xié)作上的嚴重問題翎苫,當他們嘗試與你的代碼倉庫同步時权埠,就像一大塊項目歷史突然地消失了。

下面的例子就是當你嘗試reset一個公有的commit時會發(fā)生的煎谍。

一旦你在reset后新增一個commit攘蔽,Git會認為你本地的歷史與origin/master背道而馳了,當合并commit時呐粘,需要先同步你的代碼倉庫满俗,這就有可能使你的團隊感到迷惑和無助。

所以重點就是作岖,你打算用git reset <commit>來撤銷你那糟糕的試驗時唆垃,請確保它只作用于本地(還沒被推送至遠程服務器)的改動。如果你需要修復一個公有的commit痘儡,請使用git revert辕万,因為它正是為了這個目的而被設(shè)計的。

示例

Unstage 一個文件

假設(shè)有兩個文件hello.pymain.py沉删,已經(jīng)被添加到Git倉庫中渐尿,修改這兩文件并進行提交。

# Edit both hello.py and main.py

# Stage everything in the current directory
git add .

# Realize that the changes in hello.py and main.py
# should be committed in different snapshots

# Unstage main.py
git reset main.py

# Commit only hello.py
git commit -m "Make some changes to hello.py"

# Commit main.py in a separate snapshot
git add main.py
git commit -m "Edit main.py"

正如你所看到的矾瑰,你可以使用git resetunstage掉一些不小心加入暫存區(qū)但又與此次commit無關(guān)的文件砖茸,讓你的commits保持高度的專一。

移除本地的commits

接下來的例子展示了一個更高級的使用情況殴穴,它示范了你在一個新的試驗上工作了一段時間并在提交了一些快照后凉夯,決定徹底拋棄它這整個過程究竟發(fā)生了什么。

# Create a new file called `foo.py` and add some code to it

# Commit it to the project history
git add foo.py
git commit -m "Start developing a crazy feature"

# Edit `foo.py` again and change some other tracked files, too

# Commit another snapshot
git commit -a -m "Continue my crazy feature"

# Decide to scrap the feature and remove the associated commits
git reset --hard HEAD~2

git reset HEAD~2這句命令讓當前分支回滾了兩個提交采幌,實際上劲够,從項目歷史上刪除了我們剛剛創(chuàng)建的兩個快照。請記住休傍,這種類型的reset應該只用在未推送到遠程服務器的commits上再沧,絕不要在那些已經(jīng)被推送至公有倉庫的commits上執(zhí)行上面的操作。

git clean

git clean從工作區(qū)移除未追蹤的文件尊残。這的確是一個更方便的命令,因為它使用git status瑣細地查看哪些文件未追蹤淤堵,然后手動刪除它們寝衫。就像普通的rm命令一樣,git clean是不可恢復的拐邪,所以在運行它之前請確保你是真的想要刪除那些未追蹤的文件慰毅。

git clean命令經(jīng)常和git reset --hard一起被執(zhí)行,reset僅僅影響已追蹤的文件扎阶,因此需要git clean來單獨清理未追蹤的文件汹胃,這兩個命令相結(jié)合可以讓你的工作區(qū)回滾到一個特定的commit的確切狀態(tài)婶芭。

用法

git clean -n

執(zhí)行git clean的“演習”。這向您展示哪個文件將會被刪除着饥,但不會真正地執(zhí)行犀农。

git clean -f

從當前工作區(qū)中移除未追蹤的文件。-f(force)標記是必需的宰掉,除非clean.requireForce選項被設(shè)為false(默認是true)呵哨。這不會移除.gitignore指定的未追蹤的文件。

git clean -f <path>

移除未追蹤的文件轨奄,但僅限于操作指定的路徑孟害。

git clean -df

從當前工作區(qū)中移除未追蹤的文件和目錄。

git clean -xf

從當前工作區(qū)中移除未追蹤的文件挪拟,包括Git忽略的文件挨务。

討論

當你在本地倉庫中做了一些令人尷尬的開發(fā)想要銷毀證據(jù)時,git reset --hardgit clean -f會是你最好的朋友玉组,運行著兩個命令將會使你的工作區(qū)回滾至最近的一次commit谎柄,還你一個干凈的工作區(qū)。

git cleanbuild后清理工作區(qū)是很有用的球切,比如谷誓,你可以很容易地移除.o.exe等C編譯器生成的二進制文件,這是偶爾打包項目發(fā)布前的必要步驟吨凑,-x選項達到這個目的特別方便捍歪。

記住,一起使用git resetgit clean是唯一一個具有潛在威脅的永久地刪除提交的命令鸵钝,所以請謹慎使用糙臼。事實上,在使用git clean時恩商,-f是必須的变逃,Git`的維護者甚至將它作為最基本的操作,而很多人會忘記的這一重要步驟怠堪,但這也預防了愚蠢行為而一不小心突然地刪除所有辛辛苦苦寫的代碼揽乱。

示例

下面的例子撤銷了工作區(qū)所有的改動,包括新增的文件粟矿。假設(shè)你已經(jīng)提交了一些快照凰棉,然后正在嘗試一些些新的開發(fā),但不知道自己做了什么導致了一些錯誤陌粹,想要撤銷然后重新開始撒犀。

# Edit some existing files
# Add some new files
# Realize you have no idea what you're doing

# Undo changes in tracked files
git reset --hard

# Remove untracked files
git clean -df

運行完reset/clean一系列命令后,工作區(qū)和暫存區(qū)回滾到最近的commit,git status將會告訴你這是一個干凈的工作區(qū)或舞,你現(xiàn)在可以準備重新開始了荆姆。

注意,那些新增的文件沒有被加入暫存區(qū)映凳,它們不會被git reset --hard影響胆筒,必須使用git clean刪除它們。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末魏宽,一起剝皮案震驚了整個濱河市腐泻,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌队询,老刑警劉巖派桩,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚌斩,居然都是意外死亡铆惑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門送膳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來员魏,“玉大人,你說我怎么就攤上這事叠聋∷貉郑” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵碌补,是天一觀的道長虏束。 經(jīng)常有香客問我,道長厦章,這世上最難降的妖魔是什么镇匀? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮袜啃,結(jié)果婚禮上汗侵,老公的妹妹穿的比我還像新娘。我一直安慰自己群发,他們只是感情好晰韵,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著熟妓,像睡著了一般雪猪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滑蚯,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音,去河邊找鬼告材。 笑死坤次,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的斥赋。 我是一名探鬼主播缰猴,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼疤剑!你這毒婦竟也來了滑绒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤隘膘,失蹤者是張志新(化名)和其女友劉穎疑故,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弯菊,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡纵势,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了管钳。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钦铁。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖才漆,靈堂內(nèi)的尸體忽然破棺而出牛曹,到底是詐尸還是另有隱情,我是刑警寧澤醇滥,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布黎比,位于F島的核電站,受9級特大地震影響腺办,放射性物質(zhì)發(fā)生泄漏焰手。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一怀喉、第九天 我趴在偏房一處隱蔽的房頂上張望书妻。 院中可真熱鬧,春花似錦躬拢、人聲如沸躲履。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽工猜。三九已至,卻和暖如春菱蔬,著一層夾襖步出監(jiān)牢的瞬間篷帅,已是汗流浹背史侣。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留魏身,地道東北人惊橱。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像箭昵,于是被迫代替她去往敵國和親税朴。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容

  • git常用命令 GIT常用命令備忘:http://stormzhang.com/git/2014/01/27/gi...
    新篇章閱讀 8,471評論 1 26
  • 一 Git配置和倉庫初始化 下面會介紹Git的使用,每個小節(jié)里會講解各個功能在命令行中的實現(xiàn)方式颤殴,并在每小節(jié)的最后...
    Happioo閱讀 3,351評論 0 5
  • 你看觅廓,我已經(jīng)不是那個只靠嘴上說說的關(guān)心就滿足就感動的不要不要的小姑娘了。诅病。哪亿。 在背叛欺騙和等待中成長,我也會變得開...
    楚沐沐閱讀 223評論 0 1
  • 滴答滴答……今天的雨一直在持續(xù)贤笆,忽大忽小蝇棉,忽密忽疏。 北方的天氣有些任性芥永,冷空氣說來就來篡殷,溫度瞬間下降,仿佛一腳踏...
    銀子姐閱讀 291評論 1 2
  • 概述 上一篇主要講解了YYMemoryCache的文件結(jié)構(gòu)埋涧,分析了YYMemoryCache類的相關(guān)方法板辽,本章主要...
    egoCogito_panf閱讀 3,785評論 3 11