一、Git 基礎圖解
轉自:http://www.cnblogs.com/yaozhongxiao/p/3811130.html
git中文件內容並沒有真正存儲在索引(.git/index)或者提交對象中,而是以blob的形式分別存儲在數據庫中(.git/objects),並用SHA-1值來校驗。 索引文件用識別碼列出相關的blob文件以及別的數據。對於提交來說,以樹(tree)的形式存儲,同樣用對於的哈希值識別。樹對應着工作目錄中的文件夾,樹中包含的 樹或者blob對象對應着相應的子目錄和文件。每次提交都存儲下它的上一級樹的識別碼。
如果用detached HEAD提交,那么最后一次提交會被the reflog for HEAD引用。但是過一段時間就失效,最終被回收,與git commit --amend
或者git rebase
很像。
git 模型可以抽象為 遠程倉庫——remote, 本地三級倉庫: level1——working directory level2——stage(index) level3——repository(History)
git 各個命令可以理解為在各個倉庫間轉移數據,各個命令對應對每個倉庫輸入輸出。
便於記憶可以簡單分為 低level輸入和 高level輸入, 注意各level並不一定是相鄰的level間轉移,可以跨level轉移,通過git命令的參數選項來實現,
如常見的 git checkout git reset git commit
低level輸入 | 高level 輸入 | |
working directory | 手工創建 | git checkout/git stash |
stage(index) | git add | git reset |
History(repository) | git commit | git pull |
remote | git push | - |
根據上述表格,結合git命令各種參數的結合,可以數據在各倉庫間的轉移,
需要注意的是不論參數如何變化,, 除了git reset 外,上述各命令目的倉庫按照如上表所示是確定的,參數和選項來決定數據的來源
基本用法
上面的四條命令在工作目錄、暫存目錄(也叫做索引)和倉庫之間復制文件。
git add files
把當前文件放入暫存區域。git commit
給暫存區域生成快照並提交。git reset -- files
用來撤銷最后一次git add files
,你也可以用git reset
撤銷所有暫存區域文件。(操作對象是HEAD)git checkout -- files
把文件從暫存區域復制到工作目錄,用來丟棄本地修改。(目的是working Directory)
你可以用 git reset -p
, git checkout -p
, or git add -p
進入交互模式。
也可以跳過暫存區域直接從倉庫取出文件或者直接提交代碼, 如下
git commit -a
相當於運行 git add 把所有當前目錄下的文件加入暫存區域再運行。git commit.git commit files
進行一次包含最后一次提交加上工作目錄中文件快照的提交。並且文件被添加到暫存區域。git checkout HEAD -- files
回滾到復制最后一次提交。
diff
有許多種方法查看兩次提交之間的變動。下面是一些示例。
本質上就是在前言中表述的4個level中任意指定2個倉庫進行比較
4
commit
提交時,git用暫存區域的文件創建一個新的提交,並把此時的節點設為父節點。然后把當前分支指向新的提交節點。下圖中,當前分支是master。 在運行命令之前,master指向ed489,提交后,master指向新的節點f0cec並以ed489作為父節點
5
下圖中,在master分支的祖父節點maint分支進行一次提交,生成了1800b。 這樣,maint分支就不再是master分支的祖父節點。此時,合並 (或者 衍合) 是必須的。
6
如果想更改一次提交,使用 git commit --amend
。git會使用與當前提交相同的父節點進行一次新提交,舊的提交會被取消
7
checkout
checkout命令用於從歷史提交(或者暫存區域)中拷貝文件到工作目錄,也可用於切換分支。
當給定某個文件名(或者打開-p選項,或者文件名和-p選項同時打開)時,git會從指定的提交中拷貝文件到暫存區域和工作目錄。比如,git checkout HEAD~ foo.c
會將提交節點HEAD~(即當前提交節點的父節點)中的foo.c
復制到工作目錄並且加到暫存區域中。(如果命令中沒有指定提交節點,則會從暫存區域中拷貝內容。)
注意當前分支不會發生變化(HEAD指向原處)。
8
當不指定文件名,而是給出一個(本地)分支時,那么HEAD標識會移動到那個分支(也就是說,我們“切換”到那個分支了),然后暫存區域和工作目錄中的內容會和HEAD對應的提交節點一致。新提交節點(下圖中的a47c3)中的所有文件都會被復制(到暫存區域和工作目錄中);只存在於老的提交節點(ed489)中的文件會被刪除;不屬於上述兩者的文件會被忽略,不受影響。
9
如果既沒有指定文件名,也沒有指定分支名,而是一個標簽、遠程分支、SHA-1值或者是像master~3類似的東西,就得到一個匿名分支,稱作detached HEAD(被分離的HEAD標識)。這樣可以很方便地在歷史版本之間互相切換。比如說你想要編譯1.6.6.1版本的git,你可以運行git checkout v1.6.6.1
(這是一個標簽,而非分支名),編譯,安裝,然后切換回另一個分支,比如說git checkout master
。然而,當提交操作涉及到“分離的HEAD”時,其行為會略有不同,詳情見在下面。
10
HEAD標識處於分離狀態時的提交操作
當HEAD處於分離狀態(不依附於任一分支)時,提交操作可以正常進行,但是不會更新任何已命名的分支。(你可以認為這是在更新一個匿名分支。)
11
一旦此后你切換到別的分支,比如說master,那么這個提交節點(可能)再也不會被引用到,然后就會被丟棄掉了。注意這個命令之后就不會有東西引用2eecb。
12
但是,如果你想保存這個狀態,可以用命令git checkout -b name
來創建一個新的分支。
13
- 另外, checkout還有還有如下使用
- 當執行 "git checkout ." 或者 "git checkout -- <file>" 命令時,會用暫存區全部或指定的文件替換工作區的文件。這個操作很危險,會清除工作區中未添加到暫存區的改動。
- 當執行 "git checkout HEAD ." 或者 "git checkout HEAD <file>" 命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區和以及工作區中的文件。這個命令也是極具危險性的,因為不但會清除工作區中未提交的改動,也會清除暫存區中未提交的改 動。
- git checkout -f =等價=> git checkout HEAD . 特別注意,當git checkout -f 不帶任何的commit 和文件時,會徹底刪除 index 和 working tree未提交內容。
reset
reset命令把當前分支指向另一個位置,並且有選擇的變動工作目錄和索引。也用來在從歷史倉庫中復制文件到索引,而不動工作目錄。
如果不給選項,那么當前分支指向到那個提交。如果用--hard
選項,那么工作目錄也更新,如果用--soft
選項,那么都不變。
14
如果沒有給出提交點的版本號,那么默認用HEAD。這樣,分支指向不變,但是索引會回滾到最后一次提交,如果用--hard
選項,工作目錄也同樣。(=== checkout )
15
如果給了文件名(或者 -p
選項), 那么工作效果和帶文件名的checkout差不多,除了索引被更新。
16
merge
merge 命令把不同分支合並起來。合並前,索引必須和當前提交相同。如果另一個分支是當前提交的祖父節點,那么合並命令將什么也不做。 另一種情況是如果當前提交是另一個分支的祖父節點,就導致fast-forward合並。指向只是簡單的移動,並生成一個新的提交
17
否則就是一次真正的合並。默認把當前提交(ed489 如下所示)和另一個提交(33104)以及他們的共同祖父節點(b325c)進行一次三方合並。
結果是先保存當前目錄和索引,然后和父節點33104一起做一次新提交。
18
cherry Pick
cherry-pick命令"復制"一個提交節點並在當前分支做一次完全一樣的新提交。
19
rebase
衍合是合並命令的另一種選擇。合並把兩個父分支合並進行一次提交,提交歷史不是線性的。衍合在當前分支上重演另一個分支的歷史,提交歷史是線性的。
本質上,這是線性化的自動的cherry-pick
20
上面的命令都在topic分支中進行,而不是master分支,在master分支上重演,並且把分支指向新的節點。注意舊提交沒有被引用,將被回收。
要限制回滾范圍,使用--onto
選項。下面的命令在master分支上重演當前分支從169a6以來的最近幾個提交,即2c33a。
git-fsck --lost-found (file system check) 找回丟失的對象
Verifies the connectivity and validity of the objects in the database
找回丟失的對象: http://gitbook.liuhui998.com/5_9.html
git reflog : 分支等引用變更記錄管理
Git reflog 機制 : http://geeklu.com/2013/04/git-reflog/
注意:
rebase的含義是把當前分支的提交對象在目標分支上重做一遍,並生成了新的提交對象。
所以如果在master分支上需要對test分支進行rebase,你需要的命令是
1
|
git rebase master your-branch
|
這條命令等價於兩條命令的合集
1
2
|
git checkout your-branch
git rebase master
|
示例分析
假設team中有兩個developer:tom和jerry,他們共同使用一個遠程repo,並各自clone到自己的機器上,為了簡化描述,這里假設只有一個branch:master
。
這時tom機器的repo有兩個branch master
origin/master
而jerry的機器上也是有兩個branch master
origin/master
均如下圖所示
tom和jerry分別各自開發自己的新feature,不斷有新的commit提交到他們各自私有的commit history中,所以他們的master指針不斷的向前推移,分別指向不同的commit。而又由於他們都沒有git fetch
和git push
,所以他們的origin/master
都維持不變。
jerry的repo如下
tom的repo如下,注意T1
和上圖的J1
,分別是兩個不同的commit
這時Tom首先把他的commit提交的遠程repo中,那么他本機origin/master
指針則會前進,和master
指針保持一致,如下
遠程repo如下
現在jerry也想把他的commit提交到遠程repo上去,運行git push
,毫無意外的失敗了,所以他git fetch
了一下,把遠程repo,也就是之前tom提交的T1
給拉到了他本機repo中,如下
commit history出現了分叉,要想把tom之前提交的內容包含到自己的工作中來,有一個方法就是git merge
,它會自動生成一個commit,既包含tom的提交,也包含jerry的提交,這樣就把兩個分叉的commit重新又合並在一起。但是這個自動生成的commit會有兩個parent,review代碼的時候必須要比較兩次,很不方便。
jerry為了保證commit history的線性,決定采用另外一種方法,就是git rebase
。jerry的提交J1
這時還沒有被提交到遠程repo上去,也就是他完全私有的一個commit,所以使用git rebase
改寫J1
的history完全沒有問題,改寫之后,如下
注意J1
被改寫到T1
后面了,變成了J1'
git push
后,本機repo
而遠程repo
異常的輕松,一條直線,沒有-f
所以,想維持樹的整潔,方法就是:在git push
之前,先git fetch
,再git rebase
。
git fetch origin master git rebase origin/master git push
HEAD | The current ref that you’re looking at. In most cases it’s probably refs/heads/master |
FETCH_HEAD | The SHAs of branch/remote heads that were updated during the last git fetch |
ORIG_HEAD | When doing a merge, this is the SHA of the branch you’re merging into |
MERGE_HEAD | When doing a merge, this is the SHA of the branch you’re merging from |
CHERRY_PICK_HEAD | When doing a cherry-pick, this is the SHA of the commit which you are cherry-picking |
參考:
[1] 【推薦】GIT & REPO & GERRIT : http://blog.csdn.net/edhroyal/article/details/9187727
[2] 【推薦】git-rebase : http://blog.yorkxin.org/posts/2011/07/29/git-rebase/
http://marklodato.github.io/visual-git-guide/index-zh-cn.html
轉自:http://www.cnblogs.com/angeldevil/p/3238470.html
Git與Repo入門
版本控制是什么已不用在說了,就是記錄我們對文件、目錄或工程等的修改歷史,方便查看更改歷史,備份以便恢復以前的版本,多人協作。。。
一、原始版本控制
最原始的版本控制是純手工的版本控制:修改文件,保存文件副本。有時候偷懶省事,保存副本時命名比較隨意,時間長了就不知道哪個是新的,哪個是老的了,即使知道新舊,可能也不知道每個版本是什么內容,相對上一版作了什么修改了,當幾個版本過去后,很可能就是下面的樣子了:
二、本地版本控制
手工管理比較麻煩且混亂,所以出現了本地版本控制系統,記錄文件每次的更新,可以對每個版本做一個快照,或是記錄補丁文件。比如RCS。
三、集中版本控制
但是本地版本控制系統偏向於個人使用,或者多個使用的人必須要使用相同的設備,如果需要多人協作就不好辦了,於是,集中化的版本控制系統( Centralized Version Control Systems,簡稱 CVCS )應運而生,比如Subversion,Perforce。
在CVCS中,所有的版本數據都保存在服務器上,一起工作的人從服務器上同步更新或上傳自己的修改。
但是,所有的版本數據都存在服務器上,用戶的本地設備就只有自己以前所同步的版本,如果不連網的話,用戶就看不到歷史版本,也無法切換版本驗證問題,或在不同分支工作。。
而且,所有數據都保存在單一的服務器上,有很大的風險這個服務器會損壞,這樣就會丟失所有的數據,當然可以定期備份。
四、分布式版本控制
針對CVCS的以上缺點,出現了分布式版本控制系統( Distributed Version Control System,簡稱 DVCS ),如GIT,Mercurial。
DVCS不是復制指定版本的快照,而是把所有的版本信息倉庫全部同步到本地,這樣就可以在本地查看所有版本歷史,可以離線在本地提交,只需在連網時push到相應的服務器或其他用戶那里。由於每個用戶那里保存的都是所有的版本數據,所以,只要有一個用戶的設備沒有問題就可以恢復所有的數據。
當然,這增加了本地存儲空間的占用。
GIT
必須要了解GIT的原理,才能知道每個操作的意義是什么,才能更容易地理解在什么情況下用什么操作,而不是死記命令。當然,第一步是要獲得一個GIT倉庫。
一、獲得GIT倉庫
有兩種獲得GIT倉庫的方法,一是在需要用GIT管理的項目的根目錄執行:
git init
執行后可以看到,僅僅在項目目錄多出了一個.git目錄,關於版本等的所有信息都在這個目錄里面。
另一種方式是克隆遠程目錄,由於是將遠程服務器上的倉庫完全鏡像一份至本地,而不是取某一個特定版本,所以用clone而不是checkout:
git clone <url>
二、GIT中版本的保存
記錄版本信息的方式主要有兩種:
- 記錄文件每個版本的快照
- 記錄文件每個版本之間的差異
GIT采用第一種方式。像Subversion和Perforce等版本控制系統都是記錄文件每個版本之間的差異,這就需要對比文件兩版本之間的具體差異,但是GIT不關心文件兩個版本之間的具體差別,而是關心文件的整體是否有改變,若文件被改變,在添加提交時就生成文件新版本的快照,而判斷文件整體是否改變的方法就是用SHA-1算法計算文件的校驗和。
GIT能正常工作完全信賴於這種SHA-1校驗和,當一個文件的某一個版本被記錄之后會生成這個版本的一個快照,但是一樣要能引用到這個快照,GIT中對快照的引用,對每個版本的記錄標識全是通過SHA-1校驗和來實現的。
當一個文件被改變時,它的校驗和一定會被改變(理論上存在兩個文件校驗和相同,但機率小到可以忽略不計),GIT就以此判斷文件是否被修改,及以些記錄不同版本。
在工作目錄的文件可以處於不同的狀態,比如說新添加了一個文件,GIT發覺了這個文件,但這個文件是否要納入GIT的版本控制還是要由我們自己決定,比如編譯生成的中間文件,我們肯定不想納入版本控制。下面就來看下文件狀態。
三、GIT文件操作
版本控制就是對文件的版本控制,對於Linux來說,設備,目錄等全是文件,要對文件進行修改、提交等操作,首先要知道文件當前在什么狀態,不然可能會提交了現在還不想提交的文件,或者要提交的文件沒提交上。
文件狀態
GIT倉庫所在的目錄稱為工作目錄,這個很好理解,我們的工程就在這里,工作時也是在這里做修改。
在工作目錄中的文件被分為兩種狀態,一種是已跟蹤狀態(tracked),另一種是未跟蹤狀態(untracked)。只有處於已跟蹤狀態的文件才被納入GIT的版本控制。如下圖:
當我們往工作目錄添加一個文件的時候,這個文件默認是未跟蹤狀態的,我們肯定不希望編譯生成的一大堆臨時文件默認被跟蹤還要我們每次手動將這些文件清除出去。用以下命令可以跟蹤文件:
git add <file>
上圖中右邊3個狀態都是已跟蹤狀態,其中的灰色箭頭只表示untracked<-->tracked的轉換而不是untracked<-->unmodified的轉換,新添加的文件肯定算是被修改過的。那么,staged狀態又是什么呢?這就要搞清楚GIT的三個工作區域:本地數據(倉庫)目錄,工作目錄,暫存區,如下圖所示:
git directory就是我們的本地倉庫.git目錄,里面保存了所有的版本信息等內容。
working driectory,工作目錄,就是我們的工作目錄,其中包括未跟蹤文件及已跟蹤文件,而已跟蹤文件都是從git directory取出來的文件的某一個版本或新跟蹤的文件。
staging area,暫存區,不對應一個具體目錄,其時只是git directory中的一個特殊文件。
當我們修改了一些文件后,要將其放入暫存區然后才能提交,每次提交時其實都是提交暫存區的文件到git倉庫,然后清除暫存區。而checkout某一版本時,這一版本的文件就從git倉庫取出來放到了我們的工作目錄。
文件狀態的查看
那么,我們怎么知道當前工作目錄的狀態呢?哪些文件已被暫存?有哪些未跟蹤的文件?哪些文件被修改了?所有這些只需要一個命令,git status,如下圖所示:
GIT在這一點做得很好,在輸出每個文件狀態的同時還說明了怎么操作,像上圖就有怎么暫存、怎么跟蹤文件、怎么取消暫存的說明。
文件暫存
在上圖中我們可以很清楚地看到,filea未跟蹤,fileb已被暫存(changes to be committed),但是怎么還有一個fileb是modified但unstaged呢?這是因為當我們暫存一從此文件時,暫存的是那一文件當時的版本,當暫存后再次修改了這個文件后就會提示這個文件暫存后的修改是未被暫存的。
接下來我們就看怎么暫存文件,其實也很簡單,從上圖中可以看到GIT已經提示我們了:use "git add <file>..." to update what will be committed,通過
git add <file>...
就可以暫存文件,跟蹤文件同樣是這一個命令。在這個命令中可以使用glob模式匹配,比如"file[ab]",也可以使用"git add ."添加當前目錄下的所有文件。
取消暫存文件是
git reset HEAD <file>...
若修改了一個文件想還原修改可用
git checkout -- <file>...
查看文件修改后的差異
當我們修改過一些文件之后,我們可能想查看我們都修改了什么東西,用"git status"只能查看對哪些文件做了改動,如果要看改動了什么,可以用:
git diff
比如下圖:
---a表示修改之前的文件,+++b表示修改后的文件,上圖表示在fileb的第一行后添加了一行"bb",原來文件的第一行擴展為了修改后的1、2行。
但是,前面我們明明用"git status"看到filesb做了一些修改后暫存了,然后又修改了fileb,理應有兩次修改的,怎么只有一個?
因為"git diff"顯示的是文件修改后還沒有暫存起來的內容,那如果要比較暫存區的文件與之前已經提交過的文件呢,畢竟實際提交的是暫存區的內容,可以用以下命令:
/dev/null表示之前沒有提交過這一個文件,這是將是第一次提交,用:
git diff --staged
是等效的,但GIT的版本要大於1.6.1。
再次執行"git add"將覆蓋暫存區的內容。
忽略一些文件
如果有一些部件我們不想納入版本控制,也不想在每次"git status"時看到這些文件的提示,或者很多時候我們為了方便會使用"git add ."添加所有修改的文件,這時就會添加上一些我們不想添加的文件,怎么忽略這些文件呢?
GIT當然提供了方法,只需在主目錄下建立".gitignore"文件,此文件有如下規則:
- 所有以#開頭的行會被忽略
- 可以使用glob模式匹配
- 匹配模式后跟反斜杠(/)表示要忽略的是目錄
- 如果不要忽略某模式的文件在模式前加"!"
比如:
# 此為注釋 – 將被 Git 忽略
*.a # 忽略所有 .a 結尾的文件
!lib.a # 但 lib.a 除外
/TODO # 僅僅忽略項目根目錄下的 TODO 文件,不包括 subdir/TODO
build/ # 忽略 build/ 目錄下的所有文件
doc/*.txt # 會忽略 doc/notes.txt 但不包括 doc/server/arch.txt
移除文件
當我們要刪除一個文件時,我們可能就直接用GUI刪除或者直接rm [file]了,但是看圖:
我們需要將文件添加到暫存區才能提交,而移除文件后是無法添加到暫存區的,那么怎么移除一個文件讓GIT不再將其納入版本控制呢?上圖中GIT已經給出了說明:
git rm <file>...
執行以上命令后提交就可以了,有時我們只是想將一些文件從版本控制中剔除出去,但仍保留這些文件在工作目錄中,比如我們一不小心將編譯生成的中間文件納入了版本控制,想將其從版本控制中剔除出去但在工作目錄中保留這些文件(不然再次編譯可要花費更多時間了),這時只需要添加"--cached"參數。
如果我們之前不是通過"git rm"刪除了很多文件呢?比如說通過patch或者通過GUI,如果這些文件命名沒有規則,一個一個地執行"git rm"會搞死人的,這時可以用以下命令:
移動文件
和移除文件一樣,移動文件不可以通過GUI直接重命令或用"mv"命令,而是要用"git mv",不然同移除文件一樣你會得到如下結果:
如果要重命名文件可以使用
git mv old_name new_name
這個命令等效於
mv old_name new_name
git rm old_name
git add new_name
交互式暫存
使用git add -i可以開啟交互式暫存,如圖所示,系統會列出一個功能菜單讓選擇將要執行的操作。
移除所有未跟蹤文件
git clean [options] 一般會加上參數-df,-d表示包含目錄,-f表示強制清除。
儲藏-Stashing
可能會遇到這樣的情況,你正在一個分支上進行一個特性的開發,或者一個Bug的修正,但是這時突然有其他的事情急需處理,這時該怎么辦?不可能就在這個工作進行到一半的分支上一起處理,先把修改的Copy出去?太麻煩了。這種情況下就要用到Stashing了。假如我們現在的工作目錄是這樣子的
$ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # modified: index.html # # Changed but not updated: # (use "git add <file>..." to update what will be committed) # # modified: lib/simplegit.rb
此時如果想切換分支就可以執行以下命令
$ git stash Saved working directory and index state \ "WIP on master: 049d078 added the index file" HEAD is now at 049d078 added the index file (To restore them type "git stash apply")
這時你會發現你的工作目錄變得很干凈了,就可以隨意切分支進行其他事情的處理了。
我們可能不只一次進行"git stash",通過以下命令可以查看所有stash列表
$ git stash list stash@{0}: WIP on master: 049d078 added the index file stash@{1}: WIP on master: c264051... Revert "added file_size"
當緊急事情處理完了,需要重新回來這里進行原來的工作時,只需把Stash區域的內容取出來應用到當前工作目錄就行,命令就是
git stash apply
如果不基參數就應用最新的stash,或者可以指定stash的名字,如:stash@{1},可能通過
git stash show
顯示stash的內容具體是什么,同git stash apply一樣,可以選擇指定stash的名字。
git stash apply之后再git stash list會發現,apply后的stash還在stash列表中,如果要將其從stash列表中刪除可以用
git stash drop
丟棄這個stash,stash的命令參數都可選擇指定stash名字,否則就是最新的stash。
一般情況下apply stash后應該就可以把它從stash列表刪除了,先apply再drop還是比較繁瑣的,使用以下一條命令就可以同時完成這兩個操作
git stash pop
如果我們執行git stash時工作目錄的狀態是部分文件已經加入了暫存區,部分文件沒有,當我們執行git stash apply之后會發現所有文件都變成了未暫存的,如果想維持原來的樣子操持原來暫存的文件仍然是暫存狀態,可以加上--index參數
git stash apply --index
還有這么一種情況,我們把原來的修改stash了,然后修復了其他一些東西並進行了提交,但是,這些提交的文件有些在之前已經被stash了,那么git stash apply時就很可能會遇到沖突,這種情況下就可以在stash時所以提交的基礎上新建一個分支,然后再apply stash,當然,這兩個步驟有一人簡單的完成方法
git stash branch <branch name>
四、提交與歷史
了解了文件的狀態,我們對文件進行了必要的修改后,就要把我們所做的修改放入版本庫了,這樣以后我們就可以在需要的時候恢復到現在的版本,而要恢復到某一版,一般需要查看版本的歷史。
提交
提交很簡單,直接執行"git commit"。執行git commit后會調用默認的或我們設置的編譯器要我們填寫提示說明,但是提交說明最好按GIT要求填寫:第一行填簡單說明,隔一行填寫詳細說明。因為第一行在一些情況下會被提取使用,比如查看簡短提交歷史或向別人提交補丁,所以字符數不應太多,40為好。下面看一下查看提交歷史。
查看提交歷史
查看提交歷史使用如下圖的命令
如圖所示,顯示了作者,作者郵箱,提交說明與提交時間,"git log"可以使用放多參數,比如:
僅顯示最新的1個log,用"-n"表示。
顯示簡單的SHA-1值與簡單提交說明,oneline僅顯示提交說明的第一行,所以第一行說明最好簡單點方便在一行顯示。
"git log --graph"以圖形化的方式顯示提交歷史的關系,這就可以方便地查看提交歷史的分支信息,當然是控制台用字符畫出來的圖形。
"git log"的更多參數可以查看命令幫助。
不經過暫存的提交
如果我們想跳過暫存區直接提交修改的文件,可以使用"-a"參數,但要慎重,別一不小心提交了不想提交的文件
git commit -a
如果需要快捷地填寫提交說明可使用"-m"參數
git commit -m 'commit message'
修訂提交
如果我們提交過后發現有個文件改錯了,或者只是想修改提交說明,這時可以對相應文件做出修改,將修改過的文件通過"git add"添加到暫存區,然后執行以下命令:
git commit --amend
然后修改提交說明覆蓋上次提交,但只能重寫最后一次提交。
重排提交
通過衍合(rebase)可以修改多個提交的說明,並可以重排提交歷史,拆分、合並提交,關於rebase在講到分支時再說,這里先看一下重排提交。
假設我們的提交歷史是這樣的:
如果我們想重排最后兩個提交的提交歷史,可以借助交互式rebase命令:
git rebase -i HEAD~2 或 git rebase -i 3366e1123010e7d67620ff86040a061ae76de0c8
HEAD~2表示倒數第三個提交,這條命令要指定要重排的最舊的提交的父提交,此處要重排Second commit與Third commit,所以要指定Initial commit的Commit ID。如圖所示:
注釋部分詳細說明了每個選項的作用,如果我們想交互這兩個提交,只需把開頭的這兩行交換下位置就OK了,交換位置后保存,然后看下提交歷史:
可以看到提交歷史已經變了,而且最新的兩個提交的Commit ID變了,如果這些提交已經push到了遠程服務器,就不要用這個命令了。
刪除提交與修改提交說明
如果要刪除某個提交,只需要刪除相應的行就可以了,而要修改某個提交的提交說明的話,只需要把相應行的pick改為reward。
合並提交-squashing
合並提交也很簡單,從注釋中的說明看,只需要把相應的行的pick改為squash就可以把這個提交全並到它上一行的提交中。
拆分提交
至於拆分提交,由於Git不可能知道你要做哪里把某一提交拆分開,把以我們就需要讓Git在需要拆分的提交處停下來,由我們手動修改提交,這時要把pick改為edit,這樣Git在處理到這個提交時會停下來,此時我們就可以進行相應的修改並多次提交來拆分提交。
撤銷提交
前面說了刪除提交的方法,但是如果是多人合作的話,如果某個提交已經Push到遠程倉庫,是不可以用那種方法刪除提交的,這時就要撤銷提交
git revert <commit-id>
這條命令會把指定的提交的所有修改回滾,並同時生成一個新的提交。
Reset
git reset會修改HEAD到指定的狀態,用法為
git reset [options] <commit>
這條命令會使HEAD提向指定的Commit,一般會用到3個參數,這3個參數會影響到工作區與暫存區中的修改:
- --soft: 只改變HEAD的State,不更改工作區與暫存區的內容
- --mixed(默認): 撤銷暫存區的修改,暫存區的修改會轉移到工作區
- --hard: 撤銷工作區與暫存區的修改
cherry-pick
當與別人和作開發時,會向別人貢獻代碼或者接收別人貢獻的代碼,有時候可能不想完全Merge別人貢獻的代碼,只想要其中的某一個提交,這時就可以使用cherry-pick了。就一個命令
git cherry-pick <commit-id>
filter-branch
這條命令可以修改整個歷史,如從所有歷史中刪除某個文件相關的信息,全局性地更換電子郵件地址。
五、GIT分支
分支被稱之為GIT最強大的特性,因為它非常地輕量級,如果用Perforce等工具應該知道,創建分支就是克隆原目錄的一個完整副本,對於大型工程來說,太費時費力了,而對於GIT來說,可以在瞬間生成一個新的分支,無論工程的規模有多大,因為GIT的分支其實就是一指針而已。在了解GIT分支之前,應該先了解GIT是如何存儲數據的。
前面說過,GIT存儲的不是文件各個版本的差異,而是文件的每一個版本存儲一個快照對象,然后通過SHA-1索引,不只是文件,包換每個提交都是一個對象並通過SHA-1索引。無論是文本文件,二進制文件還是提交,都是GIT對象。
GIT對象
每個對象(object) 包括三個部分:類型,大小和內容。大小就是指內容的大小,內容取決於對象的類型,有四種類型的對象:"blob"、"tree"、 "commit" 和"tag"。
- “blob”用來存儲文件數據,通常是一個文件。
- “tree”有點像一個目錄,它管理一些“tree”或是 “blob”(就像文件和子目錄)
- 一個“commit”指向一個"tree",它用來標記項目某一個特定時間點的狀態。它包括一些關於時間點的元數據,如提交時間、提交說明、作者、提交者、指向上次提交(commits)的指針等等。
- 一個“tag”是來標記某一個提交(commit) 的方法。
比如說我們執行了以下代碼進行了一次提交:
$ git add README test.rb LICENSE2
$ git commit -m 'initial commit of my project'
現在,Git 倉庫中有五個對象:三個表示文件快照內容的 blob 對象;一個記錄着目錄樹內容及其中各個文件對應 blob 對象索引的 tree 對象;以及一個包含指向 tree 對象(根目錄)的索引和其他提交信息元數據的 commit 對象。概念上來說,倉庫中的各個對象保存的數據和相互關系看起來如下圖:
如果進行多次提交,倉庫的歷史會像這樣:
分支引用
所謂的GIT分支,其實就是一個指向某一個Commit對象的指針,像下面這樣,有兩個分支,master與testing:
而我們怎么知道當前在哪一個分支呢?其實就是很簡單地使用了一個名叫HEAD的指針,如上圖所示。HEAD指針的值可以為一個SHA-1值或是一個引用,看以下例子:
git的所有版本信息都保存了Working Directory下的.git目錄,而HEAD指針就保存在.git目錄下,如上圖所有,目前為止已經有3個提交,通過查看HEAD的值可以看到我們當前在master分支:refs/heads/master,當我們通過git checkout取出某一特定提交后,HEAD的值就是成了我們checkout的提交的SHA-1值。
記錄我們當前的位置很簡單,就是能過HEAD指針,HEAD指向某一提交的SHA-1值或是某一分支的引用。
新建分支
git branch <branch-name>
有時需要在新建分支后直接切換到新建的分支,可以直接用checkout的-b選項
git checkout -b <branch-name>
刪除分支
git branch -d <branch-name>
如果在指定的分支有一些unmerged的提交,刪除分支會失敗,這里可以使用-D參數強制刪除分支。
git branch -D <branch-name>
檢出分支或提交
檢出某一分支或某一提交是同一個命令
git checkout <branch-name> | <commit>
分支合並(merge)
當我們新建一個分支進行開發,並提交了幾次更新后,感覺是時候將這個分支的內容合回主線了,這是就可以取出主線分支,然后把分支的更新merge回來:
git checkout master
git merge testing
如果master分支是testing分支的直接上游,即從master延着testing分支的提交歷史往前走可以直接走到testing分支的最新提交,那么系統什么也不需要做,只需要改變master分支的指針即可,這被稱之為"Fast Forward"。
但是,一般情況是這樣的,你取出了最新的master分支,比如說master分支最新的提交是C2(假設共3次提交C0<-C1<-C2),在此基礎上你新建了分支,當你在分支上提交了C3、C5后想將br1時merge回master時,你發現已經有其他人提交了C4,這時候就不能直接修改master的指針了,不然會丟失別人的提交,這個時候就需要將你新建分支時master所在的提交(C2)后的修改(C4),與你新建分支后在分支上的修改(C3、C5)做合並,將合並后的結果作為一個新的提交提交到master,GIT可以自動推導出應該基於哪個提交進行合並(C2),如果沒有沖突,系統會自動提交新的提交,如果有沖突,系統會提示你解決沖突,當沖突解決后,你就可以將修改加入暫存區並提交。提交歷史類似下面這樣(圖來自Pro-Git):
merge后的提交是按時間排序的,比如下圖,我們在rename提交處新建分支test,在test上提交Commit from branch test,然后回到master提交commit in master after committing in branch,再將test分支merge進master,這時看提交提交歷史,Commit from branch test是在commit in master...之前的,盡管在master上我們是在rename的基礎上提交的commit in master...而GIT會在最后添加一個新的提交(Merge branch 'test')表示我們在此處將一個分支merge進來了。這種情況會有一個問題,比如說在rename提交處某人A從你這里Copy了一個GIT倉庫,然后你release了一個patch(通過git format-patch)給A,這時候test分支還沒有merge進來,所以patch中只包含提交:commit in master...然后你把test分支merge了進來又給了A一個patch,這個patch會包含提交:Commit from branch test,而這個patch是以rename為base的,如果commit in master...和Commit from branch test修改了相同的文件,則第二次的patch可能會打不上去,因為以rename為base的patch可能在新的Code上找不到在哪個位置應用修改。
分支衍合(rebase)
有兩種方法將一個分支的改動合並進另一個分支,一個就是前面所說的分支合並,另一個就是分支衍合,這兩種方式有什么區別呢?
分支合並(merge)是將兩個分支的改動合並到一起,並生成一個新的提交,提交歷史是按時間排序的,即我們實際提交的順序,通過git log --graph或一些圖形化工具,可能很明顯地看到分支的合並歷史,如果分支比較多就很混亂,而且如果以功能點新建分支,等功能點完成后合回主線,由於merge后提交是按提交時間排序的,提交歷史就比較亂,各個功能點的提交混雜在一起,還可能遇到上面提到的patch問題。
而分支衍合(rebase)是找到兩個分支的共同祖先提交,將要被rebase進來的分支的提交依次在要被rebase到的分支上重演一遍,即回到兩個分支的共同祖先,將branch(假如叫experiment)的每次提交的差異保存到臨時文件里,然后切換到要衍合入的分支(假如是master),依次應用補丁文件。experiment上有幾次提交,在master就生成幾次新的提交,而且是連在一起的,這樣合進主線后每個功能點的提交就都在一起,而且提交歷史是線性的
對比merge與rebase的提交歷史會是下圖這樣的(圖來自Pro-GIt):
(merge)
(rebase)
rebase后C3提交就不存在了,取而代之的是C3',而master也成為了experiment的直接上游,只需一次Fast Forward(git merge)后master就指向了最新的提交,就可以刪除experiment分支了。
衍合--onto
git rebase --onto master server client
這條命令的意思是:檢出server分支與client分支共同祖先之后client上的變化,然后在master上重演一遍。
父提交
HEAD表示當前所在的提交,如果要查看當前提交父提交呢?git log查看提交歷史,顯然太麻煩了,而且輸入一長串的Commit-ID也不是一個令人愉悅的事。這時可借助兩個特殊的符號:~與^。
^ 表示指定提交的父提交,這個提交可能由多個交提交,^之后跟上數字表示第幾個父提交,不跟數字等同於^1。
~n相當於n個^,比如~3=^^^,表示第一個父提交的第一個父提交的第一個父提交。
遠程分支
遠程分支以(遠程倉庫名)/(分支名)命令,遠程分支在本地無法移動修改,當我們clone一個遠程倉庫時會自動在本地生成一個名叫original的遠程倉庫,下載遠程倉庫的所有數據,並新建一個指向它的分支original/master,但這個分支我們是無法修改的,所以需要在本地重新一個分支,比如叫master,並跟蹤遠程分支。
Clone了遠程倉庫后,我們還會在本地新建其他分支,並且可能也想跟蹤遠程分支,這時可以用以下命令:
git checkout -b [branch_name] --track|-t <remote>/<remote-banch>
和新建分支的方法一樣,只是加了一個參數--track或其縮寫形式-t,可以指定本地分支的名字,如果不指定就會被命名為remote-branch。
要拉取某個遠程倉庫的數據,可以用git fetch:
git fetch <remote>
當拉取到了遠程倉庫的數據后只是把數據保存到了一個遠程分支中,如original/master,而這個分支的數據是無法修改的,此時我們可以把這個遠程分支的數據合並到我們當前分支
git merge <remote>/<remote-branch>
如果當前分支已經跟蹤了遠程分支,那么上述兩個部分就可以合並為一個
git pull
當在本地修改提交后,我們可能需要把這些本地的提交推送到遠程倉庫,這里就可以用git push命令,由於本地可以由多個遠程倉庫,所以需要指定遠程倉庫的名字,並同時指定需要推的本地分支及需要推送到遠程倉庫的哪一個分支
git push <remote> <local-branch>:<remote-branch>
如果本地分支與遠程分支同名,命令可以更簡單
git push <remote> <branch-name> 等價於 git push <remote> refs/heads/<branch-name>:refs/for/<branch-name>
如果本地分支的名字為空,可以刪除遠程分支。
前面說過可以有不止一個遠程分支f,添加遠程分支的方法為
git remote add <short-name> <url>
六、標簽-tag
作為一個版本控制工具,針對某一時間點的某一版本打tag的功能是必不可少的,要查看tag也非常簡單,查看tag使用如下命令
git tag
參數"-l"可以對tag進行過濾
git tag -l "v1.1.*"
Git 使用的標簽有兩種類型:輕量級的(lightweight)和含附注的(annotated)。輕量級標簽就像是個不會變化的分支,實際上它就是個指向特定提交對象的引用。而含附注標簽,實際上是存儲在倉庫中的一個獨立對象,它有自身的校驗和信息,包含着標簽的名字,電子郵件地址和日期,以及標簽說明,標簽本身也允許使用 GNU Privacy Guard (GPG) 來簽署或驗證。
輕量級標簽只需在git tag后加上tag的名字,如果tag名字
git tag <tag_name>
含附注的標簽需要加上參數-a(annotated),同時加上-m跟上標簽的說明
git tag -a <tag_name> -m "<tag_description>"
如果你有自己的私鑰,還可以用 GPG 來簽署標簽,只需要把之前的 -a
改為 -s(signed)
查看標簽的內容用
git show <tag_name>
驗證已簽署的標簽用-v(verify)
git tag -v <tag_name>
有時在某一個版本忘記打tag了,可以在后期再補上,只需在打tag時加上commit-id
要將tag推送到遠程服務器上,可以用
git push <remote> <tag_name>
或者可以用下面的命令推送所有的tag
git push <remote> --tags
七、Git配置
使用"git config"可以配置Git的環境變量,這些變量可以存放在以下三個不同的地方:
/etc/gitconfig
文件:系統中對所有用戶都普遍適用的配置。若使用git config
時用--system
選項,讀寫的就是這個文件。~/.gitconfig
文件:用戶目錄下的配置文件只適用於該用戶。若使用git config
時用--global
選項,讀寫的就是這個文件。- 當前項目的 git 目錄中的配置文件(也就是工作目錄中的
.git/config
文件):這里的配置僅僅針對當前項目有效。每一個級別的配置都會覆蓋上層的相同配置,所以.git/config
里的配置會覆蓋/etc/gitconfig
中的同名變量。
在 Windows 系統上,Git 會找尋用戶主目錄下的 .gitconfig
文件。主目錄即 $HOME
變量指定的目錄,一般都是 C:\Documents and Settings\$USER
。此外,Git 還會嘗試找尋 /etc/gitconfig
文件,只不過看當初 Git 裝在什么目錄,就以此作為根目錄來定位。
最基礎的配置是配置git的用戶,用來標識作者的身份
git config --global user.name <name>
git config --global user.email <email>
文本編輯器也可以配置,比如在git commit的時候就會調用我們設置的文本編輯器
git config --global core.editor vim
另一個常用的是diff工具,比如我們想用可視化的對比工具
git config --global merge.tool meld
要查看所有的配置,可以用
git config --list
或者可以在git config后加上配置項的名字查看具體項的配置
git config user.name
作為一個懶人,雖然checkout、status等命令只是一個單詞,但是還是嫌太長了,我們還可以給命令設置別名如
git config --global alias.co checkout
這樣git co就等於git checkout
前面說地,git配置項都保存在那3個文件里,可以直接打開相應的配置文件查看配置,也可以直接修改這些配置文件來配置git,想刪除某一個配置,直接刪除相應的行就行了
八、其他
關於GIT各命令的說明可以查看相關幫助文檔,通過以下方法:
git help <command>或git <command> --help
REPO
repo start <topic_name>
開啟一個新的主題,其實就是每個Project都新建一個分支。
repo init -u <url> [OPTIONS]
在當前目錄下初始化repo,會在當前目錄生生成一個.repo目錄,像Git Project下的.git一樣,-u指定url,可以加參數-m指定manifest文件,默認是default.xml,.repo/manifests保存manifest文件。.repo/projects下有所有的project的數據信息,repo是一系列git project的集合,每個git project下的.git目錄中的refs等目錄都是鏈接到.repo/manifests下的。
repo manifest
可以根據當前各Project的版本信息生成一個manifest文件
repo sync [PROJECT1...PROJECTN]
同步Code。
repo status
查看本地所有Project的修改,在每個修改的文件前有兩個字符,第一個字符表示暫存區的狀態。
- | no change | same in HEAD and index |
A | added | not in HEAD, in index |
M | modified | in HEAD, modified in index |
D | deleted | in HEAD, not in index |
R | renamed | not in HEAD, path changed in index |
C | copied | not in HEAD, copied from another in index |
T | mode changed | same content in HEAD and index, mode changed |
U | unmerged | conflict between HEAD and index; resolution required |
每二個字符表示工作區的狀態
letter | meaning | description |
---|---|---|
- | new/unknown | not in index, in work tree |
m | modified | in index, in work tree, modified |
d | deleted | in index, not in work tree |
repo prune <topic>
刪除已經merge的分支
repo abandon <topic>
刪除分支,無論是否merged
repo branch或repo branches
查看所有分支
repo diff
查看修改
repo upload
上傳本地提交至服務器
repo forall [PROJECT_LIST]-c COMMAND
對指定的Project列表或所有Project執行命令COMMAND,加上-p參數可打印出Project的路徑。
repo forall -c 'git reset --hard HEAD;git clean -df;git rebase --abort'
這個命令可以撤銷整個工程的本地修改。
說明:文中關於Git的知識大多來自Pro-GIt,這本書感覺不錯,想學習的可以找來看:Pro-Git
二、Git 分支圖解
轉自:http://kb.cnblogs.com/page/132209/
英文原文:http://www.nvie.com/posts/a-successful-git-branching-model/
原文作者:Vincent Driessen
本文經Linux大棚博主總結精簡而成。
1
GIT,在技術層面上,絕對是一個無中心的分布式版本控制系統,但在管理層面上,我建議你保持一個中心版本庫。
2
我建議,一個中心版本庫(我們叫它origin)至少包括兩個分支,即“主分支(master)”和“開發分支(develop)”
3
要確保:團隊成員從主分支(master)獲得的都是處於可發布狀態的代碼,而從開發分支(develop)應該總能夠獲得最新開發進展的代碼。
4
在一個團隊開發協作中,我建議,要有“輔助分支”的概念。
5
“輔助分支”,大體包括如下幾類:“管理功能開發”的分支、“幫助構建可發布代碼”的分支、“可以便捷的修復發布版本關鍵BUG”的分支,等等。
6
“輔助分支”的最大特點就是“生命周期十分有限”,完成使命后即可被清除。
7
我建議至少還應設置三類“輔助分支”,我們稱之為“Feature branches”,“Release branches”,“Hotfix branches”。
至此,我們形成了如下這張最重要的組織組,包含了兩個粗體字分支(master/develop)和三個細體字分支(feature/release/hotfixes)。
8
“Feature branches”,起源於develop分支,最終也會歸於develop分支。
9
“Feature branches”常用於開發一個獨立的新功能,且其最終的結局必然只有兩個,其一是合並入“develop”分支,其二是被拋棄。最典型的“Fearture branches”一定是存在於團隊開發者那里,而不應該是“中心版本庫”中。
10
“Feature branches”起源於“develop”分支,實現方法是:
git checkout -b myfeature develop
11
“Feature branches”最終也歸於“develop”分支,實現方式是:
git checkout devleopgit merge --no-ff myfeature(--no-ff,即not fast forward,其作用是:要求git merge即使在fast forward條件下也要產生一個新的merge commit)(此處,要求采用--no-ff的方式進行分支合並,其目的在於,希望保持原有“Feature branches”整個提交鏈的完整性)git branch -d myfeaturegit push origin develop
12
“Release branch”,起源於develop分支,最終歸於“develop”或“master”分支。這類分支建議命名為“release-*”
13
“Relase branch”通常負責“短期的發布前准備工作”、“小bug的修復工作”、“版本號等元信息的准備工作”。與此同時,“develop”分支又可以承接下一個新功能的開發工作了。
14
“Release branch”產生新提交的最好時機是“develop”分支已經基本到達預期的狀態,至少希望新功能已經完全從“Feature branches”合並到“develop”分支了。
15
創建“Release branches”,方法是:
git checkout -b release-1.2 develop./bump-version.sh 1.2 (這個腳本用於將代碼所有涉及版本信息的地方都統一修改到1.2,另外,需要用戶根據自己的項目去編寫適合的bump-version.sh)git commit -a -m "Bumped version number to 1.2"
16
在一段短時間內,在“Release branches”上,我們可以繼續修復bug。在此階段,嚴禁新功能的並入,新功能應該是被合並到“develop”分支的。
17
經過若干bug修復后,“Release branches”上的代碼已經達到可發布狀態,此時,需要完成三個動作:第一是將“Release branches”合並到“master”分支,第二是一定要為master上的這個新提交打TAG(記錄里程碑),第三是要將“Release branches”合並回“develop”分支。
git checkout mastergit merge --no-ff release-1.2git tag -a 1.2 (使用-u/-s/-a參數會創建tag對象,而非軟tag)git checkout developgit merge --no-ff release-1.2git branch -d release-1.2
18
“Hotfix branches”源於“master”,歸於“develop”或“master”,通常命名為“hotfix-*”
19
“Hotfix branches”類似於“Release branch”,但產生此分支總是非預期的關鍵BUG。
20
建議設立“Hotfix branches”的原因是:希望避免“develop分支”新功能的開發必須為BUG修復讓路的情況。
21
建立“Hotfix branches”,方法是:
git checkout -b hotfix-1.2.1 master./bump-version.sh 1.2.1git commit -a -m "Bumpt version to 1.2.1" (然后可以開始問題修復工作)git commit -m "Fixed severe production problem" (在問題修復后,進行第二次提交)
22
BUG修復后,需要將“Hotfix branches”合並回“master”分支,同時也需要合並回“develop”分支,方法是:
git checkout mastergit merge --no-ff hotfix-1.2.1git tag -a 1.2.1git checkout developgit merge --no-ff hotfix-1.2.1git branch -d hotfix-1.2.1
23
還記得文章開始時的那張大圖么,我建議你把這幅大圖從這里下載下來,打印出來,貼在你寫字台的牆壁上,好處不言而喻。
三、Git 全面教程
(廖雪峰的官方網站):http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
博客園:http://www.cnblogs.com/BeginMan/category/551237.html
csdn博客:http://blog.csdn.net/JackJia2015 http://blog.csdn.net/JackJia2015/article/details/51178868
csdn博客:http://blog.csdn.net/wh_19910525/article/category/1113974
四、Git 常用命令
轉自:http://www.cnblogs.com/1-2-3/archive/2010/07/18/git-commands.html
Git 是一個很強大的分布式版本控制系統。它不但適用於管理大型開源軟件的源代碼,管理私人的文檔和源代碼也有很多優勢。
本來想着只把最有用、最常用的 Git 命令記下來,但是總覺得這個也挺有用、那個也用得着,結果越記越多。
master分支默認是公共分支,當有多人協同時,master分支在多個地方都有副本。
如果在master分支上執行git rebase test,會把master分支的提交歷史進行修改,可以使用git log仔細觀察rebase前后,master分支上的commit hash id。
一旦修改了master的commit hash id,而如果其他人已經基於之前的commit對象做了工作,那么當他拉取master的新的對象時,會需要再合並一次,這樣反復下去,會把