Merge branch 'maint'
authorShawn O. Pearce <spearce@spearce.org>
Tue, 16 Oct 2007 02:31:47 +0000 (22:31 -0400)
committerShawn O. Pearce <spearce@spearce.org>
Tue, 16 Oct 2007 02:31:47 +0000 (22:31 -0400)
* maint:
Whip post 1.5.3.4 maintenance series into shape.
rebase -i: use diff plumbing instead of porcelain
Do not remove distributed configure script
git-archive: document --exec
git-reflog: document --verbose
git-config: handle --file option with relative pathname properly
clear_commit_marks(): avoid deep recursion
git add -i: Remove unused variables
git add -i: Fix parsing of abbreviated hunk headers
git-config: don't silently ignore options after --list
Clean up "git log" format with DIFF_FORMAT_NO_OUTPUT
Fix embarrassing "git log --follow" bug

Conflicts:

RelNotes
git-rebase--interactive.sh

1  2 
Makefile
commit.c
git-rebase--interactive.sh
log-tree.c
revision.c
diff --combined Makefile
index 55fbcb73e1b7e98545ee081b823fd05284a8506f,c63d656f5d8ae3f66f0eca5760dbc71e3ab09210..d74ac93c116fe24d1a23df1c7c21d9ca0cc1c23f
+++ b/Makefile
@@@ -28,8 -28,6 +28,8 @@@ all:
  #
  # Define NO_STRCASESTR if you don't have strcasestr.
  #
 +# Define NO_MEMMEM if you don't have memmem.
 +#
  # Define NO_STRLCPY if you don't have strlcpy.
  #
  # Define NO_STRTOUMAX if you don't have strtoumax in the C library.
  # If not set it defaults to the bare 'wish'. If it is set to the empty
  # string then NO_TCLTK will be forced (this is used by configure script).
  #
 +# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
 +# parallel delta searching when packing objects.
 +#
  
  GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@@ -211,7 -206,7 +211,7 @@@ SCRIPT_SH = 
        git-ls-remote.sh \
        git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
 -      git-repack.sh git-request-pull.sh git-reset.sh \
 +      git-repack.sh git-request-pull.sh \
        git-sh-setup.sh \
        git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@@ -233,7 -228,7 +233,7 @@@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH
  
  # ... and all the rest that could be moved out of bindir to gitexecdir
  PROGRAMS = \
 -      git-convert-objects$X git-fetch-pack$X \
 +      git-fetch-pack$X \
        git-hash-object$X git-index-pack$X git-local-fetch$X \
        git-fast-import$X \
        git-daemon$X \
@@@ -358,7 -353,6 +358,7 @@@ BUILTIN_OBJS = 
        builtin-reflog.o \
        builtin-config.o \
        builtin-rerere.o \
 +      builtin-reset.o \
        builtin-rev-list.o \
        builtin-rev-parse.o \
        builtin-revert.o \
