eec0f7f2b7
Teach the status command more flexibility in how ignored files are reported. Currently, the reporting of ignored files and untracked files are linked. You cannot control how ignored files are reported independently of how untracked files are reported (i.e. `all` vs `normal`). This makes it impossible to show untracked files with the `all` option, but show ignored files with the `normal` option. This work 1) adds the ability to control the reporting of ignored files independently of untracked files and 2) introduces the concept of status reporting ignored paths that explicitly match an ignored pattern. There are 2 benefits to these changes: 1) if a consumer needs all untracked files but not all ignored files, there is a performance benefit to not scanning all contents of an ignored directory and 2) returning ignored files that explicitly match a path allow a consumer to make more informed decisions about when a status result might be stale. This commit implements --ignored=matching with --untracked-files=all. The following commit will implement --ignored=matching with --untracked=files=normal. As an example of where this flexibility could be useful is that our application (Visual Studio) runs the status command and presents the output. It shows all untracked files individually (e.g. using the '--untracked-files==all' option), and would like to know about which paths are ignored. It uses information about ignored paths to make decisions about when the status result might have changed. Additionally, many projects place build output into directories inside a repository's working directory (e.g. in "bin/" and "obj/" directories). Normal usage is to explicitly ignore these 2 directory names in the .gitignore file (rather than or in addition to the *.obj pattern).If an application could know that these directories are explicitly ignored, it could infer that all contents are ignored as well and make better informed decisions about files in these directories. It could infer that any changes under these paths would not affect the output of status. Additionally, there can be a significant performance benefit by avoiding scanning through ignored directories. When status is set to report matching ignored files, it has the following behavior. Ignored files and directories that explicitly match an exclude pattern are reported. If an ignored directory matches an exclude pattern, then the path of the directory is returned. If a directory does not match an exclude pattern, but all of its contents are ignored, then the contained files are reported instead of the directory. Signed-off-by: Jameson Miller <jamill@microsoft.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
362 lines
11 KiB
C
362 lines
11 KiB
C
#ifndef DIR_H
|
|
#define DIR_H
|
|
|
|
/* See Documentation/technical/api-directory-listing.txt */
|
|
|
|
#include "strbuf.h"
|
|
|
|
struct dir_entry {
|
|
unsigned int len;
|
|
char name[FLEX_ARRAY]; /* more */
|
|
};
|
|
|
|
#define EXC_FLAG_NODIR 1
|
|
#define EXC_FLAG_ENDSWITH 4
|
|
#define EXC_FLAG_MUSTBEDIR 8
|
|
#define EXC_FLAG_NEGATIVE 16
|
|
|
|
struct exclude {
|
|
/*
|
|
* This allows callers of last_exclude_matching() etc.
|
|
* to determine the origin of the matching pattern.
|
|
*/
|
|
struct exclude_list *el;
|
|
|
|
const char *pattern;
|
|
int patternlen;
|
|
int nowildcardlen;
|
|
const char *base;
|
|
int baselen;
|
|
unsigned flags; /* EXC_FLAG_* */
|
|
|
|
/*
|
|
* Counting starts from 1 for line numbers in ignore files,
|
|
* and from -1 decrementing for patterns from CLI args.
|
|
*/
|
|
int srcpos;
|
|
};
|
|
|
|
/*
|
|
* Each excludes file will be parsed into a fresh exclude_list which
|
|
* is appended to the relevant exclude_list_group (either EXC_DIRS or
|
|
* EXC_FILE). An exclude_list within the EXC_CMDL exclude_list_group
|
|
* can also be used to represent the list of --exclude values passed
|
|
* via CLI args.
|
|
*/
|
|
struct exclude_list {
|
|
int nr;
|
|
int alloc;
|
|
|
|
/* remember pointer to exclude file contents so we can free() */
|
|
char *filebuf;
|
|
|
|
/* origin of list, e.g. path to filename, or descriptive string */
|
|
const char *src;
|
|
|
|
struct exclude **excludes;
|
|
};
|
|
|
|
/*
|
|
* The contents of the per-directory exclude files are lazily read on
|
|
* demand and then cached in memory, one per exclude_stack struct, in
|
|
* order to avoid opening and parsing each one every time that
|
|
* directory is traversed.
|
|
*/
|
|
struct exclude_stack {
|
|
struct exclude_stack *prev; /* the struct exclude_stack for the parent directory */
|
|
int baselen;
|
|
int exclude_ix; /* index of exclude_list within EXC_DIRS exclude_list_group */
|
|
struct untracked_cache_dir *ucd;
|
|
};
|
|
|
|
struct exclude_list_group {
|
|
int nr, alloc;
|
|
struct exclude_list *el;
|
|
};
|
|
|
|
struct sha1_stat {
|
|
struct stat_data stat;
|
|
unsigned char sha1[20];
|
|
int valid;
|
|
};
|
|
|
|
/*
|
|
* Untracked cache
|
|
*
|
|
* The following inputs are sufficient to determine what files in a
|
|
* directory are excluded:
|
|
*
|
|
* - The list of files and directories of the directory in question
|
|
* - The $GIT_DIR/index
|
|
* - dir_struct flags
|
|
* - The content of $GIT_DIR/info/exclude
|
|
* - The content of core.excludesfile
|
|
* - The content (or the lack) of .gitignore of all parent directories
|
|
* from $GIT_WORK_TREE
|
|
* - The check_only flag in read_directory_recursive (for
|
|
* DIR_HIDE_EMPTY_DIRECTORIES)
|
|
*
|
|
* The first input can be checked using directory mtime. In many
|
|
* filesystems, directory mtime (stat_data field) is updated when its
|
|
* files or direct subdirs are added or removed.
|
|
*
|
|
* The second one can be hooked from cache_tree_invalidate_path().
|
|
* Whenever a file (or a submodule) is added or removed from a
|
|
* directory, we invalidate that directory.
|
|
*
|
|
* The remaining inputs are easy, their SHA-1 could be used to verify
|
|
* their contents (exclude_sha1[], info_exclude_sha1[] and
|
|
* excludes_file_sha1[])
|
|
*/
|
|
struct untracked_cache_dir {
|
|
struct untracked_cache_dir **dirs;
|
|
char **untracked;
|
|
struct stat_data stat_data;
|
|
unsigned int untracked_alloc, dirs_nr, dirs_alloc;
|
|
unsigned int untracked_nr;
|
|
unsigned int check_only : 1;
|
|
/* all data except 'dirs' in this struct are good */
|
|
unsigned int valid : 1;
|
|
unsigned int recurse : 1;
|
|
/* null SHA-1 means this directory does not have .gitignore */
|
|
unsigned char exclude_sha1[20];
|
|
char name[FLEX_ARRAY];
|
|
};
|
|
|
|
struct untracked_cache {
|
|
struct sha1_stat ss_info_exclude;
|
|
struct sha1_stat ss_excludes_file;
|
|
const char *exclude_per_dir;
|
|
struct strbuf ident;
|
|
/*
|
|
* dir_struct#flags must match dir_flags or the untracked
|
|
* cache is ignored.
|
|
*/
|
|
unsigned dir_flags;
|
|
struct untracked_cache_dir *root;
|
|
/* Statistics */
|
|
int dir_created;
|
|
int gitignore_invalidated;
|
|
int dir_invalidated;
|
|
int dir_opened;
|
|
};
|
|
|
|
struct dir_struct {
|
|
int nr, alloc;
|
|
int ignored_nr, ignored_alloc;
|
|
enum {
|
|
DIR_SHOW_IGNORED = 1<<0,
|
|
DIR_SHOW_OTHER_DIRECTORIES = 1<<1,
|
|
DIR_HIDE_EMPTY_DIRECTORIES = 1<<2,
|
|
DIR_NO_GITLINKS = 1<<3,
|
|
DIR_COLLECT_IGNORED = 1<<4,
|
|
DIR_SHOW_IGNORED_TOO = 1<<5,
|
|
DIR_COLLECT_KILLED_ONLY = 1<<6,
|
|
DIR_KEEP_UNTRACKED_CONTENTS = 1<<7,
|
|
DIR_SHOW_IGNORED_TOO_MODE_MATCHING = 1<<8
|
|
} flags;
|
|
struct dir_entry **entries;
|
|
struct dir_entry **ignored;
|
|
|
|
/* Exclude info */
|
|
const char *exclude_per_dir;
|
|
|
|
/*
|
|
* We maintain three groups of exclude pattern lists:
|
|
*
|
|
* EXC_CMDL lists patterns explicitly given on the command line.
|
|
* EXC_DIRS lists patterns obtained from per-directory ignore files.
|
|
* EXC_FILE lists patterns from fallback ignore files, e.g.
|
|
* - .git/info/exclude
|
|
* - core.excludesfile
|
|
*
|
|
* Each group contains multiple exclude lists, a single list
|
|
* per source.
|
|
*/
|
|
#define EXC_CMDL 0
|
|
#define EXC_DIRS 1
|
|
#define EXC_FILE 2
|
|
struct exclude_list_group exclude_list_group[3];
|
|
|
|
/*
|
|
* Temporary variables which are used during loading of the
|
|
* per-directory exclude lists.
|
|
*
|
|
* exclude_stack points to the top of the exclude_stack, and
|
|
* basebuf contains the full path to the current
|
|
* (sub)directory in the traversal. Exclude points to the
|
|
* matching exclude struct if the directory is excluded.
|
|
*/
|
|
struct exclude_stack *exclude_stack;
|
|
struct exclude *exclude;
|
|
struct strbuf basebuf;
|
|
|
|
/* Enable untracked file cache if set */
|
|
struct untracked_cache *untracked;
|
|
struct sha1_stat ss_info_exclude;
|
|
struct sha1_stat ss_excludes_file;
|
|
unsigned unmanaged_exclude_files;
|
|
};
|
|
|
|
/*Count the number of slashes for string s*/
|
|
extern int count_slashes(const char *s);
|
|
|
|
/*
|
|
* The ordering of these constants is significant, with
|
|
* higher-numbered match types signifying "closer" (i.e. more
|
|
* specific) matches which will override lower-numbered match types
|
|
* when populating the seen[] array.
|
|
*/
|
|
#define MATCHED_RECURSIVELY 1
|
|
#define MATCHED_FNMATCH 2
|
|
#define MATCHED_EXACTLY 3
|
|
extern int simple_length(const char *match);
|
|
extern int no_wildcard(const char *string);
|
|
extern char *common_prefix(const struct pathspec *pathspec);
|
|
extern int match_pathspec(const struct pathspec *pathspec,
|
|
const char *name, int namelen,
|
|
int prefix, char *seen, int is_dir);
|
|
extern int report_path_error(const char *ps_matched, const struct pathspec *pathspec, const char *prefix);
|
|
extern int within_depth(const char *name, int namelen, int depth, int max_depth);
|
|
|
|
extern int fill_directory(struct dir_struct *dir,
|
|
struct index_state *istate,
|
|
const struct pathspec *pathspec);
|
|
extern int read_directory(struct dir_struct *, struct index_state *istate,
|
|
const char *path, int len,
|
|
const struct pathspec *pathspec);
|
|
|
|
extern int is_excluded_from_list(const char *pathname, int pathlen,
|
|
const char *basename, int *dtype,
|
|
struct exclude_list *el,
|
|
struct index_state *istate);
|
|
struct dir_entry *dir_add_ignored(struct dir_struct *dir,
|
|
struct index_state *istate,
|
|
const char *pathname, int len);
|
|
|
|
/*
|
|
* these implement the matching logic for dir.c:excluded_from_list and
|
|
* attr.c:path_matches()
|
|
*/
|
|
extern int match_basename(const char *, int,
|
|
const char *, int, int, unsigned);
|
|
extern int match_pathname(const char *, int,
|
|
const char *, int,
|
|
const char *, int, int, unsigned);
|
|
|
|
extern struct exclude *last_exclude_matching(struct dir_struct *dir,
|
|
struct index_state *istate,
|
|
const char *name, int *dtype);
|
|
|
|
extern int is_excluded(struct dir_struct *dir,
|
|
struct index_state *istate,
|
|
const char *name, int *dtype);
|
|
|
|
extern struct exclude_list *add_exclude_list(struct dir_struct *dir,
|
|
int group_type, const char *src);
|
|
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
|
|
struct exclude_list *el, struct index_state *istate);
|
|
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
|
|
extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
|
|
extern void add_exclude(const char *string, const char *base,
|
|
int baselen, struct exclude_list *el, int srcpos);
|
|
extern void clear_exclude_list(struct exclude_list *el);
|
|
extern void clear_directory(struct dir_struct *dir);
|
|
extern int file_exists(const char *);
|
|
|
|
extern int is_inside_dir(const char *dir);
|
|
extern int dir_inside_of(const char *subdir, const char *dir);
|
|
|
|
static inline int is_dot_or_dotdot(const char *name)
|
|
{
|
|
return (name[0] == '.' &&
|
|
(name[1] == '\0' ||
|
|
(name[1] == '.' && name[2] == '\0')));
|
|
}
|
|
|
|
extern int is_empty_dir(const char *dir);
|
|
|
|
extern void setup_standard_excludes(struct dir_struct *dir);
|
|
|
|
|
|
/* Constants for remove_dir_recursively: */
|
|
|
|
/*
|
|
* If a non-directory is found within path, stop and return an error.
|
|
* (In this case some empty directories might already have been
|
|
* removed.)
|
|
*/
|
|
#define REMOVE_DIR_EMPTY_ONLY 01
|
|
|
|
/*
|
|
* If any Git work trees are found within path, skip them without
|
|
* considering it an error.
|
|
*/
|
|
#define REMOVE_DIR_KEEP_NESTED_GIT 02
|
|
|
|
/* Remove the contents of path, but leave path itself. */
|
|
#define REMOVE_DIR_KEEP_TOPLEVEL 04
|
|
|
|
/*
|
|
* Remove path and its contents, recursively. flags is a combination
|
|
* of the above REMOVE_DIR_* constants. Return 0 on success.
|
|
*
|
|
* This function uses path as temporary scratch space, but restores it
|
|
* before returning.
|
|
*/
|
|
extern int remove_dir_recursively(struct strbuf *path, int flag);
|
|
|
|
/* tries to remove the path with empty directories along it, ignores ENOENT */
|
|
extern int remove_path(const char *path);
|
|
|
|
extern int fspathcmp(const char *a, const char *b);
|
|
extern int fspathncmp(const char *a, const char *b, size_t count);
|
|
|
|
/*
|
|
* The prefix part of pattern must not contains wildcards.
|
|
*/
|
|
struct pathspec_item;
|
|
extern int git_fnmatch(const struct pathspec_item *item,
|
|
const char *pattern, const char *string,
|
|
int prefix);
|
|
|
|
extern int submodule_path_match(const struct pathspec *ps,
|
|
const char *submodule_name,
|
|
char *seen);
|
|
|
|
static inline int ce_path_match(const struct cache_entry *ce,
|
|
const struct pathspec *pathspec,
|
|
char *seen)
|
|
{
|
|
return match_pathspec(pathspec, ce->name, ce_namelen(ce), 0, seen,
|
|
S_ISDIR(ce->ce_mode) || S_ISGITLINK(ce->ce_mode));
|
|
}
|
|
|
|
static inline int dir_path_match(const struct dir_entry *ent,
|
|
const struct pathspec *pathspec,
|
|
int prefix, char *seen)
|
|
{
|
|
int has_trailing_dir = ent->len && ent->name[ent->len - 1] == '/';
|
|
int len = has_trailing_dir ? ent->len - 1 : ent->len;
|
|
return match_pathspec(pathspec, ent->name, len, prefix, seen,
|
|
has_trailing_dir);
|
|
}
|
|
|
|
int cmp_dir_entry(const void *p1, const void *p2);
|
|
int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in);
|
|
|
|
void untracked_cache_invalidate_path(struct index_state *, const char *);
|
|
void untracked_cache_remove_from_index(struct index_state *, const char *);
|
|
void untracked_cache_add_to_index(struct index_state *, const char *);
|
|
|
|
void free_untracked_cache(struct untracked_cache *);
|
|
struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);
|
|
void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
|
|
void add_untracked_cache(struct index_state *istate);
|
|
void remove_untracked_cache(struct index_state *istate);
|
|
extern void connect_work_tree_and_git_dir(const char *work_tree, const char *git_dir);
|
|
extern void relocate_gitdir(const char *path,
|
|
const char *old_git_dir,
|
|
const char *new_git_dir);
|
|
#endif
|