Merge branch 'jl/submodule-mv'
authorJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2013 21:36:15 +0000 (14:36 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2013 21:36:15 +0000 (14:36 -0700)
"git mv A B" when moving a submodule A does "the right thing",
inclusing relocating its working tree and adjusting the paths in
the .gitmodules file.

* jl/submodule-mv: (53 commits)
rm: delete .gitmodules entry of submodules removed from the work tree
mv: update the path entry in .gitmodules for moved submodules
submodule.c: add .gitmodules staging helper functions
mv: move submodules using a gitfile
mv: move submodules together with their work trees
rm: do not set a variable twice without intermediate reading.
t6131 - skip tests if on case-insensitive file system
parse_pathspec: accept :(icase)path syntax
pathspec: support :(glob) syntax
pathspec: make --literal-pathspecs disable pathspec magic
pathspec: support :(literal) syntax for noglob pathspec
kill limit_pathspec_to_literal() as it's only used by parse_pathspec()
parse_pathspec: preserve prefix length via PATHSPEC_PREFIX_ORIGIN
parse_pathspec: make sure the prefix part is wildcard-free
rename field "raw" to "_raw" in struct pathspec
tree-diff: remove the use of pathspec's raw[] in follow-rename codepath
remove match_pathspec() in favor of match_pathspec_depth()
remove init_pathspec() in favor of parse_pathspec()
remove diff_tree_{setup,release}_paths
convert common_prefix() to use struct pathspec
...

38 files changed:
1  2 
Documentation/git.txt
builtin/add.c
builtin/blame.c
builtin/check-ignore.c
builtin/checkout.c
builtin/clean.c
builtin/commit.c
builtin/grep.c
builtin/log.c
builtin/ls-files.c
builtin/ls-tree.c
builtin/mv.c
builtin/reset.c
builtin/rm.c
builtin/update-index.c
cache.h
combine-diff.c
commit.h
diff-lib.c
diff.h
dir.c
git.c
line-log.c
merge-recursive.c
path.c
pathspec.c
read-cache.c
rerere.c
resolve-undo.c
revision.c
setup.c
submodule.c
t/t0008-ignores.sh
t/t7400-submodule-basic.sh
tree-walk.c
tree.c
wt-status.c
wt-status.h
diff --combined Documentation/git.txt
index 83edf308b1f7b9779bfc409ab518a812eb231275,2c1f6f5f53cf372e9fbaa02a9433e6cb3b416332..c4f0ed59576b877a5eab2e36aa874d8c572bb905
@@@ -43,16 -43,9 +43,16 @@@ unreleased) version of Git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
 -* link:v1.8.3.2/git.html[documentation for release 1.8.3.2]
 +* link:v1.8.4/git.html[documentation for release 1.8.4]
  
  * release notes for
 +  link:RelNotes/1.8.4.txt[1.8.4].
 +
 +* link:v1.8.3.4/git.html[documentation for release 1.8.3.4]
 +
 +* release notes for
 +  link:RelNotes/1.8.3.4.txt[1.8.3.4],
 +  link:RelNotes/1.8.3.3.txt[1.8.3.3],
    link:RelNotes/1.8.3.2.txt[1.8.3.2],
    link:RelNotes/1.8.3.1.txt[1.8.3.1],
    link:RelNotes/1.8.3.txt[1.8.3].
