Git私房實用指令

Eric Hu
Akatsuki Taiwan Technology
11 min readFeb 7, 2023

使用Git多年,Git也漸漸的從一個新的版本控制工具變成現在的主流了,大學時裝了Source Tree之後對整個git版控的概念都還很模糊,後來第一份工作時遇到幾位使用git/svn多年的前輩,不吝嗇的在我面前展示了直接在terminal中下各種command,更有效的使用git的方法,於是這些command就被我牢牢地記住,一直到現在都還是常常用到。這篇blog就是介紹一下這些command,跟 可能/適合用到的情況。

加速Trace Code的指令

不論是開發功能還是解Bug,trace code都是很重要的一環。這裡列幾個我trace code時常用的指令。

git log

checkout到一條branch之後,第一件事情通常就是看這條branch多了哪些commit,幾月幾號的commit...等等。這時候會使用大家都很熟悉的git log

git log會顯示當前Branch的所有git commit以及commit message
git log  --stat

而這時如果想知道哪些檔案有被更動,可以使用git log — stat,在後方加上--stat,就會列出每個commit中被更改的檔案。

git log — stat會列出每條commit中變更的檔案以及檔案變更的行數
git log --grep="fix"

git log會列出所有commit,但有時我只想要列出滿足特定條件的commit,在後方加上--grep="keyword"後,會只列出commit message包含grep中字串的commit。

列出所有message中包含"document"的commit
git log  --stat --name-only

如果覺得git log --stat列出的訊息太詳細,只想知道被更改的檔名的話,可以補上--name-only。此時的log就只會印出更動的檔名,

另外--指令 都是可以組合的,所以可以組出像是git log --stat --grep=”fix” --name-only 這樣的command。

git blame

這個指令就是抓戰犯用的

當程式中突然出現一行製造出bug的code,想知道這行code的作者還有對應的commit時,git blame filepath 會列出該檔案當前每一行code的更改的作者還有每一行code最後被更改時對應的commit。

//檢視差異
git diff branch A branchB
git diff 97b5fb2d 77b5e826

git diff 會列出指定branch或者commit之間差異,搭配上面的--state或者--name-only就可以過濾出commit之間的差異還有變更的檔案名稱。

針對本地差異的各種操作指令

使用git就會時常開出自己的一條local branch,實作完功能在合回專案的主branch,在自己的local branch操作時,除了merge其他branch外,有不同的方式可以將其他地方的變更或commit合進當前branch,以下是一些我常用(但發現很多人沒再用)的指令。

//將指定commit挑進當前branch
git cherry-pick 86b78219

git cherry-pick可以把指定的commit挑進當前branch,當別的branch有我需要的commit,但又不想將其他branch整個merge進當前local branch時,是非常實用的指令。(理想上,我們都希望自己的local branch乾乾淨淨的,發PR的時候只會看到自己新增的那些change,不會有太多的merge commit干擾到code review的人)

git cherry-pick 86b78219 a00877b9 6959ad56 77de9537 

git cherry-pick也支援一次pick多個commit,如果需要一次將複數個commit的變更依序挑進當前branch也可以使用。

//產patch file, 打patch file
git diff commitA commitB > my_patch_file_name
git apply my_patch_file_name

有時候我們不想要把整個commit挑進當前branch(當前branch會多一個commit),而是只是想要那段期間code的變更,我們可以在diff後用>來將變更輸出為一個patch file,這個patch file只包含兩個commit之間的差異,再用apply指令就可以將這段差異匯進當前的專案。

//重寫當前commit message
git commit --amend

一套良好的commit message規則可以讓專案的所有人在合作的時候更快速的了解到每個commit的內容,通常我會在push 自己的local commit之前檢查一下message是否有錯字或者內容是否足夠清晰,如果需要修改就可以使用 — amend。

我們都知道只要git add file_path,就可以把檔案stage起來,之後在下git commit,就可以從stage的變更中生成出一個commit,但是如果需要重寫commit message的話呢? 這時候git commit --amend就非常實用,

git commit — amend可以讓你修改當前commit的message,它背後的原理其實是重新作一次git commit,所以amend修改過後的commit,其實是一個全新,完全不同的commit(commit hash會變)。

