前東家使用 Perforce 做版本控制,現(xiàn)東家使用 Git。
Git 工作原理
Git 和其他版本控制系統(tǒng)的主要差別在于,Git 只關(guān)心文件數(shù)據(jù)的整體是否發(fā)生變化,而大多數(shù)其他系統(tǒng)則只關(guān)心文件內(nèi)容的具體差異。
這類系統(tǒng)(CVS,Subversion,Perforce,Bazaar 等等)每次記錄有哪些文件作了更新,以及都更新了哪些行的什么內(nèi)容:
Git 并不保存這些前后變化的差異數(shù)據(jù)。實(shí)際上,Git 更像是把變化的文件作快照后,記錄在一個(gè)微型的文件系統(tǒng)中。每次提交更新時(shí),它會(huì)縱覽一遍所有文件的指紋信息并對(duì)文件作一快照,然后保存一個(gè)指向這次快照的索引。為提高性能,若文件沒(méi)有變化,Git 不會(huì)再次保存,而只對(duì)上次保存的快照作一鏈接。Git 的工作方式就如下圖所示:
工作區(qū) Working Directory VS 暫存區(qū) Staging Directory
本地倉(cāng)庫(kù)由 Git 維護(hù)的三棵“樹(shù)“組成:
-
工作目錄 Working Directory,它持有實(shí)際文件
例如,我們添加了一個(gè)文件NewFile.txt
,那它就是在工作目錄 Working Directory 中:
工作目錄 Working Directory -
暫存區(qū) Staging Directory 或者叫 Index Directory,緩存區(qū)域,臨時(shí)保存文件的改動(dòng)
我們通過(guò)git add
命令添加到暫存區(qū) Staging Directory 或者叫 Index Directory:
暫存區(qū) Staging Directory 或者叫 Index Directory -
倉(cāng)庫(kù) Head 區(qū),指向最后一次提交的結(jié)果
我們通過(guò)git commit
命令實(shí)際提交改動(dòng)到 Head 區(qū):
image.png
通過(guò)下面這個(gè)圖可以清晰的表示不同區(qū)的切換:
引用自:http://marklodato.github.io/visual-git-guide/index-zh-cn.html
文件的三種狀態(tài)
- 已修改 (modified):表示修改了某個(gè)文件,但還沒(méi)有提交保存
- 已暫存 (staged):表示把已修改的文件放在下次提交時(shí)要保存的清單中
- 已提交 (committed):表示該文件已經(jīng)被安全的保存在本地?cái)?shù)據(jù)庫(kù)中了
版本庫(kù)
工作區(qū)中的隱藏目錄 .git
,就是 Git 的版本庫(kù)。
.git
目錄結(jié)構(gòu)如下:
其中:
-
HEAD
文件:指示目前被檢出的分支
HEAD 文件 index
文件:保存暫存區(qū)信息-
objects
目錄:存儲(chǔ)所有數(shù)據(jù)內(nèi)容
objects 目錄 -
refs
目錄:存儲(chǔ)指向數(shù)據(jù)(分支)的提交對(duì)象的指針
refs 目錄
什么是 HEAD
HEAD 默認(rèn)指向指向當(dāng)前分支的最新提交。
也可以通過(guò) git show HEAD
命令看到當(dāng)前 HEAD 的位置:
- HEAD 可以指向某個(gè)分支,如果指向某個(gè)分支,則永遠(yuǎn)指向分支上的最新提交
- 當(dāng)然 HEAD 也可以指向某個(gè)提交
什么是 origin 和 master
通過(guò) Git 來(lái)修改代碼,主要包括如下三步:
-
從 Git 取數(shù)據(jù)
git clone
- 涉及到 遠(yuǎn)程服務(wù)器下的某個(gè)倉(cāng)庫(kù)下的某個(gè)分支
remote server/remote repository/remote branch
- 涉及到 遠(yuǎn)程服務(wù)器下的某個(gè)倉(cāng)庫(kù)下的某個(gè)分支
-
改動(dòng)代碼
- 涉及到 本地某個(gè)倉(cāng)庫(kù)下的某個(gè)分支
local repository/local branch
- 涉及到 本地某個(gè)倉(cāng)庫(kù)下的某個(gè)分支
-
將改動(dòng)傳回 Git
git push
- 涉及到 遠(yuǎn)程服務(wù)器下的某個(gè)倉(cāng)庫(kù)下的某個(gè)分支
remote server/remote repository/remote branch
- 涉及到 遠(yuǎn)程服務(wù)器下的某個(gè)倉(cāng)庫(kù)下的某個(gè)分支
涉及到兩個(gè) repository:
- 遠(yuǎn)程倉(cāng)庫(kù) Remote Repository,在遠(yuǎn)程服務(wù)器上
- 本地倉(cāng)庫(kù) Local Repository,在本地工作區(qū)上
找到最近創(chuàng)建的一個(gè) Github 項(xiàng)目:https://github.com/chenxiangcyr/spring-cloud-config-repo-demo
現(xiàn)在我們通過(guò)如下命令 Clone 到本地:
git clone git@github.com:chenxiangcyr/spring-cloud-config-repo-demo.git
在 Clone 完成之后,Git 會(huì)自動(dòng)為你將此遠(yuǎn)程倉(cāng)庫(kù)命名為 origin
,并下載其中所有的數(shù)據(jù),建立一個(gè)指向它的 master
分支的指針。
因此 origin
是遠(yuǎn)程倉(cāng)庫(kù)的名字,master
是分支的名字。
我們用 (遠(yuǎn)程倉(cāng)庫(kù)名)/(分支名)
這樣的形式表示遠(yuǎn)程分支,所以 origin/master
指向的是一個(gè)遠(yuǎn)程分支 remote branch。我們實(shí)際上是從這個(gè)遠(yuǎn)程分支 remote branch 中 Clone 數(shù)據(jù)到本地,但你無(wú)法在本地更改遠(yuǎn)程分支 remote branch 中的數(shù)據(jù)。
通過(guò) git branch -r
可以查看遠(yuǎn)程分支:
我們也可以通過(guò) cat .git/config
看到 origin
的含義:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[remote "origin"]
url = git@github.com:chenxiangcyr/spring-cloud-config-repo-demo.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
同時(shí),Git 會(huì)建立一個(gè)屬于你自己的本地 master
分支,它指向的是你剛剛從遠(yuǎn)程分支 remote branch 傳到你本地的副本。
通過(guò) git branch
可以查看本地分支:
總結(jié):
-
master
就是本地分支 local branch -
origin/master
是遠(yuǎn)程分支 remote branch -
remotes/origin/master
和origin/master
的指向是相同的 - 我們可以通過(guò)
git diff origin/master master
來(lái)顯示 本地分支 與 遠(yuǎn)程分支 的區(qū)別
隨著你不斷的改動(dòng)文件,git add
, git commit
,master
的指向會(huì)自動(dòng)移動(dòng)。最終你還要通過(guò) git push
命令來(lái)將修改推送到遠(yuǎn)程分支。
git push origin master:master
- 我們看到有兩個(gè)
master
,其中前面一個(gè)為 source,后面一個(gè)為 destination - 該命令可以理解為,在本地倉(cāng)庫(kù)中找到名字為
master
的分支,使用它去更新遠(yuǎn)程倉(cāng)庫(kù)下名字為master
的分支,如果遠(yuǎn)程倉(cāng)庫(kù)下不存在名字是master
的分支,那么新建一個(gè)。 -
git push origin master
(省略了<destination>
,等價(jià)于git push origin master:master
)
Git 基本操作
引用自:http://marklodato.github.io/visual-git-guide/index-zh-cn.html
上面的四條命令在工作目錄、暫存目錄(也叫做索引)和倉(cāng)庫(kù)之間復(fù)制文件。
git add files
把當(dāng)前文件放入暫存區(qū)域。git commit
給暫存區(qū)域生成快照并提交。-
git reset files
用來(lái)撤銷最后一次git add files
,你也可以用git reset
撤銷所有暫存區(qū)域文件。
例如我們修改了文件NewFile.txt
(添加了內(nèi)容123
),通過(guò)git add NewFile.txt
放入暫存區(qū)域,隨后通過(guò)git reset NewFile.txt
來(lái)撤銷,但是修改的內(nèi)容沒(méi)有被撤銷(依然是123
):
git reset files 用來(lái)撤銷最后一次 git add files -
git checkout files
把文件從暫存區(qū)域復(fù)制到工作目錄,用來(lái)丟棄本地修改。
例如在上面的基礎(chǔ)上執(zhí)行git checkout NewFile.txt
,這時(shí)候NewFile.txt
所修改的內(nèi)容被撤銷了。
git checkout files 把文件從暫存區(qū)域復(fù)制到工作目錄,用來(lái)丟棄本地修改
也可以跳過(guò)暫存區(qū)域直接從倉(cāng)庫(kù)取出文件或者直接提交代碼:
-
git commit -a
相當(dāng)于運(yùn)行git add
把所有當(dāng)前目錄下的文件加入暫存區(qū)域再運(yùn)行git commit
。 -
git commit files
進(jìn)行一次包含最后一次提交加上工作目錄中文件快照的提交,并且文件被添加到暫存區(qū)域。 -
git checkout HEAD files
從倉(cāng)庫(kù)歷史中回滾到復(fù)制最后一次提交到工作區(qū)。
diff 查看兩次提交之間的變動(dòng)
-
git diff NexFile.txt
查看工作目錄與暫存區(qū)的變動(dòng) -
git diff HEAD NexFile.txt
查看工作目錄與倉(cāng)庫(kù)中 HEAD 的變動(dòng) -
git diff --cached NexFile.txt
查看暫存區(qū)與倉(cāng)庫(kù)中 HEAD 的變動(dòng) -
git diff <branch> NexFile.txt
查看工作目錄與倉(cāng)庫(kù)中某個(gè)分支的變動(dòng) -
git diff <commit> <commit>
查看兩次 commit 之間的變動(dòng)
diff 查看兩次提交之間的變動(dòng)
checkout
checkout
命令用于從歷史提交(或者暫存區(qū)域)中拷貝文件到工作目錄,也可用于切換分支。
- 當(dāng)給定某個(gè)文件名(或者打開(kāi)
-p
選項(xiàng),或者文件名和-p
選項(xiàng)同時(shí)打開(kāi))時(shí),Git 會(huì)從指定的提交中拷貝文件到暫存區(qū)域和工作目錄。比如,git checkout HEAD NewFile.txt
會(huì)將提交節(jié)點(diǎn)HEAD~
(即當(dāng)前提交節(jié)點(diǎn)的父節(jié)點(diǎn))中的NewFile.txt
復(fù)制到工作目錄并且加到暫存區(qū)域中。 - 當(dāng)不指定文件名,而是給出一個(gè)(本地)分支時(shí),那么 HEAD 標(biāo)識(shí)會(huì)移動(dòng)到那個(gè)分支(也就是說(shuō),我們“切換”到那個(gè)分支了),然后暫存區(qū)域和工作目錄中的內(nèi)容會(huì)和 HEAD 對(duì)應(yīng)的提交節(jié)點(diǎn)一致。
- 如果既沒(méi)有指定文件名,也沒(méi)有指定分支名,而是一個(gè)標(biāo)簽、遠(yuǎn)程分支、SHA-1值或者是像 master~3 類似的東西,就得到一個(gè)匿名分支,稱作 detached HEAD(被分離的HEAD標(biāo)識(shí))。
reset
reset
命令把當(dāng)前分支指向另一個(gè)位置,并且有選擇的變動(dòng)工作目錄和索引。也用來(lái)在從歷史倉(cāng)庫(kù)中復(fù)制文件到索引,而不動(dòng)工作目錄。
如果不給選項(xiàng),那么當(dāng)前分支指向到那個(gè)提交。如果用 --hard
選項(xiàng),那么工作目錄也更新,如果用 --soft
選項(xiàng),那么都不變。
如果沒(méi)有給出提交點(diǎn)的版本號(hào),那么默認(rèn)用 HEAD
。這樣,分支指向不變,但是索引會(huì)回滾到最后一次提交,如果用 --hard
選項(xiàng),工作目錄也同樣。
cherry pick
cherry-pick
命令"復(fù)制"一個(gè)提交節(jié)點(diǎn)并在當(dāng)前分支做一次完全一樣的新提交。
rebase
交互式變基 (rebase)。它可以用來(lái)編輯提交信息,或者將多個(gè)提交壓縮成一個(gè)提交。
git rebase -i HEAD~n
Git 創(chuàng)建與合并分支
Git 關(guān)于分支的操作主要包括:
-
查看分支:
git branch
-
創(chuàng)建分支:
git branch <name>
-
切換分支:
git checkout <name>
-
創(chuàng)建 + 切換分支:
git checkout -b <name>
-
合并某分支到當(dāng)前分支:
git merge <name>
-
刪除分支:
git branch -d <name>
每次提交,Git 都把它們串成一條時(shí)間線,這條時(shí)間線就是一個(gè)分支。在 Git 里,這個(gè)分支叫主分支,即 master
分支。HEAD
嚴(yán)格來(lái)說(shuō)不是指向提交,而是指向 master
,master
才是指向提交的,所以, HEAD
指向的就是當(dāng)前分支。每次提交,master
分支都會(huì)向前移動(dòng)一步,這樣,隨著你不斷提交,master
分支的線也越來(lái)越長(zhǎng):
一個(gè)具體的 Case
我們目前在 master
分支上,下一階段要開(kāi)發(fā)一個(gè)新需求付款,因此需要在一個(gè)新的分支 payment
上開(kāi)發(fā),測(cè)試,最后合并到 master
分支上。
創(chuàng)建新分支:
git checkout -b payment
以上操作相當(dāng)于:
git branch payment
git checkout payment
隨后我們?cè)?payment
分支在不斷的向前推進(jìn):
這時(shí)候,突然來(lái)了一個(gè)線上的 Issue,需求緊急修復(fù),因此我們停下手動(dòng)上的工作,需要在一個(gè)新的分支 bugfix
上開(kāi)發(fā),測(cè)試,最后合并到 master
分支上。
創(chuàng)建新分支:
git checkout -b bugfix
隨后我們?cè)?bugfix
分支上進(jìn)行修改:
測(cè)試沒(méi)問(wèn)題后,合并到 master
分支上:
git checkout master
git merge bugfix
在合并的時(shí)候,由于當(dāng)前 master
分支所指向的提交是 bugfix
的直接上游,所以 Git 只是簡(jiǎn)單的將指針向前移動(dòng)。 換句話說(shuō),當(dāng)你試圖合并兩個(gè)分支時(shí),如果順著一個(gè)分支走下去能夠到達(dá)另一個(gè)分支,那么 Git 在合并兩者的時(shí)候,只會(huì)簡(jiǎn)單的將指針向前推進(jìn)(指針右移),因?yàn)檫@種情況下的合并操作沒(méi)有需要解決的分歧,這就叫做 “快進(jìn)(fast-forward)”。
隨后我們上線該緊急修復(fù),然后可以使用命令來(lái)刪除 bugfix
分支:
git branch -d bugfix
接下來(lái),我們切換到之前的 payment
分支上繼續(xù)工作:
測(cè)試沒(méi)問(wèn)題后,合并到 master
分支上:
git checkout master
git merge payment
這和你之前合并 bugfix
分支的時(shí)候看起來(lái)有一點(diǎn)不一樣。在這種情況下,你的開(kāi)發(fā)歷史從一個(gè)更早的地方開(kāi)始分叉開(kāi)來(lái)。 因?yàn)?master
分支所在提交并不是 payment
分支所在提交的直接祖先,Git 不得不做一些額外的工作。
出現(xiàn)這種情況的時(shí)候,Git 會(huì)使用兩個(gè)分支的末端所指的快照(藍(lán)色部分)以及這兩個(gè)分支的工作祖先(黃色部分),做一個(gè)簡(jiǎn)單的三方合并。
引用:
Git 教程 創(chuàng)建與合并分支
3.2 Git 分支 - 分支的新建與合并
淺析 Git 思想和工作原理
圖解Git
Git 的origin和master分析(轉(zhuǎn))