本文來自半個月前我在我司內部進行的分享。一直以爲 Git 是一個很是值得深刻學習的工具,準備此次內部分享用了好久的時間,不過關於Git的講解仍是有不少不足之處,你們有什麼建議,歡迎來本文的 githug地址討論,咱們一塊兒把 Git 學得更深一點。
Git是一個CLI(Common line interface),咱們與其的交互經常發生在命令行,(固然有時候也會使用GUI,如sourcetree,Github等等),因爲咱們的使用方式,咱們經常會忽略git倉庫自己是一個沒那麼複雜的文件系統,咱們輸入git命令時其實就是對這個文件系統進行操做。html
對 Git 文件系統的定義有一種更專業的說法,「從根本上來說 Git 是一個內容尋址(content-addressable)文件系統,並在此之上提供了一個版本控制系統的用戶界面」。git
…from [Git - 關於版本控制](https://git-scm.com/book/zh/v2/%E8%B5%B7%E6%AD%A5-%E5%85%B3%E4%BA%8E%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6)
找一個空文件夾,執行git init
後咱們會發現其中會多出一個隱藏文件夾.git
,其文件結構以下:github
➜ mkdir gitDemo && cd gitDemo && git init && tree -a Initialized empty Git repository in /Users/zhangwang/Documents/personal/Test/gitDemo/.git/ . └── .git ├── HEAD ├── branches ├── config ├── description ├── hooks │ ├── applypatch-msg.sample │ ├── commit-msg.sample │ ├── post-update.sample │ ├── pre-applypatch.sample │ ├── pre-commit.sample │ ├── pre-push.sample │ ├── pre-rebase.sample │ ├── pre-receive.sample │ ├── prepare-commit-msg.sample │ └── update.sample ├── info │ └── exclude ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 10 directories, 14 files
幾乎 Git 相關的全部操做都和這個文件夾相關,若是你是第一次見到這個文件系統,以爲陌生也很正常,不過讀完本文,每一項都會變得清晰了。算法
咱們先想另一個問題,作爲版本控制系統的 Git ,究竟會存儲那些內容在上述文件系統中,這些內容又是如何被存儲的呢?數據庫
「分支」,「commit」,「原始的文件」,「diff」…緩存
…from 熱烈的討論中
好吧,不賣關子,實際上在上述文件系統中 Git 爲咱們存儲了五種對象,這些對象存儲在/objects
和/refs
文件夾中。安全
Blobs
是Git中最基礎的數據類型,一個blob
對象就是一堆字節,一般是一個文件的二進制表示
tree
,有點相似於目錄,其內容由對其它tree
及blobs
的指向構成;
commit
,指向一個樹對象,幷包含一些代表做者及父 commit 的元數據
Tag
,指向一個commit對象,幷包含一些元數據
References
,指向一個commit
或者tag
對象
blobs
, tree
, commit
,以及聲明式的 tag
這四種對象會存儲在 .git/object
文件夾中。這些對象的名稱是一段40位的哈希值,此名稱由其內容依據sha-1
算法生成,具體到.git/object
文件夾下,會取該hash值的前 2 位爲子文件夾名稱,剩餘 38 位爲文件名,這四類對象都是二進制文件,其內容格式依據類型有所不一樣。下面咱們一項項來看:bash
Blobs
咱們都經常使用git add
這個命令,也都據說過,此命令會把文件添加到緩存區(index
)。可是有沒有想過「把文件添加到緩存區」是一種很奇怪的說法,若是說這個文件咱們曾經add
過,爲何咱們須要在修改事後再次添加到緩存區?服務器
咱們確實須要把文件從新添加到緩存區,其實每次修改後的文件,對 git 來講都是一個新文件,每次 add
一個文件,就會添加一個 Blob
對象。併發
blobs
是二進制文件,咱們不能直接查看,不過經過 Git 提供的一些更底層的命令如 git show [hash]
或者 git cat-file -p [hash]
咱們就能夠查看 .git/object
文件夾下任一文件的內容。
➜ git cat-file -p 47ca abc 456
從上面的內容中就能夠看出,blob
對象中僅僅存儲了文件的內容,若是咱們想要完整還原工做區的內容,咱們還須要把這些文件有序組合起來,這就涉及到 Git 中存儲的另一個重要的對象:tree
。
Tree objects
tree
對象記錄了咱們的文件結構,更形象的說法是,某個 tree
對象記錄了某個文件夾的結構,包含文件以及子文件夾。tree
對象的名稱也是一個40位的哈希值,文件名依據內容生成,所以若是一個文件夾中的結構有所改變,在 .git/object/
中就會出現一個新的 tree object
, 一個典型的 tree object
的內容以下:
➜ git ls-tree bb4a8638f1431e9832cfe149d7f32f31ebaa77ef 100644 blob 4be9cb419da86f9cbdc6d2ad4db763999a0b86f2 .gitignore 040000 tree dccea6a66df035ac506ab8ca6d2735f9b64f66c1 01_introduction_to_algorithms 040000 tree 363813a5406b072ec65867c6189e6894b152a7e5 02_selection_sort 040000 tree 5efc07910021b8a2de0291218cb1ec2555d06589 03_recursion 040000 tree cc15fd67f464c29495437aa81868be67cd9688b2 04_quicksort 040000 tree 9f09206e367567bf3fe0f9b96f3609eb929840f1 05_hash_tables 040000 tree c8b7b793b0318d13b25098548effde96fc9f1377 06_breadth-first_search 040000 tree 7f111006c8a37eab06a3d8931e83b00463ae0518 07_dijkstras_algorithm 040000 tree 9f6d831e5880716e0eda2d9312ea2689a8cc1439 08_greedy_algorithms 040000 tree 692a9b39721744730ad1b29c052e288aeb89c2ac 09_dynamic_programming 100644 blob 290689b29c24d3406a1ed863077a01393ae2aff3 LICENSE 100644 blob 9017b1121945799e97825f996bc0cefe3422cbaf README.md 040000 tree ce710aa0b6c23b7f81dbd582aad6f9435988a8b4 images
咱們能夠看過,tree
中包含兩種類型的文件,tree
和 blob
,這就把文件有序的組合起來了,若是咱們知道了根 tree
(能夠理解爲root
文件夾對應的tree
),咱們就有能力依據此tree
還原整個工做區。
可能咱們很早就據說過 Git 中的每個 commit
存儲的都是一個「快照」。理解了tree
對象,咱們就能夠較容易的理解「快照」這個詞了 ,接下來咱們看看 commit object
。
咱們知道,commit
記錄了咱們的提交歷史,存儲着提交時的 message,Git 分支中的一個個的節點也是由 commit 構成。一個典型的 commit object
內容以下:
➜ git cat-file -p e655 tree 73aff116086bc78a29fd31ab3fbd7d73913cf958 parent 8da64ce1d90be7e40d6bad5dd1cb1a3c135806a2 author zhangwang <zhangwang2014@iCloud.com> 1521620446 +0800 committer zhangwang <zhangwang2014@iCloud.com> 1521620446 +0800 bc
咱們來看看其中每一項的意義:
tree
:告訴咱們當前 commit
對應的根 tree
,依據此值咱們還原此 commit
對應的工做區;parent
:父 commit
的 hash 值,依據此值,咱們能夠記錄提交歷史;author
:記錄着此commit
的修改內容由誰修改;committer
:記錄着當前 commit 由誰提交;...bc
: commit message
;commit
經常位於 Git 分支上,分支每每也是由咱們主動添加的,Git 提供了一種名爲 References
的對象供咱們存儲「類分支」資源。
References
對象存儲在/git/refs/
文件夾下,該文件夾結構以下:
➜ tree .git/refs .git/refs ├── heads │ ├── master │ ├── meta-school-za │ └── ... ├── remotes │ ├── origin │ │ ├── ANDROIDBUG-4845 │ │ ├── ActivityCard-za │ │ ├── ... ├── stash └── tags
其中 heads 文件夾中的每個文件其實就對應着一條本地分支,已咱們最熟悉的 master 分支爲例,咱們看看其中的內容:
➜ cat .git/refs/heads/master 603bdb03d7134bbcaf3f84b21c9dbe902cce0e79
有沒有發現,文件 master 中的內容看起來好眼熟,它實際上是就是一個指針,指向當前分支最新的 commit 對象。因此說 Git 中的分支是很是輕量級的,弄清分支在 Git 內部是這樣存儲以後,也許咱們能夠更容易理解相似下面這種圖了。
咱們再看看 .git/refs
文件夾中其它的內容:
.git/refs/remotes
中記錄着遠程倉庫分支的本地映射,其內容只讀;.git/refs/stash
與 git stash
命令相關,後文會詳細講解;.git/refs/tag
, 輕量級的tag,與 git tag
命令相關,它也是一個指向某個commit
對象的指針;tag
是一種輔助 Git 作版本控制的對象,上面這種 tag 只是「輕量級tag」 ,此外還存在另外一種「聲明式tag」,聲明式 tag 對象能夠存儲更多的信息,其存在於.git/object/
下。
上文已經說過 Git 中存在兩種 tag
:
.git/refs/tag/
文件夾下;tag object
,此種 tag 能記錄更多的信息;兩種 tag 的內容差異較大:
# lightweight tags $ git tag 0.1 # 指向添加tag時的commit hash值 ➜ cat 0.1 e9f249828f3b6d31b895f7bc3588df7abe5cfeee # annotated tags $ git tag -a -m 'Tagged1.0' 1.0 ➜ git cat-file -p 52c2 object e9f249828f3b6d31b895f7bc3588df7abe5cfeee type commit tag 1.0 tagger zhangwang <zhangwang2014@iCloud.com> 1521625083 +0800 Tagged1.0
對比能夠發現,聲明式的 tag 不只記錄了對應的 commit ,標籤號,額外還記錄了打標籤的人,並且還能夠額外添加 tag message
(上面的-m 'Tagged1.0'
)。
值得額外說明的是,默認狀況下,git push
命令並不會推送標籤到遠程倉庫服務器上。 想要傳送,必須顯式地推送標籤到共享服務器上。 推送方法爲git push origin [tagname]
,若是要推送全部的標籤,可使用git push origin --tags
另外咱們也能夠在後期給某次 commit 打上標籤,如:
git tag -a v1.2 9fceb02
。
至此,咱們已經理解了 Git 中的這幾類資源,接下來咱們看看 Git 命令是如何操做這些資源的。
依據場景,咱們能夠粗略按照操做的是本地倉庫仍是遠程倉庫,把 Git 命令分爲本地命令和遠程命令,咱們先看本地命令,咱們本地可供操做的 Git 倉庫每每是經過 git clone
或者 git init
生成。咱們先看git init
作了些什麼。
git init
&& git init --bare
git init
:在當前文件夾下新建一個本地倉庫,在文件系統上表現爲在當前文件夾中新增一個 .git
的隱藏文件夾
如:
gitDemo on master ➜ ls -a . .. .git a.txt data
Git 中還存在一種被稱爲裸倉庫的特殊倉庫,使用命令 git init --bare
能夠初始化一個裸倉庫
其目錄結構以下:
➜ mkdir gitDemoBear && cd gitDemoBear && git init --bare && tree Initialized empty Git repository in some/path/gitDemoBear/ . ├── branches ├── hooks ├── info ├── objects │ ├── info │ └── pack └── refs ├── heads └── tags 9 directories, 14 files
和普通倉庫相比,裸倉庫沒有工做區,因此並不會存在在裸倉庫上直接提交變動的狀況,這種倉庫會直接把 .git
文件夾中的內容置於初始化的文件夾下。此外
在 config 文件下咱們會看到 bare = true
這代表當前倉庫是一個裸倉庫:
# normal bare = false logallrefupdates = true # bare bare = true
普通的方法是不能修改裸倉庫中的內容的。裸倉庫只容許貢獻者clone
,push
,pull
。
git add
咱們都知道 git add [file]
會把文件添加到緩存區。那緩存區本質上是什麼呢?
爲了理清這個問題,咱們先看下圖:
不少地方會說,git 命令操做的是三棵樹。三棵樹對應的就是上圖中的工做區( working directory )、緩存區( Index )、以及 HEAD。
工做區比較好理解,就是可供咱們直接修改的區域,HEAD
實際上是一個指針,指向最近的一次 commit 對象,這個咱們以後會詳述。Index
就是咱們說的緩存區了,它是下次 commit 涉及到的全部文件的列表。
回到git add [file]
,這個命令會依次作下面兩件事情:
.git/object/
文件夾中添加修改或者新增文件對應的 blob
對象;.git/index
文件夾中寫入該文件的名稱及對應的 blob
對象名稱;經過命令 git ls-files -s
能夠查看全部位於.git/index
中的文件,以下:
➜ git ls-files -s 100644 8baef1b4abc478178b004d62031cf7fe6db6f903 0 a.txt 100644 aceb8a25000b1c680a1a83c032daff4d800c8b95 0 b.txt 100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 c.txt 100644 0932cc0d381ab943f3618e6125995f643cad4425 0 data/d.txt
其中各項的含義以下:
100644
: 100
表明regular file,644
表明文件權限8baef1b4abc478178b004d62031cf7fe6db6f903
:blob對象的名稱;0
:當前文件的版本,若是出現衝突,咱們會看到1
,2
;data/d.txt
: 該文件的完整路徑Git 還額外提供了一個命令來幫我咱們查看文件在這三棵樹中的狀態,git status
。
git status
git status
有三個做用:
通常來講 .git/HEAD
文件中存儲着 Git 倉庫當前位於的分支:
➜ cat .git/HEAD ref: refs/heads/mate-school--encodeUri
當咱們 git add
某個文件後,git 下一步每每會提示咱們commit
它。咱們接下來看看,commit
過程發生了什麼。
git commit
對應到文件層面,git commit
作了以下幾件事情:
tree
對象,有多少個修改過的文件夾,就會添加多少個tree
對象;commit
對象,其中的的tree
指向最頂端的tree,此外還包含一些其它的元信息,commit
對象中的內容,上文已經見到過, tree
對象中會包含一級目錄下的子tree
對象及blob
對象,由此可構建當前commit的文檔快照;;經過git cat-file -p hash
可查看某個對象中的內容
經過git cat-file -t hash
可查看某個對象的類型
當咱們 git add
某個文件後,下一步咱們每每須要執行 git commit
。接下來咱們看看,commit
過程發生了什麼。
git branch
前文咱們提到過,分支在本質上僅僅是「指向提交對象的可變指針」,其內容爲所指對象校驗和(長度爲 40 的 SHA-1 值字符串)的文件(一個commit對象),因此分支的建立和銷燬都異常高效,建立一個新分支就至關於往一個文件中寫入 41 個字節(40 個字符和 1 個換行符),足見 Git 的分支多麼輕量級。
此外上文中提到的 HEAD 也能夠看作一個指向當前所在的本地分支的特殊指針。
在開發過程當中咱們會建立不少分支,全部的分支都存在於.git/refs
文件夾中。
➜ tree .git/refs .git/refs ├── heads │ ├── master │ ├── meta-school-za │ └── ... ├── remotes │ ├── origin │ │ ├── ANDROIDBUG-4845 │ │ ├── ActivityCard-za │ │ ├── ... ├── stash └── tags ➜ cat heads/feature 0cdc9f42882f032c5a556d32ed4d8f9f5af182ed
存在兩種分支,本地分支和遠程分支。
本地分支:
對應存儲在.git/refs/heads
中;
還存在一種叫作「跟蹤分支」(也叫「上游分支」)的本地分支,此類分支從一個遠程跟蹤分支檢出,是與遠程分支有直接關係的本地分支。 若是在一個跟蹤分支上輸入 git pull,Git 能自動地識別去那個遠程倉庫上的那個分支抓取併合並代碼。
遠程分支:
對應存儲在
.git/refs/remotes
中,能夠看作遠程倉庫的分支在本地的備份,其內容在本地是隻讀的。
.git/config
文件中信息進一步指明瞭遠程分支與本地分支之間的關係:
➜ cat .git/config ... [remote "origin"] url = git@git.in.zhihu.com:zhangwang/zhihu-lite.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master [remote "wxa"] url = https://git.in.zhihu.com/wxa/zhihu-lite.git fetch = +refs/heads/*:refs/remotes/wxa/*
使用 git branch [newBranchName]
能夠建立新分支 newBranchName
。不過一個更常見的用法是git checkout -b [newBranchName]
,此命令在本地建立了分支 newBranchName
,並切換到了分支 newBranchName
。咱們看看git checkout
究竟作了些什麼
git checkout
還記得前面咱們提到過的HEAD
嗎?git checkout
實際上就是在操做HEAD
。
前文中咱們提到過通常狀況下 .git/HEAD
指向本地倉庫當前操做的分支。那只是通常狀況,更準確的說法是 .git/HEAD
直接或者間接指向某個 commit
對象。
咱們知道每個 commit
對象都對應着一個快照。可依據其恢復本地的工做目錄。 HEAD
指向的 commit
是判斷工做區有何更改的基礎。
Git 中有一個比較難理解的概念叫作「HEAD分離」,映射到文件層面,其實指的是 .git/HEAD
直接指向某個commit
對象。
咱們來看git checkout
的具體用法
git checkout <file>
:此命令能夠用來清除未緩存的更改,它能夠看作是 git checkout HEAD <file>
的簡寫,
映射到文件層面,其操做爲恢復文件<file>
的內容爲,HEAD對應的快照時的內容。其不會影響已經緩存的更改的緣由在於,其實緩存過的文件就是另一個文件啦。
相應的命令還有 git checkout <commit> <file>
能夠用來恢復某文件爲某個提交時的狀態。
git checkout <branch>
切換分支到 <branch> 其其實是修改 .git/HEAD
中的內容爲 <branch>
,更新工做區內容爲 <branch>
所指向的 commit
對象的內容。
➜ cat .git/HEAD ref: refs/heads/master
git checkout <hash|tag>
HEAD直接指向一個commit
對象,更新工做區內容爲該commit
對象對應的快照,此時爲HEAD
分離狀態,切換到其它分支或者新建分支git branch -b new-branch
|| git checkout branch
可使得HEAD
再也不分離。
➜ cat .git/HEAD 8e1dbd367283a34a57cb226d23417b95122e5754
在分支上進行了一些操做後,下一步咱們要作的就是合併不一樣分支上的代碼了,接下來咱們看看git merge
是如何工做的。
git merge
Git 中分支合併有兩種算法,快速向前合併 和 三路合併。
快速向前合併:
此種狀況下,主分支沒有改動,所以在基於主分支生成的分支上作的更改,必定不會和主分支上的代碼衝突,能夠直接合並,在底層至關於修改
.refs/heads/
下主分支的內容爲最新的 commit 對象。
三路合併:
新的feature分支在開發過程當中,主分支上的代碼也作了修改並添加了新的 commit ,此時合併,須要對比 feature 分支上最新的 commit,feature 分支的 base commit 以及 master 分支上最新的 commit 這三個commit的快照。若是一切順利,這種合併會生成新的合併 commit ,格式以下:
➜ git cat-file -p 43cfbd24b7812b7cde0ca2799b5e3305bd66a9b3 tree 78f3bc25445be087a08c75ca62ca1708a9d2e33a parent 51b45f5892f640b8e9b1fec2f91a99e0d855c077 parent 96e66a5b587b074d834f50d6f6b526395b1598e5 author zhangwang <zhangwang2014@iCloud.com> 1521714339 +0800 committer zhangwang <zhangwang2014@iCloud.com> 1521714339 +0800 Merge branch 'feature'
和普通的 commit 對象的區別在於其有兩個parent
,分別指向被合併的兩個commit
。
不過三路合併每每沒有那麼順利,每每會有衝突,此時須要咱們解決完衝突後,再合併,三路合併的詳細過程以下(爲了敘述便利,假設合併發生在 master 分支與 feature 分支之間):
.git/MERGE_HEAD
。此文件的存在說明 Git 正在作合併操做。(記錄合併提交的狀態)git add
以更新 index 被提交, git commit
基於此 index 生成新的commit
;.git/refs/heads/master
中的內容指向第8步中新生成的 commit
,至此三路合併完成;Git 中的一些命令是以引入的變動即提交這樣的概念爲中心的,這樣一系列的提交,就是一系列的補丁。 這些命令以這樣的方式來管理你的分支。git cherry-pick
作的事情是將一個或者多個commit應用到當前commit的頂部,複製commit,會保留對應的二進制文件,可是會修改parent
信息。
在D commit上執行,git cherry-pick F
會將F複製一份到D上,複製的緣由在於,F的父commit變了,可是內容又須要保持不可變。
一個常見的工做流以下:
$ git checkout master $ git checkout -b foo-tmp $ git cherry-pick C D # 將foo指向foo-tmp,reset將HEAD指向了某個特殊的commit $ git checkout foo $ git reset --hard foo-tmp $ git branch -D foo-tmp
git revert 命令本質上就是一個逆向的 git cherry-pick 操做。 它將你提交中的變動的以徹底相反的方式的應用到一個新建立的提交中,本質上就是撤銷或者倒轉。
有時候咱們會想要撤銷一些commit
,這時候咱們就會用到git reset
。
git reset
git reset
具備如下常見用法:
git reset <file>
:從緩存區移除特定文件,可是不會改變工做區的內容git reset
: 重設緩存區,會取消全部文件的緩存git reset --hard
: 重置緩存區和工做區,修改其內容對最新的一次 commit 對應的內容git reset <commit>
: 移動當前分支的末端到指定的commit
處git reset --hard <commit>
: 重置緩存區和工做區,修改其內容爲指定 commit 對應的內容相對而言,git reset
是一個相對危險的操做,其危險之處在於可能會讓本地的修改丟失,可能會讓分支歷史難以尋找。
咱們看看git reset
的原理
HEAD
所指向的分支的指向:若是你正在 master 分支上工做,執行 git reset 9e5e64a
將會修改 master
讓指向 哈希值爲 9e5e64a
的 commit object
。git reset
,上述過程都會發生,不一樣用法的區別在於會如何修改工做區及緩存區的內容,若是你用的是 git reset --soft
,將僅僅執行上述過程;git reset
本質上是撤銷了上一次的 git commit
命令。執行git commit
,Git 會建立一個新的 commit 對象,並移動HEAD
所指向的分支指向該commit。 而執行git reset
會修改HEAD
所指向的分支指向HEAD~
(HEAD 的父提交),也就是把該分支的指向修改成原來的指向,此過程不會改變index
和工做目錄的內容。
—mixed
會更新索引:git reset --mixed
和 git reset
效果一致,這是git reset
的默認選項,此命令除了會撤銷一上次提交外,還會重置index
,至關於咱們回滾到了 git add
和 git commit
前的狀態。—hard
會修改工做目錄中的內容:除了發生上述過程外,還會恢復工做區爲 上一個 commit
對應的快照的內容,換句話說,是會清空工做區所作的任何更改。—hard
能夠算是reset
命令惟一的危險用法,使用它會真的銷燬數據。
若是你給 git reset
指定了一個路徑,git reset
將會跳過第 1 步,將它的做用範圍限定爲指定的文件或文件夾。 此時分支指向不會移動,不過索引和工做目錄的內容則能夠完成局部的更改,會只針對這些內容執行上述的第 二、3 步。
git reset file.txt
實際上是git reset --mixed HEAD file.txt
的簡寫形式,他會修改當前index
看起來像 HEAD 對應的commit
所依據的索引,所以能夠達到取消文件緩存的做用。
git stash
有時候,咱們在新分支上的feature
開發到一半的時候接到通知須要去修復一個線上的緊急bug?,這時候新feature
還達不到該提交的程度,命令git stash
就派上了用場。
git stash
被用來保存當前分支的工做狀態,便於再次切換回本分支時恢復。其具體用法以下:
feature
分支上執行git stash 或 git stash save
,保存當前分支的工做狀態;feature
分支,執行git stash list
,列出保存的全部stash
,執行 git stash apply
,恢復最新的stash
到工做區;也能夠覆蓋老一些的stash
, 用法如git stash apply stash@{2}
;
關於git stash
還有其它一些值得關注的點:
git stash
會恢復全部以前的文件到工做區,也就是說以前添加到緩存區的文件不會再存在於緩存區,使用 git stash apply --index
命令,則能夠恢復工做區和緩存區與以前同樣;git stash
只會儲藏已經在索引中的文件。 使用 git stash —include-untracked
或 git stash -u
命令,Git 纔會將任何未跟蹤的文件添加到stash
;git stash pop
命令能夠用來應用最新的stash
,並當即從stash
棧上扔掉它;git stash —patch
,可觸發交互式stash
會提示哪些改動想要儲藏、哪些改動須要保存在工做目錄中。➜ git stash --patch diff --git a/src/pages/index/index.mina b/src/pages/index/index.mina index 6e11ce3..038163c 100644 --- a/src/pages/index/index.mina +++ b/src/pages/index/index.mina @@ -326,6 +326,7 @@ Page<Props, Data, {}>({ }, onPageScroll({scrollTop}) { + // abc // TODO: cover-view 的 fixed top 樣式和 pullDownRefresh 有嚴重衝突。 // 當 bug 解決時,能夠在 TabNav 內使用 <cover-view> 配合滾動實現 iOS 的磁鐵效果 Stash this hunk [y,n,q,a,d,/,e,?]?
git stash branch <new branch>
:構建一個名爲new branch
的新分支,並將stash中的內容寫入該分支說完了git stash
的基本用法,咱們來看看,其在底層的實現原理:
上文中咱們提到過,Git 操做的是 工做區,緩存區及 HEAD 三棵文件樹,咱們也知道,commit
中包含的根 tree
對象指向,能夠看作文檔樹的快照。
當咱們執行git stash
時,實際上咱們就是依據工做區,緩存區及HEAD這三棵文件樹分別生成commit
對象,以後以這三個commit 爲 parent
生成新的 commit
對象,表明這次stash
,並把這個 commit 的 hash值存到.git/refs/stash
中。
當咱們執行git stash apply
時,就能夠依據存在 .git/refs/stash
文件中的 commit 對象找到 stash
時工做區,緩存區及HEAD這三棵文件樹的狀態,進而能夠恢復其內容。
gitDemo on master [$] ➜ cat .git/refs/stash 68e5413895acd479daad0c96815cdb69a3c61bef gitDemo on master [$] ➜ git cat-file -p 68e5 tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904 parent aade8236c7c291f927f0be3f51ae57f5388eafcc parent 408ef43aacaf7c255a0c3ea4f82196626a28a39b parent 6bacdafcddf0685d8e4a0b364ea346ff209a87be author zhangwang <zhangwang2014@iCloud.com> 1522397172 +0800 committer zhangwang <zhangwang2014@iCloud.com> 1522397172 +0800 WIP on master: aade823 first commit
暫留的疑問?
.git/refs/stash
文件中只存有最新的stash commit
值,git stash list
是如何生效的。
git clean
使用git clean
命令能夠去除冗餘文件或者清理工做目錄。 使用git clean -f -d
命令能夠用來移除工做目錄中全部未追蹤的文件以及空的子目錄。
此命令真的會從工做目錄中移除未被追蹤的文件。 所以若是你改變主意了,不必定能找回來那些文件的內容。 一個更安全的命令是運行 git stash --all
來移除每一項更新,可是能夠從stash
棧中找到並恢復它們。。
git clean -n
命令能夠告訴咱們git clean
的結果是什麼,以下:
$ git clean -d -n Would remove test.o Would remove tmp/
全部在不知道 git clean
命令的後果是什麼的時候,不要使用-f
,推薦先使用 -n
來看看會有什麼後果。
講到這裏,經常使用的操做本地倉庫的命令就基本上說完了,下面咱們看看 Git 提供的一些操做遠程倉庫的命令。
若是咱們是中途加入某個項目,每每咱們的開發會創建在已有的倉庫之上。若是使用github
或者gitlab
,像已有倉庫提交代碼的常見工做流是
fork
一份主倉庫的代碼到本身的遠程倉庫;clone
本身遠程倉庫代碼到本地;git remote add ...
,便於以後保持本地倉庫與主倉庫同步git pull
;git push
;MR
,待review
經過合併代碼到主倉庫;這期間涉及不少遠程命令,咱們接觸到的第一個命令極可能是git clone
,咱們先看這個命令作了些什麼
git clone
git clone
的通常用法爲git clone <url>
<url>
部分支持四種協議:本地協議(Local),HTTP 協議,SSH(Secure Shell)協議及 Git 協議。典型的用法以下:
$ git clone git://github.com/schacon/ticgit.git Cloning into 'ticgit'... remote: Reusing existing pack: 1857, done. remote: Total 1857 (delta 0), reused 0 (delta 0) Receiving objects: 100% (1857/1857), 374.35 KiB | 193.00 KiB/s, done. Resolving deltas: 100% (772/772), done. Checking connectivity... done.
git clone
作了如下三件事情
objects/
文件夾中的內容到本地倉庫; (對應Receiving objects
);Resolving deltas
);.git/refs/remote/xxx/
下;.git/HEAD
文件中存儲的內容);git pull
,保證當前分支和工做區與遠程分支一致;參考 What is git actually doing when it says it is 「resolving deltas」? - Stack Overflow
除此以外,git
會自動在.git/config
文件中寫入部份內容,
[remote "origin"] url = git@git.in.zhihu.com:zhangwang/zhihu-lite.git fetch = +refs/heads/*:refs/remotes/origin/*
默認狀況下會把clone的源倉庫取名origin
,在.git/config
中存儲其對應的地址,本地分支與遠程分支的對應規則等。
除了git clone
另外一個與遠程倉庫創建鏈接的命令爲git remote
。
git remote
git remote
爲咱們提供了管理遠程倉庫的途徑。
對遠程倉庫的管理包括,查看,添加,移除,對遠程分支的管理等等。
git remote
$ git remote origin # 添加 -v,可查看對應的連接 $ git remote -v origin https://github.com/schacon/ticgit (fetch) origin https://github.com/schacon/ticgit (push) # git remote show [remote-name] 可查看更加詳細的信息 $ git remote show origin * remote origin Fetch URL: https://github.com/schacon/ticgit Push URL: https://github.com/schacon/ticgit HEAD branch: master Remote branches: master tracked dev-branch tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date)
git remote add <shortname> <url>
$ git remote add pb https://github.com/paulboone/ticgit $ git remote -v origin https://github.com/schacon/ticgit (fetch) origin https://github.com/schacon/ticgit (push) pb https://github.com/paulboone/ticgit (fetch) pb https://github.com/paulboone/ticgit (push)
git remote rename
$ git remote rename pb paul $ git remote origin paul
git remote rm <name>
$ git remote rm paul $ git remote origin
上述示例代碼參照 Git - 遠程倉庫的使用
本地對遠程倉庫的記錄存在於.git/config
文件中,在.git/config
中咱們能夠看到以下格式的內容:
# .git/config [remote "github"] url = https://github.com/zhangwang1990/weixincrawler.git fetch = +refs/heads/*:refs/remotes/github/* [remote "zhangwang"] url = https://github.com/zhangwang1990/weixincrawler.git fetch = +refs/heads/*:refs/remotes/zhangwang/*
[remote] "github"
:表明遠程倉庫的名稱;url
:表明遠程倉庫的地址fetch
:表明遠程倉庫與本地倉庫的對應規則,這裏涉及到另一個 Git 命令,git fetch
git fetch
咱們先看git fetch
的做用:
git fetch <some remote branch>
:同步某個遠程分支的改變到本地,會下載本地沒有的數據,更新本地數據庫,並移動本地對應分支的指向。git fetch --all
會拉取全部的遠程分支的更改到本地咱們繼續看看git fetch
是如何工做的:
# config中的配置 [remote "origin"] url = /home/demo/bare-repo/ fetch = +refs/heads/*:refs/remotes/origin/* #<remote-refs>:<local-refs> 遠程的對應本地的存儲位置
fetch
的格式爲fetch = +<src>:<dst>
,其中
+
號是可選的,用來告訴 Git 即便在不能採用「快速向前合併」也要(強制)更新引用;<src>
表明遠程倉庫中分支的位置;<dst>
遠程分支對應的本地位置。咱們來看一個git fetch
的實例,看看此命令是怎麼做用於本地倉庫的:
git fetch origin
.git/refs/remotes/origin
文件夾;.git/FETCH_HEAD
的特殊文件,其中記錄着遠程分支所指向的commit
對象;git fetch origin feature-branch
,Git並不會爲咱們建立一個對應遠程分支的本地分支,可是會更新本地對應的遠程分支的指向;git checkout feature-branch
,git 會基於記錄在.git/FETCH_HEA
中的內容新建本地分支,並在.git/config
中添加以下內容,用以保證本地分支與遠程分支future-branch
的一致[branch "feature-branch"] remote = origin merge = refs/heads/feature-branch
git 每次執行git fetch
都會重寫.git/FETCH_HEA
。
上述fetch
的格式也能幫咱們理解git push
的一些用法
git push
咱們在本地某分支開發完成以後,會須要推送到遠程倉庫,這時候咱們會執行以下代碼:
git push origin featureBranch:featureBranch
此命令會幫咱們在遠程創建分支featureBranch
,之因此要這樣作的緣由也在於上面定義的fetch
模式。
由於引用規格(的格式)是 <src>:<dst>
,因此其實會在遠程倉庫創建分支featureBranch
,從這裏咱們也能夠看出,分支確實是很是輕量級的。
此外,若是咱們執行 git push origin :topic
:,這裏咱們把 <src>
留空,這意味着把遠程版本庫的 topic
分支定義爲空值,也就說會刪除對應的遠程分支。
回到git push
,咱們從資源的角度看看發生了什麼?
.git/objects/
目錄,上傳到遠程倉庫的/objects/
下;refs/heads/master
內容,指向本地最新的commit;.git/refs/remotes/delta/master
內容,指向最新的commit
;說完git push
,咱們再來看看 git pull
。
git pull
此命令的通用格式爲 git pull <remote> <branch>
它作了如下幾件事情:
git fetch <remote>
:下載最新的內容.git/FETCH_HEAD
找到應該合併到的本地分支;git merge
git pull
在大多數狀況下它的含義是一個 git fetch
緊接着一個 git merge
命令。
至此,經常使用的git
命令原理咱們都基本講解完了。若是你們有一些其它想要了解的命令,咱們能夠再一塊兒探討,補充。
Home · geeeeeeeeek/git-recipes Wiki · GitHub
gitlet.js
git-from-the-inside-out
A Hacker’s Guide to Git | Wildly Inaccurate
githug