@[TOC](文章目录)
> 茫茫人海千千万万,感谢这一秒你看到这里。希望我的文章对你的有所帮助!
>
> 愿你在未来的日子,保持热爱,奔赴山海!
# 这篇文章教会Git
## 1. Git是什么?
![](https://www.icode9.com/i/ll/?i=img_convert/eb659605979efff87ce7731e624e035a.png)
### 1.1 发展历程
话不多说,Git具体发展历程这里我就不说了,看图就完事了。在2005年的时候,Linus这个人呢,只花了两周,自己用c语言就写出了一个分布式版本控制系统,一个月之内,Linux系统的源码已经由Git管理了!牛是怎么定义的呢?大家可以体会一下。
![](https://www.icode9.com/i/ll/?i=img_convert/5d69a74fe01d2b003c4a03a0ab607926.png)
来看一下Linus,膜拜一下,远远观望一下大佬!
### 1.2 Git是什么?
![](https://www.icode9.com/i/ll/?i=img_convert/0f93036b42a36d2c41fc62713cd4e690.png)
**Git**是目前世界上最先进的
分布式版本控制系统(Distributed Version Control System,简称 DVCS)。
### 1.3 Git和SVN
接下来,让我们聊一聊,我们熟知的Git和SVN的区别吧:
* **SVN** :SVN就是集中式控制系统,版本库是集中存储在中央服务器的。而每次开发人员想要做事情的时候呢,用的都是自己的电脑,所以每次都需要先从中央服务器取得最新的版本吗,然后才开始做事情。做完再把自己最新代码推送到中央服务器中。这样有什么缺点呢,一旦中央服务器奔溃了,整个项目就奔溃了。并且最大的问题呢,是集中式版本控制系统必须联网才能工作。如果在局域网内还行,速度够快,可如果是在互联网上呢,遇到比如像我网速慢的话,可能上传一个几M的文件都需要5、6分钟,这不分分钟憋出病来。
* **Git**:而Git就是分布式版本控制系统,这其中,没有了中央服务器,每一个开发人员都是一个完整的版本库。这样有什么优点呢,一旦有一个人的系统奔溃,电脑坏掉等,都是可以从其他人那里复制一份过来就能用了。当然,Git的优势不单是不必联网这么简单,后面我们还会看到Git极其强大的
分支管理,把SVN等远远抛在了后面。
## 2.Git有什么用?
首先让我们先看看企业开发存在的场景:
### 2.1 代码合并
在一个项目当中,一个人不可能全部把所有模块全部解决。这时候就需要分配任务给其他人完成,其他人把各自的项目模块完成,再将其整合到一起。
比如,小鱼和小猫是一个项目组的战友,今天产品经理提出两个需求,让小鱼和小猫一天做完。就这样,他们做完后,最后要
合并呢?
### 2.2 代码备份
小鱼今天在公司加班,凌晨一点的时候,终于一个项目模块眼看着就可以交付代码了。可是!!!万万没想到,公司停电了,小鱼的电脑关机了,付诸多少心血的代码就这样结束了!
### 2.3 代码还原
今天产品经理对小鱼说,叫小鱼去弄一个比较复杂的功能模块,这个功能模块相对于整体项目来说及其不稳定,而且可能需要改项目中的许多代码,这个时候小鱼应该怎么做呢?
### 2.4 问题追溯
小猫是项目组刚来的新人,写的代码bug比较多。并且不诚实,每次有bug,都会说不是他的问题,是之前人写的本来就有的。这样的话,我们如何判断这bug是谁造成的呢?
看着这些场景,你是否也会头皮发麻,不知从何下手呢,不用担心,Git可以完美的解决这些问题。
## 3. Git的使用(重点)
要想使用Git解决上述问题,客官你确定不下载一个Git来使用吗,而且它完全免费。 |
### 3.1 下载
> 这里只讲述在Windows上安装Git。
我们可以直接从Git官网[下载安装程序](https://git-scm.com/download),然后按默认选项安装即可,一路傻瓜“
`git init`”即可。
安装完成后,我们可以在任意文件夹或者是桌面点右键,就可以看到以下菜单:
![](https://www.icode9.com/i/ll/?i=img_convert/6962998153039816d94a8fd3c1375d0f.png)
我们点击
`Git Bash Here`,我们就会蹦出一个类似cmd(命令窗口)的窗口,就说明客官你的Git安装成功啦!
![](https://www.icode9.com/i/ll/?i=img_convert/29c70aa4e686b02c499a5185da6ea987.png)
安装成功后,我们还需要最后一步的设置,相当于是自己机器的标签,因为Git是分布式版本控制系统,需要每一台机器的标识,不然会认不出谁是谁呢。
~~~shell
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
~~~
注意
`git config`命令的
`--global`参数,用了这个参数,表示你这台机器上所有的Git仓库都会使用这个配置,相当于一个全局设置了这个配置,当然也可以对某个仓库指定不同的用户名和Email地址。
这里可以看下我的配置:
![](https://www.icode9.com/i/ll/?i=img_convert/06e3f230f2a44dd7c7522194b46f9ed9.png)
接下来可以看下我设置的全局配置
```shell
$ cat ~/.gitconfig
```
![](https://www.icode9.com/i/ll/?i=img_convert/f51bd6d94e7f4758433adb644bd70838.png)
### 3.2 创建版本库
什么是版本库呢?版本库又名仓库,英文名**repository**。你可以简单的理解为一个目录,这个目录里面的所有文件都可以被Git管理起来,每个文件的修改、删除,Git都能跟踪,以便任何时刻都可以追踪历史,或者在将来某个时刻可以“**还原**”。
接下来,让我们学习创建版本库的命令吧:通过
`git init`命令把这个目录变成Git可以管理的仓库:
![](https://www.icode9.com/i/ll/?i=img_convert/f1b904e1272963d01f4a4d6730b7ee46.png)
瞬间Git就帮你把版本库建好了,而且告诉你是一个空的仓库(empty Git repository),当然,细心的你可以发现当前目录下多了一个
`.git`的目录,这个目录是Git来跟踪管理版本库的。注意:没事千万不要手动(**作**)修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了,导致你没办法使用。如果你没有看到
`.git`目录,那是因为这个目录默认是隐藏的。可以看我这样子操作就行了
![](https://www.icode9.com/i/ll/?i=img_convert/c95a3eab011f54c2e9d594014f1a0926.png)
### 3.3 如何把文件添加到版本库中
不知道各位客官有没有看过赵本山和宋丹丹的小品,里面有个桥段,宋丹丹问赵本山:把大象放进冰箱需要几步?而现在问你,把文件添加到本地仓库中,需要几步呢?
现在告诉你,大象放进冰箱需要三步,而把一个文件放到Git仓库只需要**两步**。更方便快捷吧!
首先,我们得先创建一个文件(
`hello.txt`),然后并在其中添加文字:helloGit!
接下来几个步骤都不截图,把代码放进来,各位看官可以自行复制粘贴到本地测试下!每一步后面都会有相应的注释。瞧,我多贴心。像我这么贴心的男生不多见了吧。O(∩_∩)O哈哈~
```shell
$ vim hello.txt (创建并进入vim编辑器,编辑hello.txt文件)
```
```txt
helloGit! (在vim编辑器中,输入i插入,写helloGit!最后使用左上角的Esc键,并输入:wq就可以 退出vim编辑器啦!)
```
**第一步**,用命令
`git add`告诉Git,把文件添加到仓库:
```shell
$ git add hello.txt (创建hello.txt后,执行这一步代码,可以实现把hello.txt暂存在缓存区 中,这个概念后面概述)
```
执行上面的命令,没有任何显示,这就对了,Unix的哲学是“**没有消息就是好消息**”,说明添加成功。
**第二步**,用命令
`git commit`告诉Git,把文件提交到仓库:
```shell
$ git commit -m "添加hello.txt到本地仓库" (这里是把缓存区中的hello.txt提交到本地仓库)
[master (root-commit) 32b0ee3] 添加hello.txt到本地仓库
1 file changed, 1 insertion(+)
create mode 100644 hello.txt
```
这里我们简单解释一下
`git commit`命令,
`-m`后面输入的是我们对本次文件提交的**简要说明**,可以输入任意内容,当然最好是**有意义**的,这样你在需要回退版本号时,就能从历史记录里方便地找到改动记录。
嫌麻烦不想输入
`-m "xxx"`行不行?确实有办法可以这么干,但是强烈不建议你这么干,因为输入提交说明对自己或者对别人阅读都很重要。实在不想输入说明的童鞋请自行Google,我实在不想告诉你这个参数,不想坑害你们。你们记得,这个**提交说明**一定要写并且有意义的。
`git commit`命令执行成功后会告诉你,1 file changed:1个文件被改动(我们新添加的
`hello.txt`文件);1 insertion(+):插入了两行内容(
`hello.txt`有一行内容)。
所以为什么Git添加文件需要
`add`,
`commit`一共两步呢?因为
`commit`可以一次提交很多文件,所以你可以多次
`add`不同的文件,比如:
```shell
$ git add file1.txt
$ git add file2.txt file3.txt
$ git commit -m "add 3 files."
```
### 3.4 查看文件状态
到这里,我们已经成功地添加并提交了一个
`hello.txt`文件,现在,是时候继续工作了,于是,我们继续修改
`hello.txt`文件,改成如下内容:
~~~txt
helloGit!
Git is nice software!
~~~
现在,我们 可以执行
`git status`命令追踪文件现在状态:
![](https://www.icode9.com/i/ll/?i=img_convert/a8773f0e755700f5f703415037d0177d.png)
* 接下来,让我解释一下这个
`git status`命令:
`git status`命令可以让我们时刻掌握仓库当前的状态,上面的命令输出告诉我们,
`hello.txt`被修改过了,但还没有准备提交的修改。
虽然Git告诉我们
`hello.txt`被修改了,但如果能看看具体修改了什么内容,能做个对比那该有多好啊。比如你上周去参加朋友的婚礼了,回来上班的时候,已经记不清上次怎么修改的
`hello.txt`,所以,Git也有帮助我们查看文件修改的内容的命令,需要执行
`git diff`这个命令看看:
![](https://www.icode9.com/i/ll/?i=img_convert/1da3a9994a79d818fb8a412de67d3bd8.png)
* 接下来,让我解释下这个
`git diff`这个命令:
`git diff`顾名思义就是查看difference,显示的格式正是Unix通用的diff格式,可以从上面的命令输出看到,我们在第三行种添加了**Git is nice software**。
知道了对
`hello.txt`作了什么修改后,再把它提交到仓库就放心多了,提交修改和提交新文件是一样的两步,第一步是
`git add`:
```shell
$ git add hello.txt
```
同样没有任何输出。在执行第二步
`git commit`之前,我们再运行
`git status`看看当前仓库的状态:
```shell
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD
..." to unstage)
modified: hello.txt
```
`git status`告诉我们,将要被提交的修改包括`hello.txt`,下一步,就可以放心地提交了:
```shell
$ git commit -m "修改hello.txt内容"
[master a3c7c44] 修改hello.txt内容
1 file changed, 2 insertions(+)
```
提交后,我们再用`git status`命令看看仓库的当前状态:
```shell
$ git status
On branch master
nothing to commit, working tree clean
```
Git告诉我们当前没有需要提交的修改,而且,工作目录是干净(**working tree clean**)的。
看到这里是不是觉得Git也太好用了吧!下面,Git更厉害的东西出来了。Git提供的**"后悔药"**。
### 3.5 后悔药
现在你也学会了修改添加提交文件了。Now,我们再来练习一次,修改`hello.txt`文件如下:
```txt
helloGit!
Git is very nice software!
```
然后尝试添加提交:
```shell
$ git add hello.txt
$ git commit -m "hello.txt中添加very"
[master 7fbdd8f] hello.txt中添加very
1 file changed, 1 insertion(+), 1 deletion(-)
```
像这样,你不断对文件进行修改,然后不断提交修改到版本库里。比如,每次项目经理提了需求,你照做完成后,提交代码。但是,其中一次提交的版本,出现了**Bug**,或者一不小心把某个重要的文件**删除**了,导致整个项目不稳定奔溃了,这可怎么办呢?俗话说的好,人生在世,总有后悔莫及的事情,可是人生是没有**后悔药**可以吃的啊!但正是没有后悔药,人生注定会精彩无比,必然想好好活这一次,会变得无比意义。如果谁有,请给我一颗吧,让我回到某一时刻,对那个女孩说一句...咳咳,有点扯远了呢!哈哈
而在Git中,不要担心,因为Git总是会有后悔药可以吃的。 |
在操作文件的时候,每当你觉得文件修改到一定程度的时候,就可以“**保存一个快照**”,这个快照在Git中被称为`commit`。一旦你把文件改乱了,或者误删了文件,还可以从最近的一个`commit`恢复,然后继续工作,而不是把几个月的工作成果全部丢失。是不是觉得很赞!嘿嘿,具体怎么操作呢,请诸君看如下:
* 首先,让我们先来回顾下我们对`hello.txt`做了些什么,一共有多少个版本被提交到了版本库中:
+ 添加hello.txt到本地仓库
~~~txt
helloGit!
~~~
+ 修改hello.txt内容
~~~txt
helloGit!
Git is nice software!
~~~
+ hello.txt中添加nice
~~~txt
helloGit!
~~~
* 嘿嘿,在实际工作中,我们脑子怎么可能记得了一个文件每次都改了什么内容(当然,厉害的你可以当我没说这句话),可是我们把文件添加提交的Git仓库中有什么用。Git可以帮我追踪文件的状态,肯定会有某个命令可以告诉我们文件的历史记录。来了,在Git中,我们可以用`git log`命令查看:
![](https://www.icode9.com/i/ll/?i=img_convert/9e2fdf4e06802be089e858f1429d3dd8.png)
* 接下来,让我解释下`git log`命令:
`git log`命令显示从最近到最远的提交日志,我们可以看到3次提交,最近的一次是hello.txt中添加nice,上一次是修改hello.txt内容,最早的一次是添加hello.txt到本地仓库。
如果嫌输出信息太多,看得眼花缭乱的,可以试试加上`--pretty=oneline`参数:
![](https://www.icode9.com/i/ll/?i=img_convert/cc64f1b6df18349cb4bfd886ef1845c9.png)
这里,需要注意的是,你看到的一大串类似`7fbdd8f ...`的是`commit id`(**版本号**),Git的`commit id`不是1,2,3……递增的数字,而是一个SHA1计算出来的一个非常大的数字,用十六进制表示,而且你看到的`commit id`和我的肯定不一样,以你自己的为准(**注意**)。为什么`commit id`需要用这么一大串数字表示呢?因为Git是分布式的版本控制系统,Git通过这个唯一的id来区分每次提交,后面我们还要研究多人在同一个版本库里工作,如果大家都用1,2,3……作为版本号,那肯定就冲突了。
每提交一个新版本,实际上Git就会把它们自动串成一条时间线。如果使用可视化工具查看Git历史,(这里没有讲述安装Git可视化工具,如果有需求大的话,可以加一期讲述安装可视化Git),就可以更清楚地看到提交历史的时间线:
![](https://www.icode9.com/i/ll/?i=img_convert/3e39a4966e32e83a9e0df026068f70f2.png)
* 铺垫了这么多,终于可以来到我们使用Git的后悔药的时候了,我们要如何把`hello.txt`回退到上个版本呢,也就是回到修改hello.txt内容的这个版本呢?
* 首先,我们要清楚的知道Git如何判断当前版本是哪个版本呢?
在Git中,用`HEAD`表示当前版本,也就是最新的提交`7fbdd8f ...`(**注意我的提交ID和你的肯定不一样**),上一个版本就是`HEAD^`,上上一个版本就是`HEAD^^`,当然往上100个版本写100个`^`比较容易数不过来,所以写成`HEAD~7fb`。
* 现在,我们要把当前版本hello.txt中添加nice回退到上一个版本修改hello.txt内容,就可以使用`git reset`命令:
```shell
$ git reset --hard head^ (或者写成 git reset --hard 7fbdd8f587f6510248a23ab3a8217c1457237b31)
HEAD is now at a3c7c44 修改hello.txt内容
```
* 接下来,让我解释一下`git reset`命令吧:
Git在执行这段代码的时候,内部发生了什么呢?
当你回退版本的时候,Git仅仅是把HEAD从指向hello.txt中添加nice:
```ascii
┌────┐
│HEAD│
└────┘
│
└──> ○ hello.txt中添加nice
│
○ 修改hello.txt内容
│
○ 添加hello.txt到本地仓库
```
改为指向`修改hello.txt内容`:
```ascii
┌────┐
│HEAD│
└────┘
│
│ ○ hello.txt中添加nice
│ │
└──> ○ 修改hello.txt内容
│
○ 添加hello.txt到本地仓库
```
然后顺便把工作区的文件更新了。所以你让`HEAD`指向哪个版本号,你就把当前版本定位在哪。
所以这是你回退版本或者恢复版本的原理。嘿嘿,知道了吗!
而这其中有个`--hard`参数,这个有什么用呢?
这个地方可以有三个参数,分别什么呢?
> * Soft: Leave working tree and index untouched
> 软重置: 不更改工作区和索引
> * Mixed: Leave working tree untouchd,reset index
> 混合: 保持工作区不变,重置索引文件
> * Hard: Reset working tree and index(discard all local changes)
> 硬重置: 重置工作区和索引(丢弃所有本地变更)
* 看看我们的文件`hello.txt`的内容是否真的变成了上个版本的内容:
```shell
$ cat hello.txt
helloGit!
Git is nice software!
```
果然被还原了。
我们还可以继续回退到上一个版本添加hello.txt到本地仓库,不过客官且慢,容我们先用`git log`再看看现在版本库的状态:
```shell
$ git log
commit a3c7c4438d964f09fdf1914cb940816b1a3345c8 (HEAD -> master)
Author: 。。。
Date: 。。。
修改hello.txt内容
commit 32b0ee32bfb99ea931bbf19b4a700d464d20bc68
Author: 。。。
Date: 。。。
添加hello.txt到本地仓库
```
最新的那个版本hello.txt中添加nice已经看不到了!这就好比你,你吃了后悔药,从21世纪坐时光穿梭机来到了20世纪了,最新的版本,肯定就看不到啦!
* 可是可是,我还想要再回到21世纪,怎么办?不会再也回不去了吧?
当然,说了在Git中,你不要怕后悔,还是有的补救的,只要上面的命令行窗口还没有被关掉,你就可以顺着往上找啊找啊,找到那个hello.txt中添加nice的`commit id`是`7fbdd8f ...`,于是就可以指定回到未来的某个版本:
```shell
$ git reset --hard 7fbdd8f
HEAD is now at 7fbdd8f hello.txt中添加nice
```
版本号没必要写全,前几位就可以了,Git会自动去找。当然也不能只写前一两位,因为Git可能会找到多个版本号,就无法确定是哪一个了。
我们现在再小心翼翼地看看`hello.txt`的内容有没有变回来:
```shell
$ cat hello.txt
helloGit!
Git is very nice software!
```
嘿嘿,没想到,别人18年后才再是个好汉,可我马上又回来了。*Imagining*
* 可是可是,如果我的命令行窗口关闭了,或者是第二天后就后悔了,想恢复到最新版本,可怎么办呀?这时候我们找不到了最新版本的`commit id`了,改怎么办?想不到吧。
好了好了,告诉你吧。记得刚说过在Git中,总是有后悔药可以吃的。所以Git提供了一个命令`git felog`用来记录你的每一次命令:
```shell
$ git reflog
7fbdd8f (HEAD -> master) HEAD@{0}: reset: moving to 7fbdd8f
a3c7c44 HEAD@{1}: reset: moving to head^
7fbdd8f (HEAD -> master) HEAD@{2}: commit: hello.txt中添加nice
a3c7c44 HEAD@{3}: commit: 修改hello.txt内容
32b0ee3 HEAD@{4}: commit (initial): 添加hello.txt到本地仓库
```
到了这里,终于舒了口气,现在,你又可以乘坐时光机回到未来了。现在找到你最新版本号的`commit id`,接下来执行下面的代码:
```shell
$ git reset --hard 7fbdd8f
HEAD is now at 7fbdd8f hello.txt中添加nice
```
再让我们看看看看`hello.txt`的内容有没有变回来:
```SHELL
$ cat hello.txt
helloGit!
Git is very nice software!
```
嘿嘿,只能说؏؏☝ᖗ乛◡乛ᖘ☝؏؏完美!!!没错,我感觉到,全都回来了。
![](https://www.icode9.com/i/ll/?i=img_convert/1942d6a4d9630f3984955ed161d369ec.png)
* 学到这里,是不是感觉有Git真好,还能有后悔药吃,随意穿梭时空!
让我们牢记一下:
- `HEAD`指向的版本就是当前版本,因此,Git允许我们在版本的历史之间穿梭,使用命令`git reset --hard commit id`。
- 穿梭前,用`git log`可以查看提交历史,以便确定要回退到哪个版本。
- 要重返未来,用`git reflog`查看命令历史,以便确定要回到未来的哪个版本。
### 3.6 深入剖析Git的工作
经过一系列的学习,我们知道使用`git add`,`git commit`命令的时候,他把文件添加提交到了哪里吗,是直接添加到了本地仓库,还是推送到了远程仓库吗?接下来,让我们深入剖析下Git吧。
* 首先,我们先了解Git的**两个仓库**和**四大工作区域**:
Git分为两种类型的仓库,**本地仓库**和**远程仓库**。
- 本地仓库:是在开发人员自己电脑上的Git仓库
- 远程仓库:是在远程服务器上的Git仓库
![](https://www.icode9.com/i/ll/?i=img_convert/f1d72c842bc012b86be057541f8fbdf9.png)
这张图也可以清晰的看出Git的四大工作区域:
- **Workspace**:你电脑本地看到的文件和目录,在Git的版本控制下,构成了工作区,比如我的`test`文件夹就是一个工作区。
- **Index/Stage**:暂存区,一般存放在 .git目录下,即.git/index,它又叫待提交更新区,用于临时存放你未提交的改动。实际上,你执行`git add`,这些文件修改就添加到在这个区域里面啦。**
- **Repository**:本地仓库,你执行`git clone`地址,就是把远程仓库克隆到本地仓库。它是一个存放在本地的版本库,其中**HEAD指向最新放入仓库的版本**。而当你执行去`git commit`,文件改动就会到本地仓库来咯~**
- **Remote**:远程仓库,就是类似**github**,**码云**等网站所提供的仓库,可以理解为远程数据交换的仓库~
* 接下来,让我们了解下Git的**工作流程**:
其实,Git的**工作流程**,在我们之前的操作过程中,基本都涉及到了。这里我再完整的概述一下吧!
- 从远程仓库拉取文件代码回来;(`git clone`)
- 在工作目录,增删改查文件;
- 把改动的文件放入暂存区;(`git add`)
- 将暂存区的文件提交本地仓库;(`git commit`)
- 将本地仓库的文件推送到远程仓库;(`git push`)
是不是发现,完整Git的正向**工作流程**我们都已经完成,在这里都忍不住给你点一波赞了哈哈~
* 而在工作流程的阶段呢,Git文件可能产生**四种状态**:
* **Untracked**: 文件还没有加入到git库,还没参与版本控制,即未跟踪状态。这时候的文件,通过`git add`状态,可以变为Staged状态
* **Unmodified**:文件已经加入git库, 但是呢,还没修改, 就是说版本库中的文件快照内容与文件夹中还完全一致。Unmodified的文件如果被修改, 就会变为Modified. 如果使用`git remove`移出版本库, 则成为Untracked文件。
* **Modified**:文件被修改了,就进入modified状态啦,文件这个状态通过stage命令可以进入staged状态
* **staged**:暂存状态. 执行`git commit`则将修改同步到库中, 这时库中的文件和本地文件又变为一致, 文件为Unmodified状态.
是不是这些,我们都已经有看到,觉得特别熟悉呢?
这个阶段学习,你就会弄明白对Git的工作流程,就会明天Git的很多操作到底干了些什么。不会处于在使用的过程中懵懵懂懂的。
### 3.7 撤销修改文件
当然,在工作过程中,你是不会犯错的!不过现在已经加班到凌晨两点半,你可能觉得在夜深人静的时候,可以干一些不为人知的事情,于是你在`hello.txt`中添加了一行:
```shell
$ cat hello.txt
helloGit!
Git is very nice software!
My stupid boss.
```
在你准备提交本次代码前,在泡的第9杯咖啡起了作用,你猛然发现了`stupid boss`可能会让你丢掉这个月的奖金!
既然错误发现得很及时,就可以很容易地纠正它。你可以删掉最后一行,手动把文件恢复到上一个版本的状态。如果用`git status`查看一下:
```shell
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: hello.txt
no changes added to commit (use "git add" and/or "git commit -a")
```
你可以发现,Git会告诉你,`git checkout -- file`可以丢弃工作区的修改,于是乎,你快速的执行这段代码:
```shell
$ git checkout -- hello.txt
```
命令`git checkout -- hello.txt`意思就是,把`hello.txt`文件在工作区的修改全部撤销,这里有两种情况:
一种是`hello.txt`自修改后还没有被放到暂存区,现在,撤销修改就回到和版本库一模一样的状态;
一种是`hello.txt`已经添加到暂存区后,又作了修改,现在,撤销修改就回到添加到暂存区后的状态。
总之,就是让这个文件回到最近一次`git commit`或`git add`时的状态,能够有效帮助撤销掉你做出错误的修改的内容。现在,我们再小心翼翼地看看`hello.txt`的文件内容:
```shell
$ cat hello.txt
helloGit!
Git is very nice software!
```
文件内容果然复原了。
`git checkout -- file`命令中的`--`很重要,没有`--`,就变成了“切换到另一个分支”的命令,我们在后面的分支管理中会再次遇到`git checkout`命令。
时间来到了凌晨三点半,你不但写了一些胡话,而且还`git add`到暂存区了:
```shell
$ cat hello.txt
helloGit!
Git is very nice software!
My stupid boss.
$ git add hello.txt
```
值得庆幸的是,在`git commit`之前,你发现了这个问题。用`git staus`查看一下,修改只是添加到了**暂存区**,还没有提交:
```
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD ..." to unstage)
modified: hello.txt
```
这个时候,贴心的Git同样会告诉我们,用命令`git reset HEAD `可以把暂存区的修改撤销掉(unstage),重新放回工作区:
```shell
$ git reset HEAD hello.txt
Unstaged changes after reset:
M hello.txt
```
`git reset`命令既可以回退版本,也可以把暂存区的修改回退到工作区。当我们用`HEAD`时,表示最新的版本。
再用`git status`查看一下,现在暂存区是干净的,工作区有修改:
```shell
$ git status
On branch master
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: hello.txt
```
还记得如何丢弃工作区的修改吗?
```shell
$ git checkout -- hello.txt
$ git status
On branch master
nothing to commit, working tree clean
```
啊!你会发现,去tmd工作,不做了。然后继续敲着代码加着班。嘿嘿,不工作是不可能的,我可是一位打工人!
![](https://www.icode9.com/i/ll/?i=img_convert/d595a26a9eaf41b0f22f00fc062eb9a2.png)
可是现在,假设你不但改错了东西,还从暂存区提交到了版本库,怎么办呢?还记得[版本回退](https://www.liaoxuefeng.com/wiki/896043488029600/897013573512192)一节吗?可以回退到上一个版本。不过,这是有条件的,就是你还没有把自己的本地版本库推送到远程。还记得Git是分布式版本控制系统吗?我们后面会讲到远程版本库,一旦你把`stupid boss`提交推送到远程版本库,你就真的惨了……等着明天你老板把你拉进小黑屋谈话吧~~~
## 4.远程仓库
到目前为止,我们已经掌握了如何在本地仓库的文件操作,也再不用担心文件的备份丢失的问题了。这只是在本台电脑的备份,可是可是,一旦你电脑系统出现问题,奔溃,需要重装系统怎么办,你的文件不是一样还是丢失了。或者说,同一个项目组,想进行多人协作开发,应该怎么办。
> **Git帮你解决这些问题!**
### 4.1 远程仓库介绍
从之前的学习,我们知道Git有两个仓库,**本地仓库**和**远程仓库**。本地仓库,我们知道了,可是远程仓库是什么?别着急,让我为各位客官慢慢讲述:
Git是**分布式版本控制系统**,同一个Git仓库,可以分布到不同的机器上。怎么分布呢?最早,肯定只有一台机器有一个原始版本库,此后,别的机器可以“克隆”这个原始版本库,而且每台机器的版本库其实都是一样的,并没有主次之分。
在这里你肯定会想,至少需要两台机器才能玩远程库不是?但是我只有一台电脑,你这不是为难我胖虎吗?
![](https://www.icode9.com/i/ll/?i=img_convert/2f522aa956208d394e2eba81a998035b.png)
其实一台电脑上也是可以克隆多个版本库的,只要不在同一个目录下。不过,现实生活中是不会有人这么傻的在一台电脑上搞几个远程库玩(如果你真的这么可爱,那当我没说),因为一台电脑上搞几个远程库完全没有意义,而且硬盘挂了或者电脑坏了会导致所有库都挂掉,所以我也不告诉你在一台电脑上怎么克隆多个仓库。没必要对吧!
而实际情况往往是这样,找一台电脑充当**服务器**的角色,每天24小时开机,其他每个人都从这个“**服务器**”仓库**克隆**一份到自己的电脑上,并且各自把各自的提交推送到服务器仓库里,也从服务器仓库中拉取别人的提交。
完全可以自己搭建一台运行Git的服务器,不过现阶段,我为了学习Git专门搭建一个服务器为自己服务,你告诉我**现实**吗?嘿嘿,别这样,肯定还有别的方法啦。
好在这个世界上有一个`GitHub`的神奇的网站。顾名思义,从这个名字就可以看出这个网站就是提供**Git仓库托管服务**的,所以,只要注册一个`GitHub`账号,就可以免费获得**Git远程仓库**。
但这毕竟是国外的网站,网速可能不是特别的流畅。在国内,我们甚至是很多公司都会使用另一个网站:**码云**,网址:https://gitee.com。接下来我会着重介绍码云如何使用,注册,连接本地仓库等问题。
### 4.2 码云的使用
#### 4.2.1 官网地址:
>官网:https://gitee.com
![](https://www.icode9.com/i/ll/?i=img_convert/449b99cdfc57173a7d517a33839507db.png)
#### 4.2.2 注册账号
不会注册?别慌!秉着为人民服务,这里我也会详细的告诉你如何操作:
![](https://www.icode9.com/i/ll/?i=img_convert/ba7f3b6c48443f45c2d856d08c8e38e1.png)
嘿嘿,妈妈再也不担心我不会注册码云账号了!
#### 4.2.3 创建远程仓库
当你注册完账号后,接下来就可以进行下一步,**创建仓库**啦。
![](https://www.icode9.com/i/ll/?i=img_convert/75ff3e0916183c18da9264ba5a1e9f18.png)
到这里,你的远程仓库宣布创建完毕啦。是不是一下子有了远程仓库,很开心。但是,你要怎么添加远程仓库呢?
#### 4.2.4 添加远程仓库
话不多说,来看看添加远程仓库的**两种方式**吧:
我们本地电脑和远程服务器可以使用两种方式来传输数据:**HTTPS**和**SSH协议**。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V1gQiLPc-1623550287379)(C:\Users\43835\Desktop\文章需要的图片\git\本地仓库如何与远程仓库沟通.png)]
**HTTPS协议**在推送代码时需要输入账号密码比较麻烦。**SSH协议**只需要配置一次,以后不再需要输入账号密码,比较方便。所以,我们学习SSH方式(没办法,**懒**是一个能很好促进人类发展的词语)
* 那什么是SSH协议
SSH 为 Secure Shell(安全外壳协议)的缩写,由 IETF 的网络小组(Network Working Group)所制定。SSH 是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pIL6UARi-1623550287380)(C:\Users\43835\Desktop\文章需要的图片\git\使用SSH协议.png)]
使用SSH协议通信时,推荐使用基于密钥的验证方式。所以你必须为自己创建一对密匙,需要把公钥放到远程仓库上面。
* 创建SSH key
首先我们可以在用户主目录下,看看有没有`.ssh`目录,如果有,再看看这个目录下有没有`id_rsa`和`id_rsa.pub`这两个文件,如果已经有了,可直接跳到下一步。如果没有,在文件夹中右键点击`Git Bash`进入cmd窗口,创建**SSH Key**,输入命令
```shell
$ ssh-keygen -t rsa
```
一路回车向下走按三次回车,不要输入任何内容即可!
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9Ok6gr7r-1623550287380)(C:\Users\43835\Desktop\文章需要的图片\git\Git创建SSH Key.jpg)]
如果一切顺利的话,可以在`C:\Users\用户名\.ssh`里面找到有`id_rsa`和`id_rsa.pub`两个文件,这两个就是**SSH Key**的秘钥对。
![](https://www.icode9.com/i/ll/?i=img_convert/4eea985d54b4ed5d1ee4288a57854fd2.png)
注意:`id_rsa`是私钥,不能泄露出去,`id_rsa.pub`是公钥,可以放心地告诉任何人。
* 码云SSH密钥配置
* 第一步: 然后将`C:\Users\Administrator\.ssh`生成的公钥文件【`id_rsa.pub`】内容复制出来。
* 第二步: 打开gitee官网--【登录】--【设置】-【SSH公钥】选项如下图:
![](https://www.icode9.com/i/ll/?i=img_convert/06c9fc3bfe7ca8a28b4b29942004c59c.png)
注意,在点击确定后,需要进行一个账号安全验证,这里输入你的**码云的登录密码**就可以了。
* 完成后,就会出现这个界面:
![](https://www.icode9.com/i/ll/?i=img_convert/a979383ca31349ca463acb5954041f75.png)
到这里呢,解释一下为什么我们一定要添加这个SSH Key ,也可以这么说,为什么码云需要这个SSH Key呢?因为码云需要识别出你推送的提交确实是你推送的,而不是别人冒充的,而Git支持SSH协议,所以,码云只要知道了你的公钥,就可以确认只有你自己才能推送。
当然,码云允许你添加多个Key。假定你有若干电脑,你一会儿在公司提交,一会儿在家里提交,只要把每台电脑的Key都添加到码云,就可以在每台电脑上往码云推送了。
* 添加公钥成功后呢,就开始添加我们的**远程仓库**
完成之前的操作之后,保证你的码云有账号,有了你本台电脑的SSH Key,有了远程仓库。
* 找到我们之前创建的远程仓库TestGit.
![](https://www.icode9.com/i/ll/?i=img_convert/eedd734860962e553b23ab278939bf52.png)
现在我们可以把一个已有的本地仓库与之关联,然后,把本地仓库的内容推送到GitHub仓库。
* 现在,我们根据码云的提示,复制上面的**SSH协议地址**,然后在本地仓库下运行命令:
```shell
$ git remote add origin git@gitee.com:prince-nezha/test-git.git
```
请千万注意,把上面的`git@gitee.com:prince-nezha/test-git.git`替换成你自己的复制的**SSH协议地址**,否则,你在本地关联的就是我的远程库,关联没有问题,但是你以后推送是推不上去的,因为你的**SSH Key公钥**不在我的账户列表中,而且也推送不到你自己的远程仓库。
添加后,远程库的名字就是`origin`,这是Git默认的叫法,也可以改成别的,但是`origin`这个名字一看就知道是远程库。
* 现在我们已经关联了远程仓库了,但是现在远程仓库还没内容,现在就需要我们把本地库的所有内容推送到远程仓库上:
```shell
$ git push -u origin master
Counting objects: 9, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (9/9), 771 bytes | 385.00 KiB/s, done.
Total 9 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To gitee.com:favorite_canned_fish/test-git.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.
```
这里解释一下:把本地库的内容推送到远程,用`git push`命令,实际上是把当前分支`master`推送到远程。
由于远程库是空的,我们第一次推送`master`分支时,加上了`-u`参数,Git不但会把本地的`master`分支内容推送的远程新的`master`分支,还会把本地的`master`分支和远程的`master`分支关联起来,在以后的推送或者拉取时就可以简化命令。
* 推送成功后,可以立刻在码云页面中看到远程仓库的内容已经和本地一模一样:
![](https://www.icode9.com/i/ll/?i=img_convert/8ec82cba209f60d87f89ec452d4672a4.png)
* 现在起,只要本地做了修改提交,就可以通过命令:
```shell
$ git push origin master
```
把本地`master`分支的最新修改推送至码云,现在,你就拥有了真正的分布式版本库!是不是很激动呢。
* 这里有个要注意的地方:**SSH警告**
当你第一次使用Git的`clone`或者`push`命令连接码云远程仓库时,会得到一个警告:
```shell
The authenticity of host 'gitee.com (xx.xx.xx.xx)' can't be established.
RSA key fingerprint is xx.xx.xx.xx.xx.
Are you sure you want to continue connecting (yes/no)?
```
这是因为Git使用SSH连接,而SSH连接在第一次验证码云服务器的Key时,需要你确认码云的Key的指纹信息是否真的来自码云的服务器,输入`yes`回车即可。
Git会输出一个警告,告诉你已经把码云的Key添加到本机的一个信任列表里了:
```shell
Warning: Permanently added 'gitee.com' (RSA) to the list of known hosts.
```
这个警告只会出现一次,后面的操作就不会有任何警告了。
* 这里来对添加远程仓库来个总结:
* 要关联一个远程库,使用命令`git remote add origin ssh协议`;
* 关联后,使用命令`git push -u origin master`第一次推送master分支的所有内容;
* 此后,每次本地提交后,只要有必要,就可以使用命令`git push origin master`推送最新修改;而且分布式版本系统的最大好处之一是在本地工作完全不需要考虑远程库的存在,也就是有没有联网都可以正常工作,而SVN在没有联网的时候是拒绝干活的!当有网络的时候,再把本地提交推送一下就完成了同步,是不是太方便了!
#### 4.2.5 克隆远程仓库
远程仓库做好了,这时候,公司来一个新员工小鱼后,需要拿到远程服务器中的代码到本地进行开发,克隆远程仓库也就是从远程把仓库复制一份到本地,克隆后会创建一个新的本地仓库。选择一个任意部署仓库的目录,然后克隆远程仓库。
![](https://www.icode9.com/i/ll/?i=img_convert/2aa62848c69322672346a40e1b3b281f.png)
小鱼现在需要怎么做呢?来吧,跟着我的操作走一遍,保准你成功!
+ 新建一个文件夹xiaoyu,将来克隆下来的代码会在这个文件夹中
+ 要先克隆到本地,首先得先获得远程仓库的SSH地址:
![](https://www.icode9.com/i/ll/?i=img_convert/17f0c9fbba987fccbfff8d5f9c9212c2.png)
+ 想之前一样,进入xiaoyu文件夹中,右键点击`Git Bash`进入进入cmd窗口,使用`git clone`的命令克隆一个本地库:
```shell
$ git clone git@gitee.com:prince-nezha/test-git.git
Cloning into 'test-git'...
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 12 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (12/12), 1.02 KiB | 346.00 KiB/s, done.
```
注意把Git库的地址换成你自己的,然后进入`test-git`目录看看,已经有`hello.txt`文件了:
```shell
$ cd test-git
$ ls
hello.txt
```
现在小鱼已经成功克隆远程仓库的代码,可以进行本地操作了。如果有多个人协作开发,那么每个人各自从远程克隆一份就可以了。
+ 注意:你也许还注意到,码云给出的地址不止一个,还可以用`https://gitee.com/prince-nezha/test-git.git`这样的地址。实际上,Git支持多种协议,默认的`git://`使用ssh,但也可以使用`https`等其他协议。
使用`https`除了速度慢以外,还有个最大的麻烦是每次推送都必须输入口令,但是在某些只开放http端口的公司内部就无法使用`ssh`协议而只能用`https`。
+ 这里还是建议使用**SSH协议**,没办法,好用又开又简单点,符合我这个懒人的特点。嘿嘿!
## 5. 分支管理(重点)
让我们来看看分支在实际开发中的用处吧
* 假设你准备开发一个新功能,但是需要两周才能完成,第一周你写了50%的代码,如果立刻提交,由于代码还没写完,不完整的代码库会导致别人不能干活了。如果等代码全部写完再一次提交,又存在丢失每天进度的巨大风险。
现在有了分支,就不用怕了。你创建了一个属于你自己的分支,别人看不到,还继续在原来的分支上正常工作,而你在自己的分支上干活,想提交就提交,直到开发完毕后,再一次性合并到原来的分支上,这样,既安全,又不影响别人工作。
* 又或者领导让你开发一个新功能,但是这个功能你也没有把握做出来,或者这个功能不稳定存在问题,这样的代码如果直接提交必然会让整个项目变得不稳定,存在风险,这个时候我们可以使用分支功能,将新功能代码提交到新分支上面,待稳定后再合并。
是不是分支这个功能听起来挺不错的?接下来让我们深入了解下分支。
### 5.1 创建与合并分支
在之前的学习中,想必你已经知道,每次提交,Git都把它们串成一条时间线,这条时间线就是一个分支。截止到目前,只有一条时间线,在Git里,这个分支叫主分支,即`master`分支。`HEAD`严格来说不是指向提交,而是指向`master`,`master`才是指向提交的,所以,`HEAD`指向的就是当前分支。
一开始的时候,`master`分支是一条线,Git用`master`指向最新的提交,再用`HEAD`指向`master`,就能确定当前分支,以及当前分支的提交点:
![](https://www.icode9.com/i/ll/?i=img_convert/4fed1b5e8cfa59f802e17df5e0f8bf58.png)
这样我们以后每次提交,每次提交,`master`分支都会向前移动一步,这样,随着你不断提交,`master`分支的线也越来越长。
当我们创建新的分支,例如`dev`时,Git新建了一个指针叫`dev`,指向`master`相同的提交,再把`HEAD`指向`dev`,就表示当前分支在`dev`上:
![](https://www.icode9.com/i/ll/?i=img_convert/bea128b6d9ef39f7523ea33cf73dabee.png)
你看,Git创建一个分支很快,因为除了增加一个`dev`指针,改改`HEAD`的指向,工作区的文件没有发生任何改变。
不过,从现在开始,对工作区的修改和提交就是针对`dev`分支了,比如新提交一次后,`dev`指针往前移动一步,而`master`指针不变:
![](https://www.icode9.com/i/ll/?i=img_convert/5174f6098c1b266815d83910bc4ad534.png)
假如我们在`dev`上的工作完成了,就可以把`dev`合并到`master`上。Git怎么合并呢?最简单的方法,就是直接把`master`指向`dev`的当前提交,就完成了合并:
![](https://www.icode9.com/i/ll/?i=img_convert/8b40af39cbfd4e1dc2a247542f33129d.png)
所以Git合并分支也很快!就改改指针,工作区内容也不变!
合并完分支后,甚至我们可以删除`dev`分支。删除`dev`分支就是把`dev`指针给删掉,删掉后,我们就剩下了一条`master`分支:
![](https://www.icode9.com/i/ll/?i=img_convert/8ef7d3ccedbdea0411cea678791cce7d.png)
这也太神奇了吧,你看得出来这波Git的分支组合拳吗,看得出来有些提交是通过分支完成的吗?
* 是不是有点懵了,接下来让我们进行实战一番,就知道是怎么回事了。
- 第一步,我们创建`dev`分支,然后切换到`dev`分支:
```shell
$ git checkout -b dev
Switched to a new branch 'dev'
```
`git checkout`命令加上`-b`参数表示创建并切换,相当于以下两条命令:
```shell
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
```
- 第二步,我们可以用`git branch`命令先查看当前分支:
```shell
$ git branch
* dev
master
```
`git branch`命令会列出所有分支,当前分支前面会标一个`*`号。
- 第三步,我们就可以在`dev`分支上正常提交,比如对`hello.txt`做个修改,加上一行:
```shell
testDev!
```
- 第四步,我们对在dev上的修改进行提交:
```shell
$ git add hello.txt
$ git commit -m "测试dev分支提交"
[dev ebc4496] 测试dev分支提交
1 file changed, 1 insertion(+)
```
- 第五步,到了这里,`dev`分支的工作完成,我们就可以切换回`master`分支:
```shell
$ git checkout master
Switched to branch 'master'
```
- 第六步,我们切换回`master`分支后,再查看一个`hello.txt`文件,刚才添加的内容不见了!因为那个提交是在`dev`分支上,而`master`分支此刻的提交点并没有变:
![git-br-on-master](https://www.icode9.com/i/ll/?i=img_convert/95a209512b005dec3c7e94f7188339f2.png)
```shell
$ cat hello.txt
helloGit!
Git is very nice software!
```
- 第七步,我们把`dev`分支的工作成果合并到`master`分支上:
```shell
$ git merge dev
Updating 21c9de4..ebc4496
Fast-forward
hello.txt | 1 +
1 file changed, 1 insertion(+)
```
`git merge`命令用于合并指定分支到当前分支。合并后,再查看`hello.txt`的内容,就可以看到,和`dev`分支的最新提交是完全一样的了。
```shell
$ cat hello.txt
helloGit!
Git is very nice software!
testDev!
```
注意到上面的 `Fast-forward`信息,Git告诉我们,这次合并是“快进模式”,也就是直接把`master`指向`dev`的当前提交,所以合并速度非常快。
当然,也不是每次合并都能`Fast-forward`,我们后面会讲其他方式的合并。
- 第八步,合并完成后,就可以放心地删除`dev`分支了:
```shell
$ git branch -d dev
Deleted branch dev (was ebc4496).
```
- 第九步,删除后,查看`branch`,就只剩下`master`分支了:
```shell
$ git branch
* master
```
因为创建、合并和删除分支非常快,所以Git支持你使用分支完成某个任务,合并后再删掉分支,这和直接在`master`分支上工作效果是一样的,但过程更安全。这也很符合多人做不同功能模块,做完再提交到服务器进行合并。
* 学到这里,是不是对分支的创建和 合并有了大概的了解呀!而且Git也非常鼓励我们使用分支:
查看分支:`git branch`
创建分支:`git branch `
切换分支:`git checkout `
创建+切换分支:`git checkout -b `
合并某分支到当前分支:`git merge `
删除分支:`git branch -d `
### 5.2 解决冲突
月有阴晴圆缺,人生的事也总不会太完美,合并分支之路 也不会是一帆风顺的。
让我们来看看合并分支的时候究竟发生了什么:
准备新的`dev1`分支,继续我们的新分支开发:
```shell
$ git checkout -b dev1
Switched to a new branch 'dev1'
```
修改`hello.txt`最后一行,改为:
```shell
creating a new branch.
```
在`dev1`分支上提交:
```shell
$ git add hello.txt
$ git commit -m "测试合并冲突问题"
[dev1 927155c] 测试合并冲突问题
1 file changed, 1 insertion(+), 2 deletions(-)
```
切换到`master`分支:
```shell
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
```
这里Git还会自动提示我们当前`master`分支比远程的`master`分支要超前1个提交。
在`master`分支上把`hello.txt`文件的最后一行改为:
```shell
creating a old branch master.
```
提交:
```shell
$ git add hello.txt
$ git commit -m "在master修改hello.txt"
[master c4fe80d] 在master修改hello.txt
1 file changed, 1 insertion(+), 1 deletion(-)
```
现在,`master`分支和`dev1`分支各自都分别有新的提交,变成了这样:
![](https://www.icode9.com/i/ll/?i=img_convert/f98059f2d7764fd7d26b9151e71e27bf.png)
这种情况下,Git无法执行“**快速合并**”,只能试图把各自的修改合并起来,但这种合并就可能会有冲突,我们试试看:
```shell
$ git merge dev1
Auto-merging hello.txt
CONFLICT (content): Merge conflict in hello.txt
Automatic merge failed; fix conflicts and then commit the result.
```
这里我们注意到,在执行了`git merge dev1`命令之后,Git控制台上的后面分支信息发生变化:
![](https://www.icode9.com/i/ll/?i=img_convert/cb790b0a78b50082251f0b68756a23ec.png)
分支从`master`变成了`master|MERGING`,这也告诉了我们这次合并有文件冲突。
果然冲突了!Git告诉我们,`hello.txt`文件存在冲突,必须**手动解决冲突**后再提交。`git status`也可以告诉我们冲突的文件:
```shell
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add ..." to mark resolution)
both modified: hello.txt
no changes added to commit (use "git add" and/or "git commit -a")
```
我们可以直接查看`hello.txt`的内容:
```shell
helloGit!
Git is very nice software!
<<<<<<< HEAD
creating a old branch master.
=======
creating a new branch.
>>>>>>> dev1
```
Git用`<<<<<<<`,`=======`,`>>>>>>>`标记出不同分支的内容,我们把这些标记出来的内容行删掉,并修改如下后保存:
```shell
helloGit!
Git is very nice software!
creating a new branch.
```
再提交:
```shell
$ git add hello.txt
$ git commit -m "修改冲突问题"
[master f8e3e2a] 修改冲突问题
```
我们就能看出来,之前的分支信息`master|MERGING`又变回`master`了
![](https://www.icode9.com/i/ll/?i=img_convert/e187d785b02843d8f3c93f39630da5e2.png)
总结来说,每次当Git无法自动合并分支时,就必须首先解决冲突。解决冲突后,再提交,合并完成。解决冲突就是把Git合并失败的文件手动编辑为我们希望的内容,再提交。
### 5.3 Bug分支
在真正的项目开发中,Bug如同家常便饭一样每天都可能发生。但是有了Bug就需要修复,不然一个项目就会存在了不稳定性或者来自各方的问候。
![](https://www.icode9.com/i/ll/?i=img_convert/0e1df274f9216c5f7c25311644af459b.png)
而在Git中,由于分支是如此的强大,所以,每个Bug都可以通过一个新的临时分支来修复,修复后,合并分支,然后将临时分支删除。
当你接到测试妹纸提出一个代号66的**bug的issue**时。你本来不想改的,或者想等到后面有时间后再来修改Bug,但是测试妹纸一直催你改Bug。
![](https://www.icode9.com/i/ll/?i=img_convert/497a6f33050c7378f95cc22e5d29a08c.png)
最后没办法,你还是得来改Bug,很自然的,你想创建一个分支`issue-66`来修复这个Bug。等等,想创建一个分支来修复Bug,但是当前正在`dev`上进行的工作还没有提交:
```shell
$ git status
On branch dev
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: git.txt
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: hello.txt
```
当然,这并不是你不想提交,而是工作只进行到一半,还没法提交。可是预计完成还需两三天的时间。但是,测试只给你两个小时内必须修复该Bug,这该怎么办?
幸好,Git还提供了一个`git stash`功能,可以把我们目前的工作现场“**储藏**”起来,等以后恢复现场后继续工作:
```shell
$ git stash
Saved working directory and index state WIP on dev: f8e3e2a 修改冲突问题
```
现在,用`git status`查看工作区,就是干净的(除非有没有被Git管理的文件),因此可以放心地创建分支来修复bug。
* **注意**:如果你使用了`git stash`后,查看工作区没生效,如同第一次查看工作区一样的话,这里就要看看是不是有新添加的文件代码,没有先添加到工作区来。说白了就是没有在Git 版本控制中的文件,是不能被`git stash`存起来的。所以你应该先把文件添加到工作区来嘛,至于添加到工作区,怎么撤销工作区,前面也讲过啦!
首先确定要在哪个分支上修复Bug,这个以你出现的Bug的最新提交分支来定,如果实在找不到,就假定需要在`master`分支上修复,就从`master`创建临时分支:
```shell
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
$ git checkout -b issue-66
Switched to a new branch 'issue-66'
```
现在修复Bug,需要找到Bug的位置,进行修改。现在这里只需要把“**Git is very nice software!**”改为“**Git is very good software!**”,然后提交:
```shell
$ git add hello.txt
$ git commit -m "fix bug 66"
[issue-66 ee213a8] fix bug 66
1 file changed, 1 insertion(+), 1 deletion(-)
```
修复完成后,切换到`master`分支,并完成合并,最后删除`issue-66`分支:
```shell
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 4 commits.
(use "git push" to publish your local commits)
$ git merge issue-66
Updating f8e3e2a..ee213a8
Fast-forward
hello.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
```
这样就修复完Bug了,是不是很方便,原来的工作空间没有动过,而且改完Bug的分支不想要了,也可以删掉。删掉分支的命令之前也介绍了。而且原计划两个小时的bug修复只花了5分钟!现在该干嘛呢?
嘿嘿,在线偷偷摸鱼吗?摸鱼这辈子是不可能的了。摸鱼一时爽,但最终摸鱼还是要付出代价的,这种在带薪提升自我的时候,怎么能浪费呢?人从工作中可以得到乐趣,这才是一种巨大的好处。现在我的心里只有一件事,**就是敲代码**。
![](https://www.icode9.com/i/ll/?i=img_convert/3f092e45b5b2f3d91f4f2dc6f84217fa.png)
现在,是时候接着回到`dev`分支干活了!
```shell
$ git checkout dev
Switched to branch 'dev'
$ git status
On branch dev
nothing to commit, working tree clean
```
可是可是,工作区是干净的,刚才的工作现场存到哪去了?不会不见了吧,要怎么找回来呀?放心,老夫不会坑害你的。嘿嘿,"**耗子尾汁**"吧。
现在我们用`git stash list`命令看看:
```shell
$ git stash list
stash@{0}: WIP on dev: f8e3e2a 修改冲突问题
```
工作现场还在,只是Git把`stash`内容存在某个地方了,但是需要恢复一下,有两个办法:
一是用`git stash apply`恢复,但是恢复后,`stash`内容并不删除,你需要用`git stash pop`来删除;
另一种方式是用`git stash pop`,恢复的同时把stash内容也删了:
```shell
$ git stash pop
On branch dev
Changes to be committed:
(use "git reset HEAD ..." to unstage)
new file: git.txt
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
modified: hello.txt
Dropped refs/stash@{0} (16d1c361031a39e7a97b8e63c3a6dcdef58ac37e)
```
再用`git stash list`查看,就看不到任何`stash`内容了:
```shell
$ git stash list
```
你可以多次`stash`,恢复的时候,先用`git stash list`查看,然后恢复指定的stash,用命令:
```shell
$ git stash apply stash@{0}
```
学到这里,是不是学会在修复Bug的时候们会通过创建新的Bug分支进行修复,然后合并,最后删除。
当手头工作没有完成时,先把工作现场`git stash`一下,然后去修复Bug,修复后,再`git stash pop`,回到工作现场;
## 6. 标签管理
Git有`commit`,为什么还要引入`tag`?
“请把上周一的那个版本打包发布,commit号是**6a5819e...**”
“一串乱七八糟的数字不好找!”
如果换一个办法:
“请把上周一的那个版本打包发布,版本号是v1.2”
“好的,按照tag v1.2查找commit就行!”
所以,`tag`就是一个让人容易记住的有意义的名字,它跟某个`commit`绑在一起。
现实是,在项目完成后,合并分支后,发布一个版本时,我们通常先在版本库中打一个标签(tag),这样,就唯一确定了打标签时刻的版本。将来无论什么时候,取某个标签的版本,就是把那个打标签的时刻的历史版本取出来。所以,标签也是版本库的一个快照。
这样也可以及时发现在哪个版本出现问题后,可以快速定位在哪个分支下,或者直接使用这个`tag`来修复Bug。Git的标签虽然是版本库的快照,但其实它就是指向某个`commit`的指针(跟分支很像对不对?但是分支可以移动,标签不能移动),所以,创建和删除标签都是瞬间完成的。
### 6.1 创建标签
我们了解了标签的用处,那如何在Git中打标签呢?
* 首先,切换到需要打标签的分支上:
```shell
$ git branch
* dev
master
$ git checkout master
Switched to branch 'master'
```
然后,敲命令`git tag `就可以打一个新标签:
```shell
$ git tag v1.0
```
可以用命令`git tag`查看所有标签:
```shell
$ git tag
v1.0
```
一般来说默认标签是打在最新提交而且是刚发布版本的`commit`上的。可是可是,如果忘了打标签。比如,现在已经是周五了,但应该在上周五发版本的时候打的标签没有打,怎么办?
方法是找到历史提交的`commit id`,然后打上就可以了:
```shell
$ git log --pretty=oneline --abbrev-commit
ee213a8 (HEAD -> master, tag: v1.0, issue-66) fix bug 66
f8e3e2a 修改冲突问题
c4fe80d 在master修改hello.txt
927155c (dev1) 测试合并冲突问题
ebc4496 测试dev分支提交
21c9de4 (origin/master) 修改hello.txt文件
7fbdd8f hello.txt中添加nice
a3c7c44 修改hello.txt内容
32b0ee3 添加hello.txt到本地仓库
```
比方说要对`fix bug 66`这次提交打标签,它对应的`commit id`是`ee213a8`,敲入命令:
```shell
$ git tag v1.1 ee213a8
```
再用命令`git stag`查看标签:
```shell
$ git tag
v1.0
v1.1
```
注意,标签不是按时间顺序列出,而是按字母排序的。可以用`git show `查看标签信息:
```shell
$ git show v1.1
commit ee213a81a0d5ba652d0e8164109e95fffc954518 (HEAD -> master, tag: v1.1, tag: v1.0, issue-66)
Author: 。。。
Date: 。。。
fix bug 66
diff --git a/hello.txt b/hello.txt
index 4a49052..6287c7d 100644
--- a/hello.txt
+++ b/hello.txt
@@ -1,4 +1,4 @@
helloGit!
-Git is very nice software!
+Git is very good software!
creating a new branch.
```
可以看到,`v1.1`确实打在`fix bug 66`这次提交上。
还可以创建带有说明的标签,用`-a`指定标签名,`-m`指定说明文字:
```shell
$ git tag -a v1.2 -m "version 1.2 released" c4fe80d
```
用命令`git show `可以看到说明文字:
```shell
$ git show v1.2
tag v1.2
Tagger: 。。。
Date: 。。。
version 1.2 released
commit c4fe80dbe89033a6daf33b440ec93470ffc019ab (tag: v1.2)
Author: 。。。
Date: 。。。
在master修改hello.txt
diff --git a/hello.txt b/hello.txt
...
```
注意:标签总是和某个`commit id`挂钩。如果这个`commit id`既出现在`master`分支,又出现在`dev`分支,那么在这两个分支上都可以看到这个标签。
* 这里小结下:
* 命令`git tag `用于新建一个标签,默认为`HEAD`,也可以指定一个`commit id`;
* 命令`git tag -a -m "版本号信息之类..."`可以指定标签信息;
* 命令`git tag`可以查看所有标签。
### 6.2 操作标签
#### 6.2.1 删除标签
如果我们的标签打错了,也是可以删除的:
```shell
$ git tag -d v1.2
Deleted tag 'v1.2' (was d0b0a0a)
```
因为创建的标签都只存储在本地,不会自动推送到远程。所以,打错的标签可以在本地安全删除。
#### 6.2.2 推送标签到远程
* 如果要推送某个标签到远程,使用命令`git push origin `:
```shell
$ git push origin v1.0
Counting objects: 15, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (15/15), 1.33 KiB | 454.00 KiB/s, done.
Total 15 (delta 2), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To gitee.com:favorite_canned_fish/test-git.git
* [new tag] v1.0 -> v1.0
```
* 或者,一次性推送全部尚未推送到远程的本地标签:
```shell
$ git push origin --tags
Total 0 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [GNK-5.0]
To gitee.com:favorite_canned_fish/test-git.git
* [new tag] v1.1 -> v1.1
```
#### 6.2.3 删除远程的标签
* 首先如果标签已经推送到远程,要删除远程标签就麻烦一点,先从本地删除:
```shell
$ git tag -d v1.1
Deleted tag 'v1.1' (was ee213a8)
```
* 然后,从远程删除。删除命令也是push,但是格式如下:
```shell
$ git push origin :refs/tags/v1.1
remote: Powered by GITEE.COM [GNK-5.0]
To gitee.com:favorite_canned_fish/test-git.git
- [deleted] v1.1
```
如果老铁们要看看是否真的从远程库删除了标签,可以登陆码云查看。这里就不带大家看啦!(可以说我懒了哈哈)
#### 6.2.4 这里小结下
- 命令`git push origin `可以推送一个本地标签;
- 命令`git push origin --tags`可以推送全部未推送过的本地标签;
- 命令`git tag -d `可以删除一个本地标签;
- 命令`git push origin :refs/tags/`可以删除一个远程标签。
## 7. 完结散花
![](https://www.icode9.com/i/ll/?i=img_convert/d3d72fefe93c0f56a86c0cc54cb37f35.png)
> 相信各位客官看到这里,相信你对Git已经初步掌握。一开始,可能觉得Git上手比较困难,没关系,多操练几次,就会越用越顺手。Git虽然极其强大,命令繁多,但常用的就那么十来个,掌握好这十几个常用命令,你已经可以得心应手地使用Git了。
这里总结下我们工作中常用的Git命令吧,如果有人跳过直接看到这里,哈哈我算你狠。是个狠人,年轻人不讲武德啊,希望你耗子尾汁。
* 首先要进入公司开发,第一步就是克隆远程版本库到本地呢
```shell
git clone url 克隆远程版本库(url即是ssh地址)
```
* 项目已经到本地了,对吧,但是你不可能在原来的分支上进行开发,所以需要新疆一个自己的分支进行项目开发。
```shell
git checkout -b dev 创建开发分支dev,并切换到该分支下
```
* 接下来,就是你在自己分支下,开发完成一定内容后,需要添加你的代码到缓存区。
```shell
git add Hello.java 把HelloWorld.java文件添加到暂存区去
```
* 添加到代码到缓存区后,就需要把在缓冲区的代码提交到仓库中。
```bash
git commit -m "新人第一次提交,希望无bug" 提交代码,并加上一些提交信息
```
* 在这过程中,你可能需要查看工作区的状态。
```shell
git status 查看当前工作区暂存区变动
```
* 提交完成后,你可以查看下你提交的日志。
```shell
git log 查看提交历史
```
* 如果你想对比一下你改了哪些内容
```shell
git diff 显示暂存区和工作区的差异
```
* 如何你觉得你的提交中,有哪些出了问题,想回退版本查看。
```shell
git reset commit_id commid_id是你从日志得到的commit_id
```
* 如果你回退版本后,想再重新回到最新版本。分为两步:
```bash
git reflog 第一步:在这当中,找到你最新提交的commit_id,复制
git reset commit_id 第二步:再次执行这段命令即可粘贴复制得到的commit_id
```
* 最后你觉得都没问题后,是不是需要推送到服务器呢
```shell
git push origin dev 将dev的所有更新全部推送到远程进行合并
```
* 如果远程的代码更新了,你的本地也是不是需要更新下代码呢
```shell
git pull 拉取远程仓库所有分支更新并合并到本地分支。
git pull origin master 将远程dev分支合并到当前本地分支
```
* 可是在合并过程中,遇到版本冲突,咋整呢
```shell
git checkout master 第一步:先切换到要合并的分支中
git merge dev 第二步:合并需要被合并的分支,然后修改冲突的地方,在提交代码即可
```
以上即是项目开发中所需要常用的命令。
> 学到这里,今天的世界打烊了,晚安!虽然这篇文章完结了,但是我还在,永不完结。我会努力保持写文章。来日方长,何惧车遥马慢!
>
> 感谢各位看到这里!愿你韶华不负,青春无悔!
注: 如果文章有任何错误和建议,请各位大佬尽情留言!也可以微信搜索太子爷哪吒公众号进行关注一波,感谢各位大佬!标签:git,一文,Git,master,shell,txt,hello,走遍
来源: https://blog.51cto.com/u_15285596/2952145