Merge branch 'maint-1.5.1' into maint
authorJunio C Hamano <junkio@cox.net>
Mon, 21 May 2007 02:57:00 +0000 (19:57 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 21 May 2007 02:57:00 +0000 (19:57 -0700)
* maint-1.5.1:
annotate: make it work from subdirectories.
git-config: Correct asciidoc documentation for --int/--bool
t1300: Add tests for git-config --bool --get
unpack-trees.c: verify_uptodate: remove dead code
Use PATH_MAX instead of TEMPFILE_PATH_LEN
branch: fix segfault when resolving an invalid HEAD

1  2 
diff.c
git.c
unpack-trees.c
diff --combined diff.c
index 33297aa8a7fd8f1c7f4711a9807b0b497d3de2ae,3c15553710baa47b47eacdca0188c1afd0ae65a5..b23e1906783664103cf9b363a93a35f7d043c8ee
--- 1/diff.c
--- 2/diff.c
+++ b/diff.c
@@@ -8,7 -8,6 +8,7 @@@
  #include "delta.h"
  #include "xdiff-interface.h"
  #include "color.h"
 +#include "attr.h"
  
  #ifdef NO_FAST_WORKING_DIRECTORY
  #define FAST_WORKING_DIRECTORY 0
@@@ -16,6 -15,8 +16,6 @@@
  #define FAST_WORKING_DIRECTORY 1
  #endif
  
 -static int use_size_cache;
 -
  static int diff_detect_rename_default;
  static int diff_rename_limit_default = -1;
  static int diff_use_color_default;
@@@ -50,49 -51,6 +50,49 @@@ static int parse_diff_color_slot(const 
        die("bad config variable '%s'", var);
  }
  
 +static struct ll_diff_driver {
 +      const char *name;
 +      struct ll_diff_driver *next;
 +      char *cmd;
 +} *user_diff, **user_diff_tail;
 +
 +/*
 + * Currently there is only "diff.<drivername>.command" variable;
 + * because there are "diff.color.<slot>" variables, we are parsing
 + * this in a bit convoluted way to allow low level diff driver
 + * called "color".
 + */
 +static int parse_lldiff_command(const char *var, const char *ep, const char *value)
 +{
 +      const char *name;
 +      int namelen;
 +      struct ll_diff_driver *drv;
 +
 +      name = var + 5;
 +      namelen = ep - name;
 +      for (drv = user_diff; drv; drv = drv->next)
 +              if (!strncmp(drv->name, name, namelen) && !drv->name[namelen])
 +                      break;
 +      if (!drv) {
 +              char *namebuf;
 +              drv = xcalloc(1, sizeof(struct ll_diff_driver));
 +              namebuf = xmalloc(namelen + 1);
 +              memcpy(namebuf, name, namelen);
 +              namebuf[namelen] = 0;
 +              drv->name = namebuf;
 +              drv->next = NULL;
 +              if (!user_diff_tail)
 +                      user_diff_tail = &user_diff;
 +              *user_diff_tail = drv;
 +              user_diff_tail = &(drv->next);
 +      }
 +
 +      if (!value)
 +              return error("%s: lacks value", var);
 +      drv->cmd = strdup(value);
 +      return 0;
 +}
 +
  /*
   * These are to give UI layer defaults.
   * The core-level commands such as git-diff-files should
@@@ -119,18 -77,11 +119,18 @@@ int git_diff_ui_config(const char *var
                        diff_detect_rename_default = DIFF_DETECT_RENAME;
                return 0;
        }
 +      if (!prefixcmp(var, "diff.")) {
 +              const char *ep = strrchr(var, '.');
 +
 +              if (ep != var + 4 && !strcmp(ep, ".command"))
 +                      return parse_lldiff_command(var, ep, value);
 +      }
        if (!prefixcmp(var, "diff.color.") || !prefixcmp(var, "color.diff.")) {
                int slot = parse_diff_color_slot(var, 11);
                color_parse(value, var, diff_colors[slot]);
                return 0;
        }
 +
        return git_default_config(var, value);
  }
  
@@@ -186,13 -137,11 +186,11 @@@ static const char *external_diff(void
        return external_diff_cmd;
  }
  
- #define TEMPFILE_PATH_LEN             50
  static struct diff_tempfile {
        const char *name; /* filename external diff should read from */
        char hex[41];
        char mode[10];
-       char tmp_path[TEMPFILE_PATH_LEN];
+       char tmp_path[PATH_MAX];
  } diff_temp[2];
  
  static int count_lines(const char *data, int size)
