什么是pathspec
pathspec是git命令中的一个可选项,它可以用于限制git命令的作用范围,这个范围通常是指仓库中的子集(包括文件和文件夹)。git中的很多命令都可以带上该选项,比如说"git ls-files"、“git ls-tree”、“git add”、“git grep”、“git diff"和"git checkout”。
使用pathspec
pathspec可以写成多种格式去匹配git仓库中的文件。
方式一:绝对路径或相对路径
pathspec最简单的用法就是直接写成文件路径字符串。举个例子来讲,假设项目中有如下结构的文件:
└─ fruits.txt
└─ books.js
└─ src
└─ clothes.txt
└─ pants.js
可以使用git ls-files加上列出路径下的文件:
git ls-files .
### 控制台输出结果
books.js
fruits.txt
src/clothes.txt
src/pants.js
git ls-files fruits.txt
### 控制台输出
furits.txt
git ls-files ./src/clothes.txt
#### 控制台输出
src/clothes.txt
git ls-files src
### 控制台输出
src/clothes.txt
src/pants.js
上述几个命令中的git ls-files后跟的路径就是pathspec,可以是相对路径和绝对路径。
还可以输入多个参数来查看多个路径文件:
git ls-files fruits.txt ./src
### 控制台输出结果
fruits.txt
src/clothes.txt
src/pants.js
方法二:使用通配符
pathspec还可以使用通配符的这种模式去匹配文件和文件夹,通配符有"*“,”?“和”[]“三种符号。需要注意的是:”*"通配符还会在子文件夹中进行文件匹配,其他两种则不会。
*通配符
假设一个项目中有如下目录结构:
└─ fruits.txt
└─ books.js
└─ src
└─ clothes.txt
└─ pants.js
可以使用"*"通配符匹配项目中的所有带txt后缀的文件:
git ls-files '*.txt'
### 控制台输出
fruits.txt
src/clothes.txt
控制台输出了根目录的fruits.txt和src文件夹下的clothes.txt。
这里需要注意的是,在使用*通配符作为pathspec的时候,如果想同时匹配子集文件夹中的文件则需要在外面加上引号。因为加引号和不加引号会被git作为两种不同的方式进行处理。
在使用pathspec的时候没有加上引号的情况下,会默认使用shell查找文件的方式。比如:
git ls-files *.txt
### 控制台输出
fruits.txt
上面结果只输出了根目录的fruits.txt,和直接使用shell输出文件相同:
ls *.txt
### 控制台输出
fruits.txt
两种方式都只会输出根目录匹配到的fruits.txt文件。
如果在pathspec外面加上了引号,那么引号内的内容会则会以fnmatch(3)的方式去解析文件和文件夹。同时,pathspec也包含"*"通配符的情况下,则还会在子文件夹中继续寻找文件。
其他几种"*"通配符的使用场景:
git ls-files '*.*' // 匹配所有文件,包含子文件夹中的文件
### 控制台输出
books.js
fruits.txt
src/clothes.txt
src/pants.js
git ls-files 'src/*.*' // 只包含src下的所有文件
### 控制台输出
src/clothes.txt
src/pants.js
git ls-files '*/*.js' // 子目录下的js文件,如果src下还有子目录且包含js文件,也会被打印
### 控制台输出
src/pants.js
以上几种方式有点类似于正则表达式的匹配规则。
?通配符
在pathspec中使用?通配符,可以匹配任意单个字符。
假设项目中有如下目录结构:
└─ books.js
└─ books.ts
└─ src
└─ books.js
└─ books.ts
这时候如果要想找到文件名是books且后缀名是.ts和.js的文件,就可以使用?去匹配,操作如下:
git ls-files 'books.?s' // 匹配books.js和books.ts文件
### 控制台输出
books.js
books.ts
结果如上,在控制台输出了根目录下的books.js和books.ts,但是在src下的相同名称的文件并没有被匹配到。说明?通配符并不会在子目录文件夹中查找。这一点是和*通配符是不一样的。
[]通配符
[]通配符和?的使用方式比较相似,不过它的匹配字符只能在一个集合中筛选。
假设项目中有如下目录结构:
└─ books.js
└─ books.ts
└─ books.ps
└─ src
└─ books.js
└─ books.ts
└─ books.ps
如果要想找到文件名是books且后缀名是.ts和.js的文件,就不能使用?了因为会匹配到.ps结尾的文件,这时候就可以使用[]通配符,操作如下:
git ls-files 'books.[jt]s' // 匹配books.js和books.ts文件
### 控制台输出
books.js
books.ts
结果如上,成功匹配到了books.js和books.ts文件并且没有匹配到books.ps文件。和?通配符相同也不会在子文件夹中继续查找。
[]通配符还有一些比较有趣的用法,比如明确要匹配的单个字符是0-9的数字。假设项目中有如下目录结构:
└─ books.1.js
└─ books.2.ts
└─ books.3.ps
└─ books.4.js
...
└─ books.9.js
现在需要查找所有文件名为books的文件,操作如下:
git ls-files 'books.[[:digit:]].[jtp]s'
### 控制台输出
books.1.js
books.2.ts
books.3.ps
books.4.js
...
books.9.js
结果如上,在控制台成功打印了想要匹配的文件。需要注意的是,使用该操作是需要两个中括号进行包裹。还有其他的操作可以参考手册。
方法:使用魔法签名
魔法签名是一种更复杂且更加灵活的匹配方式,使用方式是在pathspect的开头加上:(signature)。signature有以下几种:top、icase、litera、glob、attr和exclude。
top
top签名可以让git命令总是从根目录开始执行不管当前是在哪层目录下。这样当想获知根目录有什么文件的时候,就不用再返回根目录。
假设项目中有如下结构:
└─ books.txt
└─ fruits.txt
└─ src
└─ pants.txt
└─ folder ------ 此时在该目录下
└─clothes.txt
在src/folder文件夹中,如果想获取根目录的所有txt文件则可以这样操作:
git ls-files ':(top)*.txt'
### 控制台输出
books.txt
fruits.txt
git ls-files ':/*.txt' // 这是一种缩写格式
### 控制台输出
books.txt
fruits.txt
在控制台成功输出了根目录的所有txt文件,还可以将:(top)进行简写使用:/方式。
icase
icase的作用是用于忽略pathspec中的大小写。比如说用于匹配图片时,jpg和JPG的两种后缀的匹配。
假设项目中有如下结构:
└─ pig.jpg
└─ dog.JPG
现在需要匹配所有的jpg图片,可以使用如下操作:
git ls-files ':(icase)*.jpg'
### 控制台输出
pig.jpg
dog.JPG
这样就能忽略大小写,成功匹配到两张图片.
literal
literal可以将某些具有特殊意义的字符作为真正的字面量去匹配,比如说将*和?作为字符串而不是作为通配符。
假设项目中有如下结构:
└─ *.txt
└─ fruits.txt
如果直接使用"*.txt"进行匹配的话会输出*.txt文件和fruits.txt文件。但是现在只想匹配*.txt文件该怎么办?很简单,就是需要将*通配符进行转义,操作如下:
git ls-files ':(literal)*.txt'
#控制台输出
*.txt
如上直接将*转成了字面量字符串进行匹配。
glob
glob魔法签名的作用就是在匹配文件的时候从当前目录出发。格式是在开头加上:(glob),然后在路径中使用两个**。
glob魔法签名的第一种用法就是加在路径的最前面。假设项目中有如下目录结构:
└─ fruits.txt
└─ src
└─ books.txt
└─ folder
└─ fruits.txt
如果只想匹配项目中所有的fruits.txt文件,可以用*通配符去实现么?试试看*/fruits.txt:
git ls-files '*/fruits.txt'
### 控制台输出
src/foler/fruits.txt
如上结果只能输出子目录中的fruits.txt文件,在根目录中的文件并没有被匹配到。这种情况就可以使用glob:
git ls-files ':(glob)**/fruits.txt'
### 控制台输出
fruits.txt
src/folder/fruits.txt
如上结果在根目录和子目录的fruits.txt文件都被匹配到了。
glob还可以加在路径的中间,来表示可以匹配0个或多个文件目录。假设有如下结构目录:
└─ index.html
└─ src
└─ index.html
└─ page.html
└─ header
└─ index.html
└─ page.html
└─ logo
└─ index.html
└─ page.html
每一层目录下都有一个index.html和page.html,此时的目标是只想输出src下的所有index.html文件,如果只使用*的话是匹配不到src目录下的index.html文件的:
git ls-files 'src/*/index.html'
### 控制台输出
src/header/index.html
src/header/logo/index.html
这时候可以使用glob模式去匹配:
git ls-files ':(glob)src/**/index.html'
### 控制台输出
src/index.html
src/header/index.html
src/header/logo/index.html
glob还可以加在路径的末尾,表示匹配该路径下的所有文件,使用效果和*差不多,假设有如下目录结构:
└─ index.html
└─ src
└─ index.html
└─ header
└─ index.html
└─ logo
└─ index.html
现在需要匹配src下的所有index.html文件:
git ls-files 'src/*'
### 控制台输出
src/index.html
src/header/index.html
src/header/logo/index.html
git ls-files ':(glob)src/**'
### 控制台输出
src/index.html
src/header/index.html
src/header/logo/index.html
attr
可以使用.gitattributes文件,将一些比较常用的路径缩用一个属性名去代替。
假设项目中有如下结构:
└─ index.html
└─ src
└─ index.html
└─ page.html
└─ header
└─ index.html
└─ page.html
└─ logo
└─ index.html
└─ page.html
其中的logo目录是常用目录,可以将其添加到.gitattributes文件中为它设置快捷属性名:
src/header/logo/* logo
然后就可以在命令中使用:
git ls-files ':(attr:logo)'
### 控制台输出
src/header/logo/index.html
src/header/logo/page.html
还可以使用attr进行取反,也就是不包括快捷属性匹配的所有文件:
git ls-files ':(attr:!logo)'
### 控制台输出
index.html
src/index.html
src/page.html
src/header/index.html
src/header/page.html
.gitattributes文件的用法可以参考git的官网。
exclude
这个签名的作用是匹配除了指定路径文件以外的所有文件。该签名具有缩写格式(!或^)
假设项目中有如下目录结构:
└─ index.html
└─ src
└─ index.html
└─ header
└─ index.html
└─ logo
└─ index.html
假如想匹配除了src/header/logo下的所有文件,那么就可以用如下方法:
git ls-files ':(exclude)src/header/logo/*'
// 或
git ls-files ':!src/header/logo/*'
// 或
git ls-files ':^src/header/logo/*'
#### 控制台输出
index.html
src/index.html
src/header/index.html
组合使用魔法签名
在使用pathspec时可以将多个魔法签名写在一起,然后使用","去分隔。
假设项目中有如下结构:
└─ index.html
└─ src
└─ index.html
└─ header
└─ index.html
└─ logo
└─ index.html
└─ avatar.JPG
如果想匹配除了src/header/logo/avatar.JPG以外的所有文件:
git ls-files ':!(icase,glob,exclude)src/**/*.jpg'
### 控制台输出
index.html
src/index.html
src/header/index.html
src/header/logo/index.html
但是需要注意的是glob和literal不能一起使用。否则就会报错:
总结
pathspec的作用就是用于限制git命令的作用域范围,但是可以使用通配符或者魔法签名等方式做到更加精细的控制,让git命令的作用域范围更加的灵活多变。
参考
https://marvinsblog.net/post/2019-11-24-git-pathspec-intro/
https://css-tricks.com/git-pathspecs-and-how-to-use-them/
https://git-scm.com/docs/gitglossary/en#def_tree-ish