如何使用Git分支和Rails迁移


131

我正在使用具有许多git分支的Rails应用程序,其中许多分支包括数据库迁移。我们尝试要小心,但有时master上的某些代码要求在另一分支中删除/重命名的列。

  1. 将git分支与DB状态“耦合”在一起的最佳解决方案是什么?

  2. 这些“状态”实际上是什么?

    如果数据库大小只有几个GB,我们就不能复制数据库。

  3. 合并应该发生什么?

  4. 该解决方案也可以转换为noSQL数据库吗?

    我们目前使用MySQL,mongodb和redis


编辑:看来我忘了提到一个非常重要的观点,我只对开发环境感兴趣,但是对大型数据库(大小为几GB)感兴趣。


您正在做什么,您有一个运行您的master分支的环境,该分支的数据库可以被其他分支修改?我不了解您的工作流程是什么,或者为什么您认为需要使分支与特定数据库保持同步。
约拿(Jonah)

3
假设我们在数据库中有一个包含客户(名称,电子邮件,电话)的表,在分支中,我们拆分了其中一列(名称-> first_name + last_name)。在我们将分支与母版合并之前,母版和基于它的所有其他分支都将失败。
Kostas,

Answers:


64

在任何分支中添加新迁移时,请运行rake db:migrate并提交迁移 db/schema.rb

如果这样做,在开发中,您将能够切换到另一个具有不同迁移集的分支,只需运行即可rake db:schema:load

注意,这将重新创建整个数据库,并且现有数据将丢失

您可能只想在非常小心的一个分支上运行生产,因此这些步骤不适用于该分支(只需在rake db:migrate那里正常运行即可)。但是在开发中,从架构中重新创建数据库应该没什么大不了的,这rake db:schema:load将是要做的。


5
我认为这只能解决架构问题,每次向下迁移时数据都将丢失,再也看不到。保存某种类型的db-data-patch是一个好主意,该db-data-patch在移出分支时会被保存,而另一种在移入另一个分支时会被加载?修补程序应仅包含在途中丢失(迁移)的数据。
Kostas,

4
如果要加载数据,请使用db/seeds.rb 它。如果在其中设置了一些合理的种子数据,则不要破坏开发数据库。
安迪·林德曼

无需核可任何东西。请参阅下面的我的解决方案。请注意,您将有许多实例,并且在切换分支时,数据不存在。如果您正在使用测试进行开发,那完全没问题。
亚当·迪米特鲁克

谢谢安迪,这个答案也是我的问题。并且同意db/seeds.rb用于重新填充丢失的数据库数据
pastullo 2014年

对于大型复杂的应用程序,您需要在本地重现真实的错误,使用种子文件绝对是不可能的,您需要生产(或暂存)中的真实数据。恢复数据库可能需要花费相当长的时间,因此对于我的情况,这不是一个好的解决方案。
Joel_Blum

21

如果您有一个无法轻易复制的大型数据库,那么我建议您使用常规的迁移工具。如果您想要一个简单的过程,这就是我的建议:

  • 在切换分支之前,将(rake db:rollback)回滚到分支点之前的状态。然后,在切换分支后,运行db:migrate。这在数学上是正确的,并且只要您编写down脚本,它就可以工作。
  • 如果您忘记在切换分支之前执行此操作,通常可以安全地进行切换,回滚和再次切换,因此我认为作为工作流是可行的。
  • 如果您在不同分支中的迁移之间存在依赖关系...那么,您将必须认真考虑。

2
您必须记住,并非所有迁移都是可逆的,也就是说,建议的第一步并不能保证成功。我认为,在开发环境是一个好主意是使用rake db:schema:loadrake db:seed作为@noodl所说的。
pisaruk

@pisaruk我知道您在六年前回答了这个问题,但是阅读我很好奇一个不可逆转的迁移示例。我很难想象一种情况。我猜最简单的方法是删除包含一堆数据的列,但是可以将其“反转”以具有空列或具有某些默认值的列。您是否还在考虑其他情况?
卢克·格里菲思

1
我想您回答了您自己的问题!是的,删除列是一个很好的例子。或破坏性的数据迁移。
ndp '18

13

这是我编写的用于在包含不同迁移的分支之间进行切换的脚本:

https://gist.github.com/4076864

它不会解决您提到的所有问题,但是给定分支名称,它将:

  1. 回滚当前分支上给定分支上不存在的所有迁移
  2. 放弃对db / schema.rb文件的任何更改
  3. 签出给定的分支
  4. 运行给定分支中现有的所有新迁移
  5. 更新您的测试数据库

