2012年11月4日 星期日

《版本控制使用Git》筆記

一、準備開始

1.將任何的目錄轉換成 Git 容器:

git init

*該指令會在你專案的第一層建立一個叫 .git 的隱藏目錄,Git 會將所有的修改資訊放在此目錄中。


2.將檔案放入 Git 容器內:

git add index.html


3.取得檔案目前的狀態:

git status


4.在執行 git  commit 時開啟你所喜愛的的編輯器:

setenv GIT_EDITOR emacs

export GIT_EDITOR=vim


5.配置送交檔案的作者:

git config user.name "w1a2d3s4q5e6"

git config user.email "w1a2d3s4q5e6@gmail.com"

移除設定:
git config --unset user.name


6.檢視你的送交紀錄:

git log

*(1)僅能顯示特定檔案名稱的紀錄。若加上「--follow」參數,則可追溯歷史紀錄,並且尋找關於該內容的所有歷史。

*(2)若加上「--merge」參數,則顯示跟衝突檔案相關的 commit。

*(3)它對於檔案如何從一個狀態變成另一個狀態有非常清楚地紀錄。

*(4)它是在送交集合上執行程序


7.查詢更詳細的送交內容:

git show 送交辨識碼

*若未指明送交辨識碼,則會顯示最近一筆送交的詳細資訊。


8.顯示目前開發分支中精確的行式摘要:

git show-branch --more=最多顯示幾個分支版本數


9.檢視多個送交之間的差異:

git diff 送交辨識碼1 \ 送交辨識碼2

*顯示的內容若前面有「+」,則表示該內容為新的版本。


10.檔案移除:

git rm 檔名

*(1)會將檔案從索引以及工作目錄中移除。若加上「--cached」參數,則僅會將檔案從索引中移除,並將其移留在工作目錄中,也可用於移除檔案的準備狀態

*(2)在Git 移除檔案之前,它會確認在工作目錄中以及目前分支中(HEAD),與該檔案的版本是否相符,以防止任何更動的遺失。要讓 git rm 正常運作,在工作目錄中的檔案必須要跟 HEAD 或是索引內的內容相符。
因此若出現「error: ‘xxx’ has changes staged in the index」,則是代表檔案內容不相符。


*(3)如欲強制移除則加上「-f」參數。


加入歷史紀錄:
git commit -m "…..."


11.檔案改名:

git mv 原檔名 新檔名


加入歷史紀錄:
git commit -m "……"



12.拷貝容器:

git clone 原容器 新容器


13.配置檔案的階級架構(優先順序由高至低):

(1).git/config:以容器為影響範圍的配置檔案。

(2)~/.gitconfig:以使用者為影響範圍的配置檔案。

(3)/etc/gitconfig:影響全系統範圍的配置檔案。




二、基本的 Git 概念

1.容器(Repositories):

一個含有專案中所有可能被紀錄、管理的修訂資訊,以及歷史紀錄的資料庫。

*Git 在每個容器內維護的配置設定(如 name, email)不會經由拷貝或複製等動作傳遞出去。反而是以每個站、每個使用者以及每個容器為基礎,來管理以及審查配置檔案以及設定資訊。


2.在容器內,Git 維護兩個主要的資料結構:

(1)物件儲存:提供有效率的拷貝。

(2)索引:僅供容器使用的一個暫時的資訊,能依照需求而被修改或建立。


3.物件儲存的形態:

(1)Blobs(二進位大型物件):檔案的每個版本都是由 Blob 組成

(2)Tree(樹):代表一個階層的目錄資訊。紀錄了 blob 的辨識碼、路徑名稱以及在此目錄中所有檔案的一些資訊。

(3)Commits(送交):擁有每次容器更動時的所有資訊。每次的送交都會指向一個樹狀的物件,這個物件紀錄了送交當下該容器的狀態。

(4)Tags(標籤):將一個容易閱讀的名字賦予特定的物件(通常是 commit 物件)


4.索引(index):

為一個動態且暫時的二進位檔案,此檔案描述整個容器的目錄架構。


5.Git 追蹤內容與其他版本控制系統的差異:

(1)Git 的物件儲存是基於物件內容的雜湊演算值,而非檔案或目錄名稱。Git 追蹤的是內容,而不是檔案。

(2)Git 會從每個修訂版本到下一個檔案都儲存,因為每個雜湊必須在每個檔案的完整內容上運算。


6.標籤的形態:

(1)lightweight(輕量標籤):參照至一個送交物件(通常容器是私有的),這種標簽不會在物件儲存中建立永久的物件。

(2)annotated(標示標籤):真實地建立一個物件,該物件裡面包含你所提供的訊息。

*通常 Git 使用分支的名稱標記送交物件,這些物件指向一個樹狀物件,該物件包含了你容器內以及目錄的所有檔案




三、檔案管理以及索引

1.顯示兩組不同的改變集合:

git diff

*加上參數「--cahe」會顯示已經準備好要被送出的變更。


2.Git 中的檔案分類:

(1)被追蹤:已經在容器內或是已經在索引中準備好了。

(2)被忽略:如暫存檔、草稿檔等,必須要另外被定義。

*echo 檔案 > .gitignore

(3)不被追蹤:除了上述兩類的檔案以外。
3.將檔案的狀態轉變為「被追蹤的」:

git add 檔案 
*不要把 git add 指令當成「新增檔案」,而是「新增內容」。



4.在你工作中的檔案版本也許已經跟索引內的不同步了,當你要送交的時候,Git 會使用索引內的版本。

5.讓所有尚未被放入準備狀態的被追蹤檔案變成準備狀態(包含從工作拷貝中移除之被追蹤的檔案):

(1)git commit --all

(2)git commit -a


6. .gitignore 檔案:


可包含你想要忽略的檔案名稱模式的清單。或是可忽略某個目錄中的檔案。

*.gitignore 檔案會影響該目錄及其子目錄。

四、送交

1.當 commit 發生時,Git 會紀錄目前索引的狀態,然後將這個狀態放在物件儲存中。這個狀態並不包含每個在索引內的檔案以及目錄的拷貝。


2.Git 僅去比較目前索引的狀態和前一個狀態的差異,並且建立一個受這次 commit 影響的檔案以及目錄清單。

3.Git 會為任何已經改變的檔案建立新的 blob 物件,並且為任何改變的目錄建立新的樹狀物件,然後重複使用那些沒有變更的 blob 物件以及樹狀物件。


4.commit 是唯一讓容器改變的方法,亦即容器的改變都是由 commit 所觸發的。若沒有改變的紀錄,容器內的資料不應該要被改變。


5.ref 是個參照至某個 Git 物件的 SHA1 雜湊辨識碼,通常參照至 commit 物件。symref 則是間接指向某個 Git 物件的名稱,也稱作符號參照。


6.特殊的符號參照:

(1)HEAD:永遠指向目前分支上最新的 commit。當你修改分支時,HEAD 就會被更新。

(2)ORIG_HEAD:回復前一版本或是執行比較。

(3)FETCH_HEAD:是你抓取的上一個分支的 HEAD,並且僅在抓取的指令之後有效。

(4)MERGE_HEAD:當合併在執行時,另一個分支的 HEAD 將會被暫時地記錄在此。亦即它指向要被合併入目前 HEAD 的 commit 物件


7.瀏覽 commit 圖譜,繪出容器的 DAG

gitk

*每個 commit 可以擁有零個或多個父 commit,如下所示:

(1)普通的 commit 會擁有一個父 commit。當你修改時,你更改的部分就是新的 commit 以及其父 commit 之間的差異。

(2)通常只有一個commit,沒有父 commit:最初始的 commit 會顯示在圖中的底部。

(3)合併送交,有超過一個以上的父物件。


8.找出分支的起始點:

git merge-base


9.送交的範圍以「..」表示


10.將有問題的 commit 分離出來:

git bisect

*該指令會在「好」的 commit 以及「壞」的 commit 之間,系統化地一直選擇新的 commit。最終,會將範圍縮小至某個單一 commit,而該 commit 就是造成問題的送交。 
開始搜尋:

git bisect start

告訴 Git 哪個版本是好/壞的:
git bisect good(bad)

回到原本的分支上:
git bisect reset

*詳細指令參考「查找问题的利器 - Git Bisect


11.查看某個檔案的每個部分是由誰修改的:

git blame

*詳細指令參考「查找问题的利器 - Git Blame


12.搜尋某檔案在每個 commit 的差異處:

git log -S



五、分支

1.在某個時間點,一個容器內會有許多不同的分支,但僅僅有一個是「現行」或「有效」的分支。現行的分支決定哪些檔案在工作目錄中被取出。

2.Git 並不會紀錄分支是從哪裡起源的。當新的 commit 在分支上產生時,該分支的名稱將會持續地往前移動到新 commit 上。舊的 commit 將由其雜湊值或是相對名稱來命名。若你想要追蹤某個特定的送交,你可以指定一個輕量的標籤名稱給它。


3.分支開始的原始 commit 不會被特別標示出來,因此若要找到該 commit,可以使用分開至新分支的原始分支的名稱找到:

git merge-base original-branch new-branch

4.建立分支:

git branch 分支名稱 開始分支的commit

*當「開始分支的commit」未被指定時,預設會使用目前分支最新的送交版本。亦即預設將會從你目前正在工作的版本來開始一個新的分支


5.列出分支名稱:

git branch


6.檢視分支:

git show-branch
*該指令輸出由破折號組成的分隔線分成兩部分:

(1)在分隔線之上:一行列出一個分支的名稱,並將其包在方括號內。每個分支名稱的前端都有驚嘆號或是星號佔據某一列,第一個分支使用第一列,第二個分支使用第二列,以此類推。有星號的分支同樣是代表目前的分支。


(2)在分隔線之下:輸出一個矩陣,該矩陣代表該 commit 在那些分支中出現。在此,每個 commit 後方也列出該 commit 歷史記錄訊息的第一行。若該 commit 在該分支中出現,則用「+」、「*」、「-」在該分支的欄位上代表之。

+」代表 commit 在分支之內;「*」也代表 commit 在分支之內,只是特別強調該分支是目前正在使用的分支;「-」則代表合併 commit

*詳細參考「《看日记学git》之二十七


7.取出分支:

git checkout 分支名稱

*變更分支的影響有:

(1)在目前分支內沒有,但在要取出的分支內有的檔案以及目錄,將會從物件儲存內被取出並且放入你的工作目錄。

(2)在要取出的分支內沒有,但在目前分支內有的檔案以及目錄,將會從你的工作目錄中被移出。

(3)兩個分支都有的檔案,其內容將會變更為被取出分支的內容。


8.在你的工作目錄中,但是並未被追蹤的檔案以及目錄都不會被 Git 移除或修改。然而,若你在本地端更改某個檔案並且修改內容使其與新分支上的不同,Git 就會不允許取出目標分支。


9.將你本地端的改變跟新的工作目錄合併,藉此將你本地端的改變套用至新的分支:

git checkout -m 分支名稱


10.建立新分支的同時,又想切換過去:

git checkout -b 新分支名稱


11.刪除分支:

git branch -d 分支名稱


12.分支只是一個名稱或是指向某個擁有實際內容送交的指標


13.Git 最終會刪除再也無法被其他分支名稱或標籤名稱參照或是被盜打的 commit。若你想要保留這些 commit,你必須將它們合併至不同分支、為它們建立分支或是將一個標籤指向它們。



14.還原移除的分支或其它參照:

git reflog





六、Diffs

1. 顯示工作目錄與索引之間的差異:

git diff

*(1)它顯示在工作目錄中尚未進入準備狀態,並且應該要在下次送交前變為準備狀態的物件。
*(2)
git diff 指令不會管檔案的歷史紀錄,它僅比較任何跟分支相關的紀錄。

*(3)它是在兩個不同的點上執行程序。
2.總結你工作目錄以及給定的 commit 送交之間的差異:

git diff commit


3.呈現在索引中已經進入準備狀態的改變,以及給定的 commit 送交之間的比較:
git diff --cached commit


4.顯示兩個送交之間的差異:

git diff commit1 commit2....

*它會忽略索引以及工作目錄。也可用於比較數個已存在物間儲存中的樹狀物件。

5.diff 常用參數:


(1)--M:偵測檔案名稱的變更。若該檔案名稱被修改的同時也新增了其內容,則 Git 不會顯示這類檔案。

(2)-w:不會將空白視為變更。

(3)--stat:顯示兩棵樹狀態差異的統計數據,包括有多少行被變更、新增、刪除。

(4)--color:將輸出著色。




七、合併

1.在 Git 中,合併必須發生在單一容器內。亦即所有要被合併的分支都必須在相同的容器內。


2.當一個分支中的修改與另外一個分支的修改並不衝突時,Git 會運算合併結果並且產生一個 commit 來表示這個新的統一狀態。

但當分支衝突時,Git 不會去處理這個衝突。Git 會在索引內將有爭議的部分標記為「unmerged」。


3.合併分支:

git merge 分支名稱


4.在合併送交前想放棄或重新合併:

git reset --hard HEAD
*須在執行最後的 commit 指令之前使用。



5.在合併送交後想放棄或重新合併:

git reset --hard ORIG_HEAD

*若你並未從一個乾淨的工作目錄或索引中開始合併,你會遇到一些問題,並且會遺失那些在你目錄中尚未送交的改變。

6.若你不滿意底對於衝突的解決辦法,並且想要回復到原始的衝突狀態重新解決衝突:
git checkout -m


7.導致合併的退化情況(不會真正的產生新的合併送交):

(1)已是最新的:所有從其它分支(它們的 HEAD)來的送交都已經在你的目標分支了。

(2)快轉:當你的分支 HEAD 已經擁有所有其它分支的內容。


8.合併策略:

(1)解決策略:一次只能套用在兩個分支上,使用共同的祖先當作合併的基礎,然後藉由套用從合併基礎到另一個分支頂端的變更到目前的分支,執行一個三方向的合併。

(2)遞迴策略:一次只能套用在兩個分支上,然而它是用來處理當兩個分支之間擁有多於一個合併基礎的情況

在這種情況中,Git 會為所有的共同合併基礎產生一個暫時性的合併,然後使用它來當作兩個分支的合併基礎,最後再執行三方向合併的演算法。

(3)章魚策略:用來同時合併超過兩個分支的情況,它多次呼叫「遞迴策略」,每次處理一個目標分支

(4)我們的策略:合併任意數量的其它分支,但實際上它放棄了從這些分支來的變更,然後僅使用目前分支的檔案。若你知道你已經有其它分支的所有變更,但想要結合兩者的歷史紀錄時,這會很有用。亦即它讓你紀錄你因為某些原因而執行合併,在未來 Git 將不會再次合併這些歷史紀錄。

(5)子樹策略:合併另一個分支,但所有在該分支中的東西將會合併為目前分支的子樹




八、修改送交

1.只要分支也許會出現在別的容器內,你就不應該複寫、改變或變更該分支的任何部分


2.改變你的容器及工作目錄到一個已知的狀態:

git reset

*它會將 HEAD 參照至某個指定的送交,並且預設會更新索引來配合該送交。這個指令的重點是幫 HEAD、索引和工作目錄建立並恢復已知的狀態。

*它也會在 ORIG_HEAD 參照中儲存原始的 HEAD 值。


3.git reset 指令的選項:

(1)--soft:改變 HEAD 參照並將其指向給定的送交。你索引和工作目錄中的內容不會被改變。

*僅變更符號參照的狀態,將其指向新的送交。

(2)--mixed改變 HEAD 參照並將其指向給定的送交。你索引內的內容也會被變更來對應該送交的樹狀結構,但你工作目錄的內容不會被改變。

*將你的索引變成你將所有的改變放入準備狀態後的狀態,並且它會告訴你工作目錄中有哪些是被變更的。

(3)--hard改變 HEAD 參照並將其指向給定的送交。索引的內容也會被更改為該送交對應的樹狀結構。此外,你工作目錄中的內容也會被變更為該送交的樹狀結構鎖反應的狀態。

*當你改變你的工作目錄時,完整的目錄結構會依照給定的送交而被修改。變更將會遺失而新的檔案也會被移除。在指定的送交內但不在工作目錄中的檔案將會被回復。

4.將某個特定的送交從一個容器內送入另一個分支上:

git cherry-pick


*不會在容器內改變現有的歷史紀錄,而是新增新的送交到歷史紀錄內。


5.復原某個已經在分支歷史紀錄深處的送交所造成的影響

git revert

*不會在容器內改變現有的歷史紀錄,而是新增新的送交到歷史紀錄內。


6.checkoutreset、revert比較:

checkout:你目前的分支以及 HEAD 參照將會切換到目標分支的頂端。

