Git 是目前最流行的分布式版本控制系统。它不仅可以帮助我们保存代码历史,还能支持多人协作、分支开发、版本发布、问题定位和误操作恢复。
本文将从 Git 的底层概念讲起,逐步介绍日常命令、分支合并、远程协作、撤销恢复、团队工作流以及常见问题。即使你之前没有接触过 Git,也可以按照本文一步步学习和实践。
1. Git 是什么
Git 是一个开源的分布式版本控制系统,由 Linux 内核的创建者 Linus Torvalds 于 2005 年开发。
所谓版本控制,就是记录文件每一次修改,使开发者可以:
- 查看某个文件由谁修改;
- 比较不同版本之间的差异;
- 恢复到之前的版本;
- 同时开发多个功能;
- 合并多人提交的代码;
- 为发布版本创建标签;
- 定位某个 Bug 是在哪次提交中引入的。
Git 不仅可以管理程序代码,也可以管理配置文件、脚本、文档以及其他文本内容。
1.1 Git 为什么叫"分布式版本控制"
在 Git 中,每个开发者本地通常都拥有一份完整仓库,包括:
- 当前项目文件;
- 完整提交历史;
- 分支和标签;
- Git 对象数据库;
- 本地版本控制能力。
即使暂时没有网络,开发者仍然可以:
bash
git status
git diff
git add
git commit
git log
git branch
只有在需要与其他人交换代码时,才需要执行:
bash
git fetch
git pull
git push
1.2 Git 的主要特点
- 分布式存储;
- 分支创建速度快;
- 支持离线提交;
- 数据完整性较强;
- 支持多种协作工作流;
- 具有完整的历史恢复能力;
- 适合从个人项目到大型开源项目的各种场景。
1.3 Git 记录的是快照
从概念上看,Git 每次提交保存的是项目在某个时间点的快照,而不是简单记录"第几行增加了什么"。
如果一个文件没有变化,新的提交会继续引用已有的文件对象,而不需要重复保存完全相同的内容。
需要注意的是,Git 在打包存储对象时可能使用差异压缩来节省空间,但它对用户呈现的核心模型仍然是"快照"。
2. Git、GitHub、GitLab 和 SVN 的区别
2.1 Git 和 GitHub 的区别
Git 是版本控制工具,GitHub 是基于 Git 的代码托管平台。
可以这样理解:
- Git 类似于本地使用的版本管理软件;
- GitHub 类似于存放 Git 仓库并支持团队协作的网站。
除了 GitHub,常见的 Git 托管平台还有:
- GitLab;
- Gitee;
- Bitbucket;
- Azure DevOps;
- 企业自建 Git 服务。
即使不使用 GitHub,也可以正常使用 Git。
2.2 Git 和 GitLab 的区别
GitLab 同样是基于 Git 的代码托管与研发协作平台,通常还提供:
- Merge Request;
- Issue 管理;
- CI/CD;
- 权限控制;
- 制品管理;
- 代码审查;
- 私有化部署。
GitHub 中通常称为 Pull Request,GitLab 中通常称为 Merge Request,它们的核心目的都是审查并合并代码。
2.3 Git 和 SVN 的区别
| 对比项 | Git | SVN |
|---|---|---|
| 架构 | 分布式 | 集中式 |
| 本地是否有完整历史 | 通常有 | 通常没有 |
| 离线提交 | 支持 | 不支持或能力有限 |
| 分支成本 | 很低 | 相对较高 |
| 提交到哪里 | 先提交到本地仓库 | 通常直接提交到中央仓库 |
| 网络依赖 | 大部分操作不依赖网络 | 很多操作依赖服务器 |
| 工作流 | 灵活 | 相对集中 |
| 学习成本 | 相对较高 | 相对简单 |
Git 并不是在所有场景下都绝对优于 SVN,但在现代软件开发、开源协作和持续集成场景中,Git 已经成为主流选择。
3. Git 的核心工作区域
理解 Git,最重要的是理解以下几个区域:
- 工作区;
- 暂存区;
- 本地仓库;
- 远程仓库。
#mermaid-svg-hWTWcYASaFYXNUoM{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hWTWcYASaFYXNUoM .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hWTWcYASaFYXNUoM .error-icon{fill:#552222;}#mermaid-svg-hWTWcYASaFYXNUoM .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hWTWcYASaFYXNUoM .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hWTWcYASaFYXNUoM .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM .marker.cross{stroke:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hWTWcYASaFYXNUoM p{margin:0;}#mermaid-svg-hWTWcYASaFYXNUoM .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster-label text{fill:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster-label span{color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster-label span p{background-color:transparent;}#mermaid-svg-hWTWcYASaFYXNUoM .label text,#mermaid-svg-hWTWcYASaFYXNUoM span{fill:#333;color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .node rect,#mermaid-svg-hWTWcYASaFYXNUoM .node circle,#mermaid-svg-hWTWcYASaFYXNUoM .node ellipse,#mermaid-svg-hWTWcYASaFYXNUoM .node polygon,#mermaid-svg-hWTWcYASaFYXNUoM .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .rough-node .label text,#mermaid-svg-hWTWcYASaFYXNUoM .node .label text,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape .label,#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape .label{text-anchor:middle;}#mermaid-svg-hWTWcYASaFYXNUoM .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .rough-node .label,#mermaid-svg-hWTWcYASaFYXNUoM .node .label,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape .label,#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape .label{text-align:center;}#mermaid-svg-hWTWcYASaFYXNUoM .node.clickable{cursor:pointer;}#mermaid-svg-hWTWcYASaFYXNUoM .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM .arrowheadPath{fill:#333333;}#mermaid-svg-hWTWcYASaFYXNUoM .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hWTWcYASaFYXNUoM .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hWTWcYASaFYXNUoM .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hWTWcYASaFYXNUoM .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hWTWcYASaFYXNUoM .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hWTWcYASaFYXNUoM .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hWTWcYASaFYXNUoM .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster text{fill:#333;}#mermaid-svg-hWTWcYASaFYXNUoM .cluster span{color:#333;}#mermaid-svg-hWTWcYASaFYXNUoM div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hWTWcYASaFYXNUoM .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hWTWcYASaFYXNUoM rect.text{fill:none;stroke-width:0;}#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape p,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hWTWcYASaFYXNUoM .icon-shape .label rect,#mermaid-svg-hWTWcYASaFYXNUoM .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hWTWcYASaFYXNUoM .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hWTWcYASaFYXNUoM .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hWTWcYASaFYXNUoM :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} git add
git commit
git push
git fetch
git restore / reset 等
工作区
Working Tree
暂存区
Index
本地仓库
Local Repository
远程仓库
Remote Repository
3.1 工作区
工作区就是当前可以直接看到和编辑的项目文件。
例如:
text
my-project/
├── src/
├── pom.xml
├── README.md
└── .git/
除了 .git 目录之外,其余文件通常都属于工作区内容。
3.2 暂存区
暂存区也叫:
- Staging Area;
- Index;
- 索引区。
执行下面的命令后,修改会进入暂存区:
bash
git add README.md
暂存区的作用是让开发者决定"下一次提交具体包含哪些内容"。
假设你同时修改了三个文件,但只想提交其中两个,可以执行:
bash
git add src/UserService.java
git add src/UserController.java
git commit -m "feat(user): add user query API"
未暂存的第三个文件不会进入这次提交。
3.3 本地仓库
执行 git commit 后,暂存区中的内容会形成一个提交并保存到本地仓库。
bash
git commit -m "feat: add login function"
此时提交只存在于本地,并不会自动发送到 GitHub 或 GitLab。
3.4 远程仓库
远程仓库通常位于 GitHub、GitLab、Gitee 或企业内部服务器。
执行以下命令可以将本地提交推送到远程仓库:
bash
git push origin main
其中:
origin是远程仓库名称;main是需要推送的本地分支。
origin 只是 Git 在克隆仓库时通常创建的默认别名,并不是特殊关键字。一个仓库也可以配置多个远程地址。
4. Git 的对象模型与底层原理
一个 Git 仓库的核心数据位于 .git 目录中。
text
.git/
├── HEAD
├── config
├── index
├── objects/
├── refs/
└── logs/
Git 的核心数据主要包括:
- 对象;
- 引用;
- 索引;
- Reflog。
4.1 Blob 对象
Blob 保存文件内容,但通常不保存文件名。
可以使用下面的命令把一个文件写入 Git 对象数据库:
bash
git hash-object -w README.md
查看对象类型:
bash
git cat-file -t <对象ID>
查看对象内容:
bash
git cat-file -p <对象ID>
4.2 Tree 对象
Tree 对象表示目录结构,它可以引用:
- Blob 对象;
- 其他 Tree 对象。
Tree 对象负责把文件名、目录名和具体文件内容关联起来。
4.3 Commit 对象
Commit 对象通常包含:
- 一个根 Tree;
- 一个或多个父提交;
- 作者信息;
- 提交者信息;
- 提交时间;
- 提交说明。
#mermaid-svg-LCHuOgwKUS3Alyfa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LCHuOgwKUS3Alyfa .error-icon{fill:#552222;}#mermaid-svg-LCHuOgwKUS3Alyfa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LCHuOgwKUS3Alyfa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LCHuOgwKUS3Alyfa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa .marker.cross{stroke:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LCHuOgwKUS3Alyfa p{margin:0;}#mermaid-svg-LCHuOgwKUS3Alyfa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster-label text{fill:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster-label span{color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster-label span p{background-color:transparent;}#mermaid-svg-LCHuOgwKUS3Alyfa .label text,#mermaid-svg-LCHuOgwKUS3Alyfa span{fill:#333;color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .node rect,#mermaid-svg-LCHuOgwKUS3Alyfa .node circle,#mermaid-svg-LCHuOgwKUS3Alyfa .node ellipse,#mermaid-svg-LCHuOgwKUS3Alyfa .node polygon,#mermaid-svg-LCHuOgwKUS3Alyfa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .rough-node .label text,#mermaid-svg-LCHuOgwKUS3Alyfa .node .label text,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape .label,#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape .label{text-anchor:middle;}#mermaid-svg-LCHuOgwKUS3Alyfa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .rough-node .label,#mermaid-svg-LCHuOgwKUS3Alyfa .node .label,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape .label,#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape .label{text-align:center;}#mermaid-svg-LCHuOgwKUS3Alyfa .node.clickable{cursor:pointer;}#mermaid-svg-LCHuOgwKUS3Alyfa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa .arrowheadPath{fill:#333333;}#mermaid-svg-LCHuOgwKUS3Alyfa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LCHuOgwKUS3Alyfa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LCHuOgwKUS3Alyfa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LCHuOgwKUS3Alyfa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LCHuOgwKUS3Alyfa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LCHuOgwKUS3Alyfa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster text{fill:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa .cluster span{color:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LCHuOgwKUS3Alyfa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LCHuOgwKUS3Alyfa rect.text{fill:none;stroke-width:0;}#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape p,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LCHuOgwKUS3Alyfa .icon-shape .label rect,#mermaid-svg-LCHuOgwKUS3Alyfa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LCHuOgwKUS3Alyfa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LCHuOgwKUS3Alyfa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LCHuOgwKUS3Alyfa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Commit C2
父提交 C1
根 Tree
Blob: README.md
Tree: src
Blob: UserService.java
普通提交通常只有一个父提交,合并提交可能有两个或更多父提交。
4.4 Tag 对象
附注标签,也就是 Annotated Tag,会创建独立的 Tag 对象,其中可以保存:
- 标签名称;
- 标签说明;
- 创建者;
- 创建时间;
- 签名信息;
- 指向的提交或其他对象。
4.5 Git 对象为什么通常不可变
Git 对象的 ID 由对象类型和内容计算得出。
只要对象内容改变,对象 ID 就会改变。因此,已有提交不会被直接修改。所谓"修改提交历史",本质上通常是创建一组新的提交对象,并让分支指向新的提交。
4.6 Branch 的本质
Git 分支本质上是一个可以移动的引用,它通常指向某个提交。
例如:
text
main -> C3
当在 main 分支上创建新提交 C4 后:
text
main -> C4 -> C3
创建分支并不会复制完整代码,只是创建了一个新的引用,因此 Git 分支非常轻量。
4.7 HEAD 的作用
HEAD 表示当前检出的提交位置。
正常情况下,HEAD 间接指向当前分支:
text
HEAD -> main -> C4
如果直接检出某个提交,则会进入 Detached HEAD 状态:
text
HEAD -> C2
main -> C4
Detached HEAD 并不代表仓库损坏,但如果在此状态下创建提交,应及时创建分支保存它:
bash
git switch -c recovery-branch
5. 安装和配置 Git
5.1 检查 Git 是否安装
bash
git --version
如果输出类似下面的内容,说明 Git 已安装:
text
git version 2.x.x
5.2 Windows 安装
Windows 用户可以从 Git 官网下载安装 Git for Windows:
text
https://git-scm.com/download/win
安装完成后,可以使用:
- Git Bash;
- PowerShell;
- Windows Terminal;
- IntelliJ IDEA 内置终端;
- Visual Studio Code 终端。
5.3 macOS 安装
bash
brew install git
也可以通过 Xcode Command Line Tools 安装:
bash
xcode-select --install
5.4 Linux 安装
Ubuntu 或 Debian:
bash
sudo apt update
sudo apt install git
CentOS、Rocky Linux 或 AlmaLinux:
bash
sudo dnf install git
5.5 配置用户名和邮箱
bash
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
这些信息会写入提交记录。
查看全局配置:
bash
git config --global --list
查看所有配置及来源:
bash
git config --list --show-origin
5.6 全局配置与仓库配置
Git 配置主要分为三个级别:
| 级别 | 参数 | 影响范围 |
|---|---|---|
| 系统级 | --system |
当前计算机所有用户 |
| 用户级 | --global |
当前用户的所有仓库 |
| 仓库级 | --local |
当前仓库 |
仓库级配置的优先级高于用户级配置。
只给当前仓库设置身份:
bash
git config --local user.name "Project User"
git config --local user.email "project@example.com"
5.7 设置默认分支名称
bash
git config --global init.defaultBranch main
以后执行 git init 时,默认分支会命名为 main。
5.8 配置换行符
Windows 常见配置:
bash
git config --global core.autocrlf true
macOS 和 Linux 常见配置:
bash
git config --global core.autocrlf input
团队项目更推荐使用 .gitattributes 明确指定换行规则,例如:
gitattributes
* text=auto
*.sh text eol=lf
*.bat text eol=crlf
*.cmd text eol=crlf
5.9 设置命令别名
bash
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
配置图形化日志:
bash
git config --global alias.lg "log --graph --decorate --oneline --all"
之后可以执行:
bash
git lg
6. 创建或克隆 Git 仓库
6.1 初始化新仓库
bash
mkdir git-demo
cd git-demo
git init
执行完成后,当前目录中会出现 .git 目录。
检查仓库状态:
bash
git status
6.2 在当前目录初始化
bash
git init
6.3 指定初始分支名称
bash
git init -b main
6.4 克隆远程仓库
HTTPS:
bash
git clone https://github.com/example/project.git
SSH:
bash
git clone git@github.com:example/project.git
指定本地目录名称:
bash
git clone https://github.com/example/project.git my-project
只克隆指定分支:
bash
git clone --branch develop --single-branch https://github.com/example/project.git
浅克隆:
bash
git clone --depth 1 https://github.com/example/project.git
浅克隆只获取有限历史,适合临时构建或减少下载量,但不适合所有历史分析场景。
6.5 git init 和 git clone 的区别
git init:将当前目录初始化为新仓库;git clone:复制已有远程仓库,并自动配置远程地址。
克隆完成后,可以查看远程地址:
bash
git remote -v
7. Git 中文件的生命周期
Git 中文件常见状态包括:
- Untracked:未跟踪;
- Unmodified:未修改;
- Modified:已修改;
- Staged:已暂存;
- Committed:已提交。
#mermaid-svg-ZvW70EUgWsHHZSVZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZvW70EUgWsHHZSVZ .error-icon{fill:#552222;}#mermaid-svg-ZvW70EUgWsHHZSVZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZvW70EUgWsHHZSVZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .marker.cross{stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZvW70EUgWsHHZSVZ p{margin:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-ZvW70EUgWsHHZSVZ g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-ZvW70EUgWsHHZSVZ .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-ZvW70EUgWsHHZSVZ .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZvW70EUgWsHHZSVZ .edgeLabel .label text{fill:#333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .label div .edgeLabel{color:#333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ #statediagram-barbEnd{fill:#333333;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .cluster-label,#mermaid-svg-ZvW70EUgWsHHZSVZ .nodeLabel{color:#131300;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .note-edge{stroke-dasharray:5;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note text{fill:black;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram-note .nodeLabel{color:black;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagram .edgeLabel{color:red;}#mermaid-svg-ZvW70EUgWsHHZSVZ #dependencyStart,#mermaid-svg-ZvW70EUgWsHHZSVZ #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-ZvW70EUgWsHHZSVZ .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZvW70EUgWsHHZSVZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 创建新文件
git add
修改文件
git add
git commit
暂存后再次修改
git restore
Untracked
Staged
Unmodified
Modified
7.1 查看状态
bash
git status
简洁模式:
bash
git status --short
可能看到:
text
?? new-file.txt
M README.md
M src/App.java
MM src/UserService.java
含义如下:
??:未跟踪文件;- 左侧
M:暂存区中的版本已修改; - 右侧
M:工作区中的版本已修改; MM:文件暂存后又被继续修改。
7.2 跟踪新文件
bash
git add README.md
添加当前目录所有变化:
bash
git add .
添加仓库内所有新增、修改和删除:
bash
git add -A
7.3 交互式暂存
bash
git add -p
该命令可以按代码片段选择需要暂存的内容,适合一个文件中同时包含多个逻辑修改的情况。
7.4 删除文件
同时从工作区和 Git 中删除:
bash
git rm old-file.txt
只取消 Git 跟踪,但保留本地文件:
bash
git rm --cached application-local.yml
7.5 移动或重命名文件
bash
git mv old-name.txt new-name.txt
Git 并不会在对象中永久记录"重命名事件"。很多情况下,Git 会根据内容相似度在查看差异时推断重命名。
8. 暂存区与提交
8.1 为什么需要暂存区
暂存区让开发者可以控制一次提交的边界。
例如,你同时完成了:
- 用户查询接口;
- 日志格式调整;
- 本地配置修改。
如果把它们一次性提交,后续审查、回滚和问题定位都会更加困难。借助暂存区,可以拆分成多个逻辑清晰的提交。
8.2 查看未暂存内容
bash
git diff
该命令比较:
text
工作区 ↔ 暂存区
8.3 查看已暂存内容
bash
git diff --staged
也可以使用:
bash
git diff --cached
该命令比较:
text
暂存区 ↔ HEAD
8.4 创建提交
bash
git commit -m "feat: add user query API"
8.5 打开编辑器编写提交说明
bash
git commit
推荐的提交说明格式:
text
feat(user): add user query API
Add pagination and status filtering for the user list.
Keep the existing response structure for compatibility.
8.6 修改最近一次提交
如果提交后发现遗漏了一个文件:
bash
git add missing-file.txt
git commit --amend --no-edit
如果需要同时修改提交说明:
bash
git commit --amend
--amend 会创建一个新的提交来替换原提交,因此提交 ID 会发生变化。
如果原提交已经推送到共享分支,修改前应先确认团队规则,避免随意改写公共历史。
8.7 提交前检查
bash
git status
git diff
git diff --staged
如果项目支持自动化测试,还应执行对应测试,例如:
bash
mvn test
或者:
bash
npm test
一个提交最好只表达一个完整、独立的逻辑变更。
9. 查看提交历史和代码差异
9.1 查看提交历史
bash
git log
单行显示:
bash
git log --oneline
显示分支图:
bash
git log --graph --decorate --oneline --all
查看最近五条记录:
bash
git log -5
查看某个文件的历史:
bash
git log -- README.md
跟踪文件重命名前的历史:
bash
git log --follow -- README.md
9.2 按作者查询
bash
git log --author="Alice"
9.3 按提交说明查询
bash
git log --grep="login"
9.4 按时间查询
bash
git log --since="2026-08-01" --until="2026-08-31"
9.5 查看某次提交
bash
git show <commit-id>
只查看提交基本信息:
bash
git show --stat <commit-id>
查看某个提交中的文件:
bash
git show <commit-id>:src/App.java
9.6 比较两个提交
bash
git diff <commit-a> <commit-b>
比较两个分支:
bash
git diff main feature/login
查看两个分支从共同祖先开始的变化:
bash
git diff main...feature/login
9.7 查看分支各自拥有的提交
bash
git log --left-right --graph --oneline main...feature/login
9.8 理解双点和三点
bash
git log A..B
表示可从 B 到达、但不可从 A 到达的提交。
bash
git log A...B
表示只属于 A 或只属于 B,但不同时属于两者的提交集合。
需要注意,git log A...B 和 git diff A...B 的语义并不完全相同。复杂场景下应结合命令文档确认。
10. 使用 .gitignore 忽略文件
.gitignore 用于指定不需要纳入版本控制的文件。
例如:
gitignore
# 编译输出
target/
build/
dist/
# 依赖目录
node_modules/
# IDE 配置
.idea/
.vscode/
*.iml
# 日志
*.log
logs/
# 操作系统文件
.DS_Store
Thumbs.db
# 本地配置
.env
application-local.yml
# 临时文件
*.tmp
*.swp
10.1 常见匹配规则
gitignore
*.log
忽略所有 .log 文件。
gitignore
logs/
忽略 logs 目录。
gitignore
/config/local.yml
只忽略仓库根目录下的指定文件。
gitignore
!important.log
即使前面的规则忽略了日志文件,也尝试保留 important.log。
gitignore
temp/**
忽略 temp 目录下的内容。
10.2 .gitignore 为什么没有生效
.gitignore 只影响尚未被跟踪的文件。
如果文件已经进入版本控制,需要先取消跟踪:
bash
git rm --cached application-local.yml
git commit -m "chore: stop tracking local configuration"
取消跟踪整个目录:
bash
git rm -r --cached target
10.3 查看是哪条规则忽略了文件
bash
git check-ignore -v application-local.yml
10.4 全局忽略规则
创建一个全局忽略文件,然后执行:
bash
git config --global core.excludesFile ~/.gitignore_global
适合放入操作系统或个人工具生成的文件,但项目自身产生的文件仍应写入仓库中的 .gitignore。
11. 分支、HEAD 与标签
11.1 查看分支
bash
git branch
查看本地和远程分支:
bash
git branch -a
查看分支及其上游关系:
bash
git branch -vv
11.2 创建分支
bash
git branch feature/login
创建并切换:
bash
git switch -c feature/login
传统写法:
bash
git checkout -b feature/login
在指定提交上创建分支:
bash
git switch -c hotfix/payment <commit-id>
11.3 切换分支
bash
git switch main
切换前应检查工作区:
bash
git status
如果未提交的修改会被目标分支覆盖,Git 通常会阻止切换。
11.4 重命名分支
重命名当前分支:
bash
git branch -m new-name
重命名指定分支:
bash
git branch -m old-name new-name
11.5 删除分支
安全删除已合并分支:
bash
git branch -d feature/login
强制删除:
bash
git branch -D feature/login
删除远程分支:
bash
git push origin --delete feature/login
11.6 什么是远程跟踪分支
执行:
bash
git fetch origin
后,本地可能出现:
text
origin/main
origin/develop
origin/feature/login
这些是本地保存的远程状态引用,不是直接运行在服务器上的分支。
例如:
main:本地分支;origin/main:本地记录的远程main状态;- 远程服务器上的
main:真正的远程分支。
11.7 创建标签
轻量标签:
bash
git tag v1.0.0
附注标签:
bash
git tag -a v1.0.0 -m "Release version 1.0.0"
给指定提交打标签:
bash
git tag -a v1.0.0 <commit-id> -m "Release version 1.0.0"
查看标签:
bash
git tag
git show v1.0.0
推送单个标签:
bash
git push origin v1.0.0
推送全部标签:
bash
git push origin --tags
删除本地标签:
bash
git tag -d v1.0.0
删除远程标签:
bash
git push origin --delete v1.0.0
正式发布版本时,通常推荐使用附注标签。
12. 合并分支与解决冲突
12.1 合并分支
假设要把 feature/login 合并到 main:
bash
git switch main
git merge feature/login
这里的含义是:
把
feature/login的修改合并到当前所在的main分支。
12.2 Fast-forward 合并
如果 main 在创建功能分支后没有新提交,Git 可以直接移动 main 指针:
text
合并前:
A---B main
\
C---D feature/login
合并后:
A---B---C---D main
feature/login
这种情况不需要创建新的合并提交。
只允许 Fast-forward:
bash
git merge --ff-only feature/login
12.3 创建 Merge Commit
如果两个分支都继续产生了提交,Git 通常需要执行三方合并:
text
C---D feature/login
/ \
A---B---E---M main
M 是合并提交,它通常有两个父提交。
即使可以 Fast-forward,也强制创建合并提交:
bash
git merge --no-ff feature/login
12.4 什么情况下会产生冲突
当两个分支修改了同一文件中无法自动协调的区域时,可能产生冲突。
冲突标记通常如下:
text
<<<<<<< HEAD
当前分支的内容
=======
被合并分支的内容
>>>>>>> feature/login
12.5 解决冲突的标准流程
第一步,查看冲突文件:
bash
git status
第二步,打开文件,根据业务要求保留正确内容,并删除冲突标记。
第三步,暂存解决后的文件:
bash
git add src/UserService.java
第四步,完成合并:
bash
git commit
部分 Git 版本和场景也可以执行:
bash
git merge --continue
12.6 放弃合并
bash
git merge --abort
该命令会尝试恢复到开始合并之前的状态。
如果合并前工作区本身不干净,恢复可能会更复杂,因此执行合并前最好先提交或暂存本地修改。
12.7 冲突中的三个版本
解决合并冲突时,索引中可能保存三个版本:
- Stage 1:共同祖先版本;
- Stage 2:当前分支版本,也就是 ours;
- Stage 3:被合并分支版本,也就是 theirs。
查看冲突版本:
bash
git ls-files -u
在普通 merge 场景中选择当前分支版本:
bash
git checkout --ours path/to/file
选择被合并分支版本:
bash
git checkout --theirs path/to/file
然后暂存:
bash
git add path/to/file
不要在没有理解业务逻辑的情况下机械选择 ours 或 theirs。
13. Rebase 变基
13.1 Rebase 是什么
Rebase 会把一组提交重新应用到另一个基点上。
假设当前历史如下:
text
C---D feature
/
A---B---E---F main
在 feature 分支执行:
bash
git rebase main
可能得到:
text
A---B---E---F main
\
C'---D' feature
C' 和 D' 是重新创建的新提交,因此提交 ID 与原来的 C、D 不同。
13.2 Rebase 的优点
- 提交历史更加线性;
- 功能分支可以基于最新主分支整理;
- 减少不必要的合并提交;
- 便于代码审查和历史阅读。
13.3 Rebase 的风险
Rebase 会改写提交历史。
不要随意对多人共享、已经公开使用的分支执行 Rebase,否则其他协作者的提交关系可能被打乱。
一个常见原则是:
可以整理尚未公开的个人分支,不要随意改写公共共享历史。
13.4 Rebase 冲突处理
bash
git rebase main
发生冲突后:
bash
git status
修改文件并暂存:
bash
git add conflict-file.txt
继续:
bash
git rebase --continue
跳过当前提交:
bash
git rebase --skip
放弃整个 Rebase:
bash
git rebase --abort
13.5 交互式 Rebase
整理最近三个提交:
bash
git rebase -i HEAD~3
常见操作:
| 操作 | 含义 |
|---|---|
pick |
保留提交 |
reword |
修改提交说明 |
edit |
暂停并修改提交 |
squash |
合并到前一个提交,并编辑说明 |
fixup |
合并到前一个提交,丢弃当前说明 |
drop |
删除提交 |
交互式 Rebase 适合在推送代码前整理个人提交历史。
13.6 Merge 和 Rebase 如何选择
| 场景 | 推荐方式 |
|---|---|
| 个人功能分支同步最新主分支 | 可以使用 Rebase |
| 合并公共长期分支 | 通常使用 Merge |
| 需要保留真实分支拓扑 | Merge |
| 希望历史保持线性 | Rebase 或 Squash Merge |
| 提交已经被多人基于其继续开发 | 避免 Rebase |
没有一种方式适用于所有团队,关键是团队统一规则。
14. 远程仓库与多人协作
14.1 查看远程仓库
bash
git remote -v
14.2 添加远程仓库
bash
git remote add origin git@github.com:example/project.git
14.3 修改远程地址
bash
git remote set-url origin git@github.com:example/new-project.git
14.4 查看远程详细信息
bash
git remote show origin
14.5 Fetch:只获取,不自动合并
bash
git fetch origin
git fetch 会更新远程跟踪分支,例如 origin/main,但不会直接修改当前分支中的代码。
获取后可以先检查:
bash
git log --graph --oneline --decorate main..origin/main
git diff main...origin/main
确认后再合并:
bash
git merge origin/main
14.6 Pull:获取并整合
bash
git pull
git pull 通常相当于先执行 git fetch,再根据配置执行 Merge 或 Rebase。
明确使用 Merge:
bash
git pull --no-rebase
明确使用 Rebase:
bash
git pull --rebase
只接受 Fast-forward:
bash
git pull --ff-only
为了减少意外合并,很多团队会使用:
bash
git config --global pull.ff only
这不是所有团队的唯一正确选择,应根据项目工作流决定。
14.7 Push:推送本地提交
bash
git push origin main
第一次推送并建立上游关系:
bash
git push -u origin feature/login
建立上游后,可以直接执行:
bash
git push
git pull
14.8 为什么 Push 会被拒绝
常见提示:
text
rejected
non-fast-forward
通常说明远程分支包含本地没有的提交。
可以先执行:
bash
git fetch origin
git log --graph --oneline --decorate --all
然后根据团队规则选择:
bash
git merge origin/main
或者:
bash
git rebase origin/main
处理完成后再推送。
14.9 强制推送
普通强制推送:
bash
git push --force
该命令可能覆盖他人的远程提交,风险很高。
相对更安全的方式是:
bash
git push --force-with-lease
--force-with-lease 会在远程分支状态与本地预期不一致时拒绝覆盖,但它仍然属于改写远程历史的操作,使用前必须确认分支范围和团队规则。
14.10 一个仓库配置多个远程地址
bash
git remote add origin git@github.com:your-name/project.git
git remote add upstream git@github.com:source-org/project.git
常见于 Fork 工作流:
origin:自己的仓库;upstream:原始仓库。
同步上游:
bash
git fetch upstream
git switch main
git merge --ff-only upstream/main
git push origin main
15. 撤销修改、回退版本与恢复数据
Git 撤销操作最容易出错。执行前必须先判断:
- 修改是否已经暂存;
- 修改是否已经提交;
- 提交是否已经推送;
- 分支是否由多人共享;
- 本地修改是否还有保留价值。
15.1 撤销工作区修改
恢复单个文件到暂存区版本:
bash
git restore README.md
恢复所有工作区修改:
bash
git restore .
这会丢弃未暂存修改,执行前应确认这些内容不再需要。
15.2 取消暂存
bash
git restore --staged README.md
该命令只把文件移出暂存区,不会删除工作区修改。
传统写法:
bash
git reset HEAD README.md
15.3 恢复指定提交中的文件
bash
git restore --source=<commit-id> path/to/file
如果希望把该版本同时放入暂存区:
bash
git restore --source=<commit-id> --staged --worktree path/to/file
15.4 撤销最近一次提交但保留暂存内容
bash
git reset --soft HEAD~1
结果:
- 分支回到前一个提交;
- 修改仍保留在暂存区;
- 工作区内容不变。
15.5 撤销最近一次提交并取消暂存
bash
git reset HEAD~1
默认模式是 --mixed。
结果:
- 分支回到前一个提交;
- 暂存区被重置;
- 修改仍保留在工作区。
15.6 丢弃提交和本地修改
bash
git reset --hard HEAD~1
该命令会同时重置:
- HEAD;
- 暂存区;
- 工作区。
这是高风险操作,未提交的修改可能直接丢失。
15.7 reset 三种常见模式
| 命令 | 移动 HEAD | 重置暂存区 | 重置工作区 |
|---|---|---|---|
git reset --soft |
是 | 否 | 否 |
git reset --mixed |
是 | 是 | 否 |
git reset --hard |
是 | 是 | 是 |
15.8 撤销已经公开的提交
如果错误提交已经推送到共享分支,通常优先使用:
bash
git revert <commit-id>
git revert 不删除原提交,而是创建一个新的反向提交。
撤销最近一次提交:
bash
git revert HEAD
这种方式保留完整历史,更适合公共分支。
15.9 Reset 和 Revert 的区别
| 对比项 | Reset | Revert |
|---|---|---|
| 是否移动分支指针 | 是 | 否 |
| 是否创建新提交 | 通常否 | 是 |
| 是否改写历史 | 是 | 否 |
| 适合本地未公开提交 | 是 | 可以但不一定必要 |
| 适合共享分支 | 通常不推荐 | 推荐 |
15.10 使用 Reflog 恢复误删提交
Reflog 会记录本地引用曾经指向的位置。
查看记录:
bash
git reflog
可能看到:
text
8c31a4d HEAD@{0}: reset: moving to HEAD~1
af42e91 HEAD@{1}: commit: feat: add payment API
恢复误删提交:
bash
git switch -c recovery af42e91
或者确认无误后重置当前分支:
bash
git reset --hard af42e91
更稳妥的做法是先创建恢复分支,检查内容后再决定如何合并。
Reflog 通常是本地记录,不应把它当作永久备份。记录可能因过期清理而消失。
15.11 清理未跟踪文件
预览将被删除的文件:
bash
git clean -n
删除未跟踪文件:
bash
git clean -f
预览未跟踪文件和目录:
bash
git clean -nd
删除未跟踪文件和目录:
bash
git clean -fd
git clean 删除的内容通常没有进入 Git 对象数据库,恢复难度很高。因此必须先使用 -n 预览。
16. Stash、Cherry-pick 与临时开发
16.1 Stash 的作用
当功能尚未完成,但需要临时切换分支处理紧急问题时,可以暂存当前修改:
bash
git stash push -m "WIP: user import"
查看 Stash:
bash
git stash list
恢复最近一次 Stash,并保留记录:
bash
git stash apply
恢复并删除记录:
bash
git stash pop
恢复指定记录:
bash
git stash apply stash@{1}
删除指定记录:
bash
git stash drop stash@{1}
清空所有 Stash:
bash
git stash clear
16.2 暂存未跟踪文件
默认情况下,未跟踪文件通常不会进入 Stash。
包含未跟踪文件:
bash
git stash push -u -m "WIP: include untracked files"
包含被忽略文件:
bash
git stash push -a -m "WIP: include all files"
16.3 从 Stash 创建分支
bash
git stash branch feature/recovery stash@{0}
该命令适合 Stash 与当前代码差异过大、直接恢复容易冲突的场景。
16.4 Cherry-pick 的作用
git cherry-pick 可以把某个已有提交引入当前分支。
bash
git cherry-pick <commit-id>
常见场景:
- 把修复提交同步到发布分支;
- 从错误分支取回某个提交;
- 只选择另一个分支中的部分提交。
16.5 Cherry-pick 多个提交
依次选择多个提交:
bash
git cherry-pick <commit-a> <commit-b>
选择一个提交范围:
bash
git cherry-pick <start-commit>^..<end-commit>
16.6 处理 Cherry-pick 冲突
解决冲突后:
bash
git add conflict-file.txt
git cherry-pick --continue
跳过当前提交:
bash
git cherry-pick --skip
放弃整个操作:
bash
git cherry-pick --abort
16.7 Cherry-pick 的注意事项
Cherry-pick 会创建新的提交,因此提交 ID 会改变。
如果大量依赖 Cherry-pick 在分支之间同步代码,可能出现:
- 重复提交;
- 历史关系难以理解;
- 后续合并冲突;
- 修复遗漏到某些分支。
它适合有明确目标的单个或少量提交,不应替代正常的分支合并流程。
17. 使用 Bisect、Blame 和 Grep 定位问题
17.1 使用 git blame 查找代码来源
bash
git blame src/UserService.java
查看指定行:
bash
git blame -L 20,40 src/UserService.java
git blame 可以显示:
- 每一行对应的提交;
- 作者;
- 提交时间;
- 行号。
但它只能告诉你"最后一次修改这行的是谁",不能直接证明 Bug 责任。还应结合提交上下文和业务背景分析。
17.2 使用 git grep 搜索代码
bash
git grep "UserService"
显示行号:
bash
git grep -n "UserService"
在指定提交中搜索:
bash
git grep "UserService" <commit-id>
17.3 使用 git bisect 二分定位问题
当你知道:
- 当前版本有问题;
- 某个历史版本没有问题;
可以使用二分查找定位引入问题的提交。
开始:
bash
git bisect start
标记当前版本为坏版本:
bash
git bisect bad
标记一个正常版本:
bash
git bisect good <good-commit-id>
Git 会检出中间提交。测试后继续标记:
bash
git bisect good
或者:
bash
git bisect bad
最终 Git 会定位最可能首次引入问题的提交。
结束并恢复:
bash
git bisect reset
17.4 自动执行 Bisect
如果有可自动判断成功或失败的测试脚本:
bash
git bisect run ./test.sh
约定通常是:
- 返回码
0:正常; - 返回码
1到127中除特殊保留值外:失败; - 返回码
125:当前提交无法测试,跳过。
自动 Bisect 对定位历史回归问题非常高效。
18. Worktree、Submodule 与 Git LFS
18.1 Git Worktree
普通情况下,一个 Git 仓库目录只能同时检出一个分支。
使用 Worktree,可以为同一个仓库创建多个工作目录:
bash
git worktree add ../project-hotfix hotfix/payment
查看 Worktree:
bash
git worktree list
删除 Worktree:
bash
git worktree remove ../project-hotfix
清理无效记录:
bash
git worktree prune
常见用途:
- 当前功能开发不想中断,同时修复生产问题;
- 同时运行不同版本;
- 比较两个分支;
- 在独立目录中执行构建或测试。
18.2 Git Submodule
Submodule 用于在一个 Git 仓库中引用另一个独立 Git 仓库的特定提交。
添加子模块:
bash
git submodule add git@github.com:example/common.git common
克隆包含子模块的仓库:
bash
git clone --recurse-submodules git@github.com:example/project.git
初始化已有子模块:
bash
git submodule update --init --recursive
更新子模块到父仓库记录的提交:
bash
git submodule update --recursive
父仓库记录的是子模块提交 ID,而不是子模块所有文件的普通副本。
修改子模块时,需要分别考虑:
- 子模块仓库中的提交;
- 父仓库中的 Gitlink 更新;
- 子模块远程分支;
- 团队成员是否能访问子模块远程地址。
18.3 Git LFS
Git 不适合频繁管理大型二进制文件,例如:
- 大型视频;
- PSD 文件;
- 模型文件;
- 大型压缩包;
- 游戏资源;
- 数据集。
Git LFS 会在普通 Git 历史中保存指针,并将真实大文件存储在 LFS 服务中。
安装后初始化:
bash
git lfs install
跟踪文件类型:
bash
git lfs track "*.psd"
git lfs track "*.zip"
这会修改 .gitattributes,应把它一并提交:
bash
git add .gitattributes
git commit -m "chore: track large assets with Git LFS"
Git LFS 不是 Git 核心对象存储的完全替代品,还需要远程平台提供相应支持和配额。
19. 常见 Git 工作流
19.1 Feature Branch 工作流
每个功能创建独立分支:
text
main
├── feature/login
├── feature/payment
└── fix/user-cache
基本流程:
bash
git switch main
git pull --ff-only
git switch -c feature/login
# 开发并提交
git add .
git commit -m "feat(auth): add password login"
git push -u origin feature/login
然后创建 Pull Request 或 Merge Request。
适合:
- 大多数团队项目;
- 需要代码审查;
- 功能相对独立;
- 有持续集成检查。
19.2 GitHub Flow
GitHub Flow 通常围绕一个可随时发布的主分支展开:
- 从
main创建短期分支; - 开发并持续推送;
- 创建 Pull Request;
- 自动测试和代码审查;
- 合并到
main; - 部署;
- 删除功能分支。
它适合持续交付和频繁发布的项目。
19.3 Git Flow
Git Flow 常见分支包括:
main:正式发布版本;develop:日常开发集成;feature/*:功能分支;release/*:发布准备分支;hotfix/*:生产紧急修复分支。
示意图:
#mermaid-svg-BpHqzsVgJhOJ1kYP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-BpHqzsVgJhOJ1kYP .error-icon{fill:#552222;}#mermaid-svg-BpHqzsVgJhOJ1kYP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-BpHqzsVgJhOJ1kYP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-BpHqzsVgJhOJ1kYP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-BpHqzsVgJhOJ1kYP .marker.cross{stroke:#333333;}#mermaid-svg-BpHqzsVgJhOJ1kYP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-BpHqzsVgJhOJ1kYP p{margin:0;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-id,#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-msg,#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label{fill:lightgrey;color:lightgrey;font-family:'trebuchet ms',verdana,arial,sans-serif;font-family:var(--mermaid-font-family);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label0{fill:#ffffff;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit0{stroke:hsl(240, 100%, 46.2745098039%);fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight0{stroke:hsl(60, 100%, 3.7254901961%);fill:hsl(60, 100%, 3.7254901961%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label0{fill:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow0{stroke:hsl(240, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label1{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit1{stroke:hsl(60, 100%, 43.5294117647%);fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight1{stroke:rgb(0, 0, 160.5);fill:rgb(0, 0, 160.5);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label1{fill:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow1{stroke:hsl(60, 100%, 43.5294117647%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label2{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit2{stroke:hsl(80, 100%, 46.2745098039%);fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight2{stroke:rgb(48.8333333334, 0, 146.5000000001);fill:rgb(48.8333333334, 0, 146.5000000001);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label2{fill:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow2{stroke:hsl(80, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label3{fill:#ffffff;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit3{stroke:hsl(210, 100%, 46.2745098039%);fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight3{stroke:rgb(146.5000000001, 73.2500000001, 0);fill:rgb(146.5000000001, 73.2500000001, 0);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label3{fill:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow3{stroke:hsl(210, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label4{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit4{stroke:hsl(180, 100%, 46.2745098039%);fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight4{stroke:rgb(146.5000000001, 0, 0);fill:rgb(146.5000000001, 0, 0);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label4{fill:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow4{stroke:hsl(180, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label5{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit5{stroke:hsl(150, 100%, 46.2745098039%);fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight5{stroke:rgb(146.5000000001, 0, 73.2500000001);fill:rgb(146.5000000001, 0, 73.2500000001);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label5{fill:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow5{stroke:hsl(150, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label6{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit6{stroke:hsl(300, 100%, 46.2745098039%);fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight6{stroke:rgb(0, 146.5000000001, 0);fill:rgb(0, 146.5000000001, 0);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label6{fill:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow6{stroke:hsl(300, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch-label7{fill:black;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit7{stroke:hsl(0, 100%, 46.2745098039%);fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight7{stroke:rgb(0, 146.5000000001, 146.5000000001);fill:rgb(0, 146.5000000001, 146.5000000001);}#mermaid-svg-BpHqzsVgJhOJ1kYP .label7{fill:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow7{stroke:hsl(0, 100%, 46.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .branch{stroke-width:1;stroke:#333333;stroke-dasharray:2;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-label{font-size:10px;fill:#000021;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-label-bkg{font-size:10px;fill:#ffffde;opacity:0.5;}#mermaid-svg-BpHqzsVgJhOJ1kYP .tag-label{font-size:10px;fill:#131300;}#mermaid-svg-BpHqzsVgJhOJ1kYP .tag-label-bkg{fill:#ECECFF;stroke:hsl(240, 60%, 86.2745098039%);}#mermaid-svg-BpHqzsVgJhOJ1kYP .tag-hole{fill:#333;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-merge{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-reverse{stroke:#ECECFF;fill:#ECECFF;stroke-width:3;}#mermaid-svg-BpHqzsVgJhOJ1kYP .commit-highlight-inner{stroke:#ECECFF;fill:#ECECFF;}#mermaid-svg-BpHqzsVgJhOJ1kYP .arrow{stroke-width:8;stroke-linecap:round;fill:none;}#mermaid-svg-BpHqzsVgJhOJ1kYP .gitTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-BpHqzsVgJhOJ1kYP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} main develop feature/login release/1.1.0 v1.0.0 develop login release fix v1.1.0
Git Flow 分支较多,适合有明确版本周期和多环境发布流程的项目,但对于高频持续交付项目可能显得过于复杂。
19.4 Trunk-Based Development
Trunk-Based Development 强调:
- 围绕一个主干分支开发;
- 功能分支生命周期很短;
- 频繁合并;
- 使用自动化测试保证质量;
- 未完成功能通过 Feature Flag 隐藏;
- 减少长期分支。
这种模式对持续集成、自动化测试和小批量提交能力要求较高。
19.5 Fork 工作流
常见于开源项目:
- Fork 原始仓库;
- 克隆自己的 Fork;
- 添加
upstream; - 创建功能分支;
- 推送到自己的仓库;
- 向原仓库提交 Pull Request。
bash
git clone git@github.com:your-name/project.git
cd project
git remote add upstream git@github.com:source-org/project.git
git fetch upstream
git switch -c fix/login upstream/main
19.6 如何选择工作流
| 项目情况 | 推荐方向 |
|---|---|
| 小团队、持续交付 | GitHub Flow 或 Trunk-Based |
| 大多数普通研发团队 | Feature Branch |
| 明确版本周期、多发布分支 | Git Flow 或其简化版本 |
| 开源项目 | Fork + Pull Request |
| 自动化测试不足 | 先完善质量门禁,再缩短分支周期 |
工作流不是越复杂越专业。分支和流程应服务于交付,而不是增加无意义的管理成本。
20. 如何编写高质量 Commit
20.1 一个提交只解决一个问题
不推荐:
text
修改用户接口、升级依赖、修复支付问题、调整格式
推荐拆分为:
text
feat(user): add user status filter
fix(payment): avoid duplicate refund
build: upgrade Spring Boot version
style: format user controller
20.2 Conventional Commits
常见格式:
text
<type>(<scope>): <subject>
完整形式:
text
<type>(<scope>): <subject>
<body>
<footer>
常见类型:
| 类型 | 含义 |
|---|---|
feat |
新功能 |
fix |
Bug 修复 |
docs |
文档修改 |
style |
不影响逻辑的格式调整 |
refactor |
重构 |
perf |
性能优化 |
test |
测试修改 |
build |
构建系统或依赖修改 |
ci |
CI 配置修改 |
chore |
其他维护性修改 |
revert |
撤销提交 |
示例:
text
feat(user): add account status filter
text
fix(payment): prevent duplicate refund submission
Reject repeated refund requests with the same business ID.
Closes: PAY-1024
20.3 Breaking Change
存在不兼容变更时,可以使用:
text
feat(api)!: remove deprecated user endpoint
或者在页脚说明:
text
BREAKING CHANGE: `/api/v1/users` has been removed.
20.4 高质量提交的特点
- 变更范围清晰;
- 提交说明准确;
- 不包含无关文件;
- 可以独立构建或验证;
- 不提交密码和密钥;
- 不提交本地临时配置;
- 不混入大规模无意义格式化;
- 必要时说明为什么修改,而不仅是修改了什么。
20.5 提交前自查
bash
git status
git diff
git diff --staged
git diff --check
然后执行项目测试。
确认暂存内容后再提交:
bash
git commit -m "fix(order): avoid duplicate order creation"
21. Git 安全规范与团队最佳实践
21.1 不要提交敏感信息
禁止提交:
- 数据库密码;
- API Key;
- Access Token;
- SSH 私钥;
- 云服务凭证;
- 生产环境配置;
- 用户隐私数据;
- 内部证书私钥。
即使之后删除文件,敏感信息仍可能保留在 Git 历史中。
如果密钥已经提交:
- 立即吊销或轮换密钥;
- 评估泄露范围;
- 清理 Git 历史;
- 通知相关协作者重新同步;
- 增加密钥扫描和提交前检查。
"从最新提交中删除文件"不能代替密钥轮换。
21.2 谨慎执行危险命令
以下命令执行前必须确认影响范围:
bash
git reset --hard
git clean -fd
git branch -D
git push --force
git rebase
git filter-branch
优先采用:
- 先运行
git status; - 先创建备份分支;
git clean先使用-n;- 强制推送优先考虑
--force-with-lease; - 公共提交优先使用
git revert; - 使用
git reflog查找可恢复位置。
创建安全分支:
bash
git branch backup-before-rewrite
21.3 不要直接在主分支开发
推荐:
bash
git switch main
git pull --ff-only
git switch -c feature/order-query
开发完成后通过 Pull Request 或 Merge Request 合并。
21.4 保持分支生命周期短
长期不合并的分支容易产生:
- 大量冲突;
- 重复开发;
- 集成风险;
- 测试滞后;
- 代码审查困难。
更推荐:
- 小步开发;
- 频繁同步主分支;
- 尽早创建 Draft Pull Request;
- 每次提交保持可审查;
- 尽快合并并删除分支。
21.5 保护主分支
远程平台通常可以配置:
- 禁止直接 Push;
- 必须通过 Pull Request;
- 必须通过自动化测试;
- 必须完成代码审查;
- 必须解决所有讨论;
- 禁止普通强制推送;
- 要求提交签名;
- 要求分支保持最新。
21.6 使用 Hooks 做本地检查
Git Hooks 位于:
text
.git/hooks/
常见 Hook:
pre-commit:提交前执行;commit-msg:检查提交说明;pre-push:推送前执行;post-merge:合并后执行。
Hooks 可以用于:
- 格式检查;
- 静态扫描;
- 单元测试;
- Commit Message 校验;
- 密钥扫描。
需要注意,普通 .git/hooks 内容不会随着 git clone 自动分发。团队通常会使用脚本、构建工具或专门的 Hook 管理方案统一安装。
21.7 避免提交生成物和本地环境文件
通常不应提交:
text
target/
build/
node_modules/
.idea/
*.log
.env
application-local.yml
但并非所有生成文件都应该忽略。例如:
- 依赖锁文件通常应该提交;
- 数据库迁移脚本应该提交;
- 构建产物是否提交取决于发布方式;
- 代码生成结果是否提交取决于团队规范。
22. 一个完整的 Git 实战流程
下面演示从创建仓库到完成一个功能分支的完整过程。
22.1 初始化仓库
bash
mkdir git-demo
cd git-demo
git init -b main
22.2 创建项目文件
创建 README.md:
markdown
# Git Demo
This is a Git demo project.
检查状态:
bash
git status
22.3 第一次提交
bash
git add README.md
git diff --staged
git commit -m "docs: add project README"
22.4 添加远程仓库
bash
git remote add origin git@github.com:example/git-demo.git
git remote -v
22.5 推送主分支
bash
git push -u origin main
22.6 创建功能分支
bash
git switch -c feature/hello
创建 hello.txt 后执行:
bash
git status
git diff
git add hello.txt
git diff --staged
git commit -m "feat: add hello message"
22.7 推送功能分支
bash
git push -u origin feature/hello
然后在代码托管平台创建 Pull Request 或 Merge Request。
22.8 同步主分支最新代码
bash
git fetch origin
git switch feature/hello
git rebase origin/main
如果团队使用 Merge:
bash
git merge origin/main
不要在同一功能分支上混乱地交替使用不同策略,团队应统一规则。
22.9 本地模拟合并检查
bash
git switch main
git pull --ff-only
git merge --no-ff feature/hello
运行测试,确认无误。
如果这只是本地模拟、不希望保留合并结果,可以在明确没有需要保留的本地修改时恢复。实际团队协作中通常应通过平台合并,不应随意重写已共享历史。
22.10 删除已合并分支
bash
git branch -d feature/hello
git push origin --delete feature/hello
22.11 创建发布标签
bash
git tag -a v1.0.0 -m "Release version 1.0.0"
git push origin v1.0.0
最终历史可能类似:
text
* 92ac821 (HEAD -> main, tag: v1.0.0) Merge feature/hello
|\
| * 51d26f3 feat: add hello message
|/
* a4173b2 docs: add project README
23. Git 常用命令速查表
23.1 基础配置
bash
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --list --show-origin
23.2 仓库操作
bash
git init
git init -b main
git clone <repository-url>
git status
23.3 暂存与提交
bash
git add <file>
git add .
git add -A
git add -p
git commit -m "message"
git commit --amend
23.4 查看差异
bash
git diff
git diff --staged
git diff HEAD
git diff <commit-a> <commit-b>
git show <commit-id>
23.5 查看历史
bash
git log
git log --oneline
git log --graph --decorate --oneline --all
git log --follow -- <file>
git reflog
23.6 分支操作
bash
git branch
git branch -a
git branch -vv
git switch <branch>
git switch -c <new-branch>
git branch -m <new-name>
git branch -d <branch>
23.7 合并与变基
bash
git merge <branch>
git merge --abort
git rebase <branch>
git rebase --continue
git rebase --abort
git rebase -i HEAD~3
23.8 远程操作
bash
git remote -v
git remote add origin <url>
git fetch origin
git pull --ff-only
git push
git push -u origin <branch>
git push origin --delete <branch>
23.9 撤销与恢复
bash
git restore <file>
git restore --staged <file>
git reset --soft HEAD~1
git reset HEAD~1
git reset --hard HEAD~1
git revert <commit-id>
git reflog
23.10 临时保存
bash
git stash push -m "message"
git stash push -u -m "message"
git stash list
git stash apply
git stash pop
git stash drop
23.11 标签
bash
git tag
git tag -a v1.0.0 -m "Release v1.0.0"
git push origin v1.0.0
git push origin --tags
23.12 问题定位
bash
git blame <file>
git grep "keyword"
git bisect start
git bisect good <commit>
git bisect bad
git bisect reset
24. 常见问题
24.1 为什么 git add 后还要 git commit
git add 只是把内容放入暂存区,git commit 才会创建正式的本地提交。
text
工作区 --git add--> 暂存区 --git commit--> 本地仓库
24.2 为什么 git commit 后 GitHub 上没有变化
因为提交只保存在本地,还需要推送:
bash
git push
24.3 为什么 git pull 后出现 Merge Commit
远程分支和本地分支都产生了新提交,Git 根据当前 Pull 配置执行了合并。
如果团队要求线性历史,可以使用:
bash
git pull --rebase
如果只接受 Fast-forward:
bash
git pull --ff-only
24.4 为什么切换分支失败
常见原因是当前存在未提交修改,并且这些修改会被目标分支覆盖。
先检查:
bash
git status
然后选择:
- 提交修改;
- 使用 Stash;
- 确认无用后恢复修改;
- 创建新分支保留当前工作。
24.5 为什么删除分支时提示未合并
使用:
bash
git branch -d feature/test
时,Git 会检查该分支是否已经合并。
如果确实不再需要,可以强制删除:
bash
git branch -D feature/test
删除前建议先查看未合并提交:
bash
git log main..feature/test
24.6 为什么 .gitignore 不生效
文件可能已经被 Git 跟踪。
可以执行:
bash
git rm --cached <file>
然后提交取消跟踪的修改。
24.7 如何找回误删分支
查看 Reflog:
bash
git reflog
找到原分支最后一个提交,然后创建恢复分支:
bash
git switch -c recovered-branch <commit-id>
24.8 如何撤销已经推送的提交
共享分支通常使用:
bash
git revert <commit-id>
git push
不建议直接对公共分支执行 reset 后强制推送。
24.9 origin/main 是远程分支吗
更准确地说,origin/main 是保存在本地的远程跟踪引用,表示本地最近获取到的远程 main 状态。
它只有在执行下面的命令后才会更新:
bash
git fetch origin
24.10 checkout、switch 和 restore 有什么区别
历史上 git checkout 同时承担:
- 切换分支;
- 创建分支;
- 检出提交;
- 恢复文件。
为了让语义更清晰,Git 后来提供:
git switch:主要负责切换分支;git restore:主要负责恢复文件;git checkout:仍然可用,但承担的功能更多。
新手可以优先使用 switch 和 restore。
24.11 提交越多越好吗
不是。
好的提交应该:
- 足够小,便于审查和回滚;
- 又足够完整,不是毫无意义的碎片;
- 表达一个清晰的逻辑变更;
- 在合理情况下能够独立构建或验证。
24.12 Git 能替代备份系统吗
不能完全替代。
Git 适合管理版本历史,但仍然可能遇到:
- 仓库损坏;
- 本地磁盘故障;
- 远程仓库误删;
- Reflog 过期;
- 未跟踪文件丢失;
- 敏感数据泄露;
- 大文件存储限制。
重要项目仍应配置:
- 远程仓库;
- 权限控制;
- 仓库备份;
- 分支保护;
- 制品备份;
- 数据库和运行数据备份。
25. 总结
Git 的命令很多,但核心模型并不复杂:
text
工作区 -> 暂存区 -> 本地仓库 -> 远程仓库
学习 Git 时,建议重点掌握以下内容:
- 使用
git status判断当前状态; - 使用
git diff确认修改内容; - 使用
git add控制提交范围; - 使用
git commit保存本地版本; - 使用分支隔离不同功能;
- 理解 Merge 和 Rebase 的区别;
- 理解 Fetch、Pull 和 Push 的区别;
- 公共提交优先使用 Revert 撤销;
- 使用 Reflog 恢复误操作;
- 对
reset --hard、clean -fd和强制推送保持谨慎; - 保持提交小而完整;
- 通过 Pull Request、自动化测试和分支保护保证团队代码质量。
Git 真正困难的地方不是记住所有命令,而是准确判断:
- 当前修改位于哪个区域;
- 当前分支指向哪个提交;
- 本地和远程分别有哪些提交;
- 操作是否会改写历史;
- 修改是否已经被其他人使用;
- 出现问题后是否仍有可恢复路径。
只要理解工作区、暂存区、提交、引用和远程跟踪分支之间的关系,大部分 Git 问题都可以通过下面几条命令逐步分析:
bash
git status
git diff
git diff --staged
git branch -vv
git log --graph --decorate --oneline --all
git reflog
在不确定时,先查看状态和历史,再执行修改操作,永远比直接尝试高风险命令更加可靠。