capistrano-edge 2.5.6

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