##// END OF EJS Templates
check-code: disallow use of dict(key=value) construction...
Augie Fackler -
r20688:a61ed1c2 default
parent child Browse files
Show More
@@ -1,560 +1,562
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'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
97 (r'(^| )wc[^|]*$\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 'hg pull -q file:../test'), # 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 .*/.*[^)]$', winglobmsg),
162 (r'^ reverting .*/.*[^)]$', 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 ],
171 ],
172 # warnings
172 # warnings
173 [
173 [
174 (r'^ [^*?/\n]* \(glob\)$',
174 (r'^ [^*?/\n]* \(glob\)$',
175 "glob match with no glob character (?*/)"),
175 "glob match with no glob character (?*/)"),
176 ]
176 ]
177 ]
177 ]
178
178
179 for i in [0, 1]:
179 for i in [0, 1]:
180 for p, m in testpats[i]:
180 for p, m in testpats[i]:
181 if p.startswith(r'^'):
181 if p.startswith(r'^'):
182 p = r"^ [$>] (%s)" % p[1:]
182 p = r"^ [$>] (%s)" % p[1:]
183 else:
183 else:
184 p = r"^ [$>] .*(%s)" % p
184 p = r"^ [$>] .*(%s)" % p
185 utestpats[i].append((p, m))
185 utestpats[i].append((p, m))
186
186
187 utestfilters = [
187 utestfilters = [
188 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
188 (r"<<(\S+)((.|\n)*?\n > \1)", rephere),
189 (r"( *)(#([^\n]*\S)?)", repcomment),
189 (r"( *)(#([^\n]*\S)?)", repcomment),
190 ]
190 ]
191
191
192 pypats = [
192 pypats = [
193 [
193 [
194 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
194 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
195 "tuple parameter unpacking not available in Python 3+"),
195 "tuple parameter unpacking not available in Python 3+"),
196 (r'lambda\s*\(.*,.*\)',
196 (r'lambda\s*\(.*,.*\)',
197 "tuple parameter unpacking not available in Python 3+"),
197 "tuple parameter unpacking not available in Python 3+"),
198 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
198 (r'import (.+,[^.]+\.[^.]+|[^.]+\.[^.]+,)',
199 '2to3 can\'t always rewrite "import qux, foo.bar", '
199 '2to3 can\'t always rewrite "import qux, foo.bar", '
200 'use "import foo.bar" on its own line instead.'),
200 'use "import foo.bar" on its own line instead.'),
201 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
201 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
202 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
202 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
203 (r'dict\(.*=', 'dict() is different in Py2 and 3 and is slower than {}',
204 'dict-from-generator'),
203 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
205 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
204 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
206 (r'\s<>\s', '<> operator is not available in Python 3+, use !='),
205 (r'^\s*\t', "don't use tabs"),
207 (r'^\s*\t', "don't use tabs"),
206 (r'\S;\s*\n', "semicolon"),
208 (r'\S;\s*\n', "semicolon"),
207 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
209 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
208 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
210 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
209 (r'(\w|\)),\w', "missing whitespace after ,"),
211 (r'(\w|\)),\w', "missing whitespace after ,"),
210 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
212 (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
211 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
213 (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
212 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
214 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
213 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
215 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Python 2.4'),
214 (r'(?<!def)(\s+|^|\()next\(.+\)',
216 (r'(?<!def)(\s+|^|\()next\(.+\)',
215 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
217 'no next(foo) in Python 2.4 and 2.5, use foo.next() instead'),
216 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
218 (r'(\s+)try:\n((?:\n|\1\s.*\n)*?)\1\s*yield\b.*?'
217 r'((?:\n|\1\s.*\n)+?)\1finally:',
219 r'((?:\n|\1\s.*\n)+?)\1finally:',
218 'no yield inside try/finally in Python 2.4'),
220 'no yield inside try/finally in Python 2.4'),
219 (r'.{81}', "line too long"),
221 (r'.{81}', "line too long"),
220 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
222 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
221 (r'[^\n]\Z', "no trailing newline"),
223 (r'[^\n]\Z', "no trailing newline"),
222 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
224 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
223 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
225 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
224 # "don't use underbars in identifiers"),
226 # "don't use underbars in identifiers"),
225 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
227 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
226 "don't use camelcase in identifiers"),
228 "don't use camelcase in identifiers"),
227 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
229 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
228 "linebreak after :"),
230 "linebreak after :"),
229 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
231 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
230 (r'class\s[^( \n]+\(\):',
232 (r'class\s[^( \n]+\(\):',
231 "class foo() not available in Python 2.4, use class foo(object)"),
233 "class foo() not available in Python 2.4, use class foo(object)"),
232 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
234 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
233 "Python keyword is not a function"),
235 "Python keyword is not a function"),
234 (r',]', "unneeded trailing ',' in list"),
236 (r',]', "unneeded trailing ',' in list"),
235 # (r'class\s[A-Z][^\(]*\((?!Exception)',
237 # (r'class\s[A-Z][^\(]*\((?!Exception)',
236 # "don't capitalize non-exception classes"),
238 # "don't capitalize non-exception classes"),
237 # (r'in range\(', "use xrange"),
239 # (r'in range\(', "use xrange"),
238 # (r'^\s*print\s+', "avoid using print in core and extensions"),
240 # (r'^\s*print\s+', "avoid using print in core and extensions"),
239 (r'[\x80-\xff]', "non-ASCII character literal"),
241 (r'[\x80-\xff]', "non-ASCII character literal"),
240 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
242 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
241 (r'^\s*with\s+', "with not available in Python 2.4"),
243 (r'^\s*with\s+', "with not available in Python 2.4"),
242 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
244 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
243 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
245 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
244 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
246 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
245 (r'(?<!def)\s+(any|all|format)\(',
247 (r'(?<!def)\s+(any|all|format)\(',
246 "any/all/format not available in Python 2.4", 'no-py24'),
248 "any/all/format not available in Python 2.4", 'no-py24'),
247 (r'(?<!def)\s+(callable)\(',
249 (r'(?<!def)\s+(callable)\(',
248 "callable not available in Python 3, use getattr(f, '__call__', None)"),
250 "callable not available in Python 3, use getattr(f, '__call__', None)"),
249 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
251 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
250 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
252 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
251 "gratuitous whitespace after Python keyword"),
253 "gratuitous whitespace after Python keyword"),
252 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
254 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
253 # (r'\s\s=', "gratuitous whitespace before ="),
255 # (r'\s\s=', "gratuitous whitespace before ="),
254 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
256 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
255 "missing whitespace around operator"),
257 "missing whitespace around operator"),
256 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
258 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\s',
257 "missing whitespace around operator"),
259 "missing whitespace around operator"),
258 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
260 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=|%=)\S',
259 "missing whitespace around operator"),
261 "missing whitespace around operator"),
260 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
262 (r'[^^+=*/!<>&| %-](\s=|=\s)[^= ]',
261 "wrong whitespace around ="),
263 "wrong whitespace around ="),
262 (r'\([^()]*( =[^=]|[^<>!=]= )',
264 (r'\([^()]*( =[^=]|[^<>!=]= )',
263 "no whitespace around = for named parameters"),
265 "no whitespace around = for named parameters"),
264 (r'raise Exception', "don't raise generic exceptions"),
266 (r'raise Exception', "don't raise generic exceptions"),
265 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
267 (r'raise [^,(]+, (\([^\)]+\)|[^,\(\)]+)$',
266 "don't use old-style two-argument raise, use Exception(message)"),
268 "don't use old-style two-argument raise, use Exception(message)"),
267 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
269 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
268 (r' [=!]=\s+(True|False|None)',
270 (r' [=!]=\s+(True|False|None)',
269 "comparison with singleton, use 'is' or 'is not' instead"),
271 "comparison with singleton, use 'is' or 'is not' instead"),
270 (r'^\s*(while|if) [01]:',
272 (r'^\s*(while|if) [01]:',
271 "use True/False for constant Boolean expression"),
273 "use True/False for constant Boolean expression"),
272 (r'(?:(?<!def)\s+|\()hasattr',
274 (r'(?:(?<!def)\s+|\()hasattr',
273 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
275 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
274 (r'opener\([^)]*\).read\(',
276 (r'opener\([^)]*\).read\(',
275 "use opener.read() instead"),
277 "use opener.read() instead"),
276 (r'BaseException', 'not in Python 2.4, use Exception'),
278 (r'BaseException', 'not in Python 2.4, use Exception'),
277 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
279 (r'os\.path\.relpath', 'os.path.relpath is not in Python 2.5'),
278 (r'opener\([^)]*\).write\(',
280 (r'opener\([^)]*\).write\(',
279 "use opener.write() instead"),
281 "use opener.write() instead"),
280 (r'[\s\(](open|file)\([^)]*\)\.read\(',
282 (r'[\s\(](open|file)\([^)]*\)\.read\(',
281 "use util.readfile() instead"),
283 "use util.readfile() instead"),
282 (r'[\s\(](open|file)\([^)]*\)\.write\(',
284 (r'[\s\(](open|file)\([^)]*\)\.write\(',
283 "use util.writefile() instead"),
285 "use util.writefile() instead"),
284 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
286 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
285 "always assign an opened file to a variable, and close it afterwards"),
287 "always assign an opened file to a variable, and close it afterwards"),
286 (r'[\s\(](open|file)\([^)]*\)\.',
288 (r'[\s\(](open|file)\([^)]*\)\.',
287 "always assign an opened file to a variable, and close it afterwards"),
289 "always assign an opened file to a variable, and close it afterwards"),
288 (r'(?i)descendent', "the proper spelling is descendAnt"),
290 (r'(?i)descendent', "the proper spelling is descendAnt"),
289 (r'\.debug\(\_', "don't mark debug messages for translation"),
291 (r'\.debug\(\_', "don't mark debug messages for translation"),
290 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
292 (r'\.strip\(\)\.split\(\)', "no need to strip before splitting"),
291 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
293 (r'^\s*except\s*:', "naked except clause", r'#.*re-raises'),
292 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
294 (r':\n( )*( ){1,3}[^ ]', "must indent 4 spaces"),
293 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
295 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
294 "missing _() in ui message (use () to hide false-positives)"),
296 "missing _() in ui message (use () to hide false-positives)"),
295 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
297 (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
296 ],
298 ],
297 # warnings
299 # warnings
298 [
300 [
299 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
301 (r'(^| )pp +xxxxqq[ \n][^\n]', "add two newlines after '.. note::'"),
300 ]
302 ]
301 ]
303 ]
302
304
303 pyfilters = [
305 pyfilters = [
304 (r"""(?msx)(?P<comment>\#.*?$)|
306 (r"""(?msx)(?P<comment>\#.*?$)|
305 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
307 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
306 (?P<text>(([^\\]|\\.)*?))
308 (?P<text>(([^\\]|\\.)*?))
307 (?P=quote))""", reppython),
309 (?P=quote))""", reppython),
308 ]
310 ]
309
311
310 txtfilters = []
312 txtfilters = []
311
313
312 txtpats = [
314 txtpats = [
313 [
315 [
314 ('\s$', 'trailing whitespace'),
316 ('\s$', 'trailing whitespace'),
315 ('.. note::[ \n][^\n]', 'add two newlines after note::')
317 ('.. note::[ \n][^\n]', 'add two newlines after note::')
316 ],
318 ],
317 []
319 []
318 ]
320 ]
319
321
320 cpats = [
322 cpats = [
321 [
323 [
322 (r'//', "don't use //-style comments"),
324 (r'//', "don't use //-style comments"),
323 (r'^ ', "don't use spaces to indent"),
325 (r'^ ', "don't use spaces to indent"),
324 (r'\S\t', "don't use tabs except for indent"),
326 (r'\S\t', "don't use tabs except for indent"),
325 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
327 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
326 (r'.{81}', "line too long"),
328 (r'.{81}', "line too long"),
327 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
329 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
328 (r'return\(', "return is not a function"),
330 (r'return\(', "return is not a function"),
329 (r' ;', "no space before ;"),
331 (r' ;', "no space before ;"),
330 (r'[)][{]', "space between ) and {"),
332 (r'[)][{]', "space between ) and {"),
331 (r'\w+\* \w+', "use int *foo, not int* foo"),
333 (r'\w+\* \w+', "use int *foo, not int* foo"),
332 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
334 (r'\W\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
333 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
335 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
334 (r'\w,\w', "missing whitespace after ,"),
336 (r'\w,\w', "missing whitespace after ,"),
335 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
337 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
336 (r'^#\s+\w', "use #foo, not # foo"),
338 (r'^#\s+\w', "use #foo, not # foo"),
337 (r'[^\n]\Z', "no trailing newline"),
339 (r'[^\n]\Z', "no trailing newline"),
338 (r'^\s*#import\b', "use only #include in standard C code"),
340 (r'^\s*#import\b', "use only #include in standard C code"),
339 ],
341 ],
340 # warnings
342 # warnings
341 []
343 []
342 ]
344 ]
343
345
344 cfilters = [
346 cfilters = [
345 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
347 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
346 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
348 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
347 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
349 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
348 (r'(\()([^)]+\))', repcallspaces),
350 (r'(\()([^)]+\))', repcallspaces),
349 ]
351 ]
350
352
351 inutilpats = [
353 inutilpats = [
352 [
354 [
353 (r'\bui\.', "don't use ui in util"),
355 (r'\bui\.', "don't use ui in util"),
354 ],
356 ],
355 # warnings
357 # warnings
356 []
358 []
357 ]
359 ]
358
360
359 inrevlogpats = [
361 inrevlogpats = [
360 [
362 [
361 (r'\brepo\.', "don't use repo in revlog"),
363 (r'\brepo\.', "don't use repo in revlog"),
362 ],
364 ],
363 # warnings
365 # warnings
364 []
366 []
365 ]
367 ]
366
368
367 checks = [
369 checks = [
368 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
370 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
369 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
371 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
370 ('c', r'.*\.[ch]$', cfilters, cpats),
372 ('c', r'.*\.[ch]$', cfilters, cpats),
371 ('unified test', r'.*\.t$', utestfilters, utestpats),
373 ('unified test', r'.*\.t$', utestfilters, utestpats),
372 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
374 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
373 inrevlogpats),
375 inrevlogpats),
374 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
376 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
375 inutilpats),
377 inutilpats),
376 ('txt', r'.*\.txt$', txtfilters, txtpats),
378 ('txt', r'.*\.txt$', txtfilters, txtpats),
377 ]
379 ]
378
380
379 def _preparepats():
381 def _preparepats():
380 for c in checks:
382 for c in checks:
381 failandwarn = c[-1]
383 failandwarn = c[-1]
382 for pats in failandwarn:
384 for pats in failandwarn:
383 for i, pseq in enumerate(pats):
385 for i, pseq in enumerate(pats):
384 # fix-up regexes for multi-line searches
386 # fix-up regexes for multi-line searches
385 p = pseq[0]
387 p = pseq[0]
386 # \s doesn't match \n
388 # \s doesn't match \n
387 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
389 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
388 # [^...] doesn't match newline
390 # [^...] doesn't match newline
389 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
391 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
390
392
391 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
393 pats[i] = (re.compile(p, re.MULTILINE),) + pseq[1:]
392 filters = c[2]
394 filters = c[2]
393 for i, flt in enumerate(filters):
395 for i, flt in enumerate(filters):
394 filters[i] = re.compile(flt[0]), flt[1]
396 filters[i] = re.compile(flt[0]), flt[1]
395 _preparepats()
397 _preparepats()
396
398
397 class norepeatlogger(object):
399 class norepeatlogger(object):
398 def __init__(self):
400 def __init__(self):
399 self._lastseen = None
401 self._lastseen = None
400
402
401 def log(self, fname, lineno, line, msg, blame):
403 def log(self, fname, lineno, line, msg, blame):
402 """print error related a to given line of a given file.
404 """print error related a to given line of a given file.
403
405
404 The faulty line will also be printed but only once in the case
406 The faulty line will also be printed but only once in the case
405 of multiple errors.
407 of multiple errors.
406
408
407 :fname: filename
409 :fname: filename
408 :lineno: line number
410 :lineno: line number
409 :line: actual content of the line
411 :line: actual content of the line
410 :msg: error message
412 :msg: error message
411 """
413 """
412 msgid = fname, lineno, line
414 msgid = fname, lineno, line
413 if msgid != self._lastseen:
415 if msgid != self._lastseen:
414 if blame:
416 if blame:
415 print "%s:%d (%s):" % (fname, lineno, blame)
417 print "%s:%d (%s):" % (fname, lineno, blame)
416 else:
418 else:
417 print "%s:%d:" % (fname, lineno)
419 print "%s:%d:" % (fname, lineno)
418 print " > %s" % line
420 print " > %s" % line
419 self._lastseen = msgid
421 self._lastseen = msgid
420 print " " + msg
422 print " " + msg
421
423
422 _defaultlogger = norepeatlogger()
424 _defaultlogger = norepeatlogger()
423
425
424 def getblame(f):
426 def getblame(f):
425 lines = []
427 lines = []
426 for l in os.popen('hg annotate -un %s' % f):
428 for l in os.popen('hg annotate -un %s' % f):
427 start, line = l.split(':', 1)
429 start, line = l.split(':', 1)
428 user, rev = start.split()
430 user, rev = start.split()
429 lines.append((line[1:-1], user, rev))
431 lines.append((line[1:-1], user, rev))
430 return lines
432 return lines
431
433
432 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
434 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
433 blame=False, debug=False, lineno=True):
435 blame=False, debug=False, lineno=True):
434 """checks style and portability of a given file
436 """checks style and portability of a given file
435
437
436 :f: filepath
438 :f: filepath
437 :logfunc: function used to report error
439 :logfunc: function used to report error
438 logfunc(filename, linenumber, linecontent, errormessage)
440 logfunc(filename, linenumber, linecontent, errormessage)
439 :maxerr: number of error to display before aborting.
441 :maxerr: number of error to display before aborting.
440 Set to false (default) to report all errors
442 Set to false (default) to report all errors
441
443
442 return True if no error is found, False otherwise.
444 return True if no error is found, False otherwise.
443 """
445 """
444 blamecache = None
446 blamecache = None
445 result = True
447 result = True
446 for name, match, filters, pats in checks:
448 for name, match, filters, pats in checks:
447 if debug:
449 if debug:
448 print name, f
450 print name, f
449 fc = 0
451 fc = 0
450 if not re.match(match, f):
452 if not re.match(match, f):
451 if debug:
453 if debug:
452 print "Skipping %s for %s it doesn't match %s" % (
454 print "Skipping %s for %s it doesn't match %s" % (
453 name, match, f)
455 name, match, f)
454 continue
456 continue
455 try:
457 try:
456 fp = open(f)
458 fp = open(f)
457 except IOError, e:
459 except IOError, e:
458 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
460 print "Skipping %s, %s" % (f, str(e).split(':', 1)[0])
459 continue
461 continue
460 pre = post = fp.read()
462 pre = post = fp.read()
461 fp.close()
463 fp.close()
462 if "no-" "check-code" in pre:
464 if "no-" "check-code" in pre:
463 print "Skipping %s it has no-" "check-code" % f
465 print "Skipping %s it has no-" "check-code" % f
464 return "Skip" # skip checking this file
466 return "Skip" # skip checking this file
465 for p, r in filters:
467 for p, r in filters:
466 post = re.sub(p, r, post)
468 post = re.sub(p, r, post)
467 nerrs = len(pats[0]) # nerr elements are errors
469 nerrs = len(pats[0]) # nerr elements are errors
468 if warnings:
470 if warnings:
469 pats = pats[0] + pats[1]
471 pats = pats[0] + pats[1]
470 else:
472 else:
471 pats = pats[0]
473 pats = pats[0]
472 # print post # uncomment to show filtered version
474 # print post # uncomment to show filtered version
473
475
474 if debug:
476 if debug:
475 print "Checking %s for %s" % (name, f)
477 print "Checking %s for %s" % (name, f)
476
478
477 prelines = None
479 prelines = None
478 errors = []
480 errors = []
479 for i, pat in enumerate(pats):
481 for i, pat in enumerate(pats):
480 if len(pat) == 3:
482 if len(pat) == 3:
481 p, msg, ignore = pat
483 p, msg, ignore = pat
482 else:
484 else:
483 p, msg = pat
485 p, msg = pat
484 ignore = None
486 ignore = None
485 if i >= nerrs:
487 if i >= nerrs:
486 msg = "warning: " + msg
488 msg = "warning: " + msg
487
489
488 pos = 0
490 pos = 0
489 n = 0
491 n = 0
490 for m in p.finditer(post):
492 for m in p.finditer(post):
491 if prelines is None:
493 if prelines is None:
492 prelines = pre.splitlines()
494 prelines = pre.splitlines()
493 postlines = post.splitlines(True)
495 postlines = post.splitlines(True)
494
496
495 start = m.start()
497 start = m.start()
496 while n < len(postlines):
498 while n < len(postlines):
497 step = len(postlines[n])
499 step = len(postlines[n])
498 if pos + step > start:
500 if pos + step > start:
499 break
501 break
500 pos += step
502 pos += step
501 n += 1
503 n += 1
502 l = prelines[n]
504 l = prelines[n]
503
505
504 if ignore and re.search(ignore, l, re.MULTILINE):
506 if ignore and re.search(ignore, l, re.MULTILINE):
505 if debug:
507 if debug:
506 print "Skipping %s for %s:%s (ignore pattern)" % (
508 print "Skipping %s for %s:%s (ignore pattern)" % (
507 name, f, n)
509 name, f, n)
508 continue
510 continue
509 bd = ""
511 bd = ""
510 if blame:
512 if blame:
511 bd = 'working directory'
513 bd = 'working directory'
512 if not blamecache:
514 if not blamecache:
513 blamecache = getblame(f)
515 blamecache = getblame(f)
514 if n < len(blamecache):
516 if n < len(blamecache):
515 bl, bu, br = blamecache[n]
517 bl, bu, br = blamecache[n]
516 if bl == l:
518 if bl == l:
517 bd = '%s@%s' % (bu, br)
519 bd = '%s@%s' % (bu, br)
518
520
519 errors.append((f, lineno and n + 1, l, msg, bd))
521 errors.append((f, lineno and n + 1, l, msg, bd))
520 result = False
522 result = False
521
523
522 errors.sort()
524 errors.sort()
523 for e in errors:
525 for e in errors:
524 logfunc(*e)
526 logfunc(*e)
525 fc += 1
527 fc += 1
526 if maxerr and fc >= maxerr:
528 if maxerr and fc >= maxerr:
527 print " (too many errors, giving up)"
529 print " (too many errors, giving up)"
528 break
530 break
529
531
530 return result
532 return result
531
533
532 if __name__ == "__main__":
534 if __name__ == "__main__":
533 parser = optparse.OptionParser("%prog [options] [files]")
535 parser = optparse.OptionParser("%prog [options] [files]")
534 parser.add_option("-w", "--warnings", action="store_true",
536 parser.add_option("-w", "--warnings", action="store_true",
535 help="include warning-level checks")
537 help="include warning-level checks")
536 parser.add_option("-p", "--per-file", type="int",
538 parser.add_option("-p", "--per-file", type="int",
537 help="max warnings per file")
539 help="max warnings per file")
538 parser.add_option("-b", "--blame", action="store_true",
540 parser.add_option("-b", "--blame", action="store_true",
539 help="use annotate to generate blame info")
541 help="use annotate to generate blame info")
540 parser.add_option("", "--debug", action="store_true",
542 parser.add_option("", "--debug", action="store_true",
541 help="show debug information")
543 help="show debug information")
542 parser.add_option("", "--nolineno", action="store_false",
544 parser.add_option("", "--nolineno", action="store_false",
543 dest='lineno', help="don't show line numbers")
545 dest='lineno', help="don't show line numbers")
544
546
545 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
547 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
546 lineno=True)
548 lineno=True)
547 (options, args) = parser.parse_args()
549 (options, args) = parser.parse_args()
548
550
549 if len(args) == 0:
551 if len(args) == 0:
550 check = glob.glob("*")
552 check = glob.glob("*")
551 else:
553 else:
552 check = args
554 check = args
553
555
554 ret = 0
556 ret = 0
555 for f in check:
557 for f in check:
556 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
558 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
557 blame=options.blame, debug=options.debug,
559 blame=options.blame, debug=options.debug,
558 lineno=options.lineno):
560 lineno=options.lineno):
559 ret = 1
561 ret = 1
560 sys.exit(ret)
562 sys.exit(ret)
@@ -1,100 +1,100
1 # Copyright 2010-2011 Fog Creek Software
1 # Copyright 2010-2011 Fog Creek Software
2 # Copyright 2010-2011 Unity Technologies
2 # Copyright 2010-2011 Unity Technologies
3 #
3 #
4 # This software may be used and distributed according to the terms of the
4 # This software may be used and distributed according to the terms of the
5 # GNU General Public License version 2 or any later version.
5 # GNU General Public License version 2 or any later version.
6
6
7 '''remote largefile store; the base class for wirestore'''
7 '''remote largefile store; the base class for wirestore'''
8
8
9 import urllib2
9 import urllib2
10
10
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.wireproto import remotebatch
13 from mercurial.wireproto import remotebatch
14
14
15 import lfutil
15 import lfutil
16 import basestore
16 import basestore
17
17
18 class remotestore(basestore.basestore):
18 class remotestore(basestore.basestore):
19 '''a largefile store accessed over a network'''
19 '''a largefile store accessed over a network'''
20 def __init__(self, ui, repo, url):
20 def __init__(self, ui, repo, url):
21 super(remotestore, self).__init__(ui, repo, url)
21 super(remotestore, self).__init__(ui, repo, url)
22
22
23 def put(self, source, hash):
23 def put(self, source, hash):
24 if self.sendfile(source, hash):
24 if self.sendfile(source, hash):
25 raise util.Abort(
25 raise util.Abort(
26 _('remotestore: could not put %s to remote store %s')
26 _('remotestore: could not put %s to remote store %s')
27 % (source, util.hidepassword(self.url)))
27 % (source, util.hidepassword(self.url)))
28 self.ui.debug(
28 self.ui.debug(
29 _('remotestore: put %s to remote store %s\n')
29 _('remotestore: put %s to remote store %s\n')
30 % (source, util.hidepassword(self.url)))
30 % (source, util.hidepassword(self.url)))
31
31
32 def exists(self, hashes):
32 def exists(self, hashes):
33 return dict((h, s == 0) for (h, s) in self._stat(hashes).iteritems())
33 return dict((h, s == 0) for (h, s) in # dict-from-generator
34 self._stat(hashes).iteritems())
34
35
35 def sendfile(self, filename, hash):
36 def sendfile(self, filename, hash):
36 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
37 self.ui.debug('remotestore: sendfile(%s, %s)\n' % (filename, hash))
37 fd = None
38 fd = None
38 try:
39 try:
39 try:
40 try:
40 fd = lfutil.httpsendfile(self.ui, filename)
41 fd = lfutil.httpsendfile(self.ui, filename)
41 except IOError, e:
42 except IOError, e:
42 raise util.Abort(
43 raise util.Abort(
43 _('remotestore: could not open file %s: %s')
44 _('remotestore: could not open file %s: %s')
44 % (filename, str(e)))
45 % (filename, str(e)))
45 return self._put(hash, fd)
46 return self._put(hash, fd)
46 finally:
47 finally:
47 if fd:
48 if fd:
48 fd.close()
49 fd.close()
49
50
50 def _getfile(self, tmpfile, filename, hash):
51 def _getfile(self, tmpfile, filename, hash):
51 try:
52 try:
52 chunks = self._get(hash)
53 chunks = self._get(hash)
53 except urllib2.HTTPError, e:
54 except urllib2.HTTPError, e:
54 # 401s get converted to util.Aborts; everything else is fine being
55 # 401s get converted to util.Aborts; everything else is fine being
55 # turned into a StoreError
56 # turned into a StoreError
56 raise basestore.StoreError(filename, hash, self.url, str(e))
57 raise basestore.StoreError(filename, hash, self.url, str(e))
57 except urllib2.URLError, e:
58 except urllib2.URLError, e:
58 # This usually indicates a connection problem, so don't
59 # This usually indicates a connection problem, so don't
59 # keep trying with the other files... they will probably
60 # keep trying with the other files... they will probably
60 # all fail too.
61 # all fail too.
61 raise util.Abort('%s: %s' %
62 raise util.Abort('%s: %s' %
62 (util.hidepassword(self.url), e.reason))
63 (util.hidepassword(self.url), e.reason))
63 except IOError, e:
64 except IOError, e:
64 raise basestore.StoreError(filename, hash, self.url, str(e))
65 raise basestore.StoreError(filename, hash, self.url, str(e))
65
66
66 return lfutil.copyandhash(chunks, tmpfile)
67 return lfutil.copyandhash(chunks, tmpfile)
67
68
68 def _verifyfile(self, cctx, cset, contents, standin, verified):
69 def _verifyfile(self, cctx, cset, contents, standin, verified):
69 filename = lfutil.splitstandin(standin)
70 filename = lfutil.splitstandin(standin)
70 if not filename:
71 if not filename:
71 return False
72 return False
72 fctx = cctx[standin]
73 fctx = cctx[standin]
73 key = (filename, fctx.filenode())
74 key = (filename, fctx.filenode())
74 if key in verified:
75 if key in verified:
75 return False
76 return False
76
77
77 verified.add(key)
78 verified.add(key)
78
79
79 expecthash = fctx.data()[0:40]
80 expecthash = fctx.data()[0:40]
80 stat = self._stat([expecthash])[expecthash]
81 stat = self._stat([expecthash])[expecthash]
81 if not stat:
82 if not stat:
82 return False
83 return False
83 elif stat == 1:
84 elif stat == 1:
84 self.ui.warn(
85 self.ui.warn(
85 _('changeset %s: %s: contents differ\n')
86 _('changeset %s: %s: contents differ\n')
86 % (cset, filename))
87 % (cset, filename))
87 return True # failed
88 return True # failed
88 elif stat == 2:
89 elif stat == 2:
89 self.ui.warn(
90 self.ui.warn(
90 _('changeset %s: %s missing\n')
91 _('changeset %s: %s missing\n')
91 % (cset, filename))
92 % (cset, filename))
92 return True # failed
93 return True # failed
93 else:
94 else:
94 raise RuntimeError('verify failed: unexpected response from '
95 raise RuntimeError('verify failed: unexpected response from '
95 'statlfile (%r)' % stat)
96 'statlfile (%r)' % stat)
96
97
97 def batch(self):
98 def batch(self):
98 '''Support for remote batching.'''
99 '''Support for remote batching.'''
99 return remotebatch(self)
100 return remotebatch(self)
100
@@ -1,253 +1,257
1 $ cat > correct.py <<EOF
1 $ cat > correct.py <<EOF
2 > def toto(arg1, arg2):
2 > def toto(arg1, arg2):
3 > del arg2
3 > del arg2
4 > return (5 + 6, 9)
4 > return (5 + 6, 9)
5 > EOF
5 > EOF
6 $ cat > wrong.py <<EOF
6 $ cat > wrong.py <<EOF
7 > def toto( arg1, arg2):
7 > def toto( arg1, arg2):
8 > del(arg2)
8 > del(arg2)
9 > return ( 5+6, 9)
9 > return ( 5+6, 9)
10 > EOF
10 > EOF
11 $ cat > quote.py <<EOF
11 $ cat > quote.py <<EOF
12 > # let's use quote in comments
12 > # let's use quote in comments
13 > (''' ( 4x5 )
13 > (''' ( 4x5 )
14 > but """\\''' and finally''',
14 > but """\\''' and finally''',
15 > """let's fool checkpatch""", '1+2',
15 > """let's fool checkpatch""", '1+2',
16 > '"""', 42+1, """and
16 > '"""', 42+1, """and
17 > ( 4-1 ) """, "( 1+1 )\" and ")
17 > ( 4-1 ) """, "( 1+1 )\" and ")
18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
18 > a, '\\\\\\\\', "\\\\\\" x-2", "c-1"
19 > EOF
19 > EOF
20 $ cat > non-py24.py <<EOF
20 $ cat > non-py24.py <<EOF
21 > # Using builtins that does not exist in Python 2.4
21 > # Using builtins that does not exist in Python 2.4
22 > if any():
22 > if any():
23 > x = all()
23 > x = all()
24 > y = format(x)
24 > y = format(x)
25 > # next(generator) is new in 2.6
25 > # next(generator) is new in 2.6
26 > z = next(x)
26 > z = next(x)
27 > # but generator.next() is okay
27 > # but generator.next() is okay
28 > x.next()
28 > x.next()
29 > # and we can make our own next
29 > # and we can make our own next
30 > def next(stuff):
30 > def next(stuff):
31 > pass
31 > pass
32 >
32 >
33 > # Do not complain about our own definition
33 > # Do not complain about our own definition
34 > def any(x):
34 > def any(x):
35 > pass
35 > pass
36 >
36 >
37 > # try/except/finally block does not exist in Python 2.4
37 > # try/except/finally block does not exist in Python 2.4
38 > try:
38 > try:
39 > pass
39 > pass
40 > except StandardError, inst:
40 > except StandardError, inst:
41 > pass
41 > pass
42 > finally:
42 > finally:
43 > pass
43 > pass
44 >
44 >
45 > # nested try/finally+try/except is allowed
45 > # nested try/finally+try/except is allowed
46 > try:
46 > try:
47 > try:
47 > try:
48 > pass
48 > pass
49 > except StandardError, inst:
49 > except StandardError, inst:
50 > pass
50 > pass
51 > finally:
51 > finally:
52 > pass
52 > pass
53 >
53 >
54 > # yield inside a try/finally block is not allowed in Python 2.4
54 > # yield inside a try/finally block is not allowed in Python 2.4
55 > try:
55 > try:
56 > pass
56 > pass
57 > yield 1
57 > yield 1
58 > finally:
58 > finally:
59 > pass
59 > pass
60 > try:
60 > try:
61 > yield
61 > yield
62 > pass
62 > pass
63 > finally:
63 > finally:
64 > pass
64 > pass
65 >
65 >
66 > EOF
66 > EOF
67 $ cat > classstyle.py <<EOF
67 $ cat > classstyle.py <<EOF
68 > class newstyle_class(object):
68 > class newstyle_class(object):
69 > pass
69 > pass
70 >
70 >
71 > class oldstyle_class:
71 > class oldstyle_class:
72 > pass
72 > pass
73 >
73 >
74 > class empty():
74 > class empty():
75 > pass
75 > pass
76 >
76 >
77 > no_class = 1:
77 > no_class = 1:
78 > pass
78 > pass
79 > EOF
79 > EOF
80 $ check_code="$TESTDIR"/../contrib/check-code.py
80 $ check_code="$TESTDIR"/../contrib/check-code.py
81 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
81 $ "$check_code" ./wrong.py ./correct.py ./quote.py ./non-py24.py ./classstyle.py
82 ./wrong.py:1:
82 ./wrong.py:1:
83 > def toto( arg1, arg2):
83 > def toto( arg1, arg2):
84 gratuitous whitespace in () or []
84 gratuitous whitespace in () or []
85 ./wrong.py:2:
85 ./wrong.py:2:
86 > del(arg2)
86 > del(arg2)
87 Python keyword is not a function
87 Python keyword is not a function
88 ./wrong.py:3:
88 ./wrong.py:3:
89 > return ( 5+6, 9)
89 > return ( 5+6, 9)
90 gratuitous whitespace in () or []
90 gratuitous whitespace in () or []
91 missing whitespace in expression
91 missing whitespace in expression
92 ./quote.py:5:
92 ./quote.py:5:
93 > '"""', 42+1, """and
93 > '"""', 42+1, """and
94 missing whitespace in expression
94 missing whitespace in expression
95 ./non-py24.py:2:
95 ./non-py24.py:2:
96 > if any():
96 > if any():
97 any/all/format not available in Python 2.4
97 any/all/format not available in Python 2.4
98 ./non-py24.py:3:
98 ./non-py24.py:3:
99 > x = all()
99 > x = all()
100 any/all/format not available in Python 2.4
100 any/all/format not available in Python 2.4
101 ./non-py24.py:4:
101 ./non-py24.py:4:
102 > y = format(x)
102 > y = format(x)
103 any/all/format not available in Python 2.4
103 any/all/format not available in Python 2.4
104 ./non-py24.py:6:
104 ./non-py24.py:6:
105 > z = next(x)
105 > z = next(x)
106 no next(foo) in Python 2.4 and 2.5, use foo.next() instead
106 no next(foo) in Python 2.4 and 2.5, use foo.next() instead
107 ./non-py24.py:18:
107 ./non-py24.py:18:
108 > try:
108 > try:
109 no try/except/finally in Python 2.4
109 no try/except/finally in Python 2.4
110 ./non-py24.py:35:
110 ./non-py24.py:35:
111 > try:
111 > try:
112 no yield inside try/finally in Python 2.4
112 no yield inside try/finally in Python 2.4
113 ./non-py24.py:40:
113 ./non-py24.py:40:
114 > try:
114 > try:
115 no yield inside try/finally in Python 2.4
115 no yield inside try/finally in Python 2.4
116 ./classstyle.py:4:
116 ./classstyle.py:4:
117 > class oldstyle_class:
117 > class oldstyle_class:
118 old-style class, use class foo(object)
118 old-style class, use class foo(object)
119 ./classstyle.py:7:
119 ./classstyle.py:7:
120 > class empty():
120 > class empty():
121 class foo() not available in Python 2.4, use class foo(object)
121 class foo() not available in Python 2.4, use class foo(object)
122 [1]
122 [1]
123 $ cat > python3-compat.py << EOF
123 $ cat > python3-compat.py << EOF
124 > foo <> bar
124 > foo <> bar
125 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
125 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
126 > dict(key=value)
126 > EOF
127 > EOF
127 $ "$check_code" python3-compat.py
128 $ "$check_code" python3-compat.py
128 python3-compat.py:1:
129 python3-compat.py:1:
129 > foo <> bar
130 > foo <> bar
130 <> operator is not available in Python 3+, use !=
131 <> operator is not available in Python 3+, use !=
131 python3-compat.py:2:
132 python3-compat.py:2:
132 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
133 > reduce(lambda a, b: a + b, [1, 2, 3, 4])
133 reduce is not available in Python 3+
134 reduce is not available in Python 3+
135 python3-compat.py:3:
136 > dict(key=value)
137 dict() is different in Py2 and 3 and is slower than {}
134 [1]
138 [1]
135
139
136 $ cat > is-op.py <<EOF
140 $ cat > is-op.py <<EOF
137 > # is-operator comparing number or string literal
141 > # is-operator comparing number or string literal
138 > x = None
142 > x = None
139 > y = x is 'foo'
143 > y = x is 'foo'
140 > y = x is "foo"
144 > y = x is "foo"
141 > y = x is 5346
145 > y = x is 5346
142 > y = x is -6
146 > y = x is -6
143 > y = x is not 'foo'
147 > y = x is not 'foo'
144 > y = x is not "foo"
148 > y = x is not "foo"
145 > y = x is not 5346
149 > y = x is not 5346
146 > y = x is not -6
150 > y = x is not -6
147 > EOF
151 > EOF
148
152
149 $ "$check_code" ./is-op.py
153 $ "$check_code" ./is-op.py
150 ./is-op.py:3:
154 ./is-op.py:3:
151 > y = x is 'foo'
155 > y = x is 'foo'
152 object comparison with literal
156 object comparison with literal
153 ./is-op.py:4:
157 ./is-op.py:4:
154 > y = x is "foo"
158 > y = x is "foo"
155 object comparison with literal
159 object comparison with literal
156 ./is-op.py:5:
160 ./is-op.py:5:
157 > y = x is 5346
161 > y = x is 5346
158 object comparison with literal
162 object comparison with literal
159 ./is-op.py:6:
163 ./is-op.py:6:
160 > y = x is -6
164 > y = x is -6
161 object comparison with literal
165 object comparison with literal
162 ./is-op.py:7:
166 ./is-op.py:7:
163 > y = x is not 'foo'
167 > y = x is not 'foo'
164 object comparison with literal
168 object comparison with literal
165 ./is-op.py:8:
169 ./is-op.py:8:
166 > y = x is not "foo"
170 > y = x is not "foo"
167 object comparison with literal
171 object comparison with literal
168 ./is-op.py:9:
172 ./is-op.py:9:
169 > y = x is not 5346
173 > y = x is not 5346
170 object comparison with literal
174 object comparison with literal
171 ./is-op.py:10:
175 ./is-op.py:10:
172 > y = x is not -6
176 > y = x is not -6
173 object comparison with literal
177 object comparison with literal
174 [1]
178 [1]
175
179
176 $ cat > for-nolineno.py <<EOF
180 $ cat > for-nolineno.py <<EOF
177 > except:
181 > except:
178 > EOF
182 > EOF
179 $ "$check_code" for-nolineno.py --nolineno
183 $ "$check_code" for-nolineno.py --nolineno
180 for-nolineno.py:0:
184 for-nolineno.py:0:
181 > except:
185 > except:
182 naked except clause
186 naked except clause
183 [1]
187 [1]
184
188
185 $ cat > warning.t <<EOF
189 $ cat > warning.t <<EOF
186 > $ function warnonly {
190 > $ function warnonly {
187 > > }
191 > > }
188 > $ diff -N aaa
192 > $ diff -N aaa
189 > $ function onwarn {}
193 > $ function onwarn {}
190 > EOF
194 > EOF
191 $ "$check_code" warning.t
195 $ "$check_code" warning.t
192 $ "$check_code" --warn warning.t
196 $ "$check_code" --warn warning.t
193 warning.t:1:
197 warning.t:1:
194 > $ function warnonly {
198 > $ function warnonly {
195 warning: don't use 'function', use old style
199 warning: don't use 'function', use old style
196 warning.t:3:
200 warning.t:3:
197 > $ diff -N aaa
201 > $ diff -N aaa
198 warning: don't use 'diff -N'
202 warning: don't use 'diff -N'
199 warning.t:4:
203 warning.t:4:
200 > $ function onwarn {}
204 > $ function onwarn {}
201 warning: don't use 'function', use old style
205 warning: don't use 'function', use old style
202 [1]
206 [1]
203 $ cat > raise-format.py <<EOF
207 $ cat > raise-format.py <<EOF
204 > raise SomeException, message
208 > raise SomeException, message
205 > # this next line is okay
209 > # this next line is okay
206 > raise SomeException(arg1, arg2)
210 > raise SomeException(arg1, arg2)
207 > EOF
211 > EOF
208 $ "$check_code" not-existing.py raise-format.py
212 $ "$check_code" not-existing.py raise-format.py
209 Skipping*not-existing.py* (glob)
213 Skipping*not-existing.py* (glob)
210 raise-format.py:1:
214 raise-format.py:1:
211 > raise SomeException, message
215 > raise SomeException, message
212 don't use old-style two-argument raise, use Exception(message)
216 don't use old-style two-argument raise, use Exception(message)
213 [1]
217 [1]
214
218
215 $ cat > rst.py <<EOF
219 $ cat > rst.py <<EOF
216 > """problematic rst text
220 > """problematic rst text
217 >
221 >
218 > .. note::
222 > .. note::
219 > wrong
223 > wrong
220 > """
224 > """
221 >
225 >
222 > '''
226 > '''
223 >
227 >
224 > .. note::
228 > .. note::
225 >
229 >
226 > valid
230 > valid
227 >
231 >
228 > new text
232 > new text
229 >
233 >
230 > .. note::
234 > .. note::
231 >
235 >
232 > also valid
236 > also valid
233 > '''
237 > '''
234 >
238 >
235 > """mixed
239 > """mixed
236 >
240 >
237 > .. note::
241 > .. note::
238 >
242 >
239 > good
243 > good
240 >
244 >
241 > .. note::
245 > .. note::
242 > plus bad
246 > plus bad
243 > """
247 > """
244 > EOF
248 > EOF
245 $ $check_code -w rst.py
249 $ $check_code -w rst.py
246 rst.py:3:
250 rst.py:3:
247 > .. note::
251 > .. note::
248 warning: add two newlines after '.. note::'
252 warning: add two newlines after '.. note::'
249 rst.py:26:
253 rst.py:26:
250 > .. note::
254 > .. note::
251 warning: add two newlines after '.. note::'
255 warning: add two newlines after '.. note::'
252 [1]
256 [1]
253
257
General Comments 0
You need to be logged in to leave comments. Login now