read-only-gollum 1.4.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.
Files changed (176) hide show
  1. data/Gemfile +4 -0
  2. data/HISTORY.md +102 -0
  3. data/Home.md +3 -0
  4. data/LICENSE +21 -0
  5. data/README.md +477 -0
  6. data/Rakefile +142 -0
  7. data/bin/read-only-gollum +126 -0
  8. data/docs/sanitization.md +32 -0
  9. data/lib/gollum.rb +41 -0
  10. data/lib/gollum/blob_entry.rb +78 -0
  11. data/lib/gollum/committer.rb +218 -0
  12. data/lib/gollum/file.rb +64 -0
  13. data/lib/gollum/frontend/app.rb +225 -0
  14. data/lib/gollum/frontend/public/css/dialog.css +141 -0
  15. data/lib/gollum/frontend/public/css/editor.css +537 -0
  16. data/lib/gollum/frontend/public/css/gollum.css +660 -0
  17. data/lib/gollum/frontend/public/css/ie7.css +69 -0
  18. data/lib/gollum/frontend/public/css/template.css +381 -0
  19. data/lib/gollum/frontend/public/images/icon-sprite.png +0 -0
  20. data/lib/gollum/frontend/public/javascript/editor/gollum.editor.js +1096 -0
  21. data/lib/gollum/frontend/public/javascript/editor/langs/asciidoc.js +167 -0
  22. data/lib/gollum/frontend/public/javascript/editor/langs/creole.js +104 -0
  23. data/lib/gollum/frontend/public/javascript/editor/langs/markdown.js +211 -0
  24. data/lib/gollum/frontend/public/javascript/editor/langs/org.js +173 -0
  25. data/lib/gollum/frontend/public/javascript/editor/langs/pod.js +111 -0
  26. data/lib/gollum/frontend/public/javascript/editor/langs/rdoc.js +74 -0
  27. data/lib/gollum/frontend/public/javascript/editor/langs/textile.js +175 -0
  28. data/lib/gollum/frontend/public/javascript/gollum.dialog.js +263 -0
  29. data/lib/gollum/frontend/public/javascript/gollum.js +161 -0
  30. data/lib/gollum/frontend/public/javascript/gollum.placeholder.js +54 -0
  31. data/lib/gollum/frontend/public/javascript/jquery.color.js +123 -0
  32. data/lib/gollum/frontend/public/javascript/jquery.js +7179 -0
  33. data/lib/gollum/frontend/templates/compare.mustache +38 -0
  34. data/lib/gollum/frontend/templates/create.mustache +17 -0
  35. data/lib/gollum/frontend/templates/edit.mustache +17 -0
  36. data/lib/gollum/frontend/templates/editor.mustache +116 -0
  37. data/lib/gollum/frontend/templates/error.mustache +8 -0
  38. data/lib/gollum/frontend/templates/history.mustache +58 -0
  39. data/lib/gollum/frontend/templates/layout.mustache +28 -0
  40. data/lib/gollum/frontend/templates/page.mustache +37 -0
  41. data/lib/gollum/frontend/templates/pages.mustache +35 -0
  42. data/lib/gollum/frontend/templates/search.mustache +36 -0
  43. data/lib/gollum/frontend/templates/searchbar.mustache +10 -0
  44. data/lib/gollum/frontend/views/compare.rb +94 -0
  45. data/lib/gollum/frontend/views/create.rb +48 -0
  46. data/lib/gollum/frontend/views/edit.rb +52 -0
  47. data/lib/gollum/frontend/views/editable.rb +13 -0
  48. data/lib/gollum/frontend/views/error.rb +7 -0
  49. data/lib/gollum/frontend/views/history.rb +44 -0
  50. data/lib/gollum/frontend/views/layout.rb +20 -0
  51. data/lib/gollum/frontend/views/page.rb +57 -0
  52. data/lib/gollum/frontend/views/pages.rb +19 -0
  53. data/lib/gollum/frontend/views/search.rb +20 -0
  54. data/lib/gollum/git_access.rb +248 -0
  55. data/lib/gollum/markup.rb +489 -0
  56. data/lib/gollum/page.rb +430 -0
  57. data/lib/gollum/pagination.rb +61 -0
  58. data/lib/gollum/sanitization.rb +174 -0
  59. data/lib/gollum/tex.rb +89 -0
  60. data/lib/gollum/web_sequence_diagram.rb +43 -0
  61. data/lib/gollum/wiki.rb +636 -0
  62. data/read-only-gollum.gemspec +224 -0
  63. data/templates/formatting.html +92 -0
  64. data/test/examples/empty.git/HEAD +1 -0
  65. data/test/examples/empty.git/config +5 -0
  66. data/test/examples/empty.git/description +1 -0
  67. data/test/examples/empty.git/hooks/applypatch-msg.sample +15 -0
  68. data/test/examples/empty.git/hooks/commit-msg.sample +24 -0
  69. data/test/examples/empty.git/hooks/post-commit.sample +8 -0
  70. data/test/examples/empty.git/hooks/post-receive.sample +15 -0
  71. data/test/examples/empty.git/hooks/post-update.sample +8 -0
  72. data/test/examples/empty.git/hooks/pre-applypatch.sample +14 -0
  73. data/test/examples/empty.git/hooks/pre-commit.sample +46 -0
  74. data/test/examples/empty.git/hooks/pre-rebase.sample +169 -0
  75. data/test/examples/empty.git/hooks/prepare-commit-msg.sample +36 -0
  76. data/test/examples/empty.git/hooks/update.sample +128 -0
  77. data/test/examples/empty.git/info/exclude +6 -0
  78. data/test/examples/empty.git/objects/info/.gitkeep +0 -0
  79. data/test/examples/empty.git/objects/pack/.gitkeep +0 -0
  80. data/test/examples/empty.git/refs/heads/.gitkeep +0 -0
  81. data/test/examples/lotr.git/COMMIT_EDITMSG +1 -0
  82. data/test/examples/lotr.git/HEAD +1 -0
  83. data/test/examples/lotr.git/ORIG_HEAD +1 -0
  84. data/test/examples/lotr.git/config +12 -0
  85. data/test/examples/lotr.git/description +1 -0
  86. data/test/examples/lotr.git/index +0 -0
  87. data/test/examples/lotr.git/info/exclude +6 -0
  88. data/test/examples/lotr.git/logs/HEAD +3 -0
  89. data/test/examples/lotr.git/logs/refs/heads/master +3 -0
  90. data/test/examples/lotr.git/objects/06/131480411710c92a82fe2d1e76932c70feb2e5 +0 -0
  91. data/test/examples/lotr.git/objects/0a/de1e2916346d4c1f2fb63b863fd3c16808fe44 +0 -0
  92. data/test/examples/lotr.git/objects/0e/d8cbe0a25235bd867e65193c7d837c66b328ef +3 -0
  93. data/test/examples/lotr.git/objects/12/629d666c5e3178f82f533f543d61b53dc78c0b +0 -0
  94. data/test/examples/lotr.git/objects/1d/b89ebba7e2c14d93b94ff98cfa3708a4f0d4e3 +2 -0
  95. data/test/examples/lotr.git/objects/24/49c2681badfd3c189e8ed658dacffe8ba48fe5 +0 -0
  96. data/test/examples/lotr.git/objects/25/4bdc1ba27d8b8a794538a8522d9a2b56ec2dd9 +0 -0
  97. data/test/examples/lotr.git/objects/2c/b9156ad383914561a8502fc70f5a1d887e48ad +4 -0
  98. data/test/examples/lotr.git/objects/5d/cac289a8603188d2c5caf481dcba2985126aaa +0 -0
  99. data/test/examples/lotr.git/objects/60/f12f4254f58801b9ee7db7bca5fa8aeefaa56b +0 -0
  100. data/test/examples/lotr.git/objects/71/4323c104239440a5c66ab12a67ed07a83c404f +0 -0
  101. data/test/examples/lotr.git/objects/84/0ec5b1ba1320e8ec443f28f99566f615d5af10 +0 -0
  102. data/test/examples/lotr.git/objects/93/6b83ee0dd8837adb82511e40d5e4ebe59bb675 +0 -0
  103. data/test/examples/lotr.git/objects/94/523d7ae48aeba575099dd12926420d8fd0425d +2 -0
  104. data/test/examples/lotr.git/objects/96/97dc65e095658bbd1b8e8678e08881e86d32f1 +0 -0
  105. data/test/examples/lotr.git/objects/a3/1ca2a7c352c92531a8b99815d15843b259e814 +0 -0
  106. data/test/examples/lotr.git/objects/a6/59b3763b822dd97544621fd0beef162ea37b14 +4 -0
  107. data/test/examples/lotr.git/objects/a8/ad3c09dd842a3517085bfadd37718856dee813 +0 -0
  108. data/test/examples/lotr.git/objects/aa/b61fe89d56f8614c0a8151da34f939dcedfa68 +0 -0
  109. data/test/examples/lotr.git/objects/bc/4b5fc0ce2c2ba3acef6647e4f67256ee45ab60 +0 -0
  110. data/test/examples/lotr.git/objects/c3/b43e9f08966b088e7a0192e436b7a884542e05 +0 -0
  111. data/test/examples/lotr.git/objects/dc/596d6b2dd89ab05c66f4abd7d5eb706bc17f19 +0 -0
  112. data/test/examples/lotr.git/objects/ec/da3205bee14520aab5a7bb307392064b938e83 +0 -0
  113. data/test/examples/lotr.git/objects/f4/84ebb1f40f8eb20d1bcd8d1d71934d2b8ae961 +0 -0
  114. data/test/examples/lotr.git/objects/fa/e7ef5344202bba4129abdc13060d9297d99465 +3 -0
  115. data/test/examples/lotr.git/objects/info/packs +2 -0
  116. data/test/examples/lotr.git/objects/pack/pack-dcbeaf3f6ff6c5eb08ea2b0a2d83626e8763546b.idx +0 -0
  117. data/test/examples/lotr.git/objects/pack/pack-dcbeaf3f6ff6c5eb08ea2b0a2d83626e8763546b.pack +0 -0
  118. data/test/examples/lotr.git/packed-refs +2 -0
  119. data/test/examples/lotr.git/refs/heads/master +1 -0
  120. data/test/examples/lotr.git/refs/remotes/origin/HEAD +1 -0
  121. data/test/examples/page_file_dir.git/COMMIT_EDITMSG +1 -0
  122. data/test/examples/page_file_dir.git/HEAD +1 -0
  123. data/test/examples/page_file_dir.git/config +6 -0
  124. data/test/examples/page_file_dir.git/description +1 -0
  125. data/test/examples/page_file_dir.git/index +0 -0
  126. data/test/examples/page_file_dir.git/info/exclude +6 -0
  127. data/test/examples/page_file_dir.git/logs/HEAD +1 -0
  128. data/test/examples/page_file_dir.git/logs/refs/heads/master +1 -0
  129. data/test/examples/page_file_dir.git/objects/0c/7d27db1f575263efdcab3dc650f4502a2dbcbf +0 -0
  130. data/test/examples/page_file_dir.git/objects/22/b404803c966dd92865614d86ff22ca12e50c1e +0 -0
  131. data/test/examples/page_file_dir.git/objects/25/7cc5642cb1a054f08cc83f2d943e56fd3ebe99 +0 -0
  132. data/test/examples/page_file_dir.git/objects/57/16ca5987cbf97d6bb54920bea6adde242d87e6 +0 -0
  133. data/test/examples/page_file_dir.git/objects/5b/43e14e0a15fb6f08feab1773d1c0991e9f71e2 +0 -0
  134. data/test/examples/page_file_dir.git/refs/heads/master +1 -0
  135. data/test/examples/revert.git/COMMIT_EDITMSG +1 -0
  136. data/test/examples/revert.git/HEAD +1 -0
  137. data/test/examples/revert.git/config +12 -0
  138. data/test/examples/revert.git/description +1 -0
  139. data/test/examples/revert.git/index +0 -0
  140. data/test/examples/revert.git/info/exclude +6 -0
  141. data/test/examples/revert.git/logs/HEAD +2 -0
  142. data/test/examples/revert.git/logs/refs/heads/master +2 -0
  143. data/test/examples/revert.git/objects/20/2ced67cea93c7b6bd2928aa1daef8d1d55a20d +0 -0
  144. data/test/examples/revert.git/objects/41/76394bfa11222363c66ce7e84b5f154095b6d9 +0 -0
  145. data/test/examples/revert.git/objects/6a/69f92020f5df77af6e8813ff1232493383b708 +0 -0
  146. data/test/examples/revert.git/objects/b4/785957bc986dc39c629de9fac9df46972c00fc +0 -0
  147. data/test/examples/revert.git/objects/f4/03b791119f8232b7cb0ba455c624ac6435f433 +0 -0
  148. data/test/examples/revert.git/objects/info/packs +2 -0
  149. data/test/examples/revert.git/objects/pack/pack-a561f8437234f74d0bacb9e0eebe52d207f5770d.idx +0 -0
  150. data/test/examples/revert.git/objects/pack/pack-a561f8437234f74d0bacb9e0eebe52d207f5770d.pack +0 -0
  151. data/test/examples/revert.git/packed-refs +2 -0
  152. data/test/examples/revert.git/refs/heads/master +1 -0
  153. data/test/examples/revert.git/refs/remotes/origin/HEAD +1 -0
  154. data/test/examples/yubiwa.git/HEAD +1 -0
  155. data/test/examples/yubiwa.git/config +5 -0
  156. data/test/examples/yubiwa.git/description +1 -0
  157. data/test/examples/yubiwa.git/info/exclude +6 -0
  158. data/test/examples/yubiwa.git/objects/10/fa2ddc4e3b4009d8a453aace10bd6148c1ad00 +0 -0
  159. data/test/examples/yubiwa.git/objects/52/4b82874327ea7cbf730389964ba7cb3de966de +0 -0
  160. data/test/examples/yubiwa.git/objects/58/3fc201cb457fb3f1480f3e1e5999b119633835 +0 -0
  161. data/test/examples/yubiwa.git/objects/87/bc1dd46ab3d3874d4e898d45dd512cc20a7cc8 +1 -0
  162. data/test/examples/yubiwa.git/objects/89/64ed1b4e21aa90e831763bbce9034bfda81b70 +0 -0
  163. data/test/examples/yubiwa.git/objects/9f/f6dd0660da5fba2d3374adb2b84fa653bb538b +0 -0
  164. data/test/examples/yubiwa.git/objects/ac/e97abf2b177815a1972d7db22f229f58c83309 +0 -0
  165. data/test/examples/yubiwa.git/objects/b1/f443863a4816628807fbf86141ebef055dda34 +0 -0
  166. data/test/examples/yubiwa.git/refs/heads/master +1 -0
  167. data/test/helper.rb +66 -0
  168. data/test/test_app.rb +169 -0
  169. data/test/test_committer.rb +64 -0
  170. data/test/test_file.rb +27 -0
  171. data/test/test_git_access.rb +52 -0
  172. data/test/test_markup.rb +628 -0
  173. data/test/test_page.rb +166 -0
  174. data/test/test_page_revert.rb +45 -0
  175. data/test/test_wiki.rb +462 -0
  176. metadata +470 -0
