From ea9b3687db46bf876a6f966e61bfddc1e6d25ef3 Mon Sep 17 00:00:00 2001
From: Dan Knox <dknox@threedotloft.com>
Date: Sun, 3 Mar 2013 19:43:52 -0800
Subject: [PATCH] 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                                       |   7 +
 Gemfile.lock                                  |  29 ++-
 app/assets/stylesheets/application.scss       |   1 +
 app/assets/stylesheets/sections/wiki.scss     |   6 +
 app/controllers/wikis_controller.rb           |  94 ++++++---
 app/models/gollum_wiki.rb                     | 125 +++++++++++
 app/models/wiki_page.rb                       | 181 ++++++++++++++++
 app/observers/project_observer.rb             |   5 +
 app/views/layouts/project_resource.html.haml  |   2 +-
 app/views/wikis/_form.html.haml               |  12 +-
 app/views/wikis/_main_links.html.haml         |  16 ++
 app/views/wikis/edit.html.haml                |   8 +-
 app/views/wikis/git_access.html.haml          |  36 ++++
 app/views/wikis/history.html.haml             |  28 ++-
 app/views/wikis/pages.html.haml               |  14 +-
 app/views/wikis/show.html.haml                |  19 +-
 config/routes.rb                              |   2 +
 lib/api/internal.rb                           |  10 +-
 .../features/gitlab_flavored_markdown_spec.rb |  20 --
 spec/models/gollum_wiki_spec.rb               | 196 ++++++++++++++++++
 spec/models/wiki_page_spec.rb                 | 164 +++++++++++++++
 21 files changed, 888 insertions(+), 87 deletions(-)
 create mode 100644 app/assets/stylesheets/sections/wiki.scss
 create mode 100644 app/models/gollum_wiki.rb
 create mode 100644 app/models/wiki_page.rb
 create mode 100644 app/views/wikis/_main_links.html.haml
 create mode 100644 app/views/wikis/git_access.html.haml
 create mode 100644 spec/models/gollum_wiki_spec.rb
 create mode 100644 spec/models/wiki_page_spec.rb

diff --git a/Gemfile b/Gemfile
index 324e1ce2c1b38..a532a7dcfe222 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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"
diff --git a/Gemfile.lock b/Gemfile.lock
index 3644718835972..0e35997ae9a2c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,3 +1,19 @@
+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)
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 6b500b88823a7..d6f2fa9632ecb 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -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";
diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/sections/wiki.scss
new file mode 100644
index 0000000000000..175911d731202
--- /dev/null
+++ b/app/assets/stylesheets/sections/wiki.scss
@@ -0,0 +1,6 @@
+h3.page_title .edit-wiki-header {
+  width: 780px;
+  margin-left: auto;
+  margin-right: auto;
+  padding-right: 7px;
+}
diff --git a/app/controllers/wikis_controller.rb b/app/controllers/wikis_controller.rb
index 6928029100304..940b1e9734051 100644
--- a/app/controllers/wikis_controller.rb
+++ b/app/controllers/wikis_controller.rb
@@ -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
diff --git a/app/models/gollum_wiki.rb b/app/models/gollum_wiki.rb
new file mode 100644
index 0000000000000..91641ff18730a
--- /dev/null
+++ b/app/models/gollum_wiki.rb
@@ -0,0 +1,125 @@
+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
diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb
new file mode 100644
index 0000000000000..adc77b22231eb
--- /dev/null
+++ b/app/models/wiki_page.rb
@@ -0,0 +1,181 @@
+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
diff --git a/app/observers/project_observer.rb b/app/observers/project_observer.rb
index 4b1f8295dc218..89dc97ac14025 100644
--- a/app/observers/project_observer.rb
+++ b/app/observers/project_observer.rb
@@ -18,6 +18,11 @@ def after_destroy(project)
       project.path_with_namespace
     )
 
+    GitlabShellWorker.perform_async(
+      :remove_repository,
+      project.path_with_namespace + ".wiki"
+    )
+
     project.satellite.destroy
 
     log_info("Project \"#{project.name}\" was removed")
