Commit 3b9d0717 by Carlos Martín Nieto

Merge pull request #3138 from libgit2/cmn/v22-update

Maintenance updates for v0.22
parents fb6df50b ff19d60d
......@@ -58,6 +58,7 @@
#include "git2/submodule.h"
#include "git2/tag.h"
#include "git2/transport.h"
#include "git2/transaction.h"
#include "git2/tree.h"
#include "git2/types.h"
#include "git2/version.h"
......
......@@ -254,13 +254,9 @@ static int attr_setup(git_repository *repo)
repo, GIT_ATTR_FILE__FROM_FILE, NULL, sys.ptr);
git_buf_free(&sys);
}
if (error < 0) {
if (error == GIT_ENOTFOUND) {
giterr_clear();
error = 0;
} else
return error;
}
if (error != GIT_ENOTFOUND)
return error;
if ((error = preload_attr_file(
repo, GIT_ATTR_FILE__FROM_FILE,
......
......@@ -344,6 +344,7 @@ bool git_attr_fnmatch__match(
git_attr_fnmatch *match,
git_attr_path *path)
{
const char *relpath = path->path;
const char *filename;
int flags = 0;
......@@ -360,6 +361,8 @@ bool git_attr_fnmatch__match(
if (git__prefixcmp(path->path, match->containing_dir))
return 0;
}
relpath += match->containing_dir_length;
}
if (match->flags & GIT_ATTR_FNMATCH_ICASE)
......@@ -368,7 +371,7 @@ bool git_attr_fnmatch__match(
flags |= FNM_LEADING_DIR;
if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) {
filename = path->path;
filename = relpath;
flags |= FNM_PATHNAME;
} else {
filename = path->basename;
......@@ -378,28 +381,33 @@ bool git_attr_fnmatch__match(
}
if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) {
int matchval;
bool samename;
/* for attribute checks or root ignore checks, fail match */
if (!(match->flags & GIT_ATTR_FNMATCH_IGNORE) ||
path->basename == path->path)
return false;
/* for ignore checks, use container of current item for check */
path->basename[-1] = '\0';
flags |= FNM_LEADING_DIR;
matchval = p_fnmatch(match->pattern, path->path, flags);
path->basename[-1] = '/';
return (matchval != FNM_NOMATCH);
/* fail match if this is a file with same name as ignored folder */
samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ?
!strcasecmp(match->pattern, relpath) :
!strcmp(match->pattern, relpath);
if (samename)
return false;
return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH);
}
/* if path is a directory prefix of a negated pattern, then match */
if ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) && path->is_dir) {
size_t pathlen = strlen(path->path);
size_t pathlen = strlen(relpath);
bool prefixed = (pathlen <= match->length) &&
((match->flags & GIT_ATTR_FNMATCH_ICASE) ?
!strncasecmp(match->pattern, path->path, pathlen) :
!strncmp(match->pattern, path->path, pathlen));
!strncasecmp(match->pattern, relpath, pathlen) :
!strncmp(match->pattern, relpath, pathlen));
if (prefixed && git_path_at_end_of_segment(&match->pattern[pathlen]))
return true;
......@@ -604,7 +612,7 @@ int git_attr_fnmatch__parse(
}
if (context) {
char *slash = strchr(context, '/');
char *slash = strrchr(context, '/');
size_t len;
if (slash) {
/* include the slash for easier matching */
......@@ -614,27 +622,7 @@ int git_attr_fnmatch__parse(
}
}
if ((spec->flags & GIT_ATTR_FNMATCH_FULLPATH) != 0 &&
context != NULL && git_path_root(pattern) < 0)
{
/* use context path minus the trailing filename */
char *slash = strrchr(context, '/');
size_t contextlen = slash ? slash - context + 1 : 0;
/* given an unrooted fullpath match from a file inside a repo,
* prefix the pattern with the relative directory of the source file
*/
spec->pattern = git_pool_malloc(
pool, (uint32_t)(contextlen + spec->length + 1));
if (spec->pattern) {
memcpy(spec->pattern, context, contextlen);
memcpy(spec->pattern + contextlen, pattern, spec->length);
spec->length += contextlen;
spec->pattern[spec->length] = '\0';
}
} else {
spec->pattern = git_pool_strndup(pool, pattern, spec->length);
}
spec->pattern = git_pool_strndup(pool, pattern, spec->length);
if (!spec->pattern) {
*base = git__next_line(pattern);
......
......@@ -1749,6 +1749,9 @@ static int checkout_create_the_new(
int error = 0;
git_diff_delta *delta;
size_t i;
int caps = git_index_caps(data->index);
git_index_set_caps(data->index, caps & ~GIT_INDEXCAP_NO_FILEMODE);
git_vector_foreach(&data->diff->deltas, i, delta) {
if (actions[i] & CHECKOUT_ACTION__DEFER_REMOVE) {
......@@ -1770,6 +1773,8 @@ static int checkout_create_the_new(
}
}
git_index_set_caps(data->index, caps);
return 0;
}
......@@ -2471,7 +2476,12 @@ int git_checkout_iterator(
cleanup:
if (!error && data.index != NULL &&
(data.strategy & GIT_CHECKOUT_DONT_UPDATE_INDEX) == 0)
{
int caps = git_index_caps(data.index);
git_index_set_caps(data.index, caps & ~GIT_INDEXCAP_NO_FILEMODE);
error = git_index_write(data.index);
git_index_set_caps(data.index, caps);
}
git_diff_free(data.diff);
git_iterator_free(workdir);
......
......@@ -335,7 +335,6 @@ typedef struct {
git_config_iterator *current;
const git_config *cfg;
regex_t regex;
int has_regex;
size_t i;
} all_iter;
......@@ -470,9 +469,8 @@ int git_config_iterator_glob_new(git_config_iterator **out, const git_config *cf
iter = git__calloc(1, sizeof(all_iter));
GITERR_CHECK_ALLOC(iter);
if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) < 0) {
if ((result = regcomp(&iter->regex, regexp, REG_EXTENDED)) != 0) {
giterr_set_regex(&iter->regex, result);
regfree(&iter->regex);
git__free(iter);
return -1;
}
......@@ -505,7 +503,7 @@ int git_config_backend_foreach_match(
int error = 0;
if (regexp != NULL) {
if ((error = regcomp(&regex, regexp, REG_EXTENDED)) < 0) {
if ((error = regcomp(&regex, regexp, REG_EXTENDED)) != 0) {
giterr_set_regex(&regex, error);
regfree(&regex);
return -1;
......@@ -895,7 +893,8 @@ void multivar_iter_free(git_config_iterator *_iter)
iter->iter->free(iter->iter);
git__free(iter->name);
regfree(&iter->regex);
if (iter->have_regex)
regfree(&iter->regex);
git__free(iter);
}
......@@ -916,7 +915,7 @@ int git_config_multivar_iterator_new(git_config_iterator **out, const git_config
if (regexp != NULL) {
error = regcomp(&iter->regex, regexp, REG_EXTENDED);
if (error < 0) {
if (error != 0) {
giterr_set_regex(&iter->regex, error);
error = -1;
regfree(&iter->regex);
......
......@@ -568,7 +568,7 @@ static int config_set_multivar(
}
result = regcomp(&preg, regexp, REG_EXTENDED);
if (result < 0) {
if (result != 0) {
giterr_set_regex(&preg, result);
result = -1;
goto out;
......@@ -654,7 +654,7 @@ static int config_delete_multivar(git_config_backend *cfg, const char *name, con
refcounted_strmap_free(map);
result = regcomp(&preg, regexp, REG_EXTENDED);
if (result < 0) {
if (result != 0) {
giterr_set_regex(&preg, result);
result = -1;
goto out;
......
......@@ -116,7 +116,7 @@ static int diff_driver_add_patterns(
if (error < 0)
break;
if ((error = regcomp(&pat->re, buf.ptr, regex_flags)) < 0) {
if ((error = regcomp(&pat->re, buf.ptr, regex_flags)) != 0) {
/* if regex fails to compile, warn? fail? */
error = giterr_set_regex(&pat->re, error);
regfree(&pat->re);
......
......@@ -84,11 +84,14 @@ static git_diff_delta *diff_delta__merge_like_cgit(
* index (i.e. not in HEAD nor workdir) is given as empty.
*/
if (dup->status == GIT_DELTA_DELETED) {
if (a->status == GIT_DELTA_ADDED)
if (a->status == GIT_DELTA_ADDED) {
dup->status = GIT_DELTA_UNMODIFIED;
dup->nfiles = 2;
}
/* else don't overwrite DELETE status */
} else {
dup->status = a->status;
dup->nfiles = a->nfiles;
}
git_oid_cpy(&dup->old_file.id, &a->old_file.id);
......@@ -118,10 +121,13 @@ static git_diff_delta *diff_delta__merge_like_cgit_reversed(
return dup;
if (dup->status == GIT_DELTA_DELETED) {
if (b->status == GIT_DELTA_ADDED)
if (b->status == GIT_DELTA_ADDED) {
dup->status = GIT_DELTA_UNMODIFIED;
dup->nfiles = 2;
}
} else {
dup->status = b->status;
dup->nfiles = b->nfiles;
}
git_oid_cpy(&dup->old_file.id, &b->old_file.id);
......
......@@ -11,6 +11,41 @@
#define GIT_IGNORE_DEFAULT_RULES ".\n..\n.git\n"
/**
* A negative ignore pattern can match a positive one without
* wildcards if its pattern equals the tail of the positive
* pattern. Thus
*
* foo/bar
* !bar
*
* would result in foo/bar being unignored again.
*/
static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg)
{
char *p;
if ((rule->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0
&& (neg->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0) {
/*
* no chance of matching if rule is shorter than
* the negated one
*/
if (rule->length < neg->length)
return false;
/*
* shift pattern so its tail aligns with the
* negated pattern
*/
p = rule->pattern + rule->length - neg->length;
if (strcmp(p, neg->pattern) == 0)
return true;
}
return false;
}
/**
* A negative ignore can only unignore a file which is given explicitly before, thus
*
* foo
......@@ -31,6 +66,8 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
char *path;
git_buf buf = GIT_BUF_INIT;
*out = 0;
/* path of the file relative to the workdir, so we match the rules in subdirs */
if (match->containing_dir) {
git_buf_puts(&buf, match->containing_dir);
......@@ -41,9 +78,14 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
path = git_buf_detach(&buf);
git_vector_foreach(rules, i, rule) {
/* no chance of matching w/o a wilcard */
if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD))
continue;
if (!(rule->flags & GIT_ATTR_FNMATCH_HASWILD)) {
if (does_negate_pattern(rule, match)) {
*out = 1;
goto out;
}
else
continue;
}
/*
* If we're dealing with a directory (which we know via the
......@@ -62,7 +104,6 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
if (error < 0)
goto out;
if ((error = p_fnmatch(git_buf_cstr(&buf), path, FNM_PATHNAME)) < 0) {
giterr_set(GITERR_INVALID, "error matching pattern");
goto out;
......@@ -76,7 +117,6 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
}
}
*out = 0;
error = 0;
out:
......
......@@ -193,7 +193,7 @@ int git_reflog_drop(git_reflog *reflog, size_t idx, int rewrite_previous_entry)
entry = (git_reflog_entry *)git_reflog_entry_byindex(reflog, idx);
if (entry == NULL) {
giterr_set(GITERR_REFERENCE, "No reflog entry at index "PRIuZ, idx);
giterr_set(GITERR_REFERENCE, "No reflog entry at index %"PRIuZ, idx);
return GIT_ENOTFOUND;
}
......
......@@ -39,11 +39,31 @@ git_commit_list_node *git_revwalk__commit_lookup(
return commit;
}
typedef git_array_t(git_commit_list_node*) commit_list_node_array;
static bool interesting_arr(commit_list_node_array arr)
{
git_commit_list_node **n;
size_t i = 0, size;
size = git_array_size(arr);
for (i = 0; i < size; i++) {
n = git_array_get(arr, i);
if (!*n)
break;
if (!(*n)->uninteresting)
return true;
}
return false;
}
static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit)
{
int error;
unsigned short i;
git_array_t(git_commit_list_node *) pending = GIT_ARRAY_INIT;
commit_list_node_array pending = GIT_ARRAY_INIT;
git_commit_list_node **tmp;
assert(commit);
......@@ -64,7 +84,7 @@ static int mark_uninteresting(git_revwalk *walk, git_commit_list_node *commit)
tmp = git_array_pop(pending);
commit = tmp ? *tmp : NULL;
} while (commit != NULL);
} while (commit != NULL && !interesting_arr(pending));
git_array_clear(pending);
......@@ -142,6 +162,10 @@ static int push_commit(git_revwalk *walk, const git_oid *oid, int uninteresting,
if (commit == NULL)
return -1; /* error already reported by failed lookup */
/* A previous hide already told us we don't want this commit */
if (commit->uninteresting)
return 0;
if (uninteresting)
walk->did_hide = 1;
else
......
......@@ -146,6 +146,24 @@ void test_attr_ignore__skip_gitignore_directory(void)
assert_is_ignored(true, "NewFolder/NewFolder/File.txt");
}
void test_attr_ignore__subdirectory_gitignore(void)
{
p_unlink("attr/.gitignore");
cl_assert(!git_path_exists("attr/.gitignore"));
cl_git_mkfile(
"attr/.gitignore",
"file1\n");
p_mkdir("attr/dir", 0777);
cl_git_mkfile(
"attr/dir/.gitignore",
"file2/\n");
assert_is_ignored(true, "file1");
assert_is_ignored(true, "dir/file1");
assert_is_ignored(true, "dir/file2/actual_file"); /* in ignored dir */
assert_is_ignored(false, "dir/file3");
}
void test_attr_ignore__expand_tilde_to_homedir(void)
{
git_config *cfg;
......@@ -173,3 +191,64 @@ void test_attr_ignore__expand_tilde_to_homedir(void)
assert_is_ignored(false, "example.global_with_tilde");
}
/* Ensure that the .gitignore in the subdirectory only affects
* items in the subdirectory. */
void test_attr_ignore__gitignore_in_subdir(void)
{
cl_git_pass(p_unlink("attr/.gitignore"));
cl_must_pass(p_mkdir("attr/dir1", 0777));
cl_must_pass(p_mkdir("attr/dir1/dir2", 0777));
cl_must_pass(p_mkdir("attr/dir1/dir2/dir3", 0777));
cl_git_mkfile("attr/dir1/dir2/dir3/.gitignore", "dir1/\ndir1/subdir/");
assert_is_ignored(false, "dir1/file");
assert_is_ignored(false, "dir1/dir2/file");
assert_is_ignored(false, "dir1/dir2/dir3/file");
assert_is_ignored(true, "dir1/dir2/dir3/dir1/file");
assert_is_ignored(true, "dir1/dir2/dir3/dir1/subdir/foo");
if (cl_repo_get_bool(g_repo, "core.ignorecase")) {
cl_git_mkfile("attr/dir1/dir2/dir3/.gitignore", "DiR1/\nDiR1/subdir/\n");
assert_is_ignored(false, "dir1/file");
assert_is_ignored(false, "dir1/dir2/file");
assert_is_ignored(false, "dir1/dir2/dir3/file");
assert_is_ignored(true, "dir1/dir2/dir3/dir1/file");
assert_is_ignored(true, "dir1/dir2/dir3/dir1/subdir/foo");
}
}
/* Ensure that files do not match folder cases */
void test_attr_ignore__dont_ignore_files_for_folder(void)
{
cl_git_pass(p_unlink("attr/.gitignore"));
cl_git_mkfile("attr/dir/.gitignore", "test/\n");
/* Create "test" as a file; ensure it is not ignored. */
cl_git_mkfile("attr/dir/test", "This is a file.");
assert_is_ignored(false, "dir/test");
if (cl_repo_get_bool(g_repo, "core.ignorecase"))
assert_is_ignored(false, "dir/TeSt");
/* Create "test" as a directory; ensure it is ignored. */
cl_git_pass(p_unlink("attr/dir/test"));
cl_must_pass(p_mkdir("attr/dir/test", 0777));
assert_is_ignored(true, "dir/test");
if (cl_repo_get_bool(g_repo, "core.ignorecase"))
assert_is_ignored(true, "dir/TeSt");
/* Remove "test" entirely; ensure it is not ignored.
* (As it doesn't exist, it is not a directory.)
*/
cl_must_pass(p_rmdir("attr/dir/test"));
assert_is_ignored(false, "dir/test");
if (cl_repo_get_bool(g_repo, "core.ignorecase"))
assert_is_ignored(false, "dir/TeSt");
}
......@@ -923,18 +923,43 @@ void test_checkout_tree__filemode_preserved_in_index(void)
git_index *index;
const git_index_entry *entry;
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
cl_git_pass(git_repository_index(&index, g_repo));
/* test a freshly added executable */
cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
opts.checkout_strategy = GIT_CHECKOUT_FORCE;
cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0));
cl_assert_equal_i(0100755, entry->mode);
git_commit_free(commit);
/* Now start with a commit which has a text file */
cl_git_pass(git_oid_fromstr(&executable_oid, "cf80f8de9f1185bf3a05f993f6121880dd0cfbc9"));
cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
cl_assert_equal_i(0100644, entry->mode);
git_commit_free(commit);
/* And then check out to a commit which converts the text file to an executable */
cl_git_pass(git_oid_fromstr(&executable_oid, "144344043ba4d4a405da03de3844aa829ae8be0e"));
cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));
cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
cl_assert(entry = git_index_get_bypath(index, "a/b.txt", 0));
cl_assert_equal_i(0100755, entry->mode);
git_commit_free(commit);
git_index_free(index);
}
......
......@@ -343,6 +343,18 @@ static void check_glob_iter(git_config *cfg, const char *regexp, int expected)
git_config_iterator_free(iter);
}
void test_config_read__iterator_invalid_glob(void)
{
git_config *cfg;
git_config_iterator *iter;
cl_git_pass(git_config_open_ondisk(&cfg, cl_fixture("config/config9")));
cl_git_fail(git_config_iterator_glob_new(&iter, cfg, "*"));
git_config_free(cfg);
}
void test_config_read__iterator_glob(void)
{
git_config *cfg;
......
......@@ -892,6 +892,59 @@ void test_status_ignore__negative_ignores_without_trailing_slash_inside_ignores(
cl_assert(found_parent_child2_file);
}
void test_status_ignore__negative_directory_ignores(void)
{
static const char *test_files[] = {
"empty_standard_repo/parent/child1/bar.txt",
"empty_standard_repo/parent/child2/bar.txt",
"empty_standard_repo/parent/child3/foo.txt",
"empty_standard_repo/parent/child4/bar.txt",
"empty_standard_repo/parent/nested/child5/bar.txt",
"empty_standard_repo/parent/nested/child6/bar.txt",
"empty_standard_repo/parent/nested/child7/bar.txt",
"empty_standard_repo/padded_parent/child8/bar.txt",
NULL
};
make_test_data("empty_standard_repo", test_files);
cl_git_mkfile(
"empty_standard_repo/.gitignore",
"foo.txt\n"
"parent/child1\n"
"parent/child2\n"
"parent/child4\n"
"parent/nested/child5\n"
"nested/child6\n"
"nested/child7\n"
"padded_parent/child8\n"
/* test simple exact match */
"!parent/child1\n"
/* test negating file without negating dir */
"!parent/child2/bar.txt\n"
/* test negative pattern on dir with its content
* being ignored */
"!parent/child3\n"
/* test with partial match at end */
"!child4\n"
/* test with partial match with '/' at end */
"!nested/child5\n"
/* test with complete match */
"!nested/child6\n"
/* test with trailing '/' */
"!child7/\n"
/* test with partial dir match */
"!_parent/child8\n");
refute_is_ignored("parent/child1/bar.txt");
assert_is_ignored("parent/child2/bar.txt");
assert_is_ignored("parent/child3/foo.txt");
refute_is_ignored("parent/child4/bar.txt");
assert_is_ignored("parent/nested/child5/bar.txt");
refute_is_ignored("parent/nested/child6/bar.txt");
refute_is_ignored("parent/nested/child7/bar.txt");
assert_is_ignored("padded_parent/child8/bar.txt");
}
void test_status_ignore__filename_with_cr(void)
{
int ignored;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment