right_develop 1.2.2 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,168 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # Once this file is required, the Rake DSL is loaded - don't do this except inside Rake!!
24
+ require 'rake/tasklib'
25
+
26
+ # Make sure the rest of RightDevelop & S3 is required, since this file can be
27
+ # required directly.
28
+ require 'right_develop'
29
+ require 'right_develop/s3'
30
+
31
+ # localized
32
+ require 'tmpdir'
33
+
34
+ module RightDevelop::S3
35
+
36
+ class RakeTask < ::Rake::TaskLib
37
+ DEFAULT_OPTIONS = {
38
+ :s3_namespace => :s3
39
+ }
40
+
41
+ include ::Rake::DSL if defined?(::Rake::DSL)
42
+
43
+ attr_accessor :s3_namespace
44
+
45
+ def initialize(options = {})
46
+ # Let client provide options object-style, in our initializer
47
+ options = DEFAULT_OPTIONS.merge(options)
48
+ self.s3_namespace = options[:s3_namespace]
49
+
50
+ # Let client provide options DSL-style by calling our writers
51
+ yield(self) if block_given?
52
+
53
+ namespace self.s3_namespace do
54
+
55
+ desc 'List files in S3 bucket'
56
+ task :list_files, [:bucket, :subdirectory, :recursive, :filters] do |task, args|
57
+ raise ::ArgumentError.new(":bucket is required") unless bucket = args[:bucket]
58
+ list = storage.list_files(
59
+ bucket,
60
+ :subdirectory => args[:subdirectory],
61
+ :recursive => args[:recursive] != 'false',
62
+ :filters => args[:filters])
63
+ puts "Files in S3 bucket \"#{bucket}/#{args[:subdirectory]}\":"
64
+ list.sort.each { |path| puts " #{path}" }
65
+ end
66
+
67
+ desc 'Download files from S3 bucket'
68
+ task :download_files, [:bucket, :to_dir_path, :subdirectory, :recursive, :filters] do |task, args|
69
+ raise ::ArgumentError.new(":bucket is required") unless bucket = args[:bucket]
70
+ raise ::ArgumentError.new(":to_dir_path is required") unless to_dir_path = args[:to_dir_path]
71
+ count = storage.download_files(
72
+ bucket,
73
+ to_dir_path,
74
+ :subdirectory => args[:subdirectory],
75
+ :recursive => args[:recursive] != 'false',
76
+ :filters => args[:filters])
77
+ puts "Downloaded #{count} file(s)."
78
+ end
79
+
80
+ desc 'Upload files to S3 bucket'
81
+ task :upload_files, [:bucket, :from_dir_path, :subdirectory, :recursive, :access, :filters] do |task, args|
82
+ raise ::ArgumentError.new(":bucket is required") unless bucket = args[:bucket]
83
+ raise ::ArgumentError.new(":from_dir_path is required") unless from_dir_path = args[:from_dir_path]
84
+ count = storage.upload_files(
85
+ bucket,
86
+ from_dir_path,
87
+ :subdirectory => args[:subdirectory],
88
+ :recursive => args[:recursive] != 'false',
89
+ :access => args[:access],
90
+ :filters => args[:filters])
91
+ puts "Uploaded #{count} file(s)."
92
+ end
93
+
94
+ desc 'Copy files between S3 buckets'
95
+ task :copy_files, [:from_bucket, :from_subdirectory, :to_bucket, :to_subdirectory, :recursive, :access, :filters] do |task, args|
96
+ raise ::ArgumentError.new(":from_bucket is required") unless from_bucket = args[:from_bucket]
97
+ raise ::ArgumentError.new(":to_bucket is required") unless to_bucket = args[:to_bucket]
98
+ verbose = ::Rake.application.options.trace
99
+ recursive = args[:recursive] != 'false'
100
+
101
+ # establish from/to credentials before copying.
102
+ from_storage = Interface.new(
103
+ :aws_access_key_id => ENV['FROM_AWS_ACCESS_KEY_ID'],
104
+ :aws_secret_access_key => ENV['FROM_AWS_SECRET_ACCESS_KEY'],
105
+ :logger => logger)
106
+ to_storage = Interface.new(
107
+ :aws_access_key_id => ENV['TO_AWS_ACCESS_KEY_ID'],
108
+ :aws_secret_access_key => ENV['TO_AWS_SECRET_ACCESS_KEY'],
109
+ :logger => logger)
110
+
111
+ # download
112
+ ::Dir.mktmpdir do |temp_dir|
113
+ ::Dir.chdir(temp_dir) do
114
+ download_count = from_storage.download_files(
115
+ from_bucket,
116
+ temp_dir,
117
+ :subdirectory => args[:from_subdirectory],
118
+ :recursive => recursive,
119
+ :filters => args[:filters])
120
+
121
+ upload_count = to_storage.upload_files(
122
+ to_bucket,
123
+ temp_dir,
124
+ :subdirectory => args[:to_subdirectory],
125
+ :recursive => recursive,
126
+ :access => args[:access],
127
+ :filters => nil) # already filtered during download
128
+
129
+ if upload_count == download_count
130
+ puts "Copied #{upload_count} file(s)."
131
+ else
132
+ fail "Failed to upload all downloaded files (#{upload_count} uploaded != #{download_count} downloaded)."
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ desc 'Delete files from S3 bucket'
139
+ task :delete_files, [:bucket, :subdirectory, :recursive, :filters] do |task, args|
140
+ raise ::ArgumentError.new(":bucket is required") unless bucket = args[:bucket]
141
+ count = storage.delete_files(
142
+ bucket,
143
+ :subdirectory => args[:subdirectory],
144
+ :recursive => args[:recursive] != 'false',
145
+ :filters => args[:filters])
146
+ puts "Deleted #{count} file(s)."
147
+ end
148
+
149
+ end # namespace
150
+ end # initialize
151
+
152
+ def logger
153
+ unless @logger
154
+ verbose = Rake.application.options.trace
155
+ @logger = verbose ? Logger.new(STDOUT) : RightDevelop::Utility::Shell.null_logger
156
+ end
157
+ @logger
158
+ end
159
+
160
+ def storage
161
+ @storage ||= Interface.new(
162
+ :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
163
+ :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'],
164
+ :logger => logger)
165
+ end
166
+
167
+ end # RakeTask
168
+ end # RightDevelop::Buckets
@@ -0,0 +1,31 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ module RightDevelop
24
+ module S3
25
+ # Defer loading the Rake task; it mixes the Rake DSL into everything!
26
+ # Only the Rakefiles themselves should refer to this constant.
27
+ autoload :RakeTask, 'right_develop/s3/rake_task'
28
+ end
29
+ end
30
+
31
+ require 'right_develop/s3/interface'
@@ -0,0 +1,364 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_develop/utility'
25
+ require 'right_git'
26
+
27
+ module RightDevelop::Utility::Git
28
+
29
+ DEFAULT_REPO_OPTIONS = {
30
+ :logger => ::RightDevelop::Utility::Shell.default_logger,
31
+ :shell => ::RightDevelop::Utility::Shell
32
+ }.freeze
33
+
34
+ class VerifyError < StandardError; end
35
+
36
+ module_function
37
+
38
+ # Factory method for a default repository object from the current working
39
+ # directory. The working directory can change but the repo directory is
40
+ # preserved by the object.
41
+ #
42
+ # @return [RightGit::Git::Repository] new repository
43
+ def default_repository
44
+ ::RightGit::Git::Repository.new('.', DEFAULT_REPO_OPTIONS)
45
+ end
46
+
47
+ # Performs setup of the working directory repository for automation or
48
+ # development. Currently this only involves initializing/updating submodules.
49
+ #
50
+ # @return [TrueClass] always true
51
+ def setup
52
+ default_repository.update_submodules(:recursive => true)
53
+ true
54
+ end
55
+
56
+ # @return [TrueClass|FalseClass] true if the given revision is a commit SHA
57
+ def is_sha?(revision)
58
+ ::RightGit::Git::Commit.sha?(revision)
59
+ end
60
+
61
+ # Determine if the branch given by name exists.
62
+ #
63
+ # @param [String] branch_name to query
64
+ # @param [Hash] options for query
65
+ # @option options [TrueClass|FalseClass] :remote to find remote branches
66
+ # @option options [TrueClass|FalseClass] :local to find local branches
67
+ # @option options [RightGit::Git::Repository] :repo to use or nil
68
+ #
69
+ # @return [TrueClass|FalseClass] true if branch exists
70
+ def branch_exists?(branch_name, options = {})
71
+ options = {
72
+ :remote => true,
73
+ :local => true,
74
+ :repo => nil
75
+ }.merge(options)
76
+ remote = options[:remote]
77
+ local = options[:local]
78
+ repo = options[:repo] || default_repository
79
+ unless local || remote
80
+ raise ::ArgumentError, 'Either remote or local must be true'
81
+ end
82
+ both = local && remote
83
+ repo.branches(:all => remote).any? do |branch|
84
+ branch.name == branch_name && (both || remote == branch.remote?)
85
+ end
86
+ end
87
+
88
+ # Determine if the tag given by name exists.
89
+ #
90
+ # @param [String] tag_name to query
91
+ # @param [Hash] options for query
92
+ # @option options [RightGit::Git::Repository] :repo to use or nil
93
+ #
94
+ # @return [TrueClass|FalseClass] true if tag exists
95
+ def tag_exists?(tag_name, options = {})
96
+ options = {
97
+ :repo => nil
98
+ }.merge(options)
99
+ repo = options[:repo] || default_repository
100
+
101
+ # note that remote tags cannot be queried directly; use git fetch --tags to
102
+ # import them first.
103
+ repo.tags.any? { |tag| tag.name == tag_name }
104
+ end
105
+
106
+ # Clones the repo given by URL to the given destination (if any).
107
+ #
108
+ # @param [String] repo URL to clone
109
+ # @param [String] destination path where repo is cloned
110
+ #
111
+ # @return [TrueClass] always true
112
+ def clone_to(repo, destination)
113
+ ::RightGit::Git::Repository.clone_to(repo, destination, DEFAULT_REPO_OPTIONS)
114
+ true
115
+ end
116
+
117
+ # Generates a difference from the current workspace to the given commit on the
118
+ # same branch as a sorted list of relative file paths. This is useful for
119
+ # creating a list of files to patch, etc.
120
+ #
121
+ # @param [String] commit to diff from (e.g. 'master')
122
+ # @return [String] list of relative file paths from diff or empty
123
+ def diff_files_from(commit)
124
+ git_args = ['diff', '--stat', '--name-only', commit]
125
+ result = default_repository.git_output(git_args).lines.map { |line| line.strip }.sort
126
+ # not sure if git would ever mention directories in a diff, but ignore them.
127
+ result.delete_if { |item| ::File.directory?(item) }
128
+ return result
129
+ end
130
+
131
+ # Checks out the given revision (tag, branch or SHA) and optionally creates a
132
+ # new branch from it.
133
+ #
134
+ # @param [String] revision to checkout
135
+ # @param [Hash] options for checkout
136
+ # @option options [TrueClass|FalseClass] :force to perform hard reset and force checkout
137
+ # @option options [String] :new_branch_name to create after checkout or nil
138
+ # @option options [TrueClass|FalseClass] :recursive to perform a recursive checkout to same branch or tag (but not for SHA)
139
+ #
140
+ # @return [TrueClass] always true
141
+ def checkout_revision(revision, options = {})
142
+ options = {
143
+ :new_branch_name => nil,
144
+ :force => true,
145
+ :recursive => true
146
+ }.merge(options)
147
+
148
+ # check parameters.
149
+ new_branch_name = options[:new_branch_name]
150
+ if new_branch_name && new_branch_name == revision
151
+ raise ::ArgumentError, "revision cannot be same as new_branch_name: #{revision}"
152
+ end
153
+ unless [TrueClass, FalseClass, NilClass].include?(options[:force].class)
154
+ raise ::ArgumentError, "force must be a boolean"
155
+ end
156
+ force = !!options[:force]
157
+
158
+ # hard reset any local changes before attempting checkout, if forced.
159
+ repo = default_repository
160
+ logger = repo.logger
161
+ logger.info("Performing checkout in #{repo.repo_dir.inspect}")
162
+ repo.hard_reset_to(nil) if force
163
+
164
+ # fetch to ensure revision is known and most up-to-date.
165
+ repo.fetch_all
166
+
167
+ # do full checkout of revision with submodule update before any attempt to
168
+ # create a new branch. this handles some wierd git failures where submodules
169
+ # are changing between major/minor versions of the code.
170
+ repo.checkout_to(revision, :force => true)
171
+
172
+ # note that the checkout-to-a-branch will simply switch to a local copy of
173
+ # the branch which may or may not by synchronized with its remote origin. to
174
+ # ensure the branch is synchronized, perform a pull.
175
+ is_sha = is_sha?(revision)
176
+ needs_pull = (
177
+ !is_sha &&
178
+ branch_exists?(revision, :remote => true, :local => false, :repo => repo)
179
+ )
180
+ if needs_pull
181
+ # hard reset to remote origin to overcome any local branch divergence.
182
+ repo.hard_reset_to("origin/#{revision}") if force
183
+
184
+ # a pull is not needed at this point if we forced hard reset but it is
185
+ # always nice to see it succeed in the output.
186
+ repo.spit_output('pull', 'origin', revision)
187
+ end
188
+
189
+ # perform a localized hard reset to revision just to prove that revision is
190
+ # now known to the local git database.
191
+ repo.hard_reset_to(revision)
192
+
193
+ # note that the submodule update is non-recursive for tags and branches in
194
+ # case the submodule needs to checkout to a specific branch before updating
195
+ # its own submodules. it would be strange to recursively update submodules
196
+ # from the parent and then have the recursively checked-out child revision
197
+ # (branch or tag) introduce a different set of submodules.
198
+ repo.update_submodules(:recursive => is_sha && options[:recursive])
199
+
200
+ # recursively checkout submodules, if requested and unless we determine the
201
+ # revision is a SHA (in which case recursive+SHA is ignored).
202
+ if !is_sha && options[:recursive]
203
+ repo.submodule_paths(:recursive => false).each do |submodule_path|
204
+ # note that recursion will use the current directory and create a new
205
+ # repo by calling default_repository.
206
+ ::Dir.chdir(submodule_path) do
207
+ checkout_revision(revision, options)
208
+ end
209
+ end
210
+ end
211
+
212
+ # create a new branch from fully resolved directory, if requested.
213
+ repo.spit_output('checkout', '-b', new_branch_name) if new_branch_name
214
+ true
215
+ end
216
+
217
+ # Verifies that the local repository and all submodules match the expected
218
+ # revision (branch, tag or SHA).
219
+ #
220
+ # @param [String] revision to check or nil to use base directory revision
221
+ #
222
+ # @return [TrueClass] always true
223
+ #
224
+ # @raise [VerifyError] on failure
225
+ def verify_revision(revision = nil)
226
+ repo = default_repository
227
+ logger = repo.logger
228
+ if revision
229
+ # check current directory against revision.
230
+ actual_revision = current_revision(revision, :repo => repo)
231
+ if revision != actual_revision
232
+ message =
233
+ 'Base directory is in an inconsistent state' +
234
+ " (#{revision} != #{actual_revision}): #{repo.repo_dir.inspect}"
235
+ raise VerifyError, message
236
+ end
237
+ else
238
+ # determine revision to check from local HEAD state if not given. at best
239
+ # this will be a branch or tag, at worst a SHA.
240
+ logger.info("Resolving the default branch, tag or SHA to use for verification in #{repo.repo_dir.inspect}")
241
+ revision = current_revision(nil, :repo => repo)
242
+ end
243
+
244
+ # start verify.
245
+ logger.info("Verifying consistency of revision=#{revision} in #{repo.repo_dir.inspect}")
246
+ if is_sha?(revision)
247
+ revision_type = :sha
248
+ elsif branch_exists?(revision, :remote => false, :local => true, :repo => repo)
249
+ revision_type = :branch
250
+ else
251
+ revision_type = :tag
252
+ end
253
+
254
+ # for SHAs and tags, verify that expected submodule commits are checked-out
255
+ # by looking for +,- in the submodule status. any that are out of sync will
256
+ # not have a blank space on the left-hand side.
257
+ if revision_type != :branch
258
+ repo.git_output('submodule status --recursive').lines.each do |line|
259
+ data = line.chomp
260
+ if matched = ::RightGit::Git::Repository::SUBMODULE_STATUS_REGEX.match(data)
261
+ if matched[1] != ' '
262
+ message =
263
+ 'At least one submodule is in an inconsistent state:' +
264
+ " #{::File.expand_path(matched[3])}"
265
+ raise VerifyError, message
266
+ end
267
+ else
268
+ raise VerifyError,
269
+ "Unexpected output from submodule status: #{data.inspect}"
270
+ end
271
+ end
272
+ end
273
+
274
+ # branches are not required to have up-to-date submodule commits for ad-hoc
275
+ # building purposes but all submodules should be checked-out to the same
276
+ # branch (and that branch must exist for all submodules).
277
+ #
278
+ # for the tag case (i.e. release candidate), we peform a double-check
279
+ # inside each submodule to verify that what the submodule thinks is tagged
280
+ # is the same as the submodule SHA from the parent. the same tag must exist
281
+ # and must be consistent for all submodules.
282
+ if revision_type != :sha
283
+ repo.submodule_paths(:recursive => true).each do |submodule_path|
284
+ sub_repo = ::RightGit::Git::Repository.new(submodule_path, DEFAULT_REPO_OPTIONS)
285
+ logger.info("Inspecting #{sub_repo.repo_dir.inspect}")
286
+ actual_revision = current_revision(revision, :repo => sub_repo)
287
+ if revision != actual_revision
288
+ message =
289
+ 'At least one submodule is in an inconsistent state' +
290
+ " (#{revision} != #{actual_revision}): #{sub_repo.repo_dir.inspect}"
291
+ raise VerifyError, message
292
+ end
293
+ end
294
+ end
295
+ true
296
+ end
297
+
298
+ # Attempts to determine which branch, tag or SHA to which the current
299
+ # directory is pointing. A directory can be pointing at both a branch and
300
+ # multiple tags at the same time it so uses the hint to pick one or the other,
301
+ # if given. if all else fails, the current SHA is returned.
302
+ #
303
+ # note that current revision is localized and so not directly related to the
304
+ # current state of remote branches or tags. do a fetch to ensure those.
305
+ #
306
+ # @param [String] hint for branch vs. tag or nil
307
+ # @param [Hash] options for query
308
+ # @option options [RightGit::Git::Repository] :repo to use or nil
309
+ #
310
+ # @return [String] current revision
311
+ def current_revision(hint = nil, options = {})
312
+ options = {
313
+ :repo => nil
314
+ }.merge(options)
315
+ repo = options[:repo] || default_repository
316
+
317
+ # SHA logic
318
+ actual_sha = repo.sha_for(nil)
319
+ return actual_sha if is_sha?(hint)
320
+
321
+ # branch logic
322
+ branch_hint = (
323
+ hint.nil? ||
324
+ branch_exists?(hint, :remote => true, :local => true, :repo => repo)
325
+ )
326
+ if branch_hint
327
+ branch = repo.git_output('rev-parse --abbrev-ref HEAD').strip
328
+ return branch if branch != 'HEAD'
329
+ end
330
+
331
+ # tag logic
332
+ if hint && tag_exists?(hint, :repo => repo)
333
+ hint_sha = repo.sha_for(hint)
334
+ return hint if hint_sha == actual_sha
335
+ end
336
+
337
+ # lookup tags for actual SHA, if any.
338
+ if first_tag = tags_for_sha(actual_sha, :repo => repo).first
339
+ return first_tag
340
+ end
341
+
342
+ # detached HEAD state, no matching branches or tags.
343
+ actual_sha
344
+ end
345
+
346
+ # Generates a list of tags pointing to the given SHA, if any.
347
+ # When the revision is a tag, only one tag is returned regardless of
348
+ # whether other tags reference the same SHA.
349
+ #
350
+ # @param [String] sha for tags
351
+ # @param [Hash] options for query
352
+ # @option options [RightGit::Git::Repository] :repo to use or nil
353
+ #
354
+ # @return [Array] tags for the revision or empty
355
+ def tags_for_sha(sha, options = {})
356
+ options = {
357
+ :repo => nil
358
+ }.merge(options)
359
+ repo = options[:repo] || default_repository
360
+ git_args = ['tag', '--contains', sha]
361
+ repo.git_output(git_args).lines.map { |line| line.strip }
362
+ end
363
+
364
+ end # RightDevelop::Utility::Git
@@ -0,0 +1,131 @@
1
+ #
2
+ # Copyright (c) 2013 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ # ancestor
24
+ require 'right_develop/utility'
25
+
26
+ require 'right_git'
27
+ require 'right_support'
28
+
29
+ module RightDevelop
30
+ module Utility
31
+
32
+ # extends default shell easy-singleton from right_git gem.
33
+ class Shell < ::RightGit::Shell::Default
34
+
35
+ class NullLoggerSingleton
36
+ @@logger = nil
37
+
38
+ def self.instance
39
+ @@logger ||= ::RightSupport::Log::NullLogger.new
40
+ end
41
+ end
42
+
43
+ # bundle exec sets GEM_HOME and GEM_PATH (in Windows?) and these need to
44
+ # be wacked in order to have a pristing rubygems environment since bundler
45
+ # won't clean them. also, if you 'bundle exec rake ...' and then put
46
+ # arguments to the right of the task name, then these args won't appear in
47
+ # Bundler::ORIGINAL_ENV.
48
+ # example: "bundle exec rake build:all DEBUG=true ..."
49
+ def setup_clean_env
50
+ # a little revisionist history music...
51
+ ::ENV.each do |key, value|
52
+ if key.start_with?('GEM_') || key.start_with?('BUNDLER_')
53
+ ::Bundler::ORIGINAL_ENV[key] = nil
54
+ elsif Bundler::ORIGINAL_ENV[key].nil?
55
+ ::Bundler::ORIGINAL_ENV[key] = value
56
+ end
57
+ end
58
+ ::Bundler.with_clean_env do
59
+ # now the ENV is clean and not missing any right-hand args so replace
60
+ # the ORIGINAL_ENV.
61
+ ::Bundler::ORIGINAL_ENV.replace(ENV)
62
+ end
63
+ true
64
+ end
65
+
66
+ # @return [TrueClass|FalseClass] true if running on Windows platform
67
+ def is_windows?
68
+ return !!(RUBY_PLATFORM =~ /mswin|win32|dos|mingw|cygwin/)
69
+ end
70
+
71
+ # Creates a null logger.
72
+ #
73
+ # @return [Logger] the null logger
74
+ def null_logger
75
+ NullLoggerSingleton.instance
76
+ end
77
+
78
+ # @return [Logger] default logger for STDOUT
79
+ def default_logger
80
+ @default_logger ||= ::Logger.new(STDOUT)
81
+ end
82
+
83
+ # Overrides ::RightGit::Shell::Default#execute
84
+ #
85
+ # @param [String] cmd the shell command to run
86
+ # @param [Hash] options for execution
87
+ # @option options :directory [String] to use as working directory during command execution or nil
88
+ # @option options :logger [Logger] logger for shell execution (default = STDOUT)
89
+ # @option options :outstream [IO] output stream to receive STDOUT and STDERR from command (default = none)
90
+ # @option options :raise_on_failure [TrueClass|FalseClass] if true, wil raise a RuntimeError if the command does not end successfully (default), false to ignore errors
91
+ # @option options :set_env_vars [Hash] environment variables to set during execution (default = none set)
92
+ # @option options :clear_env_vars [Hash] environment variables to clear during execution (default = none cleared but see :clean_bundler_env)
93
+ # @option options :clean_bundler_env [TrueClass|FalseClass] true to clear all bundler environment variables during execution (default), false to inherit bundler env from parent
94
+ # @option options :sudo [TrueClass|FalseClass] if true, will wrap command in sudo if needed, false to run as current user (default)
95
+ #
96
+ # @return [Integer] exitstatus of the command
97
+ #
98
+ # @raise [ShellError] on failure only if :raise_on_failure is true
99
+ def execute(cmd, options = {})
100
+ options = {
101
+ :clean_bundler_env => true,
102
+ :sudo => false
103
+ }.merge(options)
104
+
105
+ if options[:sudo]
106
+ fail "Not available in Windows" if is_windows?
107
+ cmd = "sudo #{cmd}" unless ::Process.euid == 0
108
+ end
109
+
110
+ # super execute.
111
+ super(cmd, options)
112
+ end
113
+
114
+ # Overrides ::RightGit::Shell::Default#configure_executioner
115
+ def configure_executioner(executioner, options)
116
+ # super configure early to ensure that any custom env vars are set after
117
+ # restoring the pre-bundler env.
118
+ executioner = super(executioner, options)
119
+
120
+ # clean all bundler env vars, if requested.
121
+ if options[:clean_bundler_env]
122
+ executioner = lambda do |e|
123
+ lambda { ::Bundler.with_clean_env { e.call } }
124
+ end.call(executioner)
125
+ end
126
+ executioner
127
+ end
128
+
129
+ end # Shell
130
+ end # Utility
131
+ end # RightDevelop