Git 命令速查手册

写在前面

这是一份面向日常开发的 Git 速查手册:先建立版本控制的基础模型,再按常见工作流查找命令。涉及可能丢失数据的操作时,请先确认作用范围并保留可恢复路径。

一、Git 是什么

Git 是一个分布式版本控制系统。它用提交记录项目在不同时刻的完整快照,让开发者能够比较变化、创建分支、合并协作,并在误操作后找回历史。所谓“分布式”,是指一次完整克隆通常包含仓库历史和分支信息;即使暂时没有网络,也能提交、切换分支和查看历史。

Git 本身负责版本历史,GitHub、GitLab、Gitee 等平台则提供远程托管、权限管理、代码评审和 CI/CD。会使用 Git 不等于只能依赖某个托管平台。

如果想进一步理解这些命令背后的存储机制,可以阅读《深入理解 .git 目录:Git 如何存储提交、分支与暂存区》。

1.1 Git 的四个区域

理解命令前,先记住文件会在四个区域之间流动:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
工作区(Working Tree)
    │ git add
    ↓
暂存区(Index / Staging Area)
    │ git commit
    ↓
本地仓库(Local Repository)
    │ git push
    ↓
远程仓库(Remote Repository)
  • 工作区:实际编辑的文件;
  • 暂存区:下一次提交准备包含的快照;
  • 本地仓库:保存在 .git 中的对象、引用和历史;
  • 远程仓库:用于交换提交的另一个仓库,origin 只是常见的远程名称。

git diff 默认比较工作区与暂存区,git diff --staged 比较暂存区与 HEAD,git status 则概括各区域之间的差异。

1.2 提交、分支与 HEAD

Git 的核心对象包括 Blob(文件内容)、Tree(目录快照)、Commit(一次提交)和 Tag(标签)。提交保存指向 Tree 和父提交的关系,并由内容计算对象 ID。

分支本质上是一个可移动引用,指向某次提交;HEAD 通常指向当前分支。创建分支不会复制一份代码,只是新增一个引用。提交后,当前分支向新提交移动。

1.3 文件的三种状态

  • 未跟踪(Untracked):Git 尚未纳入版本管理;
  • 已修改(Modified):已跟踪文件在工作区发生变化;
  • 已暂存(Staged):变化已经进入暂存区,等待提交。

下文按日常工作顺序整理命令。涉及 reset --hard、clean -fd、强制推送等会丢失数据的操作,执行前务必先用状态或预览命令确认范围。


二、基础操作

2.1 git init — 初始化仓库

1
2
3
4
5
# 在当前目录初始化
git init

# 初始化到指定目录
git init my-project

2.2 git clone — 克隆远程仓库

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 克隆到当前目录下的同名文件夹
git clone https://github.com/user/repo.git

# 克隆到指定目录名
git clone https://github.com/user/repo.git my-folder

# 克隆指定分支
git clone -b main https://github.com/user/repo.git

# 浅克隆(只取最近一次提交,大仓库速度快)
git clone --depth=1 https://github.com/user/repo.git

2.3 git add — 添加到暂存区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 添加指定文件
git add README.md

# 添加多个文件
git add file1.md file2.md

# 添加当前目录下所有变更
git add .

# 添加所有变更(包括已删除的文件)
git add -A

# 交互式添加(按块选择要暂存的内容)
git add -p

2.4 git commit — 提交

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 不带参数会打开编辑器让你写提交信息
git commit

# -m 直接指定提交信息(message)
git commit -m "feat: 添加用户登录功能"

# -a 提交所有已跟踪文件的修改(跳过 git add)
git commit -a -m "fix: 修复空指针异常"

# --amend 修改上一次提交(未推送到远程时使用)
git commit --amend -m "feat: 添加用户登录和注册功能"

# --allow-empty 空提交(用于触发 CI 或占位)
git commit --allow-empty -m "chore: 触发部署"

提交信息建议遵循 Conventional Commits 约定(见第十节):用 feat / fix / docs 等前缀,便于 git log 阅读与自动生成 changelog。

