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