天天看點

Git與Repo入門 版本控制 GIT  REPO

原文:http://www.open-open.com/lib/view/open1405048177091.html

版本控制

        版本控制是什麼已不用在說了,就是記錄我們對檔案、目錄或工程等的修改曆史,友善檢視更改曆史,備份以便恢複以前的版本,多人協作。。。

一、原始版本控制

        最原始的版本控制是純手工的版本控制:修改檔案,儲存檔案副本。有時候偷懶省事,儲存副本時命名比較随意,時間長了就不知道哪個是新的,哪個是 老的了,即使知道新舊,可能也不知道每個版本是什麼内容,相對上一版作了什麼修改了,當幾個版本過去後,很可能就是下面的樣子了:

Git與Repo入門 版本控制 GIT  REPO

二、本地版本控制

        手工管理比較麻煩且混亂,是以出現了本地版本控制系統,記錄檔案每次的更新,可以對每個版本做一個快照,或是記錄更新檔檔案。比如RCS。

Git與Repo入門 版本控制 GIT  REPO

三、集中版本控制

        但是本地版本控制系統偏向于個人使用,或者多個使用的人必須要使用相同的裝置,如果需要多人協作就不好辦了,于是,集中化的版本控制系統( Centralized Version Control Systems,簡稱 CVCS )應運而生,比如Subversion,Perforce。

        在CVCS中,所有的版本資料都儲存在伺服器上,一起工作的人從伺服器上同步更新或上傳自己的修改。

Git與Repo入門 版本控制 GIT  REPO

        但是,所有的版本資料都存在伺服器上,使用者的本地裝置就隻有自己以前所同步的版本,如果不連網的話,使用者就看不到曆史版本,也無法切換版本驗證問題,或在不同分支工作。。

        而且,所有資料都儲存在單一的伺服器上,有很大的風險這個伺服器會損壞,這樣就會丢失所有的資料,當然可以定期備份。

四、分布式版本控制

        針對CVCS的以上缺點,出現了分布式版本控制系統( Distributed Version Control System,簡稱 DVCS ),如GIT,Mercurial。

        DVCS不是複制指定版本的快照,而是把所有的版本資訊倉庫全部同步到本地,這樣就可以在本地檢視所有版本曆史,可以離線在本地送出,隻需在連 網時push到相應的伺服器或其他使用者那裡。由于每個使用者那裡儲存的都是所有的版本資料,是以,隻要有一個使用者的裝置沒有問題就可以恢複所有的資料。

        當然,這增加了本地存儲空間的占用。

Git與Repo入門 版本控制 GIT  REPO

GIT

        必須要了解GIT的原理,才能知道每個操作的意義是什麼,才能更容易地了解在什麼情況下用什麼操作,而不是死記指令。當然,第一步是要獲得一個GIT倉庫。

一、獲得GIT倉庫

        有兩種獲得GIT倉庫的方法,一是在需要用GIT管理的項目的根目錄執行:

git init

        執行後可以看到,僅僅在項目目錄多出了一個.git目錄,關于版本等的所有資訊都在這個目錄裡面。

        另一種方式是克隆遠端目錄,由于是将遠端伺服器上的倉庫完全鏡像一份至本地,而不是取某一個特定版本,是以用clone而不是checkout:

git clone

二、GIT中版本的儲存

        記錄版本資訊的方式主要有兩種:

  1. 記錄檔案每個版本的快照
  2. 記錄檔案每個版本之間的差異

        GIT采用第一種方式。像Subversion和Perforce等版本控制系統都是記錄檔案每個版本之間的差異,這就需要對比檔案兩版本之間 的具體差異,但是GIT不關心檔案兩個版本之間的具體差别,而是關心檔案的整體是否有改變,若檔案被改變,在添加送出時就生成檔案新版本的快照,而判斷文 件整體是否改變的方法就是用SHA-1算法計算檔案的校驗和。

        GIT能正常工作完全信賴于這種SHA-1校驗和,當一個檔案的某一個版本被記錄之後會生成這個版本的一個快照,但是一樣要能引用到這個快照,GIT中對快照的引用,對每個版本的記錄辨別全是通過SHA-1校驗和來實作的。

        當一個檔案被改變時,它的校驗和一定會被改變(理論上存在兩個檔案校驗和相同,但機率小到可以忽略不計),GIT就以此判斷檔案是否被修改,及以些記錄不同版本。

        在工作目錄的檔案可以處于不同的狀态,比如說新添加了一個檔案,GIT發覺了這個檔案,但這個檔案是否要納入GIT的版本控制還是要由我們自己決定,比如編譯生成的中間檔案,我們肯定不想納入版本控制。下面就來看下檔案狀态。

