最近我们开始使用Liquibase。虽然还没有发生,但我们想象如果两个开发人员将更改日志文件中的更改提交到共享的Git存储库会发生什么。
如何解决或避免合并冲突?把这个问题扩展一下:
Liquibase与Git结合使用的推荐工作流程是什么?
示例场景:
- Michael修改了table 'customer'中的一列。
- Jacob更改了表'account'中的列。
因此,两个开发人员都将<changeSet>
添加到相同的更改日志文件changelog.xml中。
正如评论的那样,这个场景确实没有那么令人兴奋。假设雅各布是最后一个发布代码的人。他得先拉。获取有要解决的合并冲突的警告。他通过保留代码的两个部分(Michael的和他的)来解决冲突。用Liquibase更新数据库没有问题。
高级示例场景:
-Michael修改了表'customer'中'first_name'的列'name',提交和推送。
-Jacob修改了'last_name'中'customer'表的'name'列的名称并提交。
-Jacob在提取Michael的代码时遇到了合并冲突。
-Jacob和Michael讨论了冲突,并同意必须是'last_name', Jacob提交并推动了。
michael拉出解决的冲突并运行Liquibase更新。他得到一个错误:column "name" does not exist
在我的公司,我们使用liquidbase的方式防止了这些情况的发生。基本上,您为每个更改创建一个单独的liquibase文件。我们以发起更改的JIRA票据命名这些文件,并添加一些描述性文本。每个文件,我们都把它们放在对应系统版本的文件夹中;如果下一个版本是1.22,那么当我们开始对数据库进行更改时,就会创建这个文件夹,我们将每个liquibase文件放在那里,并附带一个update.xml脚本,其中只包含这些文件。这个update.xml文件最终成为唯一真正可能发生冲突的地方,而且解决冲突很简单。
为了说明,这是src/main/liquibase
文件夹:
├── install
│ ├── projectauthor.xml
│ ├── project_obspriorities.xml
│ ├── project_priorities.xml
│ ├── project_udv.xml
│ ├── project.xml
│ ├── roles.xml
│ ├── scan.xml
│ ├── (the other table definitions in the system go here)
│
├── install.xml <-- this reads all the files in ./install
│
├── local.properties <--
├── prod.properties <-- these are database credentials (boo, hiss)
├── staging.properties <--
├── test.properties <--
│
├── update.xml <-- reads each version/master.xml file
│
├── v1.16
│ ├── 2013-06-06_EVL-2240.xml
│ ├── 2013-07-01_EVL-2286-remove-invalid-name-characters.xml
│ ├── 2013-07-02_defer-coauthor-projectauthor-unique-constraint.xml
│ └── master.xml
├── v1.17
│ ├── 2013-07-19_EVL-2295.xml
│ ├── 2013-09-11_EVL-2370_otf-mosaicking.xml
│ └── master.xml
├── v1.18
│ ├── 2014-05-05_EVL-2326-remove-prerequisite-construct.xml
│ ├── 2014-06-03_EVL-2750_fix-p-band-polarizations.xml
│ └── master.xml
install.xml文件只是一堆文件包含:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
<include file="src/main/liquibase/project/install/projectauthor.xml"/>
<include file="src/main/liquibase/project/install/project_obspriorities.xml"/>
...
</databaseChangeLog>
update.xml文件也是如此:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd">
<include file="src/main/liquibase/project/v1.18/master.xml"/>
</databaseChangeLog>
我不喜欢的工作流程的一个方面是,install/*.xml应该创建数据库,就像它在当前版本之前一样,但我们通常不记得这样做。
无论如何,这种方法将为您省去许多合并的麻烦。我们使用的是Subversion,使用这种方法没有任何合并困难。
总是存在需要手动处理的边缘情况,但它们通常很少发生。Git通常会在文本级别很好地处理变更合并,所以合并后的文件中会有两个changeset,一个接一个。
由于liquidbase通过id/author/filename跟踪changeSet,因此在最终的changeSet中Jacob的changeSet恰好在Michaels的changeSet之前结束的事实并不重要。当两个开发人员都运行最终的changeSet时,Liquibase将运行另一个开发人员的changeSet,因为他们的changeSet已被标记为已运行,而另一个没有。对于所有其他环境,两个变更集都将运行。
您的高级案例遇到了问题,因为两个开发人员都在进行相互矛盾的更改。如果两个开发人员都删除一个列,或者添加一个具有相同名称的新列,也可能遇到类似的问题。它也不总是一个开发人员与另一个开发人员之间的简单冲突,有时冲突的变更集来自合并的两个独立的功能分支。合并的变更集本身没有物理问题,问题是新的变更日志在逻辑上不正确。这不是git的问题,这是逻辑问题。
在实践中,这种类型的冲突很少发生,因为不同的开发人员和不同的分支通常在代码库的不同区域工作,当有潜在的冲突时,他们通过沟通和计划来处理它。
如果你遇到冲突,有几种方法可以解决它。通常这是通过删除不正确或重复的changeSet来处理的(就像在你的例子中一样),但也可以通过创建一个全新的changeSet来处理,它是两者的组合。在任何一种情况下,您都需要处理运行了"错误"changeSet的数据库。如何最好地处理它取决于有多少系统运行它。
如果是单个开发人员,有时最简单的方法是简单地运行liquibase changeLogSync
以将新的changeSet标记为已运行,并在数据库中手动进行更改。如果最近运行了错误的变更集,他们甚至可以运行liquibase rollbackCount X
来恢复错误的变更,然后删除变更集,然后运行liquibase update
如果有多个冲突和/或多个系统运行了问题变更集,最简单的方法通常是使用<preConditions onFail="MARK_RAN"><changeSetExecuted id=....></preConditions>
标签。您可以删除坏的changeSet,并添加一个新的changeSet,它只在旧的changeSet被执行时运行,并将数据库放回到以后的changeSet所期望的状态。在您的示例中,它将把first_name重命名回name,以便将名称改为last_name changeSet可以正常工作。
TL;DR:将/path/to/changelogfile.xml merge=union
添加到。gitattributes文件中。
我看过一些关于单独文件的讨论。有两种方法可以在liquibase中保存单独的文件:
- 使用
includeAll
标签,让新文件继续推送到文件夹。 - 在主文件中使用
include
标签来指定顺序。
这两种方法仍然会导致问题:(1)includeAll将只按照文件名的字典顺序选择文件,如果每次有新的更改提交时都执行changelog,这将工作得很好,但如果你想部署到一个全新的文件,这将失败。除非你有一个命名策略来决定顺序。但是,如果Michael和Jacob同时工作,这仍然是一个问题:他们可能会使用相同的名字,从而导致冲突。Michael和Jacob都需要编辑主文件的最后几行。换句话说:合并冲突!
实际的解决方案是不要改变你的流动性策略。只要保持你到目前为止一直遵循的策略。这里的问题根源在于源代码控制和合并机制。liquibase变更日志文件本质上是一个历史记录。可以把它想象成一个发布说明文件,开发人员只是不断地将他们的工作添加到文件的最后一行。在这种情况下,如果两个开发人员添加两行添加结束并不重要,无论顺序如何,都应该添加。因此,解决方案是在git项目的根文件夹中创建一个.gitattributes文件(如果还没有的话),并添加以下行:
/path/to/changelogfile.xml merge=union
这仍然不是失败-盈利。为了解决高级示例场景,我想不出任何自动策略或工作流。开发人员应该在接触相同的表(或相同的db对象)时进行通信。这个场景不仅是"文件"冲突,而且是语义冲突。这时一个好的CI就派上了用场。最后提交并合并代码的开发人员将得到错误,并且需要与最先赢得比赛的开发人员取得联系。
注意,也许你的Git门户不支持合并策略,你仍然会在拉请求(又名合并请求)上得到合并冲突。但是,当您看到这一点并尝试合并或重新定位以解决冲突时,冲突已经解决了。我知道GitLab确实支持它。不确定Github或BitBucket。
我不知道liquidbase,但我认为错误是在"Jacob和Michael讨论了冲突,并同意必须是'last_name', Jacob提交并推送。"
变更日志必须与将要执行的操作(或已经在数据库上执行的操作)相匹配。也就是说,你不能删除推送的更改。
因此正确的解析是name->first_name AND first_name->last_name