[PATCH] Allow multiple .hgignore files

Alexis S. L. Carvalho alexis at cecm.usp.br
Thu Feb 15 05:32:23 CST 2007


Thus spake tailgunner at smtp.ru:
> Allow multiple .hgignore files in a single repository

I don't really have a strong opinion here, so can you argue a bit why we
want this?

>  Note that non-repository-root .hgignore files are treated differently
> from root one.
> 
> 
> diff -r 01855c47da37 -r 0f35560bc179 mercurial/dirstate.py
> --- a/mercurial/dirstate.py	Wed Feb 14 01:05:09 2007 +0300
> +++ b/mercurial/dirstate.py	Wed Feb 14 01:05:09 2007 +0300
> @@ -41,7 +41,7 @@ class dirstate(object):
>          '''return the contents of .hgignore files as a list of patterns.
>  
>          the files parsed for patterns include:
> -        .hgignore in the repository root
> +        all .hgignore files in the repository
>          any additional files specified in the [ui] section of ~/.hgrc
>  
>          trailing white space is dropped.
> @@ -55,7 +55,11 @@ class dirstate(object):
>          syntax: glob   # defaults following lines to non-rooted globs
>          re:pattern     # non-rooted regular expression
>          glob:pattern   # non-rooted glob
> -        pattern        # pattern of the current default type'''
> +        pattern        # pattern of the current default type
> +
> +         Every pattern has the name of its containing directory prepended to
> +        it (this name is empty string for .hgignore in the repository root 
> +        and ~/.hgrc ignore files). '''

IIUC the desired behaviour is that a 

syntax: glob
*.c

in foo/.hgignore will match foo/file.c but not foo/bar/file.c and not
dir/foo/file.c , right?  And I guess you're still able to match files in
subdirs using either "**.c" globs or some regexps.


>          syntaxes = {'re': 'relre:', 'regexp': 'relre:', 'glob': 'relglob:'}
>          def parselines(fp):
>              for line in fp:
> @@ -67,10 +71,21 @@ class dirstate(object):
>                  line = line[:i].rstrip()
>                  if line: yield line
>          repoignore = self.wjoin('.hgignore')
> -        files = [repoignore]
> -        files.extend(self.ui.hgignorefiles())
> +        # .hgignore in repository root is considered always (we don't even
> +        # check if it exists prior to opening it)
> +        files = [(repoignore, "")]
> +        # ...but the rest of .hgignore files are considered only if they are
> +        # managed by Mercurial
> +        for f in self.map.keys():
> +            if f == ".hgignore":
> +	        pass # skip root .hgignore
> +            elif f.endswith(".hgignore"):

You probably want '/.hgignore' here.

> +                cd = f[:len(f) - len(".hgignore")]
> +                files.append((self.wjoin(f), cd))
> +        for f in self.ui.hgignorefiles():
> +            files.append((self.wjoin(f), ""))
>          pats = {}
> -        for f in files:
> +        for f, cd in files:
>              try:
>                  pats[f] = []
>                  fp = open(f)
> @@ -84,7 +99,7 @@ class dirstate(object):
>                              self.ui.warn(_("%s: ignoring invalid "
>                                             "syntax '%s'\n") % (f, s))
>                          continue
> -                    pat = syntax + line
> +                    pat = syntax + cd + line
>                      for s in syntaxes.values():
>                          if line.startswith(s):
>                              pat = line
>                              break

I don't think this will work.  AFAICS in a foo/.hgignore:

- if syntax is "relglob:" (i.e. we had a "syntax: glob" before), a "*.c"
  will be turned into relglob:foo/*.c , which will match not only
  foo/file.c , but also dir/foo/file.c

- if syntax is "relre:" (i.e. we had a "syntax: regexp" before), you
  really want to use re.escape(cd).  Even after that, this will still
  match dir/foo/file.c

- if line is something like "relglob:*.c" or "relre:*.c" your changes
  won't have any effect and we'll match '*.c' everywhere.

To handle the first two problems, you can try something like this
(completely untested):

    if not cd:
        pat = syntax + line
    elif syntax == 'relglob:':
        pat = 'glob:' + cd + line
    elif syntax == 'relre:':
        pat = 're:' + re.escape(cd) + line

To handle "relglob:*.c" lines, you can try extracting the syntax from
the line before these conditionals.

It would also be nice to have some tests - but as I said, I'm not sure
we want this...

Alexis


More information about the Mercurial-devel mailing list