Commit 3c050a29a76469b9f256322652f12b8d3cc2bac2

Authored by Dmitriy Zaporozhets
2 parents 4f23c30a 94f04939

Merge branch 'DanKnox-use_gollum_wikis'

@@ -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"
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/assets/stylesheets/sections/wiki.scss 0 → 100644
@@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
  1 +h3.page_title .edit-wiki-header {
  2 + width: 780px;
  3 + margin-left: auto;
  4 + margin-right: auto;
  5 + padding-right: 7px;
  6 +}
app/controllers/wikis_controller.rb
@@ -2,58 +2,94 @@ class WikisController &lt; ProjectResourceController @@ -2,58 +2,94 @@ class WikisController &lt; 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
app/models/gollum_wiki.rb 0 → 100644
@@ -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
app/models/wiki_page.rb 0 → 100644
@@ -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 &lt; ActiveRecord::Observer @@ -18,6 +18,11 @@ class ProjectObserver &lt; 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"
app/views/wikis/_main_links.html.haml 0 → 100644
@@ -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
app/views/wikis/git_access.html.haml 0 → 100644
@@ -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 &lt; Spinach::FeatureSteps @@ -4,17 +4,73 @@ class ProjectWiki &lt; 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
lib/tasks/gitlab/migrate_wiki.rake 0 → 100644
@@ -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
lib/wiki_to_gollum_migrator.rb 0 → 100644
@@ -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 &quot;Gitlab Flavored Markdown&quot; do @@ -207,25 +207,4 @@ describe &quot;Gitlab Flavored Markdown&quot; 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
spec/lib/wiki_to_gollum_migrator_spec.rb 0 → 100644
@@ -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
spec/models/gollum_wiki_spec.rb 0 → 100644
@@ -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
spec/models/wiki_page_spec.rb 0 → 100644
@@ -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