提交早了、写错了、提错分支了
搜「撤销」或直接看「回退与撤销」分组,先分清「还没推送」(reset / --amend)和「已经推送」(revert)两种情况,再挑命令。「常见组合」里有「在 main 上提交了想挪到新分支」这类完整配方。
使用指南
Git 命令速查是一张可以直接用的表:149 条命令按「初始化与配置、日常提交、分支、合并与变基、回退与撤销、远程协作、stash、标签、子模块与工作树、查历史与排错、忽略与属性」十一个场景分组,每条给一句中文解释、一段能直接粘贴的示例,以及「注意 / 危险」两级风险标注——会丢未提交改动或改写已共享历史的命令一眼就能认出来。
更新于 2026-09-094 个来源约 10 分钟读完
Git 命令速查是一张可以直接用的表:149 条命令按「初始化与配置、日常提交、分支、合并与变基、回退与撤销、远程协作、stash、标签、子模块与工作树、查历史与排错、忽略与属性」十一个场景分组,每条给一句中文解释、一段能直接粘贴的示例,以及「注意 / 危险」两级风险标注——会丢未提交改动或改写已共享历史的命令一眼就能认出来。
它面向三种时刻:记得要做什么但忘了命令怎么拼(搜「撤销 commit」「删除远程分支」);知道命令却不确定它会不会把东西弄丢(看危险等级和备注);以及第一次接触 Git、想弄明白 add、commit、push 之间到底在搬什么(看页首的四个区域图和下方的操作示意图)。
页面底部还有一个纯内存的 Git 模拟终端:支持 30 多个常用子命令,右侧提交图随每一条命令实时变化,附 10 个从「第一次提交」到「解决合并冲突」的练习关卡。它不是真正的 Git,也不会访问你的电脑或网络,但语义按官方手册实现,练完可以直接迁移到真机。
在搜索框输入「撤销 commit」,得到的是「回退与撤销」分组里这几条(节选):
在模拟终端里完成第 4 关「撤销最近一次提交但保留改动」的过程:
git reset --soft HEAD~1 撤销最近一次提交,改动保留在暂存区 注意
git reset HEAD~1 撤销最近一次提交,改动回到工作区未暂存
git reset --hard <commit> 分支指针、暂存区、工作区全部回到 commit 危险
git revert <commit> 生成一个新提交来抵消改动,历史不变,可安全推送user@toolun-lab:~/project (main)$ git log --oneline
7f3a1c2 (HEAD -> main) wip: 乱七八糟的提交
2b9e0d4 init: add README
user@toolun-lab:~/project (main)$ git reset --soft HEAD~1
HEAD 现在位于 2b9e0d4 init: add README
--soft:只移动分支指针,暂存区和工作区都保留(被撤销的提交内容处于已暂存状态)
提示:被丢弃的提交 7f3a1c2 仍可通过 git reflog 找回
user@toolun-lab:~/project (main)$ git status -s
A app.js
A secret.env
✔ 通关:撤销最近一次提交,但保留改动。几乎所有 Git 命令都可以理解为「把内容从一个区域搬到另一个区域」。工作区是你能看见、能编辑的文件;暂存区(index)是一份「下一次提交长什么样」的清单;本地仓库(.git)保存所有提交;远程仓库是另一台机器上的同一套东西。
理解了这张图,很多「怎么撤销」的问题就有了统一答案:改错了还没 add,用 git restore <file> 从暂存区拿回来;add 了不想提交,用 git restore --staged;提交了想撤,用 git reset;已经推送出去,只能用 git revert 追加一个反向提交。
一个提交对象记录:整棵目录树的快照(不是差异)、父提交的 id、作者与时间、说明文字。这些内容经 SHA-1(新仓库可选 SHA-256)哈希得到提交 id,所以任何一处改动——哪怕只是改一个字的说明——都会得到一个全新的 id。这就是 --amend、rebase、cherry-pick 都「改写历史」的原因:它们生成的是新提交,旧提交只是不再被引用。
分支是 .git/refs/heads/<name> 里一个 40 位的文件,内容就是某个提交的 id。HEAD 通常指向某个分支名。因此建分支是瞬时的(写一个小文件),切分支是把 HEAD 改指向另一个名字并把工作区更新成那个提交的快照,删分支只是删掉一个指针——提交本身不会立刻消失。
merge 忠实记录发生过什么:两条线在合并提交处汇合,任何人都能看出这段功能是并行开发的。rebase 让历史看起来像一直是线性的,git log 更好读、git bisect 更好用,但代价是提交 id 全变。经验法则:自己的、还没推送的分支随便 rebase;别人可能基于它工作的分支只 merge。git pull --rebase 是这条规则的日常应用——把本地几个未推送的提交搬到远程最新之后,避免一堆「Merge branch 'main' of …」。
--soft 适合「提交拆分 / 合并」:指针回去,内容仍在暂存区。--mixed(默认)适合「重新挑选要提交什么」。--hard 是真正的丢弃:工作区里没提交过的改动没有任何办法找回,而被撤掉的那些提交依旧躺在对象库里,git reflog 能看到 HEAD 每一次移动的记录,git reset --hard HEAD@{1} 就能回到 reset 之前。默认情况下 reflog 保留 90 天(不可达条目 30 天),这是 Git 给你的后悔窗口。
「危险」标注给两类命令:会丢弃工作区未提交内容的(restore、reset --hard、clean、stash drop、checkout -- <file>),以及会改写别人可能已经拿到的历史的(push --force、branch -D 未合并分支)。「注意」给会改变状态但可挽回、或需要先确认影响范围的命令(commit --amend、rebase、merge --squash、push --delete)。只读命令(log、diff、status、show、blame)不标。
git switch / git restore 需要 2.23 及以上版本,旧写法 checkout 一并列出。hash-object、cat-file、update-ref 等)与平台特有操作(GitHub PR、GitLab MR 的网页操作)。reset 三模式、线性 rebase、stash、远程推拉),不支持交互式 rebase、bisect、子模块、hooks、钩子与配置文件的全部选项;遇到冲突的 rebase 会自动放弃而不是停下等你解决。.git、不提供图形化冲突解决器。搜「撤销」或直接看「回退与撤销」分组,先分清「还没推送」(reset / --amend)和「已经推送」(revert)两种情况,再挑命令。「常见组合」里有「在 main 上提交了想挪到新分支」这类完整配方。
git rebase -i HEAD~N 配合 squash / fixup / reword;示意图里的 rebase 卡片能看到历史怎样从分叉变成直线,以及旧提交为什么变灰。
「远程协作」分组从 remote add 到 push -u、--force-with-lease,练习终端第 8 关(推送)和第 9 关(撤销已推送提交)覆盖了最常见的两次「推不上去」。
git reflog、git fsck --lost-found、git stash list 三条在「回退与撤销」里相邻;配方「找回误删的分支」两步完成。
看提交有没有推送出去。没推送:reset 直接把指针拉回去,历史干净。已推送:revert 追加一个反向提交,不改写别人已经拿到的历史;否则下次别人 pull 就会遇到分叉。
因为本地有未推送的提交、远程也有新提交,默认 pull 用 merge 把两边合起来。想避免就用 git pull --rebase,或一次性设置 git config --global pull.rebase true。
--force 无条件覆盖远程分支。--force-with-lease 会先检查远程分支还是不是你上次 fetch 时看到的那个提交,别人推过新东西就拒绝——把「覆盖掉同事的提交」这种事故挡在门外。改写历史后的推送一律用后者。
真实 Git 的 id 由内容、作者、时间戳共同哈希得到;模拟器用逻辑时钟代替真实时间并用确定性哈希,同样的操作序列得到同样的 id,方便对照参考答案。它的 id 与真实 Git 算出的不一致,这是刻意的。
命令本身跨平台一致。差别在 shell:PowerShell 里 && 需要 7.x 版本,路径用引号包住更稳妥;换行符建议按「初始化与配置」里的 core.autocrlf 条目设置。
本页所有数据(命令表、示意图、模拟终端)都是随页面一起下载的静态内容,在你的浏览器里本地运行;搜索、复制、收藏、练习都不会向服务器发送任何请求。收藏记录保存在本机 localStorage,站点无法读取。页面上的外链指向 Git 官方文档,点击后由对方站点处理。
更新于 2026-09-09
149 条 Git 命令按场景分组,每条含解释、示例与危险等级,可搜索、一键复制、收藏;附操作示意图与可练习的模拟终端
git init git init -b main # 指定初始分支名
git clone https://github.com/git/git.git git clone git@github.com:user/repo.git my-dir
git clone --depth 1 --branch v2.0 https://github.com/user/repo.git
git clone -b develop https://github.com/user/repo.git
git config --global user.name "Zhang San" git config --global user.email "zs@example.com"
git config user.email "me@company.com"
git config --list --show-origin git config --get user.name
git config --global core.autocrlf input
git config --global alias.lg "log --oneline --graph --all" git lg
Windows 通常已带 manager;macOS 用 osxkeychain 更安全。
git help rebase git reset -h # 简短用法
git status git status -s # 短格式:M 修改 A 新增 D 删除 ?? 未跟踪
git add src/app.js git add src/ # 整个目录 git add "*.md" # 通配
git add -A git add -u # 只暂存已跟踪文件的修改与删除,不含新文件
git add -p src/app.js # y 暂存这块 n 跳过 s 拆得更小 q 退出
git commit -m "fix: 修正登录超时判断"
git commit -am "docs: update readme"
容易把不想提交的改动一起带进去,提交前先 git status。
git add forgotten.js git commit --amend --no-edit # 补文件但不改说明 git commit --amend -m "新说明"
产生新的提交 id;已经推送过的提交不要 amend,否则需要强推。
git diff git diff src/app.js # 只看一个文件
git diff --staged
git diff main feature git diff HEAD~3 HEAD -- src/ git diff main...feature # feature 相对分叉点的改动
git diff --stat HEAD~1
git diff --word-diff=color README.md
git log git log -5 # 最近 5 条 git log --oneline # 一行一条
git log --oneline --graph --all --decorate
git show HEAD git show a1b2c3d --stat git show HEAD:src/app.js # 看某版本的文件内容
git rm old.txt git rm -r legacy/
git rm --cached .env echo ".env" >> .gitignore
git mv utils.js helpers.js
git branch git branch -a # 含远程分支 git branch -vv # 带最新提交与跟踪关系
git branch feature/login git branch hotfix a1b2c3d # 从指定提交创建
git switch main git switch - # 回到上一个分支
git switch -c feature/search git switch -c hotfix origin/main # 基于远程分支
git checkout -b release/1.2
git branch -m master main git push -u origin main && git push origin --delete master
git branch -d feature/login
git branch -D experiment
分支上的独有提交会成为孤儿,短期内可用 reflog 找回。
git branch --merged main git branch --no-merged # 反过来
git branch -u origin/feature/login
git branch -a --contains a1b2c3d
git checkout v1.0 git switch -c fix-from-v1 # 想保留改动就在此建分支
此状态下的提交不属于任何分支,切走后容易丢。
git branch --sort=-committerdate | head
git fetch -p git remote prune origin
git switch main git merge feature/login
git merge --no-ff feature/login
git merge --squash feature/login git commit -m "feat: login"
git merge --abort
git add src/app.js git commit # 默认说明 Merge branch ...
git checkout --theirs package-lock.json git add package-lock.json
rebase 时 ours / theirs 含义反过来:ours 是被变基到的分支。
git switch feature git rebase main
所有被搬的提交 id 都会变;已推送并有人基于它工作的分支不要 rebase。
git rebase -i HEAD~3 # pick 保留 reword 改说明 squash 并入上一条 fixup 并入且丢说明 drop 删除
git add . git rebase --continue
git rebase --abort
git rebase --onto main server client
适合「分支开错了地方」的情形。
git commit --fixup a1b2c3d git rebase -i --autosquash a1b2c3d~1
git cherry-pick a1b2c3d git cherry-pick a1b2c3d..e5f6g7h # 一段范围(不含 a1b2c3d)
git cherry-pick -n a1b2c3d
git config --global merge.tool vscode git config --global mergetool.vscode.cmd "code --wait $MERGED" git mergetool
git restore src/app.js git restore . # 整个目录
未暂存的改动会直接丢失,无法用 Git 找回。
git restore --staged src/app.js git reset HEAD src/app.js # 旧写法
git restore --source HEAD~2 config.yml git restore --source v1.0 -- src/
git reset --soft HEAD~1 git commit -m "更合适的说明"
git reset HEAD~1
git reset --hard HEAD~1 # 扔掉最后一次提交 git reset --hard origin/main # 与远程完全一致
工作区改动不可恢复;已提交的可用 git reflog 找回。
git reset package-lock.json
git revert HEAD git revert a1b2c3d --no-edit
git revert --no-commit HEAD~3..HEAD git commit -m "revert last 3 commits"
git revert -m 1 f00ba12
撤销合并后再想合入同一分支需先 revert 这个 revert。
git reflog
git reset --hard HEAD@{2} # 回到 2 步之前的状态
git branch rescue a1b2c3d # 给孤儿提交建分支git clean -n # 预览会删什么 git clean -fd # 执行 git clean -fdx # 连 .gitignore 里忽略的也删(如 node_modules)
git checkout -- src/app.js
git fsck --lost-found git show <dangling-commit-id>
git fsck --unreachable | grep commit | cut -d" " -f3 | xargs git log --merges --no-walk
git remote -v
git remote add origin git@github.com:user/repo.git
git remote set-url origin git@github.com:user/repo.git
git remote rename origin upstream
git remote remove upstream
git fetch git fetch origin main git fetch --all --prune
git pull git pull origin main
git pull --rebase origin main
git push git push origin main
git push -u origin feature/login
git push origin --delete feature/old git push origin --delete v0.1 # 也可删远程标签
git push --force-with-lease origin feature/login
优先用它而不是 --force。
git push -f origin feature/login
会抹掉别人推上去的提交;共享分支禁止。
git push --all origin
git ls-remote --heads origin git ls-remote --tags https://github.com/git/git.git
git remote add upstream https://github.com/original/repo.git git fetch upstream git switch main && git merge upstream/main
git pull --ff-only
git stash git stash push -m "登录页半成品"
git stash -u git stash -a # 连忽略文件也收
git stash list
git stash pop
git stash pop stash@{2}git stash apply stash@{1}git stash show -p stash@{0}git stash drop stash@{0}
git stash clear # 全部删除git stash branch fix-from-stash stash@{0}git stash push -m "只收起样式" -- src/styles/
git tag git tag -l "v1.*" # 按模式过滤
git tag -a v1.2.0 -m "Release 1.2.0"
git tag build-2026-09-09
git tag -a v1.1.1 a1b2c3d -m "补打"
git show v1.2.0
git tag -d v1.2.0
git push origin --delete v1.2.0
git describe --tags --always
git submodule add https://github.com/user/lib.git vendor/lib
git submodule update --init --recursive
git clone --recurse-submodules https://github.com/user/repo.git
git submodule update --remote vendor/lib git add vendor/lib && git commit -m "bump lib"
git submodule status
git submodule deinit -f vendor/lib git rm -f vendor/lib rm -rf .git/modules/vendor/lib
git worktree add ../repo-hotfix hotfix/1.2.1 git worktree add -b review ../repo-review origin/pr-42
git worktree list
git worktree remove ../repo-hotfix git worktree prune # 清理已手工删除目录的登记
git clone --filter=blob:none --sparse <url> cd repo && git sparse-checkout set apps/web packages/ui
git log -p -- src/app.js git log --follow -- src/app.js # 跨重命名跟踪
git log --grep="登录" -i --oneline
git log -S"validateToken" --oneline git log -G"regex" --oneline # 正则版
git log main..feature --oneline git log origin/main..HEAD # 还没推送的提交
git log --pretty=format:"%h %ad %an %s" --date=short -10
git log --since="2026-09-01" --until="2026-09-09" --oneline
git shortlog -sn --all --no-merges
git blame -L 40,60 src/app.js git blame -w -C src/app.js # 忽略空白、跟踪移动
git bisect start git bisect bad # 当前是坏的 git bisect good v1.1.0 # 这个版本是好的 # 反复测试并执行 git bisect good/bad git bisect reset
git bisect run npm test
git grep -n "TODO" -- "*.ts" git grep "foo" v1.0 # 在某个版本里搜
git show HEAD~5:src/config.js > old-config.js
git diff --name-only HEAD~3 HEAD git diff --name-status main # 带 M/A/D 状态
git count-objects -vH
git gc --aggressive --prune=now
prune 会真正删除不可达对象,reflog 里的后悔药会一起消失。
git rev-parse --short HEAD git rev-parse --abbrev-ref HEAD git rev-parse --show-toplevel # 仓库根目录
git archive -o release.zip v1.2.0 git archive HEAD src/ | tar -x -C /tmp/out
# .gitignore node_modules/ dist/ *.log .env* !.env.example
git check-ignore -v dist/app.js
git rm -r --cached . git add . git commit -m "chore: apply .gitignore"
echo ".DS_Store" >> ~/.gitignore_global git config --global core.excludesFile ~/.gitignore_global
git update-index --skip-worktree config/local.php git update-index --no-skip-worktree config/local.php # 取消
* text=auto *.sh text eol=lf *.png binary *.psd filter=lfs diff=lfs merge=lfs -text
git lfs install git lfs track "*.psd" git add .gitattributes
把几条命令串成「我要做一件事」的配方;展开后可整段复制。
提交早了 / 说明写错 / 想拆成两个提交(且尚未推送)
git reset --soft HEAD~1提交撤掉,改动仍在暂存区git status确认文件都在「要提交的变更」里git commit -m "新的说明"别人可能已经拉取了,不能改写历史
git revert <commit>生成反向提交git push本地实验搞乱了,想彻底回到某个提交
git log --oneline找到目标提交 idgit stash -u(可选)先收起还想保留的改动git reset --hard <commit>忘了开分支就提交了 N 次
git branch feature/x在当前位置建分支(把这些提交「存」起来)git reset --hard origin/mainmain 回到远程状态git switch feature/x开 PR 之前整理历史(分支未被他人使用)
git rebase -i HEAD~4把后面几行的 pick 改成 squash 或 fixupgit push --force-with-lease分支已推送过才需要main 已经有新提交,功能分支要在其之上继续
git fetch origingit switch feature/xgit rebase origin/main或 git merge origin/main(保留合并提交)统一默认分支名
git branch -m master maingit push -u origin maingit push origin --delete master先在托管平台把默认分支切到 maingit branch -D 之后发现还有用
git reflog找到该分支最后一次提交的 id(如 "commit: xxx")git branch <name> <commit-id>.env 或几百 MB 的文件进了历史
git rm --cached .env && echo ".env" >> .gitignore若只在最近一次提交:接着 git commit --amendgit filter-repo --path .env --invert-paths已在历史深处:用 git-filter-repo 重写全部历史(需单独安装)git push --force-with-lease --all所有协作者都要重新克隆;泄露的密钥无论如何都要作废重发main 上修好的 bug 也要进 release/1.2
git switch release/1.2git cherry-pick <commit>git pushgit merge / pull 报 CONFLICT
git status看哪些文件「双方修改」code <file>在编辑器里处理 <<<<<<< ======= >>>>>>> 标记,保留正确内容git add <file>每修好一个就 add 一个git commitrebase 时则是 git rebase --continue先在本地写了代码,后来才在 GitHub 建了仓库
git init -b main && git add -A && git commit -m "init"git remote add origin git@github.com:user/repo.gitgit push -u origin main圆点是提交、箭头指向父提交,绿色标签是分支、紫色是 HEAD 所在分支;灰色圆点表示不再被任何分支引用(可用 reflog 找回),实心是本次操作新产生的提交。
git add . && git commit -m "…"每个提交记住自己的父提交;分支只是指向某个提交的可移动指针,提交后 main 与 HEAD 一起前进。
git switch -c feature新分支和 main 指向同一个提交,所以建分支是瞬时的;之后在 feature 上提交,只有 feature 前进。
git switch main && git merge feature不会产生新的提交,历史是一条直线。想保留「这里曾有一个分支」的痕迹就用 --no-ff。
git merge featureGit 找到共同祖先 b2,把两边相对 b2 的改动合到一起,生成有两个父提交的 m6。同一处改动不同才会冲突。
git switch feature && git rebase main历史变成一条直线,但 c3、d4 被新提交 c3′、d4′ 替代(id 变了)。已推送的分支不要 rebase,否则别人的历史会与你冲突。
git reset --hard HEAD~1--soft 只动指针;--mixed 再清暂存区;--hard 连工作区一起回退。孤儿提交 c3 在 reflog 里还能找回(默认保留 30 天以上)。
git revert HEADc3 仍在历史里,新提交 r4 的内容等于把 c3 的改动反过来。已推送的提交要撤销就用它,不用 reset。
git cherry-pick x7把 x7 相对其父提交的改动重放到 main 上,得到内容相同、id 不同的 x7′。适合把某个修复单独带到发布分支。
一个纯内存的 Git 模拟器(不是真正的 Git,也不联网):支持 init / add / commit / branch / switch / merge / rebase / reset / revert / stash / tag / remote / push / pull 等常用子命令,右侧提交图实时更新。10 关从「第一次提交」到「解决合并冲突」。Linux 命令的练习在Linux 命令速查。