git基本使用
git有什么用
如果你使用Word写过长篇大论,那你一定有这样的经历:
想删除一个段落,又怕将来想恢复找不回来怎么办?有办法,先把当前文件“另存为……”一个新的Word文件,再接着改,改到一定程度,再“另存为……”一个新文件,这样一直改下去,最后你的文档目录变得杂乱不堪。
过了一周,你想找回被删除的文字,但是已经记不清删除前保存在哪个文件里了,只好一个一个文件去找,真麻烦。
看着一堆乱七八糟的文件,想保留最新的一个,然后把其他的删掉,又怕哪天会用上,还不敢删,真郁闷。
更要命的是,有些部分需要你的同事帮助填写,于是你把文件Copy到U盘里给她(也可能通过Email发送一份给她),然后,你继续修改Word文件。一天后,同事再把Word文件传给你,此时,你必须想想,发给她之后到你收到她的文件期间,你作了哪些改动,得把你的改动和她的部分合并,真困难。
git就是用来解决以上的问题的!git作为当今世界上最先进的分布式版本控制系统,很方便的解决了以往手动管理版本的模式。
安装git
在Windows上使用Git,直接从Git官网直接下载安装程序,然后按默认选项安装即可。安装完成后,在开始菜单里找到“Git”->“Git Bash”,蹦出一个类似命令行窗口的东西,就说明Git安装成功。
git基本操作

初始化仓库
git仓库,可以简单理解成一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“还原”。
1 | git init |
通过git init命令把这个目录变成Git可以管理的仓库,无论这个目录是否为空
执行过后的直观变化就是当前目录下多了一个.git的目录
添加文件到Git仓库
分两步:
- 使用命令
git add <file>,注意,可反复多次使用,添加多个文件,最后一次提交全部; - 使用命令
git commit -m <message>,完成提交。
例如:
1 | git add a.txt |
查看仓库当前状态
- 要随时掌握工作区的状态,使用
git status命令。 - 如果
git status告诉你有文件被修改过,用git diff可以查看修改内容。
查看提交日志
git log命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是3,上一次是second commit,最早的一次是first commit。
1 | $ git log |
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上--pretty=oneline参数:
1 | $ git log --pretty=oneline |
前面一大串是版本号,HEAD表示当前版本,master代表分支,再后面的部分就是提交时的说明
版本回退
假如我想回到当前版本的上一个版本该怎么办?
使用git reset命令:
1 | $ git reset --hard HEAD^ |
在Git中,用HEAD表示当前版本,也就是最新的提交,上一个版本就是HEAD^,上上一个版本就是HEAD^^,当然往上100个版本写100个^比较容易数不过来,所以写成HEAD~100。
--hard会回退到上个版本的已提交状态,而--soft会回退到上个版本的未提交状态,--mixed会回退到上个版本已添加但未提交的状态。
执行之后你再次执行git log,会发现:
1 | $ git log --pretty=oneline |
最新的版本我已经看不到了,假如我又后悔了怎么办?
如果你还没有关闭命令行窗口,直接找到最新提交的版本号,执行以下命令就可以切回对应的版本:
1 | git reset --hard dccec |
版本号没必要写全,前几位就可以了,Git会自动去找。
如果我已经关了命令行窗口呢?
Git提供了一个命令git reflog用来记录你的每一次命令:
1 | $ git reflog |
这样就可以找到之前的版本号进行回退了
撤销修改
在开始之前先说明几点以便于理解
- 什么是修改?比如你新增了一行,这就是一个修改,删除了一行,也是一个修改,更改了某些字符,也是一个修改,删了一些又加了一些,也是一个修改,甚至创建一个新文件,也算一个修改。
git add命令实际上就是把要提交的所有修改放到暂存区(Stage),然后,执行git commit就可以一次性把暂存区的所有修改提交到分支。- Git跟踪并管理的是修改,而非文件。
简单举一个例子:
第一次修改 -> git add -> 第二次修改 -> git commit
对同一个文件按照上述流程执行,会发现第一次的修改被提交了,第二次的修改不会被提交,这一点验证了上面的说法。
现在回到正题
试想这么一个场景:
现在是凌晨两点,你正在赶一份工作报告,你在readme.txt中添加了一行:
1 | $ cat readme.txt |
在你准备提交前,一杯咖啡起了作用,你猛然发现了stupid boss可能会让你丢掉这个月的奖金!
既然错误发现得很及时,你就可以手动删掉最后一行,把文件恢复正确的状态。如果用git status查看一下:
1 | $ git status |
你可以发现,Git会告诉你,git checkout -- <file>可以丢弃工作区的修改:
1 | $ git checkout -- readme.txt |
命令git checkout -- readme.txt意思就是,把readme.txt文件在工作区的修改全部撤销,这里有两种情况:
- 一种是
readme.txt自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态; - 一种是
readme.txt已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次git commit或git add时的状态。
现在,看看readme.txt的文件内容:
1 | $ cat readme.txt |
文件内容果然复原了。
git checkout -- file命令中的--很重要,没有--,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到git checkout命令。
现在假定是凌晨3点,你不但写了一些胡话,还git add到暂存区了:
1 | $ cat readme.txt |
庆幸的是,在commit之前,你发现了这个问题。用git status查看一下,修改只是添加到了暂存区,还没有提交:
1 | $ git status |
Git同样告诉我们,用命令git reset HEAD <file>可以把暂存区的修改撤销掉(unstage),重新放回工作区:
1 | $ git reset HEAD readme.txt |
git reset命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用HEAD时,表示最新的版本。
再用git status查看一下,现在暂存区是干净的,工作区有修改:
1 | $ git status |
还记得如何撤销工作区的修改吗?
1 | $ git checkout -- readme.txt |
整个世界终于清静了!
现在,假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得版本回退一节吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把stupid boss提交推送到远程版本库,你就真的惨了……
删除文件
当你在工作区中删除了已提交的文件,这是git就会发现工作区与仓库不一致
这时你有两个选择,一是确实要从版本库中删除该文件,那就用命令git rm删掉,并且git commit
1 | $ git rm test.txt |
另一种情况是删错了,因为版本库里还有呢,所以可以很轻松地把误删的文件恢复到最新版本:
1 | $ git checkout -- test.txt |
远程仓库
添加远程仓库
现在的情景是,你已经在本地创建了一个Git仓库后,又想在GitHub创建一个Git仓库,并且让这两个仓库进行远程同步,这样,GitHub上的仓库既可以作为备份,又可以让其他人通过该仓库来协作,一举多得。
在github页面上新建一个空仓库,可以见到类似如下内容:

