趁著連假啃完龍哥大作「為你自己學 Git」,拖了多年蹲完 Git 馬步,換來了卻一椿心事的輕鬆。

從今天起,我再也不必擔心因「開發老鳥不會 Git Squash 合併 Commit」的祕密敗露被江湖人恥笑了,哈哈哈哈。(謎:擔心半天結果自己說出來?)

(背後有段故事:之前在 Github 送過一個 PR,因 Commit 歷程太雜,原作者請我調一下,結果我瞎搞一陣還愈整愈亂,最後對方搖搖頭說:錯了,你該用 squash 的... 算了,我已經幫你整好了。那時就發誓要學會squash,然後幾年過去了 XD)

讀書筆記做成 Git 常用指令小抄,方便日後查詢:

  • 觀念:Git 是 Linus Torvalds 為管理 Linux 原始碼所寫的分散式版控系統,版控資料全部儲存在 .git 目錄下,不需伺服器也能運作。最著名的 Git 伺服器 Github 是全世界開源專案最主要集散地,TFS/Azure DevOps(前身是 VSTS) 也支援 Git 版控,或者自己架設一個 Git 伺服器也是選擇。
  • 觀念:Git 有三個區域,採兩段式更新。區域包含「工作目錄(Working Directory)」、「暫存區(Staging Area)」、「儲存庫(Repository)」,新增檔案初為 Untracked 狀態,加入暫存區後為 New,修改後為 Modified,刪除後則為 Deleted,使用 git add 陸續將檔案更新狀態(新增修改刪除都算)由工作目錄移入暫存區,告一段落後再 git commit 將其由暫存區提交到儲存庫。
  • 起手式 git init,在該工作目錄建立 .git 資料夾,開始用 Git 做版控
  • git add . 將該目錄及其子目錄下所有異動放入暫存區、git add --all 將整個 Git 版控範圍異動放入暫存區
  • git commit -m "Commit 說明" 將暫存區異動提交到儲存庫
  • git log --oneline --graph 以較清楚易讀的格式顯示簽入歷程
  • git log 搜尋參數:--author="Jefrrey"、--grep="wtf"、--S "Ruby"、--since="9am" --until="5pm" --after="2019-01" --before="2019-05"
  • git rm filename --cached 將檔案轉為 Untracked,在 SourceTreee 中這個動作叫 Stop Tracking
  • 檔案更名對 Git 而言是兩個動作(原檔 Deleted + 產生 Untracked 新檔),但 git add . 時若 Git 察覺檔案內容相同(SHA1 相同),狀態會變成 Renamed。要省時可用 git mv hello.html world.html
  • git commit --amend -m "修改後的Commit訊息" 修改最後一次 Commit 訊息(其實是撤掉前一次 Commit 重發一次,SHA1 不同),若不加 -m 則帶出文字編輯器讓你修改
  • 在最後 Commit 追加檔案:先 git add the_file 加入新檔,再 git commit --amend --no-edit 重發 Commit。(--no-edit 表沿用原 Commit 訊息不修改)
    提醒:不要對已 Push 出去的 Commit 做 --amend,以免造成別人困擾
  • 空目錄不會被提交,解法為在目錄下放一個 .keep 或 .gitkeep 讓 Git 感應到它
  • 不想版控的項目:編輯 .gitignore 檔案列舉排除不要納入版控的路徑(支援萬用字元),常見 .gitignore 設定可參考 https://github.com/github/gitignore
    修改 .gitignore 前就加入的項目可用 git rm --cached 清除,一口氣清理所有應忽略檔:git clean -fx
  • 檢視特定檔案的修改記錄 git log -p welcome.html (-p 為顯示修改差異)
  • git blame index.html 列出每一行程式是何時被誰修改的,-L n1,n2 參數顯示特定行數區間
  • 救回誤刪檔案或修改後反悔 git checkout program.cs
  • 取回兩個版本之前的檔案 git checkout HEAD~2 program.cs
  • 取消剛才的簽入 git reset HEAD^。HEAD 也可用最後一次 Commit SHA1(如 e12d8ef)替代,若 HEAD -> master,則寫 master^ 效果相同。^表前一次,^^表前兩次,更多次可寫 ~n。
    或者用 git log 查出某次 Commit 的 SHA1,可直接 git reset SHA1 退至該次 Commit 版本。
  • Reset 參數:拆掉 Commit 後,該次 Commit 內容何去何從?
    --mixed 預設值,只丟棄暫存區內容,工作目錄檔案不動(丟回工作目錄)
    --soft 暫存區跟工作目錄的檔案都不動,只移動 HEAD 位置(丟回暫存區)
    --hard 丟棄暫存區跟工作目錄的檔案
  • git reflog 可查看 Reset 歷程找到被拆掉的 Commit(註:reflog 只保留最近 30 天),用 git reset e12d8ef --hard 把 Commit 再救回來
  • HEAD 是一個指標指向目前的「分支」。.git/HEAD 內容為 refs/heads/master,而 .git/refs/heads/master 內容為一段 SHA1 40字元檔
    切換 Branch 時,git checkout branch1 背後是將 HEAD 檔內容為改為 refs/heads/branch1。(reflog 會留下所有 HEAD 切換記錄)
  • git add -p somefile 只 Commit 檔案部分內容(不常用) SourceTree: Stage Selected Lines
  • git hash-object 計算 SHA1 值、git cat-file -p SHA1值 印出檔案內容
  • 觀念:.git 內的物件有 Blob(檔案內容)、Tree(目錄結構)、Commit(提交資訊)、Tag(標籤) 註:書中超冷知識「在 .git 目錄裡有什麼東西」有精彩原理剖析。
  • 觀念:Git 儲存資料以 SHA1 為基礎,只要更動一個 Byte 都會重做一個 Blob,比起某些版控用差異備份方式有效率但浪費空間。但 Git 會在儲存 Blob 時會壓縮檔案內容,當物件過多或 Push 時則會觸發資源回收打包機制,打包時會引用差異備份技巧減少空間,不用太擔心其空間使用效率。
  • git branch branchName 建立分支,分支耗用成本很低(僅一個 40 Byte 檔案),請多利用。-m 改名、-d 刪除。請想像分支只是一張貼在 Commit 的貼紙,提交時換貼到最新的 Commit 上。
  • 觀念:若 master 分支出 cat,Commit 兩次,checkout master 切回主線,git merge cat 時只需 Fast-Forward 將 master 貼紙移到 cat 所在 Commit。(不想快轉硬要新增一個 Commit 的話,請加 --no-ff 參數)
    若是 master 分支出 cat、dog 各自發展,最後 cat Merge dog 或 dog Merge cat,則需要新增一個 Commit,將 cat 或 dog 貼紙放上去。
  • Rebase 合併。master 分支出 cat、dog,二分支各自 Commit 兩次,在 cat 分支 git rebase dog 可將 cat 分支接到 dog 分支的後面,cat 的兩次 Commit 需要重新計算(Applying)產生兩個新的 Commit,接在 dog 兩次 Commit 後方,而 cat 貼紙移至最後一個 Commit 上。Rebase 不會產生合併專用 Commit,看起來較乾淨,但有修改歷史之嫌,Push 前整理 OK,Push 後就不宜再用。
  • 如何取消 Rebase?用 reflog 找出 Rebase 前的最後動作 Commit SHA1,git reset b174a5a --hardgit reset ORIG_HEAD --hard(Rebase 是危險操作,Git 會留下 ORIG_HEAD 供緊急使用,其他危險動作還有 Merge、Reset)
  • 合併衝突。cat 跟 dog 都改了同一個檔案,cat 合併 dog 時會出現 CONFLICT (conent): Merge conflict in ... 字樣,而 Commit 會變成 "Uncomitted changes",衝突檔案(both modified)內容會被 Git 修改成兩邊差異對照並存的版本,務必手動修改決定採用哪邊再重新 git add 及 commit。若為 Rebase 合併發生衝突,將處於 rebase is progress 狀態,調整衝突檔案後用 git rebase --continue 繼續。
  • 非文字檔衝突時(例如:圖檔、DLL),git checkout --ours animal.jpggit checkout --theirs animal.jpg 決定使用誰的版本。
  • 在過去某個 Commit 上新增分支 git branch bird 657fce7git checkout bird 或是 git checkout -b bird 657fce7 一次搞定。
  • 修改過去歷史 git rebase -i bb0c9c2 互動模式修改歷史記錄,以文字編輯器整併 Commit。
    • pick 保留 Commit 不做修改
    • reword 保留 Commit 但改註解
    • edit 保留 Commit 但停止 Amend
    • squash 保留 Commit 但併入前一次 Commit
    • fixup 類似 squash,但捨棄本次 Commit 的 Log 訊息
    • exec 執行 Shell 指令
    • drop 移除 Commit
  • rebase -i 是解決疑難雜症的利器:將多個 Commit 合併成一個(squash)、將一個 Commit 拆成多個(edit,到某個 Commit 停下來,此時 reset HEAD^,重新 add 再 commit,拆出兩個 Commit 後再 rebase --continue,頗複雜)、在 Commit 間插入新的 Commit、調整 Commit 順序、刪除 Commit。提醒:修改歷史是很危險的事,有可能發生先後順序因果錯亂,請謹慎為之。
  • git revert HEAD --no-edit 相當於 TFS 的 Undo,會新增一個 Commit 記載退回上一個 Commit。個人專案 Reset/Rebase 較省事且乾淨俐落,若團隊協作政策不允許,就用 Revert。
  • Tag 標籤有兩種 Lightweight 輕量(只有名稱) vs Annotated 附註(可加入註解文字),但本質都像貼紙。常用於標註里程碑,如 v1.0.0、beta-release。
  • 停掉手上半成品切回問題分支處理 - 先 commit,再 reset HEAD^ 或用 git stash (將半成品暫存起來),git stash list 列出暫存項目,git stash pop stash@{2} 取出暫存項目,git stash drop stash@{0}、git stash apply stash@{0} 套用但不刪除暫存項目
  • 不小心提交帳密時如何毁屍滅跡?git filter-branch --tree-filter "rm -f config/password.file",如果已經 push 出去,就將 filter-branch 過的 Commit 重新 push -f 蓋掉。
    其他善後動作,用 filter-branch -f 再重來一次,rm .git/refs/original/refs/heads/master、git reflog expire --all -expire=now、git fsck --unreachable、git gc --prune=now
  • git cherry-pick 6a498ec 由別的分支撿 Commit 過來用
  • 觀念:Unreachable - 沒物件指向它,Dangling - 沒物件指向它,它也沒指向任何物件
  • 設定 GitHub 端點 git remote add origin git@github.com:darkthread/practice-git.git,推檔案上去 git push -u origin master、取檔案回來 git fetch、Pull = Fetch + Merge,git pull --rebase 抓回來並進行 Rebase 手動合併,複製一份 git clone git@github.com:darkthead/practice-git.git
  • FAQ: Fork GitHub 專案開發,如何跟上源頭的進度 git remote add src-project httqs://github.com/darkthread/blah.git,抓回更新git fetch src-project,合併進自己 Fork 的版本 git merge src-project/master
  • 製作更新包 git format-patch fdc7cd38..6e6ed76 -o /tmp/patches 產生 *.patch 檔,匯入方式 git am /tmp/patches/*
  • GitFlow 流程規範:
    • Master 分支 - 穩定隨時可上線,多會加上版號
    • Develop 分支 - 開發主線,Feature 由此分支出去,改好再合併進來
    • Hotfix 分支 - 從 Master 分支出來,改完合併回 Master 及 Develop
    • Release 分支 - Dev 成熟時分支到 Release 做上線前最後測試,測試沒問題合併至 Master 及 Develop
    • Feature 分支 - 由 Dev 分支出來,寫完再併合 Dev

My Git command cheatsheet


Comments

Be the first to post a comment

Post a comment


97 - 1 =