NOTE · Engineering Systems
Git 工作流与撤销操作备忘
从提交推送到 restore、revert 与 reset,整理常用 Git 操作及其风险边界。
基本配置
配置全局用户名和邮箱
进入终端,先设置全局使用的用户名与邮箱(此信息会出现在你的提交记录里):
git config --global user.name "YourName"
git config --global user.email "YourEmail@example.com"
把YourName和YourEmail@example.com替换成你自己的信息。
查看配置
- 使用
git config --list查看当前所有配置。 - 如果要针对某个项目单独配置,可以在仓库目录下去掉
--global选项进行配置。
移除配置
- 移除全局的
user.name:git config --global --unset user.name - 移除全局的
user.email:git config --global --unset user.email
如果想完全删除对应条目,也可以直接编辑全局配置文件~/.gitconfig,手动删掉相应行。
提交 (Commit) 和推送 (Push) 到云端
假设本地已有一个 Git 仓库,或者已在项目中通过
git init进行了初始化。
整理工作区并暂存 (git add)
若修改或新增文件,需要使用 git add 将它们加入暂存区 (Staging Area):
git add <文件名或目录>
一般可一次性将所有更新暂存:
git add .
其中.表示当前目录及所有子目录。
本地提交 (git commit)
将暂存区的改动打包成一次提交 (commit),并加上说明:
git commit -m "提交说明"
这里的-m是 message 的缩写,表示"将后续字符串作为提交信息",如果不使用-m,通常 git 会进入文本编辑器界面,让你编写更详细的提交消息。
设置远程仓库 (git remote add)
如拿到仓库的 URL (示例:https://github.com/YourName/YourRepo.git),然后在本地执行:
git remote add origin https://github.com/YourName/YourRepo.git
其中,「origin」是远程仓库的默认名称。
推送到云端 (git push)
将本地分支(常用的名称是main或master)推送到远程:
git push origin main
如果是首次推送且存在分支名称不一致的情况,可以加上-u参数进行追踪:
git push -u origin main
遇到 SSL 验证问题怎么办
如果在推送或克隆时出现类似SSL certificate problem: unable to get local issuer certificate的错误,大多是因为:
- 使用了自签名证书或企业内部代理,导致证书不被系统默认信任。
- 网络中存在需要安装"代理根证书"的场景(常见于公司内网)。
常见解决方案:
在系统或 Git 中添加可信任证书
- 在 Windows 上,可将企业的根证书安装到「受信任的根证书颁发机构」。
- 在 Linux / macOS 上可把证书放到系统 CA 目录并执行(不同发行版命令略有不同):
sudo update-ca-certificates - 然后在 Git 配置中指定
http.sslCAinfo来指向你的根证书文件。
确认 Git 实际使用的证书与代理配置
git config --show-origin --get-regexp '^http\.'如果组织提供了 CA 文件,可只对确实需要的仓库或域名配置,例如:
git config http.sslCAInfo /path/to/organization-ca.pem
不要通过关闭 http.sslVerify 绕过证书校验;这会让 HTTPS 连接失去服务器身份验证,应修复系统 CA、组织代理 CA、系统时间或代理配置本身。
在 Git 中"回退"到某个提交有多种方法,取决于你想要的结果和是否已经将该提交推送到远程仓库。常见的方式包括:
git 回退
git restore
git restore <文件>用于把工作区的某个(或多个) 文件"恢复"到暂存区或指定提交 (commit) 所处的状态,也就是覆盖工作区里的改动。用法:git restore path/to/file这会用当前暂存区(或提交)的内容覆盖工作区文件
path/to/file,从而丢弃本地对该文件未暂存的修改。git restore --staged <文件>用于把"已暂存区 (Staging Area)“里的内容撤回到"未暂存状态 (Working Tree)“中,让它不再处于待提交的状态,但不会影响工作区已有的实际改动。 也就是"把某文件从暂存区里移除”,与常用的git reset HEAD <文件>作用类似。用法:git restore --staged .这会把所有已暂存的文件一次性撤回到未暂存状态,但文件在工作区的修改依然保留,只是从提交列表里"卸载"了而已。
因此,当执行
git restore --staged .,实际上就是告诉 Git:“把暂存区里所有被git add过的文件,移出暂存区”,从而达到撤销git add .的效果。注意:
git restore --staged <文件>不会丢弃工作区的改动,只是让它们不处于"即将被提交"的状态。若想还原工作区的实际改动,需要使用不带--staged的git restore <文件>来覆盖工作区。
git revert (推荐在已经推送到远程后使用)
- 特点: 创建一个新的"撤销"提交,保留完整提交历史,不会破坏已有记录。
- 使用场景:
- 你已经把错误的提交 Push 到远程(尤其是多人协作时)。
- 你只想撤销特定提交的修改,又不想改动其它提交历史。
- 命令示例:
git revert <目标提交ID> # 提交信息编辑后保存,Git 会自动生成一个新的提交,撤销指定提交的更改
git reset (改变本地提交历史,属于"重写历史”)
git reset会直接移动当前分支 (HEAD) 的指针到某个提交,如果你已经将提交推送到远程,此方式需要格外小心,因为它会导致本地和远程分支历史不一致。常见模式有三种:
--soft模式
- 特点: 只移动 HEAD 指针,保留回退点之后所有文件的修改在暂存区(staged)。
- 常见语法:
git reset --soft <目标提交ID> - 使用场景: 适合想把后续提交合并成新的提交(例如重新编辑 commit message 或者合并多次提交)。
--mixed(默认)模式
- 特点: 只移动 HEAD 指针,同时把回退点之后的修改从暂存区移出到工作区 (unstaged),但不会删除代码文件改动。
- 常见语法:等价于:
git reset <目标提交ID>git reset --mixed <目标提交ID> - 使用场景: 你想撤销提交,但要保留改动在工作区,以便重新选择性地提交。
--hard模式
- 特点: 会强制丢弃被回退提交之后的所有代码改动,工作区文件也会被还原到指定提交的状态。
- 常见语法:
git reset --hard <目标提交ID> - 使用场景: 只在你确定不需要保留后续提交改动时使用,否则会导致丢失本地历史。
git checkout (仅临时切换查看)
- 特点: 让工作区临时进入该提交的状态并进入 detached HEAD。
HEAD此时直接指向提交,不再指向某个本地分支;已有分支引用不会移动。 - 使用场景: 只想查看某次提交时的代码,或进行比较等,而不想真正回退分支。
如果你想"基于某个提交创建一个新的分支",可以这样做:
# 临时查看该提交(进入 detached HEAD)
git switch --detach <commitID>
# 若决定保留在 detached HEAD 中产生的提交,创建分支名
git switch -c <新分支名>
# 也可直接基于指定提交创建并切换到新分支
git switch -c <新分支名> <commitID>
若只查看未提交任何内容,可用 git switch - 返回上一个分支。detached HEAD 中的新提交若没有建立分支或标签,之后可能难以找回。
选择建议
- 如果你已经将问题提交 Push 到远程,并且不想破坏别人基于此提交的历史,建议使用
git revert来创建一个新的回滚提交。 - 如果你只是本地实验,还没 Push 或能确保远程协作不会受到影响,使用
git reset结合对应模式可以方便地整理提交历史。
git 分支操作
新建分支
# 从当前分支创建并切到新分支 feature
git checkout -b feature
# 或在较新 Git 版本里可使用 switch
git switch -c feature
查看分支列表
可使用以下命令查看当前仓库中的本地分支:
git branch
显示带*的分支就是当前所在分支
如想查看本地和远程的所有分支,可使用:
git branch -a
切换分支
如果开发结束,想切回其他分支,可使用:
git checkout main
或 (Git 新版本)
git switch main
合并分支
假如在分支feature上完成了某个功能,并准备将它合并回main:
- 先切回要合并到的目标分支:
git checkout main - 合并来源分支(也就是
feature):git merge feature - 如果有冲突,需要手动编辑冲突文件,解决后再执行:
git add . git commit
合并完成后,main就包含了feature分支的最新更改。
删除分支
在不再需要某分支时,可以安全地删除它以保持分支列表的整洁:
# 删除本地分支(若未合并会报错)
git branch -d feature
# 若确定要强制删除(未合并或不再需要的内容)
git branch -D feature
# 删除远程分支
git push origin --delete feature
git 合并
git merge
- 作用:将另一个分支的改动合并到当前分支。若可以快进则不创建合并提交;分支已经分叉时通常会创建合并提交。
- 命令示例:
# 先切到目标分支(面向被合并进来的位置) git switch main # 合并来自 feature 分支的改动 git merge feature - 结果:若两条历史已经分叉,
main上会增加一个具有两个父提交的合并提交;若满足快进条件,Git 只移动main指针。
git rebase
- 作用:把当前分支独有的提交在指定上游分支的最新提交之后重新播放,从而得到线性历史;这些被重放的提交会获得新的提交 ID。
- 命令示例:
# 让 feature 基于最新 main 重新播放 git switch feature git rebase main - 结果:被重写的是
feature独有的提交,main不会被改动。rebase 完成后,如需把功能分支并回主分支,可执行:git switch main git merge --ff-only feature
rebase 前后的关系可简化为:
之前:C1---C2---C3 main
\
F1---F2 feature
之后:C1---C2---C3 (main)---F1'---F2' (feature)
不要在未协调的情况下 rebase 已被他人基于其开发的共享分支,因为旧的 F1/F2 会被新的 F1'/F2' 替代。
快进合并 (Fast-forward)
触发条件
- 在新建分支后,没有任何其他提交进入主分支,也就是说主分支和分支的历史是在同一条直线上。
- 从主分支上分出去的分支一直处于最新状态,主分支没有比分出去时"前进"(没有新提交),则本地再想合并回主分支时,就不需要产生单独的合并提交。
现象
- 合并后,主分支的指针会直接跳到分支的最新提交上,不会产生新的合并提交(merge commit)。
git log中不会出现Merge branch XXX这样的额外记录。
示例
当main分支指针在C2时创建了feature3,并且main分支没有再产生任何新的提交,而feature3只前进到了提交F1:
main C1 -> C2
\
feature3 F1
合并时只要main不额外有提交,就可以直接"快进"(fast-forward)到F1:
C1 -> C2 -> F1 (HEAD -> main)
无需产生合并提交
普通合并 (Non-fast-forward / Merge Commit)
触发条件
- 主分支和分支各自都有独立的前进。也就是当你从主分支上分出去之后,主分支本身也有了一些新提交,导致分支的历史和主分支的历史是"并行前进"的形态,不能简单用"指针前进"把分支合并回来。
现象
- Git 会产生一个新的合并提交 (merge commit),它有两个父提交:一个父提交指向主分支的最新提交,另一个父提交指向特性分支的最新提交。
git log中会出现Merge branch 'feature3'这样的一条记录。
示例
当从C2处创建了feature3,同时main又往前进到了C3、C4,而feature3在单独的分支也前进到F1、F2:
C1 -> C2 -> C3 -> C4 (main)
\
F1 -> F2 (feature3)
合并时,Git 就会产生一个新的合并提交M,将C4与F2连接起来:
C1---C2---C3---C4---M (main)
\ /
F1----------F2 (feature3)
这样M会有两个父提交:C4(来自主分支)和F2(来自特性分支)。
简单来说,如果分支完全在"直线上"前进,即可发生 Fast-forward;一旦分支和主分支彼此都向前迈进过,Git 就要通过创建合并提交的方式"汇合"两条不同的开发轨迹。
git 子模块
git submodule update –init –recursive
用于同时克隆、初始化并同步主仓库所依赖的所有子模块(包括子模块中的子模块,递归处理)。如果一个 git 仓库包含了子模块,那么仅仅克隆主仓库并不一定会自动克隆这些子模块。执行这条命令后,将确保:
- 未被初始化的子模块会被克隆到对应的路径,并切换到正确的提交。
- 已存在的子模块会被更新到主仓库所记录的提交版本状态。
- 如果子模块里还有子模块(嵌套子模块),在
--recursive的作用下,也会被递归地初始化并更新。
换言之,这条命令能让你在一次操作中获取并更新所有层级的子模块到主仓库所期望的版本。
git status 显示中文
若希望 Git 在路径输出中直接显示可读的非 ASCII 字符,可关闭路径转义;提交与日志编码通常保持 UTF-8:
git config --global core.quotepath false
git config --global gui.encoding utf-8
git config --global i18n.commit.encoding utf-8
git config --global i18n.logoutputencoding utf-8
# bash 环境下(临时生效):
export LESSCHARSET=utf-8
# cmd环境下(需要管理员权限)永久生效:
setx "LESSCHARSET" "utf-8" /m
core.quotepath=false 只改变 Git 如何显示非 ASCII 路径,不会重命名文件。团队仍应统一终端、编辑器和提交信息使用的编码。参考:Git status 中文路径显示示例;具体行为以当前 Git 文档为准。
旧命令与新命令对照
checkout 同时承担切分支和恢复文件等多种职责。较新 Git 提供更明确的命令:
| 目的 | 明确写法 | 兼容旧写法 |
|---|---|---|
| 切换已有分支 | git switch BRANCH | git checkout BRANCH |
| 创建并切换分支 | git switch -c BRANCH | git checkout -b BRANCH |
| 临时查看提交 | git switch --detach COMMIT | git checkout COMMIT |
| 丢弃工作区文件修改 | git restore FILE | git checkout -- FILE |
旧写法没有失效,但在教程和脚本中使用职责更单一的命令,能减少把分支名、提交名和文件路径混淆的风险。
选择 restore、revert 或 reset
| 目标 | 推荐工具 | 历史影响 |
|---|---|---|
| 丢弃尚未暂存的文件修改 | git restore FILE | 不改提交历史,但工作区内容会丢失 |
| 取消暂存、保留工作区修改 | git restore --staged FILE | 不改提交历史 |
| 撤销已经共享的提交 | git revert COMMIT | 新增一个反向提交 |
| 本地分支指针回退 | git reset | 可能重写历史,并依模式改变索引/工作区 |
| 只查看旧提交 | git switch --detach COMMIT | 不移动已有分支 |
任何会丢弃未提交内容的操作前,先保存:
git status --short
git diff
git diff --staged
需要临时收起修改时,可使用 git stash push -u -m 'DESCRIPTION',但 stash 也不是备份;重要工作应形成可识别提交或另有可靠副本。
TLS 错误的排查顺序
git config --show-origin --get-regexp '^http\.'
git config --show-origin --get-regexp '^remote\.'
依次核对系统时间、远端 URL、系统 CA、组织代理 CA、HTTP_PROXY/HTTPS_PROXY 和 Git 实际读取的配置。网上常见的 git config --global http.sslVerify false 会对所有仓库关闭服务器证书校验,因此这里只把它列为不应使用的做法,不作为解决方案。