Git 版本管理各模块知识点梳理
一、安装与配置
整体结构:
安装 (Installation)
下载安装各系统对应的安装包即可,跳过。
配置 (Configuration)
Git 的强大之处在于其高度的可定制性,所有配置都保存在纯文本文件中。
1. 配置级别与文件位置 (优先级从低到高)
级别 (Level) | 适用范围 | 配置文件位置 (Unix系统) | 配置文件位置 (Windows系统) |
---|---|---|---|
系统 (System) | 所有用户 | /etc/gitconfig | C:\Program Files\Git\etc\gitconfig |
全局 (Global) | 当前用户的所有仓库 | ~/.gitconfig 或 ~/.config/git/config | C:\Users\<用户名>\.gitconfig |
本地 (Local) | 仅当前仓库 | 当前仓库下的 .git/config | 当前仓库下的 .git\config |
重点:
- 优先级:本地 > 全局 > 系统。低级别的配置会覆盖高级别的相同配置。
- 常用级别:全局配置 (Global) 是最常用的,用于设置用户信息、首选编辑器等个人偏好。
2. 配置命令
使用 git config
命令进行设置。
git config --global <key> <value>
:设置全局配置。git config --local <key> <value>
:设置本地仓库配置(--local
是默认选项,可省略)。git config --system <key> <value>
:设置系统配置(需要管理员权限)。git config --list
:列出所有当前生效的配置。git config --get <key>
:获取某一项的具体配置值。
3. 必须配置的核心项
用户身份 (User Identity)
这是最重要的配置,每次 Git 提交都会使用这个信息,并且它会被永久记录在提交历史中,无法更改。
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
重点与注意事项:
- 必须设置:如果没有配置,在你第一次提交时 Git 会报错并提醒你。
user.email
:必须使用你在代码托管平台(如 GitHub, GitLab)注册时使用的邮箱地址。这样平台才能正确地将你的提交与账户关联,显示你的头像和贡献图。- 多平台切换:如果你在公司电脑和个人电脑上使用不同的邮箱(如公司邮箱和个人邮箱),可以在不同的仓库中使用
git config user.email ...
(不加--global
)进行本地覆盖。
好的,我们来系统地梳理一下 Git 中安装与配置的核心知识点、重点和注意事项。这是每一位开发者使用 Git 的第一步,也是构建高效、无痛工作流的基础。
4. 其他常用配置
文本编辑器 (Default Editor)
当 Git 需要你输入信息(如提交说明、解决冲突)时会启动的编辑器。
# 设置为 VS Code
git config --global core.editor "code --wait"# 设置为 Vim (Unix系统默认)
git config --global core.editor "vim"# 设置为 Notepad++ (Windows)
git config --global core.editor "'C:/Program Files/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"
注意:路径中包含空格时需要用引号括起来。--wait
参数对于 VSCode 等GUI编辑器很重要,它会告诉 Git 等待你关闭编辑器文件后再继续。
差异分析工具 (Diff Tool)
用于 git difftool
命令,以图形化方式比较差异。
# 例如配置为 VS Code 作为 difftool 和 mergetool
git config --global diff.tool vscode
git config --global difftool.vscode.cmd "code --wait --diff $LOCAL $REMOTE"
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd "code --wait $MERGED"
让命令行更友好
# 启用颜色高亮,让命令输出更易读
git config --global color.ui auto# 设置命令别名,极大提高效率
git config --global alias.co checkout # git co
git config --global alias.br branch # git br
git config --global alias.ci commit # git ci
git config --global alias.st status # git st
git config --global alias.last 'log -1 HEAD' # 查看最后一次提交
5. SSH 密钥配置 (重点中的重点)
为了安全地与远程仓库(如 GitHub)通信,需要使用 SSH 密钥进行认证,避免每次推送都输入密码。
-
生成密钥对:
ssh-keygen -t ed25519 -C "your.email@example.com" # 或者,如果系统不支持 ed25519: # ssh-keygen -t rsa -b 4096 -C "your.email@example.com"
- 按回车接受默认的密钥文件保存路径(通常是
~/.ssh/id_ed25519
)。 - 设置一个安全的密码(passphrase),可以为空,但建议设置。
- 按回车接受默认的密钥文件保存路径(通常是
-
将公钥添加到代码平台:
- 复制公钥内容:
cat ~/.ssh/id_ed25519.pub
,全选输出内容并复制。 - 登录 GitHub/GitLab -> Settings -> SSH and GPG keys -> New SSH key。
- 粘贴密钥内容,取一个可识别的标题(如 “My Work Laptop”),然后保存。
- 复制公钥内容:
-
测试连接:
ssh -T git@github.com
如果看到 “Hi username! You’ve successfully authenticated…” 的欢迎信息,即表示成功。
注意事项:
- 私钥是秘密:
id_ed25519
文件是私钥,绝不能泄露给任何人,好比是你家的钥匙。 - 公钥可以公开:
id_ed25519.pub
文件是公钥,可以放心地配置到任何需要的地方。 - 一台设备一个密钥:建议为每台电脑生成独立的密钥对,并分别添加到代码平台。这样如果某台电脑丢失,可以单独撤销其访问权限。
总结: checklist
- 已从官网正确安装 Git,并通过
git --version
验证。 - 已全局配置
user.name
和user.email
,且邮箱与代码托管平台账号一致。 - (可选但推荐) 已配置
core.editor
为自己熟悉的编辑器。 - (推荐) 已配置
color.ui auto
和常用别名 (alias) 来提升效率。 - 已生成 SSH 密钥对,并将公钥正确添加到了 GitHub/GitLab 等平台。
- 已通过
ssh -T
命令测试 SSH 连接成功。
二、创建仓库与初次提交
核心知识结构图
首先,通过一张图快速了解从创建到首次推送的全流程及其核心概念:
第一部分:创建仓库 (git init vs. git clone)
知识点
有两种主要方式可以获得一个 Git 仓库:
-
本地初始化新仓库 (
git init
)- 场景:在本地从头开始一个新项目。
- 命令:在项目根目录执行
git init
。 - 作用:在当前目录创建一个新的
.git
子目录(隐藏文件夹),其中包含所有必需的仓库元数据(如对象数据库、HEAD 指针、暂存区索引等)。此刻,你的项目文件还没有被跟踪。
-
克隆现有仓库 (
git clone
)- 场景:获取一份已存在于远程服务器(如 GitHub、GitLab、Gitee)上的仓库的完整拷贝。
- 命令:
git clone <repository_url>
。 - 作用:
- 拉取(Download)远程仓库的所有历史数据。
- 自动创建一个以仓库名命名的目录。
- 初始化
.git
目录,并将其连接到原始的远程仓库(默认命名为origin
)。 - 自动检出(Checkout)默认分支(通常是
main
或master
)的最新代码。
重点
git init
是“从无到有”,历史由你书写。git clone
是“从有到有”,历史是现成的,你在此基础上协作。
注意事项
- 执行
git init
前,确保你在正确的项目目录下。 git clone
时,如果想把仓库克隆到特定名称的目录,可以在 URL 后面加上目录名:git clone <url> <my-directory-name>
。
第二部分:初次提交 (The First Commit)
初次提交遵循 Git 的基本工作流程,但需要特别注意一些细节。
1. 检查状态 (git status
)
这是你最应该频繁使用的命令,尤其是在提交前。它会告诉你:
- 哪些文件已被修改(Modified)。
- 哪些文件已暂存(Staged),准备提交。
- 哪些文件未被跟踪(Untracked)。
在每一次操作(add, commit, …)前后,都习惯性地运行 git status
,它能让你避免很多错误。
2. 跟踪新文件 (git add
)
未被跟踪的文件(Untracked files)不会被 Git 管理,也不会被提交。你需要明确告诉 Git 开始跟踪它们。
git add <file_name>
:添加单个文件到暂存区。git add .
:添加当前目录及所有子目录下的所有新文件和修改过的文件到暂存区。(慎用,容易加入不必要的文件)git add -A
或git add --all
:添加工作区中所有的变化(包括新文件、修改、删除)到暂存区。git add -u
:添加所有已被跟踪的文件的修改和删除到暂存区(不包含新文件)。
3. 提交更改 (git commit
)
将暂存区的内容创建一个快照,永久保存到版本历史中。
git commit -m "Your commit message"
:直接附加提交信息进行提交。git commit
:会打开配置的文本编辑器(如 Vim, VSCode),让你编写更详细的提交信息。
提交信息 (Commit Message) 规范:
- 第一行(主题):简短摘要(不超过50个字符),说明本次提交的目的。建议使用英文动词开头,如
feat: Add user login feature
,fix: Resolve header alignment issue
。 - 空一行。
- 正文(可选):更详细的描述,说明为什么修改(而不是怎么修改,代码本身能体现)、解决了什么问题、有哪些突破性变化等。
- Footer(可选):例如关联 issue(
Closes #123
)。
(推荐)使用约定式提交 (Conventional Commits):如 feat:
, fix:
, docs:
, style:
, refactor:
, test:
, chore:
等前缀,使历史更清晰,便于自动化生成变更日志。
4. 查看历史 (git log
)
提交后,使用 git log
查看提交历史,确认你的提交已成功记录。
git log --oneline
:以简洁的单行模式查看历史。
第三部分:连接远程仓库并推送 (git remote & git push)
本地提交完成后,通常需要推送到远程仓库进行备份或协作。
-
关联远程仓库 (
git remote add
):- 首先在 GitHub 等平台上创建一个新的空仓库。
- 然后在本地的仓库目录下执行:
# origin 是远程仓库的默认别名,可以自定义但一般不这么做 # <url> 是远程仓库的 SSH 或 HTTPS 地址 git remote add origin <url>
- 使用
git remote -v
验证是否添加成功。
-
推送到远程 (
git push
):- 第一次推送时,需要使用
-u
(或--set-upstream
) 选项,将本地的main
分支(你的默认分支名)与远程的origin/main
分支关联起来。git push -u origin main
- 此后,在该分支上只需要简单的
git push
即可。
- 第一次推送时,需要使用
重点与注意事项
- 分支名称:现在许多平台(如 GitHub)的默认主分支名是
main
,而旧版本 Git 可能是master
。请根据你平台的实际名称推送,否则会报错error: src refspec main does not match any
。如果本地是master
,则命令应改为git push -u origin master
。你可以使用git branch
查看本地当前分支名。 - 认证方式:
- 如果使用 SSH URL(如
git@github.com:username/repo.git
),需要事先配置好 SSH 密钥。 - 如果使用 HTTPS URL(如
https://github.com/username/repo.git
),第一次推送可能会弹出窗口要求你输入平台的用户名和密码(或 Personal Access Token)。
- 如果使用 SSH URL(如
-u
选项:第一次推送时务必记住使用-u
,它为后续的git pull
和git push
建立了跟踪关系,极大简化了操作。
总结:初次提交 checklist
- (可选)已在项目目录执行
git init
或 已通过git clone
获取现有仓库。 - 已使用
git status
检查状态,确认了有哪些文件需要跟踪。 - 已使用
git add
将需要提交的文件添加到暂存区(小心不要提交敏感信息)。 - 已使用
git commit -m "message"
进行提交,并撰写了清晰的提交信息。 - (如需推送)已使用
git remote add origin <url>
关联远程仓库。 - 已使用
git push -u origin main
(或 master) 完成首次推送。
三、查看与对比变更
核心知识结构图
首先,通过一张图来建立对查看与对比变更的全局认知,理解其核心组成与工作流:
第一部分:查看状态 - git status
这是你所有操作的起点,用于回答“我现在处于什么状态?”。
知识点与命令
git status
git status -s # 或 --short,简洁模式输出
重点
- 简洁模式 (
-s
)- 输出两列标识符,非常紧凑,适合快速浏览。
- 第一列是暂存区状态,第二列是工作区状态。
- 标记含义:
??
- 未跟踪文件 (Untracked)A
- 新添加到暂存区的文件 (Added)M
- 修改过的文件 (Modified)。注意:红色M
(第二列) 表示工作区修改未暂存;绿色M
(第一列) 表示修改已暂存。
D
- 删除的文件 (Deleted)R
- 重命名的文件 (Renamed)
注意事项
- 养成习惯:在执行任何
add
或commit
操作之前和之后,都运行git status
来确认你的操作符合预期。 - 看懂输出:必须清楚区分 Changes to be committed (已暂存,绿色) 和 Changes not staged for commit (未暂存,红色) 以及 Untracked files (未跟踪)。
第二部分:对比差异 - git diff
这是核心的对比工具,用于回答“具体发生了什么变化?”。
知识点与命令
对比范围 | 命令 | 作用 |
---|---|---|
工作区 vs 暂存区 | git diff | 查看尚未暂存的修改。即,如果你现在 git add ,会添加什么。 |
暂存区 vs 最新提交 | git diff --staged 或 git diff --cached | 查看已暂存但未提交的修改。即,如果你现在 git commit ,会提交什么。 |
工作区 vs 某次提交 | git diff HEAD | 查看工作区与最新提交的所有差异(包括未暂存和已暂存的)。 |
两次提交之间 | git diff <commit1> <commit2> | 对比两次提交的差异。commit 可以是哈希值、分支名、标签名或 HEAD~1 。 |
两个分支之间 | git diff branch1..branch2 | 对比两个分支最新提交的差异。.. 可省略。 |
单个文件变化 | git diff -- <file_path> | 在任何上述命令后加上 -- 和文件名,只查看该文件的差异。 |
重点
- 输出解读:
--- a/file.txt
表示变动前(原始文件)。+++ b/file.txt
表示变动后。- 绿色
+
开头的行表示新增。 - 红色
-
开头的行表示删除。 - 上下文行(无符号)用于帮助理解代码位置。
--stat
选项:在diff
命令后加上--stat
,可以只看摘要统计,了解哪些文件改变了、增加了多少行、删除了多少行,非常高效。git diff --stat git diff HEAD~1 HEAD --stat # 查看上一次提交的统计信息
注意事项
- 顺序很重要:
git diff A B
显示的是从 A 到 B 的变化(即“如何从 A 变成 B”)。git diff B A
则显示反向变化。 git diff
默认不显示未跟踪文件:它只比较已被 Git 跟踪的文件。未跟踪文件(Untracked)的变化需要通过git status
来看。
第三部分:查看历史 - git log
用于查看提交历史,回答“这个项目是如何一步步变成这样的?”。
知识点与命令
# 基础查看
git log# 最实用的组合:单行+图形化显示分支拓扑
git log --oneline --graph --all# 查看最近 n 次提交
git log -n 2 # 查看最近2次# 查看具体文件的修改历史
git log --follow -p -- <file_path># 查看包含具体代码变化的提交(pickaxe search,查找添加或删除某字符串的提交)
git log -S "functionName"# 按作者筛选
git log --author="name"# 按提交信息筛选
git log --grep="keyword"# 美化输出格式
git log --pretty=format:"%h - %an, %ar : %s"
重点
-p
或--patch
:在log
输出中显示每次提交的详细差异(即diff
信息),这是极其强大的功能,可以结合其他选项使用。--oneline --graph --all
:这是分析分支结构和历史的黄金命令,务必掌握。- 过滤与搜索:使用
-S
和--grep
可以像侦探一样在历史中精准定位你关心的提交。
注意事项
git log
输出可能很长:学会用空格键
向下翻页,b
向上翻页,q
退出。- 理解提交哈希:不需要记住完整的哈希值,通常前7位就足以唯一标识一个提交。
第四部分:查看提交详情 - git show
用于快速查看某次提交的详细信息。
知识点与命令
git show # 显示最新提交(HEAD)的详情
git show <commit> # 显示指定提交的详情
git show --name-only <commit> # 只显示该提交修改了哪些文件,不显示具体diff
重点
git show <commit>
的输出 =git log -p -1 <commit>
。它一次性展示了提交的元信息(作者、时间、消息)和具体的变更内容。
总结:高效工作流与 Checklist
- [状态检查] 改完代码后,先运行
git status -s
,快速了解概况。 - [细节确认] 如果有未暂存的修改,运行
git diff
查看具体改了什么。 - [暂存确认] 使用
git add
后,运行git diff --staged
,确认暂存的内容是否正确无误。 - [提交前最后检查] 运行
git status
最后确认,然后提交。 - [历史追溯] 需要查看为什么代码变成这样时,使用
git log --oneline --graph -p -- <file>
深入挖掘。 - [快速查看] 只想看某次提交干了啥,用
git show <commit>
。
四、撤销与回退
核心知识结构图
首先,通过一张图来建立全局认知,理解不同撤销命令所作用的不同区域:
第一部分:撤销工作区的修改(未 git add
)
场景:你修改了文件,但还没有执行 git add
,此时你觉得修改得不好,想丢弃这些修改,还原成最后一次提交时的样子。
命令与知识点
# 传统命令
git checkout -- <file-name># 现代命令 (Git 2.23+ 推荐,语义更清晰)
git restore <file-name>
重点
- 这个操作只影响工作区。
- 它用版本库中最新版本的文件覆盖你工作区中对应的文件。
注意事项(极其重要!)
- 这是危险操作! 一旦执行,你的本地修改将彻底丢失,无法通过 Git 找回。只能依靠你的编辑器/IDE 的临时文件恢复功能。
- 执行前请务必通过
git status
和git diff
确认你要丢弃的修改正是你所想的。
第二部分:撤销暂存区的修改(已 git add
,未 git commit
)
场景:你执行了 git add
将修改放入了暂存区,但发现有些文件加多了,或者还需要再修改一下。你想把文件从暂存区撤出来,但保留工作区的修改。
命令与知识点
# 传统命令
git reset HEAD <file-name># 现代命令 (Git 2.23+ 推荐)
git restore --staged <file-name>
重点
- 这个操作影响暂存区,将其恢复到最新提交的状态。
- 它不会丢弃你的工作区修改。文件的状态会从
Changes to be committed
变回Changes not staged for commit
。 - 这是一个安全操作,因为你的修改还在工作区。
第三部分:撤销提交(已 git commit
)
这是最复杂的一部分,分为三种主要场景。
场景 1:修正最后一次提交 (git commit --amend
)
场景:刚刚完成的提交信息写错了,或者漏掉了一个文件。你想修改最后一次提交本身,而不是创建一个新的提交。
命令与知识点
# 1. 如果只是修改提交信息
git commit --amend -m "新的提交信息"# 2. 如果漏了文件,先添加到暂存区,再执行
git add <missed-file>
git commit --amend
# 随后会进入编辑器,你可以修改提交信息,也可以直接保存退出。
重点与注意事项
- 它不会增加新的提交,而是修改了最新的那次提交的哈希值(即创建了一个新的提交对象替换了旧的)。
- 仅适用于尚未推送到远程仓库的本地提交。如果已经推送,强制推送 (
git push -f
) 会导致团队历史混乱,应尽量避免。
场景 2:回退到历史某个提交 (git reset
)
场景:你想彻底丢弃最近的几次提交(包括它们所带来的代码变更),让分支的历史指针直接指回之前的某个提交。
命令与知识点
# 1. 软重置 (Soft Reset):只动历史指针,保留暂存区和工作区。
# 适用于:想重新提交(修改内容或信息)
git reset --soft <commit-hash># 2. 混合重置 (Mixed Reset,默认):动历史指针和暂存区,但保留工作区修改。
# 适用于:想重新逐个文件检查后再提交
git reset --mixed <commit-hash> # --mixed 可省略# 3. 硬重置 (Hard Reset):动历史指针、暂存区和工作区。彻底回到过去。
# 适用于:坚决丢弃所有修改,完全还原到某个旧版本。
git reset --hard <commit-hash>
<commit-hash>
可以是分支名、标签名,但更常见的是通过git log
查看到的提交哈希值(前7位即可)。- 常用写法:
HEAD~1
表示上一个提交,HEAD~2
表示上两个,以此类推。
重点与注意事项
git reset --hard
是极其危险的操作,因为它会强制覆盖你的工作区。任何未提交的修改(包括未暂存和已暂存)都将永久丢失。执行前必须百分百确认。- 同样仅适用于本地分支。如果你已经将提交推送到了远程仓库,之后又执行了
reset
,那么下次推送时必须使用git push -f
(强制推送)来覆盖远程历史。这会重写历史,对共享该分支的其他协作者是灾难性的。除非你非常清楚自己在做什么,否则不要对公共分支执行reset
。
场景 3:反转提交 (git revert
)
场景:你想安全地撤销某个历史提交所带来的更改,但不是通过删除历史的方式,而是通过创建一个新的提交来抵消那个提交的变更。这是撤销公共历史(已推送的提交)最安全、最推荐的方法。
命令与知识点
# 创建一个新的提交,该提交的内容是撤销指定提交的修改
git revert <commit-hash>
执行后 Git 会打开编辑器让你编辑这个反转提交的信息。
重点与注意事项
- 这是最安全的撤销方式,因为它不会重写历史,只是添加新的历史。
- 它适用于任何提交,包括已经推送到远程仓库的提交。
- 如果待撤销的提交是一个合并提交(Merge Commit),可能需要添加
-m
选项指定主线父提交,否则 Git 会提示你。 - 团队协作的标准做法:当你需要撤销一个已经共享给别人的提交时,永远优先选择
git revert
。
总结:选择正确的撤销命令
你的需求 | 安全级别 | 推荐命令 |
---|---|---|
丢弃工作区的修改(未 add ) | ⚠️ 危险 | git restore <file> |
撤销暂存操作(已 add ) | ✅ 安全 | git restore --staged <file> |
修改最后一次提交(未推送) | ⚠️ 本地安全 | git commit --amend |
彻底丢弃最近的几次提交(未推送) | ⚠️ 本地安全 | git reset --hard <commit> |
撤销某个历史提交的更改(已推送) | ✅ 最安全 | git revert <commit> |
黄金法则:
- 操作前先检查:永远先用
git status
和git log
确认当前状态和你要操作的对象。 - 对公共历史保持敬畏:对已经推送 (
git push
) 的提交,只使用git revert
。 - 备份重要工作:在执行任何
--hard
操作或丢弃工作区修改前,可以先手动备份文件或创建一个临时提交(git add . && git commit -m "backup"
),事后再撤销这个临时提交。
五、分支的创建与切换
核心知识结构图
首先,通过一张图快速建立对分支操作的全局认知:
第一部分:理解分支的本质
知识点
- 分支是什么? 分支本质上只是一个指向某个提交的可移动指针。
- HEAD 是什么?
HEAD
是一个特殊的指针,它指向你当前所在的分支(或者说当前检出的分支)。当你提交代码时,你所在的分支指针会向前移动,而HEAD
也跟着它一起移动。 - 默认分支:新仓库的默认分支名称传统上是
master
,但现在更推荐使用main
。你可以通过配置修改默认分支名。
重点
- 创建分支非常快:因为只是创建一个新的指针,而非复制所有文件。
- 切换分支很快:Git 会根据分支所指向的提交,快速还原对应版本的工作目录。
第二部分:分支的基本操作
1. 查看分支 (git branch
)
这是你最常用的命令之一,用于了解当前分支状态。
git branch # 列出所有*本地*分支,当前分支前有 * 号
git branch -v # 查看分支的同时,显示每个分支的最后一次提交
git branch -a # 查看所有分支(包括远程跟踪分支)
git branch -r # 查看远程跟踪分支(Remote Tracking Branches)
2. 创建分支 (git branch <new-branch-name>
)
git branch feature/login # 基于当前所在的提交(HEAD)创建一个名为 feature/login 的新分支
重点:
- 执行此命令后,你仍然停留在原来的分支上。
- 最佳实践:使用清晰的分支命名规范,例如:
feature/<功能名>
(功能分支)bugfix/<问题名>
(修复分支)hotfix/<问题名>
(热修复分支)release/<版本号>
(发布分支)
3. 切换分支 (git checkout
或 git switch
)
# 传统方式 (功能强大但语义稍复杂)
git checkout feature/login# 现代方式 (Git 2.23+ 引入,语义更清晰,专用于切换分支)
git switch feature/login
注意事项(极其重要!):
- 工作目录必须“干净”:在切换分支前,你必须:
- 提交(
commit
)当前所有的修改,或者 - 贮藏(
stash
)当前所有的修改(后续会讲到),或者 - 丢弃当前的修改(不推荐,除非确定不要)。
- 提交(
- 如果当前工作目录的状态会与目标分支的文件冲突,Git 会禁止你切换并提示你处理。
4. 创建并立即切换分支
这是一个极其常用的组合操作。
# 传统方式
git checkout -b feature/payment# 现代方式
git switch -c feature/payment # -c 是 --create 的缩写
重点:
- 该命令等同于依次执行
git branch feature/payment
和git switch feature/payment
。 - 这是你开始一项新任务或新功能的标准起点。
5. 基于特定提交或分支创建新分支
你可以基于任何一个提交点(而不仅仅是当前分支)来创建新分支。
# 先找到目标的提交哈希(比如 abc1234)
git log --oneline# 然后基于这个提交创建新分支
git branch bugfix/old-issue abc1234
git switch bugfix/old-issue
第三部分:删除分支
命令与知识点
# 安全删除:只会删除已经*合并到当前分支*的分支
git branch -d feature/login# 强制删除:无论该分支是否已合并,都会删除
git branch -D experiment
重点与注意事项
- 你不能删除当前所在的分支。必须先切换到其他分支(如
main
),然后再删除目标分支。 -d
是安全卫士:使用-d
时,如果 Git 检测到feature/login
分支的提交还没有被合并到当前所在的分支,它会拒绝删除,防止你意外丢失工作。这是一种安全机制。-D
是强制手段:只有当你百分之百确定不再需要这个分支上的任何代码(例如,它是一个失败的实验品),才使用-D
强制删除。
第四部分:远程分支操作(初步了解)
本地分支常与远程分支协作。
1. 推送本地分支到远程 (git push
)
# 首次推送本地分支,并建立跟踪关系
git push -u origin feature/login
# -u (--set-upstream) 选项是关键,它让本地分支跟踪(track)远程分支# 之后,在该分支上再次推送就简化了
git push
2. 删除远程分支
注意: 这是少数几个本地操作无法直接完成,必须通过 git push
到远程来实现的命令。
git push origin --delete feature/old-feature
# 或者更简短的写法
git push origin :feature/old-feature
3. 获取远程分支信息
你的同事推送了新分支后,你需要先获取远程的元数据信息,才能在本地看到它。
git fetch
之后,你可以基于远程跟踪分支(如 origin/feature/login
)创建一个本地分支来开展工作:
git switch -c feature/login origin/feature/login
总结:最佳实践与工作流
- 主分支保持稳定:
main
/master
分支的代码应始终是可用的、稳定的。开发工作永远在特性分支上进行。 - “早建频切”:尽早地、频繁地创建分支。每个新功能、每个 Bug 修复都应在独立的分支上进行。
- 保持分支短小:一个分支的生命周期应该相对较短(几天,而不是几周),完成特定目标后立即合并并删除,避免分支过多难以管理。
- 清晰的命名:使用一致的、具有描述性的命名规范,让人一眼就能看出分支的用途。
- 切换前保持“干净”:养成习惯,在切换分支前使用
git status
检查工作区,确保它是干净的(clean),以避免冲突和混乱。
常用命令清单:
git branch -av # 查看状态
git switch -c feat/xxx # 创建并切换新分支
git switch main # 切回主分支
git branch -d feat/xxx # 删除已合并的旧分支
git push -u origin feat/xxx # 推送新分支到远程
六、分支的合并与冲突解决
核心知识结构图
首先,通过一张图来建立对合并策略与冲突解决流程的全局认知:
第一部分:合并 (git merge
)
git merge
命令用于将一个分支的更改集成到另一个分支。它是 Git 中最常用的整合分支的方法。
1. 快进合并 (Fast-Forward Merge)
场景:当你要合并的目标分支(例如 main
)自你创建特性分支(例如 feature/login
)以来,没有产生任何新的提交时,就会发生快进合并。
原理:Git 只需要将 main
分支的指针直接移动到 feature/login
分支所指向的最新提交即可。因为历史是一条直线,所以不需要创建新的提交。
操作:
git switch main # 首先切换到要合并到的目标分支
git merge feature/login # 合并特性分支
# 因为满足快进条件,Git 会直接移动指针,完成合并
结果:合并后的历史记录仍然保持线性,非常整洁。
2. 非快进合并 (No-Fast-Forward Merge / 3-Way Merge)
场景:当你要合并的目标分支(main
)在你开发特性分支(feature/login
)的期间有了新的提交,两个分支的历史就产生了“分叉”。
原理:Git 无法简单地移动指针。它会创建一个新的合并提交(Merge Commit),这个提交有两个父提交:一个是原来 main
分支的提交,一个是 feature/login
分支的提交。
操作:
git switch main
git merge feature/login
# 即使不满足快进条件,Git 也会自动进行三方合并,并弹出编辑器让你填写合并提交的信息。
强制创建合并提交:即使满足快进条件,你也可以使用 --no-ff
(no fast-forward) 选项强制 Git 创建一个合并提交。这通常用于保留特性分支存在的历史信息。
git merge --no-ff feature/login
重点与注意事项:
git merge
是向前整合:它总是将其他分支的更改合并到当前所在的分支上。所以执行前务必用git status
或git branch
确认你当前在正确的目标分支上(如main
)。- 合并后是否需要删除特性分支? 通常是的。特性分支的使命已经完成,可以删除以保持仓库整洁:
git branch -d feature/login
。
第二部分:变基 (git rebase
)
变基是另一种整合分支的方法,它被称为“改变历史”的操作。
场景:你希望在合并之前,让你的特性分支的历史看起来像是基于目标分支的最新提交开始的,从而使历史记录呈现一条完美的直线。
原理:git rebase
会提取你在当前特性分支上的所有新提交,取消它们,然后依次在目标分支的最新提交上重新应用这些更改。
操作:
git switch feature/login # 切换到你要变基的特性分支
git rebase main # 将当前分支变基到 main 分支上
变基完成后,feature/login
分支的“基”就变成了 main
分支的最新提交。此时,你再切换回 main
分支进行合并,就一定会是一次快进合并。
git switch main
git merge feature/login # 此时是快进合并
优点:
- 历史记录非常清晰、线性,避免了不必要的合并提交,更容易追踪代码变化。
缺点与注意事项(极其重要!):
- 黄金法则:只对你本地仓库中尚未推送的提交执行变基,永远不要对已经推送到远程仓库的提交执行变基。
- 为什么? 因为变基会重写提交的历史(创建新的提交哈希值)。如果你重写了已经共享的历史,其他协作者基于旧历史的工作将会变得一团糟,同步时会出现极其复杂的冲突。
- 变基更适合在个人特性分支上整理本地提交,然后再合并到公共分支(如
main
)。
第三部分:冲突解决 (Conflict Resolution)
冲突是协作的必然产物,当 Git 无法自动合并两个分支上的同一文件的同一部分时就会发生。
1. 冲突的产生与标识
例如,你和你的同事都修改了 index.html
文件的同一行代码,当你们尝试合并时,Git 会不知道应该保留谁的修改,于是就会产生冲突。
冲突的文件中会有明显的标记:
<<<<<<< HEAD
<h1>这是主分支上的标题</h1>
=======
<h1>这是特性分支上的新标题</h1>
>>>>>>> feature/login
<<<<<<< HEAD
到=======
之间的内容是当前分支(你所在的分支,如main
)的修改。=======
到>>>>>>> feature/login
之间的内容是要合并的分支(如feature/login
)的修改。
2. 解决冲突的步骤
-
识别冲突文件:合并中断后,立即运行
git status
。在 “Unmerged paths” 部分会列出所有产生冲突的文件。 -
手动编辑文件:打开每个冲突文件,找到
<<<<<
,====
,>>>>>
标记。你的任务是和代码作者沟通后,决定保留哪一方的代码,或者将两者的代码融合。完成后,必须完全删除这些冲突标记。<!-- 正确的解决后的样子 --> <h1>这是融合后的最终标题</h1>
-
标记冲突已解决:每个文件解决完冲突后,你需要用
git add
告诉 Git 这个文件已经处理好了。git add index.html
-
完成合并:当所有冲突文件都处理并
add
后,执行git commit
来最终完成这次合并提交。Git 会为你预先准备好提交信息。
重点与注意事项
- 不要害怕冲突:冲突是正常的,它只是要求你手动做出决策。
- 解决前先理解:解决冲突时,一定要理解双方代码的意图,不要简单地随便选一个。与你的同事沟通!
- 可以使用工具:对于复杂的冲突,可以使用图形化合并工具(如 VS Code 内置的冲突解决器、Meld、Beyond Compare)来提高效率。配置方式:
git config --global merge.tool vscode
。 - 如果想中止合并:如果冲突太多太复杂,你想先放弃这次合并,回到合并前的状态,可以执行:
git merge --abort
。
总结:merge
vs rebase
如何选择?
方面 | git merge | git rebase |
---|---|---|
历史记录 | 产生合并提交,保留真实的历史脉络(有分叉)。 | 生成线性的历史记录,更整洁。 |
安全性 | 更安全,不会重写公共历史,适合公共分支。 | 危险,会重写提交历史。只适用于本地未推送的提交。 |
可追溯性 | 合并提交明确记录了分支整合的发生点和时间。 | 难以追溯分支是在哪个确切时间点整合的。 |
适用场景 | 整合公共分支、发布分支、长期特性分支。 | 整理本地特性分支,使其易于快进合并到主分支。 |
黄金实践:
- 对公共分支(如
main
,develop
)永远使用git merge
。 - 在推送你的特性分支之前,可以先在本地用
git rebase
整理你的提交历史。 - 理解团队规范: 遵循你所在团队约定的工作流(Git Flow, GitHub Flow 等)和合并策略。
七、远程仓库基础
核心知识结构图
首先,通过一张图来建立对远程仓库核心操作的全局认知:
第一部分:理解核心概念
1. 什么是远程仓库?
远程仓库是一个存储在互联网或局域网上的 Git 仓库,用于团队协作和代码共享。最常见的托管服务有 GitHub、GitLab、Gitee 等。
2. 远程别名 (Remote Alias)
本地仓库通过一个简短的别名来引用远程仓库的 URL。默认的别名是 origin
(因为你通常是从这个“源”克隆下来的)。你可以有多个远程别名,例如 upstream
用于跟踪一个开源项目的原始仓库。
3. 远程跟踪分支 (Remote-Tracking Branches)
当你与远程仓库交互时(如 fetch
或 pull
),Git 会在本地创建一种特殊的分支来代表远程分支的状态。它们命名为 <remote>/<branch>
,例如:
origin/main
:代表上次连接时,远程origin
仓库上main
分支的状态。- 重点:你不能直接在远程跟踪分支上工作。你必须基于它们创建本地分支。
第二部分:核心操作详解
1. 克隆仓库 (git clone
)
这是获取已有远程仓库的标准方式。
命令:
git clone <repository-url>
作用:
- 将远程仓库的所有数据(所有历史、所有分支)下载到本地。
- 自动创建一个与仓库同名的目录。
- 初始化本地
.git
目录。 - 自动创建一个名为
origin
的远程别名,指向你克隆的 URL。 - 自动检出(checkout) 默认分支(通常是
main
或master
)的最新代码。
注意事项:
- 使用
git clone
后,你就拥有了一个完整的、可独立工作的本地仓库。
2. 关联远程仓库 (git remote
)
如果你是在本地 git init
创建的仓库,需要手动关联到远程。
常用命令:
git remote -v # 查看已关联的远程仓库列表(显示URL)
git remote add origin <url> # 添加一个名为 origin 的远程仓库
git remote rename origin upstream # 重命名远程别名
git remote remove upstream # 删除一个远程别名
git remote show origin # 查看远程仓库的详细信息
注意事项:
<url>
有两种格式:- SSH:
git@github.com:username/repo.git
(需要配置 SSH 密钥,推荐) - HTTPS:
https://github.com/username/repo.git
(需要输入用户名/密码或 Personal Access Token)
- SSH:
3. 推送提交 (git push
)
将你的本地提交上传到远程仓库,与他人分享你的工作成果。
命令:
# 完整格式
git push <remote-name> <local-branch-name>:<remote-branch-name># 常用简化格式(将本地当前分支推送到远程同名分支)
git push origin main# 首次推送:必须使用 -u 选项建立跟踪关系 (tracking relationship)
git push -u origin main# 之后再次推送,因为建立了跟踪,可以简化为
git push
重点与注意事项:
-u
(或--set-upstream
) 选项至关重要:它建立了本地分支与远程分支的跟踪关系。设置后,以后直接使用git push
或git pull
时,Git 就知道你要推送到哪里或从哪里拉取。- 权限:你需要有远程仓库的写入权限才能推送。
- 冲突:如果在你推送之前,远程分支已经有了新的提交(通常是队友推送的),你的推送会被拒绝。你必须先执行
git pull
来整合最新的更改,解决可能的冲突后,再推送。
4. 抓取更新 (git fetch
)
从远程仓库下载所有新的数据(如新的分支、新的提交),但不会自动合并到你的工作目录中。
命令:
git fetch origin # 从 origin 远程抓取所有更新
git fetch # 如果只关联了一个远程,可省略名称
作用:
- 更新你本地的远程跟踪分支(如
origin/main
),使其与远程仓库的实际状态同步。 - 这是一个安全操作,因为它不会修改你的本地工作文件和当前分支。
使用场景:你想看看别人有没有推送新提交,但又不想立即合并到自己的工作中。
5. 拉取更新 (git pull
)
这是一个组合操作:git fetch
+ git merge
。它从远程仓库下载更新并立即尝试合并到你当前所在的本地方支。
命令:
git pull origin main # 相当于 git fetch origin + git merge origin/main
git pull # 如果当前分支已跟踪远程分支,可简化为 git pull
重点与注意事项:
- 可能产生冲突:因为
pull
包含合并操作,如果远程的更改与你的本地更改冲突,你需要手动解决这些冲突(与合并分支时的冲突解决流程完全一样)。 - 先提交再拉取:在执行
git pull
之前,最好先提交(commit
)或贮藏(stash
)你本地的修改,这样可以更干净地进行合并,万一冲突也更容易处理。
第三部分:最佳实践与工作流
- 开始工作前先同步:每天开始工作前或开始新功能前,先
git pull
一下,确保你基于最新的代码进行开发。 - 推送前先同步:在推送你的代码之前,也先
git pull
一下,确保你的推送不会因为冲突而被拒绝。 - 小步快跑,频繁推送:不要积攒大量修改后才推送。频繁地推送小提交可以减少冲突的几率和复杂度。
- 使用 SSH 密钥:相比 HTTPS,SSH 认证更安全且无需每次输入密码,体验更好。
- 理解
git fetch
和git pull
的区别:git fetch
: “嘿,远程仓库有什么新东西吗?下载下来让我看看。” (安全)git pull
: “嘿,把远程的新东西直接拿过来和我的代码合并!” (可能引发冲突)
总结:日常协作流程 Checklist
- [开始工作]
git pull
拉取最新代码。 - [本地开发] 在特性分支上工作,进行
add
,commit
。 - [准备推送] 再次
git pull
(或fetch
+merge
/rebase
) 整合远程的最新更改,解决可能出现的冲突。 - [推送分享]
git push
将你的工作推送到远程仓库。 - [查看状态] 时不时用
git status
和git log --oneline --all --graph
查看状态和历史。
八、分支推送与跟踪
核心知识结构图
首先,通过一张图来建立对远程仓库核心操作的全局认知:
一、核心关系:跟踪关系(Tracking Relationship)
本地分支和远程分支最核心的关系是跟踪关系(Tracking Branch)。
- 远程分支:本质是一个指针,记录了远程仓库(如 GitHub, GitLab)上某个分支最后一次提交的引用。本地通过
origin/branch-name
(例如origin/main
)的形式来引用这些快照。你不能直接在本地向远程分支提交代码,这些origin/*
分支只是远程分支在本地的一份只读缓存。 - 本地分支:你自己在本地仓库工作的分支。
- 跟踪关系:当一个本地分支被设置为“跟踪”一个远程分支时,它们就建立了联系。这个关系会告诉你:
- 你的本地分支相比远程分支是领先还是落后(
git status
)。 - 使用
git pull
和git push
时,如果不指定参数,Git 知道应该从哪个远程分支拉取代码,又应该推送到哪个远程分支。
- 你的本地分支相比远程分支是领先还是落后(
建立跟踪关系后,git status
会显示非常有用的信息:
On branch main
Your branch is up to date with 'origin/main'....或...On branch feature
Your branch and 'origin/feature' have diverged,
and have 1 and 2 different commits each, respectively.
查看跟踪关系
# 查看所有分支的详细信息,包括跟踪关系
git branch -vv
# 输出示例:
# * main a1b2c3d [origin/main] Fix header style
# feature-auth e4f5g6h [origin/feature-auth] Add login API
# 这里 feature-auth 分支跟踪着 origin/feature-auth# 查看某个特定远程的所有跟踪分支详情
git remote show origin
二、创建逻辑与常用场景
本地分支和远程分支的创建顺序是灵活的,但通常遵循以下模式。
场景一:基于远程分支创建本地分支(最常用)
当你的队友创建了新分支并推送到远程后,你需要在本地创建对应的跟踪分支来工作。
# 方法一: 最佳实践 (git switch)
git switch -c feature-auth origin/feature-auth
# -c 表示创建新分支,并基于远程跟踪分支 origin/feature-auth 进行创建,自动建立跟踪# 方法二: 传统方式 (git checkout)
git checkout -b feature-auth --track origin/feature-auth# 方法三: 先获取所有远程信息,再创建
git fetch --all # 首先获取远程最新状态
git switch -c feature-auth origin/feature-auth # 再创建分支
场景二:先创建本地分支,再推送到远程
本地分支和远程分支的创建顺序是灵活的,但通常遵循以下模式。
# 语法: git branch -u <remote>/<remote_branch> [<local_branch>]
# 如果省略 <local_branch>,则为当前分支设置git switch feature-auth # 切换到要设置的本地分支
git branch -u origin/feature-auth # 将其跟踪关系设置为 origin/feature-auth# 或者一步完成
git branch -u origin/feature-auth feature-auth
场景三:其他协作者获取你的远程分支
当其他协作者执行 git fetch
后,他们本地会出现 origin/feature/login
这个远程分支指针。他们就可以使用场景一的方法,基于这个远程分支创建自己的本地跟踪分支。
三、注意事项与最佳实践
-
-u
选项是核心:第一次推送分支时,务必使用git push -u
。这是建立跟踪关系、简化后续所有git push
和git pull
操作的关键。忘记使用-u
是新手最常见的错误之一。 -
git pull
的本质:当你在一个有跟踪关系的分支上执行git pull
时,它等价于:git fetch
:下载远程跟踪分支(如origin/feature-auth
)的最新内容。git merge origin/feature-auth
:将远程更新合并到你当前的本地分支。
-
永远先
git pull
再git push
在推送之前,务必先拉取远程的最新更改并合并到你的本地分支。这可以避免因其他人已经推送了代码而导致的推送失败。git fetch
:安全操作。只会将远程的最新提交下载到本地的origin/*
分支快照中,不会自动合并到你的工作分支。让你有机会审查别人的更改后再决定合并。git pull
:fetch + merge。一个便捷操作,但直接合并可能会带来意想不到的冲突。建议先fetch
,查看日志确认无误后再merge
或pull
。
-
推送失败怎么办?
如果git push
失败,通常是因为你的本地分支落后于远程分支。不要使用git push --force
除非你完全清楚后果! 这会覆盖远程的历史,对团队项目是灾难性的。正确的做法是:git pull --rebase # 优先使用 rebase 而不是 merge,可以使历史更整洁 git push
-
分支删除的同步
- 删除远程分支:本地分支删除后,远程分支不会自动删除。
git push origin --delete feature/old # 删除远程分支
- 清理本地缓存:别人删除的远程分支,在你本地依然会保留着
origin/feature/old
的缓存。需要定期清理:
这个命令会同步远程已删除的分支,清理掉本地陈旧的远程分支跟踪引用。git fetch origin --prune # 或 git remote prune origin
- 删除远程分支:本地分支删除后,远程分支不会自动删除。
-
查看跟踪关系
如果不确定分支的跟踪状态,可以使用以下命令查看:git branch -vv # 显示所有本地分支及其跟踪的远程分支 git remote show origin # 显示更详细的远程信息
-
修改跟踪关系
如果跟踪关系设置错了,可以手动修改:git branch -u origin/new-branch-name # 或 --set-upstream-to
-
分支命名一致性:为了减少混淆,团队应保持本地分支名与远程分支名一致。这简化了命令也避免了不必要的错误。
-
权限问题:你需要有远程仓库的写入权限才能推送。如果你推送失败,请检查你是否是该仓库的协作者或拥有相应权限。
-
保护分支:在一些协作流程中,主分支(如
main
,develop
)可能会被设置为保护分支。这意味着你无法直接向其推送代码,必须通过拉取请求(Pull Request) 的方式合并代码。这是保证代码质量的重要机制。 -
强制推送 (
--force
或--force-with-lease
):git push --force
:极其危险,它会用你的本地分支状态覆盖远程分支,丢弃掉远程分支上已有的、但你本地没有的提交。除非你百分百确定远程分支上的提交是无用且需要被覆盖的(例如在rebase
之后),否则绝对不要使用。git push --force-with-lease
:相对安全一些的强制推送。它会检查远程分支自从你上次拉取以来是否有其他人推送了新的提交。如果有,它会拒绝强制推送,从而防止你意外覆盖他人的工作。如果必须强制推送,优先使用此选项。
总结
操作目的 | 命令 | 说明 |
---|---|---|
查看远程分支 | git branch -r | 查看本地缓存的所有远程分支 |
基于远程分支创建本地分支 | git switch -c <local-branch> origin/<remote-branch> | 建立跟踪关系的最佳方式 |
将本地新分支推送到远程 | git push -u origin <local-branch> | -u 参数用于建立跟踪关系 |
获取远程更新(安全) | git fetch | 只下载,不合并 |
拉取并合并更新 | git pull | fetch + merge ,推送前必做 |
删除远程分支 | git push origin --delete <remote-branch> | |
清理陈旧远程分支缓存 | git fetch --prune | 保持本地远程分支列表整洁 |
最佳实践清单:
- [创建功能分支] 基于最新的
main
创建:git switch -c feature-xxx main
- [首次推送] 务必建立跟踪:
git push -u origin feature-xxx
- [日常推送] 简化操作:
git push
- [获取更新] 先拉取再工作:
git pull
- [完成功能] 合并后删除远程分支:
git push origin --delete feature-xxx
- [强制推送] 极度谨慎,优先使用
--force-with-lease
。
九、协作流程 (Pull Request / Merge Request)
核心知识结构图
首先,通过一张图来理解几种主流协作工作流的全貌与适用场景:
第一部分:核心协作流程 - 功能分支工作流 (Feature Branch Workflow)
这是目前最主流、最推荐的协作模型,核心思想是:任何新功能或修复都必须在独立的分支上完成,通过拉取请求(Pull Request, PR)或合并请求(Merge Request, MR)集成到主分支。
标准化步骤 (Step-by-Step)
-
保持主分支最新
git switch main git pull origin main
-
基于主分支创建功能分支
git switch -c feature/your-feature-name main # 分支名要有描述性,如 feature/user-auth, fix/header-bug
-
在功能分支上开发
# 进行你的工作,小步提交 git add . git commit -m "feat: add user login interface" git add . git commit -m "fix: resolve validation error in login form"
-
定期变基(Rebase)以同步主分支更新
git fetch origin git rebase origin/main # 解决可能出现的冲突,然后继续开发或测试
- 为什么变基而不是合并? 变基可以让你的功能分支历史看起来像是基于最新的
main
分支开发的,形成一条直线,使得最终的合并更清晰、更容易审查。
- 为什么变基而不是合并? 变基可以让你的功能分支历史看起来像是基于最新的
-
推送功能分支并创建 Pull Request (PR)
git push -u origin feature/your-feature-name
- 推送后,在 GitHub/GitLab 等平台界面上会提示你创建 PR/MR。
-
代码审查 (Code Review)
- 团队成员在 PR 界面上审查代码、提出评论。
- 你根据反馈在本地修改代码,然后再次提交并推送到同一分支。所有新提交会自动添加到已有的 PR 中。
# 根据审查意见修改后 git add . git commit -m "refactor: address review comments" git push # 因为之前用了 -u,这里直接push即可
-
通过审查后,合并 PR
- 审核通过后,由具有权限的人(可能是你自己或项目维护者)在平台上点击“Merge”按钮。
- 推荐使用“Squash and Merge”或“Rebase and Merge”,而不是创建合并提交,以保持主分支历史的整洁。
-
删除分支 & 更新本地
- 合并完成后,在平台上删除已合并的远程功能分支。
- 本地操作:
git switch main git pull origin main # 拉取合并后的最新代码,同步本地main git branch -d feature/your-feature-name # 删除本地功能分支
第二部分:重点与黄金法则
-
主分支永远可部署:
main
(或master
) 分支的代码应时刻处于稳定、可部署的状态。严禁将半成品直接推送到主分支。 -
分支隔离:一个功能/修复,一个分支。绝不在一个分支上同时开发多个无关的功能。这保证了功能的独立性和可追溯性。
-
小步提交,频繁推送:将工作拆分成小的逻辑单元进行提交。提交信息要清晰。频繁地将你的分支推送到远程,既是备份,也让队友能看到你的进度。
-
Pull Request 是协作的核心:
- 它是沟通平台:通过 PR 进行代码审查、讨论设计、发现问题。
- 它是质量关卡:确保代码在合并前至少经过另一双眼睛的检查。
- 它是知识共享途径:其他成员可以通过 PR 了解代码的变化和原因。
-
先拉取,再推送:在推送你的代码前,永远先执行
git pull
(或fetch
+rebase
)来整合远程的最新更改,避免推送冲突。 -
尊重保护分支:通常
main
和develop
等核心分支会被设置为保护分支。你无法直接向其推送代码,必须通过 PR 来合并。这是保证代码质量的重要机制,不要试图绕过它。
第三部分:注意事项与常见陷阱
-
Rebase 的风险:
- 黄金法则:只对你本地、尚未推送的提交执行变基。
- 绝对不要对已经推送到远程仓库的分支执行变基(除非这个分支绝对只有你一个人在操作)。变基会重写历史,导致你本地的历史与队友的历史不一致,后续同步会变成一场灾难。
-
解决冲突的责任:
- 如果 PR 合并时出现冲突,通常由发起 PR 的人负责解决。他需要在本地解决冲突后,推送更新到功能分支。
- 解决冲突后,务必进行测试,确保功能依然正常。
-
长期存活分支的同步:如果一个功能分支开发周期很长,需要定期从主分支变基过来,同步最新更改,避免最终合并时产生巨大冲突。
-
环境配置:确保团队成员的 Git 环境配置一致,特别是:
- 行尾符(推荐:
git config --global core.autocrlf true
(Windows) /input
(Mac/Linux)) - 统一使用的合并策略(如 PR 合并时使用 Squash 或 Rebase)。
- 行尾符(推荐:
-
沟通!沟通!沟通!:
- 在开始一项大功能前,在团队内同步。
- 在 PR 描述中清晰说明修改内容、动机和测试情况。
- 代码审查时,评论要友善、建设性。被评论者要以开放的心态接受批评。
总结:优秀协作者的习惯
- 开始前:
git pull
更新主分支。 - 开发中:小步提交,描述清晰,频繁推送。
- 提交前:自我审查代码,运行测试。
- 提 PR 时:填写清晰的标题和描述,方便审查者理解。
- 审查时:积极审查他人代码,及时响应他人的审查意见。
- 合并后:及时删除已合并的远程和本地分支,保持整洁。
十、贮藏与清理
好的,贮藏(Stashing)与清理(Cleaning)是 Git 中两个用于管理工作目录和暂存区状态的实用工具,它们对于保持工作流程的整洁和高效至关重要。下面我们来深入梳理相关的知识点、重点与注意事项。
核心知识结构图
首先,通过一张图快速理解 git stash
和 git clean
的核心操作与区别:
上图概括了两个命令的核心操作,接下来我们深入每个环节的细节。
第一部分:贮藏 (git stash
)
场景:你正在一个分支上开发某个功能,代码修改了一半,还不想提交。这时突然需要切换到另一个分支(比如去修复一个紧急的 Bug)。git stash
可以将你当前已跟踪文件的修改(包括暂存区和工作区)“贮藏”起来,让你的工作目录恢复到上一次提交的状态(干净的状态),从而允许你自由地切换分支。之后你可以在任何时候回来重新应用这些贮藏的修改。
核心命令与知识点
-
贮藏当前修改 (最基本用法)
git stash # 等价于 git stash push # 这会贮藏所有已跟踪文件的修改。默认不会贮藏未跟踪的文件(untracked files)。
-
贮藏并包含未跟踪的文件
git stash -u # 或 --include-untracked # 这将同时贮藏已跟踪文件的修改和未跟踪的文件。 # 非常有用,可以完全恢复工作现场。
-
贮藏并包含所有文件(甚至包括被
.gitignore
忽略的文件)git stash -a # 或 --all # 使用场景较少,但要知道有这个选项。
-
给贮藏项添加描述信息 (强烈推荐)
git stash push -m "描述信息:比如正在做什么功能" # 例如: git stash push -m "WIP: user login validation" # 这会让后续的 `git stash list` 更容易理解,避免混淆。
-
查看贮藏列表
git stash list # 输出示例: # stash@{0}: On main: WIP: user login validation # stash@{1}: On feature/add-button: some other work # 最新的贮藏总是在最上面,索引为 stash@{0}。
-
恢复贮藏
git stash apply
:恢复最新的贮藏(stash@{0}
),但不会从贮藏列表中删除它。可以多次应用。git stash apply
git stash pop
:恢复最新的贮藏,并且会将它从贮藏列表中删除。git stash pop # 绝大多数情况下,推荐使用 `pop`。
- 恢复指定的贮藏:
git stash apply stash@{1} # 恢复指定的贮藏,而不一定是最新的
-
删除贮藏
git stash drop stash@{1} # 删除指定的贮藏,不恢复 git stash clear # 清除所有贮藏项
重点与注意事项
git stash
的本质:它实际上创建了一个特殊的提交对象,这个提交不在任何分支上,但可以被 Git 引用。- 适用场景:
- 临时切换分支处理更紧急的任务。
- 拉取远程更新(
git pull
)前,如果工作区不干净,可以先贮藏,拉取后再弹出。
- 可能会发生冲突:当你恢复贮藏时,如果当前工作目录的文件与贮藏中的修改发生了冲突,你需要像解决合并冲突一样手动解决它们。
- 贮藏是基于分支的吗? 不是。贮藏列表是全局的。你可以在分支 A 贮藏,切换到分支 B,然后在那里应用贮藏(但这很可能造成冲突,通常不建议这么做)。
- 最佳实践:总是使用
-m
选项给贮藏添加描述信息。
第二部分:清理 (git clean
)
场景:你的工作目录中有一些编译生成的临时文件、日志文件或其他你明确知道不需要的未跟踪文件,你想一次性彻底删除它们,让工作目录变得极度干净。git clean
就是一个“毁灭性”的操作,用于永久删除未跟踪的文件。
核心命令与知识点
⚠️ 警告:git clean
删除的文件通常无法通过 Git 恢复!请极度谨慎!
-
“演习”模式(Dry Run) - 必做步骤!
在执行实际删除前,永远先使用-n
(–dry-run) 选项查看哪些文件将会被删除。git clean -n # 它会列出所有即将被删除的未跟踪文件,让你再次确认。
-
强制删除未跟踪的文件
确认git clean -n
的输出无误后,使用-f
(–force) 选项来执行实际删除。git clean -f
-
交互式清理
一个更安全的方式,会逐个文件询问你是否要删除。git clean -i
-
删除未跟踪的目录
默认git clean -f
不会删除空目录,除非使用-d
选项。git clean -fd # 强制删除未跟踪的文件和目录
-
连被
.gitignore
忽略的文件也一并删除
这是一个“大扫除”命令,会清理所有未跟踪的文件,包括那些被.gitignore
规则忽略的(如编译产物node_modules/
,*.o
等)。git clean -fdx # 使用时要万分小心!
重点与注意事项
git clean
只处理未跟踪的文件。它永远不会删除已跟踪的文件(即那些已经被git add
过的文件)。git clean
是git reset --hard
的补充:git reset --hard
:重置已跟踪文件的修改,使其回到最后一次提交的状态。git clean -f
:删除未跟踪的文件。git reset --hard && git clean -fd
:这是一个“组合拳”,可以将你的工作目录完全、彻底地还原到最后一次提交的原始状态,就像全新克隆出来一样。
- 终极警告:
git clean -fdx
会删除所有未被版本控制的文件。如果你有任何重要的配置文件、密钥文件或其他未跟踪的宝贵资产,在执行此命令前必须确保它们已备份或已添加到.gitignore
中。
总结:如何选择?
你的需求 | 安全级别 | 推荐命令 |
---|---|---|
临时保存已跟踪文件的半成品工作 | ✅ 安全 | git stash push -m "message" |
恢复之前贮藏的工作 | ✅ 安全 | git stash pop |
预览哪些未跟踪文件将被删除 | ✅ 非常安全 | git clean -n (Dry Run) |
删除所有无用的未跟踪文件和目录 | ⚠️ 危险(需确认) | git clean -fd |
彻底重置工作目录(包括未跟踪文件) | ⚠️ 极度危险 | git reset --hard && git clean -fd |
黄金法则:
- 想临时切换上下文? -> 用
git stash
。 - 想永久删除垃圾文件? -> 先
git clean -n
,确认无误后再git clean -f
。 - 在执行任何清理操作前,反复确认
git status
的输出,清楚自己在做什么。
十一、查看历史与追溯
核心知识结构图
首先,通过一张图来建立对查看历史与追溯操作的全局认知:
上图概括了历史查看与追溯的核心方法,接下来我们深入每个环节的细节。
第一部分:查看提交历史 (git log
)
这是最基础也是最强大的命令,有无数种方式来定制输出。
1. 基础与格式化输出
# 基本输出,按时间倒序显示提交
git log# 最常用:单行显示,并展示分支拓扑图 (--graph) 和所有分支 (--all)
git log --oneline --graph --all
# 输出示例:
# * a1b2c3d (HEAD -> main) Fix header style
# * e4f5g6h Add user profile page
# | * c7d8e9f (feature/auth) Implement login API
# |/
# * b0a1b2c Initial commit# 自定义格式化输出
git log --pretty=format:"%h - %an, %ar : %s"
# %h: 短哈希 %an: 作者名 %ar: 相对时间 %s: 提交说明
2. 筛选历史记录
这是定位问题的关键。
# 按数量筛选
git log -n 5 # 只看最近5条提交# 按时间筛选
git log --since="2023-01-01" --until="2023-01-15"
git log --since="2 weeks ago"# 按作者筛选
git log --author="John" # 作者名可以是正则表达式# 按提交信息筛选
git log --grep="bugfix" # 在提交信息中搜索关键词# 按文件筛选:查看涉及某个文件的提交
git log -- path/to/file.js# 按内容筛选(“镐查询”):查看添加或删除了特定字符串的提交
git log -S "functionName"# 组合筛选:强大的排查工具
git log --oneline --author="Alice" --since="1 week ago" --grep="fix" -- src/app.js
3. 显示每次提交的变更
-p
(或 --patch
) 选项是重中之重,它显示了每次提交的具体代码变化。
git log -p # 显示所有提交的详细差异
git log -p -n 1 # 显示最近一次提交的差异
git log -p -- path/to/file # 显示某个文件所有历史提交的差异
重点:当你需要理解为什么代码会变成这样时,git log -p -- <file>
是你最好的朋友。
第二部分:追溯文件更改
1. 查看文件的修改历史
如上所述,git log -- <file-path>
是核心命令。加上 --follow
可以追踪文件重命名。
git log --follow -- path/to/file.js
2. 逐行追溯责任 (git blame
)
场景:你想知道文件中的每一行代码是谁、在什么时候、为什么(通过提交哈希可以再查)添加的。
git blame path/to/file.js
# 输出每行代码的: 提交哈希 作者 日期时间 行号 代码内容# 更易读的模式
git blame -c path/to/file.js # 紧凑模式
git blame -L 10,20 path/to/file.js # 只查看第10到20行的追溯信息
重点:
- 主要用于定位特定代码块的引入者和引入时间。
- 在代码审查或排查某些代码的由来时非常有用。
- 不要用它来追究责任(Blame),而应用它来理解上下文(Find why)。看到奇怪的代码,根据哈希去查当时的提交信息和上下文,而不是直接去问某人。
第三部分:查看特定提交 (git show
)
场景:你已经通过 git log
找到了一个感兴趣的提交哈希,想查看这次提交的完整详细信息。
git show <commit-hash> # 显示提交的元信息(作者、时间等)和具体变更内容git show --name-only <commit-hash> # 只显示这次提交修改了哪些文件
git show --stat <commit-hash> # 显示更改的摘要统计(哪些文件,增减行数)
git show <commit-hash>:<file-path> # 显示该次提交中某个文件的完整内容
重点:git show
是 git log -p -1
的替代品,但功能更丰富。
第四部分:高级追溯 - 二分查找 (git bisect
)
场景:这是一个强大的调试工具,用于快速定位引入 Bug 或回归问题的那一个特定提交。当你的项目突然出现一个之前没有的 Bug,但你又不知道是哪次提交导致的,git bisect
可以自动化地帮你进行二分查找。
操作步骤
-
启动二分查找:
git bisect start
-
标记一个“坏”的提交(即已知存在 Bug 的提交,通常是当前
HEAD
):git bisect bad
-
标记一个“好”的提交(即已知没有该 Bug 的历史提交):
git bisect good a1b2c3d # 填写一个过去的、正常的提交哈希
-
Git 自动二分:Git 会自动检出中间的一个提交,让你测试当前提交是“好”还是“坏”。
- 你进行测试后,根据结果输入:
git bisect good # 如果当前提交没有Bug git bisect bad # 如果当前提交有Bug
- Git 会根据你的反馈继续自动检出新的提交,直到最终定位到第一个引入 Bug 的提交。
- 你进行测试后,根据结果输入:
-
结束二分查找:完成后,务必结束二分过程,回到你最初的位置。
git bisect reset
重点与注意事项:
- 这是一个自动化的过程,可以很快地从大量提交中定位问题。
- 你可以编写脚本来自动化测试过程(
git bisect run <your-script>
)。 - 使用后一定要
reset
,否则你会处于一个分离的 HEAD 状态。
总结:重点与注意事项
git log --oneline --graph --all
是你的导航仪:经常使用它来了解项目分支结构的全局状况。git log -p -- <file>
是理解代码演变的最佳工具:想了解一个文件为什么变成这样,就用这个命令。git blame
用于理解“谁”,git show
用于理解“为什么”:用blame
找到提交哈希,再用show
查看那次提交的完整上下文。- 筛选是高效的关键:不要在海量的
git log
输出中手动寻找,熟练运用--author
,--grep
,-S
,--since
等选项精准筛选。 git bisect
是排查回归问题的利器:对于难以手动定位的 Bug,学会使用这个自动化工具将极大提升你的调试效率。- 保护隐私:注意提交历史中的敏感信息(如密码、密钥)。一旦提交,即使在后来的提交中删除,历史记录中依然存在。需要使用
git filter-repo
等工具来清理整个历史(这是一个高级且危险的操作)。
十二、钩子 (Hooks)
核心知识结构图
首先,通过一张图来建立对 Git 钩子的全局认知,理解其类型、位置和核心工作流:
上图清晰地展示了钩子的分类、执行顺序及其在流程中的“把关”作用,接下来我们深入细节。
第一部分:钩子基础
1. 什么是钩子?
钩子是存储在 Git 仓库 .git/hooks/
目录下的可执行脚本。当 Git 执行到某些特定操作(如 commit
, push
, rebase
)时,它会自动查找并运行对应名称的钩子脚本。
2. 钩子的位置与启用
- 位置:每个 Git 仓库的
.git/hooks/
目录下。 - 启用:该目录下预置了大量示例脚本(以
.sample
后缀结尾)。要启用一个钩子,你需要:- 移除其
.sample
后缀。 - 确保该文件是可执行的(使用
chmod +x .git/hooks/<hook-name>
)。
- 移除其
3. 钩子的语言
钩子可以用任何你熟悉的脚本语言编写(如 Shell、Python、Ruby、Node.js 等),只要系统能解释执行它即可。必须在脚本的第一行指定解释器(如 #!/bin/sh
或 #!/usr/bin/env python3
)。
第二部分:常用的客户端钩子
客户端钩子影响你本地的开发工作流。
1. 提交工作流钩子
-
pre-commit
- 触发时机:在键入提交信息之前运行。
- 用途:检查工作区快照。例如,检查是否遗漏了调试语句(
console.log
)、是否遵循代码风格(ESLint, Prettier)、是否运行了基础测试。 - 注意事项:如果该脚本以非零值退出(
exit 1
),Git 将中止此次提交。这是最重要的钩子之一。
-
prepare-commit-msg
- 触发时机:在默认提交信息准备好之后、编辑器启动之前。
- 用途:允许你动态编辑默认的提交信息。常用于自动化生成提交信息模板,或与模板工具集成。
-
commit-msg
- 触发时机:在用户输入提交信息之后。
- 用途:验证提交信息的格式。例如,要求提交信息必须符合约定式提交,或者必须关联任务ID(如
JIRA-123: ...
)。 - 注意事项:如果脚本退出非零,Git 将中止提交。这是强制执行团队提交规范的关键钩子。
-
post-commit
- 触发时机:在整个提交过程完成之后。
- 用途:主要用于通知。例如,发送邮件、触发桌面通知等。它无法影响提交结果。
2. 其他有用的客户端钩子
-
pre-push
- 触发时机:在
git push
运行之后,但在数据真正传输到远程之前。 - 用途:在推送前运行更全面的测试套件,确保推送的代码不会破坏构建。如果测试失败,以非零值退出可以中止推送。
- 触发时机:在
-
pre-rebase
- 触发时机:在变基之前执行。
- 用途:防止对已经推送的提交执行变基(遵守黄金法则)。如果脚本退出非零,则中止变基。
-
post-checkout
/post-merge
- 触发时机:在
checkout
或merge
成功之后。 - 用途:用于管理你的工作目录。例如,自动运行
npm install
或bundle install
来确保依赖与项目版本匹配。
- 触发时机:在
第三部分:服务器端钩子
服务器端钩子在远程仓库(如 GitLab、Gitee 的服务器上)上运行,用于强制执行整个团队的策略。个人开发者较少直接配置,但必须了解其存在和作用。
-
pre-receive
- 触发时机:在处理来自客户端的推送操作之前。
- 用途:这是最重要的服务器钩子。它可以检查所有推送的提交。例如:
- 拒绝包含某些关键词的提交。
- 拒绝非快进式(non-fast-forward)的推送(即强制推送)。
- 验证用户权限(用户A不能推送到保护分支)。
- 检查提交信息格式。
- 注意事项:如果脚本退出非零,整个推送都会被拒绝,所有引用都不会被更新。
-
update
- 类似
pre-receive
,但针对每个分支运行一次。略旧一些,pre-receive
更常用。
- 类似
-
post-receive
- 触发时机:在推送操作完成之后。
- 用途:主要用于通知和触发持续集成(CI)/持续部署(CD)。例如,通知其他系统(如 Jenkins)、触发自动部署到生产环境、发送成功通知邮件等。
第四部分:重点与注意事项
-
钩子不是版本控制的:默认情况下,
.git/hooks/
目录不被 Git 跟踪。这意味着你无法通过git clone
直接将钩子共享给团队成员。- 解决方案:
- 将钩子脚本存储在项目根目录的某个文件夹(如
scripts/hooks/
)中。 - 让团队成员通过符号链接(
ln -s ../../scripts/hooks/pre-commit .git/hooks/pre-commit
)或一个安装脚本(npm run hook:setup
)来启用它们。 - 使用像 Husky (对于 Node.js 项目) 这样的工具,它可以完美地管理并共享 Git 钩子。
- 将钩子脚本存储在项目根目录的某个文件夹(如
- 解决方案:
-
性能至关重要:钩子脚本应该快速执行。尤其是在
pre-commit
这类频繁触发的钩子中,如果运行一个需要10分钟的测试套件,会严重破坏开发体验。只运行最必要的快速检查。 -
可绕过性:客户端钩子可以被用户用
--no-verify
选项绕过。git commit --no-verify # 跳过 pre-commit 和 commit-msg 钩子 git push --no-verify # 跳过 pre-push 钩子
- 因此,客户端钩子用于“提醒”和“辅助”,而不能作为绝对的安全策略。 关键的质量门禁必须放在服务器端的
pre-receive
钩子中。
- 因此,客户端钩子用于“提醒”和“辅助”,而不能作为绝对的安全策略。 关键的质量门禁必须放在服务器端的
-
脚本必须可执行且成功退出:确保你的脚本文件有执行权限(
chmod +x
),并且只有在检查失败时才以非零状态退出(exit 1
)。成功时应以0
退出。 -
提供清晰的错误信息:当钩子拒绝操作时,应该向用户输出清晰、有用的错误信息,告诉他们为什么被拒绝以及如何修复。
总结:钩子的价值
- 自动化:自动运行代码检查、测试、部署。
- 规范化:强制执行代码风格、提交信息规范。
- 集成:连接 Git 与其他开发工具(CI/CD, 项目管理工具)。
十三、高级撤销与找回 (救命用的!)
核心知识结构图
首先,通过一张图来建立对高级撤销与找回操作的全局认知,理解其核心安全网与操作流程:
上图揭示了高级撤销的核心是基于 reflog
和 fsck
,接下来我们深入每个环节的细节。
第一部分:终极安全网 - 引用日志 (git reflog
)
这是你最重要的救命稻草。 如果你只记住一件事,那就记住 git reflog
。
知识点
- 它是什么?
reflog
记录了你的本地仓库中分支顶端和 HEAD 指针的每一次改变历史。无论是提交、重置、合并、变基还是检出,几乎所有操作都会被记录下来。 - 它存多久? 默认情况下,这些记录会保存 90 天。但这只是本地记录,不会被推送到远程服务器。
核心命令
# 查看完整的 reflog
git reflog
# 或更详细的形式
git reflog show --date=iso# 查看特定分支的 reflog(如 main)
git reflog show main
输出示例:
a1b2c3d (HEAD -> main) HEAD@{2023-10-27 10:30:15}: commit: Add new feature
e4f5g6h HEAD@{2023-10-27 10:25:01}: reset: moving to HEAD~1
c7d8e9f HEAD@{2023-10-27 10:24:55}: commit: WIP: broken code
...
每一行都代表一次状态变化,左边的哈希值代表了那次操作之后的提交状态。
重点与注意事项
- 本地性:
reflog
是你本地仓库的私人历史。它无法帮你找回队友电脑上丢失的提交。 - 它是“撤销”的罗盘:当你执行了一个灾难性的操作(如
git reset --hard
到了错误的提交),不要慌。首先运行git reflog
,找到操作之前的那个状态(例如HEAD@{1}
),然后根据它进行恢复。
第二部分:高级找回场景与实践
场景 1:找回被错误重置 (git reset --hard
) 的提交
操作:
- 查看 reflog,找到执行
reset
之前那个状态的哈希值。它通常就在最新操作的下面一行。git reflog # e4f5g6h HEAD@{1}: reset: moving to HEAD~1 # 这是错误操作 # a1b2c3d HEAD@{2}: commit: Add important feature # 这是我们想要的
- 使用软重置或混合重置 回到那个状态。这会恢复你的提交,同时保留你的工作区更改。
# 使用哈希值 git reset --soft a1b2c3d # 或使用相对引用(推荐,更简单) git reset --soft HEAD@{1}
- 现在,你的重要提交又回来了,并且之前的更改已经回到了暂存区或工作区。
场景 2:恢复被删除的分支
你删除了一个还没有合并的分支(git branch -D
),现在后悔了。
操作:
- 找到该分支顶端最后指向的提交哈希。分支本身是一个指针,删除分支只是删除了指针,提交对象还在。
git reflog # 在历史记录中寻找类似这样的记录,它记录了分支指针的最后位置: # c7d8e9f (origin/feature) HEAD@{5}: checkout: moving from feature to main # a1b2c3d HEAD@{6}: commit: Finish the feature <-- 就是这个!
- 基于该哈希值重新创建分支。
git branch recovered-feature a1b2c3d
- 恭喜你,
recovered-feature
分支又回来了,包含了所有的历史提交。
- 恭喜你,
场景 3:找回一次看似“丢失”的提交
你进行了一次提交,但之后的操作让它不再被任何分支或标签引用,它变成了一个“悬空对象”(dangling object)。
操作:
- 列出所有悬空对象。Git 不会立即清理它们。
git fsck --full # 你会看到 "dangling commit <hash>" 这样的输出
- 检查这些悬空提交,看看是不是你要找的。
git show <dangling-hash> git log --oneline <dangling-hash> -n 5
- 如果找到了,就把它合并回你的分支或基于它创建新分支。
git merge <dangling-hash> # 或 git branch recovered-work <dangling-hash>
第三部分:重点与黄金法则
- 不要恐慌 (Don’t Panic!): 这是最重要的第一条。Git 几乎永远不会真正丢失数据,只要你近期操作过。先运行
git reflog
。 - 理解
reflog
是本地且临时的:它是你本地操作的日记。git clone
不会克隆 reflog,旧的 reflog 记录最终会被垃圾回收。所以,重要的提交最终还是要推送到远程仓库进行备份。 HEAD
是你的朋友:在reflog
中,HEAD@{n}
是极其有用的相对引用方式,通常比你去找哈希值更快。git reset
的三个模式在此处的用法:--soft
: 我想恢复提交,并且我的更改已经都在暂存区了。--mixed
(默认): 我想恢复提交,但更改保留在工作区(未暂存)。--hard
: 极度危险! 我想彻底抛弃当前的所有更改,强制回到那个时间点。仅在你100%确定当前工作可丢弃时使用。
git fsck
是最后的手段:当reflog
也因为某些原因找不到记录时(例如过了很久),才求助于git fsck
来扫描整个对象数据库寻找悬空对象。
注意事项
Git 的恢复与找回能力非常强大,但它的核心原则是:只能找回那些曾经被 Git 记录过(即提交过)的内容。
简单来说,Git 的恢复与找回主要针对以下内容,其范围和可能性可以用下图清晰地概括:
详细解释与操作方法
1. 已提交的内容(Committed Changes) - 几乎总是能找回
这是 Git 最擅长恢复的部分。一旦你执行了 git commit
,这次提交就被永久地记录在仓库的历史中(只要 .git
目录没有被损坏)。恢复的核心在于找到指向这些提交的“指针”。
- 恢复方式:
git reflog
: 这是你的“安全网”。它会记录你的本地仓库中所有分支的 HEAD 指针在最近几个月内的所有移动记录(包括提交、重置、拉取、合并等)。如果你误删了一个分支或执行了git reset --hard
,reflog
是你最主要的找回工具。- 步骤:
git reflog
- 查看历史操作,找到误操作之前的那个提交的哈希值。git checkout <commit-hash>
- 切换到那个提交以确认内容正确。git branch <new-branch-name> <commit-hash>
- 基于这个提交创建一个新分支,这样就彻底找回了。
git fsck
: 一个更底层的工具,可以找回悬空对象(Dangling Objects),比如那些被git reset --hard
丢弃但还未被垃圾回收的提交。用法通常是将git fsck --full --no-reflogs --unreachable --lost-found
找到的提交哈希值,再通过git show
或git checkout
查看和恢复。- 分支指针: 如果你只是删除了一个分支,但这个分支的提交还没有被垃圾回收,并且你知道它的名字,你可以直接基于它重新创建分支:
git branch <branch-name> <sha1-commit-hash>
。
2. 已暂存但未提交的内容(Staged but Uncommitted Changes) - 通常可以找回
当你执行了 git add
,文件就被添加到了“暂存区”。Git 会为暂存区的内容创建一个 blob 对象,但这个对象还没有被任何提交所引用。
- 恢复方式:
git fsck
同样可以找到这些悬空的 blob 对象。但恢复过程更繁琐,你需要用git show <blob-hash>
将内容输出到文件。- 更简单的方法是:如果你刚刚错误地覆盖了暂存区,但这个文件的内容曾经被某次提交记录过,你可以轻松地从历史提交中检出一份副本:
# 从最新的提交中恢复某个文件到暂存区和工作区 git checkout HEAD -- <file-name># 或者使用更现代的 `git restore` 命令 (Git 2.23+) git restore --source=HEAD --staged --worktree <file-name>
3. 未暂存、未提交的修改(Unstaged & Uncommitted Changes) - 几乎无法找回
这是 Git 无法帮你恢复 的区域!这些修改只存在于你的工作目录中,Git 还没有为其创建任何对象。
- 原因:Git 还没有开始跟踪这些更改。这就像你用记事本修改了一个文件但没有保存,或者按了删除键后清空了回收站。
- 恢复方式:
- 依赖你的代码编辑器/IDE 的本地历史功能(如 JetBrains 系列IDE、VSCode 的一些插件)。
- 操作系统的文件恢复工具(如果文件被删除)。
- 良好的习惯:频繁地
git add
和git commit
。
4. 被 git reset --hard
或 git commit --amend
覆盖的提交 - 可以找回
这些操作会“移动”或“替换”分支指针,导致原来的提交不再被任何分支或标签直接引用。但它们会暂时成为悬空对象,在一段时间内(默认至少 14 天)仍然存在于对象库中,直到被 Git 的垃圾回收 (git gc
) 清理掉。
- 恢复方式:使用
git reflog
找到操作之前的提交哈希值,然后创建新分支。
5. 远程仓库的恢复
你本地的 Git 仓库还保留着所有你已拉取过的远程分支的完整历史。因此,如果你的远程仓库(如 GitHub)出了问题,你完全可以用你本地的版本去重建它。
- 恢复方式:
git push --all origin # 将所有分支推送到新的远程仓库 git push --tags origin # 将所有标签推送到新的远程仓库
总结:高级撤销清单
当你搞砸了的时候:
- 核心原则:只有被 Git 对象数据库记录过的内容才能被恢复(即 blob, tree, commit 对象)。工作目录中纯粹的、未暂存的修改无法通过 Git 恢复。
- 🛑 停止操作:不要再进行任何可能会覆盖历史的 Git 操作。
- 🔍 使用
git reflog
:立即运行它,成为你的“时间机器”的控制台。 - 📌 定位错误操作前的状态:在
reflog
输出中找到你想要回去的那个时间点,记下它的哈希或HEAD@{n}
。 - 🧰 选择恢复工具:
- 想恢复分支? ->
git branch <new-name> <hash>
- 想撤销重置/提交? ->
git reset --soft <hash>
- 想彻底回到过去(丢弃现在的一切)? ->
git reset --hard <hash>
(慎用!)
- 想恢复分支? ->
- 🚀 验证与继续:恢复后,检查你的代码是否正确,然后继续工作。
- 时间窗口:即使提交变成了悬空对象,也不是永久存在的。Git 会定期运行垃圾回收 (
git gc
) 来清理它们。默认情况下,悬空对象会保留至少 2 周。所以发现问题后要尽快恢复。 - 备份是关键:对于极其重要的工作,不要 100% 依赖版本控制。定期推送到远程仓库(GitHub, GitLab等)是最好的备份策略。这既保护了你的代码免受本地硬盘损坏的影响,也提供了另一个可恢复的来源。
- 标签是安全的:标签(Tag)是一个静态的、永不移动的指针,指向一个特定的提交。因此,任何被打上标签的提交都非常安全,很难被意外丢失。
十四、子模块 (Submodules) / 子树 (Subtree)
核心概念:它们解决什么问题?
当你需要在一个 Git 项目中包含另一个 Git 项目时,就会用到它们。常见的场景包括:
- 公共库/组件:你的项目依赖一个内部开发的、也在独立演进的公共库。
- 第三方库:你想将第三方代码(如一个修改过的开源库)包含在你的项目中,同时还能偶尔获取上游更新。
- 模块化项目:一个大型项目被拆分成多个独立的仓库,主项目负责整合。
核心区别在于整合的方式:
- 子模块 (Submodule):像一个 “指针” 或 “挂载点”。它只在主仓库中记录所引用的子项目某个特定提交的哈希值,而不直接包含其文件。子项目保持完全的独立历史。
- 子树 (Subtree):像一次 “合并”。它将子项目的代码完整地合并到主仓库的一个子目录中,成为主项目历史的一部分。
为了更直观地理解它们的核心工作流程与区别,可以参考下面的序列图:
上图清晰地展示了两种方法的关键差异:
- 子模块维护的是引用关系,克隆后需要额外步骤(
update --init
)来填充内容,且内容指向特定提交。 - 子树执行的是合并操作,直接将子项目的代码和历史复制到主仓库中,成为主仓库的一部分。
子模块 (Submodules)
知识点与重点
- 是什么:它是一个独立的仓库嵌入到主仓库的一个子目录中。主仓库只存储其提交哈希引用(在
.gitmodules
文件中还有仓库URL和路径映射)。 - 初始化:
# 添加子模块 git submodule add <repository_url> <path/to/submodule> # 示例 git submodule add https://github.com/example/lib.git libs/mylib# 克隆一个包含子模块的项目后,需要额外步骤初始化 git clone <main_repository> git submodule init # 初始化本地配置文件 git submodule update # 克隆子模块仓库并检出父项目指定的提交# 或者克隆时一步到位 git clone --recurse-submodules <main_repository>
- 更新:
git submodule update --remote
:此命令会进入子模块,拉取远程最新更改,并检出子模块远程跟踪分支(默认origin/master)的最新提交。然后主仓库会看到子模块有“新提交”待提交。- 如果你只在主项目运行
git pull
,它不会自动更新子模块的提交引用。你需要再运行git submodule update
来使子模块目录回退到主项目所记录的那个提交(这可能和你刚才拉取的新提交是冲突的,需要注意)。
- 提交:
- 如果你修改了子模块的代码,你需要:
- 进入子模块目录 (
cd libs/mylib
)。 - 像在普通 Git 仓库中一样提交和推送 (
git add .
,git commit -m "..."
,git push
)。 - 返回主项目目录,你会看到子模块有“新提交”待暂存。运行
git add libs/mylib
并提交,目的是更新主项目所记录的子模块提交指针。
- 进入子模块目录 (
- 如果你修改了子模块的代码,你需要:
- 状态:使用
git submodule status
查看所有子模块的当前提交SHA和状态。
注意事项 (⚠️ 非常重要!)
- 心理模型:牢记“仓库中的仓库”。任何对子模块内容的操作都需要进入子模块目录。
- 初始化的麻烦:克隆主项目后,如果不执行
submodule init/update
,子模块目录是空的。这是新手最常见的坑。 - 指针分离状态:默认情况下,
git submodule update
会使子模块处于HEAD
分离状态(detached HEAD),即不位于任何分支上。如果你要在子模块内开发,务必先创建并切换到一个分支 (git checkout -b my-feature
),否则提交会丢失。 - 协作复杂度:团队每个人都必须了解子模块的工作流程,否则极易出现主项目引用的子模块提交不存在或未推送的情况。
- 权限:主项目的
.gitmodules
文件记录了子模块的URL。确保所有协作者都有权限访问该URL。
子树 (Subtree)
知识点与重点
- 是什么:通过将子项目代码合并到主项目的一个子目录中,将其作为主项目的一部分。子项目的完整历史(或压缩后的历史)会并入主项目的历史中。
- 添加:
# 1. 先添加子项目的远程仓库作为一个remote git remote add lib-remote https://github.com/example/lib.git# 2. 使用 subtree add 命令合并 git subtree add --prefix=libs/mylib lib-remote main --squash# --prefix=<path>:指定子目录路径 # lib-remote main:从名为lib-remote的remote的main分支合并 # --squash:将子项目的历史合并为一次提交,强烈推荐使用,避免污染主项目历史。
- 更新(获取子项目上游的更改):
这类似于一次git fetch lib-remote main git subtree pull --prefix=libs/mylib lib-remote main --squash
git merge
,可能会产生冲突,需要手动解决。 - 推送(如果你有权限修改子项目,并想把主项目中的修改推回子项目):
这个命令会从主项目的历史中筛选出对git subtree push --prefix=libs/mylib lib-remote main
libs/mylib
目录的所有更改,并将其推送到子项目的远程仓库。
注意事项
- 简单性:克隆、签出主项目后,所有代码立即可用,无需额外步骤。对团队成员更透明。
- 历史污染:如果不使用
--squash
,子项目的整个历史都会并入主项目,可能导致主项目历史变得冗长和复杂。 - 操作复杂度:
subtree
的命令比submodule
更长、更复杂。很多团队会编写脚本封装这些命令。 - 推送麻烦:
git subtree push
命令可能很慢,并且如果主项目对子目录的修改历史复杂,可能会产生冲突。在推送前确保你的本地子项目目录是最新的。 - 工具支持:IDE 和 GUI 工具对子树的识别和支持通常不如子模块好(但子模块本身的支持也并非完美)。
对比与选择指南
特性 | 子模块 (Submodule) | 子树 (Subtree) |
---|---|---|
仓库关系 | 仓库中的仓库(指针) | 仓库中的代码(合并) |
克隆/检出 | 需要额外初始化步骤 | 所有代码立即可用 |
权限管理 | 需要分别管理主项目和子项目权限 | 只需要主项目权限 |
历史记录 | 主项目历史干净,只记录指针更新 | 子项目历史会合并入主项目(可用 --squash 优化) |
更新流程 | 相对复杂,需关注指针状态 | 类似合并分支,可能需解决冲突 |
推送修改 | 进入子模块操作,很自然 | 需要 subtree push 命令,可能复杂 |
适用场景 | 1. 子项目独立且多人协作 2. 希望严格控制依赖版本 3. 子项目频繁更新,主项目不想频繁合并 | 1. 简化项目结构,减少协作成本 2. 第三方库依赖,且修改不多 3. 希望所有代码在一个仓库中 |
最终建议
- 选择子模块如果:你和你的团队对 Git 很熟悉,子项目需要独立的多方协作,并且你希望明确地跟踪子项目的精确版本(提交哈希)。
- 选择子树如果:你希望工作流更简单(
clone
即用),子项目主要是只读的(例如第三方库),或者你不想让团队成员操心额外的子模块管理步骤。对于大多数包含第三方代码的场景,子树是更简单、更安全的选择。
通用最佳实践:无论选择哪种,都要文档化!在项目的 README.md
中明确说明使用了哪种依赖管理方式,并写下基本的操作命令(如更新、提交的步骤),这对团队协作至关重要。
十五、Git 命令速查
Git 命令速查表,按照工作流程分类,便于快速查找和使用。
一、仓库初始化与配置
命令 | 描述 | 示例 |
---|---|---|
git init | 在当前目录初始化一个新的 Git 仓库 | git init |
git clone <repo_url> | 克隆一个远程仓库到本地 | git clone https://github.com/user/repo.git |
git config --global user.name "Name" | 设置全局用户名 | git config --global user.name "John Doe" |
git config --global user.email "email" | 设置全局邮箱 | git config --global user.email "john@example.com" |
git config user.name "Name" | 设置当前仓库的用户名 | git config user.name "John Doe" |
git config --list | 查看当前所有配置 | git config --list |
二、基础工作流程 (Add -> Commit)
命令 | 描述 | 示例 |
---|---|---|
git status | 查看工作区和暂存区的状态 (最常用) | git status |
git add <file> | 将指定文件添加到暂存区 | git add index.html |
git add . | 将所有修改和新增文件添加到暂存区 | git add . |
git add -A | 将所有修改、新增、删除的文件添加到暂存区 | git add -A |
git add -p | 交互式地选择修改的部分添加到暂存区 | git add -p |
git commit -m "message" | 提交暂存区的内容,并附上提交信息 | git commit -m "Fix login bug" |
git commit -am "message" | 跳过 git add ,直接提交所有已跟踪文件的修改 | git commit -am "Update docs" |
git restore --staged <file> | 将文件从暂存区撤回,但保留工作区的修改 | git restore --staged file.txt |
git restore <file> | 丢弃工作区的修改,恢复到最近一次 commit 的状态 | git restore file.txt |
git rm <file> | 删除工作区文件,并将这次删除放入暂存区 | git rm old_file.txt |
git mv <old> <new> | 移动或重命名文件,并将这次操作放入暂存区 | git mv a.txt b.txt |
三、分支与标签 (Branch & Tag)
命令 | 描述 | 示例 |
---|---|---|
git branch | 查看所有本地分支 | git branch |
git branch -r | 查看所有远程分支 | git branch -r |
git branch -a | 查看所有本地和远程分支 | git branch -a |
git branch <branch_name> | 创建一个新分支 | git branch feature-auth |
git switch <branch_name> | 切换到指定分支 (Git 2.23+) | git switch main |
git switch -c <branch_name> | 创建并切换到新分支 (Git 2.23+) | git switch -c hotfix |
git checkout <branch_name> | 切换到指定分支 (旧命令) | git checkout main |
git checkout -b <branch_name> | 创建并切换到新分支 (旧命令) | git checkout -b hotfix |
git merge <branch_name> | 将指定分支合并到当前分支 | git switch main -> git merge feature |
git branch -d <branch_name> | 删除一个已合并的分支 | git branch -d old-feature |
git branch -D <branch_name> | 强制删除一个分支 (未合并也会删除) | git branch -D broken-feature |
git push origin -d <branch_name> | 删除远程分支 | git push origin -d old-remote-branch |
git tag | 查看所有标签 | git tag |
git tag <tag_name> | 在当前提交打上轻量标签 | git tag v1.0.0 |
git tag -a <tag_name> -m "msg" | 打上带注解的附注标签 | git tag -a v1.0.0 -m "Release version 1.0.0" |
git push origin <tag_name> | 推送指定标签到远程 | git push origin v1.0.0 |
git push origin --tags | 推送所有本地标签到远程 | git push origin --tags |
四、查看历史与差异 (Log & Diff)
命令 | 描述 | 示例 |
---|---|---|
git log | 查看当前分支的提交历史 | git log |
git log --oneline | 以简洁的一行形式查看历史 | git log --oneline |
git log --graph | 以ASCII图形显示分支合并历史 | git log --oneline --graph |
git log -p | 显示每次提交的内容差异 | git log -p |
git log --follow <file> | 显示某个文件的完整历史(包括重命名) | git log --follow app.js |
git diff | 查看工作区和暂存区的差异 | git diff |
git diff --staged | 查看暂存区和最后一次提交的差异 | git diff --staged |
git diff <commit1> <commit2> | 查看两次提交之间的差异 | git diff abc123 def456 |
git blame <file> | 以列表方式查看指定文件的修改历史和作者 | git blame README.md |
五、远程协作 (Remote)
命令 | 描述 | 示例 |
---|---|---|
git remote -v | 查看已配置的远程仓库信息 | git remote -v |
git remote add <name> <url> | 添加一个新的远程仓库 | git remote add upstream https://... |
git fetch <remote> | 下载远程仓库的所有更新,但不合并 | git fetch origin |
git pull <remote> <branch> | 下载并合并远程分支到当前分支 (fetch + merge ) | git pull origin main |
git pull --rebase <remote> <branch> | 下载并以变基方式合并 | git pull --rebase origin main |
git push <remote> <branch> | 将本地分支推送到远程仓库 | git push origin feature |
git push -u origin <branch> | 首次推送分支并建立跟踪关系 | git push -u origin feature |
六、撤销与回退 (Undo)
命令 | 描述 | 应用场景 |
---|---|---|
git restore <file> | 丢弃工作区的修改 | 改乱了文件,想恢复到上次提交的样子 |
git restore --staged <file> | 将文件从暂存区撤回 | git add 后想取消暂存 |
git commit --amend | 修补最后一次提交(可修改信息或内容) | 提交信息写错了或漏了文件 |
git reset --soft <commit> | 回退到指定提交,保留工作区和暂存区 | 回退提交,但想保留所有修改 |
git reset --mixed <commit> | 默认模式,回退提交,保留工作区,清空暂存区 | 将提交拆分成多个小提交 |
git reset --hard <commit> | 彻底回退,丢弃工作区和暂存区的所有修改 | 彻底抛弃最近几次提交 |
git revert <commit> | 创建一个新的提交来撤销指定的提交 | 安全地撤销已推送的提交 |
⚠️ 警告:
git reset --hard
是危险命令,会永久丢弃代码,请谨慎使用!
七、高级与神器级命令
命令 | 描述 | 应用场景 |
---|---|---|
git stash | 将当前工作区和暂存区的修改临时储藏起来 | 临时切换分支处理紧急任务 |
git stash pop | 恢复最近一次储藏的修改,并删除储藏记录 | 处理完任务后恢复现场 |
git stash list | 查看所有储藏列表 | git stash list |
git reflog | 查看所有操作历史(包括被reset掉的提交) | 误操作后找回代码的终极神器 |
git cherry-pick <commit> | 将某个提交的修改应用到当前分支 | 只想应用另一个分支的某个特定提交 |
八、子模块 (Submodule) - 常用命令
命令 | 描述 |
---|---|
git submodule add <repo> <path> | 添加子模块 |
git submodule init | 初始化子模块(克隆后) |
git submodule update | 更新子模块(克隆后) |
git clone --recurse-submodules <repo> | 克隆项目时递归克隆所有子模块 |
git submodule update --remote | 更新子模块到远程最新提交 |
上述内容主要由 AI 提供再进行汇总整理。