Skip to content

Instantly share code, notes, and snippets.

@seawolf
Created April 6, 2021 09:08
Show Gist options
  • Save seawolf/2e703db30ca017e722abb17520bd2e48 to your computer and use it in GitHub Desktop.
Save seawolf/2e703db30ca017e722abb17520bd2e48 to your computer and use it in GitHub Desktop.
Dedupe a list of Git commits, from/to rebase-todo format, fixing-up duplicates into the first.
require 'fileutils'
FILE = '.git/rebase-merge/git-rebase-todo'
FILE_BACKUP = '.git/rebase-merge/git-rebase-todo.backup'
FILE_TEMPORARY = '.git/rebase-merge/git-rebase-todo.edited'
=begin
Dedupe a list of Git commits, from/to rebase-todo format, fixing-up duplicates into the first.
Turns this:
pick ab1232aa first commit
pick cb4180bb second commit
pick 828baccc third commit
pick cb4180bb second commit
pick ab1232aa first commit
pick 828baccc third commit
pick 91baffdd fourth commit
pick ab1232aa first commit
Into this:
pick ab1232aa first commit
fixup ab1232aa first commit
fixup ab1232aa first commit
pick cb4180bb second commit
fixup cb4180bb second commit
pick 828baccc third commit
fixup 828baccc third commit
pick 91baffdd fourth commit
=end
module GitDeDupe
class CommitListFile
def initialize(path)
@list = File.read(file).split("\n")
end
def to_commit_list
return CommitList.new(list)
end
private
attr_reader :list
end
class CommitList
def initialize(list)
@list = .map {|str| Commit.new(str) }
end
def to_rebase_commands
str = ""
grouped_commits.inject(nil) do |last_commit, commit|
str << "#{commit.operation(last_commit)} #{commit.sha} #{commit.message}\n"
commit
end
str
end
private
def grouped_commits
@list
.group_by(&:message)
.values
.flatten
end
end
class Commit
attr_reader :sha, :message
def initialize(str)
# "pick ab1232aa first commit"
@operation = str[0..3]
@sha = str[5..12]
@message = str[14..-1]
end
def operation(last_commit = nil)
return 'fixup' if fixup_for?(last_commit)
@operation
end
def fixup_for?(last_commit)
return false if last_commit.nil?
last_commit.message == message
end
end
end
# clean-up previous runs
FileUtils.rm(FILE_TEMPORARY) if File.exist?(FILE_TEMPORARY)
# don't destroy any backup from previous runs, use them!
if File.exist?(FILE_BACKUP)
FileUtils.cp(FILE_BACKUP, FILE)
else
FileUtils.cp(FILE, FILE_BACKUP)
end
# process input
file = GitDeDupe::CommitListFile.new(FILE)
commits = file.to_commit_list
list = commits.to_rebase_commands
# write output
File.open(FILE_TEMPORARY, 'w') do |f|
f.puts list
end
# clean-up
FileUtils.mv(FILE_TEMPORARY, FILE)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment