##// END OF EJS Templates
tests: ^ must be quoted when used on solaris sh...
Mads Kiilerich -
r16483:3c491036 stable
parent child Browse files
Show More
@@ -1,435 +1,435 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # check-code - a style and portability checker for Mercurial
3 # check-code - a style and portability checker for Mercurial
4 #
4 #
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
5 # Copyright 2010 Matt Mackall <mpm@selenic.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 import re, glob, os, sys
10 import re, glob, os, sys
11 import keyword
11 import keyword
12 import optparse
12 import optparse
13
13
14 def repquote(m):
14 def repquote(m):
15 t = re.sub(r"\w", "x", m.group('text'))
15 t = re.sub(r"\w", "x", m.group('text'))
16 t = re.sub(r"[^\s\nx]", "o", t)
16 t = re.sub(r"[^\s\nx]", "o", t)
17 return m.group('quote') + t + m.group('quote')
17 return m.group('quote') + t + m.group('quote')
18
18
19 def reppython(m):
19 def reppython(m):
20 comment = m.group('comment')
20 comment = m.group('comment')
21 if comment:
21 if comment:
22 return "#" * len(comment)
22 return "#" * len(comment)
23 return repquote(m)
23 return repquote(m)
24
24
25 def repcomment(m):
25 def repcomment(m):
26 return m.group(1) + "#" * len(m.group(2))
26 return m.group(1) + "#" * len(m.group(2))
27
27
28 def repccomment(m):
28 def repccomment(m):
29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
29 t = re.sub(r"((?<=\n) )|\S", "x", m.group(2))
30 return m.group(1) + t + "*/"
30 return m.group(1) + t + "*/"
31
31
32 def repcallspaces(m):
32 def repcallspaces(m):
33 t = re.sub(r"\n\s+", "\n", m.group(2))
33 t = re.sub(r"\n\s+", "\n", m.group(2))
34 return m.group(1) + t
34 return m.group(1) + t
35
35
36 def repinclude(m):
36 def repinclude(m):
37 return m.group(1) + "<foo>"
37 return m.group(1) + "<foo>"
38
38
39 def rephere(m):
39 def rephere(m):
40 t = re.sub(r"\S", "x", m.group(2))
40 t = re.sub(r"\S", "x", m.group(2))
41 return m.group(1) + t
41 return m.group(1) + t
42
42
43
43
44 testpats = [
44 testpats = [
45 [
45 [
46 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
46 (r'(pushd|popd)', "don't use 'pushd' or 'popd', use 'cd'"),
47 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
47 (r'\W\$?\(\([^\)\n]*\)\)', "don't use (()) or $(()), use 'expr'"),
48 (r'^function', "don't use 'function', use old style"),
48 (r'^function', "don't use 'function', use old style"),
49 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
49 (r'grep.*-q', "don't use 'grep -q', redirect to /dev/null"),
50 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
50 (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
51 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
51 (r'echo.*\\n', "don't use 'echo \\n', use printf"),
52 (r'echo -n', "don't use 'echo -n', use printf"),
52 (r'echo -n', "don't use 'echo -n', use printf"),
53 (r'^diff.*-\w*N', "don't use 'diff -N'"),
53 (r'^diff.*-\w*N', "don't use 'diff -N'"),
54 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
54 (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
55 (r'head -c', "don't use 'head -c', use 'dd'"),
55 (r'head -c', "don't use 'head -c', use 'dd'"),
56 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
56 (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
57 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
57 (r'ls.*-\w*R', "don't use 'ls -R', use 'find'"),
58 (r'printf.*\\\d{1,3}', "don't use 'printf \NNN', use Python"),
58 (r'printf.*\\\d{1,3}', "don't use 'printf \NNN', use Python"),
59 (r'printf.*\\x', "don't use printf \\x, use Python"),
59 (r'printf.*\\x', "don't use printf \\x, use Python"),
60 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
60 (r'\$\(.*\)', "don't use $(expr), use `expr`"),
61 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
61 (r'rm -rf \*', "don't use naked rm -rf, target a directory"),
62 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
62 (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
63 "use egrep for extended grep syntax"),
63 "use egrep for extended grep syntax"),
64 (r'/bin/', "don't use explicit paths for tools"),
64 (r'/bin/', "don't use explicit paths for tools"),
65 (r'\$PWD', "don't use $PWD, use `pwd`"),
65 (r'\$PWD', "don't use $PWD, use `pwd`"),
66 (r'[^\n]\Z', "no trailing newline"),
66 (r'[^\n]\Z', "no trailing newline"),
67 (r'export.*=', "don't export and assign at once"),
67 (r'export.*=', "don't export and assign at once"),
68 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\\^', "^ must be quoted"),
68 (r'^([^"\'\n]|("[^"\n]*")|(\'[^\'\n]*\'))*\^', "^ must be quoted"),
69 (r'^source\b', "don't use 'source', use '.'"),
69 (r'^source\b', "don't use 'source', use '.'"),
70 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
70 (r'touch -d', "don't use 'touch -d', use 'touch -t' instead"),
71 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
71 (r'ls +[^|\n-]+ +-', "options to 'ls' must come before filenames"),
72 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
72 (r'[^>\n]>\s*\$HGRCPATH', "don't overwrite $HGRCPATH, append to it"),
73 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
73 (r'^stop\(\)', "don't use 'stop' as a shell function name"),
74 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
74 (r'(\[|\btest\b).*-e ', "don't use 'test -e', use 'test -f'"),
75 (r'^alias\b.*=', "don't use alias, use a function"),
75 (r'^alias\b.*=', "don't use alias, use a function"),
76 ],
76 ],
77 # warnings
77 # warnings
78 []
78 []
79 ]
79 ]
80
80
81 testfilters = [
81 testfilters = [
82 (r"( *)(#([^\n]*\S)?)", repcomment),
82 (r"( *)(#([^\n]*\S)?)", repcomment),
83 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
83 (r"<<(\S+)((.|\n)*?\n\1)", rephere),
84 ]
84 ]
85
85
86 uprefix = r"^ \$ "
86 uprefix = r"^ \$ "
87 uprefixc = r"^ > "
87 uprefixc = r"^ > "
88 utestpats = [
88 utestpats = [
89 [
89 [
90 (r'^(\S| $ ).*(\S[ \t]+|^[ \t]+)\n', "trailing whitespace on non-output"),
90 (r'^(\S| $ ).*(\S[ \t]+|^[ \t]+)\n', "trailing whitespace on non-output"),
91 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
91 (uprefix + r'.*\|\s*sed', "use regex test output patterns instead of sed"),
92 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
92 (uprefix + r'(true|exit 0)', "explicit zero exit unnecessary"),
93 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
93 (uprefix + r'.*(?<!\[)\$\?', "explicit exit code checks unnecessary"),
94 (uprefix + r'.*\|\| echo.*(fail|error)',
94 (uprefix + r'.*\|\| echo.*(fail|error)',
95 "explicit exit code checks unnecessary"),
95 "explicit exit code checks unnecessary"),
96 (uprefix + r'set -e', "don't use set -e"),
96 (uprefix + r'set -e', "don't use set -e"),
97 (uprefixc + r'( *)\t', "don't use tabs to indent"),
97 (uprefixc + r'( *)\t', "don't use tabs to indent"),
98 (uprefixc + r'.*do\s*true;\s*done',
98 (uprefixc + r'.*do\s*true;\s*done',
99 "don't use true as loop body, use sleep 0"),
99 "don't use true as loop body, use sleep 0"),
100 ],
100 ],
101 # warnings
101 # warnings
102 []
102 []
103 ]
103 ]
104
104
105 for i in [0, 1]:
105 for i in [0, 1]:
106 for p, m in testpats[i]:
106 for p, m in testpats[i]:
107 if p.startswith(r'^'):
107 if p.startswith(r'^'):
108 p = uprefix + p[1:]
108 p = uprefix + p[1:]
109 else:
109 else:
110 p = uprefix + ".*" + p
110 p = uprefix + ".*" + p
111 utestpats[i].append((p, m))
111 utestpats[i].append((p, m))
112
112
113 utestfilters = [
113 utestfilters = [
114 (r"( *)(#([^\n]*\S)?)", repcomment),
114 (r"( *)(#([^\n]*\S)?)", repcomment),
115 ]
115 ]
116
116
117 pypats = [
117 pypats = [
118 [
118 [
119 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
119 (r'^\s*def\s*\w+\s*\(.*,\s*\(',
120 "tuple parameter unpacking not available in Python 3+"),
120 "tuple parameter unpacking not available in Python 3+"),
121 (r'lambda\s*\(.*,.*\)',
121 (r'lambda\s*\(.*,.*\)',
122 "tuple parameter unpacking not available in Python 3+"),
122 "tuple parameter unpacking not available in Python 3+"),
123 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
123 (r'(?<!def)\s+(cmp)\(', "cmp is not available in Python 3+"),
124 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
124 (r'\breduce\s*\(.*', "reduce is not available in Python 3+"),
125 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
125 (r'\.has_key\b', "dict.has_key is not available in Python 3+"),
126 (r'^\s*\t', "don't use tabs"),
126 (r'^\s*\t', "don't use tabs"),
127 (r'\S;\s*\n', "semicolon"),
127 (r'\S;\s*\n', "semicolon"),
128 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
128 (r'[^_]_\("[^"]+"\s*%', "don't use % inside _()"),
129 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
129 (r"[^_]_\('[^']+'\s*%", "don't use % inside _()"),
130 (r'\w,\w', "missing whitespace after ,"),
130 (r'\w,\w', "missing whitespace after ,"),
131 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
131 (r'\w[+/*\-<>]\w', "missing whitespace in expression"),
132 (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"),
132 (r'^\s+\w+=\w+[^,)\n]$', "missing whitespace in assignment"),
133 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
133 (r'(\s+)try:\n((?:\n|\1\s.*\n)+?)\1except.*?:\n'
134 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Py2.4'),
134 r'((?:\n|\1\s.*\n)+?)\1finally:', 'no try/except/finally in Py2.4'),
135 (r'.{85}', "line too long"),
135 (r'.{85}', "line too long"),
136 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
136 (r' x+[xo][\'"]\n\s+[\'"]x', 'string join across lines with no space'),
137 (r'[^\n]\Z', "no trailing newline"),
137 (r'[^\n]\Z', "no trailing newline"),
138 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
138 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
139 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', "don't use underbars in identifiers"),
139 # (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=', "don't use underbars in identifiers"),
140 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
140 (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
141 "don't use camelcase in identifiers"),
141 "don't use camelcase in identifiers"),
142 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
142 (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
143 "linebreak after :"),
143 "linebreak after :"),
144 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
144 (r'class\s[^( \n]+:', "old-style class, use class foo(object)"),
145 (r'class\s[^( \n]+\(\):',
145 (r'class\s[^( \n]+\(\):',
146 "class foo() not available in Python 2.4, use class foo(object)"),
146 "class foo() not available in Python 2.4, use class foo(object)"),
147 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
147 (r'\b(%s)\(' % '|'.join(keyword.kwlist),
148 "Python keyword is not a function"),
148 "Python keyword is not a function"),
149 (r',]', "unneeded trailing ',' in list"),
149 (r',]', "unneeded trailing ',' in list"),
150 # (r'class\s[A-Z][^\(]*\((?!Exception)',
150 # (r'class\s[A-Z][^\(]*\((?!Exception)',
151 # "don't capitalize non-exception classes"),
151 # "don't capitalize non-exception classes"),
152 # (r'in range\(', "use xrange"),
152 # (r'in range\(', "use xrange"),
153 # (r'^\s*print\s+', "avoid using print in core and extensions"),
153 # (r'^\s*print\s+', "avoid using print in core and extensions"),
154 (r'[\x80-\xff]', "non-ASCII character literal"),
154 (r'[\x80-\xff]', "non-ASCII character literal"),
155 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
155 (r'("\')\.format\(', "str.format() not available in Python 2.4"),
156 (r'^\s*with\s+', "with not available in Python 2.4"),
156 (r'^\s*with\s+', "with not available in Python 2.4"),
157 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
157 (r'\.isdisjoint\(', "set.isdisjoint not available in Python 2.4"),
158 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
158 (r'^\s*except.* as .*:', "except as not available in Python 2.4"),
159 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
159 (r'^\s*os\.path\.relpath', "relpath not available in Python 2.4"),
160 (r'(?<!def)\s+(any|all|format)\(',
160 (r'(?<!def)\s+(any|all|format)\(',
161 "any/all/format not available in Python 2.4"),
161 "any/all/format not available in Python 2.4"),
162 (r'(?<!def)\s+(callable)\(',
162 (r'(?<!def)\s+(callable)\(',
163 "callable not available in Python 3, use getattr(f, '__call__', None)"),
163 "callable not available in Python 3, use getattr(f, '__call__', None)"),
164 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
164 (r'if\s.*\selse', "if ... else form not available in Python 2.4"),
165 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
165 (r'^\s*(%s)\s\s' % '|'.join(keyword.kwlist),
166 "gratuitous whitespace after Python keyword"),
166 "gratuitous whitespace after Python keyword"),
167 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
167 (r'([\(\[][ \t]\S)|(\S[ \t][\)\]])', "gratuitous whitespace in () or []"),
168 # (r'\s\s=', "gratuitous whitespace before ="),
168 # (r'\s\s=', "gratuitous whitespace before ="),
169 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
169 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
170 "missing whitespace around operator"),
170 "missing whitespace around operator"),
171 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
171 (r'[^>< ](\+=|-=|!=|<>|<=|>=|<<=|>>=)\s',
172 "missing whitespace around operator"),
172 "missing whitespace around operator"),
173 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
173 (r'\s(\+=|-=|!=|<>|<=|>=|<<=|>>=)\S',
174 "missing whitespace around operator"),
174 "missing whitespace around operator"),
175 (r'[^^+=*/!<>&| -](\s=|=\s)[^= ]',
175 (r'[^^+=*/!<>&| -](\s=|=\s)[^= ]',
176 "wrong whitespace around ="),
176 "wrong whitespace around ="),
177 (r'raise Exception', "don't raise generic exceptions"),
177 (r'raise Exception', "don't raise generic exceptions"),
178 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
178 (r' is\s+(not\s+)?["\'0-9-]', "object comparison with literal"),
179 (r' [=!]=\s+(True|False|None)',
179 (r' [=!]=\s+(True|False|None)',
180 "comparison with singleton, use 'is' or 'is not' instead"),
180 "comparison with singleton, use 'is' or 'is not' instead"),
181 (r'^\s*(while|if) [01]:',
181 (r'^\s*(while|if) [01]:',
182 "use True/False for constant Boolean expression"),
182 "use True/False for constant Boolean expression"),
183 (r'(?:(?<!def)\s+|\()hasattr',
183 (r'(?:(?<!def)\s+|\()hasattr',
184 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
184 'hasattr(foo, bar) is broken, use util.safehasattr(foo, bar) instead'),
185 (r'opener\([^)]*\).read\(',
185 (r'opener\([^)]*\).read\(',
186 "use opener.read() instead"),
186 "use opener.read() instead"),
187 (r'BaseException', 'not in Py2.4, use Exception'),
187 (r'BaseException', 'not in Py2.4, use Exception'),
188 (r'os\.path\.relpath', 'os.path.relpath is not in Py2.5'),
188 (r'os\.path\.relpath', 'os.path.relpath is not in Py2.5'),
189 (r'opener\([^)]*\).write\(',
189 (r'opener\([^)]*\).write\(',
190 "use opener.write() instead"),
190 "use opener.write() instead"),
191 (r'[\s\(](open|file)\([^)]*\)\.read\(',
191 (r'[\s\(](open|file)\([^)]*\)\.read\(',
192 "use util.readfile() instead"),
192 "use util.readfile() instead"),
193 (r'[\s\(](open|file)\([^)]*\)\.write\(',
193 (r'[\s\(](open|file)\([^)]*\)\.write\(',
194 "use util.readfile() instead"),
194 "use util.readfile() instead"),
195 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
195 (r'^[\s\(]*(open(er)?|file)\([^)]*\)',
196 "always assign an opened file to a variable, and close it afterwards"),
196 "always assign an opened file to a variable, and close it afterwards"),
197 (r'[\s\(](open|file)\([^)]*\)\.',
197 (r'[\s\(](open|file)\([^)]*\)\.',
198 "always assign an opened file to a variable, and close it afterwards"),
198 "always assign an opened file to a variable, and close it afterwards"),
199 (r'(?i)descendent', "the proper spelling is descendAnt"),
199 (r'(?i)descendent', "the proper spelling is descendAnt"),
200 (r'\.debug\(\_', "don't mark debug messages for translation"),
200 (r'\.debug\(\_', "don't mark debug messages for translation"),
201 ],
201 ],
202 # warnings
202 # warnings
203 [
203 [
204 (r'.{81}', "warning: line over 80 characters"),
204 (r'.{81}', "warning: line over 80 characters"),
205 (r'^\s*except:$', "warning: naked except clause"),
205 (r'^\s*except:$', "warning: naked except clause"),
206 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
206 (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
207 "warning: unwrapped ui message"),
207 "warning: unwrapped ui message"),
208 ]
208 ]
209 ]
209 ]
210
210
211 pyfilters = [
211 pyfilters = [
212 (r"""(?msx)(?P<comment>\#.*?$)|
212 (r"""(?msx)(?P<comment>\#.*?$)|
213 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
213 ((?P<quote>('''|\"\"\"|(?<!')'(?!')|(?<!")"(?!")))
214 (?P<text>(([^\\]|\\.)*?))
214 (?P<text>(([^\\]|\\.)*?))
215 (?P=quote))""", reppython),
215 (?P=quote))""", reppython),
216 ]
216 ]
217
217
218 cpats = [
218 cpats = [
219 [
219 [
220 (r'//', "don't use //-style comments"),
220 (r'//', "don't use //-style comments"),
221 (r'^ ', "don't use spaces to indent"),
221 (r'^ ', "don't use spaces to indent"),
222 (r'\S\t', "don't use tabs except for indent"),
222 (r'\S\t', "don't use tabs except for indent"),
223 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
223 (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
224 (r'.{85}', "line too long"),
224 (r'.{85}', "line too long"),
225 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
225 (r'(while|if|do|for)\(', "use space after while/if/do/for"),
226 (r'return\(', "return is not a function"),
226 (r'return\(', "return is not a function"),
227 (r' ;', "no space before ;"),
227 (r' ;', "no space before ;"),
228 (r'\w+\* \w+', "use int *foo, not int* foo"),
228 (r'\w+\* \w+', "use int *foo, not int* foo"),
229 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
229 (r'\([^\)]+\) \w+', "use (int)foo, not (int) foo"),
230 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
230 (r'\w+ (\+\+|--)', "use foo++, not foo ++"),
231 (r'\w,\w', "missing whitespace after ,"),
231 (r'\w,\w', "missing whitespace after ,"),
232 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
232 (r'^[^#]\w[+/*]\w', "missing whitespace in expression"),
233 (r'^#\s+\w', "use #foo, not # foo"),
233 (r'^#\s+\w', "use #foo, not # foo"),
234 (r'[^\n]\Z', "no trailing newline"),
234 (r'[^\n]\Z', "no trailing newline"),
235 (r'^\s*#import\b', "use only #include in standard C code"),
235 (r'^\s*#import\b', "use only #include in standard C code"),
236 ],
236 ],
237 # warnings
237 # warnings
238 []
238 []
239 ]
239 ]
240
240
241 cfilters = [
241 cfilters = [
242 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
242 (r'(/\*)(((\*(?!/))|[^*])*)\*/', repccomment),
243 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
243 (r'''(?P<quote>(?<!")")(?P<text>([^"]|\\")+)"(?!")''', repquote),
244 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
244 (r'''(#\s*include\s+<)([^>]+)>''', repinclude),
245 (r'(\()([^)]+\))', repcallspaces),
245 (r'(\()([^)]+\))', repcallspaces),
246 ]
246 ]
247
247
248 inutilpats = [
248 inutilpats = [
249 [
249 [
250 (r'\bui\.', "don't use ui in util"),
250 (r'\bui\.', "don't use ui in util"),
251 ],
251 ],
252 # warnings
252 # warnings
253 []
253 []
254 ]
254 ]
255
255
256 inrevlogpats = [
256 inrevlogpats = [
257 [
257 [
258 (r'\brepo\.', "don't use repo in revlog"),
258 (r'\brepo\.', "don't use repo in revlog"),
259 ],
259 ],
260 # warnings
260 # warnings
261 []
261 []
262 ]
262 ]
263
263
264 checks = [
264 checks = [
265 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
265 ('python', r'.*\.(py|cgi)$', pyfilters, pypats),
266 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
266 ('test script', r'(.*/)?test-[^.~]*$', testfilters, testpats),
267 ('c', r'.*\.c$', cfilters, cpats),
267 ('c', r'.*\.c$', cfilters, cpats),
268 ('unified test', r'.*\.t$', utestfilters, utestpats),
268 ('unified test', r'.*\.t$', utestfilters, utestpats),
269 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
269 ('layering violation repo in revlog', r'mercurial/revlog\.py', pyfilters,
270 inrevlogpats),
270 inrevlogpats),
271 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
271 ('layering violation ui in util', r'mercurial/util\.py', pyfilters,
272 inutilpats),
272 inutilpats),
273 ]
273 ]
274
274
275 class norepeatlogger(object):
275 class norepeatlogger(object):
276 def __init__(self):
276 def __init__(self):
277 self._lastseen = None
277 self._lastseen = None
278
278
279 def log(self, fname, lineno, line, msg, blame):
279 def log(self, fname, lineno, line, msg, blame):
280 """print error related a to given line of a given file.
280 """print error related a to given line of a given file.
281
281
282 The faulty line will also be printed but only once in the case
282 The faulty line will also be printed but only once in the case
283 of multiple errors.
283 of multiple errors.
284
284
285 :fname: filename
285 :fname: filename
286 :lineno: line number
286 :lineno: line number
287 :line: actual content of the line
287 :line: actual content of the line
288 :msg: error message
288 :msg: error message
289 """
289 """
290 msgid = fname, lineno, line
290 msgid = fname, lineno, line
291 if msgid != self._lastseen:
291 if msgid != self._lastseen:
292 if blame:
292 if blame:
293 print "%s:%d (%s):" % (fname, lineno, blame)
293 print "%s:%d (%s):" % (fname, lineno, blame)
294 else:
294 else:
295 print "%s:%d:" % (fname, lineno)
295 print "%s:%d:" % (fname, lineno)
296 print " > %s" % line
296 print " > %s" % line
297 self._lastseen = msgid
297 self._lastseen = msgid
298 print " " + msg
298 print " " + msg
299
299
300 _defaultlogger = norepeatlogger()
300 _defaultlogger = norepeatlogger()
301
301
302 def getblame(f):
302 def getblame(f):
303 lines = []
303 lines = []
304 for l in os.popen('hg annotate -un %s' % f):
304 for l in os.popen('hg annotate -un %s' % f):
305 start, line = l.split(':', 1)
305 start, line = l.split(':', 1)
306 user, rev = start.split()
306 user, rev = start.split()
307 lines.append((line[1:-1], user, rev))
307 lines.append((line[1:-1], user, rev))
308 return lines
308 return lines
309
309
310 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
310 def checkfile(f, logfunc=_defaultlogger.log, maxerr=None, warnings=False,
311 blame=False, debug=False, lineno=True):
311 blame=False, debug=False, lineno=True):
312 """checks style and portability of a given file
312 """checks style and portability of a given file
313
313
314 :f: filepath
314 :f: filepath
315 :logfunc: function used to report error
315 :logfunc: function used to report error
316 logfunc(filename, linenumber, linecontent, errormessage)
316 logfunc(filename, linenumber, linecontent, errormessage)
317 :maxerr: number of error to display before arborting.
317 :maxerr: number of error to display before arborting.
318 Set to false (default) to report all errors
318 Set to false (default) to report all errors
319
319
320 return True if no error is found, False otherwise.
320 return True if no error is found, False otherwise.
321 """
321 """
322 blamecache = None
322 blamecache = None
323 result = True
323 result = True
324 for name, match, filters, pats in checks:
324 for name, match, filters, pats in checks:
325 if debug:
325 if debug:
326 print name, f
326 print name, f
327 fc = 0
327 fc = 0
328 if not re.match(match, f):
328 if not re.match(match, f):
329 if debug:
329 if debug:
330 print "Skipping %s for %s it doesn't match %s" % (
330 print "Skipping %s for %s it doesn't match %s" % (
331 name, match, f)
331 name, match, f)
332 continue
332 continue
333 fp = open(f)
333 fp = open(f)
334 pre = post = fp.read()
334 pre = post = fp.read()
335 fp.close()
335 fp.close()
336 if "no-" + "check-code" in pre:
336 if "no-" + "check-code" in pre:
337 if debug:
337 if debug:
338 print "Skipping %s for %s it has no- and check-code" % (
338 print "Skipping %s for %s it has no- and check-code" % (
339 name, f)
339 name, f)
340 break
340 break
341 for p, r in filters:
341 for p, r in filters:
342 post = re.sub(p, r, post)
342 post = re.sub(p, r, post)
343 if warnings:
343 if warnings:
344 pats = pats[0] + pats[1]
344 pats = pats[0] + pats[1]
345 else:
345 else:
346 pats = pats[0]
346 pats = pats[0]
347 # print post # uncomment to show filtered version
347 # print post # uncomment to show filtered version
348
348
349 if debug:
349 if debug:
350 print "Checking %s for %s" % (name, f)
350 print "Checking %s for %s" % (name, f)
351
351
352 prelines = None
352 prelines = None
353 errors = []
353 errors = []
354 for p, msg in pats:
354 for p, msg in pats:
355 # fix-up regexes for multiline searches
355 # fix-up regexes for multiline searches
356 po = p
356 po = p
357 # \s doesn't match \n
357 # \s doesn't match \n
358 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
358 p = re.sub(r'(?<!\\)\\s', r'[ \\t]', p)
359 # [^...] doesn't match newline
359 # [^...] doesn't match newline
360 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
360 p = re.sub(r'(?<!\\)\[\^', r'[^\\n', p)
361
361
362 #print po, '=>', p
362 #print po, '=>', p
363
363
364 pos = 0
364 pos = 0
365 n = 0
365 n = 0
366 for m in re.finditer(p, post, re.MULTILINE):
366 for m in re.finditer(p, post, re.MULTILINE):
367 if prelines is None:
367 if prelines is None:
368 prelines = pre.splitlines()
368 prelines = pre.splitlines()
369 postlines = post.splitlines(True)
369 postlines = post.splitlines(True)
370
370
371 start = m.start()
371 start = m.start()
372 while n < len(postlines):
372 while n < len(postlines):
373 step = len(postlines[n])
373 step = len(postlines[n])
374 if pos + step > start:
374 if pos + step > start:
375 break
375 break
376 pos += step
376 pos += step
377 n += 1
377 n += 1
378 l = prelines[n]
378 l = prelines[n]
379
379
380 if "check-code" + "-ignore" in l:
380 if "check-code" + "-ignore" in l:
381 if debug:
381 if debug:
382 print "Skipping %s for %s:%s (check-code -ignore)" % (
382 print "Skipping %s for %s:%s (check-code -ignore)" % (
383 name, f, n)
383 name, f, n)
384 continue
384 continue
385 bd = ""
385 bd = ""
386 if blame:
386 if blame:
387 bd = 'working directory'
387 bd = 'working directory'
388 if not blamecache:
388 if not blamecache:
389 blamecache = getblame(f)
389 blamecache = getblame(f)
390 if n < len(blamecache):
390 if n < len(blamecache):
391 bl, bu, br = blamecache[n]
391 bl, bu, br = blamecache[n]
392 if bl == l:
392 if bl == l:
393 bd = '%s@%s' % (bu, br)
393 bd = '%s@%s' % (bu, br)
394 errors.append((f, lineno and n + 1, l, msg, bd))
394 errors.append((f, lineno and n + 1, l, msg, bd))
395 result = False
395 result = False
396
396
397 errors.sort()
397 errors.sort()
398 for e in errors:
398 for e in errors:
399 logfunc(*e)
399 logfunc(*e)
400 fc += 1
400 fc += 1
401 if maxerr and fc >= maxerr:
401 if maxerr and fc >= maxerr:
402 print " (too many errors, giving up)"
402 print " (too many errors, giving up)"
403 break
403 break
404
404
405 return result
405 return result
406
406
407 if __name__ == "__main__":
407 if __name__ == "__main__":
408 parser = optparse.OptionParser("%prog [options] [files]")
408 parser = optparse.OptionParser("%prog [options] [files]")
409 parser.add_option("-w", "--warnings", action="store_true",
409 parser.add_option("-w", "--warnings", action="store_true",
410 help="include warning-level checks")
410 help="include warning-level checks")
411 parser.add_option("-p", "--per-file", type="int",
411 parser.add_option("-p", "--per-file", type="int",
412 help="max warnings per file")
412 help="max warnings per file")
413 parser.add_option("-b", "--blame", action="store_true",
413 parser.add_option("-b", "--blame", action="store_true",
414 help="use annotate to generate blame info")
414 help="use annotate to generate blame info")
415 parser.add_option("", "--debug", action="store_true",
415 parser.add_option("", "--debug", action="store_true",
416 help="show debug information")
416 help="show debug information")
417 parser.add_option("", "--nolineno", action="store_false",
417 parser.add_option("", "--nolineno", action="store_false",
418 dest='lineno', help="don't show line numbers")
418 dest='lineno', help="don't show line numbers")
419
419
420 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
420 parser.set_defaults(per_file=15, warnings=False, blame=False, debug=False,
421 lineno=True)
421 lineno=True)
422 (options, args) = parser.parse_args()
422 (options, args) = parser.parse_args()
423
423
424 if len(args) == 0:
424 if len(args) == 0:
425 check = glob.glob("*")
425 check = glob.glob("*")
426 else:
426 else:
427 check = args
427 check = args
428
428
429 ret = 0
429 ret = 0
430 for f in check:
430 for f in check:
431 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
431 if not checkfile(f, maxerr=options.per_file, warnings=options.warnings,
432 blame=options.blame, debug=options.debug,
432 blame=options.blame, debug=options.debug,
433 lineno=options.lineno):
433 lineno=options.lineno):
434 ret = 1
434 ret = 1
435 sys.exit(ret)
435 sys.exit(ret)
@@ -1,292 +1,292 b''
1 $ hg init
1 $ hg init
2
2
3 Setup:
3 Setup:
4
4
5 $ echo a >> a
5 $ echo a >> a
6 $ hg ci -Am 'base'
6 $ hg ci -Am 'base'
7 adding a
7 adding a
8
8
9 Refuse to amend public csets:
9 Refuse to amend public csets:
10
10
11 $ hg phase -r . -p
11 $ hg phase -r . -p
12 $ hg ci --amend
12 $ hg ci --amend
13 abort: cannot amend public changesets
13 abort: cannot amend public changesets
14 [255]
14 [255]
15 $ hg phase -r . -f -d
15 $ hg phase -r . -f -d
16
16
17 $ echo a >> a
17 $ echo a >> a
18 $ hg ci -Am 'base1'
18 $ hg ci -Am 'base1'
19
19
20 Nothing to amend:
20 Nothing to amend:
21
21
22 $ hg ci --amend
22 $ hg ci --amend
23 nothing changed
23 nothing changed
24 [1]
24 [1]
25
25
26 Amending changeset with changes in working dir:
26 Amending changeset with changes in working dir:
27
27
28 $ echo a >> a
28 $ echo a >> a
29 $ hg ci --amend -m 'amend base1'
29 $ hg ci --amend -m 'amend base1'
30 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg
30 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-amend-backup.hg
31 $ hg diff -c .
31 $ hg diff -c .
32 diff -r ad120869acf0 -r 9cd25b479c51 a
32 diff -r ad120869acf0 -r 9cd25b479c51 a
33 --- a/a Thu Jan 01 00:00:00 1970 +0000
33 --- a/a Thu Jan 01 00:00:00 1970 +0000
34 +++ b/a Thu Jan 01 00:00:00 1970 +0000
34 +++ b/a Thu Jan 01 00:00:00 1970 +0000
35 @@ -1,1 +1,3 @@
35 @@ -1,1 +1,3 @@
36 a
36 a
37 +a
37 +a
38 +a
38 +a
39 $ hg log
39 $ hg log
40 changeset: 1:9cd25b479c51
40 changeset: 1:9cd25b479c51
41 tag: tip
41 tag: tip
42 user: test
42 user: test
43 date: Thu Jan 01 00:00:00 1970 +0000
43 date: Thu Jan 01 00:00:00 1970 +0000
44 summary: amend base1
44 summary: amend base1
45
45
46 changeset: 0:ad120869acf0
46 changeset: 0:ad120869acf0
47 user: test
47 user: test
48 date: Thu Jan 01 00:00:00 1970 +0000
48 date: Thu Jan 01 00:00:00 1970 +0000
49 summary: base
49 summary: base
50
50
51
51
52 Add new file:
52 Add new file:
53
53
54 $ echo b > b
54 $ echo b > b
55 $ hg ci --amend -Am 'amend base1 new file'
55 $ hg ci --amend -Am 'amend base1 new file'
56 adding b
56 adding b
57 saved backup bundle to $TESTTMP/.hg/strip-backup/9cd25b479c51-amend-backup.hg
57 saved backup bundle to $TESTTMP/.hg/strip-backup/9cd25b479c51-amend-backup.hg
58
58
59 Remove file that was added in amended commit:
59 Remove file that was added in amended commit:
60
60
61 $ hg rm b
61 $ hg rm b
62 $ hg ci --amend -m 'amend base1 remove new file'
62 $ hg ci --amend -m 'amend base1 remove new file'
63 saved backup bundle to $TESTTMP/.hg/strip-backup/e2bb3ecffd2f-amend-backup.hg
63 saved backup bundle to $TESTTMP/.hg/strip-backup/e2bb3ecffd2f-amend-backup.hg
64
64
65 $ hg cat b
65 $ hg cat b
66 b: no such file in rev 664a9b2d60cd
66 b: no such file in rev 664a9b2d60cd
67 [1]
67 [1]
68
68
69 No changes, just a different message:
69 No changes, just a different message:
70
70
71 $ hg ci -v --amend -m 'no changes, new message'
71 $ hg ci -v --amend -m 'no changes, new message'
72 amending changeset 664a9b2d60cd
72 amending changeset 664a9b2d60cd
73 copying changeset 664a9b2d60cd to ad120869acf0
73 copying changeset 664a9b2d60cd to ad120869acf0
74 a
74 a
75 stripping amended changeset 664a9b2d60cd
75 stripping amended changeset 664a9b2d60cd
76 1 changesets found
76 1 changesets found
77 saved backup bundle to $TESTTMP/.hg/strip-backup/664a9b2d60cd-amend-backup.hg
77 saved backup bundle to $TESTTMP/.hg/strip-backup/664a9b2d60cd-amend-backup.hg
78 1 changesets found
78 1 changesets found
79 adding branch
79 adding branch
80 adding changesets
80 adding changesets
81 adding manifests
81 adding manifests
82 adding file changes
82 adding file changes
83 added 1 changesets with 1 changes to 1 files
83 added 1 changesets with 1 changes to 1 files
84 committed changeset 1:ea6e356ff2ad
84 committed changeset 1:ea6e356ff2ad
85 $ hg diff -c .
85 $ hg diff -c .
86 diff -r ad120869acf0 -r ea6e356ff2ad a
86 diff -r ad120869acf0 -r ea6e356ff2ad a
87 --- a/a Thu Jan 01 00:00:00 1970 +0000
87 --- a/a Thu Jan 01 00:00:00 1970 +0000
88 +++ b/a Thu Jan 01 00:00:00 1970 +0000
88 +++ b/a Thu Jan 01 00:00:00 1970 +0000
89 @@ -1,1 +1,3 @@
89 @@ -1,1 +1,3 @@
90 a
90 a
91 +a
91 +a
92 +a
92 +a
93 $ hg log
93 $ hg log
94 changeset: 1:ea6e356ff2ad
94 changeset: 1:ea6e356ff2ad
95 tag: tip
95 tag: tip
96 user: test
96 user: test
97 date: Thu Jan 01 00:00:00 1970 +0000
97 date: Thu Jan 01 00:00:00 1970 +0000
98 summary: no changes, new message
98 summary: no changes, new message
99
99
100 changeset: 0:ad120869acf0
100 changeset: 0:ad120869acf0
101 user: test
101 user: test
102 date: Thu Jan 01 00:00:00 1970 +0000
102 date: Thu Jan 01 00:00:00 1970 +0000
103 summary: base
103 summary: base
104
104
105
105
106 Disable default date on commit so when -d isn't given, the old date is preserved:
106 Disable default date on commit so when -d isn't given, the old date is preserved:
107
107
108 $ echo '[defaults]' >> $HGRCPATH
108 $ echo '[defaults]' >> $HGRCPATH
109 $ echo 'commit=' >> $HGRCPATH
109 $ echo 'commit=' >> $HGRCPATH
110
110
111 Test -u/-d:
111 Test -u/-d:
112
112
113 $ hg ci --amend -u foo -d '1 0'
113 $ hg ci --amend -u foo -d '1 0'
114 saved backup bundle to $TESTTMP/.hg/strip-backup/ea6e356ff2ad-amend-backup.hg
114 saved backup bundle to $TESTTMP/.hg/strip-backup/ea6e356ff2ad-amend-backup.hg
115 $ echo a >> a
115 $ echo a >> a
116 $ hg ci --amend -u foo -d '1 0'
116 $ hg ci --amend -u foo -d '1 0'
117 saved backup bundle to $TESTTMP/.hg/strip-backup/377b91ce8b56-amend-backup.hg
117 saved backup bundle to $TESTTMP/.hg/strip-backup/377b91ce8b56-amend-backup.hg
118 $ hg log -r .
118 $ hg log -r .
119 changeset: 1:2c94e4a5756f
119 changeset: 1:2c94e4a5756f
120 tag: tip
120 tag: tip
121 user: foo
121 user: foo
122 date: Thu Jan 01 00:00:01 1970 +0000
122 date: Thu Jan 01 00:00:01 1970 +0000
123 summary: no changes, new message
123 summary: no changes, new message
124
124
125
125
126 Open editor with old commit message if a message isn't given otherwise:
126 Open editor with old commit message if a message isn't given otherwise:
127
127
128 $ cat > editor << '__EOF__'
128 $ cat > editor << '__EOF__'
129 > #!/bin/sh
129 > #!/bin/sh
130 > cat $1
130 > cat $1
131 > echo "another precious commit message" > "$1"
131 > echo "another precious commit message" > "$1"
132 > __EOF__
132 > __EOF__
133 $ chmod +x editor
133 $ chmod +x editor
134 $ HGEDITOR="'`pwd`'"/editor hg commit --amend -v
134 $ HGEDITOR="'`pwd`'"/editor hg commit --amend -v
135 amending changeset 2c94e4a5756f
135 amending changeset 2c94e4a5756f
136 copying changeset 2c94e4a5756f to ad120869acf0
136 copying changeset 2c94e4a5756f to ad120869acf0
137 no changes, new message
137 no changes, new message
138
138
139
139
140 HG: Enter commit message. Lines beginning with 'HG:' are removed.
140 HG: Enter commit message. Lines beginning with 'HG:' are removed.
141 HG: Leave message empty to abort commit.
141 HG: Leave message empty to abort commit.
142 HG: --
142 HG: --
143 HG: user: foo
143 HG: user: foo
144 HG: branch 'default'
144 HG: branch 'default'
145 HG: changed a
145 HG: changed a
146 a
146 a
147 stripping amended changeset 2c94e4a5756f
147 stripping amended changeset 2c94e4a5756f
148 1 changesets found
148 1 changesets found
149 saved backup bundle to $TESTTMP/.hg/strip-backup/2c94e4a5756f-amend-backup.hg
149 saved backup bundle to $TESTTMP/.hg/strip-backup/2c94e4a5756f-amend-backup.hg
150 1 changesets found
150 1 changesets found
151 adding branch
151 adding branch
152 adding changesets
152 adding changesets
153 adding manifests
153 adding manifests
154 adding file changes
154 adding file changes
155 added 1 changesets with 1 changes to 1 files
155 added 1 changesets with 1 changes to 1 files
156 committed changeset 1:ffb49186f961
156 committed changeset 1:ffb49186f961
157
157
158 Same, but with changes in working dir (different code path):
158 Same, but with changes in working dir (different code path):
159
159
160 $ echo a >> a
160 $ echo a >> a
161 $ HGEDITOR="'`pwd`'"/editor hg commit --amend -v
161 $ HGEDITOR="'`pwd`'"/editor hg commit --amend -v
162 amending changeset ffb49186f961
162 amending changeset ffb49186f961
163 another precious commit message
163 another precious commit message
164
164
165
165
166 HG: Enter commit message. Lines beginning with 'HG:' are removed.
166 HG: Enter commit message. Lines beginning with 'HG:' are removed.
167 HG: Leave message empty to abort commit.
167 HG: Leave message empty to abort commit.
168 HG: --
168 HG: --
169 HG: user: foo
169 HG: user: foo
170 HG: branch 'default'
170 HG: branch 'default'
171 HG: changed a
171 HG: changed a
172 a
172 a
173 copying changeset 27f3aacd3011 to ad120869acf0
173 copying changeset 27f3aacd3011 to ad120869acf0
174 a
174 a
175 stripping intermediate changeset 27f3aacd3011
175 stripping intermediate changeset 27f3aacd3011
176 stripping amended changeset ffb49186f961
176 stripping amended changeset ffb49186f961
177 2 changesets found
177 2 changesets found
178 saved backup bundle to $TESTTMP/.hg/strip-backup/ffb49186f961-amend-backup.hg
178 saved backup bundle to $TESTTMP/.hg/strip-backup/ffb49186f961-amend-backup.hg
179 1 changesets found
179 1 changesets found
180 adding branch
180 adding branch
181 adding changesets
181 adding changesets
182 adding manifests
182 adding manifests
183 adding file changes
183 adding file changes
184 added 1 changesets with 1 changes to 1 files
184 added 1 changesets with 1 changes to 1 files
185 committed changeset 1:fb6cca43446f
185 committed changeset 1:fb6cca43446f
186
186
187 $ rm editor
187 $ rm editor
188 $ hg log -r .
188 $ hg log -r .
189 changeset: 1:fb6cca43446f
189 changeset: 1:fb6cca43446f
190 tag: tip
190 tag: tip
191 user: foo
191 user: foo
192 date: Thu Jan 01 00:00:01 1970 +0000
192 date: Thu Jan 01 00:00:01 1970 +0000
193 summary: another precious commit message
193 summary: another precious commit message
194
194
195
195
196 Moving bookmarks, preserve active bookmark:
196 Moving bookmarks, preserve active bookmark:
197
197
198 $ hg book book1
198 $ hg book book1
199 $ hg book book2
199 $ hg book book2
200 $ hg ci --amend -m 'move bookmarks'
200 $ hg ci --amend -m 'move bookmarks'
201 saved backup bundle to $TESTTMP/.hg/strip-backup/fb6cca43446f-amend-backup.hg
201 saved backup bundle to $TESTTMP/.hg/strip-backup/fb6cca43446f-amend-backup.hg
202 $ hg book
202 $ hg book
203 book1 1:0cf1c7a51bcf
203 book1 1:0cf1c7a51bcf
204 * book2 1:0cf1c7a51bcf
204 * book2 1:0cf1c7a51bcf
205 $ echo a >> a
205 $ echo a >> a
206 $ hg ci --amend -m 'move bookmarks'
206 $ hg ci --amend -m 'move bookmarks'
207 saved backup bundle to $TESTTMP/.hg/strip-backup/0cf1c7a51bcf-amend-backup.hg
207 saved backup bundle to $TESTTMP/.hg/strip-backup/0cf1c7a51bcf-amend-backup.hg
208 $ hg book
208 $ hg book
209 book1 1:7344472bd951
209 book1 1:7344472bd951
210 * book2 1:7344472bd951
210 * book2 1:7344472bd951
211
211
212 $ echo '[defaults]' >> $HGRCPATH
212 $ echo '[defaults]' >> $HGRCPATH
213 $ echo "commit=-d '0 0'" >> $HGRCPATH
213 $ echo "commit=-d '0 0'" >> $HGRCPATH
214
214
215 Moving branches:
215 Moving branches:
216
216
217 $ hg branch foo
217 $ hg branch foo
218 marked working directory as branch foo
218 marked working directory as branch foo
219 (branches are permanent and global, did you want a bookmark?)
219 (branches are permanent and global, did you want a bookmark?)
220 $ echo a >> a
220 $ echo a >> a
221 $ hg ci -m 'branch foo'
221 $ hg ci -m 'branch foo'
222 $ hg branch default -f
222 $ hg branch default -f
223 marked working directory as branch default
223 marked working directory as branch default
224 (branches are permanent and global, did you want a bookmark?)
224 (branches are permanent and global, did you want a bookmark?)
225 $ hg ci --amend -m 'back to default'
225 $ hg ci --amend -m 'back to default'
226 saved backup bundle to $TESTTMP/.hg/strip-backup/1661ca36a2db-amend-backup.hg
226 saved backup bundle to $TESTTMP/.hg/strip-backup/1661ca36a2db-amend-backup.hg
227 $ hg branches
227 $ hg branches
228 default 2:f24ee5961967
228 default 2:f24ee5961967
229
229
230 Close branch:
230 Close branch:
231
231
232 $ hg up -q 0
232 $ hg up -q 0
233 $ echo b >> b
233 $ echo b >> b
234 $ hg branch foo
234 $ hg branch foo
235 marked working directory as branch foo
235 marked working directory as branch foo
236 (branches are permanent and global, did you want a bookmark?)
236 (branches are permanent and global, did you want a bookmark?)
237 $ hg ci -Am 'fork'
237 $ hg ci -Am 'fork'
238 adding b
238 adding b
239 $ echo b >> b
239 $ echo b >> b
240 $ hg ci -mb
240 $ hg ci -mb
241 $ hg ci --amend --close-branch -m 'closing branch foo'
241 $ hg ci --amend --close-branch -m 'closing branch foo'
242 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg
242 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-amend-backup.hg
243
243
244 Same thing, different code path:
244 Same thing, different code path:
245
245
246 $ echo b >> b
246 $ echo b >> b
247 $ hg ci -m 'reopen branch'
247 $ hg ci -m 'reopen branch'
248 reopening closed branch head 4
248 reopening closed branch head 4
249 $ echo b >> b
249 $ echo b >> b
250 $ hg ci --amend --close-branch
250 $ hg ci --amend --close-branch
251 saved backup bundle to $TESTTMP/.hg/strip-backup/5e302dcc12b8-amend-backup.hg
251 saved backup bundle to $TESTTMP/.hg/strip-backup/5e302dcc12b8-amend-backup.hg
252 $ hg branches
252 $ hg branches
253 default 2:f24ee5961967
253 default 2:f24ee5961967
254
254
255 Refuse to amend merges:
255 Refuse to amend merges:
256
256
257 $ hg up -q default
257 $ hg up -q default
258 $ hg merge foo
258 $ hg merge foo
259 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 (branch merge, don't forget to commit)
260 (branch merge, don't forget to commit)
261 $ hg ci --amend
261 $ hg ci --amend
262 abort: cannot amend while merging
262 abort: cannot amend while merging
263 [255]
263 [255]
264 $ hg ci -m 'merge'
264 $ hg ci -m 'merge'
265 $ hg ci --amend
265 $ hg ci --amend
266 abort: cannot amend merge changesets
266 abort: cannot amend merge changesets
267 [255]
267 [255]
268
268
269 Follow copies/renames:
269 Follow copies/renames:
270
270
271 $ hg mv b c
271 $ hg mv b c
272 $ hg ci -m 'b -> c'
272 $ hg ci -m 'b -> c'
273 $ hg mv c d
273 $ hg mv c d
274 $ hg ci --amend -m 'b -> d'
274 $ hg ci --amend -m 'b -> d'
275 saved backup bundle to $TESTTMP/.hg/strip-backup/9c207120aa98-amend-backup.hg
275 saved backup bundle to $TESTTMP/.hg/strip-backup/9c207120aa98-amend-backup.hg
276 $ hg st --rev .^ --copies d
276 $ hg st --rev '.^' --copies d
277 A d
277 A d
278 b
278 b
279 $ hg cp d e
279 $ hg cp d e
280 $ hg ci -m 'e = d'
280 $ hg ci -m 'e = d'
281 $ hg cp e f
281 $ hg cp e f
282 $ hg ci --amend -m 'f = d'
282 $ hg ci --amend -m 'f = d'
283 saved backup bundle to $TESTTMP/.hg/strip-backup/fda2b3b27b22-amend-backup.hg
283 saved backup bundle to $TESTTMP/.hg/strip-backup/fda2b3b27b22-amend-backup.hg
284 $ hg st --rev .^ --copies f
284 $ hg st --rev '.^' --copies f
285 A f
285 A f
286 d
286 d
287
287
288 Can't rollback an amend:
288 Can't rollback an amend:
289
289
290 $ hg rollback
290 $ hg rollback
291 no rollback information available
291 no rollback information available
292 [1]
292 [1]
General Comments 0
You need to be logged in to leave comments. Login now