-
Notifications
You must be signed in to change notification settings - Fork 2.6k
/
push.rb
290 lines (255 loc) · 10.6 KB
/
push.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
require 'tempfile'
require 'fileutils'
require 'active_support/core_ext/string/inflections'
module Pod
class Command
class Repo < Command
class Push < Repo
self.summary = 'Push new specifications to a spec-repo'
self.description = <<-DESC
Validates `NAME.podspec` or `*.podspec` in the current working dir,
creates a directory and version folder for the pod in the local copy of
`REPO` (#{Config.instance.repos_dir}/[REPO]), copies the podspec file into the
version directory, and finally it pushes `REPO` to its remote.
DESC
self.arguments = [
CLAide::Argument.new('REPO', true),
CLAide::Argument.new('NAME.podspec', false),
]
def self.options
[
['--allow-warnings', 'Allows pushing even if there are warnings'],
['--use-libraries', 'Linter uses static libraries to install the spec'],
['--use-modular-headers', 'Lint uses modular headers during installation'],
["--sources=#{Pod::TrunkSource::TRUNK_REPO_URL}", 'The sources from which to pull dependent pods ' \
'(defaults to all available repos). Multiple sources must be comma-delimited'],
['--local-only', 'Does not perform the step of pushing REPO to its remote'],
['--no-private', 'Lint includes checks that apply only to public repos'],
['--skip-import-validation', 'Lint skips validating that the pod can be imported'],
['--skip-tests', 'Lint skips building and running tests during validation'],
['--commit-message="Fix bug in pod"', 'Add custom commit message. Opens default editor if no commit ' \
'message is specified'],
['--use-json', 'Convert the podspec to JSON before pushing it to the repo'],
['--swift-version=VERSION', 'The `SWIFT_VERSION` that should be used when linting the spec. ' \
'This takes precedence over the Swift versions specified by the spec or a `.swift-version` file'],
['--no-overwrite', 'Disallow pushing that would overwrite an existing spec'],
].concat(super)
end
def initialize(argv)
@allow_warnings = argv.flag?('allow-warnings')
@local_only = argv.flag?('local-only')
@repo = argv.shift_argument
@source = source_for_repo
@source_urls = argv.option('sources', config.sources_manager.all.map(&:url).join(',')).split(',')
@podspec = argv.shift_argument
@use_frameworks = !argv.flag?('use-libraries')
@use_modular_headers = argv.flag?('use-modular-headers', false)
@private = argv.flag?('private', true)
@message = argv.option('commit-message')
@commit_message = argv.flag?('commit-message', false)
@use_json = argv.flag?('use-json')
@swift_version = argv.option('swift-version', nil)
@skip_import_validation = argv.flag?('skip-import-validation', false)
@skip_tests = argv.flag?('skip-tests', false)
@allow_overwrite = argv.flag?('overwrite', true)
super
end
def validate!
super
help! 'A spec-repo name or url is required.' unless @repo
unless @source && @source.repo.directory?
raise Informative,
"Unable to find the `#{@repo}` repo. " \
'If it has not yet been cloned, add it via `pod repo add`.'
end
end
def run
open_editor if @commit_message && @message.nil?
check_if_push_allowed
validate_podspec_files
check_repo_status
update_repo
add_specs_to_repo
push_repo unless @local_only
end
#---------------------------------------------------------------------#
private
# @!group Push sub-steps
extend Executable
executable :git
# Open default editor to allow users to enter commit message
#
def open_editor
return if ENV['EDITOR'].nil?
file = Tempfile.new('cocoapods')
File.chmod(0777, file.path)
file.close
system("#{ENV['EDITOR']} #{file.path}")
@message = File.read file.path
end
# Temporary check to ensure that users do not push accidentally private
# specs to the master repo.
#
def check_if_push_allowed
if @source.is_a?(CDNSource)
raise Informative, 'Cannot push to a CDN source, as it is read-only.'
end
remotes, = Executable.capture_command('git', %w(remote --verbose), :capture => :merge, :chdir => repo_dir)
master_repo_urls = [
'git@github.com:CocoaPods/Specs.git',
'https://github.com/CocoaPods/Specs.git',
]
is_master_repo = master_repo_urls.any? do |url|
remotes.include?(url)
end
if is_master_repo
raise Informative, 'To push to the CocoaPods master repo use ' \
"the `pod trunk push` command.\n\nIf you are using a fork of " \
'the master repo for private purposes we recommend to migrate ' \
'to a clean private repo. To disable this check remove the ' \
'remote pointing to the CocoaPods master repo.'
end
end
# Performs a full lint against the podspecs.
#
def validate_podspec_files
UI.puts "\nValidating #{'spec'.pluralize(count)}".yellow
podspec_files.each do |podspec|
validator = Validator.new(podspec, @source_urls)
validator.allow_warnings = @allow_warnings
validator.use_frameworks = @use_frameworks
validator.use_modular_headers = @use_modular_headers
validator.ignore_public_only_results = @private
validator.swift_version = @swift_version
validator.skip_import_validation = @skip_import_validation
validator.skip_tests = @skip_tests
begin
validator.validate
rescue => e
raise Informative, "The `#{podspec}` specification does not validate." \
"\n\n#{e.message}"
end
raise Informative, "The `#{podspec}` specification does not validate." unless validator.validated?
end
end
# Checks that the repo is clean.
#
# @raise If the repo is not clean.
#
# @todo Add specs for staged and unstaged files.
#
# @todo Gracefully handle the case where source is not under git
# source control.
#
# @return [void]
#
def check_repo_status
porcelain_status, = Executable.capture_command('git', %w(status --porcelain), :capture => :merge, :chdir => repo_dir)
clean = porcelain_status == ''
raise Informative, "The repo `#{@repo}` at #{UI.path repo_dir} is not clean" unless clean
end
# Updates the git repo against the remote.
#
# @return [void]
#
def update_repo
UI.puts "Updating the `#{@repo}' repo\n".yellow
git!(%W(-C #{repo_dir} pull))
end
# Commits the podspecs to the source, which should be a git repo.
#
# @note The pre commit hook of the repo is skipped as the podspecs have
# already been linted.
#
# @return [void]
#
def add_specs_to_repo
UI.puts "\nAdding the #{'spec'.pluralize(count)} to the `#{@repo}' repo\n".yellow
podspec_files.each do |spec_file|
spec = Pod::Specification.from_file(spec_file)
output_path = @source.pod_path(spec.name) + spec.version.to_s
message = if @message && !@message.empty?
@message
elsif output_path.exist?
"[Fix] #{spec}"
elsif output_path.dirname.directory?
"[Update] #{spec}"
else
"[Add] #{spec}"
end
if output_path.exist? && !@allow_overwrite
raise Informative, "#{spec} already exists and overwriting has been disabled."
end
FileUtils.mkdir_p(output_path)
if @use_json
json_file_name = "#{spec.name}.podspec.json"
json_file = File.join(output_path, json_file_name)
File.open(json_file, 'w') { |file| file.write(spec.to_pretty_json) }
else
FileUtils.cp(spec_file, output_path)
end
# only commit if modified
if repo_git('status', '--porcelain').include?(spec.name)
UI.puts " - #{message}"
repo_git('add', spec.name)
repo_git('commit', '--no-verify', '-m', message)
else
UI.puts " - [No change] #{spec}"
end
end
end
# Pushes the git repo against the remote.
#
# @return [void]
#
def push_repo
UI.puts "\nPushing the `#{@repo}' repo\n".yellow
repo_git('push', 'origin', 'HEAD')
end
#---------------------------------------------------------------------#
private
# @!group Private helpers
# @return result of calling the git! with args in repo_dir
#
def repo_git(*args)
git!(['-C', repo_dir] + args)
end
# @return [Pathname] The directory of the repository.
#
def repo_dir
@source.specs_dir
end
# @return [Array<Pathname>] The path of the specifications to push.
#
def podspec_files
if @podspec
path = Pathname(@podspec)
raise Informative, "Couldn't find #{@podspec}" unless path.exist?
[path]
else
files = Pathname.glob('*.podspec{,.json}')
raise Informative, "Couldn't find any podspec files in current directory" if files.empty?
files
end
end
# @return [Integer] The number of the podspec files to push.
#
def count
podspec_files.count
end
# Returns source for @repo
#
# @note If URL is invalid or repo doesn't exist, validate! will throw the error
#
# @return [Source]
#
def source_for_repo
config.sources_manager.source_with_name_or_url(@repo) unless @repo.nil?
rescue
nil
end
#---------------------------------------------------------------------#
end
end
end
end