mirror of
https://github.com/git/git
synced 2024-08-27 19:49:21 +00:00
Merge branch 'nd/struct-pathspec'
* nd/struct-pathspec: (22 commits) t6004: add pathspec globbing test for log family t7810: overlapping pathspecs and depth limit grep: drop pathspec_matches() in favor of tree_entry_interesting() grep: use writable strbuf from caller for grep_tree() grep: use match_pathspec_depth() for cache/worktree grepping grep: convert to use struct pathspec Convert ce_path_match() to use match_pathspec_depth() Convert ce_path_match() to use struct pathspec struct rev_info: convert prune_data to struct pathspec pathspec: add match_pathspec_depth() tree_entry_interesting(): optimize wildcard matching when base is matched tree_entry_interesting(): support wildcard matching tree_entry_interesting(): fix depth limit with overlapping pathspecs tree_entry_interesting(): support depth limit tree_entry_interesting(): refactor into separate smaller functions diff-tree: convert base+baselen to writable strbuf glossary: define pathspec Move tree_entry_interesting() to tree-walk.c and export it tree_entry_interesting(): remove dependency on struct diff_options Convert struct diff_options to use struct pathspec ...
This commit is contained in:
commit
d5c87a802d
|
@ -273,6 +273,29 @@ This commit is referred to as a "merge commit", or sometimes just a
|
||||||
<<def_pack,pack>>, to assist in efficiently accessing the contents of a
|
<<def_pack,pack>>, to assist in efficiently accessing the contents of a
|
||||||
pack.
|
pack.
|
||||||
|
|
||||||
|
[[def_pathspec]]pathspec::
|
||||||
|
Pattern used to specify paths.
|
||||||
|
+
|
||||||
|
Pathspecs are used on the command line of "git ls-files", "git
|
||||||
|
ls-tree", "git grep", "git checkout", and many other commands to
|
||||||
|
limit the scope of operations to some subset of the tree or
|
||||||
|
worktree. See the documentation of each command for whether
|
||||||
|
paths are relative to the current directory or toplevel. The
|
||||||
|
pathspec syntax is as follows:
|
||||||
|
|
||||||
|
* any path matches itself
|
||||||
|
* the pathspec up to the last slash represents a
|
||||||
|
directory prefix. The scope of that pathspec is
|
||||||
|
limited to that subtree.
|
||||||
|
* the rest of the pathspec is a pattern for the remainder
|
||||||
|
of the pathname. Paths relative to the directory
|
||||||
|
prefix will be matched against that pattern using fnmatch(3);
|
||||||
|
in particular, '*' and '?' _can_ match directory separators.
|
||||||
|
+
|
||||||
|
For example, Documentation/*.jpg will match all .jpg files
|
||||||
|
in the Documentation subtree,
|
||||||
|
including Documentation/chapter_1/figure_1.jpg.
|
||||||
|
|
||||||
[[def_parent]]parent::
|
[[def_parent]]parent::
|
||||||
A <<def_commit_object,commit object>> contains a (possibly empty) list
|
A <<def_commit_object,commit object>> contains a (possibly empty) list
|
||||||
of the logical predecessor(s) in the line of development, i.e. its
|
of the logical predecessor(s) in the line of development, i.e. its
|
||||||
|
|
|
@ -86,7 +86,7 @@ int add_files_to_cache(const char *prefix, const char **pathspec, int flags)
|
||||||
struct rev_info rev;
|
struct rev_info rev;
|
||||||
init_revisions(&rev, prefix);
|
init_revisions(&rev, prefix);
|
||||||
setup_revisions(0, NULL, &rev, NULL);
|
setup_revisions(0, NULL, &rev, NULL);
|
||||||
rev.prune_data = pathspec;
|
init_pathspec(&rev.prune_data, pathspec);
|
||||||
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
|
rev.diffopt.output_format = DIFF_FORMAT_CALLBACK;
|
||||||
rev.diffopt.format_callback = update_callback;
|
rev.diffopt.format_callback = update_callback;
|
||||||
data.flags = flags;
|
data.flags = flags;
|
||||||
|
|
|
@ -61,7 +61,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix)
|
||||||
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
|
(rev.diffopt.output_format & DIFF_FORMAT_PATCH))
|
||||||
rev.combine_merges = rev.dense_combined_merges = 1;
|
rev.combine_merges = rev.dense_combined_merges = 1;
|
||||||
|
|
||||||
if (read_cache_preload(rev.diffopt.paths) < 0) {
|
if (read_cache_preload(rev.diffopt.pathspec.raw) < 0) {
|
||||||
perror("read_cache_preload");
|
perror("read_cache_preload");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,7 +135,7 @@ static int builtin_diff_index(struct rev_info *revs,
|
||||||
revs->max_count != -1 || revs->min_age != -1 ||
|
revs->max_count != -1 || revs->min_age != -1 ||
|
||||||
revs->max_age != -1)
|
revs->max_age != -1)
|
||||||
usage(builtin_diff_usage);
|
usage(builtin_diff_usage);
|
||||||
if (read_cache_preload(revs->diffopt.paths) < 0) {
|
if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
|
||||||
perror("read_cache_preload");
|
perror("read_cache_preload");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv
|
||||||
revs->combine_merges = revs->dense_combined_merges = 1;
|
revs->combine_merges = revs->dense_combined_merges = 1;
|
||||||
|
|
||||||
setup_work_tree();
|
setup_work_tree();
|
||||||
if (read_cache_preload(revs->diffopt.paths) < 0) {
|
if (read_cache_preload(revs->diffopt.pathspec.raw) < 0) {
|
||||||
perror("read_cache_preload");
|
perror("read_cache_preload");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
@ -374,14 +374,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
|
||||||
}
|
}
|
||||||
die("unhandled object '%s' given.", name);
|
die("unhandled object '%s' given.", name);
|
||||||
}
|
}
|
||||||
if (rev.prune_data) {
|
if (rev.prune_data.nr) {
|
||||||
const char **pathspec = rev.prune_data;
|
if (!path)
|
||||||
while (*pathspec) {
|
path = rev.prune_data.items[0].match;
|
||||||
if (!path)
|
paths += rev.prune_data.nr;
|
||||||
path = *pathspec;
|
|
||||||
paths++;
|
|
||||||
pathspec++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -651,7 +651,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix)
|
||||||
if (import_filename)
|
if (import_filename)
|
||||||
import_marks(import_filename);
|
import_marks(import_filename);
|
||||||
|
|
||||||
if (import_filename && revs.prune_data)
|
if (import_filename && revs.prune_data.nr)
|
||||||
full_tree = 1;
|
full_tree = 1;
|
||||||
|
|
||||||
get_tags_and_duplicates(&revs.pending, &extra_refs);
|
get_tags_and_duplicates(&revs.pending, &extra_refs);
|
||||||
|
|
192
builtin/grep.c
192
builtin/grep.c
|
@ -329,106 +329,6 @@ static int grep_config(const char *var, const char *value, void *cb)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Return non-zero if max_depth is negative or path has no more then max_depth
|
|
||||||
* slashes.
|
|
||||||
*/
|
|
||||||
static int accept_subdir(const char *path, int max_depth)
|
|
||||||
{
|
|
||||||
if (max_depth < 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
while ((path = strchr(path, '/')) != NULL) {
|
|
||||||
max_depth--;
|
|
||||||
if (max_depth < 0)
|
|
||||||
return 0;
|
|
||||||
path++;
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Return non-zero if name is a subdirectory of match and is not too deep.
|
|
||||||
*/
|
|
||||||
static int is_subdir(const char *name, int namelen,
|
|
||||||
const char *match, int matchlen, int max_depth)
|
|
||||||
{
|
|
||||||
if (matchlen > namelen || strncmp(name, match, matchlen))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
if (name[matchlen] == '\0') /* exact match */
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
if (!matchlen || match[matchlen-1] == '/' || name[matchlen] == '/')
|
|
||||||
return accept_subdir(name + matchlen + 1, max_depth);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* git grep pathspecs are somewhat different from diff-tree pathspecs;
|
|
||||||
* pathname wildcards are allowed.
|
|
||||||
*/
|
|
||||||
static int pathspec_matches(const char **paths, const char *name, int max_depth)
|
|
||||||
{
|
|
||||||
int namelen, i;
|
|
||||||
if (!paths || !*paths)
|
|
||||||
return accept_subdir(name, max_depth);
|
|
||||||
namelen = strlen(name);
|
|
||||||
for (i = 0; paths[i]; i++) {
|
|
||||||
const char *match = paths[i];
|
|
||||||
int matchlen = strlen(match);
|
|
||||||
const char *cp, *meta;
|
|
||||||
|
|
||||||
if (is_subdir(name, namelen, match, matchlen, max_depth))
|
|
||||||
return 1;
|
|
||||||
if (!fnmatch(match, name, 0))
|
|
||||||
return 1;
|
|
||||||
if (name[namelen-1] != '/')
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* We are being asked if the directory ("name") is worth
|
|
||||||
* descending into.
|
|
||||||
*
|
|
||||||
* Find the longest leading directory name that does
|
|
||||||
* not have metacharacter in the pathspec; the name
|
|
||||||
* we are looking at must overlap with that directory.
|
|
||||||
*/
|
|
||||||
for (cp = match, meta = NULL; cp - match < matchlen; cp++) {
|
|
||||||
char ch = *cp;
|
|
||||||
if (ch == '*' || ch == '[' || ch == '?') {
|
|
||||||
meta = cp;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!meta)
|
|
||||||
meta = cp; /* fully literal */
|
|
||||||
|
|
||||||
if (namelen <= meta - match) {
|
|
||||||
/* Looking at "Documentation/" and
|
|
||||||
* the pattern says "Documentation/howto/", or
|
|
||||||
* "Documentation/diff*.txt". The name we
|
|
||||||
* have should match prefix.
|
|
||||||
*/
|
|
||||||
if (!memcmp(match, name, namelen))
|
|
||||||
return 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta - match < namelen) {
|
|
||||||
/* Looking at "Documentation/howto/" and
|
|
||||||
* the pattern says "Documentation/h*";
|
|
||||||
* match up to "Do.../h"; this avoids descending
|
|
||||||
* into "Documentation/technical/".
|
|
||||||
*/
|
|
||||||
if (!memcmp(match, name, meta - match))
|
|
||||||
return 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
|
static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
|
||||||
{
|
{
|
||||||
void *data;
|
void *data;
|
||||||
|
@ -581,7 +481,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix)
|
||||||
free(argv);
|
free(argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
|
static int grep_cache(struct grep_opt *opt, const struct pathspec *pathspec, int cached)
|
||||||
{
|
{
|
||||||
int hit = 0;
|
int hit = 0;
|
||||||
int nr;
|
int nr;
|
||||||
|
@ -591,7 +491,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
|
||||||
struct cache_entry *ce = active_cache[nr];
|
struct cache_entry *ce = active_cache[nr];
|
||||||
if (!S_ISREG(ce->ce_mode))
|
if (!S_ISREG(ce->ce_mode))
|
||||||
continue;
|
continue;
|
||||||
if (!pathspec_matches(paths, ce->name, opt->max_depth))
|
if (!match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL))
|
||||||
continue;
|
continue;
|
||||||
/*
|
/*
|
||||||
* If CE_VALID is on, we assume worktree file and its cache entry
|
* If CE_VALID is on, we assume worktree file and its cache entry
|
||||||
|
@ -618,44 +518,29 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int grep_tree(struct grep_opt *opt, const char **paths,
|
static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec,
|
||||||
struct tree_desc *tree,
|
struct tree_desc *tree, struct strbuf *base, int tn_len)
|
||||||
const char *tree_name, const char *base)
|
|
||||||
{
|
{
|
||||||
int len;
|
int hit = 0, matched = 0;
|
||||||
int hit = 0;
|
|
||||||
struct name_entry entry;
|
struct name_entry entry;
|
||||||
char *down;
|
int old_baselen = base->len;
|
||||||
int tn_len = strlen(tree_name);
|
|
||||||
struct strbuf pathbuf;
|
|
||||||
|
|
||||||
strbuf_init(&pathbuf, PATH_MAX + tn_len);
|
|
||||||
|
|
||||||
if (tn_len) {
|
|
||||||
strbuf_add(&pathbuf, tree_name, tn_len);
|
|
||||||
strbuf_addch(&pathbuf, ':');
|
|
||||||
tn_len = pathbuf.len;
|
|
||||||
}
|
|
||||||
strbuf_addstr(&pathbuf, base);
|
|
||||||
len = pathbuf.len;
|
|
||||||
|
|
||||||
while (tree_entry(tree, &entry)) {
|
while (tree_entry(tree, &entry)) {
|
||||||
int te_len = tree_entry_len(entry.path, entry.sha1);
|
int te_len = tree_entry_len(entry.path, entry.sha1);
|
||||||
pathbuf.len = len;
|
|
||||||
strbuf_add(&pathbuf, entry.path, te_len);
|
|
||||||
|
|
||||||
if (S_ISDIR(entry.mode))
|
if (matched != 2) {
|
||||||
/* Match "abc/" against pathspec to
|
matched = tree_entry_interesting(&entry, base, tn_len, pathspec);
|
||||||
* decide if we want to descend into "abc"
|
if (matched == -1)
|
||||||
* directory.
|
break; /* no more matches */
|
||||||
*/
|
if (!matched)
|
||||||
strbuf_addch(&pathbuf, '/');
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
down = pathbuf.buf + tn_len;
|
strbuf_add(base, entry.path, te_len);
|
||||||
if (!pathspec_matches(paths, down, opt->max_depth))
|
|
||||||
;
|
if (S_ISREG(entry.mode)) {
|
||||||
else if (S_ISREG(entry.mode))
|
hit |= grep_sha1(opt, entry.sha1, base->buf, tn_len);
|
||||||
hit |= grep_sha1(opt, entry.sha1, pathbuf.buf, tn_len);
|
}
|
||||||
else if (S_ISDIR(entry.mode)) {
|
else if (S_ISDIR(entry.mode)) {
|
||||||
enum object_type type;
|
enum object_type type;
|
||||||
struct tree_desc sub;
|
struct tree_desc sub;
|
||||||
|
@ -666,18 +551,21 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
|
||||||
if (!data)
|
if (!data)
|
||||||
die("unable to read tree (%s)",
|
die("unable to read tree (%s)",
|
||||||
sha1_to_hex(entry.sha1));
|
sha1_to_hex(entry.sha1));
|
||||||
|
|
||||||
|
strbuf_addch(base, '/');
|
||||||
init_tree_desc(&sub, data, size);
|
init_tree_desc(&sub, data, size);
|
||||||
hit |= grep_tree(opt, paths, &sub, tree_name, down);
|
hit |= grep_tree(opt, pathspec, &sub, base, tn_len);
|
||||||
free(data);
|
free(data);
|
||||||
}
|
}
|
||||||
|
strbuf_setlen(base, old_baselen);
|
||||||
|
|
||||||
if (hit && opt->status_only)
|
if (hit && opt->status_only)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
strbuf_release(&pathbuf);
|
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int grep_object(struct grep_opt *opt, const char **paths,
|
static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec,
|
||||||
struct object *obj, const char *name)
|
struct object *obj, const char *name)
|
||||||
{
|
{
|
||||||
if (obj->type == OBJ_BLOB)
|
if (obj->type == OBJ_BLOB)
|
||||||
|
@ -686,20 +574,30 @@ static int grep_object(struct grep_opt *opt, const char **paths,
|
||||||
struct tree_desc tree;
|
struct tree_desc tree;
|
||||||
void *data;
|
void *data;
|
||||||
unsigned long size;
|
unsigned long size;
|
||||||
int hit;
|
struct strbuf base;
|
||||||
|
int hit, len;
|
||||||
|
|
||||||
data = read_object_with_reference(obj->sha1, tree_type,
|
data = read_object_with_reference(obj->sha1, tree_type,
|
||||||
&size, NULL);
|
&size, NULL);
|
||||||
if (!data)
|
if (!data)
|
||||||
die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
|
die("unable to read tree (%s)", sha1_to_hex(obj->sha1));
|
||||||
|
|
||||||
|
len = name ? strlen(name) : 0;
|
||||||
|
strbuf_init(&base, PATH_MAX + len + 1);
|
||||||
|
if (len) {
|
||||||
|
strbuf_add(&base, name, len);
|
||||||
|
strbuf_addch(&base, ':');
|
||||||
|
}
|
||||||
init_tree_desc(&tree, data, size);
|
init_tree_desc(&tree, data, size);
|
||||||
hit = grep_tree(opt, paths, &tree, name, "");
|
hit = grep_tree(opt, pathspec, &tree, &base, base.len);
|
||||||
|
strbuf_release(&base);
|
||||||
free(data);
|
free(data);
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
die("unable to grep from object of type %s", typename(obj->type));
|
die("unable to grep from object of type %s", typename(obj->type));
|
||||||
}
|
}
|
||||||
|
|
||||||
static int grep_objects(struct grep_opt *opt, const char **paths,
|
static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec,
|
||||||
const struct object_array *list)
|
const struct object_array *list)
|
||||||
{
|
{
|
||||||
unsigned int i;
|
unsigned int i;
|
||||||
|
@ -709,7 +607,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
|
||||||
for (i = 0; i < nr; i++) {
|
for (i = 0; i < nr; i++) {
|
||||||
struct object *real_obj;
|
struct object *real_obj;
|
||||||
real_obj = deref_tag(list->objects[i].item, NULL, 0);
|
real_obj = deref_tag(list->objects[i].item, NULL, 0);
|
||||||
if (grep_object(opt, paths, real_obj, list->objects[i].name)) {
|
if (grep_object(opt, pathspec, real_obj, list->objects[i].name)) {
|
||||||
hit = 1;
|
hit = 1;
|
||||||
if (opt->status_only)
|
if (opt->status_only)
|
||||||
break;
|
break;
|
||||||
|
@ -718,7 +616,7 @@ static int grep_objects(struct grep_opt *opt, const char **paths,
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int grep_directory(struct grep_opt *opt, const char **paths)
|
static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec)
|
||||||
{
|
{
|
||||||
struct dir_struct dir;
|
struct dir_struct dir;
|
||||||
int i, hit = 0;
|
int i, hit = 0;
|
||||||
|
@ -726,7 +624,7 @@ static int grep_directory(struct grep_opt *opt, const char **paths)
|
||||||
memset(&dir, 0, sizeof(dir));
|
memset(&dir, 0, sizeof(dir));
|
||||||
setup_standard_excludes(&dir);
|
setup_standard_excludes(&dir);
|
||||||
|
|
||||||
fill_directory(&dir, paths);
|
fill_directory(&dir, pathspec->raw);
|
||||||
for (i = 0; i < dir.nr; i++) {
|
for (i = 0; i < dir.nr; i++) {
|
||||||
hit |= grep_file(opt, dir.entries[i]->name);
|
hit |= grep_file(opt, dir.entries[i]->name);
|
||||||
if (hit && opt->status_only)
|
if (hit && opt->status_only)
|
||||||
|
@ -832,6 +730,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
||||||
struct grep_opt opt;
|
struct grep_opt opt;
|
||||||
struct object_array list = OBJECT_ARRAY_INIT;
|
struct object_array list = OBJECT_ARRAY_INIT;
|
||||||
const char **paths = NULL;
|
const char **paths = NULL;
|
||||||
|
struct pathspec pathspec;
|
||||||
struct string_list path_list = STRING_LIST_INIT_NODUP;
|
struct string_list path_list = STRING_LIST_INIT_NODUP;
|
||||||
int i;
|
int i;
|
||||||
int dummy;
|
int dummy;
|
||||||
|
@ -1059,6 +958,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
||||||
paths[0] = prefix;
|
paths[0] = prefix;
|
||||||
paths[1] = NULL;
|
paths[1] = NULL;
|
||||||
}
|
}
|
||||||
|
init_pathspec(&pathspec, paths);
|
||||||
|
pathspec.max_depth = opt.max_depth;
|
||||||
|
pathspec.recursive = 1;
|
||||||
|
|
||||||
if (show_in_pager && (cached || list.nr))
|
if (show_in_pager && (cached || list.nr))
|
||||||
die("--open-files-in-pager only works on the worktree");
|
die("--open-files-in-pager only works on the worktree");
|
||||||
|
@ -1089,16 +991,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
|
||||||
die("--cached cannot be used with --no-index.");
|
die("--cached cannot be used with --no-index.");
|
||||||
if (list.nr)
|
if (list.nr)
|
||||||
die("--no-index cannot be used with revs.");
|
die("--no-index cannot be used with revs.");
|
||||||
hit = grep_directory(&opt, paths);
|
hit = grep_directory(&opt, &pathspec);
|
||||||
} else if (!list.nr) {
|
} else if (!list.nr) {
|
||||||
if (!cached)
|
if (!cached)
|
||||||
setup_work_tree();
|
setup_work_tree();
|
||||||
|
|
||||||
hit = grep_cache(&opt, paths, cached);
|
hit = grep_cache(&opt, &pathspec, cached);
|
||||||
} else {
|
} else {
|
||||||
if (cached)
|
if (cached)
|
||||||
die("both --cached and trees are given.");
|
die("both --cached and trees are given.");
|
||||||
hit = grep_objects(&opt, paths, &list);
|
hit = grep_objects(&opt, &pathspec, &list);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (use_threads)
|
if (use_threads)
|
||||||
|
|
|
@ -89,7 +89,7 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix,
|
||||||
rev->always_show_header = 0;
|
rev->always_show_header = 0;
|
||||||
if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
|
if (DIFF_OPT_TST(&rev->diffopt, FOLLOW_RENAMES)) {
|
||||||
rev->always_show_header = 0;
|
rev->always_show_header = 0;
|
||||||
if (rev->diffopt.nr_paths != 1)
|
if (rev->diffopt.pathspec.nr != 1)
|
||||||
usage("git logs can only follow renames on one pathname at a time");
|
usage("git logs can only follow renames on one pathname at a time");
|
||||||
}
|
}
|
||||||
for (i = 1; i < argc; i++) {
|
for (i = 1; i < argc; i++) {
|
||||||
|
|
|
@ -546,7 +546,10 @@ static int do_reupdate(int ac, const char **av,
|
||||||
*/
|
*/
|
||||||
int pos;
|
int pos;
|
||||||
int has_head = 1;
|
int has_head = 1;
|
||||||
const char **pathspec = get_pathspec(prefix, av + 1);
|
const char **paths = get_pathspec(prefix, av + 1);
|
||||||
|
struct pathspec pathspec;
|
||||||
|
|
||||||
|
init_pathspec(&pathspec, paths);
|
||||||
|
|
||||||
if (read_ref("HEAD", head_sha1))
|
if (read_ref("HEAD", head_sha1))
|
||||||
/* If there is no HEAD, that means it is an initial
|
/* If there is no HEAD, that means it is an initial
|
||||||
|
@ -559,7 +562,7 @@ static int do_reupdate(int ac, const char **av,
|
||||||
struct cache_entry *old = NULL;
|
struct cache_entry *old = NULL;
|
||||||
int save_nr;
|
int save_nr;
|
||||||
|
|
||||||
if (ce_stage(ce) || !ce_path_match(ce, pathspec))
|
if (ce_stage(ce) || !ce_path_match(ce, &pathspec))
|
||||||
continue;
|
continue;
|
||||||
if (has_head)
|
if (has_head)
|
||||||
old = read_one_ent(NULL, head_sha1,
|
old = read_one_ent(NULL, head_sha1,
|
||||||
|
@ -578,6 +581,7 @@ static int do_reupdate(int ac, const char **av,
|
||||||
if (save_nr != active_nr)
|
if (save_nr != active_nr)
|
||||||
goto redo;
|
goto redo;
|
||||||
}
|
}
|
||||||
|
free_pathspec(&pathspec);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
17
cache.h
17
cache.h
|
@ -500,7 +500,22 @@ extern int index_name_is_other(const struct index_state *, const char *, int);
|
||||||
extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
|
extern int ie_match_stat(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
|
||||||
extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
|
extern int ie_modified(const struct index_state *, struct cache_entry *, struct stat *, unsigned int);
|
||||||
|
|
||||||
extern int ce_path_match(const struct cache_entry *ce, const char **pathspec);
|
struct pathspec {
|
||||||
|
const char **raw; /* get_pathspec() result, not freed by free_pathspec() */
|
||||||
|
int nr;
|
||||||
|
int has_wildcard:1;
|
||||||
|
int recursive:1;
|
||||||
|
int max_depth;
|
||||||
|
struct pathspec_item {
|
||||||
|
const char *match;
|
||||||
|
int len;
|
||||||
|
int has_wildcard:1;
|
||||||
|
} *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 index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
|
extern int index_fd(unsigned char *sha1, int fd, struct stat *st, int write_object, enum object_type type, const char *path);
|
||||||
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
|
extern int index_path(unsigned char *sha1, const char *path, struct stat *st, int write_object);
|
||||||
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
|
extern void fill_stat_cache_info(struct cache_entry *ce, struct stat *st);
|
||||||
|
|
|
@ -106,7 +106,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
|
||||||
DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
|
DIFF_OPT_TST(&revs->diffopt, HAS_CHANGES))
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (!ce_path_match(ce, revs->prune_data))
|
if (!ce_path_match(ce, &revs->prune_data))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (ce_stage(ce)) {
|
if (ce_stage(ce)) {
|
||||||
|
@ -427,7 +427,7 @@ static int oneway_diff(struct cache_entry **src, struct unpack_trees_options *o)
|
||||||
if (tree == o->df_conflict_entry)
|
if (tree == o->df_conflict_entry)
|
||||||
tree = NULL;
|
tree = NULL;
|
||||||
|
|
||||||
if (ce_path_match(idx ? idx : tree, revs->prune_data))
|
if (ce_path_match(idx ? idx : tree, &revs->prune_data))
|
||||||
do_oneway_diff(o, idx, tree);
|
do_oneway_diff(o, idx, tree);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -501,7 +501,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
|
||||||
active_nr = dst - active_cache;
|
active_nr = dst - active_cache;
|
||||||
|
|
||||||
init_revisions(&revs, NULL);
|
init_revisions(&revs, NULL);
|
||||||
revs.prune_data = opt->paths;
|
init_pathspec(&revs.prune_data, opt->pathspec.raw);
|
||||||
tree = parse_tree_indirect(tree_sha1);
|
tree = parse_tree_indirect(tree_sha1);
|
||||||
if (!tree)
|
if (!tree)
|
||||||
die("bad tree object %s", sha1_to_hex(tree_sha1));
|
die("bad tree object %s", sha1_to_hex(tree_sha1));
|
||||||
|
|
|
@ -231,8 +231,9 @@ void diff_no_index(struct rev_info *revs,
|
||||||
|
|
||||||
if (prefix) {
|
if (prefix) {
|
||||||
int len = strlen(prefix);
|
int len = strlen(prefix);
|
||||||
|
const char *paths[3];
|
||||||
|
memset(paths, 0, sizeof(paths));
|
||||||
|
|
||||||
revs->diffopt.paths = xcalloc(2, sizeof(char *));
|
|
||||||
for (i = 0; i < 2; i++) {
|
for (i = 0; i < 2; i++) {
|
||||||
const char *p = argv[argc - 2 + i];
|
const char *p = argv[argc - 2 + i];
|
||||||
/*
|
/*
|
||||||
|
@ -242,12 +243,12 @@ void diff_no_index(struct rev_info *revs,
|
||||||
p = (strcmp(p, "-")
|
p = (strcmp(p, "-")
|
||||||
? xstrdup(prefix_filename(prefix, len, p))
|
? xstrdup(prefix_filename(prefix, len, p))
|
||||||
: p);
|
: p);
|
||||||
revs->diffopt.paths[i] = p;
|
paths[i] = p;
|
||||||
}
|
}
|
||||||
|
diff_tree_setup_paths(paths, &revs->diffopt);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
revs->diffopt.paths = argv + argc - 2;
|
diff_tree_setup_paths(argv + argc - 2, &revs->diffopt);
|
||||||
revs->diffopt.nr_paths = 2;
|
|
||||||
revs->diffopt.skip_stat_unmatch = 1;
|
revs->diffopt.skip_stat_unmatch = 1;
|
||||||
if (!revs->diffopt.output_format)
|
if (!revs->diffopt.output_format)
|
||||||
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
|
revs->diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||||
|
@ -259,8 +260,8 @@ void diff_no_index(struct rev_info *revs,
|
||||||
if (diff_setup_done(&revs->diffopt) < 0)
|
if (diff_setup_done(&revs->diffopt) < 0)
|
||||||
die("diff_setup_done failed");
|
die("diff_setup_done failed");
|
||||||
|
|
||||||
if (queue_diff(&revs->diffopt, revs->diffopt.paths[0],
|
if (queue_diff(&revs->diffopt, revs->diffopt.pathspec.raw[0],
|
||||||
revs->diffopt.paths[1]))
|
revs->diffopt.pathspec.raw[1]))
|
||||||
exit(1);
|
exit(1);
|
||||||
diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
|
diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
|
||||||
diffcore_std(&revs->diffopt);
|
diffcore_std(&revs->diffopt);
|
||||||
|
|
4
diff.h
4
diff.h
|
@ -133,9 +133,7 @@ struct diff_options {
|
||||||
FILE *file;
|
FILE *file;
|
||||||
int close_file;
|
int close_file;
|
||||||
|
|
||||||
int nr_paths;
|
struct pathspec pathspec;
|
||||||
const char **paths;
|
|
||||||
int *pathlens;
|
|
||||||
change_fn_t change;
|
change_fn_t change;
|
||||||
add_remove_fn_t add_remove;
|
add_remove_fn_t add_remove;
|
||||||
diff_format_fn_t format_callback;
|
diff_format_fn_t format_callback;
|
||||||
|
|
151
dir.c
151
dir.c
|
@ -87,6 +87,21 @@ int fill_directory(struct dir_struct *dir, const char **pathspec)
|
||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int within_depth(const char *name, int namelen,
|
||||||
|
int depth, int max_depth)
|
||||||
|
{
|
||||||
|
const char *cp = name, *cpe = name + namelen;
|
||||||
|
|
||||||
|
while (cp < cpe) {
|
||||||
|
if (*cp++ != '/')
|
||||||
|
continue;
|
||||||
|
depth++;
|
||||||
|
if (depth > max_depth)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Does 'match' match the given name?
|
* Does 'match' match the given name?
|
||||||
* A match is found if
|
* A match is found if
|
||||||
|
@ -184,6 +199,95 @@ int match_pathspec(const char **pathspec, const char *name, int namelen,
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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_pathspec_item(const struct pathspec_item *item, int prefix,
|
||||||
|
const char *name, int namelen)
|
||||||
|
{
|
||||||
|
/* name/namelen has prefix cut off by caller */
|
||||||
|
const char *match = item->match + prefix;
|
||||||
|
int matchlen = item->len - prefix;
|
||||||
|
|
||||||
|
/* If the match was just the prefix, we matched */
|
||||||
|
if (!*match)
|
||||||
|
return MATCHED_RECURSIVELY;
|
||||||
|
|
||||||
|
if (matchlen <= namelen && !strncmp(match, name, matchlen)) {
|
||||||
|
if (matchlen == namelen)
|
||||||
|
return MATCHED_EXACTLY;
|
||||||
|
|
||||||
|
if (match[matchlen-1] == '/' || name[matchlen] == '/')
|
||||||
|
return MATCHED_RECURSIVELY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item->has_wildcard && !fnmatch(match, name, 0))
|
||||||
|
return MATCHED_FNMATCH;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Given a name and a list of pathspecs, see if the name matches
|
||||||
|
* any of the pathspecs. The caller is also interested in seeing
|
||||||
|
* all pathspec matches some names it calls this function with
|
||||||
|
* (otherwise the user could have mistyped the unmatched pathspec),
|
||||||
|
* and a mark is left in seen[] array for pathspec element that
|
||||||
|
* actually matched anything.
|
||||||
|
*/
|
||||||
|
int match_pathspec_depth(const struct pathspec *ps,
|
||||||
|
const char *name, int namelen,
|
||||||
|
int prefix, char *seen)
|
||||||
|
{
|
||||||
|
int i, retval = 0;
|
||||||
|
|
||||||
|
if (!ps->nr) {
|
||||||
|
if (!ps->recursive || ps->max_depth == -1)
|
||||||
|
return MATCHED_RECURSIVELY;
|
||||||
|
|
||||||
|
if (within_depth(name, namelen, 0, ps->max_depth))
|
||||||
|
return MATCHED_EXACTLY;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
name += prefix;
|
||||||
|
namelen -= prefix;
|
||||||
|
|
||||||
|
for (i = ps->nr - 1; i >= 0; i--) {
|
||||||
|
int how;
|
||||||
|
if (seen && seen[i] == MATCHED_EXACTLY)
|
||||||
|
continue;
|
||||||
|
how = match_pathspec_item(ps->items+i, prefix, name, namelen);
|
||||||
|
if (ps->recursive && ps->max_depth != -1 &&
|
||||||
|
how && how != MATCHED_FNMATCH) {
|
||||||
|
int len = ps->items[i].len;
|
||||||
|
if (name[len] == '/')
|
||||||
|
len++;
|
||||||
|
if (within_depth(name+len, namelen-len, 0, ps->max_depth))
|
||||||
|
how = MATCHED_EXACTLY;
|
||||||
|
else
|
||||||
|
how = 0;
|
||||||
|
}
|
||||||
|
if (how) {
|
||||||
|
if (retval < how)
|
||||||
|
retval = how;
|
||||||
|
if (seen && seen[i] < how)
|
||||||
|
seen[i] = how;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
static int no_wildcard(const char *string)
|
static int no_wildcard(const char *string)
|
||||||
{
|
{
|
||||||
return string[strcspn(string, "*?[{\\")] == '\0';
|
return string[strcspn(string, "*?[{\\")] == '\0';
|
||||||
|
@ -1151,3 +1255,50 @@ int remove_path(const char *name)
|
||||||
return 0;
|
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->has_wildcard = !no_wildcard(path);
|
||||||
|
if (item->has_wildcard)
|
||||||
|
pathspec->has_wildcard = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
4
dir.h
4
dir.h
|
@ -65,6 +65,10 @@ struct dir_struct {
|
||||||
#define MATCHED_FNMATCH 2
|
#define MATCHED_FNMATCH 2
|
||||||
#define MATCHED_EXACTLY 3
|
#define MATCHED_EXACTLY 3
|
||||||
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
|
extern int match_pathspec(const char **pathspec, const char *name, int namelen, int prefix, char *seen);
|
||||||
|
extern int match_pathspec_depth(const struct pathspec *pathspec,
|
||||||
|
const char *name, int namelen,
|
||||||
|
int prefix, char *seen);
|
||||||
|
extern int within_depth(const char *name, int namelen, int depth, int max_depth);
|
||||||
|
|
||||||
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
|
extern int fill_directory(struct dir_struct *dir, const char **pathspec);
|
||||||
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
|
extern int read_directory(struct dir_struct *, const char *path, int len, const char **pathspec);
|
||||||
|
|
|
@ -35,7 +35,9 @@ static void *preload_thread(void *_data)
|
||||||
struct index_state *index = p->index;
|
struct index_state *index = p->index;
|
||||||
struct cache_entry **cep = index->cache + p->offset;
|
struct cache_entry **cep = index->cache + p->offset;
|
||||||
struct cache_def cache;
|
struct cache_def cache;
|
||||||
|
struct pathspec pathspec;
|
||||||
|
|
||||||
|
init_pathspec(&pathspec, p->pathspec);
|
||||||
memset(&cache, 0, sizeof(cache));
|
memset(&cache, 0, sizeof(cache));
|
||||||
nr = p->nr;
|
nr = p->nr;
|
||||||
if (nr + p->offset > index->cache_nr)
|
if (nr + p->offset > index->cache_nr)
|
||||||
|
@ -51,7 +53,7 @@ static void *preload_thread(void *_data)
|
||||||
continue;
|
continue;
|
||||||
if (ce_uptodate(ce))
|
if (ce_uptodate(ce))
|
||||||
continue;
|
continue;
|
||||||
if (!ce_path_match(ce, p->pathspec))
|
if (!ce_path_match(ce, &pathspec))
|
||||||
continue;
|
continue;
|
||||||
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
|
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
|
||||||
continue;
|
continue;
|
||||||
|
@ -61,6 +63,7 @@ static void *preload_thread(void *_data)
|
||||||
continue;
|
continue;
|
||||||
ce_mark_uptodate(ce);
|
ce_mark_uptodate(ce);
|
||||||
} while (--nr > 0);
|
} while (--nr > 0);
|
||||||
|
free_pathspec(&pathspec);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
25
read-cache.c
25
read-cache.c
|
@ -706,30 +706,9 @@ int ce_same_name(struct cache_entry *a, struct cache_entry *b)
|
||||||
return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
|
return ce_namelen(b) == len && !memcmp(a->name, b->name, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ce_path_match(const struct cache_entry *ce, const char **pathspec)
|
int ce_path_match(const struct cache_entry *ce, const struct pathspec *pathspec)
|
||||||
{
|
{
|
||||||
const char *match, *name;
|
return match_pathspec_depth(pathspec, ce->name, ce_namelen(ce), 0, NULL);
|
||||||
int len;
|
|
||||||
|
|
||||||
if (!pathspec)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
len = ce_namelen(ce);
|
|
||||||
name = ce->name;
|
|
||||||
while ((match = *pathspec++) != NULL) {
|
|
||||||
int matchlen = strlen(match);
|
|
||||||
if (matchlen > len)
|
|
||||||
continue;
|
|
||||||
if (memcmp(name, match, matchlen))
|
|
||||||
continue;
|
|
||||||
if (matchlen && name[matchlen-1] == '/')
|
|
||||||
return 1;
|
|
||||||
if (name[matchlen] == '/' || !name[matchlen])
|
|
||||||
return 1;
|
|
||||||
if (!matchlen)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
21
revision.c
21
revision.c
|
@ -323,7 +323,7 @@ static int rev_compare_tree(struct rev_info *revs, struct commit *parent, struct
|
||||||
* tagged commit by specifying both --simplify-by-decoration
|
* tagged commit by specifying both --simplify-by-decoration
|
||||||
* and pathspec.
|
* and pathspec.
|
||||||
*/
|
*/
|
||||||
if (!revs->prune_data)
|
if (!revs->prune_data.nr)
|
||||||
return REV_TREE_SAME;
|
return REV_TREE_SAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -553,11 +553,7 @@ static void cherry_pick_list(struct commit_list *list, struct rev_info *revs)
|
||||||
|
|
||||||
left_first = left_count < right_count;
|
left_first = left_count < right_count;
|
||||||
init_patch_ids(&ids);
|
init_patch_ids(&ids);
|
||||||
if (revs->diffopt.nr_paths) {
|
ids.diffopts.pathspec = revs->diffopt.pathspec;
|
||||||
ids.diffopts.nr_paths = revs->diffopt.nr_paths;
|
|
||||||
ids.diffopts.paths = revs->diffopt.paths;
|
|
||||||
ids.diffopts.pathlens = revs->diffopt.pathlens;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Compute patch-ids for one side */
|
/* Compute patch-ids for one side */
|
||||||
for (p = list; p; p = p->next) {
|
for (p = list; p; p = p->next) {
|
||||||
|
@ -973,7 +969,7 @@ static void prepare_show_merge(struct rev_info *revs)
|
||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
if (!ce_stage(ce))
|
if (!ce_stage(ce))
|
||||||
continue;
|
continue;
|
||||||
if (ce_path_match(ce, revs->prune_data)) {
|
if (ce_path_match(ce, &revs->prune_data)) {
|
||||||
prune_num++;
|
prune_num++;
|
||||||
prune = xrealloc(prune, sizeof(*prune) * prune_num);
|
prune = xrealloc(prune, sizeof(*prune) * prune_num);
|
||||||
prune[prune_num-2] = ce->name;
|
prune[prune_num-2] = ce->name;
|
||||||
|
@ -983,7 +979,8 @@ static void prepare_show_merge(struct rev_info *revs)
|
||||||
ce_same_name(ce, active_cache[i+1]))
|
ce_same_name(ce, active_cache[i+1]))
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
revs->prune_data = prune;
|
free_pathspec(&revs->prune_data);
|
||||||
|
init_pathspec(&revs->prune_data, prune);
|
||||||
revs->limited = 1;
|
revs->limited = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1620,7 +1617,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prune_data)
|
if (prune_data)
|
||||||
revs->prune_data = get_pathspec(revs->prefix, prune_data);
|
init_pathspec(&revs->prune_data, get_pathspec(revs->prefix, prune_data));
|
||||||
|
|
||||||
if (revs->def == NULL)
|
if (revs->def == NULL)
|
||||||
revs->def = opt ? opt->def : NULL;
|
revs->def = opt ? opt->def : NULL;
|
||||||
|
@ -1651,13 +1648,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s
|
||||||
if (revs->topo_order)
|
if (revs->topo_order)
|
||||||
revs->limited = 1;
|
revs->limited = 1;
|
||||||
|
|
||||||
if (revs->prune_data) {
|
if (revs->prune_data.nr) {
|
||||||
diff_tree_setup_paths(revs->prune_data, &revs->pruning);
|
diff_tree_setup_paths(revs->prune_data.raw, &revs->pruning);
|
||||||
/* Can't prune commits with rename following: the paths change.. */
|
/* Can't prune commits with rename following: the paths change.. */
|
||||||
if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
|
if (!DIFF_OPT_TST(&revs->diffopt, FOLLOW_RENAMES))
|
||||||
revs->prune = 1;
|
revs->prune = 1;
|
||||||
if (!revs->full_diff)
|
if (!revs->full_diff)
|
||||||
diff_tree_setup_paths(revs->prune_data, &revs->diffopt);
|
diff_tree_setup_paths(revs->prune_data.raw, &revs->diffopt);
|
||||||
}
|
}
|
||||||
if (revs->combine_merges)
|
if (revs->combine_merges)
|
||||||
revs->ignore_merges = 0;
|
revs->ignore_merges = 0;
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct rev_info {
|
||||||
/* Basic information */
|
/* Basic information */
|
||||||
const char *prefix;
|
const char *prefix;
|
||||||
const char *def;
|
const char *def;
|
||||||
void *prune_data;
|
struct pathspec prune_data;
|
||||||
unsigned int early_output;
|
unsigned int early_output;
|
||||||
|
|
||||||
/* Traversal flags */
|
/* Traversal flags */
|
||||||
|
|
|
@ -70,4 +70,36 @@ test_expect_success 'diff-tree pathspec' '
|
||||||
test_cmp expected current
|
test_cmp expected current
|
||||||
'
|
'
|
||||||
|
|
||||||
|
EMPTY_TREE=4b825dc642cb6eb9a060e54bf8d69288fbee4904
|
||||||
|
|
||||||
|
test_expect_success 'diff-tree with wildcard shows dir also matches' '
|
||||||
|
git diff-tree --name-only $EMPTY_TREE $tree -- "f*" >result &&
|
||||||
|
echo file0 >expected &&
|
||||||
|
test_cmp expected result
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff-tree -r with wildcard' '
|
||||||
|
git diff-tree -r --name-only $EMPTY_TREE $tree -- "*file1" >result &&
|
||||||
|
echo path1/file1 >expected &&
|
||||||
|
test_cmp expected result
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff-tree with wildcard shows dir also matches' '
|
||||||
|
git diff-tree --name-only $tree $tree2 -- "path1/f*" >result &&
|
||||||
|
echo path1 >expected &&
|
||||||
|
test_cmp expected result
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff-tree -r with wildcard from beginning' '
|
||||||
|
git diff-tree -r --name-only $tree $tree2 -- "path1/*file1" >result &&
|
||||||
|
echo path1/file1 >expected &&
|
||||||
|
test_cmp expected result
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'diff-tree -r with wildcard' '
|
||||||
|
git diff-tree -r --name-only $tree $tree2 -- "path1/f*" >result &&
|
||||||
|
echo path1/file1 >expected &&
|
||||||
|
test_cmp expected result
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
|
@ -1,51 +1,96 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
test_description='git rev-list trivial path optimization test'
|
test_description='git rev-list trivial path optimization test
|
||||||
|
|
||||||
|
d/z1
|
||||||
|
b0 b1
|
||||||
|
o------------------------*----o master
|
||||||
|
/ /
|
||||||
|
o---------o----o----o----o side
|
||||||
|
a0 c0 c1 a1 c2
|
||||||
|
d/f0 d/f1
|
||||||
|
d/z0
|
||||||
|
|
||||||
|
'
|
||||||
|
|
||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
|
|
||||||
test_expect_success setup '
|
test_expect_success setup '
|
||||||
echo Hello > a &&
|
echo Hello >a &&
|
||||||
git add a &&
|
mkdir d &&
|
||||||
git commit -m "Initial commit" a &&
|
echo World >d/f &&
|
||||||
initial=$(git rev-parse --verify HEAD)
|
echo World >d/z &&
|
||||||
|
git add a d &&
|
||||||
|
test_tick &&
|
||||||
|
git commit -m "Initial commit" &&
|
||||||
|
git rev-parse --verify HEAD &&
|
||||||
|
git tag initial
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success path-optimization '
|
test_expect_success path-optimization '
|
||||||
commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
|
test_tick &&
|
||||||
test $(git rev-list $commit | wc -l) = 2 &&
|
commit=$(echo "Unchanged tree" | git commit-tree "HEAD^{tree}" -p HEAD) &&
|
||||||
test $(git rev-list $commit -- . | wc -l) = 1
|
test $(git rev-list $commit | wc -l) = 2 &&
|
||||||
|
test $(git rev-list $commit -- . | wc -l) = 1
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'further setup' '
|
test_expect_success 'further setup' '
|
||||||
git checkout -b side &&
|
git checkout -b side &&
|
||||||
echo Irrelevant >c &&
|
echo Irrelevant >c &&
|
||||||
git add c &&
|
echo Irrelevant >d/f &&
|
||||||
|
git add c d/f &&
|
||||||
|
test_tick &&
|
||||||
git commit -m "Side makes an irrelevant commit" &&
|
git commit -m "Side makes an irrelevant commit" &&
|
||||||
|
git tag side_c0 &&
|
||||||
echo "More Irrelevancy" >c &&
|
echo "More Irrelevancy" >c &&
|
||||||
git add c &&
|
git add c &&
|
||||||
|
test_tick &&
|
||||||
git commit -m "Side makes another irrelevant commit" &&
|
git commit -m "Side makes another irrelevant commit" &&
|
||||||
echo Bye >a &&
|
echo Bye >a &&
|
||||||
git add a &&
|
git add a &&
|
||||||
|
test_tick &&
|
||||||
git commit -m "Side touches a" &&
|
git commit -m "Side touches a" &&
|
||||||
side=$(git rev-parse --verify HEAD) &&
|
git tag side_a1 &&
|
||||||
echo "Yet more Irrelevancy" >c &&
|
echo "Yet more Irrelevancy" >c &&
|
||||||
git add c &&
|
git add c &&
|
||||||
|
test_tick &&
|
||||||
git commit -m "Side makes yet another irrelevant commit" &&
|
git commit -m "Side makes yet another irrelevant commit" &&
|
||||||
git checkout master &&
|
git checkout master &&
|
||||||
echo Another >b &&
|
echo Another >b &&
|
||||||
git add b &&
|
echo Munged >d/z &&
|
||||||
|
git add b d/z &&
|
||||||
|
test_tick &&
|
||||||
git commit -m "Master touches b" &&
|
git commit -m "Master touches b" &&
|
||||||
|
git tag master_b0 &&
|
||||||
git merge side &&
|
git merge side &&
|
||||||
echo Touched >b &&
|
echo Touched >b &&
|
||||||
git add b &&
|
git add b &&
|
||||||
|
test_tick &&
|
||||||
git commit -m "Master touches b again"
|
git commit -m "Master touches b again"
|
||||||
'
|
'
|
||||||
|
|
||||||
test_expect_success 'path optimization 2' '
|
test_expect_success 'path optimization 2' '
|
||||||
( echo "$side"; echo "$initial" ) >expected &&
|
git rev-parse side_a1 initial >expected &&
|
||||||
git rev-list HEAD -- a >actual &&
|
git rev-list HEAD -- a >actual &&
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'pathspec with leading path' '
|
||||||
|
git rev-parse master^ master_b0 side_c0 initial >expected &&
|
||||||
|
git rev-list HEAD -- d >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'pathspec with glob (1)' '
|
||||||
|
git rev-parse master^ master_b0 side_c0 initial >expected &&
|
||||||
|
git rev-list HEAD -- "d/*" >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'pathspec with glob (2)' '
|
||||||
|
git rev-parse side_c0 initial >expected &&
|
||||||
|
git rev-list HEAD -- "d/[a-m]*" >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
|
@ -182,6 +182,24 @@ do
|
||||||
test_cmp expected actual
|
test_cmp expected actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success "grep --max-depth 0 -- . t $L" '
|
||||||
|
{
|
||||||
|
echo ${HC}t/v:1:vvv
|
||||||
|
echo ${HC}v:1:vvv
|
||||||
|
} >expected &&
|
||||||
|
git grep --max-depth 0 -n -e vvv $H -- . t >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "grep --max-depth 0 -- t . $L" '
|
||||||
|
{
|
||||||
|
echo ${HC}t/v:1:vvv
|
||||||
|
echo ${HC}v:1:vvv
|
||||||
|
} >expected &&
|
||||||
|
git grep --max-depth 0 -n -e vvv $H -- t . >actual &&
|
||||||
|
test_cmp expected actual
|
||||||
|
'
|
||||||
|
|
||||||
done
|
done
|
||||||
|
|
||||||
cat >expected <<EOF
|
cat >expected <<EOF
|
||||||
|
|
280
tree-diff.c
280
tree-diff.c
|
@ -6,34 +6,18 @@
|
||||||
#include "diffcore.h"
|
#include "diffcore.h"
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
|
||||||
static char *malloc_base(const char *base, int baselen, const char *path, int pathlen)
|
static void show_entry(struct diff_options *opt, const char *prefix,
|
||||||
{
|
struct tree_desc *desc, struct strbuf *base);
|
||||||
char *newbase = xmalloc(baselen + pathlen + 2);
|
|
||||||
memcpy(newbase, base, baselen);
|
|
||||||
memcpy(newbase + baselen, path, pathlen);
|
|
||||||
memcpy(newbase + baselen + pathlen, "/", 2);
|
|
||||||
return newbase;
|
|
||||||
}
|
|
||||||
|
|
||||||
static char *malloc_fullname(const char *base, int baselen, const char *path, int pathlen)
|
static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2,
|
||||||
{
|
struct strbuf *base, struct diff_options *opt)
|
||||||
char *fullname = xmalloc(baselen + pathlen + 1);
|
|
||||||
memcpy(fullname, base, baselen);
|
|
||||||
memcpy(fullname + baselen, path, pathlen);
|
|
||||||
fullname[baselen + pathlen] = 0;
|
|
||||||
return fullname;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
|
|
||||||
const char *base, int baselen);
|
|
||||||
|
|
||||||
static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, int baselen, struct diff_options *opt)
|
|
||||||
{
|
{
|
||||||
unsigned mode1, mode2;
|
unsigned mode1, mode2;
|
||||||
const char *path1, *path2;
|
const char *path1, *path2;
|
||||||
const unsigned char *sha1, *sha2;
|
const unsigned char *sha1, *sha2;
|
||||||
int cmp, pathlen1, pathlen2;
|
int cmp, pathlen1, pathlen2;
|
||||||
char *fullname;
|
int old_baselen = base->len;
|
||||||
|
int retval = 0;
|
||||||
|
|
||||||
sha1 = tree_entry_extract(t1, &path1, &mode1);
|
sha1 = tree_entry_extract(t1, &path1, &mode1);
|
||||||
sha2 = tree_entry_extract(t2, &path2, &mode2);
|
sha2 = tree_entry_extract(t2, &path2, &mode2);
|
||||||
|
@ -42,11 +26,11 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
|
||||||
pathlen2 = tree_entry_len(path2, sha2);
|
pathlen2 = tree_entry_len(path2, sha2);
|
||||||
cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
|
cmp = base_name_compare(path1, pathlen1, mode1, path2, pathlen2, mode2);
|
||||||
if (cmp < 0) {
|
if (cmp < 0) {
|
||||||
show_entry(opt, "-", t1, base, baselen);
|
show_entry(opt, "-", t1, base);
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
if (cmp > 0) {
|
if (cmp > 0) {
|
||||||
show_entry(opt, "+", t2, base, baselen);
|
show_entry(opt, "+", t2, base);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
|
if (!DIFF_OPT_TST(opt, FIND_COPIES_HARDER) && !hashcmp(sha1, sha2) && mode1 == mode2)
|
||||||
|
@ -57,149 +41,29 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const
|
||||||
* file, we need to consider it a remove and an add.
|
* file, we need to consider it a remove and an add.
|
||||||
*/
|
*/
|
||||||
if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
|
if (S_ISDIR(mode1) != S_ISDIR(mode2)) {
|
||||||
show_entry(opt, "-", t1, base, baselen);
|
show_entry(opt, "-", t1, base);
|
||||||
show_entry(opt, "+", t2, base, baselen);
|
show_entry(opt, "+", t2, base);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strbuf_add(base, path1, pathlen1);
|
||||||
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
|
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode1)) {
|
||||||
int retval;
|
|
||||||
char *newbase = malloc_base(base, baselen, path1, pathlen1);
|
|
||||||
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
|
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
|
||||||
newbase[baselen + pathlen1] = 0;
|
|
||||||
opt->change(opt, mode1, mode2,
|
opt->change(opt, mode1, mode2,
|
||||||
sha1, sha2, newbase, 0, 0);
|
sha1, sha2, base->buf, 0, 0);
|
||||||
newbase[baselen + pathlen1] = '/';
|
|
||||||
}
|
}
|
||||||
retval = diff_tree_sha1(sha1, sha2, newbase, opt);
|
strbuf_addch(base, '/');
|
||||||
free(newbase);
|
retval = diff_tree_sha1(sha1, sha2, base->buf, opt);
|
||||||
return retval;
|
} else {
|
||||||
|
opt->change(opt, mode1, mode2, sha1, sha2, base->buf, 0, 0);
|
||||||
}
|
}
|
||||||
|
strbuf_setlen(base, old_baselen);
|
||||||
fullname = malloc_fullname(base, baselen, path1, pathlen1);
|
|
||||||
opt->change(opt, mode1, mode2, sha1, sha2, fullname, 0, 0);
|
|
||||||
free(fullname);
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Is a tree entry interesting given the pathspec we have?
|
|
||||||
*
|
|
||||||
* Pre-condition: baselen == 0 || base[baselen-1] == '/'
|
|
||||||
*
|
|
||||||
* Return:
|
|
||||||
* - 2 for "yes, and all subsequent entries will be"
|
|
||||||
* - 1 for yes
|
|
||||||
* - zero for no
|
|
||||||
* - negative for "no, and no subsequent entries will be either"
|
|
||||||
*/
|
|
||||||
static int tree_entry_interesting(struct tree_desc *desc, const char *base, int baselen, struct diff_options *opt)
|
|
||||||
{
|
|
||||||
const char *path;
|
|
||||||
const unsigned char *sha1;
|
|
||||||
unsigned mode;
|
|
||||||
int i;
|
|
||||||
int pathlen;
|
|
||||||
int never_interesting = -1;
|
|
||||||
|
|
||||||
if (!opt->nr_paths)
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
sha1 = tree_entry_extract(desc, &path, &mode);
|
|
||||||
|
|
||||||
pathlen = tree_entry_len(path, sha1);
|
|
||||||
|
|
||||||
for (i = 0; i < opt->nr_paths; i++) {
|
|
||||||
const char *match = opt->paths[i];
|
|
||||||
int matchlen = opt->pathlens[i];
|
|
||||||
int m = -1; /* signals that we haven't called strncmp() */
|
|
||||||
|
|
||||||
if (baselen >= matchlen) {
|
|
||||||
/* If it doesn't match, move along... */
|
|
||||||
if (strncmp(base, match, matchlen))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the base is a subdirectory of a path which
|
|
||||||
* was specified, all of them are interesting.
|
|
||||||
*/
|
|
||||||
if (!matchlen ||
|
|
||||||
base[matchlen] == '/' ||
|
|
||||||
match[matchlen - 1] == '/')
|
|
||||||
return 2;
|
|
||||||
|
|
||||||
/* Just a random prefix match */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Does the base match? */
|
|
||||||
if (strncmp(base, match, baselen))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
match += baselen;
|
|
||||||
matchlen -= baselen;
|
|
||||||
|
|
||||||
if (never_interesting) {
|
|
||||||
/*
|
|
||||||
* We have not seen any match that sorts later
|
|
||||||
* than the current path.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Does match sort strictly earlier than path
|
|
||||||
* with their common parts?
|
|
||||||
*/
|
|
||||||
m = strncmp(match, path,
|
|
||||||
(matchlen < pathlen) ? matchlen : pathlen);
|
|
||||||
if (m < 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we come here even once, that means there is at
|
|
||||||
* least one pathspec that would sort equal to or
|
|
||||||
* later than the path we are currently looking at.
|
|
||||||
* In other words, if we have never reached this point
|
|
||||||
* after iterating all pathspecs, it means all
|
|
||||||
* pathspecs are either outside of base, or inside the
|
|
||||||
* base but sorts strictly earlier than the current
|
|
||||||
* one. In either case, they will never match the
|
|
||||||
* subsequent entries. In such a case, we initialized
|
|
||||||
* the variable to -1 and that is what will be
|
|
||||||
* returned, allowing the caller to terminate early.
|
|
||||||
*/
|
|
||||||
never_interesting = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pathlen > matchlen)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (matchlen > pathlen) {
|
|
||||||
if (match[pathlen] != '/')
|
|
||||||
continue;
|
|
||||||
if (!S_ISDIR(mode))
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m == -1)
|
|
||||||
/*
|
|
||||||
* we cheated and did not do strncmp(), so we do
|
|
||||||
* that here.
|
|
||||||
*/
|
|
||||||
m = strncmp(match, path, pathlen);
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If common part matched earlier then it is a hit,
|
|
||||||
* because we rejected the case where path is not a
|
|
||||||
* leading directory and is shorter than match.
|
|
||||||
*/
|
|
||||||
if (!m)
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
return never_interesting; /* No matches */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* A whole sub-tree went away or appeared */
|
/* A whole sub-tree went away or appeared */
|
||||||
static void show_tree(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base, int baselen)
|
static void show_tree(struct diff_options *opt, const char *prefix,
|
||||||
|
struct tree_desc *desc, struct strbuf *base)
|
||||||
{
|
{
|
||||||
int all_interesting = 0;
|
int all_interesting = 0;
|
||||||
while (desc->size) {
|
while (desc->size) {
|
||||||
|
@ -208,31 +72,32 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_
|
||||||
if (all_interesting)
|
if (all_interesting)
|
||||||
show = 1;
|
show = 1;
|
||||||
else {
|
else {
|
||||||
show = tree_entry_interesting(desc, base, baselen,
|
show = tree_entry_interesting(&desc->entry, base, 0,
|
||||||
opt);
|
&opt->pathspec);
|
||||||
if (show == 2)
|
if (show == 2)
|
||||||
all_interesting = 1;
|
all_interesting = 1;
|
||||||
}
|
}
|
||||||
if (show < 0)
|
if (show < 0)
|
||||||
break;
|
break;
|
||||||
if (show)
|
if (show)
|
||||||
show_entry(opt, prefix, desc, base, baselen);
|
show_entry(opt, prefix, desc, base);
|
||||||
update_tree_entry(desc);
|
update_tree_entry(desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A file entry went away or appeared */
|
/* A file entry went away or appeared */
|
||||||
static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc,
|
static void show_entry(struct diff_options *opt, const char *prefix,
|
||||||
const char *base, int baselen)
|
struct tree_desc *desc, struct strbuf *base)
|
||||||
{
|
{
|
||||||
unsigned mode;
|
unsigned mode;
|
||||||
const char *path;
|
const char *path;
|
||||||
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
|
const unsigned char *sha1 = tree_entry_extract(desc, &path, &mode);
|
||||||
int pathlen = tree_entry_len(path, sha1);
|
int pathlen = tree_entry_len(path, sha1);
|
||||||
|
int old_baselen = base->len;
|
||||||
|
|
||||||
|
strbuf_add(base, path, pathlen);
|
||||||
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
|
if (DIFF_OPT_TST(opt, RECURSIVE) && S_ISDIR(mode)) {
|
||||||
enum object_type type;
|
enum object_type type;
|
||||||
char *newbase = malloc_base(base, baselen, path, pathlen);
|
|
||||||
struct tree_desc inner;
|
struct tree_desc inner;
|
||||||
void *tree;
|
void *tree;
|
||||||
unsigned long size;
|
unsigned long size;
|
||||||
|
@ -241,28 +106,25 @@ static void show_entry(struct diff_options *opt, const char *prefix, struct tree
|
||||||
if (!tree || type != OBJ_TREE)
|
if (!tree || type != OBJ_TREE)
|
||||||
die("corrupt tree sha %s", sha1_to_hex(sha1));
|
die("corrupt tree sha %s", sha1_to_hex(sha1));
|
||||||
|
|
||||||
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE)) {
|
if (DIFF_OPT_TST(opt, TREE_IN_RECURSIVE))
|
||||||
newbase[baselen + pathlen] = 0;
|
opt->add_remove(opt, *prefix, mode, sha1, base->buf, 0);
|
||||||
opt->add_remove(opt, *prefix, mode, sha1, newbase, 0);
|
|
||||||
newbase[baselen + pathlen] = '/';
|
strbuf_addch(base, '/');
|
||||||
}
|
|
||||||
|
|
||||||
init_tree_desc(&inner, tree, size);
|
init_tree_desc(&inner, tree, size);
|
||||||
show_tree(opt, prefix, &inner, newbase, baselen + 1 + pathlen);
|
show_tree(opt, prefix, &inner, base);
|
||||||
|
|
||||||
free(tree);
|
free(tree);
|
||||||
free(newbase);
|
} else
|
||||||
} else {
|
opt->add_remove(opt, prefix[0], mode, sha1, base->buf, 0);
|
||||||
char *fullname = malloc_fullname(base, baselen, path, pathlen);
|
|
||||||
opt->add_remove(opt, prefix[0], mode, sha1, fullname, 0);
|
strbuf_setlen(base, old_baselen);
|
||||||
free(fullname);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void skip_uninteresting(struct tree_desc *t, const char *base, int baselen, struct diff_options *opt, int *all_interesting)
|
static void skip_uninteresting(struct tree_desc *t, struct strbuf *base,
|
||||||
|
struct diff_options *opt, int *all_interesting)
|
||||||
{
|
{
|
||||||
while (t->size) {
|
while (t->size) {
|
||||||
int show = tree_entry_interesting(t, base, baselen, opt);
|
int show = tree_entry_interesting(&t->entry, base, 0, &opt->pathspec);
|
||||||
if (show == 2)
|
if (show == 2)
|
||||||
*all_interesting = 1;
|
*all_interesting = 1;
|
||||||
if (!show) {
|
if (!show) {
|
||||||
|
@ -276,37 +138,44 @@ static void skip_uninteresting(struct tree_desc *t, const char *base, int basele
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt)
|
int diff_tree(struct tree_desc *t1, struct tree_desc *t2,
|
||||||
|
const char *base_str, struct diff_options *opt)
|
||||||
{
|
{
|
||||||
int baselen = strlen(base);
|
struct strbuf base;
|
||||||
|
int baselen = strlen(base_str);
|
||||||
int all_t1_interesting = 0;
|
int all_t1_interesting = 0;
|
||||||
int all_t2_interesting = 0;
|
int all_t2_interesting = 0;
|
||||||
|
|
||||||
|
/* Enable recursion indefinitely */
|
||||||
|
opt->pathspec.recursive = DIFF_OPT_TST(opt, RECURSIVE);
|
||||||
|
opt->pathspec.max_depth = -1;
|
||||||
|
|
||||||
|
strbuf_init(&base, PATH_MAX);
|
||||||
|
strbuf_add(&base, base_str, baselen);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
if (DIFF_OPT_TST(opt, QUICK) &&
|
if (DIFF_OPT_TST(opt, QUICK) &&
|
||||||
DIFF_OPT_TST(opt, HAS_CHANGES))
|
DIFF_OPT_TST(opt, HAS_CHANGES))
|
||||||
break;
|
break;
|
||||||
if (opt->nr_paths) {
|
if (opt->pathspec.nr) {
|
||||||
if (!all_t1_interesting)
|
if (!all_t1_interesting)
|
||||||
skip_uninteresting(t1, base, baselen, opt,
|
skip_uninteresting(t1, &base, opt, &all_t1_interesting);
|
||||||
&all_t1_interesting);
|
|
||||||
if (!all_t2_interesting)
|
if (!all_t2_interesting)
|
||||||
skip_uninteresting(t2, base, baselen, opt,
|
skip_uninteresting(t2, &base, opt, &all_t2_interesting);
|
||||||
&all_t2_interesting);
|
|
||||||
}
|
}
|
||||||
if (!t1->size) {
|
if (!t1->size) {
|
||||||
if (!t2->size)
|
if (!t2->size)
|
||||||
break;
|
break;
|
||||||
show_entry(opt, "+", t2, base, baselen);
|
show_entry(opt, "+", t2, &base);
|
||||||
update_tree_entry(t2);
|
update_tree_entry(t2);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!t2->size) {
|
if (!t2->size) {
|
||||||
show_entry(opt, "-", t1, base, baselen);
|
show_entry(opt, "-", t1, &base);
|
||||||
update_tree_entry(t1);
|
update_tree_entry(t1);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
switch (compare_tree_entry(t1, t2, base, baselen, opt)) {
|
switch (compare_tree_entry(t1, t2, &base, opt)) {
|
||||||
case -1:
|
case -1:
|
||||||
update_tree_entry(t1);
|
update_tree_entry(t1);
|
||||||
continue;
|
continue;
|
||||||
|
@ -319,6 +188,8 @@ int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, stru
|
||||||
}
|
}
|
||||||
die("git diff-tree: internal error");
|
die("git diff-tree: internal error");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
strbuf_release(&base);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,7 +220,7 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
|
||||||
DIFF_OPT_SET(&diff_opts, RECURSIVE);
|
DIFF_OPT_SET(&diff_opts, RECURSIVE);
|
||||||
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
|
DIFF_OPT_SET(&diff_opts, FIND_COPIES_HARDER);
|
||||||
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT;
|
||||||
diff_opts.single_follow = opt->paths[0];
|
diff_opts.single_follow = opt->pathspec.raw[0];
|
||||||
diff_opts.break_opt = opt->break_opt;
|
diff_opts.break_opt = opt->break_opt;
|
||||||
paths[0] = NULL;
|
paths[0] = NULL;
|
||||||
diff_tree_setup_paths(paths, &diff_opts);
|
diff_tree_setup_paths(paths, &diff_opts);
|
||||||
|
@ -369,15 +240,16 @@ static void try_to_follow_renames(struct tree_desc *t1, struct tree_desc *t2, co
|
||||||
* diff_queued_diff, we will also use that as the path in
|
* diff_queued_diff, we will also use that as the path in
|
||||||
* the future!
|
* the future!
|
||||||
*/
|
*/
|
||||||
if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, opt->paths[0])) {
|
if ((p->status == 'R' || p->status == 'C') &&
|
||||||
|
!strcmp(p->two->path, opt->pathspec.raw[0])) {
|
||||||
/* Switch the file-pairs around */
|
/* Switch the file-pairs around */
|
||||||
q->queue[i] = choice;
|
q->queue[i] = choice;
|
||||||
choice = p;
|
choice = p;
|
||||||
|
|
||||||
/* Update the path we use from now on.. */
|
/* Update the path we use from now on.. */
|
||||||
diff_tree_release_paths(opt);
|
diff_tree_release_paths(opt);
|
||||||
opt->paths[0] = xstrdup(p->one->path);
|
opt->pathspec.raw[0] = xstrdup(p->one->path);
|
||||||
diff_tree_setup_paths(opt->paths, opt);
|
diff_tree_setup_paths(opt->pathspec.raw, opt);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The caller expects us to return a set of vanilla
|
* The caller expects us to return a set of vanilla
|
||||||
|
@ -452,36 +324,12 @@ int diff_root_tree_sha1(const unsigned char *new, const char *base, struct diff_
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int count_paths(const char **paths)
|
|
||||||
{
|
|
||||||
int i = 0;
|
|
||||||
while (*paths++)
|
|
||||||
i++;
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
|
|
||||||
void diff_tree_release_paths(struct diff_options *opt)
|
void diff_tree_release_paths(struct diff_options *opt)
|
||||||
{
|
{
|
||||||
free(opt->pathlens);
|
free_pathspec(&opt->pathspec);
|
||||||
}
|
}
|
||||||
|
|
||||||
void diff_tree_setup_paths(const char **p, struct diff_options *opt)
|
void diff_tree_setup_paths(const char **p, struct diff_options *opt)
|
||||||
{
|
{
|
||||||
opt->nr_paths = 0;
|
init_pathspec(&opt->pathspec, p);
|
||||||
opt->pathlens = NULL;
|
|
||||||
opt->paths = NULL;
|
|
||||||
|
|
||||||
if (p) {
|
|
||||||
int i;
|
|
||||||
|
|
||||||
opt->paths = p;
|
|
||||||
opt->nr_paths = count_paths(p);
|
|
||||||
if (opt->nr_paths == 0) {
|
|
||||||
opt->pathlens = NULL;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
opt->pathlens = xmalloc(opt->nr_paths * sizeof(int));
|
|
||||||
for (i=0; i < opt->nr_paths; i++)
|
|
||||||
opt->pathlens[i] = strlen(p[i]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
184
tree-walk.c
184
tree-walk.c
|
@ -1,6 +1,7 @@
|
||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "tree-walk.h"
|
#include "tree-walk.h"
|
||||||
#include "unpack-trees.h"
|
#include "unpack-trees.h"
|
||||||
|
#include "dir.h"
|
||||||
#include "tree.h"
|
#include "tree.h"
|
||||||
|
|
||||||
static const char *get_mode(const char *str, unsigned int *modep)
|
static const char *get_mode(const char *str, unsigned int *modep)
|
||||||
|
@ -455,3 +456,186 @@ int get_tree_entry(const unsigned char *tree_sha1, const char *name, unsigned ch
|
||||||
free(tree);
|
free(tree);
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int match_entry(const struct name_entry *entry, int pathlen,
|
||||||
|
const char *match, int matchlen,
|
||||||
|
int *never_interesting)
|
||||||
|
{
|
||||||
|
int m = -1; /* signals that we haven't called strncmp() */
|
||||||
|
|
||||||
|
if (*never_interesting) {
|
||||||
|
/*
|
||||||
|
* We have not seen any match that sorts later
|
||||||
|
* than the current path.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Does match sort strictly earlier than path
|
||||||
|
* with their common parts?
|
||||||
|
*/
|
||||||
|
m = strncmp(match, entry->path,
|
||||||
|
(matchlen < pathlen) ? matchlen : pathlen);
|
||||||
|
if (m < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we come here even once, that means there is at
|
||||||
|
* least one pathspec that would sort equal to or
|
||||||
|
* later than the path we are currently looking at.
|
||||||
|
* In other words, if we have never reached this point
|
||||||
|
* after iterating all pathspecs, it means all
|
||||||
|
* pathspecs are either outside of base, or inside the
|
||||||
|
* base but sorts strictly earlier than the current
|
||||||
|
* one. In either case, they will never match the
|
||||||
|
* subsequent entries. In such a case, we initialized
|
||||||
|
* the variable to -1 and that is what will be
|
||||||
|
* returned, allowing the caller to terminate early.
|
||||||
|
*/
|
||||||
|
*never_interesting = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pathlen > matchlen)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (matchlen > pathlen) {
|
||||||
|
if (match[pathlen] != '/')
|
||||||
|
return 0;
|
||||||
|
if (!S_ISDIR(entry->mode))
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m == -1)
|
||||||
|
/*
|
||||||
|
* we cheated and did not do strncmp(), so we do
|
||||||
|
* that here.
|
||||||
|
*/
|
||||||
|
m = strncmp(match, entry->path, pathlen);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If common part matched earlier then it is a hit,
|
||||||
|
* because we rejected the case where path is not a
|
||||||
|
* leading directory and is shorter than match.
|
||||||
|
*/
|
||||||
|
if (!m)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int match_dir_prefix(const char *base, int baselen,
|
||||||
|
const char *match, int matchlen)
|
||||||
|
{
|
||||||
|
if (strncmp(base, match, matchlen))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the base is a subdirectory of a path which
|
||||||
|
* was specified, all of them are interesting.
|
||||||
|
*/
|
||||||
|
if (!matchlen ||
|
||||||
|
base[matchlen] == '/' ||
|
||||||
|
match[matchlen - 1] == '/')
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Just a random prefix match */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Is a tree entry interesting given the pathspec we have?
|
||||||
|
*
|
||||||
|
* Pre-condition: either baselen == base_offset (i.e. empty path)
|
||||||
|
* or base[baselen-1] == '/' (i.e. with trailing slash).
|
||||||
|
*
|
||||||
|
* Return:
|
||||||
|
* - 2 for "yes, and all subsequent entries will be"
|
||||||
|
* - 1 for yes
|
||||||
|
* - zero for no
|
||||||
|
* - negative for "no, and no subsequent entries will be either"
|
||||||
|
*/
|
||||||
|
int tree_entry_interesting(const struct name_entry *entry,
|
||||||
|
struct strbuf *base, int base_offset,
|
||||||
|
const struct pathspec *ps)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int pathlen, baselen = base->len - base_offset;
|
||||||
|
int never_interesting = ps->has_wildcard ? 0 : -1;
|
||||||
|
|
||||||
|
if (!ps->nr) {
|
||||||
|
if (!ps->recursive || ps->max_depth == -1)
|
||||||
|
return 2;
|
||||||
|
return !!within_depth(base->buf + base_offset, baselen,
|
||||||
|
!!S_ISDIR(entry->mode),
|
||||||
|
ps->max_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
pathlen = tree_entry_len(entry->path, entry->sha1);
|
||||||
|
|
||||||
|
for (i = ps->nr - 1; i >= 0; i--) {
|
||||||
|
const struct pathspec_item *item = ps->items+i;
|
||||||
|
const char *match = item->match;
|
||||||
|
const char *base_str = base->buf + base_offset;
|
||||||
|
int matchlen = item->len;
|
||||||
|
|
||||||
|
if (baselen >= matchlen) {
|
||||||
|
/* If it doesn't match, move along... */
|
||||||
|
if (!match_dir_prefix(base_str, baselen, match, matchlen))
|
||||||
|
goto match_wildcards;
|
||||||
|
|
||||||
|
if (!ps->recursive || ps->max_depth == -1)
|
||||||
|
return 2;
|
||||||
|
|
||||||
|
return !!within_depth(base_str + matchlen + 1,
|
||||||
|
baselen - matchlen - 1,
|
||||||
|
!!S_ISDIR(entry->mode),
|
||||||
|
ps->max_depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Does the base match? */
|
||||||
|
if (!strncmp(base_str, match, baselen)) {
|
||||||
|
if (match_entry(entry, pathlen,
|
||||||
|
match + baselen, matchlen - baselen,
|
||||||
|
&never_interesting))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
if (ps->items[i].has_wildcard) {
|
||||||
|
if (!fnmatch(match + baselen, entry->path, 0))
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Match all directories. We'll try to
|
||||||
|
* match files later on.
|
||||||
|
*/
|
||||||
|
if (ps->recursive && S_ISDIR(entry->mode))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match_wildcards:
|
||||||
|
if (!ps->items[i].has_wildcard)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Concatenate base and entry->path into one and do
|
||||||
|
* fnmatch() on it.
|
||||||
|
*/
|
||||||
|
|
||||||
|
strbuf_add(base, entry->path, pathlen);
|
||||||
|
|
||||||
|
if (!fnmatch(match, base->buf + base_offset, 0)) {
|
||||||
|
strbuf_setlen(base, base_offset + baselen);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
strbuf_setlen(base, base_offset + baselen);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Match all directories. We'll try to match files
|
||||||
|
* later on.
|
||||||
|
*/
|
||||||
|
if (ps->recursive && S_ISDIR(entry->mode))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return never_interesting; /* No matches */
|
||||||
|
}
|
||||||
|
|
|
@ -60,4 +60,6 @@ static inline int traverse_path_len(const struct traverse_info *info, const stru
|
||||||
return info->pathlen + tree_entry_len(n->path, n->sha1);
|
return info->pathlen + tree_entry_len(n->path, n->sha1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern int tree_entry_interesting(const struct name_entry *, struct strbuf *, int, const struct pathspec *ps);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -323,7 +323,7 @@ static void wt_status_collect_changes_worktree(struct wt_status *s)
|
||||||
}
|
}
|
||||||
rev.diffopt.format_callback = wt_status_collect_changed_cb;
|
rev.diffopt.format_callback = wt_status_collect_changed_cb;
|
||||||
rev.diffopt.format_callback_data = s;
|
rev.diffopt.format_callback_data = s;
|
||||||
rev.prune_data = s->pathspec;
|
init_pathspec(&rev.prune_data, s->pathspec);
|
||||||
run_diff_files(&rev, 0);
|
run_diff_files(&rev, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,20 +348,22 @@ static void wt_status_collect_changes_index(struct wt_status *s)
|
||||||
rev.diffopt.detect_rename = 1;
|
rev.diffopt.detect_rename = 1;
|
||||||
rev.diffopt.rename_limit = 200;
|
rev.diffopt.rename_limit = 200;
|
||||||
rev.diffopt.break_opt = 0;
|
rev.diffopt.break_opt = 0;
|
||||||
rev.prune_data = s->pathspec;
|
init_pathspec(&rev.prune_data, s->pathspec);
|
||||||
run_diff_index(&rev, 1);
|
run_diff_index(&rev, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wt_status_collect_changes_initial(struct wt_status *s)
|
static void wt_status_collect_changes_initial(struct wt_status *s)
|
||||||
{
|
{
|
||||||
|
struct pathspec pathspec;
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
|
init_pathspec(&pathspec, s->pathspec);
|
||||||
for (i = 0; i < active_nr; i++) {
|
for (i = 0; i < active_nr; i++) {
|
||||||
struct string_list_item *it;
|
struct string_list_item *it;
|
||||||
struct wt_status_change_data *d;
|
struct wt_status_change_data *d;
|
||||||
struct cache_entry *ce = active_cache[i];
|
struct cache_entry *ce = active_cache[i];
|
||||||
|
|
||||||
if (!ce_path_match(ce, s->pathspec))
|
if (!ce_path_match(ce, &pathspec))
|
||||||
continue;
|
continue;
|
||||||
it = string_list_insert(&s->change, ce->name);
|
it = string_list_insert(&s->change, ce->name);
|
||||||
d = it->util;
|
d = it->util;
|
||||||
|
@ -376,6 +378,7 @@ static void wt_status_collect_changes_initial(struct wt_status *s)
|
||||||
else
|
else
|
||||||
d->index_status = DIFF_STATUS_ADDED;
|
d->index_status = DIFF_STATUS_ADDED;
|
||||||
}
|
}
|
||||||
|
free_pathspec(&pathspec);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void wt_status_collect_untracked(struct wt_status *s)
|
static void wt_status_collect_untracked(struct wt_status *s)
|
||||||
|
|
Loading…
Reference in a new issue