@@@ -457,10 -450,25 +457,25 @@@ help ...`
        linkgit:git-replace[1] for more information.
  
  --literal-pathspecs::
-       Treat pathspecs literally, rather than as glob patterns. This is
-       equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
+       Treat pathspecs literally (i.e. no globbing, no pathspec magic).
+       This is equivalent to setting the `GIT_LITERAL_PATHSPECS` environment
        variable to `1`.
  
+ --glob-pathspecs:
+       Add "glob" magic to all pathspec. This is equivalent to setting
+       the `GIT_GLOB_PATHSPECS` environment variable to `1`. Disabling
+       globbing on individual pathspecs can be done using pathspec
+       magic ":(literal)"
+ --noglob-pathspecs:
+       Add "literal" magic to all pathspec. This is equivalent to setting
+       the `GIT_NOGLOB_PATHSPECS` environment variable to `1`. Enabling
+       globbing on individual pathspecs can be done using pathspec
+       magic ":(glob)"
+ --icase-pathspecs:
+       Add "icase" magic to all pathspec. This is equivalent to setting
+       the `GIT_ICASE_PATHSPECS` environment variable to `1`.
  
  GIT COMMANDS
  ------------
@@@ -823,7 -831,7 +838,7 @@@ for further details
  'GIT_FLUSH'::
        If this environment variable is set to "1", then commands such
        as 'git blame' (in incremental mode), 'git rev-list', 'git log',
 -      'git check-attr', 'git check-ignore', and 'git whatchanged' will
 +      'git check-attr' and 'git check-ignore' will
        force a flush of the output stream after each record have been
        flushed. If this
        variable is set to "0", the output of these commands will be done
@@@ -867,6 -875,18 +882,18 @@@ GIT_LITERAL_PATHSPECS:
        literal paths to Git (e.g., paths previously given to you by
        `git ls-tree`, `--raw` diff output, etc).
  
+ GIT_GLOB_PATHSPECS::
+       Setting this variable to `1` will cause Git to treat all
+       pathspecs as glob patterns (aka "glob" magic).
+ GIT_NOGLOB_PATHSPECS::
+       Setting this variable to `1` will cause Git to treat all
+       pathspecs as literal (aka "literal" magic).
+ GIT_ICASE_PATHSPECS::
+       Setting this variable to `1` will cause Git to treat all
+       pathspecs as case-insensitive.
  
  Discussion[[Discussion]]
  ------------------------
diff --combined builtin/add.c
index 8266a9cb7091c046f63c7b0137daf0bce0180a12,9d52fc7915a5061a757ce937f24e894d65cb0dff..ae0bdc78bbf47094a2f0656ef8f3f561e60dc072
@@@ -166,14 -166,16 +166,16 @@@ static void update_callback(struct diff
        }
  }
  
- static void update_files_in_cache(const char *prefix, const char **pathspec,
+ static void update_files_in_cache(const char *prefix,
+                                 const struct pathspec *pathspec,
                                  struct update_callback_data *data)
  {
        struct rev_info rev;
  
        init_revisions(&rev, prefix);
        setup_revisions(0, NULL, &rev, NULL);
-       init_pathspec(&rev.prune_data, pathspec);
+       if (pathspec)
+               copy_pathspec(&rev.prune_data, pathspec);
        rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
        rev.diffopt.format_callback = update_callback;
        rev.diffopt.format_callback_data = data;
        run_diff_files(&rev, DIFF_RACY_IS_MODIFIED);
  }
  
- int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
+ int add_files_to_cache(const char *prefix,
+                      const struct pathspec *pathspec, int flags)
  {
        struct update_callback_data data;
  
  }
  
  #define WARN_IMPLICIT_DOT (1u << 0)
- static char *prune_directory(struct dir_struct *dir, const char **pathspec,
+ static char *prune_directory(struct dir_struct *dir, struct pathspec *pathspec,
                             int prefix, unsigned flag)
  {
        char *seen;
-       int i, specs;
+       int i;
        struct dir_entry **src, **dst;
  
-       for (specs = 0; pathspec[specs];  specs++)
-               /* nothing */;
-       seen = xcalloc(specs, 1);
+       seen = xcalloc(pathspec->nr, 1);
  
        src = dst = dir->entries;
        i = dir->nr;
        while (--i >= 0) {
                struct dir_entry *entry = *src++;
-               if (match_pathspec(pathspec, entry->name, entry->len,
-                                  prefix, seen))
+               if (match_pathspec_depth(pathspec, entry->name, entry->len,
+                                        prefix, seen))
                        *dst++ = entry;
                else if (flag & WARN_IMPLICIT_DOT)
                        /*
                        warn_pathless_add();
        }
        dir->nr = dst - dir->entries;
-       add_pathspec_matches_against_index(pathspec, seen, specs);
+       add_pathspec_matches_against_index(pathspec, seen);
        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; pathspec[i]; i++)
-               pathspec[i] = check_path_for_gitlink(pathspec[i]);
- }
- static void refresh(int verbose, const char **pathspec)
+ static void refresh(int verbose, const struct pathspec *pathspec)
  {
        char *seen;
-       int i, specs;
+       int i;
  
-       for (specs = 0; pathspec[specs];  specs++)
-               /* nothing */;
-       seen = xcalloc(specs, 1);
+       seen = xcalloc(pathspec->nr, 1);
        refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET,
                      pathspec, seen, _("Unstaged changes after refreshing the index:"));
-       for (i = 0; i < specs; i++) {
+       for (i = 0; i < pathspec->nr; i++) {
                if (!seen[i])
-                       die(_("pathspec '%s' did not match any files"), pathspec[i]);
+                       die(_("pathspec '%s' did not match any files"),
+                           pathspec->items[i].match);
        }
          free(seen);
  }
  
- /*
-  * 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++) {
-                       die_if_path_beyond_symlink(*p, prefix);
-               }
-       }
-       return pathspec;
- }
  int run_add_interactive(const char *revision, const char *patch_mode,
-                       const char **pathspec)
+                       const struct pathspec *pathspec)
  {
-       int status, ac, pc = 0;
+       int status, ac, i;
        const char **args;
  
-       if (pathspec)
-               while (pathspec[pc])
-                       pc++;
-       args = xcalloc(sizeof(const char *), (pc + 5));
+       args = xcalloc(sizeof(const char *), (pathspec->nr + 6));
        ac = 0;
        args[ac++] = "add--interactive";
        if (patch_mode)
        if (revision)
                args[ac++] = revision;
        args[ac++] = "--";
-       if (pc) {
-               memcpy(&(args[ac]), pathspec, sizeof(const char *) * pc);
-               ac += pc;
-       }
-       args[ac] = NULL;
+       for (i = 0; i < pathspec->nr; i++)
+               /* pass original pathspec, to be re-parsed */
+               args[ac++] = pathspec->items[i].original;
  
        status = run_command_v_opt(args, RUN_GIT_CMD);
        free(args);
  
  int interactive_add(int argc, const char **argv, const char *prefix, int patch)
  {
-       const char **pathspec = NULL;
+       struct pathspec pathspec;
  
-       if (argc) {
-               pathspec = validate_pathspec(argv, prefix);
-               if (!pathspec)
-                       return -1;
-       }
+       /*
+        * git-add--interactive itself does not parse pathspec. It
+        * simply passes the pathspec to other builtin commands. Let's
+        * hope all of them support all magic, or we'll need to limit
+        * the magic here.
+        */
+       parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
+                      PATHSPEC_PREFER_FULL |
+                      PATHSPEC_SYMLINK_LEADING_PATH |
+                      PATHSPEC_PREFIX_ORIGIN,
+                      prefix, argv);
  
        return run_add_interactive(NULL,
                                   patch ? "--patch" : NULL,
-                                  pathspec);
+                                  &pathspec);
  }
  
  static int edit_patch(int argc, const char **argv, const char *prefix)
  
        argc = setup_revisions(argc, argv, &rev, NULL);
        rev.diffopt.output_format = DIFF_FORMAT_PATCH;
 +      rev.diffopt.use_color = 0;
        DIFF_OPT_SET(&rev.diffopt, IGNORE_DIRTY_SUBMODULES);
        out = open(file, O_CREAT | O_WRONLY, 0666);
        if (out < 0)
@@@ -446,7 -411,7 +412,7 @@@ int cmd_add(int argc, const char **argv
  {
        int exit_status = 0;
        int newfd;
-       const char **pathspec;
+       struct pathspec pathspec;
        struct dir_struct dir;
        int flags;
        int add_new_files;
                fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n"));
                return 0;
        }
-       pathspec = validate_pathspec(argv, prefix);
  
        if (read_cache() < 0)
                die(_("index file corrupt"));
-       treat_gitlinks(pathspec);
+       /*
+        * Check the "pathspec '%s' did not match any files" block
+        * below before enabling new magic.
+        */
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_FULL |
+                      PATHSPEC_SYMLINK_LEADING_PATH |
+                      PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE,
+                      prefix, argv);
  
        if (add_new_files) {
                int baselen;
+               struct pathspec empty_pathspec;
  
                /* Set up the default git porcelain excludes */
                memset(&dir, 0, sizeof(dir));
                        setup_standard_excludes(&dir);
                }
  
+               memset(&empty_pathspec, 0, sizeof(empty_pathspec));
                /* This picks up the paths that are not tracked */
-               baselen = fill_directory(&dir, implicit_dot ? NULL : pathspec);
-               if (pathspec)
-                       seen = prune_directory(&dir, pathspec, baselen,
+               baselen = fill_directory(&dir, implicit_dot ? &empty_pathspec : &pathspec);
+               if (pathspec.nr)
+                       seen = prune_directory(&dir, &pathspec, baselen,
                                        implicit_dot ? WARN_IMPLICIT_DOT : 0);
        }
  
        if (refresh_only) {
-               refresh(verbose, pathspec);
+               refresh(verbose, &pathspec);
                goto finish;
        }
        if (implicit_dot && prefix)
                refresh_cache(REFRESH_QUIET);
  
-       if (pathspec) {
+       if (pathspec.nr) {
                int i;
  
                if (!seen)
-                       seen = find_pathspecs_matching_against_index(pathspec);
-               for (i = 0; pathspec[i]; i++) {
-                       if (!seen[i] && pathspec[i][0]
-                           && !file_exists(pathspec[i])) {
+                       seen = find_pathspecs_matching_against_index(&pathspec);
+               /*
+                * file_exists() assumes exact match
+                */
+               GUARD_PATHSPEC(&pathspec,
+                              PATHSPEC_FROMTOP |
+                              PATHSPEC_LITERAL |
+                              PATHSPEC_GLOB |
+                              PATHSPEC_ICASE);
+               for (i = 0; i < pathspec.nr; i++) {
+                       const char *path = pathspec.items[i].match;
+                       if (!seen[i] &&
+                           ((pathspec.items[i].magic &
+                             (PATHSPEC_GLOB | PATHSPEC_ICASE)) ||
+                            !file_exists(path))) {
                                if (ignore_missing) {
                                        int dtype = DT_UNKNOWN;
-                                       if (is_excluded(&dir, pathspec[i], &dtype))
-                                               dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
+                                       if (is_excluded(&dir, path, &dtype))
+                                               dir_add_ignored(&dir, path, pathspec.items[i].len);
                                } else
                                        die(_("pathspec '%s' did not match any files"),
-                                           pathspec[i]);
+                                           pathspec.items[i].original);
                        }
                }
                free(seen);
                 */
                update_data.implicit_dot = prefix;
                update_data.implicit_dot_len = strlen(prefix);
-               pathspec = NULL;
+               free_pathspec(&pathspec);
+               memset(&pathspec, 0, sizeof(pathspec));
        }
        update_data.flags = flags & ~ADD_CACHE_IMPLICIT_DOT;
-       update_files_in_cache(prefix, pathspec, &update_data);
+       update_files_in_cache(prefix, &pathspec, &update_data);
  
        exit_status |= !!update_data.add_errors;
        if (add_new_files)
diff --combined builtin/blame.c
index 00927e0347ce884392bd51f8bc5d886248e3d6e4,56e3d6b7b4718b05f655da97155afeec074d30a1..6da7233968b6563d85d0689e3b1cae3ebe4027fb
@@@ -22,7 -22,6 +22,7 @@@
  #include "utf8.h"
  #include "userdiff.h"
  #include "line-range.h"
 +#include "line-log.h"
  
  static char blame_usage[] = N_("git blame [options] [rev-opts] [rev] [--] file");
  
@@@ -409,7 -408,7 +409,7 @@@ static struct origin *find_origin(struc
        paths[0] = origin->path;
        paths[1] = NULL;
  
-       diff_tree_setup_paths(paths, &diff_opts);
+       parse_pathspec(&diff_opts.pathspec, PATHSPEC_ALL_MAGIC, 0, "", paths);
        diff_setup_done(&diff_opts);
  
        if (is_null_sha1(origin->commit->object.sha1))
                }
        }
        diff_flush(&diff_opts);
-       diff_tree_release_paths(&diff_opts);
+       free_pathspec(&diff_opts.pathspec);
        if (porigin) {
                /*
                 * Create a freestanding copy that is not part of
@@@ -487,15 -486,12 +487,12 @@@ static struct origin *find_rename(struc
        struct origin *porigin = NULL;
        struct diff_options diff_opts;
        int i;
-       const char *paths[2];
  
        diff_setup(&diff_opts);
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.detect_rename = DIFF_DETECT_RENAME;
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        diff_opts.single_follow = origin->path;
-       paths[0] = NULL;
-       diff_tree_setup_paths(paths, &diff_opts);
        diff_setup_done(&diff_opts);
  
        if (is_null_sha1(origin->commit->object.sha1))
                }
        }
        diff_flush(&diff_opts);
-       diff_tree_release_paths(&diff_opts);
+       free_pathspec(&diff_opts.pathspec);
        return porigin;
  }
  
@@@ -1065,7 -1061,6 +1062,6 @@@ static int find_copy_in_parent(struct s
                               int opt)
  {
        struct diff_options diff_opts;
-       const char *paths[1];
        int i, j;
        int retval;
        struct blame_list *blame_list;
        DIFF_OPT_SET(&diff_opts, RECURSIVE);
        diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
  
-       paths[0] = NULL;
-       diff_tree_setup_paths(paths, &diff_opts);
        diff_setup_done(&diff_opts);
  
        /* Try "find copies harder" on new path if requested;
        }
        reset_scanned_flag(sb);
        diff_flush(&diff_opts);
-       diff_tree_release_paths(&diff_opts);
+       free_pathspec(&diff_opts.pathspec);
        return retval;
  }
  
@@@ -1938,6 -1931,18 +1932,6 @@@ static const char *add_prefix(const cha
        return prefix_path(prefix, prefix ? strlen(prefix) : 0, path);
  }
  
 -/*
 - * Parsing of -L option
 - */
 -static void prepare_blame_range(struct scoreboard *sb,
 -                              const char *bottomtop,
 -                              long lno,
 -                              long *bottom, long *top)
 -{
 -      if (parse_range_arg(bottomtop, nth_line_cb, sb, lno, bottom, top, sb->path))
 -              usage(blame_usage);
 -}
 -
  static int git_blame_config(const char *var, const char *value, void *cb)
  {
        if (!strcmp(var, "blame.showroot")) {
@@@ -2234,27 -2239,38 +2228,27 @@@ static int blame_move_callback(const st
        return 0;
  }
  
 -static int blame_bottomtop_callback(const struct option *option, const char *arg, int unset)
 -{
 -      const char **bottomtop = option->value;
 -      if (!arg)
 -              return -1;
 -      if (*bottomtop)
 -              die("More than one '-L n,m' option given");
 -      *bottomtop = arg;
 -      return 0;
 -}
 -
  int cmd_blame(int argc, const char **argv, const char *prefix)
  {
        struct rev_info revs;
        const char *path;
        struct scoreboard sb;
        struct origin *o;
 -      struct blame_entry *ent;
 -      long dashdash_pos, bottom, top, lno;
 +      struct blame_entry *ent = NULL;
 +      long dashdash_pos, lno;
        const char *final_commit_name = NULL;
        enum object_type type;
  
 -      static const char *bottomtop = NULL;
 +      static struct string_list range_list;
        static int output_option = 0, opt = 0;
        static int show_stats = 0;
        static const char *revs_file = NULL;
        static const char *contents_from = NULL;
        static const struct option options[] = {
 -              OPT_BOOLEAN(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
 -              OPT_BOOLEAN('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
 -              OPT_BOOLEAN(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
 -              OPT_BOOLEAN(0, "show-stats", &show_stats, N_("Show work cost statistics")),
 +              OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")),
 +              OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")),
 +              OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")),
 +              OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")),
                OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE),
                OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME),
                OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER),
                OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")),
                { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback },
                { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback },
 -              OPT_CALLBACK('L', NULL, &bottomtop, N_("n,m"), N_("Process only line range n,m, counting from 1"), blame_bottomtop_callback),
 +              OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")),
                OPT__ABBREV(&abbrev),
                OPT_END()
        };
  
        struct parse_opt_ctx_t ctx;
        int cmd_is_annotate = !strcmp(argv[0], "annotate");
 +      struct range_set ranges;
 +      unsigned int range_i;
 +      long anchor;
  
        git_config(git_blame_config, NULL);
        init_revisions(&revs, NULL);
@@@ -2473,48 -2486,22 +2467,48 @@@ parse_done
        num_read_blob++;
        lno = prepare_lines(&sb);
  
 -      bottom = top = 0;
 -      if (bottomtop)
 -              prepare_blame_range(&sb, bottomtop, lno, &bottom, &top);
 -      if (bottom < 1)
 -              bottom = 1;
 -      if (top < 1)
 -              top = lno;
 -      bottom--;
 -      if (lno < top || lno < bottom)
 -              die("file %s has only %lu lines", path, lno);
 -
 -      ent = xcalloc(1, sizeof(*ent));
 -      ent->lno = bottom;
 -      ent->num_lines = top - bottom;
 -      ent->suspect = o;
 -      ent->s_lno = bottom;
 +      if (lno && !range_list.nr)
 +              string_list_append(&range_list, xstrdup("1"));
 +
 +      anchor = 1;
 +      range_set_init(&ranges, range_list.nr);
 +      for (range_i = 0; range_i < range_list.nr; ++range_i) {
 +              long bottom, top;
 +              if (parse_range_arg(range_list.items[range_i].string,
 +                                  nth_line_cb, &sb, lno, anchor,
 +                                  &bottom, &top, sb.path))
 +                      usage(blame_usage);
 +              if (lno < top || ((lno || bottom) && lno < bottom))
 +                      die("file %s has only %lu lines", path, lno);
 +              if (bottom < 1)
 +                      bottom = 1;
 +              if (top < 1)
 +                      top = lno;
 +              bottom--;
 +              range_set_append_unsafe(&ranges, bottom, top);
 +              anchor = top + 1;
 +      }
 +      sort_and_merge_range_set(&ranges);
 +
 +      for (range_i = ranges.nr; range_i > 0; --range_i) {
 +              const struct range *r = &ranges.ranges[range_i - 1];
 +              long bottom = r->start;
 +              long top = r->end;
 +              struct blame_entry *next = ent;
 +              ent = xcalloc(1, sizeof(*ent));
 +              ent->lno = bottom;
 +              ent->num_lines = top - bottom;
 +              ent->suspect = o;
 +              ent->s_lno = bottom;
 +              ent->next = next;
 +              if (next)
 +                      next->prev = ent;
 +              origin_incref(o);
 +      }
 +      origin_decref(o);
 +
 +      range_set_release(&ranges);
 +      string_list_clear(&range_list, 0);
  
        sb.ent = ent;
        sb.path = path;
diff --combined builtin/check-ignore.c
index 25aa2a5f4c7ff0d0394e4df8e5e761ae916055f6,70537c8d304e1a1940dd4a15d354b45597f6b530..e2a1cef6843b7db2f7ffb4b9c89003397948c748
@@@ -12,18 -12,18 +12,18 @@@ static const char * const check_ignore_
  NULL
  };
  
 -static int null_term_line;
 +static int nul_term_line;
  
  static const struct option check_ignore_options[] = {
        OPT__QUIET(&quiet, N_("suppress progress reporting")),
        OPT__VERBOSE(&verbose, N_("be verbose")),
        OPT_GROUP(""),
 -      OPT_BOOLEAN(0, "stdin", &stdin_paths,
 -                  N_("read file names from stdin")),
 -      OPT_BOOLEAN('z', NULL, &null_term_line,
 -                  N_("input paths are terminated by a null character")),
 -      OPT_BOOLEAN('n', "non-matching", &show_non_matching,
 -                  N_("show non-matching input paths")),
 +      OPT_BOOL(0, "stdin", &stdin_paths,
 +               N_("read file names from stdin")),
 +      OPT_BOOL('z', NULL, &nul_term_line,
 +               N_("terminate input and output records by a NUL character")),
 +      OPT_BOOL('n', "non-matching", &show_non_matching,
 +               N_("show non-matching input paths")),
        OPT_END()
  };
  
@@@ -31,7 -31,7 +31,7 @@@ static void output_exclude(const char *
  {
        char *bang  = (exclude && exclude->flags & EXC_FLAG_NEGATIVE)  ? "!" : "";
        char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : "";
 -      if (!null_term_line) {
 +      if (!nul_term_line) {
                if (!verbose) {
                        write_name_quoted(path, stdout, '\n');
                } else {
  }
  
  static int check_ignore(struct dir_struct *dir,
-                       const char *prefix, const char **pathspec)
+                       const char *prefix, int argc, const char **argv)
  {
-       const char *path, *full_path;
+       const char *full_path;
        char *seen;
        int num_ignored = 0, dtype = DT_UNKNOWN, i;
        struct exclude *exclude;
+       struct pathspec pathspec;
  
-       if (!pathspec || !*pathspec) {
+       if (!argc) {
                if (!quiet)
                        fprintf(stderr, "no pathspec given.\n");
                return 0;
        }
  
+       /*
+        * check-ignore just needs paths. Magic beyond :/ is really
+        * irrelevant.
+        */
+       parse_pathspec(&pathspec,
+                      PATHSPEC_ALL_MAGIC & ~PATHSPEC_FROMTOP,
+                      PATHSPEC_SYMLINK_LEADING_PATH |
+                      PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE |
+                      PATHSPEC_KEEP_ORDER,
+                      prefix, argv);
        /*
         * look for pathspecs matching entries in the index, since these
         * should not be ignored, in order to be consistent with
         * 'git status', 'git add' etc.
         */
-       seen = find_pathspecs_matching_against_index(pathspec);
-       for (i = 0; pathspec[i]; i++) {
-               path = pathspec[i];
-               full_path = prefix_path(prefix, prefix
-                                       ? strlen(prefix) : 0, path);
-               full_path = check_path_for_gitlink(full_path);
-               die_if_path_beyond_symlink(full_path, prefix);
+       seen = find_pathspecs_matching_against_index(&pathspec);
+       for (i = 0; i < pathspec.nr; i++) {
+               full_path = pathspec.items[i].match;
                exclude = NULL;
                if (!seen[i]) {
                        exclude = last_exclude_matching(dir, full_path, &dtype);
                }
                if (!quiet && (exclude || show_non_matching))
-                       output_exclude(path, exclude);
+                       output_exclude(pathspec.items[i].original, exclude);
                if (exclude)
                        num_ignored++;
        }
@@@ -107,7 -115,7 +115,7 @@@ static int check_ignore_stdin_paths(str
  {
        struct strbuf buf, nbuf;
        char *pathspec[2] = { NULL, NULL };
 -      int line_termination = null_term_line ? 0 : '\n';
 +      int line_termination = nul_term_line ? 0 : '\n';
        int num_ignored = 0;
  
        strbuf_init(&buf, 0);
                        strbuf_swap(&buf, &nbuf);
                }
                pathspec[0] = buf.buf;
-               num_ignored += check_ignore(dir, prefix, (const char **)pathspec);
+               num_ignored += check_ignore(dir, prefix,
+                                           1, (const char **)pathspec);
                maybe_flush_or_die(stdout, "check-ignore to stdout");
        }
        strbuf_release(&buf);
@@@ -142,7 -151,7 +151,7 @@@ int cmd_check_ignore(int argc, const ch
                if (argc > 0)
                        die(_("cannot specify pathnames with --stdin"));
        } else {
 -              if (null_term_line)
 +              if (nul_term_line)
                        die(_("-z only makes sense with --stdin"));
                if (argc == 0)
                        die(_("no path specified"));
        if (stdin_paths) {
                num_ignored = check_ignore_stdin_paths(&dir, prefix);
        } else {
-               num_ignored = check_ignore(&dir, prefix, argv);
+               num_ignored = check_ignore(&dir, prefix, argc, argv);
                maybe_flush_or_die(stdout, "ignore to stdout");
        }
  
diff --combined builtin/checkout.c
index ed39cecf9a80e4e770684b7c5ceb2d117ffbb281,7ea1100338cd79a40065700033b129d4031988d7..62a96a7e2fb868ab93f83d7b8df5aecf20aad3d4
@@@ -46,7 -46,7 +46,7 @@@ struct checkout_opts 
  
        int branch_exists;
        const char *prefix;
-       const char **pathspec;
+       struct pathspec pathspec;
        struct tree *source_tree;
  };
  
@@@ -83,12 -83,9 +83,9 @@@ static int update_some(const unsigned c
        return 0;
  }
  
- static int read_tree_some(struct tree *tree, const char **pathspec)
+ static int read_tree_some(struct tree *tree, const struct pathspec *pathspec)
  {
-       struct pathspec ps;
-       init_pathspec(&ps, pathspec);
-       read_tree_recursive(tree, "", 0, 0, &ps, update_some, NULL);
-       free_pathspec(&ps);
+       read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL);
  
        /* update the index with the given tree's info
         * for all args, expanding wildcards, and exit
@@@ -97,7 -94,7 +94,7 @@@
        return 0;
  }
  
 -static int skip_same_name(struct cache_entry *ce, int pos)
 +static int skip_same_name(const struct cache_entry *ce, int pos)
  {
        while (++pos < active_nr &&
               !strcmp(active_cache[pos]->name, ce->name))
        return pos;
  }
  
 -static int check_stage(int stage, struct cache_entry *ce, int pos)
 +static int check_stage(int stage, const struct cache_entry *ce, int pos)
  {
        while (pos < active_nr &&
               !strcmp(active_cache[pos]->name, ce->name)) {
                return error(_("path '%s' does not have their version"), ce->name);
  }
  
 -static int check_stages(unsigned stages, struct cache_entry *ce, int pos)
 +static int check_stages(unsigned stages, const struct cache_entry *ce, int pos)
  {
        unsigned seen = 0;
        const char *name = ce->name;
@@@ -228,6 -225,8 +225,6 @@@ static int checkout_paths(const struct 
        int flag;
        struct commit *head;
        int errs = 0;
 -      int stage = opts->writeout_stage;
 -      int merge = opts->merge;
        int newfd;
        struct lock_file *lock_file;
  
  
        if (opts->patch_mode)
                return run_add_interactive(revision, "--patch=checkout",
-                                          opts->pathspec);
+                                          &opts->pathspec);
  
        lock_file = xcalloc(1, sizeof(struct lock_file));
  
        newfd = hold_locked_index(lock_file, 1);
-       if (read_cache_preload(opts->pathspec) < 0)
+       if (read_cache_preload(&opts->pathspec) < 0)
                return error(_("corrupt index file"));
  
        if (opts->source_tree)
-               read_tree_some(opts->source_tree, opts->pathspec);
+               read_tree_some(opts->source_tree, &opts->pathspec);
  
-       for (pos = 0; opts->pathspec[pos]; pos++)
-               ;
-       ps_matched = xcalloc(1, pos);
+       ps_matched = xcalloc(1, opts->pathspec.nr);
  
        /*
         * Make sure all pathspecs participated in locating the paths
                 * match_pathspec() for _all_ entries when
                 * opts->source_tree != NULL.
                 */
-               if (match_pathspec(opts->pathspec, ce->name, ce_namelen(ce),
+               if (match_pathspec_depth(&opts->pathspec, ce->name, ce_namelen(ce),
                                   0, ps_matched))
                        ce->ce_flags |= CE_MATCHED;
        }
  
-       if (report_path_error(ps_matched, opts->pathspec, opts->prefix)) {
+       if (report_path_error(ps_matched, &opts->pathspec, opts->prefix)) {
                free(ps_matched);
                return 1;
        }
  
        /* Any unmerged paths? */
        for (pos = 0; pos < active_nr; pos++) {
 -              struct cache_entry *ce = active_cache[pos];
 +              const struct cache_entry *ce = active_cache[pos];
                if (ce->ce_flags & CE_MATCHED) {
                        if (!ce_stage(ce))
                                continue;
                        if (opts->force) {
                                warning(_("path '%s' is unmerged"), ce->name);
 -                      } else if (stage) {
 -                              errs |= check_stage(stage, ce, pos);
 +                      } else if (opts->writeout_stage) {
 +                              errs |= check_stage(opts->writeout_stage, ce, pos);
                        } else if (opts->merge) {
                                errs |= check_stages((1<<2) | (1<<3), ce, pos);
                        } else {
                                errs |= checkout_entry(ce, &state, NULL);
                                continue;
                        }
 -                      if (stage)
 -                              errs |= checkout_stage(stage, ce, pos, &state);
 -                      else if (merge)
 +                      if (opts->writeout_stage)
 +                              errs |= checkout_stage(opts->writeout_stage, ce, pos, &state);
 +                      else if (opts->merge)
                                errs |= checkout_merged(pos, &state);
                        pos = skip_same_name(ce, pos) - 1;
                }
@@@ -1000,7 -997,7 +995,7 @@@ static int switch_unborn_to_new_branch(
  static int checkout_branch(struct checkout_opts *opts,
                           struct branch_info *new)
  {
-       if (opts->pathspec)
+       if (opts->pathspec.nr)
                die(_("paths cannot be used with switching branches"));
  
        if (opts->patch_mode)
@@@ -1054,8 -1051,8 +1049,8 @@@ int cmd_checkout(int argc, const char *
                           N_("create and checkout a new branch")),
                OPT_STRING('B', NULL, &opts.new_branch_force, N_("branch"),
                           N_("create/reset and checkout a branch")),
 -              OPT_BOOLEAN('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
 -              OPT_BOOLEAN(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
 +              OPT_BOOL('l', NULL, &opts.new_branch_log, N_("create reflog for new branch")),
 +              OPT_BOOL(0, "detach", &opts.force_detach, N_("detach the HEAD at named commit")),
                OPT_SET_INT('t', "track",  &opts.track, N_("set upstream info for new branch"),
                        BRANCH_TRACK_EXPLICIT),
                OPT_STRING(0, "orphan", &opts.new_orphan_branch, N_("new branch"), N_("new unparented branch")),
                OPT_SET_INT('3', "theirs", &opts.writeout_stage, N_("checkout their version for unmerged files"),
                            3),
                OPT__FORCE(&opts.force, N_("force checkout (throw away local modifications)")),
 -              OPT_BOOLEAN('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
 -              OPT_BOOLEAN(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")),
 +              OPT_BOOL('m', "merge", &opts.merge, N_("perform a 3-way merge with the new branch")),
 +              OPT_BOOL(0, "overwrite-ignore", &opts.overwrite_ignore, N_("update ignored files (default)")),
                OPT_STRING(0, "conflict", &conflict_style, N_("style"),
                           N_("conflict style (merge or diff3)")),
 -              OPT_BOOLEAN('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
 +              OPT_BOOL('p', "patch", &opts.patch_mode, N_("select hunks interactively")),
                OPT_BOOL(0, "ignore-skip-worktree-bits", &opts.ignore_skipworktree,
                         N_("do not limit pathspecs to sparse entries only")),
 -              { OPTION_BOOLEAN, 0, "guess", &dwim_new_local_branch, NULL,
 -                N_("second guess 'git checkout no-such-branch'"),
 -                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
 +              OPT_HIDDEN_BOOL(0, "guess", &dwim_new_local_branch,
 +                              N_("second guess 'git checkout no-such-branch'")),
                OPT_END(),
        };
  
        }
  
        if (argc) {
-               opts.pathspec = get_pathspec(prefix, argv);
+               /*
+                * In patch mode (opts.patch_mode != 0), we pass the
+                * pathspec to an external program, git-add--interactive.
+                * Do not accept any kind of magic that that program
+                * cannot handle. Magic mask is pretty safe to be
+                * lifted for new magic when opts.patch_mode == 0.
+                */
+               parse_pathspec(&opts.pathspec, 0,
+                              opts.patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0,
+                              prefix, argv);
  
-               if (!opts.pathspec)
+               if (!opts.pathspec.nr)
                        die(_("invalid path specification"));
  
                /*
                strbuf_release(&buf);
        }
  
-       if (opts.patch_mode || opts.pathspec)
+       if (opts.patch_mode || opts.pathspec.nr)
                return checkout_paths(&opts, new.name);
        else
                return checkout_branch(&opts, &new);
diff --combined builtin/clean.c
index 4b6fd42be7aeae55c71c7b79ee45fb73973ec0d5,d540ca4a0a862ae33222df34bad63d799941774e..615cd57caf1d4cbeafc73e7037bae81a6915df59
  #include "refs.h"
  #include "string-list.h"
  #include "quote.h"
 +#include "column.h"
 +#include "color.h"
+ #include "pathspec.h"
  
  static int force = -1; /* unset */
 +static int interactive;
 +static struct string_list del_list = STRING_LIST_INIT_DUP;
 +static unsigned int colopts;
  
  static const char *const builtin_clean_usage[] = {
 -      N_("git clean [-d] [-f] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
 +      N_("git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <paths>..."),
        NULL
  };
  
@@@ -32,112 -28,11 +33,112 @@@ static const char *msg_skip_git_dir = N
  static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n");
  static const char *msg_warn_remove_failed = N_("failed to remove %s");
  
 +static int clean_use_color = -1;
 +static char clean_colors[][COLOR_MAXLEN] = {
 +      GIT_COLOR_RESET,
 +      GIT_COLOR_NORMAL,       /* PLAIN */
 +      GIT_COLOR_BOLD_BLUE,    /* PROMPT */
 +      GIT_COLOR_BOLD,         /* HEADER */
 +      GIT_COLOR_BOLD_RED,     /* HELP */
 +      GIT_COLOR_BOLD_RED,     /* ERROR */
 +};
 +enum color_clean {
 +      CLEAN_COLOR_RESET = 0,
 +      CLEAN_COLOR_PLAIN = 1,
 +      CLEAN_COLOR_PROMPT = 2,
 +      CLEAN_COLOR_HEADER = 3,
 +      CLEAN_COLOR_HELP = 4,
 +      CLEAN_COLOR_ERROR = 5,
 +};
 +
 +#define MENU_OPTS_SINGLETON           01
 +#define MENU_OPTS_IMMEDIATE           02
 +#define MENU_OPTS_LIST_ONLY           04
 +
 +struct menu_opts {
 +      const char *header;
 +      const char *prompt;
 +      int flags;
 +};
 +
 +#define MENU_RETURN_NO_LOOP           10
 +
 +struct menu_item {
 +      char hotkey;
 +      const char *title;
 +      int selected;
 +      int (*fn)();
 +};
 +
 +enum menu_stuff_type {
 +      MENU_STUFF_TYPE_STRING_LIST = 1,
 +      MENU_STUFF_TYPE_MENU_ITEM
 +};
 +
 +struct menu_stuff {
 +      enum menu_stuff_type type;
 +      int nr;
 +      void *stuff;
 +};
 +
 +static int parse_clean_color_slot(const char *var)
 +{
 +      if (!strcasecmp(var, "reset"))
 +              return CLEAN_COLOR_RESET;
 +      if (!strcasecmp(var, "plain"))
 +              return CLEAN_COLOR_PLAIN;
 +      if (!strcasecmp(var, "prompt"))
 +              return CLEAN_COLOR_PROMPT;
 +      if (!strcasecmp(var, "header"))
 +              return CLEAN_COLOR_HEADER;
 +      if (!strcasecmp(var, "help"))
 +              return CLEAN_COLOR_HELP;
 +      if (!strcasecmp(var, "error"))
 +              return CLEAN_COLOR_ERROR;
 +      return -1;
 +}
 +
  static int git_clean_config(const char *var, const char *value, void *cb)
  {
 -      if (!strcmp(var, "clean.requireforce"))
 +      if (!prefixcmp(var, "column."))
 +              return git_column_config(var, value, "clean", &colopts);
 +
 +      /* honors the color.interactive* config variables which also
 +         applied in git-add--interactive and git-stash */
 +      if (!strcmp(var, "color.interactive")) {
 +              clean_use_color = git_config_colorbool(var, value);
 +              return 0;
 +      }
 +      if (!prefixcmp(var, "color.interactive.")) {
 +              int slot = parse_clean_color_slot(var +
 +                                                strlen("color.interactive."));
 +              if (slot < 0)
 +                      return 0;
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              color_parse(value, var, clean_colors[slot]);
 +              return 0;
 +      }
 +
 +      if (!strcmp(var, "clean.requireforce")) {
                force = !git_config_bool(var, value);
 -      return git_default_config(var, value, cb);
 +              return 0;
 +      }
 +
 +      /* inspect the color.ui config variable and others */
 +      return git_color_default_config(var, value, cb);
 +}
 +
 +static const char *clean_get_color(enum color_clean ix)
 +{
 +      if (want_color(clean_use_color))
 +              return clean_colors[ix];
 +      return "";
 +}
 +
 +static void clean_print_color(enum color_clean ix)
 +{
 +      printf("%s", clean_get_color(ix));
  }
  
  static int exclude_cb(const struct option *opt, const char *arg, int unset)
@@@ -162,7 -57,7 +163,7 @@@ static int remove_dirs(struct strbuf *p
        if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
                        !resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
                if (!quiet) {
 -                      quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
 +                      quote_path_relative(path->buf, prefix, &quoted);
                        printf(dry_run ?  _(msg_would_skip_git_dir) : _(msg_skip_git_dir),
                                        quoted.buf);
                }
                /* an empty dir could be removed even if it is unreadble */
                res = dry_run ? 0 : rmdir(path->buf);
                if (res) {
 -                      quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
 +                      quote_path_relative(path->buf, prefix, &quoted);
                        warning(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                }
                        if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone))
                                ret = 1;
                        if (gone) {
 -                              quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
 +                              quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else
                                *dir_gone = 0;
                } else {
                        res = dry_run ? 0 : unlink(path->buf);
                        if (!res) {
 -                              quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
 +                              quote_path_relative(path->buf, prefix, &quoted);
                                string_list_append(&dels, quoted.buf);
                        } else {
 -                              quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
 +                              quote_path_relative(path->buf, prefix, &quoted);
                                warning(_(msg_warn_remove_failed), quoted.buf);
                                *dir_gone = 0;
                                ret = 1;
                if (!res)
                        *dir_gone = 1;
                else {
 -                      quote_path_relative(path->buf, strlen(path->buf), &quoted, prefix);
 +                      quote_path_relative(path->buf, prefix, &quoted);
                        warning(_(msg_warn_remove_failed), quoted.buf);
                        *dir_gone = 0;
                        ret = 1;
        return ret;
  }
  
 +static void pretty_print_dels(void)
 +{
 +      struct string_list list = STRING_LIST_INIT_DUP;
 +      struct string_list_item *item;
 +      struct strbuf buf = STRBUF_INIT;
 +      const char *qname;
 +      struct column_options copts;
 +
 +      for_each_string_list_item(item, &del_list) {
 +              qname = quote_path_relative(item->string, NULL, &buf);
 +              string_list_append(&list, qname);
 +      }
 +
 +      /*
 +       * always enable column display, we only consult column.*
 +       * about layout strategy and stuff
 +       */
 +      colopts = (colopts & ~COL_ENABLE_MASK) | COL_ENABLED;
 +      memset(&copts, 0, sizeof(copts));
 +      copts.indent = "  ";
 +      copts.padding = 2;
 +      print_columns(&list, colopts, &copts);
 +      strbuf_release(&buf);
 +      string_list_clear(&list, 0);
 +}
 +
 +static void pretty_print_menus(struct string_list *menu_list)
 +{
 +      unsigned int local_colopts = 0;
 +      struct column_options copts;
 +
 +      local_colopts = COL_ENABLED | COL_ROW;
 +      memset(&copts, 0, sizeof(copts));
 +      copts.indent = "  ";
 +      copts.padding = 2;
 +      print_columns(menu_list, local_colopts, &copts);
 +}
 +
 +static void prompt_help_cmd(int singleton)
 +{
 +      clean_print_color(CLEAN_COLOR_HELP);
 +      printf_ln(singleton ?
 +                _("Prompt help:\n"
 +                  "1          - select a numbered item\n"
 +                  "foo        - select item based on unique prefix\n"
 +                  "           - (empty) select nothing") :
 +                _("Prompt help:\n"
 +                  "1          - select a single item\n"
 +                  "3-5        - select a range of items\n"
 +                  "2-3,6-9    - select multiple ranges\n"
 +                  "foo        - select item based on unique prefix\n"
 +                  "-...       - unselect specified items\n"
 +                  "*          - choose all items\n"
 +                  "           - (empty) finish selecting"));
 +      clean_print_color(CLEAN_COLOR_RESET);
 +}
 +
 +/*
 + * display menu stuff with number prefix and hotkey highlight
 + */
 +static void print_highlight_menu_stuff(struct menu_stuff *stuff, int **chosen)
 +{
 +      struct string_list menu_list = STRING_LIST_INIT_DUP;
 +      struct strbuf menu = STRBUF_INIT;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct menu_item *menu_item;
 +      struct string_list_item *string_list_item;
 +      int i;
 +
 +      switch (stuff->type) {
 +      default:
 +              die("Bad type of menu_staff when print menu");
 +      case MENU_STUFF_TYPE_MENU_ITEM:
 +              menu_item = (struct menu_item *)stuff->stuff;
 +              for (i = 0; i < stuff->nr; i++, menu_item++) {
 +                      const char *p;
 +                      int highlighted = 0;
 +
 +                      p = menu_item->title;
 +                      if ((*chosen)[i] < 0)
 +                              (*chosen)[i] = menu_item->selected ? 1 : 0;
 +                      strbuf_addf(&menu, "%s%2d: ", (*chosen)[i] ? "*" : " ", i+1);
 +                      for (; *p; p++) {
 +                              if (!highlighted && *p == menu_item->hotkey) {
 +                                      strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_PROMPT));
 +                                      strbuf_addch(&menu, *p);
 +                                      strbuf_addstr(&menu, clean_get_color(CLEAN_COLOR_RESET));
 +                                      highlighted = 1;
 +                              } else {
 +                                      strbuf_addch(&menu, *p);
 +                              }
 +                      }
 +                      string_list_append(&menu_list, menu.buf);
 +                      strbuf_reset(&menu);
 +              }
 +              break;
 +      case MENU_STUFF_TYPE_STRING_LIST:
 +              i = 0;
 +              for_each_string_list_item(string_list_item, (struct string_list *)stuff->stuff) {
 +                      if ((*chosen)[i] < 0)
 +                              (*chosen)[i] = 0;
 +                      strbuf_addf(&menu, "%s%2d: %s",
 +                                  (*chosen)[i] ? "*" : " ", i+1, string_list_item->string);
 +                      string_list_append(&menu_list, menu.buf);
 +                      strbuf_reset(&menu);
 +                      i++;
 +              }
 +              break;
 +      }
 +
 +      pretty_print_menus(&menu_list);
 +
 +      strbuf_release(&menu);
 +      strbuf_release(&buf);
 +      string_list_clear(&menu_list, 0);
 +}
 +
 +static int find_unique(const char *choice, struct menu_stuff *menu_stuff)
 +{
 +      struct menu_item *menu_item;
 +      struct string_list_item *string_list_item;
 +      int i, len, found = 0;
 +
 +      len = strlen(choice);
 +      switch (menu_stuff->type) {
 +      default:
 +              die("Bad type of menu_stuff when parse choice");
 +      case MENU_STUFF_TYPE_MENU_ITEM:
 +
 +              menu_item = (struct menu_item *)menu_stuff->stuff;
 +              for (i = 0; i < menu_stuff->nr; i++, menu_item++) {
 +                      if (len == 1 && *choice == menu_item->hotkey) {
 +                              found = i + 1;
 +                              break;
 +                      }
 +                      if (!strncasecmp(choice, menu_item->title, len)) {
 +                              if (found) {
 +                                      if (len == 1) {
 +                                              /* continue for hotkey matching */
 +                                              found = -1;
 +                                      } else {
 +                                              found = 0;
 +                                              break;
 +                                      }
 +                              } else {
 +                                      found = i + 1;
 +                              }
 +                      }
 +              }
 +              break;
 +      case MENU_STUFF_TYPE_STRING_LIST:
 +              string_list_item = ((struct string_list *)menu_stuff->stuff)->items;
 +              for (i = 0; i < menu_stuff->nr; i++, string_list_item++) {
 +                      if (!strncasecmp(choice, string_list_item->string, len)) {
 +                              if (found) {
 +                                      found = 0;
 +                                      break;
 +                              }
 +                              found = i + 1;
 +                      }
 +              }
 +              break;
 +      }
 +      return found;
 +}
 +
 +
 +/*
 + * Parse user input, and return choice(s) for menu (menu_stuff).
 + *
 + * Input
 + *     (for single choice)
 + *         1          - select a numbered item
 + *         foo        - select item based on menu title
 + *                    - (empty) select nothing
 + *
 + *     (for multiple choice)
 + *         1          - select a single item
 + *         3-5        - select a range of items
 + *         2-3,6-9    - select multiple ranges
 + *         foo        - select item based on menu title
 + *         -...       - unselect specified items
 + *         *          - choose all items
 + *                    - (empty) finish selecting
 + *
 + * The parse result will be saved in array **chosen, and
 + * return number of total selections.
 + */
 +static int parse_choice(struct menu_stuff *menu_stuff,
 +                      int is_single,
 +                      struct strbuf input,
 +                      int **chosen)
 +{
 +      struct strbuf **choice_list, **ptr;
 +      int nr = 0;
 +      int i;
 +
 +      if (is_single) {
 +              choice_list = strbuf_split_max(&input, '\n', 0);
 +      } else {
 +              char *p = input.buf;
 +              do {
 +                      if (*p == ',')
 +                              *p = ' ';
 +              } while (*p++);
 +              choice_list = strbuf_split_max(&input, ' ', 0);
 +      }
 +
 +      for (ptr = choice_list; *ptr; ptr++) {
 +              char *p;
 +              int choose = 1;
 +              int bottom = 0, top = 0;
 +              int is_range, is_number;
 +
 +              strbuf_trim(*ptr);
 +              if (!(*ptr)->len)
 +                      continue;
 +
 +              /* Input that begins with '-'; unchoose */
 +              if (*(*ptr)->buf == '-') {
 +                      choose = 0;
 +                      strbuf_remove((*ptr), 0, 1);
 +              }
 +
 +              is_range = 0;
 +              is_number = 1;
 +              for (p = (*ptr)->buf; *p; p++) {
 +                      if ('-' == *p) {
 +                              if (!is_range) {
 +                                      is_range = 1;
 +                                      is_number = 0;
 +                              } else {
 +                                      is_number = 0;
 +                                      is_range = 0;
 +                                      break;
 +                              }
 +                      } else if (!isdigit(*p)) {
 +                              is_number = 0;
 +                              is_range = 0;
 +                              break;
 +                      }
 +              }
 +
 +              if (is_number) {
 +                      bottom = atoi((*ptr)->buf);
 +                      top = bottom;
 +              } else if (is_range) {
 +                      bottom = atoi((*ptr)->buf);
 +                      /* a range can be specified like 5-7 or 5- */
 +                      if (!*(strchr((*ptr)->buf, '-') + 1))
 +                              top = menu_stuff->nr;
 +                      else
 +                              top = atoi(strchr((*ptr)->buf, '-') + 1);
 +              } else if (!strcmp((*ptr)->buf, "*")) {
 +                      bottom = 1;
 +                      top = menu_stuff->nr;
 +              } else {
 +                      bottom = find_unique((*ptr)->buf, menu_stuff);
 +                      top = bottom;
 +              }
 +
 +              if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top ||
 +                  (is_single && bottom != top)) {
 +                      clean_print_color(CLEAN_COLOR_ERROR);
 +                      printf_ln(_("Huh (%s)?"), (*ptr)->buf);
 +                      clean_print_color(CLEAN_COLOR_RESET);
 +                      continue;
 +              }
 +
 +              for (i = bottom; i <= top; i++)
 +                      (*chosen)[i-1] = choose;
 +      }
 +
 +      strbuf_list_free(choice_list);
 +
 +      for (i = 0; i < menu_stuff->nr; i++)
 +              nr += (*chosen)[i];
 +      return nr;
 +}
 +
 +/*
 + * Implement a git-add-interactive compatible UI, which is borrowed
 + * from git-add--interactive.perl.
 + *
 + * Return value:
 + *
 + *   - Return an array of integers
 + *   - , and it is up to you to free the allocated memory.
 + *   - The array ends with EOF.
 + *   - If user pressed CTRL-D (i.e. EOF), no selection returned.
 + */
 +static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff)
 +{
 +      struct strbuf choice = STRBUF_INIT;
 +      int *chosen, *result;
 +      int nr = 0;
 +      int eof = 0;
 +      int i;
 +
 +      chosen = xmalloc(sizeof(int) * stuff->nr);
 +      /* set chosen as uninitialized */
 +      for (i = 0; i < stuff->nr; i++)
 +              chosen[i] = -1;
 +
 +      for (;;) {
 +              if (opts->header) {
 +                      printf_ln("%s%s%s",
 +                                clean_get_color(CLEAN_COLOR_HEADER),
 +                                _(opts->header),
 +                                clean_get_color(CLEAN_COLOR_RESET));
 +              }
 +
 +              /* chosen will be initialized by print_highlight_menu_stuff */
 +              print_highlight_menu_stuff(stuff, &chosen);
 +
 +              if (opts->flags & MENU_OPTS_LIST_ONLY)
 +                      break;
 +
 +              if (opts->prompt) {
 +                      printf("%s%s%s%s",
 +                             clean_get_color(CLEAN_COLOR_PROMPT),
 +                             _(opts->prompt),
 +                             opts->flags & MENU_OPTS_SINGLETON ? "> " : ">> ",
 +                             clean_get_color(CLEAN_COLOR_RESET));
 +              }
 +
 +              if (strbuf_getline(&choice, stdin, '\n') != EOF) {
 +                      strbuf_trim(&choice);
 +              } else {
 +                      eof = 1;
 +                      break;
 +              }
 +
 +              /* help for prompt */
 +              if (!strcmp(choice.buf, "?")) {
 +                      prompt_help_cmd(opts->flags & MENU_OPTS_SINGLETON);
 +                      continue;
 +              }
 +
 +              /* for a multiple-choice menu, press ENTER (empty) will return back */
 +              if (!(opts->flags & MENU_OPTS_SINGLETON) && !choice.len)
 +                      break;
 +
 +              nr = parse_choice(stuff,
 +                                opts->flags & MENU_OPTS_SINGLETON,
 +                                choice,
 +                                &chosen);
 +
 +              if (opts->flags & MENU_OPTS_SINGLETON) {
 +                      if (nr)
 +                              break;
 +              } else if (opts->flags & MENU_OPTS_IMMEDIATE) {
 +                      break;
 +              }
 +      }
 +
 +      if (eof) {
 +              result = xmalloc(sizeof(int));
 +              *result = EOF;
 +      } else {
 +              int j = 0;
 +
 +              /*
 +               * recalculate nr, if return back from menu directly with
 +               * default selections.
 +               */
 +              if (!nr) {
 +                      for (i = 0; i < stuff->nr; i++)
 +                              nr += chosen[i];
 +              }
 +
 +              result = xmalloc(sizeof(int) * (nr + 1));
 +              memset(result, 0, sizeof(int) * (nr + 1));
 +              for (i = 0; i < stuff->nr && j < nr; i++) {
 +                      if (chosen[i])
 +                              result[j++] = i;
 +              }
 +              result[j] = EOF;
 +      }
 +
 +      free(chosen);
 +      strbuf_release(&choice);
 +      return result;
 +}
 +
 +static int clean_cmd(void)
 +{
 +      return MENU_RETURN_NO_LOOP;
 +}
 +
 +static int filter_by_patterns_cmd(void)
 +{
 +      struct dir_struct dir;
 +      struct strbuf confirm = STRBUF_INIT;
 +      struct strbuf **ignore_list;
 +      struct string_list_item *item;
 +      struct exclude_list *el;
 +      int changed = -1, i;
 +
 +      for (;;) {
 +              if (!del_list.nr)
 +                      break;
 +
 +              if (changed)
 +                      pretty_print_dels();
 +
 +              clean_print_color(CLEAN_COLOR_PROMPT);
 +              printf(_("Input ignore patterns>> "));
 +              clean_print_color(CLEAN_COLOR_RESET);
 +              if (strbuf_getline(&confirm, stdin, '\n') != EOF)
 +                      strbuf_trim(&confirm);
 +              else
 +                      putchar('\n');
 +
 +              /* quit filter_by_pattern mode if press ENTER or Ctrl-D */
 +              if (!confirm.len)
 +                      break;
 +
 +              memset(&dir, 0, sizeof(dir));
 +              el = add_exclude_list(&dir, EXC_CMDL, "manual exclude");
 +              ignore_list = strbuf_split_max(&confirm, ' ', 0);
 +
 +              for (i = 0; ignore_list[i]; i++) {
 +                      strbuf_trim(ignore_list[i]);
 +                      if (!ignore_list[i]->len)
 +                              continue;
 +
 +                      add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1));
 +              }
 +
 +              changed = 0;
 +              for_each_string_list_item(item, &del_list) {
 +                      int dtype = DT_UNKNOWN;
 +
 +                      if (is_excluded(&dir, item->string, &dtype)) {
 +                              *item->string = '\0';
 +                              changed++;
 +                      }
 +              }
 +
 +              if (changed) {
 +                      string_list_remove_empty_items(&del_list, 0);
 +              } else {
 +                      clean_print_color(CLEAN_COLOR_ERROR);
 +                      printf_ln(_("WARNING: Cannot find items matched by: %s"), confirm.buf);
 +                      clean_print_color(CLEAN_COLOR_RESET);
 +              }
 +
 +              strbuf_list_free(ignore_list);
 +              clear_directory(&dir);
 +      }
 +
 +      strbuf_release(&confirm);
 +      return 0;
 +}
 +
 +static int select_by_numbers_cmd(void)
 +{
 +      struct menu_opts menu_opts;
 +      struct menu_stuff menu_stuff;
 +      struct string_list_item *items;
 +      int *chosen;
 +      int i, j;
 +
 +      menu_opts.header = NULL;
 +      menu_opts.prompt = N_("Select items to delete");
 +      menu_opts.flags = 0;
 +
 +      menu_stuff.type = MENU_STUFF_TYPE_STRING_LIST;
 +      menu_stuff.stuff = &del_list;
 +      menu_stuff.nr = del_list.nr;
 +
 +      chosen = list_and_choose(&menu_opts, &menu_stuff);
 +      items = del_list.items;
 +      for (i = 0, j = 0; i < del_list.nr; i++) {
 +              if (i < chosen[j]) {
 +                      *(items[i].string) = '\0';
 +              } else if (i == chosen[j]) {
 +                      /* delete selected item */
 +                      j++;
 +                      continue;
 +              } else {
 +                      /* end of chosen (chosen[j] == EOF), won't delete */
 +                      *(items[i].string) = '\0';
 +              }
 +      }
 +
 +      string_list_remove_empty_items(&del_list, 0);
 +
 +      free(chosen);
 +      return 0;
 +}
 +
 +static int ask_each_cmd(void)
 +{
 +      struct strbuf confirm = STRBUF_INIT;
 +      struct strbuf buf = STRBUF_INIT;
 +      struct string_list_item *item;
 +      const char *qname;
 +      int changed = 0, eof = 0;
 +
 +      for_each_string_list_item(item, &del_list) {
 +              /* Ctrl-D should stop removing files */
 +              if (!eof) {
 +                      qname = quote_path_relative(item->string, NULL, &buf);
 +                      printf(_("remove %s? "), qname);
 +                      if (strbuf_getline(&confirm, stdin, '\n') != EOF) {
 +                              strbuf_trim(&confirm);
 +                      } else {
 +                              putchar('\n');
 +                              eof = 1;
 +                      }
 +              }
 +              if (!confirm.len || strncasecmp(confirm.buf, "yes", confirm.len)) {
 +                      *item->string = '\0';
 +                      changed++;
 +              }
 +      }
 +
 +      if (changed)
 +              string_list_remove_empty_items(&del_list, 0);
 +
 +      strbuf_release(&buf);
 +      strbuf_release(&confirm);
 +      return MENU_RETURN_NO_LOOP;
 +}
 +
 +static int quit_cmd(void)
 +{
 +      string_list_clear(&del_list, 0);
 +      printf_ln(_("Bye."));
 +      return MENU_RETURN_NO_LOOP;
 +}
 +
 +static int help_cmd(void)
 +{
 +      clean_print_color(CLEAN_COLOR_HELP);
 +      printf_ln(_(
 +                  "clean               - start cleaning\n"
 +                  "filter by pattern   - exclude items from deletion\n"
 +                  "select by numbers   - select items to be deleted by numbers\n"
 +                  "ask each            - confirm each deletion (like \"rm -i\")\n"
 +                  "quit                - stop cleaning\n"
 +                  "help                - this screen\n"
 +                  "?                   - help for prompt selection"
 +                 ));
 +      clean_print_color(CLEAN_COLOR_RESET);
 +      return 0;
 +}
 +
 +static void interactive_main_loop(void)
 +{
 +      while (del_list.nr) {
 +              struct menu_opts menu_opts;
 +              struct menu_stuff menu_stuff;
 +              struct menu_item menus[] = {
 +                      {'c', "clean",                  0, clean_cmd},
 +                      {'f', "filter by pattern",      0, filter_by_patterns_cmd},
 +                      {'s', "select by numbers",      0, select_by_numbers_cmd},
 +                      {'a', "ask each",               0, ask_each_cmd},
 +                      {'q', "quit",                   0, quit_cmd},
 +                      {'h', "help",                   0, help_cmd},
 +              };
 +              int *chosen;
 +
 +              menu_opts.header = N_("*** Commands ***");
 +              menu_opts.prompt = N_("What now");
 +              menu_opts.flags = MENU_OPTS_SINGLETON;
 +
 +              menu_stuff.type = MENU_STUFF_TYPE_MENU_ITEM;
 +              menu_stuff.stuff = menus;
 +              menu_stuff.nr = sizeof(menus) / sizeof(struct menu_item);
 +
 +              clean_print_color(CLEAN_COLOR_HEADER);
 +              printf_ln(Q_("Would remove the following item:",
 +                           "Would remove the following items:",
 +                           del_list.nr));
 +              clean_print_color(CLEAN_COLOR_RESET);
 +
 +              pretty_print_dels();
 +
 +              chosen = list_and_choose(&menu_opts, &menu_stuff);
 +
 +              if (*chosen != EOF) {
 +                      int ret;
 +                      ret = menus[*chosen].fn();
 +                      if (ret != MENU_RETURN_NO_LOOP) {
 +                              free(chosen);
 +                              chosen = NULL;
 +                              if (!del_list.nr) {
 +                                      clean_print_color(CLEAN_COLOR_ERROR);
 +                                      printf_ln(_("No more files to clean, exiting."));
 +                                      clean_print_color(CLEAN_COLOR_RESET);
 +                                      break;
 +                              }
 +                              continue;
 +                      }
 +              } else {
 +                      quit_cmd();
 +              }
 +
 +              free(chosen);
 +              chosen = NULL;
 +              break;
 +      }
 +}
 +
  int cmd_clean(int argc, const char **argv, const char *prefix)
  {
        int i, res;
        int dry_run = 0, remove_directories = 0, quiet = 0, ignored = 0;
        int ignored_only = 0, config_set = 0, errors = 0, gone = 1;
        int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT;
 -      struct strbuf directory = STRBUF_INIT;
 +      struct strbuf abs_path = STRBUF_INIT;
        struct dir_struct dir;
-       static const char **pathspec;
+       struct pathspec pathspec;
        struct strbuf buf = STRBUF_INIT;
        struct string_list exclude_list = STRING_LIST_INIT_NODUP;
        struct exclude_list *el;
 +      struct string_list_item *item;
        const char *qname;
-       char *seen = NULL;
        struct option options[] = {
                OPT__QUIET(&quiet, N_("do not print names of files removed")),
                OPT__DRY_RUN(&dry_run, N_("dry run")),
                OPT__FORCE(&force, N_("force")),
 -              OPT_BOOLEAN('d', NULL, &remove_directories,
 +              OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")),
 +              OPT_BOOL('d', NULL, &remove_directories,
                                N_("remove whole directories")),
                { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"),
                  N_("add <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb },
 -              OPT_BOOLEAN('x', NULL, &ignored, N_("remove ignored files, too")),
 -              OPT_BOOLEAN('X', NULL, &ignored_only,
 +              OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")),
 +              OPT_BOOL('X', NULL, &ignored_only,
                                N_("remove only ignored files")),
                OPT_END()
        };
        if (ignored && ignored_only)
                die(_("-x and -X cannot be used together"));
  
 -      if (!dry_run && !force) {
 +      if (!interactive && !dry_run && !force) {
                if (config_set)
 -                      die(_("clean.requireForce set to true and neither -n nor -f given; "
 +                      die(_("clean.requireForce set to true and neither -i, -n nor -f given; "
                                  "refusing to clean"));
                else
 -                      die(_("clean.requireForce defaults to true and neither -n nor -f given; "
 +                      die(_("clean.requireForce defaults to true and neither -i, -n nor -f given; "
                                  "refusing to clean"));
        }
  
        for (i = 0; i < exclude_list.nr; i++)
                add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1));
  
-       pathspec = get_pathspec(prefix, argv);
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_CWD,
+                      prefix, argv);
  
-       fill_directory(&dir, pathspec);
-       if (pathspec)
-               seen = xmalloc(argc > 0 ? argc : 1);
+       fill_directory(&dir, &pathspec);
  
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                int len, pos;
                int matches = 0;
 -              struct cache_entry *ce;
 +              const struct cache_entry *ce;
                struct stat st;
 +              const char *rel;
  
                /*
                 * Remove the '/' at the end that directory
                                continue; /* Yup, this one exists unmerged */
                }
  
 -              /*
 -               * we might have removed this as part of earlier
 -               * recursive directory removal, so lstat() here could
 -               * fail with ENOENT.
 -               */
                if (lstat(ent->name, &st))
 -                      continue;
 +                      die_errno("Cannot lstat '%s'", ent->name);
  
-               if (pathspec) {
-                       memset(seen, 0, argc > 0 ? argc : 1);
-                       matches = match_pathspec(pathspec, ent->name, len,
-                                                0, seen);
-               }
+               if (pathspec.nr)
+                       matches = match_pathspec_depth(&pathspec, ent->name,
+                                                      len, 0, NULL);
  
                if (S_ISDIR(st.st_mode)) {
 -                      strbuf_addstr(&directory, ent->name);
                        if (remove_directories || (matches == MATCHED_EXACTLY)) {
 -                              if (remove_dirs(&directory, prefix, rm_flags, dry_run, quiet, &gone))
 -                                      errors++;
 -                              if (gone && !quiet) {
 -                                      qname = quote_path_relative(directory.buf, directory.len, &buf, prefix);
 -                                      printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 -                              }
 +                              rel = relative_path(ent->name, prefix, &buf);
 +                              string_list_append(&del_list, rel);
                        }
 -                      strbuf_reset(&directory);
                } else {
-                       if (pathspec && !matches)
+                       if (pathspec.nr && !matches)
                                continue;
 -                      res = dry_run ? 0 : unlink(ent->name);
 +                      rel = relative_path(ent->name, prefix, &buf);
 +                      string_list_append(&del_list, rel);
 +              }
 +      }
 +
 +      if (interactive && del_list.nr > 0)
 +              interactive_main_loop();
 +
 +      for_each_string_list_item(item, &del_list) {
 +              struct stat st;
 +
 +              if (prefix)
 +                      strbuf_addstr(&abs_path, prefix);
 +
 +              strbuf_addstr(&abs_path, item->string);
 +
 +              /*
 +               * we might have removed this as part of earlier
 +               * recursive directory removal, so lstat() here could
 +               * fail with ENOENT.
 +               */
 +              if (lstat(abs_path.buf, &st))
 +                      continue;
 +
 +              if (S_ISDIR(st.st_mode)) {
 +                      if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone))
 +                              errors++;
 +                      if (gone && !quiet) {
 +                              qname = quote_path_relative(item->string, NULL, &buf);
 +                              printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
 +                      }
 +              } else {
 +                      res = dry_run ? 0 : unlink(abs_path.buf);
                        if (res) {
 -                              qname = quote_path_relative(ent->name, -1, &buf, prefix);
 +                              qname = quote_path_relative(item->string, NULL, &buf);
                                warning(_(msg_warn_remove_failed), qname);
                                errors++;
                        } else if (!quiet) {
 -                              qname = quote_path_relative(ent->name, -1, &buf, prefix);
 +                              qname = quote_path_relative(item->string, NULL, &buf);
                                printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname);
                        }
                }
 +              strbuf_reset(&abs_path);
        }
-       free(seen);
  
 -      strbuf_release(&directory);
 +      strbuf_release(&abs_path);
 +      strbuf_release(&buf);
 +      string_list_clear(&del_list, 0);
        string_list_clear(&exclude_list, 0);
        return (errors != 0);
  }
diff --combined builtin/commit.c
index 60812b5b4b0a7215c1d5c441799b9d0857c51dce,4ee9ba6c63857de0689947ab51a7bcbc95b33259..80d886ab3af371a003ae026c19bd76ea83df9690
@@@ -63,18 -63,8 +63,18 @@@ N_("The previous cherry-pick is now emp
  "If you wish to commit it anyway, use:\n"
  "\n"
  "    git commit --allow-empty\n"
 +"\n");
 +
 +static const char empty_cherry_pick_advice_single[] =
 +N_("Otherwise, please use 'git reset'\n");
 +
 +static const char empty_cherry_pick_advice_multi[] =
 +N_("If you wish to skip this commit, use:\n"
  "\n"
 -"Otherwise, please use 'git reset'\n");
 +"    git reset\n"
 +"\n"
 +"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
 +"the remaining commits.\n");
  
  static const char *use_message_buffer;
  static const char commit_editmsg[] = "COMMIT_EDITMSG";
@@@ -117,7 -107,6 +117,7 @@@ static enum 
  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 const char *only_include_assumed;
@@@ -152,11 -141,8 +152,11 @@@ static void determine_whence(struct wt_
  {
        if (file_exists(git_path("MERGE_HEAD")))
                whence = FROM_MERGE;
 -      else if (file_exists(git_path("CHERRY_PICK_HEAD")))
 +      else if (file_exists(git_path("CHERRY_PICK_HEAD"))) {
                whence = FROM_CHERRY_PICK;
 +              if (file_exists(git_path("sequencer")))
 +                      sequencer_in_use = 1;
 +      }
        else
                whence = FROM_COMMIT;
        if (s)
@@@ -202,17 -188,15 +202,15 @@@ static int commit_index_files(void
   * and return the paths that match the given pattern in list.
   */
  static int list_paths(struct string_list *list, const char *with_tree,
-                     const char *prefix, const char **pattern)
+                     const char *prefix, const struct pathspec *pattern)
  {
        int i;
        char *m;
  
-       if (!pattern)
+       if (!pattern->nr)
                return 0;
  
-       for (i = 0; pattern[i]; i++)
-               ;
-       m = xcalloc(1, i);
+       m = xcalloc(1, pattern->nr);
  
        if (with_tree) {
                char *max_prefix = common_prefix(pattern);
        }
  
        for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                struct string_list_item *item;
  
                if (ce->ce_flags & CE_UPDATE)
                        continue;
-               if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
+               if (!match_pathspec_depth(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
                item = string_list_insert(list, ce->name);
                if (ce_skip_worktree(ce))
@@@ -298,17 -282,17 +296,17 @@@ static char *prepare_index(int argc, co
  {
        int fd;
        struct string_list partial;
-       const char **pathspec = NULL;
+       struct pathspec pathspec;
        char *old_index_env = NULL;
        int refresh_flags = REFRESH_QUIET;
  
        if (is_status)
                refresh_flags |= REFRESH_UNMERGED;
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_FULL,
+                      prefix, argv);
  
-       if (*argv)
-               pathspec = get_pathspec(prefix, argv);
-       if (read_cache_preload(pathspec) < 0)
+       if (read_cache_preload(&pathspec) < 0)
                die(_("index file corrupt"));
  
        if (interactive) {
         * (A) if all goes well, commit the real index;
         * (B) on failure, rollback the real index.
         */
-       if (all || (also && pathspec && *pathspec)) {
+       if (all || (also && pathspec.nr)) {
                fd = hold_locked_index(&index_lock, 1);
-               add_files_to_cache(also ? prefix : NULL, pathspec, 0);
+               add_files_to_cache(also ? prefix : NULL, &pathspec, 0);
                refresh_cache_or_die(refresh_flags);
                update_main_cache_tree(WRITE_TREE_SILENT);
                if (write_cache(fd, active_cache, active_nr) ||
         * and create commit from the_index.
         * We still need to refresh the index here.
         */
-       if (!only && (!pathspec || !*pathspec)) {
+       if (!only && !pathspec.nr) {
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache_or_die(refresh_flags);
                if (active_cache_changed) {
  
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
-       if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, pathspec))
+       if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
                exit(1);
  
        discard_cache();
@@@ -548,6 -532,7 +546,6 @@@ static void determine_author_info(struc
                                        (lb - strlen(" ") -
                                         (a + strlen("\nauthor "))));
                email = xmemdupz(lb + strlen("<"), rb - (lb + strlen("<")));
 -              date = xmemdupz(rb + strlen("> "), eol - (rb + strlen("> ")));
                len = eol - (rb + strlen("> "));
                date = xmalloc(len + 2);
                *date = '@';
@@@ -824,13 -809,8 +822,13 @@@ static int prepare_to_commit(const cha
                run_status(stdout, index_file, prefix, 0, s);
                if (amend)
                        fputs(_(empty_amend_advice), stderr);
 -              else if (whence == FROM_CHERRY_PICK)
 +              else if (whence == FROM_CHERRY_PICK) {
                        fputs(_(empty_cherry_pick_advice), stderr);
 +                      if (!sequencer_in_use)
 +                              fputs(_(empty_cherry_pick_advice_single), stderr);
 +                      else
 +                              fputs(_(empty_cherry_pick_advice_multi), stderr);
 +              }
                return 0;
        }
  
@@@ -1091,7 -1071,7 +1089,7 @@@ static int parse_and_validate_options(i
        if (patch_interactive)
                interactive = 1;
  
 -      if (!!also + !!only + !!all + !!interactive > 1)
 +      if (also + only + all + interactive > 1)
                die(_("Only one of --include/--only/--all/--interactive/--patch can be used."));
        if (argc == 0 && (also || (only && !amend)))
                die(_("No paths with --include/--only does not make sense."));
@@@ -1228,14 -1208,14 +1226,14 @@@ int cmd_status(int argc, const char **a
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
 -              OPT_BOOLEAN('z', "null", &s.null_termination,
 -                          N_("terminate entries with NUL")),
 +              OPT_BOOL('z', "null", &s.null_termination,
 +                       N_("terminate entries with NUL")),
                { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg,
                  N_("mode"),
                  N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
 -              OPT_BOOLEAN(0, "ignored", &show_ignored_in_status,
 -                          N_("show ignored files")),
 +              OPT_BOOL(0, "ignored", &show_ignored_in_status,
 +                       N_("show ignored files")),
                { 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" },
        handle_untracked_files_arg(&s);
        if (show_ignored_in_status)
                s.show_ignored_files = 1;
-       if (*argv)
-               s.pathspec = get_pathspec(prefix, argv);
+       parse_pathspec(&s.pathspec, 0,
+                      PATHSPEC_PREFER_FULL,
+                      prefix, argv);
  
-       read_cache_preload(s.pathspec);
-       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
+       read_cache_preload(&s.pathspec);
+       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
  
        fd = hold_locked_index(&index_lock, 0);
        if (0 <= fd)
@@@ -1434,24 -1415,24 +1433,24 @@@ int cmd_commit(int argc, const char **a
                OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")),
                OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")),
                OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")),
 -              OPT_BOOLEAN(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
 -              OPT_BOOLEAN('s', "signoff", &signoff, N_("add Signed-off-by:")),
 +              OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")),
 +              OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
                OPT_FILENAME('t', "template", &template_file, N_("use specified template file")),
                OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")),
                OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")),
 -              OPT_BOOLEAN(0, "status", &include_status, N_("include status in commit message template")),
 +              OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")),
                { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key id"),
                  N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
                /* end commit message options */
  
                OPT_GROUP(N_("Commit contents options")),
 -              OPT_BOOLEAN('a', "all", &all, N_("commit all changed files")),
 -              OPT_BOOLEAN('i', "include", &also, N_("add specified files to index for commit")),
 -              OPT_BOOLEAN(0, "interactive", &interactive, N_("interactively add files")),
 -              OPT_BOOLEAN('p', "patch", &patch_interactive, N_("interactively add changes")),
 -              OPT_BOOLEAN('o', "only", &only, N_("commit only specified files")),
 -              OPT_BOOLEAN('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
 -              OPT_BOOLEAN(0, "dry-run", &dry_run, N_("show what would be committed")),
 +              OPT_BOOL('a', "all", &all, N_("commit all changed files")),
 +              OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")),
 +              OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")),
 +              OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")),
 +              OPT_BOOL('o', "only", &only, N_("commit only specified files")),
 +              OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit hook")),
 +              OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")),
                OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
                            STATUS_FORMAT_SHORT),
                OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
                OPT_SET_INT(0, "long", &status_format,
                            N_("show status in long format (default)"),
                            STATUS_FORMAT_LONG),
 -              OPT_BOOLEAN('z', "null", &s.null_termination,
 -                          N_("terminate entries with NUL")),
 -              OPT_BOOLEAN(0, "amend", &amend, N_("amend previous commit")),
 -              OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
 +              OPT_BOOL('z', "null", &s.null_termination,
 +                       N_("terminate entries with NUL")),
 +              OPT_BOOL(0, "amend", &amend, N_("amend previous commit")),
 +              OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")),
                { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
                /* end commit contents options */
  
 -              { OPTION_BOOLEAN, 0, "allow-empty", &allow_empty, NULL,
 -                N_("ok to record an empty change"),
 -                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
 -              { OPTION_BOOLEAN, 0, "allow-empty-message", &allow_empty_message, NULL,
 -                N_("ok to record a change with an empty message"),
 -                PARSE_OPT_NOARG | PARSE_OPT_HIDDEN },
 +              OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty,
 +                              N_("ok to record an empty change")),
 +              OPT_HIDDEN_BOOL(0, "allow-empty-message", &allow_empty_message,
 +                              N_("ok to record a change with an empty message")),
  
                OPT_END()
        };
diff --combined builtin/grep.c
index 7877e7755c9674cddf58284ea77d594c81b9982b,76a6a60906e4c26508a63950dc72b4250155e271..03bc442e3f96ba12ec389f0fc40349a57e073dc7
@@@ -17,6 -17,7 +17,7 @@@
  #include "grep.h"
  #include "quote.h"
  #include "dir.h"
+ #include "pathspec.h"
  
  static char const * const grep_usage[] = {
        N_("git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]"),
@@@ -286,7 -287,8 +287,7 @@@ static int grep_sha1(struct grep_opt *o
        struct strbuf pathbuf = STRBUF_INIT;
  
        if (opt->relative && opt->prefix_length) {
 -              quote_path_relative(filename + tree_name_len, -1, &pathbuf,
 -                                  opt->prefix);
 +              quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf);
                strbuf_insert(&pathbuf, 0, filename, tree_name_len);
        } else {
                strbuf_addstr(&pathbuf, filename);
@@@ -317,7 -319,7 +318,7 @@@ static int grep_file(struct grep_opt *o
        struct strbuf buf = STRBUF_INIT;
  
        if (opt->relative && opt->prefix_length)
 -              quote_path_relative(filename, -1, &buf, opt->prefix);
 +              quote_path_relative(filename, opt->prefix, &buf);
        else
                strbuf_addstr(&buf, filename);
  
@@@ -375,7 -377,7 +376,7 @@@ static int grep_cache(struct grep_opt *
        read_cache();
  
        for (nr = 0; nr < active_nr; nr++) {
 -              struct cache_entry *ce = active_cache[nr];
 +              const struct cache_entry *ce = active_cache[nr];
                if (!S_ISREG(ce->ce_mode))
                        continue;
                if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
@@@ -521,7 -523,7 +522,7 @@@ static int grep_directory(struct grep_o
        if (exc_std)
                setup_standard_excludes(&dir);
  
-       fill_directory(&dir, pathspec->raw);
+       fill_directory(&dir, pathspec);
        for (i = 0; i < dir.nr; i++) {
                const char *name = dir.entries[i]->name;
                int namelen = strlen(name);
@@@ -629,7 -631,6 +630,6 @@@ int cmd_grep(int argc, const char **arg
        const char *show_in_pager = NULL, *default_pager = "dummy";
        struct grep_opt opt;
        struct object_array list = OBJECT_ARRAY_INIT;
-       const char **paths = NULL;
        struct pathspec pathspec;
        struct string_list path_list = STRING_LIST_INIT_NODUP;
        int i;
        int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
  
        struct option options[] = {
 -              OPT_BOOLEAN(0, "cached", &cached,
 +              OPT_BOOL(0, "cached", &cached,
                        N_("search in index instead of in the work tree")),
                OPT_NEGBIT(0, "no-index", &use_index,
                         N_("find in contents not managed by git"), 1),
 -              OPT_BOOLEAN(0, "untracked", &untracked,
 +              OPT_BOOL(0, "untracked", &untracked,
                        N_("search in both tracked and untracked files")),
                OPT_SET_INT(0, "exclude-standard", &opt_exclude,
                            N_("search also in ignored files"), 1),
                OPT_GROUP(""),
 -              OPT_BOOLEAN('v', "invert-match", &opt.invert,
 +              OPT_BOOL('v', "invert-match", &opt.invert,
                        N_("show non-matching lines")),
 -              OPT_BOOLEAN('i', "ignore-case", &opt.ignore_case,
 +              OPT_BOOL('i', "ignore-case", &opt.ignore_case,
                        N_("case insensitive matching")),
 -              OPT_BOOLEAN('w', "word-regexp", &opt.word_regexp,
 +              OPT_BOOL('w', "word-regexp", &opt.word_regexp,
                        N_("match patterns only at word boundaries")),
                OPT_SET_INT('a', "text", &opt.binary,
                        N_("process binary files as text"), GREP_BINARY_TEXT),
                            N_("use Perl-compatible regular expressions"),
                            GREP_PATTERN_TYPE_PCRE),
                OPT_GROUP(""),
 -              OPT_BOOLEAN('n', "line-number", &opt.linenum, N_("show line numbers")),
 +              OPT_BOOL('n', "line-number", &opt.linenum, N_("show line numbers")),
                OPT_NEGBIT('h', NULL, &opt.pathname, N_("don't show filenames"), 1),
                OPT_BIT('H', NULL, &opt.pathname, N_("show filenames"), 1),
                OPT_NEGBIT(0, "full-name", &opt.relative,
                        N_("show filenames relative to top directory"), 1),
 -              OPT_BOOLEAN('l', "files-with-matches", &opt.name_only,
 +              OPT_BOOL('l', "files-with-matches", &opt.name_only,
                        N_("show only filenames instead of matching lines")),
 -              OPT_BOOLEAN(0, "name-only", &opt.name_only,
 +              OPT_BOOL(0, "name-only", &opt.name_only,
                        N_("synonym for --files-with-matches")),
 -              OPT_BOOLEAN('L', "files-without-match",
 +              OPT_BOOL('L', "files-without-match",
                        &opt.unmatch_name_only,
                        N_("show only the names of files without match")),
 -              OPT_BOOLEAN('z', "null", &opt.null_following_name,
 +              OPT_BOOL('z', "null", &opt.null_following_name,
                        N_("print NUL after filenames")),
 -              OPT_BOOLEAN('c', "count", &opt.count,
 +              OPT_BOOL('c', "count", &opt.count,
                        N_("show the number of matches instead of matching lines")),
                OPT__COLOR(&opt.color, N_("highlight matches")),
 -              OPT_BOOLEAN(0, "break", &opt.file_break,
 +              OPT_BOOL(0, "break", &opt.file_break,
                        N_("print empty line between matches from different files")),
 -              OPT_BOOLEAN(0, "heading", &opt.heading,
 +              OPT_BOOL(0, "heading", &opt.heading,
                        N_("show filename only once above matches from same file")),
                OPT_GROUP(""),
                OPT_CALLBACK('C', "context", &opt, N_("n"),
                        N_("show <n> context lines after matches")),
                OPT_NUMBER_CALLBACK(&opt, N_("shortcut for -C NUM"),
                        context_callback),
 -              OPT_BOOLEAN('p', "show-function", &opt.funcname,
 +              OPT_BOOL('p', "show-function", &opt.funcname,
                        N_("show a line with the function name before matches")),
 -              OPT_BOOLEAN('W', "function-context", &opt.funcbody,
 +              OPT_BOOL('W', "function-context", &opt.funcbody,
                        N_("show the surrounding function")),
                OPT_GROUP(""),
                OPT_CALLBACK('f', NULL, &opt, N_("file"),
                { OPTION_CALLBACK, 0, "and", &opt, NULL,
                  N_("combine patterns specified with -e"),
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback },
 -              OPT_BOOLEAN(0, "or", &dummy, ""),
 +              OPT_BOOL(0, "or", &dummy, ""),
                { OPTION_CALLBACK, 0, "not", &opt, NULL, "",
                  PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback },
                { OPTION_CALLBACK, '(', NULL, &opt, NULL, "",
                  close_callback },
                OPT__QUIET(&opt.status_only,
                           N_("indicate hit with exit status without output")),
 -              OPT_BOOLEAN(0, "all-match", &opt.all_match,
 +              OPT_BOOL(0, "all-match", &opt.all_match,
                        N_("show only matches from files that match all patterns")),
                { OPTION_SET_INT, 0, "debug", &opt.debug, NULL,
                  N_("show parse tree for grep expression"),
                { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager,
                        N_("pager"), N_("show matching files in the pager"),
                        PARSE_OPT_OPTARG, NULL, (intptr_t)default_pager },
 -              OPT_BOOLEAN(0, "ext-grep", &external_grep_allowed__ignored,
 -                          N_("allow calling of grep(1) (ignored by this build)")),
 +              OPT_BOOL(0, "ext-grep", &external_grep_allowed__ignored,
 +                       N_("allow calling of grep(1) (ignored by this build)")),
                { OPTION_CALLBACK, 0, "help-all", &options, NULL, N_("show usage"),
                  PARSE_OPT_HIDDEN | PARSE_OPT_NOARG, help_callback },
                OPT_END()
                        verify_filename(prefix, argv[j], j == i);
        }
  
-       paths = get_pathspec(prefix, argv + i);
-       init_pathspec(&pathspec, paths);
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_CWD |
+                      (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
+                      prefix, argv + i);
        pathspec.max_depth = opt.max_depth;
        pathspec.recursive = 1;
  
diff --combined builtin/log.c
index ed4dec406e76fdda71e2882ae0b02f79c9d57c81,873af6947671d2ed47d74569e5833ebd9fb68fdd..77d0f5f3fdbcb48d75aee35a67ed57dd134c907e
@@@ -121,7 -121,7 +121,7 @@@ static void cmd_log_init_finish(int arg
        static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP};
  
        const struct option builtin_log_options[] = {
 -              OPT_BOOL(0, "quiet", &quiet, N_("suppress diff output")),
 +              OPT__QUIET(&quiet, N_("suppress diff output")),
                OPT_BOOL(0, "source", &source, N_("show source")),
                OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")),
                { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"),
@@@ -503,7 -503,7 +503,7 @@@ int cmd_show(int argc, const char **arg
        init_grep_defaults();
        git_config(git_log_config, NULL);
  
-       init_pathspec(&match_all, NULL);
+       memset(&match_all, 0, sizeof(match_all));
        init_revisions(&rev, prefix);
        rev.diff = 1;
        rev.always_show_header = 1;
@@@ -1112,21 -1112,6 +1112,21 @@@ static int cc_callback(const struct opt
        return 0;
  }
  
 +static int from_callback(const struct option *opt, const char *arg, int unset)
 +{
 +      char **from = opt->value;
 +
 +      free(*from);
 +
 +      if (unset)
 +              *from = NULL;
 +      else if (arg)
 +              *from = xstrdup(arg);
 +      else
 +              *from = xstrdup(git_committer_info(IDENT_NO_DATE));
 +      return 0;
 +}
 +
  int cmd_format_patch(int argc, const char **argv, const char *prefix)
  {
        struct commit *commit;
        int quiet = 0;
        int reroll_count = -1;
        char *branch_name = NULL;
 +      char *from = NULL;
        const struct option builtin_format_patch_options[] = {
                { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL,
                            N_("use [PATCH n/m] even with a single patch"),
                { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL,
                            N_("don't strip/add [PATCH]"),
                            PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback },
 -              OPT_BOOLEAN(0, "no-binary", &no_binary_diff,
 -                          N_("don't output binary diffs")),
 -              OPT_BOOLEAN(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
 -                          N_("don't include a patch matching a commit upstream")),
 -              { OPTION_BOOLEAN, 'p', "no-stat", &use_patch_format, NULL,
 +              OPT_BOOL(0, "no-binary", &no_binary_diff,
 +                       N_("don't output binary diffs")),
 +              OPT_BOOL(0, "ignore-if-in-upstream", &ignore_if_in_upstream,
 +                       N_("don't include a patch matching a commit upstream")),
 +              { OPTION_SET_INT, 'p', "no-stat", &use_patch_format, NULL,
                  N_("show patch format instead of default (patch + stat)"),
 -                PARSE_OPT_NONEG | PARSE_OPT_NOARG },
 +                PARSE_OPT_NONEG | PARSE_OPT_NOARG, NULL, 1},
                OPT_GROUP(N_("Messaging")),
                { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"),
                            N_("add email header"), 0, header_callback },
                            0, to_callback },
                { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"),
                            0, cc_callback },
 +              { OPTION_CALLBACK, 0, "from", &from, N_("ident"),
 +                          N_("set From address to <ident> (or committer ident if absent)"),
 +                          PARSE_OPT_OPTARG, from_callback },
                OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"),
                            N_("make first mail a reply to <message-id>")),
                { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"),
                            PARSE_OPT_OPTARG, thread_callback },
                OPT_STRING(0, "signature", &signature, N_("signature"),
                            N_("add a signature")),
 -              OPT_BOOLEAN(0, "quiet", &quiet,
 -                          N_("don't print the patch filenames")),
 +              OPT__QUIET(&quiet, N_("don't print the patch filenames")),
                OPT_END()
        };
  
  
        rev.extra_headers = strbuf_detach(&buf, NULL);
  
 +      if (from) {
 +              if (split_ident_line(&rev.from_ident, from, strlen(from)))
 +                      die(_("invalid ident line: %s"), from);
 +      }
 +
        if (start_number < 0)
                start_number = 1;
  
diff --combined builtin/ls-files.c
index 963ccc974245bbb68d0fefd4ec6e97d2ffaaae54,d3a0495f7d485134176c41e302b5a644a824dcfd..d4823c9d38e5e22dff62d979601e0b06b9c857f3
@@@ -13,6 -13,7 +13,7 @@@
  #include "parse-options.h"
  #include "resolve-undo.h"
  #include "string-list.h"
+ #include "pathspec.h"
  
  static int abbrev;
  static int show_deleted;
@@@ -30,7 -31,7 +31,7 @@@ static int debug_mode
  static const char *prefix;
  static int max_prefix_len;
  static int prefix_len;
- static const char **pathspec;
+ static struct pathspec pathspec;
  static int error_unmatch;
  static char *ps_matched;
  static const char *with_tree;
@@@ -46,14 -47,10 +47,14 @@@ static const char *tag_modified = ""
  static const char *tag_skip_worktree = "";
  static const char *tag_resolve_undo = "";
  
 -static void write_name(const char* name, size_t len)
 +static void write_name(const char *name)
  {
 -      write_name_quoted_relative(name, len, prefix, prefix_len, stdout,
 -                      line_terminator);
 +      /*
 +       * With "--full-name", prefix_len=0; this caller needs to pass
 +       * an empty string in that case (a NULL is good for "").
 +       */
 +      write_name_quoted_relative(name, prefix_len ? prefix : NULL,
 +                                 stdout, line_terminator);
  }
  
  static void show_dir_entry(const char *tag, struct dir_entry *ent)
        if (len >= ent->len)
                die("git ls-files: internal error - directory entry not superset of prefix");
  
-       if (!match_pathspec(pathspec, ent->name, ent->len, len, ps_matched))
+       if (!match_pathspec_depth(&pathspec, ent->name, ent->len, len, ps_matched))
                return;
  
        fputs(tag, stdout);
 -      write_name(ent->name, ent->len);
 +      write_name(ent->name);
  }
  
  static void show_other_files(struct dir_struct *dir)
@@@ -131,14 -128,14 +132,14 @@@ static void show_killed_files(struct di
        }
  }
  
 -static void show_ce_entry(const char *tag, struct cache_entry *ce)
 +static void show_ce_entry(const char *tag, const struct cache_entry *ce)
  {
        int len = max_prefix_len;
  
        if (len >= ce_namelen(ce))
                die("git ls-files: internal error - cache entry not superset of prefix");
  
-       if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), len, ps_matched))
+       if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), len, ps_matched))
                return;
  
        if (tag && *tag && show_valid_bit &&
                       find_unique_abbrev(ce->sha1,abbrev),
                       ce_stage(ce));
        }
 -      write_name(ce->name, ce_namelen(ce));
 +      write_name(ce->name);
        if (debug_mode) {
 -              struct stat_data *sd = &ce->ce_stat_data;
 +              const struct stat_data *sd = &ce->ce_stat_data;
  
                printf("  ctime: %d:%d\n", sd->sd_ctime.sec, sd->sd_ctime.nsec);
                printf("  mtime: %d:%d\n", sd->sd_mtime.sec, sd->sd_mtime.nsec);
@@@ -194,7 -191,7 +195,7 @@@ static void show_ru_info(void
                len = strlen(path);
                if (len < max_prefix_len)
                        continue; /* outside of the prefix */
-               if (!match_pathspec(pathspec, path, len, max_prefix_len, ps_matched))
+               if (!match_pathspec_depth(&pathspec, path, len, max_prefix_len, ps_matched))
                        continue; /* uninterested */
                for (i = 0; i < 3; i++) {
                        if (!ui->mode[i])
                        printf("%s%06o %s %d\t", tag_resolve_undo, ui->mode[i],
                               find_unique_abbrev(ui->sha1[i], abbrev),
                               i + 1);
 -                      write_name(path, len);
 +                      write_name(path);
                }
        }
  }
  
 -static int ce_excluded(struct dir_struct *dir, struct cache_entry *ce)
 +static int ce_excluded(struct dir_struct *dir, const struct cache_entry *ce)
  {
        int dtype = ce_to_dtype(ce);
        return is_excluded(dir, ce->name, &dtype);
@@@ -219,7 -216,7 +220,7 @@@ static void show_files(struct dir_struc
  
        /* For cached/deleted files we don't need to even do the readdir */
        if (show_others || show_killed) {
-               fill_directory(dir, pathspec);
+               fill_directory(dir, &pathspec);
                if (show_others)
                        show_other_files(dir);
                if (show_killed)
        }
        if (show_cached || show_stage) {
                for (i = 0; i < active_nr; i++) {
 -                      struct cache_entry *ce = active_cache[i];
 +                      const struct cache_entry *ce = active_cache[i];
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
                            !ce_excluded(dir, ce))
                                continue;
        }
        if (show_deleted || show_modified) {
                for (i = 0; i < active_nr; i++) {
 -                      struct cache_entry *ce = active_cache[i];
 +                      const struct cache_entry *ce = active_cache[i];
                        struct stat st;
                        int err;
                        if ((dir->flags & DIR_SHOW_IGNORED) &&
@@@ -277,7 -274,7 +278,7 @@@ static void prune_cache(const char *pre
        last = active_nr;
        while (last > first) {
                int next = (last + first) >> 1;
 -              struct cache_entry *ce = active_cache[next];
 +              const struct cache_entry *ce = active_cache[next];
                if (!strncmp(ce->name, prefix, max_prefix_len)) {
                        first = next+1;
                        continue;
        active_nr = last;
  }
  
- static void strip_trailing_slash_from_submodules(void)
- {
-       const char **p;
-       for (p = pathspec; *p != NULL; p++) {
-               int len = strlen(*p), pos;
-               if (len < 1 || (*p)[len - 1] != '/')
-                       continue;
-               pos = cache_name_pos(*p, len - 1);
-               if (pos >= 0 && S_ISGITLINK(active_cache[pos]->ce_mode))
-                       *p = xstrndup(*p, len - 1);
-       }
- }
  /*
   * Read the tree specified with --with-tree option
   * (typically, HEAD) into stage #1 and then
@@@ -333,13 -315,12 +319,12 @@@ void overlay_tree_on_cache(const char *
        }
  
        if (prefix) {
-               static const char *(matchbuf[2]);
-               matchbuf[0] = prefix;
-               matchbuf[1] = NULL;
-               init_pathspec(&pathspec, matchbuf);
-               pathspec.items[0].nowildcard_len = pathspec.items[0].len;
+               static const char *(matchbuf[1]);
+               matchbuf[0] = NULL;
+               parse_pathspec(&pathspec, PATHSPEC_ALL_MAGIC,
+                              PATHSPEC_PREFER_CWD, prefix, matchbuf);
        } else
-               init_pathspec(&pathspec, NULL);
+               memset(&pathspec, 0, sizeof(pathspec));
        if (read_tree(tree, 1, &pathspec))
                die("unable to read tree entries %s", tree_name);
  
        }
  }
  
- int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix)
+ int report_path_error(const char *ps_matched,
+                     const struct pathspec *pathspec,
+                     const char *prefix)
  {
        /*
         * Make sure all pathspec matched; otherwise it is an error.
         */
        struct strbuf sb = STRBUF_INIT;
-       const char *name;
        int num, errors = 0;
-       for (num = 0; pathspec[num]; num++) {
+       for (num = 0; num < pathspec->nr; num++) {
                int other, found_dup;
  
                if (ps_matched[num])
                /*
                 * The caller might have fed identical pathspec
                 * twice.  Do not barf on such a mistake.
+                * FIXME: parse_pathspec should have eliminated
+                * duplicate pathspec.
                 */
                for (found_dup = other = 0;
-                    !found_dup && pathspec[other];
+                    !found_dup && other < pathspec->nr;
                     other++) {
                        if (other == num || !ps_matched[other])
                                continue;
-                       if (!strcmp(pathspec[other], pathspec[num]))
+                       if (!strcmp(pathspec->items[other].original,
+                                   pathspec->items[num].original))
                                /*
                                 * Ok, we have a match already.
                                 */
                if (found_dup)
                        continue;
  
-               name = quote_path_relative(pathspec[num], prefix, &sb);
                error("pathspec '%s' did not match any file(s) known to git.",
-                     name);
+                     pathspec->items[num].original);
                errors++;
        }
        strbuf_release(&sb);
@@@ -461,24 -445,24 +449,24 @@@ int cmd_ls_files(int argc, const char *
                { OPTION_CALLBACK, 'z', NULL, NULL, NULL,
                        N_("paths are separated with NUL character"),
                        PARSE_OPT_NOARG, option_parse_z },
 -              OPT_BOOLEAN('t', NULL, &show_tag,
 +              OPT_BOOL('t', NULL, &show_tag,
                        N_("identify the file status with tags")),
 -              OPT_BOOLEAN('v', NULL, &show_valid_bit,
 +              OPT_BOOL('v', NULL, &show_valid_bit,
                        N_("use lowercase letters for 'assume unchanged' files")),
 -              OPT_BOOLEAN('c', "cached", &show_cached,
 +              OPT_BOOL('c', "cached", &show_cached,
                        N_("show cached files in the output (default)")),
 -              OPT_BOOLEAN('d', "deleted", &show_deleted,
 +              OPT_BOOL('d', "deleted", &show_deleted,
                        N_("show deleted files in the output")),
 -              OPT_BOOLEAN('m', "modified", &show_modified,
 +              OPT_BOOL('m', "modified", &show_modified,
                        N_("show modified files in the output")),
 -              OPT_BOOLEAN('o', "others", &show_others,
 +              OPT_BOOL('o', "others", &show_others,
                        N_("show other files in the output")),
                OPT_BIT('i', "ignored", &dir.flags,
                        N_("show ignored files in the output"),
                        DIR_SHOW_IGNORED),
 -              OPT_BOOLEAN('s', "stage", &show_stage,
 +              OPT_BOOL('s', "stage", &show_stage,
                        N_("show staged contents' object name in the output")),
 -              OPT_BOOLEAN('k', "killed", &show_killed,
 +              OPT_BOOL('k', "killed", &show_killed,
                        N_("show files on the filesystem that need to be removed")),
                OPT_BIT(0, "directory", &dir.flags,
                        N_("show 'other' directories' name only"),
                OPT_NEGBIT(0, "empty-directory", &dir.flags,
                        N_("don't show empty directories"),
                        DIR_HIDE_EMPTY_DIRECTORIES),
 -              OPT_BOOLEAN('u', "unmerged", &show_unmerged,
 +              OPT_BOOL('u', "unmerged", &show_unmerged,
                        N_("show unmerged files in the output")),
 -              OPT_BOOLEAN(0, "resolve-undo", &show_resolve_undo,
 +              OPT_BOOL(0, "resolve-undo", &show_resolve_undo,
                            N_("show resolve-undo information")),
                { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"),
                        N_("skip files matching pattern"),
                { OPTION_SET_INT, 0, "full-name", &prefix_len, NULL,
                        N_("make the output relative to the project top directory"),
                        PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL },
 -              OPT_BOOLEAN(0, "error-unmatch", &error_unmatch,
 +              OPT_BOOL(0, "error-unmatch", &error_unmatch,
                        N_("if any <file> is not in the index, treat this as an error")),
                OPT_STRING(0, "with-tree", &with_tree, N_("tree-ish"),
                        N_("pretend that paths removed since <tree-ish> are still present")),
                OPT__ABBREV(&abbrev),
 -              OPT_BOOLEAN(0, "debug", &debug_mode, N_("show debugging data")),
 +              OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")),
                OPT_END()
        };
  
        if (require_work_tree && !is_inside_work_tree())
                setup_work_tree();
  
-       pathspec = get_pathspec(prefix, argv);
-       /* be nice with submodule paths ending in a slash */
-       if (pathspec)
-               strip_trailing_slash_from_submodules();
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_CWD |
+                      PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP,
+                      prefix, argv);
  
        /* Find common prefix for all pathspec's */
-       max_prefix = common_prefix(pathspec);
+       max_prefix = common_prefix(&pathspec);
        max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
  
        /* Treat unmatching pathspec elements as errors */
-       if (pathspec && error_unmatch) {
-               int num;
-               for (num = 0; pathspec[num]; num++)
-                       ;
-               ps_matched = xcalloc(1, num);
-       }
+       if (pathspec.nr && error_unmatch)
+               ps_matched = xcalloc(1, pathspec.nr);
  
        if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given)
                die("ls-files --ignored needs some exclude pattern");
  
        if (ps_matched) {
                int bad;
-               bad = report_path_error(ps_matched, pathspec, prefix);
+               bad = report_path_error(ps_matched, &pathspec, prefix);
                if (bad)
                        fprintf(stderr, "Did you forget to 'git add'?\n");
  
diff --combined builtin/ls-tree.c
index de88563edfdae562321bad7a91e8c5fe286e8922,f6d82151813a44cedd5122ea3962e8b9b163114a..65ec93184614619cac925c17ef3ebc90cd30a82c
@@@ -10,6 -10,7 +10,7 @@@
  #include "quote.h"
  #include "builtin.h"
  #include "parse-options.h"
+ #include "pathspec.h"
  
  static int line_termination = '\n';
  #define LS_RECURSIVE 1
@@@ -35,7 -36,7 +36,7 @@@ static int show_recursive(const char *b
        if (ls_options & LS_RECURSIVE)
                return 1;
  
-       s = pathspec.raw;
+       s = pathspec._raw;
        if (!s)
                return 0;
  
@@@ -138,9 -139,9 +139,9 @@@ int cmd_ls_tree(int argc, const char **
                        LS_NAME_ONLY),
                OPT_SET_INT(0, "full-name", &chomp_prefix,
                            N_("use full path names"), 0),
 -              OPT_BOOLEAN(0, "full-tree", &full_tree,
 -                          N_("list entire tree; not just current directory "
 -                             "(implies --full-name)")),
 +              OPT_BOOL(0, "full-tree", &full_tree,
 +                       N_("list entire tree; not just current directory "
 +                          "(implies --full-name)")),
                OPT__ABBREV(&abbrev),
                OPT_END()
        };
        if (get_sha1(argv[0], sha1))
                die("Not a valid object name %s", argv[0]);
  
-       init_pathspec(&pathspec, get_pathspec(prefix, argv + 1));
+       /*
+        * show_recursive() rolls its own matching code and is
+        * generally ignorant of 'struct pathspec'. The magic mask
+        * cannot be lifted until it is converted to use
+        * match_pathspec_depth() or tree_entry_interesting()
+        */
+       parse_pathspec(&pathspec, PATHSPEC_GLOB | PATHSPEC_ICASE,
+                      PATHSPEC_PREFER_CWD,
+                      prefix, argv + 1);
        for (i = 0; i < pathspec.nr; i++)
                pathspec.items[i].nowildcard_len = pathspec.items[i].len;
        pathspec.has_wildcard = 0;
diff --combined builtin/mv.c
index be6fa77d0481c60b82a22ca01e9b8714d9849e71,7dd6bb491cbde95f41ba6311bbd9344a80e19b31..aec79d18386b52a943b20e6ebe0dfc9b6f074f0f
@@@ -9,14 -9,16 +9,16 @@@
  #include "cache-tree.h"
  #include "string-list.h"
  #include "parse-options.h"
+ #include "submodule.h"
  
  static const char * const builtin_mv_usage[] = {
        N_("git mv [options] <source>... <destination>"),
        NULL
  };
  
- static const char **copy_pathspec(const char *prefix, const char **pathspec,
-                                 int count, int base_name)
+ static const char **internal_copy_pathspec(const char *prefix,
+                                          const char **pathspec,
+                                          int count, int base_name)
  {
        int i;
        const char **result = xmalloc((count + 1) * sizeof(const char *));
@@@ -56,20 -58,21 +58,21 @@@ static struct lock_file lock_file
  
  int cmd_mv(int argc, const char **argv, const char *prefix)
  {
-       int i, newfd;
+       int i, newfd, gitmodules_modified = 0;
        int verbose = 0, show_only = 0, force = 0, ignore_errors = 0;
        struct option builtin_mv_options[] = {
                OPT__VERBOSE(&verbose, N_("be verbose")),
                OPT__DRY_RUN(&show_only, N_("dry run")),
                OPT__FORCE(&force, N_("force move/rename even if target exists")),
 -              OPT_BOOLEAN('k', NULL, &ignore_errors, N_("skip move/rename errors")),
 +              OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")),
                OPT_END(),
        };
-       const char **source, **destination, **dest_path;
+       const char **source, **destination, **dest_path, **submodule_gitfile;
        enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes;
        struct stat st;
        struct string_list src_for_dst = STRING_LIST_INIT_NODUP;
  
+       gitmodules_config();
        git_config(git_default_config, NULL);
  
        argc = parse_options(argc, argv, prefix, builtin_mv_options,
        if (read_cache() < 0)
                die(_("index file corrupt"));
  
-       source = copy_pathspec(prefix, argv, argc, 0);
+       source = internal_copy_pathspec(prefix, argv, argc, 0);
        modes = xcalloc(argc, sizeof(enum update_mode));
-       dest_path = copy_pathspec(prefix, argv + argc, 1, 0);
+       dest_path = internal_copy_pathspec(prefix, argv + argc, 1, 0);
+       submodule_gitfile = xcalloc(argc, sizeof(char *));
  
        if (dest_path[0][0] == '\0')
                /* special case: "." was normalized to "" */
-               destination = copy_pathspec(dest_path[0], argv, argc, 1);
+               destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
        else if (!lstat(dest_path[0], &st) &&
                        S_ISDIR(st.st_mode)) {
                dest_path[0] = add_slash(dest_path[0]);
-               destination = copy_pathspec(dest_path[0], argv, argc, 1);
+               destination = internal_copy_pathspec(dest_path[0], argv, argc, 1);
        } else {
                if (argc != 1)
                        die("destination '%s' is not a directory", dest_path[0]);
                                && lstat(dst, &st) == 0)
                        bad = _("cannot move directory over file");
                else if (src_is_dir) {
-                       const char *src_w_slash = add_slash(src);
-                       int len_w_slash = length + 1;
-                       int first, last;
-                       modes[i] = WORKING_DIRECTORY;
-                       first = cache_name_pos(src_w_slash, len_w_slash);
-                       if (first >= 0)
-                               die (_("Huh? %.*s is in index?"),
-                                               len_w_slash, src_w_slash);
-                       first = -1 - first;
-                       for (last = first; last < active_nr; last++) {
-                               const char *path = active_cache[last]->name;
-                               if (strncmp(path, src_w_slash, len_w_slash))
-                                       break;
-                       }
-                       free((char *)src_w_slash);
-                       if (last - first < 1)
-                               bad = _("source directory is empty");
-                       else {
-                               int j, dst_len;
-                               if (last - first > 0) {
-                                       source = xrealloc(source,
-                                                       (argc + last - first)
-                                                       * sizeof(char *));
-                                       destination = xrealloc(destination,
-                                                       (argc + last - first)
-                                                       * sizeof(char *));
-                                       modes = xrealloc(modes,
-                                                       (argc + last - first)
-                                                       * sizeof(enum update_mode));
+                       int first = cache_name_pos(src, length);
+                       if (first >= 0) {
+                               struct strbuf submodule_dotgit = STRBUF_INIT;
+                               if (!S_ISGITLINK(active_cache[first]->ce_mode))
+                                       die (_("Huh? Directory %s is in index and no submodule?"), src);
+                               if (!is_staging_gitmodules_ok())
+                                       die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
+                               strbuf_addf(&submodule_dotgit, "%s/.git", src);
+                               submodule_gitfile[i] = read_gitfile(submodule_dotgit.buf);
+                               if (submodule_gitfile[i])
+                                       submodule_gitfile[i] = xstrdup(submodule_gitfile[i]);
+                               strbuf_release(&submodule_dotgit);
+                       } else {
+                               const char *src_w_slash = add_slash(src);
+                               int last, len_w_slash = length + 1;
+                               modes[i] = WORKING_DIRECTORY;
+                               first = cache_name_pos(src_w_slash, len_w_slash);
+                               if (first >= 0)
+                                       die (_("Huh? %.*s is in index?"),
+                                                       len_w_slash, src_w_slash);
+                               first = -1 - first;
+                               for (last = first; last < active_nr; last++) {
+                                       const char *path = active_cache[last]->name;
+                                       if (strncmp(path, src_w_slash, len_w_slash))
+                                               break;
                                }
+                               free((char *)src_w_slash);
+                               if (last - first < 1)
+                                       bad = _("source directory is empty");
+                               else {
+                                       int j, dst_len;
  
-                               dst = add_slash(dst);
-                               dst_len = strlen(dst);
-                               for (j = 0; j < last - first; j++) {
-                                       const char *path =
-                                               active_cache[first + j]->name;
-                                       source[argc + j] = path;
-                                       destination[argc + j] =
-                                               prefix_path(dst, dst_len,
-                                                       path + length + 1);
-                                       modes[argc + j] = INDEX;
+                                       if (last - first > 0) {
+                                               source = xrealloc(source,
+                                                               (argc + last - first)
+                                                               * sizeof(char *));
+                                               destination = xrealloc(destination,
+                                                               (argc + last - first)
+                                                               * sizeof(char *));
+                                               modes = xrealloc(modes,
+                                                               (argc + last - first)
+                                                               * sizeof(enum update_mode));
+                                       }
+                                       dst = add_slash(dst);
+                                       dst_len = strlen(dst);
+                                       for (j = 0; j < last - first; j++) {
+                                               const char *path =
+                                                       active_cache[first + j]->name;
+                                               source[argc + j] = path;
+                                               destination[argc + j] =
+                                                       prefix_path(dst, dst_len,
+                                                               path + length + 1);
+                                               modes[argc + j] = INDEX;
+                                       }
+                                       argc += last - first;
                                }
-                               argc += last - first;
                        }
                } else if (cache_name_pos(src, length) < 0)
                        bad = _("not under version control");
                int pos;
                if (show_only || verbose)
                        printf(_("Renaming %s to %s\n"), src, dst);
-               if (!show_only && mode != INDEX &&
-                               rename(src, dst) < 0 && !ignore_errors)
-                       die_errno (_("renaming '%s' failed"), src);
+               if (!show_only && mode != INDEX) {
+                       if (rename(src, dst) < 0 && !ignore_errors)
+                               die_errno (_("renaming '%s' failed"), src);
+                       if (submodule_gitfile[i])
+                               connect_work_tree_and_git_dir(dst, submodule_gitfile[i]);
+                       if (!update_path_in_gitmodules(src, dst))
+                               gitmodules_modified = 1;
+               }
  
                if (mode == WORKING_DIRECTORY)
                        continue;
                        rename_cache_entry_at(pos, dst);
        }
  
+       if (gitmodules_modified)
+               stage_updated_gitmodules();
        if (active_cache_changed) {
                if (write_cache(newfd, active_cache, active_nr) ||
                    commit_locked_index(&lock_file))
diff --combined builtin/reset.c
index 090581564b2a71d8360c1bc112ba39ceaa96825c,86150d14f1481df93685043736c18be9ad9ef768..5e4c551531896ebd993d2a2c2903e4b7d257ed31
@@@ -133,12 -133,13 +133,13 @@@ static void update_index_from_diff(stru
        }
  }
  
- static int read_from_tree(const char **pathspec, unsigned char *tree_sha1)
+ static int read_from_tree(const struct pathspec *pathspec,
+                         unsigned char *tree_sha1)
  {
        struct diff_options opt;
  
        memset(&opt, 0, sizeof(opt));
-       diff_tree_setup_paths(pathspec, &opt);
+       copy_pathspec(&opt.pathspec, pathspec);
        opt.output_format = DIFF_FORMAT_CALLBACK;
        opt.format_callback = update_index_from_diff;
  
                return 1;
        diffcore_std(&opt);
        diff_flush(&opt);
-       diff_tree_release_paths(&opt);
+       free_pathspec(&opt.pathspec);
  
        return 0;
  }
@@@ -174,7 -175,10 +175,10 @@@ static void die_if_unmerged_cache(int r
  
  }
  
- static const char **parse_args(const char **argv, const char *prefix, const char **rev_ret)
+ static void parse_args(struct pathspec *pathspec,
+                      const char **argv, const char *prefix,
+                      int patch_mode,
+                      const char **rev_ret)
  {
        const char *rev = "HEAD";
        unsigned char unused[20];
                }
        }
        *rev_ret = rev;
-       return argv[0] ? get_pathspec(prefix, argv) : NULL;
+       parse_pathspec(pathspec, 0,
+                      PATHSPEC_PREFER_FULL |
+                      (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0),
+                      prefix, argv);
  }
  
  static int update_refs(const char *rev, const unsigned char *sha1)
@@@ -246,7 -253,7 +253,7 @@@ int cmd_reset(int argc, const char **ar
        int patch_mode = 0, unborn;
        const char *rev;
        unsigned char sha1[20];
-       const char **pathspec = NULL;
+       struct pathspec pathspec;
        const struct option options[] = {
                OPT__QUIET(&quiet, N_("be quiet, only report errors")),
                OPT_SET_INT(0, "mixed", &reset_type,
                                N_("reset HEAD, index and working tree"), MERGE),
                OPT_SET_INT(0, "keep", &reset_type,
                                N_("reset HEAD but keep local changes"), KEEP),
 -              OPT_BOOLEAN('p', "patch", &patch_mode, N_("select hunks interactively")),
 +              OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")),
                OPT_END()
        };
  
  
        argc = parse_options(argc, argv, prefix, options, git_reset_usage,
                                                PARSE_OPT_KEEP_DASHDASH);
-       pathspec = parse_args(argv, prefix, &rev);
+       parse_args(&pathspec, argv, prefix, patch_mode, &rev);
  
        unborn = !strcmp(rev, "HEAD") && get_sha1("HEAD", sha1);
        if (unborn) {
                /* reset on unborn branch: treat as reset to empty tree */
                hashcpy(sha1, EMPTY_TREE_SHA1_BIN);
-       } else if (!pathspec) {
+       } else if (!pathspec.nr) {
                struct commit *commit;
                if (get_sha1_committish(rev, sha1))
                        die(_("Failed to resolve '%s' as a valid revision."), rev);
        if (patch_mode) {
                if (reset_type != NONE)
                        die(_("--patch is incompatible with --{hard,mixed,soft}"));
-               return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", pathspec);
+               return run_add_interactive(sha1_to_hex(sha1), "--patch=reset", &pathspec);
        }
  
        /* git reset tree [--] paths... can be used to
         * load chosen paths from the tree into the index without
         * affecting the working tree nor HEAD. */
-       if (pathspec) {
+       if (pathspec.nr) {
                if (reset_type == MIXED)
                        warning(_("--mixed with paths is deprecated; use 'git reset -- <paths>' instead."));
                else if (reset_type != NONE)
                struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
                int newfd = hold_locked_index(lock, 1);
                if (reset_type == MIXED) {
-                       if (read_from_tree(pathspec, sha1))
+                       if (read_from_tree(&pathspec, sha1))
                                return 1;
                } else {
                        int err = reset_index(sha1, reset_type, quiet);
                        die(_("Could not write new index file."));
        }
  
-       if (!pathspec && !unborn) {
+       if (!pathspec.nr && !unborn) {
                /* Any resets without paths update HEAD to the head being
                 * switched to, saving the previous head in ORIG_HEAD before. */
                update_ref_status = update_refs(rev, sha1);
                if (reset_type == HARD && !update_ref_status && !quiet)
                        print_new_head_line(lookup_commit_reference(sha1));
        }
-       if (!pathspec)
+       if (!pathspec.nr)
                remove_branch_state();
  
        return update_ref_status;
diff --combined builtin/rm.c
index 18bf2189992439caafb66b103df9fbe3e76c4e37,c848dad1d01273e9b5f571d19cd65b3259a3e1b2..9b59ab3a64e00cfe3a6a73d6493001085c31d683
@@@ -11,6 -11,7 +11,7 @@@
  #include "parse-options.h"
  #include "string-list.h"
  #include "submodule.h"
+ #include "pathspec.h"
  
  static const char * const builtin_rm_usage[] = {
        N_("git rm [options] [--] <file>..."),
@@@ -58,21 -59,6 +59,21 @@@ static void print_error_files(struct st
        }
  }
  
 +static void error_removing_concrete_submodules(struct string_list *files, int *errs)
 +{
 +      print_error_files(files,
 +                        Q_("the following submodule (or one of its nested "
 +                           "submodules)\n"
 +                           "uses a .git directory:",
 +                           "the following submodules (or one of its nested "
 +                           "submodules)\n"
 +                           "use a .git directory:", files->nr),
 +                        _("\n(use 'rm -rf' if you really want to remove "
 +                          "it including all of its history)"),
 +                        errs);
 +      string_list_clear(files, 0);
 +}
 +
  static int check_submodules_use_gitfiles(void)
  {
        int i;
@@@ -82,7 -68,7 +83,7 @@@
        for (i = 0; i < list.nr; i++) {
                const char *name = list.entry[i].name;
                int pos;
 -              struct cache_entry *ce;
 +              const struct cache_entry *ce;
                struct stat st;
  
                pos = cache_name_pos(name, strlen(name));
                if (!submodule_uses_gitfile(name))
                        string_list_append(&files, name);
        }
 -      print_error_files(&files,
 -                        Q_("the following submodule (or one of its nested "
 -                           "submodules)\n uses a .git directory:",
 -                           "the following submodules (or one of its nested "
 -                           "submodules)\n use a .git directory:",
 -                           files.nr),
 -                        _("\n(use 'rm -rf' if you really want to remove "
 -                          "it including all of its history)"),
 -                        &errs);
 -      string_list_clear(&files, 0);
 +
 +      error_removing_concrete_submodules(&files, &errs);
  
        return errs;
  }
@@@ -127,7 -121,7 +128,7 @@@ static int check_local_mod(unsigned cha
        for (i = 0; i < list.nr; i++) {
                struct stat st;
                int pos;
 -              struct cache_entry *ce;
 +              const struct cache_entry *ce;
                const char *name = list.entry[i].name;
                unsigned char sha1[20];
                unsigned mode;
                            " or -f to force removal)"),
                          &errs);
        string_list_clear(&files_cached, 0);
 -      print_error_files(&files_submodule,
 -                        Q_("the following submodule (or one of its nested "
 -                           "submodule)\nuses a .git directory:",
 -                           "the following submodules (or one of its nested "
 -                           "submodule)\nuse a .git directory:",
 -                           files_submodule.nr),
 -                        _("\n(use 'rm -rf' if you really "
 -                          "want to remove it including all "
 -                          "of its history)"),
 -                        &errs);
 -      string_list_clear(&files_submodule, 0);
 +
 +      error_removing_concrete_submodules(&files_submodule, &errs);
 +
        print_error_files(&files_local,
                          Q_("the following file has local modifications:",
                             "the following files have local modifications:",
@@@ -267,20 -269,21 +268,21 @@@ static int ignore_unmatch = 0
  static struct option builtin_rm_options[] = {
        OPT__DRY_RUN(&show_only, N_("dry run")),
        OPT__QUIET(&quiet, N_("do not list removed files")),
 -      OPT_BOOLEAN( 0 , "cached",         &index_only, N_("only remove from the index")),
 +      OPT_BOOL( 0 , "cached",         &index_only, N_("only remove from the index")),
        OPT__FORCE(&force, N_("override the up-to-date check")),
 -      OPT_BOOLEAN('r', NULL,             &recursive,  N_("allow recursive removal")),
 -      OPT_BOOLEAN( 0 , "ignore-unmatch", &ignore_unmatch,
 +      OPT_BOOL('r', NULL,             &recursive,  N_("allow recursive removal")),
 +      OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch,
                                N_("exit with a zero status even if nothing matched")),
        OPT_END(),
  };
  
  int cmd_rm(int argc, const char **argv, const char *prefix)
  {
-       int i, newfd, seen_any;
-       const char **pathspec, *match;
+       int i, newfd;
+       struct pathspec pathspec;
        char *seen;
  
+       gitmodules_config();
        git_config(git_default_config, NULL);
  
        argc = parse_options(argc, argv, prefix, builtin_rm_options,
                }
        }
  
-       pathspec = get_pathspec(prefix, argv);
-       refresh_index(&the_index, REFRESH_QUIET, pathspec, NULL, NULL);
+       parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv);
+       refresh_index(&the_index, REFRESH_QUIET, &pathspec, NULL, NULL);
  
-       for (i = 0; pathspec[i] ; i++)
-               /* nothing */;
-       seen = xcalloc(i, 1);
+       seen = xcalloc(pathspec.nr, 1);
  
        for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
-               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+               if (!match_pathspec_depth(&pathspec, ce->name, ce_namelen(ce), 0, seen))
                        continue;
                ALLOC_GROW(list.entry, list.nr + 1, list.alloc);
                list.entry[list.nr].name = ce->name;
-               list.entry[list.nr++].is_submodule = S_ISGITLINK(ce->ce_mode);
+               list.entry[list.nr].is_submodule = S_ISGITLINK(ce->ce_mode);
+               if (list.entry[list.nr++].is_submodule &&
+                   !is_staging_gitmodules_ok())
+                       die (_("Please, stage your changes to .gitmodules or stash them to proceed"));
        }
  
-       seen_any = 0;
-       for (i = 0; (match = pathspec[i]) != NULL ; i++) {
-               if (!seen[i]) {
-                       if (!ignore_unmatch) {
-                               die(_("pathspec '%s' did not match any files"),
-                                   match);
+       if (pathspec.nr) {
+               const char *original;
+               int seen_any = 0;
+               for (i = 0; i < pathspec.nr; i++) {
+                       original = pathspec.items[i].original;
+                       if (!seen[i]) {
+                               if (!ignore_unmatch) {
+                                       die(_("pathspec '%s' did not match any files"),
+                                           original);
+                               }
                        }
+                       else {
+                               seen_any = 1;
+                       }
+                       if (!recursive && seen[i] == MATCHED_RECURSIVELY)
+                               die(_("not removing '%s' recursively without -r"),
+                                   *original ? original : ".");
                }
-               else {
-                       seen_any = 1;
-               }
-               if (!recursive && seen[i] == MATCHED_RECURSIVELY)
-                       die(_("not removing '%s' recursively without -r"),
-                           *match ? match : ".");
 -              if (! seen_any)
++              if (!seen_any)
+                       exit(0);
        }
-       if (!seen_any)
-               exit(0);
  
        /*
         * If not forced, the file, the index and the HEAD (if exists)
         * in the middle)
         */
        if (!index_only) {
-               int removed = 0;
+               int removed = 0, gitmodules_modified = 0;
                for (i = 0; i < list.nr; i++) {
                        const char *path = list.entry[i].name;
                        if (list.entry[i].is_submodule) {
                                if (is_empty_dir(path)) {
                                        if (!rmdir(path)) {
                                                removed = 1;
+                                               if (!remove_path_from_gitmodules(path))
+                                                       gitmodules_modified = 1;
                                                continue;
                                        }
                                } else {
                                        strbuf_addstr(&buf, path);
                                        if (!remove_dir_recursively(&buf, 0)) {
                                                removed = 1;
+                                               if (!remove_path_from_gitmodules(path))
+                                                       gitmodules_modified = 1;
                                                strbuf_release(&buf);
                                                continue;
-                                       }
+                                       } else if (!file_exists(path))
+                                               /* Submodule was removed by user */
+                                               if (!remove_path_from_gitmodules(path))
+                                                       gitmodules_modified = 1;
                                        strbuf_release(&buf);
                                        /* Fallthrough and let remove_path() fail. */
                                }
                        if (!removed)
                                die_errno("git rm: '%s'", path);
                }
+               if (gitmodules_modified)
+                       stage_updated_gitmodules();
        }
  
        if (active_cache_changed) {
diff --combined builtin/update-index.c
index c3179815164c1ba2b39f3f57690c607dfc3ea0b6,e79581879946e769a58c124e4a076efaf75df86c..e3a10d706d406845b74b2cc0c9e9a120be6adce0
@@@ -11,6 -11,7 +11,7 @@@
  #include "refs.h"
  #include "resolve-undo.h"
  #include "parse-options.h"
+ #include "pathspec.h"
  
  /*
   * Default to not allowing changes to the list of files. The
@@@ -83,7 -84,7 +84,7 @@@ static int process_lstat_error(const ch
        return error("lstat(\"%s\"): %s", path, strerror(errno));
  }
  
 -static int add_one_path(struct cache_entry *old, const char *path, int len, struct stat *st)
 +static int add_one_path(const struct cache_entry *old, const char *path, int len, struct stat *st)
  {
        int option, size;
        struct cache_entry *ce;
@@@ -142,7 -143,7 +143,7 @@@ static int process_directory(const cha
  
        /* Exact match: file or existing gitlink */
        if (pos >= 0) {
 -              struct cache_entry *ce = active_cache[pos];
 +              const struct cache_entry *ce = active_cache[pos];
                if (S_ISGITLINK(ce->ce_mode)) {
  
                        /* Do nothing to the index if there is no HEAD! */
        /* Inexact match: is there perhaps a subdirectory match? */
        pos = -pos-1;
        while (pos < active_nr) {
 -              struct cache_entry *ce = active_cache[pos++];
 +              const struct cache_entry *ce = active_cache[pos++];
  
                if (strncmp(ce->name, path, len))
                        break;
@@@ -183,7 -184,7 +184,7 @@@ static int process_path(const char *pat
  {
        int pos, len;
        struct stat st;
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
  
        len = strlen(path);
        if (has_symlink_leading_path(path, len))
@@@ -448,7 -449,7 +449,7 @@@ static int unresolve_one(const char *pa
                /* already merged */
                pos = unmerge_cache_entry_at(pos);
                if (pos < active_nr) {
 -                      struct cache_entry *ce = active_cache[pos];
 +                      const struct cache_entry *ce = active_cache[pos];
                        if (ce_stage(ce) &&
                            ce_namelen(ce) == namelen &&
                            !memcmp(ce->name, path, namelen))
                 */
                pos = -pos-1;
                if (pos < active_nr) {
 -                      struct cache_entry *ce = active_cache[pos];
 +                      const struct cache_entry *ce = active_cache[pos];
                        if (ce_namelen(ce) == namelen &&
                            !memcmp(ce->name, path, namelen)) {
                                fprintf(stderr,
@@@ -546,10 -547,11 +547,11 @@@ static int do_reupdate(int ac, const ch
         */
        int pos;
        int has_head = 1;
-       const char **paths = get_pathspec(prefix, av + 1);
        struct pathspec pathspec;
  
-       init_pathspec(&pathspec, paths);
+       parse_pathspec(&pathspec, 0,
+                      PATHSPEC_PREFER_CWD,
+                      prefix, av + 1);
  
        if (read_ref("HEAD", head_sha1))
                /* If there is no HEAD, that means it is an initial
                has_head = 0;
   redo:
        for (pos = 0; pos < active_nr; pos++) {
 -              struct cache_entry *ce = active_cache[pos];
 +              const struct cache_entry *ce = active_cache[pos];
                struct cache_entry *old = NULL;
                int save_nr;
  
diff --combined cache.h
index 558ccb91f675e87b079e54bc688706ce8b19fa44,3cff825d5c369f20dae01ab2dabedd98fc4381c1..bd6fb9f66418b5dc6441fb47fbeb63ae50f7f335
+++ b/cache.h
@@@ -189,6 -189,8 +189,8 @@@ struct cache_entry 
  #error "CE_EXTENDED_FLAGS out of range"
  #endif
  
+ struct pathspec;
  /*
   * Copy the sha1 and stat state of a cache entry from one to
   * another. But we never change the name, or the hash state!
@@@ -365,6 -367,9 +367,9 @@@ static inline enum object_type object_t
  #define GIT_NOTES_REWRITE_REF_ENVIRONMENT "GIT_NOTES_REWRITE_REF"
  #define GIT_NOTES_REWRITE_MODE_ENVIRONMENT "GIT_NOTES_REWRITE_MODE"
  #define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
+ #define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
+ #define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
+ #define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
  
  /*
   * This environment variable is expected to contain a boolean indicating
@@@ -412,6 -417,7 +417,7 @@@ extern void setup_work_tree(void)
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
  extern char *prefix_path(const char *prefix, int len, const char *path);
+ extern char *prefix_path_gently(const char *prefix, int len, int *remaining, const char *path);
  extern const char *prefix_filename(const char *prefix, int len, const char *path);
  extern int check_filename(const char *prefix, const char *name);
  extern void verify_filename(const char *prefix,
@@@ -425,8 -431,6 +431,8 @@@ extern int path_inside_repo(const char 
  extern int set_git_dir_init(const char *git_dir, const char *real_git_dir, int);
  extern int init_db(const char *template_dir, unsigned int flags);
  
 +extern void sanitize_stdfds(void);
 +
  #define alloc_nr(x) (((x)+16)*3/2)
  
  /*
  
  /* Initialize and use the cache information */
  extern int read_index(struct index_state *);
- extern int read_index_preload(struct index_state *, const char **pathspec);
+ extern int read_index_preload(struct index_state *, const struct pathspec *pathspec);
  extern int read_index_from(struct index_state *, const char *path);
  extern int is_index_unborn(struct index_state *);
  extern int read_index_unmerged(struct index_state *);
@@@ -478,7 -482,7 +484,7 @@@ extern int remove_file_from_index(struc
  extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags);
  extern int add_file_to_index(struct index_state *, const char *path, int flags);
  extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 -extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 +extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern int index_name_is_other(const struct index_state *, const char *, int);
  extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
  
  extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
  
- #define PATHSPEC_ONESTAR 1    /* the pathspec pattern satisfies GFNM_ONESTAR */
- struct pathspec {
-       const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
-       int nr;
-       unsigned int has_wildcard:1;
-       unsigned int recursive:1;
-       int max_depth;
-       struct pathspec_item {
-               const char *match;
-               int len;
-               int nowildcard_len;
-               int flags;
-       } *items;
- };
- extern int init_pathspec(struct pathspec *, const char **);
- extern void free_pathspec(struct pathspec *);
  extern int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec);
  
- extern int limit_pathspec_to_literal(void);
  #define HASH_WRITE_OBJECT 1
  #define HASH_FORMAT_CHECK 2
  extern int index_fd(unsigned char *sha1, int fd, struct stat *st, enum object_type type, const char *path, unsigned flags);
@@@ -540,7 -524,7 +526,7 @@@ extern void fill_stat_cache_info(struc
  #define REFRESH_IGNORE_MISSING        0x0008  /* ignore non-existent */
  #define REFRESH_IGNORE_SUBMODULES     0x0010  /* ignore submodules */
  #define REFRESH_IN_PORCELAIN  0x0020  /* user friendly output, not "needs update" */
- extern int refresh_index(struct index_state *, unsigned int flags, const char **pathspec, char *seen, const char *header_msg);
+ extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg);
  
  struct lock_file {
        struct lock_file *next;
@@@ -577,7 -561,6 +563,7 @@@ extern int assume_unchanged
  extern int prefer_symlink_refs;
  extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
 +extern int warn_on_object_refname_ambiguity;
  extern int shared_repository;
  extern const char *apply_default_whitespace;
  extern const char *apply_default_ignorewhitespace;
@@@ -761,7 -744,8 +747,8 @@@ int is_directory(const char *)
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
  const char *absolute_path(const char *path);
 -const char *relative_path(const char *abs, const char *base);
 +const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
+ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
  int normalize_path_copy(char *dst, const char *src);
  int longest_ancestor_length(const char *path, struct string_list *prefixes);
  char *strip_path_suffix(const char *path, const char *suffix);
@@@ -1038,6 -1022,68 +1025,6 @@@ struct pack_entry 
        struct packed_git *p;
  };
  
 -struct ref {
 -      struct ref *next;
 -      unsigned char old_sha1[20];
 -      unsigned char new_sha1[20];
 -      char *symref;
 -      unsigned int
 -              force:1,
 -              forced_update:1,
 -              deletion:1,
 -              matched:1;
 -
 -      /*
 -       * Order is important here, as we write to FETCH_HEAD
 -       * in numeric order. And the default NOT_FOR_MERGE
 -       * should be 0, so that xcalloc'd structures get it
 -       * by default.
 -       */
 -      enum {
 -              FETCH_HEAD_MERGE = -1,
 -              FETCH_HEAD_NOT_FOR_MERGE = 0,
 -              FETCH_HEAD_IGNORE = 1
 -      } fetch_head_status;
 -
 -      enum {
 -              REF_STATUS_NONE = 0,
 -              REF_STATUS_OK,
 -              REF_STATUS_REJECT_NONFASTFORWARD,
 -              REF_STATUS_REJECT_ALREADY_EXISTS,
 -              REF_STATUS_REJECT_NODELETE,
 -              REF_STATUS_REJECT_FETCH_FIRST,
 -              REF_STATUS_REJECT_NEEDS_FORCE,
 -              REF_STATUS_UPTODATE,
 -              REF_STATUS_REMOTE_REJECT,
 -              REF_STATUS_EXPECTING_REPORT
 -      } status;
 -      char *remote_status;
 -      struct ref *peer_ref; /* when renaming */
 -      char name[FLEX_ARRAY]; /* more */
 -};
 -
 -#define REF_NORMAL    (1u << 0)
 -#define REF_HEADS     (1u << 1)
 -#define REF_TAGS      (1u << 2)
 -
 -extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
 -
 -#define CONNECT_VERBOSE       (1u << 0)
 -extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
 -extern int finish_connect(struct child_process *conn);
 -extern int git_connection_is_socket(struct child_process *conn);
 -struct extra_have_objects {
 -      int nr, alloc;
 -      unsigned char (*array)[20];
 -};
 -extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
 -                                   struct ref **list, unsigned int flags,
 -                                   struct extra_have_objects *);
 -extern int server_supports(const char *feature);
 -extern int parse_feature_request(const char *features, const char *feature);
 -extern const char *server_feature_value(const char *feature, int *len_ret);
 -extern const char *parse_feature_value(const char *feature_list, const char *feature, int *len_ret);
 -
  extern struct packed_git *parse_pack_index(unsigned char *sha1, const char *idx_path);
  
  /* A hook for count-objects to report invalid files in pack directory */
@@@ -1070,9 -1116,7 +1057,9 @@@ extern int unpack_object_header(struct 
  
  struct object_info {
        /* Request */
 +      enum object_type *typep;
        unsigned long *sizep;
 +      unsigned long *disk_sizep;
  
        /* Response */
        enum {
@@@ -1116,15 -1160,11 +1103,15 @@@ extern int update_server_info(int)
  typedef int (*config_fn_t)(const char *, const char *, void *);
  extern int git_default_config(const char *, const char *, void *);
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
 +extern int git_config_from_buf(config_fn_t fn, const char *name,
 +                             const char *buf, size_t len, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern int git_config(config_fn_t fn, void *);
  extern int git_config_with_options(config_fn_t fn, void *,
 -                                 const char *filename, int respect_includes);
 +                                 const char *filename,
 +                                 const char *blob_ref,
 +                                 int respect_includes);
  extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
  extern int git_parse_ulong(const char *, unsigned long *);
  extern int git_config_int(const char *, const char *);
@@@ -1243,7 -1283,7 +1230,7 @@@ void packet_trace_identity(const char *
   * return 0 if success, 1 - if addition of a file failed and
   * ADD_FILES_IGNORE_ERRORS was specified in flags
   */
- int add_files_to_cache(const char *prefix, const char **pathspec, int flags);
+ int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
@@@ -1277,7 -1317,7 +1264,7 @@@ extern int ws_blank_line(const char *li
  #define ws_tab_width(rule)     ((rule) & WS_TAB_WIDTH_MASK)
  
  /* ls-files */
- int report_path_error(const char *ps_matched, const char **pathspec, const char *prefix);
+ int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
  void overlay_tree_on_cache(const char *tree_name, const char *prefix);
  
  char *alias_lookup(const char *alias);
diff --combined combine-diff.c
index 4fc16ad4f35c558daf5bf96865801828d10b4b9b,c973f5d07ec70d3c6b86defc3e1421ed2ea6673b..3b92c44880228a94f71a428b0f11bb1caf693c67
@@@ -10,7 -10,6 +10,7 @@@
  #include "refs.h"
  #include "userdiff.h"
  #include "sha1-array.h"
 +#include "revision.h"
  
  static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
  {
@@@ -166,7 -165,7 +166,7 @@@ static struct lline *coalesce_lines(str
  
        /*
         * Coalesce new lines into base by finding the LCS
 -       * - Create the table to run dynamic programing
 +       * - Create the table to run dynamic programming
         * - Compute the LCS
         * - Then reverse read the direction structure:
         *   - If we have MATCH, assign parent to base flag, and consume
@@@ -1306,7 -1305,7 +1306,7 @@@ void diff_tree_combined(const unsigned 
        int i, num_paths, needsep, show_log_first, num_parent = parents->nr;
  
        diffopts = *opt;
-       diff_tree_setup_paths(diffopts.pathspec.raw, &diffopts);
+       copy_pathspec(&diffopts.pathspec, &opt->pathspec);
        diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
        DIFF_OPT_SET(&diffopts, RECURSIVE);
        DIFF_OPT_CLR(&diffopts, ALLOW_EXTERNAL);
                free(tmp);
        }
  
-       diff_tree_release_paths(&diffopts);
+       free_pathspec(&diffopts.pathspec);
  }
  
  void diff_tree_combined_merge(const struct commit *commit, int dense,
                              struct rev_info *rev)
  {
 -      struct commit_list *parent = commit->parents;
 +      struct commit_list *parent = get_saved_parents(rev, commit);
        struct sha1_array parents = SHA1_ARRAY_INIT;
  
        while (parent) {
diff --combined commit.h
index f9504f70cc5b57de590099024556b9d2b6b2cdce,1f1350c4049ad0d2dedf759a0fe5f8dc5eb051da..90a5a3c361e02cd7a9562eff2ca1b15f77be0941
+++ b/commit.h
@@@ -6,7 -6,6 +6,7 @@@
  #include "strbuf.h"
  #include "decorate.h"
  #include "gpg-interface.h"
 +#include "string-list.h"
  
  struct commit_list {
        struct commit *item;
@@@ -62,9 -61,6 +62,9 @@@ struct commit_list *commit_list_insert_
                                    struct commit_list **list);
  void commit_list_sort_by_date(struct commit_list **list);
  
 +/* Shallow copy of the input list */
 +struct commit_list *copy_commit_list(struct commit_list *list);
 +
  void free_commit_list(struct commit_list *list);
  
  /* Commit formats */
@@@ -83,9 -79,6 +83,9 @@@ enum cmit_fmt 
  };
  
  struct pretty_print_context {
 +      /*
 +       * Callers should tweak these to change the behavior of pp_* functions.
 +       */
        enum cmit_fmt fmt;
        int abbrev;
        const char *subject;
        const char *output_encoding;
        struct string_list *mailmap;
        int color;
 +      struct ident_split *from_ident;
 +
 +      /*
 +       * Fields below here are manipulated internally by pp_* functions and
 +       * should not be counted on by callers.
 +       */
 +      struct string_list in_body_headers;
  };
  
  struct userformat_want {
@@@ -125,20 -111,20 +125,20 @@@ extern void userformat_find_requirement
  extern void format_commit_message(const struct commit *commit,
                                  const char *format, struct strbuf *sb,
                                  const struct pretty_print_context *context);
 -extern void pretty_print_commit(const struct pretty_print_context *pp,
 +extern void pretty_print_commit(struct pretty_print_context *pp,
                                const struct commit *commit,
                                struct strbuf *sb);
  extern void pp_commit_easy(enum cmit_fmt fmt, const struct commit *commit,
                           struct strbuf *sb);
 -void pp_user_info(const struct pretty_print_context *pp,
 +void pp_user_info(struct pretty_print_context *pp,
                  const char *what, struct strbuf *sb,
                  const char *line, const char *encoding);
 -void pp_title_line(const struct pretty_print_context *pp,
 +void pp_title_line(struct pretty_print_context *pp,
                   const char **msg_p,
                   struct strbuf *sb,
                   const char *encoding,
                   int need_8bit_cte);
 -void pp_remainder(const struct pretty_print_context *pp,
 +void pp_remainder(struct pretty_print_context *pp,
                  const char **msg_p,
                  struct strbuf *sb,
                  int indent);
@@@ -208,7 -194,7 +208,7 @@@ int in_merge_bases_many(struct commit *
  
  extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
  extern int run_add_interactive(const char *revision, const char *patch_mode,
-                              const char **pathspec);
+                              const struct pathspec *pathspec);
  
  static inline int single_parent(struct commit *commit)
  {
@@@ -268,6 -254,4 +268,6 @@@ extern void print_commit_list(struct co
   */
  extern void check_commit_signature(const struct commit* commit, struct signature_check *sigc);
  
 +int compare_commits_by_commit_date(const void *a_, const void *b_, void *unused);
 +
  #endif /* COMMIT_H */
diff --combined diff-lib.c
index f1a9053168dbf0f3b5db2b2e51525828408f88ac,0a95763063494914e30b326a87d5e007765d8fcb..346cac651da725af07a304194d8d8e04b2f080b7
@@@ -87,12 -87,10 +87,12 @@@ int run_diff_files(struct rev_info *rev
  {
        int entries, i;
        int diff_unmerged_stage = revs->max_count;
 -      int silent_on_removed = option & DIFF_SILENT_ON_REMOVED;
        unsigned ce_option = ((option & DIFF_RACY_IS_MODIFIED)
                              ? CE_MATCH_RACY_IS_DIRTY : 0);
  
 +      if (option & DIFF_SILENT_ON_REMOVED)
 +              handle_deprecated_show_diff_q(&revs->diffopt);
 +
        diff_set_mnemonic_prefix(&revs->diffopt, "i/", "w/");
  
        if (diff_unmerged_stage < 0)
                                        perror(ce->name);
                                        continue;
                                }
 -                              if (silent_on_removed)
 -                                      continue;
                                wt_mode = 0;
                        }
                        dpath->mode = wt_mode;
                                perror(ce->name);
                                continue;
                        }
 -                      if (silent_on_removed)
 -                              continue;
                        diff_addremove(&revs->diffopt, '-', ce->ce_mode,
                                       ce->sha1, !is_null_sha1(ce->sha1),
                                       ce->name, 0);
@@@ -474,7 -476,6 +474,6 @@@ static int diff_cache(struct rev_info *
        opts.dst_index = NULL;
        opts.pathspec = &revs->diffopt.pathspec;
        opts.pathspec->recursive = 1;
-       opts.pathspec->max_depth = -1;
  
        init_tree_desc(&t, tree->buffer, tree->size);
        return unpack_trees(1, &t, &opts);
@@@ -500,7 -501,7 +499,7 @@@ int do_diff_cache(const unsigned char *
        struct rev_info revs;
  
        init_revisions(&revs, NULL);
-       init_pathspec(&revs.prune_data, opt->pathspec.raw);
+       copy_pathspec(&revs.prune_data, &opt->pathspec);
        revs.diffopt = *opt;
  
        if (diff_cache(&revs, tree_sha1, NULL, 1))
diff --combined diff.h
index 5237d63a71efd2c04354850fb976b254e432a587,b8df245ad91a801a71f311369891d8f2c6c51000..44092c2176468257644a19f787038a1ff3033eaa
--- 1/diff.h
--- 2/diff.h
+++ b/diff.h
@@@ -5,6 -5,7 +5,7 @@@
  #define DIFF_H
  
  #include "tree-walk.h"
+ #include "pathspec.h"
  
  struct rev_info;
  struct diff_options;
@@@ -103,15 -104,12 +104,15 @@@ enum diff_words_type 
  };
  
  struct diff_options {
 -      const char *filter;
        const char *orderfile;
        const char *pickaxe;
        const char *single_follow;
        const char *a_prefix, *b_prefix;
        unsigned flags;
 +
 +      /* diff-filter bits */
 +      unsigned int filter;
 +
        int use_color;
        int context;
        int interhunkcontext;
@@@ -182,8 -180,6 +183,6 @@@ const char *diff_line_prefix(struct dif
  
  extern const char mime_boundary_leader[];
  
- extern void diff_tree_setup_paths(const char **paths, struct diff_options *);
- extern void diff_tree_release_paths(struct diff_options *);
  extern int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
                     const char *base, struct diff_options *opt);
  extern int diff_tree_sha1(const unsigned char *old, const unsigned char *new,
@@@ -341,8 -337,6 +340,8 @@@ extern int parse_rename_score(const cha
  
  extern long parse_algorithm_value(const char *value);
  
 +extern void handle_deprecated_show_diff_q(struct diff_options *);
 +
  extern int print_stat_summary(FILE *fp, int files,
                              int insertions, int deletions);
  extern void setup_diff_pager(struct diff_options *);
diff --combined dir.c
index 910bfcde4e432fae3bf57ec951258bc5a02bebb6,8543736debe797257b69b3805e8592d60d4ae8df..1128110a441538215e5bc046183b11b6b49f129e
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -11,6 -11,7 +11,7 @@@
  #include "dir.h"
  #include "refs.h"
  #include "wildmatch.h"
+ #include "pathspec.h"
  
  struct path_simplify {
        int len;
@@@ -51,26 -52,32 +52,32 @@@ int fnmatch_icase(const char *pattern, 
        return fnmatch(pattern, string, flags | (ignore_case ? FNM_CASEFOLD : 0));
  }
  
- inline int git_fnmatch(const char *pattern, const char *string,
-                      int flags, int prefix)
+ inline int git_fnmatch(const struct pathspec_item *item,
+                      const char *pattern, const char *string,
+                      int prefix)
  {
-       int fnm_flags = 0;
-       if (flags & GFNM_PATHNAME)
-               fnm_flags |= FNM_PATHNAME;
        if (prefix > 0) {
-               if (strncmp(pattern, string, prefix))
+               if (ps_strncmp(item, pattern, string, prefix))
                        return FNM_NOMATCH;
                pattern += prefix;
                string += prefix;
        }
-       if (flags & GFNM_ONESTAR) {
+       if (item->flags & PATHSPEC_ONESTAR) {
                int pattern_len = strlen(++pattern);
                int string_len = strlen(string);
                return string_len < pattern_len ||
-                      strcmp(pattern,
-                             string + string_len - pattern_len);
+                       ps_strcmp(item, pattern,
+                                 string + string_len - pattern_len);
        }
-       return fnmatch(pattern, string, fnm_flags);
+       if (item->magic & PATHSPEC_GLOB)
+               return wildmatch(pattern, string,
+                                WM_PATHNAME |
+                                (item->magic & PATHSPEC_ICASE ? WM_CASEFOLD : 0),
+                                NULL);
+       else
+               /* wildmatch has not learned no FNM_PATHNAME mode yet */
+               return fnmatch(pattern, string,
+                              item->magic & PATHSPEC_ICASE ? FNM_CASEFOLD : 0);
  }
  
  static int fnmatch_icase_mem(const char *pattern, int patternlen,
        return match_status;
  }
  
- static size_t common_prefix_len(const char **pathspec)
+ static size_t common_prefix_len(const struct pathspec *pathspec)
  {
-       const char *n, *first;
+       int n;
        size_t max = 0;
-       int literal = limit_pathspec_to_literal();
  
-       if (!pathspec)
-               return max;
-       first = *pathspec;
-       while ((n = *pathspec++)) {
-               size_t i, len = 0;
-               for (i = 0; first == n || i < max; i++) {
-                       char c = n[i];
-                       if (!c || c != first[i] || (!literal && is_glob_special(c)))
+       /*
+        * ":(icase)path" is treated as a pathspec full of
+        * wildcard. In other words, only prefix is considered common
+        * prefix. If the pathspec is abc/foo abc/bar, running in
+        * subdir xyz, the common prefix is still xyz, not xuz/abc as
+        * in non-:(icase).
+        */
+       GUARD_PATHSPEC(pathspec,
+                      PATHSPEC_FROMTOP |
+                      PATHSPEC_MAXDEPTH |
+                      PATHSPEC_LITERAL |
+                      PATHSPEC_GLOB |
+                      PATHSPEC_ICASE);
+       for (n = 0; n < pathspec->nr; n++) {
+               size_t i = 0, len = 0, item_len;
+               if (pathspec->items[n].magic & PATHSPEC_ICASE)
+                       item_len = pathspec->items[n].prefix;
+               else
+                       item_len = pathspec->items[n].nowildcard_len;
+               while (i < item_len && (n == 0 || i < max)) {
+                       char c = pathspec->items[n].match[i];
+                       if (c != pathspec->items[0].match[i])
                                break;
                        if (c == '/')
                                len = i + 1;
+                       i++;
                }
-               if (first == n || len < max) {
+               if (n == 0 || len < max) {
                        max = len;
                        if (!max)
                                break;
   * Returns a copy of the longest leading path common among all
   * pathspecs.
   */
- char *common_prefix(const char **pathspec)
+ char *common_prefix(const struct pathspec *pathspec)
  {
        unsigned long len = common_prefix_len(pathspec);
  
-       return len ? xmemdupz(*pathspec, len) : NULL;
+       return len ? xmemdupz(pathspec->items[0].match, len) : NULL;
  }
  
- int fill_directory(struct dir_struct *dir, const char **pathspec)
+ int fill_directory(struct dir_struct *dir, const struct pathspec *pathspec)
  {
        size_t len;
  
        len = common_prefix_len(pathspec);
  
        /* Read the directory and prune it */
-       read_directory(dir, pathspec ? *pathspec : "", len, pathspec);
+       read_directory(dir, pathspec->nr ? pathspec->_raw[0] : "", len, pathspec);
        return len;
  }
  
@@@ -171,113 -192,6 +192,6 @@@ int within_depth(const char *name, int 
        return 1;
  }
  
- /*
-  * Does 'match' match the given name?
-  * A match is found if
-  *
-  * (1) the 'match' string is leading directory of 'name', or
-  * (2) the 'match' string is a wildcard and matches 'name', or
-  * (3) the 'match' string is exactly the same as 'name'.
-  *
-  * and the return value tells which case it was.
-  *
-  * It returns 0 when there is no match.
-  */
- static int match_one(const char *match, const char *name, int namelen)
- {
-       int matchlen;
-       int literal = limit_pathspec_to_literal();
-       /* If the match was just the prefix, we matched */
-       if (!*match)
-               return MATCHED_RECURSIVELY;
-       if (ignore_case) {
-               for (;;) {
-                       unsigned char c1 = tolower(*match);
-                       unsigned char c2 = tolower(*name);
-                       if (c1 == '\0' || (!literal && is_glob_special(c1)))
-                               break;
-                       if (c1 != c2)
-                               return 0;
-                       match++;
-                       name++;
-                       namelen--;
-               }
-       } else {
-               for (;;) {
-                       unsigned char c1 = *match;
-                       unsigned char c2 = *name;
-                       if (c1 == '\0' || (!literal && is_glob_special(c1)))
-                               break;
-                       if (c1 != c2)
-                               return 0;
-                       match++;
-                       name++;
-                       namelen--;
-               }
-       }
-       /*
-        * If we don't match the matchstring exactly,
-        * we need to match by fnmatch
-        */
-       matchlen = strlen(match);
-       if (strncmp_icase(match, name, matchlen)) {
-               if (literal)
-                       return 0;
-               return !fnmatch_icase(match, name, 0) ? MATCHED_FNMATCH : 0;
-       }
-       if (namelen == matchlen)
-               return MATCHED_EXACTLY;
-       if (match[matchlen-1] == '/' || name[matchlen] == '/')
-               return MATCHED_RECURSIVELY;
-       return 0;
- }
- /*
-  * Given a name and a list of pathspecs, returns the nature of the
-  * closest (i.e. most specific) match of the name to any of the
-  * pathspecs.
-  *
-  * The caller typically calls this multiple times with the same
-  * pathspec and seen[] array but with different name/namelen
-  * (e.g. entries from the index) and is interested in seeing if and
-  * how each pathspec matches all the names it calls this function
-  * with.  A mark is left in the seen[] array for each pathspec element
-  * indicating the closest type of match that element achieved, so if
-  * seen[n] remains zero after multiple invocations, that means the nth
-  * pathspec did not match any names, which could indicate that the
-  * user mistyped the nth pathspec.
-  */
- int match_pathspec(const char **pathspec, const char *name, int namelen,
-               int prefix, char *seen)
- {
-       int i, retval = 0;
-       if (!pathspec)
-               return 1;
-       name += prefix;
-       namelen -= prefix;
-       for (i = 0; pathspec[i] != NULL; i++) {
-               int how;
-               const char *match = pathspec[i] + prefix;
-               if (seen && seen[i] == MATCHED_EXACTLY)
-                       continue;
-               how = match_one(match, name, namelen);
-               if (how) {
-                       if (retval < how)
-                               retval = how;
-                       if (seen && seen[i] < how)
-                               seen[i] = how;
-               }
-       }
-       return retval;
- }
  /*
   * Does 'match' match the given name?
   * A match is found if
@@@ -297,11 -211,44 +211,44 @@@ static int match_pathspec_item(const st
        const char *match = item->match + prefix;
        int matchlen = item->len - prefix;
  
+       /*
+        * The normal call pattern is:
+        * 1. prefix = common_prefix_len(ps);
+        * 2. prune something, or fill_directory
+        * 3. match_pathspec_depth()
+        *
+        * 'prefix' at #1 may be shorter than the command's prefix and
+        * it's ok for #2 to match extra files. Those extras will be
+        * trimmed at #3.
+        *
+        * Suppose the pathspec is 'foo' and '../bar' running from
+        * subdir 'xyz'. The common prefix at #1 will be empty, thanks
+        * to "../". We may have xyz/foo _and_ XYZ/foo after #2. The
+        * user does not want XYZ/foo, only the "foo" part should be
+        * case-insensitive. We need to filter out XYZ/foo here. In
+        * other words, we do not trust the caller on comparing the
+        * prefix part when :(icase) is involved. We do exact
+        * comparison ourselves.
+        *
+        * Normally the caller (common_prefix_len() in fact) does
+        * _exact_ matching on name[-prefix+1..-1] and we do not need
+        * to check that part. Be defensive and check it anyway, in
+        * case common_prefix_len is changed, or a new caller is
+        * introduced that does not use common_prefix_len.
+        *
+        * If the penalty turns out too high when prefix is really
+        * long, maybe change it to
+        * strncmp(match, name, item->prefix - prefix)
+        */
+       if (item->prefix && (item->magic & PATHSPEC_ICASE) &&
+           strncmp(item->match, name - prefix, item->prefix))
+               return 0;
        /* If the match was just the prefix, we matched */
        if (!*match)
                return MATCHED_RECURSIVELY;
  
-       if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
+       if (matchlen <= namelen && !ps_strncmp(item, match, name, matchlen)) {
                if (matchlen == namelen)
                        return MATCHED_EXACTLY;
  
        }
  
        if (item->nowildcard_len < item->len &&
-           !git_fnmatch(match, name,
-                        item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+           !git_fnmatch(item, match, name,
                         item->nowildcard_len - prefix))
                return MATCHED_FNMATCH;
  
@@@ -339,8 -285,17 +285,17 @@@ int match_pathspec_depth(const struct p
  {
        int i, retval = 0;
  
+       GUARD_PATHSPEC(ps,
+                      PATHSPEC_FROMTOP |
+                      PATHSPEC_MAXDEPTH |
+                      PATHSPEC_LITERAL |
+                      PATHSPEC_GLOB |
+                      PATHSPEC_ICASE);
        if (!ps->nr) {
-               if (!ps->recursive || ps->max_depth == -1)
+               if (!ps->recursive ||
+                   !(ps->magic & PATHSPEC_MAXDEPTH) ||
+                   ps->max_depth == -1)
                        return MATCHED_RECURSIVELY;
  
                if (within_depth(name, namelen, 0, ps->max_depth))
                if (seen && seen[i] == MATCHED_EXACTLY)
                        continue;
                how = match_pathspec_item(ps->items+i, prefix, name, namelen);
-               if (ps->recursive && ps->max_depth != -1 &&
+               if (ps->recursive &&
+                   (ps->magic & PATHSPEC_MAXDEPTH) &&
+                   ps->max_depth != -1 &&
                    how && how != MATCHED_FNMATCH) {
                        int len = ps->items[i].len;
                        if (name[len] == '/')
  /*
   * Return the length of the "simple" part of a path match limiter.
   */
static int simple_length(const char *match)
+ int simple_length(const char *match)
  {
        int len = -1;
  
        }
  }
  
static int no_wildcard(const char *string)
+ int no_wildcard(const char *string)
  {
        return string[simple_length(string)] == '\0';
  }
@@@ -933,7 -890,7 +890,7 @@@ enum exist_status 
   */
  static enum exist_status directory_exists_in_index_icase(const char *dirname, int len)
  {
 -      struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
 +      const struct cache_entry *ce = index_name_exists(&the_index, dirname, len + 1, ignore_case);
        unsigned char endchar;
  
        if (!ce)
@@@ -977,7 -934,7 +934,7 @@@ static enum exist_status directory_exis
        if (pos < 0)
                pos = -pos-1;
        while (pos < active_nr) {
 -              struct cache_entry *ce = active_cache[pos++];
 +              const struct cache_entry *ce = active_cache[pos++];
                unsigned char endchar;
  
                if (strncmp(ce->name, dirname, len))
@@@ -1113,7 -1070,7 +1070,7 @@@ static int exclude_matches_pathspec(con
  static int get_index_dtype(const char *path, int len)
  {
        int pos;
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
  
        ce = cache_name_exists(path, len, 0);
        if (ce) {
@@@ -1381,14 -1338,25 +1338,25 @@@ static int treat_leading_path(struct di
        return rc;
  }
  
- int read_directory(struct dir_struct *dir, const char *path, int len, const char **pathspec)
+ int read_directory(struct dir_struct *dir, const char *path, int len, const struct pathspec *pathspec)
  {
        struct path_simplify *simplify;
  
+       /*
+        * Check out create_simplify()
+        */
+       if (pathspec)
+               GUARD_PATHSPEC(pathspec,
+                              PATHSPEC_FROMTOP |
+                              PATHSPEC_MAXDEPTH |
+                              PATHSPEC_LITERAL |
+                              PATHSPEC_GLOB |
+                              PATHSPEC_ICASE);
        if (has_symlink_leading_path(path, len))
                return dir->nr;
  
-       simplify = create_simplify(pathspec);
+       simplify = create_simplify(pathspec ? pathspec->_raw : NULL);
        if (!len || treat_leading_path(dir, path, len, simplify))
                read_directory_recursive(dir, path, len, 0, simplify);
        free_simplify(simplify);
@@@ -1568,71 -1536,6 +1536,6 @@@ int remove_path(const char *name
        return 0;
  }
  
- static int pathspec_item_cmp(const void *a_, const void *b_)
- {
-       struct pathspec_item *a, *b;
-       a = (struct pathspec_item *)a_;
-       b = (struct pathspec_item *)b_;
-       return strcmp(a->match, b->match);
- }
- int init_pathspec(struct pathspec *pathspec, const char **paths)
- {
-       const char **p = paths;
-       int i;
-       memset(pathspec, 0, sizeof(*pathspec));
-       if (!p)
-               return 0;
-       while (*p)
-               p++;
-       pathspec->raw = paths;
-       pathspec->nr = p - paths;
-       if (!pathspec->nr)
-               return 0;
-       pathspec->items = xmalloc(sizeof(struct pathspec_item)*pathspec->nr);
-       for (i = 0; i < pathspec->nr; i++) {
-               struct pathspec_item *item = pathspec->items+i;
-               const char *path = paths[i];
-               item->match = path;
-               item->len = strlen(path);
-               item->flags = 0;
-               if (limit_pathspec_to_literal()) {
-                       item->nowildcard_len = item->len;
-               } else {
-                       item->nowildcard_len = simple_length(path);
-                       if (item->nowildcard_len < item->len) {
-                               pathspec->has_wildcard = 1;
-                               if (path[item->nowildcard_len] == '*' &&
-                                   no_wildcard(path + item->nowildcard_len + 1))
-                                       item->flags |= PATHSPEC_ONESTAR;
-                       }
-               }
-       }
-       qsort(pathspec->items, pathspec->nr,
-             sizeof(struct pathspec_item), pathspec_item_cmp);
-       return 0;
- }
- void free_pathspec(struct pathspec *pathspec)
- {
-       free(pathspec->items);
-       pathspec->items = NULL;
- }
- int limit_pathspec_to_literal(void)
- {
-       static int flag = -1;
-       if (flag < 0)
-               flag = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
-       return flag;
- }
  /*
   * Frees memory within dir which was allocated for exclude lists and
   * the exclude_stack.  Does not free dir itself.
diff --combined git.c
index 2025f77d0181ba646e3c793a693e5d71348b966c,cebf8827da1a614d601a500564cf77e7f23eda10..b3893e73c93c59431516bccb86e12e8f64d39319
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -147,6 -147,18 +147,18 @@@ static int handle_options(const char **
                        setenv(GIT_LITERAL_PATHSPECS_ENVIRONMENT, "0", 1);
                        if (envchanged)
                                *envchanged = 1;
+               } else if (!strcmp(cmd, "--glob-pathspecs")) {
+                       setenv(GIT_GLOB_PATHSPECS_ENVIRONMENT, "1", 1);
+                       if (envchanged)
+                               *envchanged = 1;
+               } else if (!strcmp(cmd, "--noglob-pathspecs")) {
+                       setenv(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, "1", 1);
+                       if (envchanged)
+                               *envchanged = 1;
+               } else if (!strcmp(cmd, "--icase-pathspecs")) {
+                       setenv(GIT_ICASE_PATHSPECS_ENVIRONMENT, "1", 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else if (!strcmp(cmd, "--shallow-file")) {
                        (*argv)++;
                        (*argc)--;
@@@ -324,7 -336,6 +336,7 @@@ static void handle_internal_command(in
                { "cat-file", cmd_cat_file, RUN_SETUP },
                { "check-attr", cmd_check_attr, RUN_SETUP },
                { "check-ignore", cmd_check_ignore, RUN_SETUP | NEED_WORK_TREE },
 +              { "check-mailmap", cmd_check_mailmap, RUN_SETUP },
                { "check-ref-format", cmd_check_ref_format },
                { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
                { "checkout-index", cmd_checkout_index,
@@@ -526,13 -537,6 +538,13 @@@ int main(int argc, char **av
        if (!cmd)
                cmd = "git-help";
  
 +      /*
 +       * Always open file descriptors 0/1/2 to avoid clobbering files
 +       * in die().  It also avoids messing up when the pipes are dup'ed
 +       * onto stdin/stdout/stderr in the child processes we spawn.
 +       */
 +      sanitize_stdfds();
 +
        git_setup_gettext();
  
        /*
diff --combined line-log.c
index d40c79dc2be2cc2a6f405bd9c399a929d1324dcd,843a334bf06464f08ceb1a6d68d1599db2a1f1f2..8b6e497b3ff187ee328cba395f30d3db7f86c0b4
@@@ -23,7 -23,7 +23,7 @@@ static void range_set_grow(struct range
  /* Either initialization would be fine */
  #define RANGE_SET_INIT {0}
  
 -static void range_set_init(struct range_set *rs, size_t prealloc)
 +void range_set_init(struct range_set *rs, size_t prealloc)
  {
        rs->alloc = rs->nr = 0;
        rs->ranges = NULL;
@@@ -31,7 -31,7 +31,7 @@@
                range_set_grow(rs, prealloc);
  }
  
 -static void range_set_release(struct range_set *rs)
 +void range_set_release(struct range_set *rs)
  {
        free(rs->ranges);
        rs->alloc = rs->nr = 0;
@@@ -56,7 -56,7 +56,7 @@@ static void range_set_move(struct range
  }
  
  /* tack on a _new_ range _at the end_ */
 -static void range_set_append_unsafe(struct range_set *rs, long a, long b)
 +void range_set_append_unsafe(struct range_set *rs, long a, long b)
  {
        assert(a <= b);
        range_set_grow(rs, 1);
@@@ -65,7 -65,7 +65,7 @@@
        rs->nr++;
  }
  
 -static void range_set_append(struct range_set *rs, long a, long b)
 +void range_set_append(struct range_set *rs, long a, long b)
  {
        assert(rs->nr == 0 || rs->ranges[rs->nr-1].end <= a);
        range_set_append_unsafe(rs, a, b);
@@@ -107,19 -107,16 +107,19 @@@ static void range_set_check_invariants(
   * In-place pass of sorting and merging the ranges in the range set,
   * to establish the invariants when we get the ranges from the user
   */
 -static void sort_and_merge_range_set(struct range_set *rs)
 +void sort_and_merge_range_set(struct range_set *rs)
  {
        int i;
 -      int o = 1; /* output cursor */
 +      int o = 0; /* output cursor */
  
        qsort(rs->ranges, rs->nr, sizeof(struct range), range_cmp);
  
 -      for (i = 1; i < rs->nr; i++) {
 -              if (rs->ranges[i].start <= rs->ranges[o-1].end) {
 -                      rs->ranges[o-1].end = rs->ranges[i].end;
 +      for (i = 0; i < rs->nr; i++) {
 +              if (rs->ranges[i].start == rs->ranges[i].end)
 +                      continue;
 +              if (o > 0 && rs->ranges[i].start <= rs->ranges[o-1].end) {
 +                      if (rs->ranges[o-1].end < rs->ranges[i].end)
 +                              rs->ranges[o-1].end = rs->ranges[i].end;
                } else {
                        rs->ranges[o].start = rs->ranges[i].start;
                        rs->ranges[o].end = rs->ranges[i].end;
@@@ -291,6 -288,7 +291,6 @@@ static void line_log_data_insert(struc
  
        if (p) {
                range_set_append_unsafe(&p->ranges, begin, end);
 -              sort_and_merge_range_set(&p->ranges);
                free(path);
                return;
        }
@@@ -564,14 -562,12 +564,14 @@@ parse_lines(struct commit *commit, cons
        struct nth_line_cb cb_data;
        struct string_list_item *item;
        struct line_log_data *ranges = NULL;
 +      struct line_log_data *p;
  
        for_each_string_list_item(item, args) {
                const char *name_part, *range_part;
                char *full_name;
                struct diff_filespec *spec;
                long begin = 0, end = 0;
 +              long anchor;
  
                name_part = skip_range_arg(item->string);
                if (!name_part || *name_part != ':' || !name_part[1])
                cb_data.lines = lines;
                cb_data.line_ends = ends;
  
 +              p = search_line_log_data(ranges, full_name, NULL);
 +              if (p && p->ranges.nr)
 +                      anchor = p->ranges.ranges[p->ranges.nr - 1].end + 1;
 +              else
 +                      anchor = 1;
 +
                if (parse_range_arg(range_part, nth_line, &cb_data,
 -                                  lines, &begin, &end,
 +                                  lines, anchor, &begin, &end,
                                    full_name))
                        die("malformed -L argument '%s'", range_part);
 +              if (lines < end || ((lines || begin) && lines < begin))
 +                      die("file %s has only %lu lines", name_part, lines);
                if (begin < 1)
                        begin = 1;
                if (end < 1)
                        end = lines;
                begin--;
 -              if (lines < end || lines < begin)
 -                      die("file %s has only %ld lines", name_part, lines);
                line_log_data_insert(&ranges, full_name, begin, end);
  
                free_filespec(spec);
                ends = NULL;
        }
  
 +      for (p = ranges; p; p = p->next)
 +              sort_and_merge_range_set(&p->ranges);
 +
        return ranges;
  }
  
@@@ -760,7 -747,7 +760,7 @@@ void line_log_init(struct rev_info *rev
                        r = r->next;
                }
                paths[count] = NULL;
-               init_pathspec(&rev->diffopt.pathspec, paths);
+               parse_pathspec(&rev->diffopt.pathspec, 0, 0, "", paths);
                free(paths);
        }
  }
diff --combined merge-recursive.c
index f95933b0aacce84a8b911634e5c40d5e937a322b,8395b9b08ac83f934b0e0c6fae4a9d83b0f0d6b2..40eb840a52acce0d21a17ce5a898e93f164aef97
@@@ -251,7 -251,7 +251,7 @@@ struct tree *write_tree_from_memory(str
                int i;
                fprintf(stderr, "BUG: There are unmerged index entries:\n");
                for (i = 0; i < active_nr; i++) {
 -                      struct cache_entry *ce = active_cache[i];
 +                      const struct cache_entry *ce = active_cache[i];
                        if (ce_stage(ce))
                                fprintf(stderr, "BUG: %d %.*s\n", ce_stage(ce),
                                        (int)ce_namelen(ce), ce->name);
  
        if (!cache_tree_fully_valid(active_cache_tree) &&
            cache_tree_update(active_cache_tree,
 -                            active_cache, active_nr, 0) < 0)
 +                            (const struct cache_entry * const *)active_cache,
 +                            active_nr, 0) < 0)
                die(_("error building trees"));
  
        result = lookup_tree(active_cache_tree->sha1);
@@@ -298,7 -297,7 +298,7 @@@ static int get_files_dirs(struct merge_
  {
        int n;
        struct pathspec match_all;
-       init_pathspec(&match_all, NULL);
+       memset(&match_all, 0, sizeof(match_all));
        if (read_tree_recursive(tree, "", 0, 0, &match_all, save_files_dirs, o))
                return 0;
        n = o->current_file_set.nr + o->current_directory_set.nr;
@@@ -340,7 -339,7 +340,7 @@@ static struct string_list *get_unmerged
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *item;
                struct stage_data *e;
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
  
diff --combined path.c
index 3d244d3e03ae5c3d5afe452f903ba204849ad03b,f4b49d6924b00707ccb041de9bbb30da4233d8e1..9fd28bcd080ef0a64f0b8d8a5d9d680a741d75c3
--- 1/path.c
--- 2/path.c
+++ b/path.c
@@@ -5,7 -5,13 +5,7 @@@
  #include "strbuf.h"
  #include "string-list.h"
  
 -#ifndef get_st_mode_bits
 -/*
 - * The replacement lstat(2) we use on Cygwin is incomplete and
 - * may return wrong permission bits. Most of the time we do not care,
 - * but the callsites of this wrapper do care.
 - */
 -int get_st_mode_bits(const char *path, int *mode)
 +static int get_st_mode_bits(const char *path, int *mode)
  {
        struct stat st;
        if (lstat(path, &st) < 0)
@@@ -13,6 -19,7 +13,6 @@@
        *mode = st.st_mode;
        return 0;
  }
 -#endif
  
  static char bad_path[] = "/bad-path/";
  
@@@ -434,100 -441,42 +434,100 @@@ int adjust_shared_perm(const char *path
        return 0;
  }
  
 -const char *relative_path(const char *abs, const char *base)
 +/*
 + * Give path as relative to prefix.
 + *
 + * The strbuf may or may not be used, so do not assume it contains the
 + * returned path.
 + */
 +const char *relative_path(const char *in, const char *prefix,
 +                        struct strbuf *sb)
  {
 -      static char buf[PATH_MAX + 1];
 +      int in_len = in ? strlen(in) : 0;
 +      int prefix_len = prefix ? strlen(prefix) : 0;
 +      int in_off = 0;
 +      int prefix_off = 0;
        int i = 0, j = 0;
  
 -      if (!base || !base[0])
 -              return abs;
 -      while (base[i]) {
 -              if (is_dir_sep(base[i])) {
 -                      if (!is_dir_sep(abs[j]))
 -                              return abs;
 -                      while (is_dir_sep(base[i]))
 +      if (!in_len)
 +              return "./";
 +      else if (!prefix_len)
 +              return in;
 +
 +      while (i < prefix_len && j < in_len && prefix[i] == in[j]) {
 +              if (is_dir_sep(prefix[i])) {
 +                      while (is_dir_sep(prefix[i]))
                                i++;
 -                      while (is_dir_sep(abs[j]))
 +                      while (is_dir_sep(in[j]))
                                j++;
 +                      prefix_off = i;
 +                      in_off = j;
 +              } else {
 +                      i++;
 +                      j++;
 +              }
 +      }
 +
 +      if (
 +          /* "prefix" seems like prefix of "in" */
 +          i >= prefix_len &&
 +          /*
 +           * but "/foo" is not a prefix of "/foobar"
 +           * (i.e. prefix not end with '/')
 +           */
 +          prefix_off < prefix_len) {
 +              if (j >= in_len) {
 +                      /* in="/a/b", prefix="/a/b" */
 +                      in_off = in_len;
 +              } else if (is_dir_sep(in[j])) {
 +                      /* in="/a/b/c", prefix="/a/b" */
 +                      while (is_dir_sep(in[j]))
 +                              j++;
 +                      in_off = j;
 +              } else {
 +                      /* in="/a/bbb/c", prefix="/a/b" */
 +                      i = prefix_off;
 +              }
 +      } else if (
 +                 /* "in" is short than "prefix" */
 +                 j >= in_len &&
 +                 /* "in" not end with '/' */
 +                 in_off < in_len) {
 +              if (is_dir_sep(prefix[i])) {
 +                      /* in="/a/b", prefix="/a/b/c/" */
 +                      while (is_dir_sep(prefix[i]))
 +                              i++;
 +                      in_off = in_len;
 +              }
 +      }
 +      in += in_off;
 +      in_len -= in_off;
 +
 +      if (i >= prefix_len) {
 +              if (!in_len)
 +                      return "./";
 +              else
 +                      return in;
 +      }
 +
 +      strbuf_reset(sb);
 +      strbuf_grow(sb, in_len);
 +
 +      while (i < prefix_len) {
 +              if (is_dir_sep(prefix[i])) {
 +                      strbuf_addstr(sb, "../");
 +                      while (is_dir_sep(prefix[i]))
 +                              i++;
                        continue;
 -              } else if (abs[j] != base[i]) {
 -                      return abs;
                }
                i++;
 -              j++;
        }
 -      if (
 -          /* "/foo" is a prefix of "/foo" */
 -          abs[j] &&
 -          /* "/foo" is not a prefix of "/foobar" */
 -          !is_dir_sep(base[i-1]) && !is_dir_sep(abs[j])
 -         )
 -              return abs;
 -      while (is_dir_sep(abs[j]))
 -              j++;
 -      if (!abs[j])
 -              strcpy(buf, ".");
 -      else
 -              strcpy(buf, abs + j);
 -      return buf;
 +      if (!is_dir_sep(prefix[prefix_len - 1]))
 +              strbuf_addstr(sb, "../");
 +
 +      strbuf_addstr(sb, in);
 +
 +      return sb->buf;
  }
  
  /*
   *
   * Note that this function is purely textual.  It does not follow symlinks,
   * verify the existence of the path, or make any system calls.
+  *
+  * prefix_len != NULL is for a specific case of prefix_pathspec():
+  * assume that src == dst and src[0..prefix_len-1] is already
+  * normalized, any time "../" eats up to the prefix_len part,
+  * prefix_len is reduced. In the end prefix_len is the remaining
+  * prefix that has not been overridden by user pathspec.
   */
- int normalize_path_copy(char *dst, const char *src)
+ int normalize_path_copy_len(char *dst, const char *src, int *prefix_len)
  {
        char *dst0;
  
                /* Windows: dst[-1] cannot be backslash anymore */
                while (dst0 < dst && dst[-1] != '/')
                        dst--;
+               if (prefix_len && *prefix_len > dst - dst0)
+                       *prefix_len = dst - dst0;
        }
        *dst = '\0';
        return 0;
  }
  
+ int normalize_path_copy(char *dst, const char *src)
+ {
+       return normalize_path_copy_len(dst, src, NULL);
+ }
  /*
   * path = Canonical absolute path
   * prefixes = string_list containing normalized, absolute paths without
diff --combined pathspec.c
index 6ea0867493ad3e8cfd613dd57fa5ac9649507b47,d9f41432221ab5f886aaf0a6e306cb263892ab33..4b32cc32cb5b0be495ac941c5e96d8aee8fd8ff9
@@@ -15,8 -15,8 +15,8 @@@
   * If seen[] has not already been written to, it may make sense
   * to use find_pathspecs_matching_against_index() instead.
   */
- void add_pathspec_matches_against_index(const char **pathspec,
-                                       char *seen, int specs)
+ void add_pathspec_matches_against_index(const struct pathspec *pathspec,
+                                       char *seen)
  {
        int num_unmatched = 0, i;
  
         * mistakenly think that the user gave a pathspec that did not match
         * anything.
         */
-       for (i = 0; i < specs; i++)
+       for (i = 0; i < pathspec->nr; i++)
                if (!seen[i])
                        num_unmatched++;
        if (!num_unmatched)
                return;
        for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
-               match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen);
+               match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen);
        }
  }
  
   * nature of the "closest" (i.e. most specific) matches which each of the
   * given pathspecs achieves against all items in the index.
   */
- char *find_pathspecs_matching_against_index(const char **pathspec)
+ char *find_pathspecs_matching_against_index(const struct pathspec *pathspec)
  {
-       char *seen;
-       int i;
-       for (i = 0; pathspec[i];  i++)
-               ; /* just counting */
-       seen = xcalloc(i, 1);
-       add_pathspec_matches_against_index(pathspec, seen, i);
+       char *seen = xcalloc(pathspec->nr, 1);
+       add_pathspec_matches_against_index(pathspec, seen);
        return seen;
  }
  
  /*
-  * Check the index to see whether path refers to a submodule, or
-  * something inside a submodule.  If the former, returns the path with
-  * any trailing slash stripped.  If the latter, dies with an error
-  * message.
+  * Magic pathspec
+  *
+  * Possible future magic semantics include stuff like:
+  *
+  *    { PATHSPEC_RECURSIVE, '*', "recursive" },
+  *    { PATHSPEC_REGEXP, '\0', "regexp" },
+  *
+  */
+ static struct pathspec_magic {
+       unsigned bit;
+       char mnemonic; /* this cannot be ':'! */
+       const char *name;
+ } pathspec_magic[] = {
+       { PATHSPEC_FROMTOP, '/', "top" },
+       { PATHSPEC_LITERAL,   0, "literal" },
+       { PATHSPEC_GLOB,   '\0', "glob" },
+       { PATHSPEC_ICASE,  '\0', "icase" },
+ };
+ /*
+  * Take an element of a pathspec and check for magic signatures.
+  * Append the result to the prefix. Return the magic bitmap.
+  *
+  * For now, we only parse the syntax and throw out anything other than
+  * "top" magic.
+  *
+  * NEEDSWORK: This needs to be rewritten when we start migrating
+  * get_pathspec() users to use the "struct pathspec" interface.  For
+  * example, a pathspec element may be marked as case-insensitive, but
+  * the prefix part must always match literally, and a single stupid
+  * string cannot express such a case.
   */
- const char *check_path_for_gitlink(const char *path)
+ static unsigned prefix_pathspec(struct pathspec_item *item,
+                               unsigned *p_short_magic,
+                               const char **raw, unsigned flags,
+                               const char *prefix, int prefixlen,
+                               const char *elt)
  {
-       int i, path_len = strlen(path);
-       for (i = 0; i < active_nr; i++) {
-               const struct cache_entry *ce = active_cache[i];
-               if (S_ISGITLINK(ce->ce_mode)) {
-                       int ce_len = ce_namelen(ce);
-                       if (path_len <= ce_len || path[ce_len] != '/' ||
-                           memcmp(ce->name, path, ce_len))
-                               /* path does not refer to this
-                                * submodule or anything inside it */
+       static int literal_global = -1;
+       static int glob_global = -1;
+       static int noglob_global = -1;
+       static int icase_global = -1;
+       unsigned magic = 0, short_magic = 0, global_magic = 0;
+       const char *copyfrom = elt, *long_magic_end = NULL;
+       char *match;
+       int i, pathspec_prefix = -1;
+       if (literal_global < 0)
+               literal_global = git_env_bool(GIT_LITERAL_PATHSPECS_ENVIRONMENT, 0);
+       if (literal_global)
+               global_magic |= PATHSPEC_LITERAL;
+       if (glob_global < 0)
+               glob_global = git_env_bool(GIT_GLOB_PATHSPECS_ENVIRONMENT, 0);
+       if (glob_global)
+               global_magic |= PATHSPEC_GLOB;
+       if (noglob_global < 0)
+               noglob_global = git_env_bool(GIT_NOGLOB_PATHSPECS_ENVIRONMENT, 0);
+       if (glob_global && noglob_global)
+               die(_("global 'glob' and 'noglob' pathspec settings are incompatible"));
+       if (icase_global < 0)
+               icase_global = git_env_bool(GIT_ICASE_PATHSPECS_ENVIRONMENT, 0);
+       if (icase_global)
+               global_magic |= PATHSPEC_ICASE;
+       if ((global_magic & PATHSPEC_LITERAL) &&
+           (global_magic & ~PATHSPEC_LITERAL))
+               die(_("global 'literal' pathspec setting is incompatible "
+                     "with all other global pathspec settings"));
+       if (elt[0] != ':' || literal_global) {
+               ; /* nothing to do */
+       } else if (elt[1] == '(') {
+               /* longhand */
+               const char *nextat;
+               for (copyfrom = elt + 2;
+                    *copyfrom && *copyfrom != ')';
+                    copyfrom = nextat) {
+                       size_t len = strcspn(copyfrom, ",)");
+                       if (copyfrom[len] == ',')
+                               nextat = copyfrom + len + 1;
+                       else
+                               /* handle ')' and '\0' */
+                               nextat = copyfrom + len;
+                       if (!len)
                                continue;
-                       if (path_len == ce_len + 1) {
-                               /* path refers to submodule;
-                                * strip trailing slash */
-                               return xstrndup(ce->name, ce_len);
+                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+                               if (strlen(pathspec_magic[i].name) == len &&
+                                   !strncmp(pathspec_magic[i].name, copyfrom, len)) {
+                                       magic |= pathspec_magic[i].bit;
+                                       break;
+                               }
+                               if (!prefixcmp(copyfrom, "prefix:")) {
+                                       char *endptr;
+                                       pathspec_prefix = strtol(copyfrom + 7,
+                                                                &endptr, 10);
+                                       if (endptr - copyfrom != len)
+                                               die(_("invalid parameter for pathspec magic 'prefix'"));
+                                       /* "i" would be wrong, but it does not matter */
+                                       break;
+                               }
+                       }
+                       if (ARRAY_SIZE(pathspec_magic) <= i)
+                               die(_("Invalid pathspec magic '%.*s' in '%s'"),
+                                   (int) len, copyfrom, elt);
+               }
+               if (*copyfrom != ')')
+                       die(_("Missing ')' at the end of pathspec magic in '%s'"), elt);
+               long_magic_end = copyfrom;
+               copyfrom++;
+       } else {
+               /* shorthand */
+               for (copyfrom = elt + 1;
+                    *copyfrom && *copyfrom != ':';
+                    copyfrom++) {
+                       char ch = *copyfrom;
+                       if (!is_pathspec_magic(ch))
+                               break;
+                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
+                               if (pathspec_magic[i].mnemonic == ch) {
+                                       short_magic |= pathspec_magic[i].bit;
+                                       break;
+                               }
+                       if (ARRAY_SIZE(pathspec_magic) <= i)
+                               die(_("Unimplemented pathspec magic '%c' in '%s'"),
+                                   ch, elt);
+               }
+               if (*copyfrom == ':')
+                       copyfrom++;
+       }
+       magic |= short_magic;
+       *p_short_magic = short_magic;
+       /* --noglob-pathspec adds :(literal) _unless_ :(glob) is specifed */
+       if (noglob_global && !(magic & PATHSPEC_GLOB))
+               global_magic |= PATHSPEC_LITERAL;
+       /* --glob-pathspec is overriden by :(literal) */
+       if ((global_magic & PATHSPEC_GLOB) && (magic & PATHSPEC_LITERAL))
+               global_magic &= ~PATHSPEC_GLOB;
+       magic |= global_magic;
+       if (pathspec_prefix >= 0 &&
+           (prefixlen || (prefix && *prefix)))
+               die("BUG: 'prefix' magic is supposed to be used at worktree's root");
+       if ((magic & PATHSPEC_LITERAL) && (magic & PATHSPEC_GLOB))
+               die(_("%s: 'literal' and 'glob' are incompatible"), elt);
+       if (pathspec_prefix >= 0) {
+               match = xstrdup(copyfrom);
+               prefixlen = pathspec_prefix;
+       } else if (magic & PATHSPEC_FROMTOP) {
+               match = xstrdup(copyfrom);
+               prefixlen = 0;
+       } else {
+               match = prefix_path_gently(prefix, prefixlen, &prefixlen, copyfrom);
+               if (!match)
+                       die(_("%s: '%s' is outside repository"), elt, copyfrom);
+       }
+       *raw = item->match = match;
+       /*
+        * Prefix the pathspec (keep all magic) and assign to
+        * original. Useful for passing to another command.
+        */
+       if (flags & PATHSPEC_PREFIX_ORIGIN) {
+               struct strbuf sb = STRBUF_INIT;
+               const char *start = elt;
+               if (prefixlen && !literal_global) {
+                       /* Preserve the actual prefix length of each pattern */
+                       if (long_magic_end) {
+                               strbuf_add(&sb, start, long_magic_end - start);
+                               strbuf_addf(&sb, ",prefix:%d", prefixlen);
+                               start = long_magic_end;
                        } else {
-                               die (_("Path '%s' is in submodule '%.*s'"),
-                                    path, ce_len, ce->name);
+                               if (*start == ':')
+                                       start++;
+                               strbuf_addf(&sb, ":(prefix:%d)", prefixlen);
                        }
                }
+               strbuf_add(&sb, start, copyfrom - start);
+               strbuf_addstr(&sb, match);
+               item->original = strbuf_detach(&sb, NULL);
+       } else
+               item->original = elt;
+       item->len = strlen(item->match);
+       item->prefix = prefixlen;
+       if ((flags & PATHSPEC_STRIP_SUBMODULE_SLASH_CHEAP) &&
+           (item->len >= 1 && item->match[item->len - 1] == '/') &&
+           (i = cache_name_pos(item->match, item->len - 1)) >= 0 &&
+           S_ISGITLINK(active_cache[i]->ce_mode)) {
+               item->len--;
+               match[item->len] = '\0';
+       }
+       if (flags & PATHSPEC_STRIP_SUBMODULE_SLASH_EXPENSIVE)
+               for (i = 0; i < active_nr; i++) {
+                       struct cache_entry *ce = active_cache[i];
+                       int ce_len = ce_namelen(ce);
+                       if (!S_ISGITLINK(ce->ce_mode))
+                               continue;
+                       if (item->len <= ce_len || match[ce_len] != '/' ||
+                           memcmp(ce->name, match, ce_len))
+                               continue;
+                       if (item->len == ce_len + 1) {
+                               /* strip trailing slash */
+                               item->len--;
+                               match[item->len] = '\0';
+                       } else
+                               die (_("Pathspec '%s' is in submodule '%.*s'"),
+                                    elt, ce_len, ce->name);
+               }
+       if (magic & PATHSPEC_LITERAL)
+               item->nowildcard_len = item->len;
+       else {
+               item->nowildcard_len = simple_length(item->match);
+               if (item->nowildcard_len < prefixlen)
+                       item->nowildcard_len = prefixlen;
+       }
+       item->flags = 0;
+       if (magic & PATHSPEC_GLOB) {
+               /*
+                * FIXME: should we enable ONESTAR in _GLOB for
+                * pattern "* * / * . c"?
+                */
+       } else {
+               if (item->nowildcard_len < item->len &&
+                   item->match[item->nowildcard_len] == '*' &&
+                   no_wildcard(item->match + item->nowildcard_len + 1))
+                       item->flags |= PATHSPEC_ONESTAR;
        }
-       return path;
+       /* sanity checks, pathspec matchers assume these are sane */
+       assert(item->nowildcard_len <= item->len &&
+              item->prefix         <= item->len);
+       return magic;
+ }
+ static int pathspec_item_cmp(const void *a_, const void *b_)
+ {
+       struct pathspec_item *a, *b;
+       a = (struct pathspec_item *)a_;
+       b = (struct pathspec_item *)b_;
+       return strcmp(a->match, b->match);
+ }
+ static void NORETURN unsupported_magic(const char *pattern,
+                                      unsigned magic,
+                                      unsigned short_magic)
+ {
+       struct strbuf sb = STRBUF_INIT;
+       int i, n;
+       for (n = i = 0; i < ARRAY_SIZE(pathspec_magic); i++) {
+               const struct pathspec_magic *m = pathspec_magic + i;
+               if (!(magic & m->bit))
+                       continue;
+               if (sb.len)
+                       strbuf_addstr(&sb, " ");
+               if (short_magic & m->bit)
+                       strbuf_addf(&sb, "'%c'", m->mnemonic);
+               else
+                       strbuf_addf(&sb, "'%s'", m->name);
+               n++;
+       }
+       /*
+        * We may want to substitute "this command" with a command
+        * name. E.g. when add--interactive dies when running
+        * "checkout -p"
+        */
+       die(_("%s: pathspec magic not supported by this command: %s"),
+           pattern, sb.buf);
  }
  
  /*
-  * Dies if the given path refers to a file inside a symlinked
-  * directory in the index.
+  * Given command line arguments and a prefix, convert the input to
+  * pathspec. die() if any magic in magic_mask is used.
   */
- void die_if_path_beyond_symlink(const char *path, const char *prefix)
+ void parse_pathspec(struct pathspec *pathspec,
+                   unsigned magic_mask, unsigned flags,
+                   const char *prefix, const char **argv)
  {
-       if (has_symlink_leading_path(path, strlen(path))) {
-               int len = prefix ? strlen(prefix) : 0;
-               die(_("'%s' is beyond a symbolic link"), path + len);
+       struct pathspec_item *item;
+       const char *entry = argv ? *argv : NULL;
+       int i, n, prefixlen;
+       memset(pathspec, 0, sizeof(*pathspec));
+       if (flags & PATHSPEC_MAXDEPTH_VALID)
+               pathspec->magic |= PATHSPEC_MAXDEPTH;
+       /* No arguments, no prefix -> no pathspec */
+       if (!entry && !prefix)
+               return;
+       if ((flags & PATHSPEC_PREFER_CWD) &&
+           (flags & PATHSPEC_PREFER_FULL))
+               die("BUG: PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
+       /* No arguments with prefix -> prefix pathspec */
+       if (!entry) {
+               static const char *raw[2];
+               if (flags & PATHSPEC_PREFER_FULL)
+                       return;
+               if (!(flags & PATHSPEC_PREFER_CWD))
+                       die("BUG: PATHSPEC_PREFER_CWD requires arguments");
+               pathspec->items = item = xmalloc(sizeof(*item));
+               memset(item, 0, sizeof(*item));
+               item->match = prefix;
+               item->original = prefix;
+               item->nowildcard_len = item->len = strlen(prefix);
+               item->prefix = item->len;
+               raw[0] = prefix;
+               raw[1] = NULL;
+               pathspec->nr = 1;
+               pathspec->_raw = raw;
+               return;
        }
+       n = 0;
+       while (argv[n])
+               n++;
+       pathspec->nr = n;
+       pathspec->items = item = xmalloc(sizeof(*item) * n);
+       pathspec->_raw = argv;
+       prefixlen = prefix ? strlen(prefix) : 0;
+       for (i = 0; i < n; i++) {
+               unsigned short_magic;
+               entry = argv[i];
+               item[i].magic = prefix_pathspec(item + i, &short_magic,
+                                               argv + i, flags,
+                                               prefix, prefixlen, entry);
+               if (item[i].magic & magic_mask)
+                       unsupported_magic(entry,
+                                         item[i].magic & magic_mask,
+                                         short_magic);
+               if ((flags & PATHSPEC_SYMLINK_LEADING_PATH) &&
+                   has_symlink_leading_path(item[i].match, item[i].len)) {
+                       die(_("pathspec '%s' is beyond a symbolic link"), entry);
+               }
+               if (item[i].nowildcard_len < item[i].len)
+                       pathspec->has_wildcard = 1;
+               pathspec->magic |= item[i].magic;
+       }
+       if (pathspec->magic & PATHSPEC_MAXDEPTH) {
+               if (flags & PATHSPEC_KEEP_ORDER)
+                       die("BUG: PATHSPEC_MAXDEPTH_VALID and PATHSPEC_KEEP_ORDER are incompatible");
+               qsort(pathspec->items, pathspec->nr,
+                     sizeof(struct pathspec_item), pathspec_item_cmp);
+       }
+ }
+ /*
+  * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
+  * based interface - see pathspec.c:parse_pathspec().
+  *
+  * Arguments:
+  *  - prefix - a path relative to the root of the working tree
+  *  - pathspec - a list of paths underneath the prefix path
+  *
+  * Iterates over pathspec, prepending each path with prefix,
+  * and return the resulting list.
+  *
+  * If pathspec is empty, return a singleton list containing prefix.
+  *
+  * If pathspec and prefix are both empty, return an empty list.
+  *
+  * This is typically used by built-in commands such as add.c, in order
+  * to normalize argv arguments provided to the built-in into a list of
+  * paths to process, all relative to the root of the working tree.
+  */
+ const char **get_pathspec(const char *prefix, const char **pathspec)
+ {
+       struct pathspec ps;
+       parse_pathspec(&ps,
+                      PATHSPEC_ALL_MAGIC &
+                      ~(PATHSPEC_FROMTOP | PATHSPEC_LITERAL),
+                      PATHSPEC_PREFER_CWD,
+                      prefix, pathspec);
+       return ps._raw;
+ }
+ void copy_pathspec(struct pathspec *dst, const struct pathspec *src)
+ {
+       *dst = *src;
+       dst->items = xmalloc(sizeof(struct pathspec_item) * dst->nr);
+       memcpy(dst->items, src->items,
+              sizeof(struct pathspec_item) * dst->nr);
+ }
+ void free_pathspec(struct pathspec *pathspec)
+ {
+       free(pathspec->items);
+       pathspec->items = NULL;
  }
diff --combined read-cache.c
index c3d5e3543fae75c81a7a0a00c48bb0076675a2f1,6ad2ff6cdd552a58b962c162d0c0dc78891ad9eb..737a27f81b4f46e46cd2107c1c2eca3b5c91e529
@@@ -489,7 -489,7 +489,7 @@@ int remove_index_entry_at(struct index_
  }
  
  /*
 - * Remove all cache ententries marked for removal, that is where
 + * Remove all cache entries marked for removal, that is where
   * CE_REMOVE is set in ce_flags.  This is much more effective than
   * calling remove_index_entry_at() for each entry to be removed.
   */
@@@ -722,7 -722,7 +722,7 @@@ struct cache_entry *make_cache_entry(un
        return ce;
  }
  
 -int ce_same_name(struct cache_entry *a, struct cache_entry *b)
 +int ce_same_name(const struct cache_entry *a, const struct cache_entry *b)
  {
        int len = ce_namelen(a);
        return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
@@@ -1114,7 -1114,8 +1114,8 @@@ static void show_file(const char * fmt
        printf(fmt, name);
  }
  
- int refresh_index(struct index_state *istate, unsigned int flags, const char **pathspec,
+ int refresh_index(struct index_state *istate, unsigned int flags,
+                 const struct pathspec *pathspec,
                  char *seen, const char *header_msg)
  {
        int i;
                        continue;
  
                if (pathspec &&
-                   !match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen))
+                   !match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, seen))
                        filtered = 1;
  
                if (ce_stage(ce)) {
@@@ -1760,7 -1761,7 +1761,7 @@@ static int has_racy_timestamp(struct in
  }
  
  /*
 - * Opportunisticly update the index but do not complain if we can't
 + * Opportunistically update the index but do not complain if we can't
   */
  void update_index_if_able(struct index_state *istate, struct lock_file *lockfile)
  {
diff --combined rerere.c
index 6fde8f94c27d5a3a196098af0f4716e27566dae5,4105bca86e367b4cd77c77f4ec7df4bda92ab794..1f2d21a72f56a68a7f9307bfd9092087687cffae
+++ b/rerere.c
@@@ -6,6 -6,7 +6,7 @@@
  #include "resolve-undo.h"
  #include "ll-merge.h"
  #include "attr.h"
+ #include "pathspec.h"
  
  #define RESOLVED 0
  #define PUNTED 1
@@@ -299,7 -300,7 +300,7 @@@ static int handle_cache(const char *pat
  {
        mmfile_t mmfile[3] = {{NULL}};
        mmbuffer_t result = {NULL, 0};
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
        int pos, len, i, hunk_no;
        struct rerere_io_mem io;
        int marker_size = ll_merge_marker_size(path);
  
  static int check_one_conflict(int i, int *type)
  {
 -      struct cache_entry *e = active_cache[i];
 +      const struct cache_entry *e = active_cache[i];
  
        if (!ce_stage(e)) {
                *type = RESOLVED;
  
        /* Only handle regular files with both stages #2 and #3 */
        if (i + 1 < active_nr) {
 -              struct cache_entry *e2 = active_cache[i];
 -              struct cache_entry *e3 = active_cache[i + 1];
 +              const struct cache_entry *e2 = active_cache[i];
 +              const struct cache_entry *e3 = active_cache[i + 1];
                if (ce_stage(e2) == 2 &&
                    ce_stage(e3) == 3 &&
                    ce_same_name(e, e3) &&
@@@ -398,7 -399,7 +399,7 @@@ static int find_conflict(struct string_
  
        for (i = 0; i < active_nr;) {
                int conflict_type;
 -              struct cache_entry *e = active_cache[i];
 +              const struct cache_entry *e = active_cache[i];
                i = check_one_conflict(i, &conflict_type);
                if (conflict_type == THREE_STAGED)
                        string_list_insert(conflict, (const char *)e->name);
@@@ -414,7 -415,7 +415,7 @@@ int rerere_remaining(struct string_lis
  
        for (i = 0; i < active_nr;) {
                int conflict_type;
 -              struct cache_entry *e = active_cache[i];
 +              const struct cache_entry *e = active_cache[i];
                i = check_one_conflict(i, &conflict_type);
                if (conflict_type == PUNTED)
                        string_list_insert(merge_rr, (const char *)e->name);
@@@ -656,7 -657,7 +657,7 @@@ static int rerere_forget_one_path(cons
        return 0;
  }
  
- int rerere_forget(const char **pathspec)
+ int rerere_forget(struct pathspec *pathspec)
  {
        int i, fd;
        struct string_list conflict = STRING_LIST_INIT_DUP;
        find_conflict(&conflict);
        for (i = 0; i < conflict.nr; i++) {
                struct string_list_item *it = &conflict.items[i];
-               if (!match_pathspec(pathspec, it->string, strlen(it->string),
-                                   0, NULL))
+               if (!match_pathspec_depth(pathspec, it->string, strlen(it->string),
+                                         0, NULL))
                        continue;
                rerere_forget_one_path(it->string, &merge_rr);
        }
diff --combined resolve-undo.c
index 77101f51c153ab014ecb80a02ca52daaa16cb10d,4b78e6f8b82002848b1d3472de870b74894b3d25..c09b00664e6892c84424bb4984152883460d3df6
@@@ -115,7 -115,7 +115,7 @@@ void resolve_undo_clear_index(struct in
  
  int unmerge_index_entry_at(struct index_state *istate, int pos)
  {
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
        struct string_list_item *item;
        struct resolve_undo_info *ru;
        int i, err = 0, matched;
@@@ -167,13 -167,13 +167,13 @@@ void unmerge_marked_index(struct index_
                return;
  
        for (i = 0; i < istate->cache_nr; i++) {
 -              struct cache_entry *ce = istate->cache[i];
 +              const struct cache_entry *ce = istate->cache[i];
                if (ce->ce_flags & CE_MATCHED)
                        i = unmerge_index_entry_at(istate, i);
        }
  }
  
- void unmerge_index(struct index_state *istate, const char **pathspec)
+ void unmerge_index(struct index_state *istate, const struct pathspec *pathspec)
  {
        int i;
  
                return;
  
        for (i = 0; i < istate->cache_nr; i++) {
 -              struct cache_entry *ce = istate->cache[i];
 +              const struct cache_entry *ce = istate->cache[i];
-               if (!match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, NULL))
+               if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
                        continue;
                i = unmerge_index_entry_at(istate, i);
        }
diff --combined revision.c
index ac20d1aaed7b5e1a52682dfd196f87e2f94e87c7,001623a9213293387bddd51a25039bfd6353d98c..6230a80a77bf261d47adcb97a8bd62bef4f37087
@@@ -15,7 -15,6 +15,7 @@@
  #include "string-list.h"
  #include "line-log.h"
  #include "mailmap.h"
 +#include "commit-slab.h"
  
  volatile show_early_output_fn_t show_early_output;
  
@@@ -1359,7 -1358,7 +1359,7 @@@ static void prepare_show_merge(struct r
        if (!active_nr)
                read_cache();
        for (i = 0; i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                if (!ce_stage(ce))
                        continue;
                if (ce_path_match(ce, &revs->prune_data)) {
                        i++;
        }
        free_pathspec(&revs->prune_data);
-       init_pathspec(&revs->prune_data, prune);
+       parse_pathspec(&revs->prune_data, PATHSPEC_ALL_MAGIC, 0, "", prune);
        revs->limited = 1;
  }
  
@@@ -2121,8 -2120,8 +2121,8 @@@ int setup_revisions(int argc, const cha
                 */
                ALLOC_GROW(prune_data.path, prune_data.nr+1, prune_data.alloc);
                prune_data.path[prune_data.nr++] = NULL;
-               init_pathspec(&revs->prune_data,
-                             get_pathspec(revs->prefix, prune_data.path));
+               parse_pathspec(&revs->prune_data, 0, 0,
+                              revs->prefix, prune_data.path);
        }
  
        if (revs->def == NULL)
                revs->limited = 1;
  
        if (revs->prune_data.nr) {
-               diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
+               copy_pathspec(&revs->pruning.pathspec, &revs->prune_data);
                /* Can't prune commits with rename following: the paths change.. */
                if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
                        revs->prune = 1;
                if (!revs->full_diff)
-                       diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
+                       copy_pathspec(&revs->diffopt.pathspec,
+                                     &revs->prune_data);
        }
        if (revs->combine_merges)
                revs->ignore_merges = 0;
@@@ -2764,7 -2764,7 +2765,7 @@@ static int commit_match(struct commit *
        return retval;
  }
  
 -static inline int want_ancestry(struct rev_info *revs)
 +static inline int want_ancestry(const struct rev_info *revs)
  {
        return (revs->rewrite_parents || revs->children.name);
  }
@@@ -2821,14 -2821,6 +2822,14 @@@ enum commit_action simplify_commit(stru
        if (action == commit_show &&
            !revs->show_all &&
            revs->prune && revs->dense && want_ancestry(revs)) {
 +              /*
 +               * --full-diff on simplified parents is no good: it
 +               * will show spurious changes from the commits that
 +               * were elided.  So we save the parents on the side
 +               * when --full-diff is in effect.
 +               */
 +              if (revs->full_diff)
 +                      save_parents(revs, commit);
                if (rewrite_parents(revs, commit, rewrite_one) < 0)
                        return commit_error;
        }
@@@ -2848,7 -2840,6 +2849,7 @@@ static struct commit *get_revision_1(st
                free(entry);
  
                if (revs->reflog_info) {
 +                      save_parents(revs, commit);
                        fake_reflog_parent(revs->reflog_info, commit);
                        commit->object.flags &= ~(ADDED | SEEN | SHOWN);
                }
@@@ -3048,8 -3039,6 +3049,8 @@@ struct commit *get_revision(struct rev_
        c = get_revision_internal(revs);
        if (c && revs->graph)
                graph_update(revs->graph, c);
 +      if (!c)
 +              free_saved_parents(revs);
        return c;
  }
  
@@@ -3081,54 -3070,3 +3082,54 @@@ void put_revision_mark(const struct rev
        fputs(mark, stdout);
        putchar(' ');
  }
 +
 +define_commit_slab(saved_parents, struct commit_list *);
 +
 +#define EMPTY_PARENT_LIST ((struct commit_list *)-1)
 +
 +void save_parents(struct rev_info *revs, struct commit *commit)
 +{
 +      struct commit_list **pp;
 +
 +      if (!revs->saved_parents_slab) {
 +              revs->saved_parents_slab = xmalloc(sizeof(struct saved_parents));
 +              init_saved_parents(revs->saved_parents_slab);
 +      }
 +
 +      pp = saved_parents_at(revs->saved_parents_slab, commit);
 +
 +      /*
 +       * When walking with reflogs, we may visit the same commit
 +       * several times: once for each appearance in the reflog.
 +       *
 +       * In this case, save_parents() will be called multiple times.
 +       * We want to keep only the first set of parents.  We need to
 +       * store a sentinel value for an empty (i.e., NULL) parent
 +       * list to distinguish it from a not-yet-saved list, however.
 +       */
 +      if (*pp)
 +              return;
 +      if (commit->parents)
 +              *pp = copy_commit_list(commit->parents);
 +      else
 +              *pp = EMPTY_PARENT_LIST;
 +}
 +
 +struct commit_list *get_saved_parents(struct rev_info *revs, const struct commit *commit)
 +{
 +      struct commit_list *parents;
 +
 +      if (!revs->saved_parents_slab)
 +              return commit->parents;
 +
 +      parents = *saved_parents_at(revs->saved_parents_slab, commit);
 +      if (parents == EMPTY_PARENT_LIST)
 +              return NULL;
 +      return parents;
 +}
 +
 +void free_saved_parents(struct rev_info *revs)
 +{
 +      if (revs->saved_parents_slab)
 +              clear_saved_parents(revs->saved_parents_slab);
 +}
diff --combined setup.c
index 5262319be69b3bf3fd2492dea3d30e7353d66a23,ff4499eaefaf628906d237808c4307018e29d005..f08dd649761f4d53abef66da052340a3c78d0fe5
+++ b/setup.c
@@@ -5,7 -5,19 +5,19 @@@
  static int inside_git_dir = -1;
  static int inside_work_tree = -1;
  
- static char *prefix_path_gently(const char *prefix, int len, const char *path)
+ /*
+  * Normalize "path", prepending the "prefix" for relative paths. If
+  * remaining_prefix is not NULL, return the actual prefix still
+  * remains in the path. For example, prefix = sub1/sub2/ and path is
+  *
+  *  foo          -> sub1/sub2/foo  (full prefix)
+  *  ../foo       -> sub1/foo       (remaining prefix is sub1/)
+  *  ../../bar    -> bar            (no remaining prefix)
+  *  ../../sub1/sub2/foo -> sub1/sub2/foo (but no remaining prefix)
+  *  `pwd`/../bar -> sub1/bar       (no remaining prefix)
+  */
+ char *prefix_path_gently(const char *prefix, int len,
+                        int *remaining_prefix, const char *path)
  {
        const char *orig = path;
        char *sanitized;
                const char *temp = real_path(path);
                sanitized = xmalloc(len + strlen(temp) + 1);
                strcpy(sanitized, temp);
+               if (remaining_prefix)
+                       *remaining_prefix = 0;
        } else {
                sanitized = xmalloc(len + strlen(path) + 1);
                if (len)
                        memcpy(sanitized, prefix, len);
                strcpy(sanitized + len, path);
+               if (remaining_prefix)
+                       *remaining_prefix = len;
        }
-       if (normalize_path_copy(sanitized, sanitized))
+       if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix))
                goto error_out;
        if (is_absolute_path(orig)) {
                size_t root_len, len, total;
@@@ -44,7 -60,7 +60,7 @@@
  
  char *prefix_path(const char *prefix, int len, const char *path)
  {
-       char *r = prefix_path_gently(prefix, len, path);
+       char *r = prefix_path_gently(prefix, len, NULL, path);
        if (!r)
                die("'%s' is outside repository", path);
        return r;
@@@ -53,7 -69,7 +69,7 @@@
  int path_inside_repo(const char *prefix, const char *path)
  {
        int len = prefix ? strlen(prefix) : 0;
-       char *r = prefix_path_gently(prefix, len, path);
+       char *r = prefix_path_gently(prefix, len, NULL, path);
        if (r) {
                free(r);
                return 1;
@@@ -154,155 -170,6 +170,6 @@@ void verify_non_filename(const char *pr
            "'git <command> [<revision>...] -- [<file>...]'", arg);
  }
  
- /*
-  * Magic pathspec
-  *
-  * NEEDSWORK: These need to be moved to dir.h or even to a new
-  * pathspec.h when we restructure get_pathspec() users to use the
-  * "struct pathspec" interface.
-  *
-  * Possible future magic semantics include stuff like:
-  *
-  *    { PATHSPEC_NOGLOB, '!', "noglob" },
-  *    { PATHSPEC_ICASE, '\0', "icase" },
-  *    { PATHSPEC_RECURSIVE, '*', "recursive" },
-  *    { PATHSPEC_REGEXP, '\0', "regexp" },
-  *
-  */
- #define PATHSPEC_FROMTOP    (1<<0)
- static struct pathspec_magic {
-       unsigned bit;
-       char mnemonic; /* this cannot be ':'! */
-       const char *name;
- } pathspec_magic[] = {
-       { PATHSPEC_FROMTOP, '/', "top" },
- };
- /*
-  * Take an element of a pathspec and check for magic signatures.
-  * Append the result to the prefix.
-  *
-  * For now, we only parse the syntax and throw out anything other than
-  * "top" magic.
-  *
-  * NEEDSWORK: This needs to be rewritten when we start migrating
-  * get_pathspec() users to use the "struct pathspec" interface.  For
-  * example, a pathspec element may be marked as case-insensitive, but
-  * the prefix part must always match literally, and a single stupid
-  * string cannot express such a case.
-  */
- static const char *prefix_pathspec(const char *prefix, int prefixlen, const char *elt)
- {
-       unsigned magic = 0;
-       const char *copyfrom = elt;
-       int i;
-       if (elt[0] != ':') {
-               ; /* nothing to do */
-       } else if (elt[1] == '(') {
-               /* longhand */
-               const char *nextat;
-               for (copyfrom = elt + 2;
-                    *copyfrom && *copyfrom != ')';
-                    copyfrom = nextat) {
-                       size_t len = strcspn(copyfrom, ",)");
-                       if (copyfrom[len] == ',')
-                               nextat = copyfrom + len + 1;
-                       else
-                               /* handle ')' and '\0' */
-                               nextat = copyfrom + len;
-                       if (!len)
-                               continue;
-                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
-                               if (strlen(pathspec_magic[i].name) == len &&
-                                   !strncmp(pathspec_magic[i].name, copyfrom, len)) {
-                                       magic |= pathspec_magic[i].bit;
-                                       break;
-                               }
-                       if (ARRAY_SIZE(pathspec_magic) <= i)
-                               die("Invalid pathspec magic '%.*s' in '%s'",
-                                   (int) len, copyfrom, elt);
-               }
-               if (*copyfrom != ')')
-                       die("Missing ')' at the end of pathspec magic in '%s'", elt);
-               copyfrom++;
-       } else {
-               /* shorthand */
-               for (copyfrom = elt + 1;
-                    *copyfrom && *copyfrom != ':';
-                    copyfrom++) {
-                       char ch = *copyfrom;
-                       if (!is_pathspec_magic(ch))
-                               break;
-                       for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
-                               if (pathspec_magic[i].mnemonic == ch) {
-                                       magic |= pathspec_magic[i].bit;
-                                       break;
-                               }
-                       if (ARRAY_SIZE(pathspec_magic) <= i)
-                               die("Unimplemented pathspec magic '%c' in '%s'",
-                                   ch, elt);
-               }
-               if (*copyfrom == ':')
-                       copyfrom++;
-       }
-       if (magic & PATHSPEC_FROMTOP)
-               return xstrdup(copyfrom);
-       else
-               return prefix_path(prefix, prefixlen, copyfrom);
- }
- /*
-  * N.B. get_pathspec() is deprecated in favor of the "struct pathspec"
-  * based interface - see pathspec_magic above.
-  *
-  * Arguments:
-  *  - prefix - a path relative to the root of the working tree
-  *  - pathspec - a list of paths underneath the prefix path
-  *
-  * Iterates over pathspec, prepending each path with prefix,
-  * and return the resulting list.
-  *
-  * If pathspec is empty, return a singleton list containing prefix.
-  *
-  * If pathspec and prefix are both empty, return an empty list.
-  *
-  * This is typically used by built-in commands such as add.c, in order
-  * to normalize argv arguments provided to the built-in into a list of
-  * paths to process, all relative to the root of the working tree.
-  */
- const char **get_pathspec(const char *prefix, const char **pathspec)
- {
-       const char *entry = *pathspec;
-       const char **src, **dst;
-       int prefixlen;
-       if (!prefix && !entry)
-               return NULL;
-       if (!entry) {
-               static const char *spec[2];
-               spec[0] = prefix;
-               spec[1] = NULL;
-               return spec;
-       }
-       /* Otherwise we have to re-write the entries.. */
-       src = pathspec;
-       dst = pathspec;
-       prefixlen = prefix ? strlen(prefix) : 0;
-       while (*src) {
-               *(dst++) = prefix_pathspec(prefix, prefixlen, *src);
-               src++;
-       }
-       *dst = NULL;
-       if (!*pathspec)
-               return NULL;
-       return pathspec;
- }
  
  /*
   * Test if it looks like we're at a git directory.
@@@ -360,7 -227,6 +227,7 @@@ int is_inside_work_tree(void
  
  void setup_work_tree(void)
  {
 +      struct strbuf sb = STRBUF_INIT;
        const char *work_tree, *git_dir;
        static int initialized = 0;
  
        if (getenv(GIT_WORK_TREE_ENVIRONMENT))
                setenv(GIT_WORK_TREE_ENVIRONMENT, ".", 1);
  
 -      set_git_dir(relative_path(git_dir, work_tree));
 +      set_git_dir(relative_path(git_dir, work_tree, &sb));
        initialized = 1;
 +
 +      strbuf_release(&sb);
  }
  
  static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
@@@ -911,15 -775,3 +778,15 @@@ const char *resolve_gitdir(const char *
                return suspect;
        return read_gitfile(suspect);
  }
 +
 +/* if any standard file descriptor is missing open it to /dev/null */
 +void sanitize_stdfds(void)
 +{
 +      int fd = open("/dev/null", O_RDWR, 0);
 +      while (fd != -1 && fd < 2)
 +              fd = dup(fd);
 +      if (fd == -1)
 +              die_errno("open /dev/null or dup failed");
 +      if (fd > 2)
 +              close(fd);
 +}
diff --combined submodule.c
index c0f93c208c11b2dad0d508fcb7adf14873484385,0494492bd07e4b4e954d0d01c35447e9e03872b6..1905d75b2b09ee7e1503b35bf072710a659ea1ef
@@@ -10,6 -10,7 +10,7 @@@
  #include "string-list.h"
  #include "sha1-array.h"
  #include "argv-array.h"
+ #include "blob.h"
  
  static struct string_list config_name_for_path;
  static struct string_list config_fetch_recurse_submodules_for_name;
@@@ -30,6 -31,118 +31,118 @@@ static struct sha1_array ref_tips_after
   */
  static int gitmodules_is_unmerged;
  
+ /*
+  * This flag is set if the .gitmodules file had unstaged modifications on
+  * startup. This must be checked before allowing modifications to the
+  * .gitmodules file with the intention to stage them later, because when
+  * continuing we would stage the modifications the user didn't stage herself
+  * too. That might change in a future version when we learn to stage the
+  * changes we do ourselves without staging any previous modifications.
+  */
+ static int gitmodules_is_modified;
+ int is_staging_gitmodules_ok(void)
+ {
+       return !gitmodules_is_modified;
+ }
+ /*
+  * Try to update the "path" entry in the "submodule.<name>" section of the
+  * .gitmodules file. Return 0 only if a .gitmodules file was found, a section
+  * with the correct path=<oldpath> setting was found and we could update it.
+  */
+ int update_path_in_gitmodules(const char *oldpath, const char *newpath)
+ {
+       struct strbuf entry = STRBUF_INIT;
+       struct string_list_item *path_option;
+       if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
+               return -1;
+       if (gitmodules_is_unmerged)
+               die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
+       path_option = unsorted_string_list_lookup(&config_name_for_path, oldpath);
+       if (!path_option) {
+               warning(_("Could not find section in .gitmodules where path=%s"), oldpath);
+               return -1;
+       }
+       strbuf_addstr(&entry, "submodule.");
+       strbuf_addstr(&entry, path_option->util);
+       strbuf_addstr(&entry, ".path");
+       if (git_config_set_in_file(".gitmodules", entry.buf, newpath) < 0) {
+               /* Maybe the user already did that, don't error out here */
+               warning(_("Could not update .gitmodules entry %s"), entry.buf);
+               strbuf_release(&entry);
+               return -1;
+       }
+       strbuf_release(&entry);
+       return 0;
+ }
+ /*
+  * Try to remove the "submodule.<name>" section from .gitmodules where the given
+  * path is configured. Return 0 only if a .gitmodules file was found, a section
+  * with the correct path=<path> setting was found and we could remove it.
+  */
+ int remove_path_from_gitmodules(const char *path)
+ {
+       struct strbuf sect = STRBUF_INIT;
+       struct string_list_item *path_option;
+       if (!file_exists(".gitmodules")) /* Do nothing without .gitmodules */
+               return -1;
+       if (gitmodules_is_unmerged)
+               die(_("Cannot change unmerged .gitmodules, resolve merge conflicts first"));
+       path_option = unsorted_string_list_lookup(&config_name_for_path, path);
+       if (!path_option) {
+               warning(_("Could not find section in .gitmodules where path=%s"), path);
+               return -1;
+       }
+       strbuf_addstr(&sect, "submodule.");
+       strbuf_addstr(&sect, path_option->util);
+       if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) {
+               /* Maybe the user already did that, don't error out here */
+               warning(_("Could not remove .gitmodules entry for %s"), path);
+               strbuf_release(&sect);
+               return -1;
+       }
+       strbuf_release(&sect);
+       return 0;
+ }
+ void stage_updated_gitmodules(void)
+ {
+       struct strbuf buf = STRBUF_INIT;
+       struct stat st;
+       int pos;
+       struct cache_entry *ce;
+       int namelen = strlen(".gitmodules");
+       pos = cache_name_pos(".gitmodules", namelen);
+       if (pos < 0) {
+               warning(_("could not find .gitmodules in index"));
+               return;
+       }
+       ce = active_cache[pos];
+       ce->ce_flags = namelen;
+       if (strbuf_read_file(&buf, ".gitmodules", 0) < 0)
+               die(_("reading updated .gitmodules failed"));
+       if (lstat(".gitmodules", &st) < 0)
+               die_errno(_("unable to stat updated .gitmodules"));
+       fill_stat_cache_info(ce, &st);
+       ce->ce_mode = ce_mode_from_stat(ce, st.st_mode);
+       if (remove_cache_entry_at(pos) < 0)
+               die(_("unable to remove .gitmodules from index"));
+       if (write_sha1_file(buf.buf, buf.len, blob_type, ce->sha1))
+               die(_("adding updated .gitmodules failed"));
+       if (add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE))
+               die(_("staging updated .gitmodules failed"));
+ }
  static int add_submodule_odb(const char *path)
  {
        struct strbuf objects_directory = STRBUF_INIT;
@@@ -116,6 -229,11 +229,11 @@@ void gitmodules_config(void
                                    !memcmp(ce->name, ".gitmodules", 11))
                                        gitmodules_is_unmerged = 1;
                        }
+               } else if (pos < active_nr) {
+                       struct stat st;
+                       if (lstat(".gitmodules", &st) == 0 &&
+                           ce_match_stat(active_cache[pos], &st, 0) & DATA_CHANGED)
+                               gitmodules_is_modified = 1;
                }
  
                if (!gitmodules_is_unmerged)
@@@ -134,9 -252,6 +252,9 @@@ int parse_submodule_config_option(cons
                return 0;
  
        if (!strcmp(key, "path")) {
 +              if (!value)
 +                      return config_error_nonbool(var);
 +
                config = unsorted_string_list_lookup(&config_name_for_path, value);
                if (config)
                        free(config->util);
        } else if (!strcmp(key, "ignore")) {
                char *name_cstr;
  
 +              if (!value)
 +                      return config_error_nonbool(var);
 +
                if (strcmp(value, "untracked") && strcmp(value, "dirty") &&
                    strcmp(value, "all") && strcmp(value, "none")) {
                        warning("Invalid parameter \"%s\" for config option \"submodule.%s.ignore\"", value, var);
@@@ -630,7 -742,7 +748,7 @@@ int fetch_populated_submodules(const st
                struct strbuf submodule_path = STRBUF_INIT;
                struct strbuf submodule_git_dir = STRBUF_INIT;
                struct strbuf submodule_prefix = STRBUF_INIT;
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                const char *git_dir, *name, *default_argv;
  
                if (!S_ISGITLINK(ce->ce_mode))
@@@ -1010,3 -1122,34 +1128,34 @@@ int merge_submodule(unsigned char resul
        free(merges.objects);
        return 0;
  }
+ /* 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 rel_path = STRBUF_INIT;
+       const char *real_work_tree = xstrdup(real_path(work_tree));
+       FILE *fp;
+       /* Update gitfile */
+       strbuf_addf(&file_name, "%s/.git", work_tree);
+       fp = fopen(file_name.buf, "w");
+       if (!fp)
+               die(_("Could not create git link %s"), file_name.buf);
+       fprintf(fp, "gitdir: %s\n", relative_path(git_dir, real_work_tree,
+                                                 &rel_path));
+       fclose(fp);
+       /* Update core.worktree setting */
+       strbuf_reset(&file_name);
+       strbuf_addf(&file_name, "%s/config", git_dir);
+       if (git_config_set_in_file(file_name.buf, "core.worktree",
+                                  relative_path(real_work_tree, git_dir,
+                                                &rel_path)))
+               die(_("Could not set core.worktree in %s"),
+                   file_name.buf);
+       strbuf_release(&file_name);
+       strbuf_release(&rel_path);
+       free((void *)real_work_tree);
+ }
diff --combined t/t0008-ignores.sh
index c29342d6bcd005ecc0eaee0ad5e17f65cdf45c3d,2ced8e9729f2c7b95176657bfe7bb86e3681727a..96f40fedfb7285b50243a1c1bbe64649ef7f8edb
@@@ -432,7 -432,7 +432,7 @@@ test_expect_success_multi SYMLINKS 'sym
  
  test_expect_success_multi SYMLINKS 'beyond a symlink' '' '
        test_check_ignore "a/symlink/foo" 128 &&
-       test_stderr "fatal: '\''a/symlink/foo'\'' is beyond a symbolic link"
+       test_stderr "fatal: pathspec '\''a/symlink/foo'\'' is beyond a symbolic link"
  '
  
  test_expect_success_multi SYMLINKS 'beyond a symlink from subdirectory' '' '
                cd a &&
                test_check_ignore "symlink/foo" 128
        ) &&
-       test_stderr "fatal: '\''symlink/foo'\'' is beyond a symbolic link"
+       test_stderr "fatal: pathspec '\''symlink/foo'\'' is beyond a symbolic link"
  '
  
  ############################################################################
  
  test_expect_success_multi 'submodule' '' '
        test_check_ignore "a/submodule/one" 128 &&
-       test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+       test_stderr "fatal: Pathspec '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
  '
  
  test_expect_success_multi 'submodule from subdirectory' '' '
                cd a &&
                test_check_ignore "submodule/one" 128
        ) &&
-       test_stderr "fatal: Path '\''a/submodule/one'\'' is in submodule '\''a/submodule'\''"
+       test_stderr "fatal: Pathspec '\''submodule/one'\'' is in submodule '\''a/submodule'\''"
  '
  
  ############################################################################
@@@ -697,21 -697,13 +697,21 @@@ test_expect_success PIPE 'streaming sup
        # shell, and then echo to the fd. We make sure to close it at
        # the end, so that the subprocess does get EOF and dies
        # properly.
 +      #
 +      # Similarly, we must keep "out" open so that check-ignore does
 +      # not ever get SIGPIPE trying to write to us. Not only would that
 +      # produce incorrect results, but then there would be no writer on the
 +      # other end of the pipe, and we would potentially block forever trying
 +      # to open it.
        exec 9>in &&
 +      exec 8<out &&
        test_when_finished "exec 9>&-" &&
 +      test_when_finished "exec 8<&-" &&
        echo >&9 one &&
 -      read response <out &&
 +      read response <&8 &&
        echo "$response" | grep "^\.gitignore:1:one     one" &&
        echo >&9 two &&
 -      read response <out &&
 +      read response <&8 &&
        echo "$response" | grep "^::    two"
  '
  
index a39d07446508598dded68561a946e8385a7830c3,a9dd7306c5e58257d4e4ee0d8f25890c62ab23a5..4192fe0ec6025979caea01885925c1786a4fcf63
@@@ -18,16 -18,6 +18,16 @@@ test_expect_success 'setup - initial co
        git branch initial
  '
  
 +test_expect_success 'configuration parsing' '
 +      test_when_finished "rm -f .gitmodules" &&
 +      cat >.gitmodules <<-\EOF &&
 +      [submodule "s"]
 +              path
 +              ignore
 +      EOF
 +      test_must_fail git status
 +'
 +
  test_expect_success 'setup - repository in init subdirectory' '
        mkdir init &&
        (
@@@ -783,13 -773,11 +783,11 @@@ test_expect_success 'submodule add --na
                        test_cmp expect .git
                ) &&
                echo "repo" >expect &&
-               git config -f .gitmodules submodule.repo.path >actual &&
-               test_cmp expect actual &&
+               test_must_fail git config -f .gitmodules submodule.repo.path &&
                git config -f .gitmodules submodule.repo_new.path >actual &&
                test_cmp expect actual&&
                echo "$submodurl/repo" >expect &&
-               git config -f .gitmodules submodule.repo.url >actual &&
-               test_cmp expect actual &&
+               test_must_fail git config -f .gitmodules submodule.repo.url &&
                echo "$submodurl/bare.git" >expect &&
                git config -f .gitmodules submodule.repo_new.url >actual &&
                test_cmp expect actual &&
@@@ -809,12 -797,8 +807,8 @@@ test_expect_success 'submodule add wit
                git rm repo &&
                test_must_fail git submodule add -q --name repo_new "$submodurl/repo.git" repo &&
                test ! -d repo &&
-               echo "repo" >expect &&
-               git config -f .gitmodules submodule.repo_new.path >actual &&
-               test_cmp expect actual&&
-               echo "$submodurl/bare.git" >expect &&
-               git config -f .gitmodules submodule.repo_new.url >actual &&
-               test_cmp expect actual &&
+               test_must_fail git config -f .gitmodules submodule.repo_new.path &&
+               test_must_fail git config -f .gitmodules submodule.repo_new.url &&
                echo "$submodurl/bare.git" >expect &&
                git config submodule.repo_new.url >actual &&
                test_cmp expect actual &&
@@@ -973,20 -957,4 +967,20 @@@ test_expect_success 'submodule with UTF
        git submodule >&2 &&
        test -n "$(git submodule | grep "$svname")"
  '
 +
 +test_expect_success 'submodule add clone shallow submodule' '
 +      mkdir super &&
 +      pwd=$(pwd)
 +      (
 +              cd super &&
 +              git init &&
 +              git submodule add --depth=1 file://"$pwd"/example2 submodule &&
 +              (
 +                      cd submodule &&
 +                      test 1 = $(git log --oneline | wc -l)
 +              )
 +      )
 +'
 +
 +
  test_done
diff --combined tree-walk.c
index c626135234f2fbf2d8711e32a020d2e228a307b2,c366852553d08ac9697c87a46375c47761b58c0c..5ece8c3477b11f3bf0c54196924ac24568309225
@@@ -3,6 -3,7 +3,7 @@@
  #include "unpack-trees.h"
  #include "dir.h"
  #include "tree.h"
+ #include "pathspec.h"
  
  static const char *get_mode(const char *str, unsigned int *modep)
  {
@@@ -323,6 -324,7 +324,6 @@@ static inline int prune_traversal(struc
  
  int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
  {
 -      int ret = 0;
        int error = 0;
        struct name_entry *entry = xmalloc(n*sizeof(*entry));
        int i;
                strbuf_setlen(&base, info->pathlen);
        }
        for (;;) {
 +              int trees_used;
                unsigned long mask, dirmask;
                const char *first = NULL;
                int first_len = 0;
                if (interesting < 0)
                        break;
                if (interesting) {
 -                      ret = info->fn(n, mask, dirmask, entry, info);
 -                      if (ret < 0) {
 -                              error = ret;
 +                      trees_used = info->fn(n, mask, dirmask, entry, info);
 +                      if (trees_used < 0) {
 +                              error = trees_used;
                                if (!info->show_all_errors)
                                        break;
                        }
 -                      mask &= ret;
 +                      mask &= trees_used;
                }
 -              ret = 0;
                for (i = 0; i < n; i++)
                        if (mask & (1ul << i))
                                update_extended_entry(tx + i, entry + i);
@@@ -487,13 -489,25 +488,25 @@@ int get_tree_entry(const unsigned char 
        return retval;
  }
  
- static int match_entry(const struct name_entry *entry, int pathlen,
+ static int match_entry(const struct pathspec_item *item,
+                      const struct name_entry *entry, int pathlen,
                       const char *match, int matchlen,
                       enum interesting *never_interesting)
  {
        int m = -1; /* signals that we haven't called strncmp() */
  
-       if (*never_interesting != entry_not_interesting) {
+       if (item->magic & PATHSPEC_ICASE)
+               /*
+                * "Never interesting" trick requires exact
+                * matching. We could do something clever with inexact
+                * matching, but it's trickier (and not to forget that
+                * strcasecmp is locale-dependent, at least in
+                * glibc). Just disable it for now. It can't be worse
+                * than the wildcard's codepath of '[Tt][Hi][Is][Ss]'
+                * pattern.
+                */
+               *never_interesting = entry_not_interesting;
+       else if (*never_interesting != entry_not_interesting) {
                /*
                 * We have not seen any match that sorts later
                 * than the current path.
                 * we cheated and did not do strncmp(), so we do
                 * that here.
                 */
-               m = strncmp(match, entry->path, pathlen);
+               m = ps_strncmp(item, match, entry->path, pathlen);
  
        /*
         * If common part matched earlier then it is a hit,
         * leading directory and is shorter than match.
         */
        if (!m)
+               /*
+                * match_entry does not check if the prefix part is
+                * matched case-sensitively. If the entry is a
+                * directory and part of prefix, it'll be rematched
+                * eventually by basecmp with special treatment for
+                * the prefix.
+                */
                return 1;
  
        return 0;
  }
  
- static int match_dir_prefix(const char *base,
+ /* :(icase)-aware string compare */
+ static int basecmp(const struct pathspec_item *item,
+                  const char *base, const char *match, int len)
+ {
+       if (item->magic & PATHSPEC_ICASE) {
+               int ret, n = len > item->prefix ? item->prefix : len;
+               ret = strncmp(base, match, n);
+               if (ret)
+                       return ret;
+               base += n;
+               match += n;
+               len -= n;
+       }
+       return ps_strncmp(item, base, match, len);
+ }
+ static int match_dir_prefix(const struct pathspec_item *item,
+                           const char *base,
                            const char *match, int matchlen)
  {
-       if (strncmp(base, match, matchlen))
+       if (basecmp(item, base, match, matchlen))
                return 0;
  
        /*
@@@ -592,7 -630,7 +629,7 @@@ static int match_wildcard_base(const st
                 */
                if (baselen >= matchlen) {
                        *matched = matchlen;
-                       return !strncmp(base, match, matchlen);
+                       return !basecmp(item, base, match, matchlen);
                }
  
                dirlen = matchlen;
                 * base ends with '/' so we are sure it really matches
                 * directory
                 */
-               if (strncmp(base, match, baselen))
+               if (basecmp(item, base, match, baselen))
                        return 0;
                *matched = baselen;
        } else
@@@ -634,8 -672,17 +671,17 @@@ enum interesting tree_entry_interesting
        enum interesting never_interesting = ps->has_wildcard ?
                entry_not_interesting : all_entries_not_interesting;
  
+       GUARD_PATHSPEC(ps,
+                      PATHSPEC_FROMTOP |
+                      PATHSPEC_MAXDEPTH |
+                      PATHSPEC_LITERAL |
+                      PATHSPEC_GLOB |
+                      PATHSPEC_ICASE);
        if (!ps->nr) {
-               if (!ps->recursive || ps->max_depth == -1)
+               if (!ps->recursive ||
+                   !(ps->magic & PATHSPEC_MAXDEPTH) ||
+                   ps->max_depth == -1)
                        return all_entries_interesting;
                return within_depth(base->buf + base_offset, baselen,
                                    !!S_ISDIR(entry->mode),
  
                if (baselen >= matchlen) {
                        /* If it doesn't match, move along... */
-                       if (!match_dir_prefix(base_str, match, matchlen))
+                       if (!match_dir_prefix(item, base_str, match, matchlen))
                                goto match_wildcards;
  
-                       if (!ps->recursive || ps->max_depth == -1)
+                       if (!ps->recursive ||
+                           !(ps->magic & PATHSPEC_MAXDEPTH) ||
+                           ps->max_depth == -1)
                                return all_entries_interesting;
  
                        return within_depth(base_str + matchlen + 1,
                }
  
                /* Either there must be no base, or the base must match. */
-               if (baselen == 0 || !strncmp(base_str, match, baselen)) {
-                       if (match_entry(entry, pathlen,
+               if (baselen == 0 || !basecmp(item, base_str, match, baselen)) {
+                       if (match_entry(item, entry, pathlen,
                                        match + baselen, matchlen - baselen,
                                        &never_interesting))
                                return entry_interesting;
  
                        if (item->nowildcard_len < item->len) {
-                               if (!git_fnmatch(match + baselen, entry->path,
-                                                item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+                               if (!git_fnmatch(item, match + baselen, entry->path,
                                                 item->nowildcard_len - baselen))
                                        return entry_interesting;
  
@@@ -716,8 -764,7 +763,7 @@@ match_wildcards
  
                strbuf_add(base, entry->path, pathlen);
  
-               if (!git_fnmatch(match, base->buf + base_offset,
-                                item->flags & PATHSPEC_ONESTAR ? GFNM_ONESTAR : 0,
+               if (!git_fnmatch(item, match, base->buf + base_offset,
                                 item->nowildcard_len)) {
                        strbuf_setlen(base, base_offset + baselen);
                        return entry_interesting;
diff --combined tree.c
index c0e568c08f670c45ab9615af2a41bda43569b5fd,ff72f67dc128f60b64662654ea9df5a6f9beb2f8..549e5883a01bb31010861aad991993772d238309
--- 1/tree.c
--- 2/tree.c
+++ b/tree.c
@@@ -47,7 -47,7 +47,7 @@@ static int read_one_entry_quick(const u
  }
  
  static int read_tree_1(struct tree *tree, struct strbuf *base,
-                      int stage, struct pathspec *pathspec,
+                      int stage, const struct pathspec *pathspec,
                       read_tree_fn_t fn, void *context)
  {
        struct tree_desc desc;
  
  int read_tree_recursive(struct tree *tree,
                        const char *base, int baselen,
-                       int stage, struct pathspec *pathspec,
+                       int stage, const struct pathspec *pathspec,
                        read_tree_fn_t fn, void *context)
  {
        struct strbuf sb = STRBUF_INIT;
@@@ -159,7 -159,7 +159,7 @@@ int read_tree(struct tree *tree, int st
         * sort at the end.
         */
        for (i = 0; !fn && i < active_nr; i++) {
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
                if (ce_stage(ce) == stage)
                        fn = read_one_entry;
        }
diff --combined wt-status.c
index cb24f1fa9b9f9632e5fd92bbcceb44008619fa14,33baf0a698176c7df93e14046b388b0750fcaee4..ff4b32426a36db38fba9e4cc51e09a988efe34a6
@@@ -1,4 -1,5 +1,5 @@@
  #include "cache.h"
+ #include "pathspec.h"
  #include "wt-status.h"
  #include "object.h"
  #include "dir.h"
@@@ -244,7 -245,7 +245,7 @@@ static void wt_status_print_unmerged_da
        struct strbuf onebuf = STRBUF_INIT;
        const char *one, *how = _("bug");
  
 -      one = quote_path(it->string, -1, &onebuf, s->prefix);
 +      one = quote_path(it->string, s->prefix, &onebuf);
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (d->stagemask) {
        case 1: how = _("both deleted:"); break;
@@@ -298,8 -299,8 +299,8 @@@ static void wt_status_print_change_data
                    change_type);
        }
  
 -      one = quote_path(one_name, -1, &onebuf, s->prefix);
 -      two = quote_path(two_name, -1, &twobuf, s->prefix);
 +      one = quote_path(one_name, s->prefix, &onebuf);
 +      two = quote_path(two_name, s->prefix, &twobuf);
  
        status_printf(s, color(WT_STATUS_HEADER, s), "\t");
        switch (status) {
@@@ -372,7 -373,7 +373,7 @@@ static void wt_status_collect_changed_c
  static int unmerged_mask(const char *path)
  {
        int pos, mask;
 -      struct cache_entry *ce;
 +      const struct cache_entry *ce;
  
        pos = cache_name_pos(path, strlen(path));
        if (0 <= pos)
@@@ -438,7 -439,7 +439,7 @@@ static void wt_status_collect_changes_w
        }
        rev.diffopt.format_callback = wt_status_collect_changed_cb;
        rev.diffopt.format_callback_data = s;
-       init_pathspec(&rev.prune_data, s->pathspec);
+       copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_files(&rev, 0);
  }
  
@@@ -463,22 -464,20 +464,20 @@@ static void wt_status_collect_changes_i
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 200;
        rev.diffopt.break_opt = 0;
-       init_pathspec(&rev.prune_data, s->pathspec);
+       copy_pathspec(&rev.prune_data, &s->pathspec);
        run_diff_index(&rev, 1);
  }
  
  static void wt_status_collect_changes_initial(struct wt_status *s)
  {
-       struct pathspec pathspec;
        int i;
  
-       init_pathspec(&pathspec, s->pathspec);
        for (i = 0; i < active_nr; i++) {
                struct string_list_item *it;
                struct wt_status_change_data *d;
 -              struct cache_entry *ce = active_cache[i];
 +              const struct cache_entry *ce = active_cache[i];
  
-               if (!ce_path_match(ce, &pathspec))
+               if (!ce_path_match(ce, &s->pathspec))
                        continue;
                it = string_list_insert(&s->change, ce->name);
                d = it->util;
                else
                        d->index_status = DIFF_STATUS_ADDED;
        }
-       free_pathspec(&pathspec);
  }
  
  static void wt_status_collect_untracked(struct wt_status *s)
                dir.flags |= DIR_SHOW_IGNORED_TOO;
        setup_standard_excludes(&dir);
  
-       fill_directory(&dir, s->pathspec);
+       fill_directory(&dir, &s->pathspec);
  
        for (i = 0; i < dir.nr; i++) {
                struct dir_entry *ent = dir.entries[i];
                if (cache_name_is_other(ent->name, ent->len) &&
-                   match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                   match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
                        string_list_insert(&s->untracked, ent->name);
                free(ent);
        }
        for (i = 0; i < dir.ignored_nr; i++) {
                struct dir_entry *ent = dir.ignored[i];
                if (cache_name_is_other(ent->name, ent->len) &&
-                   match_pathspec(s->pathspec, ent->name, ent->len, 0, NULL))
+                   match_pathspec_depth(&s->pathspec, ent->name, ent->len, 0, NULL))
                        string_list_insert(&s->ignored, ent->name);
                free(ent);
        }
@@@ -707,7 -705,8 +705,7 @@@ static void wt_status_print_other(struc
                struct string_list_item *it;
                const char *path;
                it = &(l->items[i]);
 -              path = quote_path(it->string, strlen(it->string),
 -                                &buf, s->prefix);
 +              path = quote_path(it->string, s->prefix, &buf);
                if (column_active(s->colopts)) {
                        string_list_append(&output, path);
                        continue;
@@@ -1292,7 -1291,7 +1290,7 @@@ static void wt_shortstatus_unmerged(str
        } else {
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
 -              one = quote_path(it->string, -1, &onebuf, s->prefix);
 +              one = quote_path(it->string, s->prefix, &onebuf);
                printf(" %s\n", one);
                strbuf_release(&onebuf);
        }
@@@ -1320,7 -1319,7 +1318,7 @@@ static void wt_shortstatus_status(struc
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
                if (d->head_path) {
 -                      one = quote_path(d->head_path, -1, &onebuf, s->prefix);
 +                      one = quote_path(d->head_path, s->prefix, &onebuf);
                        if (*one != '"' && strchr(one, ' ') != NULL) {
                                putchar('"');
                                strbuf_addch(&onebuf, '"');
                        printf("%s -> ", one);
                        strbuf_release(&onebuf);
                }
 -              one = quote_path(it->string, -1, &onebuf, s->prefix);
 +              one = quote_path(it->string, s->prefix, &onebuf);
                if (*one != '"' && strchr(one, ' ') != NULL) {
                        putchar('"');
                        strbuf_addch(&onebuf, '"');
@@@ -1348,7 -1347,7 +1346,7 @@@ static void wt_shortstatus_other(struc
        } else {
                struct strbuf onebuf = STRBUF_INIT;
                const char *one;
 -              one = quote_path(it->string, -1, &onebuf, s->prefix);
 +              one = quote_path(it->string, s->prefix, &onebuf);
                color_fprintf(s->fp, color(WT_STATUS_UNTRACKED, s), "%s", sign);
                printf(" %s\n", one);
                strbuf_release(&onebuf);
diff --combined wt-status.h
index fb7152e187a2fe7c080aab1d84b7fd2cbe6c47a1,8463672ed3f41a58218ae084a5604cd5b569350b..9966c13deb678857d5fdb0c3648865fa7189686b
@@@ -44,7 -44,7 +44,7 @@@ struct wt_status 
        int is_initial;
        char *branch;
        const char *reference;
-       const char **pathspec;
+       struct pathspec pathspec;
        int verbose;
        int amend;
        enum commit_whence whence;
@@@ -96,9 -96,9 +96,9 @@@ void wt_status_get_state(struct wt_stat
  void wt_shortstatus_print(struct wt_status *s);
  void wt_porcelain_print(struct wt_status *s);
  
 -void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...)
 -      ;
 -void status_printf(struct wt_status *s, const char *color, const char *fmt, ...)
 -      ;
 +__attribute__((format (printf, 3, 4)))
 +void status_printf_ln(struct wt_status *s, const char *color, const char *fmt, ...);
 +__attribute__((format (printf, 3, 4)))
 +void status_printf(struct wt_status *s, const char *color, const char *fmt, ...);
  
  #endif /* STATUS_H */