1、起步
1.1 版本控制
版本控制是一種記錄一個或若干文件內容變化,以便將來查閱特定版本修訂情況的系統。 版本控制系統(VCS,Version Control System)通常可以分為三類:
- 本地版本控制系統:大多都是采用某種簡單的數據庫來記錄文件的歷次更新差異。最流行的一種叫做 RCS,其工作原理是在硬盤上保存補丁集(補丁是指文件修訂前后的變化);通過應用所有的補丁,可以重新計算出各個版本的文件內容。
- 集中化的版本控制系統(CVCS,Centralized Version Control Systems):解決了在不同系統上的開發者可以協同工作的問題。諸如 CVS、Subversion 以及 Perforce 等,都有一個單一的集中管理的服務器,保存所有文件的修訂版本,而協同工作的人們都通過客戶端連到這臺服務器,取出最新的文件或者提交更新。缺點是中央服務器的單點故障會使得開發者幾乎無法做任何版本控制的任務,并且如果中心數據庫所在的磁盤發生損壞,又沒有做恰當備份,將丟失包括項目的整個變更歷史在內的所有數據
- 分布式版本控制系統(DVCS,Distributed Version Control System):典型的 DVCS 有 Git、Mercurial 以及 Darcs 等,客戶端將代碼倉庫完整地鏡像下來,而不只是提取最新版本的文件快照。這樣,任意協同工作用的服務器發生故障,都可以用任何一個鏡像出來的本地倉庫恢復。
1.2 Git 簡介
Git 與其他版本控制系統(包括 Subversion 和近似工具)的重要差別在于 Git 對待數據的方式。 像 CVS、Subversion、Perforce 等等基于差異(delta-based)的版本控制是以文件變更列表的方式存儲信息,將這些信息看作是一組基本文件和每個文件隨時間逐步累積的差異:
而 Git 則把數據看作是對小型文件系統的一系列快照。當提交或更新文件時,Git 會對當時的全部文件創建一個快照并保存這個快照的索引。 如果文件沒有修改,不會重新存儲該文件而是保留一個連接指向之前的文件。因此 Git 對待數據更像是一個快照流:
除此之外,Git 相比于 CVCS 還有一大優點就是近乎所有操作都是本地執行,因為你在本地磁盤上就有項目的完整歷史,所以大部分操作看起來瞬間完成。比如本地數據庫已經保存了完整的項目歷史,因此你無需外連服務器就訪問它們。還有當你沒有網絡時也可以提交項目代碼(到本地副本),直到有網絡之后再上傳到服務器。而像 Perforce 如果沒有網絡就無法連接到服務器,也就幾乎不能做什么事;而 用 Subversion 和 CVS 雖然能修改文件,但由于本地數據庫離線是不能向數據庫提交代碼的。
Git 通過 SHA-1 哈希機制計算校驗和保證存儲數據的完整性。即你只要修改了文件或目錄,就會生成對應的通過 SHA-1 計算出來的 40 位哈希值,如果在傳輸過程中丟失信息或損壞文件,這個哈希值的校驗會失敗。Git 數據庫保存的信息都是以文件內容的哈希值來索引,而不是文件名:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 一般只添加數據,很難使用 Git 從數據庫中刪除數據,它幾乎不會執行任何可能導致文件不可恢復的操作,這樣能盡可能地避免數據丟失。
在 Git 中,文件有三種狀態:
- 已修改(modified):修改了文件,但還沒保存到數據庫中
- 已暫存(staged):對一個已修改文件的當前版本做了標記,使之包含在下次提交的快照中
- 已提交(committed):數據已經安全地保存在本地數據庫中
三種狀態以及切換圖例:
工作區存放的是對項目中的某個版本(不一定是最新版本,因為你可以指定基于過往的某一個版本),從 Git 數據庫的壓縮數據庫中獨立提取出的文件,放在磁盤上供我們使用或修改。
暫存區是一個文件,保存了下次將要提交的文件列表信息。
Git 倉庫目錄用于保存項目的元數據和對象數據庫,從其他計算機克隆倉庫時,復制的就是這里的數據。
基本的 Git 工作流程如下:
- 在工作區中修改文件。
- 將你想要下次提交的更改選擇性地暫存,這樣只會將更改的部分添加到暫存區。
- 提交更新,找到暫存區的文件,將快照永久性存儲到 Git 目錄。
1.3 初次運行 Git 前的配置
初次運行 Git 前,通常需要對用戶信息以及文本編輯器進行配置,其中必須配置用戶信息中的用戶名與郵箱,因為每一個提交都要用到這些信息。
通過 Git 自帶的 git config
工具配置外觀和行為變量,這些變量可以存在于三個層次的配置文件中,級別由高到低依次為:
- 系統上所有用戶與倉庫的配置文件
/etc/gitconfig
:執行git config
時帶上--system
選項就會讀寫該文件中的配置變量 - 當前用戶的配置文件
~/.gitconfig
或~/.config/git/config
:傳遞--global
選項讀寫此文件,對當前用戶的所有倉庫生效 - 當前使用的倉庫的 Git 目錄中的 config 文件
.git/config
:傳遞--local
選項,當然作為默認選項不傳也是修改該文件,僅對當前
對于相同的配置變量,低級別的配置會覆蓋高級別的配置。對于用戶名以及郵箱配置,使用 --global
選項即可:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
--global
的命令只需運行一次,后續在該系統上執行其他 Git 操作時都會帶上這些信息。
此外,如果你不習慣 Git 自帶的文本編輯器,也可以通過 git config
配置,比如想使用 Emacs:
$ git config --global core.editor emacs
Windows 系統則需要指定可執行文件的完整路徑,例如 Sublime Text:
git config --global core.editor "'C:\Program Files\Sublime Text 3\sublime_text.exe' -w"
每個可執行文件路徑后還需要加若干參數,不同的編輯器參數不同,具體需要參考 A3.1 附錄 C: Git 命令 - 設置與配置。
配置完成后可以通過 git config --list
列出當前所有配置:
$ git config --list
user.name=John Doe
user.email=johndoe@example.com
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...
有時你會看到多個同名變量,這是因為前面提到的三個級別的配置文件中可能保存了同名變量,這種情況下 Git 會使用每個變量的最后一個配置,你也可以使用傳入 --show-origin
選項列出每個配置所在的文件:
$ git config --list --show-origin
file:D:/Program Files/Git/etc/gitconfig http.sslcainfo=D:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
file:D:/Program Files/Git/etc/gitconfig http.sslbackend=openssl
file:D:/Program Files/Git/etc/gitconfig diff.astextplain.textconv=astextplain
file:D:/Program Files/Git/etc/gitconfig core.autocrlf=true
file:D:/Program Files/Git/etc/gitconfig core.fscache=true
file:D:/Program Files/Git/etc/gitconfig core.symlinks=false
file:D:/Program Files/Git/etc/gitconfig credential.helper=manager
file:C:/Users/Administrator/.gitconfig user.name=John Doe
file:C:/Users/Administrator/.gitconfig user.email=John Doe@example.com
file:C:/Users/Administrator/.gitconfig https.proxy=https://127.0.0.1:7890
file:C:/Users/Administrator/.gitconfig http.proxy=http://127.0.0.1:7890
file:.git/config core.repositoryformatversion=0
file:.git/config core.filemode=false
file:.git/config core.bare=false
file:.git/config core.logallrefupdates=true
file:.git/config core.symlinks=false
file:.git/config core.ignorecase=true
file:.git/config core.sparsecheckout=true
能清楚的看出,系統級的配置文件 /etc/gitconfig
是在 Git 的安裝目錄下,而用戶級的配置文件 .gitconfig
是在用戶的個人目錄下,而倉庫級的配置文件 .git/config
就在當前倉庫的目錄下。
2、Git 基礎
2.1 獲取 Git 倉庫
通常有兩種獲取 Git 項目倉庫的方式:
- 將尚未進行版本控制的本地目錄轉換為 Git 倉庫;
- 從其他服務器克隆一個已存在的 Git 倉庫。
在已存在目錄中初始化倉庫
切換到項目目錄后執行 git init
命令:
$ cd /c/user/my_project
$ git init
執行該命令后會在當前目錄中創建一個 .git
目錄,該目錄包含初始化的 Git 倉庫的所有必須文件。但此時項目中的文件還沒有被 Git 跟蹤(track),需要進行初始化提交:
$ git add *.c
$ git add LICENSE
$ git commit -m 'initial project version'
通過 git commit
將項目文件提交到 Git 后才得到一個存在被追蹤文件且完成了初始化的 Git 倉庫。
克隆現有的倉庫
git clone
命令可以拷貝 Git 倉庫服務器上幾乎所有數據,而不僅僅是復制你工作所需要的文件。默認配置下,遠程 Git 倉庫中的每一個文件的每一個版本都將被拉取下來。
比如要克隆 Git 的鏈接庫 libgit2
,可以通過 git clone <url>
的方式:
$ git clone https://github.com/libgit2/libgit2
這會在當前目錄下創建一個名為 “libgit2” 的目錄,并在這個目錄下初始化一個 .git
文件夾, 從遠程倉庫拉取下所有數據放入 .git
文件夾,然后從中讀取最新版本的文件的拷貝。
如果你想在克隆遠程倉庫的時候,自定義本地倉庫的名字,你可以通過額外的參數指定新的目錄名:
$ git clone https://github.com/libgit2/libgit2 mylibgit
這會執行與上一條命令相同的操作,但目標目錄名變為了 mylibgit
。
2.2 記錄每次更新到倉庫
先來看文件的狀態變化周期:
工作目錄中的每一個文件都不外乎已跟蹤或未跟蹤兩種狀態:
- 已跟蹤的文件是指那些被納入了版本控制的文件,在上一次快照中有它們的記錄,在工作一段時間后, 它們的狀態可能是未修改,已修改或已暫存。簡而言之,已跟蹤的文件就是 Git 已經知道的文件。
- 工作目錄中除已跟蹤文件外的其它所有文件都屬于未跟蹤文件,它們既不存在于上次快照的記錄中,也沒有被放入暫存區。
工作目錄中新創建的文件是未跟蹤狀態,通過 git add
將其添加到暫存區變成已暫存狀態后,該文件才變成已跟蹤狀態。已暫存文件通過 git commit
提交到 Git 本地倉庫后會變為未修改狀態。已經提交到 Git 倉庫中的文件,可以進行兩種操作:
- 如果需要刪除某個文件,可以從倉庫中移除該文件使其變為未跟蹤狀態
- 如果需要編輯某個文件,可以從倉庫中取出該文件進行編輯,使其變為已修改狀態。已修改狀態的文件可以通過
git add
將其暫存
檢查當前文件狀態
使用 git status
可以查看文件狀態,這里有很多種情況,比如在克隆倉庫后立即使用此命令:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean
說明當前工作目錄相當干凈,也就是已跟蹤文件在上次提交后都未被更改過。
在項目下新創建 README 文件,然后使用 git status
能查看到該文件未被跟蹤:
$ echo 'My Project' > README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:(use "git add <file>..." to include in what will be committed)READMEnothing added to commit but untracked files present (use "git add" to track)
未跟蹤的文件意味著 Git 在之前的快照(提交)中沒有這些文件;Git 不會自動將之納入跟蹤范圍,需要通過命令明確告知 Git 要跟蹤該文件。
跟蹤新文件
可以使用 git add
開始跟蹤一個新文件:
$ git add README
再運行 git status
會看到 README
文件已被跟蹤,并處于暫存狀態:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:(use "git restore --staged <file>..." to unstage)new file: README
暫存已修改的文件
修改已被跟蹤的文件 CONTRIBUTING.md
,再運行 git status
命令:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEChanges 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: CONTRIBUTING.md
說明 CONTRIBUTING.md
還未被放入暫存區,此時需要使用 git add
將其放入暫存區。git add
是一個多功能命令,它可以:
- 開始跟蹤新文件
- 把已跟蹤的文件放到暫存區
- 用于合并時把有沖突的文件標記為已解決狀態
使用該命令后,再用 git status
查看文件狀態:
$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEmodified: CONTRIBUTING.md
假如此時再在 CONTRIBUTING.md
里再加條注釋,存盤后再運行 git status
:
$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:(use "git reset HEAD <file>..." to unstage)new file: READMEmodified: CONTRIBUTING.mdChanges 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: CONTRIBUTING.md
可以看到 CONTRIBUTING.md
同時出現在暫存區和非暫存區, 暫存區保存的是上一次運行了 git add
后的版本,即沒添加注釋的老版本;非暫存區的則是剛剛新添加了注釋的版本,因此若想把新的修改添加到暫存區,需要再次運行 git add
。
狀態簡覽
使用 git status -s
或 git status --short
會得到格式更為緊湊的輸出:
$ git status -sM README # 在工作區已修改但尚未暫存
MM Rakefile # 已修改,暫存后又作了修改
A lib/git.rb # 新添加到暫存區中的文件
M lib/simplegit.rb # 文件已修改且已暫存
?? LICENSE.txt # 新添加的未跟蹤文件
狀態標記實際上有兩位,帶有 M
的就表示文件已修改,但第一位的 M
與第二位的 M
與 MM
的含義又進行了細分。
忽略文件
通常,項目中那些自動生成的文件,如日志文件、編譯過程中創建的臨時文件等無需納入 Git 管理,也不希望它們出現在文件跟蹤列表中,因此要在 .gitignore
文件中列出要忽略的文件的模式,如:
$ cat .gitignore
*.[oa]
*~
文件 .gitignore
的格式規范如下:
- 所有空行或者以
#
開頭的行都會被 Git 忽略。 - 可以使用標準的 glob 模式匹配,它會遞歸地應用在整個工作區中。
- 匹配模式可以以(
/
)開頭防止遞歸。 - 匹配模式可以以(
/
)結尾指定目錄。 - 要忽略指定模式以外的文件或目錄,可以在模式前加上嘆號(
!
)取反。
更多實例:
# 忽略所有的 .a 文件
*.a# 但跟蹤所有的 lib.a,即便你在前面忽略了 .a 文件
!lib.a# 只忽略當前目錄下的 TODO 文件,而不忽略 subdir/TODO
/TODO# 忽略任何目錄下名為 build 的文件夾
build/# 忽略 doc/notes.txt,但不忽略 doc/server/arch.txt
doc/*.txt# 忽略 doc/ 目錄及其所有子目錄下的 .pdf 文件
doc/**/*.pdf
GitHub 有一個十分詳細的針對數十種項目及語言的
.gitignore
文件列表, 你可以在 https://github.com/github/gitignore 找到它。
倉庫根目錄下的 .gitignore
可以遞歸應用到整個倉庫,子目錄中的 .gitignore
只對其所在目錄有效。
查看已暫存和未暫存的修改
git diff
比較的是工作目錄中當前文件和暫存區域快照之間的差異,也就是修改之后還沒有暫存起來的變化內容。git diff --staged
比對已暫存文件與最后一次提交的文件差異。
當然,也可以使用圖形化工具或外部 diff 工具來比較差異。
提交更新
提交前務必確認是否有修改或新建的文件還沒有 git add
過,否則提交的時候不會記錄這些尚未暫存的變化。 這些已修改但未暫存的文件只會保留在本地磁盤。所以,每次準備提交前,先用 git status
看下,你所需要的文件是不是都已暫存起來了, 然后再運行提交命令 git commit
。
運行 git commit
命令后,會啟動文本編輯器來輸入提交說明,這里以默認的 Vim 為例:
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
# new file: README
# modified: CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C
另外,你也可以在 commit
命令后添加 -m
選項,將提交信息與命令放在同一行:
$ git commit -m "Story 182: Fix benchmarks for speed"
[master 463dc4f] Story 182: Fix benchmarks for speed2 files changed, 2 insertions(+)create mode 100644 README
跳過使用暫存區域
使用暫存區有時略顯繁瑣,因此可以使用 git commit -a
略過 git add
,自動把所有已經跟蹤過的文件暫存起來一并提交,但是要小心,有時這個選項會將不需要的文件添加到提交中。
移除文件
git rm
用于移除文件,但根據文件所處的不同位置與狀態有不同的操作參數與方式。
直接從倉庫中刪除文件,并連帶從工作目錄中刪除該文件,直接使用 git rm
后再提交即可:
Administrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ git rm readme.txt
rm 'readme.txt'Administrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ git status
On branch master
Changes to be committed:(use "git restore --staged <file>..." to unstage)deleted: readme.txtAdministrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ git commit -m 'remove readme'
[master 3d88e2d] remove readme1 file changed, 1 deletion(-)delete mode 100644 readme.txt
如果你只想刪除倉庫中的文件,但保留工作目錄中的文件,即想讓文件保留在磁盤,但是并不想讓 Git 繼續跟蹤(當你忘記添加 .gitignore
文件,不小心把一個很大的日志文件或一堆 .a
這樣的編譯生成文件添加到暫存區時,這一做法尤其有用。 ),此時使用 --cached
選項:
Administrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ git rm --cached readme.txt
rm 'readme.txt'Administrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ git status
On branch master
Changes to be committed:(use "git restore --staged <file>..." to unstage)deleted: readme.txtUntracked files:(use "git add <file>..." to include in what will be committed)readme.txtAdministrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ git commit -m 'remove readme'
[master c2cd2b7] remove readme1 file changed, 1 deletion(-)delete mode 100644 readme.txtAdministrator@Frank MINGW64 /f/Code/git/git-learning (master)
$ ls -al
total 9
drwxr-xr-x 1 Administrator 197108 0 7月 9 11:50 ./
drwxr-xr-x 1 Administrator 197108 0 7月 8 17:31 ../
drwxr-xr-x 1 Administrator 197108 0 7月 9 11:52 .git/
-rw-r--r-- 1 Administrator 197108 16 7月 9 11:50 readme.txt
可以看到使用 --cached
選項后,確實會移除倉庫中的文件,同時在未跟蹤區保留了該文件,commit 后本地文件中仍有 readme.txt,但倉庫中的 readme.txt 被刪除了。
以上說的是文件被跟蹤但未修改的情況。假如被跟蹤的文件被修改了或者已經添加到暫存區了,需要使用強制刪除選項 -f
(譯注:即 force 的首字母), 這是一種安全特性,用于防止誤刪尚未添加到快照(提交)的數據,這樣的數據不能被 Git 恢復。
刪除也可以使用
glob
模式。比如刪除log/
目錄下擴展名為.log
的所有文件。:git rm log/\*.log
移動文件
git mv
可以移動文件,也可以用于修改文件名:
$ git mv file_from file_to
重命名示例:
$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:(use "git reset HEAD <file>..." to unstage)renamed: README.md -> README
git rm
相當于運行了下面三條命令:
$ mv README.md README
$ git rm README.md
$ git add README
2.3 查看提交歷史
git log
會按時間先后順序列出所有的提交,最近的更新排在最上面:
$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700changed the version numbercommit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700removed unnecessary testcommit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700first commit
這個命令會列出每個提交的 SHA-1 校驗和、作者的名字和電子郵件地址、提交時間以及提交說明。
顯示提交引入的差異
使用 -p
或 --patch
會顯示每次提交所引入的差異,結合 -<number>
或 -n <number>
限制顯示的提交數量,可以在進行代碼審查時快速瀏覽提交帶來的變化:
$ git log -p -2
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700changed the version numberdiff --git a/Rakefile b/Rakefile
index a874b73..8f94139 100644
--- a/Rakefile
+++ b/Rakefile
@@ -5,7 +5,7 @@ require 'rake/gempackagetask'spec = Gem::Specification.new do |s|s.platform = Gem::Platform::RUBYs.name = "simplegit"
- s.version = "0.1.0"
+ s.version = "0.1.1"s.author = "Scott Chacon"s.email = "schacon@gee-mail.com"s.summary = "A simple gem for using Git in Ruby code."commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700removed unnecessary testdiff --git a/lib/simplegit.rb b/lib/simplegit.rb
index a0a60ae..47c6340 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -18,8 +18,3 @@ class SimpleGitendend
-
-if $0 == __FILE__
- git = SimpleGit.new
- puts git.show
-end
顯示簡略統計信息
使用 --stat
選項,在每次提交的下面列出所有被修改過的文件、有多少文件被修改了以及被修改過的文件的哪些行被移除或是添加了。 在每次提交的最后還有一個總結:
$ git log --stat
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700changed the version numberRakefile | 2 +-1 file changed, 1 insertion(+), 1 deletion(-)commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700removed unnecessary testlib/simplegit.rb | 5 -----1 file changed, 5 deletions(-)commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700first commitREADME | 6 ++++++Rakefile | 23 +++++++++++++++++++++++lib/simplegit.rb | 25 +++++++++++++++++++++++++3 files changed, 54 insertions(+)
定制輸出格式
使用 --pretty
選項可以使用非默認格式展示提交歷史,它有多個子選項,比如 oneline
會將每個提交放在一行顯示,在瀏覽大量的提交時非常有用:
$ git log --pretty=oneline
ca82a6dff817ec66f44342007202690a93763949 changed the version number
085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7 removed unnecessary test
a11bef06a3f659402fe7563abf99ad00de2209e6 first commit
類似的子選項有 short
,full
和 fuller
,它們展示信息的格式基本一致,只是詳盡程度不一。
簡寫為
git log --oneline
也可以。
format
子選項可以定制顯示格式,對后期提取分析格外有用:
$ git log --pretty=format:"%h - %an, %ar : %s"
ca82a6d - Scott Chacon, 6 years ago : changed the version number
085bb3b - Scott Chacon, 6 years ago : removed unnecessary test
a11bef0 - Scott Chacon, 6 years ago : first commit
它的常用選項可以參考 –pretty=format 的常用選項。
--graph
添加了一些 ASCII 字符串來形象地展示你的分支、合并歷史,與 oneline
或 format
與 --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
限制輸出長度
前面提到的 -<number>
或 -n <number>
算是一種,不過實踐中用的不多,因為 Git 默認會將所有的輸出傳送到分頁程序中,一次只會看到一頁的內容。
類似 --since
和 --until
這種按照時間作限制的選項很有用,比如列出最近兩周的所有提交:
$ git log --since=2.weeks
該命令可用的格式十分豐富——可以是類似 "2008-01-15"
的具體的某一天,也可以是類似 "2 years 1 day 3 minutes ago"
的相對日期。
此外,還可以過濾出匹配指定條件的提交。 用 --author
選項顯示指定作者的提交,用 --grep
選項搜索提交說明中的關鍵字。
還有一個 -S
過濾器,接收字符串參數,只顯示添加或刪除了該字符串的提交。假設你想找出添加或刪除了對某一個特定函數的引用的提交,可以調用:
$ git log -S function_name
如果只關心某些文件或者目錄的歷史提交,使用 --path
選項。
最后再說一個 --no-merges
,不顯示合并提交以免弄亂歷史記錄。一個綜合例子,如果要在 Git 源碼庫中查看 Junio Hamano 在 2008 年 10 月其間, 除了合并提交之外的哪一個提交修改了測試文件,可以使用下面的命令:
$ git log --pretty="%h - %s" --author='Junio C Hamano' --since="2008-10-01" \--before="2008-11-01" --no-merges -- t/
5610e3b - Fix testcase failure when extended attributes are in use
acd3b9e - Enhance hold_lock_file_for_{update,append}() API
f563754 - demonstrate breakage of detached checkout with symbolic link HEAD
d1a43f2 - reset --hard/read-tree --reset -u: remove unmerged new paths
51a94af - Fix "checkout --track -b newbranch" on detached HEAD
b0ad11e - pull: allow "git pull origin $something:$current_branch" into an unborn branch
在近 4 萬條提交中,輸出了 6 條符合條件的記錄。
git log 常用選項
選項 | 說明 |
---|---|
-p | 按補丁格式顯示每個提交引入的差異。 |
--stat | 顯示每次提交的文件修改統計信息。 |
--shortstat | 只顯示 --stat 中最后的行數修改添加移除統計。 |
--name-only | 僅在提交信息后顯示已修改的文件清單。 |
--name-status | 顯示新增、修改、刪除的文件清單。 |
--abbrev-commit | 僅顯示 SHA-1 校驗和所有 40 個字符中的前幾個字符。 |
--relative-date | 使用較短的相對時間而不是完整格式顯示日期(比如“2 weeks ago”)。 |
--graph | 在日志旁以 ASCII 圖形顯示分支與合并歷史。 |
--pretty | 使用其他格式顯示歷史提交信息。可用的選項包括 oneline、short、full、fuller 和 format(用來定義自己的格式)。 |
--oneline | --pretty=oneline --abbrev-commit 合用的簡寫。 |
使用圖形化工具查看
使用 gitk
命令可以打開一個圖形化界面:
注意在提交記錄中有 Author 與 Committer 兩種身份。Author 是作者,即實際作出修改的人,而提交者 Committer 指的是最后將此工作成果提交到倉庫的人。 比如我修改了一個文件,那么我就是這個提交的作者,但這個提交由我的同事 merge 或者 cherry-pick 到 master 分支提交,那么同事就是提交者。
2.4 撤銷操作
修正提交
有時提交完代碼才發現漏掉了幾個文件沒有添加,或者提交信息寫錯了。 此時,可以運行帶有 --amend
選項的提交命令來重新提交:
git commit --amend
該命令會提交暫存區的文件,并打開文本編輯器讓你“重新”編輯提交信息。如果自上次提交以來還沒有對文件做任何修改(例如,在上次提交后馬上執行了此命令),那么快照(提交)會保持不變,修改的只是提交信息。最終效果就是只有一個提交 —— 第二次提交將代替第一次提交的結果。
amend 是修正的意思,但實際上它不是修正原有的第一次提交,而更像是用第二次提交替換第一次提交,舊提交就像從未存在過一樣,不會出現在倉庫歷史中。它的價值所在就是可以稍微進你最后的提交,不讓小修補弄亂你的倉庫提交歷史。
取消暫存的文件
假如修改了兩個文件想分兩次獨立提交它們,但是意外輸入了 git add *
將兩個文件都暫存了,需要取消其中一個的暫存,此時 git status
會提示你使用 git reset HEAD <file>
:
$ git add *
$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)renamed: README.md -> READMEmodified: CONTRIBUTING.md
使用該命令取消 CONTRIBUTING.md
的暫存:
$ git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)renamed: README.md -> READMEChanges 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: CONTRIBUTING.md
可以看到 CONTRIBUTING.md
變成了已修改但未暫存的狀態。
git reset
是一個危險的命令,如果再加上--hard
選項則更是如此,后續在詳解reset
時會介紹更多。
撤消對文件的修改
git status
給出了不同狀態下的文件應該如何撤銷修改的提示。
對于已修改但未暫存的文件,可以使用 git checkout
丟棄這些在工作目錄下的修改:
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: CONTRIBUTING.md
按照提示執行:
$ git checkout -- CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:(use "git reset HEAD <file>..." to unstage)renamed: README.md -> README
可以看到對 CONTRIBUTING.md
的修改已經被撤消了。
請務必記得
git checkout?—?<file>
是一個危險的命令。Git 會用最近提交的版本覆蓋掉本地文件,本地的修改會消失。在 Git 中,任何已提交的東西幾乎都是可以恢復的,甚至那些被刪除的分支中的提交或使用--amend
選項覆蓋的提交也可以恢復(詳見數據恢復)。然而,任何你未提交的東西丟失后很可能再也找不到了。
2.5 遠程倉庫的使用
查看遠程倉庫
通過 git remote
查看遠程倉庫:
$ git remote
origin
origin 是遠程倉庫服務器的默認名字。你也可以指定選項 -v
,會顯示需要讀寫遠程倉庫的簡寫與其對應的 URL:
$ git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
添加遠程倉庫
前面演示過使用 git clone
自行添加遠程倉庫,實際上還可以通過 git remote add <shortname> <url>
添加一個新的遠程 Git 倉庫,同時指定一個方便使用的簡寫倉庫名:
$ git remote
origin
$ git remote add pb https://github.com/paulboone/ticgit
$ git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
pb https://github.com/paulboone/ticgit (fetch)
pb https://github.com/paulboone/ticgit (push)
然后就可以使用 pb
代替整個 URL:
$ git fetch pb
remote: Counting objects: 43, done.
remote: Compressing objects: 100% (36/36), done.
remote: Total 43 (delta 10), reused 31 (delta 5)
Unpacking objects: 100% (43/43), done.
From https://github.com/paulboone/ticgit* [new branch] master -> pb/master* [new branch] ticgit -> pb/ticgit
從遠程倉庫中抓取與拉取
從遠程倉庫拉取所有本地還沒有的數據:
$ git fetch <remote>
執行完成后,你將會擁有那個遠程倉庫中所有分支的引用,可以隨時合并或查看。
結合實例例子來說,假如你使用 git clone
克隆了一個倉庫,該命令會自動將其添加為遠程倉庫并默認以 “origin” 為簡寫。這樣,git fetch origin
會抓取克隆(或上一次抓取)后新提交的所有內容。但需注意 fetch
命令只會將數據下載到你的本地倉庫,不會自動合并或修改你當前的工作,需要你自己手動完成。
而 git pull
則會自動抓取后合并該遠程分支到當前分支。
推送到遠程倉庫
使用 git push <remote> <branch>
將本地代碼推送到遠程倉庫。假如想將 master
分支推送到 origin
服務器時(克隆時通常會自動幫你設置好那兩個名字):
$ git push origin master
當你擁有該服務器的寫權限,且在你推送之前沒有其他人推送過代碼,你才能成功推送。如果有人先于你推送了代碼,你需要先抓取他們的代碼合并進你的代碼后才可推送你的代碼。
查看某個遠程倉庫
使用 git remote show <remote>
查看遠程倉庫的更多信息:
$ git remote show origin
* remote origin# 遠程倉庫的 URLURL: https://github.com/my-org/complex-projectFetch URL: https://github.com/my-org/complex-projectPush URL: https://github.com/my-org/complex-project# 跟蹤分支:告訴你正處于 master 分支HEAD branch: master# 遠程分支信息Remote branches:master trackeddev-branch trackedmarkdown-strip tracked# issue-43 與 issue-45 后面是 new 表示這兩條分支不在本地issue-43 new (next fetch will store in remotes/origin)issue-45 new (next fetch will store in remotes/origin)# stale 譯為不新鮮的,表示該遠程分支已從遠程服務器上移除了,但你的本地可能還保留(緩存過)# 了這個分支的引用,因此需要 git remote prune 手動清理掉本地緩存的過期分支refs/remotes/origin/issue-11 stale (use 'git remote prune' to remove)# 執行 git pull 時哪些本地分支可以與它跟蹤的遠程分支自動合并Local branches configured for 'git pull':dev-branch merges with remote dev-branchmaster merges with remote master# 在特定的分支上執行 git push 會自動地推送到哪一個遠程分支Local refs configured for 'git push':dev-branch pushes to dev-branch (up to date)markdown-strip pushes to markdown-strip (up to date)master pushes to master (up to date)
遠程倉庫的重命名與移除
使用 git remote rename
來重命名遠程倉庫:
$ git remote rename pb paul
$ git remote
origin
paul
遠程分支名也同樣會跟隨倉庫重命名,比如過去的 pb/master
會變為 paul/master
。
使用 git remote remove
或 git remote rm
刪除遠程倉庫:
$ git remote remove paul
$ git remote
origin
所有和這個遠程倉庫相關的遠程跟蹤分支以及配置信息也會一起被刪除。
2.6 打標簽
給某一個提交打上標簽,以示重要,比如標記發布節點(v1.0
、 v2.0
等等)。
列出標簽
輸入 git tag
(可帶上可選的 -l
選項 --list
)以字母順序列出標簽:
$ git tag
v1.0
v2.0
也可按照特定的模式查找標簽。比如 Git 自身的源代碼倉庫包含標簽的數量超過 500 個,如果只對 1.8.5 系列感興趣,可以運行:
$ git tag -l "v1.8.5*"
v1.8.5
v1.8.5-rc0
v1.8.5-rc1
v1.8.5-rc2
v1.8.5-rc3
v1.8.5.1
v1.8.5.2
v1.8.5.3
v1.8.5.4
v1.8.5.5
當像上面這樣提供了一個匹配標簽名的通配模式時,-l
或 --list
就是強制使用的。
創建標簽
Git 支持兩種標簽:輕量標簽(lightweight)與附注標簽(annotated):
- 輕量標簽只是某個特定提交的引用
- 附注標簽是存儲在 Git 數據庫中的一個完整對象,可以被校驗。它包含打標簽者的名字、電子郵件地址、日期時間, 此外還有一個標簽信息,并且可以使用 GNU Privacy Guard (GPG)簽名并驗證
通常建議創建附注標簽,因為可以擁有更多信息。但如果只想用一個臨時標簽,或者出于某些原因不想保存附注標簽所帶有的信息,也可以使用輕量標簽。
兩種標簽都使用 git tag
命令創建,只不過使用的選項不同。
對于附注標簽而言,最簡單的方式是使用 -a
選項:
$ git tag -a v1.4 -m "my version 1.4"
$ git tag
v0.1
v1.3
v1.4
-m
用于指定存儲在標簽中的信息,如果這里沒有指定,Git 會啟動編輯器要求輸入相關信息(類似于 git commit
時如不指定 -m
會啟動編輯器要求輸入提交描述信息)。
通過 git show <tag version>
可以查看標簽信息和與之對應的提交信息:
$ git show v1.4
tag v1.4
Tagger: Ben Straub <ben@straub.cc>
Date: Sat May 3 20:19:12 2014 -0700my version 1.4commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700changed the version number
而創建輕量標簽本質上是將提交的校驗和存儲到一個文件中,不保存其他任何消息,無需使用 -a
、-s
或 -m
選項,只需要提供標簽名字:
$ git tag v1.4-lw
$ git tag
v1.3
v1.4
v1.4-lw
對輕量標簽使用 git show <tag version>
不會看到額外信息,只有提交信息:
$ git show v1.4-lw
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700changed the version number
后期打標簽
假設提交歷史是這樣的:
$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function
4682c3261057305bdd616e23b64b0857d832627b added a todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme
現在想在 “updated rakefile” 這個提交上補上 v1.2 的標簽,可以通過 -a
選項創建 v1.2 標簽時指定(部分)校驗和:
$ git tag -a v1.2 9fceb02
再查看發現已經在 9fceb02 這個提交上打上了標簽:
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5$ git show v1.2
tag v1.2
Tagger: Scott Chacon <schacon@gee-mail.com>
Date: Mon Feb 9 15:32:16 2009 -0800version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date: Sun Apr 27 20:43:35 2008 -0700updated rakefile
...
共享標簽
默認情況下,git push
命令并不會傳送標簽到遠程倉庫服務器上,需要手動執行 git push origin <tagname>
實現:
$ git push origin v1.5
Counting objects: 14, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (12/12), done.
Writing objects: 100% (14/14), 2.05 KiB | 0 bytes/s, done.
Total 14 (delta 3), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git* [new tag] v1.5 -> v1.5
如果想要一次性推送多個標簽,也可以使用帶有 --tags
選項的 git push
命令。這將會把所有不在遠程倉庫服務器上的標簽全部傳送到那里:
$ git push origin --tags
Counting objects: 1, done.
Writing objects: 100% (1/1), 160 bytes | 0 bytes/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git* [new tag] v1.4 -> v1.4* [new tag] v1.4-lw -> v1.4-lw
這樣他人從倉庫中克隆或拉取也能獲得這些標簽。
git push <remote> --tags
不區分輕量標簽和附注標簽,沒有簡單的選項能夠讓你只選擇推送一種標簽。
刪除標簽
使用 git tag -d <tagname>
刪除本地倉庫標簽:
$ git tag -d v1.4-lw
Deleted tag 'v1.4-lw' (was e7d5add)
再通過 git push <remote> :refs/tags/<tagname>
刪除遠程倉庫標簽:
$ git push origin :refs/tags/v1.4-lw
To /git@github.com:schacon/simplegit.git- [deleted] v1.4-lw
注意它刪除遠程標簽的原理是把冒號前面的空值推動到遠程標簽名,從而高效地刪除它。
還有一種更直觀的刪除遠程標簽的方式是:
$ git push origin --delete <tagname>
檢出(Checking out)標簽
使用 git checkout
命令可以查看某個標簽指向的文件版本,但該命令有一個副作用就是會是你的倉庫處于“頭指針分離(detached HEAD)”的狀態:
$ git checkout 2.0.0
Note: checking out '2.0.0'.You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:git checkout -b <new-branch>HEAD is now at 99ada87... Merge pull request #89 from schacon/appendix-final$ git checkout 2.0-beta-0.1
Previous HEAD position was 99ada87... Merge pull request #89 from schacon/appendix-final
HEAD is now at df3f601... add atlas.json and cover image
命令提示說的很清楚,當前處于頭指針分離狀態下,你可以查看、試驗性修改并提交變更,在此狀態下的所有提交都不會影響任何分支,只需切換到其他分支即可完全丟棄這些提交。如果想保留這些提交,需要通過 git checkout -b <new-branch>
命令創建新分支:
$ git checkout -b version2 v2.0.0
Switched to a new branch 'version2'
在新分支上提交這些修改。
2.7 Git 別名
使用git config
為命令設置別名:
$ git config --global alias.co checkout
$ git config --global alias.br branch
$ git config --global alias.ci commit
$ git config --global alias.st status
別名在創建你認為應該存在的命令時會很有用。比如為了讓取消暫存文件的命令更易用,可以這樣使用別名:
$ git config --global alias.unstage 'reset HEAD --'
那么以下兩個命令等價:
$ git unstage fileA
$ git reset HEAD -- fileA
通常也會添加一個 last
命令,像這樣:
$ git config --global alias.last 'log -1 HEAD'
可以輕松地看到最后一次提交:
$ git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date: Tue Aug 26 19:48:51 2008 +0800test for current headSigned-off-by: Scott Chacon <schacon@example.com>