學習網址:https://learngitbranching.js.org/?locale=zh_CN
闖關的形式,很棒。
主要
基礎篇
循序漸進地介紹 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 是一個對當前檢出記錄的符號引用 —— 也就是指向你正在其基礎上進行工作的提交記錄。
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 遠程倉庫!
是時候分享你的代碼了,讓編碼變得社交化吧
遠程倉庫
遠程倉庫並不復雜, 在如今的雲計算盛行的世界很容易把遠程倉庫想象成一個富有魔力的東西, 但實際上它們只是你的倉庫在另個一台計算機上的拷貝。你可以通過因特網與這台計算機通信 —— 也就是增加或是獲取提交記錄。
話雖如此, 遠程倉庫卻有一系列強大的特性
- 首先也是最重要的的點, 遠程倉庫是一個強大的備份。本地倉庫也有恢復文件到指定版本的能力, 但所有的信息都是保存在本地的。有了遠程倉庫以后,即使丟失了本地所有數據, 你仍可以通過遠程倉庫拿回你丟失的數據。
- 還有就是, 遠程讓代碼社交化了! 既然你的項目被托管到別的地方了, 你的朋友可以更容易地為你的項目做貢獻(或者拉取最新的變更)
現在用網站來對遠程倉庫進行可視化操作變得越發流行了(像 GitHub 或 Phabricator),但遠程倉庫永遠是這些工具的頂梁柱, 因此理解其概念非常的重要!
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