Skip to content

Instantly share code, notes, and snippets.

@KristofferC
Last active August 20, 2018 12:15
Show Gist options
  • Save KristofferC/90b480dbcb4a4e7ff6c55c63ab799722 to your computer and use it in GitHub Desktop.
Save KristofferC/90b480dbcb4a4e7ff6c55c63ab799722 to your computer and use it in GitHub Desktop.
import GitHub
import Dates
import JSON
import HTTP
############
# Settings #
############
BACKPORT_LABEL = "backport pending 1.0"
REPO = "JuliaLang/julia";
# where the release branch started
START_COMMIT = "1dd2f8b397e60c10fdacd85fc62"
GITHUB_AUTH = ENV["GITHUB_AUTH"]
# stop looking after encounting PRs opened before this date
LIMIT_DATE = Dates.Date("2018-08-01")
# refresh PRs from github
REFRESH_PRS = false
########################################
# Git executable convenience functions #
########################################
function cherry_picked_commits(start_commit::AbstractString)
commits = Set{String}()
logg = read(`git log $start_commit..HEAD`, String)
for match in eachmatch(r"\(cherry picked from commit (.*?)\)", logg)
push!(commits, match.captures[1])
end
return commits
end
get_parents(hash::AbstractString) =
return split(chomp(read(`git rev-list --parents -n 1 $hash`, String)))[2:end]
function get_real_hash(hash::AbstractString)
# check if it is a merge commit
parents = get_parents(hash)
if length(parents) == 2 # it is a merge commit, use the parent as the commit
hash = parents[2]
end
return hash
end
function try_cherry_pick(hash::AbstractString)
if !success(`git cherry-pick -x $hash`)
read(`git cherry-pick --abort`)
return false
end
return true
end
branch() = chomp(String(read(`git rev-parse --abbrev-ref HEAD`)))
if !@isdefined(sha_to_pr)
const sha_to_pr = Dict{String, Int}()
end
function find_pr_associated_with_commit(hash::AbstractString)
if haskey(sha_to_pr, hash)
return sha_to_pr[hash]
end
headers = Dict()
GitHub.authenticate_headers!(headers, getauth())
headers["User-Agent"] = "GitHub-jl"
req = HTTP.request("GET", "https://api.github.com/search/issues?q=$hash+type:pr+repo:$REPO";
headers = headers)
json = JSON.parse(String(req.body))
if json["total_count"] !== 1
return nothing
end
item = json["items"][1]
if !haskey(item, "pull_request")
return nothing
end
pr = parse(Int, basename(item["pull_request"]["url"]))
sha_to_pr[hash] = pr
return pr
end
function was_squashed_pr(pr)
parents = get_parents(pr.merge_commit_sha)
@assert length(parents) == 1
return pr.number != find_pr_associated_with_commit(parents[1])
end
##################
# Main functions #
##################
if !@isdefined(__myauth)
const __myauth = Ref{GitHub.Authorization}()
end
function getauth()
if !isassigned(__myauth)
__myauth[] = GitHub.authenticate(GITHUB_AUTH)
end
return __myauth[]
end
function collect_label_prs(backport_label::AbstractString)
# What are good parameters...?
myparams = Dict("state" => "all", "per_page" => 20, "page" => 1);
label_prs = []
i = 1
print("Collecting PRs...")
first = true
local page_data
while true
print(".")
prs, page_data = GitHub.pull_requests(REPO;
page_limit = 1, auth=getauth(),
(first ? (params = myparams,) : (start_page = page_data["next"],))...)
first = false
for pr in prs
for label in pr.labels
if label["name"] == backport_label
push!(label_prs, pr)
end
end
if pr.created_at < LIMIT_DATE
return label_prs
end
end
haskey(page_data, "next") || break
end
println()
return label_prs
end
if !@isdefined(label_prs)
const label_prs = Ref{Vector}()
end
function do_backporting(refresh_prs = false)
if !isassigned(label_prs) || refresh_prs
empty!(sha_to_pr)
label_prs[] = collect_label_prs(BACKPORT_LABEL)
end
already_backported_commits = cherry_picked_commits(START_COMMIT)
release_branch = branch()
open_prs = []
multi_commits = []
closed_prs = []
already_backported = []
backport_candidates = []
for pr in label_prs[]
if pr.state != "closed"
push!(open_prs, pr)
else
if pr.merged_at === nothing
push!(closed_prs, pr)
elseif get_real_hash(pr.merge_commit_sha) in already_backported_commits
push!(already_backported, pr)
else
push!(backport_candidates, pr)
end
end
end
sort!(closed_prs; by = x -> x.number)
sort!(already_backported; by = x -> x.merged_at)
sort!(backport_candidates; by = x -> x.merged_at)
failed_backports = []
successful_backports = []
multi_commit_prs = []
for pr in backport_candidates
if pr.commits === nothing
# When does this happen...
i = findfirst(x -> x.number == pr.number, label_prs[])
pr = GitHub.pull_request(REPO, pr.number; auth=getauth())
@assert pr.commits !== nothing
label_prs[][i] = pr
end
if pr.commits != 1
# Check if this was squashed, in that case we can still backport
if was_squashed_pr(pr) && try_cherry_pick(get_real_hash(pr.merge_commit_sha))
push!(successful_backports, pr)
else
push!(multi_commit_prs, pr)
end
elseif try_cherry_pick(get_real_hash(pr.merge_commit_sha))
push!(successful_backports, pr)
else
push!(failed_backports, pr)
end
end
# Actions to take:
remove_label_prs = [closed_prs; already_backported]
if !isempty(remove_label_prs)
sort!(remove_label_prs; by = x -> x.merged_at)
println("The following PRs are closed or already backported but still has a backport label, remove the label:")
for pr in remove_label_prs
println(" #$(pr.number) - $(pr.html_url)")
end
println()
end
if !isempty(open_prs) println("The following PRs are open but have a backport label, merge first?")
for pr in open_prs
println(" #$(pr.number) - $(pr.html_url)")
end
println()
end
if !isempty(failed_backports)
println("The following PRs failed to backport cleanly, manually backport:")
for pr in failed_backports
println(" #$(pr.number) - $(pr.html_url) - $(pr.merge_commit_sha)")
end
println()
end
if !isempty(multi_commit_prs)
println("The following PRs had multiple commits, manually backport")
for pr in multi_commit_prs
println(" #$(pr.number) - $(pr.html_url)")
end
println()
end
if !isempty(successful_backports)
println("The following PRs where backported to this branch:")
for pr in successful_backports
println(" #$(pr.number) - $(pr.html_url)")
end
printstyled("Push the updated branch"; bold=true)
println()
end
println("Update the first post with:")
function summarize_pr(pr; checked=true)
println("- [$(checked ? "x" : " ")] #$(pr.number) - $(pr.title)")
end
backported_prs = [successful_backports; already_backported]
if !isempty(backported_prs)
sort!(backported_prs; by = x -> x.merged_at)
println("Backported PRs:")
for pr in backported_prs
summarize_pr(pr)
end
end
if !isempty(failed_backports)
println()
println("Need manual backport:")
for pr in failed_backports
summarize_pr(pr; checked=false)
end
end
if !isempty(multi_commit_prs)
println("Contains multiple commits, manual intervention needed:")
for pr in multi_commit_prs
summarize_pr(pr; checked=false)
end
end
if !isempty(open_prs)
println()
println("Non-merged PRs with backport label:")
for pr in open_prs
summarize_pr(pr; checked=false)
end
end
end
do_backporting(REFRESH_PRS)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment