forked from github/backup-utils
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Script to automate backup-util releases using the existing conventions and format. The intention is to release with a single command: ``` ./script/release 2.9.1 ```
- Loading branch information
Showing
1 changed file
with
301 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,301 @@ | ||
#!/usr/bin/env ruby | ||
#/ Usage: release [--dry-run] <version> | ||
#/ | ||
#/ Publish a backup-utils release: | ||
#/ * Updates the package changelog | ||
#/ * Bumps the backup-utils version if required | ||
#/ * Creates the release pull request | ||
#/ * Creates the release draft | ||
#/ * Tags the release | ||
#/ * Builds the release assets and uploads them | ||
#/ | ||
#/ Notes: | ||
#/ * Needs GH_RELEASE_TOKEN available in the environment. | ||
#/ * Export GH_OWNER, GH_AUTHOR and GH_REPO if you want to tweak the build | ||
#/ changelog or use a different owner/repo | ||
#/ * Only pull requests labeled with bug or feature will show up in the | ||
#/ release page and the changelog. | ||
#/ | ||
# TODO: Auto-merge the release PR | ||
# TODO: Tag the release | ||
# TODO: Build and upload the binaries to the release draft | ||
require 'json' | ||
require 'net/http' | ||
require 'time' | ||
require 'erb' | ||
|
||
API_HOST = ENV["GH_HOST"] || "api.github.com" | ||
API_PORT = 443 | ||
GH_REPO = ENV["GH_REPO"] || "backup-utils" | ||
GH_OWNER = ENV["GH_OWNER"] || "github" | ||
GH_AUTHOR = ENV["GH_AUTHOR"] || "Sergio Rubio <[email protected]>" | ||
DEB_PKG_NAME = "github-backup-utils" | ||
|
||
CHANGELOG_TMPL = """ | ||
<%G= package_name %> (<%= package_version %>) UNRELEASED; urgency=medium | ||
<%- changes.each do |ch| -%> | ||
* <%= ch.strip.chomp %> | ||
<% end -%> | ||
-- <%= author %> <%= Time.now.utc.strftime('%a, %d %b %Y %H:%M:%S %z') %> | ||
""" | ||
|
||
# Override Kernel.warn | ||
def warn(msg) | ||
unless $no_warn | ||
Kernel.warn msg | ||
end | ||
end | ||
|
||
def client | ||
@http ||= begin | ||
c = Net::HTTP.new(API_HOST, API_PORT) | ||
c.use_ssl = true | ||
c | ||
end | ||
end | ||
|
||
def release_token | ||
token = ENV["GH_RELEASE_TOKEN"] | ||
raise "GH_RELEASE_TOKEN not set" if token.nil? | ||
|
||
token | ||
end | ||
|
||
def get(path) | ||
req = Net::HTTP::Get.new(path) | ||
req['Authorization'] = "token #{release_token}" | ||
client.request(req) | ||
end | ||
|
||
def post(path, body) | ||
req = Net::HTTP::Post.new(path) | ||
req['Authorization'] = "token #{release_token}" | ||
req.body = body | ||
client.request(req) | ||
end | ||
|
||
# | ||
# tag "v0.0.1", "foo tag", | ||
# "e1d0d72078f7fbef653e705bfb0fe018bd9c772e", | ||
# "foolano", "test", | ||
# "Foolano Garcia", "[email protected]" | ||
def tag(name, message, sha, owner, repo, tagger_name, tagger_email) | ||
r = { | ||
"tag": name, | ||
"message": "#{message}\n", | ||
"object": sha, | ||
"type": "commit", | ||
"tagger": { | ||
"name": tagger_name, | ||
"email": tagger_email, | ||
"date": Time.now.utc.iso8601.to_s | ||
} | ||
}.to_json | ||
res = post("/repos/#{owner}/#{repo}/git/tags", r) | ||
|
||
raise "Creating tag object failed (#{res.code})" unless res.kind_of? Net::HTTPSuccess | ||
|
||
body = { | ||
"ref": "refs/heads/#{name}", | ||
"sha": sha | ||
}.to_json | ||
res = post("/repos/#{owner}/#{repo}/git/refs", body) | ||
|
||
raise "Creating tag ref failed (#{res.code})" unless res.kind_of? Net::HTTPSuccess | ||
end | ||
|
||
def bug_or_feature?(issue_hash) | ||
return true if issue_hash["labels"].find { |l| ["bug", "feature"].include?(l["name"]) } | ||
false | ||
end | ||
|
||
def issue_from(owner, repo, issue) | ||
res = get("/repos/#{owner}/#{repo}/issues/#{issue}") | ||
raise "Issue ##{issue} not found in #{owner}/#{repo}" unless res.kind_of? Net::HTTPSuccess | ||
|
||
JSON.parse(res.body) | ||
end | ||
|
||
def beautify_changes(changes, owner, repo) | ||
c = [] | ||
changes.each do |ch| | ||
if ch =~ /#(\d+)/ | ||
begin | ||
j = issue_from(owner, repo, $1) | ||
if bug_or_feature?(j) | ||
c << "#{j['title']} ##{$1}" | ||
end | ||
rescue => e | ||
warn "Warning: #{e.message}" | ||
end | ||
end | ||
end | ||
|
||
c | ||
end | ||
|
||
def changelog() | ||
changes = `git log --pretty=oneline origin/stable...origin/master | grep "Merge pull request"`.lines | ||
raise "Building the changelog failed" if $? != 0 | ||
|
||
changes | ||
end | ||
|
||
def build_changelog(changes, package_name, package_version, author, owner, repo) | ||
ERB.new(CHANGELOG_TMPL, nil, "-").result(binding) | ||
end | ||
|
||
def update_changelog(changes, name, version, author, owner, repo, path = "debian/changelog") | ||
raise "debian/changelog not found" unless File.exist?(path) | ||
File.open("#{path}.new", 'w') do |f| | ||
f.puts build_changelog changes, name, version, author, owner, repo | ||
f.puts(File.read(path)) | ||
end | ||
File.rename("#{path}.new", path) | ||
end | ||
|
||
def create_release(tag_name, branch, owner, repo, rel_name, rel_body, draft = true) | ||
body = { | ||
"tag_name": tag_name, | ||
"target_commitish": branch, | ||
"name": rel_name, | ||
"body": rel_body, | ||
"draft": draft, | ||
"prerelease": false | ||
}.to_json | ||
res = post("/repos/#{owner}/#{repo}/releases", body) | ||
end | ||
|
||
def list_releases(owner, repo) | ||
res = get("/repos/#{owner}/#{repo}/releases") | ||
raise "Error retrieving releases" unless res.kind_of? Net::HTTPSuccess | ||
|
||
JSON.parse(res.body) | ||
end | ||
|
||
def release_available?(owner, repo, tag_name) | ||
releases = list_releases owner, repo | ||
return true if releases.find { |r| r["tag_name"] == tag_name } | ||
|
||
false | ||
end | ||
|
||
def bump_version(new_version, path = "share/github-backup-utils/version") | ||
current_version = Gem::Version.new(File.read(path).strip.chomp) | ||
if Gem::Version.new(new_version) < current_version | ||
raise "New version should be newer than #{current_version}" | ||
end | ||
File.open("#{path}.new", 'w') do |f| | ||
f.puts new_version | ||
end | ||
File.rename("#{path}.new", path) | ||
end | ||
|
||
def push_release_branch(version, changes) | ||
if !system("git checkout --quiet -b release-#{version}") | ||
raise "Creating release branch failed" | ||
end | ||
|
||
if !system("git commit --quiet -m 'Bump version: #{version}\n#{changes}' debian/changelog share/github-backup-utils/version") | ||
raise "Error commiting changelog and version" | ||
end | ||
|
||
if !system("git push --quiet origin release-#{version}") | ||
raise "Failed pushing the release branch" | ||
end | ||
end | ||
|
||
def create_release_pr(version, owner, repo, release_body) | ||
body = { | ||
"title": "Bump version: #{version}", | ||
"body": release_body, | ||
"head": "release-#{version}", | ||
"base": "master" | ||
}.to_json | ||
res = post("/repos/#{owner}/#{repo}/pulls", body) | ||
raise "Creating release PR failed (#{res.code})" unless res.kind_of? Net::HTTPSuccess | ||
end | ||
|
||
def can_auth? | ||
!ENV["GH_RELEASE_TOKEN"].nil? | ||
end | ||
|
||
args = ARGV.dup | ||
dry_run = false | ||
if args.include?("--dry-run") | ||
dry_run = true | ||
args.delete '--dry-run' | ||
end | ||
|
||
if args.include?("--no-warn") | ||
$no_warn = true | ||
args.delete '--no-warn' | ||
end | ||
|
||
if args.size < 1 | ||
$stderr.puts "Usage: release [--dry-run] <version>" | ||
exit 1 | ||
end | ||
|
||
begin | ||
version = Gem::Version.new(args[0]) | ||
rescue ArgumentError | ||
$stderr.puts "Error parsing version #{args[0]}" | ||
exit 1 | ||
end | ||
|
||
release_changes = [] | ||
release_a = false | ||
if dry_run | ||
if can_auth? | ||
release_changes = beautify_changes(changelog, GH_OWNER, GH_REPO) | ||
release_a = release_available?(GH_OWNER, GH_REPO, "v#{version}") | ||
puts "Existing release: #{release_a}" | ||
end | ||
puts "New version: #{version}" | ||
puts "Owner: #{GH_OWNER}" | ||
puts "Repo: #{GH_REPO}" | ||
puts "Author: #{GH_AUTHOR}" | ||
puts "Token: #{ENV['GH_RELEASE_TOKEN'] && "set" || "unset"}" | ||
if can_auth? | ||
puts "Changelog:" | ||
release_changes.each { |c| puts " * #{c}"} | ||
end | ||
exit | ||
end | ||
|
||
if release_a | ||
$stderr.puts "Release #{version} already exists." | ||
exit 1 | ||
end | ||
|
||
`git fetch --quiet origin --prune` | ||
branches = `git branch --all | grep release-#{version}$` | ||
if !branches.empty? | ||
$stderr.puts "Release branch release-#{version} already exists." | ||
$stderr.puts "Branches found:" | ||
branches.each_line { |l| puts "* #{l.strip.chomp}" } | ||
exit 1 | ||
end | ||
|
||
puts "Bumping version to #{version}" | ||
bump_version(version) | ||
|
||
puts "Updating changelog" | ||
update_changelog release_changes, DEB_PKG_NAME, version, GH_AUTHOR, GH_OWNER, GH_REPO | ||
|
||
puts "Creating release" | ||
release_title = "GitHub Enterprise Backup Utilities v#{version}" | ||
release_body = "Includes general improvements, bug fixes and support for GitHub Enterprise v#{version}" | ||
release_changes.each do |c| | ||
release_body += "\n* #{c}" | ||
end | ||
create_release "v#{version}", "master", GH_OWNER, GH_REPO, release_title, release_body | ||
|
||
puts "Creating and publishing the release branch" | ||
push_release_branch(version, release_changes) | ||
create_release_pr(version, GH_OWNER, GH_REPO, release_body) | ||
puts "Released!" |