--amend 会用新提交替换上一次提交(改写历史),和 rebase 同理:已推送的提交不要 amend,否则别人的仓库会和你对不上;它只适合修正还没推送的本地提交。

2.5 git status — 查看状态

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看工作区和暂存区状态
git status

# 简短输出(一行一个文件)
git status --short

# 在简短输出中同时显示被忽略的文件
git status --short --ignored

# 脚本解析时使用稳定的 porcelain v1 格式
git status --porcelain=v1

简短格式的每一行通常是 XY 路径。在没有未解决合并冲突时,X(第一列)表示暂存区相对 HEAD 的状态,Y(第二列)表示工作区相对暂存区的状态;空格表示该层没有变化。因此不能只看见一个 M 就下结论,还要看它位于哪一列。

下表列出日常最常见的组合,并用 ␠ 显式表示 ASCII 空格;完整状态还包括类型变化 T、复制检测 C 和冲突状态 U 等:

显示X(暂存区)Y(工作区)含义
A␠A空格新文件已经加入暂存区,工作区没有继续修改
M␠M空格修改已经暂存,工作区与暂存区一致
␠M空格M已跟踪文件在工作区被修改,但尚未暂存
MMMM修改已暂存,随后工作区又修改了一次;提交只包含暂存区版本
D␠D空格删除已经暂存
␠D空格D工作区中的文件已删除,但删除尚未暂存
R␠R空格重命名已经暂存,通常显示为 R old-name -> new-name
????未跟踪路径:既不在提交中,也没有加入暂存区
!!!!被忽略路径;只有使用 --ignored 时才会显示

例如下面四行代表四种不同状态:

1
2
3
4
M  src/app.cs
 M README.md
MM config/settings.json
?? local-notes.md

?? 和 !! 是特殊状态:Git 还不能按“暂存区与工作区的差异”解释未跟踪或被忽略路径,所以两列使用相同字符。发生未解决的合并冲突时,XY 则表示冲突双方相对共同祖先的状态,可能出现 UU、AA、DU 等组合;此时应结合普通 git status 的说明处理,不要继续套用上表的常规含义。

--short 面向人工快速查看;自动化脚本更适合使用 --porcelain=v1(或按需使用 v2),因为 porcelain 格式承诺不受颜色、相对路径等用户配置影响,并保持向后兼容。

2.6 git log — 查看提交历史

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 查看完整提交历史
git log

# 单行显示
git log --oneline

# 显示最近 5 条
git log --oneline -5

# 图形化显示分支合并历史
git log --oneline --graph --all

# 显示每次提交的文件变更统计
git log --stat

# 显示指定文件的提交历史
git log -- path/to/file

# 按作者筛选
git log --author="张三"

# 按时间筛选
git log --since="2026-01-01" --until="2026-03-31"

# 按提交信息搜索
git log --grep="修复"

2.7 git rm / git mv — 删除与移动文件

1
2
3
4
5
6
7
8
# 从工作区和暂存区删除已跟踪文件
git rm old-file.md

# 只停止跟踪,保留工作区文件(常用于误提交的配置文件)
git rm --cached appsettings.local.json

# 移动或重命名文件,并暂存这次变化
git mv old-name.md new-name.md

Git 不会永久存储“重命名”这一动作,而是在比较内容时根据相似度识别重命名。


三、分支管理

3.1 git branch — 分支操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看所有本地分支
git branch

# 查看所有分支(含远程)
git branch -a

# 创建新分支(不切换)
git branch feature/login

# 删除已合并的分支
git branch -d feature/login

# 强制删除分支(无论是否合并)
git branch -D feature/login

# 重命名当前分支
git branch -m new-branch-name

# 重命名指定分支
git branch -m old-name new-name

3.2 git checkout / switch — 切换分支

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 切换到已有分支
git checkout main

# 创建并切换到新分支
git checkout -b feature/login

# 切换到上一个分支
git checkout -

# Git 2.23+ 推荐用 switch 替代
git switch main
git switch -c feature/login    # 创建并切换
git switch -                   # 切换到上一个分支

3.3 git merge — 合并分支

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 将 feature 分支合并到当前分支
git merge feature/login

