##// END OF EJS Templates
cmdutil: make in-memory changes visible to external editor (issue4378)...
FUJIWARA Katsunori -
r26750:9f9ec4ab default
parent child Browse files
Show More
@@ -1,3377 +1,3382 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import hex, bin, nullid, nullrev, short
9 9 from i18n import _
10 10 import os, sys, errno, re, tempfile, cStringIO, shutil
11 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 12 import match as matchmod
13 13 import repair, graphmod, revset, phases, obsolete, pathutil
14 14 import changelog
15 15 import bookmarks
16 16 import encoding
17 17 import formatter
18 18 import crecord as crecordmod
19 19 import lock as lockmod
20 20
21 21 def ishunk(x):
22 22 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
23 23 return isinstance(x, hunkclasses)
24 24
25 25 def newandmodified(chunks, originalchunks):
26 26 newlyaddedandmodifiedfiles = set()
27 27 for chunk in chunks:
28 28 if ishunk(chunk) and chunk.header.isnewfile() and chunk not in \
29 29 originalchunks:
30 30 newlyaddedandmodifiedfiles.add(chunk.header.filename())
31 31 return newlyaddedandmodifiedfiles
32 32
33 33 def parsealiases(cmd):
34 34 return cmd.lstrip("^").split("|")
35 35
36 36 def setupwrapcolorwrite(ui):
37 37 # wrap ui.write so diff output can be labeled/colorized
38 38 def wrapwrite(orig, *args, **kw):
39 39 label = kw.pop('label', '')
40 40 for chunk, l in patch.difflabel(lambda: args):
41 41 orig(chunk, label=label + l)
42 42
43 43 oldwrite = ui.write
44 44 def wrap(*args, **kwargs):
45 45 return wrapwrite(oldwrite, *args, **kwargs)
46 46 setattr(ui, 'write', wrap)
47 47 return oldwrite
48 48
49 49 def filterchunks(ui, originalhunks, usecurses, testfile, operation=None):
50 50 if usecurses:
51 51 if testfile:
52 52 recordfn = crecordmod.testdecorator(testfile,
53 53 crecordmod.testchunkselector)
54 54 else:
55 55 recordfn = crecordmod.chunkselector
56 56
57 57 return crecordmod.filterpatch(ui, originalhunks, recordfn, operation)
58 58
59 59 else:
60 60 return patch.filterpatch(ui, originalhunks, operation)
61 61
62 62 def recordfilter(ui, originalhunks, operation=None):
63 63 """ Prompts the user to filter the originalhunks and return a list of
64 64 selected hunks.
65 65 *operation* is used for ui purposes to indicate the user
66 66 what kind of filtering they are doing: reverting, commiting, shelving, etc.
67 67 *operation* has to be a translated string.
68 68 """
69 69 usecurses = ui.configbool('experimental', 'crecord', False)
70 70 testfile = ui.config('experimental', 'crecordtest', None)
71 71 oldwrite = setupwrapcolorwrite(ui)
72 72 try:
73 73 newchunks = filterchunks(ui, originalhunks, usecurses, testfile,
74 74 operation)
75 75 finally:
76 76 ui.write = oldwrite
77 77 return newchunks
78 78
79 79 def dorecord(ui, repo, commitfunc, cmdsuggest, backupall,
80 80 filterfn, *pats, **opts):
81 81 import merge as mergemod
82 82
83 83 if not ui.interactive():
84 84 if cmdsuggest:
85 85 msg = _('running non-interactively, use %s instead') % cmdsuggest
86 86 else:
87 87 msg = _('running non-interactively')
88 88 raise error.Abort(msg)
89 89
90 90 # make sure username is set before going interactive
91 91 if not opts.get('user'):
92 92 ui.username() # raise exception, username not provided
93 93
94 94 def recordfunc(ui, repo, message, match, opts):
95 95 """This is generic record driver.
96 96
97 97 Its job is to interactively filter local changes, and
98 98 accordingly prepare working directory into a state in which the
99 99 job can be delegated to a non-interactive commit command such as
100 100 'commit' or 'qrefresh'.
101 101
102 102 After the actual job is done by non-interactive command, the
103 103 working directory is restored to its original state.
104 104
105 105 In the end we'll record interesting changes, and everything else
106 106 will be left in place, so the user can continue working.
107 107 """
108 108
109 109 checkunfinished(repo, commit=True)
110 110 merge = len(repo[None].parents()) > 1
111 111 if merge:
112 112 raise error.Abort(_('cannot partially commit a merge '
113 113 '(use "hg commit" instead)'))
114 114
115 115 status = repo.status(match=match)
116 116 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
117 117 diffopts.nodates = True
118 118 diffopts.git = True
119 119 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
120 120 originalchunks = patch.parsepatch(originaldiff)
121 121
122 122 # 1. filter patch, so we have intending-to apply subset of it
123 123 try:
124 124 chunks = filterfn(ui, originalchunks)
125 125 except patch.PatchError as err:
126 126 raise error.Abort(_('error parsing patch: %s') % err)
127 127
128 128 # We need to keep a backup of files that have been newly added and
129 129 # modified during the recording process because there is a previous
130 130 # version without the edit in the workdir
131 131 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
132 132 contenders = set()
133 133 for h in chunks:
134 134 try:
135 135 contenders.update(set(h.files()))
136 136 except AttributeError:
137 137 pass
138 138
139 139 changed = status.modified + status.added + status.removed
140 140 newfiles = [f for f in changed if f in contenders]
141 141 if not newfiles:
142 142 ui.status(_('no changes to record\n'))
143 143 return 0
144 144
145 145 modified = set(status.modified)
146 146
147 147 # 2. backup changed files, so we can restore them in the end
148 148
149 149 if backupall:
150 150 tobackup = changed
151 151 else:
152 152 tobackup = [f for f in newfiles if f in modified or f in \
153 153 newlyaddedandmodifiedfiles]
154 154 backups = {}
155 155 if tobackup:
156 156 backupdir = repo.join('record-backups')
157 157 try:
158 158 os.mkdir(backupdir)
159 159 except OSError as err:
160 160 if err.errno != errno.EEXIST:
161 161 raise
162 162 try:
163 163 # backup continues
164 164 for f in tobackup:
165 165 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
166 166 dir=backupdir)
167 167 os.close(fd)
168 168 ui.debug('backup %r as %r\n' % (f, tmpname))
169 169 util.copyfile(repo.wjoin(f), tmpname)
170 170 shutil.copystat(repo.wjoin(f), tmpname)
171 171 backups[f] = tmpname
172 172
173 173 fp = cStringIO.StringIO()
174 174 for c in chunks:
175 175 fname = c.filename()
176 176 if fname in backups:
177 177 c.write(fp)
178 178 dopatch = fp.tell()
179 179 fp.seek(0)
180 180
181 181 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
182 182 # 3a. apply filtered patch to clean repo (clean)
183 183 if backups:
184 184 # Equivalent to hg.revert
185 185 choices = lambda key: key in backups
186 186 mergemod.update(repo, repo.dirstate.p1(),
187 187 False, True, choices)
188 188
189 189 # 3b. (apply)
190 190 if dopatch:
191 191 try:
192 192 ui.debug('applying patch\n')
193 193 ui.debug(fp.getvalue())
194 194 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
195 195 except patch.PatchError as err:
196 196 raise error.Abort(str(err))
197 197 del fp
198 198
199 199 # 4. We prepared working directory according to filtered
200 200 # patch. Now is the time to delegate the job to
201 201 # commit/qrefresh or the like!
202 202
203 203 # Make all of the pathnames absolute.
204 204 newfiles = [repo.wjoin(nf) for nf in newfiles]
205 205 return commitfunc(ui, repo, *newfiles, **opts)
206 206 finally:
207 207 # 5. finally restore backed-up files
208 208 try:
209 209 dirstate = repo.dirstate
210 210 for realname, tmpname in backups.iteritems():
211 211 ui.debug('restoring %r to %r\n' % (tmpname, realname))
212 212
213 213 if dirstate[realname] == 'n':
214 214 # without normallookup, restoring timestamp
215 215 # may cause partially committed files
216 216 # to be treated as unmodified
217 217 dirstate.normallookup(realname)
218 218
219 219 util.copyfile(tmpname, repo.wjoin(realname))
220 220 # Our calls to copystat() here and above are a
221 221 # hack to trick any editors that have f open that
222 222 # we haven't modified them.
223 223 #
224 224 # Also note that this racy as an editor could
225 225 # notice the file's mtime before we've finished
226 226 # writing it.
227 227 shutil.copystat(tmpname, repo.wjoin(realname))
228 228 os.unlink(tmpname)
229 229 if tobackup:
230 230 os.rmdir(backupdir)
231 231 except OSError:
232 232 pass
233 233
234 234 def recordinwlock(ui, repo, message, match, opts):
235 235 wlock = repo.wlock()
236 236 try:
237 237 return recordfunc(ui, repo, message, match, opts)
238 238 finally:
239 239 wlock.release()
240 240
241 241 return commit(ui, repo, recordinwlock, pats, opts)
242 242
243 243 def findpossible(cmd, table, strict=False):
244 244 """
245 245 Return cmd -> (aliases, command table entry)
246 246 for each matching command.
247 247 Return debug commands (or their aliases) only if no normal command matches.
248 248 """
249 249 choice = {}
250 250 debugchoice = {}
251 251
252 252 if cmd in table:
253 253 # short-circuit exact matches, "log" alias beats "^log|history"
254 254 keys = [cmd]
255 255 else:
256 256 keys = table.keys()
257 257
258 258 allcmds = []
259 259 for e in keys:
260 260 aliases = parsealiases(e)
261 261 allcmds.extend(aliases)
262 262 found = None
263 263 if cmd in aliases:
264 264 found = cmd
265 265 elif not strict:
266 266 for a in aliases:
267 267 if a.startswith(cmd):
268 268 found = a
269 269 break
270 270 if found is not None:
271 271 if aliases[0].startswith("debug") or found.startswith("debug"):
272 272 debugchoice[found] = (aliases, table[e])
273 273 else:
274 274 choice[found] = (aliases, table[e])
275 275
276 276 if not choice and debugchoice:
277 277 choice = debugchoice
278 278
279 279 return choice, allcmds
280 280
281 281 def findcmd(cmd, table, strict=True):
282 282 """Return (aliases, command table entry) for command string."""
283 283 choice, allcmds = findpossible(cmd, table, strict)
284 284
285 285 if cmd in choice:
286 286 return choice[cmd]
287 287
288 288 if len(choice) > 1:
289 289 clist = choice.keys()
290 290 clist.sort()
291 291 raise error.AmbiguousCommand(cmd, clist)
292 292
293 293 if choice:
294 294 return choice.values()[0]
295 295
296 296 raise error.UnknownCommand(cmd, allcmds)
297 297
298 298 def findrepo(p):
299 299 while not os.path.isdir(os.path.join(p, ".hg")):
300 300 oldp, p = p, os.path.dirname(p)
301 301 if p == oldp:
302 302 return None
303 303
304 304 return p
305 305
306 306 def bailifchanged(repo, merge=True):
307 307 if merge and repo.dirstate.p2() != nullid:
308 308 raise error.Abort(_('outstanding uncommitted merge'))
309 309 modified, added, removed, deleted = repo.status()[:4]
310 310 if modified or added or removed or deleted:
311 311 raise error.Abort(_('uncommitted changes'))
312 312 ctx = repo[None]
313 313 for s in sorted(ctx.substate):
314 314 ctx.sub(s).bailifchanged()
315 315
316 316 def logmessage(ui, opts):
317 317 """ get the log message according to -m and -l option """
318 318 message = opts.get('message')
319 319 logfile = opts.get('logfile')
320 320
321 321 if message and logfile:
322 322 raise error.Abort(_('options --message and --logfile are mutually '
323 323 'exclusive'))
324 324 if not message and logfile:
325 325 try:
326 326 if logfile == '-':
327 327 message = ui.fin.read()
328 328 else:
329 329 message = '\n'.join(util.readfile(logfile).splitlines())
330 330 except IOError as inst:
331 331 raise error.Abort(_("can't read commit message '%s': %s") %
332 332 (logfile, inst.strerror))
333 333 return message
334 334
335 335 def mergeeditform(ctxorbool, baseformname):
336 336 """return appropriate editform name (referencing a committemplate)
337 337
338 338 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
339 339 merging is committed.
340 340
341 341 This returns baseformname with '.merge' appended if it is a merge,
342 342 otherwise '.normal' is appended.
343 343 """
344 344 if isinstance(ctxorbool, bool):
345 345 if ctxorbool:
346 346 return baseformname + ".merge"
347 347 elif 1 < len(ctxorbool.parents()):
348 348 return baseformname + ".merge"
349 349
350 350 return baseformname + ".normal"
351 351
352 352 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
353 353 editform='', **opts):
354 354 """get appropriate commit message editor according to '--edit' option
355 355
356 356 'finishdesc' is a function to be called with edited commit message
357 357 (= 'description' of the new changeset) just after editing, but
358 358 before checking empty-ness. It should return actual text to be
359 359 stored into history. This allows to change description before
360 360 storing.
361 361
362 362 'extramsg' is a extra message to be shown in the editor instead of
363 363 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
364 364 is automatically added.
365 365
366 366 'editform' is a dot-separated list of names, to distinguish
367 367 the purpose of commit text editing.
368 368
369 369 'getcommiteditor' returns 'commitforceeditor' regardless of
370 370 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
371 371 they are specific for usage in MQ.
372 372 """
373 373 if edit or finishdesc or extramsg:
374 374 return lambda r, c, s: commitforceeditor(r, c, s,
375 375 finishdesc=finishdesc,
376 376 extramsg=extramsg,
377 377 editform=editform)
378 378 elif editform:
379 379 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
380 380 else:
381 381 return commiteditor
382 382
383 383 def loglimit(opts):
384 384 """get the log limit according to option -l/--limit"""
385 385 limit = opts.get('limit')
386 386 if limit:
387 387 try:
388 388 limit = int(limit)
389 389 except ValueError:
390 390 raise error.Abort(_('limit must be a positive integer'))
391 391 if limit <= 0:
392 392 raise error.Abort(_('limit must be positive'))
393 393 else:
394 394 limit = None
395 395 return limit
396 396
397 397 def makefilename(repo, pat, node, desc=None,
398 398 total=None, seqno=None, revwidth=None, pathname=None):
399 399 node_expander = {
400 400 'H': lambda: hex(node),
401 401 'R': lambda: str(repo.changelog.rev(node)),
402 402 'h': lambda: short(node),
403 403 'm': lambda: re.sub('[^\w]', '_', str(desc))
404 404 }
405 405 expander = {
406 406 '%': lambda: '%',
407 407 'b': lambda: os.path.basename(repo.root),
408 408 }
409 409
410 410 try:
411 411 if node:
412 412 expander.update(node_expander)
413 413 if node:
414 414 expander['r'] = (lambda:
415 415 str(repo.changelog.rev(node)).zfill(revwidth or 0))
416 416 if total is not None:
417 417 expander['N'] = lambda: str(total)
418 418 if seqno is not None:
419 419 expander['n'] = lambda: str(seqno)
420 420 if total is not None and seqno is not None:
421 421 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
422 422 if pathname is not None:
423 423 expander['s'] = lambda: os.path.basename(pathname)
424 424 expander['d'] = lambda: os.path.dirname(pathname) or '.'
425 425 expander['p'] = lambda: pathname
426 426
427 427 newname = []
428 428 patlen = len(pat)
429 429 i = 0
430 430 while i < patlen:
431 431 c = pat[i]
432 432 if c == '%':
433 433 i += 1
434 434 c = pat[i]
435 435 c = expander[c]()
436 436 newname.append(c)
437 437 i += 1
438 438 return ''.join(newname)
439 439 except KeyError as inst:
440 440 raise error.Abort(_("invalid format spec '%%%s' in output filename") %
441 441 inst.args[0])
442 442
443 443 def makefileobj(repo, pat, node=None, desc=None, total=None,
444 444 seqno=None, revwidth=None, mode='wb', modemap=None,
445 445 pathname=None):
446 446
447 447 writable = mode not in ('r', 'rb')
448 448
449 449 if not pat or pat == '-':
450 450 if writable:
451 451 fp = repo.ui.fout
452 452 else:
453 453 fp = repo.ui.fin
454 454 if util.safehasattr(fp, 'fileno'):
455 455 return os.fdopen(os.dup(fp.fileno()), mode)
456 456 else:
457 457 # if this fp can't be duped properly, return
458 458 # a dummy object that can be closed
459 459 class wrappedfileobj(object):
460 460 noop = lambda x: None
461 461 def __init__(self, f):
462 462 self.f = f
463 463 def __getattr__(self, attr):
464 464 if attr == 'close':
465 465 return self.noop
466 466 else:
467 467 return getattr(self.f, attr)
468 468
469 469 return wrappedfileobj(fp)
470 470 if util.safehasattr(pat, 'write') and writable:
471 471 return pat
472 472 if util.safehasattr(pat, 'read') and 'r' in mode:
473 473 return pat
474 474 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
475 475 if modemap is not None:
476 476 mode = modemap.get(fn, mode)
477 477 if mode == 'wb':
478 478 modemap[fn] = 'ab'
479 479 return open(fn, mode)
480 480
481 481 def openrevlog(repo, cmd, file_, opts):
482 482 """opens the changelog, manifest, a filelog or a given revlog"""
483 483 cl = opts['changelog']
484 484 mf = opts['manifest']
485 485 dir = opts['dir']
486 486 msg = None
487 487 if cl and mf:
488 488 msg = _('cannot specify --changelog and --manifest at the same time')
489 489 elif cl and dir:
490 490 msg = _('cannot specify --changelog and --dir at the same time')
491 491 elif cl or mf:
492 492 if file_:
493 493 msg = _('cannot specify filename with --changelog or --manifest')
494 494 elif not repo:
495 495 msg = _('cannot specify --changelog or --manifest or --dir '
496 496 'without a repository')
497 497 if msg:
498 498 raise error.Abort(msg)
499 499
500 500 r = None
501 501 if repo:
502 502 if cl:
503 503 r = repo.unfiltered().changelog
504 504 elif dir:
505 505 if 'treemanifest' not in repo.requirements:
506 506 raise error.Abort(_("--dir can only be used on repos with "
507 507 "treemanifest enabled"))
508 508 dirlog = repo.dirlog(file_)
509 509 if len(dirlog):
510 510 r = dirlog
511 511 elif mf:
512 512 r = repo.manifest
513 513 elif file_:
514 514 filelog = repo.file(file_)
515 515 if len(filelog):
516 516 r = filelog
517 517 if not r:
518 518 if not file_:
519 519 raise error.CommandError(cmd, _('invalid arguments'))
520 520 if not os.path.isfile(file_):
521 521 raise error.Abort(_("revlog '%s' not found") % file_)
522 522 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
523 523 file_[:-2] + ".i")
524 524 return r
525 525
526 526 def copy(ui, repo, pats, opts, rename=False):
527 527 # called with the repo lock held
528 528 #
529 529 # hgsep => pathname that uses "/" to separate directories
530 530 # ossep => pathname that uses os.sep to separate directories
531 531 cwd = repo.getcwd()
532 532 targets = {}
533 533 after = opts.get("after")
534 534 dryrun = opts.get("dry_run")
535 535 wctx = repo[None]
536 536
537 537 def walkpat(pat):
538 538 srcs = []
539 539 if after:
540 540 badstates = '?'
541 541 else:
542 542 badstates = '?r'
543 543 m = scmutil.match(repo[None], [pat], opts, globbed=True)
544 544 for abs in repo.walk(m):
545 545 state = repo.dirstate[abs]
546 546 rel = m.rel(abs)
547 547 exact = m.exact(abs)
548 548 if state in badstates:
549 549 if exact and state == '?':
550 550 ui.warn(_('%s: not copying - file is not managed\n') % rel)
551 551 if exact and state == 'r':
552 552 ui.warn(_('%s: not copying - file has been marked for'
553 553 ' remove\n') % rel)
554 554 continue
555 555 # abs: hgsep
556 556 # rel: ossep
557 557 srcs.append((abs, rel, exact))
558 558 return srcs
559 559
560 560 # abssrc: hgsep
561 561 # relsrc: ossep
562 562 # otarget: ossep
563 563 def copyfile(abssrc, relsrc, otarget, exact):
564 564 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
565 565 if '/' in abstarget:
566 566 # We cannot normalize abstarget itself, this would prevent
567 567 # case only renames, like a => A.
568 568 abspath, absname = abstarget.rsplit('/', 1)
569 569 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
570 570 reltarget = repo.pathto(abstarget, cwd)
571 571 target = repo.wjoin(abstarget)
572 572 src = repo.wjoin(abssrc)
573 573 state = repo.dirstate[abstarget]
574 574
575 575 scmutil.checkportable(ui, abstarget)
576 576
577 577 # check for collisions
578 578 prevsrc = targets.get(abstarget)
579 579 if prevsrc is not None:
580 580 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
581 581 (reltarget, repo.pathto(abssrc, cwd),
582 582 repo.pathto(prevsrc, cwd)))
583 583 return
584 584
585 585 # check for overwrites
586 586 exists = os.path.lexists(target)
587 587 samefile = False
588 588 if exists and abssrc != abstarget:
589 589 if (repo.dirstate.normalize(abssrc) ==
590 590 repo.dirstate.normalize(abstarget)):
591 591 if not rename:
592 592 ui.warn(_("%s: can't copy - same file\n") % reltarget)
593 593 return
594 594 exists = False
595 595 samefile = True
596 596
597 597 if not after and exists or after and state in 'mn':
598 598 if not opts['force']:
599 599 ui.warn(_('%s: not overwriting - file exists\n') %
600 600 reltarget)
601 601 return
602 602
603 603 if after:
604 604 if not exists:
605 605 if rename:
606 606 ui.warn(_('%s: not recording move - %s does not exist\n') %
607 607 (relsrc, reltarget))
608 608 else:
609 609 ui.warn(_('%s: not recording copy - %s does not exist\n') %
610 610 (relsrc, reltarget))
611 611 return
612 612 elif not dryrun:
613 613 try:
614 614 if exists:
615 615 os.unlink(target)
616 616 targetdir = os.path.dirname(target) or '.'
617 617 if not os.path.isdir(targetdir):
618 618 os.makedirs(targetdir)
619 619 if samefile:
620 620 tmp = target + "~hgrename"
621 621 os.rename(src, tmp)
622 622 os.rename(tmp, target)
623 623 else:
624 624 util.copyfile(src, target)
625 625 srcexists = True
626 626 except IOError as inst:
627 627 if inst.errno == errno.ENOENT:
628 628 ui.warn(_('%s: deleted in working directory\n') % relsrc)
629 629 srcexists = False
630 630 else:
631 631 ui.warn(_('%s: cannot copy - %s\n') %
632 632 (relsrc, inst.strerror))
633 633 return True # report a failure
634 634
635 635 if ui.verbose or not exact:
636 636 if rename:
637 637 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
638 638 else:
639 639 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
640 640
641 641 targets[abstarget] = abssrc
642 642
643 643 # fix up dirstate
644 644 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
645 645 dryrun=dryrun, cwd=cwd)
646 646 if rename and not dryrun:
647 647 if not after and srcexists and not samefile:
648 648 util.unlinkpath(repo.wjoin(abssrc))
649 649 wctx.forget([abssrc])
650 650
651 651 # pat: ossep
652 652 # dest ossep
653 653 # srcs: list of (hgsep, hgsep, ossep, bool)
654 654 # return: function that takes hgsep and returns ossep
655 655 def targetpathfn(pat, dest, srcs):
656 656 if os.path.isdir(pat):
657 657 abspfx = pathutil.canonpath(repo.root, cwd, pat)
658 658 abspfx = util.localpath(abspfx)
659 659 if destdirexists:
660 660 striplen = len(os.path.split(abspfx)[0])
661 661 else:
662 662 striplen = len(abspfx)
663 663 if striplen:
664 664 striplen += len(os.sep)
665 665 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
666 666 elif destdirexists:
667 667 res = lambda p: os.path.join(dest,
668 668 os.path.basename(util.localpath(p)))
669 669 else:
670 670 res = lambda p: dest
671 671 return res
672 672
673 673 # pat: ossep
674 674 # dest ossep
675 675 # srcs: list of (hgsep, hgsep, ossep, bool)
676 676 # return: function that takes hgsep and returns ossep
677 677 def targetpathafterfn(pat, dest, srcs):
678 678 if matchmod.patkind(pat):
679 679 # a mercurial pattern
680 680 res = lambda p: os.path.join(dest,
681 681 os.path.basename(util.localpath(p)))
682 682 else:
683 683 abspfx = pathutil.canonpath(repo.root, cwd, pat)
684 684 if len(abspfx) < len(srcs[0][0]):
685 685 # A directory. Either the target path contains the last
686 686 # component of the source path or it does not.
687 687 def evalpath(striplen):
688 688 score = 0
689 689 for s in srcs:
690 690 t = os.path.join(dest, util.localpath(s[0])[striplen:])
691 691 if os.path.lexists(t):
692 692 score += 1
693 693 return score
694 694
695 695 abspfx = util.localpath(abspfx)
696 696 striplen = len(abspfx)
697 697 if striplen:
698 698 striplen += len(os.sep)
699 699 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
700 700 score = evalpath(striplen)
701 701 striplen1 = len(os.path.split(abspfx)[0])
702 702 if striplen1:
703 703 striplen1 += len(os.sep)
704 704 if evalpath(striplen1) > score:
705 705 striplen = striplen1
706 706 res = lambda p: os.path.join(dest,
707 707 util.localpath(p)[striplen:])
708 708 else:
709 709 # a file
710 710 if destdirexists:
711 711 res = lambda p: os.path.join(dest,
712 712 os.path.basename(util.localpath(p)))
713 713 else:
714 714 res = lambda p: dest
715 715 return res
716 716
717 717 pats = scmutil.expandpats(pats)
718 718 if not pats:
719 719 raise error.Abort(_('no source or destination specified'))
720 720 if len(pats) == 1:
721 721 raise error.Abort(_('no destination specified'))
722 722 dest = pats.pop()
723 723 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
724 724 if not destdirexists:
725 725 if len(pats) > 1 or matchmod.patkind(pats[0]):
726 726 raise error.Abort(_('with multiple sources, destination must be an '
727 727 'existing directory'))
728 728 if util.endswithsep(dest):
729 729 raise error.Abort(_('destination %s is not a directory') % dest)
730 730
731 731 tfn = targetpathfn
732 732 if after:
733 733 tfn = targetpathafterfn
734 734 copylist = []
735 735 for pat in pats:
736 736 srcs = walkpat(pat)
737 737 if not srcs:
738 738 continue
739 739 copylist.append((tfn(pat, dest, srcs), srcs))
740 740 if not copylist:
741 741 raise error.Abort(_('no files to copy'))
742 742
743 743 errors = 0
744 744 for targetpath, srcs in copylist:
745 745 for abssrc, relsrc, exact in srcs:
746 746 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
747 747 errors += 1
748 748
749 749 if errors:
750 750 ui.warn(_('(consider using --after)\n'))
751 751
752 752 return errors != 0
753 753
754 754 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
755 755 runargs=None, appendpid=False):
756 756 '''Run a command as a service.'''
757 757
758 758 def writepid(pid):
759 759 if opts['pid_file']:
760 760 if appendpid:
761 761 mode = 'a'
762 762 else:
763 763 mode = 'w'
764 764 fp = open(opts['pid_file'], mode)
765 765 fp.write(str(pid) + '\n')
766 766 fp.close()
767 767
768 768 if opts['daemon'] and not opts['daemon_pipefds']:
769 769 # Signal child process startup with file removal
770 770 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
771 771 os.close(lockfd)
772 772 try:
773 773 if not runargs:
774 774 runargs = util.hgcmd() + sys.argv[1:]
775 775 runargs.append('--daemon-pipefds=%s' % lockpath)
776 776 # Don't pass --cwd to the child process, because we've already
777 777 # changed directory.
778 778 for i in xrange(1, len(runargs)):
779 779 if runargs[i].startswith('--cwd='):
780 780 del runargs[i]
781 781 break
782 782 elif runargs[i].startswith('--cwd'):
783 783 del runargs[i:i + 2]
784 784 break
785 785 def condfn():
786 786 return not os.path.exists(lockpath)
787 787 pid = util.rundetached(runargs, condfn)
788 788 if pid < 0:
789 789 raise error.Abort(_('child process failed to start'))
790 790 writepid(pid)
791 791 finally:
792 792 try:
793 793 os.unlink(lockpath)
794 794 except OSError as e:
795 795 if e.errno != errno.ENOENT:
796 796 raise
797 797 if parentfn:
798 798 return parentfn(pid)
799 799 else:
800 800 return
801 801
802 802 if initfn:
803 803 initfn()
804 804
805 805 if not opts['daemon']:
806 806 writepid(os.getpid())
807 807
808 808 if opts['daemon_pipefds']:
809 809 lockpath = opts['daemon_pipefds']
810 810 try:
811 811 os.setsid()
812 812 except AttributeError:
813 813 pass
814 814 os.unlink(lockpath)
815 815 util.hidewindow()
816 816 sys.stdout.flush()
817 817 sys.stderr.flush()
818 818
819 819 nullfd = os.open(os.devnull, os.O_RDWR)
820 820 logfilefd = nullfd
821 821 if logfile:
822 822 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
823 823 os.dup2(nullfd, 0)
824 824 os.dup2(logfilefd, 1)
825 825 os.dup2(logfilefd, 2)
826 826 if nullfd not in (0, 1, 2):
827 827 os.close(nullfd)
828 828 if logfile and logfilefd not in (0, 1, 2):
829 829 os.close(logfilefd)
830 830
831 831 if runfn:
832 832 return runfn()
833 833
834 834 ## facility to let extension process additional data into an import patch
835 835 # list of identifier to be executed in order
836 836 extrapreimport = [] # run before commit
837 837 extrapostimport = [] # run after commit
838 838 # mapping from identifier to actual import function
839 839 #
840 840 # 'preimport' are run before the commit is made and are provided the following
841 841 # arguments:
842 842 # - repo: the localrepository instance,
843 843 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
844 844 # - extra: the future extra dictionnary of the changeset, please mutate it,
845 845 # - opts: the import options.
846 846 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
847 847 # mutation of in memory commit and more. Feel free to rework the code to get
848 848 # there.
849 849 extrapreimportmap = {}
850 850 # 'postimport' are run after the commit is made and are provided the following
851 851 # argument:
852 852 # - ctx: the changectx created by import.
853 853 extrapostimportmap = {}
854 854
855 855 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
856 856 """Utility function used by commands.import to import a single patch
857 857
858 858 This function is explicitly defined here to help the evolve extension to
859 859 wrap this part of the import logic.
860 860
861 861 The API is currently a bit ugly because it a simple code translation from
862 862 the import command. Feel free to make it better.
863 863
864 864 :hunk: a patch (as a binary string)
865 865 :parents: nodes that will be parent of the created commit
866 866 :opts: the full dict of option passed to the import command
867 867 :msgs: list to save commit message to.
868 868 (used in case we need to save it when failing)
869 869 :updatefunc: a function that update a repo to a given node
870 870 updatefunc(<repo>, <node>)
871 871 """
872 872 # avoid cycle context -> subrepo -> cmdutil
873 873 import context
874 874 extractdata = patch.extract(ui, hunk)
875 875 tmpname = extractdata.get('filename')
876 876 message = extractdata.get('message')
877 877 user = extractdata.get('user')
878 878 date = extractdata.get('date')
879 879 branch = extractdata.get('branch')
880 880 nodeid = extractdata.get('nodeid')
881 881 p1 = extractdata.get('p1')
882 882 p2 = extractdata.get('p2')
883 883
884 884 update = not opts.get('bypass')
885 885 strip = opts["strip"]
886 886 prefix = opts["prefix"]
887 887 sim = float(opts.get('similarity') or 0)
888 888 if not tmpname:
889 889 return (None, None, False)
890 890 msg = _('applied to working directory')
891 891
892 892 rejects = False
893 893
894 894 try:
895 895 cmdline_message = logmessage(ui, opts)
896 896 if cmdline_message:
897 897 # pickup the cmdline msg
898 898 message = cmdline_message
899 899 elif message:
900 900 # pickup the patch msg
901 901 message = message.strip()
902 902 else:
903 903 # launch the editor
904 904 message = None
905 905 ui.debug('message:\n%s\n' % message)
906 906
907 907 if len(parents) == 1:
908 908 parents.append(repo[nullid])
909 909 if opts.get('exact'):
910 910 if not nodeid or not p1:
911 911 raise error.Abort(_('not a Mercurial patch'))
912 912 p1 = repo[p1]
913 913 p2 = repo[p2 or nullid]
914 914 elif p2:
915 915 try:
916 916 p1 = repo[p1]
917 917 p2 = repo[p2]
918 918 # Without any options, consider p2 only if the
919 919 # patch is being applied on top of the recorded
920 920 # first parent.
921 921 if p1 != parents[0]:
922 922 p1 = parents[0]
923 923 p2 = repo[nullid]
924 924 except error.RepoError:
925 925 p1, p2 = parents
926 926 if p2.node() == nullid:
927 927 ui.warn(_("warning: import the patch as a normal revision\n"
928 928 "(use --exact to import the patch as a merge)\n"))
929 929 else:
930 930 p1, p2 = parents
931 931
932 932 n = None
933 933 if update:
934 934 if p1 != parents[0]:
935 935 updatefunc(repo, p1.node())
936 936 if p2 != parents[1]:
937 937 repo.setparents(p1.node(), p2.node())
938 938
939 939 if opts.get('exact') or opts.get('import_branch'):
940 940 repo.dirstate.setbranch(branch or 'default')
941 941
942 942 partial = opts.get('partial', False)
943 943 files = set()
944 944 try:
945 945 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
946 946 files=files, eolmode=None, similarity=sim / 100.0)
947 947 except patch.PatchError as e:
948 948 if not partial:
949 949 raise error.Abort(str(e))
950 950 if partial:
951 951 rejects = True
952 952
953 953 files = list(files)
954 954 if opts.get('no_commit'):
955 955 if message:
956 956 msgs.append(message)
957 957 else:
958 958 if opts.get('exact') or p2:
959 959 # If you got here, you either use --force and know what
960 960 # you are doing or used --exact or a merge patch while
961 961 # being updated to its first parent.
962 962 m = None
963 963 else:
964 964 m = scmutil.matchfiles(repo, files or [])
965 965 editform = mergeeditform(repo[None], 'import.normal')
966 966 if opts.get('exact'):
967 967 editor = None
968 968 else:
969 969 editor = getcommiteditor(editform=editform, **opts)
970 970 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
971 971 extra = {}
972 972 for idfunc in extrapreimport:
973 973 extrapreimportmap[idfunc](repo, extractdata, extra, opts)
974 974 try:
975 975 if partial:
976 976 repo.ui.setconfig('ui', 'allowemptycommit', True)
977 977 n = repo.commit(message, opts.get('user') or user,
978 978 opts.get('date') or date, match=m,
979 979 editor=editor, extra=extra)
980 980 for idfunc in extrapostimport:
981 981 extrapostimportmap[idfunc](repo[n])
982 982 finally:
983 983 repo.ui.restoreconfig(allowemptyback)
984 984 else:
985 985 if opts.get('exact') or opts.get('import_branch'):
986 986 branch = branch or 'default'
987 987 else:
988 988 branch = p1.branch()
989 989 store = patch.filestore()
990 990 try:
991 991 files = set()
992 992 try:
993 993 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
994 994 files, eolmode=None)
995 995 except patch.PatchError as e:
996 996 raise error.Abort(str(e))
997 997 if opts.get('exact'):
998 998 editor = None
999 999 else:
1000 1000 editor = getcommiteditor(editform='import.bypass')
1001 1001 memctx = context.makememctx(repo, (p1.node(), p2.node()),
1002 1002 message,
1003 1003 opts.get('user') or user,
1004 1004 opts.get('date') or date,
1005 1005 branch, files, store,
1006 1006 editor=editor)
1007 1007 n = memctx.commit()
1008 1008 finally:
1009 1009 store.close()
1010 1010 if opts.get('exact') and opts.get('no_commit'):
1011 1011 # --exact with --no-commit is still useful in that it does merge
1012 1012 # and branch bits
1013 1013 ui.warn(_("warning: can't check exact import with --no-commit\n"))
1014 1014 elif opts.get('exact') and hex(n) != nodeid:
1015 1015 raise error.Abort(_('patch is damaged or loses information'))
1016 1016 if n:
1017 1017 # i18n: refers to a short changeset id
1018 1018 msg = _('created %s') % short(n)
1019 1019 return (msg, n, rejects)
1020 1020 finally:
1021 1021 os.unlink(tmpname)
1022 1022
1023 1023 # facility to let extensions include additional data in an exported patch
1024 1024 # list of identifiers to be executed in order
1025 1025 extraexport = []
1026 1026 # mapping from identifier to actual export function
1027 1027 # function as to return a string to be added to the header or None
1028 1028 # it is given two arguments (sequencenumber, changectx)
1029 1029 extraexportmap = {}
1030 1030
1031 1031 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
1032 1032 opts=None, match=None):
1033 1033 '''export changesets as hg patches.'''
1034 1034
1035 1035 total = len(revs)
1036 1036 revwidth = max([len(str(rev)) for rev in revs])
1037 1037 filemode = {}
1038 1038
1039 1039 def single(rev, seqno, fp):
1040 1040 ctx = repo[rev]
1041 1041 node = ctx.node()
1042 1042 parents = [p.node() for p in ctx.parents() if p]
1043 1043 branch = ctx.branch()
1044 1044 if switch_parent:
1045 1045 parents.reverse()
1046 1046
1047 1047 if parents:
1048 1048 prev = parents[0]
1049 1049 else:
1050 1050 prev = nullid
1051 1051
1052 1052 shouldclose = False
1053 1053 if not fp and len(template) > 0:
1054 1054 desc_lines = ctx.description().rstrip().split('\n')
1055 1055 desc = desc_lines[0] #Commit always has a first line.
1056 1056 fp = makefileobj(repo, template, node, desc=desc, total=total,
1057 1057 seqno=seqno, revwidth=revwidth, mode='wb',
1058 1058 modemap=filemode)
1059 1059 if fp != template:
1060 1060 shouldclose = True
1061 1061 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1062 1062 repo.ui.note("%s\n" % fp.name)
1063 1063
1064 1064 if not fp:
1065 1065 write = repo.ui.write
1066 1066 else:
1067 1067 def write(s, **kw):
1068 1068 fp.write(s)
1069 1069
1070 1070 write("# HG changeset patch\n")
1071 1071 write("# User %s\n" % ctx.user())
1072 1072 write("# Date %d %d\n" % ctx.date())
1073 1073 write("# %s\n" % util.datestr(ctx.date()))
1074 1074 if branch and branch != 'default':
1075 1075 write("# Branch %s\n" % branch)
1076 1076 write("# Node ID %s\n" % hex(node))
1077 1077 write("# Parent %s\n" % hex(prev))
1078 1078 if len(parents) > 1:
1079 1079 write("# Parent %s\n" % hex(parents[1]))
1080 1080
1081 1081 for headerid in extraexport:
1082 1082 header = extraexportmap[headerid](seqno, ctx)
1083 1083 if header is not None:
1084 1084 write('# %s\n' % header)
1085 1085 write(ctx.description().rstrip())
1086 1086 write("\n\n")
1087 1087
1088 1088 for chunk, label in patch.diffui(repo, prev, node, match, opts=opts):
1089 1089 write(chunk, label=label)
1090 1090
1091 1091 if shouldclose:
1092 1092 fp.close()
1093 1093
1094 1094 for seqno, rev in enumerate(revs):
1095 1095 single(rev, seqno + 1, fp)
1096 1096
1097 1097 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1098 1098 changes=None, stat=False, fp=None, prefix='',
1099 1099 root='', listsubrepos=False):
1100 1100 '''show diff or diffstat.'''
1101 1101 if fp is None:
1102 1102 write = ui.write
1103 1103 else:
1104 1104 def write(s, **kw):
1105 1105 fp.write(s)
1106 1106
1107 1107 if root:
1108 1108 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1109 1109 else:
1110 1110 relroot = ''
1111 1111 if relroot != '':
1112 1112 # XXX relative roots currently don't work if the root is within a
1113 1113 # subrepo
1114 1114 uirelroot = match.uipath(relroot)
1115 1115 relroot += '/'
1116 1116 for matchroot in match.files():
1117 1117 if not matchroot.startswith(relroot):
1118 1118 ui.warn(_('warning: %s not inside relative root %s\n') % (
1119 1119 match.uipath(matchroot), uirelroot))
1120 1120
1121 1121 if stat:
1122 1122 diffopts = diffopts.copy(context=0)
1123 1123 width = 80
1124 1124 if not ui.plain():
1125 1125 width = ui.termwidth()
1126 1126 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1127 1127 prefix=prefix, relroot=relroot)
1128 1128 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1129 1129 width=width,
1130 1130 git=diffopts.git):
1131 1131 write(chunk, label=label)
1132 1132 else:
1133 1133 for chunk, label in patch.diffui(repo, node1, node2, match,
1134 1134 changes, diffopts, prefix=prefix,
1135 1135 relroot=relroot):
1136 1136 write(chunk, label=label)
1137 1137
1138 1138 if listsubrepos:
1139 1139 ctx1 = repo[node1]
1140 1140 ctx2 = repo[node2]
1141 1141 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1142 1142 tempnode2 = node2
1143 1143 try:
1144 1144 if node2 is not None:
1145 1145 tempnode2 = ctx2.substate[subpath][1]
1146 1146 except KeyError:
1147 1147 # A subrepo that existed in node1 was deleted between node1 and
1148 1148 # node2 (inclusive). Thus, ctx2's substate won't contain that
1149 1149 # subpath. The best we can do is to ignore it.
1150 1150 tempnode2 = None
1151 1151 submatch = matchmod.narrowmatcher(subpath, match)
1152 1152 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1153 1153 stat=stat, fp=fp, prefix=prefix)
1154 1154
1155 1155 class changeset_printer(object):
1156 1156 '''show changeset information when templating not requested.'''
1157 1157
1158 1158 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1159 1159 self.ui = ui
1160 1160 self.repo = repo
1161 1161 self.buffered = buffered
1162 1162 self.matchfn = matchfn
1163 1163 self.diffopts = diffopts
1164 1164 self.header = {}
1165 1165 self.hunk = {}
1166 1166 self.lastheader = None
1167 1167 self.footer = None
1168 1168
1169 1169 def flush(self, ctx):
1170 1170 rev = ctx.rev()
1171 1171 if rev in self.header:
1172 1172 h = self.header[rev]
1173 1173 if h != self.lastheader:
1174 1174 self.lastheader = h
1175 1175 self.ui.write(h)
1176 1176 del self.header[rev]
1177 1177 if rev in self.hunk:
1178 1178 self.ui.write(self.hunk[rev])
1179 1179 del self.hunk[rev]
1180 1180 return 1
1181 1181 return 0
1182 1182
1183 1183 def close(self):
1184 1184 if self.footer:
1185 1185 self.ui.write(self.footer)
1186 1186
1187 1187 def show(self, ctx, copies=None, matchfn=None, **props):
1188 1188 if self.buffered:
1189 1189 self.ui.pushbuffer()
1190 1190 self._show(ctx, copies, matchfn, props)
1191 1191 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1192 1192 else:
1193 1193 self._show(ctx, copies, matchfn, props)
1194 1194
1195 1195 def _show(self, ctx, copies, matchfn, props):
1196 1196 '''show a single changeset or file revision'''
1197 1197 changenode = ctx.node()
1198 1198 rev = ctx.rev()
1199 1199 if self.ui.debugflag:
1200 1200 hexfunc = hex
1201 1201 else:
1202 1202 hexfunc = short
1203 1203 # as of now, wctx.node() and wctx.rev() return None, but we want to
1204 1204 # show the same values as {node} and {rev} templatekw
1205 1205 revnode = (scmutil.intrev(rev), hexfunc(bin(ctx.hex())))
1206 1206
1207 1207 if self.ui.quiet:
1208 1208 self.ui.write("%d:%s\n" % revnode, label='log.node')
1209 1209 return
1210 1210
1211 1211 date = util.datestr(ctx.date())
1212 1212
1213 1213 # i18n: column positioning for "hg log"
1214 1214 self.ui.write(_("changeset: %d:%s\n") % revnode,
1215 1215 label='log.changeset changeset.%s' % ctx.phasestr())
1216 1216
1217 1217 # branches are shown first before any other names due to backwards
1218 1218 # compatibility
1219 1219 branch = ctx.branch()
1220 1220 # don't show the default branch name
1221 1221 if branch != 'default':
1222 1222 # i18n: column positioning for "hg log"
1223 1223 self.ui.write(_("branch: %s\n") % branch,
1224 1224 label='log.branch')
1225 1225
1226 1226 for name, ns in self.repo.names.iteritems():
1227 1227 # branches has special logic already handled above, so here we just
1228 1228 # skip it
1229 1229 if name == 'branches':
1230 1230 continue
1231 1231 # we will use the templatename as the color name since those two
1232 1232 # should be the same
1233 1233 for name in ns.names(self.repo, changenode):
1234 1234 self.ui.write(ns.logfmt % name,
1235 1235 label='log.%s' % ns.colorname)
1236 1236 if self.ui.debugflag:
1237 1237 # i18n: column positioning for "hg log"
1238 1238 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1239 1239 label='log.phase')
1240 1240 for pctx in scmutil.meaningfulparents(self.repo, ctx):
1241 1241 label = 'log.parent changeset.%s' % pctx.phasestr()
1242 1242 # i18n: column positioning for "hg log"
1243 1243 self.ui.write(_("parent: %d:%s\n")
1244 1244 % (pctx.rev(), hexfunc(pctx.node())),
1245 1245 label=label)
1246 1246
1247 1247 if self.ui.debugflag and rev is not None:
1248 1248 mnode = ctx.manifestnode()
1249 1249 # i18n: column positioning for "hg log"
1250 1250 self.ui.write(_("manifest: %d:%s\n") %
1251 1251 (self.repo.manifest.rev(mnode), hex(mnode)),
1252 1252 label='ui.debug log.manifest')
1253 1253 # i18n: column positioning for "hg log"
1254 1254 self.ui.write(_("user: %s\n") % ctx.user(),
1255 1255 label='log.user')
1256 1256 # i18n: column positioning for "hg log"
1257 1257 self.ui.write(_("date: %s\n") % date,
1258 1258 label='log.date')
1259 1259
1260 1260 if self.ui.debugflag:
1261 1261 files = ctx.p1().status(ctx)[:3]
1262 1262 for key, value in zip([# i18n: column positioning for "hg log"
1263 1263 _("files:"),
1264 1264 # i18n: column positioning for "hg log"
1265 1265 _("files+:"),
1266 1266 # i18n: column positioning for "hg log"
1267 1267 _("files-:")], files):
1268 1268 if value:
1269 1269 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1270 1270 label='ui.debug log.files')
1271 1271 elif ctx.files() and self.ui.verbose:
1272 1272 # i18n: column positioning for "hg log"
1273 1273 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1274 1274 label='ui.note log.files')
1275 1275 if copies and self.ui.verbose:
1276 1276 copies = ['%s (%s)' % c for c in copies]
1277 1277 # i18n: column positioning for "hg log"
1278 1278 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1279 1279 label='ui.note log.copies')
1280 1280
1281 1281 extra = ctx.extra()
1282 1282 if extra and self.ui.debugflag:
1283 1283 for key, value in sorted(extra.items()):
1284 1284 # i18n: column positioning for "hg log"
1285 1285 self.ui.write(_("extra: %s=%s\n")
1286 1286 % (key, value.encode('string_escape')),
1287 1287 label='ui.debug log.extra')
1288 1288
1289 1289 description = ctx.description().strip()
1290 1290 if description:
1291 1291 if self.ui.verbose:
1292 1292 self.ui.write(_("description:\n"),
1293 1293 label='ui.note log.description')
1294 1294 self.ui.write(description,
1295 1295 label='ui.note log.description')
1296 1296 self.ui.write("\n\n")
1297 1297 else:
1298 1298 # i18n: column positioning for "hg log"
1299 1299 self.ui.write(_("summary: %s\n") %
1300 1300 description.splitlines()[0],
1301 1301 label='log.summary')
1302 1302 self.ui.write("\n")
1303 1303
1304 1304 self.showpatch(changenode, matchfn)
1305 1305
1306 1306 def showpatch(self, node, matchfn):
1307 1307 if not matchfn:
1308 1308 matchfn = self.matchfn
1309 1309 if matchfn:
1310 1310 stat = self.diffopts.get('stat')
1311 1311 diff = self.diffopts.get('patch')
1312 1312 diffopts = patch.diffallopts(self.ui, self.diffopts)
1313 1313 prev = self.repo.changelog.parents(node)[0]
1314 1314 if stat:
1315 1315 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1316 1316 match=matchfn, stat=True)
1317 1317 if diff:
1318 1318 if stat:
1319 1319 self.ui.write("\n")
1320 1320 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1321 1321 match=matchfn, stat=False)
1322 1322 self.ui.write("\n")
1323 1323
1324 1324 class jsonchangeset(changeset_printer):
1325 1325 '''format changeset information.'''
1326 1326
1327 1327 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1328 1328 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1329 1329 self.cache = {}
1330 1330 self._first = True
1331 1331
1332 1332 def close(self):
1333 1333 if not self._first:
1334 1334 self.ui.write("\n]\n")
1335 1335 else:
1336 1336 self.ui.write("[]\n")
1337 1337
1338 1338 def _show(self, ctx, copies, matchfn, props):
1339 1339 '''show a single changeset or file revision'''
1340 1340 rev = ctx.rev()
1341 1341 if rev is None:
1342 1342 jrev = jnode = 'null'
1343 1343 else:
1344 1344 jrev = str(rev)
1345 1345 jnode = '"%s"' % hex(ctx.node())
1346 1346 j = encoding.jsonescape
1347 1347
1348 1348 if self._first:
1349 1349 self.ui.write("[\n {")
1350 1350 self._first = False
1351 1351 else:
1352 1352 self.ui.write(",\n {")
1353 1353
1354 1354 if self.ui.quiet:
1355 1355 self.ui.write('\n "rev": %s' % jrev)
1356 1356 self.ui.write(',\n "node": %s' % jnode)
1357 1357 self.ui.write('\n }')
1358 1358 return
1359 1359
1360 1360 self.ui.write('\n "rev": %s' % jrev)
1361 1361 self.ui.write(',\n "node": %s' % jnode)
1362 1362 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1363 1363 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1364 1364 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1365 1365 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1366 1366 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1367 1367
1368 1368 self.ui.write(',\n "bookmarks": [%s]' %
1369 1369 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1370 1370 self.ui.write(',\n "tags": [%s]' %
1371 1371 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1372 1372 self.ui.write(',\n "parents": [%s]' %
1373 1373 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1374 1374
1375 1375 if self.ui.debugflag:
1376 1376 if rev is None:
1377 1377 jmanifestnode = 'null'
1378 1378 else:
1379 1379 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1380 1380 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1381 1381
1382 1382 self.ui.write(',\n "extra": {%s}' %
1383 1383 ", ".join('"%s": "%s"' % (j(k), j(v))
1384 1384 for k, v in ctx.extra().items()))
1385 1385
1386 1386 files = ctx.p1().status(ctx)
1387 1387 self.ui.write(',\n "modified": [%s]' %
1388 1388 ", ".join('"%s"' % j(f) for f in files[0]))
1389 1389 self.ui.write(',\n "added": [%s]' %
1390 1390 ", ".join('"%s"' % j(f) for f in files[1]))
1391 1391 self.ui.write(',\n "removed": [%s]' %
1392 1392 ", ".join('"%s"' % j(f) for f in files[2]))
1393 1393
1394 1394 elif self.ui.verbose:
1395 1395 self.ui.write(',\n "files": [%s]' %
1396 1396 ", ".join('"%s"' % j(f) for f in ctx.files()))
1397 1397
1398 1398 if copies:
1399 1399 self.ui.write(',\n "copies": {%s}' %
1400 1400 ", ".join('"%s": "%s"' % (j(k), j(v))
1401 1401 for k, v in copies))
1402 1402
1403 1403 matchfn = self.matchfn
1404 1404 if matchfn:
1405 1405 stat = self.diffopts.get('stat')
1406 1406 diff = self.diffopts.get('patch')
1407 1407 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1408 1408 node, prev = ctx.node(), ctx.p1().node()
1409 1409 if stat:
1410 1410 self.ui.pushbuffer()
1411 1411 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1412 1412 match=matchfn, stat=True)
1413 1413 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1414 1414 if diff:
1415 1415 self.ui.pushbuffer()
1416 1416 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1417 1417 match=matchfn, stat=False)
1418 1418 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1419 1419
1420 1420 self.ui.write("\n }")
1421 1421
1422 1422 class changeset_templater(changeset_printer):
1423 1423 '''format changeset information.'''
1424 1424
1425 1425 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1426 1426 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1427 1427 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1428 1428 defaulttempl = {
1429 1429 'parent': '{rev}:{node|formatnode} ',
1430 1430 'manifest': '{rev}:{node|formatnode}',
1431 1431 'file_copy': '{name} ({source})',
1432 1432 'extra': '{key}={value|stringescape}'
1433 1433 }
1434 1434 # filecopy is preserved for compatibility reasons
1435 1435 defaulttempl['filecopy'] = defaulttempl['file_copy']
1436 1436 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1437 1437 cache=defaulttempl)
1438 1438 if tmpl:
1439 1439 self.t.cache['changeset'] = tmpl
1440 1440
1441 1441 self.cache = {}
1442 1442
1443 1443 # find correct templates for current mode
1444 1444 tmplmodes = [
1445 1445 (True, None),
1446 1446 (self.ui.verbose, 'verbose'),
1447 1447 (self.ui.quiet, 'quiet'),
1448 1448 (self.ui.debugflag, 'debug'),
1449 1449 ]
1450 1450
1451 1451 self._parts = {'header': '', 'footer': '', 'changeset': 'changeset',
1452 1452 'docheader': '', 'docfooter': ''}
1453 1453 for mode, postfix in tmplmodes:
1454 1454 for t in self._parts:
1455 1455 cur = t
1456 1456 if postfix:
1457 1457 cur += "_" + postfix
1458 1458 if mode and cur in self.t:
1459 1459 self._parts[t] = cur
1460 1460
1461 1461 if self._parts['docheader']:
1462 1462 self.ui.write(templater.stringify(self.t(self._parts['docheader'])))
1463 1463
1464 1464 def close(self):
1465 1465 if self._parts['docfooter']:
1466 1466 if not self.footer:
1467 1467 self.footer = ""
1468 1468 self.footer += templater.stringify(self.t(self._parts['docfooter']))
1469 1469 return super(changeset_templater, self).close()
1470 1470
1471 1471 def _show(self, ctx, copies, matchfn, props):
1472 1472 '''show a single changeset or file revision'''
1473 1473 props = props.copy()
1474 1474 props.update(templatekw.keywords)
1475 1475 props['templ'] = self.t
1476 1476 props['ctx'] = ctx
1477 1477 props['repo'] = self.repo
1478 1478 props['revcache'] = {'copies': copies}
1479 1479 props['cache'] = self.cache
1480 1480
1481 1481 try:
1482 1482 # write header
1483 1483 if self._parts['header']:
1484 1484 h = templater.stringify(self.t(self._parts['header'], **props))
1485 1485 if self.buffered:
1486 1486 self.header[ctx.rev()] = h
1487 1487 else:
1488 1488 if self.lastheader != h:
1489 1489 self.lastheader = h
1490 1490 self.ui.write(h)
1491 1491
1492 1492 # write changeset metadata, then patch if requested
1493 1493 key = self._parts['changeset']
1494 1494 self.ui.write(templater.stringify(self.t(key, **props)))
1495 1495 self.showpatch(ctx.node(), matchfn)
1496 1496
1497 1497 if self._parts['footer']:
1498 1498 if not self.footer:
1499 1499 self.footer = templater.stringify(
1500 1500 self.t(self._parts['footer'], **props))
1501 1501 except KeyError as inst:
1502 1502 msg = _("%s: no key named '%s'")
1503 1503 raise error.Abort(msg % (self.t.mapfile, inst.args[0]))
1504 1504 except SyntaxError as inst:
1505 1505 raise error.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1506 1506
1507 1507 def gettemplate(ui, tmpl, style):
1508 1508 """
1509 1509 Find the template matching the given template spec or style.
1510 1510 """
1511 1511
1512 1512 # ui settings
1513 1513 if not tmpl and not style: # template are stronger than style
1514 1514 tmpl = ui.config('ui', 'logtemplate')
1515 1515 if tmpl:
1516 1516 try:
1517 1517 tmpl = templater.unquotestring(tmpl)
1518 1518 except SyntaxError:
1519 1519 pass
1520 1520 return tmpl, None
1521 1521 else:
1522 1522 style = util.expandpath(ui.config('ui', 'style', ''))
1523 1523
1524 1524 if not tmpl and style:
1525 1525 mapfile = style
1526 1526 if not os.path.split(mapfile)[0]:
1527 1527 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1528 1528 or templater.templatepath(mapfile))
1529 1529 if mapname:
1530 1530 mapfile = mapname
1531 1531 return None, mapfile
1532 1532
1533 1533 if not tmpl:
1534 1534 return None, None
1535 1535
1536 1536 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1537 1537
1538 1538 def show_changeset(ui, repo, opts, buffered=False):
1539 1539 """show one changeset using template or regular display.
1540 1540
1541 1541 Display format will be the first non-empty hit of:
1542 1542 1. option 'template'
1543 1543 2. option 'style'
1544 1544 3. [ui] setting 'logtemplate'
1545 1545 4. [ui] setting 'style'
1546 1546 If all of these values are either the unset or the empty string,
1547 1547 regular display via changeset_printer() is done.
1548 1548 """
1549 1549 # options
1550 1550 matchfn = None
1551 1551 if opts.get('patch') or opts.get('stat'):
1552 1552 matchfn = scmutil.matchall(repo)
1553 1553
1554 1554 if opts.get('template') == 'json':
1555 1555 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1556 1556
1557 1557 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1558 1558
1559 1559 if not tmpl and not mapfile:
1560 1560 return changeset_printer(ui, repo, matchfn, opts, buffered)
1561 1561
1562 1562 try:
1563 1563 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1564 1564 buffered)
1565 1565 except SyntaxError as inst:
1566 1566 raise error.Abort(inst.args[0])
1567 1567 return t
1568 1568
1569 1569 def showmarker(ui, marker):
1570 1570 """utility function to display obsolescence marker in a readable way
1571 1571
1572 1572 To be used by debug function."""
1573 1573 ui.write(hex(marker.precnode()))
1574 1574 for repl in marker.succnodes():
1575 1575 ui.write(' ')
1576 1576 ui.write(hex(repl))
1577 1577 ui.write(' %X ' % marker.flags())
1578 1578 parents = marker.parentnodes()
1579 1579 if parents is not None:
1580 1580 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1581 1581 ui.write('(%s) ' % util.datestr(marker.date()))
1582 1582 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1583 1583 sorted(marker.metadata().items())
1584 1584 if t[0] != 'date')))
1585 1585 ui.write('\n')
1586 1586
1587 1587 def finddate(ui, repo, date):
1588 1588 """Find the tipmost changeset that matches the given date spec"""
1589 1589
1590 1590 df = util.matchdate(date)
1591 1591 m = scmutil.matchall(repo)
1592 1592 results = {}
1593 1593
1594 1594 def prep(ctx, fns):
1595 1595 d = ctx.date()
1596 1596 if df(d[0]):
1597 1597 results[ctx.rev()] = d
1598 1598
1599 1599 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1600 1600 rev = ctx.rev()
1601 1601 if rev in results:
1602 1602 ui.status(_("found revision %s from %s\n") %
1603 1603 (rev, util.datestr(results[rev])))
1604 1604 return str(rev)
1605 1605
1606 1606 raise error.Abort(_("revision matching date not found"))
1607 1607
1608 1608 def increasingwindows(windowsize=8, sizelimit=512):
1609 1609 while True:
1610 1610 yield windowsize
1611 1611 if windowsize < sizelimit:
1612 1612 windowsize *= 2
1613 1613
1614 1614 class FileWalkError(Exception):
1615 1615 pass
1616 1616
1617 1617 def walkfilerevs(repo, match, follow, revs, fncache):
1618 1618 '''Walks the file history for the matched files.
1619 1619
1620 1620 Returns the changeset revs that are involved in the file history.
1621 1621
1622 1622 Throws FileWalkError if the file history can't be walked using
1623 1623 filelogs alone.
1624 1624 '''
1625 1625 wanted = set()
1626 1626 copies = []
1627 1627 minrev, maxrev = min(revs), max(revs)
1628 1628 def filerevgen(filelog, last):
1629 1629 """
1630 1630 Only files, no patterns. Check the history of each file.
1631 1631
1632 1632 Examines filelog entries within minrev, maxrev linkrev range
1633 1633 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1634 1634 tuples in backwards order
1635 1635 """
1636 1636 cl_count = len(repo)
1637 1637 revs = []
1638 1638 for j in xrange(0, last + 1):
1639 1639 linkrev = filelog.linkrev(j)
1640 1640 if linkrev < minrev:
1641 1641 continue
1642 1642 # only yield rev for which we have the changelog, it can
1643 1643 # happen while doing "hg log" during a pull or commit
1644 1644 if linkrev >= cl_count:
1645 1645 break
1646 1646
1647 1647 parentlinkrevs = []
1648 1648 for p in filelog.parentrevs(j):
1649 1649 if p != nullrev:
1650 1650 parentlinkrevs.append(filelog.linkrev(p))
1651 1651 n = filelog.node(j)
1652 1652 revs.append((linkrev, parentlinkrevs,
1653 1653 follow and filelog.renamed(n)))
1654 1654
1655 1655 return reversed(revs)
1656 1656 def iterfiles():
1657 1657 pctx = repo['.']
1658 1658 for filename in match.files():
1659 1659 if follow:
1660 1660 if filename not in pctx:
1661 1661 raise error.Abort(_('cannot follow file not in parent '
1662 1662 'revision: "%s"') % filename)
1663 1663 yield filename, pctx[filename].filenode()
1664 1664 else:
1665 1665 yield filename, None
1666 1666 for filename_node in copies:
1667 1667 yield filename_node
1668 1668
1669 1669 for file_, node in iterfiles():
1670 1670 filelog = repo.file(file_)
1671 1671 if not len(filelog):
1672 1672 if node is None:
1673 1673 # A zero count may be a directory or deleted file, so
1674 1674 # try to find matching entries on the slow path.
1675 1675 if follow:
1676 1676 raise error.Abort(
1677 1677 _('cannot follow nonexistent file: "%s"') % file_)
1678 1678 raise FileWalkError("Cannot walk via filelog")
1679 1679 else:
1680 1680 continue
1681 1681
1682 1682 if node is None:
1683 1683 last = len(filelog) - 1
1684 1684 else:
1685 1685 last = filelog.rev(node)
1686 1686
1687 1687 # keep track of all ancestors of the file
1688 1688 ancestors = set([filelog.linkrev(last)])
1689 1689
1690 1690 # iterate from latest to oldest revision
1691 1691 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1692 1692 if not follow:
1693 1693 if rev > maxrev:
1694 1694 continue
1695 1695 else:
1696 1696 # Note that last might not be the first interesting
1697 1697 # rev to us:
1698 1698 # if the file has been changed after maxrev, we'll
1699 1699 # have linkrev(last) > maxrev, and we still need
1700 1700 # to explore the file graph
1701 1701 if rev not in ancestors:
1702 1702 continue
1703 1703 # XXX insert 1327 fix here
1704 1704 if flparentlinkrevs:
1705 1705 ancestors.update(flparentlinkrevs)
1706 1706
1707 1707 fncache.setdefault(rev, []).append(file_)
1708 1708 wanted.add(rev)
1709 1709 if copied:
1710 1710 copies.append(copied)
1711 1711
1712 1712 return wanted
1713 1713
1714 1714 class _followfilter(object):
1715 1715 def __init__(self, repo, onlyfirst=False):
1716 1716 self.repo = repo
1717 1717 self.startrev = nullrev
1718 1718 self.roots = set()
1719 1719 self.onlyfirst = onlyfirst
1720 1720
1721 1721 def match(self, rev):
1722 1722 def realparents(rev):
1723 1723 if self.onlyfirst:
1724 1724 return self.repo.changelog.parentrevs(rev)[0:1]
1725 1725 else:
1726 1726 return filter(lambda x: x != nullrev,
1727 1727 self.repo.changelog.parentrevs(rev))
1728 1728
1729 1729 if self.startrev == nullrev:
1730 1730 self.startrev = rev
1731 1731 return True
1732 1732
1733 1733 if rev > self.startrev:
1734 1734 # forward: all descendants
1735 1735 if not self.roots:
1736 1736 self.roots.add(self.startrev)
1737 1737 for parent in realparents(rev):
1738 1738 if parent in self.roots:
1739 1739 self.roots.add(rev)
1740 1740 return True
1741 1741 else:
1742 1742 # backwards: all parents
1743 1743 if not self.roots:
1744 1744 self.roots.update(realparents(self.startrev))
1745 1745 if rev in self.roots:
1746 1746 self.roots.remove(rev)
1747 1747 self.roots.update(realparents(rev))
1748 1748 return True
1749 1749
1750 1750 return False
1751 1751
1752 1752 def walkchangerevs(repo, match, opts, prepare):
1753 1753 '''Iterate over files and the revs in which they changed.
1754 1754
1755 1755 Callers most commonly need to iterate backwards over the history
1756 1756 in which they are interested. Doing so has awful (quadratic-looking)
1757 1757 performance, so we use iterators in a "windowed" way.
1758 1758
1759 1759 We walk a window of revisions in the desired order. Within the
1760 1760 window, we first walk forwards to gather data, then in the desired
1761 1761 order (usually backwards) to display it.
1762 1762
1763 1763 This function returns an iterator yielding contexts. Before
1764 1764 yielding each context, the iterator will first call the prepare
1765 1765 function on each context in the window in forward order.'''
1766 1766
1767 1767 follow = opts.get('follow') or opts.get('follow_first')
1768 1768 revs = _logrevs(repo, opts)
1769 1769 if not revs:
1770 1770 return []
1771 1771 wanted = set()
1772 1772 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1773 1773 opts.get('removed'))
1774 1774 fncache = {}
1775 1775 change = repo.changectx
1776 1776
1777 1777 # First step is to fill wanted, the set of revisions that we want to yield.
1778 1778 # When it does not induce extra cost, we also fill fncache for revisions in
1779 1779 # wanted: a cache of filenames that were changed (ctx.files()) and that
1780 1780 # match the file filtering conditions.
1781 1781
1782 1782 if match.always():
1783 1783 # No files, no patterns. Display all revs.
1784 1784 wanted = revs
1785 1785 elif not slowpath:
1786 1786 # We only have to read through the filelog to find wanted revisions
1787 1787
1788 1788 try:
1789 1789 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1790 1790 except FileWalkError:
1791 1791 slowpath = True
1792 1792
1793 1793 # We decided to fall back to the slowpath because at least one
1794 1794 # of the paths was not a file. Check to see if at least one of them
1795 1795 # existed in history, otherwise simply return
1796 1796 for path in match.files():
1797 1797 if path == '.' or path in repo.store:
1798 1798 break
1799 1799 else:
1800 1800 return []
1801 1801
1802 1802 if slowpath:
1803 1803 # We have to read the changelog to match filenames against
1804 1804 # changed files
1805 1805
1806 1806 if follow:
1807 1807 raise error.Abort(_('can only follow copies/renames for explicit '
1808 1808 'filenames'))
1809 1809
1810 1810 # The slow path checks files modified in every changeset.
1811 1811 # This is really slow on large repos, so compute the set lazily.
1812 1812 class lazywantedset(object):
1813 1813 def __init__(self):
1814 1814 self.set = set()
1815 1815 self.revs = set(revs)
1816 1816
1817 1817 # No need to worry about locality here because it will be accessed
1818 1818 # in the same order as the increasing window below.
1819 1819 def __contains__(self, value):
1820 1820 if value in self.set:
1821 1821 return True
1822 1822 elif not value in self.revs:
1823 1823 return False
1824 1824 else:
1825 1825 self.revs.discard(value)
1826 1826 ctx = change(value)
1827 1827 matches = filter(match, ctx.files())
1828 1828 if matches:
1829 1829 fncache[value] = matches
1830 1830 self.set.add(value)
1831 1831 return True
1832 1832 return False
1833 1833
1834 1834 def discard(self, value):
1835 1835 self.revs.discard(value)
1836 1836 self.set.discard(value)
1837 1837
1838 1838 wanted = lazywantedset()
1839 1839
1840 1840 # it might be worthwhile to do this in the iterator if the rev range
1841 1841 # is descending and the prune args are all within that range
1842 1842 for rev in opts.get('prune', ()):
1843 1843 rev = repo[rev].rev()
1844 1844 ff = _followfilter(repo)
1845 1845 stop = min(revs[0], revs[-1])
1846 1846 for x in xrange(rev, stop - 1, -1):
1847 1847 if ff.match(x):
1848 1848 wanted = wanted - [x]
1849 1849
1850 1850 # Now that wanted is correctly initialized, we can iterate over the
1851 1851 # revision range, yielding only revisions in wanted.
1852 1852 def iterate():
1853 1853 if follow and match.always():
1854 1854 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1855 1855 def want(rev):
1856 1856 return ff.match(rev) and rev in wanted
1857 1857 else:
1858 1858 def want(rev):
1859 1859 return rev in wanted
1860 1860
1861 1861 it = iter(revs)
1862 1862 stopiteration = False
1863 1863 for windowsize in increasingwindows():
1864 1864 nrevs = []
1865 1865 for i in xrange(windowsize):
1866 1866 rev = next(it, None)
1867 1867 if rev is None:
1868 1868 stopiteration = True
1869 1869 break
1870 1870 elif want(rev):
1871 1871 nrevs.append(rev)
1872 1872 for rev in sorted(nrevs):
1873 1873 fns = fncache.get(rev)
1874 1874 ctx = change(rev)
1875 1875 if not fns:
1876 1876 def fns_generator():
1877 1877 for f in ctx.files():
1878 1878 if match(f):
1879 1879 yield f
1880 1880 fns = fns_generator()
1881 1881 prepare(ctx, fns)
1882 1882 for rev in nrevs:
1883 1883 yield change(rev)
1884 1884
1885 1885 if stopiteration:
1886 1886 break
1887 1887
1888 1888 return iterate()
1889 1889
1890 1890 def _makefollowlogfilematcher(repo, files, followfirst):
1891 1891 # When displaying a revision with --patch --follow FILE, we have
1892 1892 # to know which file of the revision must be diffed. With
1893 1893 # --follow, we want the names of the ancestors of FILE in the
1894 1894 # revision, stored in "fcache". "fcache" is populated by
1895 1895 # reproducing the graph traversal already done by --follow revset
1896 1896 # and relating linkrevs to file names (which is not "correct" but
1897 1897 # good enough).
1898 1898 fcache = {}
1899 1899 fcacheready = [False]
1900 1900 pctx = repo['.']
1901 1901
1902 1902 def populate():
1903 1903 for fn in files:
1904 1904 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1905 1905 for c in i:
1906 1906 fcache.setdefault(c.linkrev(), set()).add(c.path())
1907 1907
1908 1908 def filematcher(rev):
1909 1909 if not fcacheready[0]:
1910 1910 # Lazy initialization
1911 1911 fcacheready[0] = True
1912 1912 populate()
1913 1913 return scmutil.matchfiles(repo, fcache.get(rev, []))
1914 1914
1915 1915 return filematcher
1916 1916
1917 1917 def _makenofollowlogfilematcher(repo, pats, opts):
1918 1918 '''hook for extensions to override the filematcher for non-follow cases'''
1919 1919 return None
1920 1920
1921 1921 def _makelogrevset(repo, pats, opts, revs):
1922 1922 """Return (expr, filematcher) where expr is a revset string built
1923 1923 from log options and file patterns or None. If --stat or --patch
1924 1924 are not passed filematcher is None. Otherwise it is a callable
1925 1925 taking a revision number and returning a match objects filtering
1926 1926 the files to be detailed when displaying the revision.
1927 1927 """
1928 1928 opt2revset = {
1929 1929 'no_merges': ('not merge()', None),
1930 1930 'only_merges': ('merge()', None),
1931 1931 '_ancestors': ('ancestors(%(val)s)', None),
1932 1932 '_fancestors': ('_firstancestors(%(val)s)', None),
1933 1933 '_descendants': ('descendants(%(val)s)', None),
1934 1934 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1935 1935 '_matchfiles': ('_matchfiles(%(val)s)', None),
1936 1936 'date': ('date(%(val)r)', None),
1937 1937 'branch': ('branch(%(val)r)', ' or '),
1938 1938 '_patslog': ('filelog(%(val)r)', ' or '),
1939 1939 '_patsfollow': ('follow(%(val)r)', ' or '),
1940 1940 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1941 1941 'keyword': ('keyword(%(val)r)', ' or '),
1942 1942 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1943 1943 'user': ('user(%(val)r)', ' or '),
1944 1944 }
1945 1945
1946 1946 opts = dict(opts)
1947 1947 # follow or not follow?
1948 1948 follow = opts.get('follow') or opts.get('follow_first')
1949 1949 if opts.get('follow_first'):
1950 1950 followfirst = 1
1951 1951 else:
1952 1952 followfirst = 0
1953 1953 # --follow with FILE behavior depends on revs...
1954 1954 it = iter(revs)
1955 1955 startrev = it.next()
1956 1956 followdescendants = startrev < next(it, startrev)
1957 1957
1958 1958 # branch and only_branch are really aliases and must be handled at
1959 1959 # the same time
1960 1960 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1961 1961 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1962 1962 # pats/include/exclude are passed to match.match() directly in
1963 1963 # _matchfiles() revset but walkchangerevs() builds its matcher with
1964 1964 # scmutil.match(). The difference is input pats are globbed on
1965 1965 # platforms without shell expansion (windows).
1966 1966 wctx = repo[None]
1967 1967 match, pats = scmutil.matchandpats(wctx, pats, opts)
1968 1968 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1969 1969 opts.get('removed'))
1970 1970 if not slowpath:
1971 1971 for f in match.files():
1972 1972 if follow and f not in wctx:
1973 1973 # If the file exists, it may be a directory, so let it
1974 1974 # take the slow path.
1975 1975 if os.path.exists(repo.wjoin(f)):
1976 1976 slowpath = True
1977 1977 continue
1978 1978 else:
1979 1979 raise error.Abort(_('cannot follow file not in parent '
1980 1980 'revision: "%s"') % f)
1981 1981 filelog = repo.file(f)
1982 1982 if not filelog:
1983 1983 # A zero count may be a directory or deleted file, so
1984 1984 # try to find matching entries on the slow path.
1985 1985 if follow:
1986 1986 raise error.Abort(
1987 1987 _('cannot follow nonexistent file: "%s"') % f)
1988 1988 slowpath = True
1989 1989
1990 1990 # We decided to fall back to the slowpath because at least one
1991 1991 # of the paths was not a file. Check to see if at least one of them
1992 1992 # existed in history - in that case, we'll continue down the
1993 1993 # slowpath; otherwise, we can turn off the slowpath
1994 1994 if slowpath:
1995 1995 for path in match.files():
1996 1996 if path == '.' or path in repo.store:
1997 1997 break
1998 1998 else:
1999 1999 slowpath = False
2000 2000
2001 2001 fpats = ('_patsfollow', '_patsfollowfirst')
2002 2002 fnopats = (('_ancestors', '_fancestors'),
2003 2003 ('_descendants', '_fdescendants'))
2004 2004 if slowpath:
2005 2005 # See walkchangerevs() slow path.
2006 2006 #
2007 2007 # pats/include/exclude cannot be represented as separate
2008 2008 # revset expressions as their filtering logic applies at file
2009 2009 # level. For instance "-I a -X a" matches a revision touching
2010 2010 # "a" and "b" while "file(a) and not file(b)" does
2011 2011 # not. Besides, filesets are evaluated against the working
2012 2012 # directory.
2013 2013 matchargs = ['r:', 'd:relpath']
2014 2014 for p in pats:
2015 2015 matchargs.append('p:' + p)
2016 2016 for p in opts.get('include', []):
2017 2017 matchargs.append('i:' + p)
2018 2018 for p in opts.get('exclude', []):
2019 2019 matchargs.append('x:' + p)
2020 2020 matchargs = ','.join(('%r' % p) for p in matchargs)
2021 2021 opts['_matchfiles'] = matchargs
2022 2022 if follow:
2023 2023 opts[fnopats[0][followfirst]] = '.'
2024 2024 else:
2025 2025 if follow:
2026 2026 if pats:
2027 2027 # follow() revset interprets its file argument as a
2028 2028 # manifest entry, so use match.files(), not pats.
2029 2029 opts[fpats[followfirst]] = list(match.files())
2030 2030 else:
2031 2031 op = fnopats[followdescendants][followfirst]
2032 2032 opts[op] = 'rev(%d)' % startrev
2033 2033 else:
2034 2034 opts['_patslog'] = list(pats)
2035 2035
2036 2036 filematcher = None
2037 2037 if opts.get('patch') or opts.get('stat'):
2038 2038 # When following files, track renames via a special matcher.
2039 2039 # If we're forced to take the slowpath it means we're following
2040 2040 # at least one pattern/directory, so don't bother with rename tracking.
2041 2041 if follow and not match.always() and not slowpath:
2042 2042 # _makefollowlogfilematcher expects its files argument to be
2043 2043 # relative to the repo root, so use match.files(), not pats.
2044 2044 filematcher = _makefollowlogfilematcher(repo, match.files(),
2045 2045 followfirst)
2046 2046 else:
2047 2047 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2048 2048 if filematcher is None:
2049 2049 filematcher = lambda rev: match
2050 2050
2051 2051 expr = []
2052 2052 for op, val in sorted(opts.iteritems()):
2053 2053 if not val:
2054 2054 continue
2055 2055 if op not in opt2revset:
2056 2056 continue
2057 2057 revop, andor = opt2revset[op]
2058 2058 if '%(val)' not in revop:
2059 2059 expr.append(revop)
2060 2060 else:
2061 2061 if not isinstance(val, list):
2062 2062 e = revop % {'val': val}
2063 2063 else:
2064 2064 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2065 2065 expr.append(e)
2066 2066
2067 2067 if expr:
2068 2068 expr = '(' + ' and '.join(expr) + ')'
2069 2069 else:
2070 2070 expr = None
2071 2071 return expr, filematcher
2072 2072
2073 2073 def _logrevs(repo, opts):
2074 2074 # Default --rev value depends on --follow but --follow behavior
2075 2075 # depends on revisions resolved from --rev...
2076 2076 follow = opts.get('follow') or opts.get('follow_first')
2077 2077 if opts.get('rev'):
2078 2078 revs = scmutil.revrange(repo, opts['rev'])
2079 2079 elif follow and repo.dirstate.p1() == nullid:
2080 2080 revs = revset.baseset()
2081 2081 elif follow:
2082 2082 revs = repo.revs('reverse(:.)')
2083 2083 else:
2084 2084 revs = revset.spanset(repo)
2085 2085 revs.reverse()
2086 2086 return revs
2087 2087
2088 2088 def getgraphlogrevs(repo, pats, opts):
2089 2089 """Return (revs, expr, filematcher) where revs is an iterable of
2090 2090 revision numbers, expr is a revset string built from log options
2091 2091 and file patterns or None, and used to filter 'revs'. If --stat or
2092 2092 --patch are not passed filematcher is None. Otherwise it is a
2093 2093 callable taking a revision number and returning a match objects
2094 2094 filtering the files to be detailed when displaying the revision.
2095 2095 """
2096 2096 limit = loglimit(opts)
2097 2097 revs = _logrevs(repo, opts)
2098 2098 if not revs:
2099 2099 return revset.baseset(), None, None
2100 2100 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2101 2101 if opts.get('rev'):
2102 2102 # User-specified revs might be unsorted, but don't sort before
2103 2103 # _makelogrevset because it might depend on the order of revs
2104 2104 revs.sort(reverse=True)
2105 2105 if expr:
2106 2106 # Revset matchers often operate faster on revisions in changelog
2107 2107 # order, because most filters deal with the changelog.
2108 2108 revs.reverse()
2109 2109 matcher = revset.match(repo.ui, expr)
2110 2110 # Revset matches can reorder revisions. "A or B" typically returns
2111 2111 # returns the revision matching A then the revision matching B. Sort
2112 2112 # again to fix that.
2113 2113 revs = matcher(repo, revs)
2114 2114 revs.sort(reverse=True)
2115 2115 if limit is not None:
2116 2116 limitedrevs = []
2117 2117 for idx, rev in enumerate(revs):
2118 2118 if idx >= limit:
2119 2119 break
2120 2120 limitedrevs.append(rev)
2121 2121 revs = revset.baseset(limitedrevs)
2122 2122
2123 2123 return revs, expr, filematcher
2124 2124
2125 2125 def getlogrevs(repo, pats, opts):
2126 2126 """Return (revs, expr, filematcher) where revs is an iterable of
2127 2127 revision numbers, expr is a revset string built from log options
2128 2128 and file patterns or None, and used to filter 'revs'. If --stat or
2129 2129 --patch are not passed filematcher is None. Otherwise it is a
2130 2130 callable taking a revision number and returning a match objects
2131 2131 filtering the files to be detailed when displaying the revision.
2132 2132 """
2133 2133 limit = loglimit(opts)
2134 2134 revs = _logrevs(repo, opts)
2135 2135 if not revs:
2136 2136 return revset.baseset([]), None, None
2137 2137 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2138 2138 if expr:
2139 2139 # Revset matchers often operate faster on revisions in changelog
2140 2140 # order, because most filters deal with the changelog.
2141 2141 if not opts.get('rev'):
2142 2142 revs.reverse()
2143 2143 matcher = revset.match(repo.ui, expr)
2144 2144 # Revset matches can reorder revisions. "A or B" typically returns
2145 2145 # returns the revision matching A then the revision matching B. Sort
2146 2146 # again to fix that.
2147 2147 revs = matcher(repo, revs)
2148 2148 if not opts.get('rev'):
2149 2149 revs.sort(reverse=True)
2150 2150 if limit is not None:
2151 2151 limitedrevs = []
2152 2152 for idx, r in enumerate(revs):
2153 2153 if limit <= idx:
2154 2154 break
2155 2155 limitedrevs.append(r)
2156 2156 revs = revset.baseset(limitedrevs)
2157 2157
2158 2158 return revs, expr, filematcher
2159 2159
2160 2160 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2161 2161 filematcher=None):
2162 2162 seen, state = [], graphmod.asciistate()
2163 2163 for rev, type, ctx, parents in dag:
2164 2164 char = 'o'
2165 2165 if ctx.node() in showparents:
2166 2166 char = '@'
2167 2167 elif ctx.obsolete():
2168 2168 char = 'x'
2169 2169 elif ctx.closesbranch():
2170 2170 char = '_'
2171 2171 copies = None
2172 2172 if getrenamed and ctx.rev():
2173 2173 copies = []
2174 2174 for fn in ctx.files():
2175 2175 rename = getrenamed(fn, ctx.rev())
2176 2176 if rename:
2177 2177 copies.append((fn, rename[0]))
2178 2178 revmatchfn = None
2179 2179 if filematcher is not None:
2180 2180 revmatchfn = filematcher(ctx.rev())
2181 2181 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2182 2182 lines = displayer.hunk.pop(rev).split('\n')
2183 2183 if not lines[-1]:
2184 2184 del lines[-1]
2185 2185 displayer.flush(ctx)
2186 2186 edges = edgefn(type, char, lines, seen, rev, parents)
2187 2187 for type, char, lines, coldata in edges:
2188 2188 graphmod.ascii(ui, state, type, char, lines, coldata)
2189 2189 displayer.close()
2190 2190
2191 2191 def graphlog(ui, repo, *pats, **opts):
2192 2192 # Parameters are identical to log command ones
2193 2193 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2194 2194 revdag = graphmod.dagwalker(repo, revs)
2195 2195
2196 2196 getrenamed = None
2197 2197 if opts.get('copies'):
2198 2198 endrev = None
2199 2199 if opts.get('rev'):
2200 2200 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2201 2201 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2202 2202 displayer = show_changeset(ui, repo, opts, buffered=True)
2203 2203 showparents = [ctx.node() for ctx in repo[None].parents()]
2204 2204 displaygraph(ui, revdag, displayer, showparents,
2205 2205 graphmod.asciiedges, getrenamed, filematcher)
2206 2206
2207 2207 def checkunsupportedgraphflags(pats, opts):
2208 2208 for op in ["newest_first"]:
2209 2209 if op in opts and opts[op]:
2210 2210 raise error.Abort(_("-G/--graph option is incompatible with --%s")
2211 2211 % op.replace("_", "-"))
2212 2212
2213 2213 def graphrevs(repo, nodes, opts):
2214 2214 limit = loglimit(opts)
2215 2215 nodes.reverse()
2216 2216 if limit is not None:
2217 2217 nodes = nodes[:limit]
2218 2218 return graphmod.nodes(repo, nodes)
2219 2219
2220 2220 def add(ui, repo, match, prefix, explicitonly, **opts):
2221 2221 join = lambda f: os.path.join(prefix, f)
2222 2222 bad = []
2223 2223
2224 2224 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2225 2225 names = []
2226 2226 wctx = repo[None]
2227 2227 cca = None
2228 2228 abort, warn = scmutil.checkportabilityalert(ui)
2229 2229 if abort or warn:
2230 2230 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2231 2231
2232 2232 badmatch = matchmod.badmatch(match, badfn)
2233 2233 dirstate = repo.dirstate
2234 2234 # We don't want to just call wctx.walk here, since it would return a lot of
2235 2235 # clean files, which we aren't interested in and takes time.
2236 2236 for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
2237 2237 True, False, full=False)):
2238 2238 exact = match.exact(f)
2239 2239 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2240 2240 if cca:
2241 2241 cca(f)
2242 2242 names.append(f)
2243 2243 if ui.verbose or not exact:
2244 2244 ui.status(_('adding %s\n') % match.rel(f))
2245 2245
2246 2246 for subpath in sorted(wctx.substate):
2247 2247 sub = wctx.sub(subpath)
2248 2248 try:
2249 2249 submatch = matchmod.narrowmatcher(subpath, match)
2250 2250 if opts.get('subrepos'):
2251 2251 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2252 2252 else:
2253 2253 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2254 2254 except error.LookupError:
2255 2255 ui.status(_("skipping missing subrepository: %s\n")
2256 2256 % join(subpath))
2257 2257
2258 2258 if not opts.get('dry_run'):
2259 2259 rejected = wctx.add(names, prefix)
2260 2260 bad.extend(f for f in rejected if f in match.files())
2261 2261 return bad
2262 2262
2263 2263 def forget(ui, repo, match, prefix, explicitonly):
2264 2264 join = lambda f: os.path.join(prefix, f)
2265 2265 bad = []
2266 2266 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2267 2267 wctx = repo[None]
2268 2268 forgot = []
2269 2269
2270 2270 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2271 2271 forget = sorted(s[0] + s[1] + s[3] + s[6])
2272 2272 if explicitonly:
2273 2273 forget = [f for f in forget if match.exact(f)]
2274 2274
2275 2275 for subpath in sorted(wctx.substate):
2276 2276 sub = wctx.sub(subpath)
2277 2277 try:
2278 2278 submatch = matchmod.narrowmatcher(subpath, match)
2279 2279 subbad, subforgot = sub.forget(submatch, prefix)
2280 2280 bad.extend([subpath + '/' + f for f in subbad])
2281 2281 forgot.extend([subpath + '/' + f for f in subforgot])
2282 2282 except error.LookupError:
2283 2283 ui.status(_("skipping missing subrepository: %s\n")
2284 2284 % join(subpath))
2285 2285
2286 2286 if not explicitonly:
2287 2287 for f in match.files():
2288 2288 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2289 2289 if f not in forgot:
2290 2290 if repo.wvfs.exists(f):
2291 2291 # Don't complain if the exact case match wasn't given.
2292 2292 # But don't do this until after checking 'forgot', so
2293 2293 # that subrepo files aren't normalized, and this op is
2294 2294 # purely from data cached by the status walk above.
2295 2295 if repo.dirstate.normalize(f) in repo.dirstate:
2296 2296 continue
2297 2297 ui.warn(_('not removing %s: '
2298 2298 'file is already untracked\n')
2299 2299 % match.rel(f))
2300 2300 bad.append(f)
2301 2301
2302 2302 for f in forget:
2303 2303 if ui.verbose or not match.exact(f):
2304 2304 ui.status(_('removing %s\n') % match.rel(f))
2305 2305
2306 2306 rejected = wctx.forget(forget, prefix)
2307 2307 bad.extend(f for f in rejected if f in match.files())
2308 2308 forgot.extend(f for f in forget if f not in rejected)
2309 2309 return bad, forgot
2310 2310
2311 2311 def files(ui, ctx, m, fm, fmt, subrepos):
2312 2312 rev = ctx.rev()
2313 2313 ret = 1
2314 2314 ds = ctx.repo().dirstate
2315 2315
2316 2316 for f in ctx.matches(m):
2317 2317 if rev is None and ds[f] == 'r':
2318 2318 continue
2319 2319 fm.startitem()
2320 2320 if ui.verbose:
2321 2321 fc = ctx[f]
2322 2322 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2323 2323 fm.data(abspath=f)
2324 2324 fm.write('path', fmt, m.rel(f))
2325 2325 ret = 0
2326 2326
2327 2327 for subpath in sorted(ctx.substate):
2328 2328 def matchessubrepo(subpath):
2329 2329 return (m.always() or m.exact(subpath)
2330 2330 or any(f.startswith(subpath + '/') for f in m.files()))
2331 2331
2332 2332 if subrepos or matchessubrepo(subpath):
2333 2333 sub = ctx.sub(subpath)
2334 2334 try:
2335 2335 submatch = matchmod.narrowmatcher(subpath, m)
2336 2336 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2337 2337 ret = 0
2338 2338 except error.LookupError:
2339 2339 ui.status(_("skipping missing subrepository: %s\n")
2340 2340 % m.abs(subpath))
2341 2341
2342 2342 return ret
2343 2343
2344 2344 def remove(ui, repo, m, prefix, after, force, subrepos):
2345 2345 join = lambda f: os.path.join(prefix, f)
2346 2346 ret = 0
2347 2347 s = repo.status(match=m, clean=True)
2348 2348 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2349 2349
2350 2350 wctx = repo[None]
2351 2351
2352 2352 for subpath in sorted(wctx.substate):
2353 2353 def matchessubrepo(matcher, subpath):
2354 2354 if matcher.exact(subpath):
2355 2355 return True
2356 2356 for f in matcher.files():
2357 2357 if f.startswith(subpath):
2358 2358 return True
2359 2359 return False
2360 2360
2361 2361 if subrepos or matchessubrepo(m, subpath):
2362 2362 sub = wctx.sub(subpath)
2363 2363 try:
2364 2364 submatch = matchmod.narrowmatcher(subpath, m)
2365 2365 if sub.removefiles(submatch, prefix, after, force, subrepos):
2366 2366 ret = 1
2367 2367 except error.LookupError:
2368 2368 ui.status(_("skipping missing subrepository: %s\n")
2369 2369 % join(subpath))
2370 2370
2371 2371 # warn about failure to delete explicit files/dirs
2372 2372 deleteddirs = util.dirs(deleted)
2373 2373 for f in m.files():
2374 2374 def insubrepo():
2375 2375 for subpath in wctx.substate:
2376 2376 if f.startswith(subpath):
2377 2377 return True
2378 2378 return False
2379 2379
2380 2380 isdir = f in deleteddirs or wctx.hasdir(f)
2381 2381 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2382 2382 continue
2383 2383
2384 2384 if repo.wvfs.exists(f):
2385 2385 if repo.wvfs.isdir(f):
2386 2386 ui.warn(_('not removing %s: no tracked files\n')
2387 2387 % m.rel(f))
2388 2388 else:
2389 2389 ui.warn(_('not removing %s: file is untracked\n')
2390 2390 % m.rel(f))
2391 2391 # missing files will generate a warning elsewhere
2392 2392 ret = 1
2393 2393
2394 2394 if force:
2395 2395 list = modified + deleted + clean + added
2396 2396 elif after:
2397 2397 list = deleted
2398 2398 for f in modified + added + clean:
2399 2399 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2400 2400 ret = 1
2401 2401 else:
2402 2402 list = deleted + clean
2403 2403 for f in modified:
2404 2404 ui.warn(_('not removing %s: file is modified (use -f'
2405 2405 ' to force removal)\n') % m.rel(f))
2406 2406 ret = 1
2407 2407 for f in added:
2408 2408 ui.warn(_('not removing %s: file has been marked for add'
2409 2409 ' (use forget to undo)\n') % m.rel(f))
2410 2410 ret = 1
2411 2411
2412 2412 for f in sorted(list):
2413 2413 if ui.verbose or not m.exact(f):
2414 2414 ui.status(_('removing %s\n') % m.rel(f))
2415 2415
2416 2416 wlock = repo.wlock()
2417 2417 try:
2418 2418 if not after:
2419 2419 for f in list:
2420 2420 if f in added:
2421 2421 continue # we never unlink added files on remove
2422 2422 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2423 2423 repo[None].forget(list)
2424 2424 finally:
2425 2425 wlock.release()
2426 2426
2427 2427 return ret
2428 2428
2429 2429 def cat(ui, repo, ctx, matcher, prefix, **opts):
2430 2430 err = 1
2431 2431
2432 2432 def write(path):
2433 2433 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2434 2434 pathname=os.path.join(prefix, path))
2435 2435 data = ctx[path].data()
2436 2436 if opts.get('decode'):
2437 2437 data = repo.wwritedata(path, data)
2438 2438 fp.write(data)
2439 2439 fp.close()
2440 2440
2441 2441 # Automation often uses hg cat on single files, so special case it
2442 2442 # for performance to avoid the cost of parsing the manifest.
2443 2443 if len(matcher.files()) == 1 and not matcher.anypats():
2444 2444 file = matcher.files()[0]
2445 2445 mf = repo.manifest
2446 2446 mfnode = ctx.manifestnode()
2447 2447 if mfnode and mf.find(mfnode, file)[0]:
2448 2448 write(file)
2449 2449 return 0
2450 2450
2451 2451 # Don't warn about "missing" files that are really in subrepos
2452 2452 def badfn(path, msg):
2453 2453 for subpath in ctx.substate:
2454 2454 if path.startswith(subpath):
2455 2455 return
2456 2456 matcher.bad(path, msg)
2457 2457
2458 2458 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2459 2459 write(abs)
2460 2460 err = 0
2461 2461
2462 2462 for subpath in sorted(ctx.substate):
2463 2463 sub = ctx.sub(subpath)
2464 2464 try:
2465 2465 submatch = matchmod.narrowmatcher(subpath, matcher)
2466 2466
2467 2467 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2468 2468 **opts):
2469 2469 err = 0
2470 2470 except error.RepoLookupError:
2471 2471 ui.status(_("skipping missing subrepository: %s\n")
2472 2472 % os.path.join(prefix, subpath))
2473 2473
2474 2474 return err
2475 2475
2476 2476 def commit(ui, repo, commitfunc, pats, opts):
2477 2477 '''commit the specified files or all outstanding changes'''
2478 2478 date = opts.get('date')
2479 2479 if date:
2480 2480 opts['date'] = util.parsedate(date)
2481 2481 message = logmessage(ui, opts)
2482 2482 matcher = scmutil.match(repo[None], pats, opts)
2483 2483
2484 2484 # extract addremove carefully -- this function can be called from a command
2485 2485 # that doesn't support addremove
2486 2486 if opts.get('addremove'):
2487 2487 if scmutil.addremove(repo, matcher, "", opts) != 0:
2488 2488 raise error.Abort(
2489 2489 _("failed to mark all new/missing files as added/removed"))
2490 2490
2491 2491 return commitfunc(ui, repo, message, matcher, opts)
2492 2492
2493 2493 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2494 2494 # avoid cycle context -> subrepo -> cmdutil
2495 2495 import context
2496 2496
2497 2497 # amend will reuse the existing user if not specified, but the obsolete
2498 2498 # marker creation requires that the current user's name is specified.
2499 2499 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2500 2500 ui.username() # raise exception if username not set
2501 2501
2502 2502 ui.note(_('amending changeset %s\n') % old)
2503 2503 base = old.p1()
2504 2504 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2505 2505
2506 2506 wlock = lock = newid = None
2507 2507 try:
2508 2508 wlock = repo.wlock()
2509 2509 lock = repo.lock()
2510 2510 tr = repo.transaction('amend')
2511 2511 try:
2512 2512 # See if we got a message from -m or -l, if not, open the editor
2513 2513 # with the message of the changeset to amend
2514 2514 message = logmessage(ui, opts)
2515 2515 # ensure logfile does not conflict with later enforcement of the
2516 2516 # message. potential logfile content has been processed by
2517 2517 # `logmessage` anyway.
2518 2518 opts.pop('logfile')
2519 2519 # First, do a regular commit to record all changes in the working
2520 2520 # directory (if there are any)
2521 2521 ui.callhooks = False
2522 2522 activebookmark = repo._activebookmark
2523 2523 try:
2524 2524 repo._activebookmark = None
2525 2525 opts['message'] = 'temporary amend commit for %s' % old
2526 2526 node = commit(ui, repo, commitfunc, pats, opts)
2527 2527 finally:
2528 2528 repo._activebookmark = activebookmark
2529 2529 ui.callhooks = True
2530 2530 ctx = repo[node]
2531 2531
2532 2532 # Participating changesets:
2533 2533 #
2534 2534 # node/ctx o - new (intermediate) commit that contains changes
2535 2535 # | from working dir to go into amending commit
2536 2536 # | (or a workingctx if there were no changes)
2537 2537 # |
2538 2538 # old o - changeset to amend
2539 2539 # |
2540 2540 # base o - parent of amending changeset
2541 2541
2542 2542 # Update extra dict from amended commit (e.g. to preserve graft
2543 2543 # source)
2544 2544 extra.update(old.extra())
2545 2545
2546 2546 # Also update it from the intermediate commit or from the wctx
2547 2547 extra.update(ctx.extra())
2548 2548
2549 2549 if len(old.parents()) > 1:
2550 2550 # ctx.files() isn't reliable for merges, so fall back to the
2551 2551 # slower repo.status() method
2552 2552 files = set([fn for st in repo.status(base, old)[:3]
2553 2553 for fn in st])
2554 2554 else:
2555 2555 files = set(old.files())
2556 2556
2557 2557 # Second, we use either the commit we just did, or if there were no
2558 2558 # changes the parent of the working directory as the version of the
2559 2559 # files in the final amend commit
2560 2560 if node:
2561 2561 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2562 2562
2563 2563 user = ctx.user()
2564 2564 date = ctx.date()
2565 2565 # Recompute copies (avoid recording a -> b -> a)
2566 2566 copied = copies.pathcopies(base, ctx)
2567 2567 if old.p2:
2568 2568 copied.update(copies.pathcopies(old.p2(), ctx))
2569 2569
2570 2570 # Prune files which were reverted by the updates: if old
2571 2571 # introduced file X and our intermediate commit, node,
2572 2572 # renamed that file, then those two files are the same and
2573 2573 # we can discard X from our list of files. Likewise if X
2574 2574 # was deleted, it's no longer relevant
2575 2575 files.update(ctx.files())
2576 2576
2577 2577 def samefile(f):
2578 2578 if f in ctx.manifest():
2579 2579 a = ctx.filectx(f)
2580 2580 if f in base.manifest():
2581 2581 b = base.filectx(f)
2582 2582 return (not a.cmp(b)
2583 2583 and a.flags() == b.flags())
2584 2584 else:
2585 2585 return False
2586 2586 else:
2587 2587 return f not in base.manifest()
2588 2588 files = [f for f in files if not samefile(f)]
2589 2589
2590 2590 def filectxfn(repo, ctx_, path):
2591 2591 try:
2592 2592 fctx = ctx[path]
2593 2593 flags = fctx.flags()
2594 2594 mctx = context.memfilectx(repo,
2595 2595 fctx.path(), fctx.data(),
2596 2596 islink='l' in flags,
2597 2597 isexec='x' in flags,
2598 2598 copied=copied.get(path))
2599 2599 return mctx
2600 2600 except KeyError:
2601 2601 return None
2602 2602 else:
2603 2603 ui.note(_('copying changeset %s to %s\n') % (old, base))
2604 2604
2605 2605 # Use version of files as in the old cset
2606 2606 def filectxfn(repo, ctx_, path):
2607 2607 try:
2608 2608 return old.filectx(path)
2609 2609 except KeyError:
2610 2610 return None
2611 2611
2612 2612 user = opts.get('user') or old.user()
2613 2613 date = opts.get('date') or old.date()
2614 2614 editform = mergeeditform(old, 'commit.amend')
2615 2615 editor = getcommiteditor(editform=editform, **opts)
2616 2616 if not message:
2617 2617 editor = getcommiteditor(edit=True, editform=editform)
2618 2618 message = old.description()
2619 2619
2620 2620 pureextra = extra.copy()
2621 2621 extra['amend_source'] = old.hex()
2622 2622
2623 2623 new = context.memctx(repo,
2624 2624 parents=[base.node(), old.p2().node()],
2625 2625 text=message,
2626 2626 files=files,
2627 2627 filectxfn=filectxfn,
2628 2628 user=user,
2629 2629 date=date,
2630 2630 extra=extra,
2631 2631 editor=editor)
2632 2632
2633 2633 newdesc = changelog.stripdesc(new.description())
2634 2634 if ((not node)
2635 2635 and newdesc == old.description()
2636 2636 and user == old.user()
2637 2637 and date == old.date()
2638 2638 and pureextra == old.extra()):
2639 2639 # nothing changed. continuing here would create a new node
2640 2640 # anyway because of the amend_source noise.
2641 2641 #
2642 2642 # This not what we expect from amend.
2643 2643 return old.node()
2644 2644
2645 2645 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2646 2646 try:
2647 2647 if opts.get('secret'):
2648 2648 commitphase = 'secret'
2649 2649 else:
2650 2650 commitphase = old.phase()
2651 2651 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2652 2652 newid = repo.commitctx(new)
2653 2653 finally:
2654 2654 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2655 2655 if newid != old.node():
2656 2656 # Reroute the working copy parent to the new changeset
2657 2657 repo.setparents(newid, nullid)
2658 2658
2659 2659 # Move bookmarks from old parent to amend commit
2660 2660 bms = repo.nodebookmarks(old.node())
2661 2661 if bms:
2662 2662 marks = repo._bookmarks
2663 2663 for bm in bms:
2664 2664 ui.debug('moving bookmarks %r from %s to %s\n' %
2665 2665 (marks, old.hex(), hex(newid)))
2666 2666 marks[bm] = newid
2667 2667 marks.recordchange(tr)
2668 2668 #commit the whole amend process
2669 2669 if createmarkers:
2670 2670 # mark the new changeset as successor of the rewritten one
2671 2671 new = repo[newid]
2672 2672 obs = [(old, (new,))]
2673 2673 if node:
2674 2674 obs.append((ctx, ()))
2675 2675
2676 2676 obsolete.createmarkers(repo, obs)
2677 2677 tr.close()
2678 2678 finally:
2679 2679 tr.release()
2680 2680 if not createmarkers and newid != old.node():
2681 2681 # Strip the intermediate commit (if there was one) and the amended
2682 2682 # commit
2683 2683 if node:
2684 2684 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2685 2685 ui.note(_('stripping amended changeset %s\n') % old)
2686 2686 repair.strip(ui, repo, old.node(), topic='amend-backup')
2687 2687 finally:
2688 2688 lockmod.release(lock, wlock)
2689 2689 return newid
2690 2690
2691 2691 def commiteditor(repo, ctx, subs, editform=''):
2692 2692 if ctx.description():
2693 2693 return ctx.description()
2694 2694 return commitforceeditor(repo, ctx, subs, editform=editform,
2695 2695 unchangedmessagedetection=True)
2696 2696
2697 2697 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2698 2698 editform='', unchangedmessagedetection=False):
2699 2699 if not extramsg:
2700 2700 extramsg = _("Leave message empty to abort commit.")
2701 2701
2702 2702 forms = [e for e in editform.split('.') if e]
2703 2703 forms.insert(0, 'changeset')
2704 2704 templatetext = None
2705 2705 while forms:
2706 2706 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2707 2707 if tmpl:
2708 2708 templatetext = committext = buildcommittemplate(
2709 2709 repo, ctx, subs, extramsg, tmpl)
2710 2710 break
2711 2711 forms.pop()
2712 2712 else:
2713 2713 committext = buildcommittext(repo, ctx, subs, extramsg)
2714 2714
2715 2715 # run editor in the repository root
2716 2716 olddir = os.getcwd()
2717 2717 os.chdir(repo.root)
2718
2719 # make in-memory changes visible to external process
2720 tr = repo.currenttransaction()
2721 repo.dirstate.write(tr)
2722 pending = tr and tr.writepending() and repo.root
2723
2718 2724 editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
2719 editform=editform)
2720
2725 editform=editform, pending=pending)
2721 2726 text = re.sub("(?m)^HG:.*(\n|$)", "", editortext)
2722 2727 os.chdir(olddir)
2723 2728
2724 2729 if finishdesc:
2725 2730 text = finishdesc(text)
2726 2731 if not text.strip():
2727 2732 raise error.Abort(_("empty commit message"))
2728 2733 if unchangedmessagedetection and editortext == templatetext:
2729 2734 raise error.Abort(_("commit message unchanged"))
2730 2735
2731 2736 return text
2732 2737
2733 2738 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2734 2739 ui = repo.ui
2735 2740 tmpl, mapfile = gettemplate(ui, tmpl, None)
2736 2741
2737 2742 try:
2738 2743 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2739 2744 except SyntaxError as inst:
2740 2745 raise error.Abort(inst.args[0])
2741 2746
2742 2747 for k, v in repo.ui.configitems('committemplate'):
2743 2748 if k != 'changeset':
2744 2749 t.t.cache[k] = v
2745 2750
2746 2751 if not extramsg:
2747 2752 extramsg = '' # ensure that extramsg is string
2748 2753
2749 2754 ui.pushbuffer()
2750 2755 t.show(ctx, extramsg=extramsg)
2751 2756 return ui.popbuffer()
2752 2757
2753 2758 def hgprefix(msg):
2754 2759 return "\n".join(["HG: %s" % a for a in msg.split("\n") if a])
2755 2760
2756 2761 def buildcommittext(repo, ctx, subs, extramsg):
2757 2762 edittext = []
2758 2763 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2759 2764 if ctx.description():
2760 2765 edittext.append(ctx.description())
2761 2766 edittext.append("")
2762 2767 edittext.append("") # Empty line between message and comments.
2763 2768 edittext.append(hgprefix(_("Enter commit message."
2764 2769 " Lines beginning with 'HG:' are removed.")))
2765 2770 edittext.append(hgprefix(extramsg))
2766 2771 edittext.append("HG: --")
2767 2772 edittext.append(hgprefix(_("user: %s") % ctx.user()))
2768 2773 if ctx.p2():
2769 2774 edittext.append(hgprefix(_("branch merge")))
2770 2775 if ctx.branch():
2771 2776 edittext.append(hgprefix(_("branch '%s'") % ctx.branch()))
2772 2777 if bookmarks.isactivewdirparent(repo):
2773 2778 edittext.append(hgprefix(_("bookmark '%s'") % repo._activebookmark))
2774 2779 edittext.extend([hgprefix(_("subrepo %s") % s) for s in subs])
2775 2780 edittext.extend([hgprefix(_("added %s") % f) for f in added])
2776 2781 edittext.extend([hgprefix(_("changed %s") % f) for f in modified])
2777 2782 edittext.extend([hgprefix(_("removed %s") % f) for f in removed])
2778 2783 if not added and not modified and not removed:
2779 2784 edittext.append(hgprefix(_("no files changed")))
2780 2785 edittext.append("")
2781 2786
2782 2787 return "\n".join(edittext)
2783 2788
2784 2789 def commitstatus(repo, node, branch, bheads=None, opts=None):
2785 2790 if opts is None:
2786 2791 opts = {}
2787 2792 ctx = repo[node]
2788 2793 parents = ctx.parents()
2789 2794
2790 2795 if (not opts.get('amend') and bheads and node not in bheads and not
2791 2796 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2792 2797 repo.ui.status(_('created new head\n'))
2793 2798 # The message is not printed for initial roots. For the other
2794 2799 # changesets, it is printed in the following situations:
2795 2800 #
2796 2801 # Par column: for the 2 parents with ...
2797 2802 # N: null or no parent
2798 2803 # B: parent is on another named branch
2799 2804 # C: parent is a regular non head changeset
2800 2805 # H: parent was a branch head of the current branch
2801 2806 # Msg column: whether we print "created new head" message
2802 2807 # In the following, it is assumed that there already exists some
2803 2808 # initial branch heads of the current branch, otherwise nothing is
2804 2809 # printed anyway.
2805 2810 #
2806 2811 # Par Msg Comment
2807 2812 # N N y additional topo root
2808 2813 #
2809 2814 # B N y additional branch root
2810 2815 # C N y additional topo head
2811 2816 # H N n usual case
2812 2817 #
2813 2818 # B B y weird additional branch root
2814 2819 # C B y branch merge
2815 2820 # H B n merge with named branch
2816 2821 #
2817 2822 # C C y additional head from merge
2818 2823 # C H n merge with a head
2819 2824 #
2820 2825 # H H n head merge: head count decreases
2821 2826
2822 2827 if not opts.get('close_branch'):
2823 2828 for r in parents:
2824 2829 if r.closesbranch() and r.branch() == branch:
2825 2830 repo.ui.status(_('reopening closed branch head %d\n') % r)
2826 2831
2827 2832 if repo.ui.debugflag:
2828 2833 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2829 2834 elif repo.ui.verbose:
2830 2835 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2831 2836
2832 2837 def revert(ui, repo, ctx, parents, *pats, **opts):
2833 2838 parent, p2 = parents
2834 2839 node = ctx.node()
2835 2840
2836 2841 mf = ctx.manifest()
2837 2842 if node == p2:
2838 2843 parent = p2
2839 2844 if node == parent:
2840 2845 pmf = mf
2841 2846 else:
2842 2847 pmf = None
2843 2848
2844 2849 # need all matching names in dirstate and manifest of target rev,
2845 2850 # so have to walk both. do not print errors if files exist in one
2846 2851 # but not other. in both cases, filesets should be evaluated against
2847 2852 # workingctx to get consistent result (issue4497). this means 'set:**'
2848 2853 # cannot be used to select missing files from target rev.
2849 2854
2850 2855 # `names` is a mapping for all elements in working copy and target revision
2851 2856 # The mapping is in the form:
2852 2857 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2853 2858 names = {}
2854 2859
2855 2860 wlock = repo.wlock()
2856 2861 try:
2857 2862 ## filling of the `names` mapping
2858 2863 # walk dirstate to fill `names`
2859 2864
2860 2865 interactive = opts.get('interactive', False)
2861 2866 wctx = repo[None]
2862 2867 m = scmutil.match(wctx, pats, opts)
2863 2868
2864 2869 # we'll need this later
2865 2870 targetsubs = sorted(s for s in wctx.substate if m(s))
2866 2871
2867 2872 if not m.always():
2868 2873 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2869 2874 names[abs] = m.rel(abs), m.exact(abs)
2870 2875
2871 2876 # walk target manifest to fill `names`
2872 2877
2873 2878 def badfn(path, msg):
2874 2879 if path in names:
2875 2880 return
2876 2881 if path in ctx.substate:
2877 2882 return
2878 2883 path_ = path + '/'
2879 2884 for f in names:
2880 2885 if f.startswith(path_):
2881 2886 return
2882 2887 ui.warn("%s: %s\n" % (m.rel(path), msg))
2883 2888
2884 2889 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2885 2890 if abs not in names:
2886 2891 names[abs] = m.rel(abs), m.exact(abs)
2887 2892
2888 2893 # Find status of all file in `names`.
2889 2894 m = scmutil.matchfiles(repo, names)
2890 2895
2891 2896 changes = repo.status(node1=node, match=m,
2892 2897 unknown=True, ignored=True, clean=True)
2893 2898 else:
2894 2899 changes = repo.status(node1=node, match=m)
2895 2900 for kind in changes:
2896 2901 for abs in kind:
2897 2902 names[abs] = m.rel(abs), m.exact(abs)
2898 2903
2899 2904 m = scmutil.matchfiles(repo, names)
2900 2905
2901 2906 modified = set(changes.modified)
2902 2907 added = set(changes.added)
2903 2908 removed = set(changes.removed)
2904 2909 _deleted = set(changes.deleted)
2905 2910 unknown = set(changes.unknown)
2906 2911 unknown.update(changes.ignored)
2907 2912 clean = set(changes.clean)
2908 2913 modadded = set()
2909 2914
2910 2915 # split between files known in target manifest and the others
2911 2916 smf = set(mf)
2912 2917
2913 2918 # determine the exact nature of the deleted changesets
2914 2919 deladded = _deleted - smf
2915 2920 deleted = _deleted - deladded
2916 2921
2917 2922 # We need to account for the state of the file in the dirstate,
2918 2923 # even when we revert against something else than parent. This will
2919 2924 # slightly alter the behavior of revert (doing back up or not, delete
2920 2925 # or just forget etc).
2921 2926 if parent == node:
2922 2927 dsmodified = modified
2923 2928 dsadded = added
2924 2929 dsremoved = removed
2925 2930 # store all local modifications, useful later for rename detection
2926 2931 localchanges = dsmodified | dsadded
2927 2932 modified, added, removed = set(), set(), set()
2928 2933 else:
2929 2934 changes = repo.status(node1=parent, match=m)
2930 2935 dsmodified = set(changes.modified)
2931 2936 dsadded = set(changes.added)
2932 2937 dsremoved = set(changes.removed)
2933 2938 # store all local modifications, useful later for rename detection
2934 2939 localchanges = dsmodified | dsadded
2935 2940
2936 2941 # only take into account for removes between wc and target
2937 2942 clean |= dsremoved - removed
2938 2943 dsremoved &= removed
2939 2944 # distinct between dirstate remove and other
2940 2945 removed -= dsremoved
2941 2946
2942 2947 modadded = added & dsmodified
2943 2948 added -= modadded
2944 2949
2945 2950 # tell newly modified apart.
2946 2951 dsmodified &= modified
2947 2952 dsmodified |= modified & dsadded # dirstate added may needs backup
2948 2953 modified -= dsmodified
2949 2954
2950 2955 # We need to wait for some post-processing to update this set
2951 2956 # before making the distinction. The dirstate will be used for
2952 2957 # that purpose.
2953 2958 dsadded = added
2954 2959
2955 2960 # in case of merge, files that are actually added can be reported as
2956 2961 # modified, we need to post process the result
2957 2962 if p2 != nullid:
2958 2963 if pmf is None:
2959 2964 # only need parent manifest in the merge case,
2960 2965 # so do not read by default
2961 2966 pmf = repo[parent].manifest()
2962 2967 mergeadd = dsmodified - set(pmf)
2963 2968 dsadded |= mergeadd
2964 2969 dsmodified -= mergeadd
2965 2970
2966 2971 # if f is a rename, update `names` to also revert the source
2967 2972 cwd = repo.getcwd()
2968 2973 for f in localchanges:
2969 2974 src = repo.dirstate.copied(f)
2970 2975 # XXX should we check for rename down to target node?
2971 2976 if src and src not in names and repo.dirstate[src] == 'r':
2972 2977 dsremoved.add(src)
2973 2978 names[src] = (repo.pathto(src, cwd), True)
2974 2979
2975 2980 # distinguish between file to forget and the other
2976 2981 added = set()
2977 2982 for abs in dsadded:
2978 2983 if repo.dirstate[abs] != 'a':
2979 2984 added.add(abs)
2980 2985 dsadded -= added
2981 2986
2982 2987 for abs in deladded:
2983 2988 if repo.dirstate[abs] == 'a':
2984 2989 dsadded.add(abs)
2985 2990 deladded -= dsadded
2986 2991
2987 2992 # For files marked as removed, we check if an unknown file is present at
2988 2993 # the same path. If a such file exists it may need to be backed up.
2989 2994 # Making the distinction at this stage helps have simpler backup
2990 2995 # logic.
2991 2996 removunk = set()
2992 2997 for abs in removed:
2993 2998 target = repo.wjoin(abs)
2994 2999 if os.path.lexists(target):
2995 3000 removunk.add(abs)
2996 3001 removed -= removunk
2997 3002
2998 3003 dsremovunk = set()
2999 3004 for abs in dsremoved:
3000 3005 target = repo.wjoin(abs)
3001 3006 if os.path.lexists(target):
3002 3007 dsremovunk.add(abs)
3003 3008 dsremoved -= dsremovunk
3004 3009
3005 3010 # action to be actually performed by revert
3006 3011 # (<list of file>, message>) tuple
3007 3012 actions = {'revert': ([], _('reverting %s\n')),
3008 3013 'add': ([], _('adding %s\n')),
3009 3014 'remove': ([], _('removing %s\n')),
3010 3015 'drop': ([], _('removing %s\n')),
3011 3016 'forget': ([], _('forgetting %s\n')),
3012 3017 'undelete': ([], _('undeleting %s\n')),
3013 3018 'noop': (None, _('no changes needed to %s\n')),
3014 3019 'unknown': (None, _('file not managed: %s\n')),
3015 3020 }
3016 3021
3017 3022 # "constant" that convey the backup strategy.
3018 3023 # All set to `discard` if `no-backup` is set do avoid checking
3019 3024 # no_backup lower in the code.
3020 3025 # These values are ordered for comparison purposes
3021 3026 backup = 2 # unconditionally do backup
3022 3027 check = 1 # check if the existing file differs from target
3023 3028 discard = 0 # never do backup
3024 3029 if opts.get('no_backup'):
3025 3030 backup = check = discard
3026 3031
3027 3032 backupanddel = actions['remove']
3028 3033 if not opts.get('no_backup'):
3029 3034 backupanddel = actions['drop']
3030 3035
3031 3036 disptable = (
3032 3037 # dispatch table:
3033 3038 # file state
3034 3039 # action
3035 3040 # make backup
3036 3041
3037 3042 ## Sets that results that will change file on disk
3038 3043 # Modified compared to target, no local change
3039 3044 (modified, actions['revert'], discard),
3040 3045 # Modified compared to target, but local file is deleted
3041 3046 (deleted, actions['revert'], discard),
3042 3047 # Modified compared to target, local change
3043 3048 (dsmodified, actions['revert'], backup),
3044 3049 # Added since target
3045 3050 (added, actions['remove'], discard),
3046 3051 # Added in working directory
3047 3052 (dsadded, actions['forget'], discard),
3048 3053 # Added since target, have local modification
3049 3054 (modadded, backupanddel, backup),
3050 3055 # Added since target but file is missing in working directory
3051 3056 (deladded, actions['drop'], discard),
3052 3057 # Removed since target, before working copy parent
3053 3058 (removed, actions['add'], discard),
3054 3059 # Same as `removed` but an unknown file exists at the same path
3055 3060 (removunk, actions['add'], check),
3056 3061 # Removed since targe, marked as such in working copy parent
3057 3062 (dsremoved, actions['undelete'], discard),
3058 3063 # Same as `dsremoved` but an unknown file exists at the same path
3059 3064 (dsremovunk, actions['undelete'], check),
3060 3065 ## the following sets does not result in any file changes
3061 3066 # File with no modification
3062 3067 (clean, actions['noop'], discard),
3063 3068 # Existing file, not tracked anywhere
3064 3069 (unknown, actions['unknown'], discard),
3065 3070 )
3066 3071
3067 3072 for abs, (rel, exact) in sorted(names.items()):
3068 3073 # target file to be touch on disk (relative to cwd)
3069 3074 target = repo.wjoin(abs)
3070 3075 # search the entry in the dispatch table.
3071 3076 # if the file is in any of these sets, it was touched in the working
3072 3077 # directory parent and we are sure it needs to be reverted.
3073 3078 for table, (xlist, msg), dobackup in disptable:
3074 3079 if abs not in table:
3075 3080 continue
3076 3081 if xlist is not None:
3077 3082 xlist.append(abs)
3078 3083 if dobackup and (backup <= dobackup
3079 3084 or wctx[abs].cmp(ctx[abs])):
3080 3085 bakname = "%s.orig" % rel
3081 3086 ui.note(_('saving current version of %s as %s\n') %
3082 3087 (rel, bakname))
3083 3088 if not opts.get('dry_run'):
3084 3089 if interactive:
3085 3090 util.copyfile(target, bakname)
3086 3091 else:
3087 3092 util.rename(target, bakname)
3088 3093 if ui.verbose or not exact:
3089 3094 if not isinstance(msg, basestring):
3090 3095 msg = msg(abs)
3091 3096 ui.status(msg % rel)
3092 3097 elif exact:
3093 3098 ui.warn(msg % rel)
3094 3099 break
3095 3100
3096 3101 if not opts.get('dry_run'):
3097 3102 needdata = ('revert', 'add', 'undelete')
3098 3103 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3099 3104 _performrevert(repo, parents, ctx, actions, interactive)
3100 3105
3101 3106 if targetsubs:
3102 3107 # Revert the subrepos on the revert list
3103 3108 for sub in targetsubs:
3104 3109 try:
3105 3110 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3106 3111 except KeyError:
3107 3112 raise error.Abort("subrepository '%s' does not exist in %s!"
3108 3113 % (sub, short(ctx.node())))
3109 3114 finally:
3110 3115 wlock.release()
3111 3116
3112 3117 def _revertprefetch(repo, ctx, *files):
3113 3118 """Let extension changing the storage layer prefetch content"""
3114 3119 pass
3115 3120
3116 3121 def _performrevert(repo, parents, ctx, actions, interactive=False):
3117 3122 """function that actually perform all the actions computed for revert
3118 3123
3119 3124 This is an independent function to let extension to plug in and react to
3120 3125 the imminent revert.
3121 3126
3122 3127 Make sure you have the working directory locked when calling this function.
3123 3128 """
3124 3129 parent, p2 = parents
3125 3130 node = ctx.node()
3126 3131 def checkout(f):
3127 3132 fc = ctx[f]
3128 3133 repo.wwrite(f, fc.data(), fc.flags())
3129 3134
3130 3135 audit_path = pathutil.pathauditor(repo.root)
3131 3136 for f in actions['forget'][0]:
3132 3137 repo.dirstate.drop(f)
3133 3138 for f in actions['remove'][0]:
3134 3139 audit_path(f)
3135 3140 try:
3136 3141 util.unlinkpath(repo.wjoin(f))
3137 3142 except OSError:
3138 3143 pass
3139 3144 repo.dirstate.remove(f)
3140 3145 for f in actions['drop'][0]:
3141 3146 audit_path(f)
3142 3147 repo.dirstate.remove(f)
3143 3148
3144 3149 normal = None
3145 3150 if node == parent:
3146 3151 # We're reverting to our parent. If possible, we'd like status
3147 3152 # to report the file as clean. We have to use normallookup for
3148 3153 # merges to avoid losing information about merged/dirty files.
3149 3154 if p2 != nullid:
3150 3155 normal = repo.dirstate.normallookup
3151 3156 else:
3152 3157 normal = repo.dirstate.normal
3153 3158
3154 3159 newlyaddedandmodifiedfiles = set()
3155 3160 if interactive:
3156 3161 # Prompt the user for changes to revert
3157 3162 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3158 3163 m = scmutil.match(ctx, torevert, {})
3159 3164 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3160 3165 diffopts.nodates = True
3161 3166 diffopts.git = True
3162 3167 reversehunks = repo.ui.configbool('experimental',
3163 3168 'revertalternateinteractivemode',
3164 3169 True)
3165 3170 if reversehunks:
3166 3171 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3167 3172 else:
3168 3173 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3169 3174 originalchunks = patch.parsepatch(diff)
3170 3175
3171 3176 try:
3172 3177
3173 3178 chunks = recordfilter(repo.ui, originalchunks)
3174 3179 if reversehunks:
3175 3180 chunks = patch.reversehunks(chunks)
3176 3181
3177 3182 except patch.PatchError as err:
3178 3183 raise error.Abort(_('error parsing patch: %s') % err)
3179 3184
3180 3185 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3181 3186 # Apply changes
3182 3187 fp = cStringIO.StringIO()
3183 3188 for c in chunks:
3184 3189 c.write(fp)
3185 3190 dopatch = fp.tell()
3186 3191 fp.seek(0)
3187 3192 if dopatch:
3188 3193 try:
3189 3194 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3190 3195 except patch.PatchError as err:
3191 3196 raise error.Abort(str(err))
3192 3197 del fp
3193 3198 else:
3194 3199 for f in actions['revert'][0]:
3195 3200 checkout(f)
3196 3201 if normal:
3197 3202 normal(f)
3198 3203
3199 3204 for f in actions['add'][0]:
3200 3205 # Don't checkout modified files, they are already created by the diff
3201 3206 if f not in newlyaddedandmodifiedfiles:
3202 3207 checkout(f)
3203 3208 repo.dirstate.add(f)
3204 3209
3205 3210 normal = repo.dirstate.normallookup
3206 3211 if node == parent and p2 == nullid:
3207 3212 normal = repo.dirstate.normal
3208 3213 for f in actions['undelete'][0]:
3209 3214 checkout(f)
3210 3215 normal(f)
3211 3216
3212 3217 copied = copies.pathcopies(repo[parent], ctx)
3213 3218
3214 3219 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3215 3220 if f in copied:
3216 3221 repo.dirstate.copy(copied[f], f)
3217 3222
3218 3223 def command(table):
3219 3224 """Returns a function object to be used as a decorator for making commands.
3220 3225
3221 3226 This function receives a command table as its argument. The table should
3222 3227 be a dict.
3223 3228
3224 3229 The returned function can be used as a decorator for adding commands
3225 3230 to that command table. This function accepts multiple arguments to define
3226 3231 a command.
3227 3232
3228 3233 The first argument is the command name.
3229 3234
3230 3235 The options argument is an iterable of tuples defining command arguments.
3231 3236 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3232 3237
3233 3238 The synopsis argument defines a short, one line summary of how to use the
3234 3239 command. This shows up in the help output.
3235 3240
3236 3241 The norepo argument defines whether the command does not require a
3237 3242 local repository. Most commands operate against a repository, thus the
3238 3243 default is False.
3239 3244
3240 3245 The optionalrepo argument defines whether the command optionally requires
3241 3246 a local repository.
3242 3247
3243 3248 The inferrepo argument defines whether to try to find a repository from the
3244 3249 command line arguments. If True, arguments will be examined for potential
3245 3250 repository locations. See ``findrepo()``. If a repository is found, it
3246 3251 will be used.
3247 3252 """
3248 3253 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3249 3254 inferrepo=False):
3250 3255 def decorator(func):
3251 3256 if synopsis:
3252 3257 table[name] = func, list(options), synopsis
3253 3258 else:
3254 3259 table[name] = func, list(options)
3255 3260
3256 3261 if norepo:
3257 3262 # Avoid import cycle.
3258 3263 import commands
3259 3264 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3260 3265
3261 3266 if optionalrepo:
3262 3267 import commands
3263 3268 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3264 3269
3265 3270 if inferrepo:
3266 3271 import commands
3267 3272 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3268 3273
3269 3274 return func
3270 3275 return decorator
3271 3276
3272 3277 return cmd
3273 3278
3274 3279 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3275 3280 # commands.outgoing. "missing" is "missing" of the result of
3276 3281 # "findcommonoutgoing()"
3277 3282 outgoinghooks = util.hooks()
3278 3283
3279 3284 # a list of (ui, repo) functions called by commands.summary
3280 3285 summaryhooks = util.hooks()
3281 3286
3282 3287 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3283 3288 #
3284 3289 # functions should return tuple of booleans below, if 'changes' is None:
3285 3290 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3286 3291 #
3287 3292 # otherwise, 'changes' is a tuple of tuples below:
3288 3293 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3289 3294 # - (desturl, destbranch, destpeer, outgoing)
3290 3295 summaryremotehooks = util.hooks()
3291 3296
3292 3297 # A list of state files kept by multistep operations like graft.
3293 3298 # Since graft cannot be aborted, it is considered 'clearable' by update.
3294 3299 # note: bisect is intentionally excluded
3295 3300 # (state file, clearable, allowcommit, error, hint)
3296 3301 unfinishedstates = [
3297 3302 ('graftstate', True, False, _('graft in progress'),
3298 3303 _("use 'hg graft --continue' or 'hg update' to abort")),
3299 3304 ('updatestate', True, False, _('last update was interrupted'),
3300 3305 _("use 'hg update' to get a consistent checkout"))
3301 3306 ]
3302 3307
3303 3308 def checkunfinished(repo, commit=False):
3304 3309 '''Look for an unfinished multistep operation, like graft, and abort
3305 3310 if found. It's probably good to check this right before
3306 3311 bailifchanged().
3307 3312 '''
3308 3313 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3309 3314 if commit and allowcommit:
3310 3315 continue
3311 3316 if repo.vfs.exists(f):
3312 3317 raise error.Abort(msg, hint=hint)
3313 3318
3314 3319 def clearunfinished(repo):
3315 3320 '''Check for unfinished operations (as above), and clear the ones
3316 3321 that are clearable.
3317 3322 '''
3318 3323 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3319 3324 if not clearable and repo.vfs.exists(f):
3320 3325 raise error.Abort(msg, hint=hint)
3321 3326 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3322 3327 if clearable and repo.vfs.exists(f):
3323 3328 util.unlink(repo.join(f))
3324 3329
3325 3330 class dirstateguard(object):
3326 3331 '''Restore dirstate at unexpected failure.
3327 3332
3328 3333 At the construction, this class does:
3329 3334
3330 3335 - write current ``repo.dirstate`` out, and
3331 3336 - save ``.hg/dirstate`` into the backup file
3332 3337
3333 3338 This restores ``.hg/dirstate`` from backup file, if ``release()``
3334 3339 is invoked before ``close()``.
3335 3340
3336 3341 This just removes the backup file at ``close()`` before ``release()``.
3337 3342 '''
3338 3343
3339 3344 def __init__(self, repo, name):
3340 3345 self._repo = repo
3341 3346 self._suffix = '.backup.%s.%d' % (name, id(self))
3342 3347 repo.dirstate._savebackup(repo.currenttransaction(), self._suffix)
3343 3348 self._active = True
3344 3349 self._closed = False
3345 3350
3346 3351 def __del__(self):
3347 3352 if self._active: # still active
3348 3353 # this may occur, even if this class is used correctly:
3349 3354 # for example, releasing other resources like transaction
3350 3355 # may raise exception before ``dirstateguard.release`` in
3351 3356 # ``release(tr, ....)``.
3352 3357 self._abort()
3353 3358
3354 3359 def close(self):
3355 3360 if not self._active: # already inactivated
3356 3361 msg = (_("can't close already inactivated backup: dirstate%s")
3357 3362 % self._suffix)
3358 3363 raise error.Abort(msg)
3359 3364
3360 3365 self._repo.dirstate._clearbackup(self._repo.currenttransaction(),
3361 3366 self._suffix)
3362 3367 self._active = False
3363 3368 self._closed = True
3364 3369
3365 3370 def _abort(self):
3366 3371 self._repo.dirstate._restorebackup(self._repo.currenttransaction(),
3367 3372 self._suffix)
3368 3373 self._active = False
3369 3374
3370 3375 def release(self):
3371 3376 if not self._closed:
3372 3377 if not self._active: # already inactivated
3373 3378 msg = (_("can't release already inactivated backup:"
3374 3379 " dirstate%s")
3375 3380 % self._suffix)
3376 3381 raise error.Abort(msg)
3377 3382 self._abort()
@@ -1,1124 +1,1127 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import getpass
12 12 import inspect
13 13 import os
14 14 import socket
15 15 import sys
16 16 import tempfile
17 17 import traceback
18 18
19 19 from .i18n import _
20 20 from .node import hex
21 21
22 22 from . import (
23 23 config,
24 24 error,
25 25 formatter,
26 26 progress,
27 27 scmutil,
28 28 util,
29 29 )
30 30
31 31 samplehgrcs = {
32 32 'user':
33 33 """# example user config (see "hg help config" for more info)
34 34 [ui]
35 35 # name and email, e.g.
36 36 # username = Jane Doe <jdoe@example.com>
37 37 username =
38 38
39 39 [extensions]
40 40 # uncomment these lines to enable some popular extensions
41 41 # (see "hg help extensions" for more info)
42 42 #
43 43 # pager =
44 44 # progress =
45 45 # color =""",
46 46
47 47 'cloned':
48 48 """# example repository config (see "hg help config" for more info)
49 49 [paths]
50 50 default = %s
51 51
52 52 # path aliases to other clones of this repo in URLs or filesystem paths
53 53 # (see "hg help config.paths" for more info)
54 54 #
55 55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 57 # my-clone = /home/jdoe/jdoes-clone
58 58
59 59 [ui]
60 60 # name and email (local to this repository, optional), e.g.
61 61 # username = Jane Doe <jdoe@example.com>
62 62 """,
63 63
64 64 'local':
65 65 """# example repository config (see "hg help config" for more info)
66 66 [paths]
67 67 # path aliases to other clones of this repo in URLs or filesystem paths
68 68 # (see "hg help config.paths" for more info)
69 69 #
70 70 # default = http://example.com/hg/example-repo
71 71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 73 # my-clone = /home/jdoe/jdoes-clone
74 74
75 75 [ui]
76 76 # name and email (local to this repository, optional), e.g.
77 77 # username = Jane Doe <jdoe@example.com>
78 78 """,
79 79
80 80 'global':
81 81 """# example system-wide hg config (see "hg help config" for more info)
82 82
83 83 [extensions]
84 84 # uncomment these lines to enable some popular extensions
85 85 # (see "hg help extensions" for more info)
86 86 #
87 87 # blackbox =
88 88 # progress =
89 89 # color =
90 90 # pager =""",
91 91 }
92 92
93 93 class ui(object):
94 94 def __init__(self, src=None):
95 95 # _buffers: used for temporary capture of output
96 96 self._buffers = []
97 97 # _bufferstates:
98 98 # should the temporary capture include stderr and subprocess output
99 99 self._bufferstates = []
100 100 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
101 101 self._reportuntrusted = True
102 102 self._ocfg = config.config() # overlay
103 103 self._tcfg = config.config() # trusted
104 104 self._ucfg = config.config() # untrusted
105 105 self._trustusers = set()
106 106 self._trustgroups = set()
107 107 self.callhooks = True
108 108
109 109 if src:
110 110 self.fout = src.fout
111 111 self.ferr = src.ferr
112 112 self.fin = src.fin
113 113
114 114 self._tcfg = src._tcfg.copy()
115 115 self._ucfg = src._ucfg.copy()
116 116 self._ocfg = src._ocfg.copy()
117 117 self._trustusers = src._trustusers.copy()
118 118 self._trustgroups = src._trustgroups.copy()
119 119 self.environ = src.environ
120 120 self.callhooks = src.callhooks
121 121 self.fixconfig()
122 122 else:
123 123 self.fout = sys.stdout
124 124 self.ferr = sys.stderr
125 125 self.fin = sys.stdin
126 126
127 127 # shared read-only environment
128 128 self.environ = os.environ
129 129 # we always trust global config files
130 130 for f in scmutil.rcpath():
131 131 self.readconfig(f, trust=True)
132 132
133 133 def copy(self):
134 134 return self.__class__(self)
135 135
136 136 def formatter(self, topic, opts):
137 137 return formatter.formatter(self, topic, opts)
138 138
139 139 def _trusted(self, fp, f):
140 140 st = util.fstat(fp)
141 141 if util.isowner(st):
142 142 return True
143 143
144 144 tusers, tgroups = self._trustusers, self._trustgroups
145 145 if '*' in tusers or '*' in tgroups:
146 146 return True
147 147
148 148 user = util.username(st.st_uid)
149 149 group = util.groupname(st.st_gid)
150 150 if user in tusers or group in tgroups or user == util.username():
151 151 return True
152 152
153 153 if self._reportuntrusted:
154 154 self.warn(_('not trusting file %s from untrusted '
155 155 'user %s, group %s\n') % (f, user, group))
156 156 return False
157 157
158 158 def readconfig(self, filename, root=None, trust=False,
159 159 sections=None, remap=None):
160 160 try:
161 161 fp = open(filename)
162 162 except IOError:
163 163 if not sections: # ignore unless we were looking for something
164 164 return
165 165 raise
166 166
167 167 cfg = config.config()
168 168 trusted = sections or trust or self._trusted(fp, filename)
169 169
170 170 try:
171 171 cfg.read(filename, fp, sections=sections, remap=remap)
172 172 fp.close()
173 173 except error.ConfigError as inst:
174 174 if trusted:
175 175 raise
176 176 self.warn(_("ignored: %s\n") % str(inst))
177 177
178 178 if self.plain():
179 179 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
180 180 'logtemplate', 'statuscopies', 'style',
181 181 'traceback', 'verbose'):
182 182 if k in cfg['ui']:
183 183 del cfg['ui'][k]
184 184 for k, v in cfg.items('defaults'):
185 185 del cfg['defaults'][k]
186 186 # Don't remove aliases from the configuration if in the exceptionlist
187 187 if self.plain('alias'):
188 188 for k, v in cfg.items('alias'):
189 189 del cfg['alias'][k]
190 190 if self.plain('revsetalias'):
191 191 for k, v in cfg.items('revsetalias'):
192 192 del cfg['revsetalias'][k]
193 193
194 194 if trusted:
195 195 self._tcfg.update(cfg)
196 196 self._tcfg.update(self._ocfg)
197 197 self._ucfg.update(cfg)
198 198 self._ucfg.update(self._ocfg)
199 199
200 200 if root is None:
201 201 root = os.path.expanduser('~')
202 202 self.fixconfig(root=root)
203 203
204 204 def fixconfig(self, root=None, section=None):
205 205 if section in (None, 'paths'):
206 206 # expand vars and ~
207 207 # translate paths relative to root (or home) into absolute paths
208 208 root = root or os.getcwd()
209 209 for c in self._tcfg, self._ucfg, self._ocfg:
210 210 for n, p in c.items('paths'):
211 211 if not p:
212 212 continue
213 213 if '%%' in p:
214 214 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
215 215 % (n, p, self.configsource('paths', n)))
216 216 p = p.replace('%%', '%')
217 217 p = util.expandpath(p)
218 218 if not util.hasscheme(p) and not os.path.isabs(p):
219 219 p = os.path.normpath(os.path.join(root, p))
220 220 c.set("paths", n, p)
221 221
222 222 if section in (None, 'ui'):
223 223 # update ui options
224 224 self.debugflag = self.configbool('ui', 'debug')
225 225 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
226 226 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
227 227 if self.verbose and self.quiet:
228 228 self.quiet = self.verbose = False
229 229 self._reportuntrusted = self.debugflag or self.configbool("ui",
230 230 "report_untrusted", True)
231 231 self.tracebackflag = self.configbool('ui', 'traceback', False)
232 232
233 233 if section in (None, 'trusted'):
234 234 # update trust information
235 235 self._trustusers.update(self.configlist('trusted', 'users'))
236 236 self._trustgroups.update(self.configlist('trusted', 'groups'))
237 237
238 238 def backupconfig(self, section, item):
239 239 return (self._ocfg.backup(section, item),
240 240 self._tcfg.backup(section, item),
241 241 self._ucfg.backup(section, item),)
242 242 def restoreconfig(self, data):
243 243 self._ocfg.restore(data[0])
244 244 self._tcfg.restore(data[1])
245 245 self._ucfg.restore(data[2])
246 246
247 247 def setconfig(self, section, name, value, source=''):
248 248 for cfg in (self._ocfg, self._tcfg, self._ucfg):
249 249 cfg.set(section, name, value, source)
250 250 self.fixconfig(section=section)
251 251
252 252 def _data(self, untrusted):
253 253 return untrusted and self._ucfg or self._tcfg
254 254
255 255 def configsource(self, section, name, untrusted=False):
256 256 return self._data(untrusted).source(section, name) or 'none'
257 257
258 258 def config(self, section, name, default=None, untrusted=False):
259 259 if isinstance(name, list):
260 260 alternates = name
261 261 else:
262 262 alternates = [name]
263 263
264 264 for n in alternates:
265 265 value = self._data(untrusted).get(section, n, None)
266 266 if value is not None:
267 267 name = n
268 268 break
269 269 else:
270 270 value = default
271 271
272 272 if self.debugflag and not untrusted and self._reportuntrusted:
273 273 for n in alternates:
274 274 uvalue = self._ucfg.get(section, n)
275 275 if uvalue is not None and uvalue != value:
276 276 self.debug("ignoring untrusted configuration option "
277 277 "%s.%s = %s\n" % (section, n, uvalue))
278 278 return value
279 279
280 280 def configpath(self, section, name, default=None, untrusted=False):
281 281 'get a path config item, expanded relative to repo root or config file'
282 282 v = self.config(section, name, default, untrusted)
283 283 if v is None:
284 284 return None
285 285 if not os.path.isabs(v) or "://" not in v:
286 286 src = self.configsource(section, name, untrusted)
287 287 if ':' in src:
288 288 base = os.path.dirname(src.rsplit(':')[0])
289 289 v = os.path.join(base, os.path.expanduser(v))
290 290 return v
291 291
292 292 def configbool(self, section, name, default=False, untrusted=False):
293 293 """parse a configuration element as a boolean
294 294
295 295 >>> u = ui(); s = 'foo'
296 296 >>> u.setconfig(s, 'true', 'yes')
297 297 >>> u.configbool(s, 'true')
298 298 True
299 299 >>> u.setconfig(s, 'false', 'no')
300 300 >>> u.configbool(s, 'false')
301 301 False
302 302 >>> u.configbool(s, 'unknown')
303 303 False
304 304 >>> u.configbool(s, 'unknown', True)
305 305 True
306 306 >>> u.setconfig(s, 'invalid', 'somevalue')
307 307 >>> u.configbool(s, 'invalid')
308 308 Traceback (most recent call last):
309 309 ...
310 310 ConfigError: foo.invalid is not a boolean ('somevalue')
311 311 """
312 312
313 313 v = self.config(section, name, None, untrusted)
314 314 if v is None:
315 315 return default
316 316 if isinstance(v, bool):
317 317 return v
318 318 b = util.parsebool(v)
319 319 if b is None:
320 320 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
321 321 % (section, name, v))
322 322 return b
323 323
324 324 def configint(self, section, name, default=None, untrusted=False):
325 325 """parse a configuration element as an integer
326 326
327 327 >>> u = ui(); s = 'foo'
328 328 >>> u.setconfig(s, 'int1', '42')
329 329 >>> u.configint(s, 'int1')
330 330 42
331 331 >>> u.setconfig(s, 'int2', '-42')
332 332 >>> u.configint(s, 'int2')
333 333 -42
334 334 >>> u.configint(s, 'unknown', 7)
335 335 7
336 336 >>> u.setconfig(s, 'invalid', 'somevalue')
337 337 >>> u.configint(s, 'invalid')
338 338 Traceback (most recent call last):
339 339 ...
340 340 ConfigError: foo.invalid is not an integer ('somevalue')
341 341 """
342 342
343 343 v = self.config(section, name, None, untrusted)
344 344 if v is None:
345 345 return default
346 346 try:
347 347 return int(v)
348 348 except ValueError:
349 349 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
350 350 % (section, name, v))
351 351
352 352 def configbytes(self, section, name, default=0, untrusted=False):
353 353 """parse a configuration element as a quantity in bytes
354 354
355 355 Units can be specified as b (bytes), k or kb (kilobytes), m or
356 356 mb (megabytes), g or gb (gigabytes).
357 357
358 358 >>> u = ui(); s = 'foo'
359 359 >>> u.setconfig(s, 'val1', '42')
360 360 >>> u.configbytes(s, 'val1')
361 361 42
362 362 >>> u.setconfig(s, 'val2', '42.5 kb')
363 363 >>> u.configbytes(s, 'val2')
364 364 43520
365 365 >>> u.configbytes(s, 'unknown', '7 MB')
366 366 7340032
367 367 >>> u.setconfig(s, 'invalid', 'somevalue')
368 368 >>> u.configbytes(s, 'invalid')
369 369 Traceback (most recent call last):
370 370 ...
371 371 ConfigError: foo.invalid is not a byte quantity ('somevalue')
372 372 """
373 373
374 374 value = self.config(section, name)
375 375 if value is None:
376 376 if not isinstance(default, str):
377 377 return default
378 378 value = default
379 379 try:
380 380 return util.sizetoint(value)
381 381 except error.ParseError:
382 382 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
383 383 % (section, name, value))
384 384
385 385 def configlist(self, section, name, default=None, untrusted=False):
386 386 """parse a configuration element as a list of comma/space separated
387 387 strings
388 388
389 389 >>> u = ui(); s = 'foo'
390 390 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
391 391 >>> u.configlist(s, 'list1')
392 392 ['this', 'is', 'a small', 'test']
393 393 """
394 394
395 395 def _parse_plain(parts, s, offset):
396 396 whitespace = False
397 397 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
398 398 whitespace = True
399 399 offset += 1
400 400 if offset >= len(s):
401 401 return None, parts, offset
402 402 if whitespace:
403 403 parts.append('')
404 404 if s[offset] == '"' and not parts[-1]:
405 405 return _parse_quote, parts, offset + 1
406 406 elif s[offset] == '"' and parts[-1][-1] == '\\':
407 407 parts[-1] = parts[-1][:-1] + s[offset]
408 408 return _parse_plain, parts, offset + 1
409 409 parts[-1] += s[offset]
410 410 return _parse_plain, parts, offset + 1
411 411
412 412 def _parse_quote(parts, s, offset):
413 413 if offset < len(s) and s[offset] == '"': # ""
414 414 parts.append('')
415 415 offset += 1
416 416 while offset < len(s) and (s[offset].isspace() or
417 417 s[offset] == ','):
418 418 offset += 1
419 419 return _parse_plain, parts, offset
420 420
421 421 while offset < len(s) and s[offset] != '"':
422 422 if (s[offset] == '\\' and offset + 1 < len(s)
423 423 and s[offset + 1] == '"'):
424 424 offset += 1
425 425 parts[-1] += '"'
426 426 else:
427 427 parts[-1] += s[offset]
428 428 offset += 1
429 429
430 430 if offset >= len(s):
431 431 real_parts = _configlist(parts[-1])
432 432 if not real_parts:
433 433 parts[-1] = '"'
434 434 else:
435 435 real_parts[0] = '"' + real_parts[0]
436 436 parts = parts[:-1]
437 437 parts.extend(real_parts)
438 438 return None, parts, offset
439 439
440 440 offset += 1
441 441 while offset < len(s) and s[offset] in [' ', ',']:
442 442 offset += 1
443 443
444 444 if offset < len(s):
445 445 if offset + 1 == len(s) and s[offset] == '"':
446 446 parts[-1] += '"'
447 447 offset += 1
448 448 else:
449 449 parts.append('')
450 450 else:
451 451 return None, parts, offset
452 452
453 453 return _parse_plain, parts, offset
454 454
455 455 def _configlist(s):
456 456 s = s.rstrip(' ,')
457 457 if not s:
458 458 return []
459 459 parser, parts, offset = _parse_plain, [''], 0
460 460 while parser:
461 461 parser, parts, offset = parser(parts, s, offset)
462 462 return parts
463 463
464 464 result = self.config(section, name, untrusted=untrusted)
465 465 if result is None:
466 466 result = default or []
467 467 if isinstance(result, basestring):
468 468 result = _configlist(result.lstrip(' ,\n'))
469 469 if result is None:
470 470 result = default or []
471 471 return result
472 472
473 473 def has_section(self, section, untrusted=False):
474 474 '''tell whether section exists in config.'''
475 475 return section in self._data(untrusted)
476 476
477 477 def configitems(self, section, untrusted=False):
478 478 items = self._data(untrusted).items(section)
479 479 if self.debugflag and not untrusted and self._reportuntrusted:
480 480 for k, v in self._ucfg.items(section):
481 481 if self._tcfg.get(section, k) != v:
482 482 self.debug("ignoring untrusted configuration option "
483 483 "%s.%s = %s\n" % (section, k, v))
484 484 return items
485 485
486 486 def walkconfig(self, untrusted=False):
487 487 cfg = self._data(untrusted)
488 488 for section in cfg.sections():
489 489 for name, value in self.configitems(section, untrusted):
490 490 yield section, name, value
491 491
492 492 def plain(self, feature=None):
493 493 '''is plain mode active?
494 494
495 495 Plain mode means that all configuration variables which affect
496 496 the behavior and output of Mercurial should be
497 497 ignored. Additionally, the output should be stable,
498 498 reproducible and suitable for use in scripts or applications.
499 499
500 500 The only way to trigger plain mode is by setting either the
501 501 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
502 502
503 503 The return value can either be
504 504 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
505 505 - True otherwise
506 506 '''
507 507 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
508 508 return False
509 509 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
510 510 if feature and exceptions:
511 511 return feature not in exceptions
512 512 return True
513 513
514 514 def username(self):
515 515 """Return default username to be used in commits.
516 516
517 517 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
518 518 and stop searching if one of these is set.
519 519 If not found and ui.askusername is True, ask the user, else use
520 520 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
521 521 """
522 522 user = os.environ.get("HGUSER")
523 523 if user is None:
524 524 user = self.config("ui", ["username", "user"])
525 525 if user is not None:
526 526 user = os.path.expandvars(user)
527 527 if user is None:
528 528 user = os.environ.get("EMAIL")
529 529 if user is None and self.configbool("ui", "askusername"):
530 530 user = self.prompt(_("enter a commit username:"), default=None)
531 531 if user is None and not self.interactive():
532 532 try:
533 533 user = '%s@%s' % (util.getuser(), socket.getfqdn())
534 534 self.warn(_("no username found, using '%s' instead\n") % user)
535 535 except KeyError:
536 536 pass
537 537 if not user:
538 538 raise error.Abort(_('no username supplied'),
539 539 hint=_('use "hg config --edit" '
540 540 'to set your username'))
541 541 if "\n" in user:
542 542 raise error.Abort(_("username %s contains a newline\n")
543 543 % repr(user))
544 544 return user
545 545
546 546 def shortuser(self, user):
547 547 """Return a short representation of a user name or email address."""
548 548 if not self.verbose:
549 549 user = util.shortuser(user)
550 550 return user
551 551
552 552 def expandpath(self, loc, default=None):
553 553 """Return repository location relative to cwd or from [paths]"""
554 554 try:
555 555 p = self.paths.getpath(loc)
556 556 if p:
557 557 return p.rawloc
558 558 except error.RepoError:
559 559 pass
560 560
561 561 if default:
562 562 try:
563 563 p = self.paths.getpath(default)
564 564 if p:
565 565 return p.rawloc
566 566 except error.RepoError:
567 567 pass
568 568
569 569 return loc
570 570
571 571 @util.propertycache
572 572 def paths(self):
573 573 return paths(self)
574 574
575 575 def pushbuffer(self, error=False, subproc=False):
576 576 """install a buffer to capture standard output of the ui object
577 577
578 578 If error is True, the error output will be captured too.
579 579
580 580 If subproc is True, output from subprocesses (typically hooks) will be
581 581 captured too."""
582 582 self._buffers.append([])
583 583 self._bufferstates.append((error, subproc))
584 584
585 585 def popbuffer(self, labeled=False):
586 586 '''pop the last buffer and return the buffered output
587 587
588 588 If labeled is True, any labels associated with buffered
589 589 output will be handled. By default, this has no effect
590 590 on the output returned, but extensions and GUI tools may
591 591 handle this argument and returned styled output. If output
592 592 is being buffered so it can be captured and parsed or
593 593 processed, labeled should not be set to True.
594 594 '''
595 595 self._bufferstates.pop()
596 596 return "".join(self._buffers.pop())
597 597
598 598 def write(self, *args, **opts):
599 599 '''write args to output
600 600
601 601 By default, this method simply writes to the buffer or stdout,
602 602 but extensions or GUI tools may override this method,
603 603 write_err(), popbuffer(), and label() to style output from
604 604 various parts of hg.
605 605
606 606 An optional keyword argument, "label", can be passed in.
607 607 This should be a string containing label names separated by
608 608 space. Label names take the form of "topic.type". For example,
609 609 ui.debug() issues a label of "ui.debug".
610 610
611 611 When labeling output for a specific command, a label of
612 612 "cmdname.type" is recommended. For example, status issues
613 613 a label of "status.modified" for modified files.
614 614 '''
615 615 self._progclear()
616 616 if self._buffers:
617 617 self._buffers[-1].extend([str(a) for a in args])
618 618 else:
619 619 for a in args:
620 620 self.fout.write(str(a))
621 621
622 622 def write_err(self, *args, **opts):
623 623 self._progclear()
624 624 try:
625 625 if self._bufferstates and self._bufferstates[-1][0]:
626 626 return self.write(*args, **opts)
627 627 if not getattr(self.fout, 'closed', False):
628 628 self.fout.flush()
629 629 for a in args:
630 630 self.ferr.write(str(a))
631 631 # stderr may be buffered under win32 when redirected to files,
632 632 # including stdout.
633 633 if not getattr(self.ferr, 'closed', False):
634 634 self.ferr.flush()
635 635 except IOError as inst:
636 636 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
637 637 raise
638 638
639 639 def flush(self):
640 640 try: self.fout.flush()
641 641 except (IOError, ValueError): pass
642 642 try: self.ferr.flush()
643 643 except (IOError, ValueError): pass
644 644
645 645 def _isatty(self, fh):
646 646 if self.configbool('ui', 'nontty', False):
647 647 return False
648 648 return util.isatty(fh)
649 649
650 650 def interactive(self):
651 651 '''is interactive input allowed?
652 652
653 653 An interactive session is a session where input can be reasonably read
654 654 from `sys.stdin'. If this function returns false, any attempt to read
655 655 from stdin should fail with an error, unless a sensible default has been
656 656 specified.
657 657
658 658 Interactiveness is triggered by the value of the `ui.interactive'
659 659 configuration variable or - if it is unset - when `sys.stdin' points
660 660 to a terminal device.
661 661
662 662 This function refers to input only; for output, see `ui.formatted()'.
663 663 '''
664 664 i = self.configbool("ui", "interactive", None)
665 665 if i is None:
666 666 # some environments replace stdin without implementing isatty
667 667 # usually those are non-interactive
668 668 return self._isatty(self.fin)
669 669
670 670 return i
671 671
672 672 def termwidth(self):
673 673 '''how wide is the terminal in columns?
674 674 '''
675 675 if 'COLUMNS' in os.environ:
676 676 try:
677 677 return int(os.environ['COLUMNS'])
678 678 except ValueError:
679 679 pass
680 680 return util.termwidth()
681 681
682 682 def formatted(self):
683 683 '''should formatted output be used?
684 684
685 685 It is often desirable to format the output to suite the output medium.
686 686 Examples of this are truncating long lines or colorizing messages.
687 687 However, this is not often not desirable when piping output into other
688 688 utilities, e.g. `grep'.
689 689
690 690 Formatted output is triggered by the value of the `ui.formatted'
691 691 configuration variable or - if it is unset - when `sys.stdout' points
692 692 to a terminal device. Please note that `ui.formatted' should be
693 693 considered an implementation detail; it is not intended for use outside
694 694 Mercurial or its extensions.
695 695
696 696 This function refers to output only; for input, see `ui.interactive()'.
697 697 This function always returns false when in plain mode, see `ui.plain()'.
698 698 '''
699 699 if self.plain():
700 700 return False
701 701
702 702 i = self.configbool("ui", "formatted", None)
703 703 if i is None:
704 704 # some environments replace stdout without implementing isatty
705 705 # usually those are non-interactive
706 706 return self._isatty(self.fout)
707 707
708 708 return i
709 709
710 710 def _readline(self, prompt=''):
711 711 if self._isatty(self.fin):
712 712 try:
713 713 # magically add command line editing support, where
714 714 # available
715 715 import readline
716 716 # force demandimport to really load the module
717 717 readline.read_history_file
718 718 # windows sometimes raises something other than ImportError
719 719 except Exception:
720 720 pass
721 721
722 722 # call write() so output goes through subclassed implementation
723 723 # e.g. color extension on Windows
724 724 self.write(prompt)
725 725
726 726 # instead of trying to emulate raw_input, swap (self.fin,
727 727 # self.fout) with (sys.stdin, sys.stdout)
728 728 oldin = sys.stdin
729 729 oldout = sys.stdout
730 730 sys.stdin = self.fin
731 731 sys.stdout = self.fout
732 732 # prompt ' ' must exist; otherwise readline may delete entire line
733 733 # - http://bugs.python.org/issue12833
734 734 line = raw_input(' ')
735 735 sys.stdin = oldin
736 736 sys.stdout = oldout
737 737
738 738 # When stdin is in binary mode on Windows, it can cause
739 739 # raw_input() to emit an extra trailing carriage return
740 740 if os.linesep == '\r\n' and line and line[-1] == '\r':
741 741 line = line[:-1]
742 742 return line
743 743
744 744 def prompt(self, msg, default="y"):
745 745 """Prompt user with msg, read response.
746 746 If ui is not interactive, the default is returned.
747 747 """
748 748 if not self.interactive():
749 749 self.write(msg, ' ', default, "\n")
750 750 return default
751 751 try:
752 752 r = self._readline(self.label(msg, 'ui.prompt'))
753 753 if not r:
754 754 r = default
755 755 if self.configbool('ui', 'promptecho'):
756 756 self.write(r, "\n")
757 757 return r
758 758 except EOFError:
759 759 raise error.Abort(_('response expected'))
760 760
761 761 @staticmethod
762 762 def extractchoices(prompt):
763 763 """Extract prompt message and list of choices from specified prompt.
764 764
765 765 This returns tuple "(message, choices)", and "choices" is the
766 766 list of tuple "(response character, text without &)".
767 767 """
768 768 parts = prompt.split('$$')
769 769 msg = parts[0].rstrip(' ')
770 770 choices = [p.strip(' ') for p in parts[1:]]
771 771 return (msg,
772 772 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
773 773 for s in choices])
774 774
775 775 def promptchoice(self, prompt, default=0):
776 776 """Prompt user with a message, read response, and ensure it matches
777 777 one of the provided choices. The prompt is formatted as follows:
778 778
779 779 "would you like fries with that (Yn)? $$ &Yes $$ &No"
780 780
781 781 The index of the choice is returned. Responses are case
782 782 insensitive. If ui is not interactive, the default is
783 783 returned.
784 784 """
785 785
786 786 msg, choices = self.extractchoices(prompt)
787 787 resps = [r for r, t in choices]
788 788 while True:
789 789 r = self.prompt(msg, resps[default])
790 790 if r.lower() in resps:
791 791 return resps.index(r.lower())
792 792 self.write(_("unrecognized response\n"))
793 793
794 794 def getpass(self, prompt=None, default=None):
795 795 if not self.interactive():
796 796 return default
797 797 try:
798 798 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
799 799 # disable getpass() only if explicitly specified. it's still valid
800 800 # to interact with tty even if fin is not a tty.
801 801 if self.configbool('ui', 'nontty'):
802 802 return self.fin.readline().rstrip('\n')
803 803 else:
804 804 return getpass.getpass('')
805 805 except EOFError:
806 806 raise error.Abort(_('response expected'))
807 807 def status(self, *msg, **opts):
808 808 '''write status message to output (if ui.quiet is False)
809 809
810 810 This adds an output label of "ui.status".
811 811 '''
812 812 if not self.quiet:
813 813 opts['label'] = opts.get('label', '') + ' ui.status'
814 814 self.write(*msg, **opts)
815 815 def warn(self, *msg, **opts):
816 816 '''write warning message to output (stderr)
817 817
818 818 This adds an output label of "ui.warning".
819 819 '''
820 820 opts['label'] = opts.get('label', '') + ' ui.warning'
821 821 self.write_err(*msg, **opts)
822 822 def note(self, *msg, **opts):
823 823 '''write note to output (if ui.verbose is True)
824 824
825 825 This adds an output label of "ui.note".
826 826 '''
827 827 if self.verbose:
828 828 opts['label'] = opts.get('label', '') + ' ui.note'
829 829 self.write(*msg, **opts)
830 830 def debug(self, *msg, **opts):
831 831 '''write debug message to output (if ui.debugflag is True)
832 832
833 833 This adds an output label of "ui.debug".
834 834 '''
835 835 if self.debugflag:
836 836 opts['label'] = opts.get('label', '') + ' ui.debug'
837 837 self.write(*msg, **opts)
838 def edit(self, text, user, extra=None, editform=None):
838
839 def edit(self, text, user, extra=None, editform=None, pending=None):
839 840 if extra is None:
840 841 extra = {}
841 842 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
842 843 text=True)
843 844 try:
844 845 f = os.fdopen(fd, "w")
845 846 f.write(text)
846 847 f.close()
847 848
848 849 environ = {'HGUSER': user}
849 850 if 'transplant_source' in extra:
850 851 environ.update({'HGREVISION': hex(extra['transplant_source'])})
851 852 for label in ('intermediate-source', 'source', 'rebase_source'):
852 853 if label in extra:
853 854 environ.update({'HGREVISION': extra[label]})
854 855 break
855 856 if editform:
856 857 environ.update({'HGEDITFORM': editform})
858 if pending:
859 environ.update({'HG_PENDING': pending})
857 860
858 861 editor = self.geteditor()
859 862
860 863 self.system("%s \"%s\"" % (editor, name),
861 864 environ=environ,
862 865 onerr=error.Abort, errprefix=_("edit failed"))
863 866
864 867 f = open(name)
865 868 t = f.read()
866 869 f.close()
867 870 finally:
868 871 os.unlink(name)
869 872
870 873 return t
871 874
872 875 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
873 876 '''execute shell command with appropriate output stream. command
874 877 output will be redirected if fout is not stdout.
875 878 '''
876 879 out = self.fout
877 880 if any(s[1] for s in self._bufferstates):
878 881 out = self
879 882 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
880 883 errprefix=errprefix, out=out)
881 884
882 885 def traceback(self, exc=None, force=False):
883 886 '''print exception traceback if traceback printing enabled or forced.
884 887 only to call in exception handler. returns true if traceback
885 888 printed.'''
886 889 if self.tracebackflag or force:
887 890 if exc is None:
888 891 exc = sys.exc_info()
889 892 cause = getattr(exc[1], 'cause', None)
890 893
891 894 if cause is not None:
892 895 causetb = traceback.format_tb(cause[2])
893 896 exctb = traceback.format_tb(exc[2])
894 897 exconly = traceback.format_exception_only(cause[0], cause[1])
895 898
896 899 # exclude frame where 'exc' was chained and rethrown from exctb
897 900 self.write_err('Traceback (most recent call last):\n',
898 901 ''.join(exctb[:-1]),
899 902 ''.join(causetb),
900 903 ''.join(exconly))
901 904 else:
902 905 output = traceback.format_exception(exc[0], exc[1], exc[2])
903 906 self.write_err(''.join(output))
904 907 return self.tracebackflag or force
905 908
906 909 def geteditor(self):
907 910 '''return editor to use'''
908 911 if sys.platform == 'plan9':
909 912 # vi is the MIPS instruction simulator on Plan 9. We
910 913 # instead default to E to plumb commit messages to
911 914 # avoid confusion.
912 915 editor = 'E'
913 916 else:
914 917 editor = 'vi'
915 918 return (os.environ.get("HGEDITOR") or
916 919 self.config("ui", "editor") or
917 920 os.environ.get("VISUAL") or
918 921 os.environ.get("EDITOR", editor))
919 922
920 923 @util.propertycache
921 924 def _progbar(self):
922 925 """setup the progbar singleton to the ui object"""
923 926 if (self.quiet or self.debugflag
924 927 or self.configbool('progress', 'disable', False)
925 928 or not progress.shouldprint(self)):
926 929 return None
927 930 return getprogbar(self)
928 931
929 932 def _progclear(self):
930 933 """clear progress bar output if any. use it before any output"""
931 934 if '_progbar' not in vars(self): # nothing loadef yet
932 935 return
933 936 if self._progbar is not None and self._progbar.printed:
934 937 self._progbar.clear()
935 938
936 939 def progress(self, topic, pos, item="", unit="", total=None):
937 940 '''show a progress message
938 941
939 942 With stock hg, this is simply a debug message that is hidden
940 943 by default, but with extensions or GUI tools it may be
941 944 visible. 'topic' is the current operation, 'item' is a
942 945 non-numeric marker of the current position (i.e. the currently
943 946 in-process file), 'pos' is the current numeric position (i.e.
944 947 revision, bytes, etc.), unit is a corresponding unit label,
945 948 and total is the highest expected pos.
946 949
947 950 Multiple nested topics may be active at a time.
948 951
949 952 All topics should be marked closed by setting pos to None at
950 953 termination.
951 954 '''
952 955 if self._progbar is not None:
953 956 self._progbar.progress(topic, pos, item=item, unit=unit,
954 957 total=total)
955 958 if pos is None or not self.configbool('progress', 'debug'):
956 959 return
957 960
958 961 if unit:
959 962 unit = ' ' + unit
960 963 if item:
961 964 item = ' ' + item
962 965
963 966 if total:
964 967 pct = 100.0 * pos / total
965 968 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
966 969 % (topic, item, pos, total, unit, pct))
967 970 else:
968 971 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
969 972
970 973 def log(self, service, *msg, **opts):
971 974 '''hook for logging facility extensions
972 975
973 976 service should be a readily-identifiable subsystem, which will
974 977 allow filtering.
975 978
976 979 *msg should be a newline-terminated format string to log, and
977 980 then any values to %-format into that format string.
978 981
979 982 **opts currently has no defined meanings.
980 983 '''
981 984
982 985 def label(self, msg, label):
983 986 '''style msg based on supplied label
984 987
985 988 Like ui.write(), this just returns msg unchanged, but extensions
986 989 and GUI tools can override it to allow styling output without
987 990 writing it.
988 991
989 992 ui.write(s, 'label') is equivalent to
990 993 ui.write(ui.label(s, 'label')).
991 994 '''
992 995 return msg
993 996
994 997 def develwarn(self, msg):
995 998 """issue a developer warning message"""
996 999 msg = 'devel-warn: ' + msg
997 1000 if self.tracebackflag:
998 1001 util.debugstacktrace(msg, 2, self.ferr, self.fout)
999 1002 else:
1000 1003 curframe = inspect.currentframe()
1001 1004 calframe = inspect.getouterframes(curframe, 2)
1002 1005 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1003 1006
1004 1007 class paths(dict):
1005 1008 """Represents a collection of paths and their configs.
1006 1009
1007 1010 Data is initially derived from ui instances and the config files they have
1008 1011 loaded.
1009 1012 """
1010 1013 def __init__(self, ui):
1011 1014 dict.__init__(self)
1012 1015
1013 1016 for name, loc in ui.configitems('paths'):
1014 1017 # No location is the same as not existing.
1015 1018 if not loc:
1016 1019 continue
1017 1020
1018 1021 # TODO ignore default-push once all consumers stop referencing it
1019 1022 # since it is handled specifically below.
1020 1023
1021 1024 self[name] = path(name, rawloc=loc)
1022 1025
1023 1026 # Handle default-push, which is a one-off that defines the push URL for
1024 1027 # the "default" path.
1025 1028 defaultpush = ui.config('paths', 'default-push')
1026 1029 if defaultpush and 'default' in self:
1027 1030 self['default']._pushloc = defaultpush
1028 1031
1029 1032 def getpath(self, name, default=None):
1030 1033 """Return a ``path`` from a string, falling back to a default.
1031 1034
1032 1035 ``name`` can be a named path or locations. Locations are filesystem
1033 1036 paths or URIs.
1034 1037
1035 1038 Returns None if ``name`` is not a registered path, a URI, or a local
1036 1039 path to a repo.
1037 1040 """
1038 1041 # Only fall back to default if no path was requested.
1039 1042 if name is None:
1040 1043 if default:
1041 1044 try:
1042 1045 return self[default]
1043 1046 except KeyError:
1044 1047 return None
1045 1048 else:
1046 1049 return None
1047 1050
1048 1051 # Most likely empty string.
1049 1052 # This may need to raise in the future.
1050 1053 if not name:
1051 1054 return None
1052 1055
1053 1056 try:
1054 1057 return self[name]
1055 1058 except KeyError:
1056 1059 # Try to resolve as a local path or URI.
1057 1060 try:
1058 1061 return path(None, rawloc=name)
1059 1062 except ValueError:
1060 1063 raise error.RepoError(_('repository %s does not exist') %
1061 1064 name)
1062 1065
1063 1066 assert False
1064 1067
1065 1068 class path(object):
1066 1069 """Represents an individual path and its configuration."""
1067 1070
1068 1071 def __init__(self, name, rawloc=None, pushloc=None):
1069 1072 """Construct a path from its config options.
1070 1073
1071 1074 ``name`` is the symbolic name of the path.
1072 1075 ``rawloc`` is the raw location, as defined in the config.
1073 1076 ``pushloc`` is the raw locations pushes should be made to.
1074 1077
1075 1078 If ``name`` is not defined, we require that the location be a) a local
1076 1079 filesystem path with a .hg directory or b) a URL. If not,
1077 1080 ``ValueError`` is raised.
1078 1081 """
1079 1082 if not rawloc:
1080 1083 raise ValueError('rawloc must be defined')
1081 1084
1082 1085 # Locations may define branches via syntax <base>#<branch>.
1083 1086 u = util.url(rawloc)
1084 1087 branch = None
1085 1088 if u.fragment:
1086 1089 branch = u.fragment
1087 1090 u.fragment = None
1088 1091
1089 1092 self.url = u
1090 1093 self.branch = branch
1091 1094
1092 1095 self.name = name
1093 1096 self.rawloc = rawloc
1094 1097 self.loc = str(u)
1095 1098 self._pushloc = pushloc
1096 1099
1097 1100 # When given a raw location but not a symbolic name, validate the
1098 1101 # location is valid.
1099 1102 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1100 1103 raise ValueError('location is not a URL or path to a local '
1101 1104 'repo: %s' % rawloc)
1102 1105
1103 1106 def _isvalidlocalpath(self, path):
1104 1107 """Returns True if the given path is a potentially valid repository.
1105 1108 This is its own function so that extensions can change the definition of
1106 1109 'valid' in this case (like when pulling from a git repo into a hg
1107 1110 one)."""
1108 1111 return os.path.isdir(os.path.join(path, '.hg'))
1109 1112
1110 1113 @property
1111 1114 def pushloc(self):
1112 1115 return self._pushloc or self.loc
1113 1116
1114 1117 # we instantiate one globally shared progress bar to avoid
1115 1118 # competing progress bars when multiple UI objects get created
1116 1119 _progresssingleton = None
1117 1120
1118 1121 def getprogbar(ui):
1119 1122 global _progresssingleton
1120 1123 if _progresssingleton is None:
1121 1124 # passing 'ui' object to the singleton is fishy,
1122 1125 # this is how the extension used to work but feel free to rework it.
1123 1126 _progresssingleton = progress.progbar(ui)
1124 1127 return _progresssingleton
@@ -1,1151 +1,1151 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 $ hg ci --amend
22 $ hg ci --amend -m 'base1'
23 23 nothing changed
24 24 [1]
25 25
26 26 $ cat >> $HGRCPATH <<EOF
27 27 > [hooks]
28 28 > pretxncommit.foo = sh -c "echo \\"pretxncommit \$HG_NODE\\"; hg id -r \$HG_NODE"
29 29 > EOF
30 30
31 31 Amending changeset with changes in working dir:
32 32 (and check that --message does not trigger an editor)
33 33
34 34 $ echo a >> a
35 35 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
36 36 pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
37 37 43f1ba15f28a tip
38 38 saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend-backup.hg (glob)
39 39 $ echo 'pretxncommit.foo = ' >> $HGRCPATH
40 40 $ hg diff -c .
41 41 diff -r ad120869acf0 -r 43f1ba15f28a a
42 42 --- a/a Thu Jan 01 00:00:00 1970 +0000
43 43 +++ b/a Thu Jan 01 00:00:00 1970 +0000
44 44 @@ -1,1 +1,3 @@
45 45 a
46 46 +a
47 47 +a
48 48 $ hg log
49 49 changeset: 1:43f1ba15f28a
50 50 tag: tip
51 51 user: test
52 52 date: Thu Jan 01 00:00:00 1970 +0000
53 53 summary: amend base1
54 54
55 55 changeset: 0:ad120869acf0
56 56 user: test
57 57 date: Thu Jan 01 00:00:00 1970 +0000
58 58 summary: base
59 59
60 60
61 61 Check proper abort for empty message
62 62
63 63 $ cat > editor.sh << '__EOF__'
64 64 > #!/bin/sh
65 65 > echo "" > "$1"
66 66 > __EOF__
67 67 $ echo b > b
68 68 $ hg add b
69 69 $ hg summary
70 70 parent: 1:43f1ba15f28a tip
71 71 amend base1
72 72 branch: default
73 73 commit: 1 added, 1 unknown
74 74 update: (current)
75 75 phases: 2 draft
76 76 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
77 77 transaction abort!
78 78 rollback completed
79 79 abort: empty commit message
80 80 [255]
81 81 $ hg summary
82 82 parent: 1:43f1ba15f28a tip
83 83 amend base1
84 84 branch: default
85 85 commit: 1 added, 1 unknown
86 86 update: (current)
87 87 phases: 2 draft
88 88
89 89 Add new file:
90 90 $ hg ci --amend -m 'amend base1 new file'
91 91 saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend-backup.hg (glob)
92 92
93 93 Remove file that was added in amended commit:
94 94 (and test logfile option)
95 95 (and test that logfile option do not trigger an editor)
96 96
97 97 $ hg rm b
98 98 $ echo 'amend base1 remove new file' > ../logfile
99 99 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
100 100 saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend-backup.hg (glob)
101 101
102 102 $ hg cat b
103 103 b: no such file in rev 74609c7f506e
104 104 [1]
105 105
106 106 No changes, just a different message:
107 107
108 108 $ hg ci -v --amend -m 'no changes, new message'
109 109 amending changeset 74609c7f506e
110 110 copying changeset 74609c7f506e to ad120869acf0
111 111 committing files:
112 112 a
113 113 committing manifest
114 114 committing changelog
115 115 stripping amended changeset 74609c7f506e
116 116 1 changesets found
117 117 uncompressed size of bundle content:
118 118 250 (changelog)
119 119 143 (manifests)
120 120 109 a
121 121 saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend-backup.hg (glob)
122 122 1 changesets found
123 123 uncompressed size of bundle content:
124 124 246 (changelog)
125 125 143 (manifests)
126 126 109 a
127 127 adding branch
128 128 adding changesets
129 129 adding manifests
130 130 adding file changes
131 131 added 1 changesets with 1 changes to 1 files
132 132 committed changeset 1:1cd866679df8
133 133 $ hg diff -c .
134 134 diff -r ad120869acf0 -r 1cd866679df8 a
135 135 --- a/a Thu Jan 01 00:00:00 1970 +0000
136 136 +++ b/a Thu Jan 01 00:00:00 1970 +0000
137 137 @@ -1,1 +1,3 @@
138 138 a
139 139 +a
140 140 +a
141 141 $ hg log
142 142 changeset: 1:1cd866679df8
143 143 tag: tip
144 144 user: test
145 145 date: Thu Jan 01 00:00:00 1970 +0000
146 146 summary: no changes, new message
147 147
148 148 changeset: 0:ad120869acf0
149 149 user: test
150 150 date: Thu Jan 01 00:00:00 1970 +0000
151 151 summary: base
152 152
153 153
154 154 Disable default date on commit so when -d isn't given, the old date is preserved:
155 155
156 156 $ echo '[defaults]' >> $HGRCPATH
157 157 $ echo 'commit=' >> $HGRCPATH
158 158
159 159 Test -u/-d:
160 160
161 161 $ cat > .hg/checkeditform.sh <<EOF
162 162 > env | grep HGEDITFORM
163 163 > true
164 164 > EOF
165 165 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
166 166 HGEDITFORM=commit.amend.normal
167 167 saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend-backup.hg (glob)
168 168 $ echo a >> a
169 169 $ hg ci --amend -u foo -d '1 0'
170 170 saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend-backup.hg (glob)
171 171 $ hg log -r .
172 172 changeset: 1:5f357c7560ab
173 173 tag: tip
174 174 user: foo
175 175 date: Thu Jan 01 00:00:01 1970 +0000
176 176 summary: no changes, new message
177 177
178 178
179 179 Open editor with old commit message if a message isn't given otherwise:
180 180
181 181 $ cat > editor.sh << '__EOF__'
182 182 > #!/bin/sh
183 183 > cat $1
184 184 > echo "another precious commit message" > "$1"
185 185 > __EOF__
186 186
187 187 at first, test saving last-message.txt
188 188
189 189 $ cat > .hg/hgrc << '__EOF__'
190 190 > [hooks]
191 191 > pretxncommit.test-saving-last-message = false
192 192 > __EOF__
193 193
194 194 $ rm -f .hg/last-message.txt
195 195 $ hg commit --amend -v -m "message given from command line"
196 196 amending changeset 5f357c7560ab
197 197 copying changeset 5f357c7560ab to ad120869acf0
198 198 committing files:
199 199 a
200 200 committing manifest
201 201 committing changelog
202 202 running hook pretxncommit.test-saving-last-message: false
203 203 transaction abort!
204 204 rollback completed
205 205 abort: pretxncommit.test-saving-last-message hook exited with status 1
206 206 [255]
207 207 $ cat .hg/last-message.txt
208 208 message given from command line (no-eol)
209 209
210 210 $ rm -f .hg/last-message.txt
211 211 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
212 212 amending changeset 5f357c7560ab
213 213 copying changeset 5f357c7560ab to ad120869acf0
214 214 no changes, new message
215 215
216 216
217 217 HG: Enter commit message. Lines beginning with 'HG:' are removed.
218 218 HG: Leave message empty to abort commit.
219 219 HG: --
220 220 HG: user: foo
221 221 HG: branch 'default'
222 222 HG: changed a
223 223 committing files:
224 224 a
225 225 committing manifest
226 226 committing changelog
227 227 running hook pretxncommit.test-saving-last-message: false
228 228 transaction abort!
229 229 rollback completed
230 230 abort: pretxncommit.test-saving-last-message hook exited with status 1
231 231 [255]
232 232
233 233 $ cat .hg/last-message.txt
234 234 another precious commit message
235 235
236 236 $ cat > .hg/hgrc << '__EOF__'
237 237 > [hooks]
238 238 > pretxncommit.test-saving-last-message =
239 239 > __EOF__
240 240
241 241 then, test editing custom commit message
242 242
243 243 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
244 244 amending changeset 5f357c7560ab
245 245 copying changeset 5f357c7560ab to ad120869acf0
246 246 no changes, new message
247 247
248 248
249 249 HG: Enter commit message. Lines beginning with 'HG:' are removed.
250 250 HG: Leave message empty to abort commit.
251 251 HG: --
252 252 HG: user: foo
253 253 HG: branch 'default'
254 254 HG: changed a
255 255 committing files:
256 256 a
257 257 committing manifest
258 258 committing changelog
259 259 stripping amended changeset 5f357c7560ab
260 260 1 changesets found
261 261 uncompressed size of bundle content:
262 262 238 (changelog)
263 263 143 (manifests)
264 264 111 a
265 265 saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend-backup.hg (glob)
266 266 1 changesets found
267 267 uncompressed size of bundle content:
268 268 246 (changelog)
269 269 143 (manifests)
270 270 111 a
271 271 adding branch
272 272 adding changesets
273 273 adding manifests
274 274 adding file changes
275 275 added 1 changesets with 1 changes to 1 files
276 276 committed changeset 1:7ab3bf440b54
277 277
278 278 Same, but with changes in working dir (different code path):
279 279
280 280 $ echo a >> a
281 281 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
282 282 amending changeset 7ab3bf440b54
283 283 committing files:
284 284 a
285 285 committing manifest
286 286 committing changelog
287 287 copying changeset a0ea9b1a4c8c to ad120869acf0
288 288 another precious commit message
289 289
290 290
291 291 HG: Enter commit message. Lines beginning with 'HG:' are removed.
292 292 HG: Leave message empty to abort commit.
293 293 HG: --
294 294 HG: user: foo
295 295 HG: branch 'default'
296 296 HG: changed a
297 297 committing files:
298 298 a
299 299 committing manifest
300 300 committing changelog
301 301 stripping intermediate changeset a0ea9b1a4c8c
302 302 stripping amended changeset 7ab3bf440b54
303 303 2 changesets found
304 304 uncompressed size of bundle content:
305 305 450 (changelog)
306 306 282 (manifests)
307 307 209 a
308 308 saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend-backup.hg (glob)
309 309 1 changesets found
310 310 uncompressed size of bundle content:
311 311 246 (changelog)
312 312 143 (manifests)
313 313 113 a
314 314 adding branch
315 315 adding changesets
316 316 adding manifests
317 317 adding file changes
318 318 added 1 changesets with 1 changes to 1 files
319 319 committed changeset 1:ea22a388757c
320 320
321 321 $ rm editor.sh
322 322 $ hg log -r .
323 323 changeset: 1:ea22a388757c
324 324 tag: tip
325 325 user: foo
326 326 date: Thu Jan 01 00:00:01 1970 +0000
327 327 summary: another precious commit message
328 328
329 329
330 330 Moving bookmarks, preserve active bookmark:
331 331
332 332 $ hg book book1
333 333 $ hg book book2
334 334 $ hg ci --amend -m 'move bookmarks'
335 335 saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend-backup.hg (glob)
336 336 $ hg book
337 337 book1 1:6cec5aa930e2
338 338 * book2 1:6cec5aa930e2
339 339 $ echo a >> a
340 340 $ hg ci --amend -m 'move bookmarks'
341 341 saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend-backup.hg (glob)
342 342 $ hg book
343 343 book1 1:48bb6e53a15f
344 344 * book2 1:48bb6e53a15f
345 345
346 346 abort does not loose bookmarks
347 347
348 348 $ cat > editor.sh << '__EOF__'
349 349 > #!/bin/sh
350 350 > echo "" > "$1"
351 351 > __EOF__
352 352 $ echo a >> a
353 353 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
354 354 transaction abort!
355 355 rollback completed
356 356 abort: empty commit message
357 357 [255]
358 358 $ hg book
359 359 book1 1:48bb6e53a15f
360 360 * book2 1:48bb6e53a15f
361 361 $ hg revert -Caq
362 362 $ rm editor.sh
363 363
364 364 $ echo '[defaults]' >> $HGRCPATH
365 365 $ echo "commit=-d '0 0'" >> $HGRCPATH
366 366
367 367 Moving branches:
368 368
369 369 $ hg branch foo
370 370 marked working directory as branch foo
371 371 (branches are permanent and global, did you want a bookmark?)
372 372 $ echo a >> a
373 373 $ hg ci -m 'branch foo'
374 374 $ hg branch default -f
375 375 marked working directory as branch default
376 376 $ hg ci --amend -m 'back to default'
377 377 saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend-backup.hg (glob)
378 378 $ hg branches
379 379 default 2:ce12b0b57d46
380 380
381 381 Close branch:
382 382
383 383 $ hg up -q 0
384 384 $ echo b >> b
385 385 $ hg branch foo
386 386 marked working directory as branch foo
387 387 (branches are permanent and global, did you want a bookmark?)
388 388 $ hg ci -Am 'fork'
389 389 adding b
390 390 $ echo b >> b
391 391 $ hg ci -mb
392 392 $ hg ci --amend --close-branch -m 'closing branch foo'
393 393 saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend-backup.hg (glob)
394 394
395 395 Same thing, different code path:
396 396
397 397 $ echo b >> b
398 398 $ hg ci -m 'reopen branch'
399 399 reopening closed branch head 4
400 400 $ echo b >> b
401 401 $ hg ci --amend --close-branch
402 402 saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend-backup.hg (glob)
403 403 $ hg branches
404 404 default 2:ce12b0b57d46
405 405
406 406 Refuse to amend during a merge:
407 407
408 408 $ hg up -q default
409 409 $ hg merge foo
410 410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 411 (branch merge, don't forget to commit)
412 412 $ hg ci --amend
413 413 abort: cannot amend while merging
414 414 [255]
415 415 $ hg ci -m 'merge'
416 416
417 417 Follow copies/renames:
418 418
419 419 $ hg mv b c
420 420 $ hg ci -m 'b -> c'
421 421 $ hg mv c d
422 422 $ hg ci --amend -m 'b -> d'
423 423 saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend-backup.hg (glob)
424 424 $ hg st --rev '.^' --copies d
425 425 A d
426 426 b
427 427 $ hg cp d e
428 428 $ hg ci -m 'e = d'
429 429 $ hg cp e f
430 430 $ hg ci --amend -m 'f = d'
431 431 saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend-backup.hg (glob)
432 432 $ hg st --rev '.^' --copies f
433 433 A f
434 434 d
435 435
436 436 $ mv f f.orig
437 437 $ hg rm -A f
438 438 $ hg ci -m removef
439 439 $ hg cp a f
440 440 $ mv f.orig f
441 441 $ hg ci --amend -m replacef
442 442 saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend-backup.hg (glob)
443 443 $ hg st --change . --copies
444 444 $ hg log -r . --template "{file_copies}\n"
445 445
446 446
447 447 Move added file (issue3410):
448 448
449 449 $ echo g >> g
450 450 $ hg ci -Am g
451 451 adding g
452 452 $ hg mv g h
453 453 $ hg ci --amend
454 454 saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend-backup.hg (glob)
455 455 $ hg st --change . --copies h
456 456 A h
457 457 $ hg log -r . --template "{file_copies}\n"
458 458
459 459
460 460 Can't rollback an amend:
461 461
462 462 $ hg rollback
463 463 no rollback information available
464 464 [1]
465 465
466 466 Preserve extra dict (issue3430):
467 467
468 468 $ hg branch a
469 469 marked working directory as branch a
470 470 (branches are permanent and global, did you want a bookmark?)
471 471 $ echo a >> a
472 472 $ hg ci -ma
473 473 $ hg ci --amend -m "a'"
474 474 saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend-backup.hg (glob)
475 475 $ hg log -r . --template "{branch}\n"
476 476 a
477 477 $ hg ci --amend -m "a''"
478 478 saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend-backup.hg (glob)
479 479 $ hg log -r . --template "{branch}\n"
480 480 a
481 481
482 482 Also preserve other entries in the dict that are in the old commit,
483 483 first graft something so there's an additional entry:
484 484
485 485 $ hg up 0 -q
486 486 $ echo z > z
487 487 $ hg ci -Am 'fork'
488 488 adding z
489 489 created new head
490 490 $ hg up 11
491 491 5 files updated, 0 files merged, 1 files removed, 0 files unresolved
492 492 $ hg graft 12
493 493 grafting 12:2647734878ef "fork" (tip)
494 494 $ hg ci --amend -m 'graft amend'
495 495 saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend-backup.hg (glob)
496 496 $ hg log -r . --debug | grep extra
497 497 extra: amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
498 498 extra: branch=a
499 499 extra: source=2647734878ef0236dda712fae9c1651cf694ea8a
500 500
501 501 Preserve phase
502 502
503 503 $ hg phase '.^::.'
504 504 11: draft
505 505 13: draft
506 506 $ hg phase --secret --force .
507 507 $ hg phase '.^::.'
508 508 11: draft
509 509 13: secret
510 510 $ hg commit --amend -m 'amend for phase' -q
511 511 $ hg phase '.^::.'
512 512 11: draft
513 513 13: secret
514 514
515 515 Test amend with obsolete
516 516 ---------------------------
517 517
518 518 Enable obsolete
519 519
520 520 $ cat >> $HGRCPATH << EOF
521 521 > [experimental]
522 522 > evolution=createmarkers,allowunstable
523 523 > EOF
524 524
525 525 Amend with no files changes
526 526
527 527 $ hg id -n
528 528 13
529 529 $ hg ci --amend -m 'babar'
530 530 $ hg id -n
531 531 14
532 532 $ hg log -Gl 3 --style=compact
533 533 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
534 534 | babar
535 535 |
536 536 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
537 537 | | fork
538 538 | |
539 539 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
540 540 | | a''
541 541 | |
542 542 $ hg log -Gl 4 --hidden --style=compact
543 543 @ 14[tip]:11 b650e6ee8614 1970-01-01 00:00 +0000 test
544 544 | babar
545 545 |
546 546 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
547 547 |/ amend for phase
548 548 |
549 549 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
550 550 | | fork
551 551 | |
552 552 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
553 553 | | a''
554 554 | |
555 555
556 556 Amend with files changes
557 557
558 558 (note: the extra commit over 15 is a temporary junk I would be happy to get
559 559 ride of)
560 560
561 561 $ echo 'babar' >> a
562 562 $ hg commit --amend
563 563 $ hg log -Gl 6 --hidden --style=compact
564 564 @ 16[tip]:11 9f9e9bccf56c 1970-01-01 00:00 +0000 test
565 565 | babar
566 566 |
567 567 | x 15 90fef497c56f 1970-01-01 00:00 +0000 test
568 568 | | temporary amend commit for b650e6ee8614
569 569 | |
570 570 | x 14:11 b650e6ee8614 1970-01-01 00:00 +0000 test
571 571 |/ babar
572 572 |
573 573 | x 13:11 68ff8ff97044 1970-01-01 00:00 +0000 test
574 574 |/ amend for phase
575 575 |
576 576 | o 12:0 2647734878ef 1970-01-01 00:00 +0000 test
577 577 | | fork
578 578 | |
579 579 o | 11 3334b7925910 1970-01-01 00:00 +0000 test
580 580 | | a''
581 581 | |
582 582
583 583
584 584 Test that amend does not make it easy to create obsolescence cycle
585 585 ---------------------------------------------------------------------
586 586
587 587 $ hg id -r 14 --hidden
588 588 b650e6ee8614 (a)
589 589 $ hg revert -ar 14 --hidden
590 590 reverting a
591 591 $ hg commit --amend
592 592 $ hg id
593 593 b99e5df575f7 (a) tip
594 594
595 595 Test that rewriting leaving instability behind is allowed
596 596 ---------------------------------------------------------------------
597 597
598 598 $ hg up '.^'
599 599 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
600 600 $ echo 'b' >> a
601 601 $ hg log --style compact -r 'children(.)'
602 602 18[tip]:11 b99e5df575f7 1970-01-01 00:00 +0000 test
603 603 babar
604 604
605 605 $ hg commit --amend
606 606 $ hg log -r 'unstable()'
607 607 changeset: 18:b99e5df575f7
608 608 branch: a
609 609 parent: 11:3334b7925910
610 610 user: test
611 611 date: Thu Jan 01 00:00:00 1970 +0000
612 612 summary: babar
613 613
614 614
615 615 Amend a merge changeset (with renames and conflicts from the second parent):
616 616
617 617 $ hg up -q default
618 618 $ hg branch -q bar
619 619 $ hg cp a aa
620 620 $ hg mv z zz
621 621 $ echo cc > cc
622 622 $ hg add cc
623 623 $ hg ci -m aazzcc
624 624 $ hg up -q default
625 625 $ echo a >> a
626 626 $ echo dd > cc
627 627 $ hg add cc
628 628 $ hg ci -m aa
629 629 $ hg merge -q bar
630 630 warning: conflicts while merging cc! (edit, then use 'hg resolve --mark')
631 631 [1]
632 632 $ hg resolve -m cc
633 633 (no more unresolved files)
634 634 $ hg ci -m 'merge bar'
635 635 $ hg log --config diff.git=1 -pr .
636 636 changeset: 23:93cd4445f720
637 637 tag: tip
638 638 parent: 22:30d96aeaf27b
639 639 parent: 21:1aa437659d19
640 640 user: test
641 641 date: Thu Jan 01 00:00:00 1970 +0000
642 642 summary: merge bar
643 643
644 644 diff --git a/a b/aa
645 645 copy from a
646 646 copy to aa
647 647 diff --git a/cc b/cc
648 648 --- a/cc
649 649 +++ b/cc
650 650 @@ -1,1 +1,5 @@
651 651 +<<<<<<< local: 30d96aeaf27b - test: aa
652 652 dd
653 653 +=======
654 654 +cc
655 655 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
656 656 diff --git a/z b/zz
657 657 rename from z
658 658 rename to zz
659 659
660 660 $ hg debugrename aa
661 661 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
662 662 $ hg debugrename zz
663 663 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
664 664 $ hg debugrename cc
665 665 cc not renamed
666 666 $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
667 667 HGEDITFORM=commit.amend.merge
668 668 $ hg log --config diff.git=1 -pr .
669 669 changeset: 24:832b50f2c271
670 670 tag: tip
671 671 parent: 22:30d96aeaf27b
672 672 parent: 21:1aa437659d19
673 673 user: test
674 674 date: Thu Jan 01 00:00:00 1970 +0000
675 675 summary: merge bar (amend message)
676 676
677 677 diff --git a/a b/aa
678 678 copy from a
679 679 copy to aa
680 680 diff --git a/cc b/cc
681 681 --- a/cc
682 682 +++ b/cc
683 683 @@ -1,1 +1,5 @@
684 684 +<<<<<<< local: 30d96aeaf27b - test: aa
685 685 dd
686 686 +=======
687 687 +cc
688 688 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
689 689 diff --git a/z b/zz
690 690 rename from z
691 691 rename to zz
692 692
693 693 $ hg debugrename aa
694 694 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
695 695 $ hg debugrename zz
696 696 zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
697 697 $ hg debugrename cc
698 698 cc not renamed
699 699 $ hg mv zz z
700 700 $ hg ci --amend -m 'merge bar (undo rename)'
701 701 $ hg log --config diff.git=1 -pr .
702 702 changeset: 26:bdafc5c72f74
703 703 tag: tip
704 704 parent: 22:30d96aeaf27b
705 705 parent: 21:1aa437659d19
706 706 user: test
707 707 date: Thu Jan 01 00:00:00 1970 +0000
708 708 summary: merge bar (undo rename)
709 709
710 710 diff --git a/a b/aa
711 711 copy from a
712 712 copy to aa
713 713 diff --git a/cc b/cc
714 714 --- a/cc
715 715 +++ b/cc
716 716 @@ -1,1 +1,5 @@
717 717 +<<<<<<< local: 30d96aeaf27b - test: aa
718 718 dd
719 719 +=======
720 720 +cc
721 721 +>>>>>>> other: 1aa437659d19 bar - test: aazzcc
722 722
723 723 $ hg debugrename z
724 724 z not renamed
725 725
726 726 Amend a merge changeset (with renames during the merge):
727 727
728 728 $ hg up -q bar
729 729 $ echo x > x
730 730 $ hg add x
731 731 $ hg ci -m x
732 732 $ hg up -q default
733 733 $ hg merge -q bar
734 734 $ hg mv aa aaa
735 735 $ echo aa >> aaa
736 736 $ hg ci -m 'merge bar again'
737 737 $ hg log --config diff.git=1 -pr .
738 738 changeset: 28:32f19415b634
739 739 tag: tip
740 740 parent: 26:bdafc5c72f74
741 741 parent: 27:4c94d5bc65f5
742 742 user: test
743 743 date: Thu Jan 01 00:00:00 1970 +0000
744 744 summary: merge bar again
745 745
746 746 diff --git a/aa b/aa
747 747 deleted file mode 100644
748 748 --- a/aa
749 749 +++ /dev/null
750 750 @@ -1,2 +0,0 @@
751 751 -a
752 752 -a
753 753 diff --git a/aaa b/aaa
754 754 new file mode 100644
755 755 --- /dev/null
756 756 +++ b/aaa
757 757 @@ -0,0 +1,3 @@
758 758 +a
759 759 +a
760 760 +aa
761 761 diff --git a/x b/x
762 762 new file mode 100644
763 763 --- /dev/null
764 764 +++ b/x
765 765 @@ -0,0 +1,1 @@
766 766 +x
767 767
768 768 $ hg debugrename aaa
769 769 aaa renamed from aa:37d9b5d994eab34eda9c16b195ace52c7b129980
770 770 $ hg mv aaa aa
771 771 $ hg ci --amend -m 'merge bar again (undo rename)'
772 772 $ hg log --config diff.git=1 -pr .
773 773 changeset: 30:1e2a06b3d312
774 774 tag: tip
775 775 parent: 26:bdafc5c72f74
776 776 parent: 27:4c94d5bc65f5
777 777 user: test
778 778 date: Thu Jan 01 00:00:00 1970 +0000
779 779 summary: merge bar again (undo rename)
780 780
781 781 diff --git a/aa b/aa
782 782 --- a/aa
783 783 +++ b/aa
784 784 @@ -1,2 +1,3 @@
785 785 a
786 786 a
787 787 +aa
788 788 diff --git a/x b/x
789 789 new file mode 100644
790 790 --- /dev/null
791 791 +++ b/x
792 792 @@ -0,0 +1,1 @@
793 793 +x
794 794
795 795 $ hg debugrename aa
796 796 aa not renamed
797 797 $ hg debugrename -r '.^' aa
798 798 aa renamed from a:a80d06849b333b8a3d5c445f8ba3142010dcdc9e
799 799
800 800 Amend a merge changeset (with manifest-level conflicts):
801 801
802 802 $ hg up -q bar
803 803 $ hg rm aa
804 804 $ hg ci -m 'rm aa'
805 805 $ hg up -q default
806 806 $ echo aa >> aa
807 807 $ hg ci -m aa
808 808 $ hg merge -q bar
809 809 local changed aa which remote deleted
810 810 use (c)hanged version or (d)elete? c
811 811 $ hg ci -m 'merge bar (with conflicts)'
812 812 $ hg log --config diff.git=1 -pr .
813 813 changeset: 33:97a298b0c59f
814 814 tag: tip
815 815 parent: 32:3d78ce4226b8
816 816 parent: 31:67db8847a540
817 817 user: test
818 818 date: Thu Jan 01 00:00:00 1970 +0000
819 819 summary: merge bar (with conflicts)
820 820
821 821
822 822 $ hg rm aa
823 823 $ hg ci --amend -m 'merge bar (with conflicts, amended)'
824 824 $ hg log --config diff.git=1 -pr .
825 825 changeset: 35:6de0c1bde1c8
826 826 tag: tip
827 827 parent: 32:3d78ce4226b8
828 828 parent: 31:67db8847a540
829 829 user: test
830 830 date: Thu Jan 01 00:00:00 1970 +0000
831 831 summary: merge bar (with conflicts, amended)
832 832
833 833 diff --git a/aa b/aa
834 834 deleted file mode 100644
835 835 --- a/aa
836 836 +++ /dev/null
837 837 @@ -1,4 +0,0 @@
838 838 -a
839 839 -a
840 840 -aa
841 841 -aa
842 842
843 843 Issue 3445: amending with --close-branch a commit that created a new head should fail
844 844 This shouldn't be possible:
845 845
846 846 $ hg up -q default
847 847 $ hg branch closewithamend
848 848 marked working directory as branch closewithamend
849 849 $ echo foo > foo
850 850 $ hg add foo
851 851 $ hg ci -m..
852 852 $ hg ci --amend --close-branch -m 'closing'
853 853 abort: can only close branch heads
854 854 [255]
855 855
856 856 This silliness fails:
857 857
858 858 $ hg branch silliness
859 859 marked working directory as branch silliness
860 860 $ echo b >> b
861 861 $ hg ci --close-branch -m'open and close'
862 862 abort: can only close branch heads
863 863 [255]
864 864
865 865 Test that amend with --secret creates new secret changeset forcibly
866 866 ---------------------------------------------------------------------
867 867
868 868 $ hg phase '.^::.'
869 869 35: draft
870 870 36: draft
871 871 $ hg commit --amend --secret -m 'amend as secret' -q
872 872 $ hg phase '.^::.'
873 873 35: draft
874 874 38: secret
875 875
876 876 Test that amend with --edit invokes editor forcibly
877 877 ---------------------------------------------------
878 878
879 879 $ hg parents --template "{desc}\n"
880 880 amend as secret
881 881 $ HGEDITOR=cat hg commit --amend -m "editor should be suppressed"
882 882 $ hg parents --template "{desc}\n"
883 883 editor should be suppressed
884 884
885 885 $ hg status --rev '.^1::.'
886 886 A foo
887 887 $ HGEDITOR=cat hg commit --amend -m "editor should be invoked" --edit
888 888 editor should be invoked
889 889
890 890
891 891 HG: Enter commit message. Lines beginning with 'HG:' are removed.
892 892 HG: Leave message empty to abort commit.
893 893 HG: --
894 894 HG: user: test
895 895 HG: branch 'silliness'
896 896 HG: added foo
897 897 $ hg parents --template "{desc}\n"
898 898 editor should be invoked
899 899
900 900 Test that "diff()" in committemplate works correctly for amending
901 901 -----------------------------------------------------------------
902 902
903 903 $ cat >> .hg/hgrc <<EOF
904 904 > [committemplate]
905 905 > changeset.commit.amend = {desc}\n
906 906 > HG: M: {file_mods}
907 907 > HG: A: {file_adds}
908 908 > HG: R: {file_dels}
909 909 > {splitlines(diff()) % 'HG: {line}\n'}
910 910 > EOF
911 911
912 912 $ hg parents --template "M: {file_mods}\nA: {file_adds}\nR: {file_dels}\n"
913 913 M:
914 914 A: foo
915 915 R:
916 916 $ hg status -amr
917 917 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo"
918 918 expecting diff of foo
919 919
920 920 HG: M:
921 921 HG: A: foo
922 922 HG: R:
923 923 HG: diff -r 6de0c1bde1c8 foo
924 924 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
925 925 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
926 926 HG: @@ -0,0 +1,1 @@
927 927 HG: +foo
928 928
929 929 $ echo y > y
930 930 $ hg add y
931 931 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of foo and y"
932 932 expecting diff of foo and y
933 933
934 934 HG: M:
935 935 HG: A: foo y
936 936 HG: R:
937 937 HG: diff -r 6de0c1bde1c8 foo
938 938 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
939 939 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
940 940 HG: @@ -0,0 +1,1 @@
941 941 HG: +foo
942 942 HG: diff -r 6de0c1bde1c8 y
943 943 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
944 944 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
945 945 HG: @@ -0,0 +1,1 @@
946 946 HG: +y
947 947
948 948 $ hg rm a
949 949 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo and y"
950 950 expecting diff of a, foo and y
951 951
952 952 HG: M:
953 953 HG: A: foo y
954 954 HG: R: a
955 955 HG: diff -r 6de0c1bde1c8 a
956 956 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
957 957 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
958 958 HG: @@ -1,2 +0,0 @@
959 959 HG: -a
960 960 HG: -a
961 961 HG: diff -r 6de0c1bde1c8 foo
962 962 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
963 963 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
964 964 HG: @@ -0,0 +1,1 @@
965 965 HG: +foo
966 966 HG: diff -r 6de0c1bde1c8 y
967 967 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
968 968 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
969 969 HG: @@ -0,0 +1,1 @@
970 970 HG: +y
971 971
972 972 $ hg rm x
973 973 $ HGEDITOR=cat hg commit --amend -e -m "expecting diff of a, foo, x and y"
974 974 expecting diff of a, foo, x and y
975 975
976 976 HG: M:
977 977 HG: A: foo y
978 978 HG: R: a x
979 979 HG: diff -r 6de0c1bde1c8 a
980 980 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
981 981 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
982 982 HG: @@ -1,2 +0,0 @@
983 983 HG: -a
984 984 HG: -a
985 985 HG: diff -r 6de0c1bde1c8 foo
986 986 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
987 987 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
988 988 HG: @@ -0,0 +1,1 @@
989 989 HG: +foo
990 990 HG: diff -r 6de0c1bde1c8 x
991 991 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
992 992 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
993 993 HG: @@ -1,1 +0,0 @@
994 994 HG: -x
995 995 HG: diff -r 6de0c1bde1c8 y
996 996 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
997 997 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
998 998 HG: @@ -0,0 +1,1 @@
999 999 HG: +y
1000 1000
1001 1001 $ echo cccc >> cc
1002 1002 $ hg status -amr
1003 1003 M cc
1004 1004 $ HGEDITOR=cat hg commit --amend -e -m "cc should be excluded" -X cc
1005 1005 cc should be excluded
1006 1006
1007 1007 HG: M:
1008 1008 HG: A: foo y
1009 1009 HG: R: a x
1010 1010 HG: diff -r 6de0c1bde1c8 a
1011 1011 HG: --- a/a Thu Jan 01 00:00:00 1970 +0000
1012 1012 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1013 1013 HG: @@ -1,2 +0,0 @@
1014 1014 HG: -a
1015 1015 HG: -a
1016 1016 HG: diff -r 6de0c1bde1c8 foo
1017 1017 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1018 1018 HG: +++ b/foo Thu Jan 01 00:00:00 1970 +0000
1019 1019 HG: @@ -0,0 +1,1 @@
1020 1020 HG: +foo
1021 1021 HG: diff -r 6de0c1bde1c8 x
1022 1022 HG: --- a/x Thu Jan 01 00:00:00 1970 +0000
1023 1023 HG: +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
1024 1024 HG: @@ -1,1 +0,0 @@
1025 1025 HG: -x
1026 1026 HG: diff -r 6de0c1bde1c8 y
1027 1027 HG: --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1028 1028 HG: +++ b/y Thu Jan 01 00:00:00 1970 +0000
1029 1029 HG: @@ -0,0 +1,1 @@
1030 1030 HG: +y
1031 1031
1032 1032 Check for issue4405
1033 1033 -------------------
1034 1034
1035 1035 Setup the repo with a file that gets moved in a second commit.
1036 1036 $ hg init repo
1037 1037 $ cd repo
1038 1038 $ touch a0
1039 1039 $ hg add a0
1040 1040 $ hg commit -m a0
1041 1041 $ hg mv a0 a1
1042 1042 $ hg commit -m a1
1043 1043 $ hg up -q 0
1044 1044 $ hg log -G --template '{rev} {desc}'
1045 1045 o 1 a1
1046 1046 |
1047 1047 @ 0 a0
1048 1048
1049 1049
1050 1050 Now we branch the repro, but re-use the file contents, so we have a divergence
1051 1051 in the file revlog topology and the changelog topology.
1052 1052 $ hg revert --rev 1 --all
1053 1053 removing a0
1054 1054 adding a1
1055 1055 $ hg ci -qm 'a1-amend'
1056 1056 $ hg log -G --template '{rev} {desc}'
1057 1057 @ 2 a1-amend
1058 1058 |
1059 1059 | o 1 a1
1060 1060 |/
1061 1061 o 0 a0
1062 1062
1063 1063
1064 1064 The way mercurial does amends is to create a temporary commit (rev 3) and then
1065 1065 fold the new and old commits together into another commit (rev 4). During this
1066 1066 process, _findlimit is called to check how far back to look for the transitive
1067 1067 closure of file copy information, but due to the divergence of the filelog
1068 1068 and changelog graph topologies, before _findlimit was fixed, it returned a rev
1069 1069 which was not far enough back in this case.
1070 1070 $ hg mv a1 a2
1071 1071 $ hg status --copies --rev 0
1072 1072 A a2
1073 1073 a0
1074 1074 R a0
1075 1075 $ hg ci --amend -q
1076 1076 $ hg log -G --template '{rev} {desc}'
1077 1077 @ 4 a1-amend
1078 1078 |
1079 1079 | o 1 a1
1080 1080 |/
1081 1081 o 0 a0
1082 1082
1083 1083
1084 1084 Before the fix, the copy information was lost.
1085 1085 $ hg status --copies --rev 0
1086 1086 A a2
1087 1087 a0
1088 1088 R a0
1089 1089 $ cd ..
1090 1090
1091 1091 Check that amend properly preserve rename from directory rename (issue-4516)
1092 1092
1093 1093 If a parent of the merge renames a full directory, any files added to the old
1094 1094 directory in the other parent will be renamed to the new directory. For some
1095 1095 reason, the rename metadata was when amending such merge. This test ensure we
1096 1096 do not regress. We have a dedicated repo because it needs a setup with renamed
1097 1097 directory)
1098 1098
1099 1099 $ hg init issue4516
1100 1100 $ cd issue4516
1101 1101 $ mkdir olddirname
1102 1102 $ echo line1 > olddirname/commonfile.py
1103 1103 $ hg add olddirname/commonfile.py
1104 1104 $ hg ci -m first
1105 1105
1106 1106 $ hg branch newdirname
1107 1107 marked working directory as branch newdirname
1108 1108 (branches are permanent and global, did you want a bookmark?)
1109 1109 $ hg mv olddirname newdirname
1110 1110 moving olddirname/commonfile.py to newdirname/commonfile.py (glob)
1111 1111 $ hg ci -m rename
1112 1112
1113 1113 $ hg update default
1114 1114 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1115 1115 $ echo line1 > olddirname/newfile.py
1116 1116 $ hg add olddirname/newfile.py
1117 1117 $ hg ci -m log
1118 1118
1119 1119 $ hg up newdirname
1120 1120 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
1121 1121 $ # create newdirname/newfile.py
1122 1122 $ hg merge default
1123 1123 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1124 1124 (branch merge, don't forget to commit)
1125 1125 $ hg ci -m add
1126 1126 $
1127 1127 $ hg debugrename newdirname/newfile.py
1128 1128 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1129 1129 $ hg status -C --change .
1130 1130 A newdirname/newfile.py
1131 1131 $ hg status -C --rev 1
1132 1132 A newdirname/newfile.py
1133 1133 $ hg status -C --rev 2
1134 1134 A newdirname/commonfile.py
1135 1135 olddirname/commonfile.py
1136 1136 A newdirname/newfile.py
1137 1137 olddirname/newfile.py
1138 1138 R olddirname/commonfile.py
1139 1139 R olddirname/newfile.py
1140 1140 $ hg debugindex newdirname/newfile.py
1141 1141 rev offset length base linkrev nodeid p1 p2
1142 1142 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
1143 1143
1144 1144 $ echo a >> newdirname/commonfile.py
1145 1145 $ hg ci --amend -m bug
1146 1146 $ hg debugrename newdirname/newfile.py
1147 1147 newdirname/newfile.py renamed from olddirname/newfile.py:690b295714aed510803d3020da9c70fca8336def (glob)
1148 1148 $ hg debugindex newdirname/newfile.py
1149 1149 rev offset length base linkrev nodeid p1 p2
1150 1150 0 0 88 0 3 34a4d536c0c0 000000000000 000000000000
1151 1151
@@ -1,1572 +1,1658 b''
1 1 $ hg init a
2 2 $ mkdir a/d1
3 3 $ mkdir a/d1/d2
4 4 $ echo line 1 > a/a
5 5 $ echo line 1 > a/d1/d2/a
6 6 $ hg --cwd a ci -Ama
7 7 adding a
8 8 adding d1/d2/a
9 9
10 10 $ echo line 2 >> a/a
11 11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12 12
13 13 import with no args:
14 14
15 15 $ hg --cwd a import
16 16 abort: need at least one patch to import
17 17 [255]
18 18
19 19 generate patches for the test
20 20
21 21 $ hg --cwd a export tip > exported-tip.patch
22 22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23 23
24 24
25 25 import exported patch
26 26 (this also tests that editor is not invoked, if the patch contains the
27 27 commit message and '--edit' is not specified)
28 28
29 29 $ hg clone -r0 a b
30 30 adding changesets
31 31 adding manifests
32 32 adding file changes
33 33 added 1 changesets with 2 changes to 2 files
34 34 updating to branch default
35 35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 37 applying ../exported-tip.patch
38 38
39 39 message and committer and date should be same
40 40
41 41 $ hg --cwd b tip
42 42 changeset: 1:1d4bd90af0e4
43 43 tag: tip
44 44 user: someone
45 45 date: Thu Jan 01 00:00:01 1970 +0000
46 46 summary: second change
47 47
48 48 $ rm -r b
49 49
50 50
51 51 import exported patch with external patcher
52 52 (this also tests that editor is invoked, if the '--edit' is specified,
53 53 regardless of the commit message in the patch)
54 54
55 55 $ cat > dummypatch.py <<EOF
56 56 > print 'patching file a'
57 57 > file('a', 'wb').write('line2\n')
58 58 > EOF
59 59 $ hg clone -r0 a b
60 60 adding changesets
61 61 adding manifests
62 62 adding file changes
63 63 added 1 changesets with 2 changes to 2 files
64 64 updating to branch default
65 65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
67 67 applying ../exported-tip.patch
68 68 second change
69 69
70 70
71 71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 72 HG: Leave message empty to abort commit.
73 73 HG: --
74 74 HG: user: someone
75 75 HG: branch 'default'
76 76 HG: changed a
77 77 $ cat b/a
78 78 line2
79 79 $ rm -r b
80 80
81 81
82 82 import of plain diff should fail without message
83 83 (this also tests that editor is invoked, if the patch doesn't contain
84 84 the commit message, regardless of '--edit')
85 85
86 86 $ hg clone -r0 a b
87 87 adding changesets
88 88 adding manifests
89 89 adding file changes
90 90 added 1 changesets with 2 changes to 2 files
91 91 updating to branch default
92 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 $ cat > $TESTTMP/editor.sh <<EOF
94 94 > env | grep HGEDITFORM
95 95 > cat \$1
96 96 > EOF
97 97 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
98 98 applying ../diffed-tip.patch
99 99 HGEDITFORM=import.normal.normal
100 100
101 101
102 102 HG: Enter commit message. Lines beginning with 'HG:' are removed.
103 103 HG: Leave message empty to abort commit.
104 104 HG: --
105 105 HG: user: test
106 106 HG: branch 'default'
107 107 HG: changed a
108 108 abort: empty commit message
109 109 [255]
110 110
111 111 Test avoiding editor invocation at applying the patch with --exact,
112 112 even if commit message is empty
113 113
114 114 $ echo a >> b/a
115 115 $ hg --cwd b commit -m ' '
116 116 $ hg --cwd b tip -T "{node}\n"
117 117 d8804f3f5396d800812f579c8452796a5993bdb2
118 118 $ hg --cwd b export -o ../empty-log.diff .
119 119 $ hg --cwd b update -q -C ".^1"
120 120 $ hg --cwd b --config extensions.strip= strip -q tip
121 121 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
122 122 applying ../empty-log.diff
123 123 $ hg --cwd b tip -T "{node}\n"
124 124 d8804f3f5396d800812f579c8452796a5993bdb2
125 125
126 126 $ rm -r b
127 127
128 128
129 129 import of plain diff should be ok with message
130 130
131 131 $ hg clone -r0 a b
132 132 adding changesets
133 133 adding manifests
134 134 adding file changes
135 135 added 1 changesets with 2 changes to 2 files
136 136 updating to branch default
137 137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 138 $ hg --cwd b import -mpatch ../diffed-tip.patch
139 139 applying ../diffed-tip.patch
140 140 $ rm -r b
141 141
142 142
143 143 import of plain diff with specific date and user
144 144 (this also tests that editor is not invoked, if
145 145 '--message'/'--logfile' is specified and '--edit' is not)
146 146
147 147 $ hg clone -r0 a b
148 148 adding changesets
149 149 adding manifests
150 150 adding file changes
151 151 added 1 changesets with 2 changes to 2 files
152 152 updating to branch default
153 153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 154 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
155 155 applying ../diffed-tip.patch
156 156 $ hg -R b tip -pv
157 157 changeset: 1:ca68f19f3a40
158 158 tag: tip
159 159 user: user@nowhere.net
160 160 date: Thu Jan 01 00:00:01 1970 +0000
161 161 files: a
162 162 description:
163 163 patch
164 164
165 165
166 166 diff -r 80971e65b431 -r ca68f19f3a40 a
167 167 --- a/a Thu Jan 01 00:00:00 1970 +0000
168 168 +++ b/a Thu Jan 01 00:00:01 1970 +0000
169 169 @@ -1,1 +1,2 @@
170 170 line 1
171 171 +line 2
172 172
173 173 $ rm -r b
174 174
175 175
176 176 import of plain diff should be ok with --no-commit
177 177 (this also tests that editor is not invoked, if '--no-commit' is
178 178 specified, regardless of '--edit')
179 179
180 180 $ hg clone -r0 a b
181 181 adding changesets
182 182 adding manifests
183 183 adding file changes
184 184 added 1 changesets with 2 changes to 2 files
185 185 updating to branch default
186 186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
188 188 applying ../diffed-tip.patch
189 189 $ hg --cwd b diff --nodates
190 190 diff -r 80971e65b431 a
191 191 --- a/a
192 192 +++ b/a
193 193 @@ -1,1 +1,2 @@
194 194 line 1
195 195 +line 2
196 196 $ rm -r b
197 197
198 198
199 199 import of malformed plain diff should fail
200 200
201 201 $ hg clone -r0 a b
202 202 adding changesets
203 203 adding manifests
204 204 adding file changes
205 205 added 1 changesets with 2 changes to 2 files
206 206 updating to branch default
207 207 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 208 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
209 209 $ hg --cwd b import -mpatch ../broken.patch
210 210 applying ../broken.patch
211 211 abort: bad hunk #1
212 212 [255]
213 213 $ rm -r b
214 214
215 215
216 216 hg -R repo import
217 217 put the clone in a subdir - having a directory named "a"
218 218 used to hide a bug.
219 219
220 220 $ mkdir dir
221 221 $ hg clone -r0 a dir/b
222 222 adding changesets
223 223 adding manifests
224 224 adding file changes
225 225 added 1 changesets with 2 changes to 2 files
226 226 updating to branch default
227 227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 228 $ cd dir
229 229 $ hg -R b import ../exported-tip.patch
230 230 applying ../exported-tip.patch
231 231 $ cd ..
232 232 $ rm -r dir
233 233
234 234
235 235 import from stdin
236 236
237 237 $ hg clone -r0 a b
238 238 adding changesets
239 239 adding manifests
240 240 adding file changes
241 241 added 1 changesets with 2 changes to 2 files
242 242 updating to branch default
243 243 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 244 $ hg --cwd b import - < exported-tip.patch
245 245 applying patch from stdin
246 246 $ rm -r b
247 247
248 248
249 249 import two patches in one stream
250 250
251 251 $ hg init b
252 252 $ hg --cwd a export 0:tip | hg --cwd b import -
253 253 applying patch from stdin
254 254 $ hg --cwd a id
255 255 1d4bd90af0e4 tip
256 256 $ hg --cwd b id
257 257 1d4bd90af0e4 tip
258 258 $ rm -r b
259 259
260 260
261 261 override commit message
262 262
263 263 $ hg clone -r0 a b
264 264 adding changesets
265 265 adding manifests
266 266 adding file changes
267 267 added 1 changesets with 2 changes to 2 files
268 268 updating to branch default
269 269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 270 $ hg --cwd b import -m 'override' - < exported-tip.patch
271 271 applying patch from stdin
272 272 $ hg --cwd b tip | grep override
273 273 summary: override
274 274 $ rm -r b
275 275
276 276 $ cat > mkmsg.py <<EOF
277 277 > import email.Message, sys
278 278 > msg = email.Message.Message()
279 279 > patch = open(sys.argv[1], 'rb').read()
280 280 > msg.set_payload('email commit message\n' + patch)
281 281 > msg['Subject'] = 'email patch'
282 282 > msg['From'] = 'email patcher'
283 283 > file(sys.argv[2], 'wb').write(msg.as_string())
284 284 > EOF
285 285
286 286
287 287 plain diff in email, subject, message body
288 288
289 289 $ hg clone -r0 a b
290 290 adding changesets
291 291 adding manifests
292 292 adding file changes
293 293 added 1 changesets with 2 changes to 2 files
294 294 updating to branch default
295 295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 296 $ python mkmsg.py diffed-tip.patch msg.patch
297 297 $ hg --cwd b import ../msg.patch
298 298 applying ../msg.patch
299 299 $ hg --cwd b tip | grep email
300 300 user: email patcher
301 301 summary: email patch
302 302 $ rm -r b
303 303
304 304
305 305 plain diff in email, no subject, message body
306 306
307 307 $ hg clone -r0 a b
308 308 adding changesets
309 309 adding manifests
310 310 adding file changes
311 311 added 1 changesets with 2 changes to 2 files
312 312 updating to branch default
313 313 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 314 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
315 315 applying patch from stdin
316 316 $ rm -r b
317 317
318 318
319 319 plain diff in email, subject, no message body
320 320
321 321 $ hg clone -r0 a b
322 322 adding changesets
323 323 adding manifests
324 324 adding file changes
325 325 added 1 changesets with 2 changes to 2 files
326 326 updating to branch default
327 327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 328 $ grep -v '^email ' msg.patch | hg --cwd b import -
329 329 applying patch from stdin
330 330 $ rm -r b
331 331
332 332
333 333 plain diff in email, no subject, no message body, should fail
334 334
335 335 $ hg clone -r0 a b
336 336 adding changesets
337 337 adding manifests
338 338 adding file changes
339 339 added 1 changesets with 2 changes to 2 files
340 340 updating to branch default
341 341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 342 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
343 343 applying patch from stdin
344 344 abort: empty commit message
345 345 [255]
346 346 $ rm -r b
347 347
348 348
349 349 hg export in email, should use patch header
350 350
351 351 $ hg clone -r0 a b
352 352 adding changesets
353 353 adding manifests
354 354 adding file changes
355 355 added 1 changesets with 2 changes to 2 files
356 356 updating to branch default
357 357 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 358 $ python mkmsg.py exported-tip.patch msg.patch
359 359 $ cat msg.patch | hg --cwd b import -
360 360 applying patch from stdin
361 361 $ hg --cwd b tip | grep second
362 362 summary: second change
363 363 $ rm -r b
364 364
365 365
366 366 subject: duplicate detection, removal of [PATCH]
367 367 The '---' tests the gitsendmail handling without proper mail headers
368 368
369 369 $ cat > mkmsg2.py <<EOF
370 370 > import email.Message, sys
371 371 > msg = email.Message.Message()
372 372 > patch = open(sys.argv[1], 'rb').read()
373 373 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
374 374 > msg['Subject'] = '[PATCH] email patch'
375 375 > msg['From'] = 'email patcher'
376 376 > file(sys.argv[2], 'wb').write(msg.as_string())
377 377 > EOF
378 378
379 379
380 380 plain diff in email, [PATCH] subject, message body with subject
381 381
382 382 $ hg clone -r0 a b
383 383 adding changesets
384 384 adding manifests
385 385 adding file changes
386 386 added 1 changesets with 2 changes to 2 files
387 387 updating to branch default
388 388 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 389 $ python mkmsg2.py diffed-tip.patch msg.patch
390 390 $ cat msg.patch | hg --cwd b import -
391 391 applying patch from stdin
392 392 $ hg --cwd b tip --template '{desc}\n'
393 393 email patch
394 394
395 395 next line
396 396 $ rm -r b
397 397
398 398
399 399 Issue963: Parent of working dir incorrect after import of multiple
400 400 patches and rollback
401 401
402 402 We weren't backing up the correct dirstate file when importing many
403 403 patches: import patch1 patch2; rollback
404 404
405 405 $ echo line 3 >> a/a
406 406 $ hg --cwd a ci -m'third change'
407 407 $ hg --cwd a export -o '../patch%R' 1 2
408 408 $ hg clone -qr0 a b
409 409 $ hg --cwd b parents --template 'parent: {rev}\n'
410 410 parent: 0
411 411 $ hg --cwd b import -v ../patch1 ../patch2
412 412 applying ../patch1
413 413 patching file a
414 414 committing files:
415 415 a
416 416 committing manifest
417 417 committing changelog
418 418 created 1d4bd90af0e4
419 419 applying ../patch2
420 420 patching file a
421 421 committing files:
422 422 a
423 423 committing manifest
424 424 committing changelog
425 425 created 6d019af21222
426 426 $ hg --cwd b rollback
427 427 repository tip rolled back to revision 0 (undo import)
428 428 working directory now based on revision 0
429 429 $ hg --cwd b parents --template 'parent: {rev}\n'
430 430 parent: 0
431 431
432 432 Test that "hg rollback" doesn't restore dirstate to one at the
433 433 beginning of the rollbacked transaction in not-"parent-gone" case.
434 434
435 435 invoking pretxncommit hook will cause marking '.hg/dirstate' as a file
436 436 to be restored at rollbacking, after DirstateTransactionPlan (see wiki
437 437 page for detail).
438 438
439 439 $ hg --cwd b branch -q foobar
440 440 $ hg --cwd b commit -m foobar
441 441 $ hg --cwd b update 0 -q
442 442 $ hg --cwd b import ../patch1 ../patch2 --config hooks.pretxncommit=true
443 443 applying ../patch1
444 444 applying ../patch2
445 445 $ hg --cwd b update -q 1
446 446 $ hg --cwd b rollback -q
447 447 $ hg --cwd b parents --template 'parent: {rev}\n'
448 448 parent: 1
449 449
450 $ hg --cwd b update -q -C 0
451 $ hg --cwd b --config extensions.strip= strip -q 1
452
453 Test visibility of in-memory distate changes inside transaction to
454 external process
455
456 $ echo foo > a/foo
457 $ hg --cwd a commit -A -m 'adding foo' foo
458 $ hg --cwd a export -o '../patch%R' 3
459
460 $ cat > $TESTTMP/checkvisibility.sh <<EOF
461 > echo "===="
462 > hg parents --template "VISIBLE {rev}:{node|short}\n"
463 > hg status -amr
464 > # test that pending changes are hidden
465 > unset HG_PENDING
466 > hg parents --template "ACTUAL {rev}:{node|short}\n"
467 > hg status -amr
468 > echo "===="
469 > EOF
470
471 == test visibility to external editor
472
473 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
474 ====
475 VISIBLE 0:80971e65b431
476 ACTUAL 0:80971e65b431
477 ====
478
479 $ HGEDITOR="sh $TESTTMP/checkvisibility.sh" hg --cwd b import -v --edit ../patch1 ../patch2 ../patch3
480 applying ../patch1
481 patching file a
482 ====
483 VISIBLE 0:80971e65b431
484 M a
485 ACTUAL 0:80971e65b431
486 M a
487 ====
488 committing files:
489 a
490 committing manifest
491 committing changelog
492 created 1d4bd90af0e4
493 applying ../patch2
494 patching file a
495 ====
496 VISIBLE 1:1d4bd90af0e4
497 M a
498 ACTUAL 0:80971e65b431
499 M a
500 ====
501 committing files:
502 a
503 committing manifest
504 committing changelog
505 created 6d019af21222
506 applying ../patch3
507 patching file foo
508 adding foo
509 ====
510 VISIBLE 2:6d019af21222
511 A foo
512 ACTUAL 0:80971e65b431
513 M a
514 ====
515 committing files:
516 foo
517 committing manifest
518 committing changelog
519 created 55e3f75b2378
520
521 $ hg --cwd b rollback -q
522
523 (content of file "a" is already changed and it should be recognized as
524 "M", even though dirstate is restored to one before "hg import")
525
526 $ (cd b && sh "$TESTTMP/checkvisibility.sh")
527 ====
528 VISIBLE 0:80971e65b431
529 M a
530 ACTUAL 0:80971e65b431
531 M a
532 ====
533 $ hg --cwd b revert --no-backup a
534 $ rm -f b/foo
535
450 536 $ rm -r b
451 537
452 538
453 539 importing a patch in a subdirectory failed at the commit stage
454 540
455 541 $ echo line 2 >> a/d1/d2/a
456 542 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
457 543
458 544 hg import in a subdirectory
459 545
460 546 $ hg clone -r0 a b
461 547 adding changesets
462 548 adding manifests
463 549 adding file changes
464 550 added 1 changesets with 2 changes to 2 files
465 551 updating to branch default
466 552 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
467 553 $ hg --cwd a export tip > tmp
468 554 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
469 555 $ dir=`pwd`
470 556 $ cd b/d1/d2 2>&1 > /dev/null
471 557 $ hg import ../../../subdir-tip.patch
472 558 applying ../../../subdir-tip.patch
473 559 $ cd "$dir"
474 560
475 561 message should be 'subdir change'
476 562 committer should be 'someoneelse'
477 563
478 564 $ hg --cwd b tip
479 565 changeset: 1:3577f5aea227
480 566 tag: tip
481 567 user: someoneelse
482 568 date: Thu Jan 01 00:00:01 1970 +0000
483 569 summary: subdir change
484 570
485 571
486 572 should be empty
487 573
488 574 $ hg --cwd b status
489 575
490 576
491 577 Test fuzziness (ambiguous patch location, fuzz=2)
492 578
493 579 $ hg init fuzzy
494 580 $ cd fuzzy
495 581 $ echo line1 > a
496 582 $ echo line0 >> a
497 583 $ echo line3 >> a
498 584 $ hg ci -Am adda
499 585 adding a
500 586 $ echo line1 > a
501 587 $ echo line2 >> a
502 588 $ echo line0 >> a
503 589 $ echo line3 >> a
504 590 $ hg ci -m change a
505 591 $ hg export tip > fuzzy-tip.patch
506 592 $ hg up -C 0
507 593 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
508 594 $ echo line1 > a
509 595 $ echo line0 >> a
510 596 $ echo line1 >> a
511 597 $ echo line0 >> a
512 598 $ hg ci -m brancha
513 599 created new head
514 600 $ hg import --config patch.fuzz=0 -v fuzzy-tip.patch
515 601 applying fuzzy-tip.patch
516 602 patching file a
517 603 Hunk #1 FAILED at 0
518 604 1 out of 1 hunks FAILED -- saving rejects to file a.rej
519 605 abort: patch failed to apply
520 606 [255]
521 607 $ hg import --no-commit -v fuzzy-tip.patch
522 608 applying fuzzy-tip.patch
523 609 patching file a
524 610 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
525 611 applied to working directory
526 612 $ hg revert -a
527 613 reverting a
528 614
529 615
530 616 import with --no-commit should have written .hg/last-message.txt
531 617
532 618 $ cat .hg/last-message.txt
533 619 change (no-eol)
534 620
535 621
536 622 test fuzziness with eol=auto
537 623
538 624 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
539 625 applying fuzzy-tip.patch
540 626 patching file a
541 627 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
542 628 applied to working directory
543 629 $ cd ..
544 630
545 631
546 632 Test hunk touching empty files (issue906)
547 633
548 634 $ hg init empty
549 635 $ cd empty
550 636 $ touch a
551 637 $ touch b1
552 638 $ touch c1
553 639 $ echo d > d
554 640 $ hg ci -Am init
555 641 adding a
556 642 adding b1
557 643 adding c1
558 644 adding d
559 645 $ echo a > a
560 646 $ echo b > b1
561 647 $ hg mv b1 b2
562 648 $ echo c > c1
563 649 $ hg copy c1 c2
564 650 $ rm d
565 651 $ touch d
566 652 $ hg diff --git
567 653 diff --git a/a b/a
568 654 --- a/a
569 655 +++ b/a
570 656 @@ -0,0 +1,1 @@
571 657 +a
572 658 diff --git a/b1 b/b2
573 659 rename from b1
574 660 rename to b2
575 661 --- a/b1
576 662 +++ b/b2
577 663 @@ -0,0 +1,1 @@
578 664 +b
579 665 diff --git a/c1 b/c1
580 666 --- a/c1
581 667 +++ b/c1
582 668 @@ -0,0 +1,1 @@
583 669 +c
584 670 diff --git a/c1 b/c2
585 671 copy from c1
586 672 copy to c2
587 673 --- a/c1
588 674 +++ b/c2
589 675 @@ -0,0 +1,1 @@
590 676 +c
591 677 diff --git a/d b/d
592 678 --- a/d
593 679 +++ b/d
594 680 @@ -1,1 +0,0 @@
595 681 -d
596 682 $ hg ci -m empty
597 683 $ hg export --git tip > empty.diff
598 684 $ hg up -C 0
599 685 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
600 686 $ hg import empty.diff
601 687 applying empty.diff
602 688 $ for name in a b1 b2 c1 c2 d; do
603 689 > echo % $name file
604 690 > test -f $name && cat $name
605 691 > done
606 692 % a file
607 693 a
608 694 % b1 file
609 695 % b2 file
610 696 b
611 697 % c1 file
612 698 c
613 699 % c2 file
614 700 c
615 701 % d file
616 702 $ cd ..
617 703
618 704
619 705 Test importing a patch ending with a binary file removal
620 706
621 707 $ hg init binaryremoval
622 708 $ cd binaryremoval
623 709 $ echo a > a
624 710 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
625 711 $ hg ci -Am addall
626 712 adding a
627 713 adding b
628 714 $ hg rm a
629 715 $ hg rm b
630 716 $ hg st
631 717 R a
632 718 R b
633 719 $ hg ci -m remove
634 720 $ hg export --git . > remove.diff
635 721 $ cat remove.diff | grep git
636 722 diff --git a/a b/a
637 723 diff --git a/b b/b
638 724 $ hg up -C 0
639 725 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
640 726 $ hg import remove.diff
641 727 applying remove.diff
642 728 $ hg manifest
643 729 $ cd ..
644 730
645 731
646 732 Issue927: test update+rename with common name
647 733
648 734 $ hg init t
649 735 $ cd t
650 736 $ touch a
651 737 $ hg ci -Am t
652 738 adding a
653 739 $ echo a > a
654 740
655 741 Here, bfile.startswith(afile)
656 742
657 743 $ hg copy a a2
658 744 $ hg ci -m copya
659 745 $ hg export --git tip > copy.diff
660 746 $ hg up -C 0
661 747 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
662 748 $ hg import copy.diff
663 749 applying copy.diff
664 750
665 751 a should contain an 'a'
666 752
667 753 $ cat a
668 754 a
669 755
670 756 and a2 should have duplicated it
671 757
672 758 $ cat a2
673 759 a
674 760 $ cd ..
675 761
676 762
677 763 test -p0
678 764
679 765 $ hg init p0
680 766 $ cd p0
681 767 $ echo a > a
682 768 $ hg ci -Am t
683 769 adding a
684 770 $ hg import -p foo
685 771 abort: invalid value 'foo' for option -p, expected int
686 772 [255]
687 773 $ hg import -p0 - << EOF
688 774 > foobar
689 775 > --- a Sat Apr 12 22:43:58 2008 -0400
690 776 > +++ a Sat Apr 12 22:44:05 2008 -0400
691 777 > @@ -1,1 +1,1 @@
692 778 > -a
693 779 > +bb
694 780 > EOF
695 781 applying patch from stdin
696 782 $ hg status
697 783 $ cat a
698 784 bb
699 785
700 786 test --prefix
701 787
702 788 $ mkdir -p dir/dir2
703 789 $ echo b > dir/dir2/b
704 790 $ hg ci -Am b
705 791 adding dir/dir2/b
706 792 $ hg import -p2 --prefix dir - << EOF
707 793 > foobar
708 794 > --- drop1/drop2/dir2/b
709 795 > +++ drop1/drop2/dir2/b
710 796 > @@ -1,1 +1,1 @@
711 797 > -b
712 798 > +cc
713 799 > EOF
714 800 applying patch from stdin
715 801 $ hg status
716 802 $ cat dir/dir2/b
717 803 cc
718 804 $ cd ..
719 805
720 806
721 807 test paths outside repo root
722 808
723 809 $ mkdir outside
724 810 $ touch outside/foo
725 811 $ hg init inside
726 812 $ cd inside
727 813 $ hg import - <<EOF
728 814 > diff --git a/a b/b
729 815 > rename from ../outside/foo
730 816 > rename to bar
731 817 > EOF
732 818 applying patch from stdin
733 819 abort: path contains illegal component: ../outside/foo (glob)
734 820 [255]
735 821 $ cd ..
736 822
737 823
738 824 test import with similarity and git and strip (issue295 et al.)
739 825
740 826 $ hg init sim
741 827 $ cd sim
742 828 $ echo 'this is a test' > a
743 829 $ hg ci -Ama
744 830 adding a
745 831 $ cat > ../rename.diff <<EOF
746 832 > diff --git a/foo/a b/foo/a
747 833 > deleted file mode 100644
748 834 > --- a/foo/a
749 835 > +++ /dev/null
750 836 > @@ -1,1 +0,0 @@
751 837 > -this is a test
752 838 > diff --git a/foo/b b/foo/b
753 839 > new file mode 100644
754 840 > --- /dev/null
755 841 > +++ b/foo/b
756 842 > @@ -0,0 +1,2 @@
757 843 > +this is a test
758 844 > +foo
759 845 > EOF
760 846 $ hg import --no-commit -v -s 1 ../rename.diff -p2
761 847 applying ../rename.diff
762 848 patching file a
763 849 patching file b
764 850 adding b
765 851 recording removal of a as rename to b (88% similar)
766 852 applied to working directory
767 853 $ hg st -C
768 854 A b
769 855 a
770 856 R a
771 857 $ hg revert -a
772 858 undeleting a
773 859 forgetting b
774 860 $ rm b
775 861 $ hg import --no-commit -v -s 100 ../rename.diff -p2
776 862 applying ../rename.diff
777 863 patching file a
778 864 patching file b
779 865 adding b
780 866 applied to working directory
781 867 $ hg st -C
782 868 A b
783 869 R a
784 870 $ cd ..
785 871
786 872
787 873 Issue1495: add empty file from the end of patch
788 874
789 875 $ hg init addemptyend
790 876 $ cd addemptyend
791 877 $ touch a
792 878 $ hg addremove
793 879 adding a
794 880 $ hg ci -m "commit"
795 881 $ cat > a.patch <<EOF
796 882 > add a, b
797 883 > diff --git a/a b/a
798 884 > --- a/a
799 885 > +++ b/a
800 886 > @@ -0,0 +1,1 @@
801 887 > +a
802 888 > diff --git a/b b/b
803 889 > new file mode 100644
804 890 > EOF
805 891 $ hg import --no-commit a.patch
806 892 applying a.patch
807 893
808 894 apply a good patch followed by an empty patch (mainly to ensure
809 895 that dirstate is *not* updated when import crashes)
810 896 $ hg update -q -C .
811 897 $ rm b
812 898 $ touch empty.patch
813 899 $ hg import a.patch empty.patch
814 900 applying a.patch
815 901 applying empty.patch
816 902 transaction abort!
817 903 rollback completed
818 904 abort: empty.patch: no diffs found
819 905 [255]
820 906 $ hg tip --template '{rev} {desc|firstline}\n'
821 907 0 commit
822 908 $ hg -q status
823 909 M a
824 910 $ cd ..
825 911
826 912 create file when source is not /dev/null
827 913
828 914 $ cat > create.patch <<EOF
829 915 > diff -Naur proj-orig/foo proj-new/foo
830 916 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
831 917 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
832 918 > @@ -0,0 +1,1 @@
833 919 > +a
834 920 > EOF
835 921
836 922 some people have patches like the following too
837 923
838 924 $ cat > create2.patch <<EOF
839 925 > diff -Naur proj-orig/foo proj-new/foo
840 926 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
841 927 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
842 928 > @@ -0,0 +1,1 @@
843 929 > +a
844 930 > EOF
845 931 $ hg init oddcreate
846 932 $ cd oddcreate
847 933 $ hg import --no-commit ../create.patch
848 934 applying ../create.patch
849 935 $ cat foo
850 936 a
851 937 $ rm foo
852 938 $ hg revert foo
853 939 $ hg import --no-commit ../create2.patch
854 940 applying ../create2.patch
855 941 $ cat foo
856 942 a
857 943
858 944 $ cd ..
859 945
860 946 Issue1859: first line mistaken for email headers
861 947
862 948 $ hg init emailconfusion
863 949 $ cd emailconfusion
864 950 $ cat > a.patch <<EOF
865 951 > module: summary
866 952 >
867 953 > description
868 954 >
869 955 >
870 956 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
871 957 > --- /dev/null
872 958 > +++ b/a
873 959 > @@ -0,0 +1,1 @@
874 960 > +a
875 961 > EOF
876 962 $ hg import -d '0 0' a.patch
877 963 applying a.patch
878 964 $ hg parents -v
879 965 changeset: 0:5a681217c0ad
880 966 tag: tip
881 967 user: test
882 968 date: Thu Jan 01 00:00:00 1970 +0000
883 969 files: a
884 970 description:
885 971 module: summary
886 972
887 973 description
888 974
889 975
890 976 $ cd ..
891 977
892 978
893 979 in commit message
894 980
895 981 $ hg init commitconfusion
896 982 $ cd commitconfusion
897 983 $ cat > a.patch <<EOF
898 984 > module: summary
899 985 >
900 986 > --- description
901 987 >
902 988 > diff --git a/a b/a
903 989 > new file mode 100644
904 990 > --- /dev/null
905 991 > +++ b/a
906 992 > @@ -0,0 +1,1 @@
907 993 > +a
908 994 > EOF
909 995 > hg import -d '0 0' a.patch
910 996 > hg parents -v
911 997 > cd ..
912 998 >
913 999 > echo '% tricky header splitting'
914 1000 > cat > trickyheaders.patch <<EOF
915 1001 > From: User A <user@a>
916 1002 > Subject: [PATCH] from: tricky!
917 1003 >
918 1004 > # HG changeset patch
919 1005 > # User User B
920 1006 > # Date 1266264441 18000
921 1007 > # Branch stable
922 1008 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
923 1009 > # Parent 0000000000000000000000000000000000000000
924 1010 > from: tricky!
925 1011 >
926 1012 > That is not a header.
927 1013 >
928 1014 > diff -r 000000000000 -r f2be6a1170ac foo
929 1015 > --- /dev/null
930 1016 > +++ b/foo
931 1017 > @@ -0,0 +1,1 @@
932 1018 > +foo
933 1019 > EOF
934 1020 applying a.patch
935 1021 changeset: 0:f34d9187897d
936 1022 tag: tip
937 1023 user: test
938 1024 date: Thu Jan 01 00:00:00 1970 +0000
939 1025 files: a
940 1026 description:
941 1027 module: summary
942 1028
943 1029
944 1030 % tricky header splitting
945 1031
946 1032 $ hg init trickyheaders
947 1033 $ cd trickyheaders
948 1034 $ hg import -d '0 0' ../trickyheaders.patch
949 1035 applying ../trickyheaders.patch
950 1036 $ hg export --git tip
951 1037 # HG changeset patch
952 1038 # User User B
953 1039 # Date 0 0
954 1040 # Thu Jan 01 00:00:00 1970 +0000
955 1041 # Node ID eb56ab91903632294ac504838508cb370c0901d2
956 1042 # Parent 0000000000000000000000000000000000000000
957 1043 from: tricky!
958 1044
959 1045 That is not a header.
960 1046
961 1047 diff --git a/foo b/foo
962 1048 new file mode 100644
963 1049 --- /dev/null
964 1050 +++ b/foo
965 1051 @@ -0,0 +1,1 @@
966 1052 +foo
967 1053 $ cd ..
968 1054
969 1055
970 1056 Issue2102: hg export and hg import speak different languages
971 1057
972 1058 $ hg init issue2102
973 1059 $ cd issue2102
974 1060 $ mkdir -p src/cmd/gc
975 1061 $ touch src/cmd/gc/mksys.bash
976 1062 $ hg ci -Am init
977 1063 adding src/cmd/gc/mksys.bash
978 1064 $ hg import - <<EOF
979 1065 > # HG changeset patch
980 1066 > # User Rob Pike
981 1067 > # Date 1216685449 25200
982 1068 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
983 1069 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
984 1070 > help management of empty pkg and lib directories in perforce
985 1071 >
986 1072 > R=gri
987 1073 > DELTA=4 (4 added, 0 deleted, 0 changed)
988 1074 > OCL=13328
989 1075 > CL=13328
990 1076 >
991 1077 > diff --git a/lib/place-holder b/lib/place-holder
992 1078 > new file mode 100644
993 1079 > --- /dev/null
994 1080 > +++ b/lib/place-holder
995 1081 > @@ -0,0 +1,2 @@
996 1082 > +perforce does not maintain empty directories.
997 1083 > +this file helps.
998 1084 > diff --git a/pkg/place-holder b/pkg/place-holder
999 1085 > new file mode 100644
1000 1086 > --- /dev/null
1001 1087 > +++ b/pkg/place-holder
1002 1088 > @@ -0,0 +1,2 @@
1003 1089 > +perforce does not maintain empty directories.
1004 1090 > +this file helps.
1005 1091 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1006 1092 > old mode 100644
1007 1093 > new mode 100755
1008 1094 > EOF
1009 1095 applying patch from stdin
1010 1096
1011 1097 #if execbit
1012 1098
1013 1099 $ hg sum
1014 1100 parent: 1:d59915696727 tip
1015 1101 help management of empty pkg and lib directories in perforce
1016 1102 branch: default
1017 1103 commit: (clean)
1018 1104 update: (current)
1019 1105 phases: 2 draft
1020 1106
1021 1107 $ hg diff --git -c tip
1022 1108 diff --git a/lib/place-holder b/lib/place-holder
1023 1109 new file mode 100644
1024 1110 --- /dev/null
1025 1111 +++ b/lib/place-holder
1026 1112 @@ -0,0 +1,2 @@
1027 1113 +perforce does not maintain empty directories.
1028 1114 +this file helps.
1029 1115 diff --git a/pkg/place-holder b/pkg/place-holder
1030 1116 new file mode 100644
1031 1117 --- /dev/null
1032 1118 +++ b/pkg/place-holder
1033 1119 @@ -0,0 +1,2 @@
1034 1120 +perforce does not maintain empty directories.
1035 1121 +this file helps.
1036 1122 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
1037 1123 old mode 100644
1038 1124 new mode 100755
1039 1125
1040 1126 #else
1041 1127
1042 1128 $ hg sum
1043 1129 parent: 1:28f089cc9ccc tip
1044 1130 help management of empty pkg and lib directories in perforce
1045 1131 branch: default
1046 1132 commit: (clean)
1047 1133 update: (current)
1048 1134 phases: 2 draft
1049 1135
1050 1136 $ hg diff --git -c tip
1051 1137 diff --git a/lib/place-holder b/lib/place-holder
1052 1138 new file mode 100644
1053 1139 --- /dev/null
1054 1140 +++ b/lib/place-holder
1055 1141 @@ -0,0 +1,2 @@
1056 1142 +perforce does not maintain empty directories.
1057 1143 +this file helps.
1058 1144 diff --git a/pkg/place-holder b/pkg/place-holder
1059 1145 new file mode 100644
1060 1146 --- /dev/null
1061 1147 +++ b/pkg/place-holder
1062 1148 @@ -0,0 +1,2 @@
1063 1149 +perforce does not maintain empty directories.
1064 1150 +this file helps.
1065 1151
1066 1152 /* The mode change for mksys.bash is missing here, because on platforms */
1067 1153 /* that don't support execbits, mode changes in patches are ignored when */
1068 1154 /* they are imported. This is obviously also the reason for why the hash */
1069 1155 /* in the created changeset is different to the one you see above the */
1070 1156 /* #else clause */
1071 1157
1072 1158 #endif
1073 1159 $ cd ..
1074 1160
1075 1161
1076 1162 diff lines looking like headers
1077 1163
1078 1164 $ hg init difflineslikeheaders
1079 1165 $ cd difflineslikeheaders
1080 1166 $ echo a >a
1081 1167 $ echo b >b
1082 1168 $ echo c >c
1083 1169 $ hg ci -Am1
1084 1170 adding a
1085 1171 adding b
1086 1172 adding c
1087 1173
1088 1174 $ echo "key: value" >>a
1089 1175 $ echo "key: value" >>b
1090 1176 $ echo "foo" >>c
1091 1177 $ hg ci -m2
1092 1178
1093 1179 $ hg up -C 0
1094 1180 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1095 1181 $ hg diff --git -c1 >want
1096 1182 $ hg diff -c1 | hg import --no-commit -
1097 1183 applying patch from stdin
1098 1184 $ hg diff --git >have
1099 1185 $ diff want have
1100 1186 $ cd ..
1101 1187
1102 1188 import a unified diff with no lines of context (diff -U0)
1103 1189
1104 1190 $ hg init diffzero
1105 1191 $ cd diffzero
1106 1192 $ cat > f << EOF
1107 1193 > c2
1108 1194 > c4
1109 1195 > c5
1110 1196 > EOF
1111 1197 $ hg commit -Am0
1112 1198 adding f
1113 1199
1114 1200 $ hg import --no-commit - << EOF
1115 1201 > # HG changeset patch
1116 1202 > # User test
1117 1203 > # Date 0 0
1118 1204 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1119 1205 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1120 1206 > 1
1121 1207 > diff -r 8679a12a975b -r f4974ab632f3 f
1122 1208 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1123 1209 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1124 1210 > @@ -0,0 +1,1 @@
1125 1211 > +c1
1126 1212 > @@ -1,0 +3,1 @@
1127 1213 > +c3
1128 1214 > @@ -3,1 +4,0 @@
1129 1215 > -c5
1130 1216 > EOF
1131 1217 applying patch from stdin
1132 1218
1133 1219 $ cat f
1134 1220 c1
1135 1221 c2
1136 1222 c3
1137 1223 c4
1138 1224
1139 1225 $ cd ..
1140 1226
1141 1227 no segfault while importing a unified diff which start line is zero but chunk
1142 1228 size is non-zero
1143 1229
1144 1230 $ hg init startlinezero
1145 1231 $ cd startlinezero
1146 1232 $ echo foo > foo
1147 1233 $ hg commit -Amfoo
1148 1234 adding foo
1149 1235
1150 1236 $ hg import --no-commit - << EOF
1151 1237 > diff a/foo b/foo
1152 1238 > --- a/foo
1153 1239 > +++ b/foo
1154 1240 > @@ -0,1 +0,1 @@
1155 1241 > foo
1156 1242 > EOF
1157 1243 applying patch from stdin
1158 1244
1159 1245 $ cd ..
1160 1246
1161 1247 Test corner case involving fuzz and skew
1162 1248
1163 1249 $ hg init morecornercases
1164 1250 $ cd morecornercases
1165 1251
1166 1252 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1167 1253 > diff --git a/a b/a
1168 1254 > --- a/a
1169 1255 > +++ b/a
1170 1256 > @@ -1,0 +1,1 @@
1171 1257 > +line
1172 1258 > EOF
1173 1259
1174 1260 $ cat > 02-no-context-middle-of-file.diff <<EOF
1175 1261 > diff --git a/a b/a
1176 1262 > --- a/a
1177 1263 > +++ b/a
1178 1264 > @@ -1,1 +1,1 @@
1179 1265 > -2
1180 1266 > +add some skew
1181 1267 > @@ -2,0 +2,1 @@
1182 1268 > +line
1183 1269 > EOF
1184 1270
1185 1271 $ cat > 03-no-context-end-of-file.diff <<EOF
1186 1272 > diff --git a/a b/a
1187 1273 > --- a/a
1188 1274 > +++ b/a
1189 1275 > @@ -10,0 +10,1 @@
1190 1276 > +line
1191 1277 > EOF
1192 1278
1193 1279 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1194 1280 > diff --git a/a b/a
1195 1281 > --- a/a
1196 1282 > +++ b/a
1197 1283 > @@ -1,1 +1,1 @@
1198 1284 > -2
1199 1285 > +add some skew
1200 1286 > @@ -2,2 +2,3 @@
1201 1287 > not matching, should fuzz
1202 1288 > ... a bit
1203 1289 > +line
1204 1290 > EOF
1205 1291
1206 1292 $ cat > a <<EOF
1207 1293 > 1
1208 1294 > 2
1209 1295 > 3
1210 1296 > 4
1211 1297 > EOF
1212 1298 $ hg ci -Am adda a
1213 1299 $ for p in *.diff; do
1214 1300 > hg import -v --no-commit $p
1215 1301 > cat a
1216 1302 > hg revert -aqC a
1217 1303 > # patch -p1 < $p
1218 1304 > # cat a
1219 1305 > # hg revert -aC a
1220 1306 > done
1221 1307 applying 01-no-context-beginning-of-file.diff
1222 1308 patching file a
1223 1309 applied to working directory
1224 1310 1
1225 1311 line
1226 1312 2
1227 1313 3
1228 1314 4
1229 1315 applying 02-no-context-middle-of-file.diff
1230 1316 patching file a
1231 1317 Hunk #1 succeeded at 2 (offset 1 lines).
1232 1318 Hunk #2 succeeded at 4 (offset 1 lines).
1233 1319 applied to working directory
1234 1320 1
1235 1321 add some skew
1236 1322 3
1237 1323 line
1238 1324 4
1239 1325 applying 03-no-context-end-of-file.diff
1240 1326 patching file a
1241 1327 Hunk #1 succeeded at 5 (offset -6 lines).
1242 1328 applied to working directory
1243 1329 1
1244 1330 2
1245 1331 3
1246 1332 4
1247 1333 line
1248 1334 applying 04-middle-of-file-completely-fuzzed.diff
1249 1335 patching file a
1250 1336 Hunk #1 succeeded at 2 (offset 1 lines).
1251 1337 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1252 1338 applied to working directory
1253 1339 1
1254 1340 add some skew
1255 1341 3
1256 1342 4
1257 1343 line
1258 1344 $ cd ..
1259 1345
1260 1346 Test partial application
1261 1347 ------------------------
1262 1348
1263 1349 prepare a stack of patches depending on each other
1264 1350
1265 1351 $ hg init partial
1266 1352 $ cd partial
1267 1353 $ cat << EOF > a
1268 1354 > one
1269 1355 > two
1270 1356 > three
1271 1357 > four
1272 1358 > five
1273 1359 > six
1274 1360 > seven
1275 1361 > EOF
1276 1362 $ hg add a
1277 1363 $ echo 'b' > b
1278 1364 $ hg add b
1279 1365 $ hg commit -m 'initial' -u Babar
1280 1366 $ cat << EOF > a
1281 1367 > one
1282 1368 > two
1283 1369 > 3
1284 1370 > four
1285 1371 > five
1286 1372 > six
1287 1373 > seven
1288 1374 > EOF
1289 1375 $ hg commit -m 'three' -u Celeste
1290 1376 $ cat << EOF > a
1291 1377 > one
1292 1378 > two
1293 1379 > 3
1294 1380 > 4
1295 1381 > five
1296 1382 > six
1297 1383 > seven
1298 1384 > EOF
1299 1385 $ hg commit -m 'four' -u Rataxes
1300 1386 $ cat << EOF > a
1301 1387 > one
1302 1388 > two
1303 1389 > 3
1304 1390 > 4
1305 1391 > 5
1306 1392 > six
1307 1393 > seven
1308 1394 > EOF
1309 1395 $ echo bb >> b
1310 1396 $ hg commit -m 'five' -u Arthur
1311 1397 $ echo 'Babar' > jungle
1312 1398 $ hg add jungle
1313 1399 $ hg ci -m 'jungle' -u Zephir
1314 1400 $ echo 'Celeste' >> jungle
1315 1401 $ hg ci -m 'extended jungle' -u Cornelius
1316 1402 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1317 1403 @ extended jungle [Cornelius] 1: +1/-0
1318 1404 |
1319 1405 o jungle [Zephir] 1: +1/-0
1320 1406 |
1321 1407 o five [Arthur] 2: +2/-1
1322 1408 |
1323 1409 o four [Rataxes] 1: +1/-1
1324 1410 |
1325 1411 o three [Celeste] 1: +1/-1
1326 1412 |
1327 1413 o initial [Babar] 2: +8/-0
1328 1414
1329 1415
1330 1416 Importing with some success and some errors:
1331 1417
1332 1418 $ hg update --rev 'desc(initial)'
1333 1419 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1334 1420 $ hg export --rev 'desc(five)' | hg import --partial -
1335 1421 applying patch from stdin
1336 1422 patching file a
1337 1423 Hunk #1 FAILED at 1
1338 1424 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1339 1425 patch applied partially
1340 1426 (fix the .rej files and run `hg commit --amend`)
1341 1427 [1]
1342 1428
1343 1429 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1344 1430 @ five [Arthur] 1: +1/-0
1345 1431 |
1346 1432 | o extended jungle [Cornelius] 1: +1/-0
1347 1433 | |
1348 1434 | o jungle [Zephir] 1: +1/-0
1349 1435 | |
1350 1436 | o five [Arthur] 2: +2/-1
1351 1437 | |
1352 1438 | o four [Rataxes] 1: +1/-1
1353 1439 | |
1354 1440 | o three [Celeste] 1: +1/-1
1355 1441 |/
1356 1442 o initial [Babar] 2: +8/-0
1357 1443
1358 1444 $ hg export
1359 1445 # HG changeset patch
1360 1446 # User Arthur
1361 1447 # Date 0 0
1362 1448 # Thu Jan 01 00:00:00 1970 +0000
1363 1449 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1364 1450 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1365 1451 five
1366 1452
1367 1453 diff -r 8e4f0351909e -r 26e6446bb252 b
1368 1454 --- a/b Thu Jan 01 00:00:00 1970 +0000
1369 1455 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1370 1456 @@ -1,1 +1,2 @@
1371 1457 b
1372 1458 +bb
1373 1459 $ hg status -c .
1374 1460 C a
1375 1461 C b
1376 1462 $ ls
1377 1463 a
1378 1464 a.rej
1379 1465 b
1380 1466
1381 1467 Importing with zero success:
1382 1468
1383 1469 $ hg update --rev 'desc(initial)'
1384 1470 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1385 1471 $ hg export --rev 'desc(four)' | hg import --partial -
1386 1472 applying patch from stdin
1387 1473 patching file a
1388 1474 Hunk #1 FAILED at 0
1389 1475 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1390 1476 patch applied partially
1391 1477 (fix the .rej files and run `hg commit --amend`)
1392 1478 [1]
1393 1479
1394 1480 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1395 1481 @ four [Rataxes] 0: +0/-0
1396 1482 |
1397 1483 | o five [Arthur] 1: +1/-0
1398 1484 |/
1399 1485 | o extended jungle [Cornelius] 1: +1/-0
1400 1486 | |
1401 1487 | o jungle [Zephir] 1: +1/-0
1402 1488 | |
1403 1489 | o five [Arthur] 2: +2/-1
1404 1490 | |
1405 1491 | o four [Rataxes] 1: +1/-1
1406 1492 | |
1407 1493 | o three [Celeste] 1: +1/-1
1408 1494 |/
1409 1495 o initial [Babar] 2: +8/-0
1410 1496
1411 1497 $ hg export
1412 1498 # HG changeset patch
1413 1499 # User Rataxes
1414 1500 # Date 0 0
1415 1501 # Thu Jan 01 00:00:00 1970 +0000
1416 1502 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1417 1503 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1418 1504 four
1419 1505
1420 1506 $ hg status -c .
1421 1507 C a
1422 1508 C b
1423 1509 $ ls
1424 1510 a
1425 1511 a.rej
1426 1512 b
1427 1513
1428 1514 Importing with unknown file:
1429 1515
1430 1516 $ hg update --rev 'desc(initial)'
1431 1517 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1432 1518 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1433 1519 applying patch from stdin
1434 1520 unable to find 'jungle' for patching
1435 1521 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1436 1522 patch applied partially
1437 1523 (fix the .rej files and run `hg commit --amend`)
1438 1524 [1]
1439 1525
1440 1526 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1441 1527 @ extended jungle [Cornelius] 0: +0/-0
1442 1528 |
1443 1529 | o four [Rataxes] 0: +0/-0
1444 1530 |/
1445 1531 | o five [Arthur] 1: +1/-0
1446 1532 |/
1447 1533 | o extended jungle [Cornelius] 1: +1/-0
1448 1534 | |
1449 1535 | o jungle [Zephir] 1: +1/-0
1450 1536 | |
1451 1537 | o five [Arthur] 2: +2/-1
1452 1538 | |
1453 1539 | o four [Rataxes] 1: +1/-1
1454 1540 | |
1455 1541 | o three [Celeste] 1: +1/-1
1456 1542 |/
1457 1543 o initial [Babar] 2: +8/-0
1458 1544
1459 1545 $ hg export
1460 1546 # HG changeset patch
1461 1547 # User Cornelius
1462 1548 # Date 0 0
1463 1549 # Thu Jan 01 00:00:00 1970 +0000
1464 1550 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1465 1551 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1466 1552 extended jungle
1467 1553
1468 1554 $ hg status -c .
1469 1555 C a
1470 1556 C b
1471 1557 $ ls
1472 1558 a
1473 1559 a.rej
1474 1560 b
1475 1561 jungle.rej
1476 1562
1477 1563 Importing multiple failing patches:
1478 1564
1479 1565 $ hg update --rev 'desc(initial)'
1480 1566 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1481 1567 $ echo 'B' > b # just to make another commit
1482 1568 $ hg commit -m "a new base"
1483 1569 created new head
1484 1570 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1485 1571 applying patch from stdin
1486 1572 patching file a
1487 1573 Hunk #1 FAILED at 0
1488 1574 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1489 1575 patch applied partially
1490 1576 (fix the .rej files and run `hg commit --amend`)
1491 1577 [1]
1492 1578 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1493 1579 @ four [Rataxes] 0: +0/-0
1494 1580 |
1495 1581 o a new base [test] 1: +1/-1
1496 1582 |
1497 1583 | o extended jungle [Cornelius] 0: +0/-0
1498 1584 |/
1499 1585 | o four [Rataxes] 0: +0/-0
1500 1586 |/
1501 1587 | o five [Arthur] 1: +1/-0
1502 1588 |/
1503 1589 | o extended jungle [Cornelius] 1: +1/-0
1504 1590 | |
1505 1591 | o jungle [Zephir] 1: +1/-0
1506 1592 | |
1507 1593 | o five [Arthur] 2: +2/-1
1508 1594 | |
1509 1595 | o four [Rataxes] 1: +1/-1
1510 1596 | |
1511 1597 | o three [Celeste] 1: +1/-1
1512 1598 |/
1513 1599 o initial [Babar] 2: +8/-0
1514 1600
1515 1601 $ hg export
1516 1602 # HG changeset patch
1517 1603 # User Rataxes
1518 1604 # Date 0 0
1519 1605 # Thu Jan 01 00:00:00 1970 +0000
1520 1606 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1521 1607 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1522 1608 four
1523 1609
1524 1610 $ hg status -c .
1525 1611 C a
1526 1612 C b
1527 1613
1528 1614 Importing some extra header
1529 1615 ===========================
1530 1616
1531 1617 $ cat > $TESTTMP/parseextra.py <<EOF
1532 1618 > import mercurial.patch
1533 1619 > import mercurial.cmdutil
1534 1620 >
1535 1621 > def processfoo(repo, data, extra, opts):
1536 1622 > if 'foo' in data:
1537 1623 > extra['foo'] = data['foo']
1538 1624 > def postimport(ctx):
1539 1625 > if 'foo' in ctx.extra():
1540 1626 > ctx.repo().ui.write('imported-foo: %s\n' % ctx.extra()['foo'])
1541 1627 >
1542 1628 > mercurial.patch.patchheadermap.append(('Foo', 'foo'))
1543 1629 > mercurial.cmdutil.extrapreimport.append('foo')
1544 1630 > mercurial.cmdutil.extrapreimportmap['foo'] = processfoo
1545 1631 > mercurial.cmdutil.extrapostimport.append('foo')
1546 1632 > mercurial.cmdutil.extrapostimportmap['foo'] = postimport
1547 1633 > EOF
1548 1634 $ printf "[extensions]\nparseextra=$TESTTMP/parseextra.py" >> $HGRCPATH
1549 1635 $ hg up -C tip
1550 1636 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1551 1637 $ cat > $TESTTMP/foo.patch <<EOF
1552 1638 > # HG changeset patch
1553 1639 > # User Rataxes
1554 1640 > # Date 0 0
1555 1641 > # Thu Jan 01 00:00:00 1970 +0000
1556 1642 > # Foo bar
1557 1643 > height
1558 1644 >
1559 1645 > --- a/a Thu Jan 01 00:00:00 1970 +0000
1560 1646 > +++ b/a Wed Oct 07 09:17:44 2015 +0000
1561 1647 > @@ -5,3 +5,4 @@
1562 1648 > five
1563 1649 > six
1564 1650 > seven
1565 1651 > +heigt
1566 1652 > EOF
1567 1653 $ hg import $TESTTMP/foo.patch
1568 1654 applying $TESTTMP/foo.patch
1569 1655 imported-foo: bar
1570 1656 $ hg log --debug -r . | grep extra
1571 1657 extra: branch=default
1572 1658 extra: foo=bar
@@ -1,193 +1,244 b''
1 1 Environment setup for MQ
2 2
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "mq=" >> $HGRCPATH
5 $ cat >> $HGRCPATH <<EOF
6 > [defaults]
7 > # explicit date to commit with fixed hashid
8 > qnew = -d "0 0"
9 > qrefresh = -d "0 0"
10 > qfold = -d "0 0"
11 > EOF
5 12 $ hg init
6 13 $ hg qinit
7 14
8 15 Should fail if no patches applied
9 16 (this tests also that editor is not invoked if '--edit' is not
10 17 specified)
11 18
12 19 $ hg qrefresh
13 20 no patches applied
14 21 [1]
15 22 $ hg qrefresh -e
16 23 no patches applied
17 24 [1]
18 25 $ hg qnew -m "First commit message" first-patch
19 26 $ echo aaaa > file
20 27 $ hg add file
21 28 $ HGEDITOR=cat hg qrefresh
22 29
23 30 Should display 'First commit message'
24 31
25 32 $ hg log -l1 --template "{desc}\n"
26 33 First commit message
27 34
28 35 Testing changing message with -m
29 36 (this tests also that '--edit' can be used with '--message', and
30 37 that '[committemplate] changeset' definition and commit log specific
31 38 template keyword 'extramsg' work well)
32 39
33 40 $ cat >> .hg/hgrc <<EOF
34 41 > [committemplate]
35 42 > listupfiles = {file_adds %
36 43 > "HG: added {file}\n" }{file_mods %
37 44 > "HG: changed {file}\n" }{file_dels %
38 45 > "HG: removed {file}\n" }{if(files, "",
39 46 > "HG: no files changed\n")}
40 47 >
41 48 > changeset = HG: this is customized commit template
42 49 > {desc}\n\n
43 50 > HG: Enter commit message. Lines beginning with 'HG:' are removed.
44 51 > HG: {extramsg}
45 52 > HG: --
46 53 > HG: user: {author}
47 54 > HG: branch '{branch}'\n{listupfiles}
48 55 > EOF
49 56
50 57 $ echo bbbb > file
51 58 $ HGEDITOR=cat hg qrefresh -m "Second commit message" -e
52 59 HG: this is customized commit template
53 60 Second commit message
54 61
55 62
56 63 HG: Enter commit message. Lines beginning with 'HG:' are removed.
57 64 HG: Leave message empty to use default message.
58 65 HG: --
59 66 HG: user: test
60 67 HG: branch 'default'
61 68 HG: added file
62 69
63 70 $ cat >> .hg/hgrc <<EOF
64 71 > # disable customizing for subsequent tests
65 72 > [committemplate]
66 73 > changeset =
67 74 > EOF
68 75
69 76 Should display 'Second commit message'
70 77
71 78 $ hg log -l1 --template "{desc}\n"
72 79 Second commit message
73 80
74 81 Testing changing message with -l
75 82
76 83 $ echo "Third commit message" > logfile
77 84 $ echo " This is the 3rd log message" >> logfile
78 85 $ echo bbbb > file
79 86 $ hg qrefresh -l logfile
80 87
81 88 Should display 'Third commit message\\\n This is the 3rd log message'
82 89
83 90 $ hg log -l1 --template "{desc}\n"
84 91 Third commit message
85 92 This is the 3rd log message
86 93
87 94 Testing changing message with -l-
88 95
89 96 $ hg qnew -m "First commit message" second-patch
90 97 $ echo aaaa > file2
91 98 $ hg add file2
92 99 $ echo bbbb > file2
93 100 $ (echo "Fifth commit message"; echo " This is the 5th log message") | hg qrefresh -l-
94 101
95 102 Should display 'Fifth commit message\\\n This is the 5th log message'
96 103
97 104 $ hg log -l1 --template "{desc}\n"
98 105 Fifth commit message
99 106 This is the 5th log message
100 107
101 108 Test saving last-message.txt:
102 109
103 110 $ cat > $TESTTMP/editor.sh << EOF
104 111 > echo "==== before editing"
105 112 > cat \$1
106 113 > echo "===="
107 114 > (echo; echo "test saving last-message.txt") >> \$1
108 115 > EOF
109 116
110 117 $ cat > $TESTTMP/commitfailure.py <<EOF
111 118 > from mercurial import error
112 119 > def reposetup(ui, repo):
113 120 > class commitfailure(repo.__class__):
114 121 > def commit(self, *args, **kwargs):
115 122 > raise error.Abort('emulating unexpected abort')
116 123 > repo.__class__ = commitfailure
117 124 > EOF
118 125
119 126 $ cat >> .hg/hgrc <<EOF
120 127 > [extensions]
121 128 > # this failure occurs before editor invocation
122 129 > commitfailure = $TESTTMP/commitfailure.py
123 130 > EOF
124 131
125 132 $ hg qapplied
126 133 first-patch
127 134 second-patch
128 135 $ hg tip --template "{files}\n"
129 136 file2
130 137
131 138 (test that editor is not invoked before transaction starting)
132 139
133 140 $ rm -f .hg/last-message.txt
134 141 $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
135 142 refresh interrupted while patch was popped! (revert --all, qpush to recover)
136 143 abort: emulating unexpected abort
137 144 [255]
138 145 $ test -f .hg/last-message.txt
139 146 [1]
140 147
141 148 (reset applied patches and directory status)
142 149
143 150 $ cat >> .hg/hgrc <<EOF
144 151 > [extensions]
145 152 > commitfailure = !
146 153 > EOF
147 154
148 155 $ hg qapplied
149 156 first-patch
150 157 $ hg status -A file2
151 158 ? file2
152 159 $ rm file2
153 160 $ hg qpush -q second-patch
154 161 now at: second-patch
155 162
156 163 (test that editor is invoked and commit message is saved into
157 164 "last-message.txt")
158 165
159 166 $ cat >> .hg/hgrc <<EOF
160 167 > [hooks]
161 168 > # this failure occurs after editor invocation
162 169 > pretxncommit.unexpectedabort = false
163 170 > EOF
164 171
165 172 $ rm -f .hg/last-message.txt
166 173 $ hg status --rev "second-patch^1" -arm
167 174 A file2
168 175 $ HGEDITOR="sh $TESTTMP/editor.sh" hg qrefresh -e
169 176 ==== before editing
170 177 Fifth commit message
171 178 This is the 5th log message
172 179
173 180
174 181 HG: Enter commit message. Lines beginning with 'HG:' are removed.
175 182 HG: Leave message empty to use default message.
176 183 HG: --
177 184 HG: user: test
178 185 HG: branch 'default'
179 186 HG: added file2
180 187 ====
181 188 transaction abort!
182 189 rollback completed
183 190 note: commit message saved in .hg/last-message.txt
184 191 refresh interrupted while patch was popped! (revert --all, qpush to recover)
185 192 abort: pretxncommit.unexpectedabort hook exited with status 1
186 193 [255]
187 194 $ cat .hg/last-message.txt
188 195 Fifth commit message
189 196 This is the 5th log message
190 197
191 198
192 199
193 200 test saving last-message.txt
201
202 Test visibility of in-memory distate changes outside transaction to
203 external process
204
205 $ cat > $TESTTMP/checkvisibility.sh <<EOF
206 > echo "===="
207 > hg parents --template "{rev}:{node|short}\n"
208 > hg status -arm
209 > echo "===="
210 > EOF
211
212 == test visibility to external editor
213
214 $ hg update -C -q first-patch
215 $ rm -f file2
216 $ hg qpush -q second-patch --config hooks.pretxncommit.unexpectedabort=
217 now at: second-patch
218 $ echo bbbb >> file2
219
220 $ sh "$TESTTMP/checkvisibility.sh"
221 ====
222 1:e30108269082
223 M file2
224 ====
225
226 $ HGEDITOR='sh "$TESTTMP/checkvisibility.sh"' hg qrefresh -e
227 ====
228 0:25e397dabed2
229 A file2
230 ====
231 transaction abort!
232 rollback completed
233 note: commit message saved in .hg/last-message.txt
234 refresh interrupted while patch was popped! (revert --all, qpush to recover)
235 abort: pretxncommit.unexpectedabort hook exited with status 1
236 [255]
237
238 (rebuilding at failure of qrefresh bases on rev #0, and it causes
239 dropping status of "file2")
240
241 $ sh "$TESTTMP/checkvisibility.sh"
242 ====
243 0:25e397dabed2
244 ====
General Comments 0
You need to be logged in to leave comments. Login now