Powered by Typecho)))
Optimized by EAimTY
日常开发时经常会遇到在稳定分支发hotfix的情况,通常的解决办法是git stash暂存当前工作区内容,再checkout到需要发补丁的分支下工作,完了再切回来。大多数时候这么干没啥问题。不过如果类似node js项目或者其他有大量外部库依赖项目时就麻烦了。比如php或者golang,通常vendor目录都会git ignore。如果刚好工作分支升级了vendor下的依赖库,与即将发补丁的分支依赖库冲突,那就麻烦了,只能clone到别的目录下来做。好在git 2.5已经把这个需求内置实现。这就要介绍今天的主角:git worktree 命令:
git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]
git worktree list [--porcelain]
git worktree lock [--reason <string>] <worktree>
git worktree move <worktree> <new-path>
git worktree prune [-n] [-v] [--expire <expire>]
git worktree remove [-f] <worktree>
git worktree unlock <worktree>
项目开发的一般流程是先fork项目,然后基于自己fork的项目开发,最后通过提交PR将自己的代码merge到上游。 这里会涉及到两个:上游仓库和自己fork的仓库,它们之间最终通过PR关联起来。 但是,由于Golang特定的GOPATH依赖,导致这种方式针对于Golang项目会有些问题。
举个例子,来演示整个过程。为描述清晰、方便,有如下约定:
上游项目为org/repo
fork项目为user/repo
按照常规的Git开发流程,在本地clone的fork项目中直接go build,会存在依赖问题:
Fork出来的项目在GOPATH中的路径发生了改变,但是代码中import的路径没有改变,所以没有使用fork项目中的package。
即使构建成功,但是真正使用也不是fork项目中修改的代码 有可能GOPATH中已经存在org/repo,所以按照上面的方式构建,不会提示错误。但是如果在user/repo中修改的代码,是无法生效的。 因为代码中引用的是org/repo的package,不是user/repo中修改过的package。
subtree 处理多层包含是没有问题的,因为包含进项目之后, 别人根本看不出这是一个 subtree, 所以它本质上还只是管理本地 repo 的一种方法。
使用 Git subtree 新建或更新子项目的时候,可以选用 --squash 参数, 它的作用就是把 subtree 子项目的更新记录进行合并,再合并到主项目中。
所以,在使用 --squash 参数的情况下, subtree add 或者 pull 操作的结果对应两个 commit, 一个是 Squash 了子项目的历史记录, 一个是 Merge 到主项目中。
这种做法下,主项目的历史记录看起来还是比较整齐的。 但在子项目有更新,需要 subtree pull 的时候,却经常需要处理冲突。 严重的,在每次 subtree pull 的时候都需要重复处理同样的冲突,非常烦人。
如果不使用 --squash 参数,子项目更新的时候,subtree pull 很顺利, 能够自动处理已解决过的冲突,缺点就是子项目的更新记录“污染”了主项目的。
假设你有一个在线商城项目 Shop,这个项目下有个订单模块 order,这个 order 模块很通用,你在下一个团购系统 Groupon,也要用到这个 order 模块,最简单的方式就是把整个 order 模块 copy 过来。
然而,这种 copy 方式,会很难维护:order 模块修改了一个Bug,然后又要复制更新到所有用了 order 模块的项目里。
这时就需要 git subtree 来管理这个 order 模块了:把 order 模块单独作为一个 git 仓库,在需要用到 order 模块的项目,通过使用 git subtree 把 order 模块当作一个 git 项目来引入,这就解决了 order 模块的共享和维护问题。
git subtree --help 可以查看 git subtree 命令的帮助文档。
git subtree 命令中,都会用到一个参数 --prefix=
git subtree add -P <子树名> <子仓库地址> <分支>
执行以上命令后,项目下就会新创建一个名为 <子树名> 的目录。
如果提前使用 git remote add <子仓库名> <子仓库地址> 添加了子项目的远程仓库地址(建议按此方式,下文都基于此),那么也可以这样:
git subtree add -P <子树名> <子仓库名> <分支>
以上命令可以多加一个 --squash 参数:
git subtree add -P <子树名> <子仓库名> <分支> --squash
--squash 参数含义是:把 subtree 的改动合并成一次commit,这样就不用写入子项目完整的历史记录。
先 fetch:
git fetch <子仓库名> <分支>
后 pull:
git subtree pull -P <子树名> <子仓库名> <分支>
以上命令也可以多加一个 --squash 参数:
git subtree pull -P <子树名> <子仓库名> <分支> --squash
git subtree push -P <子树名> <子仓库名> <分支>