Merge branch 'jm/status-ignored-files-list'
authorJunio C Hamano <gitster@pobox.com>
Mon, 13 Nov 2017 05:44:59 +0000 (14:44 +0900)
committerJunio C Hamano <gitster@pobox.com>
Mon, 13 Nov 2017 05:44:59 +0000 (14:44 +0900)
The set of paths output from "git status --ignored" was tied
closely with its "--untracked=<mode>" option, but now it can be
controlled more flexibly. Most notably, a directory that is
ignored because it is listed to be ignored in the ignore/exclude
mechanism can be handled differently from a directory that ends up
to be ignored only because all files in it are ignored.

* jm/status-ignored-files-list:
status: test ignored modes
status: document options to show matching ignored files
status: report matching ignored and normal untracked
status: add option to show ignored files differently

1  2 
builtin/commit.c
dir.c
wt-status.c
diff --combined builtin/commit.c
index 605ea8c0e9663726c64950dba07afe21516c9b26,98d84d02772c0ad5f0ed995eaba8b0d9ab6752da..cfb78fcf680f4aea96035d0158b2855a1358d674
@@@ -118,7 -118,7 +118,7 @@@ static int edit_flag = -1; /* unspecifi
  static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
  static int config_commit_verbose = -1; /* unspecified */
  static int no_post_rewrite, allow_empty_message;
- static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+ static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
  static char *sign_commit;
  
  /*
@@@ -139,7 -139,7 +139,7 @@@ static const char *cleanup_arg
  static enum commit_whence whence;
  static int sequencer_in_use;
  static int use_editor = 1, include_status = 1;
- static int show_ignored_in_status, have_option_m;
+ static int have_option_m;
  static struct strbuf message = STRBUF_INIT;
  
  static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@@@ -355,7 -355,7 +355,7 @@@ static const char *prepare_index(int ar
  
                refresh_cache_or_die(refresh_flags);
  
 -              if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 +              if (write_locked_index(&the_index, &index_lock, 0))
                        die(_("unable to create temporary index"));
  
                old_index_env = getenv(INDEX_ENVIRONMENT);
                if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) {
                        if (reopen_lock_file(&index_lock) < 0)
                                die(_("unable to write index file"));
 -                      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 +                      if (write_locked_index(&the_index, &index_lock, 0))
                                die(_("unable to update temporary index"));
                } else
                        warning(_("Failed to update main cache tree"));
                add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
                refresh_cache_or_die(refresh_flags);
                update_main_cache_tree(WRITE_TREE_SILENT);
 -              if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 +              if (write_locked_index(&the_index, &index_lock, 0))
                        die(_("unable to write new_index file"));
                commit_style = COMMIT_NORMAL;
                ret = get_lock_file_path(&index_lock);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
        update_main_cache_tree(WRITE_TREE_SILENT);
 -      if (write_locked_index(&the_index, &index_lock, CLOSE_LOCK))
 +      if (write_locked_index(&the_index, &index_lock, 0))
                die(_("unable to write new_index file"));
  
        hold_lock_file_for_update(&false_lock,
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
  
 -      if (write_locked_index(&the_index, &false_lock, CLOSE_LOCK))
 +      if (write_locked_index(&the_index, &false_lock, 0))
                die(_("unable to write temporary index file"));
  
        discard_cache();
@@@ -912,12 -912,11 +912,12 @@@ static int prepare_to_commit(const cha
                         * submodules which were manually staged, which would
                         * be really confusing.
                         */
 -                      int diff_flags = DIFF_OPT_OVERRIDE_SUBMODULE_CONFIG;
 +                      struct diff_flags flags = DIFF_FLAGS_INIT;
 +                      flags.override_submodule_config = 1;
                        if (ignore_submodule_arg &&
                            !strcmp(ignore_submodule_arg, "all"))
 -                              diff_flags |= DIFF_OPT_IGNORE_SUBMODULES;
 -                      commitable = index_differs_from(parent, diff_flags, 1);
 +                              flags.ignore_submodules = 1;
 +                      commitable = index_differs_from(parent, &flags, 1);
                }
        }
        strbuf_release(&committer_ident);
