Git學習-圖文並茂還有遊戲玩!

ML李嘉圖發表於2021-12-02

主要

基礎篇

循序漸進地介紹 Git 主要命令。

Git Commit

Git 倉庫中的提交記錄儲存的是你的目錄下所有檔案的快照,就像是把整個目錄複製,然後再貼上一樣,但比複製貼上優雅許多!

Git 希望提交記錄儘可能地輕量,因此在你每次進行提交時,它並不會盲目地複製整個目錄。條件允許的情況下,它會將當前版本與倉庫中的上一個版本進行對比,並把所有的差異打包到一起作為一個提交記錄。

Git 還儲存了提交的歷史記錄。這也是為什麼大多數提交記錄的上面都有父節點的原因 —— 我們會在圖示中用箭頭來表示這種關係。對於專案組的成員來說,維護提交歷史對大家都有好處。

關於提交記錄太深入的東西我們們就不再繼續探討了,現在你可以把提交記錄看作是專案的快照。提交記錄非常輕量,可以快速地在這些提交記錄之間切換!

Git Branch

Git 的分支也非常輕量。它們只是簡單地指向某個提交紀錄 —— 僅此而已。所以許多 Git 愛好者傳頌:

早建分支!多用分支!

這是因為即使建立再多的分支也不會造成儲存或記憶體上的開銷,並且按邏輯分解工作到不同的分支要比維護那些特別臃腫的分支簡單多了。

在將分支和提交記錄結合起來後,我們會看到兩者如何協作。現在只要記住使用分支其實就相當於在說:“我想基於這個提交以及它所有的父提交進行新的工作。”

Git Merge

太好了! 我們已經知道如何提交以及如何使用分支了。接下來我們們看看如何將兩個分支合併到一起。就是說我們新建一個分支,在其上開發某個新功能,開發完成後再合併回主線。

我們們先來看一下第一種方法 —— git merge。在 Git 中合併兩個分支時會產生一個特殊的提交記錄,它有兩個父節點。翻譯成自然語言相當於:“我要把這兩個父節點本身及它們所有的祖先都包含進來。”


Git Rebase

第二種合併分支的方法是 git rebase。Rebase 實際上就是取出一系列的提交記錄,“複製”它們,然後在另外一個地方逐個的放下去。

Rebase 的優勢就是可以創造更線性的提交歷史,這聽上去有些難以理解。如果只允許使用 Rebase 的話,程式碼庫的提交歷史將會變得異常清晰

在提交樹上移動

在接觸 Git 更高階功能之前,我們有必要先學習在你專案的提交樹上前後移動的幾種方法。

一旦熟悉瞭如何在 Git 提交樹上移動,你駕馭其它命令的能力也將水漲船高!

我們首先看一下 “HEAD”。 HEAD 是一個對當前檢出記錄的符號引用 —— 也就是指向你正在其基礎上進行工作的提交記錄。

HEAD 總是指向當前分支上最近一次提交記錄。大多數修改提交樹的 Git 命令都是從改變 HEAD 的指向開始的。

HEAD 通常情況下是指向分支名的(如 bugFix)。在你提交時,改變了 bugFix 的狀態,這一變化通過 HEAD 變得可見。

如果想看 HEAD 指向,可以通過 cat .git/HEAD 檢視, 如果 HEAD 指向的是一個引用,還可以用 git symbolic-ref HEAD 檢視它的指向。

高階篇

要開始介紹 Git 的超棒特性了,快來吧!

分離HEAD

git ckeckout c4

相對引用

通過指定提交記錄雜湊值的方式在 Git 中移動不太方便。在實際應用時,並沒有像本程式中這麼漂亮的視覺化提交樹供你參考,所以你就不得不用 git log 來查檢視提交記錄的雜湊值。

並且雜湊值在真實的 Git 世界中也會更長(譯者注:基於 SHA-1,共 40 位)。例如前一關的介紹中的提交記錄的雜湊值可能是 fed2da64c0efc5293610bdd892f82a58e8cbc5d8。舌頭都快打結了吧...

