ext 0.0.10 → 0.1.0
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.
- data/README +11 -10
- data/Rakefile +2 -2
- data/lib/{ext → extensions}/string.rb +0 -0
- data/lib/{ext → extensions}/symbol.rb +0 -0
- data/lib/externals/configuration/configuration.rb +112 -77
- data/lib/externals/configuration/old_configuration.rb +166 -0
- data/lib/externals/ext.rb +181 -48
- data/lib/externals/old_project.rb +114 -0
- data/lib/externals/old_scms/old_git_project.rb +116 -0
- data/lib/externals/old_scms/old_svn_project.rb +98 -0
- data/lib/externals/project.rb +59 -52
- data/lib/externals/project_types/rails.rb +18 -6
- data/lib/externals/scms/git_project.rb +6 -0
- data/lib/externals/scms/svn_project.rb +5 -0
- data/test/test_checkout_git.rb +4 -0
- data/test/test_checkout_with_subprojects_git.rb +10 -1
- data/test/test_checkout_with_subprojects_svn.rb +26 -12
- data/test/test_upgrade_externals_file.rb +84 -0
- metadata +9 -4
data/lib/externals/ext.rb
CHANGED
@@ -1,10 +1,16 @@
|
|
1
1
|
require 'externals/project'
|
2
|
+
require 'externals/old_project'
|
2
3
|
require 'externals/configuration/configuration'
|
4
|
+
require 'externals/configuration/old_configuration'
|
3
5
|
require 'optparse'
|
4
6
|
require 'externals/command'
|
5
|
-
require '
|
7
|
+
require 'extensions/symbol'
|
6
8
|
|
7
9
|
module Externals
|
10
|
+
#exit status
|
11
|
+
OBSOLETE_EXTERNALS_FILE = 15
|
12
|
+
|
13
|
+
|
8
14
|
PROJECT_TYPES_DIRECTORY = File.join(File.dirname(__FILE__), '..', 'externals','project_types')
|
9
15
|
|
10
16
|
# Full commands operate on the main project as well as the externals
|
@@ -37,11 +43,11 @@ module Externals
|
|
37
43
|
main project. This is automatically performed by install,
|
38
44
|
and so you probably only will run this if you are manually
|
39
45
|
maintaining .externals"],
|
40
|
-
[:install, "ext install <repository[
|
46
|
+
[:install, "ext install <repository> [-b <branch>] [path]",
|
41
47
|
"Registers <repository> in .externals under the appropriate
|
42
48
|
SCM. Checks out the project, and also adds it to the ignore
|
43
|
-
feature offered by the SCM of the main project. If the SCM
|
44
|
-
type is not obvious from the repository URL, use the --scm,
|
49
|
+
feature offered by the SCM of the main project. If the SCM
|
50
|
+
type is not obvious from the repository URL, use the --scm,
|
45
51
|
--git, or --svn flags."],
|
46
52
|
[:init, "Creates a .externals file containing only [main]
|
47
53
|
It will try to determine the SCM used by the main project,
|
@@ -53,7 +59,9 @@ module Externals
|
|
53
59
|
top and adds a .emptydir file to any empty directories it
|
54
60
|
comes across. Useful for dealing with SCMs that refuse to
|
55
61
|
track empty directories (such as git, for example)"],
|
56
|
-
[:help, "You probably just ran this command just now."]
|
62
|
+
[:help, "You probably just ran this command just now."],
|
63
|
+
[:upgrade_externals_file, "Converts the old format that stored
|
64
|
+
as [main][svn][git] to [<path1>][<path2>]..."],
|
57
65
|
]
|
58
66
|
|
59
67
|
|
@@ -65,18 +73,24 @@ module Externals
|
|
65
73
|
|
66
74
|
|
67
75
|
class Ext
|
68
|
-
Dir.entries(File.join(File.dirname(__FILE__), '..', '
|
69
|
-
require "
|
76
|
+
Dir.entries(File.join(File.dirname(__FILE__), '..', 'extensions')).each do |extension|
|
77
|
+
require "extensions/#{extension}" if extension =~ /.rb$/
|
70
78
|
end
|
71
79
|
|
72
80
|
Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','scms')).each do |project|
|
73
81
|
require "externals/scms/#{project}" if project =~ /_project.rb$/
|
74
82
|
end
|
75
|
-
|
83
|
+
|
84
|
+
Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','old_scms')).each do |project|
|
85
|
+
require "externals/old_scms/#{project}" if project =~ /_project.rb$/
|
86
|
+
end
|
87
|
+
|
76
88
|
Dir.entries(PROJECT_TYPES_DIRECTORY).each do |type|
|
77
89
|
require File.join(PROJECT_TYPES_DIRECTORY, type) if type =~ /\.rb$/
|
78
90
|
end
|
79
91
|
|
92
|
+
attr_accessor :path_calculator
|
93
|
+
|
80
94
|
def self.project_types
|
81
95
|
types = Dir.entries(PROJECT_TYPES_DIRECTORY).select do |file|
|
82
96
|
file =~ /\.rb$/
|
@@ -102,16 +116,18 @@ module Externals
|
|
102
116
|
def self.new_opts main_options, sub_options
|
103
117
|
opts = OptionParser.new
|
104
118
|
|
105
|
-
opts.banner = "ext [OPTIONS] <command> [repository[
|
119
|
+
opts.banner = "ext [OPTIONS] <command> [repository] [-b <branch>] [path]"
|
106
120
|
|
107
121
|
project_classes.each do |project_class|
|
108
122
|
project_class.fill_in_opts(opts, main_options, sub_options)
|
109
123
|
end
|
110
124
|
|
111
125
|
opts.on("--type TYPE", "-t TYPE", "The type of project the main project is. For example, 'rails'.",
|
112
|
-
|
126
|
+
String) {|type| sub_options[:scm] = main_options[:type] = type}
|
113
127
|
opts.on("--scm SCM", "-s SCM", "The SCM used to manage the main project. For example, '--scm svn'.",
|
114
|
-
|
128
|
+
String) {|scm| sub_options[:scm] = main_options[:scm] = scm}
|
129
|
+
opts.on("--branch BRANCH", "-b BRANCH", "The branch you want the subproject to checkout when doing 'ext install'",
|
130
|
+
String) {|branch| sub_options[:branch] = main_options[:branch] = branch}
|
115
131
|
opts.on("--workdir DIR", "-w DIR", "The working directory to execute commands from. Use this if for some reason you
|
116
132
|
cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
|
117
133
|
or in a Capistrano task)",
|
@@ -153,10 +169,29 @@ module Externals
|
|
153
169
|
exit
|
154
170
|
end
|
155
171
|
|
172
|
+
|
156
173
|
Dir.chdir(main_options[:workdir] || ".") do
|
174
|
+
if command == :upgrade_externals_file
|
175
|
+
main_options[:upgrade_externals_file] = true
|
176
|
+
elsif command != :help
|
177
|
+
if externals_file_obsolete?
|
178
|
+
puts "your .externals file Appears to be in an obsolete format"
|
179
|
+
puts "Please run 'ext upgrade_externals_file' to migrate it to the new format"
|
180
|
+
exit OBSOLETE_EXTERNALS_FILE
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
157
184
|
new(main_options).send(command, args, sub_options)
|
158
185
|
end
|
159
186
|
end
|
187
|
+
|
188
|
+
def self.externals_file_obsolete?
|
189
|
+
return false if !File.exists?('.externals')
|
190
|
+
|
191
|
+
open('.externals', 'r') do |f|
|
192
|
+
f.read =~ /^\s*\[git\]\s*$|^\s*\[main\]\s*$|^\s*\[svn\]\s*$/
|
193
|
+
end
|
194
|
+
end
|
160
195
|
|
161
196
|
def print_commands(commands)
|
162
197
|
commands.each do |command|
|
@@ -198,17 +233,36 @@ module Externals
|
|
198
233
|
end
|
199
234
|
|
200
235
|
def projects
|
201
|
-
|
236
|
+
p = []
|
237
|
+
configuration.sections.each do |section|
|
238
|
+
p << Ext.project_class(section[:scm]||infer_scm(section[:repository])).new(
|
239
|
+
section.attributes.merge(:path => section.title))
|
240
|
+
end
|
241
|
+
p
|
202
242
|
end
|
203
243
|
|
204
244
|
def subprojects
|
205
|
-
|
245
|
+
s = []
|
246
|
+
projects.each do |project|
|
247
|
+
s << project unless project.main_project?
|
248
|
+
end
|
249
|
+
s
|
250
|
+
end
|
251
|
+
|
252
|
+
def main_project
|
253
|
+
projects.detect {|p| p.main_project?}
|
206
254
|
end
|
207
255
|
|
208
256
|
def configuration
|
209
257
|
return @configuration if @configuration
|
210
258
|
|
211
|
-
|
259
|
+
file_string = ''
|
260
|
+
if File.exists? '.externals'
|
261
|
+
open('.externals', 'r') do |f|
|
262
|
+
file_string = f.read
|
263
|
+
end
|
264
|
+
end
|
265
|
+
@configuration = Configuration::Configuration.new(file_string)
|
212
266
|
end
|
213
267
|
|
214
268
|
def reload_configuration
|
@@ -219,12 +273,37 @@ module Externals
|
|
219
273
|
def initialize options
|
220
274
|
super()
|
221
275
|
|
222
|
-
|
276
|
+
if options[:upgrade_externals_file]
|
277
|
+
type ||= options[:type]
|
278
|
+
|
279
|
+
|
280
|
+
if type
|
281
|
+
install_project_type type
|
282
|
+
else
|
283
|
+
|
284
|
+
possible_project_types = self.class.project_types.select do |project_type|
|
285
|
+
self.class.project_type_detector(project_type).detected?
|
286
|
+
end
|
287
|
+
|
288
|
+
if possible_project_types.size > 1
|
289
|
+
raise "We found multiple project types that this could be: #{possible_project_types.join(',')}
|
290
|
+
Please use
|
291
|
+
the --type option to tell ext which to use."
|
292
|
+
elsif possible_project_types.empty?
|
293
|
+
install_project_type OldConfiguration::Configuration.new[:main][:type]
|
294
|
+
else
|
295
|
+
install_project_type possible_project_types.first
|
296
|
+
end
|
297
|
+
end
|
298
|
+
return
|
299
|
+
end
|
300
|
+
|
301
|
+
scm = configuration['.']
|
223
302
|
scm = scm['scm'] if scm
|
224
303
|
scm ||= options[:scm]
|
225
304
|
#scm ||= infer_scm(repository)
|
226
305
|
|
227
|
-
type = configuration['
|
306
|
+
type = configuration['.']
|
228
307
|
type = type['type'] if type
|
229
308
|
|
230
309
|
type ||= options[:type]
|
@@ -252,6 +331,22 @@ Please use
|
|
252
331
|
Externals.module_eval("#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
|
253
332
|
end
|
254
333
|
|
334
|
+
def self.project_classes
|
335
|
+
retval = []
|
336
|
+
registered_scms.each do |scm|
|
337
|
+
retval << project_class(scm)
|
338
|
+
end
|
339
|
+
|
340
|
+
retval
|
341
|
+
end
|
342
|
+
def self.project_class(scm)
|
343
|
+
Externals.module_eval("#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
|
344
|
+
end
|
345
|
+
|
346
|
+
def self.old_project_class(scm)
|
347
|
+
Externals.module_eval("Old#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
|
348
|
+
end
|
349
|
+
|
255
350
|
def self.project_classes
|
256
351
|
retval = []
|
257
352
|
registered_scms.each do |scm|
|
@@ -285,30 +380,38 @@ Please use
|
|
285
380
|
|
286
381
|
def install args, options
|
287
382
|
init args, options unless File.exists? '.externals'
|
288
|
-
|
383
|
+
repository = args[0]
|
384
|
+
path = args[1]
|
289
385
|
|
290
386
|
orig_options = options.dup
|
291
387
|
|
292
388
|
scm = options[:scm]
|
293
389
|
|
294
|
-
scm ||= infer_scm(
|
295
|
-
|
390
|
+
scm ||= infer_scm(repository)
|
391
|
+
|
296
392
|
unless scm
|
297
|
-
raise "Unable to determine SCM from the repository name.
|
393
|
+
raise "Unable to determine SCM from the repository name.
|
298
394
|
You need to either specify the scm used to manage the subproject
|
299
395
|
that you are installing. Use an option to specify it
|
300
396
|
(such as --git or --svn)"
|
301
397
|
end
|
302
398
|
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
399
|
+
project = self.class.project_class(scm).new(:repository => repository,
|
400
|
+
:path => path || path_calculator.new, :scm => scm)
|
401
|
+
path = project.path
|
402
|
+
|
403
|
+
raise "no path" unless path
|
404
|
+
|
405
|
+
raise "already exists" if configuration[path]
|
406
|
+
|
407
|
+
project.branch = options[:branch] if options[:branch]
|
408
|
+
|
409
|
+
attributes = project.attributes.dup
|
410
|
+
attributes.delete(:path)
|
411
|
+
configuration[path] = project.attributes
|
412
|
+
configuration.write '.externals'
|
308
413
|
reload_configuration
|
309
414
|
|
310
|
-
project = self.class.project_class(scm).new(row)
|
311
|
-
|
312
415
|
project.co
|
313
416
|
|
314
417
|
update_ignore args, orig_options
|
@@ -318,7 +421,7 @@ that you are installing. Use an option to specify it
|
|
318
421
|
#path = args[0]
|
319
422
|
|
320
423
|
|
321
|
-
scm = configuration['
|
424
|
+
scm = configuration['.']
|
322
425
|
scm = scm['scm'] if scm
|
323
426
|
|
324
427
|
scm ||= options[:scm]
|
@@ -328,9 +431,9 @@ that you are installing. Use an option to specify it
|
|
328
431
|
(such as --git or --svn)"
|
329
432
|
end
|
330
433
|
|
331
|
-
project = self.class.project_class(scm).new(".")
|
434
|
+
project = self.class.project_class(scm).new(:path => ".")
|
332
435
|
|
333
|
-
raise "only makes sense for main project" unless project.
|
436
|
+
raise "only makes sense for main project" unless project.main_project?
|
334
437
|
|
335
438
|
subprojects.each do |subproject|
|
336
439
|
puts "about to add #{subproject.path} to ignore"
|
@@ -372,14 +475,14 @@ that you are installing. Use an option to specify it
|
|
372
475
|
|
373
476
|
def status args, options
|
374
477
|
options ||= {}
|
375
|
-
repository = "."
|
376
|
-
path = "."
|
377
|
-
main_project = nil
|
478
|
+
#repository = "."
|
479
|
+
#path = "."
|
480
|
+
#main_project = nil
|
378
481
|
scm = options[:scm]
|
379
|
-
scm ||= infer_scm(repository)
|
482
|
+
#scm ||= infer_scm(repository)
|
380
483
|
|
381
484
|
if !scm
|
382
|
-
scm ||= configuration['
|
485
|
+
scm ||= configuration['.']
|
383
486
|
scm &&= scm['scm']
|
384
487
|
end
|
385
488
|
|
@@ -404,21 +507,22 @@ by creating the .externals file manually"
|
|
404
507
|
(such as --git or --svn)"
|
405
508
|
end
|
406
509
|
|
407
|
-
main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
|
408
|
-
main_project
|
510
|
+
#main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
|
511
|
+
project = main_project
|
512
|
+
project.scm ||= scm
|
513
|
+
project.st
|
409
514
|
|
410
515
|
self.class.new({}).st [], {} #args, options
|
411
516
|
end
|
412
517
|
|
413
518
|
def update args, options
|
414
519
|
options ||= {}
|
415
|
-
repository = args[0]
|
416
|
-
main_project = nil
|
520
|
+
#repository = args[0]
|
417
521
|
scm = options[:scm]
|
418
|
-
scm ||= infer_scm(repository)
|
522
|
+
#scm ||= infer_scm(repository)
|
419
523
|
|
420
524
|
if !scm
|
421
|
-
scm ||= configuration['
|
525
|
+
scm ||= configuration['.']
|
422
526
|
scm &&= scm['scm']
|
423
527
|
end
|
424
528
|
|
@@ -427,8 +531,10 @@ by creating the .externals file manually"
|
|
427
531
|
(such as --git or --svn)"
|
428
532
|
end
|
429
533
|
|
430
|
-
main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
|
431
|
-
main_project
|
534
|
+
#main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
|
535
|
+
project = main_project
|
536
|
+
project.scm ||= scm
|
537
|
+
project.up
|
432
538
|
|
433
539
|
self.class.new({}).up [], {} #args, options
|
434
540
|
end
|
@@ -506,20 +612,46 @@ Please use the --type option to tell ext which to use."
|
|
506
612
|
|
507
613
|
config = Configuration::Configuration.new_empty
|
508
614
|
|
509
|
-
|
510
|
-
|
511
|
-
|
615
|
+
raise ".externals already exists" if File.exists?('.externals')
|
616
|
+
|
617
|
+
config.add_empty_section '.'
|
618
|
+
|
619
|
+
config['.'][:scm] = scm
|
620
|
+
config['.'][:type] = type if type
|
512
621
|
|
513
|
-
config.write
|
622
|
+
config.write '.externals'
|
514
623
|
reload_configuration
|
515
624
|
end
|
516
625
|
|
626
|
+
def upgrade_externals_file args, options = {}
|
627
|
+
old = OldConfiguration::Configuration.new
|
628
|
+
|
629
|
+
config = Configuration::Configuration.new_empty
|
630
|
+
|
631
|
+
main = old['main']
|
632
|
+
config.add_empty_section '.'
|
633
|
+
|
634
|
+
config['.'][:scm] = main[:scm]
|
635
|
+
config['.'][:type] = main[:type]
|
636
|
+
|
637
|
+
old.subprojects.each do |subproject|
|
638
|
+
path = subproject.path
|
639
|
+
config.add_empty_section path
|
640
|
+
config[path][:repository] = subproject.repository
|
641
|
+
config[path][:scm] = subproject.scm
|
642
|
+
config[path][:branch] = subproject.branch if subproject.branch
|
643
|
+
end
|
644
|
+
|
645
|
+
config.write('.externals')
|
646
|
+
end
|
647
|
+
|
517
648
|
def self.project_type_detector name
|
518
649
|
Externals.module_eval("#{name.classify}Detector", __FILE__, __LINE__)
|
519
650
|
end
|
520
651
|
|
521
652
|
def install_project_type name
|
522
653
|
Externals.module_eval("#{name.classify}ProjectType", __FILE__, __LINE__).install
|
654
|
+
self.path_calculator = Externals.module_eval("#{name.classify}ProjectType::DefaultPathCalculator", __FILE__, __LINE__)
|
523
655
|
end
|
524
656
|
#
|
525
657
|
#
|
@@ -549,7 +681,8 @@ Please use the --type option to tell ext which to use."
|
|
549
681
|
(such as --git or --svn)"
|
550
682
|
end
|
551
683
|
|
552
|
-
main_project = self.class.project_class(scm).new(
|
684
|
+
main_project = self.class.project_class(scm).new(:repository => repository,
|
685
|
+
:path => path)
|
553
686
|
|
554
687
|
main_project.send(sym)
|
555
688
|
main_project
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Externals
|
2
|
+
# this regular expression will match a quoted string
|
3
|
+
# it will allow for " to be escaped with "" within the string
|
4
|
+
quoted = '(?:"(?:(?:[^"]*(?:"")?)*)")'
|
5
|
+
|
6
|
+
# this regular expression will match strings of text that are not quoted. They must appear at the start of a line or after a ,
|
7
|
+
# it will also match empty strings like ,,
|
8
|
+
unquoted = '(?:[^"\\s][^\\s$]*)'
|
9
|
+
|
10
|
+
column = "(#{quoted}|#{unquoted})"
|
11
|
+
PROJECT_LINE_REGEX = Regexp.new("^\\s*#{column}(?:\\s+#{column})?\\s*$")
|
12
|
+
|
13
|
+
class OldProject
|
14
|
+
attr_accessor :branch, :repository, :path
|
15
|
+
attr_writer :is_main, :name
|
16
|
+
|
17
|
+
def name
|
18
|
+
@name ||= (extract_name(repository))
|
19
|
+
end
|
20
|
+
|
21
|
+
def main?
|
22
|
+
@is_main
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.scm
|
26
|
+
raise "subclass responsibility"
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def scm
|
31
|
+
self.class.scm
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def initialize row_string, is_main = false
|
36
|
+
#raise "Abstract class" if self.class == Project
|
37
|
+
|
38
|
+
#It's the main project
|
39
|
+
self.is_main = is_main || row_string == "."
|
40
|
+
|
41
|
+
if row_string =~ PROJECT_LINE_REGEX
|
42
|
+
repbranch = trim_quotes($1)
|
43
|
+
self.path = trim_quotes($2)
|
44
|
+
|
45
|
+
repbranch = repbranch.strip if repbranch
|
46
|
+
|
47
|
+
if repbranch =~ /^([^\n]*):(\w+)$/
|
48
|
+
self.repository = $1
|
49
|
+
self.branch = $2
|
50
|
+
else
|
51
|
+
self.repository = repbranch
|
52
|
+
end
|
53
|
+
|
54
|
+
self.path = self.path.strip if self.path
|
55
|
+
else
|
56
|
+
raise "poorly formatted .externals entry: #{row_string}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
[:co, :ex].each do |method_name|
|
61
|
+
define_method method_name do |args|
|
62
|
+
raise "subclass responsibility"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def update_ignore path
|
67
|
+
if !ignore_contains?(path)
|
68
|
+
append_ignore path
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def checkout *args
|
73
|
+
co(*args)
|
74
|
+
end
|
75
|
+
|
76
|
+
def export *args
|
77
|
+
ex(*args)
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_name repository
|
81
|
+
if repository =~ /\/([\w_-]+)(?:\.git)?$/
|
82
|
+
$1
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def path
|
87
|
+
@path || default_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def parent_path
|
91
|
+
File.dirname path
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.project_line? line
|
95
|
+
#Make sure it's not a comment
|
96
|
+
return false if line =~ /^\s*#/
|
97
|
+
|
98
|
+
line =~ PROJECT_LINE_REGEX
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
|
103
|
+
protected
|
104
|
+
def trim_quotes value
|
105
|
+
if value
|
106
|
+
if [value[0].chr, value[-1].chr] == ['"', '"']
|
107
|
+
value[1..-2]
|
108
|
+
else
|
109
|
+
value
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|