@@@ -1076,6 -1075,19 +1076,19 @@@ static const char *find_author_by_nickn
        die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
  }
  
+ static void handle_ignored_arg(struct wt_status *s)
+ {
+       if (!ignored_arg)
+               ; /* default already initialized */
+       else if (!strcmp(ignored_arg, "traditional"))
+               s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED;
+       else if (!strcmp(ignored_arg, "no"))
+               s->show_ignored_mode = SHOW_NO_IGNORED;
+       else if (!strcmp(ignored_arg, "matching"))
+               s->show_ignored_mode = SHOW_MATCHING_IGNORED;
+       else
+               die(_("Invalid ignored mode '%s'"), ignored_arg);
+ }
  
  static void handle_untracked_files_arg(struct wt_status *s)
  {
@@@ -1364,8 -1376,10 +1377,10 @@@ int cmd_status(int argc, const char **a
                  N_("mode"),
                  N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
-               OPT_BOOL(0, "ignored", &show_ignored_in_status,
-                        N_("show ignored files")),
+               { OPTION_STRING, 0, "ignored", &ignored_arg,
+                 N_("mode"),
+                 N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"),
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" },
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        finalize_deferred_config(&s);
  
        handle_untracked_files_arg(&s);
-       if (show_ignored_in_status)
-               s.show_ignored_files = 1;
+       handle_ignored_arg(&s);
+       if (s.show_ignored_mode == SHOW_MATCHING_IGNORED &&
+           s.show_untracked_files == SHOW_NO_UNTRACKED_FILES)
+               die(_("Unsupported combination of ignored and untracked-files arguments"));
        parse_pathspec(&s.pathspec, 0,
                       PATHSPEC_PREFER_FULL,
                       prefix, argv);
@@@ -1493,8 -1511,6 +1512,8 @@@ static void print_summary(const char *p
        diff_setup_done(&rev.diffopt);
  
        head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
 +      if (!head)
 +              die_errno(_("unable to resolve HEAD after creating commit"));
        if (!strcmp(head, "HEAD"))
                head = _("detached HEAD");
        else
@@@ -1791,9 -1807,9 +1810,9 @@@ int cmd_commit(int argc, const char **a
  
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
 -          ref_transaction_update(transaction, "HEAD", oid.hash,
 +          ref_transaction_update(transaction, "HEAD", &oid,
                                   current_head
 -                                 ? current_head->object.oid.hash : null_sha1,
 +                                 ? &current_head->object.oid : &null_oid,
                                   0, sb.buf, &err) ||
            ref_transaction_commit(transaction, &err)) {
                rollback_index_files();
diff --combined dir.c
index fc2bdc79fc8ff599c8fba922dd7407cacae0c730,20457724c09a824d82026c88b84ef97417e4e6ea..047a950f55372babf61bfa9c8dbd979fc4b8bc26
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -1389,10 -1389,34 +1389,34 @@@ static enum path_treatment treat_direct
        case index_nonexistent:
                if (dir->flags & DIR_SHOW_OTHER_DIRECTORIES)
                        break;
+               if (exclude &&
+                       (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                       (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING)) {
+                       /*
+                        * This is an excluded directory and we are
+                        * showing ignored paths that match an exclude
+                        * pattern.  (e.g. show directory as ignored
+                        * only if it matches an exclude pattern).
+                        * This path will either be 'path_excluded`
+                        * (if we are showing empty directories or if
+                        * the directory is not empty), or will be
+                        * 'path_none' (empty directory, and we are
+                        * not showing empty directories).
+                        */
+                       if (!(dir->flags & DIR_HIDE_EMPTY_DIRECTORIES))
+                               return path_excluded;
+                       if (read_directory_recursive(dir, istate, dirname, len,
+                                                    untracked, 1, 1, pathspec) == path_excluded)
+                               return path_excluded;
+                       return path_none;
+               }
                if (!(dir->flags & DIR_NO_GITLINKS)) {
 -                      unsigned char sha1[20];
 -                      if (resolve_gitlink_ref(dirname, "HEAD", sha1) == 0)
 -                              return path_untracked;
 +                      struct object_id oid;
 +                      if (resolve_gitlink_ref(dirname, "HEAD", &oid) == 0)
 +                              return exclude ? path_excluded : path_untracked;
                }
                return path_recurse;
        }
@@@ -1561,6 -1585,7 +1585,7 @@@ static enum path_treatment treat_one_pa
  {
        int exclude;
        int has_path_in_index = !!index_file_exists(istate, path->buf, path->len, ignore_case);
+       enum path_treatment path_treatment;
  
        if (dtype == DT_UNKNOWN)
                dtype = get_dtype(de, istate, path->buf, path->len);
                return path_none;
        case DT_DIR:
                strbuf_addch(path, '/');
-               return treat_directory(dir, istate, untracked, path->buf, path->len,
-                                      baselen, exclude, pathspec);
+               path_treatment = treat_directory(dir, istate, untracked,
+                                                path->buf, path->len,
+                                                baselen, exclude, pathspec);
+               /*
+                * If 1) we only want to return directories that
+                * match an exclude pattern and 2) this directory does
+                * not match an exclude pattern but all of its
+                * contents are excluded, then indicate that we should
+                * recurse into this directory (instead of marking the
+                * directory itself as an ignored path).
+                */
+               if (!exclude &&
+                   path_treatment == path_excluded &&
+                   (dir->flags & DIR_SHOW_IGNORED_TOO) &&
+                   (dir->flags & DIR_SHOW_IGNORED_TOO_MODE_MATCHING))
+                       return path_recurse;
+               return path_treatment;
        case DT_REG:
        case DT_LNK:
                return exclude ? path_excluded : path_untracked;
@@@ -2279,10 -2319,10 +2319,10 @@@ static int remove_dir_recurse(struct st
        int ret = 0, original_len = path->len, len, kept_down = 0;
        int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
        int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
 -      unsigned char submodule_head[20];
 +      struct object_id submodule_head;
  
        if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
 -          !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
 +          !resolve_gitlink_ref(path->buf, "HEAD", &submodule_head)) {
                /* Do not descend and nuke a nested git work tree. */
                if (kept_up)
                        *kept_up = 1;
diff --combined wt-status.c
index ed3271c3f3eaaa399a19722bce2990d4b7398115,6760df61768a813df466f40afc62b513bd16e4ab..937a87bbd59d1ec6968380d174c3dc8226c47892
@@@ -559,12 -559,12 +559,12 @@@ static void wt_status_collect_changes_w
        init_revisions(&rev, NULL);
        setup_revisions(0, NULL, &rev, NULL);
        rev.diffopt.output_format |= DIFF_FORMAT_CALLBACK;
 -      DIFF_OPT_SET(&rev.diffopt, DIRTY_SUBMODULES);
 +      rev.diffopt.flags.dirty_submodules = 1;
        rev.diffopt.ita_invisible_in_index = 1;
        if (!s->show_untracked_files)
 -              DIFF_OPT_SET(&rev.diffopt, IGNORE_UNTRACKED_IN_SUBMODULES);
 +              rev.diffopt.flags.ignore_untracked_in_submodules = 1;
        if (s->ignore_submodule_arg) {
 -              DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 +              rev.diffopt.flags.override_submodule_config = 1;
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
        }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
@@@ -583,7 -583,7 +583,7 @@@ static void wt_status_collect_changes_i
        opt.def = s->is_initial ? EMPTY_TREE_SHA1_HEX : s->reference;
        setup_revisions(0, NULL, &rev, &opt);
  
 -      DIFF_OPT_SET(&rev.diffopt, OVERRIDE_SUBMODULE_CONFIG);
 +      rev.diffopt.flags.override_submodule_config = 1;
        rev.diffopt.ita_invisible_in_index = 1;
        if (s->ignore_submodule_arg) {
                handle_ignore_submodules_arg(&rev.diffopt, s->ignore_submodule_arg);
@@@ -658,10 -658,15 +658,15 @@@ static void wt_status_collect_untracked
        if (s->show_untracked_files != SHOW_ALL_UNTRACKED_FILES)
                dir.flags |=
                        DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
-       if (s->show_ignored_files)
+       if (s->show_ignored_mode) {
                dir.flags |= DIR_SHOW_IGNORED_TOO;
-       else
+               if (s->show_ignored_mode == SHOW_MATCHING_IGNORED)
+                       dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING;
+       } else {
                dir.untracked = the_index.untracked;
+       }
        setup_standard_excludes(&dir);
  
        fill_directory(&dir, &the_index, &s->pathspec);
@@@ -949,7 -954,7 +954,7 @@@ static void wt_longstatus_print_verbose
        const char *c = color(WT_STATUS_HEADER, s);
  
        init_revisions(&rev, NULL);
 -      DIFF_OPT_SET(&rev.diffopt, ALLOW_TEXTCONV);
 +      rev.diffopt.flags.allow_textconv = 1;
        rev.diffopt.ita_invisible_in_index = 1;
  
        memset(&opt, 0, sizeof(opt));
@@@ -1449,7 -1454,7 +1454,7 @@@ static void wt_status_get_detached_from
                return;
        }
  
 -      if (dwim_ref(cb.buf.buf, cb.buf.len, oid.hash, &ref) == 1 &&
 +      if (dwim_ref(cb.buf.buf, cb.buf.len, &oid, &ref) == 1 &&
            /* sha1 is a commit? match without further lookup */
            (!oidcmp(&cb.noid, &oid) ||
             /* perhaps sha1 is a tag, try to dereference to a commit */
@@@ -1619,7 -1624,7 +1624,7 @@@ static void wt_longstatus_print(struct 
        }
        if (s->show_untracked_files) {
                wt_longstatus_print_other(s, &s->untracked, _("Untracked files"), "add");
-               if (s->show_ignored_files)
+               if (s->show_ignored_mode)
                        wt_longstatus_print_other(s, &s->ignored, _("Ignored files"), "add -f");
                if (advice_status_u_option && 2000 < s->untracked_in_ms) {
                        status_printf_ln(s, GIT_COLOR_NORMAL, "%s", "");
@@@ -2263,8 -2268,8 +2268,8 @@@ int has_unstaged_changes(int ignore_sub
  
        init_revisions(&rev_info, NULL);
        if (ignore_submodules)
 -              DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 -      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +              rev_info.diffopt.flags.ignore_submodules = 1;
 +      rev_info.diffopt.flags.quick = 1;
        diff_setup_done(&rev_info.diffopt);
        result = run_diff_files(&rev_info, 0);
        return diff_result_code(&rev_info.diffopt, result);
@@@ -2283,8 -2288,8 +2288,8 @@@ int has_uncommitted_changes(int ignore_
  
        init_revisions(&rev_info, NULL);
        if (ignore_submodules)
 -              DIFF_OPT_SET(&rev_info.diffopt, IGNORE_SUBMODULES);
 -      DIFF_OPT_SET(&rev_info.diffopt, QUICK);
 +              rev_info.diffopt.flags.ignore_submodules = 1;
 +      rev_info.diffopt.flags.quick = 1;
        add_head_to_pending(&rev_info);
        diff_setup_done(&rev_info.diffopt);
        result = run_diff_index(&rev_info, 1);
   */
  int require_clean_work_tree(const char *action, const char *hint, int ignore_submodules, int gently)
  {
 -      struct lock_file *lock_file = xcalloc(1, sizeof(*lock_file));
 +      struct lock_file lock_file = LOCK_INIT;
        int err = 0, fd;
  
 -      fd = hold_locked_index(lock_file, 0);
 +      fd = hold_locked_index(&lock_file, 0);
        refresh_cache(REFRESH_QUIET);
        if (0 <= fd)
 -              update_index_if_able(&the_index, lock_file);
 -      rollback_lock_file(lock_file);
 +              update_index_if_able(&the_index, &lock_file);
 +      rollback_lock_file(&lock_file);
  
        if (has_unstaged_changes(ignore_submodules)) {
                /* TRANSLATORS: the action is e.g. "pull with rebase" */