gitgo 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
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