Git作為一個版本管理系統(tǒng)(VCS)最基本的功能就是保證所有操作的追蹤和回退攻锰。但是晾嘶,很多初學者會被各種各樣的命令,各種參數(shù)給弄蒙了娶吞,加上命令行提示基本都是英文垒迂,所以就跟瞎子一樣,生怕一不小心把這弄丟了妒蛇,一不小心把庫弄壞了机断,pull不了,也push不了绣夺,不知道怎么辦了吏奸。實際上git中任何操作,包括破壞性陶耍、刪除性的操作都可以可以回退奋蔚。本文中蟲蟲就帶大家了解Git中各種撤消回退大法,等你熟悉了這些方法烈钞,可以讓你放開手一心只搞代碼泊碑,Git只是幫你插上翅膀,讓你在代碼庫中自由的翱翔毯欣。
分支策略(工作流)
Git是一個去中心化的分布式版本控制系統(tǒng)馒过,這意味著代碼倉的版本控制不是統(tǒng)一的,各個客戶端都是一個完整的倉庫酗钞,然后互相通過Git服務器基于commits進行變更的交換腹忽。為了避免這種多來源的變更導致混亂来累,開發(fā)人員必須遵循各自開發(fā)組工作流程(如果沒有的話,要制定一個)留凭,該流程取決于團隊內(nèi)部工作流程:如何撤消佃扼、如何更改某些變化?如何提交PR/MR蔼夜?如何審核和合并分支兼耀?。
有三個典型的協(xié)作流程:Git flow求冷,Github flow和 GitLab flow瘤运。利用可以在開發(fā)相同功能和無縫協(xié)作的情況下解決開發(fā)人員沖突和開發(fā)平衡。
Git flow
由Vincent Driessen提出匠题,主要是建立兩個長期分支Master和Devlop開發(fā)分支拯坟,同時又臨時性的功能分支、補丁分支和預發(fā)布分支韭山。版本衍變流程圖如下:
GitHub flow
Github flow 是Github提出的相比較Git flow簡配版郁季,是 Github網(wǎng)站使用的工作流程。
該流程簡練钱磅,主要為配合CI/CD(持續(xù)集成和發(fā)布)梦裂,實現(xiàn)快速迭代。
Gitlab flow
Gitlab flow結(jié)合了上面兩種流的優(yōu)點盖淡,是gitlab產(chǎn)品使用的方法年柠。Gitlab flow以master分支為基礎(chǔ),只有master接受的commit才可以合并得到其他分支
在該流程下褪迟,版本發(fā)布也是基于master分支來推進冗恨。
撤消本地的變化
在你沒有執(zhí)行push變化推送到遠程庫(Git服務器)之前,所有變化只會影響你自己味赃,不會影響其他人掀抹。所以你可以隨意處置這些變化,就算全部丟了心俗,也可以通過clone重新復制一份渴丸,你所丟失的只不過是本地一些變化,包括未push的歷史記錄(已經(jīng)commit未push)另凌,本地索引區(qū)staging(已經(jīng)add未commit)和工作區(qū)的變化(未add),以及我們在之前的文章《讓你提高工作效率的Git的技巧 》中提到的四種文件狀態(tài):已暫存 (Staged)戒幔,已修改(Modified),未修改(Unmodified)以及未跟蹤(Untracked)吠谢。
對于不同的階段的變化,撤銷的方法也各不相同:未分級的本地更改(在您提交之前)
未commit的工作區(qū)文件變化
對已經(jīng)更改但還沒有添加到暫存區(qū)的變化時诗茎,Git本身提供一種解決方案來撤銷對某個文件的更改工坊。假如我們我們編輯了一個文件vim <file>献汗,沒有添加的暫存區(qū),文件應為unstaged文件(如果文件已創(chuàng)建王污,未跟蹤Untracked)罢吃。可以通過git statu命令查看文件狀態(tài):
在該狀態(tài)下昭齐,你有三種方法選擇撤銷這個更改:
放棄所有本地更改尿招,但保存它們以便以后重復使用git stash 。
撤銷本地變化(永久丟棄)git checkout -- <file> 阱驾。
永久丟棄對所有文件的所有本地更改 git reset --hard 就谜。
快速保存本地更改(git stash)
你在開發(fā)正當中,接到一個緊急bug修復任務里覆。由于你功能沒完全寫丧荐,你沒法馬上就commit,但你需要換到另一個分支喧枷,以完成緊急的修復虹统。這時候你就需要祭出神器git stash來保存你時下的工作,切換到需要修復的分支隧甚,修復车荔,commit,push呻逆。再用git stash pop回到你的工作狀態(tài)夸赫,繼續(xù)寫你的代碼。
git stash 還支持如下其他操作:
git stash save xxx :可以在保存時添加備注信息(類似于commit信息)咖城,這這樣對于多個stash管理和識別將更加方便茬腿,設定備注信息后,可以在list中顯示這個信息宜雀。
git stash list :列出所有以前暫儲過的工作狀態(tài)(支持多次的git stash暫存)切平。
git stash pop :用戶回到上一個存儲的工作狀態(tài)并將其從stash存儲列表中刪除(類似于數(shù)組的pop的操作)。
git stash apply xxx : 回到指定的一個stach存儲列表的工作狀態(tài)辐董,但將不會從stashed列表中刪除悴品。
commit之前分階段的本地更改
假設已經(jīng)添加了一些文件到暫存區(qū),但是你不想在該該次commit中包含他們简烘。但是又不想撤銷對這些文件的修改苔严,只是想將他們從暫存區(qū)移除。當然如果你想撤銷這些變化的話孤澎,和上一部分提到那樣可使用git reset --hard或者git stash届氢。讓我們回到我們示例倉庫:
首先,用git status 看看目前的狀態(tài):
我要要從暫存去移除一些文件覆旭,比如我們移除four.txt:
git reset HEAD four.txt
結(jié)果退子,被移除的文件處于未跟蹤狀態(tài):
如果不添加文件岖妄,則會從暫存去移除所有的文件,但是文件修改都保存,都位于工作區(qū)寂祥。
git rest
可以使用git stash 保存所有工作區(qū)文件變化荐虐,以及添加到暫存區(qū)的文件狀態(tài),這是工作區(qū)會回到上一次commit狀態(tài)的文件狀態(tài)丸凭。但是可以之后隨時返回到該工作狀態(tài)福扬。
如果要丟棄所有文件變化,和暫存區(qū)的狀態(tài)贮乳,則使用git reset --hard忧换。結(jié)果和上一個方法類似趴捅。但是沒有stash的暫存項壕鹉,也無法在會到該工作狀態(tài)瘦馍。
commit的本地變更
commit提交后胸哥,版本控制系統(tǒng)就會正式記錄該變化板甘,以git對象的形式保存修改缺狠,進入歷史存檔丢间,可以push到遠程倉抱虐,并和其他人做版本交換颈将。在未push之前梢夯,該的修改是仍未公開(無法與其他開發(fā)者交換)。所以晴圾,做撤消操作不會影響別人颂砸,我們可以隨意操作。一旦代碼push到遠程倉死姚,我們的做撤銷操作就要格外注意了人乓,盡量不要影響,不然整改團隊都不能pull都毒,push色罚,別人會拿刀砍你,釀出血案的账劲。
不修改歷史(revert)
在實際的使用中戳护,有可能有些預先的commit可能最終不是預期要push到遠程倉的,或者是一個有bug的commit瀑焦。這是我們可以可以簡單地用git revert commit-id撤銷這個commit腌且。此命令會反轉(zhuǎn)該commit中的增加的git對象,并刪除commit榛瓮,它不會修改git歷史記錄铺董。
假設有以下順序提交的A,B榆芦,C柄粹,D,E提交:A-B-C-D-E匆绣,現(xiàn)在我們想要撤銷有問題代碼的到B的commit驻右。至于如何判別B是否bug問題,蟲蟲之前的文章中有提到過就是使用git bisect崎淳,這兒就不在詳述堪夭,可以關(guān)注蟲蟲,瀏覽以前的文章拣凹。git bisect A..??E
bisect會我們通過二分法reset到A到E之間的一個commit森爽,我們做測試,然后判斷代碼是否正常嚣镜,根據(jù)這個狀態(tài)來迭代知道找到問題的commit B爬迟。
撤銷B的commit引入的狀態(tài),我們使用:
git revert B-commit-id
如果僅僅是撤銷部分文件或者目錄菊匿,但是保存在暫存區(qū)付呕,則用:
git checkout B-commit-id <file>
如果要撤銷B的commit狀態(tài),并且暫存區(qū)移除則使用rest
git reset B-commit-i <file>
還有一個方法跌捆,我們也可以選用徽职,那就是創(chuàng)建一個新分支,從有問題的地方開一個bug分支佩厚。比如A-B-C-D歷史記錄姆钉,現(xiàn)在發(fā)現(xiàn)C和D有問題。這是除了我們重置到B commit抄瓦,并強推 F(這會導致與其他開發(fā)人員沖突)潮瓶。這是新的歷史揭露為A-B-F,大家都必須強制reset -f 才跟上你的push闺鲸。另一個更可取得方法是筋讨,不改變當前的歷史,從B開始新創(chuàng)建一個新的分支摸恍,并在該分支commit F悉罕。
git checkout B-commit-i
git checkout -b new-path-of-feature
git commit -a
修改commit歷史(rebase)
還有一個常用的修改的命令就死git rebase。他也提供-i選項實現(xiàn)交互式的操作立镶。在-i模式下壁袄,可以打開一個編輯器你在該編輯器中使用一些指令來操作commit歷史:
reword?commit信息還,編輯最近一次commit的消息媚媒。(可以用命令行g(shù)it commit --amend)
edit?編輯提交內(nèi)容(提交引入的修改)和消息
squash?將多個提交合并為一個提交嗜逻,并且提供自定義或整合的commit消息
drop?刪除commit
我們來舉個實例。假想現(xiàn)在我們的倉庫歷史為all-hello-new缭召,要刪除hello栈顷。
rebase當前提交范圍:
git rebase -i fbaf080184ed
命令打開你默認的編輯器逆日,你編輯其中只需寫下指令drop New,但你保留所有其他pick提交的默認內(nèi)容萄凤。
保存并退出編輯會自動執(zhí)行rebase室抽。結(jié)果:
如果你想修改commit hello中引入的東西,類似的方法:
git rebase -i fbaf08018
命令打開編輯器靡努,您可以在提交前編寫edit new坪圾,但保留所有其他pick提交的默認內(nèi)容。
保存并退出編輯器執(zhí)行惑朦,進行編輯和提交更改:
git commit -a
反悔你的撤消
有時你撤銷了一些修改兽泄,但是又發(fā)現(xiàn)這些修改還是有用的,又想反悔漾月。通過命令git reflog我們可以追回所有已經(jīng)分離的(git log不顯示)commit-id病梢。
要查看存儲庫歷史記錄并跟蹤舊提交,可以使用以下命令:
git reflog show
輸出顯示存儲庫的歷史記錄栅屏。第一列為commit-id飘千,其他列HEAD旁邊的數(shù)字表示之前commit了多少次,可以當做下表來做引用該次的commit栈雳,在git命令(commit护奈,rebase,merge哥纫,...)中當做參數(shù)代替commit-id使用霉旗,最后一列該記錄的描述。
撤銷遠程倉中的變更
不修改commit歷史撤消遠程變更
這操作和修改本地提交的本地不修改歷史撤銷更改大致相同蛀骇。它是撤消任何遠程庫上或者公共分支的commit的首選方法厌秒。對這種需求,最好的的方法是使用分支擅憔,分支使能夠在新開發(fā)中引入現(xiàn)有修改(通過合并)和還可以提供了明確的commit時間序列和開發(fā)結(jié)構(gòu)鸵闪。
要撤銷某些commit-id中引入的更改,我們可以通過revert簡單地創(chuàng)建的commit中恢復commit-id(置換添加和刪除)暑诸,比如上圖的需求刪除B蚌讼,我們可以通過revert
git revert B-commit-id
或創(chuàng)建一個新分支:
git checkout B-commit-id
git checkout -b new-path-of-feature
修改歷史記錄的撤消遠程更改
當你想隱藏遠程倉中有些敏感新的信息時候,則必須要用該方法(一般不要用个榕,不然結(jié)果見第三部的圖)篡石。比如倉庫中包含了token,密碼西采,SSH私鑰等凰萨。這樣做的會讓你失去了真正的commit 歷史進程。還要注意的是,即便是修改了歷史記錄胖眷,commit被分離(detach了)武通,依然可以通過commit-id訪問(git沒有執(zhí)行自動清理分離commit之前)。 還有就是別人如果還沒有同步該修改珊搀,他客戶端里的信息也是完全的厅须。
修改歷史記錄
確定好要修改的內(nèi)容之后(歷史記錄的范圍或范圍舊提交),使用git rebase -i commit-id食棕。然后,此命令將顯示所有提交當前版本選擇commit-id并允許修改错沽,壓縮簿晓,刪除提交。
git rebase -i commit1-id..commit3-id
然后根據(jù)我們第三部分提到指令做修改千埃。
修改后憔儿,通過git push -f強制推送到遠程庫生效(慎用慎用慎用!)放可。
篩選要刪除的敏感信息文件(git filter-branch)
Git還允許從過去的提交中刪除敏感信息谒臼。我們可以使用行g(shù)it filter-branch,它允許我們對rebase歷史記錄做過濾耀里。這個命令也是通過rebase修改歷史記錄蜈缤,比如要刪除某些歷史記錄歷史文件一共使用:
git filter-branch --tree-filter 'rm filename' HEAD
注意git filter-branch命令在大型庫上可會很慢。還有一些相對較快的冯挎,讓我們篩選特定文件的第三方工具底哥,比如BFG Repo-cleaner。