Merge branch 'bw/ls-files-recurse-submodules'
authorJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2016 20:14:44 +0000 (13:14 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 26 Oct 2016 20:14:44 +0000 (13:14 -0700)
"git ls-files" learned "--recurse-submodules" option that can be
used to get a listing of tracked files across submodules (i.e. this
only works with "--cached" option, not for listing untracked or
ignored files). This would be a useful tool to sit on the upstream
side of a pipe that is read with xargs to work on all working tree
files from the top-level superproject.

* bw/ls-files-recurse-submodules:
ls-files: add pathspec matching for submodules
ls-files: pass through safe options for --recurse-submodules
ls-files: optionally recurse into submodules
git: make super-prefix option

1  2 
Documentation/git.txt
builtin/ls-files.c
cache.h
dir.c
environment.c
git.c
diff --combined Documentation/git.txt
index b8bec711f47918ad3dc7e05d318525819f3a51ab,2188ae605668492fe607720ada4a732350e0f4f0..2cf7e225f56af05031aa6aebea50e921cde6c9d1
@@@ -13,6 -13,7 +13,7 @@@ SYNOPSI
      [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path]
      [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]
      [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>]
+     [--super-prefix=<path>]
      <command> [<args>]
  
  DESCRIPTION
@@@ -43,10 -44,9 +44,10 @@@ unreleased) version of Git, that is ava
  branch of the `git.git` repository.
  Documentation for older releases are available here:
  
 -* link:v2.10.0/git.html[documentation for release 2.10]
 +* link:v2.10.1/git.html[documentation for release 2.10.1]
  
  * release notes for
 +  link:RelNotes/2.10.1.txt[2.10.1],
    link:RelNotes/2.10.0.txt[2.10].
  
  * link:v2.9.3/git.html[documentation for release 2.9.3]
