Git Cheatsheet.

基本指令索引

指令 說明
git add <file> 打包檔案變更
git branch 顯示本地分支列表
git commit -m "message" 提交改變,製作 commmit
git checkout <commit-SHA> 提取某個 commit 版本 worktree
git checkout -b <branch> 新增 branch
git clone <url> 複製 remote repo 至 local
git config 進行.git 工具設定
git diff <commit1> <commit2> 比較版本差異
git fetch 取得遠端最新變更但不合併
git fetch --prune 清除已刪除的遠端分支
git init git 環境初始化
git ls-files 列出所有追蹤中的檔案
git log 查看 commit log, file log
git merge <commit-SHA> 將指定 commit 合併至 HEAD
git push 推送變更到遠端分支
git pull 拉取遠端分支並合併
git remote 查看 remote 設定
git reset 讓分支回到某個 commit
git rebase <commit-SHA> 重新整理當前分支並套用變更
git reflog 查看 HEAD 的變更歷史
git restore 恢復 stage area 或者 worktree 的變更
git rm --cached <file> 讓 Git 保留檔案但停止追蹤
git status 查看目前 Git 的狀態
git show <commit> 可以看基本資訊,以及檔案差異
git stash 暫存當前變更
git tag <tag_name> 在當前 commit 打 tag 標籤

進階指令索引

指令 說明
git revert HEAD 新增反向 commit,用於取消最新的 commit,但保留取消歷史
git cat-file -p <commit-SHA> 還原 Git 物件的內容
git cat-file -t <commit-SHA> 查看物件類型
git ls-files -s 列出當前目錄下包含子目錄底下的 blobs
git clone --recurse-submodules <URL> 一併下載相依的 submodules
git cherry-pick 可以從 commit tree 的任何地方拿一個 commit 來放在 HEAD 上(只要那個 commit 不是 HEAD 的 parent)
git rebase -i <commit-SHA> 互動式 rebase,若 rebase 之中有許多 commits 要剪下貼上,可以選擇只 rebase 那些 commits,甚至可以排序

觀念

主題

狀態

檔案變動狀態

  • M: Modify
  • A: Add to staging area
  • D: Deleted
  • U: Untracted files in worktree.

檔案追蹤

  • git ls-files 列出所有追蹤中的檔案。
  • git rm --cached <file> 讓 Git 停止追蹤檔案。
  • .gitignore 用來設定 Git 忽略檔案的規則,其中列出的檔案 Git 會予以忽略,不會追蹤其改變。
    • 但已經在追蹤的檔案,不受 .gitignore 檔案影響,必須先停止追蹤 (git rm --cached <file>),再加入 .gitignore,之後該檔案的變動才不會被追蹤。

打包變更與提交紀錄

打包

指令 說明
git add . 打包所有檔案變更。
git add filename 打包指定檔案變更。
git add -p filename 選擇性地新增某檔案的部分變更。

所謂打包就是將 worktree 變更(Changes)儲存至 staging area(Staged Changes)。用意是將需要提交的變更先整理起來,再發一個 commit 提交出去。 比如你可能會想要將較不相關的變動分次提交,所以你會先打包相關的一部分儲存至 staging area,進行 commit 提交時,只有 staging area 的變更會被提交出去。沒有被 staged 的部分還維持在 “Changes"的狀態。

提交

指令 說明
git commit -m "message" 提交改變,製作 commit
git commit --amend -m "message" 拆掉最新的 commit 連同新提交到 staging area 的部分,重新製作 commit

取消更動

指令 說明
git restore -W . 就會恢復 Worktree 所有修改(預設選項)
git restore -S . 就會恢復 Stage area 所有修改 (staged changes —> changes)

切換版本

指令 說明
git checkout <commit-SHA> 切換至指定 commit 版本
git checkout <commit> <file> 指定檔案回復到某個 commit 版本
git checkout . 切換至當前版本,相當於還原當前工作區所有修改,建議以 git restore . 指令取代

checkout 切換至某個版本、提取某個版本資料,均會更新工作目錄。

  1. 整個 worktree 切換至指定 commit 版本

    • git checkout <commit-SHA>
    • 必須是工作目錄目前沒有任何修改的檔案,否則給予警告不給切換,因為他要切換至其他版本了,將改變工作目錄,而目前的修改尚未保存。
    • 切換後 HEAD 會指向該 commit。
  2. 指切換單一檔案至特定 commit 之版本

    • git checkout <commit> <file>
    • 回復指定檔案到某個 commit 版本。只會更新 worktree 檔案內容,並不會動到 HEAD。

分支

在本地 repo 的 branch 稱為 local branch; 在 remote repo 的 branch 稱為 remote branch; 而在本地 repo 用來追蹤 remote branch 的稱作 remote-tracking branch。

指令 說明
git branch 顯示本地分支列表。
git branch -a 顯示所有分支(包含遠端)
git branch -vv 顯示所有分支的詳細訊息,包含 upstream 對應
git branch -d <branch_name> 刪除本地分支
git branch -rd <remote>/<branch_name> 刪除追蹤分支 (-r 代表 remote)
git branch <branch_name> 新增 branch 於目前 HEAD 上
git branch <branch_name> <commit-SHA> 新增 branch 於指定 commit 上
git branch -f <branch_name> <commit-SHA> 強制將現有 branch 改貼到指定 commit 上,但不可以是 HEAD 正指向之 branch
git checkout -b <branch_name> 新增 branch 於目前 HEAD 上並切換到該分支
git checkout <commit-SHA> -b <branch_name> 新增 branch 於指定 commit 上並切換到該分支

其他相關:

  • git fetch --prune 若遠端已沒有該分支但本地端還有其追蹤分支的話,予以刪除。

標籤

指令 說明
git tag <tag_name> 新增 tag
git tag <tag_name> <commit> 新增 tag 於指定 commit 上
git tag -d <tag_name> 刪除 tag
git tag <tag_name> -a -m "message" 新增帶有註解(annotated)的 tag

查看歷史

指令 說明
git log 查看 commit 記錄 (預設為 HEAD 指向)
git log --oneline 單行簡潔查看 commit 記錄
git log -- <path/to/file> 只查看某個檔案的 commit 記錄
git log -p -p 顯示變更內容
git reflog 查看 HEAD 的變更歷史

改變歷史

指令 說明
git reset <commit-SHA> 拆掉當前 commit,分支回到指定 commit(預設為 –mixed)
git reset --soft <commit-SHA> 拆掉當前 commit,分支回到指定 commit,拆除之變更保留在 staged 狀態
git reset --mixed <commit-SHA> 拆掉當前 commit,分支回到指定 commit,拆除之變更保留在 changes 狀態
git reset --hard <commit-SHA> 拆掉當前 commit,分支回到指定 commit,刪除所有變更

想像 commit1 到 commit2 的過程: commit1 -> changes -> staged changes -> commit2

  • –soft 就是回退 1 步到 staged changes 狀態
  • –mixed 就是回退 2 步到 changes 狀態
  • –hard 就是回退 3 步到 commit1 狀態

遠端協作

指令 說明
git fetch <remote> 取得遠端最新變更但不合併
git pull <remote> <branch> 拉取遠端分支並合併到 HEAD
git pull --rebase 表示要使用 rebase 的方式合併 fetch 下來的 commit
git push 推送當前分支到對應的 upstream 分支
git push <remote> <branch> 推送指定分支同步至遠端
git push <remote> <branch> --force 強制推送
git push -u <remote> <branch> 推送時一併設定 branch upstream
git branch -vv 可以使用此指令 查看 branch 是否設置 upstream
git remote 查看 remote repo
git remote -v -v for verbos 提供更多訊息
git remote add <remote> <remote repository URL> 建立 remote repository 指標

upstream

upstream 就是 <local_branch> 預設進行 push/pull/fetch 等指令的 <remote_branch> 分支。 設定好 upstream 使得進行 push/pull/fetch 等指令時,不用每次都詳細指定 <local_branch> 是要跟哪個 <remote_branch> 互動,會比較方便。 若設定 <remote_branch> 為 <local_branch> 之 upstream,當然也可以反過來說 <local_branch> 是 <remote_branch> 之 downstream。

建立 upstream

當 remote 端已有該 branch 時,我們可以這樣建立 upstream。

  • 必須要先取得該 <remote_branch> 的追蹤分支,使用 git fetch <remote> <branch>
  • 為當前 HEAD 指向的 branch 設定 upstream,使用 git branch -u <remote>/<branch>git branch --set-upstream-to=<remote>/<branch>
  • 也可以指定為哪個 branch 設定 upstream,git branch -u <remote>/<branch> <branch>
  • 或者建立參考於 <remote_branch> 的新分支 git checkout <remote>/<branch> -b <branch>,就會自動設立 upstream 關係。

