C74–1.透過 Learngitbranching_Git 學習概念

· 基礎篇
1. Git commit
2. 建立 git branch
3. Git merge
4. Git rebase
· 進階篇
5. 分離 HEAD
6. 相對引用(^)
7. 相對引用二(~)
8. 在 git 中取消修改
· 自由修改提交樹
9. 介紹 cherry-pick
10. 互動式的 rebase
· 活用 git 的指令
11. 只取一個 commit
12. commit 的戲法
13. commit 的戲法 #2
14. Git tag
15. Git describe
· 進階主題
16. N次Rebase
17. 多個 parent commit
18. Branch Spaghetti

本來是直接跟著youtube的影片直接開專案搭配Terminal練習。雖然跟著練蠻順的,但還是覺得要先把一些基礎的指令跟概念搞懂會比較好 XD。

而透過這種視覺化的樹狀分支圖來練習比在 Terminal看指令要來的直觀,更能清楚指令在幹嘛,而且可以多方測試看不同的變化是否跟自己設想的一樣。

  • 參考

基礎篇

1. Git commit

1. git commit 是對當前項目狀態的快照,每個提交基於前一個提交進行變更。
2. Git 通過存儲變化量(delta)來節省空間,而不是每次都複製整個目錄。
3. 每個提交都有一個 parent commit,形成一個提交歷史紀錄。
  • 目標
輸入兩次 commit 就可以
左:原先狀態 / 右:兩次commit
## 原先已經有初始狀態 C0 是第一個提交,記錄了所有文件的快照。以及 C1 基於 C0 來變更。

git commit ## C2 基於 C1 進行變更,並提交。
git commit ## C3 基於 C2 進行變更,並提交。
  • 補充
  • 補充:撰寫Commit訊息的內容建議

2. 建立 git branch

1. Branch 是什麼:
- 它只是指向某個 commit 的引用,非常不佔空間。

2. 經常建立 branch:
- 可以更好地管理不同的工作,避免將所有更改都集中在一個 branch 上。

3. Branch 和 Commit 的關係:
- branch 指向某個 commit,並包含該 commit 及其所有 parent commit。

4. 切換分支:
- 使用 git checkout [name] 切換到新的分支。
- 或者使用 git checkout -b [name] 創建並切換到新的分支。
  • 目標
建立一個叫 bugFix 的新的 branch,然後切換過去。
  • 先 branch 在 checkout
左:原先狀態checkout main / 中:建立branch bugFix / 右:切換到 bugFix branch
## 一開始只有 main branch 和 commit C0、C1:

git branch bugFix ## 建立新的分支 bugFix
git checkout bugFix ## 由於此時還在 main 分支,因此切換到 bugFix 分支
  • 使用 git checkout -b 創建並切換到新分支
同時創建並切換到 bugFix 分支。
  • 補充

3. Git merge

  • Branch 和 Merge
1. Branch 讓你可以在分支上進行獨立的開發工作。

2. Merge 是將兩個不同的 branch 合併的操作,這樣可以將新功能或修復合併回主分支(main)。
  • Merge 的工作原理
1. git merge 會產生一個特殊的 commit,該 commit 有兩個唯一的 parent commit。

2. 如果一個 commit 有兩個 parent commit,表示該 commit 包含了這兩個 parent commit
及其所有 ancestor commit 的變更。
  • 目標
1. 建立新的 branch,叫做 bugFix
2. 用 git checkout bugFix 切換到 bugFix branch
3. commit 一次
4. 用 git checkout 切換回 main branch
5. 再 commit 一次
6. 用 git merge 將 bugFix merge 到 main
  • 第一組
左:原先checkout main / 中:建立bugFix > commit > checkout main / 右:merge bugFIx
checkout -b bugFix      ## 同時創建並切換到 bugFix branch。
git commit ## 在 bugFix branch 上進行一次 commit
git checkout main ## 切換回 main branch
git merge bugFix ## 將 bugFix branch merge 到 main branch 得到 C4
  • 第二組
將 bugFix 分支的更改合併到 main 分支後,再將 main 分支的更改合併回 bugFix 分支,
確保兩個分支都包含對方的更改。
main 分支的更改合併回 bugFix 分支
  • 差異
## 第一組
最後一步是將 bugFix 分支合併到 main 分支。
這意味 main 分支會包含 bugFix 分支的所有更改,但 bugFix 分支不會包含 main 分支的更改。

## 第二組
在合併 bugFix 到 main 之後,還進行了反向合併,即將 main 分支的更改合併回 bugFix 分支。
這樣 bugFix 分支也會包含 main 分支的所有更改。
  • 補充

