---
title: Git使用教学
date: 2026-07-08 10:00:00
tags: [编程, 教程]
categories: 教程
cover: /images/Git使用教学/github.png
description: 认识一下Git！
---

## 认识一下Git！—简介

Git是当前最先进、最主流的**分布式**版本控制系统，免费、开源！核心能力就是版本控制。再具体一点，就是面向代码文件的版本控制，代码的任何修改历史都会被记录管理起来，意味着可以恢复到到以前的任意时刻状态。支持跨区域多人协作编辑，是团队项目开发的必备基础，所以Git也就成了程序员的必备技能。

![image](/images/Git使用教学/bff72050dee7400db8045109f8ca6119.webp)

**🟢主要特点**：

![image.png](/images/Git使用教学/05512798305d4a20a38fd3b89be2b6e0.webp)

Git是Linux之父被迫开发的，为了解决Linux混乱的代码管理而开发的。Linux和Git之父 **李纳斯·托沃兹**（Linus Benedic Torvalds），来自1969年的芬兰。

---

## Git是干什么的？—基础概念

先了解下Git的基本概念，及基本框架、工作流程。

### Git概念汇总🔥

| **概念名称** | **描述** |
| --- | --- |
| **工作区**（Workspace） | 就是在电脑里能看到的代码库目录，是我们搬砖的地方，新增、修改的文件会提交到暂存区 |
| **暂存区**（stage 或 index） | 用于临时存放文件的修改，实际上上它只是一个文件（`.git/index`），保存待提交的文件列表信息。 |
| **版本库/仓库**（Repository） | Git的管理仓库，管理版本的数据库，记录文件/目录状态的地方，所有内容的修改记录（版本）都在这里。 |
| **服务端/远程仓库**（origin 或 remote） | 服务端的版本库，专用的Git服务器，为多人共享提供服务，承担中心服务器的角色。本地版本库通过push指令把代码推送到服务端版本库。 |
| **本地仓库** | 用户机器上直接使用的的的版本库 |
| **分支**（Branch） | 分支是从主线分离出去的“副本”，可以独立操作而互不干扰，仓库初始化就有一个默认主分支`master`。 |
| **头**（HEAD） | HEAD类似一个“指针”，指向当前活动 **分支** 的 **最新版本**。 |
| **提交**（Commit） | 把暂存区的所有变更的内容提交到当前仓库的活动分支。 |
| **推送**（Push） | 将本地仓库的版本推送到服务端（远程）仓库，与他人共享。 |
| **拉取**（Pull） | 从服务端（远程）仓库获取更新到本地仓库，获取他人共享的更新。 |
| **获取**（Fetch） | 从服务端（远程）仓库更新，作用同拉取（Pull），区别是不会自动合并。 |
| **冲突**（Conflict） | 多人对同一文件的工作副本进行更改，并将这些更改合并到仓库时就会面临冲突，需要人工合并处理。 |
| **合并**（Merge） | 对有冲突的文件进行合并操作，Git会自动合并变更内容，无法自动处理的冲突内容会提示人工处理。 |
| **标签**（Tags） | 标签指的是某个分支某个特定时间点的状态，可以理解为提交记录的别名，常用来标记版本。 |
| **master**（或main） | 仓库的“`master`”分支，默认的主分支，初始化仓库就有了。Github上创建的仓库默认名字为“`main`” |
| **origin/master** | 表示远程仓库（`origin`）的“`master`”分支 |
| **origin/HEAD** | 表示远程仓库（`origin`）的最新提交的位置，一般情况等于“`origin/master`” |

### 工作区/暂存区/仓库

![image.png](/images/Git使用教学/091ebdafedb746d98b5340fa107c8d1b.webp)

工作区、暂存区、版本库是Git最基本的概念，关系如下图：

![image](/images/Git使用教学/c97776f4f3b64b1e979dde63e634847e.webp)

**🔸工作区**（Workspace）就是在电脑里能看到的代码库目录，是我们搬砖的地方，新增、修改的文件会提交到暂存区。

**🔸暂存区**（stage或index） 用于临时存放文件的修改，实际上上它只是一个文件（.git/index），保存待提交的文件列表信息。

**🔸版本库/仓库**（Repository /rɪˈpɑːzətɔːri/ 仓库）Git的管理仓库，管理版本的数据库，记录文件/目录状态的地方，所有内容的修改记录（版本）都在这里。就是工作区目录下的隐藏文件夹`.git`，包含暂存区、分支、历史记录等信息。

![image](/images/Git使用教学/4b886c1cb4844010a76409ff36beb458.webp)

如上图，为对应本地仓库目录的结构关系。

### Git基本流程（图）

Git的工作流程核心就下面几个步骤，掌握了就可以开始写Bug了。

![image](/images/Git使用教学/666f0c95490744bbafe060f5d44c22b0.webp)

### Git状态（图）

Git在执行提交的时候，不是直接将工作区的修改保存到仓库，而是将暂存区域的修改保存到仓库。要提交文件，首先需要把文件加入到暂存区域中。因此，Git管理的文件有三（+2）种状态：

![image](/images/Git使用教学/f78be8de167c4fcc89b814ffda2e92f7.webp)

---

## 起步：Git安装配置

