diff --git a/HISTORY.md b/HISTORY.md index e4f977dc..79c20873 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,15 +2,18 @@ * Major Enhancements * Add Page sidebars, similar to Page footers. + * Add the ability to revert commits to the wiki. + * Add MediaWiki support. * Minor Enhancements - * Add `:sanitization` and `:history_sanitization` options for customizing + * Add `:sanitization` and `:history_sanitization` options for customizing how `Sanitize.clean` modifies formatted wiki content. * Add `--config` option for the command line, to specify a ruby file that is run during startup. - * Provide access to a parsed Nokogiri::DocumentFragment during markup + * Provide access to a parsed Nokogiri::DocumentFragment during markup rendering for added customization. * Bug Fixes * Use `@wiki.page_class` in Gollum::Markup where appropriate (#63). + * Fix parsing of Org mode file links (#87). # 1.1.0 / 2010-10-28 diff --git a/README.md b/README.md index 5100bb18..04685350 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Gollum wikis are simply Git repositories that adhere to a specific format. Gollum pages may be written in a variety of formats and can be edited in a number of ways depending on your needs. You can edit your wiki locally: -* With your favorite text editor or IDE. +* With your favorite text editor or IDE (changes will be visible after committing). * With the built-in web interface. * With the Gollum Ruby API. @@ -35,6 +35,7 @@ to install the dependencies for the formats that you plan to use. * [RDoc](http://rdoc.sourceforge.net/) * [ReStructuredText](http://docutils.sourceforge.net/rst.html) -- `easy_install docutils` * [Textile](http://www.textism.com/tools/textile/) -- `gem install RedCloth` +* [MediaWiki](http://www.mediawiki.org/wiki/Help:Formatting) -- `gem install wikicloth` ## RUNNING @@ -74,6 +75,7 @@ current list of formats and allowed extensions is: * RDoc: .rdoc * ReStructuredText: .rest.txt, .rst.txt, .rest, .rst * Textile: .textile +* MediaWiki: .mediawiki, .wiki Gollum detects the page file format via the extension, so files must have one of the supported extensions in order to be converted. @@ -90,13 +92,19 @@ The special page file `Home.ext` (where the extension is one of the supported formats) will be used as the entrance page to your wiki. If it is missing, an automatically generated table of contents will be shown instead. +## SIDEBAR FILES + +Sidebar files allow you to add a simple sidebar to your wiki. Sidebar files +are named `_Sidebar.ext` where the extension is one of the supported formats. +Sidebars affect all pages in their directory and any subdirectories that do not +have a sidebar file of their own. ## FOOTER FILES Footer files allow you to add a simple footer to your wiki. Footer files must be named `_Footer.ext` where the extension is one of the supported formats. -Footers affect all pages in their directory and any subdirectories that do not -have a footer file of their own. +Like sidebars, footers affect all pages in their directory and any +subdirectories that do not have a footer file of their own. ## HTML SANITIZATION @@ -124,6 +132,9 @@ the link text displayed on the page. If the tag is an embedded image, the first thing in the tag will be a path to an image file. Use this trick to easily remember which order things should appear in tags. +Some formats, such as MediaWiki, support the opposite syntax: + + [[Page Title|Link]] ## PAGE LINKS @@ -210,7 +221,7 @@ the pipe. ## IMAGES To display images that are contained in the Gollum repository you should use -the Gollum Image Tag. This will display the actual image on the page. +the Gollum Image Tag. This will display the actual image on the page. [[gollum.png]] @@ -268,8 +279,8 @@ This is useful for writing about the link syntax in your wiki pages. ## SYNTAX HIGHLIGHTING In page files you can get automatic syntax highlighting for a wide range of -languages (courtesy of [Pygments](http://pygments.org/)) by using the -following syntax: +languages (courtesy of [Pygments](http://pygments.org/) - must install +separately) by using the following syntax: ```ruby def foo @@ -432,4 +443,4 @@ your changes merged back into core is as follows: 1. Do not change the version number, I will do that on my end 1. If necessary, rebase your commits into logical chunks, without errors 1. Push the branch up to GitHub -1. Send me (mojombo) a pull request for your branch \ No newline at end of file +1. Send a pull request to the github/gollum project. diff --git a/bin/gollum b/bin/gollum index 11d9e91b..5ce8ce7e 100755 --- a/bin/gollum +++ b/bin/gollum @@ -19,6 +19,8 @@ require 'gollum' exec = {} options = { 'port' => 4567, 'bind' => '0.0.0.0' } +wiki_options = {} + opts = OptionParser.new do |opts| opts.banner = help @@ -42,6 +44,10 @@ opts = OptionParser.new do |opts| opts.on("--irb", "Start an irb process with gollum loaded for the current wiki.") do options['irb'] = true end + + opts.on("--page-file-dir [PATH]", "Specify the sub directory for all page files (default: repository root).") do |path| + wiki_options[:page_file_dir] = path + end end # Read command line options into `options` hash @@ -67,13 +73,13 @@ if options['irb'] ARGV.replace(args) @__initialized = true end - + ws = WorkSpace.new(binding) irb = Irb.new(ws) - + @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC] @CONF[:MAIN_CONTEXT] = irb.context - + catch(:IRB_EXIT) do irb.eval_input end @@ -81,10 +87,10 @@ if options['irb'] end begin - wiki = Gollum::Wiki.new(gollum_path) + wiki = Gollum::Wiki.new(gollum_path, wiki_options) if !wiki.exist? then raise Grit::InvalidGitRepositoryError end puts "Loaded Gollum wiki at #{File.expand_path(gollum_path).inspect}." - puts + puts puts %( page = wiki.page('page-name')) puts %( # => ) puts @@ -103,11 +109,14 @@ if options['irb'] else require 'gollum/frontend/app' Precious::App.set(:gollum_path, gollum_path) + Precious::App.set(:wiki_options, wiki_options) + if cfg = options['config'] # If the path begins with a '/' it will be considered an absolute path, # otherwise it will be relative to the CWD - cfg = File.join(Dir.getwd, cfg) unless cfg.slice(0) == File::SEPARATOR + cfg = File.join(Dir.getwd, cfg) unless cfg.slice(0) == File::SEPARATOR require cfg end + Precious::App.run!(options) end diff --git a/gollum.gemspec b/gollum.gemspec index 74ae1c28..22764d55 100644 --- a/gollum.gemspec +++ b/gollum.gemspec @@ -23,9 +23,9 @@ Gem::Specification.new do |s| s.rdoc_options = ["--charset=UTF-8"] s.extra_rdoc_files = %w[README.md LICENSE] - s.add_dependency('grit', "~> 2.3") + s.add_dependency('grit', "~> 2.4.0") s.add_dependency('github-markup', [">= 0.4.0", "< 1.0.0"]) - s.add_dependency('albino', "~> 1.0") + s.add_dependency('albino', "~> 1.2.3") s.add_dependency('sinatra', "~> 1.0") s.add_dependency('mustache', [">= 0.11.2", "< 1.0.0"]) s.add_dependency('sanitize', "~> 1.1") diff --git a/lib/gollum/albino.rb b/lib/gollum/albino.rb index c3a18a27..b731ae9d 100644 --- a/lib/gollum/albino.rb +++ b/lib/gollum/albino.rb @@ -1,13 +1,8 @@ require 'albino' class Gollum::Albino < Albino - def self.bin - Albino.bin - end - - def bin - Albino.bin - end + self.bin = ::Albino.bin + self.default_encoding = ::Albino.default_encoding def colorize(options = {}) html = super.to_s diff --git a/lib/gollum/frontend/app.rb b/lib/gollum/frontend/app.rb index 0d0b540e..355cf826 100644 --- a/lib/gollum/frontend/app.rb +++ b/lib/gollum/frontend/app.rb @@ -44,7 +44,7 @@ module Precious get '/edit/*' do @name = params[:splat].first - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) if page = wiki.page(@name) @page = page @content = page.raw_data @@ -55,11 +55,11 @@ module Precious end post '/edit/*' do - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) page = wiki.page(params[:splat].first) name = params[:rename] || page.name msg = commit_message - update_wiki_page(wiki, page, params[:content], msg, name, + update_wiki_page(wiki, page, params[:content], msg, name, params[:format]) update_wiki_page(wiki, page.footer, params[:footer], msg) if params[:footer] update_wiki_page(wiki, page.sidebar, params[:sidebar], msg) if params[:sidebar] @@ -69,7 +69,7 @@ module Precious post '/create' do name = params[:page] - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) format = params[:format].intern @@ -83,7 +83,7 @@ module Precious end post '/revert/:page/*' do - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) @name = params[:page] @page = wiki.page(@name) shas = params[:splat].first.split("/") @@ -103,7 +103,7 @@ module Precious end post '/preview' do - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) @name = "Preview" @page = wiki.preview_page(@name, params[:content], params[:format]) @content = @page.formatted_data @@ -112,7 +112,7 @@ module Precious get '/history/:name' do @name = params[:name] - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) @page = wiki.page(@name) @page_num = [params[:page].to_i, 1].max @versions = @page.versions :page => @page_num @@ -134,16 +134,20 @@ module Precious get '/compare/:name/:version_list' do @name = params[:name] @versions = params[:version_list].split(/\.{2,3}/) - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) @page = wiki.page(@name) diffs = wiki.repo.diff(@versions.first, @versions.last, @page.path) @diff = diffs.first mustache :compare end + get %r{^/(javascript|css|images)} do + halt 404 + end + get %r{/(.+?)/([0-9a-f]{40})} do name = params[:captures][0] - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) if page = wiki.page(name, params[:captures][1]) @page = page @name = name @@ -156,7 +160,7 @@ module Precious get '/search' do @query = params[:q] - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) @results = wiki.search @query @name = @query mustache :search @@ -167,7 +171,7 @@ module Precious end def show_page_or_file(name) - wiki = Gollum::Wiki.new(settings.gollum_path) + wiki = Gollum::Wiki.new(settings.gollum_path, settings.wiki_options) if page = wiki.page(name) @page = page @name = name diff --git a/lib/gollum/git_access.rb b/lib/gollum/git_access.rb index 150044d3..808e4f2e 100644 --- a/lib/gollum/git_access.rb +++ b/lib/gollum/git_access.rb @@ -2,7 +2,15 @@ module Gollum # Controls all access to the Git objects from Gollum. Extend this class to # add custom caching for special cases. class GitAccess - def initialize(path) + # Initializes the GitAccess instance. + # + # path - The String path to the Git repository that holds the + # Gollum site. + # page_file_dir - String the directory in which all page files reside + # + # Returns this instance. + def initialize(path, page_file_dir = nil) + @page_file_dir = page_file_dir @path = path @repo = Grit::Repo.new(path) clear @@ -15,7 +23,7 @@ module Gollum @repo.git.exist? end - # Public: Converts a given Git reference to a SHA, using the cache if + # Public: Converts a given Git reference to a SHA, using the cache if # available. # # ref - a String Git reference (ex: "master") @@ -29,7 +37,7 @@ module Gollum end end - # Public: Gets a recursive list of Git blobs for the whole tree at the + # Public: Gets a recursive list of Git blobs for the whole tree at the # given commit. # # ref - A String Git reference or Git SHA to a commit. @@ -144,10 +152,17 @@ module Gollum # # Returns an Array of BlobEntry instances. def tree!(sha) - tree = @repo.git.native(:ls_tree, + tree = @repo.git.native(:ls_tree, {:r => true, :l => true, :z => true}, sha) - tree.split("\0").inject([]) do |items, line| - items << parse_tree_line(line) + items = tree.split("\0").inject([]) do |memo, line| + memo << parse_tree_line(line) + end + + if dir = @page_file_dir + regex = /^#{dir}\// + items.select { |i| i.path =~ regex } + else + items end end @@ -201,7 +216,7 @@ module Gollum # Parses a line of output from the `ls-tree` command. # # line - A String line of output: - # "100644 blob 839c2291b30495b9a882c17d08254d3c90d8fb53 Home.md" + # "100644 blob 839c2291b30495b9a882c17d08254d3c90d8fb53 Home.md" # # Returns an Array of BlobEntry instances. def parse_tree_line(line) diff --git a/lib/gollum/markup.rb b/lib/gollum/markup.rb index 6b8a4582..dbb6f4b8 100644 --- a/lib/gollum/markup.rb +++ b/lib/gollum/markup.rb @@ -13,6 +13,7 @@ module Gollum @name = page.filename @data = page.text_data @version = page.version.id + @format = page.format @dir = ::File.dirname(page.path) @tagmap = {} @codemap = {} @@ -28,8 +29,8 @@ module Gollum # # Returns the formatted String content. def render(no_follow = false) - sanitize = no_follow ? - @wiki.history_sanitizer : + sanitize = no_follow ? + @wiki.history_sanitizer : @wiki.sanitizer data = extract_tex(@data.dup) @@ -120,7 +121,18 @@ module Gollum if $1 == "'" && $3 != "'" "[[#{$2}]]#{$3}" elsif $2.include?('][') - $& + if $2[0..4] == 'file:' + pre = $1 + post = $3 + parts = $2.split('][') + parts[0][0..4] = "" + link = "#{parts[1]}|#{parts[0].sub(/\.org/,'')}" + id = Digest::SHA1.hexdigest(link) + @tagmap[id] = link + "#{pre}#{id}#{post}" + else + $& + end else id = Digest::SHA1.hexdigest($2) @tagmap[id] = $2 @@ -134,32 +146,28 @@ module Gollum # final markup. # # data - The String data (with placeholders). - # no_follow - Boolean that determines if rel="nofollow" is added to all - # tags. # # Returns the marked up String data. - def process_tags(data, no_follow = false) + def process_tags(data) @tagmap.each do |id, tag| - data.gsub!(id, process_tag(tag, no_follow)) + data.gsub!(id, process_tag(tag)) end data end # Process a single tag into its final HTML form. # - # tag - The String tag contents (the stuff inside the double + # tag - The String tag contents (the stuff inside the double # brackets). - # no_follow - Boolean that determines if rel="nofollow" is added to all - # tags. # # Returns the String HTML version of the tag. - def process_tag(tag, no_follow = false) + def process_tag(tag) if html = process_image_tag(tag) html - elsif html = process_file_link_tag(tag, no_follow) + elsif html = process_file_link_tag(tag) html else - process_page_link_tag(tag, no_follow) + process_page_link_tag(tag) end end @@ -171,6 +179,8 @@ module Gollum # if it is not. def process_image_tag(tag) parts = tag.split('|') + return if parts.size.zero? + name = parts[0].strip path = if file = find_file(name) ::File.join @wiki.base_path, file.path @@ -252,15 +262,15 @@ module Gollum # Attempt to process the tag as a file link tag. # - # tag - The String tag contents (the stuff inside the double + # tag - The String tag contents (the stuff inside the double # brackets). - # no_follow - Boolean that determines if rel="nofollow" is added to all - # tags. # # Returns the String HTML if the tag is a valid file link tag or nil # if it is not. - def process_file_link_tag(tag, no_follow = false) + def process_file_link_tag(tag) parts = tag.split('|') + return if parts.size.zero? + name = parts[0].strip path = parts[1] && parts[1].strip path = if path && file = find_file(path) @@ -271,33 +281,30 @@ module Gollum nil end - tag = if name && path && file + if name && path && file %{#{name}} elsif name && path %{#{name}} else nil end - if tag && no_follow - tag.sub! /^ tags. # # Returns the String HTML if the tag is a valid page link tag or nil # if it is not. - def process_page_link_tag(tag, no_follow = false) + def process_page_link_tag(tag) parts = tag.split('|') - name = parts[0].strip - cname = @wiki.page_class.cname((parts[1] || parts[0]).strip) - tag = if name =~ %r{^https?://} && parts[1].nil? + parts.reverse! if @format == :mediawiki + + name, page_name = *parts.compact.map(&:strip) + cname = @wiki.page_class.cname(page_name || name) + + if name =~ %r{^https?://} && page_name.nil? %{#{name}} else presence = "absent" @@ -310,10 +317,6 @@ module Gollum link = ::File.join(@wiki.base_path, CGI.escape(link_name)) %{#{name}} end - if tag && no_follow - tag.sub! /^ cached } : + @codemap[id] = cached ? + { :output => cached } : { :lang => $1, :code => $2 } id end @@ -379,12 +382,19 @@ module Gollum def process_code(data) @codemap.each do |id, spec| formatted = spec[:output] || begin - lang = spec[:lang] code = spec[:code] + lang = spec[:lang] + if code.lines.all? { |line| line =~ /\A\r?\n\Z/ || line =~ /^( |\t)/ } code.gsub!(/^( |\t)/m, '') end - formatted = Gollum::Albino.new(code, lang).colorize + + formatted = begin + lang && Gollum::Albino.colorize(code, lang) + rescue ::Albino::ShellArgumentError, ::Albino::Process::TimeoutExceeded, + ::Albino::Process::MaximumOutputExceeded + end + formatted ||= "
#{CGI.escapeHTML(code)}
" update_cache(:code, id, formatted) formatted end @@ -393,7 +403,7 @@ module Gollum data end - # Hook for getting the formatted value of extracted tag data. + # Hook for getting the formatted value of extracted tag data. # # type - Symbol value identifying what type of data is being extracted. # id - String SHA1 hash of original extracted tag data. diff --git a/lib/gollum/page.rb b/lib/gollum/page.rb index faa5f1a1..61d04484 100644 --- a/lib/gollum/page.rb +++ b/lib/gollum/page.rb @@ -41,6 +41,40 @@ module Gollum filename =~ /^_/ ? false : match end + # Public: The format of a given filename. + # + # filename - The String filename. + # + # Returns the Symbol format of the page. One of: + # [ :markdown | :textile | :rdoc | :org | :rest | :asciidoc | :pod | + # :roff ] + def self.format_for(filename) + case filename.to_s + when /\.(md|mkdn?|mdown|markdown)$/i + :markdown + when /\.(textile)$/i + :textile + when /\.(rdoc)$/i + :rdoc + when /\.(org)$/i + :org + when /\.(creole)$/i + :creole + when /\.(re?st(\.txt)?)$/i + :rest + when /\.(asciidoc)$/i + :asciidoc + when /\.(pod)$/i + :pod + when /\.(\d)$/i + :roff + when /\.(media)?wiki$/i + :mediawiki + else + nil + end + end + # Reusable filter to turn a filename (without path) into a canonical name. # Strips extension, converts spaces to dashes. # @@ -143,30 +177,7 @@ module Gollum # [ :markdown | :textile | :rdoc | :org | :rest | :asciidoc | :pod | # :roff ] def format - case @blob.name - when /\.(md|mkdn?|mdown|markdown)$/i - :markdown - when /\.(textile)$/i - :textile - when /\.(rdoc)$/i - :rdoc - when /\.(org)$/i - :org - when /\.(creole)$/i - :creole - when /\.(re?st(\.txt)?)$/i - :rest - when /\.(asciidoc)$/i - :asciidoc - when /\.(pod)$/i - :pod - when /\.(\d)$/i - :roff - when /\.(media)?wiki$/i - :mediawiki - else - nil - end + self.class.format_for(@blob.name) end # Public: The current version of the page. diff --git a/lib/gollum/wiki.rb b/lib/gollum/wiki.rb index d5ce1e84..76107586 100644 --- a/lib/gollum/wiki.rb +++ b/lib/gollum/wiki.rb @@ -59,7 +59,7 @@ module Gollum end end - # Gets the default sanitization options for current pages used by + # Gets the default sanitization options for current pages used by # instances of this Wiki. def sanitization if @sanitization.nil? @@ -68,7 +68,7 @@ module Gollum @sanitization end - # Gets the default sanitization options for older page revisions used by + # Gets the default sanitization options for older page revisions used by # instances of this Wiki. def history_sanitization if @history_sanitization.nil? @@ -94,17 +94,21 @@ module Gollum # Gets the sanitization options for older page revisions used by this Wiki. attr_reader :history_sanitization + # Gets the String directory in which all page files reside. + attr_reader :page_file_dir + # Public: Initialize a new Gollum Repo. # - # repo - The String path to the Git repository that holds the Gollum + # path - The String path to the Git repository that holds the Gollum # site. # options - Optional Hash: - # :base_path - String base path for all Wiki links. - # Default: "/" - # :page_class - The page Class. Default: Gollum::Page - # :file_class - The file Class. Default: Gollum::File - # :markup_class - The markup Class. Default: Gollum::Markup - # :sanitization - An instance of Sanitization. + # :base_path - String base path for all Wiki links. + # Default: "/" + # :page_class - The page Class. Default: Gollum::Page + # :file_class - The file Class. Default: Gollum::File + # :markup_class - The markup Class. Default: Gollum::Markup + # :sanitization - An instance of Sanitization. + # :page_file_dir - String the directory in which all page files reside # # Returns a fresh Gollum::Repo. def initialize(path, options = {}) @@ -112,15 +116,16 @@ module Gollum options[:access] = path path = path.path end - @path = path - @access = options[:access] || GitAccess.new(path) - @base_path = options[:base_path] || "/" - @page_class = options[:page_class] || self.class.page_class - @file_class = options[:file_class] || self.class.file_class - @markup_class = options[:markup_class] || self.class.markup_class - @repo = @access.repo - @sanitization = options[:sanitization] || self.class.sanitization - @history_sanitization = options[:history_sanitization] || + @path = path + @page_file_dir = options[:page_file_dir] + @access = options[:access] || GitAccess.new(path, @page_file_dir) + @base_path = options[:base_path] || "/" + @page_class = options[:page_class] || self.class.page_class + @file_class = options[:file_class] || self.class.file_class + @markup_class = options[:markup_class] || self.class.markup_class + @repo = @access.repo + @sanitization = options[:sanitization] || self.class.sanitization + @history_sanitization = options[:history_sanitization] || self.class.history_sanitization end @@ -257,6 +262,82 @@ module Gollum sha1 end + # Public: Applies a reverse diff for a given page. If only 1 SHA is given, + # the reverse diff will be taken from its parent (^SHA...SHA). If two SHAs + # are given, the reverse diff is taken from SHA1...SHA2. + # + # page - The Gollum::Page to delete. + # sha1 - String SHA1 of the earlier parent if two SHAs are given, + # or the child. + # sha2 - Optional String SHA1 of the child. + # commit - The commit Hash details: + # :message - The String commit message. + # :name - The String author full name. + # :email - The String email address. + # + # Returns a String SHA1 of the new commit, or nil if the reverse diff does + # not apply. + def revert_page(page, sha1, sha2 = nil, commit = {}) + if sha2.is_a?(Hash) + commit = sha2 + sha2 = nil + end + + pcommit = @repo.commit('master') + patch = full_reverse_diff_for(page, sha1, sha2) + commit[:parent] = [pcommit] + commit[:tree] = @repo.git.apply_patch(pcommit.sha, patch) + return false unless commit[:tree] + + index = nil + sha1 = commit_index(commit) { |i| index = i } + @access.refresh + + files = [] + if page + files << [page.path, page.name, page.format] + else + # Grit::Diff can't parse reverse diffs.... yet + lines = patch.split("\n") + while line = lines.shift + if line =~ %r{^diff --git b/.+? a/(.+)$} + path = $1 + ext = ::File.extname(path) + name = ::File.basename(path, ext) + if format = ::Gollum::Page.format_for(ext) + files << [path, name, format] + end + end + end + end + + files.each do |(path, name, format)| + dir = ::File.dirname(path) + dir = '' if dir == '.' + update_working_dir(index, dir, name, format) + end + + sha1 + end + + # Public: Applies a reverse diff to the repo. If only 1 SHA is given, + # the reverse diff will be taken from its parent (^SHA...SHA). If two SHAs + # are given, the reverse diff is taken from SHA1...SHA2. + # + # sha1 - String SHA1 of the earlier parent if two SHAs are given, + # or the child. + # sha2 - Optional String SHA1 of the child. + # commit - The commit Hash details: + # :message - The String commit message. + # :name - The String author full name. + # :email - The String email address. + # + # Returns a String SHA1 of the new commit, or nil if the reverse diff does + # not apply. + def revert_commit(sha1, sha2 = nil, commit = {}) + revert_page(nil, sha1, sha2, commit) + end + # Public: Lists all pages for this wiki. # # treeish - The String commit ID or ref to find (default: master) @@ -266,7 +347,7 @@ module Gollum tree_list(treeish || 'master') end - # Public: Returns the number of pages accessible from a commit + # Public: Returns the number of pages accessible from a commit # # ref - A String ref that is either a commit SHA or references one. # @@ -285,10 +366,10 @@ module Gollum # # Returns an Array with Objects of page name and count of matches def search(query) - # See: http://github.com/Sirupsen/gollum/commit/f0a6f52bdaf6bee8253ca33bb3fceaeb27bfb87e - search_output = @repo.git.grep({:c => query}, 'master') + args = [{:c => query}, 'master', '--'] + args << '--' << @page_file_dir if @page_file_dir - search_output.split("\n").collect do |line| + @repo.git.grep(*args).split("\n").map! do |line| result = line.split(':') file_name = Gollum::Page.canonicalize_filename(::File.basename(result[1])) @@ -310,28 +391,6 @@ module Gollum @repo.log('master', nil, log_pagination_options(options)) end - def revert_page(page, sha1, sha2 = nil, commit = {}) - if sha2.is_a?(Hash) - commit = sha2 - sha2 = nil - end - - pcommit = @repo.commit('master') - patch = full_reverse_diff_for(page, sha1, sha2) - commit[:parent] = [pcommit] - commit[:tree] = @repo.git.apply_patch(pcommit.sha, patch) - return false unless commit[:tree] - - index = nil - sha1 = commit_index(commit) { |i| index = i } - dir = ::File.dirname(page.path) - dir = '' if dir == '.' - - @access.refresh - update_working_dir(index, dir, page.name, page.format) - sha1 - end - # Public: Refreshes just the cached Git reference data. This should # be called after every Gollum update. # @@ -340,7 +399,7 @@ module Gollum @access.refresh end - # Public: Creates a Sanitize instance using the Wiki's sanitization + # Public: Creates a Sanitize instance using the Wiki's sanitization # options. # # Returns a Sanitize instance. @@ -350,7 +409,7 @@ module Gollum end end - # Public: Creates a Sanitize instance using the Wiki's history sanitization + # Public: Creates a Sanitize instance using the Wiki's history sanitization # options. # # Returns a Sanitize instance. @@ -416,13 +475,17 @@ module Gollum # Returns nothing. def update_working_dir(index, dir, name, format) unless @repo.bare - path = - if dir == '' - page_file_name(name, format) - else - ::File.join(dir, page_file_name(name, format)) + if @page_file_dir + dir = dir.size.zero? ? @page_file_dir : File.join(dir, @page_file_dir) end + path = + if dir == '' + page_file_name(name, format) + else + ::File.join(dir, page_file_name(name, format)) + end + Dir.chdir(::File.join(@repo.path, '..')) do if file_path_scheduled_for_deletion?(index.tree, path) @repo.git.rm({'f' => true}, '--', path) @@ -517,7 +580,7 @@ module Gollum dir = '/' if dir.strip.empty? - fullpath = ::File.join(dir, path) + fullpath = ::File.join(*[@page_file_dir, dir, path].compact) fullpath = fullpath[1..-1] if fullpath =~ /^\// if index.current_tree && tree = index.current_tree / dir @@ -536,6 +599,14 @@ module Gollum index.add(fullpath, normalize(data)) end + # Commits to the repo. This is a common method used by Gollum for + # creating, updating, and deleting pages. There are typically three steps: + # building an index with the current tree, yielding the index for + # modification, and then writing the commit. + # + # options - Hash of option + # + # Returns the String SHA of the new Commit. def commit_index(options = {}) normalize_commit(options) parents = [options[:parent] || @repo.commit('master')] @@ -549,13 +620,38 @@ module Gollum end yield index if block_given? + options[:name] = default_committer_name if options[:name].to_s.empty? + options[:email] = default_committer_email if options[:email].to_s.empty? actor = Grit::Actor.new(options[:name], options[:email]) index.commit(options[:message], parents, actor) end + # Creates a reverse diff for the given SHAs on the given Gollum::Page. + # + # page - The Gollum::Page to scope the patch to, or a String Path. + # sha1 - String SHA1 of the earlier parent if two SHAs are given, + # or the child. + # sha2 - Optional String SHA1 of the child. + # + # Returns a String of the reverse Diff to apply. def full_reverse_diff_for(page, sha1, sha2 = nil) sha1, sha2 = "#{sha1}^", sha1 if sha2.nil? - repo.git.native(:diff, {:R => true}, sha1, sha2, '--', page.path) + args = [{:R => true}, sha1, sha2] + if page + args << '--' << (page.respond_to?(:path) ? page.path : page.to_s) + end + repo.git.native(:diff, *args) + end + + # Creates a reverse diff for the given SHAs. + # + # sha1 - String SHA1 of the earlier parent if two SHAs are given, + # or the child. + # sha2 - Optional String SHA1 of the child. + # + # Returns a String of the reverse Diff to apply. + def full_reverse_diff(sha1, sha2 = nil) + full_reverse_diff_for(nil, sha1, sha2) end # Ensures a commit hash has all the required fields for a commit. @@ -573,17 +669,26 @@ module Gollum end # Gets the default name for commits. + # + # Returns the String name. def default_committer_name @default_committer_name ||= \ @repo.config['user.name'] || self.class.default_committer_name end # Gets the default email for commits. + # + # Returns the String email address. def default_committer_email @default_committer_email ||= \ @repo.config['user.email'] || self.class.default_committer_email end + # Gets the commit object for the given ref or sha. + # + # ref - A string ref or SHA pointing to a valid commit. + # + # Returns a Grit::Commit instance. def commit_for(ref) @access.commit(ref) rescue Grit::GitRuby::Repository::NoSuchShaFound diff --git a/test/examples/page_file_dir.git/COMMIT_EDITMSG b/test/examples/page_file_dir.git/COMMIT_EDITMSG new file mode 100644 index 00000000..80260766 --- /dev/null +++ b/test/examples/page_file_dir.git/COMMIT_EDITMSG @@ -0,0 +1 @@ +initial commit diff --git a/test/examples/page_file_dir.git/HEAD b/test/examples/page_file_dir.git/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/test/examples/page_file_dir.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/examples/page_file_dir.git/config b/test/examples/page_file_dir.git/config new file mode 100644 index 00000000..af107929 --- /dev/null +++ b/test/examples/page_file_dir.git/config @@ -0,0 +1,6 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true diff --git a/test/examples/page_file_dir.git/description b/test/examples/page_file_dir.git/description new file mode 100644 index 00000000..498b267a --- /dev/null +++ b/test/examples/page_file_dir.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/test/examples/page_file_dir.git/index b/test/examples/page_file_dir.git/index new file mode 100644 index 00000000..85640369 Binary files /dev/null and b/test/examples/page_file_dir.git/index differ diff --git a/test/examples/page_file_dir.git/info/exclude b/test/examples/page_file_dir.git/info/exclude new file mode 100644 index 00000000..a5196d1b --- /dev/null +++ b/test/examples/page_file_dir.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/test/examples/page_file_dir.git/logs/HEAD b/test/examples/page_file_dir.git/logs/HEAD new file mode 100644 index 00000000..60cde1b1 --- /dev/null +++ b/test/examples/page_file_dir.git/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 22b404803c966dd92865614d86ff22ca12e50c1e rick 1295287591 -0800 commit (initial): initial commit diff --git a/test/examples/page_file_dir.git/logs/refs/heads/master b/test/examples/page_file_dir.git/logs/refs/heads/master new file mode 100644 index 00000000..60cde1b1 --- /dev/null +++ b/test/examples/page_file_dir.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 22b404803c966dd92865614d86ff22ca12e50c1e rick 1295287591 -0800 commit (initial): initial commit diff --git a/test/examples/page_file_dir.git/objects/0c/7d27db1f575263efdcab3dc650f4502a2dbcbf b/test/examples/page_file_dir.git/objects/0c/7d27db1f575263efdcab3dc650f4502a2dbcbf new file mode 100644 index 00000000..bffaebf0 Binary files /dev/null and b/test/examples/page_file_dir.git/objects/0c/7d27db1f575263efdcab3dc650f4502a2dbcbf differ diff --git a/test/examples/page_file_dir.git/objects/22/b404803c966dd92865614d86ff22ca12e50c1e b/test/examples/page_file_dir.git/objects/22/b404803c966dd92865614d86ff22ca12e50c1e new file mode 100644 index 00000000..a52143a1 Binary files /dev/null and b/test/examples/page_file_dir.git/objects/22/b404803c966dd92865614d86ff22ca12e50c1e differ diff --git a/test/examples/page_file_dir.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 b/test/examples/page_file_dir.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 new file mode 100644 index 00000000..bdcf704c Binary files /dev/null and b/test/examples/page_file_dir.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 differ diff --git a/test/examples/page_file_dir.git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 b/test/examples/page_file_dir.git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 new file mode 100644 index 00000000..cfc3920f Binary files /dev/null and b/test/examples/page_file_dir.git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 differ diff --git a/test/examples/page_file_dir.git/objects/5b/43e14e0a15fb6f08feab1773d1c0991e9f71e2 b/test/examples/page_file_dir.git/objects/5b/43e14e0a15fb6f08feab1773d1c0991e9f71e2 new file mode 100644 index 00000000..effa1c00 Binary files /dev/null and b/test/examples/page_file_dir.git/objects/5b/43e14e0a15fb6f08feab1773d1c0991e9f71e2 differ diff --git a/test/examples/page_file_dir.git/refs/heads/master b/test/examples/page_file_dir.git/refs/heads/master new file mode 100644 index 00000000..46106185 --- /dev/null +++ b/test/examples/page_file_dir.git/refs/heads/master @@ -0,0 +1 @@ +22b404803c966dd92865614d86ff22ca12e50c1e diff --git a/test/test_app.rb b/test/test_app.rb index f39506b5..96aa3f19 100644 --- a/test/test_app.rb +++ b/test/test_app.rb @@ -8,6 +8,7 @@ context "Frontend" do @path = cloned_testpath("examples/revert.git") @wiki = Gollum::Wiki.new(@path) Precious::App.set(:gollum_path, @path) + Precious::App.set(:wiki_options, {}) end teardown do @@ -16,7 +17,7 @@ context "Frontend" do test "edits page" do page_1 = @wiki.page('A') - post "/edit/A", :content => 'abc', + post "/edit/A", :content => 'abc', :format => page_1.format, :message => 'def' follow_redirect! assert last_response.ok? @@ -56,7 +57,7 @@ context "Frontend" do test "renames page" do page_1 = @wiki.page('B') - post "/edit/B", :content => 'abc', + post "/edit/B", :content => 'abc', :rename => "C", :format => page_1.format, :message => 'def' follow_redirect! diff --git a/test/test_markup.rb b/test/test_markup.rb index 098b9512..4f759ca3 100644 --- a/test/test_markup.rb +++ b/test/test_markup.rb @@ -162,6 +162,20 @@ context "Markup" do assert_equal "

a http://example.com b

", page.formatted_data end + test "page link with different text" do + @wiki.write_page("Potato", :markdown, "a [[Potato Heaad|Potato]] ", commit_details) + page = @wiki.page("Potato") + output = page.formatted_data + assert_equal "

a Potato Heaad

", output + end + + test "page link with different text on mediawiki" do + @wiki.write_page("Potato", :mediawiki, "a [[Potato|Potato Heaad]] ", commit_details) + page = @wiki.page("Potato") + output = page.formatted_data + assert_equal "

\na Potato Heaad \n

", output + end + ######################################################################### # # Images @@ -343,7 +357,7 @@ context "Markup" do content = "a\n\n```ruby\nx = 1\n```\n\nb" output = "

a

\n\n
" +
              "x = " +
-             "1\n
\n
\n\n

b

" + "1\n\n\n\n\n

b

" index = @wiki.repo.index index.add("Bilbo-Baggins.md", content) @@ -358,7 +372,7 @@ context "Markup" do content = "a\r\n\r\n```ruby\r\nx = 1\r\n```\r\n\r\nb" output = "

a

\n\n
" +
              "x = " +
-             "1\n
\n
\n\n

b

" + "1\n\n\n\n\n

b

" index = @wiki.repo.index index.add("Bilbo-Baggins.md", content) @@ -374,7 +388,7 @@ context "Markup" do output = "

a

\n\n
" +
              "x = 1" +
              "\n\ny =" +
-             " 2\n
\n
\n\n

b

" + " 2\n\n\n\n\n

b

" compare(content, output) end @@ -383,7 +397,19 @@ context "Markup" do output = "

a

\n\n
" +
              "x = 1" +
              "\n\ny =" +
-             " 2\n
\n
\n\n

b

" + " 2\n\n\n\n\n

b

" + compare(content, output) + end + + test "code block with invalid lang" do + content = "a\n\n``` ls -al;\n\tbooya\n\tboom\n```\n\nb" + output = "

a

\n\n
booya\nboom
\n\n

b

" + compare(content, output) + end + + test "code block with no lang" do + content = "a\n\n```\n\tls -al;\n\t\n```\n\nb" + output = "

a

\n\n
ls -al;\n<booya>
\n\n

b

" compare(content, output) end @@ -414,6 +440,24 @@ context "Markup" do compare(content, output, 'org') end + test "org mode style double file links" do + content = "a [[file:f.org][Google]] b" + output = "

a Google b

" + compare(content, output, 'org') + end + + test "short double links" do + content = "a [[b]] c" + output = %(

a b c

) + compare(content, output, 'org') + end + + test "double linked pipe" do + content = "a [[|]] b" + output = %(

a b

) + compare(content, output, 'org') + end + ######################################################################### # # TeX diff --git a/test/test_page_revert.rb b/test/test_page_revert.rb index 9f96852f..6dcb4bde 100644 --- a/test/test_page_revert.rb +++ b/test/test_page_revert.rb @@ -12,19 +12,30 @@ context "Page Reverting" do end test "reverts single commit" do + page1 = @wiki.page("B") + sha = @wiki.revert_commit('7c45b5f16ff3bae2a0063191ef832701214d4df5') + page2 = @wiki.page("B") + assert_equal sha, page2.version.sha + assert_equal "INITIAL", body=page2.raw_data.strip + assert_equal body, File.read(File.join(@path, "B.md")).strip + end + + test "reverts single commit for a page" do page1 = @wiki.page('B') sha = @wiki.revert_page(page1, '7c45b5f16ff3bae2a0063191ef832701214d4df5') page2 = @wiki.page('B') assert_equal sha, page2.version.sha - assert_equal "INITIAL", page2.raw_data.strip + assert_equal "INITIAL", body=page2.raw_data.strip + assert_equal body, File.read(File.join(@path, "B.md")).strip end - test "reverts multiple commits" do + test "reverts multiple commits for a page" do page1 = @wiki.page('A') sha = @wiki.revert_page(page1, '302a5491a9a5ba12c7652ac831a44961afa312d2^', 'b26b791cb7917c4f37dd9cb4d1e0efb24ac4d26f') page2 = @wiki.page('A') assert_equal sha, page2.version.sha - assert_equal "INITIAL", page2.raw_data.strip + assert_equal "INITIAL", body=page2.raw_data.strip + assert_equal body, File.read(File.join(@path, "A.md")).strip end test "cannot revert conflicting commit" do diff --git a/test/test_wiki.rb b/test/test_wiki.rb index b4766ca2..1f862dd0 100644 --- a/test/test_wiki.rb +++ b/test/test_wiki.rb @@ -61,30 +61,6 @@ context "Wiki" do @wiki.normalize_commit(commit.dup)) end - #test "#tree_map_for caches ref and tree" do - # assert @wiki.ref_map.empty? - # assert @wiki.tree_map.empty? - # @wiki.tree_map_for 'master' - # assert_equal({"master"=>"60f12f4254f58801b9ee7db7bca5fa8aeefaa56b"}, @wiki.ref_map) - # - # map = @wiki.tree_map['60f12f4254f58801b9ee7db7bca5fa8aeefaa56b'] - # assert_equal 'Bilbo-Baggins.md', map[0].path - # assert_equal '', map[0].dir - # assert_equal map[0].path, map[0].name - # assert_equal 'Mordor/Eye-Of-Sauron.md', map[3].path - # assert_equal '/Mordor', map[3].dir - # assert_equal 'Eye-Of-Sauron.md', map[3].name - #end - # - #test "#tree_map_for only caches tree for commit" do - # assert @wiki.tree_map.empty? - # @wiki.tree_map_for '60f12f4254f58801b9ee7db7bca5fa8aeefaa56b' - # assert @wiki.ref_map.empty? - # - # entry = @wiki.tree_map['60f12f4254f58801b9ee7db7bca5fa8aeefaa56b'][0] - # assert_equal 'Bilbo-Baggins.md', entry.path - #end - test "text_data" do wiki = Gollum::Wiki.new(testpath("examples/yubiwa.git")) if String.instance_methods.include?(:encoding) @@ -97,6 +73,19 @@ context "Wiki" do assert_equal page.raw_data, page.text_data end end + + test "gets reverse diff" do + diff = @wiki.full_reverse_diff('a8ad3c09dd842a3517085bfadd37718856dee813') + assert_match "b/Mordor/_Sidebar.md", diff + assert_match "b/_Sidebar.md", diff + end + + test "gets reverse diff for a page" do + diff = @wiki.full_reverse_diff_for('_Sidebar.md', 'a8ad3c09dd842a3517085bfadd37718856dee813') + regex = /b\/Mordor\/\_Sidebar\.md/ + assert_match "b/_Sidebar.md", diff + assert_no_match regex, diff + end end context "Wiki page previewing" do @@ -296,3 +285,36 @@ context "Wiki sync with working directory" do FileUtils.rm_r(@path) end end + +context "page_file_dir option" do + setup do + @path = cloned_testpath('examples/page_file_dir') + @repo = Grit::Repo.init(@path) + @page_file_dir = 'docs' + @wiki = Gollum::Wiki.new(@path, :page_file_dir => @page_file_dir) + end + + test "write a page in sub directory" do + @wiki.write_page("New Page", :markdown, "Hi", commit_details) + assert_equal "Hi", File.read(File.join(@path, @page_file_dir, "New-Page.md")) + assert !File.exist?(File.join(@path, "New-Page.md")) + end + + test "a file in page file dir should be found" do + assert @wiki.page("foo") + end + + test "a file out of page file dir should not be found" do + assert !@wiki.page("bar") + end + + test "search results should be restricted in page filer dir" do + results = @wiki.search("foo") + assert_equal 1, results.size + assert_equal "foo", results[0][:name] + end + + teardown do + FileUtils.rm_r(@path) + end +end