比較令人欣慰的是,Git 對雜湊的處理很智慧。你只需要提供能夠唯一標識提交記錄的前幾個字元即可。因此我可以僅輸入fed2 而不是上面的一長串字元。

相對引用非常給力,這裡我介紹兩個簡單的用法:

  • 使用 ^ 向上移動 1 個提交記錄
  • 使用 ~ 向上移動多個提交記錄,如 ~3
git checkout bugFix^
    
git branch -f main c6
git branch -f bugFix c0
git checkout c1

撤銷變更

在 Git 裡撤銷變更的方法很多。和提交一樣,撤銷變更由底層部分(暫存區的獨立檔案或者片段)和上層部分(變更到底是通過哪種方式被撤銷的)組成。我們這個應用主要關注的是後者。

主要有兩種方法用來撤銷變更 —— 一是 git reset,還有就是 git revert。接下來我們們逐個進行講解。

Git Reset

git reset 通過把分支記錄回退幾個提交記錄來實現撤銷改動。你可以將這想象成“改寫歷史”。git reset 向上移動分支,原來指向的提交記錄就跟從來沒有提交過一樣。

Git Revert

雖然在你的本地分支中使用 git reset 很方便,但是這種“改寫歷史”的方法對大家一起使用的遠端分支是無效的哦!

到現在我們已經學習了 Git 的基礎知識 —— 提交、分支以及在提交樹上移動。 這些概念涵蓋了 Git 90% 的功能,同樣也足夠滿足開發者的日常需求

然而, 剩餘的 10% 在處理複雜的工作流時(或者當你陷入困惑時)可能就顯得尤為重要了。接下來要討論的這個話題是“整理提交記錄” —— 開發人員有時會說“我想要把這個提交放到這裡, 那個提交放到剛才那個提交的後面”, 而接下來就講的就是它的實現方式,非常清晰、靈活,還很生動。

看起來挺複雜, 其實是個很簡單的概念。

移動提交記錄

Git Cherry-pick

本系列的第一個命令是 git cherry-pick, 命令形式為:

  • git cherry-pick <提交號>...

如果你想將一些提交複製到當前所在的位置(HEAD)下面的話, Cherry-pick 是最直接的方式了。我個人非常喜歡 cherry-pick,因為它特別簡單。

這裡有一個倉庫, 我們想將 side 分支上的工作複製到 main 分支,
你立刻想到了之前學過的 rebase 了吧?
但是我們們還是看看 cherry-pick 有什麼本領吧。

Git Cherry-pick c2 c4

互動式 rebase

當你知道你所需要的提交記錄(並且還知道這些提交記錄的雜湊值)時, 用 cherry-pick 再好不過了 —— 沒有比這更簡單的方式了。

但是如果你不清楚你想要的提交記錄的雜湊值呢? 幸好 Git 幫你想到了這一點, 我們可以利用互動式的 rebase —— 如果你想從一系列的提交記錄中找到想要的記錄, 這就是最好的方法了。

互動式 rebase 指的是使用帶引數 --interactive 的 rebase 命令, 簡寫為 -i

如果你在命令後增加了這個選項, Git 會開啟一個 UI 介面並列出將要被複制到目標分支的備選提交記錄,它還會顯示每個提交記錄的雜湊值和提交說明,提交說明有助於你理解這個提交進行了哪些更改。

在實際使用時,所謂的 UI 視窗一般會在文字編輯器 —— 如 Vim —— 中開啟一個檔案。

當 rebase UI介面開啟時, 你能做3件事:

  • 調整提交記錄的順序(通過滑鼠拖放來完成)
  • 刪除你不想要的提交(通過切換 pick 的狀態來完成,關閉就意味著你不想要這個提交記錄)
  • 合併提交。 簡而言之,它允許你把多個提交記錄合併成一個。

雜項

Git 技術、技巧與貼士大集合。

只取一個提交記錄

本地棧式提交

來看一個在開發中經常會遇到的情況:我正在解決某個特別棘手的 Bug,為了便於除錯而在程式碼中新增了一些除錯命令並向控制檯列印了一些資訊。

