🔥🔥🔥2.5W字!8个场景问题!带你了解最实用的 git 操作!!!

版权声明:本人文章仅在掘金平台发布,请勿抄袭搬运,转载请注明作者及原文链接 🦉

阅读提示:网页版带有主题和代码高亮,阅读体验更佳 🍉

网上稍微搜一搜,每一篇 git 文章都在教你怎么 git add,git push,少数文章会给你说 git cherry-pick,git stash,但是很少有文章会给你说 git rebase。

从刚工作时起,你的 leader 前辈们都在告诉你,git rebase 很危险,平时就用 git merge 就行了,所以你工作了三五年,还没怎么用过 git rebase,有时候遇到 git 问题,不是删分支就是在删分支的路上,我猜你还会把需求代码先一点一点复制粘贴下来,然后删除旧分支再建新分支,再改回去,对吧?

stop!!!

诶棒油,你的头顶长了什么东西,我眼睛里没有~

那么今天,我们 git rebase 要讲,git cherry-pick、git stash 也要讲,而且要讲一些可能大家不知道的,实用的 git 知识和操作。

带着问题找答案,先来看看下面八个问题,你是否能解决。如果你都能解决,那么恭喜你,你的 git 已然化境:

(前四个问题只能算是开胃小菜)

  • 新需求忘记开新分支,提交到其它分支了,怎么办?
  • 新开发了一个功能,合并 master 了,产品过了一段时间说不要了,怎么办?(不可以殴打产品)
  • 功能开发了一半,需要拉一下 master,但是你又不想 commit 代码怎么办?
  • 刚才提交了一个 commit,发现 message 写得有问题,怎么办?
  • 需求开发提交了几个 commit,提交 master 时领导 review 后,说你第一笔 commit 代码有问题,让你改一下,怎么办?
  • 刚才提交了很多个 commit,发现最初的 commit meesage 写得有问题,怎么办?
  • 一个大需求开发了一个月,每天拉 master 代码合并到本地分支,发现这个需求自己提了十多个 commit,需求需要发布 merge 到 master 了,领导让你把十多个 commit 合并成一个,方便 code review,怎么办?
  • 昨天提交了一个 commit 到 master 了,今天发现 commit message 写错了,但是 master 上别人的提交已经有几十上百个了,领导说你 message 写错了,改一下,怎么办?

不能完全解答的朋友就请继续往下看。

为了方便演示,我现在新建一个 git-demo 的项目,新增 a.js、b.js、c.js、d.js 四个演示文件,每个文件一句代码,每个文件一次 commit 共四个 commit:

问题1:新需求忘记开新分支,提交到其它分支了,怎么办?

我当初犯过最严重的错误就是,同时开发两个需求,忙来忙去,两个需求的 commit 搞岔了,两个分支都各有另一个需求的 commit,当时带我的同事都懵了。

然后在他一顿猛如虎的操作下,才理清了我的 commit。

其实答案很简单,就是 cherry-pick,这倒不是什么新鲜知识,很多有点工作经验的都知道,那为什么我们还讲,一是照顾新同学,二是这个命令对我们后续的题目解答很重要。

git cherry-pcik 的作用,就是将任意分支的 commit 挑拣到当前分支,但是这个挑拣不会删除原分支的 commit。

使用方式,就是紧跟着我们某个 commit 的 hash 值,可以跟多个,每个 hash 使用空格分割:

bash 复制代码
git cherry-pick 123abc 456def 789ghi

不妨在演示项目中新建一个分支 test:

我们在 test 分支新增一个 commit:

然后我们复制 e.js 的 hash,切回 master 使用 cherry-pick 将其应用到 master 上:

而 test 分支的 e.js commit 不会受到影响:

这就是 cherry-pick 最简单的应用。

不过,你聪明的小脑袋瓜可能会问,cherry-pick 任意分支?那挑拣的 commit 如果是在本分支呢?

诶,问得好,我们不妨试试。

先将刚才的 e.js 从 master 撤回。然后将 e.js、d.js 做 cherry-pick,这里的 e.js 是 test 分支的,d.js 是 master 分支自己的,我们在 master 分支做 cherry-pick 操作:

注意看红框里的内容,第一个框里说的意思,就是上一个 cherry-pick 是空的,可能是解决冲突产生的,你可以直接执行 git commit --allow-empty 将没有任何内容更改的 commit 提交到当前分支。

假设我们直接执行 git commit --allow-empty,会弹出 vim 编辑器让我们编辑该 commit 的信息:

我们不做任何操作,直接按 esc 键,然后输入 :wq 并按回车键退出:

可以看到,d.js 有两次 commit:

回到前面,除了 git commit --allow-empty,git 还提示我们有其它几个命令可以选择:

  • git cherry-pick --skip
  • git cherry-pick --continue
  • git cherry-pick --abort

git cherry-pick --skip 的意思就是跳过,d.js 本身对比 master 自身来说没有任何修改,是空的,直接跳过,仅 cherry-pick e.js。

git cherry-pick --continue 的意思是说,当你使用 git cherry-pick 遇到冲突,解决冲突并把修改添加到暂存区(使用 git add)之后,就可以使用这个命令,让 git 继续执行 cherry-pick 操作。如果你没解决冲突,一直执行这个命令,是不会有任何有意义的效果的。

git cherry-pick --abort 则是放弃此次 cherry-pick,只要放弃了,所有内容都不会 pick,包括 e.js 也不会被 pick 过去,等于直接放弃本次 pick 操作。

综上,如果你在 cherry-pick 时不小心 pick 了本分支的 commit,且是空白内容没有实质性内容冲突,最好执行 git cherry-pick --skip。但是实际上,那么多个 commit,它只会提示你 The previous cherry-pick is now empty, possibly due to conflict resolution. ,你并不知道具体是哪个,直接跳过也不见得是正确的,也可以先 pick 过来,再决定取舍。

另外,cherry-pick 的 commit 理想状态下是没有冲突的,但是很多时候会有冲突,必须解决冲突了才能继续 cherry-pick。

具体点说,冲突解决完,需要继续 git add 文件1 文件2 ...,然后修改 commit message。

这两步做完,还需要继续执行:

bash 复制代码
git cherry-pick --continue

这样呢,一次 cherry-pick 的过程才算结束。

再多说一句,git cherry-pick 本就应该用于挑拣其它分支的 commit,所以用的时候不要挑拣本分支 commit。

问题2:新开发了一个功能,合并 master 了,产品过了一段时间说不要了,怎么办?

这个问题也不算太难,直接 git revert。

使用方式,也是跟 hash 值:

bash 复制代码
git revert 123abc 456def

当然,上面的用法是针对不连续的 commit 来说的,如果你是连续的多个 commit 一起撤回,可以这么用:

git revert <start_commit>(不包含)..<end_commit>(包含),例如:

bash 复制代码
git revert abcdef123..7890abcd

问题3:功能开发了一半,需要拉一下 master,但是你又不想 commit 代码怎么办?

git stash

又是一个高频使用的命令,重要性不言而喻。场景很多,比如,我在当前 bug_fix 分支,在修复一个 bug,突然来了一个优先级更高的 bug,那已经写的代码不能直接 commit 吧?你当然可以说建一个新分支呗,一个 bug 一个分支,这也是一个解决方案,不过各家公司有各家公司的要求,具体问题具体分析。不切换分支的情况下,就可以 git stash。

修复完这个紧急 bug 后,我们需要继续修复前一个 bug,就可以执行 git stash apply 将之前暂存的代码恢复,继续开发。

不过,这里需要注意,git stash apply 是应用最近一次 stash 的代码,如果你存了很多个,就必须指定。

我们可以通过 git stash list 命令查看所有的 stash:

bash 复制代码
stash@{0}: On main: stash1
stash@{1}: On feature: stash2
stash@{2}: On feature: stash3
stash@{3}: On main: stash4

越上面的越新,如果我们要应用某个旧的,指定一下即可:

bash 复制代码
git stash apply stash@{3}

git stash 和 git commit 一样也可以设置 message:

bash 复制代码
git stash -m 'stash: 这是一个 stash'

篇幅所限,我们用表格汇总下 stash 命令:

命令 作用 示例
git stashgit stash push 将当前工作目录和暂存区的修改保存到栈中 git stash push -m "保存修改"
git stash list 查看当前保存的所有 stash git stash list
git stash apply 应用最近一次保存的 stash,应用后 stash 仍保留在栈中 git stash apply
git stash apply <stash编号> 应用指定的 stash git stash apply stash@{1}
git stash pop 应用最近一次保存的 stash,并将其从栈中删除 git stash pop
git stash pop <stash编号> 应用指定的 stash 并将其从栈中删除 git stash pop stash@{1}
git stash drop 删除最近一次保存的 stash git stash drop
git stash drop <stash编号> 删除指定的 stash git stash drop stash@{1}
git stash clear 删除栈中所有的 stash git stash clear
git stash show 查看最近一次 stash 的差异 git stash show
git stash show <stash编号> 查看指定 stash 的差异 git stash show stash@{1}
git stash show -p <stash编号> 查看指定 stash 的详细差异内容 git stash show -p stash@{1}

问题4:刚才提交了一个 commit,发现 message 写得有问题,怎么办?

git commit --amend

当我们执行完命令后,会打开 vim 编辑器:

vim 编辑器的使用其实很简单,我们输入 i,底部会提示我们进入编辑模式:

我们使用箭头移动光标位置,输入新的 message:

修改完成,按 esc 键,再输入 :wq 回车,操作完成。

问题5:需求开发提交了几个 commit,提交 master 领导 review 后,说你第一笔 commit 代码有问题,让你改一下,怎么办?

这里的意思很简单,当我们辛苦开发了一阵子需求,提了好几个 commit 后,发现某一笔(非最新一笔)的代码有问题,需要修改,一般人的做法,就是库库一阵改了,再提个新的 commit 呗。

这其实也可行,但是还是那句话,不同的公司有不同的规范,commit 的管理尺度各不相同,所以不用太较真场景和问题的解决方法。

假设我们现在不新增 commit,就在原 commit 的基础上修改,这就需要用到 rebase。我们不讲深奥的理论,就只讲实操和现象,你先用起来再说。

现在,我们将 b.js 的 const a = 1 的内容修改一下,且不新增 commit。

我们需要执行命令git rebase -i <hash>

这里有个关键点,rebase -i 操作,后面跟的这个 hash 是一个开区间,也就是不包含在内的意思,假如,我要修改 b.js,hash 就是 a.js 的 hash,假如我要修改 c.js,hash 就至少得是 b.js 的 hash。

Talk is less,show me the code.

直接看结果,假设是 a.js 的 hash:

bash 复制代码
git rebase -i 081b0c26a4d7deb04ed1625b2a84f31f24d5fbe8

那么我们就可以编辑 b、c、d 三个提交。

假设是 b.js 的 hash:

bash 复制代码
git rebase -i b8cd836ef539457228a86fca8ddeb3ec2b52017e

那么我们就可以编辑 c、d 两个提交。

其实就是在这个开区间的 hash 范围内,所有的 commit 都可以被操作。

现在,我们就修改一下 b.js 的内容,不新增 commit:

这里绿色区域的文字,默认进入是 pick,当我们要操作某个 commit 时,将其替换为对应的操作,例如:

edit 的意思就是编辑该 commit。务必记得,在 vim 编辑器里,需要先键入 i 才能进入编辑模式。然后我们继续 esc + :wq 退出:

到了这一步,我们相当于穿越时空,来到了 b.js 提交的那个时空,我们在当前可以任意修改文件,任意操作代码,然后执行 commit,或者使用 git commit --amend 修改当前 b.js 的 message。

我们将 b.js 代码内容修改一下:

修改完代码,务必将修改后的代码暂存:

暂存完,这一步我们需要修改 commit message,如果我们不修改 commit message,我们直接 git rebase --continue,系统还是会弹出 vim 编辑器让我们修改,不修改的话我们直接 esc + :wq 退出:

仔细观察红框中的内容,首先是 commit 没有什么变化,其次,master 分支名旁边的表示 rebase 进程的提示没有了。

那么,我们针对历史 commit 代码的修改就结束了。

好了,这时候聪明的你又会问,除了 edit,还有其它的操作吗?

有的,有的兄弟,这样的命令一共有 11 种:

pick 就是默认的,reword 是只修改 commit message 不修改代码,squash 是将多个 commit 合并为一个。

squash 这里比较重要,我们继续来举个例子,假设我一共 4 个 commit,那么到底什么场景下我会需要把它合并成一个呢?

首先,开发时间长,每天都可能要处理不同需求和 bug,有时就只能先 commit,然后去做其它事情。这样就会产生多个 commit,当我们开发完了,我们需要提代码给 leader review,你一个需求不能整个五六个、七八个 commit 给他看吧,这时候就需要合并 commit。

其次,假设我现在修改 bug,先写了一版,推上去发现有问题,我需要继续修改,此时我当然可以使用上面的 git rebase 的 edit 方法,不过你也可以再新增一个 commit,然后合并 commit,这也是一种方案。

OK,废话不多说。

假设我现在需要把 b.js、c.js 的 commit 合并为一个:

此时是 a.js 的 hash,才能选择 b.js 与 c.js。

bash 复制代码
git rebase -i 081b0c26a4d7deb04ed1625b2a84f31f24d5fbe8

(s 是 squash 的缩写)

但是你发现,为什么第一个 s 是红色的?

其实是因为,在 squash 操作中,第一个 commit 不允许被 squash,第一个默认就是 pick。

假设合并 b & c,需要这样:

假设合并 c & d,需要这样:

假设合并 b & c & d,需要这样:

那么继续 esc + :wq 退出,如果有冲突会按照冲突流程处理,前面我们已经处理过。

可能看到这里,你又有疑问,如果 git rebase -i <hash> 的 hash 是开区间,那我就是要编辑或者合并 a.js 的 commit 怎么办?

可以使用参数 --root 解决:

bash 复制代码
git rebase -i --root

必须这样执行,不需要加 hash。

这样又引出了新的问题,就是,我操作完了,发现搞错了,我怎么反悔?得回到 squash 前的状态啊!!

这里就涉及到 git 的 back 操作,想必你工作中也用到过,前面的内容中我们也提到了 revert。

那这里的回退,我们卖个关子,放在后面讲。

现在以表格整理一下这些命令,其余命令大家可以实操试试,毕竟实践出真知。

简称 英文全称 解释
p pick 按原样应用指定的提交,让提交按原有的顺序和内容应用到变基后的分支上。
r reword 应用该提交,但会暂停以允许修改提交信息,可用于完善之前提交时写得不够清晰或有错误的提交信息。
e edit 应用该提交,但会暂停以便修改提交内容,你能对此次提交所做的更改进行调整,之后使用 git commit --amend 更新提交。
s squash 将该提交与前一个提交合并,并且可以编辑合并后的提交信息,有助于把多个相关的小提交合并成一个更有意义的大提交。
f fixup 类似于 squash,将该提交合并到前一个提交,但会丢弃当前提交的提交信息,只保留前一个提交的信息。
x exec 在处理到该提交时执行一个 shell 命令,允许在变基过程中插入自定义操作,如运行测试脚本等。
d drop 移除该提交,即不将此提交应用到变基后的分支中,可用于去除不必要的提交。
b break 在该提交处暂停变基,让你可以手动检查状态或执行额外操作,之后使用 git rebase --continue 继续变基。
l label 在当前位置创建一个新的标签,类似于 git label 命令,方便后续引用该位置。
t reset 返回到指定的标签位置,撤销自该标签之后的所有变基操作。
m merge 引入指定的标签或提交,将其与当前分支合并,就像执行了一次 git merge 操作。

问题6:刚才提交了很多个 commit,发现最初的 commit meesage 写得有问题,怎么办?

通过前面的学习案例,后续的问题我们直接给出参考答案,减少演示带来的阅读负担。

第一个你应该想到的思路,就是 git rebase -i,并执行 reword 或者 edit 命令。

第二个思路,就是直接 revert 该 commit,然后重新 commit 时修改 message。

问题7:一个大需求开发了一个月,每天拉 master 代码合并到本地分支,发现这个需求自己提了十多个 commit,需求需要发布 merge 到 master 了,领导让你把十多个 commit 合并成一个,方便 code review,怎么办?

git rebase -i,执行 squash 操作。

当然了,这是一个理想状态,你的需求的 commit 是连续的,只有你自己的 commit。

可现实开发中,一个大需求绵延一个月,我们每天还要更新 master 的代码,我们自己的 commit 可能散落在各个时间点上,不能直接 squash,不然你就会面对几百个上千个 commit,无异于大海捞针。

最正确的做法,是我先将开发完成的分支 push 到 remote 仓库,然后我新建一个 merge request,不要直接确认,我们只是需要其 diff 出分支上的更改。因为此时 gitlab 等托管平台已经会自动 diff 出本分支的 commit,这样你不用再去翻找一月前的那些 commit 了。

然后,你新建一个分支,将原来分支上所有的 commit (这就是为什么要在原分支新建 merge request,方便你复制 commit hash)使用 cherry-pick 转移到新分支上来。

接着使用 squash 操作将其合并为一个 commit,再推送新分支到 remote,发起真正的 merge request。

怎么样!!这个思路是不是很 sao ~。

问题8:昨天提交了一个 commit 到 master 了,今天发现 commit message 写错了,但是 master 上别人的提交已经有几十上百个了,领导说你 message 写错了,改一下,怎么办?

你可能会说,git rebase -i,执行 reword 或者 edit。

是的,这不算错。

但是这和第 6 题不同,这是已经在 master 上的修改了,不是本地的 commit。

如果我们执行 rebase 操作,因为涉及到变基,代码 push 到托管平台后,会 diff 出你的变更,它会把该 commit 后的所有 commit 都识别为你的变更,你会发现你这个分支提上去 merge 到 master 时会有 N 个 commit,那些 commit 都是你同事的,且已经是在 master 上了。

理论上你直接合并也没有问题,因为你本身没有修改过任何东西,哪怕合上去起了冲突,冲突解决完就行了。但是你的领导看着你的这个操作,明明只是修改了一个 commit message,但是却多出来 N 个已经在 master 的 commit,他看到肯定会懵逼,几乎不会同意你的 merge request。

所以,最好的解决方案是就是:

将错就错~?

哈哈,大概率现实是这样的,你的 leader 会和你说,错就错了,一个 commit message 而已。

这其实没毛病,但是如果管理严格一点的公司呢?

或者说,这个问题难道真的没法解决吗?

其实方案我们在第 6 题也提过。

解决办法就是先对其 revert,然后重新 commit,这是最稳妥的解决办法。

对,思路就是这么简单,不要拘泥于 git rebase 强大的历史 commit 能力,最简单朴素的方式或许是最安全的。

一点点后续:我想反悔,怎么办?

假如我们 git rebase 出错了怎么办?

假如你还没执行完,你的分支显示是这样的:master|REBASE,那么你可以直接执行 git rebase --abort。这会直接放弃本次 rebase。

其它的情况,例如 cherry-pick 也是同理。

但是,假如你的分支变成了 ((0903e23230...)) 或者其它你看不懂的样子,并且 commit 也不见了一些,别慌!!

git 其实为我们保存了所有的历史记录。

我们使用 git reflog 可以查看:

想退出此模式的话直接 q 或者 wq

假设现在我想回到下面红框时候的 commit 状态:

此时,我只需要先复制它的 hash ------ c7d7bbb,然后 reset:

bash 复制代码
git reset --hard c7d7bbb

就能回到最初的时候了!~

不过使用 --hard 参数会丢失暂存区的更改和影响工作目录,如果你不是很明确地想要回退到某个历史记录上,建议使用 git reset --soft。下面也列一下 reset 的参数:

参数 作用描述
--soft 仅移动分支指针到指定提交,暂存区和工作目录的内容保持不变,不改变文件的修改状态,暂存的内容依然保留。
--mixed 默认参数,移动分支指针到指定提交,同时重置暂存区,使其与指定提交时的状态一致,但工作目录中的文件内容不会被修改。
--hard 移动分支指针到指定提交,并将暂存区和工作目录的内容都重置为指定提交时的状态,会覆盖未提交的修改,使用需谨慎。
--merge 用于处理合并冲突后,将分支指针重置回合并前的状态,同时保留工作目录和暂存区中已解决的冲突内容。
--keep 尝试将分支指针移动到指定提交,同时保留工作目录中的修改。若工作目录中的修改与指定提交存在冲突,重置操作会失败。
--abort 终止一个正在进行的--merge--keep重置操作。

给个三连吧!!!!!!!!!!!!

往期推荐

爆肝两个月,我用flutter开发了一款免费音乐app 80+ 👍🏻 102+ 💚

搭建一个快速开发油猴脚本的前端工程 24+ 👍🏻 42+ 💚

金九银十招聘季,IT 打工人,该怎么识别烂公司好公司? 70+ 👍🏻 80+ 💚

为什么就这个文件的 ESLint 检查失效了?

学会 TypeScript 体操,轻松看懂开源项目代码

别人休息我努力,悄悄写个 cli 工具,必须提升效率,skr~ 60+ 👍🏻 110+ 💚

一文掌握 eslint,再也不怕项目报错 20+ 👍🏻 30+ 💚

开发一个 npm 库应该做哪些工程配置? 40+ 👍🏻 50+ 💚

分享我在前端学习与开发中用到的神仙网站和工具 40+ 👍🏻 110+ 💚

uniapp 踩坑记录(二) 130+ 👍🏻 150+ 💚

闲来无事,摸鱼时让 chatgpt 帮忙,写了一个 console 样式增强库并发布 npm 100+ 👍🏻 110+ 💚

uniapp 初体验踩坑记录 30+ 👍🏻 60+ 💚

两小时学会 JS 正则表达式,终身不忘 50+ 👍🏻

【一年前端必知必会】如何写出简洁清晰的代码 50+ 👍🏻

【一年前端必知必会】了解 Blob,ArrayBuffer,Base64 40+ 👍🏻 90+ 💚

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
李少兄3 小时前
在 IntelliJ IDEA 中修改 Git 远程仓库地址
java·git·intellij-idea
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端