我发现自己一直在我们的项目中手动执行此操作,因此我认为自动化该过程会很好。


1
该脚本完全可以完成我想做的事情,我很乐意将其放入自动结帐挂钩中。
brysgo 2014年

1
就在这时,我分叉了您的要点,并使其成为结帐后的钩子:gist.github.com/brysgo/9980344
brysgo 2014年

在您的脚本中,您是要说git checkout db/schema.rb还是要说git checkout -- db/schema.rb?(即带有双破折号)
user664833 2014年

1
是的...我当时还不知道双破折号。但是,除非您有一个名为的分支,否则该命令将起作用db/schema.rb。:)
乔恩·莱蒙2014年

@brysgo的进化git_rails命令(github.com/brysgo/git-rails)很好用。谢谢乔恩(Jon :)
Zia Ul Rehman Mughal

7

每个分支都有独立的数据库

这是唯一的飞行方式。

2017年10月16日更新

一段时间后,我返回到此页面并进行了一些改进:

  • 我添加了另一个名称空间rake任务来创建一个分支,并使用来一次克隆数据库bundle exec rake git:branch
  • 现在,我意识到从master克隆并不总是您想要做的,因此我更加明确地说明了该db:clone_from_branch任务采用SOURCE_BRANCHTARGET_BRANCH环境变量。使用git:branch时会自动将当前分支用作SOURCE_BRANCH
  • 重构和简化。

config/database.yml

为了使您更轻松,这里介绍了如何更新database.yml文件以根据当前分支动态确定数据库名称。

<% 
database_prefix = 'your_app_name'
environments    = %W( development test ) 
current_branch  = `git status | head -1`.to_s.gsub('On branch ','').chomp
%>

defaults: &defaults
  pool: 5
  adapter: mysql2
  encoding: utf8
  reconnect: false
  username: root
  password:
  host: localhost

<% environments.each do |environment| %>  

<%= environment %>:
  <<: *defaults
  database: <%= [ database_prefix, current_branch, environment ].join('_') %>
<% end %>

lib/tasks/db.rake

这是一项Rake任务,可以轻松地将数据库从一个分支克隆到另一个分支。这需要一个SOURCE_BRANCHTARGET_BRANCH环境变量。基于@spalladino的任务。

namespace :db do

  desc "Clones database from another branch as specified by `SOURCE_BRANCH` and `TARGET_BRANCH` env params."
  task :clone_from_branch do

    abort "You need to provide a SOURCE_BRANCH to clone from as an environment variable." if ENV['SOURCE_BRANCH'].blank?
    abort "You need to provide a TARGET_BRANCH to clone to as an environment variable."   if ENV['TARGET_BRANCH'].blank?

    database_configuration = Rails.configuration.database_configuration[Rails.env]
    current_database_name = database_configuration["database"]

    source_db = current_database_name.sub(CURRENT_BRANCH, ENV['SOURCE_BRANCH'])
    target_db = current_database_name.sub(CURRENT_BRANCH, ENV['TARGET_BRANCH'])

    mysql_opts =  "-u #{database_configuration['username']} "
    mysql_opts << "--password=\"#{database_configuration['password']}\" " if database_configuration['password'].presence

    `mysqlshow #{mysql_opts} | grep "#{source_db}"`
    raise "Source database #{source_db} not found" if $?.to_i != 0

    `mysqlshow #{mysql_opts} | grep "#{target_db}"`
    raise "Target database #{target_db} already exists" if $?.to_i == 0

    puts "Creating empty database #{target_db}"
    `mysql #{mysql_opts} -e "CREATE DATABASE #{target_db}"`

    puts "Copying #{source_db} into #{target_db}"
    `mysqldump #{mysql_opts} #{source_db} | mysql #{mysql_opts} #{target_db}`

  end

end

lib/tasks/git.rake

此任务将在当前分支(主分支或其他分支)的基础上创建一个git分支,将其检出并将当前分支的数据库克隆到新分支的数据库中。光滑的AF。

namespace :git do

  desc "Create a branch off the current branch and clone the current branch's database."
  task :branch do 
    print 'New Branch Name: '
    new_branch_name = STDIN.gets.strip 

    CURRENT_BRANCH = `git status | head -1`.to_s.gsub('On branch ','').chomp

    say "Creating new branch and checking it out..."
    sh "git co -b #{new_branch_name}"

    say "Cloning database from #{CURRENT_BRANCH}..."

    ENV['SOURCE_BRANCH'] = CURRENT_BRANCH # Set source to be the current branch for clone_from_branch task.
    ENV['TARGET_BRANCH'] = new_branch_name
    Rake::Task['db:clone_from_branch'].invoke

    say "All done!"
  end