# 合并时不使用快进(保留分支历史)
git merge --no-ff feature/login

# 只取另一个分支中指定文件的版本(这不是 merge)
git restore --source=feature/login -- path/to/file

# 取消正在进行的合并
git merge --abort

merge 把指定分支的提交并入当前分支,结果有两种:

  • 快进合并(fast-forward):当前分支是另一分支的直接祖先(中间没有新提交)时,Git 只把指针前移,不产生新提交,历史保持直线。
  • 三方合并:两条分支各有新提交时,Git 生成一个合并提交(有两个父提交)把两条线汇合,历史是非线性的。

--no-ff 强制生成合并提交,即使能快进——目的是保留"这里曾有一条特性分支"的拓扑结构,便于事后追溯。

3.4 git rebase — 变基

rebase 是"换底":把当前分支的提交一个个摘下来,重新应用到目标分支的最新提交之上。效果是把当前分支的起点挪到目标分支顶端,得到一条线性历史。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 将当前分支变基到 main
git rebase main

# 交互式变基(修改最近 3 次提交)
git rebase -i HEAD~3

# 取消正在进行的变基
git rebase --abort

# 变基遇到冲突解决后继续
git rebase --continue

同样是把 feature 合进 main,merge 和 rebase 在图上长这样(假设 feature 有 A、B、C 三个提交,main 在此期间也前进了):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
修改前:
          A---B---C  feature
         /
    D---E---F---G  main

git merge(在 main 上执行 git merge feature):
          A---B---C  feature
         /         \
    D---E---F---G---M  main        ← M 是合并提交(父:C 和 G)

git rebase(在 feature 上执行 git rebase main):
                  A'--B'--C'  feature   ← 提交被重写,接在 G 之后
                 /
    D---E---F---G  main

关键区别:

  • merge 保留两条分支各自的历史,生成合并提交,拓扑是非线性的;
  • rebase 重写当前分支的提交(产生新的提交 ID),让它们线性地接在目标分支之后,看起来像从未分叉。

黄金法则:已经推送到远程、可能被他人拉取的提交,不要 rebase ——rebase 会改写历史,导致别人的仓库和你对不上。安全用法是只 rebase 还在自己本地的提交,例如在 feature 分支上 git rebase main 把 main 的最新改动垫到自己提交之下,再合并回 main,主线就是干净的线性历史。


四、远程协作

4.1 git remote — 远程仓库管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 查看远程仓库名
git remote

# 查看远程仓库详细信息(URL)
git remote -v

# 添加远程仓库
git remote add origin https://github.com/user/repo.git

# 修改远程仓库地址
git remote set-url origin https://github.com/user/new-repo.git

# 删除远程仓库
git remote remove origin

4.2 git fetch — 获取远程更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 获取所有远程分支的更新(不合并)
git fetch origin

# 获取指定分支
git fetch origin main

# 获取所有远程仓库的更新
git fetch --all

# 清理远程已经删除的跟踪分支
git fetch --prune

4.3 git pull — 拉取并整合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 拉取当前分支的远程更新(使用默认远程和分支)
git pull

# 拉取指定远程分支的更新并整合到当前分支
git pull origin main

# 明确使用 rebase 方式整合
git pull --rebase origin main

# 只允许快进;发生分叉时直接失败,不创建合并提交
git pull --ff-only origin main

# 明确使用 merge 方式整合
git pull --no-rebase origin main

fetch vs pull:fetch 只把远程的提交下载到本地,不动你的工作区和当前分支,是只读、安全的;pull = fetch + 整合,会直接更新当前分支。Git 原生默认在未配置整合方式时只允许快进,分支已经分叉则失败并提示先配置;Git for Windows 出厂在系统级配置中预设了 pull.rebase=false,因此在 Windows 上默认行为是合并。实际行为还会受 pull.rebase、pull.ff 等配置影响。意图重要时应显式使用 --ff-only、--rebase 或 --no-rebase。若只是想更新所有 remote 的远程跟踪分支,应使用 git fetch --all,而不是会继续整合当前分支的 git pull --all。

4.4 git push — 推送到远程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 推送到当前分支的跟踪远程(使用默认配置)
git push

# 将本地 main 推送到 origin 的同名分支
git push origin main

# 首次推送并建立跟踪关系
git push -u origin feature/login

# 推送所有本地分支
git push --all origin

# 推送标签
git push origin v1.0.0

# 推送所有标签
git push --tags

# 删除远程分支
git push origin --delete feature/login

# 安全强制推送:远端已被他人更新时拒绝覆盖
git push --force-with-lease

分支参数与 refspec

在下面这条命令中,末尾的 feature/login 首先是本地分支名:

1
git push origin feature/login

它表示“把本地 feature/login 推送到 origin 的同名分支”,是以下完整 refspec(引用规格)的简写:

1
git push origin feature/login:feature/login

refspec 的基本方向是 <源>:<目标>;用于 push 时,冒号左边是本地引用,右边是远程引用。只有两边名称相同时才能像前面那样省略右侧:

1
2
3
4
5
# 本地 feature/login → 远程 review/login
git push origin feature/login:review/login

# 当前 HEAD 指向的提交 → 远程 feature/login
git push origin HEAD:feature/login

upstream:本地分支默认跟踪谁

本地新建的 feature/login 与远程的 origin/feature/login 是两个引用。第一次推送时加 -u(--set-upstream),会在创建或更新远程分支的同时,记录本地分支的 upstream(上游跟踪分支):

1
2
3
4
git push -u origin feature/login

# 方括号中可看到当前本地分支跟踪的远程分支
git branch -vv

建立关系后,Git 才能在 git status 中计算该分支相对远程领先或落后多少提交,并让不带参数的 git push、git pull 知道默认目标。只执行 git push origin feature/login 也能创建远程分支,但在原本没有 upstream 时,它不会顺便建立这层跟踪关系。

Git 2.0 起,push.default 的内置默认值是 simple:当前分支已有同名 upstream 时,裸 git push 会推送当前分支;新分支还没有 upstream 时通常会报错并提示使用 --set-upstream。如果启用了 push.autoSetupRemote=true(Git 2.37+),Git 可以在首次裸推送时自动建立 upstream。

Visual Studio 把这套细节封装在“发布分支”和“推送”等界面操作中,所以日常使用 IDE 时通常不需要亲自输入 -u。底层仍然存在这层跟踪关系,可以随时用 git branch -vv 查看。

重写已经推送的历史前,应先与协作者确认。优先使用 --force-with-lease,不要直接使用 --force。

4.5 同一仓库推送到多个远程仓库

一个本地仓库可以配置多个具名远程仓库,例如同时关联 GitHub、GitLab 和 Gitee。最清晰、兼容性也最好的做法,是给每个托管平台使用独立的 remote:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 为同一个本地仓库配置多个远程
git remote add github git@github.com:user/project.git
git remote add gitlab git@gitlab.com:user/project.git
git remote add gitee git@gitee.com:user/project.git

# 查看配置
git remote -v

# 获取各远程的分支状态
git fetch --all --prune

# 分别推送 main;每个远程的结果都能单独确认
git push github main
git push gitlab main
git push gitee main

各 remote 可以分别维护 github/main、gitlab/main、gitee/main 等远程跟踪分支,适合需要从多个平台获取更新,或者各平台权限、分支保护规则不完全相同的场景。

Git 2.55 起,git push 也支持把多个 remote 编成一个远程组,用一条命令依次推送:

1
2
3
4
5
6
7
8
# 定义名为 publish 的远程组
git config remotes.publish "github gitlab gitee"

# 查看远程组配置
git config --get-all remotes.publish

# Git 2.55+:依次执行对三个远程的推送
git push publish main

远程组只是多次 git push 的快捷写法,不是分布式事务:各远程独立执行,某个远程失败后 Git 仍会继续推送后续成员,最终返回非零退出码;已经成功的推送不会自动回滚。远程组也不支持 --atomic。自动化脚本应检查退出码,并分别核对各远程的结果。

另一种方案,是给同一个 remote 配置多个仅用于推送的 pushurl:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# origin 原有的 url 继续用于 fetch;下面两个 pushurl 都用于 push
git remote set-url --add --push origin git@github.com:user/project.git
git remote set-url --add --push origin git@gitee.com:user/project.git

