tekkub-fugit 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/Rakefile +19 -0
  2. data/VERSION.yml +3 -3
  3. data/bin/fugit +0 -0
  4. data/lib/fugit/commit_dialog.rb +6 -9
  5. data/lib/fugit/commit_tab.rb +1 -1
  6. data/lib/fugit/create_branch_dialog.rb +65 -0
  7. data/lib/fugit/delete_branch_dialog.rb +1 -1
  8. data/lib/fugit/fetch_dialog.rb +52 -0
  9. data/lib/fugit/history_list.rb +16 -12
  10. data/lib/fugit/history_tab.rb +3 -0
  11. data/lib/fugit/index_list.rb +8 -3
  12. data/lib/fugit/io_get_line.rb +4 -4
  13. data/lib/fugit/logged_dialog.rb +49 -0
  14. data/lib/fugit/main_frame.rb +16 -1
  15. data/lib/fugit/merge_dialog.rb +68 -0
  16. data/lib/fugit/push_dialog.rb +11 -61
  17. data/lib/fugit/revert_commit_dialog.rb +51 -0
  18. data/lib/fugit/run_command_dialog.rb +40 -0
  19. data/lib/fugit/tab_toolbar.rb +145 -0
  20. data/lib/fugit.rb +19 -1
  21. data/lib/grit/API.txt +101 -0
  22. data/lib/grit/History.txt +28 -0
  23. data/lib/grit/PURE_TODO +35 -0
  24. data/lib/grit/README.txt +222 -0
  25. data/lib/grit/Rakefile +54 -0
  26. data/lib/grit/VERSION.yml +4 -0
  27. data/lib/grit/benchmarks.rb +129 -0
  28. data/lib/grit/benchmarks.txt +21 -0
  29. data/lib/grit/examples/ex_add_commit.rb +13 -0
  30. data/lib/grit/examples/ex_index.rb +14 -0
  31. data/lib/grit/grit.gemspec +36 -0
  32. data/lib/grit/lib/grit/actor.rb +36 -0
  33. data/lib/grit/lib/grit/blame.rb +61 -0
  34. data/lib/grit/lib/grit/blob.rb +117 -0
  35. data/lib/grit/lib/grit/commit.rb +238 -0
  36. data/lib/grit/lib/grit/commit_stats.rb +104 -0
  37. data/lib/grit/lib/grit/config.rb +44 -0
  38. data/lib/grit/lib/grit/diff.rb +70 -0
  39. data/lib/grit/lib/grit/errors.rb +7 -0
  40. data/lib/grit/lib/grit/git-ruby/commit_db.rb +52 -0
  41. data/lib/grit/lib/grit/git-ruby/file_index.rb +193 -0
  42. data/lib/grit/lib/grit/git-ruby/git_object.rb +344 -0
  43. data/lib/grit/lib/grit/git-ruby/internal/loose.rb +137 -0
  44. data/lib/grit/lib/grit/git-ruby/internal/mmap.rb +58 -0
  45. data/lib/grit/lib/grit/git-ruby/internal/pack.rb +382 -0
  46. data/lib/grit/lib/grit/git-ruby/internal/raw_object.rb +37 -0
  47. data/lib/grit/lib/grit/git-ruby/object.rb +319 -0
  48. data/lib/grit/lib/grit/git-ruby/repository.rb +736 -0
  49. data/lib/grit/lib/grit/git-ruby.rb +186 -0
  50. data/lib/grit/lib/grit/git.rb +141 -0
  51. data/lib/grit/lib/grit/index.rb +122 -0
  52. data/lib/grit/lib/grit/lazy.rb +33 -0
  53. data/lib/grit/lib/grit/merge.rb +45 -0
  54. data/lib/grit/lib/grit/ref.rb +99 -0
  55. data/lib/grit/lib/grit/repo.rb +441 -0
  56. data/lib/grit/lib/grit/status.rb +151 -0
  57. data/lib/grit/lib/grit/submodule.rb +84 -0
  58. data/lib/grit/lib/grit/tag.rb +66 -0
  59. data/lib/grit/lib/grit/tree.rb +104 -0
  60. data/lib/grit/lib/grit.rb +71 -0
  61. data/lib/grit/lib/open3_detach.rb +46 -0
  62. data/lib/icons/application_go.png +0 -0
  63. data/lib/icons/arrow_divide_add.png +0 -0
  64. data/lib/icons/arrow_refresh.png +0 -0
  65. data/lib/icons/cross.png +0 -0
  66. metadata +56 -10
  67. data/fugit.gemspec +0 -32
  68. data/lib/fugit/commit_tab_toolbar.rb +0 -111
