Skip to content

Instantly share code, notes, and snippets.

@locofocos
Created November 4, 2020 19:37
Show Gist options
  • Save locofocos/7c7374d872145cc80c9ff9da29b6e78f to your computer and use it in GitHub Desktop.
Save locofocos/7c7374d872145cc80c9ff9da29b6e78f to your computer and use it in GitHub Desktop.
Untangles a few git branches into a linear history by rebasing.
# Untangles a few git branches into a linear history by rebasing.
#
# Setup:
#
# In your .gitconfig file, create an alias like so (first line is Windows, 2nd line unix):
#
# [alias]
# untangle = !ruby /c/DEV/git_untangle.rb
# untangle = !ruby ~/git_untangle.rb
#
#
# Usage:
#
# Find the branch names that need to be untangled.
# Sort them into the final intended order (like "A B C", where A is the earliest branch and C is the latest branch)
# Run-
# git untangle A B C
#
# One assumption made by this script: You're the only person changing
# the states of these branches. So if someone else rebases a branch
# which you've branched off of, this script may or may not help you,
# probably depending on whether or not their commits changed anything
# (i.e. whether it's an easy case or hard case, https://git-scm.com/docs/git-rebase#_recovering_from_upstream_rebase)
# You could, however, have a great time if you assign a single person to
# run this script, rebase all the branches in one go, then have everyone else
# reset their branches to the copy from origin.
### A command to run at every step of the rebase for verification (or nil if you don't want to):
#command_line_verify = 'bundle exec rspec'
#command_line_verify = 'mvn compile test-compile checkstyle:check'
#command_line_verify = 'mvn clean install -DskipTests -DskipITs -Djacoco.skip=true -Dcheckstyle.skip=true'
command_line_verify = nil
### True if you want to interactively rebase. Feel free to use this to squash commits.
# interactive = true
interactive = false
# Grab the branch names
branch_names = ARGV
puts 'Branches:'
puts branch_names
puts
# Grab the original commit hash for each branch
original_branch_hashes = branch_names.map { |branch| `git rev-parse #{branch}`.strip! }
exit unless $?.success? # exit if the command failed
puts 'Original commit hashes:'
puts original_branch_hashes
puts
# Rebase each branch based off the original commit hash of the previous branch
index = 1 # the first branch doesn't need to be rebased
while index < branch_names.size do
this_branch = branch_names[index]
prev_branch = branch_names[index-1]
prev_branch_orig_hash = original_branch_hashes[index-1]
command = "git checkout #{this_branch}"
puts command
`#{command}`
command = "git merge-base #{prev_branch_orig_hash} #{this_branch}"
puts command
mergebase = `#{command}`
command = "git rebase "
command += "-i " if interactive
command += "-x \"#{command_line_verify}\"" if command_line_verify
command += " --onto #{prev_branch} #{mergebase}"
puts command
success = system(command)
puts
unless success
puts 'Open another terminal, fix the issue, finish the rebase, then return here and press enter to continue...'
STDIN.gets
end
index+= 1
end
puts 'Done! Make sure the graph structure looks good, then force push each of the branches: (copy/paste if you dare)'
puts
branch_names.each do |branch_name|
unless branch_name == 'master'
puts "git checkout #{branch_name}"
puts "git push --force-with-lease origin #{branch_name}"
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment