Skip to content

Instantly share code, notes, and snippets.

@bdurand
Last active October 5, 2018 17:52
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 bdurand/60f519a3764403edf147e20ede5f46f2 to your computer and use it in GitHub Desktop.
Save bdurand/60f519a3764403edf147e20ede5f46f2 to your computer and use it in GitHub Desktop.
Ruby script to manage merging and pushing branches.
#!/usr/bin/env ruby
# This script will handle refreshing your local git repository and merging in
# the latest changes from a remote branch on origin and then syncing the local
# branch back to origin (if applicable).
#
# This can be useful for such things as keeping your Pull Request branches in
# sync with the latest changes to master.
#
# Usage: `merge_branch [options] source [destination]
# * options: -v to print out all the git commands being run
# * source branch: Name of the branch to merge from
# * destination: Name of the branch to merge to; the value "." will be interpreted as the current branch
#
# You can add this into your global .gitconfig as aliases:
#
# [alias]
# merge-master = "!merge_branch master"
# merge-branch = "!merge_branch"
require 'shellwords'
require 'optparse'
require 'open3'
begin
require 'dotenv'
Dotenv.load
rescue LoadError
# Ignore
end
def print(text, output = nil)
text = text.to_s
return if text.empty?
color = case output
when :err
31
when :success
32
when :debug
34
else
0
end
text = "\e[#{color}m#{text}\e[0m"
if output == :err
STDERR.puts(text)
else
STDOUT.puts(text)
end
end
def abort_on_error!(message)
unless @git_success
print(message, :err)
exit(1)
end
end
def git(*args)
command = "/usr/bin/env git #{args.shelljoin}"
print(command, :debug) if @verbose
stdout, stderr, status = Open3.capture3(command)
stdout.chomp!
stderr.chomp!
@git_success = (status == 0)
if @verbose || status != 0
err_output = (status == 0 ? :debug : :err)
print(stderr, err_output)
print(stdout, :debug) if @verbose
end
stdout
end
def abort_if_local_changes!
changes = git(:status, "-s")
abort_on_error!("Failed to detect if there are local changes")
unless changes.empty?
print("You must commit or stash your local changes before running this script:\n#{changes}", :err)
exit(1)
end
end
def current_branch
git "rev-parse", "--abbrev-ref", "HEAD"
end
def branch(arg)
if arg == "."
current_branch
else
arg.to_s
end
end
def ask_branch(src_branch, default = nil)
prompt = "Merge #{src_branch} into which branch?"
default = nil if default == src_branch
prompt << " (#{default})" if default
STDOUT.write "#{prompt}: "
answer = STDIN.readline.strip
answer = default if answer.empty?
answer
end
def remote_exists?(branch)
!git("ls-remote", "--heads", "origin", branch).strip.empty?
end
def merge_branch(src_branch, dest_branch, push_to_origin)
git :fetch, "origin"
abort_on_error!("Failed to fetch from origin")
git :checkout, src_branch
abort_on_error!("Failed to checkout #{src_branch} branch")
if remote_exists?(src_branch)
git :pull, "origin"
abort_on_error!("Failed to pull #{src_branch} branch updates from origin")
end
git :checkout, dest_branch
abort_on_error!("Failed to checkout branch #{dest_branch}")
if push_to_origin
git :pull, "origin", dest_branch
abort_on_error!("Failed to pull #{dest_branch} updates from origin")
end
git :merge, src_branch
abort_on_error!("Failed to merge #{src_branch} into #{dest_branch}; please resolve conflicts manually")
if push_to_origin
git :push, "origin", dest_branch
abort_on_error!("Failed to push #{dest_branch} to origin")
end
end
@verbose = false
OptionParser.new do |opts|
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
@verbose = v
end
end.parse!
abort_if_local_changes!
current = current_branch
src_branch = branch(ARGV[0])
if src_branch.empty?
print("Source branch not specified", :err)
exit(1)
end
dest_branch = branch(ARGV[1])
if dest_branch.empty?
dest_branch = ask_branch(src_branch, current_branch)
end
if dest_branch.empty?
print("Destination branch not specified", :err)
exit(1)
elsif dest_branch == src_branch
print("You cannot merge a branch into itself", :err)
exit(1)
end
push_to_origin = remote_exists?(dest_branch)
merge_branch(src_branch, dest_branch, push_to_origin)
sha = git("rev-parse", "--short", "HEAD")
git(:checkout, current)
print("Successfully merged #{src_branch} into #{dest_branch}#{' and pushed to origin' if push_to_origin}", :success)
print("Your local branch is #{current_branch}", :success)
print("Merged revision SHA for #{dest_branch} is \e[34m#{sha}", :success)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment