提交 9b8466b8 编写于 作者: 沉默王二's avatar 沉默王二 💬

git

上级 66a724fd
......@@ -70,6 +70,11 @@
- [this 和 super 关键字](docs/oo/this-super.md)
- [final 关键字](docs/oo/final.md)
- [instanceof 关键字](docs/oo/instanceof.md)
- [方法重载和方法重写](docs/basic-extra-meal/override-overload.md)
- [Java 到底是值传递还是引用传递](docs/basic-extra-meal/pass-by-value.md)
- [Java的不可变对象](docs/basic-extra-meal/immutable.md)
- [可变参数](docs/basic-extra-meal/varables.md)
### **字符串**
......@@ -91,6 +96,7 @@
- [初识集合框架](docs/collection/gailan.md)
- [时间复杂度](docs/collection/big-o.md)
- [ArrayList](docs/collection/arraylist.md)
- [泛型](docs/basic-extra-meal/generic.md)
- [LinkedList](docs/collection/linkedlist.md)
- [ArrayList 重拳出击,把 LinkedList 干翻在地](docs/collection/list-war-1.md)
- [被 ArrayList 锤了一拳后,LinkedList 很不服气](docs/collection/list-war-2.md)
......@@ -100,7 +106,6 @@
- [HashMap 的扩容机制](docs/collection/hashmap-resize.md)
- [HashMap 的加载因子为什么是 0.75](docs/collection/hashmap-loadfactor.md)
- [为什么 HashMap 是线程不安全的?](docs/collection/hashmap-thread-nosafe.md)
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
### **异常处理**
......@@ -126,29 +131,27 @@
- [Java 默认的编码方式 Unicode](docs/basic-extra-meal/java-unicode.md)
- [new Integer(18) 与 Integer.valueOf(18) 有什么区别](docs/basic-extra-meal/int-cache.md)
- [自动拆箱与自动装箱](docs/basic-extra-meal/box.md)
- [方法重载和方法重写](docs/basic-extra-meal/override-overload.md)
- [Java 到底是值传递还是引用传递](docs/basic-extra-meal/pass-by-value.md)
- [浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
- [为什么重写 equals 时必须重写 hashCode 方法](docs/basic-extra-meal/equals-hashcode.md)
- [注解](docs/basic-extra-meal/annotation.md)
- [枚举](docs/basic-extra-meal/enum.md)
- [深入理解 Java 中的反射](docs/basic-extra-meal/fanshe.md)
- [泛型](docs/basic-extra-meal/generic.md)
- [Java 不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
- [Java程序在编译期发生了什么](docs/basic-extra-meal/what-happen-when-javac.md)
- [马蜂窝一面:Comparable和Comparator有什么区别?](docs/basic-extra-meal/comparable-omparator.md)
- [手撸了一个Java的不可变对象,很哇塞!](docs/basic-extra-meal/immutable.md)
- [从原理上搞懂可变参数,就靠它了](docs/basic-extra-meal/varables.md)
- [Comparable和Comparator有什么区别?](docs/basic-extra-meal/comparable-omparator.md)
## Java 企业级开发
### **Maven**
- [保姆级神器 Maven,再也不用担心项目构建搞崩了](docs/maven/maven.md)
- [项目构建神器 Maven](docs/maven/maven.md)
### **Git**
- [Git 的前世今生](docs/git/git-qiyuan.md)
- [Git 的数据模型](docs/git/shujujiegou.md)
- [Git 的内部实现](docs/git/neibushixian.md)
- [常用 Git 命令清单](docs/git/mingling.md)
## Java 进阶
......@@ -160,6 +163,11 @@
- [JVM 是什么?](docs/jvm/what-is-jvm.md)
## 八股文
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
# :paw_prints: 联系作者
- **技术交流群**
......
......@@ -3,9 +3,9 @@
- [什么是 Java](docs/overview/what-is-java.md)
- [Java 发展简史](docs/overview/java-history.md)
- [Java 的优势](docs/overview/java-advantage.md)
- [JDK 和 JRE 有什么区别](docs/overview/jdk-jre.md)
- [安装集成开发环境 Intellij IDEA](docs/overview/idea.md)
- [第一个 Java 程序:Hello World](docs/overview/hello-world.md)
- [JDK 和 JRE](docs/overview/jdk-jre.md)
- [安装 Intellij IDEA](docs/overview/idea.md)
- [Hello World](docs/overview/hello-world.md)
**Java 基础语法**
......@@ -13,31 +13,35 @@
- [基本数据类型](docs/basic-grammar/basic-data-type.md)
- [流程控制](docs/basic-grammar/flow-control.md)
- [运算符](docs/basic-grammar/operator.md)
- [注释:代码的最强辅助](docs/basic-grammar/javadoc.md)
- [注释](docs/basic-grammar/javadoc.md)
**面向对象**
- [什么是对象?什么是](docs/oo/object-class.md)
- [对象和](docs/oo/object-class.md)
- [变量](docs/oo/var.md)
- [方法](docs/oo/method.md)
- [构造方法](docs/oo/construct.md)
- [代码初始化块](docs/oo/code-init.md)
- [抽象类](docs/oo/java-abstract.md)
- [接口](docs/oo/interface.md)
- [static 关键字](docs/oo/static.md)
- [this 和 super 关键字](docs/oo/this-super.md)
- [final 关键字](docs/oo/final.md)
- [instanceof 关键字](docs/oo/instanceof.md)
- [static](docs/oo/static.md)
- [this 和 super](docs/oo/this-super.md)
- [final](docs/oo/final.md)
- [instanceof](docs/oo/instanceof.md)
- [方法重载和方法重写](docs/basic-extra-meal/override-overload.md)
- [值传递和引用传递](docs/basic-extra-meal/pass-by-value.md)
- [不可变对象](docs/basic-extra-meal/immutable.md)
- [可变参数](docs/basic-extra-meal/varables.md)
**字符串**
- [String 为什么是不可变的](docs/string/immutable.md)
- [初识String](docs/string/immutable.md)
- [字符串常量池](docs/string/constant-pool.md)
- [深入浅出 String.intern](docs/string/intern.md)
- [如何比较两个字符串是否相等](docs/string/equals.md)
- [如何拼接字符串](docs/string/join.md)
- [如何拆分字符串](docs/string/split.md)
- [String.intern](docs/string/intern.md)
- [比较字符串](docs/string/equals.md)
- [拼接字符串](docs/string/join.md)
- [拆分字符串](docs/string/split.md)
**数组**
......@@ -49,16 +53,16 @@
- [初识集合框架](docs/collection/gailan.md)
- [时间复杂度](docs/collection/big-o.md)
- [ArrayList](docs/collection/arraylist.md)
- [泛型](docs/basic-extra-meal/generic.md)
- [LinkedList](docs/collection/linkedlist.md)
- [ArrayList 重拳出击,把 LinkedList 干翻在地](docs/collection/list-war-1.md)
- [被 ArrayList 锤了一拳后,LinkedList 很不服气](docs/collection/list-war-2.md)
- [海康威视一面:Iterator与Iterable有什么区别?](docs/collection/iterator-iterable.md)
- [为什么阿里巴巴强制不要在 foreach 里执行删除操作](docs/collection/fail-fast.md)
- [ArrayList和LinkedList](docs/collection/list-war-1.md)
- [ArrayList和LinkedList第二战](docs/collection/list-war-2.md)
- [Iterator与Iterable](docs/collection/iterator-iterable.md)
- [为什么不要在 foreach 里执行删除操作](docs/collection/fail-fast.md)
- [HashMap 的 hash 原理](docs/collection/hash.md)
- [HashMap 的扩容机制](docs/collection/hashmap-resize.md)
- [HashMap 的加载因子为什么是 0.75](docs/collection/hashmap-loadfactor.md)
- [为什么 HashMap 是线程不安全的?](docs/collection/hashmap-thread-nosafe.md)
- [HashMap 精选面试题(背诵版)](docs/collection/hashmap-interview.md)
**异常处理**
......@@ -81,32 +85,29 @@
- [Java 中常用的 48 个关键字](docs/basic-extra-meal/48-keywords.md)
- [Java 命名约定](docs/basic-extra-meal/java-naming.md)
- [Java 默认的编码方式 Unicode](docs/basic-extra-meal/java-unicode.md)
- [new Integer(18) 与 Integer.valueOf(18) 有什么区别](docs/basic-extra-meal/int-cache.md)
- [new Integer(18) 与 Integer.valueOf(18) ](docs/basic-extra-meal/int-cache.md)
- [自动拆箱与自动装箱](docs/basic-extra-meal/box.md)
- [方法重载和方法重写](docs/basic-extra-meal/override-overload.md)
- [Java 到底是值传递还是引用传递](docs/basic-extra-meal/pass-by-value.md)
- [浅拷贝与深拷贝](docs/basic-extra-meal/deep-copy.md)
- [为什么重写 equals 时必须重写 hashCode 方法](docs/basic-extra-meal/equals-hashcode.md)
- [注解](docs/basic-extra-meal/annotation.md)
- [枚举](docs/basic-extra-meal/enum.md)
- [深入理解 Java 中的反射](docs/basic-extra-meal/fanshe.md)
- [泛型](docs/basic-extra-meal/generic.md)
- [Java 不能实现真正泛型的原因是什么?](docs/basic-extra-meal/true-generic.md)
- [Java 不能实现真正泛型的原因](docs/basic-extra-meal/true-generic.md)
- [Java程序在编译期发生了什么](docs/basic-extra-meal/what-happen-when-javac.md)
- [马蜂窝一面:Comparable和Comparator有什么区别?](docs/basic-extra-meal/comparable-omparator.md)
- [手撸了一个Java的不可变对象,很哇塞!](docs/basic-extra-meal/immutable.md)
- [从原理上搞懂可变参数,就靠它了](docs/basic-extra-meal/varables.md)
- [Comparable和Comparator](docs/basic-extra-meal/comparable-omparator.md)
**Maven**
- [保姆级神器 Maven,再也不用担心项目构建搞崩了](docs/maven/maven.md)
- [项目构建神器 Maven](docs/maven/maven.md)
**Git**
- [Git 的前世今生](docs/git/git-qiyuan.md)
- [Git 的数据模型](docs/git/shujujiegou.md)
- [Git 的内部实现](docs/git/neibushixian.md)
- [常用 Git 命令清单](docs/git/mingling.md)
**Java IO**
......@@ -114,7 +115,11 @@
**Java 虚拟机**
- [JVM 是什么?](docs/jvm/what-is-jvm.md)
- [初识JVM](docs/jvm/what-is-jvm.md)
**八股文**
- [HashMap](docs/collection/hashmap-interview.md)
- **其他:**
......
虽然每天多多少少都会敲一些 Git 命令,但仍然有很多记不住,可怜我这脑袋瓜子了。。
一般来说,日常使用只要记住下图中这 6 个命令就可以了,但是熟练使用 Git,恐怕要记住60~100个命令~
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/git/mingling-01.png)
在 Git 专题的[开篇](https://mp.weixin.qq.com/s/hzEnH3ThvuRDW4EeBLlumw),我就提醒大家一定要记住这几个专用名词,对掌握 Git 有很大的帮助:
- Workspace:工作区
- Index / Stage:暂存区
- Repository:仓库区(或本地仓库)
- Remote:远程仓库
当然了,没记住的话,也不要紧了,今天就趁机再温故一遍。
下面是阮一峰老师整理的常用 Git 命令清单,有必要的话,可以打印一份出来,放在工作台~
>http://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html
### 一、新建代码库
```
# 在当前目录新建一个Git代码库
$ git init
# 新建一个目录,将其初始化为Git代码库
$ git init [project-name]
# 下载一个项目和它的整个代码历史
$ git clone [url]
```
### 二、配置
Git的配置文件为.gitconfig,它可以在用户主目录下(全局配置),也可以在项目目录下(项目配置)。
```
# 显示当前的Git配置
$ git config --list
# 编辑Git配置文件
$ git config -e [--global]
# 设置提交代码时的用户信息
$ git config [--global] user.name "[name]"
$ git config [--global] user.email "[email address]"
```
### 三、增加/删除文件
```
# 添加指定文件到暂存区
$ git add [file1] [file2] ...
# 添加指定目录到暂存区,包括子目录
$ git add [dir]
# 添加当前目录的所有文件到暂存区
$ git add .
# 添加每个变化前,都会要求确认
# 对于同一个文件的多处变化,可以实现分次提交
$ git add -p
# 删除工作区文件,并且将这次删除放入暂存区
$ git rm [file1] [file2] ...
# 停止追踪指定文件,但该文件会保留在工作区
$ git rm --cached [file]
# 改名文件,并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]
```
### 四、代码提交
```
# 提交暂存区到仓库区
$ git commit -m [message]
# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]
# 提交工作区自上次commit之后的变化,直接到仓库区
$ git commit -a
# 提交时显示所有diff信息
$ git commit -v
# 使用一次新的commit,替代上一次提交
# 如果代码没有任何新变化,则用来改写上一次commit的提交信息
$ git commit --amend -m [message]
# 重做上一次commit,并包括指定文件的新变化
$ git commit --amend [file1] [file2] ...
```
### 五、分支
```
# 列出所有本地分支
$ git branch
# 列出所有远程分支
$ git branch -r
# 列出所有本地分支和远程分支
$ git branch -a
# 新建一个分支,但依然停留在当前分支
$ git branch [branch-name]
# 新建一个分支,并切换到该分支
$ git checkout -b [branch]
# 新建一个分支,指向指定commit
$ git branch [branch] [commit]
# 新建一个分支,与指定的远程分支建立追踪关系
$ git branch --track [branch] [remote-branch]
# 切换到指定分支,并更新工作区
$ git checkout [branch-name]
# 切换到上一个分支
$ git checkout -
# 建立追踪关系,在现有分支与指定的远程分支之间
$ git branch --set-upstream [branch] [remote-branch]
# 合并指定分支到当前分支
$ git merge [branch]
# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]
# 删除分支
$ git branch -d [branch-name]
# 删除远程分支
$ git push origin --delete [branch-name]
$ git branch -dr [remote/branch]
```
### 六、标签
```
# 列出所有tag
$ git tag
# 新建一个tag在当前commit
$ git tag [tag]
# 新建一个tag在指定commit
$ git tag [tag] [commit]
# 删除本地tag
$ git tag -d [tag]
# 删除远程tag
$ git push origin :refs/tags/[tagName]
# 查看tag信息
$ git show [tag]
# 提交指定tag
$ git push [remote] [tag]
# 提交所有tag
$ git push [remote] --tags
# 新建一个分支,指向某个tag
$ git checkout -b [branch] [tag]
```
### 七、查看信息
```
# 显示有变更的文件
$ git status
# 显示当前分支的版本历史
$ git log
# 显示commit历史,以及每次commit发生变更的文件
$ git log --stat
# 搜索提交历史,根据关键词
$ git log -S [keyword]
# 显示某个commit之后的所有变动,每个commit占据一行
$ git log [tag] HEAD --pretty=format:%s
# 显示某个commit之后的所有变动,其"提交说明"必须符合搜索条件
$ git log [tag] HEAD --grep feature
# 显示某个文件的版本历史,包括文件改名
$ git log --follow [file]
$ git whatchanged [file]
# 显示指定文件相关的每一次diff
$ git log -p [file]
# 显示过去5次提交
$ git log -5 --pretty --oneline
# 显示所有提交过的用户,按提交次数排序
$ git shortlog -sn
# 显示指定文件是什么人在什么时间修改过
$ git blame [file]
# 显示暂存区和工作区的差异
$ git diff
# 显示暂存区和上一个commit的差异
$ git diff --cached [file]
# 显示工作区与当前分支最新commit之间的差异
$ git diff HEAD
# 显示两次提交之间的差异
$ git diff [first-branch]...[second-branch]
# 显示今天你写了多少行代码
$ git diff --shortstat "@{0 day ago}"
# 显示某次提交的元数据和内容变化
$ git show [commit]
# 显示某次提交发生变化的文件
$ git show --name-only [commit]
# 显示某次提交时,某个文件的内容
$ git show [commit]:[filename]
# 显示当前分支的最近几次提交
$ git reflog
```
### 八、远程同步
```
# 下载远程仓库的所有变动
$ git fetch [remote]
# 显示所有远程仓库
$ git remote -v
# 显示某个远程仓库的信息
$ git remote show [remote]
# 增加一个新的远程仓库,并命名
$ git remote add [shortname] [url]
# 取回远程仓库的变化,并与本地分支合并
$ git pull [remote] [branch]
# 上传本地指定分支到远程仓库
$ git push [remote] [branch]
# 强行推送当前分支到远程仓库,即使有冲突
$ git push [remote] --force
# 推送所有分支到远程仓库
$ git push [remote] --all
```
### 九、撤销
```
# 恢复暂存区的指定文件到工作区
$ git checkout [file]
# 恢复某个commit的指定文件到暂存区和工作区
$ git checkout [commit] [file]
# 恢复暂存区的所有文件到工作区
$ git checkout .
# 重置暂存区的指定文件,与上一次commit保持一致,但工作区不变
$ git reset [file]
# 重置暂存区与工作区,与上一次commit保持一致
$ git reset --hard
# 重置当前分支的指针为指定commit,同时重置暂存区,但工作区不变
$ git reset [commit]
# 重置当前分支的HEAD为指定commit,同时重置暂存区和工作区,与指定commit一致
$ git reset --hard [commit]
# 重置当前HEAD为指定commit,但保持暂存区和工作区不变
$ git reset --keep [commit]
# 新建一个commit,用来撤销指定commit
# 后者的所有变化都将被前者抵消,并且应用到当前分支
$ git revert [commit]
# 暂时将未提交的变化移除,稍后再移入
$ git stash
$ git stash pop
```
### 十、其他
```
# 生成一个可供发布的压缩包
$ git archive
```
\ No newline at end of file
学习 Git 的内部实现,最好的办法是看 Linus 最初的代码提交,checkout 出 Git 项目的第一次提交节点,可以看到代码库中只有几个文件:一个 README,一个构建脚本 Makefile,剩下几个 C 源文件。这次 commit 的备注写的也非常特别:
```
commit e83c5163316f89bfbde7d9ab23ca2e25604af290
Author: Linus Torvalds <torvalds@ppc970.osdl.org>
Date: Thu Apr 7 15:13:13 2005 -0700
Initial revision of "git", the information manager from hell
```
在 README 中,Linus 详细描述了 Git 的设计思路。看似复杂的 Git 工作,在 Linus 的设计里,只有两种对象抽象:
- 对象数据库(“object database”);
- 当前目录缓存(“current directory cache”)。
Git 的本质就是一系列的文件对象集合,代码文件是对象、文件目录树是对象、commit 也是对象。这些文件对象的名称即内容的 SHA1 值,SHA1 哈希算法的值为 40 位。Linus 将前二位作为文件夹、后 38 位作为文件名。大家可以在 .git 目录里的 objects 里看到有很多两位字母/数字名称的目录,里面存储了很多 38 位 hash 值名称的文件,这就是 Git 的所有信息。
Linus 在设计对象的数据结构时按照 <标签ascii码表示>(blob/tree/commit) + <空格> + <长度ascii码表示> + <\0> + <二进制数据内容> 来定义,大家可以用 xxd 命令看下 objects 目录里的对象文件(需 zlib 解压),比如一个 tree 对象文件内容如下:
```
00000000: 7472 6565 2033 3700 3130 3036 3434 2068 tree 37.100644 h
00000010: 656c 6c6f 2e74 7874 0027 0c61 1ee7 2c56 ello.txt.'.a..,V
00000020: 7bc1 b2ab ec4c bc34 5bab 9f15 ba
```
对象有三种:BLOB、TREE、CHANGESET。
BLOB: 即二进制对象,这就是 Git 存储的文件,Git 不像某些 VCS (如 SVN)那样存储变更 delta 信息,而是存储文件在每一个版本的完全信息。
比如先提交了一份 hello.c 进入了 Git 库,会生成一个 BLOB 文件完整记录 hello.c 的内容;对 hello.c 修改后,再提交 commit,会再生成一个新的 BLOB 文件记录修改后的 hello.c 全部内容。
Linus 在设计时,BLOB 中仅记录文件的内容,而不包含文件名、文件属性等元数据信息,这些信息被记录在第二种对象 TREE 里。
TREE: 目录树对象。在 Linus 的设计里,TREE 对象就是一个时间切片中的目录树信息抽象,包含了文件名、文件属性及 BLOB 对象的 SHA1 值信息,但没有历史信息。这样的设计好处是可以快速比较两个历史记录的 TREE 对象,不能读取内容,而根据 SHA1 值显示一致和差异的文件。
另外,由于 TREE 上记录文件名及属性信息,对于修改文件属性或修改文件名、移动目录而不修改文件内容的情况,可以复用 BLOB 对象,节省存储资源。而 Git 在后来的开发演进中又优化了 TREE 的设计,变成了某一时间点文件夹信息的抽象,TREE 包含其子目录的 TREE 的对象信息(SHA1)。这样,对于目录结构很复杂或层级较深的 Git 库 可以节约存储资源。历史信息被记录在第三种对象 CHANGESET 里。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/git/neibushixian-01.png)
CHANGESET:即 Commit 对象。一个 CHANGESET 对象中记录了该次提交的 TREE 对象信息(SHA1),以及提交者(committer)、提交备注(commit message)等信息。
跟其他 SCM(软件配置管理)工具所不同的是,Git 的 CHANGESET 对象不记录文件重命名和属性修改操作,也不会记录文件修改的 Delta 信息等,CHANGESET 中会记录父节点 CHANGESET 对象的 SHA1 值,通过比较本节点和父节点的 TREE 信息来获取差异。
Linus 在设计 CHANGESET 父节点时允许一个节点最多有 16 个父节点,虽然超过两个父节点的合并是很奇怪的事情,但实际上,Git 是支持超过两个分支的多头合并的。
Linus 在三种对象的设计解释后着重阐述了可信(TRUST):虽然 Git 在设计上没有涉及可信的范畴,但 Git 作为配置管理工具是可以做到可信的。原因是所有的对象都以 SHA1 编码(Google 实现 SHA1 碰撞攻击是后话,且 Git 社区也准备使用更高可靠性的 SHA256 编码来代替),而签入对象的过程可信靠签名工具保证,如 GPG 工具等。
理解了 Git 的三种基本对象,那么对于 Linus 对于 Git 初始设计的“对象数据库”和“当前目录缓存”这两层抽象就很好理解了。加上原本的工作目录,Git 有三层抽象,如下图示:一个是当前工作区(Working Directory),也就是我们查看/编写代码的地方,一个是 Git 仓库(Repository),即 Linus 说的对象数据库,我们在 Git 仓看到的 .git 文件夹中存储的内容,Linus 在第一版设计时命名为 .dircache,在这两个存储抽象中还有一层中间的缓存区(Staging Area),即 .git/index 里存储的信息,我们在执行 git add 命令时,便是将当前修改加入到了缓存区。
Linus 解释了“当前目录缓存”的设计,该缓存就是一个二进制文件,内容结构很像 TREE 对象,与 TREE 对象不同的是 index 不会再包含嵌套 index 对象,即当前修改目录树内容都在一个 index 文件里。这样设计有两个好处:
- 1. 能够快速的复原缓存的完整内容,即使不小心把当前工作区的文件删除了,也可以从缓存中恢复所有文件;
- 2. 能够快速找出缓存中和当前工作区内容不一致的文件。
![](https://cdn.jsdelivr.net/gh/itwanger/toBeBetterJavaer/images/git/neibushixian-02.png)
Linus 在 Git 的第一次代码提交里便完成了 Git 的最基础功能,并可以编译使用。代码极为简洁,加上 Makefile 一共只有 848 行。感兴趣的话可以通过上一段所述方法 checkout Git 最早的 commit 上手编译玩玩,只要有 Linux 环境即可。
因为依赖库版本的问题,需要对原始 Makefile 脚本做些小修改。Git 第一个版本依赖 openssl 和 zlib 两个库,需要手工安装这两个开发库。在 ubuntu 上执行: sudo apt install libssl-dev libz-dev ;然后修改 makefile 在 LIBS= -lssl 行 中的 -lssl 改成 -lcrypto 并增加 -lz ;最后执行 make,忽略编译告警,会发现编出了7个可执行程序文件:init-db, update-cache, write-tree, commit-tree, cat-file, show-diff 和 read-tree。
下面分别简要介绍下这些可执行程序的实现:
- init-db: 初始化一个 git 本地仓库,这也就是我们现在每次初始化建立 git 库式敲击的 git init 命令。只不过一开始 Linus 建立的仓库及 cache 文件夹名称叫 .dircache,而不是我们现在所熟知的 .git 文件夹。
- update-cache: 输入文件路径,将该文件(或多个文件)加入缓冲区中。具体实现是:校验路径合法性,然后将文件计算 SHA1值,将文件内容加上 blob 头信息进行 zlib 压缩后写入到对象数据库(.dircache/objects)中;最后将文件路径、文件属性及 blob sha1 值更新到 .dircache/index 缓存文件中。
- write-tree: 将缓存的目录树信息生成 TREE 对象,并写入对象数据库中。TREE 对象的数据结构为:‘tree ‘ + 长度 + \0 + 文件树列表。文件树列表中按照 文件属性 + 文件名 + \0 + SHA1 值结构存储。写入对象成功后,返回该 TREE 对象的 SHA1 值。
- commit-tree: 将 TREE 对象信息生成 commit 节点对象并提交到版本历史中。具体实现是输入要提交的 TREE 对象 SHA1 值,并选择输入父 commit 节点(最多 16个),commit 对象信息中包含 TREE、父节点、committer 及作者的 name、email及日期信息,最后写入新的 commit 节点对象文件,并返回 commit 节点的 SHA1 值。
- cat-file: 由于所有的对象文件都经过 zlib 压缩,因此想要查看文件内容的话需要使用这个工具来解压生成临时文件,以便查看对象文件的内容。
- show-diff: 快速比较当前缓存与当前工作区的差异,因为文件的属性信息(包括修改时间、长度等)也保存在缓存的数据结构中,因此可以快速比较文件是否有修改,并展示差异部分。
- read-tree: 根据输入的 TREE 对象 SHA1 值输出打印 TREE 的内容信息。
这就是第一个可用版本的 Git 的全部七个子程序,可能用过 Git 的小伙伴会说:这怎么跟我常用的 Git 命令不一样呢?Git add, git commit 呢?是的,在最初的 Git 设计中是没有我们这些平常所使用的 git 命令的。
在 Git 的设计中,有两种命令:分别是底层命令(Plumbing commands)和高层命令(Porcelain commands)。一开始,Linus 就设计了这些给开源社区黑客使用的符合 Unix KISS 原则的命令,因为黑客们本身就是动手高手,水管坏了就撸起袖子去修理,因此这些命令被称为 plumbing commands。
后来接手 Git 的 Junio Hamano 觉得这些命令对于普通用户不太友好,因此在此之上,封装了更易于使用、接口更精美的高层命令,也就是我们今天每天使用的 git add, git commit 之类。Git add 就是封装了 update-cache 命令,而 git commit 就是封装了 write-tree, commit-tree 命令。
\ No newline at end of file
尽管 Git 的接口有些难懂,但它底层的设计和思想却非常的优雅。难懂的接口只能靠死记硬背,但优雅的底层设计则非常容易理解。我们可以通过一种自底向上的方式来学习 Git,先了解底层的数据模型,再学习它的接口。可以这么说,一旦搞懂了 Git 的数据模型,再学习它的接口并理解这些接口是如何操作数据模型的就非常容易了。
进行版本控制的方法很多,Git 拥有一个精心设计的模型,这使其能够支持版本控制所需的所有特性,比如维护历史记录、支持分支和团队协作。
### 快照
Git 将顶级目录中的文件和文件夹称作集合,并通过一系列快照来管理历史记录。在 Git 的术语中,文件被称为 blob 对象(数据对象),也就是一组数据。目录则被称为 tree(树),目录中可以包含文件和子目录。
```
<root> (tree)
|
+- foo (tree)
| |
| + bar.txt (blob, contents = "hello world")
|
+- baz.txt (blob, contents = "git is wonderful")
```
顶层的树(也就是 root) 包含了两个元素,一个名为 foo 的子树(包含了一个 blob 对象“bar.txt”),和一个 blob 对象“baz.txt”。
### 历史记录建模:关联快照
版本控制系统是如何和快照进行关联的呢?线性历史记录是一种最简单的模型,它包含了一组按照时间顺序线性排列的快照。不过,出于种种原因,Git 没有采用这种模型。
在 Git 中,历史记录是一个由快照组成的有向无环图。“有向无环图”,听起来很高大上,但其实并不难理解。我们只需要知道这代表 Git 中的每个快照都有一系列的父辈,也就是之前的一系列快照。这些快照通常被称为“commit”,看起来好像是下面这样:
```
o <-- o <-- o <-- o
^
\
--- o <-- o
```
o 表示一次 commit,也就是一次快照。箭头指向了当前 commit 的父辈。在第三次 commit 之后,历史记录分叉成了两条独立的分支,这可能是因为要同时开发两个不同的特性,它们之间是相互独立的。开发完成后,这些分支可能会被合并为一个新的 commit,这个新的 commit 会同时包含这些特性,看起来好像是下面这样:
```
o <-- o <-- o <-- o <---- o
^ /
\ v
--- o <-- o
```
Git 中的 commit 是不可改变的。当然了,这并不意味着不能被修改,只不过这种“修改”实际上是创建了一个全新的提交记录。
### 数据模型及其伪代码表示
以伪代码的形式来学习 Git 的数据模型,可能更加通俗易懂。
```
// 文件是一组数据
type blob = array<byte>
// 一个包含了文件和子目录的目录
type tree = map<string, tree | file>
// 每个 commit 都包含了一个父辈,元数据和顶层树
type commit = struct {
parent: array<commit> // 父辈
author: string // 作者
message: string // 信息
snapshot: tree // 快照
}
```
### 对象和内存寻址
Git 中的对象可以是 blob、tree 或者 commit:
```
type object = blob | tree | commit
```
Git 在存储数据的时候,所有的对象都会基于它们的安全散列算法进行寻址。
```
objects = map<string, object>
def store(object):
id = sha1(object)
objects[id] = object
def load(id):
return objects[id]
```
blob、tree 和 commit 一样,都是对象。当它们引用其他对象时,并没有真正在硬盘上保存这些对象,而是仅仅保存了它们的哈希值作为引用。
还记得之前的例子吗?
```
<root> (tree)
|
+- foo (tree)
| |
| + bar.txt (blob, contents = "hello world")
|
+- baz.txt (blob, contents = "git is wonderful")
```
root 引用的 foo 和 baz.txt 就像下面这样:
```
100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85 baz.txt
040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87 foo
```
### 引用
所有的快照都可以通过它们的哈希值来标记,但 40 位的十六进制字符实在是太难记了,很不方便。针对这个问题,Git 的解决办法是给这些哈希值赋予一个可读的名字,也就是引用(reference),引用是指向 commit 的指针,与对象不同,它是可变的,可以被更新,指向新的 commit。通常,master 引用通常会指向主分支的最新一次 commit。
```
references = map<string, string>
def update_reference(name, id):
references[name] = id
def read_reference(name):
return references[name]
def load_reference(name_or_id):
if name_or_id in references:
return load(references[name_or_id])
else:
return load(name_or_id)
```
这样,Git 就可以使用“master”这样容易被记住的名称来表示历史记录中特定的 commit,而不需要再使用一长串的十六进制字符了。
在 Git 中,当前的位置有一个特殊的索引,它就是“HEAD”。
## 仓库
我们可以粗略地给出 Git 仓库的定义了:对象 和 引用。
在硬盘上,Git 仅存储对象和引用,因为其数据模型仅包含这些东西。所有的 git 命令都对应着对 commit 树的操作。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册