Sync with v2.5.4
authorJunio C Hamano <gitster@pobox.com>
Tue, 29 Sep 2015 02:16:54 +0000 (19:16 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 29 Sep 2015 02:16:54 +0000 (19:16 -0700)
1  2 
Documentation/git.txt
builtin/blame.c
builtin/rerere.c
diff.c
http.c
transport-helper.c
transport.c
transport.h
diff --combined Documentation/git.txt
index b37cfcefcd1ffc29d7dfff1043bf8e90f4a5a260,1a262757dc9f73f39cadef45613046d09d824c9a..f9a7fb94c5becf0da161ed4167bfbd145c1af8b9
@@@ -43,22 -43,19 +43,24 @@@ unreleased) version of Git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
- * link:v2.5.3/git.html[documentation for release 2.5.3]
 +* link:v2.6.0/git.html[documentation for release 2.6]
 +
 +* release notes for
 +  link:RelNotes/2.6.0.txt[2.6].
 +
+ * link:v2.5.4/git.html[documentation for release 2.5.4]
  
  * release notes for
+   link:RelNotes/2.5.4.txt[2.5.4],
    link:RelNotes/2.5.3.txt[2.5.3],
    link:RelNotes/2.5.2.txt[2.5.2],
    link:RelNotes/2.5.1.txt[2.5.1],
    link:RelNotes/2.5.0.txt[2.5].
  
- * link:v2.4.9/git.html[documentation for release 2.4.9]
+ * link:v2.4.10/git.html[documentation for release 2.4.10]
  
  * release notes for
+   link:RelNotes/2.4.10.txt[2.4.10],
    link:RelNotes/2.4.9.txt[2.4.9],
    link:RelNotes/2.4.8.txt[2.4.8],
    link:RelNotes/2.4.7.txt[2.4.7],
    link:RelNotes/2.4.1.txt[2.4.1],
    link:RelNotes/2.4.0.txt[2.4].
  
- * link:v2.3.9/git.html[documentation for release 2.3.9]
+ * link:v2.3.10/git.html[documentation for release 2.3.10]
  
  * release notes for
+   link:RelNotes/2.3.10.txt[2.3.10],
    link:RelNotes/2.3.9.txt[2.3.9],
    link:RelNotes/2.3.8.txt[2.3.8],
    link:RelNotes/2.3.7.txt[2.3.7],
@@@ -1021,20 -1019,9 +1024,20 @@@ Unsetting the variable, or setting it t
        Enables trace messages for all packets coming in or out of a
        given program. This can help with debugging object negotiation
        or other protocol issues. Tracing is turned off at a packet
 -      starting with "PACK".
 +      starting with "PACK" (but see 'GIT_TRACE_PACKFILE' below).
        See 'GIT_TRACE' for available trace output options.
  
 +'GIT_TRACE_PACKFILE'::
 +      Enables tracing of packfiles sent or received by a
 +      given program. Unlike other trace output, this trace is
 +      verbatim: no headers, and no quoting of binary data. You almost
 +      certainly want to direct into a file (e.g.,
 +      `GIT_TRACE_PACKFILE=/tmp/my.pack`) rather than displaying it on
 +      the terminal or mixing it with other trace output.
 ++
 +Note that this is currently only implemented for the client side
 +of clones and fetches.
 +
  'GIT_TRACE_PERFORMANCE'::
        Enables performance related trace messages, e.g. total execution
        time of each Git command.
@@@ -1092,6 -1079,33 +1095,33 @@@ GIT_ICASE_PATHSPECS:
        an operation has touched every ref (e.g., because you are
        cloning a repository to make a backup).
  
+ `GIT_ALLOW_PROTOCOL`::
+       If set, provide a colon-separated list of protocols which are
+       allowed to be used with fetch/push/clone. This is useful to
+       restrict recursive submodule initialization from an untrusted
+       repository. Any protocol not mentioned will be disallowed (i.e.,
+       this is a whitelist, not a blacklist). If the variable is not
+       set at all, all protocols are enabled.  The protocol names
+       currently used by git are:
+         - `file`: any local file-based path (including `file://` URLs,
+           or local paths)
+         - `git`: the anonymous git protocol over a direct TCP
+           connection (or proxy, if configured)
+         - `ssh`: git over ssh (including `host:path` syntax,
+           `git+ssh://`, etc).
+         - `rsync`: git over rsync
+         - `http`: git over http, both "smart http" and "dumb http".
+           Note that this does _not_ include `https`; if you want both,
+           you should specify both as `http:https`.
+         - any external helpers are named by their protocol (e.g., use
+           `hg` to allow the `git-remote-hg` helper)
  
  Discussion[[Discussion]]
  ------------------------
diff --combined builtin/blame.c
index 4db01c195cd27719023eeb599e76428ad752c9c9,191368bfab7508862cf28c37420a353f1ad8dee3..245d253d041021429265eb81fb008e528613af34
@@@ -6,7 -6,6 +6,7 @@@
   */
  
  #include "cache.h"
 +#include "refs.h"
  #include "builtin.h"
  #include "blob.h"
  #include "commit.h"
@@@ -51,7 -50,7 +51,7 @@@ static int xdl_opts
  static int abbrev = -1;
  static int no_whole_file_rename;
  
 -static enum date_mode blame_date_mode = DATE_ISO8601;
 +static struct date_mode blame_date_mode = { DATE_ISO8601 };
  static size_t blame_date_width;
  
  static struct string_list mailmap;
@@@ -974,7 -973,10 +974,10 @@@ static void pass_blame_to_parent(struc
        fill_origin_blob(&sb->revs->diffopt, target, &file_o);
        num_get_patch++;
  
-       diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d);
+       if (diff_hunks(&file_p, &file_o, 0, blame_chunk_cb, &d))
+               die("unable to generate diff (%s -> %s)",
+                   sha1_to_hex(parent->commit->object.sha1),
+                   sha1_to_hex(target->commit->object.sha1));
        /* The rest are the same as the parent */
        blame_chunk(&d.dstq, &d.srcq, INT_MAX, d.offset, INT_MAX, parent);
        *d.dstq = NULL;
@@@ -1120,7 -1122,9 +1123,9 @@@ static void find_copy_in_blob(struct sc
         * file_p partially may match that image.
         */
        memset(split, 0, sizeof(struct blame_entry [3]));
-       diff_hunks(file_p, &file_o, 1, handle_split_cb, &d);
+       if (diff_hunks(file_p, &file_o, 1, handle_split_cb, &d))
+               die("unable to generate diff (%s)",
+                   sha1_to_hex(parent->commit->object.sha1));
        /* remainder, if any, all match the preimage */
        handle_split(sb, ent, d.tlno, d.plno, ent->num_lines, parent, split);
  }
@@@ -1828,7 -1832,7 +1833,7 @@@ static const char *format_time(unsigne
                size_t time_width;
                int tz;
                tz = atoi(tz_str);
 -              time_str = show_date(time, tz, blame_date_mode);
 +              time_str = show_date(time, tz, &blame_date_mode);
                strbuf_addstr(&time_buf, time_str);
                /*
                 * Add space paddings to time_buf to display a fixed width
@@@ -2188,7 -2192,7 +2193,7 @@@ static int git_blame_config(const char 
        if (!strcmp(var, "blame.date")) {
                if (!value)
                        return config_error_nonbool(var);
 -              blame_date_mode = parse_date_format(value);
 +              parse_date_format(value, &blame_date_mode);
                return 0;
        }
  
@@@ -2227,19 -2231,20 +2232,19 @@@ static struct commit_list **append_pare
  static void append_merge_parents(struct commit_list **tail)
  {
        int merge_head;
 -      const char *merge_head_file = git_path("MERGE_HEAD");
        struct strbuf line = STRBUF_INIT;
  
 -      merge_head = open(merge_head_file, O_RDONLY);
 +      merge_head = open(git_path_merge_head(), O_RDONLY);
        if (merge_head < 0) {
                if (errno == ENOENT)
                        return;
 -              die("cannot open '%s' for reading", merge_head_file);
 +              die("cannot open '%s' for reading", git_path_merge_head());
        }
  
        while (!strbuf_getwholeline_fd(&line, merge_head, '\n')) {
                unsigned char sha1[20];
                if (line.len < 40 || get_sha1_hex(line.buf, sha1))
 -                      die("unknown line in '%s': %s", merge_head_file, line.buf);
 +                      die("unknown line in '%s': %s", git_path_merge_head(), line.buf);
                tail = append_parent(tail, sha1);
        }
        close(merge_head);
@@@ -2569,13 -2574,13 +2574,13 @@@ parse_done
  
        if (cmd_is_annotate) {
                output_option |= OUTPUT_ANNOTATE_COMPAT;
 -              blame_date_mode = DATE_ISO8601;
 +              blame_date_mode.type = DATE_ISO8601;
        } else {
                blame_date_mode = revs.date_mode;
        }
  
        /* The maximum width used to show the dates */
 -      switch (blame_date_mode) {
 +      switch (blame_date_mode.type) {
        case DATE_RFC2822:
                blame_date_width = sizeof("Thu, 19 Oct 2006 16:00:04 -0700");
                break;
        case DATE_NORMAL:
                blame_date_width = sizeof("Thu Oct 19 16:00:04 2006 -0700");
                break;
 +      case DATE_STRFTIME:
 +              blame_date_width = strlen(show_date(0, 0, &blame_date_mode)) + 1; /* add the null */
 +              break;
        }
        blame_date_width -= 1; /* strip the null */
  
diff --combined builtin/rerere.c
index 12535c9b4f48cd196943c1914fefee7c47cecec2,be55e0d2a759168748b343b73391fff9d36c525a..88e1359ebcaad338d96dd942a5f88f54d6cce271
@@@ -29,9 -29,10 +29,10 @@@ static int diff_two(const char *file1, 
        xdemitconf_t xecfg;
        xdemitcb_t ecb;
        mmfile_t minus, plus;
+       int ret;
  
        if (read_mmfile(&minus, file1) || read_mmfile(&plus, file2))
-               return 1;
+               return -1;
  
        printf("--- a/%s\n+++ b/%s\n", label1, label2);
        fflush(stdout);
        memset(&xecfg, 0, sizeof(xecfg));
        xecfg.ctxlen = 3;
        ecb.outf = outf;
-       xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
+       ret = xdi_diff(&minus, &plus, &xpp, &xecfg, &ecb);
  
        free(minus.ptr);
        free(plus.ptr);
-       return 0;
+       return ret;
  }
  
  int cmd_rerere(int argc, const char **argv, const char *prefix)
  {
        struct string_list merge_rr = STRING_LIST_INIT_DUP;
 -      int i, fd, autoupdate = -1, flags = 0;
 +      int i, autoupdate = -1, flags = 0;
  
        struct option options[] = {
                OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
                return rerere_forget(&pathspec);
        }
  
 -      fd = setup_rerere(&merge_rr, flags);
 -      if (fd < 0)
 -              return 0;
 -
        if (!strcmp(argv[0], "clear")) {
                rerere_clear(&merge_rr);
        } else if (!strcmp(argv[0], "gc"))
                rerere_gc(&merge_rr);
 -      else if (!strcmp(argv[0], "status"))
 +      else if (!strcmp(argv[0], "status")) {
 +              if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0)
 +                      return 0;
                for (i = 0; i < merge_rr.nr; i++)
                        printf("%s\n", merge_rr.items[i].string);
 -      else if (!strcmp(argv[0], "remaining")) {
 +      else if (!strcmp(argv[0], "remaining")) {
                rerere_remaining(&merge_rr);
                for (i = 0; i < merge_rr.nr; i++) {
                        if (merge_rr.items[i].util != RERERE_RESOLVED)
                                 * string_list_clear() */
                                merge_rr.items[i].util = NULL;
                }
 -      } else if (!strcmp(argv[0], "diff"))
 +      } else if (!strcmp(argv[0], "diff")) {
 +              if (setup_rerere(&merge_rr, flags | RERERE_READONLY) < 0)
 +                      return 0;
                for (i = 0; i < merge_rr.nr; i++) {
                        const char *path = merge_rr.items[i].string;
                        const char *name = (const char *)merge_rr.items[i].util;
-                       diff_two(rerere_path(name, "preimage"), path, path, path);
+                       if (diff_two(rerere_path(name, "preimage"), path, path, path))
+                               die("unable to generate diff for %s", name);
                }
 -      else
 +      else
                usage_with_options(rerere_usage, options);
  
        string_list_clear(&merge_rr, 1);
diff --combined diff.c
index 08508f6a2017eb35a350268ea78a44cfc1d867e4,71f0274ce0df2e6af8cd144c78c64dbb694c1ab7..46260ed7a1d521cf95342caff83a825b34959bdc
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -2,7 -2,6 +2,7 @@@
   * Copyright (C) 2005 Junio C Hamano
   */
  #include "cache.h"
 +#include "tempfile.h"
  #include "quote.h"
  #include "diff.h"
  #include "diffcore.h"
@@@ -14,7 -13,6 +14,7 @@@
  #include "utf8.h"
  #include "userdiff.h"
  #include "sigchain.h"
 +#include "submodule-config.h"
  #include "submodule.h"
  #include "ll-merge.h"
  #include "string-list.h"
@@@ -310,26 -308,11 +310,26 @@@ static const char *external_diff(void
        return external_diff_cmd;
  }
  
 +/*
 + * Keep track of files used for diffing. Sometimes such an entry
 + * refers to a temporary file, sometimes to an existing file, and
 + * sometimes to "/dev/null".
 + */
  static struct diff_tempfile {
 -      const char *name; /* filename external diff should read from */
 +      /*
 +       * filename external diff should read from, or NULL if this
 +       * entry is currently not in use:
 +       */
 +      const char *name;
 +
        char hex[41];
        char mode[10];
 -      char tmp_path[PATH_MAX];
 +
 +      /*
 +       * If this diff_tempfile instance refers to a temporary file,
 +       * this tempfile object is used to manage its lifetime.
 +       */
 +      struct tempfile tempfile;
  } diff_temp[2];
  
  typedef unsigned long (*sane_truncate_fn)(char *line, unsigned long len);
@@@ -614,16 -597,25 +614,16 @@@ static struct diff_tempfile *claim_diff
        die("BUG: diff is failing to clean up its tempfiles");
  }
  
 -static int remove_tempfile_installed;
 -
  static void remove_tempfile(void)
  {
        int i;
        for (i = 0; i < ARRAY_SIZE(diff_temp); i++) {
 -              if (diff_temp[i].name == diff_temp[i].tmp_path)
 -                      unlink_or_warn(diff_temp[i].name);
 +              if (is_tempfile_active(&diff_temp[i].tempfile))
 +                      delete_tempfile(&diff_temp[i].tempfile);
                diff_temp[i].name = NULL;
        }
  }
  
 -static void remove_tempfile_on_signal(int signo)
 -{
 -      remove_tempfile();
 -      sigchain_pop(signo);
 -      raise(signo);
 -}
 -
  static void print_line_count(FILE *file, int count)
  {
        switch (count) {
@@@ -1041,8 -1033,9 +1041,9 @@@ static void diff_words_show(struct diff
        xpp.flags = 0;
        /* as only the hunk header will be parsed, we need a 0-context */
        xecfg.ctxlen = 0;
-       xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
-                     &xpp, &xecfg);
+       if (xdi_diff_outf(&minus, &plus, fn_out_diff_words_aux, diff_words,
+                         &xpp, &xecfg))
+               die("unable to generate word diff");
        free(minus.ptr);
        free(plus.ptr);
        if (diff_words->current_plus != diff_words->plus.text.ptr +
@@@ -2449,8 -2442,9 +2450,9 @@@ static void builtin_diff(const char *na
                        xecfg.ctxlen = strtoul(v, NULL, 10);
                if (o->word_diff)
                        init_diff_words_data(&ecbdata, o, one, two);
-               xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
+                                 &xpp, &xecfg))
+                       die("unable to generate diff for %s", one->path);
                if (o->word_diff)
                        free_diff_words_data(&ecbdata);
                if (textconv_one)
@@@ -2527,8 -2521,9 +2529,9 @@@ static void builtin_diffstat(const cha
                xpp.flags = o->xdl_opts;
                xecfg.ctxlen = o->context;
                xecfg.interhunkctxlen = o->interhunkcontext;
-               xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, diffstat_consume, diffstat,
+                                 &xpp, &xecfg))
+                       die("unable to generate diffstat for %s", one->path);
        }
  
        diff_free_filespec_data(one);
@@@ -2574,8 -2569,9 +2577,9 @@@ static void builtin_checkdiff(const cha
                memset(&xecfg, 0, sizeof(xecfg));
                xecfg.ctxlen = 1; /* at least one context line */
                xpp.flags = 0;
-               xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, checkdiff_consume, &data,
+                                 &xpp, &xecfg))
+                       die("unable to generate checkdiff for %s", one->path);
  
                if (data.ws_rule & WS_BLANK_AT_EOF) {
                        struct emit_callback ecbdata;
@@@ -2866,7 -2862,8 +2870,7 @@@ static void prep_temp_blob(const char *
        strbuf_addstr(&template, "XXXXXX_");
        strbuf_addstr(&template, base);
  
 -      fd = git_mkstemps(temp->tmp_path, PATH_MAX, template.buf,
 -                      strlen(base) + 1);
 +      fd = mks_tempfile_ts(&temp->tempfile, template.buf, strlen(base) + 1);
        if (fd < 0)
                die_errno("unable to create temp-file");
        if (convert_to_working_tree(path,
        }
        if (write_in_full(fd, blob, size) != size)
                die_errno("unable to write temp-file");
 -      close(fd);
 -      temp->name = temp->tmp_path;
 +      close_tempfile(&temp->tempfile);
 +      temp->name = get_tempfile_path(&temp->tempfile);
        strcpy(temp->hex, sha1_to_hex(sha1));
        temp->hex[40] = 0;
        sprintf(temp->mode, "%06o", mode);
@@@ -2902,6 -2899,12 +2906,6 @@@ static struct diff_tempfile *prepare_te
                return temp;
        }
  
 -      if (!remove_tempfile_installed) {
 -              atexit(remove_tempfile);
 -              sigchain_push_common(remove_tempfile_on_signal);
 -              remove_tempfile_installed = 1;
 -      }
 -
        if (!S_ISGITLINK(one->mode) &&
            (!one->sha1_valid ||
             reuse_worktree_file(name, one->sha1, 1))) {
@@@ -3821,10 -3824,9 +3825,10 @@@ int diff_opt_parse(struct diff_options 
                DIFF_OPT_SET(options, FIND_COPIES_HARDER);
        else if (!strcmp(arg, "--follow"))
                DIFF_OPT_SET(options, FOLLOW_RENAMES);
 -      else if (!strcmp(arg, "--no-follow"))
 +      else if (!strcmp(arg, "--no-follow")) {
                DIFF_OPT_CLR(options, FOLLOW_RENAMES);
 -      else if (!strcmp(arg, "--color"))
 +              DIFF_OPT_CLR(options, DEFAULT_FOLLOW_RENAMES);
 +      } else if (!strcmp(arg, "--color"))
                options->use_color = 1;
        else if (skip_prefix(arg, "--color=", &arg)) {
                int value = git_config_colorbool(NULL, arg);
@@@ -4510,8 -4512,10 +4514,10 @@@ static int diff_get_patch_id(struct dif
                xpp.flags = 0;
                xecfg.ctxlen = 3;
                xecfg.flags = 0;
-               xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
-                             &xpp, &xecfg);
+               if (xdi_diff_outf(&mf1, &mf2, patch_id_consume, &data,
+                                 &xpp, &xecfg))
+                       return error("unable to generate patch-id diff for %s",
+                                    p->one->path);
        }
  
        git_SHA1_Final(sha1, &ctx);
diff --combined http.c
index 9dce38025c7b35f7f5ad19121c0529b442aec92d,45348fb9bd7d2713bed668f6ef8ffb43783601ff..0f924a8b48f3e30fd4a646ef48acb8d066988a53
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -9,6 -9,7 +9,7 @@@
  #include "version.h"
  #include "pkt-line.h"
  #include "gettext.h"
+ #include "transport.h"
  
  int active_requests;
  int http_is_verbose;
@@@ -37,20 -38,6 +38,20 @@@ static int curl_ssl_verify = -1
  static int curl_ssl_try;
  static const char *ssl_cert;
  static const char *ssl_cipherlist;
 +static const char *ssl_version;
 +static struct {
 +      const char *name;
 +      long ssl_version;
 +} sslversions[] = {
 +      { "sslv2", CURL_SSLVERSION_SSLv2 },
 +      { "sslv3", CURL_SSLVERSION_SSLv3 },
 +      { "tlsv1", CURL_SSLVERSION_TLSv1 },
 +#if LIBCURL_VERSION_NUM >= 0x072200
 +      { "tlsv1.0", CURL_SSLVERSION_TLSv1_0 },
 +      { "tlsv1.1", CURL_SSLVERSION_TLSv1_1 },
 +      { "tlsv1.2", CURL_SSLVERSION_TLSv1_2 },
 +#endif
 +};
  #if LIBCURL_VERSION_NUM >= 0x070903
  static const char *ssl_key;
  #endif
@@@ -204,8 -191,6 +205,8 @@@ static int http_options(const char *var
        }
        if (!strcmp("http.sslcipherlist", var))
                return git_config_string(&ssl_cipherlist, var, value);
 +      if (!strcmp("http.sslversion", var))
 +              return git_config_string(&ssl_version, var, value);
        if (!strcmp("http.sslcert", var))
                return git_config_string(&ssl_cert, var, value);
  #if LIBCURL_VERSION_NUM >= 0x070903
@@@ -356,6 -341,7 +357,7 @@@ static void set_curl_keepalive(CURL *c
  static CURL *get_curl_handle(void)
  {
        CURL *result = curl_easy_init();
+       long allowed_protocols = 0;
  
        if (!result)
                die("curl_easy_init failed");
        if (http_proactive_auth)
                init_curl_http_auth(result);
  
 +      if (getenv("GIT_SSL_VERSION"))
 +              ssl_version = getenv("GIT_SSL_VERSION");
 +      if (ssl_version && *ssl_version) {
 +              int i;
 +              for (i = 0; i < ARRAY_SIZE(sslversions); i++) {
 +                      if (!strcmp(ssl_version, sslversions[i].name)) {
 +                              curl_easy_setopt(result, CURLOPT_SSLVERSION,
 +                                               sslversions[i].ssl_version);
 +                              break;
 +                      }
 +              }
 +              if (i == ARRAY_SIZE(sslversions))
 +                      warning("unsupported ssl version %s: using default",
 +                              ssl_version);
 +      }
 +
        if (getenv("GIT_SSL_CIPHER_LIST"))
                ssl_cipherlist = getenv("GIT_SSL_CIPHER_LIST");
 -
        if (ssl_cipherlist != NULL && *ssl_cipherlist)
                curl_easy_setopt(result, CURLOPT_SSL_CIPHER_LIST,
                                ssl_cipherlist);
        }
  
        curl_easy_setopt(result, CURLOPT_FOLLOWLOCATION, 1);
+       curl_easy_setopt(result, CURLOPT_MAXREDIRS, 20);
  #if LIBCURL_VERSION_NUM >= 0x071301
        curl_easy_setopt(result, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
  #elif LIBCURL_VERSION_NUM >= 0x071101
        curl_easy_setopt(result, CURLOPT_POST301, 1);
  #endif
+ #if LIBCURL_VERSION_NUM >= 0x071304
+       if (is_transport_allowed("http"))
+               allowed_protocols |= CURLPROTO_HTTP;
+       if (is_transport_allowed("https"))
+               allowed_protocols |= CURLPROTO_HTTPS;
+       if (is_transport_allowed("ftp"))
+               allowed_protocols |= CURLPROTO_FTP;
+       if (is_transport_allowed("ftps"))
+               allowed_protocols |= CURLPROTO_FTPS;
+       curl_easy_setopt(result, CURLOPT_REDIR_PROTOCOLS, allowed_protocols);
+ #else
+       if (transport_restrict_protocols())
+               warning("protocol restrictions not applied to curl redirects because\n"
+                       "your curl version is too old (>= 7.19.4)");
+ #endif
  
        if (getenv("GIT_CURL_VERBOSE"))
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
@@@ -1349,7 -1336,7 +1367,7 @@@ static int http_get_file(const char *ur
        ret = http_request_reauth(url, result, HTTP_REQUEST_FILE, options);
        fclose(result);
  
 -      if (ret == HTTP_OK && move_temp_to_file(tmpfile.buf, filename))
 +      if (ret == HTTP_OK && finalize_object_file(tmpfile.buf, filename))
                ret = HTTP_ERROR;
  cleanup:
        strbuf_release(&tmpfile);
@@@ -1436,7 -1423,7 +1454,7 @@@ static int fetch_and_setup_pack_index(s
        ret = verify_pack_index(new_pack);
        if (!ret) {
                close_pack_index(new_pack);
 -              ret = move_temp_to_file(tmp_idx, sha1_pack_index_name(sha1));
 +              ret = finalize_object_file(tmp_idx, sha1_pack_index_name(sha1));
        }
        free(tmp_idx);
        if (ret)
@@@ -1548,8 -1535,8 +1566,8 @@@ int finish_http_pack_request(struct htt
  
        unlink(sha1_pack_index_name(p->sha1));
  
 -      if (move_temp_to_file(preq->tmpfile, sha1_pack_name(p->sha1))
 -       || move_temp_to_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
 +      if (finalize_object_file(preq->tmpfile, sha1_pack_name(p->sha1))
 +       || finalize_object_file(tmp_idx, sha1_pack_index_name(p->sha1))) {
                free(tmp_idx);
                return -1;
        }
@@@ -1813,7 -1800,7 +1831,7 @@@ int finish_http_object_request(struct h
                return -1;
        }
        freq->rename =
 -              move_temp_to_file(freq->tmpfile, sha1_file_name(freq->sha1));
 +              finalize_object_file(freq->tmpfile, sha1_file_name(freq->sha1));
  
        return freq->rename;
  }
diff --combined transport-helper.c
index 99f1ace1f2b56c359eba6736d1f0ab0fb49ba980,12f50d5b94802eed31bf36377f297bbd62f7d83c..63d54271b09d34dc21076205e6cecf88a56ab422
@@@ -257,6 -257,7 +257,6 @@@ static const char *boolean_options[] = 
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP,
        TRANS_OPT_FOLLOWTAGS,
 -      TRANS_OPT_PUSH_CERT
        };
  
  static int set_helper_option(struct transport *transport,
@@@ -763,21 -764,6 +763,21 @@@ static int push_update_refs_status(stru
        return ret;
  }
  
 +static void set_common_push_options(struct transport *transport,
 +                                 const char *name, int flags)
 +{
 +      if (flags & TRANSPORT_PUSH_DRY_RUN) {
 +              if (set_helper_option(transport, "dry-run", "true") != 0)
 +                      die("helper %s does not support dry-run", name);
 +      } else if (flags & TRANSPORT_PUSH_CERT_ALWAYS) {
 +              if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
 +                      die("helper %s does not support --signed", name);
 +      } else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED) {
 +              if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "if-asked") != 0)
 +                      die("helper %s does not support --signed=if-asked", name);
 +      }
 +}
 +
  static int push_refs_with_push(struct transport *transport,
                               struct ref *remote_refs, int flags)
  {
  
        for_each_string_list_item(cas_option, &cas_options)
                set_helper_option(transport, "cas", cas_option->string);
 -
 -      if (flags & TRANSPORT_PUSH_DRY_RUN) {
 -              if (set_helper_option(transport, "dry-run", "true") != 0)
 -                      die("helper %s does not support dry-run", data->name);
 -      } else if (flags & TRANSPORT_PUSH_CERT) {
 -              if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
 -                      die("helper %s does not support --signed", data->name);
 -      }
 +      set_common_push_options(transport, data->name, flags);
  
        strbuf_addch(&buf, '\n');
        sendline(data, &buf);
@@@ -866,7 -859,14 +866,7 @@@ static int push_refs_with_export(struc
        if (!data->refspecs)
                die("remote-helper doesn't support push; refspec needed");
  
 -      if (flags & TRANSPORT_PUSH_DRY_RUN) {
 -              if (set_helper_option(transport, "dry-run", "true") != 0)
 -                      die("helper %s does not support dry-run", data->name);
 -      } else if (flags & TRANSPORT_PUSH_CERT) {
 -              if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
 -                      die("helper %s does not support --signed", data->name);
 -      }
 -
 +      set_common_push_options(transport, data->name, flags);
        if (flags & TRANSPORT_PUSH_FORCE) {
                if (set_helper_option(transport, "force", "true") != 0)
                        warning("helper %s does not support 'force'", data->name);
@@@ -1043,6 -1043,8 +1043,8 @@@ int transport_helper_init(struct transp
        struct helper_data *data = xcalloc(1, sizeof(*data));
        data->name = name;
  
+       transport_check_allowed(name);
        if (getenv("GIT_TRANSPORT_HELPER_DEBUG"))
                debug = 1;
  
diff --combined transport.c
index 2d51348f3f3a5be03b2d00ec6530ac4cabfbf7c6,164a716053ea6511a57bdcf27c3cd1810edfe36d..863eb524f9087302be6b99c89f071195dd1258b8
@@@ -291,7 -291,7 +291,7 @@@ static int write_one_ref(const char *na
  
        strbuf_addstr(buf, name);
        if (safe_create_leading_directories(buf->buf) ||
 -          write_file(buf->buf, 0, "%s\n", oid_to_hex(oid)))
 +          write_file_gently(buf->buf, "%s", oid_to_hex(oid)))
                return error("problems writing temporary file %s: %s",
                             buf->buf, strerror(errno));
        strbuf_setlen(buf, len);
@@@ -476,6 -476,9 +476,6 @@@ static int set_git_option(struct git_tr
                                die("transport: invalid depth option '%s'", value);
                }
                return 0;
 -      } else if (!strcmp(name, TRANS_OPT_PUSH_CERT)) {
 -              opts->push_cert = !!value;
 -              return 0;
        }
        return 1;
  }
@@@ -826,16 -829,10 +826,16 @@@ static int git_transport_push(struct tr
        args.progress = transport->progress;
        args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
        args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
 -      args.push_cert = !!(flags & TRANSPORT_PUSH_CERT);
        args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
        args.url = transport->url;
  
 +      if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
 +              args.push_cert = SEND_PACK_PUSH_CERT_ALWAYS;
 +      else if (flags & TRANSPORT_PUSH_CERT_IF_ASKED)
 +              args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED;
 +      else
 +              args.push_cert = SEND_PACK_PUSH_CERT_NEVER;
 +
        ret = send_pack(&args, data->fd, data->conn, remote_refs,
                        &data->extra_have);
  
@@@ -915,6 -912,42 +915,42 @@@ static int external_specification_len(c
        return strchr(url, ':') - url;
  }
  
+ static const struct string_list *protocol_whitelist(void)
+ {
+       static int enabled = -1;
+       static struct string_list allowed = STRING_LIST_INIT_DUP;
+       if (enabled < 0) {
+               const char *v = getenv("GIT_ALLOW_PROTOCOL");
+               if (v) {
+                       string_list_split(&allowed, v, ':', -1);
+                       string_list_sort(&allowed);
+                       enabled = 1;
+               } else {
+                       enabled = 0;
+               }
+       }
+       return enabled ? &allowed : NULL;
+ }
+ int is_transport_allowed(const char *type)
+ {
+       const struct string_list *allowed = protocol_whitelist();
+       return !allowed || string_list_has_string(allowed, type);
+ }
+ void transport_check_allowed(const char *type)
+ {
+       if (!is_transport_allowed(type))
+               die("transport '%s' not allowed", type);
+ }
+ int transport_restrict_protocols(void)
+ {
+       return !!protocol_whitelist();
+ }
  struct transport *transport_get(struct remote *remote, const char *url)
  {
        const char *helper;
        if (helper) {
                transport_helper_init(ret, helper);
        } else if (starts_with(url, "rsync:")) {
+               transport_check_allowed("rsync");
                ret->get_refs_list = get_refs_via_rsync;
                ret->fetch = fetch_objs_via_rsync;
                ret->push = rsync_transport_push;
                ret->smart_options = NULL;
        } else if (url_is_local_not_ssh(url) && is_file(url) && is_bundle(url, 1)) {
                struct bundle_transport_data *data = xcalloc(1, sizeof(*data));
+               transport_check_allowed("file");
                ret->data = data;
                ret->get_refs_list = get_refs_from_bundle;
                ret->fetch = fetch_refs_from_bundle;
                || starts_with(url, "ssh://")
                || starts_with(url, "git+ssh://")
                || starts_with(url, "ssh+git://")) {
-               /* These are builtin smart transports. */
+               /*
+                * These are builtin smart transports; "allowed" transports
+                * will be checked individually in git_connect.
+                */
                struct git_transport_data *data = xcalloc(1, sizeof(*data));
                ret->data = data;
                ret->set_option = NULL;
diff --combined transport.h
index d682b77b9e3be70954f3552813f66718d7262151,97707774e725b04eb1ec7a60d6fe30eedbcd8026..4336dd33eb301306ff2a57c4c2c4f8c45d61fc50
@@@ -12,6 -12,7 +12,6 @@@ struct git_transport_options 
        unsigned check_self_contained_and_connected : 1;
        unsigned self_contained_and_connected : 1;
        unsigned update_shallow : 1;
 -      unsigned push_cert : 1;
        int depth;
        const char *uploadpack;
        const char *receivepack;
@@@ -123,9 -124,8 +123,9 @@@ struct transport 
  #define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
  #define TRANSPORT_PUSH_NO_HOOK 512
  #define TRANSPORT_PUSH_FOLLOW_TAGS 1024
 -#define TRANSPORT_PUSH_CERT 2048
 -#define TRANSPORT_PUSH_ATOMIC 4096
 +#define TRANSPORT_PUSH_CERT_ALWAYS 2048
 +#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
 +#define TRANSPORT_PUSH_ATOMIC 8192
  
  #define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
  #define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
  /* Returns a transport suitable for the url */
  struct transport *transport_get(struct remote *, const char *);
  
+ /*
+  * Check whether a transport is allowed by the environment. Type should
+  * generally be the URL scheme, as described in Documentation/git.txt
+  */
+ int is_transport_allowed(const char *type);
+ /*
+  * Check whether a transport is allowed by the environment,
+  * and die otherwise.
+  */
+ void transport_check_allowed(const char *type);
+ /*
+  * Returns true if the user has attempted to turn on protocol
+  * restrictions at all.
+  */
+ int transport_restrict_protocols(void);
  /* Transport options which apply to git:// and scp-style URLs */
  
  /* The program to use on the remote side to send a pack */