diff --git a/lib/gollum/wiki.rb b/lib/gollum/wiki.rb index a5f9a094..030f5bd7 100644 --- a/lib/gollum/wiki.rb +++ b/lib/gollum/wiki.rb @@ -63,6 +63,7 @@ module Gollum @base_path = options[:base_path] || "/" @page_class = options[:page_class] || self.class.page_class @file_class = options[:file_class] || self.class.file_class + clear_cache end # Public: check whether the wiki's git repo exists on the filesystem. @@ -235,6 +236,20 @@ module Gollum # Returns the String path. attr_reader :path + # Gets a Hash cache of refs to commit SHAs. + # + # {"master" => "abc123", ...} + # + # Returns the Hash cache. + attr_reader :ref_map + + # Gets a Hash cache of commit SHAs to a recursive tree of blobs. + # + # {"abc123" => [["lib/foo.rb", "blob-sha"], [file, sha], ...], ...} + # + # Returns the Hash cache. + attr_reader :tree_map + # Normalize the data. # # data - The String data to be normalized. @@ -348,5 +363,69 @@ module Gollum commit[:email] = self.class.default_committer_email if commit[:email].to_s.empty? commit end + + # Finds a full listing of files and their blob SHA for a given ref. Each + # listing is cached based on its actual commit SHA. + # + # ref - A String ref that is either a commit SHA or references one. + # + # Returns an Array of [filename, sha] Arrays. + def tree_map_for(ref) + sha = @ref_map[ref] || ref + @tree_map[sha] || begin + real_sha = @repo.git.rev_list({:max_count=>1}, ref) + @ref_map[ref] = real_sha if real_sha != ref + @tree_map[real_sha] ||= parse_tree_for(real_sha) + end + end + + # Finds the full listing of files and their blob SHA for a given commit + # SHA. No caching or ref lookups are performed. + # + # sha - String commit SHA. + # + # Returns an Array of [filename, sha] Arrays. + def parse_tree_for(sha) + tree = @repo.git.native(:ls_tree, {:r => true, :z => true}, sha) + items = [] + tree.split("\0").each do |line| + items << parse_tree_line(line) + end + items + end + + # Parses a line of output from the `ls-tree` command. + # + # line - A String line of output: + # "100644 blob 839c2291b30495b9a882c17d08254d3c90d8fb53 Home.md" + # + # Returns an Array of [filename, sha]. + def parse_tree_line(line) + data, name = line.split("\t") + mode, type, sha = data.split(' ') + name = decode_git_path(name) + [name, sha] + end + + # Decode octal sequences (\NNN) in tree path names. + # + # path - String path name. + # + # Returns a decoded String. + def decode_git_path(path) + if path[0] == ?" && path[-1] == ?" + path = path[1...-1] + path.gsub!(/\\\d{3}/) { |m| m[1..-1].to_i(8).chr } + end + path.gsub!(/\\[rn"\\]/) { |m| eval(%("#{m.to_s}")) } + path.downcase! + path + end + + # Resets the ref and tree caches for this wiki. + def clear_cache + @ref_map = {} + @tree_map = {} + end end end diff --git a/test/test_wiki.rb b/test/test_wiki.rb index f0edfd32..f6f774d1 100644 --- a/test/test_wiki.rb +++ b/test/test_wiki.rb @@ -53,6 +53,21 @@ context "Wiki" do assert_equal({:message => 'abc', :name => 'bob', :email => 'foo@bar.com'}, @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) + assert_equal 'bilbo-baggins.md', @wiki.tree_map['60f12f4254f58801b9ee7db7bca5fa8aeefaa56b'][0][0] + 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? + assert_equal 'bilbo-baggins.md', @wiki.tree_map['60f12f4254f58801b9ee7db7bca5fa8aeefaa56b'][0][0] + end end context "Wiki page previewing" do