---
title: 进阶技巧
description: Git 进阶技巧
---

# 进阶技巧

## *Git* 分支

很多版本控制系统都有分支（*Branch*）这一概念，使用分支允许开发者在不影响开发主线的情况下，独立进行新功能的开发，经过测试后再将代码合并回主线上。

为何 *Git* 能从中众多版本控制工具中脱颖而出呢？答案正是因为 *Git* 的分支处理极其高效。与 *Git* 不同，多数版本控制系统需要创建一份完整的副本来实现分支，面对大型项目时会变得非常低效，而 *Git* 无论项目大小分支创建几乎在一瞬间就能完成，这使得在开发中频繁使用分支变得十分方便。

*Git* 分支的具体原理可以参考 [Pro Git Book](https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%AE%80%E4%BB%8B)，下面将简单解释其工作原理和常用操作。

### 分支的工作原理

可以将 Git 库看作一棵树，树的根节点是初始的提交对象，每个节点代表一个提交对象。当创建一个新的分支时，就相当于在当前节点上分叉出一条新的分支，这条分支指向一个新的节点。这两个分支共享相同的历史记录，但它们可以独立地进行修改，就像是在同一棵树上的不同枝干上生长的两个树枝一样。

每次提交代码时，就相当于在当前节点上添加一个新的子节点，新的节点会指向前一个节点，形成一条链式结构。当切换分支时，就相当于从当前节点上沿着某个分支走到另一条分支上的节点上，这个过程中 Git 会自动更新 HEAD 指针的位置，使其指向新的节点。就好像是从一棵树上的一个节点走到了另一棵树上的一个节点一样。

当需要将两个分支合并时，就相当于将两个节点的修改合并到一起，生成一个新的节点，并将当前分支指向新的节点。这个过程中 Git 会使用一些算法，如三方合并算法或递归合并算法，来自动合并两个节点的修改。就好像是~~将两棵树的树枝合并在一起，形成一棵更加繁茂的树一样。~~

总的来说，Git 中的分支就像一棵树，每个节点代表一个提交对象，每个分支指向一个节点，通过对节点的复制和指针的更新，实现了分支的创建、切换和合并等基本操作。而 Git 通过这样的树形结构，记录了代码的历史，方便进行版本控制和代码管理。

