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.
- data/.ruby-version +1 -0
- data/CHANGELOG.rdoc +6 -1
- data/Rakefile +5 -3
- data/VERSION +1 -1
- data/lib/right_develop/ci/java_spec_formatter.rb +1 -1
- data/lib/right_develop/commands/git.rb +28 -2
- data/lib/right_develop/git/rake_task.rb +102 -0
- data/lib/right_develop/git.rb +27 -19
- data/lib/right_develop/parsers/sax_parser.rb +22 -5
- data/lib/right_develop/parsers/xml_post_parser.rb +11 -2
- data/lib/right_develop/parsers.rb +1 -1
- data/lib/right_develop/s3/interface.rb +298 -0
- data/lib/right_develop/s3/rake_task.rb +168 -0
- data/lib/right_develop/s3.rb +31 -0
- data/lib/right_develop/utility/git.rb +364 -0
- data/lib/right_develop/utility/shell.rb +131 -0
- data/lib/right_develop/utility/versioning.rb +183 -0
- data/lib/right_develop/utility.rb +32 -0
- data/lib/right_develop.rb +3 -1
- data/right_develop.gemspec +25 -298
- data/right_develop.rconf +3 -3
- metadata +184 -1729
- data/lib/right_develop/git/branch.rb +0 -74
- data/lib/right_develop/git/branch_collection.rb +0 -72
- data/lib/right_develop/git/commit.rb +0 -30
- data/lib/right_develop/git/repository.rb +0 -57
@@ -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
|