Git的學(xué)習(xí)
Git簡(jiǎn)介
Git由Linux操作系統(tǒng)內(nèi)核的創(chuàng)造者Linus Torvalds在2005年創(chuàng)造职抡,是目前世界上被最廣泛使用的現(xiàn)代軟件版本管理系統(tǒng)烤镐。Git是一個(gè)成熟并處于活躍開(kāi)發(fā)狀態(tài)的高質(zhì)量開(kāi)源項(xiàng)目,目前Git支持絕大多數(shù)的操作系統(tǒng)镰吆,絕大多數(shù)的IDE也對(duì)Git有支持莉给。
版本控制
版本控制是一種記錄一個(gè)或若干文件內(nèi)容變化昆稿,以便將來(lái)查閱特定版本修訂情況的系統(tǒng)括堤。版本控制的種類(lèi)大致有三種砖第,分為‘本地版本控制’斟或、‘集中化版本控制’以及‘分布式版本控制’素征。
- 本地版本控制:
- 本地版本控制的工作原理是在本地硬盤(pán)上保存補(bǔ)丁集(補(bǔ)丁是指文件修訂前后的變化)通過(guò)應(yīng)用所有的補(bǔ)丁,可以重新計(jì)算出各個(gè)版本的文件內(nèi)容萝挤。
- 集中化版本控制:
- 本地化版本有個(gè)很大的缺陷就是很難做到協(xié)同工作御毅,集中化版本控制解決了這個(gè)問(wèn)題。
- 集中化的版本控制系統(tǒng)能夠讓在不同系統(tǒng)上的開(kāi)發(fā)者協(xié)同工作怜珍。這類(lèi)版本控制系統(tǒng)都有一個(gè)單一集中管理的服務(wù)器端蛆,保存所有文件的修訂版本,而協(xié)同工作的人都通過(guò)客戶(hù)端連到這臺(tái)服務(wù)器酥泛,取出最新的文件或者提交更新今豆。這類(lèi)版本控制系統(tǒng)的代表作便是Subversion版本控制系統(tǒng)(SVN)。
- 每個(gè)人都可以在一定程度上看到項(xiàng)目中的其他人正在做些什么柔袁,管理員可以輕松掌控每個(gè)開(kāi)發(fā)者的權(quán)限呆躲。
- 分布式版本控制:
- 不再有一個(gè)統(tǒng)一的集中管理的服務(wù)器,客戶(hù)端并不只提取最新版本的文件快照捶索,而是把代碼倉(cāng)庫(kù)完整地克隆下來(lái)插掂。
- 任何一處協(xié)同工作用的服務(wù)器發(fā)生故障,事后都可以用任何一個(gè)克隆鏡像出來(lái)的本地倉(cāng)庫(kù)恢復(fù)腥例。
- 每一次的克隆操作燥筷,實(shí)際上都是一次對(duì)代碼倉(cāng)庫(kù)的完整備份。
- 比較典型的分布式版本控制系統(tǒng)有Git院崇,也是我們接下來(lái)要討論的重點(diǎn)肆氓。
分布式版本控制系統(tǒng)Git
Git底層對(duì)象
- 實(shí)體對(duì)象
- 原文(blob)
- 樹(shù)(tree)
- 鏈表(linked list)
- 補(bǔ)丁(patch)
- 虛擬對(duì)象
- 指針(pointer)
- 引用(ref)
Git底層對(duì)象的關(guān)聯(lián)
Blob → Tree → Commit
Commit → Branch
說(shuō)明
tree:每次commit生成的文件快照,以快照樹(shù)結(jié)構(gòu)存在
commit:每個(gè)文件樹(shù)快照的容器底瓣,包含一個(gè)指向?qū)?yīng)文件樹(shù)的指針谢揪,以及作者蕉陋、提交者姓名、時(shí)間以及最重要的屬性:parent(父節(jié)點(diǎn))
linked list:關(guān)聯(lián)commit拨扶,構(gòu)成branch(分支)
ref:branch凳鬓、tag、stash等名字都是引用患民,它們都可以指向任意一個(gè)commit
-
pointer:常見(jiàn)的指針有HEAD以及FETCH_HEAD
- HEAD:指向某一個(gè)branch(指針指向引用)缩举,方便我們做分支操作,正常狀態(tài)下是attached到某個(gè)branch匹颤,如果特殊情況仅孩,也可以將HEAD指向某個(gè)commit,此時(shí)指針為deattached狀態(tài)印蓖,比較危險(xiǎn)
- FETCH_HEAD:指向目前已經(jīng)從遠(yuǎn)程倉(cāng)庫(kù)(Remote Repo)取下來(lái)的分支的末端版本辽慕,表示最后一次到本地倉(cāng)庫(kù)的更新。
HEAD和FETCH_HEAD平時(shí)都是用來(lái)操作本地倉(cāng)庫(kù)的赦肃,即Local Repo
-
- 將文件中的內(nèi)容通過(guò)其hash算法生成一個(gè)160比特的報(bào)文摘要溅蛉,即40個(gè)十六進(jìn)制數(shù)字(每個(gè)十六進(jìn)制數(shù)字占4位)。
- 通過(guò)SHA-1計(jì)算出來(lái)每個(gè)Blob和Tree的hash值他宛,可以用git hash-object 進(jìn)行測(cè)試船侧。
Git工作的原理
- 本質(zhì):Git的底層是一個(gè)微型內(nèi)容尋址系統(tǒng)
- 基于文件樹(shù)Hash快照。Git只關(guān)心文件數(shù)據(jù)的整體是否發(fā)生變化厅各,而大多數(shù)其他系統(tǒng)則只關(guān)心文件內(nèi)容的具體差異镜撩。Git并不保存這些前后變化的差異數(shù)據(jù),實(shí)際上讯检,Git更像是把變化的文件作快照后,記錄在一個(gè)微型的文件系統(tǒng)中卫旱。每次提交更新時(shí)人灼,它會(huì)縱覽一遍所有文件的指紋信息并對(duì)文件作快照,然后保存一個(gè)指向這次快照的索引顾翼。為提高性能投放,若文件沒(méi)有變化,Git不會(huì)再次保存适贸,而只對(duì)上次保存的快照作一鏈接灸芳。Git更像是個(gè)小型的文件系統(tǒng),但它同時(shí)還提供了許多以此為基礎(chǔ)的超強(qiáng)工具拜姿。
Git文件狀態(tài)
先來(lái)看一張圖
Git文件狀態(tài)有以下幾種
- Untracked:沒(méi)有加入到Git版本控制中的文件
- Unmodified:已經(jīng)提交文件(已經(jīng)存在于Git倉(cāng)庫(kù)中的文件)
- Modified:已經(jīng)存在于Git倉(cāng)庫(kù)的文件烙样,后面做了修改但是沒(méi)有提交
- Staged:已經(jīng)暫存(Git暫存區(qū))等待提交的文件
Git工作區(qū)域組成
- 工作區(qū)(WorkSpace):這是電腦上的一個(gè)目錄,存放從Git倉(cāng)庫(kù)的壓縮數(shù)據(jù)庫(kù)中提取出來(lái)的文件蕊肥,存放的文件狀態(tài)Untracked谒获、Unmodified、Modified
- 暫存區(qū)(Stage):保存了需要下次提交的文件列表信息,文件狀態(tài):Staged
- 本地倉(cāng)庫(kù)區(qū)(LocalRepo):用來(lái)保存項(xiàng)目的元數(shù)據(jù)和對(duì)象數(shù)據(jù)庫(kù)的地方(存在于工作目錄下的隱藏文件夾
.git
下批狱,備份這個(gè)文件夾即可備份整個(gè)倉(cāng)庫(kù))裸准,文件狀態(tài):Unmodified
Git在本地工作去中有三顆文件快照樹(shù):
- HEAD:上一次提交的文件快照,下一次提交的父結(jié)點(diǎn)
- WorkRepo:記錄整個(gè)工作目錄的文件樹(shù)結(jié)構(gòu)
- Stage\Index:預(yù)期下一次要提交的文件快照
.git目錄
在Mac OS X操作系統(tǒng)中這是一個(gè)隱藏的目錄赔硫,我們可以通過(guò)快捷鍵Command
+ shift
+ .
將其顯示出來(lái)
當(dāng)我們用Git命令git init
對(duì)一個(gè)目錄進(jìn)行初始化后便可以看到.git
這個(gè)文件目錄(如圖)
.git
這個(gè)目錄中包含了幾乎所有的Git存儲(chǔ)和操作的對(duì)象炒俱,如果想要復(fù)制一個(gè)版本庫(kù)只要將這個(gè)目錄復(fù)制一下即可。下面說(shuō)明下該目錄
- description:僅供GitWeb程序使用
- config:包含項(xiàng)目特有的配置
- info:包含一個(gè)全局性排除(global exclude)文件爪膊,用以放置那些記錄在
.gitignore
文件中不希望添加到版本控制中的文件 - hooks:包含客戶(hù)端或服務(wù)端的鉤子腳本(hook scripts)
- HEAD:指示目前被檢出的分支
- index:保存暫存區(qū)的相關(guān)信息
- objects:存儲(chǔ)所有數(shù)據(jù)內(nèi)容
- refs:存儲(chǔ)指向數(shù)據(jù)(Branch)的提交對(duì)象的指針
Git操作基本流程
- 在工作目錄中修改某些文件
- 對(duì)修改后的文件進(jìn)行快照权悟,保存至?xí)捍鎱^(qū)域
- 提交更新,將保存在暫存區(qū)域的文件快照永久轉(zhuǎn)儲(chǔ)到Git目錄中
Git命令
初次運(yùn)行Git的配置命令
在計(jì)算機(jī)上安裝好Git后可以定制你的Git環(huán)境惊完。每臺(tái)計(jì)算機(jī)上只需要配置一次僵芹,你也可以在任何時(shí)候再次通過(guò)運(yùn)行命令來(lái)修改這些配置。
- 全局配置用戶(hù)信息
git config --global user.name "xxx"
git config --global user.email "xxx@meituan.com"
- 檢查配置信息
git config --list
- 檢查某單一配置信息
git config <#key#>
// eg:檢查用戶(hù)名字
git config user.name
一些較為常用的Git命令
- 初始化一個(gè)Git倉(cāng)庫(kù)
// 需要進(jìn)入你想初始化為Git倉(cāng)庫(kù)的目錄執(zhí)行以下命令
git init
該命令將創(chuàng)建一個(gè)名為.git
的子目錄小槐,這個(gè)子目錄含有你初始化的Git倉(cāng)庫(kù)中所有的必須文件拇派,這些文件是Git倉(cāng)庫(kù)的骨干。 但是凿跳,到此為止我們僅僅是做了一個(gè)初始化的操作件豌,項(xiàng)目里的文件還沒(méi)有被跟蹤。
- 跟蹤項(xiàng)目里的文件
// 一般格式
git add [<options>] [--] <pathspec>...
// 常用的git add 命令
git add -u // 添加僅索引修改或刪除的文件控嗜,而不是那些創(chuàng)建的文件
git add -u [<path>] // 把path路徑中所有被跟蹤文件中被修改或者刪除的文件的信息添加到索引庫(kù)當(dāng)中茧彤,省略path即表示在當(dāng)前目錄中
git add -A(--all) // 把當(dāng)前目錄中所有被跟蹤的文件中被修改過(guò)或已刪除的文件和所有未指定的文件信息添加到索引庫(kù)。
git add . // 把工作時(shí)的所有變化提交到暫存區(qū)疆栏,包括文件內(nèi)容修改(modified)以及新文件(new)曾掂,但不包括被刪除的文件。
- 查看當(dāng)前倉(cāng)庫(kù)狀態(tài)
git status // 此條命令會(huì)較為詳細(xì)得展示當(dāng)前倉(cāng)庫(kù)的狀態(tài)
git status -s // 查看當(dāng)前倉(cāng)庫(kù)狀態(tài)(簡(jiǎn)略)
-
切換分支或者恢復(fù)文件
- 切換分支
git checkout <branch name>
- 修復(fù)工作目錄下的文件壁顶,使得跟倉(cāng)庫(kù)文件同步
git checkout <filename>
- 如果1中<branch name>和2中<filename>相同珠洗,調(diào)用上面命令會(huì)出問(wèn)題,所以對(duì)于文件同步用下面命令(撤銷(xiāo)文件修改):
git checkout -- <filename>
- 新建并切換分支
git checkout -b <branchname>
- 切換到舊的提交版本若专,commit是表示提交的id
$ git checkout <commit>
- 刪除分支:
// 需要先切換到其他分支许蓖,無(wú)法刪除正在使用的分支(切回master分支) git checkout master // 刪除本地分支xxx git branch -d xxx // 開(kāi)始推送刪除共享git服務(wù)器上分支 git push origin :xxx // 刪除遠(yuǎn)程服務(wù)器上的分支xxx git push origin --delete xxx // 強(qiáng)行刪除某個(gè)分支xxx(當(dāng)分支新增功能而且已經(jīng)commit但是沒(méi)有合并,然后現(xiàn)在不需要這個(gè)分支) git branch -D xxx
-
提交文件到本地倉(cāng)庫(kù)
- 提交xxC語(yǔ)言文件到本地倉(cāng)庫(kù)中
git add xx.c git commit -m "commit xx.c"
- 所有已經(jīng)跟蹤過(guò)的文件暫存起來(lái)一并提交,無(wú)需
git add
命令
$ git commit -a -m 'added new benchmarks'
- 修改最后一次提交
// 修改最后一次提交信息调衰,輸入下面命令后會(huì)跳出編輯器膊爪,修改相關(guān)內(nèi)容關(guān)閉即可 git commit --amend // eg:當(dāng)有些文件忘了提交、有些文件忘了刪除嚎莉,需要修改最后一次提交時(shí)可以這樣做 git add xxx // 提交忘記提交的文件 git rm xxxx // 刪除忘記刪除的文件 git commit --amend // 修改最后一次提交的信息
-
查看任意兩棵文件樹(shù)之間的差異
- 查看工作環(huán)境與暫存區(qū)之間的差異(尚未提交的文件)
git diff
- 查看暫存區(qū)域與你最后提交之間的差異
git diff --staged
- 比較兩個(gè)提交記錄的差異
git diff branchA branchB
- 檢查空白錯(cuò)誤(空格米酬、tab等)
git diff --check
- 查看合并后本分支變動(dòng)了的內(nèi)容
git diff --ours
- 查看被合并分支與合并后有哪些不同(-b去除空白)
git diff --theirs -b
- 查看合并后,合并兩個(gè)版本各自變動(dòng)內(nèi)容(-b去除空白)
git diff --base -b
擴(kuò)展
合并分支
// eg 合并某分支(branchA)到當(dāng)前分支(master)
// 使用Fast-forward模式(Git默認(rèn)模式)
git merge branchA
git branch -d branchA
// 禁用Fast-forward模式
git merge --no-ff -m "merge with no-ff" branchA
// 兩者的區(qū)別:使用Fast-forward趋箩,無(wú)法知道branchA存在過(guò)
-
與合并相關(guān)的命令
- 合并時(shí)淮逻,有沖突琼懊,用自己分支代碼解決沖突
git merge -Xours branchname
- 合并時(shí),有沖突爬早,用別人分支代碼解決沖突
git merge -Xtheirs branchname
- 使用此方式合并哼丈,留下合并記錄
git merge --no-ff -m "merge with no-ff" <branchname> git merge <branchname>
- 列出本地倉(cāng)庫(kù),分支前面有* 表示是當(dāng)前使用的分支
git branch
- 列出分支筛严,并且顯示每個(gè)分支最后一次提交信息
git branch -v
- 查看哪些分支已經(jīng)合并到當(dāng)前分支
git branch --merged
- 查看所有包含未合并工作的分支
git branch --no-merged
- 合并出現(xiàn)沖突醉旦,不想合并了,可以用下面命令撤銷(xiāo)
git merge --abort // 或者 git reset --hard HEAD
-
拉取代碼和推送代碼
git fetch // 遠(yuǎn)程倉(cāng)庫(kù)拉取到本地倉(cāng)庫(kù)(僅僅更新到本地倉(cāng)庫(kù)并沒(méi)有更新到工作區(qū)) git push // 從本地倉(cāng)庫(kù)推送到遠(yuǎn)程倉(cāng)庫(kù) git pull // 從遠(yuǎn)程倉(cāng)庫(kù)拉取到本地倉(cāng)庫(kù)并與本地進(jìn)行合并 // git pull <=> git fetch 后 git merge git pull --rebase // push本地倉(cāng)庫(kù)的commit之前桨啃,先把其它人的改動(dòng)更新到本地,這個(gè)比git pull有更嚴(yán)格的本地檢查 // git pull --rebase <=> git fetch 后 git rebase
-
回滾版本车胡、回滾暫存區(qū)文件
-
撤銷(xiāo)暫存區(qū)文件
- 撤銷(xiāo)暫存區(qū)文件xxx(其實(shí)就是把HEAD版本的xxx文件恢復(fù)到暫存區(qū))
git reset HEAD xxx
- 撤銷(xiāo)暫存區(qū)文件xxx(xxx可能會(huì)跟分支名相同,所以用--來(lái)表示后面是路徑或者文件)
git reset HEAD -- xxx // xxx是文件名字
- 把指定版本(xxx)的文件xxx.c恢復(fù)到暫存區(qū)
git reset xxx xxx.c // 第一個(gè)xxx是版本ID
根據(jù)上個(gè)命令顯示的commit對(duì)應(yīng)的值回滾
git reset --hard <commit> // --hard表示刪除工作區(qū)的所有在commit版本后的改動(dòng)
-
撤銷(xiāo)版本提交
- 恢復(fù)工作區(qū)(同步缺少的文件照瘾,對(duì)于已經(jīng)修改的文件只嘗試把倉(cāng)庫(kù)中版本合并到本地匈棘,保留本地修改)
git reset HEAD
- 恢復(fù)工作區(qū)(把本地修改的文件全部從硬盤(pán)刪除,同步成倉(cāng)庫(kù)中版本)
it reset --hard HEAD // git reset --hard HEAD 命令包括以下三個(gè)步驟 // 1.移動(dòng)HEAD分支指向上一個(gè)版本 // 2.使索引看起來(lái)像現(xiàn)在的HEAD // 3.使工作目錄看起來(lái)像索引
-
-
變基
git rebase
在Git中整合來(lái)自不同分支的修改主要有兩種方法:merge 和 rebase
變基:提取分支版本中引入的補(bǔ)丁和修改析命,然后合并到其他分支中主卫。變基操作的實(shí)質(zhì)是丟棄一些現(xiàn)有的提交,然后相應(yīng)地新建一些內(nèi)容一樣但實(shí)際上不同的提交鹃愤。
變基的準(zhǔn)則:不要對(duì)在倉(cāng)庫(kù)外有副本的分支執(zhí)行變基簇搅。
-
變基原理:
- 首先找到兩個(gè)分支(即當(dāng)前分支、變基操作的目標(biāo)基底分支)的最近共同祖先
- 然后對(duì)比當(dāng)前分支相對(duì)于該祖先的歷次提交软吐,提取相應(yīng)的修改并存為臨時(shí)文件
- 然后將當(dāng)前分支指向目標(biāo)基底
- 最后以此將之前另存為臨時(shí)文件的修改依序應(yīng)用到目標(biāo)基底分支
說(shuō)明
- 變基有風(fēng)險(xiǎn)瘩将,只在本地變基,不要變基服務(wù)器上的分支
- 變基操作的實(shí)質(zhì)是丟棄一些現(xiàn)有的提交凹耙,然后相應(yīng)地新建一些內(nèi)容一樣但實(shí)際上不同的提交姿现。如果有人依賴(lài)那些丟棄的提交,會(huì)產(chǎn)生問(wèn)題
- 如果有人變基服務(wù)器上的分支肖抱,其它人更新數(shù)據(jù)時(shí)要執(zhí)行
git pull --rebase
命令 - 只要你把變基命令當(dāng)作是在推送前清理提交使之整潔的工具备典,并且只在從未推送至共用倉(cāng)庫(kù)的提交上執(zhí)行變基命令,就不會(huì)有多大問(wèn)題虐沥。
git rebase
與 git merge
對(duì)比
- merge 是一個(gè)合并操作熊经,會(huì)將兩個(gè)分支的修改合并在一起泽艘,默認(rèn)操作的情況下會(huì)提交合并中修改的內(nèi)容
- merge 的提交歷史忠實(shí)地記錄了實(shí)際發(fā)生過(guò)什么欲险,關(guān)注點(diǎn)在真實(shí)的提交歷史上面
- rebase 并沒(méi)有進(jìn)行合并操作,只是提取了當(dāng)前分支的修改匹涮,將其復(fù)制在了目標(biāo)分支的最新提交后面
- rebase 的提交歷史反映了項(xiàng)目過(guò)程中發(fā)生了什么天试,關(guān)注點(diǎn)在開(kāi)發(fā)過(guò)程上面
- merge 與 rebase 都是非常強(qiáng)大的分支整合命令,沒(méi)有優(yōu)劣之分然低,使用哪一個(gè)應(yīng)由項(xiàng)目和團(tuán)隊(duì)的開(kāi)發(fā)需求決定
- 使用 merge 時(shí)應(yīng)考慮是采用 --no-ff 默認(rèn)操作喜每,生成一個(gè)對(duì)回顧提交歷史并不友好的合并記錄务唐,還是采用 --ff-only 方式
- rebase 操作會(huì)丟棄當(dāng)前分支已提交的 commit,故不要在已經(jīng) push 到遠(yuǎn)程带兜,和其他人正在協(xié)作開(kāi)發(fā)的分支上執(zhí)行 rebase 操作
- 當(dāng)有修改未 commit 時(shí)枫笛,不能進(jìn)行 rebase 操作,此時(shí)可以考慮先用 git stash 命令暫存
參考資料