gitgo 0.3.3

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 (76) hide show
  1. data/History +44 -0
  2. data/License.txt +22 -0
  3. data/README +45 -0
  4. data/bin/gitgo +4 -0
  5. data/lib/gitgo.rb +1 -0
  6. data/lib/gitgo/app.rb +63 -0
  7. data/lib/gitgo/controller.rb +89 -0
  8. data/lib/gitgo/controllers/code.rb +198 -0
  9. data/lib/gitgo/controllers/issue.rb +76 -0
  10. data/lib/gitgo/controllers/repo.rb +186 -0
  11. data/lib/gitgo/controllers/wiki.rb +19 -0
  12. data/lib/gitgo/document.rb +680 -0
  13. data/lib/gitgo/document/invalid_document_error.rb +34 -0
  14. data/lib/gitgo/documents/comment.rb +20 -0
  15. data/lib/gitgo/documents/issue.rb +56 -0
  16. data/lib/gitgo/git.rb +941 -0
  17. data/lib/gitgo/git/tree.rb +315 -0
  18. data/lib/gitgo/git/utils.rb +59 -0
  19. data/lib/gitgo/helper.rb +3 -0
  20. data/lib/gitgo/helper/doc.rb +28 -0
  21. data/lib/gitgo/helper/form.rb +88 -0
  22. data/lib/gitgo/helper/format.rb +200 -0
  23. data/lib/gitgo/helper/html.rb +19 -0
  24. data/lib/gitgo/helper/utils.rb +85 -0
  25. data/lib/gitgo/index.rb +421 -0
  26. data/lib/gitgo/index/idx_file.rb +119 -0
  27. data/lib/gitgo/index/sha_file.rb +135 -0
  28. data/lib/gitgo/patches/grit.rb +47 -0
  29. data/lib/gitgo/repo.rb +626 -0
  30. data/lib/gitgo/repo/graph.rb +333 -0
  31. data/lib/gitgo/repo/node.rb +122 -0
  32. data/lib/gitgo/rest.rb +87 -0
  33. data/lib/gitgo/server.rb +114 -0
  34. data/lib/gitgo/version.rb +8 -0
  35. data/public/css/gitgo.css +24 -0
  36. data/public/javascript/gitgo.js +148 -0
  37. data/public/javascript/jquery-1.4.2.min.js +154 -0
  38. data/views/app/index.erb +4 -0
  39. data/views/app/timeline.erb +27 -0
  40. data/views/app/welcome.erb +13 -0
  41. data/views/code/_comment.erb +10 -0
  42. data/views/code/_comment_form.erb +14 -0
  43. data/views/code/_comments.erb +5 -0
  44. data/views/code/_commit.erb +25 -0
  45. data/views/code/_grepnav.erb +5 -0
  46. data/views/code/_treenav.erb +3 -0
  47. data/views/code/blob.erb +6 -0
  48. data/views/code/commit_grep.erb +35 -0
  49. data/views/code/commits.erb +11 -0
  50. data/views/code/diff.erb +10 -0
  51. data/views/code/grep.erb +32 -0
  52. data/views/code/index.erb +17 -0
  53. data/views/code/obj/blob.erb +4 -0
  54. data/views/code/obj/commit.erb +25 -0
  55. data/views/code/obj/tag.erb +25 -0
  56. data/views/code/obj/tree.erb +9 -0
  57. data/views/code/tree.erb +9 -0
  58. data/views/error.erb +19 -0
  59. data/views/issue/_issue.erb +15 -0
  60. data/views/issue/_issue_form.erb +39 -0
  61. data/views/issue/edit.erb +11 -0
  62. data/views/issue/index.erb +28 -0
  63. data/views/issue/new.erb +5 -0
  64. data/views/issue/show.erb +27 -0
  65. data/views/layout.erb +34 -0
  66. data/views/not_found.erb +1 -0
  67. data/views/repo/fsck.erb +29 -0
  68. data/views/repo/help.textile +5 -0
  69. data/views/repo/help/faq.textile +19 -0
  70. data/views/repo/help/howto.textile +31 -0
  71. data/views/repo/help/trouble.textile +28 -0
  72. data/views/repo/idx.erb +29 -0
  73. data/views/repo/index.erb +72 -0
  74. data/views/repo/status.erb +16 -0
  75. data/views/wiki/index.erb +3 -0
  76. metadata +253 -0
@@ -0,0 +1,333 @@
1
+ require 'gitgo/repo/node'
2
+
3
+ module Gitgo
4
+ class Repo
5
+ # Graph performs the important and somewhat complicated task of
6
+ # deconvoluting document graphs. Graph and Node use signifant amounts of
7
+ # caching to make sure traversal of the document graph is as quick as
8
+ # possible. Graphs must be reset to detect any associations added after
9
+ # initialization.
10
+ #
11
+ # See Gitgo::Repo for terminology used in this documentation.
12
+ #
13
+ # == Deconvolution
14
+ #
15
+ # Deconvolution involves replacing each node in a DAG with the current
16
+ # versions of that node, and specifically reassigning parent and children
17
+ # links from updated nodes to their current versions.
18
+ #
19
+ # a a
20
+ # | |
21
+ # b -> b1 b1--+
22
+ # | | | |
23
+ # c | becomes c |
24
+ # | |
25
+ # d d
26
+ #
27
+ # When multiple current versions exist for a node, a new fork in the graph
28
+ # is introduced:
29
+ #
30
+ # a a
31
+ # | |--+
32
+ # b -> [b1, b2] b1 |
33
+ # | | |
34
+ # c becomes | b2
35
+ # | |
36
+ # c--+
37
+ #
38
+ # These forks can happen anywhere (including the graph head), as can
39
+ # updates that merge multiple revisions:
40
+ #
41
+ # a a
42
+ # | |
43
+ # b -> [b1, b2] -> b3 b3
44
+ # | |
45
+ # c becomes c
46
+ #
47
+ # Linkages for the convoluted graph is not directly available from Graph,
48
+ # although all nodes are (via nodes).
49
+ #
50
+ # == Notes
51
+ #
52
+ # The possibility of multiple current versions is perhaps non-intuitive,
53
+ # but entirely possible if user A modifies a document while separately
54
+ # user B modifies the same document in a different way. When these
55
+ # changes are merged, you end up with multiple current versions.
56
+ #
57
+ class Graph
58
+ include Enumerable
59
+
60
+ # A back-reference to the repo where the documents are stored
61
+ attr_reader :repo
62
+
63
+ # The graph head
64
+ attr_reader :head
65
+
66
+ # A hash of (sha, node) pairs identifying all accessible nodes
67
+ attr_reader :nodes
68
+
69
+ # Creates a new Graph
70
+ def initialize(repo, head)
71
+ @repo = repo
72
+ @head = head
73
+ reset
74
+ end
75
+
76
+ # Returns true if head is nil (implying that no meaningful nodes can be
77
+ # reached from this graph).
78
+ def empty?
79
+ head.nil?
80
+ end
81
+
82
+ # Same as node.
83
+ def [](sha)
84
+ nodes[sha]
85
+ end
86
+
87
+ # Retrieves the node for the sha, or nil if the node is inaccessible
88
+ # from this document graph.
89
+ def node(sha)
90
+ nodes[sha]
91
+ end
92
+
93
+ # Returns a hash of (node, children) pairs mapping linkages in the
94
+ # deconvoluted graph. Links does not contain updated or deleted nodes
95
+ # and typically serves as the basis for drawing graphs.
96
+ def links
97
+ @links ||= begin
98
+ links = {}
99
+ unless head.nil?
100
+ versions = nodes[nodes[head].original].versions
101
+ versions.each {|sha| collect_links(sha, links) }
102
+
103
+ links[nil] = versions
104
+ end
105
+ links
106
+ end
107
+ end
108
+
109
+ # Returns an array the tail nodes in the deconvoluted graph.
110
+ def tails
111
+ @tails ||= begin
112
+ tails = []
113
+ links.each_pair do |node, children|
114
+ tails << node if children.empty?
115
+ end
116
+ tails
117
+ end
118
+ end
119
+
120
+ # Sorts each of the children in links, using the block if given.
121
+ def sort(&block)
122
+ links.each_value do |children|
123
+ children.sort!(&block)
124
+ end
125
+ self
126
+ end
127
+
128
+ # Yields each node in the deconvoluted graph to the block with
129
+ # coordinates for rendering linkages between the nodes. The nodes are
130
+ # ordered from head to tails and respect the order of children.
131
+ #
132
+ # Each node is assigned a slot (x), and at each iteration (y), there is
133
+ # information regarding which slots are currently open, and which slots
134
+ # need to be linked to produce the graph. For example, a simple
135
+ # fork-merge could be graphed like this:
136
+ #
137
+ # Graph node x y current transistions
138
+ # * :a 0 0 [] [0,1]
139
+ # |--+
140
+ # * | :b 0 1 [1] [0]
141
+ # | |
142
+ # | * :c 1 2 [0] [0]
143
+ # |--+
144
+ # * :d 0 3 [] []
145
+ #
146
+ # Where the coordinates are the arguments yielded to the block:
147
+ #
148
+ # sha:: the sha for the node
149
+ # slot:: the slot where the node belongs (x-axis)
150
+ # index:: a counter for the number of nodes yielded (y-axis)
151
+ # current_slots:: slots currently open (|)
152
+ # transitions:: the slots that this node should connect to (|,--+)
153
+ #
154
+ def each(head=nil) # :yields: sha, slot, index, current_slots, transitions
155
+ slots = []
156
+ slot = {head => 0}
157
+
158
+ # visit walks each branch in the DAG and collects the visited nodes
159
+ # in reverse; that way uniq + reverse_each will iterate the nodes in
160
+ # order, with merges pushed down as far as necessary
161
+ order = visit(links, head)
162
+ order.uniq!
163
+
164
+ index = 0
165
+ order.reverse_each do |sha|
166
+ children = links[sha]
167
+ parent_slot = slot[sha]
168
+
169
+ # free the parent slot if possible - if no children exist then the
170
+ # sha is an open tail; keep these slots occupied
171
+ slots[parent_slot] = nil unless children.empty?
172
+
173
+ # determine currently occupied slots - any slots with a non-nil,
174
+ # non-false value; in this case a number
175
+ current_slots = slots.select {|s| s }
176
+
177
+ transitions = children.collect do |child|
178
+ # determine the next open (ie nil) slot for the child and occupy
179
+ child_slot = slot[child] ||= (slots.index(nil) || slots.length)
180
+ slots[child_slot] = child_slot
181
+ child_slot
182
+ end
183
+
184
+ yield(sha, slot[sha], index, current_slots, transitions)
185
+ index += 1
186
+ end
187
+
188
+ self
189
+ end
190
+
191
+ # Draws the graph
192
+ def draw(indent=0)
193
+ lines = []
194
+ each do |(sha, slot, index, current_slots, transitions)|
195
+ next if sha.nil?
196
+
197
+ line = []
198
+ transition = []
199
+
200
+ transitions.each do |target|
201
+ tstart = (slot * 2) + indent
202
+ tend = (target * 2) + indent
203
+
204
+ if tstart > tend
205
+ tstart, tend = tend, tstart
206
+ end
207
+
208
+ tstart.upto(tend) {|i| transition[i] = '-'}
209
+ transition[tstart] = '+'
210
+ transition[tend] = '+'
211
+ end
212
+
213
+ if transitions.include?(slot)
214
+ transition[(slot * 2) + indent] = '|'
215
+ end
216
+
217
+ current_slots.each do |cs|
218
+ line[(cs * 2) + indent] = '|'
219
+ transition[(cs * 2) + indent] = '|'
220
+ end
221
+
222
+ line[(slot * 2) + indent] = '*'
223
+
224
+ lines << line.collect! {|obj| obj.nil? ? ' ' : obj }.join
225
+ lines << transition.collect! {|obj| obj.nil? ? ' ' : obj }.join
226
+ end
227
+
228
+ lines.join("\n")
229
+ end
230
+
231
+ # Resets the graph, recollecting all nodes and links. Reset is required
232
+ # detect new nodes inserted after initialization.
233
+ #
234
+ #--
235
+ #
236
+ # Nodes are collected in an ambigous order by collect_nodes. As a
237
+ # result is not feasible to determine the relationships of previous and
238
+ # updated nodes in one pass. First collects all nodes, then deconvolute
239
+ # original nodes. Updates do not have to be deconvoluted directly
240
+ # because they will be deconvoluted from their original (indeed it will
241
+ # cause duplicates in the graph tree if updates are deconvoluted
242
+ # separately).
243
+ #
244
+ def reset
245
+ @nodes = {}
246
+ collect_nodes(head)
247
+
248
+ nodes.each_value do |node|
249
+ if node.original?
250
+ node.deconvolute
251
+ end
252
+ end
253
+
254
+ @links = nil
255
+ self
256
+ end
257
+
258
+ # Returns a string like:
259
+ #
260
+ # #<Gitgo::Repo::Graph:object_id head="sha">
261
+ #
262
+ def inspect
263
+ "#<#{self.class}:#{object_id} head=#{head.inspect}>"
264
+ end
265
+
266
+ protected
267
+
268
+ # helper method to recursively collect all nodes in the graph, with the
269
+ # raw linkage information. after nodes are collected they must be
270
+ # deconvoluted.
271
+ def collect_nodes(sha) # :nodoc:
272
+ node = nodes[sha]
273
+ return node if node
274
+
275
+ links = []
276
+ updates = []
277
+
278
+ node = Node.new(sha, nodes, links, updates)
279
+ nodes[sha] = node
280
+
281
+ repo.each_assoc(sha) do |doc_sha, doc_type|
282
+ target = collect_nodes(doc_sha)
283
+
284
+ case doc_type
285
+ when :create
286
+ when :link
287
+ links << target
288
+ when :update
289
+ updates << target
290
+ target.original = nil
291
+ when :delete
292
+ target.deleted = true
293
+ else
294
+ raise "invalid linkage: #{sha} -> #{linkage}"
295
+ end
296
+ end
297
+
298
+ node
299
+ end
300
+
301
+ # helper method to recursively collect node linkages. collect_links
302
+ # walks each current trail in the nodes and is designed to fail if
303
+ # circular linkages are detected.
304
+ def collect_links(sha, links, trail=[]) # :nodoc:
305
+ circular = trail.include?(sha)
306
+ trail.push sha
307
+
308
+ if circular
309
+ raise "circular link detected:\n #{trail.join("\n ")}\n"
310
+ end
311
+
312
+ links[sha] ||= begin
313
+ nodes[sha].children.each do |child|
314
+ collect_links(child, links, trail)
315
+ end
316
+ end
317
+
318
+ trail.pop
319
+ end
320
+
321
+ # helper method to walk the DAG and collect each visited node in
322
+ # reverse -- afterwards the unique, reversed array represents the
323
+ # graphing order for the nodes.
324
+ def visit(links, parent, visited=[]) # :nodoc:
325
+ visited.unshift(parent)
326
+ links[parent].each do |child|
327
+ visit(links, child, visited)
328
+ end
329
+ visited
330
+ end
331
+ end
332
+ end
333
+ end
@@ -0,0 +1,122 @@
1
+ module Gitgo
2
+ class Repo
3
+
4
+ # Nodes are used to cache and provide access to positional information
5
+ # (parents, children, etc) for each node in a graph. Nodes are meant to
6
+ # be read-only objects and should not be modified.
7
+ class Node
8
+
9
+ # The node sha
10
+ attr_reader :sha
11
+
12
+ # A back reference to the graph nodes this node belongs to.
13
+ attr_reader :nodes
14
+
15
+ # True if self is deleted
16
+ attr_accessor :deleted
17
+
18
+ # Set to the sha for the original node this node updates, or the sha for
19
+ # self if self is an original node.
20
+ attr_accessor :original
21
+
22
+ # Initializes a new Node. The links and updates arrays represent raw
23
+ # linkage information before deconvolution and are not made available
24
+ # because they are changed in the course of deconvolution.
25
+ def initialize(sha, nodes, links, updates)
26
+ @sha = sha
27
+ @nodes = nodes
28
+ @links = links
29
+ @updates = updates
30
+ @deleted = false
31
+ @original = sha
32
+ end
33
+
34
+ # True if self is an original node.
35
+ def original?
36
+ original == sha
37
+ end
38
+
39
+ # True if self is a current version of a node (ie not deleted or
40
+ # updated).
41
+ def current?
42
+ !deleted && @updates.empty?
43
+ end
44
+
45
+ # True if self is a tail (ie current and without children)
46
+ def tail?
47
+ current? && @links.empty?
48
+ end
49
+
50
+ # Returns an array of deconvoluted parent for self.
51
+ def parents
52
+ @parents ||= begin
53
+ parents = []
54
+
55
+ nodes.each_value do |node|
56
+ if node.current? && node.children.include?(sha)
57
+ parents << node.sha
58
+ end
59
+ end if current?
60
+
61
+ parents
62
+ end
63
+ end
64
+
65
+ # Returns an array of deconvoluted children for self.
66
+ def children
67
+ @children ||= begin
68
+ children = []
69
+
70
+ @links.each do |link|
71
+ children.concat nodes[link.original].versions
72
+ end if current?
73
+
74
+ children
75
+ end
76
+ end
77
+
78
+ # Returns an array of current versions for self.
79
+ def versions
80
+ @versions ||= deconvolute(nil)
81
+ end
82
+
83
+ # Deconvolute is a utility method used by a graph to:
84
+ #
85
+ # * aggregate links from previous versions to updates
86
+ # * determine the current versions for an original node
87
+ # * determine the original node for each update
88
+ #
89
+ # This method is public so that it may be used from a Graph, but should
90
+ # not be called otherwise.
91
+ def deconvolute(original=sha, links=nil, versions=[])
92
+ if original
93
+ @original = original
94
+ @links.concat(links) if links
95
+ @versions = versions if original == sha
96
+ end
97
+
98
+ case
99
+ when deleted
100
+ # do not register deleted notes as current
101
+ # so that they will fall out of the tree
102
+ when @updates.empty?
103
+ versions << sha
104
+ else
105
+ @updates.each do |update|
106
+ update.deconvolute(original, @links, versions)
107
+ end
108
+ end
109
+
110
+ versions
111
+ end
112
+
113
+ # Returns a string like:
114
+ #
115
+ # #<Gitgo::Repo::Node:object_id sha="sha">
116
+ #
117
+ def inspect
118
+ "#<#{self.class}:#{object_id} sha=#{sha.inspect}>"
119
+ end
120
+ end
121
+ end
122
+ end