summaryrefslogtreecommitdiff
path: root/src/pathspec.c
diff options
context:
space:
mode:
authorRussell Belfer <rb@github.com>2012-11-08 16:47:28 -0800
committerRussell Belfer <rb@github.com>2012-11-09 13:52:07 -0800
commit2e3d4b96c08f1b0e2ee9b248c53aec523d70fd25 (patch)
tree0f1ad5328b06715a742cc9fec9cd1d8f289ffb17 /src/pathspec.c
parent220d5a6c3572574a2fcf8869816390eadebbb792 (diff)
downloadlibgit2-2e3d4b96c08f1b0e2ee9b248c53aec523d70fd25.tar.gz
Move pathspec code in separate files
Diff uses a `git_strarray` of path specs to represent a subset of all files to be processed. It is useful to be able to reuse this filtering in other places outside diff, so I've moved it into a standalone set of utilities.
Diffstat (limited to 'src/pathspec.c')
-rw-r--r--src/pathspec.c151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/pathspec.c b/src/pathspec.c
new file mode 100644
index 000000000..9632f5f13
--- /dev/null
+++ b/src/pathspec.c
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2009-2012 the libgit2 contributors
+ *
+ * This file is part of libgit2, distributed under the GNU GPL v2 with
+ * a Linking Exception. For full terms see the included COPYING file.
+ */
+
+#include "pathspec.h"
+#include "attr_file.h"
+
+/* what is the common non-wildcard prefix for all items in the pathspec */
+char *git_pathspec_prefix(const git_strarray *pathspec)
+{
+ git_buf prefix = GIT_BUF_INIT;
+ const char *scan;
+
+ if (!pathspec || !pathspec->count ||
+ git_buf_common_prefix(&prefix, pathspec) < 0)
+ return NULL;
+
+ /* diff prefix will only be leading non-wildcards */
+ for (scan = prefix.ptr; *scan; ++scan) {
+ if (git__iswildcard(*scan) &&
+ (scan == prefix.ptr || (*(scan - 1) != '\\')))
+ break;
+ }
+ git_buf_truncate(&prefix, scan - prefix.ptr);
+
+ if (prefix.size <= 0) {
+ git_buf_free(&prefix);
+ return NULL;
+ }
+
+ git_buf_unescape(&prefix);
+
+ return git_buf_detach(&prefix);
+}
+
+/* is there anything in the spec that needs to be filtered on */
+bool git_pathspec_is_interesting(const git_strarray *pathspec)
+{
+ const char *str;
+
+ if (pathspec == NULL || pathspec->count == 0)
+ return false;
+ if (pathspec->count > 1)
+ return true;
+
+ str = pathspec->strings[0];
+ if (!str || !str[0] || (!str[1] && (str[0] == '*' || str[0] == '.')))
+ return false;
+ return true;
+}
+
+/* build a vector of fnmatch patterns to evaluate efficiently */
+int git_pathspec_init(
+ git_vector *vspec, const git_strarray *strspec, git_pool *strpool)
+{
+ size_t i;
+
+ memset(vspec, 0, sizeof(*vspec));
+
+ if (!git_pathspec_is_interesting(strspec))
+ return 0;
+
+ if (git_vector_init(vspec, strspec->count, NULL) < 0)
+ return -1;
+
+ for (i = 0; i < strspec->count; ++i) {
+ int ret;
+ const char *pattern = strspec->strings[i];
+ git_attr_fnmatch *match = git__calloc(1, sizeof(git_attr_fnmatch));
+ if (!match)
+ return -1;
+
+ match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE;
+
+ ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern);
+ if (ret == GIT_ENOTFOUND) {
+ git__free(match);
+ continue;
+ } else if (ret < 0)
+ return ret;
+
+ if (git_vector_insert(vspec, match) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+/* free data from the pathspec vector */
+void git_pathspec_free(git_vector *vspec)
+{
+ git_attr_fnmatch *match;
+ unsigned int i;
+
+ git_vector_foreach(vspec, i, match) {
+ git__free(match);
+ vspec->contents[i] = NULL;
+ }
+
+ git_vector_free(vspec);
+}
+
+/* match a path against the vectorized pathspec */
+bool git_pathspec_match_path(
+ git_vector *vspec, const char *path, bool disable_fnmatch, bool casefold)
+{
+ unsigned int 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 (!vspec || !vspec->length)
+ return true;
+
+ if (disable_fnmatch)
+ fnmatch_flags = -1;
+ else if (casefold)
+ fnmatch_flags = FNM_CASEFOLD;
+
+ if (casefold) {
+ use_strcmp = strcasecmp;
+ use_strncmp = strncasecmp;
+ } else {
+ use_strcmp = strcmp;
+ use_strncmp = strncmp;
+ }
+
+ git_vector_foreach(vspec, i, match) {
+ int result = use_strcmp(match->pattern, path) ? FNM_NOMATCH : 0;
+
+ if (fnmatch_flags >= 0 && result == FNM_NOMATCH)
+ result = p_fnmatch(match->pattern, path, 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 &&
+ path[match->length] == '/')
+ result = 0;
+
+ if (result == 0)
+ return (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) ? false : true;
+ }
+
+ return false;
+}
+