三、GIT檔案操作

        版本控制就是對檔案的版本控制,對于Linux來說,裝置,目錄等全是檔案,要對檔案進行修改、送出等操作,首先要知道檔案目前在什麼狀态,不然可能會送出了現在還不想送出的檔案,或者要送出的檔案沒送出上。

檔案狀态

        GIT倉庫所在的目錄稱為工作目錄,這個很好了解,我們的工程就在這裡,工作時也是在這裡做修改。

        在工作目錄中的檔案被分為兩種狀态,一種是已跟蹤狀态(tracked),另一種是未跟蹤狀态(untracked)。隻有處于已跟蹤狀态的檔案才被納入GIT的版本控制。如下圖:

Git與Repo入門 版本控制 GIT  REPO

        當我們往工作目錄添加一個檔案的時候,這個檔案預設是未跟蹤狀态的,我們肯定不希望編譯生成的一大堆臨時檔案預設被跟蹤還要我們每次手動将這些檔案清除出去。用以下指令可以跟蹤檔案:

git add

        上圖中右邊3個狀态都是已跟蹤狀态,其中的灰色箭頭隻表示untracked<-->tracked的轉換而不是 untracked<-->unmodified的轉換,新添加的檔案肯定算是被修改過的。那麼,staged狀态又是什麼呢?這就要搞清楚 GIT的三個工作區域:本地資料(倉庫)目錄,工作目錄,暫存區,如下圖所示:

Git與Repo入門 版本控制 GIT  REPO

        git directory就是我們的本地倉庫.git目錄,裡面儲存了所有的版本資訊等内容。

        working driectory,工作目錄,就是我們的工作目錄,其中包括未跟蹤檔案及已跟蹤檔案,而已跟蹤檔案都是從git directory取出來的檔案的某一個版本或新跟蹤的檔案。

        staging area,暫存區,不對應一個具體目錄,其時隻是git directory中的一個特殊檔案。

        當我們修改了一些檔案後,要将其放入暫存區然後才能送出,每次送出時其實都是送出暫存區的檔案到git倉庫,然後清除暫存區。而checkout某一版本時,這一版本的檔案就從git倉庫取出來放到了我們的工作目錄。

檔案狀态的檢視

        那麼,我們怎麼知道目前工作目錄的狀态呢?哪些檔案已被暫存?有哪些未跟蹤的檔案?哪些檔案被修改了?所有這些隻需要一個指令,git status,如下圖所示:

Git與Repo入門 版本控制 GIT  REPO

        GIT在這一點做得很好,在輸出每個檔案狀态的同時還說明了怎麼操作,像上圖就有怎麼暫存、怎麼跟蹤檔案、怎麼取消暫存的說明。

檔案暫存

        在上圖中我們可以很清楚地看到,filea未跟蹤,fileb已被暫存(changes to be committed),但是怎麼還有一個fileb是modified但unstaged呢?這是因為當我們暫存一從此檔案時,暫存的是那一檔案當時的版 本,當暫存後再次修改了這個檔案後就會提示這個檔案暫存後的修改是未被暫存的。

        接下來我們就看怎麼暫存檔案,其實也很簡單,從上圖中可以看到GIT已經提示我們了:use "git add ..." to update what will be committed,通過 

git add ...

        就可以暫存檔案,跟蹤檔案同樣是這一個指令。在這個指令中可以使用glob模式比對,比如"file[ab]",也可以使用"git add ."添加目前目錄下的所有檔案。

        取消暫存檔案是 

git reset HEAD ...

         若修改了一個檔案想還原修改可用

git checkout -- ... 

檢視檔案修改後的差異

        當我們修改過一些檔案之後,我們可能想檢視我們都修改了什麼東西,用"git status"隻能檢視對哪些檔案做了改動,如果要看改動了什麼,可以用:

