Merge branch 'sl/clean-d-ignored-fix' into maint
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:27:01 +0000 (13:27 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Jun 2017 20:27:02 +0000 (13:27 -0700)
"git clean -d" used to clean directories that has ignored files,
even though the command should not lose ignored ones without "-x".
"git status --ignored" did not list ignored and untracked files
without "-uall". These have been corrected.

* sl/clean-d-ignored-fix:
clean: teach clean -d to preserve ignored paths
dir: expose cmp_name() and check_contains()
dir: hide untracked contents of untracked dirs
dir: recurse into untracked dirs for ignored files
t7061: status --ignored should search untracked dirs
t7300: clean -d should skip dirs with ignored files

1  2 
builtin/clean.c
dir.c
diff --combined builtin/clean.c
index d861f836a29bf98d248f27632a8c47711a5da3a5,727203318726bf3e6dbd7a6881391d233b4f106e..937eb17b66ca8984a040e32b575eca43cfccb0e2
@@@ -174,10 -174,8 +174,10 @@@ static int remove_dirs(struct strbuf *p
                /* an empty dir could be removed even if it is unreadble */
                res = dry_run ? 0 : rmdir(path->buf);
                if (res) {
 +                      int saved_errno = errno;
                        quote_path_relative(path->buf, prefix, &quoted);
 -                      warning(_(msg_warn_remove_failed), quoted.buf);
 +                      errno = saved_errno;
 +                      warning_errno(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                }
                return res;
                                quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else {
 +                              int saved_errno = errno;
                                quote_path_relative(path->buf, prefix, &quoted);
 -                              warning(_(msg_warn_remove_failed), quoted.buf);
 +                              errno = saved_errno;
 +                              warning_errno(_(msg_warn_remove_failed), quoted.buf);
                                *dir_gone = 0;
                                ret = 1;
                        }
                if (!res)
                        *dir_gone = 1;
                else {
 +                      int saved_errno = errno;
                        quote_path_relative(path->buf, prefix, &quoted);
 -                      warning(_(msg_warn_remove_failed), quoted.buf);
 +                      errno = saved_errno;
 +                      warning_errno(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                        ret = 1;
                }
@@@ -857,6 -851,38 +857,38 @@@ static void interactive_main_loop(void
        }
  }
  
+ static void correct_untracked_entries(struct dir_struct *dir)
+ {
+       int src, dst, ign;
+       for (src = dst = ign = 0; src < dir->nr; src++) {
+               /* skip paths in ignored[] that cannot be inside entries[src] */
+               while (ign < dir->ignored_nr &&
+                      0 <= cmp_dir_entry(&dir->entries[src], &dir->ignored[ign]))
+                       ign++;
+               if (ign < dir->ignored_nr &&
+                   check_dir_entry_contains(dir->entries[src], dir->ignored[ign])) {
+                       /* entries[src] contains an ignored path, so we drop it */
+                       free(dir->entries[src]);
+               } else {
+                       struct dir_entry *ent = dir->entries[src++];
+                       /* entries[src] does not contain an ignored path, so we keep it */
+                       dir->entries[dst++] = ent;
+                       /* then discard paths in entries[] contained inside entries[src] */
+                       while (src < dir->nr &&
+                              check_dir_entry_contains(ent, dir->entries[src]))
+                               free(dir->entries[src++]);
+                       /* compensate for the outer loop's loop control */
+                       src--;
+               }
+       }
+       dir->nr = dst;
+ }
  int cmd_clean(int argc, const char **argv, const char *prefix)
  {
        int i, res;
  
        dir.flags |= DIR_SHOW_OTHER_DIRECTORIES;
  
+       if (remove_directories)
+               dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS;
        if (read_cache() < 0)
                die(_("index file corrupt"));
  
                       prefix, argv);
  
        fill_directory(&dir, &pathspec);
+       correct_untracked_entries(&dir);
  
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                string_list_append(&del_list, rel);
        }
  
+       for (i = 0; i < dir.nr; i++)
+               free(dir.entries[i]);
+       for (i = 0; i < dir.ignored_nr; i++)
+               free(dir.ignored[i]);
        if (interactive && del_list.nr > 0)
                interactive_main_loop();
  
                } else {
                        res = dry_run ? 0 : unlink(abs_path.buf);
                        if (res) {
 +                              int saved_errno = errno;
                                qname = quote_path_relative(item->string, NULL, &buf);
 -                              warning(_(msg_warn_remove_failed), qname);
 +                              errno = saved_errno;
 +                              warning_errno(_(msg_warn_remove_failed), qname);
                                errors++;
                        } else if (!quiet) {
                                qname = quote_path_relative(item->string, NULL, &buf);
diff --combined dir.c
index f451bfa48c0a0e7905d4c2adf4e3e05a8d272a8a,31c6e1dac008aa3c47e9e4c688c3043b1f91a9b6..31f9343f9fcf57a89cab519f284cf2e0a767744d
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -9,7 -9,6 +9,7 @@@
   */
  #include "cache.h"
  #include "dir.h"
 +#include "attr.h"
  #include "refs.h"
  #include "wildmatch.h"
  #include "pathspec.h"
@@@ -135,8 -134,7 +135,8 @@@ static size_t common_prefix_len(const s
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        for (n = 0; n < pathspec->nr; n++) {
                size_t i = 0, len = 0, item_len;
@@@ -211,36 -209,6 +211,36 @@@ int within_depth(const char *name, int 
  #define DO_MATCH_DIRECTORY (1<<1)
  #define DO_MATCH_SUBMODULE (1<<2)
  
 +static int match_attrs(const char *name, int namelen,
 +                     const struct pathspec_item *item)
 +{
 +      int i;
 +
 +      git_check_attr(name, item->attr_check);
 +      for (i = 0; i < item->attr_match_nr; i++) {
 +              const char *value;
 +              int matched;
 +              enum attr_match_mode match_mode;
 +
 +              value = item->attr_check->items[i].value;
 +              match_mode = item->attr_match[i].match_mode;
 +
 +              if (ATTR_TRUE(value))
 +                      matched = (match_mode == MATCH_SET);
 +              else if (ATTR_FALSE(value))
 +                      matched = (match_mode == MATCH_UNSET);
 +              else if (ATTR_UNSET(value))
 +                      matched = (match_mode == MATCH_UNSPECIFIED);
 +              else
 +                      matched = (match_mode == MATCH_VALUE &&
 +                                 !strcmp(item->attr_match[i].value, value));
 +              if (!matched)
 +                      return 0;
 +      }
 +
 +      return 1;
 +}
 +
  /*
   * Does 'match' match the given name?
   * A match is found if
@@@ -293,9 -261,6 +293,9 @@@ static int match_pathspec_item(const st
            strncmp(item->match, name - prefix, item->prefix))
                return 0;
  
 +      if (item->attr_match_nr && !match_attrs(name, namelen, item))
 +              return 0;
 +
        /* If the match was just the prefix, we matched */
        if (!*match)
                return MATCHED_RECURSIVELY;
@@@ -374,8 -339,7 +374,8 @@@ static int do_match_pathspec(const stru
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        if (!ps->nr) {
                if (!ps->recursive ||
@@@ -1397,8 -1361,7 +1397,8 @@@ static int simplify_away(const char *pa
                       PATHSPEC_LITERAL |
                       PATHSPEC_GLOB |
                       PATHSPEC_ICASE |
 -                     PATHSPEC_EXCLUDE);
 +                     PATHSPEC_EXCLUDE |
 +                     PATHSPEC_ATTR);
  
        for (i = 0; i < pathspec->nr; i++) {
                const struct pathspec_item *item = &pathspec->items[i];
@@@ -1784,7 -1747,10 +1784,10 @@@ static enum path_treatment read_directo
                        dir_state = state;
  
                /* recurse into subdir if instructed by treat_path */
-               if (state == path_recurse) {
+               if ((state == path_recurse) ||
+                       ((state == path_untracked) &&
+                        (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                        (get_dtype(cdir.de, path.buf, path.len) == DT_DIR))) {
                        struct untracked_cache_dir *ud;
                        ud = lookup_untracked(dir->untracked, untracked,
                                              path.buf + baselen,
        return dir_state;
  }
  
static int cmp_name(const void *p1, const void *p2)
int cmp_dir_entry(const void *p1, const void *p2)
  {
        const struct dir_entry *e1 = *(const struct dir_entry **)p1;
        const struct dir_entry *e2 = *(const struct dir_entry **)p2;
        return name_compare(e1->name, e1->len, e2->name, e2->len);
  }
  
+ /* check if *out lexically strictly contains *in */
+ int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in)
+ {
+       return (out->len < in->len) &&
+               (out->name[out->len - 1] == '/') &&
+               !memcmp(out->name, in->name, out->len);
+ }
  static int treat_leading_path(struct dir_struct *dir,
                              const char *path, int len,
                              const struct pathspec *pathspec)
@@@ -2060,8 -2034,32 +2071,32 @@@ int read_directory(struct dir_struct *d
                dir->untracked = NULL;
        if (!len || treat_leading_path(dir, path, len, pathspec))
                read_directory_recursive(dir, path, len, untracked, 0, pathspec);
-       QSORT(dir->entries, dir->nr, cmp_name);
-       QSORT(dir->ignored, dir->ignored_nr, cmp_name);
+       QSORT(dir->entries, dir->nr, cmp_dir_entry);
+       QSORT(dir->ignored, dir->ignored_nr, cmp_dir_entry);
+       /*
+        * If DIR_SHOW_IGNORED_TOO is set, read_directory_recursive() will
+        * also pick up untracked contents of untracked dirs; by default
+        * we discard these, but given DIR_KEEP_UNTRACKED_CONTENTS we do not.
+        */
+       if ((dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                    !(dir->flags & DIR_KEEP_UNTRACKED_CONTENTS)) {
+               int i, j;
+               /* remove from dir->entries untracked contents of untracked dirs */
+               for (i = j = 0; j < dir->nr; j++) {
+                       if (i &&
+                           check_dir_entry_contains(dir->entries[i - 1], dir->entries[j])) {
+                               free(dir->entries[j]);
+                               dir->entries[j] = NULL;
+                       } else {
+                               dir->entries[i++] = dir->entries[j];
+                       }
+               }
+               dir->nr = i;
+       }
        if (dir->untracked) {
                static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
                trace_printf_key(&trace_untracked_stats,
@@@ -2765,33 -2763,23 +2800,33 @@@ void untracked_cache_add_to_index(struc
  /* Update gitfile and core.worktree setting to connect work tree and git dir */
  void connect_work_tree_and_git_dir(const char *work_tree_, const char *git_dir_)
  {
 -      struct strbuf file_name = STRBUF_INIT;
 +      struct strbuf gitfile_sb = STRBUF_INIT;
 +      struct strbuf cfg_sb = STRBUF_INIT;
        struct strbuf rel_path = STRBUF_INIT;
 -      char *git_dir = real_pathdup(git_dir_, 1);
 -      char *work_tree = real_pathdup(work_tree_, 1);
 +      char *git_dir, *work_tree;
  
 -      /* Update gitfile */
 -      strbuf_addf(&file_name, "%s/.git", work_tree);
 -      write_file(file_name.buf, "gitdir: %s",
 -                 relative_path(git_dir, work_tree, &rel_path));
 +      /* Prepare .git file */
 +      strbuf_addf(&gitfile_sb, "%s/.git", work_tree_);
 +      if (safe_create_leading_directories_const(gitfile_sb.buf))
 +              die(_("could not create directories for %s"), gitfile_sb.buf);
 +
 +      /* Prepare config file */
 +      strbuf_addf(&cfg_sb, "%s/config", git_dir_);
 +      if (safe_create_leading_directories_const(cfg_sb.buf))
 +              die(_("could not create directories for %s"), cfg_sb.buf);
  
 +      git_dir = real_pathdup(git_dir_, 1);
 +      work_tree = real_pathdup(work_tree_, 1);
 +
 +      /* Write .git file */
 +      write_file(gitfile_sb.buf, "gitdir: %s",
 +                 relative_path(git_dir, work_tree, &rel_path));
        /* Update core.worktree setting */
 -      strbuf_reset(&file_name);
 -      strbuf_addf(&file_name, "%s/config", git_dir);
 -      git_config_set_in_file(file_name.buf, "core.worktree",
 +      git_config_set_in_file(cfg_sb.buf, "core.worktree",
                               relative_path(work_tree, git_dir, &rel_path));
  
 -      strbuf_release(&file_name);
 +      strbuf_release(&gitfile_sb);
 +      strbuf_release(&cfg_sb);
        strbuf_release(&rel_path);
        free(work_tree);
        free(git_dir);