##// END OF EJS Templates
changeset_printer: use node.wdirrev to calculate meaningful parentrevs...
Yuya Nishihara -
r25739:3dabc9b7 default
parent child Browse files
Show More
@@ -1,3326 +1,3326 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, 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 context, 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 raise util.Abort(_('running non-interactively, use %s instead') %
85 85 cmdsuggest)
86 86
87 87 # make sure username is set before going interactive
88 88 if not opts.get('user'):
89 89 ui.username() # raise exception, username not provided
90 90
91 91 def recordfunc(ui, repo, message, match, opts):
92 92 """This is generic record driver.
93 93
94 94 Its job is to interactively filter local changes, and
95 95 accordingly prepare working directory into a state in which the
96 96 job can be delegated to a non-interactive commit command such as
97 97 'commit' or 'qrefresh'.
98 98
99 99 After the actual job is done by non-interactive command, the
100 100 working directory is restored to its original state.
101 101
102 102 In the end we'll record interesting changes, and everything else
103 103 will be left in place, so the user can continue working.
104 104 """
105 105
106 106 checkunfinished(repo, commit=True)
107 107 merge = len(repo[None].parents()) > 1
108 108 if merge:
109 109 raise util.Abort(_('cannot partially commit a merge '
110 110 '(use "hg commit" instead)'))
111 111
112 112 status = repo.status(match=match)
113 113 diffopts = patch.difffeatureopts(ui, opts=opts, whitespace=True)
114 114 diffopts.nodates = True
115 115 diffopts.git = True
116 116 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
117 117 originalchunks = patch.parsepatch(originaldiff)
118 118
119 119 # 1. filter patch, so we have intending-to apply subset of it
120 120 try:
121 121 chunks = filterfn(ui, originalchunks)
122 122 except patch.PatchError as err:
123 123 raise util.Abort(_('error parsing patch: %s') % err)
124 124
125 125 # We need to keep a backup of files that have been newly added and
126 126 # modified during the recording process because there is a previous
127 127 # version without the edit in the workdir
128 128 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
129 129 contenders = set()
130 130 for h in chunks:
131 131 try:
132 132 contenders.update(set(h.files()))
133 133 except AttributeError:
134 134 pass
135 135
136 136 changed = status.modified + status.added + status.removed
137 137 newfiles = [f for f in changed if f in contenders]
138 138 if not newfiles:
139 139 ui.status(_('no changes to record\n'))
140 140 return 0
141 141
142 142 modified = set(status.modified)
143 143
144 144 # 2. backup changed files, so we can restore them in the end
145 145
146 146 if backupall:
147 147 tobackup = changed
148 148 else:
149 149 tobackup = [f for f in newfiles if f in modified or f in \
150 150 newlyaddedandmodifiedfiles]
151 151 backups = {}
152 152 if tobackup:
153 153 backupdir = repo.join('record-backups')
154 154 try:
155 155 os.mkdir(backupdir)
156 156 except OSError as err:
157 157 if err.errno != errno.EEXIST:
158 158 raise
159 159 try:
160 160 # backup continues
161 161 for f in tobackup:
162 162 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
163 163 dir=backupdir)
164 164 os.close(fd)
165 165 ui.debug('backup %r as %r\n' % (f, tmpname))
166 166 util.copyfile(repo.wjoin(f), tmpname)
167 167 shutil.copystat(repo.wjoin(f), tmpname)
168 168 backups[f] = tmpname
169 169
170 170 fp = cStringIO.StringIO()
171 171 for c in chunks:
172 172 fname = c.filename()
173 173 if fname in backups:
174 174 c.write(fp)
175 175 dopatch = fp.tell()
176 176 fp.seek(0)
177 177
178 178 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
179 179 # 3a. apply filtered patch to clean repo (clean)
180 180 if backups:
181 181 # Equivalent to hg.revert
182 182 choices = lambda key: key in backups
183 183 mergemod.update(repo, repo.dirstate.p1(),
184 184 False, True, choices)
185 185
186 186 # 3b. (apply)
187 187 if dopatch:
188 188 try:
189 189 ui.debug('applying patch\n')
190 190 ui.debug(fp.getvalue())
191 191 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
192 192 except patch.PatchError as err:
193 193 raise util.Abort(str(err))
194 194 del fp
195 195
196 196 # 4. We prepared working directory according to filtered
197 197 # patch. Now is the time to delegate the job to
198 198 # commit/qrefresh or the like!
199 199
200 200 # Make all of the pathnames absolute.
201 201 newfiles = [repo.wjoin(nf) for nf in newfiles]
202 202 return commitfunc(ui, repo, *newfiles, **opts)
203 203 finally:
204 204 # 5. finally restore backed-up files
205 205 try:
206 206 for realname, tmpname in backups.iteritems():
207 207 ui.debug('restoring %r to %r\n' % (tmpname, realname))
208 208 util.copyfile(tmpname, repo.wjoin(realname))
209 209 # Our calls to copystat() here and above are a
210 210 # hack to trick any editors that have f open that
211 211 # we haven't modified them.
212 212 #
213 213 # Also note that this racy as an editor could
214 214 # notice the file's mtime before we've finished
215 215 # writing it.
216 216 shutil.copystat(tmpname, repo.wjoin(realname))
217 217 os.unlink(tmpname)
218 218 if tobackup:
219 219 os.rmdir(backupdir)
220 220 except OSError:
221 221 pass
222 222
223 223 return commit(ui, repo, recordfunc, pats, opts)
224 224
225 225 def findpossible(cmd, table, strict=False):
226 226 """
227 227 Return cmd -> (aliases, command table entry)
228 228 for each matching command.
229 229 Return debug commands (or their aliases) only if no normal command matches.
230 230 """
231 231 choice = {}
232 232 debugchoice = {}
233 233
234 234 if cmd in table:
235 235 # short-circuit exact matches, "log" alias beats "^log|history"
236 236 keys = [cmd]
237 237 else:
238 238 keys = table.keys()
239 239
240 240 allcmds = []
241 241 for e in keys:
242 242 aliases = parsealiases(e)
243 243 allcmds.extend(aliases)
244 244 found = None
245 245 if cmd in aliases:
246 246 found = cmd
247 247 elif not strict:
248 248 for a in aliases:
249 249 if a.startswith(cmd):
250 250 found = a
251 251 break
252 252 if found is not None:
253 253 if aliases[0].startswith("debug") or found.startswith("debug"):
254 254 debugchoice[found] = (aliases, table[e])
255 255 else:
256 256 choice[found] = (aliases, table[e])
257 257
258 258 if not choice and debugchoice:
259 259 choice = debugchoice
260 260
261 261 return choice, allcmds
262 262
263 263 def findcmd(cmd, table, strict=True):
264 264 """Return (aliases, command table entry) for command string."""
265 265 choice, allcmds = findpossible(cmd, table, strict)
266 266
267 267 if cmd in choice:
268 268 return choice[cmd]
269 269
270 270 if len(choice) > 1:
271 271 clist = choice.keys()
272 272 clist.sort()
273 273 raise error.AmbiguousCommand(cmd, clist)
274 274
275 275 if choice:
276 276 return choice.values()[0]
277 277
278 278 raise error.UnknownCommand(cmd, allcmds)
279 279
280 280 def findrepo(p):
281 281 while not os.path.isdir(os.path.join(p, ".hg")):
282 282 oldp, p = p, os.path.dirname(p)
283 283 if p == oldp:
284 284 return None
285 285
286 286 return p
287 287
288 288 def bailifchanged(repo, merge=True):
289 289 if merge and repo.dirstate.p2() != nullid:
290 290 raise util.Abort(_('outstanding uncommitted merge'))
291 291 modified, added, removed, deleted = repo.status()[:4]
292 292 if modified or added or removed or deleted:
293 293 raise util.Abort(_('uncommitted changes'))
294 294 ctx = repo[None]
295 295 for s in sorted(ctx.substate):
296 296 ctx.sub(s).bailifchanged()
297 297
298 298 def logmessage(ui, opts):
299 299 """ get the log message according to -m and -l option """
300 300 message = opts.get('message')
301 301 logfile = opts.get('logfile')
302 302
303 303 if message and logfile:
304 304 raise util.Abort(_('options --message and --logfile are mutually '
305 305 'exclusive'))
306 306 if not message and logfile:
307 307 try:
308 308 if logfile == '-':
309 309 message = ui.fin.read()
310 310 else:
311 311 message = '\n'.join(util.readfile(logfile).splitlines())
312 312 except IOError as inst:
313 313 raise util.Abort(_("can't read commit message '%s': %s") %
314 314 (logfile, inst.strerror))
315 315 return message
316 316
317 317 def mergeeditform(ctxorbool, baseformname):
318 318 """return appropriate editform name (referencing a committemplate)
319 319
320 320 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
321 321 merging is committed.
322 322
323 323 This returns baseformname with '.merge' appended if it is a merge,
324 324 otherwise '.normal' is appended.
325 325 """
326 326 if isinstance(ctxorbool, bool):
327 327 if ctxorbool:
328 328 return baseformname + ".merge"
329 329 elif 1 < len(ctxorbool.parents()):
330 330 return baseformname + ".merge"
331 331
332 332 return baseformname + ".normal"
333 333
334 334 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
335 335 editform='', **opts):
336 336 """get appropriate commit message editor according to '--edit' option
337 337
338 338 'finishdesc' is a function to be called with edited commit message
339 339 (= 'description' of the new changeset) just after editing, but
340 340 before checking empty-ness. It should return actual text to be
341 341 stored into history. This allows to change description before
342 342 storing.
343 343
344 344 'extramsg' is a extra message to be shown in the editor instead of
345 345 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
346 346 is automatically added.
347 347
348 348 'editform' is a dot-separated list of names, to distinguish
349 349 the purpose of commit text editing.
350 350
351 351 'getcommiteditor' returns 'commitforceeditor' regardless of
352 352 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
353 353 they are specific for usage in MQ.
354 354 """
355 355 if edit or finishdesc or extramsg:
356 356 return lambda r, c, s: commitforceeditor(r, c, s,
357 357 finishdesc=finishdesc,
358 358 extramsg=extramsg,
359 359 editform=editform)
360 360 elif editform:
361 361 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
362 362 else:
363 363 return commiteditor
364 364
365 365 def loglimit(opts):
366 366 """get the log limit according to option -l/--limit"""
367 367 limit = opts.get('limit')
368 368 if limit:
369 369 try:
370 370 limit = int(limit)
371 371 except ValueError:
372 372 raise util.Abort(_('limit must be a positive integer'))
373 373 if limit <= 0:
374 374 raise util.Abort(_('limit must be positive'))
375 375 else:
376 376 limit = None
377 377 return limit
378 378
379 379 def makefilename(repo, pat, node, desc=None,
380 380 total=None, seqno=None, revwidth=None, pathname=None):
381 381 node_expander = {
382 382 'H': lambda: hex(node),
383 383 'R': lambda: str(repo.changelog.rev(node)),
384 384 'h': lambda: short(node),
385 385 'm': lambda: re.sub('[^\w]', '_', str(desc))
386 386 }
387 387 expander = {
388 388 '%': lambda: '%',
389 389 'b': lambda: os.path.basename(repo.root),
390 390 }
391 391
392 392 try:
393 393 if node:
394 394 expander.update(node_expander)
395 395 if node:
396 396 expander['r'] = (lambda:
397 397 str(repo.changelog.rev(node)).zfill(revwidth or 0))
398 398 if total is not None:
399 399 expander['N'] = lambda: str(total)
400 400 if seqno is not None:
401 401 expander['n'] = lambda: str(seqno)
402 402 if total is not None and seqno is not None:
403 403 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
404 404 if pathname is not None:
405 405 expander['s'] = lambda: os.path.basename(pathname)
406 406 expander['d'] = lambda: os.path.dirname(pathname) or '.'
407 407 expander['p'] = lambda: pathname
408 408
409 409 newname = []
410 410 patlen = len(pat)
411 411 i = 0
412 412 while i < patlen:
413 413 c = pat[i]
414 414 if c == '%':
415 415 i += 1
416 416 c = pat[i]
417 417 c = expander[c]()
418 418 newname.append(c)
419 419 i += 1
420 420 return ''.join(newname)
421 421 except KeyError as inst:
422 422 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
423 423 inst.args[0])
424 424
425 425 def makefileobj(repo, pat, node=None, desc=None, total=None,
426 426 seqno=None, revwidth=None, mode='wb', modemap=None,
427 427 pathname=None):
428 428
429 429 writable = mode not in ('r', 'rb')
430 430
431 431 if not pat or pat == '-':
432 432 if writable:
433 433 fp = repo.ui.fout
434 434 else:
435 435 fp = repo.ui.fin
436 436 if util.safehasattr(fp, 'fileno'):
437 437 return os.fdopen(os.dup(fp.fileno()), mode)
438 438 else:
439 439 # if this fp can't be duped properly, return
440 440 # a dummy object that can be closed
441 441 class wrappedfileobj(object):
442 442 noop = lambda x: None
443 443 def __init__(self, f):
444 444 self.f = f
445 445 def __getattr__(self, attr):
446 446 if attr == 'close':
447 447 return self.noop
448 448 else:
449 449 return getattr(self.f, attr)
450 450
451 451 return wrappedfileobj(fp)
452 452 if util.safehasattr(pat, 'write') and writable:
453 453 return pat
454 454 if util.safehasattr(pat, 'read') and 'r' in mode:
455 455 return pat
456 456 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
457 457 if modemap is not None:
458 458 mode = modemap.get(fn, mode)
459 459 if mode == 'wb':
460 460 modemap[fn] = 'ab'
461 461 return open(fn, mode)
462 462
463 463 def openrevlog(repo, cmd, file_, opts):
464 464 """opens the changelog, manifest, a filelog or a given revlog"""
465 465 cl = opts['changelog']
466 466 mf = opts['manifest']
467 467 dir = opts['dir']
468 468 msg = None
469 469 if cl and mf:
470 470 msg = _('cannot specify --changelog and --manifest at the same time')
471 471 elif cl and dir:
472 472 msg = _('cannot specify --changelog and --dir at the same time')
473 473 elif cl or mf:
474 474 if file_:
475 475 msg = _('cannot specify filename with --changelog or --manifest')
476 476 elif not repo:
477 477 msg = _('cannot specify --changelog or --manifest or --dir '
478 478 'without a repository')
479 479 if msg:
480 480 raise util.Abort(msg)
481 481
482 482 r = None
483 483 if repo:
484 484 if cl:
485 485 r = repo.unfiltered().changelog
486 486 elif dir:
487 487 if 'treemanifest' not in repo.requirements:
488 488 raise util.Abort(_("--dir can only be used on repos with "
489 489 "treemanifest enabled"))
490 490 dirlog = repo.dirlog(file_)
491 491 if len(dirlog):
492 492 r = dirlog
493 493 elif mf:
494 494 r = repo.manifest
495 495 elif file_:
496 496 filelog = repo.file(file_)
497 497 if len(filelog):
498 498 r = filelog
499 499 if not r:
500 500 if not file_:
501 501 raise error.CommandError(cmd, _('invalid arguments'))
502 502 if not os.path.isfile(file_):
503 503 raise util.Abort(_("revlog '%s' not found") % file_)
504 504 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
505 505 file_[:-2] + ".i")
506 506 return r
507 507
508 508 def copy(ui, repo, pats, opts, rename=False):
509 509 # called with the repo lock held
510 510 #
511 511 # hgsep => pathname that uses "/" to separate directories
512 512 # ossep => pathname that uses os.sep to separate directories
513 513 cwd = repo.getcwd()
514 514 targets = {}
515 515 after = opts.get("after")
516 516 dryrun = opts.get("dry_run")
517 517 wctx = repo[None]
518 518
519 519 def walkpat(pat):
520 520 srcs = []
521 521 if after:
522 522 badstates = '?'
523 523 else:
524 524 badstates = '?r'
525 525 m = scmutil.match(repo[None], [pat], opts, globbed=True)
526 526 for abs in repo.walk(m):
527 527 state = repo.dirstate[abs]
528 528 rel = m.rel(abs)
529 529 exact = m.exact(abs)
530 530 if state in badstates:
531 531 if exact and state == '?':
532 532 ui.warn(_('%s: not copying - file is not managed\n') % rel)
533 533 if exact and state == 'r':
534 534 ui.warn(_('%s: not copying - file has been marked for'
535 535 ' remove\n') % rel)
536 536 continue
537 537 # abs: hgsep
538 538 # rel: ossep
539 539 srcs.append((abs, rel, exact))
540 540 return srcs
541 541
542 542 # abssrc: hgsep
543 543 # relsrc: ossep
544 544 # otarget: ossep
545 545 def copyfile(abssrc, relsrc, otarget, exact):
546 546 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
547 547 if '/' in abstarget:
548 548 # We cannot normalize abstarget itself, this would prevent
549 549 # case only renames, like a => A.
550 550 abspath, absname = abstarget.rsplit('/', 1)
551 551 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
552 552 reltarget = repo.pathto(abstarget, cwd)
553 553 target = repo.wjoin(abstarget)
554 554 src = repo.wjoin(abssrc)
555 555 state = repo.dirstate[abstarget]
556 556
557 557 scmutil.checkportable(ui, abstarget)
558 558
559 559 # check for collisions
560 560 prevsrc = targets.get(abstarget)
561 561 if prevsrc is not None:
562 562 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
563 563 (reltarget, repo.pathto(abssrc, cwd),
564 564 repo.pathto(prevsrc, cwd)))
565 565 return
566 566
567 567 # check for overwrites
568 568 exists = os.path.lexists(target)
569 569 samefile = False
570 570 if exists and abssrc != abstarget:
571 571 if (repo.dirstate.normalize(abssrc) ==
572 572 repo.dirstate.normalize(abstarget)):
573 573 if not rename:
574 574 ui.warn(_("%s: can't copy - same file\n") % reltarget)
575 575 return
576 576 exists = False
577 577 samefile = True
578 578
579 579 if not after and exists or after and state in 'mn':
580 580 if not opts['force']:
581 581 ui.warn(_('%s: not overwriting - file exists\n') %
582 582 reltarget)
583 583 return
584 584
585 585 if after:
586 586 if not exists:
587 587 if rename:
588 588 ui.warn(_('%s: not recording move - %s does not exist\n') %
589 589 (relsrc, reltarget))
590 590 else:
591 591 ui.warn(_('%s: not recording copy - %s does not exist\n') %
592 592 (relsrc, reltarget))
593 593 return
594 594 elif not dryrun:
595 595 try:
596 596 if exists:
597 597 os.unlink(target)
598 598 targetdir = os.path.dirname(target) or '.'
599 599 if not os.path.isdir(targetdir):
600 600 os.makedirs(targetdir)
601 601 if samefile:
602 602 tmp = target + "~hgrename"
603 603 os.rename(src, tmp)
604 604 os.rename(tmp, target)
605 605 else:
606 606 util.copyfile(src, target)
607 607 srcexists = True
608 608 except IOError as inst:
609 609 if inst.errno == errno.ENOENT:
610 610 ui.warn(_('%s: deleted in working directory\n') % relsrc)
611 611 srcexists = False
612 612 else:
613 613 ui.warn(_('%s: cannot copy - %s\n') %
614 614 (relsrc, inst.strerror))
615 615 return True # report a failure
616 616
617 617 if ui.verbose or not exact:
618 618 if rename:
619 619 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
620 620 else:
621 621 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
622 622
623 623 targets[abstarget] = abssrc
624 624
625 625 # fix up dirstate
626 626 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
627 627 dryrun=dryrun, cwd=cwd)
628 628 if rename and not dryrun:
629 629 if not after and srcexists and not samefile:
630 630 util.unlinkpath(repo.wjoin(abssrc))
631 631 wctx.forget([abssrc])
632 632
633 633 # pat: ossep
634 634 # dest ossep
635 635 # srcs: list of (hgsep, hgsep, ossep, bool)
636 636 # return: function that takes hgsep and returns ossep
637 637 def targetpathfn(pat, dest, srcs):
638 638 if os.path.isdir(pat):
639 639 abspfx = pathutil.canonpath(repo.root, cwd, pat)
640 640 abspfx = util.localpath(abspfx)
641 641 if destdirexists:
642 642 striplen = len(os.path.split(abspfx)[0])
643 643 else:
644 644 striplen = len(abspfx)
645 645 if striplen:
646 646 striplen += len(os.sep)
647 647 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
648 648 elif destdirexists:
649 649 res = lambda p: os.path.join(dest,
650 650 os.path.basename(util.localpath(p)))
651 651 else:
652 652 res = lambda p: dest
653 653 return res
654 654
655 655 # pat: ossep
656 656 # dest ossep
657 657 # srcs: list of (hgsep, hgsep, ossep, bool)
658 658 # return: function that takes hgsep and returns ossep
659 659 def targetpathafterfn(pat, dest, srcs):
660 660 if matchmod.patkind(pat):
661 661 # a mercurial pattern
662 662 res = lambda p: os.path.join(dest,
663 663 os.path.basename(util.localpath(p)))
664 664 else:
665 665 abspfx = pathutil.canonpath(repo.root, cwd, pat)
666 666 if len(abspfx) < len(srcs[0][0]):
667 667 # A directory. Either the target path contains the last
668 668 # component of the source path or it does not.
669 669 def evalpath(striplen):
670 670 score = 0
671 671 for s in srcs:
672 672 t = os.path.join(dest, util.localpath(s[0])[striplen:])
673 673 if os.path.lexists(t):
674 674 score += 1
675 675 return score
676 676
677 677 abspfx = util.localpath(abspfx)
678 678 striplen = len(abspfx)
679 679 if striplen:
680 680 striplen += len(os.sep)
681 681 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
682 682 score = evalpath(striplen)
683 683 striplen1 = len(os.path.split(abspfx)[0])
684 684 if striplen1:
685 685 striplen1 += len(os.sep)
686 686 if evalpath(striplen1) > score:
687 687 striplen = striplen1
688 688 res = lambda p: os.path.join(dest,
689 689 util.localpath(p)[striplen:])
690 690 else:
691 691 # a file
692 692 if destdirexists:
693 693 res = lambda p: os.path.join(dest,
694 694 os.path.basename(util.localpath(p)))
695 695 else:
696 696 res = lambda p: dest
697 697 return res
698 698
699 699 pats = scmutil.expandpats(pats)
700 700 if not pats:
701 701 raise util.Abort(_('no source or destination specified'))
702 702 if len(pats) == 1:
703 703 raise util.Abort(_('no destination specified'))
704 704 dest = pats.pop()
705 705 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
706 706 if not destdirexists:
707 707 if len(pats) > 1 or matchmod.patkind(pats[0]):
708 708 raise util.Abort(_('with multiple sources, destination must be an '
709 709 'existing directory'))
710 710 if util.endswithsep(dest):
711 711 raise util.Abort(_('destination %s is not a directory') % dest)
712 712
713 713 tfn = targetpathfn
714 714 if after:
715 715 tfn = targetpathafterfn
716 716 copylist = []
717 717 for pat in pats:
718 718 srcs = walkpat(pat)
719 719 if not srcs:
720 720 continue
721 721 copylist.append((tfn(pat, dest, srcs), srcs))
722 722 if not copylist:
723 723 raise util.Abort(_('no files to copy'))
724 724
725 725 errors = 0
726 726 for targetpath, srcs in copylist:
727 727 for abssrc, relsrc, exact in srcs:
728 728 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
729 729 errors += 1
730 730
731 731 if errors:
732 732 ui.warn(_('(consider using --after)\n'))
733 733
734 734 return errors != 0
735 735
736 736 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
737 737 runargs=None, appendpid=False):
738 738 '''Run a command as a service.'''
739 739
740 740 def writepid(pid):
741 741 if opts['pid_file']:
742 742 if appendpid:
743 743 mode = 'a'
744 744 else:
745 745 mode = 'w'
746 746 fp = open(opts['pid_file'], mode)
747 747 fp.write(str(pid) + '\n')
748 748 fp.close()
749 749
750 750 if opts['daemon'] and not opts['daemon_pipefds']:
751 751 # Signal child process startup with file removal
752 752 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
753 753 os.close(lockfd)
754 754 try:
755 755 if not runargs:
756 756 runargs = util.hgcmd() + sys.argv[1:]
757 757 runargs.append('--daemon-pipefds=%s' % lockpath)
758 758 # Don't pass --cwd to the child process, because we've already
759 759 # changed directory.
760 760 for i in xrange(1, len(runargs)):
761 761 if runargs[i].startswith('--cwd='):
762 762 del runargs[i]
763 763 break
764 764 elif runargs[i].startswith('--cwd'):
765 765 del runargs[i:i + 2]
766 766 break
767 767 def condfn():
768 768 return not os.path.exists(lockpath)
769 769 pid = util.rundetached(runargs, condfn)
770 770 if pid < 0:
771 771 raise util.Abort(_('child process failed to start'))
772 772 writepid(pid)
773 773 finally:
774 774 try:
775 775 os.unlink(lockpath)
776 776 except OSError as e:
777 777 if e.errno != errno.ENOENT:
778 778 raise
779 779 if parentfn:
780 780 return parentfn(pid)
781 781 else:
782 782 return
783 783
784 784 if initfn:
785 785 initfn()
786 786
787 787 if not opts['daemon']:
788 788 writepid(os.getpid())
789 789
790 790 if opts['daemon_pipefds']:
791 791 lockpath = opts['daemon_pipefds']
792 792 try:
793 793 os.setsid()
794 794 except AttributeError:
795 795 pass
796 796 os.unlink(lockpath)
797 797 util.hidewindow()
798 798 sys.stdout.flush()
799 799 sys.stderr.flush()
800 800
801 801 nullfd = os.open(os.devnull, os.O_RDWR)
802 802 logfilefd = nullfd
803 803 if logfile:
804 804 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
805 805 os.dup2(nullfd, 0)
806 806 os.dup2(logfilefd, 1)
807 807 os.dup2(logfilefd, 2)
808 808 if nullfd not in (0, 1, 2):
809 809 os.close(nullfd)
810 810 if logfile and logfilefd not in (0, 1, 2):
811 811 os.close(logfilefd)
812 812
813 813 if runfn:
814 814 return runfn()
815 815
816 816 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
817 817 """Utility function used by commands.import to import a single patch
818 818
819 819 This function is explicitly defined here to help the evolve extension to
820 820 wrap this part of the import logic.
821 821
822 822 The API is currently a bit ugly because it a simple code translation from
823 823 the import command. Feel free to make it better.
824 824
825 825 :hunk: a patch (as a binary string)
826 826 :parents: nodes that will be parent of the created commit
827 827 :opts: the full dict of option passed to the import command
828 828 :msgs: list to save commit message to.
829 829 (used in case we need to save it when failing)
830 830 :updatefunc: a function that update a repo to a given node
831 831 updatefunc(<repo>, <node>)
832 832 """
833 833 tmpname, message, user, date, branch, nodeid, p1, p2 = \
834 834 patch.extract(ui, hunk)
835 835
836 836 update = not opts.get('bypass')
837 837 strip = opts["strip"]
838 838 prefix = opts["prefix"]
839 839 sim = float(opts.get('similarity') or 0)
840 840 if not tmpname:
841 841 return (None, None, False)
842 842 msg = _('applied to working directory')
843 843
844 844 rejects = False
845 845 dsguard = None
846 846
847 847 try:
848 848 cmdline_message = logmessage(ui, opts)
849 849 if cmdline_message:
850 850 # pickup the cmdline msg
851 851 message = cmdline_message
852 852 elif message:
853 853 # pickup the patch msg
854 854 message = message.strip()
855 855 else:
856 856 # launch the editor
857 857 message = None
858 858 ui.debug('message:\n%s\n' % message)
859 859
860 860 if len(parents) == 1:
861 861 parents.append(repo[nullid])
862 862 if opts.get('exact'):
863 863 if not nodeid or not p1:
864 864 raise util.Abort(_('not a Mercurial patch'))
865 865 p1 = repo[p1]
866 866 p2 = repo[p2 or nullid]
867 867 elif p2:
868 868 try:
869 869 p1 = repo[p1]
870 870 p2 = repo[p2]
871 871 # Without any options, consider p2 only if the
872 872 # patch is being applied on top of the recorded
873 873 # first parent.
874 874 if p1 != parents[0]:
875 875 p1 = parents[0]
876 876 p2 = repo[nullid]
877 877 except error.RepoError:
878 878 p1, p2 = parents
879 879 if p2.node() == nullid:
880 880 ui.warn(_("warning: import the patch as a normal revision\n"
881 881 "(use --exact to import the patch as a merge)\n"))
882 882 else:
883 883 p1, p2 = parents
884 884
885 885 n = None
886 886 if update:
887 887 dsguard = dirstateguard(repo, 'tryimportone')
888 888 if p1 != parents[0]:
889 889 updatefunc(repo, p1.node())
890 890 if p2 != parents[1]:
891 891 repo.setparents(p1.node(), p2.node())
892 892
893 893 if opts.get('exact') or opts.get('import_branch'):
894 894 repo.dirstate.setbranch(branch or 'default')
895 895
896 896 partial = opts.get('partial', False)
897 897 files = set()
898 898 try:
899 899 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
900 900 files=files, eolmode=None, similarity=sim / 100.0)
901 901 except patch.PatchError as e:
902 902 if not partial:
903 903 raise util.Abort(str(e))
904 904 if partial:
905 905 rejects = True
906 906
907 907 files = list(files)
908 908 if opts.get('no_commit'):
909 909 if message:
910 910 msgs.append(message)
911 911 else:
912 912 if opts.get('exact') or p2:
913 913 # If you got here, you either use --force and know what
914 914 # you are doing or used --exact or a merge patch while
915 915 # being updated to its first parent.
916 916 m = None
917 917 else:
918 918 m = scmutil.matchfiles(repo, files or [])
919 919 editform = mergeeditform(repo[None], 'import.normal')
920 920 if opts.get('exact'):
921 921 editor = None
922 922 else:
923 923 editor = getcommiteditor(editform=editform, **opts)
924 924 allowemptyback = repo.ui.backupconfig('ui', 'allowemptycommit')
925 925 try:
926 926 if partial:
927 927 repo.ui.setconfig('ui', 'allowemptycommit', True)
928 928 n = repo.commit(message, opts.get('user') or user,
929 929 opts.get('date') or date, match=m,
930 930 editor=editor)
931 931 finally:
932 932 repo.ui.restoreconfig(allowemptyback)
933 933 dsguard.close()
934 934 else:
935 935 if opts.get('exact') or opts.get('import_branch'):
936 936 branch = branch or 'default'
937 937 else:
938 938 branch = p1.branch()
939 939 store = patch.filestore()
940 940 try:
941 941 files = set()
942 942 try:
943 943 patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
944 944 files, eolmode=None)
945 945 except patch.PatchError as e:
946 946 raise util.Abort(str(e))
947 947 if opts.get('exact'):
948 948 editor = None
949 949 else:
950 950 editor = getcommiteditor(editform='import.bypass')
951 951 memctx = context.makememctx(repo, (p1.node(), p2.node()),
952 952 message,
953 953 opts.get('user') or user,
954 954 opts.get('date') or date,
955 955 branch, files, store,
956 956 editor=editor)
957 957 n = memctx.commit()
958 958 finally:
959 959 store.close()
960 960 if opts.get('exact') and opts.get('no_commit'):
961 961 # --exact with --no-commit is still useful in that it does merge
962 962 # and branch bits
963 963 ui.warn(_("warning: can't check exact import with --no-commit\n"))
964 964 elif opts.get('exact') and hex(n) != nodeid:
965 965 raise util.Abort(_('patch is damaged or loses information'))
966 966 if n:
967 967 # i18n: refers to a short changeset id
968 968 msg = _('created %s') % short(n)
969 969 return (msg, n, rejects)
970 970 finally:
971 971 lockmod.release(dsguard)
972 972 os.unlink(tmpname)
973 973
974 974 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
975 975 opts=None):
976 976 '''export changesets as hg patches.'''
977 977
978 978 total = len(revs)
979 979 revwidth = max([len(str(rev)) for rev in revs])
980 980 filemode = {}
981 981
982 982 def single(rev, seqno, fp):
983 983 ctx = repo[rev]
984 984 node = ctx.node()
985 985 parents = [p.node() for p in ctx.parents() if p]
986 986 branch = ctx.branch()
987 987 if switch_parent:
988 988 parents.reverse()
989 989
990 990 if parents:
991 991 prev = parents[0]
992 992 else:
993 993 prev = nullid
994 994
995 995 shouldclose = False
996 996 if not fp and len(template) > 0:
997 997 desc_lines = ctx.description().rstrip().split('\n')
998 998 desc = desc_lines[0] #Commit always has a first line.
999 999 fp = makefileobj(repo, template, node, desc=desc, total=total,
1000 1000 seqno=seqno, revwidth=revwidth, mode='wb',
1001 1001 modemap=filemode)
1002 1002 if fp != template:
1003 1003 shouldclose = True
1004 1004 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
1005 1005 repo.ui.note("%s\n" % fp.name)
1006 1006
1007 1007 if not fp:
1008 1008 write = repo.ui.write
1009 1009 else:
1010 1010 def write(s, **kw):
1011 1011 fp.write(s)
1012 1012
1013 1013 write("# HG changeset patch\n")
1014 1014 write("# User %s\n" % ctx.user())
1015 1015 write("# Date %d %d\n" % ctx.date())
1016 1016 write("# %s\n" % util.datestr(ctx.date()))
1017 1017 if branch and branch != 'default':
1018 1018 write("# Branch %s\n" % branch)
1019 1019 write("# Node ID %s\n" % hex(node))
1020 1020 write("# Parent %s\n" % hex(prev))
1021 1021 if len(parents) > 1:
1022 1022 write("# Parent %s\n" % hex(parents[1]))
1023 1023 write(ctx.description().rstrip())
1024 1024 write("\n\n")
1025 1025
1026 1026 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
1027 1027 write(chunk, label=label)
1028 1028
1029 1029 if shouldclose:
1030 1030 fp.close()
1031 1031
1032 1032 for seqno, rev in enumerate(revs):
1033 1033 single(rev, seqno + 1, fp)
1034 1034
1035 1035 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
1036 1036 changes=None, stat=False, fp=None, prefix='',
1037 1037 root='', listsubrepos=False):
1038 1038 '''show diff or diffstat.'''
1039 1039 if fp is None:
1040 1040 write = ui.write
1041 1041 else:
1042 1042 def write(s, **kw):
1043 1043 fp.write(s)
1044 1044
1045 1045 if root:
1046 1046 relroot = pathutil.canonpath(repo.root, repo.getcwd(), root)
1047 1047 else:
1048 1048 relroot = ''
1049 1049 if relroot != '':
1050 1050 # XXX relative roots currently don't work if the root is within a
1051 1051 # subrepo
1052 1052 uirelroot = match.uipath(relroot)
1053 1053 relroot += '/'
1054 1054 for matchroot in match.files():
1055 1055 if not matchroot.startswith(relroot):
1056 1056 ui.warn(_('warning: %s not inside relative root %s\n') % (
1057 1057 match.uipath(matchroot), uirelroot))
1058 1058
1059 1059 if stat:
1060 1060 diffopts = diffopts.copy(context=0)
1061 1061 width = 80
1062 1062 if not ui.plain():
1063 1063 width = ui.termwidth()
1064 1064 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1065 1065 prefix=prefix, relroot=relroot)
1066 1066 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1067 1067 width=width,
1068 1068 git=diffopts.git):
1069 1069 write(chunk, label=label)
1070 1070 else:
1071 1071 for chunk, label in patch.diffui(repo, node1, node2, match,
1072 1072 changes, diffopts, prefix=prefix,
1073 1073 relroot=relroot):
1074 1074 write(chunk, label=label)
1075 1075
1076 1076 if listsubrepos:
1077 1077 ctx1 = repo[node1]
1078 1078 ctx2 = repo[node2]
1079 1079 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
1080 1080 tempnode2 = node2
1081 1081 try:
1082 1082 if node2 is not None:
1083 1083 tempnode2 = ctx2.substate[subpath][1]
1084 1084 except KeyError:
1085 1085 # A subrepo that existed in node1 was deleted between node1 and
1086 1086 # node2 (inclusive). Thus, ctx2's substate won't contain that
1087 1087 # subpath. The best we can do is to ignore it.
1088 1088 tempnode2 = None
1089 1089 submatch = matchmod.narrowmatcher(subpath, match)
1090 1090 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
1091 1091 stat=stat, fp=fp, prefix=prefix)
1092 1092
1093 1093 class changeset_printer(object):
1094 1094 '''show changeset information when templating not requested.'''
1095 1095
1096 1096 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1097 1097 self.ui = ui
1098 1098 self.repo = repo
1099 1099 self.buffered = buffered
1100 1100 self.matchfn = matchfn
1101 1101 self.diffopts = diffopts
1102 1102 self.header = {}
1103 1103 self.hunk = {}
1104 1104 self.lastheader = None
1105 1105 self.footer = None
1106 1106
1107 1107 def flush(self, rev):
1108 1108 if rev in self.header:
1109 1109 h = self.header[rev]
1110 1110 if h != self.lastheader:
1111 1111 self.lastheader = h
1112 1112 self.ui.write(h)
1113 1113 del self.header[rev]
1114 1114 if rev in self.hunk:
1115 1115 self.ui.write(self.hunk[rev])
1116 1116 del self.hunk[rev]
1117 1117 return 1
1118 1118 return 0
1119 1119
1120 1120 def close(self):
1121 1121 if self.footer:
1122 1122 self.ui.write(self.footer)
1123 1123
1124 1124 def show(self, ctx, copies=None, matchfn=None, **props):
1125 1125 if self.buffered:
1126 1126 self.ui.pushbuffer()
1127 1127 self._show(ctx, copies, matchfn, props)
1128 1128 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
1129 1129 else:
1130 1130 self._show(ctx, copies, matchfn, props)
1131 1131
1132 1132 def _show(self, ctx, copies, matchfn, props):
1133 1133 '''show a single changeset or file revision'''
1134 1134 changenode = ctx.node()
1135 1135 rev = ctx.rev()
1136 1136 if self.ui.debugflag:
1137 1137 hexfunc = hex
1138 1138 else:
1139 1139 hexfunc = short
1140 1140 if rev is None:
1141 1141 pctx = ctx.p1()
1142 1142 revnode = (pctx.rev(), hexfunc(pctx.node()) + '+')
1143 1143 else:
1144 1144 revnode = (rev, hexfunc(changenode))
1145 1145
1146 1146 if self.ui.quiet:
1147 1147 self.ui.write("%d:%s\n" % revnode, label='log.node')
1148 1148 return
1149 1149
1150 1150 date = util.datestr(ctx.date())
1151 1151
1152 1152 # i18n: column positioning for "hg log"
1153 1153 self.ui.write(_("changeset: %d:%s\n") % revnode,
1154 1154 label='log.changeset changeset.%s' % ctx.phasestr())
1155 1155
1156 1156 # branches are shown first before any other names due to backwards
1157 1157 # compatibility
1158 1158 branch = ctx.branch()
1159 1159 # don't show the default branch name
1160 1160 if branch != 'default':
1161 1161 # i18n: column positioning for "hg log"
1162 1162 self.ui.write(_("branch: %s\n") % branch,
1163 1163 label='log.branch')
1164 1164
1165 1165 for name, ns in self.repo.names.iteritems():
1166 1166 # branches has special logic already handled above, so here we just
1167 1167 # skip it
1168 1168 if name == 'branches':
1169 1169 continue
1170 1170 # we will use the templatename as the color name since those two
1171 1171 # should be the same
1172 1172 for name in ns.names(self.repo, changenode):
1173 1173 self.ui.write(ns.logfmt % name,
1174 1174 label='log.%s' % ns.colorname)
1175 1175 if self.ui.debugflag:
1176 1176 # i18n: column positioning for "hg log"
1177 1177 self.ui.write(_("phase: %s\n") % ctx.phasestr(),
1178 1178 label='log.phase')
1179 1179 for pctx in self._meaningful_parentrevs(ctx):
1180 1180 label = 'log.parent changeset.%s' % pctx.phasestr()
1181 1181 # i18n: column positioning for "hg log"
1182 1182 self.ui.write(_("parent: %d:%s\n")
1183 1183 % (pctx.rev(), hexfunc(pctx.node())),
1184 1184 label=label)
1185 1185
1186 1186 if self.ui.debugflag and rev is not None:
1187 1187 mnode = ctx.manifestnode()
1188 1188 # i18n: column positioning for "hg log"
1189 1189 self.ui.write(_("manifest: %d:%s\n") %
1190 1190 (self.repo.manifest.rev(mnode), hex(mnode)),
1191 1191 label='ui.debug log.manifest')
1192 1192 # i18n: column positioning for "hg log"
1193 1193 self.ui.write(_("user: %s\n") % ctx.user(),
1194 1194 label='log.user')
1195 1195 # i18n: column positioning for "hg log"
1196 1196 self.ui.write(_("date: %s\n") % date,
1197 1197 label='log.date')
1198 1198
1199 1199 if self.ui.debugflag:
1200 1200 files = ctx.p1().status(ctx)[:3]
1201 1201 for key, value in zip([# i18n: column positioning for "hg log"
1202 1202 _("files:"),
1203 1203 # i18n: column positioning for "hg log"
1204 1204 _("files+:"),
1205 1205 # i18n: column positioning for "hg log"
1206 1206 _("files-:")], files):
1207 1207 if value:
1208 1208 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
1209 1209 label='ui.debug log.files')
1210 1210 elif ctx.files() and self.ui.verbose:
1211 1211 # i18n: column positioning for "hg log"
1212 1212 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
1213 1213 label='ui.note log.files')
1214 1214 if copies and self.ui.verbose:
1215 1215 copies = ['%s (%s)' % c for c in copies]
1216 1216 # i18n: column positioning for "hg log"
1217 1217 self.ui.write(_("copies: %s\n") % ' '.join(copies),
1218 1218 label='ui.note log.copies')
1219 1219
1220 1220 extra = ctx.extra()
1221 1221 if extra and self.ui.debugflag:
1222 1222 for key, value in sorted(extra.items()):
1223 1223 # i18n: column positioning for "hg log"
1224 1224 self.ui.write(_("extra: %s=%s\n")
1225 1225 % (key, value.encode('string_escape')),
1226 1226 label='ui.debug log.extra')
1227 1227
1228 1228 description = ctx.description().strip()
1229 1229 if description:
1230 1230 if self.ui.verbose:
1231 1231 self.ui.write(_("description:\n"),
1232 1232 label='ui.note log.description')
1233 1233 self.ui.write(description,
1234 1234 label='ui.note log.description')
1235 1235 self.ui.write("\n\n")
1236 1236 else:
1237 1237 # i18n: column positioning for "hg log"
1238 1238 self.ui.write(_("summary: %s\n") %
1239 1239 description.splitlines()[0],
1240 1240 label='log.summary')
1241 1241 self.ui.write("\n")
1242 1242
1243 1243 self.showpatch(changenode, matchfn)
1244 1244
1245 1245 def showpatch(self, node, matchfn):
1246 1246 if not matchfn:
1247 1247 matchfn = self.matchfn
1248 1248 if matchfn:
1249 1249 stat = self.diffopts.get('stat')
1250 1250 diff = self.diffopts.get('patch')
1251 1251 diffopts = patch.diffallopts(self.ui, self.diffopts)
1252 1252 prev = self.repo.changelog.parents(node)[0]
1253 1253 if stat:
1254 1254 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1255 1255 match=matchfn, stat=True)
1256 1256 if diff:
1257 1257 if stat:
1258 1258 self.ui.write("\n")
1259 1259 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1260 1260 match=matchfn, stat=False)
1261 1261 self.ui.write("\n")
1262 1262
1263 1263 def _meaningful_parentrevs(self, ctx):
1264 1264 """Return list of meaningful (or all if debug) parentrevs for rev.
1265 1265
1266 1266 For merges (two non-nullrev revisions) both parents are meaningful.
1267 1267 Otherwise the first parent revision is considered meaningful if it
1268 1268 is not the preceding revision.
1269 1269 """
1270 1270 parents = ctx.parents()
1271 1271 if len(parents) > 1:
1272 1272 return parents
1273 1273 if self.ui.debugflag:
1274 1274 return [parents[0], self.repo['null']]
1275 if parents[0].rev() >= scmutil.intrev(self.repo, ctx.rev()) - 1:
1275 if parents[0].rev() >= scmutil.intrev(ctx.rev()) - 1:
1276 1276 return []
1277 1277 return parents
1278 1278
1279 1279 class jsonchangeset(changeset_printer):
1280 1280 '''format changeset information.'''
1281 1281
1282 1282 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1283 1283 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1284 1284 self.cache = {}
1285 1285 self._first = True
1286 1286
1287 1287 def close(self):
1288 1288 if not self._first:
1289 1289 self.ui.write("\n]\n")
1290 1290 else:
1291 1291 self.ui.write("[]\n")
1292 1292
1293 1293 def _show(self, ctx, copies, matchfn, props):
1294 1294 '''show a single changeset or file revision'''
1295 1295 rev = ctx.rev()
1296 1296 if rev is None:
1297 1297 jrev = jnode = 'null'
1298 1298 else:
1299 1299 jrev = str(rev)
1300 1300 jnode = '"%s"' % hex(ctx.node())
1301 1301 j = encoding.jsonescape
1302 1302
1303 1303 if self._first:
1304 1304 self.ui.write("[\n {")
1305 1305 self._first = False
1306 1306 else:
1307 1307 self.ui.write(",\n {")
1308 1308
1309 1309 if self.ui.quiet:
1310 1310 self.ui.write('\n "rev": %s' % jrev)
1311 1311 self.ui.write(',\n "node": %s' % jnode)
1312 1312 self.ui.write('\n }')
1313 1313 return
1314 1314
1315 1315 self.ui.write('\n "rev": %s' % jrev)
1316 1316 self.ui.write(',\n "node": %s' % jnode)
1317 1317 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1318 1318 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1319 1319 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1320 1320 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1321 1321 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1322 1322
1323 1323 self.ui.write(',\n "bookmarks": [%s]' %
1324 1324 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1325 1325 self.ui.write(',\n "tags": [%s]' %
1326 1326 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1327 1327 self.ui.write(',\n "parents": [%s]' %
1328 1328 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1329 1329
1330 1330 if self.ui.debugflag:
1331 1331 if rev is None:
1332 1332 jmanifestnode = 'null'
1333 1333 else:
1334 1334 jmanifestnode = '"%s"' % hex(ctx.manifestnode())
1335 1335 self.ui.write(',\n "manifest": %s' % jmanifestnode)
1336 1336
1337 1337 self.ui.write(',\n "extra": {%s}' %
1338 1338 ", ".join('"%s": "%s"' % (j(k), j(v))
1339 1339 for k, v in ctx.extra().items()))
1340 1340
1341 1341 files = ctx.p1().status(ctx)
1342 1342 self.ui.write(',\n "modified": [%s]' %
1343 1343 ", ".join('"%s"' % j(f) for f in files[0]))
1344 1344 self.ui.write(',\n "added": [%s]' %
1345 1345 ", ".join('"%s"' % j(f) for f in files[1]))
1346 1346 self.ui.write(',\n "removed": [%s]' %
1347 1347 ", ".join('"%s"' % j(f) for f in files[2]))
1348 1348
1349 1349 elif self.ui.verbose:
1350 1350 self.ui.write(',\n "files": [%s]' %
1351 1351 ", ".join('"%s"' % j(f) for f in ctx.files()))
1352 1352
1353 1353 if copies:
1354 1354 self.ui.write(',\n "copies": {%s}' %
1355 1355 ", ".join('"%s": "%s"' % (j(k), j(v))
1356 1356 for k, v in copies))
1357 1357
1358 1358 matchfn = self.matchfn
1359 1359 if matchfn:
1360 1360 stat = self.diffopts.get('stat')
1361 1361 diff = self.diffopts.get('patch')
1362 1362 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1363 1363 node, prev = ctx.node(), ctx.p1().node()
1364 1364 if stat:
1365 1365 self.ui.pushbuffer()
1366 1366 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1367 1367 match=matchfn, stat=True)
1368 1368 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1369 1369 if diff:
1370 1370 self.ui.pushbuffer()
1371 1371 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1372 1372 match=matchfn, stat=False)
1373 1373 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1374 1374
1375 1375 self.ui.write("\n }")
1376 1376
1377 1377 class changeset_templater(changeset_printer):
1378 1378 '''format changeset information.'''
1379 1379
1380 1380 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1381 1381 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1382 1382 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1383 1383 defaulttempl = {
1384 1384 'parent': '{rev}:{node|formatnode} ',
1385 1385 'manifest': '{rev}:{node|formatnode}',
1386 1386 'file_copy': '{name} ({source})',
1387 1387 'extra': '{key}={value|stringescape}'
1388 1388 }
1389 1389 # filecopy is preserved for compatibility reasons
1390 1390 defaulttempl['filecopy'] = defaulttempl['file_copy']
1391 1391 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1392 1392 cache=defaulttempl)
1393 1393 if tmpl:
1394 1394 self.t.cache['changeset'] = tmpl
1395 1395
1396 1396 self.cache = {}
1397 1397
1398 1398 def _show(self, ctx, copies, matchfn, props):
1399 1399 '''show a single changeset or file revision'''
1400 1400
1401 1401 showlist = templatekw.showlist
1402 1402
1403 1403 # showparents() behaviour depends on ui trace level which
1404 1404 # causes unexpected behaviours at templating level and makes
1405 1405 # it harder to extract it in a standalone function. Its
1406 1406 # behaviour cannot be changed so leave it here for now.
1407 1407 def showparents(**args):
1408 1408 ctx = args['ctx']
1409 1409 parents = [[('rev', p.rev()),
1410 1410 ('node', p.hex()),
1411 1411 ('phase', p.phasestr())]
1412 1412 for p in self._meaningful_parentrevs(ctx)]
1413 1413 return showlist('parent', parents, **args)
1414 1414
1415 1415 props = props.copy()
1416 1416 props.update(templatekw.keywords)
1417 1417 props['parents'] = showparents
1418 1418 props['templ'] = self.t
1419 1419 props['ctx'] = ctx
1420 1420 props['repo'] = self.repo
1421 1421 props['revcache'] = {'copies': copies}
1422 1422 props['cache'] = self.cache
1423 1423
1424 1424 # find correct templates for current mode
1425 1425
1426 1426 tmplmodes = [
1427 1427 (True, None),
1428 1428 (self.ui.verbose, 'verbose'),
1429 1429 (self.ui.quiet, 'quiet'),
1430 1430 (self.ui.debugflag, 'debug'),
1431 1431 ]
1432 1432
1433 1433 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1434 1434 for mode, postfix in tmplmodes:
1435 1435 for type in types:
1436 1436 cur = postfix and ('%s_%s' % (type, postfix)) or type
1437 1437 if mode and cur in self.t:
1438 1438 types[type] = cur
1439 1439
1440 1440 try:
1441 1441
1442 1442 # write header
1443 1443 if types['header']:
1444 1444 h = templater.stringify(self.t(types['header'], **props))
1445 1445 if self.buffered:
1446 1446 self.header[ctx.rev()] = h
1447 1447 else:
1448 1448 if self.lastheader != h:
1449 1449 self.lastheader = h
1450 1450 self.ui.write(h)
1451 1451
1452 1452 # write changeset metadata, then patch if requested
1453 1453 key = types['changeset']
1454 1454 self.ui.write(templater.stringify(self.t(key, **props)))
1455 1455 self.showpatch(ctx.node(), matchfn)
1456 1456
1457 1457 if types['footer']:
1458 1458 if not self.footer:
1459 1459 self.footer = templater.stringify(self.t(types['footer'],
1460 1460 **props))
1461 1461
1462 1462 except KeyError as inst:
1463 1463 msg = _("%s: no key named '%s'")
1464 1464 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1465 1465 except SyntaxError as inst:
1466 1466 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1467 1467
1468 1468 def gettemplate(ui, tmpl, style):
1469 1469 """
1470 1470 Find the template matching the given template spec or style.
1471 1471 """
1472 1472
1473 1473 # ui settings
1474 1474 if not tmpl and not style: # template are stronger than style
1475 1475 tmpl = ui.config('ui', 'logtemplate')
1476 1476 if tmpl:
1477 1477 try:
1478 1478 tmpl = templater.unquotestring(tmpl)
1479 1479 except SyntaxError:
1480 1480 pass
1481 1481 return tmpl, None
1482 1482 else:
1483 1483 style = util.expandpath(ui.config('ui', 'style', ''))
1484 1484
1485 1485 if not tmpl and style:
1486 1486 mapfile = style
1487 1487 if not os.path.split(mapfile)[0]:
1488 1488 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1489 1489 or templater.templatepath(mapfile))
1490 1490 if mapname:
1491 1491 mapfile = mapname
1492 1492 return None, mapfile
1493 1493
1494 1494 if not tmpl:
1495 1495 return None, None
1496 1496
1497 1497 return formatter.lookuptemplate(ui, 'changeset', tmpl)
1498 1498
1499 1499 def show_changeset(ui, repo, opts, buffered=False):
1500 1500 """show one changeset using template or regular display.
1501 1501
1502 1502 Display format will be the first non-empty hit of:
1503 1503 1. option 'template'
1504 1504 2. option 'style'
1505 1505 3. [ui] setting 'logtemplate'
1506 1506 4. [ui] setting 'style'
1507 1507 If all of these values are either the unset or the empty string,
1508 1508 regular display via changeset_printer() is done.
1509 1509 """
1510 1510 # options
1511 1511 matchfn = None
1512 1512 if opts.get('patch') or opts.get('stat'):
1513 1513 matchfn = scmutil.matchall(repo)
1514 1514
1515 1515 if opts.get('template') == 'json':
1516 1516 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1517 1517
1518 1518 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1519 1519
1520 1520 if not tmpl and not mapfile:
1521 1521 return changeset_printer(ui, repo, matchfn, opts, buffered)
1522 1522
1523 1523 try:
1524 1524 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1525 1525 buffered)
1526 1526 except SyntaxError as inst:
1527 1527 raise util.Abort(inst.args[0])
1528 1528 return t
1529 1529
1530 1530 def showmarker(ui, marker):
1531 1531 """utility function to display obsolescence marker in a readable way
1532 1532
1533 1533 To be used by debug function."""
1534 1534 ui.write(hex(marker.precnode()))
1535 1535 for repl in marker.succnodes():
1536 1536 ui.write(' ')
1537 1537 ui.write(hex(repl))
1538 1538 ui.write(' %X ' % marker.flags())
1539 1539 parents = marker.parentnodes()
1540 1540 if parents is not None:
1541 1541 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1542 1542 ui.write('(%s) ' % util.datestr(marker.date()))
1543 1543 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1544 1544 sorted(marker.metadata().items())
1545 1545 if t[0] != 'date')))
1546 1546 ui.write('\n')
1547 1547
1548 1548 def finddate(ui, repo, date):
1549 1549 """Find the tipmost changeset that matches the given date spec"""
1550 1550
1551 1551 df = util.matchdate(date)
1552 1552 m = scmutil.matchall(repo)
1553 1553 results = {}
1554 1554
1555 1555 def prep(ctx, fns):
1556 1556 d = ctx.date()
1557 1557 if df(d[0]):
1558 1558 results[ctx.rev()] = d
1559 1559
1560 1560 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1561 1561 rev = ctx.rev()
1562 1562 if rev in results:
1563 1563 ui.status(_("found revision %s from %s\n") %
1564 1564 (rev, util.datestr(results[rev])))
1565 1565 return str(rev)
1566 1566
1567 1567 raise util.Abort(_("revision matching date not found"))
1568 1568
1569 1569 def increasingwindows(windowsize=8, sizelimit=512):
1570 1570 while True:
1571 1571 yield windowsize
1572 1572 if windowsize < sizelimit:
1573 1573 windowsize *= 2
1574 1574
1575 1575 class FileWalkError(Exception):
1576 1576 pass
1577 1577
1578 1578 def walkfilerevs(repo, match, follow, revs, fncache):
1579 1579 '''Walks the file history for the matched files.
1580 1580
1581 1581 Returns the changeset revs that are involved in the file history.
1582 1582
1583 1583 Throws FileWalkError if the file history can't be walked using
1584 1584 filelogs alone.
1585 1585 '''
1586 1586 wanted = set()
1587 1587 copies = []
1588 1588 minrev, maxrev = min(revs), max(revs)
1589 1589 def filerevgen(filelog, last):
1590 1590 """
1591 1591 Only files, no patterns. Check the history of each file.
1592 1592
1593 1593 Examines filelog entries within minrev, maxrev linkrev range
1594 1594 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1595 1595 tuples in backwards order
1596 1596 """
1597 1597 cl_count = len(repo)
1598 1598 revs = []
1599 1599 for j in xrange(0, last + 1):
1600 1600 linkrev = filelog.linkrev(j)
1601 1601 if linkrev < minrev:
1602 1602 continue
1603 1603 # only yield rev for which we have the changelog, it can
1604 1604 # happen while doing "hg log" during a pull or commit
1605 1605 if linkrev >= cl_count:
1606 1606 break
1607 1607
1608 1608 parentlinkrevs = []
1609 1609 for p in filelog.parentrevs(j):
1610 1610 if p != nullrev:
1611 1611 parentlinkrevs.append(filelog.linkrev(p))
1612 1612 n = filelog.node(j)
1613 1613 revs.append((linkrev, parentlinkrevs,
1614 1614 follow and filelog.renamed(n)))
1615 1615
1616 1616 return reversed(revs)
1617 1617 def iterfiles():
1618 1618 pctx = repo['.']
1619 1619 for filename in match.files():
1620 1620 if follow:
1621 1621 if filename not in pctx:
1622 1622 raise util.Abort(_('cannot follow file not in parent '
1623 1623 'revision: "%s"') % filename)
1624 1624 yield filename, pctx[filename].filenode()
1625 1625 else:
1626 1626 yield filename, None
1627 1627 for filename_node in copies:
1628 1628 yield filename_node
1629 1629
1630 1630 for file_, node in iterfiles():
1631 1631 filelog = repo.file(file_)
1632 1632 if not len(filelog):
1633 1633 if node is None:
1634 1634 # A zero count may be a directory or deleted file, so
1635 1635 # try to find matching entries on the slow path.
1636 1636 if follow:
1637 1637 raise util.Abort(
1638 1638 _('cannot follow nonexistent file: "%s"') % file_)
1639 1639 raise FileWalkError("Cannot walk via filelog")
1640 1640 else:
1641 1641 continue
1642 1642
1643 1643 if node is None:
1644 1644 last = len(filelog) - 1
1645 1645 else:
1646 1646 last = filelog.rev(node)
1647 1647
1648 1648 # keep track of all ancestors of the file
1649 1649 ancestors = set([filelog.linkrev(last)])
1650 1650
1651 1651 # iterate from latest to oldest revision
1652 1652 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1653 1653 if not follow:
1654 1654 if rev > maxrev:
1655 1655 continue
1656 1656 else:
1657 1657 # Note that last might not be the first interesting
1658 1658 # rev to us:
1659 1659 # if the file has been changed after maxrev, we'll
1660 1660 # have linkrev(last) > maxrev, and we still need
1661 1661 # to explore the file graph
1662 1662 if rev not in ancestors:
1663 1663 continue
1664 1664 # XXX insert 1327 fix here
1665 1665 if flparentlinkrevs:
1666 1666 ancestors.update(flparentlinkrevs)
1667 1667
1668 1668 fncache.setdefault(rev, []).append(file_)
1669 1669 wanted.add(rev)
1670 1670 if copied:
1671 1671 copies.append(copied)
1672 1672
1673 1673 return wanted
1674 1674
1675 1675 class _followfilter(object):
1676 1676 def __init__(self, repo, onlyfirst=False):
1677 1677 self.repo = repo
1678 1678 self.startrev = nullrev
1679 1679 self.roots = set()
1680 1680 self.onlyfirst = onlyfirst
1681 1681
1682 1682 def match(self, rev):
1683 1683 def realparents(rev):
1684 1684 if self.onlyfirst:
1685 1685 return self.repo.changelog.parentrevs(rev)[0:1]
1686 1686 else:
1687 1687 return filter(lambda x: x != nullrev,
1688 1688 self.repo.changelog.parentrevs(rev))
1689 1689
1690 1690 if self.startrev == nullrev:
1691 1691 self.startrev = rev
1692 1692 return True
1693 1693
1694 1694 if rev > self.startrev:
1695 1695 # forward: all descendants
1696 1696 if not self.roots:
1697 1697 self.roots.add(self.startrev)
1698 1698 for parent in realparents(rev):
1699 1699 if parent in self.roots:
1700 1700 self.roots.add(rev)
1701 1701 return True
1702 1702 else:
1703 1703 # backwards: all parents
1704 1704 if not self.roots:
1705 1705 self.roots.update(realparents(self.startrev))
1706 1706 if rev in self.roots:
1707 1707 self.roots.remove(rev)
1708 1708 self.roots.update(realparents(rev))
1709 1709 return True
1710 1710
1711 1711 return False
1712 1712
1713 1713 def walkchangerevs(repo, match, opts, prepare):
1714 1714 '''Iterate over files and the revs in which they changed.
1715 1715
1716 1716 Callers most commonly need to iterate backwards over the history
1717 1717 in which they are interested. Doing so has awful (quadratic-looking)
1718 1718 performance, so we use iterators in a "windowed" way.
1719 1719
1720 1720 We walk a window of revisions in the desired order. Within the
1721 1721 window, we first walk forwards to gather data, then in the desired
1722 1722 order (usually backwards) to display it.
1723 1723
1724 1724 This function returns an iterator yielding contexts. Before
1725 1725 yielding each context, the iterator will first call the prepare
1726 1726 function on each context in the window in forward order.'''
1727 1727
1728 1728 follow = opts.get('follow') or opts.get('follow_first')
1729 1729 revs = _logrevs(repo, opts)
1730 1730 if not revs:
1731 1731 return []
1732 1732 wanted = set()
1733 1733 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1734 1734 opts.get('removed'))
1735 1735 fncache = {}
1736 1736 change = repo.changectx
1737 1737
1738 1738 # First step is to fill wanted, the set of revisions that we want to yield.
1739 1739 # When it does not induce extra cost, we also fill fncache for revisions in
1740 1740 # wanted: a cache of filenames that were changed (ctx.files()) and that
1741 1741 # match the file filtering conditions.
1742 1742
1743 1743 if match.always():
1744 1744 # No files, no patterns. Display all revs.
1745 1745 wanted = revs
1746 1746 elif not slowpath:
1747 1747 # We only have to read through the filelog to find wanted revisions
1748 1748
1749 1749 try:
1750 1750 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1751 1751 except FileWalkError:
1752 1752 slowpath = True
1753 1753
1754 1754 # We decided to fall back to the slowpath because at least one
1755 1755 # of the paths was not a file. Check to see if at least one of them
1756 1756 # existed in history, otherwise simply return
1757 1757 for path in match.files():
1758 1758 if path == '.' or path in repo.store:
1759 1759 break
1760 1760 else:
1761 1761 return []
1762 1762
1763 1763 if slowpath:
1764 1764 # We have to read the changelog to match filenames against
1765 1765 # changed files
1766 1766
1767 1767 if follow:
1768 1768 raise util.Abort(_('can only follow copies/renames for explicit '
1769 1769 'filenames'))
1770 1770
1771 1771 # The slow path checks files modified in every changeset.
1772 1772 # This is really slow on large repos, so compute the set lazily.
1773 1773 class lazywantedset(object):
1774 1774 def __init__(self):
1775 1775 self.set = set()
1776 1776 self.revs = set(revs)
1777 1777
1778 1778 # No need to worry about locality here because it will be accessed
1779 1779 # in the same order as the increasing window below.
1780 1780 def __contains__(self, value):
1781 1781 if value in self.set:
1782 1782 return True
1783 1783 elif not value in self.revs:
1784 1784 return False
1785 1785 else:
1786 1786 self.revs.discard(value)
1787 1787 ctx = change(value)
1788 1788 matches = filter(match, ctx.files())
1789 1789 if matches:
1790 1790 fncache[value] = matches
1791 1791 self.set.add(value)
1792 1792 return True
1793 1793 return False
1794 1794
1795 1795 def discard(self, value):
1796 1796 self.revs.discard(value)
1797 1797 self.set.discard(value)
1798 1798
1799 1799 wanted = lazywantedset()
1800 1800
1801 1801 # it might be worthwhile to do this in the iterator if the rev range
1802 1802 # is descending and the prune args are all within that range
1803 1803 for rev in opts.get('prune', ()):
1804 1804 rev = repo[rev].rev()
1805 1805 ff = _followfilter(repo)
1806 1806 stop = min(revs[0], revs[-1])
1807 1807 for x in xrange(rev, stop - 1, -1):
1808 1808 if ff.match(x):
1809 1809 wanted = wanted - [x]
1810 1810
1811 1811 # Now that wanted is correctly initialized, we can iterate over the
1812 1812 # revision range, yielding only revisions in wanted.
1813 1813 def iterate():
1814 1814 if follow and match.always():
1815 1815 ff = _followfilter(repo, onlyfirst=opts.get('follow_first'))
1816 1816 def want(rev):
1817 1817 return ff.match(rev) and rev in wanted
1818 1818 else:
1819 1819 def want(rev):
1820 1820 return rev in wanted
1821 1821
1822 1822 it = iter(revs)
1823 1823 stopiteration = False
1824 1824 for windowsize in increasingwindows():
1825 1825 nrevs = []
1826 1826 for i in xrange(windowsize):
1827 1827 rev = next(it, None)
1828 1828 if rev is None:
1829 1829 stopiteration = True
1830 1830 break
1831 1831 elif want(rev):
1832 1832 nrevs.append(rev)
1833 1833 for rev in sorted(nrevs):
1834 1834 fns = fncache.get(rev)
1835 1835 ctx = change(rev)
1836 1836 if not fns:
1837 1837 def fns_generator():
1838 1838 for f in ctx.files():
1839 1839 if match(f):
1840 1840 yield f
1841 1841 fns = fns_generator()
1842 1842 prepare(ctx, fns)
1843 1843 for rev in nrevs:
1844 1844 yield change(rev)
1845 1845
1846 1846 if stopiteration:
1847 1847 break
1848 1848
1849 1849 return iterate()
1850 1850
1851 1851 def _makefollowlogfilematcher(repo, files, followfirst):
1852 1852 # When displaying a revision with --patch --follow FILE, we have
1853 1853 # to know which file of the revision must be diffed. With
1854 1854 # --follow, we want the names of the ancestors of FILE in the
1855 1855 # revision, stored in "fcache". "fcache" is populated by
1856 1856 # reproducing the graph traversal already done by --follow revset
1857 1857 # and relating linkrevs to file names (which is not "correct" but
1858 1858 # good enough).
1859 1859 fcache = {}
1860 1860 fcacheready = [False]
1861 1861 pctx = repo['.']
1862 1862
1863 1863 def populate():
1864 1864 for fn in files:
1865 1865 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1866 1866 for c in i:
1867 1867 fcache.setdefault(c.linkrev(), set()).add(c.path())
1868 1868
1869 1869 def filematcher(rev):
1870 1870 if not fcacheready[0]:
1871 1871 # Lazy initialization
1872 1872 fcacheready[0] = True
1873 1873 populate()
1874 1874 return scmutil.matchfiles(repo, fcache.get(rev, []))
1875 1875
1876 1876 return filematcher
1877 1877
1878 1878 def _makenofollowlogfilematcher(repo, pats, opts):
1879 1879 '''hook for extensions to override the filematcher for non-follow cases'''
1880 1880 return None
1881 1881
1882 1882 def _makelogrevset(repo, pats, opts, revs):
1883 1883 """Return (expr, filematcher) where expr is a revset string built
1884 1884 from log options and file patterns or None. If --stat or --patch
1885 1885 are not passed filematcher is None. Otherwise it is a callable
1886 1886 taking a revision number and returning a match objects filtering
1887 1887 the files to be detailed when displaying the revision.
1888 1888 """
1889 1889 opt2revset = {
1890 1890 'no_merges': ('not merge()', None),
1891 1891 'only_merges': ('merge()', None),
1892 1892 '_ancestors': ('ancestors(%(val)s)', None),
1893 1893 '_fancestors': ('_firstancestors(%(val)s)', None),
1894 1894 '_descendants': ('descendants(%(val)s)', None),
1895 1895 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1896 1896 '_matchfiles': ('_matchfiles(%(val)s)', None),
1897 1897 'date': ('date(%(val)r)', None),
1898 1898 'branch': ('branch(%(val)r)', ' or '),
1899 1899 '_patslog': ('filelog(%(val)r)', ' or '),
1900 1900 '_patsfollow': ('follow(%(val)r)', ' or '),
1901 1901 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1902 1902 'keyword': ('keyword(%(val)r)', ' or '),
1903 1903 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1904 1904 'user': ('user(%(val)r)', ' or '),
1905 1905 }
1906 1906
1907 1907 opts = dict(opts)
1908 1908 # follow or not follow?
1909 1909 follow = opts.get('follow') or opts.get('follow_first')
1910 1910 if opts.get('follow_first'):
1911 1911 followfirst = 1
1912 1912 else:
1913 1913 followfirst = 0
1914 1914 # --follow with FILE behaviour depends on revs...
1915 1915 it = iter(revs)
1916 1916 startrev = it.next()
1917 1917 followdescendants = startrev < next(it, startrev)
1918 1918
1919 1919 # branch and only_branch are really aliases and must be handled at
1920 1920 # the same time
1921 1921 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1922 1922 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1923 1923 # pats/include/exclude are passed to match.match() directly in
1924 1924 # _matchfiles() revset but walkchangerevs() builds its matcher with
1925 1925 # scmutil.match(). The difference is input pats are globbed on
1926 1926 # platforms without shell expansion (windows).
1927 1927 wctx = repo[None]
1928 1928 match, pats = scmutil.matchandpats(wctx, pats, opts)
1929 1929 slowpath = match.anypats() or ((match.isexact() or match.prefix()) and
1930 1930 opts.get('removed'))
1931 1931 if not slowpath:
1932 1932 for f in match.files():
1933 1933 if follow and f not in wctx:
1934 1934 # If the file exists, it may be a directory, so let it
1935 1935 # take the slow path.
1936 1936 if os.path.exists(repo.wjoin(f)):
1937 1937 slowpath = True
1938 1938 continue
1939 1939 else:
1940 1940 raise util.Abort(_('cannot follow file not in parent '
1941 1941 'revision: "%s"') % f)
1942 1942 filelog = repo.file(f)
1943 1943 if not filelog:
1944 1944 # A zero count may be a directory or deleted file, so
1945 1945 # try to find matching entries on the slow path.
1946 1946 if follow:
1947 1947 raise util.Abort(
1948 1948 _('cannot follow nonexistent file: "%s"') % f)
1949 1949 slowpath = True
1950 1950
1951 1951 # We decided to fall back to the slowpath because at least one
1952 1952 # of the paths was not a file. Check to see if at least one of them
1953 1953 # existed in history - in that case, we'll continue down the
1954 1954 # slowpath; otherwise, we can turn off the slowpath
1955 1955 if slowpath:
1956 1956 for path in match.files():
1957 1957 if path == '.' or path in repo.store:
1958 1958 break
1959 1959 else:
1960 1960 slowpath = False
1961 1961
1962 1962 fpats = ('_patsfollow', '_patsfollowfirst')
1963 1963 fnopats = (('_ancestors', '_fancestors'),
1964 1964 ('_descendants', '_fdescendants'))
1965 1965 if slowpath:
1966 1966 # See walkchangerevs() slow path.
1967 1967 #
1968 1968 # pats/include/exclude cannot be represented as separate
1969 1969 # revset expressions as their filtering logic applies at file
1970 1970 # level. For instance "-I a -X a" matches a revision touching
1971 1971 # "a" and "b" while "file(a) and not file(b)" does
1972 1972 # not. Besides, filesets are evaluated against the working
1973 1973 # directory.
1974 1974 matchargs = ['r:', 'd:relpath']
1975 1975 for p in pats:
1976 1976 matchargs.append('p:' + p)
1977 1977 for p in opts.get('include', []):
1978 1978 matchargs.append('i:' + p)
1979 1979 for p in opts.get('exclude', []):
1980 1980 matchargs.append('x:' + p)
1981 1981 matchargs = ','.join(('%r' % p) for p in matchargs)
1982 1982 opts['_matchfiles'] = matchargs
1983 1983 if follow:
1984 1984 opts[fnopats[0][followfirst]] = '.'
1985 1985 else:
1986 1986 if follow:
1987 1987 if pats:
1988 1988 # follow() revset interprets its file argument as a
1989 1989 # manifest entry, so use match.files(), not pats.
1990 1990 opts[fpats[followfirst]] = list(match.files())
1991 1991 else:
1992 1992 op = fnopats[followdescendants][followfirst]
1993 1993 opts[op] = 'rev(%d)' % startrev
1994 1994 else:
1995 1995 opts['_patslog'] = list(pats)
1996 1996
1997 1997 filematcher = None
1998 1998 if opts.get('patch') or opts.get('stat'):
1999 1999 # When following files, track renames via a special matcher.
2000 2000 # If we're forced to take the slowpath it means we're following
2001 2001 # at least one pattern/directory, so don't bother with rename tracking.
2002 2002 if follow and not match.always() and not slowpath:
2003 2003 # _makefollowlogfilematcher expects its files argument to be
2004 2004 # relative to the repo root, so use match.files(), not pats.
2005 2005 filematcher = _makefollowlogfilematcher(repo, match.files(),
2006 2006 followfirst)
2007 2007 else:
2008 2008 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
2009 2009 if filematcher is None:
2010 2010 filematcher = lambda rev: match
2011 2011
2012 2012 expr = []
2013 2013 for op, val in sorted(opts.iteritems()):
2014 2014 if not val:
2015 2015 continue
2016 2016 if op not in opt2revset:
2017 2017 continue
2018 2018 revop, andor = opt2revset[op]
2019 2019 if '%(val)' not in revop:
2020 2020 expr.append(revop)
2021 2021 else:
2022 2022 if not isinstance(val, list):
2023 2023 e = revop % {'val': val}
2024 2024 else:
2025 2025 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
2026 2026 expr.append(e)
2027 2027
2028 2028 if expr:
2029 2029 expr = '(' + ' and '.join(expr) + ')'
2030 2030 else:
2031 2031 expr = None
2032 2032 return expr, filematcher
2033 2033
2034 2034 def _logrevs(repo, opts):
2035 2035 # Default --rev value depends on --follow but --follow behaviour
2036 2036 # depends on revisions resolved from --rev...
2037 2037 follow = opts.get('follow') or opts.get('follow_first')
2038 2038 if opts.get('rev'):
2039 2039 revs = scmutil.revrange(repo, opts['rev'])
2040 2040 elif follow and repo.dirstate.p1() == nullid:
2041 2041 revs = revset.baseset()
2042 2042 elif follow:
2043 2043 revs = repo.revs('reverse(:.)')
2044 2044 else:
2045 2045 revs = revset.spanset(repo)
2046 2046 revs.reverse()
2047 2047 return revs
2048 2048
2049 2049 def getgraphlogrevs(repo, pats, opts):
2050 2050 """Return (revs, expr, filematcher) where revs is an iterable of
2051 2051 revision numbers, expr is a revset string built from log options
2052 2052 and file patterns or None, and used to filter 'revs'. If --stat or
2053 2053 --patch are not passed filematcher is None. Otherwise it is a
2054 2054 callable taking a revision number and returning a match objects
2055 2055 filtering the files to be detailed when displaying the revision.
2056 2056 """
2057 2057 limit = loglimit(opts)
2058 2058 revs = _logrevs(repo, opts)
2059 2059 if not revs:
2060 2060 return revset.baseset(), None, None
2061 2061 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2062 2062 if opts.get('rev'):
2063 2063 # User-specified revs might be unsorted, but don't sort before
2064 2064 # _makelogrevset because it might depend on the order of revs
2065 2065 revs.sort(reverse=True)
2066 2066 if expr:
2067 2067 # Revset matchers often operate faster on revisions in changelog
2068 2068 # order, because most filters deal with the changelog.
2069 2069 revs.reverse()
2070 2070 matcher = revset.match(repo.ui, expr)
2071 2071 # Revset matches can reorder revisions. "A or B" typically returns
2072 2072 # returns the revision matching A then the revision matching B. Sort
2073 2073 # again to fix that.
2074 2074 revs = matcher(repo, revs)
2075 2075 revs.sort(reverse=True)
2076 2076 if limit is not None:
2077 2077 limitedrevs = []
2078 2078 for idx, rev in enumerate(revs):
2079 2079 if idx >= limit:
2080 2080 break
2081 2081 limitedrevs.append(rev)
2082 2082 revs = revset.baseset(limitedrevs)
2083 2083
2084 2084 return revs, expr, filematcher
2085 2085
2086 2086 def getlogrevs(repo, pats, opts):
2087 2087 """Return (revs, expr, filematcher) where revs is an iterable of
2088 2088 revision numbers, expr is a revset string built from log options
2089 2089 and file patterns or None, and used to filter 'revs'. If --stat or
2090 2090 --patch are not passed filematcher is None. Otherwise it is a
2091 2091 callable taking a revision number and returning a match objects
2092 2092 filtering the files to be detailed when displaying the revision.
2093 2093 """
2094 2094 limit = loglimit(opts)
2095 2095 revs = _logrevs(repo, opts)
2096 2096 if not revs:
2097 2097 return revset.baseset([]), None, None
2098 2098 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
2099 2099 if expr:
2100 2100 # Revset matchers often operate faster on revisions in changelog
2101 2101 # order, because most filters deal with the changelog.
2102 2102 if not opts.get('rev'):
2103 2103 revs.reverse()
2104 2104 matcher = revset.match(repo.ui, expr)
2105 2105 # Revset matches can reorder revisions. "A or B" typically returns
2106 2106 # returns the revision matching A then the revision matching B. Sort
2107 2107 # again to fix that.
2108 2108 revs = matcher(repo, revs)
2109 2109 if not opts.get('rev'):
2110 2110 revs.sort(reverse=True)
2111 2111 if limit is not None:
2112 2112 limitedrevs = []
2113 2113 for idx, r in enumerate(revs):
2114 2114 if limit <= idx:
2115 2115 break
2116 2116 limitedrevs.append(r)
2117 2117 revs = revset.baseset(limitedrevs)
2118 2118
2119 2119 return revs, expr, filematcher
2120 2120
2121 2121 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
2122 2122 filematcher=None):
2123 2123 seen, state = [], graphmod.asciistate()
2124 2124 for rev, type, ctx, parents in dag:
2125 2125 char = 'o'
2126 2126 if ctx.node() in showparents:
2127 2127 char = '@'
2128 2128 elif ctx.obsolete():
2129 2129 char = 'x'
2130 2130 elif ctx.closesbranch():
2131 2131 char = '_'
2132 2132 copies = None
2133 2133 if getrenamed and ctx.rev():
2134 2134 copies = []
2135 2135 for fn in ctx.files():
2136 2136 rename = getrenamed(fn, ctx.rev())
2137 2137 if rename:
2138 2138 copies.append((fn, rename[0]))
2139 2139 revmatchfn = None
2140 2140 if filematcher is not None:
2141 2141 revmatchfn = filematcher(ctx.rev())
2142 2142 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
2143 2143 lines = displayer.hunk.pop(rev).split('\n')
2144 2144 if not lines[-1]:
2145 2145 del lines[-1]
2146 2146 displayer.flush(rev)
2147 2147 edges = edgefn(type, char, lines, seen, rev, parents)
2148 2148 for type, char, lines, coldata in edges:
2149 2149 graphmod.ascii(ui, state, type, char, lines, coldata)
2150 2150 displayer.close()
2151 2151
2152 2152 def graphlog(ui, repo, *pats, **opts):
2153 2153 # Parameters are identical to log command ones
2154 2154 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
2155 2155 revdag = graphmod.dagwalker(repo, revs)
2156 2156
2157 2157 getrenamed = None
2158 2158 if opts.get('copies'):
2159 2159 endrev = None
2160 2160 if opts.get('rev'):
2161 2161 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
2162 2162 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
2163 2163 displayer = show_changeset(ui, repo, opts, buffered=True)
2164 2164 showparents = [ctx.node() for ctx in repo[None].parents()]
2165 2165 displaygraph(ui, revdag, displayer, showparents,
2166 2166 graphmod.asciiedges, getrenamed, filematcher)
2167 2167
2168 2168 def checkunsupportedgraphflags(pats, opts):
2169 2169 for op in ["newest_first"]:
2170 2170 if op in opts and opts[op]:
2171 2171 raise util.Abort(_("-G/--graph option is incompatible with --%s")
2172 2172 % op.replace("_", "-"))
2173 2173
2174 2174 def graphrevs(repo, nodes, opts):
2175 2175 limit = loglimit(opts)
2176 2176 nodes.reverse()
2177 2177 if limit is not None:
2178 2178 nodes = nodes[:limit]
2179 2179 return graphmod.nodes(repo, nodes)
2180 2180
2181 2181 def add(ui, repo, match, prefix, explicitonly, **opts):
2182 2182 join = lambda f: os.path.join(prefix, f)
2183 2183 bad = []
2184 2184
2185 2185 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2186 2186 names = []
2187 2187 wctx = repo[None]
2188 2188 cca = None
2189 2189 abort, warn = scmutil.checkportabilityalert(ui)
2190 2190 if abort or warn:
2191 2191 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2192 2192
2193 2193 for f in wctx.walk(matchmod.badmatch(match, badfn)):
2194 2194 exact = match.exact(f)
2195 2195 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2196 2196 if cca:
2197 2197 cca(f)
2198 2198 names.append(f)
2199 2199 if ui.verbose or not exact:
2200 2200 ui.status(_('adding %s\n') % match.rel(f))
2201 2201
2202 2202 for subpath in sorted(wctx.substate):
2203 2203 sub = wctx.sub(subpath)
2204 2204 try:
2205 2205 submatch = matchmod.narrowmatcher(subpath, match)
2206 2206 if opts.get('subrepos'):
2207 2207 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
2208 2208 else:
2209 2209 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
2210 2210 except error.LookupError:
2211 2211 ui.status(_("skipping missing subrepository: %s\n")
2212 2212 % join(subpath))
2213 2213
2214 2214 if not opts.get('dry_run'):
2215 2215 rejected = wctx.add(names, prefix)
2216 2216 bad.extend(f for f in rejected if f in match.files())
2217 2217 return bad
2218 2218
2219 2219 def forget(ui, repo, match, prefix, explicitonly):
2220 2220 join = lambda f: os.path.join(prefix, f)
2221 2221 bad = []
2222 2222 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2223 2223 wctx = repo[None]
2224 2224 forgot = []
2225 2225
2226 2226 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2227 2227 forget = sorted(s[0] + s[1] + s[3] + s[6])
2228 2228 if explicitonly:
2229 2229 forget = [f for f in forget if match.exact(f)]
2230 2230
2231 2231 for subpath in sorted(wctx.substate):
2232 2232 sub = wctx.sub(subpath)
2233 2233 try:
2234 2234 submatch = matchmod.narrowmatcher(subpath, match)
2235 2235 subbad, subforgot = sub.forget(submatch, prefix)
2236 2236 bad.extend([subpath + '/' + f for f in subbad])
2237 2237 forgot.extend([subpath + '/' + f for f in subforgot])
2238 2238 except error.LookupError:
2239 2239 ui.status(_("skipping missing subrepository: %s\n")
2240 2240 % join(subpath))
2241 2241
2242 2242 if not explicitonly:
2243 2243 for f in match.files():
2244 2244 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2245 2245 if f not in forgot:
2246 2246 if repo.wvfs.exists(f):
2247 2247 # Don't complain if the exact case match wasn't given.
2248 2248 # But don't do this until after checking 'forgot', so
2249 2249 # that subrepo files aren't normalized, and this op is
2250 2250 # purely from data cached by the status walk above.
2251 2251 if repo.dirstate.normalize(f) in repo.dirstate:
2252 2252 continue
2253 2253 ui.warn(_('not removing %s: '
2254 2254 'file is already untracked\n')
2255 2255 % match.rel(f))
2256 2256 bad.append(f)
2257 2257
2258 2258 for f in forget:
2259 2259 if ui.verbose or not match.exact(f):
2260 2260 ui.status(_('removing %s\n') % match.rel(f))
2261 2261
2262 2262 rejected = wctx.forget(forget, prefix)
2263 2263 bad.extend(f for f in rejected if f in match.files())
2264 2264 forgot.extend(f for f in forget if f not in rejected)
2265 2265 return bad, forgot
2266 2266
2267 2267 def files(ui, ctx, m, fm, fmt, subrepos):
2268 2268 rev = ctx.rev()
2269 2269 ret = 1
2270 2270 ds = ctx.repo().dirstate
2271 2271
2272 2272 for f in ctx.matches(m):
2273 2273 if rev is None and ds[f] == 'r':
2274 2274 continue
2275 2275 fm.startitem()
2276 2276 if ui.verbose:
2277 2277 fc = ctx[f]
2278 2278 fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
2279 2279 fm.data(abspath=f)
2280 2280 fm.write('path', fmt, m.rel(f))
2281 2281 ret = 0
2282 2282
2283 2283 for subpath in sorted(ctx.substate):
2284 2284 def matchessubrepo(subpath):
2285 2285 return (m.always() or m.exact(subpath)
2286 2286 or any(f.startswith(subpath + '/') for f in m.files()))
2287 2287
2288 2288 if subrepos or matchessubrepo(subpath):
2289 2289 sub = ctx.sub(subpath)
2290 2290 try:
2291 2291 submatch = matchmod.narrowmatcher(subpath, m)
2292 2292 if sub.printfiles(ui, submatch, fm, fmt, subrepos) == 0:
2293 2293 ret = 0
2294 2294 except error.LookupError:
2295 2295 ui.status(_("skipping missing subrepository: %s\n")
2296 2296 % m.abs(subpath))
2297 2297
2298 2298 return ret
2299 2299
2300 2300 def remove(ui, repo, m, prefix, after, force, subrepos):
2301 2301 join = lambda f: os.path.join(prefix, f)
2302 2302 ret = 0
2303 2303 s = repo.status(match=m, clean=True)
2304 2304 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2305 2305
2306 2306 wctx = repo[None]
2307 2307
2308 2308 for subpath in sorted(wctx.substate):
2309 2309 def matchessubrepo(matcher, subpath):
2310 2310 if matcher.exact(subpath):
2311 2311 return True
2312 2312 for f in matcher.files():
2313 2313 if f.startswith(subpath):
2314 2314 return True
2315 2315 return False
2316 2316
2317 2317 if subrepos or matchessubrepo(m, subpath):
2318 2318 sub = wctx.sub(subpath)
2319 2319 try:
2320 2320 submatch = matchmod.narrowmatcher(subpath, m)
2321 2321 if sub.removefiles(submatch, prefix, after, force, subrepos):
2322 2322 ret = 1
2323 2323 except error.LookupError:
2324 2324 ui.status(_("skipping missing subrepository: %s\n")
2325 2325 % join(subpath))
2326 2326
2327 2327 # warn about failure to delete explicit files/dirs
2328 2328 deleteddirs = util.dirs(deleted)
2329 2329 for f in m.files():
2330 2330 def insubrepo():
2331 2331 for subpath in wctx.substate:
2332 2332 if f.startswith(subpath):
2333 2333 return True
2334 2334 return False
2335 2335
2336 2336 isdir = f in deleteddirs or wctx.hasdir(f)
2337 2337 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2338 2338 continue
2339 2339
2340 2340 if repo.wvfs.exists(f):
2341 2341 if repo.wvfs.isdir(f):
2342 2342 ui.warn(_('not removing %s: no tracked files\n')
2343 2343 % m.rel(f))
2344 2344 else:
2345 2345 ui.warn(_('not removing %s: file is untracked\n')
2346 2346 % m.rel(f))
2347 2347 # missing files will generate a warning elsewhere
2348 2348 ret = 1
2349 2349
2350 2350 if force:
2351 2351 list = modified + deleted + clean + added
2352 2352 elif after:
2353 2353 list = deleted
2354 2354 for f in modified + added + clean:
2355 2355 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2356 2356 ret = 1
2357 2357 else:
2358 2358 list = deleted + clean
2359 2359 for f in modified:
2360 2360 ui.warn(_('not removing %s: file is modified (use -f'
2361 2361 ' to force removal)\n') % m.rel(f))
2362 2362 ret = 1
2363 2363 for f in added:
2364 2364 ui.warn(_('not removing %s: file has been marked for add'
2365 2365 ' (use forget to undo)\n') % m.rel(f))
2366 2366 ret = 1
2367 2367
2368 2368 for f in sorted(list):
2369 2369 if ui.verbose or not m.exact(f):
2370 2370 ui.status(_('removing %s\n') % m.rel(f))
2371 2371
2372 2372 wlock = repo.wlock()
2373 2373 try:
2374 2374 if not after:
2375 2375 for f in list:
2376 2376 if f in added:
2377 2377 continue # we never unlink added files on remove
2378 2378 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2379 2379 repo[None].forget(list)
2380 2380 finally:
2381 2381 wlock.release()
2382 2382
2383 2383 return ret
2384 2384
2385 2385 def cat(ui, repo, ctx, matcher, prefix, **opts):
2386 2386 err = 1
2387 2387
2388 2388 def write(path):
2389 2389 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2390 2390 pathname=os.path.join(prefix, path))
2391 2391 data = ctx[path].data()
2392 2392 if opts.get('decode'):
2393 2393 data = repo.wwritedata(path, data)
2394 2394 fp.write(data)
2395 2395 fp.close()
2396 2396
2397 2397 # Automation often uses hg cat on single files, so special case it
2398 2398 # for performance to avoid the cost of parsing the manifest.
2399 2399 if len(matcher.files()) == 1 and not matcher.anypats():
2400 2400 file = matcher.files()[0]
2401 2401 mf = repo.manifest
2402 2402 mfnode = ctx.manifestnode()
2403 2403 if mfnode and mf.find(mfnode, file)[0]:
2404 2404 write(file)
2405 2405 return 0
2406 2406
2407 2407 # Don't warn about "missing" files that are really in subrepos
2408 2408 def badfn(path, msg):
2409 2409 for subpath in ctx.substate:
2410 2410 if path.startswith(subpath):
2411 2411 return
2412 2412 matcher.bad(path, msg)
2413 2413
2414 2414 for abs in ctx.walk(matchmod.badmatch(matcher, badfn)):
2415 2415 write(abs)
2416 2416 err = 0
2417 2417
2418 2418 for subpath in sorted(ctx.substate):
2419 2419 sub = ctx.sub(subpath)
2420 2420 try:
2421 2421 submatch = matchmod.narrowmatcher(subpath, matcher)
2422 2422
2423 2423 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2424 2424 **opts):
2425 2425 err = 0
2426 2426 except error.RepoLookupError:
2427 2427 ui.status(_("skipping missing subrepository: %s\n")
2428 2428 % os.path.join(prefix, subpath))
2429 2429
2430 2430 return err
2431 2431
2432 2432 def commit(ui, repo, commitfunc, pats, opts):
2433 2433 '''commit the specified files or all outstanding changes'''
2434 2434 date = opts.get('date')
2435 2435 if date:
2436 2436 opts['date'] = util.parsedate(date)
2437 2437 message = logmessage(ui, opts)
2438 2438 matcher = scmutil.match(repo[None], pats, opts)
2439 2439
2440 2440 # extract addremove carefully -- this function can be called from a command
2441 2441 # that doesn't support addremove
2442 2442 if opts.get('addremove'):
2443 2443 if scmutil.addremove(repo, matcher, "", opts) != 0:
2444 2444 raise util.Abort(
2445 2445 _("failed to mark all new/missing files as added/removed"))
2446 2446
2447 2447 return commitfunc(ui, repo, message, matcher, opts)
2448 2448
2449 2449 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2450 2450 # amend will reuse the existing user if not specified, but the obsolete
2451 2451 # marker creation requires that the current user's name is specified.
2452 2452 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2453 2453 ui.username() # raise exception if username not set
2454 2454
2455 2455 ui.note(_('amending changeset %s\n') % old)
2456 2456 base = old.p1()
2457 2457 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2458 2458
2459 2459 wlock = dsguard = lock = newid = None
2460 2460 try:
2461 2461 wlock = repo.wlock()
2462 2462 dsguard = dirstateguard(repo, 'amend')
2463 2463 lock = repo.lock()
2464 2464 tr = repo.transaction('amend')
2465 2465 try:
2466 2466 # See if we got a message from -m or -l, if not, open the editor
2467 2467 # with the message of the changeset to amend
2468 2468 message = logmessage(ui, opts)
2469 2469 # ensure logfile does not conflict with later enforcement of the
2470 2470 # message. potential logfile content has been processed by
2471 2471 # `logmessage` anyway.
2472 2472 opts.pop('logfile')
2473 2473 # First, do a regular commit to record all changes in the working
2474 2474 # directory (if there are any)
2475 2475 ui.callhooks = False
2476 2476 activebookmark = repo._activebookmark
2477 2477 try:
2478 2478 repo._activebookmark = None
2479 2479 opts['message'] = 'temporary amend commit for %s' % old
2480 2480 node = commit(ui, repo, commitfunc, pats, opts)
2481 2481 finally:
2482 2482 repo._activebookmark = activebookmark
2483 2483 ui.callhooks = True
2484 2484 ctx = repo[node]
2485 2485
2486 2486 # Participating changesets:
2487 2487 #
2488 2488 # node/ctx o - new (intermediate) commit that contains changes
2489 2489 # | from working dir to go into amending commit
2490 2490 # | (or a workingctx if there were no changes)
2491 2491 # |
2492 2492 # old o - changeset to amend
2493 2493 # |
2494 2494 # base o - parent of amending changeset
2495 2495
2496 2496 # Update extra dict from amended commit (e.g. to preserve graft
2497 2497 # source)
2498 2498 extra.update(old.extra())
2499 2499
2500 2500 # Also update it from the intermediate commit or from the wctx
2501 2501 extra.update(ctx.extra())
2502 2502
2503 2503 if len(old.parents()) > 1:
2504 2504 # ctx.files() isn't reliable for merges, so fall back to the
2505 2505 # slower repo.status() method
2506 2506 files = set([fn for st in repo.status(base, old)[:3]
2507 2507 for fn in st])
2508 2508 else:
2509 2509 files = set(old.files())
2510 2510
2511 2511 # Second, we use either the commit we just did, or if there were no
2512 2512 # changes the parent of the working directory as the version of the
2513 2513 # files in the final amend commit
2514 2514 if node:
2515 2515 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2516 2516
2517 2517 user = ctx.user()
2518 2518 date = ctx.date()
2519 2519 # Recompute copies (avoid recording a -> b -> a)
2520 2520 copied = copies.pathcopies(base, ctx)
2521 2521 if old.p2:
2522 2522 copied.update(copies.pathcopies(old.p2(), ctx))
2523 2523
2524 2524 # Prune files which were reverted by the updates: if old
2525 2525 # introduced file X and our intermediate commit, node,
2526 2526 # renamed that file, then those two files are the same and
2527 2527 # we can discard X from our list of files. Likewise if X
2528 2528 # was deleted, it's no longer relevant
2529 2529 files.update(ctx.files())
2530 2530
2531 2531 def samefile(f):
2532 2532 if f in ctx.manifest():
2533 2533 a = ctx.filectx(f)
2534 2534 if f in base.manifest():
2535 2535 b = base.filectx(f)
2536 2536 return (not a.cmp(b)
2537 2537 and a.flags() == b.flags())
2538 2538 else:
2539 2539 return False
2540 2540 else:
2541 2541 return f not in base.manifest()
2542 2542 files = [f for f in files if not samefile(f)]
2543 2543
2544 2544 def filectxfn(repo, ctx_, path):
2545 2545 try:
2546 2546 fctx = ctx[path]
2547 2547 flags = fctx.flags()
2548 2548 mctx = context.memfilectx(repo,
2549 2549 fctx.path(), fctx.data(),
2550 2550 islink='l' in flags,
2551 2551 isexec='x' in flags,
2552 2552 copied=copied.get(path))
2553 2553 return mctx
2554 2554 except KeyError:
2555 2555 return None
2556 2556 else:
2557 2557 ui.note(_('copying changeset %s to %s\n') % (old, base))
2558 2558
2559 2559 # Use version of files as in the old cset
2560 2560 def filectxfn(repo, ctx_, path):
2561 2561 try:
2562 2562 return old.filectx(path)
2563 2563 except KeyError:
2564 2564 return None
2565 2565
2566 2566 user = opts.get('user') or old.user()
2567 2567 date = opts.get('date') or old.date()
2568 2568 editform = mergeeditform(old, 'commit.amend')
2569 2569 editor = getcommiteditor(editform=editform, **opts)
2570 2570 if not message:
2571 2571 editor = getcommiteditor(edit=True, editform=editform)
2572 2572 message = old.description()
2573 2573
2574 2574 pureextra = extra.copy()
2575 2575 extra['amend_source'] = old.hex()
2576 2576
2577 2577 new = context.memctx(repo,
2578 2578 parents=[base.node(), old.p2().node()],
2579 2579 text=message,
2580 2580 files=files,
2581 2581 filectxfn=filectxfn,
2582 2582 user=user,
2583 2583 date=date,
2584 2584 extra=extra,
2585 2585 editor=editor)
2586 2586
2587 2587 newdesc = changelog.stripdesc(new.description())
2588 2588 if ((not node)
2589 2589 and newdesc == old.description()
2590 2590 and user == old.user()
2591 2591 and date == old.date()
2592 2592 and pureextra == old.extra()):
2593 2593 # nothing changed. continuing here would create a new node
2594 2594 # anyway because of the amend_source noise.
2595 2595 #
2596 2596 # This not what we expect from amend.
2597 2597 return old.node()
2598 2598
2599 2599 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2600 2600 try:
2601 2601 if opts.get('secret'):
2602 2602 commitphase = 'secret'
2603 2603 else:
2604 2604 commitphase = old.phase()
2605 2605 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2606 2606 newid = repo.commitctx(new)
2607 2607 finally:
2608 2608 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2609 2609 if newid != old.node():
2610 2610 # Reroute the working copy parent to the new changeset
2611 2611 repo.setparents(newid, nullid)
2612 2612
2613 2613 # Move bookmarks from old parent to amend commit
2614 2614 bms = repo.nodebookmarks(old.node())
2615 2615 if bms:
2616 2616 marks = repo._bookmarks
2617 2617 for bm in bms:
2618 2618 ui.debug('moving bookmarks %r from %s to %s\n' %
2619 2619 (marks, old.hex(), hex(newid)))
2620 2620 marks[bm] = newid
2621 2621 marks.recordchange(tr)
2622 2622 #commit the whole amend process
2623 2623 if createmarkers:
2624 2624 # mark the new changeset as successor of the rewritten one
2625 2625 new = repo[newid]
2626 2626 obs = [(old, (new,))]
2627 2627 if node:
2628 2628 obs.append((ctx, ()))
2629 2629
2630 2630 obsolete.createmarkers(repo, obs)
2631 2631 tr.close()
2632 2632 finally:
2633 2633 tr.release()
2634 2634 dsguard.close()
2635 2635 if not createmarkers and newid != old.node():
2636 2636 # Strip the intermediate commit (if there was one) and the amended
2637 2637 # commit
2638 2638 if node:
2639 2639 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2640 2640 ui.note(_('stripping amended changeset %s\n') % old)
2641 2641 repair.strip(ui, repo, old.node(), topic='amend-backup')
2642 2642 finally:
2643 2643 lockmod.release(lock, dsguard, wlock)
2644 2644 return newid
2645 2645
2646 2646 def commiteditor(repo, ctx, subs, editform=''):
2647 2647 if ctx.description():
2648 2648 return ctx.description()
2649 2649 return commitforceeditor(repo, ctx, subs, editform=editform)
2650 2650
2651 2651 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2652 2652 editform=''):
2653 2653 if not extramsg:
2654 2654 extramsg = _("Leave message empty to abort commit.")
2655 2655
2656 2656 forms = [e for e in editform.split('.') if e]
2657 2657 forms.insert(0, 'changeset')
2658 2658 while forms:
2659 2659 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2660 2660 if tmpl:
2661 2661 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2662 2662 break
2663 2663 forms.pop()
2664 2664 else:
2665 2665 committext = buildcommittext(repo, ctx, subs, extramsg)
2666 2666
2667 2667 # run editor in the repository root
2668 2668 olddir = os.getcwd()
2669 2669 os.chdir(repo.root)
2670 2670 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2671 2671 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2672 2672 os.chdir(olddir)
2673 2673
2674 2674 if finishdesc:
2675 2675 text = finishdesc(text)
2676 2676 if not text.strip():
2677 2677 raise util.Abort(_("empty commit message"))
2678 2678
2679 2679 return text
2680 2680
2681 2681 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2682 2682 ui = repo.ui
2683 2683 tmpl, mapfile = gettemplate(ui, tmpl, None)
2684 2684
2685 2685 try:
2686 2686 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2687 2687 except SyntaxError as inst:
2688 2688 raise util.Abort(inst.args[0])
2689 2689
2690 2690 for k, v in repo.ui.configitems('committemplate'):
2691 2691 if k != 'changeset':
2692 2692 t.t.cache[k] = v
2693 2693
2694 2694 if not extramsg:
2695 2695 extramsg = '' # ensure that extramsg is string
2696 2696
2697 2697 ui.pushbuffer()
2698 2698 t.show(ctx, extramsg=extramsg)
2699 2699 return ui.popbuffer()
2700 2700
2701 2701 def buildcommittext(repo, ctx, subs, extramsg):
2702 2702 edittext = []
2703 2703 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2704 2704 if ctx.description():
2705 2705 edittext.append(ctx.description())
2706 2706 edittext.append("")
2707 2707 edittext.append("") # Empty line between message and comments.
2708 2708 edittext.append(_("HG: Enter commit message."
2709 2709 " Lines beginning with 'HG:' are removed."))
2710 2710 edittext.append("HG: %s" % extramsg)
2711 2711 edittext.append("HG: --")
2712 2712 edittext.append(_("HG: user: %s") % ctx.user())
2713 2713 if ctx.p2():
2714 2714 edittext.append(_("HG: branch merge"))
2715 2715 if ctx.branch():
2716 2716 edittext.append(_("HG: branch '%s'") % ctx.branch())
2717 2717 if bookmarks.isactivewdirparent(repo):
2718 2718 edittext.append(_("HG: bookmark '%s'") % repo._activebookmark)
2719 2719 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2720 2720 edittext.extend([_("HG: added %s") % f for f in added])
2721 2721 edittext.extend([_("HG: changed %s") % f for f in modified])
2722 2722 edittext.extend([_("HG: removed %s") % f for f in removed])
2723 2723 if not added and not modified and not removed:
2724 2724 edittext.append(_("HG: no files changed"))
2725 2725 edittext.append("")
2726 2726
2727 2727 return "\n".join(edittext)
2728 2728
2729 2729 def commitstatus(repo, node, branch, bheads=None, opts={}):
2730 2730 ctx = repo[node]
2731 2731 parents = ctx.parents()
2732 2732
2733 2733 if (not opts.get('amend') and bheads and node not in bheads and not
2734 2734 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2735 2735 repo.ui.status(_('created new head\n'))
2736 2736 # The message is not printed for initial roots. For the other
2737 2737 # changesets, it is printed in the following situations:
2738 2738 #
2739 2739 # Par column: for the 2 parents with ...
2740 2740 # N: null or no parent
2741 2741 # B: parent is on another named branch
2742 2742 # C: parent is a regular non head changeset
2743 2743 # H: parent was a branch head of the current branch
2744 2744 # Msg column: whether we print "created new head" message
2745 2745 # In the following, it is assumed that there already exists some
2746 2746 # initial branch heads of the current branch, otherwise nothing is
2747 2747 # printed anyway.
2748 2748 #
2749 2749 # Par Msg Comment
2750 2750 # N N y additional topo root
2751 2751 #
2752 2752 # B N y additional branch root
2753 2753 # C N y additional topo head
2754 2754 # H N n usual case
2755 2755 #
2756 2756 # B B y weird additional branch root
2757 2757 # C B y branch merge
2758 2758 # H B n merge with named branch
2759 2759 #
2760 2760 # C C y additional head from merge
2761 2761 # C H n merge with a head
2762 2762 #
2763 2763 # H H n head merge: head count decreases
2764 2764
2765 2765 if not opts.get('close_branch'):
2766 2766 for r in parents:
2767 2767 if r.closesbranch() and r.branch() == branch:
2768 2768 repo.ui.status(_('reopening closed branch head %d\n') % r)
2769 2769
2770 2770 if repo.ui.debugflag:
2771 2771 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2772 2772 elif repo.ui.verbose:
2773 2773 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2774 2774
2775 2775 def revert(ui, repo, ctx, parents, *pats, **opts):
2776 2776 parent, p2 = parents
2777 2777 node = ctx.node()
2778 2778
2779 2779 mf = ctx.manifest()
2780 2780 if node == p2:
2781 2781 parent = p2
2782 2782 if node == parent:
2783 2783 pmf = mf
2784 2784 else:
2785 2785 pmf = None
2786 2786
2787 2787 # need all matching names in dirstate and manifest of target rev,
2788 2788 # so have to walk both. do not print errors if files exist in one
2789 2789 # but not other. in both cases, filesets should be evaluated against
2790 2790 # workingctx to get consistent result (issue4497). this means 'set:**'
2791 2791 # cannot be used to select missing files from target rev.
2792 2792
2793 2793 # `names` is a mapping for all elements in working copy and target revision
2794 2794 # The mapping is in the form:
2795 2795 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2796 2796 names = {}
2797 2797
2798 2798 wlock = repo.wlock()
2799 2799 try:
2800 2800 ## filling of the `names` mapping
2801 2801 # walk dirstate to fill `names`
2802 2802
2803 2803 interactive = opts.get('interactive', False)
2804 2804 wctx = repo[None]
2805 2805 m = scmutil.match(wctx, pats, opts)
2806 2806
2807 2807 # we'll need this later
2808 2808 targetsubs = sorted(s for s in wctx.substate if m(s))
2809 2809
2810 2810 if not m.always():
2811 2811 for abs in repo.walk(matchmod.badmatch(m, lambda x, y: False)):
2812 2812 names[abs] = m.rel(abs), m.exact(abs)
2813 2813
2814 2814 # walk target manifest to fill `names`
2815 2815
2816 2816 def badfn(path, msg):
2817 2817 if path in names:
2818 2818 return
2819 2819 if path in ctx.substate:
2820 2820 return
2821 2821 path_ = path + '/'
2822 2822 for f in names:
2823 2823 if f.startswith(path_):
2824 2824 return
2825 2825 ui.warn("%s: %s\n" % (m.rel(path), msg))
2826 2826
2827 2827 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
2828 2828 if abs not in names:
2829 2829 names[abs] = m.rel(abs), m.exact(abs)
2830 2830
2831 2831 # Find status of all file in `names`.
2832 2832 m = scmutil.matchfiles(repo, names)
2833 2833
2834 2834 changes = repo.status(node1=node, match=m,
2835 2835 unknown=True, ignored=True, clean=True)
2836 2836 else:
2837 2837 changes = repo.status(node1=node, match=m)
2838 2838 for kind in changes:
2839 2839 for abs in kind:
2840 2840 names[abs] = m.rel(abs), m.exact(abs)
2841 2841
2842 2842 m = scmutil.matchfiles(repo, names)
2843 2843
2844 2844 modified = set(changes.modified)
2845 2845 added = set(changes.added)
2846 2846 removed = set(changes.removed)
2847 2847 _deleted = set(changes.deleted)
2848 2848 unknown = set(changes.unknown)
2849 2849 unknown.update(changes.ignored)
2850 2850 clean = set(changes.clean)
2851 2851 modadded = set()
2852 2852
2853 2853 # split between files known in target manifest and the others
2854 2854 smf = set(mf)
2855 2855
2856 2856 # determine the exact nature of the deleted changesets
2857 2857 deladded = _deleted - smf
2858 2858 deleted = _deleted - deladded
2859 2859
2860 2860 # We need to account for the state of the file in the dirstate,
2861 2861 # even when we revert against something else than parent. This will
2862 2862 # slightly alter the behavior of revert (doing back up or not, delete
2863 2863 # or just forget etc).
2864 2864 if parent == node:
2865 2865 dsmodified = modified
2866 2866 dsadded = added
2867 2867 dsremoved = removed
2868 2868 # store all local modifications, useful later for rename detection
2869 2869 localchanges = dsmodified | dsadded
2870 2870 modified, added, removed = set(), set(), set()
2871 2871 else:
2872 2872 changes = repo.status(node1=parent, match=m)
2873 2873 dsmodified = set(changes.modified)
2874 2874 dsadded = set(changes.added)
2875 2875 dsremoved = set(changes.removed)
2876 2876 # store all local modifications, useful later for rename detection
2877 2877 localchanges = dsmodified | dsadded
2878 2878
2879 2879 # only take into account for removes between wc and target
2880 2880 clean |= dsremoved - removed
2881 2881 dsremoved &= removed
2882 2882 # distinct between dirstate remove and other
2883 2883 removed -= dsremoved
2884 2884
2885 2885 modadded = added & dsmodified
2886 2886 added -= modadded
2887 2887
2888 2888 # tell newly modified apart.
2889 2889 dsmodified &= modified
2890 2890 dsmodified |= modified & dsadded # dirstate added may needs backup
2891 2891 modified -= dsmodified
2892 2892
2893 2893 # We need to wait for some post-processing to update this set
2894 2894 # before making the distinction. The dirstate will be used for
2895 2895 # that purpose.
2896 2896 dsadded = added
2897 2897
2898 2898 # in case of merge, files that are actually added can be reported as
2899 2899 # modified, we need to post process the result
2900 2900 if p2 != nullid:
2901 2901 if pmf is None:
2902 2902 # only need parent manifest in the merge case,
2903 2903 # so do not read by default
2904 2904 pmf = repo[parent].manifest()
2905 2905 mergeadd = dsmodified - set(pmf)
2906 2906 dsadded |= mergeadd
2907 2907 dsmodified -= mergeadd
2908 2908
2909 2909 # if f is a rename, update `names` to also revert the source
2910 2910 cwd = repo.getcwd()
2911 2911 for f in localchanges:
2912 2912 src = repo.dirstate.copied(f)
2913 2913 # XXX should we check for rename down to target node?
2914 2914 if src and src not in names and repo.dirstate[src] == 'r':
2915 2915 dsremoved.add(src)
2916 2916 names[src] = (repo.pathto(src, cwd), True)
2917 2917
2918 2918 # distinguish between file to forget and the other
2919 2919 added = set()
2920 2920 for abs in dsadded:
2921 2921 if repo.dirstate[abs] != 'a':
2922 2922 added.add(abs)
2923 2923 dsadded -= added
2924 2924
2925 2925 for abs in deladded:
2926 2926 if repo.dirstate[abs] == 'a':
2927 2927 dsadded.add(abs)
2928 2928 deladded -= dsadded
2929 2929
2930 2930 # For files marked as removed, we check if an unknown file is present at
2931 2931 # the same path. If a such file exists it may need to be backed up.
2932 2932 # Making the distinction at this stage helps have simpler backup
2933 2933 # logic.
2934 2934 removunk = set()
2935 2935 for abs in removed:
2936 2936 target = repo.wjoin(abs)
2937 2937 if os.path.lexists(target):
2938 2938 removunk.add(abs)
2939 2939 removed -= removunk
2940 2940
2941 2941 dsremovunk = set()
2942 2942 for abs in dsremoved:
2943 2943 target = repo.wjoin(abs)
2944 2944 if os.path.lexists(target):
2945 2945 dsremovunk.add(abs)
2946 2946 dsremoved -= dsremovunk
2947 2947
2948 2948 # action to be actually performed by revert
2949 2949 # (<list of file>, message>) tuple
2950 2950 actions = {'revert': ([], _('reverting %s\n')),
2951 2951 'add': ([], _('adding %s\n')),
2952 2952 'remove': ([], _('removing %s\n')),
2953 2953 'drop': ([], _('removing %s\n')),
2954 2954 'forget': ([], _('forgetting %s\n')),
2955 2955 'undelete': ([], _('undeleting %s\n')),
2956 2956 'noop': (None, _('no changes needed to %s\n')),
2957 2957 'unknown': (None, _('file not managed: %s\n')),
2958 2958 }
2959 2959
2960 2960 # "constant" that convey the backup strategy.
2961 2961 # All set to `discard` if `no-backup` is set do avoid checking
2962 2962 # no_backup lower in the code.
2963 2963 # These values are ordered for comparison purposes
2964 2964 backup = 2 # unconditionally do backup
2965 2965 check = 1 # check if the existing file differs from target
2966 2966 discard = 0 # never do backup
2967 2967 if opts.get('no_backup'):
2968 2968 backup = check = discard
2969 2969
2970 2970 backupanddel = actions['remove']
2971 2971 if not opts.get('no_backup'):
2972 2972 backupanddel = actions['drop']
2973 2973
2974 2974 disptable = (
2975 2975 # dispatch table:
2976 2976 # file state
2977 2977 # action
2978 2978 # make backup
2979 2979
2980 2980 ## Sets that results that will change file on disk
2981 2981 # Modified compared to target, no local change
2982 2982 (modified, actions['revert'], discard),
2983 2983 # Modified compared to target, but local file is deleted
2984 2984 (deleted, actions['revert'], discard),
2985 2985 # Modified compared to target, local change
2986 2986 (dsmodified, actions['revert'], backup),
2987 2987 # Added since target
2988 2988 (added, actions['remove'], discard),
2989 2989 # Added in working directory
2990 2990 (dsadded, actions['forget'], discard),
2991 2991 # Added since target, have local modification
2992 2992 (modadded, backupanddel, backup),
2993 2993 # Added since target but file is missing in working directory
2994 2994 (deladded, actions['drop'], discard),
2995 2995 # Removed since target, before working copy parent
2996 2996 (removed, actions['add'], discard),
2997 2997 # Same as `removed` but an unknown file exists at the same path
2998 2998 (removunk, actions['add'], check),
2999 2999 # Removed since targe, marked as such in working copy parent
3000 3000 (dsremoved, actions['undelete'], discard),
3001 3001 # Same as `dsremoved` but an unknown file exists at the same path
3002 3002 (dsremovunk, actions['undelete'], check),
3003 3003 ## the following sets does not result in any file changes
3004 3004 # File with no modification
3005 3005 (clean, actions['noop'], discard),
3006 3006 # Existing file, not tracked anywhere
3007 3007 (unknown, actions['unknown'], discard),
3008 3008 )
3009 3009
3010 3010 for abs, (rel, exact) in sorted(names.items()):
3011 3011 # target file to be touch on disk (relative to cwd)
3012 3012 target = repo.wjoin(abs)
3013 3013 # search the entry in the dispatch table.
3014 3014 # if the file is in any of these sets, it was touched in the working
3015 3015 # directory parent and we are sure it needs to be reverted.
3016 3016 for table, (xlist, msg), dobackup in disptable:
3017 3017 if abs not in table:
3018 3018 continue
3019 3019 if xlist is not None:
3020 3020 xlist.append(abs)
3021 3021 if dobackup and (backup <= dobackup
3022 3022 or wctx[abs].cmp(ctx[abs])):
3023 3023 bakname = "%s.orig" % rel
3024 3024 ui.note(_('saving current version of %s as %s\n') %
3025 3025 (rel, bakname))
3026 3026 if not opts.get('dry_run'):
3027 3027 if interactive:
3028 3028 util.copyfile(target, bakname)
3029 3029 else:
3030 3030 util.rename(target, bakname)
3031 3031 if ui.verbose or not exact:
3032 3032 if not isinstance(msg, basestring):
3033 3033 msg = msg(abs)
3034 3034 ui.status(msg % rel)
3035 3035 elif exact:
3036 3036 ui.warn(msg % rel)
3037 3037 break
3038 3038
3039 3039 if not opts.get('dry_run'):
3040 3040 needdata = ('revert', 'add', 'undelete')
3041 3041 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
3042 3042 _performrevert(repo, parents, ctx, actions, interactive)
3043 3043
3044 3044 if targetsubs:
3045 3045 # Revert the subrepos on the revert list
3046 3046 for sub in targetsubs:
3047 3047 try:
3048 3048 wctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
3049 3049 except KeyError:
3050 3050 raise util.Abort("subrepository '%s' does not exist in %s!"
3051 3051 % (sub, short(ctx.node())))
3052 3052 finally:
3053 3053 wlock.release()
3054 3054
3055 3055 def _revertprefetch(repo, ctx, *files):
3056 3056 """Let extension changing the storage layer prefetch content"""
3057 3057 pass
3058 3058
3059 3059 def _performrevert(repo, parents, ctx, actions, interactive=False):
3060 3060 """function that actually perform all the actions computed for revert
3061 3061
3062 3062 This is an independent function to let extension to plug in and react to
3063 3063 the imminent revert.
3064 3064
3065 3065 Make sure you have the working directory locked when calling this function.
3066 3066 """
3067 3067 parent, p2 = parents
3068 3068 node = ctx.node()
3069 3069 def checkout(f):
3070 3070 fc = ctx[f]
3071 3071 return repo.wwrite(f, fc.data(), fc.flags())
3072 3072
3073 3073 audit_path = pathutil.pathauditor(repo.root)
3074 3074 for f in actions['forget'][0]:
3075 3075 repo.dirstate.drop(f)
3076 3076 for f in actions['remove'][0]:
3077 3077 audit_path(f)
3078 3078 try:
3079 3079 util.unlinkpath(repo.wjoin(f))
3080 3080 except OSError:
3081 3081 pass
3082 3082 repo.dirstate.remove(f)
3083 3083 for f in actions['drop'][0]:
3084 3084 audit_path(f)
3085 3085 repo.dirstate.remove(f)
3086 3086
3087 3087 normal = None
3088 3088 if node == parent:
3089 3089 # We're reverting to our parent. If possible, we'd like status
3090 3090 # to report the file as clean. We have to use normallookup for
3091 3091 # merges to avoid losing information about merged/dirty files.
3092 3092 if p2 != nullid:
3093 3093 normal = repo.dirstate.normallookup
3094 3094 else:
3095 3095 normal = repo.dirstate.normal
3096 3096
3097 3097 newlyaddedandmodifiedfiles = set()
3098 3098 if interactive:
3099 3099 # Prompt the user for changes to revert
3100 3100 torevert = [repo.wjoin(f) for f in actions['revert'][0]]
3101 3101 m = scmutil.match(ctx, torevert, {})
3102 3102 diffopts = patch.difffeatureopts(repo.ui, whitespace=True)
3103 3103 diffopts.nodates = True
3104 3104 diffopts.git = True
3105 3105 reversehunks = repo.ui.configbool('experimental',
3106 3106 'revertalternateinteractivemode',
3107 3107 True)
3108 3108 if reversehunks:
3109 3109 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3110 3110 else:
3111 3111 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3112 3112 originalchunks = patch.parsepatch(diff)
3113 3113
3114 3114 try:
3115 3115
3116 3116 chunks = recordfilter(repo.ui, originalchunks)
3117 3117 if reversehunks:
3118 3118 chunks = patch.reversehunks(chunks)
3119 3119
3120 3120 except patch.PatchError as err:
3121 3121 raise util.Abort(_('error parsing patch: %s') % err)
3122 3122
3123 3123 newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
3124 3124 # Apply changes
3125 3125 fp = cStringIO.StringIO()
3126 3126 for c in chunks:
3127 3127 c.write(fp)
3128 3128 dopatch = fp.tell()
3129 3129 fp.seek(0)
3130 3130 if dopatch:
3131 3131 try:
3132 3132 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3133 3133 except patch.PatchError as err:
3134 3134 raise util.Abort(str(err))
3135 3135 del fp
3136 3136 else:
3137 3137 for f in actions['revert'][0]:
3138 3138 wsize = checkout(f)
3139 3139 if normal:
3140 3140 normal(f)
3141 3141 elif wsize == repo.dirstate._map[f][2]:
3142 3142 # changes may be overlooked without normallookup,
3143 3143 # if size isn't changed at reverting
3144 3144 repo.dirstate.normallookup(f)
3145 3145
3146 3146 for f in actions['add'][0]:
3147 3147 # Don't checkout modified files, they are already created by the diff
3148 3148 if f not in newlyaddedandmodifiedfiles:
3149 3149 checkout(f)
3150 3150 repo.dirstate.add(f)
3151 3151
3152 3152 normal = repo.dirstate.normallookup
3153 3153 if node == parent and p2 == nullid:
3154 3154 normal = repo.dirstate.normal
3155 3155 for f in actions['undelete'][0]:
3156 3156 checkout(f)
3157 3157 normal(f)
3158 3158
3159 3159 copied = copies.pathcopies(repo[parent], ctx)
3160 3160
3161 3161 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
3162 3162 if f in copied:
3163 3163 repo.dirstate.copy(copied[f], f)
3164 3164
3165 3165 def command(table):
3166 3166 """Returns a function object to be used as a decorator for making commands.
3167 3167
3168 3168 This function receives a command table as its argument. The table should
3169 3169 be a dict.
3170 3170
3171 3171 The returned function can be used as a decorator for adding commands
3172 3172 to that command table. This function accepts multiple arguments to define
3173 3173 a command.
3174 3174
3175 3175 The first argument is the command name.
3176 3176
3177 3177 The options argument is an iterable of tuples defining command arguments.
3178 3178 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
3179 3179
3180 3180 The synopsis argument defines a short, one line summary of how to use the
3181 3181 command. This shows up in the help output.
3182 3182
3183 3183 The norepo argument defines whether the command does not require a
3184 3184 local repository. Most commands operate against a repository, thus the
3185 3185 default is False.
3186 3186
3187 3187 The optionalrepo argument defines whether the command optionally requires
3188 3188 a local repository.
3189 3189
3190 3190 The inferrepo argument defines whether to try to find a repository from the
3191 3191 command line arguments. If True, arguments will be examined for potential
3192 3192 repository locations. See ``findrepo()``. If a repository is found, it
3193 3193 will be used.
3194 3194 """
3195 3195 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
3196 3196 inferrepo=False):
3197 3197 def decorator(func):
3198 3198 if synopsis:
3199 3199 table[name] = func, list(options), synopsis
3200 3200 else:
3201 3201 table[name] = func, list(options)
3202 3202
3203 3203 if norepo:
3204 3204 # Avoid import cycle.
3205 3205 import commands
3206 3206 commands.norepo += ' %s' % ' '.join(parsealiases(name))
3207 3207
3208 3208 if optionalrepo:
3209 3209 import commands
3210 3210 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
3211 3211
3212 3212 if inferrepo:
3213 3213 import commands
3214 3214 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
3215 3215
3216 3216 return func
3217 3217 return decorator
3218 3218
3219 3219 return cmd
3220 3220
3221 3221 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3222 3222 # commands.outgoing. "missing" is "missing" of the result of
3223 3223 # "findcommonoutgoing()"
3224 3224 outgoinghooks = util.hooks()
3225 3225
3226 3226 # a list of (ui, repo) functions called by commands.summary
3227 3227 summaryhooks = util.hooks()
3228 3228
3229 3229 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3230 3230 #
3231 3231 # functions should return tuple of booleans below, if 'changes' is None:
3232 3232 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3233 3233 #
3234 3234 # otherwise, 'changes' is a tuple of tuples below:
3235 3235 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3236 3236 # - (desturl, destbranch, destpeer, outgoing)
3237 3237 summaryremotehooks = util.hooks()
3238 3238
3239 3239 # A list of state files kept by multistep operations like graft.
3240 3240 # Since graft cannot be aborted, it is considered 'clearable' by update.
3241 3241 # note: bisect is intentionally excluded
3242 3242 # (state file, clearable, allowcommit, error, hint)
3243 3243 unfinishedstates = [
3244 3244 ('graftstate', True, False, _('graft in progress'),
3245 3245 _("use 'hg graft --continue' or 'hg update' to abort")),
3246 3246 ('updatestate', True, False, _('last update was interrupted'),
3247 3247 _("use 'hg update' to get a consistent checkout"))
3248 3248 ]
3249 3249
3250 3250 def checkunfinished(repo, commit=False):
3251 3251 '''Look for an unfinished multistep operation, like graft, and abort
3252 3252 if found. It's probably good to check this right before
3253 3253 bailifchanged().
3254 3254 '''
3255 3255 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3256 3256 if commit and allowcommit:
3257 3257 continue
3258 3258 if repo.vfs.exists(f):
3259 3259 raise util.Abort(msg, hint=hint)
3260 3260
3261 3261 def clearunfinished(repo):
3262 3262 '''Check for unfinished operations (as above), and clear the ones
3263 3263 that are clearable.
3264 3264 '''
3265 3265 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3266 3266 if not clearable and repo.vfs.exists(f):
3267 3267 raise util.Abort(msg, hint=hint)
3268 3268 for f, clearable, allowcommit, msg, hint in unfinishedstates:
3269 3269 if clearable and repo.vfs.exists(f):
3270 3270 util.unlink(repo.join(f))
3271 3271
3272 3272 class dirstateguard(object):
3273 3273 '''Restore dirstate at unexpected failure.
3274 3274
3275 3275 At the construction, this class does:
3276 3276
3277 3277 - write current ``repo.dirstate`` out, and
3278 3278 - save ``.hg/dirstate`` into the backup file
3279 3279
3280 3280 This restores ``.hg/dirstate`` from backup file, if ``release()``
3281 3281 is invoked before ``close()``.
3282 3282
3283 3283 This just removes the backup file at ``close()`` before ``release()``.
3284 3284 '''
3285 3285
3286 3286 def __init__(self, repo, name):
3287 3287 repo.dirstate.write()
3288 3288 self._repo = repo
3289 3289 self._filename = 'dirstate.backup.%s.%d' % (name, id(self))
3290 3290 repo.vfs.write(self._filename, repo.vfs.tryread('dirstate'))
3291 3291 self._active = True
3292 3292 self._closed = False
3293 3293
3294 3294 def __del__(self):
3295 3295 if self._active: # still active
3296 3296 # this may occur, even if this class is used correctly:
3297 3297 # for example, releasing other resources like transaction
3298 3298 # may raise exception before ``dirstateguard.release`` in
3299 3299 # ``release(tr, ....)``.
3300 3300 self._abort()
3301 3301
3302 3302 def close(self):
3303 3303 if not self._active: # already inactivated
3304 3304 msg = (_("can't close already inactivated backup: %s")
3305 3305 % self._filename)
3306 3306 raise util.Abort(msg)
3307 3307
3308 3308 self._repo.vfs.unlink(self._filename)
3309 3309 self._active = False
3310 3310 self._closed = True
3311 3311
3312 3312 def _abort(self):
3313 3313 # this "invalidate()" prevents "wlock.release()" from writing
3314 3314 # changes of dirstate out after restoring to original status
3315 3315 self._repo.dirstate.invalidate()
3316 3316
3317 3317 self._repo.vfs.rename(self._filename, 'dirstate')
3318 3318 self._active = False
3319 3319
3320 3320 def release(self):
3321 3321 if not self._closed:
3322 3322 if not self._active: # already inactivated
3323 3323 msg = (_("can't release already inactivated backup: %s")
3324 3324 % self._filename)
3325 3325 raise util.Abort(msg)
3326 3326 self._abort()
@@ -1,1156 +1,1156 b''
1 1 # scmutil.py - Mercurial core utility functions
2 2 #
3 3 # Copyright 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 i18n import _
9 from mercurial.node import nullrev
9 from mercurial.node import nullrev, wdirrev
10 10 import util, error, osutil, revset, similar, encoding, phases
11 11 import pathutil
12 12 import match as matchmod
13 13 import os, errno, re, glob, tempfile, shutil, stat
14 14
15 15 if os.name == 'nt':
16 16 import scmwindows as scmplatform
17 17 else:
18 18 import scmposix as scmplatform
19 19
20 20 systemrcpath = scmplatform.systemrcpath
21 21 userrcpath = scmplatform.userrcpath
22 22
23 23 class status(tuple):
24 24 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
25 25 and 'ignored' properties are only relevant to the working copy.
26 26 '''
27 27
28 28 __slots__ = ()
29 29
30 30 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
31 31 clean):
32 32 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
33 33 ignored, clean))
34 34
35 35 @property
36 36 def modified(self):
37 37 '''files that have been modified'''
38 38 return self[0]
39 39
40 40 @property
41 41 def added(self):
42 42 '''files that have been added'''
43 43 return self[1]
44 44
45 45 @property
46 46 def removed(self):
47 47 '''files that have been removed'''
48 48 return self[2]
49 49
50 50 @property
51 51 def deleted(self):
52 52 '''files that are in the dirstate, but have been deleted from the
53 53 working copy (aka "missing")
54 54 '''
55 55 return self[3]
56 56
57 57 @property
58 58 def unknown(self):
59 59 '''files not in the dirstate that are not ignored'''
60 60 return self[4]
61 61
62 62 @property
63 63 def ignored(self):
64 64 '''files not in the dirstate that are ignored (by _dirignore())'''
65 65 return self[5]
66 66
67 67 @property
68 68 def clean(self):
69 69 '''files that have not been modified'''
70 70 return self[6]
71 71
72 72 def __repr__(self, *args, **kwargs):
73 73 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
74 74 'unknown=%r, ignored=%r, clean=%r>') % self)
75 75
76 76 def itersubrepos(ctx1, ctx2):
77 77 """find subrepos in ctx1 or ctx2"""
78 78 # Create a (subpath, ctx) mapping where we prefer subpaths from
79 79 # ctx1. The subpaths from ctx2 are important when the .hgsub file
80 80 # has been modified (in ctx2) but not yet committed (in ctx1).
81 81 subpaths = dict.fromkeys(ctx2.substate, ctx2)
82 82 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
83 83
84 84 missing = set()
85 85
86 86 for subpath in ctx2.substate:
87 87 if subpath not in ctx1.substate:
88 88 del subpaths[subpath]
89 89 missing.add(subpath)
90 90
91 91 for subpath, ctx in sorted(subpaths.iteritems()):
92 92 yield subpath, ctx.sub(subpath)
93 93
94 94 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
95 95 # status and diff will have an accurate result when it does
96 96 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
97 97 # against itself.
98 98 for subpath in missing:
99 99 yield subpath, ctx2.nullsub(subpath, ctx1)
100 100
101 101 def nochangesfound(ui, repo, excluded=None):
102 102 '''Report no changes for push/pull, excluded is None or a list of
103 103 nodes excluded from the push/pull.
104 104 '''
105 105 secretlist = []
106 106 if excluded:
107 107 for n in excluded:
108 108 if n not in repo:
109 109 # discovery should not have included the filtered revision,
110 110 # we have to explicitly exclude it until discovery is cleanup.
111 111 continue
112 112 ctx = repo[n]
113 113 if ctx.phase() >= phases.secret and not ctx.extinct():
114 114 secretlist.append(n)
115 115
116 116 if secretlist:
117 117 ui.status(_("no changes found (ignored %d secret changesets)\n")
118 118 % len(secretlist))
119 119 else:
120 120 ui.status(_("no changes found\n"))
121 121
122 122 def checknewlabel(repo, lbl, kind):
123 123 # Do not use the "kind" parameter in ui output.
124 124 # It makes strings difficult to translate.
125 125 if lbl in ['tip', '.', 'null']:
126 126 raise util.Abort(_("the name '%s' is reserved") % lbl)
127 127 for c in (':', '\0', '\n', '\r'):
128 128 if c in lbl:
129 129 raise util.Abort(_("%r cannot be used in a name") % c)
130 130 try:
131 131 int(lbl)
132 132 raise util.Abort(_("cannot use an integer as a name"))
133 133 except ValueError:
134 134 pass
135 135
136 136 def checkfilename(f):
137 137 '''Check that the filename f is an acceptable filename for a tracked file'''
138 138 if '\r' in f or '\n' in f:
139 139 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
140 140
141 141 def checkportable(ui, f):
142 142 '''Check if filename f is portable and warn or abort depending on config'''
143 143 checkfilename(f)
144 144 abort, warn = checkportabilityalert(ui)
145 145 if abort or warn:
146 146 msg = util.checkwinfilename(f)
147 147 if msg:
148 148 msg = "%s: %r" % (msg, f)
149 149 if abort:
150 150 raise util.Abort(msg)
151 151 ui.warn(_("warning: %s\n") % msg)
152 152
153 153 def checkportabilityalert(ui):
154 154 '''check if the user's config requests nothing, a warning, or abort for
155 155 non-portable filenames'''
156 156 val = ui.config('ui', 'portablefilenames', 'warn')
157 157 lval = val.lower()
158 158 bval = util.parsebool(val)
159 159 abort = os.name == 'nt' or lval == 'abort'
160 160 warn = bval or lval == 'warn'
161 161 if bval is None and not (warn or abort or lval == 'ignore'):
162 162 raise error.ConfigError(
163 163 _("ui.portablefilenames value is invalid ('%s')") % val)
164 164 return abort, warn
165 165
166 166 class casecollisionauditor(object):
167 167 def __init__(self, ui, abort, dirstate):
168 168 self._ui = ui
169 169 self._abort = abort
170 170 allfiles = '\0'.join(dirstate._map)
171 171 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
172 172 self._dirstate = dirstate
173 173 # The purpose of _newfiles is so that we don't complain about
174 174 # case collisions if someone were to call this object with the
175 175 # same filename twice.
176 176 self._newfiles = set()
177 177
178 178 def __call__(self, f):
179 179 if f in self._newfiles:
180 180 return
181 181 fl = encoding.lower(f)
182 182 if fl in self._loweredfiles and f not in self._dirstate:
183 183 msg = _('possible case-folding collision for %s') % f
184 184 if self._abort:
185 185 raise util.Abort(msg)
186 186 self._ui.warn(_("warning: %s\n") % msg)
187 187 self._loweredfiles.add(fl)
188 188 self._newfiles.add(f)
189 189
190 190 def filteredhash(repo, maxrev):
191 191 """build hash of filtered revisions in the current repoview.
192 192
193 193 Multiple caches perform up-to-date validation by checking that the
194 194 tiprev and tipnode stored in the cache file match the current repository.
195 195 However, this is not sufficient for validating repoviews because the set
196 196 of revisions in the view may change without the repository tiprev and
197 197 tipnode changing.
198 198
199 199 This function hashes all the revs filtered from the view and returns
200 200 that SHA-1 digest.
201 201 """
202 202 cl = repo.changelog
203 203 if not cl.filteredrevs:
204 204 return None
205 205 key = None
206 206 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
207 207 if revs:
208 208 s = util.sha1()
209 209 for rev in revs:
210 210 s.update('%s;' % rev)
211 211 key = s.digest()
212 212 return key
213 213
214 214 class abstractvfs(object):
215 215 """Abstract base class; cannot be instantiated"""
216 216
217 217 def __init__(self, *args, **kwargs):
218 218 '''Prevent instantiation; don't call this from subclasses.'''
219 219 raise NotImplementedError('attempted instantiating ' + str(type(self)))
220 220
221 221 def tryread(self, path):
222 222 '''gracefully return an empty string for missing files'''
223 223 try:
224 224 return self.read(path)
225 225 except IOError as inst:
226 226 if inst.errno != errno.ENOENT:
227 227 raise
228 228 return ""
229 229
230 230 def tryreadlines(self, path, mode='rb'):
231 231 '''gracefully return an empty array for missing files'''
232 232 try:
233 233 return self.readlines(path, mode=mode)
234 234 except IOError as inst:
235 235 if inst.errno != errno.ENOENT:
236 236 raise
237 237 return []
238 238
239 239 def open(self, path, mode="r", text=False, atomictemp=False,
240 240 notindexed=False):
241 241 '''Open ``path`` file, which is relative to vfs root.
242 242
243 243 Newly created directories are marked as "not to be indexed by
244 244 the content indexing service", if ``notindexed`` is specified
245 245 for "write" mode access.
246 246 '''
247 247 self.open = self.__call__
248 248 return self.__call__(path, mode, text, atomictemp, notindexed)
249 249
250 250 def read(self, path):
251 251 fp = self(path, 'rb')
252 252 try:
253 253 return fp.read()
254 254 finally:
255 255 fp.close()
256 256
257 257 def readlines(self, path, mode='rb'):
258 258 fp = self(path, mode=mode)
259 259 try:
260 260 return fp.readlines()
261 261 finally:
262 262 fp.close()
263 263
264 264 def write(self, path, data):
265 265 fp = self(path, 'wb')
266 266 try:
267 267 return fp.write(data)
268 268 finally:
269 269 fp.close()
270 270
271 271 def writelines(self, path, data, mode='wb', notindexed=False):
272 272 fp = self(path, mode=mode, notindexed=notindexed)
273 273 try:
274 274 return fp.writelines(data)
275 275 finally:
276 276 fp.close()
277 277
278 278 def append(self, path, data):
279 279 fp = self(path, 'ab')
280 280 try:
281 281 return fp.write(data)
282 282 finally:
283 283 fp.close()
284 284
285 285 def chmod(self, path, mode):
286 286 return os.chmod(self.join(path), mode)
287 287
288 288 def exists(self, path=None):
289 289 return os.path.exists(self.join(path))
290 290
291 291 def fstat(self, fp):
292 292 return util.fstat(fp)
293 293
294 294 def isdir(self, path=None):
295 295 return os.path.isdir(self.join(path))
296 296
297 297 def isfile(self, path=None):
298 298 return os.path.isfile(self.join(path))
299 299
300 300 def islink(self, path=None):
301 301 return os.path.islink(self.join(path))
302 302
303 303 def reljoin(self, *paths):
304 304 """join various elements of a path together (as os.path.join would do)
305 305
306 306 The vfs base is not injected so that path stay relative. This exists
307 307 to allow handling of strange encoding if needed."""
308 308 return os.path.join(*paths)
309 309
310 310 def split(self, path):
311 311 """split top-most element of a path (as os.path.split would do)
312 312
313 313 This exists to allow handling of strange encoding if needed."""
314 314 return os.path.split(path)
315 315
316 316 def lexists(self, path=None):
317 317 return os.path.lexists(self.join(path))
318 318
319 319 def lstat(self, path=None):
320 320 return os.lstat(self.join(path))
321 321
322 322 def listdir(self, path=None):
323 323 return os.listdir(self.join(path))
324 324
325 325 def makedir(self, path=None, notindexed=True):
326 326 return util.makedir(self.join(path), notindexed)
327 327
328 328 def makedirs(self, path=None, mode=None):
329 329 return util.makedirs(self.join(path), mode)
330 330
331 331 def makelock(self, info, path):
332 332 return util.makelock(info, self.join(path))
333 333
334 334 def mkdir(self, path=None):
335 335 return os.mkdir(self.join(path))
336 336
337 337 def mkstemp(self, suffix='', prefix='tmp', dir=None, text=False):
338 338 fd, name = tempfile.mkstemp(suffix=suffix, prefix=prefix,
339 339 dir=self.join(dir), text=text)
340 340 dname, fname = util.split(name)
341 341 if dir:
342 342 return fd, os.path.join(dir, fname)
343 343 else:
344 344 return fd, fname
345 345
346 346 def readdir(self, path=None, stat=None, skip=None):
347 347 return osutil.listdir(self.join(path), stat, skip)
348 348
349 349 def readlock(self, path):
350 350 return util.readlock(self.join(path))
351 351
352 352 def rename(self, src, dst):
353 353 return util.rename(self.join(src), self.join(dst))
354 354
355 355 def readlink(self, path):
356 356 return os.readlink(self.join(path))
357 357
358 358 def removedirs(self, path=None):
359 359 """Remove a leaf directory and all empty intermediate ones
360 360 """
361 361 return util.removedirs(self.join(path))
362 362
363 363 def rmtree(self, path=None, ignore_errors=False, forcibly=False):
364 364 """Remove a directory tree recursively
365 365
366 366 If ``forcibly``, this tries to remove READ-ONLY files, too.
367 367 """
368 368 if forcibly:
369 369 def onerror(function, path, excinfo):
370 370 if function is not os.remove:
371 371 raise
372 372 # read-only files cannot be unlinked under Windows
373 373 s = os.stat(path)
374 374 if (s.st_mode & stat.S_IWRITE) != 0:
375 375 raise
376 376 os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
377 377 os.remove(path)
378 378 else:
379 379 onerror = None
380 380 return shutil.rmtree(self.join(path),
381 381 ignore_errors=ignore_errors, onerror=onerror)
382 382
383 383 def setflags(self, path, l, x):
384 384 return util.setflags(self.join(path), l, x)
385 385
386 386 def stat(self, path=None):
387 387 return os.stat(self.join(path))
388 388
389 389 def unlink(self, path=None):
390 390 return util.unlink(self.join(path))
391 391
392 392 def unlinkpath(self, path=None, ignoremissing=False):
393 393 return util.unlinkpath(self.join(path), ignoremissing)
394 394
395 395 def utime(self, path=None, t=None):
396 396 return os.utime(self.join(path), t)
397 397
398 398 def walk(self, path=None, onerror=None):
399 399 """Yield (dirpath, dirs, files) tuple for each directories under path
400 400
401 401 ``dirpath`` is relative one from the root of this vfs. This
402 402 uses ``os.sep`` as path separator, even you specify POSIX
403 403 style ``path``.
404 404
405 405 "The root of this vfs" is represented as empty ``dirpath``.
406 406 """
407 407 root = os.path.normpath(self.join(None))
408 408 # when dirpath == root, dirpath[prefixlen:] becomes empty
409 409 # because len(dirpath) < prefixlen.
410 410 prefixlen = len(pathutil.normasprefix(root))
411 411 for dirpath, dirs, files in os.walk(self.join(path), onerror=onerror):
412 412 yield (dirpath[prefixlen:], dirs, files)
413 413
414 414 class vfs(abstractvfs):
415 415 '''Operate files relative to a base directory
416 416
417 417 This class is used to hide the details of COW semantics and
418 418 remote file access from higher level code.
419 419 '''
420 420 def __init__(self, base, audit=True, expandpath=False, realpath=False):
421 421 if expandpath:
422 422 base = util.expandpath(base)
423 423 if realpath:
424 424 base = os.path.realpath(base)
425 425 self.base = base
426 426 self._setmustaudit(audit)
427 427 self.createmode = None
428 428 self._trustnlink = None
429 429
430 430 def _getmustaudit(self):
431 431 return self._audit
432 432
433 433 def _setmustaudit(self, onoff):
434 434 self._audit = onoff
435 435 if onoff:
436 436 self.audit = pathutil.pathauditor(self.base)
437 437 else:
438 438 self.audit = util.always
439 439
440 440 mustaudit = property(_getmustaudit, _setmustaudit)
441 441
442 442 @util.propertycache
443 443 def _cansymlink(self):
444 444 return util.checklink(self.base)
445 445
446 446 @util.propertycache
447 447 def _chmod(self):
448 448 return util.checkexec(self.base)
449 449
450 450 def _fixfilemode(self, name):
451 451 if self.createmode is None or not self._chmod:
452 452 return
453 453 os.chmod(name, self.createmode & 0o666)
454 454
455 455 def __call__(self, path, mode="r", text=False, atomictemp=False,
456 456 notindexed=False):
457 457 '''Open ``path`` file, which is relative to vfs root.
458 458
459 459 Newly created directories are marked as "not to be indexed by
460 460 the content indexing service", if ``notindexed`` is specified
461 461 for "write" mode access.
462 462 '''
463 463 if self._audit:
464 464 r = util.checkosfilename(path)
465 465 if r:
466 466 raise util.Abort("%s: %r" % (r, path))
467 467 self.audit(path)
468 468 f = self.join(path)
469 469
470 470 if not text and "b" not in mode:
471 471 mode += "b" # for that other OS
472 472
473 473 nlink = -1
474 474 if mode not in ('r', 'rb'):
475 475 dirname, basename = util.split(f)
476 476 # If basename is empty, then the path is malformed because it points
477 477 # to a directory. Let the posixfile() call below raise IOError.
478 478 if basename:
479 479 if atomictemp:
480 480 util.ensuredirs(dirname, self.createmode, notindexed)
481 481 return util.atomictempfile(f, mode, self.createmode)
482 482 try:
483 483 if 'w' in mode:
484 484 util.unlink(f)
485 485 nlink = 0
486 486 else:
487 487 # nlinks() may behave differently for files on Windows
488 488 # shares if the file is open.
489 489 fd = util.posixfile(f)
490 490 nlink = util.nlinks(f)
491 491 if nlink < 1:
492 492 nlink = 2 # force mktempcopy (issue1922)
493 493 fd.close()
494 494 except (OSError, IOError) as e:
495 495 if e.errno != errno.ENOENT:
496 496 raise
497 497 nlink = 0
498 498 util.ensuredirs(dirname, self.createmode, notindexed)
499 499 if nlink > 0:
500 500 if self._trustnlink is None:
501 501 self._trustnlink = nlink > 1 or util.checknlink(f)
502 502 if nlink > 1 or not self._trustnlink:
503 503 util.rename(util.mktempcopy(f), f)
504 504 fp = util.posixfile(f, mode)
505 505 if nlink == 0:
506 506 self._fixfilemode(f)
507 507 return fp
508 508
509 509 def symlink(self, src, dst):
510 510 self.audit(dst)
511 511 linkname = self.join(dst)
512 512 try:
513 513 os.unlink(linkname)
514 514 except OSError:
515 515 pass
516 516
517 517 util.ensuredirs(os.path.dirname(linkname), self.createmode)
518 518
519 519 if self._cansymlink:
520 520 try:
521 521 os.symlink(src, linkname)
522 522 except OSError as err:
523 523 raise OSError(err.errno, _('could not symlink to %r: %s') %
524 524 (src, err.strerror), linkname)
525 525 else:
526 526 self.write(dst, src)
527 527
528 528 def join(self, path, *insidef):
529 529 if path:
530 530 return os.path.join(self.base, path, *insidef)
531 531 else:
532 532 return self.base
533 533
534 534 opener = vfs
535 535
536 536 class auditvfs(object):
537 537 def __init__(self, vfs):
538 538 self.vfs = vfs
539 539
540 540 def _getmustaudit(self):
541 541 return self.vfs.mustaudit
542 542
543 543 def _setmustaudit(self, onoff):
544 544 self.vfs.mustaudit = onoff
545 545
546 546 mustaudit = property(_getmustaudit, _setmustaudit)
547 547
548 548 class filtervfs(abstractvfs, auditvfs):
549 549 '''Wrapper vfs for filtering filenames with a function.'''
550 550
551 551 def __init__(self, vfs, filter):
552 552 auditvfs.__init__(self, vfs)
553 553 self._filter = filter
554 554
555 555 def __call__(self, path, *args, **kwargs):
556 556 return self.vfs(self._filter(path), *args, **kwargs)
557 557
558 558 def join(self, path, *insidef):
559 559 if path:
560 560 return self.vfs.join(self._filter(self.vfs.reljoin(path, *insidef)))
561 561 else:
562 562 return self.vfs.join(path)
563 563
564 564 filteropener = filtervfs
565 565
566 566 class readonlyvfs(abstractvfs, auditvfs):
567 567 '''Wrapper vfs preventing any writing.'''
568 568
569 569 def __init__(self, vfs):
570 570 auditvfs.__init__(self, vfs)
571 571
572 572 def __call__(self, path, mode='r', *args, **kw):
573 573 if mode not in ('r', 'rb'):
574 574 raise util.Abort('this vfs is read only')
575 575 return self.vfs(path, mode, *args, **kw)
576 576
577 577
578 578 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
579 579 '''yield every hg repository under path, always recursively.
580 580 The recurse flag will only control recursion into repo working dirs'''
581 581 def errhandler(err):
582 582 if err.filename == path:
583 583 raise err
584 584 samestat = getattr(os.path, 'samestat', None)
585 585 if followsym and samestat is not None:
586 586 def adddir(dirlst, dirname):
587 587 match = False
588 588 dirstat = os.stat(dirname)
589 589 for lstdirstat in dirlst:
590 590 if samestat(dirstat, lstdirstat):
591 591 match = True
592 592 break
593 593 if not match:
594 594 dirlst.append(dirstat)
595 595 return not match
596 596 else:
597 597 followsym = False
598 598
599 599 if (seen_dirs is None) and followsym:
600 600 seen_dirs = []
601 601 adddir(seen_dirs, path)
602 602 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
603 603 dirs.sort()
604 604 if '.hg' in dirs:
605 605 yield root # found a repository
606 606 qroot = os.path.join(root, '.hg', 'patches')
607 607 if os.path.isdir(os.path.join(qroot, '.hg')):
608 608 yield qroot # we have a patch queue repo here
609 609 if recurse:
610 610 # avoid recursing inside the .hg directory
611 611 dirs.remove('.hg')
612 612 else:
613 613 dirs[:] = [] # don't descend further
614 614 elif followsym:
615 615 newdirs = []
616 616 for d in dirs:
617 617 fname = os.path.join(root, d)
618 618 if adddir(seen_dirs, fname):
619 619 if os.path.islink(fname):
620 620 for hgname in walkrepos(fname, True, seen_dirs):
621 621 yield hgname
622 622 else:
623 623 newdirs.append(d)
624 624 dirs[:] = newdirs
625 625
626 626 def osrcpath():
627 627 '''return default os-specific hgrc search path'''
628 628 path = []
629 629 defaultpath = os.path.join(util.datapath, 'default.d')
630 630 if os.path.isdir(defaultpath):
631 631 for f, kind in osutil.listdir(defaultpath):
632 632 if f.endswith('.rc'):
633 633 path.append(os.path.join(defaultpath, f))
634 634 path.extend(systemrcpath())
635 635 path.extend(userrcpath())
636 636 path = [os.path.normpath(f) for f in path]
637 637 return path
638 638
639 639 _rcpath = None
640 640
641 641 def rcpath():
642 642 '''return hgrc search path. if env var HGRCPATH is set, use it.
643 643 for each item in path, if directory, use files ending in .rc,
644 644 else use item.
645 645 make HGRCPATH empty to only look in .hg/hgrc of current repo.
646 646 if no HGRCPATH, use default os-specific path.'''
647 647 global _rcpath
648 648 if _rcpath is None:
649 649 if 'HGRCPATH' in os.environ:
650 650 _rcpath = []
651 651 for p in os.environ['HGRCPATH'].split(os.pathsep):
652 652 if not p:
653 653 continue
654 654 p = util.expandpath(p)
655 655 if os.path.isdir(p):
656 656 for f, kind in osutil.listdir(p):
657 657 if f.endswith('.rc'):
658 658 _rcpath.append(os.path.join(p, f))
659 659 else:
660 660 _rcpath.append(p)
661 661 else:
662 662 _rcpath = osrcpath()
663 663 return _rcpath
664 664
665 def intrev(repo, rev):
665 def intrev(rev):
666 666 """Return integer for a given revision that can be used in comparison or
667 667 arithmetic operation"""
668 668 if rev is None:
669 return len(repo)
669 return wdirrev
670 670 return rev
671 671
672 672 def revsingle(repo, revspec, default='.'):
673 673 if not revspec and revspec != 0:
674 674 return repo[default]
675 675
676 676 l = revrange(repo, [revspec])
677 677 if not l:
678 678 raise util.Abort(_('empty revision set'))
679 679 return repo[l.last()]
680 680
681 681 def revpair(repo, revs):
682 682 if not revs:
683 683 return repo.dirstate.p1(), None
684 684
685 685 l = revrange(repo, revs)
686 686
687 687 if not l:
688 688 first = second = None
689 689 elif l.isascending():
690 690 first = l.min()
691 691 second = l.max()
692 692 elif l.isdescending():
693 693 first = l.max()
694 694 second = l.min()
695 695 else:
696 696 first = l.first()
697 697 second = l.last()
698 698
699 699 if first is None:
700 700 raise util.Abort(_('empty revision range'))
701 701
702 702 if first == second and len(revs) == 1 and _revrangesep not in revs[0]:
703 703 return repo.lookup(first), None
704 704
705 705 return repo.lookup(first), repo.lookup(second)
706 706
707 707 _revrangesep = ':'
708 708
709 709 def revrange(repo, revs):
710 710 """Yield revision as strings from a list of revision specifications."""
711 711
712 712 def revfix(repo, val, defval):
713 713 if not val and val != 0 and defval is not None:
714 714 return defval
715 715 return repo[val].rev()
716 716
717 717 subsets = []
718 718
719 719 revsetaliases = [alias for (alias, _) in
720 720 repo.ui.configitems("revsetalias")]
721 721
722 722 for spec in revs:
723 723 # attempt to parse old-style ranges first to deal with
724 724 # things like old-tag which contain query metacharacters
725 725 try:
726 726 # ... except for revset aliases without arguments. These
727 727 # should be parsed as soon as possible, because they might
728 728 # clash with a hash prefix.
729 729 if spec in revsetaliases:
730 730 raise error.RepoLookupError
731 731
732 732 if isinstance(spec, int):
733 733 subsets.append(revset.baseset([spec]))
734 734 continue
735 735
736 736 if _revrangesep in spec:
737 737 start, end = spec.split(_revrangesep, 1)
738 738 if start in revsetaliases or end in revsetaliases:
739 739 raise error.RepoLookupError
740 740
741 741 start = revfix(repo, start, 0)
742 742 end = revfix(repo, end, len(repo) - 1)
743 743 if end == nullrev and start < 0:
744 744 start = nullrev
745 745 if start < end:
746 746 l = revset.spanset(repo, start, end + 1)
747 747 else:
748 748 l = revset.spanset(repo, start, end - 1)
749 749 subsets.append(l)
750 750 continue
751 751 elif spec and spec in repo: # single unquoted rev
752 752 rev = revfix(repo, spec, None)
753 753 subsets.append(revset.baseset([rev]))
754 754 continue
755 755 except error.RepoLookupError:
756 756 pass
757 757
758 758 # fall through to new-style queries if old-style fails
759 759 m = revset.match(repo.ui, spec, repo)
760 760 subsets.append(m(repo))
761 761
762 762 return revset._combinesets(subsets)
763 763
764 764 def expandpats(pats):
765 765 '''Expand bare globs when running on windows.
766 766 On posix we assume it already has already been done by sh.'''
767 767 if not util.expandglobs:
768 768 return list(pats)
769 769 ret = []
770 770 for kindpat in pats:
771 771 kind, pat = matchmod._patsplit(kindpat, None)
772 772 if kind is None:
773 773 try:
774 774 globbed = glob.glob(pat)
775 775 except re.error:
776 776 globbed = [pat]
777 777 if globbed:
778 778 ret.extend(globbed)
779 779 continue
780 780 ret.append(kindpat)
781 781 return ret
782 782
783 783 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath',
784 784 badfn=None):
785 785 '''Return a matcher and the patterns that were used.
786 786 The matcher will warn about bad matches, unless an alternate badfn callback
787 787 is provided.'''
788 788 if pats == ("",):
789 789 pats = []
790 790 if not globbed and default == 'relpath':
791 791 pats = expandpats(pats or [])
792 792
793 793 def bad(f, msg):
794 794 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
795 795
796 796 if badfn is None:
797 797 badfn = bad
798 798
799 799 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
800 800 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
801 801
802 802 if m.always():
803 803 pats = []
804 804 return m, pats
805 805
806 806 def match(ctx, pats=[], opts={}, globbed=False, default='relpath', badfn=None):
807 807 '''Return a matcher that will warn about bad matches.'''
808 808 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
809 809
810 810 def matchall(repo):
811 811 '''Return a matcher that will efficiently match everything.'''
812 812 return matchmod.always(repo.root, repo.getcwd())
813 813
814 814 def matchfiles(repo, files, badfn=None):
815 815 '''Return a matcher that will efficiently match exactly these files.'''
816 816 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
817 817
818 818 def addremove(repo, matcher, prefix, opts={}, dry_run=None, similarity=None):
819 819 m = matcher
820 820 if dry_run is None:
821 821 dry_run = opts.get('dry_run')
822 822 if similarity is None:
823 823 similarity = float(opts.get('similarity') or 0)
824 824
825 825 ret = 0
826 826 join = lambda f: os.path.join(prefix, f)
827 827
828 828 def matchessubrepo(matcher, subpath):
829 829 if matcher.exact(subpath):
830 830 return True
831 831 for f in matcher.files():
832 832 if f.startswith(subpath):
833 833 return True
834 834 return False
835 835
836 836 wctx = repo[None]
837 837 for subpath in sorted(wctx.substate):
838 838 if opts.get('subrepos') or matchessubrepo(m, subpath):
839 839 sub = wctx.sub(subpath)
840 840 try:
841 841 submatch = matchmod.narrowmatcher(subpath, m)
842 842 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
843 843 ret = 1
844 844 except error.LookupError:
845 845 repo.ui.status(_("skipping missing subrepository: %s\n")
846 846 % join(subpath))
847 847
848 848 rejected = []
849 849 def badfn(f, msg):
850 850 if f in m.files():
851 851 m.bad(f, msg)
852 852 rejected.append(f)
853 853
854 854 badmatch = matchmod.badmatch(m, badfn)
855 855 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
856 856 badmatch)
857 857
858 858 unknownset = set(unknown + forgotten)
859 859 toprint = unknownset.copy()
860 860 toprint.update(deleted)
861 861 for abs in sorted(toprint):
862 862 if repo.ui.verbose or not m.exact(abs):
863 863 if abs in unknownset:
864 864 status = _('adding %s\n') % m.uipath(abs)
865 865 else:
866 866 status = _('removing %s\n') % m.uipath(abs)
867 867 repo.ui.status(status)
868 868
869 869 renames = _findrenames(repo, m, added + unknown, removed + deleted,
870 870 similarity)
871 871
872 872 if not dry_run:
873 873 _markchanges(repo, unknown + forgotten, deleted, renames)
874 874
875 875 for f in rejected:
876 876 if f in m.files():
877 877 return 1
878 878 return ret
879 879
880 880 def marktouched(repo, files, similarity=0.0):
881 881 '''Assert that files have somehow been operated upon. files are relative to
882 882 the repo root.'''
883 883 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
884 884 rejected = []
885 885
886 886 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
887 887
888 888 if repo.ui.verbose:
889 889 unknownset = set(unknown + forgotten)
890 890 toprint = unknownset.copy()
891 891 toprint.update(deleted)
892 892 for abs in sorted(toprint):
893 893 if abs in unknownset:
894 894 status = _('adding %s\n') % abs
895 895 else:
896 896 status = _('removing %s\n') % abs
897 897 repo.ui.status(status)
898 898
899 899 renames = _findrenames(repo, m, added + unknown, removed + deleted,
900 900 similarity)
901 901
902 902 _markchanges(repo, unknown + forgotten, deleted, renames)
903 903
904 904 for f in rejected:
905 905 if f in m.files():
906 906 return 1
907 907 return 0
908 908
909 909 def _interestingfiles(repo, matcher):
910 910 '''Walk dirstate with matcher, looking for files that addremove would care
911 911 about.
912 912
913 913 This is different from dirstate.status because it doesn't care about
914 914 whether files are modified or clean.'''
915 915 added, unknown, deleted, removed, forgotten = [], [], [], [], []
916 916 audit_path = pathutil.pathauditor(repo.root)
917 917
918 918 ctx = repo[None]
919 919 dirstate = repo.dirstate
920 920 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
921 921 full=False)
922 922 for abs, st in walkresults.iteritems():
923 923 dstate = dirstate[abs]
924 924 if dstate == '?' and audit_path.check(abs):
925 925 unknown.append(abs)
926 926 elif dstate != 'r' and not st:
927 927 deleted.append(abs)
928 928 elif dstate == 'r' and st:
929 929 forgotten.append(abs)
930 930 # for finding renames
931 931 elif dstate == 'r' and not st:
932 932 removed.append(abs)
933 933 elif dstate == 'a':
934 934 added.append(abs)
935 935
936 936 return added, unknown, deleted, removed, forgotten
937 937
938 938 def _findrenames(repo, matcher, added, removed, similarity):
939 939 '''Find renames from removed files to added ones.'''
940 940 renames = {}
941 941 if similarity > 0:
942 942 for old, new, score in similar.findrenames(repo, added, removed,
943 943 similarity):
944 944 if (repo.ui.verbose or not matcher.exact(old)
945 945 or not matcher.exact(new)):
946 946 repo.ui.status(_('recording removal of %s as rename to %s '
947 947 '(%d%% similar)\n') %
948 948 (matcher.rel(old), matcher.rel(new),
949 949 score * 100))
950 950 renames[new] = old
951 951 return renames
952 952
953 953 def _markchanges(repo, unknown, deleted, renames):
954 954 '''Marks the files in unknown as added, the files in deleted as removed,
955 955 and the files in renames as copied.'''
956 956 wctx = repo[None]
957 957 wlock = repo.wlock()
958 958 try:
959 959 wctx.forget(deleted)
960 960 wctx.add(unknown)
961 961 for new, old in renames.iteritems():
962 962 wctx.copy(old, new)
963 963 finally:
964 964 wlock.release()
965 965
966 966 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
967 967 """Update the dirstate to reflect the intent of copying src to dst. For
968 968 different reasons it might not end with dst being marked as copied from src.
969 969 """
970 970 origsrc = repo.dirstate.copied(src) or src
971 971 if dst == origsrc: # copying back a copy?
972 972 if repo.dirstate[dst] not in 'mn' and not dryrun:
973 973 repo.dirstate.normallookup(dst)
974 974 else:
975 975 if repo.dirstate[origsrc] == 'a' and origsrc == src:
976 976 if not ui.quiet:
977 977 ui.warn(_("%s has not been committed yet, so no copy "
978 978 "data will be stored for %s.\n")
979 979 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
980 980 if repo.dirstate[dst] in '?r' and not dryrun:
981 981 wctx.add([dst])
982 982 elif not dryrun:
983 983 wctx.copy(origsrc, dst)
984 984
985 985 def readrequires(opener, supported):
986 986 '''Reads and parses .hg/requires and checks if all entries found
987 987 are in the list of supported features.'''
988 988 requirements = set(opener.read("requires").splitlines())
989 989 missings = []
990 990 for r in requirements:
991 991 if r not in supported:
992 992 if not r or not r[0].isalnum():
993 993 raise error.RequirementError(_(".hg/requires file is corrupt"))
994 994 missings.append(r)
995 995 missings.sort()
996 996 if missings:
997 997 raise error.RequirementError(
998 998 _("repository requires features unknown to this Mercurial: %s")
999 999 % " ".join(missings),
1000 1000 hint=_("see http://mercurial.selenic.com/wiki/MissingRequirement"
1001 1001 " for more information"))
1002 1002 return requirements
1003 1003
1004 1004 def writerequires(opener, requirements):
1005 1005 reqfile = opener("requires", "w")
1006 1006 for r in sorted(requirements):
1007 1007 reqfile.write("%s\n" % r)
1008 1008 reqfile.close()
1009 1009
1010 1010 class filecachesubentry(object):
1011 1011 def __init__(self, path, stat):
1012 1012 self.path = path
1013 1013 self.cachestat = None
1014 1014 self._cacheable = None
1015 1015
1016 1016 if stat:
1017 1017 self.cachestat = filecachesubentry.stat(self.path)
1018 1018
1019 1019 if self.cachestat:
1020 1020 self._cacheable = self.cachestat.cacheable()
1021 1021 else:
1022 1022 # None means we don't know yet
1023 1023 self._cacheable = None
1024 1024
1025 1025 def refresh(self):
1026 1026 if self.cacheable():
1027 1027 self.cachestat = filecachesubentry.stat(self.path)
1028 1028
1029 1029 def cacheable(self):
1030 1030 if self._cacheable is not None:
1031 1031 return self._cacheable
1032 1032
1033 1033 # we don't know yet, assume it is for now
1034 1034 return True
1035 1035
1036 1036 def changed(self):
1037 1037 # no point in going further if we can't cache it
1038 1038 if not self.cacheable():
1039 1039 return True
1040 1040
1041 1041 newstat = filecachesubentry.stat(self.path)
1042 1042
1043 1043 # we may not know if it's cacheable yet, check again now
1044 1044 if newstat and self._cacheable is None:
1045 1045 self._cacheable = newstat.cacheable()
1046 1046
1047 1047 # check again
1048 1048 if not self._cacheable:
1049 1049 return True
1050 1050
1051 1051 if self.cachestat != newstat:
1052 1052 self.cachestat = newstat
1053 1053 return True
1054 1054 else:
1055 1055 return False
1056 1056
1057 1057 @staticmethod
1058 1058 def stat(path):
1059 1059 try:
1060 1060 return util.cachestat(path)
1061 1061 except OSError as e:
1062 1062 if e.errno != errno.ENOENT:
1063 1063 raise
1064 1064
1065 1065 class filecacheentry(object):
1066 1066 def __init__(self, paths, stat=True):
1067 1067 self._entries = []
1068 1068 for path in paths:
1069 1069 self._entries.append(filecachesubentry(path, stat))
1070 1070
1071 1071 def changed(self):
1072 1072 '''true if any entry has changed'''
1073 1073 for entry in self._entries:
1074 1074 if entry.changed():
1075 1075 return True
1076 1076 return False
1077 1077
1078 1078 def refresh(self):
1079 1079 for entry in self._entries:
1080 1080 entry.refresh()
1081 1081
1082 1082 class filecache(object):
1083 1083 '''A property like decorator that tracks files under .hg/ for updates.
1084 1084
1085 1085 Records stat info when called in _filecache.
1086 1086
1087 1087 On subsequent calls, compares old stat info with new info, and recreates the
1088 1088 object when any of the files changes, updating the new stat info in
1089 1089 _filecache.
1090 1090
1091 1091 Mercurial either atomic renames or appends for files under .hg,
1092 1092 so to ensure the cache is reliable we need the filesystem to be able
1093 1093 to tell us if a file has been replaced. If it can't, we fallback to
1094 1094 recreating the object on every call (essentially the same behaviour as
1095 1095 propertycache).
1096 1096
1097 1097 '''
1098 1098 def __init__(self, *paths):
1099 1099 self.paths = paths
1100 1100
1101 1101 def join(self, obj, fname):
1102 1102 """Used to compute the runtime path of a cached file.
1103 1103
1104 1104 Users should subclass filecache and provide their own version of this
1105 1105 function to call the appropriate join function on 'obj' (an instance
1106 1106 of the class that its member function was decorated).
1107 1107 """
1108 1108 return obj.join(fname)
1109 1109
1110 1110 def __call__(self, func):
1111 1111 self.func = func
1112 1112 self.name = func.__name__
1113 1113 return self
1114 1114
1115 1115 def __get__(self, obj, type=None):
1116 1116 # do we need to check if the file changed?
1117 1117 if self.name in obj.__dict__:
1118 1118 assert self.name in obj._filecache, self.name
1119 1119 return obj.__dict__[self.name]
1120 1120
1121 1121 entry = obj._filecache.get(self.name)
1122 1122
1123 1123 if entry:
1124 1124 if entry.changed():
1125 1125 entry.obj = self.func(obj)
1126 1126 else:
1127 1127 paths = [self.join(obj, path) for path in self.paths]
1128 1128
1129 1129 # We stat -before- creating the object so our cache doesn't lie if
1130 1130 # a writer modified between the time we read and stat
1131 1131 entry = filecacheentry(paths, True)
1132 1132 entry.obj = self.func(obj)
1133 1133
1134 1134 obj._filecache[self.name] = entry
1135 1135
1136 1136 obj.__dict__[self.name] = entry.obj
1137 1137 return entry.obj
1138 1138
1139 1139 def __set__(self, obj, value):
1140 1140 if self.name not in obj._filecache:
1141 1141 # we add an entry for the missing value because X in __dict__
1142 1142 # implies X in _filecache
1143 1143 paths = [self.join(obj, path) for path in self.paths]
1144 1144 ce = filecacheentry(paths, False)
1145 1145 obj._filecache[self.name] = ce
1146 1146 else:
1147 1147 ce = obj._filecache[self.name]
1148 1148
1149 1149 ce.obj = value # update cached copy
1150 1150 obj.__dict__[self.name] = value # update copy returned by obj.x
1151 1151
1152 1152 def __delete__(self, obj):
1153 1153 try:
1154 1154 del obj.__dict__[self.name]
1155 1155 except KeyError:
1156 1156 raise AttributeError(self.name)
@@ -1,2052 +1,2053 b''
1 1 Log on empty repository: checking consistency
2 2
3 3 $ hg init empty
4 4 $ cd empty
5 5 $ hg log
6 6 $ hg log -r 1
7 7 abort: unknown revision '1'!
8 8 [255]
9 9 $ hg log -r -1:0
10 10 abort: unknown revision '-1'!
11 11 [255]
12 12 $ hg log -r 'branch(name)'
13 13 abort: unknown revision 'name'!
14 14 [255]
15 15 $ hg log -r null -q
16 16 -1:000000000000
17 17
18 18 The g is crafted to have 2 filelog topological heads in a linear
19 19 changeset graph
20 20
21 21 $ hg init a
22 22 $ cd a
23 23 $ echo a > a
24 24 $ echo f > f
25 25 $ hg ci -Ama -d '1 0'
26 26 adding a
27 27 adding f
28 28
29 29 $ hg cp a b
30 30 $ hg cp f g
31 31 $ hg ci -mb -d '2 0'
32 32
33 33 $ mkdir dir
34 34 $ hg mv b dir
35 35 $ echo g >> g
36 36 $ echo f >> f
37 37 $ hg ci -mc -d '3 0'
38 38
39 39 $ hg mv a b
40 40 $ hg cp -f f g
41 41 $ echo a > d
42 42 $ hg add d
43 43 $ hg ci -md -d '4 0'
44 44
45 45 $ hg mv dir/b e
46 46 $ hg ci -me -d '5 0'
47 47
48 48 Make sure largefiles doesn't interfere with logging a regular file
49 49 $ hg --debug log a -T '{rev}: {desc}\n' --config extensions.largefiles=
50 50 updated patterns: ['.hglf/a', 'a']
51 51 0: a
52 52 $ hg log a
53 53 changeset: 0:9161b9aeaf16
54 54 user: test
55 55 date: Thu Jan 01 00:00:01 1970 +0000
56 56 summary: a
57 57
58 58 $ hg log glob:a*
59 59 changeset: 3:2ca5ba701980
60 60 user: test
61 61 date: Thu Jan 01 00:00:04 1970 +0000
62 62 summary: d
63 63
64 64 changeset: 0:9161b9aeaf16
65 65 user: test
66 66 date: Thu Jan 01 00:00:01 1970 +0000
67 67 summary: a
68 68
69 69 $ hg --debug log glob:a* -T '{rev}: {desc}\n' --config extensions.largefiles=
70 70 updated patterns: ['glob:.hglf/a*', 'glob:a*']
71 71 3: d
72 72 0: a
73 73
74 74 log on directory
75 75
76 76 $ hg log dir
77 77 changeset: 4:7e4639b4691b
78 78 tag: tip
79 79 user: test
80 80 date: Thu Jan 01 00:00:05 1970 +0000
81 81 summary: e
82 82
83 83 changeset: 2:f8954cd4dc1f
84 84 user: test
85 85 date: Thu Jan 01 00:00:03 1970 +0000
86 86 summary: c
87 87
88 88 $ hg log somethingthatdoesntexist dir
89 89 changeset: 4:7e4639b4691b
90 90 tag: tip
91 91 user: test
92 92 date: Thu Jan 01 00:00:05 1970 +0000
93 93 summary: e
94 94
95 95 changeset: 2:f8954cd4dc1f
96 96 user: test
97 97 date: Thu Jan 01 00:00:03 1970 +0000
98 98 summary: c
99 99
100 100
101 101 -f, non-existent directory
102 102
103 103 $ hg log -f dir
104 104 abort: cannot follow file not in parent revision: "dir"
105 105 [255]
106 106
107 107 -f, directory
108 108
109 109 $ hg up -q 3
110 110 $ hg log -f dir
111 111 changeset: 2:f8954cd4dc1f
112 112 user: test
113 113 date: Thu Jan 01 00:00:03 1970 +0000
114 114 summary: c
115 115
116 116 -f, directory with --patch
117 117
118 118 $ hg log -f dir -p
119 119 changeset: 2:f8954cd4dc1f
120 120 user: test
121 121 date: Thu Jan 01 00:00:03 1970 +0000
122 122 summary: c
123 123
124 124 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
125 125 --- /dev/null* (glob)
126 126 +++ b/dir/b* (glob)
127 127 @@ -0,0 +1,1 @@
128 128 +a
129 129
130 130
131 131 -f, pattern
132 132
133 133 $ hg log -f -I 'dir**' -p
134 134 changeset: 2:f8954cd4dc1f
135 135 user: test
136 136 date: Thu Jan 01 00:00:03 1970 +0000
137 137 summary: c
138 138
139 139 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
140 140 --- /dev/null* (glob)
141 141 +++ b/dir/b* (glob)
142 142 @@ -0,0 +1,1 @@
143 143 +a
144 144
145 145 $ hg up -q 4
146 146
147 147 -f, a wrong style
148 148
149 149 $ hg log -f -l1 --style something
150 150 abort: style 'something' not found
151 151 (available styles: bisect, changelog, compact, default, phases, status, xml)
152 152 [255]
153 153
154 154 -f, phases style
155 155
156 156
157 157 $ hg log -f -l1 --style phases
158 158 changeset: 4:7e4639b4691b
159 159 tag: tip
160 160 phase: draft
161 161 user: test
162 162 date: Thu Jan 01 00:00:05 1970 +0000
163 163 summary: e
164 164
165 165
166 166 $ hg log -f -l1 --style phases -q
167 167 4:7e4639b4691b
168 168
169 169 -f, but no args
170 170
171 171 $ hg log -f
172 172 changeset: 4:7e4639b4691b
173 173 tag: tip
174 174 user: test
175 175 date: Thu Jan 01 00:00:05 1970 +0000
176 176 summary: e
177 177
178 178 changeset: 3:2ca5ba701980
179 179 user: test
180 180 date: Thu Jan 01 00:00:04 1970 +0000
181 181 summary: d
182 182
183 183 changeset: 2:f8954cd4dc1f
184 184 user: test
185 185 date: Thu Jan 01 00:00:03 1970 +0000
186 186 summary: c
187 187
188 188 changeset: 1:d89b0a12d229
189 189 user: test
190 190 date: Thu Jan 01 00:00:02 1970 +0000
191 191 summary: b
192 192
193 193 changeset: 0:9161b9aeaf16
194 194 user: test
195 195 date: Thu Jan 01 00:00:01 1970 +0000
196 196 summary: a
197 197
198 198
199 199 one rename
200 200
201 201 $ hg up -q 2
202 202 $ hg log -vf a
203 203 changeset: 0:9161b9aeaf16
204 204 user: test
205 205 date: Thu Jan 01 00:00:01 1970 +0000
206 206 files: a f
207 207 description:
208 208 a
209 209
210 210
211 211
212 212 many renames
213 213
214 214 $ hg up -q tip
215 215 $ hg log -vf e
216 216 changeset: 4:7e4639b4691b
217 217 tag: tip
218 218 user: test
219 219 date: Thu Jan 01 00:00:05 1970 +0000
220 220 files: dir/b e
221 221 description:
222 222 e
223 223
224 224
225 225 changeset: 2:f8954cd4dc1f
226 226 user: test
227 227 date: Thu Jan 01 00:00:03 1970 +0000
228 228 files: b dir/b f g
229 229 description:
230 230 c
231 231
232 232
233 233 changeset: 1:d89b0a12d229
234 234 user: test
235 235 date: Thu Jan 01 00:00:02 1970 +0000
236 236 files: b g
237 237 description:
238 238 b
239 239
240 240
241 241 changeset: 0:9161b9aeaf16
242 242 user: test
243 243 date: Thu Jan 01 00:00:01 1970 +0000
244 244 files: a f
245 245 description:
246 246 a
247 247
248 248
249 249
250 250
251 251 log -pf dir/b
252 252
253 253 $ hg up -q 3
254 254 $ hg log -pf dir/b
255 255 changeset: 2:f8954cd4dc1f
256 256 user: test
257 257 date: Thu Jan 01 00:00:03 1970 +0000
258 258 summary: c
259 259
260 260 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
261 261 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
262 262 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
263 263 @@ -0,0 +1,1 @@
264 264 +a
265 265
266 266 changeset: 1:d89b0a12d229
267 267 user: test
268 268 date: Thu Jan 01 00:00:02 1970 +0000
269 269 summary: b
270 270
271 271 diff -r 9161b9aeaf16 -r d89b0a12d229 b
272 272 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
273 273 +++ b/b Thu Jan 01 00:00:02 1970 +0000
274 274 @@ -0,0 +1,1 @@
275 275 +a
276 276
277 277 changeset: 0:9161b9aeaf16
278 278 user: test
279 279 date: Thu Jan 01 00:00:01 1970 +0000
280 280 summary: a
281 281
282 282 diff -r 000000000000 -r 9161b9aeaf16 a
283 283 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
284 284 +++ b/a Thu Jan 01 00:00:01 1970 +0000
285 285 @@ -0,0 +1,1 @@
286 286 +a
287 287
288 288
289 289 log -pf b inside dir
290 290
291 291 $ hg --cwd=dir log -pf b
292 292 changeset: 2:f8954cd4dc1f
293 293 user: test
294 294 date: Thu Jan 01 00:00:03 1970 +0000
295 295 summary: c
296 296
297 297 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
298 298 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
299 299 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
300 300 @@ -0,0 +1,1 @@
301 301 +a
302 302
303 303 changeset: 1:d89b0a12d229
304 304 user: test
305 305 date: Thu Jan 01 00:00:02 1970 +0000
306 306 summary: b
307 307
308 308 diff -r 9161b9aeaf16 -r d89b0a12d229 b
309 309 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
310 310 +++ b/b Thu Jan 01 00:00:02 1970 +0000
311 311 @@ -0,0 +1,1 @@
312 312 +a
313 313
314 314 changeset: 0:9161b9aeaf16
315 315 user: test
316 316 date: Thu Jan 01 00:00:01 1970 +0000
317 317 summary: a
318 318
319 319 diff -r 000000000000 -r 9161b9aeaf16 a
320 320 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
321 321 +++ b/a Thu Jan 01 00:00:01 1970 +0000
322 322 @@ -0,0 +1,1 @@
323 323 +a
324 324
325 325
326 326 log -pf, but no args
327 327
328 328 $ hg log -pf
329 329 changeset: 3:2ca5ba701980
330 330 user: test
331 331 date: Thu Jan 01 00:00:04 1970 +0000
332 332 summary: d
333 333
334 334 diff -r f8954cd4dc1f -r 2ca5ba701980 a
335 335 --- a/a Thu Jan 01 00:00:03 1970 +0000
336 336 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
337 337 @@ -1,1 +0,0 @@
338 338 -a
339 339 diff -r f8954cd4dc1f -r 2ca5ba701980 b
340 340 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
341 341 +++ b/b Thu Jan 01 00:00:04 1970 +0000
342 342 @@ -0,0 +1,1 @@
343 343 +a
344 344 diff -r f8954cd4dc1f -r 2ca5ba701980 d
345 345 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
346 346 +++ b/d Thu Jan 01 00:00:04 1970 +0000
347 347 @@ -0,0 +1,1 @@
348 348 +a
349 349 diff -r f8954cd4dc1f -r 2ca5ba701980 g
350 350 --- a/g Thu Jan 01 00:00:03 1970 +0000
351 351 +++ b/g Thu Jan 01 00:00:04 1970 +0000
352 352 @@ -1,2 +1,2 @@
353 353 f
354 354 -g
355 355 +f
356 356
357 357 changeset: 2:f8954cd4dc1f
358 358 user: test
359 359 date: Thu Jan 01 00:00:03 1970 +0000
360 360 summary: c
361 361
362 362 diff -r d89b0a12d229 -r f8954cd4dc1f b
363 363 --- a/b Thu Jan 01 00:00:02 1970 +0000
364 364 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
365 365 @@ -1,1 +0,0 @@
366 366 -a
367 367 diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
368 368 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
369 369 +++ b/dir/b Thu Jan 01 00:00:03 1970 +0000
370 370 @@ -0,0 +1,1 @@
371 371 +a
372 372 diff -r d89b0a12d229 -r f8954cd4dc1f f
373 373 --- a/f Thu Jan 01 00:00:02 1970 +0000
374 374 +++ b/f Thu Jan 01 00:00:03 1970 +0000
375 375 @@ -1,1 +1,2 @@
376 376 f
377 377 +f
378 378 diff -r d89b0a12d229 -r f8954cd4dc1f g
379 379 --- a/g Thu Jan 01 00:00:02 1970 +0000
380 380 +++ b/g Thu Jan 01 00:00:03 1970 +0000
381 381 @@ -1,1 +1,2 @@
382 382 f
383 383 +g
384 384
385 385 changeset: 1:d89b0a12d229
386 386 user: test
387 387 date: Thu Jan 01 00:00:02 1970 +0000
388 388 summary: b
389 389
390 390 diff -r 9161b9aeaf16 -r d89b0a12d229 b
391 391 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
392 392 +++ b/b Thu Jan 01 00:00:02 1970 +0000
393 393 @@ -0,0 +1,1 @@
394 394 +a
395 395 diff -r 9161b9aeaf16 -r d89b0a12d229 g
396 396 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
397 397 +++ b/g Thu Jan 01 00:00:02 1970 +0000
398 398 @@ -0,0 +1,1 @@
399 399 +f
400 400
401 401 changeset: 0:9161b9aeaf16
402 402 user: test
403 403 date: Thu Jan 01 00:00:01 1970 +0000
404 404 summary: a
405 405
406 406 diff -r 000000000000 -r 9161b9aeaf16 a
407 407 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
408 408 +++ b/a Thu Jan 01 00:00:01 1970 +0000
409 409 @@ -0,0 +1,1 @@
410 410 +a
411 411 diff -r 000000000000 -r 9161b9aeaf16 f
412 412 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
413 413 +++ b/f Thu Jan 01 00:00:01 1970 +0000
414 414 @@ -0,0 +1,1 @@
415 415 +f
416 416
417 417
418 418 log -vf dir/b
419 419
420 420 $ hg log -vf dir/b
421 421 changeset: 2:f8954cd4dc1f
422 422 user: test
423 423 date: Thu Jan 01 00:00:03 1970 +0000
424 424 files: b dir/b f g
425 425 description:
426 426 c
427 427
428 428
429 429 changeset: 1:d89b0a12d229
430 430 user: test
431 431 date: Thu Jan 01 00:00:02 1970 +0000
432 432 files: b g
433 433 description:
434 434 b
435 435
436 436
437 437 changeset: 0:9161b9aeaf16
438 438 user: test
439 439 date: Thu Jan 01 00:00:01 1970 +0000
440 440 files: a f
441 441 description:
442 442 a
443 443
444 444
445 445
446 446
447 447 -f and multiple filelog heads
448 448
449 449 $ hg up -q 2
450 450 $ hg log -f g --template '{rev}\n'
451 451 2
452 452 1
453 453 0
454 454 $ hg up -q tip
455 455 $ hg log -f g --template '{rev}\n'
456 456 3
457 457 2
458 458 0
459 459
460 460
461 461 log copies with --copies
462 462
463 463 $ hg log -vC --template '{rev} {file_copies}\n'
464 464 4 e (dir/b)
465 465 3 b (a)g (f)
466 466 2 dir/b (b)
467 467 1 b (a)g (f)
468 468 0
469 469
470 470 log copies switch without --copies, with old filecopy template
471 471
472 472 $ hg log -v --template '{rev} {file_copies_switch%filecopy}\n'
473 473 4
474 474 3
475 475 2
476 476 1
477 477 0
478 478
479 479 log copies switch with --copies
480 480
481 481 $ hg log -vC --template '{rev} {file_copies_switch}\n'
482 482 4 e (dir/b)
483 483 3 b (a)g (f)
484 484 2 dir/b (b)
485 485 1 b (a)g (f)
486 486 0
487 487
488 488
489 489 log copies with hardcoded style and with --style=default
490 490
491 491 $ hg log -vC -r4
492 492 changeset: 4:7e4639b4691b
493 493 tag: tip
494 494 user: test
495 495 date: Thu Jan 01 00:00:05 1970 +0000
496 496 files: dir/b e
497 497 copies: e (dir/b)
498 498 description:
499 499 e
500 500
501 501
502 502 $ hg log -vC -r4 --style=default
503 503 changeset: 4:7e4639b4691b
504 504 tag: tip
505 505 user: test
506 506 date: Thu Jan 01 00:00:05 1970 +0000
507 507 files: dir/b e
508 508 copies: e (dir/b)
509 509 description:
510 510 e
511 511
512 512
513 513 $ hg log -vC -r4 -Tjson
514 514 [
515 515 {
516 516 "rev": 4,
517 517 "node": "7e4639b4691b9f84b81036a8d4fb218ce3c5e3a3",
518 518 "branch": "default",
519 519 "phase": "draft",
520 520 "user": "test",
521 521 "date": [5, 0],
522 522 "desc": "e",
523 523 "bookmarks": [],
524 524 "tags": ["tip"],
525 525 "parents": ["2ca5ba7019804f1f597249caddf22a64d34df0ba"],
526 526 "files": ["dir/b", "e"],
527 527 "copies": {"e": "dir/b"}
528 528 }
529 529 ]
530 530
531 531 log copies, non-linear manifest
532 532
533 533 $ hg up -C 3
534 534 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
535 535 $ hg mv dir/b e
536 536 $ echo foo > foo
537 537 $ hg ci -Ame2 -d '6 0'
538 538 adding foo
539 539 created new head
540 540 $ hg log -v --template '{rev} {file_copies}\n' -r 5
541 541 5 e (dir/b)
542 542
543 543
544 544 log copies, execute bit set
545 545
546 546 #if execbit
547 547 $ chmod +x e
548 548 $ hg ci -me3 -d '7 0'
549 549 $ hg log -v --template '{rev} {file_copies}\n' -r 6
550 550 6
551 551 #endif
552 552
553 553
554 554 log -p d
555 555
556 556 $ hg log -pv d
557 557 changeset: 3:2ca5ba701980
558 558 user: test
559 559 date: Thu Jan 01 00:00:04 1970 +0000
560 560 files: a b d g
561 561 description:
562 562 d
563 563
564 564
565 565 diff -r f8954cd4dc1f -r 2ca5ba701980 d
566 566 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
567 567 +++ b/d Thu Jan 01 00:00:04 1970 +0000
568 568 @@ -0,0 +1,1 @@
569 569 +a
570 570
571 571
572 572
573 573 log --removed file
574 574
575 575 $ hg log --removed -v a
576 576 changeset: 3:2ca5ba701980
577 577 user: test
578 578 date: Thu Jan 01 00:00:04 1970 +0000
579 579 files: a b d g
580 580 description:
581 581 d
582 582
583 583
584 584 changeset: 0:9161b9aeaf16
585 585 user: test
586 586 date: Thu Jan 01 00:00:01 1970 +0000
587 587 files: a f
588 588 description:
589 589 a
590 590
591 591
592 592
593 593 log --removed revrange file
594 594
595 595 $ hg log --removed -v -r0:2 a
596 596 changeset: 0:9161b9aeaf16
597 597 user: test
598 598 date: Thu Jan 01 00:00:01 1970 +0000
599 599 files: a f
600 600 description:
601 601 a
602 602
603 603
604 604 $ cd ..
605 605
606 606 log --follow tests
607 607
608 608 $ hg init follow
609 609 $ cd follow
610 610
611 611 $ echo base > base
612 612 $ hg ci -Ambase -d '1 0'
613 613 adding base
614 614
615 615 $ echo r1 >> base
616 616 $ hg ci -Amr1 -d '1 0'
617 617 $ echo r2 >> base
618 618 $ hg ci -Amr2 -d '1 0'
619 619
620 620 $ hg up -C 1
621 621 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
622 622 $ echo b1 > b1
623 623 $ hg ci -Amb1 -d '1 0'
624 624 adding b1
625 625 created new head
626 626
627 627
628 628 log -f
629 629
630 630 $ hg log -f
631 631 changeset: 3:e62f78d544b4
632 632 tag: tip
633 633 parent: 1:3d5bf5654eda
634 634 user: test
635 635 date: Thu Jan 01 00:00:01 1970 +0000
636 636 summary: b1
637 637
638 638 changeset: 1:3d5bf5654eda
639 639 user: test
640 640 date: Thu Jan 01 00:00:01 1970 +0000
641 641 summary: r1
642 642
643 643 changeset: 0:67e992f2c4f3
644 644 user: test
645 645 date: Thu Jan 01 00:00:01 1970 +0000
646 646 summary: base
647 647
648 648
649 649
650 650 log -f -r '1 + 4'
651 651
652 652 $ hg up -C 0
653 653 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
654 654 $ echo b2 > b2
655 655 $ hg ci -Amb2 -d '1 0'
656 656 adding b2
657 657 created new head
658 658 $ hg log -f -r '1 + 4'
659 659 changeset: 4:ddb82e70d1a1
660 660 tag: tip
661 661 parent: 0:67e992f2c4f3
662 662 user: test
663 663 date: Thu Jan 01 00:00:01 1970 +0000
664 664 summary: b2
665 665
666 666 changeset: 1:3d5bf5654eda
667 667 user: test
668 668 date: Thu Jan 01 00:00:01 1970 +0000
669 669 summary: r1
670 670
671 671 changeset: 0:67e992f2c4f3
672 672 user: test
673 673 date: Thu Jan 01 00:00:01 1970 +0000
674 674 summary: base
675 675
676 676 log -f -r null
677 677
678 678 $ hg log -f -r null
679 679 changeset: -1:000000000000
680 680 user:
681 681 date: Thu Jan 01 00:00:00 1970 +0000
682 682
683 683 $ hg log -f -r null -G
684 684 o changeset: -1:000000000000
685 685 user:
686 686 date: Thu Jan 01 00:00:00 1970 +0000
687 687
688 688
689 689
690 690 log -f with null parent
691 691
692 692 $ hg up -C null
693 693 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
694 694 $ hg log -f
695 695
696 696
697 697 log -r . with two parents
698 698
699 699 $ hg up -C 3
700 700 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
701 701 $ hg merge tip
702 702 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
703 703 (branch merge, don't forget to commit)
704 704 $ hg log -r .
705 705 changeset: 3:e62f78d544b4
706 706 parent: 1:3d5bf5654eda
707 707 user: test
708 708 date: Thu Jan 01 00:00:01 1970 +0000
709 709 summary: b1
710 710
711 711
712 712
713 713 log -r . with one parent
714 714
715 715 $ hg ci -mm12 -d '1 0'
716 716 $ hg log -r .
717 717 changeset: 5:302e9dd6890d
718 718 tag: tip
719 719 parent: 3:e62f78d544b4
720 720 parent: 4:ddb82e70d1a1
721 721 user: test
722 722 date: Thu Jan 01 00:00:01 1970 +0000
723 723 summary: m12
724 724
725 725
726 726 $ echo postm >> b1
727 727 $ hg ci -Amb1.1 -d'1 0'
728 728
729 729
730 730 log --follow-first
731 731
732 732 $ hg log --follow-first
733 733 changeset: 6:2404bbcab562
734 734 tag: tip
735 735 user: test
736 736 date: Thu Jan 01 00:00:01 1970 +0000
737 737 summary: b1.1
738 738
739 739 changeset: 5:302e9dd6890d
740 740 parent: 3:e62f78d544b4
741 741 parent: 4:ddb82e70d1a1
742 742 user: test
743 743 date: Thu Jan 01 00:00:01 1970 +0000
744 744 summary: m12
745 745
746 746 changeset: 3:e62f78d544b4
747 747 parent: 1:3d5bf5654eda
748 748 user: test
749 749 date: Thu Jan 01 00:00:01 1970 +0000
750 750 summary: b1
751 751
752 752 changeset: 1:3d5bf5654eda
753 753 user: test
754 754 date: Thu Jan 01 00:00:01 1970 +0000
755 755 summary: r1
756 756
757 757 changeset: 0:67e992f2c4f3
758 758 user: test
759 759 date: Thu Jan 01 00:00:01 1970 +0000
760 760 summary: base
761 761
762 762
763 763
764 764 log -P 2
765 765
766 766 $ hg log -P 2
767 767 changeset: 6:2404bbcab562
768 768 tag: tip
769 769 user: test
770 770 date: Thu Jan 01 00:00:01 1970 +0000
771 771 summary: b1.1
772 772
773 773 changeset: 5:302e9dd6890d
774 774 parent: 3:e62f78d544b4
775 775 parent: 4:ddb82e70d1a1
776 776 user: test
777 777 date: Thu Jan 01 00:00:01 1970 +0000
778 778 summary: m12
779 779
780 780 changeset: 4:ddb82e70d1a1
781 781 parent: 0:67e992f2c4f3
782 782 user: test
783 783 date: Thu Jan 01 00:00:01 1970 +0000
784 784 summary: b2
785 785
786 786 changeset: 3:e62f78d544b4
787 787 parent: 1:3d5bf5654eda
788 788 user: test
789 789 date: Thu Jan 01 00:00:01 1970 +0000
790 790 summary: b1
791 791
792 792
793 793
794 794 log -r tip -p --git
795 795
796 796 $ hg log -r tip -p --git
797 797 changeset: 6:2404bbcab562
798 798 tag: tip
799 799 user: test
800 800 date: Thu Jan 01 00:00:01 1970 +0000
801 801 summary: b1.1
802 802
803 803 diff --git a/b1 b/b1
804 804 --- a/b1
805 805 +++ b/b1
806 806 @@ -1,1 +1,2 @@
807 807 b1
808 808 +postm
809 809
810 810
811 811
812 812 log -r ""
813 813
814 814 $ hg log -r ''
815 815 hg: parse error: empty query
816 816 [255]
817 817
818 818 log -r <some unknown node id>
819 819
820 820 $ hg log -r 1000000000000000000000000000000000000000
821 821 abort: unknown revision '1000000000000000000000000000000000000000'!
822 822 [255]
823 823
824 824 log -k r1
825 825
826 826 $ hg log -k r1
827 827 changeset: 1:3d5bf5654eda
828 828 user: test
829 829 date: Thu Jan 01 00:00:01 1970 +0000
830 830 summary: r1
831 831
832 832 log -p -l2 --color=always
833 833
834 834 $ hg --config extensions.color= --config color.mode=ansi \
835 835 > log -p -l2 --color=always
836 836 \x1b[0;33mchangeset: 6:2404bbcab562\x1b[0m (esc)
837 837 tag: tip
838 838 user: test
839 839 date: Thu Jan 01 00:00:01 1970 +0000
840 840 summary: b1.1
841 841
842 842 \x1b[0;1mdiff -r 302e9dd6890d -r 2404bbcab562 b1\x1b[0m (esc)
843 843 \x1b[0;31;1m--- a/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
844 844 \x1b[0;32;1m+++ b/b1 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
845 845 \x1b[0;35m@@ -1,1 +1,2 @@\x1b[0m (esc)
846 846 b1
847 847 \x1b[0;32m+postm\x1b[0m (esc)
848 848
849 849 \x1b[0;33mchangeset: 5:302e9dd6890d\x1b[0m (esc)
850 850 parent: 3:e62f78d544b4
851 851 parent: 4:ddb82e70d1a1
852 852 user: test
853 853 date: Thu Jan 01 00:00:01 1970 +0000
854 854 summary: m12
855 855
856 856 \x1b[0;1mdiff -r e62f78d544b4 -r 302e9dd6890d b2\x1b[0m (esc)
857 857 \x1b[0;31;1m--- /dev/null Thu Jan 01 00:00:00 1970 +0000\x1b[0m (esc)
858 858 \x1b[0;32;1m+++ b/b2 Thu Jan 01 00:00:01 1970 +0000\x1b[0m (esc)
859 859 \x1b[0;35m@@ -0,0 +1,1 @@\x1b[0m (esc)
860 860 \x1b[0;32m+b2\x1b[0m (esc)
861 861
862 862
863 863
864 864 log -r tip --stat
865 865
866 866 $ hg log -r tip --stat
867 867 changeset: 6:2404bbcab562
868 868 tag: tip
869 869 user: test
870 870 date: Thu Jan 01 00:00:01 1970 +0000
871 871 summary: b1.1
872 872
873 873 b1 | 1 +
874 874 1 files changed, 1 insertions(+), 0 deletions(-)
875 875
876 876
877 877 $ cd ..
878 878
879 879
880 880 User
881 881
882 882 $ hg init usertest
883 883 $ cd usertest
884 884
885 885 $ echo a > a
886 886 $ hg ci -A -m "a" -u "User One <user1@example.org>"
887 887 adding a
888 888 $ echo b > b
889 889 $ hg ci -A -m "b" -u "User Two <user2@example.org>"
890 890 adding b
891 891
892 892 $ hg log -u "User One <user1@example.org>"
893 893 changeset: 0:29a4c94f1924
894 894 user: User One <user1@example.org>
895 895 date: Thu Jan 01 00:00:00 1970 +0000
896 896 summary: a
897 897
898 898 $ hg log -u "user1" -u "user2"
899 899 changeset: 1:e834b5e69c0e
900 900 tag: tip
901 901 user: User Two <user2@example.org>
902 902 date: Thu Jan 01 00:00:00 1970 +0000
903 903 summary: b
904 904
905 905 changeset: 0:29a4c94f1924
906 906 user: User One <user1@example.org>
907 907 date: Thu Jan 01 00:00:00 1970 +0000
908 908 summary: a
909 909
910 910 $ hg log -u "user3"
911 911
912 912 $ cd ..
913 913
914 914 $ hg init branches
915 915 $ cd branches
916 916
917 917 $ echo a > a
918 918 $ hg ci -A -m "commit on default"
919 919 adding a
920 920 $ hg branch test
921 921 marked working directory as branch test
922 922 (branches are permanent and global, did you want a bookmark?)
923 923 $ echo b > b
924 924 $ hg ci -A -m "commit on test"
925 925 adding b
926 926
927 927 $ hg up default
928 928 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
929 929 $ echo c > c
930 930 $ hg ci -A -m "commit on default"
931 931 adding c
932 932 $ hg up test
933 933 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
934 934 $ echo c > c
935 935 $ hg ci -A -m "commit on test"
936 936 adding c
937 937
938 938
939 939 log -b default
940 940
941 941 $ hg log -b default
942 942 changeset: 2:c3a4f03cc9a7
943 943 parent: 0:24427303d56f
944 944 user: test
945 945 date: Thu Jan 01 00:00:00 1970 +0000
946 946 summary: commit on default
947 947
948 948 changeset: 0:24427303d56f
949 949 user: test
950 950 date: Thu Jan 01 00:00:00 1970 +0000
951 951 summary: commit on default
952 952
953 953
954 954
955 955 log -b test
956 956
957 957 $ hg log -b test
958 958 changeset: 3:f5d8de11c2e2
959 959 branch: test
960 960 tag: tip
961 961 parent: 1:d32277701ccb
962 962 user: test
963 963 date: Thu Jan 01 00:00:00 1970 +0000
964 964 summary: commit on test
965 965
966 966 changeset: 1:d32277701ccb
967 967 branch: test
968 968 user: test
969 969 date: Thu Jan 01 00:00:00 1970 +0000
970 970 summary: commit on test
971 971
972 972
973 973
974 974 log -b dummy
975 975
976 976 $ hg log -b dummy
977 977 abort: unknown revision 'dummy'!
978 978 [255]
979 979
980 980
981 981 log -b .
982 982
983 983 $ hg log -b .
984 984 changeset: 3:f5d8de11c2e2
985 985 branch: test
986 986 tag: tip
987 987 parent: 1:d32277701ccb
988 988 user: test
989 989 date: Thu Jan 01 00:00:00 1970 +0000
990 990 summary: commit on test
991 991
992 992 changeset: 1:d32277701ccb
993 993 branch: test
994 994 user: test
995 995 date: Thu Jan 01 00:00:00 1970 +0000
996 996 summary: commit on test
997 997
998 998
999 999
1000 1000 log -b default -b test
1001 1001
1002 1002 $ hg log -b default -b test
1003 1003 changeset: 3:f5d8de11c2e2
1004 1004 branch: test
1005 1005 tag: tip
1006 1006 parent: 1:d32277701ccb
1007 1007 user: test
1008 1008 date: Thu Jan 01 00:00:00 1970 +0000
1009 1009 summary: commit on test
1010 1010
1011 1011 changeset: 2:c3a4f03cc9a7
1012 1012 parent: 0:24427303d56f
1013 1013 user: test
1014 1014 date: Thu Jan 01 00:00:00 1970 +0000
1015 1015 summary: commit on default
1016 1016
1017 1017 changeset: 1:d32277701ccb
1018 1018 branch: test
1019 1019 user: test
1020 1020 date: Thu Jan 01 00:00:00 1970 +0000
1021 1021 summary: commit on test
1022 1022
1023 1023 changeset: 0:24427303d56f
1024 1024 user: test
1025 1025 date: Thu Jan 01 00:00:00 1970 +0000
1026 1026 summary: commit on default
1027 1027
1028 1028
1029 1029
1030 1030 log -b default -b .
1031 1031
1032 1032 $ hg log -b default -b .
1033 1033 changeset: 3:f5d8de11c2e2
1034 1034 branch: test
1035 1035 tag: tip
1036 1036 parent: 1:d32277701ccb
1037 1037 user: test
1038 1038 date: Thu Jan 01 00:00:00 1970 +0000
1039 1039 summary: commit on test
1040 1040
1041 1041 changeset: 2:c3a4f03cc9a7
1042 1042 parent: 0:24427303d56f
1043 1043 user: test
1044 1044 date: Thu Jan 01 00:00:00 1970 +0000
1045 1045 summary: commit on default
1046 1046
1047 1047 changeset: 1:d32277701ccb
1048 1048 branch: test
1049 1049 user: test
1050 1050 date: Thu Jan 01 00:00:00 1970 +0000
1051 1051 summary: commit on test
1052 1052
1053 1053 changeset: 0:24427303d56f
1054 1054 user: test
1055 1055 date: Thu Jan 01 00:00:00 1970 +0000
1056 1056 summary: commit on default
1057 1057
1058 1058
1059 1059
1060 1060 log -b . -b test
1061 1061
1062 1062 $ hg log -b . -b test
1063 1063 changeset: 3:f5d8de11c2e2
1064 1064 branch: test
1065 1065 tag: tip
1066 1066 parent: 1:d32277701ccb
1067 1067 user: test
1068 1068 date: Thu Jan 01 00:00:00 1970 +0000
1069 1069 summary: commit on test
1070 1070
1071 1071 changeset: 1:d32277701ccb
1072 1072 branch: test
1073 1073 user: test
1074 1074 date: Thu Jan 01 00:00:00 1970 +0000
1075 1075 summary: commit on test
1076 1076
1077 1077
1078 1078
1079 1079 log -b 2
1080 1080
1081 1081 $ hg log -b 2
1082 1082 changeset: 2:c3a4f03cc9a7
1083 1083 parent: 0:24427303d56f
1084 1084 user: test
1085 1085 date: Thu Jan 01 00:00:00 1970 +0000
1086 1086 summary: commit on default
1087 1087
1088 1088 changeset: 0:24427303d56f
1089 1089 user: test
1090 1090 date: Thu Jan 01 00:00:00 1970 +0000
1091 1091 summary: commit on default
1092 1092
1093 1093 #if gettext
1094 1094
1095 1095 Test that all log names are translated (e.g. branches, bookmarks, tags):
1096 1096
1097 1097 $ hg bookmark babar -r tip
1098 1098
1099 1099 $ HGENCODING=UTF-8 LANGUAGE=de hg log -r tip
1100 1100 \xc3\x84nderung: 3:f5d8de11c2e2 (esc)
1101 1101 Zweig: test
1102 1102 Lesezeichen: babar
1103 1103 Marke: tip
1104 1104 Vorg\xc3\xa4nger: 1:d32277701ccb (esc)
1105 1105 Nutzer: test
1106 1106 Datum: Thu Jan 01 00:00:00 1970 +0000
1107 1107 Zusammenfassung: commit on test
1108 1108
1109 1109 $ hg bookmark -d babar
1110 1110
1111 1111 #endif
1112 1112
1113 1113 log -p --cwd dir (in subdir)
1114 1114
1115 1115 $ mkdir dir
1116 1116 $ hg log -p --cwd dir
1117 1117 changeset: 3:f5d8de11c2e2
1118 1118 branch: test
1119 1119 tag: tip
1120 1120 parent: 1:d32277701ccb
1121 1121 user: test
1122 1122 date: Thu Jan 01 00:00:00 1970 +0000
1123 1123 summary: commit on test
1124 1124
1125 1125 diff -r d32277701ccb -r f5d8de11c2e2 c
1126 1126 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1127 1127 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1128 1128 @@ -0,0 +1,1 @@
1129 1129 +c
1130 1130
1131 1131 changeset: 2:c3a4f03cc9a7
1132 1132 parent: 0:24427303d56f
1133 1133 user: test
1134 1134 date: Thu Jan 01 00:00:00 1970 +0000
1135 1135 summary: commit on default
1136 1136
1137 1137 diff -r 24427303d56f -r c3a4f03cc9a7 c
1138 1138 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1139 1139 +++ b/c Thu Jan 01 00:00:00 1970 +0000
1140 1140 @@ -0,0 +1,1 @@
1141 1141 +c
1142 1142
1143 1143 changeset: 1:d32277701ccb
1144 1144 branch: test
1145 1145 user: test
1146 1146 date: Thu Jan 01 00:00:00 1970 +0000
1147 1147 summary: commit on test
1148 1148
1149 1149 diff -r 24427303d56f -r d32277701ccb b
1150 1150 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1151 1151 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1152 1152 @@ -0,0 +1,1 @@
1153 1153 +b
1154 1154
1155 1155 changeset: 0:24427303d56f
1156 1156 user: test
1157 1157 date: Thu Jan 01 00:00:00 1970 +0000
1158 1158 summary: commit on default
1159 1159
1160 1160 diff -r 000000000000 -r 24427303d56f a
1161 1161 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1162 1162 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1163 1163 @@ -0,0 +1,1 @@
1164 1164 +a
1165 1165
1166 1166
1167 1167
1168 1168 log -p -R repo
1169 1169
1170 1170 $ cd dir
1171 1171 $ hg log -p -R .. ../a
1172 1172 changeset: 0:24427303d56f
1173 1173 user: test
1174 1174 date: Thu Jan 01 00:00:00 1970 +0000
1175 1175 summary: commit on default
1176 1176
1177 1177 diff -r 000000000000 -r 24427303d56f a
1178 1178 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1179 1179 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1180 1180 @@ -0,0 +1,1 @@
1181 1181 +a
1182 1182
1183 1183
1184 1184 $ cd ../..
1185 1185
1186 1186 $ hg init follow2
1187 1187 $ cd follow2
1188 1188
1189 1189 # Build the following history:
1190 1190 # tip - o - x - o - x - x
1191 1191 # \ /
1192 1192 # o - o - o - x
1193 1193 # \ /
1194 1194 # o
1195 1195 #
1196 1196 # Where "o" is a revision containing "foo" and
1197 1197 # "x" is a revision without "foo"
1198 1198
1199 1199 $ touch init
1200 1200 $ hg ci -A -m "init, unrelated"
1201 1201 adding init
1202 1202 $ echo 'foo' > init
1203 1203 $ hg ci -m "change, unrelated"
1204 1204 $ echo 'foo' > foo
1205 1205 $ hg ci -A -m "add unrelated old foo"
1206 1206 adding foo
1207 1207 $ hg rm foo
1208 1208 $ hg ci -m "delete foo, unrelated"
1209 1209 $ echo 'related' > foo
1210 1210 $ hg ci -A -m "add foo, related"
1211 1211 adding foo
1212 1212
1213 1213 $ hg up 0
1214 1214 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1215 1215 $ touch branch
1216 1216 $ hg ci -A -m "first branch, unrelated"
1217 1217 adding branch
1218 1218 created new head
1219 1219 $ touch foo
1220 1220 $ hg ci -A -m "create foo, related"
1221 1221 adding foo
1222 1222 $ echo 'change' > foo
1223 1223 $ hg ci -m "change foo, related"
1224 1224
1225 1225 $ hg up 6
1226 1226 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1227 1227 $ echo 'change foo in branch' > foo
1228 1228 $ hg ci -m "change foo in branch, related"
1229 1229 created new head
1230 1230 $ hg merge 7
1231 1231 merging foo
1232 1232 warning: conflicts during merge.
1233 1233 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1234 1234 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
1235 1235 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1236 1236 [1]
1237 1237 $ echo 'merge 1' > foo
1238 1238 $ hg resolve -m foo
1239 1239 (no more unresolved files)
1240 1240 $ hg ci -m "First merge, related"
1241 1241
1242 1242 $ hg merge 4
1243 1243 merging foo
1244 1244 warning: conflicts during merge.
1245 1245 merging foo incomplete! (edit conflicts, then use 'hg resolve --mark')
1246 1246 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
1247 1247 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
1248 1248 [1]
1249 1249 $ echo 'merge 2' > foo
1250 1250 $ hg resolve -m foo
1251 1251 (no more unresolved files)
1252 1252 $ hg ci -m "Last merge, related"
1253 1253
1254 1254 $ hg log --graph
1255 1255 @ changeset: 10:4dae8563d2c5
1256 1256 |\ tag: tip
1257 1257 | | parent: 9:7b35701b003e
1258 1258 | | parent: 4:88176d361b69
1259 1259 | | user: test
1260 1260 | | date: Thu Jan 01 00:00:00 1970 +0000
1261 1261 | | summary: Last merge, related
1262 1262 | |
1263 1263 | o changeset: 9:7b35701b003e
1264 1264 | |\ parent: 8:e5416ad8a855
1265 1265 | | | parent: 7:87fe3144dcfa
1266 1266 | | | user: test
1267 1267 | | | date: Thu Jan 01 00:00:00 1970 +0000
1268 1268 | | | summary: First merge, related
1269 1269 | | |
1270 1270 | | o changeset: 8:e5416ad8a855
1271 1271 | | | parent: 6:dc6c325fe5ee
1272 1272 | | | user: test
1273 1273 | | | date: Thu Jan 01 00:00:00 1970 +0000
1274 1274 | | | summary: change foo in branch, related
1275 1275 | | |
1276 1276 | o | changeset: 7:87fe3144dcfa
1277 1277 | |/ user: test
1278 1278 | | date: Thu Jan 01 00:00:00 1970 +0000
1279 1279 | | summary: change foo, related
1280 1280 | |
1281 1281 | o changeset: 6:dc6c325fe5ee
1282 1282 | | user: test
1283 1283 | | date: Thu Jan 01 00:00:00 1970 +0000
1284 1284 | | summary: create foo, related
1285 1285 | |
1286 1286 | o changeset: 5:73db34516eb9
1287 1287 | | parent: 0:e87515fd044a
1288 1288 | | user: test
1289 1289 | | date: Thu Jan 01 00:00:00 1970 +0000
1290 1290 | | summary: first branch, unrelated
1291 1291 | |
1292 1292 o | changeset: 4:88176d361b69
1293 1293 | | user: test
1294 1294 | | date: Thu Jan 01 00:00:00 1970 +0000
1295 1295 | | summary: add foo, related
1296 1296 | |
1297 1297 o | changeset: 3:dd78ae4afb56
1298 1298 | | user: test
1299 1299 | | date: Thu Jan 01 00:00:00 1970 +0000
1300 1300 | | summary: delete foo, unrelated
1301 1301 | |
1302 1302 o | changeset: 2:c4c64aedf0f7
1303 1303 | | user: test
1304 1304 | | date: Thu Jan 01 00:00:00 1970 +0000
1305 1305 | | summary: add unrelated old foo
1306 1306 | |
1307 1307 o | changeset: 1:e5faa7440653
1308 1308 |/ user: test
1309 1309 | date: Thu Jan 01 00:00:00 1970 +0000
1310 1310 | summary: change, unrelated
1311 1311 |
1312 1312 o changeset: 0:e87515fd044a
1313 1313 user: test
1314 1314 date: Thu Jan 01 00:00:00 1970 +0000
1315 1315 summary: init, unrelated
1316 1316
1317 1317
1318 1318 $ hg --traceback log -f foo
1319 1319 changeset: 10:4dae8563d2c5
1320 1320 tag: tip
1321 1321 parent: 9:7b35701b003e
1322 1322 parent: 4:88176d361b69
1323 1323 user: test
1324 1324 date: Thu Jan 01 00:00:00 1970 +0000
1325 1325 summary: Last merge, related
1326 1326
1327 1327 changeset: 9:7b35701b003e
1328 1328 parent: 8:e5416ad8a855
1329 1329 parent: 7:87fe3144dcfa
1330 1330 user: test
1331 1331 date: Thu Jan 01 00:00:00 1970 +0000
1332 1332 summary: First merge, related
1333 1333
1334 1334 changeset: 8:e5416ad8a855
1335 1335 parent: 6:dc6c325fe5ee
1336 1336 user: test
1337 1337 date: Thu Jan 01 00:00:00 1970 +0000
1338 1338 summary: change foo in branch, related
1339 1339
1340 1340 changeset: 7:87fe3144dcfa
1341 1341 user: test
1342 1342 date: Thu Jan 01 00:00:00 1970 +0000
1343 1343 summary: change foo, related
1344 1344
1345 1345 changeset: 6:dc6c325fe5ee
1346 1346 user: test
1347 1347 date: Thu Jan 01 00:00:00 1970 +0000
1348 1348 summary: create foo, related
1349 1349
1350 1350 changeset: 4:88176d361b69
1351 1351 user: test
1352 1352 date: Thu Jan 01 00:00:00 1970 +0000
1353 1353 summary: add foo, related
1354 1354
1355 1355
1356 1356 Also check when maxrev < lastrevfilelog
1357 1357
1358 1358 $ hg --traceback log -f -r4 foo
1359 1359 changeset: 4:88176d361b69
1360 1360 user: test
1361 1361 date: Thu Jan 01 00:00:00 1970 +0000
1362 1362 summary: add foo, related
1363 1363
1364 1364 changeset: 2:c4c64aedf0f7
1365 1365 user: test
1366 1366 date: Thu Jan 01 00:00:00 1970 +0000
1367 1367 summary: add unrelated old foo
1368 1368
1369 1369 $ cd ..
1370 1370
1371 1371 Issue2383: hg log showing _less_ differences than hg diff
1372 1372
1373 1373 $ hg init issue2383
1374 1374 $ cd issue2383
1375 1375
1376 1376 Create a test repo:
1377 1377
1378 1378 $ echo a > a
1379 1379 $ hg ci -Am0
1380 1380 adding a
1381 1381 $ echo b > b
1382 1382 $ hg ci -Am1
1383 1383 adding b
1384 1384 $ hg co 0
1385 1385 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1386 1386 $ echo b > a
1387 1387 $ hg ci -m2
1388 1388 created new head
1389 1389
1390 1390 Merge:
1391 1391
1392 1392 $ hg merge
1393 1393 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1394 1394 (branch merge, don't forget to commit)
1395 1395
1396 1396 Make sure there's a file listed in the merge to trigger the bug:
1397 1397
1398 1398 $ echo c > a
1399 1399 $ hg ci -m3
1400 1400
1401 1401 Two files shown here in diff:
1402 1402
1403 1403 $ hg diff --rev 2:3
1404 1404 diff -r b09be438c43a -r 8e07aafe1edc a
1405 1405 --- a/a Thu Jan 01 00:00:00 1970 +0000
1406 1406 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1407 1407 @@ -1,1 +1,1 @@
1408 1408 -b
1409 1409 +c
1410 1410 diff -r b09be438c43a -r 8e07aafe1edc b
1411 1411 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1412 1412 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1413 1413 @@ -0,0 +1,1 @@
1414 1414 +b
1415 1415
1416 1416 Diff here should be the same:
1417 1417
1418 1418 $ hg log -vpr 3
1419 1419 changeset: 3:8e07aafe1edc
1420 1420 tag: tip
1421 1421 parent: 2:b09be438c43a
1422 1422 parent: 1:925d80f479bb
1423 1423 user: test
1424 1424 date: Thu Jan 01 00:00:00 1970 +0000
1425 1425 files: a
1426 1426 description:
1427 1427 3
1428 1428
1429 1429
1430 1430 diff -r b09be438c43a -r 8e07aafe1edc a
1431 1431 --- a/a Thu Jan 01 00:00:00 1970 +0000
1432 1432 +++ b/a Thu Jan 01 00:00:00 1970 +0000
1433 1433 @@ -1,1 +1,1 @@
1434 1434 -b
1435 1435 +c
1436 1436 diff -r b09be438c43a -r 8e07aafe1edc b
1437 1437 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1438 1438 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1439 1439 @@ -0,0 +1,1 @@
1440 1440 +b
1441 1441
1442 1442 $ cd ..
1443 1443
1444 1444 'hg log -r rev fn' when last(filelog(fn)) != rev
1445 1445
1446 1446 $ hg init simplelog
1447 1447 $ cd simplelog
1448 1448 $ echo f > a
1449 1449 $ hg ci -Am'a' -d '0 0'
1450 1450 adding a
1451 1451 $ echo f >> a
1452 1452 $ hg ci -Am'a bis' -d '1 0'
1453 1453
1454 1454 $ hg log -r0 a
1455 1455 changeset: 0:9f758d63dcde
1456 1456 user: test
1457 1457 date: Thu Jan 01 00:00:00 1970 +0000
1458 1458 summary: a
1459 1459
1460 1460 enable obsolete to test hidden feature
1461 1461
1462 1462 $ cat >> $HGRCPATH << EOF
1463 1463 > [experimental]
1464 1464 > evolution=createmarkers
1465 1465 > EOF
1466 1466
1467 1467 $ hg log --template='{rev}:{node}\n'
1468 1468 1:a765632148dc55d38c35c4f247c618701886cb2f
1469 1469 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1470 1470 $ hg debugobsolete a765632148dc55d38c35c4f247c618701886cb2f
1471 1471 $ hg up null -q
1472 1472 $ hg log --template='{rev}:{node}\n'
1473 1473 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1474 1474 $ hg log --template='{rev}:{node}\n' --hidden
1475 1475 1:a765632148dc55d38c35c4f247c618701886cb2f
1476 1476 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1477 1477 $ hg log -r a
1478 1478 abort: hidden revision 'a'!
1479 1479 (use --hidden to access hidden revisions)
1480 1480 [255]
1481 1481
1482 1482 test that parent prevent a changeset to be hidden
1483 1483
1484 1484 $ hg up 1 -q --hidden
1485 1485 $ hg log --template='{rev}:{node}\n'
1486 1486 1:a765632148dc55d38c35c4f247c618701886cb2f
1487 1487 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1488 1488
1489 1489 test that second parent prevent a changeset to be hidden too
1490 1490
1491 1491 $ hg debugsetparents 0 1 # nothing suitable to merge here
1492 1492 $ hg log --template='{rev}:{node}\n'
1493 1493 1:a765632148dc55d38c35c4f247c618701886cb2f
1494 1494 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1495 1495 $ hg debugsetparents 1
1496 1496 $ hg up -q null
1497 1497
1498 1498 bookmarks prevent a changeset being hidden
1499 1499
1500 1500 $ hg bookmark --hidden -r 1 X
1501 1501 $ hg log --template '{rev}:{node}\n'
1502 1502 1:a765632148dc55d38c35c4f247c618701886cb2f
1503 1503 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1504 1504 $ hg bookmark -d X
1505 1505
1506 1506 divergent bookmarks are not hidden
1507 1507
1508 1508 $ hg bookmark --hidden -r 1 X@foo
1509 1509 $ hg log --template '{rev}:{node}\n'
1510 1510 1:a765632148dc55d38c35c4f247c618701886cb2f
1511 1511 0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
1512 1512
1513 1513 clear extensions configuration
1514 1514 $ echo '[extensions]' >> $HGRCPATH
1515 1515 $ echo "obs=!" >> $HGRCPATH
1516 1516 $ cd ..
1517 1517
1518 1518 test -u/-k for problematic encoding
1519 1519 # unicode: cp932:
1520 1520 # u30A2 0x83 0x41(= 'A')
1521 1521 # u30C2 0x83 0x61(= 'a')
1522 1522
1523 1523 $ hg init problematicencoding
1524 1524 $ cd problematicencoding
1525 1525
1526 1526 $ python > setup.sh <<EOF
1527 1527 > print u'''
1528 1528 > echo a > text
1529 1529 > hg add text
1530 1530 > hg --encoding utf-8 commit -u '\u30A2' -m none
1531 1531 > echo b > text
1532 1532 > hg --encoding utf-8 commit -u '\u30C2' -m none
1533 1533 > echo c > text
1534 1534 > hg --encoding utf-8 commit -u none -m '\u30A2'
1535 1535 > echo d > text
1536 1536 > hg --encoding utf-8 commit -u none -m '\u30C2'
1537 1537 > '''.encode('utf-8')
1538 1538 > EOF
1539 1539 $ sh < setup.sh
1540 1540
1541 1541 test in problematic encoding
1542 1542 $ python > test.sh <<EOF
1543 1543 > print u'''
1544 1544 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
1545 1545 > echo ====
1546 1546 > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
1547 1547 > echo ====
1548 1548 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
1549 1549 > echo ====
1550 1550 > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
1551 1551 > '''.encode('cp932')
1552 1552 > EOF
1553 1553 $ sh < test.sh
1554 1554 0
1555 1555 ====
1556 1556 1
1557 1557 ====
1558 1558 2
1559 1559 0
1560 1560 ====
1561 1561 3
1562 1562 1
1563 1563
1564 1564 $ cd ..
1565 1565
1566 1566 test hg log on non-existent files and on directories
1567 1567 $ hg init issue1340
1568 1568 $ cd issue1340
1569 1569 $ mkdir d1; mkdir D2; mkdir D3.i; mkdir d4.hg; mkdir d5.d; mkdir .d6
1570 1570 $ echo 1 > d1/f1
1571 1571 $ echo 1 > D2/f1
1572 1572 $ echo 1 > D3.i/f1
1573 1573 $ echo 1 > d4.hg/f1
1574 1574 $ echo 1 > d5.d/f1
1575 1575 $ echo 1 > .d6/f1
1576 1576 $ hg -q add .
1577 1577 $ hg commit -m "a bunch of weird directories"
1578 1578 $ hg log -l1 d1/f1 | grep changeset
1579 1579 changeset: 0:65624cd9070a
1580 1580 $ hg log -l1 f1
1581 1581 $ hg log -l1 . | grep changeset
1582 1582 changeset: 0:65624cd9070a
1583 1583 $ hg log -l1 ./ | grep changeset
1584 1584 changeset: 0:65624cd9070a
1585 1585 $ hg log -l1 d1 | grep changeset
1586 1586 changeset: 0:65624cd9070a
1587 1587 $ hg log -l1 D2 | grep changeset
1588 1588 changeset: 0:65624cd9070a
1589 1589 $ hg log -l1 D2/f1 | grep changeset
1590 1590 changeset: 0:65624cd9070a
1591 1591 $ hg log -l1 D3.i | grep changeset
1592 1592 changeset: 0:65624cd9070a
1593 1593 $ hg log -l1 D3.i/f1 | grep changeset
1594 1594 changeset: 0:65624cd9070a
1595 1595 $ hg log -l1 d4.hg | grep changeset
1596 1596 changeset: 0:65624cd9070a
1597 1597 $ hg log -l1 d4.hg/f1 | grep changeset
1598 1598 changeset: 0:65624cd9070a
1599 1599 $ hg log -l1 d5.d | grep changeset
1600 1600 changeset: 0:65624cd9070a
1601 1601 $ hg log -l1 d5.d/f1 | grep changeset
1602 1602 changeset: 0:65624cd9070a
1603 1603 $ hg log -l1 .d6 | grep changeset
1604 1604 changeset: 0:65624cd9070a
1605 1605 $ hg log -l1 .d6/f1 | grep changeset
1606 1606 changeset: 0:65624cd9070a
1607 1607
1608 1608 issue3772: hg log -r :null showing revision 0 as well
1609 1609
1610 1610 $ hg log -r :null
1611 1611 changeset: 0:65624cd9070a
1612 1612 tag: tip
1613 1613 user: test
1614 1614 date: Thu Jan 01 00:00:00 1970 +0000
1615 1615 summary: a bunch of weird directories
1616 1616
1617 1617 changeset: -1:000000000000
1618 1618 user:
1619 1619 date: Thu Jan 01 00:00:00 1970 +0000
1620 1620
1621 1621 $ hg log -r null:null
1622 1622 changeset: -1:000000000000
1623 1623 user:
1624 1624 date: Thu Jan 01 00:00:00 1970 +0000
1625 1625
1626 1626 working-directory revision requires special treatment
1627 1627
1628 1628 $ hg log -r 'wdir()'
1629 1629 changeset: 0:65624cd9070a+
1630 parent: 0:65624cd9070a
1630 1631 user: test
1631 1632 date: [A-Za-z0-9:+ ]+ (re)
1632 1633
1633 1634 $ hg log -r 'wdir()' -q
1634 1635 0:65624cd9070a+
1635 1636
1636 1637 $ hg log -r 'wdir()' --debug
1637 1638 changeset: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08+
1638 1639 phase: draft
1639 1640 parent: 0:65624cd9070a035fa7191a54f2b8af39f16b0c08
1640 1641 parent: -1:0000000000000000000000000000000000000000
1641 1642 user: test
1642 1643 date: [A-Za-z0-9:+ ]+ (re)
1643 1644 extra: branch=default
1644 1645
1645 1646 $ hg log -r 'wdir()' -Tjson
1646 1647 [
1647 1648 {
1648 1649 "rev": null,
1649 1650 "node": null,
1650 1651 "branch": "default",
1651 1652 "phase": "draft",
1652 1653 "user": "test",
1653 1654 "date": [*, 0], (glob)
1654 1655 "desc": "",
1655 1656 "bookmarks": [],
1656 1657 "tags": [],
1657 1658 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"]
1658 1659 }
1659 1660 ]
1660 1661
1661 1662 $ hg log -r 'wdir()' -Tjson -q
1662 1663 [
1663 1664 {
1664 1665 "rev": null,
1665 1666 "node": null
1666 1667 }
1667 1668 ]
1668 1669
1669 1670 $ hg log -r 'wdir()' -Tjson --debug
1670 1671 [
1671 1672 {
1672 1673 "rev": null,
1673 1674 "node": null,
1674 1675 "branch": "default",
1675 1676 "phase": "draft",
1676 1677 "user": "test",
1677 1678 "date": [*, 0], (glob)
1678 1679 "desc": "",
1679 1680 "bookmarks": [],
1680 1681 "tags": [],
1681 1682 "parents": ["65624cd9070a035fa7191a54f2b8af39f16b0c08"],
1682 1683 "manifest": null,
1683 1684 "extra": {"branch": "default"},
1684 1685 "modified": [],
1685 1686 "added": [],
1686 1687 "removed": []
1687 1688 }
1688 1689 ]
1689 1690
1690 1691 Check that adding an arbitrary name shows up in log automatically
1691 1692
1692 1693 $ cat > ../names.py <<EOF
1693 1694 > """A small extension to test adding arbitrary names to a repo"""
1694 1695 > from mercurial.namespaces import namespace
1695 1696 >
1696 1697 > def reposetup(ui, repo):
1697 1698 > foo = {'foo': repo[0].node()}
1698 1699 > names = lambda r: foo.keys()
1699 1700 > namemap = lambda r, name: foo.get(name)
1700 1701 > nodemap = lambda r, node: [name for name, n in foo.iteritems()
1701 1702 > if n == node]
1702 1703 > ns = namespace("bars", templatename="bar", logname="barlog",
1703 1704 > colorname="barcolor", listnames=names, namemap=namemap,
1704 1705 > nodemap=nodemap)
1705 1706 >
1706 1707 > repo.names.addnamespace(ns)
1707 1708 > EOF
1708 1709
1709 1710 $ hg --config extensions.names=../names.py log -r 0
1710 1711 changeset: 0:65624cd9070a
1711 1712 tag: tip
1712 1713 barlog: foo
1713 1714 user: test
1714 1715 date: Thu Jan 01 00:00:00 1970 +0000
1715 1716 summary: a bunch of weird directories
1716 1717
1717 1718 $ hg --config extensions.names=../names.py \
1718 1719 > --config extensions.color= --config color.log.barcolor=red \
1719 1720 > --color=always log -r 0
1720 1721 \x1b[0;33mchangeset: 0:65624cd9070a\x1b[0m (esc)
1721 1722 tag: tip
1722 1723 \x1b[0;31mbarlog: foo\x1b[0m (esc)
1723 1724 user: test
1724 1725 date: Thu Jan 01 00:00:00 1970 +0000
1725 1726 summary: a bunch of weird directories
1726 1727
1727 1728 $ hg --config extensions.names=../names.py log -r 0 --template '{bars}\n'
1728 1729 foo
1729 1730
1730 1731 $ cd ..
1731 1732
1732 1733 hg log -f dir across branches
1733 1734
1734 1735 $ hg init acrossbranches
1735 1736 $ cd acrossbranches
1736 1737 $ mkdir d
1737 1738 $ echo a > d/a && hg ci -Aqm a
1738 1739 $ echo b > d/a && hg ci -Aqm b
1739 1740 $ hg up -q 0
1740 1741 $ echo b > d/a && hg ci -Aqm c
1741 1742 $ hg log -f d -T '{desc}' -G
1742 1743 @ c
1743 1744 |
1744 1745 o a
1745 1746
1746 1747 Ensure that largefiles doesn't interfere with following a normal file
1747 1748 $ hg --config extensions.largefiles= log -f d -T '{desc}' -G
1748 1749 @ c
1749 1750 |
1750 1751 o a
1751 1752
1752 1753 $ hg log -f d/a -T '{desc}' -G
1753 1754 @ c
1754 1755 |
1755 1756 o a
1756 1757
1757 1758 $ cd ..
1758 1759
1759 1760 hg log -f with linkrev pointing to another branch
1760 1761 -------------------------------------------------
1761 1762
1762 1763 create history with a filerev whose linkrev points to another branch
1763 1764
1764 1765 $ hg init branchedlinkrev
1765 1766 $ cd branchedlinkrev
1766 1767 $ echo 1 > a
1767 1768 $ hg commit -Am 'content1'
1768 1769 adding a
1769 1770 $ echo 2 > a
1770 1771 $ hg commit -m 'content2'
1771 1772 $ hg up --rev 'desc(content1)'
1772 1773 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1773 1774 $ echo unrelated > unrelated
1774 1775 $ hg commit -Am 'unrelated'
1775 1776 adding unrelated
1776 1777 created new head
1777 1778 $ hg graft -r 'desc(content2)'
1778 1779 grafting 1:2294ae80ad84 "content2"
1779 1780 $ echo 3 > a
1780 1781 $ hg commit -m 'content3'
1781 1782 $ hg log -G
1782 1783 @ changeset: 4:50b9b36e9c5d
1783 1784 | tag: tip
1784 1785 | user: test
1785 1786 | date: Thu Jan 01 00:00:00 1970 +0000
1786 1787 | summary: content3
1787 1788 |
1788 1789 o changeset: 3:15b2327059e5
1789 1790 | user: test
1790 1791 | date: Thu Jan 01 00:00:00 1970 +0000
1791 1792 | summary: content2
1792 1793 |
1793 1794 o changeset: 2:2029acd1168c
1794 1795 | parent: 0:ae0a3c9f9e95
1795 1796 | user: test
1796 1797 | date: Thu Jan 01 00:00:00 1970 +0000
1797 1798 | summary: unrelated
1798 1799 |
1799 1800 | o changeset: 1:2294ae80ad84
1800 1801 |/ user: test
1801 1802 | date: Thu Jan 01 00:00:00 1970 +0000
1802 1803 | summary: content2
1803 1804 |
1804 1805 o changeset: 0:ae0a3c9f9e95
1805 1806 user: test
1806 1807 date: Thu Jan 01 00:00:00 1970 +0000
1807 1808 summary: content1
1808 1809
1809 1810
1810 1811 log -f on the file should list the graft result.
1811 1812
1812 1813 $ hg log -Gf a
1813 1814 @ changeset: 4:50b9b36e9c5d
1814 1815 | tag: tip
1815 1816 | user: test
1816 1817 | date: Thu Jan 01 00:00:00 1970 +0000
1817 1818 | summary: content3
1818 1819 |
1819 1820 o changeset: 3:15b2327059e5
1820 1821 | user: test
1821 1822 | date: Thu Jan 01 00:00:00 1970 +0000
1822 1823 | summary: content2
1823 1824 |
1824 1825 o changeset: 0:ae0a3c9f9e95
1825 1826 user: test
1826 1827 date: Thu Jan 01 00:00:00 1970 +0000
1827 1828 summary: content1
1828 1829
1829 1830
1830 1831 plain log lists the original version
1831 1832 (XXX we should probably list both)
1832 1833
1833 1834 $ hg log -G a
1834 1835 @ changeset: 4:50b9b36e9c5d
1835 1836 | tag: tip
1836 1837 | user: test
1837 1838 | date: Thu Jan 01 00:00:00 1970 +0000
1838 1839 | summary: content3
1839 1840 |
1840 1841 | o changeset: 1:2294ae80ad84
1841 1842 |/ user: test
1842 1843 | date: Thu Jan 01 00:00:00 1970 +0000
1843 1844 | summary: content2
1844 1845 |
1845 1846 o changeset: 0:ae0a3c9f9e95
1846 1847 user: test
1847 1848 date: Thu Jan 01 00:00:00 1970 +0000
1848 1849 summary: content1
1849 1850
1850 1851
1851 1852 hg log -f from the grafted changeset
1852 1853 (The bootstrap should properly take the topology in account)
1853 1854
1854 1855 $ hg up 'desc(content3)^'
1855 1856 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1856 1857 $ hg log -Gf a
1857 1858 @ changeset: 3:15b2327059e5
1858 1859 | user: test
1859 1860 | date: Thu Jan 01 00:00:00 1970 +0000
1860 1861 | summary: content2
1861 1862 |
1862 1863 o changeset: 0:ae0a3c9f9e95
1863 1864 user: test
1864 1865 date: Thu Jan 01 00:00:00 1970 +0000
1865 1866 summary: content1
1866 1867
1867 1868
1868 1869 Test that we use the first non-hidden changeset in that case.
1869 1870
1870 1871 (hide the changeset)
1871 1872
1872 1873 $ hg log -T '{node}\n' -r 1
1873 1874 2294ae80ad8447bc78383182eeac50cb049df623
1874 1875 $ hg debugobsolete 2294ae80ad8447bc78383182eeac50cb049df623
1875 1876 $ hg log -G
1876 1877 o changeset: 4:50b9b36e9c5d
1877 1878 | tag: tip
1878 1879 | user: test
1879 1880 | date: Thu Jan 01 00:00:00 1970 +0000
1880 1881 | summary: content3
1881 1882 |
1882 1883 @ changeset: 3:15b2327059e5
1883 1884 | user: test
1884 1885 | date: Thu Jan 01 00:00:00 1970 +0000
1885 1886 | summary: content2
1886 1887 |
1887 1888 o changeset: 2:2029acd1168c
1888 1889 | parent: 0:ae0a3c9f9e95
1889 1890 | user: test
1890 1891 | date: Thu Jan 01 00:00:00 1970 +0000
1891 1892 | summary: unrelated
1892 1893 |
1893 1894 o changeset: 0:ae0a3c9f9e95
1894 1895 user: test
1895 1896 date: Thu Jan 01 00:00:00 1970 +0000
1896 1897 summary: content1
1897 1898
1898 1899
1899 1900 Check that log on the file does not drop the file revision.
1900 1901
1901 1902 $ hg log -G a
1902 1903 o changeset: 4:50b9b36e9c5d
1903 1904 | tag: tip
1904 1905 | user: test
1905 1906 | date: Thu Jan 01 00:00:00 1970 +0000
1906 1907 | summary: content3
1907 1908 |
1908 1909 @ changeset: 3:15b2327059e5
1909 1910 | user: test
1910 1911 | date: Thu Jan 01 00:00:00 1970 +0000
1911 1912 | summary: content2
1912 1913 |
1913 1914 o changeset: 0:ae0a3c9f9e95
1914 1915 user: test
1915 1916 date: Thu Jan 01 00:00:00 1970 +0000
1916 1917 summary: content1
1917 1918
1918 1919
1919 1920 Even when a head revision is linkrev-shadowed.
1920 1921
1921 1922 $ hg log -T '{node}\n' -r 4
1922 1923 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1923 1924 $ hg debugobsolete 50b9b36e9c5df2c6fc6dcefa8ad0da929e84aed2
1924 1925 $ hg log -G a
1925 1926 @ changeset: 3:15b2327059e5
1926 1927 | tag: tip
1927 1928 | user: test
1928 1929 | date: Thu Jan 01 00:00:00 1970 +0000
1929 1930 | summary: content2
1930 1931 |
1931 1932 o changeset: 0:ae0a3c9f9e95
1932 1933 user: test
1933 1934 date: Thu Jan 01 00:00:00 1970 +0000
1934 1935 summary: content1
1935 1936
1936 1937
1937 1938 $ cd ..
1938 1939
1939 1940 Even when the file revision is missing from some head:
1940 1941
1941 1942 $ hg init issue4490
1942 1943 $ cd issue4490
1943 1944 $ echo '[experimental]' >> .hg/hgrc
1944 1945 $ echo 'evolution=createmarkers' >> .hg/hgrc
1945 1946 $ echo a > a
1946 1947 $ hg ci -Am0
1947 1948 adding a
1948 1949 $ echo b > b
1949 1950 $ hg ci -Am1
1950 1951 adding b
1951 1952 $ echo B > b
1952 1953 $ hg ci --amend -m 1
1953 1954 $ hg up 0
1954 1955 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
1955 1956 $ echo c > c
1956 1957 $ hg ci -Am2
1957 1958 adding c
1958 1959 created new head
1959 1960 $ hg up 'head() and not .'
1960 1961 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
1961 1962 $ hg log -G
1962 1963 o changeset: 4:db815d6d32e6
1963 1964 | tag: tip
1964 1965 | parent: 0:f7b1eb17ad24
1965 1966 | user: test
1966 1967 | date: Thu Jan 01 00:00:00 1970 +0000
1967 1968 | summary: 2
1968 1969 |
1969 1970 | @ changeset: 3:9bc8ce7f9356
1970 1971 |/ parent: 0:f7b1eb17ad24
1971 1972 | user: test
1972 1973 | date: Thu Jan 01 00:00:00 1970 +0000
1973 1974 | summary: 1
1974 1975 |
1975 1976 o changeset: 0:f7b1eb17ad24
1976 1977 user: test
1977 1978 date: Thu Jan 01 00:00:00 1970 +0000
1978 1979 summary: 0
1979 1980
1980 1981 $ hg log -f -G b
1981 1982 @ changeset: 3:9bc8ce7f9356
1982 1983 | parent: 0:f7b1eb17ad24
1983 1984 | user: test
1984 1985 | date: Thu Jan 01 00:00:00 1970 +0000
1985 1986 | summary: 1
1986 1987 |
1987 1988 $ hg log -G b
1988 1989 @ changeset: 3:9bc8ce7f9356
1989 1990 | parent: 0:f7b1eb17ad24
1990 1991 | user: test
1991 1992 | date: Thu Jan 01 00:00:00 1970 +0000
1992 1993 | summary: 1
1993 1994 |
1994 1995 $ cd ..
1995 1996
1996 1997 Check proper report when the manifest changes but not the file issue4499
1997 1998 ------------------------------------------------------------------------
1998 1999
1999 2000 $ hg init issue4499
2000 2001 $ cd issue4499
2001 2002 $ for f in A B C D F E G H I J K L M N O P Q R S T U; do
2002 2003 > echo 1 > $f;
2003 2004 > hg add $f;
2004 2005 > done
2005 2006 $ hg commit -m 'A1B1C1'
2006 2007 $ echo 2 > A
2007 2008 $ echo 2 > B
2008 2009 $ echo 2 > C
2009 2010 $ hg commit -m 'A2B2C2'
2010 2011 $ hg up 0
2011 2012 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
2012 2013 $ echo 3 > A
2013 2014 $ echo 2 > B
2014 2015 $ echo 2 > C
2015 2016 $ hg commit -m 'A3B2C2'
2016 2017 created new head
2017 2018
2018 2019 $ hg log -G
2019 2020 @ changeset: 2:fe5fc3d0eb17
2020 2021 | tag: tip
2021 2022 | parent: 0:abf4f0e38563
2022 2023 | user: test
2023 2024 | date: Thu Jan 01 00:00:00 1970 +0000
2024 2025 | summary: A3B2C2
2025 2026 |
2026 2027 | o changeset: 1:07dcc6b312c0
2027 2028 |/ user: test
2028 2029 | date: Thu Jan 01 00:00:00 1970 +0000
2029 2030 | summary: A2B2C2
2030 2031 |
2031 2032 o changeset: 0:abf4f0e38563
2032 2033 user: test
2033 2034 date: Thu Jan 01 00:00:00 1970 +0000
2034 2035 summary: A1B1C1
2035 2036
2036 2037
2037 2038 Log -f on B should reports current changesets
2038 2039
2039 2040 $ hg log -fG B
2040 2041 @ changeset: 2:fe5fc3d0eb17
2041 2042 | tag: tip
2042 2043 | parent: 0:abf4f0e38563
2043 2044 | user: test
2044 2045 | date: Thu Jan 01 00:00:00 1970 +0000
2045 2046 | summary: A3B2C2
2046 2047 |
2047 2048 o changeset: 0:abf4f0e38563
2048 2049 user: test
2049 2050 date: Thu Jan 01 00:00:00 1970 +0000
2050 2051 summary: A1B1C1
2051 2052
2052 2053 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now