如今咱们手上有了一个真实项目的 Git 仓库,并从这个仓库中取出了全部文件的工做拷贝。 接下来,对这些文件作些修改,在完成了一个阶段的目标以后,提交本次更新到仓库。git
请记住,你工做目录下的每个文件都不外乎这两种状态:已跟踪或未跟踪。 已跟踪的文件是指那些被归入了版本控制的文件,在上一次快照中有它们的记录,在工做一段时间后,它们的状态可能处于未修改,已修改或已放入暂存区。 工做目录中除已跟踪文件之外的全部其它文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有放入暂存区。 初次克隆某个仓库的时候,工做目录中的全部文件都属于已跟踪文件,并处于未修改状态。github
编辑过某些文件以后,因为自上次提交后你对它们作了修改,Git 将它们标记为已修改文件。 咱们逐步将这些修改过的文件放入暂存区,而后提交全部暂存了的修改,如此反复。因此使用 Git 时文件的生命周期以下:正则表达式
Figure 2-1. 文件的状态变化周期shell
要查看哪些文件处于什么状态,能够用 git status
命令。 若是在克隆仓库后当即使用此命令,会看到相似这样的输出:vim
$ git statusOn branch masternothing to commit, working directory clean
这说明你如今的工做目录至关干净。换句话说,全部已跟踪文件在上次提交后都未被更改过。 此外,上面的信息还代表,当前目录下没有出现任何处于未跟踪状态的新文件,不然 Git 会在这里列出来。 最后,该命令还显示了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。 如今,分支名是 “master”,这是默认的分支名。 咱们在 Git 分支 会详细讨论分支和引用。安全
如今,让咱们在项目下建立一个新的 README 文件。 若是以前并不存在这个文件,使用 git status
命令,你将看到一个新的未跟踪文件:服务器
$ echo 'My Project' > README $ git statusOn branch masterUntracked files: (use "git add <file>..." to include in what will be committed) READMEnothing added to commit but untracked files present (use "git add" to track)
在状态报告中能够看到新建的 README 文件出如今 Untracked files
下面。 未跟踪的文件意味着 Git 在以前的快照(提交)中没有这些文件;Git 不会自动将之归入跟踪范围,除非你明明白白地告诉它“我须要跟踪该文件”, 这样的处理让你没必要担忧将生成的二进制文件或其它不想被跟踪的文件包含进来。 不过如今的例子中,咱们确实想要跟踪管理 README 这个文件。less
使用命令 git add
开始跟踪一个文件。 因此,要跟踪 README 文件,运行:编辑器
$ git add README
此时再运行 git status
命令,会看到 README 文件已被跟踪,并处于暂存状态:工具
$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README
只要在 Changes to be committed
这行下面的,就说明是已暂存状态。 若是此时提交,那么该文件此时此刻的版本将被留存在历史记录中。 你可能会想起以前咱们使用 git init
后就运行了 git add (files)
命令,开始跟踪当前目录下的文件。 git add
命令使用文件或目录的路径做为参数;若是参数是目录的路径,该命令将递归地跟踪该目录下的全部文件。
如今咱们来修改一个已被跟踪的文件。 若是你修改了一个名为 CONTRIBUTING.md
的已被跟踪的文件,而后运行 git status
命令,会看到下面内容:
$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: READMEChanges 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: CONTRIBUTING.md
文件 CONTRIBUTING.md
出如今 Changes not staged for commit
这行下面,说明已跟踪文件的内容发生了变化,但尚未放到暂存区。 要暂存此次更新,须要运行 git add
命令。 这是个多功能命令:能够用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这个命令理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。 如今让咱们运行 git add
将"CONTRIBUTING.md"放到暂存区,而后再看看 git status
的输出:
$ git add CONTRIBUTING.md $ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: CONTRIBUTING.md
如今两个文件都已暂存,下次提交时就会一并记录到仓库。 假设此时,你想要在 CONTRIBUTING.md
里再加条注释, 从新编辑存盘后,准备好提交。 不过且慢,再运行 git status
看看:
$ vim CONTRIBUTING.md $ git status On branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: CONTRIBUTING.mdChanges 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: CONTRIBUTING.md
怎么回事? 如今 CONTRIBUTING.md
文件同时出如今暂存区和非暂存区。 这怎么可能呢? 好吧,实际上 Git 只不过暂存了你运行 git add
命令时的版本, 若是你如今提交,CONTRIBUTING.md
的版本是你最后一次运行 git add
命令时的那个版本,而不是你运行 git commit
时,在工做目录中的当前版本。 因此,运行了 git add
以后又做了修订的文件,须要从新运行 git add
把最新版本从新暂存起来:
$ git add CONTRIBUTING.md $ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) new file: README modified: CONTRIBUTING.md
git status
命令的输出十分详细,但其用语有些繁琐。 若是你使用 git status -s
命令或 git status --short
命令,你将获得一种更为紧凑的格式输出。 运行 git status -s
,状态报告输出以下:
$ git status -s M READMEMM Rakefile A lib/git.rb M lib/simplegit.rb?? LICENSE.txt
新添加的未跟踪文件前面有 ??
标记,新添加到暂存区中的文件前面有 A
标记,修改过的文件前面有 M
标记。 你可能注意到了 M
有两个能够出现的位置,出如今右边的 M
表示该文件被修改了可是还没放入暂存区,出如今靠左边的 M
表示该文件被修改了并放入了暂存区。 例如,上面的状态报告显示:README
文件在工做区被修改了可是尚未将修改后的文件放入暂存区,lib/simplegit.rb
文件被修改了并将修改后的文件放入了暂存区。 而 Rakefile
在工做区被修改并提交到暂存区后又在工做区中被修改了,因此在暂存区和工做区都有该文件被修改了的记录。
通常咱们总会有些文件无需归入 Git 的管理,也不但愿它们总出如今未跟踪文件列表。 一般都是些自动生成的文件,好比日志文件,或者编译过程当中建立的临时文件等。 在这种状况下,咱们能够建立一个名为.gitignore
的文件,列出要忽略的文件模式。 来看一个实际的例子:
$ cat .gitignore*.[oa]*~
第一行告诉 Git 忽略全部以 .o
或 .a
结尾的文件。通常这类对象文件和存档文件都是编译过程当中出现的。 第二行告诉 Git 忽略全部以波浪符(~)结尾的文件,许多文本编辑软件(好比 Emacs)都用这样的文件名保存副本。 此外,你可能还须要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。 要养成一开始就设置好 .gitignore 文件的习惯,以避免未来误提交这类无用的文件。
文件 .gitignore
的格式规范以下:
全部空行或者以 #
开头的行都会被 Git 忽略。
可使用标准的 glob 模式匹配。
匹配模式能够以(/
)开头防止递归。
匹配模式能够以(/
)结尾指定目录。
要忽略指定模式之外的文件或目录,能够在模式前加上惊叹号(!
)取反。
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?
)只匹配一个任意字符;若是在方括号中使用短划线分隔两个字符,表示全部在这两个字符范围内的均可以匹配(好比 [0-9]
表示匹配全部 0 到 9 的数字)。 使用两个星号(*
) 表示匹配任意中间目录,好比a/**/z
能够匹配 a/z
, a/b/z
或 a/b/c/z
等。
咱们再看一个 .gitignore 文件的例子:
# no .a files *.a # but do track lib.a, even though you're ignoring .a files above !lib.a # only ignore the TODO file in the current directory, not subdir/TODO /TODO # ignore all files in the 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
文件列表,你能够在https://github.com/github/gitignore 找到它.
若是 git status
命令的输出对于你来讲过于模糊,你想知道具体修改了什么地方,能够用 git diff
命令。 稍后咱们会详细介绍 git diff
,你可能一般会用它来回答这两个问题:当前作的哪些更新尚未暂存? 有哪些更新已经暂存起来准备好了下次提交? 尽管 git status
已经经过在相应栏下列出文件名的方式回答了这个问题,git diff
将经过文件补丁的格式显示具体哪些行发生了改变。
假如再次修改 README 文件后暂存,而后编辑 CONTRIBUTING.md
文件后先不暂存, 运行 status
命令将会看到:
$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) modified: READMEChanges 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: CONTRIBUTING.md
要查看还没有暂存的文件更新了哪些部分,不加参数直接输入 git diff
:
$ git diffdiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 8ebb991..643e24f 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -65,7 +65,8 @@ branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change-merged in.+merged in. Also, split your changes into comprehensive chunks if your patch is+longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's
此命令比较的是工做目录中当前文件和暂存区域快照之间的差别, 也就是修改以后尚未暂存起来的变化内容。
若要查看已暂存的将要添加到下次提交里的内容,能够用 git diff --cached
命令。(Git 1.6.1 及更高版本还容许使用 git diff --staged
,效果是相同的,但更好记些。)
$ git diff --stageddiff --git a/README b/READMEnew file mode 100644index 0000000..03902a1--- /dev/null+++ b/README@@ -0,0 +1 @@+My Project
请注意,git diff 自己只显示还没有暂存的改动,而不是自上次提交以来所作的全部改动。 因此有时候你一会儿暂存了全部更新过的文件后,运行 git diff
后却什么也没有,就是这个缘由。
像以前说的,暂存 CONTRIBUTING.md
后再编辑,运行 git status
会看到暂存先后的两个版本。 若是咱们的环境(终端输出)看起来以下:
$ git add CONTRIBUTING.md$ echo '# test line' >> CONTRIBUTING.md$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) modified: CONTRIBUTING.mdChanges 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: CONTRIBUTING.md
如今运行 git diff
看暂存先后的变化:
$ git diffdiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 643e24f..87f08c8 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -119,3 +119,4 @@ at the ## Starter Projects See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).+# test line
而后用 git diff --cached
查看已经暂存起来的变化:(--staged 和 --cached 是同义词)
$ git diff --cacheddiff --git a/CONTRIBUTING.md b/CONTRIBUTING.mdindex 8ebb991..643e24f 100644--- a/CONTRIBUTING.md+++ b/CONTRIBUTING.md@@ -65,7 +65,8 @@ branch directly, things can get messy. Please include a nice description of your changes when you submit your PR; if we have to read the whole diff to figure out why you're contributing in the first place, you're less likely to get feedback and have your change-merged in.+merged in. Also, split your changes into comprehensive chunks if your patch is+longer than a dozen lines. If you are starting to work on a particular area, feel free to submit a PR that highlights your work in progress (and note in the PR title that it's
在本书中,咱们使用 git diff
来分析文件差别。 可是,若是你喜欢经过图形化的方式或其它格式输出方式的话,可使用 git difftool
命令来用 Araxis ,emerge 或 vimdiff 等软件输出 diff 分析结果。 使用 git difftool --tool-help
命令来看你的系统支持哪些 Git Diff 插件。
如今的暂存区域已经准备稳当能够提交了。 在此以前,请必定要确认还有什么修改过的或新建的文件尚未 git add
过,不然提交的时候不会记录这些还没暂存起来的变化。 这些修改过的文件只保留在本地磁盘。 因此,每次准备提交前,先用 git status
看下,是否是都已暂存起来了, 而后再运行提交命令git commit
:
$ git commit
这种方式会启动文本编辑器以便输入本次提交的说明。 (默认会启用 shell 的环境变量 $EDITOR
所指定的软件,通常都是 vim 或 emacs。固然也能够按照 起步 介绍的方式,使用 git config --global core.editor
命令设定你喜欢的编辑软件。)
编辑器会显示相似下面的文本信息(本例选用 Vim 的屏显方式展现):
# Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # On branch master # Changes to be committed: # new file: README # modified: CONTRIBUTING.md # ~ ~ ~ ".git/COMMIT_EDITMSG" 9L, 283C
能够看到,默认的提交消息包含最后一次运行 git status
的输出,放在注释行里,另外开头还有一空行,供你输入提交说明。 你彻底能够去掉这些注释行,不过留着也不要紧,多少能帮你回想起此次更新的内容有哪些。 (若是想要更详细的对修改了哪些内容的提示,能够用 -v
选项,这会将你所作的改变的 diff 输出放到编辑器中从而使你知道本次提交具体作了哪些修改。) 退出编辑器时,Git 会丢掉注释行,用你输入提交附带信息生成一次提交。
另外,你也能够在 commit
命令后添加 -m
选项,将提交信息与命令放在同一行,以下所示:
$ git commit -m "Story 182: Fix benchmarks for speed"[master 463dc4f] Story 182: Fix benchmarks for speed 2 files changed, 2 insertions(+) create mode 100644 README
好,如今你已经建立了第一个提交! 能够看到,提交后它会告诉你,当前是在哪一个分支(master
)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f
),以及在本次提交中,有多少文件修订过,多少行添加和删改过。
请记住,提交时记录的是放在暂存区域的快照。 任何还未暂存的仍然保持已修改状态,能够在下次提交时归入版本管理。 每一次运行提交操做,都是对你项目做一次快照,之后能够回到这个状态,或者进行比较。
尽管使用暂存区域的方式能够精心准备要提交的细节,但有时候这么作略显繁琐。 Git 提供了一个跳过使用暂存区域的方式, 只要在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把全部已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤:
$ git statusOn branch masterChanges 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: CONTRIBUTING.mdno changes added to commit (use "git add" and/or "git commit -a")$ git commit -a -m 'added new benchmarks'[master 83e38c7] added new benchmarks 1 file changed, 5 insertions(+), 0 deletions(-)
看到了吗?提交以前再也不须要 git add
文件“CONTRIBUTING.md”了。
要从 Git 中移除某个文件,就必需要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),而后提交。 能够用 git rm
命令完成此项工做,并连带从工做目录中删除指定的文件,这样之后就不会出如今未跟踪文件清单中了。
若是只是简单地从工做目录中手工删除文件,运行 git status
时就会在 “Changes not staged for commit” 部分(也就是 未暂存清单)看到:
$ rm PROJECTS.md$ git statusOn branch masterYour branch is up-to-date with 'origin/master'.Changes not staged for commit: (use "git add/rm <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) deleted: PROJECTS.mdno changes added to commit (use "git add" and/or "git commit -a")
而后再运行 git rm
记录这次移除文件的操做:
$ git rm PROJECTS.mdrm 'PROJECTS.md'$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) deleted: PROJECTS.md
下一次提交时,该文件就再也不归入版本管理了。 若是删除以前修改过而且已经放到暂存区域的话,则必需要用强制删除选项 -f
(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删尚未添加到快照的数据,这样的数据不能被 Git 恢复。
另一种状况是,咱们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然但愿保留在当前工做目录中。 换句话说,你想让文件保留在磁盘,可是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore
文件,不当心把一个很大的日志文件或一堆 .a
这样的编译生成文件添加到暂存区时,这一作法尤为有用。 为达到这一目的,使用 --cached
选项:
$ git rm --cached README
git rm
命令后面能够列出文件或者目录的名字,也可使用 glob
模式。 比方说:
$ git rm log/\*.log
注意到星号 *
以前的反斜杠 \
, 由于 Git 有它本身的文件模式扩展匹配方式,因此咱们不用 shell 来帮忙展开。 此命令删除 log/
目录下扩展名为 .log
的全部文件。 相似的好比:
$ git rm \*~
该命令为删除以 ~
结尾的全部文件。
不像其它的 VCS 系统,Git 并不显式跟踪文件移动操做。 若是在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次更名操做。 不过 Git 很是聪明,它会推断出究竟发生了什么,至于具体是如何作到的,咱们稍后再谈。
既然如此,当你看到 Git 的 mv
命令时必定会困惑不已。 要在 Git 中对文件更名,能够这么作:
$ git mv file_from file_to
它会恰如预期般正常工做。 实际上,即使此时查看状态信息,也会明白无误地看到关于重命名操做的说明:
$ git mv README.md README$ git statusOn branch masterChanges to be committed: (use "git reset HEAD <file>..." to unstage) renamed: README.md -> README
其实,运行 git mv
就至关于运行了下面三条命令:
$ mv README.md README$ git rm README.md$ git add README
如此分开操做,Git 也会意识到这是一次更名,因此无论何种方式结果都同样。 二者惟一的区别是,mv
是一条命令而另外一种方式须要三条命令,直接用 git mv
轻便得多。 不过有时候用其余工具批处理更名的话,要记得在提交前删除老的文件名,再添加新的文件名。