gitlab-gollum-lib 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|