Merge branch 'jk/diff-no-index-with-pathspec'
"git diff --no-index dirA dirB" can limit the comparison with pathspec at the end of the command line, just like normal "git diff". * jk/diff-no-index-with-pathspec: diff --no-index: support limiting by pathspec pathspec: add flag to indicate operation without repository pathspec: add match_leading_pathspec variant
This commit is contained in:
@@ -14,7 +14,7 @@ git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]
|
||||
git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]
|
||||
git diff [<options>] <commit>...<commit> [--] [<path>...]
|
||||
git diff [<options>] <blob> <blob>
|
||||
git diff [<options>] --no-index [--] <path> <path>
|
||||
git diff [<options>] --no-index [--] <path> <path> [<pathspec>...]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
@@ -31,14 +31,18 @@ files on disk.
|
||||
further add to the index but you still haven't. You can
|
||||
stage these changes by using linkgit:git-add[1].
|
||||
|
||||
`git diff [<options>] --no-index [--] <path> <path>`::
|
||||
`git diff [<options>] --no-index [--] <path> <path> [<pathspec>...]`::
|
||||
|
||||
This form is to compare the given two paths on the
|
||||
filesystem. You can omit the `--no-index` option when
|
||||
running the command in a working tree controlled by Git and
|
||||
at least one of the paths points outside the working tree,
|
||||
or when running the command outside a working tree
|
||||
controlled by Git. This form implies `--exit-code`.
|
||||
controlled by Git. This form implies `--exit-code`. If both
|
||||
paths point to directories, additional pathspecs may be
|
||||
provided. These will limit the files included in the
|
||||
difference. All such pathspecs must be relative as they
|
||||
apply to both sides of the diff.
|
||||
|
||||
`git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]`::
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ static const char builtin_diff_usage[] =
|
||||
" or: git diff [<options>] [--merge-base] <commit> [<commit>...] <commit> [--] [<path>...]\n"
|
||||
" or: git diff [<options>] <commit>...<commit> [--] [<path>...]\n"
|
||||
" or: git diff [<options>] <blob> <blob>\n"
|
||||
" or: git diff [<options>] --no-index [--] <path> <path>"
|
||||
" or: git diff [<options>] --no-index [--] <path> <path> [<pathspec>...]"
|
||||
"\n"
|
||||
COMMON_DIFF_OPTIONS_HELP;
|
||||
|
||||
|
||||
@@ -15,20 +15,45 @@
|
||||
#include "gettext.h"
|
||||
#include "revision.h"
|
||||
#include "parse-options.h"
|
||||
#include "pathspec.h"
|
||||
#include "string-list.h"
|
||||
#include "dir.h"
|
||||
|
||||
static int read_directory_contents(const char *path, struct string_list *list)
|
||||
static int read_directory_contents(const char *path, struct string_list *list,
|
||||
const struct pathspec *pathspec,
|
||||
int skip)
|
||||
{
|
||||
struct strbuf match = STRBUF_INIT;
|
||||
int len;
|
||||
DIR *dir;
|
||||
struct dirent *e;
|
||||
|
||||
if (!(dir = opendir(path)))
|
||||
return error("Could not open directory %s", path);
|
||||
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir)))
|
||||
string_list_insert(list, e->d_name);
|
||||
if (pathspec) {
|
||||
strbuf_addstr(&match, path);
|
||||
strbuf_complete(&match, '/');
|
||||
strbuf_remove(&match, 0, skip);
|
||||
|
||||
len = match.len;
|
||||
}
|
||||
|
||||
while ((e = readdir_skip_dot_and_dotdot(dir))) {
|
||||
if (pathspec) {
|
||||
strbuf_setlen(&match, len);
|
||||
strbuf_addstr(&match, e->d_name);
|
||||
|
||||
if (!match_leading_pathspec(NULL, pathspec,
|
||||
match.buf, match.len,
|
||||
0, NULL, e->d_type == DT_DIR ? 1 : 0))
|
||||
continue;
|
||||
}
|
||||
|
||||
string_list_insert(list, e->d_name);
|
||||
}
|
||||
|
||||
strbuf_release(&match);
|
||||
closedir(dir);
|
||||
return 0;
|
||||
}
|
||||
@@ -131,7 +156,8 @@ static struct diff_filespec *noindex_filespec(const struct git_hash_algo *algop,
|
||||
}
|
||||
|
||||
static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
|
||||
const char *name1, const char *name2, int recursing)
|
||||
const char *name1, const char *name2, int recursing,
|
||||
const struct pathspec *ps, int skip1, int skip2)
|
||||
{
|
||||
int mode1 = 0, mode2 = 0;
|
||||
enum special special1 = SPECIAL_NONE, special2 = SPECIAL_NONE;
|
||||
@@ -171,9 +197,9 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
|
||||
int i1, i2, ret = 0;
|
||||
size_t len1 = 0, len2 = 0;
|
||||
|
||||
if (name1 && read_directory_contents(name1, &p1))
|
||||
if (name1 && read_directory_contents(name1, &p1, ps, skip1))
|
||||
return -1;
|
||||
if (name2 && read_directory_contents(name2, &p2)) {
|
||||
if (name2 && read_directory_contents(name2, &p2, ps, skip2)) {
|
||||
string_list_clear(&p1, 0);
|
||||
return -1;
|
||||
}
|
||||
@@ -218,7 +244,7 @@ static int queue_diff(struct diff_options *o, const struct git_hash_algo *algop,
|
||||
n2 = buffer2.buf;
|
||||
}
|
||||
|
||||
ret = queue_diff(o, algop, n1, n2, 1);
|
||||
ret = queue_diff(o, algop, n1, n2, 1, ps, skip1, skip2);
|
||||
}
|
||||
string_list_clear(&p1, 0);
|
||||
string_list_clear(&p2, 0);
|
||||
@@ -258,8 +284,10 @@ static void append_basename(struct strbuf *path, const char *dir, const char *fi
|
||||
* DWIM "diff D F" into "diff D/F F" and "diff F D" into "diff F D/F"
|
||||
* Note that we append the basename of F to D/, so "diff a/b/file D"
|
||||
* becomes "diff a/b/file D/file", not "diff a/b/file D/a/b/file".
|
||||
*
|
||||
* Return 1 if both paths are directories, 0 otherwise.
|
||||
*/
|
||||
static void fixup_paths(const char **path, struct strbuf *replacement)
|
||||
static int fixup_paths(const char **path, struct strbuf *replacement)
|
||||
{
|
||||
struct stat st;
|
||||
unsigned int isdir0 = 0, isdir1 = 0;
|
||||
@@ -282,26 +310,31 @@ static void fixup_paths(const char **path, struct strbuf *replacement)
|
||||
if ((isdir0 && ispipe1) || (ispipe0 && isdir1))
|
||||
die(_("cannot compare a named pipe to a directory"));
|
||||
|
||||
if (isdir0 == isdir1)
|
||||
return;
|
||||
/* if both paths are directories, we will enable pathspecs */
|
||||
if (isdir0 && isdir1)
|
||||
return 1;
|
||||
|
||||
if (isdir0) {
|
||||
append_basename(replacement, path[0], path[1]);
|
||||
path[0] = replacement->buf;
|
||||
} else {
|
||||
} else if (isdir1) {
|
||||
append_basename(replacement, path[1], path[0]);
|
||||
path[1] = replacement->buf;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char * const diff_no_index_usage[] = {
|
||||
N_("git diff --no-index [<options>] <path> <path>"),
|
||||
N_("git diff --no-index [<options>] <path> <path> [<pathspec>...]"),
|
||||
NULL
|
||||
};
|
||||
|
||||
int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
|
||||
int implicit_no_index, int argc, const char **argv)
|
||||
{
|
||||
int i, no_index;
|
||||
struct pathspec pathspec, *ps = NULL;
|
||||
int i, no_index, skip1 = 0, skip2 = 0;
|
||||
int ret = 1;
|
||||
const char *paths[2];
|
||||
char *to_free[ARRAY_SIZE(paths)] = { 0 };
|
||||
@@ -317,13 +350,12 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
|
||||
options = add_diff_options(no_index_options, &revs->diffopt);
|
||||
argc = parse_options(argc, argv, revs->prefix, options,
|
||||
diff_no_index_usage, 0);
|
||||
if (argc != 2) {
|
||||
if (argc < 2) {
|
||||
if (implicit_no_index)
|
||||
warning(_("Not a git repository. Use --no-index to "
|
||||
"compare two paths outside a working tree"));
|
||||
usage_with_options(diff_no_index_usage, options);
|
||||
}
|
||||
FREE_AND_NULL(options);
|
||||
for (i = 0; i < 2; i++) {
|
||||
const char *p = argv[i];
|
||||
if (!strcmp(p, "-"))
|
||||
@@ -337,7 +369,23 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
|
||||
paths[i] = p;
|
||||
}
|
||||
|
||||
fixup_paths(paths, &replacement);
|
||||
if (fixup_paths(paths, &replacement)) {
|
||||
parse_pathspec(&pathspec, PATHSPEC_FROMTOP | PATHSPEC_ATTR,
|
||||
PATHSPEC_PREFER_FULL | PATHSPEC_NO_REPOSITORY,
|
||||
NULL, &argv[2]);
|
||||
if (pathspec.nr)
|
||||
ps = &pathspec;
|
||||
|
||||
skip1 = strlen(paths[0]);
|
||||
skip1 += paths[0][skip1] == '/' ? 0 : 1;
|
||||
skip2 = strlen(paths[1]);
|
||||
skip2 += paths[1][skip2] == '/' ? 0 : 1;
|
||||
} else if (argc > 2) {
|
||||
warning(_("Limiting comparison with pathspecs is only "
|
||||
"supported if both paths are directories."));
|
||||
usage_with_options(diff_no_index_usage, options);
|
||||
}
|
||||
FREE_AND_NULL(options);
|
||||
|
||||
revs->diffopt.skip_stat_unmatch = 1;
|
||||
if (!revs->diffopt.output_format)
|
||||
@@ -354,7 +402,8 @@ int diff_no_index(struct rev_info *revs, const struct git_hash_algo *algop,
|
||||
setup_diff_pager(&revs->diffopt);
|
||||
revs->diffopt.flags.exit_with_status = 1;
|
||||
|
||||
if (queue_diff(&revs->diffopt, algop, paths[0], paths[1], 0))
|
||||
if (queue_diff(&revs->diffopt, algop, paths[0], paths[1], 0, ps,
|
||||
skip1, skip2))
|
||||
goto out;
|
||||
diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
|
||||
diffcore_std(&revs->diffopt);
|
||||
@@ -370,5 +419,7 @@ out:
|
||||
for (i = 0; i < ARRAY_SIZE(to_free); i++)
|
||||
free(to_free[i]);
|
||||
strbuf_release(&replacement);
|
||||
if (ps)
|
||||
clear_pathspec(ps);
|
||||
return ret;
|
||||
}
|
||||
|
||||
19
dir.c
19
dir.c
@@ -397,9 +397,12 @@ static int match_pathspec_item(struct index_state *istate,
|
||||
strncmp(item->match, name - prefix, item->prefix))
|
||||
return 0;
|
||||
|
||||
if (item->attr_match_nr &&
|
||||
!match_pathspec_attrs(istate, name - prefix, namelen + prefix, item))
|
||||
return 0;
|
||||
if (item->attr_match_nr) {
|
||||
if (!istate)
|
||||
BUG("magic PATHSPEC_ATTR requires an index");
|
||||
if (!match_pathspec_attrs(istate, name - prefix, namelen + prefix, item))
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* If the match was just the prefix, we matched */
|
||||
if (!*match)
|
||||
@@ -577,6 +580,16 @@ int match_pathspec(struct index_state *istate,
|
||||
prefix, seen, flags);
|
||||
}
|
||||
|
||||
int match_leading_pathspec(struct index_state *istate,
|
||||
const struct pathspec *ps,
|
||||
const char *name, int namelen,
|
||||
int prefix, char *seen, int is_dir)
|
||||
{
|
||||
unsigned flags = is_dir ? DO_MATCH_DIRECTORY | DO_MATCH_LEADING_PATHSPEC : 0;
|
||||
return match_pathspec_with_flags(istate, ps, name, namelen,
|
||||
prefix, seen, flags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a submodule is a superset of the pathspec
|
||||
*/
|
||||
|
||||
@@ -492,7 +492,7 @@ static void init_pathspec_item(struct pathspec_item *item, unsigned flags,
|
||||
if (!match) {
|
||||
const char *hint_path;
|
||||
|
||||
if (!have_git_dir())
|
||||
if ((flags & PATHSPEC_NO_REPOSITORY) || !have_git_dir())
|
||||
die(_("'%s' is outside the directory tree"),
|
||||
copyfrom);
|
||||
hint_path = repo_get_work_tree(the_repository);
|
||||
@@ -614,6 +614,10 @@ void parse_pathspec(struct pathspec *pathspec,
|
||||
(flags & PATHSPEC_PREFER_FULL))
|
||||
BUG("PATHSPEC_PREFER_CWD and PATHSPEC_PREFER_FULL are incompatible");
|
||||
|
||||
if ((flags & PATHSPEC_NO_REPOSITORY) &&
|
||||
(~magic_mask & (PATHSPEC_ATTR | PATHSPEC_FROMTOP)))
|
||||
BUG("PATHSPEC_NO_REPOSITORY is incompatible with PATHSPEC_ATTR and PATHSPEC_FROMTOP");
|
||||
|
||||
/* No arguments with prefix -> prefix pathspec */
|
||||
if (!entry) {
|
||||
if (flags & PATHSPEC_PREFER_FULL)
|
||||
|
||||
11
pathspec.h
11
pathspec.h
@@ -76,6 +76,11 @@ struct pathspec {
|
||||
* allowed, then it will automatically set for every pathspec.
|
||||
*/
|
||||
#define PATHSPEC_LITERAL_PATH (1<<6)
|
||||
/*
|
||||
* For git diff --no-index, indicate that we are operating without
|
||||
* a repository or index.
|
||||
*/
|
||||
#define PATHSPEC_NO_REPOSITORY (1<<7)
|
||||
|
||||
/**
|
||||
* Given command line arguments and a prefix, convert the input to
|
||||
@@ -184,6 +189,12 @@ int match_pathspec(struct index_state *istate,
|
||||
const char *name, int namelen,
|
||||
int prefix, char *seen, int is_dir);
|
||||
|
||||
/* Set both DO_MATCH_DIRECTORY and DO_MATCH_LEADING_PATHSPEC if is_dir true */
|
||||
int match_leading_pathspec(struct index_state *istate,
|
||||
const struct pathspec *ps,
|
||||
const char *name, int namelen,
|
||||
int prefix, char *seen, int is_dir);
|
||||
|
||||
/*
|
||||
* Determine whether a pathspec will match only entire index entries (non-sparse
|
||||
* files and/or entire sparse directories). If the pathspec has the potential to
|
||||
|
||||
@@ -295,4 +295,79 @@ test_expect_success PIPE,SYMLINKS 'diff --no-index reads from pipes' '
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index F F rejects pathspecs' '
|
||||
test_must_fail git diff --no-index -- a/1 a/2 a 2>actual.err &&
|
||||
test_grep "usage: git diff --no-index" actual.err
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index D F rejects pathspecs' '
|
||||
test_must_fail git diff --no-index -- a a/2 a 2>actual.err &&
|
||||
test_grep "usage: git diff --no-index" actual.err
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index F D rejects pathspecs' '
|
||||
test_must_fail git diff --no-index -- a/1 b b 2>actual.err &&
|
||||
test_grep "usage: git diff --no-index" actual.err
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index rejects absolute pathspec' '
|
||||
test_must_fail git diff --no-index -- a b $(pwd)/a/1
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with pathspec' '
|
||||
test_expect_code 1 git diff --name-status --no-index a b 1 >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
D a/1
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with pathspec no matches' '
|
||||
test_expect_code 0 git diff --name-status --no-index a b missing
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with negative pathspec' '
|
||||
test_expect_code 1 git diff --name-status --no-index a b ":!2" >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
D a/1
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'setup nested' '
|
||||
mkdir -p c/1/2 &&
|
||||
mkdir -p d/1/2 &&
|
||||
echo 1 >c/1/2/a &&
|
||||
echo 2 >c/1/2/b
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with pathspec nested negative pathspec' '
|
||||
test_expect_code 0 git diff --no-index c d ":!1"
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with pathspec nested pathspec' '
|
||||
test_expect_code 1 git diff --name-status --no-index c d 1/2 >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
D c/1/2/a
|
||||
D c/1/2/b
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with pathspec glob' '
|
||||
test_expect_code 1 git diff --name-status --no-index c d ":(glob)**/a" >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
D c/1/2/a
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'diff --no-index with pathspec glob and exclude' '
|
||||
test_expect_code 1 git diff --name-status --no-index c d ":(glob,exclude)**/a" >actual &&
|
||||
cat >expect <<-EOF &&
|
||||
D c/1/2/b
|
||||
EOF
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_done
|
||||
|
||||
Reference in New Issue
Block a user