问题背景

我们知道 git 仓库里的分支等等对象引用 (refs),在本地文件系统里体现是在 refs/ 目录下以文件形式存在。 例如 refs/heads/master 。同时, git 支持分支名使用路径形式,例如 features/my-awesome-idea ,其对应的文件即 refs/heads/features/my-awesome-idea

考虑这种情景,如果先创建 refs/heads/features 再创建 refs/heads/features/my-awesome-idea ,我们知道在 文件系统里一般是不允许同一级目录下存在同名多个项目的。相应的 git 会报错

refs/heads/features exists, cannot create

那么再考虑这种情况:远程先创建了 features 分支,本地拉取后,远程删除并重新创建了 features/my-awesome-idea 这个时候本地不论是做 pull 还是 fetch 操作,都会出现同样的报错。

解决

查看 git 手册,会注意到 git fetch--prune 参数

-p, –prune Before fetching, remove any remote-tracking references that no longer exist on the remote. Tags are not subject to pruning if they are fetched only because of the default tag auto-following or due to a –tags option. However, if tags are fetched due to an explicit refspec (either on the command line or in the remote configuration, for example if the remote was cloned with the –mirror option), then they are also subject to pruning. Supplying –prune-tags is a shorthand for providing the tag refspec.

没错,这个参数的确可以用,但请留意,这个参数只会处理 追踪远程 的分支,如果没有追踪远程(比如下面提到 jenkins 的做法), 依旧会报错。碰到这种情景,如果分支并不是当前关注的分支,可以做一下 git remote prune <remoteName>... 操作,都会出现同样的报错。我们注意到 该操作会清理掉指定远程的所有 refs 。同时需要注意这个操作会与远程通信。

碰到报错时,意识到是该问题,手工解决当然是可行的,但是遇到某些工具,尤其是代码持续集成、交付类的工具时, 就…只能自己动手改了。

jenkins 的问题

jenkins 同样会遇到这个问题,并且还无法自动修复。如果是 freestyle 类型的 job ,在界面上还可以点击管理选项清空 工作目录。但流水线代码类型的 job 就没这个选项了(jenkins 难用的一方面,依靠插件实现,各个功能逻辑不一致)。 另外只有在遇到这个问题时,再去点清理也非常令人沮丧。

jenkins 拉取代码,不论是界面上 SCM git 配置,还是流水线代码中 git 或者 checkout 指令,都是通过 git-plugin 以及 git-client-plugin 实现的。通过查看代码[1],我们找到与日志表现一样,通过 refspec 方式 进行代码拉取,虽然传递了 --prune 参数,显然并达到预期的效果。

我们来看一下 jenkins git 拉取代码时大致流程

通过修改两个插件的代码,

更好的解决办法

清理远程 refs 操作实际上并不是每次都需要执行,虽然对构建效率并没有太大的影响,也许可以考虑在插件中加一个全局 的配置选项,让用户选择是否打开,这样也对现有用户没有产生实际影响。 自然这样的做法涉及到需要将补丁代码提交到插件上游,笔者改天把增加选项的代码写法搞懂后再向上游提交吧。

[1]: