Merge branch 'bc/http-100-continue'
authorJunio C Hamano <gitster@pobox.com>
Thu, 5 Dec 2013 20:58:58 +0000 (12:58 -0800)
committerJunio C Hamano <gitster@pobox.com>
Thu, 5 Dec 2013 20:58:59 +0000 (12:58 -0800)
Issue "100 Continue" responses to help use of GSS-Negotiate
authentication scheme over HTTP transport when needed.

* bc/http-100-continue:
remote-curl: fix large pushes with GSSAPI
remote-curl: pass curl slot_results back through run_slot
http: return curl's AUTHAVAIL via slot_results

1  2 
http.c
http.h
remote-curl.c
diff --combined http.c
index bcf54aa35f96e8f0b87b6e2fd23408991a684b66,1ea62fad4fa95c004f77ad4f09bab2a00cfab524..ccb813b86fb47dac7dcb3a1eb86faaa9e8ecc923
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -3,7 -3,6 +3,7 @@@
  #include "sideband.h"
  #include "run-command.h"
  #include "url.h"
 +#include "urlmatch.h"
  #include "credential.h"
  #include "version.h"
  #include "pkt-line.h"
@@@ -46,8 -45,7 +46,8 @@@ static long curl_low_speed_time = -1
  static int curl_ftp_no_epsv;
  static const char *curl_http_proxy;
  static const char *curl_cookie_file;
 -static struct credential http_auth = CREDENTIAL_INIT;
 +static int curl_save_cookies;
 +struct credential http_auth = CREDENTIAL_INIT;
  static int http_proactive_auth;
  static const char *user_agent;
  
@@@ -162,7 -160,8 +162,7 @@@ static int http_options(const char *var
        if (!strcmp("http.sslcainfo", var))
                return git_config_string(&ssl_cainfo, var, value);
        if (!strcmp("http.sslcertpasswordprotected", var)) {
 -              if (git_config_bool(var, value))
 -                      ssl_cert_password_required = 1;
 +              ssl_cert_password_required = git_config_bool(var, value);
                return 0;
        }
        if (!strcmp("http.ssltry", var)) {
  
        if (!strcmp("http.cookiefile", var))
                return git_config_string(&curl_cookie_file, var, value);
 +      if (!strcmp("http.savecookies", var)) {
 +              curl_save_cookies = git_config_bool(var, value);
 +              return 0;
 +      }
  
        if (!strcmp("http.postbuffer", var)) {
                http_post_buffer = git_config_int(var, value);
@@@ -260,42 -255,6 +260,42 @@@ static int has_cert_password(void
        return 1;
  }
  
 +#if LIBCURL_VERSION_NUM >= 0x071900
 +static void set_curl_keepalive(CURL *c)
 +{
 +      curl_easy_setopt(c, CURLOPT_TCP_KEEPALIVE, 1);
 +}
 +
 +#elif LIBCURL_VERSION_NUM >= 0x071000
 +static int sockopt_callback(void *client, curl_socket_t fd, curlsocktype type)
 +{
 +      int ka = 1;
 +      int rc;
 +      socklen_t len = (socklen_t)sizeof(ka);
 +
 +      if (type != CURLSOCKTYPE_IPCXN)
 +              return 0;
 +
 +      rc = setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&ka, len);
 +      if (rc < 0)
 +              warning("unable to set SO_KEEPALIVE on socket %s",
 +                      strerror(errno));
 +
 +      return 0; /* CURL_SOCKOPT_OK only exists since curl 7.21.5 */
 +}
 +
 +static void set_curl_keepalive(CURL *c)
 +{
 +      curl_easy_setopt(c, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);
 +}
 +
 +#else
 +static void set_curl_keepalive(CURL *c)
 +{
 +      /* not supported on older curl versions */
 +}
 +#endif
 +
  static CURL *get_curl_handle(void)
  {
        CURL *result = curl_easy_init();
                curl_easy_setopt(result, CURLOPT_PROXYAUTH, CURLAUTH_ANY);
        }
  
 +      set_curl_keepalive(result);
 +
        return result;
  }
  
@@@ -384,20 -341,10 +384,20 @@@ void http_init(struct remote *remote, c
  {
        char *low_speed_limit;
        char *low_speed_time;
 +      char *normalized_url;
 +      struct urlmatch_config config = { STRING_LIST_INIT_DUP };
 +
 +      config.section = "http";
 +      config.key = NULL;
 +      config.collect_fn = http_options;
 +      config.cascade_fn = git_default_config;
 +      config.cb = NULL;
  
        http_is_verbose = 0;
 +      normalized_url = url_normalize(url, &config.url);
  
 -      git_config(http_options, NULL);
 +      git_config(urlmatch_config_entry, &config);
 +      free(normalized_url);
  
        curl_global_init(CURL_GLOBAL_ALL);
  
@@@ -566,8 -513,6 +566,8 @@@ struct active_request_slot *get_active_
        slot->callback_data = NULL;
        slot->callback_func = NULL;
        curl_easy_setopt(slot->curl, CURLOPT_COOKIEFILE, curl_cookie_file);
 +      if (curl_save_cookies)
 +              curl_easy_setopt(slot->curl, CURLOPT_COOKIEJAR, curl_cookie_file);
        curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header);
        curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, curl_errorstr);
        curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, NULL);
