right_develop 1.2.2 → 2.0.1

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.
@@ -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