gitlab-gollum-lib 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/HISTORY.md +3 -0
- data/LICENSE +21 -0
- data/README.md +601 -0
- data/Rakefile +171 -0
- data/docs/sanitization.md +33 -0
- data/gollum-lib.gemspec +75 -0
- data/lib/gollum-lib.rb +53 -0
- data/lib/gollum-lib/blob_entry.rb +95 -0
- data/lib/gollum-lib/committer.rb +236 -0
- data/lib/gollum-lib/file.rb +101 -0
- data/lib/gollum-lib/file_view.rb +155 -0
- data/lib/gollum-lib/git_access.rb +249 -0
- data/lib/gollum-lib/gitcode.rb +48 -0
- data/lib/gollum-lib/grit_ext.rb +20 -0
- data/lib/gollum-lib/helpers.rb +13 -0
- data/lib/gollum-lib/markup.rb +689 -0
- data/lib/gollum-lib/markups.rb +14 -0
- data/lib/gollum-lib/page.rb +485 -0
- data/lib/gollum-lib/pagination.rb +62 -0
- data/lib/gollum-lib/remote_code.rb +39 -0
- data/lib/gollum-lib/sanitization.rb +176 -0
- data/lib/gollum-lib/web_sequence_diagram.rb +44 -0
- data/lib/gollum-lib/wiki.rb +833 -0
- data/licenses/licenses.txt +6 -0
- metadata +315 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Gollum
|
3
|
+
class File
|
4
|
+
Wiki.file_class = self
|
5
|
+
|
6
|
+
# Public: Initialize a file.
|
7
|
+
#
|
8
|
+
# wiki - The Gollum::Wiki in question.
|
9
|
+
#
|
10
|
+
# Returns a newly initialized Gollum::File.
|
11
|
+
def initialize(wiki)
|
12
|
+
@wiki = wiki
|
13
|
+
@blob = nil
|
14
|
+
@path = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: The url path required to reach this page within the repo.
|
18
|
+
#
|
19
|
+
# Returns the String url_path
|
20
|
+
def url_path
|
21
|
+
path = self.path
|
22
|
+
path = path.sub(/\/[^\/]+$/, '/') if path.include?('/')
|
23
|
+
path
|
24
|
+
end
|
25
|
+
|
26
|
+
# Public: The url_path, but CGI escaped.
|
27
|
+
#
|
28
|
+
# Returns the String url_path
|
29
|
+
def escaped_url_path
|
30
|
+
CGI.escape(self.url_path).gsub('%2F','/')
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: The on-disk filename of the file.
|
34
|
+
#
|
35
|
+
# Returns the String name.
|
36
|
+
def name
|
37
|
+
@blob && @blob.name
|
38
|
+
end
|
39
|
+
alias filename name
|
40
|
+
|
41
|
+
# Public: The raw contents of the page.
|
42
|
+
#
|
43
|
+
# Returns the String data.
|
44
|
+
def raw_data
|
45
|
+
return nil unless @blob
|
46
|
+
|
47
|
+
if !@wiki.repo.bare && @blob.is_symlink
|
48
|
+
new_path = @blob.symlink_target(::File.join(@wiki.repo.path, '..', self.path))
|
49
|
+
return IO.read(new_path) if new_path
|
50
|
+
end
|
51
|
+
|
52
|
+
@blob.data
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: The Grit::Commit version of the file.
|
56
|
+
attr_accessor :version
|
57
|
+
|
58
|
+
# Public: The String path of the file.
|
59
|
+
attr_reader :path
|
60
|
+
|
61
|
+
# Public: The String mime type of the file.
|
62
|
+
def mime_type
|
63
|
+
@blob.mime_type
|
64
|
+
end
|
65
|
+
|
66
|
+
# Populate the File with information from the Blob.
|
67
|
+
#
|
68
|
+
# blob - The Grit::Blob that contains the info.
|
69
|
+
# path - The String directory path of the file.
|
70
|
+
#
|
71
|
+
# Returns the populated Gollum::File.
|
72
|
+
def populate(blob, path=nil)
|
73
|
+
@blob = blob
|
74
|
+
@path = "#{path}/#{blob.name}"[1..-1]
|
75
|
+
self
|
76
|
+
end
|
77
|
+
|
78
|
+
#########################################################################
|
79
|
+
#
|
80
|
+
# Internal Methods
|
81
|
+
#
|
82
|
+
#########################################################################
|
83
|
+
|
84
|
+
# Find a file in the given Gollum repo.
|
85
|
+
#
|
86
|
+
# name - The full String path.
|
87
|
+
# version - The String version ID to find.
|
88
|
+
#
|
89
|
+
# Returns a Gollum::File or nil if the file could not be found.
|
90
|
+
def find(name, version)
|
91
|
+
checked = name.downcase
|
92
|
+
map = @wiki.tree_map_for(version)
|
93
|
+
if entry = map.detect { |entry| entry.path.downcase == checked }
|
94
|
+
@path = name
|
95
|
+
@blob = entry.blob(@wiki.repo)
|
96
|
+
@version = version.is_a?(Grit::Commit) ? version : @wiki.commit_for(version)
|
97
|
+
self
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Gollum
|
3
|
+
=begin
|
4
|
+
FileView requires that:
|
5
|
+
- All files in root dir are processed first
|
6
|
+
- Then all the folders are sorted and processed
|
7
|
+
=end
|
8
|
+
class FileView
|
9
|
+
# common use cases:
|
10
|
+
# set pages to wiki.pages and show_all to false
|
11
|
+
# set pages to wiki.pages + wiki.files and show_all to true
|
12
|
+
def initialize pages, options = {}
|
13
|
+
@pages = pages
|
14
|
+
@show_all = options[:show_all] || false
|
15
|
+
@checked = options[:collapse_tree] ? '' : "checked"
|
16
|
+
end
|
17
|
+
|
18
|
+
def enclose_tree string
|
19
|
+
%Q(<ol class="tree">\n) + string + %Q(</ol>)
|
20
|
+
end
|
21
|
+
|
22
|
+
def new_page page
|
23
|
+
name = page.name
|
24
|
+
url = url_for_page page
|
25
|
+
%Q( <li class="file"><a href="#{url}"><span class="icon"></span>#{name}</a></li>)
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_folder folder_path
|
29
|
+
new_sub_folder folder_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def new_sub_folder path
|
33
|
+
<<-HTML
|
34
|
+
<li>
|
35
|
+
<label>#{path}</label> <input type="checkbox" #{@checked} />
|
36
|
+
<ol>
|
37
|
+
HTML
|
38
|
+
end
|
39
|
+
|
40
|
+
def end_folder
|
41
|
+
"</ol></li>\n"
|
42
|
+
end
|
43
|
+
|
44
|
+
def url_for_page page
|
45
|
+
url = ''
|
46
|
+
if @show_all
|
47
|
+
# Remove ext for valid pages.
|
48
|
+
filename = page.filename
|
49
|
+
filename = Page::valid_page_name?(filename) ? filename.chomp(::File.extname(filename)) : filename
|
50
|
+
|
51
|
+
url = ::File.join(::File.dirname(page.path), filename)
|
52
|
+
else
|
53
|
+
url = ::File.join(::File.dirname(page.path), page.filename_stripped)
|
54
|
+
end
|
55
|
+
url = url[2..-1] if url[0,2] == './'
|
56
|
+
url
|
57
|
+
end
|
58
|
+
|
59
|
+
def render_files
|
60
|
+
html = ''
|
61
|
+
count = @pages.size
|
62
|
+
folder_start = -1
|
63
|
+
|
64
|
+
# Process all pages until folders start
|
65
|
+
count.times do | index |
|
66
|
+
page = @pages[ index ]
|
67
|
+
path = page.path
|
68
|
+
|
69
|
+
unless path.include? '/'
|
70
|
+
# Page processed (not contained in a folder)
|
71
|
+
html += new_page page
|
72
|
+
else
|
73
|
+
# Folders start at the next index
|
74
|
+
folder_start = index
|
75
|
+
break # Pages finished, move on to folders
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# If there are no folders, then we're done.
|
80
|
+
return enclose_tree(html) if folder_start <= -1
|
81
|
+
|
82
|
+
# Handle special case of only one folder.
|
83
|
+
if (count - folder_start == 1)
|
84
|
+
page = @pages[ folder_start ]
|
85
|
+
html += <<-HTML
|
86
|
+
<li>
|
87
|
+
<label>#{::File.dirname(page.path)}</label> <input type="checkbox" #{@checked} />
|
88
|
+
<ol>
|
89
|
+
#{new_page page}
|
90
|
+
</ol>
|
91
|
+
</li>
|
92
|
+
HTML
|
93
|
+
|
94
|
+
return enclose_tree html
|
95
|
+
end
|
96
|
+
|
97
|
+
sorted_folders = []
|
98
|
+
(folder_start).upto count - 1 do | index |
|
99
|
+
sorted_folders += [[ @pages[ index ].path, index ]]
|
100
|
+
end
|
101
|
+
|
102
|
+
# http://stackoverflow.com/questions/3482814/sorting-list-of-string-paths-in-vb-net
|
103
|
+
sorted_folders.sort! do |first,second|
|
104
|
+
a = first[0]
|
105
|
+
b = second[0]
|
106
|
+
|
107
|
+
# use :: operator because gollum defines its own conflicting File class
|
108
|
+
dir_compare = ::File.dirname(a) <=> ::File.dirname(b)
|
109
|
+
|
110
|
+
# Sort based on directory name unless they're equal (0) in
|
111
|
+
# which case sort based on file name.
|
112
|
+
if dir_compare == 0
|
113
|
+
::File.basename(a) <=> ::File.basename(b)
|
114
|
+
else
|
115
|
+
dir_compare
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# keep track of folder depth, 0 = at root.
|
120
|
+
cwd_array = []
|
121
|
+
changed = false
|
122
|
+
|
123
|
+
# process rest of folders
|
124
|
+
(0...sorted_folders.size).each do | index |
|
125
|
+
page = @pages[ sorted_folders[ index ][ 1 ] ]
|
126
|
+
path = page.path
|
127
|
+
folder = ::File.dirname path
|
128
|
+
|
129
|
+
tmp_array = folder.split '/'
|
130
|
+
|
131
|
+
(0...tmp_array.size).each do | index |
|
132
|
+
if cwd_array[ index ].nil? || changed
|
133
|
+
html += new_sub_folder tmp_array[ index ]
|
134
|
+
next
|
135
|
+
end
|
136
|
+
|
137
|
+
if cwd_array[ index ] != tmp_array[ index ]
|
138
|
+
changed = true
|
139
|
+
(cwd_array.size - index).times do
|
140
|
+
html += end_folder
|
141
|
+
end
|
142
|
+
html += new_sub_folder tmp_array[ index ]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
html += new_page page
|
147
|
+
cwd_array = tmp_array
|
148
|
+
changed = false
|
149
|
+
end
|
150
|
+
|
151
|
+
# return the completed html
|
152
|
+
enclose_tree html
|
153
|
+
end # end render_files
|
154
|
+
end # end FileView class
|
155
|
+
end # end Gollum module
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# ~*~ encoding: utf-8 ~*~
|
2
|
+
module Gollum
|
3
|
+
# Controls all access to the Git objects from Gollum. Extend this class to
|
4
|
+
# add custom caching for special cases.
|
5
|
+
class GitAccess
|
6
|
+
# Initializes the GitAccess instance.
|
7
|
+
#
|
8
|
+
# path - The String path to the Git repository that holds the
|
9
|
+
# Gollum site.
|
10
|
+
# page_file_dir - String the directory in which all page files reside
|
11
|
+
#
|
12
|
+
# Returns this instance.
|
13
|
+
def initialize(path, page_file_dir = nil, bare = false)
|
14
|
+
@page_file_dir = page_file_dir
|
15
|
+
@path = path
|
16
|
+
@repo = Grit::Repo.new(path, { :is_bare => bare })
|
17
|
+
clear
|
18
|
+
end
|
19
|
+
|
20
|
+
# Public: Determines whether the Git repository exists on disk.
|
21
|
+
#
|
22
|
+
# Returns true if it exists, or false.
|
23
|
+
def exist?
|
24
|
+
@repo.git.exist?
|
25
|
+
end
|
26
|
+
|
27
|
+
# Public: Converts a given Git reference to a SHA, using the cache if
|
28
|
+
# available.
|
29
|
+
#
|
30
|
+
# ref - a String Git reference (ex: "master")
|
31
|
+
#
|
32
|
+
# Returns a String, or nil if the ref isn't found.
|
33
|
+
def ref_to_sha(ref)
|
34
|
+
ref = ref.to_s
|
35
|
+
return if ref.empty?
|
36
|
+
sha =
|
37
|
+
if sha?(ref)
|
38
|
+
ref
|
39
|
+
else
|
40
|
+
get_cache(:ref, ref) { ref_to_sha!(ref) }
|
41
|
+
end.to_s
|
42
|
+
sha.empty? ? nil : sha
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Gets a recursive list of Git blobs for the whole tree at the
|
46
|
+
# given commit.
|
47
|
+
#
|
48
|
+
# ref - A String Git reference or Git SHA to a commit.
|
49
|
+
#
|
50
|
+
# Returns an Array of BlobEntry instances.
|
51
|
+
def tree(ref)
|
52
|
+
if sha = ref_to_sha(ref)
|
53
|
+
get_cache(:tree, sha) { tree!(sha) }
|
54
|
+
else
|
55
|
+
[]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Fetches the contents of the Git blob at the given SHA.
|
60
|
+
#
|
61
|
+
# sha - A String Git SHA.
|
62
|
+
#
|
63
|
+
# Returns the String content of the blob.
|
64
|
+
def blob(sha)
|
65
|
+
cat_file!(sha)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Public: Looks up the Git commit using the given Git SHA or ref.
|
69
|
+
#
|
70
|
+
# ref - A String Git SHA or ref.
|
71
|
+
#
|
72
|
+
# Returns a Grit::Commit.
|
73
|
+
def commit(ref)
|
74
|
+
if sha?(ref)
|
75
|
+
get_cache(:commit, ref) { commit!(ref) }
|
76
|
+
else
|
77
|
+
if sha = get_cache(:ref, ref)
|
78
|
+
commit(sha)
|
79
|
+
else
|
80
|
+
if cm = commit!(ref)
|
81
|
+
set_cache(:ref, ref, cm.id)
|
82
|
+
set_cache(:commit, cm.id, cm)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Public: Clears all of the cached data that this GitAccess is tracking.
|
89
|
+
#
|
90
|
+
# Returns nothing.
|
91
|
+
def clear
|
92
|
+
@ref_map = {}
|
93
|
+
@tree_map = {}
|
94
|
+
@commit_map = {}
|
95
|
+
end
|
96
|
+
|
97
|
+
# Public: Refreshes just the cached Git reference data. This should
|
98
|
+
# be called after every Gollum update.
|
99
|
+
#
|
100
|
+
# Returns nothing.
|
101
|
+
def refresh
|
102
|
+
@ref_map.clear
|
103
|
+
end
|
104
|
+
|
105
|
+
#########################################################################
|
106
|
+
#
|
107
|
+
# Internal Methods
|
108
|
+
#
|
109
|
+
#########################################################################
|
110
|
+
|
111
|
+
# Gets the String path to the Git repository.
|
112
|
+
attr_reader :path
|
113
|
+
|
114
|
+
# Gets the Grit::Repo instance for the Git repository.
|
115
|
+
attr_reader :repo
|
116
|
+
|
117
|
+
# Gets a Hash cache of refs to commit SHAs.
|
118
|
+
#
|
119
|
+
# {"master" => "abc123", ...}
|
120
|
+
#
|
121
|
+
attr_reader :ref_map
|
122
|
+
|
123
|
+
# Gets a Hash cache of commit SHAs to a recursive tree of blobs.
|
124
|
+
#
|
125
|
+
# {"abc123" => [<BlobEntry>, <BlobEntry>]}
|
126
|
+
#
|
127
|
+
attr_reader :tree_map
|
128
|
+
|
129
|
+
# Gets a Hash cache of commit SHAs to the Grit::Commit instance.
|
130
|
+
#
|
131
|
+
# {"abcd123" => <Grit::Commit>}
|
132
|
+
#
|
133
|
+
attr_reader :commit_map
|
134
|
+
|
135
|
+
# Checks to see if the given String is a 40 character hex SHA.
|
136
|
+
#
|
137
|
+
# str - Possible String SHA.
|
138
|
+
#
|
139
|
+
# Returns true if the String is a SHA, or false.
|
140
|
+
def sha?(str)
|
141
|
+
!!(str =~ /^[0-9a-f]{40}$/)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Looks up the Git SHA for the given Git ref.
|
145
|
+
#
|
146
|
+
# ref - String Git ref.
|
147
|
+
#
|
148
|
+
# Returns a String SHA.
|
149
|
+
def ref_to_sha!(ref)
|
150
|
+
@repo.git.rev_list({:max_count=>1}, ref)
|
151
|
+
rescue Grit::GitRuby::Repository::NoSuchShaFound
|
152
|
+
end
|
153
|
+
|
154
|
+
# Looks up the Git blobs for a given commit.
|
155
|
+
#
|
156
|
+
# sha - String commit SHA.
|
157
|
+
#
|
158
|
+
# Returns an Array of BlobEntry instances.
|
159
|
+
def tree!(sha)
|
160
|
+
tree = @repo.git.native(:ls_tree,
|
161
|
+
{:r => true, :l => true, :z => true}, sha)
|
162
|
+
if tree.respond_to?(:force_encoding)
|
163
|
+
tree.force_encoding("UTF-8")
|
164
|
+
end
|
165
|
+
items = tree.split("\0").inject([]) do |memo, line|
|
166
|
+
memo << parse_tree_line(line)
|
167
|
+
end
|
168
|
+
|
169
|
+
if dir = @page_file_dir
|
170
|
+
regex = /^#{dir}\//
|
171
|
+
items.select { |i| i.path =~ regex }
|
172
|
+
else
|
173
|
+
items
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Reads the content from the Git db at the given SHA.
|
178
|
+
#
|
179
|
+
# sha - The String SHA.
|
180
|
+
#
|
181
|
+
# Returns the String content of the Git object.
|
182
|
+
def cat_file!(sha)
|
183
|
+
@repo.git.cat_file({:p => true}, sha)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Reads a Git commit.
|
187
|
+
#
|
188
|
+
# sha - The string SHA of the Git commit.
|
189
|
+
#
|
190
|
+
# Returns a Grit::Commit.
|
191
|
+
def commit!(sha)
|
192
|
+
@repo.commit(sha)
|
193
|
+
end
|
194
|
+
|
195
|
+
# Attempts to get the given data from a cache. If it doesn't exist, it'll
|
196
|
+
# pass the results of the yielded block to the cache for future accesses.
|
197
|
+
#
|
198
|
+
# name - The cache prefix used in building the full cache key.
|
199
|
+
# key - The unique cache key suffix, usually a String Git SHA.
|
200
|
+
#
|
201
|
+
# Yields a block to pass to the cache.
|
202
|
+
# Returns the cached result.
|
203
|
+
def get_cache(name, key)
|
204
|
+
cache = instance_variable_get("@#{name}_map")
|
205
|
+
value = cache[key]
|
206
|
+
if value.nil? && block_given?
|
207
|
+
set_cache(name, key, value = yield)
|
208
|
+
end
|
209
|
+
value == :_nil ? nil : value
|
210
|
+
end
|
211
|
+
|
212
|
+
# Writes some data to the internal cache.
|
213
|
+
#
|
214
|
+
# name - The cache prefix used in building the full cache key.
|
215
|
+
# key - The unique cache key suffix, usually a String Git SHA.
|
216
|
+
# value - The value to write to the cache.
|
217
|
+
#
|
218
|
+
# Returns nothing.
|
219
|
+
def set_cache(name, key, value)
|
220
|
+
cache = instance_variable_get("@#{name}_map")
|
221
|
+
cache[key] = value || :_nil
|
222
|
+
end
|
223
|
+
|
224
|
+
# Parses a line of output from the `ls-tree` command.
|
225
|
+
#
|
226
|
+
# line - A String line of output:
|
227
|
+
# "100644 blob 839c2291b30495b9a882c17d08254d3c90d8fb53 Home.md"
|
228
|
+
#
|
229
|
+
# Returns an Array of BlobEntry instances.
|
230
|
+
def parse_tree_line(line)
|
231
|
+
mode, type, sha, size, *name = line.split(/\s+/)
|
232
|
+
BlobEntry.new(sha, name.join(' '), size.to_i, mode.to_i(8))
|
233
|
+
end
|
234
|
+
|
235
|
+
# Decode octal sequences (\NNN) in tree path names.
|
236
|
+
#
|
237
|
+
# path - String path name.
|
238
|
+
#
|
239
|
+
# Returns a decoded String.
|
240
|
+
def decode_git_path(path)
|
241
|
+
if path[0] == ?" && path[-1] == ?"
|
242
|
+
path = path[1...-1]
|
243
|
+
path.gsub!(/\\\d{3}/) { |m| m[1..-1].to_i(8).chr }
|
244
|
+
end
|
245
|
+
path.gsub!(/\\[rn"\\]/) { |m| eval(%("#{m.to_s}")) }
|
246
|
+
path
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|