前言
在工作中,基本上都是使用Git进行程序版本管理,并且进行多人协同合作开发。但是往往我们并不知道为什么要使用Git,对于那些繁多的git命令也不求甚解,经常一段时间不使用就忘记了,即使记得心里还是虚虚的,担心出错造成代码库崩盘。这一切的根本原因还是在于我们对于git的理解不清晰,虽然可以使用Git版本管理工具,但是在掌握命令行的前提下使用,会更加的得心应手。
相较于先前的集中式存储SVN,Git采用的是分布式存储,带来的好处就是程序的所有版本都存储在每一台分布式的机器上,并且可以在断网情况下继续编写程序。
Git时间线与多人协作
Git将每次提交都串成一条时间线,这条时间线就是一个分支。
Git 作为一个源码管理系统,不可避免涉及到多人协作。协作必须有一个规范的工作流程,让大家有效地合作,使得项目井井有条地发展下去。"工作流程"在英语里,叫做"workflow"或者"flow",原意是水流,比喻项目像水流那样,顺畅、自然地向前流动,不会发生冲击、对撞、甚至漩涡。
Git项目库的理解与初始化
Git项目库怎么理解?其实就是被Git进行管理的文件夹。首先我们要明确的是Git是一个程序版本管理工具,程序的具体实例就是一个文件夹,所以说Git可以进行文件夹内容改动的管理。那么问题就来了,不是什么文件夹都可以被Git管理,我们需要先告诉Git管理哪些文件夹,这个「告诉」的操作就叫做Git项目库初始化,具体操作就是git init
。
-
git init
当你在本地新建一个git项目,或者你想把某个文件夹进行Git管理,首先你需要初始化该项目,初始化命令就是git init
。完成git项目初始化之后,文件夹内部会多出一个隐藏文件.git(直接打开看不见)。这就是项目的版本库,记载着项目每次提交等信息。
Git项目库的组成
Git项目库是由两部分组成的:工作区和版本库。
如上图所示,git项目库主要分为
工作区
和版本库
两个部分,工作区
指的就是我们原始的文件夹,而版本库
是指git init
初始化文件夹后新增的.git
文件。注意,.git
文件对于我们是隐藏的。Git的版本库里存了很多东西,其中最重要的就是称为
stage
(或者叫index
)的暂存区,和本地分支
(其中Git为我们自动创建第一个分支master
),以及指向本地当前分支的一个指针叫HEAD
。这里我们要有一个概念,那就是git在本地项目库中维护了和远程库一样的代码分支,
git commit
命令事实上是将代码提交到本地维护的代码分支上面,然后接下来在将本地分支同步到远程库的分支上面。版本库.git
文件夹内容快照
工作区与暂存区
我们在提交代码时候,经常先使用git add <filename>
将工作区改动的文件添加到暂存区(stage/index),然后使用git commit -m <message>
将暂存区的所有改动提交到本地分支,最后再git push origin -u <远程库分支名>
将本地分支推送到远程库。
从上面的三个步骤中,可以看到出现了一些新的概念:暂存区(stage/index)、工作区、本地分支,下面就来梳理一下这三个概念。
-
工作区(Working Directory)
就是你在电脑里能看到的目录,也是你通过IDE直接进行编程的目录,比如我的test文件夹就是一个工作区:
注意上图中,存在一个隐藏文件夹.git
,这个不算工作区,而是Git的版本库。 -
版本库(Repository)
如上面所说,工作区的隐藏文件夹.git
不算是工作区,而是Git的版本库。
Git的版本库里存了很多东西,其中最重要的就是称为stage
(或者叫index
)的暂存区,和本地分支
(其中Git为我们自动创建第一个分支master
),以及指向本地当前分支的一个指针叫HEAD
。
git常用命令汇总
添加远程库
git remote add origin <远程库地址>
当你创建好了一个本地项目之后,你希望将其与远程建立的库连接,从而在每次代码写完后,能够提交到代码托管网站,保证了安全性与结构清晰。其中的origin
指的是远程库的名字,一般都默认为origin
,也可以修改。但不建议,因为写代码是与人交流合作的工作,不要为了张扬个性让别人很难阅读与理解。
提交代码
- 提交到暂存区
git add .
或者git add <文件名>
- 提交到本地分支
git commit -m '<提交说明>'
- 推送到远程库
git push -u origin <分支名>
以上就是提交代码的三板斧,再具体说明之前,首先我们要建立一个git的形象化模型。
如上图所示,git项目库主要分为工作区
和版本库
两个部分,工作区
指的就是我们原始的文件夹,而版本库
是指git init
初始化文件夹后新增的.git
文件。注意,.git
文件对于我们是隐藏的。在版本库
中,同样主要分为两个部分:暂存区(stage)
和本地分支
。
这里我们要有一个概念,那就是git在本地项目库中维护了和远程库一样的代码分支,git commit
命令事实上是将代码提交到本地维护的代码分支上面,然后接下来在将本地分支同步到远程库的分支上面。
暂存区的概念,我理解为充当了一个缓冲的作用,创造一个后悔与再三考虑的机会。推荐廖雪峰的git教程,讲的简单清晰明了。
查看当前项目状态
git status
当我们git add .
之后,忘记了git commit
,第二天不知道代码提交到了哪一步,这个时候git status
就可以告诉我们暂存区是否干净,本地代码是否需要git push
到远程库中。
克隆远程仓库项目到本地
git clone <远程库地址>
当我们想要下载远程库中的项目代码的时候,并没有一键下载的按钮,反而很容易得到项目远程库的地址。这个地址就是为了git clone
这条命令而存在的。新手需要注意的一点是,克隆项目之前,先使用命令行进入你希望下载项目的地址,在使用git clone
,不然就不知道克隆的项目在哪,增加了挫败感。
git clone -b [分支名] [远程库地址]
另外还需要注意,使用git clone
命令克隆下来的代码默认的是主分支代码(取决于设置的默认分支),需要在本地,使用终端,切换分支命令(git checkout 分支名),才能将代码切换。但人都是懒的,有没有指定克隆的代码分支的命令呢?答案是有的。那就是在git clone -b
后面指定分支名。
拉取远程仓库更新
git pull
当别人提交了代码到主分支的时候,而你的代码还是旧的,这个时候远程库主分支上的代码就比你本地领先了一个版本。前面说的编程是与人合作的工作,你需要在新的项目代码上编写自己的模块。这个时候你就需要git pull
拉取远程库最新代码。假设你没有这样做,那么当你git commit
代码时,就会报错,说你的代码与远程库发生冲突。所以,提交代码前,git pull
一下是很好的习惯。
查看分支
上面说了本地具有代码的所有分支,那么有时候我们需要知道目前所在分支,免得写错了分支,更是麻烦。
- 查看本地分支
git branch
- 查看远程分支
git branch -r
- 查看所有分支
git branch -a
创建新分支并切换
git checkout -b <新建分支名> <基于分支名>
切换分支
git checkout <分支名>
合并分支
git checkout <需要合并分支>
先切换到需要合并其他分支的主要分支上
git merge --no-ff <被合并分支名>
基于当前主要分支,创建新结点,合并其他分支
当我们在自己的开发分支开发结束后,管理员需要将开发者的代码合并到主分支上面。这个过程就叫做合并分支,主要过程分为两步,第一步就是切换到你的主分支(例如master
)上,然后git merge
你想要合并的分支就可以了。
但是不要放松,这里经常会出错,合并失败,代码冲突。这说明两个分支,在同一处代码做了不同的修改。很想,在某个点,你不同的选择产生了两个截然不同的平行宇宙。这个时候,解决的办法只有一个,查找到冲突的地方,手动修改。
代码回滚
这个是另一个很重要的技能,防止你该版本出了重大的错误,或者找不到错误的来源。需要进行时光旅行,将代码重置到上一个版本。
在此之前,我们需要明确一个概念,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master
分支。在版本库中,存在一个HEAD
指针,HEAD
严格来说不是指向提交,而是指向master
,master
才是指向提交的,所以,HEAD
指向的就是当前分支。一开始的时候,master
分支是一条线,Git用master
指向最新的提交,再用HEAD
指向master
,就能确定当前分支,以及当前分支的提交点。
当我们创建新的分支,例如
dev
时,Git新建了一个指针叫dev
,指向master
相同的提交,再把HEAD
指向dev
,就表示当前分支在dev
上:清楚了这些,那么接下来就是代码回滚了。
git log -[数字]
每一次提交都有一个自动生成的 commit 标识符,这串标识符就是回滚到具体版本所需要的。上面命令中的数字代表了最近几次的提交日志。
git reset --hard [commit序号]
该条命令即可回滚到指定的版本。
git reset --hard HEAD^
该条命令回滚到上一个版本。
提交抵消
最近工作中遇到一种情况,在commit一次代码后,发现这次提交的代码有问题,必须撤销这次commit。如果再手动改回来,首先有点笨,其次难免会有遗漏的地方。
这个时候可以使用git revert,撤销 某次操作,此次操作之前和之后的commit和history都会保留,并且把这次撤销作为一次最新的提交。
git revert HEAD 撤销前一次 commit
git revert HEAD^ 撤销前前一次 commit
git revert <commit> (比如:fa042ce57ebbe5bb9c8db709f719cec2c58ee7ff)
撤销指定的版本,撤销也会作为一次提交进行保存。
// 然后推送到origin
git push origin <分支名>
**git revert 和 git reset的区别 **
- git revert是用一次新的commit来回滚之前的commit,git reset是直接删除指定的commit。
- 在回滚这一操作上看,效果差不多。但是在日后继续merge以前的老版本时有区别。因为git revert是用一次逆向的commit“中和”之前的提交,因此日后合并老的branch时,导致这部分改变不会再次出现,但是git reset是之间把某些commit在某个branch上删除,因而和老的branch再次merge时,这些被回滚的commit应该还会被引入。
- git reset 是把HEAD向后移动了一下,而git revert是HEAD继续前进,只是新的commit的内容和要revert的内容正好相反,能够抵消要被revert的内容。