凌云的博客

行胜于言

跟我一起学Git (三) 文件管理和Index

分类:Git| 发布时间:2014-03-03 01:36:09


本文主要介绍如何管理index和你的文件。在Git中如何增加、 删除以及重命名文件,如何登记index文件的状态。 最后还讲了.gitignore文件以及相关规则。

关于Index

当你使用git commit提交修改时,Git根据你的index而不是工作目录来确定要提交的东西。 你可以通过git status来查看index的状态。 你可以通过git diff来查看工作目录和index的差异,而通过git diff --cached可以查看当前的HEAD和index的差异。

在Git中文件的分类

  • Tracked tracked文件是任何已在版本库中或者任何已经在idex文件中staged的文件,可以通过git add xx增加一个文件
  • Ignored ignored文件是那些在Git仓库中显式声明为"invisible"或"ignored"的文件
  • Untracked 这类文件是不属于以上两类的在工作目录的其他文件

下面来看一个实例:

$ mkdir my_stuff
$ cd my_stuff/
$ git init
Initialized empty Git repository in g:/src/my_stuff/.git/
$ git status
# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)

$ echo "New data" > data
$ git status
# On branch master
#
# Initial commit
#
# 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)

初始情况下,代码库是没有文件的。因此 staged、ignore和untracked类型的文件集都是空的。 当你添加了一个文件后,git status报告有一个untracked的文件。 在你的工作环境或者编译环境中,通常会有临时文件。这种文件不应该作为代码库的源文件。 如果你需要忽略一个文件,你需要做的只是将这个文件添加到.gitignore文件中。

$ touch main.o
$ git status
# On branch master
#
# Initial commit
#
# 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)

$ echo main.o > .gitignore
$ git status
# On branch master
#
# Initial commit
#
# 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)

可以看到,main.o已经被忽略了,但是需要注意的是:虽然.gitignore对于Git来说是有特殊意义的, 但Git也会将.gitignore作为代码库中的普通文件来管理。

使用git add

git add用于stages一个文件。如果一个文件是untracked的,git add会将这个文件的状态变为tracked。 如果git add是用于一个目录的,那么这个目录的所有文件和子目录都会被staged。

让我们继续上一节的例子:

$ git status
# On branch master
#
# Initial commit
#
# 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)

$ git add data .gitignore
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   .gitignore
#       new file:   data
#

可以看到在使用git add之后,两个文件由untracked变为tracked了。 并且这两个文件将会在下一次提交的时候正式添加到代码库中。 根据Git的对象模型,当你使用git add的时候,整个文件的内容会被复制到object store。 Staging一个文件又被称为“缓存一个文件”或者“将一个文件放到目录中”。

你可以通过git ls-files查看那些staged的文件的SHA1值和文件名:

$ git ls-files --stage
100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0       .gitignore
100644 534469f67ae5ce72a7a274faf30dee3c2ea1746d 0       data

如果你修改了一些还没提交的文件(已经用git add staged了这个文件), 那么你需要再次使用git add更新这个文件。 否则将会有两个版本的文件,一个版本是git add时的,另一个是你工作目录的。

我们继续上面的例子来演示这种情况:

$ git ls-files --stage
100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0       .gitignore
100644 534469f67ae5ce72a7a274faf30dee3c2ea1746d 0       data

# 将data的内容修改为
$ cat data
New data
And some more data now
$ git hash-object data
e476983f39f6e4f453f0fe4a859410f63b58b500
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   .gitignore
#       new file:   data
#
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#       modified:   data
#

当文件修改后,在object store和index文件中的data的SHA1是 534469f67ae5ce72a7a274faf30dee3c2ea1746d, 但是我们工作目录中的data的SHA1是 e476983f39f6e4f453f0fe4a859410f63b58b500 。

让我们更新这个文件到object store和index文件中:

$ git add data
$ git ls-files --stage
100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0       .gitignore
100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0       data
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   .gitignore
#       new file:   data
#

可以在使用git add 或 git commit命令时添加--interactive参数来进行交互式的操作。

使用git commit的一些提示

使用git commit --all

在git commit时使用-a 或者 --all 选项会在提交之前自动将所有 tracked文件的修改(包括那些tracked文件的删除)staged。

编写提交日志

