654 views
# Git [toc] ## 初次設定安裝 ### 設定識別資料 在安裝Git後,第一件事情是設定使用者名稱以及電子郵件帳號,因為每次Git的提交都會使用這些資訊。 ``` $ git config --global user.name "John" $ git config --global user.email john@gmail.com ``` 其中,若有使用 ==-\-global== 參數,只需要做此設定一次,之後Git做任何事都會採用第一次的資訊。若想指定不同的名字或郵件給特定的專案,只需要在此專案目錄執行此命令,且無須加上 ==-\-global== 參數。 ### 檢查設定 ``` $ git config --list user.name=John user.email=john@gmail.com color.status=auto color.branch=auto color.interactive=auto color.diff=auto ... ``` ## 取得Git版本控制倉庫(Repository) 取得方式有兩種,一種是將現有的專案或目錄匯入Git,另一種則是從其他伺服器複製一分已存在的Repository。 ### 在現有目錄初始化Repository 若要追蹤現有的專案,只需進入該專案的目錄並執行: ``` $ git init ``` 這個命令會建立名為 ==.git== 的子目錄,裡面包含一個Git版本控制倉庫架構必要的所有檔案。 ### 複製現有的版本控制倉庫 若想要取得現有的Repository的複本,我們可以使用 ==git clone [url]== 來做到。例如:我們想複製名為Grit的Ruby Git程式庫,可以執行下列命令: ``` $ git clone git://github.com/schacon/grit.git ``` 輸入完後,就會有個叫做grit的目錄,且在其下初始化名為 ==.git== 的子目錄。若想將Repository複製到grit以外其他名字的目錄,只需要多打一個指令: ``` $ git clone git://github.com/schacon/grit.git mygrit ``` 如此一來,目錄名就會變成mygrit。 ## 提交更新到Repository 在取得檔案之後,若修改了其中一些資訊,且想要儲存這些修改過的檔案,我們就必須提交這些更動過的記錄到Repository。 在工作目錄內的每個檔案可能為兩個狀態的任一種:==**被追蹤**==或是==**尚未被追蹤**==。被追蹤的檔案是最近最新的狀態,它們可以被復原、修改,或是暫存。只要有編輯過任何被追蹤的檔案,Git就會將他們視為被更動的。接著,我們可以暫存這些檔案進入stage的狀態,然後提交這些被暫存的檔案(commit)。 ![](https://git-scm.com/figures/18333fig0201-tn.png) ### 檢視檔案狀態 我們可以利用 ==git status== 來檢視目前檔案的狀態。 ``` $ git status On branch master nothing to commit, working directory clean ``` working directory clean代表目前的工作目錄沒有未被追蹤或已被修改的檔案。Git如果有發現任何未追蹤的檔案,會將他們列出。例如:我們新增一個檔案README,再執行git status後,會看見為被追蹤的檔案。 ``` $ vim README $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) README nothing added to commit but untracked files present (use "git add" to track) ``` 可以看見它被列在Untracked files的下方,除非我們指定要將這個檔案加入,否則Git不會主動將它加入。 ### 追蹤新檔案 如果我們要追蹤剛剛新增的檔案README,我們就輸入下面的指令: ``` $ git add README ``` 如此一來,我們在檢視檔案狀態時,可以發現README已被列入追蹤且已被暫存。 ``` $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README ``` 另外,如果我們對已追蹤的檔案benchmarks.rb做了修改,我們檢視檔案可以發現以下狀況: ``` $ vim benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb ``` benchmarks.rb檔案出現在Changes not staged for commit下,代表此檔案已被追蹤,且位於做目錄的該檔案已被修改,但是尚未暫存。若要暫存該檔案,我們一樣用 ==git add== 來做到 (git add是一個多用途的指令)。最後我們可以從 ==git status== 得到以下資訊: ``` $ git add benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: benchmarks.rb ``` - <font color=red>一次加入所有檔案</font> 如果一次新增或修改了很多檔案,一個一個輸入 ==git add== 的話會很麻煩。我們可以利用 ``` $ git add . ``` 此指令可以將所有剛剛修改過或新增的檔案一次加進stage的狀態。此方法雖然方便但很容易就會加入一些不必要的檔案。 ### 忽略檔案 有些檔案可能不想他們出現在"未被追蹤檔案"的地方,例如紀錄檔或是編譯系統產生的檔案。我們可以建立一個名為 ==.gitignore== 的檔案。接著在裡面列出這些檔案檔名的特徵。 ``` $ vim .gitignore *.[oa] *~ ``` 第一列是要求忽略任何檔名為 ==.o== 或 ==.a== 結尾的檔案,可能是編譯系統建置讀者的程式碼時產生的目的檔及程式庫。第二列則是忽略所有檔名為~結尾的檔案,通常被很多文書編輯器使用的暫存檔案。 以下提供其他編寫.gitignore的規則: ``` *.a #不要追蹤檔名為.a結尾的檔案 !lib.a #但是要追蹤lib.a,即使上方已指定忽略所有的.a檔案 build/ #忽略build/目錄下所有檔案 ``` - ==.gitignore== 只對規則設定之後的檔案有效 ### 清除忽略檔案 ``` $ git clean -f #將未被追蹤的檔案刪除掉 $ git clean -n #列出打算要清除的檔案 ``` ### 檢視已暫存及尚未暫存的更動 某些情況下, ==git status== 提供的資訊太過簡要。有時我們不只想知道哪些檔案被修改過,而且想要更進一步了解做了哪些修改,這時就可以使用 ==git diff== 指令。 假設現在編輯且暫存README,接著修改benchmarks.rb卻未暫存。我們利用 ==git status== 檢視會得到以下文字: ``` $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: README Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb ``` 接下來執行 ==git diff== 來了解尚未暫存的修改。 ``` $ git diff diff --git a/benchmarks.rb b/benchmarks.rb index 3cb747f..da65585 100644 --- a/benchmarks.rb +++ b/benchmarks.rb @@ -36,6 +36,10 @@ def main @commit.parents[0].parents[0].parents[0] end + run_code(x, 'commits 1') do + git.commits.size + end + run_code(x, 'commits 2') do log = git.commits('master', 15) log.size ``` 此命令會比對目前工作目錄(working directory)及暫存區域(stage area)的版本,然後顯示尚未被存入暫存區(stage area)的變更。 若想要比對暫存區域(stage)以及最後一次提交(commit)的差異,可以使用 ==git diff --cached== 指令(Git 1.6.1之後的版本,可用比較好記的 ==git diff --staged== 指令) ``` $ git diff --cached diff --git a/README b/README new file mode 100644 index 0000000..03902a1 --- /dev/null +++ b/README2 @@ -0,0 +1,5 @@ +grit + by Tom Preston-Werner, Chris Wanstrath + http://github.com/mojombo/grit + +Grit is a Ruby library for extracting information from a Git repository ``` ### 提交修改 在暫存區的檔案已經被更新成我們想要的樣子之後,就可以開始提交變更的部分。不過剩下尚未暫存的新增檔案或是被修改但還沒使用 ==git add== 暫存的檔案將不會記錄在本次的提交中,他們仍以被修改檔案的身分存在磁碟中。 在準備要提交修改,只需要執行下面指令 ``` $ git commit ``` 接著會叫出讀者指定的編輯器。 ``` # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # new file: README # modified: benchmarks.rb # ~ ~ ~ ``` 我們可以看到預設的提交訊息包含最近一次 ==git status== 的輸出以註解方式呈現,以及螢幕最上方有一列空白列。 讀者可移除這些註解後再輸入提交的訊息,或者保留它們,提醒你現在正在進行提交。 在離開編輯器後,Git會利用這些訊息來產生新的提交。 如果想要知道更動的內容,可以多下一個參數 ==-v== 就可以得到對比結果。 ``` $ git commit -v ``` 還有一個提交方式如下: ``` $ git commit -m "Story 182: Fix benchmarks for speed" [master 463dc4f] Story 182: Fix benchmarks for speed 2 files changed, 3 insertions(+) create mode 100644 README ``` 完成提交後,可以從輸出的訊息看到此提交、放到哪個分支、SHA-1值、有多少檔案更動,以及統計此提交有多少列被新增和移除。 此外,Git還提供了以下的提交方式: ``` $ git commit -a -m 'added new benchmarks' ``` 在後方加上 ==-a==參數,Git自動將所有已被追蹤且被修改的檔案送到暫存區並開始提交程序,直接略過 ==git add== 的步驟。 ### 刪除檔案 要從Git刪除檔案,我們需要將他從已被追蹤檔案中移除(更精確地來說,是從暫存區移除),並且提交。 ==git rm==指令除了完成此工作外,也會將該檔案從工作目錄移除,此後不會在未被追蹤檔案列表看到它。 ``` $ git rm grit.gemspec rm 'grit.gemspec' $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) deleted: grit.gemspec ``` 如果我們只是用 ==rm== 將檔案從工作目錄移除,那麼在 ==git status== 的輸出,會看見該檔案被視為"已被更改且尚未被更新"(也就是尚未存到暫存區)。 ``` $ rm grit.gemspec $ git status On branch master Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: grit.gemspec no changes added to commit (use "git add" and/or "git commit -a") ``` 這樣我們還需要再經過 ==git add== 指令才能完成刪除。 - 如果忘記將某些檔案紀錄到 ==.gitignore== 且將它增加到暫存區時,可以使用下面指令,就不會再被追蹤,但並沒有將檔案刪除。 ``` $ git rm test.txt --cached ``` ### 更改名字 Git並不像其它檔案控制系統一樣,明確地追蹤檔案的移動。 若將被Git追蹤的檔名重新命名,並沒有任何metadata保存在Git中去標示此重新命名動作。 然而Git能很聰明地指出這一點。 稍後會介紹關於偵測檔案的搬動。 ``` git mv file_from file_to ``` 在執行完重新命名的動作後檢視一下狀態,可以看到下面的狀態: ``` $ git mv README.txt README $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.txt -> README ``` 不過,這相當於執行下列的命令: ``` $ mv README.txt README $ git rm README.txt $ git add README ``` ## 檢視歷史紀錄 ### 檢視提交的歷史紀錄 在提交數個更新,或是複製已有一些歷史紀錄的Repository後,我們就可以利用以下命令來檢視之前的紀錄。 ``` $ git log commit ca82a6dff817ec66f44342007202690a93763949 Author: Scott Chacon <schacon@gee-mail.com> Date: Mon Mar 17 21:52:11 2008 -0700 changed the version number commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 Author: Scott Chacon <schacon@gee-mail.com> Date: Sat Mar 15 16:40:33 2008 -0700 removed unnecessary test code commit a11bef06a3f659402fe7563abf99ad00de2209e6 Author: Scott Chacon <schacon@gee-mail.com> Date: Sat Mar 15 10:31:28 2008 -0700 first commit ``` 在未加入任何參數的情況下, ==git log== 會將越新的更新越早列出來,同時也會列出每個更新的SHA-1值、作者大名及電子郵件地址、及提交時輸入的訊息。 接下來介紹一些較常用的選項: ==-p==用來顯示每個更新之間的差別,==-n==(n為任意數字)可以限制只輸出最後n個較新的更新。 此外,還有一個選項是 ==-\-pretty== ,可以改變原本預設輸出的格式。有幾個內建的選項可供使用。其中==oneline==是將每一個更新印到單獨一行,對於檢視很多更新時很好用。 ``` $ git log --pretty=oneline ca82a6dff817ec66f44342007202690a93763949 changed the version number 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test code a11bef06a3f659402fe7563abf99ad00de2209e6 first commit ``` 還有一個選項是 ==format== ,可以自訂輸出的格式。 ``` $ git log --pretty=format:"%h - %an, %ar : %s" ca82a6d - Scott Chacon, 11 months ago : changed the version number 085bb3b - Scott Chacon, 11 months ago : removed unnecessary test code a11bef0 - Scott Chacon, 11 months ago : first commit ``` 以下列出一些 ==format== 支援的選擇 | 選項 | 選項說明 | | ------- | -------- | |%H | 該更新的SHA1雜湊值| |%h | 該更新的簡短SHA1雜湊值| |%T | 存放該更新的根目錄的Tree物件的SHA1雜湊值| |%t | 存放該更新的根目錄的Tree物件的簡短SHA1雜湊值| |%P | 該更新的父更新的SHA1雜湊值| |%p | 該更新的父更新的簡短SHA1雜湊值| |%an | 作者名字| |%ae | 作者電子郵件| |%ad | 作者的日期 (格式依據 date 選項而不同)| |%ar |相對於目前時間的作者的日期| |%cn |提交者的名字| |%ce |提交者的電子郵件| |%cd |提交的日期| |%cr |相對於目前時間的提交的日期| |%s |標題| ==oneline== 和 ==format== 選項對於另一個名為 ==-\-graph== 的選項特別有用。==-\-graph== 可以畫出分支的分歧和合併的歷史。 ``` $ git log --pretty=format:"%h %s" --graph * 2d3acf9 ignore errors from SIGCHLD on trap * 5e3ee11 Merge branch 'master' of git://github.com/dustin/grit |\ | * 420eac9 Added a method for getting the current branch. * | 30e367c timeout code and tests * | 5a09431 add timeout protection to grit * | e1193f8 support for heads with slashes in them |/ * d6016bc require time for xmlschema * 11d191e Merge branch 'defunkt' into local ``` 以下列出一些格式選項: | 選項 | 選項說明 | | ------- | -------- | |-p |顯示每個更新與上一個的差異。| |--word-diff |使用 word diff 格式顯示 patch 內容。| |--stat |顯示每個更新更動的檔案的統計及摘要資訊。| |--shortstat |僅顯示--stat提供的的訊息中關於更動、插入、刪除的文字。| |--name-only |在更新的訊息後方顯示更動的檔案列表。| |--name-status |顯示新增、更動、刪除的檔案列表。| |--abbrev-commit |僅顯示SHA1查核值的前幾位數,而不是顯示全部的40位數。| |--relative-date |以相對於目前時間方式顯示日期(例如:“2 weeks ago”),而不是完整的日期格式。| |--graph |以 ASCII 在 log 輸出旁邊畫出分支的分歧及合併。| |--pretty |以其它格式顯示更新。 可用的選項包含oneline、short、full、fuller及可自訂格式的format。| |--oneline |--pretty=oneline --abbrev-commit 的簡短用法。| ## 復原 ### 更動最後一筆更新 最常發生於太早提交更新,可能忘了加入某些檔案,或是搞砸了提交的訊息。若要試著重新提交,可以在後面加上 ==--amend== 選項: ``` $ git commit --amend ``` 如下例,若提交了更新後發現忘了一併提交某些檔案,我們可以執行此指令: ``` $ git commit -m 'initial commit' $ git add forgotten_file $ git commit --amend ``` 這些指令的僅僅會提交一個更新,第二個被提交的更新會取代第一個。 ### 取消已被暫存的檔案 假設我們修改了兩個檔案,想要以不同的更新提交他們,不過不小心執行 ==git add .== 將他們同時都加入暫存區,應該如何將其中一個移出暫存區呢?命令已附上相關的提示: ``` $ git add . $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.txt modified: benchmarks.rb ``` 在"Change to be committed"的下一行,已註明使用 =="git reset HEAD <file>..."== 來將檔案移出暫存區,我們只要按此建議執行即可! ``` $ git reset HEAD benchmarks.rb Unstaged changes after reset: M benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.txt Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb ``` ### 復原已被更動的檔案 同樣地,如果我們不需要某個檔案的修改,一樣可以透過 ==git status== 來得到答案。 ``` Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: benchmarks.rb ``` 所以我們利用 ==git checkout -\- benchmarks.rb== 指令來完成復原。 ``` $ git checkout -- benchmarks.rb $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: README.txt ``` ## 分支(branch) 我們使用分支,可以離開開發主線,然後在不影響主線的狀況下繼續工作。 ### 列出分支 ``` $ git branch ``` 首先,我們可以藉由上面指令知道目前的分支有哪些,而在HEAD指標所指向的的分支會有一個 ==*== 的符號。 ### 建立分支 ``` $ git branch testing ``` 接下來建立新的分支,只需要數入上面指令, ==git branch== ,後面再加上分支的名稱即可。 - <font color=red>Git如何知道我們目前在哪個支線工作?</font> Git還保存一個稱為HEAD的特別指標,他會指向你正在工作的分支。 ![](https://git-scm.com/figures/18333fig0305-tn.png) ### 切換分支 在我們下 ==git branch== 指令建立新分支後,並不會自動切換到新的分支。所以我們要執行已下指令才會切換到其他的分支: ``` $ git checkout testing ``` ![](https://git-scm.com/figures/18333fig0306-tn.png ) 當然,我們也可以輸入下面指令,可以達到建立新分支的同時,也切換到新的分支。``` ``` $ git checkout -b testing ``` 那麼,分支帶給我們哪些便利性? 以下,我們先做一次提交: ``` $ vim test.rb $ git commit -a -m 'made a change' ``` 便會得到下面的結果,可以發現分支testing向前移動了一格,HEAD也跟著分支testing向前移動,而分支master沒有移動。接著,我們切換到master分支,可以看到HEAD指標移回到master分支,而且工作目錄中檔案換成了master分支所指向的快照內容。 ![](https://git-scm.com/figures/18333fig0308-tn.png) 最後,我們在分支master再做一些修改與提交: ``` $ vim test.rb $ git commit -a -m 'made other changes' ``` 就會發現我們的專案提交歷史出現分岔,因為我們在建立了一個新分支後,轉換到其中進行了一些工作,然後又回到原來的主分支進行了另外一些工作。這些改變會分別孤立在不同的分支裡,我們可以在不同分支裡反覆切換,並在時機成熟時把它們合併到一起。 ![](https://git-scm.com/figures/18333fig0309-tn.png) ### 合併分支 與 刪除分支 假設在完成一系列的修改、提交等動作之後,我們得到了以下的分支圖: ![](https://git-scm.com/figures/18333fig0313-tn.png) 首先,我們先回到master分支,且將hotfix分支合併進來: ``` $ git checkout master $ git merge hotfix Updating f42c576..3a0874c Fast-forward README | 1 - 1 file changed, 1 deletion(-) ``` 可以發現出現了"Fast-forward"的提示。由於master分支所要合併的hofix分支即為master的上游,因此在合併時只需要順著分支走下去即可到達hotfix分支。因為這種單線的歷史分支不存在任何需要解決的分歧,所以這種合併過程可以成為==快進(Fast forward)==。 ![](https://git-scm.com/figures/18333fig0314-tn.png) 接下來,如果hotfix分支已經用不到了,我們可以輸入以下指令: ``` $ git branch -d hotfix Deleted branch hotfix (was 3a0874c). ``` 在git branch指令後面加上 ==-d== 參數,就可以將此分支刪除掉。 在經過又一次的提交在iss53分支上之後,我們想要將master和issue53兩個分支做合併,一樣使用 ==git merge== 指令來完成。 ![](https://git-scm.com/figures/18333fig0315-tn.png) ``` $ git checkout master $ git merge iss53 Auto-merging README Merge made by the 'recursive' strategy. README | 1 + 1 file changed, 1 insertion(+) ``` 然而這次的合併跟之前hotfix分支的合併就不一樣了。由於master分支所指向的提交物件(C4)並不是 iss53 分支的直接祖先,Git需要進行一些額外的處理。就此例而言,Git會用兩個分支的末端(C4和C5)以及共同祖先(C2)做三方合併。 ![](https://git-scm.com/figures/18333fig0316-tn.png) > 合併分支的過程(三方合併) https://blog.csdn.net/u012937029/article/details/77161584 做完三方合併後,產生一個新的快照,並且自動建立一個指向他的提交產物(C6)。 ![](https://git-scm.com/figures/18333fig0317-tn.png) - <font color=red>三方合併</font> 三方合併就是先找出一個基準,然後以基準為Base 進行合併,如果2個文件相對基準(base)都發生了改變,Git就回報發生衝突,然後讓你人工決斷。否則,Git將取相對於基準(base)變化的那個為最終結果。 >merge後有"小耳朵"? https://gitbook.tw/chapters/branch/merge-commit.html ### 遇到衝突時的分支合併 有時候在做分支合併時並不會那麼順利。如果在不同的分支中都修改了同一個檔案的同一部分,Git就無法直接將兩者合併在一起,需要由我們來決定。以下假設在合併時出現了衝突。 ``` $ git merge iss53 Auto-merging index.html CONFLICT (content): Merge conflict in index.html Automatic merge failed; fix conflicts and then commit the result. ``` Git做了合併,但沒有提交,它會停下來等你解決衝突。所以我們要來查看哪些檔在合併時發生衝突,可以使用 ==git status== 指令來了解。 ``` $ git status On branch master You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git add <file>..." to mark resolution) both modified: index.html no changes added to commit (use "git add" and/or "git commit -a") ``` 任何包含未解決衝突的檔案都會以未合併(unmerged)的狀態列出。Git會在有衝突的地方加入標準的衝突解決標記。 ``` <<<<<<< HEAD <div id="footer">contact : email.support@github.com</div> ======= <div id="footer"> please contact us at support@github.com </div> >>>>>>> iss53 ``` 在=====隔開的上半部分是HEAD(即master分支的內容,在執行merge時所在的分支)中的內容,下半部分則是iss53分支的內容。在解決了所有檔裡的所有衝突後,執行 ==git add== 會將把它們標記為已解決狀態,且保存到暫存區。最後,我們可以再執行一次 ==git status== 來確認所有衝突已解決。 ``` $ git status On branch master Changes to be committed: (use "git reset HEAD <file>..." to unstage) modified: index.html ``` ## 分支的衍合(rebase) ![](https://git-scm.com/figures/18333fig0327-tn.png) 如果現在想要將experiment分支合併master分支,除了上面提到的merge,還有一個方法叫做 ==衍合(rebase)== ,它的操作方式是將C3裡產生的變化修補在C4上再做一次。 ``` $ git checkout experiment $ git rebase master First, rewinding head to replay your work on top of it... Applying: added staged command ``` 它的原理是回到兩個分支最近的共同祖先(C2),根據目前分支(要進行衍合的分支 experiment)後續的歷次提交物件(這裡只有一個 C3),生成一系列檔修補檔,然後以基底分支(主幹分支 master)最後一個提交物件(C4)為新的出發點,逐個應用之前準備好的修補檔檔,最後會生成一個新的合併提交物件(C3'),從而改寫 experiment 的提交歷史,使它成為 master 分支的直接下游。 ![](https://git-scm.com/figures/18333fig0329-tn.png) > git rebase -\-onto? https://git-scm.com/book/zh-tw/v1/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E8%A1%8D%E5%90%88 ## 標籤 (tag) Git具備在特定時間點加入標籤去註明其重要性的功能。一般而言,我們會使用這個功能去標記出發行版本(如V1.0等等)。 ### 列出標籤 ``` $ git tag ``` 直接輸入 ==git tag== 就可以列出目前所有的標籤。也可以用特定的字串規則去搜尋標籤。例如你只對1.4.2感興趣時,你可以執行已下指令: ``` $ git tag -l 'v1.4.2.*' v1.4.2.1 v1.4.2.2 v1.4.2.3 v1.4.2.4 ``` ### 建立標籤 與 查看標籤資料 在Git裡,標籤的建立有分兩種,==輕量級(lightweight)== 和 ==含附註(annotated)==。 - 輕量級(lightweight) ``` $ git tag v1.4-lw $ git tag v0.1 v1.3 v1.4 v1.4-lw v1.5 ``` 此標籤只保存commit檢查碼,在建立時不必下任何參數,直接設定標籤名稱即可。在執行 ==git show== 的時候,也不會看到其他標籤資訊,只會顯示對應的commit。 ``` $ git show v1.4-lw commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon <schacon@gee-mail.com> Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment' ``` - 含附註(annotated) ``` $ git tag -a v1.4 -m 'my version 1.4' $ git tag v0.1 v1.3 v1.4 ``` 建立含附註的標籤,我們需要多加入一個參數 ==-a== 。而參數 ==-m== 用來設定標籤訊息。如果沒有設定該訊息,Git會啟動文字編輯器來輸入。 ``` $ git show v1.4 tag v1.4 Tagger: Scott Chacon <schacon@gee-mail.com> Date: Mon Feb 9 14:45:11 2009 -0800 my version 1.4 commit 15027957951b64cf874c3557a0f3547bd83b3ff6 Merge: 4a447f7... a6b4c97... Author: Scott Chacon <schacon@gee-mail.com> Date: Sun Feb 8 19:02:46 2009 -0800 Merge branch 'experiment' ``` 一樣透過 ==git show== 指令,我們可以看到commit資訊前,多了這個標籤的設定者資訊,下標籤時間與附註訊息。 ### 追加標籤 對於過去的commit,我們也可以加上標籤。只需要該次commit的SHA-1值(或前幾碼即可),輸入以下指令即可。 ``` $ git tag -a v1.2 9fceb02 ``` ### 分享(上傳)標籤 ``` $ git push origin v1.5 Counting objects: 50, done. Compressing objects: 100% (38/38), done. Writing objects: 100% (44/44), 4.56 KiB, done. Total 44 (delta 18), reused 8 (delta 1) To git@github.com:schacon/simplegit.git * [new tag] v1.5 -> v1.5 ``` 在預設情況下, ==git push== 並不會將標籤傳到遠端伺服器上。在建立新的標籤後,我們需要透過 ==git push origin [tagname]== 指令來將標籤推送到Repository上。 ``` $ git push origin --tags Counting objects: 50, done. Compressing objects: 100% (38/38), done. Writing objects: 100% (44/44), 4.56 KiB, done. Total 44 (delta 18), reused 8 (delta 1) To git@github.com:schacon/simplegit.git * [new tag] v0.1 -> v0.1 * [new tag] v1.2 -> v1.2 * [new tag] v1.4 -> v1.4 * [new tag] v1.4-lw -> v1.4-lw * [new tag] v1.5 -> v1.5 ``` 如果有很多標籤需要一次推送上去,可以加入 ==-\-tag== 參數,就可以達到此目的。 而當其他使用者clone或pull你的版本控制倉庫的時候,他們也會取得你所有的標籤。 ## 遠端 ### 列出遠端版本控制倉庫 ``` $ git remote ``` 它會列出當初加入遠端版本控制倉庫時指定的名稱。 若目前所在版本控制倉庫是從其它版本控制倉庫複製過來的,至少應該看到 origin,也就是 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. $ cd ticgit $ git remote origin ``` 也可以再後面加上參數 ==-v== ,便會在名稱後方顯示其URL ``` $ git remote -v origin git://github.com/schacon/ticgit.git (fetch) origin git://github.com/schacon/ticgit.git (push) ``` ### 新增 若想要新增遠端版本控制並取一個名字,可以執行 ==git remote add [name] [url]== 即可: ``` $ git remote origin $ git remote add pb git://github.com/paulboone/ticgit.git $ git remote -v origin git://github.com/schacon/ticgit.git pb git://github.com/paulboone/ticgit.git ``` 可看到命令列中的 pb 字串取代了整個 URL。 ### 刪除遠端 ``` $ git remote rm [repository] ``` ### 上傳 ``` $ git push [repository] [分支] ``` 執行以上指令,可以將檔案推送到遠端數據庫的某一個分支。 ``` $ git push [repository] [本地分支]:[遠端分支] ``` 我們也可以指定要從本地端的哪個分支傳到遠端的哪個分支 ### 擷取遠端分支 ``` $ git fetch [repository] [分支] ``` 如果想要確認遠端數據庫的修改內容,但不想合併內容到本地端數據庫,可以使用 ==git fetch== ,不會修改到本地端的分支。 ### 合併遠端分支 ``` $ git pull [repository] [分支] ``` 如果想要將遠端的檔案直接合併到本地端來,那麼可以直接使用 ==git pull== 。 - pull = fetch + merge!!! ### 刪除遠端分支 ``` $ git push origin :test ``` ### 監看 取得詳細資料 我們可以利用 ==git remote show== 來得到遠端版本控制倉庫的更多資訊。 ``` $ git remote show origin * remote origin URL: git@github.com:defunkt/github.git Remote branch merged with 'git pull' while on branch issues issues Remote branch merged with 'git pull' while on branch master master New remote branches (next fetch will store in remotes/origin) caching Stale tracking branches (use 'git remote prune') libwalker walker2 Tracked remote branches acl apiv2 dashboard2 issues master postgres Local branch pushed with 'git push' master:master ``` 在最後兩行,顯示了當你執行 ==git push== 時會自動推送到哪個分支。也會顯示哪些遠端分支還沒被同步到本地端(在這個例子是caching),哪些已同步到本地的遠端分支在遠端已被刪除(libwalker和walker2),以及當執行git pull時會自動被合併的分支。 ## cherry-pick ![](https://git-scm.com/figures/18333fig0526-tn.png) 如果想要挑選出某個版本中的修改,引入到目前工作的版本中,就可以使用cherry-pick指令。只需要找出該版本的SHA-1值,並輸入指令即可。例如,我想要拉取e43a6到主幹分支來,就輸入以下即可。 ``` $ git cherry-pick e43a6fd3e94888d76779ad79fb568ed180e5fcdf Finished one cherry-pick. [master]: created a0a41a9: "More friendly message when locking the index fails." 3 files changed, 17 insertions(+), 3 deletions(-) ``` ![](https://git-scm.com/figures/18333fig0527-tn.png) ## 回到之前的Commit ``` $ git log --oneline e12d8ef (HEAD -> master) add database.yml in config folder 85e7e30 add hello 657fce7 add container abb4f43 update index page cef6e40 create index page cc797cd init commit ``` 假設目前的 ==git log== 紀錄如上 ### 相對做法 若想要前往前一次的提交,可以輸入以下指令: ``` $ git reset e12d8ef^ $ git reset master^ $ git reset HEAD^ ``` - 最後面的 ^ 符號表示「前一次」的意思,所以 e12d8ef^ 是指在 e12d8ef 這個 Commit 的「前一次」,如果是 e12d8ef^^ 則是往前兩次,以此類推。 ### 絕對做法 如果很明確地要退回到哪個Commit的地方,也可以直接輸入那個Commit的SHA-1值。 ``` $ git reset 85e7e30 ``` ### reset模式 git reset的指令可以根據搭配的參數分成三種指令。 - mixed模式 ``` $ git reset master^ ``` -/-mixed 是預設的參數,如果沒有特別加參數,git reset 指令將會使用 --mixed 模式。這個模式會把暫存區的檔案丟掉,但不會動到工作目錄的檔案,也就是說 Commit 拆出來的檔案會留在工作目錄,但不會留在暫存區。 - soft模式 ``` $ git reset master^ --soft ``` 這個模式下的 reset,工作目錄跟暫存區的檔案都不會被丟掉,所以看起來就只有 HEAD 的移動而已。也因此,Commit 拆出來的檔案會直接放在暫存區。 - hard模式 ``` $ git reset master^ --hard ``` 在這個模式下,不管是工作目錄以及暫存區的檔案都會丟掉。 |模式 |mixed 模式(預設)|soft 模式|hard 模式| |-----|-----|-----|-----| |Commit 拆出來的檔案|丟回工作目錄|丟回暫存區|直接丟掉| >在git裡,reset不等於重新設定!! https://gitbook.tw/chapters/using-git/reset-commit.html ## 查詢程式碼是誰修改的!! 想要知道某個檔案的某一行是誰寫的嗎?在 Git 可使用 git blame 指令幫你抓出兇手: ``` $ git blame index.html abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 1) <!DOCTYPE html> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 2) <html> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 3) <head> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 4) <meta charset="utf-8"> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 5) <title>首頁</title> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 6) </head> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 7) <body> 657fce78 (Eddie Kao 2017-08-02 16:53:43 +0800 8) <div class="container"> 657fce78 (Eddie Kao 2017-08-02 16:53:43 +0800 9) </div> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 10) </body> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 11) </html> ``` 這樣可以很清楚的看得出來哪一行是誰在什麼時候寫的,而最前面看起來像亂碼的文字,正是每次 Commit 的識別代碼,表示這一行是在哪一次的 Commit 裡。 如果檔案太大,也可以加上 ==-L== 參數來指定行數。 ``` $ git blame -L 5,10 index.html abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 5) <title>首頁</title> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 6) </head> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 7) <body> 657fce78 (Eddie Kao 2017-08-02 16:53:43 +0800 8) <div class="container"> 657fce78 (Eddie Kao 2017-08-02 16:53:43 +0800 9) </div> abb4f438 (Eddie Kao 2017-08-02 16:49:49 +0800 10) </body> ``` ## 儲藏目前工作(stash) >除了stash還有別的方法!! https://gitbook.tw/chapters/faq/stash.html ## 查看分支/HEAD的移動紀錄 ``` $ git reflog 08084a5 HEAD@{0}: commit: 添加pull的說明 99daed2 HEAD@{1}: commit: 添加commit的說明 48eec1d HEAD@{2}: checkout: moving from master to issue1 326fc9f HEAD@{3}: commit: 添加add的說明 48eec1d HEAD@{4}: commit (initial): first commit ``` 可以查看過去HEAD所指向過的提交清單。也會顯示已刪除的提交以及藉由rebase等所合併的提交。 ``` $ git reflog master ``` 如果在後面加上分支名,也可以查看過去此分支所指向過的提交清單。一樣也會顯已刪除的提交以及藉由rebase等所合併的提交。 ## 打包檔案(patch) > http://chris800731.blogspot.com/2013/08/git-format-patch-commit-use-format.html