end

现在,您所需要做的就是运行bundle exec git:branch,输入新的分支名称并开始杀死僵尸。


4

也许您应该以此来暗示您的开发数据库太大了?如果可以使用db / seeds.rb和较小的数据集进行开发,则可以使用当前分支中的schema.rb和seed.rb轻松解决问题。

假设您的问题与发展有关;我无法想象为什么您需要在生产中定期切换分支机构。


我不知道db/seeds.rb,我来看看。
Kostas,

3

我在同一个问题上挣扎。这是我的解决方案:

  1. 确保所有开发人员都已检入schema.rb和所有迁移。

  2. 应该只有一个人/一台机器才能部署到生产环境。我们将此机器称为合并机器。将更改拉到合并计算机时,schema.rb的自动合并将失败。没有问题。只需将内容替换为schema.rb的先前内容即可(您可以将副本放在一边,或者如果使用它,则可以从github获取副本...)。

  3. 这是重要的一步。现在,所有开发人员的迁移都将在db / migrate文件夹中提供。继续运行bundle exec rake db:migrate。它将使合并机器上的数据库与所有更改保持一致。它还将重新生成schema.rb。

  4. 提交更改并将其发布到所有存储库(远程存储和个人,也都是远程存储库)。你应该完成!


3

这是我所做的,而且我不确定我是否已涵盖所有基础知识:

在开发中(使用postgresql):

  • sql_dump db_name> tmp / branch1.s​​ql
  • git checkout branch2
  • dropdb db_name
  • createdb db_name
  • psql db_name <tmp / branch2.sql#(来自先前的分支开关)

这比具有约5万条记录的数据库中的rake实用程序要快得多。

为了进行生产,请将master分支保持为神圣不可侵犯,并检入所有迁移,并正确合并shema.rb。完成您的标准升级过程。


对于足够小的数据库大小,在签出分支时在后台完成此操作似乎是一个非常好的解决方案。
科斯塔斯

2

您想为每个分支保留一个“数据库环境”。查看污迹/清除脚本以指向不同的实例。如果您用完了数据库实例,请让脚本剥离一个临时实例,以便当您切换到新分支时,该分支已经存在,只需要通过脚本重命名即可。数据库更新应该在执行测试之前运行。

希望这可以帮助。


此解决方案仅对“临时”分支有用。例如,如果我们有一个分支“ edge”,我们在测试各种疯狂的东西(可能与其他子分支),然后不时将其合并到master,则这两个数据库将分开(它们的数据不会是相同的)。
Kostas,

对于相反的情况,此解决方案很有用。如果您对数据库版本脚本进行版本控制,那么这是一个非常好的解决方案。
亚当·迪米特鲁克

2

我完全体验了你在这里吃的皮塔饼。在我看来,真正的问题是所有分支都没有回滚某些分支的代码。我在django世界中,所以我不太了解rake。我玩弄的想法是迁移存在于不分支的自己的仓库中(git-submodule,我最近了解到)。这样,所有分支机构都可以进行所有迁移。棘手的部分是确保每个分支都仅限于他们关心的迁移。手动执行/跟踪该操作很容易出错。但是,没有为此目的而构建的迁移工具。那是我没有前进道路的地方。


这是一个好主意,但是当分支重命名列时会发生什么?其余的分支机构将看一张破表。
Kostas,

嗯-这是最棘手的部分-该分支关心哪些迁移。因此您可以进行“同步”,并且知道“还原此迁移”,因此该列可以返回。
JohnO 2011年

1

我建议以下两种选择之一:

选项1

  1. 将数据放入seeds.rb。一个不错的选择是通过FactoryGirl / Fabrication gem创建您的种子数据。这样,如果我们假设,工厂可以与列的添加/删除一起进行更新,则可以保证数据与代码同步。
  2. 从一个分支切换到另一个分支后,运行rake db:reset,可以有效地删除/创建/设置数据库。

选项2

一直运行手动维护数据库的状态rake db:rollback/ rake db:migrate分支结账前/后。请注意,您的所有迁移都必须是可逆的,否则将无法正常工作。


0

关于开发环境:

您应该使用它rake db:migrate:redo来测试您的脚本是否可逆,但是请记住,始终要seed.rb对数据进行填充。

如果您使用git,则您的seed.rb应该随着迁移的变化而变化,并且从头db:migrate:redo开始执行(将数据用于其他机器或新数据库上的新开发)

除了“ change”之外,借助您上下的方法,您的代码始终可以覆盖当前以及从零开始的“ change”情况。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.