Show More
@@ -45,7 +45,7 b' Configs::' | |||||
45 |
|
45 | |||
46 | # Which files to track in LFS. Path tests are "**.extname" for file |
|
46 | # Which files to track in LFS. Path tests are "**.extname" for file | |
47 | # extensions, and "path:under/some/directory" for path prefix. Both |
|
47 | # extensions, and "path:under/some/directory" for path prefix. Both | |
48 |
# are relative to the repository root |
|
48 | # are relative to the repository root. | |
49 | # File size can be tested with the "size()" fileset, and tests can be |
|
49 | # File size can be tested with the "size()" fileset, and tests can be | |
50 | # joined with fileset operators. (See "hg help filesets.operators".) |
|
50 | # joined with fileset operators. (See "hg help filesets.operators".) | |
51 | # |
|
51 | # | |
@@ -55,9 +55,9 b' Configs::' | |||||
55 | # - size(">20MB") # larger than 20MB |
|
55 | # - size(">20MB") # larger than 20MB | |
56 | # - !**.txt # anything not a *.txt file |
|
56 | # - !**.txt # anything not a *.txt file | |
57 | # - **.zip | **.tar.gz | **.7z # some types of compressed files |
|
57 | # - **.zip | **.tar.gz | **.7z # some types of compressed files | |
58 |
# - |
|
58 | # - path:bin # files under "bin" in the project root | |
59 | # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz |
|
59 | # - (**.php & size(">2MB")) | (**.js & size(">5MB")) | **.tar.gz | |
60 |
# | ( |
|
60 | # | (path:bin & !path:/bin/README) | size(">1GB") | |
61 | # (default: none()) |
|
61 | # (default: none()) | |
62 | # |
|
62 | # | |
63 | # This is ignored if there is a tracked '.hglfs' file, and this setting |
|
63 | # This is ignored if there is a tracked '.hglfs' file, and this setting |
@@ -24,6 +24,7 b' from . import (' | |||||
24 | elements = { |
|
24 | elements = { | |
25 | # token-type: binding-strength, primary, prefix, infix, suffix |
|
25 | # token-type: binding-strength, primary, prefix, infix, suffix | |
26 | "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), |
|
26 | "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None), | |
|
27 | ":": (15, None, None, ("kindpat", 15), None), | |||
27 | "-": (5, None, ("negate", 19), ("minus", 5), None), |
|
28 | "-": (5, None, ("negate", 19), ("minus", 5), None), | |
28 | "not": (10, None, ("not", 10), None, None), |
|
29 | "not": (10, None, ("not", 10), None, None), | |
29 | "!": (10, None, ("not", 10), None, None), |
|
30 | "!": (10, None, ("not", 10), None, None), | |
@@ -50,7 +51,7 b' def tokenize(program):' | |||||
50 | c = program[pos] |
|
51 | c = program[pos] | |
51 | if c.isspace(): # skip inter-token whitespace |
|
52 | if c.isspace(): # skip inter-token whitespace | |
52 | pass |
|
53 | pass | |
53 | elif c in "(),-|&+!": # handle simple operators |
|
54 | elif c in "(),-:|&+!": # handle simple operators | |
54 | yield (c, None, pos) |
|
55 | yield (c, None, pos) | |
55 | elif (c in '"\'' or c == 'r' and |
|
56 | elif (c in '"\'' or c == 'r' and | |
56 | program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings |
|
57 | program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings | |
@@ -110,6 +111,18 b' def getstring(x, err):' | |||||
110 | return x[1] |
|
111 | return x[1] | |
111 | raise error.ParseError(err) |
|
112 | raise error.ParseError(err) | |
112 |
|
113 | |||
|
114 | def _getkindpat(x, y, allkinds, err): | |||
|
115 | kind = getsymbol(x) | |||
|
116 | pat = getstring(y, err) | |||
|
117 | if kind not in allkinds: | |||
|
118 | raise error.ParseError(_("invalid pattern kind: %s") % kind) | |||
|
119 | return '%s:%s' % (kind, pat) | |||
|
120 | ||||
|
121 | def getpattern(x, allkinds, err): | |||
|
122 | if x and x[0] == 'kindpat': | |||
|
123 | return _getkindpat(x[1], x[2], allkinds, err) | |||
|
124 | return getstring(x, err) | |||
|
125 | ||||
113 | def getset(mctx, x): |
|
126 | def getset(mctx, x): | |
114 | if not x: |
|
127 | if not x: | |
115 | raise error.ParseError(_("missing argument")) |
|
128 | raise error.ParseError(_("missing argument")) | |
@@ -119,6 +132,10 b' def stringset(mctx, x):' | |||||
119 | m = mctx.matcher([x]) |
|
132 | m = mctx.matcher([x]) | |
120 | return [f for f in mctx.subset if m(f)] |
|
133 | return [f for f in mctx.subset if m(f)] | |
121 |
|
134 | |||
|
135 | def kindpatset(mctx, x, y): | |||
|
136 | return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds, | |||
|
137 | _("pattern must be a string"))) | |||
|
138 | ||||
122 | def andset(mctx, x, y): |
|
139 | def andset(mctx, x, y): | |
123 | return getset(mctx.narrow(getset(mctx, x)), y) |
|
140 | return getset(mctx.narrow(getset(mctx, x)), y) | |
124 |
|
141 | |||
@@ -507,8 +524,9 b' def subrepo(mctx, x):' | |||||
507 | ctx = mctx.ctx |
|
524 | ctx = mctx.ctx | |
508 | sstate = sorted(ctx.substate) |
|
525 | sstate = sorted(ctx.substate) | |
509 | if x: |
|
526 | if x: | |
510 | # i18n: "subrepo" is a keyword |
|
527 | pat = getpattern(x, matchmod.allpatternkinds, | |
511 | pat = getstring(x, _("subrepo requires a pattern or no arguments")) |
|
528 | # i18n: "subrepo" is a keyword | |
|
529 | _("subrepo requires a pattern or no arguments")) | |||
512 | fast = not matchmod.patkind(pat) |
|
530 | fast = not matchmod.patkind(pat) | |
513 | if fast: |
|
531 | if fast: | |
514 | def m(s): |
|
532 | def m(s): | |
@@ -522,6 +540,7 b' def subrepo(mctx, x):' | |||||
522 | methods = { |
|
540 | methods = { | |
523 | 'string': stringset, |
|
541 | 'string': stringset, | |
524 | 'symbol': stringset, |
|
542 | 'symbol': stringset, | |
|
543 | 'kindpat': kindpatset, | |||
525 | 'and': andset, |
|
544 | 'and': andset, | |
526 | 'or': orset, |
|
545 | 'or': orset, | |
527 | 'minus': minusset, |
|
546 | 'minus': minusset, |
@@ -9,7 +9,8 b' Identifiers such as filenames or pattern' | |||||
9 | or double quotes if they contain characters outside of |
|
9 | or double quotes if they contain characters outside of | |
10 | ``[.*{}[]?/\_a-zA-Z0-9\x80-\xff]`` or if they match one of the |
|
10 | ``[.*{}[]?/\_a-zA-Z0-9\x80-\xff]`` or if they match one of the | |
11 | predefined predicates. This generally applies to file patterns other |
|
11 | predefined predicates. This generally applies to file patterns other | |
12 | than globs and arguments for predicates. |
|
12 | than globs and arguments for predicates. Pattern prefixes such as | |
|
13 | ``path:`` may be specified without quoting. | |||
13 |
|
14 | |||
14 | Special characters can be used in quoted identifiers by escaping them, |
|
15 | Special characters can be used in quoted identifiers by escaping them, | |
15 | e.g., ``\n`` is interpreted as a newline. To prevent them from being |
|
16 | e.g., ``\n`` is interpreted as a newline. To prevent them from being | |
@@ -75,4 +76,4 b' Some sample queries:' | |||||
75 |
|
76 | |||
76 | - Remove files listed in foo.lst that contain the letter a or b:: |
|
77 | - Remove files listed in foo.lst that contain the letter a or b:: | |
77 |
|
78 | |||
78 |
hg remove "set: |
|
79 | hg remove "set: listfile:foo.lst and (**a* or **b*)" |
@@ -17,16 +17,14 b' def _compile(tree):' | |||||
17 | if not tree: |
|
17 | if not tree: | |
18 | raise error.ParseError(_("missing argument")) |
|
18 | raise error.ParseError(_("missing argument")) | |
19 | op = tree[0] |
|
19 | op = tree[0] | |
20 | if op in {'symbol', 'string'}: |
|
20 | if op in {'symbol', 'string', 'kindpat'}: | |
21 |
name = fileset.get |
|
21 | name = fileset.getpattern(tree, {'path'}, _('invalid file pattern')) | |
22 | if name.startswith('**'): # file extension test, ex. "**.tar.gz" |
|
22 | if name.startswith('**'): # file extension test, ex. "**.tar.gz" | |
23 | ext = name[2:] |
|
23 | ext = name[2:] | |
24 | for c in ext: |
|
24 | for c in ext: | |
25 | if c in '*{}[]?/\\': |
|
25 | if c in '*{}[]?/\\': | |
26 | raise error.ParseError(_('reserved character: %s') % c) |
|
26 | raise error.ParseError(_('reserved character: %s') % c) | |
27 | return lambda n, s: n.endswith(ext) |
|
27 | return lambda n, s: n.endswith(ext) | |
28 | # TODO: teach fileset about 'path:', so that this can be a symbol and |
|
|||
29 | # not require quoting. |
|
|||
30 | elif name.startswith('path:'): # directory or full path test |
|
28 | elif name.startswith('path:'): # directory or full path test | |
31 | p = name[5:] # prefix |
|
29 | p = name[5:] # prefix | |
32 | pl = len(p) |
|
30 | pl = len(p) | |
@@ -78,7 +76,7 b' def compile(text):' | |||||
78 | for prefix test. The ``size()`` predicate is borrowed from filesets to test |
|
76 | for prefix test. The ``size()`` predicate is borrowed from filesets to test | |
79 | file size. The predicates ``all()`` and ``none()`` are also supported. |
|
77 | file size. The predicates ``all()`` and ``none()`` are also supported. | |
80 |
|
78 | |||
81 |
'(**.php & size(">10MB")) | **.zip | ( |
|
79 | '(**.php & size(">10MB")) | **.zip | (path:bin & !path:bin/README)' for | |
82 | example, will catch all php files whose size is greater than 10 MB, all |
|
80 | example, will catch all php files whose size is greater than 10 MB, all | |
83 | files whose name ends with ".zip", and all files under "bin" in the repo |
|
81 | files whose name ends with ".zip", and all files under "bin" in the repo | |
84 | root except for "bin/README". |
|
82 | root except for "bin/README". |
@@ -27,6 +27,24 b' Test operators and basic patterns' | |||||
27 | (string 're:a\\d') |
|
27 | (string 're:a\\d') | |
28 | a1 |
|
28 | a1 | |
29 | a2 |
|
29 | a2 | |
|
30 | $ fileset -v '!re:"a\d"' | |||
|
31 | (not | |||
|
32 | (kindpat | |||
|
33 | (symbol 're') | |||
|
34 | (string 'a\\d'))) | |||
|
35 | b1 | |||
|
36 | b2 | |||
|
37 | $ fileset -v 'path:a1 or glob:b?' | |||
|
38 | (or | |||
|
39 | (kindpat | |||
|
40 | (symbol 'path') | |||
|
41 | (symbol 'a1')) | |||
|
42 | (kindpat | |||
|
43 | (symbol 'glob') | |||
|
44 | (symbol 'b?'))) | |||
|
45 | a1 | |||
|
46 | b1 | |||
|
47 | b2 | |||
30 | $ fileset -v 'a1 or a2' |
|
48 | $ fileset -v 'a1 or a2' | |
31 | (or |
|
49 | (or | |
32 | (symbol 'a1') |
|
50 | (symbol 'a1') | |
@@ -80,6 +98,22 b' Test invalid syntax' | |||||
80 | hg: parse error: can't use negate operator in this context |
|
98 | hg: parse error: can't use negate operator in this context | |
81 | [255] |
|
99 | [255] | |
82 |
|
100 | |||
|
101 | $ fileset '"path":.' | |||
|
102 | hg: parse error: not a symbol | |||
|
103 | [255] | |||
|
104 | $ fileset 'path:foo bar' | |||
|
105 | hg: parse error at 9: invalid token | |||
|
106 | [255] | |||
|
107 | $ fileset 'foo:bar:baz' | |||
|
108 | hg: parse error: not a symbol | |||
|
109 | [255] | |||
|
110 | $ fileset 'foo:bar()' | |||
|
111 | hg: parse error: pattern must be a string | |||
|
112 | [255] | |||
|
113 | $ fileset 'foo:bar' | |||
|
114 | hg: parse error: invalid pattern kind: foo | |||
|
115 | [255] | |||
|
116 | ||||
83 | Test files status |
|
117 | Test files status | |
84 |
|
118 | |||
85 | $ rm a1 |
|
119 | $ rm a1 | |
@@ -346,6 +380,9 b' Test with a revision' | |||||
346 | $ fileset -r4 'subrepo("re:su.*")' |
|
380 | $ fileset -r4 'subrepo("re:su.*")' | |
347 | sub |
|
381 | sub | |
348 | sub2 |
|
382 | sub2 | |
|
383 | $ fileset -r4 'subrepo(re:su.*)' | |||
|
384 | sub | |||
|
385 | sub2 | |||
349 | $ fileset -r4 'subrepo("sub")' |
|
386 | $ fileset -r4 'subrepo("sub")' | |
350 | sub |
|
387 | sub | |
351 | $ fileset -r4 'b2 or c1' |
|
388 | $ fileset -r4 'b2 or c1' |
@@ -23,7 +23,7 b" check('none()', [], [('a.php', 123), ('b" | |||||
23 | check('!!!!((!(!!all())))', [], [('a.php', 123), ('b.txt', 0)]) |
|
23 | check('!!!!((!(!!all())))', [], [('a.php', 123), ('b.txt', 0)]) | |
24 |
|
24 | |||
25 | check('"path:a" & (**.b | **.c)', [('a/b.b', 0), ('a/c.c', 0)], [('b/c.c', 0)]) |
|
25 | check('"path:a" & (**.b | **.c)', [('a/b.b', 0), ('a/c.c', 0)], [('b/c.c', 0)]) | |
26 |
check('( |
|
26 | check('(path:a & **.b) | **.c', | |
27 | [('a/b.b', 0), ('a/c.c', 0), ('b/c.c', 0)], []) |
|
27 | [('a/b.b', 0), ('a/c.c', 0), ('b/c.c', 0)], []) | |
28 |
|
28 | |||
29 | check('**.bin - size("<20B")', [('b.bin', 21)], [('a.bin', 11), ('b.txt', 21)]) |
|
29 | check('**.bin - size("<20B")', [('b.bin', 21)], [('a.bin', 11), ('b.txt', 21)]) |
General Comments 0
You need to be logged in to leave comments.
Login now