diff --git a/Gemfile b/Gemfile index 324e1ce2c1b..a532a7dcfe2 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 36447188359..0e35997ae9a 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 6b500b88823..d6f2fa9632e 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 00000000000..175911d7312 --- /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 69280291003..940b1e97340 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 + @wiki = WikiPage.new(@gollum_wiki) - 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 + 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 - - respond_to do |format| - format.html { redirect_to project_wiki_path(@project, :index), notice: "Page was successfully deleted" } - end + @wiki = @gollum_wiki.find_page(params[:id]) + @wiki.delete if @wiki + redirect_to project_wiki_path(@project, :home), 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 00000000000..91641ff1873 --- /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 00000000000..adc77b22231 --- /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 4b1f8295dc2..89dc97ac140 100644 --- a/app/observers/project_observer.rb +++ b/app/observers/project_observer.rb @@ -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") diff --git a/app/views/layouts/project_resource.html.haml b/app/views/layouts/project_resource.html.haml index 37d0f16fa20..3c53c0f2b46 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 7758b129b07..6fa41db4f7a 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 00000000000..262ed74681c --- /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 9e221aba47d..1e78d16e53b 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 00000000000..353d86f2d4d --- /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 18df8e1d71b..609207106ab 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 - = wiki_page.created_at.to_s(:short) - (#{time_ago_in_words(wiki_page.created_at)} - ago) - %td= link_to_member(@project, wiki_page.user) + = commit.title + %td + = 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 2e0f091ce72..fe35a2ede6b 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 7ff8b5cc01e..54d2a728504 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 b06fda8f85d..2c9f0fd97e5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -185,6 +185,8 @@ Gitlab::Application.routes.draw do 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 c85c01f87bb..654dbe8c4cb 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -12,10 +12,18 @@ module Gitlab # 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 769fcd688b4..05cef191cfc 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -208,24 +208,4 @@ describe "Gitlab Flavored Markdown" do 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 00000000000..87601683275 --- /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 00000000000..67f2a6da42d --- /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