Git 基础
# 1. Git 的特点
Git 在保存和处理各种信息的时候,虽然操作起来的命令形式非常相近,但它与其他版本控制系统的做法颇为不同,因此不要尝试把各种概念和其他版本控制系统相比拟,但理解这些差异将有助于你准确地使用 Git 提供的各种工具。
# 1)直接记录快照,而非差异比较
Git 和其他版本控制系统的主要差别在于,Git 只关心文件的整体是否发生变化,而大多数其他系统则只关心文件内容的具体差异。Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接。
# 2)近乎所有操作都是本地执行
Git 在本地磁盘上就保存着所有当前项目的历史更新,所以处理起来速度飞快。
# 3)时刻保持数据完整性
在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。这项特性作为 Git 的设计哲学,建在整体架构的最底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。
Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符串。该字串由 40 个十六进制字符(0-9 及 a-f)组成,看起来就像是:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 的工作完全依赖于这类指纹字串,所以你会经常看到这样的哈希值。实际上,所有保存在 Git 数据库中的东西都是用此哈希值来作索引的,而不是靠文件名。
# 4)多数操作仅添加数据
在 Git 里,一旦提交快照之后就完全不用担心丢失数据,这种高可靠性令我们的开发工作安心不少,尽管去做各种试验性的尝试好了,再怎样也不会弄丢数据。
# 5)文件的三种状态
对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),已修改(modified)和已暂存(staged)。
- 已提交表示该文件已经被安全地保存在本地数据库中了;
- 已修改表示修改了某个文件,但还没有提交保存;
- 已暂存表示把已修改的文件放在下次提交时要保存的清单中。
由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录,暂存区域,以及本地仓库:
- Git 目录(
.git
)是保存元数据和对象数据库的地方。该目录非常重要,每次克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。 - 从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录;
- 所谓的暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件,不过标准说法还是叫暂存区域;
我们可以从文件所处的位置来判断状态:如果是 Git 目录中保存着的特定版本文件,就属于已提交状态;如果作了修改并已放入暂存区域,就属于已暂存状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修改状态。
# 2. Git 的配置
git config 专门用来配置或读取相应的工作环境变量。而正是由这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:
/etc/gitconfig
文件:系统中对所有用户都普遍适用的配置。若使用git config
时用--system
选项,读写的就是这个文件。~/.gitconfig
文件:用户目录下的配置文件只适用于该用户。若使用git config
时用--global
选项,读写的就是这个文件。- 当前项目的 git 目录中的配置文件(也就是工作目录中的
.git/config
文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以.git/config
里的配置会覆盖/etc/gitconfig
中的同名变量。
在 Windows 系统上,Git 会找寻用户主目录下的 .gitconfig
文件。主目录即 $HOME
变量指定的目录,一般都是 C:\Documents and Settings\$USER
。此外,Git 还会尝试找寻 /etc/gitconfig
文件,只不过看当初 Git 装在什么目录,就以此作为根目录来定位。
# 2.1 用户信息
配置 user.name 和 user.email,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一起被永久纳入历史记录:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
2
- 如果用了
--global
选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。
# 2.2 查看配置信息
git config --list
命令检查已有的配置信息:
$ git config --list
user.name=Scott Chacon
user.email=schacon@gmail.com
color.status=auto
color.branch=auto
color.interactive=auto
color.diff=auto
...
2
3
4
5
6
7
8
- 有时候会看到重复的变量名,那就说明它们来自不同的配置文件(比如
/etc/gitconfig
和~/.gitconfig
),不过最终 Git 实际采用的是最后一个。
也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可,像这样:
$ git config user.name
Scott Chacon
2
# 3. 获取 Git 仓库
- 在现有目录中初始化:
git init
- 克隆远程仓库:
git clone [url] <name>
,<name>
可选,用于重命名。
git 支持多种数据传输协议:https://
、git://
或 SSH 协议。
# 4. 记录每次更新到仓库
你工作目录下的每一个文件都不外乎这两种状态:已跟踪或未跟踪。
- 已跟踪的文件是指那些被纳入了版本控制的文件。
- 工作目录中除已跟踪文件以外的所有其它文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有放入暂存区。
# 4.1 检查文件的状态
git status
查看哪些文件处于什么状态。
示例
$ git status
On branch master
nothing to commit, working directory clean
2
3
这说明你现在的工作目录相当干净。换句话说,
- 所有已跟踪文件在上次提交后都未被更改过;
- 当前目录下没有出现任何处于未跟踪状态的新文件;
- 当前分支同远程服务器上对应的分支没有偏离。
# 4.2 跟踪新文件
git add [path]
开始跟踪一个文件。如果 path 是目录的路径,该命令将递归地跟踪该目录下的所有文件。
# 4.3 暂存已修改文件
已跟踪文件的内容发生了变化时,不会立刻被放到暂存区,要暂存这次更新,需要运行 git add
命令。
git add 是个多功能命令:可以用它开始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。将这个命令理解为“添加内容到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。
当在 git add 之后再修改该文件,会同时出现暂存区和非暂存区。实际上 Git 只不过暂存了你运行 git add
命令时的版本,如果你 commit,提交的版本是你最后一次运行 git add 命令时的版本。所以,运行了 git add
之后又作了修订的文件,需要重新运行 git add
把最新版本重新暂存起来。
# 4.4 状态简览
git status 命令的输出十分详细,但其用语有些繁琐。 如果你使用 git status -s
命令或 git status --short
命令,你将得到一种更为紧凑的格式输出。 运行 git status -s
,状态报告输出如下:
$ git status -s
M README
MM Rakefile
A lib/git.rb
M lib/simplegit.rb
?? LICENSE.txt
2
3
4
5
6
新添加的未跟踪文件前面有 ??
标记,新添加到暂存区中的文件前面有 A
标记,修改过的文件前面有 M
标记。 你可能注意到了 M
有两个可以出现的位置,出现在右边的 M
表示该文件被修改了但是还没放入暂存区,出现在靠左边的 M
表示该文件被修改了并放入了暂存区。比如 Rakefile
在工作区被修改并提交到暂存区后又在工作区中被修改了,所以在暂存区和工作区都有该文件被修改了的记录
# 4.5 忽略文件
.gitignore
文件列出要忽略的文件模式,其格式规范:
- 所有空行或者以
#
开头的行都会被 Git 忽略。 - 可以使用标准的 glob 模式匹配。
- 匹配模式可以以(
/
)开头防止递归。 - 匹配模式可以以(
/
)结尾指定目录。 - 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(
!
)取反。
glob 模式
glob 模式是指 shell 所使用的简化了的正则表达式。星号(*
)匹配零个或多个任意字符;[abc]
匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);问号(?
)只匹配一个任意字符;如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9]
表示匹配所有 0 到 9 的数字)。 使用两个星号(*
) 表示匹配任意中间目录,比如a/**/z
可以匹配 a/z
, a/b/z
或 a/b/c/z
等。
比如 *~
告诉 Git 忽略所有以波浪符(~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存副本。 要养成一开始就设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。
GitHub 有一个十分详细的针对数十种项目及语言的
.gitignore
文件列表,你可以在 https://github.com/github/gitignore 找到它.
# 4.6 查看已暂存和未暂存的修改
git diff
告诉你具体修改了什么地方。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
2
3
4
5
6
7
8
9
10
11
12
要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
若要查看已暂存的将要添加到下次提交里的内容,可以用 git diff --cached
命令。(Git 1.6.1 及更高版本还允许使用 git diff --staged
,效果是相同的,但更好记些。)
$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project
2
3
4
5
6
7
8
我们使用
git diff
来分析文件差异。 但是,如果你喜欢通过图形化的方式或其它格式输出方式的话,可以使用git difftool
命令来用 Araxis ,emerge 或 vimdiff 等软件输出 diff 分析结果。 使用git difftool --tool-help
命令来看你的系统支持哪些 Git Diff 插件。
# 4.7 提交更新
每次准备提交前,先用 git status
看下,是不是都已暂存起来了, 然后再运行提交命令 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
2
3
4
- 可以看到当前是在哪个分支(
master
)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f
),以及在本次提交中,有多少文件修订过,多少行添加和删改过。
请记住,提交时记录的是放在暂存区域的快照。 任何还未暂存的仍然保持已修改状态,可以在下次提交时纳入版本管理。 每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。
有人可能觉得使用暂存区域的方式略显繁琐,可以在提交的时候,给 git commit
加上 -a
选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add
步骤。
# 4.8 移除文件
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。
git rm
会将文件从已跟踪文件清单和工作目录中一同删除。
如果只是简单地从工作目录中手工删除文件,运行 git status
时就会在 “Changes not staged for commit” 部分,即本次删除操作并没有被暂存,这样之后还得再运行一次 git add/rm
命令。
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore
文件,不小心把一个很大的日志文件或一堆 .a
这样的编译生成文件添加到暂存区时,这一做法尤其有用。 为达到这一目的,使用 --cached
选项:
$ git rm --cached README
git rm
命令后面可以列出文件或者目录的名字,也可以使用glob
模式,如git rm \*~
# 4.9 移动文件
Git 并不显式跟踪文件移动操作。 如果在 Git 中重命名了某个文件,仓库中存储的元数据并不会体现出这是一次改名操作。
要在 Git 中对文件改名,可以用移动命令 mv
:
$ 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
2
3
4
5
6
7
其实,运行 git mv
就相当于运行了下面三条命令:
$ mv README.md README
$ git rm README.md
$ git add README
2
3
如此分开操作,Git 也会意识到这是一次改名,所以不管何种方式结果都一样。