[譯] Git:透過命令學概念 —— 第二部分

Mirosalva發表於2019-07-16

Git:透過命令學概念 —— 第二部分

用互動式的教程教你 Git 的原理,而非羅列常用命令。

所以,你想正確地使用 Git 嗎?

但你肯定不想僅僅學一些操作命令,你還想要理解其背後的原理,對吧?

那麼本文就是為你量身定做的!

讓我們快點開動吧!


本文的落筆點基於 Rachel M. Carmena 撰寫的 如何教授 Git 一文中提及的常規概念。

網上有很多重方法輕原理的 Git 教程,但我還是挖掘到了兼得二者的寶貴資源(也是本教程的靈感源泉),那就是 git BookReference page

因此,如果你讀完了本文還意猶未盡,就快點選上面兩個連結一探究竟吧!我真心希望本教程中介紹的概念,能幫你理解另外兩篇文章中詳解的其他 Git 功能

建議按照順序閱讀本系列文章:



合併

我們所有人一般都會工作在分支上,我們需要討論下如何通過合併來從一個分支上獲取變更到另一個分支上。

我們剛在 change_alice 分支上修改了 Alice.txt,我想說我們對所做的改變感到滿意。

如果你接著執行 git checkout master 命令,那麼我們在其他分支上建立的 提交 無法在此看到為了將變更弄到 master 分支,我們需要 合併 change_alice 分支 master 分支上。

注意:你總是將某個分支 合併 到當前分支。

快進合併

既然我們已經執行了 checked out 來切換到 master 分支,現在我們可以執行 git merge change_alice 合併命令。

由於 Alice.txt 並沒有其他衝突變更,我們在 master 分支未做修改,因此合併將在所謂的快進合併中進行。

在下面的圖表中,我們可以看到,這僅僅意味著:master 的指標會被簡單地前進到 change_alice 分支存在的位置。

第一張圖顯示了我們執行 合併 前的狀態,master 指標仍處於它之前的提交位置,同時另一個分支上我們又做了一次提交。

快進合併之前

第二張圖顯示了在我們 合併 之後發生了什麼變化。

快進合併之後

合併相異的分支

讓我們試一些更復雜的。

在 master 分支的 Bob.txt 檔案新行中新增一些文字,然後提交它。

接著執行 git checkout change_alice 命令,改變 Alice.txt 檔案並提交。

在下圖中,你可以看到我們的提交歷史現在的樣子。masterchange_alice 分支都源於同一個提交,但那之後它們發生了分歧 ,每個分支都有自己額外的提交。

不同的提交

如果你現在使用命令 git merge change_alice 來執行一個快進合併是不可能的了。取代它的是,你最愛的文字編輯器將會開啟,並且允許你修改 合併提交 操作的提交資訊,git 即將執行這個提交從而將兩個分支重新保持一致。你現在使用預設提交資訊就行。下圖顯示了我們在執行 合併 後的 git 歷史狀態。

合併分支

新的提交將我們在 change_alice 分支上的修改引入到 master 分支。

正如你之前記得那樣,git 中的修訂不僅僅是檔案的快照,還包含了它們來自何處的一些資訊。每次 提交 都包含一個或多個父級提交資訊。我們的新 合併 提交包含了 master 分支的最後提交,以及我們在另一個分支上的提交來作為這次合併的父級提交。

解決衝突

目前為止,我們的修改都沒有相互干擾。

讓我們介紹一種衝突,然後解決它。

建立一個新分支,然後將它 檢出。你知道如何操作,不過或許可以使用 git checkout -b 命令為你減少麻煩。

我把它命名為 bobby_branch

在這個分支上,我們會修改 Bob.txt 檔案。

第一行應該仍然是 Hi!! I'm Bob. I'm new here.,把它修改成 Hi!! I'm Bobby. I'm new here.

暫存檔案之後,在你再次 檢出 master 分支之前,提交 你的修改。在 master 分支我們將同一行修改為 Hi!! I'm Bob. I've been here for a while now.,接著 提交 該修改。

現在是將新分支 合併master 的時候了。

如果你嘗試這麼做,你將會看到如下的結果:

Auto-merging Bob.txt
CONFLICT (content): Merge conflict in Bob.txt
Automatic merge failed; fix conflicts and then commit the result.
複製程式碼

兩個分支都修改了同一行,此時 git 工具無法自己完全處理這種情況。

如果你執行 git status 命令,你會獲取到用來指導接下來如何繼續的所有常見幫助命令。

首先我們必須手動解決衝突。

