capistrano 2.0.0 → 2.15.2

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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.travis.yml +7 -0
  4. data/CHANGELOG +715 -18
  5. data/Gemfile +12 -0
  6. data/README.md +94 -0
  7. data/Rakefile +11 -0
  8. data/bin/cap +0 -0
  9. data/bin/capify +37 -22
  10. data/capistrano.gemspec +40 -0
  11. data/lib/capistrano/callback.rb +5 -1
  12. data/lib/capistrano/cli/execute.rb +10 -7
  13. data/lib/capistrano/cli/help.rb +39 -16
  14. data/lib/capistrano/cli/help.txt +44 -16
  15. data/lib/capistrano/cli/options.rb +71 -11
  16. data/lib/capistrano/cli/ui.rb +13 -1
  17. data/lib/capistrano/cli.rb +5 -5
  18. data/lib/capistrano/command.rb +215 -58
  19. data/lib/capistrano/configuration/actions/file_transfer.rb +29 -14
  20. data/lib/capistrano/configuration/actions/inspect.rb +3 -3
  21. data/lib/capistrano/configuration/actions/invocation.rb +212 -22
  22. data/lib/capistrano/configuration/alias_task.rb +26 -0
  23. data/lib/capistrano/configuration/callbacks.rb +26 -27
  24. data/lib/capistrano/configuration/connections.rb +130 -52
  25. data/lib/capistrano/configuration/execution.rb +34 -18
  26. data/lib/capistrano/configuration/loading.rb +91 -6
  27. data/lib/capistrano/configuration/log_formatters.rb +75 -0
  28. data/lib/capistrano/configuration/namespaces.rb +45 -12
  29. data/lib/capistrano/configuration/roles.rb +28 -2
  30. data/lib/capistrano/configuration/servers.rb +51 -10
  31. data/lib/capistrano/configuration/variables.rb +3 -3
  32. data/lib/capistrano/configuration.rb +20 -4
  33. data/lib/capistrano/errors.rb +12 -8
  34. data/lib/capistrano/ext/multistage.rb +62 -0
  35. data/lib/capistrano/ext/string.rb +5 -0
  36. data/lib/capistrano/extensions.rb +1 -1
  37. data/lib/capistrano/fix_rake_deprecated_dsl.rb +8 -0
  38. data/lib/capistrano/logger.rb +112 -5
  39. data/lib/capistrano/processable.rb +55 -0
  40. data/lib/capistrano/recipes/compat.rb +2 -2
  41. data/lib/capistrano/recipes/deploy/assets.rb +185 -0
  42. data/lib/capistrano/recipes/deploy/dependencies.rb +2 -2
  43. data/lib/capistrano/recipes/deploy/local_dependency.rb +10 -2
  44. data/lib/capistrano/recipes/deploy/remote_dependency.rb +54 -2
  45. data/lib/capistrano/recipes/deploy/scm/accurev.rb +169 -0
  46. data/lib/capistrano/recipes/deploy/scm/base.rb +31 -11
  47. data/lib/capistrano/recipes/deploy/scm/bzr.rb +14 -14
  48. data/lib/capistrano/recipes/deploy/scm/cvs.rb +10 -8
  49. data/lib/capistrano/recipes/deploy/scm/darcs.rb +12 -1
  50. data/lib/capistrano/recipes/deploy/scm/git.rb +293 -0
  51. data/lib/capistrano/recipes/deploy/scm/mercurial.rb +23 -15
  52. data/lib/capistrano/recipes/deploy/scm/none.rb +55 -0
  53. data/lib/capistrano/recipes/deploy/scm/perforce.rb +54 -28
  54. data/lib/capistrano/recipes/deploy/scm/subversion.rb +35 -17
  55. data/lib/capistrano/recipes/deploy/scm.rb +1 -1
  56. data/lib/capistrano/recipes/deploy/strategy/base.rb +32 -4
  57. data/lib/capistrano/recipes/deploy/strategy/copy.rb +238 -43
  58. data/lib/capistrano/recipes/deploy/strategy/remote.rb +1 -1
  59. data/lib/capistrano/recipes/deploy/strategy/remote_cache.rb +11 -1
  60. data/lib/capistrano/recipes/deploy/strategy/unshared_remote_cache.rb +21 -0
  61. data/lib/capistrano/recipes/deploy/strategy.rb +1 -1
  62. data/lib/capistrano/recipes/deploy.rb +265 -123
  63. data/lib/capistrano/recipes/standard.rb +1 -1
  64. data/lib/capistrano/role.rb +102 -0
  65. data/lib/capistrano/server_definition.rb +6 -1
  66. data/lib/capistrano/shell.rb +30 -33
  67. data/lib/capistrano/ssh.rb +46 -60
  68. data/lib/capistrano/task_definition.rb +16 -8
  69. data/lib/capistrano/transfer.rb +218 -0
  70. data/lib/capistrano/version.rb +6 -17
  71. data/lib/capistrano.rb +4 -1
  72. data/test/cli/execute_test.rb +3 -3
  73. data/test/cli/help_test.rb +33 -7
  74. data/test/cli/options_test.rb +109 -6
  75. data/test/cli/ui_test.rb +2 -2
  76. data/test/cli_test.rb +3 -3
  77. data/test/command_test.rb +144 -124
  78. data/test/configuration/actions/file_transfer_test.rb +41 -20
  79. data/test/configuration/actions/inspect_test.rb +21 -7
  80. data/test/configuration/actions/invocation_test.rb +91 -30
  81. data/test/configuration/alias_task_test.rb +118 -0
  82. data/test/configuration/callbacks_test.rb +41 -46
  83. data/test/configuration/connections_test.rb +187 -36
  84. data/test/configuration/execution_test.rb +18 -2
  85. data/test/configuration/loading_test.rb +17 -4
  86. data/test/configuration/namespace_dsl_test.rb +54 -5
  87. data/test/configuration/roles_test.rb +114 -4
  88. data/test/configuration/servers_test.rb +97 -4
  89. data/test/configuration/variables_test.rb +12 -2
  90. data/test/configuration_test.rb +9 -13
  91. data/test/deploy/local_dependency_test.rb +76 -0
  92. data/test/deploy/remote_dependency_test.rb +146 -0
  93. data/test/deploy/scm/accurev_test.rb +23 -0
  94. data/test/deploy/scm/base_test.rb +1 -1
  95. data/test/deploy/scm/bzr_test.rb +51 -0
  96. data/test/deploy/scm/darcs_test.rb +37 -0
  97. data/test/deploy/scm/git_test.rb +221 -0
  98. data/test/deploy/scm/mercurial_test.rb +134 -0
  99. data/test/deploy/scm/none_test.rb +35 -0
  100. data/test/deploy/scm/perforce_test.rb +23 -0
  101. data/test/deploy/scm/subversion_test.rb +40 -0
  102. data/test/deploy/strategy/copy_test.rb +240 -26
  103. data/test/extensions_test.rb +2 -2
  104. data/test/logger_formatting_test.rb +149 -0
  105. data/test/logger_test.rb +13 -2
  106. data/test/recipes_test.rb +25 -0
  107. data/test/role_test.rb +11 -0
  108. data/test/server_definition_test.rb +15 -2
  109. data/test/shell_test.rb +33 -1
  110. data/test/ssh_test.rb +40 -24
  111. data/test/task_definition_test.rb +18 -2
  112. data/test/transfer_test.rb +168 -0
  113. data/test/utils.rb +27 -33
  114. metadata +215 -102
  115. data/MIT-LICENSE +0 -20
  116. data/README +0 -43
  117. data/examples/sample.rb +0 -14
  118. data/lib/capistrano/gateway.rb +0 -131
  119. data/lib/capistrano/recipes/deploy/templates/maintenance.rhtml +0 -53
  120. data/lib/capistrano/recipes/templates/maintenance.rhtml +0 -53
  121. data/lib/capistrano/recipes/upgrade.rb +0 -33
  122. data/lib/capistrano/upload.rb +0 -146
  123. data/test/gateway_test.rb +0 -167
  124. data/test/upload_test.rb +0 -131
  125. data/test/version_test.rb +0 -24
