git 常用命令
git 常见场景操作
git 底层原理的讲解中看到一篇非常非常赞的博文,此文中有很多内容摘自下面这篇博文,做了小汇总,强烈推荐
Yelbosh 大神的 git 博文
目前最先进的开源分布式版本控制系统,来源思想是,代码开发了多个版本,以往的思想是每个版本在电脑中 copy 一份保存,以免以后版本的代码出现问题,可以直接找之前几个版本的代码,这种就好似只有两层多子树的树形结构,git 的思想是自己电脑中不用 copy 下来每个版本,每个版本逻辑上是一个结点,每次更新到下一个版本,结点就自动往后延伸,是一个线性结构,若想恢复到之前哪个版本,也就是恢复到之前哪个结点,可以用 git 相关命令来将版本变为历史版本。git 比其他版本控制优秀在于其跟踪管理是改动而不是文件本身
1991 年 Linus 创建了开源的 Linux,自此 Linux 不断发展成为服务器系统首选,2002 年之前各地开源 Linux 的代码贡献者通过 diff 方式把源代码发给 Linus,然后 Linus 本人通过手工方式合并代码,Linus 那时坚定反对 CSV 和 SVN,因为这些集中式版本控制系统不但速度慢而且需要联网才行,虽然有一些好用的商用版本控制系统,可惜需要付费,这与 Linus 提倡的开源精神不符合,到 2002 年 Linux 代码库已经大到 Linus 很难维护了,社区成员也对此表示不满,于是 Linus 选择使用了商业版本控制系统 BitKeeper,该系统的公司处于人道主义授权 Linux 社区免费使用该版本控制系统,可是在 2005 年的时候,Liunx 社区中的开发 Samba 的安德鲁视图破解 BitKeeper 协议,其实还有社区里的其他人,被 BitKeeper 公司发现,一气之下,公司决定收回 Linux 社区免费授权,之后 Linus 没有选择向 BitKeeper 所有的公司道歉,而是选择自己花了两周左右的时间自己用 C 写了一个分布式版本控制系统,这就是 Git,一个月的之内 Linux 系统源码已经可以被 Git 管理了,接着 Git 就迅速成为最流行的分布式版本控制系统,2008 年的时候,GitHub 上线,无数开源项目通过 Git 存储在 GitHub 中直到现今
集中式
版本库放置于中央服务器,常规操作是先从中央库拉取最新版本信息,之后在自己电脑中修改,再把新版本推送给中央服务器。但需要联网才能操作,速度慢。常见有 SVN,CSV 等
分布式
每个人电脑里都有个版本库,有个人电脑坏掉,不要紧可以直接从其他人电脑复制一份过来即可,但实际使用中,很少有人在两人之间电脑推送修改,为了方便交换,通常也有个类似中央服务器的电脑,但是其仅仅是为了方便大家代码统一在此处交换
git 工作区有个隐藏目录 .git,这是 git 的版本库,其中有暂存区,默认的 master 分支以及指向 master 的 HEAD 指针
进入隐藏的 .git 目录之后可以看到如上图所示结构
核心文件:config,objects,HEAD,index,refs 这 5 个文件夹
refs:refs/heads/master
sha-1 算法介绍说明:其实 sha-1 算法在两千零几年中国已经被攻破,有人就提出 git 的安全性问题,由于 git 是 linus 在此算法还没被攻破时候创建,linus 现在不以为然,因为他认为没有人会发这么大经理偷偷篡改他人代码树,他认为即使是使用老掉牙的 md5 都是可行,未来 git 中的 sha-1 算法还是有可能被替换,参见 http://www.itxm.cn/post/14240.html
其他文件:
我们可以看到 .git 目录中的文件会因为几次提交就成倍的增长,因为其中要生成对象
平时我们在工作区修改文件,没有 add 操作之前我们通过 git status 命令可以查看到有文件被 modified 而且是 not staged,这个时候修改的文件仅仅是在工作区内,之后通过 git add 操作,被修改的文件从工作区提交到暂存区(驿站)也就是 stage 里,再通过 commit 操作把暂存区 stage 中所有的内容全部提交到当前的分支结构当中,即
工作区(文件修改后 add 前)→ stage 暂存区(add 后 commit 前)→ 本地分支结构(本地 commit 后)
这样的一种三层结构
分支被合并可以被 -d 删除,分支没有被合并后 -d 删除会出错,需要 -D 强制删除
把一个分支的修改整合到另一个分支的办法有两种,第一种就是 git merge 操作,另一种就是 git rebase 操作,该命令原理就是回到两个分支最近的共同祖先,根据当前分支(也就是要进行衍合的分支experiment)后续的历次提交对象(这里只有一个 C3),生成一系列文件补丁,然后以基底分支(也就是主干分支master)最后一个提交对象(C4)为新的出发点,逐个应用之前准备好的补丁文件,最后会生成一个新的合并提交对象(C3’),从而改写 experiment 的提交历史,使它成为 master 分支的直接下游。如下图所示:
在rebase的过程中,也许会出现冲突。在这种情况,Git会停止rebase并会让你去解决冲突;在解决完冲突后,用git add命令去更新这些内容的索引, 然后,你无需执行git-commit,只要执行git rebase –continue,这样git会继续应用(apply)余下的补丁。如果要舍弃本次衍合,只需要git rebase --abort即可。切记,一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行rebase操作。
git 命令分为 procelain 和 plumbing 命令,前者是基于后者来实现的,若把 git 看成一个操作系统那么 plumbing 命令更像是一个 shell 命令,而 procelain 命令就像是通过利用 shell 命令编写的一系列系统功能或工具,下面会重点讲解 plumbing 命令以及 git 对象
plumbing 命令:
git 本质上一套内容寻址(content-addressable)文件系统,寻址无非就是查找,这样的寻址系统对于完成过 linux 系统构建的 linus 肯定不算难事。寻址无非就是查找,git 采用的是 HashTable 的方式进行查找,即 git 是通过简单键值对形式实现内容寻址,键就是文件(头+内容)的哈希值(前面 .git 目录结构中也讲了采用 sha-1 加密方式,40位,点击这里跳转到前面),值是经过压缩后的文件内容,plumbing 操作实际上是 40 位的哈希值来进行压缩包的查找
git 对象存储方式表示式:
Key = sha1(file_header + file_content)
Value = zlib(file_content)
git 对象:
前面也讲了 git 对象有 BLOB(binary large object),tree,commit 三种,点击这里跳转到前面,BLOB 存储几乎所有的文件类型,BLOB 大的二进制表示的对象,和数据库中 BLOB 类型,常用来存储数据库中图片视频是一样的;tree 是用来组织 BLOB 对象的一种数据类型,你完全可以理解成二叉树中的树结点,只不过 git 中的可是“多叉树”,commit 对象表示每一次的提交操作,是由 tree 对象衍生出来,通过每次提交,这样所有的 commit 操作便可连成一个提交树,一个 branch 实际上就是这个大树中的一个子树
stage 暂存区又叫索引库,因为暂存区信息内容保存在 .git 目录结构的 index 文件夹,git add 就是把工作区 modified 文件添加到 stage 暂存区,那么 git add 底层是如何通过 plumbing 命令完成文件索引操作的?
git add 对应着的两个基本 plumbing 命令
git hash-object #获取指定文件的key,如果带上-w选项,则会将该对象的value进行存储
git update-index #将指定的object加入索引库,需要带上—add选项
先用第一个命令将需要暂存的文件进行 key-value 转化成 git 对象,进行存储(.git 中的 objects 文件夹中),拿到这些文件的 key,然后通过第二条命令将这些对象加入到 stage 暂存区中暂存(.git 中的 index 文件夹中)
若还要根据 git 对象的 key 来查看文件信息,需要如下 plumbing 命令
git cat-file –p/-t key #获取指定key的对象信息,-p打印详细信息,-t打印对象的类型
也就是说 git add 命令本质上就是把工作区修改的文件变成 git 对象并且拿到 sha-1 值放在 objects 文件夹中,然后连同 key 一起转存到另一个 index 文件夹中(暂存区)。实际上 index 库记录是从项目初始化起,每次 add 在这里都会把文件的索引信息(时间戳和大小)更新,也就是说这个库只会越来越大
git 中所有内容以 BLOB 或者 tree 对象形式存储。如果把 git 看做 unix 系统,那么 tree 对象就好似文件系统中的目录,BLOB 对象就好似 inodes 或文件内容。我们平时操作的 add 和 commit 操作似乎没有涉及到这个 tree 对象的生成,其实有的,tree 对象只是 add 和 commit 中间的一个缓冲步骤,因为 commit 对象要根据 tree 对象来创建,下面创建 tree 对象:
git write-tree #根据索引库中的信息创建tree对象
这条命令的作用是返回生成 tree 对象的 key 值
整个工作目录对应一个 tree 对象,并且其下每一个子文件夹都是一个 tree 对象,每次的 commit 对象都对应着根 tree 对象,任何一个对象的改变都会导致其上层所有 tree 对象的重新存储
index 暂存区包括了项目仓库中所有的文件,commit 对象所对应的 tree 对象永远都是工作区根目录所对应的 tree 对象,也就是说每次 commit 之后,commit 对象会依附在这个工作区的 tree 对象上。要是仔细观察目录结构的话,可以发现对象文件夹,子文件夹对应着一个 tree 结点,文件的话对应着一个 BLOB 结点,创建 commit 对象的命令:
git commit-tree key –p key2 #根据tree对象创建commit对象,-p表示前继commit对象
该命令实现的操作类似于数据结构树中增加结点的操作,在这个命令中若是第一次提交则不需要指定 -p 选项指明父节点
下一篇
git 常用命令
git 常见场景操作