gollum 1.0.1 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of gollum might be problematic. Click here for more details.
- data/HISTORY.md +31 -1
- data/README.md +2 -2
- data/Rakefile +2 -2
- data/bin/gollum +69 -7
- data/gollum.gemspec +14 -6
- data/lib/gollum.rb +25 -1
- data/lib/gollum/blob_entry.rb +71 -0
- data/lib/gollum/file.rb +11 -6
- data/lib/gollum/frontend/app.rb +34 -24
- data/lib/gollum/frontend/templates/compare.mustache +1 -1
- data/lib/gollum/frontend/templates/create.mustache +1 -1
- data/lib/gollum/frontend/templates/edit.mustache +2 -2
- data/lib/gollum/frontend/templates/error.mustache +11 -0
- data/lib/gollum/frontend/templates/history.mustache +3 -3
- data/lib/gollum/frontend/templates/page.mustache +9 -3
- data/lib/gollum/frontend/templates/search.mustache +19 -0
- data/lib/gollum/frontend/views/error.rb +7 -0
- data/lib/gollum/frontend/views/layout.rb +6 -0
- data/lib/gollum/frontend/views/search.rb +12 -0
- data/lib/gollum/markup.rb +73 -29
- data/lib/gollum/page.rb +49 -56
- data/lib/gollum/ruby1.8.rb +3 -0
- data/lib/gollum/wiki.rb +305 -92
- data/test/helper.rb +7 -1
- data/test/test_file.rb +9 -2
- data/test/test_markup.rb +107 -23
- data/test/test_page.rb +7 -1
- data/test/test_wiki.rb +131 -48
- metadata +45 -11
data/lib/gollum/wiki.rb
CHANGED
@@ -9,6 +9,12 @@ module Gollum
|
|
9
9
|
# Sets the file class used by all instances of this Wiki.
|
10
10
|
attr_writer :file_class
|
11
11
|
|
12
|
+
# Sets the default name for commits.
|
13
|
+
attr_accessor :default_committer_name
|
14
|
+
|
15
|
+
# Sets the default email for commits.
|
16
|
+
attr_accessor :default_committer_email
|
17
|
+
|
12
18
|
# Gets the page class used by all instances of this Wiki.
|
13
19
|
# Default: Gollum::Page.
|
14
20
|
def page_class
|
@@ -32,6 +38,8 @@ module Gollum
|
|
32
38
|
end
|
33
39
|
end
|
34
40
|
|
41
|
+
self.default_committer_name = 'Anonymous'
|
42
|
+
self.default_committer_email = 'anon@anon.com'
|
35
43
|
|
36
44
|
# The String base path to prefix to internal links. For example, when set
|
37
45
|
# to "/wiki", the page "Hobbit" will be linked as "/wiki/Hobbit". Defaults
|
@@ -43,7 +51,7 @@ module Gollum
|
|
43
51
|
# repo - The String path to the Git repository that holds the Gollum
|
44
52
|
# site.
|
45
53
|
# options - Optional Hash:
|
46
|
-
# :base_path - String base path for all Wiki links.
|
54
|
+
# :base_path - String base path for all Wiki links.
|
47
55
|
# Default: "/"
|
48
56
|
# :page_class - The page Class. Default: Gollum::Page
|
49
57
|
# :file_class - The file Class. Default: Gollum::File
|
@@ -55,6 +63,7 @@ module Gollum
|
|
55
63
|
@base_path = options[:base_path] || "/"
|
56
64
|
@page_class = options[:page_class] || self.class.page_class
|
57
65
|
@file_class = options[:file_class] || self.class.file_class
|
66
|
+
clear_cache
|
58
67
|
end
|
59
68
|
|
60
69
|
# Public: check whether the wiki's git repo exists on the filesystem.
|
@@ -115,17 +124,23 @@ module Gollum
|
|
115
124
|
#
|
116
125
|
# Returns the String SHA1 of the newly written version.
|
117
126
|
def write_page(name, format, data, commit = {})
|
118
|
-
|
127
|
+
commit = normalize_commit(commit)
|
128
|
+
index = self.repo.index
|
129
|
+
|
119
130
|
if pcommit = @repo.commit('master')
|
120
|
-
|
131
|
+
index.read_tree(pcommit.tree.id)
|
121
132
|
end
|
122
133
|
|
123
|
-
|
124
|
-
index = tree_map_to_index(map)
|
134
|
+
add_to_index(index, '', name, format, data)
|
125
135
|
|
126
136
|
parents = pcommit ? [pcommit] : []
|
127
137
|
actor = Grit::Actor.new(commit[:name], commit[:email])
|
128
|
-
index.commit(commit[:message], parents, actor)
|
138
|
+
sha1 = index.commit(commit[:message], parents, actor)
|
139
|
+
|
140
|
+
@ref_map.clear
|
141
|
+
update_working_dir(index, '', name, format)
|
142
|
+
|
143
|
+
sha1
|
129
144
|
end
|
130
145
|
|
131
146
|
# Public: Update an existing page with new content. The location of the
|
@@ -144,24 +159,32 @@ module Gollum
|
|
144
159
|
#
|
145
160
|
# Returns the String SHA1 of the newly written version.
|
146
161
|
def update_page(page, name, format, data, commit = {})
|
162
|
+
commit = normalize_commit(commit)
|
147
163
|
pcommit = @repo.commit('master')
|
148
|
-
map = tree_map(pcommit.tree)
|
149
164
|
name ||= page.name
|
150
165
|
format ||= page.format
|
151
|
-
index =
|
166
|
+
index = self.repo.index
|
167
|
+
|
168
|
+
dir = ::File.dirname(page.path)
|
169
|
+
dir = '' if dir == '.'
|
170
|
+
|
171
|
+
index.read_tree(pcommit.tree.id)
|
152
172
|
|
153
173
|
if page.name == name && page.format == format
|
154
|
-
index = tree_map_to_index(map)
|
155
174
|
index.add(page.path, normalize(data))
|
156
175
|
else
|
157
|
-
|
158
|
-
dir
|
159
|
-
map = add_to_tree_map(map, dir, name, format, data)
|
160
|
-
index = tree_map_to_index(map)
|
176
|
+
index.delete(page.path)
|
177
|
+
add_to_index(index, dir, name, format, data, :allow_same_ext)
|
161
178
|
end
|
162
179
|
|
163
180
|
actor = Grit::Actor.new(commit[:name], commit[:email])
|
164
|
-
index.commit(commit[:message], [pcommit], actor)
|
181
|
+
sha1 = index.commit(commit[:message], [pcommit], actor)
|
182
|
+
|
183
|
+
@ref_map.clear
|
184
|
+
update_working_dir(index, dir, page.name, page.format)
|
185
|
+
update_working_dir(index, dir, name, format)
|
186
|
+
|
187
|
+
sha1
|
165
188
|
end
|
166
189
|
|
167
190
|
# Public: Delete a page.
|
@@ -169,19 +192,27 @@ module Gollum
|
|
169
192
|
# page - The Gollum::Page to delete.
|
170
193
|
# commit - The commit Hash details:
|
171
194
|
# :message - The String commit message.
|
172
|
-
# :
|
195
|
+
# :name - The String author full name.
|
173
196
|
# :email - The String email address.
|
174
197
|
#
|
175
198
|
# Returns the String SHA1 of the newly written version.
|
176
199
|
def delete_page(page, commit)
|
177
200
|
pcommit = @repo.commit('master')
|
178
|
-
map = tree_map(pcommit.tree)
|
179
201
|
|
180
|
-
|
181
|
-
index
|
202
|
+
index = self.repo.index
|
203
|
+
index.read_tree(pcommit.tree.id)
|
204
|
+
index.delete(page.path)
|
205
|
+
|
206
|
+
dir = ::File.dirname(page.path)
|
207
|
+
dir = '' if dir == '.'
|
182
208
|
|
183
209
|
actor = Grit::Actor.new(commit[:name], commit[:email])
|
184
|
-
index.commit(commit[:message], [pcommit], actor)
|
210
|
+
sha1 = index.commit(commit[:message], [pcommit], actor)
|
211
|
+
|
212
|
+
@ref_map.clear
|
213
|
+
update_working_dir(index, dir, page.name, page.format)
|
214
|
+
|
215
|
+
sha1
|
185
216
|
end
|
186
217
|
|
187
218
|
# Public: Lists all pages for this wiki.
|
@@ -190,11 +221,39 @@ module Gollum
|
|
190
221
|
#
|
191
222
|
# Returns an Array of Gollum::Page instances.
|
192
223
|
def pages(treeish = nil)
|
193
|
-
treeish
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
224
|
+
tree_list(treeish || 'master')
|
225
|
+
end
|
226
|
+
|
227
|
+
# Public: Returns the number of pages accessible from a commit
|
228
|
+
#
|
229
|
+
# ref - A String ref that is either a commit SHA or references one.
|
230
|
+
#
|
231
|
+
# Returns a Fixnum
|
232
|
+
def size(ref = nil)
|
233
|
+
tree_map_for(ref || 'master').inject(0) do |num, entry|
|
234
|
+
num + (@page_class.valid_page_name?(entry.name) ? 1 : 0)
|
235
|
+
end
|
236
|
+
rescue Grit::GitRuby::Repository::NoSuchShaFound
|
237
|
+
0
|
238
|
+
end
|
239
|
+
|
240
|
+
# Public: Search all pages for this wiki.
|
241
|
+
#
|
242
|
+
# query - The string to search for
|
243
|
+
#
|
244
|
+
# Returns an Array with Objects of page name and count of matches
|
245
|
+
def search(query)
|
246
|
+
# See: http://github.com/Sirupsen/gollum/commit/f0a6f52bdaf6bee8253ca33bb3fceaeb27bfb87e
|
247
|
+
search_output = @repo.git.grep({:c => query}, 'master')
|
248
|
+
|
249
|
+
search_output.split("\n").collect do |line|
|
250
|
+
result = line.split(':')
|
251
|
+
file_name = Gollum::Page.canonicalize_filename(::File.basename(result[1]))
|
252
|
+
|
253
|
+
{
|
254
|
+
:count => result[2].to_i,
|
255
|
+
:name => file_name
|
256
|
+
}
|
198
257
|
end
|
199
258
|
end
|
200
259
|
|
@@ -225,6 +284,26 @@ module Gollum
|
|
225
284
|
# Returns the String path.
|
226
285
|
attr_reader :path
|
227
286
|
|
287
|
+
# Gets a Hash cache of refs to commit SHAs.
|
288
|
+
#
|
289
|
+
# {"master" => "abc123", ...}
|
290
|
+
#
|
291
|
+
# Returns the Hash cache.
|
292
|
+
attr_reader :ref_map
|
293
|
+
|
294
|
+
# Gets a Hash cache of commit SHAs to a recursive tree of blobs.
|
295
|
+
#
|
296
|
+
# {"abc123" => [["lib/foo.rb", "blob-sha"], [file, sha], ...], ...}
|
297
|
+
#
|
298
|
+
# Returns the Hash cache.
|
299
|
+
attr_reader :tree_map
|
300
|
+
|
301
|
+
# Gets the page class used by all instances of this Wiki.
|
302
|
+
attr_reader :page_class
|
303
|
+
|
304
|
+
# Gets the file class used by all instances of this Wiki.
|
305
|
+
attr_reader :file_class
|
306
|
+
|
228
307
|
# Normalize the data.
|
229
308
|
#
|
230
309
|
# data - The String data to be normalized.
|
@@ -234,102 +313,236 @@ module Gollum
|
|
234
313
|
data.gsub(/\r/, '')
|
235
314
|
end
|
236
315
|
|
316
|
+
# Assemble a Page's filename from its name and format.
|
317
|
+
#
|
318
|
+
# name - The String name of the page (may be in human format).
|
319
|
+
# format - The Symbol format of the page.
|
320
|
+
#
|
321
|
+
# Returns the String filename.
|
322
|
+
def page_file_name(name, format)
|
323
|
+
ext = @page_class.format_to_ext(format)
|
324
|
+
@page_class.cname(name) + '.' + ext
|
325
|
+
end
|
326
|
+
|
327
|
+
# Update the given file in the repository's working directory if there
|
328
|
+
# is a working directory present.
|
329
|
+
#
|
330
|
+
# index - The Grit::Index with which to sync.
|
331
|
+
# dir - The String directory in which the file lives.
|
332
|
+
# name - The String name of the page (may be in human format).
|
333
|
+
# format - The Symbol format of the page.
|
334
|
+
#
|
335
|
+
# Returns nothing.
|
336
|
+
def update_working_dir(index, dir, name, format)
|
337
|
+
unless @repo.bare
|
338
|
+
path =
|
339
|
+
if dir == ''
|
340
|
+
page_file_name(name, format)
|
341
|
+
else
|
342
|
+
::File.join(dir, page_file_name(name, format))
|
343
|
+
end
|
344
|
+
|
345
|
+
Dir.chdir(::File.join(@repo.path, '..')) do
|
346
|
+
if file_path_scheduled_for_deletion?(index.tree, path)
|
347
|
+
@repo.git.rm({'f' => true}, '--', path)
|
348
|
+
else
|
349
|
+
@repo.git.checkout({}, 'HEAD', '--', path)
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
237
355
|
# Fill an array with a list of pages.
|
238
356
|
#
|
239
|
-
#
|
240
|
-
# tree - The Grit::Tree to start with.
|
241
|
-
# sub_tree - Optional String specifying the parent path of the Page.
|
357
|
+
# ref - A String ref that is either a commit SHA or references one.
|
242
358
|
#
|
243
359
|
# Returns a flat Array of Gollum::Page instances.
|
244
|
-
def tree_list(
|
245
|
-
list
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
when Grit::Blob
|
250
|
-
if @page_class.valid_page_name?(item.name)
|
251
|
-
page = @page_class.new(self).populate(item, path)
|
252
|
-
page.version = commit
|
253
|
-
list << page
|
254
|
-
end
|
255
|
-
when Grit::Tree
|
256
|
-
list.push *tree_list(commit, item, path)
|
257
|
-
end
|
360
|
+
def tree_list(ref)
|
361
|
+
tree_map_for(ref).inject([]) do |list, entry|
|
362
|
+
next list unless @page_class.valid_page_name?(entry.name)
|
363
|
+
sha = ref_map[ref]
|
364
|
+
list << entry.page(self, @repo.commit(sha))
|
258
365
|
end
|
259
|
-
list
|
260
366
|
end
|
261
367
|
|
262
|
-
#
|
368
|
+
# Determine if a given file is scheduled to be deleted in the next commit
|
369
|
+
# for the given Index.
|
263
370
|
#
|
264
|
-
#
|
371
|
+
# map - The Hash map:
|
372
|
+
# key - The String directory or filename.
|
373
|
+
# val - The Hash submap or the String contents of the file.
|
374
|
+
# path - The String path of the file including extension.
|
265
375
|
#
|
266
|
-
# Returns
|
267
|
-
def
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
376
|
+
# Returns the Boolean response.
|
377
|
+
def file_path_scheduled_for_deletion?(map, path)
|
378
|
+
parts = path.split('/')
|
379
|
+
if parts.size == 1
|
380
|
+
deletions = map.keys.select { |k| !map[k] }
|
381
|
+
deletions.any? { |d| d == parts.first }
|
382
|
+
else
|
383
|
+
part = parts.shift
|
384
|
+
if rest = map[part]
|
385
|
+
file_path_scheduled_for_deletion?(rest, parts.join('/'))
|
386
|
+
else
|
387
|
+
false
|
275
388
|
end
|
276
389
|
end
|
277
|
-
map
|
278
390
|
end
|
279
391
|
|
280
|
-
#
|
392
|
+
# Determine if a given page (regardless of format) is scheduled to be
|
393
|
+
# deleted in the next commit for the given Index.
|
281
394
|
#
|
282
395
|
# map - The Hash map:
|
283
396
|
# key - The String directory or filename.
|
284
397
|
# val - The Hash submap or the String contents of the file.
|
285
|
-
#
|
286
|
-
#
|
287
|
-
#
|
288
|
-
# Returns the
|
289
|
-
def
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
398
|
+
# path - The String path of the page file. This may include the format
|
399
|
+
# extension in which case it will be ignored.
|
400
|
+
#
|
401
|
+
# Returns the Boolean response.
|
402
|
+
def page_path_scheduled_for_deletion?(map, path)
|
403
|
+
parts = path.split('/')
|
404
|
+
if parts.size == 1
|
405
|
+
deletions = map.keys.select { |k| !map[k] }
|
406
|
+
downfile = parts.first.downcase.sub(/\.\w+$/, '')
|
407
|
+
deletions.any? { |d| d.downcase.sub(/\.\w+$/, '') == downfile }
|
408
|
+
else
|
409
|
+
part = parts.shift
|
410
|
+
if rest = map[part]
|
411
|
+
page_path_scheduled_for_deletion?(rest, parts.join('/'))
|
412
|
+
else
|
413
|
+
false
|
299
414
|
end
|
300
415
|
end
|
301
|
-
index
|
302
416
|
end
|
303
417
|
|
304
|
-
|
305
|
-
|
306
|
-
|
418
|
+
# Adds a page to the given Index.
|
419
|
+
#
|
420
|
+
# index - The Grit::Index to which the page will be added.
|
421
|
+
# dir - The String subdirectory of the Gollum::Page without any
|
422
|
+
# prefix or suffix slashes (e.g. "foo/bar").
|
423
|
+
# name - The String Gollum::Page name.
|
424
|
+
# format - The Symbol Gollum::Page format.
|
425
|
+
# data - The String wiki data to store in the tree map.
|
426
|
+
# allow_same_ext - A Boolean determining if the tree map allows the same
|
427
|
+
# filename with the same extension.
|
428
|
+
#
|
429
|
+
# Raises Gollum::DuplicatePageError if a matching filename already exists.
|
430
|
+
# This way, pages are not inadvertently overwritten.
|
431
|
+
#
|
432
|
+
# Returns nothing (modifies the Index in place).
|
433
|
+
def add_to_index(index, dir, name, format, data, allow_same_ext = false)
|
434
|
+
path = page_file_name(name, format)
|
435
|
+
|
436
|
+
dir = '/' if dir.strip.empty?
|
307
437
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
438
|
+
fullpath = ::File.join(dir, path)
|
439
|
+
fullpath = fullpath[1..-1] if fullpath =~ /^\//
|
440
|
+
|
441
|
+
if index.current_tree && tree = index.current_tree / dir
|
442
|
+
downpath = path.downcase.sub(/\.\w+$/, '')
|
443
|
+
|
444
|
+
tree.blobs.each do |blob|
|
445
|
+
next if page_path_scheduled_for_deletion?(index.tree, fullpath)
|
446
|
+
file = blob.name.downcase.sub(/\.\w+$/, '')
|
447
|
+
file_ext = ::File.extname(blob.name).sub(/^\./, '')
|
448
|
+
if downpath == file && !(allow_same_ext && file_ext == ext)
|
449
|
+
raise DuplicatePageError.new(dir, blob.name, path)
|
450
|
+
end
|
451
|
+
end
|
312
452
|
end
|
313
453
|
|
314
|
-
(
|
315
|
-
map
|
454
|
+
index.add(fullpath, normalize(data))
|
316
455
|
end
|
317
456
|
|
318
|
-
#
|
457
|
+
# Ensures a commit hash has all the required fields for a commit.
|
319
458
|
#
|
320
|
-
#
|
321
|
-
#
|
459
|
+
# commit - The commit Hash details:
|
460
|
+
# :message - The String commit message.
|
461
|
+
# :name - The String author full name.
|
462
|
+
# :email - The String email address.
|
322
463
|
#
|
323
|
-
# Returns the
|
324
|
-
def
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
464
|
+
# Returns the commit Hash
|
465
|
+
def normalize_commit(commit = {})
|
466
|
+
commit[:name] = default_committer_name if commit[:name].to_s.empty?
|
467
|
+
commit[:email] = default_committer_email if commit[:email].to_s.empty?
|
468
|
+
commit
|
469
|
+
end
|
470
|
+
|
471
|
+
# Gets the default name for commits.
|
472
|
+
def default_committer_name
|
473
|
+
@default_committer_name ||= \
|
474
|
+
@repo.config['user.name'] || self.class.default_committer_name
|
475
|
+
end
|
476
|
+
|
477
|
+
# Gets the default email for commits.
|
478
|
+
def default_committer_email
|
479
|
+
@default_committer_email ||= \
|
480
|
+
@repo.config['user.email'] || self.class.default_committer_email
|
481
|
+
end
|
482
|
+
|
483
|
+
# Finds a full listing of files and their blob SHA for a given ref. Each
|
484
|
+
# listing is cached based on its actual commit SHA.
|
485
|
+
#
|
486
|
+
# ref - A String ref that is either a commit SHA or references one.
|
487
|
+
#
|
488
|
+
# Returns an Array of BlobEntry instances.
|
489
|
+
def tree_map_for(ref)
|
490
|
+
sha = @ref_map[ref] || ref
|
491
|
+
@tree_map[sha] || begin
|
492
|
+
real_sha = @repo.git.rev_list({:max_count=>1}, ref)
|
493
|
+
@ref_map[ref] = real_sha if real_sha != ref
|
494
|
+
@tree_map[real_sha] ||= parse_tree_for(real_sha)
|
495
|
+
end
|
496
|
+
rescue Grit::GitRuby::Repository::NoSuchShaFound
|
497
|
+
[]
|
498
|
+
end
|
499
|
+
|
500
|
+
# Finds the full listing of files and their blob SHA for a given commit
|
501
|
+
# SHA. No caching or ref lookups are performed.
|
502
|
+
#
|
503
|
+
# sha - String commit SHA.
|
504
|
+
#
|
505
|
+
# Returns an Array of BlobEntry instances.
|
506
|
+
def parse_tree_for(sha)
|
507
|
+
tree = @repo.git.native(:ls_tree, {:r => true, :z => true}, sha)
|
508
|
+
items = []
|
509
|
+
tree.split("\0").each do |line|
|
510
|
+
items << parse_tree_line(line)
|
511
|
+
end
|
512
|
+
items
|
513
|
+
end
|
514
|
+
|
515
|
+
# Parses a line of output from the `ls-tree` command.
|
516
|
+
#
|
517
|
+
# line - A String line of output:
|
518
|
+
# "100644 blob 839c2291b30495b9a882c17d08254d3c90d8fb53 Home.md"
|
519
|
+
#
|
520
|
+
# Returns an Array of BlobEntry instances.
|
521
|
+
def parse_tree_line(line)
|
522
|
+
data, name = line.split("\t")
|
523
|
+
mode, type, sha = data.split(' ')
|
524
|
+
name = decode_git_path(name)
|
525
|
+
BlobEntry.new sha, name
|
526
|
+
end
|
527
|
+
|
528
|
+
# Decode octal sequences (\NNN) in tree path names.
|
529
|
+
#
|
530
|
+
# path - String path name.
|
531
|
+
#
|
532
|
+
# Returns a decoded String.
|
533
|
+
def decode_git_path(path)
|
534
|
+
if path[0] == ?" && path[-1] == ?"
|
535
|
+
path = path[1...-1]
|
536
|
+
path.gsub!(/\\\d{3}/) { |m| m[1..-1].to_i(8).chr }
|
330
537
|
end
|
331
|
-
(
|
332
|
-
|
538
|
+
path.gsub!(/\\[rn"\\]/) { |m| eval(%("#{m.to_s}")) }
|
539
|
+
path
|
540
|
+
end
|
541
|
+
|
542
|
+
# Resets the ref and tree caches for this wiki.
|
543
|
+
def clear_cache
|
544
|
+
@ref_map = {}
|
545
|
+
@tree_map = {}
|
333
546
|
end
|
334
547
|
end
|
335
|
-
end
|
548
|
+
end
|