##// END OF EJS Templates
test-revert.t: fix wc check-code false positive
Matt Mackall -
r23134:22e76e37 default
parent child Browse files
Show More
@@ -1,581 +1,581
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'(^| )\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 '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 (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)descendent', "the proper spelling is descendAnt"),
294 (r'(?i)descendent', "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,1197 +1,1197
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ echo 123 > a
3 $ echo 123 > a
4 $ echo 123 > c
4 $ echo 123 > c
5 $ echo 123 > e
5 $ echo 123 > e
6 $ hg add a c e
6 $ hg add a c e
7 $ hg commit -m "first" a c e
7 $ hg commit -m "first" a c e
8
8
9 nothing changed
9 nothing changed
10
10
11 $ hg revert
11 $ hg revert
12 abort: no files or directories specified
12 abort: no files or directories specified
13 (use --all to revert all files)
13 (use --all to revert all files)
14 [255]
14 [255]
15 $ hg revert --all
15 $ hg revert --all
16
16
17 Introduce some changes and revert them
17 Introduce some changes and revert them
18 --------------------------------------
18 --------------------------------------
19
19
20 $ echo 123 > b
20 $ echo 123 > b
21
21
22 $ hg status
22 $ hg status
23 ? b
23 ? b
24 $ echo 12 > c
24 $ echo 12 > c
25
25
26 $ hg status
26 $ hg status
27 M c
27 M c
28 ? b
28 ? b
29 $ hg add b
29 $ hg add b
30
30
31 $ hg status
31 $ hg status
32 M c
32 M c
33 A b
33 A b
34 $ hg rm a
34 $ hg rm a
35
35
36 $ hg status
36 $ hg status
37 M c
37 M c
38 A b
38 A b
39 R a
39 R a
40
40
41 revert removal of a file
41 revert removal of a file
42
42
43 $ hg revert a
43 $ hg revert a
44 $ hg status
44 $ hg status
45 M c
45 M c
46 A b
46 A b
47
47
48 revert addition of a file
48 revert addition of a file
49
49
50 $ hg revert b
50 $ hg revert b
51 $ hg status
51 $ hg status
52 M c
52 M c
53 ? b
53 ? b
54
54
55 revert modification of a file (--no-backup)
55 revert modification of a file (--no-backup)
56
56
57 $ hg revert --no-backup c
57 $ hg revert --no-backup c
58 $ hg status
58 $ hg status
59 ? b
59 ? b
60
60
61 revert deletion (! status) of a added file
61 revert deletion (! status) of a added file
62 ------------------------------------------
62 ------------------------------------------
63
63
64 $ hg add b
64 $ hg add b
65
65
66 $ hg status b
66 $ hg status b
67 A b
67 A b
68 $ rm b
68 $ rm b
69 $ hg status b
69 $ hg status b
70 ! b
70 ! b
71 $ hg revert -v b
71 $ hg revert -v b
72 forgetting b
72 forgetting b
73 $ hg status b
73 $ hg status b
74 b: * (glob)
74 b: * (glob)
75
75
76 $ ls
76 $ ls
77 a
77 a
78 c
78 c
79 e
79 e
80
80
81 Test creation of backup (.orig) files
81 Test creation of backup (.orig) files
82 -------------------------------------
82 -------------------------------------
83
83
84 $ echo z > e
84 $ echo z > e
85 $ hg revert --all -v
85 $ hg revert --all -v
86 saving current version of e as e.orig
86 saving current version of e as e.orig
87 reverting e
87 reverting e
88
88
89 revert on clean file (no change)
89 revert on clean file (no change)
90 --------------------------------
90 --------------------------------
91
91
92 $ hg revert a
92 $ hg revert a
93 no changes needed to a
93 no changes needed to a
94
94
95 revert on an untracked file
95 revert on an untracked file
96 ---------------------------
96 ---------------------------
97
97
98 $ echo q > q
98 $ echo q > q
99 $ hg revert q
99 $ hg revert q
100 file not managed: q
100 file not managed: q
101 $ rm q
101 $ rm q
102
102
103 revert on file that does not exists
103 revert on file that does not exists
104 -----------------------------------
104 -----------------------------------
105
105
106 $ hg revert notfound
106 $ hg revert notfound
107 notfound: no such file in rev 334a9e57682c
107 notfound: no such file in rev 334a9e57682c
108 $ touch d
108 $ touch d
109 $ hg add d
109 $ hg add d
110 $ hg rm a
110 $ hg rm a
111 $ hg commit -m "second"
111 $ hg commit -m "second"
112 $ echo z > z
112 $ echo z > z
113 $ hg add z
113 $ hg add z
114 $ hg st
114 $ hg st
115 A z
115 A z
116 ? e.orig
116 ? e.orig
117
117
118 revert to another revision (--rev)
118 revert to another revision (--rev)
119 ----------------------------------
119 ----------------------------------
120
120
121 $ hg revert --all -r0
121 $ hg revert --all -r0
122 adding a
122 adding a
123 removing d
123 removing d
124 forgetting z
124 forgetting z
125
125
126 revert explicitly to parent (--rev)
126 revert explicitly to parent (--rev)
127 -----------------------------------
127 -----------------------------------
128
128
129 $ hg revert --all -rtip
129 $ hg revert --all -rtip
130 forgetting a
130 forgetting a
131 undeleting d
131 undeleting d
132 $ rm a *.orig
132 $ rm a *.orig
133
133
134 revert to another revision (--rev) and exact match
134 revert to another revision (--rev) and exact match
135 --------------------------------------------------
135 --------------------------------------------------
136
136
137 exact match are more silent
137 exact match are more silent
138
138
139 $ hg revert -r0 a
139 $ hg revert -r0 a
140 $ hg st a
140 $ hg st a
141 A a
141 A a
142 $ hg rm d
142 $ hg rm d
143 $ hg st d
143 $ hg st d
144 R d
144 R d
145
145
146 should keep d removed
146 should keep d removed
147
147
148 $ hg revert -r0 d
148 $ hg revert -r0 d
149 no changes needed to d
149 no changes needed to d
150 $ hg st d
150 $ hg st d
151 R d
151 R d
152
152
153 $ hg update -C
153 $ hg update -C
154 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
155
155
156 revert of exec bit
156 revert of exec bit
157 ------------------
157 ------------------
158
158
159 #if execbit
159 #if execbit
160 $ chmod +x c
160 $ chmod +x c
161 $ hg revert --all
161 $ hg revert --all
162 reverting c
162 reverting c
163
163
164 $ test -x c || echo non-executable
164 $ test -x c || echo non-executable
165 non-executable
165 non-executable
166
166
167 $ chmod +x c
167 $ chmod +x c
168 $ hg commit -m exe
168 $ hg commit -m exe
169
169
170 $ chmod -x c
170 $ chmod -x c
171 $ hg revert --all
171 $ hg revert --all
172 reverting c
172 reverting c
173
173
174 $ test -x c && echo executable
174 $ test -x c && echo executable
175 executable
175 executable
176 #endif
176 #endif
177
177
178 $ cd ..
178 $ cd ..
179
179
180
180
181 Issue241: update and revert produces inconsistent repositories
181 Issue241: update and revert produces inconsistent repositories
182 --------------------------------------------------------------
182 --------------------------------------------------------------
183
183
184 $ hg init a
184 $ hg init a
185 $ cd a
185 $ cd a
186 $ echo a >> a
186 $ echo a >> a
187 $ hg commit -A -d '1 0' -m a
187 $ hg commit -A -d '1 0' -m a
188 adding a
188 adding a
189 $ echo a >> a
189 $ echo a >> a
190 $ hg commit -d '2 0' -m a
190 $ hg commit -d '2 0' -m a
191 $ hg update 0
191 $ hg update 0
192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 $ mkdir b
193 $ mkdir b
194 $ echo b > b/b
194 $ echo b > b/b
195
195
196 call `hg revert` with no file specified
196 call `hg revert` with no file specified
197 ---------------------------------------
197 ---------------------------------------
198
198
199 $ hg revert -rtip
199 $ hg revert -rtip
200 abort: no files or directories specified
200 abort: no files or directories specified
201 (use --all to revert all files, or 'hg update 1' to update)
201 (use --all to revert all files, or 'hg update 1' to update)
202 [255]
202 [255]
203
203
204 call `hg revert` with --all
204 call `hg revert` with --all
205 ---------------------------
205 ---------------------------
206
206
207 $ hg revert --all -rtip
207 $ hg revert --all -rtip
208 reverting a
208 reverting a
209
209
210
210
211 Issue332: confusing message when reverting directory
211 Issue332: confusing message when reverting directory
212 ----------------------------------------------------
212 ----------------------------------------------------
213
213
214 $ hg ci -A -m b
214 $ hg ci -A -m b
215 adding b/b
215 adding b/b
216 created new head
216 created new head
217 $ echo foobar > b/b
217 $ echo foobar > b/b
218 $ mkdir newdir
218 $ mkdir newdir
219 $ echo foo > newdir/newfile
219 $ echo foo > newdir/newfile
220 $ hg add newdir/newfile
220 $ hg add newdir/newfile
221 $ hg revert b newdir
221 $ hg revert b newdir
222 reverting b/b (glob)
222 reverting b/b (glob)
223 forgetting newdir/newfile (glob)
223 forgetting newdir/newfile (glob)
224 $ echo foobar > b/b
224 $ echo foobar > b/b
225 $ hg revert .
225 $ hg revert .
226 reverting b/b (glob)
226 reverting b/b (glob)
227
227
228
228
229 reverting a rename target should revert the source
229 reverting a rename target should revert the source
230 --------------------------------------------------
230 --------------------------------------------------
231
231
232 $ hg mv a newa
232 $ hg mv a newa
233 $ hg revert newa
233 $ hg revert newa
234 $ hg st a newa
234 $ hg st a newa
235 ? newa
235 ? newa
236
236
237 $ cd ..
237 $ cd ..
238
238
239 $ hg init ignored
239 $ hg init ignored
240 $ cd ignored
240 $ cd ignored
241 $ echo '^ignored$' > .hgignore
241 $ echo '^ignored$' > .hgignore
242 $ echo '^ignoreddir$' >> .hgignore
242 $ echo '^ignoreddir$' >> .hgignore
243 $ echo '^removed$' >> .hgignore
243 $ echo '^removed$' >> .hgignore
244
244
245 $ mkdir ignoreddir
245 $ mkdir ignoreddir
246 $ touch ignoreddir/file
246 $ touch ignoreddir/file
247 $ touch ignoreddir/removed
247 $ touch ignoreddir/removed
248 $ touch ignored
248 $ touch ignored
249 $ touch removed
249 $ touch removed
250
250
251 4 ignored files (we will add/commit everything)
251 4 ignored files (we will add/commit everything)
252
252
253 $ hg st -A -X .hgignore
253 $ hg st -A -X .hgignore
254 I ignored
254 I ignored
255 I ignoreddir/file
255 I ignoreddir/file
256 I ignoreddir/removed
256 I ignoreddir/removed
257 I removed
257 I removed
258 $ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
258 $ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
259
259
260 $ echo >> ignored
260 $ echo >> ignored
261 $ echo >> ignoreddir/file
261 $ echo >> ignoreddir/file
262 $ hg rm removed ignoreddir/removed
262 $ hg rm removed ignoreddir/removed
263
263
264 should revert ignored* and undelete *removed
264 should revert ignored* and undelete *removed
265 --------------------------------------------
265 --------------------------------------------
266
266
267 $ hg revert -a --no-backup
267 $ hg revert -a --no-backup
268 reverting ignored
268 reverting ignored
269 reverting ignoreddir/file (glob)
269 reverting ignoreddir/file (glob)
270 undeleting ignoreddir/removed (glob)
270 undeleting ignoreddir/removed (glob)
271 undeleting removed
271 undeleting removed
272 $ hg st -mardi
272 $ hg st -mardi
273
273
274 $ hg up -qC
274 $ hg up -qC
275 $ echo >> ignored
275 $ echo >> ignored
276 $ hg rm removed
276 $ hg rm removed
277
277
278 should silently revert the named files
278 should silently revert the named files
279 --------------------------------------
279 --------------------------------------
280
280
281 $ hg revert --no-backup ignored removed
281 $ hg revert --no-backup ignored removed
282 $ hg st -mardi
282 $ hg st -mardi
283
283
284 Reverting copy (issue3920)
284 Reverting copy (issue3920)
285 --------------------------
285 --------------------------
286
286
287 someone set up us the copies
287 someone set up us the copies
288
288
289 $ rm .hgignore
289 $ rm .hgignore
290 $ hg update -C
290 $ hg update -C
291 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
291 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 $ hg mv ignored allyour
292 $ hg mv ignored allyour
293 $ hg copy removed base
293 $ hg copy removed base
294 $ hg commit -m rename
294 $ hg commit -m rename
295
295
296 copies and renames, you have no chance to survive make your time (issue3920)
296 copies and renames, you have no chance to survive make your time (issue3920)
297
297
298 $ hg update '.^'
298 $ hg update '.^'
299 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
299 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
300 $ hg revert -rtip -a
300 $ hg revert -rtip -a
301 adding allyour
301 adding allyour
302 adding base
302 adding base
303 removing ignored
303 removing ignored
304 $ hg status -C
304 $ hg status -C
305 A allyour
305 A allyour
306 ignored
306 ignored
307 A base
307 A base
308 removed
308 removed
309 R ignored
309 R ignored
310
310
311 Test revert of a file added by one side of the merge
311 Test revert of a file added by one side of the merge
312 ====================================================
312 ====================================================
313
313
314 remove any pending change
314 remove any pending change
315
315
316 $ hg revert --all
316 $ hg revert --all
317 forgetting allyour
317 forgetting allyour
318 forgetting base
318 forgetting base
319 undeleting ignored
319 undeleting ignored
320 $ hg purge --all --config extensions.purge=
320 $ hg purge --all --config extensions.purge=
321
321
322 Adds a new commit
322 Adds a new commit
323
323
324 $ echo foo > newadd
324 $ echo foo > newadd
325 $ hg add newadd
325 $ hg add newadd
326 $ hg commit -m 'other adds'
326 $ hg commit -m 'other adds'
327 created new head
327 created new head
328
328
329
329
330 merge it with the other head
330 merge it with the other head
331
331
332 $ hg merge # merge 1 into 2
332 $ hg merge # merge 1 into 2
333 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
333 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
334 (branch merge, don't forget to commit)
334 (branch merge, don't forget to commit)
335 $ hg summary
335 $ hg summary
336 parent: 2:b8ec310b2d4e tip
336 parent: 2:b8ec310b2d4e tip
337 other adds
337 other adds
338 parent: 1:f6180deb8fbe
338 parent: 1:f6180deb8fbe
339 rename
339 rename
340 branch: default
340 branch: default
341 commit: 2 modified, 1 removed (merge)
341 commit: 2 modified, 1 removed (merge)
342 update: (current)
342 update: (current)
343
343
344 clarifies who added what
344 clarifies who added what
345
345
346 $ hg status
346 $ hg status
347 M allyour
347 M allyour
348 M base
348 M base
349 R ignored
349 R ignored
350 $ hg status --change 'p1()'
350 $ hg status --change 'p1()'
351 A newadd
351 A newadd
352 $ hg status --change 'p2()'
352 $ hg status --change 'p2()'
353 A allyour
353 A allyour
354 A base
354 A base
355 R ignored
355 R ignored
356
356
357 revert file added by p1() to p1() state
357 revert file added by p1() to p1() state
358 -----------------------------------------
358 -----------------------------------------
359
359
360 $ hg revert -r 'p1()' 'glob:newad?'
360 $ hg revert -r 'p1()' 'glob:newad?'
361 $ hg status
361 $ hg status
362 M allyour
362 M allyour
363 M base
363 M base
364 R ignored
364 R ignored
365
365
366 revert file added by p1() to p2() state
366 revert file added by p1() to p2() state
367 ------------------------------------------
367 ------------------------------------------
368
368
369 $ hg revert -r 'p2()' 'glob:newad?'
369 $ hg revert -r 'p2()' 'glob:newad?'
370 removing newadd
370 removing newadd
371 $ hg status
371 $ hg status
372 M allyour
372 M allyour
373 M base
373 M base
374 R ignored
374 R ignored
375 R newadd
375 R newadd
376
376
377 revert file added by p2() to p2() state
377 revert file added by p2() to p2() state
378 ------------------------------------------
378 ------------------------------------------
379
379
380 $ hg revert -r 'p2()' 'glob:allyou?'
380 $ hg revert -r 'p2()' 'glob:allyou?'
381 $ hg status
381 $ hg status
382 M allyour
382 M allyour
383 M base
383 M base
384 R ignored
384 R ignored
385 R newadd
385 R newadd
386
386
387 revert file added by p2() to p1() state
387 revert file added by p2() to p1() state
388 ------------------------------------------
388 ------------------------------------------
389
389
390 $ hg revert -r 'p1()' 'glob:allyou?'
390 $ hg revert -r 'p1()' 'glob:allyou?'
391 removing allyour
391 removing allyour
392 $ hg status
392 $ hg status
393 M base
393 M base
394 R allyour
394 R allyour
395 R ignored
395 R ignored
396 R newadd
396 R newadd
397
397
398 Systematic behavior validation of most possible cases
398 Systematic behavior validation of most possible cases
399 =====================================================
399 =====================================================
400
400
401 This section tests most of the possible combinations of working directory
401 This section tests most of the possible combinations of working directory
402 changes and inter-revision changes. The number of possible cases is significant
402 changes and inter-revision changes. The number of possible cases is significant
403 but they all have a slighly different handling. So this section commits to
403 but they all have a slighly different handling. So this section commits to
404 generating and testing all of them to allow safe refactoring of the revert code.
404 generating and testing all of them to allow safe refactoring of the revert code.
405
405
406 A python script is used to generate a file history for each combination of
406 A python script is used to generate a file history for each combination of
407 changes between, on one side the working directory and its parent and on
407 changes between, on one side the working directory and its parent and on
408 the other side, changes between a revert target (--rev) and working directory
408 the other side, changes between a revert target (--rev) and working directory
409 parent. The three states generated are:
409 parent. The three states generated are:
410
410
411 - a "base" revision
411 - a "base" revision
412 - a "parent" revision
412 - a "parent" revision
413 - the working directory (based on "parent")
413 - the working directory (based on "parent")
414
414
415 The file generated have names of the form:
415 The file generated have names of the form:
416
416
417 <changeset-state>_<working-copy-state>
417 <changeset-state>_<working-copy-state>
418
418
419 Here, "changeset-state" conveys the state in "base" and "parent" (or the change
419 Here, "changeset-state" conveys the state in "base" and "parent" (or the change
420 that happen between them), "working-copy-state" is self explanatory.
420 that happen between them), "working-copy-state" is self explanatory.
421
421
422 All known states are not tested yet. See inline documentation for details.
422 All known states are not tested yet. See inline documentation for details.
423 Special cases from merge and rename are not tested by this section.
423 Special cases from merge and rename are not tested by this section.
424
424
425 There are also multiple cases where the current revert implementation is known to
425 There are also multiple cases where the current revert implementation is known to
426 slightly misbehave.
426 slightly misbehave.
427
427
428 Write the python script to disk
428 Write the python script to disk
429 -------------------------------
429 -------------------------------
430
430
431 $ cat << EOF > gen-revert-cases.py
431 $ cat << EOF > gen-revert-cases.py
432 > # generate proper file state to test revert behavior
432 > # generate proper file state to test revert behavior
433 > import sys
433 > import sys
434 > import os
434 > import os
435 >
435 >
436 > # content of the file in "base" and "parent"
436 > # content of the file in "base" and "parent"
437 > # None means no file at all
437 > # None means no file at all
438 > ctxcontent = {
438 > ctxcontent = {
439 > # clean: no change from base to parent
439 > # clean: no change from base to parent
440 > 'clean': ['base', 'base'],
440 > 'clean': ['base', 'base'],
441 > # modified: file content change from base to parent
441 > # modified: file content change from base to parent
442 > 'modified': ['base', 'parent'],
442 > 'modified': ['base', 'parent'],
443 > # added: file is missing from base and added in parent
443 > # added: file is missing from base and added in parent
444 > 'added': [None, 'parent'],
444 > 'added': [None, 'parent'],
445 > # removed: file exist in base but is removed from parent
445 > # removed: file exist in base but is removed from parent
446 > 'removed': ['base', None],
446 > 'removed': ['base', None],
447 > # file exist neither in base not in parent
447 > # file exist neither in base not in parent
448 > 'missing': [None, None],
448 > 'missing': [None, None],
449 > }
449 > }
450 >
450 >
451 > # content of file in working copy
451 > # content of file in working copy
452 > wccontent = {
452 > wccontent = {
453 > # clean: wc content is the same as parent
453 > # clean: wc content is the same as parent
454 > 'clean': lambda cc: cc[1],
454 > 'clean': lambda cc: cc[1],
455 > # revert: wc content is the same as base
455 > # revert: wc content is the same as base
456 > 'revert': lambda cc: cc[0],
456 > 'revert': lambda cc: cc[0],
457 > # wc: file exist with a content different from base and parent
457 > # wc: file exist with a content different from base and parent
458 > 'wc': lambda cc: 'wc',
458 > 'wc': lambda cc: 'wc',
459 > # removed: file is missing and marked as untracked
459 > # removed: file is missing and marked as untracked
460 > 'removed': lambda cc: None,
460 > 'removed': lambda cc: None,
461 > # deleted: file is recorded as tracked but missing
461 > # deleted: file is recorded as tracked but missing
462 > # rely on file deletion outside of this script
462 > # rely on file deletion outside of this script
463 > 'deleted': lambda cc:'TOBEDELETED',
463 > 'deleted': lambda cc:'TOBEDELETED',
464 > }
464 > }
465 > # untracked-X is a version of X where the file is not tracked (? unknown)
465 > # untracked-X is a version of X where the file is not tracked (? unknown)
466 > wccontent['untracked-clean'] = wccontent['clean']
466 > wccontent['untracked-clean'] = wccontent['clean']
467 > wccontent['untracked-revert'] = wccontent['revert']
467 > wccontent['untracked-revert'] = wccontent['revert']
468 > wccontent['untracked-wc'] = wccontent['wc']
468 > wccontent['untracked-wc'] = wccontent['wc']
469 >
469 >
470 > # build the combination of possible states
470 > # build the combination of possible states
471 > combination = []
471 > combination = []
472 > for ctxkey in ctxcontent:
472 > for ctxkey in ctxcontent:
473 > for wckey in wccontent:
473 > for wckey in wccontent:
474 > filename = "%s_%s" % (ctxkey, wckey)
474 > filename = "%s_%s" % (ctxkey, wckey)
475 > combination.append((filename, ctxkey, wckey))
475 > combination.append((filename, ctxkey, wckey))
476 >
476 >
477 > # make sure we have stable output
477 > # make sure we have stable output
478 > combination.sort()
478 > combination.sort()
479 >
479 >
480 > # retrieve the state we must generate
480 > # retrieve the state we must generate
481 > target = sys.argv[1]
481 > target = sys.argv[1]
482 >
482 >
483 > # compute file content
483 > # compute file content
484 > content = []
484 > content = []
485 > for filename, ctxkey, wckey in combination:
485 > for filename, ctxkey, wckey in combination:
486 > cc = ctxcontent[ctxkey]
486 > cc = ctxcontent[ctxkey]
487 > if target == 'filelist':
487 > if target == 'filelist':
488 > print filename
488 > print filename
489 > elif target == 'base':
489 > elif target == 'base':
490 > content.append((filename, cc[0]))
490 > content.append((filename, cc[0]))
491 > elif target == 'parent':
491 > elif target == 'parent':
492 > content.append((filename, cc[1]))
492 > content.append((filename, cc[1]))
493 > elif target == 'wc':
493 > elif target == 'wc':
494 > content.append((filename, wccontent[wckey](cc)))
494 > content.append((filename, wccontent[wckey](cc)))
495 > else:
495 > else:
496 > print >> sys.stderr, "unknown target:", target
496 > print >> sys.stderr, "unknown target:", target
497 > sys.exit(1)
497 > sys.exit(1)
498 >
498 >
499 > # write actual content
499 > # write actual content
500 > for filename, data in content:
500 > for filename, data in content:
501 > if data is not None:
501 > if data is not None:
502 > f = open(filename, 'w')
502 > f = open(filename, 'w')
503 > f.write(data + '\n')
503 > f.write(data + '\n')
504 > f.close()
504 > f.close()
505 > elif os.path.exists(filename):
505 > elif os.path.exists(filename):
506 > os.remove(filename)
506 > os.remove(filename)
507 > EOF
507 > EOF
508
508
509 check list of planned files
509 check list of planned files
510
510
511 $ python gen-revert-cases.py filelist
511 $ python gen-revert-cases.py filelist
512 added_clean
512 added_clean
513 added_deleted
513 added_deleted
514 added_removed
514 added_removed
515 added_revert
515 added_revert
516 added_untracked-clean
516 added_untracked-clean
517 added_untracked-revert
517 added_untracked-revert
518 added_untracked-wc
518 added_untracked-wc
519 added_wc
519 added_wc
520 clean_clean
520 clean_clean
521 clean_deleted
521 clean_deleted
522 clean_removed
522 clean_removed
523 clean_revert
523 clean_revert
524 clean_untracked-clean
524 clean_untracked-clean
525 clean_untracked-revert
525 clean_untracked-revert
526 clean_untracked-wc
526 clean_untracked-wc
527 clean_wc
527 clean_wc
528 missing_clean
528 missing_clean
529 missing_deleted
529 missing_deleted
530 missing_removed
530 missing_removed
531 missing_revert
531 missing_revert
532 missing_untracked-clean
532 missing_untracked-clean
533 missing_untracked-revert
533 missing_untracked-revert
534 missing_untracked-wc
534 missing_untracked-wc
535 missing_wc
535 missing_wc
536 modified_clean
536 modified_clean
537 modified_deleted
537 modified_deleted
538 modified_removed
538 modified_removed
539 modified_revert
539 modified_revert
540 modified_untracked-clean
540 modified_untracked-clean
541 modified_untracked-revert
541 modified_untracked-revert
542 modified_untracked-wc
542 modified_untracked-wc
543 modified_wc
543 modified_wc
544 removed_clean
544 removed_clean
545 removed_deleted
545 removed_deleted
546 removed_removed
546 removed_removed
547 removed_revert
547 removed_revert
548 removed_untracked-clean
548 removed_untracked-clean
549 removed_untracked-revert
549 removed_untracked-revert
550 removed_untracked-wc
550 removed_untracked-wc
551 removed_wc
551 removed_wc
552
552
553 Script to make a simple text version of the content
553 Script to make a simple text version of the content
554 ---------------------------------------------------
554 ---------------------------------------------------
555
555
556 $ cat << EOF >> dircontent.py
556 $ cat << EOF >> dircontent.py
557 > # generate a simple text view of the directory for easy comparison
557 > # generate a simple text view of the directory for easy comparison
558 > import os
558 > import os
559 > files = os.listdir('.')
559 > files = os.listdir('.')
560 > files.sort()
560 > files.sort()
561 > for filename in files:
561 > for filename in files:
562 > if os.path.isdir(filename):
562 > if os.path.isdir(filename):
563 > continue
563 > continue
564 > content = open(filename).read()
564 > content = open(filename).read()
565 > print '%-6s %s' % (content.strip(), filename)
565 > print '%-6s %s' % (content.strip(), filename)
566 > EOF
566 > EOF
567
567
568 Generate appropriate repo state
568 Generate appropriate repo state
569 -------------------------------
569 -------------------------------
570
570
571 $ hg init revert-ref
571 $ hg init revert-ref
572 $ cd revert-ref
572 $ cd revert-ref
573
573
574 Generate base changeset
574 Generate base changeset
575
575
576 $ python ../gen-revert-cases.py base
576 $ python ../gen-revert-cases.py base
577 $ hg addremove --similarity 0
577 $ hg addremove --similarity 0
578 adding clean_clean
578 adding clean_clean
579 adding clean_deleted
579 adding clean_deleted
580 adding clean_removed
580 adding clean_removed
581 adding clean_revert
581 adding clean_revert
582 adding clean_untracked-clean
582 adding clean_untracked-clean
583 adding clean_untracked-revert
583 adding clean_untracked-revert
584 adding clean_untracked-wc
584 adding clean_untracked-wc
585 adding clean_wc
585 adding clean_wc
586 adding modified_clean
586 adding modified_clean
587 adding modified_deleted
587 adding modified_deleted
588 adding modified_removed
588 adding modified_removed
589 adding modified_revert
589 adding modified_revert
590 adding modified_untracked-clean
590 adding modified_untracked-clean
591 adding modified_untracked-revert
591 adding modified_untracked-revert
592 adding modified_untracked-wc
592 adding modified_untracked-wc
593 adding modified_wc
593 adding modified_wc
594 adding removed_clean
594 adding removed_clean
595 adding removed_deleted
595 adding removed_deleted
596 adding removed_removed
596 adding removed_removed
597 adding removed_revert
597 adding removed_revert
598 adding removed_untracked-clean
598 adding removed_untracked-clean
599 adding removed_untracked-revert
599 adding removed_untracked-revert
600 adding removed_untracked-wc
600 adding removed_untracked-wc
601 adding removed_wc
601 adding removed_wc
602 $ hg status
602 $ hg status
603 A clean_clean
603 A clean_clean
604 A clean_deleted
604 A clean_deleted
605 A clean_removed
605 A clean_removed
606 A clean_revert
606 A clean_revert
607 A clean_untracked-clean
607 A clean_untracked-clean
608 A clean_untracked-revert
608 A clean_untracked-revert
609 A clean_untracked-wc
609 A clean_untracked-wc
610 A clean_wc
610 A clean_wc
611 A modified_clean
611 A modified_clean
612 A modified_deleted
612 A modified_deleted
613 A modified_removed
613 A modified_removed
614 A modified_revert
614 A modified_revert
615 A modified_untracked-clean
615 A modified_untracked-clean
616 A modified_untracked-revert
616 A modified_untracked-revert
617 A modified_untracked-wc
617 A modified_untracked-wc
618 A modified_wc
618 A modified_wc
619 A removed_clean
619 A removed_clean
620 A removed_deleted
620 A removed_deleted
621 A removed_removed
621 A removed_removed
622 A removed_revert
622 A removed_revert
623 A removed_untracked-clean
623 A removed_untracked-clean
624 A removed_untracked-revert
624 A removed_untracked-revert
625 A removed_untracked-wc
625 A removed_untracked-wc
626 A removed_wc
626 A removed_wc
627 $ hg commit -m 'base'
627 $ hg commit -m 'base'
628
628
629 (create a simple text version of the content)
629 (create a simple text version of the content)
630
630
631 $ python ../dircontent.py > ../content-base.txt
631 $ python ../dircontent.py > ../content-base.txt
632 $ cat ../content-base.txt
632 $ cat ../content-base.txt
633 base clean_clean
633 base clean_clean
634 base clean_deleted
634 base clean_deleted
635 base clean_removed
635 base clean_removed
636 base clean_revert
636 base clean_revert
637 base clean_untracked-clean
637 base clean_untracked-clean
638 base clean_untracked-revert
638 base clean_untracked-revert
639 base clean_untracked-wc
639 base clean_untracked-wc
640 base clean_wc
640 base clean_wc
641 base modified_clean
641 base modified_clean
642 base modified_deleted
642 base modified_deleted
643 base modified_removed
643 base modified_removed
644 base modified_revert
644 base modified_revert
645 base modified_untracked-clean
645 base modified_untracked-clean
646 base modified_untracked-revert
646 base modified_untracked-revert
647 base modified_untracked-wc
647 base modified_untracked-wc
648 base modified_wc
648 base modified_wc
649 base removed_clean
649 base removed_clean
650 base removed_deleted
650 base removed_deleted
651 base removed_removed
651 base removed_removed
652 base removed_revert
652 base removed_revert
653 base removed_untracked-clean
653 base removed_untracked-clean
654 base removed_untracked-revert
654 base removed_untracked-revert
655 base removed_untracked-wc
655 base removed_untracked-wc
656 base removed_wc
656 base removed_wc
657
657
658 Create parent changeset
658 Create parent changeset
659
659
660 $ python ../gen-revert-cases.py parent
660 $ python ../gen-revert-cases.py parent
661 $ hg addremove --similarity 0
661 $ hg addremove --similarity 0
662 adding added_clean
662 adding added_clean
663 adding added_deleted
663 adding added_deleted
664 adding added_removed
664 adding added_removed
665 adding added_revert
665 adding added_revert
666 adding added_untracked-clean
666 adding added_untracked-clean
667 adding added_untracked-revert
667 adding added_untracked-revert
668 adding added_untracked-wc
668 adding added_untracked-wc
669 adding added_wc
669 adding added_wc
670 removing removed_clean
670 removing removed_clean
671 removing removed_deleted
671 removing removed_deleted
672 removing removed_removed
672 removing removed_removed
673 removing removed_revert
673 removing removed_revert
674 removing removed_untracked-clean
674 removing removed_untracked-clean
675 removing removed_untracked-revert
675 removing removed_untracked-revert
676 removing removed_untracked-wc
676 removing removed_untracked-wc
677 removing removed_wc
677 removing removed_wc
678 $ hg status
678 $ hg status
679 M modified_clean
679 M modified_clean
680 M modified_deleted
680 M modified_deleted
681 M modified_removed
681 M modified_removed
682 M modified_revert
682 M modified_revert
683 M modified_untracked-clean
683 M modified_untracked-clean
684 M modified_untracked-revert
684 M modified_untracked-revert
685 M modified_untracked-wc
685 M modified_untracked-wc
686 M modified_wc
686 M modified_wc
687 A added_clean
687 A added_clean
688 A added_deleted
688 A added_deleted
689 A added_removed
689 A added_removed
690 A added_revert
690 A added_revert
691 A added_untracked-clean
691 A added_untracked-clean
692 A added_untracked-revert
692 A added_untracked-revert
693 A added_untracked-wc
693 A added_untracked-wc
694 A added_wc
694 A added_wc
695 R removed_clean
695 R removed_clean
696 R removed_deleted
696 R removed_deleted
697 R removed_removed
697 R removed_removed
698 R removed_revert
698 R removed_revert
699 R removed_untracked-clean
699 R removed_untracked-clean
700 R removed_untracked-revert
700 R removed_untracked-revert
701 R removed_untracked-wc
701 R removed_untracked-wc
702 R removed_wc
702 R removed_wc
703 $ hg commit -m 'parent'
703 $ hg commit -m 'parent'
704
704
705 (create a simple text version of the content)
705 (create a simple text version of the content)
706
706
707 $ python ../dircontent.py > ../content-parent.txt
707 $ python ../dircontent.py > ../content-parent.txt
708 $ cat ../content-parent.txt
708 $ cat ../content-parent.txt
709 parent added_clean
709 parent added_clean
710 parent added_deleted
710 parent added_deleted
711 parent added_removed
711 parent added_removed
712 parent added_revert
712 parent added_revert
713 parent added_untracked-clean
713 parent added_untracked-clean
714 parent added_untracked-revert
714 parent added_untracked-revert
715 parent added_untracked-wc
715 parent added_untracked-wc
716 parent added_wc
716 parent added_wc
717 base clean_clean
717 base clean_clean
718 base clean_deleted
718 base clean_deleted
719 base clean_removed
719 base clean_removed
720 base clean_revert
720 base clean_revert
721 base clean_untracked-clean
721 base clean_untracked-clean
722 base clean_untracked-revert
722 base clean_untracked-revert
723 base clean_untracked-wc
723 base clean_untracked-wc
724 base clean_wc
724 base clean_wc
725 parent modified_clean
725 parent modified_clean
726 parent modified_deleted
726 parent modified_deleted
727 parent modified_removed
727 parent modified_removed
728 parent modified_revert
728 parent modified_revert
729 parent modified_untracked-clean
729 parent modified_untracked-clean
730 parent modified_untracked-revert
730 parent modified_untracked-revert
731 parent modified_untracked-wc
731 parent modified_untracked-wc
732 parent modified_wc
732 parent modified_wc
733
733
734 Setup working directory
734 Setup working directory
735
735
736 $ python ../gen-revert-cases.py wc | cat
736 $ python ../gen-revert-cases.py wc
737 $ hg addremove --similarity 0
737 $ hg addremove --similarity 0
738 removing added_removed
738 removing added_removed
739 removing added_revert
739 removing added_revert
740 removing added_untracked-revert
740 removing added_untracked-revert
741 removing clean_removed
741 removing clean_removed
742 adding missing_deleted
742 adding missing_deleted
743 adding missing_untracked-wc
743 adding missing_untracked-wc
744 adding missing_wc
744 adding missing_wc
745 removing modified_removed
745 removing modified_removed
746 adding removed_deleted
746 adding removed_deleted
747 adding removed_revert
747 adding removed_revert
748 adding removed_untracked-revert
748 adding removed_untracked-revert
749 adding removed_untracked-wc
749 adding removed_untracked-wc
750 adding removed_wc
750 adding removed_wc
751 $ hg forget *untracked*
751 $ hg forget *untracked*
752 $ rm *deleted*
752 $ rm *deleted*
753 $ hg status
753 $ hg status
754 M added_wc
754 M added_wc
755 M clean_wc
755 M clean_wc
756 M modified_revert
756 M modified_revert
757 M modified_wc
757 M modified_wc
758 A missing_wc
758 A missing_wc
759 A removed_revert
759 A removed_revert
760 A removed_wc
760 A removed_wc
761 R added_removed
761 R added_removed
762 R added_revert
762 R added_revert
763 R added_untracked-clean
763 R added_untracked-clean
764 R added_untracked-revert
764 R added_untracked-revert
765 R added_untracked-wc
765 R added_untracked-wc
766 R clean_removed
766 R clean_removed
767 R clean_untracked-clean
767 R clean_untracked-clean
768 R clean_untracked-revert
768 R clean_untracked-revert
769 R clean_untracked-wc
769 R clean_untracked-wc
770 R modified_removed
770 R modified_removed
771 R modified_untracked-clean
771 R modified_untracked-clean
772 R modified_untracked-revert
772 R modified_untracked-revert
773 R modified_untracked-wc
773 R modified_untracked-wc
774 ! added_deleted
774 ! added_deleted
775 ! clean_deleted
775 ! clean_deleted
776 ! missing_deleted
776 ! missing_deleted
777 ! modified_deleted
777 ! modified_deleted
778 ! removed_deleted
778 ! removed_deleted
779 ? missing_untracked-wc
779 ? missing_untracked-wc
780 ? removed_untracked-revert
780 ? removed_untracked-revert
781 ? removed_untracked-wc
781 ? removed_untracked-wc
782
782
783 $ hg status --rev 'desc("base")'
783 $ hg status --rev 'desc("base")'
784 M clean_wc
784 M clean_wc
785 M modified_clean
785 M modified_clean
786 M modified_wc
786 M modified_wc
787 M removed_wc
787 M removed_wc
788 A added_clean
788 A added_clean
789 A added_wc
789 A added_wc
790 A missing_wc
790 A missing_wc
791 R clean_removed
791 R clean_removed
792 R clean_untracked-clean
792 R clean_untracked-clean
793 R clean_untracked-revert
793 R clean_untracked-revert
794 R clean_untracked-wc
794 R clean_untracked-wc
795 R modified_removed
795 R modified_removed
796 R modified_untracked-clean
796 R modified_untracked-clean
797 R modified_untracked-revert
797 R modified_untracked-revert
798 R modified_untracked-wc
798 R modified_untracked-wc
799 R removed_clean
799 R removed_clean
800 R removed_deleted
800 R removed_deleted
801 R removed_removed
801 R removed_removed
802 R removed_untracked-clean
802 R removed_untracked-clean
803 R removed_untracked-revert
803 R removed_untracked-revert
804 R removed_untracked-wc
804 R removed_untracked-wc
805 ! added_deleted
805 ! added_deleted
806 ! clean_deleted
806 ! clean_deleted
807 ! missing_deleted
807 ! missing_deleted
808 ! modified_deleted
808 ! modified_deleted
809 ! removed_deleted
809 ! removed_deleted
810 ? missing_untracked-wc
810 ? missing_untracked-wc
811
811
812 (create a simple text version of the content)
812 (create a simple text version of the content)
813
813
814 $ python ../dircontent.py > ../content-wc.txt
814 $ python ../dircontent.py > ../content-wc.txt
815 $ cat ../content-wc.txt
815 $ cat ../content-wc.txt
816 parent added_clean
816 parent added_clean
817 parent added_untracked-clean
817 parent added_untracked-clean
818 wc added_untracked-wc
818 wc added_untracked-wc
819 wc added_wc
819 wc added_wc
820 base clean_clean
820 base clean_clean
821 base clean_revert
821 base clean_revert
822 base clean_untracked-clean
822 base clean_untracked-clean
823 base clean_untracked-revert
823 base clean_untracked-revert
824 wc clean_untracked-wc
824 wc clean_untracked-wc
825 wc clean_wc
825 wc clean_wc
826 wc missing_untracked-wc
826 wc missing_untracked-wc
827 wc missing_wc
827 wc missing_wc
828 parent modified_clean
828 parent modified_clean
829 base modified_revert
829 base modified_revert
830 parent modified_untracked-clean
830 parent modified_untracked-clean
831 base modified_untracked-revert
831 base modified_untracked-revert
832 wc modified_untracked-wc
832 wc modified_untracked-wc
833 wc modified_wc
833 wc modified_wc
834 base removed_revert
834 base removed_revert
835 base removed_untracked-revert
835 base removed_untracked-revert
836 wc removed_untracked-wc
836 wc removed_untracked-wc
837 wc removed_wc
837 wc removed_wc
838
838
839 $ cd ..
839 $ cd ..
840
840
841 Test revert --all to parent content
841 Test revert --all to parent content
842 -----------------------------------
842 -----------------------------------
843
843
844 (setup from reference repo)
844 (setup from reference repo)
845
845
846 $ cp -r revert-ref revert-parent-all
846 $ cp -r revert-ref revert-parent-all
847 $ cd revert-parent-all
847 $ cd revert-parent-all
848
848
849 check revert output
849 check revert output
850
850
851 $ hg revert --all
851 $ hg revert --all
852 reverting added_deleted
852 reverting added_deleted
853 undeleting added_removed
853 undeleting added_removed
854 undeleting added_revert
854 undeleting added_revert
855 undeleting added_untracked-clean
855 undeleting added_untracked-clean
856 undeleting added_untracked-revert
856 undeleting added_untracked-revert
857 undeleting added_untracked-wc
857 undeleting added_untracked-wc
858 reverting added_wc
858 reverting added_wc
859 reverting clean_deleted
859 reverting clean_deleted
860 undeleting clean_removed
860 undeleting clean_removed
861 undeleting clean_untracked-clean
861 undeleting clean_untracked-clean
862 undeleting clean_untracked-revert
862 undeleting clean_untracked-revert
863 undeleting clean_untracked-wc
863 undeleting clean_untracked-wc
864 reverting clean_wc
864 reverting clean_wc
865 forgetting missing_deleted
865 forgetting missing_deleted
866 forgetting missing_wc
866 forgetting missing_wc
867 reverting modified_deleted
867 reverting modified_deleted
868 undeleting modified_removed
868 undeleting modified_removed
869 reverting modified_revert
869 reverting modified_revert
870 undeleting modified_untracked-clean
870 undeleting modified_untracked-clean
871 undeleting modified_untracked-revert
871 undeleting modified_untracked-revert
872 undeleting modified_untracked-wc
872 undeleting modified_untracked-wc
873 reverting modified_wc
873 reverting modified_wc
874 forgetting removed_deleted
874 forgetting removed_deleted
875 forgetting removed_revert
875 forgetting removed_revert
876 forgetting removed_wc
876 forgetting removed_wc
877
877
878 Compare resulting directory with revert target.
878 Compare resulting directory with revert target.
879
879
880 The diff is filtered to include change only. The only difference should be
880 The diff is filtered to include change only. The only difference should be
881 additional `.orig` backup file when applicable.
881 additional `.orig` backup file when applicable.
882
882
883 $ python ../dircontent.py > ../content-parent-all.txt
883 $ python ../dircontent.py > ../content-parent-all.txt
884 $ cd ..
884 $ cd ..
885 $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
885 $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
886 +wc added_untracked-wc.orig
886 +wc added_untracked-wc.orig
887 +wc added_wc.orig
887 +wc added_wc.orig
888 +wc clean_untracked-wc.orig
888 +wc clean_untracked-wc.orig
889 +wc clean_wc.orig
889 +wc clean_wc.orig
890 +wc missing_untracked-wc
890 +wc missing_untracked-wc
891 +wc missing_wc
891 +wc missing_wc
892 +base modified_revert.orig
892 +base modified_revert.orig
893 +base modified_untracked-revert.orig
893 +base modified_untracked-revert.orig
894 +wc modified_untracked-wc.orig
894 +wc modified_untracked-wc.orig
895 +wc modified_wc.orig
895 +wc modified_wc.orig
896 +base removed_revert
896 +base removed_revert
897 +base removed_untracked-revert
897 +base removed_untracked-revert
898 +wc removed_untracked-wc
898 +wc removed_untracked-wc
899 +wc removed_wc
899 +wc removed_wc
900
900
901 Test revert --all to "base" content
901 Test revert --all to "base" content
902 -----------------------------------
902 -----------------------------------
903
903
904 (setup from reference repo)
904 (setup from reference repo)
905
905
906 $ cp -r revert-ref revert-base-all
906 $ cp -r revert-ref revert-base-all
907 $ cd revert-base-all
907 $ cd revert-base-all
908
908
909 check revert output
909 check revert output
910
910
911 $ hg revert --all --rev 'desc(base)'
911 $ hg revert --all --rev 'desc(base)'
912 removing added_clean
912 removing added_clean
913 removing added_deleted
913 removing added_deleted
914 removing added_wc
914 removing added_wc
915 reverting clean_deleted
915 reverting clean_deleted
916 undeleting clean_removed
916 undeleting clean_removed
917 undeleting clean_untracked-clean
917 undeleting clean_untracked-clean
918 undeleting clean_untracked-revert
918 undeleting clean_untracked-revert
919 undeleting clean_untracked-wc
919 undeleting clean_untracked-wc
920 reverting clean_wc
920 reverting clean_wc
921 forgetting missing_deleted
921 forgetting missing_deleted
922 forgetting missing_wc
922 forgetting missing_wc
923 reverting modified_clean
923 reverting modified_clean
924 reverting modified_deleted
924 reverting modified_deleted
925 undeleting modified_removed
925 undeleting modified_removed
926 undeleting modified_untracked-clean
926 undeleting modified_untracked-clean
927 undeleting modified_untracked-revert
927 undeleting modified_untracked-revert
928 undeleting modified_untracked-wc
928 undeleting modified_untracked-wc
929 reverting modified_wc
929 reverting modified_wc
930 adding removed_clean
930 adding removed_clean
931 reverting removed_deleted
931 reverting removed_deleted
932 adding removed_removed
932 adding removed_removed
933 adding removed_untracked-clean
933 adding removed_untracked-clean
934 adding removed_untracked-revert
934 adding removed_untracked-revert
935 adding removed_untracked-wc
935 adding removed_untracked-wc
936 reverting removed_wc
936 reverting removed_wc
937
937
938 Compare resulting directory with revert target.
938 Compare resulting directory with revert target.
939
939
940 The diff is filtered to include change only. The only difference should be
940 The diff is filtered to include change only. The only difference should be
941 additional `.orig` backup file when applicable.
941 additional `.orig` backup file when applicable.
942
942
943 $ python ../dircontent.py > ../content-base-all.txt
943 $ python ../dircontent.py > ../content-base-all.txt
944 $ cd ..
944 $ cd ..
945 $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
945 $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
946 +parent added_untracked-clean
946 +parent added_untracked-clean
947 +wc added_untracked-wc
947 +wc added_untracked-wc
948 +wc added_wc.orig
948 +wc added_wc.orig
949 +wc clean_untracked-wc.orig
949 +wc clean_untracked-wc.orig
950 +wc clean_wc.orig
950 +wc clean_wc.orig
951 +wc missing_untracked-wc
951 +wc missing_untracked-wc
952 +wc missing_wc
952 +wc missing_wc
953 +parent modified_untracked-clean.orig
953 +parent modified_untracked-clean.orig
954 +wc modified_untracked-wc.orig
954 +wc modified_untracked-wc.orig
955 +wc modified_wc.orig
955 +wc modified_wc.orig
956 +wc removed_untracked-wc.orig
956 +wc removed_untracked-wc.orig
957 +wc removed_wc.orig
957 +wc removed_wc.orig
958
958
959 Test revert to parent content with explicit file name
959 Test revert to parent content with explicit file name
960 -----------------------------------------------------
960 -----------------------------------------------------
961
961
962 (setup from reference repo)
962 (setup from reference repo)
963
963
964 $ cp -r revert-ref revert-parent-explicit
964 $ cp -r revert-ref revert-parent-explicit
965 $ cd revert-parent-explicit
965 $ cd revert-parent-explicit
966
966
967 revert all files individually and check the output
967 revert all files individually and check the output
968 (output is expected to be different than in the --all case)
968 (output is expected to be different than in the --all case)
969
969
970 $ for file in `python ../gen-revert-cases.py filelist`; do
970 $ for file in `python ../gen-revert-cases.py filelist`; do
971 > echo '### revert for:' $file;
971 > echo '### revert for:' $file;
972 > hg revert $file;
972 > hg revert $file;
973 > echo
973 > echo
974 > done
974 > done
975 ### revert for: added_clean
975 ### revert for: added_clean
976 no changes needed to added_clean
976 no changes needed to added_clean
977
977
978 ### revert for: added_deleted
978 ### revert for: added_deleted
979
979
980 ### revert for: added_removed
980 ### revert for: added_removed
981
981
982 ### revert for: added_revert
982 ### revert for: added_revert
983
983
984 ### revert for: added_untracked-clean
984 ### revert for: added_untracked-clean
985
985
986 ### revert for: added_untracked-revert
986 ### revert for: added_untracked-revert
987
987
988 ### revert for: added_untracked-wc
988 ### revert for: added_untracked-wc
989
989
990 ### revert for: added_wc
990 ### revert for: added_wc
991
991
992 ### revert for: clean_clean
992 ### revert for: clean_clean
993 no changes needed to clean_clean
993 no changes needed to clean_clean
994
994
995 ### revert for: clean_deleted
995 ### revert for: clean_deleted
996
996
997 ### revert for: clean_removed
997 ### revert for: clean_removed
998
998
999 ### revert for: clean_revert
999 ### revert for: clean_revert
1000 no changes needed to clean_revert
1000 no changes needed to clean_revert
1001
1001
1002 ### revert for: clean_untracked-clean
1002 ### revert for: clean_untracked-clean
1003
1003
1004 ### revert for: clean_untracked-revert
1004 ### revert for: clean_untracked-revert
1005
1005
1006 ### revert for: clean_untracked-wc
1006 ### revert for: clean_untracked-wc
1007
1007
1008 ### revert for: clean_wc
1008 ### revert for: clean_wc
1009
1009
1010 ### revert for: missing_clean
1010 ### revert for: missing_clean
1011 missing_clean: no such file in rev * (glob)
1011 missing_clean: no such file in rev * (glob)
1012
1012
1013 ### revert for: missing_deleted
1013 ### revert for: missing_deleted
1014
1014
1015 ### revert for: missing_removed
1015 ### revert for: missing_removed
1016 missing_removed: no such file in rev * (glob)
1016 missing_removed: no such file in rev * (glob)
1017
1017
1018 ### revert for: missing_revert
1018 ### revert for: missing_revert
1019 missing_revert: no such file in rev * (glob)
1019 missing_revert: no such file in rev * (glob)
1020
1020
1021 ### revert for: missing_untracked-clean
1021 ### revert for: missing_untracked-clean
1022 missing_untracked-clean: no such file in rev * (glob)
1022 missing_untracked-clean: no such file in rev * (glob)
1023
1023
1024 ### revert for: missing_untracked-revert
1024 ### revert for: missing_untracked-revert
1025 missing_untracked-revert: no such file in rev * (glob)
1025 missing_untracked-revert: no such file in rev * (glob)
1026
1026
1027 ### revert for: missing_untracked-wc
1027 ### revert for: missing_untracked-wc
1028 file not managed: missing_untracked-wc
1028 file not managed: missing_untracked-wc
1029
1029
1030 ### revert for: missing_wc
1030 ### revert for: missing_wc
1031
1031
1032 ### revert for: modified_clean
1032 ### revert for: modified_clean
1033 no changes needed to modified_clean
1033 no changes needed to modified_clean
1034
1034
1035 ### revert for: modified_deleted
1035 ### revert for: modified_deleted
1036
1036
1037 ### revert for: modified_removed
1037 ### revert for: modified_removed
1038
1038
1039 ### revert for: modified_revert
1039 ### revert for: modified_revert
1040
1040
1041 ### revert for: modified_untracked-clean
1041 ### revert for: modified_untracked-clean
1042
1042
1043 ### revert for: modified_untracked-revert
1043 ### revert for: modified_untracked-revert
1044
1044
1045 ### revert for: modified_untracked-wc
1045 ### revert for: modified_untracked-wc
1046
1046
1047 ### revert for: modified_wc
1047 ### revert for: modified_wc
1048
1048
1049 ### revert for: removed_clean
1049 ### revert for: removed_clean
1050 removed_clean: no such file in rev * (glob)
1050 removed_clean: no such file in rev * (glob)
1051
1051
1052 ### revert for: removed_deleted
1052 ### revert for: removed_deleted
1053
1053
1054 ### revert for: removed_removed
1054 ### revert for: removed_removed
1055 removed_removed: no such file in rev * (glob)
1055 removed_removed: no such file in rev * (glob)
1056
1056
1057 ### revert for: removed_revert
1057 ### revert for: removed_revert
1058
1058
1059 ### revert for: removed_untracked-clean
1059 ### revert for: removed_untracked-clean
1060 removed_untracked-clean: no such file in rev * (glob)
1060 removed_untracked-clean: no such file in rev * (glob)
1061
1061
1062 ### revert for: removed_untracked-revert
1062 ### revert for: removed_untracked-revert
1063 file not managed: removed_untracked-revert
1063 file not managed: removed_untracked-revert
1064
1064
1065 ### revert for: removed_untracked-wc
1065 ### revert for: removed_untracked-wc
1066 file not managed: removed_untracked-wc
1066 file not managed: removed_untracked-wc
1067
1067
1068 ### revert for: removed_wc
1068 ### revert for: removed_wc
1069
1069
1070
1070
1071 check resulting directory againt the --all run
1071 check resulting directory againt the --all run
1072 (There should be no difference)
1072 (There should be no difference)
1073
1073
1074 $ python ../dircontent.py > ../content-parent-explicit.txt
1074 $ python ../dircontent.py > ../content-parent-explicit.txt
1075 $ cd ..
1075 $ cd ..
1076 $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
1076 $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
1077 [1]
1077 [1]
1078
1078
1079 Test revert to "base" content with explicit file name
1079 Test revert to "base" content with explicit file name
1080 -----------------------------------------------------
1080 -----------------------------------------------------
1081
1081
1082 (setup from reference repo)
1082 (setup from reference repo)
1083
1083
1084 $ cp -r revert-ref revert-base-explicit
1084 $ cp -r revert-ref revert-base-explicit
1085 $ cd revert-base-explicit
1085 $ cd revert-base-explicit
1086
1086
1087 revert all files individually and check the output
1087 revert all files individually and check the output
1088 (output is expected to be different than in the --all case)
1088 (output is expected to be different than in the --all case)
1089
1089
1090 $ for file in `python ../gen-revert-cases.py filelist`; do
1090 $ for file in `python ../gen-revert-cases.py filelist`; do
1091 > echo '### revert for:' $file;
1091 > echo '### revert for:' $file;
1092 > hg revert $file --rev 'desc(base)';
1092 > hg revert $file --rev 'desc(base)';
1093 > echo
1093 > echo
1094 > done
1094 > done
1095 ### revert for: added_clean
1095 ### revert for: added_clean
1096
1096
1097 ### revert for: added_deleted
1097 ### revert for: added_deleted
1098
1098
1099 ### revert for: added_removed
1099 ### revert for: added_removed
1100 no changes needed to added_removed
1100 no changes needed to added_removed
1101
1101
1102 ### revert for: added_revert
1102 ### revert for: added_revert
1103 no changes needed to added_revert
1103 no changes needed to added_revert
1104
1104
1105 ### revert for: added_untracked-clean
1105 ### revert for: added_untracked-clean
1106 no changes needed to added_untracked-clean
1106 no changes needed to added_untracked-clean
1107
1107
1108 ### revert for: added_untracked-revert
1108 ### revert for: added_untracked-revert
1109 no changes needed to added_untracked-revert
1109 no changes needed to added_untracked-revert
1110
1110
1111 ### revert for: added_untracked-wc
1111 ### revert for: added_untracked-wc
1112 no changes needed to added_untracked-wc
1112 no changes needed to added_untracked-wc
1113
1113
1114 ### revert for: added_wc
1114 ### revert for: added_wc
1115
1115
1116 ### revert for: clean_clean
1116 ### revert for: clean_clean
1117 no changes needed to clean_clean
1117 no changes needed to clean_clean
1118
1118
1119 ### revert for: clean_deleted
1119 ### revert for: clean_deleted
1120
1120
1121 ### revert for: clean_removed
1121 ### revert for: clean_removed
1122
1122
1123 ### revert for: clean_revert
1123 ### revert for: clean_revert
1124 no changes needed to clean_revert
1124 no changes needed to clean_revert
1125
1125
1126 ### revert for: clean_untracked-clean
1126 ### revert for: clean_untracked-clean
1127
1127
1128 ### revert for: clean_untracked-revert
1128 ### revert for: clean_untracked-revert
1129
1129
1130 ### revert for: clean_untracked-wc
1130 ### revert for: clean_untracked-wc
1131
1131
1132 ### revert for: clean_wc
1132 ### revert for: clean_wc
1133
1133
1134 ### revert for: missing_clean
1134 ### revert for: missing_clean
1135 missing_clean: no such file in rev * (glob)
1135 missing_clean: no such file in rev * (glob)
1136
1136
1137 ### revert for: missing_deleted
1137 ### revert for: missing_deleted
1138
1138
1139 ### revert for: missing_removed
1139 ### revert for: missing_removed
1140 missing_removed: no such file in rev * (glob)
1140 missing_removed: no such file in rev * (glob)
1141
1141
1142 ### revert for: missing_revert
1142 ### revert for: missing_revert
1143 missing_revert: no such file in rev * (glob)
1143 missing_revert: no such file in rev * (glob)
1144
1144
1145 ### revert for: missing_untracked-clean
1145 ### revert for: missing_untracked-clean
1146 missing_untracked-clean: no such file in rev * (glob)
1146 missing_untracked-clean: no such file in rev * (glob)
1147
1147
1148 ### revert for: missing_untracked-revert
1148 ### revert for: missing_untracked-revert
1149 missing_untracked-revert: no such file in rev * (glob)
1149 missing_untracked-revert: no such file in rev * (glob)
1150
1150
1151 ### revert for: missing_untracked-wc
1151 ### revert for: missing_untracked-wc
1152 file not managed: missing_untracked-wc
1152 file not managed: missing_untracked-wc
1153
1153
1154 ### revert for: missing_wc
1154 ### revert for: missing_wc
1155
1155
1156 ### revert for: modified_clean
1156 ### revert for: modified_clean
1157
1157
1158 ### revert for: modified_deleted
1158 ### revert for: modified_deleted
1159
1159
1160 ### revert for: modified_removed
1160 ### revert for: modified_removed
1161
1161
1162 ### revert for: modified_revert
1162 ### revert for: modified_revert
1163 no changes needed to modified_revert
1163 no changes needed to modified_revert
1164
1164
1165 ### revert for: modified_untracked-clean
1165 ### revert for: modified_untracked-clean
1166
1166
1167 ### revert for: modified_untracked-revert
1167 ### revert for: modified_untracked-revert
1168
1168
1169 ### revert for: modified_untracked-wc
1169 ### revert for: modified_untracked-wc
1170
1170
1171 ### revert for: modified_wc
1171 ### revert for: modified_wc
1172
1172
1173 ### revert for: removed_clean
1173 ### revert for: removed_clean
1174
1174
1175 ### revert for: removed_deleted
1175 ### revert for: removed_deleted
1176
1176
1177 ### revert for: removed_removed
1177 ### revert for: removed_removed
1178
1178
1179 ### revert for: removed_revert
1179 ### revert for: removed_revert
1180 no changes needed to removed_revert
1180 no changes needed to removed_revert
1181
1181
1182 ### revert for: removed_untracked-clean
1182 ### revert for: removed_untracked-clean
1183
1183
1184 ### revert for: removed_untracked-revert
1184 ### revert for: removed_untracked-revert
1185
1185
1186 ### revert for: removed_untracked-wc
1186 ### revert for: removed_untracked-wc
1187
1187
1188 ### revert for: removed_wc
1188 ### revert for: removed_wc
1189
1189
1190
1190
1191 check resulting directory againt the --all run
1191 check resulting directory againt the --all run
1192 (There should be no difference)
1192 (There should be no difference)
1193
1193
1194 $ python ../dircontent.py > ../content-base-explicit.txt
1194 $ python ../dircontent.py > ../content-base-explicit.txt
1195 $ cd ..
1195 $ cd ..
1196 $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
1196 $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
1197 [1]
1197 [1]
General Comments 0
You need to be logged in to leave comments. Login now