4. Git rebase

  • Rebase 是什麼
1. Rebase 是另一種合併分支的方法。
它會取出一連串的 commit,"複製"它們,然後把它們接在另一個基礎提交之上。

2. Rebase 的優點是可以建立更線性的 commit history,使得 commit log 更加簡潔好看。
  • Rebase 的作用
1. 使用 rebase,可以將某個分支上的修改移動到另一個分支的最前端。
創建一個更線性、更簡潔的歷史記錄。

2. Rebase 不會刪除原來的 commit,而是創建它們的副本,並將這些副本應用在新的基礎提交之上。
  • 目標
1. 建立 bugFix branch
2. commit 一次
3. 切換回 main branch 再 commit 一次
4. 再次切換到 bugFix branch,接著 rebase bugFix 這個 branch 到 main branch 上
  • 方式一
左:原先 checkout main / 中:checkout bugFix / 右:rebase main
git checkout -b bugFix   ## 建立 bugFix branch
git commit ## 在 bugFix branch 上 commit 一次
git checkout main ## 切換回 main branch
git commit ## 在 main branch 上 commit 一次
git checkout bugFIx ## 再次切換到 bugFix branch
git rebase main ## rebase bugFix 這個 branch 到 main branch 上
  • 方式二
rebase main bugFix
git checkout -b bugFix    
git commit
git checkout main
git commit
git rebase main bugFix ## rebase bugFix branch 到 main branch 上
  • 補充

進階篇

5. 分離 HEAD

  • HEAD 是什麼
1. 在 Git 中,HEAD 是一個指向當前檢出的 commit 的引用。它告訴我們目前所在的 commit。
2. HEAD 通常指向一個 branch 名稱,比如 main 或 bugFix。
  • HEAD 的作用
1. 當你進行 commit 操作時,HEAD 會指向最近一次的 commit。
2. 大多數 Git 指令會修改 HEAD 所指向的 commit。
  • 分離 HEAD (Detached HEAD)
分離 HEAD 是指 HEAD 指向一個特定的 commit,而不是一個 branch 的名稱。
  • 目標
從 bugFix 分離出 HEAD 並且讓它指向一個 commit。
通過 hash 值可以指定 commit。每個 commit 的 hash 值顯示在各自的圓圈中。
## 原先狀態 HEAD 指向 bugFix 分支,bugFix 分支指向 commit C4。
HEAD -> bugFfix -> C4

## 切換到特定的 commit(分離 HEAD)
git checkout C4

## 現在,HEAD 不再指向 bugFix 分支,而是指向特定的 commit C4。
HEAD -> C4
bugFix -> C4
  • 補充

6. 相對引用(^)

  • 相對引用是什麼
1. 在 Git 中,相對引用是一種用來方便地移動到不同 commit 的方法,
避免直接使用長且難記的 hash 值。

2. 相對引用可以從一個易於記憶的地方(比如說 branch 名稱或 HEAD)開始工作。
  • 相對引用的符號
^:向上一個 commit。
~<num>:向上多個 commit。
  • 目標
1. 切換到 bugFix 的 parent commit。這會分離出 HEAD。
2. 透過直接指定 hash 值的方式也可以過關,但是還是試試相對引用吧!
  • 方式一:切換到 commit C4 使用 HEAD 相對引用
git checkout C4      ## 原先在main,因此 chekcout C4( 或 checkout bugFix)
git checkout HEAD^ ## 使用 HEAD 相對引用,現在 HEAD 指向 C3
  • 方式二:切換到 bugFix 分支的 parent commit。
## 切換到 bugFix 分支的 parent commit。
## 指示 Git 將 HEAD 指向 bugFix 分支所指向的 commit 的 parent commit。

git checkout bugFix^
  • 補充

7. 相對引用二(~)

  • 波浪符號 ~ 的作用:
~ 符號用來在 commit tree 中向上移動多個 commit。這比使用多個 ^ 符號更加方便。
~ 符號後面可以跟一個數字,表示向上移動的 commit 數量。
  • 示例使用
HEAD~1 表示從 HEAD 向上移動一個 commit。
HEAD~4 表示從 HEAD 向上移動四個 commit。
  • Branch 強制移動
使用 -f 選項可以強制讓分支指向另一個 commit。

## 例如:
git branch -f main HEAD~3 將 main 分支強制移動到從 HEAD 向上數第三個 parent commit。
  • 目標
