美文网首页收藏问题Git使用CocoaPods
手把手带你玩git之gitignore

手把手带你玩git之gitignore

作者: 区影 | 来源:发表于2016-12-07 18:31 被阅读340次

内容提要

  • 忽略文件
  • 忽略目录的四种不同方式
  • /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/za/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

它的组织形式就是按上文说的“分层组织”。比如:

  • 系统层
  • IDE层
  • 语言层

附录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/ 下面居然有,删除它们。同时在新拷贝的和本地现有的都删除。

相关文章

网友评论

本文标题:手把手带你玩git之gitignore

本文链接:https://www.haomeiwen.com/subject/oljdmttx.html