內容提要
- 忽略文件
- 忽略目錄的四種不同方式
/mytmp
/mytmp/*
**/mytmp
**/mytmp/*
- 例外
- 用
!
表示例外; - 作用于
/mytmp/*
,不能作用于/mytmp
- ignore 生效的前提
- 通配符語法
- 一個星表示任意字符;
- 兩個星表示任意路徑。
- 兩種形式:共享式和獨享式
- 官方模板
語法
當我們進行代碼開發時,把代碼存到遠程代碼庫上。但是總有一些代碼是不需要上傳的,比如編譯的中間文件,單元測試自動生成的測試報告等。這個時候我們需要告訴git
,哪些文件應該忽略,git
提供了這種機制,它通過.gitignore
配置文件來實現。通常該文件放在項目的根目錄下,我們可以手動創建它,然后編輯內容。
忽略文件
在 .gitignore
文件中編輯:
#.gitignore for java
*.class
第一行以#
開頭的是注釋,*.class
表示忽略“所有”以.class
為后綴的文件(其中*
號表示glob模式匹配的通配符)。這里的“所有”無論它在哪個目錄下。
實驗驗證下,創建多級子目錄,每個目錄創建一個.class
文件,結構如下:
? demo-gitignore git:(master) tree
.
├── L1.class
└── child1
├── L2.class
└── child2
├── L3.class
└── child3
└── L4.class
3 directories, 4 files
執行git status
,看看有沒有被忽略?
? demo-gitignore git:(master) git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
當然也可以不用通配符,例如
# project specified gitignore
Hello.xml
表示忽略“所有”名字叫Hello.xml
的文件。
忽略目錄
語法上,以/
開頭的表示忽略目錄。比如/mytmp
表示忽略“根目錄下”名叫mytmp
的目錄,并非表示“所有”。
在上述3個childX目錄下,各自創建一個mytmp
子目錄(實驗時請勿用tmp
,以免用戶目錄下的~/.gitignore
已經配置過忽略tmp
),并在每個mytmp
目錄下創建Hello.xml
文件(因為如果沒有文件,git不會理會空目錄的)。
形如:
demo-gitignore
├── child1
│ ├── child2
│ │ ├── child3
│ │ │ └── mytmp
│ │ │ └── Hello.xml
│ │ └── mytmp
│ │ └── Hello.xml
│ └── mytmp
│ └── Hello.xml
└── mytmp
└── Hello.xml
? demo-gitignore git:(master) ? git status -s
?? child1/
?? mytmp/
在.gitignore
中添加/mytmp
忽略后,再看status:
? demo-gitignore git:(master) ? git status -s
M .gitignore
?? child1/
首先發現?? mytmp/
已經不見了(被忽略了)。第一行.gitignore的變化是因為剛添加/mytmp
,尚未提交;第二行?? child1/
為什么還在?因為我們只是忽略了/mytmp
目錄,并沒有忽略其下的文件Hello.xml?其實是只忽略根目錄下的/mytmp
,子目錄下的/mytmp
并不被忽略。
? demo-gitignore git:(master) ? git add child1
? demo-gitignore git:(master) ? git status -s
M .gitignore
A child1/child2/child3/mytmp/Hello.xml
A child1/child2/mytmp/Hello.xml
A child1/mytmp/Hello.xml
上述唯獨沒有提到根目錄demo-gitignore
下的mytmp
目錄。如果要讓所有目錄下的mytmp
目錄都被忽略呢? 前綴加兩個*
號(即:**
)。
# project specified gitignore
**/mytmp
此時mytmp
,都不再顯示,無論是哪級子目錄:
? demo-gitignore git:(master) ? git status -s
M .gitignore
如果我們要“排除(不忽略)” /child1/child2/mytmp
目錄呢?
用!/child1/child2/mytmp
排除。
# project specified gitignore
**/mytmp
!/child1/child2/mytmp
結果驗證如下:
? demo-gitignore git:(master) ? git status -s
M .gitignore
A child1/child2/mytmp/Hello.xml
總結備忘
以
/
開頭忽略目錄,表示當前。例如/mytmp
表示忽略根目錄下的mytmp。
以**/
開頭,忽略所有目錄。例如**/mytmp
表示忽略所有層級下的mytmp目錄。
用!
開頭表示例外。例如!/child1/child2/mytmp
表示單獨強調“不忽略”/child1/child2/mytmp的 mytmp 目錄。
忽略的例外
如前文所說,例外用!
表示。這里補充下關于“文件”的例外。在上述的實驗環境中,新創建文件 demo-gitignore/mytmp/HelloExpectional.xml
,并配置.gitignore
如下:
# project specified gitignore
/mytmp/*
!/mytmp/HelloExpectional.xml
它表示忽略根目錄/下的mytmp子目錄下的所有文件(星號表示),但是/mytmp/HelloExpectional.xml
文件例外(不忽略)。
? demo-gitignore git:(master) ? git add .
? demo-gitignore git:(master) ? git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: .gitignore
new file: child1/child2/child3/mytmp/Hello.xml
new file: child1/child2/mytmp/Hello.xml
new file: child1/mytmp/Hello.xml
new file: mytmp/HelloExpectional.xml
如上結果,只有mytmp/Hello.xml被忽略。如預期。如果要所有mytmp呢?用**/tmp
呀。
為什么ignore 沒生效?
緊接著上面,把.gitignore
內容修改為:
# project specified gitignore
**/mytmp/*
!/mytmp/HelloExpectional.xml
查看status,發現并沒有變化?
? demo-gitignore git:(master) ? git status -s
MM .gitignore
A child1/child2/child3/mytmp/Hello.xml
A child1/child2/mytmp/Hello.xml
A child1/mytmp/Hello.xml
A mytmp/HelloExpectional.xml
預期應該是只有mytmp/HelloExceptional.xml
不被忽略,其他均被忽略。新配置為什么沒生效?因為前文git add .
的時候,已經加入git索引了,gitignore只能對untracked
狀態的資源起作用。
先把他們從tracked (to be committed)
中撤掉:
? demo-gitignore git:(master) ? git rm --cached -r child1
rm 'child1/child2/child3/mytmp/Hello.xml'
rm 'child1/child2/mytmp/Hello.xml'
rm 'child1/mytmp/Hello.xml'
? demo-gitignore git:(master) ? git rm --cached -r mytmp
rm 'mytmp/HelloExpectional.xml'
? demo-gitignore git:(master) ?
命令解釋如下:
git rm --cached
表示直接刪除“索引區”的內容(不是導出到Working dir,也不是提交到版本庫)。后面接文件,表示操作對象;-r
是當操作對象為目錄時,表示遞歸。
接著實驗看看新的ignore規則:
? demo-gitignore git:(master) ? git add .
? demo-gitignore git:(master) ? git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: .gitignore
new file: mytmp/HelloExpectional.xml
目錄忽略,它的子目錄和文件呢?
當我們忽略一個目錄時,它下面的子目錄和文件也一起被忽略嗎?
在demo-gitignore/mytmp
創建一級子目錄son-of-mytmp
和二級子目錄grandson-of-mytmp
,并各自放一個文件,如下結構:
? demo-gitignore git:(master) ? tree mytmp
mytmp
├── Hello.xml
├── HelloExpectional.xml
└── son-of-mytmp
├── grandson-of-mytmp
│ └── grandson.xml
└── son.xml
2 directories, 4 files
ignore配置:
# project specified gitignore
/mytmp
!/mytmp/HelloExpectional.xml
查看狀態:
? demo-gitignore git:(master) ? git status -s
M .gitignore
?? child1/
的的確確 根目錄下的mytmp目錄及其子目錄,都被忽略了。但與此同時奇怪的是!/mytmp/HelloExpectional.xml
“例外設置”并沒有生效?
如果調整 ignore 設置:
# project specified gitignore
/mytmp/*
!/mytmp/HelloExpectional.xml
從/mytmp
調整為/mytmp/*
,結果例外生效了。
? demo-gitignore git:(master) ? git add .
? demo-gitignore git:(master) ? git status -s
M .gitignore
A child1/child2/child3/mytmp/Hello.xml
A child1/child2/mytmp/Hello.xml
A child1/mytmp/Hello.xml
A mytmp/HelloExpectional.xml
總結備忘
忽略目錄
/mytmp
和/mytmp/*
,都會遞歸影響其子目錄和文件的忽略。
只有/mytmp/*
忽略,才能添加形如!/mytmp/HelloExpectional.xml
的例外。
glob模式語法
所謂“glob模式”就是我們常見的bash下簡化的正則表達式。就4招:
- 星號
*
,通配多個字符; - 兩個星號
**
,表示任意中間層目錄。例如a/**/z
可以匹配目錄a/z
,a/b/z
或a/b/c/z
等。 - 問號
?
,通配單個字符; - 方號
[]
,枚舉單個字符。例如[abc]
表示要么a
,要么b
,要么c
,但是ab
兩個字符是不能匹配的,只能是1個。 - 范圍
[0-9]
或[a-z]
表示任意一個數字或字母。 - 嘆號
!
,表示“取反”,表示“不忽略”的語義。
使用習慣
基本概念
-
.gitignore
文件是項目根目錄下的一個隱藏文件,不是.git
子目錄下的。 -
.gitignore
文件對其所在的目錄及其全部子目錄均有效。當然用戶級HOME目錄下~/.gitignore
文件全局有效,項目的ignore繼承覆蓋用戶級的。 - 配置文件
.gitignore
本身需要加入版本庫,以便其他組員能共享同一套資源忽略管理規則。
? demo-gitignore git:(master) ? touch .gitignore
? demo-gitignore git:(master) ? git status -s
?? .gitignore
? demo-gitignore git:(master) ? git add .gitignore
? demo-gitignore git:(master) ? git commit .gitignore -m 'create project specified gitignore conf'
共享式 與 獨享式
ignore 規則既可以選擇“共享式”讓全組員使用同樣的規則(文件位置是項目根目錄下的.gitignore
文件),好處是大家的配置一樣,不好是.gitignore
內容太多,維護太累。也可以選擇“獨享式”,只對自己生效,其他組員看不到(因為都不上傳到版本庫)。“獨享式”有兩種形式:
- 用戶級的 位置在
~/.gitignore
用戶HOME目錄下; - 項目級的 位置在
.git/info/exclude
,它也是一個ignore文件,語法規則是一樣的。注意:盡管.git
目錄一定是要上傳到版本庫的(它就是版本庫本身),但是卻留下了exclue
是不上傳的。感覺.git
的設計者很有用心。
那我們什么時候共享式,什么時候獨享式呢?個人覺得,更多的是團隊的一個約定。我們可以先對需要ignore的東西,做個大致分類:
-
操作系統層面的 比如Mac OS的
.DS_Store
, windows的Thumbs.db
; -
IDE層面的 比如Eclipse的
.project
,.settings/
和.classpath
。 IDE層面還包括“樸素IDE”,比如臨時用VIM應急修改了個東西,意外的閃崩生成了一個.swap
文件或有些編輯器會生成.bak
備份文件。 - 中間結果類 比如程序運行一下,就打些日志到文件。再比如嵌入式數據庫生成的臨時文件。
- 語言相關的 剛說的“中間結果”日志類的是通用的,無論哪種語言開發的程序都會輸出日志,除此外,還有喝多跟語言編譯相關的,比如JAVA的.class字節碼,比如Web項目構建時生成的.war包。
了解這些后,或許我們可以把前面兩類作為“獨享式”只作用于自己本地,比如你用Mac那你配Mac的ignore,用Eclipse配Eclipse的;別人用Window,他自己配置Windows的。然后把中間結果和語言相關的,弄成“共享式”的,在全組員中共享。
這么多配置需要我們自己寫嗎? 當然不用,這些問題很多開發者都是要遇到同樣的問題的,把各種環境窮舉下? 事實上有人給我們做了。
官方ignore模板
官方提供ignore模板 https://github.com/github/gitignore
它的組織形式就是按上文說的“分層組織”。比如:
- 系統層
- Mac的: macOS.gitignore
- Linux的: Linux.gitignore
- IDE層
- Eclipse的: Eclipse.gitignore
- VIM樸素編輯器的: Vim.gitignore
- NetBeans的: NetBeans.gitignore
- 語言層
- JAVA的:Java.gitignore
- GO的: Go.gitignore
- LUA的: Lua.gitignore
- C的:C.gitignore
- C++的: C++
附錄1:如何刪除已經提交,但不需要提交的資源?
盡管提倡項目開始的時候,就需要對資源ignore 規則進行設置。但是現實常常沒有那么理想,往往提交后才發現提交了一些不應該提交的東西。怎么刪除它們?
首先要區分兩類刪除:
真的不需要的,比如每次編譯產生的
.class
,這些文件真的不需要。需要,但不想提交到版本庫的。比如某些臨時的document,你打算提交到wiki,而不是版本庫。
找出“已經提交,但不需要提交的”資源
從遠程拷貝一份。之所以這么做,不用當前本地的,是因為ignore規則的存在,本地一定與遠程不是完全一致的(從文件系統的角度說的完全一致,不是git diff角度)。
git clone http://10.77.144.192:11824/blueocean/passport.git
然后,比如假設我們之前誤提交了.class
文件,那么需要找出:
find . -name "*.class"
發現./WebRoot/WEB-INF/classes/
下面居然有,刪除它們。同時在新拷貝的和本地現有的都刪除。