而若是遠端沒有該 branch,我們可以推送 <local_branch> 上去建立 <remote_branch> 同時建立 upstream 關係。 git push -u <remote> <local_branch>:<remote_branch> 或者 local 跟 remote 同名的話 git push -u <remote> <branch>

取消 upstream

git branch --unset-upstream

查看 upstream

  • git branch -vv 可以看到所有 branch 的詳細訊息,包含該 branch 是否有 upstream branch。
  • git remote show <remote> 會顯示 <remote> 的詳細訊息,包含 <local_branch> 對應到的 <remote_branch>,即 upstream。

git clone

  • 若是由 github clone 下來的 repo,預設只會得到 1 個 default branch 以及 1 個 指向該 github repo 的 remote(名為 origin)。 使用 git branch -a 可以看到其他 remote 分支,若你想要建立該 <remote_branch> 的 downstream 可以 git branch <branch> <remote_branch>

git push

一定要知道來源與目標,來源可以是 commit 但目標一定是 branch,目標不存在也沒關係,會自動創建。 push 前,須保證不會 conflict,所以會被檢查 remote_branch 的 history,local commit 是否都有,若沒有要先 fetch 更新,merge 後在 local 解決衝突才能重新 push。

git push <remote> <commit>:<remote_branch>

  • 即 push <local_branch> -> <remote_branch>,意思是把 <local_branch> 上傳到 <remote> 然後合併到 <remote_branch>,如果 remote repo 沒有該 branch 則會在 remote 上自動創建一個。
    • 可以看到本地跟遠端的分支名稱可以不同,雖然很少會需要這麼做。
    • 甚至本地端可以直接指定 commit

git push <remote> <branch>

  • 同名,把同名 local 同步到 remote

git push

  • 都沒給參數代表可以推理出來,當前 HEAD 需指向設定好 upstream 的 branch,如此可推得 <remote> 以及 <remote_branch>。

特別技: 如何在 local 端刪除 remote 端的分支? 就是讓 <local_branch> 的地方留空,git push <remote> :<remote_branch> ,push 一個空 branch 到 remote 代表刪除。

git fetch

主要是更新遠端分支的狀態(所以 tracking-branch 會改變),若有需要會自動創建 remote-tracking branch。

git fetch

  • 更新所有 remote_branch 的狀態。 git fetch <remote> <branch>
  • 更新指定 remote_branch 的狀態。 git fetch <remote> <src>:<dst>
  • 我個人認為不要使用,因為他讓 fetch 不再只是單純更新 tracking-branches 的動作,還包含快速移動分支等 merge 概念,概念被複雜化了。
  • 可指定更新給誰,可以是 local branch 或 tracking branch 或 HEAD
  • dst 不可是當前 checkout 的分支
  • 若 src 是 branch 則會更新 tracking branch 也會將新的 commit 增加到 dst 上。
  • 若 src 是 commit 則不會更新 tracking branch,只會將新的 commit 增加到 dst 上。

git pull

git pull = git fetch + git merge,也就是 fetch 遠端分支的更新,並嘗試 merge。

記得 merge 是針對 HEAD 位置的操作。

git pull <remote> <remote_commit>:<local_branch> = git fetch <remote> <remote_commit>:<local_branch> + git merge <local_branch>

  • 我個人認為不要使用,因為指令結果複雜不好記憶,fetch 指令在這樣的格式下不只是 fetch remote 狀態,還會移動 local_branch,然而 pull 又包含 merge 又會移動 HEAD 位置。總之若非很熟悉,太複雜的指令沒必要用,可以用多個基礎指令一步步完成就好,好記又不容易錯。
  • 得以指定 fetch source 以及 merge target
  • 由 HEAD 指向的 branch 去 merge 收到 fetch 的 <local_branch>

git pull origin bar:bugFix 相當於:

git fetch origin bar:bugFix; 
git merge bugFix

git pull <remote> <branch> = git fetch <remote> <branch> + git merge <branch>

git pull origin foo 相當於:

git fetch origin foo; 
git merge origin/foo

git pull = git fetch <remote> <branch> + git merge <remote_branch>

  • git pull 沒有參數,故由 HEAD 指向之 branch 推論,該 branch 需要設定好 upstream,固可推得哪個 <remote> 以及哪個 <remote_branch>。
  • 可以看到使用 git fetch <remote> <branch> 只會取得 upstream 更新,並不是 git fetch 取得所有更新。

git pull 相當於:

git fetch <remote> <upstream>
git merge <upstream>

合併

Merge

指令 說明
git merge <commit-SHA> 當前分支嘗試合併某 commit 內容 (fast forward 優先)
git merge <commit-SHA> --no-ff -m “merge message” 使用 –no-ff 禁用 fast forward,必會產生 merge 訊息
git merge --squash <commit-SHA> ???
git merge --abort 當 merge conflict 時,若想放棄這次 merge 回復 merge 前狀態時使用

git merge 一定是對 HEAD 位置 merge,HEAD 不一定要指向 branch(但通常是),merge 完 HEAD 會移動,若HEAD 指向 branch 則 branch 也會移動。

Fast Forward(快速前進)是一種 合併(merge)策略,當 當前分支的 HEAD 沒有與目標分支產生分歧,而目標分支只是在 HEAD 之後新增了 commit 時,Git 會直接把 HEAD 指向最新的 commit,而不產生新的 merge commit。

Fast Forward 合併的條件:只要 commits 鍊是可以溯源,那必定可以 Fast Forward。 比如當前分支 A 是目標分支 B 的祖先之一,那從 B commits 溯源並訂可以找到 A commits,則必可以 Fast Forward。

  • No Fast-Forward 的 merge,必定會額外產生出一個 commit 紀錄是由哪兩個 commit 合併的,故會看到 2 個 parents。當你想要特意保留 merge 訊息可使用。
  • Fast-Forward merge 則不會有 merge commit,僅是將當前分支直接快轉移動貼到另一個 commit 上,故會看不出來曾經有與其他 branch 合併過。

Merge Conflict

當兩分枝執行合併,但兩個分支都修改了一樣的內容,導致讓 git 不知怎麼合併,這就是 merge conflict。 此時,merge 會被暫停並且 git 會把衝突的檔案以及兩分枝的差異標示給你看,讓你自行選擇怎麼修正。 不管你怎麼修正,修完之後再次 git addgit commit 提交,就算是解決 conflict 了。

Rebase

指令 說明
git rebase <commit-SHA> 把自己跟別人不同的 commit 都剪下重新長在別人身上。
git rebase --continue 當 rebase 遇到 conflict 時,待解決後以此指令繼續 rebase 過程。
git rebase <on-commit> <from-commit> 比對 commits 差異,將 不同的 commits 剪下貼在
git rebase -i <commit-SHA> 互動式 rebase,自由決定要剪下哪些 commits,還可以調整順序,按照指定順序重新長在指定之 commit 上!
git pull --rebase 使用 rebase 的方式處理 fetch 到的 commit

Rebase 與 merge 之差別:

  • 不會另外做出 merge 物件。
  • 有點像將 commit 剪下貼上的概念,把新增的 commits 都剪下,基於 base 重新製作 coomits。

比如說有兩個 branches branch_A,branch_B 共同的 parent 是基於 commit_parent,此時若要用 rebase 方式合併,假設說要 let branch_A rebased on branch_B,則會發現她會把從 commit_parent 之後 branch_A 長出的 commit 一個個剪下,且重新插枝在 branch_B 現在的 commit 上。假設從 commit_parent 之後 branch_A 長出 2 個 commits,那就會將這兩個 commits 剪下,然後 rebased on branch_B,重新製作兩個 commits 插到 branch_B 之後。所以這時候,會看到 branch_A 與 branch_B 在同一條路上了,而且 branch_A 領先 branch_B 2 個 commits。

rebase 完會 branch 會前進,移動到新 commit 上。

注意: rebase 是修改 commit 歷史的行為。

Cherry-Pick

git cherry-pick <commit1> <commit2> <commit3>... 就是去複製某些 commit 重新製作嘗試將改變貼自己身上(HEAD)。

這是一個非常強大的工具,甚至讓你可以重新整理你的整個 branches 之 commits 排列順序等等。

cherry-pick 沒有改變歷史,他是造新的一條 branch 出來。cherry-pick 也會讓 HEAD 以及 branch 移動。

暫存變更

指令 說明
git stash 暫存當前變更
git stash -m "<message>" -m 可以註解提供說明訊息
git stash list 列出所有 stash
git stash pop 取回最近一次的 stash,並從列表中移除
git stash pop "stash@{<num>"} 取回編號 num 的 stash,並從列表中移除
git stash apply 取回最近一次的 stash,但不從列表中移除
git stash drop 刪除最近一次的 stash
git stash clear 清除所有 stash
  • pop/apply/drop 都可以按照 stash id 選擇存取哪個 stash。
  • stash id 編號越小越近期的 stash
  • 不論是 worktree 變更或者已經 staged 的變更,都可以被 stash。只不過 pop 回來都會變成是 changes 狀態,不會是 staged 的狀態。
  • 即使 stash 是在比較舊的 commit 上,而 pop 時 HEAD 是在比較新的 commit 上,只要不衝突,依然可以成功 pop 將變更加入。

