Skip to content

Instantly share code, notes, and snippets.

@dan-manges
Created April 25, 2023 13:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dan-manges/8b0297e4e2fa1609506dc09ca2ef9d84 to your computer and use it in GitHub Desktop.
Save dan-manges/8b0297e4e2fa1609506dc09ca2ef9d84 to your computer and use it in GitHub Desktop.
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