如今咱們手上有了一個真實項目的 Git 倉庫,並從這個倉庫中取出了全部文件的工做拷貝。 接下來,對這些文件作些修改,在完成了一個階段的目標以後,提交本次更新到倉庫。git
請記住,你工做目錄下的每個文件都不外乎這兩種狀態:已跟蹤或未跟蹤。 已跟蹤的文件是指那些被歸入了版本控制的文件,在上一次快照中有它們的記錄,在工做一段時間後,它們的狀態可能處於未修改,已修改或已放入暫存區。 工做目錄中除已跟蹤文件之外的全部其它文件都屬於未跟蹤文件,它們既不存在於上次快照的記錄中,也沒有放入暫存區。 初次克隆某個倉庫的時候,工做目錄中的全部文件都屬於已跟蹤文件,並處於未修改狀態。github
編輯過某些文件以後,因爲自上次提交後你對它們作了修改,Git 將它們標記爲已修改文件。 咱們逐步將這些修改過的文件放入暫存區,而後提交全部暫存了的修改,如此反覆。因此使用 Git 時文件的生命週期以下:正則表達式
Figure 2-1. 文件的狀態變化週期shell
要查看哪些文件處於什麼狀態,能夠用 git status
命令。 若是在克隆倉庫後當即使用此命令,會看到相似這樣的輸出:vim
$ git statusOn branch masternothing to commit, working directory clean
這說明你如今的工做目錄至關乾淨。換句話說,全部已跟蹤文件在上次提交後都未被更改過。 此外,上面的信息還代表,當前目錄下沒有出現任何處於未跟蹤狀態的新文件,不然 Git 會在這裏列出來。 最後,該命令還顯示了當前所在分支,並告訴你這個分支同遠程服務器上對應的分支沒有偏離。 如今,分支名是 「master」,這是默認的分支名。 咱們在 Git 分支 會詳細討論分支和引用。安全
如今,讓咱們在項目下建立一個新的 README 文件。 若是以前並不存在這個文件,使用 git status
命令,你將看到一個新的未跟蹤文件:服務器
$ echo 'My Project' > README $ git statusOn branch masterUntracked 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)
在狀態報告中能夠看到新建的 README 文件出如今 Untracked files
下面。 未跟蹤的文件意味着 Git 在以前的快照(提交)中沒有這些文件;Git 不會自動將之歸入跟蹤範圍,除非你明明白白地告訴它「我須要跟蹤該文件」, 這樣的處理讓你沒必要擔憂將生成的二進制文件或其它不想被跟蹤的文件包含進來。 不過如今的例子中,咱們確實想要跟蹤管理 README 這個文件。less
使用命令 git add
開始跟蹤一個文件。 因此,要跟蹤 README 文件,運行:編輯器
$ git add README
此時再運行 git status
命令,會看到 README 文件已被跟蹤,並處於暫存狀態:工具
$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README
只要在 Changes to be committed
這行下面的,就說明是已暫存狀態。 若是此時提交,那麼該文件此時此刻的版本將被留存在歷史記錄中。 你可能會想起以前咱們使用 git init
後就運行了 git add (files)
命令,開始跟蹤當前目錄下的文件。 git add
命令使用文件或目錄的路徑做爲參數;若是參數是目錄的路徑,該命令將遞歸地跟蹤該目錄下的全部文件。
如今咱們來修改一個已被跟蹤的文件。 若是你修改了一個名爲 CONTRIBUTING.md
的已被跟蹤的文件,而後運行 git status
命令,會看到下面內容:
$ git statusOn branch masterChanges 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
出如今 Changes not staged for commit
這行下面,說明已跟蹤文件的內容發生了變化,但尚未放到暫存區。 要暫存此次更新,須要運行 git add
命令。 這是個多功能命令:能夠用它開始跟蹤新文件,或者把已跟蹤的文件放到暫存區,還能用於合併時把有衝突的文件標記爲已解決狀態等。 將這個命令理解爲「添加內容到下一次提交中」而不是「將一個文件添加到項目中」要更加合適。 如今讓咱們運行 git add
將"CONTRIBUTING.md"放到暫存區,而後再看看 git status
的輸出:
$ git add CONTRIBUTING.md $ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: CONTRIBUTING.md
如今兩個文件都已暫存,下次提交時就會一併記錄到倉庫。 假設此時,你想要在 CONTRIBUTING.md
裏再加條註釋, 從新編輯存盤後,準備好提交。 不過且慢,再運行 git status
看看:
$ vim CONTRIBUTING.md $ git status On branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: 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 只不過暫存了你運行 git add
命令時的版本, 若是你如今提交,CONTRIBUTING.md
的版本是你最後一次運行 git add
命令時的那個版本,而不是你運行 git commit
時,在工做目錄中的當前版本。 因此,運行了 git add
以後又做了修訂的文件,須要從新運行 git add
把最新版本從新暫存起來:
$ git add CONTRIBUTING.md $ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: CONTRIBUTING.md
git status
命令的輸出十分詳細,但其用語有些繁瑣。 若是你使用 git status -s
命令或 git status --short
命令,你將獲得一種更爲緊湊的格式輸出。 運行 git status -s
,狀態報告輸出以下:
$ git status -s M READMEMM Rakefile A lib/git.rb M lib/simplegit.rb?? LICENSE.txt
新添加的未跟蹤文件前面有 ??
標記,新添加到暫存區中的文件前面有 A
標記,修改過的文件前面有 M
標記。 你可能注意到了 M
有兩個能夠出現的位置,出如今右邊的 M
表示該文件被修改了可是還沒放入暫存區,出如今靠左邊的 M
表示該文件被修改了並放入了暫存區。 例如,上面的狀態報告顯示:README
文件在工做區被修改了可是尚未將修改後的文件放入暫存區,lib/simplegit.rb
文件被修改了並將修改後的文件放入了暫存區。 而 Rakefile
在工做區被修改並提交到暫存區後又在工做區中被修改了,因此在暫存區和工做區都有該文件被修改了的記錄。
通常咱們總會有些文件無需歸入 Git 的管理,也不但願它們總出如今未跟蹤文件列表。 一般都是些自動生成的文件,好比日誌文件,或者編譯過程當中建立的臨時文件等。 在這種狀況下,咱們能夠建立一個名爲.gitignore
的文件,列出要忽略的文件模式。 來看一個實際的例子:
$ cat .gitignore*.[oa]*~
第一行告訴 Git 忽略全部以 .o
或 .a
結尾的文件。通常這類對象文件和存檔文件都是編譯過程當中出現的。 第二行告訴 Git 忽略全部以波浪符(~)結尾的文件,許多文本編輯軟件(好比 Emacs)都用這樣的文件名保存副本。 此外,你可能還須要忽略 log,tmp 或者 pid 目錄,以及自動生成的文檔等等。 要養成一開始就設置好 .gitignore 文件的習慣,以避免未來誤提交這類無用的文件。
文件 .gitignore
的格式規範以下:
全部空行或者以 #
開頭的行都會被 Git 忽略。
可使用標準的 glob 模式匹配。
匹配模式能夠以(/
)開頭防止遞歸。
匹配模式能夠以(/
)結尾指定目錄。
要忽略指定模式之外的文件或目錄,能夠在模式前加上驚歎號(!
)取反。
所謂的 glob 模式是指 shell 所使用的簡化了的正則表達式。 星號(*
)匹配零個或多個任意字符;[abc]
匹配任何一個列在方括號中的字符(這個例子要麼匹配一個 a,要麼匹配一個 b,要麼匹配一個 c);問號(?
)只匹配一個任意字符;若是在方括號中使用短劃線分隔兩個字符,表示全部在這兩個字符範圍內的均可以匹配(好比 [0-9]
表示匹配全部 0 到 9 的數字)。 使用兩個星號(*
) 表示匹配任意中間目錄,好比a/**/z
能夠匹配 a/z
, a/b/z
或 a/b/c/z
等。
咱們再看一個 .gitignore 文件的例子:
# no .a files *.a # but do track lib.a, even though you're ignoring .a files above !lib.a # only ignore the TODO file in the current directory, not subdir/TODO /TODO # ignore all files in the build/ directory build/ # ignore doc/notes.txt, but not doc/server/arch.txt doc/*.txt # ignore all .pdf files in the doc/ directory doc/**/*.pdf
GitHub 有一個十分詳細的針對數十種項目及語言的 .gitignore
文件列表,你能夠在https://github.com/github/gitignore 找到它.
若是 git status
命令的輸出對於你來講過於模糊,你想知道具體修改了什麼地方,能夠用 git diff
命令。 稍後咱們會詳細介紹 git diff
,你可能一般會用它來回答這兩個問題:當前作的哪些更新尚未暫存? 有哪些更新已經暫存起來準備好了下次提交? 儘管 git status
已經經過在相應欄下列出文件名的方式回答了這個問題,git diff
將經過文件補丁的格式顯示具體哪些行發生了改變。
假如再次修改 README 文件後暫存,而後編輯 CONTRIBUTING.md
文件後先不暫存, 運行 status
命令將會看到:
$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) modified: 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
要查看還沒有暫存的文件更新了哪些部分,不加參數直接輸入 git diff
:
$ git diffdiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 8ebb991..643e24f 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -65,7 +65,8 @@ branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change-merged in.+merged in. Also, split your changes into comprehensive chunks if your patch is+longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's
此命令比較的是工做目錄中當前文件和暫存區域快照之間的差別, 也就是修改以後尚未暫存起來的變化內容。
若要查看已暫存的將要添加到下次提交裏的內容,能夠用 git diff --cached
命令。(Git 1.6.1 及更高版本還容許使用 git diff --staged
,效果是相同的,但更好記些。)
$ git diff --stageddiff --git a/README b/READMEnew file mode 100644index 0000000..03902a1--- /dev/null+++ b/README@@ -0,0 +1 @@+My Project
請注意,git diff 自己只顯示還沒有暫存的改動,而不是自上次提交以來所作的全部改動。 因此有時候你一會兒暫存了全部更新過的文件後,運行 git diff
後卻什麼也沒有,就是這個緣由。
像以前說的,暫存 CONTRIBUTING.md
後再編輯,運行 git status
會看到暫存先後的兩個版本。 若是咱們的環境(終端輸出)看起來以下:
$ git add CONTRIBUTING.md$ echo '# test line' >> CONTRIBUTING.md$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) modified: 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
如今運行 git diff
看暫存先後的變化:
$ git diffdiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 643e24f..87f08c8 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -119,3 +119,4 @@ at the ## Starter Projects See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).+# test line
而後用 git diff --cached
查看已經暫存起來的變化:(--staged 和 --cached 是同義詞)
$ git diff --cacheddiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 8ebb991..643e24f 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -65,7 +65,8 @@ branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change-merged in.+merged in. Also, split your changes into comprehensive chunks if your patch is+longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's
在本書中,咱們使用 git diff
來分析文件差別。 可是,若是你喜歡經過圖形化的方式或其它格式輸出方式的話,可使用 git difftool
命令來用 Araxis ,emerge 或 vimdiff 等軟件輸出 diff 分析結果。 使用 git difftool --tool-help
命令來看你的系統支持哪些 Git Diff 插件。
如今的暫存區域已經準備穩當能夠提交了。 在此以前,請必定要確認還有什麼修改過的或新建的文件尚未 git add
過,不然提交的時候不會記錄這些還沒暫存起來的變化。 這些修改過的文件只保留在本地磁盤。 因此,每次準備提交前,先用 git status
看下,是否是都已暫存起來了, 而後再運行提交命令git commit
:
$ git commit
這種方式會啓動文本編輯器以便輸入本次提交的說明。 (默認會啓用 shell 的環境變量 $EDITOR
所指定的軟件,通常都是 vim 或 emacs。固然也能夠按照 起步 介紹的方式,使用 git config --global core.editor
命令設定你喜歡的編輯軟件。)
編輯器會顯示相似下面的文本信息(本例選用 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 # Changes to be committed: # new file: README # modified: CONTRIBUTING.md # ~ ~ ~ ".git/COMMIT_EDITMSG" 9L, 283C
能夠看到,默認的提交消息包含最後一次運行 git status
的輸出,放在註釋行裏,另外開頭還有一空行,供你輸入提交說明。 你徹底能夠去掉這些註釋行,不過留着也不要緊,多少能幫你回想起此次更新的內容有哪些。 (若是想要更詳細的對修改了哪些內容的提示,能夠用 -v
選項,這會將你所作的改變的 diff 輸出放到編輯器中從而使你知道本次提交具體作了哪些修改。) 退出編輯器時,Git 會丟掉註釋行,用你輸入提交附帶信息生成一次提交。
另外,你也能夠在 commit
命令後添加 -m
選項,將提交信息與命令放在同一行,以下所示:
$ git commit -m "Story 182: Fix benchmarks for speed"[master 463dc4f] Story 182: Fix benchmarks for speed 2 files changed, 2 insertions(+) create mode 100644 README
好,如今你已經建立了第一個提交! 能夠看到,提交後它會告訴你,當前是在哪一個分支(master
)提交的,本次提交的完整 SHA-1 校驗和是什麼(463dc4f
),以及在本次提交中,有多少文件修訂過,多少行添加和刪改過。
請記住,提交時記錄的是放在暫存區域的快照。 任何還未暫存的仍然保持已修改狀態,能夠在下次提交時歸入版本管理。 每一次運行提交操做,都是對你項目做一次快照,之後能夠回到這個狀態,或者進行比較。
儘管使用暫存區域的方式能夠精心準備要提交的細節,但有時候這麼作略顯繁瑣。 Git 提供了一個跳過使用暫存區域的方式, 只要在提交的時候,給 git commit
加上 -a
選項,Git 就會自動把全部已經跟蹤過的文件暫存起來一併提交,從而跳過 git add
步驟:
$ git statusOn branch masterChanges 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.mdno changes added to commit (use "git add" and/or "git commit -a")$ git commit -a -m 'added new benchmarks'[master 83e38c7] added new benchmarks 1 file changed, 5 insertions(+), 0 deletions(-)
看到了嗎?提交以前再也不須要 git add
文件「CONTRIBUTING.md」了。
要從 Git 中移除某個文件,就必需要從已跟蹤文件清單中移除(確切地說,是從暫存區域移除),而後提交。 能夠用 git rm
命令完成此項工做,並連帶從工做目錄中刪除指定的文件,這樣之後就不會出如今未跟蹤文件清單中了。
若是隻是簡單地從工做目錄中手工刪除文件,運行 git status
時就會在 「Changes not staged for commit」 部分(也就是 未暫存清單)看到:
$ rm PROJECTS.md$ git statusOn branch masterYour branch is up-to-date with 'origin/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: PROJECTS.mdno changes added to commit (use "git add" and/or "git commit -a")
而後再運行 git rm
記錄這次移除文件的操做:
$ git rm PROJECTS.mdrm 'PROJECTS.md'$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) deleted: PROJECTS.md
下一次提交時,該文件就再也不歸入版本管理了。 若是刪除以前修改過而且已經放到暫存區域的話,則必需要用強制刪除選項 -f
(譯註:即 force 的首字母)。 這是一種安全特性,用於防止誤刪尚未添加到快照的數據,這樣的數據不能被 Git 恢復。
另一種狀況是,咱們想把文件從 Git 倉庫中刪除(亦即從暫存區域移除),但仍然但願保留在當前工做目錄中。 換句話說,你想讓文件保留在磁盤,可是並不想讓 Git 繼續跟蹤。 當你忘記添加 .gitignore
文件,不當心把一個很大的日誌文件或一堆 .a
這樣的編譯生成文件添加到暫存區時,這一作法尤爲有用。 爲達到這一目的,使用 --cached
選項:
$ git rm --cached README
git rm
命令後面能夠列出文件或者目錄的名字,也可使用 glob
模式。 比方說:
$ git rm log/\*.log
注意到星號 *
以前的反斜槓 \
, 由於 Git 有它本身的文件模式擴展匹配方式,因此咱們不用 shell 來幫忙展開。 此命令刪除 log/
目錄下擴展名爲 .log
的全部文件。 相似的好比:
$ git rm \*~
該命令爲刪除以 ~
結尾的全部文件。
不像其它的 VCS 系統,Git 並不顯式跟蹤文件移動操做。 若是在 Git 中重命名了某個文件,倉庫中存儲的元數據並不會體現出這是一次更名操做。 不過 Git 很是聰明,它會推斷出究竟發生了什麼,至於具體是如何作到的,咱們稍後再談。
既然如此,當你看到 Git 的 mv
命令時必定會困惑不已。 要在 Git 中對文件更名,能夠這麼作:
$ git mv file_from file_to
它會恰如預期般正常工做。 實際上,即使此時查看狀態信息,也會明白無誤地看到關於重命名操做的說明:
$ git mv README.md README$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.md -> README
其實,運行 git mv
就至關於運行了下面三條命令:
$ mv README.md README$ git rm README.md$ git add README
如此分開操做,Git 也會意識到這是一次更名,因此無論何種方式結果都同樣。 二者惟一的區別是,mv
是一條命令而另外一種方式須要三條命令,直接用 git mv
輕便得多。 不過有時候用其餘工具批處理更名的話,要記得在提交前刪除老的文件名,再添加新的文件名。