@@ -0,0 +1,174 @@
1
+ module Gollum
2
+ # Encapsulate sanitization options.
3
+ #
4
+ # This class does not yet support all options of Sanitize library.
5
+ # See http://github.com/rgrove/sanitize/.
6
+ class Sanitization
7
+ # Default whitelisted elements.
8
+ ELEMENTS = [
9
+ 'a', 'abbr', 'acronym', 'address', 'area', 'b', 'big',
10
+ 'blockquote', 'br', 'button', 'caption', 'center', 'cite',
11
+ 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir',
12
+ 'div', 'dl', 'dt', 'em', 'fieldset', 'font', 'form', 'h1',
13
+ 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'input',
14
+ 'ins', 'kbd', 'label', 'legend', 'li', 'map', 'menu',
15
+ 'ol', 'optgroup', 'option', 'p', 'pre', 'q', 's', 'samp',
16
+ 'select', 'small', 'span', 'strike', 'strong', 'sub',
17
+ 'sup', 'table', 'tbody', 'td', 'textarea', 'tfoot', 'th',
18
+ 'thead', 'tr', 'tt', 'u', 'ul', 'var'
19
+ ].freeze
20
+
21
+ # Default whitelisted attributes.
22
+ ATTRIBUTES = {
23
+ 'a' => ['href'],
24
+ 'img' => ['src'],
25
+ :all => ['abbr', 'accept', 'accept-charset',
26
+ 'accesskey', 'action', 'align', 'alt', 'axis',
27
+ 'border', 'cellpadding', 'cellspacing', 'char',
28
+ 'charoff', 'class', 'charset', 'checked', 'cite',
29
+ 'clear', 'cols', 'colspan', 'color',
30
+ 'compact', 'coords', 'datetime', 'dir',
31
+ 'disabled', 'enctype', 'for', 'frame',
32
+ 'headers', 'height', 'hreflang',
33
+ 'hspace', 'ismap', 'label', 'lang',
34
+ 'longdesc', 'maxlength', 'media', 'method',
35
+ 'multiple', 'name', 'nohref', 'noshade',
36
+ 'nowrap', 'prompt', 'readonly', 'rel', 'rev',
37
+ 'rows', 'rowspan', 'rules', 'scope',
38
+ 'selected', 'shape', 'size', 'span',
39
+ 'start', 'summary', 'tabindex', 'target',
40
+ 'title', 'type', 'usemap', 'valign', 'value',
41
+ 'vspace', 'width']
42
+ }.freeze
43
+
44
+ # Default whitelisted protocols for URLs.
45
+ PROTOCOLS = {
46
+ 'a' => {'href' => ['http', 'https', 'mailto', 'ftp', 'irc', :relative]},
47
+ 'img' => {'src' => ['http', 'https', :relative]}
48
+ }.freeze
49
+
50
+ ADD_ATTRIBUTES = lambda do |env, node|
51
+ if add = env[:config][:add_attributes][node.name]
52
+ add.each do |key, value|
53
+ node[key] = value
54
+ end
55
+ end
56
+ end
57
+
58
+ # Default elements whose contents will be removed in addition
59
+ # to the elements themselve
60
+ REMOVE_CONTENTS = [
61
+ 'script',
62
+ 'style'
63
+ ].freeze
64
+
65
+ # Default transformers to force @id attributes with 'wiki-' prefix
66
+ TRANSFORMERS = [
67
+ lambda do |env|
68
+ node = env[:node]
69
+ return if env[:is_whitelisted] || !node.element?
70
+ prefix = env[:config][:id_prefix]
71
+ found_attrs = %w(id name).select do |key|
72
+ if value = node[key]
73
+ node[key] = value.gsub(/\A(#{prefix})?/, prefix)
74
+ end
75
+ end
76
+ if found_attrs.size > 0
77
+ ADD_ATTRIBUTES.call(env, node)
78
+ {}
79
+ end
80
+ end,
81
+ lambda do |env|
82
+ node = env[:node]
83
+ return unless value = node['href']
84
+ prefix = env[:config][:id_prefix]
85
+ node['href'] = value.gsub(/\A\#(#{prefix})?/, '#'+prefix)
86
+ ADD_ATTRIBUTES.call(env, node)
87
+ {}
88
+ end
89
+ ].freeze
90
+
91
+ # Gets an Array of whitelisted HTML elements. Default: ELEMENTS.
92
+ attr_reader :elements
93
+
94
+ # Gets a Hash describing which attributes are allowed in which HTML
95
+ # elements. Default: ATTRIBUTES.
96
+ attr_reader :attributes
97
+
98
+ # Gets a Hash describing which URI protocols are allowed in HTML
99
+ # attributes. Default: PROTOCOLS
100
+ attr_reader :protocols
101
+
102
+ # Gets a Hash describing which URI protocols are allowed in HTML
103
+ # attributes. Default: TRANSFORMERS
104
+ attr_reader :transformers
105
+
106
+ # Gets or sets a String prefix which is added to ID attributes.
107
+ # Default: 'wiki-'
108
+ attr_accessor :id_prefix
109
+
110
+ # Gets a Hash describing HTML attributes that Sanitize should add.
111
+ # Default: {}
112
+ attr_reader :add_attributes
113
+
114
+ # Gets an Array of element names whose contents will be removed in addition
115
+ # to the elements themselves. Default: REMOVE_CONTENTS
116
+ attr_reader :remove_contents
117
+
118
+ # Sets a boolean determining whether Sanitize allows HTML comments in the
119
+ # output. Default: false.
120
+ attr_writer :allow_comments
121
+
122
+ def initialize
123
+ @elements = ELEMENTS
124
+ @attributes = ATTRIBUTES
125
+ @protocols = PROTOCOLS
126
+ @transformers = TRANSFORMERS
127
+ @add_attributes = {}
128
+ @remove_contents = REMOVE_CONTENTS
129
+ @allow_comments = false
130
+ @id_prefix = 'wiki-'
131
+ yield self if block_given?
132
+ end
133
+
134
+ # Determines if Sanitize should allow HTML comments.
135
+ #
136
+ # Returns True if comments are allowed, or False.
137
+ def allow_comments?
138
+ !!@allow_comments
139
+ end
140
+
141
+ # Modifies the current Sanitization instance to sanitize older revisions
142
+ # of pages.
143
+ #
144
+ # Returns a Sanitization instance.
145
+ def history_sanitization
146
+ self.class.new do |sanitize|
147
+ sanitize.add_attributes['a'] = {'rel' => 'nofollow'}
148
+ end
149
+ end
150
+
151
+ # Builds a Hash of options suitable for Sanitize.clean.
152
+ #
153
+ # Returns a Hash.
154
+ def to_hash
155
+ { :elements => elements,
156
+ :attributes => attributes,
157
+ :protocols => protocols,
158
+ :add_attributes => add_attributes,
159
+ :remove_contents => remove_contents,
160
+ :allow_comments => allow_comments?,
161
+ :transformers => transformers,
162
+ :id_prefix => id_prefix
163
+ }
164
+ end
165
+
166
+ # Builds a Sanitize instance from the current options.
167
+ #
168
+ # Returns a Sanitize instance.
169
+ def to_sanitize
170
+ Sanitize.new(to_hash)
171
+ end
172
+ end
173
+ end
174
+
@@ -0,0 +1,89 @@
1
+ require 'fileutils'
2
+ require 'shellwords'
3
+ require 'tmpdir'
4
+ require 'posix/spawn'
5
+
6
+ module Gollum
7
+ module Tex
8
+ class Error < StandardError; end
9
+
10
+ extend POSIX::Spawn
11
+
12
+ Template = <<-EOS
13
+ \\documentclass[12pt]{article}
14
+ \\usepackage{color}
15
+ \\usepackage[dvips]{graphicx}
16
+ \\pagestyle{empty}
17
+ \\pagecolor{white}
18
+ \\begin{document}
19
+ {\\color{black}
20
+ \\begin{eqnarray*}
21
+ %s
22
+ \\end{eqnarray*}}
23
+ \\end{document}
24
+ EOS
25
+
26
+ class << self
27
+ attr_accessor :latex_path, :dvips_path, :convert_path
28
+ end
29
+
30
+ self.latex_path = 'latex'
31
+ self.dvips_path = 'dvips'
32
+ self.convert_path = 'convert'
33
+
34
+ def self.check_dependencies!
35
+ return if @dependencies_available
36
+
37
+ if `which latex` == ""
38
+ raise Error, "`latex` command not found"
39
+ end
40
+
41
+ if `which dvips` == ""
42
+ raise Error, "`dvips` command not found"
43
+ end
44
+
45
+ if `which convert` == ""
46
+ raise Error, "`convert` command not found"
47
+ end
48
+
49
+ if `which gs` == ""
50
+ raise Error, "`gs` command not found"
51
+ end
52
+
53
+ @dependencies_available = true
54
+ end
55
+
56
+ def self.render_formula(formula)
57
+ check_dependencies!
58
+
59
+ Dir.mktmpdir('tex') do |path|
60
+ tex_path = ::File.join(path, 'formula.tex')
61
+ dvi_path = ::File.join(path, 'formula.dvi')
62
+ eps_path = ::File.join(path, 'formula.eps')
63
+ png_path = ::File.join(path, 'formula.png')
64
+
65
+ ::File.open(tex_path, 'w') { |f| f.write(Template % formula) }
66
+
67
+ result = sh latex_path, '-interaction=batchmode', 'formula.tex', :chdir => path
68
+ raise Error, "`latex` command failed: #{result}" unless ::File.exist?(dvi_path)
69
+
70
+ result = sh dvips_path, '-o', eps_path, '-E', dvi_path
71
+ raise Error, "`dvips` command failed: #{result}" unless ::File.exist?(eps_path)
72
+ result = sh convert_path, '+adjoin',
73
+ '-antialias',
74
+ '-transparent', 'white',
75
+ '-density', '150x150',
76
+ eps_path, png_path
77
+ raise Error, "`convert` command failed: #{result}" unless ::File.exist?(png_path)
78
+
79
+ ::File.read(png_path)
80
+ end
81
+ end
82
+
83
+ private
84
+ def self.sh(*args)
85
+ pid = spawn *args
86
+ Process::waitpid(pid)
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,43 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'open-uri'
4
+
5
+ class Gollum::WebSequenceDiagram
6
+ WSD_URL = "http://www.websequencediagrams.com/index.php"
7
+
8
+ # Initialize a new WebSequenceDiagram object.
9
+ #
10
+ # code - The String containing the sequence diagram markup.
11
+ # style - The String containing the rendering style.
12
+ #
13
+ # Returns a new Gollum::WebSequenceDiagram object
14
+ def initialize(code, style)
15
+ @code = code
16
+ @style = style
17
+ @tag = ""
18
+
19
+ render
20
+ end
21
+
22
+ # Render the sequence diagram on the remote server and store the url to
23
+ # the rendered image.
24
+ #
25
+ # Returns nil.
26
+ def render
27
+ response = Net::HTTP.post_form(URI.parse(WSD_URL), 'style' => @style, 'message' => @code)
28
+ if response.body =~ /img: "(.+)"/
29
+ url = "http://www.websequencediagrams.com/#{$1}"
30
+ @tag = "<img src=\"#{url}\" />"
31
+ else
32
+ puts response.body
33
+ @tag ="Sorry, unable to render sequence diagram at this time."
34
+ end
35
+ end
36
+
37
+ # Gets the HTML IMG tag for the sequence diagram.
38
+ #
39
+ # Returns a String containing the IMG tag.
40
+ def to_tag
41
+ @tag
42
+ end
43
+ end
@@ -0,0 +1,636 @@
1
+ module Gollum
2
+ class Wiki
3
+ include Pagination
4
+
5
+ class << self
6
+ # Sets the page class used by all instances of this Wiki.
7
+ attr_writer :page_class
8
+
9
+ # Sets the file class used by all instances of this Wiki.
10
+ attr_writer :file_class
11
+
12
+ # Sets the markup class used by all instances of this Wiki.
13
+ attr_writer :markup_classes
14
+
15
+ # Sets the default ref for the wiki.
16
+ attr_accessor :default_ref
17
+
18
+ # Sets the default name for commits.
19
+ attr_accessor :default_committer_name
20
+
21
+ # Sets the default email for commits.
22
+ attr_accessor :default_committer_email
23
+
24
+ # Array of chars to substitute whitespace for when trying to locate file in git repo.
25
+ attr_accessor :default_ws_subs
26
+
27
+ # Sets sanitization options. Set to false to deactivate
28
+ # sanitization altogether.
29
+ attr_writer :sanitization
30
+
31
+ # Sets sanitization options. Set to false to deactivate
32
+ # sanitization altogether.
33
+ attr_writer :history_sanitization
34
+
35
+ # Gets the page class used by all instances of this Wiki.
36
+ # Default: Gollum::Page.
37
+ def page_class
38
+ @page_class ||
39
+ if superclass.respond_to?(:page_class)
40
+ superclass.page_class
41
+ else
42
+ ::Gollum::Page
43
+ end
44
+ end
45
+
46
+ # Gets the file class used by all instances of this Wiki.
47
+ # Default: Gollum::File.
48
+ def file_class
49
+ @file_class ||
50
+ if superclass.respond_to?(:file_class)
51
+ superclass.file_class
52
+ else
53
+ ::Gollum::File
54
+ end
55
+ end
56
+
57
+ # Gets the markup class used by all instances of this Wiki.
58
+ # Default: Gollum::Markup
59
+ def markup_classes
60
+ @markup_classes ||=
61
+ if superclass.respond_to?(:markup_classes)
62
+ superclass.markup_classes
63
+ else
64
+ Hash.new(::Gollum::Markup)
65
+ end
66
+ end
67
+
68
+ # Gets the default markup class used by all instances of this Wiki.
69
+ # Kept for backwards compatibility until Gollum v2.x
70
+ def markup_class(language=:default)
71
+ markup_classes[language]
72
+ end
73
+
74
+ # Sets the default markup class used by all instances of this Wiki.
75
+ # Kept for backwards compatibility until Gollum v2.x
76
+ def markup_class=(default)
77
+ @markup_classes = Hash.new(default).update(markup_classes)
78
+ default
79
+ end
80
+
81
+ alias_method :default_markup_class, :markup_class
82
+ alias_method :default_markup_class=, :markup_class=
83
+
84
+ # Gets the default sanitization options for current pages used by
85
+ # instances of this Wiki.
86
+ def sanitization
87
+ if @sanitization.nil?
88
+ @sanitization = Sanitization.new
89
+ end
90
+ @sanitization
91
+ end
92
+
93
+ # Gets the default sanitization options for older page revisions used by
94
+ # instances of this Wiki.
95
+ def history_sanitization
96
+ if @history_sanitization.nil?
97
+ @history_sanitization = sanitization ?
98
+ sanitization.history_sanitization :
99
+ false
100
+ end
101
+ @history_sanitization
102
+ end
103
+ end
104
+
105
+ self.default_ref = 'master'
106
+ self.default_committer_name = 'Anonymous'
107
+ self.default_committer_email = 'anon@anon.com'
108
+
109
+ self.default_ws_subs = ['_','-']
110
+
111
+ # The String base path to prefix to internal links. For example, when set
112
+ # to "/wiki", the page "Hobbit" will be linked as "/wiki/Hobbit". Defaults
113
+ # to "/".
114
+ attr_reader :base_path
115
+
116
+ # Gets the sanitization options for current pages used by this Wiki.
117
+ attr_reader :sanitization
118
+
119
+ # Gets the sanitization options for older page revisions used by this Wiki.
120
+ attr_reader :history_sanitization
121
+
122
+ # Gets the String ref in which all page files reside.
123
+ attr_reader :ref
124
+
125
+ # Gets the String directory in which all page files reside.
126
+ attr_reader :page_file_dir
127
+
128
+ # Gets the Array of chars to sub for ws in filenames.
129
+ attr_reader :ws_subs
130
+
131
+ # Public: Initialize a new Gollum Repo.
132
+ #
133
+ # path - The String path to the Git repository that holds the Gollum
134
+ # site.
135
+ # options - Optional Hash:
136
+ # :base_path - String base path for all Wiki links.
137
+ # Default: "/"
138
+ # :page_class - The page Class. Default: Gollum::Page
139
+ # :file_class - The file Class. Default: Gollum::File
140
+ # :markup_classes - A hash containing the markup Classes for each
141
+ # document type. Default: { Gollum::Markup }
142
+ # :sanitization - An instance of Sanitization.
143
+ # :page_file_dir - String the directory in which all page files reside
144
+ # :ref - String the repository ref to retrieve pages from
145
+ # :ws_subs - Array of chars to sub for ws in filenames.
146
+ #
147
+ # Returns a fresh Gollum::Repo.
148
+ def initialize(path, options = {})
149
+ if path.is_a?(GitAccess)
150
+ options[:access] = path
151
+ path = path.path
152
+ end
153
+ @path = path
154
+ @page_file_dir = options[:page_file_dir]
155
+ @access = options[:access] || GitAccess.new(path, @page_file_dir)
156
+ @base_path = options[:base_path] || "/"
157
+ @page_class = options[:page_class] || self.class.page_class
158
+ @file_class = options[:file_class] || self.class.file_class
159
+ @markup_classes = options[:markup_classes] || self.class.markup_classes
160
+ @repo = @access.repo
161
+ @ref = options[:ref] || self.class.default_ref
162
+ @sanitization = options[:sanitization] || self.class.sanitization
163
+ @ws_subs = options[:ws_subs] ||
164
+ self.class.default_ws_subs
165
+ @history_sanitization = options[:history_sanitization] ||
166
+ self.class.history_sanitization
167
+ end
168
+
169
+ # Public: check whether the wiki's git repo exists on the filesystem.
170
+ #
171
+ # Returns true if the repo exists, and false if it does not.
172
+ def exist?
173
+ @access.exist?
174
+ end
175
+
176
+ # Public: Get the formatted page for a given page name.
177
+ #
178
+ # name - The human or canonical String page name of the wiki page.
179
+ # version - The String version ID to find (default: @ref).
180
+ #
181
+ # Returns a Gollum::Page or nil if no matching page was found.
182
+ def page(name, version = @ref)
183
+ @page_class.new(self).find(name, version)
184
+ end
185
+
186
+ # Public: Get the static file for a given name.
187
+ #
188
+ # name - The full String pathname to the file.
189
+ # version - The String version ID to find (default: @ref).
190
+ #
191
+ # Returns a Gollum::File or nil if no matching file was found.
192
+ def file(name, version = @ref)
193
+ @file_class.new(self).find(name, version)
194
+ end
195
+
196
+ # Public: Create an in-memory Page with the given data and format. This
197
+ # is useful for previewing what content will look like before committing
198
+ # it to the repository.
199
+ #
200
+ # name - The String name of the page.
201
+ # format - The Symbol format of the page.
202
+ # data - The new String contents of the page.
203
+ #
204
+ # Returns the in-memory Gollum::Page.
205
+ def preview_page(name, data, format)
206
+ page = @page_class.new(self)
207
+ ext = @page_class.format_to_ext(format.to_sym)
208
+ name = @page_class.cname(name) + '.' + ext
209
+ blob = OpenStruct.new(:name => name, :data => data)
210
+ page.populate(blob)
211
+ page.version = @access.commit('master')
212
+ page
213
+ end
214
+
215
+ # Public: Write a new version of a page to the Gollum repo root.
216
+ #
217
+ # name - The String name of the page.
218
+ # format - The Symbol format of the page.
219
+ # data - The new String contents of the page.
220
+ # commit - The commit Hash details:
221
+ # :message - The String commit message.
222
+ # :name - The String author full name.
223
+ # :email - The String email address.
224
+ # :parent - Optional Grit::Commit parent to this update.
225
+ # :tree - Optional String SHA of the tree to create the
226
+ # index from.
227
+ # :committer - Optional Gollum::Committer instance. If provided,
228
+ # assume that this operation is part of batch of
229
+ # updates and the commit happens later.
230
+ #
231
+ # Returns the String SHA1 of the newly written version, or the
232
+ # Gollum::Committer instance if this is part of a batch update.
233
+ def write_page(name, format, data, commit = {})
234
+ multi_commit = false
235
+
236
+ committer = if obj = commit[:committer]
237
+ multi_commit = true
238
+ obj
239
+ else
240
+ Committer.new(self, commit)
241
+ end
242
+
243
+ filename = Gollum::Page.cname(name)
244
+
245
+ committer.add_to_index('', filename, format, data)
246
+
247
+ committer.after_commit do |index, sha|
248
+ @access.refresh
249
+ index.update_working_dir('', filename, format)
250
+ end
251
+
252
+ multi_commit ? committer : committer.commit
253
+ end
254
+
255
+ # Public: Update an existing page with new content. The location of the
256
+ # page inside the repository will not change. If the given format is
257
+ # different than the current format of the page, the filename will be
258
+ # changed to reflect the new format.
259
+ #
260
+ # page - The Gollum::Page to update.
261
+ # name - The String extension-less name of the page.
262
+ # format - The Symbol format of the page.
263
+ # data - The new String contents of the page.
264
+ # commit - The commit Hash details:
265
+ # :message - The String commit message.
266
+ # :name - The String author full name.
267
+ # :email - The String email address.
268
+ # :parent - Optional Grit::Commit parent to this update.
269
+ # :tree - Optional String SHA of the tree to create the
270
+ # index from.
271
+ # :committer - Optional Gollum::Committer instance. If provided,
272
+ # assume that this operation is part of batch of
273
+ # updates and the commit happens later.
274
+ #
275
+ # Returns the String SHA1 of the newly written version, or the
276
+ # Gollum::Committer instance if this is part of a batch update.
277
+ def update_page(page, name, format, data, commit = {})
278
+ name ||= page.name
279
+ format ||= page.format
280
+ dir = ::File.dirname(page.path)
281
+ dir = '' if dir == '.'
282
+ filename = (rename = page.name != name) ?
283
+ Gollum::Page.cname(name) : page.filename_stripped
284
+
285
+ multi_commit = false
286
+
287
+ committer = if obj = commit[:committer]
288
+ multi_commit = true
289
+ obj
290
+ else
291
+ Committer.new(self, commit)
292
+ end
293
+
294
+ if !rename && page.format == format
295
+ committer.add(page.path, normalize(data))
296
+ else
297
+ committer.delete(page.path)
298
+ committer.add_to_index(dir, filename, format, data, :allow_same_ext)
299
+ end
300
+
301
+ committer.after_commit do |index, sha|
302
+ @access.refresh
303
+ index.update_working_dir(dir, page.filename_stripped, page.format)
304
+ index.update_working_dir(dir, filename, format)
305
+ end
306
+
307
+ multi_commit ? committer : committer.commit
308
+ end
309
+
310
+ # Public: Delete a page.
311
+ #
312
+ # page - The Gollum::Page to delete.
313
+ # commit - The commit Hash details:
314
+ # :message - The String commit message.
315
+ # :name - The String author full name.
316
+ # :email - The String email address.
317
+ # :parent - Optional Grit::Commit parent to this update.
318
+ # :tree - Optional String SHA of the tree to create the
319
+ # index from.
320
+ # :committer - Optional Gollum::Committer instance. If provided,
321
+ # assume that this operation is part of batch of
322
+ # updates and the commit happens later.
323
+ #
324
+ # Returns the String SHA1 of the newly written version, or the
325
+ # Gollum::Committer instance if this is part of a batch update.
326
+ def delete_page(page, commit)
327
+ multi_commit = false
328
+
329
+ committer = if obj = commit[:committer]
330
+ multi_commit = true
331
+ obj
332
+ else
333
+ Committer.new(self, commit)
334
+ end
335
+
336
+ committer.delete(page.path)
337
+
338
+ committer.after_commit do |index, sha|
339
+ dir = ::File.dirname(page.path)
340
+ dir = '' if dir == '.'
341
+
342
+ @access.refresh
343
+ index.update_working_dir(dir, page.filename_stripped, page.format)
344
+ end
345
+
346
+ multi_commit ? committer : committer.commit
347
+ end
348
+
349
+ # Public: Applies a reverse diff for a given page. If only 1 SHA is given,
350
+ # the reverse diff will be taken from its parent (^SHA...SHA). If two SHAs
351
+ # are given, the reverse diff is taken from SHA1...SHA2.
352
+ #
353
+ # page - The Gollum::Page to delete.
354
+ # sha1 - String SHA1 of the earlier parent if two SHAs are given,
355
+ # or the child.
356
+ # sha2 - Optional String SHA1 of the child.
357
+ # commit - The commit Hash details:
358
+ # :message - The String commit message.
359
+ # :name - The String author full name.
360
+ # :email - The String email address.
361
+ # :parent - Optional Grit::Commit parent to this update.
362
+ #
363
+ # Returns a String SHA1 of the new commit, or nil if the reverse diff does
364
+ # not apply.
365
+ def revert_page(page, sha1, sha2 = nil, commit = {})
366
+ if sha2.is_a?(Hash)
367
+ commit = sha2
368
+ sha2 = nil
369
+ end
370
+
371
+ patch = full_reverse_diff_for(page, sha1, sha2)
372
+ committer = Committer.new(self, commit)
373
+ parent = committer.parents[0]
374
+ committer.options[:tree] = @repo.git.apply_patch(parent.sha, patch)
375
+ return false unless committer.options[:tree]
376
+ committer.after_commit do |index, sha|
377
+ @access.refresh
378
+
379
+ files = []
380
+ if page
381
+ files << [page.path, page.filename_stripped, page.format]
382
+ else
383
+ # Grit::Diff can't parse reverse diffs.... yet
384
+ patch.each_line do |line|
385
+ if line =~ %r{^diff --git b/.+? a/(.+)$}
386
+ path = $1
387
+ ext = ::File.extname(path)
388
+ name = ::File.basename(path, ext)
389
+ if format = ::Gollum::Page.format_for(ext)
390
+ files << [path, name, format]
391
+ end
392
+ end
393
+ end
394
+ end
395
+
396
+ files.each do |(path, name, format)|
397
+ dir = ::File.dirname(path)
398
+ dir = '' if dir == '.'
399
+ index.update_working_dir(dir, name, format)
400
+ end
401
+ end
402
+
403
+ committer.commit
404
+ end
405
+
406
+ # Public: Applies a reverse diff to the repo. If only 1 SHA is given,
407
+ # the reverse diff will be taken from its parent (^SHA...SHA). If two SHAs
408
+ # are given, the reverse diff is taken from SHA1...SHA2.
409
+ #
410
+ # sha1 - String SHA1 of the earlier parent if two SHAs are given,
411
+ # or the child.
412
+ # sha2 - Optional String SHA1 of the child.
413
+ # commit - The commit Hash details:
414
+ # :message - The String commit message.
415
+ # :name - The String author full name.
416
+ # :email - The String email address.
417
+ #
418
+ # Returns a String SHA1 of the new commit, or nil if the reverse diff does
419
+ # not apply.
420
+ def revert_commit(sha1, sha2 = nil, commit = {})
421
+ revert_page(nil, sha1, sha2, commit)
422
+ end
423
+
424
+ # Public: Lists all pages for this wiki.
425
+ #
426
+ # treeish - The String commit ID or ref to find (default: @ref)
427
+ #
428
+ # Returns an Array of Gollum::Page instances.
429
+ def pages(treeish = nil)
430
+ tree_list(treeish || @ref)
431
+ end
432
+
433
+ # Public: Returns the number of pages accessible from a commit
434
+ #
435
+ # ref - A String ref that is either a commit SHA or references one.
436
+ #
437
+ # Returns a Fixnum
438
+ def size(ref = nil)
439
+ tree_map_for(ref || @ref).inject(0) do |num, entry|
440
+ num + (@page_class.valid_page_name?(entry.name) ? 1 : 0)
441
+ end
442
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
443
+ 0
444
+ end
445
+
446
+ # Public: Search all pages for this wiki.
447
+ #
448
+ # query - The string to search for
449
+ #
450
+ # Returns an Array with Objects of page name and count of matches
451
+ def search(query)
452
+ args = [{}, '-i', '-c', query, @ref, '--']
453
+ args << '--' << @page_file_dir if @page_file_dir
454
+
455
+ @repo.git.grep(*args).split("\n").map! do |line|
456
+ result = line.split(':')
457
+ file_name = Gollum::Page.canonicalize_filename(::File.basename(result[1]))
458
+
459
+ {
460
+ :count => result[2].to_i,
461
+ :name => file_name
462
+ }
463
+ end
464
+ end
465
+
466
+ # Public: All of the versions that have touched the Page.
467
+ #
468
+ # options - The options Hash:
469
+ # :page - The Integer page number (default: 1).
470
+ # :per_page - The Integer max count of items to return.
471
+ #
472
+ # Returns an Array of Grit::Commit.
473
+ def log(options = {})
474
+ @repo.log(@ref, nil, log_pagination_options(options))
475
+ end
476
+
477
+ # Public: Refreshes just the cached Git reference data. This should
478
+ # be called after every Gollum update.
479
+ #
480
+ # Returns nothing.
481
+ def clear_cache
482
+ @access.refresh
483
+ end
484
+
485
+ # Public: Creates a Sanitize instance using the Wiki's sanitization
486
+ # options.
487
+ #
488
+ # Returns a Sanitize instance.
489
+ def sanitizer
490
+ if options = sanitization
491
+ @sanitizer ||= options.to_sanitize
492
+ end
493
+ end
494
+
495
+ # Public: Creates a Sanitize instance using the Wiki's history sanitization
496
+ # options.
497
+ #
498
+ # Returns a Sanitize instance.
499
+ def history_sanitizer
500
+ if options = history_sanitization
501
+ @history_sanitizer ||= options.to_sanitize
502
+ end
503
+ end
504
+
505
+ #########################################################################
506
+ #
507
+ # Internal Methods
508
+ #
509
+ #########################################################################
510
+
511
+ # The Grit::Repo associated with the wiki.
512
+ #
513
+ # Returns the Grit::Repo.
514
+ attr_reader :repo
515
+
516
+ # The String path to the Git repository that holds the Gollum site.
517
+ #
518
+ # Returns the String path.
519
+ attr_reader :path
520
+
521
+ # Gets the page class used by all instances of this Wiki.
522
+ attr_reader :page_class
523
+
524
+ # Gets the file class used by all instances of this Wiki.
525
+ attr_reader :file_class
526
+
527
+ # Gets the markup class used by all instances of this Wiki.
528
+ attr_reader :markup_classes
529
+
530
+ # Normalize the data.
531
+ #
532
+ # data - The String data to be normalized.
533
+ #
534
+ # Returns the normalized data String.
535
+ def normalize(data)
536
+ data.gsub(/\r/, '')
537
+ end
538
+
539
+ # Assemble a Page's filename from its name and format.
540
+ #
541
+ # name - The String name of the page (should be pre-canonicalized).
542
+ # format - The Symbol format of the page.
543
+ #
544
+ # Returns the String filename.
545
+ def page_file_name(name, format)
546
+ name + '.' + @page_class.format_to_ext(format)
547
+ end
548
+
549
+ # Fill an array with a list of pages.
550
+ #
551
+ # ref - A String ref that is either a commit SHA or references one.
552
+ #
553
+ # Returns a flat Array of Gollum::Page instances.
554
+ def tree_list(ref)
555
+ if sha = @access.ref_to_sha(ref)
556
+ commit = @access.commit(sha)
557
+ tree_map_for(sha).inject([]) do |list, entry|
558
+ next list unless @page_class.valid_page_name?(entry.name)
559
+ list << entry.page(self, commit)
560
+ end
561
+ else
562
+ []
563
+ end
564
+ end
565
+
566
+ # Creates a reverse diff for the given SHAs on the given Gollum::Page.
567
+ #
568
+ # page - The Gollum::Page to scope the patch to, or a String Path.
569
+ # sha1 - String SHA1 of the earlier parent if two SHAs are given,
570
+ # or the child.
571
+ # sha2 - Optional String SHA1 of the child.
572
+ #
573
+ # Returns a String of the reverse Diff to apply.
574
+ def full_reverse_diff_for(page, sha1, sha2 = nil)
575
+ sha1, sha2 = "#{sha1}^", sha1 if sha2.nil?
576
+ args = [{:R => true}, sha1, sha2]
577
+ if page
578
+ args << '--' << (page.respond_to?(:path) ? page.path : page.to_s)
579
+ end
580
+ repo.git.native(:diff, *args)
581
+ end
582
+
583
+ # Creates a reverse diff for the given SHAs.
584
+ #
585
+ # sha1 - String SHA1 of the earlier parent if two SHAs are given,
586
+ # or the child.
587
+ # sha2 - Optional String SHA1 of the child.
588
+ #
589
+ # Returns a String of the reverse Diff to apply.
590
+ def full_reverse_diff(sha1, sha2 = nil)
591
+ full_reverse_diff_for(nil, sha1, sha2)
592
+ end
593
+
594
+ # Gets the default name for commits.
595
+ #
596
+ # Returns the String name.
597
+ def default_committer_name
598
+ @default_committer_name ||= \
599
+ @repo.config['user.name'] || self.class.default_committer_name
600
+ end
601
+
602
+ # Gets the default email for commits.
603
+ #
604
+ # Returns the String email address.
605
+ def default_committer_email
606
+ @default_committer_email ||= \
607
+ @repo.config['user.email'] || self.class.default_committer_email
608
+ end
609
+
610
+ # Gets the commit object for the given ref or sha.
611
+ #
612
+ # ref - A string ref or SHA pointing to a valid commit.
613
+ #
614
+ # Returns a Grit::Commit instance.
615
+ def commit_for(ref)
616
+ @access.commit(ref)
617
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
618
+ end
619
+
620
+ # Finds a full listing of files and their blob SHA for a given ref. Each
621
+ # listing is cached based on its actual commit SHA.
622
+ #
623
+ # ref - A String ref that is either a commit SHA or references one.
624
+ #
625
+ # Returns an Array of BlobEntry instances.
626
+ def tree_map_for(ref)
627
+ @access.tree(ref)
628
+ rescue Grit::GitRuby::Repository::NoSuchShaFound
629
+ []
630
+ end
631
+
632
+ def inspect
633
+ %(#<#{self.class.name}:#{object_id} #{@repo.path}>)
634
+ end
635
+ end
636
+ end