亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

directory search
Guides gitattributes giteveryday gitglossary gitignore gitmodules gitrevisions gittutorial gitworkflows Administration git archive git bundle git clean git filter-branch git fsck git gc git instaweb git reflog Basic Snapshotting git add git commit git diff git mv git reset git rm git status Branching and Merging git branch git checkout git log git merge git mergetool git stash git tag Debugging git bisect git blame git grep Email git am git format-patch git request-pull git send-email External Systems git fast-import git svn Getting and Creating Projects git clone git init Git git annotate git archimport git bisect-lk2009 git check-attr git check-mailmap git check-ref-format git checkout-index git cherry git citool git column git credential git credential-cache git credential-store git cvsexportcommit git cvsimport git cvsserver git diff-files git diff-tree git difftool git fast-export git fetch-pack git fmt-merge-msg git get-tar-commit-id git gui git http-backend git http-fetch git http-push git imap-send git index-pack git interpret-trailers git ls-remote git ls-tree git mailinfo git mailsplit git merge-file git merge-index git merge-one-file git merge-tree git mktag git mktree git name-rev git notes git p4 git pack-objects git pack-redundant git pack-refs git parse-remote git patch-id git prune git prune-packed git quiltimport git receive-pack git remote-ext git remote-fd git remote-testgit git repack git replace git rerere git send-pack git sh-i18n git sh-setup git shell git show-branch git show-index git stripspace git unpack-file git unpack-objects git upload-archive git upload-pack git var git verify-commit git verify-tag git whatchanged git worktree Inspection and Comparison git describe git shortlog git show Miscellaneous api credentials api index gitcli gitcore tutorial gitcredentials gitcvs migration gitdiffcore githooks gitk gitnamespaces gitremote helpers gitrepository layout gitsubmodules gittutorial 2 gitweb gitweb.conf pack format User Manual Patching git apply git cherry-pick git rebase git revert Plumbing Commands git cat-file git check-ignore git commit-tree git count-objects git diff-index git for-each-ref git hash-object git ls-files git merge-base git read-tree git rev-list git rev-parse git show-ref git symbolic-ref git update-index git update-ref git verify-pack git write-tree Server Admin git daemon git update-server-info Setup and Config git git config git help Sharing and Updating Projects git fetch git pull git push git remote git submodule
characters

名稱

gitcore-tutorial  - 面向開(kāi)發(fā)人員的Git核心教程

概要

git *

描述

本教程介紹了如何使用“core”Git命令來(lái)設(shè)置和使用Git存儲(chǔ)庫(kù)。

如果你只需要使用Git作為修訂控制系統(tǒng),你可能更喜歡從“Git教程簡(jiǎn)介”(gittutorial[7])或Git用戶手冊(cè)開(kāi)始。

但是,如果您想了解Git的內(nèi)部組件,那么對(duì)這些低級(jí)工具的理解會(huì)很有幫助。

核心Git通常被稱為“plumbing”,其中更漂亮的用戶界面稱為“porcelain”。您可能不想經(jīng)常直接使用管道,但可以很好地知道porcelain沒(méi)有沖洗時(shí)plumbing的功能。

本文最初編寫(xiě)時(shí),很多瓷器命令都是shell腳本。為了簡(jiǎn)單起見(jiàn),它仍然用它們作為例子來(lái)說(shuō)明管道是如何配合在一起形成瓷器命令的。源代碼樹(shù)包含一些在contrib/examples/中的腳本以供參考。雖然這些不再作為shell腳本實(shí)現(xiàn),但對(duì)管道層命令做什么的描述仍然有效。

注意

更深入的技術(shù)細(xì)節(jié)通常被標(biāo)記為Notes,您可以在第一次閱讀時(shí)跳過(guò)。

創(chuàng)建一個(gè)git倉(cāng)庫(kù)

創(chuàng)建一個(gè)新的Git倉(cāng)庫(kù)不是一件容易的事情:所有的Git倉(cāng)庫(kù)都是空的,你唯一需要做的就是找到一個(gè)你想用作工作樹(shù)的子目錄 - 對(duì)于一個(gè)全新的項(xiàng)目來(lái)說(shuō)是空的,或者您想要導(dǎo)入到Git中的現(xiàn)有工作樹(shù)。

對(duì)于我們的第一個(gè)例子,我們將從頭開(kāi)始一個(gè)全新的存儲(chǔ)庫(kù),沒(méi)有預(yù)先存在的文件,我們將調(diào)用它git-tutorial。首先,為它創(chuàng)建一個(gè)子目錄,切換到該子目錄,并使用以下命令初始化Git基礎(chǔ)結(jié)構(gòu)git init

$ mkdir git-tutorial
$ cd git-tutorial
$ git init

Git會(huì)回復(fù)

Initialized empty Git repository in .git/

這只是Git說(shuō)你沒(méi)有做過(guò)任何奇怪事情的方式,并且它會(huì).git為你的新項(xiàng)目創(chuàng)建一個(gè)本地目錄設(shè)置。你現(xiàn)在會(huì)有一個(gè).git目錄,你可以用它來(lái)檢查ls。對(duì)于您的新空項(xiàng)目,它應(yīng)該顯示三個(gè)條目,其中包括:

  • 一個(gè)叫做HEAD的文件,里面有ref: refs/heads/master。這類似于符號(hào)鏈接并指向refs/heads/master相對(duì)于該HEAD文件。不要擔(dān)心HEAD鏈接指向的文件甚至不存在 - 您尚未創(chuàng)建將啟動(dòng)您的HEAD開(kāi)發(fā)分支的提交。

  • 一個(gè)名為objects的子目錄,其中將包含項(xiàng)目的所有對(duì)象。你永遠(yuǎn)不應(yīng)該有任何真正的理由直接看對(duì)象,但你可能想知道這些對(duì)象是什么包含data你的存儲(chǔ)庫(kù)中的所有實(shí)體。

  • 一個(gè)名為refs的子目錄,其中包含對(duì)對(duì)象的引用。

特別是,refs子目錄將包含另外兩個(gè)子目錄,分別命名headstags分別。他們完全按照他們的名字暗示:它們包含對(duì)任意數(shù)量不同heads開(kāi)發(fā)(aka branches)的引用,以及為tags創(chuàng)建存儲(chǔ)庫(kù)中特定版本而創(chuàng)建的任何引用。

注意:特殊的master頭是默認(rèn)分支,這就是為什么.git/HEAD文件被創(chuàng)建指向它,即使它尚不存在?;旧?,這個(gè)HEAD鏈接應(yīng)該始終指向你現(xiàn)在正在工作的分支,并且你總是開(kāi)始期待在master分支上工作。

然而,這僅僅是一個(gè)約定,你可以隨意命名你的分支什么,不必連過(guò)have一個(gè)master分支。不過(guò),許多Git工具會(huì)認(rèn)為.git/HEAD是有效的。

注意

一個(gè)對(duì)象由其160位SHA-1散列(又名對(duì)象名稱)標(biāo)識(shí),而對(duì)象的引用始終是該SHA-1名稱的40字節(jié)十六進(jìn)制表示。預(yù)計(jì)refs子目錄中的文件將包含這些十六進(jìn)制引用(通常在最后有一個(gè)最后的\n),因此當(dāng)您實(shí)際啟動(dòng)時(shí),您應(yīng)該期望在這些refs子目錄中看到許多包含這些引用的41字節(jié)文件填充你的樹(shù)。

注意

完成本教程后,高級(jí)用戶可能想看看gitrepository-layout5。

你現(xiàn)在已經(jīng)創(chuàng)建了你的第一個(gè)Git倉(cāng)庫(kù)。當(dāng)然,由于它是空的,這不是很有用,所以讓我們開(kāi)始用數(shù)據(jù)填充它。

填充一個(gè)git倉(cāng)庫(kù)

我們會(huì)保持這種簡(jiǎn)單和愚蠢的,所以我們將開(kāi)始填充一些簡(jiǎn)單的文件,以獲得它的感覺(jué)。

首先創(chuàng)建你想在你的Git倉(cāng)庫(kù)中維護(hù)的隨機(jī)文件。我們將從一些不好的例子開(kāi)始,以了解它的工作原理:

$ echo "Hello World" >hello
$ echo "Silly example" >example

你現(xiàn)在在你的工作樹(shù)(又名working directory)中創(chuàng)建了兩個(gè)文件,但要真正檢查你的努力工作,你必須經(jīng)歷兩個(gè)步驟:

  • 用有關(guān)工作樹(shù)狀態(tài)的信息填寫(xiě)index文件(又名cache)。

  • 將該索引文件作為對(duì)象提交。

第一步很簡(jiǎn)單:當(dāng)你想告訴Git關(guān)于工作樹(shù)的任何修改時(shí),你就可以使用git update-index程序。該程序通常只需要一個(gè)你想更新的文件名列表,但為了避免微不足道的錯(cuò)誤,它拒絕向索引添加新的條目(或刪除現(xiàn)有的條目),除非你明確地告訴它你正在添加一個(gè)新的條目--add標(biāo)志(或移除--remove標(biāo)志)。

因此,要使用剛剛創(chuàng)建的兩個(gè)文件填充索引,可以這樣做

$ git update-index --add hello example

你現(xiàn)在已經(jīng)告訴Git跟蹤這兩個(gè)文件。

事實(shí)上,正如你所做的那樣,如果你現(xiàn)在查看你的對(duì)象目錄,你會(huì)注意到Git會(huì)在對(duì)象數(shù)據(jù)庫(kù)中添加兩個(gè)新對(duì)象。如果你完成上述步驟,你現(xiàn)在應(yīng)該可以做到

$ ls .git/objects/??/*

并看兩個(gè)文件:

.git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238.git/objects/f2/4c74a2e500f5ee1332c86b94199f52b1d1d962

它們分別與名稱為557db...和的對(duì)象相對(duì)應(yīng)f24c7...。

如果你愿意,你可以用git cat-file來(lái)查看這些對(duì)象,但是你必須使用對(duì)象名稱,而不是對(duì)象的文件名:

$ git cat-file -t 557db03de997c86a4a028e1ebd3a1ceb225be238

當(dāng)-t告訴git cat-file告訴你對(duì)象的“類型”是什么。Git會(huì)告訴你,你有一個(gè)“blob”對(duì)象(即,只是一個(gè)普通文件),你可以看到內(nèi)容

$ git cat-file blob 557db03

這將打印出“Hello World”。該對(duì)象557db03只不過(guò)是文件的內(nèi)容hello。

注意

不要將該對(duì)象與文件hello本身混淆。該對(duì)象實(shí)際上只是文件的特定內(nèi)容,不管您稍后更改文件hello中的內(nèi)容,我們剛剛查看的對(duì)象都不會(huì)改變。對(duì)象是不可變的。

注意

第二個(gè)例子說(shuō)明,可以在大部分地方將對(duì)象名縮寫(xiě)為僅前幾個(gè)十六進(jìn)制數(shù)字。

無(wú)論如何,正如我們前面提到的,通常你永遠(yuǎn)都不會(huì)真正看到對(duì)象本身,而輸入長(zhǎng)40個(gè)字符的十六進(jìn)制名稱并不是你通常想要做的事情。上面的題目只是為了表明git update-index做了一些神奇的事情,并且實(shí)際上將文件的內(nèi)容保存到了Git對(duì)象數(shù)據(jù)庫(kù)中。

更新索引也做了其他事情:它創(chuàng)建了一個(gè).git/index文件。這是描述您當(dāng)前工作樹(shù)的索引,以及您應(yīng)該非常清楚的內(nèi)容。同樣,你通常不會(huì)擔(dān)心索引文件本身,但是你應(yīng)該知道這樣一個(gè)事實(shí),即到目前為止你還沒(méi)有真正“檢查”你的文件到Git中,你只是告訴Git。

但是,由于Git知道它們,現(xiàn)在可以開(kāi)始使用一些最基本的Git命令來(lái)操縱文件或查看它們的狀態(tài)。

特別是,我們甚至不會(huì)將兩個(gè)文件檢入到Git中,然后我們將首先添加另一行hello

$ echo "It's a new day for git" >>hello

現(xiàn)在你可以,因?yàn)槟愀嬖VGit關(guān)于以前的狀態(tài)hello,請(qǐng)問(wèn)使用下面的git diff-files命令Git樹(shù)中的變化與舊索引相比:

$ git diff-files

噢不,這不是很可讀。它只是說(shuō)出它自己的內(nèi)部版本diff,但是這個(gè)內(nèi)部版本實(shí)際上只是告訴你,它已經(jīng)注意到“hello”已被修改,并且它的舊對(duì)象內(nèi)容已被替換為其他東西。

為了使它可讀,我們可以git diff-files通過(guò)使用-p標(biāo)志來(lái)告訴輸出差異作為補(bǔ)?。?/p>

$ git diff-files -p
diff --git a/hello b/hello
index 557db03..263414f 100644--- a/hello+++ b/hello
@@ -1 +1,2 @@
 Hello World+It's a new day for git

即我們通過(guò)添加另一條線所導(dǎo)致的變化的差異hello

換句話說(shuō),git diff-files總是向我們顯示索引中記錄的內(nèi)容與工作樹(shù)中當(dāng)前內(nèi)容之間的區(qū)別。這非常有用。

一個(gè)通用的簡(jiǎn)寫(xiě)git diff-files -p是只寫(xiě)git diff,它會(huì)做同樣的事情。

$ git diff
diff --git a/hello b/hello
index 557db03..263414f 100644--- a/hello+++ b/hello
@@ -1 +1,2 @@
 Hello World+It's a new day for git

提交git狀態(tài)

現(xiàn)在,我們要進(jìn)入Git的下一個(gè)階段,即在索引中獲取Git知道的文件,并將它們作為真正的樹(shù)進(jìn)行提交。我們分兩個(gè)階段來(lái)完成:創(chuàng)建一個(gè)tree對(duì)象,并將該tree對(duì)象作為commit對(duì)象提交,同時(shí)解釋樹(shù)的全部?jī)?nèi)容以及我們?nèi)绾芜M(jìn)入該狀態(tài)的信息。

創(chuàng)建一個(gè)樹(shù)對(duì)象是微不足道的,并且完成git write-tree。沒(méi)有選項(xiàng)或其他輸入:git write-tree將采用當(dāng)前索引狀態(tài),并編寫(xiě)描述整個(gè)索引的對(duì)象。換句話說(shuō),我們現(xiàn)在將所有不同的文件名與他們的內(nèi)容(以及他們的權(quán)限)結(jié)合在一起,我們創(chuàng)建了一個(gè)Git“目錄”對(duì)象的等價(jià)物:

$ git write-tree

并且這只會(huì)輸出結(jié)果樹(shù)的名稱,在這種情況下(如果您完全按照我所描述的那樣完成)它應(yīng)該是

8988da15d077d4829fc51d8544c097def6644dbb

這是另一個(gè)難以理解的對(duì)象名稱。再一次,如果你愿意的話,你可以使用git cat-file -t 8988d...看到這次對(duì)象不是一個(gè)“blob”對(duì)象,而是一個(gè)“樹(shù)”對(duì)象(你也可以用它git cat-file來(lái)實(shí)際輸出原始對(duì)象的內(nèi)容,但你會(huì)看到主要二進(jìn)制混亂,所以這不那么有趣)。

但是 - 通常你不會(huì)git write-tree自己使用它,因?yàn)橥ǔD憧偸鞘褂迷?code>git commit-tree命令將一棵樹(shù)提交到一個(gè)提交對(duì)象中。事實(shí)上,根本不能實(shí)際使用git write-tree它,但只是將其結(jié)果作為參數(shù)傳遞給它git commit-tree。

git commit-tree通常需要幾個(gè)參數(shù) - 它想知道parent提交的內(nèi)容是什么,但由于這是該新存儲(chǔ)庫(kù)中的第一次提交,并且沒(méi)有父母,我們只需要傳入樹(shù)的對(duì)象名稱。但是,git commit-tree也希望在其標(biāo)準(zhǔn)輸入上獲得提交消息,并將提交的結(jié)果對(duì)象名稱寫(xiě)入其標(biāo)準(zhǔn)輸出。

這就是我們創(chuàng)建.git/refs/heads/master指向的文件的地方HEAD。這個(gè)文件應(yīng)該包含對(duì)主分支樹(shù)頂端的引用,因?yàn)檫@正是git commit-tree吐出來(lái)的東西,所以我們可以用一系列簡(jiǎn)單的shell命令來(lái)完成這一切:

$ tree=$(git write-tree)$ commit=$(echo 'Initial commit' | git commit-tree $tree)$ git update-ref HEAD $commit

在這種情況下,這會(huì)創(chuàng)建一個(gè)與其他任何內(nèi)容都無(wú)關(guān)的全新提交。通常情況下,你這樣做只是一次為一個(gè)項(xiàng)目永遠(yuǎn),其隨后所有的提交將在較早提交頂部父。

再次,通常你永遠(yuǎn)不會(huì)親自去做這件事。有一個(gè)有用的腳本git commit,它會(huì)為你做所有這些。所以你可以寫(xiě)下來(lái)git commit,而且它會(huì)為你完成上面的magic腳本。

做出改變

請(qǐng)記住我們是如何完成git update-indexon文件的hello,然后再進(jìn)行更改hello,并且可以將新?tīng)顟B(tài)hello與我們保存在索引文件中的狀態(tài)進(jìn)行比較?

此外,請(qǐng)記住我是如何git write-tree索引文件的內(nèi)容寫(xiě)入樹(shù)中的,因此我們剛剛承諾的實(shí)際上是文件的原始內(nèi)容hello,而不是新的內(nèi)容。我們這樣做的目的是為了顯示索引狀態(tài)和工作樹(shù)中的狀態(tài)之間的區(qū)別,以及它們?nèi)绾尾黄ヅ?,即使我們犯了錯(cuò)誤。

和以前一樣,如果我們git diff-files -p在我們的git-tutorial項(xiàng)目中做,我們?nèi)匀粫?huì)看到我們上次看到的同樣的區(qū)別:索引文件并沒(méi)有因提交任何內(nèi)容而改變。然而,現(xiàn)在我們已經(jīng)犯了一些事件,我們也可以學(xué)習(xí)使用一個(gè)新的命令:git diff-index。

git diff-files顯示索引文件和工作樹(shù)git diff-index之間的區(qū)別不同,顯示了提交樹(shù)與索引文件或工作樹(shù)之間的區(qū)別。換句話說(shuō),我們git diff-index希望一棵樹(shù)能夠與之相抗衡,而在我們提交之前,我們不能這樣做,因?yàn)槲覀儧](méi)有任何可以相互區(qū)分的東西。

但現(xiàn)在我們可以做到

$ git diff-index -p HEAD

(這里-p的含義與它的意思相同git diff-files),它會(huì)向我們展示同樣的差異,但是卻出于完全不同的原因?,F(xiàn)在我們將工作樹(shù)與索引文件進(jìn)行比較,但是對(duì)照我們剛剛編寫(xiě)的樹(shù)。恰恰相反,這兩個(gè)顯然是相同的,所以我們得到了相同的結(jié)果。

再次,因?yàn)檫@是一種常見(jiàn)的操作,您也可以簡(jiǎn)單地使用

$ git diff HEAD

最終為你做上述事情。

換句話說(shuō),git diff-index通常將一棵樹(shù)與工作樹(shù)進(jìn)行比較,但是在給定該--cached標(biāo)志時(shí),會(huì)告訴它僅僅比較索引緩存內(nèi)容,并完全忽略當(dāng)前工作樹(shù)狀態(tài)。由于我們只是將索引文件寫(xiě)入HEAD,git diff-index --cached -p HEAD因此應(yīng)該返回一組空白的差異,而這正是它所做的。

注意

因此,git diff-index確實(shí)總是使用索引進(jìn)行比較,并且說(shuō)它將樹(shù)與工作樹(shù)進(jìn)行比較因此不是嚴(yán)格準(zhǔn)確的。特別是,無(wú)論是否使用--cached標(biāo)志,要比較的文件列表(“元數(shù)據(jù)”)總是來(lái)自索引文件。--cached標(biāo)志只確定要比較的文件內(nèi)容是否來(lái)自工作樹(shù)。這并不難理解,只要您意識(shí)到Git根本就不知道(或關(guān)心)文件,而不會(huì)明確地告知它。Git永遠(yuǎn)不會(huì)去查找要比較的文件,它希望你告訴它文件是什么,這就是索引的用途。

但是,我們下一步要承諾我們所做的改變,并且再次了解發(fā)生了什么,請(qǐng)牢記“工作樹(shù)內(nèi)容”,“索引文件”和“承諾樹(shù)”之間的差異。我們?cè)诠ぷ鳂?shù)中進(jìn)行了更改,我們需要處理索引文件,因此我們需要做的第一件事是更新索引緩存:

$ git update-index hello

(注意--add這次我們不需要這個(gè)標(biāo)志,因?yàn)镚it已經(jīng)知道這個(gè)文件了)。

注意git diff-*這里的不同版本會(huì)發(fā)生什么。我們更新后hello的指數(shù),git diff-files -p現(xiàn)在顯示無(wú)顯著差異,但git diff-index -p HEAD仍然沒(méi)有顯示當(dāng)前狀態(tài)是我們犯下的狀態(tài)不同。實(shí)際上,git diff-index無(wú)論我們是否使用該--cached標(biāo)志,現(xiàn)在都顯示出同樣的差異,因?yàn)楝F(xiàn)在索引與工作樹(shù)是一致的。

現(xiàn)在,由于我們已經(jīng)更新hello了索引,我們可以提交新版本。我們可以通過(guò)再次手工編寫(xiě)樹(shù)并提交樹(shù)來(lái)完成此操作(這次我們必須使用-p HEAD標(biāo)志來(lái)告訴提交HEAD是新提交的父代,并且這不是初始提交任何更多),但你已經(jīng)完成了,所以我們這次只使用有用的腳本:

$ git commit

它會(huì)啟動(dòng)一個(gè)編輯器來(lái)編寫(xiě)提交消息,并告訴你一些關(guān)于你已經(jīng)完成的事情。

寫(xiě)下你想要的任何消息,所有#以此開(kāi)始的行將被刪除,其余的將被用作改變的提交消息。如果您決定此時(shí)不想提交任何內(nèi)容(您可以繼續(xù)編輯內(nèi)容并更新索引),則可以留下一條空的消息。否則git commit會(huì)為你做出改變。

你現(xiàn)在已經(jīng)做出了你的第一個(gè)真正的Git提交。如果你有興趣研究git commit真正的功能,請(qǐng)隨時(shí)調(diào)查:這是一些非常簡(jiǎn)單的shell腳本,用于生成有用的(?)提交消息頭文件,以及一些實(shí)際執(zhí)行提交本身的單行程序(git commit) 。

檢查更改

雖然創(chuàng)建更改很有用,但如果以后可以告訴更改了哪些內(nèi)容,則更有用。對(duì)此的最有用的命令是另一個(gè)diff家庭,即git diff-tree。

git diff-tree可以給兩棵任意的樹(shù),它會(huì)告訴你它們之間的區(qū)別。也許更普遍的是,你可以給它一個(gè)單一的提交對(duì)象,它會(huì)找出那個(gè)提交本身的父對(duì)象,并直接顯示它們之間的區(qū)別。因此,為了獲得我們已經(jīng)多次看到的差異,我們現(xiàn)在可以做

$ git diff-tree -p HEAD

(再次,-p意味著將差異顯示為人類可讀的補(bǔ)?。?,并且它將顯示上次提交(in HEAD)實(shí)際上發(fā)生了什么變化。

Note

Here is an ASCII art by Jon Loeliger that illustrates how various diff-* commands compare things.                diff-tree              +----+              |    |              |    |              V    V           +-----------+           | Object DB |           |  Backing  |           |   Store   |           +-----------+             ^    ^             |    |             |    |  diff-index --cached             |    | diff-index  |    V             |  +-----------+             |  |   Index   |             |  |  "cache"  |             |  +-----------+             |    ^             |    |             |    |  diff-files             |    |             V    V           +-----------+           |  Working  |           | Directory |           +-----------+

更有意思的是,你也可以給git diff-tree這個(gè)--pretty標(biāo)志,告訴它也顯示提交信息和作者以及提交日期,你可以告訴它顯示一系列的差異?;蛘撸憧梢愿嬖V它是“沉默”,并且根本不顯示差異,但只顯示實(shí)際的提交信息。

實(shí)際上,與git rev-list程序(產(chǎn)生修訂列表)一起,git diff-tree最終成為變化的真正來(lái)源。你可以模仿git log,git log -p等用一個(gè)簡(jiǎn)單的腳本,管道輸出git rev-listgit diff-tree --stdin,這是究竟如何早期版本git log中實(shí)現(xiàn)。

標(biāo)記一個(gè)版本

在Git中,有兩種標(biāo)簽,一種是“輕”標(biāo)簽,另一種是“帶標(biāo)簽的標(biāo)簽”。

一個(gè)“l(fā)ight”標(biāo)簽在技術(shù)上只不過(guò)是一個(gè)分支,除了我們把它放在.git/refs/tags/子目錄中而不是調(diào)用它head。所以最簡(jiǎn)單的標(biāo)簽形式僅僅涉及

$ git tag my-first-tag

它只是把當(dāng)前HEAD.git/refs/tags/my-first-tag文件寫(xiě)入文件,然后你可以在這個(gè)特定的狀態(tài)下使用這個(gè)符號(hào)名稱。例如,你可以做

$ git diff my-first-tag

將當(dāng)前狀態(tài)與當(dāng)前顯然是空白區(qū)別的標(biāo)簽進(jìn)行比較,但如果您繼續(xù)開(kāi)發(fā)和提交內(nèi)容,則可以使用標(biāo)簽作為“定位點(diǎn)”來(lái)查看標(biāo)記后發(fā)生了哪些變化。

一個(gè)“帶注釋的標(biāo)簽”實(shí)際上是一個(gè)真正的Git對(duì)象,它不僅包含一個(gè)指向你想要標(biāo)記的狀態(tài)的指針,而且還包含一個(gè)小標(biāo)簽名稱和消息,還有一個(gè)可選的PGP簽名,表示是的,你確實(shí)是這樣做的標(biāo)簽。您可以使用-a-s標(biāo)志創(chuàng)建這些帶注釋的標(biāo)簽git tag

$ git tag -s <tagname>

這將簽署當(dāng)前HEAD(但您也可以給它另一個(gè)參數(shù),指定要標(biāo)記的東西,例如,您可以使用標(biāo)記當(dāng)前mybranch點(diǎn)git tag <tagname> mybranch)。

通常,您只會(huì)為主要版本或類似的東西做簽名標(biāo)記,而輕量級(jí)標(biāo)記對(duì)于您想要執(zhí)行的任何標(biāo)記都很有用 - 無(wú)論何時(shí)您決定要記住某個(gè)特定點(diǎn),只需為其創(chuàng)建一個(gè)專用標(biāo)記,并且在那個(gè)時(shí)候你有一個(gè)很好的符號(hào)名稱。

復(fù)制存儲(chǔ)庫(kù)

Git倉(cāng)庫(kù)通常是完全自給自足和可重新定位的。例如,與CVS不同,“存儲(chǔ)庫(kù)”和“工作樹(shù)”沒(méi)有單獨(dú)的概念。Git倉(cāng)庫(kù)通常工作樹(shù),本地Git信息隱藏在.git子目錄中。沒(méi)有別的。你看到的是你得到的。

注意

您可以告訴Git將Git內(nèi)部信息從它跟蹤的目錄中分離出來(lái),但現(xiàn)在我們將忽略它:這不是普通項(xiàng)目的工作方式,而只是用于特殊用途。因此,“Git信息始終與其描述的工作樹(shù)直接相關(guān)”的心智模型在技術(shù)上可能不是100%準(zhǔn)確的,但它對(duì)于所有正常使用來(lái)說(shuō)都是一個(gè)很好的模型。

這有兩個(gè)含義:

  • 如果您對(duì)創(chuàng)建的教程庫(kù)感到厭倦(或者您犯了一個(gè)錯(cuò)誤并想要重新開(kāi)始),那么您可以執(zhí)行簡(jiǎn)單的$ rm -rf git-tutorial

它會(huì)消失。沒(méi)有外部存儲(chǔ)庫(kù),并且您創(chuàng)建的項(xiàng)目之外沒(méi)有任何歷史記錄。

  • 如果你想移動(dòng)或復(fù)制一個(gè)Git倉(cāng)庫(kù),你可以這樣做。有git clone命令,但如果你想要做的只是創(chuàng)建一個(gè)你的倉(cāng)庫(kù)的副本(附帶所有完整的歷史記錄),你可以用常規(guī)的方式來(lái)完成cp -a git-tutorial new-git-tutorial。請(qǐng)注意,當(dāng)您移動(dòng)或復(fù)制Git存儲(chǔ)庫(kù)時(shí),您的Git索引文件(緩存各種信息,特別是所涉及文件的一些“統(tǒng)計(jì)”信息)可能需要刷新。所以在你cp -a創(chuàng)建一個(gè)新副本之后,你會(huì)想要做$ git update-index --refresh

在新的存儲(chǔ)庫(kù)中確保索引文件是最新的。

請(qǐng)注意,第二點(diǎn)即使在機(jī)器上也是如此。您可以復(fù)制一個(gè)遠(yuǎn)程的Git倉(cāng)庫(kù)與任何常規(guī)復(fù)制機(jī)制,是它scprsyncwget。

在復(fù)制遠(yuǎn)程存儲(chǔ)庫(kù)時(shí),您至少需要更新索引緩存,尤其是在其他人的存儲(chǔ)庫(kù)中,您通常希望確保索引緩存處于某種已知狀態(tài)(您不需要知道他們做了什么,且還沒(méi)有檢查),所以通常你會(huì)先于git update-index

$ git read-tree --reset HEAD
$ git update-index --refresh

這將強(qiáng)制從指向的樹(shù)中重新構(gòu)建索引HEAD。它將索引內(nèi)容重置為HEAD,然后git update-index確保將所有索引條目與檢出文件進(jìn)行匹配。如果原始存儲(chǔ)庫(kù)在其工作樹(shù)中有未提交的更改,則git update-index --refresh通知它們并告訴您需要更新它們。

以上內(nèi)容也可以簡(jiǎn)單寫(xiě)成

$ git reset

實(shí)際上很多常見(jiàn)的Git命令組合都可以通過(guò)git xyz接口編寫(xiě)腳本。您可以通過(guò)查看各種git腳本所做的工作來(lái)了解情況。例如,git reset用于實(shí)現(xiàn)在上述的兩行git reset,但像一些git statusgit commit稍微圍繞基本Git命令更復(fù)雜的腳本。

許多(大多數(shù)?)公共遠(yuǎn)程存儲(chǔ)庫(kù)不會(huì)包含任何檢出的文件或甚至索引文件,并且包含實(shí)際的核心Git文件。這樣的存儲(chǔ)庫(kù)通常甚至沒(méi)有該.git子目錄,但直接在存儲(chǔ)庫(kù)中包含所有Git文件。

要?jiǎng)?chuàng)建自己的“原始”Git存儲(chǔ)庫(kù)的本地活動(dòng)副本,首先要為項(xiàng)目創(chuàng)建自己的子目錄,然后將原始存儲(chǔ)庫(kù)內(nèi)容復(fù)制到.git目錄中。例如,要?jiǎng)?chuàng)建自己的Git存儲(chǔ)庫(kù)副本,您需要執(zhí)行以下操作

$ mkdir my-git
$ cd my-git
$ rsync -rL rsync://rsync.kernel.org/pub/scm/git/git.git/ .git

其次是

$ git read-tree HEAD

來(lái)填充索引。然而,現(xiàn)在你已經(jīng)填充了索引,并且你擁有所有的Git內(nèi)部文件,但是你會(huì)注意到你實(shí)際上沒(méi)有任何工作樹(shù)文件可以工作。為了得到這些,你會(huì)檢查出來(lái)

$ git checkout-index -u -a

其中-u標(biāo)志意味著你要檢出,以保持指數(shù)最高最新(這樣你就不必事后刷新),-a標(biāo)志的意思是“簽出的所有文件”(如果你有一個(gè)陳舊的副本或簽出樹(shù)的舊版本,你可能還需要添加-f第一個(gè)標(biāo)志,告訴git checkout-index來(lái)強(qiáng)制覆蓋任何舊文件)。

再次,這可以全部簡(jiǎn)化

$ git clone git://git.kernel.org/pub/scm/git/git.git/ my-git
$ cd my-git
$ git checkout

這將最終為你做上述所有的事情。

您現(xiàn)在已經(jīng)成功復(fù)制了其他人的(我的)遠(yuǎn)程存儲(chǔ)庫(kù),并將其簽出。

創(chuàng)建一個(gè)新的分支

Git中的分支實(shí)際上只不過(guò)是從.git/refs/子目錄中進(jìn)入Git對(duì)象數(shù)據(jù)庫(kù)的指針,正如我們已經(jīng)討論的那樣,HEAD分支只不過(guò)是這些對(duì)象指針之一的符號(hào)鏈接。

您可以隨時(shí)通過(guò)在項(xiàng)目歷史記錄中選擇一個(gè)任意點(diǎn)來(lái)創(chuàng)建一個(gè)新分支,然后將該對(duì)象的SHA-1名稱寫(xiě)入一個(gè)文件中.git/refs/heads/。你可以使用你想要的任何文件名(實(shí)際上是子目錄),但是慣例是調(diào)用“普通”分支master。盡管如此,這只是一個(gè)慣例,沒(méi)有什么可以強(qiáng)制它。

舉個(gè)例子,讓我們回到我們之前使用的git-tutorial存儲(chǔ)庫(kù),并在其中創(chuàng)建一個(gè)分支。你只需說(shuō)出你想要簽出一個(gè)新的分支就可以做到這一點(diǎn):

$ git checkout -b mybranch

將在當(dāng)前HEAD位置創(chuàng)建一個(gè)新的分支,并切換到它。

注意

如果您決定在歷史中的某個(gè)其他位置啟動(dòng)新分支,而不是當(dāng)前的HEAD,那么您可以通過(guò)告訴git checkout檢出結(jié)果的基礎(chǔ)來(lái)做到這一點(diǎn)。換句話說(shuō),如果你有一個(gè)更早的標(biāo)簽或分支,你只需要做$ git checkout -b mybranch early-commit,它會(huì)在之前的提交中創(chuàng)建新的分支mybranch,然后檢查當(dāng)時(shí)的狀態(tài)。

你可以隨時(shí)跳回原來(lái)的master分支

$ git checkout master

(或者任何其他分支名稱),如果你忘記了你碰巧在哪個(gè)分支上,那么簡(jiǎn)單

$ cat .git/HEAD

會(huì)告訴你它指向的地方。要獲得您擁有的分支名單,您可以說(shuō)

$ git branch

它曾經(jīng)只是一個(gè)簡(jiǎn)單的腳本而已ls .git/refs/heads。目前分支前面會(huì)有一個(gè)星號(hào)。

有時(shí)您可能希望創(chuàng)建一個(gè)新的分支,without實(shí)際檢查并切換到該分支。如果是這樣,只需使用該命令

$ git branch <branchname> [startingpoint]

這將簡(jiǎn)單create分支,但不會(huì)做任何進(jìn)一步的事情。然后,你可以稍后 - 一旦你決定要在該分支上進(jìn)行實(shí)際開(kāi)發(fā),就可以git checkout使用branchname作為參數(shù)切換到該分支。

合并兩個(gè)分支

擁有一個(gè)分支的想法之一是你做了一些(可能是實(shí)驗(yàn)性的)工作,并最終將它合并回主分支。因此,假設(shè)您創(chuàng)建了mybranch與原始master分支相同的上述內(nèi)容,那么讓我們確保我們?cè)谠摲种е?,并在那里做一些工作?/p>

$ git checkout mybranch
$ echo "Work, work, work" >>hello
$ git commit -m "Some work." -i hello

在這里,我們只是添加了另一行hello,并且我們使用了一個(gè)簡(jiǎn)寫(xiě)來(lái)完成這兩個(gè)操作,git update-index hellogit commit通過(guò)直接給文件名直接git commit添加一個(gè)-i標(biāo)志(include除了索引文件到目前為止您做了什么之外,它還告訴Git 該文件)提交)。該-m標(biāo)志是從命令行提供提交日志消息。

現(xiàn)在,為了讓它更有趣一些,讓我們假設(shè)別人在原始分支中做了一些工作,并通過(guò)回到主分支來(lái)模擬它,并在那里編輯相同的文件:

$ git checkout master

在這里,花一些時(shí)間看看內(nèi)容hello,注意它們不包含我們剛才所做的工作mybranch- 因?yàn)檫@項(xiàng)工作在master分支中根本沒(méi)有發(fā)生。然后做

$ echo "Play, play, play" >>hello
$ echo "Lots of fun" >>example
$ git commit -m "Some fun." -i hello example

因?yàn)橹鞣种э@然心情更好。

現(xiàn)在,你有兩個(gè)分支,并且你決定要合并完成的工作。在我們這樣做之前,讓我們介紹一個(gè)很酷的圖形工具,幫助您查看正在發(fā)生的事情:

$ gitk --all

會(huì)以圖形方式向你展示你的兩個(gè)分支(這--all就是說(shuō):通常它會(huì)向你顯示你當(dāng)前的HEAD)和他們的歷史。你也可以看到他們是如何來(lái)自一個(gè)共同的來(lái)源。

無(wú)論如何,讓我們退出gitk^Q或文件菜單),并決定我們要將我們?cè)?code>mybranch分支上所做的工作合并到master分支中(這也是我們的工作HEAD)。要做到這一點(diǎn),有一個(gè)很好的腳本調(diào)用git merge,它想知道你想要解決哪些分支以及合并的內(nèi)容:

$ git merge -m "Merge work in mybranch" mybranch

如果可以自動(dòng)解析合并,則第一個(gè)參數(shù)將用作提交消息。

現(xiàn)在,在這種情況下,我們故意創(chuàng)建了需要手動(dòng)修正合并的情況,因此,Git會(huì)自動(dòng)完成它的功能(在這種情況下,它只是合并example文件,在mybranch分支中沒(méi)有差異),并說(shuō):

        Auto-merging hello        CONFLICT (content): Merge conflict in hello
        Automatic merge failed; fix conflicts and then commit the result.

它告訴你它做了一個(gè)“自動(dòng)合并”,由于hello沖突導(dǎo)致失敗。

不用擔(dān)心。如果你曾經(jīng)使用過(guò)CVS,那么它就會(huì)以相同的形式在hello留下(小小的)沖突,所以讓我們?cè)谖覀兊木庉嬈髦写蜷_(kāi)hello(不管怎么說(shuō)),然后以某種方式修復(fù)它。我建議讓hello包含所有四行:

Hello World
It's a new day for git
Play, play, play
Work, work, work

一旦你對(duì)你的手動(dòng)合并感到滿意,就執(zhí)行

$ git commit -i hello

這會(huì)非常大聲地警告你,你現(xiàn)在正在進(jìn)行合并(這是正確的,所以不要介意),并且你可以寫(xiě)一個(gè)關(guān)于你的冒險(xiǎn)的小型合并信息git merge

完成后,啟動(dòng)gitk --all以圖形方式查看歷史記錄的樣子。注意,mybranch仍然存在,你可以切換到它,并繼續(xù)使用它,如果你想。該mybranch分支不包含合并,但下一次您從master分支中合并它時(shí),Git會(huì)知道您如何合并它,因此您不必再次合并。

另一個(gè)有用的工具,特別是如果你不總是在X-Window環(huán)境下工作,是git show-branch

$ git show-branch --topo-order --more=1 master mybranch* [master] Merge work in mybranch ! [mybranch] Some work.---  [master] Merge work in mybranch*+ [mybranch] Some work.*  [master^] Some fun.

前兩行表示它顯示了兩個(gè)分支,它們的樹(shù)頂部提交的標(biāo)題,您當(dāng)前在master分支上(注意星號(hào)*字符),后面的輸出行的第一列用于顯示master分支中包含的提交以及分支的第二列mybranch。顯示了三個(gè)提交以及他們的標(biāo)題。它們都在第一列中有空白字符(*顯示當(dāng)前分支上的普通提交,-是合并提交),這意味著它們現(xiàn)在是master分支的一部分。只有“某些工作”提交+在第二列中具有加號(hào)字符,因?yàn)?code>mybranch尚未合并到主分支的這些提交中。提交日志消息之前的括號(hào)內(nèi)的字符串是一個(gè)短名稱,可用于命名提交。在上面的例子中,master并且mybranch是分支頭。master^master分支頭的第一位家長(zhǎng)。如果您想查看更復(fù)雜的案例,請(qǐng)參閱gitrevisions[7]。

注意

如果沒(méi)有--more = 1選項(xiàng),git show-branch將不會(huì)輸出master ^ commit,因?yàn)閙ybranch提交是master和mybranch提示的共同祖先。有關(guān)詳細(xì)信息,請(qǐng)參閱git-show-branch1。

注意

如果合并后主分支上有更多的提交,默認(rèn)情況下合并提交本身不會(huì)由git show-branch顯示。在這種情況下,您需要提供--sparse選項(xiàng)以使合并提交可見(jiàn)。

現(xiàn)在,讓我們假裝你是一個(gè)完成所有工作的人mybranch,并且你辛勤工作的成果最終被合并到master分支中。讓我們回到mybranch,并運(yùn)行git merge以獲取“上游變更”回到您的分支。

$ git checkout mybranch
$ git merge -m "Merge upstream changes." master

這會(huì)輸出這樣的內(nèi)容(實(shí)際的提交對(duì)象名稱會(huì)有所不同)

Updating from ae3a2da... to a80b4aa....Fast-forward (no commit created; -m option ignored)
 example | 1 +
 hello   | 1 + 2 files changed, 2 insertions(+)

因?yàn)槟愕姆种](méi)有包含任何已經(jīng)合并到master分支中的東西,合并操作實(shí)際上并沒(méi)有進(jìn)行合并。相反,它只是將分支樹(shù)的頂部更新為分支樹(shù)的頂部master。這通常被稱為fast-forward合并。

您可以再次運(yùn)行gitk --all以查看commit ancestry的外觀,或者運(yùn)行show-branch,這可以告訴您這一點(diǎn)。

$ git show-branch master mybranch! [master] Merge work in mybranch * [mybranch] Merge work in mybranch---- [master] Merge work in mybranch

合并外部工作

通常情況下,與其他人合并比合并你自己的分支要普遍得多,所以值得指出的是,Git也使得它非常簡(jiǎn)單,事實(shí)上,與做一個(gè)沒(méi)有什么不同git merge。事實(shí)上,遠(yuǎn)程合并最終不過(guò)是“將工作從遠(yuǎn)程存儲(chǔ)庫(kù)獲取到臨時(shí)標(biāo)記中”,然后是一個(gè)git merge。

不出所料,從遠(yuǎn)程存儲(chǔ)庫(kù)中獲取git fetch

$ git fetch <remote-repository>

可以使用下列其中一個(gè)傳輸命令來(lái)從以下位置下載存儲(chǔ)庫(kù):

SSH

remote.machine:/path/to/repo.git/ or

ssh://remote.machine/path/to/repo.git/

此傳輸可用于上傳和下載,并要求您擁有ssh遠(yuǎn)程計(jì)算機(jī)的登錄權(quán)限。它通過(guò)交換兩端提交的頭部提交并轉(zhuǎn)移(接近)最小的一組對(duì)象來(lái)找出對(duì)方缺乏的對(duì)象集合。這是在庫(kù)之間交換Git對(duì)象的最有效方式。

本地目錄

/path/to/repo.git/

此傳輸與SSH傳輸相同,但用于sh在本地計(jì)算機(jī)上運(yùn)行兩端,而不是在遠(yuǎn)程計(jì)算機(jī)上運(yùn)行另一端ssh。

Git Native

git://remote.machine/path/to/repo.git/

此傳輸設(shè)計(jì)用于匿名下載。與SSH傳輸一樣,它可以找到下游端缺少的對(duì)象集合,并將其轉(zhuǎn)移(接近)最小的一組對(duì)象。

HTTP(S)

http://remote.machine/path/to/repo.git/

Downloader從http和https URL首先通過(guò)查看repo.git/refs/目錄下指定的refname從遠(yuǎn)程站點(diǎn)獲取最高的提交對(duì)象名稱,然后嘗試通過(guò)repo.git/objects/xx/xxx...使用該提交對(duì)象的對(duì)象名稱進(jìn)行下載來(lái)獲取提交對(duì)象。然后它讀取提交對(duì)象以找出其父提交和關(guān)聯(lián)樹(shù)對(duì)象;它會(huì)重復(fù)這個(gè)過(guò)程,直到它獲得所有必需的對(duì)象。由于這種行為,他們有時(shí)也被稱為commit walkers。

commit walkers有時(shí)也被稱為dumb transports,因?yàn)樗鼈儾恍枰魏蔚腉it知道智能服務(wù)器如Git機(jī)傳輸一樣。任何股票甚至不支持目錄索引的HTTP服務(wù)器就足夠了。但是您必須準(zhǔn)備好您的存儲(chǔ)庫(kù)git update-server-info來(lái)幫助傳輸下載者。

一旦你從遠(yuǎn)程倉(cāng)庫(kù)獲取,merge當(dāng)前分支。

然而 - fetch是一件很常見(jiàn)的事情,然后立即merge就被調(diào)用git pull,你可以簡(jiǎn)單地做

$ git pull <remote-repository>

并且可選地給遠(yuǎn)端的分支名稱作為第二個(gè)參數(shù)。

注意

根本不需要使用任何分支,通過(guò)保留盡可能多的本地存儲(chǔ)庫(kù),因?yàn)槟M麚碛蟹种?,并使用git pull合并它們,就像在分支之間合并一樣。這種方法的優(yōu)點(diǎn)是,它可以讓你為每個(gè)分支保存一組文件,并且如果你同時(shí)處理多行開(kāi)發(fā),你可能會(huì)發(fā)現(xiàn)來(lái)回切換更容易。當(dāng)然,您將支付更多磁盤使用的代價(jià)來(lái)保存多個(gè)工作樹(shù),但是現(xiàn)在磁盤空間很便宜。

您可能會(huì)不時(shí)從同一個(gè)遠(yuǎn)程存儲(chǔ)庫(kù)中獲取數(shù)據(jù)。簡(jiǎn)而言之,您可以將遠(yuǎn)程存儲(chǔ)庫(kù)URL存儲(chǔ)在本地存儲(chǔ)庫(kù)的配置文件中,如下所示:

$ git config remote.linus.url http://www.kernel.org/pub/scm/git/git.git/

并使用“l(fā)inus”關(guān)鍵字git pull而不是完整的URL。

示例:

  1. git pull linus

  2. git pull linus tag v0.99.1

以上相當(dāng)于:

  1. git pull http://www.kernel.org/pub/scm/git/git.git/ HEAD

  2. git pull http://www.kernel.org/pub/scm/git/git.git/ tag v0.99.1

合并如何工作?

我們說(shuō)這個(gè)教程展示了什么管道可以幫助你應(yīng)對(duì)不沖水的瓷器,但我們迄今沒(méi)有談到合并的真正效果。如果您是第一次使用本教程,我會(huì)建議跳至“發(fā)布您的作品”部分,稍后再回來(lái)。

好,還跟得上嗎?為了讓我們看一個(gè)示例,讓我們回到先前的存儲(chǔ)庫(kù),帶有“hello”和“example”文件,并讓我們回到預(yù)合并狀態(tài):

$ git show-branch --more=2 master mybranch! [master] Merge work in mybranch * [mybranch] Merge work in mybranch---- [master] Merge work in mybranch+* [master^2] Some work.+* [master^] Some fun.

請(qǐng)記住,在運(yùn)行git merge之前,我們的masterhead在“享受些樂(lè)趣”。承諾,而我們的mybranchhead在“做些工作”。commit.

$ git checkout mybranch
$ git reset --hard master^2$ git checkout master
$ git reset --hard master^

倒回后,提交結(jié)構(gòu)應(yīng)如下所示:

$ git show-branch* [master] Some fun. ! [mybranch] Some work.--*  [master] Some fun. + [mybranch] Some work.*+ [master^] Initial commit

現(xiàn)在我們已經(jīng)準(zhǔn)備好嘗試手動(dòng)合并了。

git merge命令,當(dāng)合并兩個(gè)分支時(shí),使用3-way合并算法。首先,它找到它們之間的共同祖先。它使用的命令是git merge-base

$ mb=$(git merge-base HEAD mybranch)

該命令將公共祖先的提交對(duì)象名稱寫(xiě)入標(biāo)準(zhǔn)輸出,因此我們將其輸出捕獲到一個(gè)變量中,因?yàn)槲覀儗⒃谙乱徊街惺褂盟?。順便說(shuō)一下,在這種情況下,共同的祖先提交是“初始提交”提交。你可以告訴它:

$ git name-rev --name-only --tags $mb
my-first-tag

找到一個(gè)共同的祖先提交后,第二步是這樣的:

$ git read-tree -m -u $mb HEAD mybranch

這與git read-tree我們已經(jīng)看到的命令是一樣的,但是與以前的例子不同,它需要三棵樹(shù)。這將每棵樹(shù)的內(nèi)容讀入stage索引文件中的不同內(nèi)容(第一棵樹(shù)進(jìn)入第一階段,第二棵樹(shù)進(jìn)入第二階段,等等)。在將三棵樹(shù)讀入三個(gè)階段之后,所有三個(gè)階段中相同的路徑都collapsed進(jìn)入階段0.在三個(gè)階段中的兩個(gè)階段中相同的路徑折疊到階段0,從階段2獲取SHA-1或者階段3,與第一階段不同(即只有一側(cè)從共同祖先改變)。

collapsing操作之后,三棵樹(shù)中不同的路徑將保留在非零階段。此時(shí),您可以使用以下命令檢查索引文件:

$ git ls-files --stage100644 7f8b141b65fdcee47321e399a2598a235a032422 0        example100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1        hello100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2        hello100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello

在我們僅有兩個(gè)文件的示例中,我們沒(méi)有沒(méi)有更改的文件,因此只能example導(dǎo)致崩潰。但是在現(xiàn)實(shí)生活中的大型項(xiàng)目中,當(dāng)一次提交中只有少量文件發(fā)生變化時(shí),這collapsing往往會(huì)使相當(dāng)快速的大部分路徑輕松合并,從而在非零階段只發(fā)生少量實(shí)際變化。

要僅查看非零階段,請(qǐng)使用--unmerged標(biāo)志:

$ git ls-files --unmerged100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1        hello100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2        hello100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello

合并的下一步是合并這三個(gè)版本的文件,使用3-way合并。這是通過(guò)將git merge-one-file命令作為命令的參數(shù)之一來(lái)完成的git merge-index

$ git merge-index git-merge-one-file hello
Auto-merging hello
ERROR: Merge conflict in hello
fatal: merge program failed

git merge-one-file用參數(shù)調(diào)用腳本來(lái)描述這三個(gè)版本,并負(fù)責(zé)將合并結(jié)果留在工作樹(shù)中。這是一個(gè)相當(dāng)簡(jiǎn)單的shell腳本,最終調(diào)用mergeRCS套件中的程序執(zhí)行文件級(jí)3路合并。在這種情況下,merge檢測(cè)到?jīng)_突,并將帶有沖突標(biāo)記的合并結(jié)果留在工作樹(shù)中。如果ls-files --stage在此時(shí)再次運(yùn)行,可以看到這一點(diǎn):

$ git ls-files --stage100644 7f8b141b65fdcee47321e399a2598a235a032422 0        example100644 557db03de997c86a4a028e1ebd3a1ceb225be238 1        hello100644 ba42a2a96e3027f3333e13ede4ccf4498c3ae942 2        hello100644 cc44c73eb783565da5831b4d820c962954019b69 3        hello

這是索引文件的狀態(tài),并且工作文件git merge將控制權(quán)返回給您,并將沖突合并留給您解決。請(qǐng)注意,路徑hello尚未被合并,此時(shí)您看到的git diff是自第2階段(即您的版本)以來(lái)的差異。

發(fā)布你的作品

所以,我們可以使用別人的作品從遠(yuǎn)程倉(cāng)庫(kù),但如何能準(zhǔn)備一個(gè)資料庫(kù),讓其他人撤出?

您在工作樹(shù)中執(zhí)行真正的工作,并將主存儲(chǔ)庫(kù)掛在其.git子目錄下。您可以遠(yuǎn)程訪問(wèn)該存儲(chǔ)庫(kù),并要求人們從中獲取信息,但實(shí)際上這并不是通常的做法。推薦的方法是擁有一個(gè)公共存儲(chǔ)庫(kù),讓其他人可以訪問(wèn)該存儲(chǔ)庫(kù),并且當(dāng)您在主工作樹(shù)中所做的更改狀態(tài)良好時(shí),請(qǐng)從中更新公共存儲(chǔ)庫(kù)。這通常被稱為pushing。

注意

這個(gè)公共存儲(chǔ)庫(kù)可以進(jìn)一步被鏡像,這就是kernel.org上的Git存儲(chǔ)庫(kù)的管理方式。

將本地(專用)存儲(chǔ)庫(kù)中的更改發(fā)布到遠(yuǎn)程(公用)存儲(chǔ)庫(kù)需要在遠(yuǎn)程計(jì)算機(jī)上具有寫(xiě)權(quán)限。您需要有一個(gè)SSH帳戶才能運(yùn)行單個(gè)命令,git-receive-pack。

首先,您需要在將存放公共存儲(chǔ)庫(kù)的遠(yuǎn)程機(jī)器上創(chuàng)建一個(gè)空的存儲(chǔ)庫(kù)。這個(gè)空的存儲(chǔ)庫(kù)將被填充并隨后推入,以保持最新?tīng)顟B(tài)。顯然,這個(gè)存儲(chǔ)庫(kù)的創(chuàng)建只需要完成一次。

注意

git push使用一對(duì)命令,本地機(jī)器上的git send-pack和遠(yuǎn)程機(jī)器上的git-receive-pack。兩者之間的通信通過(guò)網(wǎng)絡(luò)在內(nèi)部使用SSH連接。

您的私有存儲(chǔ)庫(kù)的Git目錄通常是.git,但您的公共存儲(chǔ)庫(kù)通常以項(xiàng)目名稱命名,即<project>.git。讓我們?yōu)轫?xiàng)目創(chuàng)建一個(gè)這樣的公共存儲(chǔ)庫(kù)my-git。登錄到遠(yuǎn)程機(jī)器后,創(chuàng)建一個(gè)空目錄:

$ mkdir my-git.git

然后,通過(guò)運(yùn)行將該目錄設(shè)置為Git存儲(chǔ)庫(kù)git init,但這次由于其名稱.git并不常見(jiàn),所以我們的做法略有不同:

$ GIT_DIR=my-git.git git init

確保此目錄可用于您希望通過(guò)您選擇的交通工具提取您的更改的其他人。你也需要確保你在$PATH有這個(gè)git-receive-pack程序。

注意

當(dāng)你直接運(yùn)行程序時(shí),許多sshd安裝不會(huì)將你的shell作為登錄shell調(diào)用; 這意味著如果您的登錄shell是bash,則只讀取.bashrc而不是.bash_profile。作為一種解決方法,確保.bashrc設(shè)置$ PATH,以便您可以運(yùn)行g(shù)it-receive-pack程序。

注意

如果你打算發(fā)布這個(gè)通過(guò)http訪問(wèn)的倉(cāng)庫(kù),你應(yīng)該在這個(gè)時(shí)候執(zhí)行mv my-git.git/hooks/post-update.sample my-git.git/hooks/post-update。這可以確保每次你進(jìn)入這個(gè)倉(cāng)庫(kù)時(shí),都會(huì)運(yùn)行g(shù)it update-server-info。

您的“公共存儲(chǔ)庫(kù)”現(xiàn)在已準(zhǔn)備好接受您的更改。回到你有你的私人存儲(chǔ)庫(kù)的機(jī)器。從那里運(yùn)行這個(gè)命令:

$ git push <public-host>:/path/to/my-git.git master

這會(huì)使您的公共存儲(chǔ)庫(kù)與master您的當(dāng)前存儲(chǔ)庫(kù)中與指定的分支頭(即本例中)和它們可訪問(wèn)的對(duì)象相匹配。

作為一個(gè)真實(shí)的例子,這是我更新公共Git存儲(chǔ)庫(kù)的方式。Kernel.org鏡像網(wǎng)絡(luò)負(fù)責(zé)傳播給其他公開(kāi)可見(jiàn)的機(jī)器:

$ git push master.kernel.org:/pub/scm/git/git.git/

打包你的倉(cāng)庫(kù)

之前,我們看到.git/objects/??/為目錄創(chuàng)建的每個(gè)Git對(duì)象都存儲(chǔ)目錄下的一個(gè)文件。這種表示對(duì)于原子級(jí)和安全地創(chuàng)建是有效的,但在網(wǎng)絡(luò)上傳輸并不方便。由于Git對(duì)象一旦創(chuàng)建就不可變,因此可以通過(guò)“將它們組合在一起”來(lái)優(yōu)化存儲(chǔ)。命令

$ git repack

會(huì)為你完成。如果你按照教程的例子,你現(xiàn)在已經(jīng)在.git/objects/??/目錄中累積了約17個(gè)對(duì)象。git repack告訴您打包了多少個(gè)對(duì)象,并將打包文件存儲(chǔ)在.git/objects/pack目錄中。

注意

您將在.git/objects/pack目錄中看到兩個(gè)文件,即pack  -  *。pack和pack  -  *。idx。它們彼此密切相關(guān),如果您出于任何原因手動(dòng)將它們復(fù)制到不同的存儲(chǔ)庫(kù),則應(yīng)確保將它們復(fù)制在一起。前者保存包中對(duì)象的所有數(shù)據(jù),后者保存隨機(jī)訪問(wèn)的索引。

如果你偏執(zhí),運(yùn)行git verify-pack命令會(huì)檢測(cè)你是否有腐敗的包裝,但不要太擔(dān)心。我們的項(xiàng)目總是完美的;-)。

一旦你打包了對(duì)象,你就不需要保留包文件中包含的解壓對(duì)象了。

$ git prune-packed

會(huì)為你刪除它們。

如果您好奇,您可以在跑步find .git/objects -type f前后嘗試跑步git prune-packed。此外git count-objects還會(huì)告訴您存儲(chǔ)庫(kù)中有多少個(gè)未打包對(duì)象以及它們占用了多少空間。

注意

對(duì)于HTTP傳輸來(lái)說(shuō),git pull稍微麻煩,因?yàn)榇虬拇鎯?chǔ)庫(kù)可能包含相對(duì)較少包中的相對(duì)較少的對(duì)象。如果你期望從你的公共倉(cāng)庫(kù)獲取很多HTTP請(qǐng)求,你可能需要經(jīng)常重新打包和修剪,或者永遠(yuǎn)不要修剪。

如果此時(shí)再次運(yùn)行git repack,則會(huì)顯示“沒(méi)有新包裝”。一旦繼續(xù)開(kāi)發(fā)并累積更改,git repack再次運(yùn)行將創(chuàng)建一個(gè)新包,其中包含自上次打包存儲(chǔ)庫(kù)后創(chuàng)建的對(duì)象。我們建議您在初次導(dǎo)入后盡快打包項(xiàng)目(除非您從頭開(kāi)始項(xiàng)目),然后git repack每隔一段時(shí)間運(yùn)行一次,具體取決于項(xiàng)目的活躍程度。

當(dāng)一個(gè)儲(chǔ)存庫(kù)是通過(guò)同步git pushgit pull填充在源存儲(chǔ)庫(kù)對(duì)象通常存儲(chǔ)在目的地解壓。雖然這允許您在兩端使用不同的打包策略,但這也意味著您可能需要每隔一段時(shí)間重新打包兩個(gè)存儲(chǔ)庫(kù)。

與他人合作

盡管Git是一個(gè)真正的分布式系統(tǒng),但通過(guò)非正式的開(kāi)發(fā)人員層次來(lái)組織項(xiàng)目通常很方便。Linux內(nèi)核開(kāi)發(fā)就是這樣運(yùn)行的。在Randy Dunlap的演講中有一個(gè)很好的例子(第17頁(yè),“合并到Mainline”)。

應(yīng)該強(qiáng)調(diào)的是,這個(gè)層次純粹是非正式的。在Git中沒(méi)有任何基礎(chǔ)強(qiáng)制執(zhí)行這個(gè)層次結(jié)構(gòu)所暗示的“補(bǔ)丁流程鏈”。您不必僅從一個(gè)遠(yuǎn)程存儲(chǔ)庫(kù)中獲取數(shù)據(jù)。

“項(xiàng)目領(lǐng)導(dǎo)”的推薦工作流程如下所示:

  1. 在本地計(jì)算機(jī)上準(zhǔn)備主要存儲(chǔ)庫(kù)。你的工作在那里完成。

  2. 準(zhǔn)備一個(gè)可供他人訪問(wèn)的公共存儲(chǔ)庫(kù)。如果其他人通過(guò)啞傳輸協(xié)議(HTTP)從您的存儲(chǔ)庫(kù)中提取數(shù)據(jù),則需要保留此存儲(chǔ)庫(kù)dumb transport friendly。之后git init,$GIT_DIR/hooks/post-update.sample從標(biāo)準(zhǔn)模板復(fù)制將包含呼叫,git update-server-info但您需要手動(dòng)啟用掛鉤mv post-update.sample post-update。這確保git update-server-info了必要的文件保持最新。

  3. 從主存儲(chǔ)庫(kù)推入公共存儲(chǔ)庫(kù)。

  4. git repack公共存儲(chǔ)庫(kù)。這會(huì)建立一個(gè)包含初始對(duì)象集合作為基準(zhǔn)的大包,并且可能git prune用于從存儲(chǔ)庫(kù)中提取的傳輸支持打包存儲(chǔ)庫(kù)。

  5. 繼續(xù)在主存儲(chǔ)庫(kù)中工作。您的更改包括修改您自己的修改,通過(guò)電子郵件收到的修補(bǔ)程序以及從您的“子系統(tǒng)維護(hù)者”的“公共”存儲(chǔ)庫(kù)中拉取合并。只要你愿意,你可以重新包裝這個(gè)私人存儲(chǔ)庫(kù)。

  6. 將更改推送到公共存儲(chǔ)庫(kù),并向公眾發(fā)布。

  7. 每過(guò)一段時(shí)間,git repack公共存儲(chǔ)庫(kù)。返回步驟5.繼續(xù)工作。

為該項(xiàng)目工作并擁有自己的“公共存儲(chǔ)庫(kù)”的“子系統(tǒng)維護(hù)人員”推薦的工作周期如下所示:

  1. 通過(guò)git clone在“項(xiàng)目負(fù)責(zé)人”的公共存儲(chǔ)庫(kù)上運(yùn)行,準(zhǔn)備工作存儲(chǔ)庫(kù)。用于初始克隆的URL存儲(chǔ)在remote.origin.url配置變量中。

  2. 準(zhǔn)備一個(gè)可供他人訪問(wèn)的公共存儲(chǔ)庫(kù),就像“項(xiàng)目負(fù)責(zé)人”一樣。

  3. 將“項(xiàng)目負(fù)責(zé)人”公共存儲(chǔ)庫(kù)中的打包文件復(fù)制到公共存儲(chǔ)庫(kù),除非“項(xiàng)目負(fù)責(zé)人”存儲(chǔ)庫(kù)與您的計(jì)算機(jī)位于同一臺(tái)計(jì)算機(jī)上。在后一種情況下,您可以使用objects/info/alternates文件指向您從中借用的存儲(chǔ)庫(kù)。

  4. 從主存儲(chǔ)庫(kù)推入公共存儲(chǔ)庫(kù)。運(yùn)行git repack,并且可能git prune用于從存儲(chǔ)庫(kù)中提取的傳輸支持打包的存儲(chǔ)庫(kù)。

  5. 繼續(xù)在主存儲(chǔ)庫(kù)中工作。您的更改包括修改您自己的修改,通過(guò)電子郵件收到的修補(bǔ)程序,以及拉動(dòng)“項(xiàng)目負(fù)責(zé)人”的“公共”存儲(chǔ)庫(kù)和可能的“子子系統(tǒng)維護(hù)人員”所產(chǎn)生的合并。只要你愿意,你可以重新包裝這個(gè)私人存儲(chǔ)庫(kù)。

  6. 將更改推送到公共存儲(chǔ)庫(kù),并請(qǐng)求您的“項(xiàng)目負(fù)責(zé)人”和可能的“子子系統(tǒng)維護(hù)人員”從中抽取。

  7. 每過(guò)一段時(shí)間,git repack公共存儲(chǔ)庫(kù)。返回步驟5.繼續(xù)工作。

沒(méi)有“公共”存儲(chǔ)庫(kù)的“個(gè)人開(kāi)發(fā)人員”的建議工作周期稍有不同。它是這樣的:

  1. 通過(guò)git clone“項(xiàng)目負(fù)責(zé)人”(或“子系統(tǒng)維護(hù)人員”,如果您在子系統(tǒng)上工作)的公共存儲(chǔ)庫(kù)準(zhǔn)備工作存儲(chǔ)庫(kù)。用于初始克隆的URL存儲(chǔ)在remote.origin.url配置變量中。

  2. master分支機(jī)構(gòu)的倉(cāng)庫(kù)中工作。

  3. git fetch origin每隔一段時(shí)間從上游的公共存儲(chǔ)庫(kù)運(yùn)行。這只有前半部分,git pull但不合并。公共存儲(chǔ)庫(kù)的頭部存儲(chǔ)在.git/refs/remotes/origin/master

  4. 使用git cherry origin查看哪些補(bǔ)丁那些被接受,和/或使用git rebase origin端口的未合并的變化著更新的上游。

  5. 使用git format-patch origin準(zhǔn)備用于電子郵件提交補(bǔ)丁,你的上游,并發(fā)送出去。返回第2步并繼續(xù)。

與他人合作,共享存儲(chǔ)庫(kù)風(fēng)格

如果您來(lái)自CVS背景,上一節(jié)中提出的合作風(fēng)格對(duì)您來(lái)說(shuō)可能是新的。你不必?fù)?dān)心。Git支持您可能更熟悉的“共享公共存儲(chǔ)庫(kù)”合作風(fēng)格。

有關(guān)詳細(xì)信息,請(qǐng)參閱gitcvs-migration[7]。

將你的工作捆綁在一起

您可能一次只能處理一件以上的事情。使用Git分支來(lái)管理那些或多或少的獨(dú)立任務(wù)是很容易的。

我們已經(jīng)看到了分支機(jī)構(gòu)以前的工作方式,以兩個(gè)分支機(jī)構(gòu)的“樂(lè)趣和工作”為例。如果有兩個(gè)以上的分支,這個(gè)想法是一樣的。假設(shè)你從“主”頭開(kāi)始,并在“主”分支中有一些新代碼,并在“提交 - 修復(fù)”和“差異修復(fù)”分支中有兩個(gè)獨(dú)立修復(fù):

$ git show-branch! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection.  * [master] Release candidate #1--- +  [diff-fix] Fix rename detection. +  [diff-fix~1] Better common substring algorithm.+   [commit-fix] Fix commit message normalization.  * [master] Release candidate #1++* [diff-fix~2] Pretty-print messages.

這兩個(gè)修補(bǔ)程序都經(jīng)過(guò)了很好的測(cè)試,在這一點(diǎn)上,您想要在它們兩個(gè)中進(jìn)行合并。你可以先合并diff-fix然后再合并commit-fix,如下所示:

$ git merge -m "Merge fix in diff-fix" diff-fix
$ git merge -m "Merge fix in commit-fix" commit-fix

這將導(dǎo)致:

$ git show-branch! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection.  * [master] Merge fix in commit-fix---  - [master] Merge fix in commit-fix+ * [commit-fix] Fix commit message normalization.  - [master~1] Merge fix in diff-fix +* [diff-fix] Fix rename detection. +* [diff-fix~1] Better common substring algorithm.  * [master~2] Release candidate #1++* [master~3] Pretty-print messages.

但是,沒(méi)有什么特別的理由要先合并一個(gè)分支,然后再合并,當(dāng)你有一系列真正獨(dú)立的變化時(shí)(如果順序重要,那么它們就不是定義上的獨(dú)立)。您可以將這兩個(gè)分支同時(shí)合并到當(dāng)前分支中。首先讓我們撤銷我們剛剛做的并重新開(kāi)始。我們希望在這兩次合并之前將主分支重置為master~2

$ git reset --hard master~2

你可以確保git show-branchgit merge你剛剛做的那兩個(gè)之前匹配狀態(tài)。然后,不是連續(xù)運(yùn)行兩個(gè)git merge命令,而是合并這兩個(gè)分支頭(這被稱為making an Octopus):

$ git merge commit-fix diff-fix
$ git show-branch! [commit-fix] Fix commit message normalization. ! [diff-fix] Fix rename detection.  * [master] Octopus merge of branches 'diff-fix' and 'commit-fix'---  - [master] Octopus merge of branches 'diff-fix' and 'commit-fix'+ * [commit-fix] Fix commit message normalization. +* [diff-fix] Fix rename detection. +* [diff-fix~1] Better common substring algorithm.  * [master~1] Release candidate #1++* [master~2] Pretty-print messages.

請(qǐng)注意,你不應(yīng)該因?yàn)槟憧梢宰鯫ctopus。如果要同時(shí)合并兩個(gè)以上的獨(dú)立更改,章魚(yú)是一件有效的事情,并且通??梢愿菀椎夭榭刺峤粴v史記錄。但是,如果您與正在合并且需要手工解決的任何分支合并沖突,則表示發(fā)生在這些分支中的發(fā)展畢竟不是獨(dú)立的,并且您應(yīng)該一次合并兩個(gè)分支,記錄如何你解決了沖突,以及你偏好一方的變化。否則,它會(huì)使項(xiàng)目的歷史難以跟上,并不容易。

也可以看看

gittutorial[7], gittutorial-2[7], gitcvs-migration[7], git-help[1], giteveryday[7], The Git User’s Manual

Previous article: Next article: