[Git] 使用 Git SubTree 来共享函示库源代码

Posted petewell

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[Git] 使用 Git SubTree 来共享函示库源代码相关的知识,希望对你有一定的参考价值。

采用 GitSubtree 的方式来共用多个 project 会共用的 sourceCode,不过 subtree 的数据似乎不多,查到的数据都没有顺利的让我完成我的 subtree 情境,所以自己参考数据并且尝试成功后,就写一篇 blog 来记录一下,希望 subtree 以后会更方便用


写在前面的前面 (2017/05/06更新)

经过了大概几个月的使用,近日决定放弃用 Subtree 改回用 SubModule,因为遇到了一些问题,最严重的是两个使用同一个 Subtree 会莫名的不同步,
不同步的意思就是一边更新上去了,并确认在 Github 上已经是新的,另外一边 subtree pull 回来的竟然不是最新的,这样就算了,此时把 subtree push 回去,
要发 PR 还会发现竟然会把之前修改的盖回去!!这也不是第一次遇到,第二次遇到,所以决定要放弃 Subtree 改回去 Submodule ,应该稳定运行是没有问题,虽然麻烦了一点。

其他的问题例如:
1. 你在 Subtree 的 repo 会看到许多的 commit ,那些 commit 是使用 subtree repo 的人所造成的,并不一定真的对 subtree repo 里面的文件有影响
2. Main project 把修改 push 回 subtree repo 后,发 PR 竟然文件没有任何变更,这样就算了,直接 clone subtree repo 下来,下 merge 竟然会说是 unrelated repo... 真的是 WTF

也许是我 git 命令及使用方式不熟悉,所以才会有此问题,但现阶段来说研究这个浪费时间,所以决定改走回 submodule 的老路。

不过如果你还是有兴趣试看看的话,欢迎继续往下看


写在前面

为什么会用到 SubTree 呢?因为之前同事有用了 SubModule 觉得麻烦,虽然目前 Git 的 GUI 工具其实应该都有支持了,但如果是操作的话,相较于 SubTree 来说是繁琐了一些。
最近共用 Library Project 的需求再起(WPF & UWP)所以花了点时间找了替代 SubModule 的方案,看起来就只有 SubTree 啰~
看到一些地方也说建议大家可以用 SubTree 来替代 SubModule

SubTree vs SubModule

当初的出发点只是要找一个替代品,没有仔细的想过这问题,也是被问到后才去查的,其实目的上当然是差不多,但使用的情境上,或者看命令的设计上还是有差距的
查到 这篇 觉得解释的不错,直接把原文节录出来

  • submodule is a better fit for component-based development, where your main project depends on a fixed version of another component (repo). You keep only references in your parent repo (gitlinks, special entries in the index)
  • subtree is more like a system-based development, where your all repo contains everything at once, and you can modify any part.

我是这么理解的

  • SubModule 适合像是说所谓的共用函示库你是没有主空权的状况下,通常你用的都是一个固定版本,就是变动不那么频繁的函示库,在命令的使用情境上 SubModuel 的确有比较繁琐
  • SubTree 就是比较像我们需要的情境,会跟着 Main Project 一起成长的与改变的,在命令上使用相对简单许多

关于 git subtree vs git submodule 命令的操作,可以看 这篇 ,在里面就可以看到使用 git subModule 的状况下,命令需要满繁琐的,而 subTree 就几个简单的命令就搞定了


But... 人生最想不到的就是这个But...不然我也不会想要写这一篇 blog 了 XD

就让我来说一下从 SubTree 从头开始和我遇到的状况吧~(命令详细的参数使用方式就麻烦自行去查,谢谢~)

一、分割原来的源代码

因为我们要共用的函示库本来是我们 main repo 的一个数据夹,所以第一个步骤就是要把这个数据夹切割出来成为另外一个 repo,而 git 就有提供了一个 git subtree split 的命令,就可以指定一个目录,把那个目录的东西切割到另外一个 branch 去,
接着就可以把那个 branch 的东西 push 到你远端的另外一个函示库专用的 repo 了。

然后就遇到第一个问题,在 split 后我看 split 出来的数据夹也没有任何东西 (ex: .git 的数据夹,没有 .git 数据夹就不会有原来的 commit log 了) 阿我这样到底要怎么 push 啦?XD 从 GUI tool 是可以看到我 split 出来的另外一个 branch没错,结果搞半天后,
发现因为那个是个 branch 所以索性就切换到那个 branch,然后就可以 push 了 囧

OK~ 解决

二、加入远端的 repo 为 subtree 并且 push 回去 main repo 后让 git subtree 命令也都运行正常

这边就是卡最久的地方,出乎意料的麻烦,不过也多学到了一些东西就是。

要加入一个另外一个 repo 当作自己的 Subtree 来源,首先当然就是要用 git remote add 来增加一个 remote 的来源,就让新增的这个 remote 叫 shareLibraryRepo 吧!
然后就是要把它加入自己的 main repo 当作 subtree 啦~ 这时候我用第一时间查到的命令就是用 git subtree add -P ShareLibrary shareLibraryRepo master 来把 repo 放到我的 ShareLibrary 的数据夹当作 subtree ,
一切都看起来很简单顺利,然后我就把 main repo 给 push 回去,这时候当然要自己测试一下这样测试一下别人拉回去后,如果要用 git subtree pull 可不可以正常运行啰!

所以我就弄了一个干净的 main repo ,一样加了一个叫 shareLibraryRepo remote 后,下了 git subtree pull -P ShareLibrary shareLibraryRepo master 的命令,要来更新看看,结果就遇到了

refusing to merge unrelated histories

的错误消息... WTF... 那时候以为中间那边弄错了,重试了一次结果还是遇到一样的问题... 囧

只好再去找数据,又花了点时间结果找到 Github 其实就有 这篇 在介绍 subtree merge 的方式,照着上面的步骤做后,成功了~ 用了干净的 main repo 后,可以正常的下 git subtree pull 的命令更新 shareLibraryRepo 的 repo 了!
不过... 事情当然没有这么简单... =.=

当我开心的在 main repo 做了一些修改,包含有修改到 ShareLibrary 的东西,所以 commit 完 main repo 后,想要把对于 ShareLibrary 修改的东西一样 push 回去,然后就开心的下了 git subtree push -P ShareLibrary shareLibrary master,然后就遇到

! [rejected]??????? 72a6157733c4e0bf22f72b443e4ad3be0bc555ce -> master (non-fast-forward)
error: failed to push some refs to ‘git@github.com:*‘
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Merge the remote changes (e.g. ‘git pull‘)
hint: before pushing again.
hint: See the ‘Note about fast-forwards‘ in ‘git push --help‘ for details.

的错误消息... 被 reject !!! WTF... 然后又查半天找不到什么好方式,然后我就转方式想说一不做二不休,想说用 force push 的方式,但很不幸的 git subtree push 并不支持 --force 的参数,那....有办法吗?答案是有的!请参考下列命令的方式

git push shareLibraryRepo `git subtree split -P ShareLibrary --ignore-joins`:master --force

利用的是 git push 的 chain command 的方式来达到,嗯~总算松了一口起,起码刚刚改的一堆东西还可以回去 shareLibrary 的 repo!

但是这不是个解决问题的方式,总不能每次都要 force push 啊!!所以又花了时间尝试了整合 subtree 的命令步骤,最后终于发现了关键的一个地方...直接讲结论...

1. 用 git subtree add 是对的!
2. 记得下完 git subtree add 后,一定要多下一个下面的命令!记得下完 git subtree add 后,一定要多下一个下面的命令!记得下完 git subtree add 后,一定要多下一个下面的命令!因为很重要所以说三次
git merge -s ours --no-commit --allow-unrelated-histories shareLibraryRepo/master

这行命令就是建立好 subtree 关系的关键步骤啊!!!(抱头)就差了这一个步骤,所以让我一开始会遇到 refusing to merge unrelated histories 的错误消息 >"<

真的是雷!我其实不确定这到底是 bug 还是 feature (摊手)

总之在这之后,git subtree pull/push 都运行正常啦!(洒花)

王子和公主从此过着幸福快乐的日子


写在后面

尝试了 SubTree 的东西,大概有点心得

  • main repo 有没有加入 subtree 对 main repo 的操作没有任何影响,就把它视为 main repo 的一步份即可,无须特别理会
    • 换句话说修改到 subtree 里面的 sourcecode commit & push 回去 main repo 后,其他在同一个 main repo 的人不需要用 subtree 命令去 pull,就直接 pull main repo 即可,因为基本上它就是 main repo 的一部分
  • 有影响的状况只有
    • (1) subtree 的 repo 有更新,并且你想要 pull 回来的时候
    • (2) 你有修改到 subtree 的东西并且想 push 回去的时候
  • 要 pull / push subtree 的 repo 几个步骤
    • (1) 加入 shareLibrary 的 repo 为 remote 的来源(这个只要加过一次即可)
    • (2) 使用 git subtree 的命令来 pull / pull
  • git subtree 仅有 add / pull / push / split / merge 几个 command 而已,其实没有太复杂
    • split 比较特别,用途是将你目前 repo 的一个目录可以切成另外一个 repo,目的就是切割你原来 repo 的一个目录然后可以当作 subtree
  • subtree 最好不要加到与原来 shareLibrary 相同的路径,不然原来的 commit 历史纪录会被一并推回去远端的 repo (这点我不确定是不是因为这样的缘故,不过我是这样解决的)
  • 建议可以看看的连结:
    • mastering subtree
      • 里面有提到 git subtree 的命令,其实是包装好的一些命令的只是先帮你包装好而已
      • 例如 git subtree pull/push 其实就跟上面看到的 force push 的是一样的,其实都是透过 chaine command 的方式做到,都会帮你串 git subtree split 的命令
      • 例如 git subtree add 也是帮你做好了在那篇 Github 文章的几个步骤

最后也是要来说一下目前用 subtree 所觉得的缺点

  1. git subtree push 会花很久的时间,就像心得里面因为他会先 split 然后再 push,split 的时候就会计算那个数据夹的 commit 哪些是有关系的,通常都会花一段时间,目前这个还没找到解法
    1. 所以如果要比较快的 workaround 的方式,就是直接去修改 shareLibrary 的那个 repo,然后再用 git subtree pull 的方式拉回来,不过这样其实应该比较麻烦,所以就不要每次修改到 subtree 就都要 push 就好
  2. GUI 工具支持度不足,目前只知道 SourceTree 有支持,不过还好,就下操作而已

以上~ 感谢各位的观赏~ 我们下次再见~


2017/01/18 更新:

上面有说道 git subtree push 会花很久的时间,后来发现原来是有一个地方做的方式改变一下就不会了,如果你原本就照上面的方式使用 git subtree add 的命令,没有没有加其他的参数的话,应该就不会遇到我说的 git subtree push 的时候会很久的问题;
我会说会花很久的原因是因为我再下 git subtree add? 的时候多加了一个 --squash 的参数,这个参数可以让 subtree 加入的时候只会产生一个 commit ,但是看样子这样的缺点就是因为他没有完整的 subtree 的 history,所以会导致在 subtree push 的时候必须"重新"
检视一次纪录所导致,所以... 如果没有特别理由,还是不要用 --squash 噜~

原文:大专栏  [Git] 使用 Git SubTree 来共享函示库源代码
























以上是关于[Git] 使用 Git SubTree 来共享函示库源代码的主要内容,如果未能解决你的问题,请参考以下文章

git submodule subtree常用指令

git subtree 使用

git subtree:无缝管理通用子项目

使用 git-subtree 添加远程仓库的子目录

Git Subtree 只有一个文件或目录

Git应用详解第十讲:Git子库:submodule与subtree