ext 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/MIT_LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2008 Miles Georgi, Azimux.com, nopugs.com Consulting
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,108 @@
1
+ Externals is a project that allows you to use the workflow normally made
2
+ possible by svn:externals in an SCM independent manner.
3
+
4
+ I was inspired to create this project because I had several projects that had
5
+ a mix of plugins managed by git and by svn. Git's submodule feature is
6
+ not exactly like svn:externals. Basically, I don't like how you have to manually
7
+ checkout a branch, and I don't like git status not propagating through the
8
+ submodules. Also, the branch tip doesn't automatically move with git-submodule.
9
+ Subversion always checks out the branch tip for subprojects when performing a
10
+ checkout or update.
11
+
12
+ Externals is designed such that adding support for a new SCM, or new project
13
+ types is easy.
14
+
15
+ The externals executable is called ext. Commands come in a long form and a
16
+ short form. The longer form applies the action to the main project. The short
17
+ forms apply the action to all sub projects.
18
+
19
+ The commands and usage are as follows (from 'ext help'):
20
+
21
+ ext [OPTIONS] <command> [repository[:branch]] [path]
22
+ -g, --git same as '--scm git' Uses git to checkout/export the main project
23
+ -s, --svn, --subversion same as '--scm svn' Uses subversion to checkout/export the main project
24
+ -t, --type TYPE The type of project the main project is. For example, 'rails'.
25
+ --scm SCM The SCM used to manage the main project. For example, '--scm svn'.
26
+ -w, --workdir DIR The working directory to execute commands from. Use this if for some reason you
27
+ cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
28
+ or in a Capistrano task)
29
+ --help does the same as 'ext help' If you use this with a command
30
+ it will ignore the command and run help instead.
31
+
32
+
33
+
34
+ Commands that apply to the main project or the .externals file:
35
+ update_ignore, install, init, touch_emptydirs, help
36
+
37
+ update_ignore Adds all paths to subprojects that are
38
+ registered in .externals to the ignore feature of the
39
+ main project. This is automatically performed by install,
40
+ and so you probably only will run this if you are manually
41
+ maintaining .externals
42
+
43
+ install Usage: ext install <repository[:branch]> [path]
44
+ Registers <repository> in .externals under the appropriate
45
+ SCM. Checks out the project, and also adds it to the ignore
46
+ feature offered by the SCM of the main project. If the SCM type
47
+ is not obvious from the repository URL, use the --scm, --git,
48
+ or --svn flags.
49
+
50
+ init Creates a .externals file containing only [main]
51
+ It will try to determine the SCM used by the main project,
52
+ as well as the project type. You don't have to specify
53
+ a project type if you don't want to or if your project type
54
+ isn't supported. It just means that when using 'install'
55
+ that you'll want to specify the path.
56
+
57
+ touch_emptydirs Recurses through all directories from the
58
+ top and adds a .emptydir file to any empty directories it
59
+ comes across. Useful for dealing with SCMs that refuse to
60
+ track empty directories (such as git, for example)
61
+
62
+ help You probably just ran this command just now.
63
+
64
+
65
+
66
+ Commands that apply to the main project and all subprojects:
67
+ checkout, export, status, update
68
+
69
+ checkout Usage: ext checkout <repository>
70
+
71
+ Checks out <repository>, and checks out any subprojects
72
+ registered in <repository>'s .externals file.
73
+
74
+ export Usage: ext export <repository>
75
+
76
+ Like checkout except this command fetches as little
77
+ history as possible.
78
+
79
+ status Usage: ext status
80
+
81
+ Prints out the status of the main project, followed by
82
+ the status of each subproject.
83
+
84
+ update Usage: ext update
85
+
86
+ Brings the main project, and all subprojects, up to the
87
+ latest version.
88
+
89
+
90
+
91
+ Commands that only apply to the subprojects:
92
+ co, ex, st, up
93
+
94
+ co Like checkout, but skips the main project and
95
+ only checks out subprojects.
96
+
97
+ ex Like export, but skips the main project.
98
+
99
+ st Like status, but skips the main project.
100
+
101
+ up Like update, but skips the main project.
102
+
103
+
104
+ The externals project is copyright 2008 by Miles Georgi, nopugs.com, azimux.com
105
+ and is released under the MIT license.
106
+
107
+ The license is available in the same directory as this README
108
+ file and is named MIT_LICENSE.txt
data/Rakefile ADDED
@@ -0,0 +1,64 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rake/gempackagetask'
5
+
6
+ Rake::TestTask.new('test') do |task|
7
+ task.libs = [File.expand_path('lib'),File.expand_path('test')]
8
+ task.pattern = './test/test_*.rb'
9
+ task.warning = true
10
+ end
11
+
12
+ gem_specification = Gem::Specification.new do |specification|
13
+ specification.name = 'ext'
14
+ specification.version = '0.0.5'
15
+ specification.platform = Gem::Platform::RUBY
16
+ specification.rubyforge_project = 'ext'
17
+
18
+ specification.summary =
19
+ %{Provides an SCM agnostic way to manage subprojects with a workflow similar
20
+ to the svn:externals feature of subversion. It's particularly useful for rails
21
+ projects that have some plugins managed by svn and some managed by git.}
22
+ specification.description =
23
+ %{Provides an SCM agnostic way to manage subprojects with a workflow similar
24
+ to the scm:externals feature of subversion. It's particularly useful for rails
25
+ projects that have some plugins managed by svn and some managed by git.
26
+
27
+ For example, "ext install git://github.com/rails/rails.git" from within a rails
28
+ application directory will realize that this belongs in the vendor/rails folder.
29
+ It will also realize that this URL is a git repository and clone it into that
30
+ folder.
31
+
32
+ It will also add the vendor/rails folder to the ignore feature for the SCM of
33
+ the main project. Let's say that the main project is being managed by
34
+ subversion. In that case it adds "rails" to the svn:ignore property of the
35
+ vendor folder. It also adds the URL to the .externals file so that when this
36
+ project is checked out via "ext checkout" it knows where to fetch the
37
+ subprojects.
38
+
39
+ There are several other useful commands, such as init, touch_emptydirs, add_all,
40
+ export, status. I plan to put up a tutorial at http://nopugs.com/ext-tutorial
41
+
42
+ The reason I made this project is that I was frustrated by two things:
43
+
44
+ 1. In my opinion, the workflow for svn:externals is far superior to
45
+ git-submodule.
46
+
47
+ 2. Even if git-submodule was as useful as svn:externals, I would still like a
48
+ uniform way to fetch all of the subprojects regardless of the SCM used to manage
49
+ the main project.}
50
+
51
+ specification.author = "Miles Georgi"
52
+ specification.email = "azimux@gmail.com"
53
+ specification.homepage = "http://nopugs.com/ext-tutorial"
54
+
55
+ specification.test_files = FileList['test/test_*.rb']
56
+ specification.executables = ['ext', 'ext.bat']
57
+ specification.files = ['Rakefile', 'README', 'MIT_LICENSE.txt'] +
58
+ FileList['lib/**/*.rb']
59
+ #specification.require_path = 'lib'
60
+ end
61
+
62
+ Rake::GemPackageTask.new(gem_specification) do |package|
63
+ package.need_zip = package.need_tar = false
64
+ end
data/bin/ext ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'externals/ext'
4
+
5
+ Externals::Ext.run(*ARGV)
data/bin/ext.bat ADDED
@@ -0,0 +1,6 @@
1
+ @ECHO OFF
2
+ IF NOT "%~f0" == "~f0" GOTO :WinNT
3
+ @"ruby.exe" "ext" %1 %2 %3 %4 %5 %6 %7 %8 %9
4
+ GOTO :EOF
5
+ :WinNT
6
+ @"ruby.exe" "%~dpn0" %*
data/lib/ext/string.rb ADDED
@@ -0,0 +1,9 @@
1
+ String.class_eval do
2
+ def cap_first
3
+ "#{self[0].chr.upcase}#{self[1..(self.length - 1)]}"
4
+ end
5
+
6
+ def classify
7
+ split('_').map(&:cap_first).join
8
+ end
9
+ end
data/lib/ext/symbol.rb ADDED
@@ -0,0 +1,7 @@
1
+ unless Symbol.respond_to? :to_proc
2
+ class Symbol
3
+ def to_proc
4
+ proc { |*args| args[0].send(self, *args[1...args.size]) }
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ module Externals
2
+ class Command
3
+ attr_reader :name, :usage, :summary
4
+ def initialize name, usage, summary = nil
5
+ @name = name
6
+ @usage = usage
7
+ @summary = summary
8
+
9
+ if !@summary
10
+ @summary, @usage = @usage, @summary
11
+ end
12
+ end
13
+
14
+ def to_s
15
+ retval = StringIO.new
16
+ retval.printf "%-16s", name
17
+ if usage
18
+ retval.printf "Usage: #{usage}\n"
19
+ else
20
+ dont_pad_first = true
21
+ end
22
+
23
+ summary.split(/\n/).each_with_index do |line, index|
24
+ if index == 0 && dont_pad_first
25
+ retval.printf "%s\n", line.strip
26
+ else
27
+ retval.printf "%16s%s\n", '', line.strip
28
+ end
29
+ end
30
+
31
+ retval.printf "\n"
32
+ retval.string
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,159 @@
1
+ module Externals
2
+ module Configuration
3
+ SECTION_TITLE_REGEX = /^\s*\[(\w+)\]\s*$/
4
+ SECTION_TITLE_REGEX_NO_GROUPS = /^\s*\[(?:\w+)\]\s*$/
5
+
6
+
7
+ class Section
8
+ attr_accessor :title_string, :body_string, :title, :rows, :scm
9
+
10
+ def main?
11
+ title == 'main'
12
+ end
13
+
14
+ def initialize title_string, body_string, scm = nil
15
+ self.title_string = title_string
16
+ self.body_string = body_string
17
+ self.scm = scm
18
+
19
+ self.title = SECTION_TITLE_REGEX.match(title_string)[1]
20
+
21
+ self.scm ||= self.title
22
+
23
+ raise "Invalid section title: #{title_string}" unless title
24
+
25
+ self.rows = body_string.split(/\n/)
26
+ end
27
+
28
+ def setting key
29
+ if !main?
30
+ raise "this isn't a section of the configuration that can contain settings"
31
+ end
32
+
33
+ rows.each do |row|
34
+ if row =~ /\s*(\w+)\s*=\s*([^#]*)(?:#.*)?$/ && key.to_s == $1
35
+ return $2
36
+ end
37
+ end
38
+ nil
39
+ end
40
+
41
+ def [] key
42
+ setting(key)
43
+ end
44
+
45
+
46
+ def projects
47
+ return @projects if @projects
48
+
49
+ @projects = []
50
+
51
+ if main?
52
+ @projects = [Ext.project_class(self['scm']).new(".", :is_main)]
53
+ else
54
+ rows.each do |row|
55
+ if Project.project_line?(row)
56
+ @projects << Ext.project_class(title).new(row)
57
+ end
58
+ end
59
+ @projects
60
+ end
61
+ end
62
+
63
+ def add_row(row)
64
+ rows << row
65
+
66
+ self.body_string = body_string.chomp + "\n#{row}\n"
67
+ clear_caches
68
+ end
69
+
70
+ def clear_caches
71
+ @projects = nil
72
+ end
73
+
74
+ def to_s
75
+ "#{title_string}#{body_string}"
76
+ end
77
+ end
78
+
79
+ class Configuration
80
+ attr_accessor :file_string
81
+
82
+ def sections
83
+ @sections ||= []
84
+ end
85
+
86
+ def [] title
87
+ title = title.to_s
88
+ sections.detect {|section| section.title == title}
89
+ end
90
+
91
+ def add_empty_section title
92
+ raise "Section already exists" if self[title]
93
+ sections << Section.new("\n\n[#{title.to_s}]\n", "")
94
+ end
95
+
96
+ def self.new_empty
97
+ new nil, true
98
+ end
99
+
100
+ def initialize externals_file = nil, empty = false
101
+ if empty
102
+ self.file_string = ''
103
+ return
104
+ end
105
+
106
+ if !externals_file && File.exists?('.externals')
107
+ open('.externals', 'r') do |f|
108
+ externals_file = f.read
109
+ end
110
+ end
111
+
112
+ externals_file ||= ""
113
+
114
+ self.file_string = externals_file
115
+
116
+ titles = externals_file.grep SECTION_TITLE_REGEX
117
+ bodies = externals_file.split SECTION_TITLE_REGEX_NO_GROUPS
118
+
119
+ if titles.size > 0 && bodies.size > 0
120
+ if titles.size + 1 != bodies.size
121
+ raise "bodies and sections do not match up"
122
+ end
123
+
124
+ bodies = bodies[1..(bodies.size - 1)]
125
+
126
+ (0...(bodies.size)).each do |index|
127
+ sections << Section.new(titles[index], bodies[index])
128
+ end
129
+ end
130
+ end
131
+
132
+ def projects
133
+ retval = []
134
+ sections.each do |section|
135
+ retval += section.projects
136
+ end
137
+
138
+ retval
139
+ end
140
+
141
+ def subprojects
142
+ retval = []
143
+ sections.each do |section|
144
+ retval += section.projects unless section.main?
145
+ end
146
+
147
+ retval
148
+ end
149
+
150
+ def write path = ".externals"
151
+ open(path, 'w') do |f|
152
+ sections.each do |section|
153
+ f.write section.to_s
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,557 @@
1
+ require 'externals/project'
2
+ require 'externals/configuration/configuration'
3
+ require 'optparse'
4
+ require 'externals/command'
5
+ require 'ext/symbol'
6
+
7
+ module Externals
8
+ PROJECT_TYPES_DIRECTORY = File.join(File.dirname(__FILE__), '..', 'externals','project_types')
9
+
10
+ # Full commands operate on the main project as well as the externals
11
+ # short commands only operate on the externals
12
+ # Main commands only operate on the main project
13
+ FULL_COMMANDS_HASH = [
14
+ [:checkout, "ext checkout <repository>", %{
15
+ Checks out <repository>, and checks out any subprojects
16
+ registered in <repository>'s .externals file.}],
17
+ [:export, "ext export <repository>", %{
18
+ Like checkout except this command fetches as little
19
+ history as possible.}],
20
+ [:status, "ext status", %{
21
+ Prints out the status of the main project, followed by
22
+ the status of each subproject.}],
23
+ [:update, "ext update", %{
24
+ Brings the main project, and all subprojects, up to the
25
+ latest version.}]
26
+ ]
27
+ SHORT_COMMANDS_HASH = [
28
+ [:co, "Like checkout, but skips the main project and
29
+ only checks out subprojects."],
30
+ [:ex, "Like export, but skips the main project."],
31
+ [:st, "Like status, but skips the main project."],
32
+ [:up, "Like update, but skips the main project."]
33
+ ]
34
+ MAIN_COMMANDS_HASH = [
35
+ [:update_ignore, "Adds all paths to subprojects that are
36
+ registered in .externals to the ignore feature of the
37
+ main project. This is automatically performed by install,
38
+ and so you probably only will run this if you are manually
39
+ maintaining .externals"],
40
+ [:install, "ext install <repository[:branch]> [path]",
41
+ "Registers <repository> in .externals under the appropriate
42
+ 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,
45
+ --git, or --svn flags."],
46
+ [:init, "Creates a .externals file containing only [main]
47
+ It will try to determine the SCM used by the main project,
48
+ as well as the project type. You don't have to specify
49
+ a project type if you don't want to or if your project type
50
+ isn't supported. It just means that when using 'install'
51
+ that you'll want to specify the path."],
52
+ [:touch_emptydirs, "Recurses through all directories from the
53
+ top and adds a .emptydir file to any empty directories it
54
+ comes across. Useful for dealing with SCMs that refuse to
55
+ track empty directories (such as git, for example)"],
56
+ [:help, "You probably just ran this command just now."]
57
+ ]
58
+
59
+
60
+ FULL_COMMANDS = FULL_COMMANDS_HASH.map(&:first)
61
+ SHORT_COMMANDS = SHORT_COMMANDS_HASH.map(&:first)
62
+ MAIN_COMMANDS = MAIN_COMMANDS_HASH.map(&:first)
63
+
64
+ COMMANDS = FULL_COMMANDS + SHORT_COMMANDS + MAIN_COMMANDS
65
+
66
+
67
+ class Ext
68
+ Dir.entries(File.join(File.dirname(__FILE__), '..', 'ext')).each do |extension|
69
+ require "ext/#{extension}" if extension =~ /.rb$/
70
+ end
71
+
72
+ Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','scms')).each do |project|
73
+ require "externals/scms/#{project}" if project =~ /_project.rb$/
74
+ end
75
+
76
+
77
+ def self.project_types
78
+ types = Dir.entries(PROJECT_TYPES_DIRECTORY).select do |file|
79
+ file =~ /\.rb$/
80
+ end
81
+
82
+ types.map do |type|
83
+ /^(.*)\.rb$/.match(type)[1]
84
+ end
85
+ end
86
+
87
+ #puts "Project types available: #{project_types.join(' ')}"
88
+
89
+ def self.project_type_files
90
+ project_types.map do |project_type|
91
+ "#{File.join(PROJECT_TYPES_DIRECTORY, project_type)}.rb"
92
+ end
93
+ end
94
+
95
+ project_type_files.each do |file|
96
+ require file
97
+ end
98
+
99
+ def self.new_opts main_options, sub_options
100
+ opts = OptionParser.new
101
+
102
+ opts.banner = "ext [OPTIONS] <command> [repository[:branch]] [path]"
103
+
104
+ project_classes.each do |project_class|
105
+ project_class.fill_in_opts(opts, main_options, sub_options)
106
+ end
107
+
108
+ opts.on("--type TYPE", "-t TYPE", "The type of project the main project is. For example, 'rails'.",
109
+ Integer) {|type| sub_options[:scm] = main_options[:type] = type}
110
+ opts.on("--scm SCM", "-s SCM", "The SCM used to manage the main project. For example, '--scm svn'.",
111
+ Integer) {|scm| sub_options[:scm] = main_options[:scm] = scm}
112
+ opts.on("--workdir DIR", "-w DIR", "The working directory to execute commands from. Use this if for some reason you
113
+ cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
114
+ or in a Capistrano task)",
115
+ String) {|dir|
116
+ raise "No such directory: #{dir}" unless File.exists?(dir) && File.directory?(dir)
117
+ main_options[:workdir] = dir
118
+ }
119
+ opts.on("--help", "does the same as 'ext help' If you use this with a command
120
+ it will ignore the command and run help instead.") {main_options[:help] = true}
121
+ end
122
+
123
+ def self.run *arguments
124
+
125
+ main_options = {}
126
+ sub_options = {}
127
+
128
+ opts = new_opts main_options, sub_options
129
+
130
+ args = opts.parse(arguments)
131
+
132
+ unless args.nil? || args.empty?
133
+ command = args[0]
134
+ args = args[1..(args.size - 1)] || []
135
+ end
136
+
137
+ command &&= command.to_sym
138
+
139
+ command = :help if main_options[:help]
140
+
141
+ if !command || command.to_s == ''
142
+ puts "hey... you didn't tell me what you want to do."
143
+ puts "Try 'ext help' for a list of commands"
144
+ exit
145
+ end
146
+
147
+ unless COMMANDS.index command
148
+ puts "unknown command: #{command}"
149
+ puts "for a list of commands try 'ext help'"
150
+ exit
151
+ end
152
+
153
+ Dir.chdir(main_options[:workdir] || ".") do
154
+ new(main_options).send(command, args, sub_options)
155
+ end
156
+ end
157
+
158
+ def print_commands(commands)
159
+ commands.each do |command|
160
+ puts Command.new(*command)
161
+ end
162
+ puts
163
+ end
164
+
165
+ def help(args, options)
166
+ puts "#{self.class.new_opts({},{}).to_s}\n\n"
167
+
168
+ puts "\nCommands that apply to the main project or the .externals file:"
169
+ puts "#{MAIN_COMMANDS.join(', ')}\n\n"
170
+ print_commands(MAIN_COMMANDS_HASH)
171
+
172
+ puts "\nCommands that apply to the main project and all subprojects:"
173
+ puts "#{FULL_COMMANDS.join(', ')}\n\n"
174
+ print_commands(FULL_COMMANDS_HASH)
175
+
176
+ puts "\nCommands that only apply to the subprojects:"
177
+ puts "#{SHORT_COMMANDS.join(', ')}\n\n"
178
+ print_commands(SHORT_COMMANDS_HASH)
179
+ end
180
+
181
+ def self.registered_scms
182
+ return @registered_scms if @registered_scms
183
+ @registered_scms ||= []
184
+
185
+ scmdir = File.join(File.dirname(__FILE__), 'scms')
186
+
187
+ Dir.entries(scmdir).each do |file|
188
+ if file =~ /^(.*)_project\.rb$/
189
+ @registered_scms << $1
190
+ end
191
+ end
192
+
193
+ @registered_scms
194
+ end
195
+
196
+ def projects
197
+ configuration.projects
198
+ end
199
+
200
+ def subprojects
201
+ configuration.subprojects
202
+ end
203
+
204
+ def configuration
205
+ return @configuration if @configuration
206
+
207
+ @configuration = Configuration::Configuration.new
208
+ end
209
+
210
+ def reload_configuration
211
+ @configuration = nil
212
+ configuration
213
+ end
214
+
215
+ def initialize options
216
+ super()
217
+
218
+ scm = configuration['main']
219
+ scm = scm['scm'] if scm
220
+ scm ||= options[:scm]
221
+ #scm ||= infer_scm(repository)
222
+
223
+ type = configuration['main']
224
+ type = type['type'] if type
225
+
226
+ type ||= options[:type]
227
+
228
+ if type
229
+ install_project_type type
230
+ else
231
+ possible_project_types = self.class.project_types.select do |project_type|
232
+ self.class.project_type_detector(project_type).detected?
233
+ end
234
+
235
+ if possible_project_types.size > 1
236
+ raise "We found multiple project types that this could be: #{possible_project_types.join(',')}
237
+ Please use
238
+ the --type option to tell ext which to use."
239
+ else
240
+ possible_project_types.each do |project_type|
241
+ install_project_type project_type
242
+ end
243
+ end
244
+ end
245
+ end
246
+
247
+ def self.project_class(scm)
248
+ Externals.module_eval("#{scm.to_s.cap_first}Project", __FILE__, __LINE__)
249
+ end
250
+
251
+ def self.project_classes
252
+ retval = []
253
+ registered_scms.each do |scm|
254
+ retval << project_class(scm)
255
+ end
256
+
257
+ retval
258
+ end
259
+
260
+ SHORT_COMMANDS.each do |command_name|
261
+ define_method command_name do |args, options|
262
+ project_name_or_path = nil
263
+
264
+ if args && !args.empty?
265
+ project_name_or_path = args.first
266
+ end
267
+
268
+ if project_name_or_path
269
+ project = subprojects.detect do |project|
270
+ project.name == project_name_or_path || project.path == project_name_or_path
271
+ end
272
+
273
+ raise "no such project" unless project
274
+
275
+ project.send command_name, args, options
276
+ else
277
+ subprojects.each {|p| p.send(*([command_name, args, options].flatten))}
278
+ end
279
+ end
280
+ end
281
+
282
+ def install args, options
283
+ init args, options unless File.exists? '.externals'
284
+ row = args.join " "
285
+
286
+ orig_options = options.dup
287
+
288
+ scm = options[:scm]
289
+
290
+ scm ||= infer_scm(row)
291
+
292
+ if !configuration[scm]
293
+ configuration.add_empty_section(scm)
294
+ end
295
+ configuration[scm].add_row(row)
296
+ configuration.write
297
+ reload_configuration
298
+
299
+ project = self.class.project_class(scm).new(row)
300
+
301
+ project.co
302
+
303
+ update_ignore args, orig_options
304
+ end
305
+
306
+ def update_ignore args, options
307
+ #path = args[0]
308
+
309
+
310
+ scm = configuration['main']
311
+ scm = scm['scm'] if scm
312
+
313
+ scm ||= options[:scm]
314
+
315
+ unless scm
316
+ raise "You need to either specify the scm as the first line in .externals (for example, scm = git), or use an option to specify it
317
+ (such as --git or --svn)"
318
+ end
319
+
320
+ project = self.class.project_class(scm).new(".")
321
+
322
+ raise "only makes sense for main project" unless project.main?
323
+
324
+ subprojects.each do |subproject|
325
+ puts "about to add #{subproject.path} to ignore"
326
+ project.update_ignore subproject.path
327
+ puts "finished adding #{subproject.path}"
328
+ end
329
+ end
330
+
331
+ def touch_emptydirs args, options
332
+ require 'find'
333
+
334
+ excludes = ['.','..','.svn', '.git']
335
+
336
+ excludes.dup.each do |exclude|
337
+ excludes << "./#{exclude}"
338
+ end
339
+
340
+ paths = []
341
+
342
+ Find.find('.') do |f|
343
+ if File.directory?(f)
344
+ excluded = false
345
+ File.split(f).each do |part|
346
+ exclude ||= excludes.index(part)
347
+ end
348
+
349
+ if !excluded && ((Dir.entries(f) - excludes).size == 0)
350
+ paths << f
351
+ end
352
+ end
353
+ end
354
+
355
+ paths.each do |p|
356
+ open(File.join(p,".emptydir"), "w").close
357
+ end
358
+
359
+ end
360
+
361
+
362
+ def status args, options
363
+ options ||= {}
364
+ repository = "."
365
+ path = "."
366
+ main_project = nil
367
+ scm = options[:scm]
368
+ scm ||= infer_scm(repository)
369
+
370
+ if !scm
371
+ scm ||= configuration['main']
372
+ scm &&= scm['scm']
373
+ end
374
+
375
+ if !scm
376
+ possible_project_classes = self.class.project_classes.select do |project_class|
377
+ project_class.detected?
378
+ end
379
+
380
+ raise "Could not determine this projects scm" if possible_project_classes.empty?
381
+ if possible_project_classes.size > 1
382
+ raise "This project appears to be managed by multiple SCMs: #{
383
+ possible_project_classes.map(&:to_s).join(',')}
384
+ Please explicitly declare the SCM (by using --git or --svn, or,
385
+ by creating the .externals file manually"
386
+ end
387
+
388
+ scm = possible_project_classes.first.scm
389
+ end
390
+
391
+ unless scm
392
+ raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
393
+ (such as --git or --svn)"
394
+ end
395
+
396
+ main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
397
+ main_project.st
398
+
399
+ self.class.new({}).st [], {} #args, options
400
+ end
401
+
402
+ def update args, options
403
+ options ||= {}
404
+ repository = args[0]
405
+ main_project = nil
406
+ scm = options[:scm]
407
+ scm ||= infer_scm(repository)
408
+
409
+ if !scm
410
+ scm ||= configuration['main']
411
+ scm &&= scm['scm']
412
+ end
413
+
414
+ unless scm
415
+ raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
416
+ (such as --git or --svn)"
417
+ end
418
+
419
+ main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
420
+ main_project.up
421
+
422
+ self.class.new({}).up [], {} #args, options
423
+ end
424
+
425
+ def checkout args, options
426
+ options ||= {}
427
+
428
+ repository = args[0]
429
+ path = args[1] || "."
430
+
431
+ main_project = do_checkout_or_export repository, path, options, :checkout
432
+
433
+ if path == "."
434
+ path = main_project.name
435
+ end
436
+
437
+ Dir.chdir path do
438
+ self.class.new({}).co [], {} #args, options
439
+ end
440
+ end
441
+
442
+ def export args, options
443
+ options ||= {}
444
+
445
+ repository = args[0]
446
+ path = args[1] || "."
447
+
448
+ main_project = do_checkout_or_export repository, path, options, :checkout
449
+
450
+ if path == "."
451
+ path = main_project.name
452
+ end
453
+
454
+ Dir.chdir path do
455
+ self.class.new({}).ex [], {} #args, options
456
+ end
457
+ end
458
+
459
+ def init args, options = {}
460
+ raise ".externals already exists" if File.exists? '.externals'
461
+
462
+ scm = options[:scm]
463
+ type = options[:type]
464
+
465
+ if !scm
466
+ possible_project_classes = self.class.project_classes.select do |project_class|
467
+ project_class.detected?
468
+ end
469
+
470
+ raise "Could not determine this projects scm" if possible_project_classes.empty?
471
+ if possible_project_classes.size > 1
472
+ raise "This project appears to be managed by multiple SCMs: #{
473
+ possible_project_classes.map(&:to_s).join(',')}
474
+ Please explicitly declare the SCM (using --git or --svn, or, by creating .externals manually"
475
+ end
476
+
477
+ scm = possible_project_classes.first.scm
478
+ end
479
+
480
+ if !type
481
+ possible_project_types = self.class.project_types.select do |project_type|
482
+ self.class.project_type_detector(project_type).detected?
483
+ end
484
+
485
+ if possible_project_types.size > 1
486
+ raise "We found multiple project types that this could be: #{possible_project_types.join(',')}
487
+ Please use the --type option to tell ext which to use."
488
+ elsif possible_project_types.size == 0
489
+ puts "WARNING: We could not automatically determine the project type.
490
+ Be sure to specify paths when adding subprojects to your .externals file"
491
+ else
492
+ type = possible_project_types.first
493
+ end
494
+ end
495
+
496
+ config = Configuration::Configuration.new_empty
497
+
498
+ config.sections << Configuration::Section.new("[main]\n",
499
+ "scm = #{scm}\n" +
500
+ "#{'type = ' + type if type}\n")
501
+
502
+ config.write
503
+ reload_configuration
504
+ end
505
+
506
+ def self.project_type_detector name
507
+ Externals.module_eval("#{name.classify}Detector", __FILE__, __LINE__)
508
+ end
509
+
510
+ def install_project_type name
511
+ Externals.module_eval("#{name.classify}ProjectType", __FILE__, __LINE__).install
512
+ end
513
+ #
514
+ #
515
+ # def self.determine_project_type path = "."
516
+ # Dir.chdir path do
517
+ # raise "not done"
518
+ # end
519
+ # end
520
+
521
+ protected
522
+ def do_checkout_or_export repository, path, options, sym
523
+ if File.exists?('.externals')
524
+ raise "seems main project is already checked out here?"
525
+ else
526
+ #We appear to be attempting to checkout/export a main project
527
+ scm = options[:scm]
528
+
529
+ scm ||= infer_scm(repository)
530
+
531
+ if !scm
532
+ scm ||= configuration['main']
533
+ scm &&= scm['scm']
534
+ end
535
+
536
+ unless scm
537
+ raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
538
+ (such as --git or --svn)"
539
+ end
540
+
541
+ main_project = self.class.project_class(scm).new("#{repository} #{path}", :is_main)
542
+
543
+ main_project.send(sym)
544
+ main_project
545
+ end
546
+ end
547
+
548
+ def infer_scm(path)
549
+ self.class.registered_scms.each do |scm|
550
+ return scm if self.class.project_class(scm).scm_path?(path)
551
+ end
552
+ nil
553
+ end
554
+ end
555
+ end
556
+
557
+