Git的一些总结
.git 目錄結構
|── HEAD|── branches // 分支|── config // 配置|── description // 項目的描述|── hooks // 鉤子| |── pre-commit.sample| |── pre-push.sample| └── ...|── info| └── exclude // 類似.gitignore 用于排除文件|── objects // 存儲了blob,tree,commit對象| |── info| └── pack // 用于優(yōu)化倉庫體積,通過patch的方式└── refs |── heads└── tags // 標簽 復制代碼blob,tree,commit對象
blob
blob對象是文件內容的快照
$ git cat-file -t e0f5c6 blob$ git cat-file -p e0f5c6 reademe 復制代碼tree
tree對象描述了工作目錄,每個節(jié)點指向對應的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 復制代碼commit
commit對象是對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 復制代碼擴展 - 手動創(chuàng)建 commit
mktree // 從標準格式文本中創(chuàng)建一個樹read-tree // 從倉庫中讀取到 index 文件ls-files -s // 檢查當前 index 文件的結構write-tree // 通過這個 index 在倉庫中創(chuàng)建一個樹commit-tree // 將一個 tree 包裝為 commit 對象-p 指定父commit-m 添加描述branch -f master HEAD // 更改分支指向 復制代碼工作區(qū)和暫存區(qū)
工作區(qū)
工作區(qū)就是我們的工作目錄
暫存區(qū)
暫存區(qū)類似一個 tree 對象
$ 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 復制代碼當我們clone一個倉庫,或者檢出一個提交``的時候,此時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的時候做了什么
-
從文件中創(chuàng)建 blob
-
將 blob 寫入倉庫
-
更新 index
Commit的時候 做了什么
- 從 index 文件創(chuàng)建 tree
- 將 tree 寫入倉庫
- 創(chuàng)建一個 commit 對象將樹封裝起來
- 將 HEAD 作為新創(chuàng)建 commit 的父 commit,并更新 HEAD 未新創(chuàng)建的 commit
擴展:從一個 tree 更新工作區(qū)
$ git read-tree $TREE_HASH // 從一個 tree 寫入到 index $ git checkout-index -a // 從 index 檢出到工作區(qū) 復制代碼分支,標簽,HEAD
branch
# refs/heads/dev 47c871bb634324cfcc41e5a5affee6aa35301e03 // branch總是指向最新的提交$ git cat-file -t 47c871b commit 復制代碼標簽
# refs/tags/dev 47c871bb634324cfcc41e5a5affee6aa35301e03 // tag指向固定的提交// 同上 復制代碼HEAD
# HEAD ref: refs/heads/dev 此時HEAD隨分支前進$ git checkout HEAD^ # HEAD 47c871bb634324cfcc41e5a5affee6aa35301e03 分離HEAD,不隨分支前進// 同上 復制代碼merge
merge 常用于將兩條分支合并;
略過快速合并
標準的三方合并
上圖:
after:此時,你的開發(fā)歷史從一個更早的地方開始分叉開來(diverged)。 因為,master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些額外的工作。 出現這種情況的時候,Git 會使用兩個分支的末端所指的快照(C4 和 C5)以及這兩個分支的工作祖先(C2),做一個簡單的三方合并。
我們分析一下兩條分支合并的過程
- 找到兩條分支對應的commit對象;
- 找到兩個commit對象共同的祖先commit對象;
- 通過兩個commit對象下的tree對象對比每個blob對象的差異;
- 如果不同并且其中一個blob對象與祖先相同,則默認自動合并;
- 如果不同并且都不與祖先相同
- 修改同一處地方 產生conflict,需要手動合并
- 沒有修改同一處地方 自動合并
查看合并基底
$ git merge-base master iss53 // C2的HASH_ID 復制代碼合并
* master $ git merge iss53 // 此時產生沖突$ git merge --abort // 撤銷合并 $ git commit -a // 解決沖突后,提交 復制代碼查看沖突
$ 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 復制代碼小技巧
1.有時候我們格式化文件之后,在之后的合并中會產生很多沖突,有沒有辦法忽略空格上的更改嗎?
git merge [branch] --ignore-space-change git merge [branch] -s recursive -X ignore-space-change-s 選擇策略-x 策略選項 復制代碼2.通過revert撤銷合并,后面再次merge的時候提示已經合并過了?
再次revert,或者通過新建一個相同的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 // 此時合并就沒問題了 復制代碼rebase
繼續(xù)上圖:
after:首先回到兩個分支最近的共同祖先,根據當前分支(也就是要進行衍合的分支 experiment)后續(xù)的歷次提交對象(這里只有一個 C4),生成一系列文件補丁;
然后以基底分支(也就是主干分支master)最后一個提交對象(C3)為新的出發(fā)點,逐個應用之前準備好的補丁文件;
最后會生成一個新的合并提交對象(C4'),從而改寫 experiment 的提交歷史,使它成為 master 分支的直接下游
需要注意的點
-
rebase是逐步應用補丁,可能會有多個rebase階段,每次解決沖突都需要:
$ git add . $ git rebase --continue 復制代碼 -
rebase完成之后會丟失之前的對C4的指向,導致C4無法再被找到,此時C4存在于.git/objects中,等待下次gc被回收;
-
rebase類似于多個merge過程,比如已被應用的補丁產生的新的提交會與下一個補丁進行新的三方合并;
黃金準則 - 公用分支不可作為衍合分支
上圖:
rebase的濫用可能會導致混亂的提交歷史交互式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合并到上一個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 復制代碼- 如果之前錯誤的合并了某次提交,可以通過drop刪除該提交
- 如果想修改某次提交的信息,可以將該提交對應的狀態(tài)改為edit
cherry-pick - 摘櫻桃
cherry-pick常用于將某些提交應用于其他的分支;
cherry-pick可以理解為”挑揀”提交,它會獲取某一個分支的單筆提交,并作為一個新的提交引入到你當前分支上。 當我們需要在本地合入其他分支的提交時,如果我們不想對整個分支進行合并,而是只想將某一次提交合入到本地當前分支上,那么就要使用cherry-pick了。
# cherry-pick的方式與 merge 有所不同,merge 的過程相當于兩個 tree 的差異對比,而cherry-pick更像是應用更改;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ā)生的改變,并將這些改變應用到主分支上,cherry-pick產生的三方合并,其merge-base是該提交的父提交;
* branch2 $ git cherry-pick B...H // 三點語法,前開后閉第一次 base:B our:E their: F 產生提交 F' 第二次 base:F our:F' their: G 產生提交 G' 第三次 base:G our:G' their: H 產生提交 H' 復制代碼轉載于:https://juejin.im/post/5b8907c851882542d14da61c
總結
- 上一篇: Java 打包 FatJar 方法小结
- 下一篇: 梦到抱着两只猫是什么意思