對於像這個簡單衝突來說,你最喜愛的文字編輯器就夠用了。而對於合併含有很多變化的多個檔案來說,使用更強大的工具會讓你輕鬆不少,我建議選用包含版本控制工具,具有友好合並介面的你最喜愛的 IDE。

如果你開啟 Bob.txt 檔案,你會看到一些類似下面的內容(我已經截斷了之前可能放在第二行的其他內容):

<<<<<<< HEAD
Hi! I'm Bob. I've been here for a while now.
=======
Hi! I'm Bobby. I'm new here.
>>>>>>> bobby_branch
[...你在第 2 行傳入的隨便什麼內容]
複製程式碼

在上面你可以看到當前 HEAD 上 Bob.txt 發生的變化,在下面你可以看到我們正嘗試合併進來的分支所做的更改。

為了手工解決衝突,你只需要確保檔案最終保留一些合理內容,而不包含 git 引入檔案的特殊行。

所以繼續修改檔案為下面這種內容:

Hi! I'm Bobby. I've been here for a while now.
[...]
複製程式碼

從這裡開始,我們即將要做的事是適用於任何變更。

在我們執行 add Bob.txt 新增檔案後,暫存這些變更,然後執行 提交

我們已經瞭解為解決衝突所做的變更提交,就是合併過程中一直都有的合併提交

實際上你應該意識到在解決衝突的過程中,如果你不想繼續 合併 程式,你可以通過執行 git merge --abort 命令直接 中止 它。

變基

Git 有另外一種純淨方式來整合兩個分支的變化,叫做 變基

我們始終記得一個分支總是基於另外一個分支。當你建立它時,從某處開始分叉

在我們簡單的合併樣例中,我們從 master 分支的某次提交建立了一個分支,然後提交了在 masterchange_alice 分別提交了一些變更。

當一個分支相對於它所基於的分支產生了改變,如果你想要把最新的變更整合到你當前的分支,變基 提供了一種比 合併 更加純淨的處理方式。

正如你所看到的,一次 合併 引入了一個合併提交,這個過程中兩邊的歷史記錄得到整合。

很容易看得出,變基僅僅改變了你的分支所依賴的歷史記錄點(建立分支所基於的某次提交)。

為了嘗試這一點,我們首先將 master 分支再次檢出,然後基於它來建立/檢出一個新分支。

我稱自己的新分支為 add_patrick,然後我新增了一個新檔案 Patrick.txt,然後以資訊 “Add Patrick” 提交了該檔案。

在你為該分支新增了一條提交後,返回 master 分支,做一點修改然後提交它。我做的修改是為 Alice.txt 檔案多加了一些文字。

就像我們合併樣例中那樣,兩個分支有公共祖先,然而歷史是不同的,你可以從下圖中看出:

一次變基之前的歷史記錄

現在讓我們再執行 checkout add_patrick 命令,然後把 master 上做的修改獲取到我們正在操作的分支上!

當我們執行 git rebase master 命令,我們讓 add_patrick 分支重新以當前狀態的 master 分支做了基準。

上面這條命令為我們提供了目前操作的友好提示:

First, rewinding head to replay your work on top of it...
Applying: Add Patrick
複製程式碼

我們知道 HEAD 是我們所在的工作環境中當前提交的指標。

在變基操作執行之前,它的指向與 add_patrick 分支一致。發生了變基,它會首先移回到兩個分支的公共祖先,然後移動到我們想要定為基點的那個分支的當前頂點。

所以 HEAD 移動到 0cfc1d2 這次提交,然後到 7639f4b 這次提交,它是位於 master 分支的頂點。

然後變基操作會將我們在 add_patrick 分支上做的每一個提交都應用到那個頂點上。

為了更精確瞭解 gitHEAD 指標移回到分支的公共祖先過程中做了什麼,可以把你在被操作的分支上每次提交都儲存一部分(修改的 差異點、提交資訊、作者等等。)。

在上面操作之後,你正在變基的分支需要 檢出 最新的提交,然後把存下的所有變化以一條新提交應用到前面的提交之上。

所以在我們原先簡單的檢視中,我們認為在 變基 之後,0cfc1d2 這次提交不再指向它歷史中原公共祖先,而是指向 master 分支的頂部。

事實上,0cfc1d2 這次提交消失了,並且 add_patrick 分支以一個新提交 0ccaba8 為開始,它以 master 分支的最新提交作為公共祖先。

