Merge branch 'maint-1.7.11' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 12 Sep 2012 21:08:05 +0000 (14:08 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 12 Sep 2012 21:08:05 +0000 (14:08 -0700)
1  2 
builtin/apply.c
builtin/rev-parse.c
http.c
revision.c
t/lib-httpd.sh
t/lib-httpd/apache.conf
diff --combined builtin/apply.c
index 3bf71dc4522564463945673aaba0501a51141bb2,d2180b03744e093c51ffe61168391d4c4f814212..ca8695ad31073618b286bc4a086b0b74083aaad7
@@@ -16,9 -16,6 +16,9 @@@
  #include "dir.h"
  #include "diff.h"
  #include "parse-options.h"
 +#include "xdiff-interface.h"
 +#include "ll-merge.h"
 +#include "rerere.h"
  
  /*
   *  --check turns on checking that the working tree matches the
@@@ -49,7 -46,6 +49,7 @@@ static int apply_with_reject
  static int apply_verbosely;
  static int allow_overlap;
  static int no_add;
 +static int threeway;
  static const char *fake_ancestor;
  static int line_termination = '\n';
  static unsigned int p_context = UINT_MAX;
@@@ -196,17 -192,12 +196,17 @@@ struct patch 
        unsigned int is_copy:1;
        unsigned int is_rename:1;
        unsigned int recount:1;
 +      unsigned int conflicted_threeway:1;
 +      unsigned int direct_to_threeway:1;
        struct fragment *fragments;
        char *result;
        size_t resultsize;
        char old_sha1_prefix[41];
        char new_sha1_prefix[41];
        struct patch *next;
 +
 +      /* three-way fallback result */
 +      unsigned char threeway_stage[3][20];
  };
  
  static void free_fragment_list(struct fragment *list)
@@@ -379,8 -370,8 +379,8 @@@ static void prepare_image(struct image 
  static void clear_image(struct image *image)
  {
        free(image->buf);
 -      image->buf = NULL;
 -      image->len = 0;
 +      free(image->line_allocated);
 +      memset(image, 0, sizeof(*image));
  }
  
  /* fmt must contain _one_ %s and no other substitution */
@@@ -1095,15 -1086,23 +1095,23 @@@ static int gitdiff_unrecognized(const c
        return -1;
  }
  
- static const char *stop_at_slash(const char *line, int llen)
+ /*
+  * Skip p_value leading components from "line"; as we do not accept
+  * absolute paths, return NULL in that case.
+  */
+ static const char *skip_tree_prefix(const char *line, int llen)
  {
-       int nslash = p_value;
+       int nslash;
        int i;
  
+       if (!p_value)
+               return (llen && line[0] == '/') ? NULL : line;
+       nslash = p_value;
        for (i = 0; i < llen; i++) {
                int ch = line[i];
                if (ch == '/' && --nslash <= 0)
-                       return &line[i];
+                       return (i == 0) ? NULL : &line[i + 1];
        }
        return NULL;
  }
@@@ -1133,12 -1132,11 +1141,11 @@@ static char *git_header_name(const cha
                if (unquote_c_style(&first, line, &second))
                        goto free_and_fail1;
  
-               /* advance to the first slash */
-               cp = stop_at_slash(first.buf, first.len);
-               /* we do not accept absolute paths */
-               if (!cp || cp == first.buf)
+               /* strip the a/b prefix including trailing slash */
+               cp = skip_tree_prefix(first.buf, first.len);
+               if (!cp)
                        goto free_and_fail1;
-               strbuf_remove(&first, 0, cp + 1 - first.buf);
+               strbuf_remove(&first, 0, cp - first.buf);
  
                /*
                 * second points at one past closing dq of name.
                if (*second == '"') {
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail1;
-                       cp = stop_at_slash(sp.buf, sp.len);
-                       if (!cp || cp == sp.buf)
+                       cp = skip_tree_prefix(sp.buf, sp.len);
+                       if (!cp)
                                goto free_and_fail1;
                        /* They must match, otherwise ignore */
-                       if (strcmp(cp + 1, first.buf))
+                       if (strcmp(cp, first.buf))
                                goto free_and_fail1;
                        strbuf_release(&sp);
                        return strbuf_detach(&first, NULL);
                }
  
                /* unquoted second */
-               cp = stop_at_slash(second, line + llen - second);
-               if (!cp || cp == second)
+               cp = skip_tree_prefix(second, line + llen - second);
+               if (!cp)
                        goto free_and_fail1;
-               cp++;
-               if (line + llen - cp != first.len + 1 ||
+               if (line + llen - cp != first.len ||
                    memcmp(first.buf, cp, first.len))
                        goto free_and_fail1;
                return strbuf_detach(&first, NULL);
        }
  
        /* unquoted first name */
-       name = stop_at_slash(line, llen);
-       if (!name || name == line)
+       name = skip_tree_prefix(line, llen);
+       if (!name)
                return NULL;
-       name++;
  
        /*
         * since the first name is unquoted, a dq if exists must be
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail2;
  
-                       np = stop_at_slash(sp.buf, sp.len);
-                       if (!np || np == sp.buf)
+                       np = skip_tree_prefix(sp.buf, sp.len);
+                       if (!np)
                                goto free_and_fail2;
-                       np++;
  
                        len = sp.buf + sp.len - np;
                        if (len < second - name &&
                case '\n':
                        return NULL;
                case '\t': case ' ':
-                       second = stop_at_slash(name + len, line_len - len);
+                       /*
+                        * Is this the separator between the preimage
+                        * and the postimage pathname?  Again, we are
+                        * only interested in the case where there is
+                        * no rename, as this is only to set def_name
+                        * and a rename patch has the names elsewhere
+                        * in an unambiguous form.
+                        */
+                       if (!name[len + 1])
+                               return NULL; /* no postimage name */
+                       second = skip_tree_prefix(name + len + 1,
+                                                 line_len - (len + 1));
                        if (!second)
                                return NULL;
-                       second++;
-                       if (second[len] == '\n' && !strncmp(name, second, len)) {
+                       /*
+                        * Does len bytes starting at "name" and "second"
+                        * (that are separated by one HT or SP we just
+                        * found) exactly match?
+                        */
+                       if (second[len] == '\n' && !strncmp(name, second, len))
                                return xmemdupz(name, len);
-                       }
                }
        }
  }
@@@ -2945,17 -2954,20 +2963,17 @@@ static int apply_fragments(struct imag
        return 0;
  }
  
 -static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
 +static int read_blob_object(struct strbuf *buf, const unsigned char *sha1, unsigned mode)
  {
 -      if (!ce)
 -              return 0;
 -
 -      if (S_ISGITLINK(ce->ce_mode)) {
 +      if (S_ISGITLINK(mode)) {
                strbuf_grow(buf, 100);
 -              strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(ce->sha1));
 +              strbuf_addf(buf, "Subproject commit %s\n", sha1_to_hex(sha1));
        } else {
                enum object_type type;
                unsigned long sz;
                char *result;
  
 -              result = read_sha1_file(ce->sha1, &type, &sz);
 +              result = read_sha1_file(sha1, &type, &sz);
                if (!result)
                        return -1;
                /* XXX read_sha1_file NUL-terminates */
        return 0;
  }
  
 +static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
 +{
 +      if (!ce)
 +              return 0;
 +      return read_blob_object(buf, ce->sha1, ce->ce_mode);
 +}
 +
  static struct patch *in_fn_table(const char *name)
  {
        struct string_list_item *item;
   * item->util in the filename table records the status of the path.
   * Usually it points at a patch (whose result records the contents
   * of it after applying it), but it could be PATH_WAS_DELETED for a
 - * path that a previously applied patch has already removed.
 + * path that a previously applied patch has already removed, or
 + * PATH_TO_BE_DELETED for a path that a later patch would remove.
 + *
 + * The latter is needed to deal with a case where two paths A and B
 + * are swapped by first renaming A to B and then renaming B to A;
 + * moving A to B should not be prevented due to presense of B as we
 + * will remove it in a later patch.
   */
 - #define PATH_TO_BE_DELETED ((struct patch *) -2)
 +#define PATH_TO_BE_DELETED ((struct patch *) -2)
  #define PATH_WAS_DELETED ((struct patch *) -1)
  
  static int to_be_deleted(struct patch *patch)
@@@ -3049,324 -3048,127 +3067,324 @@@ static void prepare_fn_table(struct pat
        }
  }
  
 -static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
 +static int checkout_target(struct cache_entry *ce, struct stat *st)
 +{
 +      struct checkout costate;
 +
 +      memset(&costate, 0, sizeof(costate));
 +      costate.base_dir = "";
 +      costate.refresh_cache = 1;
 +      if (checkout_entry(ce, &costate, NULL) || lstat(ce->name, st))
 +              return error(_("cannot checkout %s"), ce->name);
 +      return 0;
 +}
 +
 +static struct patch *previous_patch(struct patch *patch, int *gone)
 +{
 +      struct patch *previous;
 +
 +      *gone = 0;
 +      if (patch->is_copy || patch->is_rename)
 +              return NULL; /* "git" patches do not depend on the order */
 +
 +      previous = in_fn_table(patch->old_name);
 +      if (!previous)
 +              return NULL;
 +
 +      if (to_be_deleted(previous))
 +              return NULL; /* the deletion hasn't happened yet */
 +
 +      if (was_deleted(previous))
 +              *gone = 1;
 +
 +      return previous;
 +}
 +
 +static int verify_index_match(struct cache_entry *ce, struct stat *st)
 +{
 +      if (S_ISGITLINK(ce->ce_mode)) {
 +              if (!S_ISDIR(st->st_mode))
 +                      return -1;
 +              return 0;
 +      }
 +      return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 +}
 +
 +#define SUBMODULE_PATCH_WITHOUT_INDEX 1
 +
 +static int load_patch_target(struct strbuf *buf,
 +                           struct cache_entry *ce,
 +                           struct stat *st,
 +                           const char *name,
 +                           unsigned expected_mode)
 +{
 +      if (cached) {
 +              if (read_file_or_gitlink(ce, buf))
 +                      return error(_("read of %s failed"), name);
 +      } else if (name) {
 +              if (S_ISGITLINK(expected_mode)) {
 +                      if (ce)
 +                              return read_file_or_gitlink(ce, buf);
 +                      else
 +                              return SUBMODULE_PATCH_WITHOUT_INDEX;
 +              } else {
 +                      if (read_old_data(st, name, buf))
 +                              return error(_("read of %s failed"), name);
 +              }
 +      }
 +      return 0;
 +}
 +
 +/*
 + * We are about to apply "patch"; populate the "image" with the
 + * current version we have, from the working tree or from the index,
 + * depending on the situation e.g. --cached/--index.  If we are
 + * applying a non-git patch that incrementally updates the tree,
 + * we read from the result of a previous diff.
 + */
 +static int load_preimage(struct image *image,
 +                       struct patch *patch, struct stat *st, struct cache_entry *ce)
  {
        struct strbuf buf = STRBUF_INIT;
 -      struct image image;
        size_t len;
        char *img;
 -      struct patch *tpatch;
 +      struct patch *previous;
 +      int status;
  
 -      if (!(patch->is_copy || patch->is_rename) &&
 -          (tpatch = in_fn_table(patch->old_name)) != NULL && !to_be_deleted(tpatch)) {
 -              if (was_deleted(tpatch)) {
 -                      return error(_("patch %s has been renamed/deleted"),
 -                              patch->old_name);
 -              }
 +      previous = previous_patch(patch, &status);
 +      if (status)
 +              return error(_("path %s has been renamed/deleted"),
 +                           patch->old_name);
 +      if (previous) {
                /* We have a patched copy in memory; use that. */
 -              strbuf_add(&buf, tpatch->result, tpatch->resultsize);
 -      } else if (cached) {
 -              if (read_file_or_gitlink(ce, &buf))
 +              strbuf_add(&buf, previous->result, previous->resultsize);
 +      } else {
 +              status = load_patch_target(&buf, ce, st,
 +                                         patch->old_name, patch->old_mode);
 +              if (status < 0)
 +                      return status;
 +              else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) {
 +                      /*
 +                       * There is no way to apply subproject
 +                       * patch without looking at the index.
 +                       * NEEDSWORK: shouldn't this be flagged
 +                       * as an error???
 +                       */
 +                      free_fragment_list(patch->fragments);
 +                      patch->fragments = NULL;
 +              } else if (status) {
                        return error(_("read of %s failed"), patch->old_name);
 -      } else if (patch->old_name) {
 -              if (S_ISGITLINK(patch->old_mode)) {
 -                      if (ce) {
 -                              read_file_or_gitlink(ce, &buf);
 -                      } else {
 -                              /*
 -                               * There is no way to apply subproject
 -                               * patch without looking at the index.
 -                               * NEEDSWORK: shouldn't this be flagged
 -                               * as an error???
 -                               */
 -                              free_fragment_list(patch->fragments);
 -                              patch->fragments = NULL;
 -                      }
 -              } else {
 -                      if (read_old_data(st, patch->old_name, &buf))
 -                              return error(_("read of %s failed"), patch->old_name);
                }
        }
  
        img = strbuf_detach(&buf, &len);
 -      prepare_image(&image, img, len, !patch->is_binary);
 +      prepare_image(image, img, len, !patch->is_binary);
 +      return 0;
 +}
  
 -      if (apply_fragments(&image, patch) < 0)
 -              return -1; /* note with --reject this succeeds. */
 -      patch->result = image.buf;
 -      patch->resultsize = image.len;
 -      add_to_fn_table(patch);
 -      free(image.line_allocated);
 +static int three_way_merge(struct image *image,
 +                         char *path,
 +                         const unsigned char *base,
 +                         const unsigned char *ours,
 +                         const unsigned char *theirs)
 +{
 +      mmfile_t base_file, our_file, their_file;
 +      mmbuffer_t result = { NULL };
 +      int status;
  
 -      if (0 < patch->is_delete && patch->resultsize)
 -              return error(_("removal patch leaves file contents"));
 +      read_mmblob(&base_file, base);
 +      read_mmblob(&our_file, ours);
 +      read_mmblob(&their_file, theirs);
 +      status = ll_merge(&result, path,
 +                        &base_file, "base",
 +                        &our_file, "ours",
 +                        &their_file, "theirs", NULL);
 +      free(base_file.ptr);
 +      free(our_file.ptr);
 +      free(their_file.ptr);
 +      if (status < 0 || !result.ptr) {
 +              free(result.ptr);
 +              return -1;
 +      }
 +      clear_image(image);
 +      image->buf = result.ptr;
 +      image->len = result.size;
  
 +      return status;
 +}
 +
 +/*
 + * When directly falling back to add/add three-way merge, we read from
 + * the current contents of the new_name.  In no cases other than that
 + * this function will be called.
 + */
 +static int load_current(struct image *image, struct patch *patch)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      int status, pos;
 +      size_t len;
 +      char *img;
 +      struct stat st;
 +      struct cache_entry *ce;
 +      char *name = patch->new_name;
 +      unsigned mode = patch->new_mode;
 +
 +      if (!patch->is_new)
 +              die("BUG: patch to %s is not a creation", patch->old_name);
 +
 +      pos = cache_name_pos(name, strlen(name));
 +      if (pos < 0)
 +              return error(_("%s: does not exist in index"), name);
 +      ce = active_cache[pos];
 +      if (lstat(name, &st)) {
 +              if (errno != ENOENT)
 +                      return error(_("%s: %s"), name, strerror(errno));
 +              if (checkout_target(ce, &st))
 +                      return -1;
 +      }
 +      if (verify_index_match(ce, &st))
 +              return error(_("%s: does not match index"), name);
 +
 +      status = load_patch_target(&buf, ce, &st, name, mode);
 +      if (status < 0)
 +              return status;
 +      else if (status)
 +              return -1;
 +      img = strbuf_detach(&buf, &len);
 +      prepare_image(image, img, len, !patch->is_binary);
        return 0;
  }
  
 -static int check_to_create_blob(const char *new_name, int ok_if_exists)
 +static int try_threeway(struct image *image, struct patch *patch,
 +                      struct stat *st, struct cache_entry *ce)
  {
 -      struct stat nst;
 -      if (!lstat(new_name, &nst)) {
 -              if (S_ISDIR(nst.st_mode) || ok_if_exists)
 -                      return 0;
 -              /*
 -               * A leading component of new_name might be a symlink
 -               * that is going to be removed with this patch, but
 -               * still pointing at somewhere that has the path.
 -               * In such a case, path "new_name" does not exist as
 -               * far as git is concerned.
 -               */
 -              if (has_symlink_leading_path(new_name, strlen(new_name)))
 -                      return 0;
 +      unsigned char pre_sha1[20], post_sha1[20], our_sha1[20];
 +      struct strbuf buf = STRBUF_INIT;
 +      size_t len;
 +      int status;
 +      char *img;
 +      struct image tmp_image;
 +
 +      /* No point falling back to 3-way merge in these cases */
 +      if (patch->is_delete ||
 +          S_ISGITLINK(patch->old_mode) || S_ISGITLINK(patch->new_mode))
 +              return -1;
  
 -              return error(_("%s: already exists in working directory"), new_name);
 +      /* Preimage the patch was prepared for */
 +      if (patch->is_new)
 +              write_sha1_file("", 0, blob_type, pre_sha1);
 +      else if (get_sha1(patch->old_sha1_prefix, pre_sha1) ||
 +               read_blob_object(&buf, pre_sha1, patch->old_mode))
 +              return error("repository lacks the necessary blob to fall back on 3-way merge.");
 +
 +      fprintf(stderr, "Falling back to three-way merge...\n");
 +
 +      img = strbuf_detach(&buf, &len);
 +      prepare_image(&tmp_image, img, len, 1);
 +      /* Apply the patch to get the post image */
 +      if (apply_fragments(&tmp_image, patch) < 0) {
 +              clear_image(&tmp_image);
 +              return -1;
 +      }
 +      /* post_sha1[] is theirs */
 +      write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, post_sha1);
 +      clear_image(&tmp_image);
 +
 +      /* our_sha1[] is ours */
 +      if (patch->is_new) {
 +              if (load_current(&tmp_image, patch))
 +                      return error("cannot read the current contents of '%s'",
 +                                   patch->new_name);
 +      } else {
 +              if (load_preimage(&tmp_image, patch, st, ce))
 +                      return error("cannot read the current contents of '%s'",
 +                                   patch->old_name);
 +      }
 +      write_sha1_file(tmp_image.buf, tmp_image.len, blob_type, our_sha1);
 +      clear_image(&tmp_image);
 +
 +      /* in-core three-way merge between post and our using pre as base */
 +      status = three_way_merge(image, patch->new_name,
 +                               pre_sha1, our_sha1, post_sha1);
 +      if (status < 0) {
 +              fprintf(stderr, "Failed to fall back on three-way merge...\n");
 +              return status;
 +      }
 +
 +      if (status) {
 +              patch->conflicted_threeway = 1;
 +              if (patch->is_new)
 +                      hashclr(patch->threeway_stage[0]);
 +              else
 +                      hashcpy(patch->threeway_stage[0], pre_sha1);
 +              hashcpy(patch->threeway_stage[1], our_sha1);
 +              hashcpy(patch->threeway_stage[2], post_sha1);
 +              fprintf(stderr, "Applied patch to '%s' with conflicts.\n", patch->new_name);
 +      } else {
 +              fprintf(stderr, "Applied patch to '%s' cleanly.\n", patch->new_name);
        }
 -      else if ((errno != ENOENT) && (errno != ENOTDIR))
 -              return error("%s: %s", new_name, strerror(errno));
        return 0;
  }
  
 -static int verify_index_match(struct cache_entry *ce, struct stat *st)
 +static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
  {
 -      if (S_ISGITLINK(ce->ce_mode)) {
 -              if (!S_ISDIR(st->st_mode))
 +      struct image image;
 +
 +      if (load_preimage(&image, patch, st, ce) < 0)
 +              return -1;
 +
 +      if (patch->direct_to_threeway ||
 +          apply_fragments(&image, patch) < 0) {
 +              /* Note: with --reject, apply_fragments() returns 0 */
 +              if (!threeway || try_threeway(&image, patch, st, ce) < 0)
                        return -1;
 -              return 0;
        }
 -      return ce_match_stat(ce, st, CE_MATCH_IGNORE_VALID|CE_MATCH_IGNORE_SKIP_WORKTREE);
 +      patch->result = image.buf;
 +      patch->resultsize = image.len;
 +      add_to_fn_table(patch);
 +      free(image.line_allocated);
 +
 +      if (0 < patch->is_delete && patch->resultsize)
 +              return error(_("removal patch leaves file contents"));
 +
 +      return 0;
  }
  
 +/*
 + * If "patch" that we are looking at modifies or deletes what we have,
 + * we would want it not to lose any local modification we have, either
 + * in the working tree or in the index.
 + *
 + * This also decides if a non-git patch is a creation patch or a
 + * modification to an existing empty file.  We do not check the state
 + * of the current tree for a creation patch in this function; the caller
 + * check_patch() separately makes sure (and errors out otherwise) that
 + * the path the patch creates does not exist in the current tree.
 + */
  static int check_preimage(struct patch *patch, struct cache_entry **ce, struct stat *st)
  {
        const char *old_name = patch->old_name;
 -      struct patch *tpatch = NULL;
 -      int stat_ret = 0;
 +      struct patch *previous = NULL;
 +      int stat_ret = 0, status;
        unsigned st_mode = 0;
  
 -      /*
 -       * Make sure that we do not have local modifications from the
 -       * index when we are looking at the index.  Also make sure
 -       * we have the preimage file to be patched in the work tree,
 -       * unless --cached, which tells git to apply only in the index.
 -       */
        if (!old_name)
                return 0;
  
        assert(patch->is_new <= 0);
 +      previous = previous_patch(patch, &status);
  
 -      if (!(patch->is_copy || patch->is_rename) &&
 -          (tpatch = in_fn_table(old_name)) != NULL && !to_be_deleted(tpatch)) {
 -              if (was_deleted(tpatch))
 -                      return error(_("%s: has been deleted/renamed"), old_name);
 -              st_mode = tpatch->new_mode;
 +      if (status)
 +              return error(_("path %s has been renamed/deleted"), old_name);
 +      if (previous) {
 +              st_mode = previous->new_mode;
        } else if (!cached) {
                stat_ret = lstat(old_name, st);
                if (stat_ret && errno != ENOENT)
                        return error(_("%s: %s"), old_name, strerror(errno));
        }
  
 -      if (to_be_deleted(tpatch))
 -              tpatch = NULL;
 -
 -      if (check_index && !tpatch) {
 +      if (check_index && !previous) {
                int pos = cache_name_pos(old_name, strlen(old_name));
                if (pos < 0) {
                        if (patch->is_new < 0)
                }
                *ce = active_cache[pos];
                if (stat_ret < 0) {
 -                      struct checkout costate;
 -                      /* checkout */
 -                      memset(&costate, 0, sizeof(costate));
 -                      costate.base_dir = "";
 -                      costate.refresh_cache = 1;
 -                      if (checkout_entry(*ce, &costate, NULL) ||
 -                          lstat(old_name, st))
 +                      if (checkout_target(*ce, st))
                                return -1;
                }
                if (!cached && verify_index_match(*ce, st))
                return error(_("%s: %s"), old_name, strerror(errno));
        }
  
 -      if (!cached && !tpatch)
 +      if (!cached && !previous)
                st_mode = ce_mode_from_stat(*ce, st->st_mode);
  
        if (patch->is_new < 0)
        return 0;
  }
  
 +
 +#define EXISTS_IN_INDEX 1
 +#define EXISTS_IN_WORKTREE 2
 +
 +static int check_to_create(const char *new_name, int ok_if_exists)
 +{
 +      struct stat nst;
 +
 +      if (check_index &&
 +          cache_name_pos(new_name, strlen(new_name)) >= 0 &&
 +          !ok_if_exists)
 +              return EXISTS_IN_INDEX;
 +      if (cached)
 +              return 0;
 +
 +      if (!lstat(new_name, &nst)) {
 +              if (S_ISDIR(nst.st_mode) || ok_if_exists)
 +                      return 0;
 +              /*
 +               * A leading component of new_name might be a symlink
 +               * that is going to be removed with this patch, but
 +               * still pointing at somewhere that has the path.
 +               * In such a case, path "new_name" does not exist as
 +               * far as git is concerned.
 +               */
 +              if (has_symlink_leading_path(new_name, strlen(new_name)))
 +                      return 0;
 +
 +              return EXISTS_IN_WORKTREE;
 +      } else if ((errno != ENOENT) && (errno != ENOTDIR)) {
 +              return error("%s: %s", new_name, strerror(errno));
 +      }
 +      return 0;
 +}
 +
  /*
   * Check and apply the patch in-core; leave the result in patch->result
   * for the caller to write it out to the final destination.
@@@ -3469,45 -3242,31 +3487,45 @@@ static int check_patch(struct patch *pa
                return status;
        old_name = patch->old_name;
  
 +      /*
 +       * A type-change diff is always split into a patch to delete
 +       * old, immediately followed by a patch to create new (see
 +       * diff.c::run_diff()); in such a case it is Ok that the entry
 +       * to be deleted by the previous patch is still in the working
 +       * tree and in the index.
 +       *
 +       * A patch to swap-rename between A and B would first rename A
 +       * to B and then rename B to A.  While applying the first one,
 +       * the presense of B should not stop A from getting renamed to
 +       * B; ask to_be_deleted() about the later rename.  Removal of
 +       * B and rename from A to B is handled the same way by asking
 +       * was_deleted().
 +       */
        if ((tpatch = in_fn_table(new_name)) &&
 -                      (was_deleted(tpatch) || to_be_deleted(tpatch)))
 -              /*
 -               * A type-change diff is always split into a patch to
 -               * delete old, immediately followed by a patch to
 -               * create new (see diff.c::run_diff()); in such a case
 -               * it is Ok that the entry to be deleted by the
 -               * previous patch is still in the working tree and in
 -               * the index.
 -               */
 +          (was_deleted(tpatch) || to_be_deleted(tpatch)))
                ok_if_exists = 1;
        else
                ok_if_exists = 0;
  
        if (new_name &&
            ((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
 -              if (check_index &&
 -                  cache_name_pos(new_name, strlen(new_name)) >= 0 &&
 -                  !ok_if_exists)
 +              int err = check_to_create(new_name, ok_if_exists);
 +
 +              if (err && threeway) {
 +                      patch->direct_to_threeway = 1;
 +              } else switch (err) {
 +              case 0:
 +                      break; /* happy */
 +              case EXISTS_IN_INDEX:
                        return error(_("%s: already exists in index"), new_name);
 -              if (!cached) {
 -                      int err = check_to_create_blob(new_name, ok_if_exists);
 -                      if (err)
 -                              return err;
 +                      break;
 +              case EXISTS_IN_WORKTREE:
 +                      return error(_("%s: already exists in working directory"),
 +                                   new_name);
 +              default:
 +                      return err;
                }
 +
                if (!patch->new_mode) {
                        if (0 < patch->is_new)
                                patch->new_mode = S_IFREG | 0644;
@@@ -3588,7 -3347,7 +3606,7 @@@ static void build_fake_ancestor(struct 
                name = patch->old_name ? patch->old_name : patch->new_name;
                if (0 < patch->is_new)
                        continue;
 -              else if (get_sha1(patch->old_sha1_prefix, sha1))
 +              else if (get_sha1_blob(patch->old_sha1_prefix, sha1))
                        /* git diff has no index line for mode/type changes */
                        if (!patch->lines_added && !patch->lines_deleted) {
                                if (get_current_sha1(patch->old_name, sha1))
@@@ -3768,8 -3527,7 +3786,8 @@@ static void add_index_file(const char *
        ce = xcalloc(1, ce_size);
        memcpy(ce->name, path, namelen);
        ce->ce_mode = create_ce_mode(mode);
 -      ce->ce_flags = namelen;
 +      ce->ce_flags = create_ce_flags(0);
 +      ce->ce_namelen = namelen;
        if (S_ISGITLINK(mode)) {
                const char *s = buf;
  
@@@ -3871,33 -3629,6 +3889,33 @@@ static void create_one_file(char *path
        die_errno(_("unable to write file '%s' mode %o"), path, mode);
  }
  
 +static void add_conflicted_stages_file(struct patch *patch)
 +{
 +      int stage, namelen;
 +      unsigned ce_size, mode;
 +      struct cache_entry *ce;
 +
 +      if (!update_index)
 +              return;
 +      namelen = strlen(patch->new_name);
 +      ce_size = cache_entry_size(namelen);
 +      mode = patch->new_mode ? patch->new_mode : (S_IFREG | 0644);
 +
 +      remove_file_from_cache(patch->new_name);
 +      for (stage = 1; stage < 4; stage++) {
 +              if (is_null_sha1(patch->threeway_stage[stage - 1]))
 +                      continue;
 +              ce = xcalloc(1, ce_size);
 +              memcpy(ce->name, patch->new_name, namelen);
 +              ce->ce_mode = create_ce_mode(mode);
 +              ce->ce_flags = create_ce_flags(stage);
 +              ce->ce_namelen = namelen;
 +              hashcpy(ce->sha1, patch->threeway_stage[stage - 1]);
 +              if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD) < 0)
 +                      die(_("unable to add cache entry for %s"), patch->new_name);
 +      }
 +}
 +
  static void create_file(struct patch *patch)
  {
        char *path = patch->new_name;
        if (!mode)
                mode = S_IFREG | 0644;
        create_one_file(path, mode, buf, size);
 -      add_index_file(path, mode, buf, size);
 +
 +      if (patch->conflicted_threeway)
 +              add_conflicted_stages_file(patch);
 +      else
 +              add_index_file(path, mode, buf, size);
  }
  
  /* phase zero is to remove, phase one is to create */
@@@ -4014,7 -3741,6 +4032,7 @@@ static int write_out_results(struct pat
        int phase;
        int errs = 0;
        struct patch *l;
 +      struct string_list cpath = STRING_LIST_INIT_DUP;
  
        for (phase = 0; phase < 2; phase++) {
                l = list;
                                errs = 1;
                        else {
                                write_out_one_result(l, phase);
 -                              if (phase == 1 && write_out_one_reject(l))
 -                                      errs = 1;
 +                              if (phase == 1) {
 +                                      if (write_out_one_reject(l))
 +                                              errs = 1;
 +                                      if (l->conflicted_threeway) {
 +                                              string_list_append(&cpath, l->new_name);
 +                                              errs = 1;
 +                                      }
 +                              }
                        }
                        l = l->next;
                }
        }
 +
 +      if (cpath.nr) {
 +              struct string_list_item *item;
 +
 +              sort_string_list(&cpath);
 +              for_each_string_list_item(item, &cpath)
 +                      fprintf(stderr, "U %s\n", item->string);
 +              string_list_clear(&cpath, 0);
 +
 +              rerere(0);
 +      }
 +
        return errs;
  }
  
@@@ -4169,12 -3877,8 +4187,12 @@@ static int apply_patch(int fd, const ch
            !apply_with_reject)
                exit(1);
  
 -      if (apply && write_out_results(list))
 -              exit(1);
 +      if (apply && write_out_results(list)) {
 +              if (apply_with_reject)
 +                      exit(1);
 +              /* with --3way, we still need to write the index out */
 +              return 1;
 +      }
  
        if (fake_ancestor)
                build_fake_ancestor(list, fake_ancestor);
@@@ -4307,8 -4011,6 +4325,8 @@@ int cmd_apply(int argc, const char **ar
                        N_("apply a patch without touching the working tree")),
                OPT_BOOLEAN(0, "apply", &force_apply,
                        N_("also apply the patch (use with --stat/--summary/--check)")),
 +              OPT_BOOL('3', "3way", &threeway,
 +                       N_( "attempt three-way merge if a patch does not apply")),
                OPT_FILENAME(0, "build-fake-ancestor", &fake_ancestor,
                        N_("build a temporary index based on embedded index information")),
                { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
        argc = parse_options(argc, argv, prefix, builtin_apply_options,
                        apply_usage, 0);
  
 +      if (apply_with_reject && threeway)
 +              die("--reject and --3way cannot be used together.");
 +      if (cached && threeway)
 +              die("--cached and --3way cannot be used together.");
 +      if (threeway) {
 +              if (is_not_gitdir)
 +                      die(_("--3way outside a repository"));
 +              check_index = 1;
 +      }
        if (apply_with_reject)
                apply = apply_verbosely = 1;
        if (!force_apply && (diffstat || numstat || summary || check || fake_ancestor))
diff --combined builtin/rev-parse.c
index 32788a9f86427237d0f4838362c1e3bc722fa258,47b4e7adb956517c22edc580892d63a99d9f219a..25e225f06717bfc2fd1f988269ff503a907d9302
@@@ -195,12 -195,6 +195,12 @@@ static int anti_reference(const char *r
        return 0;
  }
  
 +static int show_abbrev(const unsigned char *sha1, void *cb_data)
 +{
 +      show_rev(NORMAL, sha1, NULL);
 +      return 0;
 +}
 +
  static void show_datestring(const char *flag, const char *datestr)
  {
        static char buffer[100];
@@@ -230,6 -224,7 +230,7 @@@ static int try_difference(const char *a
        const char *next;
        const char *this;
        int symmetric;
+       static const char head_by_default[] = "HEAD";
  
        if (!(dotdot = strstr(arg, "..")))
                return 0;
        next += symmetric;
  
        if (!*next)
-               next = "HEAD";
+               next = head_by_default;
        if (dotdot == arg)
-               this = "HEAD";
+               this = head_by_default;
+       if (this == head_by_default && next == head_by_default &&
+           !symmetric) {
+               /*
+                * Just ".."?  That is not a range but the
+                * pathspec for the parent directory.
+                */
+               *dotdot = '.';
+               return 0;
+       }
 -      if (!get_sha1(this, sha1) && !get_sha1(next, end)) {
 +      if (!get_sha1_committish(this, sha1) && !get_sha1_committish(next, end)) {
                show_rev(NORMAL, end, next);
                show_rev(symmetric ? NORMAL : REVERSED, sha1, this);
                if (symmetric) {
@@@ -284,7 -290,7 +296,7 @@@ static int try_parent_shorthands(const 
                return 0;
  
        *dotdot = 0;
 -      if (get_sha1(arg, sha1))
 +      if (get_sha1_committish(arg, sha1))
                return 0;
  
        if (!parents_only)
@@@ -595,10 -601,6 +607,10 @@@ int cmd_rev_parse(int argc, const char 
                                for_each_ref(show_reference, NULL);
                                continue;
                        }
 +                      if (!prefixcmp(arg, "--disambiguate=")) {
 +                              for_each_abbrev(arg + 15, show_abbrev, NULL);
 +                              continue;
 +                      }
                        if (!strcmp(arg, "--bisect")) {
                                for_each_ref_in("refs/bisect/bad", show_reference, NULL);
                                for_each_ref_in("refs/bisect/good", anti_reference, NULL);
diff --combined http.c
index 18bc6bf7bf17bab9037fade3af2151da6b298b2d,0a27b146adb15ad97407ed2c196878058f0ccec8..9bac1d89fdb639f6991bfe9f683ccf7bd5ada4cb
--- 1/http.c
--- 2/http.c
+++ b/http.c
@@@ -4,7 -4,6 +4,7 @@@
  #include "run-command.h"
  #include "url.h"
  #include "credential.h"
 +#include "version.h"
  
  int active_requests;
  int http_is_verbose;
@@@ -300,7 -299,7 +300,7 @@@ static CURL *get_curl_handle(void
                curl_easy_setopt(result, CURLOPT_VERBOSE, 1);
  
        curl_easy_setopt(result, CURLOPT_USERAGENT,
 -              user_agent ? user_agent : GIT_HTTP_USER_AGENT);
 +              user_agent ? user_agent : git_user_agent());
  
        if (curl_ftp_no_epsv)
                curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
@@@ -745,6 -744,33 +745,35 @@@ char *get_remote_object_url(const char 
        return strbuf_detach(&buf, NULL);
  }
  
+ int handle_curl_result(struct active_request_slot *slot)
+ {
+       struct slot_results *results = slot->results;
+       if (results->curl_result == CURLE_OK) {
+               credential_approve(&http_auth);
+               return HTTP_OK;
+       } else if (missing_target(results))
+               return HTTP_MISSING_TARGET;
+       else if (results->http_code == 401) {
+               if (http_auth.username && http_auth.password) {
+                       credential_reject(&http_auth);
+                       return HTTP_NOAUTH;
+               } else {
+                       credential_fill(&http_auth);
+                       init_curl_http_auth(slot->curl);
+                       return HTTP_REAUTH;
+               }
+       } else {
++#if LIBCURL_VERSION_NUM >= 0x070c00
+               if (!curl_errorstr[0])
+                       strlcpy(curl_errorstr,
+                               curl_easy_strerror(results->curl_result),
+                               sizeof(curl_errorstr));
++#endif
+               return HTTP_ERROR;
+       }
+ }
  /* http_request() targets */
  #define HTTP_REQUEST_STRBUF   0
  #define HTTP_REQUEST_FILE     1
@@@ -792,28 -818,7 +821,7 @@@ static int http_request(const char *url
  
        if (start_active_slot(slot)) {
                run_active_slot(slot);
-               if (results.curl_result == CURLE_OK)
-                       ret = HTTP_OK;
-               else if (missing_target(&results))
-                       ret = HTTP_MISSING_TARGET;
-               else if (results.http_code == 401) {
-                       if (http_auth.username && http_auth.password) {
-                               credential_reject(&http_auth);
-                               ret = HTTP_NOAUTH;
-                       } else {
-                               credential_fill(&http_auth);
-                               init_curl_http_auth(slot->curl);
-                               ret = HTTP_REAUTH;
-                       }
-               } else {
- #if LIBCURL_VERSION_NUM >= 0x070c00
-                       if (!curl_errorstr[0])
-                               strlcpy(curl_errorstr,
-                                       curl_easy_strerror(results.curl_result),
-                                       sizeof(curl_errorstr));
- #endif
-                       ret = HTTP_ERROR;
-               }
+               ret = handle_curl_result(slot);
        } else {
                error("Unable to start HTTP request for %s", url);
                ret = HTTP_START_FAILED;
        curl_slist_free_all(headers);
        strbuf_release(&buf);
  
-       if (ret == HTTP_OK)
-               credential_approve(&http_auth);
        return ret;
  }
  
diff --combined revision.c
index 442a9452334704d90e4457fdeb11014fb886c58a,72104962d36b9672fd9dfc6ec850c99ccfc3803c..cbcae1086b346e2b956db7ef9b2729385eaa2ec9
@@@ -1002,7 -1002,7 +1002,7 @@@ static int add_parents_only(struct rev_
                flags ^= UNINTERESTING;
                arg++;
        }
 -      if (get_sha1(arg, sha1))
 +      if (get_sha1_committish(arg, sha1))
                return 0;
        while (1) {
                it = get_reference(revs, arg, sha1, 0);
@@@ -1116,16 -1116,16 +1116,16 @@@ static void prepare_show_merge(struct r
        revs->limited = 1;
  }
  
 -int handle_revision_arg(const char *arg_, struct rev_info *revs,
 -                      int flags,
 -                      int cant_be_filename)
 +int handle_revision_arg(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)
  {
 -      unsigned mode;
 +      struct object_context oc;
        char *dotdot;
        struct object *object;
        unsigned char sha1[20];
        int local_flags;
        const char *arg = arg_;
 +      int cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;
 +      unsigned get_sha1_flags = 0;
  
        dotdot = strstr(arg, "..");
        if (dotdot) {
                const char *this = arg;
                int symmetric = *next == '.';
                unsigned int flags_exclude = flags ^ UNINTERESTING;
+               static const char head_by_default[] = "HEAD";
                unsigned int a_flags;
  
                *dotdot = 0;
                next += symmetric;
  
                if (!*next)
-                       next = "HEAD";
+                       next = head_by_default;
                if (dotdot == arg)
-                       this = "HEAD";
+                       this = head_by_default;
+               if (this == head_by_default && next == head_by_default &&
+                   !symmetric) {
+                       /*
+                        * Just ".."?  That is not a range but the
+                        * pathspec for the parent directory.
+                        */
+                       if (!cant_be_filename) {
+                               *dotdot = '.';
+                               return -1;
+                       }
+               }
 -              if (!get_sha1(this, from_sha1) &&
 -                  !get_sha1(next, sha1)) {
 +              if (!get_sha1_committish(this, from_sha1) &&
 +                  !get_sha1_committish(next, sha1)) {
                        struct commit *a, *b;
                        struct commit_list *exclude;
  
                local_flags = UNINTERESTING;
                arg++;
        }
 -      if (get_sha1_with_mode(arg, sha1, &mode))
 +
 +      if (revarg_opt & REVARG_COMMITTISH)
 +              get_sha1_flags = GET_SHA1_COMMITTISH;
 +
 +      if (get_sha1_with_context(arg, get_sha1_flags, sha1, &oc))
                return revs->ignore_missing ? 0 : -1;
        if (!cant_be_filename)
                verify_non_filename(revs->prefix, arg);
        object = get_reference(revs, arg, sha1, flags ^ local_flags);
        add_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);
 -      add_pending_object_with_mode(revs, object, arg, mode);
 +      add_pending_object_with_mode(revs, object, arg, oc.mode);
        return 0;
  }
  
@@@ -1263,7 -1271,7 +1275,7 @@@ static void read_revisions_from_stdin(s
                        }
                        die("options not supported in --stdin mode");
                }
 -              if (handle_revision_arg(sb.buf, revs, 0, 1))
 +              if (handle_revision_arg(sb.buf, revs, 0, REVARG_CANNOT_BE_FILENAME))
                        die("bad revision '%s'", sb.buf);
        }
        if (seen_dashdash)
@@@ -1714,7 -1722,7 +1726,7 @@@ static int handle_revision_pseudo_opt(c
   */
  int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct setup_revision_opt *opt)
  {
 -      int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0;
 +      int i, flags, left, seen_dashdash, read_from_stdin, got_rev_arg = 0, revarg_opt;
        struct cmdline_pathspec prune_data;
        const char *submodule = NULL;
  
  
        /* Second, deal with arguments and options */
        flags = 0;
 +      revarg_opt = opt ? opt->revarg_opt : 0;
 +      if (seen_dashdash)
 +              revarg_opt |= REVARG_CANNOT_BE_FILENAME;
        read_from_stdin = 0;
        for (left = i = 1; i < argc; i++) {
                const char *arg = argv[i];
                        continue;
                }
  
 -              if (handle_revision_arg(arg, revs, flags, seen_dashdash)) {
 +
 +              if (handle_revision_arg(arg, revs, flags, revarg_opt)) {
                        int j;
                        if (seen_dashdash || *arg == '^')
                                die("bad revision '%s'", arg);
        if (revs->def && !revs->pending.nr && !got_rev_arg) {
                unsigned char sha1[20];
                struct object *object;
 -              unsigned mode;
 -              if (get_sha1_with_mode(revs->def, sha1, &mode))
 +              struct object_context oc;
 +              if (get_sha1_with_context(revs->def, 0, sha1, &oc))
                        die("bad default revision '%s'", revs->def);
                object = get_reference(revs, revs->def, sha1, 0);
 -              add_pending_object_with_mode(revs, object, revs->def, mode);
 +              add_pending_object_with_mode(revs, object, revs->def, oc.mode);
        }
  
        /* Did the user ask for any diff output? Run the diff! */
@@@ -2370,28 -2374,29 +2382,28 @@@ static struct commit *get_revision_inte
        }
  
        /*
 -       * Now pick up what they want to give us
 +       * If our max_count counter has reached zero, then we are done. We
 +       * don't simply return NULL because we still might need to show
 +       * boundary commits. But we want to avoid calling get_revision_1, which
 +       * might do a considerable amount of work finding the next commit only
 +       * for us to throw it away.
 +       *
 +       * If it is non-zero, then either we don't have a max_count at all
 +       * (-1), or it is still counting, in which case we decrement.
         */
 -      c = get_revision_1(revs);
 -      if (c) {
 -              while (0 < revs->skip_count) {
 -                      revs->skip_count--;
 -                      c = get_revision_1(revs);
 -                      if (!c)
 -                              break;
 +      if (revs->max_count) {
 +              c = get_revision_1(revs);
 +              if (c) {
 +                      while (0 < revs->skip_count) {
 +                              revs->skip_count--;
 +                              c = get_revision_1(revs);
 +                              if (!c)
 +                                      break;
 +                      }
                }
 -      }
  
 -      /*
 -       * Check the max_count.
 -       */
 -      switch (revs->max_count) {
 -      case -1:
 -              break;
 -      case 0:
 -              c = NULL;
 -              break;
 -      default:
 -              revs->max_count--;
 +              if (revs->max_count > 0)
 +                      revs->max_count--;
        }
  
        if (c)
diff --combined t/lib-httpd.sh
index d773542680d9f3c3d77e52871877560a9e545a26,0f31ef9be487759829c141031f8925167b135537..02f442bfadbb1239785a456cddc50223da4f3834
@@@ -43,10 -43,6 +43,10 @@@ TEST_PATH="$TEST_DIRECTORY"/lib-http
  HTTPD_ROOT_PATH="$PWD"/httpd
  HTTPD_DOCUMENT_ROOT_PATH=$HTTPD_ROOT_PATH/www
  
 +# hack to suppress apache PassEnv warnings
 +GIT_VALGRIND=$GIT_VALGRIND; export GIT_VALGRIND
 +GIT_VALGRIND_OPTIONS=$GIT_VALGRIND_OPTIONS; export GIT_VALGRIND_OPTIONS
 +
  if ! test -x "$LIB_HTTPD_PATH"
  then
        skip_all="skipping test, no web server found at '$LIB_HTTPD_PATH'"
@@@ -167,3 -163,42 +167,42 @@@ test_http_push_nonff() 
                test_i18ngrep "Updates were rejected because" output
        '
  }
+ setup_askpass_helper() {
+       test_expect_success 'setup askpass helper' '
+               write_script "$TRASH_DIRECTORY/askpass" <<-\EOF &&
+               echo >>"$TRASH_DIRECTORY/askpass-query" "askpass: $*" &&
+               cat "$TRASH_DIRECTORY/askpass-response"
+               EOF
+               GIT_ASKPASS="$TRASH_DIRECTORY/askpass" &&
+               export GIT_ASKPASS &&
+               export TRASH_DIRECTORY
+       '
+ }
+ set_askpass() {
+       >"$TRASH_DIRECTORY/askpass-query" &&
+       echo "$*" >"$TRASH_DIRECTORY/askpass-response"
+ }
+ expect_askpass() {
+       dest=$HTTPD_DEST
+       {
+               case "$1" in
+               none)
+                       ;;
+               pass)
+                       echo "askpass: Password for 'http://$2@$dest': "
+                       ;;
+               both)
+                       echo "askpass: Username for 'http://$dest': "
+                       echo "askpass: Password for 'http://$2@$dest': "
+                       ;;
+               *)
+                       false
+                       ;;
+               esac
+       } >"$TRASH_DIRECTORY/askpass-expect" &&
+       test_cmp "$TRASH_DIRECTORY/askpass-expect" \
+                "$TRASH_DIRECTORY/askpass-query"
+ }
diff --combined t/lib-httpd/apache.conf
index 36b1596a10c04b5c5c095e96e8e247463eb3f022,ec8618dfde3b106c79f1d056bc0ed085075fc8c8..49d5d877ceeeba3738b0939f14d059c2a75ff3ae
@@@ -42,30 -42,25 +42,28 @@@ ErrorLog error.lo
  </IfModule>
  </IfVersion>
  
 +PassEnv GIT_VALGRIND
 +PassEnv GIT_VALGRIND_OPTIONS
 +
  Alias /dumb/ www/
- Alias /auth/ www/auth/
+ Alias /auth/dumb/ www/auth/dumb/
  
- <Location /smart/>
+ <LocationMatch /smart/>
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
        SetEnv GIT_HTTP_EXPORT_ALL
- </Location>
- <Location /smart_noexport/>
+ </LocationMatch>
+ <LocationMatch /smart_noexport/>
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
- </Location>
- <Location /smart_custom_env/>
+ </LocationMatch>
+ <LocationMatch /smart_custom_env/>
        SetEnv GIT_EXEC_PATH ${GIT_EXEC_PATH}
        SetEnv GIT_HTTP_EXPORT_ALL
        SetEnv GIT_COMMITTER_NAME "Custom User"
        SetEnv GIT_COMMITTER_EMAIL custom@example.com
- </Location>
- ScriptAlias /smart/ ${GIT_EXEC_PATH}/git-http-backend/
- ScriptAlias /smart_noexport/ ${GIT_EXEC_PATH}/git-http-backend/
- ScriptAlias /smart_custom_env/ ${GIT_EXEC_PATH}/git-http-backend/
+ </LocationMatch>
+ ScriptAliasMatch /smart_*[^/]*/(.*) ${GIT_EXEC_PATH}/git-http-backend/$1
  <Directory ${GIT_EXEC_PATH}>
 -      Options None
 +      Options FollowSymlinks
  </Directory>
  <Files ${GIT_EXEC_PATH}/git-http-backend>
        Options ExecCGI
@@@ -94,6 -89,13 +92,13 @@@ SSLEngine O
        Require valid-user
  </Location>
  
+ <LocationMatch "^/auth-push/.*/git-receive-pack$">
+       AuthType Basic
+       AuthName "git-auth"
+       AuthUserFile passwd
+       Require valid-user
+ </LocationMatch>
  <IfDefine DAV>
        LoadModule dav_module modules/mod_dav.so
        LoadModule dav_fs_module modules/mod_dav_fs.so