git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: What's cooking in git.git (Oct 2012, #01; Tue, 2)

From
Michael Haggerty <mhagger@alum.mit.edu>
Date
Oct 4, 2012, 09:34 UTC
Message-ID
<506D5837.6020708@alum.mit.edu>
In-Reply-To
<7vbogj5sji.fsf@alter.siamese.dyndns.org>
On 10/03/2012 08:17 PM, Junio C Hamano wrote:
Show 32 quoted lines
> Nguyen Thai Ngoc Duy <pclouds@gmail.com> writes:
> 
>> There's an interesting case: "**foo". According to our rules, that
>> pattern does not contain slashes therefore is basename match. But some
>> might find that confusing because "**" can match slashes,...
> 
> By "our rules", if you mean "if a pattern has slash, it is anchored",
> that obviously need to be updated with this series, if "**" is meant
> to match multiple hierarchies.
>> I think the latter makes more sense. When users put "**" they expect
>> to match some slashes. But that may call for a refactoring in
>> path_matches() in attr.c. Putting strstr(pattern, "**") in that
>> matching function may increase overhead unnecessarily.
>>
>> The third option is just die() and let users decide either "*foo",
>> "**/foo" or "/**foo", never "**foo".
> 
> For the double-star at the beginning, you should just turn it into "**/"
> if it is not followed by a slash internally, I think.
> 
> What is the semantics of ** in the first place?  Is it described to
> a reasonable level of detail in the documentation updates?  For
> example does "**foo" match "afoo", "a/b/foo", "a/bfoo", "a/foo/b",
> "a/bfoo/c"?  Does "x**y" match "xy", "xay", "xa/by", "x/a/y"?
> 
> I am guessing that the only sensible definition is that "**"
> requires anything that comes before it (if exists) is at a proper
> hierarchy boundary, and anything matches it is also at a proper
> hierarchy boundary, so "x**y" matches "x/a/y" and not "xy", "xay",
> nor "xa/by" in the above example.  If "x**y" can match "xy" or "xay"
> (or "**foo" can match "afoo"), it would be unreasonable to say it
> implies the pattern is anchored at any level, no?

Given that there is no obvious interpretation for what a construct like "x**y" would mean, and many plausible guesses (most of which sound rather useless), I suggest that we forbid it. This will make the feature easier to explain and make .gitignore files that use it easier to understand.

I think that 98% of the usefulness of "**" would be in constructs where it replaces a proper part of the pathname, like "**/SOMETHING" or "SOMETHING/**/SOMETHING"; in other words, where its use matches the regexp "(^|/)\*\*/". In these constructs the only ambiguity is whether "**/" matches regexp

    "([^/]+/)+"
or
    "([^/]+/)*"

(e.g., whether "foo/**/bar" matches "foo/bar"). I personally prefer the second, because the first behavior can be had using the second interpretation by using "SOMETHING/*/**/SOMETHING", whereas the second behavior cannot be implemented in terms of the first in a single line of the .gitignore file.

Optionally, one might also like to support "SOMETHING/**" or "**" alone in the obvious ways.

As for the implementation, it is quite easy to textually convert a glob pattern, including "**" parts, into a regexp. I happen to have written some Python code that does this for another project (see below). An obvious optimization would be to read any literal parts of the path off the beginning of the glob pattern and only use regexps for the tail part. Would a regexp-based implementation be too slow?

Michael
_filename_char_pattern = r'[^/]'
_glob_patterns = [
    ('?', _filename_char_pattern),
    ('/**', r'(/.+)?'),
    ('**/', r'(.+/)?'),
    ('*', _filename_char_pattern + r'*'),
    ]
def glob_to_regexp(pattern):
    pattern = os.path.normpath(pattern) # remove trivial redundancies
    if pattern == '**':
        # This case has to be handled separately because it doesn't
        # involve a '/' character adjacent to the '**' pattern.  (Such
        # slashes otherwise have to be considered part of the pattern
        # to handle the matching of zero path components.)
        return re.compile(
            r'^' + _filename_char_pattern + r'(.+' +
_filename_char_pattern + r')?$'
            )
    regexp = [r'^']
    i = 0
    while i < len(pattern):
        for (s, r) in _glob_patterns:
            if pattern.startswith(s, i):
                regexp.append(r)
                i += len(s)
                break
        else:
            # AFAIK it's a normal character.  Escape it and add it to
            # pattern.
            regexp.append(re.escape(pattern[i]))
            i += 1
    regexp.append(r'$')
    return re.compile(''.join(regexp))
-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/
Previous: Junio C HamanoNext: Nguyen Thai Ngoc Duy
Message 18 of 28 in “What's cooking in git.git (Oct 2012, #01; Tue, 2)”
  1. Junio C HamanoOct 2, 2012
  2. Nguyen Thai Ngoc DuyOct 3, 2012
  3. Junio C HamanoOct 3, 2012
  4. Nguyen Thai Ngoc DuyOct 4, 2012
  5. Junio C HamanoOct 4, 2012
  6. 0/6 wildmatch part 2Nguyễn Thái Ngọc Duy, Oct 4, 2012
  7. 1/6 attr: remove the union in struct match_attrNguyễn Thái Ngọc Duy, Oct 4, 2012
  8. 2/6 attr: avoid strlen() on every matchNguyễn Thái Ngọc Duy, Oct 4, 2012
  9. 3/6 attr: avoid searching for basename on every matchNguyễn Thái Ngọc Duy, Oct 4, 2012
  10. 4/6 attr: more matching optimizations from .gitignoreNguyễn Thái Ngọc Duy, Oct 4, 2012
  11. 5/6 gitignore: do not do basename match with patterns that have '**'Nguyễn Thái Ngọc Duy, Oct 4, 2012
  12. Junio C HamanoOct 4, 2012
  13. Johannes SixtOct 5, 2012
  14. Nguyen Thai Ngoc DuyOct 5, 2012
  15. 6/6 t3001: note about expected "**" behaviorNguyễn Thái Ngọc Duy, Oct 4, 2012
  16. Junio C HamanoOct 4, 2012
  17. Junio C HamanoOct 4, 2012
  18. Michael HaggertyOct 4, 2012
  19. Nguyen Thai Ngoc DuyOct 4, 2012
  20. Michael HaggertyOct 4, 2012
  21. Junio C HamanoOct 4, 2012
  22. Andreas SchwabOct 5, 2012
  23. Matthieu MoyOct 5, 2012
  24. Andreas SchwabOct 5, 2012
  25. Nguyen Thai Ngoc DuyOct 5, 2012
  26. David Michael BarrOct 4, 2012
  27. Junio C HamanoOct 4, 2012
  28. Florian AchleitnerOct 30, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.