Git的概念和基本使用
概念篇
1. Git简介:
鉴于有些同学可能还不知道Git是什么,我首先对Git做个简短的介绍。Git就是类似于svn的一个版本控制工具,他其实和hg更像一些,hg也是一个分布式版本控制工具,可以说git是当前最流行也是功能最强大的开源版本控制工具。
Git和linux的创始人都是linus,Git也是为了linux代码的托管而开发的。最初Git刚开源那会进行了修改,并且linus通过合并请求后都是靠linus手工进行代码合并,后来随着社区的活跃度和其发展的速度迅猛,linus力不从心了,当时出现就借助了Bitkeeper进行代码托管,当时这个也是收费的(考虑到linux的社区的强大,免费提供给了linux社区),社区中的众多大牛也总是会搞些事情,就开始尝试破解 Bitkeeper,后来也就因为这个原因,Bitkeeper公司终止了linux的托管,linus其实并不看好svn或者是cvs等,后来就利用了两周时间开发了Git,Git也就由此诞生。后来随着github的发展也是一火再火。
Git特点
接下来我们就来说说Git有什么特点和Git能够为我们做什么,他和svn有什么区别。其实我们最熟悉的还是svn,有个中央仓库,大家都上去checkout代码,或者根据自己的需求建立分支,切换分支,但是这其中我们依赖最大的就是网络,如果网络挂了,我们所写的代码就不能提交到中央仓库,也就不能靠svn来管理了,可以说暂时是离线状态,而且在分支切换的时候也是一个耗时的过程。然后我们再说Git,Git呢是一个分布式的版本控制工具,他没有中央仓库的概念,只要我们的pc上面安装了Git,那么我们每个人都可以在自己电脑上创建仓库,并且供其他人员clone。Clone就是对一个仓库的克隆,类似于svn中的checkout,但是呢不同之处在于clone会对仓库的所有信息进行克隆,你可以认为每次clone就相当于把仓库copy了一份,是一个整体,一个完整的仓库。但是我们工作中为了代码的同步方便和代码的集中管理,我们还是需要一个中心仓库,然后大家都上去clone,后续对所有提交都push到这个中心仓库上面。Git除了分布式的一大特点还有一个亮点就是分支管理,和svn的分支管理不同得失,Git采用的是指针的概念,学过c++的可能知道指针,java里面也有程序计数器,其实都是差不多的。他指向的是内存区域而不是具体文件,后面我们会演示到。
主要特点总结如下:
•是一款免费的、开源的、分布式的版本控制系统。
•GIT是分布式的,这是GIT和其它版本控制系统,最核心的区别。
•每一个 Git克隆 都是一个完整的文件库,含有全部历史记录和修订追踪能力
•依赖于网络连接或中心服务器
•版本库本地化,支持离线提交,相对独立不影响协同开发
•其最大特色就是“分支”及“合并”操作非常快速、简便。
2. Git的下载
下载地址:https://www.git-scm.com/downloads ,然后我们进入到官网后点击downloads,这里就出现了不同操作系统的安装文件,如果是linux的我们也可以基于源码去安装。这个地方大家注意一下如果有人喜欢使用windows的doc那么就勾选中间这一项,其实这个工具对linux最初是有依赖性的,其实我觉得不论对linux有没有依赖性大家都应该要学会使用linux的简单的操作命令,而且后面如果想真的理解git的工作流程希望当家后面都能够自己去把Git的命令去敲一遍。
3. 概念
下面我们讲解一下Git的主要概念,在这之前呢我们先来看一下在Git下的工作流程
1、克隆 Git 资源作为工作目录。
2、在克隆的资源上添加或修改文件。
3、如果其他人修改了,你可以更新资源。
4、在提交前查看修改。
5、提交修改。
6、在修改完成后,如果发现错误,可以撤回提交并再次修改并提交
- 版本库
这个就是类似于一个简单的数据库,我们本地仓库创建的.git文件夹就包含了所有的仓库信息,这个信息是包含每次修订和历史信息的全部内容。前面也大概说过了就不再重复。
- 分支
这个概念估计大家都接触过并且使用过svn的分支,就比如每次新需求来了基本上都需要拉一个单独的分支进行开发,待测试后合并到trunk,在这里我们是master,有trunk或者master进行发布上线,当然这里过程中可能还需要打tags,其实git中tag或者后面我们要讲到的bug分支、featuer等都是分支的一种特殊应用而已。
- 标签
这里我们的项目如果达到一个重要阶段,或是里程碑或是每一次小的项目阶段或是我们一个需求的上线可能都要打一个标签。这个标签可以代表我们阶段的稳定性。基本上每次tag都是基于master的最新版本,当然Git也是支持后补Tag的,只要基于commitid就可以打任一阶段的标签。
- 远程仓库
上面我也们也提到过,Git 并不像 SVN 那样有个中心服务器,通常我们所做得各种操作基本上都是在本地执行,如果你想通过 Git 分享你的代码或者与其他开发人员合作。 你就需要将数据放到一台其他开发人员能够连接的服务器上。
实际情况往往是这样,找一台电脑充当服务器的角色,每天24小时开机,其他每个人都从这个“服务器”仓库克隆一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
类似于有名的GitHub或者开源中国(码云)等,当然,千万千万注意不要把公司的代码推到这上面哈,如果被安全组的扫描出来别说我没提醒大家。
- 工作区、暂存区、版本库
工作区、暂存区、版本库这三个概念呢我放在一起给大家讲,我们开发的代码会不停的在这几个区域中存在。我们可以认为工作区其实就是我们的编写程序的地方也就是出了.Git文件夹里得内容的之外的所有文件和文件夹,但让是必须在.Git文件夹存在的父文件夹以内的,我们所有编写的代码都在这里面。然后版本库其实包含了暂存区和分支两部分,暂存区其实就是我们每次提交之前先进行的git add操作之后的一个待提交列表的一个区域,这样说不太好的是这里面不单纯是一个列表,可以认为是一个到提交列表的一个文件区域。分支部分就是咱们版本库最重要的概念,里面存放了我们的所有分支和操作记录,其中的master也是一个分支。从暂存区commit之后就进入到了版本库中。可以通过如下步骤进一步理解。
1、初始化的只有工作区
2、add到暂存区
3、commit到了主分支
- Git权限问题
说了这么多,我们都只到Git是可以代替我们现在的svn的,但是我们的权限怎么办呢,上面一直也没有讲到Git的权限,我这里大概的给大家说下。
Git也有一个很“严重”的问题,那就是没有权限控制功能。Git的创建是作为开源社区的代码版本管理工具而存在的,但当我们把Git引入到团队内部的开发流程中后就会发现没有权限控制的GIT无法保护核心代码的安全,有时候一段错误的代码神不知鬼不觉的出现在底层类库中造成灾难性的后果。幸好还有SSH。由于Git的主流连接方式是SSH连接,因此当我们试图控制一个Git中心库权限的时候,可以通过控制SSH登录用户的权限来间接的达到目的。具体的配置和操作我就不给大家演示了,后面如果有需要或者谁想了解这块可以百度一下或者找我也行哈。
Git的远程仓库连接有两种,其中一种是https(或者http,要求每次和远程仓库的交互都需要输入密码),还有一种是ssh(只要通过ssh-keygen生成秘钥,讲公钥上传到服务端就不需要再次输入密码)
命令操作篇
1. Git配置
当我们安装Git成功后,并且在使用Git之前都需要配置,起码要配置自己的个人信息,有的资料说的是自定义Git,比如设置一些用户信息和命令别名(这里可以理解为快捷键),还有就是定义一些文件忽略提交,这点呢就和我们的svn是一样的,这里可以将忽略文件可以添加.gitignore文件,。
# 配置提交时的⽤户名和邮箱
git config --global user.name <you user name>
git config --global user.email <you email>
## 下⾯这个配置换⾏符的转换, Git默认应该是Linux的换⾏符
git config --global core.autocrlf false
## 拒绝提交含有混合换⾏符的⽂件
git config --global core.safecrlf true
# 配置git命令别名
## 如果你对svn命令⽐较情有独钟,可以做如下设置
git config --global alias.ci “commit -m”
git config --global alias.st "status -s"
git config --global alias.co "checkout"
git config --global alias.br "branch"
git config --global alias.unstage "reset HEAD --"
## 下⾯的设置让git的显示更加⽅便
git config --global alias.gitlog "log --graph --oneline --color"
##加⼊配置错了想删除,⽐如执⾏了下⾯的命令
git config --global user.ci "commit -m"
git config --global --unset user.ci
# 配置⼀些全局忽略命令
git config --global core.excludesfile '~/.gitignore'
cd ~
vim .gitignore
## 然后将下⾯的内容复制到.gitignore⽂件中
.DS_Store
*.class
*.idea
*.iml
*.project
*.settings
2. 根据场景进行操作命令
比如我们进入到一个新的项目,或者说由于某个原因要重建一个项目和仓库,比如我现在为了给大家演示,这个需求我就创建了一个demo工程名字叫mygit,里面具体使用什么来构建还有内容我们先不关心,这时候我们首要初始化仓库,并且将这个仓库和远程的仓库建立关联
这里我们看到我这个工程,大家和熟悉的结构,首先我们先创建仓库,我们在这个工程的目录上右键,其中可以看到git gui和git bash两个菜单,由于我们这里先说的是命令,我们就用选择git bash
执行:
git init
我们看到返回信息,我们看到这里初始换了一个空仓库,并且声称了一个.git文件夹,可能有些人知道linux中以点开头的文件夹都是隐藏文件,我们看windows界面中也是看不到这个文件夹的,也是隐藏文件。我这里用命令来查看下
执行:
ls -al
其实这里的.git文件夹就是整个的本地仓库,我们用git status查看下当前仓库的状态
执行:
git status
我们看到这里当前分支是是master分支,并且存在未标记的文件,src没有在这里显示,因为这里是个空文件夹,这里我们看到其实有些文件我们是不希望仓库帮我们管理的比如这里的.idea,mygit.iml,这就回到上面我们说的那个gitignore文件,我们只要在仓库的根目录下创建一个.gitignore文件夹,并且将我们这些忽略文件添加进去就行了
执行:
vi .gitignore
我们在来查看git status
此刻我们就应该将我们的项目提交到仓库并且推送到远程仓库,然后和其他同事一同协作开发,我们执行git add file先将要提交的文件添加到暂存区,暂存区上面咱们讲过,这里就不多说,如果不明白的同学就先理解为待提交列表就行。
执行:
git add .gitignore(这里是提交了单个文件,我们也可以利用下面的命令来提交全部文件)
git add . (这里也可以使用pdf中提到的其他参数来提交)
完事儿之后我们就可以将暂存区的文件提交到我们本地仓库来管理了
执行:
git commit -m "初始化项目,此项目用于讲解git常用命令"
此刻我们在用git status来查看,我们的工作去和暂存区已经很干净了,我们可以使用命令 git log 来查看我们的提价记录,我们可以看到详细的提交信息,这里的commitid代表这提交的唯一id,这个是一个sha1的64为,而不是想svn一样的递增的版本号,这个据说是全球唯一的。是否属实咱们这里不做深入探究。这里还有其他的比如作者的信息,提交日期,备注等。
我们还需要推送到远程仓库,我这里创建了一个远程仓库,TestProj,首先我们需要建立管理:
执行:
git remote add origin http://git.test.com/platform/channel/TestProj.git
然后我们将我们的mast分支推送到远程仓库:
执行:
git push -u origin master
推送成功,我们登录远程仓库看下,我们可以用同样的房方法将我们的brh1推送到远程仓库,因为之前创建的分支都是在本地仓库上面的。
由于远程库是空的,我们第一次推送master分支时,加上了-u参数,Git不但会把本地的master分支内容推送的远程新的master分支,还会把本地的master分支和远程的master分支关联起来,在以后的推送或者拉取时就可以简化命令。
一般情况我们的系统开发都是多个需求交替进行,甚至由于一些紧急需求的插入,不得不暂停手头的工作,先去开发其紧急需求,这里和svn的开发方式一样的地方就是我们需要创建分支,这里创建分支也很简单。
执行:
git branch brh1
这里我们可以执行一下git branch或者Git status来查看下当前分支
然后我们切换到新的brh1分支
执行:
git checkout brh1
然后我们再来执行上面命令来查看一下当前的状态,已经是brh1分支了,其实我们这里可能当时创建了分支立马就要切换到新的分支上,我们这里还可以用git checkout -b brh2,这里我就不做演示了,浪费大家时间。
此刻我们先接到的是需求1,正在开发,比如我们新创建了一个文件,写入内容是“你好,我是需求1,我不紧急”。
执行:
echo "你好,我是需求1,我不紧急哦" > test.txt
然后我继续开发,后面继续添加了一些新的功能操作,添加了:“感觉敲代码,挺无聊的,我们出去透个气吧”
执行:
echo "感觉敲代码,挺无聊的,我们出去透个气吧" >> test.txt
此刻我们接到了新需求,需要暂存当前的工作内容,其实此刻可能会有个坑,有人可能将代码就直接commit了,其实此刻你提交的不是最新内容,比如说我们后面添加的这一句就没有被提交,我们可以去远程仓库看一下,因为每次commit都只会commit已经添加到暂存区的文件,我们也可以直接执行git commit -a -m ""不过这个是官方不推荐的,建议每次都添加到暂存区之后再commit。
好回到上面的话题我们收到了新的紧急需求,就需要暂存当前的工作状态,那我们来操作
执行:
git add test.txt(将当前状态加入到暂存区)
git stash save "保存我的需求1"
然后我们创建分支开发新的需求,看情况我们一般都是先切会master然后创建分支
执行:
git checkout -b brh2
echo "我是紧急需求,请先开发我" > test2.txt
git add test2.txt
git commit "开发了一个紧急需求"
此时我们开发完这个需求后可能需要紧急上线,此刻我们既要将分支合并到master上,我们上线也都是以master分支为主,一般情况我们都是先切回到master分支,然后进行merge
执行:
git checkout master
git merge brh2
此刻我们就可以删除分支了brh2了(git branch -d brh2),我们还要删除远程分支(git push origin --delete brh2)
合并之后我们就可以打tag进行测试和上线了,打tag也很容易:
执行:
git tag -a v0.1 -m "紧急需求开发完成,并且通过测试,我要上线了,祈祷。。。"
此刻我们只是在本地创建了tag,我们还需要将此次的tag推送到远程仓库
执行:
git push origin v0.1
完事儿之后我就要切回原来的分支继续开发需求1了
git checkout brh1
git stash pop
echo "胡汉三又回来了" >test.txt
git add .
git commit等
至此我们所做的工作都是一个人在玩儿,如果别人想拿到你的代码怎么办呢,其实很简单,首先我们要clone一个仓库。
执行:
git clone http://git.test.com/platform/channel/TestProj.git
我们通过git log可以考到前面我用我账号的所有提交记录,这也是在告诉大家,每次clone的都是一个完整的仓库,而不只是源文件。同时如果我改了一些文件,其他人怎么拿到呢,这个也很简单,只要pull就可以了。
执行:
git pull
存在多人开发,就难免会有代码冲突的情况,比如说俩人同时改了一个文件的某些部位,我们用之前工程中的HelloWorld.java来测试。
我都在之前加一行代码,然后分别提交并推送远程仓库,发现第二个提交的报错了,他要我们先pull一下代码,然后我执行pull,提示我们自动合并失败了,然后我重新编辑这个代码,并且commit,最终推送远程仓库。
从版本库中回退:
下面我们说下版本回退,也有的资料上叫他时光穿梭,其实就是给我们一个卖后悔药的地方,其实这也是所有版本控制的核心功能之一。这块可能也比较难懂,因为它涉及到版本库的回退和暂存区的回退,这块我尽量讲清楚,大家也仔细听这块。
我们先看一下我之前的提交记录:
执行:
git log --pretty=oneline --graph
git log --pretty=oneline --abbrev-commit
这里我们看到每次提交的记录,这里HEAD指向的是当前最新版本,如果我们想回到上一个版本只要执行,git reset --hard HEAD^,两个版本就在HEAD^^,当然我们这里不可能每次都通过^^来表示,如果我没想回到任意一个版本其实就可以将HEAD换成commitid就可以了。
执行:
git reset --hard 9d7a7c0(HEAD)
当然如果我们回到这个版本之后又想回到哪个最新版本怎么办呢,其实也很简单,只要找到最新的那个commitid就可以,但是同时又有个问题,如果说刚才我这个窗口关闭了怎么办呢,也有办法,在git里面你想怎么玩都行,前提条件是你要对git很熟悉,这里我们可以使用git reflog
当然也可以针对某个文件来进行恢复,比如:git log --pretty=oneline --abbrev-commit test.txt
从暂存区中回退:
如果我修改了某个文件,但是没有提交到暂存区,我要向回退那么可以使用
执行:
echo "这是版本回退的一个例子" >> test.txt
git checkout test.txt
如果已经git add到了暂存区,那么我们先要撤销暂存区,git reset test.txt HEAD,然后在执行git checkout test.txt
执行:
git reset test.txt HEAD
git checkout test.txt
删除某个不用的文件也是我们工作中常用的功能,我们本地删除文件 rm -rf 然后提交就行了,如果在早起版本的git删除了还需要在执行git rm file
比较文件
我们在将diff这个命令,这个在工作中也是使用频繁的,比如我们修改了test.txt。
执行:
git diff test.txt
git diff 11923a3 test.txt(制定到某个版本)
补丁
后面还有一个补丁的概念,可能用的不是很多,但是我觉得人家既然有这个功能就是有价值的,为了保证这个培训的完整,我还是决定讲下这个,应用场景呢,比如说你在家里开发,同事vpn连不上,但是呢某个功能是你开发,还必须要你修改,你就可以修改完成发个patch过来,在网速很慢的情况这种方式也很好,然后你同事执行你的patch就可以了,我们将当前的所有操作都提交一下,用其它同事账号也拉一下,保证两个环境现在是一样的哈,我这里就不用分支了,但是在实际开发过程中都用分支去工作。
比如现在我们修改了test.txt中的某个bug:
执行:
echo "我是来修改bug的" >> test.txt
然后我们可以有两种方式生成patch,首先我们先使用diff
执行:
git diff test.txt > patch.diff
然后来到其它同事这段,执行:
git apply /d/work/project1/mygit1/patch.diff
然后commit 和push之后我们来仓库看一下
还有一种就是用git format-patch生成git的专用补丁
执行:
echo "我是第二次修复bug的补丁包" >> test.txt
git commit -a -m "修复了第二个补丁"
git format-patch -M master
然后在其他同学这里执行上面的那个回复命令就可以了
3. 可能会遇到的问题
1、每次都只会commit已经add的问题件
2、还有一个比较坑的地方就是大家要注意fetch和pull的区别,建议如果在啦代码的时候没有特殊情况就使用pull
3、还有一个比较坑的地方,比如说我们创建了分支,并且在远程仓库也存在分支brh01,如果删除了本地分支,执行推送有可能就把远程分支也删除掉,就找不到了