English中文
avatar
plankbevelen
前端开发工程师
中国 - 成都
文章
11
分类
5
标签
17
目录
  • 多人开发同一分支
  • 只想看看远程有没有新东西
  • 不要长期在共享分支上开发
  • 合分支时冲突
  • 看懂冲突文件标记
  • 只要别人分支上的某一次提交
  • 撤掉已经推到共享分支的提交
  • 有未提交改动却要临时切分支
  • 改上一次提交的说明或漏加的文件
  • 强推被拒,或者不该强推
  • reset --hard 之后提交没了
  • 功能分支同步最新代码:rebase 换底座
  • HEAD、切分支、reset 的区别
  • checkout 一个 hash 之后的 detached HEAD
  • 工作区、暂存区、本地仓库
  • 一句话分清 abort、reset、revert
  • 快进合并(fast-forward)
  • 文件已经提交过,再加 .gitignore 没用
  • 冲突时的三方:base、ours、theirs

git操作注意事项

前端工程实践2026-09-07 20:33git

多人开发同一分支

远程有别人的新提交,本地也有自己的提交,这时 git pull 和 git pull --rebase 结果不一样。

复制代码
远程:a --- b --- c
本地:a --- b --- d

git pull 走 merge,会多生成一个合并节点 M,M 有两个父(c 和 d),c、d 都保留,历史是分叉再合流:

复制代码
a --- b --- c
       \      \
        d ---- M

git pull --rebase 是把你的 d 摘下来,接到 c 后面重放成 d',历史是一条直线:

复制代码
a --- b --- c --- d'

注意 rebase 重放的只是你本地多出来的提交,远程已有的 c 不会被改写。两个人改到同一处时,rebase 一样会冲突,解决完 git add,再 git rebase --continue;不想继续就 git rebase --abort。

共享分支上 rebase 完不要随手强推,原因后面说。

只想看看远程有没有新东西

本地其实有两套指针:qa 是你自己的分支,origin/qa 是「上次同步时远程在哪」的备忘录。

git fetch 只更新 origin/qa 这类备忘录,不动你当前分支,也不动工作区:

bash 复制代码
git fetch
git log qa..origin/qa   // 看看远程多了什么

git pull = fetch + 把远程合进当前分支,会改你的代码。想先看一眼,用 fetch。

不要长期在共享分支上开发

直接在 qa 上改、提交、推送,短期省事,代价是:半成品会污染别人拉到的代码;多人挤在一个分支尖上,冲突更密;几个需求的提交混在一起,想撤其中一个很难;也没有清晰的评审边界。

推荐流程:

bash 复制代码
git checkout qa && git pull
git checkout -b feature/xxx
// 开发、提交
git checkout qa && git pull
git merge feature/xxx
git push

一个需求一个分支,依赖升级、顺手改的无关代码不要塞进同一个 feature。

合分支时冲突

冲突出现后,仓库处在「merge 进行中」的状态。这时不要再去 git pull --rebase:merge 和 rebase 都是需要占用仓库状态的操作,Git 一次只允许进行一个,多半直接报错,硬搞状态更乱。

只有两条路:

bash 复制代码
// 继续合
// 改完冲突文件
git add <文件>
git commit

// 放弃这次合
git merge --abort

放弃优先用 merge --abort,语义清楚。git reset --hard HEAD 多数时候也能回到 merge 前,但 --hard 会连未提交的改动一起丢,容易误伤。

看懂冲突文件标记

复制代码
<<<<<<< HEAD
当前分支的版本
=======
合进来那边的版本
>>>>>>> feature

HEAD 那侧是你现在所在的分支,不是「以前的版本」;下面那侧是被合入或被挑入的内容。

改完文件(把标记删干净)之后必须收尾,不能直接 push:

bash 复制代码
git add <文件>
git commit                  // merge
git cherry-pick --continue  // cherry-pick
git rebase --continue       // rebase

只要别人分支上的某一次提交

不想合整条分支,只要其中一个提交,用 cherry-pick。它做的是:算出那次提交相对它父提交的差异,打到你当前分支上,再生成一个新提交。

复制代码
qa:       A --- B --- C
feature:  A --- B --- D --- E --- F

git cherry-pick E
qa:       A --- B --- C --- E'

原来的 E 还在 feature 上,F 不会跟着过来。冲突的原因是这块补丁跟当前代码对不上:

bash 复制代码
git add <文件>
git cherry-pick --continue
// 或
git cherry-pick --abort

如果被挑的提交依赖同分支更早的改动,只挑一个可能编译不过,要么一起挑,要么确认它是独立的。

撤掉已经推到共享分支的提交

已经 push、别人也拉过了,就用 revert,不要 reset 之后强推。

revert 不是把旧提交从历史里删掉,而是再推一个新提交,把那部分改动反做回去。旧节点仍在历史里,只是分支最新的内容不再包含它。这样别人正常 pull 就能同步,不会对不上。

merge 提交要指定主线,光写 hash 一般会被拒绝:

bash 复制代码
git revert <普通提交hash>
git revert -m 1 <merge提交hash>

-m 1 表示以第一个父(执行 merge 时你所在的那侧)为正线,撤掉这次合并从另一侧引进来的改动,不是把两边的提交内容全清掉。另外,一条分支被 merge 后又 revert,之后再合同一条分支容易合不进来,需要先 revert 那个 revert。

有未提交改动却要临时切分支

bash 复制代码
git stash          // 未跟踪的新文件用 git stash -u
git checkout 其他分支
// 处理完
git checkout 原分支
git stash pop

pop 会取回并删掉这条记录,apply 会保留,怕冲突就先 git stash list 看一眼再决定。

改上一次提交的说明或漏加的文件

bash 复制代码
git add 漏掉的文件
git commit --amend

amend 不是新增一次提交,而是把最后一次重新打包成一个新提交,hash 会变:

复制代码
amend 前:A --- B --- C
amend 后:A --- B --- C'

没 push 过随便用。已经 push 过再 amend,本地就和远程分叉了,普通 push 会被拒,只能强推 —— 所以这招留给自己一个人用的分支。

强推被拒,或者不该强推

普通 push 只允许快进:远程分支尖是你历史里的祖先,你在后面接着推。amend、rebase 这类改写历史的操作会让本地和远程分叉,push 就会被拒。

bash 复制代码
git push --force              // 直接覆盖远程
git push --force-with-lease   // 远程若被别人推过,就拒绝这次覆盖

--force-with-lease 的「拒绝」指推送失败、远程保持原样,避免把同事刚推的提交盖掉。被拒之后先 git fetch 看看远程到哪了,再决定怎么处理。

共享分支不要强推。别人已经基于旧历史干活,你一覆盖,他们的提交在分支上就消失了,全员历史对不上。共享分支要撤代码,用 revert。

reset --hard 之后提交没了

分两种情况:

  • 已经 commit、只是没 push:能救。--hard 只是把分支尖挪走了,提交对象通常还在。
  • 从没 commit 过的工作区改动:基本救不回来。
bash 复制代码
git reflog                    // 找到 reset 之前的那次提交
git branch recover <hash>     // 先拉一条分支保住它

reflog 记录的是本机 HEAD 的移动轨迹,所以「看起来没了」的多数情况都能捞回来。

功能分支同步最新代码:rebase 换底座

feature 拉出来之后,共享分支一直在往前走,你的底座就旧了。基于过时代码开发,越晚合回去冲突越多。

bash 复制代码
git checkout feature
git rebase qa
复制代码
rebase 前:
qa:       A --- B --- C --- F --- G
feature:  A --- B --- C --- D --- E

rebase 后:
feature:  A --- B --- C --- F --- G --- D' --- E'

结果不是「把代码换成 qa」,而是以最新 qa 为底,再把你的提交重放一遍,你的功能还在。git pull --rebase 是同一件事,只是底座换成远程最新。

也可以在 feature 上 git merge qa 来同步,能用,就是会多出合并节点,历史更岔。

HEAD、切分支、reset 的区别

HEAD 表示「你现在在哪」,一般是 HEAD -> 分支 -> 提交。

  • git switch feature:换的是 HEAD 挂在哪条分支上,两条分支的尖都不动。
  • git reset --hard <hash>:不换分支,而是把当前分支的尖挪到指定提交,--hard 连工作区一起改。

一句话:切分支换的是「我站在哪条线上」,reset 挪的是「我这条线指向哪」。

checkout 一个 hash 之后的 detached HEAD

git checkout <hash> 之后 HEAD 直接指向某个提交,不挂任何分支,这就是 detached HEAD。看旧版本、排查问题没问题,但在上面继续提交就危险:这些提交没有分支指着,一切走就很难找。

要保住就先建分支:

bash 复制代码
git switch -c recover-wip

工作区、暂存区、本地仓库

复制代码
工作区 --git add--> 暂存区 --git commit--> 本地仓库 --git push--> 远程

commit 只到本地仓库,push 才上远程。回退对应的两条:

bash 复制代码
git restore <文件>            // 丢弃工作区改动
git restore --staged <文件>   // 撤出暂存区,改动仍留在工作区

git checkout -- <文件> 是 restore 的旧写法,别和切分支的 checkout 混。

一句话分清 abort、reset、revert

  • git merge --abort:取消正在进行的合并,回到合之前。
  • git reset --hard:把当前分支尖和工作区强行拨到某个提交,本地未推送时用,会丢未提交内容。
  • git revert:用一个新提交反做掉旧改动,历史保留,已推送的共享分支用它。

快进合并(fast-forward)

如果你的分支没有新提交,只是落后于目标分支,合并时 Git 不需要造合并节点,直接把分支尖往前移,这叫快进:

复制代码
合并前:
qa:       A --- B --- C --- D
我的分支: A --- B

合并后:我的分支尖直接移到 D,不产生新节点

两边都有新提交时就不能快进,要么产生 merge 节点,要么先 rebase。push 被拒的报错里常出现的 non-fast-forward,说的就是这件事。

文件已经提交过,再加 .gitignore 没用

.gitignore 只对未被跟踪的文件生效。已经提交进仓库的文件,加规则也照样被跟踪。

bash 复制代码
git rm --cached <文件>   // 从版本控制移除,本地文件保留
git commit -m "stop tracking"

配置文件、本地环境文件建议一开始就写进 .gitignore。已经推上去的密钥要先轮换,删文件不等于历史里没有。

冲突时的三方:base、ours、theirs

Git 合并是三方比较:共同祖先(base)、当前分支(ours)、合入的一侧(theirs)。两边相对 base 改了不同地方,Git 能自动合;改了同一处,就交给你决定,也就是冲突标记的由来。

bash 复制代码
git checkout --ours <文件>     // 直接要当前分支的版本
git checkout --theirs <文件>   // 直接要合入那侧的版本

整文件二选一才用这两条,多数冲突还是要手动合成一版。注意 rebase 时 ours 和 theirs 的含义是反过来的:此时「当前」是被重放到的那个底座。