mbailey-capistrano 2.5.5
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/CHANGELOG.rdoc +761 -0
- data/Manifest +104 -0
- data/README.rdoc +66 -0
- data/Rakefile +34 -0
- data/bin/cap +4 -0
- data/bin/capify +78 -0
- data/examples/sample.rb +14 -0
- data/lib/capistrano/callback.rb +45 -0
- data/lib/capistrano/cli/execute.rb +84 -0
- data/lib/capistrano/cli/help.rb +125 -0
- data/lib/capistrano/cli/help.txt +75 -0
- data/lib/capistrano/cli/options.rb +224 -0
- data/lib/capistrano/cli/ui.rb +40 -0
- data/lib/capistrano/cli.rb +47 -0
- data/lib/capistrano/command.rb +283 -0
- data/lib/capistrano/configuration/actions/file_transfer.rb +47 -0
- data/lib/capistrano/configuration/actions/inspect.rb +46 -0
- data/lib/capistrano/configuration/actions/invocation.rb +293 -0
- data/lib/capistrano/configuration/callbacks.rb +148 -0
- data/lib/capistrano/configuration/connections.rb +200 -0
- data/lib/capistrano/configuration/execution.rb +132 -0
- data/lib/capistrano/configuration/loading.rb +197 -0
- data/lib/capistrano/configuration/namespaces.rb +197 -0
- data/lib/capistrano/configuration/roles.rb +73 -0
- data/lib/capistrano/configuration/servers.rb +85 -0
- data/lib/capistrano/configuration/variables.rb +127 -0
- data/lib/capistrano/configuration.rb +43 -0
- data/lib/capistrano/errors.rb +15 -0
- data/lib/capistrano/extensions.rb +57 -0
- data/lib/capistrano/logger.rb +59 -0
- data/lib/capistrano/processable.rb +53 -0
- data/lib/capistrano/recipes/compat.rb +32 -0
- data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
- data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
- data/lib/capistrano/recipes/deploy/remote_dependency.rb +105 -0
- data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
- data/lib/capistrano/recipes/deploy/scm/base.rb +196 -0
- data/lib/capistrano/recipes/deploy/scm/bzr.rb +83 -0
- data/lib/capistrano/recipes/deploy/scm/cvs.rb +152 -0
- data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
- data/lib/capistrano/recipes/deploy/scm/git.rb +271 -0
- data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
- data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
- data/lib/capistrano/recipes/deploy/scm/perforce.rb +133 -0
- data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
- data/lib/capistrano/recipes/deploy/scm.rb +19 -0
- data/lib/capistrano/recipes/deploy/strategy/base.rb +79 -0
- data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/copy.rb +210 -0
- data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
- data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
- data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +56 -0
- data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
- data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/recipes/deploy.rb +562 -0
- data/lib/capistrano/recipes/standard.rb +37 -0
- data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
- data/lib/capistrano/recipes/upgrade.rb +33 -0
- data/lib/capistrano/role.rb +102 -0
- data/lib/capistrano/server_definition.rb +56 -0
- data/lib/capistrano/shell.rb +260 -0
- data/lib/capistrano/ssh.rb +99 -0
- data/lib/capistrano/task_definition.rb +70 -0
- data/lib/capistrano/transfer.rb +216 -0
- data/lib/capistrano/version.rb +18 -0
- data/lib/capistrano.rb +2 -0
- data/setup.rb +1346 -0
- data/test/cli/execute_test.rb +132 -0
- data/test/cli/help_test.rb +165 -0
- data/test/cli/options_test.rb +317 -0
- data/test/cli/ui_test.rb +28 -0
- data/test/cli_test.rb +17 -0
- data/test/command_test.rb +286 -0
- data/test/configuration/actions/file_transfer_test.rb +61 -0
- data/test/configuration/actions/inspect_test.rb +65 -0
- data/test/configuration/actions/invocation_test.rb +224 -0
- data/test/configuration/callbacks_test.rb +220 -0
- data/test/configuration/connections_test.rb +349 -0
- data/test/configuration/execution_test.rb +175 -0
- data/test/configuration/loading_test.rb +132 -0
- data/test/configuration/namespace_dsl_test.rb +311 -0
- data/test/configuration/roles_test.rb +144 -0
- data/test/configuration/servers_test.rb +121 -0
- data/test/configuration/variables_test.rb +184 -0
- data/test/configuration_test.rb +88 -0
- data/test/deploy/local_dependency_test.rb +76 -0
- data/test/deploy/remote_dependency_test.rb +114 -0
- data/test/deploy/scm/accurev_test.rb +23 -0
- data/test/deploy/scm/base_test.rb +55 -0
- data/test/deploy/scm/git_test.rb +167 -0
- data/test/deploy/scm/mercurial_test.rb +129 -0
- data/test/deploy/strategy/copy_test.rb +258 -0
- data/test/extensions_test.rb +69 -0
- data/test/fixtures/cli_integration.rb +5 -0
- data/test/fixtures/config.rb +5 -0
- data/test/fixtures/custom.rb +3 -0
- data/test/logger_test.rb +123 -0
- data/test/role_test.rb +11 -0
- data/test/server_definition_test.rb +121 -0
- data/test/shell_test.rb +90 -0
- data/test/ssh_test.rb +104 -0
- data/test/task_definition_test.rb +101 -0
- data/test/transfer_test.rb +160 -0
- data/test/utils.rb +38 -0
- metadata +205 -0
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/scm/base'
|
2
|
+
require 'rexml/xpath'
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
module Capistrano
|
6
|
+
module Deploy
|
7
|
+
module SCM
|
8
|
+
# Accurev bridge for use by Capistrano. This implementation does not
|
9
|
+
# implement all features of a Capistrano SCM module. The ones that are
|
10
|
+
# left out are either exceedingly difficult to implement with Accurev
|
11
|
+
# or are considered bad form.
|
12
|
+
#
|
13
|
+
# When using this module in a project, the following variables are used:
|
14
|
+
# * :repository - This should match the depot that code lives in. If your code
|
15
|
+
# exists in a subdirectory, you can append the path depot.
|
16
|
+
# eg. foo-depot/bar_dir
|
17
|
+
# * :stream - The stream in the depot that code should be pulled from. If
|
18
|
+
# left blank, the depot stream will be used
|
19
|
+
# * :revision - Should be in the form 'stream/transaction'.
|
20
|
+
class Accurev < Base
|
21
|
+
include REXML
|
22
|
+
default_command 'accurev'
|
23
|
+
|
24
|
+
# Defines pseudo-revision value for the most recent changes to be deployed.
|
25
|
+
def head
|
26
|
+
"#{stream}/highest"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Given an Accurev revision identifier, this method returns an identifier that
|
30
|
+
# can be used for later SCM calls. This returned identifier will not
|
31
|
+
# change as a result of further SCM activity.
|
32
|
+
def query_revision(revision)
|
33
|
+
internal_revision = InternalRevision.parse(revision)
|
34
|
+
return revision unless internal_revision.psuedo_revision?
|
35
|
+
|
36
|
+
logger.debug("Querying for real revision for #{internal_revision}")
|
37
|
+
rev_stream = internal_revision.stream
|
38
|
+
|
39
|
+
logger.debug("Determining what type of stream #{rev_stream} is...")
|
40
|
+
stream_xml = yield show_streams_for(rev_stream)
|
41
|
+
stream_doc = Document.new(stream_xml)
|
42
|
+
type = XPath.first(stream_doc, '//streams/stream/@type').value
|
43
|
+
|
44
|
+
case type
|
45
|
+
when 'snapshot'
|
46
|
+
InternalRevision.new(rev_stream, 'highest').to_s
|
47
|
+
else
|
48
|
+
logger.debug("Getting latest transaction id in #{rev_stream}")
|
49
|
+
# Doing another yield for a second Accurev call. Hopefully this is ok.
|
50
|
+
hist_xml = yield scm(:hist, '-ftx', '-s', rev_stream, '-t', 'now.1')
|
51
|
+
hist_doc = Document.new(hist_xml)
|
52
|
+
transaction_id = XPath.first(hist_doc, '//AcResponse/transaction/@id').value
|
53
|
+
InternalRevision.new(stream, transaction_id).to_s
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Pops a copy of the code for the specified Accurev revision identifier.
|
58
|
+
# The revision identifier is represented as a stream & transaction ID combo.
|
59
|
+
# Accurev can only pop a particular transaction if a stream is created on the server
|
60
|
+
# with a time basis of that transaction id. Therefore, we will create a stream with
|
61
|
+
# the required criteria and pop that.
|
62
|
+
def export(revision_id, destination)
|
63
|
+
revision = InternalRevision.parse(revision_id)
|
64
|
+
logger.debug("Exporting #{revision.stream}/#{revision.transaction_id} to #{destination}")
|
65
|
+
|
66
|
+
commands = [
|
67
|
+
change_or_create_stream("#{revision.stream}-capistrano-deploy", revision),
|
68
|
+
"mkdir -p #{destination}",
|
69
|
+
scm_quiet(:pop, "-Rv #{stream}", "-L #{destination}", "'/./#{subdir}'")
|
70
|
+
]
|
71
|
+
if subdir
|
72
|
+
commands.push(
|
73
|
+
"mv #{destination}/#{subdir}/* #{destination}",
|
74
|
+
"rm -rf #{File.join(destination, subdir)}"
|
75
|
+
)
|
76
|
+
end
|
77
|
+
commands.join(' && ')
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns the command needed to show the changes that exist between the two revisions.
|
81
|
+
def log(from, to=head)
|
82
|
+
logger.info("Getting transactions between #{from} and #{to}")
|
83
|
+
from_rev = InternalRevision.parse(from)
|
84
|
+
to_rev = InternalRevision.parse(to)
|
85
|
+
|
86
|
+
[
|
87
|
+
scm(:hist, '-s', from_rev.stream, '-t', "#{to_rev.transaction_id}-#{from_rev.transaction_id}"),
|
88
|
+
"sed -e '/transaction #{from_rev.transaction_id}/ { Q }'"
|
89
|
+
].join(' | ')
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the command needed to show the diff between what is deployed and what is
|
93
|
+
# pending. Because Accurev can not do this task without creating some streams,
|
94
|
+
# two time basis streams will be created for the purposes of doing the diff.
|
95
|
+
def diff(from, to=head)
|
96
|
+
from = InternalRevision.parse(from)
|
97
|
+
to = InternalRevision.parse(to)
|
98
|
+
|
99
|
+
from_stream = "#{from.stream}-capistrano-diff-from"
|
100
|
+
to_stream = "#{to.stream}-capistrano-diff-to"
|
101
|
+
|
102
|
+
[
|
103
|
+
change_or_create_stream(from_stream, from),
|
104
|
+
change_or_create_stream(to_stream, to),
|
105
|
+
scm(:diff, '-v', from_stream, '-V', to_stream, '-a')
|
106
|
+
].join(' && ')
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
def depot
|
111
|
+
repository.split('/')[0]
|
112
|
+
end
|
113
|
+
|
114
|
+
def stream
|
115
|
+
variable(:stream) || depot
|
116
|
+
end
|
117
|
+
|
118
|
+
def subdir
|
119
|
+
repository.split('/')[1..-1].join('/') unless repository.index('/').nil?
|
120
|
+
end
|
121
|
+
|
122
|
+
def change_or_create_stream(name, revision)
|
123
|
+
[
|
124
|
+
scm_quiet(:mkstream, '-b', revision.stream, '-s', name, '-t', revision.transaction_id),
|
125
|
+
scm_quiet(:chstream, '-b', revision.stream, '-s', name, '-t', revision.transaction_id)
|
126
|
+
].join('; ')
|
127
|
+
end
|
128
|
+
|
129
|
+
def show_streams_for(stream)
|
130
|
+
scm :show, '-fx', '-s', stream, :streams
|
131
|
+
end
|
132
|
+
|
133
|
+
def scm_quiet(*args)
|
134
|
+
scm(*args) + (variable(:scm_verbose) ? '' : '&> /dev/null')
|
135
|
+
end
|
136
|
+
|
137
|
+
class InternalRevision
|
138
|
+
attr_reader :stream, :transaction_id
|
139
|
+
|
140
|
+
def self.parse(string)
|
141
|
+
match = /([^\/]+)(\/(.+)){0,1}/.match(string)
|
142
|
+
raise "Unrecognized revision identifier: #{string}" unless match
|
143
|
+
|
144
|
+
stream = match[1]
|
145
|
+
transaction_id = match[3] || 'highest'
|
146
|
+
InternalRevision.new(stream, transaction_id)
|
147
|
+
end
|
148
|
+
|
149
|
+
def initialize(stream, transaction_id)
|
150
|
+
@stream = stream
|
151
|
+
@transaction_id = transaction_id
|
152
|
+
end
|
153
|
+
|
154
|
+
def psuedo_revision?
|
155
|
+
@transaction_id == 'highest'
|
156
|
+
end
|
157
|
+
|
158
|
+
def to_s
|
159
|
+
"#{stream}/#{transaction_id}"
|
160
|
+
end
|
161
|
+
|
162
|
+
def ==(other)
|
163
|
+
(stream == other.stream) && (transaction_id == other.transaction_id)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,196 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Deploy
|
3
|
+
module SCM
|
4
|
+
|
5
|
+
# The ancestor class for all Capistrano SCM implementations. It provides
|
6
|
+
# minimal infrastructure for subclasses to build upon and override.
|
7
|
+
#
|
8
|
+
# Note that subclasses that implement this abstract class only return
|
9
|
+
# the commands that need to be executed--they do not execute the commands
|
10
|
+
# themselves. In this way, the deployment method may execute the commands
|
11
|
+
# either locally or remotely, as necessary.
|
12
|
+
class Base
|
13
|
+
class << self
|
14
|
+
# If no parameters are given, it returns the current configured
|
15
|
+
# name of the command-line utility of this SCM. If a parameter is
|
16
|
+
# given, the defeault command is set to that value.
|
17
|
+
def default_command(value=nil)
|
18
|
+
if value
|
19
|
+
@default_command = value
|
20
|
+
else
|
21
|
+
@default_command
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Wraps an SCM instance and forces all messages sent to it to be
|
27
|
+
# relayed to the underlying SCM instance, in "local" mode. See
|
28
|
+
# Base#local.
|
29
|
+
class LocalProxy
|
30
|
+
def initialize(scm)
|
31
|
+
@scm = scm
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(sym, *args, &block)
|
35
|
+
@scm.local { return @scm.send(sym, *args, &block) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# The options available for this SCM instance to reference. Should be
|
40
|
+
# treated like a hash.
|
41
|
+
attr_reader :configuration
|
42
|
+
|
43
|
+
# Creates a new SCM instance with the given configuration options.
|
44
|
+
def initialize(configuration={})
|
45
|
+
@configuration = configuration
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns a proxy that wraps the SCM instance and forces it to operate
|
49
|
+
# in "local" mode, which changes how variables are looked up in the
|
50
|
+
# configuration. Normally, if the value of a variable "foo" is needed,
|
51
|
+
# it is queried for in the configuration as "foo". However, in "local"
|
52
|
+
# mode, first "local_foo" would be looked for, and only if it is not
|
53
|
+
# found would "foo" be used. This allows for both (e.g.) "scm_command"
|
54
|
+
# and "local_scm_command" to be set, if the two differ.
|
55
|
+
#
|
56
|
+
# Alternatively, it may be called with a block, and for the duration of
|
57
|
+
# the block, all requests on this configuration object will be
|
58
|
+
# considered local.
|
59
|
+
def local
|
60
|
+
if block_given?
|
61
|
+
begin
|
62
|
+
saved, @local_mode = @local_mode, true
|
63
|
+
yield
|
64
|
+
ensure
|
65
|
+
@local_mode = saved
|
66
|
+
end
|
67
|
+
else
|
68
|
+
LocalProxy.new(self)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns true if running in "local" mode. See #local.
|
73
|
+
def local?
|
74
|
+
@local_mode
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the string used to identify the latest revision in the
|
78
|
+
# repository. This will be passed as the "revision" parameter of
|
79
|
+
# the methods below.
|
80
|
+
def head
|
81
|
+
raise NotImplementedError, "`head' is not implemented by #{self.class.name}"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Checkout a copy of the repository, at the given +revision+, to the
|
85
|
+
# given +destination+. The checkout is suitable for doing development
|
86
|
+
# work in, e.g. allowing subsequent commits and updates.
|
87
|
+
def checkout(revision, destination)
|
88
|
+
raise NotImplementedError, "`checkout' is not implemented by #{self.class.name}"
|
89
|
+
end
|
90
|
+
|
91
|
+
# Resynchronize the working copy in +destination+ to the specified
|
92
|
+
# +revision+.
|
93
|
+
def sync(revision, destination)
|
94
|
+
raise NotImplementedError, "`sync' is not implemented by #{self.class.name}"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Compute the difference between the two revisions, +from+ and +to+.
|
98
|
+
def diff(from, to=nil)
|
99
|
+
raise NotImplementedError, "`diff' is not implemented by #{self.class.name}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Return a log of all changes between the two specified revisions,
|
103
|
+
# +from+ and +to+, inclusive.
|
104
|
+
def log(from, to=nil)
|
105
|
+
raise NotImplementedError, "`log' is not implemented by #{self.class.name}"
|
106
|
+
end
|
107
|
+
|
108
|
+
# If the given revision represents a "real" revision, this should
|
109
|
+
# simply return the revision value. If it represends a pseudo-revision
|
110
|
+
# (like Subversions "HEAD" identifier), it should yield a string
|
111
|
+
# containing the commands that, when executed will return a string
|
112
|
+
# that this method can then extract the real revision from.
|
113
|
+
def query_revision(revision)
|
114
|
+
raise NotImplementedError, "`query_revision' is not implemented by #{self.class.name}"
|
115
|
+
end
|
116
|
+
|
117
|
+
# Returns the revision number immediately following revision, if at
|
118
|
+
# all possible. A block should always be passed to this method, which
|
119
|
+
# accepts a command to invoke and returns the result, although a
|
120
|
+
# particular SCM's implementation is not required to invoke the block.
|
121
|
+
#
|
122
|
+
# By default, this method simply returns the revision itself. If a
|
123
|
+
# particular SCM is able to determine a subsequent revision given a
|
124
|
+
# revision identifier, it should override this method.
|
125
|
+
def next_revision(revision)
|
126
|
+
revision
|
127
|
+
end
|
128
|
+
|
129
|
+
# Should analyze the given text and determine whether or not a
|
130
|
+
# response is expected, and if so, return the appropriate response.
|
131
|
+
# If no response is expected, return nil. The +state+ parameter is a
|
132
|
+
# hash that may be used to preserve state between calls. This method
|
133
|
+
# is used to define how Capistrano should respond to common prompts
|
134
|
+
# and messages from the SCM, like password prompts and such. By
|
135
|
+
# default, the output is simply displayed.
|
136
|
+
def handle_data(state, stream, text)
|
137
|
+
logger.info "[#{stream}] #{text}"
|
138
|
+
nil
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns the name of the command-line utility for this SCM. It first
|
142
|
+
# looks at the :scm_command variable, and if it does not exist, it
|
143
|
+
# then falls back to whatever was defined by +default_command+.
|
144
|
+
#
|
145
|
+
# If scm_command is set to :default, the default_command will be
|
146
|
+
# returned.
|
147
|
+
def command
|
148
|
+
command = variable(:scm_command)
|
149
|
+
command = nil if command == :default
|
150
|
+
command || default_command
|
151
|
+
end
|
152
|
+
|
153
|
+
# A helper method that can be used to define SCM commands naturally.
|
154
|
+
# It returns a single string with all arguments joined by spaces,
|
155
|
+
# with the scm command prefixed onto it.
|
156
|
+
def scm(*args)
|
157
|
+
[command, *args].compact.join(" ")
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# A helper for accessing variable values, which takes into
|
163
|
+
# consideration the current mode ("normal" vs. "local").
|
164
|
+
def variable(name)
|
165
|
+
if local? && configuration.exists?("local_#{name}".to_sym)
|
166
|
+
return configuration["local_#{name}".to_sym]
|
167
|
+
else
|
168
|
+
configuration[name]
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# A reference to a Logger instance that the SCM can use to log
|
173
|
+
# activity.
|
174
|
+
def logger
|
175
|
+
@logger ||= variable(:logger) || Capistrano::Logger.new(:output => STDOUT)
|
176
|
+
end
|
177
|
+
|
178
|
+
# A helper for accessing the default command name for this SCM. It
|
179
|
+
# simply delegates to the class' +default_command+ method.
|
180
|
+
def default_command
|
181
|
+
self.class.default_command
|
182
|
+
end
|
183
|
+
|
184
|
+
# A convenience method for accessing the declared repository value.
|
185
|
+
def repository
|
186
|
+
variable(:repository)
|
187
|
+
end
|
188
|
+
|
189
|
+
def arguments
|
190
|
+
variable(:scm_arguments)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/scm/base'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Deploy
|
5
|
+
module SCM
|
6
|
+
|
7
|
+
# Implements the Capistrano SCM interface for the Bazaar-NG revision
|
8
|
+
# control system (http://bazaar-vcs.org/).
|
9
|
+
class Bzr < Base
|
10
|
+
# Sets the default command name for this SCM. Users may override this
|
11
|
+
# by setting the :scm_command variable.
|
12
|
+
default_command "bzr"
|
13
|
+
|
14
|
+
# Bazaar-NG doesn't support any pseudo-id's, so we'll use the convention
|
15
|
+
# in this adapter that the :head symbol means the most recently
|
16
|
+
# committed revision.
|
17
|
+
def head
|
18
|
+
:head
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the command that will check out the given revision to the
|
22
|
+
# given destination.
|
23
|
+
def checkout(revision, destination)
|
24
|
+
scm :checkout, "--lightweight", revswitch(revision), repository, destination
|
25
|
+
end
|
26
|
+
|
27
|
+
# The bzr 'update' command does not support updating to a specific
|
28
|
+
# revision, so this just does update, followed by revert (unless
|
29
|
+
# updating to head).
|
30
|
+
def sync(revision, destination)
|
31
|
+
commands = [scm(:update, destination)]
|
32
|
+
commands << [scm(:revert, revswitch(revision), destination)] if revision != head
|
33
|
+
commands.join(" && ")
|
34
|
+
end
|
35
|
+
|
36
|
+
# The bzr 'export' does an export similar to other SCM systems
|
37
|
+
def export(revision, destination)
|
38
|
+
scm :export, revswitch(revision), destination, repository
|
39
|
+
end
|
40
|
+
|
41
|
+
# The bzr "diff" command doesn't accept a repository argument, so it
|
42
|
+
# must be run from within a working tree.
|
43
|
+
def diff(from, to=nil)
|
44
|
+
switch = "-r#{from}"
|
45
|
+
switch << "..#{to}" if to
|
46
|
+
|
47
|
+
scm :diff, switch
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a log of changes between the two revisions (inclusive).
|
51
|
+
def log(from, to=nil)
|
52
|
+
scm :log, "--short", "-r#{from}..#{to}", repository
|
53
|
+
end
|
54
|
+
|
55
|
+
# Attempts to translate the given revision identifier to a "real"
|
56
|
+
# revision. If the identifier is :head, the "bzr revno" command will
|
57
|
+
# be yielded, and the block must execute the command and return the
|
58
|
+
# output. The revision will be extracted from the output and returned.
|
59
|
+
# If the 'revision' argument, on the other hand, is not :head, it is
|
60
|
+
# simply returned.
|
61
|
+
def query_revision(revision)
|
62
|
+
revision
|
63
|
+
end
|
64
|
+
|
65
|
+
# Increments the given revision number and returns it.
|
66
|
+
def next_revision(revision)
|
67
|
+
revision.to_i + 1
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def revswitch(revision)
|
73
|
+
if revision == :head || revision.nil?
|
74
|
+
nil
|
75
|
+
else
|
76
|
+
"-r #{revision}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/scm/base'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Deploy
|
5
|
+
module SCM
|
6
|
+
|
7
|
+
# Implements the Capistrano SCM interface for the CVS revision
|
8
|
+
# control system.
|
9
|
+
class Cvs < Base
|
10
|
+
# Sets the default command name for this SCM. Users may override this
|
11
|
+
# by setting the :scm_command variable.
|
12
|
+
default_command "cvs"
|
13
|
+
|
14
|
+
# CVS understands 'HEAD' to refer to the latest revision in the
|
15
|
+
# repository.
|
16
|
+
def head
|
17
|
+
"HEAD"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the command that will check out the given revision to the
|
21
|
+
# given destination.
|
22
|
+
def checkout(revision, destination)
|
23
|
+
[ prep_destination(destination),
|
24
|
+
scm(verbose, cvs_root, :checkout, cvs_revision(revision), cvs_destination(destination), variable(:scm_module))
|
25
|
+
].join(' && ')
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the command that will do an "cvs update" to the given
|
29
|
+
# revision, for the working copy at the given destination.
|
30
|
+
def sync(revision, destination)
|
31
|
+
[ prep_destination(destination),
|
32
|
+
scm(verbose, cvs_root, :update, cvs_revision(revision), cvs_destination(destination))
|
33
|
+
].join(' && ')
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns the command that will do an "cvs export" of the given revision
|
37
|
+
# to the given destination.
|
38
|
+
def export(revision, destination)
|
39
|
+
[ prep_destination(destination),
|
40
|
+
scm(verbose, cvs_root, :export, cvs_revision(revision), cvs_destination(destination), variable(:scm_module))
|
41
|
+
].join(' && ')
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the command that will do an "cvs diff" for the two revisions.
|
45
|
+
def diff(from, to=nil)
|
46
|
+
rev_type = revision_type(from)
|
47
|
+
if rev_type == :date
|
48
|
+
range_args = "-D '#{from}' -D '#{to || 'now'}'"
|
49
|
+
else
|
50
|
+
range_args = "-r '#{from}' -r '#{to || head}'"
|
51
|
+
end
|
52
|
+
scm cvs_root, :diff, range_args
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns an "cvs log" command for the two revisions.
|
56
|
+
def log(from, to=nil)
|
57
|
+
rev_type = revision_type(from)
|
58
|
+
if rev_type == :date
|
59
|
+
range_arg = "-d '#{from}<#{to || 'now'}'"
|
60
|
+
else
|
61
|
+
range_arg = "-r '#{from}:#{to || head}'"
|
62
|
+
end
|
63
|
+
scm cvs_root, :log, range_arg
|
64
|
+
end
|
65
|
+
|
66
|
+
# Unfortunately, cvs doesn't support the concept of a revision number like
|
67
|
+
# subversion and other SCM's do. For now, we'll rely on getting the timestamp
|
68
|
+
# of the latest checkin under the revision that's passed to us.
|
69
|
+
def query_revision(revision)
|
70
|
+
return revision if revision_type(revision) == :date
|
71
|
+
revision = yield(scm(cvs_root, :log, "-r#{revision}")).
|
72
|
+
grep(/^date:/).
|
73
|
+
map { |line| line[/^date: (.*?);/, 1] }.
|
74
|
+
sort.last + " UTC"
|
75
|
+
return revision
|
76
|
+
end
|
77
|
+
|
78
|
+
# Determines what the response should be for a particular bit of text
|
79
|
+
# from the SCM. Password prompts, connection requests, passphrases,
|
80
|
+
# etc. are handled here.
|
81
|
+
def handle_data(state, stream, text)
|
82
|
+
logger.info "[#{stream}] #{text}"
|
83
|
+
case text
|
84
|
+
when /\bpassword.*:/i
|
85
|
+
# prompting for a password
|
86
|
+
"#{variable(:scm_password) || variable(:password)}\n"
|
87
|
+
when %r{\(yes/no\)}
|
88
|
+
# let's be agreeable...
|
89
|
+
"yes\n"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
# Constructs the CVSROOT command-line option
|
96
|
+
def cvs_root
|
97
|
+
root = ""
|
98
|
+
root << "-d #{repository} " if repository
|
99
|
+
root
|
100
|
+
end
|
101
|
+
|
102
|
+
# Constructs the destination dir command-line option
|
103
|
+
def cvs_destination(destination)
|
104
|
+
dest = ""
|
105
|
+
if destination
|
106
|
+
dest_parts = destination.split(/\//);
|
107
|
+
dest << "-d #{dest_parts.pop}"
|
108
|
+
end
|
109
|
+
dest
|
110
|
+
end
|
111
|
+
|
112
|
+
# attempts to guess what type of revision we're working with
|
113
|
+
def revision_type(rev)
|
114
|
+
return :date if rev =~ /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2} UTC$/ # i.e 2007-05-15 08:13:25 UTC
|
115
|
+
return :date if rev =~ /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/ # i.e 2007-05-15 08:13:25
|
116
|
+
return :revision if rev =~ /^\d/ # i.e. 1.2.1
|
117
|
+
return :tag # i.e. RELEASE_1_2
|
118
|
+
end
|
119
|
+
|
120
|
+
# constructs the appropriate command-line switch for specifying a
|
121
|
+
# "revision" in CVS. This could be a tag, branch, revision (i.e. 1.3)
|
122
|
+
# or a date (to be used with -d)
|
123
|
+
def cvs_revision(rev)
|
124
|
+
revision = ""
|
125
|
+
revision << case revision_type(rev)
|
126
|
+
when :date
|
127
|
+
"-D \"#{rev}\"" if revision_type(rev) == :date
|
128
|
+
when :revision
|
129
|
+
"-r #{rev}"
|
130
|
+
else
|
131
|
+
"-r #{head}"
|
132
|
+
end
|
133
|
+
return revision
|
134
|
+
end
|
135
|
+
|
136
|
+
# If verbose output is requested, return nil, otherwise return the
|
137
|
+
# command-line switch for "quiet" ("-Q").
|
138
|
+
def verbose
|
139
|
+
variable(:scm_verbose) ? nil : "-Q"
|
140
|
+
end
|
141
|
+
|
142
|
+
def prep_destination(destination)
|
143
|
+
dest_parts = destination.split(/\//);
|
144
|
+
checkout_dir = dest_parts.pop
|
145
|
+
dest = dest_parts.join('/')
|
146
|
+
"mkdir -p #{ dest } && cd #{ dest }"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'capistrano/recipes/deploy/scm/base'
|
2
|
+
|
3
|
+
module Capistrano
|
4
|
+
module Deploy
|
5
|
+
module SCM
|
6
|
+
|
7
|
+
# Implements the Capistrano SCM interface for the darcs revision
|
8
|
+
# control system (http://www.abridgegame.org/darcs/).
|
9
|
+
class Darcs < Base
|
10
|
+
# Sets the default command name for this SCM. Users may override this
|
11
|
+
# by setting the :scm_command variable.
|
12
|
+
default_command "darcs"
|
13
|
+
|
14
|
+
# Because darcs does not have any support for pseudo-ids, we'll just
|
15
|
+
# return something here that we can use in the helpers below for
|
16
|
+
# determining whether we need to look up the latest revision.
|
17
|
+
def head
|
18
|
+
:head
|
19
|
+
end
|
20
|
+
|
21
|
+
# Returns the command that will check out the given revision to the
|
22
|
+
# given destination. The 'revision' parameter must be the 'hash' value
|
23
|
+
# for the revision in question, as given by 'darcs changes --xml-output'.
|
24
|
+
def checkout(revision, destination)
|
25
|
+
scm :get, verbose, "--repo-name=#{destination}", "--to-match='hash #{revision}'", repository
|
26
|
+
end
|
27
|
+
|
28
|
+
# Tries to update the destination repository in-place, to bring it up
|
29
|
+
# to the given revision. Note that because darcs' "pull" operation
|
30
|
+
# does not support a "to-match" argument (or similar), this basically
|
31
|
+
# nukes the destination directory and re-gets it.
|
32
|
+
def sync(revision, destination)
|
33
|
+
["rm -rf #{destination}", checkout(revision, destination)].join(" && ")
|
34
|
+
end
|
35
|
+
|
36
|
+
# Darcs does not have a real 'export' option; there is 'darcs dist',
|
37
|
+
# but that presupposes a utility that can untar and ungzip the dist
|
38
|
+
# file. We'll cheat and just do a get, followed by a deletion of the
|
39
|
+
# _darcs metadata directory.
|
40
|
+
def export(revision, destination)
|
41
|
+
[checkout(revision, destination), "rm -rf #{destination}/_darcs"].join(" && ")
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns the command that will do a "darcs diff" for the two revisions.
|
45
|
+
# Each revision must be the 'hash' identifier of a darcs revision.
|
46
|
+
def diff(from, to=nil)
|
47
|
+
scm :diff, "--from-match 'hash #{from}'", to && "--to-match 'hash #{to}'"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the log of changes between the two revisions. Each revision
|
51
|
+
# must be the 'hash' identifier of a darcs revision.
|
52
|
+
def log(from, to=nil)
|
53
|
+
scm :changes, "--from-match 'hash #{from}'", to && "--to-match 'hash #{to}'", "--repo=#{repository}"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Attempts to translate the given revision identifier to a "real"
|
57
|
+
# revision. If the identifier is a symbol, it is assumed to be a
|
58
|
+
# pseudo-id. Otherwise, it will be immediately returned. If it is a
|
59
|
+
# pseudo-id, a set of commands to execute will be yielded, and the
|
60
|
+
# result of executing those commands must be returned by the block.
|
61
|
+
# This method will then extract the actual revision hash from the
|
62
|
+
# returned data.
|
63
|
+
def query_revision(revision)
|
64
|
+
case revision
|
65
|
+
when :head
|
66
|
+
xml = yield(scm(:changes, "--last 1", "--xml-output", "--repo=#{repository}"))
|
67
|
+
return xml[/hash='(.*?)'/, 1]
|
68
|
+
else return revision
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def verbose
|
75
|
+
case variable(:scm_verbose)
|
76
|
+
when nil then "-q"
|
77
|
+
when false then nil
|
78
|
+
else "-v"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|