releasinator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,529 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'rake'
4
+ require 'colorize'
5
+ require 'json'
6
+ require 'tempfile'
7
+ require_relative '../command_processor'
8
+ require_relative '../config_hash'
9
+ require_relative '../copy_file'
10
+ require_relative '../current_release'
11
+ require_relative '../downstream_repo'
12
+ require_relative '../publisher'
13
+ require_relative '../validator'
14
+
15
+ include Releasinator
16
+
17
+ DOWNSTREAM_REPOS = "downstream_repos"
18
+
19
+ def get_base_dir
20
+ if @releasinator_config.has_key?(:base_docs_dir)
21
+ @releasinator_config[:base_docs_dir]
22
+ else
23
+ '.'
24
+ end
25
+ end
26
+
27
+ def use_git_flow()
28
+ return @releasinator_config[:use_git_flow] if @releasinator_config.has_key? :use_git_flow
29
+ false
30
+ end
31
+
32
+ def full_file_sync(options)
33
+ return options[:full_file_sync] if options.has_key? :full_file_sync
34
+ false
35
+ end
36
+
37
+ def release_to_github(options)
38
+ return options[:release_to_github] if options.has_key? :release_to_github
39
+ false
40
+ end
41
+
42
+ def git_tag(new_tag, changelog)
43
+ confirm_tag_overwrite(new_tag)
44
+ puts "tagging with changelog: \n\n#{@currentRelease.changelog}\n".yellow
45
+ changelog_tempfile = Tempfile.new("#{new_tag}.changelog")
46
+ changelog_tempfile.write(changelog)
47
+ changelog_tempfile.close
48
+ # include changelog in annotated tag
49
+ CommandProcessor.command("git tag -a -f #{new_tag} -F #{changelog_tempfile.path}")
50
+ changelog_tempfile.unlink
51
+ end
52
+
53
+ desc "read and validate the config, adding one if not found"
54
+ task :config do
55
+ @releasinator_config = ConfigHash.new(verbose == true, Rake.application.options.trace == true)
56
+ @validator = Validator.new(@releasinator_config)
57
+ @validator.validate_config
58
+ end
59
+
60
+ namespace :validate do
61
+
62
+ desc "validate that git is the correct version"
63
+ task :git_version => :config do
64
+ @validator.validate_git_version
65
+ end
66
+
67
+ desc "validate that git reports no untracked, unstaged, or uncommitted changes"
68
+ task :git => :config do
69
+ @validator.validate_clean_git
70
+ end
71
+
72
+ desc "validate current branch matches the latest on the server and follows naming conventions"
73
+ task :branch => [:config, :changelog] do
74
+ current_branch = GitUtil.get_current_branch()
75
+ @validator.validate_matches_branch(current_branch)
76
+ if use_git_flow()
77
+ expected_release_branch = "release/#{@currentRelease.release}"
78
+ abort("git flow expects the current branch to be either 'develop' or 'release/#{@currentRelease.release}'. Current branch is '#{current_branch}'".red) unless current_branch == expected_release_branch || current_branch == "develop"
79
+ else
80
+ abort("non-git flow expects releases to come from the master branch. Current branch is '#{current_branch}'".red) unless current_branch == "master"
81
+ end
82
+ end
83
+
84
+ desc "validate the presence README.md, renaming a similar file if found"
85
+ task :readme => :config do
86
+ @validator.validate_exist(get_base_dir(), "README.md", DOWNSTREAM_REPOS)
87
+ end
88
+
89
+ desc "validate the presence LICENSE, renaming a similar file if found - also validates that its referenced from README.md"
90
+ task :license => :config do
91
+ @validator.validate_exist(get_base_dir(), "LICENSE", DOWNSTREAM_REPOS)
92
+ @validator.validate_referenced_in_readme(get_base_dir(), "LICENSE")
93
+ end
94
+
95
+ desc "validate the presence CONTRIBUTING.md, renaming a similar file if found - also validates that its referenced from README.md"
96
+ task :contributing => :config do
97
+ @validator.validate_exist(get_base_dir(), "CONTRIBUTING.md", DOWNSTREAM_REPOS)
98
+ @validator.validate_referenced_in_readme(get_base_dir(), "CONTRIBUTING.md")
99
+ end
100
+
101
+ desc "validate the presence .github/ISSUE_TEMPLATE.md"
102
+ task :issue_template => :config do
103
+ @validator.validate_exist(get_base_dir(), ".github/ISSUE_TEMPLATE.md", DOWNSTREAM_REPOS)
104
+ end
105
+
106
+ desc "validate the presence, formatting, and semver sequence of CHANGELOG.md"
107
+ task :changelog => :config do
108
+ @currentRelease = @validator.validate_changelog(get_base_dir(), DOWNSTREAM_REPOS)
109
+ end
110
+
111
+ desc "validate the presence of .gitignore, adding it and any appropriate releasinator lines if necessary"
112
+ task :gitignore => :config do
113
+ @validator.validate_gitignore("#{DOWNSTREAM_REPOS}/", @releasinator_config.has_key?(:downstream_repos))
114
+ end
115
+
116
+ desc "validate all submodules are on the latest origin/master versions"
117
+ task :submodules => :config do
118
+ @validator.validate_submodules
119
+ end
120
+
121
+ desc "validate the current user can push to local repo"
122
+ task :github_permissions_local => [:config] do
123
+ @validator.validate_github_permissions(GitUtil.repo_url)
124
+ end
125
+
126
+ desc "validate the current user can push to downstream repos"
127
+ task :github_permissions_downstream, [:downstream_repo_index] => [:config] do |t, args|
128
+ if @releasinator_config.has_key?(:downstream_repos)
129
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
130
+ @validator.validate_github_permissions(downstream_repo.url)
131
+ end
132
+ else
133
+ Printer.success("Not validating permissions of downstream repos. None found.")
134
+ end
135
+ end
136
+
137
+ desc "run any configatron.custom_validation_methods"
138
+ task :custom => :config do
139
+ if @releasinator_config.has_key?(:custom_validation_methods)
140
+ @releasinator_config[:custom_validation_methods].each do |validate_method|
141
+ validate_method.call
142
+ end
143
+ Printer.success("All configatron.custom_validation_methods succeeded.")
144
+ else
145
+ Printer.success("No configatron.custom_validation_methods found.")
146
+ end
147
+ end
148
+
149
+ desc "validate all"
150
+ task :all =>
151
+ [
152
+ :git_version,
153
+ :gitignore,
154
+ :git,
155
+ :branch,
156
+ :submodules,
157
+ :readme,
158
+ :changelog,
159
+ :license,
160
+ :contributing,
161
+ :issue_template,
162
+ :github_permissions_local,
163
+ :github_permissions_downstream,
164
+ :custom
165
+ ] do
166
+ Printer.success("All validations passed.")
167
+ end
168
+ end
169
+
170
+ task :default => :release
171
+
172
+ desc "release all"
173
+ task :release => [:"validate:all",:"local:build",:"pm:all",:"downstream:all",:"local:push",:"docs:all"] do
174
+ Printer.success("Done releasing.")
175
+ end
176
+
177
+ def confirm_tag_overwrite(new_tag)
178
+ tag_results = CommandProcessor.command('git tag -l')
179
+ tag_results.split.each do |existing_tag|
180
+ if existing_tag == new_tag
181
+ Printer.check_proceed("Tag #{existing_tag} already present. Overwrite tag #{existing_tag}?", "Tag #{existing_tag} not overwritten.")
182
+ end
183
+ end
184
+ end
185
+
186
+ desc "iterate over the prerelease_checklist_items, asking the user if each is done"
187
+ task :prerelease_checklist => :config do
188
+ @releasinator_config[:prerelease_checklist_items].each do |prerelease_item|
189
+ Printer.check_proceed("#{prerelease_item}", "Then no release for you!")
190
+ end
191
+ end
192
+
193
+ namespace :local do
194
+ desc "ask user whether to proceed with release"
195
+ task :confirm do
196
+ Printer.check_proceed("You're about to release #{@currentRelease.release}!", "Then no release for you!")
197
+ end
198
+
199
+ desc "change branch for git flow, if using git flow"
200
+ task :prepare => [:config, :"validate:changelog"] do
201
+ if use_git_flow()
202
+ CommandProcessor.command("git checkout -b release/#{@currentRelease.release} develop") unless GitUtil.get_current_branch() != "develop"
203
+ end
204
+ end
205
+
206
+ desc "tag the local repo"
207
+ task :tag => :config do
208
+ git_tag(@currentRelease.release, @currentRelease.changelog)
209
+ end
210
+
211
+ desc "build the local repo"
212
+ task :build => [:config, :"validate:changelog", :prerelease_checklist, :confirm, :prepare, :tag] do
213
+ puts "building #{@currentRelease.release}" if @releasinator_config[:verbose]
214
+ @releasinator_config[:build_method].call
215
+ if @releasinator_config.has_key? :post_build_methods
216
+ @releasinator_config[:post_build_methods].each do |post_build_method|
217
+ post_build_method.call(@currentRelease.release)
218
+ end
219
+ end
220
+ end
221
+
222
+ desc "run the git flow branch magic (if configured) and push local to remote"
223
+ task :push => :config do
224
+ if use_git_flow()
225
+ CommandProcessor.command("git checkout master")
226
+ CommandProcessor.command("git pull")
227
+ CommandProcessor.command("git merge --no-ff release/#{@currentRelease.release}")
228
+ GitUtil.delete_branch "release/#{@currentRelease.release}"
229
+ CommandProcessor.command("git checkout develop")
230
+ CommandProcessor.command("git merge master")
231
+ CommandProcessor.command("git push origin develop --tags")
232
+ end
233
+ CommandProcessor.command("git push origin master --tags")
234
+ if @releasinator_config[:release_to_github]
235
+ Publisher.new(@releasinator_config).publish_draft(GitUtil.repo_url, @currentRelease)
236
+ end
237
+ end
238
+ end
239
+
240
+ namespace :pm do
241
+ desc "publish and wait for package manager"
242
+ task :all => [:publish, :wait]
243
+
244
+ desc "call configured publish_to_package_manager_method"
245
+ task :publish => [:config, :"validate:changelog"] do
246
+ @releasinator_config[:publish_to_package_manager_method].call(@currentRelease.release)
247
+ end
248
+
249
+ desc "call configured wait_for_package_manager_method"
250
+ task :wait => [:config, :"validate:changelog"] do
251
+ @releasinator_config[:wait_for_package_manager_method].call(@currentRelease.release)
252
+ end
253
+ end
254
+
255
+ def copy_the_file(root_dir, copy_file, version=nil)
256
+ Dir.mkdir(copy_file.target_dir) unless File.exists?(copy_file.target_dir)
257
+ # use __VERSION__ to auto-substitute the version in any input param
258
+ source_file_name = copy_file.source_file.gsub("__VERSION__", "#{version}")
259
+ target_dir_name = copy_file.target_dir.gsub("__VERSION__", "#{version}")
260
+ destination_file_name = copy_file.target_name.gsub("__VERSION__", "#{version}")
261
+ CommandProcessor.command("cp -R #{root_dir}/#{source_file_name} #{target_dir_name}/#{destination_file_name}")
262
+ end
263
+
264
+ def get_new_branch_name(new_branch_name, version)
265
+ new_branch_name.gsub("__VERSION__", "#{version}")
266
+ end
267
+
268
+ def reset_repo(branch_name)
269
+ # resets the repo to a clean state
270
+ GitUtil.checkout(branch_name)
271
+ CommandProcessor.command("git fetch origin --prune --recurse-submodules -j9")
272
+ CommandProcessor.command("git reset --hard origin/#{branch_name}")
273
+ CommandProcessor.command("git clean -x -d -f")
274
+ end
275
+
276
+ def get_downstream_repos(downstream_repo_index)
277
+ repos_to_iterate_over = {}
278
+ if downstream_repo_index
279
+ index = Integer(downstream_repo_index) rescue false
280
+ if !index
281
+ Printer.fail("downstream_repo_index:#{downstream_repo_index} not a valid integer")
282
+ abort()
283
+ end
284
+ downstream_repo_max_index = @releasinator_config[:downstream_repos].size - 1
285
+ if index < 0
286
+ Printer.fail("Index out of bounds downstream_repo_index: #{index} < 0")
287
+ abort()
288
+ end
289
+ if index > downstream_repo_max_index
290
+ Printer.fail("Index out of bounds downstream_repo_index: #{index} >= #{downstream_repo_max_index}")
291
+ abort()
292
+ end
293
+ # keep original index for printing
294
+ repos_to_iterate_over[@releasinator_config[:downstream_repos][index]] = index
295
+ else
296
+ @releasinator_config[:downstream_repos].each_with_index do |downstream_repo, index|
297
+ repos_to_iterate_over[downstream_repo] = index
298
+ end
299
+ end
300
+ repos_to_iterate_over
301
+ end
302
+
303
+ namespace :downstream do
304
+ desc "build, package, and push all downstream repos"
305
+ task :all => [:reset,:prepare,:build,:package,:push] do
306
+ Printer.success("Done with all downstream tasks.")
307
+ end
308
+
309
+ desc "reset the downstream repos to their starting state"
310
+ task :reset, [:downstream_repo_index] => [:config, :"validate:changelog"] do |t, args|
311
+ if @releasinator_config.has_key?(:downstream_repos)
312
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
313
+ puts "resetting downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
314
+ Dir.mkdir(DOWNSTREAM_REPOS) unless File.exists?(DOWNSTREAM_REPOS)
315
+ Dir.chdir(DOWNSTREAM_REPOS) do
316
+ CommandProcessor.command("git clone --origin origin #{downstream_repo.url} #{downstream_repo.name}") unless File.exists?(downstream_repo.name)
317
+
318
+ Dir.chdir(downstream_repo.name) do
319
+ reset_repo(downstream_repo.branch)
320
+
321
+ if downstream_repo.options.has_key? :new_branch_name
322
+ new_branch_name = get_new_branch_name(downstream_repo.options[:new_branch_name], @currentRelease.release)
323
+ GitUtil.delete_branch new_branch_name
324
+ end
325
+ end
326
+ end
327
+ end
328
+ Printer.success("Done resetting downstream repos.")
329
+ else
330
+ Printer.success("Not resetting downstream repos. None found.")
331
+ end
332
+ end
333
+
334
+ desc "prepare downstream release, copying files from base_docs_dir and any other configured files"
335
+ task :prepare, [:downstream_repo_index] => [:config, :"validate:changelog", :reset] do |t, args|
336
+ if @releasinator_config.has_key?(:downstream_repos)
337
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
338
+ puts "preparing downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
339
+
340
+ root_dir = Dir.pwd.strip
341
+ copy_from_dir = root_dir + "/" + get_base_dir()
342
+
343
+ Dir.chdir(DOWNSTREAM_REPOS) do
344
+ Dir.chdir(downstream_repo.name) do
345
+
346
+ if downstream_repo.options.has_key? :new_branch_name
347
+ new_branch_name = get_new_branch_name(downstream_repo.options[:new_branch_name], @currentRelease.release)
348
+ CommandProcessor.command("git checkout -b #{new_branch_name}")
349
+ end
350
+
351
+ if full_file_sync(downstream_repo.options)
352
+ # remove old everything
353
+ CommandProcessor.command("rm -rf *")
354
+
355
+ # update all sdk files
356
+ CommandProcessor.command("rsync -av --exclude='#{DOWNSTREAM_REPOS}' --exclude='.git/' #{copy_from_dir}/* .")
357
+ CommandProcessor.command("rsync -av --exclude='#{DOWNSTREAM_REPOS}' --exclude='.git/' #{copy_from_dir}/.[!.]* .")
358
+ end
359
+
360
+ # copy custom files
361
+ if downstream_repo.options.has_key? :files_to_copy
362
+ downstream_repo.options[:files_to_copy].each do |copy_file|
363
+ copy_the_file(root_dir, copy_file, @currentRelease.release)
364
+ end
365
+ end
366
+
367
+ if downstream_repo.options.has_key? :post_copy_methods
368
+ downstream_repo.options[:post_copy_methods].each do |method|
369
+ method.call(@currentRelease.release)
370
+ end
371
+ end
372
+
373
+ if GitUtil.is_clean_git?
374
+ Printer.fail("Nothing changed in #{downstream_repo.name}!")
375
+ abort()
376
+ end
377
+ # add everything to git and commit
378
+ CommandProcessor.command("git add .")
379
+ CommandProcessor.command("git add -u .")
380
+ if downstream_repo.options.has_key? :new_branch_name
381
+ commit_message = "Update #{@releasinator_config[:product_name]} to #{@currentRelease.release}"
382
+ else
383
+ commit_message = "Release #{@currentRelease.release}"
384
+ end
385
+ CommandProcessor.command("git commit -am \"#{commit_message}\"")
386
+ end
387
+ end
388
+ end
389
+ Printer.success("Done preparing downstream repos.")
390
+ else
391
+ Printer.success("Not preparing downstream repos. None found.")
392
+ end
393
+ end
394
+
395
+ desc "call all build_methods for each downstream repo"
396
+ task :build, [:downstream_repo_index] => :config do |t, args|
397
+ if @releasinator_config.has_key?(:downstream_repos)
398
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
399
+ puts "building downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
400
+ Dir.chdir(DOWNSTREAM_REPOS) do
401
+ Dir.chdir(downstream_repo.name) do
402
+ # build any files to verify release
403
+ if downstream_repo.options.has_key? :build_methods
404
+ downstream_repo.options[:build_methods].each do |method|
405
+ method.call
406
+ end
407
+ end
408
+ end
409
+ end
410
+ end
411
+ Printer.success("Done building downstream repos.")
412
+ else
413
+ Printer.success("Not building downstream repos. None found.")
414
+ end
415
+ end
416
+
417
+ desc "tag all non-branch downstream repos"
418
+ task :package, [:downstream_repo_index] => [:config,:"validate:changelog"] do |t, args|
419
+ if @releasinator_config.has_key?(:downstream_repos)
420
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
421
+ puts "packaging downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
422
+ Dir.chdir(DOWNSTREAM_REPOS) do
423
+ Dir.chdir(downstream_repo.name) do
424
+ # don't tag those where new branches are created
425
+ git_tag(@currentRelease.release, @currentRelease.changelog) unless downstream_repo.options.has_key? :new_branch_name
426
+ end
427
+ end
428
+ end
429
+ Printer.success("Done packaging downstream repos.")
430
+ else
431
+ Printer.success("Not packaging downstream repos. None found.")
432
+ end
433
+ end
434
+
435
+ desc "push tags and creates draft release, or pushes branch and creates pull request, depending on the presence of new_branch_name"
436
+ task :push, [:downstream_repo_index] => [:config,:"validate:changelog"] do |t, args|
437
+ if @releasinator_config.has_key?(:downstream_repos)
438
+ get_downstream_repos(args[:downstream_repo_index]).each do |downstream_repo, index|
439
+ puts "pushing downstream_repo[#{index}]: #{downstream_repo.url}" if @releasinator_config[:verbose]
440
+ Dir.chdir(DOWNSTREAM_REPOS) do
441
+ Dir.chdir(downstream_repo.name) do
442
+ if downstream_repo.options.has_key? :new_branch_name
443
+ new_branch_name = get_new_branch_name(downstream_repo.options[:new_branch_name], @currentRelease.release)
444
+ CommandProcessor.command("git push -u origin #{new_branch_name}")
445
+ Publisher.new(@releasinator_config).publish_pull_request(downstream_repo.url, @currentRelease, @releasinator_config[:product_name], downstream_repo.branch, new_branch_name)
446
+ else
447
+ CommandProcessor.command("git push origin master --tags")
448
+ Publisher.new(@releasinator_config).publish_draft(downstream_repo.url, @currentRelease) unless ! release_to_github(downstream_repo.options)
449
+ end
450
+ end
451
+ end
452
+ end
453
+ Printer.success("Done pushing downstream repos.")
454
+ else
455
+ Printer.success("Not pushing downstream repos. None found.")
456
+ end
457
+ end
458
+ end
459
+
460
+ namespace :docs do
461
+ desc "build, copy, and push docs to gh-pages branch"
462
+ task :all => [:build, :package, :push]
463
+
464
+ desc "build docs"
465
+ task :build => [:config] do
466
+ if @releasinator_config.has_key?(:doc_build_method)
467
+ @releasinator_config[:doc_build_method].call
468
+ Printer.success("doc_build_method done.")
469
+ else
470
+ Printer.success("No doc_build_method found.")
471
+ end
472
+ end
473
+
474
+ desc "copy and commit docs to gh-pages branch"
475
+ task :package => [:config,:"validate:changelog"] do
476
+ if @releasinator_config.has_key?(:doc_files_to_copy)
477
+ root_dir = Dir.pwd.strip
478
+
479
+ doc_target_dir = "."
480
+ doc_target_dir = @releasinator_config[:doc_target_dir] if @releasinator_config.has_key?(:doc_target_dir)
481
+
482
+ Dir.chdir(doc_target_dir) do
483
+ current_branch = GitUtil.get_current_branch()
484
+
485
+ GitUtil.init_gh_pages()
486
+ reset_repo("gh-pages")
487
+ @releasinator_config[:doc_files_to_copy].each do |copy_file|
488
+ copy_the_file(root_dir, copy_file)
489
+ end
490
+
491
+ CommandProcessor.command("git add .")
492
+ CommandProcessor.command("git commit -m \"Update docs for release #{@currentRelease.release}\"")
493
+
494
+ # switch back to previous branch
495
+ CommandProcessor.command("git checkout #{current_branch}")
496
+ end
497
+ Printer.success("Doc files copied.")
498
+ else
499
+ Printer.success("No doc_files_to_copy found.")
500
+ end
501
+ end
502
+
503
+ desc "push gh-pages branch"
504
+ task :push => [:config] do
505
+ if @releasinator_config.has_key?(:doc_build_method)
506
+ doc_target_dir = "."
507
+ doc_target_dir = @releasinator_config[:doc_target_dir] if @releasinator_config.has_key?(:doc_target_dir)
508
+
509
+ Dir.chdir(doc_target_dir) do
510
+ current_branch = GitUtil.get_current_branch()
511
+ CommandProcessor.command("git checkout gh-pages")
512
+ CommandProcessor.command("git push origin gh-pages")
513
+ # switch back to previous branch
514
+ CommandProcessor.command("git checkout #{current_branch}")
515
+ end
516
+ Printer.success("Docs pushed.")
517
+ else
518
+ Printer.success("No docs pushed.")
519
+ end
520
+ end
521
+ end
522
+
523
+ def replace_string(filepath, string_to_replace, new_string)
524
+ IO.write(filepath,
525
+ File.open(filepath) do |file|
526
+ file.read.gsub(string_to_replace, new_string)
527
+ end
528
+ )
529
+ end