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,128 @@
1
+ module Gifts::Grit
2
+
3
+ class CommitStats
4
+
5
+ attr_reader :id, :files, :additions, :deletions, :total
6
+
7
+ # Instantiate a new CommitStats
8
+ # +id+ is the id of the commit
9
+ # +files+ is an array of :
10
+ # [ [filename, adds, deletes, total],
11
+ # [filename, adds, deletes, total],
12
+ # [filename, adds, deletes, total] ]
13
+ #
14
+ # Returns Gifts::Grit::CommitStats (baked)
15
+ def initialize(repo, id, files)
16
+ @repo = repo
17
+ @id = id
18
+ @files = files
19
+ @additions = files.inject(0) { |total, a| total += a[1] }
20
+ @deletions = files.inject(0) { |total, a| total += a[2] }
21
+ @total = files.inject(0) { |total, a| total += a[3] }
22
+ end
23
+
24
+ # Find all commit stats matching the given criteria.
25
+ # +repo+ is the Repo
26
+ # +ref+ is the ref from which to begin (SHA1 or name) or nil for --all
27
+ # +options+ is a Hash of optional arguments to git
28
+ # :max_count is the maximum number of commits to fetch
29
+ # :skip is the number of commits to skip
30
+ #
31
+ # Returns assoc array [sha, Gifts::Grit::Commit[] (baked)]
32
+ def self.find_all(repo, ref, options = {})
33
+ allowed_options = [:max_count, :skip, :since]
34
+
35
+ default_options = {:numstat => true}
36
+ actual_options = default_options.merge(options)
37
+
38
+ if ref
39
+ output = repo.git.log(actual_options, ref)
40
+ else
41
+ output = repo.git.log(actual_options.merge(:all => true))
42
+ end
43
+
44
+ self.list_from_string(repo, output)
45
+ end
46
+
47
+ # Parse out commit information into an array of baked Commit objects
48
+ # +repo+ is the Repo
49
+ # +text+ is the text output from the git command (raw format)
50
+ #
51
+ # Returns assoc array [sha, Gifts::Grit::Commit[] (baked)]
52
+ def self.list_from_string(repo, text)
53
+ lines = text.split("\n")
54
+
55
+ commits = []
56
+
57
+ while !lines.empty?
58
+ id = lines.shift.split.last
59
+
60
+ lines.shift
61
+ lines.shift
62
+ lines.shift
63
+
64
+ message_lines = []
65
+ message_lines << lines.shift[4..-1] while lines.first =~ /^ {4}/ || lines.first == ''
66
+
67
+ lines.shift while lines.first && lines.first.empty?
68
+
69
+ files = []
70
+ while lines.first =~ /^([-\d]+)\s+([-\d]+)\s+(.+)/
71
+ (additions, deletions, filename) = lines.shift.split
72
+ additions = additions.to_i
73
+ deletions = deletions.to_i
74
+ total = additions + deletions
75
+ files << [filename, additions, deletions, total]
76
+ end
77
+
78
+ lines.shift while lines.first && lines.first.empty?
79
+
80
+ commits << [id, CommitStats.new(repo, id, files)]
81
+ end
82
+
83
+ commits
84
+ end
85
+
86
+ # Pretty object inspection
87
+ def inspect
88
+ %Q{#<Gifts::Grit::CommitStats "#{@id}">}
89
+ end
90
+
91
+ # Convert to an easy-to-traverse structure
92
+ def to_diffstat
93
+ files.map do |metadata|
94
+ DiffStat.new(*metadata)
95
+ end
96
+ end
97
+
98
+ # private
99
+
100
+ def to_hash
101
+ {
102
+ 'id' => id,
103
+ 'files' => files,
104
+ 'additions' => additions,
105
+ 'deletions' => deletions,
106
+ 'total' => total
107
+ }
108
+ end
109
+
110
+ end # CommitStats
111
+
112
+ class DiffStat
113
+ attr_reader :filename, :additions, :deletions
114
+
115
+ def initialize(filename, additions, deletions, total=nil)
116
+ @filename, @additions, @deletions = filename, additions, deletions
117
+ end
118
+
119
+ def net
120
+ additions - deletions
121
+ end
122
+
123
+ def inspect
124
+ "#{filename}: +#{additions} -#{deletions}"
125
+ end
126
+ end
127
+
128
+ end # Gifts::Grit
@@ -0,0 +1,44 @@
1
+ module Gifts::Grit
2
+
3
+ class Config
4
+ def initialize(repo)
5
+ @repo = repo
6
+ end
7
+
8
+ def []=(key, value)
9
+ @repo.git.config({}, key, value)
10
+ @data = nil
11
+ end
12
+
13
+ def [](key)
14
+ data[key]
15
+ end
16
+
17
+ def fetch(key, default = nil)
18
+ data[key] || default || raise(IndexError.new("key not found"))
19
+ end
20
+
21
+ def keys
22
+ data.keys
23
+ end
24
+
25
+ protected
26
+ def data
27
+ @data ||= load_config
28
+ end
29
+
30
+ def load_config
31
+ hash = {}
32
+ config_lines.map do |line|
33
+ key, value = line.split(/=/, 2)
34
+ hash[key] = value
35
+ end
36
+ hash
37
+ end
38
+
39
+ def config_lines
40
+ @repo.git.config(:list => true).split(/\n/)
41
+ end
42
+ end # Config
43
+
44
+ end # Gifts::Grit
@@ -0,0 +1,97 @@
1
+ module Gifts::Grit
2
+
3
+ class Diff
4
+ attr_reader :a_path, :b_path
5
+ attr_reader :a_blob, :b_blob
6
+ attr_reader :a_mode, :b_mode
7
+ attr_reader :new_file, :deleted_file, :renamed_file
8
+ attr_reader :similarity_index
9
+ attr_reader :binary_file
10
+ attr_accessor :diff
11
+
12
+ def initialize(repo, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff, renamed_file = false, similarity_index = 0, binary_file = false)
13
+ @repo = repo
14
+ @a_path = a_path
15
+ @b_path = b_path
16
+ @a_blob = a_blob =~ /^0{40}$/ ? nil : Blob.create(repo, :id => a_blob)
17
+ @b_blob = b_blob =~ /^0{40}$/ ? nil : Blob.create(repo, :id => b_blob)
18
+ @a_mode = a_mode
19
+ @b_mode = b_mode
20
+ @new_file = new_file || @a_blob.nil?
21
+ @deleted_file = deleted_file || @b_blob.nil?
22
+ @renamed_file = renamed_file
23
+ @similarity_index = similarity_index.to_i
24
+ @diff = diff
25
+ @binary_file = binary_file
26
+ end
27
+
28
+ def self.list_from_string(repo, text, a)
29
+ lines = text.split("\n")
30
+
31
+ diffs = []
32
+
33
+ while !lines.empty?
34
+ m, a_path, b_path = *lines.shift.match(%r{^diff --git "?a\/(.+?)(?<!\\)"? "?b\/(.+?)(?<!\\)"?$})
35
+
36
+ if lines.first =~ /^old mode/
37
+ m, a_mode = *lines.shift.match(/^old mode (\d+)/)
38
+ m, b_mode = *lines.shift.match(/^new mode (\d+)/)
39
+ end
40
+
41
+ if lines.empty? || lines.first =~ /^diff --git/
42
+ diffs << Diff.new(repo, a_path, b_path, nil, nil, a_mode, b_mode, false, false, nil)
43
+ next
44
+ end
45
+
46
+ sim_index = 0
47
+ new_file = false
48
+ deleted_file = false
49
+ renamed_file = false
50
+ binary_file = false
51
+
52
+ if lines.first =~ /^new file/
53
+ m, b_mode = lines.shift.match(/^new file mode (.+)$/)
54
+ a_mode = nil
55
+ new_file = true
56
+ elsif lines.first =~ /^deleted file/
57
+ m, a_mode = lines.shift.match(/^deleted file mode (.+)$/)
58
+ b_mode = nil
59
+ deleted_file = true
60
+ elsif lines.first =~ /^similarity index (\d+)\%/
61
+ sim_index = $1.to_i
62
+ renamed_file = true
63
+ 3.times { lines.shift } # shift away the similarity line and the 2 `rename from/to ...` lines
64
+ end
65
+
66
+ if sim_index == 100
67
+ ls_tree = repo.git.native(:ls_tree, {}, a, a_path)
68
+ m = ls_tree.match(/^(\d+) blob ([0-9A-Fa-f]+)/)
69
+ a_mode = b_mode = m[0]
70
+ a_blob = b_blob = m[1]
71
+ diff = nil
72
+ else
73
+ m, a_blob, b_blob, b_mode = *lines.shift.match(%r{^index ([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+) ?(.+)?$})
74
+ b_mode.strip! if b_mode
75
+
76
+ diff_lines = []
77
+ while lines.first && lines.first !~ /^diff/
78
+ line = lines.shift
79
+ diff_lines << line if line !~ /^(-{3}|\+{3})/
80
+ end
81
+
82
+ if diff =~ /\ABinary/
83
+ binary_file = true
84
+ diff = nil
85
+ else
86
+ diff = diff_lines.join("\n")
87
+ end
88
+ end
89
+
90
+ diffs << Diff.new(repo, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff, renamed_file, sim_index, binary_file)
91
+ end
92
+
93
+ diffs
94
+ end
95
+ end # Diff
96
+
97
+ end # Gifts::Grit
@@ -0,0 +1,10 @@
1
+ module Gifts::Grit
2
+ class InvalidGitRepositoryError < StandardError
3
+ end
4
+
5
+ class NoSuchPathError < StandardError
6
+ end
7
+
8
+ class InvalidObjectType < StandardError
9
+ end
10
+ end
@@ -0,0 +1,262 @@
1
+ require 'gifts/grit/git-ruby/repository'
2
+
3
+ module Gifts::Grit
4
+
5
+ # the functions in this module intercept the calls to git binary
6
+ # made by the grit objects and attempts to run them in pure ruby
7
+ # if it will be faster, or if the git binary is not available (!!TODO!!)
8
+ module GitRuby
9
+
10
+ attr_accessor :ruby_git_repo, :git_file_index
11
+
12
+ def init(options, *args)
13
+ if options.size == 0
14
+ Gifts::Grit::GitRuby::Repository.init(@git_dir)
15
+ else
16
+ method_missing('init', options, *args)
17
+ end
18
+ end
19
+
20
+ def cat_file(options, sha)
21
+ if options[:t]
22
+ file_type(sha)
23
+ elsif options[:s]
24
+ file_size(sha)
25
+ elsif options[:p]
26
+ try_run { ruby_git.cat_file(sha) }
27
+ end
28
+ rescue Gifts::Grit::GitRuby::Repository::NoSuchShaFound
29
+ ''
30
+ end
31
+
32
+ def cat_ref(options, ref)
33
+ sha = rev_parse({}, ref)
34
+ cat_file(options, sha)
35
+ end
36
+
37
+ # lib/grit/tree.rb:16: output = repo.git.ls_tree({}, treeish, *paths)
38
+ def ls_tree(options, treeish, *paths)
39
+ sha = rev_parse({}, treeish)
40
+ ruby_git.ls_tree(sha, paths.flatten, options.delete(:r))
41
+ rescue Gifts::Grit::GitRuby::Repository::NoSuchShaFound
42
+ ''
43
+ end
44
+
45
+ # git diff --full-index 'ec037431382e83c3e95d4f2b3d145afbac8ea55d' 'f1ec1aea10986159456846b8a05615b87828d6c6'
46
+ def diff(options, sha1, sha2 = nil)
47
+ try_run { ruby_git.diff(sha1, sha2, options) }
48
+ end
49
+
50
+ def rev_list(options, *refs)
51
+ refs = ['master'] if refs.empty?
52
+ options.delete(:skip) if options[:skip].to_i == 0
53
+ allowed_options = [:max_count, :since, :until, :pretty] # this is all I can do right now
54
+ if ((options.keys - allowed_options).size > 0) || refs.size > 1
55
+ method_missing('rev-list', options, *refs)
56
+ elsif (options.size == 0)
57
+ # pure rev-list
58
+ ref = refs.first
59
+ begin
60
+ file_index.commits_from(rev_parse({}, ref)).join("\n") + "\n"
61
+ rescue
62
+ method_missing('rev-list', options, *refs)
63
+ end
64
+ else
65
+ ref = refs.first
66
+ aref = rev_parse({:verify => true}, ref)
67
+ if aref.is_a? Array
68
+ method_missing('rev-list', options, *refs)
69
+ else
70
+ try_run { ruby_git.rev_list(aref, options) }
71
+ end
72
+ end
73
+ end
74
+
75
+ def rev_parse(options, string)
76
+ raise RuntimeError, "invalid string: #{string.inspect}" unless string.is_a?(String)
77
+
78
+ # Split ranges, but don't split when specifying a ref:path.
79
+ # Don't split HEAD:some/path/in/repo..txt
80
+ # Do split sha1..sha2
81
+ if string !~ /:/ && string =~ /\.\./
82
+ (sha1, sha2) = string.split('..')
83
+ return [rev_parse({}, sha1), rev_parse({}, sha2)]
84
+ end
85
+
86
+ if /^[0-9a-f]{40}$/.match(string) # passing in a sha - just no-op it
87
+ return string.chomp
88
+ end
89
+
90
+ head = File.join(@git_dir, 'refs', 'heads', string)
91
+ return File.read(head).chomp if File.file?(head)
92
+
93
+ head = File.join(@git_dir, 'refs', 'remotes', string)
94
+ return File.read(head).chomp if File.file?(head)
95
+
96
+ head = File.join(@git_dir, 'refs', 'tags', string)
97
+ return File.read(head).chomp if File.file?(head)
98
+
99
+ ## check packed-refs file, too
100
+ packref = File.join(@git_dir, 'packed-refs')
101
+ if File.file?(packref)
102
+ File.readlines(packref).each do |line|
103
+ if m = /^(\w{40}) refs\/.+?\/(.*?)$/.match(line)
104
+ next if !Regexp.new(Regexp.escape(string) + '$').match(m[3])
105
+ return m[1].chomp
106
+ end
107
+ end
108
+ end
109
+
110
+ ## !! more - partials and such !!
111
+
112
+ # revert to calling git - grr
113
+ return method_missing('rev-parse', options, string).chomp
114
+ end
115
+
116
+ def refs(options, prefix)
117
+ refs = []
118
+ already = {}
119
+ Dir.chdir(@git_dir) do
120
+ files = Dir.glob(prefix + '/**/*')
121
+ files.each do |ref|
122
+ next if !File.file?(ref)
123
+ id = File.read(ref).chomp
124
+ name = ref.sub("#{prefix}/", '')
125
+ if !already[name]
126
+ refs << "#{name} #{id}"
127
+ already[name] = true
128
+ end
129
+ end
130
+
131
+ if File.file?('packed-refs')
132
+ File.readlines('packed-refs').each do |line|
133
+ if m = /^(\w{40}) (.*?)$/.match(line)
134
+ next if !Regexp.new('^' + prefix).match(m[2])
135
+ name = m[2].sub("#{prefix}/", '')
136
+ if !already[name]
137
+ refs << "#{name} #{m[1]}"
138
+ already[name] = true
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ refs.join("\n")
146
+ end
147
+
148
+ def tags(options, prefix)
149
+ refs = []
150
+ already = {}
151
+
152
+ Dir.chdir(repo.path) do
153
+ files = Dir.glob(prefix + '/**/*')
154
+
155
+ files.each do |ref|
156
+ next if !File.file?(ref)
157
+
158
+ id = File.read(ref).chomp
159
+ name = ref.sub("#{prefix}/", '')
160
+
161
+ if !already[name]
162
+ refs << "#{name} #{id}"
163
+ already[name] = true
164
+ end
165
+ end
166
+
167
+ if File.file?('packed-refs')
168
+ lines = File.readlines('packed-refs')
169
+ lines.each_with_index do |line, i|
170
+ if m = /^(\w{40}) (.*?)$/.match(line)
171
+ next if !Regexp.new('^' + prefix).match(m[2])
172
+ name = m[2].sub("#{prefix}/", '')
173
+
174
+ # Annotated tags in packed-refs include a reference
175
+ # to the commit object on the following line.
176
+ next_line = lines[i + 1]
177
+
178
+ id =
179
+ if next_line && next_line[0] == ?^
180
+ next_line[1..-1].chomp
181
+ else
182
+ m[1]
183
+ end
184
+
185
+ if !already[name]
186
+ refs << "#{name} #{id}"
187
+ already[name] = true
188
+ end
189
+ end
190
+ end
191
+ end
192
+ end
193
+
194
+ refs.join("\n")
195
+ end
196
+
197
+ def file_size(ref)
198
+ try_run { ruby_git.cat_file_size(ref).to_s }
199
+ end
200
+
201
+ def file_type(ref)
202
+ try_run { ruby_git.cat_file_type(ref).to_s }
203
+ end
204
+
205
+ def ruby_git
206
+ @ruby_git_repo ||= Repository.new(@git_dir)
207
+ end
208
+
209
+ private
210
+
211
+ def try_run
212
+ ret = ''
213
+ Timeout.timeout(self.class.git_timeout) do
214
+ ret = yield
215
+ end
216
+ @bytes_read += ret.size
217
+
218
+ #if @bytes_read > 5242880 # 5.megabytes
219
+ # bytes = @bytes_read
220
+ # @bytes_read = 0
221
+ # raise Gifts::Grit::Git::GitTimeout.new(command, bytes)
222
+ #end
223
+
224
+ ret
225
+ rescue Timeout::Error => e
226
+ bytes = @bytes_read
227
+ @bytes_read = 0
228
+ raise Gifts::Grit::Git::GitTimeout.new(command, bytes)
229
+ end
230
+
231
+ def looking_for(commit, path = nil)
232
+ tree_sha = ruby_git.get_subtree(rev_parse({}, commit), path)
233
+
234
+ looking_for = []
235
+ ruby_git.get_object_by_sha1(tree_sha).entry.each do |e|
236
+ if path && !(path == '' || path == '.' || path == './')
237
+ file = File.join(path, e.name)
238
+ else
239
+ file = e.name
240
+ end
241
+ file += '/' if e.type == :directory
242
+ looking_for << file
243
+ end
244
+ looking_for
245
+ end
246
+
247
+ def clean_paths(commit_array)
248
+ new_commits = {}
249
+ commit_array.each do |file, sha|
250
+ file = file.chop if file[file.size - 1 , 1] == '/'
251
+ new_commits[file] = sha
252
+ end
253
+ new_commits
254
+ end
255
+
256
+ # TODO
257
+ # git grep -n 'foo' 'master'
258
+ # git log --pretty='raw' --max-count='1' 'master' -- 'LICENSE'
259
+ # git log --pretty='raw' --max-count='1' 'master' -- 'test'
260
+
261
+ end
262
+ end