如果你是在编辑器中编写日志信息,并且由于某些原因想取消提交, 那么直接退出编辑器并且不保存内容就行了。 但是如果你已经保存了信息,但是没退出编辑器, 那么需要将日志信息清空然后保存再退出从而取消提交。

使用git rm(Using git rm)

git rm用于删除一个文件,很自然地可以认为它是git add的反操作。 但是因为错误地删除一个文件有可能会导致问题发生,所以Git会更认真地对git rm进行检查。

继续上一节的例子:

$ echo "Random stuff" > oops
$ git rm oops
fatal: pathspec 'oops' did not match any files

因为git rm也会对index文件进行操作,这个命令不会删除那些不在Git仓库或者index中的文件。

$ git add oops
$ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#       new file:   .gitignore
#       new file:   data
#       new file:   oops
#

可以使用git rm --cached将一个文件从staged转为unstaged:

$ git ls-files --stage
100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0       .gitignore
100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0       data
100644 fcd87b055f261557434fa9956e6ce29433a5cd1c 0       oops
$ git rm --cached oops
rm 'oops'
$ git ls-files --stage
100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0       .gitignore
100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0       data

使用git rm --cached会将一个文件从index中删除,但会保留在工作目录中。

如果你希望在下一次提交时删除文件,只需要使用git rm命令:

$ git add  data .gitignore
$ git commit  -m "Add some files"
[master (root-commit) f917c45] Add some files
2 files changed, 3 insertions(+)
create mode 100644 .gitignore
create mode 100644 data
$ git rm data
rm 'data'
$ rm oops
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       deleted:    data
#

需要注意的是在使用git rm时,Git会检查工作目录的文件是否和 当前分支的最新提交中的文件一致,如果不一致Git会拒绝删除。 当然你可以使用git rm -f强制删除这个文件,而不检查是否一致。

如果你希望将删除的文件重新添加回来,你首先可能会想到以下命令:

$ git add data
fatal: pathspec 'data' did not match any files

糟!git rm删除了工作目录的文件。 那应该怎么办呢?别担心,版本管理系统很擅长恢复旧版本的文件。

$ git checkout HEAD -- data
$ git status
# On branch master
nothing to commit, working directory clean
$ cat data
New data
And some more data now

使用git mv

可以使用git mv来移动文件:

$ git mv data mydata
$ git status
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       renamed:    data -> mydata
#
$ git commit -m"Moved data to mydata"
[master 9ad02b5] Moved data to mydata
1 file changed, 0 insertions(+), 0 deletions(-)
rename data => mydata (100%)

如果你使用git log来查看重命名后的文件的日志,你会发现只有这么一条:

$ git log mydata
commit 9ad02b5370332ce5b00d3d78135cf6cd5ea5ce7e
Author: Jianlong Chen <jianlong99@gmail.com>
Date:   Thu Jan 30 13:12:44 2014 +0800

Moved data to mydata

如果要查看完整的历史,可以使用--follow参数:

$ git log --follow mydata
commit 9ad02b5370332ce5b00d3d78135cf6cd5ea5ce7e
Author: Jianlong Chen <jianlong99@gmail.com>
Date:   Thu Jan 30 13:12:44 2014 +0800

Moved data to mydata

commit f917c4539b5a923f6f19ab8dc2fb58f0c65163c3
Author: Jianlong Chen <jianlong99@gmail.com>
Date:   Thu Jan 30 13:01:14 2014 +0800

Add some files

.gitignore文件

.gitignore文件的格式如下:

  • 空行或者以#开头的行:忽略
  • 一个简单的字面文件名,在任何子目录下匹配的文件
  • 以/结尾的目录名,以/的名字表示目录名,Git会忽略这个名字的目录但不会忽略这个名字的文件
  • shell通配符
  • 以!开头的路径表示反转这个路径的含义

每个目录都可以有.gitignore的,关于.gitignore的优先级如下(由高到低):

  • 在命令行指定的规则
  • 在当前目录的.gitignore
  • 在父目录,父目录的父目录一直到到工作目录的顶层。离当前目录越近的优先级越高
  • 在.git/info/exclude文件中的规则
  • 在配置项core.excludefile中的规则

在.gitignore中的规则是全局的,而.git/info/exclude和core.excludefile 中的规则在git clone时不会被复制。