scm 0.1.0.pre1

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/lib/scm/svn.rb ADDED
@@ -0,0 +1,497 @@
1
+ require 'scm/repository'
2
+ require 'scm/commits/svn'
3
+
4
+ module SCM
5
+ #
6
+ # Interacts with SubVersion (SVN) repositories.
7
+ #
8
+ class SVN < Repository
9
+
10
+ # SVN status codes displayed in the First Column
11
+ STATUSES = {
12
+ 'A' => :added,
13
+ 'C' => :conflicted,
14
+ 'D' => :deleted,
15
+ 'I' => :ignored,
16
+ 'M' => :modified,
17
+ 'R' => :replaced,
18
+ 'X' => :unversioned,
19
+ '?' => :untracked,
20
+ '!' => :missing,
21
+ '~' => :obstructed
22
+ }
23
+
24
+ LOG_SEPARATOR = '------------------------------------------------------------------------'
25
+
26
+ #
27
+ # Initializes the SVN repository.
28
+ #
29
+ # @param [String] path
30
+ # The path to the SVN repository.
31
+ #
32
+ def initialize(path)
33
+ path = File.expand_path(path)
34
+
35
+ super(path)
36
+
37
+ @root = if File.basename(@path) == 'trunk'
38
+ File.dirname(@path)
39
+ else
40
+ @path
41
+ end
42
+
43
+ @trunk = File.join(@root,'trunk')
44
+ @branches = File.join(@root,'branches')
45
+ @tags = File.join(@root,'tags')
46
+ end
47
+
48
+ #
49
+ # Creates an SVN repository.
50
+ #
51
+ # @param [String] path
52
+ # The path to the repository.
53
+ #
54
+ # @return [SVN]
55
+ # The new SVN repository.
56
+ #
57
+ # @raise [RuntimeError]
58
+ #
59
+ def self.create(path,options={})
60
+ path = File.expand_path(path)
61
+
62
+ unless system('svnadmin','create',path)
63
+ raise("could not create SVN repository #{path.dump}")
64
+ end
65
+
66
+ return new(path)
67
+ end
68
+
69
+ #
70
+ # Checks out a remote SVN repository.
71
+ #
72
+ # @param [URI, String] uri
73
+ # The URI of the remote repository.
74
+ #
75
+ # @param [Hash] options
76
+ # Additional options.
77
+ #
78
+ # @option options [String, Integer] :commits
79
+ # The commits to include.
80
+ #
81
+ # @option options [String] :dest
82
+ # The destination directory to clone into.
83
+ #
84
+ # @return [Boolean]
85
+ # Specifies whether the clone was successful.
86
+ #
87
+ def self.checkout(uri,options={})
88
+ arguments = []
89
+
90
+ if options[:commits]
91
+ arguments << '--revision' << options[:commits]
92
+ end
93
+
94
+ arguments << uri
95
+ arguments << options[:dest] if options[:dest]
96
+
97
+ system('svn','checkout',*arguments)
98
+ end
99
+
100
+ #
101
+ # @see checkout
102
+ #
103
+ def self.clone(uri,options={})
104
+ checkout(uri,options)
105
+ end
106
+
107
+ #
108
+ # Queries the status of the repository.
109
+ #
110
+ # @param [Array] paths
111
+ # Optional paths to query the statuses of.
112
+ #
113
+ # @return [Hash{String => Symbol}]
114
+ # The paths and their repsective statuses.
115
+ #
116
+ def status(*paths)
117
+ statuses = {}
118
+
119
+ popen('svn status',*paths) do |line|
120
+ status = line[0,1]
121
+ path = line[8..-1]
122
+
123
+ statuses[path] = STATUSES[status]
124
+ end
125
+
126
+ return statuses
127
+ end
128
+
129
+ #
130
+ # Adds paths to the repository.
131
+ #
132
+ # @param [Array] paths
133
+ # The paths to add to the repository.
134
+ #
135
+ def add(*paths)
136
+ svn(:add,*paths)
137
+ end
138
+
139
+ #
140
+ # Moves a file or directory.
141
+ #
142
+ # @param [String] source
143
+ # The path of the source file/directory.
144
+ #
145
+ # @param [String] dest
146
+ # The new destination path.
147
+ #
148
+ # @param [Boolean] force
149
+ # Specifies whether to force the move.
150
+ #
151
+ def move(source,dest,force=false)
152
+ arguments = []
153
+
154
+ arguments << '--force' if force
155
+ arguments << source << dest
156
+
157
+ svn(:mv,*arguments)
158
+ end
159
+
160
+ #
161
+ # Removes files or directories.
162
+ #
163
+ # @param [String, Array] paths
164
+ # The path(s) to remove.
165
+ #
166
+ # @param [Hash] options
167
+ # Additional options.
168
+ #
169
+ # @option options [Boolean] :force (false)
170
+ # Specifies whether to forcibly remove the files/directories.
171
+ #
172
+ # @note
173
+ # {#remove} does not respond to the `:recursive` option.
174
+ # SVN removes directories recursively by default.
175
+ #
176
+ def remove(paths,options={})
177
+ arguments = []
178
+
179
+ arguments << '--force' if options[:force]
180
+ arguments += ['--', *paths]
181
+
182
+ svn(:rm,*arguments)
183
+ end
184
+
185
+ #
186
+ # Makes a SVN commit.
187
+ #
188
+ # @param [String] message
189
+ # The message for the commit.
190
+ #
191
+ # @param [Hash] options
192
+ # Commit options.
193
+ #
194
+ # @option options [String] :paths
195
+ # The path of the file to commit.
196
+ #
197
+ # @return [Boolean]
198
+ # Specifies whether the commit was successfully made.
199
+ #
200
+ def commit(message=nil,options={})
201
+ arguments = []
202
+
203
+ if message
204
+ arguments << '-m' << message
205
+ end
206
+
207
+ if options[:paths]
208
+ arguments += [*options[:paths]]
209
+ end
210
+
211
+ svn(:commit,*arguments)
212
+ end
213
+
214
+ #
215
+ # Lists branches in the SVN repository.
216
+ #
217
+ # @return [Array<String>]
218
+ # The branch names.
219
+ #
220
+ def branches
221
+ branches = []
222
+
223
+ Dir.glob(File.join(@branches,'*')) do |path|
224
+ branches << File.basename(path) if File.directory(path)
225
+ end
226
+
227
+ return branches
228
+ end
229
+
230
+ #
231
+ # The current branch.
232
+ #
233
+ # @return [String]
234
+ # The name of the current branch.
235
+ #
236
+ def current_branch
237
+ if @path == @trunk
238
+ 'trunk'
239
+ else
240
+ File.basename(@path)
241
+ end
242
+ end
243
+
244
+ #
245
+ # Swtiches to another SVN branch.
246
+ #
247
+ # @param [String, Symbol] name
248
+ # The name of the branch to switch to.
249
+ # The name may also be `trunk`, to switch back to the `trunk`
250
+ # directory.
251
+ #
252
+ # @return [Boolean]
253
+ # Specifies whether the branch was successfully switched.
254
+ #
255
+ def switch_branch(name)
256
+ name = name.to_s
257
+ branch_dir = if name == 'trunk'
258
+ @trunk
259
+ else
260
+ File.join(@branches,name)
261
+ end
262
+
263
+ if File.directory?(branch_dir)
264
+ @path = branch_dir
265
+ return true
266
+ else
267
+ return false
268
+ end
269
+ end
270
+
271
+ #
272
+ # Deletes a branch.
273
+ #
274
+ # @param [String] name
275
+ # The name of the branch to delete.
276
+ #
277
+ # @return [Boolean]
278
+ # Specifies whether the branch was successfully deleted.
279
+ #
280
+ def delete_branch(name)
281
+ branch_dir = File.join(@branchs,name)
282
+
283
+ return false unless File.directory?(branch_dir)
284
+
285
+ svn(:rm,File.join('..','branchs',name))
286
+ end
287
+
288
+ #
289
+ # Lists tags in the SVN repository.
290
+ #
291
+ # @return [Array<String>]
292
+ # The tag names.
293
+ #
294
+ def tags
295
+ tags = []
296
+
297
+ Dir.glob(File.join(@tags,'*')) do |path|
298
+ tags << File.basename(path) if File.directory(path)
299
+ end
300
+
301
+ return tags
302
+ end
303
+
304
+ #
305
+ # Creates a SVN tag.
306
+ #
307
+ # @param [String] name
308
+ # The name for the tag.
309
+ #
310
+ # @param [String] commit
311
+ # The commit argument is not supported by {SVN}.
312
+ #
313
+ # @return [Boolean]
314
+ # Specifies whether the tag was successfully created.
315
+ #
316
+ # @raise [ArgumentError
317
+ # The `commit` argument was specified.
318
+ #
319
+ def tag(name,commit=nil)
320
+ if commit
321
+ raise(ArgumentError,"the commit argument is not supported by #{SVN}")
322
+ end
323
+
324
+ return false unless File.directory?(@trunk)
325
+
326
+ File.mkdir(@tags) unless File.directory?(@tags)
327
+
328
+ svn(:cp,@trunk,File.join(@tags,name))
329
+ end
330
+
331
+ #
332
+ # Deletes a SVN tag.
333
+ #
334
+ # @param [String] name
335
+ # The name of the tag.
336
+ #
337
+ # @return [Boolean]
338
+ # Specifies whether the tag was successfully deleted.
339
+ #
340
+ def delete_tag(name)
341
+ tag_dir = File.join(@tags,name)
342
+
343
+ return false unless File.directory?(tag_dir)
344
+
345
+ svn(:rm,tag_dir)
346
+ end
347
+
348
+ #
349
+ # Prints a SVN log.
350
+ #
351
+ # @param [String] :commit
352
+ # Commit to begin the log at.
353
+ #
354
+ # @param [String] :paths
355
+ # File to list commits for.
356
+ #
357
+ def log(options={})
358
+ arguments = []
359
+
360
+ if options[:commit]
361
+ arguments << '-c' << options[:commit]
362
+ end
363
+
364
+ if options[:paths]
365
+ arguments += [*options[:paths]]
366
+ end
367
+
368
+ svn(:log,*arguments)
369
+ end
370
+
371
+ #
372
+ # @return [true]
373
+ #
374
+ # @note no-op
375
+ #
376
+ def push(options={})
377
+ true
378
+ end
379
+
380
+ #
381
+ # Pulls changes from the remote SVN repository.
382
+ #
383
+ # @param [Hash] options
384
+ # Additional options.
385
+ #
386
+ # @option options [Boolean] :force
387
+ # Specifies whether to force pushing the changes.
388
+ #
389
+ # @return [Boolean]
390
+ # Specifies whether the changes were successfully pulled.
391
+ #
392
+ def pull(options={})
393
+ arguments = []
394
+ arguments << '-f' if options[:force]
395
+
396
+ svn(:update,*arguments)
397
+ end
398
+
399
+ #
400
+ # Lists the commits in the SVN repository.
401
+ #
402
+ # @param [Hash] options
403
+ # Additional options.
404
+ #
405
+ # @option options [String] :commit
406
+ # Commit to start at.
407
+ #
408
+ # @option options [Symbol, String] :branch
409
+ # The branch to list commits within.
410
+ #
411
+ # @option options [Integer] :limit
412
+ # The number of commits to list.
413
+ #
414
+ # @option options [String, Array<String>] :paths
415
+ # The path(s) to list commits for.
416
+ #
417
+ # @yield [commit]
418
+ # The given block will be passed each commit.
419
+ #
420
+ # @yieldparam [Commits::SVN] commit
421
+ # A commit from the repository.
422
+ #
423
+ # @return [Enumerator<Commits::SVN>]
424
+ # The commits in the repository.
425
+ #
426
+ def commits(options={})
427
+ return enum_for(:commits,options) unless block_given?
428
+
429
+ arguments = []
430
+
431
+ if options[:commit]
432
+ arguments << '--revision' << options[:commit]
433
+ end
434
+
435
+ if options[:limit]
436
+ arguments << '--limit' << options[:limit]
437
+ end
438
+
439
+ if options[:paths]
440
+ arguments.push(*options[:paths])
441
+ end
442
+
443
+ revision = nil
444
+ date = nil
445
+ author = nil
446
+ summary = ''
447
+
448
+ io = popen('svn log',*arguments)
449
+
450
+ # eat the first LOG_SEPARATOR
451
+ io.readline
452
+
453
+ until io.eof?
454
+ line = io.readline
455
+ line.chomp!
456
+
457
+ revision, author, date, changes = line.split(' | ',4)
458
+ revision = revision[1..-1].to_i
459
+ date = Time.parse(date)
460
+
461
+ # eat the empty line separating the metadata from the summary
462
+ line.readline
463
+
464
+ loop do
465
+ line = io.readline
466
+ break if line == LOG_SEPARATOR
467
+
468
+ summary << line
469
+ end
470
+
471
+ yield Commits::SVN.new(revision,date,author,summary)
472
+
473
+ revision = date = author = nil
474
+ summary = ''
475
+ end
476
+ end
477
+
478
+ protected
479
+
480
+ #
481
+ # Runs a SVN command.
482
+ #
483
+ # @param [Symbol] command
484
+ # The SVN command to run.
485
+ #
486
+ # @param [Array] arguments
487
+ # Additional arguments to pass to the SVN command.
488
+ #
489
+ # @return [Boolean]
490
+ # Specifies whether the SVN command exited successfully.
491
+ #
492
+ def svn(command,*arguments)
493
+ run(:svn,command,*arguments)
494
+ end
495
+
496
+ end
497
+ end
data/lib/scm/util.rb ADDED
@@ -0,0 +1,65 @@
1
+ require 'fileutils'
2
+ require 'shellwords'
3
+
4
+ module SCM
5
+ module Util
6
+ protected
7
+
8
+ #
9
+ # Runs a program.
10
+ #
11
+ # @param [String, Symbol] program
12
+ # The name or path of the program.
13
+ #
14
+ # @param [Array] arguments
15
+ # Optional arguments for the program.
16
+ #
17
+ # @return [Boolean]
18
+ # Specifies whether the program exited successfully.
19
+ #
20
+ def run(program,*arguments)
21
+ arguments = arguments.map { |arg| arg.to_s }
22
+
23
+ system(program.to_s,*arguments)
24
+ end
25
+
26
+ #
27
+ # Runs a command as a separate process.
28
+ #
29
+ # @param [String] command
30
+ # The command to run.
31
+ #
32
+ # @param [Array] arguments
33
+ # Additional arguments for the command.
34
+ #
35
+ # @yield [line]
36
+ # The given block will be passed each line read-in.
37
+ #
38
+ # @yieldparam [String] line
39
+ # A line read from the program.
40
+ #
41
+ # @return [IO]
42
+ # The stdout of the command being ran.
43
+ #
44
+ def popen(command,*arguments)
45
+ unless arguments.empty?
46
+ command = command.dup
47
+
48
+ arguments.each do |arg|
49
+ command << ' ' << Shellwords.shellescape(arg.to_s)
50
+ end
51
+ end
52
+
53
+ io = IO.popen(command)
54
+
55
+ if block_given?
56
+ io.each_line do |line|
57
+ line.chomp!
58
+ yield line
59
+ end
60
+ end
61
+
62
+ return io
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,4 @@
1
+ module SCM
2
+ # SCM version
3
+ VERSION = "0.1.0.pre1"
4
+ end
data/lib/scm.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'scm/version'
2
+ require 'scm/git'
3
+ require 'scm/svn'
4
+ require 'scm/scm'
data/scm.gemspec ADDED
@@ -0,0 +1,131 @@
1
+ # encoding: utf-8
2
+
3
+ require 'yaml'
4
+
5
+ Gem::Specification.new do |gemspec|
6
+ root = File.dirname(__FILE__)
7
+ lib_dir = File.join(root,'lib')
8
+ files = if File.directory?('.git')
9
+ `git ls-files`.split($/)
10
+ elsif File.directory?('.hg')
11
+ `hg manifest`.split($/)
12
+ elsif File.directory?('.svn')
13
+ `svn ls -R`.split($/).select { |path| File.file?(path) }
14
+ else
15
+ Dir['{**/}{.*,*}'].select { |path| File.file?(path) }
16
+ end
17
+
18
+ filter_files = lambda { |paths|
19
+ case paths
20
+ when Array
21
+ (files & paths)
22
+ when String
23
+ (files & Dir[paths])
24
+ end
25
+ }
26
+
27
+ version = {
28
+ :file => 'scm/version.rb',
29
+ :constant => 'SCM::VERSION'
30
+ }
31
+
32
+ defaults = {
33
+ 'name' => File.basename(File.dirname(__FILE__)),
34
+ 'files' => files,
35
+ 'executables' => filter_files['bin/*'].map { |path| File.basename(path) },
36
+ 'test_files' => filter_files['{test/{**/}*_test.rb,spec/{**/}*_spec.rb}'],
37
+ 'extra_doc_files' => filter_files['*.{txt,rdoc,md,markdown,tt,textile}'],
38
+ }
39
+
40
+ metadata = defaults.merge(YAML.load_file('gemspec.yml'))
41
+
42
+ gemspec.name = metadata.fetch('name',defaults[:name])
43
+ gemspec.version = if metadata['version']
44
+ metadata['version']
45
+ else
46
+ $LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
47
+
48
+ require version[:file]
49
+ eval(version[:constant])
50
+ end
51
+
52
+ gemspec.summary = metadata.fetch('summary',metadata['description'])
53
+ gemspec.description = metadata.fetch('description',metadata['summary'])
54
+
55
+ case metadata['license']
56
+ when Array
57
+ gemspec.licenses = metadata['license']
58
+ when String
59
+ gemspec.license = metadata['license']
60
+ end
61
+
62
+ case metadata['authors']
63
+ when Array
64
+ gemspec.authors = metadata['authors']
65
+ when String
66
+ gemspec.author = metadata['authors']
67
+ end
68
+
69
+ gemspec.email = metadata['email']
70
+ gemspec.homepage = metadata['homepage']
71
+
72
+ case metadata['require_paths']
73
+ when Array
74
+ gemspec.require_paths = metadata['require_paths']
75
+ when String
76
+ gemspec.require_path = metadata['require_paths']
77
+ end
78
+
79
+ gemspec.files = filter_files[metadata['files']]
80
+
81
+ gemspec.executables = metadata['executables']
82
+ gemspec.extensions = metadata['extensions']
83
+
84
+ if Gem::VERSION < '1.7.'
85
+ gemspec.default_executable = gemspec.executables.first
86
+ end
87
+
88
+ gemspec.test_files = filter_files[metadata['test_files']]
89
+
90
+ unless gemspec.files.include?('.document')
91
+ gemspec.extra_rdoc_files = metadata['extra_doc_files']
92
+ end
93
+
94
+ gemspec.post_install_message = metadata['post_install_message']
95
+ gemspec.requirements = metadata['requirements']
96
+
97
+ if gemspec.respond_to?(:required_ruby_version=)
98
+ gemspec.required_ruby_version = metadata['required_ruby_version']
99
+ end
100
+
101
+ if gemspec.respond_to?(:required_rubygems_version=)
102
+ gemspec.required_rubygems_version = metadata['required_ruby_version']
103
+ end
104
+
105
+ parse_versions = lambda { |versions|
106
+ case versions
107
+ when Array
108
+ versions.map { |v| v.to_s }
109
+ when String
110
+ versions.split(/,\s*/)
111
+ end
112
+ }
113
+
114
+ if metadata['dependencies']
115
+ metadata['dependencies'].each do |name,versions|
116
+ gemspec.add_dependency(name,parse_versions[versions])
117
+ end
118
+ end
119
+
120
+ if metadata['runtime_dependencies']
121
+ metadata['runtime_dependencies'].each do |name,versions|
122
+ gemspec.add_runtime_dependency(name,parse_versions[versions])
123
+ end
124
+ end
125
+
126
+ if metadata['development_dependencies']
127
+ metadata['development_dependencies'].each do |name,versions|
128
+ gemspec.add_development_dependency(name,parse_versions[versions])
129
+ end
130
+ end
131
+ end