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 @@ |
| 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 @@ |
| 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 @@ |
| 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 | ... | ... |