@@@ -402,14 -396,12 +402,14 @@@ ifeq ($(uname_S),Darwin
        NEEDS_LIBICONV = YesPlease
        OLD_ICONV = UnfortunatelyYes
        NO_STRLCPY = YesPlease
 +      NO_MEMMEM = YesPlease
  endif
  ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
        SHELL_PATH = /bin/bash
        NO_STRCASESTR = YesPlease
 +      NO_MEMMEM = YesPlease
        NO_HSTRERROR = YesPlease
        ifeq ($(uname_R),5.8)
                NEEDS_LIBICONV = YesPlease
@@@ -432,7 -424,6 +432,7 @@@ ifeq ($(uname_O),Cygwin
        NO_D_TYPE_IN_DIRENT = YesPlease
        NO_D_INO_IN_DIRENT = YesPlease
        NO_STRCASESTR = YesPlease
 +      NO_MEMMEM = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
  endif
  ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
 +      NO_MEMMEM = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
  endif
  ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
 +      NO_MEMMEM = YesPlease
        NEEDS_LIBICONV = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
@@@ -467,7 -456,6 +467,7 @@@ ifeq ($(uname_S),NetBSD
  endif
  ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
 +      NO_MEMMEM = YesPlease
        NO_STRLCPY = YesPlease
        NEEDS_LIBICONV=YesPlease
  endif
@@@ -479,7 -467,6 +479,7 @@@ ifeq ($(uname_S),IRIX64
        NO_IPV6=YesPlease
        NO_SETENV=YesPlease
        NO_STRCASESTR=YesPlease
 +      NO_MEMMEM = YesPlease
        NO_STRLCPY = YesPlease
        NO_SOCKADDR_STORAGE=YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
@@@ -674,15 -661,6 +674,15 @@@ ifdef NO_HSTRERRO
        COMPAT_CFLAGS += -DNO_HSTRERROR
        COMPAT_OBJS += compat/hstrerror.o
  endif
 +ifdef NO_MEMMEM
 +      COMPAT_CFLAGS += -DNO_MEMMEM
 +      COMPAT_OBJS += compat/memmem.o
 +endif
 +
 +ifdef THREADED_DELTA_SEARCH
 +      BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
 +      EXTLIBS += -lpthread
 +endif
  
  ifeq ($(TCLTK_PATH),)
  NO_TCLTK=NoThanks
@@@ -947,10 -925,6 +947,10 @@@ tags
        $(RM) tags
        $(FIND) . -name '*.[hcS]' -print | xargs ctags -a
  
 +cscope:
 +      $(RM) cscope*
 +      $(FIND) . -name '*.[hcS]' -print | xargs cscope -b
 +
  ### Detect prefix changes
  TRACK_CFLAGS = $(subst ','\'',$(ALL_CFLAGS)):\
               $(bindir_SQ):$(gitexecdir_SQ):$(template_dir_SQ):$(prefix_SQ)
@@@ -1092,14 -1066,17 +1092,17 @@@ dist-doc
  
  ### Cleaning rules
  
+ distclean: clean
+       $(RM) configure
  clean:
        $(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
                $(LIB_FILE) $(XDIFF_LIB)
        $(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X
        $(RM) $(TEST_PROGRAMS)
 -      $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
 +      $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags cscope*
        $(RM) -r autom4te.cache
-       $(RM) configure config.log config.mak.autogen config.mak.append config.status config.cache
+       $(RM) config.log config.mak.autogen config.mak.append config.status config.cache
        $(RM) -r $(GIT_TARNAME) .doc-tmp-dir
        $(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
        $(RM) $(htmldocs).tar.gz $(manpages).tar.gz
@@@ -1115,7 -1092,7 +1118,7 @@@ endi
        $(RM) GIT-VERSION-FILE GIT-CFLAGS GIT-GUI-VARS
  
  .PHONY: all install clean strip
 -.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags .FORCE-GIT-CFLAGS
 +.PHONY: .FORCE-GIT-VERSION-FILE TAGS tags cscope .FORCE-GIT-CFLAGS
  
  ### Check documentation
  #
diff --combined commit.c
index 20fb2209cbcbc19343e7e52378e74afef52e3a6e,1fbdd2d51b8e798ac73c4b020f8fa47b1d6fb168..ac24266e935054c6909b8fbd513ffa937e2ae092
+++ b/commit.c
@@@ -441,28 -441,33 +441,33 @@@ struct commit *pop_most_recent_commit(s
  
  void clear_commit_marks(struct commit *commit, unsigned int mark)
  {
-       struct commit_list *parents;
+       while (commit) {
+               struct commit_list *parents;
  
-       commit->object.flags &= ~mark;
-       parents = commit->parents;
-       while (parents) {
-               struct commit *parent = parents->item;
+               if (!(mark & commit->object.flags))
+                       return;
  
-               /* Have we already cleared this? */
-               if (mark & parent->object.flags)
-                       clear_commit_marks(parent, mark);
-               parents = parents->next;
+               commit->object.flags &= ~mark;
+               parents = commit->parents;
+               if (!parents)
+                       return;
+               while ((parents = parents->next))
+                       clear_commit_marks(parents->item, mark);
+               commit = commit->parents->item;
        }
  }
  
  /*
   * Generic support for pretty-printing the header
   */
 -static int get_one_line(const char *msg, unsigned long len)
 +static int get_one_line(const char *msg)
  {
        int ret = 0;
  
 -      while (len--) {
 +      for (;;) {
                char c = *msg++;
                if (!c)
                        break;
@@@ -485,25 -490,31 +490,25 @@@ static int is_rfc2047_special(char ch
        return (non_ascii(ch) || (ch == '=') || (ch == '?') || (ch == '_'));
  }
  
 -static int add_rfc2047(char *buf, const char *line, int len,
 +static void add_rfc2047(struct strbuf *sb, const char *line, int len,
                       const char *encoding)
  {
 -      char *bp = buf;
 -      int i, needquote;
 -      char q_encoding[128];
 -      const char *q_encoding_fmt = "=?%s?q?";
 +      int i, last;
  
 -      for (i = needquote = 0; !needquote && i < len; i++) {
 +      for (i = 0; i < len; i++) {
                int ch = line[i];
                if (non_ascii(ch))
 -                      needquote++;
 -              if ((i + 1 < len) &&
 -                  (ch == '=' && line[i+1] == '?'))
 -                      needquote++;
 +                      goto needquote;
 +              if ((i + 1 < len) && (ch == '=' && line[i+1] == '?'))
 +                      goto needquote;
        }
 -      if (!needquote)
 -              return sprintf(buf, "%.*s", len, line);
 -
 -      i = snprintf(q_encoding, sizeof(q_encoding), q_encoding_fmt, encoding);
 -      if (sizeof(q_encoding) < i)
 -              die("Insanely long encoding name %s", encoding);
 -      memcpy(bp, q_encoding, i);
 -      bp += i;
 -      for (i = 0; i < len; i++) {
 +      strbuf_add(sb, line, len);
 +      return;
 +
 +needquote:
 +      strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
 +      strbuf_addf(sb, "=?%s?q?", encoding);
 +      for (i = last = 0; i < len; i++) {
                unsigned ch = line[i] & 0xFF;
                /*
                 * We encode ' ' using '=20' even though rfc2047
                 * leave the underscore in place.
                 */
                if (is_rfc2047_special(ch) || ch == ' ') {
 -                      sprintf(bp, "=%02X", ch);
 -                      bp += 3;
 +                      strbuf_add(sb, line + last, i - last);
 +                      strbuf_addf(sb, "=%02X", ch);
 +                      last = i + 1;
                }
 -              else
 -                      *bp++ = ch;
        }
 -      memcpy(bp, "?=", 2);
 -      bp += 2;
 -      return bp - buf;
 -}
 -
 -static unsigned long bound_rfc2047(unsigned long len, const char *encoding)
 -{
 -      /* upper bound of q encoded string of length 'len' */
 -      unsigned long elen = strlen(encoding);
 -
 -      return len * 3 + elen + 100;
 +      strbuf_add(sb, line + last, len - last);
 +      strbuf_addstr(sb, "?=");
  }
  
 -static int add_user_info(const char *what, enum cmit_fmt fmt, char *buf,
 +static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
                         const char *line, enum date_mode dmode,
                         const char *encoding)
  {
        char *date;
        int namelen;
        unsigned long time;
 -      int tz, ret;
 +      int tz;
        const char *filler = "    ";
  
        if (fmt == CMIT_FMT_ONELINE)
 -              return 0;
 +              return;
        date = strchr(line, '>');
        if (!date)
 -              return 0;
 +              return;
        namelen = ++date - line;
        time = strtoul(date, &date, 10);
        tz = strtol(date, NULL, 10);
                char *name_tail = strchr(line, '<');
                int display_name_length;
                if (!name_tail)
 -                      return 0;
 +                      return;
                while (line < name_tail && isspace(name_tail[-1]))
                        name_tail--;
                display_name_length = name_tail - line;
                filler = "";
 -              strcpy(buf, "From: ");
 -              ret = strlen(buf);
 -              ret += add_rfc2047(buf + ret, line, display_name_length,
 -                                 encoding);
 -              memcpy(buf + ret, name_tail, namelen - display_name_length);
 -              ret += namelen - display_name_length;
 -              buf[ret++] = '\n';
 -      }
 -      else {
 -              ret = sprintf(buf, "%s: %.*s%.*s\n", what,
 +              strbuf_addstr(sb, "From: ");
 +              add_rfc2047(sb, line, display_name_length, encoding);
 +              strbuf_add(sb, name_tail, namelen - display_name_length);
 +              strbuf_addch(sb, '\n');
 +      } else {
 +              strbuf_addf(sb, "%s: %.*s%.*s\n", what,
                              (fmt == CMIT_FMT_FULLER) ? 4 : 0,
                              filler, namelen, line);
        }
        switch (fmt) {
        case CMIT_FMT_MEDIUM:
 -              ret += sprintf(buf + ret, "Date:   %s\n",
 -                             show_date(time, tz, dmode));
 +              strbuf_addf(sb, "Date:   %s\n", show_date(time, tz, dmode));
                break;
        case CMIT_FMT_EMAIL:
 -              ret += sprintf(buf + ret, "Date: %s\n",
 -                             show_date(time, tz, DATE_RFC2822));
 +              strbuf_addf(sb, "Date: %s\n", show_date(time, tz, DATE_RFC2822));
                break;
        case CMIT_FMT_FULLER:
 -              ret += sprintf(buf + ret, "%sDate: %s\n", what,
 -                             show_date(time, tz, dmode));
 +              strbuf_addf(sb, "%sDate: %s\n", what, show_date(time, tz, dmode));
                break;
        default:
                /* notin' */
                break;
        }
 -      return ret;
  }
  
  static int is_empty_line(const char *line, int *len_p)
        return !len;
  }
  
 -static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *commit, int abbrev)
 +static void add_merge_info(enum cmit_fmt fmt, struct strbuf *sb,
 +                      const struct commit *commit, int abbrev)
  {
        struct commit_list *parent = commit->parents;
 -      int offset;
  
        if ((fmt == CMIT_FMT_ONELINE) || (fmt == CMIT_FMT_EMAIL) ||
            !parent || !parent->next)
 -              return 0;
 +              return;
  
 -      offset = sprintf(buf, "Merge:");
 +      strbuf_addstr(sb, "Merge:");
  
        while (parent) {
                struct commit *p = parent->item;
                dots = (abbrev && strlen(hex) != 40) ?  "..." : "";
                parent = parent->next;
  
 -              offset += sprintf(buf + offset, " %s%s", hex, dots);
 +              strbuf_addf(sb, " %s%s", hex, dots);
        }
 -      buf[offset++] = '\n';
 -      return offset;
 +      strbuf_addch(sb, '\n');
  }
  
  static char *get_header(const struct commit *commit, const char *key)
                if (eol - line > key_len &&
                    !strncmp(line, key, key_len) &&
                    line[key_len] == ' ') {
 -                      int len = eol - line - key_len;
 -                      char *ret = xmalloc(len);
 -                      memcpy(ret, line + key_len + 1, len - 1);
 -                      ret[len - 1] = '\0';
 -                      return ret;
 +                      return xmemdupz(line + key_len + 1, eol - line - key_len - 1);
                }
                line = next;
        }
  
  static char *replace_encoding_header(char *buf, const char *encoding)
  {
 -      char *encoding_header = strstr(buf, "\nencoding ");
 -      char *header_end = strstr(buf, "\n\n");
 -      char *end_of_encoding_header;
 -      int encoding_header_pos;
 -      int encoding_header_len;
 -      int new_len;
 -      int need_len;
 -      int buflen = strlen(buf) + 1;
 -
 -      if (!header_end)
 -              header_end = buf + buflen;
 -      if (!encoding_header || encoding_header >= header_end)
 -              return buf;
 -      encoding_header++;
 -      end_of_encoding_header = strchr(encoding_header, '\n');
 -      if (!end_of_encoding_header)
 +      struct strbuf tmp;
 +      size_t start, len;
 +      char *cp = buf;
 +
 +      /* guess if there is an encoding header before a \n\n */
 +      while (strncmp(cp, "encoding ", strlen("encoding "))) {
 +              cp = strchr(cp, '\n');
 +              if (!cp || *++cp == '\n')
 +                      return buf;
 +      }
 +      start = cp - buf;
 +      cp = strchr(cp, '\n');
 +      if (!cp)
                return buf; /* should not happen but be defensive */
 -      end_of_encoding_header++;
 -
 -      encoding_header_len = end_of_encoding_header - encoding_header;
 -      encoding_header_pos = encoding_header - buf;
 +      len = cp + 1 - (buf + start);
  
 +      strbuf_init(&tmp, 0);
 +      strbuf_attach(&tmp, buf, strlen(buf), strlen(buf) + 1);
        if (is_encoding_utf8(encoding)) {
                /* we have re-coded to UTF-8; drop the header */
 -              memmove(encoding_header, end_of_encoding_header,
 -                      buflen - (encoding_header_pos + encoding_header_len));
 -              return buf;
 -      }
 -      new_len = strlen(encoding);
 -      need_len = new_len + strlen("encoding \n");
 -      if (encoding_header_len < need_len) {
 -              buf = xrealloc(buf, buflen + (need_len - encoding_header_len));
 -              encoding_header = buf + encoding_header_pos;
 -              end_of_encoding_header = encoding_header + encoding_header_len;
 +              strbuf_remove(&tmp, start, len);
 +      } else {
 +              /* just replaces XXXX in 'encoding XXXX\n' */
 +              strbuf_splice(&tmp, start + strlen("encoding "),
 +                                        len - strlen("encoding \n"),
 +                                        encoding, strlen(encoding));
        }
 -      memmove(end_of_encoding_header + (need_len - encoding_header_len),
 -              end_of_encoding_header,
 -              buflen - (encoding_header_pos + encoding_header_len));
 -      memcpy(encoding_header + 9, encoding, strlen(encoding));
 -      encoding_header[9 + new_len] = '\n';
 -      return buf;
 +      return strbuf_detach(&tmp, NULL);
  }
  
  static char *logmsg_reencode(const struct commit *commit,
@@@ -705,7 -752,7 +710,7 @@@ static void fill_person(struct interp *
        start = end + 1;
        while (end > 0 && isspace(msg[end - 1]))
                end--;
 -      table[0].value = xstrndup(msg, end);
 +      table[0].value = xmemdupz(msg, end);
  
        if (start >= len)
                return;
        if (end >= len)
                return;
  
 -      table[1].value = xstrndup(msg + start, end - start);
 +      table[1].value = xmemdupz(msg + start, end - start);
  
        /* parse date */
        for (start = end + 1; start < len && isspace(msg[start]); start++)
        if (msg + start == ep)
                return;
  
 -      table[5].value = xstrndup(msg + start, ep - (msg + start));
 +      table[5].value = xmemdupz(msg + start, ep - (msg + start));
  
        /* parse tz */
        for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
        interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
  }
  
 -static long format_commit_message(const struct commit *commit,
 -              const char *msg, char **buf_p, unsigned long *space_p)
 +void format_commit_message(const struct commit *commit,
 +                           const void *format, struct strbuf *sb)
  {
        struct interp table[] = {
                { "%H" },       /* commit hash */
        };
        struct commit_list *p;
        char parents[1024];
 +      unsigned long len;
        int i;
        enum { HEADER, SUBJECT, BODY } state;
 +      const char *msg = commit->buffer;
  
        if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
                die("invalid interp table!");
                        ; /* do nothing */
  
                if (state == SUBJECT) {
 -                      table[ISUBJECT].value = xstrndup(msg + i, eol - i);
 +                      table[ISUBJECT].value = xmemdupz(msg + i, eol - i);
                        i = eol;
                }
                if (i == eol) {
                                        msg + i + 10, eol - i - 10);
                else if (!prefixcmp(msg + i, "encoding "))
                        table[IENCODING].value =
 -                              xstrndup(msg + i + 9, eol - i - 9);
 +                              xmemdupz(msg + i + 9, eol - i - 9);
                i = eol;
        }
        if (msg[i])
                table[IBODY].value = xstrdup(msg + i);
 -      for (i = 0; i < ARRAY_SIZE(table); i++)
 -              if (!table[i].value)
 -                      interp_set_entry(table, i, "<unknown>");
 -
 -      do {
 -              char *buf = *buf_p;
 -              unsigned long space = *space_p;
  
 -              space = interpolate(buf, space, user_format,
 -                                  table, ARRAY_SIZE(table));
 -              if (!space)
 -                      break;
 -              buf = xrealloc(buf, space);
 -              *buf_p = buf;
 -              *space_p = space;
 -      } while (1);
 +      len = interpolate(sb->buf + sb->len, strbuf_avail(sb),
 +                              format, table, ARRAY_SIZE(table));
 +      if (len > strbuf_avail(sb)) {
 +              strbuf_grow(sb, len);
 +              interpolate(sb->buf + sb->len, strbuf_avail(sb) + 1,
 +                                      format, table, ARRAY_SIZE(table));
 +      }
 +      strbuf_setlen(sb, sb->len + len);
        interp_clear_table(table, ARRAY_SIZE(table));
 -
 -      return strlen(*buf_p);
  }
  
  static void pp_header(enum cmit_fmt fmt,
                      const char *encoding,
                      const struct commit *commit,
                      const char **msg_p,
 -                    unsigned long *len_p,
 -                    unsigned long *ofs_p,
 -                    char **buf_p,
 -                    unsigned long *space_p)
 +                    struct strbuf *sb)
  {
        int parents_shown = 0;
  
        for (;;) {
                const char *line = *msg_p;
 -              char *dst;
 -              int linelen = get_one_line(*msg_p, *len_p);
 -              unsigned long len;
 +              int linelen = get_one_line(*msg_p);
  
                if (!linelen)
                        return;
                *msg_p += linelen;
 -              *len_p -= linelen;
  
                if (linelen == 1)
                        /* End of header */
                        return;
  
 -              ALLOC_GROW(*buf_p, linelen + *ofs_p + 20, *space_p);
 -              dst = *buf_p + *ofs_p;
 -
                if (fmt == CMIT_FMT_RAW) {
 -                      memcpy(dst, line, linelen);
 -                      *ofs_p += linelen;
 +                      strbuf_add(sb, line, linelen);
                        continue;
                }
  
                             parent = parent->next, num++)
                                ;
                        /* with enough slop */
 -                      num = *ofs_p + num * 50 + 20;
 -                      ALLOC_GROW(*buf_p, num, *space_p);
 -                      dst = *buf_p + *ofs_p;
 -                      *ofs_p += add_merge_info(fmt, dst, commit, abbrev);
 +                      strbuf_grow(sb, num * 50 + 20);
 +                      add_merge_info(fmt, sb, commit, abbrev);
                        parents_shown = 1;
                }
  
                 * FULLER shows both authors and dates.
                 */
                if (!memcmp(line, "author ", 7)) {
 -                      len = linelen;
 -                      if (fmt == CMIT_FMT_EMAIL)
 -                              len = bound_rfc2047(linelen, encoding);
 -                      ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
 -                      dst = *buf_p + *ofs_p;
 -                      *ofs_p += add_user_info("Author", fmt, dst,
 -                                              line + 7, dmode, encoding);
 +                      strbuf_grow(sb, linelen + 80);
 +                      add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
                }
 -
                if (!memcmp(line, "committer ", 10) &&
                    (fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
 -                      len = linelen;
 -                      if (fmt == CMIT_FMT_EMAIL)
 -                              len = bound_rfc2047(linelen, encoding);
 -                      ALLOC_GROW(*buf_p, *ofs_p + len + 80, *space_p);
 -                      dst = *buf_p + *ofs_p;
 -                      *ofs_p += add_user_info("Commit", fmt, dst,
 -                                              line + 10, dmode, encoding);
 +                      strbuf_grow(sb, linelen + 80);
 +                      add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
                }
        }
  }
  
  static void pp_title_line(enum cmit_fmt fmt,
                          const char **msg_p,
 -                        unsigned long *len_p,
 -                        unsigned long *ofs_p,
 -                        char **buf_p,
 -                        unsigned long *space_p,
 -                        int indent,
 +                        struct strbuf *sb,
                          const char *subject,
                          const char *after_subject,
                          const char *encoding,
                          int plain_non_ascii)
  {
 -      char *title;
 -      unsigned long title_alloc, title_len;
 -      unsigned long len;
 +      struct strbuf title;
 +
 +      strbuf_init(&title, 80);
  
 -      title_len = 0;
 -      title_alloc = 80;
 -      title = xmalloc(title_alloc);
        for (;;) {
                const char *line = *msg_p;
 -              int linelen = get_one_line(line, *len_p);
 -              *msg_p += linelen;
 -              *len_p -= linelen;
 +              int linelen = get_one_line(line);
  
 +              *msg_p += linelen;
                if (!linelen || is_empty_line(line, &linelen))
                        break;
  
 -              if (title_alloc <= title_len + linelen + 2) {
 -                      title_alloc = title_len + linelen + 80;
 -                      title = xrealloc(title, title_alloc);
 -              }
 -              len = 0;
 -              if (title_len) {
 +              strbuf_grow(&title, linelen + 2);
 +              if (title.len) {
                        if (fmt == CMIT_FMT_EMAIL) {
 -                              len++;
 -                              title[title_len++] = '\n';
 +                              strbuf_addch(&title, '\n');
                        }
 -                      len++;
 -                      title[title_len++] = ' ';
 +                      strbuf_addch(&title, ' ');
                }
 -              memcpy(title + title_len, line, linelen);
 -              title_len += linelen;
 +              strbuf_add(&title, line, linelen);
        }
  
 -      /* Enough slop for the MIME header and rfc2047 */
 -      len = bound_rfc2047(title_len, encoding)+ 1000;
 -      if (subject)
 -              len += strlen(subject);
 -      if (after_subject)
 -              len += strlen(after_subject);
 -      if (encoding)
 -              len += strlen(encoding);
 -      ALLOC_GROW(*buf_p, title_len + *ofs_p + len, *space_p);
 -
 +      strbuf_grow(sb, title.len + 1024);
        if (subject) {
 -              len = strlen(subject);
 -              memcpy(*buf_p + *ofs_p, subject, len);
 -              *ofs_p += len;
 -              *ofs_p += add_rfc2047(*buf_p + *ofs_p,
 -                                    title, title_len, encoding);
 +              strbuf_addstr(sb, subject);
 +              add_rfc2047(sb, title.buf, title.len, encoding);
        } else {
 -              memcpy(*buf_p + *ofs_p, title, title_len);
 -              *ofs_p += title_len;
 +              strbuf_addbuf(sb, &title);
        }
 -      (*buf_p)[(*ofs_p)++] = '\n';
 +      strbuf_addch(sb, '\n');
 +
        if (plain_non_ascii) {
                const char *header_fmt =
                        "MIME-Version: 1.0\n"
                        "Content-Type: text/plain; charset=%s\n"
                        "Content-Transfer-Encoding: 8bit\n";
 -              *ofs_p += snprintf(*buf_p + *ofs_p,
 -                                 *space_p - *ofs_p,
 -                                 header_fmt, encoding);
 +              strbuf_addf(sb, header_fmt, encoding);
        }
        if (after_subject) {
 -              len = strlen(after_subject);
 -              memcpy(*buf_p + *ofs_p, after_subject, len);
 -              *ofs_p += len;
 +              strbuf_addstr(sb, after_subject);
        }
 -      free(title);
        if (fmt == CMIT_FMT_EMAIL) {
 -              ALLOC_GROW(*buf_p, *ofs_p + 20, *space_p);
 -              (*buf_p)[(*ofs_p)++] = '\n';
 +              strbuf_addch(sb, '\n');
        }
 +      strbuf_release(&title);
  }
  
  static void pp_remainder(enum cmit_fmt fmt,
                         const char **msg_p,
 -                       unsigned long *len_p,
 -                       unsigned long *ofs_p,
 -                       char **buf_p,
 -                       unsigned long *space_p,
 +                       struct strbuf *sb,
                         int indent)
  {
        int first = 1;
        for (;;) {
                const char *line = *msg_p;
 -              int linelen = get_one_line(line, *len_p);
 +              int linelen = get_one_line(line);
                *msg_p += linelen;
 -              *len_p -= linelen;
  
                if (!linelen)
                        break;
                }
                first = 0;
  
 -              ALLOC_GROW(*buf_p, *ofs_p + linelen + indent + 20, *space_p);
 +              strbuf_grow(sb, linelen + indent + 20);
                if (indent) {
 -                      memset(*buf_p + *ofs_p, ' ', indent);
 -                      *ofs_p += indent;
 +                      memset(sb->buf + sb->len, ' ', indent);
 +                      strbuf_setlen(sb, sb->len + indent);
                }
 -              memcpy(*buf_p + *ofs_p, line, linelen);
 -              *ofs_p += linelen;
 -              (*buf_p)[(*ofs_p)++] = '\n';
 +              strbuf_add(sb, line, linelen);
 +              strbuf_addch(sb, '\n');
        }
  }
  
 -unsigned long pretty_print_commit(enum cmit_fmt fmt,
 -                                const struct commit *commit,
 -                                unsigned long len,
 -                                char **buf_p, unsigned long *space_p,
 -                                int abbrev, const char *subject,
 -                                const char *after_subject,
 +void pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit,
 +                                struct strbuf *sb, int abbrev,
 +                                const char *subject, const char *after_subject,
                                  enum date_mode dmode)
  {
 -      unsigned long offset = 0;
        unsigned long beginning_of_body;
        int indent = 4;
        const char *msg = commit->buffer;
        int plain_non_ascii = 0;
        char *reencoded;
        const char *encoding;
 -      char *buf;
  
 -      if (fmt == CMIT_FMT_USERFORMAT)
 -              return format_commit_message(commit, msg, buf_p, space_p);
 +      if (fmt == CMIT_FMT_USERFORMAT) {
 +              format_commit_message(commit, user_format, sb);
 +              return;
 +      }
  
        encoding = (git_log_output_encoding
                    ? git_log_output_encoding
        reencoded = logmsg_reencode(commit, encoding);
        if (reencoded) {
                msg = reencoded;
 -              len = strlen(reencoded);
        }
  
        if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
        if (fmt == CMIT_FMT_EMAIL && !after_subject) {
                int i, ch, in_body;
  
 -              for (in_body = i = 0; (ch = msg[i]) && i < len; i++) {
 +              for (in_body = i = 0; (ch = msg[i]); i++) {
                        if (!in_body) {
                                /* author could be non 7-bit ASCII but
                                 * the log may be so; skip over the
                                 * header part first.
                                 */
 -                              if (ch == '\n' &&
 -                                  i + 1 < len && msg[i+1] == '\n')
 +                              if (ch == '\n' && msg[i+1] == '\n')
                                        in_body = 1;
                        }
                        else if (non_ascii(ch)) {
                }
        }
  
 -      pp_header(fmt, abbrev, dmode, encoding,
 -                commit, &msg, &len,
 -                &offset, buf_p, space_p);
 +      pp_header(fmt, abbrev, dmode, encoding, commit, &msg, sb);
        if (fmt != CMIT_FMT_ONELINE && !subject) {
 -              ALLOC_GROW(*buf_p, offset + 20, *space_p);
 -              (*buf_p)[offset++] = '\n';
 +              strbuf_addch(sb, '\n');
        }
  
        /* Skip excess blank lines at the beginning of body, if any... */
        for (;;) {
 -              int linelen = get_one_line(msg, len);
 +              int linelen = get_one_line(msg);
                int ll = linelen;
                if (!linelen)
                        break;
                if (!is_empty_line(msg, &ll))
                        break;
                msg += linelen;
 -              len -= linelen;
        }
  
        /* These formats treat the title line specially. */
 -      if (fmt == CMIT_FMT_ONELINE
 -          || fmt == CMIT_FMT_EMAIL)
 -              pp_title_line(fmt, &msg, &len, &offset,
 -                            buf_p, space_p, indent,
 -                            subject, after_subject, encoding,
 -                            plain_non_ascii);
 -
 -      beginning_of_body = offset;
 -      if (fmt != CMIT_FMT_ONELINE)
 -              pp_remainder(fmt, &msg, &len, &offset,
 -                           buf_p, space_p, indent);
 -
 -      while (offset && isspace((*buf_p)[offset-1]))
 -              offset--;
 +      if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL)
 +              pp_title_line(fmt, &msg, sb, subject,
 +                            after_subject, encoding, plain_non_ascii);
  
 -      ALLOC_GROW(*buf_p, offset + 20, *space_p);
 -      buf = *buf_p;
 +      beginning_of_body = sb->len;
 +      if (fmt != CMIT_FMT_ONELINE)
 +              pp_remainder(fmt, &msg, sb, indent);
 +      strbuf_rtrim(sb);
  
        /* Make sure there is an EOLN for the non-oneline case */
        if (fmt != CMIT_FMT_ONELINE)
 -              buf[offset++] = '\n';
 +              strbuf_addch(sb, '\n');
  
        /*
         * The caller may append additional body text in e-mail
         * format.  Make sure we did not strip the blank line
         * between the header and the body.
         */
 -      if (fmt == CMIT_FMT_EMAIL && offset <= beginning_of_body)
 -              buf[offset++] = '\n';
 -      buf[offset] = '\0';
 +      if (fmt == CMIT_FMT_EMAIL && sb->len <= beginning_of_body)
 +              strbuf_addch(sb, '\n');
        free(reencoded);
 -      return offset;
  }
  
  struct commit *pop_commit(struct commit_list **stack)
@@@ -1209,12 -1343,12 +1214,12 @@@ void sort_in_topological_order_fn(struc
                next=next->next;
        }
        /*
 -         * find the tips
 -         *
 -         * tips are nodes not reachable from any other node in the list
 -         *
 -         * the tips serve as a starting set for the work queue.
 -         */
 +       * find the tips
 +       *
 +       * tips are nodes not reachable from any other node in the list
 +       *
 +       * the tips serve as a starting set for the work queue.
 +       */
        next=*list;
        insert = &work;
        while (next) {
                        if (pn) {
                                /*
                                 * parents are only enqueued for emission
 -                                 * when all their children have been emitted thereby
 -                                 * guaranteeing topological order.
 -                                 */
 +                               * when all their children have been emitted thereby
 +                               * guaranteeing topological order.
 +                               */
                                pn->indegree--;
                                if (!pn->indegree) {
                                        if (!lifo)
                        parents=parents->next;
                }
                /*
 -                 * work_item is a commit all of whose children
 -                 * have already been emitted. we can emit it now.
 -                 */
 +               * work_item is a commit all of whose children
 +               * have already been emitted. we can emit it now.
 +               */
                *pptr = work_node->list_item;
                pptr = &(*pptr)->next;
                *pptr = NULL;
@@@ -1353,7 -1487,8 +1358,7 @@@ static struct commit_list *merge_bases(
  }
  
  struct commit_list *get_merge_bases(struct commit *one,
 -                                  struct commit *two,
 -                                    int cleanup)
 +                                      struct commit *two, int cleanup)
  {
        struct commit_list *list;
        struct commit **rslt;
index 050140d666b2d68ab91f6d41d64779178e99c70b,50b79ff8ff289f43ca92738656e37bc2545c5bc6..df4cedb8594ef795933bb02447d9a96815e2d64b
@@@ -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
+       git diff-tree -p "$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
                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
  
        else
                NEWHEAD=$(git rev-parse HEAD)
        fi &&
 -      message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
 -      git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
 -      git symbolic-ref HEAD $HEADNAME && {
 +      case $HEADNAME in
 +      refs/*)
 +              message="$GIT_REFLOG_ACTION: $HEADNAME onto $SHORTONTO)" &&
 +              git update-ref -m "$message" $HEADNAME $NEWHEAD $OLDHEAD &&
 +              git symbolic-ref HEAD $HEADNAME
 +              ;;
 +      esac && {
                test ! -f "$DOTEST"/verbose ||
-                       git diff --stat $(cat "$DOTEST"/head)..HEAD
+                       git diff-tree --stat $(cat "$DOTEST"/head)..HEAD
        } &&
        rm -rf "$DOTEST" &&
 +      git gc --auto &&
        warn "Successfully rebased and updated $HEADNAME."
  
        exit
@@@ -354,9 -330,7 +354,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
  
  
                HEADNAME=$(cat "$DOTEST"/head-name)
                HEAD=$(cat "$DOTEST"/head)
 -              git symbolic-ref HEAD $HEADNAME &&
 +              case $HEADNAME in
 +              refs/*)
 +                      git symbolic-ref HEAD $HEADNAME
 +                      ;;
 +              esac &&
                output git reset --hard $HEAD &&
                rm -rf "$DOTEST"
                exit
  
                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"
 -              git symbolic-ref HEAD > "$DOTEST"/head-name ||
 -                      die "Could not get HEAD"
 +              git symbolic-ref HEAD > "$DOTEST"/head-name 2> /dev/null ||
 +                      echo "detached HEAD" > "$DOTEST"/head-name
  
                echo $HEAD > "$DOTEST"/head
                echo $UPSTREAM > "$DOTEST"/upstream
@@@ -499,18 -468,17 +499,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 log-tree.c
index 23191543d5324f20622048103a821ded4d13b77a,b509c0c7ec239d4f426807406d94c34feeecf009..62edd344558049f05c33888de9d2fc2a854b51e9
@@@ -79,14 -79,25 +79,14 @@@ static int detect_any_signoff(char *let
        return seen_head && seen_name;
  }
  
 -static unsigned long append_signoff(char **buf_p, unsigned long *buf_sz_p,
 -                                  unsigned long at, const char *signoff)
 +static void append_signoff(struct strbuf *sb, const char *signoff)
  {
        static const char signed_off_by[] = "Signed-off-by: ";
        size_t signoff_len = strlen(signoff);
        int has_signoff = 0;
        char *cp;
 -      char *buf;
 -      unsigned long buf_sz;
  
 -      buf = *buf_p;
 -      buf_sz = *buf_sz_p;
 -      if (buf_sz <= at + strlen(signed_off_by) + signoff_len + 3) {
 -              buf_sz += strlen(signed_off_by) + signoff_len + 3;
 -              buf = xrealloc(buf, buf_sz);
 -              *buf_p = buf;
 -              *buf_sz_p = buf_sz;
 -      }
 -      cp = buf;
 +      cp = sb->buf;
  
        /* First see if we already have the sign-off by the signer */
        while ((cp = strstr(cp, signed_off_by))) {
                has_signoff = 1;
  
                cp += strlen(signed_off_by);
 -              if (cp + signoff_len >= buf + at)
 +              if (cp + signoff_len >= sb->buf + sb->len)
                        break;
                if (strncmp(cp, signoff, signoff_len))
                        continue;
                if (!isspace(cp[signoff_len]))
                        continue;
                /* we already have him */
 -              return at;
 +              return;
        }
  
        if (!has_signoff)
 -              has_signoff = detect_any_signoff(buf, at);
 +              has_signoff = detect_any_signoff(sb->buf, sb->len);
  
        if (!has_signoff)
 -              buf[at++] = '\n';
 +              strbuf_addch(sb, '\n');
  
 -      strcpy(buf + at, signed_off_by);
 -      at += strlen(signed_off_by);
 -      strcpy(buf + at, signoff);
 -      at += signoff_len;
 -      buf[at++] = '\n';
 -      buf[at] = 0;
 -      return at;
 +      strbuf_addstr(sb, signed_off_by);
 +      strbuf_add(sb, signoff, signoff_len);
 +      strbuf_addch(sb, '\n');
  }
  
  static unsigned int digits_in_number(unsigned int number)
  
  void show_log(struct rev_info *opt, const char *sep)
  {
 -      char *msgbuf = NULL;
 -      unsigned long msgbuf_len = 0;
 +      struct strbuf msgbuf;
        struct log_info *log = opt->loginfo;
        struct commit *commit = log->commit, *parent = log->parent;
        int abbrev = opt->diffopt.abbrev;
        int abbrev_commit = opt->abbrev_commit ? opt->abbrev : 40;
        const char *extra;
 -      int len;
        const char *subject = NULL, *extra_headers = opt->extra_headers;
  
        opt->loginfo = NULL;
        /*
         * And then the pretty-printed message itself
         */
 -      len = pretty_print_commit(opt->commit_format, commit, ~0u,
 -                                &msgbuf, &msgbuf_len, abbrev, subject,
 -                                extra_headers, opt->date_mode);
 +      strbuf_init(&msgbuf, 0);
 +      pretty_print_commit(opt->commit_format, commit, &msgbuf,
 +                                abbrev, subject, extra_headers, opt->date_mode);
  
        if (opt->add_signoff)
 -              len = append_signoff(&msgbuf, &msgbuf_len, len,
 -                                   opt->add_signoff);
 +              append_signoff(&msgbuf, opt->add_signoff);
        if (opt->show_log_size)
 -              printf("log size %i\n", len);
 +              printf("log size %i\n", (int)msgbuf.len);
  
 -      printf("%s%s%s", msgbuf, extra, sep);
 -      free(msgbuf);
 +      if (msgbuf.len)
 +              printf("%s%s%s", msgbuf.buf, extra, sep);
 +      strbuf_release(&msgbuf);
  }
  
  int log_tree_diff_flush(struct rev_info *opt)
                 * output for readability.
                 */
                show_log(opt, opt->diffopt.msg_sep);
-               if (opt->verbose_header &&
+               if ((opt->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT) &&
+                   opt->verbose_header &&
                    opt->commit_format != CMIT_FMT_ONELINE) {
                        int pch = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
                        if ((pch & opt->diffopt.output_format) == pch)
diff --combined revision.c
index 5d294be308eed42c244c4424618b43d11048d4e6,48756b5d4466d06123922573e4330fc4344c1dcb..e76da0d448f81d8b6b496990bc2568fef3662671
@@@ -1134,7 -1134,22 +1134,7 @@@ int setup_revisions(int argc, const cha
                                continue;
                        }
                        if (!strncmp(arg, "--date=", 7)) {
 -                              if (!strcmp(arg + 7, "relative"))
 -                                      revs->date_mode = DATE_RELATIVE;
 -                              else if (!strcmp(arg + 7, "iso8601") ||
 -                                       !strcmp(arg + 7, "iso"))
 -                                      revs->date_mode = DATE_ISO8601;
 -                              else if (!strcmp(arg + 7, "rfc2822") ||
 -                                       !strcmp(arg + 7, "rfc"))
 -                                      revs->date_mode = DATE_RFC2822;
 -                              else if (!strcmp(arg + 7, "short"))
 -                                      revs->date_mode = DATE_SHORT;
 -                              else if (!strcmp(arg + 7, "local"))
 -                                      revs->date_mode = DATE_LOCAL;
 -                              else if (!strcmp(arg + 7, "default"))
 -                                      revs->date_mode = DATE_NORMAL;
 -                              else
 -                                      die("unknown date format %s", arg);
 +                              revs->date_mode = parse_date_format(arg + 7);
                                continue;
                        }
                        if (!strcmp(arg, "--log-size")) {
        if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)
                revs->diff = 1;
  
-       /* Pickaxe needs diffs */
-       if (revs->diffopt.pickaxe)
+       /* Pickaxe and rename following needs diffs */
+       if (revs->diffopt.pickaxe || revs->diffopt.follow_renames)
                revs->diff = 1;
  
        if (revs->topo_order)