gifts 0.0.1

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 (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