二、Git 基础
1、获取Git仓库
有两种取得 Git 项目仓库的方法。第一种是在现有项目或目录下导入所有文件到 Git 中;第二种是从一个服务器克隆一个现有的 Git 仓库。
1.1 在现有目录中初始化仓库
如果你打算使用 Git 来对现有的项目进行管理,你只需要进入该项目目录并输入:
# git init
该命令将创建一个名为 .git 的子目录,这个子目录含有你初始化的 Git 仓库中所有的必须文件,这些文件是Git 仓库的骨干。但是,在这个时候,我们仅仅是做了一个初始化的操作,你的项目里的文件还没有被跟踪。(参
见 Git 内部原理 来了解更多关于到底 .git 文件夹中包含了哪些文件的信息。)
如果你是在一个已经存在文件的文件夹(而不是空文件夹)中初始化 Git 仓库来进行版本控制的话,你应该开始跟踪这些文件并提交。
你可通过 git add 命令来实现对指定文件的跟踪,然后执行 git commit 提交:
现在,你已经得到了一个实际维护(或者说是跟踪)着若干个文件的Git 仓库。
1.2 克隆现有的仓库
如果你想获得一份已经存在了的 Git 仓库的拷贝,这时就要用到 git clone 命令。
注意:如果你对其它的 VCS 系统(比如说Subversion)很熟悉,请留心一下你所使用的命令是"clone"而不是"checkout"。
这是 Git 区别于其它版本控制系统的一个重要特性,Git 克隆的是该 Git 仓库服务器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。
当你执行 git clone 命令的时候,默认配置下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。事实上,如果你的服务器的磁盘坏掉了,你通常可以使用任何一个克隆下来的用户端来重建服务器上的仓库(虽然可能会丢失某些服务器端的挂钩设置,但是所有版本的数据仍在,详见 在服务器上搭建 Git )。
克隆仓库的命令格式是 git clone [url] 。
比如,要克隆 Git 的可链接库 libgit2,可以用下面的命令:# git clone https://github.com/libgit2/libgit2
这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 .git 文件夹,从远程仓库拉取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。
如果你进入到这个新建的 libgit2 文件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。
如果你想在克隆远程仓库的时候,自定义本地仓库的名字,你可以使用如下命令:
# git clone https://github.com/libgit2/libgit2 mylibgit
这将执行与上一个命令相同的操作,不过在本地创建的仓库名字变为 mylibgit。
Git 支持多种数据传输协议。上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用SSH 传输协议,比如 user@server:path/to/repo.git 。
在服务器上搭建 Git将会介绍所有这些协议在服务器端如何配置使用,以及各种方式之间的利弊。
2、记录每次更新到仓库
请记住,你工作目录下的每一个文件都不外乎这两种状态:已跟踪或未跟踪。
已跟踪的文件是指那些被纳入了版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后,它们的状态可能处于未修改,已修改或已放入暂存区。
工作目录中除已跟踪文件以外的所有其它文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有放入暂存区。
初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态。
编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件。我们逐步将这些修改过的文件放入暂存区,然后提交所有暂存了的修改,如此反复。
所以使用 Git 时文件的生命周期如下:
2.1 检查当前文件状态
要查看哪些文件处于什么状态,可以用 git status 命令。如果在克隆仓库后立即使用此命令,会看到类似这
样的输出:
# git status
On branch master
nothing to commit, working directory clean
这说明你现在的工作目录相当干净。换句话说,所有已跟踪文件在上次提交后都未被更改过。
此外,上面的信息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 Git 会在这里列出来。
最后,该命令还显示了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。
现在,分支名是 “master”,这是默认的分支名。我们在 Git 分支 会详细讨论分支和引用。
现在,让我们在项目下创建一个新的 README 文件。如果之前并不存在这个文件,使用 git status 命令,你将看到一个新的未跟踪文件:
# echo 'My Project' > README
# git status
On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
README
nothing added to commit but untracked files present (use "git add" totrack)
在状态报告中可以看到新建的 README 文件出现在 Untracked files 下面。
未跟踪的文件意味着 Git在之前的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文件”,这样的处理让你不必担心将生成的二进制文件或其它不想被跟踪的文件包含进来。不过现在的例子中,我们确实想要跟踪管理 README 这个文件。
2.2 跟踪新文件
使用命令 git add 开始跟踪一个文件。所以,要跟踪 README 文件,运行:
# git add README
此时再运行 git status 命令,会看到 README 文件已被跟踪,并处于暂存状态:
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
只要在 Changes to be committed 这行下面的,就说明是已暂存状态。
如果此时提交,那么该文件此时此刻的版本将被留存在历史记录中。你可能会想起之前我们使用 git init 后就运行了 git add (files) 命令,开始跟踪当前目录下的文件。
git add 命令使用文件或目录的路径作为参数;如果参数是目录的路径,该命令将递归地跟踪该目录下的所有文件。
注意:跟踪的新文件会被直接添加到暂存区
总结:git add filename/directory
2.3 暂存已修改文件
现在我们来修改一个已被跟踪的文件。如果你修改了一个名为 CONTRIBUTING.md 的已被跟踪的文件,然后运行 git status 命令,会看到下面内容:
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
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: CONTRIBUTING.md
文件 CONTRIBUTING.md 出现在 Changes not staged for commit 这行下面,说明已跟踪文件的内容发生了变化,但还没有放到暂存区。
要暂存这次更新,需要运行 git add 命令。
这是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。
将这个命令理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。
现在让我们运行 git add 将"CONTRIBUTING.md"放到暂存区,然后再看看 git status 的输出:
# git add CONTRIBUTING.md
# git status
On branch master
Changes 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 master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
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: CONTRIBUTING.md
怎么回事?现在 CONTRIBUTING.md 文件同时出现在暂存区和非暂存区。这怎么可能呢?
好吧,实际上 Git 只不过暂存了你运行 git add 命令时的版本,如果你现在提交,CONTRIBUTING.md 的版本是你最后一次运行git add 命令时的那个版本,而不是你运行 git commit 时,在工作目录中的当前版本。
所以,运行了 gita dd 之后又作了修订的文件,需要重新运行 git add 把最新版本重新暂存起来:
# git add CONTRIBUTING.md
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: README
modified: CONTRIBUTING.md
2.4 状态简览
git status 命令的输出十分详细,但其用语有些繁琐。如果你使用 git status -s 命令或 git status --short 命令,你将得到一种更为紧凑的格式输出。
运行 git status -s ,状态报告输出如下:
# git status -s
M README =>M出现在右边:文件已被修改,还未放入暂存区
MM Rakefile =>文件修改后已放入暂存区,然后文件又被修改
A lib/git.rb =>新添加到暂存区的文件
M lib/simplegit.rb =>M出现在左边:文件已被修改,且已放入暂存区
?? LICENSE.txt =>未被跟踪的文件
AM =>新添加到暂存区的文件又被修改了
新添加的未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。
你可能注意到了 M 有两个可以出现的位置,出现在右边的 M 表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M 表示该文件被修改了并放入了暂存区。
例如,上面的状态报告显示: README 文件在工作区被修改了但是还没有将修改后的文件放入暂存区,lib/simplegit.rb 文件被修改了并将修改后的文件放入了暂存区。
而Rakefile 在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录。
总结:git status -s
2.5 忽略文件
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。通常都是些自动生成的文件,比如日志文件,或者编译过程中创建的临时文件等。
在这种情况下,我们可以创建一个名为 .gitignore的文件,列出要忽略的文件模式。来看一个实际的例子:
# cat .gitignore
*.[oa]
*~
第一行告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。
第二行告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。
此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。
要养成一开始就设置好.gitignore 文件的习惯,以免将来误提交这类无用的文件。
忽略.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
TIP:GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表,你可以在https://github.com/github/gitignore 找到它.
2.6 查看已暂存和未暂存的修改
如果 git status 命令的输出对于你来说过于模糊,你想知道具体修改了什么地方,可以用 git diff 命令。
你可能通常会用它来回答这两个问题:当前做的哪些更新还没有暂存?有哪些更新已经暂存起来准备好了下次提交?
尽管 git status 已经通过在相应栏下列出文件名的方式回答了这个问题,git diff 将通过文件补丁的格式显示具体哪些行发生了改变。
假如再次修改 README 文件后暂存,然后编辑 CONTRIBUTING.md 文件后先不暂存,运行 status 命令将会看到:
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: README
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: CONTRIBUTING.md
要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff:
# git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 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 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。所以有时候你一下子暂存了所有更新过的文件后,运行 git diff 后却什么也没有,就是这个原因。
像之前说的,暂存 CONTRIBUTING.md 后再编辑,运行 git status 会看到暂存前后的两个版本。如果我们的环境(终端输出)看起来如下:
# git add CONTRIBUTING.md
# echo '# test line' >> CONTRIBUTING.md
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: CONTRIBUTING.md
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: CONTRIBUTING.md
现在运行 git diff 看暂存前后的变化:
# git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 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 --cached
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 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
NOTE:
Git Diff 的插件版本
在本书中,我们使用 git diff 来分析文件差异。但是,如果你喜欢通过图形化的方式或其它格式输出方式的话,可以使用 git difftool 命令来用 Araxis ,emerge 或 vimdiff 等软
件输出 diff 分析结果。
使用 git difftool --tool-help 命令来看你的系统支持哪些 GitDiff 插件。
总结:
1、查看已放入暂存区的文件内容(下次提交的内容):git diff --stated (git diff --cached)
2、查看当前的文件:cat filename
3、查看已放入暂存区文件和又修改的文件之间的差异:git diff
2.7 提交更新
现在的暂存区域已经准备妥当可以提交了。在此之前,请一定要确认还有什么修改过的或新建的文件还没有 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 commit -m '注释'
2.8 跳过使用暂存区域
Git 提供了一个跳过使用暂存区域的方式,只要在提交的时候,给 git commit 加上 -a 选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:
# git status
On branch master
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: CONTRIBUTING.md
no 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 commit -a -m '注释' #把所有已经跟踪过的文件暂存起来一并提交(包括暂存后又修改但是尚未又暂存的文件)
2.9 移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。
可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changes not staged for commit” 部分(也就是 未暂存清单)看到:
# rm PROJECTS.md
# git status
On branch master
Your 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.md
no changes added to commit (use "git add" and/or "git commit -a")
然后再运行 git rm 记录此次移除文件的操作:
# git rm PROJECTS.md
rm 'PROJECTS.md'
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: PROJECTS.md
# git rm PROJECTS.md
rm 'PROJECTS.md'
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
deleted: PROJECTS.md
下一次提交时,该文件就不再纳入版本管理了。
如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f(译注:即 force 的首字母)。这是一种安全特性,用于防止误删还没有添加到快照的数据,这样的数据不能被 Git 恢复。
总结:
彻底删除文件:
1、从暂存区删除,连带删除本地目录中的文件
git rm filename
2、提交
git commit -m ''
整个步骤流程:
git rm filename -> git commit -m '注释'
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。
当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。
为达到这一目的,使用 --cached 选项:
# git rm --cached README
git rm 命令后面可以列出文件或者目录的名字,也可以使用 glob 模式。比方说:
# git rm log/\*.log
注意到星号 * 之前的反斜杠 \,因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。
此命令删除 log/ 目录下扩展名为 .log 的所有文件。类似的比如:
# git rm \*~
该命令为删除以 ~ 结尾的所有文件。
从暂存区删除文件,本地不删除 (根据需要再把文件加入忽略列表)
1、git rm --cached filename
2、git commit -m '注释'
2.10 移动文件
Git 并不显式跟踪文件移动操作。如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。
要在 Git 中对文件改名,可以这么做:
# git mv file_from file_to
它会恰如预期般正常工作。实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:
# git mv README.md README
# git status
On branch master
Changes 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 轻便得多。
不过有时候用其他工具批处理改名的话,要记得在提交前删除老的文件名,再添加新的文件名。
总结:
修改文件名:git mv old_filename new_filename
3、查看提交历史
在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史.完成这个任务最简单而又有效的工具是 git log 命令。
默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
git log 有许多选项可以帮助你搜寻你所要找的提交,接下来我们介绍些最常用的。
一个常用的选项是 -p,用来显示每次提交的内容差异。你也可以加上 -2 来仅显示最近两次提交:git log -p -2
该选项除了显示基本信息之外,还在附带了每次 commit 的变化。
当进行代码审查,或者快速浏览某个搭档提交的 commit 所带来的变化的时候,这个参数就非常有用了。
你也可以为 git log 附带一系列的总结性选项。比如说,如果你想看到每次提交的简略的统计信息,你可以使用 --stat 选项:git log --stat
--stat 选项在每次提交的下面列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。在每次提交的最后还有一个总结。
另外一个常用的选项是 --pretty。这个选项可以指定使用不同于默认格式的方式展示提交历史。这个选项有一些内建的子选项供你使用。
比如用 oneline 将每个提交放在一行显示,查看的提交数很大时非常有用。另外还有 short,full 和 fuller 可以用,展示的信息或多或少有些不同。
# git log --pretty=oneline
但最有意思的是 format,可以定制要显示的记录格式。这样的输出对后期提取分析格外有用 — 因为你知道输出的格式不会随着Git的更新而发生改变:
git log --pretty=format 常用的选项 列出了常用的格式占位符写法及其代表的意义。
Table 1. git log --pretty=format 常用的选项
作者指的是实际作出修改的人,提交者指的是最后将此工作成果提交到仓库的人。
所以,当你为某个项目发布补丁,然后某个核心成员将你的补丁并入项目时,你就是作者,而那个核心成员就是提交者。我们会在 分布式 Git 再详细介绍两者之间的细微差别。
当 oneline 或 format 与另一个 log 选项 --graph 结合使用时尤其有用。这个选项添加了一些ASCII字符串来形象地展示你的分支、合并历史:git log --pretty=format:"%h %s" --graph
以上只是简单介绍了一些 git log 命令支持的选项。git log 的常用选项 列出了我们目前涉及到的和没涉及到的选项,已经它们是如何影响 log 命令的输出的:
汇总:
1、git log
默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
2、git log -p -1
选项 -p 用来显示每次提交的内容差异,加上 -1 来仅显示最近一次提交,还附带了每次 commit 的变化。
3、git log --stat
看每次提交的简略的统计信息,可以使用 --stat 选项
--stat 选项在每次提交的下面列出额所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。
4、git log --pretty=oneline
选项 --pretty可以指定使用不同于默认格式的方式展示提交历史。这个选项有一些内建的子选项供你使用。
比如用 oneline 将每个提交放在一行显示,查看的提交数很大时非常有用。另外还有 short,full 和 fuller 可以用,展示的信息或多或少有些不同
git log --pretty=short
git log --pretty=full
git log --pretty=fuller
5、git log --pretty=format:"%h - %an, %ar : %s"
format,可以定制要显示的记录格式
3.1 限制输出长度
除了定制输出格式的选项之外,git log 还有许多非常实用的限制输出长度的选项,也就是只输出部分提交信息。之前你已经看到过 -2 了,它只显示最近的两条提交,实际上,这是 -<n> 选项的写法,其中的 n 可以是任何整数,表示仅显示
最近的若干条提交。不过实践中我们是不太用这个选项的,Git 在输出所有提交时会自动调用分页程序,所以你一次只会看到一页的内容。
另外还有按照时间作限制的选项,比如 --since 和 --until 也很有用。例如,下面的命令列出所有最近两周内的提交:git log --since=2.weeks
这个命令可以在多种格式下工作,比如说具体的某一天 "2008-01-15",或者是相对地多久以前 "2 years 1day 3 minutes ago"。
还可以给出若干搜索条件,列出符合的提交。用 --author 选项显示指定作者的提交,用 --grep 选项搜索提交说明中的关键字。
(请注意,如果要得到同时满足这两个选项搜索条件的提交,就必须用 --all-match 选项。否则,满足任意一个条件的提交都会被匹配出来)
另一个非常有用的筛选选项是 -S,可以列出那些添加或移除了某些字符串的提交。比如说,你想找出添加或移除了某一个特定函数的引用的提交,你可以这样使用:
# git log -Sfunction_name
最后一个很实用的 git log 选项是路径(path),如果只关心某些文件或者目录的历史提交,可以在 git log 选项的最后指定它们的路径。因为是放在最后位置上的选项,所以用两个短划线(--)隔开之前的选项和后面限定的
路径名。
在 限制 git log 输出的选项 中列出了常用的选项
来看一个实际的例子,如果要查看 Git 仓库中,2008 年 10 月期间,Junio Hamano 提交的但未合并的测试文件,可以用下面的查询命令:
# git log --pretty="%h - %s" --author=gitster --since="2008-10-01" \
--before="2008-11-01" --no-merges -- t/
4、撤销操作
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。此时,可以运行带有 --amend 选项的提交命令尝试重新提交:
# git commit --amend
这个命令会将暂存区中的文件提交。如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令),那么快照会保持不变,而你所修改的只是提交信息。
文本编辑器启动后,可以看到之前的提交信息。编辑后保存会覆盖原来的提交信息。或者使用 git commit --amend -m 'XXXXX'
例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
# git commit -m 'initial commit'
# git add forgotten_file
# git commit --amend -m '注释'
最终你只会有一个提交 - 第二次提交将代替第一次提交的结果。
总结:
1、修改最后提交时的注释信息:git commit --amend -m '注释'
2、新增文件后再提交,替代上一次的提交:
git add filename
git commit --amend -m '注释'
4.1 取消暂存的文件
接下来演示如何操作暂存区域与工作目录中已修改的文件。这些命令在修改文件状态的同时,也会提示如何撤消操作。
例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交,但是却意外地输入了git add * 暂存了它们两个。如何只取消暂存两个中的一个呢?git status 命令提示了你:
# git add *
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
modified: CONTRIBUTING.md
在 “Changes to be committed” 文字正下方,提示使用 git reset HEAD <file>... 来取消暂存。所以,我们可以这样来取消暂存 CONTRIBUTING.md 文件:
# git reset HEAD CONTRIBUTING.md
Unstaged changes after reset:
M CONTRIBUTING.md
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
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: CONTRIBUTING.md
这个命令有点儿奇怪,但是起作用了。CONTRIBUTING.md 文件已经是修改未暂存的状态了。
总结:取消文件的暂存状态,也就是把文件恢复成未跟踪的状态:git reset HEAD filename
git reset HEAD filename与git rm --cached filename的区别:两者虽然都是把文件变成未被跟踪的状态,不过前者不会删除暂存区中的文件,而后者会删除暂存区中的该文件
NOTE
虽然在调用时加上 --hard 选项可以令 git reset 成为一个危险的命令(译注:可能导致工作目录中所有当前进度丢失!),但本例中工作目录内的文件并不会被修改。
不加选项地调用 git reset 并不危险 — 它只会修改暂存区域。
4.2 撤消对文件的修改
如果你并不想保留对 CONTRIBUTING.md 文件的修改怎么办?你该如何方便地撤消修改 - 将它还原成上次提交时的样子(或者刚克隆完的样子,或者刚把它放入工作目录时的样子)?
幸运的是,git status 也告诉了你应该如何做。在最后一个例子中,未暂存区域是这样:
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: CONTRIBUTING.md
它非常清楚地告诉了你如何撤消之前所做的修改。让我们来按照提示执行:
# git checkout -- CONTRIBUTING.md
# git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
renamed: README.md -> README
可以看到那些修改已经被撤消了。
总结:撤销对文件的修改,把文件恢复成最后一次提交到暂存区时的状态(内容):git checkout -- filename
IMPORTANT
你需要知道 git checkout -- [file] 是一个危险的命令,这很重要。你对那个文件做的任何修改都会消失 - 你只是拷贝了另一个文件来覆盖它。除非你确实清楚不想要那个文件了,否则不要使用这个命令。
在 Git 中任何 已提交的 东西几乎总是可以恢复的。甚至那些被删除的分支中的提交或使用 --amend 选项覆盖的提交也可以恢复(阅读 数据恢复 了解数据恢复)。然而,任何你未提交的东西丢失后很可能再也找不到了。
5、远程仓库的使用
远程仓库是指托管在因特网或其他网络中的你的项目的版本库。
你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写。
与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。
管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等。
5.1 查看远程仓库
查看你已经配置的远程仓库服务器,可以运行 git remote 命令。它会列出你指定的每一个远程服务器的简写。
如果你已经克隆了自己的仓库,那么至少应该能看到 origin - 这是 Git 给你克隆的仓库服务器的默认名字:
也可以指定选项 -v,会显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。
# git remote -v
origin https://github.com/schacon/ticgit (fetch)
origin https://github.com/schacon/ticgit (push)
如果你的远程仓库不止一个,该命令会将它们全部列出。例如,与几个协作者合作的,拥有多个远程仓库的仓库看起来像下面这样:
注意这些远程仓库使用了不同的协议;我们将会在 在服务器上搭建 Git 中了解关于它们的更多信息。
5.2 添加远程仓库
运行 git remote add <shortname> <url> 添加一个新的远程 Git 仓库,同时指定一个你可以轻松引用的简写:
现在你可以在命令行中使用字符串 pb 来代替整个 URL。例如,如果你想拉取 Paul 的仓库中有但你没有的信息,可以运行 git fetch pb:
现在 Paul 的 master 分支可以在本地通过 pb/master 访问到 - 你可以将它合并到自己的某个分支中,或者如果你想要查看它的话,可以检出一个指向该点的本地分支。(我们将会在 Git 分支 中详细介绍什么是分支以及如何
使用分支。)
5.3 从远程仓库中抓取与拉取
从远程仓库中获得数据,可以执行: git fetch [remote-name]
这个命令会访问远程仓库,从中拉取所有你还没有的数据。执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。
如果你使用 clone 命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 “origin” 为简写。所以,git fetch origin 会抓取克隆(或上一次抓取)后新推送的所有工作。
必须注意 git fetch 命令会将数据拉取到你的本地仓库 - 它并不会自动合并或修改你当前的工作。当准备好时你必须手动将其合并入你的工作。
分支合并
如果你有一个分支设置为跟踪一个远程分支(阅读下一节与 Git 分支 了解更多信息),可以使用 git pull 命令来自动的抓取然后合并远程分支到当前分支。
这对你来说可能是一个更简单或更舒服的工作流程;默认情况下,git clone 命令会自动设置本地 master 分支跟踪克隆的远程仓库的 master 分支(或不管是什么名字的默认分支)。
运行 git pull 通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。
5.4 推送到远程仓库
当你想分享你的项目时,必须将其推送到上游。这个命令很简单:git push [remote-name] [branch-name]。
当你想要将 master 分支推送到 origin 服务器时(再次说明,克隆时通常会自动帮你设置好那两个名字),那么运行这个命令就可以将你所做的备份到服务器:
# git push origin master
只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。你必须先将他们的工作拉取下来并将其合并
进你的工作后才能推送。阅读 Git 分支 了解如何推送到远程仓库服务器的详细信息。
5.5 查看远程仓库
如果想要查看某一个远程仓库的更多信息,可以使用 git remote show [remote-name] 命令。如果想以一个特定的缩写名运行这个命令,例如 origin,会得到像下面类似的信息:
它同样会列出远程仓库的 URL 与跟踪分支的信息。
这些信息非常有用,它告诉你正处于 master 分支,并且如果运行 git pull,就会抓取所有的远程引用,然后将远程 master 分支合并到本地 master 分支。它也会列出拉取到的所有远程引用。
这是一个经常遇到的简单例子。如果你是 Git 的重度使用者,那么还可以通过 git remote show 看到更多的信息。
这个命令列出了当你在特定的分支上执行 git push 会自动地推送到哪一个远程分支。它也同样地列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了,还有当你执行 git pull 时哪些分支会自动合并。
5.6 远程仓库的移除与重命名
如果想要重命名引用的名字可以运行 git remote rename 去修改一个远程仓库的简写名。例如,想要将 pb重命名为 paul,可以用 git remote rename 这样做:
值得注意的是这同样也会修改你的远程分支名字。那些过去引用 pb/master 的现在会引用 paul/master。
如果因为一些原因想要移除一个远程仓库 - 你已经从服务器上搬走了或不再想使用某一个特定的镜像了,又或者某一个贡献者不再贡献了 - 可以使用 git remote rm :
总结:
git remote add <shortname> <url> :添加远程仓库
git remote rm remote_name:移除远程仓库
git remote show remote_name:查看远程仓库的信息
git fetch [remote-name] :从远程仓库中拉取所有你还没有的数据。执行完成后,你将会拥有那个远程仓库中所有分支的引用,可以随时合并或查看。(注意:只拉取,不合并)
git push [remote-name] [branch-name]:将 本地 master 分支推送到 [remote-name]服务器的[branch-name]分支上(注意:若有其他人在你之前已推送过,你必须先拉取更新后才能进行推送)
git pull :通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支 (注意:拉取并合并)
git remote rename old_remote_name new_remote_name:修改一个远程仓库的简写名
实际操作:
客户端1操作:
1、查看客户端上面的远程分支
2、客户端本地新增文件,添加,推送到远程分支
客户端2的操作
1、查看远程分支
2、从远程分支拉取
6、打标签
Git 可以给历史中的某一个提交打上标签,以示重要。比较有代表性的是人们会使用这个功能来标记发布结点(v1.0 等等)。
6.1 列出标签
在 Git 中列出已有的标签是非常简单直观的。只需要输入 git tag:
# git tag
v0.1
v1.3
这个命令以字母顺序列出标签;但是它们出现的顺序并不重要。
你也可以使用特定的模式查找标签。例如,Git 自身的源代码仓库包含标签的数量超过 500 个。如果只对 1.8.5系列感兴趣,可以运行:
git tag -l 'v1.8.5*'
6.2 创建标签
Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated)。
一个轻量标签很像一个不会改变的分支 - 它只是一个特定提交的引用。然而,附注标签是存储在 Git 数据库中的一个完整对象。它们是可以被校验的;其中包含打标签者的名字、电子邮件地址、日期时间;还有一个标签信息;并且可以使用 GNU Privacy Guard(GPG)签名与验证。
通常建议创建附注标签,这样你可以拥有以上所有信息;但是如果你只是想用一个临时的标签,或者因为某些原因不想要保存那些信息,轻量标签也是可用的。
6.2.1 附注标签
在 Git 中创建一个附注标签是很简单的。最简单的方式是当你在运行 tag 命令时指定 -a 选项:git tag -a v1.4 -m 'my version 1.4'
-m 选项指定了一条将会存储在标签中的信息。如果没有为附注标签指定一条信息,Git 会运行编辑器要求你输入信息。
通过使用 git show 命令可以看到标签信息与对应的提交信息:
输出显示了打标签者的信息、打标签的日期时间、附注信息,然后显示具体的提交信息。
6.2.2 轻量标签
轻量标签本质上是将提交校验和存储到一个文件中 - 没有保存任何其他信息。创建轻量标签,不需要使用 -a、-s 或 -m 选项,只需要提供标签名字:git tag v1.5-lw
这时,如果在标签上运行 git show,你不会看到额外的标签信息。命令只会显示出提交信息:git tag v1.5-lw
6.3 后期打标签
也可以对过去的提交打标签。假设提交历史是这样的:git log --pretty=oneline
现在,假设在 v1.2 时你忘记给项目打标签,也就是在 “hahha” 提交。你可以在之后补上标签。要在那个提交上打标签,你需要在命令的末尾指定提交的校验和(或部分校验和): git tag -a v1.2 696fd132
可以看到你已经在那次提交上打上标签了:
6.4 共享标签
默认情况下,git push 命令并不会传送标签到远程仓库服务器上。在创建完标签后你必须显式地推送标签到共享服务器上。这个过程就像共享远程分支一样 - 你可以运行 git push origin [tagname]。
如果想要一次性推送很多标签,也可以使用带有 --tags 选项的 git push 命令。这将会把所有不在远程仓库服务器上的标签全部传送到那里。
现在,当其他人从仓库中克隆或拉取,他们也能得到你的那些标签。
6.5 检出标签
在 Git 中你并不能真的检出一个标签,因为它们并不能像分支一样来回移动。如果你想要工作目录与仓库中特定的标签版本完全一样,可以使用 git checkout -b [branchname] [tagname] 在特定的标签上创建一个新分支:
# git checkout -b version2 v2.0.0
Switched to a new branch 'version2'
当然,如果在这之后又进行了一次提交,version2 分支会因为改动向前移动了,那么 version2 分支就会和v2.0.0 标签稍微有些不同,这时就应该当心了。
7、Git别名
Git 并不会在你输入部分命令时自动推断出你想要的命令。如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。这里有一些例子你可以试试:
# git config --global alias.co checkout
# git config --global alias.br branch
# git config --global alias.ci commit
# git config --global alias.st status
这意味着,当要输入 git commit`时,只需要输入 `git ci。随着你继续不断地使用 Git,可能也会经常使用其他命令,所以创建别名时不要犹豫。在创建你认为应该存在的命令时这个技术会很有用。
例如,为了解决取消暂存文件的易用性问题,可以向 Git 中添加你自己的取消暂存别名:git config --global alias.unstage 'reset HEAD --'
这会使下面的两个命令等价:
# git unstage fileA
# git reset HEAD -- fileA
这样看起来更清楚一些。通常也会添加一个 last 命令,像这样:# git config --global alias.last 'log -1 HEAD'
这样,可以轻松地看到最后一次提交:
# git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date: Tue Aug 26 19:48:51 2008 +0800
test for current head
Signed-off-by: Scott Chacon <schacon@example.com>
可以看出,Git 只是简单地将别名替换为对应的命令。然而,你可能想要执行外部命令,而不是一个 Git 子命令。如果是那样的话,可以在命令前面加入 ! 符号。
如果你自己要写一些与 Git 仓库协作的工具的话,那会很有用。我们现在演示将 git visual 定义为 gitk 的别名:# git config --global alias.visual '!gitk'