Git05-文件管理和索引
- 如果项目是用版本控制系统进行管理的,可以在工作目录里编辑,然后把修改提交给版本库来保管。Git的工作原理与之类似,但是它在工作目录和版本库之间添加索引(index),用来暂存(stage)或收集修改。当使用Git管理代码时,在工作目录下的编辑,会在索引中进行累积,提交时会把索引中累积的修改作为一次变更。
- 可以把Git的索引看作一组打算的或预期的修改。这就意味着,可以在提交之前添加、删除、移动或者重复编辑文件,只有在提交后才会在版本库里实现积累的变更。而大多数重要的工作其实是在提交之前完成的。
- 注意,一次提交其实有两个步骤:暂存变更和提交变更。在工作目录下而不在索引中的变更是没暂存的,因此也不会提交。
- 当添加或修改文件时,Git允许把两步合并成一步。
git commit index.html
-
- 当移动或者删除文件时,这两步必须分开执行。
git rm index.html git commit
1、索引的作用
- Linus Torvalds曾说:如果不了解索引的目的,就不能完全领会Git的强大之处。
- Git的索引不包含任何文件内容,它仅仅追踪你想要提交的那些内容。当执行git commit命令的时候,Git会通过检查索引而不是工作目录来找到提交的内容。
- 在任何时候都可通过git status命令来查询索引的状态。它会明确列出哪些文件是暂存的。也可以通过一些底层命令来查看Git的内部状态,例如git is-files。
- 在暂存过程中,git diff命令十分有用。这条命令可以显示两组不同的差异:
- git diff可以列出仍留在工作目录中且未暂存的变更。
- git diff --cached会列出已经暂存的变更。
- git diff的两种形式可以引导你完成暂存变更的过程。最初,git diff显示所有修改的,--cached是空的。而当暂存时,前者的集合会收缩,后者会增大。如果所有修改都暂存了并准备提交,--cached将是满的,而git diff为空。
2、Git中的文件分类
- Git将所有文件分成3类:已追踪的、被忽略的以及未追踪的。
- 已追踪的(Tracked)文件是指已经在版本库中的文件,或者是已暂存到索引中的文件。如果想将新文件somefile添加为已追踪的文件,需要执行git add somefile。
- 被忽略的(Ignored)文件必须在版本库中被明确声明为不可见或被忽略,即使它可能在工作目录中出现。一个软件项目通常都会有很多被忽略的文件。普通被忽略的文件包括临时文件、个人笔记、编译器输出文件以及构建过程中自动生成的大多数文件等。Git维护一个默认忽略文件列表,也可以配置版本库来识别其他文件。
- 未追踪的(Untracked)文件是指那些不在前两类中的文件。Git把工作目录下的所有文件当成一个集合,减去已追踪的文件和被忽略的文件,剩下的部分作为未追踪的文件。
1、创建一个新的版本库
- 通过创建一个全新的工作目录和版本库,然后探讨不同类别的文件。
//(1)创建目录 ]# mkdir my_stuff ]# cd my_stuff/ //(2)初始化一个新的版本库 ]# git init Initialized empty Git repository in /root/my_stuff/.git/
2、未追踪的文件
- 最初,目录里没有文件,因此未追踪的文件是空的,已追踪的文件和被忽略的文件也都是空的。但创建data文件后,git status就会报告一个未追踪的文件。
//(1)创建一个新文件 ]# echo "New data" > data //(2)查看索引状态 ]# git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) data nothing added to commit but untracked files present (use "git add" to track)
3、被忽略的文件
- 只需要将要忽略的文件名添加到一个特殊的文件.gitignore中就可以了。
- 这样main.o就被忽略了,但是git status现在显示了一个新的未追踪的文件.gitignore。虽然.gitignore文件对Git有特殊的意义,但是它和版本库中其他普通文件都是同样管理的。除非把.gitignore添加到索引中,否则Git仍会把它当成未追踪的文件。
//(1)创建一个新文件 ]# touch main.o //(2)查看索引状态 ]# git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) data main.o nothing added to commit but untracked files present (use "git add" to track) //(3)忽略main.o文件 ]# echo main.o > .gitignore //(4)查看索引状态 ]# git status On branch master No commits yet Untracked files: (use "git add <file>..." to include in what will be committed) .gitignore data nothing added to commit but untracked files present (use "git add" to track)
3、使用git add
- git add命令将暂存一个文件。就Git文件分类而言,如果一个文件是未追踪的,那么git add就会将文件的状态转化成已追踪的(添加到索引中)。如果git add作用于一个目录,那么该目录下的文件和子目录都会递归暂存起来。
- 也会将每个文件的全部内容都被复制到对象库中,并且按文件的SHA1的值来索引。暂存一个文件也称作缓存(caching)一个文件,或者叫“把文件放进索引”。
1、改变文件状态
- 上面最后一条命令,git status显示有两个未追踪的文件,并提醒你要追踪一个文件,只需要使用git add就可以了。
- 在执行git add命令后,就会将data和.gitignore文件暂存和追踪了,并准备在下次提交的时候加到版本库中。
//继续上一节的例子 //(1)改变文件的状态为已追踪的 ]# git add .gitignore data //(2)查看索引状态 ]# git status On branch master No commits yet Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: data
2、查看已追踪的文件
- 使用git ls-files命令可以查看暂存文件和其SHA1值。
]# git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 .gitignore 100644 534469f67ae5ce72a7a274faf30dee3c2ea1746d 0 data
3、编辑文件
- 版本库中大多数的日常变化可能只是简单的编辑。在任何编辑之后,提交之前,请必须执行git add命令,用最新版本的文件去更新索引。如果不这么做,你将会得到两个不同版本的文件:一个是在对象库里被捕获并被索引引用的,另一个则在你的工作目录下。
//(1)编辑data文件 ]# vim data New data And some more data now //(2)查看文件的SHA1值 ]# git hash-object data e476983f39f6e4f453f0fe4a859410f63b58b500 //(3)更新索引 ]# git add data //(4)查看已追踪的文件 ]# git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 .gitignore 100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0 data
- 现在索引有了更新后的文件版本。也就是说,“文件已经暂存了”,或者简单来说,"data文件在索引中”。后一种说法不是很准确,因为实际上文件存储在对象库中,索引只是指向它而已。
- 看似无用地处理SHA1散列值和索引却带来一个关键点:与其把git add看成“添加这个文件”,不如看作“添加这个内容”。
- 在任何情况下,最重要的事是要记住工作目录下的文件版本和索引中暂存的文件版本可能是不同步的。当提交的时候,Git会使用索引中的文件版本。
- 注意,对于git add或git commit而言,--interactive选项都会是探索哪个文件将为提交而暂存很有用的方式。
4、使用git commit
- git commit的-a或者--all选项会让提交自动暂存所有GIT已知的且未暂存的文件,包括从工作副本中删除已追踪的文件。
1、创建一个新的版本库
//(1)创建目录 ]# mkdir commit-all-example ]# cd commit-all-example/ //(2)初始化一个新的版本库 ]# git init Initialized empty Git repository in /root/commit-all-example/.git/
2、创建一个提交
//(1)创建新文件 ]# echo something >> ready ]# echo somthing else >> notyet //(2)暂存文件 ]# git add ready notyet //(3)进行提交 ]# git commit -m "Setup"
3、修改文件并暂存
- 修改ready文件并用"git add"把它添加到索引中。
//(1)修改文件 ]# vim ready something add something //(2)暂存文件 ]# git add ready
4、仅修改文件,不暂存
- 仅修改notyet文件,不进行暂存。
]# vim notyet somthing else add somthing
5、添加子目录和文件,不暂存
- 在一个子目录里添加一个新文件,但不执行git add命令。
]# mkdir subdir ]# echo Nope >> subdir/new
6、查看索引状态
]# git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: ready Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: notyet Untracked files: (use "git add <file>..." to include in what will be committed) subdir/
7、常规提交,不带--all
- 仅会提交已经暂存的文件
//(1)提交,不带--all ]# git commit -m "no --all" [master e220346] no --all 1 file changed, 1 insertion(+) //(2)查看索引状态 ]# git status On branch master Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: notyet Untracked files: (use "git add <file>..." to include in what will be committed) subdir/
8、提交,带--all
- git commit --all命令会递归遍历整个版本库,暂存所有已知的和修改的文件,然后提交它们。
- 由于subdir/是一个全新的目录,而且该目录下没有任何文件名或路径是已追踪的,所以,即使是-all选项也不能将其提交。
//(1)提交,带--all ]# git commit --all -m "have --all" [master 1988472] have --all 1 file changed, 1 insertion(+) //(2)查看索引状态 ]# git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) subdir/ nothing added to commit but untracked files present (use "git add" to track)
- Git会递归遍历整个版本库,查找修改的和删除的文件,但是全新的文件目录subdir及其中所有文件还是不会成为提交的一部分。
5、使用git rm
5.1、删除git中的文件
- git rm命令会将文件从索引和工作目录中都删除,但并不会删除该文件在版本库中blob对象。文件的任何对象,只要是提交到版本库中,就会留在对象库里并保存历史记录。
- git rm --cached命令会将文件从索引中删除,但不会删除工作目录中的。
- Git可以从索引或者同时从索引和工作目录中删除一个文件。Git不会只从工作目录中删除一个文件,普通的rm命令可用于这一目的。
- git rm也是对索引进行操作的命令,所以它对没有添加到版本库或索引中的文件是不起作用的
//(1)查看索引中的文件 ]# git ls-files -s 100644 52a61c51469a5776fc3b609214b13507b4489068 0 notyet 100644 c5aaf37621c81151a70927c3e2762776d0200b7e 0 ready //(2)同时删除索引和工作目录中的文件 ]# git rm notyet //(3)要将一个文件由已暂存的转化成未暂存的 ]# git rm --cached ready
- 注意,使用gitrm --cached会把文件标记为未追踪的,却在工作目录下仍留有一份副本,这是很危险的。因为你也许会忘记这个文件是不再被追踪的Git要检查工作文件的内容是最新的,使用这种方法则无视了这个检查所以要谨慎使用。
- Git在删除一个文件之前,它会先检查当前分支中的最新版本(Git命令调用HEAD的版本)与工作目录中的文件是否一致。这个验证会防止文件的修改(由于你的编辑)意外丢失。
- 可以使用git rm -f来强制删除文件。强制就是明确授权,即使从上次提交以来你已经修改了该文件,还是会删除它。
5.2、恢复工作目录中的文件
- 注意,该命令只是执行了git rm,但没有再次进行提交时,才会有作用。
]# git checkout HEAD -- notyet
6、使用git mv
- Git会先删除索引中的路径名data,并添加新的路径名newdata,然后将对象库中的原始blob对象与newdata重新关联起来。
1、移动git中的文件
//(1)查看索引中的文件 ]# git ls-files -s 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 .gitignore 100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0 data //(2)移动git中的文件 ]# git mv data newdata
2、查看是否移动了
//(1)查看索引中的文件 ]# git ls-files -s 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 .gitignore 100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0 newdata //(2)查看索引状态 ]# git status On branch master Changes to be committed: (use "git restore --staged <file>..." to unstage) renamed: data -> newdata
3、提交
]# git commit -m "Moved data to newdata" [master 0a26c3b] Moved data to newdata 1 file changed, 0 insertions(+), 0 deletions(-) rename data => newdata (100%)
4、查看文件的历史记录
- VCS的经典问题之一就是文件重命名会导致它们丢失对文件历史记录的追踪。而Git即使经历过重命名,也仍然能保留此信息。
]# git log --follow newdata commit 0a26c3b383e841274dd1459bd878a009ff80565c (HEAD -> master) Author: heng ha <hengha@123.com> Date: Fri Mar 17 20:23:00 2023 +0800 Moved data to newdata commit d4b3c014c9537478e8601e099a0519fe01cfc110 Author: heng ha <hengha@123.com> Date: Fri Mar 17 20:05:42 2023 +0800 have --all
7、.gitignore文件
可以忽略任何文件,只要将想要忽略的文件的文件名加到同一目录下的.gitignore中。
- .gitignore文件可以包含一个文件名模式列表,指定哪些文件要忽略。.gitignore文件的格式如下:
- 空行会被忽略。
- 以"#"开头的行用于注释。如果#跟在其他字符后面,它就不表示注释了。
- 文件名匹配所在目录(及子目录)中的所有同名文件。
- 目录名由末尾的反斜线(/)标记,可以匹配同名的目录和子目录,但不匹配文件或符号链接。
- 可以使用shell通配符,如星号(*),这种模式可扩展为shell通配模式。正如标准shell通配符一样,因为不能跨目录匹配,所以一个星号只能匹配一个文件或目录名。但对于那些通过斜线来指定目录名的模式(例如,debug/32bit/*.o),星号仍可以成为其中一部分。
- 以"!"开头,会对该行其余部分的模式进行取反。
- Git允许在版本库中任何目录下都有.gitignore文件。每个.gitignore文件都只影响该目录及其所有子目录。.gitignore的规则也是级联的:可以覆盖高层目录中的规则,只要在其子目录包含一个取反模式(使用起始的“!”)。
- 为了解决多个.gitignore目录的层次结构问题,也为了允许命令行对忽略文件列表的增编,Git按照下列从高到低的优先顺序:
- 在命令行上指定的模式。
- 从相同目录的.gitignore文件中读取的模式。
- 上层目录中的模式,向上进行。因此,当前目录的模式能覆盖上层目录的模式,而最接近当前目录的上层目录的模式优先于更上层的目录的模式。
- .git/info/exclude文件的模式。
- 配置变量core.excludedfile指定的文件中的模式。
- 因为在版本库中.gitignore文件被视为普通文件,所以在复制操作过程中它会被复制,并适用于你的版本库的所有副本。一般情况下,只有当模式普遍适用于所有派生的版本库时,才应该把条目放进版本控制下的.gitignore文件中。
- 如果排除模式某种程度上特定于你的版本库,并且不(或可能不)适用于其他人复制的版本库,那么这个模式应该放到.git/info/exclude文件里,因为它在复制操作期间不会传播。它的格式和适用对象与.gitignore文件是一样的。
- 下面一种情景,排除.o文件(编译器从源码生成的)是很典型的。为了忽略.o文件,要将*.o添加到顶层的.gitignore文件中。但是,如果有一个特定的*.o文件是由其他人提供的,你不能自己生成一个替代,那该怎么办?
//(1)进入顶级目录 ]# cd my_stuff/ //(2)在顶级目录中设置,排除所有以.o结尾的文件 ]# vim .gitignore *.o //(3)进入子目录 ]# cd subdir/ //(4)不排除该子目录中的driver.o文件 ]# vim .gitignore !driver.o
8、Git中对象模型和文件的详细视图
- 让我们跟随该系列的4幅图来可视化file1和file2文件从编辑到在索引中暂存,再到最终提交的整个过程,加深我们对文件管理的理解。下面的每一幅图都会同时显示你的工作目录、索引以及对象库。为了简单起见,让我们只看master分支。
- 初始状态如图5-1所示。在这里,工作目录包含file1和file2两个文件,分别包含内容“foo”和“bar,”。
- 除了工作目录下的file1和file2之外,master分支还有一个提交,它记录了跟file1和file2内容完全一样的“foo”和“bar,”的树。此外,该索引记录两个SHA1值a23bf和9d3a2,与那两个文件分别对应。工作目录、索引以及对象库都是同步和一致的。没有什么是脏的。
- 图5-2显示了在工作目录中对file1编辑后的变化,现在它的内容包含“quux.”。索引和对象库中没有变化,但是工作目录现在是脏的。
- 当使用git add filel来暂存file1的编辑时,一些有趣的变化发生了。
- 如图5-3所示,Git首先取出工作目录中filel的版本,为它的内容计算一个SHA1的散列ID(bd71363),然后把那个ID保存在对象库中。接下来,Git就会记录在索引中的filel路径名已更新为新的bd71363的SHA1值。
- 由于file2的内容未发生改变而且没有任何gitadd来暂存file2,因此索引继续指向原始blob对象。此时,你已经在索引中暂存了file1文件,而且工作目录和索引是一致的。不过,就HEAD而言,索引是脏的,因为索引中的树跟在master分支的HEAD提交的树在对象库里是不一样的。
- 最后,当所有变更都暂存到索引中后,一个提交将它们应用到版本库中。git commit的作用如图5-4所示。
- 如图5-4所示,提交启动了三个步骤。首先,虚拟树对象(即索引)在转换成一个真实的树对象后,会以SHA1命名,然后放到对象库中。其次,用你的日志消息创建一个新的提交对象。新的提交将会指向新创建的树对象以及前一个或父提交。最后,master分支的引用从最近一次提交移动到新创建的提交对象,成为新的master HEAD。
- 一个有趣的细节是,工作目录、索引和对象库(由master分支的HEAD表示)再次同步,变得一致了,就如同它们在图5-1中一样。
# #