Unverified Commit 8d1b36a6 by Edward Thomson Committed by GitHub

Merge pull request #4942 from libgit2/ethomson/v0.27.8

Release v0.27.8
parents 313440c3 703885a8
v0.27.8
-------
This is a bugfix release with the following change:
- Negative gitignore rules should match git's behavior. For example,
given a gitignore rule of `*.test` and a second gitignore rule of
`!dir/*`, we would incorrect apply the negation rules. With this
fix, we behave like git.
- Always provide custom transport implementations with the URL in the
action function. v0.27.7 included a change that would erroneously
provide NULL to subsequent calls to the action function. This is
fixed.
- Fix several bugs parsing malformed commits and malformed trees.
- Allow configuration file directory locations to be specified as
`/dev/null`.
- Ensure that when an error occurs reading from the loose ODB backend
that we do not segfault.
- Ensure that when a filter stream application fails that we do not
segfault.
- Ensure that any configuration reading failures are propagated while
loading submodule information.
- Peel annotated tags fully when creating an annotated commit.
- Ensure that numbers are parsed correctly in a variety of places.
v0.27.7
-------
......
......@@ -710,7 +710,7 @@ GIT_EXTERN(int) git_reference_normalize_name(
*/
GIT_EXTERN(int) git_reference_peel(
git_object **out,
git_reference *ref,
const git_reference *ref,
git_otype type);
/**
......
......@@ -7,10 +7,10 @@
#ifndef INCLUDE_git_version_h__
#define INCLUDE_git_version_h__
#define LIBGIT2_VERSION "0.27.7"
#define LIBGIT2_VERSION "0.27.8"
#define LIBGIT2_VER_MAJOR 0
#define LIBGIT2_VER_MINOR 27
#define LIBGIT2_VER_REVISION 7
#define LIBGIT2_VER_REVISION 8
#define LIBGIT2_VER_PATCH 0
#define LIBGIT2_SOVERSION 27
......
......@@ -123,19 +123,19 @@ int git_annotated_commit_from_ref(
git_repository *repo,
const git_reference *ref)
{
git_reference *resolved;
git_object *peeled;
int error = 0;
assert(out && repo && ref);
*out = NULL;
if ((error = git_reference_resolve(&resolved, ref)) < 0)
if ((error = git_reference_peel(&peeled, ref, GIT_OBJ_COMMIT)) < 0)
return error;
error = annotated_commit_init_from_id(out,
repo,
git_reference_target(resolved),
git_object_id(peeled),
git_reference_name(ref));
if (!error) {
......@@ -143,7 +143,7 @@ int git_annotated_commit_from_ref(
GITERR_CHECK_ALLOC((*out)->ref_name);
}
git_reference_free(resolved);
git_object_free(peeled);
return error;
}
......
......@@ -312,8 +312,9 @@ static int apply_binary(
&patch->binary.old_file)) < 0)
goto done;
/* Verify that the resulting file with the reverse patch applied matches the source file */
if (source_len != reverse.size ||
memcmp(source, reverse.ptr, source_len) != 0) {
(source_len && memcmp(source, reverse.ptr, source_len) != 0)) {
error = apply_err("binary patch did not apply cleanly");
goto done;
}
......
......@@ -594,8 +594,9 @@ int git_attr_fnmatch__parse(
}
if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) {
spec->flags = spec->flags |
GIT_ATTR_FNMATCH_NEGATIVE | GIT_ATTR_FNMATCH_LEADINGDIR;
spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE;
if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0)
spec->flags |= GIT_ATTR_FNMATCH_LEADINGDIR;
pattern++;
}
......
......@@ -419,7 +419,7 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj)
return -1;
/* Some tools create multiple author fields, ignore the extra ones */
while ((size_t)(buffer_end - buffer) >= strlen("author ") && !git__prefixcmp(buffer, "author ")) {
while (!git__prefixncmp(buffer, buffer_end - buffer, "author ")) {
if (git_signature__parse(&dummy_sig, &buffer, buffer_end, "author ", '\n') < 0)
return -1;
......
......@@ -109,7 +109,7 @@ int git_config_add_file_ondisk(
assert(cfg && path);
res = p_stat(path, &st);
if (res < 0 && errno != ENOENT) {
if (res < 0 && errno != ENOENT && errno != ENOTDIR) {
giterr_set(GITERR_CONFIG, "failed to stat '%s'", path);
return -1;
}
......@@ -513,6 +513,8 @@ int git_config_backend_foreach_match(
regex_t regex;
int error = 0;
assert(backend && cb);
if (regexp != NULL) {
if ((error = p_regcomp(&regex, regexp, REG_EXTENDED)) != 0) {
giterr_set_regex(&regex, error);
......
......@@ -816,6 +816,8 @@ int git_diff_find_similar(
diff_find_match *best_match;
git_diff_file swap;
assert(diff);
if ((error = normalize_find_opts(diff, &opts, given_opts)) < 0)
return error;
......
......@@ -809,6 +809,7 @@ static int proxy_stream_close(git_writestream *s)
{
struct proxy_stream *proxy_stream = (struct proxy_stream *)s;
git_buf *writebuf;
git_error_state error_state = {0};
int error;
assert(proxy_stream);
......@@ -826,6 +827,11 @@ static int proxy_stream_close(git_writestream *s)
git_buf_sanitize(proxy_stream->output);
writebuf = proxy_stream->output;
} else {
/* close stream before erroring out taking care
* to preserve the original error */
giterr_state_capture(&error_state, error);
proxy_stream->target->close(proxy_stream->target);
giterr_state_restore(&error_state);
return error;
}
......
......@@ -133,23 +133,12 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match
continue;
}
/*
* When dealing with a directory, we add '/<star>' so
* p_fnmatch() honours FNM_PATHNAME. Checking for LEADINGDIR
* alone isn't enough as that's also set for nagations, so we
* need to check that NEGATIVE is off.
*/
git_buf_clear(&buf);
if (rule->containing_dir) {
if (rule->containing_dir)
git_buf_puts(&buf, rule->containing_dir);
}
error = git_buf_puts(&buf, rule->pattern);
git_buf_puts(&buf, rule->pattern);
if ((rule->flags & (GIT_ATTR_FNMATCH_LEADINGDIR | GIT_ATTR_FNMATCH_NEGATIVE)) == GIT_ATTR_FNMATCH_LEADINGDIR)
error = git_buf_PUTS(&buf, "/*");
if (error < 0)
if (git_buf_oom(&buf))
goto out;
if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) {
......@@ -203,7 +192,10 @@ static int parse_ignore_file(
break;
}
match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG;
match->flags =
GIT_ATTR_FNMATCH_ALLOWSPACE |
GIT_ATTR_FNMATCH_ALLOWNEG |
GIT_ATTR_FNMATCH_NOLEADINGDIR;
if (!(error = git_attr_fnmatch__parse(
match, &attrs->pool, context, &scan)))
......@@ -445,6 +437,9 @@ static bool ignore_lookup_in_rules(
git_attr_fnmatch *match;
git_vector_rforeach(&file->rules, j, match) {
if (match->flags & GIT_ATTR_FNMATCH_DIRECTORY &&
path->is_dir == GIT_DIR_FLAG_FALSE)
continue;
if (git_attr_fnmatch__match(match, path)) {
*ignored = ((match->flags & GIT_ATTR_FNMATCH_NEGATIVE) == 0) ?
GIT_IGNORE_TRUE : GIT_IGNORE_FALSE;
......
......@@ -1028,11 +1028,15 @@ static int loose_backend__readstream(
done:
if (error < 0) {
git_futils_mmap_free(&stream->map);
git_zstream_free(&stream->zstream);
git_hash_ctx_cleanup(hash_ctx);
git__free(hash_ctx);
git__free(stream);
if (stream) {
git_futils_mmap_free(&stream->map);
git_zstream_free(&stream->zstream);
git__free(stream);
}
if (hash_ctx) {
git_hash_ctx_cleanup(hash_ctx);
git__free(hash_ctx);
}
}
git_buf_free(&object_path);
......
......@@ -458,26 +458,6 @@ done:
return error;
}
static int parse_number(git_off_t *out, git_patch_parse_ctx *ctx)
{
const char *end;
int64_t num;
if (!git__isdigit(ctx->parse_ctx.line[0]))
return -1;
if (git__strntol64(&num, ctx->parse_ctx.line, ctx->parse_ctx.line_len, &end, 10) < 0)
return -1;
if (num < 0)
return -1;
*out = num;
git_parse_advance_chars(&ctx->parse_ctx, (end - ctx->parse_ctx.line));
return 0;
}
static int parse_int(int *out, git_patch_parse_ctx *ctx)
{
git_off_t num;
......
......@@ -709,6 +709,8 @@ int git_reference_rename(
git_signature *who;
int error;
assert(out && ref);
if ((error = git_reference__log_signature(&who, ref->db->repo)) < 0)
return error;
......@@ -1337,7 +1339,7 @@ int git_reference_is_note(const git_reference *ref)
return git_reference__is_note(ref->name);
}
static int peel_error(int error, git_reference *ref, const char* msg)
static int peel_error(int error, const git_reference *ref, const char* msg)
{
giterr_set(
GITERR_INVALID,
......@@ -1347,10 +1349,11 @@ static int peel_error(int error, git_reference *ref, const char* msg)
int git_reference_peel(
git_object **peeled,
git_reference *ref,
const git_reference *ref,
git_otype target_type)
{
git_reference *resolved = NULL;
const git_reference *resolved = NULL;
git_reference *allocated = NULL;
git_object *target = NULL;
int error;
......@@ -1359,8 +1362,10 @@ int git_reference_peel(
if (ref->type == GIT_REF_OID) {
resolved = ref;
} else {
if ((error = git_reference_resolve(&resolved, ref)) < 0)
if ((error = git_reference_resolve(&allocated, ref)) < 0)
return peel_error(error, ref, "Cannot resolve reference");
resolved = allocated;
}
/*
......@@ -1389,9 +1394,7 @@ int git_reference_peel(
cleanup:
git_object_free(target);
if (resolved != ref)
git_reference_free(resolved);
git_reference_free(allocated);
return error;
}
......
......@@ -248,7 +248,7 @@ int git_signature__parse(git_signature *sig, const char **buffer_out,
if ((tz_start[0] != '-' && tz_start[0] != '+') ||
git__strntol32(&offset, tz_start + 1,
buffer_end - tz_start + 1, &tz_end, 10) < 0) {
buffer_end - tz_start - 1, &tz_end, 10) < 0) {
/* malformed timezone, just assume it's zero */
offset = 0;
}
......
......@@ -181,7 +181,7 @@ static int load_submodule_names(git_strmap *out, git_repository *repo, git_confi
if ((error = git_config_iterator_glob_new(&iter, cfg, key)) < 0)
return error;
while (git_config_next(&entry, iter) == 0) {
while ((error = git_config_next(&entry, iter)) == 0) {
const char *fdot, *ldot;
fdot = strchr(entry->name, '.');
ldot = strrchr(entry->name, '.');
......
......@@ -399,21 +399,21 @@ static int tree_error(const char *str, const char *path)
return -1;
}
static int parse_mode(unsigned int *modep, const char *buffer, const char **buffer_out)
static int parse_mode(uint16_t *mode_out, const char *buffer, size_t buffer_len, const char **buffer_out)
{
unsigned char c;
unsigned int mode = 0;
int32_t mode;
int error;
if (*buffer == ' ')
if (!buffer_len || git__isspace(*buffer))
return -1;
while ((c = *buffer++) != ' ') {
if (c < '0' || c > '7')
return -1;
mode = (mode << 3) + (c - '0');
}
*modep = mode;
*buffer_out = buffer;
if ((error = git__strntol32(&mode, buffer, buffer_len, buffer_out, 8)) < 0)
return error;
if (mode < 0 || mode > UINT16_MAX)
return -1;
*mode_out = mode;
return 0;
}
......@@ -437,11 +437,14 @@ int git_tree__parse(void *_tree, git_odb_object *odb_obj)
git_tree_entry *entry;
size_t filename_len;
const char *nul;
unsigned int attr;
uint16_t attr;
if (parse_mode(&attr, buffer, &buffer) < 0 || !buffer)
if (parse_mode(&attr, buffer, buffer_end - buffer, &buffer) < 0 || !buffer)
return tree_error("failed to parse tree: can't parse filemode", NULL);
if (buffer >= buffer_end || (*buffer++) != ' ')
return tree_error("failed to parse tree: missing space after filemode", NULL);
if ((nul = memchr(buffer, 0, buffer_end - buffer)) == NULL)
return tree_error("failed to parse tree: object is corrupted", NULL);
......
......@@ -83,36 +83,56 @@ int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const cha
/*
* White space
*/
while (git__isspace(*p))
p++;
while (nptr_len && git__isspace(*p))
p++, nptr_len--;
if (!nptr_len)
goto Return;
/*
* Sign
*/
if (*p == '-' || *p == '+')
if (*p++ == '-')
if (*p == '-' || *p == '+') {
if (*p == '-')
neg = 1;
p++;
nptr_len--;
}
if (!nptr_len)
goto Return;
/*
* Base
* Automatically detect the base if none was given to us.
* Right now, we assume that a number starting with '0x'
* is hexadecimal and a number starting with '0' is
* octal.
*/
if (base == 0) {
if (*p != '0')
base = 10;
else {
else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X'))
base = 16;
else
base = 8;
if (p[1] == 'x' || p[1] == 'X') {
p += 2;
base = 16;
}
}
} else if (base == 16 && *p == '0') {
if (p[1] == 'x' || p[1] == 'X')
p += 2;
} else if (base < 0 || 36 < base)
}
if (base < 0 || 36 < base)
goto Return;
/*
* Skip prefix of '0x'-prefixed hexadecimal numbers. There is no
* need to do the same for '0'-prefixed octal numbers as a
* leading '0' does not have any impact. Also, if we skip a
* leading '0' in such a string, then we may end up with no
* digits left and produce an error later on which isn't one.
*/
if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
nptr_len -= 2;
}
/*
* Non-empty sequence of digits
*/
for (; nptr_len > 0; p++,ndig++,nptr_len--) {
......
......@@ -43,7 +43,8 @@ static int apply_patchfile(
if (error == 0) {
cl_assert_equal_i(new_len, result.size);
cl_assert(memcmp(new, result.ptr, new_len) == 0);
if (new_len)
cl_assert(memcmp(new, result.ptr, new_len) == 0);
cl_assert_equal_s(filename_expected, filename);
cl_assert_equal_i(mode_expected, mode);
......
......@@ -43,6 +43,26 @@ void test_commit_signature__leading_and_trailing_crud_is_trimmed(void)
assert_name_and_email("nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com", "nulltoken \xe2\x98\xba", "emeric.fermas@gmail.com");
}
void test_commit_signature__timezone_does_not_read_oob(void)
{
const char *header = "A <a@example.com> 1461698487 +1234", *header_end;
git_signature *sig;
/* Let the buffer end midway between the timezone offeset's "+12" and "34" */
header_end = header + strlen(header) - 2;
sig = git__calloc(1, sizeof(git_signature));
cl_assert(sig);
cl_git_pass(git_signature__parse(sig, &header, header_end, NULL, '\0'));
cl_assert_equal_s(sig->name, "A");
cl_assert_equal_s(sig->email, "a@example.com");
cl_assert_equal_i(sig->when.time, 1461698487);
cl_assert_equal_i(sig->when.offset, 12);
git_signature_free(sig);
}
void test_commit_signature__angle_brackets_in_names_are_not_supported(void)
{
cl_git_fail(try_build_signature("<Phil Haack", "phil@haack", 1234567890, 60));
......
......@@ -524,6 +524,26 @@ void test_config_read__fallback_from_local_to_global_and_from_global_to_system(v
git_config_free(cfg);
}
void test_config_read__parent_dir_is_file(void)
{
git_config *cfg;
int count;
cl_git_pass(git_config_new(&cfg));
/*
* Verify we can add non-existing files when the parent directory is not
* a directory.
*/
cl_git_pass(git_config_add_file_ondisk(cfg, "/dev/null/.gitconfig",
GIT_CONFIG_LEVEL_SYSTEM, NULL, 0));
count = 0;
cl_git_pass(git_config_foreach(cfg, count_cfg_entries_and_compare_levels, &count));
cl_assert_equal_i(0, count);
git_config_free(cfg);
}
/*
* At the beginning of the test, config18 has:
* int32global = 28
......
......@@ -64,6 +64,28 @@ void test_core_strtol__int64(void)
assert_l64_fails("-0x8000000000000001", 16);
}
void test_core_strtol__base_autodetection(void)
{
assert_l64_parses("0", 0, 0);
assert_l64_parses("00", 0, 0);
assert_l64_parses("0x", 0, 0);
assert_l64_parses("0foobar", 0, 0);
assert_l64_parses("07", 7, 0);
assert_l64_parses("017", 15, 0);
assert_l64_parses("0x8", 8, 0);
assert_l64_parses("0x18", 24, 0);
}
void test_core_strtol__buffer_length_with_autodetection_truncates(void)
{
int64_t i64;
cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0));
cl_assert_equal_i(i64, 1);
cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0));
cl_assert_equal_i(i64, 1);
}
void test_core_strtol__buffer_length_truncates(void)
{
int32_t i32;
......@@ -76,6 +98,26 @@ void test_core_strtol__buffer_length_truncates(void)
cl_assert_equal_i(i64, 1);
}
void test_core_strtol__buffer_length_with_leading_ws_truncates(void)
{
int64_t i64;
cl_git_fail(git__strntol64(&i64, " 1", 1, NULL, 10));
cl_git_pass(git__strntol64(&i64, " 11", 2, NULL, 10));
cl_assert_equal_i(i64, 1);
}
void test_core_strtol__buffer_length_with_leading_sign_truncates(void)
{
int64_t i64;
cl_git_fail(git__strntol64(&i64, "-1", 1, NULL, 10));
cl_git_pass(git__strntol64(&i64, "-11", 2, NULL, 10));
cl_assert_equal_i(i64, -1);
}
void test_core_strtol__error_message_cuts_off(void)
{
assert_l32_fails("2147483657foobar", 10);
......
#include "clar_libgit2.h"
static git_repository *g_repo;
void test_merge_annotated_commit__initialize(void)
{
g_repo = cl_git_sandbox_init("testrepo");
}
void test_merge_annotated_commit__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_merge_annotated_commit__lookup_annotated_tag(void)
{
git_annotated_commit *commit;
git_reference *ref;
cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/tags/test"));
cl_git_pass(git_annotated_commit_from_ref(&commit, g_repo, ref));
git_annotated_commit_free(commit);
git_reference_free(ref);
}
......@@ -1158,27 +1158,58 @@ void test_status_ignore__subdir_ignore_everything_except_certain_files(void)
void test_status_ignore__deeper(void)
{
int ignored;
const char *test_files[] = {
"empty_standard_repo/foo.data",
"empty_standard_repo/bar.data",
"empty_standard_repo/dont_ignore/foo.data",
"empty_standard_repo/dont_ignore/bar.data",
NULL
};
g_repo = cl_git_sandbox_init("empty_standard_repo");
make_test_data("empty_standard_repo", test_files);
cl_git_mkfile("empty_standard_repo/.gitignore",
"*.data\n"
"!dont_ignore/*.data\n");
cl_git_mkfile("empty_standard_repo/.gitignore",
"*.data\n"
"!dont_ignore/*.data\n");
assert_is_ignored("foo.data");
assert_is_ignored("bar.data");
cl_git_pass(p_mkdir("empty_standard_repo/dont_ignore", 0777));
cl_git_mkfile("empty_standard_repo/foo.data", "");
cl_git_mkfile("empty_standard_repo/bar.data", "");
cl_git_mkfile("empty_standard_repo/dont_ignore/foo.data", "");
cl_git_mkfile("empty_standard_repo/dont_ignore/bar.data", "");
refute_is_ignored("dont_ignore/foo.data");
refute_is_ignored("dont_ignore/bar.data");
}
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "foo.data"));
cl_assert_equal_i(1, ignored);
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "bar.data"));
cl_assert_equal_i(1, ignored);
void test_status_ignore__unignored_dir_with_ignored_contents(void)
{
static const char *test_files[] = {
"empty_standard_repo/dir/a.test",
"empty_standard_repo/dir/subdir/a.test",
NULL
};
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/foo.data"));
cl_assert_equal_i(0, ignored);
cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "dont_ignore/bar.data"));
cl_assert_equal_i(0, ignored);
make_test_data("empty_standard_repo", test_files);
cl_git_mkfile(
"empty_standard_repo/.gitignore",
"*.test\n"
"!dir/*\n");
refute_is_ignored("dir/a.test");
assert_is_ignored("dir/subdir/a.test");
}
void test_status_ignore__unignored_subdirs(void)
{
static const char *test_files[] = {
"empty_standard_repo/dir/a.test",
"empty_standard_repo/dir/subdir/a.test",
NULL
};
make_test_data("empty_standard_repo", test_files);
cl_git_mkfile(
"empty_standard_repo/.gitignore",
"dir/*\n"
"!dir/*/\n");
assert_is_ignored("dir/a.test");
refute_is_ignored("dir/subdir/a.test");
}
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