读懂 diff
diff 的输出格式,可以分为:正常格式,上下文格式,合并格式.这里只介绍合并格式.通过 -u 选项指定了 diff 的输出为合并格式.如:
$ cat f1
a
a
a
b
a
a
a
$ cat f2
a
a
a
a
a
a
a
$ diff f1 f2 -u
--- f1 2014-06-03 00:47:10.859206823 +0800
+++ f2 2014-06-03 00:47:06.319206762 +0800
@@ -1,7 +1,7 @@
a
a
a
-b
+a
a
a
a
diff -u 的输出可以分为三个部分,其中第一部分如下:
--- f1 2014-06-03 00:47:10.859206823 +0800
+++ f2 2014-06-03 00:47:06.319206762 +0800
# --- 表示改动前的文件.+++ 表示改动后的文件.
第2部分如下:
@@ -1,7 +1,7 @@
# @@ ... @@ 指明了一个改动位置.
# -1,7 中;'-'表示 --- 指向的文件,即改动前的文件.'1' 表示第1行,'7'表示连续7行.
# +1,7 中;'+'表示 +++ 指向的文件,即改动后的文件.'1' 表示第1行,'7'表示连续7行.
# @@ ... @@ 的总的意思就是文件 f1 的[1,8)行经过如下改动后变为 f2 的[1,8)行.
第3部分就是改动的详细内容:
a
a
a
-b # '-' 表示移除该行.
+a # '+' 表示新增该行.
a
a
a
智能协议/哑协议
智能协议,会话的两个版本库会打开辅助程序进行数据传输,直观感受: 具有进度显示,按需传输速度快.
哑协议,会话的两个版本库不会打开辅助程序,直观感受: 不具有进度显示,速度慢.
快进式推进
远程版本库相应分支的最新提交是本地版本库相应分支最新提交的祖先,则此时 git push 为快进式推进.
.git/logs
日志空间,该目录下所有文件均是日志.
每一个引用文件在 .git/logs 下都有对应的日志文件,用于记录引用文件内容的变更,即当引用文件内容发生变化时,就将新的内容追加在它对应的日志文件的末尾.如:
$ cat .git/refs/heads/master # 显示 master 引用文件的内容
485499
$ cat .git/logs/refs/heads/master # 显示 master 对应日志文件的内容
000000 f3e91c commit (initial): 初始化
f3e91c 236aa5 commit: * get_modify_args.c
236aa5 4f7bff merge 重做: Fast-forward
4f7bff 11176b commit: 添加了 unmap 子命令
11176b e137ac commit: 新增 protect 子命令
e137ac 49a5d7 commit (amend): 新增 protect 子命令
49a5d7 485499 commit (amend): 新增 protect 子命令
$ git commit -m"测试" --allow-empty # 创建一个空的提交.
[master 903b0f] 测试
$ cat .git/refs/heads/master # master 引用文件内容变化,指向着最新提交.
903b0f
$ cat .git/logs/refs/heads/master # master 对应日志文件内容也变化
000000 f3e91c commit (initial): 初始化
f3e91c 236aa5 commit: * get_modify_args.c
236aa5 4f7bff merge 重做: Fast-forward
4f7bff 11176b commit: 添加了 unmap 子命令
11176b e137ac commit: 新增 protect 子命令
e137ac 49a5d7 commit (amend): 新增 protect 子命令
49a5d7 485499 commit (amend): 新增 protect 子命令
485499 903b0f commit: 测试
.git/objects
该目录用于存放所有的 git object.即 commit object,tag object,blob object,tree object.
若一个 git object 的 SHA1 值为 XYXXX...YYY,则其在 .git/objects 下的路径为: XY/XXX...YYY.如 commit object 903b0f4f9ad3d1f59a9cd12eb63ab6f21dbec1b9 的路径为:
$ ls -l .git/objects/90/3b0f4f9ad3d1f59a9cd12eb63ab6f21dbec1b9
-r--r--r-- .git/objects/90/3b0f4f9ad3d1f59a9cd12eb63ab6f21dbec1b9
.git/index 暂存区
.git/index 可以认为就是一个 tree object,存放着暂存区目录树的信息.
当使用git status.
若一个文件仅在工作区或者暂存区中存在,则输出相应的提示信息.
否则一个文件在工作区,暂存区中均存在,此时:
// 在 tree object 中保存着文件的最后一次修改时间
if(文件在工作区的最后一次修改时间 == 文件在暂存区中的最后一次修改时间){
说明文件没有经过改动;
略过该文件,继续下一个文件;
}else{
比较文件的内容;
// 在 tree object 中存在着保存文件内容的 blob object 的 SHA1 值.
if(文件内容没有经过改变)
更新文件在暂存区中的最后一次修改时间;
// 这样下一次调用 git status 就不必要读取文件内容.
else
输出相应的提示信息,表明文件经过改动.
}
当使用 git diff 时,与 git status 类似,只不过当文件内容经过改动时,输出改动信息.
当使用 git add 时:
if(文件在暂存区中不存在 || 文件经过改动){
新建一个 blob object,将改动后的文件信息写入该 blob object 中;
更新文件在暂存区的文件状态信息,以及对应 blob object 的 SHA1 值;
// 参考 tree object 的结构.
}else 直接返回.
标签
存放着 .git/refs/tags/ 下,也是引用文件,通过对 commit object/tag object 的引用来表明当前提交具有'里程碑'的意义.
轻量级标签,仅是通过 commit object 的引用来表明当前提交具有里程碑的意义,并没有新建一个 tag object.轻量级标签不可以带有注释,以及不可以被签名.
重量级标签,是对 tag object 的引用,即通过新建一个 tag object 来表明当前提交具有里程碑的意义,可以带有注释,以及可以被签名.如:
$ git tag Qing # 创建一个轻量级标签
$ git tag -m"注释" Zhong # 创建一个重量级标签
$ ls -l .git/refs/tags/
总用量 8
-rw-r--r-- 1 root root 41 6月 1 13:12 Qing
-rw-r--r-- 1 root root 41 6月 1 13:12 Zhong
$ git cat-file -t $(cat .git/refs/tags/Zhong) # 重量级标签是对 tag object 的引用.
tag
$ git cat-file -t $(cat .git/refs/tags/Qing) # 轻量级标签是对 commit object 的引用
commit
git push/git fetch 与 标签
当使用 git push 时,默认是不会上传标签的,即 tag object 与 .git/refs/tags/ 下的内容不会被上传.除非显式上传,如使用 --tags 选项,或者在 git push 时使用引用表达式.
当使用 git pull 时,默认会下载当前远程分支上的 tag ,而不会获取其他分支.
当本地已有同名 tag ,则默认不会下载对应的 tag ,除非显式使用引用表达式!
此时下载下来的 tag 引用并不是存放在 .git/refs/remote/<远程版本库名>/tags 中,而是统一存放在 .git/refs/tags 下.
当使用 git branch -d -r 删除远程分支时,并不会删除关联的 tag object.
工作区,暂存区,版本库
git init 创建的 .git 目录就被成为 git 版本库;.git 目录所处的目录为工作区.
工作区,也相当于一个 tree object,保存着位于工作区的文件,目录信息.
裸版本库
裸版本库,也即没有工作区的版本库,此时 core.bare==true.如:
$ git clone --bare Test/ Git_bare
克隆到裸版本库 'Git_bare'...
完成。
Checking connectivity... done
$ cd Git_bare/
$ ls # 裸版本库没有工作区,
branches config description HEAD hooks info objects packed-refs refs
$ git config core.bare
true # 并且 core.bare 为真
分支
分支,就是由若干个 commit object 组成的一条链.从应用的角度,分支可以分为:
发布分支,待补充
特性分支,待补充
卖主分支,待补充
git 配置
git 配置文件是由一系列名值对组成,并且使用'节'的概念将这些名值对组织在一起,如:
$ cat .git/config
[core] # 就是一个节,
repositoryformatversion = 0
filemode = true # 通过 core.filemode 来访问该选项.
bare = false
logallrefupdates = true
[remote "origin"]
url = git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
fetch = +refs/heads/*:refs/remotes/origin/* # 通过 remote.origin.fetc 来访问该选项.
[branch "master"]
remote = origin # 通过 branch.master.remote 来访问该项
merge = refs/heads/master
注意,对配置项的访问方式.
配置文件的层次
级别名称 | 文件位置 | 访问/修改 | 优先级别 |
系统级别 | /etc/gitconfig | git config --system | 最低 |
用户级别 | ~/.gitconfig | git config --global | 中等 |
项目级别 | 项目目录/.git/config | git config | 最高 |
优先级高的会覆盖优先级低的
常用配置
color
ui true/false,若 color.ui 为真,则在 git 的输出中会开启颜色显示.
core
logAllRefUpdates true/false 若为 true,表明启用引用日志功能,即每当引用文件(.git/HEAD,.git/refs/heads/*.*,.git/refs/stash)改变时,就将改变信息记录在 .git/logs/ 指定的文件中.裸版本库不会将该属性设置为 true,所以若有必要,应该手动设置.
receive
denyNonFastForwards true/false 若未真,则总会拒绝其他版本库的非快进式提交.
denyDeletes true/false 若为真,则禁止其他版本库通过 git push :remote-ref 来删除本地引用,效果如下:
A$ git branch # 版本库 A 当前分支. b1 b2 * master A$ git config receive.denyDeletes true B$ git push origin :refs/heads/b1 # 在 B 中试图删除 A 的 b1 分支. remote: error: denying ref deletion for refs/heads/b1 To /root/CCProject/A ! [remote rejected] b1 (deletion prohibited) error: 无法推送一些引用到 '/root/CCProject/A' # 由于在 A 中 receive.denyDeletes==true,所以删除失败 A$ git branch -D b1 # denyDeletes 只是禁止其他版本库删除,而不是本地版本库 已删除分支 b1(曾为 787d106). A$ git config receive.denyDeletes false B$ git push origin :refs/heads/b2 To /root/CCProject/A - [deleted] b2 # 由于 receive.denyDeletes==false,所以在 B 中删除 A 的 b1 分支成功. A$ git branch * master
merge
tool 配置了调用 git mergetool 时启用的工具.若配置的工具不被 git 内部支持,则还需要配置 merge.<tool>.path(确定路径) merge.<tool>.cmd(调用该工具时的命令),不过一般下 git 支持很多种工具,包括: kdiff3,meld.
conflictstyle merge/diff3
# 若其值为 merge,则会在冲突文件使用'<<<<<<<','=======','>>>>>>>'对冲突内容标识,如: <<<<<<< #本地修改的版本 ======== >>>>>>> #他人修改的版本 # 若其值为 diff3,则会在冲突文件下使用'<<<','|||','>>>','==='对冲突内容进行标识,如: <<<<<<<#本地修改的版本 |||||||#祖先版本 =======#他人修改的版本 >>>>>>>