##// END OF EJS Templates
check-code: allow disabling msys path check
Matt Mackall -
r24205:abcb1ee3 default
parent child Browse files
Show More
@@ -1,581 +1,581 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """style and portability checker for Mercurial
10 """style and portability checker for Mercurial
11
11
12 when a rule triggers wrong, do one of the following (prefer one from top):
12 when a rule triggers wrong, do one of the following (prefer one from top):
13 * do the work-around the rule suggests
13 * do the work-around the rule suggests
14 * doublecheck that it is a false match
14 * doublecheck that it is a false match
15 * improve the rule pattern
15 * improve the rule pattern
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
16 * add an ignore pattern to the rule (3rd arg) which matches your good line
17 (you can append a short comment and match this, like: #re-raises, # no-py24)
17 (you can append a short comment and match this, like: #re-raises, # no-py24)
18 * change the pattern to a warning and list the exception in test-check-code-hg
18 * change the pattern to a warning and list the exception in test-check-code-hg
19 * ONLY use no--check-code for skipping entire files from external sources
19 * ONLY use no--check-code for skipping entire files from external sources
20 """
20 """
21
21
22 import re, glob, os, sys
22 import re, glob, os, sys
23 import keyword
23 import keyword
24 import optparse
24 import optparse
25 try:
25 try:
26 import re2
26 import re2
27 except ImportError:
27 except ImportError:
28 re2 = None
28 re2 = None
29
29
30 def compilere(pat, multiline=False):
30 def compilere(pat, multiline=False):
31 if multiline:
31 if multiline:
32 pat = '(?m)' + pat
32 pat = '(?m)' + pat
33 if re2:
33 if re2:
34 try:
34 try:
35 return re2.compile(pat)
35 return re2.compile(pat)
36 except re2.error:
36 except re2.error:
37 pass
37 pass
38 return re.compile(pat)
38 return re.compile(pat)
39
39
40 def repquote(m):
40 def repquote(m):
41 fromc = '.:'
41 fromc = '.:'
42 tochr = 'pq'
42 tochr = 'pq'
43 def encodechr(i):
43 def encodechr(i):
44 if i > 255:
44 if i > 255:
45 return 'u'
45 return 'u'
46 c = chr(i)
46 c = chr(i)
47 if c in ' \n':
47 if c in ' \n':
48 return c
48 return c
49 if c.isalpha():
49 if c.isalpha():
50 return 'x'
50 return 'x'
51 if c.isdigit():
51 if c.isdigit():
52 return 'n'
52 return 'n'
53 try:
53 try:
54 return tochr[fromc.find(c)]
54 return tochr[fromc.find(c)]
55 except (ValueError, IndexError):
55 except (ValueError, IndexError):
56 return 'o'
56 return 'o'
57 t = m.group('text')
57 t = m.group('text')
58 tt = ''.join(encodechr(i) for i in xrange(256))
58 tt = ''.join(encodechr(i) for i in xrange(256))
59 t = t.translate(tt)
59 t = t.translate(tt)
60 return m.group('quote') + t + m.group('quote')
60 return m.group('quote') + t + m.group('quote')
61
61
62 def reppython(m):
62 def reppython(m):
63 comment = m.group('comment')
63 comment = m.group('comment')
64 if comment:
64 if comment:
65 l = len(comment.rstrip())
65 l = len(comment.rstrip())
66 return "#" * l + comment[l:]
66 return "#" * l + comment[l:]
67 return repquote(m)
67 return repquote(m)
68
68
69 def repcomment(m):
69 def repcomment(m):
70 return m.group(1) + "#" * len(m.group(2))
70 return m.group(1) + "#" * len(m.group(2))
71
71
72 def repccomment(m):
72 def repccomment(m):
73 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
73 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
74 return m.group(1) + t + "*/"
74 return m.group(1) + t + "*/"
75
75
76 def repcallspaces(m):
76 def repcallspaces(m):
77 t = re.sub(r"\n\s+", "\n", m.group(2))
77 t = re.sub(r"\n\s+", "\n", m.group(2))
78 return m.group(1) + t
78 return m.group(1) + t
79
79
80 def repinclude(m):
80 def repinclude(m):
81 return m.group(1) + "<foo>"
81 return m.group(1) + "<foo>"
82
82
83 def rephere(m):
83 def rephere(m):
84 t = re.sub(r"\S", "x", m.group(2))
84 t = re.sub(r"\S", "x", m.group(2))
85 return m.group(1) + t
85 return m.group(1) + t
86
86
87
87
88 testpats = [
88 testpats = [
89 [
89 [
90 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
90 (r'pushd|popd', "don't use 'pushd' or 'popd', use 'cd'"),
91 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
91 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
92 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
92 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
93 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
93 (r'(?<!hg )grep.*-a', "don't use 'grep -a', use in-line python"),
94 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
94 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
95 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
95 (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
96 (r'echo -n', "don't use 'echo -n', use printf"),
96 (r'echo -n', "don't use 'echo -n', use printf"),
97 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
97 (r'(^|\|\s*)\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
98 (r'head -c', "don't use 'head -c', use 'dd'"),
98 (r'head -c', "don't use 'head -c', use 'dd'"),
99 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
99 (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
100 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
100 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
101 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
101 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
102 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
102 (r'printf.*[^\\]\\([1-9]|0\d)', "don't use 'printf \NNN', use Python"),
103 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
103 (r'printf.*[^\\]\\x', "don't use printf \\x, use Python"),
104 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
104 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
105 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
105 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
106 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
106 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
107 "use egrep for extended grep syntax"),
107 "use egrep for extended grep syntax"),
108 (r'/bin/', "don't use explicit paths for tools"),
108 (r'/bin/', "don't use explicit paths for tools"),
109 (r'[^\n]\Z', "no trailing newline"),
109 (r'[^\n]\Z', "no trailing newline"),
110 (r'export.*=', "don't export and assign at once"),
110 (r'export.*=', "don't export and assign at once"),
111 (r'^source\b', "don't use 'source', use '.'"),
111 (r'^source\b', "don't use 'source', use '.'"),
112 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
112 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
113 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
113 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
114 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
114 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
115 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
115 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
116 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
116 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
117 (r'^alias\b.*=', "don't use alias, use a function"),
117 (r'^alias\b.*=', "don't use alias, use a function"),
118 (r'if\s*!', "don't use '!' to negate exit status"),
118 (r'if\s*!', "don't use '!' to negate exit status"),
119 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
119 (r'/dev/u?random', "don't use entropy, use /dev/zero"),
120 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
120 (r'do\s*true;\s*done', "don't use true as loop body, use sleep 0"),
121 (r'^( *)\t', "don't use tabs to indent"),
121 (r'^( *)\t', "don't use tabs to indent"),
122 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
122 (r'sed (-e )?\'(\d+|/[^/]*/)i(?!\\\n)',
123 "put a backslash-escaped newline after sed 'i' command"),
123 "put a backslash-escaped newline after sed 'i' command"),
124 (r'^diff *-\w*u.*$\n(^ \$ |^$)', "prefix diff -u with cmp"),
124 (r'^diff *-\w*u.*$\n(^ \$ |^$)', "prefix diff -u with cmp"),
125 ],
125 ],
126 # warnings
126 # warnings
127 [
127 [
128 (r'^function', "don't use 'function', use old style"),
128 (r'^function', "don't use 'function', use old style"),
129 (r'^diff.*-\w*N', "don't use 'diff -N'"),
129 (r'^diff.*-\w*N', "don't use 'diff -N'"),
130 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
130 (r'\$PWD|\${PWD}', "don't use $PWD, use `pwd`"),
131 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
131 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
132 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
132 (r'kill (`|\$\()', "don't use kill, use killdaemons.py")
133 ]
133 ]
134 ]
134 ]
135
135
136 testfilters = [
136 testfilters = [
137 (r"( *)(#([^\n]*\S)?)", repcomment),
137 (r"( *)(#([^\n]*\S)?)", repcomment),
138 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
138 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
139 ]
139 ]
140
140
141 winglobmsg = "use (glob) to match Windows paths too"
141 winglobmsg = "use (glob) to match Windows paths too"
142 uprefix = r"^ \$ "
142 uprefix = r"^ \$ "
143 utestpats = [
143 utestpats = [
144 [
144 [
145 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
145 (r'^(\S.*|| [$>] .*)[ \t]\n', "trailing whitespace on non-output"),
146 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
146 (uprefix + r'.*\|\s*sed[^|>\n]*\n',
147 "use regex test output patterns instead of sed"),
147 "use regex test output patterns instead of sed"),
148 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
148 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
149 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
149 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
150 (uprefix + r'.*\|\| echo.*(fail|error)',
150 (uprefix + r'.*\|\| echo.*(fail|error)',
151 "explicit exit code checks unnecessary"),
151 "explicit exit code checks unnecessary"),
152 (uprefix + r'set -e', "don't use set -e"),
152 (uprefix + r'set -e', "don't use set -e"),
153 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
153 (uprefix + r'(\s|fi\b|done\b)', "use > for continued lines"),
154 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
154 (uprefix + r'.*:\.\S*/', "x:.y in a path does not work on msys, rewrite "
155 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
155 "as x://.y, or see `hg log -k msys` for alternatives", r'-\S+:\.|' #-Rxxx
156 'hg pull -q file:../test'), # in test-pull.t which is skipped on windows
156 '# no-msys'), # in test-pull.t which is skipped on windows
157 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
157 (r'^ saved backup bundle to \$TESTTMP.*\.hg$', winglobmsg),
158 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
158 (r'^ changeset .* references (corrupted|missing) \$TESTTMP/.*[^)]$',
159 winglobmsg),
159 winglobmsg),
160 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
160 (r'^ pulling from \$TESTTMP/.*[^)]$', winglobmsg,
161 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
161 '\$TESTTMP/unix-repo$'), # in test-issue1802.t which skipped on windows
162 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
162 (r'^ reverting (?!subrepo ).*/.*[^)]$', winglobmsg),
163 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
163 (r'^ cloning subrepo \S+/.*[^)]$', winglobmsg),
164 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
164 (r'^ pushing to \$TESTTMP/.*[^)]$', winglobmsg),
165 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
165 (r'^ pushing subrepo \S+/\S+ to.*[^)]$', winglobmsg),
166 (r'^ moving \S+/.*[^)]$', winglobmsg),
166 (r'^ moving \S+/.*[^)]$', winglobmsg),
167 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
167 (r'^ no changes made to subrepo since.*/.*[^)]$', winglobmsg),
168 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
168 (r'^ .*: largefile \S+ not available from file:.*/.*[^)]$', winglobmsg),
169 (r'^ .*file://\$TESTTMP',
169 (r'^ .*file://\$TESTTMP',
170 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
170 'write "file:/*/$TESTTMP" + (glob) to match on windows too'),
171 (r'^ (cat|find): .*: No such file or directory',
171 (r'^ (cat|find): .*: No such file or directory',
172 'use test -f to test for file existence'),
172 'use test -f to test for file existence'),
173 ],
173 ],
174 # warnings
174 # warnings
175 [
175 [
176 (r'^ [^*?/\n]* \(glob\)$',
176 (r'^ [^*?/\n]* \(glob\)$',
177 "glob match with no glob character (?*/)"),
177 "glob match with no glob character (?*/)"),
178 ]
178 ]
179 ]
179 ]
180
180
181 for i in [0, 1]:
181 for i in [0, 1]:
182 for tp in testpats[i]:
182 for tp in testpats[i]:
183 p = tp[0]
183 p = tp[0]
184 m = tp[1]
184 m = tp[1]
185 if p.startswith(r'^'):
185 if p.startswith(r'^'):
186 p = r"^ [$>] (%s)" % p[1:]
186 p = r"^ [$>] (%s)" % p[1:]
187 else:
187 else:
188 p = r"^ [$>] .*(%s)" % p
188 p = r"^ [$>] .*(%s)" % p
189 utestpats[i].append((p, m) + tp[2:])
189 utestpats[i].append((p, m) + tp[2:])
190
190
191 utestfilters = [
191 utestfilters = [
192 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
192 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
193 (r"( *)(#([^\n]*\S)?)", repcomment),
193 (r"( *)(#([^\n]*\S)?)", repcomment),
194 ]
194 ]
195
195
196 pypats = [
196 pypats = [
197 [
197 [
198 (r'\([^)]*\*\w[^()]+\w+=', "can't pass varargs with keyword in Py2.5"),
198 (r'\([^)]*\*\w[^()]+\w+=', "can't pass varargs with keyword in Py2.5"),
199 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
199 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
200 "tuple parameter unpacking not available in Python 3+"),
200 "tuple parameter unpacking not available in Python 3+"),
201 (r'lambda\s*\(.*,.*\)',
201 (r'lambda\s*\(.*,.*\)',
202 "tuple parameter unpacking not available in Python 3+"),
202 "tuple parameter unpacking not available in Python 3+"),
203 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
203 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
204 '2to3 can\'t always rewrite "import qux, foo.bar", '
204 '2to3 can\'t always rewrite "import qux, foo.bar", '
205 'use "import foo.bar" on its own line instead.'),
205 'use "import foo.bar" on its own line instead.'),
206 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
206 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
207 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
207 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
208 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
208 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
209 'dict-from-generator'),
209 'dict-from-generator'),
210 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
210 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
211 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
211 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
212 (r'^\s*\t', "don't use tabs"),
212 (r'^\s*\t', "don't use tabs"),
213 (r'\S;\s*\n', "semicolon"),
213 (r'\S;\s*\n', "semicolon"),
214 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
214 (r'[^_]_\([ \t\n]*(?:"[^"]+"[ \t\n+]*)+%', "don't use % inside _()"),
215 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
215 (r"[^_]_\([ \t\n]*(?:'[^']+'[ \t\n+]*)+%", "don't use % inside _()"),
216 (r'(\w|\)),\w', "missing whitespace after ,"),
216 (r'(\w|\)),\w', "missing whitespace after ,"),
217 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
217 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
218 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
218 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
219 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n'
219 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)(\1except.*?:\n'
220 r'((?:\n|\1\s.*\n)+?))+\1finally:',
220 r'((?:\n|\1\s.*\n)+?))+\1finally:',
221 'no try/except/finally in Python 2.4'),
221 'no try/except/finally in Python 2.4'),
222 (r'(?<!def)(\s+|^|\()next\(.+\)',
222 (r'(?<!def)(\s+|^|\()next\(.+\)',
223 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
223 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
224 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
224 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
225 r'((?:\n|\1\s.*\n)+?)\1finally:',
225 r'((?:\n|\1\s.*\n)+?)\1finally:',
226 'no yield inside try/finally in Python 2.4'),
226 'no yield inside try/finally in Python 2.4'),
227 (r'.{81}', "line too long"),
227 (r'.{81}', "line too long"),
228 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
228 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
229 (r'[^\n]\Z', "no trailing newline"),
229 (r'[^\n]\Z', "no trailing newline"),
230 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
230 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
231 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
231 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
232 # "don't use underbars in identifiers"),
232 # "don't use underbars in identifiers"),
233 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
233 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
234 "don't use camelcase in identifiers"),
234 "don't use camelcase in identifiers"),
235 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
235 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
236 "linebreak after :"),
236 "linebreak after :"),
237 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
237 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
238 (r'class\s[^( \n]+\(\):',
238 (r'class\s[^( \n]+\(\):',
239 "class foo() not available in Python 2.4, use class foo(object)"),
239 "class foo() not available in Python 2.4, use class foo(object)"),
240 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
240 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
241 "Python keyword is not a function"),
241 "Python keyword is not a function"),
242 (r',]', "unneeded trailing ',' in list"),
242 (r',]', "unneeded trailing ',' in list"),
243 # (r'class\s[A-Z][^\(]*\((?!Exception)',
243 # (r'class\s[A-Z][^\(]*\((?!Exception)',
244 # "don't capitalize non-exception classes"),
244 # "don't capitalize non-exception classes"),
245 # (r'in range\(', "use xrange"),
245 # (r'in range\(', "use xrange"),
246 # (r'^\s*print\s+', "avoid using print in core and extensions"),
246 # (r'^\s*print\s+', "avoid using print in core and extensions"),
247 (r'[\x80-\xff]', "non-ASCII character literal"),
247 (r'[\x80-\xff]', "non-ASCII character literal"),
248 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
248 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
249 (r'^\s*with\s+', "with not available in Python 2.4"),
249 (r'^\s*with\s+', "with not available in Python 2.4"),
250 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
250 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
251 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
251 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
252 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
252 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
253 (r'(?<!def)\s+(any|all|format)\(',
253 (r'(?<!def)\s+(any|all|format)\(',
254 "any/all/format not available in Python 2.4", 'no-py24'),
254 "any/all/format not available in Python 2.4", 'no-py24'),
255 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
255 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
256 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
256 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
257 "gratuitous whitespace after Python keyword"),
257 "gratuitous whitespace after Python keyword"),
258 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
258 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
259 # (r'\s\s=', "gratuitous whitespace before ="),
259 # (r'\s\s=', "gratuitous whitespace before ="),
260 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
260 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
261 "missing whitespace around operator"),
261 "missing whitespace around operator"),
262 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
262 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
263 "missing whitespace around operator"),
263 "missing whitespace around operator"),
264 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
264 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
265 "missing whitespace around operator"),
265 "missing whitespace around operator"),
266 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
266 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
267 "wrong whitespace around ="),
267 "wrong whitespace around ="),
268 (r'\([^()]*( =[^=]|[^<>!=]= )',
268 (r'\([^()]*( =[^=]|[^<>!=]= )',
269 "no whitespace around = for named parameters"),
269 "no whitespace around = for named parameters"),
270 (r'raise Exception', "don't raise generic exceptions"),
270 (r'raise Exception', "don't raise generic exceptions"),
271 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
271 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
272 "don't use old-style two-argument raise, use Exception(message)"),
272 "don't use old-style two-argument raise, use Exception(message)"),
273 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
273 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
274 (r' [=!]=\s+(True|False|None)',
274 (r' [=!]=\s+(True|False|None)',
275 "comparison with singleton, use 'is' or 'is not' instead"),
275 "comparison with singleton, use 'is' or 'is not' instead"),
276 (r'^\s*(while|if) [01]:',
276 (r'^\s*(while|if) [01]:',
277 "use True/False for constant Boolean expression"),
277 "use True/False for constant Boolean expression"),
278 (r'(?:(?<!def)\s+|\()hasattr',
278 (r'(?:(?<!def)\s+|\()hasattr',
279 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
279 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
280 (r'opener\([^)]*\).read\(',
280 (r'opener\([^)]*\).read\(',
281 "use opener.read() instead"),
281 "use opener.read() instead"),
282 (r'BaseException', 'not in Python 2.4, use Exception'),
282 (r'BaseException', 'not in Python 2.4, use Exception'),
283 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
283 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
284 (r'opener\([^)]*\).write\(',
284 (r'opener\([^)]*\).write\(',
285 "use opener.write() instead"),
285 "use opener.write() instead"),
286 (r'[\s\(](open|file)\([^)]*\)\.read\(',
286 (r'[\s\(](open|file)\([^)]*\)\.read\(',
287 "use util.readfile() instead"),
287 "use util.readfile() instead"),
288 (r'[\s\(](open|file)\([^)]*\)\.write\(',
288 (r'[\s\(](open|file)\([^)]*\)\.write\(',
289 "use util.writefile() instead"),
289 "use util.writefile() instead"),
290 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
290 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
291 "always assign an opened file to a variable, and close it afterwards"),
291 "always assign an opened file to a variable, and close it afterwards"),
292 (r'[\s\(](open|file)\([^)]*\)\.',
292 (r'[\s\(](open|file)\([^)]*\)\.',
293 "always assign an opened file to a variable, and close it afterwards"),
293 "always assign an opened file to a variable, and close it afterwards"),
294 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
294 (r'(?i)descend[e]nt', "the proper spelling is descendAnt"),
295 (r'\.debug\(\_', "don't mark debug messages for translation"),
295 (r'\.debug\(\_', "don't mark debug messages for translation"),
296 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
296 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
297 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
297 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
298 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
298 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
299 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
299 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
300 "missing _() in ui message (use () to hide false-positives)"),
300 "missing _() in ui message (use () to hide false-positives)"),
301 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
301 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
302 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
302 (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
303 ],
303 ],
304 # warnings
304 # warnings
305 [
305 [
306 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
306 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
307 ]
307 ]
308 ]
308 ]
309
309
310 pyfilters = [
310 pyfilters = [
311 (r"""(?msx)(?P<comment>\#.*?$)|
311 (r"""(?msx)(?P<comment>\#.*?$)|
312 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
312 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
313 (?P<text>(([^\\]|\\.)*?))
313 (?P<text>(([^\\]|\\.)*?))
314 (?P=quote))""", reppython),
314 (?P=quote))""", reppython),
315 ]
315 ]
316
316
317 txtfilters = []
317 txtfilters = []
318
318
319 txtpats = [
319 txtpats = [
320 [
320 [
321 ('\s$', 'trailing whitespace'),
321 ('\s$', 'trailing whitespace'),
322 ('.. note::[ \n][^\n]', 'add two newlines after note::')
322 ('.. note::[ \n][^\n]', 'add two newlines after note::')
323 ],
323 ],
324 []
324 []
325 ]
325 ]
326
326
327 cpats = [
327 cpats = [
328 [
328 [
329 (r'//', "don't use //-style comments"),
329 (r'//', "don't use //-style comments"),
330 (r'^ ', "don't use spaces to indent"),
330 (r'^ ', "don't use spaces to indent"),
331 (r'\S\t', "don't use tabs except for indent"),
331 (r'\S\t', "don't use tabs except for indent"),
332 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
332 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
333 (r'.{81}', "line too long"),
333 (r'.{81}', "line too long"),
334 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
334 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
335 (r'return\(', "return is not a function"),
335 (r'return\(', "return is not a function"),
336 (r' ;', "no space before ;"),
336 (r' ;', "no space before ;"),
337 (r'[)][{]', "space between ) and {"),
337 (r'[)][{]', "space between ) and {"),
338 (r'\w+\* \w+', "use int *foo, not int* foo"),
338 (r'\w+\* \w+', "use int *foo, not int* foo"),
339 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
339 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
340 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
340 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
341 (r'\w,\w', "missing whitespace after ,"),
341 (r'\w,\w', "missing whitespace after ,"),
342 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
342 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
343 (r'^#\s+\w', "use #foo, not # foo"),
343 (r'^#\s+\w', "use #foo, not # foo"),
344 (r'[^\n]\Z', "no trailing newline"),
344 (r'[^\n]\Z', "no trailing newline"),
345 (r'^\s*#import\b', "use only #include in standard C code"),
345 (r'^\s*#import\b', "use only #include in standard C code"),
346 ],
346 ],
347 # warnings
347 # warnings
348 []
348 []
349 ]
349 ]
350
350
351 cfilters = [
351 cfilters = [
352 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
352 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
353 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
353 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
354 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
354 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
355 (r'(\()([^)]+\))', repcallspaces),
355 (r'(\()([^)]+\))', repcallspaces),
356 ]
356 ]
357
357
358 inutilpats = [
358 inutilpats = [
359 [
359 [
360 (r'\bui\.', "don't use ui in util"),
360 (r'\bui\.', "don't use ui in util"),
361 ],
361 ],
362 # warnings
362 # warnings
363 []
363 []
364 ]
364 ]
365
365
366 inrevlogpats = [
366 inrevlogpats = [
367 [
367 [
368 (r'\brepo\.', "don't use repo in revlog"),
368 (r'\brepo\.', "don't use repo in revlog"),
369 ],
369 ],
370 # warnings
370 # warnings
371 []
371 []
372 ]
372 ]
373
373
374 webtemplatefilters = []
374 webtemplatefilters = []
375
375
376 webtemplatepats = [
376 webtemplatepats = [
377 [],
377 [],
378 [
378 [
379 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
379 (r'{desc(\|(?!websub|firstline)[^\|]*)+}',
380 'follow desc keyword with either firstline or websub'),
380 'follow desc keyword with either firstline or websub'),
381 ]
381 ]
382 ]
382 ]
383
383
384 checks = [
384 checks = [
385 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
385 ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
386 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
386 ('test script', r'(.*/)?test-[^.~]*$', '', testfilters, testpats),
387 ('c', r'.*\.[ch]$', '', cfilters, cpats),
387 ('c', r'.*\.[ch]$', '', cfilters, cpats),
388 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
388 ('unified test', r'.*\.t$', '', utestfilters, utestpats),
389 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
389 ('layering violation repo in revlog', r'mercurial/revlog\.py', '',
390 pyfilters, inrevlogpats),
390 pyfilters, inrevlogpats),
391 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
391 ('layering violation ui in util', r'mercurial/util\.py', '', pyfilters,
392 inutilpats),
392 inutilpats),
393 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
393 ('txt', r'.*\.txt$', '', txtfilters, txtpats),
394 ('web template', r'mercurial/templates/.*\.tmpl', '',
394 ('web template', r'mercurial/templates/.*\.tmpl', '',
395 webtemplatefilters, webtemplatepats),
395 webtemplatefilters, webtemplatepats),
396 ]
396 ]
397
397
398 def _preparepats():
398 def _preparepats():
399 for c in checks:
399 for c in checks:
400 failandwarn = c[-1]
400 failandwarn = c[-1]
401 for pats in failandwarn:
401 for pats in failandwarn:
402 for i, pseq in enumerate(pats):
402 for i, pseq in enumerate(pats):
403 # fix-up regexes for multi-line searches
403 # fix-up regexes for multi-line searches
404 p = pseq[0]
404 p = pseq[0]
405 # \s doesn't match \n
405 # \s doesn't match \n
406 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
406 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
407 # [^...] doesn't match newline
407 # [^...] doesn't match newline
408 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
408 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
409
409
410 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
410 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
411 filters = c[3]
411 filters = c[3]
412 for i, flt in enumerate(filters):
412 for i, flt in enumerate(filters):
413 filters[i] = re.compile(flt[0]), flt[1]
413 filters[i] = re.compile(flt[0]), flt[1]
414 _preparepats()
414 _preparepats()
415
415
416 class norepeatlogger(object):
416 class norepeatlogger(object):
417 def __init__(self):
417 def __init__(self):
418 self._lastseen = None
418 self._lastseen = None
419
419
420 def log(self, fname, lineno, line, msg, blame):
420 def log(self, fname, lineno, line, msg, blame):
421 """print error related a to given line of a given file.
421 """print error related a to given line of a given file.
422
422
423 The faulty line will also be printed but only once in the case
423 The faulty line will also be printed but only once in the case
424 of multiple errors.
424 of multiple errors.
425
425
426 :fname: filename
426 :fname: filename
427 :lineno: line number
427 :lineno: line number
428 :line: actual content of the line
428 :line: actual content of the line
429 :msg: error message
429 :msg: error message
430 """
430 """
431 msgid = fname, lineno, line
431 msgid = fname, lineno, line
432 if msgid != self._lastseen:
432 if msgid != self._lastseen:
433 if blame:
433 if blame:
434 print "%s:%d (%s):" % (fname, lineno, blame)
434 print "%s:%d (%s):" % (fname, lineno, blame)
435 else:
435 else:
436 print "%s:%d:" % (fname, lineno)
436 print "%s:%d:" % (fname, lineno)
437 print " > %s" % line
437 print " > %s" % line
438 self._lastseen = msgid
438 self._lastseen = msgid
439 print " " + msg
439 print " " + msg
440
440
441 _defaultlogger = norepeatlogger()
441 _defaultlogger = norepeatlogger()
442
442
443 def getblame(f):
443 def getblame(f):
444 lines = []
444 lines = []
445 for l in os.popen('hg annotate -un %s' % f):
445 for l in os.popen('hg annotate -un %s' % f):
446 start, line = l.split(':', 1)
446 start, line = l.split(':', 1)
447 user, rev = start.split()
447 user, rev = start.split()
448 lines.append((line[1:-1], user, rev))
448 lines.append((line[1:-1], user, rev))
449 return lines
449 return lines
450
450
451 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
451 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
452 blame=False, debug=False, lineno=True):
452 blame=False, debug=False, lineno=True):
453 """checks style and portability of a given file
453 """checks style and portability of a given file
454
454
455 :f: filepath
455 :f: filepath
456 :logfunc: function used to report error
456 :logfunc: function used to report error
457 logfunc(filename, linenumber, linecontent, errormessage)
457 logfunc(filename, linenumber, linecontent, errormessage)
458 :maxerr: number of error to display before aborting.
458 :maxerr: number of error to display before aborting.
459 Set to false (default) to report all errors
459 Set to false (default) to report all errors
460
460
461 return True if no error is found, False otherwise.
461 return True if no error is found, False otherwise.
462 """
462 """
463 blamecache = None
463 blamecache = None
464 result = True
464 result = True
465
465
466 try:
466 try:
467 fp = open(f)
467 fp = open(f)
468 except IOError, e:
468 except IOError, e:
469 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
469 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
470 return result
470 return result
471 pre = post = fp.read()
471 pre = post = fp.read()
472 fp.close()
472 fp.close()
473
473
474 for name, match, magic, filters, pats in checks:
474 for name, match, magic, filters, pats in checks:
475 if debug:
475 if debug:
476 print name, f
476 print name, f
477 fc = 0
477 fc = 0
478 if not (re.match(match, f) or (magic and re.search(magic, f))):
478 if not (re.match(match, f) or (magic and re.search(magic, f))):
479 if debug:
479 if debug:
480 print "Skipping %s for %s it doesn't match %s" % (
480 print "Skipping %s for %s it doesn't match %s" % (
481 name, match, f)
481 name, match, f)
482 continue
482 continue
483 if "no-" "check-code" in pre:
483 if "no-" "check-code" in pre:
484 print "Skipping %s it has no-" "check-code" % f
484 print "Skipping %s it has no-" "check-code" % f
485 return "Skip" # skip checking this file
485 return "Skip" # skip checking this file
486 for p, r in filters:
486 for p, r in filters:
487 post = re.sub(p, r, post)
487 post = re.sub(p, r, post)
488 nerrs = len(pats[0]) # nerr elements are errors
488 nerrs = len(pats[0]) # nerr elements are errors
489 if warnings:
489 if warnings:
490 pats = pats[0] + pats[1]
490 pats = pats[0] + pats[1]
491 else:
491 else:
492 pats = pats[0]
492 pats = pats[0]
493 # print post # uncomment to show filtered version
493 # print post # uncomment to show filtered version
494
494
495 if debug:
495 if debug:
496 print "Checking %s for %s" % (name, f)
496 print "Checking %s for %s" % (name, f)
497
497
498 prelines = None
498 prelines = None
499 errors = []
499 errors = []
500 for i, pat in enumerate(pats):
500 for i, pat in enumerate(pats):
501 if len(pat) == 3:
501 if len(pat) == 3:
502 p, msg, ignore = pat
502 p, msg, ignore = pat
503 else:
503 else:
504 p, msg = pat
504 p, msg = pat
505 ignore = None
505 ignore = None
506 if i >= nerrs:
506 if i >= nerrs:
507 msg = "warning: " + msg
507 msg = "warning: " + msg
508
508
509 pos = 0
509 pos = 0
510 n = 0
510 n = 0
511 for m in p.finditer(post):
511 for m in p.finditer(post):
512 if prelines is None:
512 if prelines is None:
513 prelines = pre.splitlines()
513 prelines = pre.splitlines()
514 postlines = post.splitlines(True)
514 postlines = post.splitlines(True)
515
515
516 start = m.start()
516 start = m.start()
517 while n < len(postlines):
517 while n < len(postlines):
518 step = len(postlines[n])
518 step = len(postlines[n])
519 if pos + step > start:
519 if pos + step > start:
520 break
520 break
521 pos += step
521 pos += step
522 n += 1
522 n += 1
523 l = prelines[n]
523 l = prelines[n]
524
524
525 if ignore and re.search(ignore, l, re.MULTILINE):
525 if ignore and re.search(ignore, l, re.MULTILINE):
526 if debug:
526 if debug:
527 print "Skipping %s for %s:%s (ignore pattern)" % (
527 print "Skipping %s for %s:%s (ignore pattern)" % (
528 name, f, n)
528 name, f, n)
529 continue
529 continue
530 bd = ""
530 bd = ""
531 if blame:
531 if blame:
532 bd = 'working directory'
532 bd = 'working directory'
533 if not blamecache:
533 if not blamecache:
534 blamecache = getblame(f)
534 blamecache = getblame(f)
535 if n < len(blamecache):
535 if n < len(blamecache):
536 bl, bu, br = blamecache[n]
536 bl, bu, br = blamecache[n]
537 if bl == l:
537 if bl == l:
538 bd = '%s@%s' % (bu, br)
538 bd = '%s@%s' % (bu, br)
539
539
540 errors.append((f, lineno and n + 1, l, msg, bd))
540 errors.append((f, lineno and n + 1, l, msg, bd))
541 result = False
541 result = False
542
542
543 errors.sort()
543 errors.sort()
544 for e in errors:
544 for e in errors:
545 logfunc(*e)
545 logfunc(*e)
546 fc += 1
546 fc += 1
547 if maxerr and fc >= maxerr:
547 if maxerr and fc >= maxerr:
548 print " (too many errors, giving up)"
548 print " (too many errors, giving up)"
549 break
549 break
550
550
551 return result
551 return result
552
552
553 if __name__ == "__main__":
553 if __name__ == "__main__":
554 parser = optparse.OptionParser("%prog [options] [files]")
554 parser = optparse.OptionParser("%prog [options] [files]")
555 parser.add_option("-w", "--warnings", action="store_true",
555 parser.add_option("-w", "--warnings", action="store_true",
556 help="include warning-level checks")
556 help="include warning-level checks")
557 parser.add_option("-p", "--per-file", type="int",
557 parser.add_option("-p", "--per-file", type="int",
558 help="max warnings per file")
558 help="max warnings per file")
559 parser.add_option("-b", "--blame", action="store_true",
559 parser.add_option("-b", "--blame", action="store_true",
560 help="use annotate to generate blame info")
560 help="use annotate to generate blame info")
561 parser.add_option("", "--debug", action="store_true",
561 parser.add_option("", "--debug", action="store_true",
562 help="show debug information")
562 help="show debug information")
563 parser.add_option("", "--nolineno", action="store_false",
563 parser.add_option("", "--nolineno", action="store_false",
564 dest='lineno', help="don't show line numbers")
564 dest='lineno', help="don't show line numbers")
565
565
566 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
566 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
567 lineno=True)
567 lineno=True)
568 (options, args) = parser.parse_args()
568 (options, args) = parser.parse_args()
569
569
570 if len(args) == 0:
570 if len(args) == 0:
571 check = glob.glob("*")
571 check = glob.glob("*")
572 else:
572 else:
573 check = args
573 check = args
574
574
575 ret = 0
575 ret = 0
576 for f in check:
576 for f in check:
577 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
577 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
578 blame=options.blame, debug=options.debug,
578 blame=options.blame, debug=options.debug,
579 lineno=options.lineno):
579 lineno=options.lineno):
580 ret = 1
580 ret = 1
581 sys.exit(ret)
581 sys.exit(ret)
@@ -1,92 +1,92 b''
1 #require serve
1 #require serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5
5
6 $ echo foo>foo
6 $ echo foo>foo
7 $ hg addremove
7 $ hg addremove
8 adding foo
8 adding foo
9 $ hg commit -m 1
9 $ hg commit -m 1
10
10
11 $ hg verify
11 $ hg verify
12 checking changesets
12 checking changesets
13 checking manifests
13 checking manifests
14 crosschecking files in changesets and manifests
14 crosschecking files in changesets and manifests
15 checking files
15 checking files
16 1 files, 1 changesets, 1 total revisions
16 1 files, 1 changesets, 1 total revisions
17
17
18 $ hg serve -p $HGPORT -d --pid-file=hg.pid
18 $ hg serve -p $HGPORT -d --pid-file=hg.pid
19 $ cat hg.pid >> $DAEMON_PIDS
19 $ cat hg.pid >> $DAEMON_PIDS
20 $ cd ..
20 $ cd ..
21
21
22 $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy
22 $ hg clone --pull http://foo:bar@localhost:$HGPORT/ copy
23 requesting all changes
23 requesting all changes
24 adding changesets
24 adding changesets
25 adding manifests
25 adding manifests
26 adding file changes
26 adding file changes
27 added 1 changesets with 1 changes to 1 files
27 added 1 changesets with 1 changes to 1 files
28 updating to branch default
28 updating to branch default
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30
30
31 $ cd copy
31 $ cd copy
32 $ hg verify
32 $ hg verify
33 checking changesets
33 checking changesets
34 checking manifests
34 checking manifests
35 crosschecking files in changesets and manifests
35 crosschecking files in changesets and manifests
36 checking files
36 checking files
37 1 files, 1 changesets, 1 total revisions
37 1 files, 1 changesets, 1 total revisions
38
38
39 $ hg co
39 $ hg co
40 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 $ cat foo
41 $ cat foo
42 foo
42 foo
43
43
44 $ hg manifest --debug
44 $ hg manifest --debug
45 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
45 2ed2a3912a0b24502043eae84ee4b279c18b90dd 644 foo
46
46
47 $ hg pull
47 $ hg pull
48 pulling from http://foo@localhost:$HGPORT/
48 pulling from http://foo@localhost:$HGPORT/
49 searching for changes
49 searching for changes
50 no changes found
50 no changes found
51
51
52 $ hg rollback --dry-run --verbose
52 $ hg rollback --dry-run --verbose
53 repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/)
53 repository tip rolled back to revision -1 (undo pull: http://foo:***@localhost:$HGPORT/)
54
54
55 Issue622: hg init && hg pull -u URL doesn't checkout default branch
55 Issue622: hg init && hg pull -u URL doesn't checkout default branch
56
56
57 $ cd ..
57 $ cd ..
58 $ hg init empty
58 $ hg init empty
59 $ cd empty
59 $ cd empty
60 $ hg pull -u ../test
60 $ hg pull -u ../test
61 pulling from ../test
61 pulling from ../test
62 requesting all changes
62 requesting all changes
63 adding changesets
63 adding changesets
64 adding manifests
64 adding manifests
65 adding file changes
65 adding file changes
66 added 1 changesets with 1 changes to 1 files
66 added 1 changesets with 1 changes to 1 files
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
68
68
69 Test 'file:' uri handling:
69 Test 'file:' uri handling:
70
70
71 $ hg pull -q file://../test-does-not-exist
71 $ hg pull -q file://../test-does-not-exist
72 abort: file:// URLs can only refer to localhost
72 abort: file:// URLs can only refer to localhost
73 [255]
73 [255]
74
74
75 $ hg pull -q file://../test
75 $ hg pull -q file://../test
76 abort: file:// URLs can only refer to localhost
76 abort: file:// URLs can only refer to localhost
77 [255]
77 [255]
78
78
79 $ hg pull -q file:../test
79 $ hg pull -q file:../test # no-msys
80
80
81 It's tricky to make file:// URLs working on every platform with
81 It's tricky to make file:// URLs working on every platform with
82 regular shell commands.
82 regular shell commands.
83
83
84 $ URL=`$PYTHON -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
84 $ URL=`$PYTHON -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
85 $ hg pull -q "$URL"
85 $ hg pull -q "$URL"
86 abort: file:// URLs can only refer to localhost
86 abort: file:// URLs can only refer to localhost
87 [255]
87 [255]
88
88
89 $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
89 $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
90 $ hg pull -q "$URL"
90 $ hg pull -q "$URL"
91
91
92 $ cd ..
92 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now