相关操作

本章介绍如何初始化一个仓库(repository)、开始或停止追踪(track)文件、暂存(stage)或提交(commit)更改,以及不同提交(commits)间的差异、如何配置 git 来忽略指定的文件和文件模式。

1. 获取 git 仓库

1.1 在现有目录中初始化仓库

进入该项目目录并输入:

git init

该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 git 仓库中所有的必须文件,这些文件是 git 仓库的骨干。

1.2 克隆现有的仓库

克隆仓库的命令格式是 git clone [url]

比如,要克隆 git 的可链接库 libgit2 ,可以用下面的命令:

git clone https://github.com/libgit2/libgit2

这会在当前目录下创建一个名为 libgit2 的目录,并在这个目录下初始化一个 .git 文件夹,从远程仓库拉取所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。

如果想在克隆时自定义本地仓库的名字,可以用命令:

git clone https://github.com/libgit2/libgit2 mylibgit

git 支持多种数据传输协议。上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用 SSH 传输协议,比如 user@service:path/to/repo.git

2. 记录每次更新到仓库

2.1 检查当前文件状态

git status 命令。

如果在克隆仓库后立即使用此命令,会看到类似这样的输出:

git status
On branch master
nothing to commit, working tree clean

这说明所有已跟踪文件在上次提交后都未被更改过。此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 git 会在这里列出来。最后,该命令还显示了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。分支名是 master,这是默认的分支名。

现在,我们在项目下创建一个新的 README 文件。如果之前并不存在这个文件,使用 git status 命令,会看到一个新的未跟踪文件。

未跟踪的文件意味着 git 在之前的快照(提交)中没有这些文件;git 不会自动将之纳入跟踪范围,除非你明确告诉它,这样的处理让你不必担心将生成的二进制文件或其它不想被跟踪的文件包含进来。不过现在的例子中,我们确实想要跟踪管理 README 这个文件。

2.2 跟踪新文件

使用命令 git add 开始跟踪一个文件。

所以,要跟踪 README 文件,运行:

git add README

此时再运行 git status 命令,会看到 README 文件已被追踪,并处于暂存状态。

如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。git add . 表示添加当前目录下所有文件。

2.3 暂存已修改文件

现在我们来修改一个已被跟踪的文件。如果你修改了一个名为 CONTRIBUTING.md 的已被跟踪的文件,然后运行 git status 命令,会看到已跟踪文件的内容发生了变化,但是没有放到暂存区。

要暂存这次更新,需要运行 git add 命令:

git add CONTRIBUTING.md

这是个多功能命令:

可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。将这个命令理解为「添加内容到下一次提交中」而不是「将一个文件添加到项目中」要更加合适。现在文件已暂存,下次提交时就会一并记录到仓库。

假设此时,你想要在 CONTRIBUTING.md 里再加条注释,重新编辑存盘后,准备好提交。不过且慢,再运行 git status 看看。现在 CONTRIBUTING.md 文件同时出现在暂存区和非暂存区。这怎么可能呢?好吧,实际上 git 只不过暂存了你运行 git add 命令时的版本,如果现在提交,CONTRIBUTING.md 的版本是最后一次运行 git add 命令时的那个版本,而不是运行 git commit 时,在工作目录中的当前版本。所以,运行了 git add 之后又做了修订的文件,需要重新运行 git add 把最新版本重新暂存起来。

2.4 状态简览

git status 命令的输出十分详细,但其用于有些繁琐。如果你使用 git status -s 命令或 git status --short 命令,你将得到一种更为紧凑的格式输出。运行 git status -s,状态报告输出如下:

git status -s
 M README
MM Makefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

新添加的未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。你可能注意到了 M 有两个可以出现的位置,出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入暂存区。例如,上面的状态报告显示: README 文件在工作区域被修改了但是还没有将修改后的文件放入暂存区,lib/simplegit.rb 文件被修改了并将修改后的文件放入暂存区。而 Makefile 在工作区被修改并提交到暂存区后又在工作区中被修改了,所以暂存区和工作区都有该文件被修改了的记录。

2.5 忽略文件

一般我们总会有些文件无需纳入 git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。在这种情况下,我们可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。来看一个实际的例子:

cat .gitignore
*.[oa]
*~

第一行告诉 git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。第二行告诉 git 忽略所有以 ~ 结尾的文件,许多文本编辑软件都用这样的文件名保存副本。此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。要养成一开始就设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。

