svnbranch 0.2.0

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/bin/svnbranch ADDED
@@ -0,0 +1,848 @@
1
+ #!/usr/bin/ruby -w
2
+ # -*- Ruby -*-
3
+ #
4
+ # PURPOSE: Manage Subversion branches with ease.
5
+ #
6
+ # USAGE:
7
+ # See man page, or type 'svnbranch help'
8
+ #
9
+ # REQUIREMENT:
10
+ # RubyGems gem
11
+ # Watcher gem
12
+ # Common Subversion repository naming conventions, i.e.:
13
+ # repos/project/trunk/<main code here>
14
+ # repos/project/branches/<optional sub-branch>/<branch-name>/<branch-code>
15
+ # repos/project/tags/<optional sub-branch>/<branch-name>-PRE/<branch-code>
16
+ # repos/project/tags/<optional sub-branch>/<branch-name>-POST/<branch-code>
17
+ #
18
+ # Created by Karrick McDermott on 2008-07-31.
19
+ # Copyright (c) 2008 All rights reserved.
20
+ ######################################################################
21
+
22
+ # Standard Ruby Installation
23
+ require 'fileutils'
24
+ require 'open3'
25
+
26
+ # Third-party Gems
27
+ require 'rubygems'
28
+ require 'watcher'
29
+
30
+ require File.expand_path(
31
+ File.join(File.dirname(__FILE__), '..', 'lib', 'svnbranch'))
32
+
33
+ ######################################################################
34
+ # CONSTANTS
35
+ RSYNC_OPTS = "-a --delete --delete-excluded --exclude '*~' --exclude '#*#'"
36
+ SSH_OPTS='-q -o PasswordAuthentication=no -o StrictHostKeyChecking=no -o ConnectTimeout=2'
37
+
38
+ ######################################################################
39
+ def branch_exists? (repos, branch)
40
+ # NOTE: This works for any subdirectory on the Subversion repository,
41
+ # including the trunk, branches, and tags.
42
+
43
+ # assume branch exists
44
+ branch_exists = true
45
+
46
+ $watcher.verbose("Looking for repository branch [#{branch}]") do
47
+ # Use begin / rescue rather than Watcher in order to stifle expected
48
+ # error messages that occur if branch doesn't exist.
49
+ begin
50
+ subversion("svn ls #{repos}/#{branch}")
51
+ rescue
52
+ # execute will throw if branch missing
53
+ # If here, then branch was not found
54
+ branch_exists = false
55
+ end
56
+ end
57
+
58
+ return branch_exists
59
+ end
60
+
61
+ ######################################################################
62
+ def close_branch! (force = false)
63
+ # Example: We are in a branch other than the trunk, and decide to
64
+ # call svnbranch, when work is perceived complete on a branch:
65
+ # $ svnbranch close
66
+
67
+ branch_path = nil
68
+ repos = nil
69
+ post_tag = nil
70
+
71
+ $watcher.verbose("Checking work environment") do
72
+ current_dir = inspect_current_work_directory
73
+ repos = current_dir[:repos]
74
+ $watcher.debug("repository = [#{repos}]")
75
+ # "https://svn.example.com/svn/project_name"
76
+ branch_path = current_dir[:branch]
77
+ $watcher.debug("branch_path = [#{branch_path}]")
78
+ # "trunk"
79
+ # "branches/bug-fixes/BUG-1234"
80
+
81
+ if branch_path == 'trunk'
82
+ msg = "Cannot close a branch when you are in the trunk"
83
+ raise RuntimeError.new(msg)
84
+ end
85
+
86
+ # Check for Subversion commit in this directory
87
+ verify_subversion_commit(Dir.getwd())
88
+
89
+ # NOTE: Expect branch_path like below:
90
+ # branches/bug-fixes/BUG-1234
91
+ branch_path_split = branch_path.split(/\//)
92
+ branch_name = branch_path_split.pop # capture the right-most element
93
+ branch_path_split.shift # discard the prefix (branches | tags)
94
+ sub_branch = branch_path_split.join('/') # reassemble
95
+
96
+ # If sub-branch empty, then no sub-branch
97
+ sub_branch = '/' + sub_branch unless sub_branch == ''
98
+
99
+ pre_tag = "tags#{sub_branch}/#{branch_name}-PRE"
100
+ post_tag = "tags#{sub_branch}/#{branch_name}-POST"
101
+
102
+ msg = "Verifying branch exists [#{branch_path}]"
103
+ $watcher.verbose(msg) do
104
+ [branch_path, pre_tag].each do |b|
105
+ unless branch_exists?(repos, b)
106
+ msg = "Resource missing [#{b}] on Subversion server;"
107
+ raise RuntimeError.new(msg)
108
+ end
109
+ end
110
+ end
111
+
112
+ msg = "Verifying branch not yet closed [#{branch_path}]"
113
+ $watcher.verbose(msg) do
114
+ if branch_exists?(repos, post_tag)
115
+ if force
116
+ remove_branch!(repos, post_tag)
117
+ else
118
+ msg = "Resource already exists [#{post_tag}];"
119
+ msg << " use '-f' to force overwrite"
120
+ raise RuntimeError.new(msg)
121
+ end
122
+ end
123
+ end
124
+ end
125
+
126
+ # Tag post-work for branch for later merge purposes
127
+ tag_branch!(repos, branch_path, post_tag)
128
+ end
129
+
130
+ ######################################################################
131
+ def command_exists? (cmd)
132
+ # determine whether the [cmd] utility exists on system
133
+ command_exists = false
134
+
135
+ $watcher.verbose("Looking for [#{cmd}] utilitiy on system") do
136
+ # Use begin / rescue rather than Watcher in order to stifle expected
137
+ # error messages that occur if branch doesn't exist.
138
+ begin
139
+ command_exists = execute("which #{cmd}").first.strip
140
+ rescue
141
+ # execute will throw if branch missing
142
+ # If here, then branch was not found
143
+ end
144
+ end
145
+
146
+ return command_exists
147
+ end
148
+
149
+ ######################################################################
150
+ def copy_branch! (repos, existing_branch, new_branch)
151
+ msg = "Copying Subversion branch [#{existing_branch}]"
152
+ msg << " to [#{new_branch}]"
153
+ $watcher.verbose(msg) do
154
+ existing_branch = repos + '/' + existing_branch
155
+ new_branch = repos + '/' + new_branch
156
+ cmd = "svn copy -m \"#{msg}\" #{existing_branch} #{new_branch}"
157
+ # NOTE: Not a 'safe' command because modifies server
158
+ subversion(cmd, false)
159
+ end
160
+ end
161
+
162
+ ######################################################################
163
+ def copy_dir! (src, dst)
164
+ # src -- source directory for copy
165
+ # dst -- destination directory for copy
166
+
167
+ src = File.expand_path(src)
168
+ dst = File.expand_path(dst)
169
+ parent = File.expand_path(File.join(dst,'..'))
170
+
171
+ msg = "Verifying parent directory [#{parent}] exists"
172
+ $watcher.debug(msg) do
173
+
174
+ end
175
+
176
+ msg = "Copying working directory to new branch directory"
177
+ $watcher.debug(msg) do
178
+ # Use [rsync] if available (much more efficient)
179
+ if command_exists?('rsync') == false
180
+ copy_dir_with_ruby!(src, dst)
181
+ else
182
+ copy_dir_with_rsync!(src, dst)
183
+ end
184
+ end
185
+ end
186
+
187
+ ######################################################################
188
+ def copy_dir_with_rsync! (src, dst)
189
+ # src -- source directory for copy
190
+ # dst -- destination directory for copy
191
+
192
+ # Note: No need to remove existsing directory or files because
193
+ # rsync will take care of it.
194
+
195
+ msg = "Copying data from [#{src}] to [#{dst}] using Rsync"
196
+ $watcher.verbose(msg) do
197
+ cmd = "rsync #{RSYNC_OPTS} #{src}/ #{dst}"
198
+ execute(cmd)
199
+ end
200
+ end
201
+
202
+ ######################################################################
203
+ def copy_dir_with_ruby! (src, dst)
204
+ # src -- source directory for copy
205
+ # dst -- destination directory for copy
206
+
207
+ # Must remove dst if present
208
+ if File.exists?(dst)
209
+ $watcher.verbose("Removing previous directory [#{dst}]") do
210
+ #FileUtils.remove_entry_secure(dst)
211
+ FileUtils.remove_entry(dst)
212
+ end
213
+ end
214
+
215
+ msg = "Copying data from [#{src}] to [#{dst}] using Ruby"
216
+ $watcher.verbose(msg) do
217
+ # FileUtils.cp_r(src, dst)
218
+ FileUtils.copy_entry(src, dst, preserve = true)
219
+ end
220
+ end
221
+
222
+ ######################################################################
223
+ # create_branch!
224
+ #
225
+ # force -- forces creation of new branch regardless of uncommitted changes
226
+ # new_dir -- true => create a local copy of branch
227
+ # false => re-use current working directory
228
+ ######################################################################
229
+ def create_branch! (new_branch_name, force, new_dir = true)
230
+ # Example: We are in trunk, and decide call svnbranch, as below:
231
+ # $ svnbranch create bug-fixes/BUG-1234
232
+
233
+ # new_branch_name -- bug-fixes/BUG-1234
234
+
235
+
236
+ start_dir = File.expand_path(Dir.getwd())
237
+ if new_dir == true
238
+ parent_dir = File.join(start_dir, '..')
239
+ $watcher.debug("parent_dir = [#{parent_dir}]")
240
+ new_dir = File.expand_path(File.join(parent_dir, 'branches', new_branch_name))
241
+ $watcher.debug("new_dir = [#{new_dir}]")
242
+ verify_path!(File.expand_path(File.join(new_dir,'..')))
243
+ end
244
+
245
+ # new_branch_path -- branches/bug-fixes/BUG-1234
246
+ # new_pre_tag -- tags/bug-fixes/BUG-1234-PRE
247
+ # new_post_tag -- tags/bug-fixes/BUG-1234-POST
248
+ new_branch_path = "branches/#{new_branch_name}"
249
+ new_pre_tag = "tags/#{new_branch_name}-PRE"
250
+ new_post_tag = "tags/#{new_branch_name}-POST"
251
+
252
+ # parent_branch_path
253
+ # if no sub-branches -> ""
254
+ # if sub-branches -> "pat/experimental"
255
+ path_array = new_branch_path.split(/\//)
256
+ path_array.pop
257
+ parent_branch_path = path_array.join('/')
258
+ $watcher.debug("parent_branch_path = [#{parent_branch_path}]")
259
+ path_array.shift
260
+ parent_tags_path = path_array.unshift('tags').join('/')
261
+ $watcher.debug("parent_tags_path = [#{parent_tags_path}]")
262
+
263
+ repos = nil
264
+ working_branch = nil
265
+
266
+ $watcher.verbose("Checking work environment") do
267
+ if new_branch_name == 'trunk'
268
+ msg = "Cannot create a trunk branch"
269
+ raise RuntimeError.new(msg)
270
+ end
271
+
272
+ # repos -- https://svn.example.com/svn/project
273
+ # working_branch
274
+ # trunk
275
+ # pat/experimental
276
+ current_dir = inspect_current_work_directory
277
+ repos = current_dir[:repos]
278
+ $watcher.debug("repository = [#{repos}]")
279
+ working_branch = current_dir[:branch]
280
+ $watcher.debug("working_branch = [#{working_branch}]")
281
+
282
+ # Verify local changes committed
283
+ if not subversion_committed?(start_dir) and not force
284
+ msg = "Files in this branch have not been committed;"
285
+ msg << " use '-f' for force override"
286
+ raise RuntimeError.new(msg)
287
+ end
288
+
289
+ # Verify / create branch and sub-branches on repository server
290
+ verify_branch!(repos, parent_branch_path)
291
+ verify_branch!(repos, parent_tags_path)
292
+
293
+ msg = "Verifying branch [#{new_branch_name}] doesn't already exist"
294
+ $watcher.verbose(msg) do
295
+ # Ensure branch, PRE-branch, and POST-branch don't exist
296
+ [new_branch_path, new_pre_tag, new_post_tag].each do |b|
297
+ # NOTE: Okay to use [branch_exists?] for a tag...
298
+ if branch_exists?(repos, b)
299
+ if force
300
+ remove_branch!(repos, b)
301
+ else
302
+ msg = "Resource already exists [#{b}]; check your"
303
+ msg << " <new-branch> argument, or use '-f' to force overwrite"
304
+ raise RuntimeError.new(msg)
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
310
+
311
+ # Duplicate existing branch on Subversion server
312
+ copy_branch!(repos, working_branch, new_branch_path)
313
+
314
+ # Tag pre-work for branch for later merge purposes
315
+ tag_branch!(repos, new_branch_path, new_pre_tag)
316
+
317
+ if new_dir == false
318
+ # re-use current working directory
319
+ switch_branch!(repos, start_dir, new_branch_path)
320
+ else
321
+ # When do this?
322
+ # NOTE: Although it would be prudent to perform this prior to
323
+ # directory duplication so to prevent merging remote updates
324
+ # twice, (once for new branch, and once when merging back
325
+ # with working_branch), there is a small possibility that user
326
+ # would not want this. THE JURY IS OUT!
327
+ update_branch!(repos, working_branch) if false
328
+
329
+ # Duplicate existing working directory on this host
330
+ copy_dir!(start_dir, new_dir)
331
+
332
+ # Go to new_branch_dir, and tell Subversion to switch it
333
+ # to new branch
334
+ switch_branch!(repos, new_dir, new_branch_path)
335
+ end
336
+ end
337
+
338
+ ######################################################################
339
+ def delete_branch! (branch_name)
340
+ # Example: We are in the trunk, and decide to delete all
341
+ # references to a different branch.
342
+ # $ svnbranch delete bug-fixes/BUG-1234
343
+
344
+ # sub_branch -- bug-fixes
345
+ # branch_name -- BUG-1234
346
+
347
+ $watcher.verbose("Checking work environment") do
348
+ current_dir = inspect_current_work_directory
349
+ repos = current_dir[:repos]
350
+ $watcher.debug("repository = [#{repos}]")
351
+ # "https://svn.example.com/svn/project_name"
352
+
353
+ branch_path = "branches/#{branch_name}"
354
+ # branches/bug-fixes/BUG-1234
355
+ pre_tag = "tags/#{branch_name}-PRE"
356
+ # tags/bug-fixes/BUG-1234-PRE
357
+ post_tag = "tags/#{branch_name}-POST"
358
+ # tags/bug-fixes/BUG-1234-POST
359
+
360
+ # Use branch_path from method parameter, not working directory
361
+ $watcher.debug("branch_path = [#{branch_path}]")
362
+ $watcher.debug("pre_tag = [#{pre_tag}]")
363
+ $watcher.debug("post_tag = [#{post_tag}]")
364
+
365
+ $watcher.verbose("Deleteing branch [#{branch_path}]") do
366
+ [branch_path, pre_tag, post_tag].each do |b|
367
+ if branch_exists?(repos, b)
368
+ msg = "Deleteing resource [#{b}]"
369
+ $watcher.verbose(msg) do
370
+ cmd = "svn rm -m \"#{msg}\" #{repos}/#{b}"
371
+ # NOTE: Not a 'safe' command because modifies server
372
+ subversion(cmd, false)
373
+ end
374
+ end
375
+ end
376
+ end
377
+ end
378
+ end
379
+
380
+ ######################################################################
381
+ # execute -- execute a command
382
+ def execute (command)
383
+ std_out = ''
384
+ std_err = ''
385
+
386
+ $watcher.debug('[' + command + ']') do
387
+ Open3.popen3(command) do |stdin, stdout, stderr|
388
+ # read from stdout and stderr (IO classes)
389
+ std_out = stdout.readlines
390
+ std_err = stderr.readlines
391
+ end
392
+
393
+ # log the output lines if debugging
394
+ if std_out.class == Array
395
+ std_out.each { |line| $watcher.debug(line.strip) }
396
+ else
397
+ $watcher.debug(std_out.inspect)
398
+ end
399
+ end
400
+
401
+ # filter out pseudo-terminal errors
402
+ std_err.delete_if { |e| e =~ /^Pseudo-terminal/ }
403
+
404
+ # check for remaining errors
405
+ if std_err.length > 0
406
+ # log the output lines if debugging
407
+ if std_err.class == Array
408
+ std_err.each { |line| $watcher.debug(line.strip) }
409
+ else
410
+ $watcher.debug(std_err.inspect)
411
+ end
412
+ raise RuntimeError.new(std_err.join("\n"))
413
+ end
414
+
415
+ # return the output lines as an array
416
+ std_out
417
+ end
418
+
419
+ ######################################################################
420
+ def inspect_current_work_directory
421
+ repos = nil
422
+ url = nil
423
+ $watcher.verbose("Inspecting current Subversion work directory") do
424
+ subversion("svn info").each do |line|
425
+ if line =~ /^URL: (.*)$/
426
+ url = $1
427
+ $watcher.debug("Repository URL: #{$1}") if $DEBUG
428
+ end
429
+ end
430
+
431
+ if url == nil
432
+ msg = "Current directory does not appear to be a Subversion"
433
+ msg << " working directory"
434
+ raise RuntimeError.new(msg)
435
+ end
436
+ end
437
+
438
+ # NOTE: Current branch may be either [trunk] or some different name.
439
+ # If different name, then it should be a member of the
440
+ # [branches] subdirectory on the Subversion server.
441
+
442
+ # Break up the URL to a repository parent directory and branch name
443
+ # Example: "https://svn.example.com/svn/project/trunk"
444
+ # $1 --> "https://svn.example.com/svn/project"
445
+ # $2 --> "trunk"
446
+ # Example: "https://svn.example.com/svn/project/branches/bug-fixes/BUG-1234"
447
+ # $1 --> "https://svn.example.com/svn/project"
448
+ # $2 --> "branches/bug-fixes/BUG-1234"
449
+ md = url.match(/(.*)\/(branches\/.*|trunk)/)
450
+ return {:repos => md[1], :branch => md[2]}
451
+ end
452
+
453
+ ######################################################################
454
+ def man_page
455
+ found = false
456
+ $:.each do |path|
457
+ if not found
458
+ filename = File.join(path,'..','man1',File.basename($0)+'.1')
459
+ filename = File.expand_path(filename)
460
+ $watcher.debug("Looking for [#{filename}]") do
461
+ if File.exists?(filename)
462
+ found = true
463
+ system("man #{filename}")
464
+ end
465
+ end
466
+ end
467
+ end
468
+ end
469
+
470
+ ######################################################################
471
+ def make_repository_path! (repos, branch)
472
+ $watcher.verbose("Creating repository branch [#{branch}]") do
473
+ cmd = "svn mkdir --parents"
474
+ cmd << " -m \"Creating repository sub-branch [#{branch}]\""
475
+ cmd << " #{repos}/#{branch}"
476
+ # NOTE: Not a 'safe' command because modifies server
477
+ subversion(cmd, false)
478
+ end
479
+ end
480
+
481
+ ######################################################################
482
+ def merge_branch! (merge_branch)
483
+ # Example: We are in a branch, and decide to merge in the changes
484
+ # from a different branch into this directory.
485
+ # $ svnbranch merge bug-fixes/BUG-1234
486
+
487
+ # merge_branch -- bug-fixes/BUG-1234
488
+ # sub_branch -- bug-fixes
489
+ # branch_name -- BUG-1234
490
+
491
+ repos = nil
492
+ working_branch = nil
493
+ merge_branch_path = "branches/#{merge_branch}"
494
+ # branches/bug-fixes/BUG-1234
495
+ merge_pre_tag = "tags/#{merge_branch}-PRE"
496
+ # tags/bug-fixes/BUG-1234-PRE
497
+ merge_post_tag = "tags/#{merge_branch}-POST"
498
+ # tags/bug-fixes/BUG-1234-POST
499
+
500
+ $watcher.verbose("Checking work environment") do
501
+ if merge_branch == 'trunk'
502
+ msg = "Cannot merge the trunk branch into a different branch."
503
+ raise RuntimeError.new(msg)
504
+ end
505
+
506
+ current_dir = inspect_current_work_directory
507
+ repos = current_dir[:repos]
508
+ $watcher.debug("repository = [#{repos}]")
509
+ # "https://svn.example.com/svn/project_name"
510
+ working_branch = current_dir[:branch]
511
+ # "trunk"
512
+ # "branches/2.1-maint"
513
+
514
+ msg = "Verifying branch exists [#{merge_branch_path}]"
515
+ $watcher.verbose(msg) do
516
+ [merge_branch_path, merge_pre_tag, merge_post_tag].each do |b|
517
+ unless branch_exists?(repos, b)
518
+ msg = "Resource missing [#{b}]; check your"
519
+ msg << " <merge-branch> argument"
520
+ raise RuntimeError.new(msg)
521
+ end
522
+ end
523
+ end
524
+
525
+ # Update this Subversion work directory
526
+ update_branch!(repos, working_branch)
527
+ end
528
+
529
+ # Pull in diff from PRE to POST into this work directory
530
+ msg = "Merging changes from branch [#{merge_branch}] into "
531
+ msg << "working directory [#{Dir.getwd()}]"
532
+ $watcher.verbose(msg) do
533
+ # NOTE: This gets the diffs between the PRE- and POST- tags, then applies
534
+ # those changes to the current directory.
535
+ # NOTE: Because the destination working directory is left off of this
536
+ # command, Subversion will apply the diffs to the current
537
+ # directory. It is imperative to be in the working directory when
538
+ # the below command is invoked.
539
+ cmd = "svn merge #{repos}/#{merge_pre_tag} #{repos}/#{merge_post_tag}"
540
+ subversion(cmd)
541
+ end
542
+
543
+ msg = "The changes for [#{merge_branch}] have been merged into the"
544
+ msg << " working directory [#{Dir.getwd()}]"
545
+ $watcher.always(msg)
546
+ $watcher.always("=================================================================")
547
+ $watcher.always("= NOW BUILD, TEST, AND RESOLVE PROBLEMS BEFORE YOU COMMIT! =")
548
+ $watcher.always("= (Do *not* re-merge the same merge branch into this directory) =")
549
+ $watcher.always("= Please add the below line into your next commit log: =")
550
+ $watcher.always("= \"Merged [#{merge_branch}] into [#{working_branch}].\" =")
551
+ $watcher.always("=============================================================
552
+ ====")
553
+ end
554
+
555
+ ######################################################################
556
+ # Remove traces of a branch (or tag) if it exists
557
+ def remove_branch! (repos, branch)
558
+ if branch_exists?(repos, branch)
559
+ msg = "Removing Subversion branch [#{branch}]"
560
+ $watcher.verbose(msg) do
561
+ cmd = "svn rm -m \"#{msg}\" #{repos}/#{branch}"
562
+ # NOTE: Not a 'safe' command because modifies server
563
+ subversion(cmd, false)
564
+ end
565
+ end
566
+ end
567
+
568
+ ######################################################################
569
+ # Easily turn off execution of subversion commands while developing
570
+ # A 'safe' command doesn't change Subversion repository on server.
571
+ def subversion (command, safe = true)
572
+ if safe or not $DEBUG
573
+ return execute(command)
574
+ else
575
+ return $watcher.debug("SIMULATE: [#{command}]")
576
+ end
577
+ end
578
+
579
+ ######################################################################
580
+ def subversion_committed? (branch_dir)
581
+ subversion_committed = false
582
+
583
+ msg = "Determining whether changes committed back to branch"
584
+ msg << " [#{branch_dir}]"
585
+ $watcher.verbose(msg) do
586
+ # Use begin / rescue rather than Watcher to stifle expected
587
+ # error messages when branch doesn't exist.
588
+ begin
589
+ cmd = "svn status #{branch_dir}"
590
+ # status = subversion(cmd).split(/\n/).delete_if do |line|
591
+ status = subversion(cmd).delete_if do |line|
592
+ line =~ /(^X\s+)|(^\s*$)|(^Performing\s)|(^\?\s+README.txt)/
593
+ end
594
+
595
+ if status.length > 0
596
+ # NOT NEEDED SINCE status COMMAND LISTS
597
+ # status.each { |line| $watcher.debug(line) }
598
+ raise RuntimeError.new("Must commit changes to branch first")
599
+ end
600
+
601
+ # execute will throw if branch missing
602
+ # If here, then branch was found
603
+ subversion_committed = true
604
+ rescue
605
+ # no-op
606
+ end
607
+ end
608
+
609
+ return subversion_committed
610
+ end
611
+
612
+ ######################################################################
613
+ def switch_branch! (repos, branch_dir, branch_path)
614
+ # branch_dir -- Subversion work directory where switch
615
+ # branch_path -- Subversion branch path
616
+
617
+ msg = "Switching [#{branch_dir}] directory to newly created "
618
+ msg << "branch [#{branch_path}]"
619
+ $watcher.verbose(msg) do
620
+ cmd = "svn switch #{repos}/#{branch_path} #{branch_dir}"
621
+ subversion(cmd)
622
+ end
623
+ end
624
+
625
+ ######################################################################
626
+ def switch_this_dir! (branch_name)
627
+ # branch_name -- branch name to switch to
628
+
629
+ repos = nil
630
+
631
+ # If trunk specified, no 'branches' prefix is required.
632
+ if branch_name == 'trunk'
633
+ branch_path = branch_name
634
+ else
635
+ branch_path = "branches/#{branch_name}"
636
+ end
637
+ $watcher.debug("branch_path = [#{branch_path}]")
638
+
639
+ $watcher.verbose("Checking work environment") do
640
+ current_dir = inspect_current_work_directory
641
+ repos = current_dir[:repos]
642
+ $watcher.debug("repository = [#{repos}]")
643
+ # "https://svn.example.com/svn/project_name"
644
+ working_branch = current_dir[:branch]
645
+ # "trunk"
646
+ # "branches/2.1-maint"
647
+
648
+ # Check for Subversion commit in this directory
649
+ verify_subversion_commit(Dir.getwd())
650
+
651
+ # Verify / create branch and sub-branches on repository server
652
+ verify_branch!(repos, branch_path)
653
+ end
654
+
655
+ switch_branch!(repos, Dir.getwd(), branch_path)
656
+ end
657
+
658
+ ######################################################################
659
+ def tag_branch! (repos, branch, tag)
660
+ # branch -- branch into which changes are placed
661
+ # tag -- tag to use to mark the start or end of branch
662
+ # (for merging)
663
+
664
+ status = if tag =~ /-PRE$/
665
+ 'the start of '
666
+ elsif tag =~ /-POST$/
667
+ 'the end of '
668
+ else
669
+ ''
670
+ end
671
+
672
+ msg = "Creating Subversion tag to mark #{status}[#{branch}]"
673
+ $watcher.verbose(msg) do
674
+ branch = repos + '/' + branch
675
+ tag = repos + '/' + tag
676
+ cmd = "svn copy -m \"#{msg}\" #{branch} #{tag}"
677
+ # NOTE: Not a 'safe' command because modifies server
678
+ subversion(cmd, false)
679
+ end
680
+ end
681
+
682
+ ######################################################################
683
+ def update_branch! (repos, branch_name)
684
+ msg = "Updating Subversion work directory from [#{branch_name}]"
685
+ $watcher.verbose(msg) do
686
+ subversion("svn up")
687
+ end
688
+ end
689
+
690
+ ######################################################################
691
+ def verify_branch! (repos, branch)
692
+ # Ensures branch exists on repository, or throws exception
693
+
694
+ # This block sets up our condition handling mechanism:
695
+ # Make 2 passes: if first fails because of missing branch,
696
+ # attempt to create the branch.
697
+ # For other failures, re-raise exception.
698
+ params = {:failure => :error, :tries => 2, :proc => lambda do |e|
699
+ if e.message =~ /branch not found/
700
+ make_repository_path!(repos, branch)
701
+ else
702
+ # if not a simple "branch not found" error, then punt
703
+ raise
704
+ end
705
+ end
706
+ }
707
+
708
+ # This code does the verification, and invokes condition handling
709
+ $watcher.verbose("Verifying repository branch [#{branch}]", params) do
710
+ unless branch_exists?(repos, branch)
711
+ raise RuntimeError.new("branch not found [#{branch}]")
712
+ end
713
+ end
714
+ end
715
+
716
+ ######################################################################
717
+ def verify_path! (path)
718
+ # Ensures path exists on local system, or throws exception
719
+
720
+ # This block sets up our condition handling mechanism:
721
+ # Make 2 passes: if first fails because of missing path,
722
+ # attempt to create the path.
723
+ # For other failures, re-raise exception.
724
+ params = {:failure => :error, :tries => 2, :proc => lambda do |e|
725
+ if e.message =~ /path not found/
726
+ FileUtils.mkdir_p(path)
727
+ else
728
+ # if not a simple "path not found" error, then punt
729
+ raise
730
+ end
731
+ end
732
+ }
733
+
734
+ # This code does the verification, and invokes condition handling
735
+ $watcher.verbose("Verifying path exists [#{path}]", params) do
736
+ unless File.directory?(path)
737
+ raise RuntimeError.new("path not found [#{path}]")
738
+ end
739
+ end
740
+ end
741
+
742
+ ######################################################################
743
+ def verify_subversion_commit (branch_dir)
744
+ msg = "Verifying data in this Subversion working directory are"
745
+ msg << " committed [#{branch_dir}]"
746
+ $watcher.verbose(msg) do
747
+ unless subversion_committed?(branch_dir)
748
+ msg = "Must commit changes to working directory first."
749
+ raise RuntimeError.new(msg)
750
+ end
751
+ end
752
+ end
753
+
754
+ ######################################################################
755
+ # DO NOT ENCLOSE; MUST RUN AT TOP-LEVEL OF FILE FOR BIN INSTALLATION.
756
+ # if $0 == __FILE__
757
+ start_dir = Dir.getwd()
758
+
759
+ force = false # Do not force a change if error
760
+ switch = false # Switch present working directory
761
+ options = true
762
+ verbosity = :verbose
763
+
764
+ # $DEBUG = true
765
+
766
+ while options
767
+ case ARGV[0]
768
+ when '-d' : verbosity = :debug ; ARGV.shift
769
+ when '-f' : force = true ; ARGV.shift
770
+ when '-s', '--switch' : switch = true ; ARGV.shift
771
+ when '-v' : verbosity = :verbose ; ARGV.shift
772
+ when '--version'
773
+ puts Svnbranch::VERSION
774
+ exit
775
+ when '-q' : verbosity = :always ; ARGV.shift
776
+ else options = false
777
+ end
778
+ end
779
+
780
+ $watcher = Watcher.create(:verbosity => verbosity,
781
+ :warn_symbol => 'WARNING',
782
+ :error_symbol => 'ERROR')
783
+
784
+ begin
785
+ errors = false
786
+ $watcher.debug("Validating command line arguments") do
787
+ msg = "usage:\n"
788
+ case ARGV.shift
789
+ when '-h', '--help', 'help'
790
+ man_page
791
+ exit
792
+ when 'create'
793
+ if ARGV.length == 1
794
+ create_branch!(ARGV.shift, force, !switch)
795
+ else
796
+ msg << " #{File.basename($0)} [ -s ] create [<sub-branch>/]<new-branch>\n\n"
797
+ STDERR.puts msg
798
+ raise ArgumentError.new("Invalid command line arguments")
799
+ end
800
+ when 'close'
801
+ if switch == false and ARGV.length == 0
802
+ close_branch!(force)
803
+ else
804
+ msg << " #{File.basename($0)} close\n\n"
805
+ STDERR.puts msg
806
+ raise ArgumentError.new("Invalid command line arguments")
807
+ end
808
+ when 'merge'
809
+ if switch == false and ARGV.length == 1
810
+ merge_branch!(ARGV.shift)
811
+ else
812
+ msg << " #{File.basename($0)} merge [<sub-branch>/]<merge-branch>\n\n"
813
+ STDERR.puts msg
814
+ raise ArgumentError.new("Invalid command line arguments")
815
+ end
816
+ when 'delete'
817
+ if switch == false and ARGV.length == 1
818
+ delete_branch!(ARGV.shift)
819
+ else
820
+ msg << " #{File.basename($0)} delete [<sub_branch>/]<doomed-branch>\n\n"
821
+ STDERR.puts msg
822
+ raise ArgumentError.new("Invalid command line arguments")
823
+ end
824
+ when 'switch'
825
+ if switch == false and ARGV.length == 1
826
+ switch_this_dir!(ARGV.shift)
827
+ else
828
+ msg << " #{File.basename($0)} switch [<sub_branch>/]<switch-branch>\n\n"
829
+ STDERR.puts msg
830
+ raise ArgumentError.new("Invalid command line arguments")
831
+ end
832
+ else
833
+ msg << " #{File.basename($0)} [ -s ] create [<sub-branch>/]<new-branch>\n"
834
+ msg << " #{File.basename($0)} close\n"
835
+ msg << " #{File.basename($0)} merge [<sub-branch>/]<merge-branch>\n"
836
+ msg << " #{File.basename($0)} delete [<sub_branch>/]<doomed-branch>\n"
837
+ msg << " #{File.basename($0)} switch [<sub_branch>/]<switch-branch>\n\n"
838
+ STDERR.puts msg
839
+ raise ArgumentError.new("Invalid command line arguments")
840
+ end
841
+ end
842
+ rescue
843
+ errors = true
844
+ Dir.chdir(start_dir)
845
+ end
846
+
847
+ exit errors == false
848
+ #end