Git 分支
几乎所有到版本系统都以某种形式支持分支、使用分支意味着你可以把你的工作从开发主线上分离出来、一面影响开发主线。在很多版本控制系统中、这是一个略微低效的过程、通常需要完成创建一个源代码的副本。对于大项目来说、这样的过程会耗费很多时间。
Git 处理分支的方式可谓是难以置信的轻量、创建分支这一操作几乎能在瞬间完成、并且在不同分支之间的切换也是一样便捷。与其他版本控制系统不同、Git 鼓励在工作流中频繁地使用分支与合并。
Git 保持的不是文件的变化或者差异、而是一系列不同时刻的快照。在进行提交操作时、Git 会保存一个提交对象。
在进行提交操作时、Git 会保存一个提交对象。知道了Git 保存数据的方式、我们可以自然的想到--该提交对象会包含一个只想暂存内容快照的指针,还包含作者的姓名和邮箱、提交输入的信息以及指向它的父对象的指针。首次提交对象没有父对象、普通提交操作产生的提交对象有一个父对象、而由多个分支合并产生的提交对象有多个父对象。
我们假设现在的工作目录中有三个要被暂存和提交的文件。暂存操作会为每一个文件计算校验和、然后会把当前版本的文件快照保存到暂存区中
git add README test.rb LICENSE git commit -m 'initial commit of my project' 复制代码
进行提交时、Git 会先计算每个子目录的检验和、然后在 Git 仓库中将这些检验和保存为树对象。随后 Git 会创建一个提交对象、它除了包含上面提到的信息外、还包含了指向这个树对象的指针、如此以来、Git 就可以在需要的时候重现此次保存的快照。
现在 Git仓库中包含五个对象、三个blob 对象(保存着文件快照)、一个树对象(记录着目录结构和blob对象索引)以及一个提交对象(包含指向树对象的指针以及所有提交信息)
做些修改后再次提交、那么这次产生的提交对象会包含一个指向上次提交对象的指针
Git 的分支、其实本质上仅仅是一个指向提交对象的可变指针。Git 的默认分支名字是 master。在多次提交操作之后、你其实已经有一个指向最后那个提交对象的 master 分支。master 分支会在每次提交时自动向前移动。
分支创建
git branch testing 复制代码
Git 创建分支只是为你创建了一个可移动的新指针、它会在你当前所在的提交对象上创建一个指针
那么 Git 是如何知道当前在哪个分支上的、它使用一个名为 HEAD 的特殊指针。在 Git 中、它是一个指针、指向当前所在的本地分支。Git Branch 命令仅仅创建一个新分支、并不会自动切换到新分支中去
可以使用 git log --decorate 查看各个分支当前所指的对象
分支切换
切换到一个已存在的分支、可以使用 git checkout 命令
git checkout other-branch 复制代码
这样 HEAD 就指向了 other-branch 分支了
如果我们这个时候修改某个文件、再次 commit
testing 分支向前移动了、但是 master 分支却没有、它仍然指向运行 git checkout 时所指的对象。
然后我们现在切换回 master 分支
git checkout master 复制代码
这个命令做了两件事、第一件事就是使 HEAD 指回 master 分支、而是将工作目录恢复成 master 分支所指向的快照内容。也就是说、你现在所做修改的话、项目将回到一个较旧的版本。本质上来说、这就是忽略 testing 粉做所做的修改。
这个时候我们在 master 分支上进行一次 commit
分支的新建与合并
新建分支
git checkout -b iss53 Switched to a new branch "iss53" 复制代码
$ vim index.html $ git commit -a -m 'added a new footer [issue 53]' 复制代码
线上出现问题、需要紧急修复
git checkout master git checkout -b hotfix ............ git commit -m 'hotfix information' 复制代码
将hotfix 合并到master 分支然后紧急部署上线
$git checkout master $git merge hotfix Updating f42c576..3a0874c Fast-forward index.html | 2 ++ 1 file changed, 2 insertions(+) 复制代码
在合并到时候、可以看到 “fast forward” 快进 这个词。由于想要合并到分支 hotfix 所指向的提交 C4 是 C2 的直接后继、因此 Git 会直接将指针向前移动。换句话说、当你试图合并两个分支时、如果顺着一个分支走下去能够到达另一个分支、那么 Git 合并它时、只会简单讲指针向前推进。这种情况下的合并没有需要解决的分歧、叫做快进。
部署紧急修复到生产、然后就可以删除 hotfix 分支了、然后切换到 iss53 中继续工作
git branch -d hotfix git checkout iss53 ....... git commit -m 'iss53 some commit information' 复制代码
现在打算将 iss53 合并到 master 分支中、
git checkout master git merge iss53 复制代码
这和之前合并 hotfix 分支的时候不一样、在这种情况下、iss53的开发历史从一个更早的地方开叉出来、因为master 分支所在提交并不是 iss53 所在提交的直接祖先、Git 不得不做一些额外工作。
Git 使用两个分支的末端所指的快照(C4 和 C5) 以及这两个分支的公共祖先(C2)、做一个简单的三方合并。
和之前分支指针向前推荐所不同的是、Git将此次三方合并的结果做了一个新的快照并自动创建一个新的提交指向它、这个被称作是合并提交、它的特别之处在于它不止有一个父提交。
遇到冲突时的分支合并
有时候合并并不会那么顺利。如果你在两个不同的分支中、对同一文件的同一个部分进行了不同的修改、Git 就无法干净的将它们合并。Git 会暂停下来、等待你解决合并产生的重提、在合并冲突的任意时刻使用 git status 命令来查看那些因合并冲突而处于未合并状态的文件。出现冲突的文件会包含一些特殊的区段、看上去如下
这表示 HEAD 所指示的版本在这个区段的上半部分、而 iss53 分支所指示的版本在 ======= 的下半部分。为了解决冲突、你必须选择使用 ======= 分割的两部分中的一个、或者你可以自行合并这些内容。
<div id="footer"> please contact us at email.support@github.com </div> 复制代码
上述的冲突解决方案保留了其中一个分支的修改、并将<<<<<< 和 =======和>>>>>>这些删除掉。
在你解决了所有文件里的冲突之后、对每个文件使用 git add 命令来将其表尾冲突已解决。一旦暂存这些原本有冲突的文件、Git 就会将它们标为冲突已解决。
分支管理
git branch 命令不仅可以创建和删除分支。如果不加任何参数运行它、则会得到当前所有分支的一个列表。
--merged 与 --no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。
拉取
当 git fetch 命令从服务器上抓取本地没有到数据时、它并不会修改工作目录中的内容。它只会获取数据然后让你合并。然后有一个命令叫做 git pull 在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge命令。git pull 会查找当前所在分支所跟踪的服务器的分支、然后从服务器抓取数据然后尝试合并入那个远程分支。
删除远程分支
git push origin --delete serverBranchName 复制代码
rebase
在 Git 中整合来自不同分支的修改主要有两种方法:merge 和 rebase。
如果使用 merge 进行整合分支
如果使用rebase 则是提取C4中引入的补丁和修改、然后在 C3 的基础上应用一次。你可以使用rebase 命令将某个分支的所有修改都迁移到另一分支上。
在这个例子中、可以将 experiment分支 rebase 到 master 分支上
git checkout experiment git rebase master 复制代码
它的原理是首先找到这两个分支的最近共同祖先C2、然后对比当前分支相对于祖先的历次提交、提取相应的修改保存为临时文件、然后将当前分支指向目标基底C3、最后将之前的临时文件的修改依次应用。
最后将 master merge 到 experiment
要用 rebase 需要遵循这么一条准则
如果提交存在于你的仓库之外、而别人可能基于这些提交进行开发、那么不要进行rebase
rebase 操作的实际是丢弃一些现有的提交、然后相应地新建一些内容一样但实际上不同的提交。
总的原则就是、只对尚未推送或分享给别人的本地修改执行 rebase 操作清理历史、从不对已推送到别处的提交执行rebase操作。