scm 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
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