Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Sun, 30 Sep 2007 06:32:36 +0000 (23:32 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 30 Sep 2007 06:32:36 +0000 (23:32 -0700)
* maint:
  git-remote: exit with non-zero status after detecting errors.
  rebase -i: squash should retain the authorship of the _first_ commit
  git-add--interactive: Improve behavior on bogus input
  git-add--interactive: Allow Ctrl-D to exit

1  2 
Documentation/git-rebase.txt
git-rebase--interactive.sh
git-remote.perl
t/t3404-rebase-interactive.sh

@@@ -8,9 -8,8 +8,9 @@@ git-rebase - Forward-port local commit
  SYNOPSIS
  --------
  [verse]
 -'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] [-C<n>]
 -      [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
 +'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
 +      [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
 +      [--onto <newbase>] <upstream> [<branch>]
  'git-rebase' --continue | --skip | --abort
  
  DESCRIPTION
@@@ -210,10 -209,6 +210,10 @@@ OPTION
        context exist they all must match.  By default no context is
        ever ignored.
  
 +--whitespace=<nowarn|warn|error|error-all|strip>::
 +      This flag is passed to the `git-apply` program
 +      (see gitlink:git-apply[1]) that applies the patch.
 +
  -i, \--interactive::
        Make a list of the commits which are about to be rebased.  Let the
        user edit that list before rebasing.  This mode can also be used to
@@@ -298,7 -293,7 +298,7 @@@ rebasing
  If you want to fold two or more commits into one, replace the command
  "pick" with "squash" for the second and subsequent commit.  If the
  commits had different authors, it will attribute the squashed commit to
- the author of the last commit.
+ the author of the first commit.
  
  In both cases, or when a "pick" does not succeed (because of merge
  errors), the loop will stop to let you fix things, and you can continue
@@@ -36,14 -36,14 +36,14 @@@ warn () 
  output () {
        case "$VERBOSE" in
        '')
 -              "$@" > "$DOTEST"/output 2>&1
 +              output=$("$@" 2>&1 )
                status=$?
 -              test $status != 0 &&
 -                      cat "$DOTEST"/output
 +              test $status != 0 && printf "%s\n" "$output"
                return $status
 -      ;;
 +              ;;
        *)
                "$@"
 +              ;;
        esac
  }
  
@@@ -63,7 -63,6 +63,7 @@@ comment_for_reflog () 
        ''|rebase*)
                GIT_REFLOG_ACTION="rebase -i ($1)"
                export GIT_REFLOG_ACTION
 +              ;;
        esac
  }
  
@@@ -71,23 -70,22 +71,23 @@@ mark_action_done () 
        sed -e 1q < "$TODO" >> "$DONE"
        sed -e 1d < "$TODO" >> "$TODO".new
        mv -f "$TODO".new "$TODO"
 -      count=$(($(wc -l < "$DONE")))
 -      total=$(($count+$(wc -l < "$TODO")))
 +      count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l)))
 +      total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l)))
        printf "Rebasing (%d/%d)\r" $count $total
        test -z "$VERBOSE" || echo
  }
  
  make_patch () {
 -      parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
 +      parent_sha1=$(git rev-parse --verify "$1"^) ||
 +              die "Cannot get patch for $1^"
        git diff "$parent_sha1".."$1" > "$DOTEST"/patch
 +      test -f "$DOTEST"/message ||
 +              git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
 +      test -f "$DOTEST"/author-script ||
 +              get_author_ident_from_commit "$1" > "$DOTEST"/author-script
  }
  
  die_with_patch () {
 -      test -f "$DOTEST"/message ||
 -              git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message
 -      test -f "$DOTEST"/author-script ||
 -              get_author_ident_from_commit $sha1 > "$DOTEST"/author-script
        make_patch "$1"
        die "$2"
  }
@@@ -97,18 -95,13 +97,18 @@@ die_abort () 
        die "$1"
  }
  
 +has_action () {
 +      grep -vqe '^$' -e '^#' "$1"
 +}
 +
  pick_one () {
        no_ff=
        case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
        test -d "$REWRITTEN" &&
                pick_one_preserving_merges "$@" && return
 -      parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
 +      parent_sha1=$(git rev-parse --verify $sha1^) ||
 +              die "Could not get the parent of $sha1"
        current_sha1=$(git rev-parse --verify HEAD)
        if test $no_ff$current_sha1 = $parent_sha1; then
                output git reset --hard $sha1
@@@ -136,7 -129,7 +136,7 @@@ pick_one_preserving_merges () 
        fast_forward=t
        preserve=t
        new_parents=
 -      for p in $(git rev-list --parents -1 $sha1 | cut -d -f2-)
 +      for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
        do
                if test -f "$REWRITTEN"/$p
                then
                                ;; # do nothing; that parent is already there
                        *)
                                new_parents="$new_parents $new_p"
 +                              ;;
                        esac
                fi
        done
        case $fast_forward in
        t)
                output warn "Fast forward to $sha1"
 -              test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
 +              test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1
                ;;
        f)
                test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
  
 -              first_parent=$(expr "$new_parents" : " \([^ ]*\)")
 +              first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
                # detach HEAD to current parent
                output git checkout $first_parent 2> /dev/null ||
                        die "Cannot move HEAD to $first_parent"
  
                echo $sha1 > "$DOTEST"/current-commit
                case "$new_parents" in
 -              \ *\ *)
 +              ' '*' '*)
                        # redo merge
                        author_script=$(get_author_ident_from_commit $sha1)
                        eval "$author_script"
 -                      msg="$(git cat-file commit $sha1 | \
 -                              sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
 +                      msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
                        # NEEDSWORK: give rerere a chance
 -                      if ! output git merge $STRATEGY -m "$msg" $new_parents
 +                      if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
 +                              GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
 +                              GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
 +                              output git merge $STRATEGY -m "$msg" \
 +                                      $new_parents
                        then
 -                              echo "$msg" > "$GIT_DIR"/MERGE_MSG
 +                              printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
                                die Error redoing merge $sha1
                        fi
                        ;;
                *)
                        output git cherry-pick $STRATEGY "$@" ||
                                die_with_patch $sha1 "Could not pick $sha1"
 +                      ;;
                esac
 +              ;;
        esac
  }
  
@@@ -225,28 -212,27 +225,28 @@@ peek_next_command () 
  }
  
  do_next () {
 -      test -f "$DOTEST"/message && rm "$DOTEST"/message
 -      test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script
 +      rm -f "$DOTEST"/message "$DOTEST"/author-script \
 +              "$DOTEST"/amend || exit
        read command sha1 rest < "$TODO"
        case "$command" in
 -      \#|'')
 +      '#'*|'')
                mark_action_done
                ;;
 -      pick)
 +      pick|p)
                comment_for_reflog pick
  
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
                ;;
 -      edit)
 +      edit|e)
                comment_for_reflog edit
  
                mark_action_done
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
                make_patch $sha1
 +              : > "$DOTEST"/amend
                warn
                warn "You can amend the commit now, with"
                warn
                warn
                exit 0
                ;;
 -      squash)
 +      squash|s)
                comment_for_reflog squash
  
 -              test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
 +              has_action "$DONE" ||
                        die "Cannot 'squash' without a previous commit"
  
                mark_action_done
                make_squash_message $sha1 > "$MSG"
                case "$(peek_next_command)" in
 -              squash)
 +              squash|s)
                        EDIT_COMMIT=
                        USE_OUTPUT=output
                        cp "$MSG" "$SQUASH_MSG"
 -              ;;
 +                      ;;
                *)
                        EDIT_COMMIT=-e
                        USE_OUTPUT=
 -                      test -f "$SQUASH_MSG" && rm "$SQUASH_MSG"
 +                      rm -f "$SQUASH_MSG" || exit
 +                      ;;
                esac
  
                failed=f
+               author_script=$(get_author_ident_from_commit HEAD)
                output git reset --soft HEAD^
                pick_one -n $sha1 || failed=t
-               author_script=$(get_author_ident_from_commit $sha1)
                echo "$author_script" > "$DOTEST"/author-script
                case $failed in
                f)
                        # This is like --amend, but with a different message
                        eval "$author_script"
 -                      export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
 +                      GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
 +                      GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
 +                      GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
                        $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT
                        ;;
                t)
                        warn
                        warn "Could not apply $sha1... $rest"
                        die_with_patch $sha1 ""
 +                      ;;
                esac
                ;;
        *)
                warn "Unknown command: $command $sha1 $rest"
                die_with_patch $sha1 "Please fix this in the file $TODO."
 +              ;;
        esac
        test -s "$TODO" && return
  
@@@ -349,9 -330,7 +349,9 @@@ d
                git update-index --refresh &&
                git diff-files --quiet &&
                ! git diff-index --cached --quiet HEAD &&
 -              . "$DOTEST"/author-script &&
 +              . "$DOTEST"/author-script && {
 +                      test ! -f "$DOTEST"/amend || git reset --soft HEAD^
 +              } &&
                export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE &&
                git commit -F "$DOTEST"/message -e
  
  
                require_clean_work_tree
  
 -              mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
                if test ! -z "$2"
                then
                        output git show-ref --verify --quiet "refs/heads/$2" ||
                HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
                UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
  
 +              mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
 +
                test -z "$ONTO" && ONTO=$UPSTREAM
  
                : > "$DOTEST"/interactive || die "Could not mark as interactive"
@@@ -490,18 -468,17 +490,18 @@@ EO
                        $UPSTREAM...$HEAD | \
                        sed -n "s/^>/pick /p" >> "$TODO"
  
 -              test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
 +              has_action "$TODO" ||
                        die_abort "Nothing to do"
  
                cp "$TODO" "$TODO".backup
                git_editor "$TODO" ||
                        die "Could not execute editor"
  
 -              test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
 +              has_action "$TODO" ||
                        die_abort "Nothing to do"
  
                output git checkout $ONTO && do_rest
 +              ;;
        esac
        shift
  done
diff --combined git-remote.perl
@@@ -218,7 -218,7 +218,7 @@@ sub prune_remote 
        my ($name, $ls_remote) = @_;
        if (!exists $remote->{$name}) {
                print STDERR "No such remote $name\n";
-               return;
+               return 1;
        }
        my $info = $remote->{$name};
        update_ls_remote($ls_remote, $info);
                my @v = $git->command(qw(rev-parse --verify), "$prefix/$to_prune");
                $git->command(qw(update-ref -d), "$prefix/$to_prune", $v[0]);
        }
+       return 0;
  }
  
  sub show_remote {
        my ($name, $ls_remote) = @_;
        if (!exists $remote->{$name}) {
                print STDERR "No such remote $name\n";
-               return;
+               return 1;
        }
        my $info = $remote->{$name};
        update_ls_remote($ls_remote, $info);
                print "  Local branch(es) pushed with 'git push'\n";
                print "    @pushed\n";
        }
