社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Git

聊聊Git中的8个坑

脚本之家 • 2 月前 • 173 次点击  

将 脚本之家 设为“星标⭐”

第一时间收到文章更新
图片
来源 | 苏三说技术 (ID:susanSayJava)

前言

对于程序员来说,Git是最常用的软件之一。

Git这东西,用好了是神器,用不好就是灾难。

今天这篇文章,盘了一下这些年在项目里遇到过的Git“坑”,挑出8个最常见的,跟大家好好聊聊。

希望对你会有所帮助。

一、git reset --hard 丢了代码

1.1 场景重现

有些小伙伴在工作中可能遇到过这样的场景:改了一堆代码,突然发现方向错了,想回到之前的某个版本。你很自信地敲了git reset --hard HEAD~3,然后——发现写了两天的代码全没了。

这不是段子,这是真事。

1.2 为什么会出现这个问题?

git reset --hard是Git里最危险的操作之一。

它的作用是: 把当前分支的HEAD指针移到指定提交,同时强制更新工作目录和暂存区。

用最直白的话说,它会物理删除你工作目录里所有“多余”的文件和改动。

1.3 正确解法

解法一:用git reflog救回来

只要你还没执行git gc(垃圾回收),所有的提交记录其实都还在Git的数据库里。

git reflog记录了本地仓库的所有HEAD变动,包括reset之前的操作。

# 查看所有操作记录
git reflog

# 找到reset之前的commit hash,比如abc123
git reset --hard abc123

解法二:用git reset --soft或git reset --mixed代替

# --soft:只移动HEAD,保留暂存区和工作目录
git reset --soft HEAD~3

# --mixed(默认):移动HEAD,重置暂存区,保留工作目录
git reset HEAD~3

核心原则:

  • 想丢弃修改用git checkout -- ,别用reset --hard
  • 每次reset之前先看一眼git status
  • 不确定的时候就先用git stash把改动存起来

底层原理:Git的每次提交都是一个完整的快照,不会真正“删除”任何东西。

reset只是移动了分支指针,被“丢弃”的提交在Git的垃圾回收机制运行前一直存在。

git reflog就是找回它们的保险绳。

二、合并冲突解决后忘记提交

2.1 场景重现

解决完一个复杂的合并冲突,你松了口气,心想“总算搞定了”。

然后你切到另一个分支去处理别的任务。

等你切回来的时候,发现冲突标记还在文件里,之前辛辛苦苦解决的冲突全白干了,而且有的文件被改乱了,你甚至不知道哪些冲突已经解决过。

2.2 为什么会出现这个问题?

Git的工作区(Working Directory)是独立于分支的。

你切分支的时候,如果工作区有未提交的修改,Git的行为取决于这些修改是否会与新分支冲突。

如果切换目标分支没有修改这些文件,Git会直接带走未提交的修改。

但问题在于:解决冲突留下的标记(<<<<<<<、>>>>>>>)不算“修改”——它们是文件内容的一部分。

Git不会帮你清理这些标记,也不会提醒你“还有冲突标记没处理”。

2.3 正确解法

解法一:养成习惯——解决完冲突立即git commit

# 解决冲突后立即提交
git add .
git commit -m "合并XXX分支,解决冲突"

解法二:用git merge --abort撤销合并

如果在解决冲突的过程中你发现自己搞不定了,或者切错分支了:

# 完全撤销本次合并,回到合并前的状态
git merge --abort

这个命令会撤销一切,回到执行git merge之前的状态,不会留下任何冲突标记。

解法三:用git stash暂存未提交的修改

# 暂存当前所有未提交的修改
git stash push -u -m "正在解决冲突中,切分支处理紧急bug"

# 切换分支处理紧急事务
git checkout hotfix

# 处理完切回来,恢复工作状态
git checkout feature-branch
git stash pop

核心原则:

  • 解决完冲突 立即提交,别拖
  • 不确定的时候用git merge --abort安全退出
  • 临时中断用git stash而不是硬切

三、git stash pop 冲突导致stash丢失

3.1 场景重现

你git stash push把代码暂存起来了。

切完分支回来,执行git stash pop想把代码恢复,结果啪,冲突了。

更糟糕的是,你发现stash里的内容也不见了。

3.2 为什么会出现这个问题?

git stash pop等于git stash apply + git stash drop两条命令的合并。

如果应用时发生了冲突,Git只执行了git stash apply,然后把stash留在了栈里。

但从用户视角看,冲突发生了,stash好像也不见了,问题就卡住了。

3.3 正确解法

# 查看stash还在不在(大概率还在)
git stash list

# 如果冲突已经解决,手动清理stash
git stash drop

# 如果冲突还没解决,可以用git stash show查看stash里的内容
git stash show -p stash@{0}

核心原则:

  • 用git stash apply代替git stash pop
  • apply之后确认没问题,再手动git stash drop
  • 这样就算有冲突,stash也还在,可以重新apply

四、git push --force 覆盖了别人的代码

4.1 场景重现

“我push的时候提示冲突了,我就用git push --force了一下,然后同事说他写的代码没了,这是怎么回事?”

这是团队Git协作里最常见、最严重的错误。

4.2 为什么会出现这个问题?

git push --force的语义是:把本地仓库的分支状态直接覆盖远程仓库,不做任何检查。

如果你的本地分支落后于远程,--force会用旧数据强行覆盖新数据, 同事已经push上去的代码会被永久抹除。

4.3 正确解法

解法一:用--force-with-lease代替--force

# 安全替代方案:只有在远程分支没有被其他人更新过的情况下才推送
git push --force-with-lease

--force-with-lease会检查远程分支是否和你上次拉取时一致。

如果远程分支已经被别人更新了,推送会直接失败,提示你先pull。

解法二:先pull再push,永远不用--force

# 先拉取最新代码
git pull --rebase

# 再推送(用普通push)
git push

核心原则:

  • 绝对不用git push --force
  • 用--force-with-lease替代
  • push前先看一眼git status

底层原理:git push --force是“盲写”,而--force-with-lease是“带条件地写”——只有在远程分支状态和自己预期一致时才执行强制推送。

五、git rebase 把分支基线搞错了

5.1 场景重现

你准备把feature分支变基到最新的master上,结果把master变基到了feature上。

整个master的分支线被分岔了,其他人的工作全乱套了。

5.2 正确解法

# 正确的rebase用法:站在feature分支,变基到master
git checkout feature
git rebase master

# 错误的用法:站在master,变基到feature(千万别干!)
git checkout master
git rebase feature  # ❌ 这一行会搞乱master的历史

如果已经做了错误的rebase:

# 用git reflog找到rebase之前的位置,用git reset --hard恢复到操作前的提交
git reflog
git reset --hard

核心原则:

  • rebase的是当前分支,目标分支不变
  • 先切到要动的分支,再执行rebase
  • 已经推送到远程的分支尽量不要rebase(会把远程协作变成灾难)

六、git cherry-pick 冲突处理的误区

6.1 场景重现

你cherry-pick了一个commit,发现冲突了,手动改完文件之后,直接执行git commit -m "xxx"。

然后发现cherry-pick的状态还没结束,提交历史变得乱七八糟。

6.2 正确解法

# 正确的cherry-pick冲突处理流程
git cherry-pick abc123

# 如果有冲突,解决冲突后
git add .

# 继续cherry-pick(而不是直接commit)
git cherry-pick --continue

# 如果搞乱了,可以撤销
git cherry-pick --abort

核心区别:git commit和git cherry-pick --continue都会产生一个新提交,但--continue会保留cherry-pick的元信息(作者、时间、提交信息)并正确完成操作。

直接 git commit会中断cherry-pick流程,导致操作不完整。

七、git branch -D 误删分支

7.1 场景重现

你删了一个以为自己不需要的分支,结果发现上面还有几个重要的commit没合并。

直接git branch -D feature,分支没了,commit也找不到了。

7.2 正确解法

# 删除分支前先用-d检查是否已合并
git branch -d feature  # 安全删除:只删除已合并的分支

# 强制删除前确认
git branch -D feature  # 危险命令

# 如果已经误删,用reflog找回
git reflog
# 找到分支最后一次commit的hash
git checkout -b feature

底层原理:Git的分支本质上只是指向某个commit的指针。

删除分支只是删除了这个指针,commit本身还在(直到垃圾回收)。

所以误删分支通常可以找回,关键是不要拖太久。

八、git commit --amend 推送后的问题

8.1 场景重现

你刚推送了一个commit到远程,突然发现漏了一个文件,或者提交信息写错了。

你很自然地执行了git commit --amend,然后git push,结果被拒绝了。

8.2 为什么会出现这个问题?

git commit --amend会创建一个新的commit,替换掉当前的commit。

commit的hash会变。

而远程仓库的分支还指向旧的commit。当你执行git push时,Git发现你本地的commit历史和远程不一致,于是拒绝推送。

更麻烦的是,如果其他人已经基于旧的commit做了工作,你的变基会让团队协作乱套。

8.3 正确解法

解法一:还没push,随便amend

git add .
git commit --amend -m "新的提交信息"

解法二:已经push了,绝对不要amend

# ❌ 错误做法
git commit --amend
git push --force  # 千万不要!会覆盖远程历史

# ✅ 正确做法:追加一个新的修复commit
git add .
git commit -m "fix: 补充遗漏的文件"
git push

核心原则:

  • amend只适用于还没push到远程的commit
  • 已经push的commit,用追加commit替代amend
  • 如果一定要改,用git push --force-with-lease,但仅限于你一个人使用该分支的情况

九、Git操作速查表

操作
安全做法
危险做法
原因
回退代码git reset --softgit reset --hard
--hard会删除工作目录改动
推送覆盖git push --force-with-leasegit push --force
--force会覆盖别人代码
删除分支git branch -dgit branch -D
-D不检查是否已合并
恢复stashgit stash apply
 + git stash drop
git stash pop
pop冲突时会丢stash
提交修正git commit --amend
(未推送时)
git commit --amend
(已推送后)
已推送的amend会改变历史
合并冲突中断git merge --abort
直接git commit
直接commit会留下冲突标记
暂存工作git stash push -u
直接切分支
直接切可能带脏工作区

十、写在最后

在版本控制的世界里, 最大的误区就是以为Git会自动保护你。

事实恰恰相反——工具越强大,误操作的成本就越高。

reset --hard 删除的是物理文件,push --force 抹除的是团队协作的历史记录,git commit --amend 改变的是已经发布的事实。

我见过被 git push --force 搞得被迫回退整个版本的大项目,也见过 git stash pop 搞丢了一整周工作量的案例。

这些事之所以反复发生,不是开发者不够小心,而是Git的设计本身给了你一把能拆房子的锤子——你看到的是“撤销”,它执行的是“抹除”。

如果你从这篇文章带走一句话,我希望是:每次操作Git之前,先看一眼git status。

它能告诉你你在哪个分支、工作区是否干净、有没有未跟踪的文件——这些信息能帮你避开90%的坑。

养成习惯,比学会所有命令更重要。

图片
  推荐阅读:
  1. 技术强的很,为什么面试大厂一面二面总是挂?!
  2. 越会用 AI 的人,学习能力退化得越快
  3. OpenJDK 全面禁止 AI 生成代码
  4. AI越强越不能放弃学编程!斯坦福教授:别被“几分钟出Demo”骗走基本功,编程正在从背语法变成练判断
  5. PNG 三个字母的意思是「我不是 GIF」

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/200616