Merge branch 'jk/http-auth-redirects'
authorJunio C Hamano <gitster@pobox.com>
Wed, 30 Oct 2013 19:09:53 +0000 (12:09 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 30 Oct 2013 19:09:53 +0000 (12:09 -0700)
Handle the case where http transport gets redirected during the
authorization request better.

* jk/http-auth-redirects:
http.c: Spell the null pointer as NULL
remote-curl: rewrite base url from info/refs redirects
remote-curl: store url as a strbuf
remote-curl: make refs_url a strbuf
http: update base URLs when we see redirects
http: provide effective url to callers
http: hoist credential request out of handle_curl_result
http: refactor options to http_get_*
http_request: factor out curlinfo_strbuf
http_get_file: style fixes

1  2 
http-push.c
http.c
remote-curl.c
t/lib-httpd.sh
t/lib-httpd/apache.conf
t/t5551-http-fetch.sh
diff --combined http-push.c
index 69200baf7651c2bcb99d6bc2068429e22865f900,09ebb8f48c80dac22bd5c7cab77e1cfcf19933ed..34cb70f90ea14a18848bd270463f5a34dc2c8fd3
@@@ -1330,7 -1330,8 +1330,7 @@@ static struct object_list **process_tre
                        break;
                }
  
 -      free(tree->buffer);
 -      tree->buffer = NULL;
 +      free_tree_buffer(tree);
        return p;
  }
  
@@@ -1542,7 -1543,7 +1542,7 @@@ static int remote_exists(const char *pa
  
        sprintf(url, "%s%s", repo->url, path);
  
-       switch (http_get_strbuf(url, NULL, NULL, 0)) {
+       switch (http_get_strbuf(url, NULL, NULL)) {
        case HTTP_OK:
                ret = 1;
                break;
@@@ -1566,7 -1567,7 +1566,7 @@@ static void fetch_symref(const char *pa
        url = xmalloc(strlen(repo->url) + strlen(path) + 1);
        sprintf(url, "%s%s", repo->url, path);
  
-       if (http_get_strbuf(url, NULL, &buffer, 0) != HTTP_OK)
+       if (http_get_strbuf(url, &buffer, NULL) != HTTP_OK)
                die("Couldn't get %s for remote symref\n%s", url,
                    curl_errorstr);
        free(url);
@@@ -1975,7 -1976,7 +1975,7 @@@ int main(int argc, char **argv
                pushing = 0;
                if (prepare_revision_walk(&revs))
                        die("revision walk setup failed");
 -              mark_edges_uninteresting(revs.commits, &revs, NULL);
 +              mark_edges_uninteresting(&revs, NULL);
                objects_to_send = get_delta(&revs, ref_lock);
                finish_all_active_slots();
  
diff --combined http.c
index 0ddb164a6f239789664c77f1dd97a31086fc71e3,3447945ee4f4e0e4b273d979ea1805b4c0f63b4c..bcf54aa35f96e8f0b87b6e2fd23408991a684b66
--- 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);
@@@ -861,7 -806,6 +861,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);
@@@ -1050,7 -1072,7 +1127,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;
@@@ -1103,6 -1125,7 +1180,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 remote-curl.c
index b5ebe0180069c1c1f96b769a37c5f3eb9459dfe9,ef1684b9df3ee5cff0d63ab5f7ed51e7f351a686..c9b891adbf134193f07681a631ae368671e04aa6
@@@ -6,24 -6,23 +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 */;
        }
@@@ -130,7 -113,8 +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;
@@@ -203,40 -187,47 +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;
@@@ -463,6 -455,8 +474,8 @@@ static int post_rpc(struct rpc_state *r
        if (large_request) {
                do {
                        err = probe_rpc(rpc);
+                       if (err == HTTP_REAUTH)
+                               credential_fill(&http_auth);
                } while (err == HTTP_REAUTH);
                if (err != HTTP_OK)
                        return -1;
@@@ -562,8 -556,10 +575,10 @@@ retry
        curl_easy_setopt(slot->curl, CURLOPT_FILE, rpc);
  
        err = run_slot(slot);
-       if (err == HTTP_REAUTH && !large_request)
+       if (err == HTTP_REAUTH && !large_request) {
+               credential_fill(&http_auth);
                goto retry;
+       }
        if (err != HTTP_OK)
                err = -1;
  
@@@ -598,7 -594,7 +613,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);
@@@ -650,7 -646,7 +665,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;
@@@ -673,7 -669,7 +688,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++) {
@@@ -795,7 -789,7 +810,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;
@@@ -811,7 -805,6 +826,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]);
  
@@@ -909,14 -900,12 +924,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 {
diff --combined t/lib-httpd.sh
index 54dbbfe5ceeb9bf91cdad3393c2fdfe95c06fd18,7059cc6c21518ec43df28770ffc18d5e0ac1a660..ad8f1ef71efcd7e449d2cad8edd3cf80c98ca188
@@@ -141,11 -141,10 +141,11 @@@ stop_httpd() 
                -f "$TEST_PATH/apache.conf" $HTTPD_PARA -k stop
  }
  
 -test_http_push_nonff() {
 +test_http_push_nonff () {
        REMOTE_REPO=$1
        LOCAL_REPO=$2
        BRANCH=$3
 +      EXPECT_CAS_RESULT=${4-failure}
  
        test_expect_success 'non-fast-forward push fails' '
                cd "$REMOTE_REPO" &&
        test_expect_success 'non-fast-forward push shows help message' '
                test_i18ngrep "Updates were rejected because" output
        '
 +
 +      test_expect_${EXPECT_CAS_RESULT} 'force with lease aka cas' '
 +              HEAD=$( cd "$REMOTE_REPO" && git rev-parse --verify HEAD ) &&
 +              test_when_finished '\''
 +                      (cd "$REMOTE_REPO" && git update-ref HEAD "$HEAD")
 +              '\'' &&
 +              (
 +                      cd "$LOCAL_REPO" &&
 +                      git push -v --force-with-lease=$BRANCH:$HEAD origin
 +              ) &&
 +              git rev-parse --verify "$BRANCH" >expect &&
 +              (
 +                      cd "$REMOTE_REPO" && git rev-parse --verify HEAD
 +              ) >actual &&
 +              test_cmp expect actual
 +      '
  }
  
  setup_askpass_helper() {
@@@ -204,7 -187,8 +204,8 @@@ set_askpass() 
  }
  
  expect_askpass() {
-       dest=$HTTPD_DEST
+       dest=$HTTPD_DEST${3+/$3}
        {
                case "$1" in
                none)
diff --combined t/lib-httpd/apache.conf
index 397c4804010c7566a4f969822c413e46fe817e53,4a261f13f533daaa3d687d2bb88481b560432378..3a03e8263db7e1c632bf0552ec94ae6834de4bf9
@@@ -22,9 -22,6 +22,9 @@@ ErrorLog error.lo
  <IfModule !mod_version.c>
        LoadModule version_module modules/mod_version.so
  </IfModule>
 +<IfModule !mod_headers.c>
 +      LoadModule headers_module modules/mod_headers.so
 +</IfModule>
  
  <IfVersion < 2.4>
  LockFile accept.lock
@@@ -90,11 -87,6 +90,11 @@@ Alias /auth/dumb/ www/auth/dumb
        SetEnv GIT_HTTP_EXPORT_ALL
        SetEnv GIT_NAMESPACE ns
  </LocationMatch>
 +<LocationMatch /smart_cookies/>
 +      SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
 +      SetEnv GIT_HTTP_EXPORT_ALL
 +      Header set Set-Cookie name=value
 +</LocationMatch>
  ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
  ScriptAlias /broken_smart/ broken-smart-http.sh/
  <Directory ${GIT_EXEC_PATH}>
  RewriteEngine on
  RewriteRule ^/smart-redir-perm/(.*)$ /smart/$1 [R=301]
  RewriteRule ^/smart-redir-temp/(.*)$ /smart/$1 [R=302]
+ RewriteRule ^/smart-redir-auth/(.*)$ /auth/smart/$1 [R=301]
+ RewriteRule ^/smart-redir-limited/(.*)/info/refs$ /smart/$1/info/refs [R=301]
  
  <IfDefine SSL>
  LoadModule ssl_module modules/mod_ssl.so
diff --combined t/t5551-http-fetch.sh
index 8196af19f68ac3772a81326828f7cdf83ae399a1,1b71bb515646e47181da33f83f97f04b6e0e8617..fb16833f7673ed33cd097b98e4ca91df1a89b135
@@@ -113,6 -113,10 +113,10 @@@ test_expect_success 'follow redirects (
        git clone $HTTPD_URL/smart-redir-temp/repo.git --quiet repo-t
  '
  
+ test_expect_success 'redirects re-root further requests' '
+       git clone $HTTPD_URL/smart-redir-limited/repo.git repo-redir-limited
+ '
  test_expect_success 'clone from password-protected repository' '
        echo two >expect &&
        set_askpass user@host &&
@@@ -146,6 -150,13 +150,13 @@@ test_expect_success 'no-op half-auth fe
        expect_askpass none
  '
  
+ test_expect_success 'redirects send auth to new location' '
+       set_askpass user@host &&
+       git -c credential.useHttpPath=true \
+         clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth &&
+       expect_askpass both user@host auth/smart/repo.git
+ '
  test_expect_success 'disable dumb http on server' '
        git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/repo.git" \
                config http.getanyfile false
@@@ -187,22 -198,6 +198,22 @@@ test_expect_success 'dumb clone via htt
        test_cmp expect actual
  '
  
 +cat >cookies.txt <<EOF
 +127.0.0.1     FALSE   /smart_cookies/ FALSE   0       othername       othervalue
 +EOF
 +cat >expect_cookies.txt <<EOF
 +
 +127.0.0.1     FALSE   /smart_cookies/ FALSE   0       othername       othervalue
 +127.0.0.1     FALSE   /smart_cookies/repo.git/info/   FALSE   0       name    value
 +EOF
 +test_expect_success 'cookies stored in http.cookiefile when http.savecookies set' '
 +      git config http.cookiefile cookies.txt &&
 +      git config http.savecookies true &&
 +      git ls-remote $HTTPD_URL/smart_cookies/repo.git master &&
 +      tail -3 cookies.txt > cookies_tail.txt
 +      test_cmp expect_cookies.txt cookies_tail.txt
 +'
 +
  test -n "$GIT_TEST_LONG" && test_set_prereq EXPENSIVE
  
  test_expect_success EXPENSIVE 'create 50,000 tags in the repo' '