Commit 3c050a29a76469b9f256322652f12b8d3cc2bac2
Exists in
master
and in
4 other branches
Merge branch 'DanKnox-use_gollum_wikis'
Showing
31 changed files
with
1253 additions
and
99 deletions
Show diff stats
Gemfile
@@ -99,6 +99,13 @@ gem "colored" | @@ -99,6 +99,13 @@ gem "colored" | ||
99 | # GitLab settings | 99 | # GitLab settings |
100 | gem 'settingslogic' | 100 | gem 'settingslogic' |
101 | 101 | ||
102 | +# Wiki | ||
103 | +# - Use latest master to resolve Gem dependency with Pygemnts | ||
104 | +# github-linquist needs pygments 0.4.2 but Gollum 2.4.11 | ||
105 | +# requires pygments 0.3.2. The latest master Gollum has been updated | ||
106 | +# to use pygments 0.4.2. Change this after next Gollum release. | ||
107 | +gem "gollum", "~> 2.4.0", git: "git://github.com/github/gollum.git" | ||
108 | + | ||
102 | # Misc | 109 | # Misc |
103 | gem "foreman" | 110 | gem "foreman" |
104 | gem "git" | 111 | gem "git" |
Gemfile.lock
1 | GIT | 1 | GIT |
2 | + remote: git://github.com/github/gollum.git | ||
3 | + revision: 544d499ab170c9d9b355b7a0160afc74139ee2a4 | ||
4 | + specs: | ||
5 | + gollum (2.4.11) | ||
6 | + github-markdown (~> 0.5.3) | ||
7 | + github-markup (>= 0.7.5, < 1.0.0) | ||
8 | + grit (~> 2.5.0) | ||
9 | + mustache (>= 0.99.4, < 1.0.0) | ||
10 | + nokogiri (~> 1.5.6) | ||
11 | + pygments.rb (~> 0.4.2) | ||
12 | + sanitize (~> 2.0.3) | ||
13 | + sinatra (~> 1.3.5) | ||
14 | + stringex (~> 1.5.1) | ||
15 | + useragent (~> 0.4.16) | ||
16 | + | ||
17 | +GIT | ||
2 | remote: https://github.com/ctran/annotate_models.git | 18 | remote: https://github.com/ctran/annotate_models.git |
3 | revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e | 19 | revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e |
4 | specs: | 20 | specs: |
@@ -145,6 +161,7 @@ GEM | @@ -145,6 +161,7 @@ GEM | ||
145 | escape_utils (~> 0.2.3) | 161 | escape_utils (~> 0.2.3) |
146 | mime-types (~> 1.19) | 162 | mime-types (~> 1.19) |
147 | pygments.rb (>= 0.2.13) | 163 | pygments.rb (>= 0.2.13) |
164 | + github-markdown (0.5.3) | ||
148 | github-markup (0.7.5) | 165 | github-markup (0.7.5) |
149 | gitlab-grack (1.0.0) | 166 | gitlab-grack (1.0.0) |
150 | rack (~> 1.4.1) | 167 | rack (~> 1.4.1) |
@@ -176,6 +193,10 @@ GEM | @@ -176,6 +193,10 @@ GEM | ||
176 | grape-entity (0.2.0) | 193 | grape-entity (0.2.0) |
177 | activesupport | 194 | activesupport |
178 | multi_json (>= 1.3.2) | 195 | multi_json (>= 1.3.2) |
196 | + grit (2.5.0) | ||
197 | + diff-lcs (~> 1.1) | ||
198 | + mime-types (~> 1.15) | ||
199 | + posix-spawn (~> 0.3.6) | ||
179 | grit_ext (0.6.2) | 200 | grit_ext (0.6.2) |
180 | charlock_holmes (~> 0.6.9) | 201 | charlock_holmes (~> 0.6.9) |
181 | growl (1.0.3) | 202 | growl (1.0.3) |
@@ -237,7 +258,8 @@ GEM | @@ -237,7 +258,8 @@ GEM | ||
237 | sprockets (~> 2.0) | 258 | sprockets (~> 2.0) |
238 | multi_json (1.6.1) | 259 | multi_json (1.6.1) |
239 | multi_xml (0.5.3) | 260 | multi_xml (0.5.3) |
240 | - multipart-post (1.2.0) | 261 | + multipart-post (1.1.5) |
262 | + mustache (0.99.4) | ||
241 | mysql2 (0.3.11) | 263 | mysql2 (0.3.11) |
242 | net-ldap (0.2.2) | 264 | net-ldap (0.2.2) |
243 | nokogiri (1.5.6) | 265 | nokogiri (1.5.6) |
@@ -373,6 +395,8 @@ GEM | @@ -373,6 +395,8 @@ GEM | ||
373 | rspec-mocks (~> 2.12.0) | 395 | rspec-mocks (~> 2.12.0) |
374 | rubyntlm (0.1.1) | 396 | rubyntlm (0.1.1) |
375 | rubyzip (0.9.9) | 397 | rubyzip (0.9.9) |
398 | + sanitize (2.0.3) | ||
399 | + nokogiri (>= 1.4.4, < 1.6) | ||
376 | sass (3.2.5) | 400 | sass (3.2.5) |
377 | sass-rails (3.2.5) | 401 | sass-rails (3.2.5) |
378 | railties (~> 3.2.0) | 402 | railties (~> 3.2.0) |
@@ -429,6 +453,7 @@ GEM | @@ -429,6 +453,7 @@ GEM | ||
429 | tilt (~> 1.1, != 1.3.0) | 453 | tilt (~> 1.1, != 1.3.0) |
430 | stamp (0.5.0) | 454 | stamp (0.5.0) |
431 | state_machine (1.1.2) | 455 | state_machine (1.1.2) |
456 | + stringex (1.5.1) | ||
432 | temple (0.5.5) | 457 | temple (0.5.5) |
433 | test_after_commit (0.0.1) | 458 | test_after_commit (0.0.1) |
434 | therubyracer (0.10.2) | 459 | therubyracer (0.10.2) |
@@ -451,6 +476,7 @@ GEM | @@ -451,6 +476,7 @@ GEM | ||
451 | kgio (~> 2.6) | 476 | kgio (~> 2.6) |
452 | rack | 477 | rack |
453 | raindrops (~> 0.7) | 478 | raindrops (~> 0.7) |
479 | + useragent (0.4.16) | ||
454 | virtus (0.5.4) | 480 | virtus (0.5.4) |
455 | backports (~> 2.6.1) | 481 | backports (~> 2.6.1) |
456 | descendants_tracker (~> 0.0.1) | 482 | descendants_tracker (~> 0.0.1) |
@@ -499,6 +525,7 @@ DEPENDENCIES | @@ -499,6 +525,7 @@ DEPENDENCIES | ||
499 | gitlab_meta (= 5.0) | 525 | gitlab_meta (= 5.0) |
500 | gitlab_omniauth-ldap (= 1.0.2) | 526 | gitlab_omniauth-ldap (= 1.0.2) |
501 | gitlab_yaml_db (= 1.0.0) | 527 | gitlab_yaml_db (= 1.0.0) |
528 | + gollum (~> 2.4.0)! | ||
502 | gon | 529 | gon |
503 | grape (~> 0.3.1) | 530 | grape (~> 0.3.1) |
504 | grape-entity (~> 0.2.0) | 531 | grape-entity (~> 0.2.0) |
app/assets/stylesheets/application.scss
@@ -34,6 +34,7 @@ | @@ -34,6 +34,7 @@ | ||
34 | @import "sections/login.scss"; | 34 | @import "sections/login.scss"; |
35 | @import "sections/editor.scss"; | 35 | @import "sections/editor.scss"; |
36 | @import "sections/admin.scss"; | 36 | @import "sections/admin.scss"; |
37 | +@import "sections/wiki.scss"; | ||
37 | 38 | ||
38 | @import "highlight/white.scss"; | 39 | @import "highlight/white.scss"; |
39 | @import "highlight/dark.scss"; | 40 | @import "highlight/dark.scss"; |
app/controllers/wikis_controller.rb
@@ -2,58 +2,94 @@ class WikisController < ProjectResourceController | @@ -2,58 +2,94 @@ class WikisController < ProjectResourceController | ||
2 | before_filter :authorize_read_wiki! | 2 | before_filter :authorize_read_wiki! |
3 | before_filter :authorize_write_wiki!, only: [:edit, :create, :history] | 3 | before_filter :authorize_write_wiki!, only: [:edit, :create, :history] |
4 | before_filter :authorize_admin_wiki!, only: :destroy | 4 | before_filter :authorize_admin_wiki!, only: :destroy |
5 | + before_filter :load_gollum_wiki | ||
5 | 6 | ||
6 | def pages | 7 | def pages |
7 | - @wiki_pages = @project.wikis.group(:slug).ordered | 8 | + @wiki_pages = @gollum_wiki.pages |
8 | end | 9 | end |
9 | 10 | ||
10 | def show | 11 | def show |
11 | - @most_recent_wiki = @project.wikis.where(slug: params[:id]).ordered.first | ||
12 | - if params[:version_id] | ||
13 | - @wiki = @project.wikis.find(params[:version_id]) | ||
14 | - else | ||
15 | - @wiki = @most_recent_wiki | ||
16 | - end | 12 | + @wiki = @gollum_wiki.find_page(params[:id], params[:version_id]) |
17 | 13 | ||
18 | if @wiki | 14 | if @wiki |
19 | render 'show' | 15 | render 'show' |
20 | else | 16 | else |
21 | - if can?(current_user, :write_wiki, @project) | ||
22 | - @wiki = @project.wikis.new(slug: params[:id]) | ||
23 | - render 'edit' | ||
24 | - else | ||
25 | - render 'empty' | ||
26 | - end | 17 | + return render('empty') unless can?(current_user, :write_wiki, @project) |
18 | + @wiki = WikiPage.new(@gollum_wiki) | ||
19 | + @wiki.title = params[:id] | ||
20 | + | ||
21 | + render 'edit' | ||
27 | end | 22 | end |
28 | end | 23 | end |
29 | 24 | ||
30 | def edit | 25 | def edit |
31 | - @wiki = @project.wikis.where(slug: params[:id]).ordered.first | ||
32 | - @wiki = Wiki.regenerate_from @wiki | 26 | + @wiki = @gollum_wiki.find_page(params[:id]) |
27 | + end | ||
28 | + | ||
29 | + def update | ||
30 | + @wiki = @gollum_wiki.find_page(params[:id]) | ||
31 | + | ||
32 | + return render('empty') unless can?(current_user, :write_wiki, @project) | ||
33 | + | ||
34 | + if @wiki.update(content, format, message) | ||
35 | + redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.' | ||
36 | + else | ||
37 | + render 'edit' | ||
38 | + end | ||
33 | end | 39 | end |
34 | 40 | ||
35 | def create | 41 | def create |
36 | - @wiki = @project.wikis.new(params[:wiki]) | ||
37 | - @wiki.user = current_user | ||
38 | - | ||
39 | - respond_to do |format| | ||
40 | - if @wiki.save | ||
41 | - format.html { redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.' } | ||
42 | - else | ||
43 | - format.html { render action: "edit" } | ||
44 | - end | 42 | + @wiki = WikiPage.new(@gollum_wiki) |
43 | + | ||
44 | + if @wiki.create(wiki_params) | ||
45 | + redirect_to project_wiki_path(@project, @wiki), notice: 'Wiki was successfully updated.' | ||
46 | + else | ||
47 | + render action: "edit" | ||
45 | end | 48 | end |
46 | end | 49 | end |
47 | 50 | ||
48 | def history | 51 | def history |
49 | - @wiki_pages = @project.wikis.where(slug: params[:id]).ordered | 52 | + unless @wiki = @gollum_wiki.find_page(params[:id]) |
53 | + redirect_to project_wiki_path(@project, :home), notice: "Page not found" | ||
54 | + end | ||
50 | end | 55 | end |
51 | 56 | ||
52 | def destroy | 57 | def destroy |
53 | - @wikis = @project.wikis.where(slug: params[:id]).delete_all | 58 | + @wiki = @gollum_wiki.find_page(params[:id]) |
59 | + @wiki.delete if @wiki | ||
60 | + redirect_to project_wiki_path(@project, :home), notice: "Page was successfully deleted" | ||
61 | + end | ||
54 | 62 | ||
55 | - respond_to do |format| | ||
56 | - format.html { redirect_to project_wiki_path(@project, :index), notice: "Page was successfully deleted" } | ||
57 | - end | 63 | + def git_access |
58 | end | 64 | end |
65 | + | ||
66 | + private | ||
67 | + | ||
68 | + def load_gollum_wiki | ||
69 | + @gollum_wiki = GollumWiki.new(@project, current_user) | ||
70 | + | ||
71 | + # Call #wiki to make sure the Wiki Repo is initialized | ||
72 | + @gollum_wiki.wiki | ||
73 | + rescue GollumWiki::CouldNotCreateWikiError => ex | ||
74 | + flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." | ||
75 | + redirect_to @project | ||
76 | + return false | ||
77 | + end | ||
78 | + | ||
79 | + def wiki_params | ||
80 | + params[:wiki].slice(:title, :content, :format, :message) | ||
81 | + end | ||
82 | + | ||
83 | + def content | ||
84 | + params[:wiki][:content] | ||
85 | + end | ||
86 | + | ||
87 | + def format | ||
88 | + params[:wiki][:format] | ||
89 | + end | ||
90 | + | ||
91 | + def message | ||
92 | + params[:wiki][:message] | ||
93 | + end | ||
94 | + | ||
59 | end | 95 | end |
app/helpers/gitlab_markdown_helper.rb
@@ -49,4 +49,12 @@ module GitlabMarkdownHelper | @@ -49,4 +49,12 @@ module GitlabMarkdownHelper | ||
49 | 49 | ||
50 | @markdown.render(text).html_safe | 50 | @markdown.render(text).html_safe |
51 | end | 51 | end |
52 | + | ||
53 | + def render_wiki_content(wiki_page) | ||
54 | + if wiki_page.format == :markdown | ||
55 | + markdown(wiki_page.content) | ||
56 | + else | ||
57 | + wiki_page.formatted_content.html_safe | ||
58 | + end | ||
59 | + end | ||
52 | end | 60 | end |
@@ -0,0 +1,118 @@ | @@ -0,0 +1,118 @@ | ||
1 | +class GollumWiki | ||
2 | + | ||
3 | + MARKUPS = { | ||
4 | + "Markdown" => :markdown, | ||
5 | + "RDoc" => :rdoc | ||
6 | + } | ||
7 | + | ||
8 | + class CouldNotCreateWikiError < StandardError; end | ||
9 | + | ||
10 | + # Returns a string describing what went wrong after | ||
11 | + # an operation fails. | ||
12 | + attr_reader :error_message | ||
13 | + | ||
14 | + def initialize(project, user = nil) | ||
15 | + @project = project | ||
16 | + @user = user | ||
17 | + end | ||
18 | + | ||
19 | + def path_with_namespace | ||
20 | + @project.path_with_namespace + ".wiki" | ||
21 | + end | ||
22 | + | ||
23 | + def url_to_repo | ||
24 | + gitlab_shell.url_to_repo(path_with_namespace) | ||
25 | + end | ||
26 | + | ||
27 | + def ssh_url_to_repo | ||
28 | + url_to_repo | ||
29 | + end | ||
30 | + | ||
31 | + def http_url_to_repo | ||
32 | + http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('') | ||
33 | + end | ||
34 | + | ||
35 | + # Returns the Gollum::Wiki object. | ||
36 | + def wiki | ||
37 | + @wiki ||= begin | ||
38 | + Gollum::Wiki.new(path_to_repo) | ||
39 | + rescue Grit::NoSuchPathError | ||
40 | + create_repo! | ||
41 | + end | ||
42 | + end | ||
43 | + | ||
44 | + # Returns an Array of Gitlab WikiPage instances or an | ||
45 | + # empty Array if this Wiki has no pages. | ||
46 | + def pages | ||
47 | + wiki.pages.map { |page| WikiPage.new(self, page, true) } | ||
48 | + end | ||
49 | + | ||
50 | + # Returns the last 30 Commit objects accross the entire | ||
51 | + # repository. | ||
52 | + def recent_history | ||
53 | + Commit.fresh_commits(wiki.repo, 30) | ||
54 | + end | ||
55 | + | ||
56 | + # Finds a page within the repository based on a tile | ||
57 | + # or slug. | ||
58 | + # | ||
59 | + # title - The human readable or parameterized title of | ||
60 | + # the page. | ||
61 | + # | ||
62 | + # Returns an initialized WikiPage instance or nil | ||
63 | + def find_page(title, version = nil) | ||
64 | + if page = wiki.page(title, version) | ||
65 | + WikiPage.new(self, page, true) | ||
66 | + else | ||
67 | + nil | ||
68 | + end | ||
69 | + end | ||
70 | + | ||
71 | + def create_page(title, content, format = :markdown, message = nil) | ||
72 | + commit = commit_details(:created, message, title) | ||
73 | + | ||
74 | + wiki.write_page(title, format, content, commit) | ||
75 | + rescue Gollum::DuplicatePageError => e | ||
76 | + @error_message = "Duplicate page: #{e.message}" | ||
77 | + return false | ||
78 | + end | ||
79 | + | ||
80 | + def update_page(page, content, format = :markdown, message = nil) | ||
81 | + commit = commit_details(:updated, message, page.title) | ||
82 | + | ||
83 | + wiki.update_page(page, page.name, format, content, commit) | ||
84 | + end | ||
85 | + | ||
86 | + def delete_page(page, message = nil) | ||
87 | + wiki.delete_page(page, commit_details(:deleted, message, page.title)) | ||
88 | + end | ||
89 | + | ||
90 | + private | ||
91 | + | ||
92 | + def create_repo! | ||
93 | + if gitlab_shell.add_repository(path_with_namespace) | ||
94 | + Gollum::Wiki.new(path_to_repo) | ||
95 | + else | ||
96 | + raise CouldNotCreateWikiError | ||
97 | + end | ||
98 | + end | ||
99 | + | ||
100 | + def commit_details(action, message = nil, title = nil) | ||
101 | + commit_message = message || default_message(action, title) | ||
102 | + | ||
103 | + {email: @user.email, name: @user.name, message: commit_message} | ||
104 | + end | ||
105 | + | ||
106 | + def default_message(action, title) | ||
107 | + "#{@user.username} #{action} page: #{title}" | ||
108 | + end | ||
109 | + | ||
110 | + def gitlab_shell | ||
111 | + @gitlab_shell ||= Gitlab::Shell.new | ||
112 | + end | ||
113 | + | ||
114 | + def path_to_repo | ||
115 | + @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git") | ||
116 | + end | ||
117 | + | ||
118 | +end |
@@ -0,0 +1,181 @@ | @@ -0,0 +1,181 @@ | ||
1 | +class WikiPage | ||
2 | + include ActiveModel::Validations | ||
3 | + include ActiveModel::Conversion | ||
4 | + include StaticModel | ||
5 | + extend ActiveModel::Naming | ||
6 | + | ||
7 | + def self.primary_key | ||
8 | + 'slug' | ||
9 | + end | ||
10 | + | ||
11 | + def self.model_name | ||
12 | + ActiveModel::Name.new(self, nil, 'wiki') | ||
13 | + end | ||
14 | + | ||
15 | + def to_key | ||
16 | + [:slug] | ||
17 | + end | ||
18 | + | ||
19 | + validates :title, presence: true | ||
20 | + validates :content, presence: true | ||
21 | + | ||
22 | + # The Gitlab GollumWiki instance. | ||
23 | + attr_reader :wiki | ||
24 | + | ||
25 | + # The raw Gollum::Page instance. | ||
26 | + attr_reader :page | ||
27 | + | ||
28 | + # The attributes Hash used for storing and validating | ||
29 | + # new Page values before writing to the Gollum repository. | ||
30 | + attr_accessor :attributes | ||
31 | + | ||
32 | + def initialize(wiki, page = nil, persisted = false) | ||
33 | + @wiki = wiki | ||
34 | + @page = page | ||
35 | + @persisted = persisted | ||
36 | + @attributes = {}.with_indifferent_access | ||
37 | + | ||
38 | + set_attributes if persisted? | ||
39 | + end | ||
40 | + | ||
41 | + # The escaped URL path of this page. | ||
42 | + def slug | ||
43 | + @attributes[:slug] | ||
44 | + end | ||
45 | + | ||
46 | + alias :to_param :slug | ||
47 | + | ||
48 | + # The formatted title of this page. | ||
49 | + def title | ||
50 | + @attributes[:title] || "" | ||
51 | + end | ||
52 | + | ||
53 | + # Sets the title of this page. | ||
54 | + def title=(new_title) | ||
55 | + @attributes[:title] = new_title | ||
56 | + end | ||
57 | + | ||
58 | + # The raw content of this page. | ||
59 | + def content | ||
60 | + @attributes[:content] | ||
61 | + end | ||
62 | + | ||
63 | + # The processed/formatted content of this page. | ||
64 | + def formatted_content | ||
65 | + @attributes[:formatted_content] | ||
66 | + end | ||
67 | + | ||
68 | + # The markup format for the page. | ||
69 | + def format | ||
70 | + @attributes[:format] || :markdown | ||
71 | + end | ||
72 | + | ||
73 | + # The commit message for this page version. | ||
74 | + def message | ||
75 | + version.try(:message) | ||
76 | + end | ||
77 | + | ||
78 | + # The Gitlab Commit instance for this page. | ||
79 | + def version | ||
80 | + return nil unless persisted? | ||
81 | + | ||
82 | + @version ||= Commit.new(@page.version) | ||
83 | + end | ||
84 | + | ||
85 | + # Returns an array of Gitlab Commit instances. | ||
86 | + def versions | ||
87 | + return [] unless persisted? | ||
88 | + | ||
89 | + @page.versions.map { |v| Commit.new(v) } | ||
90 | + end | ||
91 | + | ||
92 | + # Returns the Date that this latest version was | ||
93 | + # created on. | ||
94 | + def created_at | ||
95 | + @page.version.date | ||
96 | + end | ||
97 | + | ||
98 | + # Returns boolean True or False if this instance | ||
99 | + # is an old version of the page. | ||
100 | + def historical? | ||
101 | + @page.historical? | ||
102 | + end | ||
103 | + | ||
104 | + # Returns boolean True or False if this instance | ||
105 | + # has been fully saved to disk or not. | ||
106 | + def persisted? | ||
107 | + @persisted == true | ||
108 | + end | ||
109 | + | ||
110 | + # Creates a new Wiki Page. | ||
111 | + # | ||
112 | + # attr - Hash of attributes to set on the new page. | ||
113 | + # :title - The title for the new page. | ||
114 | + # :content - The raw markup content. | ||
115 | + # :format - Optional symbol representing the | ||
116 | + # content format. Can be any type | ||
117 | + # listed in the GollumWiki::MARKUPS | ||
118 | + # Hash. | ||
119 | + # :message - Optional commit message to set on | ||
120 | + # the new page. | ||
121 | + # | ||
122 | + # Returns the String SHA1 of the newly created page | ||
123 | + # or False if the save was unsuccessful. | ||
124 | + def create(attr = {}) | ||
125 | + @attributes.merge!(attr) | ||
126 | + | ||
127 | + save :create_page, title, content, format, message | ||
128 | + end | ||
129 | + | ||
130 | + # Updates an existing Wiki Page, creating a new version. | ||
131 | + # | ||
132 | + # new_content - The raw markup content to replace the existing. | ||
133 | + # format - Optional symbol representing the content format. | ||
134 | + # See GollumWiki::MARKUPS Hash for available formats. | ||
135 | + # message - Optional commit message to set on the new version. | ||
136 | + # | ||
137 | + # Returns the String SHA1 of the newly created page | ||
138 | + # or False if the save was unsuccessful. | ||
139 | + def update(new_content = "", format = :markdown, message = nil) | ||
140 | + @attributes[:content] = new_content | ||
141 | + @attributes[:format] = format | ||
142 | + | ||
143 | + save :update_page, @page, content, format, message | ||
144 | + end | ||
145 | + | ||
146 | + # Destroys the WIki Page. | ||
147 | + # | ||
148 | + # Returns boolean True or False. | ||
149 | + def delete | ||
150 | + if wiki.delete_page(@page) | ||
151 | + true | ||
152 | + else | ||
153 | + false | ||
154 | + end | ||
155 | + end | ||
156 | + | ||
157 | + private | ||
158 | + | ||
159 | + def set_attributes | ||
160 | + attributes[:slug] = @page.escaped_url_path | ||
161 | + attributes[:title] = @page.title | ||
162 | + attributes[:content] = @page.raw_data | ||
163 | + attributes[:formatted_content] = @page.formatted_data | ||
164 | + attributes[:format] = @page.format | ||
165 | + end | ||
166 | + | ||
167 | + def save(method, *args) | ||
168 | + if valid? && wiki.send(method, *args) | ||
169 | + @page = wiki.wiki.paged(title) | ||
170 | + | ||
171 | + set_attributes | ||
172 | + | ||
173 | + @persisted = true | ||
174 | + else | ||
175 | + errors.add(:base, wiki.error_message) if wiki.error_message | ||
176 | + @persisted = false | ||
177 | + end | ||
178 | + @persisted | ||
179 | + end | ||
180 | + | ||
181 | +end |
app/observers/project_observer.rb
@@ -18,6 +18,11 @@ class ProjectObserver < ActiveRecord::Observer | @@ -18,6 +18,11 @@ class ProjectObserver < ActiveRecord::Observer | ||
18 | project.path_with_namespace | 18 | project.path_with_namespace |
19 | ) | 19 | ) |
20 | 20 | ||
21 | + GitlabShellWorker.perform_async( | ||
22 | + :remove_repository, | ||
23 | + project.path_with_namespace + ".wiki" | ||
24 | + ) | ||
25 | + | ||
21 | project.satellite.destroy | 26 | project.satellite.destroy |
22 | 27 | ||
23 | log_info("Project \"#{project.name}\" was removed") | 28 | log_info("Project \"#{project.name}\" was removed") |
app/views/layouts/project_resource.html.haml
@@ -36,7 +36,7 @@ | @@ -36,7 +36,7 @@ | ||
36 | %span.count.merge_counter= @project.merge_requests.opened.count | 36 | %span.count.merge_counter= @project.merge_requests.opened.count |
37 | 37 | ||
38 | = nav_link(html_options: {class: "#{project_wiki_tab_class}"}) do | 38 | = nav_link(html_options: {class: "#{project_wiki_tab_class}"}) do |
39 | - = link_to 'Wiki', project_wiki_path(@project, :index) | 39 | + = link_to 'Wiki', project_wiki_path(@project, :home) |
40 | 40 | ||
41 | - if can? current_user, :admin_project, @project | 41 | - if can? current_user, :admin_project, @project |
42 | = nav_link(html_options: {class: "#{project_tab_class}"}) do | 42 | = nav_link(html_options: {class: "#{project_tab_class}"}) do |
app/views/wikis/_form.html.haml
@@ -8,9 +8,12 @@ | @@ -8,9 +8,12 @@ | ||
8 | 8 | ||
9 | .ui-box.ui-box-show | 9 | .ui-box.ui-box-show |
10 | .ui-box-head | 10 | .ui-box-head |
11 | - = f.label :title | ||
12 | - .input= f.text_field :title, class: 'span8' | ||
13 | - = f.hidden_field :slug | 11 | + %h3.page_title |
12 | + .edit-wiki-header | ||
13 | + = @wiki.title.titleize | ||
14 | + = f.hidden_field :title, value: @wiki.title | ||
15 | + = f.select :format, options_for_select(GollumWiki::MARKUPS, {selected: @wiki.format}), {}, class: "pull-right input-medium" | ||
16 | + = f.label :format, class: "pull-right", style: "padding-right: 20px;" | ||
14 | .ui-box-body | 17 | .ui-box-body |
15 | .input | 18 | .input |
16 | %span.cgray | 19 | %span.cgray |
@@ -22,6 +25,9 @@ | @@ -22,6 +25,9 @@ | ||
22 | .ui-box-bottom | 25 | .ui-box-bottom |
23 | = f.label :content | 26 | = f.label :content |
24 | .input= f.text_area :content, class: 'span8 js-gfm-input' | 27 | .input= f.text_area :content, class: 'span8 js-gfm-input' |
28 | + .ui-box-bottom | ||
29 | + = f.label :commit_message | ||
30 | + .input= f.text_field :message, class: 'span8' | ||
25 | .actions | 31 | .actions |
26 | = f.submit 'Save', class: "btn-save btn" | 32 | = f.submit 'Save', class: "btn-save btn" |
27 | = link_to "Cancel", project_wiki_path(@project, :index), class: "btn btn-cancel" | 33 | = link_to "Cancel", project_wiki_path(@project, :index), class: "btn btn-cancel" |
@@ -0,0 +1,16 @@ | @@ -0,0 +1,16 @@ | ||
1 | +%span.pull-right | ||
2 | + = link_to project_wiki_path(@project, :home), class: "btn btn-small grouped" do | ||
3 | + Home | ||
4 | + = link_to pages_project_wikis_path(@project), class: "btn btn-small grouped" do | ||
5 | + Pages | ||
6 | + - if (@wiki && @wiki.persisted?) | ||
7 | + = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do | ||
8 | + History | ||
9 | + - if can?(current_user, :write_wiki, @project) | ||
10 | + - if @wiki && @wiki.persisted? | ||
11 | + = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do | ||
12 | + %i.icon-edit | ||
13 | + Edit | ||
14 | + = link_to git_access_project_wikis_path(@project), class: "btn btn-small grouped" do | ||
15 | + %i.icon-download-alt | ||
16 | + Git Access |
app/views/wikis/_nav.html.haml
1 | %ul.nav.nav-tabs | 1 | %ul.nav.nav-tabs |
2 | - if @project.wiki_enabled | 2 | - if @project.wiki_enabled |
3 | = nav_link(controller: 'wikis') do | 3 | = nav_link(controller: 'wikis') do |
4 | - = link_to 'Wiki', project_wiki_path(@project, :index) | 4 | + = link_to 'Wiki', project_wiki_path(@project, :home) |
5 | 5 | ||
6 | - if @project.wall_enabled | 6 | - if @project.wall_enabled |
7 | = nav_link(path: 'projects#wall') do | 7 | = nav_link(path: 'projects#wall') do |
app/views/wikis/edit.html.haml
1 | -%h3.page_title Editing page | 1 | +%h3.page_title |
2 | + Editing page | ||
3 | + = render partial: 'main_links' | ||
2 | = render 'form' | 4 | = render 'form' |
3 | 5 | ||
4 | .pull-right | 6 | .pull-right |
5 | - - if can? current_user, :admin_wiki, @project | 7 | + - if @wiki.persisted? && can?(current_user, :admin_wiki, @project) |
6 | = link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete, class: "btn btn-small btn-remove" do | 8 | = link_to project_wiki_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete, class: "btn btn-small btn-remove" do |
7 | Delete this page | 9 | Delete this page |
@@ -0,0 +1,36 @@ | @@ -0,0 +1,36 @@ | ||
1 | +%h3.page_title | ||
2 | + Git Access | ||
3 | + %strong= @gollum_wiki.path_with_namespace | ||
4 | + = render partial: 'main_links' | ||
5 | + | ||
6 | +%br | ||
7 | +.content | ||
8 | + .project_clone_panel | ||
9 | + .row | ||
10 | + .span7 | ||
11 | + .form-horizontal | ||
12 | + .input-prepend.project_clone_holder | ||
13 | + %button{class: "btn active", :"data-clone" => @gollum_wiki.ssh_url_to_repo} SSH | ||
14 | + %button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase | ||
15 | + = text_field_tag :project_clone, @gollum_wiki.url_to_repo, class: "one_click_select input-xxlarge", readonly: true | ||
16 | + .git-empty | ||
17 | + %fieldset | ||
18 | + %legend Install Gollum: | ||
19 | + %pre.dark | ||
20 | + :preserve | ||
21 | + gem install gollum | ||
22 | + | ||
23 | + %legend Clone Your Wiki: | ||
24 | + %pre.dark | ||
25 | + :preserve | ||
26 | + git clone #{@gollum_wiki.path_with_namespace}.git | ||
27 | + cd #{@gollum_wiki.path_with_namespace} | ||
28 | + | ||
29 | + %legend Start Gollum And Edit Locally: | ||
30 | + %pre.dark | ||
31 | + :preserve | ||
32 | + gollum | ||
33 | + == Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin | ||
34 | + >> Thin web server (v1.5.0 codename Knife) | ||
35 | + >> Maximum connections set to 1024 | ||
36 | + >> Listening on 0.0.0.0:4567, CTRL+C to stop |
app/views/wikis/history.html.haml
1 | %h3.page_title | 1 | %h3.page_title |
2 | %span.cgray History for | 2 | %span.cgray History for |
3 | - = @wiki_pages.first.title | 3 | + = @wiki.title.titleize |
4 | + = render partial: 'main_links' | ||
4 | %br | 5 | %br |
5 | %table | 6 | %table |
6 | %thead | 7 | %thead |
7 | %tr | 8 | %tr |
8 | %th Page version | 9 | %th Page version |
10 | + %th Author | ||
11 | + %th Commit Message | ||
9 | %th Last updated | 12 | %th Last updated |
10 | - %th Updated by | 13 | + %th Format |
11 | %tbody | 14 | %tbody |
12 | - - @wiki_pages.each_with_index do |wiki_page, i| | 15 | + - @wiki.versions.each do |version| |
16 | + - commit = CommitDecorator.new(version) | ||
13 | %tr | 17 | %tr |
14 | %td | 18 | %td |
15 | - %strong | ||
16 | - = link_to project_wiki_path(@project, wiki_page, version_id: wiki_page.id) do | ||
17 | - Version | ||
18 | - = @wiki_pages.count - i | 19 | + = link_to project_wiki_path(@project, @wiki, version_id: commit.id) do |
20 | + = commit.short_id | ||
21 | + %td= commit.author_link avatar: true, size: 24 | ||
22 | + %td | ||
23 | + = commit.title | ||
19 | %td | 24 | %td |
20 | - = wiki_page.created_at.to_s(:short) | ||
21 | - (#{time_ago_in_words(wiki_page.created_at)} | ||
22 | - ago) | ||
23 | - %td= link_to_member(@project, wiki_page.user) | 25 | + = time_ago_in_words(version.date) |
26 | + ago | ||
27 | + %td | ||
28 | + %strong | ||
29 | + = @wiki.page.wiki.page(@wiki.page.name, commit.id).try(:format) |
app/views/wikis/pages.html.haml
1 | = render 'wikis/nav' | 1 | = render 'wikis/nav' |
2 | -%h3.page_title All Pages | 2 | +%h3.page_title |
3 | + All Pages | ||
4 | + = render partial: 'main_links' | ||
3 | %br | 5 | %br |
4 | %table | 6 | %table |
5 | %thead | 7 | %thead |
6 | %tr | 8 | %tr |
7 | %th Title | 9 | %th Title |
8 | - %th Slug | 10 | + %th Format |
9 | %th Last updated | 11 | %th Last updated |
10 | %th Updated by | 12 | %th Updated by |
11 | %tbody | 13 | %tbody |
12 | - @wiki_pages.each do |wiki_page| | 14 | - @wiki_pages.each do |wiki_page| |
13 | %tr | 15 | %tr |
14 | %td | 16 | %td |
15 | - %strong= link_to wiki_page.title, project_wiki_path(@project, wiki_page) | ||
16 | - %td= wiki_page.slug | 17 | + %strong= link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page) |
18 | + %td | ||
19 | + %strong= wiki_page.format | ||
17 | %td | 20 | %td |
18 | = wiki_page.created_at.to_s(:short) do | 21 | = wiki_page.created_at.to_s(:short) do |
19 | (#{time_ago_in_words(wiki_page.created_at)} | 22 | (#{time_ago_in_words(wiki_page.created_at)} |
20 | ago) | 23 | ago) |
21 | - %td= link_to_member(@project, wiki_page.user) | 24 | + - commit = CommitDecorator.decorate(wiki_page.version) |
25 | + %td= commit.author_link avatar: true, size: 24 |
app/views/wikis/show.html.haml
1 | = render 'wikis/nav' | 1 | = render 'wikis/nav' |
2 | -- if @wiki != @most_recent_wiki | ||
3 | - .alert | 2 | +%h3.page_title |
3 | + = @wiki.title.titleize | ||
4 | + = render partial: 'main_links' | ||
5 | +%br | ||
6 | +- if @wiki.historical? | ||
7 | + .warning_message | ||
4 | This is an old version of this page. | 8 | This is an old version of this page. |
5 | You can view the #{link_to "most recent version", project_wiki_path(@project, @wiki)} or browse the #{link_to "history", history_project_wiki_path(@project, @wiki)}. | 9 | You can view the #{link_to "most recent version", project_wiki_path(@project, @wiki)} or browse the #{link_to "history", history_project_wiki_path(@project, @wiki)}. |
6 | 10 | ||
7 | .file_holder | 11 | .file_holder |
8 | - .file_title | ||
9 | - = @wiki.title | ||
10 | - %span.options | ||
11 | - = link_to pages_project_wikis_path(@project), class: "btn btn-tiny grouped" do | ||
12 | - Pages | ||
13 | - - if can? current_user, :write_wiki, @project | ||
14 | - = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-tiny grouped" do | ||
15 | - History | ||
16 | - = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-tiny grouped" do | ||
17 | - %i.icon-edit | ||
18 | - Edit | ||
19 | - | ||
20 | .file_content.wiki | 12 | .file_content.wiki |
21 | = preserve do | 13 | = preserve do |
22 | - = markdown @wiki.content | 14 | + = render_wiki_content(@wiki) |
23 | 15 | ||
24 | -%p.time Last edited by #{link_to_member @project, @wiki.user}, #{time_ago_in_words @wiki.created_at} ago | 16 | +- commit = CommitDecorator.new(@wiki.version) |
17 | +%p.time Last edited by #{commit.author_link(avatar: true, size: 16)} #{time_ago_in_words @wiki.created_at} ago |
config/routes.rb
@@ -185,6 +185,8 @@ Gitlab::Application.routes.draw do | @@ -185,6 +185,8 @@ Gitlab::Application.routes.draw do | ||
185 | resources :wikis, only: [:show, :edit, :destroy, :create] do | 185 | resources :wikis, only: [:show, :edit, :destroy, :create] do |
186 | collection do | 186 | collection do |
187 | get :pages | 187 | get :pages |
188 | + put ':id' => 'wikis#update' | ||
189 | + get :git_access | ||
188 | end | 190 | end |
189 | 191 | ||
190 | member do | 192 | member do |
features/project/wiki.feature
@@ -5,5 +5,32 @@ Feature: Project Wiki | @@ -5,5 +5,32 @@ Feature: Project Wiki | ||
5 | Given I visit project wiki page | 5 | Given I visit project wiki page |
6 | 6 | ||
7 | Scenario: Add new page | 7 | Scenario: Add new page |
8 | - Given I create Wiki page | ||
9 | - Then I should see newly created wiki page | 8 | + Given I create the Wiki Home page |
9 | + Then I should see the newly created wiki page | ||
10 | + | ||
11 | + Scenario: Edit existing page | ||
12 | + Given I have an existing Wiki page | ||
13 | + And I browse to that Wiki page | ||
14 | + And I click on the Edit button | ||
15 | + And I change the content | ||
16 | + Then I should see the updated content | ||
17 | + | ||
18 | + Scenario: View page history | ||
19 | + Given I have an existing wiki page | ||
20 | + And That page has two revisions | ||
21 | + And I browse to that Wiki page | ||
22 | + And I click the History button | ||
23 | + Then I should see both revisions | ||
24 | + | ||
25 | + Scenario: Destroy Wiki page | ||
26 | + Given I have an existing wiki page | ||
27 | + And I browse to that Wiki page | ||
28 | + And I click on the Edit button | ||
29 | + And I click on the "Delete this page" button | ||
30 | + Then The page should be deleted | ||
31 | + | ||
32 | + Scenario: View all pages | ||
33 | + Given I have an existing wiki page | ||
34 | + And I browse to that Wiki page | ||
35 | + And I click on the "Pages" button | ||
36 | + Then I should see the existing page in the pages list |
features/steps/project/project_wiki.rb
@@ -4,17 +4,73 @@ class ProjectWiki < Spinach::FeatureSteps | @@ -4,17 +4,73 @@ class ProjectWiki < Spinach::FeatureSteps | ||
4 | include SharedNote | 4 | include SharedNote |
5 | include SharedPaths | 5 | include SharedPaths |
6 | 6 | ||
7 | - Given 'I create Wiki page' do | ||
8 | - fill_in "Title", :with => 'Test title' | 7 | + Given 'I create the Wiki Home page' do |
9 | fill_in "Content", :with => '[link test](test)' | 8 | fill_in "Content", :with => '[link test](test)' |
10 | click_on "Save" | 9 | click_on "Save" |
11 | end | 10 | end |
12 | 11 | ||
13 | - Then 'I should see newly created wiki page' do | ||
14 | - page.should have_content "Test title" | 12 | + Then 'I should see the newly created wiki page' do |
13 | + page.should have_content "Home" | ||
15 | page.should have_content "link test" | 14 | page.should have_content "link test" |
16 | 15 | ||
17 | click_link "link test" | 16 | click_link "link test" |
18 | page.should have_content "Editing page" | 17 | page.should have_content "Editing page" |
19 | end | 18 | end |
19 | + | ||
20 | + Given 'I have an existing Wiki page' do | ||
21 | + wiki.create_page("existing", "content", :markdown, "first commit") | ||
22 | + @page = wiki.find_page("existing") | ||
23 | + end | ||
24 | + | ||
25 | + And 'I browse to that Wiki page' do | ||
26 | + visit project_wiki_path(project, @page) | ||
27 | + end | ||
28 | + | ||
29 | + And 'I click on the Edit button' do | ||
30 | + click_on "Edit" | ||
31 | + end | ||
32 | + | ||
33 | + And 'I change the content' do | ||
34 | + fill_in "Content", :with => 'Updated Wiki Content' | ||
35 | + click_on "Save" | ||
36 | + end | ||
37 | + | ||
38 | + Then 'I should see the updated content' do | ||
39 | + page.should have_content "Updated Wiki Content" | ||
40 | + end | ||
41 | + | ||
42 | + And 'That page has two revisions' do | ||
43 | + @page.update("new content", :markdown, "second commit") | ||
44 | + end | ||
45 | + | ||
46 | + And 'I click the History button' do | ||
47 | + click_on "History" | ||
48 | + end | ||
49 | + | ||
50 | + Then 'I should see both revisions' do | ||
51 | + page.should have_content current_user.name | ||
52 | + page.should have_content "first commit" | ||
53 | + page.should have_content "second commit" | ||
54 | + end | ||
55 | + | ||
56 | + And 'I click on the "Delete this page" button' do | ||
57 | + click_on "Delete this page" | ||
58 | + end | ||
59 | + | ||
60 | + Then 'The page should be deleted' do | ||
61 | + page.should have_content "Page was successfully deleted" | ||
62 | + end | ||
63 | + | ||
64 | + And 'I click on the "Pages" button' do | ||
65 | + click_on "Pages" | ||
66 | + end | ||
67 | + | ||
68 | + Then 'I should see the existing page in the pages list' do | ||
69 | + page.should have_content current_user.name | ||
70 | + page.should have_content @page.title.titleize | ||
71 | + end | ||
72 | + | ||
73 | + def wiki | ||
74 | + @gollum_wiki = GollumWiki.new(project, current_user) | ||
75 | + end | ||
20 | end | 76 | end |
features/steps/shared/paths.rb
@@ -165,7 +165,7 @@ module SharedPaths | @@ -165,7 +165,7 @@ module SharedPaths | ||
165 | end | 165 | end |
166 | 166 | ||
167 | Given "I visit my project's wiki page" do | 167 | Given "I visit my project's wiki page" do |
168 | - visit project_wiki_path(@project, :index) | 168 | + visit project_wiki_path(@project, :home) |
169 | end | 169 | end |
170 | 170 | ||
171 | When 'I visit project hooks page' do | 171 | When 'I visit project hooks page' do |
@@ -260,7 +260,7 @@ module SharedPaths | @@ -260,7 +260,7 @@ module SharedPaths | ||
260 | end | 260 | end |
261 | 261 | ||
262 | Given 'I visit project wiki page' do | 262 | Given 'I visit project wiki page' do |
263 | - visit project_wiki_path(@project, :index) | 263 | + visit project_wiki_path(@project, :home) |
264 | end | 264 | end |
265 | 265 | ||
266 | def root_ref | 266 | def root_ref |
features/support/env.rb
@@ -37,6 +37,9 @@ DatabaseCleaner.strategy = :truncation | @@ -37,6 +37,9 @@ DatabaseCleaner.strategy = :truncation | ||
37 | Spinach.hooks.before_scenario do | 37 | Spinach.hooks.before_scenario do |
38 | # Use tmp dir for FS manipulations | 38 | # Use tmp dir for FS manipulations |
39 | Gitlab.config.gitlab_shell.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path')) | 39 | Gitlab.config.gitlab_shell.stub(repos_path: Rails.root.join('tmp', 'test-git-base-path')) |
40 | + Gitlab::Shell.any_instance.stub(:add_repository) do |path| | ||
41 | + create_temp_repo("#{Rails.root}/tmp/test-git-base-path/#{path}.git") | ||
42 | + end | ||
40 | FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path | 43 | FileUtils.rm_rf Gitlab.config.gitlab_shell.repos_path |
41 | FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path | 44 | FileUtils.mkdir_p Gitlab.config.gitlab_shell.repos_path |
42 | DatabaseCleaner.start | 45 | DatabaseCleaner.start |
@@ -51,3 +54,9 @@ Spinach.hooks.before_run do | @@ -51,3 +54,9 @@ Spinach.hooks.before_run do | ||
51 | 54 | ||
52 | include FactoryGirl::Syntax::Methods | 55 | include FactoryGirl::Syntax::Methods |
53 | end | 56 | end |
57 | + | ||
58 | +def create_temp_repo(path) | ||
59 | + FileUtils.mkdir_p path | ||
60 | + command = "git init --quiet --bare #{path};" | ||
61 | + system(command) | ||
62 | +end |
lib/api/internal.rb
@@ -12,10 +12,18 @@ module Gitlab | @@ -12,10 +12,18 @@ module Gitlab | ||
12 | # ref - branch name | 12 | # ref - branch name |
13 | # | 13 | # |
14 | get "/allowed" do | 14 | get "/allowed" do |
15 | + # Check for *.wiki repositories. | ||
16 | + # Strip out the .wiki from the pathname before finding the | ||
17 | + # project. This applies the correct project permissions to | ||
18 | + # the wiki repository as well. | ||
19 | + project_path = params[:project] | ||
20 | + project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ | ||
21 | + | ||
15 | key = Key.find(params[:key_id]) | 22 | key = Key.find(params[:key_id]) |
16 | - project = Project.find_with_namespace(params[:project]) | 23 | + project = Project.find_with_namespace(project_path) |
17 | git_cmd = params[:action] | 24 | git_cmd = params[:action] |
18 | 25 | ||
26 | + | ||
19 | if key.is_deploy_key | 27 | if key.is_deploy_key |
20 | project == key.project && git_cmd == 'git-upload-pack' | 28 | project == key.project && git_cmd == 'git-upload-pack' |
21 | else | 29 | else |
@@ -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 |
spec/features/gitlab_flavored_markdown_spec.rb
@@ -207,25 +207,4 @@ describe "Gitlab Flavored Markdown" do | @@ -207,25 +207,4 @@ describe "Gitlab Flavored Markdown" do | ||
207 | page.should have_link("##{issue.id}") | 207 | page.should have_link("##{issue.id}") |
208 | end | 208 | end |
209 | end | 209 | end |
210 | - | ||
211 | - | ||
212 | - describe "for wikis" do | ||
213 | - before do | ||
214 | - visit project_wiki_path(project, :index) | ||
215 | - fill_in "Title", with: "Circumvent ##{issue.id}" | ||
216 | - fill_in "Content", with: "# Other pages\n\n* [Foo](foo)\n* [Bar](bar)\n\nAlso look at ##{issue.id} :-)" | ||
217 | - click_on "Save" | ||
218 | - end | ||
219 | - | ||
220 | - it "should NOT render title in wikis#show" do | ||
221 | - within(".content .file_title") do # page title | ||
222 | - page.should have_content("Circumvent ##{issue.id}") | ||
223 | - page.should_not have_link("##{issue.id}") | ||
224 | - end | ||
225 | - end | ||
226 | - | ||
227 | - it "should render content in wikis#show" do | ||
228 | - page.should have_link("##{issue.id}") | ||
229 | - end | ||
230 | - end | ||
231 | end | 210 | end |
spec/helpers/gitlab_markdown_helper_spec.rb
@@ -363,4 +363,28 @@ describe GitlabMarkdownHelper do | @@ -363,4 +363,28 @@ describe GitlabMarkdownHelper do | ||
363 | markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}") | 363 | markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}") |
364 | end | 364 | end |
365 | end | 365 | end |
366 | + | ||
367 | + describe "#render_wiki_content" do | ||
368 | + before do | ||
369 | + @wiki = stub('WikiPage') | ||
370 | + @wiki.stub(:content).and_return('wiki content') | ||
371 | + end | ||
372 | + | ||
373 | + it "should use Gitlab Flavored Markdown for markdown files" do | ||
374 | + @wiki.stub(:format).and_return(:markdown) | ||
375 | + | ||
376 | + helper.should_receive(:markdown).with('wiki content') | ||
377 | + | ||
378 | + helper.render_wiki_content(@wiki) | ||
379 | + end | ||
380 | + | ||
381 | + it "should use the Gollum renderer for all other file types" do | ||
382 | + @wiki.stub(:format).and_return(:rdoc) | ||
383 | + formatted_content_stub = stub('formatted_content') | ||
384 | + formatted_content_stub.should_receive(:html_safe) | ||
385 | + @wiki.stub(:formatted_content).and_return(formatted_content_stub) | ||
386 | + | ||
387 | + helper.render_wiki_content(@wiki) | ||
388 | + end | ||
389 | + end | ||
366 | end | 390 | 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 |
@@ -0,0 +1,196 @@ | @@ -0,0 +1,196 @@ | ||
1 | +require "spec_helper" | ||
2 | + | ||
3 | +describe GollumWiki do | ||
4 | + | ||
5 | + def create_temp_repo(path) | ||
6 | + FileUtils.mkdir_p path | ||
7 | + command = "git init --quiet #{path};" | ||
8 | + system(command) | ||
9 | + end | ||
10 | + | ||
11 | + def remove_temp_repo(path) | ||
12 | + FileUtils.rm_rf path | ||
13 | + end | ||
14 | + | ||
15 | + def commit_details | ||
16 | + commit = {name: user.name, email: user.email, message: "test commit"} | ||
17 | + end | ||
18 | + | ||
19 | + def create_page(name, content) | ||
20 | + subject.wiki.write_page(name, :markdown, content, commit_details) | ||
21 | + end | ||
22 | + | ||
23 | + def destroy_page(page) | ||
24 | + subject.wiki.delete_page(page, commit_details) | ||
25 | + end | ||
26 | + | ||
27 | + let(:project) { create(:project) } | ||
28 | + let(:repository) { project.repository } | ||
29 | + let(:user) { project.owner } | ||
30 | + let(:gitlab_shell) { Gitlab::Shell.new } | ||
31 | + | ||
32 | + subject { GollumWiki.new(project, user) } | ||
33 | + | ||
34 | + before do | ||
35 | + create_temp_repo(subject.send(:path_to_repo)) | ||
36 | + end | ||
37 | + | ||
38 | + describe "#path_with_namespace" do | ||
39 | + it "returns the project path with namespace with the .wiki extension" do | ||
40 | + subject.path_with_namespace.should == project.path_with_namespace + ".wiki" | ||
41 | + end | ||
42 | + end | ||
43 | + | ||
44 | + describe "#url_to_repo" do | ||
45 | + it "returns the correct ssh url to the repo" do | ||
46 | + subject.url_to_repo.should == gitlab_shell.url_to_repo(subject.path_with_namespace) | ||
47 | + end | ||
48 | + end | ||
49 | + | ||
50 | + describe "#ssh_url_to_repo" do | ||
51 | + it "equals #url_to_repo" do | ||
52 | + subject.ssh_url_to_repo.should == subject.url_to_repo | ||
53 | + end | ||
54 | + end | ||
55 | + | ||
56 | + describe "#http_url_to_repo" do | ||
57 | + it "provides the full http url to the repo" do | ||
58 | + gitlab_url = Gitlab.config.gitlab.url | ||
59 | + repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git" | ||
60 | + subject.http_url_to_repo.should == repo_http_url | ||
61 | + end | ||
62 | + end | ||
63 | + | ||
64 | + describe "#wiki" do | ||
65 | + it "contains a Gollum::Wiki instance" do | ||
66 | + subject.wiki.should be_a Gollum::Wiki | ||
67 | + end | ||
68 | + | ||
69 | + before do | ||
70 | + Gitlab::Shell.any_instance.stub(:add_repository) do | ||
71 | + create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git") | ||
72 | + end | ||
73 | + project.stub(:path_with_namespace).and_return("non-existant") | ||
74 | + end | ||
75 | + | ||
76 | + it "creates a new wiki repo if one does not yet exist" do | ||
77 | + wiki = GollumWiki.new(project, user) | ||
78 | + wiki.create_page("index", "test content").should_not == false | ||
79 | + | ||
80 | + FileUtils.rm_rf wiki.send(:path_to_repo) | ||
81 | + end | ||
82 | + | ||
83 | + it "raises CouldNotCreateWikiError if it can't create the wiki repository" do | ||
84 | + Gitlab::Shell.any_instance.stub(:add_repository).and_return(false) | ||
85 | + expect { GollumWiki.new(project, user).wiki }.to raise_exception(GollumWiki::CouldNotCreateWikiError) | ||
86 | + end | ||
87 | + end | ||
88 | + | ||
89 | + describe "#pages" do | ||
90 | + before do | ||
91 | + create_page("index", "This is an awesome new Gollum Wiki") | ||
92 | + @pages = subject.pages | ||
93 | + end | ||
94 | + | ||
95 | + after do | ||
96 | + destroy_page(@pages.first.page) | ||
97 | + end | ||
98 | + | ||
99 | + it "returns an array of WikiPage instances" do | ||
100 | + @pages.first.should be_a WikiPage | ||
101 | + end | ||
102 | + | ||
103 | + it "returns the correct number of pages" do | ||
104 | + @pages.count.should == 1 | ||
105 | + end | ||
106 | + end | ||
107 | + | ||
108 | + describe "#find_page" do | ||
109 | + before do | ||
110 | + create_page("index page", "This is an awesome Gollum Wiki") | ||
111 | + end | ||
112 | + | ||
113 | + after do | ||
114 | + destroy_page(subject.pages.first.page) | ||
115 | + end | ||
116 | + | ||
117 | + it "returns the latest version of the page if it exists" do | ||
118 | + page = subject.find_page("index page") | ||
119 | + page.title.should == "index page" | ||
120 | + end | ||
121 | + | ||
122 | + it "returns nil if the page does not exist" do | ||
123 | + subject.find_page("non-existant").should == nil | ||
124 | + end | ||
125 | + | ||
126 | + it "can find a page by slug" do | ||
127 | + page = subject.find_page("index-page") | ||
128 | + page.title.should == "index page" | ||
129 | + end | ||
130 | + | ||
131 | + it "returns a WikiPage instance" do | ||
132 | + page = subject.find_page("index page") | ||
133 | + page.should be_a WikiPage | ||
134 | + end | ||
135 | + end | ||
136 | + | ||
137 | + describe "#create_page" do | ||
138 | + after do | ||
139 | + destroy_page(subject.pages.first.page) | ||
140 | + end | ||
141 | + | ||
142 | + it "creates a new wiki page" do | ||
143 | + subject.create_page("test page", "this is content").should_not == false | ||
144 | + subject.pages.count.should == 1 | ||
145 | + end | ||
146 | + | ||
147 | + it "returns false when a duplicate page exists" do | ||
148 | + subject.create_page("test page", "content") | ||
149 | + subject.create_page("test page", "content").should == false | ||
150 | + end | ||
151 | + | ||
152 | + it "stores an error message when a duplicate page exists" do | ||
153 | + 2.times { subject.create_page("test page", "content") } | ||
154 | + subject.error_message.should =~ /Duplicate page:/ | ||
155 | + end | ||
156 | + | ||
157 | + it "sets the correct commit message" do | ||
158 | + subject.create_page("test page", "some content", :markdown, "commit message") | ||
159 | + subject.pages.first.page.version.message.should == "commit message" | ||
160 | + end | ||
161 | + end | ||
162 | + | ||
163 | + describe "#update_page" do | ||
164 | + before do | ||
165 | + create_page("update-page", "some content") | ||
166 | + @gollum_page = subject.wiki.paged("update-page") | ||
167 | + subject.update_page(@gollum_page, "some other content", :markdown, "updated page") | ||
168 | + @page = subject.pages.first.page | ||
169 | + end | ||
170 | + | ||
171 | + after do | ||
172 | + destroy_page(@page) | ||
173 | + end | ||
174 | + | ||
175 | + it "updates the content of the page" do | ||
176 | + @page.raw_data.should == "some other content" | ||
177 | + end | ||
178 | + | ||
179 | + it "sets the correct commit message" do | ||
180 | + @page.version.message.should == "updated page" | ||
181 | + end | ||
182 | + end | ||
183 | + | ||
184 | + describe "#delete_page" do | ||
185 | + before do | ||
186 | + create_page("index", "some content") | ||
187 | + @page = subject.wiki.paged("index") | ||
188 | + end | ||
189 | + | ||
190 | + it "deletes the page" do | ||
191 | + subject.delete_page(@page) | ||
192 | + subject.pages.count.should == 0 | ||
193 | + end | ||
194 | + end | ||
195 | + | ||
196 | +end |
@@ -0,0 +1,164 @@ | @@ -0,0 +1,164 @@ | ||
1 | +require "spec_helper" | ||
2 | + | ||
3 | +describe WikiPage do | ||
4 | + | ||
5 | + def create_temp_repo(path) | ||
6 | + FileUtils.mkdir_p path | ||
7 | + command = "git init --quiet #{path};" | ||
8 | + system(command) | ||
9 | + end | ||
10 | + | ||
11 | + def remove_temp_repo(path) | ||
12 | + FileUtils.rm_rf path | ||
13 | + end | ||
14 | + | ||
15 | + def commit_details | ||
16 | + commit = {name: user.name, email: user.email, message: "test commit"} | ||
17 | + end | ||
18 | + | ||
19 | + def create_page(name, content) | ||
20 | + wiki.wiki.write_page(name, :markdown, content, commit_details) | ||
21 | + end | ||
22 | + | ||
23 | + def destroy_page(title) | ||
24 | + page = wiki.wiki.paged(title) | ||
25 | + wiki.wiki.delete_page(page, commit_details) | ||
26 | + end | ||
27 | + | ||
28 | + let(:project) { create(:project) } | ||
29 | + let(:repository) { project.repository } | ||
30 | + let(:user) { project.owner } | ||
31 | + let(:wiki) { GollumWiki.new(project, user) } | ||
32 | + | ||
33 | + subject { WikiPage.new(wiki) } | ||
34 | + | ||
35 | + before do | ||
36 | + create_temp_repo(wiki.send(:path_to_repo)) | ||
37 | + end | ||
38 | + | ||
39 | + describe "#initialize" do | ||
40 | + context "when initialized with an existing gollum page" do | ||
41 | + before do | ||
42 | + create_page("test page", "test content") | ||
43 | + @page = wiki.wiki.paged("test page") | ||
44 | + @wiki_page = WikiPage.new(wiki, @page, true) | ||
45 | + end | ||
46 | + | ||
47 | + it "sets the slug attribute" do | ||
48 | + @wiki_page.slug.should == "test-page" | ||
49 | + end | ||
50 | + | ||
51 | + it "sets the title attribute" do | ||
52 | + @wiki_page.title.should == "test page" | ||
53 | + end | ||
54 | + | ||
55 | + it "sets the formatted content attribute" do | ||
56 | + @wiki_page.content.should == "test content" | ||
57 | + end | ||
58 | + | ||
59 | + it "sets the format attribute" do | ||
60 | + @wiki_page.format.should == :markdown | ||
61 | + end | ||
62 | + | ||
63 | + it "sets the message attribute" do | ||
64 | + @wiki_page.message.should == "test commit" | ||
65 | + end | ||
66 | + | ||
67 | + it "sets the version attribute" do | ||
68 | + @wiki_page.version.should be_a Commit | ||
69 | + end | ||
70 | + end | ||
71 | + end | ||
72 | + | ||
73 | + describe "validations" do | ||
74 | + before do | ||
75 | + subject.attributes = {title: 'title', content: 'content'} | ||
76 | + end | ||
77 | + | ||
78 | + it "validates presence of title" do | ||
79 | + subject.attributes.delete(:title) | ||
80 | + subject.valid?.should be_false | ||
81 | + end | ||
82 | + | ||
83 | + it "validates presence of content" do | ||
84 | + subject.attributes.delete(:content) | ||
85 | + subject.valid?.should be_false | ||
86 | + end | ||
87 | + end | ||
88 | + | ||
89 | + before do | ||
90 | + @wiki_attr = {title: "Index", content: "Home Page", format: "markdown"} | ||
91 | + end | ||
92 | + | ||
93 | + describe "#create" do | ||
94 | + after do | ||
95 | + destroy_page("Index") | ||
96 | + end | ||
97 | + | ||
98 | + context "with valid attributes" do | ||
99 | + it "saves the wiki page" do | ||
100 | + subject.create(@wiki_attr) | ||
101 | + wiki.find_page("Index").should_not be_nil | ||
102 | + end | ||
103 | + | ||
104 | + it "returns true" do | ||
105 | + subject.create(@wiki_attr).should == true | ||
106 | + end | ||
107 | + end | ||
108 | + end | ||
109 | + | ||
110 | + describe "#update" do | ||
111 | + before do | ||
112 | + create_page("Update", "content") | ||
113 | + @page = wiki.find_page("Update") | ||
114 | + end | ||
115 | + | ||
116 | + after do | ||
117 | + destroy_page("Update") | ||
118 | + end | ||
119 | + | ||
120 | + context "with valid attributes" do | ||
121 | + it "updates the content of the page" do | ||
122 | + @page.update("new content") | ||
123 | + @page = wiki.find_page("Update") | ||
124 | + end | ||
125 | + | ||
126 | + it "returns true" do | ||
127 | + @page.update("more content").should be_true | ||
128 | + end | ||
129 | + end | ||
130 | + end | ||
131 | + | ||
132 | + describe "#destroy" do | ||
133 | + before do | ||
134 | + create_page("Delete Page", "content") | ||
135 | + @page = wiki.find_page("Delete Page") | ||
136 | + end | ||
137 | + | ||
138 | + it "should delete the page" do | ||
139 | + @page.delete | ||
140 | + wiki.pages.should be_empty | ||
141 | + end | ||
142 | + | ||
143 | + it "should return true" do | ||
144 | + @page.delete.should == true | ||
145 | + end | ||
146 | + end | ||
147 | + | ||
148 | + describe "#versions" do | ||
149 | + before do | ||
150 | + create_page("Update", "content") | ||
151 | + @page = wiki.find_page("Update") | ||
152 | + end | ||
153 | + | ||
154 | + after do | ||
155 | + destroy_page("Update") | ||
156 | + end | ||
157 | + | ||
158 | + it "returns an array of all commits for the page" do | ||
159 | + 3.times { |i| @page.update("content #{i}") } | ||
160 | + @page.versions.count.should == 4 | ||
161 | + end | ||
162 | + end | ||
163 | + | ||
164 | +end |