@@ -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
@@ -54,7 +54,7 @@ module Capistrano
54
54
  # and "local_scm_command" to be set, if the two differ.
55
55
  #
56
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
57
+ # the block, all requests on this configuration object will be
58
58
  # considered local.
59
59
  def local
60
60
  if block_given?
@@ -114,6 +114,18 @@ module Capistrano
114
114
  raise NotImplementedError, "`query_revision' is not implemented by #{self.class.name}"
115
115
  end
116
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
+
117
129
  # Should analyze the given text and determine whether or not a
118
130
  # response is expected, and if so, return the appropriate response.
119
131
  # If no response is expected, return nil. The +state+ parameter is a
@@ -138,15 +150,22 @@ module Capistrano
138
150
  command || default_command
139
151
  end
140
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
+
141
160
  private
142
161
 
143
162
  # A helper for accessing variable values, which takes into
144
163
  # consideration the current mode ("normal" vs. "local").
145
- def variable(name)
164
+ def variable(name, default = nil)
146
165
  if local? && configuration.exists?("local_#{name}".to_sym)
147
- return configuration["local_#{name}".to_sym]
166
+ return configuration["local_#{name}".to_sym].nil? ? default : configuration["local_#{name}".to_sym]
148
167
  else
149
- configuration[name]
168
+ configuration[name].nil? ? default : configuration[name]
150
169
  end