diff --git a/app/views/layouts/project_resource.html.haml b/app/views/layouts/project_resource.html.haml
index 37d0f16fa20c7..3c53c0f2b46f7 100644
--- a/app/views/layouts/project_resource.html.haml
+++ b/app/views/layouts/project_resource.html.haml
@@ -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
diff --git a/app/views/wikis/_form.html.haml b/app/views/wikis/_form.html.haml
index 7758b129b07ae..6fa41db4f7a9e 100644
--- a/app/views/wikis/_form.html.haml
+++ b/app/views/wikis/_form.html.haml
@@ -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"
diff --git a/app/views/wikis/_main_links.html.haml b/app/views/wikis/_main_links.html.haml
new file mode 100644
index 0000000000000..262ed74681c5c
--- /dev/null
+++ b/app/views/wikis/_main_links.html.haml
@@ -0,0 +1,16 @@
+%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
diff --git a/app/views/wikis/edit.html.haml b/app/views/wikis/edit.html.haml
index 9e221aba47dfc..1e78d16e53baa 100644
--- a/app/views/wikis/edit.html.haml
+++ b/app/views/wikis/edit.html.haml
@@ -1,8 +1,10 @@
-%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
diff --git a/app/views/wikis/git_access.html.haml b/app/views/wikis/git_access.html.haml
new file mode 100644
index 0000000000000..353d86f2d4d3a
--- /dev/null
+++ b/app/views/wikis/git_access.html.haml
@@ -0,0 +1,36 @@
+%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
diff --git a/app/views/wikis/history.html.haml b/app/views/wikis/history.html.haml
index 18df8e1d71b6d..609207106aba5 100644
--- a/app/views/wikis/history.html.haml
+++ b/app/views/wikis/history.html.haml
@@ -1,23 +1,29 @@
 %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)
diff --git a/app/views/wikis/pages.html.haml b/app/views/wikis/pages.html.haml
index 2e0f091ce72cf..fe35a2ede6bf5 100644
--- a/app/views/wikis/pages.html.haml
+++ b/app/views/wikis/pages.html.haml
@@ -1,20 +1,24 @@
-%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
diff --git a/app/views/wikis/show.html.haml b/app/views/wikis/show.html.haml
index 7ff8b5cc01ec9..54d2a7285049e 100644
--- a/app/views/wikis/show.html.haml
+++ b/app/views/wikis/show.html.haml
@@ -1,16 +1,8 @@
 %h3.page_title
-  = @wiki.title
-  %span.pull-right
-    = link_to pages_project_wikis_path(@project), class: "btn btn-small grouped" do
-      Pages
-    - if can? current_user, :write_wiki, @project
-      = link_to history_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
-        History
-      = link_to edit_project_wiki_path(@project, @wiki), class: "btn btn-small grouped" do
-        %i.icon-edit
-        Edit
+  = @wiki.title.titleize
+  = render partial: 'main_links'
 %br
-- if @wiki != @most_recent_wiki
+- if @wiki.historical?
   .warning_message
     This is an old version of this page.
     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 @@
 .file_holder
   .file_content.wiki
     = preserve do
-      = markdown @wiki.content
+      = @wiki.formatted_content.html_safe
 
-%p.time Last edited by #{link_to_member @project, @wiki.user}, #{time_ago_in_words @wiki.created_at} ago
+- commit = CommitDecorator.new(@wiki.version)
+%p.time Last edited by #{commit.author_link(avatar: true, size: 16)} #{time_ago_in_words @wiki.created_at} ago
diff --git a/config/routes.rb b/config/routes.rb
index b06fda8f85da6..2c9f0fd97e56e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -185,6 +185,8 @@
     resources :wikis, only: [:show, :edit, :destroy, :create] do
       collection do
         get :pages
+        put ':id' => 'wikis#update'
+        get :git_access
       end
 
       member do
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index c85c01f87bb17..654dbe8c4cba7 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -12,10 +12,18 @@ class Internal < Grape::API
       #   ref - branch name
       #
       get "/allowed" do
+        # Check for *.wiki repositories.
+        # Strip out the .wiki from the pathname before finding the
+        # project. This applies the correct project permissions to
+        # the wiki repository as well.
+        project_path = params[:project]
+        project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/
+
         key = Key.find(params[:key_id])
-        project = Project.find_with_namespace(params[:project])
+        project = Project.find_with_namespace(project_path)
         git_cmd = params[:action]
 
+
         if key.is_deploy_key
           project == key.project && git_cmd == 'git-upload-pack'
         else
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 769fcd688b40e..05cef191cfc09 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -208,24 +208,4 @@
     end
   end
 
-
-  describe "for wikis" do
-    before do
-      visit project_wiki_path(project, :index)
-      fill_in "Title", with: "Circumvent ##{issue.id}"
-      fill_in "Content", with: "# Other pages\n\n* [Foo](foo)\n* [Bar](bar)\n\nAlso look at ##{issue.id} :-)"
-      click_on "Save"
-    end
-
-    it "should NOT render title in wikis#show" do
-      within(".content h3") do # page title
-        page.should have_content("Circumvent ##{issue.id}")
-        page.should_not have_link("##{issue.id}")
-      end
-    end
-
-    it "should render content in wikis#show" do
-      page.should have_link("##{issue.id}")
-    end
-  end
 end
