davetron5000-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 +49 -0
  3. data/LICENSE +22 -0
  4. data/README.md +216 -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/actor.rb +41 -0
  9. data/lib/grit/blame.rb +61 -0
  10. data/lib/grit/blob.rb +126 -0
  11. data/lib/grit/commit.rb +242 -0
  12. data/lib/grit/commit_stats.rb +128 -0
  13. data/lib/grit/config.rb +44 -0
  14. data/lib/grit/diff.rb +70 -0
  15. data/lib/grit/errors.rb +7 -0
  16. data/lib/grit/git-ruby/commit_db.rb +52 -0
  17. data/lib/grit/git-ruby/file_index.rb +193 -0
  18. data/lib/grit/git-ruby/git_object.rb +350 -0
  19. data/lib/grit/git-ruby/internal/file_window.rb +58 -0
  20. data/lib/grit/git-ruby/internal/loose.rb +137 -0
  21. data/lib/grit/git-ruby/internal/pack.rb +382 -0
  22. data/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  23. data/lib/grit/git-ruby/object.rb +325 -0
  24. data/lib/grit/git-ruby/repository.rb +736 -0
  25. data/lib/grit/git-ruby.rb +186 -0
  26. data/lib/grit/git.rb +140 -0
  27. data/lib/grit/index.rb +122 -0
  28. data/lib/grit/lazy.rb +33 -0
  29. data/lib/grit/merge.rb +45 -0
  30. data/lib/grit/ref.rb +99 -0
  31. data/lib/grit/repo.rb +447 -0
  32. data/lib/grit/ruby1.9.rb +7 -0
  33. data/lib/grit/status.rb +151 -0
  34. data/lib/grit/submodule.rb +88 -0
  35. data/lib/grit/tag.rb +66 -0
  36. data/lib/grit/tree.rb +123 -0
  37. data/lib/grit.rb +68 -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 +51 -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 +206 -0
  48. data/test/test_commit_stats.rb +33 -0
  49. data/test/test_commit_write.rb +54 -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 +84 -0
  54. data/test/test_grit.rb +32 -0
  55. data/test/test_head.rb +47 -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 +347 -0
  63. data/test/test_rubygit.rb +188 -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 +67 -0
  69. data/test/test_tree.rb +101 -0
  70. metadata +141 -0
data/lib/grit/repo.rb ADDED
@@ -0,0 +1,447 @@
1
+ module Grit
2
+
3
+ class Repo
4
+ DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
5
+
6
+ # The path of the git repo as a String
7
+ attr_accessor :path
8
+ attr_accessor :working_dir
9
+ attr_reader :bare
10
+
11
+ # The git command line interface object
12
+ attr_accessor :git
13
+
14
+ # Create a new Repo instance
15
+ # +path+ is the path to either the root git directory or the bare git repo
16
+ # +options+ :is_bare force to load a bare repo
17
+ #
18
+ # Examples
19
+ # g = Repo.new("/Users/tom/dev/grit")
20
+ # g = Repo.new("/Users/tom/public/grit.git")
21
+ #
22
+ # Returns Grit::Repo
23
+ def initialize(path, options = {})
24
+ epath = File.expand_path(path)
25
+
26
+ if File.exist?(File.join(epath, '.git'))
27
+ self.working_dir = epath
28
+ self.path = File.join(epath, '.git')
29
+ @bare = false
30
+ elsif File.exist?(epath) && (epath =~ /\.git$/ || options[:is_bare])
31
+ self.path = epath
32
+ @bare = true
33
+ elsif File.exist?(epath)
34
+ raise InvalidGitRepositoryError.new(epath)
35
+ else
36
+ raise NoSuchPathError.new(epath)
37
+ end
38
+
39
+ self.git = Git.new(self.path)
40
+ end
41
+
42
+ # Does nothing yet...
43
+ def self.init(path)
44
+ # !! TODO !!
45
+ # create directory
46
+ # generate initial git directory
47
+ # create new Grit::Repo on that dir, return it
48
+ end
49
+
50
+ # The project's description. Taken verbatim from GIT_REPO/description
51
+ #
52
+ # Returns String
53
+ def description
54
+ File.open(File.join(self.path, 'description')).read.chomp
55
+ end
56
+
57
+ def blame(file, commit = nil)
58
+ Blame.new(self, file, commit)
59
+ end
60
+
61
+
62
+ # An array of Head objects representing the branch heads in
63
+ # this repo
64
+ #
65
+ # Returns Grit::Head[] (baked)
66
+ def heads
67
+ Head.find_all(self)
68
+ end
69
+
70
+ alias_method :branches, :heads
71
+
72
+ def get_head(head_name)
73
+ heads.find { |h| h.name == head_name }
74
+ end
75
+
76
+ def is_head?(head_name)
77
+ get_head(head_name)
78
+ end
79
+
80
+ # Object reprsenting the current repo head.
81
+ #
82
+ # Returns Grit::Head (baked)
83
+ def head
84
+ Head.current(self)
85
+ end
86
+
87
+
88
+ # Commits current index
89
+ #
90
+ # [+message+] the commit message
91
+ # [+author+] the author of the commit, if not the runner; a Grit::Actor or git-compatible string
92
+ #
93
+ # Returns true/false if commit worked
94
+ def commit_index(message,author=nil)
95
+ author = author.to_git_format if author && author.kind_of?(Actor)
96
+ options = author.nil? ? {} : {:author,author}
97
+ self.git.commit(options, '-m', message)
98
+ end
99
+
100
+ # Commits all tracked and modified files
101
+ #
102
+ # [+message+] the commit message
103
+ # [+author+] the author of the commit, if not the runner; a Grit::Actor or git-compatible string
104
+ #
105
+ # Returns true/false if commit worked
106
+ def commit_all(message,author=nil)
107
+ author = author.to_git_format if author && author.kind_of?(Actor)
108
+ options = author.nil? ? {} : {:author,author}
109
+ self.git.commit(options, '-a', '-m', message)
110
+ end
111
+
112
+ # Adds files to the index
113
+ def add(*files)
114
+ self.git.add({}, *files.flatten)
115
+ end
116
+
117
+ # Remove files from the index
118
+ def remove(*files)
119
+ self.git.rm({}, *files.flatten)
120
+ end
121
+
122
+
123
+ def blame_tree(commit, path = nil)
124
+ commit_array = self.git.blame_tree(commit, path)
125
+
126
+ final_array = {}
127
+ commit_array.each do |file, sha|
128
+ final_array[file] = commit(sha)
129
+ end
130
+ final_array
131
+ end
132
+
133
+ def status
134
+ Status.new(self)
135
+ end
136
+
137
+
138
+ # An array of Tag objects that are available in this repo
139
+ #
140
+ # Returns Grit::Tag[] (baked)
141
+ def tags
142
+ Tag.find_all(self)
143
+ end
144
+
145
+ # An array of Remote objects representing the remote branches in
146
+ # this repo
147
+ #
148
+ # Returns Grit::Remote[] (baked)
149
+ def remotes
150
+ Remote.find_all(self)
151
+ end
152
+
153
+ # An array of Ref objects representing the refs in
154
+ # this repo
155
+ #
156
+ # Returns Grit::Ref[] (baked)
157
+ def refs
158
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
159
+ end
160
+
161
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
162
+ options = {:max_count => max_count,
163
+ :skip => skip}
164
+
165
+ CommitStats.find_all(self, start, options)
166
+ end
167
+
168
+ # An array of Commit objects representing the history of a given ref/commit
169
+ # +start+ is the branch/commit name (default 'master')
170
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
171
+ # +skip+ is the number of commits to skip (default 0)
172
+ #
173
+ # Returns Grit::Commit[] (baked)
174
+ def commits(start = 'master', max_count = 10, skip = 0)
175
+ options = {:max_count => max_count,
176
+ :skip => skip}
177
+
178
+ Commit.find_all(self, start, options)
179
+ end
180
+
181
+ # The Commits objects that are reachable via +to+ but not via +from+
182
+ # Commits are returned in chronological order.
183
+ # +from+ is the branch/commit name of the younger item
184
+ # +to+ is the branch/commit name of the older item
185
+ #
186
+ # Returns Grit::Commit[] (baked)
187
+ def commits_between(from, to)
188
+ Commit.find_all(self, "#{from}..#{to}").reverse
189
+ end
190
+
191
+ # The Commits objects that are newer than the specified date.
192
+ # Commits are returned in chronological order.
193
+ # +start+ is the branch/commit name (default 'master')
194
+ # +since+ is a string represeting a date/time
195
+ # +extra_options+ is a hash of extra options
196
+ #
197
+ # Returns Grit::Commit[] (baked)
198
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
199
+ options = {:since => since}.merge(extra_options)
200
+
201
+ Commit.find_all(self, start, options)
202
+ end
203
+
204
+ # The number of commits reachable by the given branch/commit
205
+ # +start+ is the branch/commit name (default 'master')
206
+ #
207
+ # Returns Integer
208
+ def commit_count(start = 'master')
209
+ Commit.count(self, start)
210
+ end
211
+
212
+ # The Commit object for the specified id
213
+ # +id+ is the SHA1 identifier of the commit
214
+ #
215
+ # Returns Grit::Commit (baked)
216
+ def commit(id)
217
+ options = {:max_count => 1}
218
+
219
+ Commit.find_all(self, id, options).first
220
+ end
221
+
222
+ # Returns a list of commits that is in +other_repo+ but not in self
223
+ #
224
+ # Returns Grit::Commit[]
225
+ def commit_deltas_from(other_repo, ref = "master", other_ref = "master")
226
+ # TODO: we should be able to figure out the branch point, rather than
227
+ # rev-list'ing the whole thing
228
+ repo_refs = self.git.rev_list({}, ref).strip.split("\n")
229
+ other_repo_refs = other_repo.git.rev_list({}, other_ref).strip.split("\n")
230
+
231
+ (other_repo_refs - repo_refs).map do |ref|
232
+ Commit.find_all(other_repo, ref, {:max_count => 1}).first
233
+ end
234
+ end
235
+
236
+ # The Tree object for the given treeish reference
237
+ # +treeish+ is the reference (default 'master')
238
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
239
+ #
240
+ # Examples
241
+ # repo.tree('master', ['lib/'])
242
+ #
243
+ # Returns Grit::Tree (baked)
244
+ def tree(treeish = 'master', paths = [])
245
+ Tree.construct(self, treeish, paths)
246
+ end
247
+
248
+ # The Blob object for the given id
249
+ # +id+ is the SHA1 id of the blob
250
+ #
251
+ # Returns Grit::Blob (unbaked)
252
+ def blob(id)
253
+ Blob.create(self, :id => id)
254
+ end
255
+
256
+ # The commit log for a treeish
257
+ #
258
+ # Returns Grit::Commit[]
259
+ def log(commit = 'master', path = nil, options = {})
260
+ default_options = {:pretty => "raw"}
261
+ actual_options = default_options.merge(options)
262
+ arg = path ? [commit, '--', path] : [commit]
263
+ commits = self.git.log(actual_options, *arg)
264
+ Commit.list_from_string(self, commits)
265
+ end
266
+
267
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
268
+ # +a+ is the base commit
269
+ # +b+ is the other commit
270
+ # +paths+ is an optional list of file paths on which to restrict the diff
271
+ def diff(a, b, *paths)
272
+ self.git.diff({}, a, b, '--', *paths)
273
+ end
274
+
275
+ # The commit diff for the given commit
276
+ # +commit+ is the commit name/id
277
+ #
278
+ # Returns Grit::Diff[]
279
+ def commit_diff(commit)
280
+ Commit.diff(self, commit)
281
+ end
282
+
283
+ # Initialize a bare git repository at the given path
284
+ # +path+ is the full path to the repo (traditionally ends with /<name>.git)
285
+ # +options+ is any additional options to the git init command
286
+ #
287
+ # Examples
288
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
289
+ #
290
+ # Returns Grit::Repo (the newly created repo)
291
+ def self.init_bare(path, git_options = {}, repo_options = {})
292
+ git = Git.new(path)
293
+ git.init(git_options)
294
+ self.new(path, repo_options)
295
+ end
296
+
297
+ # Fork a bare git repository from this repo
298
+ # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
299
+ # +options+ is any additional options to the git clone command (:bare and :shared are true by default)
300
+ #
301
+ # Returns Grit::Repo (the newly forked repo)
302
+ def fork_bare(path, options = {})
303
+ default_options = {:bare => true, :shared => true}
304
+ real_options = default_options.merge(options)
305
+ self.git.clone(real_options, self.path, path)
306
+ Repo.new(path)
307
+ end
308
+
309
+ # Archive the given treeish
310
+ # +treeish+ is the treeish name/id (default 'master')
311
+ # +prefix+ is the optional prefix
312
+ #
313
+ # Examples
314
+ # repo.archive_tar
315
+ # # => <String containing tar archive>
316
+ #
317
+ # repo.archive_tar('a87ff14')
318
+ # # => <String containing tar archive for commit a87ff14>
319
+ #
320
+ # repo.archive_tar('master', 'myproject/')
321
+ # # => <String containing tar archive and prefixed with 'myproject/'>
322
+ #
323
+ # Returns String (containing tar archive)
324
+ def archive_tar(treeish = 'master', prefix = nil)
325
+ options = {}
326
+ options[:prefix] = prefix if prefix
327
+ self.git.archive(options, treeish)
328
+ end
329
+
330
+ # Archive and gzip the given treeish
331
+ # +treeish+ is the treeish name/id (default 'master')
332
+ # +prefix+ is the optional prefix
333
+ #
334
+ # Examples
335
+ # repo.archive_tar_gz
336
+ # # => <String containing tar.gz archive>
337
+ #
338
+ # repo.archive_tar_gz('a87ff14')
339
+ # # => <String containing tar.gz archive for commit a87ff14>
340
+ #
341
+ # repo.archive_tar_gz('master', 'myproject/')
342
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
343
+ #
344
+ # Returns String (containing tar.gz archive)
345
+ def archive_tar_gz(treeish = 'master', prefix = nil)
346
+ options = {}
347
+ options[:prefix] = prefix if prefix
348
+ self.git.archive(options, treeish, "| gzip")
349
+ end
350
+
351
+ # Write an archive directly to a file
352
+ # +treeish+ is the treeish name/id (default 'master')
353
+ # +prefix+ is the optional prefix (default nil)
354
+ # +filename+ is the name of the file (default 'archive.tar.gz')
355
+ # +format+ is the optional format (default nil)
356
+ # +pipe+ is the command to run the output through (default 'gzip')
357
+ #
358
+ # Returns nothing
359
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz', format = nil, pipe = "gzip")
360
+ options = {}
361
+ options[:prefix] = prefix if prefix
362
+ options[:format] = format if format
363
+ self.git.archive(options, treeish, "| #{pipe} > #{filename}")
364
+ end
365
+
366
+ # Enable git-daemon serving of this repository by writing the
367
+ # git-daemon-export-ok file to its git directory
368
+ #
369
+ # Returns nothing
370
+ def enable_daemon_serve
371
+ FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
372
+ end
373
+
374
+ # Disable git-daemon serving of this repository by ensuring there is no
375
+ # git-daemon-export-ok file in its git directory
376
+ #
377
+ # Returns nothing
378
+ def disable_daemon_serve
379
+ FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
380
+ end
381
+
382
+ def gc_auto
383
+ self.git.gc({:auto => true})
384
+ end
385
+
386
+ # The list of alternates for this repo
387
+ #
388
+ # Returns Array[String] (pathnames of alternates)
389
+ def alternates
390
+ alternates_path = File.join(self.path, *%w{objects info alternates})
391
+
392
+ if File.exist?(alternates_path)
393
+ File.read(alternates_path).strip.split("\n")
394
+ else
395
+ []
396
+ end
397
+ end
398
+
399
+ # Sets the alternates
400
+ # +alts+ is the Array of String paths representing the alternates
401
+ #
402
+ # Returns nothing
403
+ def alternates=(alts)
404
+ alts.each do |alt|
405
+ unless File.exist?(alt)
406
+ raise "Could not set alternates. Alternate path #{alt} must exist"
407
+ end
408
+ end
409
+
410
+ if alts.empty?
411
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
412
+ f.write ''
413
+ end
414
+ else
415
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
416
+ f.write alts.join("\n")
417
+ end
418
+ end
419
+ end
420
+
421
+ def config
422
+ @config ||= Config.new(self)
423
+ end
424
+
425
+ def index
426
+ Index.new(self)
427
+ end
428
+
429
+ def update_ref(head, commit_sha)
430
+ return nil if !commit_sha || (commit_sha.size != 40)
431
+
432
+ ref_heads = File.join(self.path, 'refs', 'heads')
433
+ FileUtils.mkdir_p(ref_heads)
434
+ File.open(File.join(ref_heads, head), 'w') do |f|
435
+ f.write(commit_sha)
436
+ end
437
+ commit_sha
438
+
439
+ end
440
+
441
+ # Pretty object inspection
442
+ def inspect
443
+ %Q{#<Grit::Repo "#{@path}">}
444
+ end
445
+ end # Repo
446
+
447
+ end # Grit
@@ -0,0 +1,7 @@
1
+ class String
2
+ if ((defined? RUBY_VERSION) && (RUBY_VERSION[0..2] == "1.9"))
3
+ def getord(offset); self[offset].ord; end
4
+ else
5
+ alias :getord :[]
6
+ end
7
+ end
@@ -0,0 +1,151 @@
1
+ module Grit
2
+
3
+ class Status
4
+ include Enumerable
5
+
6
+ @base = nil
7
+ @files = nil
8
+
9
+ def initialize(base)
10
+ @base = base
11
+ construct_status
12
+ end
13
+
14
+ def changed
15
+ @files.select { |k, f| f.type == 'M' }
16
+ end
17
+
18
+ def added
19
+ @files.select { |k, f| f.type == 'A' }
20
+ end
21
+
22
+ def deleted
23
+ @files.select { |k, f| f.type == 'D' }
24
+ end
25
+
26
+ def untracked
27
+ @files.select { |k, f| f.untracked }
28
+ end
29
+
30
+ def pretty
31
+ out = ''
32
+ self.each do |file|
33
+ out << file.path
34
+ out << "\n\tsha(r) " + file.sha_repo.to_s + ' ' + file.mode_repo.to_s
35
+ out << "\n\tsha(i) " + file.sha_index.to_s + ' ' + file.mode_index.to_s
36
+ out << "\n\ttype " + file.type.to_s
37
+ out << "\n\tstage " + file.stage.to_s
38
+ out << "\n\tuntrac " + file.untracked.to_s
39
+ out << "\n"
40
+ end
41
+ out << "\n"
42
+ out
43
+ end
44
+
45
+ # enumerable method
46
+
47
+ def [](file)
48
+ @files[file]
49
+ end
50
+
51
+ def each
52
+ @files.each do |k, file|
53
+ yield file
54
+ end
55
+ end
56
+
57
+ class StatusFile
58
+ attr_accessor :path, :type, :stage, :untracked
59
+ attr_accessor :mode_index, :mode_repo
60
+ attr_accessor :sha_index, :sha_repo
61
+
62
+ @base = nil
63
+
64
+ def initialize(base, hash)
65
+ @base = base
66
+ @path = hash[:path]
67
+ @type = hash[:type]
68
+ @stage = hash[:stage]
69
+ @mode_index = hash[:mode_index]
70
+ @mode_repo = hash[:mode_repo]
71
+ @sha_index = hash[:sha_index]
72
+ @sha_repo = hash[:sha_repo]
73
+ @untracked = hash[:untracked]
74
+ end
75
+
76
+ def blob(type = :index)
77
+ if type == :repo
78
+ @base.object(@sha_repo)
79
+ else
80
+ @base.object(@sha_index) rescue @base.object(@sha_repo)
81
+ end
82
+ end
83
+
84
+ end
85
+
86
+ private
87
+
88
+ def construct_status
89
+ @files = ls_files
90
+
91
+ Dir.chdir(@base.working_dir) do
92
+ # find untracked in working dir
93
+ Dir.glob('**/*') do |file|
94
+ if !@files[file]
95
+ @files[file] = {:path => file, :untracked => true} if !File.directory?(file)
96
+ end
97
+ end
98
+
99
+ # find modified in tree
100
+ diff_files.each do |path, data|
101
+ @files[path] ? @files[path].merge!(data) : @files[path] = data
102
+ end
103
+
104
+ # find added but not committed - new files
105
+ diff_index('HEAD').each do |path, data|
106
+ @files[path] ? @files[path].merge!(data) : @files[path] = data
107
+ end
108
+
109
+ @files.each do |k, file_hash|
110
+ @files[k] = StatusFile.new(@base, file_hash)
111
+ end
112
+ end
113
+ end
114
+
115
+ # compares the index and the working directory
116
+ def diff_files
117
+ hsh = {}
118
+ @base.git.diff_files.split("\n").each do |line|
119
+ (info, file) = line.split("\t")
120
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
121
+ hsh[file] = {:path => file, :mode_file => mode_src.to_s[1, 7], :mode_index => mode_dest,
122
+ :sha_file => sha_src, :sha_index => sha_dest, :type => type}
123
+ end
124
+ hsh
125
+ end
126
+
127
+ # compares the index and the repository
128
+ def diff_index(treeish)
129
+ hsh = {}
130
+ @base.git.diff_index({}, treeish).split("\n").each do |line|
131
+ (info, file) = line.split("\t")
132
+ (mode_src, mode_dest, sha_src, sha_dest, type) = info.split
133
+ hsh[file] = {:path => file, :mode_repo => mode_src.to_s[1, 7], :mode_index => mode_dest,
134
+ :sha_repo => sha_src, :sha_index => sha_dest, :type => type}
135
+ end
136
+ hsh
137
+ end
138
+
139
+ def ls_files
140
+ hsh = {}
141
+ lines = @base.git.ls_files({:stage => true})
142
+ lines.split("\n").each do |line|
143
+ (info, file) = line.split("\t")
144
+ (mode, sha, stage) = info.split
145
+ hsh[file] = {:path => file, :mode_index => mode, :sha_index => sha, :stage => stage}
146
+ end
147
+ hsh
148
+ end
149
+ end
150
+
151
+ end
@@ -0,0 +1,88 @@
1
+ module Grit
2
+
3
+ class Submodule
4
+ attr_reader :id
5
+ attr_reader :mode
6
+ attr_reader :name
7
+
8
+ # Create a Submodule containing just the specified attributes
9
+ # +repo+ is the Repo
10
+ # +atts+ is a Hash of instance variable data
11
+ #
12
+ # Returns Grit::Submodule (unbaked)
13
+ def self.create(repo, atts)
14
+ self.allocate.create_initialize(repo, atts)
15
+ end
16
+
17
+ # Initializer for Submodule.create
18
+ # +repo+ is the Repo
19
+ # +atts+ is a Hash of instance variable data
20
+ #
21
+ # Returns Grit::Submodule
22
+ def create_initialize(repo, atts)
23
+ @repo = repo
24
+ atts.each do |k, v|
25
+ instance_variable_set("@#{k}".to_sym, v)
26
+ end
27
+ self
28
+ end
29
+
30
+ # The url of this submodule
31
+ # +ref+ is the committish that should be used to look up the url
32
+ #
33
+ # Returns String
34
+ def url(ref)
35
+ config = self.class.config(@repo, ref)
36
+
37
+ lookup = config.keys.inject({}) do |acc, key|
38
+ id = config[key]['id']
39
+ acc[id] = config[key]['url']
40
+ acc
41
+ end
42
+
43
+ lookup[@id]
44
+ end
45
+
46
+ # The configuration information for the given +repo+
47
+ # +repo+ is the Repo
48
+ # +ref+ is the committish (defaults to 'master')
49
+ #
50
+ # Returns a Hash of { <path:String> => { 'url' => <url:String>, 'id' => <id:String> } }
51
+ # Returns {} if no .gitmodules file was found
52
+ def self.config(repo, ref = "master")
53
+ commit = repo.commit(ref)
54
+ blob = commit.tree/'.gitmodules'
55
+ return {} unless blob
56
+
57
+ lines = blob.data.gsub(/\r\n?/, "\n" ).split("\n")
58
+
59
+ config = {}
60
+ current = nil
61
+
62
+ lines.each do |line|
63
+ if line =~ /^\[submodule "(.+)"\]$/
64
+ current = $1
65
+ config[current] = {}
66
+ config[current]['id'] = (commit.tree/current).id
67
+ elsif line =~ /^\t(\w+) = (.+)$/
68
+ config[current][$1] = $2
69
+ config[current]['id'] = (commit.tree/$2).id if $1 == 'path'
70
+ else
71
+ # ignore
72
+ end
73
+ end
74
+
75
+ config
76
+ end
77
+
78
+ def basename
79
+ File.basename(name)
80
+ end
81
+
82
+ # Pretty object inspection
83
+ def inspect
84
+ %Q{#<Grit::Submodule "#{@id}">}
85
+ end
86
+ end # Submodule
87
+
88
+ end # Grit