+       return 0;
  }
  
  sub add_remote {
  
        for (@$track) {
                $git->command('config', '--add', "remote.$name.fetch",
 -                            "+refs/heads/$_:refs/remotes/$name/$_");
 +                              $opts->{'mirror'} ?
 +                              "+refs/$_:refs/$_" :
 +                              "+refs/heads/$_:refs/remotes/$name/$_");
        }
        if ($opts->{'fetch'}) {
                $git->command('fetch', $name);
@@@ -316,34 -316,6 +318,34 @@@ sub update_remote 
        }
  }
  
 +sub rm_remote {
 +      my ($name) = @_;
 +      if (!exists $remote->{$name}) {
 +              print STDERR "No such remote $name\n";
 +              return;
 +      }
 +
 +      $git->command('config', '--remove-section', "remote.$name");
 +
 +      eval {
 +          my @trackers = $git->command('config', '--get-regexp',
 +                      'branch.*.remote', $name);
 +              for (@trackers) {
 +                      /^branch\.(.*)?\.remote/;
 +                      $git->config('--unset', "branch.$1.remote");
 +                      $git->config('--unset', "branch.$1.merge");
 +              }
 +      };
 +
 +
 +      my @refs = $git->command('for-each-ref',
 +              '--format=%(refname) %(objectname)', "refs/remotes/$name");
 +      for (@refs) {
 +              ($ref, $object) = split;
 +              $git->command(qw(update-ref -d), $ref, $object);
 +      }
 +}
 +
  sub add_usage {
        print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
        exit(1);
@@@ -381,9 -353,11 +383,11 @@@ elsif ($ARGV[0] eq 'show') 
                print STDERR "Usage: git remote show <remote>\n";
                exit(1);
        }
+       my $status = 0;
        for (; $i < @ARGV; $i++) {
-               show_remote($ARGV[$i], $ls_remote);
+               $status |= show_remote($ARGV[$i], $ls_remote);
        }
+       exit($status);
  }
  elsif ($ARGV[0] eq 'update') {
        if (@ARGV <= 1) {
@@@ -409,9 -383,11 +413,11 @@@ elsif ($ARGV[0] eq 'prune') 
                print STDERR "Usage: git remote prune <remote>\n";
                exit(1);
        }
+       my $status = 0;
        for (; $i < @ARGV; $i++) {
-               prune_remote($ARGV[$i], $ls_remote);
+               $status |= prune_remote($ARGV[$i], $ls_remote);
        }
+         exit($status);
  }
  elsif ($ARGV[0] eq 'add') {
        my %opts = ();
                        shift @ARGV;
                        next;
                }
 +              if ($opt eq '--mirror') {
 +                      $opts{'mirror'} = 1;
 +                      next;
 +              }
                add_usage();
        }
        if (@ARGV != 3) {
        }
        add_remote($ARGV[1], $ARGV[2], \%opts);
  }
 +elsif ($ARGV[0] eq 'rm') {
 +      if (@ARGV <= 1) {
 +              print STDERR "Usage: git remote rm <remote>\n";
 +              exit(1);
 +      }
 +      rm_remote($ARGV[1]);
 +}
  else {
        print STDERR "Usage: git remote\n";
        print STDERR "       git remote add <name> <url>\n";
 +      print STDERR "       git remote rm <name>\n";
        print STDERR "       git remote show <name>\n";
        print STDERR "       git remote prune <name>\n";
        print STDERR "       git remote update [group]\n";
@@@ -80,7 -80,7 +80,7 @@@ cat "$1".tm
  action=pick
  for line in $FAKE_LINES; do
        case $line in
 -      squash)
 +      squash|edit)
                action="$line";;
        *)
                echo sed -n "${line}s/^pick/$action/p"
@@@ -180,7 -180,7 +180,7 @@@ test_expect_success 'squash' 
  '
  
  test_expect_success 'retain authorship when squashing' '
-       git show HEAD | grep "^Author: Nitfol"
+       git show HEAD | grep "^Author: Twerp Snog"
  '
  
  test_expect_success 'preserve merges with -p' '
@@@ -297,16 -297,4 +297,16 @@@ test_expect_success 'ignore patch if i
        test $HEAD = $(git rev-parse HEAD^)
  '
  
 +test_expect_success '--continue tries to commit, even for "edit"' '
 +      parent=$(git rev-parse HEAD^) &&
 +      test_tick &&
 +      FAKE_LINES="edit 1" git rebase -i HEAD^ &&
 +      echo edited > file7 &&
 +      git add file7 &&
 +      FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
 +      test edited = $(git show HEAD:file7) &&
 +      git show HEAD | grep chouette &&
 +      test $parent = $(git rev-parse HEAD^)
 +'
 +
  test_done