一般來說 Git 的操作會涉及到幾個區域

  • 硬碟區 disk : 檔案存放的一般資料夾,也會被稱為 workspace
  • 暫存區 staging : 保存 git add 紀錄的地方,也被稱為 index
  • 本地端 git local : 保存 git commit 紀錄的地方,也被稱為 repository
  • 遠端 git remote : git push 的倉儲,如 GithubGitlab 等等

而各個之間狀態變化的簡單關係如下圖所示 :

以上就是最基本的使用和定義。


revert

  • git revert
  • git reset

兩者都是 「Git 撤消 commit」 的一種形式,但是 revert 和 reset 的不同點是 git revert 是會增加一個反操作的 commit :

那麼和 reset 這個相對直觀的命令相比 revert 有什麼好處呢 ?

revert 可以撤銷中間任意一個 commit

比如下圖有一個 Change commit 後又有一個 Change2 commit , 假設這個 Change commit 的 id 是 70a2..:

若用 git revert 70a2 這樣最終得到的結果,就相當於只做了一個 Change2 ,而這個效果是我們用 reset 沒有辦法輕鬆地達到的。

雖然用 Git 來做版本管理時,偶爾會需要撤銷某些操作,但對於已經推上遠端 Github / Gitlab repository 的程式碼,並且該分支已經有多人協作的時候,撤銷操作就不能隨便做了!

對於這些「已經 push 到了遠端的 commit」,其 commit 是只能往前走,不可以刪除和更改的,故不可以進行 git reset 只能使用 git revert

如果我們修改的分支是除了自己之外沒有任何其他人用,就可以用 git reset 把某些歷史 commit 直接砍掉,但注意這個時候想同步到遠端的話必須使用 git push -f 強制推送更改,因為遠端的 歷史 commit 可能和自己本地端不一樣。

對於「公有分支」絕對不應該使用 git push -f ; 但是如果這是「個人分支」的話其實是可以使用 git push -f

「直接 Merge」 VS 「Rebase 後才 Merge」 的差異

假設情境(合併前)

      D---E  master
     /
A---B---C---F  origin/master

直接使用 merge

      D--------E
     /          \
A---B---C---F----G   master, origin/master

# G 為 merge commit,記錄了 D–E 和 F 的合併狀態。

使用 rebase 後才 merge

A---B---C---F---D^---E^   master, origin/master

# D^ 和 E^ 內容其實和 D 和 E 是一樣的,只是 commit SHA 不同。

為什麼 commit SHA 會改變呢 ? 因為 git commit 生成的 SHA 會依賴前一個 commit ,由於 rebase 導致依賴的前一個 commit 和原本的不一樣,所以 commit SHA 才會改變。

表格整理

項目mergerebase 後才 merge
歷史紀錄保留完整分支脈絡,會多一個 merge commit重寫歷史,變成線性結構
Commit SHA不變會改變(如 D → D’)
衝突處理次數只需處理一次每個 commit 都可能衝突,要多次處理
操作難度相對簡單需要每步都手動確認,再來 git rebase --continue
推薦使用時機大範圍修改且預期有大 conflict想保持線性紀錄

特別注意,是在新開的 branch 上使用 rebase ,而不是在 main/master 上使用 rebase,因為 rebase 其實算是一個「危險」的操作,會改寫 commit 的歷史

origin 是什麼

在 Git 中,origin 是預設的遠端名稱,代表 clone 時的來源,有些 cli 會加上 origin 是為了明確指定要從哪個 remote 抓取資料,因為可能有多個遠端如 origin、upstream 等等 :

# 用來查看目前專案所設定的 remote repository
git remote -v

# Terminal:
# origin  git@github.com:Aryido/aryido.github.io.git (fetch)
# origin  git@github.com:Aryido/aryido.github.io.git (push)

# 目前只有一個 origin 遠端

那什麼情況下會有多個遠端倉庫呢?

  • 同步多個平台的倉庫,例如 GitHub 和 GitLab
  • Fork 開源專案:origin 與 upstream

當從 GitHub 上 fork 一個開源專案時,預設的遠端名稱為 origin 會指向自己的 fork。但為了同步原始專案的更新,可以新增一個名為 upstream 的遠端,指向原始專案的倉庫。這樣可以方便地從原始專案拉取更新,然後是把更改推送到自己的 fork。

fetch & pull 差異

  • fetch

# 從預設 remote(origin)抓取所有分支更新,以下兩個等價
git fetch
git fetch origin

# 從指定的 remote(如 origin)抓下特定 <branch> 更新
git fetch origin <branch>

# 對於所有有設定的 remote ,抓取所有分支的更新,例如同時抓 origin 和 upstream 的所有 branch 更新
git fetch --all

特別注意 git fetch 只是下載所需的資料,不會合併和更新任何的檔案

  • pull

fetch 做完後,其實 branch 資料都還沒更新, fetch 只有把資料下載下來而已,還需要手動合併 :

git fetch origin master
# 手動把更新合併進來
git merge origin/master

---

# 等同於上面兩步:fetch + merge
git pull

pull = fetch + merge

origin master 還是 origin/master ? 何時使用斜線 /

當從 remote 抓取分支時,Git 會在本地建立對應的遠端追蹤分支,其命名格式為: <remote>/<branch>

例如: origin/master 表示遠端 origin 的 master 分支「在本地的追蹤分支」:

# 會使用斜線 / ,表示以「遠端在本地的追蹤分支」為基礎,rebase 當前 branch
git rebase origin/master

在某些命令中,需要分別指定「遠端名稱」和「分支名稱」作為參數,此時不使用斜線 /:

# 表示從遠端 origin 抓取 master 分支的更新
git fetch origin master

reflog

reflog 是縮寫,全稱是 reference logs

  • git log: commits 的紀錄
  • git reflog: 使用者在 git 上的操作紀錄

使用場景: 例如用了 git reset 語法,將版本還原到太前面的版本,用 git log 上看不到那些比較新的 commit ,此時想回復到原來狀態該怎麼辦?此時可以用 git reflog 指令,它會詳細顯示你每個指令的 SHA-1 值,幫助復原回去。


參考資料