Commit ea9b3687 authored by Dan Knox's avatar Dan Knox

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.
parent 8e8372d5
......@@ -99,6 +99,13 @@ gem "colored"
# GitLab settings
gem 'settingslogic'
# Wiki
# - Use latest master to resolve Gem dependency with Pygemnts
# github-linquist needs pygments 0.4.2 but Gollum 2.4.11
# requires pygments 0.3.2. The latest master Gollum has been updated
# to use pygments 0.4.2. Change this after next Gollum release.
gem "gollum", "~> 2.4.0", git: "git://github.com/github/gollum.git"
# Misc
gem "foreman"
gem "git"
......
GIT
remote: git://github.com/github/gollum.git
revision: 544d499ab170c9d9b355b7a0160afc74139ee2a4
specs:
gollum (2.4.11)
github-markdown (~> 0.5.3)
github-markup (>= 0.7.5, < 1.0.0)
grit (~> 2.5.0)
mustache (>= 0.99.4, < 1.0.0)
nokogiri (~> 1.5.6)
pygments.rb (~> 0.4.2)
sanitize (~> 2.0.3)
sinatra (~> 1.3.5)
stringex (~> 1.5.1)
useragent (~> 0.4.16)
GIT
remote: https://github.com/ctran/annotate_models.git
revision: be4e26825b521f0b2d86b181e2dff89901aa9b1e
......@@ -139,6 +155,7 @@ GEM
escape_utils (~> 0.2.3)
mime-types (~> 1.19)
pygments.rb (>= 0.2.13)
github-markdown (0.5.3)
github-markup (0.7.5)
gitlab-grack (1.0.0)
rack (~> 1.4.1)
......@@ -170,6 +187,10 @@ GEM
grape-entity (0.2.0)
activesupport
multi_json (>= 1.3.2)
grit (2.5.0)
diff-lcs (~> 1.1)
mime-types (~> 1.15)
posix-spawn (~> 0.3.6)
grit_ext (0.6.2)
charlock_holmes (~> 0.6.9)
growl (1.0.3)
......@@ -231,7 +252,8 @@ GEM
sprockets (~> 2.0)
multi_json (1.6.1)
multi_xml (0.5.3)
multipart-post (1.2.0)
multipart-post (1.1.5)
mustache (0.99.4)
mysql2 (0.3.11)
net-ldap (0.2.2)
nokogiri (1.5.6)
......@@ -365,6 +387,8 @@ GEM
rspec-mocks (~> 2.12.0)
rubyntlm (0.1.1)
rubyzip (0.9.9)
sanitize (2.0.3)
nokogiri (>= 1.4.4, < 1.6)
sass (3.2.5)
sass-rails (3.2.5)
railties (~> 3.2.0)
......@@ -418,6 +442,7 @@ GEM
tilt (~> 1.1, != 1.3.0)
stamp (0.5.0)
state_machine (1.1.2)
stringex (1.5.1)
temple (0.5.5)
test_after_commit (0.0.1)
therubyracer (0.10.2)
......@@ -440,6 +465,7 @@ GEM
kgio (~> 2.6)
rack
raindrops (~> 0.7)
useragent (0.4.16)
virtus (0.5.4)
backports (~> 2.6.1)
descendants_tracker (~> 0.0.1)
......@@ -487,6 +513,7 @@ DEPENDENCIES
gitlab_meta (= 5.0)
gitlab_omniauth-ldap (= 1.0.2)
gitlab_yaml_db (= 1.0.0)
gollum (~> 2.4.0)!
gon
grape (~> 0.3.1)
grape-entity (~> 0.2.0)
......
......@@ -33,6 +33,7 @@
@import "sections/login.scss";
@import "sections/editor.scss";
@import "sections/admin.scss";
@import "sections/wiki.scss";
@import "highlight/white.scss";
@import "highlight/dark.scss";
......
h3.page_title .edit-wiki-header {
width: 780px;
margin-left: auto;
margin-right: auto;
padding-right: 7px;
}
......@@ -2,58 +2,94 @@ class WikisController < ProjectResourceController
before_filter :authorize_read_wiki!
before_filter :authorize_write_wiki!, only: [:edit, :create, :history]
before_filter :authorize_admin_wiki!, only: :destroy
before_filter :load_gollum_wiki
def pages
@wiki_pages = @project.wikis.group(:slug).ordered
@wiki_pages = @gollum_wiki.pages
end
def show
@most_recent_wiki = @project.wikis.where(slug: params[:id]).ordered.first
if params[:version_id]
@wiki = @project.wikis.find(params[:version_id])
else
@wiki = @most_recent_wiki
end
@wiki = @gollum_wiki.find_page(params[:id], params[:version_id])
if @wiki
render 'show'
else
if can?(current_user, :write_wiki, @project)
@wiki = @project.wikis.new(slug: params[:id])
render 'edit'
else
render 'empty'
end
return render('empty') unless can?(current_user, :write_wiki, @project)
@wiki = WikiPage.new(@gollum_wiki)
@wiki.title = params[:id]
render 'edit'
end
end
def edit
@wiki = @project.wikis.where(slug: params[:id]).ordered.first
@wiki = Wiki.regenerate_from @wiki
@wiki = @gollum_wiki.find_page(params[:id])
end
def update
@wiki = @gollum_wiki.find_page(params[:id])
return render('empty') unless can?(current_user, :write_wiki, @project)
if @wiki.update(content, format, message)
redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.'
else
render 'edit'
end
end
def create
@wiki = @project.wikis.new(params[:wiki])
@wiki.user = current_user
respond_to do |format|
if @wiki.save
format.html { redirect_to [@project, @wiki], notice: 'Wiki was successfully updated.' }
else
format.html { render action: "edit" }
end
@wiki = WikiPage.new(@gollum_wiki)
if @wiki.create(wiki_params)
redirect_to project_wiki_path(@project, @wiki), notice: 'Wiki was successfully updated.'
else
render action: "edit"
end
end
def history
@wiki_pages = @project.wikis.where(slug: params[:id]).ordered
unless @wiki = @gollum_wiki.find_page(params[:id])
redirect_to project_wiki_path(@project, :home), notice: "Page not found"
end
end
def destroy
@wikis = @project.wikis.where(slug: params[:id]).delete_all
@wiki = @gollum_wiki.find_page(params[:id])
@wiki.delete if @wiki
redirect_to project_wiki_path(@project, :home), notice: "Page was successfully deleted"
end
respond_to do |format|
format.html { redirect_to project_wiki_path(@project, :index), notice: "Page was successfully deleted" }
end
def git_access
end
private
def load_gollum_wiki
@gollum_wiki = GollumWiki.new(@project, current_user)
# Call #wiki to make sure the Wiki Repo is initialized
@gollum_wiki.wiki
rescue GollumWiki::CouldNotCreateWikiError => ex
flash[:notice] = "Could not create Wiki Repository at this time. Please try again later."
redirect_to @project
return false
end
def wiki_params
params[:wiki].slice(:title, :content, :format, :message)
end
def content
params[:wiki][:content]
end
def format
params[:wiki][:format]
end
def message
params[:wiki][:message]
end
end
class GollumWiki
MARKUPS = {
"Markdown" => :markdown,
"Textile" => :textile,
"RDoc" => :rdoc,
"Org-mode" => :org,
"Creole" => :creole,
"reStructuredText" => :rest,
"AsciiDoc" => :asciidoc,
"MediaWiki" => :mediawiki,
"Pod" => :post
}
class CouldNotCreateWikiError < StandardError; end
# Returns a string describing what went wrong after
# an operation fails.
attr_reader :error_message
def initialize(project, user = nil)
@project = project
@user = user
end
def path_with_namespace
@project.path_with_namespace + ".wiki"
end
def url_to_repo
gitlab_shell.url_to_repo(path_with_namespace)
end
def ssh_url_to_repo
url_to_repo
end
def http_url_to_repo
http_url = [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
end
# Returns the Gollum::Wiki object.
def wiki
@wiki ||= begin
Gollum::Wiki.new(path_to_repo)
rescue Grit::NoSuchPathError
create_repo!
end
end
# Returns an Array of Gitlab WikiPage instances or an
# empty Array if this Wiki has no pages.
def pages
wiki.pages.map { |page| WikiPage.new(self, page, true) }
end
# Returns the last 30 Commit objects accross the entire
# repository.
def recent_history
Commit.fresh_commits(wiki.repo, 30)
end
# Finds a page within the repository based on a tile
# or slug.
#
# title - The human readable or parameterized title of
# the page.
#
# Returns an initialized WikiPage instance or nil
def find_page(title, version = nil)
if page = wiki.page(title, version)
WikiPage.new(self, page, true)
else
nil
end
end
def create_page(title, content, format = :markdown, message = nil)
commit = commit_details(:created, message, title)
wiki.write_page(title, format, content, commit)
rescue Gollum::DuplicatePageError => e
@error_message = "Duplicate page: #{e.message}"
return false
end
def update_page(page, content, format = :markdown, message = nil)
commit = commit_details(:updated, message, page.title)
wiki.update_page(page, page.name, format, content, commit)
end
def delete_page(page, message = nil)
wiki.delete_page(page, commit_details(:deleted, message, page.title))
end
private
def create_repo!
if gitlab_shell.add_repository(path_with_namespace)
Gollum::Wiki.new(path_to_repo)
else
raise CouldNotCreateWikiError
end
end
def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title)
{email: @user.email, name: @user.name, message: commit_message}
end
def default_message(action, title)
"#{@user.username} #{action} page: #{title}"
end
def gitlab_shell
@gitlab_shell ||= Gitlab::Shell.new
end
def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, "#{path_with_namespace}.git")
end
end
class WikiPage
include ActiveModel::Validations
include ActiveModel::Conversion
include StaticModel
extend ActiveModel::Naming
def self.primary_key
'slug'
end
def self.model_name
ActiveModel::Name.new(self, nil, 'wiki')
end
def to_key
[:slug]
end
validates :title, presence: true
validates :content, presence: true
# The Gitlab GollumWiki instance.
attr_reader :wiki
# The raw Gollum::Page instance.
attr_reader :page
# The attributes Hash used for storing and validating
# new Page values before writing to the Gollum repository.
attr_accessor :attributes
def initialize(wiki, page = nil, persisted = false)
@wiki = wiki
@page = page
@persisted = persisted
@attributes = {}.with_indifferent_access
set_attributes if persisted?
end
# The escaped URL path of this page.
def slug
@attributes[:slug]
end
alias :to_param :slug
# The formatted title of this page.
def title
@attributes[:title] || ""
end
# Sets the title of this page.
def title=(new_title)
@attributes[:title] = new_title
end
# The raw content of this page.
def content
@attributes[:content]
end
# The processed/formatted content of this page.
def formatted_content
@attributes[:formatted_content]
end
# The markup format for the page.
def format
@attributes[:format] || :markdown
end
# The commit message for this page version.
def message
version.try(:message)
end
# The Gitlab Commit instance for this page.
def version
return nil unless persisted?
@version ||= Commit.new(@page.version)
end
# Returns an array of Gitlab Commit instances.
def versions
return [] unless persisted?
@page.versions.map { |v| Commit.new(v) }
end
# Returns the Date that this latest version was
# created on.
def created_at
@page.version.date
end
# Returns boolean True or False if this instance
# is an old version of the page.
def historical?
@page.historical?
end
# Returns boolean True or False if this instance
# has been fully saved to disk or not.
def persisted?
@persisted == true
end
# Creates a new Wiki Page.
#
# attr - Hash of attributes to set on the new page.
# :title - The title for the new page.
# :content - The raw markup content.
# :format - Optional symbol representing the
# content format. Can be any type
# listed in the GollumWiki::MARKUPS
# Hash.
# :message - Optional commit message to set on
# the new page.
#
# Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful.
def create(attr = {})
@attributes.merge!(attr)
save :create_page, title, content, format, message
end
# Updates an existing Wiki Page, creating a new version.
#
# new_content - The raw markup content to replace the existing.
# format - Optional symbol representing the content format.
# See GollumWiki::MARKUPS Hash for available formats.
# message - Optional commit message to set on the new version.
#
# Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful.
def update(new_content = "", format = :markdown, message = nil)
@attributes[:content] = new_content
@attributes[:format] = format
save :update_page, @page, content, format, message
end
# Destroys the WIki Page.
#
# Returns boolean True or False.
def delete
if wiki.delete_page(@page)
true
else
false
end
end
private
def set_attributes
attributes[:slug] = @page.escaped_url_path
attributes[:title] = @page.title
attributes[:content] = @page.raw_data
attributes[:formatted_content] = @page.formatted_data
attributes[:format] = @page.format
end
def save(method, *args)
if valid? && wiki.send(method, *args)
@page = wiki.wiki.paged(title)
set_attributes
@persisted = true
else
errors.add(:base, wiki.error_message) if wiki.error_message
@persisted = false
end
@persisted
end
end
......@@ -18,6 +18,11 @@ class ProjectObserver < ActiveRecord::Observer
project.path_with_namespace
)
GitlabShellWorker.perform_async(
:remove_repository,
project.path_with_namespace + ".wiki"
)
project.satellite.destroy
log_info("Project \"#{project.name}\" was removed")
......
......@@ -41,6 +41,6 @@
- if @project.wiki_enabled
= nav_link(controller: :wikis) do
= link_to 'Wiki', project_wiki_path(@project, :index)
= link_to 'Wiki', project_wiki_path(@project, :home)
.content= yield
......@@ -8,9 +8,12 @@
.ui-box.ui-box-show
.ui-box-head
= f.label :title
.input= f.text_field :title, class: 'span8'
= f.hidden_field :slug
%h3.page_title
.edit-wiki-header
= @wiki.title.titleize
= f.hidden_field :title, value: @wiki.title
= f.select :format, options_for_select(GollumWiki::MARKUPS, {selected: @wiki.format}), {}, class: "pull-right input-medium"
= f.label :format, class: "pull-right", style: "padding-right: 20px;"
.ui-box-body
.input
%span.cgray
......@@ -22,6 +25,9 @@
.ui-box-bottom
= f.label :content
.input= f.text_area :content, class: 'span8 js-gfm-input'
.ui-box-bottom
= f.label :commit_message
.input= f.text_field :message, class: 'span8'
.actions
= f.submit 'Save', class: "btn-save btn"
= link_to "Cancel", project_wiki_path(@project, :index), class: "btn btn-cancel"
%span.pull-right
= link_to project_wiki_path(@project, :home), class: "btn btn-small grouped" do
Home
= link_to pages_project_wikis_path(@project), class: "btn btn-small grouped" do
Pages
- if (@wiki && @wiki.persisted?)
= link_to history_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
History
- if can?(current_user, :write_wiki, @project)
- if @wiki && @wiki.persisted?
= link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
%i.icon-edit
Edit
= link_to git_access_project_wikis_path(@project), class: "btn btn-small grouped" do
%i.icon-download-alt
Git Access
%h3.page_title Editing page
%h3.page_title
Editing page
= render partial: 'main_links'
%hr
= render 'form'
.pull-right
- if can? current_user, :admin_wiki, @project
= 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
Delete this page
\ No newline at end of file
= 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
Delete this page
%h3.page_title
Git Access
%strong= @gollum_wiki.path_with_namespace
= render partial: 'main_links'
%br
.content
.project_clone_panel
.row
.span7
.form-horizontal
.input-prepend.project_clone_holder
%button{class: "btn active", :"data-clone" => @gollum_wiki.ssh_url_to_repo} SSH
%button{class: "btn", :"data-clone" => @gollum_wiki.http_url_to_repo}= Gitlab.config.gitlab.protocol.upcase
= text_field_tag :project_clone, @gollum_wiki.url_to_repo, class: "one_click_select input-xxlarge", readonly: true
.git-empty
%fieldset
%legend Install Gollum:
%pre.dark
:preserve
gem install gollum
%legend Clone Your Wiki:
%pre.dark
:preserve
git clone #{@gollum_wiki.path_with_namespace}.git
cd #{@gollum_wiki.path_with_namespace}
%legend Start Gollum And Edit Locally:
%pre.dark
:preserve
gollum
== Sinatra/1.3.5 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.5.0 codename Knife)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
%h3.page_title
%span.cgray History for
= @wiki_pages.first.title
= @wiki.title.titleize
= render partial: 'main_links'
%br
%table
%thead
%tr
%th Page version
%th Author
%th Commit Message
%th Last updated
%th Updated by
%th Format
%tbody
- @wiki_pages.each_with_index do |wiki_page, i|
- @wiki.versions.each do |version|
- commit = CommitDecorator.new(version)
%tr
%td
%strong
= link_to project_wiki_path(@project, wiki_page, version_id: wiki_page.id) do
Version
= @wiki_pages.count - i
= link_to project_wiki_path(@project, @wiki, version_id: commit.id) do
= commit.short_id
%td= commit.author_link avatar: true, size: 24
%td
= commit.title
%td
= wiki_page.created_at.to_s(:short)
(#{time_ago_in_words(wiki_page.created_at)}
ago)
%td= link_to_member(@project, wiki_page.user)
= time_ago_in_words(version.date)
ago
%td
%strong
= @wiki.page.wiki.page(@wiki.page.name, commit.id).try(:format)
%h3.page_title All Pages
%h3.page_title
All Pages
= render partial: 'main_links'
%br
%table
%thead
%tr
%th Title
%th Slug
%th Format
%th Last updated
%th Updated by
%tbody
- @wiki_pages.each do |wiki_page|
%tr
%td
%strong= link_to wiki_page.title, project_wiki_path(@project, wiki_page)
%td= wiki_page.slug
%strong= link_to wiki_page.title.titleize, project_wiki_path(@project, wiki_page)
%td
%strong= wiki_page.format
%td
= wiki_page.created_at.to_s(:short) do
(#{time_ago_in_words(wiki_page.created_at)}
ago)
%td= link_to_member(@project, wiki_page.user)
- commit = CommitDecorator.decorate(wiki_page.version)
%td= commit.author_link avatar: true, size: 24