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 |