Git 实用操作全指南:从基础到进阶
学习 Git 最权威的资料是其官方文档,推荐优先查阅:Git 官方文档(中文版)。本文整理了日常开发中高频使用的 Git 命令,按核心场景分类,方便我快速查阅和使用。
一、Git 核心概念:工作区、暂存区与仓库
Git 工作流程围绕三个核心区域展开,理解它们的关系是熟练使用 Git 的基础。
- 工作区:本地电脑中实际编辑文件的目录,即日常写代码的地方。
- 暂存区(索引):临时存放修改的区域,用于确认哪些修改需要提交到仓库。
- 本地仓库:存储所有提交历史的数据库,记录了项目的完整版本变化。
- 远程仓库:托管在服务器上的仓库(如 GitHub、GitLab),用于团队协作和代码备份。
核心流程:工作区 →(git add)→ 暂存区 →(git commit)→ 本地仓库 →(git push)→ 远程仓库
二、基础操作:仓库初始化与文件管理
这部分包含从创建 / 克隆仓库到跟踪、提交文件的基础命令,是日常开发的高频操作。
1. 仓库创建与克隆
git init:在当前目录初始化一个新的 Git 仓库。git clone <URL>:从指定 URL 克隆远程仓库到本地。git clone --depth 1 <URL> <目录名>:只克隆最近 1 次提交历史,减少下载体积,并指定本地目录。git clone -b <分支名> <URL>:克隆远程仓库的指定分支。
2. 文件跟踪与删除(git add / rm)
git add <文件名>:将工作区的文件添加到暂存区,开始跟踪该文件。git add [dir]:添加指定目录到暂存区,包括子目录。git add -u:将正在跟踪的被修改过的工作区文件添加到暂存区。git add .:将当前目录所有文件添加到暂存区。git rm <文件名>:删除被 Git 跟踪的文件(同时删除工作区文件)。git rm --cache <文件名>:从暂存区移除文件,不再跟踪,但保留工作区文件。
3. 文件移动与重命名(git mv)
git mv <原文件名> <新文件名>:重命名一个已被 Git 跟踪的文件。git mv <原目录/文件名> <新目录/文件名>:将文件移动到新目录并重命名。- 注意:此命令能保留文件的提交历史。你用
git status查看会发现renamed: playbooks/networking/wifi_ap.md -> playbooks/wifi/wifi_access_point.md这样的log。如果直接使用操作系统的mv或rename命令移动文件,Git 会将其视为“删除原文件”和“添加新文件”两个独立操作,会丢失修改的关联性。使用git mv可以避免这个问题。
3. 提交修改
先检查配置gitconfig--globaluser.name# 查询全局user.namegitconfig--globaluser.email# 查询全局user.emailgitconfig--global--list# 查询全局配置gitconfig--globaluser.name"xxx"# 配置全局user.namegitconfig--globaluser.email"xxx"# 配置全局user.emailgit commit -m "提交说明":将暂存区的修改提交到本地仓库,并添加说明。git commit -s -m "提交说明":添加签名。git commit -s 后回车:添加签名,Git 会打开默认编辑器(如vim或nano)(git config --global core.editor vim命令可将git默认编辑器调整为vim)git commit -am "提交说明":直接将已跟踪文件的修改(未暂存)添加到暂存区并提交。(省去了git add ,太着急了吧)
4. 查看历史提交(git log)
git status:查看文件状态(未暂存文件红色,暂存文件绿色,提交后无显示)。git log:查看提交历史,按时间倒序排列。git log -n 2:查看最近两次的提交历史,按时间倒序排列。git log da35b6 -n 2:查看da35b6在内最近两次的提交历史,按时间倒序排列。git log -p -2:查看最近 2 次提交的具体修改内容(对比差异)。git log --pretty=oneline:将每条提交记录压缩为一行显示,简洁高效。git log --pretty=format:"%h 【%an】 %ar :《%s》":自定义提交记录格式,包含简化哈希(% h)、作者(% an)、距今时间(% ar)、提交说明(% s)。git log --graph:以图形化形式展示提交历史,清晰查看分支合并关系。git log -- <文件名>:查看对指定文件的所有提交git log -S "function":看哪些提交改了function这个字符串git log -p -S "function":在上一个基础上看修改function这个字符串的具体内容git log -p -S "function"– driver/your_driver.c :在上一个基础上缩小查找范围git log -L :函数名:文件名:通过-L选项指定函数范围,直接定位修改过该函数的所有提交git log branch1..branch2 --oneline:查看branch2在branch1基础上还有哪些提交
三、差异对比(diff):工作区、暂存区与仓库的差异
通过git diff命令查看不同区域的文件差异,是定位修改内容的关键操作。
| 命令 | 对比范围 | 用途 |
|---|---|---|
git diff | 工作区 vs 暂存区(git add) | 查看未暂存的修改 |
git diff HEAD <文件名> | 工作区 vs 本地仓库(当前 HEAD) | 查看工作区与最新提交的差异 |
git diff --cached <文件名> | 暂存区(git add) vs 本地仓库(当前 HEAD) | 查看已暂存但未提交的修改 |
git diff refs/remotes/origin/master | 工作区 vs 远程 master 分支 | 查看本地与远程分支的差异 |
git diff <旧提交ID1> <新提交ID2> --stat | 两次提交之间 | 查看两次提交的文件修改统计(如修改行数) |
git diff <旧提交ID1> <新提交ID2> <文件名> | 两次提交的指定文件 | 查看具体文件在两次提交间的差异 |
四、远程仓库操作:连接与同步
团队协作中需频繁与远程仓库交互,包括连接、推送、拉取等操作。
1. 远程仓库管理
git remote -v:查看已配置的远程仓库 URL(-v表示显示详细信息)。git remote add <远程名> <URL>:添加远程仓库,通常远程名设为origin。git remote rm <远程名>:删除指定的远程仓库配置。git remote rename <旧名> <新名>:修改远程仓库的名称。
2. 推送与拉取
git push <远程仓库> <本地分支名>:<远程分支名>:将特定本地分支推送到远程仓库,若远程分支不存在则自动创建。git push <远程仓库> <远程分支名>:简化写法,将本地master分支推送到origin远程的master分支。git push -u <远程仓库> <远程分支名>:推送分支并建立 “跟踪关系”,后续推送直接用git push。git push <远程仓库> --delete <分支名>删除 指定的 远程仓库 中的 指定分支git fetch <远程仓库>:拉取远程仓库的所有分支更新到本地(不合并到当前分支),若不指定分支则拉取所有分支。git fetch --prune:拉取远程更新的同时,删除本地存在但远程已删除的分支,保持同步。
3. 远程分支查看删除等
git branch -r:只查看本地缓存的远程分支(如origin/master)。git branch -a:查看所有分支(本地分支白色,当前分支绿色,远程分支红色)。git branch -vv:查看本地分支与远程分支的关联关系,以及最近一次提交信息。git remote show <远程名>:展示本地分支与远程分支的详细关联情况,包括更新状态。
五、分支操作:创建、切换与合并
分支是 Git 核心功能,用于并行开发、修复 bug 等场景,避免影响主分支。
1. 分支创建与切换
git branch <分支名>:创建新分支(如git branch Lyrix)。git checkout <分支名>或git switch <分支名>:切换到指定分支(两者功能一致)。git checkout -b <新分支名>:创建新分支并立即切换到该分支。git checkout -b <本地分支名> <远程名>/<远程分支名>:基于远程分支创建本地分支,并建立跟踪关系。git checkout .:放弃工作区所有未暂存的修改,恢复到最近一次提交状态。
2. 分支合并
git merge <待合并分支名>:将指定分支的内容合并到当前分支(如在master分支执行git merge Lyrix,将Lyrix合并到master)。- 合并冲突处理:合并时若出现冲突,先用
git status查看冲突文件,用编辑器修改冲突内容(保留需要的代码,删除冲突标记<<<<<<<=======>>>>>>>,),再执行git add和git commit完成合并。
3. 分支删除
git branch -d <分支名>:删除已合并到其他分支的本地分支(若分支未合并,会提示报错)。git branch -D <分支名>:强制删除未合并的本地分支(谨慎使用,避免丢失代码)。git push <远程名> -d <远程分支名>:删除远程仓库的指定分支(如git push origin -d Lyrix)。
4. 摘樱桃 cherry-pick
git cherry-pick <提交ID1> <提交ID2> <提交ID3>:将其他分支的某个特定提交 “复制” 到当前分支,适用于只需要单个提交、不需要整分支合并的场景(俗称 “摘樱桃”)。可以一次摘很多,用空格分隔,但注意他们是。
5. 分支重命名
git branch -m <new_branch_name>:重命名当前所在分支。
六、进阶操作:储藏、补丁与版本回退,子模块
这部分包含应对临时需求、代码复用、版本修正的进阶命令,解决开发中的特殊场景。
1. 工作区储藏(Stash)
当需要临时切换分支,但当前工作区修改未完成、不便提交时,用stash保存工作区状态。
git stash:将工作区和暂存区的修改保存到 “储藏栈”,工作区恢复干净状态。git stash save "储藏说明":保存储藏时添加说明,方便后续识别。git stash pop:应用储藏栈中最近的一次储藏,并删除该储藏记录(只能用一次)。git stash apply:应用指定的储藏(如git stash apply stash@{1}),且不删除储藏记录(可多次应用)。git stash list:查看所有储藏记录。git stash drop <储藏ID>:删除指定的储藏记录(如git stash drop stash@{0})。git stash show:查看储藏内容中哪些文件有修改。git stash show -p:查看储藏内容的具体修改差异。
2. 补丁操作(Patch)
通过补丁文件分享代码修改,适用于未通过远程仓库协作的场景(如邮件发送修改)。
(1)生成补丁(git diff或git format-patch)
git diff <文件名> > <补丁名>.patch:生成单个文件的修改补丁(如git diff Test.java > test.patch)。git diff > <补丁名>.patch:生成所有修改文件的补丁。git format-patch <提交ID1>..<提交ID2> --stdout > <补丁名>.patch:生成两个提交间所有修改的补丁(不包含提交ID1)。git format-patch -1 <提交ID> --stdout > <补丁名>.patch:生成单个提交的补丁。git format-patch HEAD~3 --stdout > <补丁名>.patch:生成最近 3 次提交的补丁。
(2)应用补丁
git apply <补丁名>.patch:应用git diff生成的补丁,需手动执行git add和git commit。git am <补丁名>.patch:应用git format-patch生成的补丁,自动包含提交信息,无需手动提交。git apply --check <补丁名>.patch:检查补丁是否可正常应用(无输出则表示无冲突)。git apply -R <补丁名>.patch:撤销已应用的补丁。
3. 版本回退与修改
(1)版本回退(reset)
修改 HEAD 指针位置,回退到历史版本,适用于需要彻底删除后续提交的场景(谨慎使用,避免丢失代码)。
git reflog:查看所有历史版本的操作记录,获取需要回退的提交 ID。(如果之前rebase啥的把有用从commit给误删了,需要用这条命令查看之前 HEAD 指针的记录)git reset --hard <提交ID>:强制回退到指定版本,工作区、暂存区均同步修改(会覆盖本地未提交的代码,需提前备份)。git reset --soft <提交ID>:回退到指定版本,暂存区和工作区的修改保留(未提交状态)。git reset --mixed <提交ID>:回退到指定版本,暂存区修改清空,工作区修改保留(默认模式)。- 远程版本回退:若已推送到远程仓库,需执行
git push -f <远程名> <分支名>强制覆盖远程分支(团队协作中需提前沟通,避免影响他人)。
(2)反做(revert)
撤销某个历史提交的修改,但保留该提交之后的所有版本,适用于需要 “取消旧修改” 但不删除后续提交的场景(比reset更安全,推荐优先使用)。
git revert -n <提交ID>:反做指定提交的修改(-n表示不自动提交)。- 修改冲突文件(若有),执行
git add和git commit -m "撤销XXX提交",再用git push推送到远程。
(3)修改提交历史(rebase)
用于 ①合并多个提交、②提交顺序调整、③提交说明修改、④适用于提交历史混乱时整理记录(仅整理本地未推送的提交,已推送的提交禁止使用)。
git rebase -i HEAD~2:交互式修改最近 2 次提交(HEAD~2表示最近 2 次,可替换为具体提交 ID)。git rebase -i <commit_id>:修改指定的<commit_id>只有的提交git rebase -i --root:从最初的 commit 之前开始修改。- 执行命令后会弹出编辑界面,按提示修改提交(如将
pick改为squash合并提交,删除行则删除提交)具体步骤参考我的博客:【git rebase操作】.
4. git 子模块(git submodule)
在大型项目、开源项目或者包含第三方依赖库的代码仓库中经常用到。简单来说,Git 允许你把另一个独立的 Git 仓库,当作当前仓库的一个“子文件夹”来引用。
比如,你的项目依赖了一个第三方的开源库,你不想把它的代码直接复制粘贴到你的项目里(这样不好同步更新),于是你选择把它作为一个“子模块”挂载进来。
当你拉取包含git子模块的项目时你只需要执行:
# 初始化并递归更新当前仓库中所有的 Git 子模块(Submodule)gitsubmodule update--init--recursive如果你作为大型项目开发这个,如何第三方开源仓库的指定 commit 作为子模块挂载到你的工程呢?
第一步:添加子模块:
# 语法:git submodule add <第三方仓库地址> <你希望存放的子模块目录名>gitsubmoduleaddhttps://github.com/username/repo.git path/to/submodule执行完这条命令后,Git 会自动把这个仓库克隆到你指定的目录下,并且会在你的项目根目录生成一个
.gitmodules文件(用来记录子模块的仓库地址和路径)。进入子模块,切换到指定的 Commit
刚添加完时,子模块默认会停留在第三方仓库的默认分支(比如
main)的最新提交上。我们需要把它切换到你想要的那个“指定 commit”。# 1. 进入子模块的目录cdpath/to/submodule# 2. 检出(checkout)你指定的 commit ID(或者 tag 标签)gitcheckout abc123456789# 3. 退出子模块目录,回到主仓库根目录cd../..第三步:在主仓库中提交“版本锁定”记录(最关键的一步)
把这个“子模块指向了 abc1234 版本”的信息,记录到主仓库的提交历史中。
# 1. 将子模块目录的变更添加到暂存区(注意:这里 add 的是子模块的文件夹路径,不要带尾部斜杠)gitaddpath/to/submodule# 2. 提交到主仓库,并写上清晰的日志gitcommit-m"添加第三方库 xxx 作为子模块,并锁定到指定版本 abc1234"第四步:推送到远程仓库
最后,把你的主仓库(包含
.gitmodules文件和子模块的 commit 指针)推送到远程:gitpush origin main
七、标签操作(tag):版本标记与发布
通过标签(Tag)标记重要版本(如发布版本v1.0),方便后续快速定位和回滚。
git tag <标签名>:在当前提交创建标签(如git tag v1.0)。git tag:查看所有本地标签。git tag -d <标签名>:删除本地标签。git push <远程名> <标签名>:将指定标签推送到远程仓库(如git push origin v1.0)。git push <远程名> --tags:将所有本地标签推送到远程仓库。git checkout <标签名>:切换到指定标签对应的版本(少用)(处于 “分离头指针” 状态,修改需创建新分支)。
[!WARNING]
git checkout <标签名>使用注意,如果当前暂存区有来自不同仓库的同名tag, 则该命令可能会不如你意。这样操作:
- 获取远程仓库 tag 对应的 commit ID
gitls-remote repoA tags/<tag_name>
- 基于 commit ID 切换到目标版本
gitcheckout a1b2c3d4e5f6abcdef1234567890abcd12345678
八、常见问题与补充说明
如何查看当前所在分支?
执行git status、git log或git branch --list,当前分支会用*标记或显示为绿色。
合并分支时必须切换到目标分支吗?
是的。例如要将Lyrix合并到master,必须先切换到master分支,再执行git merge Lyrix。
本地分支与远程分支如何同步?
- 执行
git fetch <远程名>拉取远程最新更新; - 执行
git merge <远程名>/<远程分支名>将远程更新合并到本地分支(如git merge origin/master)。
Fast Forward Merge 与 3-Way Merge 的区别?
- Fast Forward Merge:目标分支(如
master)没有新提交,直接将目标分支指针指向待合并分支(如Lyrix),无合并记录。 - 3-Way Merge:目标分支有新提交,需基于两者的共同祖先创建新的合并提交,保留合并记录。
日常开发分支管理思路
你的思路本质上是通过分支隔离 “有效成果” 和 “临时调试”,这是合理的,但可以进一步优化分支策略,让流程更清晰、协作更顺畅(即使是个人开发)。以下是具体建议:
一、你的方案的合理性与可优化点
合理性:
- 用两个分支隔离 “干净的有效补丁” 和 “杂乱的调试过程”,避免有效代码被临时调试代码污染,方便后续整理和合并。
可优化点:
- 若仅用两个分支,长期开发可能导致 “有效分支” 堆积过多补丁,难以追溯每个功能的来源;“调试分支” 过于杂乱,回头看时可能分不清哪些调试是针对哪个问题的。
二、更推荐的分支管理策略(适合个人开发)
核心思路:按 “功能 / 任务” 拆分短期分支,用一个 “主开发分支” 整合有效成果,用 “临时调试分支” 做尝试,形成 “短期尝试→验证有效→合并到主开发分支” 的闭环。
具体分支结构建议:
| 分支类型 | 命名示例 | 用途 | 生命周期 |
|---|---|---|---|
| 主开发分支 | dev或develop | 存放经过验证的、可整合的有效代码(类似你的 “有效 patch 分支”) | 长期存在,持续更新 |
| 功能 / 任务分支 | feature/xxx | 针对单个功能或任务(如 “修复 XXbug”“实现 XX 功能”)创建的短期分支 | 功能完成并合并后删除 |
| 调试 / 实验分支 | debug/xxx-issue | 记录针对某个问题的调试过程(带临时日志、调试代码) | 问题解决后可删除或归档 |
| 稳定版本分支 | release/v1.0(可选) | 从dev分支拉出,用于发布或测试的稳定版本 | 长期保留,仅修复 bug |
三、日常开发流程(按步骤说明)
从主分支创建功能分支
每次开始一个明确的任务(如 “开发 A 功能”“修复 B bug”),从dev分支创建一个功能分支:
gitcheckout devgitpull# 确保主分支最新gitcheckout-bfeature/implement-A# 功能分支这个分支只专注于该任务,提交记录尽量清晰(如 “完成 A 功能的核心逻辑”“修复 A 功能的边界条件”)。
用调试分支做 “脏实验”
若开发中需要做大量尝试性调试(如 “测试某个猜想”“临时加日志排查问题”),从当前功能分支创建调试分支:
gitcheckout feature/implement-Agitcheckout-bdebug/test-A-logic# 调试分支这里可以随意提交临时代码(甚至带printk、注释掉部分逻辑),不用担心污染功能分支。
验证有效后合并到功能分支
调试出有效结论后(如 “这个思路可行”“这段代码能修复问题”),将调试分支中的有效代码整理后合并到功能分支:
# 切换到功能分支gitcheckout feature/implement-A# 合并调试分支的有效代码(可选 rebase 让提交更整洁)gitmerge--squashdebug/test-A-logic# 把调试分支的提交压缩成一个gitcommit-m"完成A功能的XX逻辑(解决了XX问题)"# 若提交描述信息拼写错误、不准确时重新编辑描述。修改最近一次提交的描述(未推送时使用)gitcommit--amend# 如果修改历史提交信息,需要git rebase, 修改pick为reword(只改提交描述),如果已经推送到远端想想强推则git push --force-with# 调试分支完成使命,可删除gitbranch-ddebug/test-A-logic功能完成后合并到主开发分支
当功能分支的代码经过充分验证(如自测通过、无调试残留),合并到dev分支:
gitcheckout devgitmerge feature/implement-Agitpush origin dev# 功能分支可删除(或保留以备后续修改)gitbranch-dfeature/implement-A四、关键技巧:让分支管理更高效
提交记录 “原子化”
每个提交只做一件事(如 “修复 XX 函数的参数错误”“添加 XX 功能的配置项”),避免一个提交包含多个不相关修改。这样后续回滚或查找问题时更方便。
善用git stash临时保存工作区
若调试到一半需要切换分支(如突然要改另一个 bug),可用git stash保存当前未提交的修改,切换分支处理完后再git stash pop恢复,避免频繁创建临时分支。
定期 “清理” 无用分支
功能分支和调试分支完成后及时删除,避免本地分支列表过于臃肿(可用git branch -vv查看已合并的分支,放心删除)。
必要时 “归档” 调试过程
- 若某些调试过程有参考价值(如 “解决了一个罕见 bug 的思路”),可以不删除调试分支,而是用标签(
git tag debug/20241028-xxx-issue)标记,方便后续查阅。
五、为什么这样更有效?
- 聚焦性:功能分支只对应一个任务,提交记录清晰,方便追溯;调试分支隔离临时操作,不影响核心代码。
- 灵活性:即使调试分支搞乱了,也不会影响功能分支和主分支,大不了删掉调试分支重新来。
- 可扩展性:若后续需要多人协作,这种结构能无缝衔接(每人在自己的功能分支开发,通过
dev分支整合)。
简单说,核心就是 “按任务拆分,按有效性分层”:用功能分支保证代码的 “可追溯性”,用调试分支承担 “试错成本”,用主分支沉淀 “有效成果”。比单纯两个分支更有条理,长期开发会更省力。
六、如何将测试分支高效合并到功能分支
关键原则:减少 “调试提交” 的杂乱度
与其事后整理,不如在调试时养成习惯,减少后续工作量:
- 调试代码单独标记:在调试语句前统一标记(如
// DEBUG_LYRIX:),方便后续全局搜索删除; - 避免 “为调试而提交”:非必要不提交纯调试代码(可用
git stash临时保存),只在有有效进展时提交; - 小步提交有效代码:调试中一旦有明确的有效修改(如修复一个逻辑错误),立即提交并写清楚注释(如 “修复 XX 条件判断错误”),避免有效代码和调试代码混在一起。