reset:不會改變你的分支。然而,若你提供分支的名稱,它會改變你目前的工作目錄,讓它看起來如同你所指定分支的頂端。換句話說,reset 會重設目前分支的 HEAD 參照。

revert:與兩者不同,是在送交上運作,而不是在檔案上運作。因此不會修改到歷史紀錄。

詳細請參考「What's the difference between Git Revert, Checkout and Reset?


7.更改目前分支上最新的送交:

git commit --amend

*常用於修改送交之後打錯字的狀況。


8.重新指定送交的基礎位置:


git rebase

*常用於保留一序列關於其它分支最新開發的送交,通常是 master 分支或是在其它容器內的追蹤分支。


9.完整移植一個分支上的開發線到另一個完全不同的分支上:

git rebase --onto


10.重新指定基礎位置的動作。該指令藉由送交被解決的衝突,並且處理序列中的下一個送交回復動作:git rebase --continue


11.放棄整個重新指定基礎位置的動作,並回復你的容器到執行 git rebase 指令前的狀態:


git rebase --abort


12.修改多個送交,讓它們組成一個分支並且放回相同的分支或是不同的分支上:

git reabse -i



13.重新指定一個送交序列的基礎位置,會導致 Git 產生一整個新的送交序列。它們擁有新的 SHA1 送交辨識碼、基於新的起始狀態並且有不同的 diff,雖然裡面的變更集合會導致相同的最終狀態。


14.若重新指定基礎位置的做法不是個好選擇,並且你仍然需要分支上的改變,那麼合併會個正確的選擇。


15.重新指定基礎位置會用新的送交複寫舊的送交


16.使用舊的且重新指定基礎位置前的送交的使用者,將會遇到問題。


17.若你有分支使用重新指定基礎位置前的送交,你也需要依序重新指定它的基礎位置


18.若在不同的容器中,有使用者使用重新指定基礎位置前的送交,就算它已經移入你的容器內,使用者仍擁有該送交的拷貝,所以使用者也會需要修復他們的送交歷史。



九、遠端容器

1.遠端容器是一個參照到另一個容器的方式。


2.在一個容器中,你可以定義任何數量的遠端容器,藉此建立分享容器的網路。


3.要追蹤其他容器內的資料,Git 使用追蹤分支。每個你容器內的追蹤分支是一個本地分支,該本地分支就如同在遠端容器內某個特別分支的代理伺服器。


4.基於指定的原始容器建立一個新的 Git 容器:

git clone

*通常在使用該指令時,存在 refs/heads/ 中原始容器的本地開發分支,會變成遠端追蹤分支,放在新複製容器中的 refs/remotes/ 下。而在原始容器中 refs/remotes/ 下的遠端追蹤分支將不會被複製。


5.在原始容器內的標籤會被拷貝到複製容器內,可從這些參照到達的物件也會被拷貝。然而,如掛鉤、配置檔案以及 reflog 等容器的特定資訊,都不會被拷貝到複製容器內。


6.每個新的複製容器預設會維護一個「連結」,能夠藉由遠端的方式(origin)連回父容器。然而,原始的容器並不會知道複製容器的資訊,也不會有連結能夠連到任何的複製容器。這是一個單向的關係。


7.與遠端容器相關的 Git 指令:

(1)git fetch:從遠端容器中取得物件及其相關資訊。

(2)git pull:同上,但同時會將變更合併至相關的分支。

(3)git push:將物件及其相關的資訊傳送至遠端物件。

(4)git ls-remote:顯示在遠端容器中的參照。


8.當你複製了一個容器,就算你建立了本地的送交並且建立了本地的分支,你仍然可以同步原始來源容器的變更。藉由追蹤分支,Git 允許你跟隨這兩個分支一起開發。


9.在複製的動作進行時,Git 在複製容器內會為每個在原始容器內的主題分支,建立一個遠端追蹤分支。本地的容器使用它的追蹤分支來跟隨或是追蹤在遠端容器的變更。這些遠端追蹤分支會依據被複製的遠端容器來使用全新的命名空間。


10.遠端容器是一個存在容器設定檔案中的名稱實例。它包含兩個不同的部分。第一個部分使用 URL 的形式記載了其他的容器名稱,它會列出存取協定以及資料的位置。第二部分稱作 refspec,是將在遠端容器中的分支名稱對應到你本地容器的分支名稱紀錄。