git diff

        比如下圖:

Git與Repo入門 版本控制 GIT  REPO

        ---a表示修改之前的檔案,+++b表示修改後的檔案,上圖表示在fileb的第一行後添加了一行"bb",原來檔案的第一行擴充為了修改後的1、2行。

        但是,前面我們明明用"git status"看到filesb做了一些修改後暫存了,然後又修改了fileb,理應有兩次修改的,怎麼隻有一個?

        因為"git diff"顯示的是檔案修改後還沒有暫存起來的内容,那如果要比較暫存區的檔案與之前已經送出過的檔案呢,畢竟實際送出的是暫存區的内容,可以用以下指令:

Git與Repo入門 版本控制 GIT  REPO

        /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與Repo入門 版本控制 GIT  REPO

        我們需要将檔案添加到暫存區才能送出,而移除檔案後是無法添加到暫存區的,那麼怎麼移除一個檔案讓GIT不再将其納入版本控制呢?上圖中GIT已經給出了說明:

git rm ...

        執行以上指令後送出就可以了,有時我們隻是想将一些檔案從版本控制中剔除出去,但仍保留這些檔案在工作目錄中,比如我們一不小心将編譯生成的中 間檔案納入了版本控制,想将其從版本控制中剔除出去但在工作目錄中保留這些檔案(不然再次編譯可要花費更多時間了),這時隻需要添加"--cached" 參數。

        如果我們之前不是通過"git rm"删除了很多檔案呢?比如說通過patch或者通過GUI,如果這些檔案命名沒有規則,一個一個地執行"git rm"會搞死人的,這時可以用以下指令:

Git與Repo入門 版本控制 GIT  REPO

移動檔案

        和移除檔案一樣,移動檔案不可以通過GUI直接重指令或用"mv"指令,而是要用"git mv",不然同移除檔案一樣你會得到如下結果:

Git與Repo入門 版本控制 GIT  REPO

        如果要重命名檔案可以使用

git mv old_name new_name

        這個指令等效于

mv old_name new_name

git rm old_name

git add new_name

互動式暫存

        使用git add -i可以開啟互動式暫存,如圖所示,系統會列出一個功能菜單讓選擇将要執行的操作。

Git與Repo入門 版本控制 GIT  REPO

移除所有未跟蹤檔案

git clean [options]  一般會加上參數-df,-d表示包含目錄,-f表示強制清除。

儲藏-Stashing

        可能會遇到這樣的情況,你正在一個分支上進行一個特性的開發,或者一個Bug的修正,但是這時突然有其他的事情急需處理,這時該怎麼辦?不可能 就在這個工作進行到一半的分支上一起處理,先把修改的Copy出去?太麻煩了。這種情況下就要用到Stashing了。假如我們現在的工作目錄是這樣子的

$ git status
# On branch master
# Changes to be committed:
#
(use "git reset HEAD ..." to unstage)
#
#
modified:
index.html
#
# Changed but not updated:
#
(use "git add ..." to update what will be committed)
#
#
modified:
lib/simplegit.rb      

        此時如果想切換分支就可以執行以下指令

?

1 2

$ 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清單

?

1 2

$ git stash list

[email protected]{0}: WIP on master: 049d078 added the index 

file

[email protected]{1}: WIP on master: c264051... Revert 

"added file_size"

        當緊急事情處理完了,需要重新回來這裡進行原來的工作時,隻需把Stash區域的内容取出來應用到目前工作目錄就行,指令就是

