OSDN Git Service

Create Wiki migration task.
authorDan Knox <dknox@threedotloft.com>
Mon, 11 Mar 2013 02:10:44 +0000 (19:10 -0700)
committerDan Knox <dknox@threedotloft.com>
Mon, 11 Mar 2013 02:10:44 +0000 (19:10 -0700)
This commit adds a new Rake task for migrating all of your existing
Wiki content from your database into new Gollum repositories.

The bulk of the logic happens within the `WikiToGollumMigrator`
class which is decently test covered and located in the lib directory.

The new Rake task can be executed by running:

   `bundle exec rake gitlab:wiki:migrate`

It will output a nice log of every project that it migrates along
with success or failure messages.

I have used it on my own installation to migrate my Wikis successfully.

lib/tasks/gitlab/migrate_wiki.rake [new file with mode: 0644]
lib/wiki_to_gollum_migrator.rb [new file with mode: 0644]
spec/lib/wiki_to_gollum_migrator_spec.rb [new file with mode: 0644]

diff --git a/lib/tasks/gitlab/migrate_wiki.rake b/lib/tasks/gitlab/migrate_wiki.rake
new file mode 100644 (file)
index 0000000..9b2f34c
--- /dev/null
@@ -0,0 +1,20 @@
+namespace :gitlab do
+  namespace :wiki do
+
+    # This task will migrate all of the existing Wiki
+    # content stored in your database into the new
+    # Gollum Wiki system. A new repository named
+    # namespace/project.wiki.git will be created for
+    # each project that currently has Wiki pages in
+    # the database.
+    #
+    # Notes:
+    #  * The existing Wiki content will remain in your
+    #    database in-tact.
+    desc "GITLAB | Migrate Wiki content from database to Gollum repositories."
+    task :migrate => :environment do
+      wiki_migrator = WikiToGollumMigrator.new
+      wiki_migrator.migrate!
+    end
+  end
+end
diff --git a/lib/wiki_to_gollum_migrator.rb b/lib/wiki_to_gollum_migrator.rb
new file mode 100644 (file)
index 0000000..6083533
--- /dev/null
@@ -0,0 +1,103 @@
+class WikiToGollumMigrator
+
+  attr_reader :projects
+
+  def initialize
+    @projects = []
+
+    Project.find_in_batches(batch_size: 50) do |batch|
+      batch.each { |p| @projects << p if p.wikis.any? }
+    end
+  end
+
+  def migrate!
+    projects.each do |project|
+      log "\nMigrating Wiki for '#{project.path_with_namespace}'"
+      wiki = create_gollum_repo(project)
+      create_pages project, wiki
+      log "Project '#{project.path_with_namespace}' migrated. " + "[OK]".green
+    end
+  end
+
+  private
+
+  def create_gollum_repo(project)
+    GollumWiki.new(project, nil).wiki
+  end
+
+  def create_pages(project, wiki)
+    pages = project.wikis.group(:slug).all
+
+    pages.each do |page|
+      create_page_and_revisions(project, page)
+    end
+  end
+
+  def create_page_and_revisions(project, page)
+    # Grab all revisions of the page
+    revisions = project.wikis.where(slug: page.slug).ordered.all
+
+    # Remove the first revision created from the array
+    # and use it to create the Gollum page. Each successive revision
+    # will then be applied to the new Gollum page as an update.
+    first_rev = revisions.pop
+
+    wiki = GollumWiki.new(project, page.user)
+    wiki_page = WikiPage.new(wiki)
+
+    attributes = extract_attributes_from_page(first_rev)
+
+    if wiki_page.create(attributes)
+      log "  Created page '#{wiki_page.title}' " + "[OK]".green
+
+      # Reverse the revisions to create them in the correct
+      # chronological order.
+      create_revisions(project, wiki_page, revisions.reverse)
+    else
+      log "  Failed to create page '#{wiki_page.title}' " + "[FAILED]".red
+    end
+  end
+
+  def create_revisions(project, page, revisions)
+    revisions.each do |revision|
+      log "    Creating revisions..."
+      # Reinitialize a new GollumWiki instance for each page
+      # and revision created so the correct User is shown in
+      # the commit message.
+      wiki = GollumWiki.new(project, revision.user)
+      wiki_page = wiki.find_page(page.slug)
+
+      attributes = extract_attributes_from_page(revision)
+
+      content = attributes[:content]
+
+      if wiki_page.update(content)
+        log "    Created revision " + "[OK]".green
+      else
+        log "    Failed to create revision " + "[FAILED]".red
+      end
+    end
+  end
+
+  def extract_attributes_from_page(page)
+    attributes = page.attributes
+                     .with_indifferent_access
+                     .slice(:title, :content)
+
+    # Change 'index' pages to 'home' pages to match Gollum standards
+    if attributes[:title].downcase == "index"
+      attributes[:title] = "home" unless home_already_exists?(project)
+    end
+
+    attributes
+  end
+
+  def home_already_exists?(project)
+    project.wikis.where(title: 'home').any? || project.wikis.where(title: 'Home').any?
+  end
+
+  def log(message)
+    puts message
+  end
+
+end
diff --git a/spec/lib/wiki_to_gollum_migrator_spec.rb b/spec/lib/wiki_to_gollum_migrator_spec.rb
new file mode 100644 (file)
index 0000000..a784d83
--- /dev/null
@@ -0,0 +1,114 @@
+require "spec_helper"
+
+describe WikiToGollumMigrator do
+
+  def create_wiki_for(project)
+    3.times { @pages[project.id] << create_page(project) }
+  end
+
+  def create_revisions_for(project)
+    @pages[project.id].each do |page|
+      create_revision(page)
+    end
+  end
+
+  def create_page(project)
+    page = project.wikis.new(title: "Page #{rand(1000)}", content: "Content")
+    page.user = project.owner
+    page.slug = page.title.parameterize
+    page.save!
+    page
+  end
+
+  def create_revision(page)
+    revision = page.dup
+    revision.content = "Updated Content"
+    revision.save!
+  end
+
+  def create_temp_repo(path)
+    FileUtils.mkdir_p path
+    command = "git init --quiet --bare #{path};"
+    system(command)
+  end
+
+  before do
+    @repo_path = "#{Rails.root}/tmp/test-git-base-path"
+    @projects = []
+    @pages = Hash.new {|h,k| h[k] = Array.new }
+
+    @projects << create(:project)
+    @projects << create(:project)
+
+    @projects.each do |project|
+      create_wiki_for project
+      create_revisions_for project
+    end
+
+    @project_without_wiki = create(:project)
+  end
+
+  context "Before the migration" do
+    it "has two projects with valid wikis" do
+      @projects.each do |project|
+        pages = project.wikis.group(:slug).all
+        pages.count.should == 3
+      end
+    end
+
+    it "has two revision for each page" do
+      @projects.each do |project|
+        @pages[project.id].each do |page|
+          revisions = project.wikis.where(slug: page.slug)
+          revisions.count.should == 2
+        end
+      end
+    end
+  end
+
+  describe "#initialize" do
+    it "finds all projects that have existing wiki pages" do
+      Project.count.should == 3
+      subject.projects.count.should == 2
+    end
+  end
+
+  context "#migrate!" do
+    before do
+      Gitlab::Shell.any_instance.stub(:add_repository) do |path|
+        create_temp_repo("#{@repo_path}/#{path}.git")
+      end
+
+      subject.stub(:log).as_null_object
+
+      subject.migrate!
+    end
+
+    it "creates a new Gollum Wiki for each project" do
+      @projects.each do |project|
+        wiki_path = project.path_with_namespace + ".wiki.git"
+        full_path = @repo_path + "/" + wiki_path
+        File.exist?(full_path).should be_true
+        File.directory?(full_path).should be_true
+      end
+    end
+
+    it "creates a gollum page for each unique Wiki page" do
+      @projects.each do |project|
+        wiki = GollumWiki.new(project, nil)
+        wiki.pages.count.should == 3
+      end
+    end
+
+    it "creates a new revision for each old revision of the page" do
+      @projects.each do |project|
+        wiki = GollumWiki.new(project, nil)
+        wiki.pages.each do |page|
+          page.versions.count.should == 2
+        end
+      end
+    end
+  end
+
+
+end