目录
Git工作区域
工作区间的关系
Git简介
版本管理
本地执行
文件状态
git内部原理
Git对象
块
目录树
提交
引用
tag
HEAD
分支
.git 结构说明
版本演变
分支
创建分支
切换分支
远程分支
跟踪分支
合并(merge)
快进合并(fast-forward)
合并冲突
变基(rebase)
变基冲突
交互式变基(interactive)
变基 vs. 合并
应用提交(cherry-pick)
储藏(stash)
参考
1、工作目录(Working Directory)
存放项目代码的地方
2、暂存区(Stage/Index)
用于临时存放改动,事实上它只是一个文件,保存即将提交到文件列表信息
3、资源库(Repository或Git Directory)
仓库区(或本地仓库),就是安全存放数据的位置,这里面有提交的所有版本的数据。其中HEAD指向最新放入仓库的版本
4、远程的git仓库(Remote Directory)
远程仓库,托管代码的服务器。
Git与其他版本管理工具最重要的不同点是:其他工具记录下文件的变化情况(delta-based),而git是基于快照(snapshots),每次变更都会是存储文件的快照。
delta-based方式:
快照流方式:
只有被修改过的文件才会在对应版本产生出新的快照。所以每次提交所产生的快照不是整个文件系统,而是被修改过的那部分文件。(上图虚线圈出的文件表示没有生成新的快照)
Git在每个用户机器上都有一份完整的版本库,所以在集中式版本控制系统通常所做的提交代码、比较代码、分支合并等工作在本地就可以完成,而且速度极快。但是不同开发者之间的代码协作,还是需要通过网络连接远程仓库进行交换的。
Git管理的文件存在三个状态:
在git版本库中,git维护两个主要数据结构:对象库(object store),索引(index)。
对象库是git版本库实现的心脏,包含四种类型:
为了有效的利用磁盘空间和网络带宽名,git把对象压缩并存储在打包文件(pack file)里,这些文件也在对象库里。
文件的每一个版本表示为一个块。一个blob被视为一个存储任意数据,且内部结构被程序忽略的变量或文件的黑盒。一个blob保存一个文件的数据,但不包含任何关于这个文件的元数据(Metadata,描述数据的数据)。
一个目录树对象代表一层目录信息。它记录blob标识符、路径名和在一个目录里所有文件的元数据。它也可以递归引用其他目录树或子树对象,从而建立一个包含文件和子目录的完整层次结构。
一个提交对象保存版本库中每一次变化的元数据,每一个提交对象指向一个目录树对象,这个树对象在一张完整的快照中捕获提交时版本库的状态。
从提交流角度来看,提交对象会包含一个指向上次提交对象(父对象)的指针。
引用指的是对提交记录的引用,提交记录用哈希值(SHA-1)唯一标识,每个引用用一个文件表示,文件中保存其引用的提交记录的哈希值,文件名为引用的名称。本地分支名称、远程跟踪分支名称和标签名都是引用。
heads目录下有个名为master的文件,内容为:1defc8eb6a0fb360438c9672cb31bbc2e46d621e
Git 使用两种主要类型的标签:轻量标签(lightweight)与附注标签(annotated)
一个轻量标签很像一个不会改变的分支 - 它只是一个特定提交的引用。
附注标签是存储在 Git 数据库中的一个完整对象。 它们是可以被校验的;其中包含打标签者的名字、电子邮件地址、日期时间;还有一个标签信息;并且可以使用 GNU Privacy Guard (GPG)签名与验证
.git/refs/tags/目录
中的文件特定的提交记录
tag
.git/HEAD 。里面存储的内容: ref: refs/heads/master
本地分支
,即引用的引用某个提交记录
,称为HEAD detached
, 即分离头指针状态tag
,git将这种情况也处理成HEAD detached
远端分支
, git将这种情况也处理成HEAD detached
HEAD
工作区
检出的文件(或者说文件在修改之前)是属于哪个提交记录
的git checkout 指令
,就是在改变HEAD的指向
git checkout 本地分支名
git checkout 提交记录哈希值
, detachedgit checkout 远端分支名
, detachedgit checkout tag名
, detached不同的提交记录
.git/refs/heads/
本地仓库
有多个本地分支
.git/refs/remotes/<远端仓库名>/
本地仓库
可以对应多个远端仓库
, 同时每个远端仓库
可以有多个远端分支
Git仅有一个提交树。而Git 的分支其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 它会在每次的提交操作中自动向前移动。
创建分支只是创建了一个可以移动的新的指针。HEAD指向当前分支。
切换分支就是将HEAD指针指向另一个本地分支。
从一次提交创建不同分支,之后又分别在这些分支上做出了新的提交。这时项目将会产生分叉提交历史,多个提交将指向同一个父提交。这些分叉如果想要最终合并回原来的分支,就要通过合并或变基操作来解决。
远程引用是对远程仓库的引用(指针),包括分支、标签等等。
远程跟踪分支是远程分支状态的引用。 它们是你不能移动的本地引用,当你做任何网络通信操作时,它们会自动移动。 远程跟踪分支像是你上次连接到远程仓库时,那些分支所处状态的书签。
它们以 (remote)/(branch) 形式命名。 例如,如果你想要看你最后一次与远程仓库 origin 通信时 master
分支的状态,你可以查看 origin/master 分支。
从一个远程跟踪分支检出(check out)一个本地分支会自动创建一个叫做 “跟踪分支”(有时候也叫做 “上游分支”)。 跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull
或git push
,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master
的 master
分支。 然而,如果你愿意的话可以设置其他的跟踪分支 - 其他远程仓库上的跟踪分支,或者不跟踪 master
分支。
跟踪分支信息一般保存在local级别的配置文件当中
branch.master.remote=origin
branch.master.merge=refs/heads/master
branch.dev_5.2.remote=origin
branch.dev_5.2.merge=refs/heads/dev_5.2
我们想将出现分叉提交的分支整合在一起时,可以使用合并(merge)操作来完成。
Git 会使用两个分支的末端所指的快照以及这两个分支的工作祖先,做一个简单的三方合并。
和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
Git 会自行决定选取哪一个提交作为最优的共同祖先,并以此作为合并的基础。可以将合并理解为,从分叉提交中提取全部快照整合在一起,最后做一次新的提交。并且新提交拥有多个父提交。
当试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候,只会简单的将指针向前推进(指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
master只要向前推进就可以完成与iss53的合并,所以会使用快进合并。
有时候合并操作不会如此顺利。 如果在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。在合并它们的时候就会产生合并冲突。
此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。等你手动解决之后,Git 会询问刚才的合并是否成功。 如果你回答是,Git 会暂存那些文件以表明冲突已解决。
如果你对结果感到满意,并且确定之前有冲突的的文件都已经暂存了,这时你可以输入 git commit
来完成合并提交。 从而产生一次新的合并提交。
与无冲突合并的区别主要有:
在 Git 中整合来自不同分支的修改主要有两种方法:merge
以及 rebase
。
你可以提取在一个分支中引入的补丁和修改,然后在另一个分支的基础上应用一次。 在 Git 中,这种操作就叫做 变基。 你可以使用变基将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
它的原理是首先找到这两个分支(即当前分支、变基操作的目标基底分支)的最近共同祖先,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件,然后将当前分支指向目标基底, 最后以此将之前另存为临时文件的修改依序应用。
变基也并非完美无缺,要用它得遵守一条准则:
不要对在你的仓库外有副本的分支执行变基。
如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
(也就是说已经推送到远程分支的内容就不要进行变基了,否则会对别人的造成困扰)
我们知道合并时有可能产生冲突,而变基时仍然有可能产生冲突问题。
我们在基底分支和补丁分支修改了同一个文件时就要手动进行冲突处理。
如果在变基时发现冲突,git会停止变基操作要求手动解决冲突。
手动解决冲突后,git会使用手动解决冲突的文件重新建立补丁提交。
交互式变基主要用于将多次提交合并成一次提交。
我们通常会在完成一个功能时,合并杂乱的提交从而使提交树更加简洁。
交互式变基允许我们自由的选择提交,并且重新编辑提交说明。
总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史,从不对已推送至别处的提交执行变基操作。
cherry-pick允许我们提取一个或多个现有的提交,并使用这些提交的快照来创建新的提交。
也就是说我们能够提取某一次提交的变更,应用在其他分支当中。
这个功能在处理生产bug时将会非常有用。如果我们在开发分支正在进行开发时出现了一个生产bug,就需要创建一个bug分支。但是bug分支即要合并到开发分支进行测试,又要合并到生产分支解决问题,显然使用分支合并方式无法完美的解决这个问题。
上面这种情况使用cherry-pick正合适。在bug分支修改完之后,我们可以将修复bug的提交分别cherry-pick到生产和开发分支。由于使用cherry-pick创建的提交标识名都是一致的,在生产上线时执行变基操作并不会产生冲突,会完美的合并成一次提交。
需要注意的是如果我们对cherry-pick的提交进行了交互式变基,那么在合并的时候就无法确认两次提交的关系,会要求我们手动合并。所以如果考虑到将来要将cherry-pick的两个分支进行合并的话,最好还是不要在cherry-pick提交上进行交互式变基操作。
有时,当你在项目的一部分上已经工作一段时间后,所有东西都进入了混乱的状态,而这时你想要切换到另一个分支做一点别的事情。 问题是,你不想仅仅因为过会儿回到这一点而为做了一半的工作创建一次提交。 针对这个问题的答案是 git stash
命令。
储藏会处理工作目录的脏的状态 - 即,修改的跟踪文件与暂存改动 - 然后将未完成的修改保存到一个栈上,而你可以在任何时候重新应用这些改动。
Git 分支 - 变基