Git不權(quán)威總結(jié)
歡迎閱讀
本文僅總結(jié)使用git最基本的概念,點(diǎn)到為止的原理,和最常用的操作,更深入的學(xué)習(xí)可以參考我附上的延伸資料
基本知識(shí)
歷史背景
git是一款版本管理工具。在還沒(méi)有互聯(lián)網(wǎng)的時(shí)代,單機(jī)上就已經(jīng)出現(xiàn)了版本管理工具(如VCS),但開(kāi)發(fā)者互相之間難以協(xié)同。后來(lái)有了集中式版本管理工具(如SVN),由一個(gè)集中的服務(wù)器來(lái)維護(hù)版本,其他人不斷向其提交修改,這樣做的不方便之處在于一旦在沒(méi)有網(wǎng)絡(luò)的環(huán)境,便無(wú)法提交代碼;另外單節(jié)點(diǎn)的集中服務(wù)器一旦出故障,后果不堪設(shè)想;而且在開(kāi)源運(yùn)動(dòng)發(fā)展和互聯(lián)網(wǎng)更加普及的時(shí)代背景下,越來(lái)越多的項(xiàng)目需要遍布在全球各地的開(kāi)發(fā)者的協(xié)同,很難維護(hù)一個(gè)穩(wěn)定的超級(jí)服務(wù)器,所以分布式版本管理工具git便應(yīng)運(yùn)而生
git的作者是大名鼎鼎linus,在設(shè)計(jì)之初linus就為git提出了幾個(gè)目標(biāo)
- 操作速度快
- 設(shè)計(jì)簡(jiǎn)單
- 強(qiáng)力支持非線性的開(kāi)發(fā)模型(即允許上千個(gè)并行開(kāi)發(fā)的分支)
- 完全分布式
- 能夠管理超大規(guī)模的項(xiàng)目(如Linux內(nèi)核)
就我實(shí)際使用經(jīng)驗(yàn)來(lái)看,這些目標(biāo)基本都實(shí)現(xiàn)了
整體認(rèn)識(shí)
我的技術(shù)觀中,從大的層面來(lái)看,在解決同一個(gè)難題的技術(shù)中,后來(lái)者會(huì)更易用。所以我們不要把git想的很難,還沒(méi)學(xué)就打退堂鼓了。這也是我覺(jué)得首先需要對(duì)git有一個(gè)整體認(rèn)識(shí)的必要性,你會(huì)發(fā)現(xiàn)git的總體設(shè)計(jì)是很簡(jiǎn)單容易理解的
git是一個(gè)代碼版本管理工具
每個(gè)文件的每一次修改都會(huì)被記錄下來(lái)
在整體修改完成并提交后,會(huì)在git中形成一個(gè)項(xiàng)目的完整快照,并且永久保存在git庫(kù)中
一個(gè)個(gè)的修改按照先后順序形成一條分支,每個(gè)快照都是一個(gè)節(jié)點(diǎn)
你可以隨時(shí)將整個(gè)項(xiàng)目或者某一個(gè)文件恢復(fù)到某一個(gè)節(jié)點(diǎn)的版本
這樣的分支你可以從任何一個(gè)節(jié)點(diǎn)發(fā)起上千條,并隨時(shí)將他們合并
你的協(xié)作者可以克隆你的git庫(kù),與你的一模一樣,可以隨時(shí)將他的某一個(gè)分支與你的一個(gè)分支合并

我理解git的核心功能和用法其實(shí)就這么多,其他的都無(wú)非是對(duì)各個(gè)細(xì)節(jié)的完善,如各種區(qū)的劃分、checkout、reset、log、config等
.git
在項(xiàng)目根目錄中執(zhí)行git init便創(chuàng)建了一個(gè)git庫(kù),.git文件夾維護(hù)了git庫(kù)的全部信息。這里介紹幾個(gè)主要概念,方便對(duì)git如何維護(hù)版本的理解
SHA-1校驗(yàn)和
git為每一次提交、每一個(gè)文件對(duì)象樹(shù)、每一個(gè)修改都維護(hù)了唯一的id,整個(gè)id是文件內(nèi)容和頭信息的SHA-1校驗(yàn)和,是一個(gè)40位的字符串,如5453545dccd33565a585ffe5f53fda3e067b84d8。既能唯一定位對(duì)象,也能校驗(yàn)對(duì)象內(nèi)容是否發(fā)生改變
文件對(duì)象blob,樹(shù)對(duì)象tree,提交對(duì)象commit
簡(jiǎn)單說(shuō)每一次提交commit,都會(huì)生成一個(gè)commit對(duì)象,它指向一個(gè)tree對(duì)象,這個(gè)tree對(duì)象指向了項(xiàng)目中的所有blob對(duì)象和目錄對(duì)象(也是個(gè)tree),注意文件是版本的,對(duì)應(yīng)了每次提交,比如某一個(gè)文件在項(xiàng)目整個(gè)周期中從來(lái)沒(méi)修改過(guò),那么雖然每個(gè)快照他的版本都指向了最初的blob對(duì)象。整個(gè)結(jié)構(gòu)是這個(gè)樣子:

引用
.git除了對(duì)象外,另一些重要文件是引用,包括常見(jiàn)的HEAD、tag等,這些文件中指向了具體的一個(gè)commit對(duì)象,類(lèi)似助記符吧
工作區(qū)劃分
為了有序的將文件修改提交入git庫(kù),git為項(xiàng)目劃分了三個(gè)區(qū)域
- 工作區(qū),所有新建、剛修改的文件都索引在這個(gè)區(qū)。你也可以檢出(checkout)某個(gè)版本的文件到工作區(qū)
- 暫存區(qū),你可以將工作區(qū)中的任何變動(dòng)有選擇的加入暫存區(qū),可以理解為提交前的緩存區(qū)
- 版本庫(kù),暫存區(qū)中修改提交后便進(jìn)入了版本庫(kù),被永久記錄了下來(lái)

branch
分支是git的殺手锏,非常輕量級(jí),用過(guò)svn的同學(xué)肯定會(huì)驚訝于git分支操作之快捷。分支之所以好用,因?yàn)槲覀儗?shí)際研發(fā)中確實(shí)是真實(shí)存在多個(gè)“分支”的。首先線上當(dāng)前運(yùn)行的代碼屬于一個(gè)已經(jīng)發(fā)布的“分支”,你本地開(kāi)發(fā)的是研發(fā)“分支”,pm可能隨時(shí)要求你給當(dāng)前線上打補(bǔ)丁或者修bug,此時(shí)你需要基于線上版本而不是你的研發(fā)版本進(jìn)行處理。假如沒(méi)有branch模型,這些操作就會(huì)非常麻煩而且容易出錯(cuò)。git便鼓勵(lì)你頻繁的創(chuàng)建、合并分支來(lái)完成各種研發(fā)工作
一個(gè)比較良好的實(shí)踐是
- 一個(gè)release分支,即當(dāng)前線上運(yùn)行的分支
- 一個(gè)鏡像分支,用于合并不同待上線需求,進(jìn)行發(fā)布前校驗(yàn)
- 一批dev分支,用于開(kāi)發(fā)者對(duì)功能進(jìn)行合并調(diào)試
- 一批feature分支,用于開(kāi)發(fā)者自己功能開(kāi)發(fā)
- 臨時(shí)的hot fix分支,用于緊急修復(fù)bug、打補(bǔ)丁
遠(yuǎn)程庫(kù)
分布式是git的主要特點(diǎn),允許分布式協(xié)作。其他人可以克隆你的git庫(kù),你也可以把其他人的git庫(kù)添加到的你遠(yuǎn)程源中
- 每個(gè)庫(kù)都是完整且獨(dú)立的,包含了項(xiàng)目的所有信息,包括歷史修改
- 庫(kù)之間是可以進(jìn)行分支合并操作的,以此進(jìn)行代碼交換
- 為了方便協(xié)作,目前比較流行的是在github中創(chuàng)建一個(gè)庫(kù),然后感興趣者可以clone你的庫(kù),做一些修改再提交你進(jìn)行審查合并
實(shí)戰(zhàn)操作
前面總計(jì)了這么多知識(shí),其實(shí)都有意回避了具體的操作,因?yàn)槲艺J(rèn)為只要道理搞明白了,這些操作都只是一個(gè)熟能生巧的過(guò)程。下面我按照常用程度對(duì)一些非常實(shí)用的操作進(jìn)行總結(jié)
工作區(qū)
# add
git add file/dir/* # 將新建文件,或者改動(dòng)加入暫存區(qū)。-p參數(shù)可以精細(xì)操作具體修改而不是整個(gè)文件
# commit
git commit # 將暫存區(qū)中修改提交入git庫(kù)
git commit -m '備注'
git commit -a # 同時(shí)將工作區(qū)中已索引文件的修改都提交入庫(kù),注意不包括新增文件(即Untracked files)
git commit --amend # 本次提交與上一次提交合并,最終只形成一個(gè)ci
# diff
git diff # 比較工作區(qū)中file與git庫(kù)的區(qū)別
git diff --cached/staged file # 比較暫存區(qū)中file與git庫(kù)的區(qū)別
# rm
git rm file # 物理刪除 + 從git中移除
git rm --cached file # 僅從git庫(kù)中移除,即不再跟蹤文件
# stash
git stash # 將工作區(qū)現(xiàn)場(chǎng)(已跟蹤文件)儲(chǔ)藏起來(lái),等以后恢復(fù)后繼續(xù)工作
git stash list # 查看保存的工作現(xiàn)場(chǎng)
git stash apply # 恢復(fù)工作現(xiàn)場(chǎng)
git stash drop # 刪除stash內(nèi)容
git stash pop # 恢復(fù)的同時(shí)直接刪除stash內(nèi)容
git stash apply stash@{0} # 恢復(fù)指定的工作現(xiàn)場(chǎng),當(dāng)你保存了不只一份工作現(xiàn)場(chǎng)時(shí)
分支
# branch
git branch new_branch
git branch -m old new # 重命名分支
git branch -d new_branch # 刪除分支
git branch -D test # 強(qiáng)制刪除test分支
git checkout -b new_branch # 創(chuàng)建分支并切過(guò)去
git checkout -b test dev # 基于dev新建test分支,并切換
git branch # 查看分支
git branch -r # 列出遠(yuǎn)端分支
git branch -a # 列出所有分支
# merge
git branch --merge # 查看已經(jīng)合并到當(dāng)前分支的分支
git branch --no-merge # 查看未合并到當(dāng)前分支的分支
git merge test # 將test分支合并到當(dāng)前分支
git merge --squash test # 合并壓縮,將test上的commit壓縮為一條
# 沖突
# git中的沒(méi)有類(lèi)似svn中的conflic概念,本質(zhì)是“自動(dòng)合并失敗,無(wú)法add”,所以需要用戶編輯代碼后,自行add
# cherry-pick
git cherry-pick commit-id # 將本地其他分支的一個(gè)提交應(yīng)用到當(dāng)前分支
rebase
變基,將一個(gè)分支的修改在目標(biāo)分支上重放一遍(原來(lái)的分支需要手動(dòng)清除),這樣可以達(dá)到合并的效果,同時(shí)分支樹(shù)是線性的,更加美觀、易維護(hù)
git rebase master # 將master上超前的提交,變基到當(dāng)前分支
git rebase master feature # 將master變基到指定分支
不要使用變基……
如果是公開(kāi)且與其他人共享的分支,那么重寫(xiě)公開(kāi)的共享分支 將會(huì)搞砸團(tuán)隊(duì)中的其他會(huì)員
要是提交分支的精確歷史重要(因?yàn)樽兓鶎⒅貙?xiě)提交歷史)
根據(jù)上述準(zhǔn)則,我會(huì)針對(duì)短期生命的本地分支使用變基,而對(duì)公開(kāi)倉(cāng)庫(kù)的分支使用合并
配置管理
# config
git config --[global|system|local] alias.co checkout # 創(chuàng)建別名
git config --[global|system|local] alias.br branch
git config --[global|system|local] alias.ci commit
git config --[global|system|local] alias.st status
git config --global alias.lg "log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit"
git config --global alias.gpush '!f() { : push ; r=$1; [[ -z $r ]] && r=origin; b=$2; t=$(awk "{ print \$2 }" $(git rev-parse --git-dir)/HEAD); t=${t#refs/heads/}; [[ -z $b ]] && b=$t; cmd="git push $r HEAD:refs/for/$b%topic=$t"; echo $cmd; echo; $cmd; }; f'
# 高亮
git config --global color.status auto
git config --global color.branch auto
git config color.ui true
# 配置用戶名 郵箱
git config --global user.name 'liufuxin'
git config --global user.email 'liufuxin@baidu.com'
# 查看配置的信息
git config --list
# .gitignore
# 可以配置git忽略哪些文件
- \* 通配符
- . 無(wú)特殊意義
- ? 單個(gè)字符
- [a-z]
- ! 反模式
內(nèi)容跟蹤
# blame
git blame file # 查看文件的每一行的版本和修改人信息
# log
git show commit-id [file] # 查看某次提交修改的內(nèi)容,加file單看某個(gè)文件
git log -p FILE_NAME # 查看某個(gè)文件提交記錄
# 單行顯示
git log --pretty=oneline
git log --pretty=oneline --max-count=2
git log --pretty=oneline --since='5 minutes ago'
git log --pretty=oneline --until='5 minutes ago'
git log --pretty=oneline --author=<your name>
git log --pretty=oneline --all
# 樹(shù)形結(jié)構(gòu),更加美觀
git log --all --pretty=format:'%h %cd %s (%an)' --since='7 days ago' --author=liufuxin
git log --graph --decorate --oneline --simplify-by-decoration --all
# reflog
git reflog # 顯示最近操作的commit id,可用于誤舍棄分支
數(shù)據(jù)恢復(fù)
# checkout
git checkout -- file # 使用暫存區(qū)或者git庫(kù)替換工作目錄中的文件
git checkout HEAD file # 使用git庫(kù)替換工作目錄
# revert
git revert commit-id # 再生成一個(gè)撤銷(xiāo)最近一次提交的提交
# reset
# reset可以讓HEAD重新指向過(guò)去的某次ci,后續(xù)分支從此重新向前
# 舊的分支就成了不可見(jiàn)分支從分支,而且會(huì)在下次垃圾回收時(shí)回收
# reset丟失的分支將只能通過(guò)commit-id獲取,結(jié)合reflog可以恢復(fù)
git reset --soft commit-id # 將HEAD移動(dòng)到指定的提交對(duì)象,同時(shí)將HEAD移動(dòng)過(guò)程中的修改,都恢復(fù)到stage,work保持不變
git reset --mixed commit-id # 將HEAD移動(dòng)到指定的repo,同時(shí)將HEAD移動(dòng)過(guò)程中的修改,都恢復(fù)到work
git reset --hard commit-id # 將HEAD移動(dòng)到指定的repo,同時(shí)將HEAD移動(dòng)過(guò)程中的修改,都拋棄,work保持commit-id
git reset commit-id [--] file # 特殊形式,只能使用mixed模式,目標(biāo)是文件或目錄
遠(yuǎn)程倉(cāng)庫(kù)
# fetch
git remote add origin git@github.com:yanhaijing/test.git # 添加源
git push -u origin master # push同時(shí)設(shè)置默認(rèn)跟蹤分支
git pull # 與git fetch; git merge;等價(jià)
git clone git://github.com/schacon/grit.git mypro # 克隆到自定義文件夾
# 刪除遠(yuǎn)程文件
git rm --cached filename/-r directory
git commit "xxxx"
git push