Git的一些总结
.git 目錄結(jié)構(gòu)
|── HEAD|── branches // 分支|── config // 配置|── description // 項(xiàng)目的描述|── hooks // 鉤子| |── pre-commit.sample| |── pre-push.sample| └── ...|── info| └── exclude // 類似.gitignore 用于排除文件|── objects // 存儲(chǔ)了blob,tree,commit對(duì)象| |── info| └── pack // 用于優(yōu)化倉庫體積,通過patch的方式└── refs |── heads└── tags // 標(biāo)簽 復(fù)制代碼blob,tree,commit對(duì)象
blob
blob對(duì)象是文件內(nèi)容的快照
$ git cat-file -t e0f5c6 blob$ git cat-file -p e0f5c6 reademe 復(fù)制代碼tree
tree對(duì)象描述了工作目錄,每個(gè)節(jié)點(diǎn)指向?qū)?yīng)的blob或者子tree
$ git cat-file -t 443322 tree$ git cat-file -p 443322 100644 blob 723ef36f4e4f32c4560383aa5987c575a30c6535 .gitignore 100644 blob 56a6051ca2b02b04ef92d5150c9ef600403cb1de 1 100644 blob d218c7660f5672293d2b2241741f2e3f25008b9e 2 040000 tree 74080098daf8a1fa7368c2feac12cfab0e648d02 3 100644 blob e0f5c6d282792ef63ea012f200f5d7749b084fa0 README.md 復(fù)制代碼commit
commit對(duì)象是對(duì)tree的封裝
$ git cat-file -t 186f17807d commit$ git cat-file -p 186f17807d tree 4433224cb7cbb72dae00b5138c8961522c531707 parent 45d0db32885c40a8c3244fa6ec24df2d7a631a3c author 孫健 <jian.sun@ymm56.com> 1535621955 +0800 committer 孫健 <jian.sun@ymm56.com> 1535621955 +0800 復(fù)制代碼擴(kuò)展 - 手動(dòng)創(chuàng)建 commit
mktree // 從標(biāo)準(zhǔn)格式文本中創(chuàng)建一個(gè)樹read-tree // 從倉庫中讀取到 index 文件ls-files -s // 檢查當(dāng)前 index 文件的結(jié)構(gòu)write-tree // 通過這個(gè) index 在倉庫中創(chuàng)建一個(gè)樹commit-tree // 將一個(gè) tree 包裝為 commit 對(duì)象-p 指定父commit-m 添加描述branch -f master HEAD // 更改分支指向 復(fù)制代碼工作區(qū)和暫存區(qū)
工作區(qū)
工作區(qū)就是我們的工作目錄
暫存區(qū)
暫存區(qū)類似一個(gè) tree 對(duì)象
$ git ls-files -s 100644 723ef36f4e4f32c4560383aa5987c575a30c6535 0 .gitignore 100644 56a6051ca2b02b04ef92d5150c9ef600403cb1de 0 1 100644 d218c7660f5672293d2b2241741f2e3f25008b9e 0 2 100644 00750edc07d6415dcc07ae0351e9397b0222b7ba 0 3/3 100644 e0f5c6d282792ef63ea012f200f5d7749b084fa0 0 README.md 復(fù)制代碼當(dāng)我們clone一個(gè)倉庫,或者檢出一個(gè)提交``的時(shí)候,此時(shí)HEAD == 暫存區(qū) == 工作區(qū)
-
工作區(qū)修改,未添加到暫存區(qū) - HEAD == 暫存區(qū) != 工作區(qū)
-
工作區(qū)修改,添加到暫存區(qū) - HEAD != 暫存區(qū) == 工作區(qū)
-
工作區(qū)修改,添加到暫存區(qū),提交到倉庫 - HEAD == 暫存區(qū) == 工作區(qū) - nothing to commit, working tree clean
add的時(shí)候做了什么
-
從文件中創(chuàng)建 blob
-
將 blob 寫入倉庫
-
更新 index
Commit的時(shí)候 做了什么
- 從 index 文件創(chuàng)建 tree
- 將 tree 寫入倉庫
- 創(chuàng)建一個(gè) commit 對(duì)象將樹封裝起來
- 將 HEAD 作為新創(chuàng)建 commit 的父 commit,并更新 HEAD 未新創(chuàng)建的 commit
擴(kuò)展:從一個(gè) tree 更新工作區(qū)
$ git read-tree $TREE_HASH // 從一個(gè) tree 寫入到 index $ git checkout-index -a // 從 index 檢出到工作區(qū) 復(fù)制代碼分支,標(biāo)簽,HEAD
branch
# refs/heads/dev 47c871bb634324cfcc41e5a5affee6aa35301e03 // branch總是指向最新的提交$ git cat-file -t 47c871b commit 復(fù)制代碼標(biāo)簽
# refs/tags/dev 47c871bb634324cfcc41e5a5affee6aa35301e03 // tag指向固定的提交// 同上 復(fù)制代碼HEAD
# HEAD ref: refs/heads/dev 此時(shí)HEAD隨分支前進(jìn)$ git checkout HEAD^ # HEAD 47c871bb634324cfcc41e5a5affee6aa35301e03 分離HEAD,不隨分支前進(jìn)// 同上 復(fù)制代碼merge
merge 常用于將兩條分支合并;
略過快速合并
標(biāo)準(zhǔn)的三方合并
上圖:
after:此時(shí),你的開發(fā)歷史從一個(gè)更早的地方開始分叉開來(diverged)。 因?yàn)?#xff0c;master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些額外的工作。 出現(xiàn)這種情況的時(shí)候,Git 會(huì)使用兩個(gè)分支的末端所指的快照(C4 和 C5)以及這兩個(gè)分支的工作祖先(C2),做一個(gè)簡單的三方合并。
我們分析一下兩條分支合并的過程
- 找到兩條分支對(duì)應(yīng)的commit對(duì)象;
- 找到兩個(gè)commit對(duì)象共同的祖先commit對(duì)象;
- 通過兩個(gè)commit對(duì)象下的tree對(duì)象對(duì)比每個(gè)blob對(duì)象的差異;
- 如果不同并且其中一個(gè)blob對(duì)象與祖先相同,則默認(rèn)自動(dòng)合并;
- 如果不同并且都不與祖先相同
- 修改同一處地方 產(chǎn)生conflict,需要手動(dòng)合并
- 沒有修改同一處地方 自動(dòng)合并
查看合并基底
$ git merge-base master iss53 // C2的HASH_ID 復(fù)制代碼合并
* master $ git merge iss53 // 此時(shí)產(chǎn)生沖突$ git merge --abort // 撤銷合并 $ git commit -a // 解決沖突后,提交 復(fù)制代碼查看沖突
$ git show :1:hello.rb > hello.common.rb // 祖先 $ git show :2:hello.rb > hello.ours.rb // 我 $ git show :3:hello.rb > hello.theirs.rb // 他$ git ls-files -u 復(fù)制代碼小技巧
1.有時(shí)候我們格式化文件之后,在之后的合并中會(huì)產(chǎn)生很多沖突,有沒有辦法忽略空格上的更改嗎?
git merge [branch] --ignore-space-change git merge [branch] -s recursive -X ignore-space-change-s 選擇策略-x 策略選項(xiàng) 復(fù)制代碼2.通過revert撤銷合并,后面再次merge的時(shí)候提示已經(jīng)合并過了?
再次revert,或者通過新建一個(gè)相同的commit,指定其父commit;
$ git commit-tree $TREE_HASH -p $PARENT_HASH // 需要合并的 commit 的tree hash 和其parent hash $ git branch -f $BRANCH $NEW_COMMIT_HASH // 將分支指向新的 commit $ git merge $branch // 此時(shí)合并就沒問題了 復(fù)制代碼rebase
繼續(xù)上圖:
after:首先回到兩個(gè)分支最近的共同祖先,根據(jù)當(dāng)前分支(也就是要進(jìn)行衍合的分支 experiment)后續(xù)的歷次提交對(duì)象(這里只有一個(gè) C4),生成一系列文件補(bǔ)丁;
然后以基底分支(也就是主干分支master)最后一個(gè)提交對(duì)象(C3)為新的出發(fā)點(diǎn),逐個(gè)應(yīng)用之前準(zhǔn)備好的補(bǔ)丁文件;
最后會(huì)生成一個(gè)新的合并提交對(duì)象(C4'),從而改寫 experiment 的提交歷史,使它成為 master 分支的直接下游
需要注意的點(diǎn)
-
rebase是逐步應(yīng)用補(bǔ)丁,可能會(huì)有多個(gè)rebase階段,每次解決沖突都需要:
$ git add . $ git rebase --continue 復(fù)制代碼 -
rebase完成之后會(huì)丟失之前的對(duì)C4的指向,導(dǎo)致C4無法再被找到,此時(shí)C4存在于.git/objects中,等待下次gc被回收;
-
rebase類似于多個(gè)merge過程,比如已被應(yīng)用的補(bǔ)丁產(chǎn)生的新的提交會(huì)與下一個(gè)補(bǔ)丁進(jìn)行新的三方合并;
黃金準(zhǔn)則 - 公用分支不可作為衍合分支
上圖:
rebase的濫用可能會(huì)導(dǎo)致混亂的提交歷史交互式rebase
$ git rebase -i HEAD~6pick fb257ad9 某次提交說明 pick fb257ad9 某次提交說明 drop fb257ad9 某次提交說明# Rebase a0daba3d..fb257ad9 onto a0daba3d (1 command) # # Commands: # p, pick <commit> = use commit # r, reword <commit> = use commit, but edit the commit message # e, edit <commit> = use commit, but stop for amending // 修改某次commit # s, squash <commit> = use commit, but meld into previous commit // 將commit合并到上一個(gè)commit # f, fixup <commit> = like "squash", but discard this commit's log message # x, exec <command> = run command (the rest of the line) using shell # d, drop <commit> = remove commit // 刪除某次commit # l, label <label> = label current HEAD with a name # t, reset <label> = reset HEAD to a label # m, merge [-C <commit> | -c <commit>] <label> [# <oneline>] # . create a merge commit using the original merge commit's 復(fù)制代碼- 如果之前錯(cuò)誤的合并了某次提交,可以通過drop刪除該提交
- 如果想修改某次提交的信息,可以將該提交對(duì)應(yīng)的狀態(tài)改為edit
cherry-pick - 摘櫻桃
cherry-pick常用于將某些提交應(yīng)用于其他的分支;
cherry-pick可以理解為”挑揀”提交,它會(huì)獲取某一個(gè)分支的單筆提交,并作為一個(gè)新的提交引入到你當(dāng)前分支上。 當(dāng)我們需要在本地合入其他分支的提交時(shí),如果我們不想對(duì)整個(gè)分支進(jìn)行合并,而是只想將某一次提交合入到本地當(dāng)前分支上,那么就要使用cherry-pick了。
# cherry-pick的方式與 merge 有所不同,merge 的過程相當(dāng)于兩個(gè) tree 的差異對(duì)比,而cherry-pick更像是應(yīng)用更改;C<---D<---E branch2/ master A<---B \F<---G<---H branch3|HEAD*** after ***C<---D<---E<---F'<---G'<---H' branch2/ master A<---B \F<---G<---H branch3|HEAD 復(fù)制代碼當(dāng)我們應(yīng)用某個(gè)提交的時(shí)候,實(shí)際上會(huì)通過該提交與其父提交的差異得到發(fā)生的改變,并將這些改變應(yīng)用到主分支上,cherry-pick產(chǎn)生的三方合并,其merge-base是該提交的父提交;
* branch2 $ git cherry-pick B...H // 三點(diǎn)語法,前開后閉第一次 base:B our:E their: F 產(chǎn)生提交 F' 第二次 base:F our:F' their: G 產(chǎn)生提交 G' 第三次 base:G our:G' their: H 產(chǎn)生提交 H' 復(fù)制代碼轉(zhuǎn)載于:https://juejin.im/post/5b8907c851882542d14da61c
總結(jié)
- 上一篇: Java 打包 FatJar 方法小结
- 下一篇: 梦到抱着两只猫是什么意思