@@@ -860,12 -809,7 +858,12 @@@ static void show_stats(struct diffstat_
  
                if (data->files[i]->is_binary) {
                        show_name(prefix, name, len, reset, set);
 -                      printf("  Bin\n");
 +                      printf("  Bin ");
 +                      printf("%s%d%s", del_c, deleted, reset);
 +                      printf(" -> ");
 +                      printf("%s%d%s", add_c, added, reset);
 +                      printf(" bytes");
 +                      printf("\n");
                        goto free_diffstat_file;
                }
                else if (data->files[i]->is_unmerged) {
@@@ -1100,39 -1044,13 +1098,39 @@@ static void emit_binary_diff(mmfile_t *
        emit_binary_diff_body(two, one);
  }
  
 +static void setup_diff_attr_check(struct git_attr_check *check)
 +{
 +      static struct git_attr *attr_diff;
 +
 +      if (!attr_diff)
 +              attr_diff = git_attr("diff", 4);
 +      check->attr = attr_diff;
 +}
 +
  #define FIRST_FEW_BYTES 8000
 -static int mmfile_is_binary(mmfile_t *mf)
 +static int file_is_binary(struct diff_filespec *one)
  {
 -      long sz = mf->size;
 +      unsigned long sz;
 +      struct git_attr_check attr_diff_check;
 +
 +      setup_diff_attr_check(&attr_diff_check);
 +      if (!git_checkattr(one->path, 1, &attr_diff_check)) {
 +              const char *value = attr_diff_check.value;
 +              if (ATTR_TRUE(value))
 +                      return 0;
 +              else if (ATTR_FALSE(value))
 +                      return 1;
 +      }
 +
 +      if (!one->data) {
 +              if (!DIFF_FILE_VALID(one))
 +                      return 0;
 +              diff_populate_filespec(one, 0);
 +      }
 +      sz = one->size;
        if (FIRST_FEW_BYTES < sz)
                sz = FIRST_FEW_BYTES;
 -      return !!memchr(mf->ptr, 0, sz);
 +      return !!memchr(one->data, 0, sz);
  }
  
  static void builtin_diff(const char *name_a,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
 -      if (!o->text && (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))) {
 +      if (!o->text && (file_is_binary(one) || file_is_binary(two))) {
                /* Quite common confusing case */
                if (mf1.size == mf2.size &&
                    !memcmp(mf1.ptr, mf2.ptr, mf1.size))
        }
  
   free_ab_and_return:
 +      diff_free_filespec_data(one);
 +      diff_free_filespec_data(two);
        free(a_one);
        free(b_two);
        return;
@@@ -1262,16 -1178,14 +1260,16 @@@ static void builtin_diffstat(const cha
                diff_populate_filespec(two, 0);
                data->deleted = count_lines(one->data, one->size);
                data->added = count_lines(two->data, two->size);
 -              return;
 +              goto free_and_return;
        }
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
 -      if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
 +      if (file_is_binary(one) || file_is_binary(two)) {
                data->is_binary = 1;
 -      else {
 +              data->added = mf2.size;
 +              data->deleted = mf1.size;
 +      } else {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                xdemitconf_t xecfg;
                ecb.priv = diffstat;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
        }
 +
 + free_and_return:
 +      diff_free_filespec_data(one);
 +      diff_free_filespec_data(two);
  }
  
  static void builtin_checkdiff(const char *name_a, const char *name_b,
        if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
                die("unable to read files to diff");
  
 -      if (mmfile_is_binary(&mf2))
 -              return;
 +      if (file_is_binary(two))
 +              goto free_and_return;
        else {
                /* Crazy xdl interfaces.. */
                xpparam_t xpp;
                ecb.priv = &data;
                xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
        }
 + free_and_return:
 +      diff_free_filespec_data(one);
 +      diff_free_filespec_data(two);
  }
  
  struct diff_filespec *alloc_filespec(const char *path)
@@@ -1406,12 -1313,61 +1404,12 @@@ static int reuse_worktree_file(const ch
        return 1;
  }
  
 -static struct sha1_size_cache {
 -      unsigned char sha1[20];
 -      unsigned long size;
 -} **sha1_size_cache;
 -static int sha1_size_cache_nr, sha1_size_cache_alloc;
 -
 -static struct sha1_size_cache *locate_size_cache(unsigned char *sha1,
 -                                               int find_only,
 -                                               unsigned long size)
 -{
 -      int first, last;
 -      struct sha1_size_cache *e;
 -
 -      first = 0;
 -      last = sha1_size_cache_nr;
 -      while (last > first) {
 -              int cmp, next = (last + first) >> 1;
 -              e = sha1_size_cache[next];
 -              cmp = hashcmp(e->sha1, sha1);
 -              if (!cmp)
 -                      return e;
 -              if (cmp < 0) {
 -                      last = next;
 -                      continue;
 -              }
 -              first = next+1;
 -      }
 -      /* not found */
 -      if (find_only)
 -              return NULL;
 -      /* insert to make it at "first" */
 -      if (sha1_size_cache_alloc <= sha1_size_cache_nr) {
 -              sha1_size_cache_alloc = alloc_nr(sha1_size_cache_alloc);
 -              sha1_size_cache = xrealloc(sha1_size_cache,
 -                                         sha1_size_cache_alloc *
 -                                         sizeof(*sha1_size_cache));
 -      }
 -      sha1_size_cache_nr++;
 -      if (first < sha1_size_cache_nr)
 -              memmove(sha1_size_cache + first + 1, sha1_size_cache + first,
 -                      (sha1_size_cache_nr - first - 1) *
 -                      sizeof(*sha1_size_cache));
 -      e = xmalloc(sizeof(struct sha1_size_cache));
 -      sha1_size_cache[first] = e;
 -      hashcpy(e->sha1, sha1);
 -      e->size = size;
 -      return e;
 -}
 -
  static int populate_from_stdin(struct diff_filespec *s)
  {
  #define INCREMENT 1024
        char *buf;
        unsigned long size;
 -      int got;
 +      ssize_t got;
  
        size = 0;
        buf = NULL;
        return 0;
  }
  
 +static int diff_populate_gitlink(struct diff_filespec *s, int size_only)
 +{
 +      int len;
 +      char *data = xmalloc(100);
 +      len = snprintf(data, 100,
 +              "Subproject commit %s\n", sha1_to_hex(s->sha1));
 +      s->data = data;
 +      s->size = len;
 +      s->should_free = 1;
 +      if (size_only) {
 +              s->data = NULL;
 +              free(data);
 +      }
 +      return 0;
 +}
 +
  /*
   * While doing rename detection and pickaxe operation, we may need to
   * grab the data for the blob (or file) for our own in-core comparison.
@@@ -1461,15 -1401,11 +1459,15 @@@ int diff_populate_filespec(struct diff_
        if (S_ISDIR(s->mode))
                return -1;
  
 -      if (!use_size_cache)
 -              size_only = 0;
 -
        if (s->data)
 -              return err;
 +              return 0;
 +
 +      if (size_only && 0 < s->size)
 +              return 0;
 +
 +      if (S_ISDIRLNK(s->mode))
 +              return diff_populate_gitlink(s, size_only);
 +
        if (!s->sha1_valid ||
            reuse_worktree_file(s->path, s->sha1, 0)) {
                struct stat st;
                /*
                 * Convert from working tree format to canonical git format
                 */
 -              buf = s->data;
                size = s->size;
 -              if (convert_to_git(s->path, &buf, &size)) {
 +              buf = convert_to_git(s->path, s->data, &size);
 +              if (buf) {
                        munmap(s->data, s->size);
                        s->should_munmap = 0;
                        s->data = buf;
        }
        else {
                enum object_type type;
 -              struct sha1_size_cache *e;
 -
 -              if (size_only && use_size_cache &&
 -                  (e = locate_size_cache(s->sha1, 1, 0)) != NULL) {
 -                      s->size = e->size;
 -                      return 0;
 -              }
 -
 -              if (size_only) {
 +              if (size_only)
                        type = sha1_object_info(s->sha1, &s->size);
 -                      if (use_size_cache && 0 < type)
 -                              locate_size_cache(s->sha1, 0, s->size);
 -              }
                else {
                        s->data = read_sha1_file(s->sha1, &type, &s->size);
                        s->should_free = 1;
@@@ -1544,11 -1491,8 +1542,11 @@@ void diff_free_filespec_data(struct dif
                free(s->data);
        else if (s->should_munmap)
                munmap(s->data, s->size);
 -      s->should_free = s->should_munmap = 0;
 -      s->data = NULL;
 +
 +      if (s->should_free || s->should_munmap) {
 +              s->should_free = s->should_munmap = 0;
 +              s->data = NULL;
 +      }
        free(s->cnt_data);
        s->cnt_data = NULL;
  }
@@@ -1561,7 -1505,7 +1559,7 @@@ static void prep_temp_blob(struct diff_
  {
        int fd;
  
-       fd = git_mkstemp(temp->tmp_path, TEMPFILE_PATH_LEN, ".diff_XXXXXX");
+       fd = git_mkstemp(temp->tmp_path, PATH_MAX, ".diff_XXXXXX");
        if (fd < 0)
                die("unable to create temp-file");
        if (write_in_full(fd, blob, size) != size)
@@@ -1748,30 -1692,6 +1746,30 @@@ static void run_external_diff(const cha
        }
  }
  
 +static const char *external_diff_attr(const char *name)
 +{
 +      struct git_attr_check attr_diff_check;
 +
 +      setup_diff_attr_check(&attr_diff_check);
 +      if (!git_checkattr(name, 1, &attr_diff_check)) {
 +              const char *value = attr_diff_check.value;
 +              if (!ATTR_TRUE(value) &&
 +                  !ATTR_FALSE(value) &&
 +                  !ATTR_UNSET(value)) {
 +                      struct ll_diff_driver *drv;
 +
 +                      if (!user_diff_tail) {
 +                              user_diff_tail = &user_diff;
 +                              git_config(git_diff_ui_config);
 +                      }
 +                      for (drv = user_diff; drv; drv = drv->next)
 +                              if (!strcmp(drv->name, value))
 +                                      return drv->cmd;
 +              }
 +      }
 +      return NULL;
 +}
 +
  static void run_diff_cmd(const char *pgm,
                         const char *name,
                         const char *other,
                         struct diff_options *o,
                         int complete_rewrite)
  {
 +      if (!o->allow_external)
 +              pgm = NULL;
 +      else {
 +              const char *cmd = external_diff_attr(name);
 +              if (cmd)
 +                      pgm = cmd;
 +      }
 +
        if (pgm) {
                run_external_diff(pgm, name, other, one, two, xfrm_msg,
                                  complete_rewrite);
@@@ -1885,8 -1797,8 +1883,8 @@@ static void run_diff(struct diff_filepa
  
                if (o->binary) {
                        mmfile_t mf;
 -                      if ((!fill_mmfile(&mf, one) && mmfile_is_binary(&mf)) ||
 -                          (!fill_mmfile(&mf, two) && mmfile_is_binary(&mf)))
 +                      if ((!fill_mmfile(&mf, one) && file_is_binary(one)) ||
 +                          (!fill_mmfile(&mf, two) && file_is_binary(two)))
                                abbrev = 40;
                }
                len += snprintf(msg + len, sizeof(msg) - len,
@@@ -2040,6 -1952,8 +2038,6 @@@ int diff_setup_done(struct diff_option
                         */
                        read_cache();
        }
 -      if (options->setup & DIFF_SETUP_USE_SIZE_CACHE)
 -              use_size_cache = 1;
        if (options->abbrev <= 0 || 40 < options->abbrev)
                options->abbrev = 40; /* full */
  
@@@ -2779,7 -2693,7 +2777,7 @@@ static int diff_get_patch_id(struct dif
                        return error("unable to read files to diff");
  
                /* Maybe hash p->two? into the patch id? */
 -              if (mmfile_is_binary(&mf2))
 +              if (file_is_binary(p->two))
                        continue;
  
                len1 = remove_space(p->one->path, strlen(p->one->path));
diff --combined git.c
index f20090721aa799a8dfd780291c8a83bd834b45b5,3d0754cbaa2545c656891febde240911b2335f7d..29b55a16047837084fd9e2e8238137b8a2fe44ea
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -225,7 -225,7 +225,7 @@@ static void handle_internal_command(in
                int option;
        } commands[] = {
                { "add", cmd_add, RUN_SETUP | NOT_BARE },
-               { "annotate", cmd_annotate, USE_PAGER },
+               { "annotate", cmd_annotate, RUN_SETUP | USE_PAGER },
                { "apply", cmd_apply },
                { "archive", cmd_archive },
                { "blame", cmd_blame, RUN_SETUP },
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "checkout-index", cmd_checkout_index, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
 +              { "check-attr", cmd_check_attr, RUN_SETUP | NOT_BARE },
                { "cherry", cmd_cherry, RUN_SETUP },
                { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NOT_BARE },
                { "commit-tree", cmd_commit_tree, RUN_SETUP },
diff --combined unpack-trees.c
index 906ce69ea6daf98cdb4f9dd3d6498264159b6b76,6d1f0d13a36cf4bcdc9e01d910866a6b624bbe6f..cac2411b9de7b4889abe6b0b84df25d24a38c7e5
@@@ -4,7 -4,6 +4,7 @@@
  #include "tree-walk.h"
  #include "cache-tree.h"
  #include "unpack-trees.h"
 +#include "progress.h"
  
  #define DBRT_DEBUG 1
  
@@@ -71,6 -70,7 +71,6 @@@ static int entcmp(const char *name1, in
  
  static int unpack_trees_rec(struct tree_entry_list **posns, int len,
                            const char *base, struct unpack_trees_options *o,
 -                          int *indpos,
                            struct tree_entry_list *df_conflict_list)
  {
        int baselen = strlen(base);
                cache_name = NULL;
  
                /* Check the cache */
 -              if (o->merge && *indpos < active_nr) {
 +              if (o->merge && o->pos < active_nr) {
                        /* This is a bit tricky: */
                        /* If the index has a subdirectory (with
                         * contents) as the first name, it'll get a
                         * file case.
                         */
  
 -                      cache_name = active_cache[*indpos]->name;
 +                      cache_name = active_cache[o->pos]->name;
                        if (strlen(cache_name) > baselen &&
                            !memcmp(cache_name, base, baselen)) {
                                cache_name += baselen;
  
                if (cache_name && !strcmp(cache_name, first)) {
                        any_files = 1;
 -                      src[0] = active_cache[*indpos];
 -                      remove_cache_entry_at(*indpos);
 +                      src[0] = active_cache[o->pos];
 +                      remove_cache_entry_at(o->pos);
                }
  
                for (i = 0; i < len; i++) {
  #if DBRT_DEBUG > 1
                                printf("Added %d entries\n", ret);
  #endif
 -                              *indpos += ret;
 +                              o->pos += ret;
                        } else {
                                for (i = 0; i < src_size; i++) {
                                        if (src[i]) {
                        newbase[baselen + pathlen] = '/';
                        newbase[baselen + pathlen + 1] = '\0';
                        if (unpack_trees_rec(subposns, len, newbase, o,
 -                                           indpos, df_conflict_list)) {
 +                                           df_conflict_list)) {
                                retval = -1;
                                goto leave_directory;
                        }
   * directories, in case this unlink is the removal of the
   * last entry in the directory -- empty directories are removed.
   */
 -static void unlink_entry(char *name)
 +static void unlink_entry(char *name, char *last_symlink)
  {
        char *cp, *prev;
  
 +      if (has_symlink_leading_path(name, last_symlink))
 +              return;
        if (unlink(name))
                return;
        prev = NULL;
        }
  }
  
 -static volatile sig_atomic_t progress_update;
 -
 -static void progress_interval(int signum)
 -{
 -      progress_update = 1;
 -}
 -
 -static void setup_progress_signal(void)
 -{
 -      struct sigaction sa;
 -      struct itimerval v;
 -
 -      memset(&sa, 0, sizeof(sa));
 -      sa.sa_handler = progress_interval;
 -      sigemptyset(&sa.sa_mask);
 -      sa.sa_flags = SA_RESTART;
 -      sigaction(SIGALRM, &sa, NULL);
 -
 -      v.it_interval.tv_sec = 1;
 -      v.it_interval.tv_usec = 0;
 -      v.it_value = v.it_interval;
 -      setitimer(ITIMER_REAL, &v, NULL);
 -}
 -
  static struct checkout state;
  static void check_updates(struct cache_entry **src, int nr,
 -              struct unpack_trees_options *o)
 +                      struct unpack_trees_options *o)
  {
        unsigned short mask = htons(CE_UPDATE);
 -      unsigned last_percent = 200, cnt = 0, total = 0;
 +      unsigned cnt = 0, total = 0;
 +      struct progress progress;
 +      char last_symlink[PATH_MAX];
  
        if (o->update && o->verbose_update) {
                for (total = cnt = 0; cnt < nr; cnt++) {
                                total++;
                }
  
 -              /* Don't bother doing this for very small updates */
 -              if (total < 250)
 -                      total = 0;
 -
 -              if (total) {
 -                      fprintf(stderr, "Checking files out...\n");
 -                      setup_progress_signal();
 -                      progress_update = 1;
 -              }
 +              start_progress_delay(&progress, "Checking %u files out...",
 +                                   "", total, 50, 2);
                cnt = 0;
        }
  
 +      *last_symlink = '\0';
        while (nr--) {
                struct cache_entry *ce = *src++;
  
 -              if (total) {
 -                      if (!ce->ce_mode || ce->ce_flags & mask) {
 -                              unsigned percent;
 -                              cnt++;
 -                              percent = (cnt * 100) / total;
 -                              if (percent != last_percent ||
 -                                  progress_update) {
 -                                      fprintf(stderr, "%4u%% (%u/%u) done\r",
 -                                              percent, cnt, total);
 -                                      last_percent = percent;
 -                                      progress_update = 0;
 -                              }
 -                      }
 -              }
 +              if (total)
 +                      if (!ce->ce_mode || ce->ce_flags & mask)
 +                              display_progress(&progress, ++cnt);
                if (!ce->ce_mode) {
                        if (o->update)
 -                              unlink_entry(ce->name);
 +                              unlink_entry(ce->name, last_symlink);
                        continue;
                }
                if (ce->ce_flags & mask) {
                        ce->ce_flags &= ~mask;
 -                      if (o->update)
 +                      if (o->update) {
                                checkout_entry(ce, &state, NULL);
 +                              *last_symlink = '\0';
 +                      }
                }
        }
 -      if (total) {
 -              signal(SIGALRM, SIG_IGN);
 -              fputc('\n', stderr);
 -      }
 +      if (total)
 +              stop_progress(&progress);;
  }
  
  int unpack_trees(struct object_list *trees, struct unpack_trees_options *o)
  {
 -      int indpos = 0;
        unsigned len = object_list_length(trees);
        struct tree_entry_list **posns;
        int i;
                        posn = posn->next;
                }
                if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
 -                                   o, &indpos, &df_conflict_list))
 +                                   o, &df_conflict_list))
                        return -1;
        }
  
@@@ -414,10 -452,6 +414,6 @@@ static void verify_uptodate(struct cach
                        return;
                errno = 0;
        }
-       if (o->reset) {
-               ce->ce_flags |= htons(CE_UPDATE);
-               return;
-       }
        if (errno == ENOENT)
                return;
        die("Entry '%s' not uptodate. Cannot merge.", ce->name);
@@@ -429,64 -463,6 +425,64 @@@ static void invalidate_ce_path(struct c
                cache_tree_invalidate_path(active_cache_tree, ce->name);
  }
  
 +static int verify_clean_subdirectory(const char *path, const char *action,
 +                                    struct unpack_trees_options *o)
 +{
 +      /*
 +       * we are about to extract "path"; we would not want to lose
 +       * anything in the existing directory there.
 +       */
 +      int namelen;
 +      int pos, i;
 +      struct dir_struct d;
 +      char *pathbuf;
 +      int cnt = 0;
 +
 +      /*
 +       * First let's make sure we do not have a local modification
 +       * in that directory.
 +       */
 +      namelen = strlen(path);
 +      pos = cache_name_pos(path, namelen);
 +      if (0 <= pos)
 +              return cnt; /* we have it as nondirectory */
 +      pos = -pos - 1;
 +      for (i = pos; i < active_nr; i++) {
 +              struct cache_entry *ce = active_cache[i];
 +              int len = ce_namelen(ce);
 +              if (len < namelen ||
 +                  strncmp(path, ce->name, namelen) ||
 +                  ce->name[namelen] != '/')
 +                      break;
 +              /*
 +               * ce->name is an entry in the subdirectory.
 +               */
 +              if (!ce_stage(ce)) {
 +                      verify_uptodate(ce, o);
 +                      ce->ce_mode = 0;
 +              }
 +              cnt++;
 +      }
 +
 +      /*
 +       * Then we need to make sure that we do not lose a locally
 +       * present file that is not ignored.
 +       */
 +      pathbuf = xmalloc(namelen + 2);
 +      memcpy(pathbuf, path, namelen);
 +      strcpy(pathbuf+namelen, "/");
 +
 +      memset(&d, 0, sizeof(d));
 +      if (o->dir)
 +              d.exclude_per_dir = o->dir->exclude_per_dir;
 +      i = read_directory(&d, path, pathbuf, namelen+1, NULL);
 +      if (i)
 +              die("Updating '%s' would lose untracked files in it",
 +                  path);
 +      free(pathbuf);
 +      return cnt;
 +}
 +
  /*
   * We do not want to remove or overwrite a working tree file that
   * is not tracked, unless it is ignored.
@@@ -498,62 -474,9 +494,62 @@@ static void verify_absent(const char *p
  
        if (o->index_only || o->reset || !o->update)
                return;
 -      if (!lstat(path, &st) && !(o->dir && excluded(o->dir, path)))
 +
 +      if (!lstat(path, &st)) {
 +              int cnt;
 +
 +              if (o->dir && excluded(o->dir, path))
 +                      /*
 +                       * path is explicitly excluded, so it is Ok to
 +                       * overwrite it.
 +                       */
 +                      return;
 +              if (S_ISDIR(st.st_mode)) {
 +                      /*
 +                       * We are checking out path "foo" and
 +                       * found "foo/." in the working tree.
 +                       * This is tricky -- if we have modified
 +                       * files that are in "foo/" we would lose
 +                       * it.
 +                       */
 +                      cnt = verify_clean_subdirectory(path, action, o);
 +
 +                      /*
 +                       * If this removed entries from the index,
 +                       * what that means is:
 +                       *
 +                       * (1) the caller unpack_trees_rec() saw path/foo
 +                       * in the index, and it has not removed it because
 +                       * it thinks it is handling 'path' as blob with
 +                       * D/F conflict;
 +                       * (2) we will return "ok, we placed a merged entry
 +                       * in the index" which would cause o->pos to be
 +                       * incremented by one;
 +                       * (3) however, original o->pos now has 'path/foo'
 +                       * marked with "to be removed".
 +                       *
 +                       * We need to increment it by the number of
 +                       * deleted entries here.
 +                       */
 +                      o->pos += cnt;
 +                      return;
 +              }
 +
 +              /*
 +               * The previous round may already have decided to
 +               * delete this path, which is in a subdirectory that
 +               * is being replaced with a blob.
 +               */
 +              cnt = cache_name_pos(path, strlen(path));
 +              if (0 <= cnt) {
 +                      struct cache_entry *ce = active_cache[cnt];
 +                      if (!ce_stage(ce) && !ce->ce_mode)
 +                              return;
 +              }
 +
                die("Untracked working tree file '%s' "
                    "would be %s by merge.", path, action);
 +      }
  }
  
  static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@@@ -598,7 -521,7 +594,7 @@@ static int deleted_entry(struct cache_e
        return 1;
  }
  
 -static int keep_entry(struct cache_entry *ce)
 +static int keep_entry(struct cache_entry *ce, struct unpack_trees_options *o)
  {
        add_cache_entry(ce, ADD_CACHE_OK_TO_ADD);
        return 1;
@@@ -629,6 -552,7 +625,6 @@@ int threeway_merge(struct cache_entry *
        int count;
        int head_match = 0;
        int remote_match = 0;
 -      const char *path = NULL;
  
        int df_conflict_head = 0;
        int df_conflict_remote = 0;
        int i;
  
        for (i = 1; i < o->head_idx; i++) {
 -              if (!stages[i])
 +              if (!stages[i] || stages[i] == o->df_conflict_entry)
                        any_anc_missing = 1;
 -              else {
 -                      if (!path)
 -                              path = stages[i]->name;
 +              else
                        no_anc_exists = 0;
 -              }
        }
  
        index = stages[0];
                remote = NULL;
        }
  
 -      if (!path && index)
 -              path = index->name;
 -      if (!path && head)
 -              path = head->name;
 -      if (!path && remote)
 -              path = remote->name;
 -
        /* First, if there's a #16 situation, note that to prevent #13
         * and #14.
         */
        if (o->aggressive) {
                int head_deleted = !head && !df_conflict_head;
                int remote_deleted = !remote && !df_conflict_remote;
 +              const char *path = NULL;
 +
 +              if (index)
 +                      path = index->name;
 +              else if (head)
 +                      path = head->name;
 +              else if (remote)
 +                      path = remote->name;
 +              else {
 +                      for (i = 1; i < o->head_idx; i++) {
 +                              if (stages[i] && stages[i] != o->df_conflict_entry) {
 +                                      path = stages[i]->name;
 +                                      break;
 +                              }
 +                      }
 +              }
 +
                /*
                 * Deleted in both.
                 * Deleted in one and unchanged in the other.
  
        o->nontrivial_merge = 1;
  
 -      /* #2, #3, #4, #6, #7, #9, #11. */
 +      /* #2, #3, #4, #6, #7, #9, #10, #11. */
        count = 0;
        if (!head_match || !remote_match) {
                for (i = 1; i < o->head_idx; i++) {
 -                      if (stages[i]) {
 -                              keep_entry(stages[i]);
 +                      if (stages[i] && stages[i] != o->df_conflict_entry) {
 +                              keep_entry(stages[i], o);
                                count++;
                                break;
                        }
                show_stage_entry(stderr, "remote ", stages[remote_match]);
        }
  #endif
 -      if (head) { count += keep_entry(head); }
 -      if (remote) { count += keep_entry(remote); }
 +      if (head) { count += keep_entry(head, o); }
 +      if (remote) { count += keep_entry(remote, o); }
        return count;
  }
  
@@@ -792,18 -709,12 +788,18 @@@ int twoway_merge(struct cache_entry **s
                struct unpack_trees_options *o)
  {
        struct cache_entry *current = src[0];
 -      struct cache_entry *oldtree = src[1], *newtree = src[2];
 +      struct cache_entry *oldtree = src[1];
 +      struct cache_entry *newtree = src[2];
  
        if (o->merge_size != 2)
                return error("Cannot do a twoway merge of %d trees",
                             o->merge_size);
  
 +      if (oldtree == o->df_conflict_entry)
 +              oldtree = NULL;
 +      if (newtree == o->df_conflict_entry)
 +              newtree = NULL;
 +
        if (current) {
                if ((!oldtree && !newtree) || /* 4 and 5 */
                    (!oldtree && newtree &&
                    (oldtree && newtree &&
                     same(oldtree, newtree)) || /* 14 and 15 */
                    (oldtree && newtree &&
 -                   !same(oldtree, newtree) && /* 18 and 19*/
 +                   !same(oldtree, newtree) && /* 18 and 19 */
                     same(current, newtree))) {
 -                      return keep_entry(current);
 +                      return keep_entry(current, o);
                }
                else if (oldtree && !newtree && same(current, oldtree)) {
                        /* 10 or 11 */
@@@ -859,7 -770,7 +855,7 @@@ int bind_merge(struct cache_entry **src
        if (a && old)
                die("Entry '%s' overlaps.  Cannot bind.", a->name);
        if (!a)
 -              return keep_entry(old);
 +              return keep_entry(old, o);
        else
                return merged_entry(a, NULL, o);
  }
@@@ -889,7 -800,7 +885,7 @@@ int oneway_merge(struct cache_entry **s
                            ce_match_stat(old, &st, 1))
                                old->ce_flags |= htons(CE_UPDATE);
                }
 -              return keep_entry(old);
 +              return keep_entry(old, o);
        }
        return merged_entry(a, old, o);
  }