capistrano-edge 2.5.6

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.
Files changed (109) hide show
  1. data/CHANGELOG.rdoc +770 -0
  2. data/Manifest +104 -0
  3. data/README.rdoc +66 -0
  4. data/Rakefile +35 -0
  5. data/bin/cap +4 -0
  6. data/bin/capify +95 -0
  7. data/capistrano.gemspec +51 -0
  8. data/examples/sample.rb +14 -0
  9. data/lib/capistrano.rb +2 -0
  10. data/lib/capistrano/callback.rb +45 -0
  11. data/lib/capistrano/cli.rb +47 -0
  12. data/lib/capistrano/cli/execute.rb +84 -0
  13. data/lib/capistrano/cli/help.rb +125 -0
  14. data/lib/capistrano/cli/help.txt +75 -0
  15. data/lib/capistrano/cli/options.rb +224 -0
  16. data/lib/capistrano/cli/ui.rb +40 -0
  17. data/lib/capistrano/command.rb +283 -0
  18. data/lib/capistrano/configuration.rb +43 -0
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +47 -0
  20. data/lib/capistrano/configuration/actions/inspect.rb +46 -0
  21. data/lib/capistrano/configuration/actions/invocation.rb +293 -0
  22. data/lib/capistrano/configuration/callbacks.rb +148 -0
  23. data/lib/capistrano/configuration/connections.rb +204 -0
  24. data/lib/capistrano/configuration/execution.rb +143 -0
  25. data/lib/capistrano/configuration/loading.rb +197 -0
  26. data/lib/capistrano/configuration/namespaces.rb +197 -0
  27. data/lib/capistrano/configuration/roles.rb +73 -0
  28. data/lib/capistrano/configuration/servers.rb +85 -0
  29. data/lib/capistrano/configuration/variables.rb +127 -0
  30. data/lib/capistrano/errors.rb +15 -0
  31. data/lib/capistrano/extensions.rb +57 -0
  32. data/lib/capistrano/logger.rb +59 -0
  33. data/lib/capistrano/processable.rb +53 -0
  34. data/lib/capistrano/recipes/compat.rb +32 -0
  35. data/lib/capistrano/recipes/deploy.rb +438 -0
  36. data/lib/capistrano/recipes/deploy/dependencies.rb +44 -0
  37. data/lib/capistrano/recipes/deploy/local_dependency.rb +54 -0
  38. data/lib/capistrano/recipes/deploy/remote_dependency.rb +105 -0
  39. data/lib/capistrano/recipes/deploy/scm.rb +19 -0
  40. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  41. data/lib/capistrano/recipes/deploy/scm/base.rb +196 -0
  42. data/lib/capistrano/recipes/deploy/scm/bzr.rb +83 -0
  43. data/lib/capistrano/recipes/deploy/scm/cvs.rb +152 -0
  44. data/lib/capistrano/recipes/deploy/scm/darcs.rb +85 -0
  45. data/lib/capistrano/recipes/deploy/scm/git.rb +274 -0
  46. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +137 -0
  47. data/lib/capistrano/recipes/deploy/scm/none.rb +44 -0
  48. data/lib/capistrano/recipes/deploy/scm/perforce.rb +138 -0
  49. data/lib/capistrano/recipes/deploy/scm/subversion.rb +121 -0
  50. data/lib/capistrano/recipes/deploy/strategy.rb +19 -0
  51. data/lib/capistrano/recipes/deploy/strategy/base.rb +79 -0
  52. data/lib/capistrano/recipes/deploy/strategy/checkout.rb +20 -0
  53. data/lib/capistrano/recipes/deploy/strategy/copy.rb +210 -0
  54. data/lib/capistrano/recipes/deploy/strategy/export.rb +20 -0
  55. data/lib/capistrano/recipes/deploy/strategy/remote.rb +52 -0
  56. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +56 -0
  57. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +53 -0
  58. data/lib/capistrano/recipes/ext/rails-database-migrations.rb +50 -0
  59. data/lib/capistrano/recipes/ext/web-disable-enable.rb +40 -0
  60. data/lib/capistrano/recipes/standard.rb +37 -0
  61. data/lib/capistrano/recipes/templates/maintenance.rhtml +53 -0
  62. data/lib/capistrano/recipes/upgrade.rb +33 -0
  63. data/lib/capistrano/role.rb +102 -0
  64. data/lib/capistrano/server_definition.rb +56 -0
  65. data/lib/capistrano/shell.rb +260 -0
  66. data/lib/capistrano/ssh.rb +99 -0
  67. data/lib/capistrano/task_definition.rb +70 -0
  68. data/lib/capistrano/transfer.rb +216 -0
  69. data/lib/capistrano/version.rb +18 -0
  70. data/setup.rb +1346 -0
  71. data/test/cli/execute_test.rb +132 -0
  72. data/test/cli/help_test.rb +165 -0
  73. data/test/cli/options_test.rb +317 -0
  74. data/test/cli/ui_test.rb +28 -0
  75. data/test/cli_test.rb +17 -0
  76. data/test/command_test.rb +286 -0
  77. data/test/configuration/actions/file_transfer_test.rb +61 -0
  78. data/test/configuration/actions/inspect_test.rb +65 -0
  79. data/test/configuration/actions/invocation_test.rb +224 -0
  80. data/test/configuration/callbacks_test.rb +220 -0
  81. data/test/configuration/connections_test.rb +349 -0
  82. data/test/configuration/execution_test.rb +175 -0
  83. data/test/configuration/loading_test.rb +132 -0
  84. data/test/configuration/namespace_dsl_test.rb +311 -0
  85. data/test/configuration/roles_test.rb +144 -0
  86. data/test/configuration/servers_test.rb +121 -0
  87. data/test/configuration/variables_test.rb +184 -0
  88. data/test/configuration_test.rb +88 -0
  89. data/test/deploy/local_dependency_test.rb +76 -0
  90. data/test/deploy/remote_dependency_test.rb +114 -0
  91. data/test/deploy/scm/accurev_test.rb +23 -0
  92. data/test/deploy/scm/base_test.rb +55 -0
  93. data/test/deploy/scm/git_test.rb +184 -0
  94. data/test/deploy/scm/mercurial_test.rb +129 -0
  95. data/test/deploy/scm/none_test.rb +35 -0
  96. data/test/deploy/strategy/copy_test.rb +258 -0
  97. data/test/extensions_test.rb +69 -0
  98. data/test/fixtures/cli_integration.rb +5 -0
  99. data/test/fixtures/config.rb +5 -0
  100. data/test/fixtures/custom.rb +3 -0
  101. data/test/logger_test.rb +123 -0
  102. data/test/role_test.rb +11 -0
  103. data/test/server_definition_test.rb +121 -0
  104. data/test/shell_test.rb +90 -0
  105. data/test/ssh_test.rb +104 -0
  106. data/test/task_definition_test.rb +101 -0
  107. data/test/transfer_test.rb +160 -0
  108. data/test/utils.rb +38 -0
  109. metadata +321 -0
@@ -0,0 +1,44 @@
1
+ require 'capistrano/recipes/deploy/local_dependency'
2
+ require 'capistrano/recipes/deploy/remote_dependency'
3
+
4
+ module Capistrano
5
+ module Deploy
6
+ class Dependencies
7
+ include Enumerable
8
+
9
+ attr_reader :configuration
10
+
11
+ def initialize(configuration)
12
+ @configuration = configuration
13
+ @dependencies = []
14
+ yield self if block_given?
15
+ end
16
+
17
+ def check
18
+ yield self
19
+ self
20
+ end
21
+
22
+ def remote
23
+ dep = RemoteDependency.new(configuration)
24
+ @dependencies << dep
25
+ dep
26
+ end
27
+
28
+ def local
29
+ dep = LocalDependency.new(configuration)
30
+ @dependencies << dep
31
+ dep
32
+ end
33
+
34
+ def each
35
+ @dependencies.each { |d| yield d }
36
+ self
37
+ end
38
+
39
+ def pass?
40
+ all? { |d| d.pass? }
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,54 @@
1
+ module Capistrano
2
+ module Deploy
3
+ class LocalDependency
4
+ attr_reader :configuration
5
+ attr_reader :message
6
+
7
+ def initialize(configuration)
8
+ @configuration = configuration
9
+ @success = true
10
+ end
11
+
12
+ def command(command)
13
+ @message ||= "`#{command}' could not be found in the path on the local host"
14
+ @success = find_in_path(command)
15
+ self
16
+ end
17
+
18
+ def or(message)
19
+ @message = message
20
+ self
21
+ end
22
+
23
+ def pass?
24
+ @success
25
+ end
26
+
27
+ private
28
+
29
+ # Searches the path, looking for the given utility. If an executable
30
+ # file is found that matches the parameter, this returns true.
31
+ def find_in_path(utility)
32
+ path = (ENV['PATH'] || "").split(File::PATH_SEPARATOR)
33
+ suffixes = self.class.on_windows? ? self.class.windows_executable_extensions : [""]
34
+
35
+ path.each do |dir|
36
+ suffixes.each do |sfx|
37
+ file = File.join(dir, utility + sfx)
38
+ return true if File.executable?(file)
39
+ end
40
+ end
41
+
42
+ false
43
+ end
44
+
45
+ def self.on_windows?
46
+ RUBY_PLATFORM =~ /mswin|mingw/
47
+ end
48
+
49
+ def self.windows_executable_extensions
50
+ %w(.exe .bat .com .cmd)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,105 @@
1
+ require 'capistrano/errors'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ class RemoteDependency
6
+ attr_reader :configuration
7
+ attr_reader :hosts
8
+
9
+ def initialize(configuration)
10
+ @configuration = configuration
11
+ @success = true
12
+ @hosts = nil
13
+ end
14
+
15
+ def directory(path, options={})
16
+ @message ||= "`#{path}' is not a directory"
17
+ try("test -d #{path}", options)
18
+ self
19
+ end
20
+
21
+ def file(path, options={})
22
+ @message ||= "`#{path}' is not a file"
23
+ try("test -f #{path}", options)
24
+ self
25
+ end
26
+
27
+ def writable(path, options={})
28
+ @message ||= "`#{path}' is not writable"
29
+ try("test -w #{path}", options)
30
+ self
31
+ end
32
+
33
+ def command(command, options={})
34
+ @message ||= "`#{command}' could not be found in the path"
35
+ try("which #{command}", options)
36
+ self
37
+ end
38
+
39
+ def gem(name, version, options={})
40
+ @message ||= "gem `#{name}' #{version} could not be found"
41
+ gem_cmd = configuration.fetch(:gem_command, "gem")
42
+ try("#{gem_cmd} specification --version '#{version}' #{name} 2>&1 | awk 'BEGIN { s = 0 } /^name:/ { s = 1; exit }; END { if(s == 0) exit 1 }'", options)
43
+ self
44
+ end
45
+
46
+ def match(command, expect, options={})
47
+ expect = Regexp.new(Regexp.escape(expect.to_s)) unless expect.is_a?(Regexp)
48
+
49
+ output_per_server = {}
50
+ try("#{command} ", options) do |ch, stream, out|
51
+ output_per_server[ch[:server]] ||= ''
52
+ output_per_server[ch[:server]] += out
53
+ end
54
+
55
+ # It is possible for some of these commands to return a status != 0
56
+ # (for example, rake --version exits with a 1). For this check we
57
+ # just care if the output matches, so we reset the success flag.
58
+ @success = true
59
+
60
+ errored_hosts = []
61
+ output_per_server.each_pair do |server, output|
62
+ next if output =~ expect
63
+ errored_hosts << server
64
+ end
65
+
66
+ if errored_hosts.any?
67
+ @hosts = errored_hosts.join(', ')
68
+ output = output_per_server[errored_hosts.first]
69
+ @message = "the output #{output.inspect} from #{command.inspect} did not match #{expect.inspect}"
70
+ @success = false
71
+ end
72
+
73
+ self
74
+ end
75
+
76
+ def or(message)
77
+ @message = message
78
+ self
79
+ end
80
+
81
+ def pass?
82
+ @success
83
+ end
84
+
85
+ def message
86
+ s = @message.dup
87
+ s << " (#{@hosts})" if @hosts
88
+ s
89
+ end
90
+
91
+ private
92
+
93
+ def try(command, options)
94
+ return unless @success # short-circuit evaluation
95
+ configuration.invoke_command(command, options) do |ch,stream,out|
96
+ warn "#{ch[:server]}: #{out}" if stream == :err
97
+ yield ch, stream, out if block_given?
98
+ end
99
+ rescue Capistrano::CommandError => e
100
+ @success = false
101
+ @hosts = e.hosts.join(', ')
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,19 @@
1
+ module Capistrano
2
+ module Deploy
3
+ module SCM
4
+ def self.new(scm, config={})
5
+ scm_file = "capistrano/recipes/deploy/scm/#{scm}"
6
+ require(scm_file)
7
+
8
+ scm_const = scm.to_s.capitalize.gsub(/_(.)/) { $1.upcase }
9
+ if const_defined?(scm_const)
10
+ const_get(scm_const).new(config)
11
+ else
12
+ raise Capistrano::Error, "could not find `#{name}::#{scm_const}' in `#{scm_file}'"
13
+ end
14
+ rescue LoadError
15
+ raise Capistrano::Error, "could not find any SCM named `#{scm}'"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -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