git stash apply      

        如果不基參數就應用最新的stash,或者可以指定stash的名字,如:[email protected]{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       

四、送出與曆史

        了解了檔案的狀态,我們對檔案進行了必要的修改後,就要把我們所做的修改放入版本庫了,這樣以後我們就可以在需要的時候恢複到現在的版本,而要恢複到某一版,一般需要檢視版本的曆史。

送出

        送出很簡單,直接執行"git commit"。執行git commit後會調用預設的或我們設定的編譯器要我們填寫提示說明,但是送出說明最好按GIT要求填寫:第一行填簡單說明,隔一行填寫詳細說明。因為第一 行在一些情況下會被提取使用,比如檢視簡短送出曆史或向别人送出更新檔,是以字元數不應太多,40為好。下面看一下檢視送出曆史。

檢視送出曆史

        檢視送出曆史使用如下圖的指令

Git與Repo入門 版本控制 GIT  REPO

        如圖所示,顯示了作者,作者郵箱,送出說明與送出時間,"git log"可以使用放多參數,比如:

Git與Repo入門 版本控制 GIT  REPO

        僅顯示最新的1個log,用"-n"表示。

Git與Repo入門 版本控制 GIT  REPO

        顯示簡單的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在講到分支時再說,這裡先看一下重排送出。

        假設我們的送出曆史是這樣的:

Git與Repo入門 版本控制 GIT  REPO

        如果我們想重排最後兩個送出的送出曆史,可以借助互動式rebase指令: 

 git rebase -i HEAD~2 或 git rebase -i 3366e1123010e7d67620ff86040a061ae76de0c8

        HEAD~2表示倒數第三個送出,這條指令要指定要重排的最舊的送出的父送出,此處要重排Second commit與Third commit,是以要指定Initial commit的Commit ID。如圖所示:

Git與Repo入門 版本控制 GIT  REPO

        注釋部分詳細說明了每個選項的作用,如果我們想互動這兩個送出,隻需把開頭的這兩行交換下位置就OK了,交換位置後儲存,然後看下送出曆史:

Git與Repo入門 版本控制 GIT  REPO

        可以看到送出曆史已經變了,而且最新的兩個送出的Commit ID變了,如果這些送出已經push到了遠端伺服器,就不要用這個指令了。

删除送出與修改送出說明

        如果要删除某個送出,隻需要删除相應的行就可以了,而要修改某個送出的送出說明的話,隻需要把相應行的pick改為reward。

合并送出-squashing

        合并送出也很簡單,從注釋中的說明看,隻需要把相應的行的pick改為squash就可以把這個送出全并到它上一行的送出中。

拆分送出

        至于拆分送出,由于Git不可能知道你要做哪裡把某一送出拆分開,把以我們就需要讓Git在需要拆分的送出處停下來,由我們手動修改送出,這時要把pick改為edit,這樣Git在處理到這個送出時會停下來,此時我們就可以進行相應的修改并多次送出來拆分送出。

撤銷送出

        前面說了删除送出的方法,但是如果是多人合作的話,如果某個送出已經Push到遠端倉庫,是不可以用那種方法删除送出的,這時就要撤銷送出

git revert

        這條指令會把指定的送出的所有修改復原,并同時生成一個新的送出。

Reset

        git reset會修改HEAD到指定的狀态,用法為

git reset [options]

        這條指令會使HEAD提向指定的Commit,一般會用到3個參數,這3個參數會影響到工作區與暫存區中的修改:

  • --soft: 隻改變HEAD的State,不更改工作區與暫存區的内容
  • --mixed(預設): 撤銷暫存區的修改,暫存區的修改會轉移到工作區
  • --hard: 撤銷工作區與暫存區的修改

cherry-pick

        當與别人和作開發時,會向别人貢獻代碼或者接收别人貢獻的代碼,有時候可能不想完全Merge别人貢獻的代碼,隻想要其中的某一個送出,這時就可以使用cherry-pick了。就一個指令

 git cherry-pick

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與Repo入門 版本控制 GIT  REPO

        如果進行多次送出,倉庫的曆史會像這樣:

Git與Repo入門 版本控制 GIT  REPO

分支引用

        所謂的GIT分支,其實就是一個指向某一個Commit對象的指針,像下面這樣,有兩個分支,master與testing:

Git與Repo入門 版本控制 GIT  REPO

        而我們怎麼知道目前在哪一個分支呢?其實就是很簡單地使用了一個名叫HEAD的指針,如上圖所示。HEAD指針的值可以為一個SHA-1值或是一個引用,看以下例子:

Git與Repo入門 版本控制 GIT  REPO

        git的所有版本資訊都儲存了Working Directory下的.git目錄,而HEAD指針就儲存在.git目錄下,如上圖所有,目前為止已經有3個送出,通過檢視HEAD的值可以看到我們當 前在master分支:refs/heads/master,當我們通過git checkout取出某一特定送出後,HEAD的值就是成了我們checkout的送出的SHA-1值。

        記錄我們目前的位置很簡單,就是能過HEAD指針,HEAD指向某一送出的SHA-1值或是某一分支的引用。

建立分支

git branch

        有時需要在建立分支後直接切換到建立的分支,可以直接用checkout的-b選項

git checkout -b

删除分支

git branch -d

        如果在指定的分支有一些unmerged的送出,删除分支會失敗,這裡可以使用-D參數強制删除分支。

git branch -D

檢出分支或送出

        檢出某一分支或某一送出是同一個指令

git checkout |

分支合并(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):

Git與Repo入門 版本控制 GIT  REPO

        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上找不到在哪個位置應用修 改。

Git與Repo入門 版本控制 GIT  REPO

分支衍合(rebase)

        有兩種方法将一個分支的改動合并進另一個分支,一個就是前面所說的分支合并,另一個就是分支衍合,這兩種方式有什麼差別呢?

        分支合并(merge)是将兩個分支的改動合并到一起,并生成一個新的送出,送出曆史是按時間排序的,即我們實際送出的順序,通過git log --graph或一些圖形化工具,可能很明顯地看到分支的合并曆史,如果分支比較多就很混亂,而且如果以功能點建立分支,等功能點完成後合回主線,由于 merge後送出是按送出時間排序的,送出曆史就比較亂,各個功能點的送出混雜在一起,還可能遇到上面提到的patch問題。

        而分支衍合(rebase)是找到兩個分支的共同祖先送出,将要被rebase進來的分支的送出依次在要被rebase到的分支上重演一遍,即 回到兩個分支的共同祖先,将branch(假如叫experiment)的每次送出的差異儲存到臨時檔案裡,然後切換到要衍合入的分支(假如是 master),依次應用更新檔檔案。experiment上有幾次送出,在master就生成幾次新的送出,而且是連在一起的,這樣合進主線後每個功能點 的送出就都在一起,而且送出曆史是線性的

         對比merge與rebase的送出曆史會是下圖這樣的(圖來自Pro-GIt):

Git與Repo入門 版本控制 GIT  REPO

(merge)

Git與Repo入門 版本控制 GIT  REPO

(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 /

        和建立分支的方法一樣,隻是加了一個參數--track或其縮寫形式-t,可以指定本地分支的名字,如果不指定就會被命名為remote-branch。

        要拉取某個遠端倉庫的資料,可以用git fetch:

git fetch

        當拉取到了遠端倉庫的資料後隻是把資料儲存到了一個遠端分支中,如original/master,而這個分支的資料是無法修改的,此時我們可以把這個遠端分支的資料合并到我們目前分支

git merge /

        如果目前分支已經跟蹤了遠端分支,那麼上述兩個部分就可以合并為一個

git pull

        當在本地修改送出後,我們可能需要把這些本地的送出推送到遠端倉庫,這裡就可以用git push指令,由于本地可以由多個遠端倉庫,是以需要指定遠端倉庫的名字,并同時指定需要推的本地分支及需要推送到遠端倉庫的哪一個分支

git push :

        如果本地分支與遠端分支同名,指令可以更簡單

git push 等價于 git push refs/heads/:refs/for/

        如果本地分支的名字為空,可以删除遠端分支。

        前面說過可以有不止一個遠端分支f,添加遠端分支的方法為

git remote add

六、标簽-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

        含附注的标簽需要加上參數-a(annotated),同時加上-m跟上标簽的說明

git tag -a -m ""

        如果你有自己的私鑰,還可以用 GPG 來簽署标簽,隻需要把之前的 

-a

 改為 

-s(signed)

        檢視标簽的内容用

 git show

        驗證已簽署的标簽用-v(verify)

git tag -v

        有時在某一個版本忘記打tag了,可以在後期再補上,隻需在打tag時加上commit-id

        要将tag推送到遠端伺服器上,可以用

git push

        或者可以用下面的指令推送所有的tag

git push --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

git config --global user.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與Repo入門 版本控制 GIT  REPO

八、其他

        關于GIT各指令的說明可以檢視相關幫助文檔,通過以下方法:

git help

 REPO

repo start

        開啟一個新的主題,其實就是每個Project都建立一個分支。

repo init -u [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  

        删除已經merge的分支

repo abandon

        删除分支,無論是否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'

        這個指令可以撤銷整個工程的本地修改。