diff --git a/spec/models/gollum_wiki_spec.rb b/spec/models/gollum_wiki_spec.rb
new file mode 100644
index 0000000000000..8760168327516
--- /dev/null
+++ b/spec/models/gollum_wiki_spec.rb
@@ -0,0 +1,196 @@
+require "spec_helper"
+
+describe GollumWiki do
+
+  def create_temp_repo(path)
+    FileUtils.mkdir_p path
+    command = "git init --quiet #{path};"
+    system(command)
+  end
+
+  def remove_temp_repo(path)
+    FileUtils.rm_rf path
+  end
+
+  def commit_details
+    commit = {name: user.name, email: user.email, message: "test commit"}
+  end
+
+  def create_page(name, content)
+    subject.wiki.write_page(name, :markdown, content, commit_details)
+  end
+
+  def destroy_page(page)
+    subject.wiki.delete_page(page, commit_details)
+  end
+
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+  let(:user) { project.owner }
+  let(:gitlab_shell) { Gitlab::Shell.new }
+
+  subject { GollumWiki.new(project, user) }
+
+  before do
+    create_temp_repo(subject.send(:path_to_repo))
+  end
+
+  describe "#path_with_namespace" do
+    it "returns the project path with namespace with the .wiki extension" do
+      subject.path_with_namespace.should == project.path_with_namespace + ".wiki"
+    end
+  end
+
+  describe "#url_to_repo" do
+    it "returns the correct ssh url to the repo" do
+      subject.url_to_repo.should == gitlab_shell.url_to_repo(subject.path_with_namespace)
+    end
+  end
+
+  describe "#ssh_url_to_repo" do
+    it "equals #url_to_repo" do
+      subject.ssh_url_to_repo.should == subject.url_to_repo
+    end
+  end
+
+  describe "#http_url_to_repo" do
+    it "provides the full http url to the repo" do
+      gitlab_url = Gitlab.config.gitlab.url
+      repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git"
+      subject.http_url_to_repo.should == repo_http_url
+    end
+  end
+
+  describe "#wiki" do
+    it "contains a Gollum::Wiki instance" do
+      subject.wiki.should be_a Gollum::Wiki
+    end
+
+    before do
+      Gitlab::Shell.any_instance.stub(:add_repository) do
+        create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git")
+      end
+      project.stub(:path_with_namespace).and_return("non-existant")
+    end
+
+    it "creates a new wiki repo if one does not yet exist" do
+      wiki = GollumWiki.new(project, user)
+      wiki.create_page("index", "test content").should_not == false
+
+      FileUtils.rm_rf wiki.send(:path_to_repo)
+    end
+
+    it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
+      Gitlab::Shell.any_instance.stub(:add_repository).and_return(false)
+      expect { GollumWiki.new(project, user).wiki }.to raise_exception(GollumWiki::CouldNotCreateWikiError)
+    end
+  end
+
+  describe "#pages" do
+    before do
+      create_page("index", "This is an awesome new Gollum Wiki")
+      @pages = subject.pages
+    end
+
+    after do
+      destroy_page(@pages.first.page)
+    end
+
+    it "returns an array of WikiPage instances" do
+      @pages.first.should be_a WikiPage
+    end
+
+    it "returns the correct number of pages" do
+      @pages.count.should == 1
+    end
+  end
+
+  describe "#find_page" do
+    before do
+      create_page("index page", "This is an awesome Gollum Wiki")
+    end
+
+    after do
+      destroy_page(subject.pages.first.page)
+    end
+
+    it "returns the latest version of the page if it exists" do
+      page = subject.find_page("index page")
+      page.title.should == "index page"
+    end
+
+    it "returns nil if the page does not exist" do
+      subject.find_page("non-existant").should == nil
+    end
+
+    it "can find a page by slug" do
+      page = subject.find_page("index-page")
+      page.title.should == "index page"
+    end
+
+    it "returns a WikiPage instance" do
+      page = subject.find_page("index page")
+      page.should be_a WikiPage
+    end
+  end
+
+  describe "#create_page" do
+    after do
+      destroy_page(subject.pages.first.page)
+    end
+
+    it "creates a new wiki page" do
+      subject.create_page("test page", "this is content").should_not == false
+      subject.pages.count.should == 1
+    end
+
+    it "returns false when a duplicate page exists" do
+      subject.create_page("test page", "content")
+      subject.create_page("test page", "content").should == false
+    end
+
+    it "stores an error message when a duplicate page exists" do
+      2.times { subject.create_page("test page", "content") }
+      subject.error_message.should =~ /Duplicate page:/
+    end
+
+    it "sets the correct commit message" do
+      subject.create_page("test page", "some content", :markdown, "commit message")
+      subject.pages.first.page.version.message.should == "commit message"
+    end
+  end
+
+  describe "#update_page" do
+    before do
+      create_page("update-page", "some content")
+      @gollum_page = subject.wiki.paged("update-page")
+      subject.update_page(@gollum_page, "some other content", :markdown, "updated page")
+      @page = subject.pages.first.page
+    end
+
+    after do
+      destroy_page(@page)
+    end
+
+    it "updates the content of the page" do
+      @page.raw_data.should == "some other content"
+    end
+
+    it "sets the correct commit message" do
+      @page.version.message.should == "updated page"
+    end
+  end
+
+  describe "#delete_page" do
+    before do
+      create_page("index", "some content")
+      @page = subject.wiki.paged("index")
+    end
+
+    it "deletes the page" do
+      subject.delete_page(@page)
+      subject.pages.count.should == 0
+    end
+  end
+
+end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
new file mode 100644
index 0000000000000..67f2a6da42d91
--- /dev/null
+++ b/spec/models/wiki_page_spec.rb
@@ -0,0 +1,164 @@
+require "spec_helper"
+
+describe WikiPage do
+
+  def create_temp_repo(path)
+    FileUtils.mkdir_p path
+    command = "git init --quiet #{path};"
+    system(command)
+  end
+
+  def remove_temp_repo(path)
+    FileUtils.rm_rf path
+  end
+
+  def commit_details
+    commit = {name: user.name, email: user.email, message: "test commit"}
+  end
+
+  def create_page(name, content)
+    wiki.wiki.write_page(name, :markdown, content, commit_details)
+  end
+
+  def destroy_page(title)
+    page = wiki.wiki.paged(title)
+    wiki.wiki.delete_page(page, commit_details)
+  end
+
+  let(:project) { create(:project) }
+  let(:repository) { project.repository }
+  let(:user) { project.owner }
+  let(:wiki) { GollumWiki.new(project, user) }
+
+  subject { WikiPage.new(wiki) }
+
+  before do
+    create_temp_repo(wiki.send(:path_to_repo))
+  end
+
+  describe "#initialize" do
+    context "when initialized with an existing gollum page" do
+      before do
+        create_page("test page", "test content")
+        @page = wiki.wiki.paged("test page")
+        @wiki_page = WikiPage.new(wiki, @page, true)
+      end
+
+      it "sets the slug attribute" do
+        @wiki_page.slug.should == "test-page"
+      end
+
+      it "sets the title attribute" do
+        @wiki_page.title.should == "test page"
+      end
+
+      it "sets the formatted content attribute" do
+        @wiki_page.content.should == "test content"
+      end
+
+      it "sets the format attribute" do
+        @wiki_page.format.should == :markdown
+      end
+
+      it "sets the message attribute" do
+        @wiki_page.message.should == "test commit"
+      end
+
+      it "sets the version attribute" do
+        @wiki_page.version.should be_a Commit
+      end
+    end
+  end
+
+  describe "validations" do
+    before do
+      subject.attributes = {title: 'title', content: 'content'}
+    end
+
+    it "validates presence of title" do
+      subject.attributes.delete(:title)
+      subject.valid?.should be_false
+    end
+
+    it "validates presence of content" do
+      subject.attributes.delete(:content)
+      subject.valid?.should be_false
+    end
+  end
+
+  before do
+    @wiki_attr = {title: "Index", content: "Home Page", format: "markdown"}
+  end
+
+  describe "#create" do
+    after do
+      destroy_page("Index")
+    end
+
+    context "with valid attributes" do
+      it "saves the wiki page" do
+        subject.create(@wiki_attr)
+        wiki.find_page("Index").should_not be_nil
+      end
+
+      it "returns true" do
+        subject.create(@wiki_attr).should == true
+      end
+    end
+  end
+
+  describe "#update" do
+    before do
+      create_page("Update", "content")
+      @page = wiki.find_page("Update")
+    end
+
+    after do
+      destroy_page("Update")
+    end
+
+    context "with valid attributes" do
+      it "updates the content of the page" do
+        @page.update("new content")
+        @page = wiki.find_page("Update")
+      end
+
+      it "returns true" do
+        @page.update("more content").should be_true
+      end
+    end
+  end
+
+  describe "#destroy" do
+    before do
+      create_page("Delete Page", "content")
+      @page = wiki.find_page("Delete Page")
+    end
+
+    it "should delete the page" do
+      @page.delete
+      wiki.pages.should be_empty
+    end
+
+    it "should return true" do
+      @page.delete.should == true
+    end
+  end
+
+  describe "#versions" do
+    before do
+      create_page("Update", "content")
+      @page = wiki.find_page("Update")
+    end
+
+    after do
+      destroy_page("Update")
+    end
+
+    it "returns an array of all commits for the page" do
+      3.times { |i| @page.update("content #{i}") }
+      @page.versions.count.should == 4
+    end
+  end
+
+end
-- 
GitLab