Git官网：[www.git-scm.com/](https://www.git-scm.com/ "https://www.git-scm.com/") 下载安装包进行安装。Git的使用有两种方式：

指令`git --version`查看安装版本号

```git
$ git --version
git version 2.33.0.windows.2
```

> 📢 本文是在Windows 平台上完成的，不过这个对学习Git没什么影响。

### Git的配置文件

Git有三个主要的配置文件：三个配置文件的优先级是**① < ② < ③**

![image](/images/Git使用教学/31c076f9d4b64b4eadba339a8113559c.webp)

```bash
#查看git配置
git config --list
git config -l

#查看系统配置
git config --system --list

#查看当前用户（global）全局配置
git config --list --global

#查看当前仓库配置信息
git config --local  --list
```

仓库的配置是上面多个配置的集合：

```bash
$ git config --list
$ git config -l
diff.astextplain.textconv=astextplain
http.sslbackend=openssl
http.sslcainfo=C:/Program Files/Git/mingw64/ssl/certs/ca-bundle.crt
core.autocrlf=true
core.fscache=true
core.symlinks=false
pull.rebase=false
credential.helper=manager-core
credential.https://dev.azure.com.usehttppath=true
init.defaultbranch=master
user.name=Kanding
user.email=123anding@163.com
```

### 3.2、配置-初始化用户

当安装Git后首先要做的事情是配置你的用户信息—— 告诉Git你是谁？配置 **用户名**、**邮箱地址**，每次提交文件时都会带上这个用户信息，查看历史记录时就知道是谁干的了。

配置用户信息：

```bash
$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
# 配置完后，看看用户配置文件：
$ cat 'C:\Users\Kwongad\.gitconfig'
[user]
        name = Kanding
        email = 123anding@163.com
```

### 配置-忽略.gitignore

工作目录中的文件并不是全都需要纳入版本管理，如日志、临时文件、私有配置文件等不需要也不能纳入版本管理，那该怎么办呢？

![image.png](/images/Git使用教学/042a124fa2fe41b1b64791ffb2c192ec.webp)

在工作区根目录下创建“`.gitignore`”文件，文件中配置不需要进行版本管理的文件、文件夹。“`.gitignore`”文件本身是被纳入版本管理的，可以共享。有如下规则：

```bash
#为注释
*.txt #忽略所有“.txt”结尾的文件
!lib.txt #lib.txt除外
/temp #仅忽略项目根目录下的temp文件,不包括其它目录下的temp，如不包括“src/temp”
build/ #忽略build/目录下的所有文件
doc/*.txt #会忽略 doc/notes.txt 但不包括 doc/server/arch.txt
```

> 📢 各种语言项目的常用`.gitignore`文件配置：[github.com/github/giti…](https://github.com/github/gitignore "https://github.com/github/gitignore")

---

## Git的GUI工具们

如果不想用命令行工具，完全可以安装一个Git的GUI工具，用的更简单、更舒服。不用记那么多命令了，极易上手，不过Git基础还是需要学习了解一下的。

> ❓对于Git，建议用命令行，这样你才能真的理解Git的思想？ ✔️其实都只是工具而已，适合自己的就是最好的，没必要纠结，多写点Bug更重要！

### SourceTree

SourceTree的[官网](https://www.sourcetreeapp.com/ "https://www.sourcetreeapp.com/") 下载安装包，支持Window、Mac系统，按照提示完成安装。

![image.png](/images/Git使用教学/938e73a171c745efa3f279d98f60dd79.webp)

![image](/images/Git使用教学/1384a781a80042faaf754c0de52b8d6d.webp)

### TortoiseGit

![image.png](/images/Git使用教学/8165d603319245278464511ad2cc71cb.webp)

[TortoiseGit](https://tortoisegit.org/ "https://tortoisegit.org/") 官网下载安装包，及中文语言包，按照提示完成安装。小乌龟的Git是集成到操作系统里的，直接右键文件夹就可以进行Git操作了。

![image.png](/images/Git使用教学/dca67d3a374d41e0bbae7b5d66c8b0e1.webp)

![image.png](/images/Git使用教学/5d4597b570744fbda1cf1879103a0511.webp)

![image.png](/images/Git使用教学/7494f0ff8dae4e1c8029fc1640938229.webp)

### VSCode中的Git

VSCode自带的Git工具基本已经可以满足日常使用了，既有可视化功能，也能敲命令，习惯了不就不用安装其他GUI工具了。不过还是可以再安装一些VSCode插件，来增强Git功能。

![image.png](/images/Git使用教学/2c02bbef03d445b6b3c3e5ce64ae632b.webp)

---

## Git使用入门

### 创建仓库

创建本地仓库的方法有两种：

```bash
# 准备一个文件夹“KwebNote”作为仓库目录，命令行进入该文件夹
Kwongad@Kwongad-T14 MINGW64 ~
$ cd d:
Kwongad@Kwongad-T14 MINGW64 /d
$ cd Project_Files
Kwongad@Kwongad-T14 MINGW64 /d/Project_Files
# 多次cd指令进入到仓库目录KwebNote：“cd <目录名称>”指令进入目录，“cd ..”返回上级目录（有空格）
Kwongad@Kwongad-T14 MINGW64 /d/Project_Files/github.kwong/KwebNote

# 开始初始化项目，也可指定目录：git init [文件目录]
$ git init
Initialized empty Git repository in D:/Project_Files/github.Kwong/KwebNote/.git/
```

> **📢注意**：Git指令的执行，都需在仓库目录下。

创建完多出了一个被隐藏的`.git`目录，这就是本地仓库Git的工作场所。

![image.png](/images/Git使用教学/183d6f2e27c34fe8a1dad03d190ad108.webp)

克隆远程仓库，如在github上创建的仓库“`https://github.com/kwonganding/KWebNote.git`”

```bash
$ git clone 'https://github.com/kwonganding/KWebNote.git'
Cloning into 'KWebNote'...
remote: Enumerating objects: 108, done.
remote: Counting objects: 100% (108/108), done.
remote: Compressing objects: 100% (60/60), done.
remote: Total 108 (delta 48), reused 88 (delta 34), pack-reused 0
Receiving objects: 100% (108/108), 9.36 KiB | 736.00 KiB/s, done.
Resolving deltas: 100% (48/48), done.
```

会在当前目录下创建“KWebNote”项目目录。

![image.png](/images/Git使用教学/377739fca6a740819ce778c705e9cd22.webp)

### 暂存区add

可以简单理解为，`git add`命令就是把要提交的所有修改放到暂存区（Stage），然后，执行`git commit`就可以一次性把暂存区的所有修改提交到仓库。

| **指令** | **描述** |
| --- | --- |
| git add \[file1\] \[file2\] | 添加文件到暂存区，包括修改的文件、新增的文件 |
| git add \[dir\] | 同上，添加目录到暂存区，包括子目录 |
| git add . | 同上，添加**所有**修改、新增文件（未跟踪）到暂存区 |
| git rm \[file\] | 删除工作区文件，并且将这次删除放入暂存区 |

```bash
# 添加指定文件到暂存区，包括被修改的文件
$ git add [file1] [file2] ...

# 添加当前目录的所有文件到暂存区
$ git add .

# 删除工作区文件，并且将这次删除放入暂存区
$ git rm [file1] [file2] ...

# 改名文件，并且将这个改名放入暂存区
$ git mv [file-original] [file-renamed]
```

修改文件“R.md”，未暂存：

![image.png](/images/Git使用教学/69ca93a857ca45769ae56f0492f1d9c6.webp)

执行`git add .`暂存：

![image.png](/images/Git使用教学/7c5ebb71c0bb4bec9a5fa9284c70e8f7.webp)

### 提交commit-记录

`git commit`提交是以时间顺序排列被保存到数据库中的，就如游戏关卡一样，每一次提交（commit）就会产生一条记录：`id + 描述 + 快照内容`。

多个提交就形成了一条时间线，每次提交完，会移动当前分支`master`、`HEAD`的“指针”位置。

![image](/images/Git使用教学/9a3233f335334f30902d0255ff1a6477.webp)

Sourcetree上的历史记录：

![image.png](/images/Git使用教学/77c51e33545943dcb2833872ca9fa798.webp)

> 📢一般情况，每完成一个小功能、一个Bu就可以提交一次，这样会形成比较清晰的历史记录。

**🔥指令：**

| **指令** | **描述** |
| --- | --- |
| git commit -m '说明' | 提交变更，参数`-m`设置提交的描述信息，应该正确提交，不带该参数会进入说明编辑模式 |
| git commit -a | 参数`-a`，表示直接从工作区提交到版本库，略过了`git add`步骤，不包括新增的文件 |
| git commit \[file\] | 提交暂存区的指定文件到仓库区 |
| git commit --amend -m | 使用一次新的`commit`，替代上一次提交，会修改`commit`的`hash`值（id） |
| git log -n20 | 查看日志(最近20条)，不带参数`-n`则显示所有日志 |
| git log -n20 --oneline | 参数“`--oneline`”可以让日志输出更简洁（一行） |
| git log -n20 --graph | 参数“`--graph`”可视化显示分支关系 |
| git log --follow \[file\] | 显示某个文件的版本历史 |
| git blame \[file\] | 以列表形式显示指定文件的修改记录 |
| git **reflog** | 查看所有可用的历史版本记录（实际是HEAD变更记录），包含被回退的记录（**重要**） |
| git status | 查看本地仓库状态，比较常用的指令，加参数`-s`简洁模式 |

![image](/images/Git使用教学/14f7d7d18ad84b71b547456b5f5d35e1.webp)

通过`git log`指令可以查看提交记录日志，可以很方便的查看每次提交修改了哪些文件，改了哪些内容，从而进行恢复等操作。

```bash
# 提交暂存区到仓库区
$ git commit -m [message]
# 提交所有修改到仓库
$ git commit -a -m'修改README的版权信息'

# 提交暂存区的指定文件到仓库区
$ git commit [file1] [file2] ... -m [message]

# 使用一次新的commit，替代上一次提交
# 如果代码没有任何新变化，则用来改写上一次commit的提交信息
$ git commit --amend -m [message]

$ git log -n2
commit 412b56448568ff362ef312507e78797befcf2846 (HEAD -> main)
Author: Kanding <123anding@163.com>
Date:   Thu Dec 1 19:02:22 2022 +0800

commit c0ef58e3738f7d54545d8c13d603cddeee328fcb
Author: Kanding <123anding@163.com>
Date:   Thu Dec 1 16:52:56 2022 +0800

# 用参数“--oneline”可以让日志输出更简洁（一行）
$ git log -n2 --oneline
5444126 (HEAD -> main, origin/main, origin/HEAD) Update README.md
228362e Merge branch 'main' of github.com:kwonganding/KWebNote
```

### Git的“指针”引用们

Git中最重要的就是提交记录了，其他如**标签**、**分支**、**HEAD** 都对提交记录的“指针”引用，指向这些提交记录，理解这一点很重要。

![image](/images/Git使用教学/13cd7d7c62e0424c8e900754e6494a76.webp)

上图中：

打开这些文件内容看看，就更容易理解这些“指针”的真面目了。

```bash
# tag
$ git tag -a 'v1' -m'v1版本'
$ cat .git/refs/tags/v1
a2e2c9caea35e176cf61e96ad9d5a929cfb82461

# main分支指向最新的提交
$ cat .git/refs/heads/main
8f4244550c2b6c23a543b741c362b13768442090

# HEAD指向当前活动分支
$ cat .git/HEAD
ref: refs/heads/main

# 切换到dev分支，HEAD指向了dev
$ git switch dev
Switched to branch 'dev'
$ cat .git/HEAD
ref: refs/heads/dev
```

这里的主分支名字为“`main`”，是因为该仓库是从Github上克隆的，Github上创建的仓库默认主分支名字就是“`main`”，本地创建的仓库默认主分支名字为“`master`”。

> **📢“指针”引用**：之所以用引号的“指针”，是为了便于统一和理解。和指针原理类似，都是一个指向，只是实际上可能更复杂一点，且不同的“指针”引用会有区别。

### 提交的唯一标识id，HEAD~n是什么意思？

每一个提交都有一个唯一标识，主要就是提交的`hash`值`commit id`，在很多指令中会用到，如版本回退、拣选提交等，需要指定一个提交。那标识唯一提交有两种方式：

通过`git log`、`git reflog`可以查看历史日志，可以看每次提交的唯一编号（hash）。区别是`git reflog`可以查看所有操作的记录（实际是HEAD变更记录），包括被撤销回退的提交记录。

```bash
$ git reflog -n10
5acc914 (HEAD -> main) HEAD@{0}: reset: moving to HEAD~
738748b (dev) HEAD@{1}: reset: moving to HEAD~
9312c3e HEAD@{2}: reset: moving to HEAD~
db03fcb HEAD@{3}: reset: moving to HEAD~
1b81fb3 HEAD@{4}: reset: moving to HEAD~
41ea423 HEAD@{5}: reset: moving to HEAD~
d3e15f9 HEAD@{6}: reset: moving to d3e15f9
1b81fb3 HEAD@{7}: reset: moving to HEAD~1
41ea423 HEAD@{8}: reset: moving to HEAD~
d3e15f9 HEAD@{9}: reset: moving to HEAD~
```

### 比较diff

`git diff`用来比较不同文件版本之间的差异。

| **指令** | **描述** |
| --- | --- |
| **git diff** | 查看暂存区和工作区的差异 |
| git diff \[file\] | 同上，指定文件 |
| git diff --cached | 查看已暂存的改动，就是暂存区与新版本`HEAD`进行比较 |
| git diff --staged | 同上 |
| git diff --cached \[file\] | 同上，指定文件 |
| git diff HEAD | 查看已暂存的+未暂存的所有改动，就是与最新版本`HEAD`进行比较 |
| git diff HEAD~ | 同上，与上一个版本比较。`HEAD~`表示上一个版本，`HEAD~10`为最近第10个版本 |
| git diff \[id\] \[id\] | 查看两次提交之间的差异 |
| git diff \[branch\] | 查看工作区和分支直接的差异 |

☘️画个图更清晰些：

![image](/images/Git使用教学/565ccedcf6664d6598178327bdf6f75a.webp)

```bash
# 查看文件的修改
$ git diff README.md

# 查看两次提交的差异
$ git diff 8f4244 1da22

# 显示今天你写了多少行代码：工作区+暂存区
$ git diff --shortstat "@{0 day ago}"
```

---

## 远程仓库

Git作为分布式的版本管理系统，每个终端都有自己的Git仓库。但团队协作还需一个中间仓库，作为中心，同步各个仓库。于是服务端（远程）仓库就来承担这个职责，服务端不仅有仓库，还配套相关管理功能。

![image](/images/Git使用教学/7566420b76e34e489e59871a4a0da9b3.webp)

可以用公共的Git服务器，也可以自己搭建一套Git服务器。

### 远程用户登录

Git服务器一般提供两种登录验证方式：

![image.png](/images/Git使用教学/2c1cc6ce8c34485989cf0b823f00d124.webp)

```bash
#查看当前远程仓库使用的那种协议连接：
$ git remote -v
origin  git@github.com:kwonganding/KWebNote.git (fetch)
origin  https://github.com/kwonganding/KWebNote.git (push)

# 更改为https地址，即可切换连接模式。还需要禁用掉SSL, 才能正常使用https管理git
git config --global http.sslVerify false
```

### 🔑远程用户登录：HTTS

基于HTTPS的地址连接远程仓库，Github的共有仓库克隆、拉取（pull）是不需要验证的。

![image.png](/images/Git使用教学/c57d5111fdb54323ba6fd64626f84435.webp)

```bash
$ git clone 'https://github.com/kwonganding/KWebNote.git'
Cloning into 'KWebNote'...

# 仓库配置文件“.git/config”
[remote "origin"]
 	url = https://github.com/kwonganding/KWebNote.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	pushurl = https://github.com/kwonganding/KWebNote.git
```

推送（push）代码的时候就会提示输入用户名、密码了，否则无法提交。记住用户密码的方式有两种：

```bash
# 直接修改仓库的配置文件“.git/config”
[remote "origin"]
 	url = https://用户名:密码@github.com/kwonganding/KWebNote.git
	fetch = +refs/heads/*:refs/remotes/origin/*
	pushurl = https://github.com/kwonganding/KWebNote.git
```

```bash
# 参数“--global”全局有效，也可以针对仓库设置“--local”
# store 表示永久存储，也可以设置临时存储
git config --global credential.helper store

# 存储内容如下，打开文件“仓库\.git\.git-credentials”
https://kwonganding:[加密内容付费可见]@github.com
```

### 🔑远程用户登录：SSH

![image.png](/images/Git使用教学/73179047882b469cb72949d3a0537be7.webp)

SSH（Secure Shell，安全外壳）是一种网络安全协议，通过加密和认证机制实现安全的访问和文件传输等业务，多用来进行远程登录、数据传输。SSH通过公钥、私钥非对称加密数据，所以SSH需要生成一个公私钥对，公钥放服务器上，私有自己留着进行认证。

![](/images/Git使用教学/b4c7bed1fa8e478aa68aefa3b5e94ff2.webp)

**① 生成公私钥**：通过Git指令`ssh-keygen -t rsa`生成公私钥，一路回车即可完成。生成在“`C:\Users\用户名\.ssh`”目录下，文件`id_rsa.pub`的内容就是公钥。

![image.png](/images/Git使用教学/ea24f85292134500a84374062f7ee6a4.webp)

**② 配置公钥**：打开`id_rsa.pub`文件，复制内容。Github上，打开`Setting`➤ `SSH and GPG keys` ➤ `SSH keys` ➤ 按钮`New SSH key`，标题（Title）随意，秘钥内容粘贴进去即可。

![image.png](/images/Git使用教学/0e03cbbc0ee74dad80dd4855e412cba0.webp)

SSH配置完后，可用`ssh -T git@github.com`来检测是否连接成功。

```bash
$ ssh -T git@github.com
Hi kwonganding! You've successfully authenticated, but GitHub does not provide shell access.
```

### 远程仓库指令🔥

| **指令** | **描述** |
| --- | --- |
| git clone \[git地址\] | 从远程仓库克隆到本地（当前目录） |
| git remote -v | 查看所有远程仓库，不带参数`-v`只显示名称 |
| git remote show \[remote\] | 显示某个远程仓库的信息 |
| git remote add \[name\] \[url\] | 增加一个新的远程仓库，并命名 |
| git remote rename \[old\] \[new\] | 修改远程仓库名称 |
| **git pull \[remote\] \[branch\]** | 取回远程仓库的变化，并与本地版本合并 |
| **git pull** | 同上，针对当前分支 |
| git fetch \[remote\] | 获取远程仓库的所有变动到本地仓库，不会自动合并！需要手动合并 |
| **git push** | 推送当前分支到远程仓库 |
| git push \[remote\] \[branch\] | 推送本地当前分支到远程仓库的指定分支 |
| git push \[remote\] --force/-f | 强行推送当前分支到远程仓库，即使有冲突，⚠️很危险！ |
| git push \[remote\] --all | 推送所有分支到远程仓库 |
| git push –u | 参数`–u`表示与远程分支建立关联，第一次执行的时候用，后面就不需要了 |
| git remote rm \[remote-name\] | 删除远程仓库 |
| git pull --rebase | 使用rebase的模式进行合并 |

### 推送push/拉取pull

`git push`、`git pull`是团队协作中最常用的指令，用于同步本地、服务端的更新，与他人协作。

**🔸推送**（push）：推送本地仓库到远程仓库。

**🔸拉取**（pull）：从服务端（远程）仓库更新到本地仓库。

![image](/images/Git使用教学/365395e73a254ef6abfd5a99e78992b0.webp)

### fetch与pull有什么不同？

两者都是从服务端获取更新，主要区别是`fetch`不会自动合并，不会影响当前工作区内容。

> `git pull` = `git fetch` + `git merge`

![image](/images/Git使用教学/abf7648ba0314c3f9f5ad30c4e1e20c4.webp)

```bash
# fetch只更新版本库
$ git fetch
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 663 bytes | 44.00 KiB/s, done.
From github.com:kwonganding/KWebNote
   2ba12ca..c64f5b5  main       -> origin/main

# 执行合并，合并自己
$ git merge
Updating 2ba12ca..c64f5b5
Fast-forward
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
```

---

## Git利器-分支

分支是从主线分离出去的“副本”，分支就像是平行宇宙，可独立发展，独立编辑、提交，也可以和其他分支合并。分支是Git的核心必杀利器之一，分支创建、切换、删除都非常快，他非常的轻量。所以，早建分支！多用分支！

![image.png](/images/Git使用教学/4b9ab3c40bcf47858c8ee108ab35ff91.webp)

### 分支Branch

比如有一个项目团队，准备10月份发布新版本，要新开发一堆黑科技功能，占领市场。你和小伙伴“小美”一起负责开发一个新功能A，开发周期2周，在这两周你们的代码不能影响其他人，不影响主分支。这个时候就可以为这个新功能创建一个分支，你们两在这个分支上干活，2周后代码开发完了、测试通过，就可以合并进要发版的开发分支了。安全、高效，不影响其他人工作，完美！

![image](/images/Git使用教学/c58ef8e5f5334ee6909d9c9a55298e71.webp)

在实际项目中，一般会建几个主线分支。

**分支**就是指向某一个提交记录的“指针”引用，因此创建分支是非常快的，不管仓库多大。当我们运行`git branch dev`创建了一个名字为`dev`的分支，Git实际上是在`.git\refs\heads`下创建一个`dev`的引用文件（没有扩展名）。

```bash
$ git branch dev
$ cat .git/refs/heads/dev
ca88989e7c286fb4ba56785c2cd8727ea1a07b97
```

### 分支指令🔥

| **指令** | **描述** |
| --- | --- |
| git branch | 列出所有本地分支，加参数`-v`显示详细列表，下同 |
| git branch -r | 列出所有远程分支 |
| git branch -a | 列出所有本地分支和远程分支，用不同颜色区分 |
| git branch \[branch-name\] | 新建一个分支，但依然停留在当前分支 |
| git branch -d dev | 删除`dev`分支，-D（大写）强制删除 |
| **git checkout -b** dev | 从当前分支创建并切换到`dev`分支 |
| git checkout **\-b** feature1 dev | 从本地`dev`分支代码创建一个 `feature1`分支，并切换到新分支 |
| git branch \[branch\] \[commit\] | 新建一个分支，指向指定`commit id` |
| git branch --track \[branch\] \[remote-branch\] | 新建一个分支，与指定的远程分支建立关联 |
| git checkout -b hotfix remote hotfix | 从远端`remote`的`hotfix`分支创建本地`hotfix`分支 |
| git branch --set-upstream \[branch\] \[remote-branch\] | 在现有分支与指定的远程分支之间建立跟踪关联：  
`git branch --set-upstream hotfix remote/hotfix` |
| **git checkout** \[branch-name\] | 切换到指定分支，并更新工作区 |
| git checkout . | 撤销工作区的（未暂存）修改，把暂存区恢复到工作区。 |
| git checkout HEAD . | 撤销工作区、暂存区的修改，用`HEAD`指向的当前分支最新版本替换 |
| git merge \[branch\] | 合并指定分支到当前分支 |
| **git merge --no-ff** dev | 合并`dev`分支到当前分支，参数`--no-ff`禁用快速合并模式 |
| git push origin --delete \[branch-name\] | 删除远程分支 |
| git rebase master | 将当前分支变基合并到`master`分支 |
| ✅switch：新的分支切换指令 | 切换功能和`checkout`一样，`switch`只单纯的用于切换 |
| git switch master | 切换到已有的`master`分支 |
| git switch -c dev | 创建并切换到新的`dev`分支 |

> 📢**关于 `checkout` 指令**：`checkout`是Git的底层指令，比较常用，也比较危险，他会重写工作区。支持的功能比较多，能撤销修改，能切换分支，这也导致了这个指令比较复杂。在Git 2.23版本以后，增加了`git switch`、`git reset`指令。

```bash
$ git branch
  dev
* main
# 列出了当前的所有分支，星号“*”开头的“main”为当前活动分支。
```

### 分支的切换checkout

代码仓库可以有多个分支，`master`为默认的主分支，但只有一个分支在工作状态。所以要操作不同分支，需要切换到该分支，`HEAD`就是指向当前正在活动的分支。

![image](/images/Git使用教学/477ba6ccb6224187a72331fbc2cd3b7a.webp)

```bash
# 切换到dev分支，HEAD指向了dev
# 此处 switch 作用同 checkout，switch只用于切换，不像checkout功能很多
$ git switch dev
Switched to branch 'dev'
$ cat .git/HEAD
ref: refs/heads/dev
```

使用 `git checkout dev`切换分支时，干了两件事：

此时的活动分支就是`dev`了，后续的提交就会更新到`dev`分支了。

**❓切换时还没提交的代码怎么办？**

### 合并merge&冲突

把两个分支的修改内容合并到一起，常用的合并指令`git merge [branch]`，将分支`[branch]`合并到当前分支。根据要合并的内容的不同，具体合并过程就会有多种情况。

![image](/images/Git使用教学/0bb900a1fb09455e979ebe78e9ed4717.webp)

### 🔸快速合并（Fast forward）

如下图，`master`分支么有任何提交，“`git merge dev`”合并分支`dev`到`master`，此时合并速度就非常快，直接移动`master`的“指针”引用到`dev`即可。这就是快速合并（Fast forward），不会产生新的提交。

![image](/images/Git使用教学/d9e4500d07dd4608859822eff0946aa1.webp)

> **📢强制不用快速合并**：`git merge --no-ff -m "merge with no-ff" dev`，参数`--no-ff`不启用快速合并，会产生一个新的合并提交记录。

### 🔸普通合并

如果`master`有变更，存在分支交叉，则会把两边的变更合并成一个提交。

![image](/images/Git使用教学/11697f2dcf804ad9a9c34836743389f5.webp)

上图中，创建`dev`分支后，两个分支都有修改提交，因此两个分支就不在一条顺序线上了，此时合并`dev`到`master`就得把他们的修改进行合并操作了。

### 🔸处理冲突<<<<<<< HEAD

在有冲突的文件中，`<<<<<<< HEAD`开头的内容就表示是有冲突的部分，需要人工处理，可以借助一些第三方的对比工具。人工处理完毕后，完成合并提交，才最终完成此次合并。`=======`分割线上方是当前分支的内容，下方是被合并分支的变更内容。

![image.png](/images/Git使用教学/790e17969958424c992eaf7a204641eb.webp)

### 变基rebase

把两个分支的修改内容合并到一起的办法有两种：`merge` 和 `rebase`，作用都是一样的，区别是`rebase`的提交历史更简洁，干掉了分叉，merge的提交历史更完整。

![image](/images/Git使用教学/6009e8d4b42f4fdfbaf2e3330b3bd5f9.webp)

```bash
$ git rebase master
$ git checkout master
$ git merge dev
```

---

## 标签管理

**标签**（Tags）指的是某个分支某个特定时间点的状态，是对某一个提交记录的的**固定**“指针”引用。一经创建，不可移动，存储在工作区根目录下`.git\refs\tags`。可以理解为某一次提交（编号）的别名，常用来标记版本。所以发布时，一般都会打一个版本标签，作为该版本的快照，指向对应提交`commit`。

当项目达到一个关键节点，希望永远记住那个特别的提交快照，你可以使用 `git tag` 给它打上标签。比如我们今天终于完成了V1.1版本的开发、测试，并成功上线了，那就可给今天最后这个提交打一个标签“V1.1”，便于版本管理。

默认标签是打在最新提交的commit上的，如果希望在指定的提交上打标签则带上提交编号（commit id）：`git tag v0.9 f52c633`

![image](/images/Git使用教学/2bb33deac27f4730b2026ff92ce38a20.webp)

| **指令** | **描述** |
| --- | --- |
| git tag | 查看标签列表 |
| git tag -l 'a\*' | 查看名称是“a”开头的标签列表，带查询参数 |
| git show \[tagname\] | 查看标签信息 |
| **git tag** \[tagname\] | 创建一个标签，默认标签是打在最新提交的commit上的 |
| git tag \[tagname\] \[commit id\] | 新建一个tag在指定commit上 |
| git tag -a v5.1 -m'v5.1版本' | 创建标签`v5.1.1039`，`-a`指定标签名，`-m`指定说明文字 |
| git tag -d \[tagname\] | 删除本地标签 |
| git checkout v5.1.1039 | 切换标签，同切换分支 |
| git push \[remote\] v5.1 | 推送标签，标签不会默认随代码推送推送到服务端 |
| git push \[remote\] --tags | 提交所有tag |

如果要推送某个标签到远程，使用命令`git push origin [tagname]`，或者，一次性推送全部到远程：`git push origin --tags`

> 📢注意：标签总是和某个commit挂钩。如果这个commit既出现在master分支，又出现在dev分支，那么在这两个分支上都可以看到这个标签。

```bash
# tag
$ git tag -a 'v1' -m'v1版本'
$ cat .git/refs/tags/v1
a2e2c9caea35e176cf61e96ad9d5a929cfb82461

# 查看标签列表
$ git tag
v1
```

---

## 后悔药-怎么撤销变更？

发现写错了要回退怎么办？看看下面几种后悔指令吧！

![image](/images/Git使用教学/79de5cfe72814bca8d97411d49e59977.webp)

### 后悔指令🔥

| **指令** | **描述** |
| --- | --- |
| git checkout . | 撤销工作区的（未暂存）修改，把暂存区恢复到工作区。不影响暂存区，如果没暂存，则撤销所有工作区修改 |
| git checkout \[file\] | 同上，`file`指定文件 |
| git checkout HEAD . | 撤销工作区、暂存区的修改，用`HEAD`指向的当前分支最新版本替换工作区、暂存区 |
| git checkout HEAD \[file\] | 同上，`file`指定文件 |
|  |  |
| git reset | 撤销暂存区状态，同`git reset HEAD`，不影响工作区 |
| git reset HEAD \[file\] | 同上，指定文件`file`，`HEAD`可省略 |
| git reset \[commit\] | 回退到指定版本，清空暂存区，不影响工作区。工作区需要手动`git checkout`签出 |
| git reset --soft \[commit\] | 移动分支`master`、`HEAD`到指定的版本，不影响暂存区、工作区，需手动`git checkout`签出更新 |
| git reset --hard HEAD | 撤销工作区、暂存区的修改，用当前最新版 |
| git reset --hard HEAD~ | 回退到上一个版本，并重置工作区、暂存区内容。 |
| git reset --hard \[commit\] | 回退到指定版本，并重置工作区、暂存区内容。 |
|  |  |
| git **revert**\[commit\] | 撤销一个提交，会用一个新的提交（原提交的逆向操作）来完成撤销操作，如果已`push`则重新`push`即可 |

```bash
# 只撤销工作区的修改（未暂存）
$ git checkout .
Updated 1 path from the index

# 撤销工作区、暂存区的修改
$ git checkout HEAD .
Updated 1 path from f951a96
```

### 回退版本reset

`reset`是专门用来撤销修改、回退版本的指令，支持的场景比较多，多种撤销姿势，所以参数组合也比较多。简单理解就是移动`master`分支、`HEAD`的“指针”地址，理解这一点就基本掌握`reset`了。

如下图：

![image](/images/Git使用教学/45ad235c2b8b440ebe32481e56ab483f.webp)

`reset`有三种模式，对应三种参数：`mixed`（默认模式）、`soft`、`hard`。三种参数的主要区别就是对工作区、暂存区的操作不同。

![image](/images/Git使用教学/737dd913911b4e57a2e7dedd4f094d9b.webp)

| **模式名称\\** | **描述** | **HEAD的位置** | **暂存区** | **工作区** |
| --- | --- | --- | --- | --- |
| **soft** | 回退到某一个版本，工作区不变，需手动`git checkout` | 修改 | 不修改 | 不修改 |
| **mixed**(默认) | 撤销暂存区状态，不影响工作区，需手动`git checkout` | 修改 | 修改 | 不修改 |
| **hard** | 重置未提交修改（工作区、暂存区） | 修改 | 修改 | 修改 |

穿梭前，用`git log`可以查看提交历史，以便确定要回退到哪个版本。要重返未来，用`git reflog`查看命令历史，以便确定要回到未来的哪个版本。

```bash
git reset [--soft | --mixed | --hard] [HEAD]

# 撤销暂存区
$ git reset
Unstaged changes after reset:
M       R.md

# 撤销工作区、暂存区修改
$ git reset --hard HEAD

# 回退版本库到上一个版本，并重置工作区、暂存
$ git reset --hard HEAD~

# 回到原来的版本（恢复上一步的撤销操作），并重置工作区、暂存
$ git reset --hard 5f8b961

# 查看所有历史提交记录
$ git reflog
ccb9937 (HEAD -> main, origin/main, origin/HEAD) HEAD@{0}: commit: 报表新增导入功能
8f61a60 HEAD@{1}: commit: bug：修复报表导出bug
4869ff7 HEAD@{2}: commit: 用户报表模块开发
4b1028c HEAD@{3}: commit: 财务报表模块开发完成
```

### 撤销提交revert

安全的撤销某一个提交记录，基本原理就是生产一个新的提交，用原提交的逆向操作来完成撤销操作。注意，这不同于`reset`，`reset`是回退版本，revert只是用于撤销某一次历史提交，操作是比较安全的。

![image](/images/Git使用教学/3138dde33c5f40a09fe209bb30cd08f2.webp)

如上图：

```bash
# revert撤销指定的提交，“-m”附加说明
$ git revert 41ea42 -m'撤销对***的修改'
[main 967560f] Revert "123"
                            1 file changed, 1 deletion(-)
```

### checkout/reset/revert总结

| **标题 \\ 指令** | **checkout** | **reset** | **revert** |
| --- | --- | --- | --- |
| **主要作用（撤销）** | 撤销工作区、暂存区未提交修改 | 回退版本，重置工作区、暂存区 | 撤销某一次提交 |
| **撤销工作区** | git checkout \[file\] | git reset HEAD \[file\] |  |
| **撤销工作区、暂存区** | git checkout HEAD \[file\] | git reset --hard HEAD \[file\] |  |
| **回退版本** |  | git reset --hard \[commit\] |  |
| **安全性** | 只针对未提交修改，**安全** | 如回退了已`push`提交，**不安全** | **安全** |

可看出`reset`完全可以替代`checkout`来执行撤销、回退操作，`reset`本来也是专门用来干这个事情的，可以抛弃`checkout`了（撤销的时候）。

---

## 工作中的Git实践

### Git flow

Git flow（Git工作流程）是指软件项目中的一种Git分支管理模型，经过了大量的实践和优化，被认为是现代敏捷软件开发和DevOps（开发、技术运营和质量保障三者的交集）的最佳实践。Git flow主要流程及关键分支：[原图地址-processon](https://www.processon.com/view/59ec836de4b0c86d400e99f1?fromnew=1 "https://www.processon.com/view/59ec836de4b0c86d400e99f1?fromnew=1")

![image.png](/images/Git使用教学/62805963c11f409497cbdf98cfecb5b3.webp)

**✅主分支：master**，稳定版本代码分支，对外可以随时编译发布的分支，不允许直接Push代码，只能请求合并（pull request），且只接受`hotfix`、`release`分支的代码合并。

**✅热修复分支：hotfix**，针对线上紧急问题、bug修复的代码分支，修复完后合并到主分支、开发分支。

![image.png](/images/Git使用教学/e799d590479c4b45a1da4af6d37aa709.webp)

**✅发版分支：release**，版本发布分支，用于迭代版本发布。迭代开发完成后，合并`dev`代码到`release`，在`release`分支上编译发布版本，以及修改bug（定时同步bug修改到`dev`分支）。测试完成后此版本可以作为发版使用，然后把稳定的代码push到`master`分支，并打上版本标签。

**✅开发分支：dev**，开发版本分支，针对迭代任务开发的分支，日常开发原则上都在此分支上面，迭代完成后合并到release分支，开发、发版两不误。

![image.png](/images/Git使用教学/47dee6fb94154ab0abf61abcdf37b3eb.webp)

**✅其他开发分支：dev-xxx**，开发人员可以针对模块自己创建本地分支，开发完成后合并到dev开发分支，然后删除本地分支。

### 10.2、金屋藏娇stash

当你正在`dev`分支开发一个功能时，代码写了一半，突然有一个线上的bug急需要马上修改。`dev`分支Bug没写完，不方便提交，就不能切换到主分支去修复线上bug。Git提供一个`stash`功能，可以把当前**工作区、暂存区** 未提交的内容“隐藏”起来，就像什么都没发生一样。

```bash
# 有未提交修改，切换分支时报错
$ git checkout dev
error: Your local changes to the following files would be overwritten by checkout:
        README.md
Please commit your changes or stash them before you switch branches.
Aborting

# 隐藏
$ git stash
Saved working directory and index state WIP on main: 2bc012c s

# 查看被隐藏的内容
$ git stash list
stash@{0}: WIP on main: 2bc012c s

# 比较一下，什么都没有，一切都没有发生过！
$ git diff

# 去其他分支修改bug，修复完成回到当前分支，恢复工作区
$ git stash pop
```

在上面示例中，有未提交修改，切换分支时报错。错误提示信息很明确了，`commit`提交或`stash`隐藏：`Please commit your changes or stash them before you switch branches.`

> 📢 如果切换分支时，未提交修改的内容没有冲突，是可以成功切换的，未提交修改会被带过去。

| **指令** | **描述** |
| --- | --- |
| git stash | 把未提交内容隐藏起来，包括未暂存、已暂存。 等以后恢复现场后继续工作 |
| git stash list | 查看所有被隐藏的内容列表 |
| git stash pop | 恢复被隐藏的内容，同时删除隐藏记录 |
| git stash save "message" | 同`git stash`，可以备注说明`message` |
| git stash apply | 恢复被隐藏的文件，但是隐藏记录不删除 |
| git stash drop | 删除隐藏记录 |

> 🪧当然这里先提交到本地也是可以的，只是提交不是一个完整的功能代码，而是残缺的一部分，影响也不大。

### 拣选提交cherry-pick

当有一个紧急bug，在`dev`上修复完，我们需要把`dev`上的这个bug修复所做的修改“复制”到`master`分支，但不想把整个dev合并过去。为了方便操作，Git专门提供了一个`cherry-pick`命令，让我们能复制一个特定的提交到当前分支，而不管这个提交在哪个分支。

![image](/images/Git使用教学/fe43365ecea04a0b843ed23e79a77936.webp)

如上图，操作过程相当于将该提交导出为补丁文件，然后在当前`HEAD`上重放，形成无论内容还是提交说明都一致的提交。

```bash
# 选择一个commit，合并进当前分支
$ git cherry-pick [commit]
```

---
原文链接 ：https://juejin.cn/post/7195030726096453690