diff options
| author | Edward Thomson <ethomson@edwardthomson.com> | 2019-06-15 15:47:41 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-06-15 15:47:41 +0100 |
| commit | fef847ae57d74e93563bc04222d9da7007fffc4f (patch) | |
| tree | 30e23c0b5b069f04c024e86fc9c1934ad2aaf04a /src | |
| parent | 2b6594de4d9a70eadeee63ec9549071d6b16a676 (diff) | |
| parent | 13ded47cbd898662f48490d28ca00bc50344afd8 (diff) | |
| download | libgit2-fef847ae57d74e93563bc04222d9da7007fffc4f.tar.gz | |
Merge pull request #5110 from pks-t/pks/wildmatch
Replace fnmatch with wildmatch
Diffstat (limited to 'src')
| -rw-r--r-- | src/attr_file.c | 26 | ||||
| -rw-r--r-- | src/attr_file.h | 5 | ||||
| -rw-r--r-- | src/config_file.c | 47 | ||||
| -rw-r--r-- | src/describe.c | 5 | ||||
| -rw-r--r-- | src/fnmatch.c | 248 | ||||
| -rw-r--r-- | src/fnmatch.h | 48 | ||||
| -rw-r--r-- | src/ignore.c | 16 | ||||
| -rw-r--r-- | src/pathspec.c | 26 | ||||
| -rw-r--r-- | src/posix.h | 1 | ||||
| -rw-r--r-- | src/refdb_fs.c | 7 | ||||
| -rw-r--r-- | src/refspec.c | 8 | ||||
| -rw-r--r-- | src/status.c | 9 | ||||
| -rw-r--r-- | src/tag.c | 3 | ||||
| -rw-r--r-- | src/wildmatch.c | 320 | ||||
| -rw-r--r-- | src/wildmatch.h | 23 |
15 files changed, 411 insertions, 381 deletions
diff --git a/src/attr_file.c b/src/attr_file.c index f01a743d2..edcfbdad5 100644 --- a/src/attr_file.c +++ b/src/attr_file.c @@ -15,6 +15,7 @@ #include "git2/tree.h" #include "blob.h" #include "index.h" +#include "wildmatch.h" #include <ctype.h> static void attr_file_free(git_attr_file *file) @@ -401,18 +402,13 @@ bool git_attr_fnmatch__match( } if (match->flags & GIT_ATTR_FNMATCH_ICASE) - flags |= FNM_CASEFOLD; - if (match->flags & GIT_ATTR_FNMATCH_LEADINGDIR) - flags |= FNM_LEADING_DIR; + flags |= WM_CASEFOLD; if (match->flags & GIT_ATTR_FNMATCH_FULLPATH) { filename = relpath; - flags |= FNM_PATHNAME; + flags |= WM_PATHNAME; } else { filename = path->basename; - - if (path->is_dir) - flags |= FNM_LEADING_DIR; } if ((match->flags & GIT_ATTR_FNMATCH_DIRECTORY) && !path->is_dir) { @@ -427,8 +423,6 @@ bool git_attr_fnmatch__match( path->basename == relpath) return false; - flags |= FNM_LEADING_DIR; - /* fail match if this is a file with same name as ignored folder */ samename = (match->flags & GIT_ATTR_FNMATCH_ICASE) ? !strcasecmp(match->pattern, relpath) : @@ -437,10 +431,10 @@ bool git_attr_fnmatch__match( if (samename) return false; - return (p_fnmatch(match->pattern, relpath, flags) != FNM_NOMATCH); + return (wildmatch(match->pattern, relpath, flags) == WM_MATCH); } - return (p_fnmatch(match->pattern, filename, flags) != FNM_NOMATCH); + return (wildmatch(match->pattern, filename, flags) == WM_MATCH); } bool git_attr_rule__match( @@ -658,8 +652,6 @@ int git_attr_fnmatch__parse( if (*pattern == '!' && (spec->flags & GIT_ATTR_FNMATCH_ALLOWNEG) != 0) { spec->flags = spec->flags | GIT_ATTR_FNMATCH_NEGATIVE; - if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0) - spec->flags |= GIT_ATTR_FNMATCH_LEADINGDIR; pattern++; } @@ -715,14 +707,6 @@ int git_attr_fnmatch__parse( if (--slash_count <= 0) spec->flags = spec->flags & ~GIT_ATTR_FNMATCH_FULLPATH; } - if ((spec->flags & GIT_ATTR_FNMATCH_NOLEADINGDIR) == 0 && - spec->length >= 2 && - pattern[spec->length - 1] == '*' && - pattern[spec->length - 2] == '/') { - spec->length -= 2; - spec->flags = spec->flags | GIT_ATTR_FNMATCH_LEADINGDIR; - /* leave FULLPATH match on, however */ - } if (context) { char *slash = strrchr(context, '/'); diff --git a/src/attr_file.h b/src/attr_file.h index fedf55af5..7a45516fb 100644 --- a/src/attr_file.h +++ b/src/attr_file.h @@ -32,12 +32,9 @@ #define GIT_ATTR_FNMATCH_MATCH_ALL (1U << 8) #define GIT_ATTR_FNMATCH_ALLOWNEG (1U << 9) #define GIT_ATTR_FNMATCH_ALLOWMACRO (1U << 10) -#define GIT_ATTR_FNMATCH_LEADINGDIR (1U << 11) -#define GIT_ATTR_FNMATCH_NOLEADINGDIR (1U << 12) #define GIT_ATTR_FNMATCH__INCOMING \ - (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | \ - GIT_ATTR_FNMATCH_ALLOWMACRO | GIT_ATTR_FNMATCH_NOLEADINGDIR) + (GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_ALLOWMACRO) typedef enum { GIT_ATTR_FILE__IN_MEMORY = 0, diff --git a/src/config_file.c b/src/config_file.c index 716205851..27b9cc5d7 100644 --- a/src/config_file.c +++ b/src/config_file.c @@ -18,6 +18,7 @@ #include "array.h" #include "config_parse.h" #include "config_entries.h" +#include "wildmatch.h" #include <ctype.h> #include <sys/types.h> @@ -697,41 +698,41 @@ static int do_match_gitdir( int *matches, const git_repository *repo, const char *cfg_file, - const char *value, + const char *condition, bool case_insensitive) { - git_buf path = GIT_BUF_INIT; - int error, fnmatch_flags; - - if (value[0] == '.' && git_path_is_dirsep(value[1])) { - git_path_dirname_r(&path, cfg_file); - git_buf_joinpath(&path, path.ptr, value + 2); - } else if (value[0] == '~' && git_path_is_dirsep(value[1])) - git_sysdir_expand_global_file(&path, value + 1); - else if (!git_path_is_absolute(value)) - git_buf_joinpath(&path, "**", value); + git_buf pattern = GIT_BUF_INIT, gitdir = GIT_BUF_INIT; + int error; + + if (condition[0] == '.' && git_path_is_dirsep(condition[1])) { + git_path_dirname_r(&pattern, cfg_file); + git_buf_joinpath(&pattern, pattern.ptr, condition + 2); + } else if (condition[0] == '~' && git_path_is_dirsep(condition[1])) + git_sysdir_expand_global_file(&pattern, condition + 1); + else if (!git_path_is_absolute(condition)) + git_buf_joinpath(&pattern, "**", condition); else - git_buf_sets(&path, value); + git_buf_sets(&pattern, condition); - if (git_buf_oom(&path)) { + if (git_path_is_dirsep(condition[strlen(condition) - 1])) + git_buf_puts(&pattern, "**"); + + if (git_buf_oom(&pattern)) { error = -1; goto out; } - if (git_path_is_dirsep(value[strlen(value) - 1])) - git_buf_puts(&path, "**"); - - fnmatch_flags = FNM_PATHNAME|FNM_LEADING_DIR; - if (case_insensitive) - fnmatch_flags |= FNM_IGNORECASE; - - if ((error = p_fnmatch(path.ptr, git_repository_path(repo), fnmatch_flags)) < 0) + if ((error = git_repository_item_path(&gitdir, repo, GIT_REPOSITORY_ITEM_GITDIR)) < 0) goto out; - *matches = (error == 0); + if (git_path_is_dirsep(gitdir.ptr[gitdir.size - 1])) + git_buf_truncate(&gitdir, gitdir.size - 1); + *matches = wildmatch(pattern.ptr, gitdir.ptr, + WM_PATHNAME | (case_insensitive ? WM_CASEFOLD : 0)) == WM_MATCH; out: - git_buf_dispose(&path); + git_buf_dispose(&pattern); + git_buf_dispose(&gitdir); return error; } diff --git a/src/describe.c b/src/describe.c index 7f715193d..42e5848c2 100644 --- a/src/describe.c +++ b/src/describe.c @@ -16,10 +16,11 @@ #include "commit_list.h" #include "oidmap.h" #include "refs.h" +#include "repository.h" #include "revwalk.h" #include "tag.h" #include "vector.h" -#include "repository.h" +#include "wildmatch.h" /* Ported from https://github.com/git/git/blob/89dde7882f71f846ccd0359756d27bebc31108de/builtin/describe.c */ @@ -214,7 +215,7 @@ static int get_name(const char *refname, void *payload) return 0; /* Accept only tags that match the pattern, if given */ - if (data->opts->pattern && (!is_tag || p_fnmatch(data->opts->pattern, + if (data->opts->pattern && (!is_tag || wildmatch(data->opts->pattern, refname + strlen(GIT_REFS_TAGS_DIR), 0))) return 0; diff --git a/src/fnmatch.c b/src/fnmatch.c deleted file mode 100644 index 3cc2a27ba..000000000 --- a/src/fnmatch.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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. - */ - -/* - * This file contains code originally derrived from OpenBSD fnmatch.c - * - * Copyright (c) 1989, 1993, 1994 - * The Regents of the University of California. All rights reserved. - * - * This code is derived from software contributed to Berkeley by - * Guido van Rossum. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * 3. Neither the name of the University nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS - * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT - * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY - * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -/* - * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. - * Compares a filename or pathname to a pattern. - */ - -#include "fnmatch.h" - -#include <ctype.h> -#include <stdio.h> -#include <string.h> - -#define EOS '\0' - -#define RANGE_MATCH 1 -#define RANGE_NOMATCH 0 -#define RANGE_ERROR (-1) - -static int rangematch(const char *, char, int, char **); - -static int -p_fnmatchx(const char *pattern, const char *string, int flags, size_t recurs) -{ - const char *stringstart; - char *newp; - char c, test; - int recurs_flags = flags & ~FNM_PERIOD; - - if (recurs-- == 0) - return FNM_NORES; - - for (stringstart = string;;) - switch (c = *pattern++) { - case EOS: - if ((flags & FNM_LEADING_DIR) && *string == '/') - return (0); - return (*string == EOS ? 0 : FNM_NOMATCH); - case '?': - if (*string == EOS) - return (FNM_NOMATCH); - if (*string == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - ++string; - break; - case '*': - c = *pattern; - - /* Let '**' override PATHNAME match for this segment. - * It will be restored if/when we recurse below. - */ - if (c == '*') { - c = *++pattern; - /* star-star-slash is at the end, match by default */ - if (c == EOS) - return 0; - /* Double-star must be at end or between slashes */ - if (c != '/') - return (FNM_NOMATCH); - - c = *++pattern; - do { - int e = p_fnmatchx(pattern, string, recurs_flags, recurs); - if (e != FNM_NOMATCH) - return e; - string = strchr(string, '/'); - } while (string++); - - /* If we get here, we didn't find a match */ - return FNM_NOMATCH; - } - - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - - /* Optimize for pattern with * at end or before /. */ - if (c == EOS) { - if (flags & FNM_PATHNAME) - return ((flags & FNM_LEADING_DIR) || - strchr(string, '/') == NULL ? - 0 : FNM_NOMATCH); - else - return (0); - } else if (c == '/' && (flags & FNM_PATHNAME)) { - if ((string = strchr(string, '/')) == NULL) - return (FNM_NOMATCH); - break; - } - - /* General case, use recursion. */ - while ((test = *string) != EOS) { - int e; - - e = p_fnmatchx(pattern, string, recurs_flags, recurs); - if (e != FNM_NOMATCH) - return e; - if (test == '/' && (flags & FNM_PATHNAME)) - break; - ++string; - } - return (FNM_NOMATCH); - case '[': - if (*string == EOS) - return (FNM_NOMATCH); - if (*string == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); - if (*string == '.' && (flags & FNM_PERIOD) && - (string == stringstart || - ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); - - switch (rangematch(pattern, *string, flags, &newp)) { - case RANGE_ERROR: - /* not a good range, treat as normal text */ - goto normal; - case RANGE_MATCH: - pattern = newp; - break; - case RANGE_NOMATCH: - return (FNM_NOMATCH); - } - ++string; - break; - case '\\': - if (!(flags & FNM_NOESCAPE)) { - if ((c = *pattern++) == EOS) { - c = '\\'; - --pattern; - } - } - /* FALLTHROUGH */ - default: - normal: - if (c != *string && !((flags & FNM_CASEFOLD) && - (git__tolower((unsigned char)c) == - git__tolower((unsigned char)*string)))) - return (FNM_NOMATCH); - ++string; - break; - } - /* NOTREACHED */ -} - -static int -rangematch(const char *pattern, char test, int flags, char **newp) -{ - int negate, ok; - char c, c2; - - /* - * A bracket expression starting with an unquoted circumflex - * character produces unspecified results (IEEE 1003.2-1992, - * 3.13.2). This implementation treats it like '!', for - * consistency with the regular expression syntax. - * J.T. Conklin (conklin@ngai.kaleida.com) - */ - if ((negate = (*pattern == '!' || *pattern == '^')) != 0) - ++pattern; - - if (flags & FNM_CASEFOLD) - test = (char)git__tolower((unsigned char)test); - - /* - * A right bracket shall lose its special meaning and represent - * itself in a bracket expression if it occurs first in the list. - * -- POSIX.2 2.8.3.2 - */ - ok = 0; - c = *pattern++; - do { - if (c == '\\' && !(flags & FNM_NOESCAPE)) - c = *pattern++; - if (c == EOS) - return (RANGE_ERROR); - if (c == '/' && (flags & FNM_PATHNAME)) - return (RANGE_NOMATCH); - if ((flags & FNM_CASEFOLD)) - c = (char)git__tolower((unsigned char)c); - if (*pattern == '-' - && (c2 = *(pattern+1)) != EOS && c2 != ']') { - pattern += 2; - if (c2 == '\\' && !(flags & FNM_NOESCAPE)) - c2 = *pattern++; - if (c2 == EOS) - return (RANGE_ERROR); - if (flags & FNM_CASEFOLD) - c2 = (char)git__tolower((unsigned char)c2); - if (c <= test && test <= c2) - ok = 1; - } else if (c == test) - ok = 1; - } while ((c = *pattern++) != ']'); - - *newp = (char *)pattern; - return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); -} - -int -p_fnmatch(const char *pattern, const char *string, int flags) -{ - return p_fnmatchx(pattern, string, flags, 64); -} - diff --git a/src/fnmatch.h b/src/fnmatch.h deleted file mode 100644 index ddaae15bb..000000000 --- a/src/fnmatch.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#ifndef INCLUDE_fnmatch_h__ -#define INCLUDE_fnmatch_h__ - -#include "common.h" - -#define FNM_NOMATCH 1 /* Match failed. */ -#define FNM_NOSYS 2 /* Function not supported (unused). */ -#define FNM_NORES 3 /* Out of resources */ - -#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ -#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ -#define FNM_PERIOD 0x04 /* Period must be matched by period. */ -#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */ -#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ - -#define FNM_IGNORECASE FNM_CASEFOLD -#define FNM_FILE_NAME FNM_PATHNAME - -extern int p_fnmatch(const char *pattern, const char *string, int flags); - -#endif diff --git a/src/ignore.c b/src/ignore.c index 5427efa29..3f748b80f 100644 --- a/src/ignore.c +++ b/src/ignore.c @@ -12,7 +12,7 @@ #include "attrcache.h" #include "path.h" #include "config.h" -#include "fnmatch.h" +#include "wildmatch.h" #define GIT_IGNORE_INTERNAL "[internal]exclude" @@ -101,7 +101,7 @@ static int does_negate_pattern(git_attr_fnmatch *rule, git_attr_fnmatch *neg) */ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match) { - int error = 0, fnflags; + int error = 0, wildmatch_flags; size_t i; git_attr_fnmatch *rule; char *path; @@ -109,9 +109,9 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match *out = 0; - fnflags = FNM_PATHNAME; + wildmatch_flags = WM_PATHNAME; if (match->flags & GIT_ATTR_FNMATCH_ICASE) - fnflags |= FNM_IGNORECASE; + wildmatch_flags |= WM_CASEFOLD; /* path of the file relative to the workdir, so we match the rules in subdirs */ if (match->containing_dir) { @@ -141,13 +141,13 @@ static int does_negate_rule(int *out, git_vector *rules, git_attr_fnmatch *match if (git_buf_oom(&buf)) goto out; - if ((error = p_fnmatch(git_buf_cstr(&buf), path, fnflags)) < 0) { + if ((error = wildmatch(git_buf_cstr(&buf), path, wildmatch_flags)) < 0) { git_error_set(GIT_ERROR_INVALID, "error matching pattern"); goto out; } /* if we found a match, we want to keep this rule */ - if (error != FNM_NOMATCH) { + if (error != WM_NOMATCH) { *out = 1; error = 0; goto out; @@ -193,9 +193,7 @@ static int parse_ignore_file( } match->flags = - GIT_ATTR_FNMATCH_ALLOWSPACE | - GIT_ATTR_FNMATCH_ALLOWNEG | - GIT_ATTR_FNMATCH_NOLEADINGDIR; + GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; if (!(error = git_attr_fnmatch__parse( match, &attrs->pool, context, &scan))) diff --git a/src/pathspec.c b/src/pathspec.c index 07795f256..9eb952308 100644 --- a/src/pathspec.c +++ b/src/pathspec.c @@ -16,6 +16,7 @@ #include "index.h" #include "bitvec.h" #include "diff.h" +#include "wildmatch.h" /* what is the common non-wildcard prefix for all items in the pathspec */ char *git_pathspec_prefix(const git_strarray *pathspec) @@ -84,8 +85,7 @@ int git_pathspec__vinit( if (!match) return -1; - match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | - GIT_ATTR_FNMATCH_ALLOWNEG | GIT_ATTR_FNMATCH_NOLEADINGDIR; + match->flags = GIT_ATTR_FNMATCH_ALLOWSPACE | GIT_ATTR_FNMATCH_ALLOWNEG; ret = git_attr_fnmatch__parse(match, strpool, NULL, &pattern); if (ret == GIT_ENOTFOUND) { @@ -110,7 +110,7 @@ void git_pathspec__vfree(git_vector *vspec) } struct pathspec_match_context { - int fnmatch_flags; + int wildmatch_flags; int (*strcomp)(const char *, const char *); int (*strncomp)(const char *, const char *, size_t); }; @@ -121,11 +121,11 @@ static void pathspec_match_context_init( bool casefold) { if (disable_fnmatch) - ctxt->fnmatch_flags = -1; + ctxt->wildmatch_flags = -1; else if (casefold) - ctxt->fnmatch_flags = FNM_CASEFOLD; + ctxt->wildmatch_flags = WM_CASEFOLD; else - ctxt->fnmatch_flags = 0; + ctxt->wildmatch_flags = 0; if (casefold) { ctxt->strcomp = git__strcasecmp; @@ -141,16 +141,16 @@ static int pathspec_match_one( struct pathspec_match_context *ctxt, const char *path) { - int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : FNM_NOMATCH; + int result = (match->flags & GIT_ATTR_FNMATCH_MATCH_ALL) ? 0 : WM_NOMATCH; - if (result == FNM_NOMATCH) - result = ctxt->strcomp(match->pattern, path) ? FNM_NOMATCH : 0; + if (result == WM_NOMATCH) + result = ctxt->strcomp(match->pattern, path) ? WM_NOMATCH : 0; - if (ctxt->fnmatch_flags >= 0 && result == FNM_NOMATCH) - result = p_fnmatch(match->pattern, path, ctxt->fnmatch_flags); + if (ctxt->wildmatch_flags >= 0 && result == WM_NOMATCH) + result = wildmatch(match->pattern, path, ctxt->wildmatch_flags); /* if we didn't match, look for exact dirname prefix match */ - if (result == FNM_NOMATCH && + if (result == WM_NOMATCH && (match->flags & GIT_ATTR_FNMATCH_HASWILD) == 0 && ctxt->strncomp(path, match->pattern, match->length) == 0 && path[match->length] == '/') @@ -159,7 +159,7 @@ static int pathspec_match_one( /* if we didn't match and this is a negative match, check for exact * match of filename with leading '!' */ - if (result == FNM_NOMATCH && + if (result == WM_NOMATCH && (match->flags & GIT_ATTR_FNMATCH_NEGATIVE) != 0 && *path == '!' && ctxt->strncomp(path + 1, match->pattern, match->length) == 0 && diff --git a/src/posix.h b/src/posix.h index 2934f2479..170ff2606 100644 --- a/src/posix.h +++ b/src/posix.h @@ -11,7 +11,6 @@ #include <fcntl.h> #include <time.h> -#include "fnmatch.h" /* stat: file mode type testing macros */ #ifndef S_IFGITLINK diff --git a/src/refdb_fs.c b/src/refdb_fs.c index c8533e69f..e5ee392fc 100644 --- a/src/refdb_fs.c +++ b/src/refdb_fs.c @@ -18,6 +18,7 @@ #include "iterator.h" #include "sortedcache.h" #include "signature.h" +#include "wildmatch.h" #include <git2/tag.h> #include <git2/object.h> @@ -571,7 +572,7 @@ static int iter_load_loose_paths(refdb_fs_backend *backend, refdb_fs_iter *iter) ref_name = git_buf_cstr(&path); if (git__suffixcmp(ref_name, ".lock") == 0 || - (iter->glob && p_fnmatch(iter->glob, ref_name, 0) != 0)) + (iter->glob && wildmatch(iter->glob, ref_name, 0) != 0)) continue; ref_dup = git_pool_strdup(&iter->pool, ref_name); @@ -617,7 +618,7 @@ static int refdb_fs_backend__iterator_next( if (ref->flags & PACKREF_SHADOWED) continue; - if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) continue; *out = git_reference__alloc(ref->name, &ref->oid, &ref->peel); @@ -660,7 +661,7 @@ static int refdb_fs_backend__iterator_next_name( if (ref->flags & PACKREF_SHADOWED) continue; - if (iter->glob && p_fnmatch(iter->glob, ref->name, 0) != 0) + if (iter->glob && wildmatch(iter->glob, ref->name, 0) != 0) continue; *out = ref->name; diff --git a/src/refspec.c b/src/refspec.c index dab053634..854240a84 100644 --- a/src/refspec.c +++ b/src/refspec.c @@ -9,10 +9,10 @@ #include "git2/errors.h" -#include "util.h" -#include "posix.h" #include "refs.h" +#include "util.h" #include "vector.h" +#include "wildmatch.h" int git_refspec__parse(git_refspec *refspec, const char *input, bool is_fetch) { @@ -213,7 +213,7 @@ int git_refspec_src_matches(const git_refspec *refspec, const char *refname) if (refspec == NULL || refspec->src == NULL) return false; - return (p_fnmatch(refspec->src, refname, 0) == 0); + return (wildmatch(refspec->src, refname, 0) == 0); } int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) @@ -221,7 +221,7 @@ int git_refspec_dst_matches(const git_refspec *refspec, const char *refname) if (refspec == NULL || refspec->dst == NULL) return false; - return (p_fnmatch(refspec->dst, refname, 0) == 0); + return (wildmatch(refspec->dst, refname, 0) == 0); } static int refspec_transform( diff --git a/src/status.c b/src/status.c index 8d3185f0e..a01736dee 100644 --- a/src/status.c +++ b/src/status.c @@ -16,6 +16,7 @@ #include "repository.h" #include "ignore.h" #include "index.h" +#include "wildmatch.h" #include "git2/diff.h" #include "diff.h" @@ -456,7 +457,7 @@ struct status_file_info { char *expected; unsigned int count; unsigned int status; - int fnm_flags; + int wildmatch_flags; int ambiguous; }; @@ -468,11 +469,11 @@ static int get_one_status(const char *path, unsigned int status, void *data) sfi->count++; sfi->status = status; - strcomp = (sfi->fnm_flags & FNM_CASEFOLD) ? git__strcasecmp : git__strcmp; + strcomp = (sfi->wildmatch_flags & WM_CASEFOLD) ? git__strcasecmp : git__strcmp; if (sfi->count > 1 || (strcomp(sfi->expected, path) != 0 && - p_fnmatch(sfi->expected, path, sfi->fnm_flags) != 0)) + wildmatch(sfi->expected, path, sfi->wildmatch_flags) != 0)) { sfi->ambiguous = true; return GIT_EAMBIGUOUS; /* git_error_set will be done by caller */ @@ -499,7 +500,7 @@ int git_status_file( if ((sfi.expected = git__strdup(path)) == NULL) return -1; if (index->ignore_case) - sfi.fnm_flags = FNM_CASEFOLD; + sfi.wildmatch_flags = WM_CASEFOLD; opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; opts.flags = GIT_STATUS_OPT_INCLUDE_IGNORED | @@ -10,6 +10,7 @@ #include "commit.h" #include "signature.h" #include "message.h" +#include "wildmatch.h" #include "git2/object.h" #include "git2/repository.h" #include "git2/signature.h" @@ -475,7 +476,7 @@ static int tag_list_cb(const char *tag_name, git_oid *oid, void *data) GIT_UNUSED(oid); if (!*filter->pattern || - p_fnmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) + wildmatch(filter->pattern, tag_name + GIT_REFS_TAGS_DIR_LEN, 0) == 0) { char *matched = git__strdup(tag_name + GIT_REFS_TAGS_DIR_LEN); GIT_ERROR_CHECK_ALLOC(matched); diff --git a/src/wildmatch.c b/src/wildmatch.c new file mode 100644 index 000000000..865f3f035 --- /dev/null +++ b/src/wildmatch.c @@ -0,0 +1,320 @@ +/* + * 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. + * + * Do shell-style pattern matching for ?, \, [], and * characters. + * It is 8bit clean. + * + * Written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986. + * Rich $alz is now <rsalz@bbn.com>. + * + * Modified by Wayne Davison to special-case '/' matching, to make '**' + * work differently than '*', and to fix the character-class code. + * + * Imported from git.git. + */ + +#include "wildmatch.h" + +#define GIT_SPACE 0x01 +#define GIT_DIGIT 0x02 +#define GIT_ALPHA 0x04 +#define GIT_GLOB_SPECIAL 0x08 +#define GIT_REGEX_SPECIAL 0x10 +#define GIT_PATHSPEC_MAGIC 0x20 +#define GIT_CNTRL 0x40 +#define GIT_PUNCT 0x80 + +enum { + S = GIT_SPACE, + A = GIT_ALPHA, + D = GIT_DIGIT, + G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ + R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ + P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ + X = GIT_CNTRL, + U = GIT_PUNCT, + Z = GIT_CNTRL | GIT_SPACE +}; + +static const unsigned char sane_ctype[256] = { + X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ + X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ + S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ + D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ + A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ + P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ + A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ + /* Nothing in the 128.. range */ +}; + +#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) +#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) + +typedef unsigned char uchar; + +/* What character marks an inverted character class? */ +#define NEGATE_CLASS '!' +#define NEGATE_CLASS2 '^' + +#define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ + && *(class) == *(litmatch) \ + && strncmp((char*)class, litmatch, len) == 0) + +#if defined STDC_HEADERS || !defined isascii +# define ISASCII(c) 1 +#else +# define ISASCII(c) isascii(c) +#endif + +#ifdef isblank +# define ISBLANK(c) (ISASCII(c) && isblank(c)) +#else +# define ISBLANK(c) ((c) == ' ' || (c) == '\t') +#endif + +#ifdef isgraph +# define ISGRAPH(c) (ISASCII(c) && isgraph(c)) +#else +# define ISGRAPH(c) (ISASCII(c) && isprint(c) && !isspace(c)) +#endif + +#define ISPRINT(c) (ISASCII(c) && isprint(c)) +#define ISDIGIT(c) (ISASCII(c) && isdigit(c)) +#define ISALNUM(c) (ISASCII(c) && isalnum(c)) +#define ISALPHA(c) (ISASCII(c) && isalpha(c)) +#define ISCNTRL(c) (ISASCII(c) && iscntrl(c)) +#define ISLOWER(c) (ISASCII(c) && islower(c)) +#define ISPUNCT(c) (ISASCII(c) && ispunct(c)) +#define ISSPACE(c) (ISASCII(c) && isspace(c)) +#define ISUPPER(c) (ISASCII(c) && isupper(c)) +#define ISXDIGIT(c) (ISASCII(c) && isxdigit(c)) + +/* Match pattern "p" against "text" */ +static int dowild(const uchar *p, const uchar *text, unsigned int flags) +{ + uchar p_ch; + const uchar *pattern = p; + + for ( ; (p_ch = *p) != '\0'; text++, p++) { + int matched, match_slash, negated; + uchar t_ch, prev_ch; + if ((t_ch = *text) == '\0' && p_ch != '*') + return WM_ABORT_ALL; + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + switch (p_ch) { + case '\\': + /* Literal match with following character. Note that the test + * in "default" handles the p[1] == '\0' failure case. */ + p_ch = *++p; + /* FALLTHROUGH */ + default: + if (t_ch != p_ch) + return WM_NOMATCH; + continue; + case '?': + /* Match anything but '/'. */ + if ((flags & WM_PATHNAME) && t_ch == '/') + return WM_NOMATCH; + continue; + case '*': + if (*++p == '*') { + const uchar *prev_p = p - 2; + while (*++p == '*') {} + if (!(flags & WM_PATHNAME)) + /* without WM_PATHNAME, '*' == '**' */ + match_slash = 1; + else if ((prev_p < pattern || *prev_p == '/') && + (*p == '\0' || *p == '/' || + (p[0] == '\\' && p[1] == '/'))) { + /* + * Assuming we already match 'foo/' and are at + * <star star slash>, just assume it matches + * nothing and go ahead match the rest of the + * pattern with the remaining string. This + * helps make foo/<*><*>/bar (<> because + * otherwise it breaks C comment syntax) match + * both foo/bar and foo/a/bar. + */ + if (p[0] == '/' && + dowild(p + 1, text, flags) == WM_MATCH) + return WM_MATCH; + match_slash = 1; + } else /* WM_PATHNAME is set */ + match_slash = 0; + } else + /* without WM_PATHNAME, '*' == '**' */ + match_slash = flags & WM_PATHNAME ? 0 : 1; + if (*p == '\0') { + /* Trailing "**" matches everything. Trailing "*" matches + * only if there are no more slash characters. */ + if (!match_slash) { + if (strchr((char*)text, '/') != NULL) + return WM_NOMATCH; + } + return WM_MATCH; + } else if (!match_slash && *p == '/') { + /* + * _one_ asterisk followed by a slash + * with WM_PATHNAME matches the next + * directory + */ + const char *slash = strchr((char*)text, '/'); + if (!slash) + return WM_NOMATCH; + text = (const uchar*)slash; + /* the slash is consumed by the top-level for loop */ + break; + } + while (1) { + if (t_ch == '\0') + break; + /* + * Try to advance faster when an asterisk is + * followed by a literal. We know in this case + * that the string before the literal + * must belong to "*". + * If match_slash is false, do not look past + * the first slash as it cannot belong to '*'. + */ + if (!is_glob_special(*p)) { + p_ch = *p; + if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) + p_ch = tolower(p_ch); + while ((t_ch = *text) != '\0' && + (match_slash || t_ch != '/')) { + if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) + t_ch = tolower(t_ch); + if (t_ch == p_ch) + break; + text++; + } + if (t_ch != p_ch) + return WM_NOMATCH; + } + if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { + if (!match_slash || matched != WM_ABORT_TO_STARSTAR) + return matched; + } else if (!match_slash && t_ch == '/') + return WM_ABORT_TO_STARSTAR; + t_ch = *++text; + } + return WM_ABORT_ALL; + case '[': + p_ch = *++p; +#ifdef NEGATE_CLASS2 + if (p_ch == NEGATE_CLASS2) + p_ch = NEGATE_CLASS; +#endif + /* Assign literal 1/0 because of "matched" comparison. */ + negated = p_ch == NEGATE_CLASS ? 1 : 0; + if (negated) { + /* Inverted character class. */ + p_ch = *++p; + } + prev_ch = 0; + matched = 0; + do { + if (!p_ch) + return WM_ABORT_ALL; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + if (t_ch == p_ch) + matched = 1; + } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { + p_ch = *++p; + if (p_ch == '\\') { + p_ch = *++p; + if (!p_ch) + return WM_ABORT_ALL; + } + if (t_ch <= p_ch && t_ch >= prev_ch) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { + uchar t_ch_upper = toupper(t_ch); + if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) + matched = 1; + } + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (p_ch == '[' && p[1] == ':') { + const uchar *s; + int i; + for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ + if (!p_ch) + return WM_ABORT_ALL; + i = p - s - 1; + if (i < 0 || p[-1] != ':') { + /* Didn't find ":]", so treat like a normal set. */ + p = s - 2; + p_ch = '['; + if (t_ch == p_ch) + matched = 1; + continue; + } + if (CC_EQ(s,i, "alnum")) { + if (ISALNUM(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "alpha")) { + if (ISALPHA(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "blank")) { + if (ISBLANK(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "cntrl")) { + if (ISCNTRL(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "digit")) { + if (ISDIGIT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "graph")) { + if (ISGRAPH(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "lower")) { + if (ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "print")) { + if (ISPRINT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "punct")) { + if (ISPUNCT(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "space")) { + if (ISSPACE(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "upper")) { + if (ISUPPER(t_ch)) + matched = 1; + else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) + matched = 1; + } else if (CC_EQ(s,i, "xdigit")) { + if (ISXDIGIT(t_ch)) + matched = 1; + } else /* malformed [:class:] string */ + return WM_ABORT_ALL; + p_ch = 0; /* This makes "prev_ch" get set to 0. */ + } else if (t_ch == p_ch) + matched = 1; + } while (prev_ch = p_ch, (p_ch = *++p) != ']'); + if (matched == negated || + ((flags & WM_PATHNAME) && t_ch == '/')) + return WM_NOMATCH; + continue; + } + } + + return *text ? WM_NOMATCH : WM_MATCH; +} + +/* Match the "pattern" against the "text" string. */ +int wildmatch(const char *pattern, const char *text, unsigned int flags) +{ + return dowild((const uchar*)pattern, (const uchar*)text, flags); +} diff --git a/src/wildmatch.h b/src/wildmatch.h new file mode 100644 index 000000000..44bb575a6 --- /dev/null +++ b/src/wildmatch.h @@ -0,0 +1,23 @@ +/* + * 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_wildmatch_h__ +#define INCLUDE_wildmatch_h__ + +#include "common.h" + +#define WM_CASEFOLD 1 +#define WM_PATHNAME 2 + +#define WM_NOMATCH 1 +#define WM_MATCH 0 +#define WM_ABORT_ALL -1 +#define WM_ABORT_TO_STARSTAR -2 + +int wildmatch(const char *pattern, const char *text, unsigned int flags); + +#endif |
