pluginfactory 1.0.4 → 1.0.5
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 +70 -319
- data/LICENSE +1 -1
- data/README +0 -4
- data/Rakefile +90 -68
- data/lib/pluginfactory.rb +7 -11
- data/rake/dependencies.rb +1 -1
- data/rake/helpers.rb +75 -48
- data/rake/hg.rb +261 -0
- data/rake/manual.rb +74 -76
- data/rake/packaging.rb +40 -8
- data/rake/publishing.rb +70 -69
- data/rake/rdoc.rb +11 -26
- data/rake/style.rb +1 -1
- data/rake/svn.rb +582 -516
- data/rake/testing.rb +6 -21
- data/rake/win32.rb +119 -23
- data/spec/pluginfactory_spec.rb +112 -124
- metadata +18 -129
data/rake/packaging.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
#
|
2
2
|
# Packaging Rake Tasks
|
3
|
-
|
3
|
+
|
4
4
|
#
|
5
5
|
|
6
6
|
require 'rbconfig'
|
7
|
+
require 'pathname'
|
7
8
|
require 'rake/packagetask'
|
8
9
|
require 'rake/gempackagetask'
|
9
10
|
|
11
|
+
require Pathname( __FILE__ ).dirname.expand_path + 'hg.rb'
|
12
|
+
|
10
13
|
include Config
|
11
14
|
|
12
15
|
### Task: gem
|
@@ -16,8 +19,7 @@ Rake::PackageTask.new( PKG_NAME, PKG_VERSION ) do |task|
|
|
16
19
|
task.need_tar_bz2 = true
|
17
20
|
task.need_zip = true
|
18
21
|
task.package_dir = PKGDIR.to_s
|
19
|
-
task.package_files = RELEASE_FILES.
|
20
|
-
collect {|f| f.relative_path_from(BASEDIR).to_s }
|
22
|
+
task.package_files = RELEASE_FILES.collect {|f| f.to_s }
|
21
23
|
end
|
22
24
|
task :package => [:gem]
|
23
25
|
|
@@ -36,14 +38,31 @@ file gempath.to_s => [PKGDIR.to_s] + GEMSPEC.files do
|
|
36
38
|
end
|
37
39
|
end
|
38
40
|
|
41
|
+
|
42
|
+
prerelease_gempath = PKGDIR + SNAPSHOT_GEM_NAME
|
43
|
+
|
44
|
+
desc "Build a pre-release RubyGem package"
|
45
|
+
task :prerelease_gem => prerelease_gempath.to_s
|
46
|
+
file prerelease_gempath.to_s => [PKGDIR.to_s] + GEMSPEC.files do
|
47
|
+
when_writing( "Creating prerelease GEM" ) do
|
48
|
+
gemspec = GEMSPEC.clone
|
49
|
+
gemspec.version = Gem::Version.create( "%s.%s" % [GEMSPEC.version, PKG_BUILD] )
|
50
|
+
Gem::Builder.new( gemspec ).build
|
51
|
+
verbose( true ) do
|
52
|
+
mv SNAPSHOT_GEM_NAME, prerelease_gempath
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
39
58
|
### Task: install
|
40
59
|
desc "Install #{PKG_NAME} as a conventional library"
|
41
|
-
task :install do
|
60
|
+
task :install => "spec:quiet" do
|
42
61
|
log "Installing #{PKG_NAME} as a conventional library"
|
43
62
|
sitelib = Pathname.new( CONFIG['sitelibdir'] )
|
44
63
|
sitearch = Pathname.new( CONFIG['sitearchdir'] )
|
45
64
|
Dir.chdir( LIBDIR ) do
|
46
|
-
LIB_FILES.each do |libfile|
|
65
|
+
LIB_FILES.collect {|path| Pathname(path) }.each do |libfile|
|
47
66
|
relpath = libfile.relative_path_from( LIBDIR )
|
48
67
|
target = sitelib + relpath
|
49
68
|
FileUtils.mkpath target.dirname,
|
@@ -53,8 +72,10 @@ task :install do
|
|
53
72
|
end
|
54
73
|
end
|
55
74
|
if EXTDIR.exist?
|
75
|
+
trace " looking for a binary extension (%s)" % [ EXTDIR + "*.#{Config::CONFIG['DLEXT']}" ]
|
56
76
|
Dir.chdir( EXTDIR ) do
|
57
|
-
Pathname.glob(
|
77
|
+
Pathname.glob( "*.#{Config::CONFIG['DLEXT']}" ) do |dl|
|
78
|
+
trace " found: #{dl}"
|
58
79
|
target = sitearch + dl.basename
|
59
80
|
FileUtils.install dl, target,
|
60
81
|
:mode => 0755, :verbose => true, :noop => $dryrun
|
@@ -82,7 +103,7 @@ task :uninstall do
|
|
82
103
|
sitearch = Pathname.new( CONFIG['sitearchdir'] )
|
83
104
|
|
84
105
|
Dir.chdir( LIBDIR ) do
|
85
|
-
LIB_FILES.each do |libfile|
|
106
|
+
LIB_FILES.collect {|path| Pathname(path) }.each do |libfile|
|
86
107
|
relpath = libfile.relative_path_from( LIBDIR )
|
87
108
|
target = sitelib + relpath
|
88
109
|
FileUtils.rm_f target, :verbose => true, :noop => $dryrun
|
@@ -91,8 +112,10 @@ task :uninstall do
|
|
91
112
|
end
|
92
113
|
end
|
93
114
|
if EXTDIR.exist?
|
115
|
+
trace " looking for a binary extension (%s)" % [ EXTDIR + "*.#{Config::CONFIG['DLEXT']}" ]
|
94
116
|
Dir.chdir( EXTDIR ) do
|
95
|
-
Pathname.glob(
|
117
|
+
Pathname.glob( "*.#{Config::CONFIG['DLEXT']}" ) do |dl|
|
118
|
+
trace " found: #{dl}"
|
96
119
|
target = sitearch + dl.basename
|
97
120
|
FileUtils.rm target, :verbose => true, :noop => $dryrun
|
98
121
|
end
|
@@ -110,3 +133,12 @@ end
|
|
110
133
|
|
111
134
|
|
112
135
|
|
136
|
+
desc "Add development depdendencies to the gemspec -- this is meant to be chained " +
|
137
|
+
"together with :gem"
|
138
|
+
task :include_dev_dependencies do
|
139
|
+
DEVELOPMENT_DEPENDENCIES.each do |name, version|
|
140
|
+
version = '>= 0' if version.length.zero?
|
141
|
+
GEMSPEC.add_development_dependency( name, version )
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
data/rake/publishing.rb
CHANGED
@@ -26,8 +26,8 @@ class Net::SMTP
|
|
26
26
|
return self
|
27
27
|
end
|
28
28
|
end
|
29
|
-
|
30
|
-
|
29
|
+
|
30
|
+
|
31
31
|
#######
|
32
32
|
private
|
33
33
|
#######
|
@@ -100,7 +100,7 @@ begin
|
|
100
100
|
|
101
101
|
|
102
102
|
namespace :release do
|
103
|
-
task :default => [
|
103
|
+
task :default => [ :prep_release, :upload, :publish, :announce ]
|
104
104
|
|
105
105
|
desc "Re-publish the release with the current version number"
|
106
106
|
task :rerelease => [ :upload, :publish, :announce ]
|
@@ -111,31 +111,28 @@ begin
|
|
111
111
|
$publish_privately = true
|
112
112
|
Rake::Task['release:rerelease'].invoke
|
113
113
|
end
|
114
|
-
|
114
|
+
|
115
115
|
|
116
116
|
desc "Generate the release notes"
|
117
117
|
task :notes => [RELEASE_NOTES_FILE]
|
118
118
|
file RELEASE_NOTES_FILE do |task|
|
119
|
-
|
120
|
-
|
121
|
-
trace "Last release tag is: %p" % [ last_rel_tag ]
|
122
|
-
start = get_last_changed_rev( last_rel_tag ) || 1
|
123
|
-
trace "Starting rev is: %p" % [ start ]
|
124
|
-
log_output = make_svn_log( '.', start, 'HEAD' )
|
119
|
+
last_tag = MercurialHelpers.get_tags.grep( /\d+\.\d+\.\d+/ ).
|
120
|
+
collect {|ver| vvec(ver) }.sort.last.unpack( 'N*' ).join('.')
|
125
121
|
|
126
122
|
File.open( task.name, File::WRONLY|File::TRUNC|File::CREAT ) do |fh|
|
127
|
-
fh.
|
123
|
+
fh.puts "Release Notes for #{PKG_VERSION}",
|
124
|
+
"--------------------------------", '', ''
|
128
125
|
end
|
129
126
|
|
130
127
|
edit task.name
|
131
128
|
end
|
132
129
|
CLOBBER.include( RELEASE_NOTES_FILE )
|
133
|
-
|
134
|
-
|
130
|
+
|
131
|
+
|
135
132
|
desc "Upload project documentation and packages to #{PROJECT_HOST}"
|
136
133
|
task :upload => [ :upload_docs, :upload_packages ]
|
137
134
|
task :project => :upload # the old name
|
138
|
-
|
135
|
+
|
139
136
|
desc "Publish the project docs to #{PROJECT_HOST}"
|
140
137
|
task :upload_docs => [ :rdoc ] do
|
141
138
|
when_writing( "Publishing docs to #{PROJECT_SCPDOCURL}" ) do
|
@@ -172,7 +169,7 @@ begin
|
|
172
169
|
== Installation
|
173
170
|
|
174
171
|
Via gems:
|
175
|
-
|
172
|
+
|
176
173
|
$ sudo gem install #{GEMSPEC.name}
|
177
174
|
|
178
175
|
or from source:
|
@@ -185,7 +182,7 @@ begin
|
|
185
182
|
== Changes
|
186
183
|
#{relnotes}
|
187
184
|
}.gsub( /^\t+/, '' )
|
188
|
-
|
185
|
+
|
189
186
|
File.open( task.name, File::WRONLY|File::TRUNC|File::CREAT ) do |fh|
|
190
187
|
fh.print( announce_body )
|
191
188
|
end
|
@@ -193,8 +190,8 @@ begin
|
|
193
190
|
edit task.name
|
194
191
|
end
|
195
192
|
CLOBBER.include( RELEASE_ANNOUNCE_FILE )
|
196
|
-
|
197
|
-
|
193
|
+
|
194
|
+
|
198
195
|
desc 'Send out a release announcement'
|
199
196
|
task :announce => [RELEASE_ANNOUNCE_FILE] do
|
200
197
|
email = TMail::Mail.new
|
@@ -217,13 +214,13 @@ begin
|
|
217
214
|
puts '---',
|
218
215
|
email.to_s,
|
219
216
|
'---'
|
220
|
-
|
217
|
+
|
221
218
|
ask_for_confirmation( "Will send via #{SMTP_HOST}." ) do
|
222
219
|
pwent = Etc.getpwuid( Process.euid )
|
223
220
|
curuser = pwent ? pwent.name : 'unknown'
|
224
221
|
username = prompt_with_default( "SMTP user", curuser )
|
225
222
|
password = prompt_for_password()
|
226
|
-
|
223
|
+
|
227
224
|
trace "Creating SMTP connection to #{SMTP_HOST}:#{SMTP_PORT}"
|
228
225
|
smtp = Net::SMTP.new( SMTP_HOST, SMTP_PORT )
|
229
226
|
smtp.set_debug_output( $stdout )
|
@@ -237,73 +234,77 @@ begin
|
|
237
234
|
trace "done."
|
238
235
|
end
|
239
236
|
end
|
240
|
-
|
241
|
-
|
237
|
+
|
238
|
+
|
242
239
|
desc 'Publish the new release to RubyForge'
|
243
240
|
task :publish => [:clean, :package, :notes] do |task|
|
244
241
|
project = GEMSPEC.rubyforge_project
|
245
242
|
|
246
|
-
|
247
|
-
|
248
|
-
rf.configure
|
249
|
-
|
250
|
-
group_id = rf.autoconfig['group_ids'][RUBYFORGE_GROUP] or
|
251
|
-
fail "Your configuration doesn't have a group id for '#{RUBYFORGE_GROUP}'"
|
252
|
-
|
253
|
-
# If this project doesn't yet exist, create it
|
254
|
-
unless rf.autoconfig['package_ids'].key?( project )
|
255
|
-
ask_for_confirmation( "Package '#{project}' doesn't exist on RubyForge. Create it?" ) do
|
256
|
-
log "Creating new package '#{project}'"
|
257
|
-
rf.create_package( group_id, project )
|
258
|
-
end
|
259
|
-
end
|
260
|
-
|
261
|
-
package_id = rf.autoconfig['package_ids'][ project ]
|
262
|
-
|
263
|
-
# Make sure this release doesn't already exist
|
264
|
-
releases = rf.autoconfig['release_ids']
|
265
|
-
if releases.key?( GEMSPEC.name ) && releases[ GEMSPEC.name ].key?( PKG_VERSION )
|
266
|
-
log "Rubyforge seems to already have #{ PKG_FILE_NAME }"
|
243
|
+
if $publish_privately
|
244
|
+
log "Skipping push of release files to RubyForge"
|
267
245
|
else
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
246
|
+
rf = RubyForge.new
|
247
|
+
log "Loading RubyForge config"
|
248
|
+
rf.configure
|
249
|
+
|
250
|
+
group_id = rf.autoconfig['group_ids'][RUBYFORGE_GROUP] or
|
251
|
+
fail "Your configuration doesn't have a group id for '#{RUBYFORGE_GROUP}'"
|
252
|
+
|
253
|
+
# If this project doesn't yet exist, create it
|
254
|
+
unless rf.autoconfig['package_ids'].key?( project )
|
255
|
+
ask_for_confirmation( "Package '#{project}' doesn't exist on RubyForge. Create it?" ) do
|
256
|
+
log "Creating new package '#{project}'"
|
257
|
+
rf.create_package( group_id, project )
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
package_id = rf.autoconfig['package_ids'][ project ]
|
262
|
+
|
263
|
+
# Make sure this release doesn't already exist
|
264
|
+
releases = rf.autoconfig['release_ids']
|
265
|
+
if releases.key?( GEMSPEC.name ) && releases[ GEMSPEC.name ].key?( PKG_VERSION )
|
266
|
+
log "Rubyforge seems to already have #{ PKG_FILE_NAME }"
|
267
|
+
else
|
268
|
+
config = rf.userconfig or
|
269
|
+
fail "You apparently haven't set up your RubyForge credentials on this machine."
|
270
|
+
config['release_notes'] = GEMSPEC.description
|
271
|
+
config['release_changes'] = File.read( RELEASE_NOTES_FILE )
|
272
|
+
|
273
|
+
files = FileList[ PKGDIR + GEM_FILE_NAME ]
|
274
|
+
files.include PKGDIR + "#{PKG_FILE_NAME}.tar.gz"
|
275
|
+
files.include PKGDIR + "#{PKG_FILE_NAME}.tar.bz2"
|
276
|
+
files.include PKGDIR + "#{PKG_FILE_NAME}.zip"
|
277
|
+
|
278
|
+
log "Releasing #{PKG_FILE_NAME}"
|
279
|
+
when_writing do
|
280
|
+
log "Publishing to RubyForge: \n",
|
281
|
+
"\tproject: #{RUBYFORGE_GROUP}\n",
|
282
|
+
"\tpackage: #{PKG_NAME.downcase}\n",
|
283
|
+
"\tpackage version: #{PKG_VERSION}\n",
|
284
|
+
"\tfiles: " + files.collect {|f| f.to_s }.join(', ') + "\n"
|
285
|
+
|
286
|
+
ask_for_confirmation( "Publish to RubyForge?" ) do
|
287
|
+
log 'Logging in...'
|
288
|
+
rf.login
|
289
|
+
log "Adding the new release to the '#{project}' project"
|
290
|
+
rf.add_release( group_id, package_id, PKG_VERSION, *files )
|
291
|
+
end
|
291
292
|
end
|
292
293
|
end
|
293
294
|
end
|
294
295
|
end
|
295
296
|
end
|
296
|
-
|
297
|
+
|
297
298
|
rescue LoadError => err
|
298
299
|
if !Object.const_defined?( :Gem )
|
299
300
|
require 'rubygems'
|
300
301
|
retry
|
301
302
|
end
|
302
|
-
|
303
|
+
|
303
304
|
task :no_release_tasks do
|
304
305
|
fail "Release tasks not defined: #{err.message}"
|
305
306
|
end
|
306
|
-
|
307
|
+
|
307
308
|
task :release => :no_release_tasks
|
308
309
|
task "release:announce" => :no_release_tasks
|
309
310
|
task "release:publish" => :no_release_tasks
|
data/rake/rdoc.rb
CHANGED
@@ -1,45 +1,30 @@
|
|
1
1
|
#
|
2
2
|
# RDoc Rake tasks
|
3
|
-
|
3
|
+
|
4
4
|
#
|
5
5
|
|
6
|
+
gem 'rdoc', '>= 2.4.3'
|
7
|
+
|
8
|
+
require 'rubygems'
|
6
9
|
require 'rdoc/rdoc'
|
7
10
|
require 'rake/clean'
|
8
|
-
|
9
|
-
|
10
|
-
if RDoc::RDoc::GENERATORS.key?( 'darkfish' )
|
11
|
-
$have_darkfish = true
|
12
|
-
else
|
13
|
-
trace "No darkfish generator."
|
14
|
-
$have_darkfish = false
|
15
|
-
end
|
16
|
-
|
11
|
+
require 'rdoc/task'
|
17
12
|
|
18
13
|
# Append docs/lib to the load path if it exists for a locally-installed Darkfish
|
19
14
|
DOCSLIB = DOCSDIR + 'lib'
|
20
15
|
$LOAD_PATH.unshift( DOCSLIB.to_s ) if DOCSLIB.exist?
|
21
16
|
|
22
17
|
# Make relative string paths of all the stuff we need to generate docs for
|
23
|
-
DOCFILES = LIB_FILES + EXT_FILES + GEMSPEC.extra_rdoc_files
|
18
|
+
DOCFILES = Rake::FileList[ LIB_FILES + EXT_FILES + GEMSPEC.extra_rdoc_files ]
|
24
19
|
|
25
20
|
|
26
21
|
directory RDOCDIR.to_s
|
27
22
|
CLOBBER.include( RDOCDIR )
|
28
23
|
|
29
24
|
desc "Build API documentation in #{RDOCDIR}"
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
trace "Building docs with arguments: %s" % [ args.join(' ') ]
|
37
|
-
RDoc::RDoc.new.document( args ) rescue nil
|
38
|
-
end
|
39
|
-
|
40
|
-
desc "Rebuild API documentation in #{RDOCDIR}"
|
41
|
-
task :rerdoc do
|
42
|
-
rm_r( RDOCDIR ) if RDOCDIR.exist?
|
43
|
-
Rake::Task[ :rdoc ].invoke
|
25
|
+
RDoc::Task.new do |task|
|
26
|
+
task.main = "README"
|
27
|
+
task.rdoc_files.include( DOCFILES )
|
28
|
+
task.rdoc_dir = RDOCDIR.to_s
|
29
|
+
task.options = RDOC_OPTIONS
|
44
30
|
end
|
45
|
-
|
data/rake/style.rb
CHANGED
data/rake/svn.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#
|
2
2
|
# Subversion Rake Tasks
|
3
|
-
|
3
|
+
|
4
4
|
#
|
5
5
|
# Authors:
|
6
6
|
# * Michael Granger <ged@FaerieMUD.org>
|
@@ -10,593 +10,659 @@ require 'pp'
|
|
10
10
|
require 'yaml'
|
11
11
|
require 'date'
|
12
12
|
require 'time'
|
13
|
+
require 'abbrev'
|
14
|
+
|
15
|
+
unless defined?( SVN_DOTDIR )
|
16
|
+
|
17
|
+
# Subversion constants -- directory names for releases and tags
|
18
|
+
SVN_TRUNK_DIR = 'trunk'
|
19
|
+
SVN_RELEASES_DIR = 'releases'
|
20
|
+
SVN_BRANCHES_DIR = 'branches'
|
21
|
+
SVN_TAGS_DIR = 'tags'
|
22
|
+
|
23
|
+
SVN_DOTDIR = BASEDIR + '.svn'
|
24
|
+
SVN_ENTRIES = SVN_DOTDIR + 'entries'
|
25
|
+
|
26
|
+
# Ignore .svn directories in the various FileLists used by the main Rakefile
|
27
|
+
[
|
28
|
+
BIN_FILES,
|
29
|
+
LIB_FILES,
|
30
|
+
EXT_FILES,
|
31
|
+
DATA_FILES,
|
32
|
+
SPEC_FILES,
|
33
|
+
TEST_FILES,
|
34
|
+
EXTRA_PKGFILES,
|
35
|
+
].each {|filelist| filelist.exclude(/\.svn/) }
|
36
|
+
|
37
|
+
# Strftime format for tags/releases
|
38
|
+
TAG_TIMESTAMP_FORMAT = '%Y%m%d-%H%M%S'
|
39
|
+
TAG_TIMESTAMP_PATTERN = /\d{4}\d{2}\d{2}-\d{6}/
|
40
|
+
|
41
|
+
RELEASE_VERSION_PATTERN = /\d+\.\d+\.\d+/
|
42
|
+
|
43
|
+
DEFAULT_KEYWORDS = %w[Date Rev Author URL Id]
|
44
|
+
KEYWORDED_FILEDIRS = %w[applets spec bin etc ext experiments examples lib misc docs]
|
45
|
+
KEYWORDED_FILEPATTERN = /
|
46
|
+
^(?:
|
47
|
+
(?:meta)?rakefile.* # Rakefiles
|
48
|
+
|
|
49
|
+
.*\.(?:rb|c|h|js|html|css|template|erb|page) # Source file extensions
|
50
|
+
|
|
51
|
+
readme|install|todo
|
52
|
+
)$/ix
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
###
|
57
|
+
### Subversion-specific Helpers
|
58
|
+
###
|
59
|
+
module SubversionHelpers
|
60
|
+
|
61
|
+
###############
|
62
|
+
module_function
|
63
|
+
###############
|
64
|
+
|
65
|
+
### Return a new tag for the given time
|
66
|
+
def make_new_tag( time=Time.now )
|
67
|
+
return time.strftime( TAG_TIMESTAMP_FORMAT )
|
68
|
+
end
|
13
69
|
|
14
|
-
# Strftime format for tags/releases
|
15
|
-
TAG_TIMESTAMP_FORMAT = '%Y%m%d-%H%M%S'
|
16
|
-
TAG_TIMESTAMP_PATTERN = /\d{4}\d{2}\d{2}-\d{6}/
|
17
|
-
|
18
|
-
RELEASE_VERSION_PATTERN = /\d+\.\d+\.\d+/
|
19
|
-
|
20
|
-
DEFAULT_EDITOR = 'vi'
|
21
|
-
DEFAULT_KEYWORDS = %w[Date Rev Author URL Id]
|
22
|
-
KEYWORDED_FILEDIRS = %w[applets spec bin etc ext experiments examples lib misc docs]
|
23
|
-
KEYWORDED_FILEPATTERN = /
|
24
|
-
^(?:
|
25
|
-
(?:meta)?rakefile.* # Rakefiles
|
26
|
-
|
|
27
|
-
.*\.(?:rb|c|h|js|html|css|template|erb|page) # Source file extensions
|
28
|
-
|
|
29
|
-
readme|install|todo
|
30
|
-
)$/ix
|
31
|
-
|
32
|
-
COMMIT_MSG_FILE = 'commit-msg.txt'
|
33
|
-
|
34
|
-
SVN_TRUNK_DIR = 'trunk' unless defined?( SVN_TRUNK_DIR )
|
35
|
-
SVN_RELEASES_DIR = 'branches' unless defined?( SVN_RELEASES_DIR )
|
36
|
-
SVN_BRANCHES_DIR = 'branches' unless defined?( SVN_BRANCHES_DIR )
|
37
|
-
SVN_TAGS_DIR = 'tags' unless defined?( SVN_TAGS_DIR )
|
38
|
-
|
39
|
-
FILE_INDENT = " " * 12
|
40
|
-
LOG_INDENT = " " * 3
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
###
|
45
|
-
### Subversion-specific Helpers
|
46
|
-
###
|
47
|
-
|
48
|
-
### Return a new tag for the given time
|
49
|
-
def make_new_tag( time=Time.now )
|
50
|
-
return time.strftime( TAG_TIMESTAMP_FORMAT )
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
### Get the subversion information for the current working directory as
|
55
|
-
### a hash.
|
56
|
-
def get_svn_info( dir='.' )
|
57
|
-
return {} unless File.directory?( File.join(dir, '.svn') )
|
58
|
-
info = IO.read( '|-' ) or exec 'svn', 'info', dir
|
59
|
-
return YAML.load( info ) # 'svn info' outputs valid YAML! Yay!
|
60
|
-
rescue NotImplementedError
|
61
|
-
trace "No fork(), proceeding without svn info..."
|
62
|
-
return {}
|
63
|
-
end
|
64
|
-
|
65
|
-
|
66
|
-
### Get a list of the objects registered with subversion under the specified directory and
|
67
|
-
### return them as an Array of Pathame objects.
|
68
|
-
def get_svn_filelist( dir='.' )
|
69
|
-
list = IO.read( '|-' ) or exec 'svn', 'st', '-v', '--ignore-externals', dir
|
70
|
-
|
71
|
-
# Split into lines, filter out the unknowns, and grab the filenames as Pathnames
|
72
|
-
# :FIXME: This will break if we ever put in a file with spaces in its name. This
|
73
|
-
# will likely be the least of our worries if we do so, however, so it's not worth
|
74
|
-
# the additional complexity to make it handle that case. If we do need that, there's
|
75
|
-
# always the --xml output for 'svn st'...
|
76
|
-
return list.split( $/ ).
|
77
|
-
reject {|line| line =~ /^(\?|(\s*|--- .*)$)/ }.
|
78
|
-
collect {|fn| Pathname(fn[/\S+$/]) }
|
79
|
-
end
|
80
|
-
|
81
|
-
### Return the URL to the repository root for the specified +dir+.
|
82
|
-
def get_svn_repo_root( dir='.' )
|
83
|
-
info = get_svn_info( dir )
|
84
|
-
return info['Repository Root']
|
85
|
-
end
|
86
|
-
|
87
|
-
|
88
|
-
### Return the Subversion URL to the given +dir+.
|
89
|
-
def get_svn_url( dir='.' )
|
90
|
-
info = get_svn_info( dir )
|
91
|
-
return info['URL']
|
92
|
-
end
|
93
|
-
|
94
|
-
|
95
|
-
### Return the path of the specified +dir+ under the svn root of the
|
96
|
-
### checkout.
|
97
|
-
def get_svn_path( dir='.' )
|
98
|
-
root = get_svn_repo_root( dir )
|
99
|
-
url = get_svn_url( dir )
|
100
|
-
|
101
|
-
return url.sub( root + '/', '' )
|
102
|
-
end
|
103
|
-
|
104
|
-
|
105
|
-
### Return the keywords for the specified array of +files+ as a Hash keyed by filename.
|
106
|
-
def get_svn_keyword_map( *files )
|
107
|
-
files.flatten!
|
108
|
-
files.push( '.' ) if files.empty?
|
109
|
-
|
110
|
-
cmd = ['svn', 'pg', 'svn:keywords', *files]
|
111
|
-
|
112
|
-
# trace "Executing: svn pg svn:keywords " + files.join(' ')
|
113
|
-
output = IO.read( '|-' ) or exec( 'svn', 'pg', 'svn:keywords', *files )
|
114
|
-
|
115
|
-
kwmap = {}
|
116
|
-
output.split( "\n" ).each do |line|
|
117
|
-
next if line !~ /\s+-\s+/
|
118
|
-
path, keywords = line.split( /\s+-\s+/, 2 )
|
119
|
-
kwmap[ path ] = keywords.split
|
120
|
-
end
|
121
|
-
|
122
|
-
return kwmap
|
123
|
-
end
|
124
|
-
|
125
|
-
|
126
|
-
### Return the latest revision number of the specified +dir+ as an Integer.
|
127
|
-
def get_svn_rev( dir='.' )
|
128
|
-
info = get_svn_info( dir )
|
129
|
-
return info['Revision']
|
130
|
-
end
|
131
|
-
|
132
|
-
|
133
|
-
### Return the latest revision number of the specified +dir+ as an Integer.
|
134
|
-
def get_last_changed_rev( dir='.' )
|
135
|
-
info = get_svn_info( dir )
|
136
|
-
return info['Last Changed Rev']
|
137
|
-
end
|
138
|
-
|
139
|
-
|
140
|
-
### Return a list of the entries at the specified Subversion url. If
|
141
|
-
### no +url+ is specified, it will default to the list in the URL
|
142
|
-
### corresponding to the current working directory.
|
143
|
-
def svn_ls( url=nil )
|
144
|
-
url ||= get_svn_url()
|
145
|
-
list = IO.read( '|-' ) or exec 'svn', 'ls', url
|
146
|
-
|
147
|
-
trace 'svn ls of %s: %p' % [url, list] if $trace
|
148
|
-
|
149
|
-
return [] if list.nil? || list.empty?
|
150
|
-
return list.split( $INPUT_RECORD_SEPARATOR )
|
151
|
-
end
|
152
|
-
|
153
|
-
|
154
|
-
### Return the URL of the latest timestamp in the tags directory.
|
155
|
-
def get_latest_svn_timestamp_tag
|
156
|
-
rooturl = get_svn_repo_root()
|
157
|
-
tagsurl = rooturl + "/#{SVN_TAGS_DIR}"
|
158
|
-
|
159
|
-
tags = svn_ls( tagsurl ).grep( TAG_TIMESTAMP_PATTERN ).sort
|
160
|
-
return nil if tags.nil? || tags.empty?
|
161
|
-
return tagsurl + '/' + tags.last
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
### Get a subversion diff of the specified targets and return it. If no targets are
|
166
|
-
### specified, the current directory will be diffed instead.
|
167
|
-
def get_svn_diff( *targets )
|
168
|
-
targets << BASEDIR if targets.empty?
|
169
|
-
trace "Getting svn diff for targets: %p" % [targets]
|
170
|
-
log = IO.read( '|-' ) or exec 'svn', 'diff', *(targets.flatten)
|
171
|
-
|
172
|
-
return log
|
173
|
-
end
|
174
|
-
|
175
|
-
|
176
|
-
### Get a subversion status as an Array of tuples of the form:
|
177
|
-
### [ <status>, <path> ]
|
178
|
-
def get_svn_status( *targets )
|
179
|
-
targets << BASEDIR if targets.empty?
|
180
|
-
trace "Getting svn status for targets: %p" % [targets]
|
181
|
-
status = IO.read( '|-' ) or exec 'svn', 'st', '--ignore-externals', *(targets.flatten)
|
182
|
-
entries = status.split( /\n/ ).
|
183
|
-
select {|line| line !~ /^(\s*|--- .*)$/ }.
|
184
|
-
collect do |line|
|
185
|
-
flag, path = line.strip.split( /\s+/, 2 )
|
186
|
-
[ flag, Pathname.new(path) ]
|
187
|
-
end
|
188
|
-
|
189
|
-
return entries
|
190
|
-
end
|
191
|
-
|
192
|
-
|
193
|
-
### Return the URL of the latest timestamp in the tags directory.
|
194
|
-
def get_latest_release_tag
|
195
|
-
rooturl = get_svn_repo_root()
|
196
|
-
releaseurl = rooturl + "/#{SVN_RELEASES_DIR}"
|
197
|
-
|
198
|
-
tags = svn_ls( releaseurl ).grep( RELEASE_VERSION_PATTERN ).sort_by do |tag|
|
199
|
-
tag[RELEASE_VERSION_PATTERN].split('.').collect {|i| Integer(i) }
|
200
|
-
end
|
201
|
-
return nil if tags.empty?
|
202
70
|
|
203
|
-
|
204
|
-
|
71
|
+
### Get the subversion information for the current working directory as
|
72
|
+
### a hash.
|
73
|
+
def get_svn_info( dir='.' )
|
74
|
+
return {} unless File.directory?( File.join(dir, '.svn') )
|
75
|
+
info = IO.read( '|-' ) or exec 'svn', 'info', dir.to_s
|
76
|
+
return YAML.load( info ) # 'svn info' outputs valid YAML! Yay!
|
77
|
+
rescue NotImplementedError
|
78
|
+
trace "No fork(), proceeding without svn info..."
|
79
|
+
return {}
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
### Get a list of the objects registered with subversion under the specified directory and
|
84
|
+
### return them as an Array of Pathame objects.
|
85
|
+
def get_svn_filelist( dir='.' )
|
86
|
+
list = IO.read( '|-' ) or exec 'svn', 'st', '-v', '--ignore-externals', dir
|
87
|
+
|
88
|
+
# Split into lines, filter out the unknowns, and grab the filenames as Pathnames
|
89
|
+
# :FIXME: This will break if we ever put in a file with spaces in its name. This
|
90
|
+
# will likely be the least of our worries if we do so, however, so it's not worth
|
91
|
+
# the additional complexity to make it handle that case. If we do need that, there's
|
92
|
+
# always the --xml output for 'svn st'...
|
93
|
+
return list.split( $/ ).
|
94
|
+
reject {|line| line =~ /^(\?|(\s*|--- .*)$)/ }.
|
95
|
+
collect {|fn| Pathname(fn[/\S+$/]) }
|
96
|
+
end
|
97
|
+
|
98
|
+
### Return the URL to the repository root for the specified +dir+.
|
99
|
+
def get_svn_repo_root( dir='.' )
|
100
|
+
info = get_svn_info( dir )
|
101
|
+
return info['Repository Root']
|
102
|
+
end
|
103
|
+
|
205
104
|
|
105
|
+
### Return the Subversion URL to the given +dir+.
|
106
|
+
def get_svn_url( dir='.' )
|
107
|
+
info = get_svn_info( dir )
|
108
|
+
return info['URL']
|
109
|
+
end
|
206
110
|
|
207
|
-
### Extract a diff from the specified subversion working +dir+ and return it.
|
208
|
-
def make_svn_commit_log( dir='.' )
|
209
|
-
diff = IO.read( '|-' ) or exec 'svn', 'diff'
|
210
|
-
fail "No differences." if diff.empty?
|
211
111
|
|
212
|
-
|
213
|
-
|
112
|
+
### Return the path of the specified +dir+ under the svn root of the
|
113
|
+
### checkout.
|
114
|
+
def get_svn_path( dir='.' )
|
115
|
+
root = get_svn_repo_root( dir )
|
116
|
+
url = get_svn_url( dir )
|
117
|
+
|
118
|
+
return url.sub( root + '/', '' )
|
119
|
+
end
|
214
120
|
|
215
121
|
|
216
|
-
###
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
log = IO.read( '|-' ) or exec 'svn', 'log', "-r#{start}:#{finish}", dir
|
221
|
-
fail "No log between #{start} and #{finish}." if log.empty?
|
122
|
+
### Return the keywords for the specified array of +files+ as a Hash keyed by filename.
|
123
|
+
def get_svn_keyword_map( *files )
|
124
|
+
files.flatten!
|
125
|
+
files.push( '.' ) if files.empty?
|
222
126
|
|
223
|
-
|
224
|
-
end
|
127
|
+
cmd = ['svn', 'pg', 'svn:keywords', *files]
|
225
128
|
|
129
|
+
# trace "Executing: svn pg svn:keywords " + files.join(' ')
|
130
|
+
output = IO.read( '|-' ) or exec( 'svn', 'pg', 'svn:keywords', *files )
|
226
131
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
132
|
+
kwmap = {}
|
133
|
+
output.split( "\n" ).each do |line|
|
134
|
+
next if line !~ /\s+-\s+/
|
135
|
+
path, keywords = line.split( /\s+-\s+/, 2 )
|
136
|
+
kwmap[ path ] = keywords.split
|
137
|
+
end
|
233
138
|
|
234
|
-
|
235
|
-
end
|
139
|
+
return kwmap
|
140
|
+
end
|
236
141
|
|
237
142
|
|
238
|
-
###
|
239
|
-
def
|
240
|
-
|
143
|
+
### Return the latest revision number of the specified +dir+ as an Integer.
|
144
|
+
def get_svn_rev( dir='.' )
|
145
|
+
info = get_svn_info( dir )
|
146
|
+
return info['Revision']
|
147
|
+
end
|
148
|
+
alias get_vcs_rev get_svn_rev
|
241
149
|
|
242
|
-
changelog = ''
|
243
|
-
path_prefix = '/' + get_svn_path( dir ) + '/'
|
244
150
|
|
245
|
-
|
151
|
+
### Return the latest revision number of the specified +dir+ as an Integer.
|
152
|
+
def get_last_changed_rev( dir='.' )
|
153
|
+
info = get_svn_info( dir )
|
154
|
+
return info['Last Changed Rev']
|
155
|
+
end
|
246
156
|
|
247
|
-
parser = XML::Parser.string( xmllog )
|
248
|
-
root = parser.parse.root
|
249
|
-
root.find( '//log/logentry' ).to_a.reverse.each do |entry|
|
250
|
-
trace "Making a changelog entry for r%s" % [ entry['revision'] ]
|
251
157
|
|
252
|
-
|
253
|
-
|
254
|
-
|
158
|
+
### Return a list of the entries at the specified Subversion url. If
|
159
|
+
### no +url+ is specified, it will default to the list in the URL
|
160
|
+
### corresponding to the current working directory.
|
161
|
+
def svn_ls( url=nil )
|
162
|
+
url ||= get_svn_url()
|
163
|
+
list = IO.read( '|-' ) or exec 'svn', 'ls', url
|
255
164
|
|
256
|
-
|
257
|
-
pathname = path.content
|
258
|
-
pathname.sub!( path_prefix , '' ) if pathname.count('/') > 1
|
165
|
+
trace 'svn ls of %s: %p' % [url, list] if $trace
|
259
166
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
167
|
+
return [] if list.nil? || list.empty?
|
168
|
+
return list.split( $INPUT_RECORD_SEPARATOR )
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
### Return the URL of the latest timestamp in the tags directory.
|
173
|
+
def get_latest_svn_timestamp_tag
|
174
|
+
rooturl = get_svn_repo_root()
|
175
|
+
tagsurl = rooturl + "/#{SVN_TAGS_DIR}"
|
176
|
+
|
177
|
+
tags = svn_ls( tagsurl ).grep( TAG_TIMESTAMP_PATTERN ).sort
|
178
|
+
return nil if tags.nil? || tags.empty?
|
179
|
+
return tagsurl + '/' + tags.last
|
180
|
+
end
|
181
|
+
|
182
|
+
|
183
|
+
### Get a subversion diff of the specified targets and return it. If no targets are
|
184
|
+
### specified, the current directory will be diffed instead.
|
185
|
+
def get_svn_diff( *targets )
|
186
|
+
targets << BASEDIR if targets.empty?
|
187
|
+
trace "Getting svn diff for targets: %p" % [targets]
|
188
|
+
log = IO.read( '|-' ) or exec 'svn', 'diff', *(targets.flatten)
|
189
|
+
|
190
|
+
return log
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
### Generate a commit log and invoke the user's editor on it.
|
195
|
+
def edit_commit_log
|
196
|
+
diff = make_commit_log()
|
197
|
+
|
198
|
+
File.open( COMMIT_MSG_FILE, File::WRONLY|File::TRUNC|File::CREAT ) do |fh|
|
199
|
+
fh.print( diff )
|
200
|
+
end
|
201
|
+
|
202
|
+
edit( COMMIT_MSG_FILE )
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
### Get a subversion status as an Array of tuples of the form:
|
207
|
+
### [ <status>, <path> ]
|
208
|
+
def get_svn_status( *targets )
|
209
|
+
targets << BASEDIR if targets.empty?
|
210
|
+
trace "Getting svn status for targets: %p" % [targets]
|
211
|
+
status = IO.read( '|-' ) or exec 'svn', 'st', '--ignore-externals', *(targets.flatten)
|
212
|
+
entries = status.split( /\n/ ).
|
213
|
+
select {|line| line !~ /^(\s*|--- .*)$/ }.
|
214
|
+
collect do |line|
|
215
|
+
flag, path = line.strip.split( /\s+/, 2 )
|
216
|
+
[ flag, Pathname.new(path) ]
|
271
217
|
end
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
218
|
+
|
219
|
+
return entries
|
220
|
+
end
|
221
|
+
|
222
|
+
|
223
|
+
### Return the URL of the latest timestamp in the tags directory.
|
224
|
+
def get_latest_release_tag
|
225
|
+
rooturl = get_svn_repo_root()
|
226
|
+
releaseurl = rooturl + "/#{SVN_RELEASES_DIR}"
|
227
|
+
|
228
|
+
tags = svn_ls( releaseurl ).grep( RELEASE_VERSION_PATTERN ).sort_by do |tag|
|
229
|
+
tag[RELEASE_VERSION_PATTERN].split('.').collect {|i| Integer(i) }
|
281
230
|
end
|
282
|
-
|
283
|
-
end
|
284
|
-
|
285
|
-
date = Time.parse( entry.find_first('date').content )
|
286
|
-
|
287
|
-
# cvs2svn doesn't set 'author'
|
288
|
-
author = 'unknown'
|
289
|
-
if entry.find_first( 'author' )
|
290
|
-
author = entry.find_first( 'author' ).content
|
291
|
-
end
|
292
|
-
|
293
|
-
msg = entry.find_first( 'msg' ).content
|
294
|
-
rev = entry['revision']
|
295
|
-
|
296
|
-
changelog << "-- #{date.rfc2822} by #{author} (r#{rev}) -----\n"
|
297
|
-
changelog << " Added: " << humanize_file_list(added) << "\n" unless added.empty?
|
298
|
-
changelog << " Changed: " << humanize_file_list(changed) << "\n" unless changed.empty?
|
299
|
-
changelog << " Deleted: " << humanize_file_list(deleted) << "\n" unless deleted.empty?
|
300
|
-
changelog << "\n"
|
301
|
-
|
302
|
-
indent = msg[/^(\s*)/] + LOG_INDENT
|
303
|
-
|
304
|
-
changelog << indent << msg.strip.gsub(/\n\s*/m, "\n#{indent}")
|
305
|
-
changelog << "\n\n\n"
|
306
|
-
end
|
307
|
-
|
308
|
-
return changelog
|
309
|
-
end
|
231
|
+
return nil if tags.empty?
|
310
232
|
|
233
|
+
return releaseurl + '/' + tags.last
|
234
|
+
end
|
311
235
|
|
312
|
-
### Returns a human-scannable file list by joining and truncating the list if it's too long.
|
313
|
-
def humanize_file_list( list, indent=FILE_INDENT )
|
314
|
-
listtext = list[0..5].join( "\n#{indent}" )
|
315
|
-
if list.length > 5
|
316
|
-
listtext << " (and %d other/s)" % [ list.length - 5 ]
|
317
|
-
end
|
318
|
-
|
319
|
-
return listtext
|
320
|
-
end
|
321
236
|
|
237
|
+
### Return the names of all existing branches.
|
238
|
+
def get_branch_names
|
239
|
+
rooturl = get_svn_repo_root()
|
240
|
+
branchesurl = rooturl + "/#{SVN_BRANCHES_DIR}"
|
322
241
|
|
323
|
-
|
324
|
-
|
325
|
-
pathnames.flatten!
|
242
|
+
return svn_ls( branchesurl )
|
243
|
+
end
|
326
244
|
|
327
|
-
map = pathnames.inject({}) do |map,path|
|
328
|
-
map[ path.dirname ] ||= []
|
329
|
-
map[ path.dirname ] << path.basename
|
330
|
-
map
|
331
|
-
end
|
332
245
|
|
333
|
-
|
246
|
+
### Extract a diff from the specified subversion working +dir+ and return it.
|
247
|
+
def make_svn_commit_log( dir='.' )
|
248
|
+
diff = IO.read( '|-' ) or exec 'svn', 'diff'
|
249
|
+
fail "No differences." if diff.empty?
|
334
250
|
|
335
|
-
|
336
|
-
|
337
|
-
io = open( '|-' ) or exec 'svn', 'pg', 'svn:ignore', dir
|
338
|
-
ignorelist = io.read.strip
|
339
|
-
ignorelist << "\n" << files.join("\n")
|
340
|
-
system 'svn', 'ps', 'svn:ignore', ignorelist, dir
|
341
|
-
end
|
342
|
-
end
|
251
|
+
return diff
|
252
|
+
end
|
343
253
|
|
344
254
|
|
345
|
-
###
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
255
|
+
### Extract the svn log from the specified subversion working +dir+,
|
256
|
+
### starting from rev +start+ and ending with rev +finish+, and return it.
|
257
|
+
def make_svn_log( dir='.', start='PREV', finish='HEAD' )
|
258
|
+
trace "svn log -r#{start}:#{finish} #{dir}"
|
259
|
+
log = IO.read( '|-' ) or exec 'svn', 'log', "-r#{start}:#{finish}", dir
|
260
|
+
fail "No log between #{start} and #{finish}." if log.empty?
|
261
|
+
|
262
|
+
return log
|
352
263
|
end
|
353
|
-
end
|
354
|
-
end
|
355
264
|
|
356
265
|
|
266
|
+
### Extract the verbose XML svn log from the specified subversion working +dir+,
|
267
|
+
### starting from rev +start+ and ending with rev +finish+, and return it.
|
268
|
+
def make_xml_svn_log( dir='.', start='PREV', finish='HEAD' )
|
269
|
+
trace "svn log --xml --verbose -r#{start}:#{finish} #{dir}"
|
270
|
+
log = IO.read( '|-' ) or exec 'svn', 'log', '--verbose', '--xml', "-r#{start}:#{finish}", dir
|
271
|
+
fail "No log between #{start} and #{finish}." if log.empty?
|
272
|
+
|
273
|
+
return log
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
### Create a changelog from the subversion log of the specified +dir+ and return it.
|
278
|
+
def make_svn_changelog( dir='.' )
|
279
|
+
require 'xml/libxml'
|
280
|
+
|
281
|
+
changelog = ''
|
282
|
+
path_prefix = '/' + get_svn_path( dir ) + '/'
|
283
|
+
|
284
|
+
xmllog = make_xml_svn_log( dir, 0 )
|
285
|
+
|
286
|
+
parser = XML::Parser.string( xmllog )
|
287
|
+
root = parser.parse.root
|
288
|
+
root.find( '//log/logentry' ).to_a.reverse.each do |entry|
|
289
|
+
trace "Making a changelog entry for r%s" % [ entry['revision'] ]
|
357
290
|
|
358
|
-
|
359
|
-
|
360
|
-
|
291
|
+
added = []
|
292
|
+
deleted = []
|
293
|
+
changed = []
|
361
294
|
|
362
|
-
|
363
|
-
|
295
|
+
entry.find( 'paths/path').each do |path|
|
296
|
+
pathname = path.content
|
297
|
+
pathname.sub!( path_prefix , '' ) if pathname.count('/') > 1
|
364
298
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
299
|
+
case path['action']
|
300
|
+
when 'A', 'R'
|
301
|
+
if path['copyfrom-path']
|
302
|
+
verb = path['action'] == 'A' ? 'renamed' : 'copied'
|
303
|
+
added << "%s\n#{FILE_INDENT}-> #{verb} from %s@r%s" % [
|
304
|
+
pathname,
|
305
|
+
path['copyfrom-path'],
|
306
|
+
path['copyfrom-rev'],
|
307
|
+
]
|
308
|
+
else
|
309
|
+
added << "%s (new)" % [ pathname ]
|
310
|
+
end
|
373
311
|
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
312
|
+
when 'M'
|
313
|
+
changed << pathname
|
314
|
+
|
315
|
+
when 'D'
|
316
|
+
deleted << pathname
|
317
|
+
|
318
|
+
else
|
319
|
+
log "Unknown action %p in rev %d" % [ path['action'], entry['revision'] ]
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
323
|
+
|
324
|
+
date = Time.parse( entry.find_first('date').content )
|
325
|
+
|
326
|
+
# cvs2svn doesn't set 'author'
|
327
|
+
author = 'unknown'
|
328
|
+
if entry.find_first( 'author' )
|
329
|
+
author = entry.find_first( 'author' ).content
|
330
|
+
end
|
331
|
+
|
332
|
+
msg = entry.find_first( 'msg' ).content
|
333
|
+
rev = entry['revision']
|
334
|
+
|
335
|
+
changelog << "-- #{date.rfc2822} by #{author} (r#{rev}) -----\n"
|
336
|
+
changelog << " Added: " << humanize_file_list(added) << "\n" unless added.empty?
|
337
|
+
changelog << " Changed: " << humanize_file_list(changed) << "\n" unless changed.empty?
|
338
|
+
changelog << " Deleted: " << humanize_file_list(deleted) << "\n" unless deleted.empty?
|
339
|
+
changelog << "\n"
|
340
|
+
|
341
|
+
indent = msg[/^(\s*)/] + LOG_INDENT
|
342
|
+
|
343
|
+
changelog << indent << msg.strip.gsub(/\n\s*/m, "\n#{indent}")
|
344
|
+
changelog << "\n\n\n"
|
345
|
+
end
|
346
|
+
|
347
|
+
return changelog
|
378
348
|
end
|
379
|
-
end
|
380
349
|
|
381
350
|
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
end
|
388
|
-
|
389
|
-
svninfo = get_svn_info()
|
390
|
-
svntrunk = Pathname.new( svninfo['Repository Root'] ) + SVN_TRUNK_DIR
|
391
|
-
svnbranchdir = Pathname.new( svninfo['Repository Root'] ) + SVN_BRANCHES_DIR
|
392
|
-
svnbranch = svnbranchdir + args.name
|
393
|
-
|
394
|
-
desc = "Making a new branch: #{svnbranch}"
|
395
|
-
ask_for_confirmation( desc ) do
|
396
|
-
msg = prompt_with_default( "Commit log: ", "Making a '#{args.name}' branch" )
|
397
|
-
run 'svn', 'cp', '-m', msg, svntrunk, svnbranch
|
398
|
-
ask_for_confirmation( "Switch to the new branch?", false ) do
|
399
|
-
run 'svn', 'sw', svnbranch
|
351
|
+
### Returns a human-scannable file list by joining and truncating the list if it's too long.
|
352
|
+
def humanize_file_list( list, indent=FILE_INDENT )
|
353
|
+
listtext = list[0..5].join( "\n#{indent}" )
|
354
|
+
if list.length > 5
|
355
|
+
listtext << " (and %d other/s)" % [ list.length - 5 ]
|
400
356
|
end
|
357
|
+
|
358
|
+
return listtext
|
401
359
|
end
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
360
|
+
|
361
|
+
|
362
|
+
### Add the list of +pathnames+ to the svn:ignore list.
|
363
|
+
def svn_ignore_files( *pathnames )
|
364
|
+
pathnames.flatten!
|
365
|
+
|
366
|
+
map = pathnames.inject({}) do |map,path|
|
367
|
+
map[ path.dirname ] ||= []
|
368
|
+
map[ path.dirname ] << path.basename
|
369
|
+
map
|
370
|
+
end
|
371
|
+
|
372
|
+
trace "Ignoring %d files in %d directories." % [ pathnames.length, map.length ]
|
373
|
+
|
374
|
+
map.each do |dir, files|
|
375
|
+
trace " %s: %p" % [ dir, files ]
|
376
|
+
io = open( '|-' ) or exec 'svn', 'pg', 'svn:ignore', dir
|
377
|
+
ignorelist = io.read.strip
|
378
|
+
ignorelist << "\n" << files.join("\n")
|
379
|
+
system 'svn', 'ps', 'svn:ignore', ignorelist, dir
|
380
|
+
end
|
415
381
|
end
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
release = PKG_VERSION
|
427
|
-
svnrelease = svnrel + release
|
428
|
-
|
429
|
-
unless svn_ls( svnrel.dirname ).include?( svnrel.basename.to_s + '/' )
|
430
|
-
log "Releases path #{svnrel} does not exist."
|
431
|
-
ask_for_confirmation( "To continue I'll need to create it." ) do
|
432
|
-
run 'svn', 'mkdir', svnrel, '-m', 'Creating releases/ directory'
|
382
|
+
|
383
|
+
|
384
|
+
### Delete the files in the given +filelist+ after confirming with the user.
|
385
|
+
def delete_extra_files( filelist )
|
386
|
+
description = humanize_file_list( filelist, ' ' )
|
387
|
+
log "Files to delete:\n ", description
|
388
|
+
ask_for_confirmation( "Really delete them?", false ) do
|
389
|
+
filelist.each do |f|
|
390
|
+
rm_rf( f, :verbose => true )
|
391
|
+
end
|
433
392
|
end
|
434
|
-
else
|
435
|
-
trace "Found release dir #{svnrel}"
|
436
393
|
end
|
437
394
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
395
|
+
end # module SubversionHelpers
|
396
|
+
|
397
|
+
|
398
|
+
###
|
399
|
+
### Tasks
|
400
|
+
###
|
401
|
+
|
402
|
+
desc "Subversion tasks"
|
403
|
+
namespace :svn do
|
404
|
+
include SubversionHelpers
|
405
|
+
|
406
|
+
desc "Copy the HEAD revision of the current #{SVN_TRUNK_DIR}/ to #{SVN_TAGS_DIR}/ with a " +
|
407
|
+
"current timestamp."
|
408
|
+
task :tag do
|
409
|
+
svninfo = get_svn_info()
|
410
|
+
tag = make_new_tag()
|
411
|
+
svntrunk = svninfo['Repository Root'] + "/#{SVN_TRUNK_DIR}"
|
412
|
+
svntagdir = svninfo['Repository Root'] + "/#{SVN_TAGS_DIR}"
|
413
|
+
svntag = svntagdir + '/' + tag
|
414
|
+
|
415
|
+
desc = "Tagging trunk as #{svntag}"
|
416
|
+
ask_for_confirmation( desc ) do
|
417
|
+
msg = prompt_with_default( "Commit log: ", "Tagging for code push" )
|
418
|
+
run 'svn', 'cp', '-m', msg, svntrunk, svntag
|
419
|
+
end
|
446
420
|
end
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
421
|
+
|
422
|
+
|
423
|
+
desc "Copy the HEAD revision of the current #{SVN_TRUNK_DIR}/ to #{SVN_BRANCHES_DIR} with a " +
|
424
|
+
"user-specified name."
|
425
|
+
task :branch, [:name] do |task, args|
|
426
|
+
branchname = args.name
|
427
|
+
unless branchname
|
428
|
+
branchname = prompt( "Branch name" ) or abort
|
429
|
+
end
|
430
|
+
|
431
|
+
svninfo = get_svn_info()
|
432
|
+
svntrunk = Pathname.new( svninfo['Repository Root'] ) + SVN_TRUNK_DIR
|
433
|
+
svnbranchdir = Pathname.new( svninfo['Repository Root'] ) + SVN_BRANCHES_DIR
|
434
|
+
svnbranch = svnbranchdir + branchname
|
435
|
+
|
436
|
+
desc = "Making a new branch: #{svnbranch}"
|
437
|
+
ask_for_confirmation( desc ) do
|
438
|
+
msg = prompt_with_default( "Commit log: ", "Making a '#{args.name}' branch" )
|
439
|
+
run 'svn', 'cp', '-m', msg, svntrunk, svnbranch
|
440
|
+
ask_for_confirmation( "Switch to the new branch?", false ) do
|
441
|
+
run 'svn', 'sw', svnbranch
|
442
|
+
end
|
443
|
+
end
|
452
444
|
end
|
453
|
-
end
|
454
445
|
|
455
|
-
### Task for debugging the #get_target_args helper
|
456
|
-
task :show_targets do
|
457
|
-
$stdout.puts "Targets from ARGV (%p): %p" % [ARGV, get_target_args()]
|
458
|
-
end
|
459
446
|
|
447
|
+
desc "Switch the working copy to the named branch"
|
448
|
+
task :switch, [:name] do |task, args|
|
449
|
+
branches = get_branch_names().collect {|name| name.chomp('/') }
|
460
450
|
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
desc "Show the (pre-edited) commit log for the current directory"
|
465
|
-
task :show_commitlog do
|
466
|
-
puts make_svn_commit_log()
|
467
|
-
end
|
468
|
-
|
451
|
+
unless args.name
|
452
|
+
log "Branches are:\n" + branches.collect {|br| " #{br}" }.join( "\n" )
|
469
453
|
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
454
|
+
begin
|
455
|
+
oldproc = Readline.completion_proc
|
456
|
+
abbrev = branches.abbrev
|
457
|
+
Readline.completion_proc = lambda{|string| abbrev[string] }
|
458
|
+
|
459
|
+
name = prompt( "Branch to switch to" ) or abort
|
460
|
+
args.with_defaults( :name => name )
|
461
|
+
ensure
|
462
|
+
Readline.completion_proc = oldproc unless oldproc.nil?
|
463
|
+
end
|
464
|
+
end
|
476
465
|
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
466
|
+
svninfo = get_svn_info()
|
467
|
+
abort "Branch '#{args.name}' does not exist" unless branches.include?( args.name )
|
468
|
+
branchuri = Pathname.new( svninfo['Repository Root'] ) + SVN_BRANCHES_DIR + args.name
|
469
|
+
run 'svn', 'sw', branchuri
|
481
470
|
end
|
482
|
-
|
471
|
+
task :sw => :switch
|
483
472
|
|
484
473
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
474
|
+
desc "Switch to the trunk if the working copy isn't there already."
|
475
|
+
task :trunk do
|
476
|
+
svninfo = get_svn_info()
|
477
|
+
svntrunk = Pathname.new( svninfo['Repository Root'] ) + SVN_TRUNK_DIR
|
478
|
+
|
479
|
+
if svninfo['URL'] != svntrunk.to_s
|
480
|
+
log "Switching to #{svntrunk}"
|
481
|
+
run 'svn', 'sw', svntrunk
|
482
|
+
else
|
483
|
+
log "You are already on trunk (#{svntrunk})"
|
484
|
+
end
|
485
|
+
end
|
489
486
|
|
490
487
|
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
files_to_add << entry[1]
|
506
|
-
when 'i'
|
507
|
-
files_to_ignore << entry[1]
|
508
|
-
when 'd'
|
509
|
-
files_to_delete << entry[1]
|
488
|
+
desc "Copy the most recent tag to #{SVN_RELEASES_DIR}/#{PKG_VERSION}"
|
489
|
+
task :prep_release do
|
490
|
+
last_tag = get_latest_svn_timestamp_tag()
|
491
|
+
svninfo = get_svn_info()
|
492
|
+
svnroot = Pathname.new( svninfo['Repository Root'] )
|
493
|
+
svntrunk = svnroot + SVN_TRUNK_DIR
|
494
|
+
svnrel = svnroot + SVN_RELEASES_DIR
|
495
|
+
release = PKG_VERSION
|
496
|
+
svnrelease = svnrel + release
|
497
|
+
|
498
|
+
unless svn_ls( svnrel.dirname ).include?( svnrel.basename.to_s + '/' )
|
499
|
+
log "Releases path #{svnrel} does not exist."
|
500
|
+
ask_for_confirmation( "To continue I'll need to create it." ) do
|
501
|
+
run 'svn', 'mkdir', svnrel, '-m', 'Creating releases/ directory'
|
510
502
|
end
|
503
|
+
else
|
504
|
+
trace "Found release dir #{svnrel}"
|
511
505
|
end
|
512
|
-
|
513
|
-
|
514
|
-
|
506
|
+
|
507
|
+
releases = svn_ls( svnrel ).collect {|name| name.sub(%r{/$}, '') }
|
508
|
+
trace "Releases: %p" % [releases]
|
509
|
+
if releases.include?( release )
|
510
|
+
error "Version #{release} already has a branch (#{svnrelease}). Did you mean " +
|
511
|
+
"to increment the version in #{VERSION_FILE}?"
|
512
|
+
fail
|
513
|
+
else
|
514
|
+
trace "No #{release} version currently exists"
|
515
515
|
end
|
516
|
-
|
517
|
-
|
518
|
-
|
516
|
+
|
517
|
+
desc = "Tagging trunk as #{svnrelease}..."
|
518
|
+
ask_for_confirmation( desc ) do
|
519
|
+
msg = prompt_with_default( "Commit log: ", "Branching for release" )
|
520
|
+
run 'svn', 'cp', '-m', msg, svntrunk, svnrelease
|
519
521
|
end
|
522
|
+
end
|
520
523
|
|
521
|
-
|
522
|
-
|
524
|
+
### Task for debugging the #get_target_args helper
|
525
|
+
task :show_targets do
|
526
|
+
$stdout.puts "Targets from ARGV (%p): %p" % [ARGV, get_target_args()]
|
527
|
+
end
|
528
|
+
|
529
|
+
|
530
|
+
desc "Update from Subversion"
|
531
|
+
task :update do
|
532
|
+
run 'svn', 'up', '--ignore-externals'
|
533
|
+
end
|
534
|
+
|
535
|
+
|
536
|
+
desc "Add/ignore any files that are unknown in the working copy"
|
537
|
+
task :newfiles do
|
538
|
+
log "Checking for new files..."
|
539
|
+
entries = get_svn_status()
|
540
|
+
|
541
|
+
unless entries.empty?
|
542
|
+
files_to_add = []
|
543
|
+
files_to_ignore = []
|
544
|
+
files_to_delete = []
|
545
|
+
|
546
|
+
entries.find_all {|entry| entry[0] == '?'}.each do |entry|
|
547
|
+
action = prompt_with_default( " #{entry[1]}: (a)dd, (i)gnore, (s)kip (d)elete", 's' )
|
548
|
+
case action
|
549
|
+
when 'a'
|
550
|
+
files_to_add << entry[1]
|
551
|
+
when 'i'
|
552
|
+
files_to_ignore << entry[1]
|
553
|
+
when 'd'
|
554
|
+
files_to_delete << entry[1]
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
unless files_to_add.empty?
|
559
|
+
run 'svn', 'add', *files_to_add
|
560
|
+
end
|
561
|
+
|
562
|
+
unless files_to_ignore.empty?
|
563
|
+
svn_ignore_files( *files_to_ignore )
|
564
|
+
end
|
565
|
+
|
566
|
+
unless files_to_delete.empty?
|
567
|
+
delete_extra_files( files_to_delete )
|
568
|
+
end
|
523
569
|
end
|
524
570
|
end
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
571
|
+
task :add => :newfiles
|
572
|
+
|
573
|
+
|
574
|
+
desc "Check in all the changes in your current working copy"
|
575
|
+
task :checkin => ['svn:update', 'svn:newfiles', 'test', 'svn:fix_keywords', COMMIT_MSG_FILE] do
|
576
|
+
targets = get_target_args()
|
577
|
+
$stderr.puts '---', File.read( COMMIT_MSG_FILE ), '---'
|
578
|
+
ask_for_confirmation( "Continue with checkin?" ) do
|
579
|
+
run 'svn', 'ci', '-F', COMMIT_MSG_FILE, targets
|
580
|
+
rm_f COMMIT_MSG_FILE
|
581
|
+
end
|
582
|
+
end
|
583
|
+
task :commit => :checkin
|
584
|
+
task :ci => :checkin
|
585
|
+
|
586
|
+
|
587
|
+
task :clean do
|
535
588
|
rm_f COMMIT_MSG_FILE
|
536
589
|
end
|
537
|
-
end
|
538
|
-
task :commit => :checkin
|
539
|
-
task :ci => :checkin
|
540
|
-
|
541
|
-
|
542
|
-
task :clean do
|
543
|
-
rm_f COMMIT_MSG_FILE
|
544
|
-
end
|
545
590
|
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
591
|
+
|
592
|
+
desc "Check and fix any missing keywords for any files in the project which need them"
|
593
|
+
task :fix_keywords do
|
594
|
+
log "Checking subversion keywords..."
|
595
|
+
paths = get_svn_filelist( BASEDIR ).
|
596
|
+
select {|path| path.file? && path.to_s =~ KEYWORDED_FILEPATTERN }
|
597
|
+
|
598
|
+
trace "Looking at %d paths for keywords:\n %p" % [paths.length, paths]
|
599
|
+
kwmap = get_svn_keyword_map( paths )
|
600
|
+
|
601
|
+
buf = ''
|
602
|
+
PP.pp( kwmap, buf, 132 )
|
603
|
+
trace "keyword map is: %s" % [buf]
|
604
|
+
|
605
|
+
files_needing_fixups = paths.find_all do |path|
|
606
|
+
(kwmap[path.to_s] & DEFAULT_KEYWORDS) != DEFAULT_KEYWORDS
|
607
|
+
end
|
608
|
+
|
609
|
+
unless files_needing_fixups.empty?
|
610
|
+
$stderr.puts "Files needing keyword fixes: ",
|
611
|
+
files_needing_fixups.collect {|f|
|
612
|
+
" %s: %s" % [f, kwmap[f] ? kwmap[f].join(' ') : "(no keywords)"]
|
613
|
+
}
|
614
|
+
ask_for_confirmation( "Will add default keywords to these files." ) do
|
615
|
+
run 'svn', 'ps', 'svn:keywords', DEFAULT_KEYWORDS.join(' '), *files_needing_fixups
|
616
|
+
end
|
617
|
+
else
|
618
|
+
log "Keywords are all up to date."
|
571
619
|
end
|
572
|
-
else
|
573
|
-
log "Keywords are all up to date."
|
574
620
|
end
|
621
|
+
|
622
|
+
|
623
|
+
task :debug_helpers do
|
624
|
+
methods = [
|
625
|
+
:get_last_changed_rev,
|
626
|
+
:get_latest_release_tag,
|
627
|
+
:get_latest_svn_timestamp_tag,
|
628
|
+
:get_svn_diff,
|
629
|
+
:get_svn_filelist,
|
630
|
+
:get_svn_info,
|
631
|
+
:get_svn_keyword_map,
|
632
|
+
:get_svn_path,
|
633
|
+
:get_svn_repo_root,
|
634
|
+
:get_svn_rev,
|
635
|
+
:get_svn_status,
|
636
|
+
:get_svn_url,
|
637
|
+
:svn_ls,
|
638
|
+
]
|
639
|
+
maxlen = methods.collect {|sym| sym.to_s.length }.max
|
640
|
+
|
641
|
+
methods.each do |meth|
|
642
|
+
res = send( meth )
|
643
|
+
puts "%*s => %p" % [ maxlen, colorize(meth.to_s, :cyan), res ]
|
644
|
+
end
|
645
|
+
end
|
646
|
+
|
575
647
|
end
|
576
648
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
:get_svn_status,
|
591
|
-
:get_svn_url,
|
592
|
-
:svn_ls,
|
593
|
-
]
|
594
|
-
maxlen = methods.collect {|sym| sym.to_s.length }.max
|
595
|
-
|
596
|
-
methods.each do |meth|
|
597
|
-
res = send( meth )
|
598
|
-
puts "%*s => %p" % [ maxlen, colorize(meth.to_s, :cyan), res ]
|
649
|
+
if SVN_DOTDIR.exist?
|
650
|
+
trace "Defining subversion VCS tasks"
|
651
|
+
|
652
|
+
desc "Check in all the changes in your current working copy"
|
653
|
+
task :ci => 'svn:ci'
|
654
|
+
desc "Check in all the changes in your current working copy"
|
655
|
+
task :checkin => 'svn:ci'
|
656
|
+
|
657
|
+
desc "Tag a release"
|
658
|
+
task :prep_release => 'svn:prep_release'
|
659
|
+
|
660
|
+
file COMMIT_MSG_FILE do
|
661
|
+
edit_commit_log()
|
599
662
|
end
|
663
|
+
else
|
664
|
+
trace "Not defining subversion tasks: no #{SVN_DOTDIR}"
|
600
665
|
end
|
601
|
-
|
666
|
+
|
667
|
+
end # unless defined?( SVN_DOTDIR )
|
602
668
|
|