# git简介

## git的诞生

1991年 Linus开创了开源的Linux

2002年以前  Linus手动合并代码

2002年 BitKeeper免费授权

2005年 BitKeeper被尝试破解，收回授权

2005年 Linus花了两周时间用C写出了git，并且一个月之内将Linux搬迁到git上

2008年 GitHub上线

## 集中式与分布式

集中式

​	CVS、SVN

分布式

​	Git

# Git安装

git最新版本，没有安装包的话，去官方地址下载https://github.com/git-for-windows/git/releases/download/v2.23.0.windows.1/Git-2.23.0-64-bit.exe

一路next直到安装完毕，保持默认的选项

安装完毕后，打开windows的`cmd` 命令界面，输入如下命令，来设置自己的用户名和邮箱。

```bash
git config --global user.name "Aaron"
git config --global user.email "Aaron@eagleslab.com"
```

# 创建版本库

版本库又名仓库，英文名**repository** ，你可以简单的解成一个目录，这个目录里面的所有文件都可以被Git管理起来，每个文件的修改、删除，Git都能跟踪，以便任何时刻都可以追踪历史，或者在将来某个时刻可以“还原”。

**第一步** 找个空的地方创建一个文件夹

**第二步** 在文件夹中打开`cmd`界面，然后输入`git init` 命令把这个目录变成Git可以管理的仓库 

![image-20191026205132429](02.git基础/image-20191026205132429.png)

我们也可以发现，在这个文件夹中会创建一个`.git`的目录， 这个目录是Git来跟踪管理版本库的 

## 添加文件到版本库

这边要注意，git只能追踪文本文件的改动，而二进制文件只能记录大小的变化。

**第一步** 

- 在我们的git文件夹中创建一个记事本文件，不过注意，千万别用windows自带的记事本编辑内容！
- 在记事本文件中写一些内容，比如`学前沿IT，到英格科技` 
- 使用命令`git add`告诉git，把文件添加到暂存区中

```git
git add readme.txt
```

执行上面的命令，没有任何显示

**第二步**

用命令`git commit`告诉Git，把文件提交到仓库

```git
C:\Users\Aaron\Desktop\gitlearn>git commit -m "write a readme file"
[master (root-commit) 4121352] write a readme file
 1 file changed, 1 insertions(+)
 create mode 100644 readme.txt
```

`git commit`后面的`-m`是输入本次提交的说明的，可以输入任何内容，最好是自己能看懂的，这样就可以从理事会记录里面方便的找到改动记录。

执行成功后`1 file changed` 一个文件被改动了，也就是我们添加的`readme.txt`

`1 insertions(+)`表示插入了一行内容，我们写的那个`学前沿IT，到英格科技`

**注意**

因为在`commit`前需要`add`一下，所以可以一次提交多个文件

```bash
C:\Users\Aaron\Desktop\gitlearn>git add file1.txt
C:\Users\Aaron\Desktop\gitlearn>git add file2.txt
C:\Users\Aaron\Desktop\gitlearn>git commit -m "two files"
[master dc43809] two files
 2 files changed, 2 insertions(+)
 create mode 100644 file1.txt
 create mode 100644 file2.txt
```

# 回滚操作

我们修改上面的文件`readme.txt`

然后使用`git status`查看

```bash
C:\Users\Aaron\Desktop\gitlearn>git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   readme.txt

no changes added to commit (use "git add" and/or "git commit -a")
```

 `git status`命令可以让我们时刻掌握仓库当前的状态，上面的命令输出告诉我们，`readme.txt`被修改过了，但还没有准备提交的修改。 

如果想看看具体修改了什么内容，我们可以使用`git diff`来查看

![image-20191026211316876](02.git基础/image-20191026211316876.png)

可以看到在第一行后面加了一个换行符，然后加了一行在下面

下面我们将其添加，然后查看`status`

![image-20191026211507764](02.git基础/image-20191026211507764.png)

 `git status`告诉我们，将要被提交的修改包括`readme.txt`，下一步，就可以放心地提交了 

![image-20191026211724339](02.git基础/image-20191026211724339.png)

提交后，我们再用`git status`命令看看仓库的当前状态：

![image-20191026211757079](02.git基础/image-20191026211757079.png)

 Git告诉我们当前没有需要提交的修改，而且，工作目录是干净（working tree clean）的 

## 版本回退

我们创建一个文件叫`game` ，在这个文件中写入以下内容

```
屠龙勇士村来了一个新勇士叫"林克"
```

然后提交，提交的说明就是"新的开始"

![image-20191026212115336](02.git基础/image-20191026212115336.png)

在文件中追加内容

```
勇士赤手空拳来到了野外
```

