svnauto 1.0.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/INSTALL +48 -0
- data/LICENSE +22 -0
- data/README +81 -0
- data/THANKS +8 -0
- data/TODO +9 -0
- data/bin/sc +35 -0
- data/doc/manual.txt +241 -0
- data/lib/sc.rb +38 -0
- data/lib/sc/command.rb +118 -0
- data/lib/sc/commands/bug.rb +161 -0
- data/lib/sc/commands/checkout.rb +71 -0
- data/lib/sc/commands/config.rb +58 -0
- data/lib/sc/commands/create.rb +68 -0
- data/lib/sc/commands/experimental.rb +142 -0
- data/lib/sc/commands/info.rb +54 -0
- data/lib/sc/commands/list.rb +40 -0
- data/lib/sc/commands/release.rb +98 -0
- data/lib/sc/config_file.rb +87 -0
- data/lib/sc/constants.rb +40 -0
- data/lib/sc/dispatcher.rb +189 -0
- data/lib/sc/path.rb +51 -0
- data/lib/sc/project.rb +265 -0
- data/lib/sc/repository.rb +108 -0
- data/lib/sc/svn.rb +96 -0
- data/lib/sc/version.rb +111 -0
- data/test/projfiles/file_one +3 -0
- data/test/projfiles/file_two +3 -0
- data/test/setup.rb +97 -0
- data/test/test_bug.rb +55 -0
- data/test/test_checkout.rb +20 -0
- data/test/test_create.rb +20 -0
- data/test/test_experimental.rb +86 -0
- data/test/test_release.rb +37 -0
- data/test/test_version.rb +41 -0
- metadata +99 -0
@@ -0,0 +1,108 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
#
|
24
|
+
################################################################################
|
25
|
+
module SC
|
26
|
+
################################################################################
|
27
|
+
class Repository
|
28
|
+
################################################################################
|
29
|
+
# The URL to the repository
|
30
|
+
attr_accessor :url
|
31
|
+
|
32
|
+
################################################################################
|
33
|
+
# The name the user gave for this repository
|
34
|
+
attr_accessor :name
|
35
|
+
|
36
|
+
################################################################################
|
37
|
+
# The local directory where checkouts happen
|
38
|
+
attr_accessor :workspace
|
39
|
+
|
40
|
+
################################################################################
|
41
|
+
# Additional options to send to svn
|
42
|
+
attr_accessor :options
|
43
|
+
|
44
|
+
################################################################################
|
45
|
+
# Prompt the user to enter the necessary attributes for a repository
|
46
|
+
def self.ask
|
47
|
+
options = {}
|
48
|
+
|
49
|
+
options[:name] = Constants::TERMINAL.ask("Repository Name (used with sc -r): ")
|
50
|
+
options[:url] = Constants::TERMINAL.ask("Repository URL: ")
|
51
|
+
|
52
|
+
url = URI.parse(options[:url])
|
53
|
+
if url.scheme.nil? and Constants::TERMINAL.agree("Repository URL is missing protocol, assume it's local? ")
|
54
|
+
url.scheme = 'file'
|
55
|
+
end
|
56
|
+
|
57
|
+
if url.scheme.downcase == 'file'
|
58
|
+
url.path = File.expand_path(url.path)
|
59
|
+
|
60
|
+
if !File.exist?(url.path) and Constants::TERMINAL.agree("Local repository does not exist, should I create it? ")
|
61
|
+
create_local(url.path)
|
62
|
+
end
|
63
|
+
|
64
|
+
options[:url] = "file://#{url.path}"
|
65
|
+
end
|
66
|
+
|
67
|
+
options[:workspace] = Constants::TERMINAL.ask("Directory where checkouts go (can be relative to ~/): ") do |q|
|
68
|
+
q.default = default_workspace(options[:name])
|
69
|
+
end
|
70
|
+
|
71
|
+
self.new(options)
|
72
|
+
end
|
73
|
+
|
74
|
+
################################################################################
|
75
|
+
def self.create_local (path)
|
76
|
+
Dir.mkdir(path) unless File.exist?(path)
|
77
|
+
system('svnadmin', 'create', path, '--fs-type', 'fsfs') or exit 1
|
78
|
+
end
|
79
|
+
|
80
|
+
################################################################################
|
81
|
+
def self.default_workspace (name)
|
82
|
+
"develop/#{name.gsub(/[^\w_-]+/, '_').downcase}"
|
83
|
+
end
|
84
|
+
|
85
|
+
################################################################################
|
86
|
+
# setup the details for a repository object
|
87
|
+
def initialize (options={})
|
88
|
+
@url = options[:url]
|
89
|
+
@name = options[:name]
|
90
|
+
@workspace = options[:workspace]
|
91
|
+
@options = options[:options]
|
92
|
+
end
|
93
|
+
|
94
|
+
################################################################################
|
95
|
+
# get the full path for the workspace
|
96
|
+
def workspace
|
97
|
+
Path.absolute_from_home(@workspace || self.class.default_workspace(@name))
|
98
|
+
end
|
99
|
+
|
100
|
+
################################################################################
|
101
|
+
# convert the repository to a string
|
102
|
+
def to_s
|
103
|
+
"#{@name} (#{@url})"
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
################################################################################
|
data/lib/sc/svn.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
#
|
24
|
+
################################################################################
|
25
|
+
module SC
|
26
|
+
class Svn
|
27
|
+
################################################################################
|
28
|
+
# Run a svn subcommand
|
29
|
+
def self._svn (*args)
|
30
|
+
subcommand = caller(0)[0].sub(/^.*:in\s`([^']+).*$/, '\1')
|
31
|
+
shell_line = "#{subcommand} #{args.join(' ')}"
|
32
|
+
|
33
|
+
Constants::TERMINAL.say(Constants::TERMINAL.color("svn #{shell_line}", :yellow)) if $DEBUG
|
34
|
+
|
35
|
+
if block_given?
|
36
|
+
`svn #{shell_line} 2>&1`.each {|line| yield(line)}
|
37
|
+
else
|
38
|
+
system("svn #{shell_line}")
|
39
|
+
end
|
40
|
+
|
41
|
+
if $? != 0 and !['info', 'list', 'merge'].include?(subcommand)
|
42
|
+
raise "svn command failed: #{shell_line}"
|
43
|
+
end
|
44
|
+
|
45
|
+
$? == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
################################################################################
|
49
|
+
# Check to see if the given path exists in the repository
|
50
|
+
def self.has_path (path)
|
51
|
+
self.list(path) do |line|
|
52
|
+
return false if line.match(/^svn:/)
|
53
|
+
end
|
54
|
+
|
55
|
+
true
|
56
|
+
end
|
57
|
+
|
58
|
+
################################################################################
|
59
|
+
# create a branch using svn copy
|
60
|
+
def self.branch (project, source, dest)
|
61
|
+
return if self.has_path(dest)
|
62
|
+
|
63
|
+
relative_dest = dest.sub("#{project.url}/", '')
|
64
|
+
dest_dirs = relative_dest.split(/\//)
|
65
|
+
dest_dirs.pop # don't need the last one
|
66
|
+
dest_path = project.url
|
67
|
+
|
68
|
+
branch_or_tag = dest.match(/branches/) ? 'branch' : 'tag'
|
69
|
+
|
70
|
+
dest_dirs.each do |dir|
|
71
|
+
dest_path << "/#{dir}"
|
72
|
+
self.mkdir('-m', "'#{Constants::ME}: creating #{branch_or_tag} path #{dest_path}'", dest_path) unless self.has_path(dest_path)
|
73
|
+
end
|
74
|
+
|
75
|
+
self.copy('-m', "'#{Constants::ME}: creating #{branch_or_tag} #{relative_dest}'", source, dest)
|
76
|
+
end
|
77
|
+
|
78
|
+
################################################################################
|
79
|
+
# some magic to add class methods for each svn subcommand
|
80
|
+
class << self
|
81
|
+
`svn help 2>&1`.each do |line|
|
82
|
+
if line.match(/^Available subcommands/) .. line.match(/^\s*$/)
|
83
|
+
next if line.match(/^Available/) # there has to be a better way
|
84
|
+
subcommand = line.chomp.strip
|
85
|
+
subcommand.sub!(/\s.*$/, '')
|
86
|
+
next if line.match(/^\s*$/)
|
87
|
+
eval "alias #{subcommand} _svn"
|
88
|
+
end
|
89
|
+
|
90
|
+
raise "the svn command line client is not in your path" unless $? == 0
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
################################################################################
|
data/lib/sc/version.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
################################################################################
|
2
|
+
#
|
3
|
+
# Copyright (C) 2006 Peter J Jones (pjones@pmade.com)
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
# a copy of this software and associated documentation files (the
|
7
|
+
# "Software"), to deal in the Software without restriction, including
|
8
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
# the following conditions:
|
12
|
+
#
|
13
|
+
# The above copyright notice and this permission notice shall be
|
14
|
+
# included in all copies or substantial portions of the Software.
|
15
|
+
#
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
+
#
|
24
|
+
################################################################################
|
25
|
+
module SC
|
26
|
+
################################################################################
|
27
|
+
# Help deal with version numbers. Verson numbers are made up of three parts: the major,
|
28
|
+
# minor, and macro numbers. These numbers are separated by periods:
|
29
|
+
#
|
30
|
+
# MAJOR.MINOR.MACRO as in 1.0.2
|
31
|
+
#
|
32
|
+
# In SC, release branches are created for MAJOR.MINOR release numbers. Release
|
33
|
+
# tags are used for MAJOR.MINOR.MACRO. That means that version 1.1 is open for
|
34
|
+
# bug fixes and feature enhancements, but version 1.1.4 is locked and can't be
|
35
|
+
# updated.
|
36
|
+
#
|
37
|
+
class Version
|
38
|
+
################################################################################
|
39
|
+
# version objects can be compared
|
40
|
+
include Comparable
|
41
|
+
|
42
|
+
################################################################################
|
43
|
+
# major release number
|
44
|
+
attr_reader :major
|
45
|
+
|
46
|
+
################################################################################
|
47
|
+
# minor release number
|
48
|
+
attr_reader :minor
|
49
|
+
|
50
|
+
################################################################################
|
51
|
+
# macro release number
|
52
|
+
attr_reader :macro
|
53
|
+
|
54
|
+
################################################################################
|
55
|
+
# the encoded version number for sorting
|
56
|
+
attr_reader :encoded
|
57
|
+
|
58
|
+
################################################################################
|
59
|
+
# create a new version object with the given version
|
60
|
+
def initialize (version_string)
|
61
|
+
parse_version(version_string)
|
62
|
+
end
|
63
|
+
|
64
|
+
################################################################################
|
65
|
+
# convert back to a string
|
66
|
+
def to_s
|
67
|
+
str = "#{@major}.#{@minor}"
|
68
|
+
str << ".#{@macro}" unless @macro.nil?
|
69
|
+
str
|
70
|
+
end
|
71
|
+
|
72
|
+
################################################################################
|
73
|
+
# give back just major.minor
|
74
|
+
def major_minor
|
75
|
+
"#{@major}.#{@minor}"
|
76
|
+
end
|
77
|
+
|
78
|
+
################################################################################
|
79
|
+
# update encoding and whatnot on change
|
80
|
+
[:major, :minor, :macro].each do |part|
|
81
|
+
define_method "#{part}=", lambda { |value|
|
82
|
+
instance_variable_set("@#{part}", value)
|
83
|
+
parse_version(self.to_s)
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
################################################################################
|
88
|
+
# allow comparison
|
89
|
+
def <=> (other)
|
90
|
+
@encoded <=> other.encoded
|
91
|
+
end
|
92
|
+
|
93
|
+
################################################################################
|
94
|
+
private
|
95
|
+
|
96
|
+
################################################################################
|
97
|
+
def parse_version (version_string)
|
98
|
+
@major, @minor, @macro = version_string.split(/\./, 3)
|
99
|
+
|
100
|
+
@encoded = [@major, @minor, @macro].inject("") do |enc, i|
|
101
|
+
part = i.dup if i
|
102
|
+
part ||= '0'
|
103
|
+
raise "malformed version string #{version_string}, part #{part}" unless part.match(/^\d+$/)
|
104
|
+
enc << part.rjust(6, '0')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
################################################################################
|
110
|
+
end
|
111
|
+
################################################################################
|
data/test/setup.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'sc'
|
2
|
+
$DEBUG = true
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
module SC
|
6
|
+
module Test
|
7
|
+
################################################################################
|
8
|
+
def setup
|
9
|
+
@config_file = File.join(File.dirname(__FILE__), 'sc_config_file.yml')
|
10
|
+
ENV[SC::ConfigFile::ENV_OVERRIDE] = @config_file
|
11
|
+
|
12
|
+
@repository = File.join(File.dirname(__FILE__), 'repos')
|
13
|
+
@sandbox = File.join(File.dirname(__FILE__), 'sandbox')
|
14
|
+
@projfiles = File.join(File.dirname(__FILE__), 'projfiles')
|
15
|
+
|
16
|
+
[@repository, @sandbox].each do |dir|
|
17
|
+
system('mkdir', dir) or raise "mkdir #{dir}: failed"
|
18
|
+
end
|
19
|
+
|
20
|
+
SC::Repository.create_local(@repository)
|
21
|
+
|
22
|
+
cf = SC::ConfigFile.new
|
23
|
+
cf[:repositories] << SC::Repository.new({
|
24
|
+
:url => "file://#{File.expand_path(@repository)}",
|
25
|
+
:name => "test",
|
26
|
+
:workspace => File.expand_path(@sandbox)
|
27
|
+
})
|
28
|
+
cf.save
|
29
|
+
|
30
|
+
run_sc(%W(create -f test))
|
31
|
+
@project = SC::Project.new(:name => 'test', :repository => cf[:repositories].first)
|
32
|
+
end
|
33
|
+
|
34
|
+
################################################################################
|
35
|
+
def teardown
|
36
|
+
File.unlink(@config_file) if File.exists?(@config_file)
|
37
|
+
|
38
|
+
[@repository, @sandbox].each do |dir|
|
39
|
+
system('rm', '-rf', dir) or raise "rm -rf #{dir}: failed"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
################################################################################
|
44
|
+
def run_sc (args)
|
45
|
+
args[1,0] = '--force' if $add_force_flag
|
46
|
+
SC::Dispatcher.new.run(%W(-rtest -ptest).concat(args))
|
47
|
+
end
|
48
|
+
|
49
|
+
################################################################################
|
50
|
+
def add_files (to)
|
51
|
+
%W(file_one file_two).each do |file|
|
52
|
+
system('cp', File.join(@projfiles, file), to)
|
53
|
+
end
|
54
|
+
|
55
|
+
system("cd #{to}; svn add *; svn ci -m add")
|
56
|
+
end
|
57
|
+
|
58
|
+
################################################################################
|
59
|
+
def checkout
|
60
|
+
run_sc(%W(checkout))
|
61
|
+
assert(File.exist?(File.join(@sandbox, 'test-trunk')))
|
62
|
+
end
|
63
|
+
|
64
|
+
################################################################################
|
65
|
+
def setup_project_files
|
66
|
+
checkout
|
67
|
+
add_files(File.join(@sandbox, 'test-trunk'))
|
68
|
+
end
|
69
|
+
|
70
|
+
################################################################################
|
71
|
+
def make_release (version, *extras)
|
72
|
+
setup_project_files
|
73
|
+
run_sc(['release', '-f', extras, version.to_s].flatten)
|
74
|
+
assert(SC::Svn.has_path(@project.release("#{version.major_minor}/file_one")))
|
75
|
+
assert(File.exist?(File.join(@sandbox, "test-rel-#{version.major_minor}")))
|
76
|
+
end
|
77
|
+
|
78
|
+
################################################################################
|
79
|
+
def interactive
|
80
|
+
$inside_interactive ||= 0
|
81
|
+
$inside_interactive += 1
|
82
|
+
|
83
|
+
$add_force_flag = true if $inside_interactive == 1
|
84
|
+
yield
|
85
|
+
$add_force_flag = false if $inside_interactive == 1
|
86
|
+
|
87
|
+
if $inside_interactive == 1
|
88
|
+
teardown
|
89
|
+
setup
|
90
|
+
yield
|
91
|
+
end
|
92
|
+
|
93
|
+
$inside_interactive -= 1
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
data/test/test_bug.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'test/setup.rb'
|
2
|
+
|
3
|
+
class TestBug < Test::Unit::TestCase
|
4
|
+
include SC::Test
|
5
|
+
|
6
|
+
################################################################################
|
7
|
+
def test_create
|
8
|
+
interactive do
|
9
|
+
make_release(SC::Version.new('1.0'))
|
10
|
+
run_sc(%W(bug -r 1.0 1))
|
11
|
+
assert(SC::Svn.has_path(@project.branches('bug/1/file_one')))
|
12
|
+
assert(SC::Svn.has_path(@project.tags('bug/PRE-1/file_one')))
|
13
|
+
|
14
|
+
@should_checkout_to = File.join(@sandbox, 'test-bug-1')
|
15
|
+
assert(File.exist?(@should_checkout_to))
|
16
|
+
|
17
|
+
prop = ''
|
18
|
+
SC::Svn.propget(SC::Bug::REL_BRN_PROP, @should_checkout_to) do |line|
|
19
|
+
prop << line.chomp
|
20
|
+
end
|
21
|
+
|
22
|
+
assert_equal('1.0', prop.strip)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
################################################################################
|
27
|
+
def test_good_close
|
28
|
+
interactive do
|
29
|
+
test_create
|
30
|
+
|
31
|
+
File.open(File.join(@should_checkout_to, 'file_one'), 'a') do |file|
|
32
|
+
file << "Line for Bug Fix 1"
|
33
|
+
end
|
34
|
+
|
35
|
+
Dir.chdir(@should_checkout_to) do
|
36
|
+
SC::Svn.commit('-m', "'testing merge'")
|
37
|
+
end
|
38
|
+
|
39
|
+
run_sc(%W(bug --close 1))
|
40
|
+
|
41
|
+
has_line = false
|
42
|
+
SC::Svn.cat(@project.branches('rel/1.0/file_one')) do |line|
|
43
|
+
has_line = true if line.match(/Line for Bug Fix 1/)
|
44
|
+
end
|
45
|
+
|
46
|
+
assert(has_line)
|
47
|
+
|
48
|
+
has_line = false
|
49
|
+
SC::Svn.cat("#{@project.trunk}/file_one") do |line|
|
50
|
+
has_line = true if line.match(/Line for Bug Fix 1/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|