Sync with v1.8.1.4
authorJunio C Hamano <gitster@pobox.com>
Wed, 20 Feb 2013 05:57:27 +0000 (21:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 20 Feb 2013 05:57:27 +0000 (21:57 -0800)
1  2 
Documentation/git.txt
imap-send.c
diff --combined Documentation/git.txt
index 2d975e3b0143cd1a56e2709d0d3977925906fcf0,da0115f3d0569c2a7addaf5eff49a06d0ebf572c..0847cdcc6803f0af19d203b613222ac7e22d0cdd
@@@ -27,11 -27,11 +27,11 @@@ commands.  The link:user-manual.html[Gi
  in-depth introduction.
  
  After you mastered the basic concepts, you can come back to this
 -page to learn what commands git offers.  You can learn more about
 -individual git commands with "git help command".  linkgit:gitcli[7]
 +page to learn what commands Git offers.  You can learn more about
 +individual Git commands with "git help command".  linkgit:gitcli[7]
  manual page gives you an overview of the command line command syntax.
  
 -Formatted and hyperlinked version of the latest git documentation
 +Formatted and hyperlinked version of the latest Git documentation
  can be viewed at `http://git-htmldocs.googlecode.com/git/git.html`.
  
  ifdef::stalenotes[]
  ============
  
  You are reading the documentation for the latest (possibly
 -unreleased) version of git, that is available from 'master'
 +unreleased) version of Git, that is available from 'master'
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
- * link:v1.8.1.3/git.html[documentation for release 1.8.1.3]
+ * link:v1.8.1.4/git.html[documentation for release 1.8.1.4]
  
  * release notes for
+   link:RelNotes/1.8.1.4.txt[1.8.1.4],
    link:RelNotes/1.8.1.3.txt[1.8.1.3],
    link:RelNotes/1.8.1.2.txt[1.8.1.2],
    link:RelNotes/1.8.1.1.txt[1.8.1.1],
@@@ -357,12 -358,12 +358,12 @@@ endif::stalenotes[
  OPTIONS
  -------
  --version::
 -      Prints the git suite version that the 'git' program came from.
 +      Prints the Git suite version that the 'git' program came from.
  
  --help::
        Prints the synopsis and a list of the most commonly used
        commands. If the option '--all' or '-a' is given then all
 -      available commands are printed. If a git command is named this
 +      available commands are printed. If a Git command is named this
        option will bring up the manual page for that command.
  +
  Other options are available to control how the manual page is
@@@ -377,22 -378,22 +378,22 @@@ help ...`
        'git config' (subkeys separated by dots).
  
  --exec-path[=<path>]::
 -      Path to wherever your core git programs are installed.
 +      Path to wherever your core Git programs are installed.
        This can also be controlled by setting the GIT_EXEC_PATH
        environment variable. If no path is given, 'git' will print
        the current setting and then exit.
  
  --html-path::
 -      Print the path, without trailing slash, where git's HTML
 +      Print the path, without trailing slash, where Git's HTML
        documentation is installed and exit.
  
  --man-path::
        Print the manpath (see `man(1)`) for the man pages for
 -      this version of git and exit.
 +      this version of Git and exit.
  
  --info-path::
        Print the path where the Info files documenting this
 -      version of git are installed and exit.
 +      version of Git are installed and exit.
  
  -p::
  --paginate::
        below).
  
  --no-pager::
 -      Do not pipe git output into a pager.
 +      Do not pipe Git output into a pager.
  
  --git-dir=<path>::
        Set the path to the repository. This can also be controlled by
        more detailed discussion).
  
  --namespace=<path>::
 -      Set the git namespace.  See linkgit:gitnamespaces[7] for more
 +      Set the Git namespace.  See linkgit:gitnamespaces[7] for more
        details.  Equivalent to setting the `GIT_NAMESPACE` environment
        variable.
  
        directory.
  
  --no-replace-objects::
 -      Do not use replacement refs to replace git objects. See
 +      Do not use replacement refs to replace Git objects. See
        linkgit:git-replace[1] for more information.
  
 +--literal-pathspecs::
 +      Treat pathspecs literally, rather than as glob patterns. This is
 +      equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
 +      variable to `1`.
 +
  
  GIT COMMANDS
  ------------
  
 -We divide git into high level ("porcelain") commands and low level
 +We divide Git into high level ("porcelain") commands and low level
  ("plumbing") commands.
  
  High-level commands (porcelain)
@@@ -477,7 -473,7 +478,7 @@@ include::cmds-foreignscminterface.txt[
  Low-level commands (plumbing)
  -----------------------------
  
 -Although git includes its
 +Although Git includes its
  own porcelain layer, its low-level commands are sufficient to support
  development of alternative porcelains.  Developers of such porcelains
  might start by reading about linkgit:git-update-index[1] and
@@@ -535,9 -531,10 +536,9 @@@ include::cmds-purehelpers.txt[
  Configuration Mechanism
  -----------------------
  
 -Starting from 0.99.9 (actually mid 0.99.8.GIT), `.git/config` file
 -is used to hold per-repository configuration options.  It is a
 -simple text file modeled after `.ini` format familiar to some
 -people.  Here is an example:
 +Git uses a simple text format to store customizations that are per
 +repository and are per user.  Such a configuration file may look
 +like this:
  
  ------------
  #
  ; user identity
  [user]
        name = "Junio C Hamano"
 -      email = "junkio@twinsun.com"
 +      email = "gitster@pobox.com"
  
  ------------
  
  Various commands read from the configuration file and adjust
  their operation accordingly.  See linkgit:git-config[1] for a
 -list.
 +list and more details about the configuration mechanism.
  
  
  Identifier Terminology
  
  Symbolic Identifiers
  --------------------
 -Any git command accepting any <object> can also use the following
 +Any Git command accepting any <object> can also use the following
  symbolic notation:
  
  HEAD::
@@@ -633,13 -630,13 +634,13 @@@ Please see linkgit:gitglossary[7]
  
  Environment Variables
  ---------------------
 -Various git commands use the following environment variables:
 +Various Git commands use the following environment variables:
  
 -The git Repository
 +The Git Repository
  ~~~~~~~~~~~~~~~~~~
 -These environment variables apply to 'all' core git commands. Nb: it
 +These environment variables apply to 'all' core Git commands. Nb: it
  is worth noting that they may be used/overridden by SCMS sitting above
 -git so take care if using Cogito etc.
 +Git so take care if using Cogito etc.
  
  'GIT_INDEX_FILE'::
        This environment allows the specification of an alternate
        directory is used.
  
  'GIT_ALTERNATE_OBJECT_DIRECTORIES'::
 -      Due to the immutable nature of git objects, old objects can be
 +      Due to the immutable nature of Git objects, old objects can be
        archived into shared, read-only directories. This variable
        specifies a ":" separated (on Windows ";" separated) list
 -      of git object directories which can be used to search for git
 +      of Git object directories which can be used to search for Git
        objects. New objects will not be written to these directories.
  
  'GIT_DIR'::
        option and the core.worktree configuration variable.
  
  'GIT_NAMESPACE'::
 -      Set the git namespace; see linkgit:gitnamespaces[7] for details.
 +      Set the Git namespace; see linkgit:gitnamespaces[7] for details.
        The '--namespace' command-line option also sets this value.
  
  'GIT_CEILING_DIRECTORIES'::
        This should be a colon-separated list of absolute paths.
 -      If set, it is a list of directories that git should not chdir
 +      If set, it is a list of directories that Git should not chdir
        up into while looking for a repository directory.
        It will not exclude the current working directory or
        a GIT_DIR set on the command line or in the environment.
  
  'GIT_DISCOVERY_ACROSS_FILESYSTEM'::
        When run in a directory that does not have ".git" repository
 -      directory, git tries to find such a directory in the parent
 +      directory, Git tries to find such a directory in the parent
        directories to find the top of the working tree, but by default it
        does not cross filesystem boundaries.  This environment variable
 -      can be set to true to tell git not to stop at filesystem
 +      can be set to true to tell Git not to stop at filesystem
        boundaries.  Like 'GIT_CEILING_DIRECTORIES', this will not affect
        an explicit repository directory set via 'GIT_DIR' or on the
        command line.
  
 -git Commits
 +Git Commits
  ~~~~~~~~~~~
  'GIT_AUTHOR_NAME'::
  'GIT_AUTHOR_EMAIL'::
  'EMAIL'::
        see linkgit:git-commit-tree[1]
  
 -git Diffs
 +Git Diffs
  ~~~~~~~~~
  'GIT_DIFF_OPTS'::
        Only valid setting is "--unified=??" or "-u??" to set the
        number of context lines shown when a unified diff is created.
        This takes precedence over any "-U" or "--unified" option
 -      value passed on the git diff command line.
 +      value passed on the Git diff command line.
  
  'GIT_EXTERNAL_DIFF'::
        When the environment variable 'GIT_EXTERNAL_DIFF' is set, the
@@@ -746,13 -743,13 +747,13 @@@ othe
  
  'GIT_PAGER'::
        This environment variable overrides `$PAGER`. If it is set
 -      to an empty string or to the value "cat", git will not launch
 +      to an empty string or to the value "cat", Git will not launch
        a pager.  See also the `core.pager` option in
        linkgit:git-config[1].
  
  'GIT_EDITOR'::
        This environment variable overrides `$EDITOR` and `$VISUAL`.
 -      It is used by several git commands when, on interactive mode,
 +      It is used by several Git commands when, on interactive mode,
        an editor is to be launched. See also linkgit:git-var[1]
        and the `core.editor` option in linkgit:git-config[1].
  
@@@ -773,7 -770,7 +774,7 @@@ personal `.ssh/config` file.  Please co
  for further details.
  
  'GIT_ASKPASS'::
 -      If this environment variable is set, then git commands which need to
 +      If this environment variable is set, then Git commands which need to
        acquire passwords or passphrases (e.g. for HTTP or IMAP authentication)
        will call this program with a suitable prompt as command line argument
        and read the password from its STDOUT. See also the 'core.askpass'
        after each commit-oriented record have been flushed.   If this
        variable is set to "0", the output of these commands will be done
        using completely buffered I/O.   If this environment variable is
 -      not set, git will choose buffered or record-oriented flushing
 +      not set, Git will choose buffered or record-oriented flushing
        based on whether stdout appears to be redirected to a file or not.
  
  'GIT_TRACE'::
        If this variable is set to "1", "2" or "true" (comparison
 -      is case insensitive), git will print `trace:` messages on
 +      is case insensitive), Git will print `trace:` messages on
        stderr telling about alias expansion, built-in command
        execution and external command execution.
        If this variable is set to an integer value greater than 1
 -      and lower than 10 (strictly) then git will interpret this
 +      and lower than 10 (strictly) then Git will interpret this
        value as an open file descriptor and will try to write the
        trace messages into this file descriptor.
        Alternatively, if this variable is set to an absolute path
 -      (starting with a '/' character), git will interpret this
 +      (starting with a '/' character), Git will interpret this
        as a file path and will try to write the trace messages
        into it.
  
 +GIT_LITERAL_PATHSPECS::
 +      Setting this variable to `1` will cause Git to treat all
 +      pathspecs literally, rather than as glob patterns. For example,
 +      running `GIT_LITERAL_PATHSPECS=1 git log -- '*.c'` will search
 +      for commits that touch the path `*.c`, not any paths that the
 +      glob `*.c` matches. You might want this if you are feeding
 +      literal paths to Git (e.g., paths previously given to you by
 +      `git ls-tree`, `--raw` diff output, etc).
 +
 +
  Discussion[[Discussion]]
  ------------------------
  
  More detail on the following is available from the
 -link:user-manual.html#git-concepts[git concepts chapter of the
 +link:user-manual.html#git-concepts[Git concepts chapter of the
  user-manual] and linkgit:gitcore-tutorial[7].
  
 -A git project normally consists of a working directory with a ".git"
 +A Git project normally consists of a working directory with a ".git"
  subdirectory at the top level.  The .git directory contains, among other
  things, a compressed object database representing the complete history
  of the project, an "index" file which links that history to the current
@@@ -878,12 -865,12 +879,12 @@@ FURTHER DOCUMENTATIO
  ---------------------
  
  See the references in the "description" section to get started
 -using git.  The following is probably more detail than necessary
 +using Git.  The following is probably more detail than necessary
  for a first-time user.
  
 -The link:user-manual.html#git-concepts[git concepts chapter of the
 +The link:user-manual.html#git-concepts[Git concepts chapter of the
  user-manual] and linkgit:gitcore-tutorial[7] both provide
 -introductions to the underlying git architecture.
 +introductions to the underlying Git architecture.
  
  See linkgit:gitworkflows[7] for an overview of recommended workflows.
  
@@@ -891,7 -878,7 +892,7 @@@ See also the link:howto-index.html[howt
  examples.
  
  The internals are documented in the
 -link:technical/api-index.html[GIT API documentation].
 +link:technical/api-index.html[Git API documentation].
  
  Users migrating from CVS may also want to
  read linkgit:gitcvs-migration[7].
  Authors
  -------
  Git was started by Linus Torvalds, and is currently maintained by Junio
 -C Hamano. Numerous contributions have come from the git mailing list
 +C Hamano. Numerous contributions have come from the Git mailing list
  <git@vger.kernel.org>.  http://www.ohloh.net/p/git/contributors/summary
  gives you a more complete list of contributors.
  
diff --combined imap-send.c
index 21dc20b57d392c5789043968d28a3b958b2b50c7,ef500111ec0bca6514e519d39e7a269a7342cddf..43ac4e0bdfdba8850eff176b53d6bd071e8f424c
@@@ -31,8 -31,50 +31,9 @@@ typedef void *SSL
  #else
  #include <openssl/evp.h>
  #include <openssl/hmac.h>
+ #include <openssl/x509v3.h>
  #endif
  
 -struct store_conf {
 -      char *name;
 -      const char *path; /* should this be here? its interpretation is driver-specific */
 -      char *map_inbox;
 -      char *trash;
 -      unsigned max_size; /* off_t is overkill */
 -      unsigned trash_remote_new:1, trash_only_new:1;
 -};
 -
 -/* For message->status */
 -#define M_RECENT       (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */
 -#define M_DEAD         (1<<1) /* expunged */
 -#define M_FLAGS        (1<<2) /* flags fetched */
 -
 -struct message {
 -      struct message *next;
 -      size_t size; /* zero implies "not fetched" */
 -      int uid;
 -      unsigned char flags, status;
 -};
 -
 -struct store {
 -      struct store_conf *conf; /* foreign */
 -
 -      /* currently open mailbox */
 -      const char *name; /* foreign! maybe preset? */
 -      char *path; /* own */
 -      struct message *msgs; /* own */
 -      int uidvalidity;
 -      unsigned char opts; /* maybe preset? */
 -      /* note that the following do _not_ reflect stats from msgs, but mailbox totals */
 -      int count; /* # of messages */
 -      int recent; /* # of recent messages - don't trust this beyond the initial read */
 -};
 -
 -struct msg_data {
 -      char *data;
 -      int len;
 -      unsigned char flags;
 -};
 -
  static const char imap_send_usage[] = "git imap-send < <mbox>";
  
  #undef DRV_OK
@@@ -50,6 -92,8 +51,6 @@@ static void imap_warn(const char *, ...
  
  static char *next_arg(char **);
  
 -static void free_generic_messages(struct message *);
 -
  __attribute__((format (printf, 3, 4)))
  static int nfsnprintf(char *buf, int blen, const char *fmt, ...);
  
@@@ -93,6 -137,20 +94,6 @@@ static struct imap_server_conf server 
        NULL,   /* auth_method */
  };
  
 -struct imap_store_conf {
 -      struct store_conf gen;
 -      struct imap_server_conf *server;
 -};
 -
 -#define NIL   (void *)0x1
 -#define LIST  (void *)0x2
 -
 -struct imap_list {
 -      struct imap_list *next, *child;
 -      char *val;
 -      int len;
 -};
 -
  struct imap_socket {
        int fd[2];
        SSL *ssl;
@@@ -109,6 -167,7 +110,6 @@@ struct imap_cmd
  
  struct imap {
        int uidnext; /* from SELECT responses */
 -      struct imap_list *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */
        unsigned caps, rcaps; /* CAPABILITY results */
        /* command queue */
        int nexttag, num_in_progress, literal_pending;
  };
  
  struct imap_store {
 -      struct store gen;
 +      /* currently open mailbox */
 +      const char *name; /* foreign! maybe preset? */
        int uidvalidity;
        struct imap *imap;
        const char *prefix;
 -      unsigned /*currentnc:1,*/ trashnc:1;
  };
  
  struct imap_cmd_cb {
@@@ -168,6 -227,14 +169,6 @@@ static const char *cap_list[] = 
  static int get_cmd_result(struct imap_store *ctx, struct imap_cmd *tcmd);
  
  
 -static const char *Flags[] = {
 -      "Draft",
 -      "Flagged",
 -      "Answered",
 -      "Seen",
 -      "Deleted",
 -};
 -
  #ifndef NO_OPENSSL
  static void ssl_socket_perror(const char *func)
  {
@@@ -200,12 -267,64 +201,64 @@@ static void socket_perror(const char *f
        }
  }
  
+ #ifdef NO_OPENSSL
  static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
  {
- #ifdef NO_OPENSSL
        fprintf(stderr, "SSL requested but SSL support not compiled in\n");
        return -1;
+ }
  #else
+ static int host_matches(const char *host, const char *pattern)
+ {
+       if (pattern[0] == '*' && pattern[1] == '.') {
+               pattern += 2;
+               if (!(host = strchr(host, '.')))
+                       return 0;
+               host++;
+       }
+       return *host && *pattern && !strcasecmp(host, pattern);
+ }
+ static int verify_hostname(X509 *cert, const char *hostname)
+ {
+       int len;
+       X509_NAME *subj;
+       char cname[1000];
+       int i, found;
+       STACK_OF(GENERAL_NAME) *subj_alt_names;
+       /* try the DNS subjectAltNames */
+       found = 0;
+       if ((subj_alt_names = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL))) {
+               int num_subj_alt_names = sk_GENERAL_NAME_num(subj_alt_names);
+               for (i = 0; !found && i < num_subj_alt_names; i++) {
+                       GENERAL_NAME *subj_alt_name = sk_GENERAL_NAME_value(subj_alt_names, i);
+                       if (subj_alt_name->type == GEN_DNS &&
+                           strlen((const char *)subj_alt_name->d.ia5->data) == (size_t)subj_alt_name->d.ia5->length &&
+                           host_matches(hostname, (const char *)(subj_alt_name->d.ia5->data)))
+                               found = 1;
+               }
+               sk_GENERAL_NAME_pop_free(subj_alt_names, GENERAL_NAME_free);
+       }
+       if (found)
+               return 0;
+       /* try the common name */
+       if (!(subj = X509_get_subject_name(cert)))
+               return error("cannot get certificate subject");
+       if ((len = X509_NAME_get_text_by_NID(subj, NID_commonName, cname, sizeof(cname))) < 0)
+               return error("cannot get certificate common name");
+       if (strlen(cname) == (size_t)len && host_matches(hostname, cname))
+               return 0;
+       return error("certificate owner '%s' does not match hostname '%s'",
+                    cname, hostname);
+ }
+ static int ssl_socket_connect(struct imap_socket *sock, int use_tls_only, int verify)
+ {
  #if (OPENSSL_VERSION_NUMBER >= 0x10000000L)
        const SSL_METHOD *meth;
  #else
  #endif
        SSL_CTX *ctx;
        int ret;
+       X509 *cert;
  
        SSL_library_init();
        SSL_load_error_strings();
                return -1;
        }
  
+       if (verify) {
+               /* make sure the hostname matches that of the certificate */
+               cert = SSL_get_peer_certificate(sock->ssl);
+               if (!cert)
+                       return error("unable to get peer certificate.");
+               if (verify_hostname(cert, server.host) < 0)
+                       return -1;
+       }
        return 0;
- #endif
  }
+ #endif
  
  static int socket_read(struct imap_socket *sock, char *buf, int len)
  {
@@@ -411,6 -540,16 +474,6 @@@ static char *next_arg(char **s
        return ret;
  }
  
 -static void free_generic_messages(struct message *msgs)
 -{
 -      struct message *tmsg;
 -
 -      for (; msgs; msgs = tmsg) {
 -              tmsg = msgs->next;
 -              free(msgs);
 -      }
 -}
 -
  static int nfsnprintf(char *buf, int blen, const char *fmt, ...)
  {
        int ret;
@@@ -538,9 -677,35 +601,9 @@@ static int imap_exec_m(struct imap_stor
        }
  }
  
 -static int is_atom(struct imap_list *list)
 -{
 -      return list && list->val && list->val != NIL && list->val != LIST;
 -}
 -
 -static int is_list(struct imap_list *list)
 +static int skip_imap_list_l(char **sp, int level)
  {
 -      return list && list->val == LIST;
 -}
 -
 -static void free_list(struct imap_list *list)
 -{
 -      struct imap_list *tmp;
 -
 -      for (; list; list = tmp) {
 -              tmp = list->next;
 -              if (is_list(list))
 -                      free_list(list->child);
 -              else if (is_atom(list))
 -                      free(list->val);
 -              free(list);
 -      }
 -}
 -
 -static int parse_imap_list_l(struct imap *imap, char **sp, struct imap_list **curp, int level)
 -{
 -      struct imap_list *cur;
 -      char *s = *sp, *p;
 -      int n, bytes;
 +      char *s = *sp;
  
        for (;;) {
                while (isspace((unsigned char)*s))
                        s++;
                        break;
                }
 -              *curp = cur = xmalloc(sizeof(*cur));
 -              curp = &cur->next;
 -              cur->val = NULL; /* for clean bail */
                if (*s == '(') {
                        /* sublist */
                        s++;
 -                      cur->val = LIST;
 -                      if (parse_imap_list_l(imap, &s, &cur->child, level + 1))
 -                              goto bail;
 -              } else if (imap && *s == '{') {
 -                      /* literal */
 -                      bytes = cur->len = strtol(s + 1, &s, 10);
 -                      if (*s != '}')
 -                              goto bail;
 -
 -                      s = cur->val = xmalloc(cur->len);
 -
 -                      /* dump whats left over in the input buffer */
 -                      n = imap->buf.bytes - imap->buf.offset;
 -
 -                      if (n > bytes)
 -                              /* the entire message fit in the buffer */
 -                              n = bytes;
 -
 -                      memcpy(s, imap->buf.buf + imap->buf.offset, n);
 -                      s += n;
 -                      bytes -= n;
 -
 -                      /* mark that we used part of the buffer */
 -                      imap->buf.offset += n;
 -
 -                      /* now read the rest of the message */
 -                      while (bytes > 0) {
 -                              if ((n = socket_read(&imap->buf.sock, s, bytes)) <= 0)
 -                                      goto bail;
 -                              s += n;
 -                              bytes -= n;
 -                      }
 -
 -                      if (buffer_gets(&imap->buf, &s))
 +                      if (skip_imap_list_l(&s, level + 1))
                                goto bail;
                } else if (*s == '"') {
                        /* quoted string */
                        s++;
 -                      p = s;
                        for (; *s != '"'; s++)
                                if (!*s)
                                        goto bail;
 -                      cur->len = s - p;
                        s++;
 -                      cur->val = xmemdupz(p, cur->len);
                } else {
                        /* atom */
 -                      p = s;
                        for (; *s && !isspace((unsigned char)*s); s++)
                                if (level && *s == ')')
                                        break;
 -                      cur->len = s - p;
 -                      if (cur->len == 3 && !memcmp("NIL", p, 3))
 -                              cur->val = NIL;
 -                      else
 -                              cur->val = xmemdupz(p, cur->len);
                }
  
                if (!level)
                        goto bail;
        }
        *sp = s;
 -      *curp = NULL;
        return 0;
  
  bail:
 -      *curp = NULL;
        return -1;
  }
  
 -static struct imap_list *parse_imap_list(struct imap *imap, char **sp)
 +static void skip_list(char **sp)
  {
 -      struct imap_list *head;
 -
 -      if (!parse_imap_list_l(imap, sp, &head, 0))
 -              return head;
 -      free_list(head);
 -      return NULL;
 -}
 -
 -static struct imap_list *parse_list(char **sp)
 -{
 -      return parse_imap_list(NULL, sp);
 +      skip_imap_list_l(sp, 0);
  }
  
  static void parse_capability(struct imap *imap, char *cmd)
@@@ -614,7 -836,7 +677,7 @@@ static int parse_response_code(struct i
        *p++ = 0;
        arg = next_arg(&s);
        if (!strcmp("UIDVALIDITY", arg)) {
 -              if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg))) {
 +              if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg))) {
                        fprintf(stderr, "IMAP error: malformed UIDVALIDITY status\n");
                        return RESP_BAD;
                }
                for (; isspace((unsigned char)*p); p++);
                fprintf(stderr, "*** IMAP ALERT *** %s\n", p);
        } else if (cb && cb->ctx && !strcmp("APPENDUID", arg)) {
 -              if (!(arg = next_arg(&s)) || !(ctx->gen.uidvalidity = atoi(arg)) ||
 +              if (!(arg = next_arg(&s)) || !(ctx->uidvalidity = atoi(arg)) ||
                    !(arg = next_arg(&s)) || !(*(int *)cb->ctx = atoi(arg))) {
                        fprintf(stderr, "IMAP error: malformed APPENDUID status\n");
                        return RESP_BAD;
@@@ -661,28 -883,20 +724,28 @@@ static int get_cmd_result(struct imap_s
                        }
  
                        if (!strcmp("NAMESPACE", arg)) {
 -                              imap->ns_personal = parse_list(&cmd);
 -                              imap->ns_other = parse_list(&cmd);
 -                              imap->ns_shared = parse_list(&cmd);
 +                              /* rfc2342 NAMESPACE response. */
 +                              skip_list(&cmd); /* Personal mailboxes */
 +                              skip_list(&cmd); /* Others' mailboxes */
 +                              skip_list(&cmd); /* Shared mailboxes */
                        } else if (!strcmp("OK", arg) || !strcmp("BAD", arg) ||
                                   !strcmp("NO", arg) || !strcmp("BYE", arg)) {
                                if ((resp = parse_response_code(ctx, NULL, cmd)) != RESP_OK)
                                        return resp;
 -                      } else if (!strcmp("CAPABILITY", arg))
 +                      } else if (!strcmp("CAPABILITY", arg)) {
                                parse_capability(imap, cmd);
 -                      else if ((arg1 = next_arg(&cmd))) {
 -                              if (!strcmp("EXISTS", arg1))
 -                                      ctx->gen.count = atoi(arg);
 -                              else if (!strcmp("RECENT", arg1))
 -                                      ctx->gen.recent = atoi(arg);
 +                      } else if ((arg1 = next_arg(&cmd))) {
 +                              ; /*
 +                                 * Unhandled response-data with at least two words.
 +                                 * Ignore it.
 +                                 *
 +                                 * NEEDSWORK: Previously this case handled '<num> EXISTS'
 +                                 * and '<num> RECENT' but as a probably-unintended side
 +                                 * effect it ignores other unrecognized two-word
 +                                 * responses.  imap-send doesn't ever try to read
 +                                 * messages or mailboxes these days, so consider
 +                                 * eliminating this case.
 +                                 */
                        } else {
                                fprintf(stderr, "IMAP error: unable to parse untagged response\n");
                                return RESP_BAD;
@@@ -784,12 -998,16 +847,12 @@@ static void imap_close_server(struct im
                imap_exec(ictx, NULL, "LOGOUT");
                socket_shutdown(&imap->buf.sock);
        }
 -      free_list(imap->ns_personal);
 -      free_list(imap->ns_other);
 -      free_list(imap->ns_shared);
        free(imap);
  }
  
 -static void imap_close_store(struct store *ctx)
 +static void imap_close_store(struct imap_store *ctx)
  {
 -      imap_close_server((struct imap_store *)ctx);
 -      free_generic_messages(ctx->msgs);
 +      imap_close_server(ctx);
        free(ctx);
  }
  
@@@ -874,7 -1092,7 +937,7 @@@ static int auth_cram_md5(struct imap_st
        return 0;
  }
  
 -static struct store *imap_open_store(struct imap_server_conf *srvc)
 +static struct imap_store *imap_open_store(struct imap_server_conf *srvc)
  {
        struct imap_store *ctx;
        struct imap *imap;
        } /* !preauth */
  
        ctx->prefix = "";
 -      ctx->trashnc = 1;
 -      return (struct store *)ctx;
 +      return ctx;
  
  bail:
 -      imap_close_store(&ctx->gen);
 +      imap_close_store(ctx);
        return NULL;
  }
  
 -static int imap_make_flags(int flags, char *buf)
 -{
 -      const char *s;
 -      unsigned i, d;
 -
 -      for (i = d = 0; i < ARRAY_SIZE(Flags); i++)
 -              if (flags & (1 << i)) {
 -                      buf[d++] = ' ';
 -                      buf[d++] = '\\';
 -                      for (s = Flags[i]; *s; s++)
 -                              buf[d++] = *s;
 -              }
 -      buf[0] = '(';
 -      buf[d++] = ')';
 -      return d;
 -}
 -
 -static void lf_to_crlf(struct msg_data *msg)
 +/*
 + * Insert CR characters as necessary in *msg to ensure that every LF
 + * character in *msg is preceded by a CR.
 + */
 +static void lf_to_crlf(struct strbuf *msg)
  {
        char *new;
 -      int i, j, lfnum = 0;
 -
 -      if (msg->data[0] == '\n')
 -              lfnum++;
 -      for (i = 1; i < msg->len; i++) {
 -              if (msg->data[i - 1] != '\r' && msg->data[i] == '\n')
 -                      lfnum++;
 +      size_t i, j;
 +      char lastc;
 +
 +      /* First pass: tally, in j, the size of the new string: */
 +      for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
 +              if (msg->buf[i] == '\n' && lastc != '\r')
 +                      j++; /* a CR will need to be added here */
 +              lastc = msg->buf[i];
 +              j++;
        }
  
 -      new = xmalloc(msg->len + lfnum);
 -      if (msg->data[0] == '\n') {
 -              new[0] = '\r';
 -              new[1] = '\n';
 -              i = 1;
 -              j = 2;
 -      } else {
 -              new[0] = msg->data[0];
 -              i = 1;
 -              j = 1;
 -      }
 -      for ( ; i < msg->len; i++) {
 -              if (msg->data[i] != '\n') {
 -                      new[j++] = msg->data[i];
 -                      continue;
 -              }
 -              if (msg->data[i - 1] != '\r')
 +      new = xmalloc(j + 1);
 +
 +      /*
 +       * Second pass: write the new string.  Note that this loop is
 +       * otherwise identical to the first pass.
 +       */
 +      for (i = j = 0, lastc = '\0'; i < msg->len; i++) {
 +              if (msg->buf[i] == '\n' && lastc != '\r')
                        new[j++] = '\r';
 -              /* otherwise it already had CR before */
 -              new[j++] = '\n';
 +              lastc = new[j++] = msg->buf[i];
        }
 -      msg->len += lfnum;
 -      free(msg->data);
 -      msg->data = new;
 +      strbuf_attach(msg, new, j, j + 1);
  }
  
 -static int imap_store_msg(struct store *gctx, struct msg_data *data)
 +/*
 + * Store msg to IMAP.  Also detach and free the data from msg->data,
 + * leaving msg->data empty.
 + */
 +static int imap_store_msg(struct imap_store *ctx, struct strbuf *msg)
  {
 -      struct imap_store *ctx = (struct imap_store *)gctx;
        struct imap *imap = ctx->imap;
        struct imap_cmd_cb cb;
        const char *prefix, *box;
 -      int ret, d;
 -      char flagstr[128];
 +      int ret;
  
 -      lf_to_crlf(data);
 +      lf_to_crlf(msg);
        memset(&cb, 0, sizeof(cb));
  
 -      cb.dlen = data->len;
 -      cb.data = xmalloc(cb.dlen);
 -      memcpy(cb.data, data->data, data->len);
 -
 -      d = 0;
 -      if (data->flags) {
 -              d = imap_make_flags(data->flags, flagstr);
 -              flagstr[d++] = ' ';
 -      }
 -      flagstr[d] = 0;
 +      cb.dlen = msg->len;
 +      cb.data = strbuf_detach(msg, NULL);
  
 -      box = gctx->name;
 +      box = ctx->name;
        prefix = !strcmp(box, "INBOX") ? "" : ctx->prefix;
        cb.create = 0;
 -      ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr);
 +      ret = imap_exec_m(ctx, &cb, "APPEND \"%s%s\" ", prefix, box);
        imap->caps = imap->rcaps;
        if (ret != DRV_OK)
                return ret;
 -      gctx->count++;
  
        return DRV_OK;
  }
  
 -static void encode_html_chars(struct strbuf *p)
 -{
 -      int i;
 -      for (i = 0; i < p->len; i++) {
 -              if (p->buf[i] == '&')
 -                      strbuf_splice(p, i, 1, "&amp;", 5);
 -              if (p->buf[i] == '<')
 -                      strbuf_splice(p, i, 1, "&lt;", 4);
 -              if (p->buf[i] == '>')
 -                      strbuf_splice(p, i, 1, "&gt;", 4);
 -              if (p->buf[i] == '"')
 -                      strbuf_splice(p, i, 1, "&quot;", 6);
 -      }
 -}
 -static void wrap_in_html(struct msg_data *msg)
 +static void wrap_in_html(struct strbuf *msg)
  {
        struct strbuf buf = STRBUF_INIT;
 -      struct strbuf **lines;
 -      struct strbuf **p;
        static char *content_type = "Content-Type: text/html;\n";
        static char *pre_open = "<pre>\n";
        static char *pre_close = "</pre>\n";
 -      int added_header = 0;
 -
 -      strbuf_attach(&buf, msg->data, msg->len, msg->len);
 -      lines = strbuf_split(&buf, '\n');
 -      strbuf_release(&buf);
 -      for (p = lines; *p; p++) {
 -              if (! added_header) {
 -                      if ((*p)->len == 1 && *((*p)->buf) == '\n') {
 -                              strbuf_addstr(&buf, content_type);
 -                              strbuf_addbuf(&buf, *p);
 -                              strbuf_addstr(&buf, pre_open);
 -                              added_header = 1;
 -                              continue;
 -                      }
 -              }
 -              else
 -                      encode_html_chars(*p);
 -              strbuf_addbuf(&buf, *p);
 -      }
 +      const char *body = strstr(msg->buf, "\n\n");
 +
 +      if (!body)
 +              return; /* Headers but no body; no wrapping needed */
 +
 +      body += 2;
 +
 +      strbuf_add(&buf, msg->buf, body - msg->buf - 1);
 +      strbuf_addstr(&buf, content_type);
 +      strbuf_addch(&buf, '\n');
 +      strbuf_addstr(&buf, pre_open);
 +      strbuf_addstr_xml_quoted(&buf, body);
        strbuf_addstr(&buf, pre_close);
 -      strbuf_list_free(lines);
 -      msg->len  = buf.len;
 -      msg->data = strbuf_detach(&buf, NULL);
 +
 +      strbuf_release(msg);
 +      *msg = buf;
  }
  
  #define CHUNKSIZE 0x1000
  
 -static int read_message(FILE *f, struct msg_data *msg)
 +static int read_message(FILE *f, struct strbuf *all_msgs)
  {
 -      struct strbuf buf = STRBUF_INIT;
 -
 -      memset(msg, 0, sizeof(*msg));
 -
        do {
 -              if (strbuf_fread(&buf, CHUNKSIZE, f) <= 0)
 +              if (strbuf_fread(all_msgs, CHUNKSIZE, f) <= 0)
                        break;
        } while (!feof(f));
  
 -      msg->len  = buf.len;
 -      msg->data = strbuf_detach(&buf, NULL);
 -      return msg->len;
 +      return ferror(f) ? -1 : 0;
  }
  
 -static int count_messages(struct msg_data *msg)
 +static int count_messages(struct strbuf *all_msgs)
  {
        int count = 0;
 -      char *p = msg->data;
 +      char *p = all_msgs->buf;
  
        while (1) {
                if (!prefixcmp(p, "From ")) {
        return count;
  }
  
 -static int split_msg(struct msg_data *all_msgs, struct msg_data *msg, int *ofs)
 +/*
 + * Copy the next message from all_msgs, starting at offset *ofs, to
 + * msg.  Update *ofs to the start of the following message.  Return
 + * true iff a message was successfully copied.
 + */
 +static int split_msg(struct strbuf *all_msgs, struct strbuf *msg, int *ofs)
  {
        char *p, *data;
 +      size_t len;
  
 -      memset(msg, 0, sizeof *msg);
        if (*ofs >= all_msgs->len)
                return 0;
  
 -      data = &all_msgs->data[*ofs];
 -      msg->len = all_msgs->len - *ofs;
 +      data = &all_msgs->buf[*ofs];
 +      len = all_msgs->len - *ofs;
  
 -      if (msg->len < 5 || prefixcmp(data, "From "))
 +      if (len < 5 || prefixcmp(data, "From "))
                return 0;
  
        p = strchr(data, '\n');
        if (p) {
 -              p = &p[1];
 -              msg->len -= p-data;
 -              *ofs += p-data;
 +              p++;
 +              len -= p - data;
 +              *ofs += p - data;
                data = p;
        }
  
        p = strstr(data, "\nFrom ");
        if (p)
 -              msg->len = &p[1] - data;
 +              len = &p[1] - data;
  
 -      msg->data = xmemdupz(data, msg->len);
 -      *ofs += msg->len;
 +      strbuf_add(msg, data, len);
 +      *ofs += len;
        return 1;
  }
  
@@@ -1294,9 -1567,8 +1357,9 @@@ static int git_imap_config(const char *
  
  int main(int argc, char **argv)
  {
 -      struct msg_data all_msgs, msg;
 -      struct store *ctx = NULL;
 +      struct strbuf all_msgs = STRBUF_INIT;
 +      struct strbuf msg = STRBUF_INIT;
 +      struct imap_store *ctx = NULL;
        int ofs = 0;
        int r;
        int total, n = 0;
        }
  
        /* read the messages */
 -      if (!read_message(stdin, &all_msgs)) {
 +      if (read_message(stdin, &all_msgs)) {
 +              fprintf(stderr, "error reading input\n");
 +              return 1;
 +      }
 +
 +      if (all_msgs.len == 0) {
                fprintf(stderr, "nothing to send\n");
                return 1;
        }
        ctx->name = imap_folder;
        while (1) {
                unsigned percent = n * 100 / total;
 +
                fprintf(stderr, "%4u%% (%d/%d) done\r", percent, n, total);
                if (!split_msg(&all_msgs, &msg, &ofs))
                        break;