# 查看 origin 的全部推送地址
git remote get-url --all --push origin

# 首次推送并把本地 main 的 upstream 设为 origin/main
git push -u origin main

# 后续直接执行裸 git push,也会推送到 origin 的全部 pushurl
git push

只要当前分支的 upstream 指向 origin(克隆得到的默认分支通常已经如此),并且没有 remote.pushDefault 或 branch.<name>.pushRemote 覆盖推送目标,裸 git push 就会选中 origin,继而推送到它的全部 pushurl。这是“不指定远程名称,默认推送到多个仓库”最直接的配置。

pushurl 适合把同一份内容单向镜像到多个位置,但 fetch 仍只使用第一个普通 url,本地通常也只有一套 origin/* 远程跟踪分支。需要分别观察各平台的分支状态时,优先使用多个具名 remote。Git 2.55 的远程组目前仍需显式写成 git push publish;remote.pushDefault 用来指定默认 remote,不能把远程组变成裸 git push 的默认目标。

Visual Studio 可能只把这次操作概括为一次“推送”,而 origin/* 也只反映抓取来源的状态。第一次启用多个 pushurl 后,建议在终端执行 git remote get-url --all --push origin 核对地址,并分别登录各托管平台确认目标分支确实更新,不要只看 IDE 中的一组 incoming/outgoing 数字。

git push --all origin 表示把所有本地分支推送到一个远程,并不表示推送到所有远程;不要把它与多远程推送混淆。


五、撤销与回退

5.1 git reset — 回退提交

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 回退到上一个提交(默认 --mixed 模式)
git reset HEAD~1

# 软回退:保留修改在暂存区(撤销 commit,不撤销 add)
git reset --soft HEAD~1
git reset --mixed HEAD~1

# 硬回退:丢弃所有修改(谨慎使用)
git reset --hard HEAD~1

# 回退到指定提交
git reset --soft abc1234

三种模式都把 HEAD 往回移,区别在"剩下的改动放哪":

模式工作区暂存区用途
--soft保留保留(改动都还在暂存区)只撤销 commit,改动和 add 都留着,改完信息重新提交
--mixed(默认)保留清空(改动退回工作区,需重新 add)撤销 commit 且撤销 add,重新挑选要提交的内容
--hard丢弃丢弃彻底放弃这些改动(不可逆,谨慎)

HEAD~1 表示上一个提交,HEAD~3 表示往上三个提交。

5.2 git revert — 撤销提交(生成新提交)

1
2
3
4
5
6
7
8
9
# 撤销指定提交(会生成一个新的撤销提交)
git revert abc1234

# 撤销最近一次提交
git revert HEAD

# 撤销多个提交(不自动提交,最后一起提交)
git revert --no-commit HEAD~3..HEAD
git commit -m "revert: 撤销最近三次提交"

reset vs revert:reset 是"回退历史",适合未推送的提交;revert 是"新增一个撤销提交",适合已推送的提交,不影响他人。

5.3 git checkout — 恢复文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 恢复工作区指定文件(丢弃未暂存的修改)
git checkout -- file.md

# Git 2.23+ 推荐用 restore 替代
git restore file.md

# 从暂存区恢复文件到工作区
git restore --staged file.md

# 恢复某个文件到指定提交的版本
git restore --source=abc1234 file.md

5.4 git stash — 暂存工作区

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 暂存当前修改
git stash

# 暂存时附加说明
git stash save "开发到一半的功能"

# 暂存时包含未跟踪的文件
git stash -u

# 查看暂存列表
git stash list

# 恢复最近一次暂存
git stash pop

# 恢复指定暂存(不删除记录)
git stash apply stash@{1}

# 删除指定暂存
git stash drop stash@{0}

# 清空所有暂存
git stash clear

git stash push -m "说明" 是目前更明确的写法;默认不会包含未跟踪文件,需要使用 -u。


六、标签与发布

6.1 git tag — 标签管理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# 查看所有标签
git tag

# 创建轻量标签
git tag v1.0.0

# 创建附注标签(推荐,包含作者、日期、说明)
git tag -a v1.0.0 -m "正式版本 1.0.0"

# 给指定提交打标签
git tag -a v0.9.0 abc1234 -m "补打标签"

# 查看标签详情
git show v1.0.0

# 删除本地标签
git tag -d v1.0.0

# 删除远程标签
git push origin --delete v1.0.0

七、查看与搜索

7.1 git show — 查看对象与提交

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 查看最新提交及差异
git show

# 查看指定提交
git show abc1234

# 查看某次提交中的文件内容
git show abc1234:path/to/file.md

# 查看标签指向的对象
git show v1.0.0

7.2 git diff — 查看差异

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 查看工作区与暂存区的差异
git diff

# 查看暂存区与最新提交的差异
git diff --staged

# 查看两个提交之间的差异
git diff abc1234 def5678

# 查看指定文件的差异
git diff -- path/to/file

# 只显示变更的文件名
git diff --name-only

# 显示差异统计(增删行数)
git diff --stat

7.3 git blame — 查看文件每行的修改者

1
2
3
4
5
6
7
8
# 查看文件每行的最后修改者和提交
git blame README.md

# 指定行范围(第 10 到 30 行)
git blame -L 10,30 README.md

# 只显示邮箱
git blame -e README.md

7.4 git grep — 在仓库中搜索

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 在所有已跟踪文件中搜索关键词
git grep "TODO"

# 显示行号
git grep -n "TODO"

# 只显示文件名
git grep -l "TODO"

# 在指定提交中搜索
git grep "TODO" abc1234

八、实用技巧

8.1 git cherry-pick — 摘取提交

1
2
3
4
5
6
7
8
# 将指定提交应用到当前分支
git cherry-pick abc1234

# 摘取多个提交
git cherry-pick abc1234 def5678

# 摘取提交但不自动提交(方便修改后再提交)
git cherry-pick -n abc1234

8.2 git bisect — 二分查找问题提交

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 开始二分查找
git bisect start

# 标记当前提交有问题
git bisect bad

# 标记某个正常的提交
git bisect good v1.0.0

# Git 会自动切换到中间提交,测试后标记
git bisect good   # 或 git bisect bad

# 找到问题提交后结束查找
git bisect reset

8.3 git reflog — 查看引用移动记录

1
2
3
4
5
6
7
8
# 查看 HEAD 的移动记录,包括提交、重置和分支切换等
git reflog

# 查看指定分支的操作记录
git reflog show feature/login

# 在仍可访问的提交上创建分支,避免只进入 detached HEAD
git switch -c recovered-work abc1234

reflog 记录本地引用的更新历史,常能找回因误删分支或错误 reset 而失去名称的提交;它是本地、会过期的记录,不是永久备份。

8.4 git worktree — 多分支同时工作

git worktree 允许一个仓库同时拥有主工作树和多个关联工作树。它们共享对象数据库、分支、标签、远程仓库配置等仓库级数据,但各自拥有独立的工作区文件、HEAD 和暂存区。因此,它比重新克隆一份仓库更省空间,也特别适合“当前功能做到一半,临时修复线上问题”或同时构建、测试不同分支。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# 查看所有工作树
git worktree list

# 在新工作树中检出一个已经存在的分支
git worktree add ../wt-hotfix hotfix/urgent

# 从 origin/main 创建新分支,并放入新工作树
git fetch origin
git worktree add -b feature/payment ../wt-payment origin/main

# 临时审阅或测试某个提交,不创建分支
git worktree add --detach ../wt-review origin/main

# 查看某个关联工作树是否干净
git -C ../wt-hotfix status --short

# 删除干净的关联工作树,同时清理管理记录
git worktree remove ../wt-hotfix

同一个本地分支默认不能同时检出到两个工作树,因为两个目录并行修改同一分支很容易互相覆盖。确实只想查看同一个提交时,使用 --detach;需要并行开发时,为每个工作树创建独立分支。

关联工作树目录里的 .git 通常是一个指向主仓库管理目录的文件,不是完整的 .git 目录。不要直接复制它,也不要直接删除工作树目录;优先使用 git worktree move 和 git worktree remove:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
# 移动关联工作树
git worktree move ../wt-payment ../payment-work

# 先预览,再清理已经不存在的工作树留下的管理记录
git worktree prune --dry-run --verbose
git worktree prune

# 关联工作树被手工移动后,修复它与主仓库的连接
git worktree repair ../payment-work

# 移动磁盘等位置可能暂时离线时,锁定以防被 prune 清理
git worktree lock --reason "存放在移动磁盘" ../payment-work
git worktree unlock ../payment-work

所有工作树共享分支引用和 stash 列表:在一个工作树中创建的提交,其他工作树会立即看到;在一个工作树中执行 git stash,记录也会出现在其他工作树的 git stash list 中。删除前务必在目标工作树内检查 git status。git worktree remove --force 能删除包含修改或未跟踪文件的工作树,可能造成无法由 Git 恢复的数据丢失,不应当作常规清理手段。

8.5 常用配置

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 设置用户名和邮箱
git config --global user.name "张三"
git config --global user.email "zhangsan@example.com"

# 设置默认分支名为 main
git config --global init.defaultBranch main

# 设置默认编辑器
git config --global core.editor "code --wait"

# 设置别名
git config --global alias.st status
git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.lg "log --oneline --graph --all"

# 查看所有配置
git config --list

# Git for Windows 安装了 Git Credential Manager 时使用
git config --global credential.helper manager

九、仓库迁移与高级操作

9.1 git bundle — 离线迁移与备份仓库历史

git bundle 把 Git 对象与引用打包成单个文件,适合隔离网络之间传输仓库,也可以制作可验证的历史备份。Bundle 可以被 clone 和 fetch 读取,但不能像普通远程仓库那样直接 push 写入。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 创建包含所有可达引用的完整 Bundle
git bundle create repo-backup.bundle --all

# 检查 Bundle 格式和前置提交是否满足
git bundle verify repo-backup.bundle

# 查看 Bundle 包含哪些分支或标签
git bundle list-heads repo-backup.bundle

# 从 Bundle 克隆仓库
git clone repo-backup.bundle restored-repo

# 从 Bundle 获取 main 分支到当前仓库
git fetch repo-backup.bundle main:refs/remotes/bundle/main

# 创建 main 分支在 v1.0.0 之后的增量 Bundle
git bundle create update.bundle v1.0.0..main

完整 Bundle 只包含引用可达的提交和 Git 对象,不包含工作区未提交修改、暂存区、每仓库配置、Hooks 等本地状态。增量 Bundle 依赖接收方已经具有范围左侧的前置提交,接收前应执行 git bundle verify。

9.2 git submodule — 管理子模块

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 添加另一个仓库作为子模块
git submodule add https://github.com/user/theme.git themes/theme

# 克隆后初始化并检出子模块
git submodule update --init --recursive

# 克隆时直接递归获取子模块
git clone --recurse-submodules https://github.com/user/repo.git

# 按 .gitmodules 中配置的远程分支更新
git submodule update --remote --recursive

# 查看子模块状态
git submodule status --recursive

父仓库记录的是子模块提交 ID,不是子模块文件内容。如果直接修改子模块代码,应先在子模块仓库中提交;当父仓库记录的子模块提交 ID 发生变化时,再在父仓库中提交这次指针更新。

9.3 git sparse-checkout — 只检出部分目录

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 启用 Cone 模式,适合按目录选择
git sparse-checkout init --cone

# 只检出两个目录
git sparse-checkout set src docs

# 再增加一个目录
git sparse-checkout add tools

# 关闭稀疏检出,恢复完整工作区
git sparse-checkout disable

稀疏检出主要减少工作区文件数量;若还希望减少首次下载的数据量,可在克隆时结合部分克隆参数,例如 git clone --filter=blob:none --sparse <url>。

9.4 git archive — 导出不含 Git 历史的源码包

1
2
3
4
5
6
# 将当前 HEAD 导出为 zip
git archive --format=zip --output=source.zip HEAD

# 导出指定标签,并给文件添加顶层目录
git archive --format=tar.gz --prefix=myapp-1.0.0/ \
  --output=myapp-1.0.0.tar.gz v1.0.0

archive 适合发布源码快照;需要保留提交历史或离线迁移时,应使用 bundle。

9.5 git clean — 清理未跟踪文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 预览将要删除的未跟踪文件,建议先执行
git clean -n

# 删除未跟踪文件
git clean -f

# 同时删除未跟踪目录
git clean -fd

# 连被 .gitignore 忽略的文件也删除(高风险)
git clean -fdx

git clean 删除的文件通常不能通过 Git 恢复。先用 -n 预览,并避免在目标范围不清楚时使用 -x。

9.6 git maintenance / fsck — 维护与完整性检查

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 立即运行仓库维护任务
git maintenance run

# 为当前仓库启用后台定期维护
git maintenance start

# 检查对象数据库的连通性和有效性
git fsck --full

# 查看松散对象与磁盘占用
git count-objects -vH

Git 通常会自动执行必要维护。除非正在诊断仓库体积或性能问题,否则不必频繁手动运行激进的清理命令。


十、提交信息规范

推荐使用 Conventional Commits(约定式提交):用统一前缀让 git log 更易读,并为自动生成 changelog、计算语义化版本号提供依据。完整结构如下(header、body、footer 之间用空行分隔):

1
2
3
4
5
<type>(<scope>)!: <description>

<body>

<footer(s)>
  • type(必填):本次提交的性质,见下表;
  • scope(可选):影响范围,如 auth、api;使用时须放在圆括号中,省略时连同括号一起省略;
  • !(可选):紧跟 type 或 scope,表示破坏性变更(breaking change);
  • description(必填):简短描述;推荐使用祈使句,末尾不加句号;
  • body(可选):解释为什么这么改;推荐每行不超过约 72 字符;
  • footer(可选,可有多个):BREAKING CHANGE: 说明、Closes #123 关联 issue、Refs: 引用等。

常用 type:

类型说明
feat新功能
fix修复 Bug
docs文档变更
style代码格式(不影响逻辑)
refactor重构(不是新功能也不是修复)
perf性能优化
test增加或修改测试
chore构建工具或辅助工具变更
ciCI/CD 配置变更

单行示例:

1
2
3
4
git commit -m "feat(auth): 添加 OAuth2 第三方登录"
git commit -m "fix(api): 修复分页查询越界问题"
git commit -m "docs: 更新 API 接口文档"
git commit -m "revert: 撤销 abc1234 的登录改动"

带破坏性变更与正文、脚注的完整示例(多个 -m 各成一段,分别对应 header / body / footer):

1
2
3
4
5
git commit \
  -m "feat(auth)!: 移除已废弃的旧版登录接口" \
  -m "旧接口自 v2.0 起弃用,本次彻底删除,调用方需改用 OAuth2。" \
  -m "BREAKING CHANGE: 删除 POST /login/legacy,迁移见 #42" \
  -m "Closes #40"

Conventional Commits 明确规定了 feat、fix 和破坏性变更的语义化版本影响:fix 对应 PATCH,feat 对应 MINOR,带 ! 或 BREAKING CHANGE: 的提交对应 MAJOR。表中的其他类型以及常见的 build(构建系统或外部依赖)、revert(撤销提交,本文 4.2 已使用)属于工具生态和团队实践中的惯例,可以自行扩展,但应在仓库内保持一致。

规范一旦推送就难改:提交信息在 commit 时写下,但真正的约束在推送之后——一旦进入远程共享历史,再改就要动 --amend / rebase,进而触发 1.4 的 amend 禁忌、2.4 的 rebase 黄金法则和 3.4 的 --force-with-lease。写的时候就写好,比推送后补救便宜得多。规范化的 type 还能让工具链自动派生价值:commitizen 引导填写、commitlint 校验、release-please 按类型生成 changelog 与版本号(同类的 standard-version 已被官方标记为弃用,其 README 建议 GitHub 用户改用 release-please)。

参考资料

Licensed under CC BY-NC-SA 4.0
最后更新于 Wednesday, September 2, 2026