這些除錯和列印語句都在它們各自的提交記錄裡。最後我終於找到了造成這個 Bug 的根本原因,解決掉以後覺得沾沾自喜!

最後就差把 bugFix 分支裡的工作合併回 main 分支了。你可以選擇通過 fast-forward 快速合併到 main 分支上,但這樣的話 main 分支就會包含我這些除錯語句了。你肯定不想這樣,應該還有更好的方式……

實際我們只要讓 Git 複製解決問題的那一個提交記錄就可以了。跟之前我們在“整理提交記錄”中學到的一樣,我們可以使用

  • git rebase -i
  • git cherry-pick

來達到目的。

Git Tag

相信通過前面課程的學習你已經發現了:分支很容易被人為移動,並且當有新的提交時,它也會移動。分支很容易被改變,大部分分支還只是臨時的,並且還一直在變。

你可能會問了:有沒有什麼可以 永遠 指向某個提交記錄的標識呢,比如軟體釋出新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,有沒有比分支更好的可以永遠指向這些提交的方法呢?

當然有了!Git 的 tag 就是幹這個用的啊,它們可以(在某種程度上 —— 因為標籤可以被刪除後重新在另外一個位置建立同名的標籤)永久地將某個特定的提交命名為里程碑,然後就可以像分支一樣引用了。

更難得的是,它們並不會隨著新的提交而移動。你也不能檢出到某個標籤上面進行修改提交,它就像是提交樹上的一個錨點,標識了某個特定的位置。

我們們先建立一個標籤,指向提交記錄 C1,表示這是我們 1.0 版本。

git tag v1 c0

Git Describe

由於標籤在程式碼庫中起著“錨點”的作用,Git 還為此專門設計了一個命令用來描述離你最近的錨點(也就是標籤),它就是 git describe

Git Describe 能幫你在提交歷史中移動了多次以後找到方向;當你用 git bisect(一個查詢產生 Bug 的提交記錄的指令)找到某個提交記錄時,或者是當你坐在你那剛剛度假回來的同事的電腦前時, 可能會用到這個命令。

git describe 的語法是:

git describe <ref>

<ref> 可以是任何能被 Git 識別成提交記錄的引用,如果你沒有指定的話,Git 會以你目前所檢出的位置(HEAD)。

它輸出的結果是這樣的:

<tag>_<numCommits>_g<hash>

tag 表示的是離 ref 最近的標籤, 
numCommits 是表示這個 ref 與 tag 相差有多少個提交記錄, 
hash 表示的是你所給定的 ref 所表示的提交記錄雜湊值的前幾位。

當 ref 提交記錄上有某個標籤時,則只輸出標籤名稱。

遠端

Push & Pull —— Git 遠端倉庫!

是時候分享你的程式碼了,讓編碼變得社交化吧

遠端倉庫

遠端倉庫並不複雜, 在如今的雲端計算盛行的世界很容易把遠端倉庫想象成一個富有魔力的東西, 但實際上它們只是你的倉庫在另個一臺計算機上的拷貝。你可以通過因特網與這臺計算機通訊 —— 也就是增加或是獲取提交記錄。

話雖如此, 遠端倉庫卻有一系列強大的特性

  • 首先也是最重要的的點, 遠端倉庫是一個強大的備份。本地倉庫也有恢復檔案到指定版本的能力, 但所有的資訊都是儲存在本地的。有了遠端倉庫以後,即使丟失了本地所有資料, 你仍可以通過遠端倉庫拿回你丟失的資料。
  • 還有就是, 遠端讓程式碼社交化了! 既然你的專案被託管到別的地方了, 你的朋友可以更容易地為你的專案做貢獻(或者拉取最新的變更)

現在用網站來對遠端倉庫進行視覺化操作變得越發流行了(像 GitHubPhabricator),但遠端倉庫永遠是這些工具的頂樑柱, 因此理解其概念非常的重要!

Git clone

直到現在, 教程都聚焦於本地倉庫的操作(branch、merge、rebase 等等)。但我們現在需要學習遠端倉庫的操作 —— 我們需要一個配置這種環境的命令, 它就是 git clone

從技術上來講,git clone 命令在真實的環境下的作用是在本地建立一個遠端倉庫的拷貝(比如從 github.com)。

遠端分支

既然你已經看過 git clone 命令了,我們們深入地看一下發生了什麼。

你可能注意到的第一個事就是在我們的本地倉庫多了一個名為 o/main 的分支, 這種型別的分支就叫遠端分支。由於遠端分支的特性導致其擁有一些特殊屬性。

遠端分支反映了遠端倉庫(在你上次和它通訊時)的狀態。這會有助於你理解本地的工作與公共工作的差別 —— 這是你與別人分享工作成果前至關重要的一步。

遠端分支有一個特別的屬性,在你檢出時自動進入分離 HEAD 狀態。Git 這麼做是出於不能直接在這些分支上進行操作的原因, 你必須在別的地方完成你的工作, (更新了遠端分支之後)再用遠端分享你的工作成果。

為什麼有 o/

你可能想問這些遠端分支的前面的 o/ 是什麼意思呢?好吧, 遠端分支有一個命名規範 —— 它們的格式是:

  • /

因此,如果你看到一個名為 o/main 的分支,那麼這個分支就叫 main,遠端倉庫的名稱就是 o

大多數的開發人員會將它們主要的遠端倉庫命名為 origin,並不是 o。這是因為當你用 git clone 某個倉庫時,Git 已經幫你把遠端倉庫的名稱設定為 origin

不過 origin 對於我們的 UI 來說太長了,因此不得不使用簡寫 o ? 但是要記住, 當你使用真正的 Git 時, 你的遠端倉庫預設為 origin

Git Fetch

Git 遠端倉庫相當的操作實際可以歸納為兩點:向

  • 遠端倉庫傳輸資料
  • 從遠端倉庫獲取資料。

既然我們能與遠端倉庫同步,那麼就可以分享任何能被 Git 管理的更新(因此可以分享程式碼、檔案、想法、情書等等)。

git fetch 做了些什麼?

git fetch 完成了僅有的但是很重要的兩步:

  • 從遠端倉庫下載本地倉庫中缺失的提交記錄
  • 更新遠端分支指標(如 o/main)

git fetch 實際上將本地倉庫中的遠端分支更新成了遠端倉庫相應分支最新的狀態。

遠端分支反映了遠端倉庫在你最後一次與它通訊時的狀態,git fetch 就是你與遠端倉庫通訊的方式了!希望我說的夠明白了,你已經瞭解 git fetch 與遠端分支之間的關係了吧。

git fetch 通常通過網際網路(使用 http://git:// 協議) 與遠端倉庫通訊。

git fetch 不會做的事?

git fetch 並不會改變你本地倉庫的狀態。它不會更新你的 main 分支,也不會修改你磁碟上的檔案。

理解這一點很重要,因為許多開發人員誤以為執行了 git fetch 以後,他們本地倉庫就與遠端倉庫同步了。它可能已經將進行這一操作所需的所有資料都下載了下來,但是並沒有修改你本地的檔案。

所以, 你可以將 git fetch 的理解為單純的下載操作。

Git Pull

既然我們已經知道了如何用 git fetch 獲取遠端的資料, 現在我們學習如何將這些變化更新到我們的工作當中。

其實有很多方法的 —— 當遠端分支中有新的提交時,你可以像合併本地分支那樣來合併遠端分支。也就是說就是你可以執行以下命令:

  • git cherry-pick o/main
  • git rebase o/main
  • git merge o/main
  • 等等

實際上,由於先抓取更新再合併到本地分支這個流程很常用,因此 Git 提供了一個專門的命令來完成這兩個操作。它就是我們要講的 git pull

git pull 就是 git fetch 和 git merge 的縮寫!

Git Push

OK,我們已經學過了如何從遠端倉庫獲取更新併合併到本地的分支當中。這非常棒……但是我如何與大家分享我的成果呢?

嗯,上傳自己分享內容與下載他人的分享剛好相反,那與 git pull 相反的命令是什麼呢?git push

git push 負責將你的變更上傳到指定的遠端倉庫,並在遠端倉庫上合併你的新提交記錄。一旦 git push 完成, 你的朋友們就可以從這個遠端倉庫下載你分享的成果了!

你可以將 git push 想象成釋出你成果的命令。它有許多應用技巧,稍後我們會了解到,但是我們們還是先從基礎的開始吧……

*注意 —— git push 不帶任何引數時的行為與 Git 的一個名為 push.default 的配置有關。它的預設值取決於你正使用的 Git 的版本,但是在教程中我們使用的是 upstream。 這沒什麼太大的影響,但是在你的專案中進行推送之前,最好檢查一下這個配置。*

偏離的提交歷史

現在我們已經知道了如何從其它地方 pull 提交記錄,以及如何 push 我們自己的變更。看起來似乎沒什麼難度,但是為何還會讓人們如此困惑呢?

困難來自於遠端庫提交歷史的偏離

例子

假設你週一克隆了一個倉庫,然後開始研發某個新功能。
到週五時,你新功能開發測試完畢,可以釋出了。
但是 —— 天啊!你的同事這周寫了一堆程式碼,還改了許多你的功能中使用的 API,這些變動會導致你新開發的功能變得不可用。
但是他們已經將那些提交推送到遠端倉庫了,因此你的工作就變成了基於專案舊版的程式碼,與遠端倉庫最新的程式碼不匹配了。

這種情況下, git push 就不知道該如何操作了。如果你執行 git push,Git 應該讓遠端倉庫回到星期一那天的狀態嗎?
還是直接在新程式碼的基礎上新增你的程式碼,亦或由於你的提交已經過時而直接忽略你的提交?

因為這情況(歷史偏離)有許多的不確定性,Git 是不會允許你 push 變更的。實際上它會強制你先合併遠端最新的程式碼,然後才能分享你的工作。



那該如何解決這個問題呢?很簡單,你需要做的就是使你的工作基於最新的遠端分支。

有許多方法做到這一點呢,不過最直接的方法就是通過 rebase 調整你的工作。我們們繼續,看看怎麼 rebase!
也可以使用merge替換rebase的方法。

很好!但是要敲那麼多命令,有沒有更簡單一點的?

當然 —— 前面已經介紹過 git pull 就是 fetch 和 merge 的簡寫,類似的 git pull --rebase 就是 fetch 和 rebase 的簡寫!

其實,你直接 pull push也是一樣的,…………

鎖定的Master

如果你是在一個大的合作團隊中工作, 很可能是main被鎖定了, 需要一些Pull Request流程來合併修改。如果你直接提交(commit)到本地main, 然後試圖推送(push)修改, 你將會收到這樣類似的資訊:

! [遠端伺服器拒絕] main -> main (TF402455: 不允許推送(push)這個分支; 你必須使用pull request來更新這個分支.)

遠端伺服器拒絕直接推送(push)提交到main,因為策略配置要求 pull requests 來提交更新。

你應該按照流程,新建一個分支,,推送(push)這個分支並申請pull request,但是你忘記並直接提交給了main。現在你卡住並且無法推送你的更新。

解決方法: 新建一個分支feature,推送到遠端伺服器。 然後reset你的Master分支和遠端伺服器保持一致, 否則下次你pull並且他人的提交和你衝突的時候就會有問題。

IDEA中使用Git

IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine 中的 Git

JetBrains IDEs(比如 IntelliJ IDEA,PyCharm,WebStorm,PhpStorm,RubyMine,以及其他)自帶 Git 整合外掛。外掛在 IDE 中提供了一個專門的頁面,可以使用 Git 和 GitHub 的 Pull Request。

該整合外掛依賴於 Git 的命令列客戶端,所以需要先安裝一個 Git 客戶端。官方文件請訪問: https://www.jetbrains.com/help/idea/using-git-integration.html

參考

學習網址:https://learngitbranching.js.org/,闖關的形式,很棒。

關卡答案:https://blog.csdn.net/qq_34519487/article/details/107882290

Git-中文文件:https://git-scm.com/book/zh/v2

相關文章