iT邦幫忙

2025 iThome 鐵人賽

DAY 30
0
Software Development

深入一點點認識 Git系列 第 30

Day 30-深入一點點認識 Git:在遠端開分支 & 合併分支跟在本地進行效果一樣嗎?

  • 分享至 

  • xImage
  •  

Medium 好讀版點此


在本地端可以用 git branch 產生新分支、可以用 git merge 合併兩個分支;在遠端(如 GitHub)也可以開新分支與用發 pull request(PR)的方式合併分支,那在本地端與遠端進行這兩項操作,效果究竟是否相同?

在《深入一點點認識 Git》系列文的最後一篇,我們將透過觀察 .git/ 資料夾的變化,找出以上問題的答案。

前置準備

首先在本地端建立第一筆 commit:

echo "hello, world" > hello_world.txt
git add hello_world.txt
git commit -m "Initial commit"

得 .git/ 結構如下:

.git/
├── ...
│
├── logs/
│   ├── refs/
│   │    └── heads/
│   │         └── main # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072239 +0800 commit (initial): Initial commit
│   └── HEAD           # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072239 +0800 commit (initial): Initial commit
│
├── objects/
│   ├── 4b/                                        # blob物件
│   │   └── 5fa63702dd96796042e92787f464e28f09f17d
│   ├── 89/                                        # tree物件
│   │   └── 34288024c536ae07113abd94e0975284a707ac
│   ├── 97/                                        # commit物件
│   │   └── 16f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │    └── main   # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   └── tags/
│
├── COMMIT_EDITMSG  # Initial commit
├── ...
└── index # 100644 4b5fa63702dd96796042e92787f464e28f09f17d 0 hello_world.txt

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513QjznNzZYMP.png

接著把遠端倉儲網址設定為 remote

git remote add origin https://github.com/ruifuhong/Day30.git

把剛剛在本地的專案推到遠端 GitHub 倉儲:

git push -u origin main

在 GitHub 上建立分支

仿照 Day 29,我們於 GitHub 上建立新的 README.md 檔案:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513nsDQCmIUkG.png

簡單輸入 README.md 內容後,點選右上角的 Commit changes... 按鈕:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513eVNQkKNgMC.png

Commit 訊息使用預設的不變,但這次我們改選「創建一個新分支」,並將新分支命名為 feature,如下圖紅框處:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513KFUX8NElYE.png

因為 GitHub 假設這個新分支後面要合併進 main 分支,所以會開啟一個 pull request(PR),我們不改動該 PR 內容,直接點選下圖紅箭頭所指的「Create pull request」按鍵:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513nXUJvkWOIN.png

現在遠端有 mainfeature 兩分支,但在本地端只有 main 分支,遠端並有一個 PR 待合併:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513aPBlYpBLmt.png

使用 git pull 觀察

經過以下指令:

git pull

把遠端倉儲的版本拉下來後,本地端的 .git/ 資料夾長這樣:

.git/
├── ...
│
├── logs/
│     ├── refs/
│     │   ├── heads/
│     │   │     └── main   # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072239 +0800 commit (initial): Initial commit
│     │   └── remotes/
│     │          └── origin/
│     │                 ├── feature # 0000000000000000000000000000000000000000 4d977033b1474489164a0489ba324d9ef40c3615 Ralph <ralph@ralphmail.com> 1757131386 +0800 pull: storing head
│     │                 └── main # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072641 +0800 update by push
│     └── HEAD   # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072239 +0800 commit (initial): Initial commit                         
│
├── objects/
│   ├── 4b/                                        # 舊有blob物件
│   │   └── 5fa63702dd96796042e92787f464e28f09f17d
│   ├── 4d/                                        # 新增commit物件
│   │   └── 977033b1474489164a0489ba324d9ef40c3615
│   ├── 31/                                        # 新增tree物件
│   │   └── 161639bcde43c9e4b599faab2107fd98948f0e
│   ├── 52/                                        # 新增blob物件
│   │   └── 9b7254396083b8c44da1197fe19e470b79f89e
│   ├── 89/                                        # 舊有tree物件
│   │   └── 34288024c536ae07113abd94e0975284a707ac
│   ├── 97/                                        # 舊有commit物件
│   │   └── 16f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │    └── main          # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   ├── remotes/
│   │    └── origin/
│   │          ├── feature # 4d977033b1474489164a0489ba324d9ef40c3615
│   │          └── main    # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   └── tags/
│
├── COMMIT-EDITMSG         # Initial commit
├── ...
├── FETCH_HEAD # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086  branch 'main' of https://github.com/ruifuhong/Day30
│              # 4d977033b1474489164a0489ba324d9ef40c3615 not-for-merge branch 'feature' of https://github.com/ruifuhong/Day30
├── HEAD  # ref: refs/heads/main
├── index # 100644 4b5fa63702dd96796042e92787f464e28f09f17d 0       hello_world.txt
└── ORIG_HEAD # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086

