ext 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +5 -0
- data/README +55 -15
- data/lib/externals/configuration/configuration.rb +8 -0
- data/lib/externals/ext.rb +130 -34
- data/lib/externals/extensions/string.rb +31 -1
- data/lib/externals/project.rb +4 -0
- data/lib/externals/scms/git_project.rb +37 -4
- data/lib/externals/scms/svn_project.rb +121 -17
- data/lib/externals/test_case.rb +210 -8
- data/test/test_checkout_with_subprojects_git.rb +52 -1
- data/test/test_projects.rb +10 -0
- data/test/test_string_extensions.rb +32 -0
- data/test/test_svn_branches.rb +501 -0
- metadata +6 -4
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
September 28, 2011 Version 1.0.3 released
|
2
|
+
- "up" now changes branches of subprojects
|
3
|
+
- Subversion branch support added
|
4
|
+
- "switch" command added.
|
5
|
+
|
1
6
|
September 8, 2011 Version 1.0.2 released
|
2
7
|
- For git, fixed bug preventing "ext up" from changing the branches of subprojects as expected.
|
3
8
|
- For git, fixed bug preventing "ext checkout -b <branch_name>" from functioning as expected.
|
data/README
CHANGED
@@ -21,26 +21,35 @@ forms apply the action to all sub projects.
|
|
21
21
|
|
22
22
|
The commands and usage are as follows (from 'ext help'):
|
23
23
|
There's a tutorial available at http://nopugs.com/ext-tutorial
|
24
|
-
There's a tutorial available at http://nopugs.com/ext-tutorial
|
25
24
|
|
26
25
|
ext [OPTIONS] <command> [repository] [-b <branch>] [path]
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
26
|
+
-g, --git same as '--scm git' Uses git to checkout/export the
|
27
|
+
main project
|
28
|
+
--svn, --subversion same as '--scm svn' Uses subversion to
|
29
|
+
checkout/export the main project
|
30
|
+
-t, --type TYPE The type of project the main project is. For example,
|
31
|
+
'rails'.
|
32
|
+
-s, --scm SCM The SCM used to manage the main project. For example,
|
33
|
+
'--scm svn'.
|
34
|
+
-b, --branch BRANCH The branch you want the subproject to checkout when
|
35
|
+
doing 'ext install'
|
36
|
+
-f, --force_removal When doing an uninstall of a subproject, remove it's
|
37
|
+
files and subfolders, too.
|
38
|
+
-w, --workdir DIR The working directory to execute commands from. Use
|
39
|
+
this if for some reason you cannot execute ext from
|
40
|
+
the main project's directory (or if it's just
|
41
|
+
inconvenient, such as in a script or in a Capistrano
|
42
|
+
task)
|
43
|
+
--help does the same as 'ext help' If you use this with a
|
44
|
+
command it will ignore the command and run help
|
45
|
+
instead.
|
46
|
+
--version Displays the version number of externals and then
|
47
|
+
exits. Same as 'ext version'
|
48
|
+
|
40
49
|
|
41
50
|
|
42
51
|
Commands that apply to the main project or the .externals file:
|
43
|
-
freeze, help, init, install, touch_emptydirs, uninstall, update_ignore,
|
52
|
+
freeze, help, init, install, switch, touch_emptydirs, uninstall, update_ignore, version
|
44
53
|
|
45
54
|
freeze Usage: ext freeze project [REVISION]
|
46
55
|
|
@@ -65,6 +74,11 @@ install Usage: ext install <repository> [-b <branch>] [path]
|
|
65
74
|
type is not obvious from the repository URL, use the --scm,
|
66
75
|
--git, or --svn flags.
|
67
76
|
|
77
|
+
switch Usage: ext switch <branch_name>
|
78
|
+
Changes to the named branch <branch_name> and updates any
|
79
|
+
subprojects and applies any changes that have been made to the
|
80
|
+
.externals file.
|
81
|
+
|
68
82
|
touch_emptydirs Recurses through all directories from the
|
69
83
|
top and adds a .emptydir file to any empty directories it
|
70
84
|
comes across. Useful for dealing with SCMs that refuse to
|
@@ -166,7 +180,33 @@ This will pass "--non-interactive --trust-certid" to all co calls dealing with
|
|
166
180
|
subversion projects, regardless of which project it is.
|
167
181
|
|
168
182
|
|
183
|
+
BRANCHES WITH SUBVERSION
|
184
|
+
------------------------
|
185
|
+
Subversion does not have a branch feature. Instead, subversion users use the copy
|
186
|
+
feature to copy directories within the repository (branch), a switch command to switch the
|
187
|
+
URL that the working directory points at (checkout), and a merge feature to merge
|
188
|
+
directories (merge).
|
189
|
+
|
190
|
+
ext handles branches in Subversion by splitting the URL given by 'svn info' into
|
191
|
+
a repository part and a branch part.
|
192
|
+
|
193
|
+
If you find yourself doing this:
|
194
|
+
|
195
|
+
ext checkout svn+ssh://someserver/somepath/repository/branches/new_feature
|
196
|
+
|
197
|
+
Then you almost certainly mean to do this:
|
198
|
+
|
199
|
+
ext checkout svn+ssh://someserver/somepath/repository -b branches/new_feature
|
200
|
+
|
201
|
+
For ext to use branch-related features with Subversion repositories, the repository
|
202
|
+
must be known. For subprojects this is never a problem, but for the main project,
|
203
|
+
it's important that there is a repository field under [.] in the .externals file.
|
204
|
+
This can be accomplished by using -b with commands like 'ext checkout' and 'ext init'
|
205
|
+
or by manually editing the .externals file.
|
206
|
+
|
169
207
|
|
208
|
+
Copyright Information
|
209
|
+
---------------------
|
170
210
|
The externals project is copyright 2008 by Miles Georgi, nopugs.com, azimux.com
|
171
211
|
and is released under the MIT license.
|
172
212
|
|
@@ -134,6 +134,14 @@ module Externals
|
|
134
134
|
sections << Section.new("[#{title.to_s}]", "")
|
135
135
|
end
|
136
136
|
|
137
|
+
def removed_project_paths other_config
|
138
|
+
all_paths - other_config.all_paths
|
139
|
+
end
|
140
|
+
|
141
|
+
def all_paths
|
142
|
+
sections.map(&:title)
|
143
|
+
end
|
144
|
+
|
137
145
|
def self.new_empty
|
138
146
|
new nil, true
|
139
147
|
end
|
data/lib/externals/ext.rb
CHANGED
@@ -5,7 +5,7 @@ require 'externals/command'
|
|
5
5
|
require 'externals/extensions/symbol'
|
6
6
|
|
7
7
|
module Externals
|
8
|
-
VERSION = '1.0.
|
8
|
+
VERSION = '1.0.3'
|
9
9
|
PROJECT_TYPES_DIRECTORY = File.join(File.dirname(__FILE__), '..', 'externals','project_types')
|
10
10
|
|
11
11
|
# Full commands operate on the main project as well as the externals
|
@@ -51,6 +51,10 @@ module Externals
|
|
51
51
|
feature offered by the SCM of the main project. If the SCM
|
52
52
|
type is not obvious from the repository URL, use the --scm,
|
53
53
|
--git, or --svn flags."],
|
54
|
+
[:switch, "ext switch <branch_name>",
|
55
|
+
"Changes to the named branch <branch_name> and updates any
|
56
|
+
subprojects and applies any changes that have been made to the
|
57
|
+
.externals file."],
|
54
58
|
[:touch_emptydirs, "Recurses through all directories from the
|
55
59
|
top and adds a .emptydir file to any empty directories it
|
56
60
|
comes across. Useful for dealing with SCMs that refuse to
|
@@ -75,20 +79,19 @@ module Externals
|
|
75
79
|
|
76
80
|
COMMANDS = FULL_COMMANDS + SHORT_COMMANDS + MAIN_COMMANDS
|
77
81
|
|
82
|
+
Dir.entries(File.join(File.dirname(__FILE__), 'extensions')).each do |extension|
|
83
|
+
require "externals/extensions/#{extension}" if extension =~ /.rb$/
|
84
|
+
end
|
78
85
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
|
84
|
-
Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','scms')).each do |project|
|
85
|
-
require "externals/scms/#{project}" if project =~ /_project.rb$/
|
86
|
-
end
|
86
|
+
Dir.entries(File.join(File.dirname(__FILE__), '..', 'externals','scms')).each do |project|
|
87
|
+
require "externals/scms/#{project}" if project =~ /_project.rb$/
|
88
|
+
end
|
87
89
|
|
88
|
-
|
89
|
-
|
90
|
-
|
90
|
+
Dir.entries(PROJECT_TYPES_DIRECTORY).each do |type|
|
91
|
+
require File.join(PROJECT_TYPES_DIRECTORY, type) if type =~ /\.rb$/
|
92
|
+
end
|
91
93
|
|
94
|
+
class Ext
|
92
95
|
attr_accessor :path_calculator
|
93
96
|
|
94
97
|
def self.project_types
|
@@ -114,35 +117,52 @@ module Externals
|
|
114
117
|
end
|
115
118
|
|
116
119
|
def self.new_opts main_options, sub_options
|
117
|
-
opts = OptionParser.new
|
118
|
-
|
119
|
-
|
120
|
+
opts = OptionParser.new(
|
121
|
+
"ext [OPTIONS] <command> [repository] [-b <branch>] [path]"
|
122
|
+
)
|
123
|
+
opts.summary_indent = ' '
|
124
|
+
opts.summary_width = 24
|
125
|
+
summary_width = 53
|
120
126
|
|
121
127
|
project_classes.each do |project_class|
|
122
|
-
project_class.fill_in_opts(opts, main_options, sub_options
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
opts.on("--
|
132
|
-
String
|
133
|
-
|
128
|
+
project_class.fill_in_opts(opts, main_options, sub_options,
|
129
|
+
:summary_width => summary_width)
|
130
|
+
end
|
131
|
+
|
132
|
+
opts.on("--type TYPE", "-t TYPE",
|
133
|
+
String,
|
134
|
+
*"The type of project the main project is.
|
135
|
+
For example, 'rails'.".lines_by_width(summary_width)
|
136
|
+
) {|type| sub_options[:scm] = main_options[:type] = type}
|
137
|
+
opts.on("--scm SCM", "-s SCM",
|
138
|
+
String,
|
139
|
+
*"The SCM used to manage the main project. For example, '--scm svn'.".lines_by_width(summary_width)
|
140
|
+
) {|scm| sub_options[:scm] = main_options[:scm] = scm}
|
141
|
+
opts.on("--branch BRANCH", "-b BRANCH",
|
142
|
+
String,
|
143
|
+
*"The branch you want the
|
144
|
+
subproject to checkout when doing 'ext install'".lines_by_width(summary_width)
|
145
|
+
) {|branch| sub_options[:branch] = main_options[:branch] = branch}
|
146
|
+
opts.on("--force_removal", "-f",
|
147
|
+
String,
|
148
|
+
*"When doing an uninstall of a subproject,
|
149
|
+
remove it's files and subfolders, too.".lines_by_width(summary_width)
|
150
|
+
) {|branch| sub_options[:force_removal] = true}
|
151
|
+
opts.on("--workdir DIR", "-w DIR", String, *"The working directory to execute commands from. Use this if for some reason you
|
134
152
|
cannot execute ext from the main project's directory (or if it's just inconvenient, such as in a script
|
135
|
-
or in a Capistrano task)"
|
136
|
-
String) {|dir|
|
153
|
+
or in a Capistrano task)".lines_by_width(summary_width)) {|dir|
|
137
154
|
raise "No such directory: #{dir}" unless File.exists?(dir) && File.directory?(dir)
|
138
155
|
main_options[:workdir] = dir
|
139
156
|
}
|
140
|
-
opts.on(
|
141
|
-
|
142
|
-
|
143
|
-
|
157
|
+
opts.on(
|
158
|
+
"--help", *"does the same as 'ext help' If you use this with a command
|
159
|
+
it will ignore the command and run help instead.".lines_by_width(summary_width)
|
160
|
+
) {main_options[:help] = true}
|
161
|
+
opts.on("--version", *"Displays the version number of externals and then exits.
|
162
|
+
Same as 'ext version'".lines_by_width(summary_width)) {
|
144
163
|
main_options[:version] = true
|
145
164
|
}
|
165
|
+
opts
|
146
166
|
end
|
147
167
|
|
148
168
|
def self.run *arguments
|
@@ -539,6 +559,70 @@ by creating the .externals file manually"
|
|
539
559
|
self.class.new({}).st [], {} #args, options
|
540
560
|
end
|
541
561
|
|
562
|
+
def switch args, options
|
563
|
+
branch = args[0]
|
564
|
+
|
565
|
+
options ||= {}
|
566
|
+
scm = options[:scm]
|
567
|
+
|
568
|
+
if !scm
|
569
|
+
scm ||= configuration['.']
|
570
|
+
scm &&= scm['scm']
|
571
|
+
end
|
572
|
+
|
573
|
+
if !scm
|
574
|
+
possible_project_classes = self.class.project_classes.select do |project_class|
|
575
|
+
project_class.detected?
|
576
|
+
end
|
577
|
+
|
578
|
+
raise "Could not determine this projects scm" if possible_project_classes.empty?
|
579
|
+
if possible_project_classes.size > 1
|
580
|
+
raise "This project appears to be managed by multiple SCMs: #{
|
581
|
+
possible_project_classes.map(&:to_s).join(',')}
|
582
|
+
Please explicitly declare the SCM (by using --git or --svn, or,
|
583
|
+
by creating the .externals file manually"
|
584
|
+
end
|
585
|
+
|
586
|
+
scm = possible_project_classes.first.scm
|
587
|
+
end
|
588
|
+
|
589
|
+
unless scm
|
590
|
+
raise "You need to either specify the scm as the first line in .externals, or use an option to specify it
|
591
|
+
(such as --git or --svn)"
|
592
|
+
end
|
593
|
+
|
594
|
+
old_config = configuration
|
595
|
+
project = main_project
|
596
|
+
project.scm ||= scm
|
597
|
+
|
598
|
+
if project.current_branch == branch
|
599
|
+
puts "Already on branch #{branch}"
|
600
|
+
else
|
601
|
+
project.switch branch, options
|
602
|
+
project.up
|
603
|
+
|
604
|
+
reload_configuration
|
605
|
+
|
606
|
+
#update subprojects
|
607
|
+
self.class.new({}).up [], {} #args, options
|
608
|
+
|
609
|
+
removed_project_paths = old_config.removed_project_paths(
|
610
|
+
configuration
|
611
|
+
).select{|path| File.exists?(path)}
|
612
|
+
|
613
|
+
if !removed_project_paths.empty?
|
614
|
+
puts "WARNING: The following subprojects are no longer being maintained in the
|
615
|
+
.externals file. You might want to remove them. You can copy and paste the
|
616
|
+
commands below if you actually wish to delete them."
|
617
|
+
removed_project_paths.each do |path|
|
618
|
+
if File.exists? path
|
619
|
+
puts " rm -r #{path}"
|
620
|
+
end
|
621
|
+
end
|
622
|
+
end
|
623
|
+
end
|
624
|
+
end
|
625
|
+
|
542
626
|
def update args, options
|
543
627
|
options ||= {}
|
544
628
|
#repository = args[0]
|
@@ -635,11 +719,23 @@ Please use the --type option to tell ext which to use."
|
|
635
719
|
end
|
636
720
|
|
637
721
|
config = Configuration::Configuration.new_empty
|
638
|
-
|
639
722
|
raise ".externals already exists" if File.exists?('.externals')
|
640
723
|
|
641
724
|
config.add_empty_section '.'
|
642
725
|
|
726
|
+
# If we are using subversion, we should warn about not setting a branch
|
727
|
+
if scm == "svn"
|
728
|
+
if options[:branch]
|
729
|
+
config['.'][:repository] = SvnProject.extract_repository(
|
730
|
+
SvnProject.info_url,
|
731
|
+
options[:branch]
|
732
|
+
)
|
733
|
+
elsif args[0]
|
734
|
+
config['.'][:repository] = args[0].strip
|
735
|
+
else
|
736
|
+
end
|
737
|
+
end
|
738
|
+
|
643
739
|
config['.'][:scm] = scm
|
644
740
|
config['.'][:type] = type if type
|
645
741
|
|
@@ -2,8 +2,38 @@ String.class_eval do
|
|
2
2
|
def cap_first
|
3
3
|
"#{self[0].chr.upcase}#{self[1..(self.length - 1)]}"
|
4
4
|
end
|
5
|
-
|
5
|
+
|
6
6
|
def classify
|
7
7
|
split('_').map(&:cap_first).join
|
8
8
|
end
|
9
|
+
|
10
|
+
#avoid collision
|
11
|
+
raise unless instance_methods.include?("lines")
|
12
|
+
raise if instance_methods.include?("lines_by_width")
|
13
|
+
def lines_by_width(width = 32)
|
14
|
+
width ||= 32
|
15
|
+
lines = []
|
16
|
+
string = gsub(/\s+/, ' ')
|
17
|
+
while string.size > 0
|
18
|
+
if string.size <= width
|
19
|
+
lines << string
|
20
|
+
string = ""
|
21
|
+
else
|
22
|
+
index = string[0, width + 1].rindex(/\s/)
|
23
|
+
unless index
|
24
|
+
# let's find the first space we can.
|
25
|
+
index = string.index(/\s/)
|
26
|
+
end
|
27
|
+
if index
|
28
|
+
lines << string[0, index]
|
29
|
+
string = string[(index + 1)..-1]
|
30
|
+
else
|
31
|
+
lines << string
|
32
|
+
string = ""
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
lines
|
38
|
+
end
|
9
39
|
end
|
data/lib/externals/project.rb
CHANGED
@@ -56,6 +56,10 @@ module Externals
|
|
56
56
|
self.class.default_branch
|
57
57
|
end
|
58
58
|
|
59
|
+
def switch branch_name, options = {}
|
60
|
+
raise "subclass responsibility"
|
61
|
+
end
|
62
|
+
|
59
63
|
def initialize hash
|
60
64
|
raise "Abstract class" if self.class == Project
|
61
65
|
raise "expected hash" unless hash.is_a? Hash
|
@@ -22,6 +22,9 @@ module Externals
|
|
22
22
|
|
23
23
|
puts(gitclonecmd = "git #{opts} clone \"#{repository}\" #{dest}")
|
24
24
|
puts `#{gitclonecmd}`
|
25
|
+
unless $? == 0
|
26
|
+
raise "git clone of #{repository} failed."
|
27
|
+
end
|
25
28
|
|
26
29
|
change_to_branch_revision(command)
|
27
30
|
end
|
@@ -86,6 +89,33 @@ module Externals
|
|
86
89
|
end
|
87
90
|
end
|
88
91
|
|
92
|
+
def switch branch_name, options = {}
|
93
|
+
cb = current_branch
|
94
|
+
if cb == branch_name
|
95
|
+
puts "Already on branch #{branch_name}"
|
96
|
+
else
|
97
|
+
# This allows the main project to be checked out to a directory
|
98
|
+
# that doesn't match it's name.
|
99
|
+
Dir.chdir path do
|
100
|
+
# let's see if the branch exists in the remote repository
|
101
|
+
# and if not, fetch it.
|
102
|
+
if !branch_exists("origin/#{branch_name}")
|
103
|
+
puts `git #{scm_opts} fetch`
|
104
|
+
end
|
105
|
+
|
106
|
+
# if the local branch doens't exist, add --track -b
|
107
|
+
if branch_exists(branch_name)
|
108
|
+
puts `git #{scm_opts} checkout #{branch_name}`
|
109
|
+
else
|
110
|
+
puts `git #{resolve_opts("co")} checkout --track -b #{branch_name} origin/#{branch_name}`
|
111
|
+
end
|
112
|
+
unless $? == 0
|
113
|
+
raise "Could not checkout origin/#{branch_name}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
89
119
|
def ex *args
|
90
120
|
if path != '.'
|
91
121
|
(rmdircmd = "rmdir #{path}")
|
@@ -131,9 +161,12 @@ module Externals
|
|
131
161
|
path =~ /^git:/ || path =~ /.git$/
|
132
162
|
end
|
133
163
|
|
134
|
-
def self.fill_in_opts opts, main_options, sub_options
|
135
|
-
opts.on("--git", "-g",
|
136
|
-
Integer
|
164
|
+
def self.fill_in_opts opts, main_options, sub_options, options
|
165
|
+
opts.on("--git", "-g",
|
166
|
+
Integer,
|
167
|
+
*"same as '--scm git' Uses git to
|
168
|
+
checkout/export the main project".lines_by_width(options[:summary_width])
|
169
|
+
) {sub_options[:scm] = main_options[:scm] = 'git'}
|
137
170
|
end
|
138
171
|
|
139
172
|
def self.detected?
|
@@ -150,7 +183,7 @@ module Externals
|
|
150
183
|
text.split(/\n/).detect {|r| r.strip == path.strip}
|
151
184
|
end
|
152
185
|
|
153
|
-
def ignore_text(path)
|
186
|
+
def ignore_text(path = nil)
|
154
187
|
return '' unless File.exists? '.gitignore'
|
155
188
|
retval = ''
|
156
189
|
open('.gitignore') do |f|
|