移動 HEAD,main 和 bugFix 到目標所示的位置。
git checkout HEAD~1 / git checkout C1 / git checkout HEAD^
git checkout HEAD~1        ## 將 HEAD 移動到當前 HEAD 的上一個 commit。
git branch -f main C6 ## 強制將 main 分支指向 C6。
git branch -f bugFix C0 ## 強制將 bugFix 分支指向 C0。
  • 補充

8. 在 git 中取消修改

  • 在 Git 中,主要有兩種方法來取消修改:git reset 和 git revert。
1. git reset:
- 將分支的參考點退回到上一個 commit,類似於"重寫歷史"。

2. git revert:
- 創建一個新的 commit,該 commit 引入了與要取消的 commit 相反的更改。
  • 目標
分別取消 local branch 和 pushed branch 上的最近的一次 commit。

記住 pushed 是一個 remote branch,local 是一個 local branch。
左:原先chekcout local / 中:local reset HEAD^ > C1 / 右:pushed revert HEAD > C2'
git reset HEAD^        ## 回退 local 分支到上一個 commit > C1
git checkout pushed ## 切換到 pushed 分支
git revert HEAD ## 撤銷 pushed 分支的最新提交
  • 補充:

自由修改提交樹

9. 介紹 cherry-pick

  • 移動 commit
1. 移動 commit 是指將某個 commit 複製到另一個位置,使其出現在新的位置上。
2. 這個操作在 Git 中非常靈活,可以根據需要重構 commit 的順序或位置。
  • git cherry-pick
1. git cherry-pick 是一個用來複製一個或多個 commit 並將它們應用到當前分支的指令。
2. 它可以將指定的 commit 從一個分支複製到另一個分支,而不影響其他 commit。
  • 當想要複製幾個 commit 並且接在你目前的位置(HEAD)下面時,這是一個非常直接的方式。
git cherry-pick <Commit1> <Commit2> <...>
  • 衝突處理:
1. 在進行 cherry-pick 操作時,如果遇到衝突,需要手動解決衝突。
2. 解決衝突後,使用 git add 將解決的文件添加到暫存區,然後使用
git cherry-pick --continue 繼續 cherry-pick 操作。
  • 提交順序:
cherry-pick 會按順序應用指定的 commit,所以請確保指定的 commit 順序正確,
以避免意外的變更。
  • 目標
只需要從三個 branch 複製幾個 commit 到 main 下面,你可以從視覺化的目標看到
我們需要哪些 commit。
當下是checkout main,所以直接 cherry-pick C3 C4 C7。

10. 互動式的 rebase

  • 互動式的 rebase 是什麼 (git rebase -i(interactive rebase))
git rebase -i(interactive rebase)是一種 rebase 方法。
它允許你在 rebase 之前檢查和修改 commit 的順序。
當你不知道需要哪些 commit 時,互動式的 rebase 可以讓你方便地選擇、忽略或合併 commit。
  • 使用場景
當你需要整理 commit 歷史,使其更具可讀性和邏輯性。
當你需要忽略或修改某些 commit 時。
  • 目標
使用互動式的 rebase,並且完成視覺化目標所表示 commit 的順序
左:checkout main / 中:rebase -i HEAD~4 重新排序 / 右:完成
## 打開一個互動式的界面,顯示從 HEAD 向上數四個 commit,可以在這個界面中對這些
commit 進行操作。

git rebase -i HEAD~4

(git rebase -i overHere 也可以)
  • 補充
  • 補充:cherry-pick 與 Git Interactive Rebase 差別

活用 git 的指令

11. 只取一個 commit

  • 問題背景:
1. 當你在一個分支(如 bugFix)中進行了多次 commit 來調試和修復 bug,
但其中一些 commit 包含了不需要的 debug 指令。

2. 現在你只想將其中一個特定的修復 commit 合併到主分支(main),而不包括那些
不需要的 debug 指令。
  • 解決方案:
1. 使用 git cherry-pick 可以選擇性地將特定的 commit 從一個分支複製到另一個分支。
2. 使用 git rebase -i 可以進行交互式 rebase,重新排列或選擇 commit。
  • 目標
隨意決定要選擇使用哪個指令,但是 bugFix 所指向的那個 commit 一定要可以被
main branch 包含到。
  • 方式一:git rebase -i
git rebase -i main      ## 使用 rebase -i main 在 bugFix 分支上重構 commit 
git rebase bugFix main ## 將bugFix分支的變更同步到 main 分支,使 main 包含 C4'變更
  • 方式二:git cherry-pick
## 使用 git cherry-pick 將 bugFix 分支的 C4 變更應用到 main 分支上,生成一個新的 C4'。
git checkout main
git cherry-pick bugFix
  • 為什麼方法一需要 git rebase bugFix main 而方法二不需要?
