boof-grit 1.1.2

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