mirror of
https://git.proxmox.com/git/libgit2
synced 2025-08-15 13:06:29 +00:00
commit
7779437fd5
@ -46,6 +46,7 @@ enum {
|
||||
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),
|
||||
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
|
||||
GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
|
||||
GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -96,6 +96,8 @@ typedef enum {
|
||||
* the top-level directory will be included (with a trailing
|
||||
* slash on the entry name). Given this flag, the directory
|
||||
* itself will not be included, but all the files in it will.
|
||||
* - GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH indicates that the given
|
||||
* path will be treated as a literal path, and not as a pathspec.
|
||||
*/
|
||||
|
||||
enum {
|
||||
@ -104,6 +106,7 @@ enum {
|
||||
GIT_STATUS_OPT_INCLUDE_UNMODIFIED = (1 << 2),
|
||||
GIT_STATUS_OPT_EXCLUDE_SUBMODULES = (1 << 3),
|
||||
GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS = (1 << 4),
|
||||
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH = (1 << 5),
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -426,17 +426,7 @@ int git_attr_fnmatch__parse(
|
||||
return -1;
|
||||
} else {
|
||||
/* strip '\' that might have be used for internal whitespace */
|
||||
char *to = spec->pattern;
|
||||
for (scan = spec->pattern; *scan; to++, scan++) {
|
||||
if (*scan == '\\')
|
||||
scan++; /* skip '\' but include next char */
|
||||
if (to != scan)
|
||||
*to = *scan;
|
||||
}
|
||||
if (to != scan) {
|
||||
*to = '\0';
|
||||
spec->length = (to - spec->pattern);
|
||||
}
|
||||
spec->length = git__unescape(spec->pattern);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -496,3 +496,7 @@ bool git_buf_is_binary(const git_buf *buf)
|
||||
return ((printable >> 7) < nonprintable);
|
||||
}
|
||||
|
||||
void git_buf_unescape(git_buf *buf)
|
||||
{
|
||||
buf->size = git__unescape(buf->ptr);
|
||||
}
|
||||
|
@ -151,4 +151,7 @@ int git_buf_common_prefix(git_buf *buf, const git_strarray *strings);
|
||||
/* Check if buffer looks like it contains binary data */
|
||||
bool git_buf_is_binary(const git_buf *buf);
|
||||
|
||||
/* Unescape all characters in a buffer */
|
||||
void git_buf_unescape(git_buf *buf);
|
||||
|
||||
#endif
|
||||
|
24
src/diff.c
24
src/diff.c
@ -20,14 +20,21 @@ static char *diff_prefix_from_pathspec(const git_strarray *pathspec)
|
||||
return NULL;
|
||||
|
||||
/* diff prefix will only be leading non-wildcards */
|
||||
for (scan = prefix.ptr; *scan && !git__iswildcard(*scan); ++scan);
|
||||
for (scan = prefix.ptr; *scan; ++scan) {
|
||||
if (git__iswildcard(*scan) &&
|
||||
(scan == prefix.ptr || (*(scan - 1) != '\\')))
|
||||
break;
|
||||
}
|
||||
git_buf_truncate(&prefix, scan - prefix.ptr);
|
||||
|
||||
if (prefix.size > 0)
|
||||
return git_buf_detach(&prefix);
|
||||
if (prefix.size <= 0) {
|
||||
git_buf_free(&prefix);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
git_buf_free(&prefix);
|
||||
return NULL;
|
||||
git_buf_unescape(&prefix);
|
||||
|
||||
return git_buf_detach(&prefix);
|
||||
}
|
||||
|
||||
static bool diff_pathspec_is_interesting(const git_strarray *pathspec)
|
||||
@ -54,7 +61,11 @@ static bool diff_path_matches_pathspec(git_diff_list *diff, const char *path)
|
||||
return true;
|
||||
|
||||
git_vector_foreach(&diff->pathspec, i, match) {
|
||||
int result = p_fnmatch(match->pattern, path, 0);
|
||||
int result = strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
|
||||
|
||||
if (((diff->opts.flags & GIT_DIFF_DISABLE_PATHSPEC_MATCH) == 0) &&
|
||||
result == FNM_NOMATCH)
|
||||
result = p_fnmatch(match->pattern, path, 0);
|
||||
|
||||
/* if we didn't match, look for exact dirname prefix match */
|
||||
if (result == FNM_NOMATCH &&
|
||||
@ -826,4 +837,3 @@ int git_diff_merge(
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
@ -99,6 +99,8 @@ int git_status_foreach_ext(
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_INCLUDE_UNMODIFIED;
|
||||
if ((opts->flags & GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS) != 0)
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
|
||||
if ((opts->flags & GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH) != 0)
|
||||
diffopt.flags = diffopt.flags | GIT_DIFF_DISABLE_PATHSPEC_MATCH;
|
||||
/* TODO: support EXCLUDE_SUBMODULES flag */
|
||||
|
||||
if (show != GIT_STATUS_SHOW_WORKDIR_ONLY &&
|
||||
@ -176,10 +178,12 @@ static int get_one_status(const char *path, unsigned int status, void *data)
|
||||
sfi->count++;
|
||||
sfi->status = status;
|
||||
|
||||
if (sfi->count > 1 || strcmp(sfi->expected, path) != 0) {
|
||||
if (sfi->count > 1 ||
|
||||
(strcmp(sfi->expected, path) != 0 &&
|
||||
p_fnmatch(sfi->expected, path, 0) != 0)) {
|
||||
giterr_set(GITERR_INVALID,
|
||||
"Ambiguous path '%s' given to git_status_file", sfi->expected);
|
||||
return -1;
|
||||
return GIT_EAMBIGUOUS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
18
src/util.c
18
src/util.c
@ -435,3 +435,21 @@ int git__parse_bool(int *out, const char *value)
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
size_t git__unescape(char *str)
|
||||
{
|
||||
char *scan, *pos = str;
|
||||
|
||||
for (scan = str; *scan; pos++, scan++) {
|
||||
if (*scan == '\\' && *(scan + 1) != '\0')
|
||||
scan++; /* skip '\' but include next char */
|
||||
if (pos != scan)
|
||||
*pos = *scan;
|
||||
}
|
||||
|
||||
if (pos != scan) {
|
||||
*pos = '\0';
|
||||
}
|
||||
|
||||
return (pos - str);
|
||||
}
|
||||
|
@ -238,4 +238,13 @@ extern int git__parse_bool(int *out, const char *value);
|
||||
*/
|
||||
int git__date_parse(git_time_t *out, const char *date);
|
||||
|
||||
/*
|
||||
* Unescapes a string in-place.
|
||||
*
|
||||
* Edge cases behavior:
|
||||
* - "jackie\" -> "jacky\"
|
||||
* - "chan\\" -> "chan\"
|
||||
*/
|
||||
extern size_t git__unescape(char *str);
|
||||
|
||||
#endif /* INCLUDE_util_h__ */
|
||||
|
@ -658,3 +658,23 @@ void test_core_buffer__puts_escaped(void)
|
||||
|
||||
git_buf_free(&a);
|
||||
}
|
||||
|
||||
static void assert_unescape(char *expected, char *to_unescape) {
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
|
||||
cl_git_pass(git_buf_sets(&buf, to_unescape));
|
||||
git_buf_unescape(&buf);
|
||||
cl_assert_equal_s(expected, buf.ptr);
|
||||
cl_assert_equal_i(strlen(expected), buf.size);
|
||||
|
||||
git_buf_free(&buf);
|
||||
}
|
||||
|
||||
void test_core_buffer__unescape(void)
|
||||
{
|
||||
assert_unescape("Escaped\\", "Es\\ca\\ped\\");
|
||||
assert_unescape("Es\\caped\\", "Es\\\\ca\\ped\\\\");
|
||||
assert_unescape("\\", "\\");
|
||||
assert_unescape("\\", "\\\\");
|
||||
assert_unescape("", "");
|
||||
}
|
||||
|
@ -517,6 +517,85 @@ void test_status_worktree__status_file_with_clean_index_and_empty_workdir(void)
|
||||
cl_git_pass(p_unlink("my-index"));
|
||||
}
|
||||
|
||||
void test_status_worktree__bracket_in_filename(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_index *index;
|
||||
status_entry_single result;
|
||||
unsigned int status_flags;
|
||||
int error;
|
||||
|
||||
#define FILE_WITH_BRACKET "LICENSE[1].md"
|
||||
#define FILE_WITHOUT_BRACKET "LICENSE1.md"
|
||||
|
||||
cl_git_pass(git_repository_init(&repo, "with_bracket", 0));
|
||||
cl_git_mkfile("with_bracket/" FILE_WITH_BRACKET, "I have a bracket in my name\n");
|
||||
|
||||
/* file is new to working directory */
|
||||
|
||||
memset(&result, 0, sizeof(result));
|
||||
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
|
||||
cl_assert_equal_i(1, result.count);
|
||||
cl_assert(result.status == GIT_STATUS_WT_NEW);
|
||||
|
||||
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
|
||||
cl_assert(status_flags == GIT_STATUS_WT_NEW);
|
||||
|
||||
/* ignore the file */
|
||||
|
||||
cl_git_rewritefile("with_bracket/.gitignore", "*.md\n.gitignore\n");
|
||||
|
||||
memset(&result, 0, sizeof(result));
|
||||
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
|
||||
cl_assert_equal_i(2, result.count);
|
||||
cl_assert(result.status == GIT_STATUS_IGNORED);
|
||||
|
||||
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
|
||||
cl_assert(status_flags == GIT_STATUS_IGNORED);
|
||||
|
||||
/* don't ignore the file */
|
||||
|
||||
cl_git_rewritefile("with_bracket/.gitignore", ".gitignore\n");
|
||||
|
||||
memset(&result, 0, sizeof(result));
|
||||
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
|
||||
cl_assert_equal_i(2, result.count);
|
||||
cl_assert(result.status == GIT_STATUS_WT_NEW);
|
||||
|
||||
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
|
||||
cl_assert(status_flags == GIT_STATUS_WT_NEW);
|
||||
|
||||
/* add the file to the index */
|
||||
|
||||
cl_git_pass(git_repository_index(&index, repo));
|
||||
cl_git_pass(git_index_add(index, FILE_WITH_BRACKET, 0));
|
||||
cl_git_pass(git_index_write(index));
|
||||
|
||||
memset(&result, 0, sizeof(result));
|
||||
cl_git_pass(git_status_foreach(repo, cb_status__single, &result));
|
||||
cl_assert_equal_i(2, result.count);
|
||||
cl_assert(result.status == GIT_STATUS_INDEX_NEW);
|
||||
|
||||
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITH_BRACKET));
|
||||
cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
|
||||
|
||||
/* Create file without bracket */
|
||||
|
||||
cl_git_mkfile("with_bracket/" FILE_WITHOUT_BRACKET, "I have no bracket in my name!\n");
|
||||
|
||||
cl_git_pass(git_status_file(&status_flags, repo, FILE_WITHOUT_BRACKET));
|
||||
cl_assert(status_flags == GIT_STATUS_WT_NEW);
|
||||
|
||||
cl_git_pass(git_status_file(&status_flags, repo, "LICENSE\\[1\\].md"));
|
||||
cl_assert(status_flags == GIT_STATUS_INDEX_NEW);
|
||||
|
||||
error = git_status_file(&status_flags, repo, FILE_WITH_BRACKET);
|
||||
cl_git_fail(error);
|
||||
cl_assert(error == GIT_EAMBIGUOUS);
|
||||
|
||||
git_index_free(index);
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
||||
void test_status_worktree__space_in_filename(void)
|
||||
{
|
||||
@ -647,3 +726,49 @@ void test_status_worktree__filemode_changes(void)
|
||||
|
||||
git_config_free(cfg);
|
||||
}
|
||||
|
||||
int cb_status__expected_path(const char *p, unsigned int s, void *payload)
|
||||
{
|
||||
const char *expected_path = (const char *)payload;
|
||||
|
||||
GIT_UNUSED(s);
|
||||
|
||||
if (payload == NULL)
|
||||
cl_fail("Unexpected path");
|
||||
|
||||
cl_assert_equal_s(expected_path, p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void test_status_worktree__disable_pathspec_match(void)
|
||||
{
|
||||
git_repository *repo;
|
||||
git_status_options opts;
|
||||
char *file_with_bracket = "LICENSE[1].md",
|
||||
*imaginary_file_with_bracket = "LICENSE[1-2].md";
|
||||
|
||||
cl_git_pass(git_repository_init(&repo, "pathspec", 0));
|
||||
cl_git_mkfile("pathspec/LICENSE[1].md", "screaming bracket\n");
|
||||
cl_git_mkfile("pathspec/LICENSE1.md", "no bracket\n");
|
||||
|
||||
memset(&opts, 0, sizeof(opts));
|
||||
opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
|
||||
GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
|
||||
opts.pathspec.count = 1;
|
||||
opts.pathspec.strings = &file_with_bracket;
|
||||
|
||||
cl_git_pass(
|
||||
git_status_foreach_ext(repo, &opts, cb_status__expected_path,
|
||||
file_with_bracket)
|
||||
);
|
||||
|
||||
/* Test passing a pathspec matching files in the workdir. */
|
||||
/* Must not match because pathspecs are disabled. */
|
||||
opts.pathspec.strings = &imaginary_file_with_bracket;
|
||||
cl_git_pass(
|
||||
git_status_foreach_ext(repo, &opts, cb_status__expected_path, NULL)
|
||||
);
|
||||
|
||||
git_repository_free(repo);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user