我們讓它看起來就像,add_patrick 分支是以當前 master 分支為基點,而不是分支的較舊版本,不過我們這樣做相當於重寫了該分支的歷史。

在本教程的末尾,我們會多學習一些重寫歷史以及什麼時候適宜和不適宜這麼做。

變基之後的歷史記錄

當你自己的工作分支是基於一個共享分支時,例如 master 分支,變基 是一種相當強大的工具。

使用變基操作,可以確保你能經常整合別人提交到 master 分支的變更和推送,並且保證一條幹淨線性的歷史,在你的工作文字需要引入到共享分支時,這種歷史可以做 快進合併

相對於包含合併提交的凌亂歷史,保持歷史的線性也可以使提交日誌更加有用(試一下 git log --graph,或者看一下 GitHubGitLab 的分支檢視)。

解決衝突

就像 合併 過程中,如果遇到兩次提交修改了一個檔案中同樣位置的內容塊,你可能會遇到衝突。

然而當你在 變基 過程中遇到衝突,你無需在額外的合併提交中解決它,卻可以在當前正在執行的提交中解決它。

同樣地,將你的修改直接以原始分支的當前狀態為基準。

事實上,你在 變基 過程中的解決衝突操作非常類似你在 合併 中的操作,所以如果你不太確定如何操作的話,可以回過頭檢視那個小節。

唯一的區別在於,由於你沒有引入合併提交,所以不需要你提交衝突解決結果。只需新增變更到暫存環境,然後執行 git rebase --continue 命令。衝突將會在剛剛執行的提交中得到解決。

當合並時,你一直都可以停止和丟棄目前你做的所有內容,通過執行 git rebase --abort 命令。

更新遠端變更到本地工作環境

目前為止,我們已經學習瞭如何生成和共享內容變更。

如果你是獨自工作,這些已經夠用了。但是通常我們是多人共同處理一項工作,並且我們想要從遠端倉庫以某種方式獲取他們的變更到我們自己的工作環境中。

由於已經過了一段時間,讓我們看一下 git 的元件:

git 元件

就像你的工作環境,每個工作在同一份原始碼的人都有他們自己的工作環境。

許多工作環境

所有這些工作環境都有它們自己的進行中暫存的變更,這些會在某個節點被 提交本地倉庫,最終 推送遠端倉庫

我們的例子中,我們會使用 GitHub 提供的線上工具,來模擬在我們工作時其他人對遠端倉庫生成的變更。

檢視你在 github.com 網上對這個倉庫的 fork 分支,開啟 Alice.txt 檔案。

找到編輯按鈕,通過網站來生成和提交一個變更。

github 編輯

在這個倉庫中,我已經在一個叫做 fetching_changes_sample 的分支上為 Alice.txt 檔案新增了一個遠端倉庫變更,但是在你的該倉庫版本,你當然可以直接改變 master 分支上這個檔案。

獲取更新

我們還記得,當你執行 git push 命令時,會將本地倉庫的變更同步到遠端倉庫

為了獲取遠端倉庫中的變更到本地倉庫,你可以使用 git fetch 命令。

這個操作獲取到遠端的任何變更到你的本地倉庫,包含提交和分支。

這點要注意,變更還沒有被整合到本地分支,更不用說工作空間暫存區域

獲取更新

如果你現在執行 git status 命令,你會看到 git 命令的另一個很棒的例子,告訴你現在正發生什麼:

git status
On branch fetching_changes_sample
Your branch is behind 'origin/fetching_changes_sample' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
複製程式碼

拉取更新

由於我們沒有工作中暫存的變更,我們現在可以執行 git pull 命令來從倉庫中拉取所有變更到我們的工作空間。

拉取會隱式地 獲取 遠端倉庫,但有時候單獨執行 獲取 是個好選擇。 例如,當你想要同步任何新的遠端分支,或者你想在像 origin/master 這種分支上執行 git rebase 之前,需要確保你的本地倉庫是最新的。

拉取更新

在我們 拉取 之前,讓我們本地修改一個檔案來看會發生什麼。

讓我們在工作空間再次修改 Alice.txt 檔案!

如果你現在嘗試做一次 git pull,你會看到如下錯誤:

git pull
Updating df3ad1d..418e6f0
error: Your local changes to the following files would be overwritten by merge:
Alice.txt
Please commit your changes or stash them before you merge.
Aborting
複製程式碼

你無法 拉取 任何變更,因為工作空間中有些檔案被修改,同時你正在拉取進來的提交也有這些檔案的變化。

