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
require "tmpdir" | |
require "securerandom" | |
require "set" | |
REPO = ARGV[0] || raise("need repo") | |
EMAILS = ARGV[1...] | |
EMAIL_MAP = { | |
"old-email@example.com" => "new-email@example.com" | |
} | |
MAPPED_COMMITS = {} | |
NEW_COMMITS = Set.new | |
TMP_DIR = Dir.mktmpdir("convert") | |
def build_commit_message(subject, original_commit, body) | |
if subject =~ /^(.+) \(#(\d+)\)$/ | |
sanitized_subject = $1 | |
original_pr = $2 | |
elsif subject =~ /^Merge pull request #(\d+) (.+)$/ | |
original_pr = $1 | |
sanitized_subject = "Merge pull request #{$2}" | |
else | |
original_pr = nil | |
sanitized_subject = subject | |
end | |
sanitized_subject = sanitized_subject.gsub(/#(\d+)/) { "PR #{$1}" } | |
co_authors = body.split("\n").select { |line| line.start_with?("Co-authored-by") } | |
result = [] | |
result << "#{sanitized_subject}\n" | |
result << "\n" | |
result << "Original PR: #{original_pr}\n" if original_pr | |
result << "Original Commit: #{original_commit}\n" | |
if co_authors.any? | |
result << "\n" | |
co_authors.each { |co| result << co } | |
end | |
"#{TMP_DIR}/#{SecureRandom.uuid}".tap do |file| | |
File.open(file, "w") { |f| f.write result.join } | |
end | |
end | |
# %H = commit hash | |
# %h = abbreviated commit hash | |
# %T = tree hash | |
# %P = parent hashes | |
# %an = author name | |
# %ae = author email | |
# %aD = author date, rfc2822 style | |
# %cn = committer name | |
# %ce = committer email | |
# %cD = committer date, rfc2822 style | |
# %s = subject | |
# %b = body | |
def import_commit(original_commit_hash) | |
# 0 1 2 3 4 5 6 7 8 9 10 11 12 | |
format = %w[%H %h %T %P %an %ae %aD %cn %ce %cD %s %H %b] | |
results = `git show --no-patch --pretty=#{format.join("%n")} #{original_commit_hash}`.split("\n") | |
raise "failed to show #{original_commit_hash.inspect}" unless $?.success? | |
unless results[11] == original_commit_hash | |
raise "format error: #{results}" | |
end | |
mapped_parents = results[3].split(" ").map do |commit| | |
MAPPED_COMMITS.fetch(commit) # will raise if is not mapped | |
end | |
commit_message_file = build_commit_message(results[10], results[0], results[12...].join("\n")) | |
email = EMAIL_MAP[results[5]] || results[5] | |
command = [ | |
"env", | |
"GIT_AUTHOR_NAME='#{results[4]}'", | |
"GIT_AUTHOR_EMAIL='#{email}'", | |
"GIT_AUTHOR_DATE='#{results[6]}'", | |
"GIT_COMMITTER_NAME='#{results[4]}'", | |
"GIT_COMMITTER_EMAIL='#{email}'", | |
"GIT_COMMITTER_DATE='#{results[9]}'", | |
"git commit-tree", | |
mapped_parents.map { |parent| "-p #{parent}" }, | |
(results[5].include?("[bot]") ? "" : "-S"), | |
"-F #{commit_message_file}", | |
results[2] | |
].flatten.join(" ") | |
STDERR.puts "Rewriting #{original_commit_hash}" | |
puts command | |
new_commit = `#{command}`.strip | |
puts new_commit | |
raise "commit failed" unless $?.success? | |
MAPPED_COMMITS[original_commit_hash] = new_commit | |
NEW_COMMITS << new_commit | |
new_commit | |
end | |
def import_new_commits | |
new_commits = `git log --reverse --pretty='%H' origin/main-oss`.split("\n") | |
raise "git log command failed" unless $?.success? | |
new_commits.each do |new_commit| | |
next if NEW_COMMITS.include?(new_commit) | |
original_commit = `git show --no-patch --pretty=%b #{new_commit} | grep 'Original Commit'`.split(" ")[2].strip | |
raise "failed to get original commit" if !$?.success? || original_commit.empty? | |
next if MAPPED_COMMITS.include?(original_commit) | |
puts "#{original_commit} rewritten to #{new_commit}" | |
MAPPED_COMMITS[original_commit] = new_commit | |
NEW_COMMITS << new_commit | |
end | |
end | |
Dir.chdir("../#{REPO}") do | |
`git fetch` | |
raise "fetch failed" unless $?.success? | |
commits = `git log --reverse --pretty='%H' origin/main`.split("\n") | |
raise "git log command failed" unless $?.success? | |
`git branch -r | grep main-oss` | |
first_commit = !$?.success? | |
unless first_commit | |
import_new_commits | |
end | |
commits.each do |commit| | |
if MAPPED_COMMITS.include?(commit) | |
first_commit = false | |
next | |
end | |
author = `git show --no-patch --pretty=%ae #{commit}`.strip | |
if EMAILS.include?(author) | |
new_commit = import_commit(commit) | |
if first_commit | |
`git push origin main:main-oss` | |
raise "push failed" unless $?.success? | |
`git push -f origin #{new_commit}:main-oss` | |
raise "pushed failed" unless $?.success? | |
else | |
`git push origin #{new_commit}:main-oss` | |
raise "pushed failed" unless $?.success? | |
end | |
else | |
puts "Waiting for #{author} to rewrite commit #{commit}..." | |
loop do | |
sleep 3 | |
puts "Fetching..." | |
`git fetch 2>&1 | grep main-oss` | |
break if $?.success? | |
end | |
import_new_commits | |
raise "expected #{commit} to be imported" unless MAPPED_COMMITS.include?(commit) | |
end | |
first_commit = false | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment