首先我们需要了解一点,Git 保存的数据不是文件的变化或者差异,而是一系列不同时刻的文件快照。
在进行提交操作时,Git会保存一个提交对象,该对象包括一个指向缓存内容快照的指针,作者的姓名、邮箱、提交时输入的信息以及指向他的父对象指针。首次提交的提交对象没有父对象,而有多个分支合并产生的提交对象有多个父对象。
此时Git仓库中有4个对象:两个个 blob 对象(保存着文件快照)、一个树对象(记录着目录结构和 blob 对象索引)以及一个提交对象(包含着指向前述树对象的指针和所有提交信息)
这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 它会在每次的提交操作中自动向前移动。
$ git branch testing
git branch 命令仅仅 创建 一个新分支,并不会自动切换到新分支中去,所以现在虽然创建了一个testing分支,但是我们依然再master分支上。
HEAD指向当前分支,执行代码git checkout testing 后, HEAD就指向 testing 分支了
$ git checkout testing
此时当我们再次修改提交的时候,HEAD 分支随着提交操作自动向前移动,而master分支则不向前移动
$ git checkout master
$ vim test.txt
$ git commit -a -m 'made other changes'
我们再切换到master分支,做些修改之后再提交,此时项目就产生了分叉
可以简单地使用 git log 命令查看分叉历史。 运行 git log –oneline –decorate –graph –all ,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况
由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符)。
在工作中,我们经常在新功能开发时,接到旧功能维护的指令,往往会有些手忙脚乱,使用Git的话,问题将会简单很多。
1、为实现某个新的需求,创建一个分支。
2、在这个分支上开展工作。
正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:
1、切换到你的线上分支
2、为这个紧急任务新建一个分支,并在其中修复它。
3、在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
4、切换回你最初工作的分支上,继续工作。
$ git checkout -b new1
它相当于以下两条命名的简写
$ git branch new1
$ git checkout new1
此时可以在这个分支上修改提交,new1 分支也随着工作进展向前推进
此时接到紧急任务(new2),就需要退回master分支,重新创建一个新分支(new2),当然,退回之前,还需要将工作目录和暂存区里的那些修改先提交(不提交的话,可能会和即将检出的分支产生冲突,从而组织Git切换到该分支),提交之后,你就可以切换回 master 分支上了。
$ git checkout master
这个时候,工作目录里的内容就会变成new1 分支修改之前一摸一样,也就是说,Git会自动添加、修改、删除文件,以确保此时的工作目录和这个分支的最后一次提交时的内容保持一致。
接下来,你就可以开始紧急任务 new2 了,首先建立 new2 分支
$ git checkout -b new2
new2 问题解决完之后,测试确保正确的情况下,提交这些修改,然后可以将其合并到master分支上来部署到线上,这里使用 git merge
$ git checkout master
$ git merge new2
现在,最新的修改已经在master 分支所指向的提交快照,接下来可以着手发布的事情,发布之后,我们应该先删除new2 分支, 人后在回到之前被打断的工作中,删除分支使用 git branch -d
$ git branch -d new2
$ git checkout new1
此时在new1 上的工作,但是在new2 上的工作并没有包含到new1 分支上,你可以等new1 问题解决之后,也合并到master分支上,合并方法和new2 的一样,使用 git merge
$ git checkout master
$ git merge new1
当然,本次合并的工作要比上次和并做更多的工作,Git 会使用两个分支的末端所指的快照以及这两个分支的工作祖先,做一个简单的三方合并,并将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它
如果你对new1 问题的修改和对new2 问题的修改都涉及到同一个文件的同一处,在合并的时候就会产生合并冲突。此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并(unmerged)状态的文件
冲突待解决文件都会以未合并状态标识出来,我们可以打开这些冲突文件,然后手动解决冲突。在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
如果想使用图形化工具来解决冲突,可以运行 git mergetool。
如果对结果感到满意,并且确定之前的冲突文件已经暂存了,这时可以输入 git commit 来完成合并提交,当然你可以修改上述信息,添加一些细节给未来检视这个合并的读者一些帮助,告诉他们你是如何解决合并冲突的,以及理由是什么
我们已经了解了如何创建、合并、删除分支了,接下来俩姐一下分支管理。git branch 命令不只可以创建和删除分支,如果不加任何参数运行,会得到一个当前的所有分支列表
$ git branch
–merged 与 –no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。 如果要查看哪些分支已经合并到当前分支,可以运行 git branch –merged
$ git branch --merged
查看所有包含未合并工作的分支,可以运行 git branch –no-merged,此时如果删除未合并的分支,使用 git branch -d 命令会失败,如果确实想删除的话,可以使用 git branch -D
因为 Git 使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支,也不是什么难事。 也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支,你可以定期地把某些特性分支合并入其他分支中。
特性分支对任何规模的项目都适用。 特性分支是一种短期分支,它被用来实现单一特性或其相关工作。 也许你从来没有在其他的版本控制系统(VCS)上这么做过,因为在那些版本控制系统中创建和合并分支通常很费劲。 然而,在 Git 中一天之内多次创建、使用、合并、删除分支都很常见。
请牢记,当你做这么多操作的时候,这些分支全部都存于本地。 当你新建和合并分支的时候,所有这一切都只发生在你本地的 Git 版本库中 —— 没有与服务器发生交互
远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你可以通过 git ls-remote (remote) 来显式地获得远程引用的完整列表,或者通过 git remote show (remote) 获得远程分支的更多信息。 然而,一个更常见的做法是利用远程跟踪分支。
克隆之后,本地仓库将拉取服务器上的所有数据,并创建一个名为 origin/master的远程 分支和一个名为 master 的本地分支
如果你在本地的master分支上做了一些工作,同时其他人在远程仓库中推送提交并更新了master分支,那么此时就需要同步远程上的数据
$ git fetch origin
这个命令查找 “origin” 是哪一个服务器,从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master 指针指向新的、更新后的位置。
当然我们可能有多个远程仓库,我们可以运行 git fetch anoterone 来抓取远程仓库 anoterone有而本地没有的数据。Git会设置远程跟踪分支 anoterone/master 指向 anoterone的 master 分支
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步 - 你必须显式地推送想要分享的分支
$ git push (remote) (branch):
下一次其他协作者从服务器上抓取数据时,他们会在本地生成一个远程分支 origin/branchName,指向服务器的 branchName 分支的引用
要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话说,这种情况下,不会有一个新的 branchName分支 - 只有一个不可以修改的 origin/branchName指针。
可以运行 git merge origin/serverfix 将这些工作合并到当前所在的分支。 如果想要在自己的 serverfix 分支上工作,可以将其建立在远程跟踪分支之上
当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。 然而,如果你愿意的话可以设置其他的跟踪分支 - 其他远程仓库上的跟踪分支,或者不跟踪 master 分支。 最简单的就是之前看到的例子,运行 git checkout -b [branch] [remotename]/[branch]。
$ git checkout --track origin/serverfix
如果想要将本地分支与远程分支设置为不同名字,你可以轻松地增加一个不同名字的本地分支的上一个命令:
$ git checkout -b sf origin/branchName
当 git fetch 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 然而,有一个命令叫作 git pull 在大多数情况下它的含义是一个 git fetch 紧接着一个 git merge 命令。 如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 clone 或 checkout 命令为你创建的,git pull 都会查找当前分支所跟踪的服务器与分支,从服务器上抓取数据然后尝试合并入那个远程分支。
由于 git pull 的魔法经常令人困惑所以通常单独显式地使用 fetch 与 merge 命令会更好一些。
假设你已经通过远程分支做完所有的工作了 - 也就是说你和你的协作者已经完成了一个特性并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 –delete 选项的 git push 命令来删除一个远程分支。 如果想要从服务器上删除 serverfix 分支,运行下面的命令:
$ git push origin --delete branchName
基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。