1#!/usr/local/bin/perl
2
3use 5.008;
4use strict;
5use warnings;
6use Git qw(unquote_path);
7use Git::I18N;
8
9binmode(STDOUT, ":raw");
10
11my $repo = Git->repository();
12
13my $menu_use_color = $repo->get_colorbool('color.interactive');
14my ($prompt_color, $header_color, $help_color) =
15	$menu_use_color ? (
16		$repo->get_color('color.interactive.prompt', 'bold blue'),
17		$repo->get_color('color.interactive.header', 'bold'),
18		$repo->get_color('color.interactive.help', 'red bold'),
19	) : ();
20my $error_color = ();
21if ($menu_use_color) {
22	my $help_color_spec = ($repo->config('color.interactive.help') or
23				'red bold');
24	$error_color = $repo->get_color('color.interactive.error',
25					$help_color_spec);
26}
27
28my $diff_use_color = $repo->get_colorbool('color.diff');
29my ($fraginfo_color) =
30	$diff_use_color ? (
31		$repo->get_color('color.diff.frag', 'cyan'),
32	) : ();
33my ($diff_context_color) =
34	$diff_use_color ? (
35		$repo->get_color($repo->config('color.diff.context') ? 'color.diff.context' : 'color.diff.plain', ''),
36	) : ();
37my ($diff_old_color) =
38	$diff_use_color ? (
39		$repo->get_color('color.diff.old', 'red'),
40	) : ();
41my ($diff_new_color) =
42	$diff_use_color ? (
43		$repo->get_color('color.diff.new', 'green'),
44	) : ();
45
46my $normal_color = $repo->get_color("", "reset");
47
48my $diff_algorithm = $repo->config('diff.algorithm');
49my $diff_filter = $repo->config('interactive.difffilter');
50
51my $use_readkey = 0;
52my $use_termcap = 0;
53my %term_escapes;
54
55sub ReadMode;
56sub ReadKey;
57if ($repo->config_bool("interactive.singlekey")) {
58	eval {
59		require Term::ReadKey;
60		Term::ReadKey->import;
61		$use_readkey = 1;
62	};
63	if (!$use_readkey) {
64		print STDERR "missing Term::ReadKey, disabling interactive.singlekey\n";
65	}
66	eval {
67		require Term::Cap;
68		my $termcap = Term::Cap->Tgetent;
69		foreach (values %$termcap) {
70			$term_escapes{$_} = 1 if /^\e/;
71		}
72		$use_termcap = 1;
73	};
74}
75
76sub colored {
77	my $color = shift;
78	my $string = join("", @_);
79
80	if (defined $color) {
81		# Put a color code at the beginning of each line, a reset at the end
82		# color after newlines that are not at the end of the string
83		$string =~ s/(\n+)(.)/$1$color$2/g;
84		# reset before newlines
85		$string =~ s/(\n+)/$normal_color$1/g;
86		# codes at beginning and end (if necessary):
87		$string =~ s/^/$color/;
88		$string =~ s/$/$normal_color/ unless $string =~ /\n$/;
89	}
90	return $string;
91}
92
93# command line options
94my $patch_mode_only;
95my $patch_mode;
96my $patch_mode_revision;
97
98sub apply_patch;
99sub apply_patch_for_checkout_commit;
100sub apply_patch_for_stash;
101
102my %patch_modes = (
103	'stage' => {
104		DIFF => 'diff-files -p',
105		APPLY => sub { apply_patch 'apply --cached', @_; },
106		APPLY_CHECK => 'apply --cached',
107		FILTER => 'file-only',
108		IS_REVERSE => 0,
109	},
110	'stash' => {
111		DIFF => 'diff-index -p HEAD',
112		APPLY => sub { apply_patch 'apply --cached', @_; },
113		APPLY_CHECK => 'apply --cached',
114		FILTER => undef,
115		IS_REVERSE => 0,
116	},
117	'reset_head' => {
118		DIFF => 'diff-index -p --cached',
119		APPLY => sub { apply_patch 'apply -R --cached', @_; },
120		APPLY_CHECK => 'apply -R --cached',
121		FILTER => 'index-only',
122		IS_REVERSE => 1,
123	},
124	'reset_nothead' => {
125		DIFF => 'diff-index -R -p --cached',
126		APPLY => sub { apply_patch 'apply --cached', @_; },
127		APPLY_CHECK => 'apply --cached',
128		FILTER => 'index-only',
129		IS_REVERSE => 0,
130	},
131	'checkout_index' => {
132		DIFF => 'diff-files -p',
133		APPLY => sub { apply_patch 'apply -R', @_; },
134		APPLY_CHECK => 'apply -R',
135		FILTER => 'file-only',
136		IS_REVERSE => 1,
137	},
138	'checkout_head' => {
139		DIFF => 'diff-index -p',
140		APPLY => sub { apply_patch_for_checkout_commit '-R', @_ },
141		APPLY_CHECK => 'apply -R',
142		FILTER => undef,
143		IS_REVERSE => 1,
144	},
145	'checkout_nothead' => {
146		DIFF => 'diff-index -R -p',
147		APPLY => sub { apply_patch_for_checkout_commit '', @_ },
148		APPLY_CHECK => 'apply',
149		FILTER => undef,
150		IS_REVERSE => 0,
151	},
152	'worktree_head' => {
153		DIFF => 'diff-index -p',
154		APPLY => sub { apply_patch 'apply -R', @_ },
155		APPLY_CHECK => 'apply -R',
156		FILTER => undef,
157		IS_REVERSE => 1,
158	},
159	'worktree_nothead' => {
160		DIFF => 'diff-index -R -p',
161		APPLY => sub { apply_patch 'apply', @_ },
162		APPLY_CHECK => 'apply',
163		FILTER => undef,
164		IS_REVERSE => 0,
165	},
166);
167
168$patch_mode = 'stage';
169my %patch_mode_flavour = %{$patch_modes{$patch_mode}};
170
171sub run_cmd_pipe {
172	if ($^O eq 'MSWin32') {
173		my @invalid = grep {m/[":*]/} @_;
174		die "$^O does not support: @invalid\n" if @invalid;
175		my @args = map { m/ /o ? "\"$_\"": $_ } @_;
176		return qx{@args};
177	} else {
178		my $fh = undef;
179		open($fh, '-|', @_) or die;
180		my @out = <$fh>;
181		close $fh || die "Cannot close @_ ($!)";
182		return @out;
183	}
184}
185
186my ($GIT_DIR) = run_cmd_pipe(qw(git rev-parse --git-dir));
187
188if (!defined $GIT_DIR) {
189	exit(1); # rev-parse would have already said "not a git repo"
190}
191chomp($GIT_DIR);
192
193sub refresh {
194	my $fh;
195	open $fh, 'git update-index --refresh |'
196	    or die;
197	while (<$fh>) {
198		;# ignore 'needs update'
199	}
200	close $fh;
201}
202
203sub list_untracked {
204	map {
205		chomp $_;
206		unquote_path($_);
207	}
208	run_cmd_pipe(qw(git ls-files --others --exclude-standard --), @ARGV);
209}
210
211# TRANSLATORS: you can adjust this to align "git add -i" status menu
212my $status_fmt = __('%12s %12s %s');
213my $status_head = sprintf($status_fmt, __('staged'), __('unstaged'), __('path'));
214
215{
216	my $initial;
217	sub is_initial_commit {
218		$initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
219			unless defined $initial;
220		return $initial;
221	}
222}
223
224{
225	my $empty_tree;
226	sub get_empty_tree {
227		return $empty_tree if defined $empty_tree;
228
229		($empty_tree) = run_cmd_pipe(qw(git hash-object -t tree /dev/null));
230		chomp $empty_tree;
231		return $empty_tree;
232	}
233}
234
235sub get_diff_reference {
236	my $ref = shift;
237	if (defined $ref and $ref ne 'HEAD') {
238		return $ref;
239	} elsif (is_initial_commit()) {
240		return get_empty_tree();
241	} else {
242		return 'HEAD';
243	}
244}
245
246# Returns list of hashes, contents of each of which are:
247# VALUE:	pathname
248# BINARY:	is a binary path
249# INDEX:	is index different from HEAD?
250# FILE:		is file different from index?
251# INDEX_ADDDEL:	is it add/delete between HEAD and index?
252# FILE_ADDDEL:	is it add/delete between index and file?
253# UNMERGED:	is the path unmerged
254
255sub list_modified {
256	my ($only) = @_;
257	my (%data, @return);
258	my ($add, $del, $adddel, $file);
259
260	my $reference = get_diff_reference($patch_mode_revision);
261	for (run_cmd_pipe(qw(git diff-index --cached
262			     --numstat --summary), $reference,
263			     '--', @ARGV)) {
264		if (($add, $del, $file) =
265		    /^([-\d]+)	([-\d]+)	(.*)/) {
266			my ($change, $bin);
267			$file = unquote_path($file);
268			if ($add eq '-' && $del eq '-') {
269				$change = __('binary');
270				$bin = 1;
271			}
272			else {
273				$change = "+$add/-$del";
274			}
275			$data{$file} = {
276				INDEX => $change,
277				BINARY => $bin,
278				FILE => __('nothing'),
279			}
280		}
281		elsif (($adddel, $file) =
282		       /^ (create|delete) mode [0-7]+ (.*)$/) {
283			$file = unquote_path($file);
284			$data{$file}{INDEX_ADDDEL} = $adddel;
285		}
286	}
287
288	for (run_cmd_pipe(qw(git diff-files --ignore-submodules=dirty --numstat --summary --raw --), @ARGV)) {
289		if (($add, $del, $file) =
290		    /^([-\d]+)	([-\d]+)	(.*)/) {
291			$file = unquote_path($file);
292			my ($change, $bin);
293			if ($add eq '-' && $del eq '-') {
294				$change = __('binary');
295				$bin = 1;
296			}
297			else {
298				$change = "+$add/-$del";
299			}
300			$data{$file}{FILE} = $change;
301			if ($bin) {
302				$data{$file}{BINARY} = 1;
303			}
304		}
305		elsif (($adddel, $file) =
306		       /^ (create|delete) mode [0-7]+ (.*)$/) {
307			$file = unquote_path($file);
308			$data{$file}{FILE_ADDDEL} = $adddel;
309		}
310		elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.)	(.*)$/) {
311			$file = unquote_path($2);
312			if (!exists $data{$file}) {
313				$data{$file} = +{
314					INDEX => __('unchanged'),
315					BINARY => 0,
316				};
317			}
318			if ($1 eq 'U') {
319				$data{$file}{UNMERGED} = 1;
320			}
321		}
322	}
323
324	for (sort keys %data) {
325		my $it = $data{$_};
326
327		if ($only) {
328			if ($only eq 'index-only') {
329				next if ($it->{INDEX} eq __('unchanged'));
330			}
331			if ($only eq 'file-only') {
332				next if ($it->{FILE} eq __('nothing'));
333			}
334		}
335		push @return, +{
336			VALUE => $_,
337			%$it,
338		};
339	}
340	return @return;
341}
342
343sub find_unique {
344	my ($string, @stuff) = @_;
345	my $found = undef;
346	for (my $i = 0; $i < @stuff; $i++) {
347		my $it = $stuff[$i];
348		my $hit = undef;
349		if (ref $it) {
350			if ((ref $it) eq 'ARRAY') {
351				$it = $it->[0];
352			}
353			else {
354				$it = $it->{VALUE};
355			}
356		}
357		eval {
358			if ($it =~ /^$string/) {
359				$hit = 1;
360			};
361		};
362		if (defined $hit && defined $found) {
363			return undef;
364		}
365		if ($hit) {
366			$found = $i + 1;
367		}
368	}
369	return $found;
370}
371
372# inserts string into trie and updates count for each character
373sub update_trie {
374	my ($trie, $string) = @_;
375	foreach (split //, $string) {
376		$trie = $trie->{$_} ||= {COUNT => 0};
377		$trie->{COUNT}++;
378	}
379}
380
381# returns an array of tuples (prefix, remainder)
382sub find_unique_prefixes {
383	my @stuff = @_;
384	my @return = ();
385
386	# any single prefix exceeding the soft limit is omitted
387	# if any prefix exceeds the hard limit all are omitted
388	# 0 indicates no limit
389	my $soft_limit = 0;
390	my $hard_limit = 3;
391
392	# build a trie modelling all possible options
393	my %trie;
394	foreach my $print (@stuff) {
395		if ((ref $print) eq 'ARRAY') {
396			$print = $print->[0];
397		}
398		elsif ((ref $print) eq 'HASH') {
399			$print = $print->{VALUE};
400		}
401		update_trie(\%trie, $print);
402		push @return, $print;
403	}
404
405	# use the trie to find the unique prefixes
406	for (my $i = 0; $i < @return; $i++) {
407		my $ret = $return[$i];
408		my @letters = split //, $ret;
409		my %search = %trie;
410		my ($prefix, $remainder);
411		my $j;
412		for ($j = 0; $j < @letters; $j++) {
413			my $letter = $letters[$j];
414			if ($search{$letter}{COUNT} == 1) {
415				$prefix = substr $ret, 0, $j + 1;
416				$remainder = substr $ret, $j + 1;
417				last;
418			}
419			else {
420				my $prefix = substr $ret, 0, $j;
421				return ()
422				    if ($hard_limit && $j + 1 > $hard_limit);
423			}
424			%search = %{$search{$letter}};
425		}
426		if (ord($letters[0]) > 127 ||
427		    ($soft_limit && $j + 1 > $soft_limit)) {
428			$prefix = undef;
429			$remainder = $ret;
430		}
431		$return[$i] = [$prefix, $remainder];
432	}
433	return @return;
434}
435
436# filters out prefixes which have special meaning to list_and_choose()
437sub is_valid_prefix {
438	my $prefix = shift;
439	return (defined $prefix) &&
440	    !($prefix =~ /[\s,]/) && # separators
441	    !($prefix =~ /^-/) &&    # deselection
442	    !($prefix =~ /^\d+/) &&  # selection
443	    ($prefix ne '*') &&      # "all" wildcard
444	    ($prefix ne '?');        # prompt help
445}
446
447# given a prefix/remainder tuple return a string with the prefix highlighted
448# for now use square brackets; later might use ANSI colors (underline, bold)
449sub highlight_prefix {
450	my $prefix = shift;
451	my $remainder = shift;
452
453	if (!defined $prefix) {
454		return $remainder;
455	}
456
457	if (!is_valid_prefix($prefix)) {
458		return "$prefix$remainder";
459	}
460
461	if (!$menu_use_color) {
462		return "[$prefix]$remainder";
463	}
464
465	return "$prompt_color$prefix$normal_color$remainder";
466}
467
468sub error_msg {
469	print STDERR colored $error_color, @_;
470}
471
472sub list_and_choose {
473	my ($opts, @stuff) = @_;
474	my (@chosen, @return);
475	if (!@stuff) {
476	    return @return;
477	}
478	my $i;
479	my @prefixes = find_unique_prefixes(@stuff) unless $opts->{LIST_ONLY};
480
481      TOPLOOP:
482	while (1) {
483		my $last_lf = 0;
484
485		if ($opts->{HEADER}) {
486			my $indent = $opts->{LIST_FLAT} ? "" : "     ";
487			print colored $header_color, "$indent$opts->{HEADER}\n";
488		}
489		for ($i = 0; $i < @stuff; $i++) {
490			my $chosen = $chosen[$i] ? '*' : ' ';
491			my $print = $stuff[$i];
492			my $ref = ref $print;
493			my $highlighted = highlight_prefix(@{$prefixes[$i]})
494			    if @prefixes;
495			if ($ref eq 'ARRAY') {
496				$print = $highlighted || $print->[0];
497			}
498			elsif ($ref eq 'HASH') {
499				my $value = $highlighted || $print->{VALUE};
500				$print = sprintf($status_fmt,
501				    $print->{INDEX},
502				    $print->{FILE},
503				    $value);
504			}
505			else {
506				$print = $highlighted || $print;
507			}
508			printf("%s%2d: %s", $chosen, $i+1, $print);
509			if (($opts->{LIST_FLAT}) &&
510			    (($i + 1) % ($opts->{LIST_FLAT}))) {
511				print "\t";
512				$last_lf = 0;
513			}
514			else {
515				print "\n";
516				$last_lf = 1;
517			}
518		}
519		if (!$last_lf) {
520			print "\n";
521		}
522
523		return if ($opts->{LIST_ONLY});
524
525		print colored $prompt_color, $opts->{PROMPT};
526		if ($opts->{SINGLETON}) {
527			print "> ";
528		}
529		else {
530			print ">> ";
531		}
532		my $line = <STDIN>;
533		if (!$line) {
534			print "\n";
535			$opts->{ON_EOF}->() if $opts->{ON_EOF};
536			last;
537		}
538		chomp $line;
539		last if $line eq '';
540		if ($line eq '?') {
541			$opts->{SINGLETON} ?
542			    singleton_prompt_help_cmd() :
543			    prompt_help_cmd();
544			next TOPLOOP;
545		}
546		for my $choice (split(/[\s,]+/, $line)) {
547			my $choose = 1;
548			my ($bottom, $top);
549
550			# Input that begins with '-'; unchoose
551			if ($choice =~ s/^-//) {
552				$choose = 0;
553			}
554			# A range can be specified like 5-7 or 5-.
555			if ($choice =~ /^(\d+)-(\d*)$/) {
556				($bottom, $top) = ($1, length($2) ? $2 : 1 + @stuff);
557			}
558			elsif ($choice =~ /^\d+$/) {
559				$bottom = $top = $choice;
560			}
561			elsif ($choice eq '*') {
562				$bottom = 1;
563				$top = 1 + @stuff;
564			}
565			else {
566				$bottom = $top = find_unique($choice, @stuff);
567				if (!defined $bottom) {
568					error_msg sprintf(__("Huh (%s)?\n"), $choice);
569					next TOPLOOP;
570				}
571			}
572			if ($opts->{SINGLETON} && $bottom != $top) {
573				error_msg sprintf(__("Huh (%s)?\n"), $choice);
574				next TOPLOOP;
575			}
576			for ($i = $bottom-1; $i <= $top-1; $i++) {
577				next if (@stuff <= $i || $i < 0);
578				$chosen[$i] = $choose;
579			}
580		}
581		last if ($opts->{IMMEDIATE} || $line eq '*');
582	}
583	for ($i = 0; $i < @stuff; $i++) {
584		if ($chosen[$i]) {
585			push @return, $stuff[$i];
586		}
587	}
588	return @return;
589}
590
591sub singleton_prompt_help_cmd {
592	print colored $help_color, __ <<'EOF' ;
593Prompt help:
5941          - select a numbered item
595foo        - select item based on unique prefix
596           - (empty) select nothing
597EOF
598}
599
600sub prompt_help_cmd {
601	print colored $help_color, __ <<'EOF' ;
602Prompt help:
6031          - select a single item
6043-5        - select a range of items
6052-3,6-9    - select multiple ranges
606foo        - select item based on unique prefix
607-...       - unselect specified items
608*          - choose all items
609           - (empty) finish selecting
610EOF
611}
612
613sub status_cmd {
614	list_and_choose({ LIST_ONLY => 1, HEADER => $status_head },
615			list_modified());
616	print "\n";
617}
618
619sub say_n_paths {
620	my $did = shift @_;
621	my $cnt = scalar @_;
622	if ($did eq 'added') {
623		printf(__n("added %d path\n", "added %d paths\n",
624			   $cnt), $cnt);
625	} elsif ($did eq 'updated') {
626		printf(__n("updated %d path\n", "updated %d paths\n",
627			   $cnt), $cnt);
628	} elsif ($did eq 'reverted') {
629		printf(__n("reverted %d path\n", "reverted %d paths\n",
630			   $cnt), $cnt);
631	} else {
632		printf(__n("touched %d path\n", "touched %d paths\n",
633			   $cnt), $cnt);
634	}
635}
636
637sub update_cmd {
638	my @mods = list_modified('file-only');
639	return if (!@mods);
640
641	my @update = list_and_choose({ PROMPT => __('Update'),
642				       HEADER => $status_head, },
643				     @mods);
644	if (@update) {
645		system(qw(git update-index --add --remove --),
646		       map { $_->{VALUE} } @update);
647		say_n_paths('updated', @update);
648	}
649	print "\n";
650}
651
652sub revert_cmd {
653	my @update = list_and_choose({ PROMPT => __('Revert'),
654				       HEADER => $status_head, },
655				     list_modified());
656	if (@update) {
657		if (is_initial_commit()) {
658			system(qw(git rm --cached),
659				map { $_->{VALUE} } @update);
660		}
661		else {
662			my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
663						 map { $_->{VALUE} } @update);
664			my $fh;
665			open $fh, '| git update-index --index-info'
666			    or die;
667			for (@lines) {
668				print $fh $_;
669			}
670			close($fh);
671			for (@update) {
672				if ($_->{INDEX_ADDDEL} &&
673				    $_->{INDEX_ADDDEL} eq 'create') {
674					system(qw(git update-index --force-remove --),
675					       $_->{VALUE});
676					printf(__("note: %s is untracked now.\n"), $_->{VALUE});
677				}
678			}
679		}
680		refresh();
681		say_n_paths('reverted', @update);
682	}
683	print "\n";
684}
685
686sub add_untracked_cmd {
687	my @add = list_and_choose({ PROMPT => __('Add untracked') },
688				  list_untracked());
689	if (@add) {
690		system(qw(git update-index --add --), @add);
691		say_n_paths('added', @add);
692	} else {
693		print __("No untracked files.\n");
694	}
695	print "\n";
696}
697
698sub run_git_apply {
699	my $cmd = shift;
700	my $fh;
701	open $fh, '| git ' . $cmd . " --allow-overlap";
702	print $fh @_;
703	return close $fh;
704}
705
706sub parse_diff {
707	my ($path) = @_;
708	my @diff_cmd = split(" ", $patch_mode_flavour{DIFF});
709	if (defined $diff_algorithm) {
710		splice @diff_cmd, 1, 0, "--diff-algorithm=${diff_algorithm}";
711	}
712	if (defined $patch_mode_revision) {
713		push @diff_cmd, get_diff_reference($patch_mode_revision);
714	}
715	my @diff = run_cmd_pipe("git", @diff_cmd, qw(--no-color --), $path);
716	my @colored = ();
717	if ($diff_use_color) {
718		my @display_cmd = ("git", @diff_cmd, qw(--color --), $path);
719		if (defined $diff_filter) {
720			# quotemeta is overkill, but sufficient for shell-quoting
721			my $diff = join(' ', map { quotemeta } @display_cmd);
722			@display_cmd = ("$diff | $diff_filter");
723		}
724
725		@colored = run_cmd_pipe(@display_cmd);
726	}
727	my (@hunk) = { TEXT => [], DISPLAY => [], TYPE => 'header' };
728
729	if (@colored && @colored != @diff) {
730		print STDERR
731		  "fatal: mismatched output from interactive.diffFilter\n",
732		  "hint: Your filter must maintain a one-to-one correspondence\n",
733		  "hint: between its input and output lines.\n";
734		exit 1;
735	}
736
737	for (my $i = 0; $i < @diff; $i++) {
738		if ($diff[$i] =~ /^@@ /) {
739			push @hunk, { TEXT => [], DISPLAY => [],
740				TYPE => 'hunk' };
741		}
742		push @{$hunk[-1]{TEXT}}, $diff[$i];
743		push @{$hunk[-1]{DISPLAY}},
744			(@colored ? $colored[$i] : $diff[$i]);
745	}
746	return @hunk;
747}
748
749sub parse_diff_header {
750	my $src = shift;
751
752	my $head = { TEXT => [], DISPLAY => [], TYPE => 'header' };
753	my $mode = { TEXT => [], DISPLAY => [], TYPE => 'mode' };
754	my $deletion = { TEXT => [], DISPLAY => [], TYPE => 'deletion' };
755	my $addition;
756
757	for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
758		if ($src->{TEXT}->[$i] =~ /^new file/) {
759			$addition = 1;
760			$head->{TYPE} = 'addition';
761		}
762		my $dest =
763		   $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ? $mode :
764		   $src->{TEXT}->[$i] =~ /^deleted file/ ? $deletion :
765		   $head;
766		push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
767		push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
768	}
769	return ($head, $mode, $deletion, $addition);
770}
771
772sub hunk_splittable {
773	my ($text) = @_;
774
775	my @s = split_hunk($text);
776	return (1 < @s);
777}
778
779sub parse_hunk_header {
780	my ($line) = @_;
781	my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
782	    $line =~ /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
783	$o_cnt = 1 unless defined $o_cnt;
784	$n_cnt = 1 unless defined $n_cnt;
785	return ($o_ofs, $o_cnt, $n_ofs, $n_cnt);
786}
787
788sub format_hunk_header {
789	my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) = @_;
790	return ("@@ -$o_ofs" .
791		(($o_cnt != 1) ? ",$o_cnt" : '') .
792		" +$n_ofs" .
793		(($n_cnt != 1) ? ",$n_cnt" : '') .
794		" @@\n");
795}
796
797sub split_hunk {
798	my ($text, $display) = @_;
799	my @split = ();
800	if (!defined $display) {
801		$display = $text;
802	}
803	# If there are context lines in the middle of a hunk,
804	# it can be split, but we would need to take care of
805	# overlaps later.
806
807	my ($o_ofs, undef, $n_ofs) = parse_hunk_header($text->[0]);
808	my $hunk_start = 1;
809
810      OUTER:
811	while (1) {
812		my $next_hunk_start = undef;
813		my $i = $hunk_start - 1;
814		my $this = +{
815			TEXT => [],
816			DISPLAY => [],
817			TYPE => 'hunk',
818			OLD => $o_ofs,
819			NEW => $n_ofs,
820			OCNT => 0,
821			NCNT => 0,
822			ADDDEL => 0,
823			POSTCTX => 0,
824			USE => undef,
825		};
826
827		while (++$i < @$text) {
828			my $line = $text->[$i];
829			my $display = $display->[$i];
830			if ($line =~ /^\\/) {
831				push @{$this->{TEXT}}, $line;
832				push @{$this->{DISPLAY}}, $display;
833				next;
834			}
835			if ($line =~ /^ /) {
836				if ($this->{ADDDEL} &&
837				    !defined $next_hunk_start) {
838					# We have seen leading context and
839					# adds/dels and then here is another
840					# context, which is trailing for this
841					# split hunk and leading for the next
842					# one.
843					$next_hunk_start = $i;
844				}
845				push @{$this->{TEXT}}, $line;
846				push @{$this->{DISPLAY}}, $display;
847				$this->{OCNT}++;
848				$this->{NCNT}++;
849				if (defined $next_hunk_start) {
850					$this->{POSTCTX}++;
851				}
852				next;
853			}
854
855			# add/del
856			if (defined $next_hunk_start) {
857				# We are done with the current hunk and
858				# this is the first real change for the
859				# next split one.
860				$hunk_start = $next_hunk_start;
861				$o_ofs = $this->{OLD} + $this->{OCNT};
862				$n_ofs = $this->{NEW} + $this->{NCNT};
863				$o_ofs -= $this->{POSTCTX};
864				$n_ofs -= $this->{POSTCTX};
865				push @split, $this;
866				redo OUTER;
867			}
868			push @{$this->{TEXT}}, $line;
869			push @{$this->{DISPLAY}}, $display;
870			$this->{ADDDEL}++;
871			if ($line =~ /^-/) {
872				$this->{OCNT}++;
873			}
874			else {
875				$this->{NCNT}++;
876			}
877		}
878
879		push @split, $this;
880		last;
881	}
882
883	for my $hunk (@split) {
884		$o_ofs = $hunk->{OLD};
885		$n_ofs = $hunk->{NEW};
886		my $o_cnt = $hunk->{OCNT};
887		my $n_cnt = $hunk->{NCNT};
888
889		my $head = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
890		my $display_head = $head;
891		unshift @{$hunk->{TEXT}}, $head;
892		if ($diff_use_color) {
893			$display_head = colored($fraginfo_color, $head);
894		}
895		unshift @{$hunk->{DISPLAY}}, $display_head;
896	}
897	return @split;
898}
899
900sub find_last_o_ctx {
901	my ($it) = @_;
902	my $text = $it->{TEXT};
903	my ($o_ofs, $o_cnt) = parse_hunk_header($text->[0]);
904	my $i = @{$text};
905	my $last_o_ctx = $o_ofs + $o_cnt;
906	while (0 < --$i) {
907		my $line = $text->[$i];
908		if ($line =~ /^ /) {
909			$last_o_ctx--;
910			next;
911		}
912		last;
913	}
914	return $last_o_ctx;
915}
916
917sub merge_hunk {
918	my ($prev, $this) = @_;
919	my ($o0_ofs, $o0_cnt, $n0_ofs, $n0_cnt) =
920	    parse_hunk_header($prev->{TEXT}[0]);
921	my ($o1_ofs, $o1_cnt, $n1_ofs, $n1_cnt) =
922	    parse_hunk_header($this->{TEXT}[0]);
923
924	my (@line, $i, $ofs, $o_cnt, $n_cnt);
925	$ofs = $o0_ofs;
926	$o_cnt = $n_cnt = 0;
927	for ($i = 1; $i < @{$prev->{TEXT}}; $i++) {
928		my $line = $prev->{TEXT}[$i];
929		if ($line =~ /^\+/) {
930			$n_cnt++;
931			push @line, $line;
932			next;
933		} elsif ($line =~ /^\\/) {
934			push @line, $line;
935			next;
936		}
937
938		last if ($o1_ofs <= $ofs);
939
940		$o_cnt++;
941		$ofs++;
942		if ($line =~ /^ /) {
943			$n_cnt++;
944		}
945		push @line, $line;
946	}
947
948	for ($i = 1; $i < @{$this->{TEXT}}; $i++) {
949		my $line = $this->{TEXT}[$i];
950		if ($line =~ /^\+/) {
951			$n_cnt++;
952			push @line, $line;
953			next;
954		} elsif ($line =~ /^\\/) {
955			push @line, $line;
956			next;
957		}
958		$ofs++;
959		$o_cnt++;
960		if ($line =~ /^ /) {
961			$n_cnt++;
962		}
963		push @line, $line;
964	}
965	my $head = format_hunk_header($o0_ofs, $o_cnt, $n0_ofs, $n_cnt);
966	@{$prev->{TEXT}} = ($head, @line);
967}
968
969sub coalesce_overlapping_hunks {
970	my (@in) = @_;
971	my @out = ();
972
973	my ($last_o_ctx, $last_was_dirty);
974	my $ofs_delta = 0;
975
976	for (@in) {
977		if ($_->{TYPE} ne 'hunk') {
978			push @out, $_;
979			next;
980		}
981		my $text = $_->{TEXT};
982		my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
983						parse_hunk_header($text->[0]);
984		unless ($_->{USE}) {
985			$ofs_delta += $o_cnt - $n_cnt;
986			# If this hunk has been edited then subtract
987			# the delta that is due to the edit.
988			if ($_->{OFS_DELTA}) {
989				$ofs_delta -= $_->{OFS_DELTA};
990			}
991			next;
992		}
993		if ($ofs_delta) {
994			if ($patch_mode_flavour{IS_REVERSE}) {
995				$o_ofs -= $ofs_delta;
996			} else {
997				$n_ofs += $ofs_delta;
998			}
999			$_->{TEXT}->[0] = format_hunk_header($o_ofs, $o_cnt,
1000							     $n_ofs, $n_cnt);
1001		}
1002		# If this hunk was edited then adjust the offset delta
1003		# to reflect the edit.
1004		if ($_->{OFS_DELTA}) {
1005			$ofs_delta += $_->{OFS_DELTA};
1006		}
1007		if (defined $last_o_ctx &&
1008		    $o_ofs <= $last_o_ctx &&
1009		    !$_->{DIRTY} &&
1010		    !$last_was_dirty) {
1011			merge_hunk($out[-1], $_);
1012		}
1013		else {
1014			push @out, $_;
1015		}
1016		$last_o_ctx = find_last_o_ctx($out[-1]);
1017		$last_was_dirty = $_->{DIRTY};
1018	}
1019	return @out;
1020}
1021
1022sub reassemble_patch {
1023	my $head = shift;
1024	my @patch;
1025
1026	# Include everything in the header except the beginning of the diff.
1027	push @patch, (grep { !/^[-+]{3}/ } @$head);
1028
1029	# Then include any headers from the hunk lines, which must
1030	# come before any actual hunk.
1031	while (@_ && $_[0] !~ /^@/) {
1032		push @patch, shift;
1033	}
1034
1035	# Then begin the diff.
1036	push @patch, grep { /^[-+]{3}/ } @$head;
1037
1038	# And then the actual hunks.
1039	push @patch, @_;
1040
1041	return @patch;
1042}
1043
1044sub color_diff {
1045	return map {
1046		colored((/^@/  ? $fraginfo_color :
1047			 /^\+/ ? $diff_new_color :
1048			 /^-/  ? $diff_old_color :
1049			 $diff_context_color),
1050			$_);
1051	} @_;
1052}
1053
1054my %edit_hunk_manually_modes = (
1055	stage => N__(
1056"If the patch applies cleanly, the edited hunk will immediately be
1057marked for staging."),
1058	stash => N__(
1059"If the patch applies cleanly, the edited hunk will immediately be
1060marked for stashing."),
1061	reset_head => N__(
1062"If the patch applies cleanly, the edited hunk will immediately be
1063marked for unstaging."),
1064	reset_nothead => N__(
1065"If the patch applies cleanly, the edited hunk will immediately be
1066marked for applying."),
1067	checkout_index => N__(
1068"If the patch applies cleanly, the edited hunk will immediately be
1069marked for discarding."),
1070	checkout_head => N__(
1071"If the patch applies cleanly, the edited hunk will immediately be
1072marked for discarding."),
1073	checkout_nothead => N__(
1074"If the patch applies cleanly, the edited hunk will immediately be
1075marked for applying."),
1076	worktree_head => N__(
1077"If the patch applies cleanly, the edited hunk will immediately be
1078marked for discarding."),
1079	worktree_nothead => N__(
1080"If the patch applies cleanly, the edited hunk will immediately be
1081marked for applying."),
1082);
1083
1084sub recount_edited_hunk {
1085	local $_;
1086	my ($oldtext, $newtext) = @_;
1087	my ($o_cnt, $n_cnt) = (0, 0);
1088	for (@{$newtext}[1..$#{$newtext}]) {
1089		my $mode = substr($_, 0, 1);
1090		if ($mode eq '-') {
1091			$o_cnt++;
1092		} elsif ($mode eq '+') {
1093			$n_cnt++;
1094		} elsif ($mode eq ' ' or $mode eq "\n") {
1095			$o_cnt++;
1096			$n_cnt++;
1097		}
1098	}
1099	my ($o_ofs, undef, $n_ofs, undef) =
1100					parse_hunk_header($newtext->[0]);
1101	$newtext->[0] = format_hunk_header($o_ofs, $o_cnt, $n_ofs, $n_cnt);
1102	my (undef, $orig_o_cnt, undef, $orig_n_cnt) =
1103					parse_hunk_header($oldtext->[0]);
1104	# Return the change in the number of lines inserted by this hunk
1105	return $orig_o_cnt - $orig_n_cnt - $o_cnt + $n_cnt;
1106}
1107
1108sub edit_hunk_manually {
1109	my ($oldtext) = @_;
1110
1111	my $hunkfile = $repo->repo_path . "/addp-hunk-edit.diff";
1112	my $fh;
1113	open $fh, '>', $hunkfile
1114		or die sprintf(__("failed to open hunk edit file for writing: %s"), $!);
1115	print $fh Git::comment_lines __("Manual hunk edit mode -- see bottom for a quick guide.\n");
1116	print $fh @$oldtext;
1117	my $is_reverse = $patch_mode_flavour{IS_REVERSE};
1118	my ($remove_plus, $remove_minus) = $is_reverse ? ('-', '+') : ('+', '-');
1119	my $comment_line_char = Git::get_comment_line_char;
1120	print $fh Git::comment_lines sprintf(__ <<EOF, $remove_minus, $remove_plus, $comment_line_char),
1121---
1122To remove '%s' lines, make them ' ' lines (context).
1123To remove '%s' lines, delete them.
1124Lines starting with %s will be removed.
1125EOF
1126__($edit_hunk_manually_modes{$patch_mode}),
1127# TRANSLATORS: 'it' refers to the patch mentioned in the previous messages.
1128__ <<EOF2 ;
1129If it does not apply cleanly, you will be given an opportunity to
1130edit again.  If all lines of the hunk are removed, then the edit is
1131aborted and the hunk is left unchanged.
1132EOF2
1133	close $fh;
1134
1135	chomp(my ($editor) = run_cmd_pipe(qw(git var GIT_EDITOR)));
1136	system('sh', '-c', $editor.' "$@"', $editor, $hunkfile);
1137
1138	if ($? != 0) {
1139		return undef;
1140	}
1141
1142	open $fh, '<', $hunkfile
1143		or die sprintf(__("failed to open hunk edit file for reading: %s"), $!);
1144	my @newtext = grep { !/^\Q$comment_line_char\E/ } <$fh>;
1145	close $fh;
1146	unlink $hunkfile;
1147
1148	# Abort if nothing remains
1149	if (!grep { /\S/ } @newtext) {
1150		return undef;
1151	}
1152
1153	# Reinsert the first hunk header if the user accidentally deleted it
1154	if ($newtext[0] !~ /^@/) {
1155		unshift @newtext, $oldtext->[0];
1156	}
1157	return \@newtext;
1158}
1159
1160sub diff_applies {
1161	return run_git_apply($patch_mode_flavour{APPLY_CHECK} . ' --check',
1162			     map { @{$_->{TEXT}} } @_);
1163}
1164
1165sub _restore_terminal_and_die {
1166	ReadMode 'restore';
1167	print "\n";
1168	exit 1;
1169}
1170
1171sub prompt_single_character {
1172	if ($use_readkey) {
1173		local $SIG{TERM} = \&_restore_terminal_and_die;
1174		local $SIG{INT} = \&_restore_terminal_and_die;
1175		ReadMode 'cbreak';
1176		my $key = ReadKey 0;
1177		ReadMode 'restore';
1178		if ($use_termcap and $key eq "\e") {
1179			while (!defined $term_escapes{$key}) {
1180				my $next = ReadKey 0.5;
1181				last if (!defined $next);
1182				$key .= $next;
1183			}
1184			$key =~ s/\e/^[/;
1185		}
1186		print "$key" if defined $key;
1187		print "\n";
1188		return $key;
1189	} else {
1190		return <STDIN>;
1191	}
1192}
1193
1194sub prompt_yesno {
1195	my ($prompt) = @_;
1196	while (1) {
1197		print colored $prompt_color, $prompt;
1198		my $line = prompt_single_character;
1199		return undef unless defined $line;
1200		return 0 if $line =~ /^n/i;
1201		return 1 if $line =~ /^y/i;
1202	}
1203}
1204
1205sub edit_hunk_loop {
1206	my ($head, $hunks, $ix) = @_;
1207	my $hunk = $hunks->[$ix];
1208	my $text = $hunk->{TEXT};
1209
1210	while (1) {
1211		my $newtext = edit_hunk_manually($text);
1212		if (!defined $newtext) {
1213			return undef;
1214		}
1215		my $newhunk = {
1216			TEXT => $newtext,
1217			TYPE => $hunk->{TYPE},
1218			USE => 1,
1219			DIRTY => 1,
1220		};
1221		$newhunk->{OFS_DELTA} = recount_edited_hunk($text, $newtext);
1222		# If this hunk has already been edited then add the
1223		# offset delta of the previous edit to get the real
1224		# delta from the original unedited hunk.
1225		$hunk->{OFS_DELTA} and
1226				$newhunk->{OFS_DELTA} += $hunk->{OFS_DELTA};
1227		if (diff_applies($head,
1228				 @{$hunks}[0..$ix-1],
1229				 $newhunk,
1230				 @{$hunks}[$ix+1..$#{$hunks}])) {
1231			$newhunk->{DISPLAY} = [color_diff(@{$newtext})];
1232			return $newhunk;
1233		}
1234		else {
1235			prompt_yesno(
1236				# TRANSLATORS: do not translate [y/n]
1237				# The program will only accept that input
1238				# at this point.
1239				# Consider translating (saying "no" discards!) as
1240				# (saying "n" for "no" discards!) if the translation
1241				# of the word "no" does not start with n.
1242				__('Your edited hunk does not apply. Edit again '
1243				   . '(saying "no" discards!) [y/n]? ')
1244				) or return undef;
1245		}
1246	}
1247}
1248
1249my %help_patch_modes = (
1250	stage => N__(
1251"y - stage this hunk
1252n - do not stage this hunk
1253q - quit; do not stage this hunk or any of the remaining ones
1254a - stage this hunk and all later hunks in the file
1255d - do not stage this hunk or any of the later hunks in the file"),
1256	stash => N__(
1257"y - stash this hunk
1258n - do not stash this hunk
1259q - quit; do not stash this hunk or any of the remaining ones
1260a - stash this hunk and all later hunks in the file
1261d - do not stash this hunk or any of the later hunks in the file"),
1262	reset_head => N__(
1263"y - unstage this hunk
1264n - do not unstage this hunk
1265q - quit; do not unstage this hunk or any of the remaining ones
1266a - unstage this hunk and all later hunks in the file
1267d - do not unstage this hunk or any of the later hunks in the file"),
1268	reset_nothead => N__(
1269"y - apply this hunk to index
1270n - do not apply this hunk to index
1271q - quit; do not apply this hunk or any of the remaining ones
1272a - apply this hunk and all later hunks in the file
1273d - do not apply this hunk or any of the later hunks in the file"),
1274	checkout_index => N__(
1275"y - discard this hunk from worktree
1276n - do not discard this hunk from worktree
1277q - quit; do not discard this hunk or any of the remaining ones
1278a - discard this hunk and all later hunks in the file
1279d - do not discard this hunk or any of the later hunks in the file"),
1280	checkout_head => N__(
1281"y - discard this hunk from index and worktree
1282n - do not discard this hunk from index and worktree
1283q - quit; do not discard this hunk or any of the remaining ones
1284a - discard this hunk and all later hunks in the file
1285d - do not discard this hunk or any of the later hunks in the file"),
1286	checkout_nothead => N__(
1287"y - apply this hunk to index and worktree
1288n - do not apply this hunk to index and worktree
1289q - quit; do not apply this hunk or any of the remaining ones
1290a - apply this hunk and all later hunks in the file
1291d - do not apply this hunk or any of the later hunks in the file"),
1292	worktree_head => N__(
1293"y - discard this hunk from worktree
1294n - do not discard this hunk from worktree
1295q - quit; do not discard this hunk or any of the remaining ones
1296a - discard this hunk and all later hunks in the file
1297d - do not discard this hunk or any of the later hunks in the file"),
1298	worktree_nothead => N__(
1299"y - apply this hunk to worktree
1300n - do not apply this hunk to worktree
1301q - quit; do not apply this hunk or any of the remaining ones
1302a - apply this hunk and all later hunks in the file
1303d - do not apply this hunk or any of the later hunks in the file"),
1304);
1305
1306sub help_patch_cmd {
1307	local $_;
1308	my $other = $_[0] . ",?";
1309	print colored $help_color, __($help_patch_modes{$patch_mode}), "\n",
1310		map { "$_\n" } grep {
1311			my $c = quotemeta(substr($_, 0, 1));
1312			$other =~ /,$c/
1313		} split "\n", __ <<EOF ;
1314g - select a hunk to go to
1315/ - search for a hunk matching the given regex
1316j - leave this hunk undecided, see next undecided hunk
1317J - leave this hunk undecided, see next hunk
1318k - leave this hunk undecided, see previous undecided hunk
1319K - leave this hunk undecided, see previous hunk
1320s - split the current hunk into smaller hunks
1321e - manually edit the current hunk
1322? - print help
1323EOF
1324}
1325
1326sub apply_patch {
1327	my $cmd = shift;
1328	my $ret = run_git_apply $cmd, @_;
1329	if (!$ret) {
1330		print STDERR @_;
1331	}
1332	return $ret;
1333}
1334
1335sub apply_patch_for_checkout_commit {
1336	my $reverse = shift;
1337	my $applies_index = run_git_apply 'apply '.$reverse.' --cached --check', @_;
1338	my $applies_worktree = run_git_apply 'apply '.$reverse.' --check', @_;
1339
1340	if ($applies_worktree && $applies_index) {
1341		run_git_apply 'apply '.$reverse.' --cached', @_;
1342		run_git_apply 'apply '.$reverse, @_;
1343		return 1;
1344	} elsif (!$applies_index) {
1345		print colored $error_color, __("The selected hunks do not apply to the index!\n");
1346		if (prompt_yesno __("Apply them to the worktree anyway? ")) {
1347			return run_git_apply 'apply '.$reverse, @_;
1348		} else {
1349			print colored $error_color, __("Nothing was applied.\n");
1350			return 0;
1351		}
1352	} else {
1353		print STDERR @_;
1354		return 0;
1355	}
1356}
1357
1358sub patch_update_cmd {
1359	my @all_mods = list_modified($patch_mode_flavour{FILTER});
1360	error_msg sprintf(__("ignoring unmerged: %s\n"), $_->{VALUE})
1361		for grep { $_->{UNMERGED} } @all_mods;
1362	@all_mods = grep { !$_->{UNMERGED} } @all_mods;
1363
1364	my @mods = grep { !($_->{BINARY}) } @all_mods;
1365	my @them;
1366
1367	if (!@mods) {
1368		if (@all_mods) {
1369			print STDERR __("Only binary files changed.\n");
1370		} else {
1371			print STDERR __("No changes.\n");
1372		}
1373		return 0;
1374	}
1375	if ($patch_mode_only) {
1376		@them = @mods;
1377	}
1378	else {
1379		@them = list_and_choose({ PROMPT => __('Patch update'),
1380					  HEADER => $status_head, },
1381					@mods);
1382	}
1383	for (@them) {
1384		return 0 if patch_update_file($_->{VALUE});
1385	}
1386}
1387
1388# Generate a one line summary of a hunk.
1389sub summarize_hunk {
1390	my $rhunk = shift;
1391	my $summary = $rhunk->{TEXT}[0];
1392
1393	# Keep the line numbers, discard extra context.
1394	$summary =~ s/@@(.*?)@@.*/$1 /s;
1395	$summary .= " " x (20 - length $summary);
1396
1397	# Add some user context.
1398	for my $line (@{$rhunk->{TEXT}}) {
1399		if ($line =~ m/^[+-].*\w/) {
1400			$summary .= $line;
1401			last;
1402		}
1403	}
1404
1405	chomp $summary;
1406	return substr($summary, 0, 80) . "\n";
1407}
1408
1409
1410# Print a one-line summary of each hunk in the array ref in
1411# the first argument, starting with the index in the 2nd.
1412sub display_hunks {
1413	my ($hunks, $i) = @_;
1414	my $ctr = 0;
1415	$i ||= 0;
1416	for (; $i < @$hunks && $ctr < 20; $i++, $ctr++) {
1417		my $status = " ";
1418		if (defined $hunks->[$i]{USE}) {
1419			$status = $hunks->[$i]{USE} ? "+" : "-";
1420		}
1421		printf "%s%2d: %s",
1422			$status,
1423			$i + 1,
1424			summarize_hunk($hunks->[$i]);
1425	}
1426	return $i;
1427}
1428
1429my %patch_update_prompt_modes = (
1430	stage => {
1431		mode => N__("Stage mode change [y,n,q,a,d%s,?]? "),
1432		deletion => N__("Stage deletion [y,n,q,a,d%s,?]? "),
1433		addition => N__("Stage addition [y,n,q,a,d%s,?]? "),
1434		hunk => N__("Stage this hunk [y,n,q,a,d%s,?]? "),
1435	},
1436	stash => {
1437		mode => N__("Stash mode change [y,n,q,a,d%s,?]? "),
1438		deletion => N__("Stash deletion [y,n,q,a,d%s,?]? "),
1439		addition => N__("Stash addition [y,n,q,a,d%s,?]? "),
1440		hunk => N__("Stash this hunk [y,n,q,a,d%s,?]? "),
1441	},
1442	reset_head => {
1443		mode => N__("Unstage mode change [y,n,q,a,d%s,?]? "),
1444		deletion => N__("Unstage deletion [y,n,q,a,d%s,?]? "),
1445		addition => N__("Unstage addition [y,n,q,a,d%s,?]? "),
1446		hunk => N__("Unstage this hunk [y,n,q,a,d%s,?]? "),
1447	},
1448	reset_nothead => {
1449		mode => N__("Apply mode change to index [y,n,q,a,d%s,?]? "),
1450		deletion => N__("Apply deletion to index [y,n,q,a,d%s,?]? "),
1451		addition => N__("Apply addition to index [y,n,q,a,d%s,?]? "),
1452		hunk => N__("Apply this hunk to index [y,n,q,a,d%s,?]? "),
1453	},
1454	checkout_index => {
1455		mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1456		deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1457		addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1458		hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1459	},
1460	checkout_head => {
1461		mode => N__("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "),
1462		deletion => N__("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "),
1463		addition => N__("Discard addition from index and worktree [y,n,q,a,d%s,?]? "),
1464		hunk => N__("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "),
1465	},
1466	checkout_nothead => {
1467		mode => N__("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "),
1468		deletion => N__("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "),
1469		addition => N__("Apply addition to index and worktree [y,n,q,a,d%s,?]? "),
1470		hunk => N__("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "),
1471	},
1472	worktree_head => {
1473		mode => N__("Discard mode change from worktree [y,n,q,a,d%s,?]? "),
1474		deletion => N__("Discard deletion from worktree [y,n,q,a,d%s,?]? "),
1475		addition => N__("Discard addition from worktree [y,n,q,a,d%s,?]? "),
1476		hunk => N__("Discard this hunk from worktree [y,n,q,a,d%s,?]? "),
1477	},
1478	worktree_nothead => {
1479		mode => N__("Apply mode change to worktree [y,n,q,a,d%s,?]? "),
1480		deletion => N__("Apply deletion to worktree [y,n,q,a,d%s,?]? "),
1481		addition => N__("Apply addition to worktree [y,n,q,a,d%s,?]? "),
1482		hunk => N__("Apply this hunk to worktree [y,n,q,a,d%s,?]? "),
1483	},
1484);
1485
1486sub patch_update_file {
1487	my $quit = 0;
1488	my ($ix, $num);
1489	my $path = shift;
1490	my ($head, @hunk) = parse_diff($path);
1491	($head, my $mode, my $deletion, my $addition) = parse_diff_header($head);
1492	for (@{$head->{DISPLAY}}) {
1493		print;
1494	}
1495
1496	if (@{$mode->{TEXT}}) {
1497		unshift @hunk, $mode;
1498	}
1499	if (@{$deletion->{TEXT}}) {
1500		foreach my $hunk (@hunk) {
1501			push @{$deletion->{TEXT}}, @{$hunk->{TEXT}};
1502			push @{$deletion->{DISPLAY}}, @{$hunk->{DISPLAY}};
1503		}
1504		@hunk = ($deletion);
1505	}
1506
1507	$num = scalar @hunk;
1508	$ix = 0;
1509
1510	while (1) {
1511		my ($prev, $next, $other, $undecided, $i);
1512		$other = '';
1513
1514		last if ($ix and !$num);
1515		if ($num <= $ix) {
1516			$ix = 0;
1517		}
1518		for ($i = 0; $i < $ix; $i++) {
1519			if (!defined $hunk[$i]{USE}) {
1520				$prev = 1;
1521				$other .= ',k';
1522				last;
1523			}
1524		}
1525		if ($ix) {
1526			$other .= ',K';
1527		}
1528		for ($i = $ix + 1; $i < $num; $i++) {
1529			if (!defined $hunk[$i]{USE}) {
1530				$next = 1;
1531				$other .= ',j';
1532				last;
1533			}
1534		}
1535		if ($ix < $num - 1) {
1536			$other .= ',J';
1537		}
1538		if ($num > 1) {
1539			$other .= ',g,/';
1540		}
1541		for ($i = 0; $i < $num; $i++) {
1542			if (!defined $hunk[$i]{USE}) {
1543				$undecided = 1;
1544				last;
1545			}
1546		}
1547		last if (!$undecided && ($num || !$addition));
1548
1549		if ($num) {
1550			if ($hunk[$ix]{TYPE} eq 'hunk' &&
1551			    hunk_splittable($hunk[$ix]{TEXT})) {
1552				$other .= ',s';
1553			}
1554			if ($hunk[$ix]{TYPE} eq 'hunk') {
1555				$other .= ',e';
1556			}
1557			for (@{$hunk[$ix]{DISPLAY}}) {
1558				print;
1559			}
1560		}
1561		my $type = $num ? $hunk[$ix]{TYPE} : $head->{TYPE};
1562		print colored $prompt_color, "(", ($ix+1), "/", ($num ? $num : 1), ") ",
1563			sprintf(__($patch_update_prompt_modes{$patch_mode}{$type}), $other);
1564
1565		my $line = prompt_single_character;
1566		last unless defined $line;
1567		if ($line) {
1568			if ($line =~ /^y/i) {
1569				if ($num) {
1570					$hunk[$ix]{USE} = 1;
1571				} else {
1572					$head->{USE} = 1;
1573				}
1574			}
1575			elsif ($line =~ /^n/i) {
1576				if ($num) {
1577					$hunk[$ix]{USE} = 0;
1578				} else {
1579					$head->{USE} = 0;
1580				}
1581			}
1582			elsif ($line =~ /^a/i) {
1583				if ($num) {
1584					while ($ix < $num) {
1585						if (!defined $hunk[$ix]{USE}) {
1586							$hunk[$ix]{USE} = 1;
1587						}
1588						$ix++;
1589					}
1590				} else {
1591					$head->{USE} = 1;
1592					$ix++;
1593				}
1594				next;
1595			}
1596			elsif ($line =~ /^g(.*)/) {
1597				my $response = $1;
1598				unless ($other =~ /g/) {
1599					error_msg __("No other hunks to goto\n");
1600					next;
1601				}
1602				my $no = $ix > 10 ? $ix - 10 : 0;
1603				while ($response eq '') {
1604					$no = display_hunks(\@hunk, $no);
1605					if ($no < $num) {
1606						print __("go to which hunk (<ret> to see more)? ");
1607					} else {
1608						print __("go to which hunk? ");
1609					}
1610					$response = <STDIN>;
1611					if (!defined $response) {
1612						$response = '';
1613					}
1614					chomp $response;
1615				}
1616				if ($response !~ /^\s*\d+\s*$/) {
1617					error_msg sprintf(__("Invalid number: '%s'\n"),
1618							     $response);
1619				} elsif (0 < $response && $response <= $num) {
1620					$ix = $response - 1;
1621				} else {
1622					error_msg sprintf(__n("Sorry, only %d hunk available.\n",
1623							      "Sorry, only %d hunks available.\n", $num), $num);
1624				}
1625				next;
1626			}
1627			elsif ($line =~ /^d/i) {
1628				if ($num) {
1629					while ($ix < $num) {
1630						if (!defined $hunk[$ix]{USE}) {
1631							$hunk[$ix]{USE} = 0;
1632						}
1633						$ix++;
1634					}
1635				} else {
1636					$head->{USE} = 0;
1637					$ix++;
1638				}
1639				next;
1640			}
1641			elsif ($line =~ /^q/i) {
1642				if ($num) {
1643					for ($i = 0; $i < $num; $i++) {
1644						if (!defined $hunk[$i]{USE}) {
1645							$hunk[$i]{USE} = 0;
1646						}
1647					}
1648				} elsif (!defined $head->{USE}) {
1649					$head->{USE} = 0;
1650				}
1651				$quit = 1;
1652				last;
1653			}
1654			elsif ($line =~ m|^/(.*)|) {
1655				my $regex = $1;
1656				unless ($other =~ m|/|) {
1657					error_msg __("No other hunks to search\n");
1658					next;
1659				}
1660				if ($regex eq "") {
1661					print colored $prompt_color, __("search for regex? ");
1662					$regex = <STDIN>;
1663					if (defined $regex) {
1664						chomp $regex;
1665					}
1666				}
1667				my $search_string;
1668				eval {
1669					$search_string = qr{$regex}m;
1670				};
1671				if ($@) {
1672					my ($err,$exp) = ($@, $1);
1673					$err =~ s/ at .*git-add--interactive line \d+, <STDIN> line \d+.*$//;
1674					error_msg sprintf(__("Malformed search regexp %s: %s\n"), $exp, $err);
1675					next;
1676				}
1677				my $iy = $ix;
1678				while (1) {
1679					my $text = join ("", @{$hunk[$iy]{TEXT}});
1680					last if ($text =~ $search_string);
1681					$iy++;
1682					$iy = 0 if ($iy >= $num);
1683					if ($ix == $iy) {
1684						error_msg __("No hunk matches the given pattern\n");
1685						last;
1686					}
1687				}
1688				$ix = $iy;
1689				next;
1690			}
1691			elsif ($line =~ /^K/) {
1692				if ($other =~ /K/) {
1693					$ix--;
1694				}
1695				else {
1696					error_msg __("No previous hunk\n");
1697				}
1698				next;
1699			}
1700			elsif ($line =~ /^J/) {
1701				if ($other =~ /J/) {
1702					$ix++;
1703				}
1704				else {
1705					error_msg __("No next hunk\n");
1706				}
1707				next;
1708			}
1709			elsif ($line =~ /^k/) {
1710				if ($other =~ /k/) {
1711					while (1) {
1712						$ix--;
1713						last if (!$ix ||
1714							 !defined $hunk[$ix]{USE});
1715					}
1716				}
1717				else {
1718					error_msg __("No previous hunk\n");
1719				}
1720				next;
1721			}
1722			elsif ($line =~ /^j/) {
1723				if ($other !~ /j/) {
1724					error_msg __("No next hunk\n");
1725					next;
1726				}
1727			}
1728			elsif ($line =~ /^s/) {
1729				unless ($other =~ /s/) {
1730					error_msg __("Sorry, cannot split this hunk\n");
1731					next;
1732				}
1733				my @split = split_hunk($hunk[$ix]{TEXT}, $hunk[$ix]{DISPLAY});
1734				if (1 < @split) {
1735					print colored $header_color, sprintf(
1736						__n("Split into %d hunk.\n",
1737						    "Split into %d hunks.\n",
1738						    scalar(@split)), scalar(@split));
1739				}
1740				splice (@hunk, $ix, 1, @split);
1741				$num = scalar @hunk;
1742				next;
1743			}
1744			elsif ($line =~ /^e/) {
1745				unless ($other =~ /e/) {
1746					error_msg __("Sorry, cannot edit this hunk\n");
1747					next;
1748				}
1749				my $newhunk = edit_hunk_loop($head, \@hunk, $ix);
1750				if (defined $newhunk) {
1751					splice @hunk, $ix, 1, $newhunk;
1752				}
1753			}
1754			else {
1755				help_patch_cmd($other);
1756				next;
1757			}
1758			# soft increment
1759			while (1) {
1760				$ix++;
1761				last if ($ix >= $num ||
1762					 !defined $hunk[$ix]{USE});
1763			}
1764		}
1765	}
1766
1767	@hunk = coalesce_overlapping_hunks(@hunk) if ($num);
1768
1769	my $n_lofs = 0;
1770	my @result = ();
1771	for (@hunk) {
1772		if ($_->{USE}) {
1773			push @result, @{$_->{TEXT}};
1774		}
1775	}
1776
1777	if (@result or $head->{USE}) {
1778		my @patch = reassemble_patch($head->{TEXT}, @result);
1779		my $apply_routine = $patch_mode_flavour{APPLY};
1780		&$apply_routine(@patch);
1781		refresh();
1782	}
1783
1784	print "\n";
1785	return $quit;
1786}
1787
1788sub diff_cmd {
1789	my @mods = list_modified('index-only');
1790	@mods = grep { !($_->{BINARY}) } @mods;
1791	return if (!@mods);
1792	my (@them) = list_and_choose({ PROMPT => __('Review diff'),
1793				     IMMEDIATE => 1,
1794				     HEADER => $status_head, },
1795				   @mods);
1796	return if (!@them);
1797	my $reference = (is_initial_commit()) ? get_empty_tree() : 'HEAD';
1798	system(qw(git diff -p --cached), $reference, '--',
1799		map { $_->{VALUE} } @them);
1800}
1801
1802sub quit_cmd {
1803	print __("Bye.\n");
1804	exit(0);
1805}
1806
1807sub help_cmd {
1808# TRANSLATORS: please do not translate the command names
1809# 'status', 'update', 'revert', etc.
1810	print colored $help_color, __ <<'EOF' ;
1811status        - show paths with changes
1812update        - add working tree state to the staged set of changes
1813revert        - revert staged set of changes back to the HEAD version
1814patch         - pick hunks and update selectively
1815diff          - view diff between HEAD and index
1816add untracked - add contents of untracked files to the staged set of changes
1817EOF
1818}
1819
1820sub process_args {
1821	return unless @ARGV;
1822	my $arg = shift @ARGV;
1823	if ($arg =~ /--patch(?:=(.*))?/) {
1824		if (defined $1) {
1825			if ($1 eq 'reset') {
1826				$patch_mode = 'reset_head';
1827				$patch_mode_revision = 'HEAD';
1828				$arg = shift @ARGV or die __("missing --");
1829				if ($arg ne '--') {
1830					$patch_mode_revision = $arg;
1831
1832					# NEEDSWORK: Instead of comparing to the literal "HEAD",
1833					# compare the commit objects instead so that other ways of
1834					# saying the same thing (such as "@") are also handled
1835					# appropriately.
1836					#
1837					# This applies to the cases below too.
1838					$patch_mode = ($arg eq 'HEAD' ?
1839						       'reset_head' : 'reset_nothead');
1840					$arg = shift @ARGV or die __("missing --");
1841				}
1842			} elsif ($1 eq 'checkout') {
1843				$arg = shift @ARGV or die __("missing --");
1844				if ($arg eq '--') {
1845					$patch_mode = 'checkout_index';
1846				} else {
1847					$patch_mode_revision = $arg;
1848					$patch_mode = ($arg eq 'HEAD' ?
1849						       'checkout_head' : 'checkout_nothead');
1850					$arg = shift @ARGV or die __("missing --");
1851				}
1852			} elsif ($1 eq 'worktree') {
1853				$arg = shift @ARGV or die __("missing --");
1854				if ($arg eq '--') {
1855					$patch_mode = 'checkout_index';
1856				} else {
1857					$patch_mode_revision = $arg;
1858					$patch_mode = ($arg eq 'HEAD' ?
1859						       'worktree_head' : 'worktree_nothead');
1860					$arg = shift @ARGV or die __("missing --");
1861				}
1862			} elsif ($1 eq 'stage' or $1 eq 'stash') {
1863				$patch_mode = $1;
1864				$arg = shift @ARGV or die __("missing --");
1865			} else {
1866				die sprintf(__("unknown --patch mode: %s"), $1);
1867			}
1868		} else {
1869			$patch_mode = 'stage';
1870			$arg = shift @ARGV or die __("missing --");
1871		}
1872		die sprintf(__("invalid argument %s, expecting --"),
1873			       $arg) unless $arg eq "--";
1874		%patch_mode_flavour = %{$patch_modes{$patch_mode}};
1875		$patch_mode_only = 1;
1876	}
1877	elsif ($arg ne "--") {
1878		die sprintf(__("invalid argument %s, expecting --"), $arg);
1879	}
1880}
1881
1882sub main_loop {
1883	my @cmd = ([ 'status', \&status_cmd, ],
1884		   [ 'update', \&update_cmd, ],
1885		   [ 'revert', \&revert_cmd, ],
1886		   [ 'add untracked', \&add_untracked_cmd, ],
1887		   [ 'patch', \&patch_update_cmd, ],
1888		   [ 'diff', \&diff_cmd, ],
1889		   [ 'quit', \&quit_cmd, ],
1890		   [ 'help', \&help_cmd, ],
1891	);
1892	while (1) {
1893		my ($it) = list_and_choose({ PROMPT => __('What now'),
1894					     SINGLETON => 1,
1895					     LIST_FLAT => 4,
1896					     HEADER => __('*** Commands ***'),
1897					     ON_EOF => \&quit_cmd,
1898					     IMMEDIATE => 1 }, @cmd);
1899		if ($it) {
1900			eval {
1901				$it->[1]->();
1902			};
1903			if ($@) {
1904				print "$@";
1905			}
1906		}
1907	}
1908}
1909
1910process_args();
1911refresh();
1912if ($patch_mode_only) {
1913	patch_update_cmd();
1914}
1915else {
1916	status_cmd();
1917	main_loop();
1918}
1919