如何用 Git 合并两个库(合并历史记录,解决冲突/改写路径)

盐柱语法
• 阅读 11403

本来已经不写文字博客了,一般心得都录成了视频(这在我看来是更好的方式),但是今天遇到一个关于 Git 的问题不太好重现也不便于录制视频,加上它本身很具有代表性也很有用,所以还是记录于此。

背景

一个中型规模项目,开始规划时就打算采用 C/S 架构,后端是单纯的 API 服务,前端在 Web 上搞一个 SPA,之后再搞其他端也就顺理成章了。只可以第一次弄没经验,有些细节最初没有考虑到。

创建项目的时候前后端真是完全分离的,分成了两个目录,创建了两个 repos。一开始只有一个人干的时候倒也没什么,开两个窗口切来切去也就罢了,后来一是部署起来麻烦,二来主要是其他开发者加入后,代码的版本管理、提交、合并、审核等等等等都变得越来越繁琐。

后来一想:架构上分离而已,干嘛非要两个目录两个 repos?真是自找麻烦!于是就开始考虑整合。

要求

把两个目录并成一个倒不难,但是要完整保留双方的历史记录就有些麻烦了,这也是唯一一个必须要实现的目标。

过程

首先为了便于描述,约定整合前两个目录分别叫做 frontendbackend,合并后的结构与名称应当如下:

- project/                  => 即最开始的 frontend,整合完后更名
  - .gitignore              => 合并两个 repos 的忽略文件
  - .git/                   => 最终仅余一个 repo
  + client/                 => 对应 frontend
  + server/                 => 对应 backend

以下步骤是以 frontend 为基点,把 backend 移进来,实际上反过来也是一样的,自行替换对应的名称即可。在开始之前先清理两个 repos 里的工作记录,该提交的提交,该备份的备份,保持干净。

1. $ [~] cd frontend
2. $ [frontend] git remote add -f backend /fullpath/to/backend
3. $ [frontend] git merge --strategy ours --no-commit backend/master
4. $ [frontend] mkdir -p server
5. $ [frontend] git read-tree --prefix=server/ -u backend/master
6. $ [frontend] git commit --message '完成 backend 的迁移,新目录为 server'
7. $ [frontend] mkdir -p client
8. # 拷贝 frontend 的原始项目文件(除了 .git/ 和 .gitignore 以外)至 client/
9. $ [frontend] cd ..; mv frontend/ project/; cd project
10. $ [project] cat server/.gitignore >> .gitignore
11. # 整理合并后的 .gitignore,修复其中的路径缺失并保存;修复各种项目依赖的缺失,本地测试。
12. $ [project] git add --all; git commit --message '迁移整合完成!'

以上是完整的步骤先列出来方便参考,下面做一个详细的解释。

整个过程中主要用到的工具是 mergeread-tree,前者用于合并历史记录并且中断在最后提交之前,所产生的文件冲突不会被写入硬盘;然后利用后者重写整个文件树并把读取到的内容(读取的目标是 backend)写入新的路径下。最后提交以结束合并。

2步里,我们把 backend 作为 remote server 添加到 frontend 库中。-f 的作用是在添加后立刻 fetch。要注意一定得使用绝对路径来引用 backend 库。

3步里,--strategy ours 比较难以理解,且听我详细道来:一般来说当合并两个文件树时,如果遇到冲突我们是需要手动去解决它的,但是目前我们要做的不是解决冲突,而是在引入 backend 历史记录的前提下完整保留 frontend 的内容。冲突肯定是会有的,即使两个不同的项目也是如此,比方说两边都有 README.mdapp/config/ 等文件或目录,但是我们不关心冲突,我们只要保留 frontend 的文件树并且把 backend 的历史记录合并进来。

--strategy ours 会完成全部的合并解析,但是所有的冲突都以“我”为准,不允许外来的冲突覆盖“我”的文件内容。最终的结果就是:

  1. backend 的历史记录被合并到 frontend 的历史记录中
  2. backend 的文件树被读取并和 frontend 的文件树比对进行冲突解析:

    • 如果发现冲突,以 frontend 为准,丢弃所有内容变更
    • 没冲突的则保留(但是我们也不要的,见后面的内容)

这也是后面紧接着使用 --no-commit 的原因,该选项会在合并解析完成后中断,停留在最后的提交步骤之前。我们知道,只要你还没 commit,那么 merge 的结果就暂时保存在缓存区中,只有完成提交步骤合并才算彻底完成(文件树被正式改变)。这就给我们一个机会来重新读取 backend 的文件树,并改写其保存的位置。不过在此之前,第4步先要创建目标子目录(很重要!)。

5步开始 read-tree 了,--prefix 用于指定文件树读取后保存的路径,相对于当前路径并且一定要追加 /-u 是说在读取后更新 index,使得 working treeindex 保持同步。如果你不小心忘了加 -u,可以在这一步之后执行 git add --update,一样的效果。

这一步在背后有些细节比较抽象,之前的 merge 也曾读取过 backend 的文件树,但经过冲突解析之后已经面目全非,分析如下:

  • 有冲突的被丢弃,因此一部分文件/目录其实已经不存在了
  • 没冲突的被保留,但是路径还在 frontend 的根路径下

