Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Wed, 24 Nov 2010 21:24:49 +0000 (13:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Wed, 24 Nov 2010 21:24:49 +0000 (13:24 -0800)
* maint:
imap-send: link against libcrypto for HMAC and others
git-send-email.perl: Deduplicate "to:" and "cc:" entries with names
mingw: do not set errno to 0 on success

1  2 
Makefile
compat/mingw.c
git-send-email.perl
diff --combined Makefile
index 53986b1ecb76fcb30a51c4fc1a86a49ca6549724,cd0f6481721f9ec47bdc2c0da93936fdfe806a2c..919ed2b7ec87cb8399bb7c1119251c1704e2fbe7
+++ b/Makefile
@@@ -270,7 -270,6 +270,7 @@@ STRIP ?= stri
  #   infodir
  #   htmldir
  #   ETC_GITCONFIG (but not sysconfdir)
 +#   ETC_GITATTRIBUTES
  # can be specified as a relative path some/where/else;
  # this is interpreted as relative to $(prefix) and "git" at
  # runtime figures out where they are based on the path to the executable.
@@@ -289,11 -288,9 +289,11 @@@ htmldir = share/doc/git-do
  ifeq ($(prefix),/usr)
  sysconfdir = /etc
  ETC_GITCONFIG = $(sysconfdir)/gitconfig
 +ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
  else
  sysconfdir = $(prefix)/etc
  ETC_GITCONFIG = etc/gitconfig
 +ETC_GITATTRIBUTES = etc/gitattributes
  endif
  lib = lib
  # DESTDIR=
@@@ -401,7 -398,6 +401,7 @@@ EXTRA_PROGRAMS 
  # ... and all the rest that could be moved out of bindir to gitexecdir
  PROGRAMS += $(EXTRA_PROGRAMS)
  
 +PROGRAM_OBJS += daemon.o
  PROGRAM_OBJS += fast-import.o
  PROGRAM_OBJS += imap-send.o
  PROGRAM_OBJS += shell.o
@@@ -497,8 -493,6 +497,8 @@@ LIB_H += compat/bswap.
  LIB_H += compat/cygwin.h
  LIB_H += compat/mingw.h
  LIB_H += compat/win32/pthread.h
 +LIB_H += compat/win32/syslog.h
 +LIB_H += compat/win32/sys/poll.h
  LIB_H += csum-file.h
  LIB_H += decorate.h
  LIB_H += delta.h
@@@ -1067,6 -1061,7 +1067,6 @@@ ifeq ($(uname_S),Windows
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
        RUNTIME_PREFIX = YesPlease
 -      NO_POSIX_ONLY_PROGRAMS = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        NO_NSEC = YesPlease
        USE_WIN32_MMAP = YesPlease
        NO_CURL = YesPlease
        NO_PYTHON = YesPlease
        BLK_SHA1 = YesPlease
 +      NO_POSIX_GOODIES = UnfortunatelyYes
        NATIVE_CRLF = YesPlease
  
        CC = compat/vcbuild/scripts/clink.pl
        AR = compat/vcbuild/scripts/lib.pl
        CFLAGS =
        BASIC_CFLAGS = -nologo -I. -I../zlib -Icompat/vcbuild -Icompat/vcbuild/include -DWIN32 -D_CONSOLE -DHAVE_STRING_H -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE
 -      COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o
 +      COMPAT_OBJS = compat/msvc.o compat/fnmatch/fnmatch.o compat/winansi.o compat/win32/pthread.o compat/win32/syslog.o compat/win32/sys/poll.o
        COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
        BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
        EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
@@@ -1120,6 -1114,7 +1120,6 @@@ ifneq (,$(findstring MINGW,$(uname_S))
        NO_SVN_TESTS = YesPlease
        NO_PERL_MAKEMAKER = YesPlease
        RUNTIME_PREFIX = YesPlease
 -      NO_POSIX_ONLY_PROGRAMS = YesPlease
        NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
        NO_NSEC = YesPlease
        USE_WIN32_MMAP = YesPlease
        NO_PYTHON = YesPlease
        BLK_SHA1 = YesPlease
        ETAGS_TARGET = ETAGS
 +      NO_INET_PTON = YesPlease
 +      NO_INET_NTOP = YesPlease
 +      NO_POSIX_GOODIES = UnfortunatelyYes
        COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
        COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
        COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
 -              compat/win32/pthread.o
 +              compat/win32/pthread.o compat/win32/syslog.o \
 +              compat/win32/sys/poll.o
        EXTLIBS += -lws2_32
        PTHREAD_LIBS =
        X = .exe
@@@ -1252,6 -1243,9 +1252,6 @@@ ifdef ZLIB_PAT
  endif
  EXTLIBS += -lz
  
 -ifndef NO_POSIX_ONLY_PROGRAMS
 -      PROGRAM_OBJS += daemon.o
 -endif
  ifndef NO_OPENSSL
        OPENSSL_LIBSSL = -lssl
        ifdef OPENSSLDIR
@@@ -1400,11 -1394,9 +1400,11 @@@ endi
  endif
  ifdef NO_INET_NTOP
        LIB_OBJS += compat/inet_ntop.o
 +      BASIC_CFLAGS += -DNO_INET_NTOP
  endif
  ifdef NO_INET_PTON
        LIB_OBJS += compat/inet_pton.o
 +      BASIC_CFLAGS += -DNO_INET_PTON
  endif
  
  ifdef NO_ICONV
@@@ -1419,10 -1411,6 +1419,10 @@@ ifdef NO_DEFLATE_BOUN
        BASIC_CFLAGS += -DNO_DEFLATE_BOUND
  endif
  
 +ifdef NO_POSIX_GOODIES
 +      BASIC_CFLAGS += -DNO_POSIX_GOODIES
 +endif
 +
  ifdef BLK_SHA1
        SHA1_HEADER = "block-sha1/sha1.h"
        LIB_OBJS += block-sha1/sha1.o
@@@ -1535,7 -1523,6 +1535,7 @@@ endi
  
  SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
  ETC_GITCONFIG_SQ = $(subst ','\'',$(ETC_GITCONFIG))
 +ETC_GITATTRIBUTES_SQ = $(subst ','\'',$(ETC_GITATTRIBUTES))
  
  DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
  bindir_SQ = $(subst ','\'',$(bindir))
@@@ -1914,8 -1901,6 +1914,8 @@@ builtin/init-db.s builtin/init-db.o: EX
  
  config.s config.o: EXTRA_CPPFLAGS = -DETC_GITCONFIG='"$(ETC_GITCONFIG_SQ)"'
  
 +attr.s attr.o: EXTRA_CPPFLAGS = -DETC_GITATTRIBUTES='"$(ETC_GITATTRIBUTES_SQ)"'
 +
  http.s http.o: EXTRA_CPPFLAGS = -DGIT_HTTP_USER_AGENT='"git/$(GIT_VERSION)"'
  
  ifdef NO_EXPAT
@@@ -1936,7 -1921,7 +1936,7 @@@ git-%$X: %.o $(GITLIBS
  
  git-imap-send$X: imap-send.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
-               $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL)
+               $(LIBS) $(OPENSSL_LINK) $(OPENSSL_LIBSSL) $(LIB_4_CRYPTO)
  
  git-http-fetch$X: revision.o http.o http-walker.o http-fetch.o $(GITLIBS)
        $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \
diff --combined compat/mingw.c
index 29f403649f0e3c6d4240fc93e6fa1787a017b9ea,b98e6000062134f01a0611fb17a3fd43250f7989..fdbf093f6eb704ec0ca0e5abf6baf1f94350676b
@@@ -127,7 -127,7 +127,7 @@@ int mingw_open (const char *filename, i
        mode = va_arg(args, int);
        va_end(args);
  
 -      if (!strcmp(filename, "/dev/null"))
 +      if (filename && !strcmp(filename, "/dev/null"))
                filename = "nul";
  
        fd = open(filename, oflags, mode);
@@@ -160,7 -160,7 +160,7 @@@ ssize_t mingw_write(int fd, const void 
  #undef fopen
  FILE *mingw_fopen (const char *filename, const char *otype)
  {
 -      if (!strcmp(filename, "/dev/null"))
 +      if (filename && !strcmp(filename, "/dev/null"))
                filename = "nul";
        return fopen(filename, otype);
  }
@@@ -192,15 -192,13 +192,16 @@@ static inline time_t filetime_to_time_t
  /* We keep the do_lstat code in a separate function to avoid recursion.
   * When a path ends with a slash, the stat will fail with ENOENT. In
   * this case, we strip the trailing slashes and stat again.
 + *
 + * If follow is true then act like stat() and report on the link
 + * target. Otherwise report on the link itself.
   */
 -static int do_lstat(const char *file_name, struct stat *buf)
 +static int do_lstat(int follow, const char *file_name, struct stat *buf)
  {
+       int err;
        WIN32_FILE_ATTRIBUTE_DATA fdata;
  
-       if (!(errno = get_file_attr(file_name, &fdata))) {
+       if (!(err = get_file_attr(file_name, &fdata))) {
                buf->st_ino = 0;
                buf->st_gid = 0;
                buf->st_uid = 0;
                buf->st_atime = filetime_to_time_t(&(fdata.ftLastAccessTime));
                buf->st_mtime = filetime_to_time_t(&(fdata.ftLastWriteTime));
                buf->st_ctime = filetime_to_time_t(&(fdata.ftCreationTime));
 +              if (fdata.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
 +                      WIN32_FIND_DATAA findbuf;
 +                      HANDLE handle = FindFirstFileA(file_name, &findbuf);
 +                      if (handle != INVALID_HANDLE_VALUE) {
 +                              if ((findbuf.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) &&
 +                                              (findbuf.dwReserved0 == IO_REPARSE_TAG_SYMLINK)) {
 +                                      if (follow) {
 +                                              char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
 +                                              buf->st_size = readlink(file_name, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
 +                                      } else {
 +                                              buf->st_mode = S_IFLNK;
 +                                      }
 +                                      buf->st_mode |= S_IREAD;
 +                                      if (!(findbuf.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
 +                                              buf->st_mode |= S_IWRITE;
 +                              }
 +                              FindClose(handle);
 +                      }
 +              }
                return 0;
        }
+       errno = err;
        return -1;
  }
  
   * complete. Note that Git stat()s are redirected to mingw_lstat()
   * too, since Windows doesn't really handle symlinks that well.
   */
 -int mingw_lstat(const char *file_name, struct stat *buf)
 +static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
  {
        int namelen;
        static char alt_name[PATH_MAX];
  
 -      if (!do_lstat(file_name, buf))
 +      if (!do_lstat(follow, file_name, buf))
                return 0;
  
        /* if file_name ended in a '/', Windows returned ENOENT;
  
        memcpy(alt_name, file_name, namelen);
        alt_name[namelen] = 0;
 -      return do_lstat(alt_name, buf);
 +      return do_lstat(follow, alt_name, buf);
 +}
 +
 +int mingw_lstat(const char *file_name, struct stat *buf)
 +{
 +      return do_stat_internal(0, file_name, buf);
 +}
 +int mingw_stat(const char *file_name, struct stat *buf)
 +{
 +      return do_stat_internal(1, file_name, buf);
  }
  
  #undef fstat
@@@ -408,6 -379,71 +410,6 @@@ int pipe(int filedes[2]
        return 0;
  }
  
 -int poll(struct pollfd *ufds, unsigned int nfds, int timeout)
 -{
 -      int i, pending;
 -
 -      if (timeout >= 0) {
 -              if (nfds == 0) {
 -                      Sleep(timeout);
 -                      return 0;
 -              }
 -              return errno = EINVAL, error("poll timeout not supported");
 -      }
 -
 -      /* When there is only one fd to wait for, then we pretend that
 -       * input is available and let the actual wait happen when the
 -       * caller invokes read().
 -       */
 -      if (nfds == 1) {
 -              if (!(ufds[0].events & POLLIN))
 -                      return errno = EINVAL, error("POLLIN not set");
 -              ufds[0].revents = POLLIN;
 -              return 0;
 -      }
 -
 -repeat:
 -      pending = 0;
 -      for (i = 0; i < nfds; i++) {
 -              DWORD avail = 0;
 -              HANDLE h = (HANDLE) _get_osfhandle(ufds[i].fd);
 -              if (h == INVALID_HANDLE_VALUE)
 -                      return -1;      /* errno was set */
 -
 -              if (!(ufds[i].events & POLLIN))
 -                      return errno = EINVAL, error("POLLIN not set");
 -
 -              /* this emulation works only for pipes */
 -              if (!PeekNamedPipe(h, NULL, 0, NULL, &avail, NULL)) {
 -                      int err = GetLastError();
 -                      if (err == ERROR_BROKEN_PIPE) {
 -                              ufds[i].revents = POLLHUP;
 -                              pending++;
 -                      } else {
 -                              errno = EINVAL;
 -                              return error("PeekNamedPipe failed,"
 -                                      " GetLastError: %u", err);
 -                      }
 -              } else if (avail) {
 -                      ufds[i].revents = POLLIN;
 -                      pending++;
 -              } else
 -                      ufds[i].revents = 0;
 -      }
 -      if (!pending) {
 -              /* The only times that we spin here is when the process
 -               * that is connected through the pipes is waiting for
 -               * its own input data to become available. But since
 -               * the process (pack-objects) is itself CPU intensive,
 -               * it will happily pick up the time slice that we are
 -               * relinquishing here.
 -               */
 -              Sleep(0);
 -              goto repeat;
 -      }
 -      return 0;
 -}
 -
  struct tm *gmtime_r(const time_t *timep, struct tm *result)
  {
        /* gmtime() in MSVCRT.DLL is thread-safe, but not reentrant */
@@@ -637,14 -673,6 +639,14 @@@ static int env_compare(const void *a, c
        return strcasecmp(*ea, *eb);
  }
  
 +struct pinfo_t {
 +      struct pinfo_t *next;
 +      pid_t pid;
 +      HANDLE proc;
 +} pinfo_t;
 +struct pinfo_t *pinfo = NULL;
 +CRITICAL_SECTION pinfo_cs;
 +
  static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **env,
                              const char *dir,
                              int prepend_cmd, int fhin, int fhout, int fherr)
                return -1;
        }
        CloseHandle(pi.hThread);
 -      return (pid_t)pi.hProcess;
 +
 +      /*
 +       * The process ID is the human-readable identifier of the process
 +       * that we want to present in log and error messages. The handle
 +       * is not useful for this purpose. But we cannot close it, either,
 +       * because it is not possible to turn a process ID into a process
 +       * handle after the process terminated.
 +       * Keep the handle in a list for waitpid.
 +       */
 +      EnterCriticalSection(&pinfo_cs);
 +      {
 +              struct pinfo_t *info = xmalloc(sizeof(struct pinfo_t));
 +              info->pid = pi.dwProcessId;
 +              info->proc = pi.hProcess;
 +              info->next = pinfo;
 +              pinfo = info;
 +      }
 +      LeaveCriticalSection(&pinfo_cs);
 +
 +      return (pid_t)pi.dwProcessId;
  }
  
  static pid_t mingw_spawnve(const char *cmd, const char **argv, char **env,
@@@ -866,30 -875,6 +868,30 @@@ void mingw_execvp(const char *cmd, cha
        free_path_split(path);
  }
  
 +void mingw_execv(const char *cmd, char *const *argv)
 +{
 +      mingw_execve(cmd, argv, environ);
 +}
 +
 +int mingw_kill(pid_t pid, int sig)
 +{
 +      if (pid > 0 && sig == SIGTERM) {
 +              HANDLE h = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
 +
 +              if (TerminateProcess(h, -1)) {
 +                      CloseHandle(h);
 +                      return 0;
 +              }
 +
 +              errno = err_win_to_posix(GetLastError());
 +              CloseHandle(h);
 +              return -1;
 +      }
 +
 +      errno = EINVAL;
 +      return -1;
 +}
 +
  static char **copy_environ(void)
  {
        char **env;
@@@ -974,22 -959,19 +976,22 @@@ static int WSAAPI getaddrinfo_stub(cons
                                   const struct addrinfo *hints,
                                   struct addrinfo **res)
  {
 -      struct hostent *h = gethostbyname(node);
 +      struct hostent *h = NULL;
        struct addrinfo *ai;
        struct sockaddr_in *sin;
  
 -      if (!h)
 -              return WSAGetLastError();
 +      if (node) {
 +              h = gethostbyname(node);
 +              if (!h)
 +                      return WSAGetLastError();
 +      }
  
        ai = xmalloc(sizeof(struct addrinfo));
        *res = ai;
        ai->ai_flags = 0;
        ai->ai_family = AF_INET;
 -      ai->ai_socktype = hints->ai_socktype;
 -      switch (hints->ai_socktype) {
 +      ai->ai_socktype = hints ? hints->ai_socktype : 0;
 +      switch (ai->ai_socktype) {
        case SOCK_STREAM:
                ai->ai_protocol = IPPROTO_TCP;
                break;
                break;
        }
        ai->ai_addrlen = sizeof(struct sockaddr_in);
 -      ai->ai_canonname = strdup(h->h_name);
 +      if (hints && (hints->ai_flags & AI_CANONNAME))
 +              ai->ai_canonname = h ? strdup(h->h_name) : NULL;
 +      else
 +              ai->ai_canonname = NULL;
  
        sin = xmalloc(ai->ai_addrlen);
        memset(sin, 0, ai->ai_addrlen);
        sin->sin_family = AF_INET;
 +      /* Note: getaddrinfo is supposed to allow service to be a string,
 +       * which should be looked up using getservbyname. This is
 +       * currently not implemented */
        if (service)
                sin->sin_port = htons(atoi(service));
 -      sin->sin_addr = *(struct in_addr *)h->h_addr;
 +      if (h)
 +              sin->sin_addr = *(struct in_addr *)h->h_addr;
 +      else if (hints && (hints->ai_flags & AI_PASSIVE))
 +              sin->sin_addr.s_addr = INADDR_ANY;
 +      else
 +              sin->sin_addr.s_addr = INADDR_LOOPBACK;
        ai->ai_addr = (struct sockaddr *)sin;
        ai->ai_next = 0;
        return 0;
@@@ -1170,10 -1141,7 +1172,10 @@@ int mingw_getnameinfo(const struct sock
  int mingw_socket(int domain, int type, int protocol)
  {
        int sockfd;
 -      SOCKET s = WSASocket(domain, type, protocol, NULL, 0, 0);
 +      SOCKET s;
 +
 +      ensure_socket_initialization();
 +      s = WSASocket(domain, type, protocol, NULL, 0, 0);
        if (s == INVALID_SOCKET) {
                /*
                 * WSAGetLastError() values are regular BSD error codes
@@@ -1203,45 -1171,6 +1205,45 @@@ int mingw_connect(int sockfd, struct so
        return connect(s, sa, sz);
  }
  
 +#undef bind
 +int mingw_bind(int sockfd, struct sockaddr *sa, size_t sz)
 +{
 +      SOCKET s = (SOCKET)_get_osfhandle(sockfd);
 +      return bind(s, sa, sz);
 +}
 +
 +#undef setsockopt
 +int mingw_setsockopt(int sockfd, int lvl, int optname, void *optval, int optlen)
 +{
 +      SOCKET s = (SOCKET)_get_osfhandle(sockfd);
 +      return setsockopt(s, lvl, optname, (const char*)optval, optlen);
 +}
 +
 +#undef listen
 +int mingw_listen(int sockfd, int backlog)
 +{
 +      SOCKET s = (SOCKET)_get_osfhandle(sockfd);
 +      return listen(s, backlog);
 +}
 +
 +#undef accept
 +int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
 +{
 +      int sockfd2;
 +
 +      SOCKET s1 = (SOCKET)_get_osfhandle(sockfd1);
 +      SOCKET s2 = accept(s1, sa, sz);
 +
 +      /* convert into a file descriptor */
 +      if ((sockfd2 = _open_osfhandle(s2, O_RDWR|O_BINARY)) < 0) {
 +              int err = errno;
 +              closesocket(s2);
 +              return error("unable to make a socket file descriptor: %s",
 +                      strerror(err));
 +      }
 +      return sockfd2;
 +}
 +
  #undef rename
  int mingw_rename(const char *pold, const char *pnew)
  {
@@@ -1459,7 -1388,6 +1461,7 @@@ void mingw_open_html(const char *unixpa
                        const char *, const char *, const char *, INT);
        T ShellExecute;
        HMODULE shell32;
 +      int r;
  
        shell32 = LoadLibrary("shell32.dll");
        if (!shell32)
                die("cannot run browser");
  
        printf("Launching default browser to display HTML ...\n");
 -      ShellExecute(NULL, "open", htmlpath, NULL, "\\", 0);
 -
 +      r = (int)ShellExecute(NULL, "open", htmlpath, NULL, "\\", SW_SHOWNORMAL);
        FreeLibrary(shell32);
 +      /* see the MSDN documentation referring to the result codes here */
 +      if (r <= 32) {
 +              die("failed to launch browser for %.*s", MAX_PATH, unixpath);
 +      }
  }
  
  int link(const char *oldpath, const char *newpath)
@@@ -1513,58 -1438,6 +1515,58 @@@ char *getpass(const char *prompt
        return strbuf_detach(&buf, NULL);
  }
  
 +pid_t waitpid(pid_t pid, int *status, unsigned options)
 +{
 +      HANDLE h = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION,
 +          FALSE, pid);
 +      if (!h) {
 +              errno = ECHILD;
 +              return -1;
 +      }
 +
 +      if (pid > 0 && options & WNOHANG) {
 +              if (WAIT_OBJECT_0 != WaitForSingleObject(h, 0)) {
 +                      CloseHandle(h);
 +                      return 0;
 +              }
 +              options &= ~WNOHANG;
 +      }
 +
 +      if (options == 0) {
 +              struct pinfo_t **ppinfo;
 +              if (WaitForSingleObject(h, INFINITE) != WAIT_OBJECT_0) {
 +                      CloseHandle(h);
 +                      return 0;
 +              }
 +
 +              if (status)
 +                      GetExitCodeProcess(h, (LPDWORD)status);
 +
 +              EnterCriticalSection(&pinfo_cs);
 +
 +              ppinfo = &pinfo;
 +              while (*ppinfo) {
 +                      struct pinfo_t *info = *ppinfo;
 +                      if (info->pid == pid) {
 +                              CloseHandle(info->proc);
 +                              *ppinfo = info->next;
 +                              free(info);
 +                              break;
 +                      }
 +                      ppinfo = &info->next;
 +              }
 +
 +              LeaveCriticalSection(&pinfo_cs);
 +
 +              CloseHandle(h);
 +              return pid;
 +      }
 +      CloseHandle(h);
 +
 +      errno = EINVAL;
 +      return -1;
 +}
 +
  #ifndef NO_MINGW_REPLACE_READDIR
  /* MinGW readdir implementation to avoid extra lstats for Git */
  struct mingw_DIR
diff --combined git-send-email.perl
index f68ed5a5d3208eb0669d7dc1289f40c567e077c7,81b2ea5633a9692f52af67ab8bfd42b68781cf4d..6e2d79ac661e3f31926c07291eddd953e0446cef
@@@ -1,4 -1,4 +1,4 @@@
 -#!/usr/bin/perl -w
 +#!/usr/bin/perl
  #
  # Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>
  # Copyright 2005 Ryan Anderson <ryan@michonline.com>
@@@ -16,7 -16,6 +16,7 @@@
  #    and second line is the subject of the message.
  #
  
 +use 5.008;
  use strict;
  use warnings;
  use Term::ReadLine;
@@@ -25,7 -24,6 +25,7 @@@ use Text::ParseWords
  use Data::Dumper;
  use Term::ANSIColor;
  use File::Temp qw/ tempdir tempfile /;
 +use File::Spec::Functions qw(catfile);
  use Error qw(:try);
  use Git;
  
@@@ -62,7 -60,6 +62,7 @@@ git send-email [options] <file | direct
      --envelope-sender       <str>  * Email envelope sender.
      --smtp-server       <str:int>  * Outgoing SMTP server to use. The port
                                       is optional. Default 'localhost'.
 +    --smtp-server-option    <str>  * Outgoing SMTP server option to use.
      --smtp-server-port      <int>  * Outgoing SMTP server port.
      --smtp-user             <str>  * Username for SMTP-AUTH.
      --smtp-pass             <str>  * Password for SMTP-AUTH; not necessary.
@@@ -73,7 -70,6 +73,7 @@@
  
    Automating:
      --identity              <str>  * Use the sendemail.<id> options.
 +    --to-cmd                <str>  * Email To: via `<str> \$patch_path`
      --cc-cmd                <str>  * Email Cc: via `<str> \$patch_path`
      --suppress-cc           <str>  * author, self, sob, cc, cccmd, body, bodycc, all.
      --[no-]signed-off-by-cc        * Send to Signed-off-by: addresses. Default on.
@@@ -139,8 -135,11 +139,8 @@@ my $have_mail_address = eval { require 
  my $smtp;
  my $auth;
  
 -sub unique_email_list(@);
 -sub cleanup_compose_files();
 -
  # Variables we fill in automatically, or via prompting:
 -my (@to,$no_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
 +my (@to,$no_to,@initial_to,@cc,$no_cc,@initial_cc,@bcclist,$no_bcc,@xh,
        $initial_reply_to,$initial_subject,@files,
        $author,$sender,$smtp_authpass,$annotate,$compose,$time);
  
@@@ -190,11 -189,9 +190,11 @@@ sub do_edit 
  }
  
  # Variables with corresponding config settings
 -my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc, $cc_cmd);
 -my ($smtp_server, $smtp_server_port, $smtp_authuser, $smtp_encryption);
 -my ($identity, $aliasfiletype, @alias_files, @smtp_host_parts, $smtp_domain);
 +my ($thread, $chain_reply_to, $suppress_from, $signed_off_by_cc);
 +my ($to_cmd, $cc_cmd);
 +my ($smtp_server, $smtp_server_port, @smtp_server_options);
 +my ($smtp_authuser, $smtp_encryption);
 +my ($identity, $aliasfiletype, @alias_files, $smtp_domain);
  my ($validate, $confirm);
  my (@suppress_cc);
  my ($auto_8bit_encoding);
@@@ -215,12 -212,10 +215,12 @@@ my %config_bool_settings = 
  my %config_settings = (
      "smtpserver" => \$smtp_server,
      "smtpserverport" => \$smtp_server_port,
 +    "smtpserveroption" => \@smtp_server_options,
      "smtpuser" => \$smtp_authuser,
      "smtppass" => \$smtp_authpass,
 -      "smtpdomain" => \$smtp_domain,
 -    "to" => \@to,
 +    "smtpdomain" => \$smtp_domain,
 +    "to" => \@initial_to,
 +    "tocmd" => \$to_cmd,
      "cc" => \@initial_cc,
      "cccmd" => \$cc_cmd,
      "aliasfiletype" => \$aliasfiletype,
@@@ -278,8 -273,7 +278,8 @@@ $SIG{INT}  = \&signal_handler
  my $rc = GetOptions("sender|from=s" => \$sender,
                      "in-reply-to=s" => \$initial_reply_to,
                    "subject=s" => \$initial_subject,
 -                  "to=s" => \@to,
 +                  "to=s" => \@initial_to,
 +                  "to-cmd=s" => \$to_cmd,
                    "no-to" => \$no_to,
                    "cc=s" => \@initial_cc,
                    "no-cc" => \$no_cc,
                    "no-bcc" => \$no_bcc,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
 +                  "smtp-server-option=s" => \@smtp_server_options,
                    "smtp-server-port=s" => \$smtp_server_port,
                    "smtp-user=s" => \$smtp_authuser,
                    "smtp-pass:s" => \$smtp_authpass,
@@@ -374,7 -367,7 +374,7 @@@ my(%suppress_cc)
  if (@suppress_cc) {
        foreach my $entry (@suppress_cc) {
                die "Unknown --suppress-cc field: '$entry'\n"
 -                      unless $entry =~ /^(all|cccmd|cc|author|self|sob|body|bodycc)$/;
 +                      unless $entry =~ /^(?:all|cccmd|cc|author|self|sob|body|bodycc)$/;
                $suppress_cc{$entry} = 1;
        }
  }
@@@ -419,7 -412,7 +419,7 @@@ my ($repoauthor, $repocommitter)
  
  # Verify the user input
  
 -foreach my $entry (@to) {
 +foreach my $entry (@initial_to) {
        die "Comma in --to entry: $entry'\n" unless $entry !~ m/,/;
  }
  
@@@ -518,12 -511,12 +518,12 @@@ while (defined(my $f = shift @ARGV)) 
                push @rev_list_opts, "--", @ARGV;
                @ARGV = ();
        } elsif (-d $f and !check_file_rev_conflict($f)) {
 -              opendir(DH,$f)
 +              opendir my $dh, $f
                        or die "Failed to opendir $f: $!";
  
 -              push @files, grep { -f $_ } map { +$f . "/" . $_ }
 -                              sort readdir(DH);
 -              closedir(DH);
 +              push @files, grep { -f $_ } map { catfile($f, $_) }
 +                              sort readdir $dh;
 +              closedir $dh;
        } elsif ((-f $f or -p $f) and !check_file_rev_conflict($f)) {
                push @files, $f;
        } else {
@@@ -555,7 -548,7 +555,7 @@@ if (@files) 
        usage();
  }
  
 -sub get_patch_subject($) {
 +sub get_patch_subject {
        my $fn = shift;
        open (my $fh, '<', $fn);
        while (my $line = <$fh>) {
@@@ -573,7 -566,7 +573,7 @@@ if ($compose) 
        $compose_filename = ($repo ?
                tempfile(".gitsendemail.msg.XXXXXX", DIR => $repo->repo_path()) :
                tempfile(".gitsendemail.msg.XXXXXX", DIR => "."))[1];
 -      open(C,">",$compose_filename)
 +      open my $c, ">", $compose_filename
                or die "Failed to open for writing $compose_filename: $!";
  
  
        my $tpl_subject = $initial_subject || '';
        my $tpl_reply_to = $initial_reply_to || '';
  
 -      print C <<EOT;
 +      print $c <<EOT;
  From $tpl_sender # This line is ignored.
  GIT: Lines beginning in "GIT:" will be removed.
  GIT: Consider including an overall diffstat or table of contents
@@@ -594,9 -587,9 +594,9 @@@ In-Reply-To: $tpl_reply_t
  
  EOT
        for my $f (@files) {
 -              print C get_patch_subject($f);
 +              print $c get_patch_subject($f);
        }
 -      close(C);
 +      close $c;
  
        if ($annotate) {
                do_edit($compose_filename, @files);
                do_edit($compose_filename);
        }
  
 -      open(C2,">",$compose_filename . ".final")
 +      open my $c2, ">", $compose_filename . ".final"
                or die "Failed to open $compose_filename.final : " . $!;
  
 -      open(C,"<",$compose_filename)
 +      open $c, "<", $compose_filename
                or die "Failed to open $compose_filename : " . $!;
  
        my $need_8bit_cte = file_has_nonascii($compose_filename);
        my $in_body = 0;
        my $summary_empty = 1;
 -      while(<C>) {
 +      while(<$c>) {
                next if m/^GIT:/;
                if ($in_body) {
                        $summary_empty = 0 unless (/^\n$/);
                } elsif (/^\n$/) {
                        $in_body = 1;
                        if ($need_8bit_cte) {
 -                              print C2 "MIME-Version: 1.0\n",
 +                              print $c2 "MIME-Version: 1.0\n",
                                         "Content-Type: text/plain; ",
                                           "charset=UTF-8\n",
                                         "Content-Transfer-Encoding: 8bit\n";
                        print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
                        next;
                }
 -              print C2 $_;
 +              print $c2 $_;
        }
 -      close(C);
 -      close(C2);
 +      close $c;
 +      close $c2;
  
        if ($summary_empty) {
                print "Summary email is empty, skipping it\n";
@@@ -685,7 -678,7 +685,7 @@@ sub ask 
  
  my %broken_encoding;
  
 -sub file_declares_8bit_cte($) {
 +sub file_declares_8bit_cte {
        my $fn = shift;
        open (my $fh, '<', $fn);
        while (my $line = <$fh>) {
@@@ -714,7 -707,7 +714,7 @@@ if (!defined $auto_8bit_encoding && sca
  
  if (!$force) {
        for my $f (@files) {
 -              if (get_patch_subject($f) =~ /\*\*\* SUBJECT HERE \*\*\*/) {
 +              if (get_patch_subject($f) =~ /\Q*** SUBJECT HERE ***\E/) {
                        die "Refusing to send because the patch\n\t$f\n"
                                . "has the template subject '*** SUBJECT HERE ***'. "
                                . "Pass --force if you really want to send.\n";
@@@ -731,9 -724,9 +731,9 @@@ if (!defined $sender) 
        $prompting++;
  }
  
 -if (!@to) {
 +if (!@initial_to && !defined $to_cmd) {
        my $to = ask("Who should the emails be sent to? ");
 -      push @to, parse_address_line($to) if defined $to; # sanitized/validated later
 +      push @initial_to, parse_address_line($to) if defined $to; # sanitized/validated later
        $prompting++;
  }
  
@@@ -751,8 -744,8 +751,8 @@@ sub expand_one_alias 
        return $aliases{$alias} ? expand_aliases(@{$aliases{$alias}}) : $alias;
  }
  
 -@to = expand_aliases(@to);
 -@to = (map { sanitize_address($_) } @to);
 +@initial_to = expand_aliases(@initial_to);
 +@initial_to = (map { sanitize_address($_) } @initial_to);
  @initial_cc = expand_aliases(@initial_cc);
  @bcclist = expand_aliases(@bcclist);
  
@@@ -786,8 -779,8 +786,8 @@@ our ($message_id, %mail, $subject, $rep
  
  sub extract_valid_address {
        my $address = shift;
 -      my $local_part_regexp = '[^<>"\s@]+';
 -      my $domain_regexp = '[^.<>"\s@]+(?:\.[^.<>"\s@]+)+';
 +      my $local_part_regexp = qr/[^<>"\s@]+/;
 +      my $domain_regexp = qr/[^.<>"\s@]+(?:\.[^.<>"\s@]+)+/;
  
        # check for a local address:
        return $address if ($address =~ /^($local_part_regexp)$/);
@@@ -828,7 -821,7 +828,7 @@@ sub make_message_id 
                last if (defined $du_part and $du_part ne '');
        }
        if (not defined $du_part or $du_part eq '') {
 -              use Sys::Hostname qw();
 +              require Sys::Hostname;
                $du_part = 'user@' . Sys::Hostname::hostname();
        }
        my $message_id_template = "<%s-git-send-email-%s>";
@@@ -861,8 -854,8 +861,8 @@@ sub quote_rfc2047 
  
  sub is_rfc2047_quoted {
        my $s = shift;
 -      my $token = '[^][()<>@,;:"\/?.= \000-\037\177-\377]+';
 -      my $encoded_text = '[!->@-~]+';
 +      my $token = qr/[^][()<>@,;:"\/?.= \000-\037\177-\377]+/;
 +      my $encoded_text = qr/[!->@-~]+/;
        length($s) <= 75 &&
        $s =~ m/^(?:"[[:ascii:]]*"|=\?$token\?$token\?$encoded_text\?=)$/o;
  }
@@@ -873,7 -866,7 +873,7 @@@ sub sanitize_address 
        my ($recipient_name, $recipient_addr) = ($recipient =~ /^(.*?)\s*(<.*)/);
  
        if (not $recipient_name) {
 -              return "$recipient";
 +              return $recipient;
        }
  
        # if recipient_name is already quoted, do nothing
        # double quotes are needed if specials or CTLs are included
        elsif ($recipient_name =~ /[][()<>@,;:\\".\000-\037\177]/) {
                $recipient_name =~ s/(["\\\r])/\\$1/g;
 -              $recipient_name = "\"$recipient_name\"";
 +              $recipient_name = qq["$recipient_name"];
        }
  
        return "$recipient_name $recipient_addr";
@@@ -960,7 -953,7 +960,7 @@@ sub maildomain 
  sub send_message {
        my @recipients = unique_email_list(@to);
        @cc = (grep { my $cc = extract_valid_address($_);
-                     not grep { $cc eq $_ } @recipients
+                     not grep { $cc eq $_ || $_ =~ /<\Q${cc}\E>$/ } @recipients
                    }
               map { sanitize_address($_) }
               @cc);
@@@ -1035,8 -1028,6 +1035,8 @@@ X-Mailer: git-send-email $gitversio
                }
        }
  
 +      unshift (@sendmail_parameters, @smtp_server_options);
 +
        if ($dry_run) {
                # We don't want to send the email.
        } elsif ($smtp_server =~ m#^/#) {
                        exec($smtp_server, @sendmail_parameters) or die $!;
                }
                print $sm "$header\n$message";
 -              close $sm or die $?;
 +              close $sm or die $!;
        } else {
  
                if (!defined $smtp_server) {
@@@ -1152,13 -1143,12 +1152,13 @@@ $subject = $initial_subject
  $message_num = 0;
  
  foreach my $t (@files) {
 -      open(F,"<",$t) or die "can't open file $t";
 +      open my $fh, "<", $t or die "can't open file $t";
  
        my $author = undef;
        my $author_encoding;
        my $has_content_type;
        my $body_encoding;
 +      @to = ();
        @cc = ();
        @xh = ();
        my $input_format = undef;
        $message = "";
        $message_num++;
        # First unfold multiline header fields
 -      while(<F>) {
 +      while(<$fh>) {
                last if /^\s*$/;
                if (/^\s+\S/ and @header) {
                        chomp($header[$#header]);
                                        $1, $_) unless $quiet;
                                push @cc, $1;
                        }
 +                      elsif (/^To:\s+(.*)$/) {
 +                              foreach my $addr (parse_address_line($1)) {
 +                                      printf("(mbox) Adding to: %s from line '%s'\n",
 +                                              $addr, $_) unless $quiet;
 +                                      push @to, sanitize_address($addr);
 +                              }
 +                      }
                        elsif (/^Cc:\s+(.*)$/) {
                                foreach my $addr (parse_address_line($1)) {
                                        if (unquote_rfc2047($addr) eq $sender) {
                }
        }
        # Now parse the message body
 -      while(<F>) {
 +      while(<$fh>) {
                $message .=  $_;
                if (/^(Signed-off-by|Cc): (.*)$/i) {
                        chomp;
                                $c, $_) unless $quiet;
                }
        }
 -      close F;
 -
 -      if (defined $cc_cmd && !$suppress_cc{'cccmd'}) {
 -              open(F, "$cc_cmd \Q$t\E |")
 -                      or die "(cc-cmd) Could not execute '$cc_cmd'";
 -              while(<F>) {
 -                      my $c = $_;
 -                      $c =~ s/^\s*//g;
 -                      $c =~ s/\n$//g;
 -                      next if ($c eq $sender and $suppress_from);
 -                      push @cc, $c;
 -                      printf("(cc-cmd) Adding cc: %s from: '%s'\n",
 -                              $c, $cc_cmd) unless $quiet;
 -              }
 -              close F
 -                      or die "(cc-cmd) failed to close pipe to '$cc_cmd'";
 -      }
 +      close $fh;
 +
 +      push @to, recipients_cmd("to-cmd", "to", $to_cmd, $t)
 +              if defined $to_cmd;
 +      push @cc, recipients_cmd("cc-cmd", "cc", $cc_cmd, $t)
 +              if defined $cc_cmd && !$suppress_cc{'cccmd'};
  
        if ($broken_encoding{$t} && !$has_content_type) {
                $has_content_type = 1;
                ($confirm =~ /^(?:auto|compose)$/ && $compose && $message_num == 1));
        $needs_confirm = "inform" if ($needs_confirm && $confirm_unconfigured && @cc);
  
 +      @to = (@initial_to, @to);
        @cc = (@initial_cc, @cc);
  
        my $message_was_sent = send_message();
        $message_id = undef;
  }
  
 +# Execute a command (e.g. $to_cmd) to get a list of email addresses
 +# and return a results array
 +sub recipients_cmd {
 +      my ($prefix, $what, $cmd, $file) = @_;
 +
 +      my $sanitized_sender = sanitize_address($sender);
 +      my @addresses = ();
 +      open my $fh, "$cmd \Q$file\E |"
 +          or die "($prefix) Could not execute '$cmd'";
 +      while (my $address = <$fh>) {
 +              $address =~ s/^\s*//g;
 +              $address =~ s/\s*$//g;
 +              $address = sanitize_address($address);
 +              next if ($address eq $sanitized_sender and $suppress_from);
 +              push @addresses, $address;
 +              printf("($prefix) Adding %s: %s from: '%s'\n",
 +                     $what, $address, $cmd) unless $quiet;
 +              }
 +      close $fh
 +          or die "($prefix) failed to close pipe to '$cmd'";
 +      return @addresses;
 +}
 +
  cleanup_compose_files();
  
 -sub cleanup_compose_files() {
 +sub cleanup_compose_files {
        unlink($compose_filename, $compose_filename . ".final") if $compose;
  }
  
  $smtp->quit if $smtp;
  
 -sub unique_email_list(@) {
 +sub unique_email_list {
        my %seen;
        my @emails;