Commit ea9b3687db46bf876a6f966e61bfddc1e6d25ef3

Authored by Dan Knox
1 parent 8e8372d5

Replace current Wiki system with Gollum Wikis.

This commit replaces the old database backed Wiki system with the
excellent Gollum git based Wiki system.

The UI has been updated to allow for utilizing the extra features
that Gollum provides. Specifically:

* Edit page now allows you to choose the content format.
* Edit page allows you to provide a commit message for the change.
* History page now shows Format, Commit Message, and Commit Hash.
* A new Git Access page has been added with the Wiki Repo URL.
* The default page has been changed to Home from Index to match
the Gollum standard.

The old Wiki model has been left in tact to provide for the
development of a migration script that will move all content stored
in the old Wiki system into new Gollum Wikis.
Gemfile
... ... @@ -99,6 +99,13 @@ gem "colored"
99 99 # GitLab settings
100 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 109 # Misc
103 110 gem "foreman"
104 111 gem "git"
... ...
Gemfile.lock
1 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 18 remote: https://github.com/ctran/annotate_models.git
3 19 revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
4 20 specs:
... ... @@ -139,6 +155,7 @@ GEM
139 155 escape_utils (~> 0.2.3)
140 156 mime-types (~> 1.19)
141 157 pygments.rb (>= 0.2.13)
  158 + github-markdown (0.5.3)
142 159 github-markup (0.7.5)
143 160 gitlab-grack (1.0.0)
144 161 rack (~> 1.4.1)
... ... @@ -170,6 +187,10 @@ GEM
170 187 grape-entity (0.2.0)
171 188 activesupport
172 189 multi_json (>= 1.3.2)
  190 + grit (2.5.0)
  191 + diff-lcs (~> 1.1)
  192 + mime-types (~> 1.15)
  193 + posix-spawn (~> 0.3.6)
173 194 grit_ext (0.6.2)
174 195 charlock_holmes (~> 0.6.9)
175 196 growl (1.0.3)
... ... @@ -231,7 +252,8 @@ GEM
231 252 sprockets (~> 2.0)
232 253 multi_json (1.6.1)
233 254 multi_xml (0.5.3)
234   - multipart-post (1.2.0)
  255 + multipart-post (1.1.5)
  256 + mustache (0.99.4)
235 257 mysql2 (0.3.11)
236 258 net-ldap (0.2.2)
237 259 nokogiri (1.5.6)
... ... @@ -365,6 +387,8 @@ GEM
365 387 rspec-mocks (~> 2.12.0)
366 388 rubyntlm (0.1.1)
367 389 rubyzip (0.9.9)
  390 + sanitize (2.0.3)
  391 + nokogiri (>= 1.4.4, < 1.6)
368 392 sass (3.2.5)
369 393 sass-rails (3.2.5)
370 394 railties (~> 3.2.0)
... ... @@ -418,6 +442,7 @@ GEM
418 442 tilt (~> 1.1, != 1.3.0)
419 443 stamp (0.5.0)
420 444 state_machine (1.1.2)
  445 + stringex (1.5.1)
421 446 temple (0.5.5)
422 447 test_after_commit (0.0.1)
423 448 therubyracer (0.10.2)
... ... @@ -440,6 +465,7 @@ GEM
440 465 kgio (~> 2.6)
441 466 rack
442 467 raindrops (~> 0.7)
  468 + useragent (0.4.16)
443 469 virtus (0.5.4)
444 470 backports (~> 2.6.1)
445 471 descendants_tracker (~> 0.0.1)
... ... @@ -487,6 +513,7 @@ DEPENDENCIES
487 513 gitlab_meta (= 5.0)
488 514 gitlab_omniauth-ldap (= 1.0.2)
489 515 gitlab_yaml_db (= 1.0.0)
  516 + gollum (~> 2.4.0)!
490 517 gon
491 518 grape (~> 0.3.1)
492 519 grape-entity (~> 0.2.0)
... ...
app/assets/stylesheets/application.scss
... ... @@ -33,6 +33,7 @@
33 33 @import "sections/login.scss";
34 34 @import "sections/editor.scss";
35 35 @import "sections/admin.scss";
  36 +@import "sections/wiki.scss";
36 37  
37 38 @import "highlight/white.scss";
38 39 @import "highlight/dark.scss";
... ...
app/assets/stylesheets/sections/wiki.scss 0 → 100644
... ... @@ -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 2 before_filter :authorize_read_wiki!
3 3 before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
4 4 before_filter :authorize_admin_wiki!, only: :destroy
  5 + before_filter :load_gollum_wiki
5 6  
6 7 def pages
7   - @wiki_pages = @project.wikis.group(:slug).ordered
  8 + @wiki_pages = @gollum_wiki.pages
8 9 end
9 10  
10 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 14 if @wiki
19 15 render 'show'
20 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 22 end
28 23 end
29 24  
30 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 39 end
34 40  
35 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 48 end
46 49 end
47 50  
48 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 55 end
51 56  
52 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 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 95 end
... ...
app/models/gollum_wiki.rb 0 → 100644
... ... @@ -0,0 +1,125 @@
  1 +class GollumWiki
  2 +
  3 + MARKUPS = {
  4 + "Markdown" => :markdown,
  5 + "Textile" => :textile,
  6 + "RDoc" => :rdoc,
  7 + "Org-mode" => :org,
  8 + "Creole" => :creole,
  9 + "reStructuredText" => :rest,
  10 + "AsciiDoc" => :asciidoc,
  11 + "MediaWiki" => :mediawiki,
  12 + "Pod" => :post
  13 + }
  14 +
  15 + class CouldNotCreateWikiError < StandardError; end
  16 +
  17 + # Returns a string describing what went wrong after
  18 + # an operation fails.
  19 + attr_reader :error_message
  20 +
  21 + def initialize(project, user = nil)
  22 + @project = project
  23 + @user = user
  24 + end
  25 +
  26 + def path_with_namespace
  27 + @project.path_with_namespace + ".wiki"
  28 + end
  29 +
  30 + def url_to_repo
  31 + gitlab_shell.url_to_repo(path_with_namespace)
  32 + end
  33 +
  34 + def ssh_url_to_repo
  35 + url_to_repo
  36 + end
  37 +
  38 + def http_url_to_repo
  39 + http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
  40 + end
  41 +
  42 + # Returns the Gollum::Wiki object.
  43 + def wiki
  44 + @wiki ||= begin
  45 + Gollum::Wiki.new(path_to_repo)
  46 + rescue Grit::NoSuchPathError
  47 + create_repo!
  48 + end
  49 + end
  50 +
  51 + # Returns an Array of Gitlab WikiPage instances or an
  52 + # empty Array if this Wiki has no pages.
  53 + def pages
  54 + wiki.pages.map { |page| WikiPage.new(self, page, true) }
  55 + end
  56 +
  57 + # Returns the last 30 Commit objects accross the entire
  58 + # repository.
  59 + def recent_history
  60 + Commit.fresh_commits(wiki.repo, 30)
  61 + end
  62 +
  63 + # Finds a page within the repository based on a tile
  64 + # or slug.
  65 + #
  66 + # title - The human readable or parameterized title of
  67 + # the page.
  68 + #
  69 + # Returns an initialized WikiPage instance or nil
  70 + def find_page(title, version = nil)
  71 + if page = wiki.page(title, version)
  72 + WikiPage.new(self, page, true)
  73 + else
  74 + nil
  75 + end
  76 + end
  77 +
  78 + def create_page(title, content, format = :markdown, message = nil)
  79 + commit = commit_details(:created, message, title)
  80 +
  81 + wiki.write_page(title, format, content, commit)
  82 + rescue Gollum::DuplicatePageError => e
  83 + @error_message = "Duplicate page: #{e.message}"
  84 + return false
  85 + end
  86 +
  87 + def update_page(page, content, format = :markdown, message = nil)
  88 + commit = commit_details(:updated, message, page.title)
  89 +
  90 + wiki.update_page(page, page.name, format, content, commit)
  91 + end
  92 +
  93 + def delete_page(page, message = nil)
  94 + wiki.delete_page(page, commit_details(:deleted, message, page.title))
  95 + end
  96 +
  97 + private
  98 +
  99 + def create_repo!
  100 + if gitlab_shell.add_repository(path_with_namespace)
  101 + Gollum::Wiki.new(path_to_repo)
  102 + else
  103 + raise CouldNotCreateWikiError
  104 + end
  105 + end
  106 +
  107 + def commit_details(action, message = nil, title = nil)
  108 + commit_message = message || default_message(action, title)
  109 +
  110 + {email: @user.email, name: @user.name, message: commit_message}
  111 + end
  112 +
  113 + def default_message(action, title)
  114 + "#{@user.username} #{action} page: #{title}"
  115 + end
  116 +
  117 + def gitlab_shell
  118 + @gitlab_shell ||= Gitlab::Shell.new
  119 + end
  120 +
  121 + def path_to_repo
  122 + @path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
  123 + end
  124 +
  125 +end
... ...
app/models/wiki_page.rb 0 → 100644
... ... @@ -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 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 26 project.satellite.destroy
22 27  
23 28 log_info("Project \"#{project.name}\" was removed")
... ...
app/views/layouts/project_resource.html.haml
... ... @@ -41,6 +41,6 @@
41 41  
42 42 - if @project.wiki_enabled
43 43 = nav_link(controller: :wikis) do
44   - = link_to 'Wiki', project_wiki_path(@project, :index)
  44 + = link_to 'Wiki', project_wiki_path(@project, :home)
45 45  
46 46 .content= yield
... ...
app/views/wikis/_form.html.haml
... ... @@ -8,9 +8,12 @@
8 8  
9 9 .ui-box.ui-box-show
10 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 17 .ui-box-body
15 18 .input
16 19 %span.cgray
... ... @@ -22,6 +25,9 @@
22 25 .ui-box-bottom
23 26 = f.label :content
24 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 31 .actions
26 32 = f.submit 'Save', class: "btn-save btn"
27 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 @@
  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/edit.html.haml
1   -%h3.page_title Editing page
  1 +%h3.page_title
  2 + Editing page
  3 + = render partial: 'main_links'
2 4 %hr
3 5 = render 'form'
4 6  
5 7 .pull-right
6 8 - if can? current_user, :admin_wiki, @project
7   - = 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   - Delete this page
9 9 \ No newline at end of file
  10 + = link_to project_wikis_path(@project, @wiki), confirm: "Are you sure you want to delete this page?", method: :delete, class: "btn btn-small btn-remove" do
  11 + Delete this page
... ...
app/views/wikis/git_access.html.haml 0 → 100644
... ... @@ -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 1 %h3.page_title
2 2 %span.cgray History for
3   - = @wiki_pages.first.title
  3 + = @wiki.title.titleize
  4 + = render partial: 'main_links'
4 5 %br
5 6 %table
6 7 %thead
7 8 %tr
8 9 %th Page version
  10 + %th Author
  11 + %th Commit Message
9 12 %th Last updated
10   - %th Updated by
  13 + %th Format
11 14 %tbody
12   - - @wiki_pages.each_with_index do |wiki_page, i|
  15 + - @wiki.versions.each do |version|
  16 + - commit = CommitDecorator.new(version)
13 17 %tr
14 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 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   -%h3.page_title All Pages
  1 +%h3.page_title
  2 + All Pages
  3 + = render partial: 'main_links'
2 4 %br
3 5 %table
4 6 %thead
5 7 %tr
6 8 %th Title
7   - %th Slug
  9 + %th Format
8 10 %th Last updated
9 11 %th Updated by
10 12 %tbody
11 13 - @wiki_pages.each do |wiki_page|
12 14 %tr
13 15 %td
14   - %strong= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
15   - %td= wiki_page.slug
  16 + %strong= link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page)
  17 + %td
  18 + %strong= wiki_page.format
16 19 %td
17 20 = wiki_page.created_at.to_s(:short) do
18 21 (#{time_ago_in_words(wiki_page.created_at)}
19 22 ago)
20   - %td= link_to_member(@project, wiki_page.user)
  23 + - commit = CommitDecorator.decorate(wiki_page.version)
  24 + %td= commit.author_link avatar: true, size: 24
... ...
app/views/wikis/show.html.haml
1 1 %h3.page_title
2   - = @wiki.title
3   - %span.pull-right
4   - = link_to pages_project_wikis_path(@project), class: "btn btn-small grouped" do
5   - Pages
6   - - if can? current_user, :write_wiki, @project
7   - = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
8   - History
9   - = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
10   - %i.icon-edit
11   - Edit
  2 + = @wiki.title.titleize
  3 + = render partial: 'main_links'
12 4 %br
13   -- if @wiki != @most_recent_wiki
  5 +- if @wiki.historical?
14 6 .warning_message
15 7 This is an old version of this page.
16 8 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)}.
... ... @@ -18,6 +10,7 @@
18 10 .file_holder
19 11 .file_content.wiki
20 12 = preserve do
21   - = markdown @wiki.content
  13 + = @wiki.formatted_content.html_safe
22 14  
23   -%p.time Last edited by #{link_to_member @project, @wiki.user}, #{time_ago_in_words @wiki.created_at} ago
  15 +- commit = CommitDecorator.new(@wiki.version)
  16 +%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 185 resources :wikis, only: [:show, :edit, :destroy, :create] do
186 186 collection do
187 187 get :pages
  188 + put ':id' => 'wikis#update'
  189 + get :git_access
188 190 end
189 191  
190 192 member do
... ...
lib/api/internal.rb
... ... @@ -12,10 +12,18 @@ module Gitlab
12 12 # ref - branch name
13 13 #
14 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 22 key = Key.find(params[:key_id])
16   - project = Project.find_with_namespace(params[:project])
  23 + project = Project.find_with_namespace(project_path)
17 24 git_cmd = params[:action]
18 25  
  26 +
19 27 if key.is_deploy_key
20 28 project == key.project && git_cmd == 'git-upload-pack'
21 29 else
... ...
spec/features/gitlab_flavored_markdown_spec.rb
... ... @@ -208,24 +208,4 @@ describe &quot;Gitlab Flavored Markdown&quot; do
208 208 end
209 209 end
210 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 h3") 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 211 end
... ...
spec/models/gollum_wiki_spec.rb 0 → 100644
... ... @@ -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 @@
  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
... ...