[PATCH] Allow multiple .hgignore files

tailgunner at smtp.ru tailgunner at smtp.ru
Thu Feb 15 16:20:08 CST 2007


On Thu, 15 Feb 2007 09:32:23 -0200
"Alexis S. L. Carvalho" <alexis at cecm.usp.br> wrote:

> 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?
> 

I got used to this functionality in Subversion which allowed per-direcory ignore lists,
and I miss it. Sometimes it's handy to define ignore list locally, without polluting
global .hgignore file. I'd really like to be able to split my large .hgignore into 3-4 pieces.

> 
> 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? 

Not really. The goal is to match foo/bar/file.c, but not dir/foo/file.c, so that
foo/.hgignore adds its contents to the global .hgignore for the tree
rooted at foo.

> 
> 
> >          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.
>
 
Yep

> > +                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.
>

I guess you're right. I should rework the patch.
 
> It would also be nice to have some tests - but as I said, I'm not sure
> we want this...
> 

If this feature is acceptable, tests are no problem. Thanks for the review.


More information about the Mercurial-devel mailing list