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