一、Git诞生
Linus在1991年创建了开源的Linux,全世界热心的志愿者在世界各地为Linux编写代码。
在2002年以前,世界各地的志愿者把源代码文件通过diff的方式发给Linus,然后由Linus本人通过手工方式合并代码!
不过,到了2002年,Linux系统已经发展了十年了,代码库之大让Linus很难继续通过手工方式管理了,社区的弟兄们也对这种方式表达了强烈不满,于是Linus选择了一个商业的版本控制系统BitKeeper,BitKeeper的东家BitMover公司出于人道主义精神,授权Linux社区免费使用这个版本控制系统。
安定团结的大好局面在2005年就被打破了,原因是Linux社区牛人聚集,不免沾染了一些梁山好汉的江湖习气。开发Samba的Andrew试图破解BitKeeper的协议(这么干的其实也不只他一个),被BitMover公司发现了(监控工作做得不错!),于是BitMover公司怒了,要收回Linux社区的免费使用权。
Linus花了两周时间自己用C写了一个分布式版本控制系统,这就是Git!一个月之内,Linux系统的源码已经由Git管理了!
二、抛弃SVN
集中式版本控制系统,版本库是集中存放在中央服务器的,而干活的时候,用的都是自己的电脑,所以要先从中央服务器取得最新的版本,然后开始干活,干完活了,再把自己的活推送给中央服务器。中央服务器就好比是一个图书馆,你要改一本书,必须先从图书馆借出来,然后回到家自己改,改完了,再放回图书馆。
必须联网才能工作
分布式版本控制系统根本没有“中央服务器”,每个人的电脑上都是一个完整的版本库,这样,你工作的时候,就不需要联网了,因为版本库就在你自己的电脑上。既然每个人电脑上都有一个完整的版本库,那多个人如何协作呢?比方说你在自己电脑上改了文件A,你的同事也在他的电脑上改了文件A,这时,你们俩之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
在实际使用分布式版本控制系统的时候,其实很少在两人之间的电脑上推送版本库的修改,因为可能你们俩不在一个局域网内,两台电脑互相访问不了,也可能今天你的同事病了,他的电脑压根没有开机。因此,分布式版本控制系统通常也有一台充当“中央服务器”的电脑,但这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
当然,Git的优势不单是不必联网这么简单,后面我们还会看到Git极其强大的分支管理,把SVN等远远抛在了后面。
三、Git一些基本概念
1、仓库/版本库/repository
# 选择一个合适的地方,创建一个空目录
$ mkdir learngit
$ cd learngit
$ pwd
/Users/michael/learngit
$ git init
Initialized empty Git repository in /Users/michael/learngit/.git/
如果你使用Windows系统,为了避免遇到各种莫名其妙的问题,请确保目录名(包括父目录)不包含中文。
多了一个.git
的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。
此处暂停一下,我们直接结合 项目搭建 来讲
2、工作区和暂存区
工作区(Working Directory),就是你在电脑里能看到的目录
版本库(Repository),工作区有一个隐藏目录.git
,这个不算工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage(或者叫index)的暂存区,
还有Git为我们自动创建的第一个分支master
,以及指向master
的一个指针叫HEAD
。
下面我们结合命令和IDEA实际体验一下这些概念:
1、git status 查看当前仓库状态
2、新建一个test-git.txt
3、git status
4、git add test-git.txt 将test-git.txt加入暂存区
5、git status
6、git commit -m "add test-git.txt" 提交到本地仓库
7、修改test-git.txt, 加一句 Hello World
8、git status
9、git add test-git.txt 或直接commit
(理论上是不能直接commit的,但是通过IDEA可以,我猜是IDEA是帮我们完成了一次add)
10、git diff 命令,查看修改内容
像这样,你不断对文件进行修改,然后不断提交修改到版本库里,就好比玩RPG游戏时,每通过一关就会自动把游戏状态存盘,如果某一关没过去,你还可以选择读取前一关的状态。有些时候,在打Boss之前,你会手动存盘,以便万一打Boss失败了,可以从最近的地方重新开始。Git也是一样,每当你觉得文件修改到一定程度的时候,就可以“保存一个快照”,这个快照在Git中被称为 commit 。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个 commit 恢复,然后继续工作,而不是把几个月的工作成果全部丢失。
讲完提交,接下来讲一下回退,分几种情况:
1)回退还在工作区的修改
git checkout -- test-git.txt
一种是test-git.txt 修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是test-git.txt 已经添加到暂存区后,作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次 git commit 或 git add 时的状态。
用IDEA的提示操作其实会更快一点
2)回退add到暂存区的修改,在commit之前
git reset HEAD test-git.txt
命令git reset HEAD file可以把暂存区的修改撤销掉(unstage),重新放回工作区,要回退工作区的修改就照第一步
3)回退仓库版本,commit之后的
git reset --hard HEAD^
上一个版本就是HEAD^,上上 个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100
git reset --hard commit_id(版本号)
就可以随意时光穿梭到各个版本了
穿梭前, git log可以查看提交历史,以便确定要回退到哪个版本。
要重返未来, git reflog查看命令历史,以便确定要回到未来的哪个版本。
4)保存工作区并回退
工作只进行到一半,还没法提交,预计完成还需1天时间。但是,必须在两个小时内修复该bug,怎么办?
幸好,Git还提供了一个stash功能,可以把当前工作现场“储藏”起来,等以后恢复现场后继续工作:
git stash
现在,用git status查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。
工作区是干净的,刚才的工作现场存到哪去了?用git stash list命令看看:
恢复工作区,一是用git stash apply恢复,但是恢复后,stash内容并不删除,你需要用git stash drop来删除;
另一种方式是用git stash pop,恢复的同时把stash内容也删了
3、分支管理
在Git里,这个分支叫主分支,即master
分支。
master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点:
每次提交,master
分支都会向前移动一步,这样,随着你不断提交,master
分支的线也越来越长;
当我们创建新的分支,例如dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:
Git创建一个分支很快,因为除了增加一个dev
指针,改改HEAD
的指向,工作区的文件都没有任何变化!
从现在开始,对工作区的修改和提交就是针对dev
分支了,比如新提交一次后,dev
指针往前移动一步,而master
指针不变:
假如我们在dev
上的工作完成了,就可以把dev
合并到master
上。Git怎么合并呢?最简单的方法,就是直接把master
指向dev
的当前提交,就完成了合并:
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev
分支。删除dev
分支就是把dev
指针给删掉,删掉后,我们就剩下了一条master
分支:
总结命令如下:
查看分支:git branch
创建分支:git branch <name>
切换分支:git checkout <name>
创建+切换分支:git checkout -b <name>
合并某分支到当前分支:git merge <name>
删除分支:git branch -d <name>
解决冲突
master
分支和feature1
分支各自都分别有新的提交,变成了这样
存在冲突,必须手动解决冲突后再提交,Git用<<<<<<<
,=======
,>>>>>>>
标记出不同分支的内容
补充说明 reset 的三个参数
soft指还原HEAD的指向
mixed 修改HEAD和暂存区,是reset的默认参数
hard 一步到位,直接修改工作区,比较危险,容易丢失数据
除了reset,还有rebase,revert两种回滚的方法,具体可以自己研究下
4、远程多人协作
添加远程仓库地址
git remote add mine http://gogs.tv.flnetiot.com/nixuebing/FLW-Utils.git
获取远程分支的更新 或者说指针
git fetch
checkout分支 -- 之后就是分支管理