@@@ -602,6 -602,11 +603,11 @@@ foo.bar= ...`) sets `foo.bar` to the em
        details.  Equivalent to setting the `GIT_NAMESPACE` environment
        variable.
  
+ --super-prefix=<path>::
+       Currently for internal use only.  Set a prefix which gives a path from
+       above a repository down to its root.  One use is to give submodules
+       context about the superproject that invoked it.
  --bare::
        Treat the repository as a bare repository.  If GIT_DIR
        environment is not set, it is set to the current working
diff --combined builtin/ls-files.c
index 197f153f501e4498122896d4e1242d4c5e1651cc,0f25914348e657712bfe252363f4fac3c59a9d84..1592290815c8b93701fa9d175d76f7a7ed85f7d2
@@@ -14,6 -14,7 +14,7 @@@
  #include "resolve-undo.h"
  #include "string-list.h"
  #include "pathspec.h"
+ #include "run-command.h"
  
  static int abbrev;
  static int show_deleted;
@@@ -28,8 -29,11 +29,11 @@@ static int show_valid_bit
  static int line_terminator = '\n';
  static int debug_mode;
  static int show_eol;
+ static int recurse_submodules;
+ static struct argv_array submodules_options = ARGV_ARRAY_INIT;
  
  static const char *prefix;
+ static const char *super_prefix;
  static int max_prefix_len;
  static int prefix_len;
  static struct pathspec pathspec;
@@@ -67,12 -71,25 +71,25 @@@ static void write_eolinfo(const struct 
  
  static void write_name(const char *name)
  {
+       /*
+        * Prepend the super_prefix to name to construct the full_name to be
+        * written.
+        */
+       struct strbuf full_name = STRBUF_INIT;
+       if (super_prefix) {
+               strbuf_addstr(&full_name, super_prefix);
+               strbuf_addstr(&full_name, name);
+               name = full_name.buf;
+       }
        /*
         * 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);
+       strbuf_release(&full_name);
  }
  
  static void show_dir_entry(const char *tag, struct dir_entry *ent)
@@@ -152,55 -169,117 +169,117 @@@ static void show_killed_files(struct di
        }
  }
  
+ /*
+  * Compile an argv_array with all of the options supported by --recurse_submodules
+  */
+ static void compile_submodule_options(const struct dir_struct *dir, int show_tag)
+ {
+       if (line_terminator == '\0')
+               argv_array_push(&submodules_options, "-z");
+       if (show_tag)
+               argv_array_push(&submodules_options, "-t");
+       if (show_valid_bit)
+               argv_array_push(&submodules_options, "-v");
+       if (show_cached)
+               argv_array_push(&submodules_options, "--cached");
+       if (show_eol)
+               argv_array_push(&submodules_options, "--eol");
+       if (debug_mode)
+               argv_array_push(&submodules_options, "--debug");
+ }
+ /**
+  * Recursively call ls-files on a submodule
+  */
+ static void show_gitlink(const struct cache_entry *ce)
+ {
+       struct child_process cp = CHILD_PROCESS_INIT;
+       int status;
+       int i;
+       argv_array_pushf(&cp.args, "--super-prefix=%s%s/",
+                        super_prefix ? super_prefix : "",
+                        ce->name);
+       argv_array_push(&cp.args, "ls-files");
+       argv_array_push(&cp.args, "--recurse-submodules");
+       /* add supported options */
+       argv_array_pushv(&cp.args, submodules_options.argv);
+       /*
+        * Pass in the original pathspec args.  The submodule will be
+        * responsible for prepending the 'submodule_prefix' prior to comparing
+        * against the pathspec for matches.
+        */
+       argv_array_push(&cp.args, "--");
+       for (i = 0; i < pathspec.nr; i++)
+               argv_array_push(&cp.args, pathspec.items[i].original);
+       cp.git_cmd = 1;
+       cp.dir = ce->name;
+       status = run_command(&cp);
+       if (status)
+               exit(status);
+ }
  static void show_ce_entry(const char *tag, const struct cache_entry *ce)
  {
+       struct strbuf name = STRBUF_INIT;
        int len = max_prefix_len;
+       if (super_prefix)
+               strbuf_addstr(&name, super_prefix);
+       strbuf_addstr(&name, ce->name);
  
        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,
-                           S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode)))
-               return;
+       if (recurse_submodules && S_ISGITLINK(ce->ce_mode) &&
+           submodule_path_match(&pathspec, name.buf, ps_matched)) {
+               show_gitlink(ce);
+       } else if (match_pathspec(&pathspec, name.buf, name.len,
+                                 len, ps_matched,
+                                 S_ISDIR(ce->ce_mode) ||
+                                 S_ISGITLINK(ce->ce_mode))) {
+               if (tag && *tag && show_valid_bit &&
+                   (ce->ce_flags & CE_VALID)) {
+                       static char alttag[4];
+                       memcpy(alttag, tag, 3);
+                       if (isalpha(tag[0]))
+                               alttag[0] = tolower(tag[0]);
+                       else if (tag[0] == '?')
+                               alttag[0] = '!';
+                       else {
+                               alttag[0] = 'v';
+                               alttag[1] = tag[0];
+                               alttag[2] = ' ';
+                               alttag[3] = 0;
+                       }
+                       tag = alttag;
+               }
  
-       if (tag && *tag && show_valid_bit &&
-           (ce->ce_flags & CE_VALID)) {
-               static char alttag[4];
-               memcpy(alttag, tag, 3);
-               if (isalpha(tag[0]))
-                       alttag[0] = tolower(tag[0]);
-               else if (tag[0] == '?')
-                       alttag[0] = '!';
-               else {
-                       alttag[0] = 'v';
-                       alttag[1] = tag[0];
-                       alttag[2] = ' ';
-                       alttag[3] = 0;
+               if (!show_stage) {
+                       fputs(tag, stdout);
+               } else {
+                       printf("%s%06o %s %d\t",
+                              tag,
+                              ce->ce_mode,
 -                             find_unique_abbrev(ce->sha1,abbrev),
++                             find_unique_abbrev(ce->oid.hash, abbrev),
+                              ce_stage(ce));
+               }
+               write_eolinfo(ce, ce->name);
+               write_name(ce->name);
+               if (debug_mode) {
+                       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);
+                       printf("  dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
+                       printf("  uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
+                       printf("  size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
                }
-               tag = alttag;
        }
  
-       if (!show_stage) {
-               fputs(tag, stdout);
-       } else {
-               printf("%s%06o %s %d\t",
-                      tag,
-                      ce->ce_mode,
-                      find_unique_abbrev(ce->oid.hash,abbrev),
-                      ce_stage(ce));
-       }
-       write_eolinfo(ce, ce->name);
-       write_name(ce->name);
-       if (debug_mode) {
-               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);
-               printf("  dev: %d\tino: %d\n", sd->sd_dev, sd->sd_ino);
-               printf("  uid: %d\tgid: %d\n", sd->sd_uid, sd->sd_gid);
-               printf("  size: %d\tflags: %x\n", sd->sd_size, ce->ce_flags);
-       }
+       strbuf_release(&name);
  }
  
  static void show_ru_info(void)
@@@ -468,6 -547,8 +547,8 @@@ int cmd_ls_files(int argc, const char *
                { 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_BOOL(0, "recurse-submodules", &recurse_submodules,
+                       N_("recurse through submodules")),
                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"),
        prefix = cmd_prefix;
        if (prefix)
                prefix_len = strlen(prefix);
+       super_prefix = get_super_prefix();
        git_config(git_default_config, NULL);
  
        if (read_cache() < 0)
        if (require_work_tree && !is_inside_work_tree())
                setup_work_tree();
  
+       if (recurse_submodules)
+               compile_submodule_options(&dir, show_tag);
+       if (recurse_submodules &&
+           (show_stage || show_deleted || show_others || show_unmerged ||
+            show_killed || show_modified || show_resolve_undo || with_tree))
+               die("ls-files --recurse-submodules unsupported mode");
+       if (recurse_submodules && error_unmatch)
+               die("ls-files --recurse-submodules does not support "
+                   "--error-unmatch");
        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);
+       /*
+        * Find common prefix for all pathspec's
+        * This is used as a performance optimization which unfortunately cannot
+        * be done when recursing into submodules
+        */
+       if (recurse_submodules)
+               max_prefix = NULL;
+       else
+               max_prefix = common_prefix(&pathspec);
        max_prefix_len = max_prefix ? strlen(max_prefix) : 0;
  
        /* Treat unmatching pathspec elements as errors */
diff --combined cache.h
index 05ecb889ebd7c33386a9c3b4c32b32a4ce1f32da,8cf495db75914f68666fe7efcc0135b8788efcdd..5f2f03090fbc343f9e83a1879f20c74804cf6590
+++ b/cache.h
@@@ -173,7 -173,7 +173,7 @@@ struct cache_entry 
        unsigned int ce_flags;
        unsigned int ce_namelen;
        unsigned int index;     /* for link extension */
 -      unsigned char sha1[20];
 +      struct object_id oid;
        char name[FLEX_ARRAY]; /* more */
  };
  
@@@ -367,9 -367,8 +367,9 @@@ extern void free_name_hash(struct index
  #define rename_cache_entry_at(pos, new_name) rename_index_entry_at(&the_index, (pos), (new_name))
  #define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
  #define remove_file_from_cache(path) remove_file_from_index(&the_index, (path))
 -#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags), 0)
 -#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags), 0)
 +#define add_to_cache(path, st, flags) add_to_index(&the_index, (path), (st), (flags))
 +#define add_file_to_cache(path, flags) add_file_to_index(&the_index, (path), (flags))
 +#define chmod_cache_entry(ce, flip) chmod_index_entry(&the_index, (ce), (flip))
  #define refresh_cache(flags) refresh_index(&the_index, (flags), NULL, NULL, NULL)
  #define ce_match_stat(ce, st, options) ie_match_stat(&the_index, (ce), (st), (options))
  #define ce_modified(ce, st, options) ie_modified(&the_index, (ce), (st), (options))
@@@ -409,6 -408,7 +409,7 @@@ static inline enum object_type object_t
  #define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
  #define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
  #define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
+ #define GIT_SUPER_PREFIX_ENVIRONMENT "GIT_INTERNAL_SUPER_PREFIX"
  #define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
  #define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
  #define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
  #define GIT_GLOB_PATHSPECS_ENVIRONMENT "GIT_GLOB_PATHSPECS"
  #define GIT_NOGLOB_PATHSPECS_ENVIRONMENT "GIT_NOGLOB_PATHSPECS"
  #define GIT_ICASE_PATHSPECS_ENVIRONMENT "GIT_ICASE_PATHSPECS"
 +#define GIT_QUARANTINE_ENVIRONMENT "GIT_QUARANTINE_PATH"
  
  /*
   * This environment variable is expected to contain a boolean indicating
   */
  extern const char * const local_repo_env[];
  
 +/*
 + * Returns true iff we have a configured git repository (either via
 + * setup_git_directory, or in the environment via $GIT_DIR).
 + */
 +int have_git_dir(void);
 +
  extern int is_bare_repository_cfg;
  extern int is_bare_repository(void);
  extern int is_inside_git_dir(void);
@@@ -476,6 -469,7 +477,7 @@@ extern int get_common_dir_noenv(struct 
  extern int get_common_dir(struct strbuf *sb, const char *gitdir);
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
+ extern const char *get_super_prefix(void);
  extern const char *get_git_work_tree(void);
  
  /*
@@@ -527,10 -521,9 +529,10 @@@ extern void verify_non_filename(const c
  extern int path_inside_repo(const char *prefix, const char *path);
  
  #define INIT_DB_QUIET 0x0001
 +#define INIT_DB_EXIST_OK 0x0002
  
 -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 int init_db(const char *git_dir, const char *real_git_dir,
 +                 const char *template_dir, unsigned int flags);
  
  extern void sanitize_stdfds(void);
  extern int daemonize(void);
@@@ -590,10 -583,9 +592,10 @@@ extern int remove_file_from_index(struc
  #define ADD_CACHE_IGNORE_ERRORS       4
  #define ADD_CACHE_IGNORE_REMOVAL 8
  #define ADD_CACHE_INTENT 16
 -extern int add_to_index(struct index_state *, const char *path, struct stat *, int flags, int force_mode);
 -extern int add_file_to_index(struct index_state *, const char *path, int flags, int force_mode);
 +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, unsigned int refresh_options);
 +extern int chmod_index_entry(struct index_state *, struct cache_entry *ce, char flip);
  extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
  extern int index_name_is_other(const struct index_state *, const char *, int);
@@@ -675,15 -667,8 +677,15 @@@ extern size_t delta_base_cache_limit
  extern unsigned long big_file_threshold;
  extern unsigned long pack_size_limit_cfg;
  
 +/*
 + * Accessors for the core.sharedrepository config which lazy-load the value
 + * from the config (if not already set). The "reset" function can be
 + * used to unset "set" or cached value, meaning that the value will be loaded
 + * fresh from the config file on the next call to get_shared_repository().
 + */
  void set_shared_repository(int value);
  int get_shared_repository(void);
 +void reset_shared_repository(void);
  
  /*
   * Do replace refs need to be checked this run?  This variable is
@@@ -1207,11 -1192,6 +1209,11 @@@ struct object_context 
  #define GET_SHA1_FOLLOW_SYMLINKS 0100
  #define GET_SHA1_ONLY_TO_DIE    04000
  
 +#define GET_SHA1_DISAMBIGUATORS \
 +      (GET_SHA1_COMMIT | GET_SHA1_COMMITTISH | \
 +      GET_SHA1_TREE | GET_SHA1_TREEISH | \
 +      GET_SHA1_BLOB)
 +
  extern int get_sha1(const char *str, unsigned char *sha1);
  extern int get_sha1_commit(const char *str, unsigned char *sha1);
  extern int get_sha1_committish(const char *str, unsigned char *sha1);
@@@ -1226,8 -1206,6 +1228,8 @@@ extern int get_oid(const char *str, str
  typedef int each_abbrev_fn(const unsigned char *sha1, void *);
  extern int for_each_abbrev(const char *prefix, each_abbrev_fn, void *);
  
 +extern int set_disambiguate_hint_config(const char *var, const char *value);
 +
  /*
   * Try to read a SHA1 in hexadecimal format from the 40 characters
   * starting at hex.  Write the 20-byte result to sha1 in binary form.
@@@ -1255,7 -1233,7 +1257,7 @@@ extern char *sha1_to_hex(const unsigne
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
  extern int interpret_branch_name(const char *str, int len, struct strbuf *);
 -extern int get_sha1_mb(const char *str, unsigned char *sha1);
 +extern int get_oid_mb(const char *str, struct object_id *oid);
  
  extern int validate_headref(const char *ref);
  
@@@ -1365,7 -1343,6 +1367,7 @@@ struct checkout 
                 not_new:1,
                 refresh_cache:1;
  };
 +#define CHECKOUT_INIT { NULL, "" }
  
  #define TEMPORARY_FILENAME_LENGTH 25
  extern int checkout_entry(struct cache_entry *ce, const struct checkout *state, char *topath);
@@@ -1391,46 -1368,16 +1393,46 @@@ extern void remove_scheduled_dirs(void)
  
  extern struct alternate_object_database {
        struct alternate_object_database *next;
 -      char *name;
 -      char base[FLEX_ARRAY]; /* more */
 +
 +      /* see alt_scratch_buf() */
 +      struct strbuf scratch;
 +      size_t base_len;
 +
 +      char path[FLEX_ARRAY];
  } *alt_odb_list;
  extern void prepare_alt_odb(void);
  extern void read_info_alternates(const char * relative_base, int depth);
  extern char *compute_alternate_path(const char *path, struct strbuf *err);
 -extern void add_to_alternates_file(const char *reference);
  typedef int alt_odb_fn(struct alternate_object_database *, void *);
  extern int foreach_alt_odb(alt_odb_fn, void*);
  
 +/*
 + * Allocate a "struct alternate_object_database" but do _not_ actually
 + * add it to the list of alternates.
 + */
 +struct alternate_object_database *alloc_alt_odb(const char *dir);
 +
 +/*
 + * Add the directory to the on-disk alternates file; the new entry will also
 + * take effect in the current process.
 + */
 +extern void add_to_alternates_file(const char *dir);
 +
 +/*
 + * Add the directory to the in-memory list of alternates (along with any
 + * recursive alternates it points to), but do not modify the on-disk alternates
 + * file.
 + */
 +extern void add_to_alternates_memory(const char *dir);
 +
 +/*
 + * Returns a scratch strbuf pre-filled with the alternate object directory,
 + * including a trailing slash, which can be used to access paths in the
 + * alternate. Always use this over direct access to alt->scratch, as it
 + * cleans up any previous use of the scratch buffer.
 + */
 +extern struct strbuf *alt_scratch_buf(struct alternate_object_database *alt);
 +
  struct pack_window {
        struct pack_window *next;
        unsigned char *base;
@@@ -1633,15 -1580,7 +1635,15 @@@ struct object_info 
                } packed;
        } u;
  };
 +
 +/*
 + * Initializer for a "struct object_info" that wants no items. You may
 + * also memset() the memory to all-zeroes.
 + */
 +#define OBJECT_INFO_INIT {NULL}
 +
  extern int sha1_object_info_extended(const unsigned char *, struct object_info *, unsigned flags);
 +extern int packed_object_info(struct packed_git *pack, off_t offset, struct object_info *);
  
  /* Dumb servers support */
  extern int update_server_info(int);
@@@ -1876,6 -1815,7 +1878,6 @@@ extern void write_file(const char *path
  
  /* pager.c */
  extern void setup_pager(void);
 -extern const char *pager_program;
  extern int pager_in_use(void);
  extern int pager_use_color;
  extern int term_columns(void);
@@@ -1908,7 -1848,7 +1910,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 struct pathspec *pathspec, int flags, int force_mode);
 +int add_files_to_cache(const char *prefix, const struct pathspec *pathspec, int flags);
  
  /* diff.c */
  extern int diff_auto_refresh_index;
diff --combined dir.c
index 3bad1ade8d59f2ae5a02d50ae7db85642f9dcb0e,28e97367d9ddc3748ae15f0cc7dbfdccf900190b..f9412e0213f64100c71dfcc7fcb61e9df4103d00
--- 1/dir.c
--- 2/dir.c
+++ b/dir.c
@@@ -207,8 -207,9 +207,9 @@@ int within_depth(const char *name, int 
        return 1;
  }
  
- #define DO_MATCH_EXCLUDE   1
- #define DO_MATCH_DIRECTORY 2
+ #define DO_MATCH_EXCLUDE   (1<<0)
+ #define DO_MATCH_DIRECTORY (1<<1)
+ #define DO_MATCH_SUBMODULE (1<<2)
  
  /*
   * Does 'match' match the given name?
@@@ -283,6 -284,32 +284,32 @@@ static int match_pathspec_item(const st
                         item->nowildcard_len - prefix))
                return MATCHED_FNMATCH;
  
+       /* Perform checks to see if "name" is a super set of the pathspec */
+       if (flags & DO_MATCH_SUBMODULE) {
+               /* name is a literal prefix of the pathspec */
+               if ((namelen < matchlen) &&
+                   (match[namelen] == '/') &&
+                   !ps_strncmp(item, match, name, namelen))
+                       return MATCHED_RECURSIVELY;
+               /* name" doesn't match up to the first wild character */
+               if (item->nowildcard_len < item->len &&
+                   ps_strncmp(item, match, name,
+                              item->nowildcard_len - prefix))
+                       return 0;
+               /*
+                * Here is where we would perform a wildmatch to check if
+                * "name" can be matched as a directory (or a prefix) against
+                * the pathspec.  Since wildmatch doesn't have this capability
+                * at the present we have to punt and say that it is a match,
+                * potentially returning a false positive
+                * The submodules themselves will be able to perform more
+                * accurate matching to determine if the pathspec matches.
+                */
+               return MATCHED_RECURSIVELY;
+       }
        return 0;
  }
  
@@@ -386,6 -413,21 +413,21 @@@ int match_pathspec(const struct pathspe
        return negative ? 0 : positive;
  }
  
+ /**
+  * Check if a submodule is a superset of the pathspec
+  */
+ int submodule_path_match(const struct pathspec *ps,
+                        const char *submodule_name,
+                        char *seen)
+ {
+       int matched = do_match_pathspec(ps, submodule_name,
+                                       strlen(submodule_name),
+                                       0, seen,
+                                       DO_MATCH_DIRECTORY |
+                                       DO_MATCH_SUBMODULE);
+       return matched;
+ }
  int report_path_error(const char *ps_matched,
                      const struct pathspec *pathspec,
                      const char *prefix)
@@@ -525,7 -567,7 +567,7 @@@ static void *read_skip_worktree_file_fr
                return NULL;
        if (!ce_skip_worktree(active_cache[pos]))
                return NULL;
 -      data = read_sha1_file(active_cache[pos]->sha1, &type, &sz);
 +      data = read_sha1_file(active_cache[pos]->oid.hash, &type, &sz);
        if (!data || type != OBJ_BLOB) {
                free(data);
                return NULL;
        *size = xsize_t(sz);
        if (sha1_stat) {
                memset(&sha1_stat->stat, 0, sizeof(sha1_stat->stat));
 -              hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
 +              hashcpy(sha1_stat->sha1, active_cache[pos]->oid.hash);
        }
        return data;
  }
@@@ -713,8 -755,7 +755,8 @@@ static int add_excludes(const char *fna
                                 !ce_stage(active_cache[pos]) &&
                                 ce_uptodate(active_cache[pos]) &&
                                 !would_convert_to_git(fname))
 -                              hashcpy(sha1_stat->sha1, active_cache[pos]->sha1);
 +                              hashcpy(sha1_stat->sha1,
 +                                      active_cache[pos]->oid.hash);
                        else
                                hash_sha1_file(buf, size, "blob", sha1_stat->sha1);
                        fill_stat_data(&sha1_stat->stat, &st);
@@@ -2005,8 -2046,8 +2047,8 @@@ int read_directory(struct dir_struct *d
        if (!len || treat_leading_path(dir, path, len, simplify))
                read_directory_recursive(dir, path, len, untracked, 0, simplify);
        free_simplify(simplify);
 -      qsort(dir->entries, dir->nr, sizeof(struct dir_entry *), cmp_name);
 -      qsort(dir->ignored, dir->ignored_nr, sizeof(struct dir_entry *), cmp_name);
 +      QSORT(dir->entries, dir->nr, cmp_name);
 +      QSORT(dir->ignored, dir->ignored_nr, cmp_name);
        if (dir->untracked) {
                static struct trace_key trace_untracked_stats = TRACE_KEY_INIT(UNTRACKED_STATS);
                trace_printf_key(&trace_untracked_stats,
diff --combined environment.c
index cd5aa57179240d615cb0628c9e24f44ac4b8c2d3,d12d7db20a555251d5789dc4f1067c267b6ebea7..cdc097f80c4b876818fbe28dc1cc1a54cde68902
@@@ -40,6 -40,7 +40,6 @@@ size_t packed_git_window_size = DEFAULT
  size_t packed_git_limit = DEFAULT_PACKED_GIT_LIMIT;
  size_t delta_base_cache_limit = 96 * 1024 * 1024;
  unsigned long big_file_threshold = 512 * 1024 * 1024;
 -const char *pager_program;
  int pager_use_color = 1;
  const char *editor_program;
  const char *askpass_program;
@@@ -99,6 -100,8 +99,8 @@@ static char *work_tree
  static const char *namespace;
  static size_t namespace_len;
  
+ static const char *super_prefix;
  static const char *git_dir, *git_common_dir;
  static char *git_object_dir, *git_index_file, *git_graft_file;
  int git_db_env, git_index_env, git_graft_env, git_common_dir_env;
@@@ -119,6 -122,7 +121,7 @@@ const char * const local_repo_env[] = 
        NO_REPLACE_OBJECTS_ENVIRONMENT,
        GIT_REPLACE_REF_BASE_ENVIRONMENT,
        GIT_PREFIX_ENVIRONMENT,
+       GIT_SUPER_PREFIX_ENVIRONMENT,
        GIT_SHALLOW_FILE_ENVIRONMENT,
        GIT_COMMON_DIR_ENVIRONMENT,
        NULL
@@@ -195,13 -199,6 +198,13 @@@ int is_bare_repository(void
        return is_bare_repository_cfg && !get_git_work_tree();
  }
  
 +int have_git_dir(void)
 +{
 +      return startup_info->have_repository
 +              || git_dir
 +              || getenv(GIT_DIR_ENVIRONMENT);
 +}
 +
  const char *get_git_dir(void)
  {
        if (!git_dir)
@@@ -228,6 -225,16 +231,16 @@@ const char *strip_namespace(const char 
        return namespaced_ref + namespace_len;
  }
  
+ const char *get_super_prefix(void)
+ {
+       static int initialized;
+       if (!initialized) {
+               super_prefix = getenv(GIT_SUPER_PREFIX_ENVIRONMENT);
+               initialized = 1;
+       }
+       return super_prefix;
+ }
  static int git_work_tree_initialized;
  
  /*
@@@ -351,8 -358,3 +364,8 @@@ int get_shared_repository(void
        }
        return the_shared_repository;
  }
 +
 +void reset_shared_repository(void)
 +{
 +      need_shared_repository_from_config = 1;
 +}
diff --combined git.c
index ab5c99cf70c3b46fb70b0e6a6bf5520e392e099c,df737682d5e00e375b2cd5f3ac825cc020ff3723..be58788deb9ce0bca84138c882f4566c36b8e9fc
--- 1/git.c
--- 2/git.c
+++ b/git.c
@@@ -164,6 -164,20 +164,20 @@@ static int handle_options(const char **
                        setenv(GIT_WORK_TREE_ENVIRONMENT, cmd, 1);
                        if (envchanged)
                                *envchanged = 1;
+               } else if (!strcmp(cmd, "--super-prefix")) {
+                       if (*argc < 2) {
+                               fprintf(stderr, "No prefix given for --super-prefix.\n" );
+                               usage(git_usage_string);
+                       }
+                       setenv(GIT_SUPER_PREFIX_ENVIRONMENT, (*argv)[1], 1);
+                       if (envchanged)
+                               *envchanged = 1;
+                       (*argv)++;
+                       (*argc)--;
+               } else if (skip_prefix(cmd, "--super-prefix=", &cmd)) {
+                       setenv(GIT_SUPER_PREFIX_ENVIRONMENT, cmd, 1);
+                       if (envchanged)
+                               *envchanged = 1;
                } else if (!strcmp(cmd, "--bare")) {
                        char *cwd = xgetcwd();
                        is_bare_repository_cfg = 1;
@@@ -310,6 -324,7 +324,7 @@@ static int handle_alias(int *argcp, con
   * RUN_SETUP for reading from the configuration file.
   */
  #define NEED_WORK_TREE                (1<<3)
+ #define SUPPORT_SUPER_PREFIX  (1<<4)
  
  struct cmd_struct {
        const char *cmd;
@@@ -344,6 -359,13 +359,13 @@@ static int run_builtin(struct cmd_struc
        }
        commit_pager_choice();
  
+       if (!help && get_super_prefix()) {
+               if (!(p->option & SUPPORT_SUPER_PREFIX))
+                       die("%s doesn't support --super-prefix", p->cmd);
+               if (prefix)
+                       die("can't use --super-prefix from a subdirectory");
+       }
        if (!help && p->option & NEED_WORK_TREE)
                setup_work_tree();
  
@@@ -421,7 -443,7 +443,7 @@@ static struct cmd_struct commands[] = 
        { "init-db", cmd_init_db },
        { "interpret-trailers", cmd_interpret_trailers, RUN_SETUP_GENTLY },
        { "log", cmd_log, RUN_SETUP },
-       { "ls-files", cmd_ls_files, RUN_SETUP },
+       { "ls-files", cmd_ls_files, RUN_SETUP | SUPPORT_SUPER_PREFIX },
        { "ls-remote", cmd_ls_remote, RUN_SETUP_GENTLY },
        { "ls-tree", cmd_ls_tree, RUN_SETUP },
        { "mailinfo", cmd_mailinfo },
        { "pack-objects", cmd_pack_objects, RUN_SETUP },
        { "pack-redundant", cmd_pack_redundant, RUN_SETUP },
        { "pack-refs", cmd_pack_refs, RUN_SETUP },
 -      { "patch-id", cmd_patch_id },
 +      { "patch-id", cmd_patch_id, RUN_SETUP_GENTLY },
        { "pickaxe", cmd_blame, RUN_SETUP },
        { "prune", cmd_prune, RUN_SETUP },
        { "prune-packed", cmd_prune_packed, RUN_SETUP },
@@@ -558,6 -580,9 +580,9 @@@ static void execv_dashed_external(cons
        const char *tmp;
        int status;
  
+       if (get_super_prefix())
+               die("%s doesn't support --super-prefix", argv[0]);
        if (use_pager == -1)
                use_pager = check_pager_config(argv[0]);
        commit_pager_choice();