diff options
author | Vicent Marti <tanoku@gmail.com> | 2013-07-10 21:06:53 +0200 |
---|---|---|
committer | Vicent Marti <tanoku@gmail.com> | 2013-07-10 21:06:53 +0200 |
commit | c0e529f379ded1311c7d8caba4af7c20c540c987 (patch) | |
tree | 2c2594c00603a6032d8bbdecec458387475d8ea5 /src | |
parent | bf3ee3cf3130d50cb1813c3ec31993a96dae0607 (diff) | |
parent | 406dd556e20117b3cc2e5c53410d65314fd14056 (diff) | |
download | libgit2-c0e529f379ded1311c7d8caba4af7c20c540c987.tar.gz |
Merge branch 'arrbee/examples-log' into development
Diffstat (limited to 'src')
-rw-r--r-- | src/array.h | 5 | ||||
-rw-r--r-- | src/bitvec.h | 75 | ||||
-rw-r--r-- | src/checkout.c | 10 | ||||
-rw-r--r-- | src/commit.c | 74 | ||||
-rw-r--r-- | src/commit.h | 5 | ||||
-rw-r--r-- | src/diff.c | 22 | ||||
-rw-r--r-- | src/diff.h | 2 | ||||
-rw-r--r-- | src/index.c | 42 | ||||
-rw-r--r-- | src/index.h | 6 | ||||
-rw-r--r-- | src/pathspec.c | 621 | ||||
-rw-r--r-- | src/pathspec.h | 51 |
11 files changed, 783 insertions, 130 deletions
diff --git a/src/array.h b/src/array.h index 2d77c71a0..248010425 100644 --- a/src/array.h +++ b/src/array.h @@ -30,6 +30,9 @@ #define git_array_init(a) \ do { (a).size = (a).asize = 0; (a).ptr = NULL; } while (0) +#define git_array_init_to_size(a, desired) \ + do { (a).size = 0; (a).asize = desired; (a).ptr = git__calloc(desired, sizeof(*(a).ptr)); } while (0) + #define git_array_clear(a) \ do { git__free((a).ptr); git_array_init(a); } while (0) @@ -63,4 +66,6 @@ GIT_INLINE(void *) git_array_grow(git_array_generic_t *a, size_t item_size) #define git_array_size(a) (a).size +#define git_array_valid_index(a, i) ((i) < (a).size) + #endif diff --git a/src/bitvec.h b/src/bitvec.h new file mode 100644 index 000000000..fd6f0ccf8 --- /dev/null +++ b/src/bitvec.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_bitvec_h__ +#define INCLUDE_bitvec_h__ + +#include "util.h" + +/* + * This is a silly little fixed length bit vector type that will store + * vectors of 64 bits or less directly in the structure and allocate + * memory for vectors longer than 64 bits. You can use the two versions + * transparently through the API and avoid heap allocation completely when + * using a short bit vector as a result. + */ +typedef struct { + size_t length; + union { + uint64_t *words; + uint64_t bits; + } u; +} git_bitvec; + +GIT_INLINE(int) git_bitvec_init(git_bitvec *bv, size_t capacity) +{ + memset(bv, 0x0, sizeof(*bv)); + + if (capacity >= 64) { + bv->length = (capacity / 64) + 1; + bv->u.words = git__calloc(bv->length, sizeof(uint64_t)); + if (!bv->u.words) + return -1; + } + + return 0; +} + +#define GIT_BITVEC_MASK(BIT) ((uint64_t)1 << (BIT % 64)) +#define GIT_BITVEC_WORD(BV, BIT) (BV->length ? &BV->u.words[BIT / 64] : &BV->u.bits) + +GIT_INLINE(void) git_bitvec_set(git_bitvec *bv, size_t bit, bool on) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + uint64_t mask = GIT_BITVEC_MASK(bit); + + if (on) + *word |= mask; + else + *word &= ~mask; +} + +GIT_INLINE(bool) git_bitvec_get(git_bitvec *bv, size_t bit) +{ + uint64_t *word = GIT_BITVEC_WORD(bv, bit); + return (*word & GIT_BITVEC_MASK(bit)) != 0; +} + +GIT_INLINE(void) git_bitvec_clear(git_bitvec *bv) +{ + if (!bv->length) + bv->u.bits = 0; + else + memset(bv->u.words, 0x0, bv->length * sizeof(uint64_t)); +} + +GIT_INLINE(void) git_bitvec_free(git_bitvec *bv) +{ + if (bv->length) + git__free(bv->u.words); +} + +#endif diff --git a/src/checkout.c b/src/checkout.c index 8f9ec64e4..ec9da7e2e 100644 --- a/src/checkout.c +++ b/src/checkout.c @@ -246,10 +246,10 @@ static int checkout_action_wd_only( bool remove = false; git_checkout_notify_t notify = GIT_CHECKOUT_NOTIFY_NONE; - if (!git_pathspec_match_path( + if (!git_pathspec__match( pathspec, wd->path, (data->strategy & GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH) != 0, - git_iterator_ignore_case(workdir), NULL)) + git_iterator_ignore_case(workdir), NULL, NULL)) return 0; /* check if item is tracked in the index but not in the checkout diff */ @@ -607,7 +607,7 @@ static int checkout_get_actions( uint32_t *actions = NULL; if (data->opts.paths.count > 0 && - git_pathspec_init(&pathspec, &data->opts.paths, &pathpool) < 0) + git_pathspec__vinit(&pathspec, &data->opts.paths, &pathpool) < 0) return -1; if ((error = git_iterator_current(&wditem, workdir)) < 0 && @@ -659,7 +659,7 @@ static int checkout_get_actions( goto fail; } - git_pathspec_free(&pathspec); + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); return 0; @@ -670,7 +670,7 @@ fail: *actions_ptr = NULL; git__free(actions); - git_pathspec_free(&pathspec); + git_pathspec__vfree(&pathspec); git_pool_clear(&pathpool); return error; diff --git a/src/commit.c b/src/commit.c index 1ab9b34f7..cc912a7be 100644 --- a/src/commit.c +++ b/src/commit.c @@ -19,30 +19,19 @@ #include <stdarg.h> -static void clear_parents(git_commit *commit) -{ - size_t i; - - for (i = 0; i < commit->parent_ids.length; ++i) { - git_oid *parent = git_vector_get(&commit->parent_ids, i); - git__free(parent); - } - - git_vector_clear(&commit->parent_ids); -} - void git_commit__free(void *_commit) { git_commit *commit = _commit; - clear_parents(commit); - git_vector_free(&commit->parent_ids); + git_array_clear(commit->parent_ids); git_signature_free(commit->author); git_signature_free(commit->committer); + git__free(commit->raw_header); git__free(commit->message); git__free(commit->message_encoding); + git__free(commit); } @@ -171,12 +160,34 @@ int git_commit_create( int git_commit__parse(void *_commit, git_odb_object *odb_obj) { git_commit *commit = _commit; - const char *buffer = git_odb_object_data(odb_obj); - const char *buffer_end = buffer + git_odb_object_size(odb_obj); + const char *buffer_start = git_odb_object_data(odb_obj), *buffer; + const char *buffer_end = buffer_start + git_odb_object_size(odb_obj); git_oid parent_id; + size_t parent_count = 0, header_len; - if (git_vector_init(&commit->parent_ids, 4, NULL) < 0) - return -1; + /* find end-of-header (counting parents as we go) */ + for (buffer = buffer_start; buffer < buffer_end; ++buffer) { + if (!strncmp("\n\n", buffer, 2)) { + ++buffer; + break; + } + if (!strncmp("\nparent ", buffer, strlen("\nparent "))) + ++parent_count; + } + + header_len = buffer - buffer_start; + commit->raw_header = git__strndup(buffer_start, header_len); + GITERR_CHECK_ALLOC(commit->raw_header); + + /* point "buffer" to header data */ + buffer = commit->raw_header; + buffer_end = commit->raw_header + header_len; + + if (parent_count < 1) + parent_count = 1; + + git_array_init_to_size(commit->parent_ids, parent_count); + GITERR_CHECK_ARRAY(commit->parent_ids); if (git_oid__parse(&commit->tree_id, &buffer, buffer_end, "tree ") < 0) goto bad_buffer; @@ -186,13 +197,10 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) */ while (git_oid__parse(&parent_id, &buffer, buffer_end, "parent ") == 0) { - git_oid *new_id = git__malloc(sizeof(git_oid)); + git_oid *new_id = git_array_alloc(commit->parent_ids); GITERR_CHECK_ALLOC(new_id); git_oid_cpy(new_id, &parent_id); - - if (git_vector_insert(&commit->parent_ids, new_id) < 0) - return -1; } commit->author = git__malloc(sizeof(git_signature)); @@ -208,8 +216,8 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (git_signature__parse(commit->committer, &buffer, buffer_end, "committer ", '\n') < 0) return -1; - /* Parse add'l header entries until blank line found */ - while (buffer < buffer_end && *buffer != '\n') { + /* Parse add'l header entries */ + while (buffer < buffer_end) { const char *eoln = buffer; while (eoln < buffer_end && *eoln != '\n') ++eoln; @@ -223,15 +231,18 @@ int git_commit__parse(void *_commit, git_odb_object *odb_obj) if (eoln < buffer_end && *eoln == '\n') ++eoln; - buffer = eoln; } - /* buffer is now at the end of the header, double-check and move forward into the message */ - if (buffer < buffer_end && *buffer == '\n') - buffer++; + /* point "buffer" to data after header */ + buffer = git_odb_object_data(odb_obj); + buffer_end = buffer + git_odb_object_size(odb_obj); + + buffer += header_len; + if (*buffer == '\n') + ++buffer; - /* parse commit message */ + /* extract commit message */ if (buffer <= buffer_end) { commit->message = git__strndup(buffer, buffer_end - buffer); GITERR_CHECK_ALLOC(commit->message); @@ -255,9 +266,10 @@ GIT_COMMIT_GETTER(const git_signature *, author, commit->author) GIT_COMMIT_GETTER(const git_signature *, committer, commit->committer) GIT_COMMIT_GETTER(const char *, message, commit->message) GIT_COMMIT_GETTER(const char *, message_encoding, commit->message_encoding) +GIT_COMMIT_GETTER(const char *, raw_header, commit->raw_header) GIT_COMMIT_GETTER(git_time_t, time, commit->committer->when.time) GIT_COMMIT_GETTER(int, time_offset, commit->committer->when.offset) -GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)commit->parent_ids.length) +GIT_COMMIT_GETTER(unsigned int, parentcount, (unsigned int)git_array_size(commit->parent_ids)) GIT_COMMIT_GETTER(const git_oid *, tree_id, &commit->tree_id); int git_commit_tree(git_tree **tree_out, const git_commit *commit) @@ -271,7 +283,7 @@ const git_oid *git_commit_parent_id( { assert(commit); - return git_vector_get(&commit->parent_ids, n); + return git_array_get(commit->parent_ids, n); } int git_commit_parent( diff --git a/src/commit.h b/src/commit.h index d0981b125..22fc898a1 100644 --- a/src/commit.h +++ b/src/commit.h @@ -10,14 +10,14 @@ #include "git2/commit.h" #include "tree.h" #include "repository.h" -#include "vector.h" +#include "array.h" #include <time.h> struct git_commit { git_object object; - git_vector parent_ids; + git_array_t(git_oid) parent_ids; git_oid tree_id; git_signature *author; @@ -25,6 +25,7 @@ struct git_commit { char *message_encoding; char *message; + char *raw_header; }; void git_commit__free(void *commit); diff --git a/src/diff.c b/src/diff.c index 0980f412a..cc7be451f 100644 --- a/src/diff.c +++ b/src/diff.c @@ -81,11 +81,11 @@ static int diff_delta__from_one( DIFF_FLAG_IS_SET(diff, GIT_DIFF_IGNORE_SUBMODULES)) return 0; - if (!git_pathspec_match_path( + if (!git_pathspec__match( &diff->pathspec, entry->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), - &matched_pathspec)) + &matched_pathspec, NULL)) return 0; delta = diff_delta__alloc(diff, status, entry->path); @@ -247,6 +247,11 @@ GIT_INLINE(const char *) diff_delta__path(const git_diff_delta *delta) return str; } +const char *git_diff_delta__path(const git_diff_delta *delta) +{ + return diff_delta__path(delta); +} + int git_diff_delta__cmp(const void *a, const void *b) { const git_diff_delta *da = a, *db = b; @@ -387,7 +392,7 @@ static int diff_list_apply_options( DIFF_FLAG_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE, icase); /* initialize pathspec from options */ - if (git_pathspec_init(&diff->pathspec, &opts->pathspec, pool) < 0) + if (git_pathspec__vinit(&diff->pathspec, &opts->pathspec, pool) < 0) return -1; } @@ -473,7 +478,7 @@ static void diff_list_free(git_diff_list *diff) } git_vector_free(&diff->deltas); - git_pathspec_free(&diff->pathspec); + git_pathspec__vfree(&diff->pathspec); git_pool_clear(&diff->pool); git__memzero(diff, sizeof(*diff)); @@ -634,11 +639,11 @@ static int maybe_modified( bool new_is_workdir = (info->new_iter->type == GIT_ITERATOR_TYPE_WORKDIR); const char *matched_pathspec; - if (!git_pathspec_match_path( + if (!git_pathspec__match( &diff->pathspec, oitem->path, DIFF_FLAG_IS_SET(diff, GIT_DIFF_DISABLE_PATHSPEC_MATCH), DIFF_FLAG_IS_SET(diff, GIT_DIFF_DELTAS_ARE_ICASE), - &matched_pathspec)) + &matched_pathspec, NULL)) return 0; memset(&noid, 0, sizeof(noid)); @@ -1235,6 +1240,11 @@ size_t git_diff_num_deltas_of_type(git_diff_list *diff, git_delta_t type) return count; } +int git_diff_is_sorted_icase(const git_diff_list *diff) +{ + return (diff->opts.flags & GIT_DIFF_DELTAS_ARE_ICASE) != 0; +} + int git_diff__paired_foreach( git_diff_list *head2idx, git_diff_list *idx2wd, diff --git a/src/diff.h b/src/diff.h index 6ef03ee7c..d09a130bc 100644 --- a/src/diff.h +++ b/src/diff.h @@ -76,6 +76,8 @@ extern void git_diff_list_addref(git_diff_list *diff); extern int git_diff_delta__cmp(const void *a, const void *b); extern int git_diff_delta__casecmp(const void *a, const void *b); +extern const char *git_diff_delta__path(const git_diff_delta *delta); + extern bool git_diff_delta__should_skip( const git_diff_options *opts, const git_diff_delta *delta); diff --git a/src/index.c b/src/index.c index ffa84e53f..21efd2c63 100644 --- a/src/index.c +++ b/src/index.c @@ -101,8 +101,6 @@ static int parse_index(git_index *index, const char *buffer, size_t buffer_size) static bool is_index_extended(git_index *index); static int write_index(git_index *index, git_filebuf *file); -static int index_find(size_t *at_pos, git_index *index, const char *path, int stage); - static void index_entry_free(git_index_entry *entry); static void index_entry_reuc_free(git_index_reuc_entry *reuc); @@ -114,7 +112,7 @@ static int index_srch(const void *key, const void *array_member) ret = strcmp(srch_key->path, entry->path); - if (ret == 0) + if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY) ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); return ret; @@ -128,7 +126,7 @@ static int index_isrch(const void *key, const void *array_member) ret = strcasecmp(srch_key->path, entry->path); - if (ret == 0) + if (ret == 0 && srch_key->stage != GIT_INDEX_STAGE_ANY) ret = srch_key->stage - GIT_IDXENTRY_STAGE(entry); return ret; @@ -562,7 +560,7 @@ const git_index_entry *git_index_get_bypath( git_vector_sort(&index->entries); - if (index_find(&pos, index, path, stage) < 0) { + if (git_index__find(&pos, index, path, stage) < 0) { giterr_set(GITERR_INDEX, "Index does not contain %s", path); return NULL; } @@ -719,7 +717,8 @@ static int index_insert(git_index *index, git_index_entry *entry, int replace) entry->flags |= GIT_IDXENTRY_NAMEMASK; /* look if an entry with this path already exists */ - if (!index_find(&position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { + if (!git_index__find( + &position, index, entry->path, GIT_IDXENTRY_STAGE(entry))) { existing = (git_index_entry **)&index->entries.contents[position]; /* update filemode to existing values if stat is not trusted */ @@ -831,7 +830,7 @@ int git_index_remove(git_index *index, const char *path, int stage) git_vector_sort(&index->entries); - if (index_find(&position, index, path, stage) < 0) { + if (git_index__find(&position, index, path, stage) < 0) { giterr_set(GITERR_INDEX, "Index does not contain %s at stage %d", path, stage); return GIT_ENOTFOUND; @@ -887,7 +886,8 @@ int git_index_remove_directory(git_index *index, const char *dir, int stage) return error; } -static int index_find(size_t *at_pos, git_index *index, const char *path, int stage) +int git_index__find( + size_t *at_pos, git_index *index, const char *path, int stage) { struct entry_srch_key srch_key; @@ -896,7 +896,8 @@ static int index_find(size_t *at_pos, git_index *index, const char *path, int st srch_key.path = path; srch_key.stage = stage; - return git_vector_bsearch2(at_pos, &index->entries, index->entries_search, &srch_key); + return git_vector_bsearch2( + at_pos, &index->entries, index->entries_search, &srch_key); } int git_index_find(size_t *at_pos, git_index *index, const char *path) @@ -2053,7 +2054,7 @@ int git_index_add_all( git_iterator *wditer = NULL; const git_index_entry *wd = NULL; git_index_entry *entry; - git_pathspec_context ps; + git_pathspec ps; const char *match; size_t existing; bool no_fnmatch = (flags & GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH) != 0; @@ -2074,7 +2075,7 @@ int git_index_add_all( if (git_repository__cvar(&ignorecase, repo, GIT_CVAR_IGNORECASE) < 0) return -1; - if ((error = git_pathspec_context_init(&ps, paths)) < 0) + if ((error = git_pathspec__init(&ps, paths)) < 0) return error; /* optionally check that pathspec doesn't mention any ignored files */ @@ -2091,14 +2092,14 @@ int git_index_add_all( while (!(error = git_iterator_advance(&wd, wditer))) { /* check if path actually matches */ - if (!git_pathspec_match_path( - &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match)) + if (!git_pathspec__match( + &ps.pathspec, wd->path, no_fnmatch, ignorecase, &match, NULL)) continue; /* skip ignored items that are not already in the index */ if ((flags & GIT_INDEX_ADD_FORCE) == 0 && git_iterator_current_is_ignored(wditer) && - index_find(&existing, index, wd->path, 0) < 0) + git_index__find(&existing, index, wd->path, 0) < 0) continue; /* issue notification callback if requested */ @@ -2148,7 +2149,7 @@ int git_index_add_all( cleanup: git_iterator_free(wditer); - git_pathspec_context_free(&ps); + git_pathspec__clear(&ps); return error; } @@ -2168,13 +2169,13 @@ static int index_apply_to_all( { int error = 0; size_t i; - git_pathspec_context ps; + git_pathspec ps; const char *match; git_buf path = GIT_BUF_INIT; assert(index); - if ((error = git_pathspec_context_init(&ps, paths)) < 0) + if ((error = git_pathspec__init(&ps, paths)) < 0) return error; git_vector_sort(&index->entries); @@ -2183,8 +2184,9 @@ static int index_apply_to_all( git_index_entry *entry = git_vector_get(&index->entries, i); /* check if path actually matches */ - if (!git_pathspec_match_path( - &ps.pathspec, entry->path, false, index->ignore_case, &match)) + if (!git_pathspec__match( + &ps.pathspec, entry->path, false, index->ignore_case, + &match, NULL)) continue; /* issue notification callback if requested */ @@ -2231,7 +2233,7 @@ static int index_apply_to_all( } git_buf_free(&path); - git_pathspec_context_free(&ps); + git_pathspec__clear(&ps); return error; } diff --git a/src/index.h b/src/index.h index a59107a7b..40577e105 100644 --- a/src/index.h +++ b/src/index.h @@ -47,13 +47,17 @@ struct git_index_conflict_iterator { size_t cur; }; -extern void git_index_entry__init_from_stat(git_index_entry *entry, struct stat *st); +extern void git_index_entry__init_from_stat( + git_index_entry *entry, struct stat *st); extern size_t git_index__prefix_position(git_index *index, const char *path); extern int git_index_entry__cmp(const void *a, const void *b); extern int git_index_entry__cmp_icase(const void *a, const void *b); +extern int git_index__find( + size_t *at_pos, git_index *index, const char *path, int stage); + extern void git_index__set_ignore_case(git_index *index, bool ignore_case); #endif diff --git a/src/pathspec.c b/src/pathspec.c index f029836d0..625726e0b 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -5,9 +5,16 @@ * a Linking Exception. For full terms see the included COPYING file. */ +#include "git2/pathspec.h" +#include "git2/diff.h" #include "pathspec.h" #include "buf_text.h" #include "attr_file.h" +#include "iterator.h" +#include "repository.h" +#include "index.h" +#include "bitvec.h" +#include "diff.h" /* what is the common non-wildcard prefix for all items in the pathspec */ char *git_pathspec_prefix(const git_strarray *pathspec) @@ -56,7 +63,7 @@ bool git_pathspec_is_empty(const git_strarray *pathspec) } /* build a vector of fnmatch patterns to evaluate efficiently */ -int git_pathspec_init( +int git_pathspec__vinit( git_vector *vspec, const git_strarray *strspec, git_pool *strpool) { size_t i; @@ -93,7 +100,7 @@ int git_pathspec_init( } /* free data from the pathspec vector */ -void git_pathspec_free(git_vector *vspec) +void git_pathspec__vfree(git_vector *vspec) { git_attr_fnmatch *match; unsigned int i; @@ -106,88 +113,602 @@ void git_pathspec_free(git_vector *vspec) git_vector_free(vspec); } +struct pathspec_match_context { + int fnmatch_flags; + int (*strcomp)(const char *, const char *); + int (*strncomp)(const char *, const char *, size_t); +}; + +static void pathspec_match_context_init( + struct pathspec_match_context *ctxt, + bool disable_fnmatch, + bool casefold) +{ + if (disable_fnmatch) + ctxt->fnmatch_flags = -1; + else if (casefold) + ctxt->fnmatch_flags = FNM_CASEFOLD; + else + ctxt->fnmatch_flags = 0; + + if (casefold) { + ctxt->strcomp = git__strcasecmp; + ctxt->strncomp = git__strncasecmp; + } else { + ctxt->strcomp = git__strcmp; + ctxt->strncomp = git__strncmp; + } +} + +static int pathspec_match_one( + const git_attr_fnmatch *match, + struct pathspec_match_context *ctxt, + const char *path) +{ + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + + if (result == FNM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0; + + if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH) + result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags); + + /* if we didn't match, look for exact dirname prefix match */ + if (result == FNM_NOMATCH && + (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && + ctxt->strncomp(path, match->pattern, match->length) == 0 && + path[match->length] == '/') + result = 0; + + if (result == 0) + return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? 0 : 1; + return -1; +} + +static int git_pathspec__match_at( + size_t *matched_at, + const git_vector *vspec, + struct pathspec_match_context *ctxt, + const char *path0, + const char *path1) +{ + int result = GIT_ENOTFOUND; + size_t i = 0; + const git_attr_fnmatch *match; + + git_vector_foreach(vspec, i, match) { + if (path0 && (result = pathspec_match_one(match, ctxt, path0)) >= 0) + break; + if (path1 && (result = pathspec_match_one(match, ctxt, path1)) >= 0) + break; + } + + *matched_at = i; + return result; +} + /* match a path against the vectorized pathspec */ -bool git_pathspec_match_path( - git_vector *vspec, +bool git_pathspec__match( + const git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold, - const char **matched_pathspec) + const char **matched_pathspec, + size_t *matched_at) { - size_t i; - git_attr_fnmatch *match; - int fnmatch_flags = 0; - int (*use_strcmp)(const char *, const char *); - int (*use_strncmp)(const char *, const char *, size_t); + int result; + size_t pos; + struct pathspec_match_context ctxt; if (matched_pathspec) *matched_pathspec = NULL; + if (matched_at) + *matched_at = GIT_PATHSPEC_NOMATCH; if (!vspec || !vspec->length) return true; - if (disable_fnmatch) - fnmatch_flags = -1; - else if (casefold) - fnmatch_flags = FNM_CASEFOLD; + pathspec_match_context_init(&ctxt, disable_fnmatch, casefold); - if (casefold) { - use_strcmp = git__strcasecmp; - use_strncmp = git__strncasecmp; - } else { - use_strcmp = git__strcmp; - use_strncmp = git__strncmp; + result = git_pathspec__match_at(&pos, vspec, &ctxt, path, NULL); + if (result >= 0) { + if (matched_pathspec) { + const git_attr_fnmatch *match = git_vector_get(vspec, pos); + *matched_pathspec = match->pattern; + } + + if (matched_at) + *matched_at = pos; } - git_vector_foreach(vspec, i, match) { - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + return (result > 0); +} + + +int git_pathspec__init(git_pathspec *ps, const git_strarray *paths) +{ + int error = 0; + + memset(ps, 0, sizeof(*ps)); + + ps->prefix = git_pathspec_prefix(paths); + + if ((error = git_pool_init(&ps->pool, 1, 0)) < 0 || + (error = git_pathspec__vinit(&ps->pathspec, paths, &ps->pool)) < 0) + git_pathspec__clear(ps); + + return error; +} + +void git_pathspec__clear(git_pathspec *ps) +{ + git__free(ps->prefix); + git_pathspec__vfree(&ps->pathspec); + git_pool_clear(&ps->pool); + memset(ps, 0, sizeof(*ps)); +} + +int git_pathspec_new(git_pathspec **out, const git_strarray *pathspec) +{ + int error = 0; + git_pathspec *ps = git__malloc(sizeof(git_pathspec)); + GITERR_CHECK_ALLOC(ps); + + if ((error = git_pathspec__init(ps, pathspec)) < 0) { + git__free(ps); + return error; + } + + GIT_REFCOUNT_INC(ps); + *out = ps; + return 0; +} + +static void pathspec_free(git_pathspec *ps) +{ + git_pathspec__clear(ps); + git__free(ps); +} + +void git_pathspec_free(git_pathspec *ps) +{ + if (!ps) + return; + GIT_REFCOUNT_DEC(ps, pathspec_free); +} + +int git_pathspec_matches_path( + const git_pathspec *ps, uint32_t flags, const char *path) +{ + bool no_fnmatch = (flags & GIT_PATHSPEC_NO_GLOB) != 0; + bool casefold = (flags & GIT_PATHSPEC_IGNORE_CASE) != 0; + + assert(ps && path); + + return (0 != git_pathspec__match( + &ps->pathspec, path, no_fnmatch, casefold, NULL, NULL)); +} + +static void pathspec_match_free(git_pathspec_match_list *m) +{ + git_pathspec_free(m->pathspec); + m->pathspec = NULL; + + git_array_clear(m->matches); + git_array_clear(m->failures); + git_pool_clear(&m->pool); + git__free(m); +} + +static git_pathspec_match_list *pathspec_match_alloc( + git_pathspec *ps, int datatype) +{ + git_pathspec_match_list *m = git__calloc(1, sizeof(git_pathspec_match_list)); + + if (m != NULL && git_pool_init(&m->pool, 1, 0) < 0) { + pathspec_match_free(m); + m = NULL; + } + + /* need to keep reference to pathspec and increment refcount because + * failures array stores pointers to the pattern strings of the + * pathspec that had no matches + */ + GIT_REFCOUNT_INC(ps); + m->pathspec = ps; + m->datatype = datatype; + + return m; +} + +GIT_INLINE(size_t) pathspec_mark_pattern(git_bitvec *used, size_t pos) +{ + if (!git_bitvec_get(used, pos)) { + git_bitvec_set(used, pos, true); + return 1; + } + + return 0; +} + +static size_t pathspec_mark_remaining( + git_bitvec *used, + git_vector *patterns, + struct pathspec_match_context *ctxt, + size_t start, + const char *path0, + const char *path1) +{ + size_t count = 0; + + if (path1 == path0) + path1 = NULL; + + for (; start < patterns->length; ++start) { + const git_attr_fnmatch *pat = git_vector_get(patterns, start); + + if (git_bitvec_get(used, start)) + continue; + + if (path0 && pathspec_match_one(pat, ctxt, path0) > 0) + count += pathspec_mark_pattern(used, start); + else if (path1 && pathspec_match_one(pat, ctxt, path1) > 0) + count += pathspec_mark_pattern(used, start); + } + + return count; +} + +static int pathspec_build_failure_array( + git_pathspec_string_array_t *failures, + git_vector *patterns, + git_bitvec *used, + git_pool *pool) +{ + size_t pos; + char **failed; + const git_attr_fnmatch *pat; + + for (pos = 0; pos < patterns->length; ++pos) { + if (git_bitvec_get(used, pos)) + continue; + + if ((failed = git_array_alloc(*failures)) == NULL) + return -1; + + pat = git_vector_get(patterns, pos); + + if ((*failed = git_pool_strdup(pool, pat->pattern)) == NULL) + return -1; + } + + return 0; +} + +static int pathspec_match_from_iterator( + git_pathspec_match_list **out, + git_iterator *iter, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + const git_index_entry *entry = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t pos, used_ct = 0, found_files = 0; + git_index *index = NULL; + git_bitvec used_patterns; + char **file; + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_STRINGS); + GITERR_CHECK_ALLOC(m); + } + + if ((error = git_iterator_reset(iter, ps->prefix, ps->prefix)) < 0) + goto done; - if (result == FNM_NOMATCH) - result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0; + if (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR && + (error = git_repository_index__weakptr( + &index, git_iterator_owner(iter))) < 0) + goto done; - if (fnmatch_flags >= 0 && result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, fnmatch_flags); + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_iterator_ignore_case(iter)); - /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && - (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && - use_strncmp(path, match->pattern, match->length) == 0 && - path[match->length] == '/') - result = 0; + while (!(error = git_iterator_advance(&entry, iter))) { + /* search for match with entry->path */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, entry->path, NULL); - if (result == 0) { - if (matched_pathspec) - *matched_pathspec = match->pattern; + /* no matches for this path */ + if (result < 0) + continue; - return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true; + /* if result was a negative pattern match, then don't list file */ + if (!result) { + used_ct += pathspec_mark_pattern(&used_patterns, pos); + continue; } + + /* check if path is ignored and untracked */ + if (index != NULL && + git_iterator_current_is_ignored(iter) && + git_index__find(NULL, index, entry->path, GIT_INDEX_STAGE_ANY) < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + ++found_files; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, entry->path, NULL); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched path into matches array */ + if ((file = (char **)git_array_alloc(m->matches)) == NULL || + (*file = git_pool_strdup(&m->pool, entry->path)) == NULL) { + error = -1; + goto done; + } + } + + if (error < 0 && error != GIT_ITEROVER) + goto done; + error = 0; + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_files) { + giterr_set(GITERR_INVALID, "No matching files were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; } - return false; + return error; } +static git_iterator_flag_t pathspec_match_iter_flags(uint32_t flags) +{ + git_iterator_flag_t f = 0; + + if ((flags & GIT_PATHSPEC_IGNORE_CASE) != 0) + f |= GIT_ITERATOR_IGNORE_CASE; + else if ((flags & GIT_PATHSPEC_USE_CASE) != 0) + f |= GIT_ITERATOR_DONT_IGNORE_CASE; -int git_pathspec_context_init( - git_pathspec_context *ctxt, const git_strarray *paths) + return f; +} + +int git_pathspec_match_workdir( + git_pathspec_match_list **out, + git_repository *repo, + uint32_t flags, + git_pathspec *ps) { int error = 0; + git_iterator *iter; + + assert(repo); - memset(ctxt, 0, sizeof(*ctxt)); + if (!(error = git_iterator_for_workdir( + &iter, repo, pathspec_match_iter_flags(flags), NULL, NULL))) { - ctxt->prefix = git_pathspec_prefix(paths); + error = pathspec_match_from_iterator(out, iter, flags, ps); - if ((error = git_pool_init(&ctxt->pool, 1, 0)) < 0 || - (error = git_pathspec_init(&ctxt->pathspec, paths, &ctxt->pool)) < 0) - git_pathspec_context_free(ctxt); + git_iterator_free(iter); + } return error; } -void git_pathspec_context_free( - git_pathspec_context *ctxt) +int git_pathspec_match_index( + git_pathspec_match_list **out, + git_index *index, + uint32_t flags, + git_pathspec *ps) { - git__free(ctxt->prefix); - git_pathspec_free(&ctxt->pathspec); - git_pool_clear(&ctxt->pool); - memset(ctxt, 0, sizeof(*ctxt)); + int error = 0; + git_iterator *iter; + + assert(index); + + if (!(error = git_iterator_for_index( + &iter, index, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; } + +int git_pathspec_match_tree( + git_pathspec_match_list **out, + git_tree *tree, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_iterator *iter; + + assert(tree); + + if (!(error = git_iterator_for_tree( + &iter, tree, pathspec_match_iter_flags(flags), NULL, NULL))) { + + error = pathspec_match_from_iterator(out, iter, flags, ps); + + git_iterator_free(iter); + } + + return error; +} + +int git_pathspec_match_diff( + git_pathspec_match_list **out, + git_diff_list *diff, + uint32_t flags, + git_pathspec *ps) +{ + int error = 0; + git_pathspec_match_list *m = NULL; + struct pathspec_match_context ctxt; + git_vector *patterns = &ps->pathspec; + bool find_failures = out && (flags & GIT_PATHSPEC_FIND_FAILURES) != 0; + bool failures_only = !out || (flags & GIT_PATHSPEC_FAILURES_ONLY) != 0; + size_t i, pos, used_ct = 0, found_deltas = 0; + const git_diff_delta *delta, **match; + git_bitvec used_patterns; + + assert(diff); + + if (git_bitvec_init(&used_patterns, patterns->length) < 0) + return -1; + + if (out) { + *out = m = pathspec_match_alloc(ps, PATHSPEC_DATATYPE_DIFF); + GITERR_CHECK_ALLOC(m); + } + + pathspec_match_context_init( + &ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0, + git_diff_is_sorted_icase(diff)); + + git_vector_foreach(&diff->deltas, i, delta) { + /* search for match with delta */ + int result = git_pathspec__match_at( + &pos, patterns, &ctxt, delta->old_file.path, delta->new_file.path); + + /* no matches for this path */ + if (result < 0) + continue; + + /* mark the matched pattern as used */ + used_ct += pathspec_mark_pattern(&used_patterns, pos); + + /* if result was a negative pattern match, then don't list file */ + if (!result) + continue; + + ++found_deltas; + + /* if find_failures is on, check if any later patterns also match */ + if (find_failures && used_ct < patterns->length) + used_ct += pathspec_mark_remaining( + &used_patterns, patterns, &ctxt, pos + 1, + delta->old_file.path, delta->new_file.path); + + /* if only looking at failures, exit early or just continue */ + if (failures_only || !out) { + if (used_ct == patterns->length) + break; + continue; + } + + /* insert matched delta into matches array */ + if (!(match = (const git_diff_delta **)git_array_alloc(m->matches))) { + error = -1; + goto done; + } else { + *match = delta; + } + } + + /* insert patterns that had no matches into failures array */ + if (find_failures && used_ct < patterns->length && + (error = pathspec_build_failure_array( + &m->failures, patterns, &used_patterns, &m->pool)) < 0) + goto done; + + /* if every pattern failed to match, then we have failed */ + if ((flags & GIT_PATHSPEC_NO_MATCH_ERROR) != 0 && !found_deltas) { + giterr_set(GITERR_INVALID, "No matching deltas were found"); + error = GIT_ENOTFOUND; + } + +done: + git_bitvec_free(&used_patterns); + + if (error < 0) { + pathspec_match_free(m); + if (out) *out = NULL; + } + + return error; +} + +void git_pathspec_match_list_free(git_pathspec_match_list *m) +{ + if (m) + pathspec_match_free(m); +} + +size_t git_pathspec_match_list_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->matches) : 0; +} + +const char *git_pathspec_match_list_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_STRINGS || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const char **)git_array_get(m->matches, pos)); +} + +const git_diff_delta *git_pathspec_match_list_diff_entry( + const git_pathspec_match_list *m, size_t pos) +{ + if (!m || m->datatype != PATHSPEC_DATATYPE_DIFF || + !git_array_valid_index(m->matches, pos)) + return NULL; + + return *((const git_diff_delta **)git_array_get(m->matches, pos)); +} + +size_t git_pathspec_match_list_failed_entrycount( + const git_pathspec_match_list *m) +{ + return m ? git_array_size(m->failures) : 0; +} + +const char * git_pathspec_match_list_failed_entry( + const git_pathspec_match_list *m, size_t pos) +{ + char **entry = m ? git_array_get(m->failures, pos) : NULL; + + return entry ? *entry : NULL; +} + diff --git a/src/pathspec.h b/src/pathspec.h index f6509df4c..40cd21c3f 100644 --- a/src/pathspec.h +++ b/src/pathspec.h @@ -8,9 +8,35 @@ #define INCLUDE_pathspec_h__ #include "common.h" +#include <git2/pathspec.h> #include "buffer.h" #include "vector.h" #include "pool.h" +#include "array.h" + +/* public compiled pathspec */ +struct git_pathspec { + git_refcount rc; + char *prefix; + git_vector pathspec; + git_pool pool; +}; + +enum { + PATHSPEC_DATATYPE_STRINGS = 0, + PATHSPEC_DATATYPE_DIFF = 1, +}; + +typedef git_array_t(char *) git_pathspec_string_array_t; + +/* public interface to pathspec matching */ +struct git_pathspec_match_list { + git_pathspec *pathspec; + git_array_t(void *) matches; + git_pathspec_string_array_t failures; + git_pool pool; + int datatype; +}; /* what is the common non-wildcard prefix for all items in the pathspec */ extern char *git_pathspec_prefix(const git_strarray *pathspec); @@ -19,36 +45,31 @@ extern char *git_pathspec_prefix(const git_strarray *pathspec); extern bool git_pathspec_is_empty(const git_strarray *pathspec); /* build a vector of fnmatch patterns to evaluate efficiently */ -extern int git_pathspec_init( +extern int git_pathspec__vinit( git_vector *vspec, const git_strarray *strspec, git_pool *strpool); /* free data from the pathspec vector */ -extern void git_pathspec_free(git_vector *vspec); +extern void git_pathspec__vfree(git_vector *vspec); + +#define GIT_PATHSPEC_NOMATCH ((size_t)-1) /* * Match a path against the vectorized pathspec. * The matched pathspec is passed back into the `matched_pathspec` parameter, * unless it is passed as NULL by the caller. */ -extern bool git_pathspec_match_path( - git_vector *vspec, +extern bool git_pathspec__match( + const git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold, - const char **matched_pathspec); + const char **matched_pathspec, + size_t *matched_at); /* easy pathspec setup */ -typedef struct { - char *prefix; - git_vector pathspec; - git_pool pool; -} git_pathspec_context; - -extern int git_pathspec_context_init( - git_pathspec_context *ctxt, const git_strarray *paths); +extern int git_pathspec__init(git_pathspec *ps, const git_strarray *paths); -extern void git_pathspec_context_free( - git_pathspec_context *ctxt); +extern void git_pathspec__clear(git_pathspec *ps); #endif |