Files
blog/source/_posts/Git使用教学.md
T
geek b3f44d235a fix: 修复博客图片加载失败(掘金防盗链)
- 将《Git使用教学》中的掘金外链图片下载至 source/images/ 并替换为站内路径
- _config.yml 注入 no-referrer meta 作为兜底方案
- 新增 tools/download-juejin-images.js 批量下载/替换脚本
2026-07-08 21:52:22 +08:00

920 lines
41 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
## 01、认识一下Git!—简介
Git是当前最先进、最主流的**分布式**版本控制系统,免费、开源!核心能力就是版本控制。再具体一点,就是面向代码文件的版本控制,代码的任何修改历史都会被记录管理起来,意味着可以恢复到到以前的任意时刻状态。支持跨区域多人协作编辑,是团队项目开发的必备基础,所以Git也就成了程序员的必备技能。
![image](/images/bff72050dee7400db8045109f8ca6119.webp)
**🟢主要特点**
![image.png](/images/05512798305d4a20a38fd3b89be2b6e0.webp)
Git是Linux之父被迫开发的,为了解决Linux混乱的代码管理而开发的。Linux和Git之父 **李纳斯·托沃兹**Linus Benedic Torvalds),来自1969年的芬兰。
---
## 02、Git是干什么的?—基础概念
先了解下Git的基本概念,及基本框架、工作流程。
## 2.1、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`” |
## 2.2、工作区/暂存区/仓库
![image.png](/images/091ebdafedb746d98b5340fa107c8d1b.webp)
工作区、暂存区、版本库是Git最基本的概念,关系如下图:
![image](/images/c97776f4f3b64b1e979dde63e634847e.webp)
**🔸工作区**(Workspace)就是在电脑里能看到的代码库目录,是我们搬砖的地方,新增、修改的文件会提交到暂存区。
**🔸暂存区**(stage或index) 用于临时存放文件的修改,实际上上它只是一个文件(.git/index),保存待提交的文件列表信息。
**🔸版本库/仓库**Repository /rɪˈpɑːzətɔːri/ 仓库)Git的管理仓库,管理版本的数据库,记录文件/目录状态的地方,所有内容的修改记录(版本)都在这里。就是工作区目录下的隐藏文件夹`.git`,包含暂存区、分支、历史记录等信息。
![image](/images/4b886c1cb4844010a76409ff36beb458.webp)
如上图,为对应本地仓库目录的结构关系。
## 2.3、Git基本流程(图)
Git的工作流程核心就下面几个步骤,掌握了就可以开始写Bug了。
![image](/images/666f0c95490744bbafe060f5d44c22b0.webp)
## 2.4、Git状态(图)
Git在执行提交的时候,不是直接将工作区的修改保存到仓库,而是将暂存区域的修改保存到仓库。要提交文件,首先需要把文件加入到暂存区域中。因此,Git管理的文件有三(+2)种状态:
![image](/images/f78be8de167c4fcc89b814ffda2e92f7.webp)
---
## 03、起步: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没什么影响。
## 3.1、Git的配置文件
Git有三个主要的配置文件:三个配置文件的优先级是**① < ② < ③**
![image](/images/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
```
## 3.3、配置-忽略.gitignore
工作目录中的文件并不是全都需要纳入版本管理,如日志、临时文件、私有配置文件等不需要也不能纳入版本管理,那该怎么办呢?
![image.png](/images/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")
---
## 04、Git的GUI工具们
如果不想用命令行工具,完全可以安装一个Git的GUI工具,用的更简单、更舒服。不用记那么多命令了,极易上手,不过Git基础还是需要学习了解一下的。
> ❓对于Git,建议用命令行,这样你才能真的理解Git的思想? ✔️其实都只是工具而已,适合自己的就是最好的,没必要纠结,多写点Bug更重要!
## 4.1、SourceTree
SourceTree的[官网](https://www.sourcetreeapp.com/ "https://www.sourcetreeapp.com/") 下载安装包,支持Window、Mac系统,按照提示完成安装。
![image.png](/images/938e73a171c745efa3f279d98f60dd79.webp)
![image](/images/1384a781a80042faaf754c0de52b8d6d.webp)
## 4.2、TortoiseGit
![image.png](/images/8165d603319245278464511ad2cc71cb.webp)
[TortoiseGit](https://tortoisegit.org/ "https://tortoisegit.org/") 官网下载安装包,及中文语言包,按照提示完成安装。小乌龟的Git是集成到操作系统里的,直接右键文件夹就可以进行Git操作了。
![image.png](/images/dca67d3a374d41e0bbae7b5d66c8b0e1.webp)
![image.png](/images/5d4597b570744fbda1cf1879103a0511.webp)
![image.png](/images/7494f0ff8dae4e1c8029fc1640938229.webp)
## 4.3、VSCode中的Git
VSCode自带的Git工具基本已经可以满足日常使用了,既有可视化功能,也能敲命令,习惯了不就不用安装其他GUI工具了。不过还是可以再安装一些VSCode插件,来增强Git功能。
![image.png](/images/2c02bbef03d445b6b3c3e5ce64ae632b.webp)
---
## 05、Git使用入门
## 5.1、创建仓库
创建本地仓库的方法有两种:
```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/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/377739fca6a740819ce778c705e9cd22.webp)
## 5.2、暂存区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/69ca93a857ca45769ae56f0492f1d9c6.webp)
执行`git add .`暂存:
![image.png](/images/7c5ebb71c0bb4bec9a5fa9284c70e8f7.webp)
## 5.3、提交commit-记录
`git commit`提交是以时间顺序排列被保存到数据库中的,就如游戏关卡一样,每一次提交(commit)就会产生一条记录:`id + 描述 + 快照内容`
多个提交就形成了一条时间线,每次提交完,会移动当前分支`master``HEAD`的“指针”位置。
![image](/images/9a3233f335334f30902d0255ff1a6477.webp)
Sourcetree上的历史记录:
![image.png](/images/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/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
```
## 5.4、Git的“指针”引用们
Git中最重要的就是提交记录了,其他如**标签**、**分支**、**HEAD** 都对提交记录的“指针”引用,指向这些提交记录,理解这一点很重要。
![image](/images/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`”。
> **📢“指针”引用**:之所以用引号的“指针”,是为了便于统一和理解。和指针原理类似,都是一个指向,只是实际上可能更复杂一点,且不同的“指针”引用会有区别。
## 5.5、提交的唯一标识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~
```
## 5.6、比较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/565ccedcf6664d6598178327bdf6f75a.webp)
```bash
# 查看文件的修改
$ git diff README.md
# 查看两次提交的差异
$ git diff 8f4244 1da22
# 显示今天你写了多少行代码:工作区+暂存区
$ git diff --shortstat "@{0 day ago}"
```
---
## 06、远程仓库
Git作为分布式的版本管理系统,每个终端都有自己的Git仓库。但团队协作还需一个中间仓库,作为中心,同步各个仓库。于是服务端(远程)仓库就来承担这个职责,服务端不仅有仓库,还配套相关管理功能。
![image](/images/7566420b76e34e489e59871a4a0da9b3.webp)
可以用公共的Git服务器,也可以自己搭建一套Git服务器。
## 6.1、远程用户登录
Git服务器一般提供两种登录验证方式:
![image.png](/images/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/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/73179047882b469cb72949d3a0537be7.webp)
SSHSecure Shell,安全外壳)是一种网络安全协议,通过加密和认证机制实现安全的访问和文件传输等业务,多用来进行远程登录、数据传输。SSH通过公钥、私钥非对称加密数据,所以SSH需要生成一个公私钥对,公钥放服务器上,私有自己留着进行认证。
![](/images/b4c7bed1fa8e478aa68aefa3b5e94ff2.webp)
**① 生成公私钥**:通过Git指令`ssh-keygen -t rsa`生成公私钥,一路回车即可完成。生成在“`C:\Users\用户名\.ssh`”目录下,文件`id_rsa.pub`的内容就是公钥。
![image.png](/images/ea24f85292134500a84374062f7ee6a4.webp)
**② 配置公钥**:打开`id_rsa.pub`文件,复制内容。Github上,打开`Setting``SSH and GPG keys``SSH keys` ➤ 按钮`New SSH key`,标题(Title)随意,秘钥内容粘贴进去即可。
![image.png](/images/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.
```
## 6.2、远程仓库指令🔥
| **指令** | **描述** |
| --- | --- |
| 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的模式进行合并 |
## 6.3、推送push/拉取pull
`git push``git pull`是团队协作中最常用的指令,用于同步本地、服务端的更新,与他人协作。
**🔸推送**(push):推送本地仓库到远程仓库。
**🔸拉取**(pull):从服务端(远程)仓库更新到本地仓库。
![image](/images/365395e73a254ef6abfd5a99e78992b0.webp)
## 6.4、fetch与pull有什么不同?
两者都是从服务端获取更新,主要区别是`fetch`不会自动合并,不会影响当前工作区内容。
> `git pull` = `git fetch` + `git merge`
![image](/images/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(-)
```
---
## 07、Git利器-分支
分支是从主线分离出去的“副本”,分支就像是平行宇宙,可独立发展,独立编辑、提交,也可以和其他分支合并。分支是Git的核心必杀利器之一,分支创建、切换、删除都非常快,他非常的轻量。所以,早建分支!多用分支!
![image.png](/images/4b9ab3c40bcf47858c8ee108ab35ff91.webp)
## 7.1、分支Branch
比如有一个项目团队,准备10月份发布新版本,要新开发一堆黑科技功能,占领市场。你和小伙伴“小美”一起负责开发一个新功能A,开发周期2周,在这两周你们的代码不能影响其他人,不影响主分支。这个时候就可以为这个新功能创建一个分支,你们两在这个分支上干活,2周后代码开发完了、测试通过,就可以合并进要发版的开发分支了。安全、高效,不影响其他人工作,完美!
![image](/images/c58ef8e5f5334ee6909d9c9a55298e71.webp)
在实际项目中,一般会建几个主线分支。
**分支**就是指向某一个提交记录的“指针”引用,因此创建分支是非常快的,不管仓库多大。当我们运行`git branch dev`创建了一个名字为`dev`的分支,Git实际上是在`.git\refs\heads`下创建一个`dev`的引用文件(没有扩展名)。
```bash
$ git branch dev
$ cat .git/refs/heads/dev
ca88989e7c286fb4ba56785c2cd8727ea1a07b97
```
## 7.2、分支指令🔥
| **指令** | **描述** |
| --- | --- |
| 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”为当前活动分支。
```
## 7.3、分支的切换checkout
代码仓库可以有多个分支,`master`为默认的主分支,但只有一个分支在工作状态。所以要操作不同分支,需要切换到该分支,`HEAD`就是指向当前正在活动的分支。
![image](/images/477ba6ccb6224187a72331fbc2cd3b7a.webp)
```bash
# 切换到dev分支,HEAD指向了dev
# 此处 switch 作用同 checkoutswitch只用于切换,不像checkout功能很多
$ git switch dev
Switched to branch 'dev'
$ cat .git/HEAD
ref: refs/heads/dev
```
使用 `git checkout dev`切换分支时,干了两件事:
此时的活动分支就是`dev`了,后续的提交就会更新到`dev`分支了。
**❓切换时还没提交的代码怎么办?**
## 7.4、合并merge&冲突
把两个分支的修改内容合并到一起,常用的合并指令`git merge [branch]`,将分支`[branch]`合并到当前分支。根据要合并的内容的不同,具体合并过程就会有多种情况。
![image](/images/0bb900a1fb09455e979ebe78e9ed4717.webp)
### 🔸快速合并(Fast forward
如下图,`master`分支么有任何提交,“`git merge dev`”合并分支`dev``master`,此时合并速度就非常快,直接移动`master`的“指针”引用到`dev`即可。这就是快速合并(Fast forward),不会产生新的提交。
![image](/images/d9e4500d07dd4608859822eff0946aa1.webp)
> **📢强制不用快速合并**`git merge --no-ff -m "merge with no-ff" dev`,参数`--no-ff`不启用快速合并,会产生一个新的合并提交记录。
### 🔸普通合并
如果`master`有变更,存在分支交叉,则会把两边的变更合并成一个提交。
![image](/images/11697f2dcf804ad9a9c34836743389f5.webp)
上图中,创建`dev`分支后,两个分支都有修改提交,因此两个分支就不在一条顺序线上了,此时合并`dev``master`就得把他们的修改进行合并操作了。
### 🔸处理冲突<<<<<<< HEAD
在有冲突的文件中,`<<<<<<< HEAD`开头的内容就表示是有冲突的部分,需要人工处理,可以借助一些第三方的对比工具。人工处理完毕后,完成合并提交,才最终完成此次合并。`=======`分割线上方是当前分支的内容,下方是被合并分支的变更内容。
![image.png](/images/790e17969958424c992eaf7a204641eb.webp)
## 7.5、变基rebase
把两个分支的修改内容合并到一起的办法有两种:`merge``rebase`,作用都是一样的,区别是`rebase`的提交历史更简洁,干掉了分叉,merge的提交历史更完整。
![image](/images/6009e8d4b42f4fdfbaf2e3330b3bd5f9.webp)
```bash
$ git rebase master
$ git checkout master
$ git merge dev
```
---
## 08、标签管理
**标签**(Tags)指的是某个分支某个特定时间点的状态,是对某一个提交记录的的**固定**“指针”引用。一经创建,不可移动,存储在工作区根目录下`.git\refs\tags`。可以理解为某一次提交(编号)的别名,常用来标记版本。所以发布时,一般都会打一个版本标签,作为该版本的快照,指向对应提交`commit`
当项目达到一个关键节点,希望永远记住那个特别的提交快照,你可以使用 `git tag` 给它打上标签。比如我们今天终于完成了V1.1版本的开发、测试,并成功上线了,那就可给今天最后这个提交打一个标签“V1.1”,便于版本管理。
默认标签是打在最新提交的commit上的,如果希望在指定的提交上打标签则带上提交编号(commit id):`git tag v0.9 f52c633`
![image](/images/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
```
---
## 09、后悔药-怎么撤销变更?
发现写错了要回退怎么办?看看下面几种后悔指令吧!
![image](/images/79de5cfe72814bca8d97411d49e59977.webp)
## 9.1、后悔指令🔥
| **指令** | **描述** |
| --- | --- |
| 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
```
## 9.2、回退版本reset
`reset`是专门用来撤销修改、回退版本的指令,支持的场景比较多,多种撤销姿势,所以参数组合也比较多。简单理解就是移动`master`分支、`HEAD`的“指针”地址,理解这一点就基本掌握`reset`了。
如下图:
![image](/images/45ad235c2b8b440ebe32481e56ab483f.webp)
`reset`有三种模式,对应三种参数:`mixed`(默认模式)、`soft``hard`。三种参数的主要区别就是对工作区、暂存区的操作不同。
![image](/images/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: 财务报表模块开发完成
```
## 9.3、撤销提交revert
安全的撤销某一个提交记录,基本原理就是生产一个新的提交,用原提交的逆向操作来完成撤销操作。注意,这不同于`reset``reset`是回退版本,revert只是用于撤销某一次历史提交,操作是比较安全的。
![image](/images/3138dde33c5f40a09fe209bb30cd08f2.webp)
如上图:
```bash
# revert撤销指定的提交,“-m”附加说明
$ git revert 41ea42 -m'撤销对***的修改'
[main 967560f] Revert "123"
1 file changed, 1 deletion(-)
```
## 9.4、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`了(撤销的时候)。
---
## 10、工作中的Git实践
## 10.1、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/62805963c11f409497cbdf98cfecb5b3.webp)
**✅主分支:master**,稳定版本代码分支,对外可以随时编译发布的分支,不允许直接Push代码,只能请求合并(pull request),且只接受`hotfix``release`分支的代码合并。
**✅热修复分支:hotfix**,针对线上紧急问题、bug修复的代码分支,修复完后合并到主分支、开发分支。
![image.png](/images/e799d590479c4b45a1da4af6d37aa709.webp)
**✅发版分支:release**,版本发布分支,用于迭代版本发布。迭代开发完成后,合并`dev`代码到`release`,在`release`分支上编译发布版本,以及修改bug(定时同步bug修改到`dev`分支)。测试完成后此版本可以作为发版使用,然后把稳定的代码push到`master`分支,并打上版本标签。
**✅开发分支:dev**,开发版本分支,针对迭代任务开发的分支,日常开发原则上都在此分支上面,迭代完成后合并到release分支,开发、发版两不误。
![image.png](/images/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/fe43365ecea04a0b843ed23e79a77936.webp)
如上图,操作过程相当于将该提交导出为补丁文件,然后在当前`HEAD`上重放,形成无论内容还是提交说明都一致的提交。
```bash
# 选择一个commit,合并进当前分支
$ git cherry-pick [commit]
```
---
## 参考资料
---
> **©️版权申明**:版权所有@安木夕,本文内容仅供学习,欢迎指正、交流,转载请注明出处![*原文编辑地址-语雀*](https://www.yuque.com/kanding/ktech/ccgylqhnb94ug4bu "https://www.yuque.com/kanding/ktech/ccgylqhnb94ug4bu")