提交早了、寫錯了、提錯分支了
搜「撤銷」或直接看「回退與撤銷」分組,先分清「還沒推送」(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 命令速查。