在遠端開分支後再經過 git pull,本地端資料夾共有以下變化:

  • logs/remotes/origin:除了 main 分支外,還多了遠端建立的 feature 分支歷史紀錄,且該歷史紀錄並無上代,表示 feature 分支的第一個 commit 就是 4d97703...
  • objects/:遠端建立 commit 形成的 blob、tree 與 commit 物件也通通出現。
  • refs:遠端參考一樣多了 feature 分支,指向 4d97703... 這個 commit;然而,本地端目前依然只有 main 分支。
  • FETCH_HEADmain 的指向 9716f8a...、feature 的指向 4d97703... 且標示 not-for-merge
  • index:仍為 4b5fa63... 這個 blob 物件。
  • ORIG_HEAD:指向 9716f8a...

圖示如下:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513KqYMcggpoT.png

看來遠端開分支帶來的變化都有被帶到本地,但如果輸入 git branch 查看本機目前有的分支,會看到什麼呢?

https://ithelp.ithome.com.tw/upload/images/20250929/20178513vRNJG9Upz7.png

為何沒有 main 分支?其實如果回頭看 .git/ 資料夾,也確實會發先 main 分支只存在於 /remotes 中,表示本地端是沒有 feature 分支的:

├── refs/
│   ├── heads/
│   │    └── main              # 本地端沒有feature分支
│   ├── remotes/
│   │    └── origin/
│   │          ├── feature     # 遠端才有feature分支
│   │          └── main
│   └── tags/

如果要查看不限於本地的所有分支,要在 git branch 指令加上 -a 如下:

git branch -a

這樣就能看到那個遠端的 feature 分支了:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513kS14vLXoMw.png

那要怎麼在本地端切換到 feature 分支呢?

其實一樣可以直接下 git switch,這樣在本地端就自動長出 feature 分支了呢:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513RfwovTccw0.png

再回去觀察 .git/ 資料夾,可發現本地端的 feature 分支長出來了:

├── refs/
│   ├── heads/
│   │    ├── feature           # git switch後,本地端也長出feature分支
│   │    └── main
│   ├── remotes/
│   │    └── origin/
│   │          ├── feature
│   │          └── main
│   └── tags/

另一方面,工作目錄在 git pull 後還沒有在 feature 分支上的 README.md 檔案,經過 git switch,這檔案也出現了:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513v9Vqzb5PDm.png

使用 git clone 觀察

而如果是在空資料夾透過 git clone 把整份專案克隆下來,本地的 .git/ 資料夾如下:

.git/
├── ...
│
├── logs/
│     └── refs/
│         ├── heads/
│         │     └── main   # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757135590 +0800 clone: from https://github.com/ruifuhong/Day30
│         └── remotes/
│                └── origin/
│                       └── HEAD      # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757135590 +0800 clone: from https://github.com/ruifuhong/Day30
│                              
├── objects/
│   ├── info/
│   └── pack/      # pack-52f61e3d010cb84169d7a15a7ef53a4d5409b7e6.idx
│                  # pack-52f61e3d010cb84169d7a15a7ef53a4d5409b7e6.pack
│
├── refs/
│   ├── heads/
│   │    └── main        # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   ├── remotes/
│   │    └── origin/
│   │          └── HEAD  # ref: refs/remotes/origin/main
│   └── tags/
│
├── ...
├── HEAD           # ref: refs/heads/main
├── index          # 100644 4b5fa63702dd96796042e92787f464e28f09f17d 0       hello_world.txt
└── packed-refs    # pack-refs with: peeled fully-peeled sorted 
                   # 4d977033b1474489164a0489ba324d9ef40c3615 refs/remotes/origin/feature
                   # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 refs/remotes/origin/main

本地資料夾變化整理如下:

  • logs/:不論本地端或遠端,都只有包含 9716f8a... 這筆 commit 的紀錄。
  • objects/:如 Day 29 說明,使用 git clone 得到的物件是打包過的,透過 git verify-pack -v .git/objects/pack/pack-*.idx 指令檢視物件內容,可得全部六個物件:

https://ithelp.ithome.com.tw/upload/images/20250929/20178513hg92o0hXYt.png

  • refs/:本機端的是 main 指向 9716f8a...,但遠端的是寫 HEAD 指向 main 分支。
  • packed-refs:遠端 feature 分支指向 4d97703...、遠端 main 分支指向 9716f8a...

那這時候本地端有 feature 分支嗎?
https://ithelp.ithome.com.tw/upload/images/20250929/20178513SUYvnhPyi5.png

答案依然是沒有的,但透過 git switch 切換分支,就一樣可以建立本地的 feature 分支:
https://ithelp.ithome.com.tw/upload/images/20250929/2017851325vlLFEbuO.png

此時 .git/ 資料夾的 refs/ 如下:

├── refs/
│   ├── heads/
│   │    ├── feature           # 新分支,指向4d977033b1474489164a0489ba324d9ef40c3615
│   │    └── main
│   ├── remotes/
│   │    └── origin/
│   │          └── HEAD
│   └── tags/

在 GitHub 上合併分支

點選這個「Merge pull request」按鍵,把 GitHub 上的 feature 分支合併進 main 分支:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513C2rpxajrew.png

我們使用預設的合併訊息,但刪除延伸說明,直接點 Confirm merge 完成合併:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513SnJVsD7NW8.png

完成合併!
https://ithelp.ithome.com.tw/upload/images/20250929/20178513mt9ldMnZt7.png

使用 git pull 觀察

若回到本地端原始狀態(尚未把遠端 feature 分支拉下來),直接使用 git pull 這個合併完的分支,會發生什麼事呢?

.git/ 資料夾變化如下:

.git/
├── ...
│
├── logs/
│   ├── refs/
│   │    ├── heads/
│   │    │     └── main      # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072239 +0800 commit (initial): Initial commit
│   │    │                   # 新增9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 be0d15a10a7609be326fc0de996486fc6c64f665 Ralph <ralph@ralphmail.com> 1757137350 +0800 pull: Fast-forward
│   │    └── remotes/
│   │          └── origin/
│   │                ├── feature # 0000000000000000000000000000000000000000 4d977033b1474489164a0489ba324d9ef40c3615 Ralph <ralph@ralphmail.com> 1757137350 +0800 pull: storing head
│   │                └── main    # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072641 +0800 update by push
│   │                            # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 be0d15a10a7609be326fc0de996486fc6c64f665 Ralph <ralph@ralphmail.com> 1757137350 +0800 pull: fast-forward
│   │
│   └── HEAD  # 0000000000000000000000000000000000000000 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 Ralph <ralph@ralphmail.com> 1757072239 +0800 commit (initial): Initial commit
│             # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086 be0d15a10a7609be326fc0de996486fc6c64f665 Ralph <ralph@ralphmail.com> 1757137350 +0800 pull: Fast-forward
│
├── objects/
│   ├── 4b/
│   │   └── 5fa63702dd96796042e92787f464e28f09f17d
│   ├── 4d/
│   │   └── 977033b1474489164a0489ba324d9ef40c3615
│   ├── 31/
│   │   └── 161639bcde43c9e4b599faab2107fd98948f0e
│   ├── 52/
│   │   └── 9b7254396083b8c44da1197fe19e470b79f89e
│   ├── 89/
│   │   └── 34288024c536ae07113abd94e0975284a707ac
│   ├── 97/
│   │   └── 16f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086
│   ├── be/                                          # 新增commit物件
│   │   └── 0d15a10a7609be326fc0de996486fc6c64f665
│   ├── info/
│   └── pack/
│
├── refs/
│   ├── heads/
│   │    └── main            # be0d15a10a7609be326fc0de996486fc6c64f665
│   ├── remotes/
│   │    └── origin/
│   │          ├── feature   # 4d977033b1474489164a0489ba324d9ef40c3615
│   │          └── main      # be0d15a10a7609be326fc0de996486fc6c64f665
│   └── tags/
│
├── COMMIT-EDITMSG           # Initial commit
├── ...
├── FETCH_HEAD  # be0d15a10a7609be326fc0de996486fc6c64f665  branch 'main' of https://github.com/ruifuhong/Day30
│               # 4d977033b1474489164a0489ba324d9ef40c3615 not-for-merge branch 'feature' of https://github.com/ruifuhong/Day30
├── HEAD        # ref: refs/heads/main
├── index       # 100644 529b7254396083b8c44da1197fe19e470b79f89e 0       README.md
│               # 100644 4b5fa63702dd96796042e92787f464e28f09f17d 0       hello_world.txt
└── ORIG_HEAD   # 9716f8a33bf4ffdcb0f75fe6fe6f65c00d7fb086

