Git 工作原理,基本操作,創(chuàng)建與合并分支

前東家使用 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)容:


其他系統(tǒng)在每個(gè)版本中記錄著各個(gè)文化的具體差異

Git 并不保存這些前后變化的差異數(shù)據(jù)。實(shí)際上,Git 更像是把變化的文件作快照后,記錄在一個(gè)微型的文件系統(tǒng)中。每次提交更新時(shí),它會(huì)縱覽一遍所有文件的指紋信息并對(duì)文件作一快照,然后保存一個(gè)指向這次快照的索引。為提高性能,若文件沒(méi)有變化,Git 不會(huì)再次保存,而只對(duì)上次保存的快照作一鏈接。Git 的工作方式就如下圖所示:


Git保存每次更新時(shí)的文件快照

工作區(qū) Working Directory VS 暫存區(qū) Staging Directory

Git 工作流

本地倉(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

不同區(qū)的切換

文件的三種狀態(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)如下:

.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)前分支的最新提交。


HEAD 默認(rèn)指向指向當(dāng)前分支的最新提交

也可以通過(guò) git show HEAD 命令看到當(dāng)前 HEAD 的位置:

通過(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
  • 改動(dòng)代碼
    • 涉及到 本地某個(gè)倉(cāng)庫(kù)下的某個(gè)分支 local repository/local branch
  • 將改動(dòng)傳回 Git git push
    • 涉及到 遠(yuǎn)程服務(wù)器下的某個(gè)倉(cāng)庫(kù)下的某個(gè)分支 remote server/remote repository/remote branch

涉及到兩個(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)程分支:

查看遠(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/masterorigin/master 的指向是相同的
  • 我們可以通過(guò) git diff origin/master master 來(lái)顯示 本地分支 與 遠(yuǎn)程分支 的區(qū)別

隨著你不斷的改動(dòng)文件,git add, git commitmaster 的指向會(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),工作目錄也同樣。

reset

cherry pick

cherry-pick 命令"復(fù)制"一個(gè)提交節(jié)點(diǎn)并在當(dāng)前分支做一次完全一樣的新提交。

cherry pick

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ō)不是指向提交,而是指向 mastermaster 才是指向提交的,所以, 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))

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 在 版本回退 里,你已經(jīng)知道,每次提交,Git都把它們串成一條時(shí)間線,這條時(shí)間線就是一個(gè)分支。截止到目前,只有一條...
    sidney_c閱讀 301評(píng)論 0 0
  • Git 基礎(chǔ) 基本原理 客戶端并不是只提取最新版本的文件快照,而是把代碼倉(cāng)庫(kù)完整的鏡像下來(lái)。這樣一來(lái),任何一處協(xié)同...
    __silhouette閱讀 15,942評(píng)論 5 147
  • 夏日伊始的思念 纏繞著太陽(yáng)熾熱的光線 跨進(jìn)南回歸線 落滿地的秋葉 層層疊疊 滿是回憶的積淀 晴天夜空的星星 是你寄...
    惠施chen閱讀 211評(píng)論 0 2
  • 總有一天你會(huì)和我遇見(jiàn) 像好友一樣卻失散多年 你一句我一句單純聊天 說(shuō)你過(guò)的還不錯(cuò)在以前 靚麗光鮮映入了我眼簾 講我...
    沈九壹閱讀 185評(píng)論 0 0
  • 又是一陣風(fēng)雨來(lái)襲 而我獨(dú)往其中 別問(wèn)我,我是誰(shuí) 一場(chǎng)從未相遇的運(yùn) 而你不曾遇見(jiàn) 而這不曾懷戀 而我失去了記憶 只是...
    念及我x閱讀 184評(píng)論 0 1