Git——分支管理(2)

NeoNexus發表於2024-05-06

Git——分支管理(2)

提示:圖床在國外且動圖比較多的情況下,需要時間載入。

目錄:

目錄
  • Git——分支管理(2)
    • 提示:圖床在國外且動圖比較多的情況下,需要時間載入。
    • 目錄:
    • Git基礎
      • Git的分支與HEAD
        • Git的儲存機制
        • Git的分支指標
      • Git的遠端倉庫
      • Git的遠端分支管理
    • 遠端分支和本地倉庫的衝突處理
      • 檢視當前分支情況

[lab.magiconch.com][福音戰士標題生成器]-1695021721029

Git基礎

關於Git的基礎請參考上一篇基礎的教程,這裡主要是關於Git的分支管理的內容,在講解完成之後會以實戰的形式讓各位更好的理解如何使用Git這一個強大的工具。

Git的分支與HEAD

Git的儲存機制

當你發起提交的時候,Git儲存的是提交物件,其中包含了指向暫存區快照的指標。

Git在倉庫區一般會這樣儲存物件:

graph LR A("提交物件") --> C("Tree物件") -->B("Blob1物件") C-->D("Blob2物件")
  • 提交物件,一次提交包含著後設資料以及指向專案根目錄的樹物件的指標,以便需要的時候重新建立這次快照。

    image.png
  • Blob 物件,只用於儲存單個檔案內容,一般都是二進位制的資料檔案,不包含任何其他檔案資訊,比如不包含檔名和其他後設資料,可以同時有多個。

  • Tree 物件,對應檔案系統的目錄結構,裡面主要有:子目錄 (tree),檔案列表 (blob),檔案型別以及一些資料檔案許可權模型等。

Git將檔案的快照儲存為物件,每個物件都有一個唯一的雜湊值。當檔案發生變化時,Git會計算新檔案的雜湊值,並將其作為新物件儲存在物件資料庫中。當Git檢測到兩個檔案版本相似時,它會計算差異(即兩個版本之間的不同之處),並將這些差異作為一個新的物件儲存。這個差異物件會引用原始檔案的雜湊值,以便可以重構原始檔案的內容。

Git的分支指標

Git的分支只不過是一個指向某次提交的輕量級的可以移動指標,Git預設的分支名是master,當你發起提交的時候就有了一個指向最後一次提交的master分支。每次提交時他都會自己向前移動。

舉個例子:

graph LR H("HEAD") -->G G("master") ==> Commit_C direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end Commit_B --> Commit_C

也就是說分支其實就是指標,指標指向某一次提交。其中比較特殊的是HEAD指標,是用來指向當前分支指標的指標,表示當前在master分支上。

Git的遠端倉庫

遠端分支是指遠端倉庫的分支指標,這些指標存在於本地且無法被移動,當你與伺服器進行網路通訊的時候,他會自動更新,也就是使用pull等命令的時候,遠端分支有點像書籤,會提示你上一次連線伺服器時遠端倉庫中每個分支的位置。

graph LR G("master") ==> Commit_C direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end Commit_B --> Commit_C H("remote") ==> Commit_C

remote指標同master指標一樣,都是一個指標而已。

Git的遠端分支管理

合併衝突

Fast-forward操作指的是當前分支所在位置是在已有要合併分支的後面,打個比方:

graph LR G("main") ==> Commit_B direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end Commit_B --> Commit_C H("orgin/main") ==> Commit_C

main節點落後於伺服器上的main(以後都稱之為origin/main),此時執行Fast-forward操作就是把伺服器的遠端分支合併到main之上,此時main指標會向前走一個,這樣的操作就是Fast-forward。

合併之後結果如下:

graph LR G("main") ==> Commit_C direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end Commit_B --> Commit_C H("orgin/main") ==> Commit_C

merge操作指的是在本地分支和遠端分支在同一個基點產生分歧的時候如下圖所示:

graph LR G("main") ==> Commit_B direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end Commit_A --> Commit_C H("orgin/main") ==> Commit_C

在你的第一次提交之後本地已有個第二次提交,遠端也有一個基於第一次提交的遠端提交,此時就需要merge將兩次提交合併成新的提交:merge_commit,來進行合併。

graph LR direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end subgraph merge_commit direction TB G("提交物件C") --> H("檔案物件C") end Commit_A --> Commit_B Commit_A --> Commit_C Commit_C --> merge_commit Commit_B --> merge_commit merge_commit --> J("orgin/main") merge_commit --> I("main")

Rebase操作指的是在上述情況之上直接將某個分支之上所有提交的更改在另一個分支上重現一遍。

為了Rebase需要先找到兩個要整合的分支的共同祖先,然後取得當前所在分支的每次提交引入的變更,並把這些變更儲存成檔案,這之後將當前分支重置為要整合到的分支,然後在該分支上依次引入之前儲存的每個更改。

在合併之前分支已成這個樣子:

graph LR G("main") ==> Commit_B direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C direction TB E("提交物件C") --> F("檔案物件C") end Commit_A --> Commit_C H("orgin/main") ==> Commit_C

當我們想把遠端分支的內容整合到main分支之上(就是將orgin/main rebase到 main)的話就會有如下情況,合併之後:

graph LR G("main") ==> Commit_B direction LR subgraph Commit_A direction TB A("提交物件A") --> B("檔案物件A") end subgraph Commit_B direction TB C("提交物件B") --> D("檔案物件B") end Commit_A --> Commit_B subgraph Commit_C+ direction TB E("提交物件C") --> F("檔案物件C") end Commit_B --> Commit_C+ H("orgin/main") ==> Commit_C+

可以看到這裡實際上變化成了Fast-forward所處理的情況,再次執行Fast-forward(merge)即可完成操作。

遠端分支和本地倉庫的衝突處理

檢視當前分支情況

檢視當前分支資訊:

(base) neo@NeoNeuxs:~/git_test/Essays$ git branch
* main
  test

加上引數v可以顯示當前每個分支的最新提交和其的提交資訊,加上a則會包括當前的遠端分支:

(base) neo@NeoNeuxs:~/git_test/Essays$ git branch -av
* main                dd0258f [ahead 3] Merge branch 'test'
  test                8eafb31 update git rm pycharm
  remotes/origin/HEAD -> origin/main
  remotes/origin/main e5d18d7 del copy

*代表了HEAD指標所處位置,也就是當前所在的分支。 update git rm pycharm就是對應的提交資訊。e5d18d7就是提交物件的雜湊值。

如果要檢視已併入當前的分支的所有分支資訊可以使用:

(base) neo@NeoNeuxs:~/git_test/Essays$ git branch -av --merged
* main                dd0258f [ahead 3] Merge branch 'test'
  test                8eafb31 update git rm pycharm
  remotes/origin/HEAD -> origin/main
  remotes/origin/main e5d18d7 del copy

相反的,顯示沒有併入分支的命令可以使用:

(base) neo@NeoNeuxs:~/Desktop/Essays$ git branch -av --no-merged
  remotes/origin/HEAD -> origin/main
  remotes/origin/main e5d18d7 del copy

但是以上的方式都不夠明顯展示分支的變化及歷史,所以推薦還是使用如下命令:

git log --graph --decorate --oneline --all

如圖展示了兩個本地的倉庫,其中二者的遠端倉庫是保持一致的,也就是說二者都有一樣的遠端資訊,但是本地卻差別很大,我們的工作就是儘量合併二者的內容並最佳化分支結構。

image-20240506012058410

在看到二者的分支圖的時候我們就能看到:

image-20240506012643030

簡單來說我們的解決方案如下

首先rebase倉庫A,將倉庫A的main分支rebase到遠端上然後提交到伺服器。

然後將遠端新的內容pull到倉庫B,再進行一次rebase然後將內容上傳到伺服器。最終再pull倉庫A、倉庫B、伺服器就可以實現同步。

(base) neo@NeoNeuxs:~/Desktop/Essays$ git rebase origin/main
Successfully rebased and updated refs/heads/main.

reabase之後:

image-20240506020430611

由於倉庫B還有部分內容沒有提交,這裡就再提交一次:

(base) neo@NeoNeuxs:~/git_test/Essays$ git commit -a -m "finished git 1"
[main e2fdc30] finished git 1
 1 file changed, 61 insertions(+), 6 deletions(-)

提交之後分支如下,本次提交併不影響合併方案。

image-20240506020817574
git pull --rebase
# 等價於:
git fetch origin/main
git rebase origin/main
image-20240506023005761

其中的分支變化如下:

image-20240506032249539

關於rebase的變化的解釋:

Git會將該合併提交的更改拆分成多個單獨的提交,並將它們重新應用在遠端分支的頂部。這意味著原始的合併提交(在這個例子中是 dd0258f)不再存在於變基後的分支歷史中。

在git的變基過程遵循如下方式,在兩個分支的共同祖先開始提取被變基分支(master)上的有效修改,然後將被變基分支的有效更改指向基分支(orgin/master)的最新提交。

245df5e提交和e5d18提交完全一致,並不是有效提交,所以被捨棄。

image-20240506025758663

需要注意的是,沒有手動推送上圖中test分支,伺服器上實際上是沒有245df5e,8eafb31的資訊的。

參考:精通Git(第二版)

相關文章