为什么使用git
这个问题的答案就在于,集中式版本控制和分布式版本控制的的区别。
那么怎样算得上集中式版本控制呢?
比如说SVN就是集中式版本控制系统,版本库是集中放在中央服务器的,而干活的时候,用的都是自己的电脑,所以首先要从中央服务器哪里得到最新的版本,然后干活,干完后,需要把自己做完的活推送到中央服务器。集中式版本控制系统是必须联网才能工作,如果在局域网还可以,带宽够大,速度够快,如果在互联网下,如果网速慢的话,就郁闷了。
集中式版本控制存在如下缺陷: 依赖网络性强,由于只使用一个中央服务器,所以会有单点问题。
以上说了集中式版本控制,那么分布式版本控制工具又是怎么样的呢?
分布式版本控制的代表就是git
Git 是分布式版本控制系统,每个人的电脑就是一个完整的版本库,这样,工作的时候就不需要联网了,因为版本都是在自己的电脑上。既然每个人的电脑都有一个完整的版本库,那多个人如何协作呢?比如说自己在电脑上改了文件A,其他人也在电脑上改了文件A,这时,如果是自己先push上传,别想要push时远程库就会阻止并提示他更新最新的远程版本库中的内容后再push。下图就是分布式版本控制工具管理方式:
分布式版本控制:解决了集中式版本控制的单点问题,和网络依赖性问题,适用于互联网多地区协同开发,但是在只是在局域网下开发,个人认为还是使用svn会更方便,git虽然性能优越但是使用上没有SVN简便。
git 和svn等系统的存储差异
从概念上来说,其他大部分系统以文件变更列表的方式存储信息。这类系统将它们存储的信息看作是一组基本文件和每个文件随时间逐步积累的差异。(被称作基于差异的版本控制)
如下图:
Figure . 存储每个文件与初始版本的差异.
git对待数据的方式与上图不同,而git是把每个文件看做一个小的文件系统然后为其生成快照,从而管理一系列快照。在git中,每当你提交更新或保存项目的状态时,它基本上对待当前项目的文件创建一份快照,并保存这个快照指向的索引。为了效率,如果文件没有修改,git不再更新存储该文件,而是只保留一个链接指向之前存储的文件。git对待数据更像是一个快照流
Figure.存储项目随时间改变的快照.
git简介
同生活中的许多伟大事件一样,Git 诞生于一个极富纷争大举创新的年代。Linux 内核
开源项目有着为数众广的参与者。绝大多数的 Linux 内核维护工作都花在了提交补丁和保
存归档的繁琐事务上(1991-2002 年间)。到 2002 年,整个项目组开始启用分布式版本
控制系统 BitKeeper 来管理和维护代码。
到 2005 年的时候,开发 BitKeeper 的商业公司同 Linux 内核开源社区的合作关系结
束,他们收回了免费使用 BitKeeper 的权力。这就迫使 Linux 开源社区(特别是 Linux 的
缔造者 Linus Torvalds )不得不吸取教训,只有开发一套属于自己的版本控制系统才不至于
重蹈覆辙。他们对新的系统订了若干目标:
• 速度
• 简单的设计
• 对非线性开发模式的强力支持(允许上千个并行开发的分支)
• 完全分布式
• 有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
工作流程
一般工作流程如下:
1.从远程仓库中克隆 Git 资源作为本地仓库。
2.从本地仓库中 checkout 代码然后进行代码修改
3.在提交前先将代码提交到暂存区。
4.提交修改。提交到本地仓库。本地仓库中保存修改的各个历史版本。
5.在修改完成后,需要和团队成员共享代码时,可以将代码 push 到远程仓库。
下图展示了 Git 的工作流程:
git 的工作目录介绍
其中 ./git 文件夹下就是git的本地仓库
git的使用
创建仓库
1 | git init 要初始的文件夹路径 |
添加文件到暂存区
1 | git add 要加入的文件 |
将暂存区文件添加到本地仓库,并将本地仓库推送到远程仓库中
将缓存区域文件添加到本地仓库1
git commit
将本地仓库推送到远程仓库中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18spiderbao@spiderbao-CW65S:~/git_study$ git push ssh://git@39.xxx.171.29/home/git/git_study
fatal: 当前分支 master 没有对应的上游分支。
为推送当前分支并建立与远程上游的跟踪,使用
git push --set-upstream ssh://git@39.XXX.171.29/home/git/git_study master
spiderbao@spiderbao-CW65S:~/git_study$ git push --set-upstream ssh://git@39.XXX.171.29/home/git/git_study master
git@39.XXX.171.29's password:
Permission denied, please try again.
git@39.XXX.171.29's password:
对象计数中: 6, 完成.
Delta compression using up to 4 threads.
压缩对象中: 100% (3/3), 完成.
写入对象中: 100% (6/6), 523 bytes | 523.00 KiB/s, 完成.
Total 6 (delta 0), reused 0 (delta 0)
To ssh://39.XXX.171.29/home/git/git_study
* [new branch] master -> master
分支 'master' 设置为跟踪来自 'ssh://git@39.XXX.171.29/home/git/git_study' 的远程分支 'master'。
查看修改历史
1 | spiderbao@spiderbao-CW65S:~/git_study$ git log |
还原修改
reset 还原,还原后历史版本不存在1
2spiderbao@spiderbao-CW65S:~/git_study$ git reset --hard 1910c01353a8f12304e2a923164f69e33925a03a
HEAD 现在位于 1910c01 first commit
删除文件
1 | spiderbao@spiderbao-CW65S:~/git_study$ git rm test |
忽略文件或文件夹
在文件目录下创建gitignore 文件1
2touch .gitignore
vim .gitignore
忽略语法
1 | 空行或是以 # 开头的行即注释行将被忽略。 |
解决文件冲突
编辑有冲突后的文件,再次提交即可
文件更新或拉取
1 | spiderbao@spiderbao-CW65S:~/cloneToGit$ git clone ssh://git@39.xxx.xxx.29/home/git/git_study |
git分支管理
为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照
在进行提交操作时,git会保存一个提交对象(commit object)。该提交对象会包含一个指向暂存快照的指针。但不仅仅是这样,该提交对象还包含了作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象
案例:
我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交
1 | $ git add README test.rb LICENSE |
当使用 git commit
进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个 树 对象 (记录着目录结构和 blob 对象索引)以及一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。
Figure 首次提交对象及其树结构
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
Figure 提交对象及其父对象
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master
。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master
分支。 master
分支会在每次提交时自动向前移动。
Note: Git 的 master
分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为 git init
命令默认创建它,并且大多数人都懒得去改动它。
创建分支
1 | git branch 新分支名称 |
选择分支
1 | git checkout 分支名称 |
合并分支
1 | spiderbao@spiderbao-CW65S:~/git_study$ git merge mybatis |
git 贮藏(stash)
个人认为此功能用来间当前缓存区中的信息暂时贮藏,便于切换别的分支去处理事情。等处理完后再切换到当前分支并恢复贮藏信息并继续当前分支的开发。
使用场景:
由于分支的切换需要干净的暂存区(stage,也是就是这个区域内不能有信息)。所以导致想要切换版本就必须面临提交当前工作区信息到版本库中或者不提交。而这两者都具有弊端,前者能保存当前工作区中的内容,但是会对提交历史产生影响。后者虽然不会对仓库的历史产生影响,但是当前工作区中的信息也不会保存。使用git的贮藏功能可以兼备以上所期望的2点。实现过程如下案例
场景:
当前项目的分支情况如下:
1 | * commit 394e7afc1077df0bf4d399c8d6d1e6821eb7f49d (HEAD -> experiment) |
当用户在experiment开发时,接到了临时的紧急通知需要修改master分支上的几处BUG。
1.使用git status 查看当前项目的情况,将要保存的信息放入暂存区(stage)处
1 | 位于分支 experiment |
1 | $ git add TODO |
1 | 位于分支 experiment |
将暂存区中的内容通过stash 放入特定的一个栈中
1
2$ git stash
保存工作目录和索引状态 WIP on experiment: 394e7af test
切换分支工作,解决BUG并提交
1
2
3$ git checkout master
切换到分支 'master'
......
切换分支并恢复贮藏信息
1
2
3
4
5
6
7
8
9
10$ git checkout experiment
切换到分支 'experiment'
$ git stash apply stash@{0}
位于分支 experiment
要提交的变更:
(使用 "git reset HEAD <文件>..." 以取消暂存)
新文件: TODO
新文件: index.html
新文件: lib/simplegit.rb
清理贮藏区中的信息
1
2
3
4$ git stash list
stash@{0}: WIP on experiment: 394e7af test
$ git stash drop stash@{0}
丢弃了 stash@{0} (b1cb57a6cb76585027a27eed6779bcab6ca4f400)