diff --git a/gollum.gemspec b/gollum.gemspec index 22764d55..bcea6b4f 100644 --- a/gollum.gemspec +++ b/gollum.gemspec @@ -23,12 +23,12 @@ Gem::Specification.new do |s| s.rdoc_options = ["--charset=UTF-8"] s.extra_rdoc_files = %w[README.md LICENSE] - s.add_dependency('grit', "~> 2.4.0") + s.add_dependency('grit', "~> 2.4.1") s.add_dependency('github-markup', [">= 0.4.0", "< 1.0.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") + s.add_dependency('sanitize', "~> 2.0.0") s.add_dependency('nokogiri', "~> 1.4") s.add_development_dependency('RedCloth') @@ -37,6 +37,7 @@ Gem::Specification.new do |s| s.add_development_dependency('rdiscount') s.add_development_dependency('shoulda') s.add_development_dependency('rack-test') + s.add_development_dependency('wikicloth') # = MANIFEST = s.files = %w[ diff --git a/lib/gollum.rb b/lib/gollum.rb index 6970d0be..2320b281 100644 --- a/lib/gollum.rb +++ b/lib/gollum.rb @@ -12,6 +12,7 @@ require 'gollum/ruby1.8' # internal require 'gollum/git_access' +require 'gollum/committer' require 'gollum/pagination' require 'gollum/blob_entry' require 'gollum/wiki' diff --git a/lib/gollum/committer.rb b/lib/gollum/committer.rb new file mode 100644 index 00000000..a8b73673 --- /dev/null +++ b/lib/gollum/committer.rb @@ -0,0 +1,217 @@ +module Gollum + # Responsible for handling the commit process for a Wiki. It sets up the + # Git index, provides methods for modifying the tree, and stores callbacks + # to be fired after the commit has been made. This is specifically + # designed to handle multiple updated pages in a single commit. + class Committer + # Gets the instance of the Gollum::Wiki that is being updated. + attr_reader :wiki + + # Gets a Hash of commit options. + attr_reader :options + + # Initializes the Committer. + # + # wiki - The Gollum::Wiki instance that is being updated. + # options - The commit Hash details: + # :message - The String commit message. + # :name - The String author full name. + # :email - The String email address. + # :parent - Optional Grit::Commit parent to this update. + # :tree - Optional String SHA of the tree to create the + # index from. + # :committer - Optional Gollum::Committer instance. If provided, + # assume that this operation is part of batch of + # updates and the commit happens later. + # + # Returns the Committer instance. + def initialize(wiki, options = {}) + @wiki = wiki + @options = options + @callbacks = [] + end + + # Public: References the Git index for this commit. + # + # Returns a Grit::Index. + def index + @index ||= begin + idx = @wiki.repo.index + if tree = options[:tree] + idx.read_tree(tree) + elsif parent = parents.first + idx.read_tree(parent.tree.id) + end + idx + end + end + + # Public: The committer for this commit. + # + # Returns a Grit::Actor. + def actor + @actor ||= begin + @options[:name] = @wiki.default_committer_name if @options[:name].to_s.empty? + @options[:email] = @wiki.default_committer_email if @options[:email].to_s.empty? + Grit::Actor.new(@options[:name], @options[:email]) + end + end + + # Public: The parent commits to this pending commit. + # + # Returns an array of Grit::Commit instances. + def parents + @parents ||= begin + arr = [@options[:parent] || @wiki.repo.commit('master')] + arr.flatten! + arr.compact! + arr + end + end + + # Adds a page to the given Index. + # + # dir - The String subdirectory of the Gollum::Page without any + # prefix or suffix slashes (e.g. "foo/bar"). + # name - The String Gollum::Page name. + # format - The Symbol Gollum::Page format. + # data - The String wiki data to store in the tree map. + # allow_same_ext - A Boolean determining if the tree map allows the same + # filename with the same extension. + # + # Raises Gollum::DuplicatePageError if a matching filename already exists. + # This way, pages are not inadvertently overwritten. + # + # Returns nothing (modifies the Index in place). + def add_to_index(dir, name, format, data, allow_same_ext = false) + path = @wiki.page_file_name(name, format) + + dir = '/' if dir.strip.empty? + + fullpath = ::File.join(*[@wiki.page_file_dir, dir, path].compact) + fullpath = fullpath[1..-1] if fullpath =~ /^\// + + if index.current_tree && tree = index.current_tree / dir + downpath = path.downcase.sub(/\.\w+$/, '') + + tree.blobs.each do |blob| + next if page_path_scheduled_for_deletion?(index.tree, fullpath) + file = blob.name.downcase.sub(/\.\w+$/, '') + file_ext = ::File.extname(blob.name).sub(/^\./, '') + if downpath == file && !(allow_same_ext && file_ext == ext) + raise DuplicatePageError.new(dir, blob.name, path) + end + end + end + + index.add(fullpath, @wiki.normalize(data)) + end + + # Update the given file in the repository's working directory if there + # is a working directory present. + # + # dir - The String directory in which the file lives. + # name - The String name of the page (may be in human format). + # format - The Symbol format of the page. + # + # Returns nothing. + def update_working_dir(dir, name, format) + unless @wiki.repo.bare + if @wiki.page_file_dir + dir = dir.size.zero? ? @wiki.page_file_dir : File.join(dir, @wiki.page_file_dir) + end + + path = + if dir == '' + @wiki.page_file_name(name, format) + else + ::File.join(dir, @wiki.page_file_name(name, format)) + end + + Dir.chdir(::File.join(@wiki.repo.path, '..')) do + if file_path_scheduled_for_deletion?(index.tree, path) + @wiki.repo.git.rm({'f' => true}, '--', path) + else + @wiki.repo.git.checkout({}, 'HEAD', '--', path) + end + end + end + end + + # Writes the commit to Git and runs the after_commit callbacks. + # + # Returns the String SHA1 of the new commit. + def commit + sha1 = index.commit(@options[:message], parents, actor) + @callbacks.each do |cb| + cb.call(self, sha1) + end + sha1 + end + + # Adds a callback to be fired after a commit. + # + # block - A block that expects this Committer instance and the created + # commit's SHA1 as the arguments. + # + # Returns nothing. + def after_commit(&block) + @callbacks << block + end + + # Determine if a given page (regardless of format) is scheduled to be + # deleted in the next commit for the given Index. + # + # map - The Hash map: + # key - The String directory or filename. + # val - The Hash submap or the String contents of the file. + # path - The String path of the page file. This may include the format + # extension in which case it will be ignored. + # + # Returns the Boolean response. + def page_path_scheduled_for_deletion?(map, path) + parts = path.split('/') + if parts.size == 1 + deletions = map.keys.select { |k| !map[k] } + downfile = parts.first.downcase.sub(/\.\w+$/, '') + deletions.any? { |d| d.downcase.sub(/\.\w+$/, '') == downfile } + else + part = parts.shift + if rest = map[part] + page_path_scheduled_for_deletion?(rest, parts.join('/')) + else + false + end + end + end + + # Determine if a given file is scheduled to be deleted in the next commit + # for the given Index. + # + # map - The Hash map: + # key - The String directory or filename. + # val - The Hash submap or the String contents of the file. + # path - The String path of the file including extension. + # + # Returns the Boolean response. + def file_path_scheduled_for_deletion?(map, path) + parts = path.split('/') + if parts.size == 1 + deletions = map.keys.select { |k| !map[k] } + deletions.any? { |d| d == parts.first } + else + part = parts.shift + if rest = map[part] + file_path_scheduled_for_deletion?(rest, parts.join('/')) + else + false + end + end + end + + # Proxies methods t + def method_missing(name, *args) + index.send(name, *args) + end + end +end \ No newline at end of file diff --git a/lib/gollum/frontend/app.rb b/lib/gollum/frontend/app.rb index 355cf826..49687403 100644 --- a/lib/gollum/frontend/app.rb +++ b/lib/gollum/frontend/app.rb @@ -58,11 +58,14 @@ module Precious 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, + committer = Gollum::Committer.new(wiki, commit_message) + commit = {:committer => committer} + + update_wiki_page(wiki, page, params[:content], commit, 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] + update_wiki_page(wiki, page.footer, params[:footer], commit) if params[:footer] + update_wiki_page(wiki, page.sidebar, params[:sidebar], commit) if params[:sidebar] + committer.commit redirect "/#{CGI.escape(Gollum::Page.cname(name))}" end @@ -187,10 +190,12 @@ module Precious end def update_wiki_page(wiki, page, content, commit_message, name = nil, format = nil) - return if !page || !content || page.raw_data == content - name ||= page.name - format = (format || page.format).to_sym - wiki.update_page(page, name, format, content, commit_message) + return if !page || + ((!content || page.raw_data == content) && page.format == format) + name ||= page.name + format = (format || page.format).to_sym + content ||= page.raw_data + wiki.update_page(page, name, format, content.to_s, commit_message) end def commit_message diff --git a/lib/gollum/frontend/public/javascript/gollum-editor/langs/org.js b/lib/gollum/frontend/public/javascript/gollum-editor/langs/org.js new file mode 100644 index 00000000..24d5aa21 --- /dev/null +++ b/lib/gollum/frontend/public/javascript/gollum-editor/langs/org.js @@ -0,0 +1,173 @@ +/** + * Org-mode Language Definition +**/ +(function() { + +var OrgMode = { + + 'function-bold' : { + search: /([^\n]+)([\n\s]*)/g, + replace: "*$1*$2" + }, + + 'function-italic' : { + search: /([^\n]+)([\n\s]*)/g, + replace: "/$1/$2" + }, + + 'function-code' : { + search: /(^[\n]+)([\n\s]*)/g, + replace: "=$1=$2" + }, + + 'function-ul' : { + search: /(.+)([\n]?)/g, + replace: "* $1$2" + }, + + /* This works, just like it works for Markdown */ + 'function-ol' : { + search: /(.+)([\n]?)/g, + replace: "1. $1$2" + }, + + 'function-blockquote' : { + search: /(.+)([\n]?)/g, + replace: "#+BEGIN_QUOTE\n$1$2\n#+END_QUOTE\n" + }, + + 'function-h1' : { + search: /(.+)([\n]?)/g, + replace: "* $1$2" + }, + + 'function-h2' : { + search: /(.+)([\n]?)/g, + replace: "** $1$2" + }, + + 'function-h3' : { + search: /(.+)([\n]?)/g, + replace: "*** $1$2" + }, + + 'function-link' : { + exec: function( txt, selText, $field ) { + var results = null; + $.GollumEditor.Dialog.init({ + title: 'Insert Link', + fields: [ + { + id: 'text', + name: 'Link Text', + type: 'text' + }, + { + id: 'href', + name: 'URL', + type: 'text' + } + ], + OK: function( res ) { + var rep = ''; + if ( res['text'] && res['href'] ) { + rep = '[[' + res['href'] + '][' + + res['text'] + ']]'; + } + else if ( res['href'] ) { + rep = '[[' + res['href'] + ']]'; + } + $.GollumEditor.replaceSelection( rep ); + } + }); + } + }, + + 'function-image' : { + exec: function( txt, selText, $field ) { + var results = null; + $.GollumEditor.Dialog.init({ + title: 'Insert Image', + fields: [ + { + id: 'url', + name: 'Image URL', + type: 'text' + }, + ], + OK: function( res ) { + var rep = ''; + if ( res['url'] ) { + rep = '[[' + res['url'] + ']]'; + } + $.GollumEditor.replaceSelection( rep ); + } + }); + } + } + +}; + +var OrgModeHelp = [ + + { + menuName: 'Block Elements', + content: [ + { + menuName: 'Paragraphs & Breaks', + data: '

To create a paragraph, simply create a block of text that is not separated by one or more blank lines. Blocks of text separated by one or more blank lines will be parsed as paragraphs.

' + }, + { + menuName: 'Headers', + data: '

Simply prefix your header text with the number of * characters to specify heading depth. For example: * Header 1, ** Header 2 and *** Header 3 will be progressively smaller headers.

' + }, + { + menuName: 'Blockquotes', + data: '

To create a blockquote, simple embed the text between #+BEGIN_QUOTE and #+END_QUOTE. An example quote block is displayed below:
#+BEGIN_QUOTE
This is my quote block. Quote something nice here, otherwise there is no point in quoting.
#+END_QUOTE

' + }, + { + menuName: 'Lists', + data: '

Org-mode supports both ordered and unordered lists. To create an ordered list, simply prefix each line with a number (any number will do — this is why the editor only uses one number.) To create an unordered list, you can prefix each line with + or -.

' + }, + { + menuName: 'Code Blocks', + data: '

Code Blocks are similar to blockquote, except that #+BEGIN_EXAMPLE and #+END_EXAMPLE are used.

' + }, + { + menuName: 'Tables', + data: '

Org-mode supports simple tables (tables with equal number of cells in each row). To create a simple table, just separate the contents of each cell with a | character. For example,

|one|two|three|
|four|five|six|


will appear as a table with two rows and three columns. Additionally,

|one|two|three|
|---+---+-----|
|four|five|six|


will also appear as a table, but the first row will be interpreted as a header row and the <th> tag will be used to render it.

' + }, + ] + }, + + { + menuName: 'Span Elements', + content: [ + { + menuName: 'Links', + data: '

To create links to external pages, you need to enclose the URI in double square brackets. (i.e., [[http://github.com/]] will automatically be parsed to http://github.com/)If you want to add text, to be displayed to the user, you write the URI and the text next to each other, both enclosed in square brackets and both of them together enclosed in another pair of square brackets. For example, if you want your link to display the text “GitHub”, you write [[http://github.com][GitHub]].

' + }, + + { + menuName: 'Emphasis', + data: '

Forward slashes (/) are treated as emphasis and are wrapped with an <i> tag. Asterisks (*) are treated as bold using the <b> tag.

' + }, + + { + menuName: 'Code', + data: '

To create inline spans of code, simply wrap the code in equal signs (=). Orgmode will turn =myFunction= into myFunction.

' + }, + + { + menuName: 'Images', + data: "

Org-mode image syntax is exactly same as the syntax that you would use for a URI to link to itself. The image URI is enclosed in double square brackets. Alt text on images is not currently supported by Gollum's Org-mode parser.

" + } + ] + }, +]; + + +jQuery.GollumEditor.defineLanguage('org', OrgMode); +jQuery.GollumEditor.defineHelp('org', OrgModeHelp); + +})(); diff --git a/lib/gollum/git_access.rb b/lib/gollum/git_access.rb index 808e4f2e..21444f68 100644 --- a/lib/gollum/git_access.rb +++ b/lib/gollum/git_access.rb @@ -221,7 +221,7 @@ module Gollum # Returns an Array of BlobEntry instances. def parse_tree_line(line) mode, type, sha, size, *name = line.split(/\s+/) - BlobEntry.new(sha, name.to_s, size.to_i) + BlobEntry.new(sha, name.join(' '), size.to_i) end # Decode octal sequences (\NNN) in tree path names. diff --git a/lib/gollum/page.rb b/lib/gollum/page.rb index 61d04484..be6cc10e 100644 --- a/lib/gollum/page.rb +++ b/lib/gollum/page.rb @@ -136,7 +136,7 @@ module Gollum Sanitize.clean(header.to_html) else Sanitize.clean(name) - end + end.strip end # Public: The path of the page within the repo. @@ -333,9 +333,9 @@ module Gollum # path - The String directory path of the page file. # # Returns the populated Gollum::Page. - def populate(blob, path) + def populate(blob, path=nil) @blob = blob - @path = (path + '/' + blob.name)[1..-1] + @path = "#{path}/#{blob.name}"[1..-1] self end diff --git a/lib/gollum/wiki.rb b/lib/gollum/wiki.rb index 76107586..e8a65246 100644 --- a/lib/gollum/wiki.rb +++ b/lib/gollum/wiki.rb @@ -168,10 +168,10 @@ module Gollum def preview_page(name, data, format) page = @page_class.new(self) ext = @page_class.format_to_ext(format.to_sym) - path = @page_class.cname(name) + '.' + ext - blob = OpenStruct.new(:name => path, :data => data) - page.populate(blob, path) - page.version = @access.commit('HEAD') + name = @page_class.cname(name) + '.' + ext + blob = OpenStruct.new(:name => name, :data => data) + page.populate(blob) + page.version = @access.commit('master') page end @@ -181,22 +181,36 @@ module Gollum # format - The Symbol format of the page. # data - The new String contents of the page. # commit - The commit Hash details: - # :message - The String commit message. - # :name - The String author full name. - # :email - The String email address. + # :message - The String commit message. + # :name - The String author full name. + # :email - The String email address. + # :parent - Optional Grit::Commit parent to this update. + # :tree - Optional String SHA of the tree to create the + # index from. + # :committer - Optional Gollum::Committer instance. If provided, + # assume that this operation is part of batch of + # updates and the commit happens later. # - # Returns the String SHA1 of the newly written version. + # Returns the String SHA1 of the newly written version, or the + # Gollum::Committer instance if this is part of a batch update. def write_page(name, format, data, commit = {}) - index = nil - sha1 = commit_index(commit) do |idx| - index = idx - add_to_index(index, '', name, format, data) + multi_commit = false + + committer = if obj = commit[:committer] + multi_commit = true + obj + else + Committer.new(self, commit) end - @access.refresh - update_working_dir(index, '', name, format) + committer.add_to_index('', name, format, data) - sha1 + committer.after_commit do |index, sha| + @access.refresh + index.update_working_dir('', name, format) + end + + multi_commit ? committer : committer.commit end # Public: Update an existing page with new content. The location of the @@ -209,57 +223,85 @@ module Gollum # format - The Symbol format of the page. # data - The new String contents of the page. # commit - The commit Hash details: - # :message - The String commit message. - # :name - The String author full name. - # :email - The String email address. + # :message - The String commit message. + # :name - The String author full name. + # :email - The String email address. + # :parent - Optional Grit::Commit parent to this update. + # :tree - Optional String SHA of the tree to create the + # index from. + # :committer - Optional Gollum::Committer instance. If provided, + # assume that this operation is part of batch of + # updates and the commit happens later. # - # Returns the String SHA1 of the newly written version. + # Returns the String SHA1 of the newly written version, or the + # Gollum::Committer instance if this is part of a batch update. def update_page(page, name, format, data, commit = {}) name ||= page.name format ||= page.format dir = ::File.dirname(page.path) dir = '' if dir == '.' - index = nil - sha1 = commit_index(commit) do |idx| - index = idx - if page.name == name && page.format == format - index.add(page.path, normalize(data)) - else - index.delete(page.path) - add_to_index(index, dir, name, format, data, :allow_same_ext) - end + multi_commit = false + + committer = if obj = commit[:committer] + multi_commit = true + obj + else + Committer.new(self, commit) end - @access.refresh - update_working_dir(index, dir, page.name, page.format) - update_working_dir(index, dir, name, format) + if page.name == name && page.format == format + committer.add(page.path, normalize(data)) + else + committer.delete(page.path) + committer.add_to_index(dir, name, format, data, :allow_same_ext) + end - sha1 + committer.after_commit do |index, sha| + @access.refresh + index.update_working_dir(dir, page.name, page.format) + index.update_working_dir(dir, name, format) + end + + multi_commit ? committer : committer.commit end # Public: Delete a page. # # page - The Gollum::Page to delete. # commit - The commit Hash details: - # :message - The String commit message. - # :name - The String author full name. - # :email - The String email address. + # :message - The String commit message. + # :name - The String author full name. + # :email - The String email address. + # :parent - Optional Grit::Commit parent to this update. + # :tree - Optional String SHA of the tree to create the + # index from. + # :committer - Optional Gollum::Committer instance. If provided, + # assume that this operation is part of batch of + # updates and the commit happens later. # - # Returns the String SHA1 of the newly written version. + # Returns the String SHA1 of the newly written version, or the + # Gollum::Committer instance if this is part of a batch update. def delete_page(page, commit) - index = nil - sha1 = commit_index(commit) do |idx| - index = idx - index.delete(page.path) + multi_commit = false + + committer = if obj = commit[:committer] + multi_commit = true + obj + else + Committer.new(self, commit) + end + + committer.delete(page.path) + + committer.after_commit do |index, sha| + dir = ::File.dirname(page.path) + dir = '' if dir == '.' + + @access.refresh + index.update_working_dir(dir, page.name, page.format) end - dir = ::File.dirname(page.path) - dir = '' if dir == '.' - - @access.refresh - update_working_dir(index, dir, page.name, page.format) - - sha1 + multi_commit ? committer : committer.commit end # Public: Applies a reverse diff for a given page. If only 1 SHA is given, @@ -274,6 +316,7 @@ module Gollum # :message - The String commit message. # :name - The String author full name. # :email - The String email address. + # :parent - Optional Grit::Commit parent to this update. # # Returns a String SHA1 of the new commit, or nil if the reverse diff does # not apply. @@ -283,41 +326,39 @@ module Gollum 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] + patch = full_reverse_diff_for(page, sha1, sha2) + committer = Committer.new(self, commit) + parent = committer.parents[0] + committer.options[:tree] = @repo.git.apply_patch(parent.sha, patch) + return false unless committer.options[:tree] + committer.after_commit do |index, sha| + @access.refresh - 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] + files = [] + if page + files << [page.path, page.name, page.format] + else + # Grit::Diff can't parse reverse diffs.... yet + patch.each_line do |line| + 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 == '.' + index.update_working_dir(dir, name, format) + end end - files.each do |(path, name, format)| - dir = ::File.dirname(path) - dir = '' if dir == '.' - update_working_dir(index, dir, name, format) - end - - sha1 + committer.commit end # Public: Applies a reverse diff to the repo. If only 1 SHA is given, @@ -464,38 +505,6 @@ module Gollum @page_class.cname(name) + '.' + ext end - # Update the given file in the repository's working directory if there - # is a working directory present. - # - # index - The Grit::Index with which to sync. - # dir - The String directory in which the file lives. - # name - The String name of the page (may be in human format). - # format - The Symbol format of the page. - # - # Returns nothing. - def update_working_dir(index, dir, name, format) - unless @repo.bare - 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) - else - @repo.git.checkout({}, 'HEAD', '--', path) - end - end - end - end - # Fill an array with a list of pages. # # ref - A String ref that is either a commit SHA or references one. @@ -510,122 +519,6 @@ module Gollum end end - # Determine if a given file is scheduled to be deleted in the next commit - # for the given Index. - # - # map - The Hash map: - # key - The String directory or filename. - # val - The Hash submap or the String contents of the file. - # path - The String path of the file including extension. - # - # Returns the Boolean response. - def file_path_scheduled_for_deletion?(map, path) - parts = path.split('/') - if parts.size == 1 - deletions = map.keys.select { |k| !map[k] } - deletions.any? { |d| d == parts.first } - else - part = parts.shift - if rest = map[part] - file_path_scheduled_for_deletion?(rest, parts.join('/')) - else - false - end - end - end - - # Determine if a given page (regardless of format) is scheduled to be - # deleted in the next commit for the given Index. - # - # map - The Hash map: - # key - The String directory or filename. - # val - The Hash submap or the String contents of the file. - # path - The String path of the page file. This may include the format - # extension in which case it will be ignored. - # - # Returns the Boolean response. - def page_path_scheduled_for_deletion?(map, path) - parts = path.split('/') - if parts.size == 1 - deletions = map.keys.select { |k| !map[k] } - downfile = parts.first.downcase.sub(/\.\w+$/, '') - deletions.any? { |d| d.downcase.sub(/\.\w+$/, '') == downfile } - else - part = parts.shift - if rest = map[part] - page_path_scheduled_for_deletion?(rest, parts.join('/')) - else - false - end - end - end - - # Adds a page to the given Index. - # - # index - The Grit::Index to which the page will be added. - # dir - The String subdirectory of the Gollum::Page without any - # prefix or suffix slashes (e.g. "foo/bar"). - # name - The String Gollum::Page name. - # format - The Symbol Gollum::Page format. - # data - The String wiki data to store in the tree map. - # allow_same_ext - A Boolean determining if the tree map allows the same - # filename with the same extension. - # - # Raises Gollum::DuplicatePageError if a matching filename already exists. - # This way, pages are not inadvertently overwritten. - # - # Returns nothing (modifies the Index in place). - def add_to_index(index, dir, name, format, data, allow_same_ext = false) - path = page_file_name(name, format) - - dir = '/' if dir.strip.empty? - - fullpath = ::File.join(*[@page_file_dir, dir, path].compact) - fullpath = fullpath[1..-1] if fullpath =~ /^\// - - if index.current_tree && tree = index.current_tree / dir - downpath = path.downcase.sub(/\.\w+$/, '') - - tree.blobs.each do |blob| - next if page_path_scheduled_for_deletion?(index.tree, fullpath) - file = blob.name.downcase.sub(/\.\w+$/, '') - file_ext = ::File.extname(blob.name).sub(/^\./, '') - if downpath == file && !(allow_same_ext && file_ext == ext) - raise DuplicatePageError.new(dir, blob.name, path) - end - end - end - - 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')] - parents.flatten! - parents.compact! - index = self.repo.index - if tree = options[:tree] - index.read_tree(tree) - elsif parent = parents[0] - index.read_tree(parent.tree.id) - 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. @@ -654,20 +547,6 @@ module Gollum full_reverse_diff_for(nil, sha1, sha2) end - # Ensures a commit hash has all the required fields for a commit. - # - # commit - The commit Hash details: - # :message - The String commit message. - # :name - The String author full name. - # :email - The String email address. - # - # Returns the commit Hash - def normalize_commit(commit = {}) - commit[:name] = default_committer_name if commit[:name].to_s.empty? - commit[:email] = default_committer_email if commit[:email].to_s.empty? - commit - end - # Gets the default name for commits. # # Returns the String name. diff --git a/test/test_app.rb b/test/test_app.rb index 96aa3f19..b68c5da5 100644 --- a/test/test_app.rb +++ b/test/test_app.rb @@ -30,14 +30,15 @@ context "Frontend" do end test "edits page footer and sidebar" do - page_1 = @wiki.page('A') - foot_1 = page_1.footer - side_1 = page_1.sidebar + commits = @wiki.repo.commits('master').size + page_1 = @wiki.page('A') + foot_1 = page_1.footer + side_1 = page_1.sidebar post "/edit/A", - :footer => 'footer', :sidebar => 'sidebar', - :format => page_1.format, :message => 'def' + :footer => 'footer', :page => "A", :sidebar => 'sidebar', :message => 'def' follow_redirect! + assert_equal "/A", last_request.fullpath assert last_response.ok? @wiki.clear_cache @@ -53,6 +54,7 @@ context "Frontend" do assert_equal 'sidebar', side_2.raw_data assert_equal 'def', side_2.version.message assert_not_equal side_1.version.sha, side_2.version.sha + assert_equal commits+1, @wiki.repo.commits('master').size end test "renames page" do @@ -61,6 +63,7 @@ context "Frontend" do :rename => "C", :format => page_1.format, :message => 'def' follow_redirect! + assert_equal "/C", last_request.fullpath assert last_response.ok? @wiki.clear_cache @@ -138,4 +141,4 @@ context "Frontend" do def app Precious::App end -end \ No newline at end of file +end diff --git a/test/test_committer.rb b/test/test_committer.rb new file mode 100644 index 00000000..68157844 --- /dev/null +++ b/test/test_committer.rb @@ -0,0 +1,50 @@ +require File.expand_path(File.join(File.dirname(__FILE__), "helper")) + +context "Wiki" do + setup do + @wiki = Gollum::Wiki.new(testpath("examples/lotr.git")) + end + + test "normalizes commit hash" do + commit = {:message => 'abc'} + name = @wiki.repo.config['user.name'] + email = @wiki.repo.config['user.email'] + committer = Gollum::Committer.new(@wiki, commit) + assert_equal name, committer.actor.name + assert_equal email, committer.actor.email + + commit[:name] = 'bob' + commit[:email] = '' + committer = Gollum::Committer.new(@wiki, commit) + assert_equal 'bob', committer.actor.name + assert_equal email, committer.actor.email + + commit[:email] = 'foo@bar.com' + committer = Gollum::Committer.new(@wiki, commit) + assert_equal 'bob', committer.actor.name + assert_equal 'foo@bar.com', committer.actor.email + end + + test "yield after_commit callback" do + @path = cloned_testpath('examples/lotr.git') + yielded = nil + begin + wiki = Gollum::Wiki.new(@path) + committer = Gollum::Committer.new(wiki) + committer.after_commit do |index, sha1| + yielded = sha1 + assert_equal committer, index + end + + res = wiki.write_page("Gollum", :markdown, "# Gollum", + :committer => committer) + + assert_equal committer, res + + sha1 = committer.commit + assert_equal sha1, yielded + ensure + FileUtils.rm_rf(@path) + end + end +end \ No newline at end of file diff --git a/test/test_markup.rb b/test/test_markup.rb index 4f759ca3..6c8db408 100644 --- a/test/test_markup.rb +++ b/test/test_markup.rb @@ -238,6 +238,27 @@ context "Markup" do assert_equal %{

a a b

}, output end + test "image with absolute path on a preview" do + @wiki = Gollum::Wiki.new(@path, :base_path => '/wiki') + index = @wiki.repo.index + index.add("alpha.jpg", "hi") + index.commit("Add alpha.jpg") + + page = @wiki.preview_page("Test", "a [[/alpha.jpg]] b", :markdown) + assert_equal %{

a b

}, page.formatted_data + end + + test "image with relative path on a preview" do + @wiki = Gollum::Wiki.new(@path, :base_path => '/wiki') + index = @wiki.repo.index + index.add("alpha.jpg", "hi") + index.add("greek/alpha.jpg", "hi") + index.commit("Add alpha.jpg") + + page = @wiki.preview_page("Test", "a [[alpha.jpg]] [[greek/alpha.jpg]] b", :markdown) + assert_equal %{

a b

}, page.formatted_data + end + test "image with alt" do content = "a [[alpha.jpg|alt=Alpha Dog]] b" output = %{

a Alpha Dog b

} diff --git a/test/test_wiki.rb b/test/test_wiki.rb index 1f862dd0..4ac9aa4e 100644 --- a/test/test_wiki.rb +++ b/test/test_wiki.rb @@ -44,23 +44,6 @@ context "Wiki" do assert_equal 4, @wiki.size end - test "normalizes commit hash" do - commit = {:message => 'abc'} - name = @wiki.repo.config['user.name'] - email = @wiki.repo.config['user.email'] - assert_equal({:message => 'abc', :name => name, :email => email}, - @wiki.normalize_commit(commit.dup)) - - commit[:name] = 'bob' - commit[:email] = '' - assert_equal({:message => 'abc', :name => 'bob', :email => email}, - @wiki.normalize_commit(commit.dup)) - - commit[:email] = 'foo@bar.com' - assert_equal({:message => 'abc', :name => 'bob', :email => 'foo@bar.com'}, - @wiki.normalize_commit(commit.dup)) - end - test "text_data" do wiki = Gollum::Wiki.new(testpath("examples/yubiwa.git")) if String.instance_methods.include?(:encoding)