Commit c0e529f3 by Vicent Marti

Merge branch 'arrbee/examples-log' into development

parents bf3ee3cf 406dd556
......@@ -3,7 +3,7 @@
CC = gcc
CFLAGS = -g -I../include -I../src -Wall -Wextra -Wmissing-prototypes -Wno-missing-field-initializers
LFLAGS = -L../build -lgit2 -lz
APPS = general showindex diff rev-list cat-file status
APPS = general showindex diff rev-list cat-file status log rev-parse
all: $(APPS)
......
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
static void check(int error, const char *message, const char *arg)
{
if (!error)
return;
if (arg)
fprintf(stderr, "%s '%s' (%d)\n", message, arg, error);
else
fprintf(stderr, "%s (%d)\n", message, error);
exit(1);
}
static void usage(const char *message, const char *arg)
{
if (message && arg)
fprintf(stderr, "%s: %s\n", message, arg);
else if (message)
fprintf(stderr, "%s\n", message);
fprintf(stderr, "usage: log [<options>]\n");
exit(1);
}
struct log_state {
git_repository *repo;
const char *repodir;
git_revwalk *walker;
int hide;
int sorting;
};
static void set_sorting(struct log_state *s, unsigned int sort_mode)
{
if (!s->repo) {
if (!s->repodir) s->repodir = ".";
check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
"Could not open repository", s->repodir);
}
if (!s->walker)
check(git_revwalk_new(&s->walker, s->repo),
"Could not create revision walker", NULL);
if (sort_mode == GIT_SORT_REVERSE)
s->sorting = s->sorting ^ GIT_SORT_REVERSE;
else
s->sorting = sort_mode | (s->sorting & GIT_SORT_REVERSE);
git_revwalk_sorting(s->walker, s->sorting);
}
static void push_rev(struct log_state *s, git_object *obj, int hide)
{
hide = s->hide ^ hide;
if (!s->walker) {
check(git_revwalk_new(&s->walker, s->repo),
"Could not create revision walker", NULL);
git_revwalk_sorting(s->walker, s->sorting);
}
if (!obj)
check(git_revwalk_push_head(s->walker),
"Could not find repository HEAD", NULL);
else if (hide)
check(git_revwalk_hide(s->walker, git_object_id(obj)),
"Reference does not refer to a commit", NULL);
else
check(git_revwalk_push(s->walker, git_object_id(obj)),
"Reference does not refer to a commit", NULL);
git_object_free(obj);
}
static int add_revision(struct log_state *s, const char *revstr)
{
git_revspec revs;
int hide = 0;
if (!s->repo) {
if (!s->repodir) s->repodir = ".";
check(git_repository_open_ext(&s->repo, s->repodir, 0, NULL),
"Could not open repository", s->repodir);
}
if (!revstr) {
push_rev(s, NULL, hide);
return 0;
}
if (*revstr == '^') {
revs.flags = GIT_REVPARSE_SINGLE;
hide = !hide;
if (git_revparse_single(&revs.from, s->repo, revstr + 1) < 0)
return -1;
} else if (git_revparse(&revs, s->repo, revstr) < 0)
return -1;
if ((revs.flags & GIT_REVPARSE_SINGLE) != 0)
push_rev(s, revs.from, hide);
else {
push_rev(s, revs.to, hide);
if ((revs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
git_oid base;
check(git_merge_base(&base, s->repo,
git_object_id(revs.from), git_object_id(revs.to)),
"Could not find merge base", revstr);
check(git_object_lookup(&revs.to, s->repo, &base, GIT_OBJ_COMMIT),
"Could not find merge base commit", NULL);
push_rev(s, revs.to, hide);
}
push_rev(s, revs.from, !hide);
}
return 0;
}
static void print_time(const git_time *intime, const char *prefix)
{
char sign, out[32];
struct tm intm;
int offset, hours, minutes;
time_t t;
offset = intime->offset;
if (offset < 0) {
sign = '-';
offset = -offset;
} else {
sign = '+';
}
hours = offset / 60;
minutes = offset % 60;
t = (time_t)intime->time + (intime->offset * 60);
gmtime_r(&t, &intm);
strftime(out, sizeof(out), "%a %b %e %T %Y", &intm);
printf("%s%s %c%02d%02d\n", prefix, out, sign, hours, minutes);
}
static void print_commit(git_commit *commit)
{
char buf[GIT_OID_HEXSZ + 1];
int i, count;
const git_signature *sig;
const char *scan, *eol;
git_oid_tostr(buf, sizeof(buf), git_commit_id(commit));
printf("commit %s\n", buf);
if ((count = (int)git_commit_parentcount(commit)) > 1) {
printf("Merge:");
for (i = 0; i < count; ++i) {
git_oid_tostr(buf, 8, git_commit_parent_id(commit, i));
printf(" %s", buf);
}
printf("\n");
}
if ((sig = git_commit_author(commit)) != NULL) {
printf("Author: %s <%s>\n", sig->name, sig->email);
print_time(&sig->when, "Date: ");
}
printf("\n");
for (scan = git_commit_message(commit); scan && *scan; ) {
for (eol = scan; *eol && *eol != '\n'; ++eol) /* find eol */;
printf(" %.*s\n", (int)(eol - scan), scan);
scan = *eol ? eol + 1 : NULL;
}
printf("\n");
}
static int print_diff(
const git_diff_delta *delta,
const git_diff_range *range,
char usage,
const char *line,
size_t line_len,
void *data)
{
(void)delta; (void)range; (void)usage; (void)line_len; (void)data;
fputs(line, stdout);
return 0;
}
static int match_int(int *value, const char *arg, int allow_negative)
{
char *found;
*value = (int)strtol(arg, &found, 10);
return (found && *found == '\0' && (allow_negative || *value >= 0));
}
static int match_int_arg(
int *value, const char *arg, const char *pfx, int allow_negative)
{
size_t pfxlen = strlen(pfx);
if (strncmp(arg, pfx, pfxlen) != 0)
return 0;
if (!match_int(value, arg + pfxlen, allow_negative))
usage("Invalid value after argument", arg);
return 1;
}
static int match_with_parent(
git_commit *commit, int i, git_diff_options *opts)
{
git_commit *parent;
git_tree *a, *b;
git_diff_list *diff;
int ndeltas;
check(git_commit_parent(&parent, commit, (size_t)i), "Get parent", NULL);
check(git_commit_tree(&a, parent), "Tree for parent", NULL);
check(git_commit_tree(&b, commit), "Tree for commit", NULL);
check(git_diff_tree_to_tree(&diff, git_commit_owner(commit), a, b, opts),
"Checking diff between parent and commit", NULL);
ndeltas = (int)git_diff_num_deltas(diff);
git_diff_list_free(diff);
git_tree_free(a);
git_tree_free(b);
git_commit_free(parent);
return ndeltas > 0;
}
struct log_options {
int show_diff;
int skip, limit;
int min_parents, max_parents;
git_time_t before;
git_time_t after;
char *author;
char *committer;
};
int main(int argc, char *argv[])
{
int i, count = 0, printed = 0, parents;
char *a;
struct log_state s;
struct log_options opt;
git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
git_oid oid;
git_commit *commit = NULL;
git_pathspec *ps = NULL;
git_threads_init();
memset(&s, 0, sizeof(s));
s.sorting = GIT_SORT_TIME;
memset(&opt, 0, sizeof(opt));
opt.max_parents = -1;
for (i = 1; i < argc; ++i) {
a = argv[i];
if (a[0] != '-') {
if (!add_revision(&s, a))
++count;
else /* try failed revision parse as filename */
break;
} else if (!strcmp(a, "--")) {
++i;
break;
}
else if (!strcmp(a, "--date-order"))
set_sorting(&s, GIT_SORT_TIME);
else if (!strcmp(a, "--topo-order"))
set_sorting(&s, GIT_SORT_TOPOLOGICAL);
else if (!strcmp(a, "--reverse"))
set_sorting(&s, GIT_SORT_REVERSE);
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
s.repodir = a + strlen("--git-dir=");
else if (match_int_arg(&opt.skip, a, "--skip=", 0))
/* found valid --skip */;
else if (match_int_arg(&opt.limit, a, "--max-count=", 0))
/* found valid --max-count */;
else if (a[1] >= '0' && a[1] <= '9') {
if (!match_int(&opt.limit, a + 1, 0))
usage("Invalid limit on number of commits", a);
} else if (!strcmp(a, "-n")) {
if (i + 1 == argc || !match_int(&opt.limit, argv[i], 0))
usage("Argument -n not followed by valid count", argv[i]);
else
++i;
}
else if (!strcmp(a, "--merges"))
opt.min_parents = 2;
else if (!strcmp(a, "--no-merges"))
opt.max_parents = 1;
else if (!strcmp(a, "--no-min-parents"))
opt.min_parents = 0;
else if (!strcmp(a, "--no-max-parents"))
opt.max_parents = -1;
else if (match_int_arg(&opt.max_parents, a, "--max-parents=", 1))
/* found valid --max-parents */;
else if (match_int_arg(&opt.min_parents, a, "--min-parents=", 0))
/* found valid --min_parents */;
else if (!strcmp(a, "-p") || !strcmp(a, "-u") || !strcmp(a, "--patch"))
opt.show_diff = 1;
else
usage("Unsupported argument", a);
}
if (!count)
add_revision(&s, NULL);
diffopts.pathspec.strings = &argv[i];
diffopts.pathspec.count = argc - i;
if (diffopts.pathspec.count > 0)
check(git_pathspec_new(&ps, &diffopts.pathspec),
"Building pathspec", NULL);
printed = count = 0;
for (; !git_revwalk_next(&oid, s.walker); git_commit_free(commit)) {
check(git_commit_lookup(&commit, s.repo, &oid),
"Failed to look up commit", NULL);
parents = (int)git_commit_parentcount(commit);
if (parents < opt.min_parents)
continue;
if (opt.max_parents > 0 && parents > opt.max_parents)
continue;
if (diffopts.pathspec.count > 0) {
int unmatched = parents;
if (parents == 0) {
git_tree *tree;
check(git_commit_tree(&tree, commit), "Get tree", NULL);
if (git_pathspec_match_tree(
NULL, tree, GIT_PATHSPEC_NO_MATCH_ERROR, ps) != 0)
unmatched = 1;
git_tree_free(tree);
} else if (parents == 1) {
unmatched = match_with_parent(commit, 0, &diffopts) ? 0 : 1;
} else {
for (i = 0; i < parents; ++i) {
if (match_with_parent(commit, i, &diffopts))
unmatched--;
}
}
if (unmatched > 0)
continue;
}
if (count++ < opt.skip)
continue;
if (printed++ >= opt.limit) {
git_commit_free(commit);
break;
}
print_commit(commit);
if (opt.show_diff) {
git_tree *a = NULL, *b = NULL;
git_diff_list *diff = NULL;
if (parents > 1)
continue;
check(git_commit_tree(&b, commit), "Get tree", NULL);
if (parents == 1) {
git_commit *parent;
check(git_commit_parent(&parent, commit, 0), "Get parent", NULL);
check(git_commit_tree(&a, parent), "Tree for parent", NULL);
git_commit_free(parent);
}
check(git_diff_tree_to_tree(
&diff, git_commit_owner(commit), a, b, &diffopts),
"Diff commit with parent", NULL);
check(git_diff_print_patch(diff, print_diff, NULL),
"Displaying diff", NULL);
git_diff_list_free(diff);
git_tree_free(a);
git_tree_free(b);
}
}
git_pathspec_free(ps);
git_revwalk_free(s.walker);
git_repository_free(s.repo);
git_threads_shutdown();
return 0;
}
#include <stdio.h>
#include <git2.h>
#include <stdlib.h>
#include <string.h>
static void check(int error, const char *message, const char *arg)
{
if (!error)
return;
if (arg)
fprintf(stderr, "%s %s (%d)\n", message, arg, error);
else
fprintf(stderr, "%s(%d)\n", message, error);
exit(1);
}
static void usage(const char *message, const char *arg)
{
if (message && arg)
fprintf(stderr, "%s: %s\n", message, arg);
else if (message)
fprintf(stderr, "%s\n", message);
fprintf(stderr, "usage: rev-parse [ --option ] <args>...\n");
exit(1);
}
struct parse_state {
git_repository *repo;
const char *repodir;
int not;
};
static int parse_revision(struct parse_state *ps, const char *revstr)
{
git_revspec rs;
char str[GIT_OID_HEXSZ + 1];
if (!ps->repo) {
if (!ps->repodir)
ps->repodir = ".";
check(git_repository_open_ext(&ps->repo, ps->repodir, 0, NULL),
"Could not open repository from", ps->repodir);
}
check(git_revparse(&rs, ps->repo, revstr), "Could not parse", revstr);
if ((rs.flags & GIT_REVPARSE_SINGLE) != 0) {
git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
printf("%s\n", str);
git_object_free(rs.from);
}
else if ((rs.flags & GIT_REVPARSE_RANGE) != 0) {
git_oid_tostr(str, sizeof(str), git_object_id(rs.to));
printf("%s\n", str);
git_object_free(rs.to);
if ((rs.flags & GIT_REVPARSE_MERGE_BASE) != 0) {
git_oid base;
check(git_merge_base(&base, ps->repo,
git_object_id(rs.from), git_object_id(rs.to)),
"Could not find merge base", revstr);
git_oid_tostr(str, sizeof(str), &base);
printf("%s\n", str);
}
git_oid_tostr(str, sizeof(str), git_object_id(rs.from));
printf("^%s\n", str);
git_object_free(rs.from);
}
else {
check(0, "Invalid results from git_revparse", revstr);
}
return 0;
}
int main(int argc, char *argv[])
{
int i;
char *a;
struct parse_state ps;
git_threads_init();
memset(&ps, 0, sizeof(ps));
for (i = 1; i < argc; ++i) {
a = argv[i];
if (a[0] != '-') {
if (parse_revision(&ps, a) != 0)
break;
} else if (!strcmp(a, "--not"))
ps.not = !ps.not;
else if (!strncmp(a, "--git-dir=", strlen("--git-dir=")))
ps.repodir = a + strlen("--git-dir=");
else
usage("Cannot handle argument", a);
}
git_repository_free(ps.repo);
git_threads_shutdown();
return 0;
}
......@@ -56,5 +56,6 @@
#include "git2/message.h"
#include "git2/pack.h"
#include "git2/stash.h"
#include "git2/pathspec.h"
#endif
......@@ -130,6 +130,14 @@ GIT_EXTERN(const git_signature *) git_commit_committer(const git_commit *commit)
GIT_EXTERN(const git_signature *) git_commit_author(const git_commit *commit);
/**
* Get the full raw text of the commit header.
*
* @param commit a previously loaded commit
* @return the header text of the commit
*/
GIT_EXTERN(const char *) git_commit_raw_header(const git_commit *commit);
/**
* Get the tree pointed to by a commit.
*
* @param tree_out pointer where to store the tree object
......
......@@ -798,6 +798,14 @@ GIT_EXTERN(size_t) git_diff_num_deltas_of_type(
git_delta_t type);
/**
* Check if deltas are sorted case sensitively or insensitively.
*
* @param diff Diff list to check
* @return 0 if case sensitive, 1 if case is ignored
*/
GIT_EXTERN(int) git_diff_is_sorted_icase(const git_diff_list *diff);
/**
* Return the diff delta and patch for an entry in the diff list.
*
* The `git_diff_patch` is a newly created object contains the text diffs
......
......@@ -138,6 +138,14 @@ typedef enum {
GIT_INDEX_ADD_CHECK_PATHSPEC = (1u << 2),
} git_index_add_option_t;
/**
* Match any index stage.
*
* Some index APIs take a stage to match; pass this value to match
* any entry matching the path regardless of stage.
*/
#define GIT_INDEX_STAGE_ANY -1
/** @name Index File Functions
*
* These functions work on the index file itself.
......
/*
* 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_git_pathspec_h__
#define INCLUDE_git_pathspec_h__
#include "common.h"
#include "types.h"
#include "strarray.h"
#include "diff.h"
/**
* Compiled pathspec
*/
typedef struct git_pathspec git_pathspec;
/**
* List of filenames matching a pathspec
*/
typedef struct git_pathspec_match_list git_pathspec_match_list;
/**
* Options controlling how pathspec match should be executed
*
* - GIT_PATHSPEC_IGNORE_CASE forces match to ignore case; otherwise
* match will use native case sensitivity of platform filesystem
* - GIT_PATHSPEC_USE_CASE forces case sensitive match; otherwise
* match will use native case sensitivity of platform filesystem
* - GIT_PATHSPEC_NO_GLOB disables glob patterns and just uses simple
* string comparison for matching
* - GIT_PATHSPEC_NO_MATCH_ERROR means the match functions return error
* code GIT_ENOTFOUND if no matches are found; otherwise no matches is
* still success (return 0) but `git_pathspec_match_list_entrycount`
* will indicate 0 matches.
* - GIT_PATHSPEC_FIND_FAILURES means that the `git_pathspec_match_list`
* should track which patterns matched which files so that at the end of
* the match we can identify patterns that did not match any files.
* - GIT_PATHSPEC_FAILURES_ONLY means that the `git_pathspec_match_list`
* does not need to keep the actual matching filenames. Use this to
* just test if there were any matches at all or in combination with
* GIT_PATHSPEC_FIND_FAILURES to validate a pathspec.
*/
typedef enum {
GIT_PATHSPEC_DEFAULT = 0,
GIT_PATHSPEC_IGNORE_CASE = (1u << 0),
GIT_PATHSPEC_USE_CASE = (1u << 1),
GIT_PATHSPEC_NO_GLOB = (1u << 2),
GIT_PATHSPEC_NO_MATCH_ERROR = (1u << 3),
GIT_PATHSPEC_FIND_FAILURES = (1u << 4),
GIT_PATHSPEC_FAILURES_ONLY = (1u << 5),
} git_pathspec_flag_t;
/**
* Compile a pathspec
*
* @param out Output of the compiled pathspec
* @param pathspec A git_strarray of the paths to match
* @return 0 on success, <0 on failure
*/
GIT_EXTERN(int) git_pathspec_new(
git_pathspec **out, const git_strarray *pathspec);
/**
* Free a pathspec
*
* @param ps The compiled pathspec
*/
GIT_EXTERN(void) git_pathspec_free(git_pathspec *ps);
/**
* Try to match a path against a pathspec
*
* Unlike most of the other pathspec matching functions, this will not
* fall back on the native case-sensitivity for your platform. You must
* explicitly pass flags to control case sensitivity or else this will
* fall back on being case sensitive.
*
* @param ps The compiled pathspec
* @param flags Combination of git_pathspec_flag_t options to control match
* @param path The pathname to attempt to match
* @return 1 is path matches spec, 0 if it does not
*/
GIT_EXTERN(int) git_pathspec_matches_path(
const git_pathspec *ps, uint32_t flags, const char *path);
/**
* Match a pathspec against the working directory of a repository.
*
* This matches the pathspec against the current files in the working
* directory of the repository. It is an error to invoke this on a bare
* repo. This handles git ignores (i.e. ignored files will not be
* considered to match the `pathspec` unless the file is tracked in the
* index).
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param repo The repository in which to match; bare repo is an error
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag was given
*/
GIT_EXTERN(int) git_pathspec_match_workdir(
git_pathspec_match_list **out,
git_repository *repo,
uint32_t flags,
git_pathspec *ps);
/**
* Match a pathspec against entries in an index.
*
* This matches the pathspec against the files in the repository index.
*
* NOTE: At the moment, the case sensitivity of this match is controlled
* by the current case-sensitivity of the index object itself and the
* USE_CASE and IGNORE_CASE flags will have no effect. This behavior will
* be corrected in a future release.
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param index The index to match against
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
*/
GIT_EXTERN(int) git_pathspec_match_index(
git_pathspec_match_list **out,
git_index *index,
uint32_t flags,
git_pathspec *ps);
/**
* Match a pathspec against files in a tree.
*
* This matches the pathspec against the files in the given tree.
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param tree The root-level tree to match against
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
*/
GIT_EXTERN(int) git_pathspec_match_tree(
git_pathspec_match_list **out,
git_tree *tree,
uint32_t flags,
git_pathspec *ps);
/**
* Match a pathspec against files in a diff list.
*
* This matches the pathspec against the files in the given diff list.
*
* If `out` is not NULL, this returns a `git_patchspec_match_list`. That
* contains the list of all matched filenames (unless you pass the
* `GIT_PATHSPEC_FAILURES_ONLY` flag) and may also contain the list of
* pathspecs with no match (if you used the `GIT_PATHSPEC_FIND_FAILURES`
* flag). You must call `git_pathspec_match_list_free()` on this object.
*
* @param out Output list of matches; pass NULL to just get return value
* @param diff A generated diff list
* @param flags Combination of git_pathspec_flag_t options to control match
* @param ps Pathspec to be matched
* @return 0 on success, -1 on error, GIT_ENOTFOUND if no matches and
* the GIT_PATHSPEC_NO_MATCH_ERROR flag is used
*/
GIT_EXTERN(int) git_pathspec_match_diff(
git_pathspec_match_list **out,
git_diff_list *diff,
uint32_t flags,
git_pathspec *ps);
/**
* Free memory associates with a git_pathspec_match_list
*
* @param m The git_pathspec_match_list to be freed
*/
GIT_EXTERN(void) git_pathspec_match_list_free(git_pathspec_match_list *m);
/**
* Get the number of items in a match list.
*
* @param m The git_pathspec_match_list object
* @return Number of items in match list
*/
GIT_EXTERN(size_t) git_pathspec_match_list_entrycount(
const git_pathspec_match_list *m);
/**
* Get a matching filename by position.
*
* This routine cannot be used if the match list was generated by
* `git_pathspec_match_diff`. If so, it will always return NULL.
*
* @param m The git_pathspec_match_list object
* @param pos The index into the list
* @return The filename of the match
*/
GIT_EXTERN(const char *) git_pathspec_match_list_entry(
const git_pathspec_match_list *m, size_t pos);
/**
* Get a matching diff delta by position.
*
* This routine can only be used if the match list was generated by
* `git_pathspec_match_diff`. Otherwise it will always return NULL.
*
* @param m The git_pathspec_match_list object
* @param pos The index into the list
* @return The filename of the match
*/
GIT_EXTERN(const git_diff_delta *) git_pathspec_match_list_diff_entry(
const git_pathspec_match_list *m, size_t pos);
/**
* Get the number of pathspec items that did not match.
*
* This will be zero unless you passed GIT_PATHSPEC_FIND_FAILURES when
* generating the git_pathspec_match_list.
*
* @param m The git_pathspec_match_list object
* @return Number of items in original pathspec that had no matches
*/
GIT_EXTERN(size_t) git_pathspec_match_list_failed_entrycount(
const git_pathspec_match_list *m);
/**
* Get an original pathspec string that had no matches.
*
* This will be return NULL for positions out of range.
*
* @param m The git_pathspec_match_list object
* @param pos The index into the failed items
* @return The pathspec pattern that didn't match anything
*/
GIT_EXTERN(const char *) git_pathspec_match_list_failed_entry(
const git_pathspec_match_list *m, size_t pos);
#endif
......@@ -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
/*
* 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
......@@ -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;
......
......@@ -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(
......
......@@ -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);
......
......@@ -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,
......
......@@ -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);
......
......@@ -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;
}
......
......@@ -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
......@@ -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);
}
/* match a path against the vectorized pathspec */
bool git_pathspec_match_path(
git_vector *vspec,
const char *path,
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,
const char **matched_pathspec)
bool casefold)
{
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);
if (matched_pathspec)
*matched_pathspec = NULL;
if (!vspec || !vspec->length)
return true;
if (disable_fnmatch)
fnmatch_flags = -1;
ctxt->fnmatch_flags = -1;
else if (casefold)
fnmatch_flags = FNM_CASEFOLD;
ctxt->fnmatch_flags = FNM_CASEFOLD;
else
ctxt->fnmatch_flags = 0;
if (casefold) {
use_strcmp = git__strcasecmp;
use_strncmp = git__strncasecmp;
ctxt->strcomp = git__strcasecmp;
ctxt->strncomp = git__strncasecmp;
} else {
use_strcmp = git__strcmp;
use_strncmp = git__strncmp;
ctxt->strcomp = git__strcmp;
ctxt->strncomp = git__strncmp;
}
}
git_vector_foreach(vspec, i, match) {
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 = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0;
if (fnmatch_flags >= 0 && result == FNM_NOMATCH)
result = p_fnmatch(match->pattern, path, fnmatch_flags);
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 &&
use_strncmp(path, match->pattern, match->length) == 0 &&
ctxt->strncomp(path, match->pattern, match->length) == 0 &&
path[match->length] == '/')
result = 0;
if (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(
const git_vector *vspec,
const char *path,
bool disable_fnmatch,
bool casefold,
const char **matched_pathspec,
size_t *matched_at)
{
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;
pathspec_match_context_init(&ctxt, disable_fnmatch, casefold);
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;
}
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));
}
return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
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;
}
return false;
/* 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 (git_iterator_type(iter) == GIT_ITERATOR_TYPE_WORKDIR &&
(error = git_repository_index__weakptr(
&index, git_iterator_owner(iter))) < 0)
goto done;
pathspec_match_context_init(
&ctxt, (flags & GIT_PATHSPEC_NO_GLOB) != 0,
git_iterator_ignore_case(iter));
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);
/* no matches for this path */
if (result < 0)
continue;
/* 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 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;
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);
if (!(error = git_iterator_for_workdir(
&iter, repo, 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_index(
git_pathspec_match_list **out,
git_index *index,
uint32_t flags,
git_pathspec *ps)
{
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))) {
int git_pathspec_context_init(
git_pathspec_context *ctxt, const git_strarray *paths)
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;
memset(ctxt, 0, sizeof(*ctxt));
/* 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;
}
ctxt->prefix = git_pathspec_prefix(paths);
done:
git_bitvec_free(&used_patterns);
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);
if (error < 0) {
pathspec_match_free(m);
if (out) *out = NULL;
}
return error;
}
void git_pathspec_context_free(
git_pathspec_context *ctxt)
void git_pathspec_match_list_free(git_pathspec_match_list *m)
{
git__free(ctxt->prefix);
git_pathspec_free(&ctxt->pathspec);
git_pool_clear(&ctxt->pool);
memset(ctxt, 0, sizeof(*ctxt));
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;
}
......@@ -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
#include "clar_libgit2.h"
#include "bitvec.h"
#if 0
static void print_bitvec(git_bitvec *bv)
{
int b;
if (!bv->length) {
for (b = 63; b >= 0; --b)
fprintf(stderr, "%d", (bv->u.bits & (1ul << b)) ? 1 : 0);
} else {
for (b = bv->length * 8; b >= 0; --b)
fprintf(stderr, "%d", (bv->u.ptr[b >> 3] & (b & 0x0ff)) ? 1 : 0);
}
fprintf(stderr, "\n");
}
#endif
static void set_some_bits(git_bitvec *bv, size_t length)
{
size_t i;
for (i = 0; i < length; ++i) {
if (i % 3 == 0 || i % 7 == 0)
git_bitvec_set(bv, i, true);
}
}
static void check_some_bits(git_bitvec *bv, size_t length)
{
size_t i;
for (i = 0; i < length; ++i)
cl_assert_equal_b(i % 3 == 0 || i % 7 == 0, git_bitvec_get(bv, i));
}
void test_core_bitvec__0(void)
{
git_bitvec bv;
cl_git_pass(git_bitvec_init(&bv, 32));
set_some_bits(&bv, 16);
check_some_bits(&bv, 16);
git_bitvec_clear(&bv);
set_some_bits(&bv, 32);
check_some_bits(&bv, 32);
git_bitvec_clear(&bv);
set_some_bits(&bv, 64);
check_some_bits(&bv, 64);
git_bitvec_free(&bv);
cl_git_pass(git_bitvec_init(&bv, 128));
set_some_bits(&bv, 32);
check_some_bits(&bv, 32);
set_some_bits(&bv, 128);
check_some_bits(&bv, 128);
git_bitvec_free(&bv);
cl_git_pass(git_bitvec_init(&bv, 4000));
set_some_bits(&bv, 4000);
check_some_bits(&bv, 4000);
git_bitvec_free(&bv);
}
#include "clar_libgit2.h"
#include "diff_helpers.h"
static git_repository *g_repo = NULL;
void test_diff_pathspec__initialize(void)
{
g_repo = cl_git_sandbox_init("status");
}
void test_diff_pathspec__cleanup(void)
{
cl_git_sandbox_cleanup();
}
void test_diff_pathspec__0(void)
{
const char *a_commit = "26a125ee"; /* the current HEAD */
const char *b_commit = "0017bd4a"; /* the start */
git_tree *a = resolve_commit_oid_to_tree(g_repo, a_commit);
git_tree *b = resolve_commit_oid_to_tree(g_repo, b_commit);
git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
git_diff_list *diff = NULL;
git_strarray paths = { NULL, 1 };
char *path;
git_pathspec *ps;
git_pathspec_match_list *matches;
cl_assert(a);
cl_assert(b);
path = "*_file";
paths.strings = &path;
cl_git_pass(git_pathspec_new(&ps, &paths));
cl_git_pass(git_pathspec_match_tree(&matches, a, GIT_PATHSPEC_DEFAULT, ps));
cl_assert_equal_i(7, git_pathspec_match_list_entrycount(matches));
cl_assert_equal_s("current_file", git_pathspec_match_list_entry(matches,0));
cl_assert(git_pathspec_match_list_diff_entry(matches,0) == NULL);
git_pathspec_match_list_free(matches);
cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, NULL, a, &opts));
cl_git_pass(git_pathspec_match_diff(
&matches, diff, GIT_PATHSPEC_DEFAULT, ps));
cl_assert_equal_i(7, git_pathspec_match_list_entrycount(matches));
cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL);
cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL);
cl_assert_equal_s("current_file",
git_pathspec_match_list_diff_entry(matches,0)->new_file.path);
cl_assert_equal_i(GIT_DELTA_ADDED,
git_pathspec_match_list_diff_entry(matches,0)->status);
git_pathspec_match_list_free(matches);
git_diff_list_free(diff);
diff = NULL;
cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts));
cl_git_pass(git_pathspec_match_diff(
&matches, diff, GIT_PATHSPEC_DEFAULT, ps));
cl_assert_equal_i(3, git_pathspec_match_list_entrycount(matches));
cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL);
cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL);
cl_assert_equal_s("subdir/current_file",
git_pathspec_match_list_diff_entry(matches,0)->new_file.path);
cl_assert_equal_i(GIT_DELTA_DELETED,
git_pathspec_match_list_diff_entry(matches,0)->status);
git_pathspec_match_list_free(matches);
git_diff_list_free(diff);
diff = NULL;
cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts));
cl_git_pass(git_pathspec_match_diff(
&matches, diff, GIT_PATHSPEC_DEFAULT, ps));
cl_assert_equal_i(4, git_pathspec_match_list_entrycount(matches));
cl_assert(git_pathspec_match_list_diff_entry(matches, 0) != NULL);
cl_assert(git_pathspec_match_list_entry(matches, 0) == NULL);
cl_assert_equal_s("modified_file",
git_pathspec_match_list_diff_entry(matches,0)->new_file.path);
cl_assert_equal_i(GIT_DELTA_MODIFIED,
git_pathspec_match_list_diff_entry(matches,0)->status);
git_pathspec_match_list_free(matches);
git_diff_list_free(diff);
diff = NULL;
git_tree_free(a);
git_tree_free(b);
}
#include "clar_libgit2.h"
#include "git2/pathspec.h"
static git_repository *g_repo;
void test_repo_pathspec__initialize(void)
{
g_repo = cl_git_sandbox_init("status");
}
void test_repo_pathspec__cleanup(void)
{
cl_git_sandbox_cleanup();
g_repo = NULL;
}
static char *str0[] = { "*_file", "new_file", "garbage" };
static char *str1[] = { "*_FILE", "NEW_FILE", "GARBAGE" };
static char *str2[] = { "staged_*" };
static char *str3[] = { "!subdir", "*_file", "new_file" };
static char *str4[] = { "*" };
static char *str5[] = { "S*" };
void test_repo_pathspec__workdir0(void)
{
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "*_file", "new_file", "garbage" } */
s.strings = str0; s.count = ARRAY_SIZE(str0);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 0));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_FIND_FAILURES | GIT_PATHSPEC_FAILURES_ONLY, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
}
void test_repo_pathspec__workdir1(void)
{
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "*_FILE", "NEW_FILE", "GARBAGE" } */
s.strings = str1; s.count = ARRAY_SIZE(str1);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_IGNORE_CASE, ps));
cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_USE_CASE, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_fail(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_NO_MATCH_ERROR, ps));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
}
void test_repo_pathspec__workdir2(void)
{
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "staged_*" } */
s.strings = str2; s.count = ARRAY_SIZE(str2);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_fail(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_NO_MATCH_ERROR, ps));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_NO_GLOB | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
}
void test_repo_pathspec__workdir3(void)
{
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "!subdir", "*_file", "new_file" } */
s.strings = str3; s.count = ARRAY_SIZE(str3);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_workdir(&m, g_repo,
GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
cl_assert_equal_s("new_file", git_pathspec_match_list_entry(m, 2));
cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 3));
cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4));
cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 5));
cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 6));
cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
}
void test_repo_pathspec__workdir4(void)
{
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "*" } */
s.strings = str4; s.count = ARRAY_SIZE(str4);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_workdir(&m, g_repo, 0, ps));
cl_assert_equal_sz(13, git_pathspec_match_list_entrycount(m));
cl_assert_equal_s("这", git_pathspec_match_list_entry(m, 12));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
}
void test_repo_pathspec__index0(void)
{
git_index *idx;
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
cl_git_pass(git_repository_index(&idx, g_repo));
/* { "*_file", "new_file", "garbage" } */
s.strings = str0; s.count = ARRAY_SIZE(str0);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_index(&m, idx, 0, ps));
cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2));
cl_assert_equal_s("staged_new_file", git_pathspec_match_list_entry(m, 3));
cl_assert_equal_s("staged_new_file_deleted_file", git_pathspec_match_list_entry(m, 4));
cl_assert_equal_s("staged_new_file_modified_file", git_pathspec_match_list_entry(m, 5));
cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6));
cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 7));
cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 8));
cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 9));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_index(&m, idx,
GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0));
cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1));
cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
git_index_free(idx);
}
void test_repo_pathspec__index1(void)
{
/* Currently the USE_CASE and IGNORE_CASE flags don't work on the
* index because the index sort order for the index iterator is
* set by the index itself. I think the correct fix is for the
* index not to embed a global sort order but to support traversal
* in either case sensitive or insensitive order in a stateless
* manner.
*
* Anyhow, as it is, there is no point in doing this test.
*/
#if 0
git_index *idx;
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
cl_git_pass(git_repository_index(&idx, g_repo));
/* { "*_FILE", "NEW_FILE", "GARBAGE" } */
s.strings = str1; s.count = ARRAY_SIZE(str1);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_pathspec_match_index(&m, idx,
GIT_PATHSPEC_USE_CASE, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_index(&m, idx,
GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(3, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_index(&m, idx,
GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(10, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
git_pathspec_free(ps);
git_index_free(idx);
#endif
}
void test_repo_pathspec__tree0(void)
{
git_object *tree;
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "*_file", "new_file", "garbage" } */
s.strings = str0; s.count = ARRAY_SIZE(str0);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}"));
cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(4, git_pathspec_match_list_entrycount(m));
cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2));
cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3));
cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 4));
cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0));
cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1));
cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2));
git_pathspec_match_list_free(m);
git_object_free(tree);
cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}"));
cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(7, git_pathspec_match_list_entrycount(m));
cl_assert_equal_s("current_file", git_pathspec_match_list_entry(m, 0));
cl_assert_equal_s("modified_file", git_pathspec_match_list_entry(m, 1));
cl_assert_equal_s("staged_changes_modified_file", git_pathspec_match_list_entry(m, 2));
cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 3));
cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 4));
cl_assert_equal_s("subdir/deleted_file", git_pathspec_match_list_entry(m, 5));
cl_assert_equal_s("subdir/modified_file", git_pathspec_match_list_entry(m, 6));
cl_assert_equal_s(NULL, git_pathspec_match_list_entry(m, 7));
cl_assert_equal_sz(2, git_pathspec_match_list_failed_entrycount(m));
cl_assert_equal_s("new_file", git_pathspec_match_list_failed_entry(m, 0));
cl_assert_equal_s("garbage", git_pathspec_match_list_failed_entry(m, 1));
cl_assert_equal_s(NULL, git_pathspec_match_list_failed_entry(m, 2));
git_pathspec_match_list_free(m);
git_object_free(tree);
git_pathspec_free(ps);
}
void test_repo_pathspec__tree5(void)
{
git_object *tree;
git_strarray s;
git_pathspec *ps;
git_pathspec_match_list *m;
/* { "S*" } */
s.strings = str5; s.count = ARRAY_SIZE(str5);
cl_git_pass(git_pathspec_new(&ps, &s));
cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD~2^{tree}"));
cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
GIT_PATHSPEC_USE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(0, git_pathspec_match_list_entrycount(m));
cl_assert_equal_sz(1, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(5, git_pathspec_match_list_entrycount(m));
cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0));
cl_assert_equal_s("staged_delete_modified_file", git_pathspec_match_list_entry(m, 4));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
git_object_free(tree);
cl_git_pass(git_revparse_single(&tree, g_repo, "HEAD^{tree}"));
cl_git_pass(git_pathspec_match_tree(&m, (git_tree *)tree,
GIT_PATHSPEC_IGNORE_CASE | GIT_PATHSPEC_FIND_FAILURES, ps));
cl_assert_equal_sz(9, git_pathspec_match_list_entrycount(m));
cl_assert_equal_s("staged_changes", git_pathspec_match_list_entry(m, 0));
cl_assert_equal_s("subdir.txt", git_pathspec_match_list_entry(m, 5));
cl_assert_equal_s("subdir/current_file", git_pathspec_match_list_entry(m, 6));
cl_assert_equal_sz(0, git_pathspec_match_list_failed_entrycount(m));
git_pathspec_match_list_free(m);
git_object_free(tree);
git_pathspec_free(ps);
}
void test_repo_pathspec__in_memory(void)
{
static char *strings[] = { "one", "two*", "!three*", "*four" };
git_strarray s = { strings, ARRAY_SIZE(strings) };
git_pathspec *ps;
cl_git_pass(git_pathspec_new(&ps, &s));
cl_assert(git_pathspec_matches_path(ps, 0, "one"));
cl_assert(!git_pathspec_matches_path(ps, 0, "ONE"));
cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_IGNORE_CASE, "ONE"));
cl_assert(git_pathspec_matches_path(ps, 0, "two"));
cl_assert(git_pathspec_matches_path(ps, 0, "two.txt"));
cl_assert(!git_pathspec_matches_path(ps, 0, "three.txt"));
cl_assert(git_pathspec_matches_path(ps, 0, "anything.four"));
cl_assert(!git_pathspec_matches_path(ps, 0, "three.four"));
cl_assert(!git_pathspec_matches_path(ps, 0, "nomatch"));
cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two"));
cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "two*"));
cl_assert(!git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "anyfour"));
cl_assert(git_pathspec_matches_path(ps, GIT_PATHSPEC_NO_GLOB, "*four"));
git_pathspec_free(ps);
}
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