public
Description: Rake tasks for working with migrations in a typical git branching workflow.
Homepage:
Clone URL: git://github.com/technoweenie/rails_migration_buddy.git
100644 102 lines (96 sloc) 3.693 kb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
namespace :git do
  task :init do
    begin
      require 'grit'
    rescue LoadError
      puts "Oops!"
      puts "sudo gem install grit"
    end
    require 'pathname'
    @repo = Grit::Repo.new RAILS_ROOT
    @run = !ENV['RUN'].nil?
 
    @status = `git status`.split("\n")
    @git_branch = `git branch`.split("\n")
    ENV['MASTER'] ||= 'master'
    @git_branch.each { |b| ENV['BRANCH'] = b[2..-1] if b[0..0] == '*' } unless ENV['BRANCH']
    raise "Need a branch name: BRANCH=mychanges" unless ENV['BRANCH']
    @master, @branch = filter_migrations ENV['MASTER'], ENV['BRANCH']
    unless @status.any? { |s| s =~ /^# On branch #{ENV['BRANCH']}/ }
      puts "Hey, you're not on the #{ENV['BRANCH'].inspect} branch, what are you trying to pull anyway?"
      puts
      puts @git_branch
      exit
    end
    unless @status.any? { |s| s =~ /\(working directory clean\)/ }
      puts "Hey, your working directory isn't clean, what are you trying to pull anyway?"
      puts
      puts @status
      exit
    end
  end
 
  task :rebase_migrations => :init do
    if @master.empty?
      puts "No new migrations in #{ENV['MASTER'].inspect}"
      if @branch.empty?
        puts "No new migrations in #{ENV['BRANCH'].inspect} either, you're clear!"
      else
        puts "#{@branch.size} new migration(s) in #{ENV['BRANCH'].inspect}: #{@branch.inspect}."
        puts "You should 'rake db:migrate VERSION=#{lowest_migration_number(@branch) - 1}' before merging with #{ENV['MASTER'].inspect}"
      end
    else
      puts "#{@master.size} new migration(s) in #{ENV['MASTER'].inspect}: #{@master.inspect}."
      if @branch.empty?
        puts "No new migrations in #{ENV['BRANCH'].inspect}. Just remember to 'rake db:migrate' AFTER merging with #{ENV['MASTER'].inspect}."
      else
        operations = renumber_migrations_with_offset(@branch, @master.size)
        puts "#{@branch.size} new migration(s) in #{ENV['BRANCH'].inspect}: #{@branch.inspect}."
        puts
        puts "#{'Suggested ' unless @run}Renumbering..."
        root = Pathname.new(RAILS_ROOT)
        migrate = root + 'db/migrate'
        operations.sort_by { |(old, new)| old }.each do |(old, new)|
          cmd = "git mv #{(migrate + old).relative_path_from(root)} #{(migrate + new).relative_path_from(root)}"
          puts cmd
          system cmd if @run
        end
        puts
        if @run
          puts "Migrations rebased! Next actions:"
          puts " rake db:migrate VERSION=#{lowest_migration_number(@branch) - 1}"
          puts " git commit -m '...'"
          puts " git checkout #{ENV['MASTER']}"
          puts " git merge #{ENV['BRANCH']}"
          puts " rake db:migrate"
          puts
          puts 'Have a nice day!'
        else
          puts "Run with RUN=1 to actually rename the files."
        end
      end
    end
  end
  
  def lowest_migration_number(migrations)
    migrations.collect { |m| m.to_i }.min
  end
  
  def renumber_migrations_with_offset(migrations, offset)
    migrations.inject({}) do |hash, m|
      num, *base = m.split("_")
      base.unshift "%03d" % (num.to_i + offset)
      hash.update m => base.join("_")
    end
  end
  
  def migrations_for(branch_name)
    head = @repo.tree(branch_name)
    tree = head / 'db' / 'migrate'
    tree.contents.collect { |c| c.name }.delete_if { |n| n !~ /^\d+_[^\.]+.rb$/ }.sort
  rescue
    puts "Trouble with the #{branch_name} branch..."
    raise
  end
  
  def filter_migrations(master, branch)
    master_migrations = migrations_for(master)
    branch_migrations = migrations_for(branch)
    common = master_migrations & branch_migrations
    [master_migrations - common, branch_migrations - common]
  end
end