## 方法一
需要 git rebase bugFix main 是因為在第一步的互動式 rebase 過程中,bugFix 分支上
的 commit C4 被調整到 main 分支的基礎上,但是 main 分支的 HEAD 並沒有移動到 C4'。
透過第二步的 rebase,將 main 分支的 HEAD 移動到 C4'。

## 方法二
直接透過 cherry-pick 將 C4 應用到 main 分支,生成了新的 commit C4',
這一步操作已經完成了目標狀態的需求,不需要再進行額外的 rebase 操作。
  • 補充

12. commit 的戲法

  • 目標(禁用cherry-pick)
1. 先用 git rebase -i 將 commit 重新排序,然後把我們想要修改的 commit 移到最前面
2. 然後用 git commit --amend 來進行一些修改
3. 接著再用 git rebase -i 來將他們按照最開始的順序重新排好
4. 最後我們把 main 移到這個修改的最前端(用你自己喜歡的方法),就大功告成啦!

當然還有許多方法可以完成這個任務(cherry-pick),但現在暫時只注意上面這種方法。

最後所產生的 commit tree,因為我們把 commit 移動了兩次,所以會分別產生 (單引號) commit。
還有一個 apostrophe commit 是因為修改 commit 而加進來的。
左:原先在caption / 右:rebase -i 重新排序 > amend
左:再次重新排序 / 右:rebase
git rebase -i main        ## 將要修改的 commit 移到最前面:C3' C2'
git commit --amend ## checkout 為 C2' commit --amend 為 C2''
git rebase -i main ## 再次排序為 C2''' C3''
git rebase caption main ## 將 main 移到修改後的 commit C3''
  • 補充:git commit — amend

13. commit 的戲法 #2

  • git cherry-pick:從 commit tree 中選取一個 commit,並將它應用到當前的 HEAD,適用於不希望重新排列多個 commit 的情況。
  • 目標
在這一關和上一關一樣要去修改一個 commit 叫做C2,但要避免使用 rebase -i
左:原先在caption / 右:checkout main > cherry-pick
左:amend / 右:cherry-pick
git checkout main      // 切換到 main 分支
git cherry-pick C2 // 將 C2 的更改應用到 main 分支。這就會產生C2'
git commit --amend // 這樣會創建一個新的提交 C2'',代表修改過的 C2' 提交。
git cherry-pick C3 // 這會將 C3 提交應用到 main 分支。產生C3'
  • 結論:
通過使用 git cherry-pick,能夠靈活地移動和修改提交,而不需要進行多次的 rebase -i 操作。
這種方法能夠避免多次的衝突處理,並且能夠更容易地管理提交歷史。
通過創建臨時分支並應用所需的提交,就可以達到重新排列和修改提交的目的。

14. Git tag

  • 使用 git tag <tagname> <commit> 命令來創建一個 tag,標記特定的 commit。
  • 什麼是 Git Tag?
1. Git Tag 是一個用來標記特定 commit 的固定標記,它不會像分支那樣隨著新的 commit 移動。

2. Tag 通常用來標記重要的里程碑,比如軟體的版本釋出或重大 bug 修復。
  • 為什麼使用 Git Tag?
1. 分支會隨著新的 commit 而變動,而 tag 可以永遠指向特定的 commit,這讓它非常適合用
來標記特定的歷史時刻。

2. Tag 的存在就像是 commit tree 上的錨點,表示特定的訊息。
  • 目標
建立一個如視覺化目標裡面的 tag,然後 checkout 到 v1 上面,要注意你會進到
分離 HEAD 的狀態,這是因為你不能夠直接在 v1 上面做 commit。
左:原本checkout main / 中: tag v0 C1、tag v1 C2/ 右:checkout v1
git tag v0 C1      // 將 C1 tag v0
git tag v1 C2 // C2 tag v1
git checkout v1 // 移到 v1 tag

or

git tag v1 side~1
git tag v0 main~2
git checkout v1
  • 補充

15. Git describe

  • 什麼是 git describe
1. git describe 是一個用來顯示離你最近的錨點(也就是 tag)的指令。

2. 當你想知道某個 commit 距離最近的 tag 有多遠時,這個指令非常有用。
  • 使用場景:
1. 當你完成了一個 git bisect(找尋有 bug 的 commit 的指令),
需要確認找到的 commit 距離最近的 tag 有多遠。

2. 當你使用別人的電腦工作,需要知道你當前工作的位置距離最近的 tag 有多遠。
  • 基本使用方式:
## <ref> 是任何一個可以被 Git 解讀成 commit 的位置,
如果你沒有指定的話,Git 會以你目前所在的位置(HEAD)為準。

git describe <ref>
  • 輸出格式:
<tag>_<numCommits>_g<hash>

<tag>:表示的是離 <ref> 最近的 tag。
<numCommits>:表示這個 tag 離 <ref> 有多少個 commit。
<hash>:表示的是你所給定的 <ref> 所表示的 commit 的前七個 id。
  • 目標
git describe 查看。
git describe main      // v0_2_gC2
git describe side // v1_1_gC4
git describe bugFix //v1_2_gC6
  • 補充

進階主題

16. N次Rebase

  • 什麼是 rebase 多個 branch?
1. 當你有多個分支時,你可以通過 git rebase 將這些分支的工作整合到主分支(main)上。

2. 這樣可以保持一個有序的 commit history,使得最終的 commit history 看起來像是
按順序進行的。
  • 為什麼需要有序的 commit history?
1. 有序的 commit history 可以讓歷史記錄更加清晰易讀。

2. 當查看或追溯更改時,有序的 commit history 會更容易理解每個改動的意圖和順序。
  • 目標
## rebase 多個 branch
現在我們有很多 branch !讓我們做一下 rebase,將這些分支接到 main branch 上吧。

但是你的主管找了點麻煩,他們希望得到有序的 commit history,也就是我們最終的結果
是 C7' 在最下面,C6' 在它上面,以此類推。
左:原先狀態checkout main / 中:依序rebase / 右:rebase another main
## 將所有的分支依次基於前一個分支進行 rebase,最終再將 main 基於 another。

1. 線性歷史:
- 所有的提交都將被重寫並形成一條連續的歷史線,最終的提交歷史會很清晰,沒有分叉。

2. 避免合併提交:
- rebase 操作會避免生成(merge commit),這使得提交歷史更乾淨。
  • 補充

17. 多個 parent commit

  • 什麼是多個 Parent Commit?
1. 在 Git 中,一個 merge commit 可以有多個 parent commit。
這是因為它合併了來自多個分支的變更。

2. 當我們使用相對引用符號(如 ^ 和 ~)來移動時,可以選擇具體的 parent commit。
  • 選擇 Parent Commit
1. ^ 符號後面可以接一個數字,表示選擇哪一個 parent commit。

2. Git 預設會選擇第一個 parent commit。

3. 使用 ^2 表示選擇第二個 parent commit,以此類推。
  • 目標
在指定的目標位置上面建立一個新的 branch。
很明顯可以直接使用 commit 的 hash 值(比如 C6),但這邊要使用剛剛講到的相對引用的符號。
左:原先checkout main / 右:建立bugWork分支在C2
## 因為 checkout 就在 main,可以用 HEAD。
git branch bugWork HEAD~^2~ // 直接在指定的提交點,創建分支 bugWork。

or

git branch bugWork main~^2~
  • 明確的參考點(例子是 main) 與 HEAD
使用 main:
- 當希望指向一個穩定、已知的參考點時,使用 main 更加合適。

使用 HEAD:
- 在當前分支位置確定且短期內不會變動的情況下,使用 HEAD 也可以,但風險在於 HEAD
會隨分支切換而變化。
  • 補充

18. Branch Spaghetti

  • Branch Spaghetti
1. 當我們的主分支(main)相較於其他分支(one、two 和 three)有更多的 commit 時,
有時需要將主分支的變更應用到這些分支上。

2. 在這個過程中,我們可能需要重新排序、取消或完全重排某些 commit。
  • 目標
現在我們的 main branch 是比 one two 和 three 這三個 branch 多了幾個 commit。
由於某種原因,我們需要將 main 所新增的幾個 commit 套用到其它三個 branch 上面。

one branch 需要重新排序和取消 C5 這一個 commit,
two 需要完全重排,
hree 只需要再一個 commit。
左:原先checkout main / 中:checkout > cherry-pick / 右:branch -f
git checkout one                // 原先在main,因此checkout one
git cherry-pick C4 C3 C2 // 從 main 分支複製需要的 commit

git checkout two // 切換到 two分支
git cherry-pick C5 C4 C3 C2 // 重排 commit

git branch -f three C2 // three 分支:將分支重置到 C2
  • 補充

--

--

wei Tsao 學習紀錄
彼得潘的 Swift iOS / Flutter App 開發教室

Hi ! 我是wei , 先前未接觸過程式開發設計,想藉此來記錄自己的學習歷程,以利培養自己的程式邏輯 :)