![Git 分支示意图](/img/4/4/Branch.png)
[source](https://www.atlassian.com/zh/git/tutorials/using-branches)


### 创建分支

新分支的创建非常简单，只需要在 `git branch` 命令后面加上分支名即可。
```bash
git branch [branch_name]
```

此命令会在当前分支的基础上新建一个名为 `[branch-name]` 的分支,新建的分支会指向当前分支的最新提交对象，也就是目前分支的最后一次提交。 <br />
这使得新分支的历史记录和当前分支完全一致，就像在树枝分叉一样，分叉之前的部分是完全一致的。

> `git branch` 命令也可以用来查看当前仓库中的分支，不加参数时会列出所有分支，当前分支会在前面加上一个星号 `*`。

### 切换分支

新分支创建之后，*Git* 并不会自动切换到新分支上，需要手动切换到新分支上才能开始在新分支上进行开发。
```bash
git checkout [branch-name]
```

此命令会将当前分支切换到 `[branch-name]` 分支，切换分支的过程中，*Git* 会自动更新 `HEAD` 指针的位置，使其指向新分支的最新提交对象。

![](/img/4/4/git-switch-branch.gif)

> 在 *Git 2.23* 版本之后，可以使用 `git switch` 命令来切换分支，使用方法与 `git checkout` 命令相同，但目前主要使用的还是 `git checkout` 命令。 <br />


### 合并分支

需要将开发完成的分支合并到主分支上时，需要进行合并分支操作，首先介绍 `git merge` 命令。
```bash
git merge [branch-name]
```

此命令会将 `[branch-name]` 分支合并到当前分支上，可以理解为分叉的树枝又合并在了一起。 <br />
合并分支的过程中，*Git* 会自动合并两个节点的修改，然后生成一个新的提交对象，最后将当前分支指向新的提交对象。

![](/img/4/4/git-merge-branch.gif)

---

另一种合并分支的方法是 *变基（rebase）* ，即将当前分支的提交历史全部移至另一分支，就像将树枝进行嫁接一样。 <br />
```bash
git rebase [branch-name]
```

此命令会将当前分支变基到 `[branch-name]` 分支上，然后将 `[branch-name]` 分支指向当前分支的最新提交对象。 <br />
变基能使提交历史变得更加线性，以便人们更清晰的查看代码库的发展，但变基也有自身的缺点，特别是在远程仓库中使用时，可能会造成一些问题，因此在使用变基时需要谨慎。（这部分内容之后会讲解）

![](/img/4/4/git-rebase-branch.gif)

> 合并分支时建议遵循该原则： <br />
> 只对尚未推送或分享给别人的本地修改执行变基操作清理历史， 从不对已推送至别处的提交执行变基操作，这样，你才能享受到两种方式带来的便利。
> [Pro Git Book](https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%8F%98%E5%9F%BA)


## 远程仓库

在参与 *Git* 项目协作之前，还有一个关键的知识点需要掌握，那就是远程仓库。 <br />
远程仓库是指托管在互联网或者其他网络中的 *Git* 代码仓库，它是实现代码共享和协作的重要工具。通过 *Git* 命令，我们可以与远程仓库进行交互，例如推送代码、拉取代码、合并代码等操作。这样，我们就可以方便地与其他开发者进行协作，共同推进项目的进展。

### 添加远程仓库

在[基础操作](./basic-concept)中，我们提到了可以通过克隆（`git clone`）的方式来创建 *Git* 仓库，克隆的过程中，*Git* 会自动将远程仓库的地址添加到本地仓库中（将远程仓库与本地仓库相关联）。 <br />
如需将新建的本地仓库与远程仓库进行关联，可以使用以下命令。 <br />
```bash
git remote add [remote-name] [remote-url]
```

此命令会将位于 `[remote-url]` 的远程仓库与本地仓库相关联，并将其命名为 `[remote-name]`，这样就可以通过 `[remote-name]` 来指代位于 `[remote-url]` 的远程仓库了。 <br />
例如，我们可以将 ` Shift2Modern ` 位于 [*GitHub*](https://github.com/BPCClub/Shift2Modern.git) 上的远程仓库与本地仓库相关联，并将其命名为 `gh-origin`。 <br />
` git remote add gh-origin https://github.com/BPCClub/Shift2Modern.git `

> 需要注意的是，必须保证远程仓库的可访问性，否则关联后无法与远程仓库进行交互。 <br />
> 常见的远程仓库托管平台有 [*GitHub*](https://github.com)、[*GitLab*](https://gitlab.com)、[*Gitee*](https://gitee.com) 等，这些平台提供有限程度的免费仓库托管服务，可以方便地创建远程仓库。 <br />
> 此外，我们专门写了 [*GitHub*](/guide/github/introduction) 的入门教程，供读者参考。


### 查看与重命名远程仓库

如需查看当前本地仓库关联的远程仓库，可以使用以下命令。 <br />
```bash
git remote
```

执行此命令，会列出所有远程仓库的名称，即上节的 `[remote-name]`。 <br />
如需进一步查看详细信息，请在此基础上添加 `-v` 参数，即`git remote -v`。
此命令会列出所有远程仓库的名称和远程仓库的 *推送* 和 *拉取* 地址。
> 如需查看指定远程仓库的详细信息，请使用如下命令。 <br />
> `git remote show [remote-name]`

觉得远程仓库的名称不太好听？*Git* 贴心地提供了重命名的功能。 <br />
```bash
git remote rename [old-name] [new-name]
```

此命令会将 `[old-name]` 远程仓库的名称修改为 `[new-name]`。 <br />


### 推送到远程仓库

当本地仓库的更改提交后，需要将这些更改推送至远程仓库，以保持两者之间的同步。 <br />
```bash
git push [remote-name] [branch-name]
```

此命令会将 `[branch-name]` 分支中已提交的更改推送至名为 `[remote-name]` 的远程仓库中。 <br />

> 必须拥有对远程仓库的写入权限，并且在推送前没有其他人向该远程服务器推送过更改时才能成功执行。如果其他人已经向远程服务器推送了更改，需要先 *拉取* 更新 并解决可能存在的 *冲突* ，然后才能推送更改。


### 抓取与拉取

远程仓库存在的意义之一就是为了协作，因此，我们需要从远程仓库中获取其他人提交的更改，以便在本地仓库中进行查看和修改。 <br />
常见的获取远程仓库更改的方式有两种，分别是 *抓取（fetch）* 和 *拉取（pull）* 。 <br />

```bash
git fetch [remote-name]
```

此命令会将 `[remote-name]` 远程仓库中 **所有分支** 的最新提交从远程仓库中拉取到本地仓库中，但不会自动合并更改，需要手动将其合并。 <br />

![](/img/4/4/git-fetch.gif)

---

由于 `fetch` 不能自动进行合并，为了简化操作，*Git* 提供了 *拉取（pull）* 命令。相当于 `git fetch` 和 `git merge` 的组合。
```bash
git pull [remote-name] [branch-name]
```

此命令会将 `[branch-name]` 分支中的最新提交从 `[remote-name]` 远程仓库中拉取到本地仓库中，并自动合并更改。 <br />
省略其他参数，直接使用 `git pull`，会默认对当前分支进行拉取和合并操作。

![](/img/4/4/git-pull.gif)

> 关于合并过程中可能出现的 *冲突* ，请参考“[遇到冲突时的分支合并](https://git-scm.com/book/zh/v2/Git-%E5%88%86%E6%94%AF-%E5%88%86%E6%94%AF%E7%9A%84%E6%96%B0%E5%BB%BA%E4%B8%8E%E5%90%88%E5%B9%B6#_basic_merge_conflicts)”。


### 删除远程仓库

如果不再需要某个远程仓库，可以使用以下命令将其删除。 <br />
```bash
git remote remove [remote-name]
```

此命令会将 `[remote-name]` 远程仓库从本地仓库中删除（与该远程仓库相关的分支和配置文件都将一并删除）。 <br />

## 写在最后

本教程只是对 *Git* 的基本使用进行了简单的介绍，在日常的使用中，这些知识不能完全满足需求，还需要读者进一步学习相关知识。 <br />
我们收集了一些 *Git* 的学习资料，整合到了 [参考手册](./git-reference) 板块中，供读者参考。 <br />

### Git GUI

*Git* 本身是一个命令行工具，但是也有很多第三方图形化界面的工具，可以省去记忆命令的麻烦，方便地进行操作。 <br />
以下是一些常见的 *Git* 图形化界面工具： <br />
- [*Git GUI*](https://git-scm.com/docs/git-gui) <br />
  *Git* 官方出品的图形化界面工具，界面比较简洁，功能也比较简单。

- [*GitKraken*](https://www.gitkraken.com) <br />
  非常推荐！不仅界面美观，功能也很丰富，但是部分高级功能需要付费。

- [*GitHub Desktop*](https://desktop.github.com) <br />
  *GitHub* 官方出品的图形化界面工具，界面简洁，基本功能完善，还为 *GitHub* 做了相应的功能适配。 <br />
  我们也编写了相关的教程，可以在 [这里](/guide/github/desktop) 查看。

- [*Sourcetree*](https://www.sourcetreeapp.com) <br />
  界面美观和简洁程度介于 *GitKraken* 和 *Git GUI* 之间，功能也比较完善,主要用于 *MacOS* 平台，*Windows* 平台也有相应的版本。