然后提交，提交的说明就是"走出新手村"

![image-20191026212310506](02.git基础/image-20191026212310506.png)

在文件中追加内容

```
勇士林克被Lv1怪物史莱姆一屁股坐死
```

然后提交，提交的说明就是"阵亡"

![image-20191026212439959](02.git基础/image-20191026212439959.png)

在文件中追加内容

```
勇士林克被公主"塞尔达"所救，丢失全部金币！
```

然后提交，提交的说明就是"复活"

![image-20191026212614476](02.git基础/image-20191026212614476.png)

下面我们来看下这个悲催的勇士的经历，可以使用`git log`查看历史提交

![image-20191026212755780](02.git基础/image-20191026212755780.png)

 `git log`命令显示从最近到最远的提交日志，我们可以看到3次提交 

 如果嫌输出信息太多，看得眼花缭乱的，可以试试加上`--pretty=oneline`参数 

![image-20191026212850868](02.git基础/image-20191026212850868.png)

其实勇士的悲剧完全可以逆转，我们可以**读档**到`新的开始`，让勇士在出门前带把武器

![image-20191026213156010](02.git基础/image-20191026213156010.png)

 Git的版本回退速度非常快，因为Git在内部有个指向当前版本的`HEAD`指针，当你回退版本的时候，Git仅仅是把HEAD从指向`新的开始`： 

```ascii
┌────┐
│HEAD│
└────┘
   │
   └──> ○ 复活
        │
        ○ 阵亡
        │
        ○ 走出新手村
        │
        ○ 新的开始
```

改为指向`add distributed`：

```ascii
┌────┐
│HEAD│
└────┘
   │
   │   ○ 复活
   │   │
   │   ○ 阵亡
   │   │
   │   ○ 走出新手村
   │   │
   └──>○ 新的开始
```

 在Git中，用`HEAD`表示当前版本，上一个版本就是`HEAD^`，上上一个版本就是`HEAD^^`，当然往上100个版本写100个`^`比较容易数不过来，所以写成`HEAD~100`。 

 Git提供了一个命令`git reflog`用来记录你的每一次命令：

## 工作区和暂存区

### 工作区

就是你在电脑里能看到的目录，比如上面的`gitlearn`文件夹

![image-20191026213858444](02.git基础/image-20191026213858444.png)

### 版本库

 工作区有一个隐藏目录`.git`，这个不算工作区，而是Git的版本库。 

 Git的版本库里存了很多东西，其中最重要的就是称为stage（或者叫index）的暂存区，还有Git为我们自动创建的第一个分支`master`，以及指向`master`的一个指针叫`HEAD`。 

![image-20191026214144680](02.git基础/image-20191026214144680.png)

前面讲了我们把文件往Git版本库里添加的时候，是分两步执行的：

第一步是用`git add`把文件添加进去，实际上就是把文件修改添加到暂存区；

第二步是用`git commit`提交更改，实际上就是把暂存区的所有内容提交到当前分支。

因为我们创建Git版本库时，Git自动为我们创建了唯一一个`master`分支，所以，现在，`git commit`就是往`master`分支上提交更改。

你可以简单理解为，需要提交的文件修改通通放到暂存区，然后，一次性提交暂存区的所有修改。

## 管理修改

git不是管理文件，而是管理修改的操作。

我们修改readme.txt，然后add提交到暂存区

![image-20191026214759593](02.git基础/image-20191026214759593.png)

然后再次修改readme.txt，这次我们直接commit

![image-20191026214923112](02.git基础/image-20191026214923112.png)

然后查看状态

![image-20191026214953282](02.git基础/image-20191026214953282.png)

我们会发现第二次修改的并没有被提交

查看一下工作区的文件和仓库的文件的区别

![image-20191026215100477](02.git基础/image-20191026215100477.png)

## 撤销修改

- 当你改乱了工作区某个文件的内容，想直接丢弃工作区的修改时，用命令`git checkout -- file`。

- 当你不但改乱了工作区某个文件的内容，还添加到了暂存区时，想丢弃修改，分两步，第一步用命令`git reset HEAD `，就回到了上一种情况，第二步按上一种情况操作操作。

## 删除文件

我们将文件从工作区删除

![image-20191026220634410](02.git基础/image-20191026220634410.png)

git会直接察觉到我们的删除操作，如果这个时候提交，那么就会从版本库中删除该文件

![image-20191026220737779](02.git基础/image-20191026220737779.png)

但是如果误删除了，可以还原到版本库中的最新版本

![image-20191026220846139](02.git基础/image-20191026220846139.png)

如果要删除暂存区中的文件，可以使用`git rm`

# 远程仓库

注册github

打开命令行，创建key

![image-20191026222146813](02.git基础/image-20191026222146813.png)

打开github的个人用户设置

![image-20191026222232810](02.git基础/image-20191026222232810.png)

在里面找到`SSH and GPG keys`

![image-20191026222304248](02.git基础/image-20191026222304248.png)

新建一个ssh keys

![image-20191026222341875](02.git基础/image-20191026222341875.png)

然后按照下图提示填写

![image-20191026222527219](02.git基础/image-20191026222527219.png)

然后你就成功添加了ssh的key，下次登录github的时候，就会直接被识别出你的身份啦

![image-20191026222731981](02.git基础/image-20191026222731981.png)

## 添加远程仓库

首先回到登录之后的页面

![image-20191026222810944](02.git基础/image-20191026222810944.png)

创建你的仓库

![image-20191026222939132](02.git基础/image-20191026222939132.png)

根据这边的提示可以将我们本地的仓库和远程的仓库关联起来哦

![image-20191026223058803](02.git基础/image-20191026223058803.png)

推送成功的画面

![image-20191026223328983](02.git基础/image-20191026223328983.png)

观察到远程仓库和我们本地仓库已经关联一致

![image-20191026223401158](02.git基础/image-20191026223401158.png)

要关联一个远程库，使用命令`git remote add origin git@server-name:path/repo-name.git`；

关联后，使用命令`git push -u origin master`第一次推送master分支的所有内容；

此后，每次本地提交后，只要有必要，就可以使用命令`git push origin master`推送最新修改；

## 从远程仓库克隆

```
git clone <地址>
```

# 分支管理

## 创建与合并分支

 一开始的时候，`master`分支是一条线，Git用`master`指向最新的提交，再用`HEAD`指向`master`，就能确定当前分支，以及当前分支的提交点： 

![image-20191027123341011](02.git基础/image-20191027123341011.png)

每次提交，`master`分支都会向前移动一步，这样，随着你不断提交，`master`分支的线也越来越长。

当我们创建新的分支，例如`dev`时，Git新建了一个指针叫`dev`，指向`master`相同的提交，再把`HEAD`指向`dev`，就表示当前分支在`dev`上：

![image-20191027123400412](02.git基础/image-20191027123400412.png)

你看，Git创建一个分支很快，因为除了增加一个`dev`指针，改改`HEAD`的指向，工作区的文件都没有任何变化！

不过，从现在开始，对工作区的修改和提交就是针对`dev`分支了，比如新提交一次后，`dev`指针往前移动一步，而`master`指针不变：

![image-20191027123426946](02.git基础/image-20191027123426946.png)

 假如我们在`dev`上的工作完成了，就可以把`dev`合并到`master`上。Git怎么合并呢？最简单的方法，就是直接把`master`指向`dev`的当前提交，就完成了合并： 

![image-20191027123523590](02.git基础/image-20191027123523590.png)

合并完分支后，甚至可以删除`dev`分支。删除`dev`分支就是把`dev`指针给删掉，删掉后，我们就剩下了一条`master`分支：

![image-20191027123606591](02.git基础/image-20191027123606591.png)

 首先，我们创建`dev`分支，然后切换到`dev`分支： 

```bash
ubuntu@DESKTOP-HQ0R9B5:/mnt/c/Users/Aaron/Desktop/learngit$ git checkout -b dev
Switched to a new branch 'dev'
```

 `git checkout`命令加上`-b`参数表示创建并切换，相当于以下两条命令： 

```bash
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
```

 然后，用`git branch`命令查看当前分支： 

```bash
ubuntu@DESKTOP-HQ0R9B5:/mnt/c/Users/Aaron/Desktop/learngit$ git branch
* dev
  master
```

`git branch`命令会列出所有分支，当前分支前面会标一个`*`号。

然后，我们就可以在`dev`分支上正常提交，比如对`readme.txt`做个修改，加上一行。

然后提交

![image-20191027124746947](02.git基础/image-20191027124746947.png)

 切换回`master`分支后，再查看一个`readme.txt`文件，刚才添加的内容不见了！因为那个提交是在`dev`分支上，而`master`分支此刻的提交点并没有变 

![image-20191027124809015](02.git基础/image-20191027124809015.png)

 现在，我们把`dev`分支的工作成果合并到`master`分支上： 

![image-20191027124912044](02.git基础/image-20191027124912044.png)

`git merge`命令用于合并指定分支到当前分支。合并后，再查看`readme.txt`的内容，就可以看到，和`dev`分支的最新提交是完全一样的。

注意到上面的`Fast-forward`信息，Git告诉我们，这次合并是“快进模式”，也就是直接把`master`指向`dev`的当前提交，所以合并速度非常快。

 合并完成后，就可以放心地删除`dev`分支了： 

![image-20191027125002911](02.git基础/image-20191027125002911.png)

 删除后，查看`branch`，就只剩下`master`分支了： 

![image-20191027125038982](02.git基础/image-20191027125038982.png)

### switch

 最新版本的Git提供了新的`git switch`命令来切换分支 

 创建并切换到新的`dev`分支，可以使用： 

```
$ git switch -c dev
```

直接切换到已有的`master`分支，可以使用：

```
$ git switch master
```

使用新的`git switch`命令，比`git checkout`要更容易理解。

### 命令总结

Git鼓励大量使用分支：

查看分支：`git branch`

创建分支：`git branch `

切换分支：`git checkout `或者`git switch `

创建+切换分支：`git checkout -b `或者`git switch -c `

合并某分支到当前分支：`git merge `

删除分支：`git branch -d `

## 解决冲突

准备新的`feature1`分支，继续我们的新分支开发：

![image-20191027130450579](02.git基础/image-20191027130450579.png)

修改`readme.txt`最后一行

 在`feature1`分支上提交

![image-20191027130621731](02.git基础/image-20191027130621731.png)

切换到`master`分支，修改`readme.txt`最后一行为别的内容，然后提交

![image-20191027130755666](02.git基础/image-20191027130755666.png)

 现在，`master`分支和`feature1`分支各自都分别有新的提交，变成了这样： 

![image-20191027130814425](02.git基础/image-20191027130814425.png)

 Git无法执行“快速合并”，只能试图把各自的修改合并起来，但这种合并就可能会有冲突 

![image-20191027130908476](02.git基础/image-20191027130908476.png)

 Git告诉我们，`readme.txt`文件存在冲突，必须手动解决冲突后再提交。`git status`也可以告诉我们冲突的文件： 

![image-20191027130931402](02.git基础/image-20191027130931402.png)

 我们可以直接查看readme.txt的内容： 

![image-20191027131016245](02.git基础/image-20191027131016245.png)

 Git用`<<<<<<<`，`=======`，`>>>>>>>`标记出不同分支的内容，我们修改后保存，然后提交：

![image-20191027131149032](02.git基础/image-20191027131149032.png)

现在，`master`分支和`feature1`分支变成了下图所示：

![image-20191027131204136](02.git基础/image-20191027131204136.png)

用带参数的`git log`也可以看到分支的合并情况：

![image-20191027131309319](02.git基础/image-20191027131309319.png)

## 分支策略

 首先，`master`分支应该是非常稳定的，也就是仅用来发布新版本，平时不能在上面干活 

 所以，团队合作的分支看起来就像这样 

![image-20191027131459753](02.git基础/image-20191027131459753.png)

# 标签管理

## 创建标签

 首先，切换到需要打标签的分支上： 

![image-20191027131658489](02.git基础/image-20191027131658489.png)

 然后，敲命令`git tag `就可以打一个新标签： 

![image-20191027131722212](02.git基础/image-20191027131722212.png)

 可以用命令`git tag`查看所有标签： 

![image-20191027131746160](02.git基础/image-20191027131746160.png)

可以给历史的commit id加上标签

![image-20191027131848176](02.git基础/image-20191027131848176.png)

 注意，标签不是按时间顺序列出，而是按字母排序的。可以用`git show `查看标签信息： 

![image-20191027131920369](02.git基础/image-20191027131920369.png)

 还可以创建带有说明的标签，用`-a`指定标签名，`-m`指定说明文字： 

![image-20191027132037874](02.git基础/image-20191027132037874.png)

 用命令`git show `可以看到说明文字： 

![image-20191027132118336](02.git基础/image-20191027132118336.png)

## 操作标签

如果标签打错了，也可以删除：

![image-20191027132213310](02.git基础/image-20191027132213310.png)

因为创建的标签都只存储在本地，不会自动推送到远程。所以，打错的标签可以在本地安全删除。

如果要推送某个标签到远程，使用命令`git push origin `：

![image-20191027132413256](02.git基础/image-20191027132413256.png)

如果标签已经推送到远程，要删除远程标签就麻烦一点，先从本地删除：

![image-20191027132447546](02.git基础/image-20191027132447546.png)

然后，从远程删除。删除命令也是push，但是格式如下：

![image-20191027132513867](02.git基础/image-20191027132513867.png)

- 命令`git push origin `可以推送一个本地标签；
- 命令`git push origin --tags`可以推送全部未推送过的本地标签；
- 命令`git tag -d `可以删除一个本地标签；
- 命令`git push origin :refs/tags/`可以删除一个远程标签。

