ext 1.0.2 → 1.0.3
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/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|
|