Skip to content

Commit 3bcab31

Browse files
committed
Add main script
1 parent 205c25e commit 3bcab31

File tree

1 file changed

+251
-0
lines changed

1 file changed

+251
-0
lines changed

git-resolve-conflict-using-kdiff3.pl

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
#!/usr/bin/env perl
2+
use warnings;
3+
use strict;
4+
# use diagnostics; Depends on perl_pods package on cygwin (not installed by default, https://stackoverflow.com/a/36538115/23118). Not available in git-for-windows.
5+
use File::Temp qw/ tempfile /;
6+
#use Data::Printer;
7+
use Text::Wrap;
8+
use Term::ANSIColor;
9+
use String::ShellQuote;
10+
11+
###use IPC::System::Simple qw(capturex);
12+
13+
# In case of problems with "X Error: BadShmSeg (invalid shared segment parameter) 128", run
14+
# env QT_X11_NO_MITSHM=1 git-resolve-conflict-using-kdiff3
15+
16+
my $terminal_width = 80;
17+
18+
# http://stackoverflow.com/a/823638/23118
19+
if (eval {require Term::ReadKey;1;}) {
20+
#module loaded
21+
Term::ReadKey->import();
22+
($terminal_width, undef, undef, undef) = GetTerminalSize();
23+
$terminal_width = 80 if ($terminal_width > 80);
24+
}
25+
26+
$Text::Wrap::columns = $terminal_width;
27+
28+
sub help {
29+
my $line1 = "This command will interactively start kdiff3 with all versions "
30+
. "of a file involved in a merge conflict. Git has natively support for using "
31+
. "kdiff3 for the command mergetool (and difftool) so if you just run the "
32+
. "command git-merge you are set, however there are many situations were a "
33+
. "conflict might occur where git is unable to assist with kdiff3 "
34+
. "(git-rebase, git-cherry-pick).\n";
35+
print wrap('', '', $line1), "\n";
36+
print "This command fills that void.\n";
37+
}
38+
39+
my $one_descr = "the common ancestor";
40+
my $two_descr = "the current branch";
41+
my $three_descr = "the other branch";
42+
43+
my %one = ();
44+
my %two = ();
45+
my %three = ();
46+
47+
# Example output from ls-files -u
48+
# 100644 d0ede601dde8db821c130de742e01b0c805730bc 1 main.c
49+
# 100644 8adaa18e17c8f381ed97035e884ced00b1d27a72 2 main.c
50+
# 100644 e71a590ceea22ce3cd5096e5bf07f7e6739378da 3 main.c
51+
#
52+
# This function updates global hashes %one, %two and %three.
53+
# It returns an array with the file names.
54+
sub find_unmerged_files {
55+
my $toplevel_dir = shift @_;
56+
my %tmp;
57+
open(PIPE, "git ls-files --unmerged " . shell_quote($toplevel_dir) . " -z |");
58+
foreach my $line (split(/\0/, <PIPE>)) {
59+
chomp($line);
60+
my ($prefix, $file) = split(/\t/, $line);
61+
my (undef, $sha, $number) = split(/ /, $prefix);
62+
$one{$file} = $sha if $number == 1;
63+
$two{$file} = $sha if $number == 2;
64+
$three{$file} = $sha if $number == 3;
65+
$tmp{$file} = 0;
66+
}
67+
close(PIPE);
68+
return keys %tmp;
69+
}
70+
71+
sub any_files_modified {
72+
my $ret = 0;
73+
open(PIPE, 'git status --porcelain |');
74+
foreach my $line (<PIPE>) {
75+
if ($line =~ /^[MUDA]/) {
76+
$ret = 1;
77+
last;
78+
}
79+
}
80+
close(PIPE);
81+
return $ret;
82+
}
83+
84+
sub prompt {
85+
my $display_prompt = shift @_;
86+
my $chars = shift @_;
87+
my $default = shift @_;
88+
my $input;
89+
do {
90+
print "$display_prompt [$chars] ($default): ";
91+
$input = <>;
92+
chomp $input;
93+
$input = $default if $input =~ /^$/;
94+
} while (! ($input =~ /^[$chars]/));
95+
return $input;
96+
}
97+
98+
sub blob2file {
99+
my $file = shift @_;
100+
my $sha1 = shift @_;
101+
my (undef, $tempfilename) = tempfile($file . '.XXXXXX');
102+
### my $blob = capturex("git", "cat-file", "blob", $sha1);
103+
#system("sh", "-c", "git", "cat-file", "blob", $sha1, ">", $tempfilename);
104+
system("git cat-file blob $sha1 > " . shell_quote($tempfilename));
105+
return $tempfilename;
106+
}
107+
108+
################################################################################
109+
110+
my $git_dir = `git rev-parse --git-dir`;
111+
chomp($git_dir);
112+
113+
if ($git_dir eq "") {
114+
print "$0: error: not within a git repository\n\n";
115+
help();
116+
exit 1;
117+
}
118+
119+
my $git_toplevel_dir = `git rev-parse --show-toplevel`;
120+
chomp($git_toplevel_dir);
121+
122+
my @unmerged_files = find_unmerged_files($git_toplevel_dir); # modifies %one, %two and %three as well
123+
124+
if (scalar(@unmerged_files) == 0) {
125+
print "$0: error: no files in conflict\n\n";
126+
help();
127+
exit 1;
128+
}
129+
130+
print '=' x $terminal_width, "\n";
131+
print scalar(@unmerged_files), " unmerged files in total:\n";
132+
foreach my $file (@unmerged_files) {
133+
print("\t$file\n");
134+
}
135+
print '=' x $terminal_width, "\n";
136+
137+
my $n = 1;
138+
foreach my $file (@unmerged_files) {
139+
print "Handling " . colored($file, 'green' ) . " (", $n++, "/", scalar(@unmerged_files), "): ";
140+
my $file_added_on_only_one_branch = 0;
141+
my $file_removed = 0;
142+
143+
# http://gitster.livejournal.com/25801.html
144+
if (defined $one{$file} && defined $two{$file} && defined $three{$file}) {
145+
my $msg = "Modified on both branches\n";
146+
print colored($msg, 'yellow' );
147+
}
148+
if (defined $one{$file} && defined $two{$file} && !defined $three{$file}) {
149+
my $msg = "Deleted on ${three_descr} but modified on ${two_descr}\n";
150+
print colored($msg, 'yellow' );
151+
$file_removed = 3;
152+
}
153+
if (defined $one{$file} && !defined $two{$file} && defined $three{$file}) {
154+
my $msg = "Modified on ${three_descr} but deleted on ${two_descr}\n";
155+
print colored($msg, 'yellow' );
156+
$file_removed = 2;
157+
}
158+
if (defined $one{$file} && !defined $two{$file} && !defined $three{$file}) {
159+
my $msg = "Deleted on both branches\n";
160+
print colored($msg, 'yellow' );
161+
$file_removed = 23;
162+
}
163+
if (!defined $one{$file} && defined $two{$file} && defined $three{$file}) {
164+
my $msg = "File added independently on both branches\n";
165+
print colored($msg, 'yellow' );
166+
}
167+
if (!defined $one{$file} && defined $two{$file} && !defined $three{$file}) {
168+
my $msg = "File added only on ${two_descr}\n";
169+
print colored($msg, 'yellow' );
170+
$file_added_on_only_one_branch = 2;
171+
}
172+
if (!defined $one{$file} && !defined $two{$file} && defined $three{$file}) {
173+
my $msg = "File added only on ${three_descr}\n";
174+
print colored($msg, 'yellow' );
175+
$file_added_on_only_one_branch = 3;
176+
}
177+
178+
print("1: ", substr($one{$file}, 0, 7), " ") if (defined $one{$file});
179+
print("2: ", substr($two{$file}, 0, 7), " ") if (defined $two{$file});
180+
print("3: ", substr($three{$file}, 0, 7), "") if (defined $three{$file});
181+
print "\n";
182+
183+
my $input;
184+
if ($file_added_on_only_one_branch) {
185+
$input = prompt("Add or remove " . colored($file, 'green' ) . " (or skip/quit)?", "AaRrSsQq", "a");
186+
last if $input =~ /^[Qq]/;
187+
if ($input =~ /^[Rr]/) {
188+
system("git", "rm", $file);
189+
next;
190+
} elsif ($input =~ /^[Aa]/) {
191+
system("git", "add", $file);
192+
next;
193+
} elsif ($input =~ /^[Ss]/) {
194+
next;
195+
}
196+
}
197+
if ($file_removed) {
198+
$input = prompt("Remove " . colored($file, 'green' ) . " (or skip/quit)?", "RrSsQq", "?"); # No default choice
199+
last if $input =~ /^[Qq]/;
200+
if ($input =~ /^[Rr]/) {
201+
system("git", "rm", $file);
202+
next;
203+
} elsif ($input =~ /^[Ss]/) {
204+
next;
205+
}
206+
}
207+
208+
$input = prompt("Launch kdiff3 for " . colored($file, 'green' ) . "?", "YyNnQq", "y");
209+
last if $input =~ /^[Qq]/;
210+
next if $input =~ /^[Nn]/;
211+
212+
my @input_files = ();
213+
push @input_files, blob2file($file, $one{$file} ) if defined $one{$file};
214+
push @input_files, blob2file($file, $two{$file} ) if defined $two{$file};
215+
push @input_files, blob2file($file, $three{$file}) if defined $three{$file};
216+
system("kdiff3", "-o", "$file.merged", @input_files);
217+
unlink(@input_files);
218+
219+
$input = prompt("Update " . colored($file, 'green' ) . " with merge result?", "YyNnQq", "y");
220+
unlink("$file.merged") unless $input =~ /^[Yy]/;
221+
last if $input =~ /^[Qq]/;
222+
next if $input =~ /^[Nn]/;
223+
224+
system("mv", "$file.merged", $file);
225+
system("git", "add", $file);
226+
227+
}
228+
229+
system("git", "status");
230+
231+
print "Command(s) suggested to continue:\n\n\n";
232+
if (-d "$git_dir/rebase-merge" || -d "$git_dir/rebase-apply") {
233+
if (any_files_modified()) {
234+
print "git diff --cached\n\n";
235+
print "git rebase --continue\n";
236+
} else {
237+
print "git rebase --skip\n";
238+
}
239+
} elsif (-f "$git_dir/REVERT_HEAD") {
240+
if (any_files_modified()) {
241+
print "git diff --cached\n\n";
242+
print "git revert --continue\n";
243+
} else {
244+
print "git revert --abort\n";
245+
}
246+
} elsif (-f "$git_dir/CHERRY_PICK_HEAD" || -f "$git_dir/MERGE_HEAD") {
247+
print "git diff --cached\n\n";
248+
print "git commit\n";
249+
}
250+
print "\n\n";
251+

0 commit comments

Comments
 (0)