Sync with 'maint'
authorJunio C Hamano <gitster@pobox.com>
Fri, 12 Apr 2013 20:54:01 +0000 (13:54 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 12 Apr 2013 20:54:01 +0000 (13:54 -0700)
* maint:
Correct common spelling mistakes in comments and tests
kwset: fix spelling in comments
precompose-utf8: fix spelling of "want" in error message
compat/nedmalloc: fix spelling in comments
compat/regex: fix spelling and grammar in comments
obstack: fix spelling of similar
contrib/subtree: fix spelling of accidentally
git-remote-mediawiki: spelling fixes
doc: various spelling fixes
fast-export: fix argument name in error messages
Documentation: distinguish between ref and offset deltas in pack-format
i18n: make the translation of -u advice in one go

12 files changed:
1  2 
Documentation/revisions.txt
builtin/apply.c
builtin/fast-export.c
commit.c
commit.h
perl/Git.pm
sequencer.c
t/t4014-format-patch.sh
t/t4124-apply-ws-rule.sh
t/t6030-bisect-porcelain.sh
transport.h
wt-status.c
index 1707d451b604e05de69d8a3d9f8dc326079d4742,b0f72206a09ffe6a703d923756b86c4964c1ad88..8855b1a0ac99012fcd98fcb3272b39e465d84626
@@@ -55,7 -55,7 +55,7 @@@ when you run `git cherry-pick`
  +
  Note that any of the 'refs/*' cases above may come either from
  the '$GIT_DIR/refs' directory or from the '$GIT_DIR/packed-refs' file.
- While the ref name encoding is unspecified, UTF-8 is prefered as
+ While the ref name encoding is unspecified, UTF-8 is preferred as
  some output processing may assume ref names in UTF-8.
  
  '<refname>@\{<date>\}', e.g. 'master@\{yesterday\}', 'HEAD@\{5 minutes ago\}'::
    object of that type is found or the object cannot be
    dereferenced anymore (in which case, barf).  '<rev>{caret}0'
    is a short-hand for '<rev>{caret}\{commit\}'.
 ++
 +'rev{caret}\{object\}' can be used to make sure 'rev' names an
 +object that exists, without requiring 'rev' to be a tag, and
 +without dereferencing 'rev'; because a tag is already an object,
 +it does not have to be dereferenced even once to get to an object.
  
  '<rev>{caret}\{\}', e.g. 'v0.99.8{caret}\{\}'::
    A suffix '{caret}' followed by an empty brace pair
diff --combined builtin/apply.c
index 5b882d01f6622b1be4f494d2f4a3cbda5adeec08,f6a3c97dd5903a8b7d42383da370428067c3e14e..30eefc3c7b390800e9452a06da18208d2fef4f11
@@@ -1921,7 -1921,7 +1921,7 @@@ static int parse_binary(char *buffer, u
  }
  
  /*
-  * Read the patch text in "buffer" taht extends for "size" bytes; stop
+  * Read the patch text in "buffer" that extends for "size" bytes; stop
   * reading after seeing a single patch (i.e. changes to a single file).
   * Create fragments (i.e. patch hunks) and hang them to the given patch.
   * Return the number of bytes consumed, so that the caller can call us
@@@ -2117,10 -2117,10 +2117,10 @@@ static void update_pre_post_images(stru
  
        /*
         * Adjust the common context lines in postimage. This can be
 -       * done in-place when we are just doing whitespace fixing,
 -       * which does not make the string grow, but needs a new buffer
 -       * when ignoring whitespace causes the update, since in this case
 -       * we could have e.g. tabs converted to multiple spaces.
 +       * done in-place when we are shrinking it with whitespace
 +       * fixing, but needs a new buffer when ignoring whitespace or
 +       * expanding leading tabs to spaces.
 +       *
         * We trust the caller to tell us if the update can be done
         * in place (postlen==0) or not.
         */
@@@ -2185,7 -2185,7 +2185,7 @@@ static int match_fragment(struct image 
        int i;
        char *fixed_buf, *buf, *orig, *target;
        struct strbuf fixed;
 -      size_t fixed_len;
 +      size_t fixed_len, postlen;
        int preimage_limit;
  
        if (preimage->nr + try_lno <= img->nr) {
        strbuf_init(&fixed, preimage->len + 1);
        orig = preimage->buf;
        target = img->buf + try;
 +      postlen = 0;
        for (i = 0; i < preimage_limit; i++) {
                size_t oldlen = preimage->line[i].len;
                size_t tgtlen = img->line[try_lno + i].len;
                match = (tgtfix.len == fixed.len - fixstart &&
                         !memcmp(tgtfix.buf, fixed.buf + fixstart,
                                             fixed.len - fixstart));
 +              postlen += tgtfix.len;
  
                strbuf_release(&tgtfix);
                if (!match)
         * hunk match.  Update the context lines in the postimage.
         */
        fixed_buf = strbuf_detach(&fixed, &fixed_len);
 +      if (postlen < postimage->len)
 +              postlen = 0;
        update_pre_post_images(preimage, postimage,
 -                             fixed_buf, fixed_len, 0);
 +                             fixed_buf, fixed_len, postlen);
        return 1;
  
   unmatch_exit:
@@@ -3029,7 -3025,7 +3029,7 @@@ static struct patch *in_fn_table(const 
   *
   * The latter is needed to deal with a case where two paths A and B
   * are swapped by first renaming A to B and then renaming B to A;
-  * moving A to B should not be prevented due to presense of B as we
+  * moving A to B should not be prevented due to presence of B as we
   * will remove it in a later patch.
   */
  #define PATH_TO_BE_DELETED ((struct patch *) -2)
@@@ -3513,7 -3509,7 +3513,7 @@@ static int check_patch(struct patch *pa
         *
         * A patch to swap-rename between A and B would first rename A
         * to B and then rename B to A.  While applying the first one,
-        * the presense of B should not stop A from getting renamed to
+        * the presence of B should not stop A from getting renamed to
         * B; ask to_be_deleted() about the later rename.  Removal of
         * B and rename from A to B is handled the same way by asking
         * was_deleted().
diff --combined builtin/fast-export.c
index f44b76cb330250f969a50fd684465dc5c1cb4ecc,ad9d0c46e8e32471f7c2ede9620b66c0711a4de6..725c0a7dca70ae8cbb31f571e0284f5c31a61cff
@@@ -43,7 -43,7 +43,7 @@@ static int parse_opt_signed_tag_mode(co
        else if (!strcmp(arg, "strip"))
                signed_tag_mode = STRIP;
        else
-               return error("Unknown signed-tag mode: %s", arg);
+               return error("Unknown signed-tags mode: %s", arg);
        return 0;
  }
  
@@@ -113,13 -113,12 +113,13 @@@ static void show_progress(void
                printf("progress %d objects\n", counter);
  }
  
 -static void handle_object(const unsigned char *sha1)
 +static void export_blob(const unsigned char *sha1)
  {
        unsigned long size;
        enum object_type type;
        char *buf;
        struct object *object;
 +      int eaten;
  
        if (no_data)
                return;
        if (is_null_sha1(sha1))
                return;
  
 -      object = parse_object(sha1);
 -      if (!object)
 -              die ("Could not read blob %s", sha1_to_hex(sha1));
 -
 -      if (object->flags & SHOWN)
 +      object = lookup_object(sha1);
 +      if (object && object->flags & SHOWN)
                return;
  
        buf = read_sha1_file(sha1, &type, &size);
        if (!buf)
                die ("Could not read blob %s", sha1_to_hex(sha1));
 +      if (check_sha1_signature(sha1, buf, size, typename(type)) < 0)
 +              die("sha1 mismatch in blob %s", sha1_to_hex(sha1));
 +      object = parse_object_buffer(sha1, type, size, buf, &eaten);
 +      if (!object)
 +              die("Could not read blob %s", sha1_to_hex(sha1));
  
        mark_next_object(object);
  
        show_progress();
  
        object->flags |= SHOWN;
 -      free(buf);
 +      if (!eaten)
 +              free(buf);
  }
  
  static int depth_first(const void *a_, const void *b_)
@@@ -316,7 -312,7 +316,7 @@@ static void handle_commit(struct commi
        /* Export the referenced blobs, and remember the marks. */
        for (i = 0; i < diff_queued_diff.nr; i++)
                if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
 -                      handle_object(diff_queued_diff.queue[i]->two->sha1);
 +                      export_blob(diff_queued_diff.queue[i]->two->sha1);
  
        mark_next_object(&commit->object);
        if (!is_encoding_utf8(encoding))
@@@ -420,7 -416,7 +420,7 @@@ static void handle_tag(const char *name
                        switch(signed_tag_mode) {
                        case ABORT:
                                die ("Encountered signed tag %s; use "
-                                    "--signed-tag=<mode> to handle it.",
+                                    "--signed-tags=<mode> to handle it.",
                                     sha1_to_hex(tag->object.sha1));
                        case WARN:
                                warning ("Exporting signed tag %s",
@@@ -516,7 -512,7 +516,7 @@@ static void get_tags_and_duplicates(str
                                commit = (struct commit *)tag;
                                break;
                        case OBJ_BLOB:
 -                              handle_object(tag->object.sha1);
 +                              export_blob(tag->object.sha1);
                                continue;
                        default: /* OBJ_TAG (nested tags) is already handled */
                                warning("Tag points to object of unexpected type %s, skipping.",
@@@ -618,12 -614,9 +618,12 @@@ static void import_marks(char *input_fi
                        || *mark_end != ' ' || get_sha1(mark_end + 1, sha1))
                        die("corrupt mark line: %s", line);
  
 +              if (last_idnum < mark)
 +                      last_idnum = mark;
 +
                object = parse_object(sha1);
                if (!object)
 -                      die ("Could not read blob %s", sha1_to_hex(sha1));
 +                      continue;
  
                if (object->flags & SHOWN)
                        error("Object %s already has a mark", sha1_to_hex(sha1));
                        continue;
  
                mark_object(object, mark);
 -              if (last_idnum < mark)
 -                      last_idnum = mark;
  
                object->flags |= SHOWN;
        }
@@@ -646,7 -641,6 +646,7 @@@ int cmd_fast_export(int argc, const cha
        struct string_list extra_refs = STRING_LIST_INIT_NODUP;
        struct commit *commit;
        char *export_filename = NULL, *import_filename = NULL;
 +      uint32_t lastimportid;
        struct option options[] = {
                OPT_INTEGER(0, "progress", &progress,
                            N_("show progress after <n> objects")),
  
        if (import_filename)
                import_marks(import_filename);
 +      lastimportid = last_idnum;
  
        if (import_filename && revs.prune_data.nr)
                full_tree = 1;
  
        handle_tags_and_duplicates(&extra_refs);
  
 -      if (export_filename)
 +      if (export_filename && lastimportid != last_idnum)
                export_marks(export_filename);
  
        if (use_done_feature)
diff --combined commit.c
index 516a4ff7d21d5ebcfabda7b1b886eb10b05cebfc,1a41757ee377d804782ccfb4e27c54b607dcf91d..888e02ae2f65ab566555465e5d88d02bbe52420f
+++ b/commit.c
@@@ -463,23 -463,14 +463,23 @@@ static void clear_commit_marks_1(struc
        }
  }
  
 -void clear_commit_marks(struct commit *commit, unsigned int mark)
 +void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark)
  {
        struct commit_list *list = NULL;
 -      commit_list_insert(commit, &list);
 +
 +      while (nr--) {
 +              commit_list_insert(*commit, &list);
 +              commit++;
 +      }
        while (list)
                clear_commit_marks_1(&list, pop_commit(&list), mark);
  }
  
 +void clear_commit_marks(struct commit *commit, unsigned int mark)
 +{
 +      clear_commit_marks_many(1, &commit, mark);
 +}
 +
  void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
  {
        struct object *object;
@@@ -806,7 -797,8 +806,7 @@@ struct commit_list *get_merge_bases_man
        if (!result || !result->next) {
                if (cleanup) {
                        clear_commit_marks(one, all_flags);
 -                      for (i = 0; i < n; i++)
 -                              clear_commit_marks(twos[i], all_flags);
 +                      clear_commit_marks_many(n, twos, all_flags);
                }
                return result;
        }
        free_commit_list(result);
  
        clear_commit_marks(one, all_flags);
 -      for (i = 0; i < n; i++)
 -              clear_commit_marks(twos[i], all_flags);
 +      clear_commit_marks_many(n, twos, all_flags);
  
        cnt = remove_redundant(rslt, cnt);
        result = NULL;
@@@ -841,7 -834,7 +841,7 @@@ struct commit_list *get_merge_bases(str
  }
  
  /*
-  * Is "commit" a decendant of one of the elements on the "with_commit" list?
+  * Is "commit" a descendant of one of the elements on the "with_commit" list?
   */
  int is_descendant_of(struct commit *commit, struct commit_list *with_commit)
  {
  }
  
  /*
 - * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
 + * Is "commit" an ancestor of one of the "references"?
   */
 -int in_merge_bases(struct commit *commit, struct commit *reference)
 +int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
  {
        struct commit_list *bases;
 -      int ret = 0;
 +      int ret = 0, i;
  
 -      if (parse_commit(commit) || parse_commit(reference))
 +      if (parse_commit(commit))
                return ret;
 +      for (i = 0; i < nr_reference; i++)
 +              if (parse_commit(reference[i]))
 +                      return ret;
  
 -      bases = paint_down_to_common(commit, 1, &reference);
 +      bases = paint_down_to_common(commit, nr_reference, reference);
        if (commit->object.flags & PARENT2)
                ret = 1;
        clear_commit_marks(commit, all_flags);
 -      clear_commit_marks(reference, all_flags);
 +      clear_commit_marks_many(nr_reference, reference, all_flags);
        free_commit_list(bases);
        return ret;
  }
  
 +/*
 + * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
 + */
 +int in_merge_bases(struct commit *commit, struct commit *reference)
 +{
 +      return in_merge_bases_many(commit, 1, &reference);
 +}
 +
  struct commit_list *reduce_heads(struct commit_list *heads)
  {
        struct commit_list *p;
@@@ -1041,76 -1023,6 +1041,76 @@@ free_return
        free(buf);
  }
  
 +static struct {
 +      char result;
 +      const char *check;
 +} sigcheck_gpg_status[] = {
 +      { 'G', "\n[GNUPG:] GOODSIG " },
 +      { 'B', "\n[GNUPG:] BADSIG " },
 +      { 'U', "\n[GNUPG:] TRUST_NEVER" },
 +      { 'U', "\n[GNUPG:] TRUST_UNDEFINED" },
 +};
 +
 +static void parse_gpg_output(struct signature_check *sigc)
 +{
 +      const char *buf = sigc->gpg_status;
 +      int i;
 +
 +      /* Iterate over all search strings */
 +      for (i = 0; i < ARRAY_SIZE(sigcheck_gpg_status); i++) {
 +              const char *found, *next;
 +
 +              if (!prefixcmp(buf, sigcheck_gpg_status[i].check + 1)) {
 +                      /* At the very beginning of the buffer */
 +                      found = buf + strlen(sigcheck_gpg_status[i].check + 1);
 +              } else {
 +                      found = strstr(buf, sigcheck_gpg_status[i].check);
 +                      if (!found)
 +                              continue;
 +                      found += strlen(sigcheck_gpg_status[i].check);
 +              }
 +              sigc->result = sigcheck_gpg_status[i].result;
 +              /* The trust messages are not followed by key/signer information */
 +              if (sigc->result != 'U') {
 +                      sigc->key = xmemdupz(found, 16);
 +                      found += 17;
 +                      next = strchrnul(found, '\n');
 +                      sigc->signer = xmemdupz(found, next - found);
 +              }
 +      }
 +}
 +
 +void check_commit_signature(const struct commit* commit, struct signature_check *sigc)
 +{
 +      struct strbuf payload = STRBUF_INIT;
 +      struct strbuf signature = STRBUF_INIT;
 +      struct strbuf gpg_output = STRBUF_INIT;
 +      struct strbuf gpg_status = STRBUF_INIT;
 +      int status;
 +
 +      sigc->result = 'N';
 +
 +      if (parse_signed_commit(commit->object.sha1,
 +                              &payload, &signature) <= 0)
 +              goto out;
 +      status = verify_signed_buffer(payload.buf, payload.len,
 +                                    signature.buf, signature.len,
 +                                    &gpg_output, &gpg_status);
 +      if (status && !gpg_output.len)
 +              goto out;
 +      sigc->gpg_output = strbuf_detach(&gpg_output, NULL);
 +      sigc->gpg_status = strbuf_detach(&gpg_status, NULL);
 +      parse_gpg_output(sigc);
 +
 + out:
 +      strbuf_release(&gpg_status);
 +      strbuf_release(&gpg_output);
 +      strbuf_release(&payload);
 +      strbuf_release(&signature);
 +}
 +
 +
 +
  void append_merge_tag_headers(struct commit_list *parents,
                              struct commit_extra_header ***tail)
  {
diff --combined commit.h
index 87b4b6cc0c036d4b7d2ff67cf9d6e98c58f13793,252c7f871c1c90423ae11190ba98cea5b94422de..057ff8045efb2cc80f747ee15b2a308a6dead5e5
+++ b/commit.h
@@@ -5,7 -5,6 +5,7 @@@
  #include "tree.h"
  #include "strbuf.h"
  #include "decorate.h"
 +#include "gpg-interface.h"
  
  struct commit_list {
        struct commit *item;
@@@ -138,7 -137,6 +138,7 @@@ struct commit *pop_most_recent_commit(s
  struct commit *pop_commit(struct commit_list **stack);
  
  void clear_commit_marks(struct commit *commit, unsigned int mark);
 +void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
  void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
  
  /*
@@@ -166,7 -164,7 +166,7 @@@ extern struct commit_list *get_merge_ba
  extern struct commit_list *get_merge_bases_many(struct commit *one, int n, struct commit **twos, int cleanup);
  extern struct commit_list *get_octopus_merge_bases(struct commit_list *in);
  
- /* largest postive number a signed 32-bit integer can contain */
+ /* largest positive number a signed 32-bit integer can contain */
  #define INFINITE_DEPTH 0x7fffffff
  
  extern int register_shallow(const unsigned char *sha1);
@@@ -178,7 -176,6 +178,7 @@@ extern struct commit_list *get_shallow_
  
  int is_descendant_of(struct commit *, struct commit_list *);
  int in_merge_bases(struct commit *, struct commit *);
 +int in_merge_bases_many(struct commit *, int, struct commit **);
  
  extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
  extern int run_add_interactive(const char *revision, const char *patch_mode,
@@@ -233,13 -230,4 +233,13 @@@ extern void print_commit_list(struct co
                              const char *format_cur,
                              const char *format_last);
  
 +/*
 + * Check the signature of the given commit. The result of the check is stored
 + * in sig->check_result, 'G' for a good signature, 'U' for a good signature
 + * from an untrusted signer, 'B' for a bad signature and 'N' for no signature
 + * at all.  This may allocate memory for sig->gpg_output, sig->gpg_status,
 + * sig->signer and sig->key.
 + */
 +extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc);
 +
  #endif /* COMMIT_H */
diff --combined perl/Git.pm
index 96cac39a4c8ebc0ce4e111271429fdf68b03bf97,f207b47183a00fc02320a3785a8d4085b42259fe..650db90853c6ff2a769e981afd2954ffbb97469c
@@@ -60,7 -60,6 +60,7 @@@ require Exporter
                  version exec_path html_path hash_object git_cmd_try
                  remote_refs prompt
                  get_tz_offset
 +                credential credential_read credential_write
                  temp_acquire temp_release temp_reset temp_path);
  
  
@@@ -270,13 -269,13 +270,13 @@@ sub command 
  
        if (not defined wantarray) {
                # Nothing to pepper the possible exception with.
 -              _cmd_close($fh, $ctx);
 +              _cmd_close($ctx, $fh);
  
        } elsif (not wantarray) {
                local $/;
                my $text = <$fh>;
                try {
 -                      _cmd_close($fh, $ctx);
 +                      _cmd_close($ctx, $fh);
                } catch Git::Error::Command with {
                        # Pepper with the output:
                        my $E = shift;
                my @lines = <$fh>;
                defined and chomp for @lines;
                try {
 -                      _cmd_close($fh, $ctx);
 +                      _cmd_close($ctx, $fh);
                } catch Git::Error::Command with {
                        my $E = shift;
                        $E->{'-outputref'} = \@lines;
@@@ -316,7 -315,7 +316,7 @@@ sub command_oneline 
        my $line = <$fh>;
        defined $line and chomp $line;
        try {
 -              _cmd_close($fh, $ctx);
 +              _cmd_close($ctx, $fh);
        } catch Git::Error::Command with {
                # Pepper with the output:
                my $E = shift;
@@@ -384,7 -383,7 +384,7 @@@ have more complicated structure
  sub command_close_pipe {
        my ($self, $fh, $ctx) = _maybe_self(@_);
        $ctx ||= '<unknown>';
 -      _cmd_close($fh, $ctx);
 +      _cmd_close($ctx, $fh);
  }
  
  =item command_bidi_pipe ( COMMAND [, ARGUMENTS... ] )
@@@ -421,7 -420,7 +421,7 @@@ and it is the fourth value returned by 
  is:
  
        my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');
 -      print "000000000\n" $out;
 +      print $out "000000000\n";
        while (<$in>) { ... }
        $r->command_close_bidi_pipe($pid, $in, $out, $ctx);
  
@@@ -429,26 -428,23 +429,26 @@@ Note that you should not rely on whatev
  currently it is simply the command name but in future the context might
  have more complicated structure.
  
 +C<PIPE_IN> and C<PIPE_OUT> may be C<undef> if they have been closed prior to
 +calling this function.  This may be useful in a query-response type of
 +commands where caller first writes a query and later reads response, eg:
 +
 +      my ($pid, $in, $out, $ctx) = $r->command_bidi_pipe('cat-file --batch-check');
 +      print $out "000000000\n";
 +      close $out;
 +      while (<$in>) { ... }
 +      $r->command_close_bidi_pipe($pid, $in, undef, $ctx);
 +
 +This idiom may prevent potential dead locks caused by data sent to the output
 +pipe not being flushed and thus not reaching the executed command.
 +
  =cut
  
  sub command_close_bidi_pipe {
        local $?;
 -      my ($pid, $in, $out, $ctx) = @_;
 -      foreach my $fh ($in, $out) {
 -              unless (close $fh) {
 -                      if ($!) {
 -                              carp "error closing pipe: $!";
 -                      } elsif ($? >> 8) {
 -                              throw Git::Error::Command($ctx, $? >>8);
 -                      }
 -              }
 -      }
 -
 +      my ($self, $pid, $in, $out, $ctx) = _maybe_self(@_);
 +      _cmd_close($ctx, (grep { defined } ($in, $out)));
        waitpid $pid, 0;
 -
        if ($? >> 8) {
                throw Git::Error::Command($ctx, $? >>8);
        }
@@@ -1024,163 -1020,13 +1024,163 @@@ sub _close_cat_blob 
  }
  
  
 +=item credential_read( FILEHANDLE )
 +
 +Reads credential key-value pairs from C<FILEHANDLE>.  Reading stops at EOF or
 +when an empty line is encountered.  Each line must be of the form C<key=value>
 +with a non-empty key.  Function returns hash with all read values.  Any white
 +space (other than new-line character) is preserved.
 +
 +=cut
 +
 +sub credential_read {
 +      my ($self, $reader) = _maybe_self(@_);
 +      my %credential;
 +      while (<$reader>) {
 +              chomp;
 +              if ($_ eq '') {
 +                      last;
 +              } elsif (!/^([^=]+)=(.*)$/) {
 +                      throw Error::Simple("unable to parse git credential data:\n$_");
 +              }
 +              $credential{$1} = $2;
 +      }
 +      return %credential;
 +}
 +
 +=item credential_write( FILEHANDLE, CREDENTIAL_HASHREF )
 +
 +Writes credential key-value pairs from hash referenced by
 +C<CREDENTIAL_HASHREF> to C<FILEHANDLE>.  Keys and values cannot contain
 +new-lines or NUL bytes characters, and key cannot contain equal signs nor be
 +empty (if they do Error::Simple is thrown).  Any white space is preserved.  If
 +value for a key is C<undef>, it will be skipped.
 +
 +If C<'url'> key exists it will be written first.  (All the other key-value
 +pairs are written in sorted order but you should not depend on that).  Once
 +all lines are written, an empty line is printed.
 +
 +=cut
 +
 +sub credential_write {
 +      my ($self, $writer, $credential) = _maybe_self(@_);
 +      my ($key, $value);
 +
 +      # Check if $credential is valid prior to writing anything
 +      while (($key, $value) = each %$credential) {
 +              if (!defined $key || !length $key) {
 +                      throw Error::Simple("credential key empty or undefined");
 +              } elsif ($key =~ /[=\n\0]/) {
 +                      throw Error::Simple("credential key contains invalid characters: $key");
 +              } elsif (defined $value && $value =~ /[\n\0]/) {
 +                      throw Error::Simple("credential value for key=$key contains invalid characters: $value");
 +              }
 +      }
 +
 +      for $key (sort {
 +              # url overwrites other fields, so it must come first
 +              return -1 if $a eq 'url';
 +              return  1 if $b eq 'url';
 +              return $a cmp $b;
 +      } keys %$credential) {
 +              if (defined $credential->{$key}) {
 +                      print $writer $key, '=', $credential->{$key}, "\n";
 +              }
 +      }
 +      print $writer "\n";
 +}
 +
 +sub _credential_run {
 +      my ($self, $credential, $op) = _maybe_self(@_);
 +      my ($pid, $reader, $writer, $ctx) = command_bidi_pipe('credential', $op);
 +
 +      credential_write $writer, $credential;
 +      close $writer;
 +
 +      if ($op eq "fill") {
 +              %$credential = credential_read $reader;
 +      }
 +      if (<$reader>) {
 +              throw Error::Simple("unexpected output from git credential $op response:\n$_\n");
 +      }
 +
 +      command_close_bidi_pipe($pid, $reader, undef, $ctx);
 +}
 +
 +=item credential( CREDENTIAL_HASHREF [, OPERATION ] )
 +
 +=item credential( CREDENTIAL_HASHREF, CODE )
 +
 +Executes C<git credential> for a given set of credentials and specified
 +operation.  In both forms C<CREDENTIAL_HASHREF> needs to be a reference to
 +a hash which stores credentials.  Under certain conditions the hash can
 +change.
 +
 +In the first form, C<OPERATION> can be C<'fill'>, C<'approve'> or C<'reject'>,
 +and function will execute corresponding C<git credential> sub-command.  If
 +it's omitted C<'fill'> is assumed.  In case of C<'fill'> the values stored in
 +C<CREDENTIAL_HASHREF> will be changed to the ones returned by the C<git
 +credential fill> command.  The usual usage would look something like:
 +
 +      my %cred = (
 +              'protocol' => 'https',
 +              'host' => 'example.com',
 +              'username' => 'bob'
 +      );
 +      Git::credential \%cred;
 +      if (try_to_authenticate($cred{'username'}, $cred{'password'})) {
 +              Git::credential \%cred, 'approve';
 +              ... do more stuff ...
 +      } else {
 +              Git::credential \%cred, 'reject';
 +      }
 +
 +In the second form, C<CODE> needs to be a reference to a subroutine.  The
 +function will execute C<git credential fill> to fill the provided credential
 +hash, then call C<CODE> with C<CREDENTIAL_HASHREF> as the sole argument.  If
 +C<CODE>'s return value is defined, the function will execute C<git credential
 +approve> (if return value yields true) or C<git credential reject> (if return
 +value is false).  If the return value is undef, nothing at all is executed;
 +this is useful, for example, if the credential could neither be verified nor
 +rejected due to an unrelated network error.  The return value is the same as
 +what C<CODE> returns.  With this form, the usage might look as follows:
 +
 +      if (Git::credential {
 +              'protocol' => 'https',
 +              'host' => 'example.com',
 +              'username' => 'bob'
 +      }, sub {
 +              my $cred = shift;
 +              return !!try_to_authenticate($cred->{'username'},
 +                                           $cred->{'password'});
 +      }) {
 +              ... do more stuff ...
 +      }
 +
 +=cut
 +
 +sub credential {
 +      my ($self, $credential, $op_or_code) = (_maybe_self(@_), 'fill');
 +
 +      if ('CODE' eq ref $op_or_code) {
 +              _credential_run $credential, 'fill';
 +              my $ret = $op_or_code->($credential);
 +              if (defined $ret) {
 +                      _credential_run $credential, $ret ? 'approve' : 'reject';
 +              }
 +              return $ret;
 +      } else {
 +              _credential_run $credential, $op_or_code;
 +      }
 +}
 +
  { # %TEMP_* Lexical Context
  
  my (%TEMP_FILEMAP, %TEMP_FILES);
  
  =item temp_acquire ( NAME )
  
- Attempts to retreive the temporary file mapped to the string C<NAME>. If an
+ Attempts to retrieve the temporary file mapped to the string C<NAME>. If an
  associated temp file has not been created this session or was closed, it is
  created, cached, and set for autoflush and binmode.
  
@@@ -1529,11 -1375,9 +1529,11 @@@ sub _execv_git_cmd { exec('git', @_); 
  
  # Close pipe to a subprocess.
  sub _cmd_close {
 -      my ($fh, $ctx) = @_;
 -      if (not close $fh) {
 -              if ($!) {
 +      my $ctx = shift @_;
 +      foreach my $fh (@_) {
 +              if (close $fh) {
 +                      # nop
 +              } elsif ($!) {
                        # It's just close, no point in fatalities
                        carp "error closing pipe: $!";
                } elsif ($? >> 8) {
diff --combined sequencer.c
index baa031052e4e3f77bb6d4a257a5669ca5a5d617e,bad5077911667e11e522043ae09875489d4591c4..ee4f8c6ed4f040072b4d706c26d04a07f665c9f6
  #define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
  
  const char sign_off_header[] = "Signed-off-by: ";
 +static const char cherry_picked_prefix[] = "(cherry picked from commit ";
 +
 +static int is_rfc2822_line(const char *buf, int len)
 +{
 +      int i;
 +
 +      for (i = 0; i < len; i++) {
 +              int ch = buf[i];
 +              if (ch == ':')
 +                      return 1;
 +              if (!isalnum(ch) && ch != '-')
 +                      break;
 +      }
 +
 +      return 0;
 +}
 +
 +static int is_cherry_picked_from_line(const char *buf, int len)
 +{
 +      /*
 +       * We only care that it looks roughly like (cherry picked from ...)
 +       */
 +      return len > strlen(cherry_picked_prefix) + 1 &&
 +              !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')';
 +}
 +
 +/*
 + * Returns 0 for non-conforming footer
 + * Returns 1 for conforming footer
 + * Returns 2 when sob exists within conforming footer
 + * Returns 3 when sob exists within conforming footer as last entry
 + */
 +static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
 +      int ignore_footer)
 +{
 +      char prev;
 +      int i, k;
 +      int len = sb->len - ignore_footer;
 +      const char *buf = sb->buf;
 +      int found_sob = 0;
 +
 +      /* footer must end with newline */
 +      if (!len || buf[len - 1] != '\n')
 +              return 0;
 +
 +      prev = '\0';
 +      for (i = len - 1; i > 0; i--) {
 +              char ch = buf[i];
 +              if (prev == '\n' && ch == '\n') /* paragraph break */
 +                      break;
 +              prev = ch;
 +      }
 +
 +      /* require at least one blank line */
 +      if (prev != '\n' || buf[i] != '\n')
 +              return 0;
 +
 +      /* advance to start of last paragraph */
 +      while (i < len - 1 && buf[i] == '\n')
 +              i++;
 +
 +      for (; i < len; i = k) {
 +              int found_rfc2822;
 +
 +              for (k = i; k < len && buf[k] != '\n'; k++)
 +                      ; /* do nothing */
 +              k++;
 +
 +              found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
 +              if (found_rfc2822 && sob &&
 +                  !strncmp(buf + i, sob->buf, sob->len))
 +                      found_sob = k;
 +
 +              if (!(found_rfc2822 ||
 +                    is_cherry_picked_from_line(buf + i, k - i - 1)))
 +                      return 0;
 +      }
 +      if (found_sob == i)
 +              return 3;
 +      if (found_sob)
 +              return 2;
 +      return 1;
 +}
  
  static void remove_sequencer_state(void)
  {
@@@ -216,7 -133,7 +216,7 @@@ static void print_advice(int show_hint
        if (msg) {
                fprintf(stderr, "%s\n", msg);
                /*
-                * A conflict has occured but the porcelain
+                * A conflict has occurred but the porcelain
                 * (typically rebase --interactive) wants to take care
                 * of the commit itself so remove CHERRY_PICK_HEAD
                 */
@@@ -320,7 -237,7 +320,7 @@@ static int do_recursive_merge(struct co
        rollback_lock_file(&index_lock);
  
        if (opts->signoff)
 -              append_signoff(msgbuf, 0);
 +              append_signoff(msgbuf, 0, 0);
  
        if (!clean) {
                int i;
@@@ -579,9 -496,7 +579,9 @@@ static int do_pick_commit(struct commi
                }
  
                if (opts->record_origin) {
 -                      strbuf_addstr(&msgbuf, "(cherry picked from commit ");
 +                      if (!has_conforming_footer(&msgbuf, NULL, 0))
 +                              strbuf_addch(&msgbuf, '\n');
 +                      strbuf_addstr(&msgbuf, cherry_picked_prefix);
                        strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
                        strbuf_addstr(&msgbuf, ")\n");
                }
@@@ -1106,67 -1021,62 +1106,67 @@@ int sequencer_pick_revisions(struct rep
        return pick_commits(todo_list, opts);
  }
  
 -static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
 -{
 -      int ch;
 -      int hit = 0;
 -      int i, j, k;
 -      int len = sb->len - ignore_footer;
 -      int first = 1;
 -      const char *buf = sb->buf;
 -
 -      for (i = len - 1; i > 0; i--) {
 -              if (hit && buf[i] == '\n')
 -                      break;
 -              hit = (buf[i] == '\n');
 -      }
 -
 -      while (i < len - 1 && buf[i] == '\n')
 -              i++;
 -
 -      for (; i < len; i = k) {
 -              for (k = i; k < len && buf[k] != '\n'; k++)
 -                      ; /* do nothing */
 -              k++;
 -
 -              if ((buf[k] == ' ' || buf[k] == '\t') && !first)
 -                      continue;
 -
 -              first = 0;
 -
 -              for (j = 0; i + j < len; j++) {
 -                      ch = buf[i + j];
 -                      if (ch == ':')
 -                              break;
 -                      if (isalnum(ch) ||
 -                          (ch == '-'))
 -                              continue;
 -                      return 0;
 -              }
 -      }
 -      return 1;
 -}
 -
 -void append_signoff(struct strbuf *msgbuf, int ignore_footer)
 +void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
  {
 +      unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
        struct strbuf sob = STRBUF_INIT;
 -      int i;
 +      int has_footer;
  
        strbuf_addstr(&sob, sign_off_header);
        strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
                                getenv("GIT_COMMITTER_EMAIL")));
        strbuf_addch(&sob, '\n');
 -      for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--)
 -              ; /* do nothing */
 -      if (prefixcmp(msgbuf->buf + i, sob.buf)) {
 -              if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer))
 -                      strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1);
 -              strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len);
 +
 +      /*
 +       * If the whole message buffer is equal to the sob, pretend that we
 +       * found a conforming footer with a matching sob
 +       */
 +      if (msgbuf->len - ignore_footer == sob.len &&
 +          !strncmp(msgbuf->buf, sob.buf, sob.len))
 +              has_footer = 3;
 +      else
 +              has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
 +
 +      if (!has_footer) {
 +              const char *append_newlines = NULL;
 +              size_t len = msgbuf->len - ignore_footer;
 +
 +              if (!len) {
 +                      /*
 +                       * The buffer is completely empty.  Leave foom for
 +                       * the title and body to be filled in by the user.
 +                       */
 +                      append_newlines = "\n\n";
 +              } else if (msgbuf->buf[len - 1] != '\n') {
 +                      /*
 +                       * Incomplete line.  Complete the line and add a
 +                       * blank one so that there is an empty line between
 +                       * the message body and the sob.
 +                       */
 +                      append_newlines = "\n\n";
 +              } else if (len == 1) {
 +                      /*
 +                       * Buffer contains a single newline.  Add another
 +                       * so that we leave room for the title and body.
 +                       */
 +                      append_newlines = "\n";
 +              } else if (msgbuf->buf[len - 2] != '\n') {
 +                      /*
 +                       * Buffer ends with a single newline.  Add another
 +                       * so that there is an empty line between the message
 +                       * body and the sob.
 +                       */
 +                      append_newlines = "\n";
 +              } /* else, the buffer already ends with two newlines. */
 +
 +              if (append_newlines)
 +                      strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
 +                              append_newlines, strlen(append_newlines));
        }
 +
 +      if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
 +              strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
 +                              sob.buf, sob.len);
 +
        strbuf_release(&sob);
  }
diff --combined t/t4014-format-patch.sh
index b993dae64574cd2828fc636d3afc15b2c0c2e5a0,183fbe5bb3ea533f8f5f76f397d53671f912d6b0..86ee0771077a9da0404a276873c81dd17600ceb4
@@@ -742,21 -742,21 +742,21 @@@ test_expect_success 'format-patch --sig
        test 2 = $(grep "my sig" output | wc -l)
  '
  
- test_expect_success 'format.signature="" supresses signatures' '
+ test_expect_success 'format.signature="" suppresses signatures' '
        git config format.signature "" &&
        git format-patch --stdout -1 >output &&
        check_patch output &&
        ! grep "^-- \$" output
  '
  
- test_expect_success 'format-patch --no-signature supresses signatures' '
+ test_expect_success 'format-patch --no-signature suppresses signatures' '
        git config --unset-all format.signature &&
        git format-patch --stdout --no-signature -1 >output &&
        check_patch output &&
        ! grep "^-- \$" output
  '
  
- test_expect_success 'format-patch --signature="" supresses signatures' '
+ test_expect_success 'format-patch --signature="" suppresses signatures' '
        git format-patch --stdout --signature="" -1 >output &&
        check_patch output &&
        ! grep "^-- \$" output
@@@ -972,268 -972,6 +972,268 @@@ test_expect_success 'empty subject pref
        test_cmp expect actual
  '
  
 +append_signoff()
 +{
 +      C=$(git commit-tree HEAD^^{tree} -p HEAD) &&
 +      git format-patch --stdout --signoff $C^..$C >append_signoff.patch &&
 +      sed -n -e "1,/^---$/p" append_signoff.patch |
 +              egrep -n "^Subject|Sign|^$"
 +}
 +
 +test_expect_success 'signoff: commit with no body' '
 +      append_signoff </dev/null >actual &&
 +      cat <<\EOF | sed "s/EOL$//" >expected &&
 +4:Subject: [PATCH] EOL
 +8:
 +9:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: commit with only subject' '
 +      echo subject | append_signoff >actual &&
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +9:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: commit with only subject that does not end with NL' '
 +      printf subject | append_signoff >actual &&
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +9:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: no existing signoffs' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +11:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: no existing signoffs and no trailing NL' '
 +      printf "subject\n\nbody" | append_signoff >actual &&
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +11:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: some random signoff' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +
 +Signed-off-by: my@house
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +11:Signed-off-by: my@house
 +12:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: misc conforming footer elements' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +
 +Signed-off-by: my@house
 +(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
 +Tested-by: Some One <someone@example.com>
 +Bug: 1234
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +11:Signed-off-by: my@house
 +15:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: some random signoff-alike' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +Fooled-by-me: my@house
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +11:
 +12:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: not really a signoff' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +I want to mention about Signed-off-by: here.
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +9:I want to mention about Signed-off-by: here.
 +10:
 +11:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: not really a signoff (2)' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +My unfortunate
 +Signed-off-by: example happens to be wrapped here.
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:Signed-off-by: example happens to be wrapped here.
 +11:
 +12:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +Signed-off-by: my@house
 +Signed-off-by: your@house
 +
 +A lot of houses.
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +9:Signed-off-by: my@house
 +10:Signed-off-by: your@house
 +11:
 +13:
 +14:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: the same signoff at the end' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +
 +Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +11:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
 +      printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
 +              append_signoff >actual &&
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +9:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: the same signoff NOT at the end' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +
 +Signed-off-by: C O Mitter <committer@example.com>
 +Signed-off-by: my@house
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +11:Signed-off-by: C O Mitter <committer@example.com>
 +12:Signed-off-by: my@house
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: detect garbage in non-conforming footer' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +
 +Tested-by: my@house
 +Some Trash
 +Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +13:Signed-off-by: C O Mitter <committer@example.com>
 +14:
 +15:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
 +test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
 +      append_signoff <<\EOF >actual &&
 +subject
 +
 +body
 +
 +Reviewed-id: Noone
 +Tested-by: my@house
 +Change-id: Ideadbeef
 +Signed-off-by: C O Mitter <committer@example.com>
 +Bug: 1234
 +EOF
 +      cat >expected <<\EOF &&
 +4:Subject: [PATCH] subject
 +8:
 +10:
 +14:Signed-off-by: C O Mitter <committer@example.com>
 +EOF
 +      test_cmp expected actual
 +'
 +
  test_expect_success 'format patch ignores color.ui' '
        test_unconfig color.ui &&
        git format-patch --stdout -1 >expect &&
diff --combined t/t4124-apply-ws-rule.sh
index 0bbcf0603d20380e71ef27cec654ec6c604561d1,581a801649f20a74e81c32f9503f233c3cd3d507..5d0c5983381b4072d915de0f8d75053b19172d66
@@@ -47,7 -47,7 +47,7 @@@ test_fix () 
        # find touched lines
        $DIFF file target | sed -n -e "s/^> //p" >fixed
  
-       # the changed lines are all expeced to change
+       # the changed lines are all expected to change
        fixed_cnt=$(wc -l <fixed)
        case "$1" in
        '') expect_cnt=$fixed_cnt ;;
@@@ -486,30 -486,4 +486,30 @@@ test_expect_success 'same, but with CR-
        test_cmp one expect
  '
  
 +test_expect_success 'whitespace=fix to expand' '
 +      qz_to_tab_space >preimage <<-\EOF &&
 +      QQa
 +      QQb
 +      QQc
 +      ZZZZZZZZZZZZZZZZd
 +      QQe
 +      QQf
 +      QQg
 +      EOF
 +      qz_to_tab_space >patch <<-\EOF &&
 +      diff --git a/preimage b/preimage
 +      --- a/preimage
 +      +++ b/preimage
 +      @@ -1,7 +1,6 @@
 +       QQa
 +       QQb
 +       QQc
 +      -QQd
 +       QQe
 +       QQf
 +       QQg
 +      EOF
 +      git -c core.whitespace=tab-in-indent apply --whitespace=fix patch
 +'
 +
  test_done
index 2fce99a0754481a36b3644d01f8577310e19d763,8bf99e10a3f862e8502fc901b1ea5d7c16e947c9..8bf53de3ef6bc7cc5de14b70e93ffe42b9b3f094
@@@ -164,7 -164,7 +164,7 @@@ test_expect_success 'bisect start: exis
        cp .git/BISECT_START saved &&
        test_must_fail git bisect start $HASH4 foo -- &&
        git branch > branch.output &&
 -      test_i18ngrep "* (no branch)" branch.output > /dev/null &&
 +      test_i18ngrep "* (no branch, bisect started on other)" branch.output > /dev/null &&
        test_cmp saved .git/BISECT_START
  '
  test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
@@@ -190,7 -190,7 +190,7 @@@ test_expect_success 'bisect start: no "
  # $HASH1 is good, $HASH4 is bad, we skip $HASH3
  # but $HASH2 is bad,
  # so we should find $HASH2 as the first bad commit
- test_expect_success 'bisect skip: successfull result' '
+ test_expect_success 'bisect skip: successful result' '
        git bisect reset &&
        git bisect start $HASH4 $HASH1 &&
        git bisect skip &&
diff --combined transport.h
index 93544627087bdaed1373efb2205fc6f94b89527c,e7beb815dd546cf67cb84255d3ca894878f48ae7..fcb1d25d96a750c171c4341a9c5f08992915fb6b
@@@ -74,7 -74,7 +74,7 @@@ struct transport 
                       const char *executable, int fd[2]);
  
        /** get_refs_list(), fetch(), and push_refs() can keep
-        * resources (such as a connection) reserved for futher
+        * resources (such as a connection) reserved for further
         * use. disconnect() releases these resources.
         **/
        int (*disconnect)(struct transport *connection);
  #define TRANSPORT_PUSH_PRUNE 128
  #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
  #define TRANSPORT_PUSH_NO_HOOK 512
 +#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
  
  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
diff --combined wt-status.c
index 09416db348cb0e5e1bfc1fe82878c9afe627e09f,81e4fa519f4b0efbc54dab1475b8bdbb19f9ece2..ec5f27c5992e5fec5fb54384a04ffacf1745ce4a
@@@ -965,32 -965,13 +965,32 @@@ static void show_cherry_pick_in_progres
        wt_status_print_trailer(s);
  }
  
 +static void show_revert_in_progress(struct wt_status *s,
 +                                      struct wt_status_state *state,
 +                                      const char *color)
 +{
 +      status_printf_ln(s, color, _("You are currently reverting commit %s."),
 +                       find_unique_abbrev(state->revert_head_sha1, DEFAULT_ABBREV));
 +      if (advice_status_hints) {
 +              if (has_unmerged(s))
 +                      status_printf_ln(s, color,
 +                              _("  (fix conflicts and run \"git revert --continue\")"));
 +              else
 +                      status_printf_ln(s, color,
 +                              _("  (all conflicts fixed: run \"git revert --continue\")"));
 +              status_printf_ln(s, color,
 +                      _("  (use \"git revert --abort\" to cancel the revert operation)"));
 +      }
 +      wt_status_print_trailer(s);
 +}
 +
  static void show_bisect_in_progress(struct wt_status *s,
                                struct wt_status_state *state,
                                const char *color)
  {
        if (state->branch)
                status_printf_ln(s, color,
 -                               _("You are currently bisecting branch '%s'."),
 +                               _("You are currently bisecting, started from branch '%s'."),
                                 state->branch);
        else
                status_printf_ln(s, color,
  /*
   * Extract branch information from rebase/bisect
   */
 -static void read_and_strip_branch(struct strbuf *sb,
 -                                const char **branch,
 -                                const char *path)
 +static char *read_and_strip_branch(const char *path)
  {
 +      struct strbuf sb = STRBUF_INIT;
        unsigned char sha1[20];
  
 -      strbuf_reset(sb);
 -      if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0)
 -              return;
 +      if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
 +              goto got_nothing;
  
 -      while (sb->len && sb->buf[sb->len - 1] == '\n')
 -              strbuf_setlen(sb, sb->len - 1);
 -      if (!sb->len)
 -              return;
 -      if (!prefixcmp(sb->buf, "refs/heads/"))
 -              *branch = sb->buf + strlen("refs/heads/");
 -      else if (!prefixcmp(sb->buf, "refs/"))
 -              *branch = sb->buf;
 -      else if (!get_sha1_hex(sb->buf, sha1)) {
 +      while (&sb.len && sb.buf[sb.len - 1] == '\n')
 +              strbuf_setlen(&sb, sb.len - 1);
 +      if (!sb.len)
 +              goto got_nothing;
 +      if (!prefixcmp(sb.buf, "refs/heads/"))
 +              strbuf_remove(&sb,0, strlen("refs/heads/"));
 +      else if (!prefixcmp(sb.buf, "refs/"))
 +              ;
 +      else if (!get_sha1_hex(sb.buf, sha1)) {
                const char *abbrev;
                abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
 -              strbuf_reset(sb);
 -              strbuf_addstr(sb, abbrev);
 -              *branch = sb->buf;
 -      } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */
 -              ;
 +              strbuf_reset(&sb);
 +              strbuf_addstr(&sb, abbrev);
 +      } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
 +              goto got_nothing;
        else                    /* bisect */
 -              *branch = sb->buf;
 +              ;
 +      return strbuf_detach(&sb, NULL);
 +
 +got_nothing:
 +      strbuf_release(&sb);
 +      return NULL;
  }
  
 -static void wt_status_print_state(struct wt_status *s)
 +struct grab_1st_switch_cbdata {
 +      int found;
 +      struct strbuf buf;
 +      unsigned char nsha1[20];
 +};
 +
 +static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
 +                         const char *email, unsigned long timestamp, int tz,
 +                         const char *message, void *cb_data)
  {
 -      const char *state_color = color(WT_STATUS_HEADER, s);
 -      struct strbuf branch = STRBUF_INIT;
 -      struct strbuf onto = STRBUF_INIT;
 -      struct wt_status_state state;
 -      struct stat st;
 +      struct grab_1st_switch_cbdata *cb = cb_data;
 +      const char *target = NULL, *end;
  
 -      memset(&state, 0, sizeof(state));
 +      if (prefixcmp(message, "checkout: moving from "))
 +              return 0;
 +      message += strlen("checkout: moving from ");
 +      target = strstr(message, " to ");
 +      if (!target)
 +              return 0;
 +      target += strlen(" to ");
 +      strbuf_reset(&cb->buf);
 +      hashcpy(cb->nsha1, nsha1);
 +      for (end = target; *end && *end != '\n'; end++)
 +              ;
 +      strbuf_add(&cb->buf, target, end - target);
 +      cb->found = 1;
 +      return 1;
 +}
 +
 +static void wt_status_get_detached_from(struct wt_status_state *state)
 +{
 +      struct grab_1st_switch_cbdata cb;
 +      struct commit *commit;
 +      unsigned char sha1[20];
 +      char *ref = NULL;
 +
 +      strbuf_init(&cb.buf, 0);
 +      if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
 +              strbuf_release(&cb.buf);
 +              return;
 +      }
 +
 +      if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
 +          /* sha1 is a commit? match without further lookup */
 +          (!hashcmp(cb.nsha1, sha1) ||
 +           /* perhaps sha1 is a tag, try to dereference to a commit */
 +           ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
 +            !hashcmp(cb.nsha1, commit->object.sha1)))) {
 +              int ofs;
 +              if (!prefixcmp(ref, "refs/tags/"))
 +                      ofs = strlen("refs/tags/");
 +              else if (!prefixcmp(ref, "refs/remotes/"))
 +                      ofs = strlen("refs/remotes/");
 +              else
 +                      ofs = 0;
 +              state->detached_from = xstrdup(ref + ofs);
 +      } else
 +              state->detached_from =
 +                      xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
 +      hashcpy(state->detached_sha1, cb.nsha1);
 +
 +      free(ref);
 +      strbuf_release(&cb.buf);
 +}
 +
 +void wt_status_get_state(struct wt_status_state *state,
 +                       int get_detached_from)
 +{
 +      struct stat st;
 +      unsigned char sha1[20];
  
        if (!stat(git_path("MERGE_HEAD"), &st)) {
 -              state.merge_in_progress = 1;
 +              state->merge_in_progress = 1;
        } else if (!stat(git_path("rebase-apply"), &st)) {
                if (!stat(git_path("rebase-apply/applying"), &st)) {
 -                      state.am_in_progress = 1;
 +                      state->am_in_progress = 1;
                        if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
 -                              state.am_empty_patch = 1;
 +                              state->am_empty_patch = 1;
                } else {
 -                      state.rebase_in_progress = 1;
 -                      read_and_strip_branch(&branch, &state.branch,
 -                                            "rebase-apply/head-name");
 -                      read_and_strip_branch(&onto, &state.onto,
 -                                            "rebase-apply/onto");
 +                      state->rebase_in_progress = 1;
 +                      state->branch = read_and_strip_branch("rebase-apply/head-name");
 +                      state->onto = read_and_strip_branch("rebase-apply/onto");
                }
        } else if (!stat(git_path("rebase-merge"), &st)) {
                if (!stat(git_path("rebase-merge/interactive"), &st))
 -                      state.rebase_interactive_in_progress = 1;
 +                      state->rebase_interactive_in_progress = 1;
                else
 -                      state.rebase_in_progress = 1;
 -              read_and_strip_branch(&branch, &state.branch,
 -                                    "rebase-merge/head-name");
 -              read_and_strip_branch(&onto, &state.onto,
 -                                    "rebase-merge/onto");
 +                      state->rebase_in_progress = 1;
 +              state->branch = read_and_strip_branch("rebase-merge/head-name");
 +              state->onto = read_and_strip_branch("rebase-merge/onto");
        } else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
 -              state.cherry_pick_in_progress = 1;
 +              state->cherry_pick_in_progress = 1;
        }
        if (!stat(git_path("BISECT_LOG"), &st)) {
 -              state.bisect_in_progress = 1;
 -              read_and_strip_branch(&branch, &state.branch,
 -                                    "BISECT_START");
 +              state->bisect_in_progress = 1;
 +              state->branch = read_and_strip_branch("BISECT_START");
 +      }
 +      if (!stat(git_path("REVERT_HEAD"), &st) &&
 +          !get_sha1("REVERT_HEAD", sha1)) {
 +              state->revert_in_progress = 1;
 +              hashcpy(state->revert_head_sha1, sha1);
        }
  
 -      if (state.merge_in_progress)
 -              show_merge_in_progress(s, &state, state_color);
 -      else if (state.am_in_progress)
 -              show_am_in_progress(s, &state, state_color);
 -      else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
 -              show_rebase_in_progress(s, &state, state_color);
 -      else if (state.cherry_pick_in_progress)
 -              show_cherry_pick_in_progress(s, &state, state_color);
 -      if (state.bisect_in_progress)
 -              show_bisect_in_progress(s, &state, state_color);
 -      strbuf_release(&branch);
 -      strbuf_release(&onto);
 +      if (get_detached_from)
 +              wt_status_get_detached_from(state);
 +}
 +
 +static void wt_status_print_state(struct wt_status *s,
 +                                struct wt_status_state *state)
 +{
 +      const char *state_color = color(WT_STATUS_HEADER, s);
 +      if (state->merge_in_progress)
 +              show_merge_in_progress(s, state, state_color);
 +      else if (state->am_in_progress)
 +              show_am_in_progress(s, state, state_color);
 +      else if (state->rebase_in_progress || state->rebase_interactive_in_progress)
 +              show_rebase_in_progress(s, state, state_color);
 +      else if (state->cherry_pick_in_progress)
 +              show_cherry_pick_in_progress(s, state, state_color);
 +      else if (state->revert_in_progress)
 +              show_revert_in_progress(s, state, state_color);
 +      if (state->bisect_in_progress)
 +              show_bisect_in_progress(s, state, state_color);
  }
  
  void wt_status_print(struct wt_status *s)
  {
        const char *branch_color = color(WT_STATUS_ONBRANCH, s);
        const char *branch_status_color = color(WT_STATUS_HEADER, s);
 +      struct wt_status_state state;
 +
 +      memset(&state, 0, sizeof(state));
 +      wt_status_get_state(&state,
 +                          s->branch && !strcmp(s->branch, "HEAD"));
  
        if (s->branch) {
                const char *on_what = _("On branch ");
                if (!prefixcmp(branch_name, "refs/heads/"))
                        branch_name += 11;
                else if (!strcmp(branch_name, "HEAD")) {
 -                      branch_name = "";
                        branch_status_color = color(WT_STATUS_NOBRANCH, s);
 -                      on_what = _("Not currently on any branch.");
 +                      if (state.detached_from) {
 +                              unsigned char sha1[20];
 +                              branch_name = state.detached_from;
 +                              if (!get_sha1("HEAD", sha1) &&
 +                                  !hashcmp(sha1, state.detached_sha1))
 +                                      on_what = _("HEAD detached at ");
 +                              else
 +                                      on_what = _("HEAD detached from ");
 +                      } else {
 +                              branch_name = "";
 +                              on_what = _("Not currently on any branch.");
 +                      }
                }
                status_printf(s, color(WT_STATUS_HEADER, s), "");
                status_printf_more(s, branch_status_color, "%s", on_what);
                        wt_status_print_tracking(s);
        }
  
 -      wt_status_print_state(s);
 +      wt_status_print_state(s, &state);
 +      free(state.branch);
 +      free(state.onto);
 +      free(state.detached_from);
 +
        if (s->is_initial) {
                status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
                status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
                if (advice_status_u_option && 2000 < s->untracked_in_ms) {
                        status_printf_ln(s, GIT_COLOR_NORMAL, "");
                        status_printf_ln(s, GIT_COLOR_NORMAL,
-                                _("It took %.2f seconds to enumerate untracked files."
-                                  "  'status -uno'"),
-                                s->untracked_in_ms / 1000.0);
-                       status_printf_ln(s, GIT_COLOR_NORMAL,
-                                _("may speed it up, but you have to be careful not"
-                                  " to forget to add"));
-                       status_printf_ln(s, GIT_COLOR_NORMAL,
-                                _("new files yourself (see 'git help status')."));
+                                        _("It took %.2f seconds to enumerate untracked files. 'status -uno'\n"
+                                          "may speed it up, but you have to be careful not to forget to add\n"
+                                          "new files yourself (see 'git help status')."),
+                                        s->untracked_in_ms / 1000.0);
                }
        } else if (s->commitable)
                status_printf_ln(s, GIT_COLOR_NORMAL, _("Untracked files not listed%s"),