一. Git 介绍
Git作为一款分布式的版本控制工具,作为一名程序员,是必须要掌握的.
最初由林纳斯·托瓦兹(Linus Torvalds)创作,于2005年以GPL发布。最初目的是为更好地管理Linux内核开发而设计.后来git内核已经成熟到可以独立地用作版本控制,使的很多著名的软件都开始使用git进行版本控制.
了解更多,可点击
维基百科 - Git 和 廖雪峰的Git博客
Git虽然是文件的版本控制工具,但它所管理的并不是文件,比较贴切的说,它控制的应该是文件的修改.
目前Git有很多桌面应用,可以方便实现各种功能,比如我推荐的GitKraken
,但是如果要更好使用桌面应用,对于下文中的概念好命令还是很有必要的.
1.1 Git和SVN的对比
Git | Svn |
---|---|
分布式的 | 集中式的 |
把内容按元数据(记录改动)方式存储 | 按文件 |
下载后,脱网也可查看全部log | 需要联网需要联网查看log |
没有一个全局的版本号 | 有 |
内容存储使用的是SHA-1哈希算法,内容完整性要优于SVN | 差于Git |
二. Git入门
2.1 基础概念介绍
git共有四个区
- 工作区(Working Area)
- 暂存区(Stage)
- 本地仓库(Local Repository),仅自己可见
- 远程仓库(Remote Repository),全组成员可见
同时还有五种状态
- 未修改(Origin)
- 已修改(Modified)&未追踪(Untracked)
- 已暂存(Staged)
- 已提交(Committed)
- 已推送(Pushed)
对于图上的所有命令,之后都会有详细的介绍,各位不要捉急.
2.2 Git的安装和GitHub的使用
Git的安装就不介绍了,网上很多.
GitHub是通过Git进行版本控制的软件源代码托管服务,并且它免费,在小组开发时,我们可以将代码托管到GitHub上,非常方便.官网https://github.com/
2.3 创建版本库
版本库,说白了就是我们要交给Git管理的文件夹.
$ cd ~/workspace/git/
$ git init
命令行进入到目标目录,然后输入git init
,就会在当前目录下生成一个.git
的文件夹,此时,这个版本库就创建好了.所有在此目录下和子目录下的文件改动,都会被git发现..git
文件夹就是这个工程的本地版本库.
我们也可以直接在GitHub clone代码到本地,方法如下:
$ git clone git@github.com:michaelliao/bootstrap.git
2.4 基本使用
此时在git
目录下,新建文件helloWorld.scala
.然后想继续修改,但是怕最近的修改出错,想可以随时回到当前状态,那要怎么做呢?如果我想让这个开发的文件,在小组内的其他成员也见,又要怎么做呢?
git add => 暂存区
第一步就是先将要保存的文件加入暂存区
:
$ git add helloWorld.scala
注意,
add
可以一次提交多个文件,如git add a.scala b.scala
,也可以多次提交,如git add a.scala
git add b.scala
.
如果使用
git add --all
,提交当前工作区的全部更改到暂存区
如果使用git add .
,提交当前目录下的全部更改到暂存区
git status 状态查看器
使用下面命令,可以查看当前工作区的修改情况:
$ git status
Git非常清楚地告诉我们,helloWorld.scala
还从来没有被添加过,所以它的状态是Untracked
。如果我们已经添加过helloWorld.scala
文件,在对其进行修改,那么它就是modified
状态
git checkout 放弃修改
如果想放弃现在没有add
,可以使用下面命令,将工作区的改动还原
$ git checkout -- helloWorld.scala
注意,这个
--
是必须要有的,不然它就会切换分支了,分支概念后面说.
可是,如果add
之后,又想放弃目前暂存区的更改呢?,就要使用下面的语句,然后重复上面的步骤.
git reset HEAD helloWorld.scala
关于 reset 和 HEAD 的概念,之后会讲到.
git commit => 本地仓库
第二步,将缓存区的内容提交到本地仓库
中:
$ git commit -m "wrote a hello file"
此时,我们已经将本次更改保存到本地仓库,现在就可以放心大胆的继续开发hello了.因为我们已经有了一个现在时间的代码快照,随时可以回到快照.
-m后面的String,是对于本次commit的备注,可以没有,也可以使任何内容,但建议是一段可描述本次提交的语言,方便日后自己追溯,也方便其他人可以了解本次提交,方便他大胆的更新或合并你本次的代码.关于更新和合并下文讲.
现在可以使用git status
查看一下当前什么状态.
git push
但是当前的hello文件只有你自己可见,要想小组的其他人也能看到并且编辑,需要将它同步到远程.方法如下:
$ git push origin master
这样就讲本地仓库中提交的代码push到远程的master分支上了.此时,其他小组成员就可以看到你之前编写的文件并且可以同步和修改了.
git pull
那么其他成员如何下载你的最新代码呢?使用如下命令即可:
$ git pull
如果git pull提示
no tracking information
,则说明本地分支和远程分支的链接关系没有创建,用命令git branch --set-upstream-to <branch-name> origin/<branch-name>
。
三. 版本穿越
如果想要回到之前的某个版本,应该如何做呢?
3.1 git历史书
首先应该查询我之前的所有操作,通过命令:
$ git log
查看最近的操作,但是内容有些多,可以加上参数--pretty=oneline
简化输出信息.
输出信息类似下面:
f078079284c567571286ef7a168f095af9acdd03 (HEAD -> Clock-0704, tag: v1.0, origin/Clock-0704) Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
bbb87262b8d8d61d85fc6579ab46527e431ee176 (origin/alex-0704) 修复无限bug
4f0a047023f8a4902f40f03fb5f6040775a9e1ee Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
第一个空格前面的一串数字,就是那次commit的commit id(提交版本号),和SVN不一样,Git的commit id不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的commit id和我的肯定不一样,以你自己的为准.为什么commit id需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了.
知道了历史书,我们就可以定位具体的穿越位置,使用
$ git reset --hard 4f0a047023f8a4902f40f03fb5f6040775a9e1ee
我们就穿越成功了.
reset 的 --hard参数后面讲解
最后跟的commit id不用写全,git会自动查询,但也不要写的太少,推荐写七位左右
此时有个语法糖, 如果我们只是想返回最近的上一次提交,可以使用
$ git reset --hard HEAD^
返回上上次呢git reset --hard HEAD^^
,上三次呢?
没错,是你想的那样,但是有别的方法git reset --hard HEAD~3
,不然100次不是很累.
使用git log
查询一下历史,已经看不到4f0a之后的提交了.我们穿越成功了,但是git log
没有了之后的commit id,如果我们反悔了,岂不是回不去了.
3.2 git万年历
不要怕,在Git中,总是有后悔药可以吃的,Git提供了一个命令git reflog
用来记录你的每一次命令:
$ git reflog
输出信息类似下面:
f078079 (HEAD -> Clock-0704, tag: v1.0, origin/Clock-0704) HEAD@{0}: commit (merge): Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
4f0a047 HEAD@{1}: commit (merge): Merge remote-tracking branch 'origin/alex-0704' into Clock-0704
89529a2 HEAD@{2}: commit: report over
再次穿越:
$ git reset --hard f078079
没错,我又回来了.
这里要说一下,git切换分支的速度非常快,因为git把你的每次commit编程了链表的一个点.内部有个指向当前版本的
HEAD
指针,当你回退版本的时候,Git仅仅是把HEAD换个指向而已.
3.3 代码回滚的讲解
代码回滚的方式分三种Reset、Checkout、Revert
那么应该如何使用和选择呢?
reset 详解
reset 是移动HEAD指针,如果HEAD指针指向了之前提交的commit id,就等于放弃了近期的代码更改,回到了当时,如上面的例子,但是它比想象的要强大,同时支持几个参数,如下:
- --mixed – 默认选项。缓存区和你指定的提交同步,但工作目录不受影响,也就是缓存区移入工作区,然后 工作区 > 本地仓库
- --soft – 缓存区和工作目录都不会被改变,也就是最近修改优先
- --hard – 缓存区和工作目录都同步到你指定的提交,也就是本地仓库优先
checkout 详解
主要是处理工作区的修改
revert 详解
创建一个修改来修改之前的提交,不会影响历史,是最安全的回滚办法.好像Ctrl + Z
文件层面操作
git reset
和 git checkout
命令也接受文件路径作为参数。这时它的行为就大为不同了。它不会作用于整份提交,参数将它限制于特定文件。
如:
git reset HEAD~2 helloWorld.scala
会把最近的第二次提交中的helloWorld.scala
文件提取出来放到暂存区.
而:
git checkout HEAD~2 helloWorld.scala
会把最近的第二次提交中的helloWorld.scala
文件提取出来放到工作区.
总结
git revert
当做Ctrl + Z
,它不会修改历史记录,并且会生成记录.所以执行之前,要先stash
.git reset HEAD
用来撤销没有提交的更改.git checkout
主要是处理工作区中没有add的修改
命令 | 作用域 | 常用情景 |
---|---|---|
git reset | 提交层面 | 在私有分支上舍弃一些没有提交的更改 |
git reset | 文件层面 | 舍弃缓存区中的更改 |
git checkout | 提交层面 | 切换分支或查看旧版本 |
git checkout | 文件层面 | 舍弃工作目录中的更改 |
git revert | 提交层面 | 在公共分支上回滚更改 |
git revert | 文件层面 | (然而并没有) |
四. 分支概念
如果对于同一个项目,领导要张三去开发新功能的同时,要李四去修复当前版本的一个bug呢?
这时,我们就要做一个比穿越时间更神奇的事情了,就是创建平行宇宙 => 分支.
分支可以将某个时间节点的代码分别放到两个平行线上,这两条线上的开发互不影响,只有在两者需要合并的时候重合即可.
4.1 分支基础
在版本回退里,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
就像这样:
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
git checkout
首先,我们创建dev分支
$ git branch dev
查看现在都有哪些分支:
$ git branch
* master
dev
有*号的表示是当前分支
然后切换到dev分支:
$ git checkout dev
也有语法糖,可以二步合一
$ git checkout -b dev
参数-b就是分支不存在,则创建分支
现在我们在dev分支已经开发完了,要把代码统一回master分支.要先切换回master
合并分支:
$ git checkout master
$ git merge dev
此时,用
$ git log --graph --pretty=oneline --abbrev-commit
可以查看分支合并过程
合并完成,删除之前的dev分支
$ git branch -d dev
五. 番外
5.1 stash 暂存
可以缓存工作区的修改到stash中,那在什么场景使用呢?
比如,你正在开发新模块,但之前分支中有改动,需要合并分支,可是你们项目中有一些配置文件,如数据库配置等你连接到了自己的数据库上,每次合并分支还要重新修改,很麻烦,这时就可以先stash,然后merge,在把stash中的文件修改提取出来.
如何提取:
$ git apply
$ git pop
这两句的功能一样,都是拿出刚才stash的文件修改,区别在于,apply只是提取,不会删除刚才的stash,而pop就像弹栈一样,在提取的同时删除了stash.
可以使用git stash list
查看全部stash列表.使用git stash apply stash@{0}
恢复指定stash
5.2 Rebase 变基
不是很好理解,就当做是最后一次的 merge 吧。
因为rebase 后,两个分支的修改记录都会合并为一个分支的记录,好像另一个分支从来没有出现过一样。
5.3 tag 标签管理
可以理解为commit的收藏功能.
$ git tag v1.0
使用上面命令,就会在最近的commit上创建一个tag,也可以使用 git tag v1.0 commitId
给指定commit打tag
删除标签
$ git tag -d v1.0
从远程删除。删除命令也是push,但是格式如下:
$ git push origin :refs/tags/v0.9
5.4 .gitignore 忽略文件
忽略文件的原则是:
- 忽略操作系统自动生成的文件,比如缩略图等;
- 忽略编译生成的中间文件、可执行文件等,也就是如果一个文件是通过另一个文件自动生成的,那自动生成的文件就没必要放进版本库,比如Java编译产生的.class文件;
- 忽略你自己的带有敏感信息的配置文件,比如存放口令的配置文件。
5.5 设置别名
$ git config --global alias.co checkout
$ git config --global alias.ci commit
$ git config --global alias.br branch