差異比較

指令 說明
git diff 比較 a.暫存區 與 b.工作區
git diff HEAD 比較 a.當前 commit 與 b.工作區
git diff --staged 比較 a.當前 commit 與 b.暫存區
git diff <commit> 比較 a.某個 commit 與 b.當前 commit
git diff <commit1>..<commit2> 比較 a.commit1 與 b.commmit2
git diff <commit1>..<commit2> -- <file> 比較 a.commit1 與 b.commmit2 間指定檔案之變化

基本名詞及概念

HEAD 是一個 reference,他可以指向 branch 也可以直接指向 commit,不管是哪種,它最終都可以推論出一個 commit,表示目前所 checkout 的 commit。

  1. HEAD -> branch -> commit
  2. HEAD -> commit
  • 輸入指令時因為 HEAD 往往已經指向某個 commit,許多針對當前 HEAD 指向之指令就可以不用再指定 commit,非常方便。
  • cat .git/HEAD 可以查詢 HEAD 指向

當 HEAD 不是指向 branch 則稱為斷頭(detached head)。

  • git checkout <branch> 為 HEAD -> branch -> commit,沒有斷頭。
  • git checkout <commit> 為 HEAD -> commit,就會形成斷頭。
  • git checkout <tag> 為 HEAD -> commit,也會形成斷頭。

其他

  • branch 指向某個 commit,會隨著新的 commit 移動。
  • remote 是遠端 repository。
  • upstream 就是某個 branch 其預設 push 的 remote。
  • commit 指向 root tree object 以及 parent commit。

快速定位

依照 HEAD 移動紀錄

  • git reflog 可看到 HEAD 的移動紀錄
  • @ 可代表 HEAD
  • git checkout "@{1}" 定位 HEAD 的上 1 動之 commit 位址
  • git checkout "@{4}" 定位 HEAD 的前 4 動之 commit 位址

依照 commit 指向的第一個父 commit 去回溯。

  • ^ 代表「parent(父節點)」,它會指向當前 commit 的 第 1 個父 commit。
    • 格式 “^”
    • git checkout "HEAD^" 或者 git checkout "@^"
    • git checkout "HEAD^^" 則是往前回溯 2 次。
    • 若 ^ 後面有數字比如 ^2 則代表指向當前 commit 的第 2 個父 commit。
  • ~n 代表「向前 n 代的 commit」,相當於不斷往 第一個父 commit 回溯 n 次。
    • 格式 “~n”
    • git checkout "HEAD~1" 或者 git checkout "@~1" 或者 git checkout "3bdf7c55~1" 或者 git checkout "main~1"
  • 有趣的是移動是可以疊加的,可以
    • @^^~^^
    • @^^2^^~~
    • main^2~3

Git 四大物件

Git 有:blob / tree / commit / tag 四大物件。

每個 git 物件之檔名,均是由內容(不確定是壓縮前或後)及一些其他資訊所計算出的 SHA 值,即使內容只有一個字元差異,也會產生一個新的 git 物件。SHA 的算法很好的用作 ID 表示特定的檔案,且每個檔案名稱長度相同,方便物件關聯檔案控管。

而這些物件的檔案內容則都是壓縮過的,可以用工具還原為未經壓縮之內容。

物件檢視

  • git cat-file -t <commit-SHA> 查看物件類型。
  • git cat-file -p <commit-SHA> 還原 Git 物件壓縮前的內容。

SHA 值計算 cat <file> | git hash-object --stdin 計算 git 物件之檔案名稱(SHA 值)。

  1. Blob 內容是原始檔案內容經過 git 壓縮演算法而成。
  2. Tree 內容指向其他
    • Tree 或
    • Blob 物件(含 blob 原始檔案之檔名)。
  3. Commit 內容指向
    • git 跟目錄的 Tree 物件
    • parent commit
    • commit 訊息
  4. Tag
    • 指向一個 commit

git add 後產生新的 Blob 物件。 git commit 後產生 Tree、commit 物件,把所有物件串起來。

基本設定

設定

  • git config --global user.email <email_addr>
  • git config --global user.name <username>

查看設定 git config --global --get user.name

初次連接 remote repo

設定 remote 以及 upstream

git remote add <remote> <remote repository URL>
git push -u <remote> <branch>