Merge branch 'mm/add-u-A-sans-pathspec'
authorJunio C Hamano <gitster@pobox.com>
Mon, 4 Feb 2013 18:25:13 +0000 (10:25 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 4 Feb 2013 18:25:14 +0000 (10:25 -0800)
Forbid "git add -u" and "git add -A" without pathspec run from a
subdirectory, to train people to type "." (or ":/") to make the
choice of default does not matter.

* mm/add-u-A-sans-pathspec:
add: warn when -u or -A is used without pathspec

1  2 
builtin/add.c
diff --combined builtin/add.c
index 7cb6cca56dc58d02d075bf6042115f00265a9b68,3bcf4c202e826152b24d46b68734dd67b46dc308..7738025a2e56ea3fbd256666998040981360e5ab
@@@ -6,7 -6,6 +6,7 @@@
  #include "cache.h"
  #include "builtin.h"
  #include "dir.h"
 +#include "pathspec.h"
  #include "exec_cmd.h"
  #include "cache-tree.h"
  #include "run-command.h"
@@@ -98,6 -97,39 +98,6 @@@ int add_files_to_cache(const char *pref
        return !!data.add_errors;
  }
  
 -static void fill_pathspec_matches(const char **pathspec, char *seen, int specs)
 -{
 -      int num_unmatched = 0, i;
 -
 -      /*
 -       * Since we are walking the index as if we were walking the directory,
 -       * we have to mark the matched pathspec as seen; otherwise we will
 -       * mistakenly think that the user gave a pathspec that did not match
 -       * anything.
 -       */
 -      for (i = 0; i < specs; i++)
 -              if (!seen[i])
 -                      num_unmatched++;
 -      if (!num_unmatched)
 -              return;
 -      for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 -              match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
 -      }
 -}
 -
 -static char *find_used_pathspec(const char **pathspec)
 -{
 -      char *seen;
 -      int i;
 -
 -      for (i = 0; pathspec[i];  i++)
 -              ; /* just counting */
 -      seen = xcalloc(i, 1);
 -      fill_pathspec_matches(pathspec, seen, i);
 -      return seen;
 -}
 -
  static char *prune_directory(struct dir_struct *dir, const char **pathspec, int prefix)
  {
        char *seen;
                        *dst++ = entry;
        }
        dir->nr = dst - dir->entries;
 -      fill_pathspec_matches(pathspec, seen, specs);
 +      add_pathspec_matches_against_index(pathspec, seen, specs);
        return seen;
  }
  
 +/*
 + * Checks the index to see whether any path in pathspec refers to
 + * something inside a submodule.  If so, dies with an error message.
 + */
  static void treat_gitlinks(const char **pathspec)
  {
        int i;
        if (!pathspec || !*pathspec)
                return;
  
 -      for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 -              if (S_ISGITLINK(ce->ce_mode)) {
 -                      int len = ce_namelen(ce), j;
 -                      for (j = 0; pathspec[j]; j++) {
 -                              int len2 = strlen(pathspec[j]);
 -                              if (len2 <= len || pathspec[j][len] != '/' ||
 -                                  memcmp(ce->name, pathspec[j], len))
 -                                      continue;
 -                              if (len2 == len + 1)
 -                                      /* strip trailing slash */
 -                                      pathspec[j] = xstrndup(ce->name, len);
 -                              else
 -                                      die (_("Path '%s' is in submodule '%.*s'"),
 -                                              pathspec[j], len, ce->name);
 -                      }
 -              }
 -      }
 +      for (i = 0; pathspec[i]; i++)
 +              pathspec[i] = check_path_for_gitlink(pathspec[i]);
  }
  
  static void refresh(int verbose, const char **pathspec)
          free(seen);
  }
  
 -static const char **validate_pathspec(int argc, const char **argv, const char *prefix)
 +/*
 + * Normalizes argv relative to prefix, via get_pathspec(), and then
 + * runs die_if_path_beyond_symlink() on each path in the normalized
 + * list.
 + */
 +static const char **validate_pathspec(const char **argv, const char *prefix)
  {
        const char **pathspec = get_pathspec(prefix, argv);
  
        if (pathspec) {
                const char **p;
                for (p = pathspec; *p; p++) {
 -                      if (has_symlink_leading_path(*p, strlen(*p))) {
 -                              int len = prefix ? strlen(prefix) : 0;
 -                              die(_("'%s' is beyond a symbolic link"), *p + len);
 -                      }
 +                      die_if_path_beyond_symlink(*p, prefix);
                }
        }
  
@@@ -206,7 -248,7 +206,7 @@@ int interactive_add(int argc, const cha
        const char **pathspec = NULL;
  
        if (argc) {
 -              pathspec = validate_pathspec(argc, argv, prefix);
 +              pathspec = validate_pathspec(argv, prefix);
                if (!pathspec)
                        return -1;
        }
@@@ -321,6 -363,35 +321,35 @@@ static int add_files(struct dir_struct 
        return exit_status;
  }
  
+ static void warn_pathless_add(const char *option_name, const char *short_name) {
+       /*
+        * To be consistent with "git add -p" and most Git
+        * commands, we should default to being tree-wide, but
+        * this is not the original behavior and can't be
+        * changed until users trained themselves not to type
+        * "git add -u" or "git add -A". For now, we warn and
+        * keep the old behavior. Later, this warning can be
+        * turned into a die(...), and eventually we may
+        * reallow the command with a new behavior.
+        */
+       warning(_("The behavior of 'git add %s (or %s)' with no path argument from a\n"
+                 "subdirectory of the tree will change in Git 2.0 and should not be used anymore.\n"
+                 "To add content for the whole tree, run:\n"
+                 "\n"
+                 "  git add %s :/\n"
+                 "  (or git add %s :/)\n"
+                 "\n"
+                 "To restrict the command to the current directory, run:\n"
+                 "\n"
+                 "  git add %s .\n"
+                 "  (or git add %s .)\n"
+                 "\n"
+                 "With the current Git version, the command is restricted to the current directory."),
+               option_name, short_name,
+               option_name, short_name,
+               option_name, short_name);
+ }
  int cmd_add(int argc, const char **argv, const char *prefix)
  {
        int exit_status = 0;
        int add_new_files;
        int require_pathspec;
        char *seen = NULL;
+       const char *option_with_implicit_dot = NULL;
+       const char *short_option_with_implicit_dot = NULL;
  
        git_config(add_config, NULL);
  
                die(_("-A and -u are mutually incompatible"));
        if (!show_only && ignore_missing)
                die(_("Option --ignore-missing can only be used together with --dry-run"));
-       if ((addremove || take_worktree_changes) && !argc) {
+       if (addremove) {
+               option_with_implicit_dot = "--all";
+               short_option_with_implicit_dot = "-A";
+       }
+       if (take_worktree_changes) {
+               option_with_implicit_dot = "--update";
+               short_option_with_implicit_dot = "-u";
+       }
+       if (option_with_implicit_dot && !argc) {
                static const char *here[2] = { ".", NULL };
+               if (prefix)
+                       warn_pathless_add(option_with_implicit_dot,
+                                         short_option_with_implicit_dot);
                argc = 1;
                argv = here;
        }
                fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
                return 0;
        }
 -      pathspec = validate_pathspec(argc, argv, prefix);
 +      pathspec = validate_pathspec(argv, prefix);
  
        if (read_cache() < 0)
                die(_("index file corrupt"));
  
                path_exclude_check_init(&check, &dir);
                if (!seen)
 -                      seen = find_used_pathspec(pathspec);
 +                      seen = find_pathspecs_matching_against_index(pathspec);
                for (i = 0; pathspec[i]; i++) {
                        if (!seen[i] && pathspec[i][0]
                            && !file_exists(pathspec[i])) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
 -                                      if (path_excluded(&check, pathspec[i], -1, &dtype))
 +                                      if (is_path_excluded(&check, pathspec[i], -1, &dtype))
                                                dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
                                } else
                                        die(_("pathspec '%s' did not match any files"),