151
170
  end
152
171
 
@@ -162,17 +181,18 @@ module Capistrano
162
181
  self.class.default_command
163
182
  end
164
183
 
165
- # A helper method that can be used to define SCM commands naturally.
166
- # It returns a single string with all arguments joined by spaces,
167
- # with the scm command prefixed onto it.
168
- def scm(*args)
169
- [command, *args].compact.join(" ")
170
- end
171
-
172
184
  # A convenience method for accessing the declared repository value.
173
185
  def repository
174
186
  variable(:repository)
175
187
  end
188
+
189
+ def arguments(command = :all)
190
+ value = variable(:scm_arguments)
191
+ if value.is_a?(Hash)
192
+ value = value[command]
193
+ end
194
+ value
195
+ end
176
196
  end
177
197
 
178
198
  end
@@ -21,7 +21,7 @@ module Capistrano
21
21
  # Returns the command that will check out the given revision to the
22
22
  # given destination.
23
23
  def checkout(revision, destination)
24
- scm :branch, revswitch(revision), repository, destination
24
+ scm :checkout, "--lightweight", revswitch(revision), repository, destination
25
25
  end
26
26
 
27
27
  # The bzr 'update' command does not support updating to a specific
@@ -33,13 +33,9 @@ module Capistrano
33
33
  commands.join(" && ")
34
34
  end
35
35
 
36
- # The bzr 'export' command would work fine, except it doesn't let you
37
- # specify the repository itself, so it only works if there is a working
38
- # tree handy, locally. Since this needs to work even on a remote host
39
- # where there is no working tree, we'll just do a checkout, followed
40
- # by a deletion of the ".bzr" metadata directory.
36
+ # The bzr 'export' does an export similar to other SCM systems
41
37
  def export(revision, destination)
42
- [checkout(revision, destination) && "rm -rf #{destination}/.bzr"].join(" && ")
38
+ scm :export, revswitch(revision), destination, repository
43
39
  end
44
40
 
45
41
  # The bzr "diff" command doesn't accept a repository argument, so it
@@ -63,20 +59,24 @@ module Capistrano
63
59
  # If the 'revision' argument, on the other hand, is not :head, it is
64
60
  # simply returned.
65
61
  def query_revision(revision)
66
- if revision == head
67
- yield(scm(:revno, repository)).chomp
68
- else
69
- revision
70
- end
62
+ return revision unless :head == revision
63
+
64
+ command = scm('revno', repository)
65
+ result = yield(command)
66
+ end
67
+
68
+ # Increments the given revision number and returns it.
69
+ def next_revision(revision)
70
+ revision.to_i + 1
71
71
  end
