gitgo 0.3.3
Sign up to get free protection for your applications and to get access to all the features.
- 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,315 @@
|
|
1
|
+
module Gitgo
|
2
|
+
class Git
|
3
|
+
|
4
|
+
# Tree represents an in-memory working tree for git. Trees are initialized
|
5
|
+
# with a Grit::Tree. In general tree contents are represented as (path,
|
6
|
+
# [:mode,sha]) pairs, but subtrees can be expanded into (path, Tree)
|
7
|
+
# pairs.
|
8
|
+
#
|
9
|
+
# See Git for an example of Tree usage in practice.
|
10
|
+
#
|
11
|
+
# === Efficiency
|
12
|
+
#
|
13
|
+
# Modes are symbolized in the internal [:mode,sha] entries because they
|
14
|
+
# are rarely needed as strings and are typically very redundant.
|
15
|
+
# Symbolizing shas makes less sense because they are frequently used as
|
16
|
+
# strings. However it does make sense to use the same string instance to
|
17
|
+
# represent a sha in multiple places. As a result trees have an internal
|
18
|
+
# string_table that functions like a symbol table, ie it maps the same
|
19
|
+
# string content to a single shared instance. The string table is managed
|
20
|
+
# at the class level through the string_table method.
|
21
|
+
#
|
22
|
+
# Trees only expand as needed. This saves memory and cycles because it is
|
23
|
+
# expensive to read, parse, and maintain the git tree data. In general
|
24
|
+
# trees will stay fairly compact unless certain expensive operations are
|
25
|
+
# performed. These are:
|
26
|
+
#
|
27
|
+
# * each_pair (with expand == true)
|
28
|
+
# * each_tree (with expand == true)
|
29
|
+
# * flatten
|
30
|
+
# * to_hash (with expand == true)
|
31
|
+
#
|
32
|
+
# Avoid these methods if possible, or ensure they are rarely executed.
|
33
|
+
class Tree
|
34
|
+
class << self
|
35
|
+
|
36
|
+
# Returns the string table for shas. Specify clear to reset the
|
37
|
+
# string table.
|
38
|
+
def string_table(clear=false)
|
39
|
+
@string_table.clear if clear
|
40
|
+
@string_table ||= Hash.new {|hash, key| hash[key] = key.freeze }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
include Enumerable
|
44
|
+
|
45
|
+
# The tree mode.
|
46
|
+
attr_reader :mode
|
47
|
+
|
48
|
+
# Initializes a new Tree. The input tree should be a Grit::Tree or nil.
|
49
|
+
def initialize(tree=nil)
|
50
|
+
@index = nil
|
51
|
+
@tree = tree
|
52
|
+
|
53
|
+
if tree
|
54
|
+
self.mode = tree.mode
|
55
|
+
self.sha = tree.id
|
56
|
+
else
|
57
|
+
@mode = nil
|
58
|
+
@sha = nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# Sets mode, symbolizing if necessary. Mode may be set to nil in which
|
63
|
+
# case the git.default_tree_mode is adopted when the tree is written.
|
64
|
+
def mode=(mode)
|
65
|
+
@mode = mode ? mode.to_sym : nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets the sha for self. Sha may be set to nil, in which case it will
|
69
|
+
# be calculated when a repo is committed.
|
70
|
+
def sha=(sha)
|
71
|
+
@sha = sha ? string(sha) : nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the sha representing the contents for self. If check is true,
|
75
|
+
# sha will check that neither self nor any subtree is modified before
|
76
|
+
# returning the sha. If modified, the sha is set to nil to flag a repo
|
77
|
+
# to recalculate the sha on commit.
|
78
|
+
#
|
79
|
+
# Note that check does not validate the sha correctly represents the
|
80
|
+
# contents of self.
|
81
|
+
def sha(check=true)
|
82
|
+
if @sha && check
|
83
|
+
index.each_value do |value|
|
84
|
+
if value.kind_of?(Tree) && value.sha.nil?
|
85
|
+
@sha = nil
|
86
|
+
break
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
@sha
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the keys (ie paths) for all entries in self. Keys are
|
95
|
+
# returned as strings
|
96
|
+
def keys
|
97
|
+
index.keys.collect {|keys| keys.to_s }
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the entry for the specified path, either a [:mode,sha] pair
|
101
|
+
# for a blob or a Tree for a subtree.
|
102
|
+
def [](path)
|
103
|
+
case
|
104
|
+
when entry = index[path]
|
105
|
+
entry
|
106
|
+
when tree = index.delete(path.to_sym)
|
107
|
+
index[string(path)] = Tree.new(tree)
|
108
|
+
else
|
109
|
+
nil
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Sets the entry for the specified path. The entry should be a
|
114
|
+
# [:mode,sha] array, or a Tree. A nil entry indicates removal.
|
115
|
+
def []=(path, entry)
|
116
|
+
# ensure an unexpanded tree is removed
|
117
|
+
index.delete(path.to_sym)
|
118
|
+
|
119
|
+
path = string(path)
|
120
|
+
case entry
|
121
|
+
when Array
|
122
|
+
mode, sha = entry
|
123
|
+
index[path] = [mode.to_sym, string(sha)]
|
124
|
+
when Tree
|
125
|
+
index[path] = entry
|
126
|
+
when nil
|
127
|
+
index.delete(path)
|
128
|
+
else
|
129
|
+
raise "invalid entry: #{entry.inspect}"
|
130
|
+
end
|
131
|
+
|
132
|
+
# add/remove content modifies self so
|
133
|
+
# the sha can and should be invalidated
|
134
|
+
@sha = nil
|
135
|
+
end
|
136
|
+
|
137
|
+
def merge!(another)
|
138
|
+
another.each_pair do |path, entry|
|
139
|
+
self[path] = entry
|
140
|
+
end
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
# Yields each (path, entry) pair to the block, as an array, ordered by
|
145
|
+
# path. Implemented to get access to the enumerable methods.
|
146
|
+
def each
|
147
|
+
each_pair do |key, value|
|
148
|
+
yield [key, value]
|
149
|
+
end
|
150
|
+
self
|
151
|
+
end
|
152
|
+
|
153
|
+
# Yields each (path, entry) pair to the block, ordered by path. Entries
|
154
|
+
# can be [:mode,sha] arrays or Trees. If expand is true then subtrees
|
155
|
+
# will be expanded, but strongly consider whether or not expansion is
|
156
|
+
# necessary because it is computationally expensive.
|
157
|
+
def each_pair(expand=false)
|
158
|
+
|
159
|
+
# sorting the keys is important when writing the tree;
|
160
|
+
# unsorted keys cause warnings in git fsck
|
161
|
+
keys = index.keys.sort_by {|key| key.to_s }
|
162
|
+
store = expand ? self : index
|
163
|
+
|
164
|
+
keys.each {|key| yield(key, store[key]) }
|
165
|
+
end
|
166
|
+
|
167
|
+
# Yields the (path, [:mode, sha]) pairs for each blob to the block.
|
168
|
+
def each_blob
|
169
|
+
each_pair do |key, value|
|
170
|
+
next unless value.kind_of?(Array)
|
171
|
+
yield(key, value)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Yields the (path, entry) pairs for each tree to the block. Subtrees
|
176
|
+
# are expanded if specified, in which case all entries will be Trees.
|
177
|
+
# Without expansion, entries may be [:mode,sha] arrays or Trees.
|
178
|
+
def each_tree(expand=false)
|
179
|
+
each_pair(expand) do |key, value|
|
180
|
+
next unless value.kind_of?(Tree)
|
181
|
+
yield(key, value)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Returns the subtree indicated by the specified segments (an array of
|
186
|
+
# paths), or nil if no such subtree exists. If force is true then
|
187
|
+
# missing subtrees will be created.
|
188
|
+
def subtree(segments, force=false)
|
189
|
+
return self if segments.empty?
|
190
|
+
|
191
|
+
key = segments.shift
|
192
|
+
tree = self[key]
|
193
|
+
|
194
|
+
if !tree.kind_of?(Tree)
|
195
|
+
return nil unless force
|
196
|
+
self[key] = tree = Tree.new(nil)
|
197
|
+
end
|
198
|
+
|
199
|
+
tree.subtree(segments, force)
|
200
|
+
end
|
201
|
+
|
202
|
+
# Flattens all paths under self into a single array.
|
203
|
+
def flatten(prefix=nil, target={})
|
204
|
+
keys.each do |key|
|
205
|
+
next unless entry = self[key]
|
206
|
+
|
207
|
+
key = key.to_s
|
208
|
+
key = File.join(prefix, key) if prefix
|
209
|
+
|
210
|
+
if entry.kind_of?(Tree)
|
211
|
+
entry.flatten(key, target)
|
212
|
+
else
|
213
|
+
target[key] = entry
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
target
|
218
|
+
end
|
219
|
+
|
220
|
+
# Returns self as a hash, expanding if specified.
|
221
|
+
def to_hash(expand=false)
|
222
|
+
hash = {}
|
223
|
+
each_pair(expand) do |key, value|
|
224
|
+
hash[key] = case value
|
225
|
+
when Tree then value.to_hash
|
226
|
+
when Array then value
|
227
|
+
else to_entry(value)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
hash
|
231
|
+
end
|
232
|
+
|
233
|
+
# Returns true if the to_hash results of self and another are equal.
|
234
|
+
def eql?(another)
|
235
|
+
self.to_hash == another.to_hash
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns true if the to_hash results of self and another are equal.
|
239
|
+
def ==(another)
|
240
|
+
self.to_hash == another.to_hash
|
241
|
+
end
|
242
|
+
|
243
|
+
# Writes self to the git instance. All subtrees will likewise be
|
244
|
+
# written. Returns a [mode, sha] entry.
|
245
|
+
#
|
246
|
+
# Tree format:
|
247
|
+
#
|
248
|
+
# mode name\0[packedsha]mode name\0[packedsha]...
|
249
|
+
#
|
250
|
+
# Note there are no newlines separating tree entries.
|
251
|
+
def write_to(git)
|
252
|
+
self.mode ||= git.default_tree_mode
|
253
|
+
self.sha ||= begin
|
254
|
+
lines = []
|
255
|
+
each_pair(false) do |key, entry|
|
256
|
+
mode, sha = case entry
|
257
|
+
when Tree then entry.write_to(git)
|
258
|
+
when Array then entry
|
259
|
+
else [entry.mode, entry.id]
|
260
|
+
end
|
261
|
+
|
262
|
+
# modes should not begin with zeros (although it is not fatal
|
263
|
+
# if they do), otherwise fsck will print warnings like this:
|
264
|
+
#
|
265
|
+
# warning in tree 980127...: contains zero-padded file modes
|
266
|
+
lines << zero_strip("#{mode} #{key}\0#{[sha].pack("H*")}")
|
267
|
+
end
|
268
|
+
|
269
|
+
git.set(:tree, lines.join)
|
270
|
+
end
|
271
|
+
|
272
|
+
[mode, sha]
|
273
|
+
end
|
274
|
+
|
275
|
+
protected
|
276
|
+
|
277
|
+
# returns or initializes the internal working tree (index)
|
278
|
+
def index # :nodoc:
|
279
|
+
@index ||= begin
|
280
|
+
index = {}
|
281
|
+
|
282
|
+
@tree.contents.each do |obj|
|
283
|
+
key = obj.name
|
284
|
+
if obj.respond_to?(:contents)
|
285
|
+
index[key.to_sym] = obj
|
286
|
+
else
|
287
|
+
index[string(key)] = to_entry(obj)
|
288
|
+
end
|
289
|
+
end if @tree
|
290
|
+
@tree = nil
|
291
|
+
|
292
|
+
index
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# helper to lookup the string table entry for key
|
297
|
+
def string(key) # :nodoc:
|
298
|
+
Tree.string_table[key.to_s]
|
299
|
+
end
|
300
|
+
|
301
|
+
# converts obj into a [:mode, sha] entry
|
302
|
+
def to_entry(obj) # :nodoc:
|
303
|
+
[obj.mode.to_sym, string(obj.id)]
|
304
|
+
end
|
305
|
+
|
306
|
+
def zero_strip(str) # :nodoc:
|
307
|
+
return str unless str[0] == ?0
|
308
|
+
|
309
|
+
index = 1
|
310
|
+
index += 1 while str[index] == ?0
|
311
|
+
str[index, str.length - index]
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Gitgo
|
2
|
+
class Git
|
3
|
+
|
4
|
+
# A set of utility functions split out for ease of testing.
|
5
|
+
module Utils
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# Executes the block having set the env variables. All ENV variables
|
9
|
+
# that start with GIT_ will be removed regardless of whether they are
|
10
|
+
# specified in env or not.
|
11
|
+
def with_env(env={})
|
12
|
+
overrides = {}
|
13
|
+
begin
|
14
|
+
ENV.keys.each do |key|
|
15
|
+
if key =~ /^GIT_/
|
16
|
+
overrides[key] = ENV.delete(key)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
env.each_pair do |key, value|
|
21
|
+
overrides[key] ||= nil
|
22
|
+
ENV[key] = value
|
23
|
+
end
|
24
|
+
|
25
|
+
yield
|
26
|
+
ensure
|
27
|
+
overrides.each_pair do |key, value|
|
28
|
+
if value
|
29
|
+
ENV[key] = value
|
30
|
+
else
|
31
|
+
ENV.delete(key)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Splits a path along slashes into an array, stripping empty strings
|
38
|
+
# from each end. An array may be provided in place of path; it will be
|
39
|
+
# duplicated before being stripped of nil/empty entries.
|
40
|
+
def split(path)
|
41
|
+
array = path.kind_of?(String) ? path.split("/") : path.dup
|
42
|
+
array.shift if nil_or_empty_string?(array[0])
|
43
|
+
array.pop if nil_or_empty_string?(array[-1])
|
44
|
+
array
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns true if the object is nil, or empty, and assumes the object is
|
48
|
+
# a string (ie that it responds to empty?).
|
49
|
+
def nil_or_empty_string?(obj)
|
50
|
+
obj.nil? || obj.empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns true if the object is nil, or responds to empty and is empty.
|
54
|
+
def nil_or_empty?(obj)
|
55
|
+
obj.nil? || (obj.respond_to?(:empty?) && obj.empty?)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/gitgo/helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Gitgo
|
2
|
+
module Helper
|
3
|
+
class Doc
|
4
|
+
|
5
|
+
attr_reader :controller
|
6
|
+
|
7
|
+
def initialize(controller)
|
8
|
+
@controller = controller
|
9
|
+
end
|
10
|
+
|
11
|
+
def url(*paths)
|
12
|
+
controller.url(*paths)
|
13
|
+
end
|
14
|
+
|
15
|
+
def at
|
16
|
+
controller.user_ref
|
17
|
+
end
|
18
|
+
|
19
|
+
def active_shas
|
20
|
+
@active_shas ||= repo.rev_list(at)
|
21
|
+
end
|
22
|
+
|
23
|
+
def active?(sha)
|
24
|
+
at && active_shas.include?(sha)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'rack/utils'
|
2
|
+
|
3
|
+
module Gitgo
|
4
|
+
module Helper
|
5
|
+
class Form
|
6
|
+
include Rack::Utils
|
7
|
+
|
8
|
+
DEFAULT_STATES = %w{open closed}
|
9
|
+
|
10
|
+
attr_reader :controller
|
11
|
+
|
12
|
+
def initialize(controller)
|
13
|
+
@controller = controller
|
14
|
+
end
|
15
|
+
|
16
|
+
def url(*paths)
|
17
|
+
controller.url(paths)
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
#
|
22
|
+
#
|
23
|
+
|
24
|
+
def value(str)
|
25
|
+
str
|
26
|
+
end
|
27
|
+
|
28
|
+
#
|
29
|
+
# documents
|
30
|
+
#
|
31
|
+
|
32
|
+
def at(sha)
|
33
|
+
return '(unknown)' unless sha
|
34
|
+
|
35
|
+
refs = refs.select {|ref| ref.commit.sha == sha }
|
36
|
+
refs.collect! {|ref| escape_html ref.name }
|
37
|
+
|
38
|
+
ref_names = refs.empty? ? nil : " (#{refs.join(', ')})"
|
39
|
+
"#{sha_a(sha)}#{ref_names}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def author_value(author)
|
43
|
+
escape_html(author)
|
44
|
+
end
|
45
|
+
|
46
|
+
def title_value(title)
|
47
|
+
escape_html(title)
|
48
|
+
end
|
49
|
+
|
50
|
+
def tags_value(tags)
|
51
|
+
tags ? tags.join(', ') : ''
|
52
|
+
end
|
53
|
+
|
54
|
+
def content_value(content)
|
55
|
+
content
|
56
|
+
end
|
57
|
+
|
58
|
+
def each_tag(tags, *selected) # :yields: value, select_or_check, content
|
59
|
+
tags.sort.each do |tag|
|
60
|
+
yield escape_html(tag), selected.include?(tag), escape_html(tag)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def each_ref(refs, selected_name) # :yields: value, select_or_check, content
|
65
|
+
refs.each do |ref|
|
66
|
+
yield escape_html(ref.commit), selected_name == ref.name, escape_html(ref.name)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def each_ref_name(refs, selected_name) # :yields: value, select_or_check, content
|
71
|
+
found_selected_name = false
|
72
|
+
|
73
|
+
refs.each do |ref|
|
74
|
+
select_or_check = selected_name == ref.name
|
75
|
+
found_selected_name = true if select_or_check
|
76
|
+
|
77
|
+
yield escape_html(ref.name), select_or_check, escape_html(ref.name)
|
78
|
+
end
|
79
|
+
|
80
|
+
if found_selected_name
|
81
|
+
yield("", false, "(none)")
|
82
|
+
else
|
83
|
+
yield(selected_name, true, selected_name.to_s.empty? ? "(none)" : selected_name)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|