文件 .gitignore 的格式规范如下:

  • 所有空行或者以 # 开头的行都会被 git 忽略。
  • 可以使用标准的 glob 模式匹配。
  • 匹配模式可以以 / 开头防止递归。
  • 匹配模式可以以 / 结尾指定目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上 ! 取反。

所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。

  • * 匹配零个或多个任意字符;
  • [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个a,要么匹配一个b,要么匹配一个c);
  • ? 只匹配一个任意字符;

    如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。

  • ** 表示匹配任意中间目录(比如 a/**/z 可以匹配 a/za/b/za/b/c/z)。

我们再看一个 .gitignore 文件的例子:

# no .a file
*.a

# but do track lib.a, even though you're ignoring .a files above
!lib.a

# only ignore the TODO file in the current directoty, not subdir/TODO
/TODO

# ignore all files in build/ directory
build/

# ignore doc/notes.txt, but not doc/server/arch.txt
doc/*.txt

# ignore all .pdf files in the doc/ directory
doc/**/*.pdf

GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,可以在 这里 找到它。

2.6 查看已暂存和未暂存的修改

使用 git diff 命令。

你可能通常会用它来回答这两个问题:当前做的哪些更新还没有暂存?有哪些更新已暂存起来准备好下次提交?git diff 将通过文件补丁的格式显示具体哪些行发生了改变。

假如再次修改 README 文件后暂存,然后编辑 CONTRIBUTING.md 文件后先不暂存。

要查看尚未暂存的文件更新了哪些部分,直接输入 git diff

git diff

此命令比较的是工作目录中当前文件和暂存区域快照之间的差异,也就是修改之后还没有暂存起来的变化内容。

若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --staged 命令。

git diff --staged

请注意,git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。所以有时候你一下子暂存了所有更新过的文件后,运行 git diff 后却什么也没有,就是这个原因。

总结:

  1. git diff --> 差异:当前文件 vs 暂存快照
  2. git diff --staged --> 差异:暂存快照 vs 仓库

2.7 提交更新

每次提交前,先用 git status 看下,是不是都已暂存起来了,然后再运行提交命令 git commit

git commit

这种方式会启动文本编辑器以便输入本次提交的说明。默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。退出编辑器时,git 会丢掉注释行,用你输入提交附带信息生成一次提交。

你也可以在 commit 命令后添加 -m 选项,将提交信息与命令放在同一行,如下所示:

git commit -m "Story 182: Fix benchmarks for speed"

提交后它会告诉你,当前是在哪个分支提交的,本次提交的完整 SHA-1 校验和是什么,以及在本次提交中,有多少文件修订过,多少行添加和删改过。

请记住,提交时记录的是放在暂存区域的快照。任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。每一次运行提交操作,都是对你项目做一次快照,以后可以回到这个状态,或者进行比较。

2.8 跳过使用暂存区域

尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit 加上 -a 选项,git 就会自动把所有已经跟踪过的文件暂存起来一并提交。

git commit -a -m 'added new benchmarks'

这样就不再需要 git add 要追踪的文件了。

2.9 移除文件

要从 git 中移除某个文件,就必须要从暂存区移除,然后提交。可以用 git rm 命令,它会连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。

不论是否手动从工作目录删除了该文件,都需要运行 git rm 来记录此次移除文件的操作:(当然也可以在手动删除文件后,用 git add 来添加记录)

git rm PROJECTS.md

下次提交时,该文件就不再纳入版本管理了。如果删除之前修改过并且已经放入到暂存区域的话,则必须要用强制删除选项 -f这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 git 恢复。

另外一种情况是,我们想把文件从 git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,让文件保存在磁盘中,但并不想让 git 继续追踪。当你忘记添加 .gitignore 文件,又不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区,这一做法尤其有用。使用 --cached 选项:

git rm --cached README

git rm 命令后可以列出文件或者目录的名字,也可以使用 glob 模式。比方说:

git rm log/\*.log

注意到星号 * 之前的反斜杠 \,因为 git 有它自己的文件模式扩展匹配方式,所以我们不用 Shell 来帮忙展开。

此命令删除 log 目录下扩展名为 .log 的所有文件。类似的比如:

git rm \*~

该命令删除以 ~ 结尾的所有文件。

2.10 移动文件

要在 git 中对文件改名,可以这么做:

git mv file_from file_to

其实,运行 git mv 就相当于运行了下面三条命令:

mv README.md README
git rm README.md
git add README

如此分开操作,git 也会意识到这是一次改名,所以不管何种方式结果都一样。两者唯一的区别是,mv 是一条命令而另一种方式需要三条命令,直接用 git mv 轻便的多。不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。