经过再次 read-tree,上面的“遗迹”得以修复,结果如下:

  • 有冲突的因为已被丢弃,所以直接从本次读取中获得,且路径前面追加 --prefix 选项的值
  • 没冲突的虽然被保留,但是由于本次读取追加了 prefix,所以它们的路径也被改变,相当于在缓存里做了一次 git mv

好了,重点就是这些,之后的步骤都很寻常,只要小心操作就没什么难理解的。

点赞
收藏
评论区
推荐文章
放学路上 放学路上
3年前
git常见合并冲突场景以及解决办法
git很好用,但是遇到合并冲突就会很痛苦!在此之前我一直都是个人开发者,因此对git的使用也不熟悉,本文将持续总结我在使用git合并过程中遇到的冲突的场景以及产生冲突后的解决办法。如果有不足的的地方还请各位大佬指正!一、第一种情况1、场景描述:当两人都拉取了同一个分支的代码,然后两人都对代码进行了修改,假设其中一人把他修改完的代码上传到master分支,这时
Stella981 Stella981
3年前
Git之SSH公钥与私钥
今天来探讨一下如何使用Git的操作,来进一步的实现代码的下载到本地,我原来也不是很明白git中生成公钥和私钥的作用,我一直在想,git里面你把自己的公钥发给了git的服务器,他是怎么判断的,每一次我换完系统,在重新装机,他是怎么确定是换完系统还是依然可以使用git的,今天我们就来讲解一下最为简单的SSh的方法的clone项目的操作:1.下载git的客户端
Stella981 Stella981
3年前
JGit与远程仓库链接使用的两种验证方式(ssh和https)
JGit是使用JAVA的API来操控Git仓库的库,由Eclipse公司维护。他提供的API分成两个层次,底层命令和高层命令。底层API是直接作用于低级的仓库对象,高层的API是一个面向普通用户级别功能友好的前端。JGit主要通过SSH和HTTP(S)的方式与远程仓库进行交互,此外也可以用Git协议(只读)。通过这两种方式,必然是需要添加验证信息的。介
Stella981 Stella981
3年前
Gitolite 构建 Git 服务器
如果不是要和他人协同开发,Git根本就不需要架设服务器。Git在本地可以直接使用本地版本库的路径完成git版本库间的操作。但是如果需要和他人分享版本库、协作开发,就需要能够通过特定的网络协议操作Git库。Git支持的协议很丰富,架设服务器的选择也很多,不同的方案有着各自的优缺点。 HTTPGITDAEM
Wesley13 Wesley13
3年前
Git中代码冲突的解决方式
使用Git管理代码,在进行代码提交的时候,如果出现了冲突该怎么办呢,下面就两种常见的冲突给出了解决方法,希望对大家有所帮助。冲突1:当你commit以后,在执行gitpullrebase的时候出现冲突,请按如下步骤解决:1 找到冲突文件,解决冲突2 执行gitaddxxx(xxx为冲突文件全路径)3 执行git
Stella981 Stella981
3年前
Git客户端图文详解如何安装配置GitHub操作流程攻略
Git介绍分布式:Git版本控制系统是一个分布式的系统,是用来保存工程源代码历史状态的命令行工具;保存点:Git的保存点可以追踪源码中的文件,并能得到某一个时间点上的整个工程项目额状态;可以在该保存点将多人提交的源码合并,也可以会退到某一个保存点上;Git离线操作性:Git可以离线进行代码提交,因此它称得上是完全的
Stella981 Stella981
3年前
Git 版本控制管理(一)
  Git是一个分布式版本控制工具,它的作者 LinusTorvalds 是这样给我们介绍Git —— Thestupidcontenttracker(傻瓜式的内容跟踪器)关于Git的产生背景在此不做讲解,有兴趣的可以搜索一下。先介绍一下Git的特点,主要有两大特点:版本控制:可以解决多人同时开发的代码问题,也可以解决找
Stella981 Stella981
3年前
GitFlow原理浅析
一、Git优点分布式存储,本地仓库包含了远程仓库的所有内容.安全性高,远程仓库文件丢失了也不怕优秀的分支模型,创建/合并分支非常的方便方便快速,由于代码本地都有存储,所以从远程拉取和分支合并时都非常快捷当分支过多时,如何管理这些分支呢?我们团队采用了GitFlow的模式
住儿 住儿
2年前
Git Rebase
什么是gitrebase?gitrebase是将更改从一个分支合并到另一个分支的两个git过程之一(另一个是gitmerge)。虽然merge总是可以简单的地合并分支,但是
绣鸾 绣鸾
1年前
Fork for Mac(Git客户端)
是一款适用于Mac平台的Git客户端,它提供了一系列强大的功能,包括分支管理、代码比较、合并和冲突解决等。以下是Fork的一些特点和优点:直观的用户界面:Fork具有直观的用户界面和简单易用的操作,可以帮助用户快速地学习和使用Git。分支管理:该软件提供了
陈哥聊测试 陈哥聊测试
9个月前
情绪稳定!别再让Git合并冲突影响你工作了
在我看来,Git合并冲突是不可避免的。在本文,我想和大家简单分享一下遇到Git冲突该如何解决,希望对大家有所帮助。