Skip to content

Instantly share code, notes, and snippets.

@TylerRick
Created September 14, 2010 23:35
Show Gist options
  • Save TylerRick/579963 to your computer and use it in GitHub Desktop.
Save TylerRick/579963 to your computer and use it in GitHub Desktop.
An early version of git-merge-better, part of http://github.com/TylerRick/tyler-git
#!/usr/bin/env ruby
#---------------------------------------------------------------------------------------------------
# TODO:
# doesn't handle if multiple conflict markers!!
#---------------------------------------------------------------------------------------------------
require 'pathname'
require 'facets/file/rewrite'
require 'quality_extensions/pathname'
#---------------------------------------------------------------------------------------------------
# Parse args
require 'optparse'
@options = {
:use => 'conflicted'
}
def parse_args
ARGV.options do |opts|
opts.banner = <<End
Usage: #{File.basename($0)} [options] file_with_conflict
An alternative to git mergetool.
End
opts.on("--use which", "'conflicted' (default) to use conflicted file and strip out conflict versions") {|v| @options[:use] = v }
opts.on("-n", "--no-act", "Don't create/copy/modify files or open the editor") { @options[:no_act] = true }
opts.on("-v", "--verbose", "Be more verbose") { @options[:verbose] = true }
opts.on("-h", "--help", "Show this help message.") { puts opts; exit }
opts.parse!
end
end
parse_args
if ARGV.size == 0
ARGV.unshift '--help'
parse_args
end
#---------------------------------------------------------------------------------------------------
@file = Pathname.new(ARGV[0])
ext = @file.extname
#---------------------------------------------------------------------------------------------------
if (file = Pathname.new('.git/rebase-apply/onto')).exist?
upstream = file.read.chomp
upstream = `git name-rev --name-only --always --no-undefined #{upstream}`.chomp
merge_type = :rebase
end
if (file = Pathname.new('.git/rebase-apply/orig-head')).exist?
mine = file.read.chomp
mine = `git name-rev --name-only --always --no-undefined #{mine}`.chomp
end
merge_type ||= :merge
upstream ||= 'MERGE_HEAD'
mine ||= 'HEAD'
#merge_base = `git merge-base MERGE_HEAD HEAD`.chomp
merge_base = `git merge-base #{upstream} #{mine}`.chomp
merge_base = `git name-rev --name-only --always --no-undefined #{merge_base}`.chomp
#merge_head = `git rev-parse MERGE_HEAD`.chomp
puts "upstream=#{upstream.inspect}"
puts "mine=#{mine.inspect}"
puts "merge_base=#{merge_base.inspect}"
#---------------------------------------------------------------------------------------------------
# TODO: how do we know?
# differs if doing rebase master vs merge master
# look at strings after <<<<<<<?
# perhaps MERGE_HEAD is missing/different for rebase?
mine_section = :top
def remove_section(s, section)
puts "removing #{section.inspect}"
if section == :top
s[/<<<<<<<.*=======/m] = ''
s[/>>>>>>>.*$/] = ''
else
s[/<<<<<<<.*$/] = ''
s[/=======.*>>>>>>>[^\n]*$/m] = ''
end
s
end
#---------------------------------------------------------------------------------------------------
files = {'actual' => @file}
['mine_conflicted', 'upstream_conflicted'].each do |suffix|
unless @options[:no_act]
#if @options[:use] == 'conflicted'
new_file = @file.add_suffix(".#{suffix}#{ext}")
files[suffix] = new_file
@file.cp new_file
File.rewrite new_file do |s|
if suffix =~ /upstream/
section_to_remove = mine_section == :top ? :top : :bottom
else
section_to_remove = mine_section == :top ? :bottom : :top
end
s = remove_section(s.dup, section_to_remove)
s
end
#end
end
end
['mine_actual', 'upstream_actual'].each do |suffix|
unless @options[:no_act]
#if @options[:use] == 'git show'
new_file = @file.add_suffix(".#{suffix}#{ext}")
files[suffix] = new_file
if suffix =~ /upstream/
command = "git show #{upstream}:'#{@file}' >> '#{new_file}'"
else
command = "git show #{mine}:'#{@file}' >> '#{new_file}'"
end
files[suffix].open('w') {|f| f.puts command }
system command
#end
end
end
# TODO: can we do git checkout --theirs?
# or git cat-save commit:file ?
# > git-cat-save -c MERGE_HEAD:app/views/events/show.html.erb
# git show MERGE_HEAD:'app/views/events/show.html.erb' > 'app/views/events/show.html.erb.MERGE_HEAD.erb'
if merge_type == :rebase
upstream_from = "#{upstream}~15"
mine_from = "#{upstream}"
else
upstream_from = "#{merge_base}^"
mine_from = "#{merge_base}^"
end
[['upstream', upstream_from, upstream],
['mine', mine_from, mine]
].each do |suffix, from, up_to|
#--reverse
puts command = "git log -p #{from}..#{up_to} -- #{@file}"
#system command
files["#{suffix}.diff"] = @file.add_suffix(".#{suffix}.diff")
files["#{suffix}.diff"].open('w') {|f| f.puts command }
system "#{command} >> #{files["#{suffix}.diff"]}"
end
#gitk MERGE_HEAD
#puts command="vimdiff #{files['upstream']} #{files['mine']}"
puts command="vim -c 'e #{files['mine_conflicted']} | vert diffsplit #{files['upstream_conflicted']} | " +
# in their own window:
"tabedit #{files['mine_actual']} | vert diffsplit #{files['upstream_actual']} | wincmd w | " +
# diffs above main files (total of 4 buffers in window):
#"split #{files['upstream.diff']} | wincmd w | wincmd w | split #{files['mine.diff']} | wincmd w | " +
# diffs in their own window:
"tabedit #{files['mine.diff']} | vert split #{files['upstream.diff']} | wincmd w | " +
"tabedit #{@file} | vert diffsplit #{files['mine']} | wincmd w | " +
"tabnext 1 '"
exec command unless @options[:no_act]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment