Git cherry-pick极速理解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Git cherry-pick极速理解相关的知识,希望对你有一定的参考价值。

参考技术A cherry-pick,中文为遴选(个人喜欢叫做樱桃捡),可以理解为merge的一种,都是接收其他分支的修改,但是 cherry-pick 接受单个或者多个提交记录而非整个分支

如果使用 merge ,把b分支合并到 a 分支,变成 a3-merge ,则 b 分支的快照会完全覆盖 a ,此时 a3-merge 的文件快照为:

如果使用 cherry-pick ,仅接受 b1 的修改:

此时a分支中 file1不变,因为b1没有修改,file2接受了修改,结果为:

git使用进阶——stash及cherry-pick的理解和实例

俗话说,条条大路通罗马,很多时候要解决一个问题的办法都不止一个,但是不同的办法效率可能就有所区别。在git使用的时候,stashpick就是其中两个可以一定程度上提升效率的功能。

stash

场景需求分析

假如有这样一个需求场景:
我有一个master分支,基于master分支拉取了dev分支,并完成了一个用户管理的功能开发,现在已经正式发布使用。
那么现在我有了一个新的工作任务,需要继续进行一个商品管理的功能开发,于是我增加了一个商品信息实体类和一个商品管理的业务接口类,分别是GoodsGoodsController
但是呢,这个整体功能并没有开发完成,而是只开发了新增商品和查询,并且还没有完成测试。例如当前的业务接口代码如下:

/**
 * @Author tuzongxun
 * @Date 2021/9/19
 */
@RestController
@RequestMapping("/goods")
public class GoodsController {
   @PostMapping
   public String addGoods(@RequestBody Goods goods){
      System.out.println(goods);
      return "add success";
   }

   @GetMapping
   public List<Goods> findGoodsList(){
      List<Goods> list=new ArrayList<>();
      list.add(new Goods("apple",5.5));
      return list;
   }
}

就在这个时候,发现之前的用户管理功能有问题,需要立即修改
怎么办呢,自然是需要基于master分支拉取一个新的修复分支来紧急修复这个问题。
于是,我执行了git checkout master,准备切换到master分支之后再创建一个fix_user分支。
但是当我再来看我的代码的时候,就会发现关于商品管理的代码也都还在,例如使用git status会看到如下结果:

On branch master
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        src/main/java/com/example/gitdemo/controller/GoodsController.java
        src/main/java/com/example/gitdemo/model/Goods.java

nothing added to commit but untracked files present (use "git add" to track)

这个时候,如果我直接创建新的fix_user分支,实际上还是会把这些没有提交的代码带过去,但是我这个分支就是为了紧急处理用户管理的问题,把商品管理的也带过去显然是不合适的。
怎么办呢,一般可能会有这些可选的操作:

  1. 直接删除掉商品管理相关的内容,然后在master上创建fix_user,然后进行问题修复。但是由于商品管理的代码并没有在dev上提交,所以一旦现在删除,那么实际上就相当于之前的功夫都白费了,于是可能就会有第二种操作。
  2. 基于上边所说,可能会有人选择先把商品管理的这些内容手动找个地方备份起来,然后再删除,删完了再创建fix_user分支,然后进行用户管理的功能修复。那么这样操作确实可以保证在不影响问题修复开发的同时,还保证了之前的功夫不会白费。但是问题是,如果现在这个功能涉及到的文件并不止一两个,而是十几个,或者几十个,那么这种手动备份和后边的手动恢复就又会花费大量的时间,且还会增加很多的犯错率。那么既然手动备份和恢复也不好,就可能又有如下的第三种选择。
  3. 既然手动备份可能麻烦,而直接删除又会丢失之前的工作内容,那么索性就直接在dev上把商品管理功能的代码提交了算了。然后再切换到master,再基于master创建fix_user分支进行问题修复。这样的操作理论上是可以的,并且可能也有不少人就是这样干的。只不过在实际开发的时候也会有一些问题,比如从规范性上讲,没有开发完成和测试完成的功能一般是不建议提交的,这样就会增加多余的提交记录。那么之所以这样干,实际上就是为了一方面不丢失之前的功夫,另一方面又不想手动的管理这种备份,那么对于这种需求,git刚好就提供了可用的解决方案。
  4. git有一个功能叫stash,翻译过来是贮藏,有些教程上把这个操作叫做保存现场。利用这个功能,就可能即备份了当前修改的内容,又不需要手动备份,同时还不会增加新的提交记录。

注:实际上还有一种情况,当master和dev当前commit版本不一致时,没有commit之前是无法checkout的,上边可以checkout就是因为当前master和dev的版本一致。

stash操作

stash保存

这个功能,其实很久前我就听过,当时的同事还曾给我推荐过,但是由于那时候的git使用没有太多要求,所以一直就没有去了解和使用,但是既然是能提高效率的,自然还是了解了学会了更好。
在上边,我已经从dev切换到了master,现在就再切回dev,之后执行git stash保存之前增加的文件,然后输出如下:

No local changes to save

看到上边的输出可能会很奇怪,明明有两个新的文件,为什么说没有改变呢?实际上,是因为这两个文件都是全新的文件,现在只是存在于本地文件系统中,还没有被git管理,git stash操作的是被git管理了的内容。
基于之前的知识可以知道,这里需要使用add操作把新增的文件交给git管理,例如我使用了git add .之后,使用git status输出结果如下:

On branch dev
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   src/main/java/com/example/gitdemo/controller/GoodsController.java
        new file:   src/main/java/com/example/gitdemo/model/Goods.java

这时候再执行git stash操作后就有了新的输出:

Saved working directory and index state WIP on dev: ce1c82e add user manage

stash查询

从上边的提示来看应该是stash保存成功了,可以使用git stash list查询,结果如下:

stash@{0}: WIP on dev: ce1c82e add user manage

有查询结果,证明确实保存成功了,这时候再使用git status就会看到和使用commit后的结果一样:

On branch dev
nothing to commit, working tree clean

同时,在开发工具或者文件目录查看,会发现之前为了商品管理而增加的两个类文件都已经不见了,好像是回到了商品管理开发之前的状态。那么这个时候,就可以愉快的切换到master然后创建fix_user进行问题修改了。

stash恢复

当前用户管理的功能修复完成后,自然是需要再切换回dev重新开发商品管理未完成的功能,切换回去之后就需要把之前stash存下来的内容恢复回来,例如git stash pop,输出如下:

On branch dev
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        new file:   src/main/java/com/example/gitdemo/controller/GoodsController.java
        new file:   src/main/java/com/example/gitdemo/model/Goods.java

Dropped refs/stash@{0} (b8194bcd302c48b9044e4ec9ec10e9105b0e4e72)

上边的输出,可以看到在最后一行进行了一个删除操作,再使用git stash list会看到已经查不到之前存的那个记录了。
如果需要恢复的同时,保留stash的内容,可以使用git stash apply操作。

指定名称保存

上边的操作确实是可以的,但是实际上也有些小问题,首先就是之前查看记录时输出如下:

stash@{0}: WIP on dev: ce1c82e add user manage

实际上,我这里保存的是商品管理的功能,但是从上边的输出完全看不出来。那么当stash的记录很多之后,或者间隔时间长了之后,很可能就难以分辨到底这个记录有些什么内容,所以一般在stash的时候可以指定一个名称,以方便后续的使用,例如这里再执行git stash save "goods add and find",然后再执行git stash list查询,输出结果如下:

stash@{0}: On dev: goods add and find

指定记录恢复

除了git stash有些小问题外,git stash pop或者git stash apply也有些问题,那就是他们只会恢复最近的一次记录,如果有需求需要恢复其他记录,就需要其他的操作,例如git stash pop stash@{0}

注:stash恢复也可能会有冲突。

cherry-pick

除了stash,pick也是一个很好用的功能,可以用于代码的转移,例如有这样一个场景:

需求场景分析

上边讲stash时说,先在devstash,然后切换到master创建新的分支fix_user修复用户管理的bug,之后再切换回dev进行商品管理的后续开发。
假如这里在fix_user分支修复完用户管理的bug后,我忘记了切换回dev,而是直接在fix_user分支中恢复了商品管理stash的代码,并且进行了功能完善和后续测试,然后进行了代码的提交。
这时候使用git log --pretty=oneline --abbrev-commit查看日志时输出如下:

d2ce68d (HEAD -> fix_user) add goods and find
ce1c82e (master, dev) add user manage
02087dc init

然后从日志中发现分支用错了,我需要切换回dev,这时候怎么办呢,就可以使用pick功能进行代码的转移,准确的说应该是提交的复制。

cherry-pick操作

针对上边所说,完整cherry-pick操作如下:

  1. 使用git checkout dev切换到dev分支;
  2. 使用git cherry-pick d2ce68dfix_user中的提交复制过来。

这里需要注意的,这个复制只是复制内容,而不会复制id,例如再使用git log --pretty=oneline --abbrev-commit输出结果如下:

69be4a4 (HEAD -> dev) add goods and find
ce1c82e (master) add user manage
02087dc init

可以看到当前已经在dev分支了,并且最新的提交已经包含了add goods and find,但是前边的id是69be4a4而不是d2ce68d

注:上边的代码转移需求,使用cherry-pick并不是唯一的办法,但是是相对更简单快捷的方式。同时,cherry-pick也可能造成代码冲突。另外,如果需要转移多次提交,需要从前往后一次次的操作而不能直接到最后一次提交。

以上是关于Git cherry-pick极速理解的主要内容,如果未能解决你的问题,请参考以下文章

git使用进阶——stash及cherry-pick的理解和实例

git cherry-pick

git不同分支局部代码合并 git cherry-pick

git执行cherry-pick时修改提交信息

git cherry-pick

git cherry-pick