72
72
 
73
73
  private
74
74
 
75
75
  def revswitch(revision)
76
- if revision == :head
76
+ if revision == :head || revision.nil?
77
77
  nil
78
78
  else
79
- "-r #{revision}"
79
+ "-r #{revision}".chomp
80
80
  end
81
81
  end
82
82
  end
@@ -63,15 +63,16 @@ module Capistrano
63
63
  scm cvs_root, :log, range_arg
64
64
  end
65
65
 
66
- # Unfortunately, cvs doesn't support the concept of a revision number like
66
+ # Unfortunately, cvs doesn't support the concept of a revision number like
67
67
  # subversion and other SCM's do. For now, we'll rely on getting the timestamp
68
68
  # of the latest checkin under the revision that's passed to us.
69
69
  def query_revision(revision)
70
70
  return revision if revision_type(revision) == :date
71
71
  revision = yield(scm(cvs_root, :log, "-r#{revision}")).
72
- grep(/^date:/).
72
+ split("\n").
73
+ select { |line| line =~ /^date:/ }.
73
74
  map { |line| line[/^date: (.*?);/, 1] }.
74
- sort.last
75
+ sort.last + " UTC"
75
76
  return revision
76
77
  end
77
78
 
@@ -98,7 +99,7 @@ module Capistrano
98
99
  root << "-d #{repository} " if repository
99
100
  root
100
101
  end
101
-
102
+
102
103
  # Constructs the destination dir command-line option
103
104
  def cvs_destination(destination)
104
105
  dest = ""
@@ -108,23 +109,24 @@ module Capistrano
108
109
  end
109
110
  dest
110
111
  end
111
-
112
+
112
113
  # attempts to guess what type of revision we're working with
113
114
  def revision_type(rev)
115
+ 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
114
116
  return :date if rev =~ /^\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2}$/ # i.e 2007-05-15 08:13:25
115
117
  return :revision if rev =~ /^\d/ # i.e. 1.2.1
116
118
  return :tag # i.e. RELEASE_1_2
117
119
  end
118
-
120
+
119
121
  # constructs the appropriate command-line switch for specifying a
120
122
  # "revision" in CVS. This could be a tag, branch, revision (i.e. 1.3)
121
123
  # or a date (to be used with -d)
122
124
  def cvs_revision(rev)
123
125
  revision = ""
124
126
  revision << case revision_type(rev)
125
- when :date:
127
+ when :date
126
128
  "-D \"#{rev}\"" if revision_type(rev) == :date
127
- when :revision:
129
+ when :revision
128
130
  "-r #{rev}"
129
131
  else
130
132
  "-r #{head}"
@@ -18,11 +18,22 @@ module Capistrano
18
18
  :head
19
19
  end
20
20
 
21
+ def to_match(revision)
22
+ if revision.nil? || revision == self.head
23
+ nil
24
+ else
25
+ "--to-match='hash #{revision}'"
26
+ end
27
+ end
28
+
21
29
  # Returns the command that will check out the given revision to the
22
30
  # given destination. The 'revision' parameter must be the 'hash' value
23
31
  # for the revision in question, as given by 'darcs changes --xml-output'.
24
32
  def checkout(revision, destination)
25
- scm :get, verbose, "--repo-name=#{destination}", "--to-match='hash #{revision}'", repository
33
+ scm :get, *[verbose,
34
+ "--repo-name=#{destination}",
35
+ to_match(revision),
36
+ repository].compact
26
37
  end
27
38
 
28
39
  # Tries to update the destination repository in-place, to bring it up
@@ -0,0 +1,293 @@
1
+ require 'capistrano/recipes/deploy/scm/base'
2
+
3
+ module Capistrano
4
+ module Deploy
5
+ module SCM
6
+
7
+ # An SCM module for using Git as your source control tool with Capistrano
8
+ # 2.0. If you are using Capistrano 1.x, use this plugin instead:
9
+ #
10
+ # http://scie.nti.st/2007/3/16/capistrano-with-git-shared-repository
11
+ #
12
+ # Assumes you are using a shared Git repository.
13
+ #
14
+ # Parts of this plugin borrowed from Scott Chacon's version, which I
15
+ # found on the Capistrano mailing list but failed to be able to get
16
+ # working.
17
+ #
18
+ # FEATURES:
19
+ #
20
+ # * Very simple, only requiring 2 lines in your deploy.rb.
21
+ # * Can deploy different branches, tags, or any SHA1 easily.
22
+ # * Supports prompting for password / passphrase upon checkout.
23
+ # (I am amazed at how some plugins don't do this)
24
+ # * Supports :scm_command, :scm_password, :scm_passphrase Capistrano
25
+ # directives.
26
+ #
27
+ # CONFIGURATION
28
+ # -------------
29
+ #
30
+ # Use this plugin by adding the following line in your config/deploy.rb:
31
+ #
32
+ # set :scm, :git
33
+ #
34
+ # Set <tt>:repository</tt> to the path of your Git repo:
35
+ #
36
+ # set :repository, "someuser@somehost:/home/myproject"
37
+ #
38
+ # The above two options are required to be set, the ones below are
39
+ # optional.
40
+ #
41
+ # You may set <tt>:branch</tt>, which is the reference to the branch, tag,
42
+ # or any SHA1 you are deploying, for example:
43
+ #
44
+ # set :branch, "master"
45
+ #
46
+ # Otherwise, HEAD is assumed. I strongly suggest you set this. HEAD is
47
+ # not always the best assumption.
48
+ #
49
+ # You may also set <tt>:remote</tt>, which will be used as a name for remote
50
+ # tracking of repositories. This option is intended for use with the
51
+ # <tt>:remote_cache</tt> strategy in a distributed git environment.
52
+ #
53
+ # For example in the projects <tt>config/deploy.rb</tt>:
54
+ #
55
+ # set :repository, "#{scm_user}@somehost:~/projects/project.git"
56
+ # set :remote, "#{scm_user}"
57
+ #
58
+ # Then each person with deploy priveledges can add the following to their
59
+ # local <tt>~/.caprc</tt> file:
60
+ #
61
+ # set :scm_user, 'someuser'
62
+ #
63
+ # Now any time a person deploys the project, their repository will be
64
+ # setup as a remote git repository within the cached repository.
65
+ #
66
+ # The <tt>:scm_command</tt> configuration variable, if specified, will
67
+ # be used as the full path to the git executable on the *remote* machine:
68
+ #
69
+ # set :scm_command, "/opt/local/bin/git"
70
+ #
71
+ # For compatibility with deploy scripts that may have used the 1.x
72
+ # version of this plugin before upgrading, <tt>:git</tt> is still
73
+ # recognized as an alias for :scm_command.
74
+ #
75
+ # Set <tt>:scm_password</tt> to the password needed to clone your repo
76
+ # if you don't have password-less (public key) entry:
77
+ #
78
+ # set :scm_password, "my_secret'
79
+ #
80
+ # Otherwise, you will be prompted for a password.
81
+ #
82
+ # <tt>:scm_passphrase</tt> is also supported.
83
+ #
84
+ # The remote cache strategy is also supported.
85
+ #
86
+ # set :repository_cache, "git_master"
87
+ # set :deploy_via, :remote_cache
88
+ #
89
+ # For faster clone, you can also use shallow cloning. This will set the
90
+ # '--depth' flag using the depth specified. This *cannot* be used
91
+ # together with the :remote_cache strategy
92
+ #
93
+ # set :git_shallow_clone, 1
94
+ #
95
+ # For those that don't like to leave your entire repository on
96
+ # your production server you can:
97
+ #
98
+ # set :deploy_via, :export
99
+ #
100
+ # To deploy from a local repository:
101
+ #
102
+ # set :repository, "file://."
103
+ # set :deploy_via, :copy
104
+ #
105
+ # AUTHORS
106
+ # -------
107
+ #
108
+ # Garry Dolley http://scie.nti.st
109
+ # Contributions by Geoffrey Grosenbach http://topfunky.com
110
+ # Scott Chacon http://jointheconversation.org
111
+ # Alex Arnell http://twologic.com
112
+ # and Phillip Goldenburg
113
+
114
+ class Git < Base
115
+ # Sets the default command name for this SCM on your *local* machine.
116
+ # Users may override this by setting the :scm_command variable.
117
+ default_command "git"
118
+
119
+ # When referencing "head", use the branch we want to deploy or, by
120
+ # default, Git's reference of HEAD (the latest changeset in the default
121
+ # branch, usually called "master").
122
+ def head
123
+ variable(:branch) || 'HEAD'
124
+ end
125
+
126
+ def origin
127
+ variable(:remote) || 'origin'
128
+ end
129
+
130
+ # Performs a clone on the remote machine, then checkout on the branch
131
+ # you want to deploy.
132
+ def checkout(revision, destination)
133
+ git = command
134
+ remote = origin
135
+
136
+ args = []
137
+
138
+ # Add an option for the branch name so :git_shallow_clone works with branches
139
+ args << "-b #{variable(:branch)}" unless variable(:branch).nil?
140
+ args << "-o #{remote}" unless remote == 'origin'
141
+ if depth = variable(:git_shallow_clone)
142
+ args << "--depth #{depth}"
143
+ end
144
+
145
+ execute = []
146
+ execute << "#{git} clone #{verbose} #{args.join(' ')} #{variable(:repository)} #{destination}"
147
+
148
+ # checkout into a local branch rather than a detached HEAD
149
+ execute << "cd #{destination} && #{git} checkout #{verbose} -b deploy #{revision}"
150
+
151
+ if variable(:git_enable_submodules)
152
+ execute << "#{git} submodule #{verbose} init"
153
+ execute << "#{git} submodule #{verbose} sync"
154
+ if false == variable(:git_submodules_recursive)
155
+ execute << "#{git} submodule #{verbose} update --init"
156
+ else
157
+ execute << %Q(export GIT_RECURSIVE=$([ ! "`#{git} --version`" \\< "git version 1.6.5" ] && echo --recursive))
158
+ execute << "#{git} submodule #{verbose} update --init $GIT_RECURSIVE"
159
+ end
160
+ end
161
+
162
+ execute.compact.join(" && ").gsub(/\s+/, ' ')
163
+ end
164
+
165
+ # An expensive export. Performs a checkout as above, then
166
+ # removes the repo.
167
+ def export(revision, destination)
168
+ checkout(revision, destination) << " && rm -Rf #{destination}/.git"
169
+ end
170
+
171
+ # Merges the changes to 'head' since the last fetch, for remote_cache
172
+ # deployment strategy
173
+ def sync(revision, destination)
174
+ git = command
175
+ remote = origin
176
+
177
+ execute = []
178
+ execute << "cd #{destination}"
179
+
180
+ # Use git-config to setup a remote tracking branches. Could use
181
+ # git-remote but it complains when a remote of the same name already
182
+ # exists, git-config will just silenty overwrite the setting every
183
+ # time. This could cause wierd-ness in the remote cache if the url
184
+ # changes between calls, but as long as the repositories are all
185
+ # based from each other it should still work fine.
186
+ if remote != 'origin'
187
+ execute << "#{git} config remote.#{remote}.url #{variable(:repository)}"
188
+ execute << "#{git} config remote.#{remote}.fetch +refs/heads/*:refs/remotes/#{remote}/*"
189
+ end
190
+
191
+ # since we're in a local branch already, just reset to specified revision rather than merge
192
+ execute << "#{git} fetch #{verbose} #{remote} && #{git} fetch --tags #{verbose} #{remote} && #{git} reset #{verbose} --hard #{revision}"
193
+
194
+ if variable(:git_enable_submodules)
195
+ execute << "#{git} submodule #{verbose} init"
196
+ execute << "#{git} submodule #{verbose} sync"
197
+ if false == variable(:git_submodules_recursive)
198
+ execute << "#{git} submodule #{verbose} update --init"
199
+ else
200
+ execute << %Q(export GIT_RECURSIVE=$([ ! "`#{git} --version`" \\< "git version 1.6.5" ] && echo --recursive))
201
+ execute << "#{git} submodule #{verbose} update --init $GIT_RECURSIVE"
202
+ end
203
+ end
204
+
205
+ # Make sure there's nothing else lying around in the repository (for
206
+ # example, a submodule that has subsequently been removed).
207
+ execute << "#{git} clean #{verbose} -d -x -f"
208
+
209
+ execute.join(" && ")
210
+ end
211
+
212
+ # Returns a string of diffs between two revisions
213
+ def diff(from, to=nil)
214
+ return scm :diff, from unless to
215
+ scm :diff, "#{from}..#{to}"
216
+ end
217
+
218
+ # Returns a log of changes between the two revisions (inclusive).
219
+ def log(from, to=nil)
220
+ scm :log, "#{from}..#{to}"
221
+ end
222
+
223
+ # Getting the actual commit id, in case we were passed a tag
224
+ # or partial sha or something - it will return the sha if you pass a sha, too
225
+ def query_revision(revision)
226
+ raise ArgumentError, "Deploying remote branches is no longer supported. Specify the remote branch as a local branch for the git repository you're deploying from (ie: '#{revision.gsub('origin/', '')}' rather than '#{revision}')." if revision =~ /^origin\//
227
+ return revision if revision =~ /^[0-9a-f]{40}$/
228
+ command = scm('ls-remote', repository, revision)
229
+ result = yield(command)
230
+ revdata = result.split(/[\t\n]/)
231
+ newrev = nil
232
+ revdata.each_slice(2) do |refs|
233
+ rev, ref = *refs
234
+ if ref.sub(/refs\/.*?\//, '').strip == revision.to_s
235
+ newrev = rev
236
+ break
237
+ end
238
+ end
239
+ return newrev if newrev =~ /^[0-9a-f]{40}$/
240
+
241
+ # If sha is not found on remote, try expanding from local repository
242
+ command = scm('rev-parse --revs-only', revision)
243
+ newrev = yield(command).to_s.strip
244
+
245
+ raise "Unable to resolve revision for '#{revision}' on repository '#{repository}'." unless newrev =~ /^[0-9a-f]{40}$/
246
+ return newrev
247
+ end
248
+
249
+ def command
250
+ # For backwards compatibility with 1.x version of this module
251
+ variable(:git) || super
252
+ end
253
+
254
+ # Determines what the response should be for a particular bit of text
255
+ # from the SCM. Password prompts, connection requests, passphrases,
256
+ # etc. are handled here.
257
+ def handle_data(state, stream, text)
258
+ host = state[:channel][:host]
259
+ logger.info "[#{host} :: #{stream}] #{text}"
260
+ case text
261
+ when /\bpassword.*:/i
262
+ # git is prompting for a password
263
+ unless pass = variable(:scm_password)
264
+ pass = Capistrano::CLI.password_prompt
265
+ end
266
+ "#{pass}\n"
267
+ when %r{\(yes/no\)}
268
+ # git is asking whether or not to connect
269
+ "yes\n"
270
+ when /passphrase/i
271
+ # git is asking for the passphrase for the user's key
272
+ unless pass = variable(:scm_passphrase)
273
+ pass = Capistrano::CLI.password_prompt
274
+ end
275
+ "#{pass}\n"
276
+ when /accept \(t\)emporarily/
277
+ # git is asking whether to accept the certificate
278
+ "t\n"
279
+ end
280
+ end
281
+
282
+ private
283
+
284
+ # If verbose output is requested, return nil, otherwise return the
285
+ # command-line switch for "quiet" ("-q").
286
+ def verbose
287
+ variable(:scm_verbose) ? nil : "-q"
288
+ end
289
+ end
290
+ end
291
+ end
292
+ end
293
+