gifts 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. data/.gitignore +17 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +9 -0
  6. data/gifts.gemspec +32 -0
  7. data/lib/gifts.rb +22 -0
  8. data/lib/gifts/commit_table.rb +70 -0
  9. data/lib/gifts/database.rb +49 -0
  10. data/lib/gifts/diff_table.rb +55 -0
  11. data/lib/gifts/file_table.rb +51 -0
  12. data/lib/gifts/repo_table.rb +31 -0
  13. data/lib/gifts/table_base.rb +18 -0
  14. data/lib/gifts/term_table.rb +28 -0
  15. data/lib/gifts/user_table.rb +19 -0
  16. data/lib/gifts/version.rb +3 -0
  17. data/spec/.gitkeeper +0 -0
  18. data/vendor/lib/LICENSE-grit +22 -0
  19. data/vendor/lib/LICENSE-grit_ext +22 -0
  20. data/vendor/lib/gifts/grit.rb +73 -0
  21. data/vendor/lib/gifts/grit/actor.rb +52 -0
  22. data/vendor/lib/gifts/grit/blame.rb +70 -0
  23. data/vendor/lib/gifts/grit/blob.rb +126 -0
  24. data/vendor/lib/gifts/grit/commit.rb +324 -0
  25. data/vendor/lib/gifts/grit/commit_stats.rb +128 -0
  26. data/vendor/lib/gifts/grit/config.rb +44 -0
  27. data/vendor/lib/gifts/grit/diff.rb +97 -0
  28. data/vendor/lib/gifts/grit/errors.rb +10 -0
  29. data/vendor/lib/gifts/grit/git-ruby.rb +262 -0
  30. data/vendor/lib/gifts/grit/git-ruby/commit_db.rb +52 -0
  31. data/vendor/lib/gifts/grit/git-ruby/git_object.rb +353 -0
  32. data/vendor/lib/gifts/grit/git-ruby/internal/file_window.rb +58 -0
  33. data/vendor/lib/gifts/grit/git-ruby/internal/loose.rb +137 -0
  34. data/vendor/lib/gifts/grit/git-ruby/internal/pack.rb +398 -0
  35. data/vendor/lib/gifts/grit/git-ruby/internal/raw_object.rb +44 -0
  36. data/vendor/lib/gifts/grit/git-ruby/repository.rb +784 -0
  37. data/vendor/lib/gifts/grit/git.rb +501 -0
  38. data/vendor/lib/gifts/grit/index.rb +222 -0
  39. data/vendor/lib/gifts/grit/lazy.rb +35 -0
  40. data/vendor/lib/gifts/grit/merge.rb +45 -0
  41. data/vendor/lib/gifts/grit/ref.rb +98 -0
  42. data/vendor/lib/gifts/grit/repo.rb +722 -0
  43. data/vendor/lib/gifts/grit/ruby1.9.rb +15 -0
  44. data/vendor/lib/gifts/grit/status.rb +153 -0
  45. data/vendor/lib/gifts/grit/submodule.rb +88 -0
  46. data/vendor/lib/gifts/grit/tag.rb +97 -0
  47. data/vendor/lib/gifts/grit/tree.rb +125 -0
  48. data/vendor/lib/gifts/grit_ext.rb +41 -0
  49. data/vendor/lib/gifts/grit_ext/actor.rb +15 -0
  50. data/vendor/lib/gifts/grit_ext/blob.rb +26 -0
  51. data/vendor/lib/gifts/grit_ext/commit.rb +15 -0
  52. data/vendor/lib/gifts/grit_ext/diff.rb +23 -0
  53. data/vendor/lib/gifts/grit_ext/tag.rb +10 -0
  54. data/vendor/lib/gifts/grit_ext/tree.rb +10 -0
  55. data/vendor/lib/gifts/grit_ext/version.rb +7 -0
  56. metadata +256 -0
@@ -0,0 +1,44 @@
1
+ #
2
+ # converted from the gitrb project
3
+ #
4
+ # authors:
5
+ # Matthias Lederhofer <matled@gmx.net>
6
+ # Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
7
+ #
8
+ # provides native ruby access to git objects and pack files
9
+ #
10
+
11
+ require 'digest/sha1'
12
+
13
+ module Gifts::Grit
14
+ module GitRuby
15
+ module Internal
16
+ OBJ_NONE = 0
17
+ OBJ_COMMIT = 1
18
+ OBJ_TREE = 2
19
+ OBJ_BLOB = 3
20
+ OBJ_TAG = 4
21
+
22
+ OBJ_TYPES = [nil, :commit, :tree, :blob, :tag].freeze
23
+
24
+ class RawObject
25
+ attr_accessor :type, :content
26
+ def initialize(type, content)
27
+ @type = type
28
+ @content = content
29
+ end
30
+
31
+ def sha1
32
+ Digest::SHA1.digest("%s %d\0" % [@type, @content.length] + @content)
33
+ end
34
+
35
+ def to_hash
36
+ {
37
+ :type => @type,
38
+ :content => @content
39
+ }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,784 @@
1
+ #
2
+ # converted from the gitrb project
3
+ #
4
+ # authors:
5
+ # Matthias Lederhofer <matled@gmx.net>
6
+ # Simon 'corecode' Schubert <corecode@fs.ei.tum.de>
7
+ # Scott Chacon <schacon@gmail.com>
8
+ #
9
+ # provides native ruby access to git objects and pack files
10
+ #
11
+ require 'gifts/grit/git-ruby/internal/raw_object'
12
+ require 'gifts/grit/git-ruby/internal/pack'
13
+ require 'gifts/grit/git-ruby/internal/loose'
14
+ require 'gifts/grit/git-ruby/git_object'
15
+
16
+ require 'rubygems'
17
+ require 'diff/lcs'
18
+ require 'diff/lcs/hunk'
19
+
20
+ # have to do this so it doesn't interfere with Gifts::Grit::Diff
21
+ module Difference
22
+ include Diff
23
+ end
24
+
25
+ module Gifts::Grit
26
+ module GitRuby
27
+ class Repository
28
+
29
+ class NoSuchShaFound < StandardError
30
+ end
31
+
32
+ class NoSuchPath < StandardError
33
+ end
34
+
35
+ attr_accessor :git_dir, :options
36
+
37
+ def initialize(git_dir, options = {})
38
+ @git_dir = git_dir
39
+ @options = options
40
+ @packs = []
41
+ end
42
+
43
+ # returns the loose objects object lazily
44
+ def loose
45
+ @loose ||= initloose
46
+ end
47
+
48
+ # returns the array of pack list objects
49
+ def packs
50
+ @packs ||= initpacks
51
+ end
52
+
53
+
54
+ # prints out the type, shas and content of all of the pack files
55
+ def show
56
+ packs.each do |p|
57
+ puts p.name
58
+ puts
59
+ p.each_sha1 do |s|
60
+ puts "**#{p[s].type}**"
61
+ if p[s].type.to_s == 'commit'
62
+ puts s.unpack('H*')
63
+ puts p[s].content
64
+ end
65
+ end
66
+ puts
67
+ end
68
+ end
69
+
70
+
71
+ # returns a raw object given a SHA1
72
+ def get_raw_object_by_sha1(sha1o)
73
+ raise NoSuchShaFound if sha1o.nil? || sha1o.empty? || !sha1o.is_a?(String)
74
+
75
+ sha1 = [sha1o.chomp].pack("H*")
76
+ # try packs
77
+ packs.each do |pack|
78
+ o = pack[sha1]
79
+ return pack[sha1] if o
80
+ end
81
+
82
+ # try loose storage
83
+ loose.each do |lsobj|
84
+ o = lsobj[sha1]
85
+ return o if o
86
+ end
87
+
88
+ # try packs again, maybe the object got packed in the meantime
89
+ initpacks
90
+ packs.each do |pack|
91
+ o = pack[sha1]
92
+ return o if o
93
+ end
94
+
95
+ # puts "*#{sha1o}*"
96
+ raise NoSuchShaFound
97
+ end
98
+
99
+ def cached(key, object, do_cache = true)
100
+ object
101
+ end
102
+
103
+ # returns GitRuby object of any type given a SHA1
104
+ def get_object_by_sha1(sha1)
105
+ r = get_raw_object_by_sha1(sha1)
106
+ return nil if !r
107
+ GitObject.from_raw(r)
108
+ end
109
+
110
+ # writes a raw object into the git repo
111
+ def put_raw_object(content, type)
112
+ loose.first.put_raw_object(content, type)
113
+ end
114
+
115
+ # returns true or false if that sha exists in the db
116
+ def object_exists?(sha1)
117
+ sha_hex = [sha1].pack("H*")
118
+ return true if in_packs?(sha_hex)
119
+ return true if in_loose?(sha_hex)
120
+ initpacks
121
+ return true if in_packs?(sha_hex) #maybe the object got packed in the meantime
122
+ false
123
+ end
124
+
125
+ # returns true if the hex-packed sha is in the packfiles
126
+ def in_packs?(sha_hex)
127
+ # try packs
128
+ packs.each do |pack|
129
+ return true if pack[sha_hex]
130
+ end
131
+ false
132
+ end
133
+
134
+ # returns true if the hex-packed sha is in the loose objects
135
+ def in_loose?(sha_hex)
136
+ loose.each do |lsobj|
137
+ return true if lsobj[sha_hex]
138
+ end
139
+ false
140
+ end
141
+
142
+
143
+ # returns the file type (as a symbol) of this sha
144
+ def cat_file_type(sha)
145
+ get_raw_object_by_sha1(sha).type
146
+ end
147
+
148
+ # returns the file size (as an int) of this sha
149
+ def cat_file_size(sha)
150
+ get_raw_object_by_sha1(sha).content.size
151
+ end
152
+
153
+ # returns the raw file contents of this sha
154
+ def cat_file(sha)
155
+ get_object_by_sha1(sha).raw_content
156
+ end
157
+
158
+ # returns a 2-d hash of the tree
159
+ # ['blob']['FILENAME'] = {:mode => '100644', :sha => SHA}
160
+ # ['tree']['DIRNAME'] = {:mode => '040000', :sha => SHA}
161
+ def list_tree(sha)
162
+ data = {'blob' => {}, 'tree' => {}, 'link' => {}, 'commit' => {}}
163
+ get_object_by_sha1(sha).entry.each do |e|
164
+ data[e.format_type][e.name] = {:mode => e.format_mode, :sha => e.sha1}
165
+ end
166
+ data
167
+ end
168
+
169
+ # returns the raw (cat-file) output for a tree
170
+ # if given a commit sha, it will print the tree of that commit
171
+ # if given a path limiter array, it will limit the output to those
172
+ # if asked for recrusive trees, will traverse trees
173
+ def ls_tree(sha, paths = [], recursive = false)
174
+ if paths.size > 0
175
+ # pathing
176
+ part = []
177
+ paths.each do |path|
178
+ part += ls_tree_path(sha, path)
179
+ end
180
+ return part.join("\n")
181
+ else
182
+ get_raw_tree(sha, recursive)
183
+ end
184
+ end
185
+
186
+ def get_raw_tree(sha, recursive = false)
187
+ o = get_raw_object_by_sha1(sha)
188
+ if o.type == :commit
189
+ tree = get_object_by_sha1(sha).tree
190
+ elsif o.type == :tag
191
+ commit_sha = get_object_by_sha1(sha).object
192
+ tree = get_object_by_sha1(commit_sha).tree
193
+ elsif o.type == :tree
194
+ tree = sha
195
+ else
196
+ return nil
197
+ end
198
+
199
+ recursive ? get_raw_trees(tree) : cat_file(tree)
200
+ end
201
+
202
+ # Grabs tree contents recursively,
203
+ # e.g. `git ls-tree -r sha`
204
+ def get_raw_trees(sha, path = '')
205
+ out = ''
206
+ cat_file(sha).split("\n").each do |line|
207
+ mode, type, sha, name = line.split(/\s/)
208
+
209
+ if type == 'tree'
210
+ full_name = path.empty? ? name : "#{path}/#{name}"
211
+ out << get_raw_trees(sha, full_name)
212
+ elsif path.empty?
213
+ out << line + "\n"
214
+ else
215
+ out << line.gsub(name, "#{path}/#{name}") + "\n"
216
+ end
217
+ end
218
+
219
+ out
220
+ end
221
+
222
+ # return array of tree entries
223
+ ## TODO : refactor this to remove the fugly
224
+ def ls_tree_path(sha, path, append = nil)
225
+ tree = get_raw_tree(sha)
226
+ if path =~ /\//
227
+ paths = path.split('/')
228
+ last = path[path.size - 1, 1]
229
+ if (last == '/') && (paths.size == 1)
230
+ append = append ? File.join(append, paths.first) : paths.first
231
+ dir_name = tree.split("\n").select { |p| p.split("\t")[1] == paths.first }.first
232
+ raise NoSuchPath if !dir_name
233
+ next_sha = dir_name.split(' ')[2]
234
+ tree = get_raw_tree(next_sha)
235
+ tree = tree.split("\n")
236
+ if append
237
+ mod_tree = []
238
+ tree.each do |ent|
239
+ (info, fpath) = ent.split("\t")
240
+ mod_tree << [info, File.join(append, fpath)].join("\t")
241
+ end
242
+ mod_tree
243
+ else
244
+ tree
245
+ end
246
+ else
247
+ raise NoSuchPath if tree.nil?
248
+ next_path = paths.shift
249
+ dir_name = tree.split("\n").select { |p| p.split("\t")[1] == next_path }.first
250
+ raise NoSuchPath if !dir_name
251
+ next_sha = dir_name.split(' ')[2]
252
+ next_path = append ? File.join(append, next_path) : next_path
253
+ if (last == '/')
254
+ ls_tree_path(next_sha, paths.join("/") + '/', next_path)
255
+ else
256
+ ls_tree_path(next_sha, paths.join("/"), next_path)
257
+ end
258
+ end
259
+ else
260
+ raise NoSuchPath if tree.nil?
261
+ tree = tree.split("\n")
262
+ tree = tree.select { |p| p.split("\t")[1] == path }
263
+ if append
264
+ mod_tree = []
265
+ tree.each do |ent|
266
+ (info, fpath) = ent.split("\t")
267
+ mod_tree << [info, File.join(append, fpath)].join("\t")
268
+ end
269
+ mod_tree
270
+ else
271
+ tree
272
+ end
273
+ end
274
+ end
275
+
276
+ # returns an array of GitRuby Commit objects
277
+ # [ [sha, raw_output], [sha, raw_output], [sha, raw_output] ... ]
278
+ #
279
+ # takes the following options:
280
+ # :since - Time object specifying that you don't want commits BEFORE this
281
+ # :until - Time object specifying that you don't want commit AFTER this
282
+ # :first_parent - tells log to only walk first parent
283
+ # :path_limiter - string or array of strings to limit path
284
+ # :max_count - number to limit the output
285
+ def log(sha, options = {})
286
+ @already_searched = {}
287
+ walk_log(sha, options)
288
+ end
289
+
290
+ def truncate_arr(arr, sha)
291
+ new_arr = []
292
+ arr.each do |a|
293
+ if a[0] == sha
294
+ return new_arr
295
+ end
296
+ new_arr << a
297
+ end
298
+ return new_arr
299
+ end
300
+
301
+ def rev_list(sha, options)
302
+ if sha.is_a? Array
303
+ (end_sha, sha) = sha
304
+ end
305
+
306
+ log = log(sha, options)
307
+ log = log.sort { |a, b| a[2] <=> b[2] }.reverse
308
+
309
+ if end_sha
310
+ log = truncate_arr(log, end_sha)
311
+ end
312
+
313
+ # shorten the list if it's longer than max_count (had to get everything in branches)
314
+ if options[:max_count]
315
+ if (opt_len = options[:max_count].to_i) < log.size
316
+ log = log[0, opt_len]
317
+ end
318
+ end
319
+
320
+ if options[:pretty] == 'raw'
321
+ log.map {|k, v| v }.join('')
322
+ else
323
+ log.map {|k, v| k }.join("\n")
324
+ end
325
+ end
326
+
327
+ # called by log() to recursively walk the tree
328
+ def walk_log(sha, opts, total_size = 0)
329
+ return [] if @already_searched[sha] # to prevent rechecking branches
330
+ @already_searched[sha] = true
331
+
332
+ array = []
333
+ if (sha)
334
+ o = get_raw_object_by_sha1(sha)
335
+ if o.type == :tag
336
+ commit_sha = get_object_by_sha1(sha).object
337
+ c = get_object_by_sha1(commit_sha)
338
+ else
339
+ c = GitObject.from_raw(o)
340
+ end
341
+
342
+ return [] if c.type != :commit
343
+
344
+ add_sha = true
345
+
346
+ if opts[:since] && opts[:since].is_a?(Time) && (opts[:since] > c.committer.date)
347
+ add_sha = false
348
+ end
349
+ if opts[:until] && opts[:until].is_a?(Time) && (opts[:until] < c.committer.date)
350
+ add_sha = false
351
+ end
352
+
353
+ # follow all parents unless '--first-parent' is specified #
354
+ subarray = []
355
+
356
+ if !c.parent.first && opts[:path_limiter] # check for the last commit
357
+ add_sha = false
358
+ end
359
+
360
+ if (!opts[:max_count] || ((array.size + total_size) < opts[:max_count]))
361
+
362
+ if !opts[:path_limiter]
363
+ output = c.raw_log(sha)
364
+ array << [sha, output, c.committer.date]
365
+ end
366
+
367
+ if (opts[:max_count] && (array.size + total_size) >= opts[:max_count])
368
+ return array
369
+ end
370
+
371
+ c.parent.each do |psha|
372
+ if psha && !files_changed?(c.tree, get_object_by_sha1(psha).tree,
373
+ opts[:path_limiter])
374
+ add_sha = false
375
+ end
376
+ subarray += walk_log(psha, opts, (array.size + total_size))
377
+ next if opts[:first_parent]
378
+ end
379
+
380
+ if opts[:path_limiter] && add_sha
381
+ output = c.raw_log(sha)
382
+ array << [sha, output, c.committer.date]
383
+ end
384
+
385
+ if add_sha
386
+ array += subarray
387
+ end
388
+ end
389
+
390
+ end
391
+
392
+ array
393
+ end
394
+
395
+ def diff(commit1, commit2, options = {})
396
+ patch = ''
397
+
398
+ commit_obj1 = get_object_by_sha1(commit1)
399
+ tree1 = commit_obj1.tree
400
+ if commit2
401
+ tree2 = get_object_by_sha1(commit2).tree
402
+ else
403
+ tree2 = get_object_by_sha1(commit_obj1.parent.first).tree
404
+ end
405
+
406
+ qdiff = quick_diff(tree1, tree2)
407
+
408
+ qdiff.sort.each do |diff_arr|
409
+ path, status, treeSHA1, treeSHA2 = *diff_arr
410
+ format, lines, output = :unified, 3, ''
411
+ file_length_difference = 0
412
+
413
+ fileA = treeSHA1 ? cat_file(treeSHA1) : ''
414
+ fileB = treeSHA2 ? cat_file(treeSHA2) : ''
415
+
416
+ sha1 = treeSHA1 || '0000000000000000000000000000000000000000'
417
+ sha2 = treeSHA2 || '0000000000000000000000000000000000000000'
418
+
419
+ data_old = fileA.split(/\n/).map! { |e| e.chomp }
420
+ data_new = fileB.split(/\n/).map! { |e| e.chomp }
421
+
422
+ diffs = Difference::LCS.diff(data_old, data_new)
423
+ next if diffs.empty?
424
+
425
+ a_path = "a/#{path.gsub('./', '')}"
426
+ b_path = "b/#{path.gsub('./', '')}"
427
+
428
+ header = "diff --git #{a_path} #{b_path}"
429
+ if options[:full_index]
430
+ header << "\n" + 'index ' + sha1 + '..' + sha2
431
+ header << ' 100644' if treeSHA2 # hard coding this because i don't think we use it
432
+ else
433
+ header << "\n" + 'index ' + sha1[0,7] + '..' + sha2[0,7]
434
+ header << ' 100644' if treeSHA2 # hard coding this because i don't think we use it
435
+ end
436
+ header << "\n--- " + (treeSHA1 ? a_path : '/dev/null')
437
+ header << "\n+++ " + (treeSHA2 ? b_path : '/dev/null')
438
+ header += "\n"
439
+
440
+ oldhunk = hunk = nil
441
+
442
+ diffs.each do |piece|
443
+ begin
444
+ hunk = Difference::LCS::Hunk.new(data_old, data_new, piece, lines, file_length_difference)
445
+ file_length_difference = hunk.file_length_difference
446
+
447
+ next unless oldhunk
448
+
449
+ if lines > 0 && hunk.overlaps?(oldhunk)
450
+ hunk.unshift(oldhunk)
451
+ else
452
+ output << oldhunk.diff(format)
453
+ end
454
+ ensure
455
+ oldhunk = hunk
456
+ output << "\n"
457
+ end
458
+ end
459
+
460
+ output << oldhunk.diff(format)
461
+ output << "\n"
462
+
463
+ patch << header + output.lstrip
464
+ end
465
+ patch
466
+ rescue
467
+ '' # one of the trees was bad or lcs isn't there - no diff
468
+ end
469
+
470
+ def quick_what_changed(t1, t2, path, type)
471
+ changed = []
472
+
473
+ t1[type].each do |file, hsh|
474
+ t2_file = t2[type][file] rescue nil
475
+ full = File.join(path, file)
476
+ if !t2_file
477
+ changed << [full, 'added', hsh[:sha], nil] # not in parent
478
+ elsif (hsh[:sha] != t2_file[:sha])
479
+ changed << [full, 'modified', hsh[:sha], t2_file[:sha]] # file changed
480
+ end
481
+ end if t1
482
+
483
+ t2[type].each do |file, hsh|
484
+ if !t1 || !t1[type][file]
485
+ changed << [File.join(path, file), 'removed', nil, hsh[:sha]]
486
+ end
487
+ end if t2
488
+
489
+ changed
490
+ end
491
+
492
+ # takes 2 tree shas and recursively walks them to find out what
493
+ # files or directories have been modified in them and returns an
494
+ # array of changes
495
+ # [ [full_path, 'added', tree1_hash, nil],
496
+ # [full_path, 'removed', nil, tree2_hash],
497
+ # [full_path, 'modified', tree1_hash, tree2_hash]
498
+ # ]
499
+ def quick_diff(tree1, tree2, path = '.', recurse = true)
500
+ # handle empty trees
501
+ return changed if tree1 == tree2
502
+
503
+ t1 = list_tree(tree1) if tree1
504
+ t2 = list_tree(tree2) if tree2
505
+
506
+ # finding files that are different
507
+ changed = quick_what_changed(t1, t2, path, 'blob') +
508
+ quick_what_changed(t1, t2, path, 'link')
509
+
510
+ t1['tree'].each do |dir, hsh|
511
+ t2_tree = t2['tree'][dir] rescue nil
512
+ full = File.join(path, dir)
513
+ if !t2_tree
514
+ if recurse
515
+ changed += quick_diff(hsh[:sha], nil, full, true)
516
+ else
517
+ changed << [full, 'added', hsh[:sha], nil] # not in parent
518
+ end
519
+ elsif (hsh[:sha] != t2_tree[:sha])
520
+ if recurse
521
+ changed += quick_diff(hsh[:sha], t2_tree[:sha], full, true)
522
+ else
523
+ changed << [full, 'modified', hsh[:sha], t2_tree[:sha]] # file changed
524
+ end
525
+ end
526
+ end if t1
527
+ t2['tree'].each do |dir, hsh|
528
+ t1_tree = t1['tree'][dir] rescue nil
529
+ full = File.join(path, dir)
530
+ if !t1_tree
531
+ if recurse
532
+ changed += quick_diff(nil, hsh[:sha], full, true)
533
+ else
534
+ changed << [full, 'removed', nil, hsh[:sha]]
535
+ end
536
+ end
537
+ end if t2
538
+
539
+ changed
540
+ end
541
+
542
+ # returns true if the files in path_limiter were changed, or no path limiter
543
+ # used by the log() function when passed with a path_limiter
544
+ def files_changed?(tree_sha1, tree_sha2, path_limiter = nil)
545
+ if path_limiter
546
+ mod = quick_diff(tree_sha1, tree_sha2)
547
+ files = mod.map { |c| c.first }
548
+ path_limiter.to_a.each do |filepath|
549
+ if files.include?(filepath)
550
+ return true
551
+ end
552
+ end
553
+ return false
554
+ end
555
+ true
556
+ end
557
+
558
+ def get_subtree(commit_sha, path)
559
+ tree_sha = get_object_by_sha1(commit_sha).tree
560
+
561
+ if path && !(path == '' || path == '.' || path == './')
562
+ paths = path.split('/')
563
+ paths.each do |pathname|
564
+ tree = get_object_by_sha1(tree_sha)
565
+ if entry = tree.entry.select { |e| e.name == pathname }.first
566
+ tree_sha = entry.sha1 rescue nil
567
+ else
568
+ return false
569
+ end
570
+ end
571
+ end
572
+
573
+ tree_sha
574
+ end
575
+
576
+ def blame_tree(commit_sha, path)
577
+ # find subtree
578
+ tree_sha = get_subtree(commit_sha, path)
579
+ return {} if !tree_sha
580
+
581
+ looking_for = []
582
+ get_object_by_sha1(tree_sha).entry.each do |e|
583
+ looking_for << File.join('.', e.name)
584
+ end
585
+
586
+ @already_searched = {}
587
+ commits = look_for_commits(commit_sha, path, looking_for)
588
+
589
+ # cleaning up array
590
+ arr = {}
591
+ commits.each do |commit_array|
592
+ key = commit_array[0].gsub('./', '')
593
+ arr[key] = commit_array[1]
594
+ end
595
+ arr
596
+ end
597
+
598
+ def look_for_commits(commit_sha, path, looking_for, options = {})
599
+ return [] if @already_searched[commit_sha] # to prevent rechecking branches
600
+
601
+ @already_searched[commit_sha] = true
602
+
603
+ commit = get_object_by_sha1(commit_sha)
604
+ tree_sha = get_subtree(commit_sha, path)
605
+
606
+ found_data = []
607
+
608
+ # at the beginning of the branch
609
+ if commit.parent.size == 0
610
+ looking_for.each do |search|
611
+ # prevents the rare case of multiple branch starting points with
612
+ # files that have never changed
613
+ if found_data.assoc(search)
614
+ found_data << [search, commit_sha]
615
+ end
616
+ end
617
+ return found_data
618
+ end
619
+
620
+ # go through the parents recursively, looking for somewhere this has been changed
621
+ commit.parent.each do |pc|
622
+ diff = quick_diff(tree_sha, get_subtree(pc, path), '.', false)
623
+
624
+ # remove anything found
625
+ looking_for.each do |search|
626
+ if match = diff.assoc(search)
627
+ found_data << [search, commit_sha, match]
628
+ looking_for.delete(search)
629
+ end
630
+ end
631
+
632
+ if looking_for.size <= 0 # we're done
633
+ return found_data
634
+ end
635
+
636
+ found_data += look_for_commits(pc, path, looking_for) # recurse into parent
637
+ return found_data if options[:first_parent]
638
+ end
639
+
640
+ ## TODO : find most recent commit with change in any parent
641
+ found_data
642
+ end
643
+
644
+ # initialize a git repository
645
+ def self.init(dir, bare = true)
646
+
647
+ FileUtils.mkdir_p(dir) if !File.exists?(dir)
648
+
649
+ FileUtils.cd(dir) do
650
+ if(File.exists?('objects'))
651
+ return false # already initialized
652
+ else
653
+ # initialize directory
654
+ create_initial_config(bare)
655
+ FileUtils.mkdir_p('refs/heads')
656
+ FileUtils.mkdir_p('refs/tags')
657
+ FileUtils.mkdir_p('objects/info')
658
+ FileUtils.mkdir_p('objects/pack')
659
+ FileUtils.mkdir_p('branches')
660
+ add_file('description', 'Unnamed repository; edit this file to name it for gitweb.')
661
+ add_file('HEAD', "ref: refs/heads/master\n")
662
+ FileUtils.mkdir_p('hooks')
663
+ FileUtils.cd('hooks') do
664
+ add_file('applypatch-msg', '# add shell script and make executable to enable')
665
+ add_file('post-commit', '# add shell script and make executable to enable')
666
+ add_file('post-receive', '# add shell script and make executable to enable')
667
+ add_file('post-update', '# add shell script and make executable to enable')
668
+ add_file('pre-applypatch', '# add shell script and make executable to enable')
669
+ add_file('pre-commit', '# add shell script and make executable to enable')
670
+ add_file('pre-rebase', '# add shell script and make executable to enable')
671
+ add_file('update', '# add shell script and make executable to enable')
672
+ end
673
+ FileUtils.mkdir_p('info')
674
+ add_file('info/exclude', "# *.[oa]\n# *~")
675
+ end
676
+ end
677
+ end
678
+
679
+ def self.create_initial_config(bare = false)
680
+ bare ? bare_status = 'true' : bare_status = 'false'
681
+ config = "[core]\n\trepositoryformatversion = 0\n\tfilemode = true\n\tbare = #{bare_status}\n\tlogallrefupdates = true"
682
+ add_file('config', config)
683
+ end
684
+
685
+ def self.add_file(name, contents)
686
+ File.open(name, 'w') do |f|
687
+ f.write contents
688
+ end
689
+ end
690
+
691
+ def close
692
+ @packs.each do |pack|
693
+ pack.close
694
+ end if @packs
695
+ end
696
+
697
+ protected
698
+
699
+ def git_path(path)
700
+ return "#@git_dir/#{path}"
701
+ end
702
+
703
+ private
704
+
705
+ def initloose
706
+ @loaded = []
707
+ @loose = []
708
+ load_loose(git_path('objects'))
709
+ load_alternate_loose(git_path('objects'))
710
+ @loose
711
+ end
712
+
713
+ def each_alternate_path(path)
714
+ alt = File.join(path, 'info/alternates')
715
+ return if !File.exists?(alt)
716
+
717
+ File.readlines(alt).each do |line|
718
+ path = line.chomp
719
+ if path[0, 2] == '..'
720
+ yield File.expand_path(File.join(@git_dir, 'objects', path))
721
+
722
+ # XXX this is here for backward compatibility with grit < 2.3.0
723
+ # relative alternate objects paths are expanded relative to the
724
+ # objects directory, not the git repository directory.
725
+ yield File.expand_path(File.join(@git_dir, path))
726
+ else
727
+ yield path
728
+ end
729
+ end
730
+ end
731
+
732
+ def load_alternate_loose(pathname)
733
+ # load alternate loose, too
734
+ each_alternate_path pathname do |path|
735
+ next if @loaded.include?(path)
736
+ next if !File.exist?(path)
737
+ load_loose(path)
738
+ load_alternate_loose(path)
739
+ end
740
+ end
741
+
742
+ def load_loose(path)
743
+ @loaded << path
744
+ return if !File.exists?(path)
745
+ @loose << Gifts::Grit::GitRuby::Internal::LooseStorage.new(path)
746
+ end
747
+
748
+ def initpacks
749
+ close
750
+ @loaded_packs = []
751
+ @packs = []
752
+ load_packs(git_path("objects/pack"))
753
+ load_alternate_packs(git_path('objects'))
754
+ @packs
755
+ end
756
+
757
+ def load_alternate_packs(pathname)
758
+ each_alternate_path pathname do |path|
759
+ full_pack = File.join(path, 'pack')
760
+ next if @loaded_packs.include?(full_pack)
761
+ load_packs(full_pack)
762
+ load_alternate_packs(path)
763
+ end
764
+ end
765
+
766
+ def load_packs(path)
767
+ @loaded_packs << path
768
+ return if !File.exists?(path)
769
+ Dir.open(path) do |dir|
770
+ dir.each do |entry|
771
+ next if !(entry =~ /\.pack$/i)
772
+ pack = Gifts::Grit::GitRuby::Internal::PackStorage.new(File.join(path,entry))
773
+ if @options[:map_packfile]
774
+ pack.cache_objects
775
+ end
776
+ @packs << pack
777
+ end
778
+ end
779
+ end
780
+
781
+ end
782
+
783
+ end
784
+ end