Commit f0aa54e0fbce27600aa02a1ee5465e2ab5c18ccc
1 parent
1479f172
Exists in
master
and in
4 other branches
Create Wiki migration task.
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.
Showing
3 changed files
with
237 additions
and
0 deletions
Show diff stats
| @@ -0,0 +1,20 @@ | @@ -0,0 +1,20 @@ | ||
| 1 | +namespace :gitlab do | ||
| 2 | + namespace :wiki do | ||
| 3 | + | ||
| 4 | + # This task will migrate all of the existing Wiki | ||
| 5 | + # content stored in your database into the new | ||
| 6 | + # Gollum Wiki system. A new repository named | ||
| 7 | + # namespace/project.wiki.git will be created for | ||
| 8 | + # each project that currently has Wiki pages in | ||
| 9 | + # the database. | ||
| 10 | + # | ||
| 11 | + # Notes: | ||
| 12 | + # * The existing Wiki content will remain in your | ||
| 13 | + # database in-tact. | ||
| 14 | + desc "GITLAB | Migrate Wiki content from database to Gollum repositories." | ||
| 15 | + task :migrate => :environment do | ||
| 16 | + wiki_migrator = WikiToGollumMigrator.new | ||
| 17 | + wiki_migrator.migrate! | ||
| 18 | + end | ||
| 19 | + end | ||
| 20 | +end |
| @@ -0,0 +1,103 @@ | @@ -0,0 +1,103 @@ | ||
| 1 | +class WikiToGollumMigrator | ||
| 2 | + | ||
| 3 | + attr_reader :projects | ||
| 4 | + | ||
| 5 | + def initialize | ||
| 6 | + @projects = [] | ||
| 7 | + | ||
| 8 | + Project.find_in_batches(batch_size: 50) do |batch| | ||
| 9 | + batch.each { |p| @projects << p if p.wikis.any? } | ||
| 10 | + end | ||
| 11 | + end | ||
| 12 | + | ||
| 13 | + def migrate! | ||
| 14 | + projects.each do |project| | ||
| 15 | + log "\nMigrating Wiki for '#{project.path_with_namespace}'" | ||
| 16 | + wiki = create_gollum_repo(project) | ||
| 17 | + create_pages project, wiki | ||
| 18 | + log "Project '#{project.path_with_namespace}' migrated. " + "[OK]".green | ||
| 19 | + end | ||
| 20 | + end | ||
| 21 | + | ||
| 22 | + private | ||
| 23 | + | ||
| 24 | + def create_gollum_repo(project) | ||
| 25 | + GollumWiki.new(project, nil).wiki | ||
| 26 | + end | ||
| 27 | + | ||
| 28 | + def create_pages(project, wiki) | ||
| 29 | + pages = project.wikis.group(:slug).all | ||
| 30 | + | ||
| 31 | + pages.each do |page| | ||
| 32 | + create_page_and_revisions(project, page) | ||
| 33 | + end | ||
| 34 | + end | ||
| 35 | + | ||
| 36 | + def create_page_and_revisions(project, page) | ||
| 37 | + # Grab all revisions of the page | ||
| 38 | + revisions = project.wikis.where(slug: page.slug).ordered.all | ||
| 39 | + | ||
| 40 | + # Remove the first revision created from the array | ||
| 41 | + # and use it to create the Gollum page. Each successive revision | ||
| 42 | + # will then be applied to the new Gollum page as an update. | ||
| 43 | + first_rev = revisions.pop | ||
| 44 | + | ||
| 45 | + wiki = GollumWiki.new(project, page.user) | ||
| 46 | + wiki_page = WikiPage.new(wiki) | ||
| 47 | + | ||
| 48 | + attributes = extract_attributes_from_page(first_rev) | ||
| 49 | + | ||
| 50 | + if wiki_page.create(attributes) | ||
| 51 | + log " Created page '#{wiki_page.title}' " + "[OK]".green | ||
| 52 | + | ||
| 53 | + # Reverse the revisions to create them in the correct | ||
| 54 | + # chronological order. | ||
| 55 | + create_revisions(project, wiki_page, revisions.reverse) | ||
| 56 | + else | ||
| 57 | + log " Failed to create page '#{wiki_page.title}' " + "[FAILED]".red | ||
| 58 | + end | ||
| 59 | + end | ||
| 60 | + | ||
| 61 | + def create_revisions(project, page, revisions) | ||
| 62 | + revisions.each do |revision| | ||
| 63 | + log " Creating revisions..." | ||
| 64 | + # Reinitialize a new GollumWiki instance for each page | ||
| 65 | + # and revision created so the correct User is shown in | ||
| 66 | + # the commit message. | ||
| 67 | + wiki = GollumWiki.new(project, revision.user) | ||
| 68 | + wiki_page = wiki.find_page(page.slug) | ||
| 69 | + | ||
| 70 | + attributes = extract_attributes_from_page(revision) | ||
| 71 | + | ||
| 72 | + content = attributes[:content] | ||
| 73 | + | ||
| 74 | + if wiki_page.update(content) | ||
| 75 | + log " Created revision " + "[OK]".green | ||
| 76 | + else | ||
| 77 | + log " Failed to create revision " + "[FAILED]".red | ||
| 78 | + end | ||
| 79 | + end | ||
| 80 | + end | ||
| 81 | + | ||
| 82 | + def extract_attributes_from_page(page) | ||
| 83 | + attributes = page.attributes | ||
| 84 | + .with_indifferent_access | ||
| 85 | + .slice(:title, :content) | ||
| 86 | + | ||
| 87 | + # Change 'index' pages to 'home' pages to match Gollum standards | ||
| 88 | + if attributes[:title].downcase == "index" | ||
| 89 | + attributes[:title] = "home" unless home_already_exists?(project) | ||
| 90 | + end | ||
| 91 | + | ||
| 92 | + attributes | ||
| 93 | + end | ||
| 94 | + | ||
| 95 | + def home_already_exists?(project) | ||
| 96 | + project.wikis.where(title: 'home').any? || project.wikis.where(title: 'Home').any? | ||
| 97 | + end | ||
| 98 | + | ||
| 99 | + def log(message) | ||
| 100 | + puts message | ||
| 101 | + end | ||
| 102 | + | ||
| 103 | +end |
| @@ -0,0 +1,114 @@ | @@ -0,0 +1,114 @@ | ||
| 1 | +require "spec_helper" | ||
| 2 | + | ||
| 3 | +describe WikiToGollumMigrator do | ||
| 4 | + | ||
| 5 | + def create_wiki_for(project) | ||
| 6 | + 3.times { @pages[project.id] << create_page(project) } | ||
| 7 | + end | ||
| 8 | + | ||
| 9 | + def create_revisions_for(project) | ||
| 10 | + @pages[project.id].each do |page| | ||
| 11 | + create_revision(page) | ||
| 12 | + end | ||
| 13 | + end | ||
| 14 | + | ||
| 15 | + def create_page(project) | ||
| 16 | + page = project.wikis.new(title: "Page #{rand(1000)}", content: "Content") | ||
| 17 | + page.user = project.owner | ||
| 18 | + page.slug = page.title.parameterize | ||
| 19 | + page.save! | ||
| 20 | + page | ||
| 21 | + end | ||
| 22 | + | ||
| 23 | + def create_revision(page) | ||
| 24 | + revision = page.dup | ||
| 25 | + revision.content = "Updated Content" | ||
| 26 | + revision.save! | ||
| 27 | + end | ||
| 28 | + | ||
| 29 | + def create_temp_repo(path) | ||
| 30 | + FileUtils.mkdir_p path | ||
| 31 | + command = "git init --quiet --bare #{path};" | ||
| 32 | + system(command) | ||
| 33 | + end | ||
| 34 | + | ||
| 35 | + before do | ||
| 36 | + @repo_path = "#{Rails.root}/tmp/test-git-base-path" | ||
| 37 | + @projects = [] | ||
| 38 | + @pages = Hash.new {|h,k| h[k] = Array.new } | ||
| 39 | + | ||
| 40 | + @projects << create(:project) | ||
| 41 | + @projects << create(:project) | ||
| 42 | + | ||
| 43 | + @projects.each do |project| | ||
| 44 | + create_wiki_for project | ||
| 45 | + create_revisions_for project | ||
| 46 | + end | ||
| 47 | + | ||
| 48 | + @project_without_wiki = create(:project) | ||
| 49 | + end | ||
| 50 | + | ||
| 51 | + context "Before the migration" do | ||
| 52 | + it "has two projects with valid wikis" do | ||
| 53 | + @projects.each do |project| | ||
| 54 | + pages = project.wikis.group(:slug).all | ||
| 55 | + pages.count.should == 3 | ||
| 56 | + end | ||
| 57 | + end | ||
| 58 | + | ||
| 59 | + it "has two revision for each page" do | ||
| 60 | + @projects.each do |project| | ||
| 61 | + @pages[project.id].each do |page| | ||
| 62 | + revisions = project.wikis.where(slug: page.slug) | ||
| 63 | + revisions.count.should == 2 | ||
| 64 | + end | ||
| 65 | + end | ||
| 66 | + end | ||
| 67 | + end | ||
| 68 | + | ||
| 69 | + describe "#initialize" do | ||
| 70 | + it "finds all projects that have existing wiki pages" do | ||
| 71 | + Project.count.should == 3 | ||
| 72 | + subject.projects.count.should == 2 | ||
| 73 | + end | ||
| 74 | + end | ||
| 75 | + | ||
| 76 | + context "#migrate!" do | ||
| 77 | + before do | ||
| 78 | + Gitlab::Shell.any_instance.stub(:add_repository) do |path| | ||
| 79 | + create_temp_repo("#{@repo_path}/#{path}.git") | ||
| 80 | + end | ||
| 81 | + | ||
| 82 | + subject.stub(:log).as_null_object | ||
| 83 | + | ||
| 84 | + subject.migrate! | ||
| 85 | + end | ||
| 86 | + | ||
| 87 | + it "creates a new Gollum Wiki for each project" do | ||
| 88 | + @projects.each do |project| | ||
| 89 | + wiki_path = project.path_with_namespace + ".wiki.git" | ||
| 90 | + full_path = @repo_path + "/" + wiki_path | ||
| 91 | + File.exist?(full_path).should be_true | ||
| 92 | + File.directory?(full_path).should be_true | ||
| 93 | + end | ||
| 94 | + end | ||
| 95 | + | ||
| 96 | + it "creates a gollum page for each unique Wiki page" do | ||
| 97 | + @projects.each do |project| | ||
| 98 | + wiki = GollumWiki.new(project, nil) | ||
| 99 | + wiki.pages.count.should == 3 | ||
| 100 | + end | ||
| 101 | + end | ||
| 102 | + | ||
| 103 | + it "creates a new revision for each old revision of the page" do | ||
| 104 | + @projects.each do |project| | ||
| 105 | + wiki = GollumWiki.new(project, nil) | ||
| 106 | + wiki.pages.each do |page| | ||
| 107 | + page.versions.count.should == 2 | ||
| 108 | + end | ||
| 109 | + end | ||
| 110 | + end | ||
| 111 | + end | ||
| 112 | + | ||
| 113 | + | ||
| 114 | +end |