记一次慌乱的git实操

306 阅读9分钟

前言

  在某天一个即将下班的时间点,群里传来了一条新信息,里面写着XXX已发布到生产环境,我突然意识到好像有点不对劲,因为这个需求是我负责的,我的需求都还没合到master,咋就发布了。

  这时候我开始慌了,因为是刚入职的新公司,确实不是很了解整个上线的流程,我就保持着在上家公司的一个发布习惯,按理说应该是pre预发布分支统一合并到master进行版本迭代才对。后面一问,发现是我太想当然了,现在只需要将我这个被测试验证完的需求分支合到master就可以了。

  听到这里,大家觉得一切都很合理对吧。但是我开始慌了,因为是已经快到下班时间了,又遇到这种事情,所以内心是又急又燥。大家可能会说不就是将功能分支合到master吗,这有啥慌的。没错,正常来说如果我的功能分支被测试验证通过了直接合进去不就完事了吗,但我只能说我的分支不干净了,怎么个不干净呢,这时候又回到上面我所设想的那样说起,当初设想的时将pre分支整体合到master中,所以在一次将功能分支合到pre分支时,遇到了冲突,大家可能已经猜到了我要怎么做了。

  没错,我直接将pre合到我的分支上来了,解决完冲突后重新push上去了,这么一看好像没什么毛病是吗,但是有经验的同学可能已经知道是什么问题了。是的,我的分支已经不干净了,因为pre分支里还有着其他同事不能上线的待验证分支,这时候怎么办呢,我勒了个着急呀,马上转手先把问题丢给deepseek,让他先给点方案,同时我在想着怎么处理风险最小,因为平时确实很少遇到这种情况,接下来就让我讲讲我在慌乱中找到的几种解决方案以及我最后实施的策略。

我这里简单模拟一下场景:

image.png

简单来说就是featB分支在合并完pre分支后还有一些新的改动在合并之后,这里我需要做的就是将pre的merger提交给去掉并保留合并后新增的提交

方案一:git rebase -i

  大家可能对 git rebase target-branch 会比较熟悉,他的作用是将当前分支的提交记录变基到(target-branch)目标分支,使得目标分支的提交历史看起来更线性。但是我们这里要做的不是两个分支的变基操作,因此就不展开讲解了,想要了解更多的朋友可以参考下面这篇文章:

git rebase详解(图解+最简单示例,一次就懂)

git rebase -i(交互式变基)

   我们先来看看AI给出的解释:git rebase -i(交互式变基)的作用是允许你以交互方式修改提交历史。它常用于整理、合并或调整提交记录,使版本历史更清晰。

  那为什么会给出这个方案呢,是因为该命令可以通过交互界面指定每个提交的处理方式,其中就包括删除,常用的处理方式有以下几种:

  • pick (p): 保留该提交(默认操作)。
  • reword (r): 保留提交,但修改提交信息。
  • squash (s): 将提交合并到前一个提交中,并保留提交信息供编辑。
  • drop (d): 删除该提交。

   这里要关注的是drop,目的就是要将"pre merge into feature/xxx"这段提交记录给删除。在没有改动的情况下,交互式界面是基于最初在安装git的时候选择的编辑工具,接下来我将通过该命令移除merge pre的commit记录。

git rebase -i commitID

首先切换到featB分支,然后通过命令输出当前分支的提交记录

git log --oneline 

image.png

从上图可以看到,当前featB分支中已经包含了pre分支中的所有提交记录,因此我们需要找到合并pre分支的前一个commitID,该操作会改变commitID结点之后所有提交,所以这里我们需要复制merge记录的前一个commitID,也就是16ee6b5

git rebase -i 16ee6b5

这时候就会弹出文本编辑器,我这里设置成了在vscode内置终端中打开,具体信息如下图:

image.png

那这时候问题就来了,我到底应该删除掉哪些commit,我们回到featB的Graph图看一下

image.png

从上图中可以看出,我们需要删除的是第一条线之外的所有提交,也就是右侧所有分支中的记录。具体操作就是将pick修改成drop标识,然后保存并离开即可。

image.png

这时候我们需要在编辑器中将对应的pick修改成drop,然后保存并关闭。这时候我们就成功的将当前分支之外的记录给删除了。

image.png

看起来这种方案也能解决问题对吧,但其实这种方案使用的前提是取决于当前操作的commit是否已经推送远程分支,未推送那确实没什么风险,但是一旦推送过远程分支,修改过后的分支就没有办法再推送上去了,除非使用-f强行推送去覆盖远程的提交记录,听起来就知道风险实在太大了,因此不建议在团队合作中使用。

不过这种交互式变基很适合用来合并当前分支的一些无意义提交信息,例如什么拼写错误、变量修改、缺陷修复等一些commit记录,通过reword标记可以保留这些提交,并修改提交信息,感兴趣的大家可以尝试一下。

方案二:git reset --hard + git cherry-pick

这个方案的核心是在当前分支中(featB)新建一个用来cherry-pick的副本,然后通过reset(Head指向回退)到指定commit,最后使用cherry-pick选择reset前需要保留的commit。

恢复分支

因为在上个方案的操作中,我们对featB分支进行了rebase操作,因此我们需要将其恢复到reabse之前的状态,来重新执行当前的方案。

git reflog // 查看历史操作记录

image.png

git reset --hard 3ce8d29 // 回退到rebase之前的提交

通过git reflog 和 git reset操作,我们的featB分支已经恢复回原来的状态。接下来我们就开始当前方案的实操

1. git reset --hard

git reset 命令用于回退版本,可以指定退回某一次提交的版本。

git reset 命令语法格式如下:

git reset [--soft | --mixed | --hard] [HEAD]

我们这里主要使用到了git reset --hard,关于git reset的详细介绍可以看看以下参考资料:

55.Git Reset 详解版本回滚的三种模式 - 知乎

1.1 基于当前分支创建备份分支

我们需要在featB分支上再创建一个备份分支,主要是为了后续对其使用cherry-pick操作。

git branch backup // 创建backup备份分支

1.2 使用git reset --hard 回退到合并pre之前的版本

还是一样,先通过git log --oneline 输出当前分支提交记录

image.png

找到合并pre分支的前一条记录的commitId,然后直接执行git reset --hard commitId

git reset --hard 16ee6b5

image.png
执行完后,featB就剩下这三条记录了,是不是感觉少了什么,我们把合并pre分支后的新增记录也给回退了。 不过没关系,我们在操作前还备份了backup分支,此时backup分支还是reset前的状态,接下来就需要将当前分支丢失的新增记录给找回来。

2. git cherry-pick

git cherry-pick命令的作用,就是将指定的提交(commit)应用于其他分支。

 git cherry-pick <commitHash> // 可同时选择一个或多个commitHash

上面命令会将指定的提交commitHash,应用于当前分支。这会在当前分支产生一个新的提交,并且会生成新的哈希值。

关于git cherry-pick的详细介绍可以看看以下参考资料:

git cherry-pick 教程 - 阮一峰的网络日志

2.1 将featB分支回退前的原有提交记录还原

因为需要还原featB分支自身被回退的提交记录,我们先切换到backup分支查看需要还原记录的commitHash值

image.png

这里因为是模拟的情景,所以能比较清楚的区分不同分支的提交记录,实际情况下大家可以通过Graph图去区分和查找对应的commitHash。

所以这里我们需要通过git cherry-pick 选中的两个commit记录应用到featB分支中。

确保当前分支为featB,并将上图选中的两个commitHash应用到featB分支

git cherry-pick 3ce8d29 82e0b5e 

执行成功后可以看到原有featB分支的修改已经还原了。

image.png

实现完以上步骤后,我们的功能分支终于变干净了,可以愉快的合并到master分支了。

方案三:git revert(待验证)

这个方案被因为操作复杂并且风险不小,因此被我排除掉了。当然也可能是我对他的了解和掌握不够。

总结

   作者最后使用的是第二种方案解决了问题,因为当时处于着急的状态,所以还有很多情况没有考虑到,有问题或者风险的地方欢迎各位指出。还有一个处理的不是很好的地方就是当初在解决冲突时,应该用新的分支去解决冲突并合并,这样可以避免自己的功能分支在下次merge时还会出现冲突的问题,并且还不需要合并pre进来。也当是一种经验教训吧。

   大家在实操的过程中一定要用备份分支来处理,避免把自己的分支弄乱了,并且还不知道怎么恢复回来。可以在本地分支新建分支去模拟各种处理方案,待方案可行时再去处理真正需要解决的分支。