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.
- data/History +44 -0
- data/License.txt +22 -0
- data/README +45 -0
- data/bin/gitgo +4 -0
- data/lib/gitgo.rb +1 -0
- data/lib/gitgo/app.rb +63 -0
- data/lib/gitgo/controller.rb +89 -0
- data/lib/gitgo/controllers/code.rb +198 -0
- data/lib/gitgo/controllers/issue.rb +76 -0
- data/lib/gitgo/controllers/repo.rb +186 -0
- data/lib/gitgo/controllers/wiki.rb +19 -0
- data/lib/gitgo/document.rb +680 -0
- data/lib/gitgo/document/invalid_document_error.rb +34 -0
- data/lib/gitgo/documents/comment.rb +20 -0
- data/lib/gitgo/documents/issue.rb +56 -0
- data/lib/gitgo/git.rb +941 -0
- data/lib/gitgo/git/tree.rb +315 -0
- data/lib/gitgo/git/utils.rb +59 -0
- data/lib/gitgo/helper.rb +3 -0
- data/lib/gitgo/helper/doc.rb +28 -0
- data/lib/gitgo/helper/form.rb +88 -0
- data/lib/gitgo/helper/format.rb +200 -0
- data/lib/gitgo/helper/html.rb +19 -0
- data/lib/gitgo/helper/utils.rb +85 -0
- data/lib/gitgo/index.rb +421 -0
- data/lib/gitgo/index/idx_file.rb +119 -0
- data/lib/gitgo/index/sha_file.rb +135 -0
- data/lib/gitgo/patches/grit.rb +47 -0
- data/lib/gitgo/repo.rb +626 -0
- data/lib/gitgo/repo/graph.rb +333 -0
- data/lib/gitgo/repo/node.rb +122 -0
- data/lib/gitgo/rest.rb +87 -0
- data/lib/gitgo/server.rb +114 -0
- data/lib/gitgo/version.rb +8 -0
- data/public/css/gitgo.css +24 -0
- data/public/javascript/gitgo.js +148 -0
- data/public/javascript/jquery-1.4.2.min.js +154 -0
- data/views/app/index.erb +4 -0
- data/views/app/timeline.erb +27 -0
- data/views/app/welcome.erb +13 -0
- data/views/code/_comment.erb +10 -0
- data/views/code/_comment_form.erb +14 -0
- data/views/code/_comments.erb +5 -0
- data/views/code/_commit.erb +25 -0
- data/views/code/_grepnav.erb +5 -0
- data/views/code/_treenav.erb +3 -0
- data/views/code/blob.erb +6 -0
- data/views/code/commit_grep.erb +35 -0
- data/views/code/commits.erb +11 -0
- data/views/code/diff.erb +10 -0
- data/views/code/grep.erb +32 -0
- data/views/code/index.erb +17 -0
- data/views/code/obj/blob.erb +4 -0
- data/views/code/obj/commit.erb +25 -0
- data/views/code/obj/tag.erb +25 -0
- data/views/code/obj/tree.erb +9 -0
- data/views/code/tree.erb +9 -0
- data/views/error.erb +19 -0
- data/views/issue/_issue.erb +15 -0
- data/views/issue/_issue_form.erb +39 -0
- data/views/issue/edit.erb +11 -0
- data/views/issue/index.erb +28 -0
- data/views/issue/new.erb +5 -0
- data/views/issue/show.erb +27 -0
- data/views/layout.erb +34 -0
- data/views/not_found.erb +1 -0
- data/views/repo/fsck.erb +29 -0
- data/views/repo/help.textile +5 -0
- data/views/repo/help/faq.textile +19 -0
- data/views/repo/help/howto.textile +31 -0
- data/views/repo/help/trouble.textile +28 -0
- data/views/repo/idx.erb +29 -0
- data/views/repo/index.erb +72 -0
- data/views/repo/status.erb +16 -0
- data/views/wiki/index.erb +3 -0
- 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
|