圖示如下,可發現新增了一個合併的 commit 物件,且本地與遠端的 main 分支都指向它:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513FRyiasG96h.png

因為已經合併,我們就不用再理會 feature 分支,也不用下 git switch 在本地端建立這條分支。

使用 git clone 觀察

.git/
├── ...
│
├── logs/
│     └── refs/
│         ├── heads/
│         │     └── main              # 0000000000000000000000000000000000000000 be0d15a10a7609be326fc0de996486fc6c64f665 Ralph <ralph@ralphmail.com> 1757167054 +0800 clone: from https://github.com/ruifuhong/Day30
│         └── remotes/
│                └── origin/
│                       └── HEAD      # 0000000000000000000000000000000000000000 be0d15a10a7609be326fc0de996486fc6c64f665 Ralph <ralph@ralphmail.com> 1757167054 +0800 clone: from https://github.com/ruifuhong/Day30
│                              
├── objects/
│   ├── info/
│   └── pack/      # pack-106eec24ca3fb261662c7299d2939e60a9dbe379.idx
│                  # pack-106eec24ca3fb261662c7299d2939e60a9dbe379.pack
│
├── refs/
│   ├── heads/
│   │    └── main              # be0d15a10a7609be326fc0de996486fc6c64f665
│   ├── remotes/
│   │    └── origin/
│   │          └── HEAD        # ref: refs/remotes/origin/main
│   └── tags/
│
├── ...
├── HEAD           # ref: refs/heads/main
├── index          # 100644 529b7254396083b8c44da1197fe19e470b79f89e 0       README.md
│                  # 100644 4b5fa63702dd96796042e92787f464e28f09f17d 0       hello_world.txt
└── packed-refs    # pack-refs with: peeled fully-peeled sorted 
                   # 4d977033b1474489164a0489ba324d9ef40c3615 refs/remotes/origin/feature
                   # be0d15a10a7609be326fc0de996486fc6c64f665 refs/remotes/origin/main
  • logs/:不論本地端或遠端,都從合併的 commit be0d15a... 開始記錄。
  • objects/:一樣是打包過的物件,透過 git verify-pack -v .git/objects/pack/pack-*.idx 指令檢視物件內容,可得全部七個物件,包含合併物件 be0d15a...(反白處):

https://ithelp.ithome.com.tw/upload/images/20250929/201785138gsrLHB24i.png

  • refs/:本機端的是 main 已改指向 be0d15a...,遠端依然是寫 HEAD 指向 main 分支。
  • packed-refs:遠端 feature 分支仍指向 4d97703...、遠端 main 分支已改指向合併的 commit be0d15a...

額外觀察:簽章

因為合併 commit 是在 GitHub 上做的,一如 Day 29 文章觀察,這個合併 commit 也帶有自己的簽章:
https://ithelp.ithome.com.tw/upload/images/20250929/20178513pupUB4PlD9.png

小結

若在遠端開分支再 git pullgit clone 到本地,則 .git/ 資料夾會分開記錄遠端跟本地的分支紀錄,而在本地端用 git switch 等指令切換到遠端新增的分支前,本地紀錄不會有這新分支。

在遠端以 pull request 的方式合併分支,其形成的 git 物件結構與在本地用 git merge 相似,都是產生新的合併 commit,上代為合併相關兩分支原本最新的 commit,而這個新合併 commit 因為在遠端產生,所以預設自帶簽章。

總而言之,在遠端開分支與合併分支的效果與在本地進行類似,比較大的差別是:如果拉或克隆下來後,沒有在本地切換到新分支,則本地端不會產生在遠端才出現的新分支。

參考資料

  1. 與其它開發者的互動 - 使用 Pull Request(PR)

上一篇
Day 29-深入一點點認識 Git:git fetch/pull 與 git clone 不只使用時機不同
下一篇
iThome 鐵人賽初體驗完賽回顧:《深入一點點認識 Git》
系列文
深入一點點認識 Git31
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言