pull --rebase: exit early when the working directory is dirty
[gitweb.git] / git-add--interactive.perl
index 0cdd80073bcbe0d9ffea30de85f9dda57f552e7f..903953e68e98535e754b5a5dd6ba22eb6074ef20 100755 (executable)
@@ -3,42 +3,29 @@
 use strict;
 use Git;
 
-# Prompt colors:
-my ($prompt_color, $header_color, $help_color, $normal_color);
-# Diff colors:
-my ($new_color, $old_color, $fraginfo_color, $metainfo_color, $whitespace_color);
-
-my ($use_color, $diff_use_color);
 my $repo = Git->repository();
 
-$use_color = $repo->get_colorbool('color.interactive');
-
-if ($use_color) {
-       # Set interactive colors:
+my $menu_use_color = $repo->get_colorbool('color.interactive');
+my ($prompt_color, $header_color, $help_color) =
+       $menu_use_color ? (
+               $repo->get_color('color.interactive.prompt', 'bold blue'),
+               $repo->get_color('color.interactive.header', 'bold'),
+               $repo->get_color('color.interactive.help', 'red bold'),
+       ) : ();
 
-       # Grab the 3 main colors in git color string format, with sane
-       # (visible) defaults:
-       $prompt_color = $repo->get_color("color.interactive.prompt", "bold blue");
-       $header_color = $repo->get_color("color.interactive.header", "bold");
-       $help_color = $repo->get_color("color.interactive.help", "red bold");
-       $normal_color = $repo->get_color("", "reset");
+my $diff_use_color = $repo->get_colorbool('color.diff');
+my ($fraginfo_color) =
+       $diff_use_color ? (
+               $repo->get_color('color.diff.frag', 'cyan'),
+       ) : ();
 
-       # Do we also set diff colors?
-       $diff_use_color = $repo->get_colorbool('color.diff');
-       if ($diff_use_color) {
-               $new_color = $repo->get_color("color.diff.new", "green");
-               $old_color = $repo->get_color("color.diff.old", "red");
-               $fraginfo_color = $repo->get_color("color.diff.frag", "cyan");
-               $metainfo_color = $repo->get_color("color.diff.meta", "bold");
-               $whitespace_color = $repo->get_color("color.diff.whitespace", "normal red");
-       }
-}
+my $normal_color = $repo->get_color("", "reset");
 
 sub colored {
        my $color = shift;
        my $string = join("", @_);
 
-       if ($use_color) {
+       if (defined $color) {
                # Put a color code at the beginning of each line, a reset at the end
                # color after newlines that are not at the end of the string
                $string =~ s/(\n+)(.)/$1$color$2/g;
@@ -95,6 +82,19 @@ sub list_untracked {
 my $status_fmt = '%12s %12s %s';
 my $status_head = sprintf($status_fmt, 'staged', 'unstaged', 'path');
 
+{
+       my $initial;
+       sub is_initial_commit {
+               $initial = system('git rev-parse HEAD -- >/dev/null 2>&1') != 0
+                       unless defined $initial;
+               return $initial;
+       }
+}
+
+sub get_empty_tree {
+       return '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
+}
+
 # Returns list of hashes, contents of each of which are:
 # VALUE:       pathname
 # BINARY:      is a binary path
@@ -116,8 +116,10 @@ sub list_modified {
                return if (!@tracked);
        }
 
+       my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
        for (run_cmd_pipe(qw(git diff-index --cached
-                            --numstat --summary HEAD --), @tracked)) {
+                            --numstat --summary), $reference,
+                            '--', @tracked)) {
                if (($add, $del, $file) =
                    /^([-\d]+)  ([-\d]+)        (.*)/) {
                        my ($change, $bin);
@@ -304,7 +306,7 @@ sub highlight_prefix {
                return "$prefix$remainder";
        }
 
-       if (!$use_color) {
+       if (!$menu_use_color) {
                return "[$prefix]$remainder";
        }
 
@@ -489,21 +491,27 @@ sub revert_cmd {
                                       HEADER => $status_head, },
                                     list_modified());
        if (@update) {
-               my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
-                                        map { $_->{VALUE} } @update);
-               my $fh;
-               open $fh, '| git update-index --index-info'
-                   or die;
-               for (@lines) {
-                       print $fh $_;
+               if (is_initial_commit()) {
+                       system(qw(git rm --cached),
+                               map { $_->{VALUE} } @update);
                }
-               close($fh);
-               for (@update) {
-                       if ($_->{INDEX_ADDDEL} &&
-                           $_->{INDEX_ADDDEL} eq 'create') {
-                               system(qw(git update-index --force-remove --),
-                                      $_->{VALUE});
-                               print "note: $_->{VALUE} is untracked now.\n";
+               else {
+                       my @lines = run_cmd_pipe(qw(git ls-tree HEAD --),
+                                                map { $_->{VALUE} } @update);
+                       my $fh;
+                       open $fh, '| git update-index --index-info'
+                           or die;
+                       for (@lines) {
+                               print $fh $_;
+                       }
+                       close($fh);
+                       for (@update) {
+                               if ($_->{INDEX_ADDDEL} &&
+                                   $_->{INDEX_ADDDEL} eq 'create') {
+                                       system(qw(git update-index --force-remove --),
+                                              $_->{VALUE});
+                                       print "note: $_->{VALUE} is untracked now.\n";
+                               }
                        }
                }
                refresh();
@@ -542,6 +550,21 @@ sub parse_diff {
        return @hunk;
 }
 
+sub parse_diff_header {
+       my $src = shift;
+
+       my $head = { TEXT => [], DISPLAY => [] };
+       my $mode = { TEXT => [], DISPLAY => [] };
+
+       for (my $i = 0; $i < @{$src->{TEXT}}; $i++) {
+               my $dest = $src->{TEXT}->[$i] =~ /^(old|new) mode (\d+)$/ ?
+                       $mode : $head;
+               push @{$dest->{TEXT}}, $src->{TEXT}->[$i];
+               push @{$dest->{DISPLAY}}, $src->{DISPLAY}->[$i];
+       }
+       return ($head, $mode);
+}
+
 sub hunk_splittable {
        my ($text) = @_;
 
@@ -787,9 +810,40 @@ sub patch_update_file {
        my ($ix, $num);
        my $path = shift;
        my ($head, @hunk) = parse_diff($path);
+       ($head, my $mode) = parse_diff_header($head);
        for (@{$head->{DISPLAY}}) {
                print;
        }
+
+       if (@{$mode->{TEXT}}) {
+               while (1) {
+                       print @{$mode->{DISPLAY}};
+                       print colored $prompt_color,
+                               "Stage mode change [y/n/a/d/?]? ";
+                       my $line = <STDIN>;
+                       if ($line =~ /^y/i) {
+                               $mode->{USE} = 1;
+                               last;
+                       }
+                       elsif ($line =~ /^n/i) {
+                               $mode->{USE} = 0;
+                               last;
+                       }
+                       elsif ($line =~ /^a/i) {
+                               $_->{USE} = 1 foreach ($mode, @hunk);
+                               last;
+                       }
+                       elsif ($line =~ /^d/i) {
+                               $_->{USE} = 0 foreach ($mode, @hunk);
+                               last;
+                       }
+                       else {
+                               help_patch_cmd('');
+                               next;
+                       }
+               }
+       }
+
        $num = scalar @hunk;
        $ix = 0;
 
@@ -912,6 +966,9 @@ sub patch_update_file {
 
        my $n_lofs = 0;
        my @result = ();
+       if ($mode->{USE}) {
+               push @result, @{$mode->{TEXT}};
+       }
        for (@hunk) {
                my $text = $_->{TEXT};
                my ($o_ofs, $o_cnt, $n_ofs, $n_cnt) =
@@ -969,7 +1026,9 @@ sub diff_cmd {
                                     HEADER => $status_head, },
                                   @mods);
        return if (!@them);
-       system(qw(git diff -p --cached HEAD --), map { $_->{VALUE} } @them);
+       my $reference = is_initial_commit() ? get_empty_tree() : 'HEAD';
+       system(qw(git diff -p --cached), $reference, '--',
+               map { $_->{VALUE} } @them);
 }
 
 sub quit_cmd {