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