如何在.hgignore中模拟语言互补操作符?

3 投票
3 回答
589 浏览
提问于 2025-04-15 12:40

我有一个Python的正则表达式,用来匹配一组文件名。我想知道怎么改它,这样我就可以在Mercurial的.hgignore文件中使用,以忽略那些符合这个表达式的文件。

事情的经过是这样的: 我有一个很大的源代码树,里面到处都是*.ml文件。我想把它们放到一个新的代码库里。但是还有一些其他不太重要的文件,它们太大了,不适合放进这个库。我正在尝试找到适合放在.hgignore文件里的表达式。

第一个观察: Python似乎没有正则语言的补充操作符(据我所知,它只能补充字符集)。(顺便问一下,为什么呢?)

第二个观察: 下面这个在Python中的正则表达式:

re.compile("^.*(?<!\.ml)$")

按预期工作:

abcabc - match  
abc.ml - no match  
x/abcabc - match  
x/abc.ml - no match

但是,当我把完全相同的表达式放到.hgignore文件里时,我得到的是:

$ hg st --all  
?  abc.ml  
I .hgignore  
I abcabc  
I x/xabc  
I x/xabc.ml  

根据.hgignore的手册,Mercurial使用的只是普通的Python正则表达式。那么为什么我会得到不同的结果呢? Mercurial是怎么找到x/xabc.ml这个匹配的?

有没有人知道有什么更简单的方法来解决缺少正则语言补充操作符的问题?

3 个回答

0

这个问题主要是因为在子目录中的匹配和根目录中的匹配是不一样的。请注意以下几点:

$ hg --version
Mercurial Distributed SCM (version 1.1.2)

这是一个旧版本,但它的表现和现在的一样。我的项目里有以下文件:

$ find . -name 'abc*' -print
./x/abcabc
./x/abc.ml
./abcabc
./abc.ml

这是我的 .hgignore 文件:

$ cat .hgignore
^.*(?<!\.ml)$

现在,当我运行 stat 命令时:

$ hg stat
? abc.ml

所以,hg 没有成功识别 x/abc.ml。但这真的是正则表达式的问题吗?也许不是:

$ python
Python 2.6.2 (release26-maint, Apr 19 2009, 01:56:41) 
[GCC 4.3.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import mercurial.ignore
>>> import os
>>> root = os.getcwd()
>>> ignorefunc = mercurial.ignore.ignore(root, ['.hgignore'], lambda msg: None)
>>> 
>>> ignorefunc("abc.ml") # No match - this is correct
>>> ignorefunc("abcabc") # Match - this is correct, we want to ignore this
<_sre.SRE_Match object at 0xb7c765d0>
>>> ignorefunc("abcabc").span() 
(0, 6)
>>> ignorefunc("x/abcabc").span() # Match - this is correct, we want to ignore this
(0, 8)
>>> ignorefunc("x/abc.ml") # No match - this is correct!
>>> 

注意到 ignorefuncabcabcx/abcabc 视为相同(匹配 - 也就是忽略),而 abc.mlx/abc.ml 也是被视为相同(不匹配 - 也就是不忽略)。

所以,也许逻辑错误在 Mercurial 的其他地方,或者我可能在看 Mercurial 的错误部分(虽然如果真是这样我会感到惊讶)。除非我漏掉了什么,也许需要向 Mercurial 提交一个 bug(而不是 Martin Geisler 提到的 RFE)。

0

通过一些测试,我发现了两个看起来有效的解决方案。第一个方案是指向一个子目录,这似乎很重要。第二个方案比较脆弱,因为它只允许使用一个后缀。我是在Windows XP(经过一些调整,让它更像Unix系统)上,使用Mercurial 1.2.1进行这些测试的。

(我加的评论用# message标注。)

$ hg --version
Mercurial Distributed SCM (version 1.2.1)

$ cat .hgignore
syntax: regexp
^x/.+(?<!\.ml)$       # rooted to x/ subdir
#^.+[^.][^m][^l]$

$ hg status --all
? .hgignore           # not affected by x/ regex
? abc.ml              # not affected by x/ regex
? abcabc              # not affected by x/ regex
? x\saveme.ml         # versioned, is *.ml
I x\abcabc            # ignored, is not *.ml
I x\ignoreme.txt      # ignored, is not *.ml

第二个方案是:

$ cat .hgignore
syntax: regexp
#^x/.+(?<!\.ml)$
^.+[^.][^m][^l]$      # brittle, can only use one suffix

$ hg status --all
? abc.ml              # versioned, is *.ml
? x\saveme.ml         # versioned, is *.ml
I .hgignore           # ignored, is not *.ml
I abcabc              # ignored, is not *.ml
I x\abcabc            # ignored, is not *.ml
I x\ignoreme.txt      # ignored, is not *.ml

根据我对原问题的理解,第二个方案的表现是完全符合预期的。第一个方案在子目录中表现符合预期,但它更灵活。

1

正则表达式会逐个应用到每个子目录的部分和文件名,而不是一次性应用到整个相对路径上。举个例子,如果我的项目里有 a/b/c/d,每个正则表达式会分别应用到 a、a/b、a/b/c 以及 a/b/c/d。如果其中任何一个部分匹配上了,那个文件就会被忽略。(你可以通过尝试 ^bar$ 来验证这一点,比如在 bar/foo 的情况下,你会发现 bar/foo 被忽略了。)

^.*(?<!\.ml)$ 会忽略 x/xabc.ml,因为这个模式匹配到了 x(也就是子目录)。

这意味着没有任何正则表达式能帮到你,因为你的模式必然会匹配到第一个子目录部分。

撰写回答