1、前言
假設當前已經git clone了一個倉庫項目,並從項目倉庫中獲得了所有文件的工作拷貝,那么,如何對這些文件進行修改,完成了一定階段的修改后,提交本次更新到倉庫?
工作目錄下的每個文件都處於兩種狀態:未跟蹤和已跟蹤狀態,已跟蹤的文件是指哪些已經納入版本控制的文件,上一次快照中有這些文件的記錄,在目錄下工作一段時間后,文件的狀態可能處於未修改、已修改或者處於暫存區。工作目錄中除了已跟蹤的文件以外,其它的文件屬於未跟蹤狀態,它們既不存在於上一次記錄的快照中,也沒有放入暫存區。當我們初次git clone某個倉庫時,工作目錄下的所有文件都屬於已跟蹤狀態,並處於文件未修改狀態。
當我們編輯和修改某些文件后,由於自上次提交之后對這些文件進行了修改,git會將這些文件標識為已修改文件,在工作過程中,我們還會逐步將這些修改過的文件放入暫存區,然后提交所有暫存了的修改,如此反復,使用git操作時,文件的生命周期如下所示:

2、查看工作目錄的文件狀態
想要查看當前工作目錄的文件狀態,可以使用下面的命令:
$ git status
如果我們在git clone某個倉庫后,馬上在當前的工作目錄下執行該命令,查看當前的文件狀態,執行結果輸出如下:

該輸出結果表明,當前工作目錄是相當干凈的,換句話來說,也就是所有已跟蹤文件在上次提交后都沒有被修改過,另外,上面的結果還表示了,當前工作目錄並沒有出現任何處於未跟蹤狀態的新文件,如果有未跟蹤狀態的新文件,git會列舉出來,最后,輸出信息還表明了,當前所在的分支為"master",為默認分支,並告訴我們當前的分支同遠程服務器上對應的分支沒有發生偏離。
接下來,在項目當中創建一個新的app.c文件,並在文件里面輸入一些C代碼,如果在此之前,項目中並沒有存在該文件的話,這時候,使用git status命令進行查看,將會看到一個新的未跟蹤狀態文件,如下:
創建新的app.c文件:
$ cd myGitTest $ touch app.c $ vim app.c
在app.c文件在輸入下面的內容,然后保存退出:

新的app.c文件創建並編輯后,再次使用下面命令查看當前文件的狀態:
$ git status
新的文件狀態輸出結果,如下所示:

在上面的文件狀態輸出結果可以看到,新創建的app.c文件出現在了"Untracked files"行下面,說明新創建的app.c文件目前處於未跟蹤狀態。未跟蹤的文件說明在之前的快照中並沒有這些文件,git不會自動將這些文件納入跟蹤范圍,這樣的處理,可以讓我們不必擔心生成的二進制文件或者不想被跟蹤的文件包含進來,但是,在上面的新創建app.c文件,我們是需要跟蹤管理的。
3、跟蹤管理新文件
在項目倉庫中,如果我們需要開始跟蹤管理一個新的文件,可以使用下面的命令:
$ git add <file>
例如,在上面舉的例子當中,我們新創建了一個app.c文件,從git status輸出的文件狀態結果可以知道,新創建的app.c文件處於未跟蹤狀態,因此,可以使用下面的命令,開始跟蹤管理app.c文件:
$ git add app.c
此時,新創建的app.c已經處於跟蹤狀態了,這個時候,我們再使用下面命令查看當前文件的狀態:
$ git status
命令的執行結果如下所示:

從上面的輸出結果可以看到,當我們使用git add命令對新創建的app.c文件進行跟蹤管理后,app.c的文件狀態從未跟蹤狀態轉變為了跟蹤狀態,並且,目前app.c文件處於暫存狀態,此外,我們可以從輸出結果可以看到,新創建的app.c文件被列舉在"Changes to be committed"行下,如果,這個時候進行提交的話,那么新創建的app.c文件此時此刻的版本將會留存在歷史記錄中。
另一個方面,git add命令使用文件或目錄作為參數,如果參數是目錄的話,將遞歸地跟蹤管理到該目錄下的所有文件。
4、暫存已修改的文件
在上面的例子基礎下,接下來,我們對當前工作目錄下已跟蹤的文件README.md進行編輯修改,修改完成后,使用git status命令查看當前的文件狀態:
$ vim README.md
$ git status
使用vim編輯器對README.md文件進行修改后,執行git status命令后,文件狀態的輸出結果如下所示:

從上面的輸出結果可以看到,我們編輯修改過后的文件README.md,被列舉在了"Changes not staged for commit"行下面,說明已跟蹤的文件的內容已經發生了改變,但是還沒有暫存這次內容更新,也就是對README.md文件的修改,我們並沒有暫存下來。
如果我們想暫存這次README.md文件內容的更新,可以使用下面的命令:
$ git add README.md
git add命令是一個多功能命令,我們可以用它開始跟蹤新創建的文件,或者將已跟蹤的文件放到暫存區,還能用於合並時把有沖突的文件標識為已解決狀態等,在這次操作當中,該命令理解為"添加新內容到下一次提交中"而不是"將一個文件添加到項目中",在上面的命令執行后,編輯修改過的文件README.md將被放到暫存區。
這時候,我們再使用下面的命令查看當前文件的狀態:
$ git status
命令執行后,輸出的結果如下所示:

上面的輸出結果表明,現在兩個文件app.c和README.md都已經被暫存起來,下次提交的時候將會一並記錄到倉庫中。
假如此時,我們需要在文件README.md中編輯並添加新的內容,然后再用git status進行文件的狀態查看:
$ vim README.md
$ git status
上面的命令執行后,工作目錄的文件狀態如下所示:

從上面的輸出結果可以看到,文件README.md同時出現在了暫存區和非暫存區,為什么會出現這樣的結果呢?實際上,git只不過暫存了前面運行了git add命令時的版本,如果現在提交的話,此時README.md的版本是最后一次運行git add命令時的哪個版本,而不是運行git commit命令時,當前工作目錄的版本,因此,我們需要注意的是,運行了git add命令暫存后,再次對暫存區的文件進行編輯修改,一定要再次運行git add命令把最新的文件版本暫存起來。
接下來,我們使用下面的命令將再次編輯修改的README.md文件暫存起來,並查看文件狀態是否發生改變:
$ git add README.md
$ git status
命令執行后,輸出結果如下所示:

從輸出結果可以看到,兩個文件都處於暫存區了,包括再次對README.md文件編輯修改的內容也再次被暫存起來了,這正是我們想要的結果。
5、查看已暫存和未暫存的修改
在上面舉的例子當中,我們使用git status命令可以查看當前工作目錄的文件狀態,命令運行后,從輸出結果可以看到我們未跟蹤哪些文件、修改了哪些文件和暫存了哪些修改,但是這個命令是模糊的,我們並看不到具體修改了文件哪些地方,如果我們想查看已經暫存和未暫存的修改,可以使用下面的命令:
$ git diff
上面的命令將通過文件補丁的形式顯示文件哪些地方進行了修改,非常詳細。
接下來,基於上面的例子,假如我們再次編輯修改已經暫存的文件README.md,在文件的最后,添加一些版本信息:
README.md文件修改之前,如下:

README.md文件修改之后,如下:

從上面的文件修改前后對比,可以知道,我們對README.md文件只是簡單增添了12和13這兩行,12行為空行,13行添加了"Version:1.0"字符串。
接下來,我們先不要去暫存文件README.md的內容修改,運行下面命令查看當前文件狀態:
$ git status
命令執行完成后,輸出結果如下所示:

從上面的輸出結果可以看到,被修改過后的README.md文件,再次被列舉到"Changes not staged for commit"行下面,說明此時的內容修改還並沒有暫存起來。
接下來,我們需要查看還沒有暫存的文件修改了哪些部分,也就是剛剛修改的README.md文件,不加任何參數,直接輸入下面的命令:
$ git diff
命令執行后,輸出結果如下:

在上面的輸出顯示,重點是黃色圈出的地方,顯示為綠色的地方,也就是下面的兩行:
+
+Version:1.0
在上面列出的兩行中,行的前面都帶有'+'字符,說明是文件新添加的內容,在上面文件修改對比的時候,也說過了,我們就是增加了這兩行。
需要注意的是,git diff命令本身只顯示文件沒有暫存的內容修改,而不是自上次提交以來所做的所有改動,因此,當我們一下子暫存了所有修改更新過的文件后,運行git diff命令卻什么都沒有輸出,就是因為這個原因。
接下來,我們可以使用下面的命令來查看文件已經暫存起來的內容修改(--staged和--cached參數是一樣的效果):
$ git diff --staged or $ git diff --cached
命令執行后,輸出如下所示:

從上面的輸出結果來看,分別對比了暫存的兩個文件:新創建的app.c文件和編輯修改的README.md文件,其中,紅色的內容表示刪除,綠色的內容表示添加的,文件被修改過的地方都很詳細地顯示出來了。
6、提交已暫存的更新
基於上面的例子,現在,項目的暫存區域已經准備妥當了,就可以使用命令進行更新提交了,但是在此之前,我們一定要確認還有沒有修改過的文件或者新創建的文件沒有被git add過,否則的話,提交的時候並不會去記錄這些沒有暫存的變化,這些修改過的文件將只留在本地磁盤中,因此,我們要切記,每次提交時,需要先用git status查看,要提交的更新是否都已暫存起來了,如果輸出顯示還有沒有暫存起來的文件,則需要使用git add命令進行暫存,再運行提交命令,如下:
$ git status
$ git add .
$ git commit
這種方式的提交將會啟動文本編輯器以便輸入本次提交的說明,默認會啟用shell的環境變量$EDITOR所指定的文本編輯軟件,一般是vim或者emacs,可以使用git config命令進行修改。
例如,提交的時候會類似顯示下面的內容:

從vim編輯器顯示的內容看,默認的提交信息包含了最后一次使用git status命令的輸出內容,放在了注釋的地方,此外,開頭還空出了一行,用於我們輸入提交說明,當我們沒有輸入任何的話,退出編輯器,本次提交將會中斷,也就是提交將不會成功。
另外,我們也可以在git commit提交的時候添加-m選項,將提交的信息和命令放在一同一行,如下提交的命令:
$ git status $ git add . $ git commit -m "create app.c and modified files"
提交命令執行后,輸出如下所示:

從上面的輸出結果可以看到,我們已經成功創建了一個提交,此外,從提交的輸出信息看,還能知道是在哪個分支進行提交的,本次提交的完整SHA-1校驗和是什么,本次提交的校驗和為4a4d50d,還有就是,本次提交修改了多少個文件,添加了多少行,刪除了多少行,非常詳細。
需要注意的是,提交時記錄的是放在暫存區的文件的快照,任何還沒有暫存的仍然保持已修改的狀態,我們可以在下次提交時納入版本管理,每次運行提交操作,都是對項目作一次快照,下次可以回到這個狀態,或者對其進行比較。
7、提交時跳過使用暫存區域
在上面提交的例子中,我們知道,使用暫存區的方式進行提交,能夠精心准備需要提交的細節,但是有時候卻顯得比較繁瑣,基於此,git提供了一個跳過使用暫存區域的方式,當我們使用git commit命令進行提交的時候,加上-a選項即可,這時候,git會自動把所有已經跟蹤過的文件暫存起來,並一起進行提交,從而跳過了git add這個步驟,比較方便。
基於上面的例子,將剛剛提交的內容git push到遠程服務器后,再次對app.c文件進行修改,然后使用git status進行文件狀態查看,如下:
$ git push origin master
$ vim app.c
$ git status
輸出如下所示:

從輸出結果可以看到,此時app.c文件被列舉在了"Changes not staged for commit"這行,說明此時,app.c文件的內容發生了變化,但是此次變化並沒有被暫存起來,接下來,我們運行下面的命令進行提交:
$ git commit -a -m "modified app.c"
命令執行完成后,輸出如下:

從輸出結果看,本次提交成功了,並且得到了SHA-1校驗和為aac6bfb,所以,在使用git commit命令進行提交時,使用-a參數,可以直接跳過使用暫存區,也就是git add命令。
8、文件移除
要想從git倉庫中移除文件,就必須從已跟蹤的文件清單中進行文件移除,准確地說,應該是從暫存區域中移除文件,然后進行更新提交,對於此操作,我們可以使用下面的命令進行:
$ git rm <file>
命令執行后,將從工作目錄中刪除指定的文件,並且,被刪除的文件也不會再出現在未跟蹤的文件清單中了。
如果在使用git進行版本管理的時候,只是簡單地使用rm命令從工作目錄刪除文件,當我們運行git status命令查看文件狀態時,將會看到被刪除的文件被列舉在"Changes not staged for commit"該行下面,也就是處於未暫存狀態,如下:
基於上面git clone的項目,對倉庫的hello.c文件進行修改,並使用git add命令將其添加到暫存區,然后使用rm命令直接刪除文件,看看會出現什么情況,命令如下:
$ cd myGitTest
$ vim hello.c
$ git add .
$ git stauts
命令執行后,輸出如下:

從上圖可以看到,文件hello.c修改的內容已經被暫存起來了,接下來,直接使用rm命令刪除文件,並使用git status查看當前文件狀態,命令如下:
$ rm -rf hello.c $ git status
命令執行后,輸出如下:

從上面的輸出結果可以看到,對於hello.c文件,剛剛修改的內容還留在暫存區域,並沒有從跟蹤清單中除去,hello.c文件同時被列舉在了"Changes not staged for commit"該行下面,這時候,可以使用git add命令進行暫存,下次提交時,該文件將不再納入版本管理。
接下來,我們來看看使用git rm命令進行刪除的效果,首先,我們新創建一個文件test.txt,然后使用git add命令納入版本管理,再使用git status進行文件狀態查看,命令如下:
$ touch test.txt $ git add test.txt $ git status
命令執行后,結果輸出如下:

從輸出結果可以知道,新創建的文件test.txt被列舉在了"Changes to be committed"行下面,也就是,此文件已經位於暫存區域了,我們可以直接進行提交,但是本次我們不直接提交,而是使用git rm命令對其進行暫存區移除,並從磁盤中進行刪除,命令如下:
$ git rm -f test.txt $ git status
輸出結果如下:

從上面的輸出結果可以看到,處於暫存區域的hello.c已經被移除了,並且該文件也不會留在了本地磁盤,在下次提交時,並不會再納入版本管理了,對於已經存在暫存區域的文件,刪除的話需要使用-f選項,這是git的一種安全特性,防止我們誤刪還沒有添加到快照的數據,這樣的數據不能再被git恢復。
另外一種情況就是,我們想把文件從git倉庫中移除,也就是從暫存區域中移除,但是仍然希望將文件留在本地磁盤中,例如,當我們還沒有添加.gitignore文件時,不小編譯產生了一堆*.o文件,如果這些文件被添加到暫存區域時,這個做法將會比較有用。
接下來,繼續基於上面的例子進行講解,此時新創建的文件test.txt處於暫存區域,使用git status查看文件狀態,如下所示:

接下來,我們要將處於暫存區域的test.txt文件進行移除,但是文件還需要留在本地磁盤,可以使用下面的命令:
$ git rm --cached test.txt $ git status
命令執行后,輸出如下:

從輸出結果可以看到,新創建的test.txt文件已經從暫存區進行移除了,並被列舉在了"Untracked files"該行下面,說明此時文件仍然留在本地磁盤中,並處於未跟蹤的狀態。
9、文件移動和重命名
和其它的版本控制管理系統不一樣,git並不會顯示跟蹤文件移動操作,如果在git中重命名了某個文件,倉庫中存儲的元數據並不會體現出是一次改名操作,不過,git會推斷出究竟發了什么操作。
當我們需要在git中對文件改名時,可以使用下面的命令進行操作:
$ git mv old_name new_name
基於上面的例子,現在,我們需要對當前工作目錄下的README.md文件重命名為README,然后查看當前的文件狀態,命令如下:
$ cd myGitTest $ git mv README.md README $ git status
命令執行后,輸出如下:

從輸出結果可以知道,README.md文件已經成功被改名,並且改名后的文件被列舉在了"Changes to be committed"行下面,表示我們的修改已經處於暫存區了,可以進行提交了。
其實,當我們在項目中運行git mv命令時相當於運行了下面的個命令:
$ mv README.md README $ git rm README.md $ git add README
當我們分開使用這些命令時,git也能夠意識到這是一次文件改名操作,所以不管哪種方式,結果都是一樣的。