11.讓容器內的每個遠端容器都藉由確認並且抓取容器中的新送交而被更新:

git remote update

*若你不想要更新全部的遠端容器,你可以在遠端容器初始新增時使用「-f」選項來限制 fetch 指令僅更新單一個遠端容器。


12.要讓 Git 能在兩個不同的歷史紀錄之間執行合併,兩個歷史紀錄都必須要出現在同一個容器內的兩個不同分支上。

然而,或因為複製而使得歷史紀錄在不同的容器中,遠端分支一定是藉由 fetch 的動作帶入你的容器內。因此你可以直接使用 git fetch 指令或是使用一部份的 git pull 指令。

13.任何在你本地複製容器內的分支上建立的開發工作都無法被父容器看到,除非你直接要求將它傳遞回父容器。

相同的,在你容器內分支的刪除仍然是本地的變更,並且除非你要求在父容器中也要移除該分支,要不然在父容器中的分支也不會被移除。


14.在遠端容器的介面維護配置檔案:
git remote


15.直接管理你配置檔案中的紀錄:

git config

*加上參數「-l」可以列出配置檔案中包含變數名稱的完整內容。


16.push 指令的動作會更新包含 HEAD 送交的容器狀態。也就是說,就算開發者沒有在遠端容器做任何工作,分支的參照以及 HEAD 仍然有可能會改變,使得它門與被取出的檔案以及分支不同步。

因此你最好只推播至裸容器。


17.發佈容器:

git-daemon



十、管理容器

1.當你發佈一個別的使用者可能會複製的容器時,你應該將它視為一個靜態的容器,並且避免複寫任何分支的歷史紀錄

2.容器關係的重點是在於兩個容器之間資料是如何被交換的。亦即任何你寄送變更的容器通常都視為你的上傳;任何把你的變更作為基礎的容器則被視為你的下載。



十一、補綴檔案

1.Git 交換補綴檔案的指令:

(1)產生電子郵件格式的補綴檔案:
git format-patch

(2)使用 SMTP 方式寄出 Git 的補綴檔案:
git send-email
(3)套用在電子郵件訊息中的補綴檔案:
git am


2.在建立補綴檔案之前,驗證有哪些送交集合會產生補綴檔案:

git rev-list --no-merges -v since..until


3.將一系列的補綴檔案送入到某個共同的目錄:

git format-patch -o 目錄



十二、掛鉤

1.在你私人容器中設定的掛鉤並不會傳遞到新複製的容器,也不會影響新複製容器的行為。


2.掛鉤是在你目前本地分支的狀態下或是遠端容器的狀態下執行的。


3.一個「前」掛鉤會在動作完成之前被執行。你可以使用這類的掛鉤來在套用之前同意、拒絕或調整一個變更;一個「後」掛鉤會在動作完成之後執行,並且可以用來觸發通知或載入其他的處理程序。


4.詳細掛鉤種類、使用方式請參考「Git Community Book 中文版 - Git Hooks




十三、結合專案

1.從子專案合併整個歷史紀錄:

git pull -s subtree

*只有在你的子專案的歷史紀錄也是存在 Git 中才可以被使用。


2.gitlink:從樹狀物件直接指向送交物件:

git add git


3.當你切換分支或是在某人的分支上執行 git pull 指令時,你永遠需要執行 git submodule update  指令來獲得子模組的對應集合。


4.若你切換到另一個分支但又不想執行 git submodule update 指令,Git 將會認為你是故意變更你的子模組來指向一個「新」的送交(實際上是你之前使用的舊送交)。若你繼續執行 git commit -a 指令,你將會意外的變更 gitlink。


5.你可以藉由取出對的版本的子模組、在子模組目錄中執行 git add 指令,然後執行 git commit 指令來更新一個已經存在的 gitlink。


6.若你已經在你的分支上更新並送交一個 gitlink,且若你在另一個更新同樣 gitlink 的分支上使用 git pull 指令或是 git merge 指令,如此不同的更新 gitlink 的行為,Git 會不知道要將它視為衝突或是要單純挑選其中一個。你必須記住要自己解決 gitlinks 的衝突。



*推薦網站:
git(1) Manual Page


Git Community Book 中文版


沒有留言:

張貼留言