跟着操作即可,注意将地址改为自己的github,同时远程库的名字就是origin,这是Git默认的叫法
删除远程库
如果添加的时候地址写错了,或者就是想删除远程库,可以用git remote rm <name>命令。使用前,建议先用git remote -v查看远程库信息:
1 | git remote -v |
然后,根据名字删除,比如删除origin:
1 | git remote rm origin |
此处的“删除”其实是解除了本地和远程的绑定关系,并不是物理上删除了远程库。远程库本身并没有任何改动。要真正删除远程库,需要登录到GitHub,在后台页面找到删除按钮再删除。
从远处仓库克隆
要克隆一个仓库,使用git clone命令克隆,同时还需要知道该仓库的地址。
例如:
1 | git clone https://github.com/splend21/test.git |
git分支
分支在实际中有什么用呢?假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
在之前的版本回退中,你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即master分支。HEAD严格来说不是指向提交,而是指向master,master才是指向提交的,所以,HEAD指向的就是当前分支。
一开始的时候,master分支是一条线,Git用master指向最新的提交,再用HEAD指向master,就能确定当前分支,以及当前分支的提交点:
1 | HEAD |
每次提交,master分支都会向前移动一步,这样,随着你不断提交,master分支的线也越来越长。
当我们创建新的分支,例如dev时,Git新建了一个指针叫dev,指向master相同的提交,再把HEAD指向dev,就表示当前分支在dev上:
1 | master |
你看,Git创建一个分支很快,因为除了增加一个dev指针,改改HEAD的指向,工作区的文件都没有任何变化!
不过,从现在开始,对工作区的修改和提交就是针对dev分支了,比如新提交一次后,dev指针往前移动一步,而master指针不变:
1 | master |
假如我们在dev上的工作完成了,就可以把dev合并到master上。Git怎么合并呢?最简单的方法,就是直接把master指向dev的当前提交,就完成了合并:
1 | HEAD |
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至可以删除dev分支。删除dev分支就是把dev指针给删掉,删掉后,我们就剩下了一条master分支:
1 | HEAD |
创建与合并分支
每个仓库都自带主分支master,当前仓库仅有一个readme.txt
首先,我们创建dev分支,然后切换到dev分支:
1 | $ git checkout -b dev |
git checkout命令加上-b参数表示创建并切换,相当于以下两条命令:
1 | $ git branch dev |
然后,用git branch命令查看当前分支:
1 | $ git branch |
git branch命令会列出所有分支,当前分支前面会标一个*号。
然后,我们就可以在dev分支上正常提交,比如对readme.txt做个修改,加上一行:
1 | Creating a new branch is quick. |
然后提交:
1 | $ git add readme.txt |
现在,dev分支的工作完成,我们就可以切换回master分支:
1 | $ git checkout master |
切换回master分支后,再查看一个readme.txt文件,刚才添加的内容不见了!因为那个提交是在dev分支上,而master分支此刻的提交点并没有变:
1 | HEAD |
现在,我们把dev分支的工作成果合并到master分支上:
1 | $ git merge dev |
git merge命令用于合并指定分支到当前分支。合并后,再查看readme.txt的内容,就可以看到,和dev分支的最新提交是完全一样的。
注意到上面的Fast-forward信息,Git告诉我们,这次合并是“快进模式”,也就是直接把master指向dev的当前提交,所以合并速度非常快。
当然,也不是每次合并都能Fast-forward,我们后面会讲其他方式的合并。
合并完成后,就可以放心地删除dev分支了:
1 | $ git branch -d dev |
删除后,查看branch,就只剩下master分支了:
1 | $ git branch |
因为创建、合并和删除分支非常快,所以Git鼓励你使用分支完成某个任务,合并后再删掉分支,这和直接在master分支上工作效果是一样的,但过程更安全。
我们注意到切换分支使用git checkout <branch>,而前面讲过的撤销修改则是git checkout -- <file>,同一个命令,有两种作用,确实有点令人迷惑。
实际上,切换分支这个动作,用switch更科学。因此,最新版本的Git提供了新的git switch命令来切换分支:
创建并切换到新的dev分支,可以使用:
1 | $ git switch -c dev |
直接切换到已有的master分支,可以使用:
1 | $ git switch master |
使用新的git switch命令,比git checkout要更容易理解。
解决冲突
人生不如意之事十之八九,合并分支往往也不是一帆风顺的。
准备新的feature1分支,继续我们的新分支开发:
1 | $ git switch -c feature1 |
修改readme.txt最后一行,改为:
1 | Creating a new branch is quick AND simple. |
在feature1分支上提交:
1 | $ git add readme.txt |
切换到master分支:
1 | $ git switch master |
在master分支上把readme.txt文件的最后一行改为:
1 | Creating a new branch is quick & simple. |
提交:
1 | $ git add readme.txt |
现在,master分支和feature1分支各自都分别有新的提交,变成了这样:
1 | HEAD |
这种情况下,Git无法执行“快速合并”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
1 | $ git merge feature1 |
果然冲突了!Git告诉我们,readme.txt文件存在冲突,必须手动解决冲突后再提交。git status也可以告诉我们冲突的文件:
1 | $ git status |
我们可以直接查看readme.txt的内容:
1 | <<<<<<< HEAD |
Git用<<<<<<<,=======,>>>>>>>标记出不同分支的内容,我们修改如下后保存:
1 | Creating a new branch is quick and simple. |
再提交:
1 | $ git add readme.txt |
现在,master分支和feature1分支变成了下图所示:
1 | HEAD |
用带参数的git log也可以看到分支的合并情况:
1 | $ git log --graph --pretty=oneline --abbrev-commit |
最后,删除feature1分支:
1 | $ git branch -d feature1 |
工作完成。