要注意的是,amend、rebase等指令都只建議在自己的local branch上操作,因為是會改寫到git歷史的,如果改寫到已經被push上remote的部分,會造成branch的分歧,這時候只能force push,force push對專案是有風險的,一般來說主branch都會封鎖force psuh操作,如果對git觀念還是有點模糊,建議建立一個練習用的git repo,多建立一些不同的branch來練習。

// -i 是 --interactive的意思

git rebase -i 825c8de

git rebase系列的command,相對來說比較少人用,rebase就是改寫branch的歷史,如果沒處理好造成與主branch的衝突,merge時就會發生慘案。但是,但是,但是,rebase在整理自己的branch時,非常的實用,

使用git rebase -i _commit_hash進入interactive mode後,可以改寫該commit hash後所有commit的歷史,rebase interactive內另外也提供了多種commad可以對個別commit做不同的操作。活用這個指令可以一次整理、合併、刪除多個commit。

以上圖為例,這一條local branch最新的4個commit,前3個是update readme.md相關,第4個則是修改code的部分。

假設我不希望我發PR的時候,對方看到重複的Update Readme.md commit message,我想乾脆把這三次commit的變更打包成單一一次commit。
我就可以下git rebase -i 7d1d346 這段指令,接著會看到下圖

對照前一章截圖可以看出,現在我能夠編輯7d1d346 後的3個commit,預設都是pick(p)。我們可以將pick更改為r、e、s、f,我最常用的是squash(s),squash的commit會去跟他的前一個commit融合。可以達成我要打包多個commit的目的。

將想合併的commit前面的pick改成s

改好之後,如果是在terminal就是:wq儲存,如果是vscode就是儲存該page之後關掉tab,會出現這個畫面。

這頁是填合併後,新的commit message的內容,預設會有所有原本的commit的message,可以直接刪掉重寫。

重寫合併後的commit message

最後一樣:wq結束這波操作,會看到rebase成功的訊息(沒有成功就是有衝突,要修掉衝突之後在下git rebase --continue),完成之後用git log檢查,會發現原本最新的3個重複的 Update Readme.md commit 已經變成單一一個commit(d09c2ea),commit message是Update lots of README.md contents 。這單一個commit就包含了apply c3d + 1e8 + a6e的三個變更的結果。

將過度分散的commit,rebase成較少、每一個commit都包含更完整的變更的習慣,是能夠提升協作的效率的。別人能更容易理解我們每條commit的內容,當遇到問題需要進行revert或者cherry-pick的操作時也會更加容易。

拯救消失的Commit紀錄

git reflog

有時候,我們checkout出一條branch在上面開發了好幾天,功能都寫好了要push到remote前卻不小心把branch刪了,或者reset到開始動工之前的某個commit上,沒push上去的變更看似都消失、前功盡棄了,這時候只要記得 - 只要你commit過,那本地就有那段紀錄!(存在.git中),要撈回那些commit 也很簡單,下git reflog ,所有我們在本地做出過的commit,checkout branch的操作,用reflog都能找到。

這時候要拯救那些消失的commit很簡單,開一條新branch把reflog裡面對應的commit依序cherry-pick挑回來就行。

所以把自己的commit整理乾淨很重要,尤其是救火的時候,commit歷史越乾淨,出事的時候越容易復原

Repo很大但是硬碟空間不夠、下載太久時

git這種分散式的機制是每一個本地中,會存有所有commit的差異,當專案的commit數量非常多,或者是commit內部包含git無法壓縮得很好的格式(圖片)時,整個repo佔據的容量會非常大,有時候一個2GB的專案,包含所有branch、commit的紀錄,專案中隱藏的.git資料夾可能會超過數十GB。造成硬碟空間容易不足或者repo難以clone的問題。這時候我們可以利用shallow clone來限制需要下載的差異的層數。

git clone –depth 1 <repo_url>

在很多情況下我們是不需要知道太久以前的記錄的,甚至我們只需要拿到當下設定的head的版本就好(比如說某個遠端build版本用的CI),我們可以使用--depth N來限制本地只包含前N筆的差異,這樣本地的repo資料夾容量比起full clone就會小很多了。

如果使用git fetch — all就會把所有歷史中的差異再次抓下來,要避免這種狀況就是連git fetch時也要附上 --depth N

自己常用的command暫時只想到這些,有想到別的改天再補上。

--

--