我先開一會兒吐槽大會,git 這東西我用了兩年,根本尼瑪用不明白。
我搞不明白的一個重要原因就是,命令的功能太雜,有時候一個需求可以用好幾種命令解決,而且有的命令還 tm 有别名。這導緻什麼問題呢,我在網上找到的答案五花八門,竟然都能達成目的,難以找到規律,毫無套路可言。對于我這種不喜歡動腦子,隻喜歡玩套路的人來說,簡直不能接受。
以前我用 Git,就知道add .,然後commit -m,最後push origin master一套帶走,或者就是把 Git 作為下載器,去clone别人的項目。但是在工作中呢,和别人一起開發代碼,就需要處理一些複雜情況,比如解決沖突,比如手殘恢複,等等等實用場景,這些我在後文都會列舉。
對于工具的學習,我認為應該多做減法,隻撿最有用的學,那些奇技淫巧不學也罷,應該把時間投入更有價值的事情中。
所以本文不是一個大而全 Git 命令的使用手冊,而是根據實際工作中最常見問題,提供小而美的解決方案,僅僅涉及四個命令:add,commit,reset,checkout。
PS:舊文 一起刷題學習 Git/SQL/正則表達式 介紹了一個可視化學習 Git 的網站,非常優秀,值得一刷。
一、預備知識首先,在進入 Git 的各種神仙操作之前,一定要明白 git 的三個「分區」是什麼,否則的話你一定沒辦法真正理解 Git 的原理。
本地 Git 的三個分區分别是:working directory,stage/index area,commit history。
https://rogerdudler.GitHub.io/git-guide
working directory是「工作目錄」,也就是我們肉眼能夠看到的文件,後文我們稱其為work dir區。
當我們在work dir中執行git add相關命令後,就會把work dir中的修改添加到「暫存區」stage area(或者叫index area)中去,後文我們稱暫存區為stage區。
當stage中存在修改時,我們使用git commit相關命令之後,就會把stage中的修改保存到「提交曆史」commit history中,也就是HEAD指針指向的位置。後文我們稱「提交曆史」為history區。
關于commit history我們多說幾句,任何修改隻要進入commit history,基本可以認為永遠不會丢失了。每個commit都有一個唯一的 Hash 值,我們經常說的HEAD或者master分支,都可以理解為一個指向某個commit的指針。
work dir和stage區域的狀态,可以通過命令git status來查看,history區域的提交曆史可以通過git log命令來查看。
好的,如果上面的内容你都能夠理解,那麼本文就完全圍繞這三個概念展開,下面就是一個「狀态轉移圖」:
需求一,如何把work dir中的修改加入stage。
這個是最簡單,使用 git add 相關的命令就行了。順便一提,add有個别名叫做stage,也就是說你可能見到git stage相關的命令,這個命令和git add命令是完全一樣的。
風險等級:無風險。
理由:不會改變任或撤銷任何已作出的修改,而且還會将work dir中未追蹤的修改(Untracked file)添加到暫存區stage中進行追蹤。
需求二,如何把stage中的修改還原到work dir中。
這個需求很常見,也很重要,比如我先将當前work dir中的修改添加到stage中,然後又對work dir中的文件進行了修改,但是又後悔了,如何把work dir中的全部或部分文件還原成stage中的樣子呢?
來個實際場景,我先新建兩個文件,然後把他們都加到stage:
$toucha.txtb.txt
$gitadd.
$gitstatus
Onbranchmaster
Changestobecommitted:
newfile:a.txt
newfile:b.txt
然後我又修改了a.txt文件:
$echohelloworld>>a.txt
$gitstatus
Onbranchmaster
Changestobecommitted:
newfile:a.txt
newfile:b.txt
Changesnotstagedforcommit:
modified:a.txt
現在,我後悔了,我認為不應該修改a.txt,我想把它還原成stage中的空文件,怎麼辦?
答案是,使用 checkout 命令:
$gitcheckouta.txt
Updated1pathfromtheindex
$gitstatus
Onbranchmaster
Changestobecommitted:
newfile:a.txt
newfile:b.txt
看到了麼,輸出顯示從index區(也就是stage區)更新了一個文件,也就是把work dir中a.txt文件還原成了stage中的狀态(一個空文件)。
當然,如果work dir中被修改的文件很多,可以使用通配符全部恢複成stage:
$gitcheckout.
有一點需要指出的是,checkout命令隻會把被「修改」的文件恢複成stage的狀态,如果work dir中新增了新文件,你使用git checkout .是不會删除新文件的。
風險等級:中風險。
理由:在work dir做出的「修改」會被stage覆蓋,無法恢複。所以使用該命令你應該确定work dir中的修改可以抛棄。
需求三,将stage區的文件添加到history區。
很簡單,就是 git commit 相關的命令,一般我們就是這樣用的:
$gitcommit-m'一些描述'
再簡單提一些常見場景, 比如說commit完之後,突然發現一些錯别字需要修改,又不想為改幾個錯别字而新開一個commit到history區,那麼就可以使用下面這個命令:
$gitcommit--amend
這樣就是把錯别字的修改和之前的那個commit中的修改合并,作為一個commit提交到history區。
風險等級:無風險。
理由:不會改變任或撤銷任何已作出的修改,而且還會将stage區的修改加入history區并分配一個 Hash 值。隻要不亂動本地的.git文件夾,進入history的修改就永遠不會丢失。
需求四,将history區的文件還原到stage區。
這個需求很常見,比如說我用了一個git add .一股腦把所有修改加入stage,但是突然想起來文件a.txt中的代碼我還沒寫完,不應該把它commit到history區,所以我得把它從stage中撤銷,等後面我寫完了再提交。
$echoaaa>>a.txt;echobbb>>b.txt;
$gitadd.
$gitstatus
Onbranchmaster
Changestobecommitted:
modified:a.txt
modified:b.txt
如何把a.txt從stage區還原出來呢?可以使用 git reset 命令:
$gitreseta.txt
$gitstatus
Onbranchmaster
Changestobecommitted:
modified:b.txt
Changesnotstagedforcommit:
modified:a.txt
你看,這樣就可以把a.txt文件從stage區移出,這時候進行git commit相關的操作就不會把這個文件一起提交到history區了。
上面的這個命令是一個簡寫,實際上reset命令的完整寫法如下:
$gitreset--mixedHEADa.txt
其中,mixed是一個模式(mode)參數,如果reset省略這個選項的話默認是mixed模式;HEAD指定了一個曆史提交的 hash 值;a.txt指定了一個或者多個文件。
該命令的自然語言描述是:不改變work dir中的任何數據,将stage區域中的a.txt文件還原成HEAD指向的commit history中的樣子。就相當于把對a.txt的修改從stage區撤銷,但依然保存在work dir中,變為unstage的狀态。
風險等級:低風險。
理由:不會改變work dir中的數據,會改變stage區的數據,所以應确保stage中被改動數據是可以抛棄的。
需求五,将work dir的修改提交到history區。
這個需求很簡單啦,先git add然後git commit就行了,或者一個快捷方法是使用命令git commit -a。
風險等級:無風險。
理由:顯而易見。
需求六,将history區的曆史提交還原到work dir中。
這個場景,我說一個極端一點的例子:比如我從 GitHub 上clone了一個項目,然後亂改了一通代碼,結果發現我寫的代碼根本跑不通,于是後悔了,幹脆不改了,我想恢複成最初的模樣,怎麼辦?
依然是使用checkout命令,但是和之前的使用方式有一些不同:
$gitcheckoutHEAD.
Updated12pathsfromd480c4f
這樣,work dir和stage中所有的「修改」都會被撤銷,恢複成HEAD指向的那個history commit。
注意,類似之前通過stage恢複work dir的checkout命令,這裡撤銷的也隻是修改,新增的文件不會被撤銷。
當然,隻要找到任意一個commit的 HASH 值,checkout命令可就以将文件恢複成任一個history commit中的樣子:
$gitcheckout2bdf04asome_test.go
Updated1pathfrom2bdf04a
#前文的用法顯示updatefromindex
比如,我改了某個測試文件,結果發現測試跑不過了,所以就把該文件恢複到了它能跑過的那個曆史版本……
風險等級:高風險。
理由:這個操作會将指定文件在work dir的數據恢複成指定commit的樣子,且會删除該文件在stage中的數據,都無法恢複,所以應該慎重使用。
三、其他技巧需求一,合并多個commit。
比如說我本地從17bd20c到HEAD有多個commit,但我希望把他們合并成一個commit推到遠程倉庫,這時候就可以使用reset命令:
$gitreset17bd20c
$gitadd.
$gitcommit-m'balabala'
回顧一下剛才說的reset命令的作用,相當于把 HEAD 移到了17bd20c這個commit,而且不會修改work dir中的數據,所以隻要add再commit,就相當于把中間的多個commit合并到一個了。
需求二,由于HEAD指針的回退,導緻有的commit在git log命令中無法看到,怎麼得到它們的 Hash 值呢?
再重複一遍,隻要你不亂動本地的.git文件夾,任何修改隻要提交到commit history中,都永遠不會丢失,看不到某些commit隻是因為它們不是我們當前HEAD位置的「曆史」提交,我們可以使用如下命令查看操作記錄:
$gitreflog
比如reset,checkout等等關鍵操作都會在這裡留下記錄,所有commit的 Hash 值都能在這裡找到,所以如果你發現有哪個commit突然找不到了,一定都可以在這裡找到。
需求三,怎麼解決沖突?
記住,Git 雖然高大上,但也不要迷戀,一定要懂得借助先進的工具。
比較流行的代碼編輯器或者 IDE 都會集成方便的可視化 Git 工具,至于解決沖突,可視化的表現方式不是比你在命令行裡git diff看半天要清晰明了得多?隻需要點點點就行了。
所以說,隻要明白本文講的這些基本操作,夠你用的了,平時能用圖形化工具就多用圖形化工具,畢竟工具都是為人服務的。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!