0x000 導讀
講git
的文章很多,但是大部分都是一個套路,講概念,講命令,太多的概念和命令總是讓人有一種稀裡糊塗的感覺,講的很對,但似乎沒能講透,沒有醍醐灌頂的感覺,大概是我的悟性差吧。所以這幾天一直在做各種git
的實驗,並且閱讀了一些部落格、文件、資料,綜合下來整理出了這麼一篇文章。注意:
- 本篇文章旨在說明我對
git
的理解,只是一家之言,聊以分享。 - 本片文章不是一篇命令教程,或者概念說明,需要一定的
git
使用經驗和踩坑經驗。 - 為了閱讀方便,
commitID
只保留4位
0x001 總結[提前]
這是一篇比較亂七八糟的文章,不從傳統出發,嘗試用自己的思想去理解git
這一神奇的工具。以前我覺得git
是命運石之門
,我們在不同的時間線(分支)上跳躍,所有的事件都必須且只能發生在時間線上。但是現在我覺得git
是無限的可能性的集合
,一個事件可以引申出無限的可能性。而我們要做的是用工具(branch、tag、reset、rebase、merge....)來組織這些可能性,從而構成一個有序的、向前發展的歷史,引導整個歷史的發展,構建可以走向未來的工程。
0x002 存檔和讀檔
-
存檔
其實吧,版本就是存檔,就是遊戲中的存檔,我們不斷的推進專案,完成一個又一個任務,然後中途不斷的存檔,就形成了版本迭代。而版本多了,自然就需要管理了,自然就有了版本管理系統。
在遊戲中,存檔可以手動存檔,也可以到指定點存檔,也可以自動定場景存檔。在遊戲中,存檔之後形成的東西叫做檔案,而在
git
中,叫做commit
。我們可以使用git add
+git commit
完成一個檔案的建立,或者說版本的建立。一個
commit
擁有許多的屬性,比如ID
、Message
、Date
、Author:
等等,這些資訊都有助於我們瞭解這個版本,就像遊戲中的存檔會以關卡名/圖片之類的資訊來提示你這個存檔所代表的進度,比如使用git log
可以獲取以下資訊:commit 4963 (HEAD -> master) Author: ********** Date: Thu Jan 10 15:22:12 2019 +0800 版本H commit 1a42 Author: ********** Date: Thu Jan 10 15:25:01 2019 +0800 版本G commit 931b Author: ********** Date: Thu Jan 10 15:24:50 2019 +0800 版本F .... 複製程式碼
-
讀檔
既然有存檔,那就有讀檔。遊戲中直接選擇一個檔案就行了,那
git
中呢?如果有視覺化操作工具,那我們直接點選也是可以的,但現在不使用工具,而使用命令列,該如何呢。讀取一個存檔說白了在git
中就是讀取一個commit
而已,所以我們可以使用git checkout
和git reset
兩個命令來做到,那如何指定版本呢?前面提到的commit
屬性中的ID
可以幫我們實現這個目標。- 環境說明:我在倉庫中
git commit
了8個,每個commit
都新增了一個從a-h
的檔案,並在commit
資訊中新增版本標記 - 使用
git checkout
切到版本A,可以發現,此時只有檔案a$ git checkout 401e Note: checking out '401e'. ... HEAD is now at 401e1b6 版本A $ ls README.md a.txt 複製程式碼
- 使用
git reset
切換到版本G,可以發現,此時有了a-g
幾個檔案了$ git reset 1a42 Unstaged changes after reset: D b.txt D c.txt D d.txt D e.txt D f.txt D g.txt $ git stash Saved working directory and index state WIP on (no branch): 1a4268d 版本G l$ ls README.md a.txt b.txt c.txt d.txt e.txt f.txt g.txt 複製程式碼
- 環境說明:我在倉庫中
-
總結: 我們通過
commit
的ID
屬性配合其他命令來達到了任意讀檔的目的,可以在各種版本中隨意穿梭,快樂的很啊。而讀檔的姿勢其實還有很多,但不外乎是對commit
操作方式的不同,在git
中,我覺得commit 才是構成整個版本管理的基本栗子。每一個commit
都是獨立的個體,雖然和其他commit
存在著關聯,但是依舊是獨立的,而我們在commit
構成節點序列中來回移動和操作,就可以達到所有對版本管理的目的。
0x003 別名系統
在上一個章節中,我們已經可以依靠一些命令和commit ID
做到在版本中自由的穿梭,但是卻帶來一個問題,那就是commit ID
的記憶難度。commit ID
是hash
值,儘管git
支援只提供前幾位就能匹配到hash
,並且也提供了commit message
來說明commit
,但是依舊存在commit
的辨識和記憶問題。而這個問題,可以通過別名系統
來解決。
所謂的別名系統
,其實是我自己歸納的概念,指的其實就是HEAD
、branch
、tag
這三個東西。在我看來,這三個東西都是一樣的東西,都是別名,也就是標記一個commit
的東西而已,只是在行為表現上有一些區別。
-
HEAD
一個倉庫只有一個
HEAD
,指向你當前所在的commit
。如果你建立了一個commit
,HEAD
將會指向這個新的commit
。也可以通過命令,強制HEAD
指向某個commit
,比如reset
、checkout
。也就是不論你在哪個commit
之上,那麼HEAD
就在哪兒,或者說,其實你能在哪個commit
,是通過修改HEAD
指向的commit
實現的。-
通過修改
HEAD
在各個版本之間旋轉跳躍
-
-
branch
一開始我覺得這個東西才是
git
的核心,因為建立專案的時候,我們就處於master
分支之上,並且我們在工作中,往往也是基於分支工作的。但是後來發現,分支在本質上毫無意義,並不需要真的基於branch
去工作,基於commit
就行了。而branch
只是提供了一個方式來管理這些commit。branch
和HEAD
相同點是隨著新的commit
的建立,branch
指向的commit
會不斷更新,當然前提是你需要在這個branch
所在的commit
上建立新的commit
。而branch
和HEAD
的不同點在於HEAD
只能有一個,branch
可以有多個。實驗一:用
branch
來實現切換版本- 目前的庫情況
$ git log --pretty=oneline 1a42 (HEAD) 版本G 931b 版本F 071d 版本E 0caa 版本D 7855 版本C 1295 版本B 401e 版本A 複製程式碼
- 為版本
A-G
分別建立一個分支$ git checkout 1a42 -b G Switched to a new branch 'G' $ git checkout 931b -b F Switched to a new branch 'F' $ git checkout 071d -b E Switched to a new branch 'E' $ git checkout 0caa -b D Switched to a new branch 'D' $ git checkout 7855 -b C Switched to a new branch 'C' $ git checkout 1295 -b B Switched to a new branch 'B' $ git checkout 401e -b A Switched to a new branch 'A' $ git log --pretty=oneline 1a42 (HEAD -> G) 版本G 931b (F) 版本F 071d (E) 版本E 0caa (D) 版本D 7855 (C) 版本C 1295 (B) 版本B 401e (A) 版本A 複製程式碼
- 接下來就可以換一種方式在版本之間跳躍了,並且不需要記住或者查詢冗長的
commit ID
$ git checkout A Switched to branch 'A' $ git checkout B Switched to branch 'B' $ git checkout C Switched to branch 'C' $ git checkout E Switched to branch 'E' $ git checkout F Switched to branch 'F' $ git checkout G Switched to branch 'G' 複製程式碼
實驗二:分支跟隨新的
commit
- 當前庫的情況,注意:這裡的
HEAD -> G
表示HEAD
指向了branch G
,而branch G
指向了版本G
$ git log --pretty=oneline 1a42 (HEAD -> G) 版本G 931b (F) 版本F 071d (E) 版本E 0caa (D) 版本D 7855 (C) 版本C 1295 (B) 版本B 401e (A) 版本A 複製程式碼
- 新增一個檔案,建立一個
commit
$ echo 'h'> h.txt $ git add h.txt $ git commit -m '版本H' [G d346d27] 版本H 1 file changed, 1 insertion(+) create mode 100644 h.txt 複製程式碼
- 此時檢視
log
,可以看到HEAD
和G
都指向了版本H
,就是所謂的branch
跟著commit
動,但是它真的是跟著commit
動嗎?$ git log --pretty=oneline d346 (HEAD -> G) 版本H 1a42 版本G 931b (F) 版本F 071d (E) 版本E 0caa (D) 版本D 7855 (C) 版本C 1295 (B) 版本B 401e (A) 版本A 複製程式碼
實驗三:分支跟著啥動
- 將
HEAD
指向版本G
的commit
,而不是分支G
,也就是使用git checkout commitID
,而不是使用git checkout branchName
,可以看到,此時HEAD
不指向G
,而是HEAD
和G
同時指向了版本H
的commit
。$ git checkout d346 # 版本 H 的 commitID $ git log --pretty=oneline d346 (HEAD, G) 版本H 1a42 版本G 931b (F) 版本F 071d (E) 版本E 0caa (D) 版本D 7855 (C) 版本C 1295 (B) 版本B 401e (A) 版本A 複製程式碼
- 繼續建立一個
commit
,可以看到,這個時候分支G
不再跟著commit
移動了,所以,只有在HEAD
指向branch
的時候,branch
才會向前移動,也就是隻要HEAD
來到branch
身邊,branch
就會跟著HEAD
跑。$ echo 'i'> i.txt $ git add i.txt $ git commit -m "版本I" [detached HEAD 2e836eb] 版本I 1 file changed, 1 insertion(+) create mode 100644 i.txt $ git log --pretty=oneline 2e83 (HEAD) 版本I d346 (G) 版本H 1a42 版本G 931b (F) 版本F 071d (E) 版本E 0caa (D) 版本D 7855 (C) 版本C 1295 (B) 版本B 401e (A) 版本A 複製程式碼
- 目前的庫情況
-
tag
tag
是比較特殊的一個別名型別,他無法移動,或者說不推薦移動。一旦一個tag
和指向某個coimmit
,就不希望它移動,因為tag
就是用來標記這個commit
的,他是一個孤獨而忠誠的守望者,而不像branch
,花間游龍似的浪子。- 現在庫的情況
$ git log --pretty=oneline 1a42 (HEAD, G) 版本G 931b (F) 版本F 071d (E) 版本E 0caa (D) 版本D 7855 (C) 版本C 1295 (B) 版本B 401e (A) 版本A 複製程式碼
- 為每個版本新增一個
tag
,為了區別分支名,統統加了個T
$ git tag TA A $ git tag TB B $ git tag TC C $ git tag TD D $ git tag TE E $ git tag TF F $ git tag TG G $ git log --pretty=oneline 1a42 (HEAD, tag: G, G) 版本G 931b (tag: TF, F) 版本F 071d (tag: TE, E) 版本E 0caa (tag: TD, D) 版本D 7855 (tag: TC, C) 版本C 1295 (tag: TB, B) 版本B 401e (tag: TA, A) 版本A 複製程式碼
- 現在又多了一種旋轉跳躍的方式了
$ git checkout TA Previous HEAD position was 1a4268d 版本G HEAD is now at 401e1b6 版本A $ git checkout TB Previous HEAD position was 401e1b6 版本A HEAD is now at 1295260 版本B $ git checkout TC Previous HEAD position was 1295260 版本B HEAD is now at 7855905 版本C $ git checkout TD Previous HEAD position was 7855905 版本C HEAD is now at 0caa2b7 版本D $ git checkout TE Previous HEAD position was 0caa2b7 版本D HEAD is now at 071d00a 版本E $ git checkout TF Previous HEAD position was 071d00a 版本E HEAD is now at 931b3c9 版本F $ git checkout TG Previous HEAD position was 931b3c9 版本F HEAD is now at 1a4268d 版本G 複製程式碼
- 總結
所以,不管是
HEAD
、tag
、branch
,都是一種別名,除了行為表現上的差別,沒有太大的不同,特別是branch
和tag
,不過都只是提供了一種管理commit
的方式。
- 現在庫的情況
0x004 分叉
在上一章節中,我們揭開了別名系統的紅蓋頭,這一章,我們就開始探索一下分叉的神祕。
和遊戲中的存檔一樣,有時候一個遊戲有許多的選擇,這些選擇指向了不同的結果。而作為遊戲玩家,我們希望能夠走完所有的選擇,以探索更多的遊戲樂趣。所以我們會在做選擇的時候存檔,而當我們走完一個選擇,就會讀取這個存檔,繼續往另一個選擇探索。這個時候,就產生了兩個不同的劇情走向,這就是分叉。
在git
中,其實我們可以有無數的選擇,每一個commit
可以建立無數的commit
,就會引申出無數的可能。
- 我們遇到了一個抉擇,所以需要建立版本,暫時稱為
版本X
吧$ git log --pretty=oneline 2cae (HEAD) 版本X .... 複製程式碼
- 然後我們選擇了走
Y
,並且沿著Y1
一直走到Y3
,這是盡頭$ git log --pretty=oneline d2e0 (HEAD) 版本Y3 4ca8 版本Y2 fcff 版本Y1 2cae 版本X ... 複製程式碼
- 接著我們返回
X
,並選擇另一個選擇Z
,從Z1
走到Z3
$ git checkout 2cae # 切到`版本X` $ git log --pretty=oneline 16ff (HEAD) 版本Z3 0ca5 版本Z2 b4a7 版本Z1 2cae 版本X ... 複製程式碼
- 總結
可以看到,我們順著兩個選擇一直往下發展,在這發展的過程中,我們完全沒有用到tag
和branch
,也就是為了印證commit 是構成 git 世界的基本栗子這一說明。
從git log
中,我們看不到了Y
走向,那Y
真的消失了嗎?不是的,我們依舊可以通過Y
的commit ID
來尋回Y
的記錄。當然為了方便在YZ
中切換,我們可以使用branch
來標記一下YZ
兩個走向,這樣就形成了YZ
兩個branch
了,也就是分叉!
那那些沒有被branch
或者tag
標記的commit
呢?他們會消失嗎?會,也不會。不會是因為不被標記的commit
將變成dangling commit
,我稱之為遊離的commit
,是git
中最孤獨的存在,只要我們知道commitID
,就會可喚回它。但是很大的可能是我們永遠不會記得這麼一個不被引用的commit
,所以我呼籲,善待每一個commit
。會是因為還是可能會被回收的,看這裡,git 也有 gc。
0x003 合併
和遊戲的存檔不同的是,git
中的版本可以合併,也就是說我可以在分支Y
中做完任務Y1
、Y2
、Y3
,然後分支Z
中完成任務Z1
、Z2
、Z3
,然後合併這兩個分支,結果回到了X
,但是卻完成了Y1-y3
、Z1-Z3
,並拿到了神器Y
和Z
,這讓boss
怎麼活?
-
實驗一:使用
merge
合併commit
- 建立
版本O
$ echo O >> o.txt $ git add o.txt $ git commit -m '版本O' [detached HEAD 478fa6d] 版本O 1 file changed, 1 insertion(+) create mode 100644 o.txt 複製程式碼
- 基於
版本O
建立版本P1
$ echo P >>p1.txt $ git add p1.txt $ git commit -m '版本P1' [detached HEAD a3ab178] 版本P1 1 file changed, 1 insertion(+) create mode 100644 p1.txt $ git log --pretty=oneline a3ab (HEAD) 版本P1 478f 版本O 複製程式碼
- 基於
版本O
建立版本P2
$ git checkout 478f # 版本O 的 commitID $ echo p2 >> p2.txt $ git add p2.txt $ git commit -m '版本P2' [detached HEAD cbccf52] 版本P2 1 file changed, 1 insertion(+) create mode 100644 p2.txt $ git log --pretty=oneline cbcc (HEAD) 版本P2 478f 版本O 複製程式碼
- 合併
版本P1
到版本P2
$ git merge a3ab # 版本P1 的 commitID $ git log --pretty=oneline 656a (HEAD) Merge commit 'a3ab' into HEAD cbcc 版本P2 a3ab 版本P1 478f 版本O 複製程式碼
- 建立
-
實驗三:使用
rebase
合併切換到
版本P2
,在版本P2
中使用rebase
$ git checkout cbcc # 版本P2 的 commitID .... HEAD is now at cbccf52 版本P2 $ git rebase a3ab # 版本P1 的 commitID First, rewinding head to replay your work on top of it... Applying: 版本P2 $ git log --pretty=oneline 3bd7 (HEAD) 版本P2 a3ab 版本P1 478f 版本O 複製程式碼
-
實驗四:使用
cherry-pick
合併- 切換到
版本O
,新建版本P3
$ echo 'p3'>> p3.txt $ git add p3.txt $ git commig -m '版本P3' git: 'commig' is not a git command. See 'git --help'. The most similar command is commit $ git commit -m '版本P3' [detached HEAD ae09e94] 版本P3 1 file changed, 1 insertion(+) create mode 100644 p3.txt $ git log --pretty=oneline ae09 (HEAD) 版本P3 478f 版本O 複製程式碼
- 切換到
版本P2
中使用cherry-pick
合併版本P3
的東西$ git checkout 3bd7 # 版本P2 的commitID ... HEAD is now at 3bd7820 版本P2 $ git cherry-pick ae09 # 版本P3 的 commitID [detached HEAD f9dfba2] 版本P3 Date: Sat Jan 12 11:35:27 2019 +0800 1 file changed, 1 insertion(+) create mode 100644 p3.txt $ git log --pretty=oneline f9df (HEAD) 版本P3 3bd7 版本P2 a3ab 版本P1 478f 版本O 複製程式碼
- 切換到
-
注意:合併中的衝突解決
合併的過程中可能會出現衝突,比如同時拿到神器
P1
、P2
,但是在P1
中賣掉了O
之前拿到的裝備S
,而在P2
中則為S
鑲上了寶石,那麼合併之後要怎麼處理?是賣掉S
?還是保留鑲寶石的S
?還是鑲了寶石再賣掉?深井冰啊!我不要面子的啊... 所以這裡就涉及到了合併的衝突解決,這裡不再贅述,不是我要講的內容。
0x005 變基
這個名詞讓人想入菲菲啊,每次專案新成員加入,總是會提醒他們注意要變基....
這裡不去說merge
和rebase
的愛恨情仇,只說一些rebase
的操作,用rebase
來整理commit
。
上面說到commit 是構成 git 世界的基本栗子,所以,我們需要掌握一些栗子
的操作方式
-
檢視
commit
,可以使用git log
,如果需要尋回忘記的commit
,可以使用reflog
來嘗試看看是否能夠找到$ git log --pretty=oneline 68de (HEAD -> X) Merge branches 'Y' and 'Z' into X 16ff (Z) 版本Z3 ... $ git reflog 23e799e (HEAD) HEAD@{0}: rebase -i (pick): 版本Z 01a10d6 HEAD@{1}: rebase -i (squash): 版本Y a15dd72 HEAD@{2}: rebase -i (squash): # This is a combination of 2 commits. b6f2ea3 HEAD@{3}: rebase -i (start): checkout 1004 f4c4ccc HEAD@{4}: rebase -i (abort): updating HEAD ... 複製程式碼
-
建立
建立使用
git add
+git commit
就行了$ echo error > error.txt $ git add error.txt $ git commit -m '一個錯誤的版本' [X bc90774] 一個錯誤的版本 1 file changed, 1 insertion(+) create mode 100644 error.txt $ git log --pretty=oneline bc90 (HEAD -> X) 一個錯誤的版本 68de Merge branches 'Y' and 'Z' into X ... 複製程式碼
-
更新上一個
commit
,直接使用git commit --amend
$ echo error2 >> error.txt $ git add error.txt $ git commit --amend // 這裡將開啟一個vim視窗 一個錯誤的版本 # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Date: Fri Jan 11 17:21:18 2019 +0800 # # On branch X # Changes to be committed: # new file: error.txt # // 儲存退出之後輸出 [X d5c4487] 一個錯誤的版本 Date: Fri Jan 11 17:21:18 2019 +0800 1 file changed, 2 insertions(+) create mode 100644 error.txt 複製程式碼
-
要更新歷史中的
commit
也是可以做到的,例如需要在版本X中加入檔案x1.txt
,要使用互動式模式git rebase -i 2e83 # 指向 版本X 的前一個 commit 複製程式碼
此時將開啟一個互動式視窗
pick 913b571 版本X pick 0eca5e3 版本Y1 pick 33a9ca3 版本Y2 pick b95b3ca 版本Y3 pick 839c481 版本Z1 pick 6fb6cb3 版本Z2 pick c28d3e0 版本Z3 ... 複製程式碼
將
版本X
前面的pick
改為e
或者edit
,儲存,然後退出,這個時候,倉庫將會回到版本X
的狀態,並輸出Stopped at 913b571... 版本X You can amend the commit now, with git commit --amend Once you are satisfied with your changes, run git rebase --continue 複製程式碼
新增檔案
x1
$ echo x1 > x1.txt $ git add x* $ git commit --amend // 開啟互動式視窗可以修改 commit message $ git rebase --comtinue Successfully rebased and updated detached HEAD. 複製程式碼
此時又回會到原本的版本並且多出了檔案
x1
,就像是在版本X
中就已經加入一樣 -
插入一個新的
commit
,上面的栗子中不使用--amend
,就會在X
和Y1
之間插入一個新的commit
$ git rebase -i 2e83 // 互動式視窗,吧`pick`改為`e` $ echo x2 > x2.txt $ git add x2.txt $ git commit -m '插入一個版本X2' [detached HEAD 1b4821f] 插入一個版本X2 1 file changed, 1 insertion(+) create mode 100644 x2.txt $ git rebase --continue Successfully rebased and updated detached HEAD. $ git log --pretty=oneline 30a5 (HEAD) 版本Z3 4b00 版本Z2 cc1d 版本Z1 595e 版本Y3 4456 版本Y2 b6f2 版本Y1 1b48 插入一個版本X2 1004 版本X 複製程式碼
-
刪除
刪除一個分支可以使用互動式
rebase
,命令:git rebase -i commitID
,這裡的commitID
必須是你要刪除的commit
的前一個commit
。$ git rebase -i 68de 複製程式碼
此時將會開啟一個
vim
視窗pick bf2c542 版本Y1 pick 588feec 版本Y2 pick 1b2ae37 版本Y3 pick 38f7cf3 版本Z1 pick 080e442 版本Z2 pick 206a7ae 版本Z3 pick 6b01f70 一個錯誤的版本 # Rebase 2caeda3..6b01f70 onto 2caeda3 (7 commands) # # Commands: # p, pick = use commit # r, reword = use commit, but edit the commit message # e, edit = use commit, but stop for amending # s, squash = use commit, but meld into previous commit # f, fixup = like "squash", but discard this commit's log message # x, exec = run command (the rest of the line) using shell # d, drop = remove commit ... 複製程式碼
要刪除
一個錯誤的版本
,將前面的pick
改為d
或者drop
d 6b01f70 一個錯誤的版本 複製程式碼
儲存退出,輸出
Successfully rebased and updated refs/heads/X. 複製程式碼
-
合併多個
commit
,比如合併Z1-Z3
,開啟互動式視窗之後,將Z2
、Z3
的pick
改為s
$ git rebase -i 100468330c7819173760938d9e6d4b02f37ba001 // 開啟了互動式視窗 pick bf2c542 版本Y1 pick 588feec 版本Y2 pick 1b2ae37 版本Y3 pick 38f7cf3 版本Z1 s 080e442 版本Z2 s 206a7ae 版本Z3 複製程式碼
儲存退出以後,又開啟互動式視窗,顯示要合併的
commit
的message
,這裡可以修改commit
。# This is a combination of 3 commits. # This is the 1st commit message: 版本Z1 # This is the commit message #2: 版本Z2 # This is the commit message #3: 版本Z3 複製程式碼
這裡修改為
Z
,儲存,退出,輸出,可以看到,Z1-Z3
消失了,取而代之的是Z
,對Y1-Y3
做操作detached HEAD f4c4ccc] 版本Z Date: Fri Jan 11 16:27:00 2019 +0800 1 file changed, 3 insertions(+) create mode 100644 z.txt Successfully rebased and updated detached HEAD. $ git log --pretty=oneline f4c4 (HEAD) 版本Z 595e 版本Y3 4456 版本Y2 b6f2 版本Y1 $ git rebase -i 1004 [detached HEAD 01a10d6] 版本Y Date: Fri Jan 11 16:24:37 2019 +0800 1 file changed, 3 insertions(+) create mode 100644 y.txt Successfully rebased and updated detached HEAD. $ git rebase --continue 複製程式碼
-
重新排序
commit
順序,比如重排版本Y
和版本Z
,交換一下順序就好了$ git log --pretty=oneline 23e7 (HEAD) 版本Z 01a1 版本Y $ git rebase -i 1b48 複製程式碼
這時候開啟互動式視窗,顯示
pick a1942a3 版本Y pick eeabc6c 版本Z 複製程式碼
將它交換順序,儲存,退出
pick eeabc6c 版本Z pick a1942a3 版本Y 複製程式碼
檢視結果
Successfully rebased and updated detached HEAD. $ git log --pretty=oneline a194 (HEAD) 版本Y eeab 版本Z 複製程式碼