@@ -0,0 +1,441 @@
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
+ # Check out a branch
89
+ #
90
+ # Returns true/false if success, error message
91
+ def checkout(branch)
92
+ self.git.checkout({}, branch)
93
+ [self.git.last_status.success?, self.git.last_err]
94
+ end
95
+
96
+
97
+ # Commits current index
98
+ #
99
+ # Returns true/false if commit worked
100
+ def commit_index(message)
101
+ self.git.commit({}, '-m', message)
102
+ end
103
+
104
+ # Commits all tracked and modified files
105
+ #
106
+ # Returns true/false if commit worked
107
+ def commit_all(message)
108
+ self.git.commit({}, '-a', '-m', message)
109
+ end
110
+
111
+ # Adds files to the index
112
+ def add(*files)
113
+ self.git.add({}, *files.flatten)
114
+ end
115
+
116
+ # Adds changed, tracked files to the index
117
+ def add_updated
118
+ self.git.add({:update => true})
119
+ end
120
+
121
+ # Adds all files to the index
122
+ def add_all
123
+ self.git.add({:all => true})
124
+ end
125
+
126
+ # Remove files from the index
127
+ # delete from working copy, stage the file to be removed on commit
128
+ def remove(*files)
129
+ self.git.rm({}, *files.flatten)
130
+ end
131
+
132
+ # Unstage files from the index
133
+ # remove from index, but not from working copy
134
+ def reset_all
135
+ self.git.reset
136
+ end
137
+
138
+
139
+ def blame_tree(commit, path = nil)
140
+ commit_array = self.git.blame_tree(commit, path)
141
+
142
+ final_array = {}
143
+ commit_array.each do |file, sha|
144
+ final_array[file] = commit(sha)
145
+ end
146
+ final_array
147
+ end
148
+
149
+ def status
150
+ Status.new(self)
151
+ end
152
+
153
+
154
+ # An array of Tag objects that are available in this repo
155
+ #
156
+ # Returns Grit::Tag[] (baked)
157
+ def tags
158
+ Tag.find_all(self)
159
+ end
160
+
161
+ # An array of Remote objects representing the remote branches in
162
+ # this repo
163
+ #
164
+ # Returns Grit::Remote[] (baked)
165
+ def remotes
166
+ Remote.find_all(self)
167
+ end
168
+
169
+ # An array of Ref objects representing the refs in
170
+ # this repo
171
+ #
172
+ # Returns Grit::Ref[] (baked)
173
+ def refs
174
+ [ Head.find_all(self), Tag.find_all(self), Remote.find_all(self) ].flatten
175
+ end
176
+
177
+ def commit_stats(start = 'master', max_count = 10, skip = 0)
178
+ options = {:max_count => max_count,
179
+ :skip => skip}
180
+
181
+ CommitStats.find_all(self, start, options)
182
+ end
183
+
184
+ # An array of Commit objects representing the history of a given ref/commit
185
+ # +start+ is the branch/commit name (default 'master')
186
+ # +max_count+ is the maximum number of commits to return (default 10, use +false+ for all)
187
+ # +skip+ is the number of commits to skip (default 0)
188
+ #
189
+ # Returns Grit::Commit[] (baked)
190
+ def commits(start = 'master', max_count = 10, skip = 0)
191
+ options = {:max_count => max_count,
192
+ :skip => skip}
193
+
194
+ Commit.find_all(self, start, options)
195
+ end
196
+
197
+ # The Commits objects that are reachable via +to+ but not via +from+
198
+ # Commits are returned in chronological order.
199
+ # +from+ is the branch/commit name of the younger item
200
+ # +to+ is the branch/commit name of the older item
201
+ #
202
+ # Returns Grit::Commit[] (baked)
203
+ def commits_between(from, to)
204
+ Commit.find_all(self, "#{from}..#{to}").reverse
205
+ end
206
+
207
+ # The Commits objects that are newer than the specified date.
208
+ # Commits are returned in chronological order.
209
+ # +start+ is the branch/commit name (default 'master')
210
+ # +since+ is a string represeting a date/time
211
+ # +extra_options+ is a hash of extra options
212
+ #
213
+ # Returns Grit::Commit[] (baked)
214
+ def commits_since(start = 'master', since = '1970-01-01', extra_options = {})
215
+ options = {:since => since}.merge(extra_options)
216
+
217
+ Commit.find_all(self, start, options)
218
+ end
219
+
220
+ # The number of commits reachable by the given branch/commit
221
+ # +start+ is the branch/commit name (default 'master')
222
+ #
223
+ # Returns Integer
224
+ def commit_count(start = 'master')
225
+ Commit.count(self, start)
226
+ end
227
+
228
+ # The Commit object for the specified id
229
+ # +id+ is the SHA1 identifier of the commit
230
+ #
231
+ # Returns Grit::Commit (baked)
232
+ def commit(id)
233
+ options = {:max_count => 1}
234
+
235
+ Commit.find_all(self, id, options).first
236
+ end
237
+
238
+ # The Tree object for the given treeish reference
239
+ # +treeish+ is the reference (default 'master')
240
+ # +paths+ is an optional Array of directory paths to restrict the tree (deafult [])
241
+ #
242
+ # Examples
243
+ # repo.tree('master', ['lib/'])
244
+ #
245
+ # Returns Grit::Tree (baked)
246
+ def tree(treeish = 'master', paths = [])
247
+ Tree.construct(self, treeish, paths)
248
+ end
249
+
250
+ # The Blob object for the given id
251
+ # +id+ is the SHA1 id of the blob
252
+ #
253
+ # Returns Grit::Blob (unbaked)
254
+ def blob(id)
255
+ Blob.create(self, :id => id)
256
+ end
257
+
258
+ # The commit log for a treeish
259
+ #
260
+ # Returns Grit::Commit[]
261
+ def log(commit = 'master', path = nil, options = {})
262
+ default_options = {:pretty => "raw"}
263
+ actual_options = default_options.merge(options)
264
+ arg = path ? [commit, '--', path] : [commit]
265
+ commits = self.git.log(actual_options, *arg)
266
+ Commit.list_from_string(self, commits)
267
+ end
268
+
269
+ # The diff from commit +a+ to commit +b+, optionally restricted to the given file(s)
270
+ # +a+ is the base commit
271
+ # +b+ is the other commit
272
+ # +paths+ is an optional list of file paths on which to restrict the diff
273
+ def diff(a, b, *paths)
274
+ self.git.diff({}, a, b, '--', *paths)
275
+ end
276
+
277
+ # The commit diff for the given commit
278
+ # +commit+ is the commit name/id
279
+ #
280
+ # Returns Grit::Diff[]
281
+ def commit_diff(commit)
282
+ Commit.diff(self, commit)
283
+ end
284
+
285
+ # Initialize a bare git repository at the given path
286
+ # +path+ is the full path to the repo (traditionally ends with /<name>.git)
287
+ # +options+ is any additional options to the git init command
288
+ #
289
+ # Examples
290
+ # Grit::Repo.init_bare('/var/git/myrepo.git')
291
+ #
292
+ # Returns Grit::Repo (the newly created repo)
293
+ def self.init_bare(path, git_options = {}, repo_options = {})
294
+ git = Git.new(path)
295
+ git.init(git_options)
296
+ self.new(path, repo_options)
297
+ end
298
+
299
+ # Fork a bare git repository from this repo
300
+ # +path+ is the full path of the new repo (traditionally ends with /<name>.git)
301
+ # +options+ is any additional options to the git clone command (:bare and :shared are true by default)
302
+ #
303
+ # Returns Grit::Repo (the newly forked repo)
304
+ def fork_bare(path, options = {})
305
+ default_options = {:bare => true, :shared => true}
306
+ real_options = default_options.merge(options)
307
+ self.git.clone(real_options, self.path, path)
308
+ Repo.new(path)
309
+ end
310
+
311
+ # Archive the given treeish
312
+ # +treeish+ is the treeish name/id (default 'master')
313
+ # +prefix+ is the optional prefix
314
+ #
315
+ # Examples
316
+ # repo.archive_tar
317
+ # # => <String containing tar archive>
318
+ #
319
+ # repo.archive_tar('a87ff14')
320
+ # # => <String containing tar archive for commit a87ff14>
321
+ #
322
+ # repo.archive_tar('master', 'myproject/')
323
+ # # => <String containing tar archive and prefixed with 'myproject/'>
324
+ #
325
+ # Returns String (containing tar archive)
326
+ def archive_tar(treeish = 'master', prefix = nil)
327
+ options = {}
328
+ options[:prefix] = prefix if prefix
329
+ self.git.archive(options, treeish)
330
+ end
331
+
332
+ # Archive and gzip the given treeish
333
+ # +treeish+ is the treeish name/id (default 'master')
334
+ # +prefix+ is the optional prefix
335
+ #
336
+ # Examples
337
+ # repo.archive_tar_gz
338
+ # # => <String containing tar.gz archive>
339
+ #
340
+ # repo.archive_tar_gz('a87ff14')
341
+ # # => <String containing tar.gz archive for commit a87ff14>
342
+ #
343
+ # repo.archive_tar_gz('master', 'myproject/')
344
+ # # => <String containing tar.gz archive and prefixed with 'myproject/'>
345
+ #
346
+ # Returns String (containing tar.gz archive)
347
+ def archive_tar_gz(treeish = 'master', prefix = nil)
348
+ options = {}
349
+ options[:prefix] = prefix if prefix
350
+ self.git.archive(options, treeish, "| gzip")
351
+ end
352
+
353
+ # run archive directly to a file
354
+ def archive_to_file(treeish = 'master', prefix = nil, filename = 'archive.tar.gz')
355
+ options = {}
356
+ options[:prefix] = prefix if prefix
357
+ self.git.archive(options, treeish, "| gzip > #{filename}")
358
+ end
359
+
360
+ # Enable git-daemon serving of this repository by writing the
361
+ # git-daemon-export-ok file to its git directory
362
+ #
363
+ # Returns nothing
364
+ def enable_daemon_serve
365
+ FileUtils.touch(File.join(self.path, DAEMON_EXPORT_FILE))
366
+ end
367
+
368
+ # Disable git-daemon serving of this repository by ensuring there is no
369
+ # git-daemon-export-ok file in its git directory
370
+ #
371
+ # Returns nothing
372
+ def disable_daemon_serve
373
+ FileUtils.rm_f(File.join(self.path, DAEMON_EXPORT_FILE))
374
+ end
375
+
376
+ def gc_auto
377
+ self.git.gc({:auto => true})
378
+ end
379
+
380
+ # The list of alternates for this repo
381
+ #
382
+ # Returns Array[String] (pathnames of alternates)
383
+ def alternates
384
+ alternates_path = File.join(self.path, *%w{objects info alternates})
385
+
386
+ if File.exist?(alternates_path)
387
+ File.read(alternates_path).strip.split("\n")
388
+ else
389
+ []
390
+ end
391
+ end
392
+
393
+ # Sets the alternates
394
+ # +alts+ is the Array of String paths representing the alternates
395
+ #
396
+ # Returns nothing
397
+ def alternates=(alts)
398
+ alts.each do |alt|
399
+ unless File.exist?(alt)
400
+ raise "Could not set alternates. Alternate path #{alt} must exist"
401
+ end
402
+ end
403
+
404
+ if alts.empty?
405
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
406
+ f.write ''
407
+ end
408
+ else
409
+ File.open(File.join(self.path, *%w{objects info alternates}), 'w') do |f|
410
+ f.write alts.join("\n")
411
+ end
412
+ end
413
+ end
414
+
415
+ def config
416
+ @config ||= Config.new(self)
417
+ end
418
+
419
+ def index
420
+ Index.new(self)
421
+ end
422
+
423
+ def update_ref(head, commit_sha)
424
+ return nil if !commit_sha || (commit_sha.size != 40)
425
+
426
+ ref_heads = File.join(self.path, 'refs', 'heads')
427
+ FileUtils.mkdir_p(ref_heads)
428
+ File.open(File.join(ref_heads, head), 'w') do |f|
429
+ f.write(commit_sha)
430
+ end
431
+ commit_sha
432
+
433
+ end
434
+
435
+ # Pretty object inspection
436
+ def inspect
437
+ %Q{#<Grit::Repo "#{@path}">}
438
+ end
439
+ end # Repo
440
+
441
+ end # Grit
@@ -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,84 @@
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.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
+ # Pretty object inspection
79
+ def inspect
80
+ %Q{#<Grit::Submodule "#{@id}">}
81
+ end
82
+ end # Submodule
83
+
84
+ end # Grit