@@@ -761,6 -706,12 +761,12 @@@ void finish_active_slot(struct active_r
        if (slot->results != NULL) {
                slot->results->curl_result = slot->curl_result;
                slot->results->http_code = slot->http_code;
+ #if LIBCURL_VERSION_NUM >= 0x070a08
+               curl_easy_getinfo(slot->curl, CURLINFO_HTTPAUTH_AVAIL,
+                                 &slot->results->auth_avail);
+ #else
+               slot->results->auth_avail = 0;
+ #endif
        }
  
        /* Run callback if appropriate */
@@@ -861,6 -812,7 +867,6 @@@ int handle_curl_result(struct slot_resu
                        credential_reject(&http_auth);
                        return HTTP_NOAUTH;
                } else {
 -                      credential_fill(&http_auth);
                        return HTTP_REAUTH;
                }
        } else {
        }
  }
  
 +static CURLcode curlinfo_strbuf(CURL *curl, CURLINFO info, struct strbuf *buf)
 +{
 +      char *ptr;
 +      CURLcode ret;
 +
 +      strbuf_reset(buf);
 +      ret = curl_easy_getinfo(curl, info, &ptr);
 +      if (!ret && ptr)
 +              strbuf_addstr(buf, ptr);
 +      return ret;
 +}
 +
  /* http_request() targets */
  #define HTTP_REQUEST_STRBUF   0
  #define HTTP_REQUEST_FILE     1
  
 -static int http_request(const char *url, struct strbuf *type,
 -                      void *result, int target, int options)
 +static int http_request(const char *url,
 +                      void *result, int target,
 +                      const struct http_get_options *options)
  {
        struct active_request_slot *slot;
        struct slot_results results;
        }
  
        strbuf_addstr(&buf, "Pragma:");
 -      if (options & HTTP_NO_CACHE)
 +      if (options && options->no_cache)
                strbuf_addstr(&buf, " no-cache");
 -      if (options & HTTP_KEEP_ERROR)
 +      if (options && options->keep_error)
                curl_easy_setopt(slot->curl, CURLOPT_FAILONERROR, 0);
  
        headers = curl_slist_append(headers, buf.buf);
                ret = HTTP_START_FAILED;
        }
  
 -      if (type) {
 -              char *t;
 -              strbuf_reset(type);
 -              curl_easy_getinfo(slot->curl, CURLINFO_CONTENT_TYPE, &t);
 -              if (t)
 -                      strbuf_addstr(type, t);
 -      }
 +      if (options && options->content_type)
 +              curlinfo_strbuf(slot->curl, CURLINFO_CONTENT_TYPE,
 +                              options->content_type);
 +
 +      if (options && options->effective_url)
 +              curlinfo_strbuf(slot->curl, CURLINFO_EFFECTIVE_URL,
 +                              options->effective_url);
  
        curl_slist_free_all(headers);
        strbuf_release(&buf);
        return ret;
  }
  
 +/*
 + * Update the "base" url to a more appropriate value, as deduced by
 + * redirects seen when requesting a URL starting with "url".
 + *
 + * The "asked" parameter is a URL that we asked curl to access, and must begin
 + * with "base".
 + *
 + * The "got" parameter is the URL that curl reported to us as where we ended
 + * up.
 + *
 + * Returns 1 if we updated the base url, 0 otherwise.
 + *
 + * Our basic strategy is to compare "base" and "asked" to find the bits
 + * specific to our request. We then strip those bits off of "got" to yield the
 + * new base. So for example, if our base is "http://example.com/foo.git",
 + * and we ask for "http://example.com/foo.git/info/refs", we might end up
 + * with "https://other.example.com/foo.git/info/refs". We would want the
 + * new URL to become "https://other.example.com/foo.git".
 + *
 + * Note that this assumes a sane redirect scheme. It's entirely possible
 + * in the example above to end up at a URL that does not even end in
 + * "info/refs".  In such a case we simply punt, as there is not much we can
 + * do (and such a scheme is unlikely to represent a real git repository,
 + * which means we are likely about to abort anyway).
 + */
 +static int update_url_from_redirect(struct strbuf *base,
 +                                  const char *asked,
 +                                  const struct strbuf *got)
 +{
 +      const char *tail;
 +      size_t tail_len;
 +
 +      if (!strcmp(asked, got->buf))
 +              return 0;
 +
 +      if (prefixcmp(asked, base->buf))
 +              die("BUG: update_url_from_redirect: %s is not a superset of %s",
 +                  asked, base->buf);
 +
 +      tail = asked + base->len;
 +      tail_len = strlen(tail);
 +
 +      if (got->len < tail_len ||
 +          strcmp(tail, got->buf + got->len - tail_len))
 +              return 0; /* insane redirect scheme */
 +
 +      strbuf_reset(base);
 +      strbuf_add(base, got->buf, got->len - tail_len);
 +      return 1;
 +}
 +
  static int http_request_reauth(const char *url,
 -                             struct strbuf *type,
                               void *result, int target,
 -                             int options)
 +                             struct http_get_options *options)
  {
 -      int ret = http_request(url, type, result, target, options);
 +      int ret = http_request(url, result, target, options);
 +
 +      if (options && options->effective_url && options->base_url) {
 +              if (update_url_from_redirect(options->base_url,
 +                                           url, options->effective_url)) {
 +                      credential_from_url(&http_auth, options->base_url->buf);
 +                      url = options->effective_url->buf;
 +              }
 +      }
 +
        if (ret != HTTP_REAUTH)
                return ret;
  
         * making our next request. We only know how to do this for
         * the strbuf case, but that is enough to satisfy current callers.
         */
 -      if (options & HTTP_KEEP_ERROR) {
 +      if (options && options->keep_error) {
                switch (target) {
                case HTTP_REQUEST_STRBUF:
                        strbuf_reset(result);
                        die("BUG: HTTP_KEEP_ERROR is only supported with strbufs");
                }
        }
 -      return http_request(url, type, result, target, options);
 +
 +      credential_fill(&http_auth);
 +
 +      return http_request(url, result, target, options);
  }
  
  int http_get_strbuf(const char *url,
 -                  struct strbuf *type,
 -                  struct strbuf *result, int options)
 +                  struct strbuf *result,
 +                  struct http_get_options *options)
  {
 -      return http_request_reauth(url, type, result,
 -                                 HTTP_REQUEST_STRBUF, options);
 +      return http_request_reauth(url, result, HTTP_REQUEST_STRBUF, options);
  }
  
  /*
   * If a previous interrupted download is detected (i.e. a previous temporary
   * file is still around) the download is resumed.
   */
 -static int http_get_file(const char *url, const char *filename, int options)
 +static int http_get_file(const char *url, const char *filename,
 +                       struct http_get_options *options)
  {
        int ret;
        struct strbuf tmpfile = STRBUF_INIT;
  
        strbuf_addf(&tmpfile, "%s.temp", filename);
        result = fopen(tmpfile.buf, "a");
 -      if (! result) {
 +      if (!result) {
                error("Unable to open local file %s", tmpfile.buf);
                ret = HTTP_ERROR;
                goto cleanup;
        }
  
 -      ret = http_request_reauth(url, NULL, result, HTTP_REQUEST_FILE, options);
 +      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 && move_temp_to_file(tmpfile.buf, filename))
                ret = HTTP_ERROR;
  cleanup:
        strbuf_release(&tmpfile);
  
  int http_fetch_ref(const char *base, struct ref *ref)
  {
 +      struct http_get_options options = {0};
        char *url;
        struct strbuf buffer = STRBUF_INIT;
        int ret = -1;
  
 +      options.no_cache = 1;
 +
        url = quote_ref_url(base, ref->name);
 -      if (http_get_strbuf(url, NULL, &buffer, HTTP_NO_CACHE) == HTTP_OK) {
 +      if (http_get_strbuf(url, &buffer, &options) == HTTP_OK) {
                strbuf_rtrim(&buffer);
                if (buffer.len == 40)
                        ret = get_sha1_hex(buffer.buf, ref->old_sha1);
@@@ -1127,7 -1001,7 +1133,7 @@@ static char *fetch_pack_index(unsigned 
        strbuf_addf(&buf, "%s.temp", sha1_pack_index_name(sha1));
        tmp = strbuf_detach(&buf, NULL);
  
 -      if (http_get_file(url, tmp, 0) != HTTP_OK) {
 +      if (http_get_file(url, tmp, NULL) != HTTP_OK) {
                error("Unable to get pack index %s", url);
                free(tmp);
                tmp = NULL;
@@@ -1180,7 -1054,6 +1186,7 @@@ add_pack
  
  int http_get_info_packs(const char *base_url, struct packed_git **packs_head)
  {
 +      struct http_get_options options = {0};
        int ret = 0, i = 0;
        char *url, *data;
        struct strbuf buf = STRBUF_INIT;
        strbuf_addstr(&buf, "objects/info/packs");
        url = strbuf_detach(&buf, NULL);
  
 -      ret = http_get_strbuf(url, NULL, &buf, HTTP_NO_CACHE);
 +      options.no_cache = 1;
 +      ret = http_get_strbuf(url, &buf, &options);
        if (ret != HTTP_OK)
                goto cleanup;
  
diff --combined http.h
index 12ca5c892d61a063c856bcdef9154a103c0aa4bc,81d48432930b3b68f8f420028ba69b1f91063080..cd37d5881cb0e63e52bd33d0b49bed49645340f5
--- 1/http.h
--- 2/http.h
+++ b/http.h
@@@ -54,6 -54,7 +54,7 @@@
  struct slot_results {
        CURLcode curl_result;
        long http_code;
+       long auth_avail;
  };
  
  struct active_request_slot {
@@@ -102,7 -103,6 +103,7 @@@ extern void http_cleanup(void)
  extern int active_requests;
  extern int http_is_verbose;
  extern size_t http_post_buffer;
 +extern struct credential http_auth;
  
  extern char curl_errorstr[CURL_ERROR_SIZE];
  
@@@ -126,30 -126,11 +127,30 @@@ extern void append_remote_object_url(st
  extern char *get_remote_object_url(const char *url, const char *hex,
                                   int only_two_digit_prefix);
  
 -/* Options for http_request_*() */
 -#define HTTP_NO_CACHE         1
 -#define HTTP_KEEP_ERROR               2
 +/* Options for http_get_*() */
 +struct http_get_options {
 +      unsigned no_cache:1,
 +               keep_error:1;
 +
 +      /* If non-NULL, returns the content-type of the response. */
 +      struct strbuf *content_type;
 +
 +      /*
 +       * If non-NULL, returns the URL we ended up at, including any
 +       * redirects we followed.
 +       */
 +      struct strbuf *effective_url;
 +
 +      /*
 +       * If both base_url and effective_url are non-NULL, the base URL will
 +       * be munged to reflect any redirections going from the requested url
 +       * to effective_url. See the definition of update_url_from_redirect
 +       * for details.
 +       */
 +      struct strbuf *base_url;
 +};
  
 -/* Return values for http_request_*() */
 +/* Return values for http_get_*() */
  #define HTTP_OK                       0
  #define HTTP_MISSING_TARGET   1
  #define HTTP_ERROR            2
   *
   * If the result pointer is NULL, a HTTP HEAD request is made instead of GET.
   */
 -int http_get_strbuf(const char *url, struct strbuf *content_type, struct strbuf *result, int options);
 +int http_get_strbuf(const char *url, struct strbuf *result, struct http_get_options *options);
  
  extern int http_fetch_ref(const char *base, struct ref *ref);
  
diff --combined remote-curl.c
index c9b891adbf134193f07681a631ae368671e04aa6,427d50f8dec6e94ed831683403aa435bcfe83080..91b07a41456fe5cdb2157a648353d982b88f7e9a
@@@ -6,26 -6,21 +6,26 @@@
  #include "exec_cmd.h"
  #include "run-command.h"
  #include "pkt-line.h"
 +#include "string-list.h"
  #include "sideband.h"
  #include "argv-array.h"
 +#include "credential.h"
  
  static struct remote *remote;
 -static const char *url; /* always ends with a trailing slash */
 +/* always ends with a trailing slash */
 +static struct strbuf url = STRBUF_INIT;
  
  struct options {
        int verbosity;
        unsigned long depth;
        unsigned progress : 1,
 +              check_self_contained_and_connected : 1,
                followtags : 1,
                dry_run : 1,
                thin : 1;
  };
  static struct options options;
 +static struct string_list cas_options = STRING_LIST_INIT_DUP;
  
  static int set_option(const char *name, const char *value)
  {
                        return -1;
                return 0;
        }
 +      else if (!strcmp(name, "check-connectivity")) {
 +              if (!strcmp(value, "true"))
 +                      options.check_self_contained_and_connected = 1;
 +              else if (!strcmp(value, "false"))
 +                      options.check_self_contained_and_connected = 0;
 +              else
 +                      return -1;
 +              return 0;
 +      }
 +      else if (!strcmp(name, "cas")) {
 +              struct strbuf val = STRBUF_INIT;
 +              strbuf_addf(&val, "--" CAS_OPT_NAME "=%s", value);
 +              string_list_append(&cas_options, val.buf);
 +              strbuf_release(&val);
 +              return 0;
 +      }
        else {
                return 1 /* unsupported */;
        }
@@@ -132,8 -111,7 +132,8 @@@ static struct ref *parse_info_refs(stru
                        mid = &data[i];
                if (data[i] == '\n') {
                        if (mid - start != 40)
 -                              die("%sinfo/refs not valid: is this a git repository?", url);
 +                              die("%sinfo/refs not valid: is this a git repository?",
 +                                  url.buf);
                        data[i] = 0;
                        ref_name = mid + 1;
                        ref = xmalloc(sizeof(struct ref) +
        }
  
        ref = alloc_ref("HEAD");
 -      if (!http_fetch_ref(url, ref) &&
 +      if (!http_fetch_ref(url.buf, ref) &&
            !resolve_remote_symref(ref, refs)) {
                ref->next = refs;
                refs = ref;
@@@ -206,47 -184,40 +206,47 @@@ static struct discovery* discover_refs(
        struct strbuf exp = STRBUF_INIT;
        struct strbuf type = STRBUF_INIT;
        struct strbuf buffer = STRBUF_INIT;
 +      struct strbuf refs_url = STRBUF_INIT;
 +      struct strbuf effective_url = STRBUF_INIT;
        struct discovery *last = last_discovery;
 -      char *refs_url;
        int http_ret, maybe_smart = 0;
 +      struct http_get_options options;
  
        if (last && !strcmp(service, last->service))
                return last;
        free_discovery(last);
  
 -      strbuf_addf(&buffer, "%sinfo/refs", url);
 -      if ((!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) &&
 +      strbuf_addf(&refs_url, "%sinfo/refs", url.buf);
 +      if ((!prefixcmp(url.buf, "http://") || !prefixcmp(url.buf, "https://")) &&
             git_env_bool("GIT_SMART_HTTP", 1)) {
                maybe_smart = 1;
 -              if (!strchr(url, '?'))
 -                      strbuf_addch(&buffer, '?');
 +              if (!strchr(url.buf, '?'))
 +                      strbuf_addch(&refs_url, '?');
                else
 -                      strbuf_addch(&buffer, '&');
 -              strbuf_addf(&buffer, "service=%s", service);
 +                      strbuf_addch(&refs_url, '&');
 +              strbuf_addf(&refs_url, "service=%s", service);
        }
 -      refs_url = strbuf_detach(&buffer, NULL);
  
 -      http_ret = http_get_strbuf(refs_url, &type, &buffer,
 -                                 HTTP_NO_CACHE | HTTP_KEEP_ERROR);
 +      memset(&options, 0, sizeof(options));
 +      options.content_type = &type;
 +      options.effective_url = &effective_url;
 +      options.base_url = &url;
 +      options.no_cache = 1;
 +      options.keep_error = 1;
 +
 +      http_ret = http_get_strbuf(refs_url.buf, &buffer, &options);
        switch (http_ret) {
        case HTTP_OK:
                break;
        case HTTP_MISSING_TARGET:
                show_http_message(&type, &buffer);
 -              die("repository '%s' not found", url);
 +              die("repository '%s' not found", url.buf);
        case HTTP_NOAUTH:
                show_http_message(&type, &buffer);
 -              die("Authentication failed for '%s'", url);
 +              die("Authentication failed for '%s'", url.buf);
        default:
                show_http_message(&type, &buffer);
 -              die("unable to access '%s': %s", url, curl_errorstr);
 +              die("unable to access '%s': %s", url.buf, curl_errorstr);
        }
  
        last= xcalloc(1, sizeof(*last_discovery));
        else
                last->refs = parse_info_refs(last);
  
 -      free(refs_url);
 +      strbuf_release(&refs_url);
        strbuf_release(&exp);
        strbuf_release(&type);
 +      strbuf_release(&effective_url);
        strbuf_release(&buffer);
        last_discovery = last;
        return last;
@@@ -394,25 -364,29 +394,29 @@@ static size_t rpc_in(char *ptr, size_t 
        return size;
  }
  
- static int run_slot(struct active_request_slot *slot)
+ static int run_slot(struct active_request_slot *slot,
+                   struct slot_results *results)
  {
        int err;
-       struct slot_results results;
+       struct slot_results results_buf;
  
-       slot->results = &results;
+       if (!results)
+               results = &results_buf;
+       slot->results = results;
        slot->curl_result = curl_easy_perform(slot->curl);
        finish_active_slot(slot);
  
-       err = handle_curl_result(&results);
+       err = handle_curl_result(results);
        if (err != HTTP_OK && err != HTTP_REAUTH) {
                error("RPC failed; result=%d, HTTP code = %ld",
-                     results.curl_result, results.http_code);
+                     results->curl_result, results->http_code);
        }
  
        return err;
  }
  
- static int probe_rpc(struct rpc_state *rpc)
+ static int probe_rpc(struct rpc_state *rpc, struct slot_results *results)
  {
        struct active_request_slot *slot;
        struct curl_slist *headers = NULL;
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, &buf);
  
-       err = run_slot(slot);
+       err = run_slot(slot, results);
  
        curl_slist_free_all(headers);
        strbuf_release(&buf);
@@@ -449,6 -423,7 +453,7 @@@ static int post_rpc(struct rpc_state *r
        char *gzip_body = NULL;
        size_t gzip_size = 0;
        int err, large_request = 0;
+       int needs_100_continue = 0;
  
        /* Try to load the entire request, if we can fit it into the
         * allocated buffer space we can use HTTP/1.0 and avoid the
        }
  
        if (large_request) {
+               struct slot_results results;
                do {
-                       err = probe_rpc(rpc);
+                       err = probe_rpc(rpc, &results);
 +                      if (err == HTTP_REAUTH)
 +                              credential_fill(&http_auth);
                } while (err == HTTP_REAUTH);
                if (err != HTTP_OK)
                        return -1;
+               if (results.auth_avail & CURLAUTH_GSSNEGOTIATE)
+                       needs_100_continue = 1;
        }
  
        headers = curl_slist_append(headers, rpc->hdr_content_type);
        headers = curl_slist_append(headers, rpc->hdr_accept);
-       headers = curl_slist_append(headers, "Expect:");
+       headers = curl_slist_append(headers, needs_100_continue ?
+               "Expect: 100-continue" : "Expect:");
  
  retry:
        slot = get_active_slot();
        curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, rpc_in);
        curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
  
-       err = run_slot(slot);
+       err = run_slot(slot, NULL);
 -      if (err == HTTP_REAUTH && !large_request)
 +      if (err == HTTP_REAUTH && !large_request) {
 +              credential_fill(&http_auth);
                goto retry;
 +      }
        if (err != HTTP_OK)
                err = -1;
  
@@@ -613,7 -590,7 +624,7 @@@ static int rpc_service(struct rpc_stat
        rpc->out = client.out;
        strbuf_init(&rpc->result, 0);
  
 -      strbuf_addf(&buf, "%s%s", url, svc);
 +      strbuf_addf(&buf, "%s%s", url.buf, svc);
        rpc->service_url = strbuf_detach(&buf, NULL);
  
        strbuf_addf(&buf, "Content-Type: application/x-%s-request", svc);
@@@ -665,7 -642,7 +676,7 @@@ static int fetch_dumb(int nr_heads, str
        for (i = 0; i < nr_heads; i++)
                targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
  
 -      walker = get_http_walker(url);
 +      walker = get_http_walker(url.buf);
        walker->get_all = 1;
        walker->get_tree = 1;
        walker->get_history = 1;
@@@ -688,7 -665,7 +699,7 @@@ static int fetch_git(struct discovery *
        struct strbuf preamble = STRBUF_INIT;
        char *depth_arg = NULL;
        int argc = 0, i, err;
 -      const char *argv[15];
 +      const char *argv[16];
  
        argv[argc++] = "fetch-pack";
        argv[argc++] = "--stateless-rpc";
                argv[argc++] = "-v";
                argv[argc++] = "-v";
        }
 +      if (options.check_self_contained_and_connected)
 +              argv[argc++] = "--check-self-contained-and-connected";
        if (!options.progress)
                argv[argc++] = "--no-progress";
        if (options.depth) {
                depth_arg = strbuf_detach(&buf, NULL);
                argv[argc++] = depth_arg;
        }
 -      argv[argc++] = url;
 +      argv[argc++] = url.buf;
        argv[argc++] = NULL;
  
        for (i = 0; i < nr_heads; i++) {
@@@ -810,7 -785,7 +821,7 @@@ static int push_dav(int nr_spec, char *
                argv[argc++] = "--dry-run";
        if (options.verbosity > 1)
                argv[argc++] = "--verbose";
 -      argv[argc++] = url;
 +      argv[argc++] = url.buf;
        for (i = 0; i < nr_spec; i++)
                argv[argc++] = specs[i];
        argv[argc++] = NULL;
@@@ -826,7 -801,6 +837,7 @@@ static int push_git(struct discovery *h
        struct rpc_state rpc;
        int i, err;
        struct argv_array args;
 +      struct string_list_item *cas_option;
  
        argv_array_init(&args);
        argv_array_pushl(&args, "send-pack", "--stateless-rpc", "--helper-status",
        else if (options.verbosity > 1)
                argv_array_push(&args, "--verbose");
        argv_array_push(&args, options.progress ? "--progress" : "--no-progress");
 -      argv_array_push(&args, url);
 +      for_each_string_list_item(cas_option, &cas_options)
 +              argv_array_push(&args, cas_option->string);
 +      argv_array_push(&args, url.buf);
        for (i = 0; i < nr_spec; i++)
                argv_array_push(&args, specs[i]);
  
@@@ -924,12 -896,14 +935,12 @@@ int main(int argc, const char **argv
        remote = remote_get(argv[1]);
  
        if (argc > 2) {
 -              end_url_with_slash(&buf, argv[2]);
 +              end_url_with_slash(&url, argv[2]);
        } else {
 -              end_url_with_slash(&buf, remote->url[0]);
 +              end_url_with_slash(&url, remote->url[0]);
        }
  
 -      url = strbuf_detach(&buf, NULL);
 -
 -      http_init(remote, url, 0);
 +      http_init(remote, url.buf, 0);
  
        do {
                if (strbuf_getline(&buf, stdin, '\n') == EOF) {
                        printf("fetch\n");
                        printf("option\n");
                        printf("push\n");
 +                      printf("check-connectivity\n");
                        printf("\n");
                        fflush(stdout);
                } else {