這種情況的一種解決方法是,為了獲取你比較信任的某個點上的變更,在你最終提交它們之前,可以把本地變更 新增暫存空間。但是在你最終提交它們之前,而這是學習另一個很棒工具的一個好時機。

儲藏變更

在任何時刻如果你有一些本地變更,還不想放進一個提交中,或者想存在某個地方來以其他某種角度來解決一個問題,你可以將這些變更 儲藏 起來。

一次 git stash 基本上是一個變更的堆疊,這裡面你可以儲存對工作空間的任何變更。

最常用的命令是:git stash,它將對工作空間的任何修改儲藏起來。還有 git stash pop 命令,它拿到儲藏起來的最近修改,並將其再次應用到工作空間

就如堆疊命令的命名,git stash pop 命令在應用變更之前,將最近儲存的變更移除。

如果你想保留儲藏的變更,你可以使用 git stash apply 命令,這種方式不會在應用變更之前從儲藏中移除它們。

為了檢查你當前的 儲藏,你可以使用 git stash list 命令來列出各個單獨的條目,還可以使用 git stash show 命令來顯示 儲藏 中最近條目的變更。

另一個好用的命令是 git stash branch {BRANCH NAME},它從當前的 HEAD 開始建立一個分支,此時你儲藏了變更,並把它們應用到了新建分支中。

現在我們瞭解了 git stash 命令,讓我們執行它,用來從工作空間中移除我們對 Alice.txt 檔案做的本地變更,這樣我們就可以繼續上面的操作,執行 git pull 命令來拉取我們在網站上做的遠端變更。

在那之後,讓我們執行 git stash pop 命令來取回本地變更。

因為我們 拉取 的提交和 儲藏 的變更都修改了 Alice.txt 檔案,所以你需要解決衝突,就像在 合併變基 中你做的那樣。 完成 新增 後,提交這個變更。

包含衝突的拉取

現在我們已經理解如何 獲取拉取 遠端變更到我們的工作環境,正是製造一些衝突的時候!

不要推送那個修改 Alice.txt 檔案的提交,回到你位於 github.com遠端倉庫

這裡我們又要修改 Alice.txt 檔案並提交它。

現在實際上在我們的本地遠端倉庫之間存在兩處衝突。

不要忘了執行 git fetch 命令來檢視遠端的變更,而不是立即 拉取 它。

如果你現在執行 git status 命令,你會看到兩個分支各有一個與對方不同的提交。

git status
On branch fetching_changes_sample
Your branch and 'origin/fetching_changes_sample' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
複製程式碼

另外,我們已經在上面不同提交中修改了同一個檔案,為了介紹 合併 中的衝突這個概念,所以我們需要解決它。

當你執行 git pull 命令時,而本地遠端倉庫之間存在著差異,就會發生與 合併 兩個分支過程時同樣的事情。

額外的,你可以認為遠端倉庫本地倉庫分支之間的關係是一種從一個分支上建立另一個分支的特殊情況。

本地分支是基於你從遠端倉庫最近一次獲取的分支狀態的。

如果以這種方式思考,這兩種選項來獲取遠端倉庫變化就很有道理:

當你執行 git pull 命令,本地遠端倉庫的版本就會 合併。就像 合併 不同分支一樣,這會引入一個合併提交。

因為任何本地分支都基於它們各自的遠端版本,我們也可以對它執行 變基,這樣做的話我們在本地做的任何變更,都表現為基於遠端倉庫中的最新可用版本。

為了這麼做,我們可以使用 git pull --rebase 命令(或者簡寫git pull -r)。

變基這小節中已經詳細介紹了,保持一個乾淨線性的歷史提交記錄是有好處的,所以我才強烈建議當你需要執行 git pull 命令時,不妨使用 git pull -r 替代。

你也可以告訴 git 使用 變基 來代替 合併,作為你執行 git pull 命令時的預設策略,通過一個像這樣 git config --global pull.rebase true 的命令來設定 pull.rebase 標識。

在我介紹前面幾個段落之後,如果你還沒有執行過 git pull 命令的話,讓我現在一起執行 git pull -r 來獲取遠端變更吧,讓它顯得就像我們的新提交位於那些遠端變更之後。

當然就像一個正常的 變基(或者 合併)操作,你需要解決我們引入的衝突,以便 git pull 命令可以完成。

歡迎繼續閱讀本系列其他文章:

如果發現譯文存在錯誤或其他需要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可獲得相應獎勵積分。文章開頭的 本文永久連結 即為本文在 GitHub 上的 MarkDown 連結。


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章