如何管理工作目錄,以便用戶可以更高效地新建提交。如何在處理工作區(qū)和暫存區(qū)文件的過程中修復(fù)錯誤,以及如何修復(fù)最近一次提交記錄中的問題;同時還會了解到如何安全地使用暫存機制和多個工作目錄處理工作流中的中斷問題。
主要內(nèi)容有以下幾點:
- 忽略文件:特意不讓版本控制器管理某些文件。
- 文件屬性:特定路徑的配置。
- 處理文件和二進制文件。
- 為了提高版本庫的可移植性,提供對文件尾行轉(zhuǎn)換的支持。
- git reset命令在不同模式下的應(yīng)用。
- 為了處理中斷而隱藏暫存變更。
- 搜索和查看文件。
- 交互式文件重置和恢復(fù)文件變更。
- 通過移除未跟蹤文件來保持工作區(qū)整潔。
1、忽略文件
用戶的工作區(qū)(也稱工作樹)內(nèi)部的內(nèi)容既可以被Git跟蹤,也可以不被它跟蹤。被跟蹤文件,顧名思義,Git將會跟蹤該文件的一系列變更記錄。對于Git來說,如果一個文件存在于暫存區(qū)(也稱索引),如果沒有特別聲明的話,那么該文件將會被系統(tǒng)跟蹤,并作為下一個修訂記錄的一部分。用戶添加文件后,系統(tǒng)跟蹤這些文件的目的是將它們當(dāng)作項目歷史的一部分。
索引或者暫存區(qū)不僅可以用來告知Git系統(tǒng)跟蹤哪些文件,而且可以作為新建提交的一種暫存器。而且索引或者暫存區(qū)可以幫助用戶解決合并沖突。
通常對于某些獨立文件或者某類文件來說,用戶永遠都不希望它們作為項目歷史記錄的一部分而存在,并且也不希望系統(tǒng)跟蹤它們。這些文件可能是編輯器的備份文件,也有可能是項目編譯系統(tǒng)自動生成的臨時文件。
用戶不希望Git系統(tǒng)自動添加上述文件,例如,在使用git add :/
(添加整個工作區(qū))和git add.
(添加當(dāng)前目錄下的所有文件)命令批量添加文件時,或者使用git add --all
命令更新工作區(qū)狀態(tài)索引時。
另外一方面,用戶又希望Git能夠有效地防止將不必要的文件添加到系統(tǒng)中。同時用戶還希望在執(zhí)行g(shù)it status命令后不顯示這些文件,因為它們的數(shù)量可能會非常龐大。有時它們也可能會和一些新增的未知文件混在一起。因此用戶希望這類文件特意不被跟蹤,即忽略它們。
未跟蹤和重新跟蹤的文件:
如果用戶希望忽略某個以前被跟蹤的文件,例如從手工生成HTML文件遷移到使用類似Markdown這樣的輕量級標記語言時,用戶通常需要在不將它們從工作目錄中刪除的情況下,將它們添加到忽略文件列表中,對它們?nèi)∠?。為此,用戶可以使?code>git rm--cached <文件名>命令。為了添加(開始跟蹤)一個特意不跟蹤(即忽略)的文件,用戶需要使用命令git add -f
。
1.1、將文件刻意標記為不跟蹤的
這種情況下,用戶可以通過Git系統(tǒng)中g(shù)itignore文件添加一組shell glob模式來指定希望忽略的文件,文件中每個模式占用一行的位置。
可以通過配置變量core.excludesFile指定每個用戶的個性化配置文件,該變量默認的值是$XDG_CONFIG_HOME/git/ignore
。如果環(huán)境變量$XDG_ CONFIG_HOME
未設(shè)置或者為空的話,那么其默認值為$HOME/.config/git/ignore
。每個本地版本庫的$GIT_DIR/info/exclude
文件在本地版本庫克隆的管理區(qū)中。
.gitignore文件在項目工作區(qū)目錄下;該文件通常是被系統(tǒng)跟蹤記錄并且可以和其他開發(fā)人員共享的。一些諸如git clean的命令還支持用戶通過命令行聲明忽略模式。
在判斷是否忽略某個路徑時,Git系統(tǒng)會根據(jù)上述列表中的模式以一定的順序進行模式匹配,然后根據(jù)就近優(yōu)先原則決定輸出結(jié)果。.gitignore文件也是按照一定的順序進行檢查的,它會從項目的頂級目錄開始,依次遍歷項目中的所有文件。
為了增強gitignore文件的可讀性,用戶可以使用空白行將文件分組(空白行不匹配文件)。用戶還可以對模式進行描述或者使用附帶注釋的模式組,以#開頭的代表一行注釋(為了實現(xiàn)對一個#開頭的哈希字符串進行模式匹配,通常會在第一個哈希字符前面使用\
對它轉(zhuǎn)義,例如\#*#
)。字符尾部的空格會被忽略,除非使用\對它進行轉(zhuǎn)義。
gitignore文件中的每一行都代表一種UNIX的glob模式,即shell通配符。通配符*可以匹配0個或者多個字符(任意字符串),通配符?可以匹配任意單個字符。你還會了解到字符類方括號[…]的使用,例如下面的模式匹配示例:
*.[oa]
*~
這里的第一行內(nèi)容是告訴Git系統(tǒng)忽略所有后綴名是.a或者.o的文件(例如靜態(tài)鏈接庫),以及軟件編譯過程中產(chǎn)生的臨時文件。第二行是告訴Git系統(tǒng)忽略所有以~結(jié)尾的文件,這類文件常見于很多UNIX文本編輯器采用的臨時備份文件。
如果模式中不包含 /
,即文件路徑的分隔符,Git會將它視為一個shell glob通配符,并且根據(jù)它查找相應(yīng)的文件名和目錄名,例如.gitignore文件的路徑或者某個版本庫頂級目錄。以/
結(jié)尾的模式是一個例外,它主要是用來匹配目錄的,除非目錄級下的反斜杠被移除了。以反斜杠開頭的模式是用來匹配路徑名稱前面位置的,它的含義有以下幾種:
- 模式中不包含反斜杠匹配版本庫中的任意路徑,那么我們可以說該模式是遞歸的。例如*.o模式匹配任意的文件對象,其中既包含gitignore文件,也包括file.o和obj/file.o這樣的子目錄。
- 以一個反斜杠結(jié)尾的模式只匹配目錄,否則它就是遞歸的(除非它還包含其他斜杠)。例如auto/模式將會匹配頂層的auto目錄以及src/auto目錄,但是它不會匹配名為auto的文件(或者一個標記鏈接)。
- 如果希望固定一個模式,并且確保它是非遞歸的,那么可以在其起始位置添加一個反斜杠進行轉(zhuǎn)義,例如/TODO文件將會忽略當(dāng)前層級的TODO文件,但是不會忽略子目錄中的文件,例如src/TODO。
- 包含斜杠的模式都是固化并且非遞歸的,通配符不會匹配作為目錄分隔符的斜杠。如果用戶希望匹配任意目錄,那么可以使用兩個連續(xù)的星號替換路徑地址的某個部分(例如/foo,foo/和foo//bar)。例如doc/.html匹配的是doc/index.html文件,但是無法匹配地址doc/api/ index.html。為了匹配doc目錄下的任意HTML文件,用戶可以使用doc/**/.html模式(或者將.html模式添加到doc/. gitignore文件中)。
- 用戶還可以在模式前面加一個感嘆號!前綴使之失效,任何根據(jù)先前的規(guī)則被排除的文件,現(xiàn)在又再次被包含(不再被忽略)進來了。例如已經(jīng)忽略所有生成的HTML文件,但是希望包含某個通過手工生成的文件,用戶可以在gitignore文件中做如下設(shè)置:
# 忽略所有通過AsciiDoc源代碼工具生成的HTML文件
*.html
# 下列有手工生成的文件將會是個例外
!welcome.html
注意,Git基于性能方面的考慮不會排除某個目錄(至少2.7版的程序是如此),這意味著當(dāng)父目錄被排除后,用戶不能將其目錄下的某個文件再次包含到跟蹤列表中。也就是說,為了將某個子目錄作為例外被跟蹤,必須做如下配置:
#除了目錄t0001/bin之外,將會排除所有文件。
/
!/t0001
/t0001/
!/t0001/bin
為了匹配一個用!開始的模式,必須使用一個反斜杠對其進行轉(zhuǎn)義,例如模式!important!.md是為了匹配!important!.md。
1.2、確定忽略文件類型
現(xiàn)在我們已經(jīng)知道了如何將文件狀態(tài)特意標記為不被跟蹤的(忽略),那么接下來的問題是哪些(哪一類)文件應(yīng)該被標記。另外一個問題是:上述文件的位置是什么?以及我們應(yīng)該如何在3個gitignore文件中聲明需要忽略的文件類型?
首先,用戶永遠不應(yīng)該跟蹤自動生成的文件(通常是由項目的編譯系統(tǒng)生成的)。如果用戶將它們添加到版本庫中了,那么它們很有可能無法和源文件保持同步。此外,它們也不是必須添加的文件,因為系統(tǒng)可以很容易地重新生成它們。唯一的例外是生成這些文件的源文件極少發(fā)生變動,并且生成它們需要額外的工具輔助,但是開發(fā)人員有可能沒有這些工具(如果源代碼經(jīng)常發(fā)生變更,用戶可以使用一個孤兒分支存放這些生成的文件,只在發(fā)布預(yù)覽版程序時更新該分支)。
這些是所有開發(fā)人員都希望忽略的文件,因此它們應(yīng)該被放到一個被跟蹤的.gitignore文件中。模式列表將會是經(jīng)由版本控制的,并且可以通過克隆副本分發(fā)給其他開發(fā)人員。大家可以在https://github.com/github/gitignore上找到和若干編程語言有關(guān)的一組非常有用的.gitignore模版。
其次,臨時文件和特定產(chǎn)品相關(guān)的用戶工具鏈,這些內(nèi)容通常都不會與其他開發(fā)人員共享。如果模式對版本庫和用戶都是有效的,例如在版本庫中的輔助文件,并且該文件也會影響特定的工作流用戶(例如該項目中IDE程序),那么它就應(yīng)該被放到每個克隆的$GIT_DIR/info/exclude文件夾中。
在用戶采用的通用忽略模式中,不必特意聲明版本庫(或者項目),一般來說可以通過core. excludesFile配置變量中進行聲明,同時對于每個用戶(全局)還可以在~/.gitconfig(或者~/.config/git/config)配置做相關(guān)設(shè)置。一般來說,默認的配置文件路徑是~/.config/git/ignore
。
專屬于每個用戶的忽略文件應(yīng)該不會是~/.gitignore,因為如果用戶希望讓~/directory ($HOME)主目錄保持版本控制,那么該文件有可能就會是用戶主目錄對應(yīng)版本庫中的.gitignore文件。
這里是編輯器或者IDE程序生成的備份或者臨時文件對應(yīng)的模式匹配文件所在之處。
已忽略文件通常也是無關(guān)緊要的:
警告:不要將重要資料添加到忽略列表中,這些資料通常是用戶不希望在某個版本庫中被跟蹤的,但是內(nèi)容相比忽略文件列表中的內(nèi)容來說卻是非常重要的!被Git忽略的這類文件既可以很容易地被重新生成(產(chǎn)品編譯系統(tǒng)生成的中間文件),而且對于用戶來說又是無關(guān)緊要的(臨時文件或者備份文件)。
因此Git會認為這類已忽略的文件用處不大,當(dāng)需要做一些清理工作時,即使在沒有向用戶提示的情況下也可能把它們刪除,例如,如果已忽略文件和當(dāng)前簽出的修訂內(nèi)容有沖突時。
1.3、忽略文件列表
用戶可以在執(zhí)行status命令時使用–ignored選項查看被忽略的文件:
$ git status --ignored
On branch master
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
rand.c~
no changes added to commit (use "git add" and/or "git commit -a")
$ git status --short --branch --ignored
## master
!! rand.c~
用戶還可以使用清理被忽略文件的測試選項:git clean -Xnd和底層(管道化)的命令git ls-files:
$ git ls-files --others --ignored --exclude-standard
rand.c~
后一個命令還可以用來顯示匹配忽略模式的被跟蹤文件。找到這類文件往往意味著某些文件需要被取消跟蹤(也可能是源代碼文件臨時生成的中間文件),或者也可能是忽略模式覆蓋的范圍過于寬泛了。因為Git是通過暫存區(qū)(緩存)已有的文件來識別哪些文件需要被跟蹤的,相關(guān)的命令如下:
$ git ls-files --cached --ignored --exclude-standard
底層命令(Plumbing)和高層命令(Porcelain)的區(qū)別:
- Git的命令主要分為兩種:一種是方便和用戶交互的高層命令,另外一種是方便編寫shell腳本的管道化底層命令。它們之間的區(qū)別在于:高層命令的輸出結(jié)果可以變更并不斷完善,例如在遇到與HEAD分離的情況下,執(zhí)行g(shù)it branch命令后的輸出結(jié)果顯示了其中的分離過程(無分支到與HEAD分離)。同時底層命令中也有選項(通常是–porcelain)來決定是否選擇無變更的輸出結(jié)果。它們的輸出結(jié)果和行為是可以根據(jù)主題配置的。
- 另外一個比較重要的區(qū)別是高層命令會嘗試猜測用戶的意圖,并且會使用默認參數(shù)和默認配置。底層命令則沒有那么智能,用戶必須通過–exclude-standard這樣的選項和git ls-files命令搭配使用,才能讓它采用忽略文件的默認設(shè)置。
1.4、忽略跟蹤文件內(nèi)的變更
也許用戶版本庫中不少文件發(fā)生了變更,但是它們很少被提交。這類文件可以是若干本地配置文件,為了適應(yīng)用戶本地環(huán)境經(jīng)過編輯配置,但是用戶永遠不希望將它們提交到遠程上游分支。這也可以是某個包含新發(fā)布的預(yù)覽版軟件名稱的文件,只有當(dāng)它們添加了下一個預(yù)覽版程序便簽后才會被提交。用戶可能希望讓這類文件大部分時間都保持“雜亂無章”的狀態(tài),但是又不希望Git系統(tǒng)經(jīng)常向用戶提示這些文件發(fā)生了變更。為了防止干擾其他變更信息的提示,用戶應(yīng)該將這類信息忽略。
用戶可以對Git進行配置,讓它跳過對工作目錄的檢查(假定它始終是最新版本),并使用文件的暫存版本替代,可以對某個文件設(shè)定相應(yīng)的skip-worktree標記來達到此目的。為此用戶將會需要用到底層的git update-index命令,它相當(dāng)于面向用戶的高層命令git add(用戶可以使用’git ls-files’查看文件的狀態(tài)和標記):
$ git update-index --skip-worktree GIT-VERSION-NAME
$ git ls-files –v
S GIT-VERSION-NAME
H Makefile
不過這種對工作區(qū)的省略也會影響到git stash命令;為了暫存用戶的變更并且保持工作目的整潔,用戶需要禁用該標記(至少是臨時措施)。為了讓Git再次監(jiān)測到工作目錄中的修訂版本,并且開始跟蹤文件的變更記錄,可以執(zhí)行下列命令:
$ git update-index --no-assume-unchanged GIT-VERSION-NAME
還有一個類似的選項assume-unchanged,它可以用來讓Git系統(tǒng)完全忽略文件的變更,甚至可以假定文件沒有發(fā)生任何變化。當(dāng)文件被這個標簽標記之后,它們將永遠不會出現(xiàn)在git status或git diff命令的輸出結(jié)果中,與之相關(guān)的變更將不會被暫存或提交。
有時這是非常有用的,特別是在檢查一個大型項目的文件變更時。不要對被跟蹤的文件使用assume- unchanged選項已達到忽略文件的目的。用戶務(wù)必確保文件沒有發(fā)生變更,不會發(fā)生欺騙Git系統(tǒng)的情況,例如git stash save命令將會根據(jù)你的設(shè)置進行保存,這樣就可能失去用戶本地文件的變更記錄。
2、文件屬性
Git中有一些配置和選項可以用來聲明基本路徑,它的機制和忽略文件類似(將文件特意標記為不跟蹤的)。這些路徑聲明設(shè)置被稱為屬性。
為了給文件指定一些屬性以便匹配給定的模式,用戶需要在下列g(shù)itattribute文件中添加一行由空格分隔的屬性集合作為模式(它的機制和gitignore文件類似)。
單個用戶配置文件:對于每個用戶來說,文件中的屬性會影響與該用戶有關(guān)的所有版本庫,該文件默認路徑是~/.config/git/attributes,并且是通過配置變量core.attributesFile聲明的。
版本庫屬性文件:該文件一般在本地版本庫克隆的管理區(qū)中,文件路徑是.git/info/attributes,這些屬性只會對聲明的單個版本庫克隆產(chǎn)生影響(對于單個用戶工作流來說)。項目中的.gitattributes文件,其中的屬性可以和團隊其他成員共享。
模式匹配文件的規(guī)則和前面提到的gitignore文件類似,除非存在不兼容的省略模式。
對于給定路徑,每個屬性可以設(shè)置為下列幾個狀態(tài):設(shè)置(設(shè)定值為true),未設(shè)置(設(shè)定值為false),以及給定值或者unspecified:
pattern* set -unset set-to=value !unspecified
注意,用字符串給一個屬性賦值時,等號(=)兩邊是沒有空格的!
當(dāng)存在多個模式匹配路徑時,后一行的屬性會覆蓋前一行的基本屬性。Gitattribute文件采用順序優(yōu)先原則,即在給定目錄中從單個用戶文件到.gitattributes文件,這和gitignore文件類似。
2.1、配置Diff和merge
在Git中,用戶可以通過屬性功能配置如何顯示文件不同版本之間的差異,以及如何對文件執(zhí)行三路合并。該特性可以用來擴展上述操作的功能,使得差異比較結(jié)果內(nèi)容更豐富,合并操作出現(xiàn)沖突的概率更小。它甚至可以用來高效地比較二進制文件。為此我們一般需要設(shè)置差異比較(diff)和合并操作(merge)的驅(qū)動。
屬性文件只能告訴用戶使用哪些驅(qū)動,其余的信息都在配置文件里面,而且配置信息并不是像.gitattributes文件那樣自動就可以和其他開發(fā)人員共享的(用戶還可以創(chuàng)建一個共享配置片段,然后將它添加到版本庫中,方便和其他開發(fā)人員使用相對路徑引用它)。方便易用的工具配置信息在不同電腦和操作系統(tǒng)上的表現(xiàn)可能不盡相同,不過這也意味著某些信息對跨平臺的要求要高一些。
不過,幸好系統(tǒng)內(nèi)置了不少差異比較和合并操作的驅(qū)動供用戶選擇。
3、使用reset命令修復(fù)錯誤
在開發(fā)過程的任意階段,用戶也許會希望撤銷一些東西以便達到修復(fù)錯誤的目的,或者希望將當(dāng)前的工作成果丟棄。在Git核心中不存在git undo這樣的命令,也沒有任何命令存在–undo選項可供使用。與此同時,倒是很多命令中都有–abort選項,可以讓用戶丟棄當(dāng)前的工作成果。沒有這類命令或者選項的原因之一就是為了避免在被撤銷內(nèi)容上產(chǎn)生歧義(這對于多步操作尤其重要)。
很多問題都可以通過git reset命令進行修復(fù)。它的用途非常廣泛,理解它的工作原理會讓你在實際應(yīng)用中如虎添翼。
3.1、回退分支head
git reset
命令在完整的樹模式下不僅會影響當(dāng)前分支首部,而且還會影響索引(暫存區(qū))和工作目錄。注意,重置不會影響當(dāng)前的分支,只會對簽出狀態(tài)下的內(nèi)容產(chǎn)生影響。為了只重置當(dāng)前分支首部,不影響所有工作目錄,可以使用git reset --soft [<revision>]
命令。
從效率上來看,我們只修改了當(dāng)前分支的指針(如下圖所示的master分支)指向了一個給定的修訂節(jié)點(HEAD^
—代表本示例中的上一提交記錄),暫存區(qū)和工作區(qū)都沒有受到影響。這一操作使得用戶丟棄了將要提交狀態(tài)中所有已變更的文件(分支上與上一版本存在差異文件),使用git status命令后會顯示這一變化。
1、移除和修改提交
上述命令的工作方式意味著軟性重置可以用來撤銷創(chuàng)建提交記錄的行為。這也適用于提交的修改,這種方式要比git commit
命令和--amend
選項一起使用更簡單一些。事實上,執(zhí)行下列命令:
$ git commit --amend [<options>]
和執(zhí)行下列命令是等效的:
$ git reset --soft HEAD^
$ git commit --reedit-message=ORIG_HEAD [<options>]
和使用軟性重置不同之處在于,git commit --amend
命令也適用于合并提交。在修改提交時,如果用戶只想修改提交的注釋信息,那么不需要使用任何命令選項輔助。如果用戶想修復(fù)一個工作目錄中的問題,但是不想修改注釋信息,那么可以使用-a --no-edit
選項。如果用戶希望修復(fù)用于糾正Git配置項目中的作者信息,那么可以使用--reset-author --no-edit
選項。
2、使用reset壓縮提交記錄
用戶并不僅限于將分支的首部移動到上一個提交節(jié)點。通過軟性重置,用戶還可以插入幾個更早的提交記錄(例如提交和bug修復(fù),或者引入新的功能函數(shù)),將一個提交一分為二(或者更多),又或者使用squash指令進行交互式變基操作。對于后者,用戶實際上可以插入任意一系列的提交記錄,而且不局限于最近的幾個提交記錄。
3.2、重置分支head和索引
reset命令的默認模式也被稱為混合式重置(因為它是介于軟性重置和強制重置之間的),修改當(dāng)前分支的首部指向給定的修訂節(jié)點,同時重置索引,將相關(guān)修訂的內(nèi)容寫入暫存區(qū)(見下圖)。
這一操作使得用戶丟棄了處于擬提交狀態(tài)并且未暫存的所有已變更文件(分支上與上一版本存在差異文件),執(zhí)行git status
命令后會顯示這一變化。git reset --mixed
命令還會使用簡要狀態(tài)格式報告那些未更新的內(nèi)容。用戶還可以訪問reset命令的歷史版本,例如撤銷新增的文件。如果用戶沒有暫存任何變更的話(或者用戶可以直接丟棄這些變更),那么上述操作可以使用git reset
命令實現(xiàn)。如果用戶希望撤銷添加某個特定文件的操作,那么可以使用git rm --cached <file>
命令。
1、使用reset分割提交
用戶還可以使用混合式的reset命令將一個提交一分為二。首先,運行git reset HEAD^
命令,將分支首部和索引指向上一修訂節(jié)點。然后向第一個提交以交互式添加的方式添加用戶希望添加的變更,繼而根據(jù)索引創(chuàng)建第一個提交(git add -i
和git commit
)。第二個提交可以根據(jù)工作目錄狀態(tài)創(chuàng)建(git commit -a
)。
如果交互式移除變更時更簡便一些,那么用戶也可以這么做。使用git reset–soft HEAD^命令后,可以對每個文件執(zhí)行reset命令,實現(xiàn)交互式地撤銷暫存變更,根據(jù)索引的構(gòu)造狀態(tài)創(chuàng)建第一個提交記錄,然后根據(jù)工作目錄創(chuàng)建第二個提交記錄。
依次循環(huán)往復(fù)操作,用戶就可以替代交互式變基,實現(xiàn)分割歷史提交記錄的目的。變基操作會切換到適當(dāng)?shù)奶峤挥涗浬?,實際的分割操作大致和上述操作是一樣的。
2、使用WIP提交保存和重排變更狀態(tài)
假如你正在某個分支上進行功能開發(fā)工作,這時有一個緊急的bug修復(fù)請求使得你不得不中斷目前的工作。你又不想舍棄目前分支上產(chǎn)生的變更記錄,但是現(xiàn)在的工作目錄又有些凌亂。一個比較可行的解決方案是通過創(chuàng)建一個臨時提交(工作進行中的快照,WIP),以便保存當(dāng)前工作區(qū)的狀態(tài):
$ git commit -a -m 'snapshot WIP (Work In Progress)'
然后用戶就可以停下目前的工作,切換到維護分支,創(chuàng)建一個提交專門修復(fù)相關(guān)問題。之后用戶只需要回到上一個分支(使用簽出命令),然后從歷史記錄中移除WIP提交(使用軟性重置),切換到未暫存的起始狀態(tài)即可(使用混合式重置):
$ git checkout -
$ git reset --soft HEAD^
$ git reset
不過使用git stash 命令處理中斷更方便一些。換句話說,這類臨時提交(類似的概念驗證式的工作)和暫存的不同之處在于,它可以和團隊其他成員共享。
3.3、丟棄變更和回退分支
有時看著一團亂麻的工作目錄,用戶也許非常希望丟棄所有的變更,將工作目錄和暫存區(qū)(索引)的狀態(tài)恢復(fù)到最近一次提交的狀態(tài)(最新的穩(wěn)定版本程序)。又或者用戶希望將版本庫的狀態(tài)回退到以往的某個修訂版本。如下圖所示,強制性重置(reset)操作將會修改當(dāng)前分支首部的指向,并且重置索引和工作目錄樹。被跟蹤文件產(chǎn)生的所有變更都會被丟棄。
該命令還可以用于撤銷(移除)一個提交,就好像該提交從未存在過一樣。執(zhí)行git reset --hard HEAD^
命令后,將會高效地將上一個提交節(jié)點移除(不過短時間內(nèi)還可以通過reflog恢復(fù)),除非該節(jié)點還可以通過其他分支進行訪問。
另外一個常見的用法是通過git reset --hard
命令丟棄工作目錄下的變更。
特別重要的一點需要謹記:強制性重置操作是不可恢復(fù)地將暫存區(qū)和工作目錄中的所有變更移除。用戶無法撤銷這部分操作!變更記錄將會永遠消失!
1、將提交指向特性分支
假如用戶正在master分支上工作,而且已經(jīng)創(chuàng)建了一系列的提交,同時發(fā)現(xiàn)正在研發(fā)的功能特性之間聯(lián)系非常緊密,繼而希望將它們整合到一個主題分支上。用戶希望將在master分支上的一系列提交(例如最近的3個修訂版本)遷移到上述特性分支上。
用戶需要創(chuàng)建一個feature分支,保存為提交的變更(如果有的話),將master分支上與主題特性相關(guān)的提交記錄移除,最后切換到feature分支繼續(xù)工作(或者可以使用變基操作實現(xiàn)):
$ git branch feature/topic
$ git stash
No local changes to save
$ git reset --hard HEAD~3
HEAD is now at f82887f before
$ git checkout feature/topic
Switched to branch 'feature/topic'
當(dāng)然,如果有本地的變更需要保存,在上述命令之前需要先執(zhí)行git stash pop
命令。
2、撤銷合并或拉取操作
強制性重置還可以通過git reset --hard HEAD
(HEAD是默認參數(shù),也可以省略)命令取消一個失敗的合并操作,例如用戶不打算解決合并沖突時(當(dāng)然在當(dāng)前的Git系統(tǒng)中,也可以使用git merge --abort
命令達到相同目的)。
用戶還可以通過git reset --hard ORIG_HEAD
(這里可以用HEAD@{1}
替代ORIG_HEAD
)命令移除一個成功執(zhí)行的快進式拉取操作或者變基操作(以及其他移動分支首部的操作)。
3.4、安全模式重置——保留用戶變更
強制性重置操作會丟棄用戶本地的所有變更,它的效果和git checkout -f命令類似。有時用戶也許希望在回退當(dāng)前分支的同時保留本地的變更,這就是git reset --keep命令能夠?qū)崿F(xiàn)的功能(見下圖)。
這種模式會重置暫存區(qū)(索引實體),但是仍然保留當(dāng)前本地工作目錄下未暫存的變更。如果遇到異常,該重置操作將會被終止。這意味著工作區(qū)中的變更被保存了,并且被移動到了新的提交記錄中,其工作原理和git checkout <branch>
命令處理未提交的變更記錄類似。執(zhí)行成功的情況和隱藏變更類似,強制性重置,然后取消隱藏變更。
git reset --keep 命令的工作原理是更新(工作目錄下)我們回退的目標版本和分支首部引用版本之間有差異的文件內(nèi)容。如果在分支首部和目標修訂版本之間存在任何差異,并且本地文件存在未提交的變更記錄,那么重置操作都會被終止。
1、將變更重定向到較早的修訂
假定用戶正在忙著做一些事情,不過突然發(fā)現(xiàn)自己在工作目錄下的工作本應(yīng)該屬于另外一個分支,并且這些工作和當(dāng)前分支上的上一個提交沒有什么關(guān)聯(lián)。例如,用戶也許在master分支上開始處理bug修復(fù)工作,但是現(xiàn)在發(fā)現(xiàn)當(dāng)前的工作還會影響維護分支maint。
這意味著bug修復(fù)的提交記錄應(yīng)該被加入更早的分支,起點可能是上述分支的共同祖先節(jié)點(或者是引入bug的某個位置)。這可能會導(dǎo)致master和maint分支合并相同的bug修復(fù)提交節(jié)點:
$ edit
$ git checkout -b bugfix-127
$ git reset --keep start
另外一種替代性方案是使用更簡潔的git stash
命令:
$ edit
$ git stash
$ git checkout -b bugfix-127 start
$ git stash pop
4、隱藏暫存變更
一般來說,計劃趕不上變化,當(dāng)用戶參與到一個項目中來時,經(jīng)常需要臨時保存一下當(dāng)前的工作狀態(tài),然后處理其他的工作。git stash
命令是處理這類問題的好幫手。
暫存操作會保存用戶凌亂無章的工作目錄狀態(tài),該狀態(tài)是指用戶工作目錄下已經(jīng)發(fā)生變更的被跟蹤文件(當(dāng)然,用戶還可以使用–include-untracked選項暫存未跟蹤的文件)以及暫存區(qū)的狀態(tài),系統(tǒng)保存該狀態(tài)之后,通過運行git reset --hard HEAD
命令,將會重置工作目錄和索引,回退到最近一次提交的修訂版本(為了匹配首部提交)。用戶之后還可以隨意地訪問已經(jīng)暫存的變更記錄。
暫存記錄是保存在堆棧上的:默認情況下,用戶讀取的是最后一次入棧的暫存變更(stash@{0}
)。當(dāng)然,用戶還可以查看暫存變更列表(使用git stash list
命令),并且可以顯式訪問某個特定的暫存變更記錄。
4.1、使用git stash
如果用戶不希望被其他工作打斷太久,那么可以簡單地將目前的工作暫存起來,處理完別的事務(wù)之后,再將暫存的記錄恢復(fù)即可:
$ git stash
$ ... handle interruption ...
$ git stash pop
默認情況下,git stash pop
命令會恢復(fù)最后一次暫存的變更記錄,如果恢復(fù)成功,那么該記錄將會從堆棧中刪除。若希望查看用戶的暫存記錄列表,可以使用git stash list
命令:
$ git stash list
stash@{0}: WIP on master: 049d078 Use strtol(), atoi() is deprecated
stash@{1}: WIP on master: c264051 Error checking for <number>
用戶可以聲明暫存名稱作為參數(shù)訪問任意歷史暫存記錄。例如,可以執(zhí)行通過git stash apply stash@{1}
命令訪問第二個記錄,而且用戶可以使用git stash drop stash@{1}
命令刪除該記錄(從暫存列表中刪除)。git stash pop
命令只是apply+drop的快捷方式。
Git為暫存添加的默認描述信息非常有助于用戶回憶是在哪里暫存的該記錄(給定分支或者提交),但是無法告知用戶當(dāng)時具體做了些什么,以及暫存的內(nèi)容是什么。不過用戶可以在暫存列表中像使用diff命令那樣通過git stash show -p
命令查看暫存列表細節(jié)。不過如果用戶希望了解暫存變更記錄后中斷的細節(jié),那么最好在保存當(dāng)前狀態(tài)的暫存記錄中附加更詳細的描述信息:
$ git stash save 'Add <count>'
Saved working directory and index state On master: Add <count>
HEAD is now at 049d078 Use strtol(), atoi() is deprecated
Git將會使用用戶提供的信息描述已暫存的變更:
$ git stash list
stash@{0}: On master: Add <count>
stash@{1}: WIP on master: c264051 Error checking for <number>
有時候,當(dāng)用戶在目前工作的分支上執(zhí)行git stash save
后,因為該分支上新增了太多變更,以致于出現(xiàn)無法順利執(zhí)行git stash pop
命令的現(xiàn)象,這主要是因為用戶暫存變更后,新增了不少基于該修訂的修訂版本。如果用戶希望在暫存變更之外再新建一個常規(guī)的提交,或者只是希望測試一下暫存的變更,那么可以使用git stash branch <分支名>
命令。這會在用戶保存變更的那個修訂版本的基礎(chǔ)上新建一個分支并切換到該分支,恢復(fù)用戶之前保存的變更,然后將上述暫存的變更在暫存列表中刪除。
4.2、隱藏和暫存區(qū)
默認情況下,暫存操作會重置工作目錄和暫存區(qū)狀態(tài)到HEAD引用的版本。用戶可以使用git stash
命令保留索引的狀態(tài),然后通過使用--keep-index
選項將工作區(qū)重置到暫存狀態(tài)。
用戶還可以使用git stash --patch
命令在將變更暫存之后,指定工作區(qū)的表現(xiàn)形式。
在恢復(fù)暫存變更時,Git系統(tǒng)一般會嘗試只恢復(fù)工作區(qū)的暫存變更,然后將它們和當(dāng)前工作目錄狀態(tài)整合(例如和暫存區(qū)的內(nèi)容對應(yīng))。如果在整合過程中出現(xiàn)沖突,這些記錄會被當(dāng)作普通的索引存儲到暫存區(qū)中,即使存在沖突,Git系統(tǒng)也不會丟棄暫存記錄。
用戶還可以使用–index選項恢復(fù)暫存區(qū)被隱藏的暫存記錄,如果記錄之間存在沖突,整合操作將不會成功執(zhí)行(因為暫存區(qū)沒有地方為這種沖突記錄提供存儲空間)。
4.3、暫存探幽
也許用戶恢復(fù)了某些暫存變更之后,完成了某些工作,由于某些原因又希望撤銷恢復(fù)的暫存變更?;蛘哂脩粢驗槭д`將部分暫存內(nèi)容刪除了,又或者清空了所有暫存記錄(可以使用git stash clear
命令),現(xiàn)在希望恢復(fù)這些記錄。又或者用戶想看看暫存變更之后工作目錄中的文件組織結(jié)構(gòu)。例如,用戶需要知道當(dāng)創(chuàng)建一個暫存記錄后,Git系統(tǒng)內(nèi)部到底執(zhí)行了哪些操作。
為了暫存用戶的變更記錄,Git系統(tǒng)會自動創(chuàng)建兩個提交對象:一個是和索引有關(guān)的(暫存區(qū)),另一是和工作目錄有關(guān)的。通過git stash --include- untracked
命令,Git系統(tǒng)會另外為未跟蹤文件自動創(chuàng)建提交對象。
提交對象中包含工作目錄下的工作進度,即暫存對象,并且將包含暫存區(qū)內(nèi)容的提交對象作為其第二個父對象。提交對象中存放在了一個特別的引用中:refs/stash
。工作中(WIP)和索引的提交對象中都包含用戶保存變更時的修訂版本,并且將它當(dāng)作第一個(僅對于索引提交對象來說)父對象。
我們可以使用git log --graph
命令或者gitk圖形化工具查看它們:
$ git stash save --quiet 'Add <count>'
$ git log --oneline --graph --decorate --boundary stash ^HEAD
* 81ef667 (refs/stash) On master: Add <count>
|\
| * ed95050 index on master: 765b095 Added .gitignore
|/
o 765b095 (HEAD, master) Added .gitignore
$ git show-ref --abbrev
765b095 refs/heads/master
81ef667 refs/stash
這里我們不得不使用git show-ref
命令(我們還可以使用git for-each-ref
替代),這是因為git branch -a
命令只顯示分支信息,但是不顯示相關(guān)的引用信息。
當(dāng)保存未跟蹤變更記錄時,情況和下列步驟類似:
$ git stash --include-untracked
Saved working directory and index state WIP on master: 765b095 Added\
.gitignore
HEAD is now at 765b095 Added .gitignore
$ git log --oneline --graph --decorate --boundary stash ^HEAD
*-. bb76632 (refs/stash) WIP on master: 765b095 Added .gitignore
|\ \
| | * 1ae1716 untracked files on master: 765b095 Added .gitignore
| * d093b52 index on master: 765b095 Added .gitignore
|/
o 765b095 (HEAD, B) Added .gitignore
我們可以看到,未跟蹤文件提交是WIP提交對象的第三個父提交,而且它沒有父提交。這就是暫存的工作原理,但是Git系統(tǒng)如何維護暫存棧呢?
如果你之前注意過git stash命令的輸出結(jié)果,其中的stash@{}表達式和reflog的類似,那么你應(yīng)該已經(jīng)猜到了,Git在引用日志中查找舊的暫存記錄的方式是通過refs/stash引用實現(xiàn)的:
$ git reflog stash
81ef667 stash@{0}: On master: Add <count>
bb76632 stash@{1}: WIP on master: Added .gitignore
1、暫存記錄撤銷
接下來將會演示本小節(jié)的第一個示例:撤銷以前執(zhí)行git stash apply
命令的操作結(jié)果。一個可以滿足上述需求的備選解決方案是修改暫存中和工作目錄變更有關(guān)的補丁,然后反向應(yīng)用它:
$ git stash show -p stash@{0} | git apply -R -
注意,git stash命令的-p選項會顯示強制補丁而非變更摘要。我們可以使用git show -m stash@{0}
命令(-m選項是必需的,因為WIP提交在暫存中是以合并提交的形式存在的),或者也可以簡單地使用git diff stash@{0}^1 stash@{0}
命令替代git stash show -p
。
2、恢復(fù)誤刪除的暫存
接下來將演示第二個示例:恢復(fù)誤刪除的暫存記錄。如果它們?nèi)匀辉诎姹編熘?,用戶可以通過引用搜索所有不可達的提交對象和類似的暫存記錄(它們使用嚴格模式并且附帶了一個注釋信息的合并提交對象)來恢復(fù)。
一個簡化版的解決方案可能是這樣的:
$ git fsck --unreachable |
grep "unreachable commit " | cut -d" " -f3 |
git log --stdin --merges --no-walk --grep="WIP on "
第一行是找到所有不可達的對象,第二行是過濾除了提交和與之對應(yīng)的SHA-1碼標識符之外的所有信息,第三行的意思是進一步過濾,只顯示注釋中包含"WIP on "的合并提交記錄。文章來源:http://www.zghlxwxcb.cn/news/detail-769744.html
不過這個方案并不是完美無缺的,例如查找一個自定義注釋信息的暫存記錄(該記錄是通過git stash save"信息"命令創(chuàng)建的)。文章來源地址http://www.zghlxwxcb.cn/news/detail-769744.html
到了這里,關(guān)于Git——工作區(qū)管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!