##// END OF EJS Templates
revert: use modified information from both statuses...
Pierre-Yves David -
r22190:55308ab8 default
parent child Browse files
Show More
@@ -1,2693 +1,2698 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
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 lock as lockmod
17 17
18 18 def parsealiases(cmd):
19 19 return cmd.lstrip("^").split("|")
20 20
21 21 def findpossible(cmd, table, strict=False):
22 22 """
23 23 Return cmd -> (aliases, command table entry)
24 24 for each matching command.
25 25 Return debug commands (or their aliases) only if no normal command matches.
26 26 """
27 27 choice = {}
28 28 debugchoice = {}
29 29
30 30 if cmd in table:
31 31 # short-circuit exact matches, "log" alias beats "^log|history"
32 32 keys = [cmd]
33 33 else:
34 34 keys = table.keys()
35 35
36 36 for e in keys:
37 37 aliases = parsealiases(e)
38 38 found = None
39 39 if cmd in aliases:
40 40 found = cmd
41 41 elif not strict:
42 42 for a in aliases:
43 43 if a.startswith(cmd):
44 44 found = a
45 45 break
46 46 if found is not None:
47 47 if aliases[0].startswith("debug") or found.startswith("debug"):
48 48 debugchoice[found] = (aliases, table[e])
49 49 else:
50 50 choice[found] = (aliases, table[e])
51 51
52 52 if not choice and debugchoice:
53 53 choice = debugchoice
54 54
55 55 return choice
56 56
57 57 def findcmd(cmd, table, strict=True):
58 58 """Return (aliases, command table entry) for command string."""
59 59 choice = findpossible(cmd, table, strict)
60 60
61 61 if cmd in choice:
62 62 return choice[cmd]
63 63
64 64 if len(choice) > 1:
65 65 clist = choice.keys()
66 66 clist.sort()
67 67 raise error.AmbiguousCommand(cmd, clist)
68 68
69 69 if choice:
70 70 return choice.values()[0]
71 71
72 72 raise error.UnknownCommand(cmd)
73 73
74 74 def findrepo(p):
75 75 while not os.path.isdir(os.path.join(p, ".hg")):
76 76 oldp, p = p, os.path.dirname(p)
77 77 if p == oldp:
78 78 return None
79 79
80 80 return p
81 81
82 82 def bailifchanged(repo):
83 83 if repo.dirstate.p2() != nullid:
84 84 raise util.Abort(_('outstanding uncommitted merge'))
85 85 modified, added, removed, deleted = repo.status()[:4]
86 86 if modified or added or removed or deleted:
87 87 raise util.Abort(_('uncommitted changes'))
88 88 ctx = repo[None]
89 89 for s in sorted(ctx.substate):
90 90 if ctx.sub(s).dirty():
91 91 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
92 92
93 93 def logmessage(ui, opts):
94 94 """ get the log message according to -m and -l option """
95 95 message = opts.get('message')
96 96 logfile = opts.get('logfile')
97 97
98 98 if message and logfile:
99 99 raise util.Abort(_('options --message and --logfile are mutually '
100 100 'exclusive'))
101 101 if not message and logfile:
102 102 try:
103 103 if logfile == '-':
104 104 message = ui.fin.read()
105 105 else:
106 106 message = '\n'.join(util.readfile(logfile).splitlines())
107 107 except IOError, inst:
108 108 raise util.Abort(_("can't read commit message '%s': %s") %
109 109 (logfile, inst.strerror))
110 110 return message
111 111
112 112 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
113 113 editform='', **opts):
114 114 """get appropriate commit message editor according to '--edit' option
115 115
116 116 'finishdesc' is a function to be called with edited commit message
117 117 (= 'description' of the new changeset) just after editing, but
118 118 before checking empty-ness. It should return actual text to be
119 119 stored into history. This allows to change description before
120 120 storing.
121 121
122 122 'extramsg' is a extra message to be shown in the editor instead of
123 123 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
124 124 is automatically added.
125 125
126 126 'editform' is a dot-separated list of names, to distinguish
127 127 the purpose of commit text editing.
128 128
129 129 'getcommiteditor' returns 'commitforceeditor' regardless of
130 130 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
131 131 they are specific for usage in MQ.
132 132 """
133 133 if edit or finishdesc or extramsg:
134 134 return lambda r, c, s: commitforceeditor(r, c, s,
135 135 finishdesc=finishdesc,
136 136 extramsg=extramsg,
137 137 editform=editform)
138 138 elif editform:
139 139 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
140 140 else:
141 141 return commiteditor
142 142
143 143 def loglimit(opts):
144 144 """get the log limit according to option -l/--limit"""
145 145 limit = opts.get('limit')
146 146 if limit:
147 147 try:
148 148 limit = int(limit)
149 149 except ValueError:
150 150 raise util.Abort(_('limit must be a positive integer'))
151 151 if limit <= 0:
152 152 raise util.Abort(_('limit must be positive'))
153 153 else:
154 154 limit = None
155 155 return limit
156 156
157 157 def makefilename(repo, pat, node, desc=None,
158 158 total=None, seqno=None, revwidth=None, pathname=None):
159 159 node_expander = {
160 160 'H': lambda: hex(node),
161 161 'R': lambda: str(repo.changelog.rev(node)),
162 162 'h': lambda: short(node),
163 163 'm': lambda: re.sub('[^\w]', '_', str(desc))
164 164 }
165 165 expander = {
166 166 '%': lambda: '%',
167 167 'b': lambda: os.path.basename(repo.root),
168 168 }
169 169
170 170 try:
171 171 if node:
172 172 expander.update(node_expander)
173 173 if node:
174 174 expander['r'] = (lambda:
175 175 str(repo.changelog.rev(node)).zfill(revwidth or 0))
176 176 if total is not None:
177 177 expander['N'] = lambda: str(total)
178 178 if seqno is not None:
179 179 expander['n'] = lambda: str(seqno)
180 180 if total is not None and seqno is not None:
181 181 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
182 182 if pathname is not None:
183 183 expander['s'] = lambda: os.path.basename(pathname)
184 184 expander['d'] = lambda: os.path.dirname(pathname) or '.'
185 185 expander['p'] = lambda: pathname
186 186
187 187 newname = []
188 188 patlen = len(pat)
189 189 i = 0
190 190 while i < patlen:
191 191 c = pat[i]
192 192 if c == '%':
193 193 i += 1
194 194 c = pat[i]
195 195 c = expander[c]()
196 196 newname.append(c)
197 197 i += 1
198 198 return ''.join(newname)
199 199 except KeyError, inst:
200 200 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
201 201 inst.args[0])
202 202
203 203 def makefileobj(repo, pat, node=None, desc=None, total=None,
204 204 seqno=None, revwidth=None, mode='wb', modemap=None,
205 205 pathname=None):
206 206
207 207 writable = mode not in ('r', 'rb')
208 208
209 209 if not pat or pat == '-':
210 210 fp = writable and repo.ui.fout or repo.ui.fin
211 211 if util.safehasattr(fp, 'fileno'):
212 212 return os.fdopen(os.dup(fp.fileno()), mode)
213 213 else:
214 214 # if this fp can't be duped properly, return
215 215 # a dummy object that can be closed
216 216 class wrappedfileobj(object):
217 217 noop = lambda x: None
218 218 def __init__(self, f):
219 219 self.f = f
220 220 def __getattr__(self, attr):
221 221 if attr == 'close':
222 222 return self.noop
223 223 else:
224 224 return getattr(self.f, attr)
225 225
226 226 return wrappedfileobj(fp)
227 227 if util.safehasattr(pat, 'write') and writable:
228 228 return pat
229 229 if util.safehasattr(pat, 'read') and 'r' in mode:
230 230 return pat
231 231 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
232 232 if modemap is not None:
233 233 mode = modemap.get(fn, mode)
234 234 if mode == 'wb':
235 235 modemap[fn] = 'ab'
236 236 return open(fn, mode)
237 237
238 238 def openrevlog(repo, cmd, file_, opts):
239 239 """opens the changelog, manifest, a filelog or a given revlog"""
240 240 cl = opts['changelog']
241 241 mf = opts['manifest']
242 242 msg = None
243 243 if cl and mf:
244 244 msg = _('cannot specify --changelog and --manifest at the same time')
245 245 elif cl or mf:
246 246 if file_:
247 247 msg = _('cannot specify filename with --changelog or --manifest')
248 248 elif not repo:
249 249 msg = _('cannot specify --changelog or --manifest '
250 250 'without a repository')
251 251 if msg:
252 252 raise util.Abort(msg)
253 253
254 254 r = None
255 255 if repo:
256 256 if cl:
257 257 r = repo.unfiltered().changelog
258 258 elif mf:
259 259 r = repo.manifest
260 260 elif file_:
261 261 filelog = repo.file(file_)
262 262 if len(filelog):
263 263 r = filelog
264 264 if not r:
265 265 if not file_:
266 266 raise error.CommandError(cmd, _('invalid arguments'))
267 267 if not os.path.isfile(file_):
268 268 raise util.Abort(_("revlog '%s' not found") % file_)
269 269 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
270 270 file_[:-2] + ".i")
271 271 return r
272 272
273 273 def copy(ui, repo, pats, opts, rename=False):
274 274 # called with the repo lock held
275 275 #
276 276 # hgsep => pathname that uses "/" to separate directories
277 277 # ossep => pathname that uses os.sep to separate directories
278 278 cwd = repo.getcwd()
279 279 targets = {}
280 280 after = opts.get("after")
281 281 dryrun = opts.get("dry_run")
282 282 wctx = repo[None]
283 283
284 284 def walkpat(pat):
285 285 srcs = []
286 286 badstates = after and '?' or '?r'
287 287 m = scmutil.match(repo[None], [pat], opts, globbed=True)
288 288 for abs in repo.walk(m):
289 289 state = repo.dirstate[abs]
290 290 rel = m.rel(abs)
291 291 exact = m.exact(abs)
292 292 if state in badstates:
293 293 if exact and state == '?':
294 294 ui.warn(_('%s: not copying - file is not managed\n') % rel)
295 295 if exact and state == 'r':
296 296 ui.warn(_('%s: not copying - file has been marked for'
297 297 ' remove\n') % rel)
298 298 continue
299 299 # abs: hgsep
300 300 # rel: ossep
301 301 srcs.append((abs, rel, exact))
302 302 return srcs
303 303
304 304 # abssrc: hgsep
305 305 # relsrc: ossep
306 306 # otarget: ossep
307 307 def copyfile(abssrc, relsrc, otarget, exact):
308 308 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
309 309 if '/' in abstarget:
310 310 # We cannot normalize abstarget itself, this would prevent
311 311 # case only renames, like a => A.
312 312 abspath, absname = abstarget.rsplit('/', 1)
313 313 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
314 314 reltarget = repo.pathto(abstarget, cwd)
315 315 target = repo.wjoin(abstarget)
316 316 src = repo.wjoin(abssrc)
317 317 state = repo.dirstate[abstarget]
318 318
319 319 scmutil.checkportable(ui, abstarget)
320 320
321 321 # check for collisions
322 322 prevsrc = targets.get(abstarget)
323 323 if prevsrc is not None:
324 324 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
325 325 (reltarget, repo.pathto(abssrc, cwd),
326 326 repo.pathto(prevsrc, cwd)))
327 327 return
328 328
329 329 # check for overwrites
330 330 exists = os.path.lexists(target)
331 331 samefile = False
332 332 if exists and abssrc != abstarget:
333 333 if (repo.dirstate.normalize(abssrc) ==
334 334 repo.dirstate.normalize(abstarget)):
335 335 if not rename:
336 336 ui.warn(_("%s: can't copy - same file\n") % reltarget)
337 337 return
338 338 exists = False
339 339 samefile = True
340 340
341 341 if not after and exists or after and state in 'mn':
342 342 if not opts['force']:
343 343 ui.warn(_('%s: not overwriting - file exists\n') %
344 344 reltarget)
345 345 return
346 346
347 347 if after:
348 348 if not exists:
349 349 if rename:
350 350 ui.warn(_('%s: not recording move - %s does not exist\n') %
351 351 (relsrc, reltarget))
352 352 else:
353 353 ui.warn(_('%s: not recording copy - %s does not exist\n') %
354 354 (relsrc, reltarget))
355 355 return
356 356 elif not dryrun:
357 357 try:
358 358 if exists:
359 359 os.unlink(target)
360 360 targetdir = os.path.dirname(target) or '.'
361 361 if not os.path.isdir(targetdir):
362 362 os.makedirs(targetdir)
363 363 if samefile:
364 364 tmp = target + "~hgrename"
365 365 os.rename(src, tmp)
366 366 os.rename(tmp, target)
367 367 else:
368 368 util.copyfile(src, target)
369 369 srcexists = True
370 370 except IOError, inst:
371 371 if inst.errno == errno.ENOENT:
372 372 ui.warn(_('%s: deleted in working copy\n') % relsrc)
373 373 srcexists = False
374 374 else:
375 375 ui.warn(_('%s: cannot copy - %s\n') %
376 376 (relsrc, inst.strerror))
377 377 return True # report a failure
378 378
379 379 if ui.verbose or not exact:
380 380 if rename:
381 381 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
382 382 else:
383 383 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
384 384
385 385 targets[abstarget] = abssrc
386 386
387 387 # fix up dirstate
388 388 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
389 389 dryrun=dryrun, cwd=cwd)
390 390 if rename and not dryrun:
391 391 if not after and srcexists and not samefile:
392 392 util.unlinkpath(repo.wjoin(abssrc))
393 393 wctx.forget([abssrc])
394 394
395 395 # pat: ossep
396 396 # dest ossep
397 397 # srcs: list of (hgsep, hgsep, ossep, bool)
398 398 # return: function that takes hgsep and returns ossep
399 399 def targetpathfn(pat, dest, srcs):
400 400 if os.path.isdir(pat):
401 401 abspfx = pathutil.canonpath(repo.root, cwd, pat)
402 402 abspfx = util.localpath(abspfx)
403 403 if destdirexists:
404 404 striplen = len(os.path.split(abspfx)[0])
405 405 else:
406 406 striplen = len(abspfx)
407 407 if striplen:
408 408 striplen += len(os.sep)
409 409 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
410 410 elif destdirexists:
411 411 res = lambda p: os.path.join(dest,
412 412 os.path.basename(util.localpath(p)))
413 413 else:
414 414 res = lambda p: dest
415 415 return res
416 416
417 417 # pat: ossep
418 418 # dest ossep
419 419 # srcs: list of (hgsep, hgsep, ossep, bool)
420 420 # return: function that takes hgsep and returns ossep
421 421 def targetpathafterfn(pat, dest, srcs):
422 422 if matchmod.patkind(pat):
423 423 # a mercurial pattern
424 424 res = lambda p: os.path.join(dest,
425 425 os.path.basename(util.localpath(p)))
426 426 else:
427 427 abspfx = pathutil.canonpath(repo.root, cwd, pat)
428 428 if len(abspfx) < len(srcs[0][0]):
429 429 # A directory. Either the target path contains the last
430 430 # component of the source path or it does not.
431 431 def evalpath(striplen):
432 432 score = 0
433 433 for s in srcs:
434 434 t = os.path.join(dest, util.localpath(s[0])[striplen:])
435 435 if os.path.lexists(t):
436 436 score += 1
437 437 return score
438 438
439 439 abspfx = util.localpath(abspfx)
440 440 striplen = len(abspfx)
441 441 if striplen:
442 442 striplen += len(os.sep)
443 443 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
444 444 score = evalpath(striplen)
445 445 striplen1 = len(os.path.split(abspfx)[0])
446 446 if striplen1:
447 447 striplen1 += len(os.sep)
448 448 if evalpath(striplen1) > score:
449 449 striplen = striplen1
450 450 res = lambda p: os.path.join(dest,
451 451 util.localpath(p)[striplen:])
452 452 else:
453 453 # a file
454 454 if destdirexists:
455 455 res = lambda p: os.path.join(dest,
456 456 os.path.basename(util.localpath(p)))
457 457 else:
458 458 res = lambda p: dest
459 459 return res
460 460
461 461
462 462 pats = scmutil.expandpats(pats)
463 463 if not pats:
464 464 raise util.Abort(_('no source or destination specified'))
465 465 if len(pats) == 1:
466 466 raise util.Abort(_('no destination specified'))
467 467 dest = pats.pop()
468 468 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
469 469 if not destdirexists:
470 470 if len(pats) > 1 or matchmod.patkind(pats[0]):
471 471 raise util.Abort(_('with multiple sources, destination must be an '
472 472 'existing directory'))
473 473 if util.endswithsep(dest):
474 474 raise util.Abort(_('destination %s is not a directory') % dest)
475 475
476 476 tfn = targetpathfn
477 477 if after:
478 478 tfn = targetpathafterfn
479 479 copylist = []
480 480 for pat in pats:
481 481 srcs = walkpat(pat)
482 482 if not srcs:
483 483 continue
484 484 copylist.append((tfn(pat, dest, srcs), srcs))
485 485 if not copylist:
486 486 raise util.Abort(_('no files to copy'))
487 487
488 488 errors = 0
489 489 for targetpath, srcs in copylist:
490 490 for abssrc, relsrc, exact in srcs:
491 491 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
492 492 errors += 1
493 493
494 494 if errors:
495 495 ui.warn(_('(consider using --after)\n'))
496 496
497 497 return errors != 0
498 498
499 499 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
500 500 runargs=None, appendpid=False):
501 501 '''Run a command as a service.'''
502 502
503 503 def writepid(pid):
504 504 if opts['pid_file']:
505 505 mode = appendpid and 'a' or 'w'
506 506 fp = open(opts['pid_file'], mode)
507 507 fp.write(str(pid) + '\n')
508 508 fp.close()
509 509
510 510 if opts['daemon'] and not opts['daemon_pipefds']:
511 511 # Signal child process startup with file removal
512 512 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
513 513 os.close(lockfd)
514 514 try:
515 515 if not runargs:
516 516 runargs = util.hgcmd() + sys.argv[1:]
517 517 runargs.append('--daemon-pipefds=%s' % lockpath)
518 518 # Don't pass --cwd to the child process, because we've already
519 519 # changed directory.
520 520 for i in xrange(1, len(runargs)):
521 521 if runargs[i].startswith('--cwd='):
522 522 del runargs[i]
523 523 break
524 524 elif runargs[i].startswith('--cwd'):
525 525 del runargs[i:i + 2]
526 526 break
527 527 def condfn():
528 528 return not os.path.exists(lockpath)
529 529 pid = util.rundetached(runargs, condfn)
530 530 if pid < 0:
531 531 raise util.Abort(_('child process failed to start'))
532 532 writepid(pid)
533 533 finally:
534 534 try:
535 535 os.unlink(lockpath)
536 536 except OSError, e:
537 537 if e.errno != errno.ENOENT:
538 538 raise
539 539 if parentfn:
540 540 return parentfn(pid)
541 541 else:
542 542 return
543 543
544 544 if initfn:
545 545 initfn()
546 546
547 547 if not opts['daemon']:
548 548 writepid(os.getpid())
549 549
550 550 if opts['daemon_pipefds']:
551 551 lockpath = opts['daemon_pipefds']
552 552 try:
553 553 os.setsid()
554 554 except AttributeError:
555 555 pass
556 556 os.unlink(lockpath)
557 557 util.hidewindow()
558 558 sys.stdout.flush()
559 559 sys.stderr.flush()
560 560
561 561 nullfd = os.open(os.devnull, os.O_RDWR)
562 562 logfilefd = nullfd
563 563 if logfile:
564 564 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
565 565 os.dup2(nullfd, 0)
566 566 os.dup2(logfilefd, 1)
567 567 os.dup2(logfilefd, 2)
568 568 if nullfd not in (0, 1, 2):
569 569 os.close(nullfd)
570 570 if logfile and logfilefd not in (0, 1, 2):
571 571 os.close(logfilefd)
572 572
573 573 if runfn:
574 574 return runfn()
575 575
576 576 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
577 577 """Utility function used by commands.import to import a single patch
578 578
579 579 This function is explicitly defined here to help the evolve extension to
580 580 wrap this part of the import logic.
581 581
582 582 The API is currently a bit ugly because it a simple code translation from
583 583 the import command. Feel free to make it better.
584 584
585 585 :hunk: a patch (as a binary string)
586 586 :parents: nodes that will be parent of the created commit
587 587 :opts: the full dict of option passed to the import command
588 588 :msgs: list to save commit message to.
589 589 (used in case we need to save it when failing)
590 590 :updatefunc: a function that update a repo to a given node
591 591 updatefunc(<repo>, <node>)
592 592 """
593 593 tmpname, message, user, date, branch, nodeid, p1, p2 = \
594 594 patch.extract(ui, hunk)
595 595
596 596 editor = getcommiteditor(editform='import.normal', **opts)
597 597 update = not opts.get('bypass')
598 598 strip = opts["strip"]
599 599 sim = float(opts.get('similarity') or 0)
600 600 if not tmpname:
601 601 return (None, None, False)
602 602 msg = _('applied to working directory')
603 603
604 604 rejects = False
605 605
606 606 try:
607 607 cmdline_message = logmessage(ui, opts)
608 608 if cmdline_message:
609 609 # pickup the cmdline msg
610 610 message = cmdline_message
611 611 elif message:
612 612 # pickup the patch msg
613 613 message = message.strip()
614 614 else:
615 615 # launch the editor
616 616 message = None
617 617 ui.debug('message:\n%s\n' % message)
618 618
619 619 if len(parents) == 1:
620 620 parents.append(repo[nullid])
621 621 if opts.get('exact'):
622 622 if not nodeid or not p1:
623 623 raise util.Abort(_('not a Mercurial patch'))
624 624 p1 = repo[p1]
625 625 p2 = repo[p2 or nullid]
626 626 elif p2:
627 627 try:
628 628 p1 = repo[p1]
629 629 p2 = repo[p2]
630 630 # Without any options, consider p2 only if the
631 631 # patch is being applied on top of the recorded
632 632 # first parent.
633 633 if p1 != parents[0]:
634 634 p1 = parents[0]
635 635 p2 = repo[nullid]
636 636 except error.RepoError:
637 637 p1, p2 = parents
638 638 else:
639 639 p1, p2 = parents
640 640
641 641 n = None
642 642 if update:
643 643 if p1 != parents[0]:
644 644 updatefunc(repo, p1.node())
645 645 if p2 != parents[1]:
646 646 repo.setparents(p1.node(), p2.node())
647 647
648 648 if opts.get('exact') or opts.get('import_branch'):
649 649 repo.dirstate.setbranch(branch or 'default')
650 650
651 651 partial = opts.get('partial', False)
652 652 files = set()
653 653 try:
654 654 patch.patch(ui, repo, tmpname, strip=strip, files=files,
655 655 eolmode=None, similarity=sim / 100.0)
656 656 except patch.PatchError, e:
657 657 if not partial:
658 658 raise util.Abort(str(e))
659 659 if partial:
660 660 rejects = True
661 661
662 662 files = list(files)
663 663 if opts.get('no_commit'):
664 664 if message:
665 665 msgs.append(message)
666 666 else:
667 667 if opts.get('exact') or p2:
668 668 # If you got here, you either use --force and know what
669 669 # you are doing or used --exact or a merge patch while
670 670 # being updated to its first parent.
671 671 m = None
672 672 else:
673 673 m = scmutil.matchfiles(repo, files or [])
674 674 n = repo.commit(message, opts.get('user') or user,
675 675 opts.get('date') or date, match=m,
676 676 editor=editor, force=partial)
677 677 else:
678 678 if opts.get('exact') or opts.get('import_branch'):
679 679 branch = branch or 'default'
680 680 else:
681 681 branch = p1.branch()
682 682 store = patch.filestore()
683 683 try:
684 684 files = set()
685 685 try:
686 686 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
687 687 files, eolmode=None)
688 688 except patch.PatchError, e:
689 689 raise util.Abort(str(e))
690 690 editor = getcommiteditor(editform='import.bypass')
691 691 memctx = context.makememctx(repo, (p1.node(), p2.node()),
692 692 message,
693 693 opts.get('user') or user,
694 694 opts.get('date') or date,
695 695 branch, files, store,
696 696 editor=editor)
697 697 n = memctx.commit()
698 698 finally:
699 699 store.close()
700 700 if opts.get('exact') and hex(n) != nodeid:
701 701 raise util.Abort(_('patch is damaged or loses information'))
702 702 if n:
703 703 # i18n: refers to a short changeset id
704 704 msg = _('created %s') % short(n)
705 705 return (msg, n, rejects)
706 706 finally:
707 707 os.unlink(tmpname)
708 708
709 709 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
710 710 opts=None):
711 711 '''export changesets as hg patches.'''
712 712
713 713 total = len(revs)
714 714 revwidth = max([len(str(rev)) for rev in revs])
715 715 filemode = {}
716 716
717 717 def single(rev, seqno, fp):
718 718 ctx = repo[rev]
719 719 node = ctx.node()
720 720 parents = [p.node() for p in ctx.parents() if p]
721 721 branch = ctx.branch()
722 722 if switch_parent:
723 723 parents.reverse()
724 724 prev = (parents and parents[0]) or nullid
725 725
726 726 shouldclose = False
727 727 if not fp and len(template) > 0:
728 728 desc_lines = ctx.description().rstrip().split('\n')
729 729 desc = desc_lines[0] #Commit always has a first line.
730 730 fp = makefileobj(repo, template, node, desc=desc, total=total,
731 731 seqno=seqno, revwidth=revwidth, mode='wb',
732 732 modemap=filemode)
733 733 if fp != template:
734 734 shouldclose = True
735 735 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
736 736 repo.ui.note("%s\n" % fp.name)
737 737
738 738 if not fp:
739 739 write = repo.ui.write
740 740 else:
741 741 def write(s, **kw):
742 742 fp.write(s)
743 743
744 744
745 745 write("# HG changeset patch\n")
746 746 write("# User %s\n" % ctx.user())
747 747 write("# Date %d %d\n" % ctx.date())
748 748 write("# %s\n" % util.datestr(ctx.date()))
749 749 if branch and branch != 'default':
750 750 write("# Branch %s\n" % branch)
751 751 write("# Node ID %s\n" % hex(node))
752 752 write("# Parent %s\n" % hex(prev))
753 753 if len(parents) > 1:
754 754 write("# Parent %s\n" % hex(parents[1]))
755 755 write(ctx.description().rstrip())
756 756 write("\n\n")
757 757
758 758 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
759 759 write(chunk, label=label)
760 760
761 761 if shouldclose:
762 762 fp.close()
763 763
764 764 for seqno, rev in enumerate(revs):
765 765 single(rev, seqno + 1, fp)
766 766
767 767 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
768 768 changes=None, stat=False, fp=None, prefix='',
769 769 listsubrepos=False):
770 770 '''show diff or diffstat.'''
771 771 if fp is None:
772 772 write = ui.write
773 773 else:
774 774 def write(s, **kw):
775 775 fp.write(s)
776 776
777 777 if stat:
778 778 diffopts = diffopts.copy(context=0)
779 779 width = 80
780 780 if not ui.plain():
781 781 width = ui.termwidth()
782 782 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
783 783 prefix=prefix)
784 784 for chunk, label in patch.diffstatui(util.iterlines(chunks),
785 785 width=width,
786 786 git=diffopts.git):
787 787 write(chunk, label=label)
788 788 else:
789 789 for chunk, label in patch.diffui(repo, node1, node2, match,
790 790 changes, diffopts, prefix=prefix):
791 791 write(chunk, label=label)
792 792
793 793 if listsubrepos:
794 794 ctx1 = repo[node1]
795 795 ctx2 = repo[node2]
796 796 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
797 797 tempnode2 = node2
798 798 try:
799 799 if node2 is not None:
800 800 tempnode2 = ctx2.substate[subpath][1]
801 801 except KeyError:
802 802 # A subrepo that existed in node1 was deleted between node1 and
803 803 # node2 (inclusive). Thus, ctx2's substate won't contain that
804 804 # subpath. The best we can do is to ignore it.
805 805 tempnode2 = None
806 806 submatch = matchmod.narrowmatcher(subpath, match)
807 807 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
808 808 stat=stat, fp=fp, prefix=prefix)
809 809
810 810 class changeset_printer(object):
811 811 '''show changeset information when templating not requested.'''
812 812
813 813 def __init__(self, ui, repo, patch, diffopts, buffered):
814 814 self.ui = ui
815 815 self.repo = repo
816 816 self.buffered = buffered
817 817 self.patch = patch
818 818 self.diffopts = diffopts
819 819 self.header = {}
820 820 self.hunk = {}
821 821 self.lastheader = None
822 822 self.footer = None
823 823
824 824 def flush(self, rev):
825 825 if rev in self.header:
826 826 h = self.header[rev]
827 827 if h != self.lastheader:
828 828 self.lastheader = h
829 829 self.ui.write(h)
830 830 del self.header[rev]
831 831 if rev in self.hunk:
832 832 self.ui.write(self.hunk[rev])
833 833 del self.hunk[rev]
834 834 return 1
835 835 return 0
836 836
837 837 def close(self):
838 838 if self.footer:
839 839 self.ui.write(self.footer)
840 840
841 841 def show(self, ctx, copies=None, matchfn=None, **props):
842 842 if self.buffered:
843 843 self.ui.pushbuffer()
844 844 self._show(ctx, copies, matchfn, props)
845 845 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
846 846 else:
847 847 self._show(ctx, copies, matchfn, props)
848 848
849 849 def _show(self, ctx, copies, matchfn, props):
850 850 '''show a single changeset or file revision'''
851 851 changenode = ctx.node()
852 852 rev = ctx.rev()
853 853
854 854 if self.ui.quiet:
855 855 self.ui.write("%d:%s\n" % (rev, short(changenode)),
856 856 label='log.node')
857 857 return
858 858
859 859 log = self.repo.changelog
860 860 date = util.datestr(ctx.date())
861 861
862 862 hexfunc = self.ui.debugflag and hex or short
863 863
864 864 parents = [(p, hexfunc(log.node(p)))
865 865 for p in self._meaningful_parentrevs(log, rev)]
866 866
867 867 # i18n: column positioning for "hg log"
868 868 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
869 869 label='log.changeset changeset.%s' % ctx.phasestr())
870 870
871 871 branch = ctx.branch()
872 872 # don't show the default branch name
873 873 if branch != 'default':
874 874 # i18n: column positioning for "hg log"
875 875 self.ui.write(_("branch: %s\n") % branch,
876 876 label='log.branch')
877 877 for bookmark in self.repo.nodebookmarks(changenode):
878 878 # i18n: column positioning for "hg log"
879 879 self.ui.write(_("bookmark: %s\n") % bookmark,
880 880 label='log.bookmark')
881 881 for tag in self.repo.nodetags(changenode):
882 882 # i18n: column positioning for "hg log"
883 883 self.ui.write(_("tag: %s\n") % tag,
884 884 label='log.tag')
885 885 if self.ui.debugflag and ctx.phase():
886 886 # i18n: column positioning for "hg log"
887 887 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
888 888 label='log.phase')
889 889 for parent in parents:
890 890 # i18n: column positioning for "hg log"
891 891 self.ui.write(_("parent: %d:%s\n") % parent,
892 892 label='log.parent changeset.%s' % ctx.phasestr())
893 893
894 894 if self.ui.debugflag:
895 895 mnode = ctx.manifestnode()
896 896 # i18n: column positioning for "hg log"
897 897 self.ui.write(_("manifest: %d:%s\n") %
898 898 (self.repo.manifest.rev(mnode), hex(mnode)),
899 899 label='ui.debug log.manifest')
900 900 # i18n: column positioning for "hg log"
901 901 self.ui.write(_("user: %s\n") % ctx.user(),
902 902 label='log.user')
903 903 # i18n: column positioning for "hg log"
904 904 self.ui.write(_("date: %s\n") % date,
905 905 label='log.date')
906 906
907 907 if self.ui.debugflag:
908 908 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
909 909 for key, value in zip([# i18n: column positioning for "hg log"
910 910 _("files:"),
911 911 # i18n: column positioning for "hg log"
912 912 _("files+:"),
913 913 # i18n: column positioning for "hg log"
914 914 _("files-:")], files):
915 915 if value:
916 916 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
917 917 label='ui.debug log.files')
918 918 elif ctx.files() and self.ui.verbose:
919 919 # i18n: column positioning for "hg log"
920 920 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
921 921 label='ui.note log.files')
922 922 if copies and self.ui.verbose:
923 923 copies = ['%s (%s)' % c for c in copies]
924 924 # i18n: column positioning for "hg log"
925 925 self.ui.write(_("copies: %s\n") % ' '.join(copies),
926 926 label='ui.note log.copies')
927 927
928 928 extra = ctx.extra()
929 929 if extra and self.ui.debugflag:
930 930 for key, value in sorted(extra.items()):
931 931 # i18n: column positioning for "hg log"
932 932 self.ui.write(_("extra: %s=%s\n")
933 933 % (key, value.encode('string_escape')),
934 934 label='ui.debug log.extra')
935 935
936 936 description = ctx.description().strip()
937 937 if description:
938 938 if self.ui.verbose:
939 939 self.ui.write(_("description:\n"),
940 940 label='ui.note log.description')
941 941 self.ui.write(description,
942 942 label='ui.note log.description')
943 943 self.ui.write("\n\n")
944 944 else:
945 945 # i18n: column positioning for "hg log"
946 946 self.ui.write(_("summary: %s\n") %
947 947 description.splitlines()[0],
948 948 label='log.summary')
949 949 self.ui.write("\n")
950 950
951 951 self.showpatch(changenode, matchfn)
952 952
953 953 def showpatch(self, node, matchfn):
954 954 if not matchfn:
955 955 matchfn = self.patch
956 956 if matchfn:
957 957 stat = self.diffopts.get('stat')
958 958 diff = self.diffopts.get('patch')
959 959 diffopts = patch.diffopts(self.ui, self.diffopts)
960 960 prev = self.repo.changelog.parents(node)[0]
961 961 if stat:
962 962 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
963 963 match=matchfn, stat=True)
964 964 if diff:
965 965 if stat:
966 966 self.ui.write("\n")
967 967 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
968 968 match=matchfn, stat=False)
969 969 self.ui.write("\n")
970 970
971 971 def _meaningful_parentrevs(self, log, rev):
972 972 """Return list of meaningful (or all if debug) parentrevs for rev.
973 973
974 974 For merges (two non-nullrev revisions) both parents are meaningful.
975 975 Otherwise the first parent revision is considered meaningful if it
976 976 is not the preceding revision.
977 977 """
978 978 parents = log.parentrevs(rev)
979 979 if not self.ui.debugflag and parents[1] == nullrev:
980 980 if parents[0] >= rev - 1:
981 981 parents = []
982 982 else:
983 983 parents = [parents[0]]
984 984 return parents
985 985
986 986
987 987 class changeset_templater(changeset_printer):
988 988 '''format changeset information.'''
989 989
990 990 def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
991 991 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
992 992 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
993 993 defaulttempl = {
994 994 'parent': '{rev}:{node|formatnode} ',
995 995 'manifest': '{rev}:{node|formatnode}',
996 996 'file_copy': '{name} ({source})',
997 997 'extra': '{key}={value|stringescape}'
998 998 }
999 999 # filecopy is preserved for compatibility reasons
1000 1000 defaulttempl['filecopy'] = defaulttempl['file_copy']
1001 1001 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1002 1002 cache=defaulttempl)
1003 1003 if tmpl:
1004 1004 self.t.cache['changeset'] = tmpl
1005 1005
1006 1006 self.cache = {}
1007 1007
1008 1008 def _meaningful_parentrevs(self, ctx):
1009 1009 """Return list of meaningful (or all if debug) parentrevs for rev.
1010 1010 """
1011 1011 parents = ctx.parents()
1012 1012 if len(parents) > 1:
1013 1013 return parents
1014 1014 if self.ui.debugflag:
1015 1015 return [parents[0], self.repo['null']]
1016 1016 if parents[0].rev() >= ctx.rev() - 1:
1017 1017 return []
1018 1018 return parents
1019 1019
1020 1020 def _show(self, ctx, copies, matchfn, props):
1021 1021 '''show a single changeset or file revision'''
1022 1022
1023 1023 showlist = templatekw.showlist
1024 1024
1025 1025 # showparents() behaviour depends on ui trace level which
1026 1026 # causes unexpected behaviours at templating level and makes
1027 1027 # it harder to extract it in a standalone function. Its
1028 1028 # behaviour cannot be changed so leave it here for now.
1029 1029 def showparents(**args):
1030 1030 ctx = args['ctx']
1031 1031 parents = [[('rev', p.rev()), ('node', p.hex())]
1032 1032 for p in self._meaningful_parentrevs(ctx)]
1033 1033 return showlist('parent', parents, **args)
1034 1034
1035 1035 props = props.copy()
1036 1036 props.update(templatekw.keywords)
1037 1037 props['parents'] = showparents
1038 1038 props['templ'] = self.t
1039 1039 props['ctx'] = ctx
1040 1040 props['repo'] = self.repo
1041 1041 props['revcache'] = {'copies': copies}
1042 1042 props['cache'] = self.cache
1043 1043
1044 1044 # find correct templates for current mode
1045 1045
1046 1046 tmplmodes = [
1047 1047 (True, None),
1048 1048 (self.ui.verbose, 'verbose'),
1049 1049 (self.ui.quiet, 'quiet'),
1050 1050 (self.ui.debugflag, 'debug'),
1051 1051 ]
1052 1052
1053 1053 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1054 1054 for mode, postfix in tmplmodes:
1055 1055 for type in types:
1056 1056 cur = postfix and ('%s_%s' % (type, postfix)) or type
1057 1057 if mode and cur in self.t:
1058 1058 types[type] = cur
1059 1059
1060 1060 try:
1061 1061
1062 1062 # write header
1063 1063 if types['header']:
1064 1064 h = templater.stringify(self.t(types['header'], **props))
1065 1065 if self.buffered:
1066 1066 self.header[ctx.rev()] = h
1067 1067 else:
1068 1068 if self.lastheader != h:
1069 1069 self.lastheader = h
1070 1070 self.ui.write(h)
1071 1071
1072 1072 # write changeset metadata, then patch if requested
1073 1073 key = types['changeset']
1074 1074 self.ui.write(templater.stringify(self.t(key, **props)))
1075 1075 self.showpatch(ctx.node(), matchfn)
1076 1076
1077 1077 if types['footer']:
1078 1078 if not self.footer:
1079 1079 self.footer = templater.stringify(self.t(types['footer'],
1080 1080 **props))
1081 1081
1082 1082 except KeyError, inst:
1083 1083 msg = _("%s: no key named '%s'")
1084 1084 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1085 1085 except SyntaxError, inst:
1086 1086 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1087 1087
1088 1088 def gettemplate(ui, tmpl, style):
1089 1089 """
1090 1090 Find the template matching the given template spec or style.
1091 1091 """
1092 1092
1093 1093 # ui settings
1094 1094 if not tmpl and not style:
1095 1095 tmpl = ui.config('ui', 'logtemplate')
1096 1096 if tmpl:
1097 1097 try:
1098 1098 tmpl = templater.parsestring(tmpl)
1099 1099 except SyntaxError:
1100 1100 tmpl = templater.parsestring(tmpl, quoted=False)
1101 1101 return tmpl, None
1102 1102 else:
1103 1103 style = util.expandpath(ui.config('ui', 'style', ''))
1104 1104
1105 1105 if style:
1106 1106 mapfile = style
1107 1107 if not os.path.split(mapfile)[0]:
1108 1108 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1109 1109 or templater.templatepath(mapfile))
1110 1110 if mapname:
1111 1111 mapfile = mapname
1112 1112 return None, mapfile
1113 1113
1114 1114 if not tmpl:
1115 1115 return None, None
1116 1116
1117 1117 # looks like a literal template?
1118 1118 if '{' in tmpl:
1119 1119 return tmpl, None
1120 1120
1121 1121 # perhaps a stock style?
1122 1122 if not os.path.split(tmpl)[0]:
1123 1123 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1124 1124 or templater.templatepath(tmpl))
1125 1125 if mapname and os.path.isfile(mapname):
1126 1126 return None, mapname
1127 1127
1128 1128 # perhaps it's a reference to [templates]
1129 1129 t = ui.config('templates', tmpl)
1130 1130 if t:
1131 1131 try:
1132 1132 tmpl = templater.parsestring(t)
1133 1133 except SyntaxError:
1134 1134 tmpl = templater.parsestring(t, quoted=False)
1135 1135 return tmpl, None
1136 1136
1137 1137 if tmpl == 'list':
1138 1138 ui.write(_("available styles: %s\n") % templater.stylelist())
1139 1139 raise util.Abort(_("specify a template"))
1140 1140
1141 1141 # perhaps it's a path to a map or a template
1142 1142 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1143 1143 # is it a mapfile for a style?
1144 1144 if os.path.basename(tmpl).startswith("map-"):
1145 1145 return None, os.path.realpath(tmpl)
1146 1146 tmpl = open(tmpl).read()
1147 1147 return tmpl, None
1148 1148
1149 1149 # constant string?
1150 1150 return tmpl, None
1151 1151
1152 1152 def show_changeset(ui, repo, opts, buffered=False):
1153 1153 """show one changeset using template or regular display.
1154 1154
1155 1155 Display format will be the first non-empty hit of:
1156 1156 1. option 'template'
1157 1157 2. option 'style'
1158 1158 3. [ui] setting 'logtemplate'
1159 1159 4. [ui] setting 'style'
1160 1160 If all of these values are either the unset or the empty string,
1161 1161 regular display via changeset_printer() is done.
1162 1162 """
1163 1163 # options
1164 1164 patch = None
1165 1165 if opts.get('patch') or opts.get('stat'):
1166 1166 patch = scmutil.matchall(repo)
1167 1167
1168 1168 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1169 1169
1170 1170 if not tmpl and not mapfile:
1171 1171 return changeset_printer(ui, repo, patch, opts, buffered)
1172 1172
1173 1173 try:
1174 1174 t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
1175 1175 except SyntaxError, inst:
1176 1176 raise util.Abort(inst.args[0])
1177 1177 return t
1178 1178
1179 1179 def showmarker(ui, marker):
1180 1180 """utility function to display obsolescence marker in a readable way
1181 1181
1182 1182 To be used by debug function."""
1183 1183 ui.write(hex(marker.precnode()))
1184 1184 for repl in marker.succnodes():
1185 1185 ui.write(' ')
1186 1186 ui.write(hex(repl))
1187 1187 ui.write(' %X ' % marker._data[2])
1188 1188 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1189 1189 sorted(marker.metadata().items()))))
1190 1190 ui.write('\n')
1191 1191
1192 1192 def finddate(ui, repo, date):
1193 1193 """Find the tipmost changeset that matches the given date spec"""
1194 1194
1195 1195 df = util.matchdate(date)
1196 1196 m = scmutil.matchall(repo)
1197 1197 results = {}
1198 1198
1199 1199 def prep(ctx, fns):
1200 1200 d = ctx.date()
1201 1201 if df(d[0]):
1202 1202 results[ctx.rev()] = d
1203 1203
1204 1204 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1205 1205 rev = ctx.rev()
1206 1206 if rev in results:
1207 1207 ui.status(_("found revision %s from %s\n") %
1208 1208 (rev, util.datestr(results[rev])))
1209 1209 return str(rev)
1210 1210
1211 1211 raise util.Abort(_("revision matching date not found"))
1212 1212
1213 1213 def increasingwindows(windowsize=8, sizelimit=512):
1214 1214 while True:
1215 1215 yield windowsize
1216 1216 if windowsize < sizelimit:
1217 1217 windowsize *= 2
1218 1218
1219 1219 class FileWalkError(Exception):
1220 1220 pass
1221 1221
1222 1222 def walkfilerevs(repo, match, follow, revs, fncache):
1223 1223 '''Walks the file history for the matched files.
1224 1224
1225 1225 Returns the changeset revs that are involved in the file history.
1226 1226
1227 1227 Throws FileWalkError if the file history can't be walked using
1228 1228 filelogs alone.
1229 1229 '''
1230 1230 wanted = set()
1231 1231 copies = []
1232 1232 minrev, maxrev = min(revs), max(revs)
1233 1233 def filerevgen(filelog, last):
1234 1234 """
1235 1235 Only files, no patterns. Check the history of each file.
1236 1236
1237 1237 Examines filelog entries within minrev, maxrev linkrev range
1238 1238 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1239 1239 tuples in backwards order
1240 1240 """
1241 1241 cl_count = len(repo)
1242 1242 revs = []
1243 1243 for j in xrange(0, last + 1):
1244 1244 linkrev = filelog.linkrev(j)
1245 1245 if linkrev < minrev:
1246 1246 continue
1247 1247 # only yield rev for which we have the changelog, it can
1248 1248 # happen while doing "hg log" during a pull or commit
1249 1249 if linkrev >= cl_count:
1250 1250 break
1251 1251
1252 1252 parentlinkrevs = []
1253 1253 for p in filelog.parentrevs(j):
1254 1254 if p != nullrev:
1255 1255 parentlinkrevs.append(filelog.linkrev(p))
1256 1256 n = filelog.node(j)
1257 1257 revs.append((linkrev, parentlinkrevs,
1258 1258 follow and filelog.renamed(n)))
1259 1259
1260 1260 return reversed(revs)
1261 1261 def iterfiles():
1262 1262 pctx = repo['.']
1263 1263 for filename in match.files():
1264 1264 if follow:
1265 1265 if filename not in pctx:
1266 1266 raise util.Abort(_('cannot follow file not in parent '
1267 1267 'revision: "%s"') % filename)
1268 1268 yield filename, pctx[filename].filenode()
1269 1269 else:
1270 1270 yield filename, None
1271 1271 for filename_node in copies:
1272 1272 yield filename_node
1273 1273
1274 1274 for file_, node in iterfiles():
1275 1275 filelog = repo.file(file_)
1276 1276 if not len(filelog):
1277 1277 if node is None:
1278 1278 # A zero count may be a directory or deleted file, so
1279 1279 # try to find matching entries on the slow path.
1280 1280 if follow:
1281 1281 raise util.Abort(
1282 1282 _('cannot follow nonexistent file: "%s"') % file_)
1283 1283 raise FileWalkError("Cannot walk via filelog")
1284 1284 else:
1285 1285 continue
1286 1286
1287 1287 if node is None:
1288 1288 last = len(filelog) - 1
1289 1289 else:
1290 1290 last = filelog.rev(node)
1291 1291
1292 1292
1293 1293 # keep track of all ancestors of the file
1294 1294 ancestors = set([filelog.linkrev(last)])
1295 1295
1296 1296 # iterate from latest to oldest revision
1297 1297 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1298 1298 if not follow:
1299 1299 if rev > maxrev:
1300 1300 continue
1301 1301 else:
1302 1302 # Note that last might not be the first interesting
1303 1303 # rev to us:
1304 1304 # if the file has been changed after maxrev, we'll
1305 1305 # have linkrev(last) > maxrev, and we still need
1306 1306 # to explore the file graph
1307 1307 if rev not in ancestors:
1308 1308 continue
1309 1309 # XXX insert 1327 fix here
1310 1310 if flparentlinkrevs:
1311 1311 ancestors.update(flparentlinkrevs)
1312 1312
1313 1313 fncache.setdefault(rev, []).append(file_)
1314 1314 wanted.add(rev)
1315 1315 if copied:
1316 1316 copies.append(copied)
1317 1317
1318 1318 return wanted
1319 1319
1320 1320 def walkchangerevs(repo, match, opts, prepare):
1321 1321 '''Iterate over files and the revs in which they changed.
1322 1322
1323 1323 Callers most commonly need to iterate backwards over the history
1324 1324 in which they are interested. Doing so has awful (quadratic-looking)
1325 1325 performance, so we use iterators in a "windowed" way.
1326 1326
1327 1327 We walk a window of revisions in the desired order. Within the
1328 1328 window, we first walk forwards to gather data, then in the desired
1329 1329 order (usually backwards) to display it.
1330 1330
1331 1331 This function returns an iterator yielding contexts. Before
1332 1332 yielding each context, the iterator will first call the prepare
1333 1333 function on each context in the window in forward order.'''
1334 1334
1335 1335 follow = opts.get('follow') or opts.get('follow_first')
1336 1336
1337 1337 if opts.get('rev'):
1338 1338 revs = scmutil.revrange(repo, opts.get('rev'))
1339 1339 elif follow:
1340 1340 revs = repo.revs('reverse(:.)')
1341 1341 else:
1342 1342 revs = revset.spanset(repo)
1343 1343 revs.reverse()
1344 1344 if not revs:
1345 1345 return []
1346 1346 wanted = set()
1347 1347 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1348 1348 fncache = {}
1349 1349 change = repo.changectx
1350 1350
1351 1351 # First step is to fill wanted, the set of revisions that we want to yield.
1352 1352 # When it does not induce extra cost, we also fill fncache for revisions in
1353 1353 # wanted: a cache of filenames that were changed (ctx.files()) and that
1354 1354 # match the file filtering conditions.
1355 1355
1356 1356 if not slowpath and not match.files():
1357 1357 # No files, no patterns. Display all revs.
1358 1358 wanted = revs
1359 1359
1360 1360 if not slowpath and match.files():
1361 1361 # We only have to read through the filelog to find wanted revisions
1362 1362
1363 1363 try:
1364 1364 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1365 1365 except FileWalkError:
1366 1366 slowpath = True
1367 1367
1368 1368 # We decided to fall back to the slowpath because at least one
1369 1369 # of the paths was not a file. Check to see if at least one of them
1370 1370 # existed in history, otherwise simply return
1371 1371 for path in match.files():
1372 1372 if path == '.' or path in repo.store:
1373 1373 break
1374 1374 else:
1375 1375 return []
1376 1376
1377 1377 if slowpath:
1378 1378 # We have to read the changelog to match filenames against
1379 1379 # changed files
1380 1380
1381 1381 if follow:
1382 1382 raise util.Abort(_('can only follow copies/renames for explicit '
1383 1383 'filenames'))
1384 1384
1385 1385 # The slow path checks files modified in every changeset.
1386 1386 # This is really slow on large repos, so compute the set lazily.
1387 1387 class lazywantedset(object):
1388 1388 def __init__(self):
1389 1389 self.set = set()
1390 1390 self.revs = set(revs)
1391 1391
1392 1392 # No need to worry about locality here because it will be accessed
1393 1393 # in the same order as the increasing window below.
1394 1394 def __contains__(self, value):
1395 1395 if value in self.set:
1396 1396 return True
1397 1397 elif not value in self.revs:
1398 1398 return False
1399 1399 else:
1400 1400 self.revs.discard(value)
1401 1401 ctx = change(value)
1402 1402 matches = filter(match, ctx.files())
1403 1403 if matches:
1404 1404 fncache[value] = matches
1405 1405 self.set.add(value)
1406 1406 return True
1407 1407 return False
1408 1408
1409 1409 def discard(self, value):
1410 1410 self.revs.discard(value)
1411 1411 self.set.discard(value)
1412 1412
1413 1413 wanted = lazywantedset()
1414 1414
1415 1415 class followfilter(object):
1416 1416 def __init__(self, onlyfirst=False):
1417 1417 self.startrev = nullrev
1418 1418 self.roots = set()
1419 1419 self.onlyfirst = onlyfirst
1420 1420
1421 1421 def match(self, rev):
1422 1422 def realparents(rev):
1423 1423 if self.onlyfirst:
1424 1424 return repo.changelog.parentrevs(rev)[0:1]
1425 1425 else:
1426 1426 return filter(lambda x: x != nullrev,
1427 1427 repo.changelog.parentrevs(rev))
1428 1428
1429 1429 if self.startrev == nullrev:
1430 1430 self.startrev = rev
1431 1431 return True
1432 1432
1433 1433 if rev > self.startrev:
1434 1434 # forward: all descendants
1435 1435 if not self.roots:
1436 1436 self.roots.add(self.startrev)
1437 1437 for parent in realparents(rev):
1438 1438 if parent in self.roots:
1439 1439 self.roots.add(rev)
1440 1440 return True
1441 1441 else:
1442 1442 # backwards: all parents
1443 1443 if not self.roots:
1444 1444 self.roots.update(realparents(self.startrev))
1445 1445 if rev in self.roots:
1446 1446 self.roots.remove(rev)
1447 1447 self.roots.update(realparents(rev))
1448 1448 return True
1449 1449
1450 1450 return False
1451 1451
1452 1452 # it might be worthwhile to do this in the iterator if the rev range
1453 1453 # is descending and the prune args are all within that range
1454 1454 for rev in opts.get('prune', ()):
1455 1455 rev = repo[rev].rev()
1456 1456 ff = followfilter()
1457 1457 stop = min(revs[0], revs[-1])
1458 1458 for x in xrange(rev, stop - 1, -1):
1459 1459 if ff.match(x):
1460 1460 wanted = wanted - [x]
1461 1461
1462 1462 # Now that wanted is correctly initialized, we can iterate over the
1463 1463 # revision range, yielding only revisions in wanted.
1464 1464 def iterate():
1465 1465 if follow and not match.files():
1466 1466 ff = followfilter(onlyfirst=opts.get('follow_first'))
1467 1467 def want(rev):
1468 1468 return ff.match(rev) and rev in wanted
1469 1469 else:
1470 1470 def want(rev):
1471 1471 return rev in wanted
1472 1472
1473 1473 it = iter(revs)
1474 1474 stopiteration = False
1475 1475 for windowsize in increasingwindows():
1476 1476 nrevs = []
1477 1477 for i in xrange(windowsize):
1478 1478 try:
1479 1479 rev = it.next()
1480 1480 if want(rev):
1481 1481 nrevs.append(rev)
1482 1482 except (StopIteration):
1483 1483 stopiteration = True
1484 1484 break
1485 1485 for rev in sorted(nrevs):
1486 1486 fns = fncache.get(rev)
1487 1487 ctx = change(rev)
1488 1488 if not fns:
1489 1489 def fns_generator():
1490 1490 for f in ctx.files():
1491 1491 if match(f):
1492 1492 yield f
1493 1493 fns = fns_generator()
1494 1494 prepare(ctx, fns)
1495 1495 for rev in nrevs:
1496 1496 yield change(rev)
1497 1497
1498 1498 if stopiteration:
1499 1499 break
1500 1500
1501 1501 return iterate()
1502 1502
1503 1503 def _makefollowlogfilematcher(repo, files, followfirst):
1504 1504 # When displaying a revision with --patch --follow FILE, we have
1505 1505 # to know which file of the revision must be diffed. With
1506 1506 # --follow, we want the names of the ancestors of FILE in the
1507 1507 # revision, stored in "fcache". "fcache" is populated by
1508 1508 # reproducing the graph traversal already done by --follow revset
1509 1509 # and relating linkrevs to file names (which is not "correct" but
1510 1510 # good enough).
1511 1511 fcache = {}
1512 1512 fcacheready = [False]
1513 1513 pctx = repo['.']
1514 1514
1515 1515 def populate():
1516 1516 for fn in files:
1517 1517 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1518 1518 for c in i:
1519 1519 fcache.setdefault(c.linkrev(), set()).add(c.path())
1520 1520
1521 1521 def filematcher(rev):
1522 1522 if not fcacheready[0]:
1523 1523 # Lazy initialization
1524 1524 fcacheready[0] = True
1525 1525 populate()
1526 1526 return scmutil.matchfiles(repo, fcache.get(rev, []))
1527 1527
1528 1528 return filematcher
1529 1529
1530 1530 def _makenofollowlogfilematcher(repo, pats, opts):
1531 1531 '''hook for extensions to override the filematcher for non-follow cases'''
1532 1532 return None
1533 1533
1534 1534 def _makelogrevset(repo, pats, opts, revs):
1535 1535 """Return (expr, filematcher) where expr is a revset string built
1536 1536 from log options and file patterns or None. If --stat or --patch
1537 1537 are not passed filematcher is None. Otherwise it is a callable
1538 1538 taking a revision number and returning a match objects filtering
1539 1539 the files to be detailed when displaying the revision.
1540 1540 """
1541 1541 opt2revset = {
1542 1542 'no_merges': ('not merge()', None),
1543 1543 'only_merges': ('merge()', None),
1544 1544 '_ancestors': ('ancestors(%(val)s)', None),
1545 1545 '_fancestors': ('_firstancestors(%(val)s)', None),
1546 1546 '_descendants': ('descendants(%(val)s)', None),
1547 1547 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1548 1548 '_matchfiles': ('_matchfiles(%(val)s)', None),
1549 1549 'date': ('date(%(val)r)', None),
1550 1550 'branch': ('branch(%(val)r)', ' or '),
1551 1551 '_patslog': ('filelog(%(val)r)', ' or '),
1552 1552 '_patsfollow': ('follow(%(val)r)', ' or '),
1553 1553 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1554 1554 'keyword': ('keyword(%(val)r)', ' or '),
1555 1555 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1556 1556 'user': ('user(%(val)r)', ' or '),
1557 1557 }
1558 1558
1559 1559 opts = dict(opts)
1560 1560 # follow or not follow?
1561 1561 follow = opts.get('follow') or opts.get('follow_first')
1562 1562 followfirst = opts.get('follow_first') and 1 or 0
1563 1563 # --follow with FILE behaviour depends on revs...
1564 1564 it = iter(revs)
1565 1565 startrev = it.next()
1566 1566 try:
1567 1567 followdescendants = startrev < it.next()
1568 1568 except (StopIteration):
1569 1569 followdescendants = False
1570 1570
1571 1571 # branch and only_branch are really aliases and must be handled at
1572 1572 # the same time
1573 1573 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1574 1574 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1575 1575 # pats/include/exclude are passed to match.match() directly in
1576 1576 # _matchfiles() revset but walkchangerevs() builds its matcher with
1577 1577 # scmutil.match(). The difference is input pats are globbed on
1578 1578 # platforms without shell expansion (windows).
1579 1579 pctx = repo[None]
1580 1580 match, pats = scmutil.matchandpats(pctx, pats, opts)
1581 1581 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1582 1582 if not slowpath:
1583 1583 for f in match.files():
1584 1584 if follow and f not in pctx:
1585 1585 # If the file exists, it may be a directory, so let it
1586 1586 # take the slow path.
1587 1587 if os.path.exists(repo.wjoin(f)):
1588 1588 slowpath = True
1589 1589 continue
1590 1590 else:
1591 1591 raise util.Abort(_('cannot follow file not in parent '
1592 1592 'revision: "%s"') % f)
1593 1593 filelog = repo.file(f)
1594 1594 if not filelog:
1595 1595 # A zero count may be a directory or deleted file, so
1596 1596 # try to find matching entries on the slow path.
1597 1597 if follow:
1598 1598 raise util.Abort(
1599 1599 _('cannot follow nonexistent file: "%s"') % f)
1600 1600 slowpath = True
1601 1601
1602 1602 # We decided to fall back to the slowpath because at least one
1603 1603 # of the paths was not a file. Check to see if at least one of them
1604 1604 # existed in history - in that case, we'll continue down the
1605 1605 # slowpath; otherwise, we can turn off the slowpath
1606 1606 if slowpath:
1607 1607 for path in match.files():
1608 1608 if path == '.' or path in repo.store:
1609 1609 break
1610 1610 else:
1611 1611 slowpath = False
1612 1612
1613 1613 if slowpath:
1614 1614 # See walkchangerevs() slow path.
1615 1615 #
1616 1616 # pats/include/exclude cannot be represented as separate
1617 1617 # revset expressions as their filtering logic applies at file
1618 1618 # level. For instance "-I a -X a" matches a revision touching
1619 1619 # "a" and "b" while "file(a) and not file(b)" does
1620 1620 # not. Besides, filesets are evaluated against the working
1621 1621 # directory.
1622 1622 matchargs = ['r:', 'd:relpath']
1623 1623 for p in pats:
1624 1624 matchargs.append('p:' + p)
1625 1625 for p in opts.get('include', []):
1626 1626 matchargs.append('i:' + p)
1627 1627 for p in opts.get('exclude', []):
1628 1628 matchargs.append('x:' + p)
1629 1629 matchargs = ','.join(('%r' % p) for p in matchargs)
1630 1630 opts['_matchfiles'] = matchargs
1631 1631 else:
1632 1632 if follow:
1633 1633 fpats = ('_patsfollow', '_patsfollowfirst')
1634 1634 fnopats = (('_ancestors', '_fancestors'),
1635 1635 ('_descendants', '_fdescendants'))
1636 1636 if pats:
1637 1637 # follow() revset interprets its file argument as a
1638 1638 # manifest entry, so use match.files(), not pats.
1639 1639 opts[fpats[followfirst]] = list(match.files())
1640 1640 else:
1641 1641 opts[fnopats[followdescendants][followfirst]] = str(startrev)
1642 1642 else:
1643 1643 opts['_patslog'] = list(pats)
1644 1644
1645 1645 filematcher = None
1646 1646 if opts.get('patch') or opts.get('stat'):
1647 1647 # When following files, track renames via a special matcher.
1648 1648 # If we're forced to take the slowpath it means we're following
1649 1649 # at least one pattern/directory, so don't bother with rename tracking.
1650 1650 if follow and not match.always() and not slowpath:
1651 1651 # _makelogfilematcher expects its files argument to be relative to
1652 1652 # the repo root, so use match.files(), not pats.
1653 1653 filematcher = _makefollowlogfilematcher(repo, match.files(),
1654 1654 followfirst)
1655 1655 else:
1656 1656 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1657 1657 if filematcher is None:
1658 1658 filematcher = lambda rev: match
1659 1659
1660 1660 expr = []
1661 1661 for op, val in opts.iteritems():
1662 1662 if not val:
1663 1663 continue
1664 1664 if op not in opt2revset:
1665 1665 continue
1666 1666 revop, andor = opt2revset[op]
1667 1667 if '%(val)' not in revop:
1668 1668 expr.append(revop)
1669 1669 else:
1670 1670 if not isinstance(val, list):
1671 1671 e = revop % {'val': val}
1672 1672 else:
1673 1673 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1674 1674 expr.append(e)
1675 1675
1676 1676 if expr:
1677 1677 expr = '(' + ' and '.join(expr) + ')'
1678 1678 else:
1679 1679 expr = None
1680 1680 return expr, filematcher
1681 1681
1682 1682 def getgraphlogrevs(repo, pats, opts):
1683 1683 """Return (revs, expr, filematcher) where revs is an iterable of
1684 1684 revision numbers, expr is a revset string built from log options
1685 1685 and file patterns or None, and used to filter 'revs'. If --stat or
1686 1686 --patch are not passed filematcher is None. Otherwise it is a
1687 1687 callable taking a revision number and returning a match objects
1688 1688 filtering the files to be detailed when displaying the revision.
1689 1689 """
1690 1690 if not len(repo):
1691 1691 return [], None, None
1692 1692 limit = loglimit(opts)
1693 1693 # Default --rev value depends on --follow but --follow behaviour
1694 1694 # depends on revisions resolved from --rev...
1695 1695 follow = opts.get('follow') or opts.get('follow_first')
1696 1696 possiblyunsorted = False # whether revs might need sorting
1697 1697 if opts.get('rev'):
1698 1698 revs = scmutil.revrange(repo, opts['rev'])
1699 1699 # Don't sort here because _makelogrevset might depend on the
1700 1700 # order of revs
1701 1701 possiblyunsorted = True
1702 1702 else:
1703 1703 if follow and len(repo) > 0:
1704 1704 revs = repo.revs('reverse(:.)')
1705 1705 else:
1706 1706 revs = revset.spanset(repo)
1707 1707 revs.reverse()
1708 1708 if not revs:
1709 1709 return revset.baseset(), None, None
1710 1710 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1711 1711 if possiblyunsorted:
1712 1712 revs.sort(reverse=True)
1713 1713 if expr:
1714 1714 # Revset matchers often operate faster on revisions in changelog
1715 1715 # order, because most filters deal with the changelog.
1716 1716 revs.reverse()
1717 1717 matcher = revset.match(repo.ui, expr)
1718 1718 # Revset matches can reorder revisions. "A or B" typically returns
1719 1719 # returns the revision matching A then the revision matching B. Sort
1720 1720 # again to fix that.
1721 1721 revs = matcher(repo, revs)
1722 1722 revs.sort(reverse=True)
1723 1723 if limit is not None:
1724 1724 limitedrevs = revset.baseset()
1725 1725 for idx, rev in enumerate(revs):
1726 1726 if idx >= limit:
1727 1727 break
1728 1728 limitedrevs.append(rev)
1729 1729 revs = limitedrevs
1730 1730
1731 1731 return revs, expr, filematcher
1732 1732
1733 1733 def getlogrevs(repo, pats, opts):
1734 1734 """Return (revs, expr, filematcher) where revs is an iterable of
1735 1735 revision numbers, expr is a revset string built from log options
1736 1736 and file patterns or None, and used to filter 'revs'. If --stat or
1737 1737 --patch are not passed filematcher is None. Otherwise it is a
1738 1738 callable taking a revision number and returning a match objects
1739 1739 filtering the files to be detailed when displaying the revision.
1740 1740 """
1741 1741 limit = loglimit(opts)
1742 1742 # Default --rev value depends on --follow but --follow behaviour
1743 1743 # depends on revisions resolved from --rev...
1744 1744 follow = opts.get('follow') or opts.get('follow_first')
1745 1745 if opts.get('rev'):
1746 1746 revs = scmutil.revrange(repo, opts['rev'])
1747 1747 elif follow:
1748 1748 revs = repo.revs('reverse(:.)')
1749 1749 else:
1750 1750 revs = revset.spanset(repo)
1751 1751 revs.reverse()
1752 1752 if not revs:
1753 1753 return revset.baseset([]), None, None
1754 1754 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1755 1755 if expr:
1756 1756 # Revset matchers often operate faster on revisions in changelog
1757 1757 # order, because most filters deal with the changelog.
1758 1758 if not opts.get('rev'):
1759 1759 revs.reverse()
1760 1760 matcher = revset.match(repo.ui, expr)
1761 1761 # Revset matches can reorder revisions. "A or B" typically returns
1762 1762 # returns the revision matching A then the revision matching B. Sort
1763 1763 # again to fix that.
1764 1764 revs = matcher(repo, revs)
1765 1765 if not opts.get('rev'):
1766 1766 revs.sort(reverse=True)
1767 1767 if limit is not None:
1768 1768 count = 0
1769 1769 limitedrevs = revset.baseset([])
1770 1770 it = iter(revs)
1771 1771 while count < limit:
1772 1772 try:
1773 1773 limitedrevs.append(it.next())
1774 1774 except (StopIteration):
1775 1775 break
1776 1776 count += 1
1777 1777 revs = limitedrevs
1778 1778
1779 1779 return revs, expr, filematcher
1780 1780
1781 1781 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1782 1782 filematcher=None):
1783 1783 seen, state = [], graphmod.asciistate()
1784 1784 for rev, type, ctx, parents in dag:
1785 1785 char = 'o'
1786 1786 if ctx.node() in showparents:
1787 1787 char = '@'
1788 1788 elif ctx.obsolete():
1789 1789 char = 'x'
1790 1790 copies = None
1791 1791 if getrenamed and ctx.rev():
1792 1792 copies = []
1793 1793 for fn in ctx.files():
1794 1794 rename = getrenamed(fn, ctx.rev())
1795 1795 if rename:
1796 1796 copies.append((fn, rename[0]))
1797 1797 revmatchfn = None
1798 1798 if filematcher is not None:
1799 1799 revmatchfn = filematcher(ctx.rev())
1800 1800 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1801 1801 lines = displayer.hunk.pop(rev).split('\n')
1802 1802 if not lines[-1]:
1803 1803 del lines[-1]
1804 1804 displayer.flush(rev)
1805 1805 edges = edgefn(type, char, lines, seen, rev, parents)
1806 1806 for type, char, lines, coldata in edges:
1807 1807 graphmod.ascii(ui, state, type, char, lines, coldata)
1808 1808 displayer.close()
1809 1809
1810 1810 def graphlog(ui, repo, *pats, **opts):
1811 1811 # Parameters are identical to log command ones
1812 1812 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1813 1813 revdag = graphmod.dagwalker(repo, revs)
1814 1814
1815 1815 getrenamed = None
1816 1816 if opts.get('copies'):
1817 1817 endrev = None
1818 1818 if opts.get('rev'):
1819 1819 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1820 1820 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1821 1821 displayer = show_changeset(ui, repo, opts, buffered=True)
1822 1822 showparents = [ctx.node() for ctx in repo[None].parents()]
1823 1823 displaygraph(ui, revdag, displayer, showparents,
1824 1824 graphmod.asciiedges, getrenamed, filematcher)
1825 1825
1826 1826 def checkunsupportedgraphflags(pats, opts):
1827 1827 for op in ["newest_first"]:
1828 1828 if op in opts and opts[op]:
1829 1829 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1830 1830 % op.replace("_", "-"))
1831 1831
1832 1832 def graphrevs(repo, nodes, opts):
1833 1833 limit = loglimit(opts)
1834 1834 nodes.reverse()
1835 1835 if limit is not None:
1836 1836 nodes = nodes[:limit]
1837 1837 return graphmod.nodes(repo, nodes)
1838 1838
1839 1839 def add(ui, repo, match, dryrun, listsubrepos, prefix, explicitonly):
1840 1840 join = lambda f: os.path.join(prefix, f)
1841 1841 bad = []
1842 1842 oldbad = match.bad
1843 1843 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1844 1844 names = []
1845 1845 wctx = repo[None]
1846 1846 cca = None
1847 1847 abort, warn = scmutil.checkportabilityalert(ui)
1848 1848 if abort or warn:
1849 1849 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1850 1850 for f in repo.walk(match):
1851 1851 exact = match.exact(f)
1852 1852 if exact or not explicitonly and f not in repo.dirstate:
1853 1853 if cca:
1854 1854 cca(f)
1855 1855 names.append(f)
1856 1856 if ui.verbose or not exact:
1857 1857 ui.status(_('adding %s\n') % match.rel(join(f)))
1858 1858
1859 1859 for subpath in sorted(wctx.substate):
1860 1860 sub = wctx.sub(subpath)
1861 1861 try:
1862 1862 submatch = matchmod.narrowmatcher(subpath, match)
1863 1863 if listsubrepos:
1864 1864 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1865 1865 False))
1866 1866 else:
1867 1867 bad.extend(sub.add(ui, submatch, dryrun, listsubrepos, prefix,
1868 1868 True))
1869 1869 except error.LookupError:
1870 1870 ui.status(_("skipping missing subrepository: %s\n")
1871 1871 % join(subpath))
1872 1872
1873 1873 if not dryrun:
1874 1874 rejected = wctx.add(names, prefix)
1875 1875 bad.extend(f for f in rejected if f in match.files())
1876 1876 return bad
1877 1877
1878 1878 def forget(ui, repo, match, prefix, explicitonly):
1879 1879 join = lambda f: os.path.join(prefix, f)
1880 1880 bad = []
1881 1881 oldbad = match.bad
1882 1882 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1883 1883 wctx = repo[None]
1884 1884 forgot = []
1885 1885 s = repo.status(match=match, clean=True)
1886 1886 forget = sorted(s[0] + s[1] + s[3] + s[6])
1887 1887 if explicitonly:
1888 1888 forget = [f for f in forget if match.exact(f)]
1889 1889
1890 1890 for subpath in sorted(wctx.substate):
1891 1891 sub = wctx.sub(subpath)
1892 1892 try:
1893 1893 submatch = matchmod.narrowmatcher(subpath, match)
1894 1894 subbad, subforgot = sub.forget(ui, submatch, prefix)
1895 1895 bad.extend([subpath + '/' + f for f in subbad])
1896 1896 forgot.extend([subpath + '/' + f for f in subforgot])
1897 1897 except error.LookupError:
1898 1898 ui.status(_("skipping missing subrepository: %s\n")
1899 1899 % join(subpath))
1900 1900
1901 1901 if not explicitonly:
1902 1902 for f in match.files():
1903 1903 if f not in repo.dirstate and not os.path.isdir(match.rel(join(f))):
1904 1904 if f not in forgot:
1905 1905 if os.path.exists(match.rel(join(f))):
1906 1906 ui.warn(_('not removing %s: '
1907 1907 'file is already untracked\n')
1908 1908 % match.rel(join(f)))
1909 1909 bad.append(f)
1910 1910
1911 1911 for f in forget:
1912 1912 if ui.verbose or not match.exact(f):
1913 1913 ui.status(_('removing %s\n') % match.rel(join(f)))
1914 1914
1915 1915 rejected = wctx.forget(forget, prefix)
1916 1916 bad.extend(f for f in rejected if f in match.files())
1917 1917 forgot.extend(forget)
1918 1918 return bad, forgot
1919 1919
1920 1920 def cat(ui, repo, ctx, matcher, prefix, **opts):
1921 1921 err = 1
1922 1922
1923 1923 def write(path):
1924 1924 fp = makefileobj(repo, opts.get('output'), ctx.node(),
1925 1925 pathname=os.path.join(prefix, path))
1926 1926 data = ctx[path].data()
1927 1927 if opts.get('decode'):
1928 1928 data = repo.wwritedata(path, data)
1929 1929 fp.write(data)
1930 1930 fp.close()
1931 1931
1932 1932 # Automation often uses hg cat on single files, so special case it
1933 1933 # for performance to avoid the cost of parsing the manifest.
1934 1934 if len(matcher.files()) == 1 and not matcher.anypats():
1935 1935 file = matcher.files()[0]
1936 1936 mf = repo.manifest
1937 1937 mfnode = ctx._changeset[0]
1938 1938 if mf.find(mfnode, file)[0]:
1939 1939 write(file)
1940 1940 return 0
1941 1941
1942 1942 # Don't warn about "missing" files that are really in subrepos
1943 1943 bad = matcher.bad
1944 1944
1945 1945 def badfn(path, msg):
1946 1946 for subpath in ctx.substate:
1947 1947 if path.startswith(subpath):
1948 1948 return
1949 1949 bad(path, msg)
1950 1950
1951 1951 matcher.bad = badfn
1952 1952
1953 1953 for abs in ctx.walk(matcher):
1954 1954 write(abs)
1955 1955 err = 0
1956 1956
1957 1957 matcher.bad = bad
1958 1958
1959 1959 for subpath in sorted(ctx.substate):
1960 1960 sub = ctx.sub(subpath)
1961 1961 try:
1962 1962 submatch = matchmod.narrowmatcher(subpath, matcher)
1963 1963
1964 1964 if not sub.cat(ui, submatch, os.path.join(prefix, sub._path),
1965 1965 **opts):
1966 1966 err = 0
1967 1967 except error.RepoLookupError:
1968 1968 ui.status(_("skipping missing subrepository: %s\n")
1969 1969 % os.path.join(prefix, subpath))
1970 1970
1971 1971 return err
1972 1972
1973 1973 def duplicatecopies(repo, rev, fromrev, skiprev=None):
1974 1974 '''reproduce copies from fromrev to rev in the dirstate
1975 1975
1976 1976 If skiprev is specified, it's a revision that should be used to
1977 1977 filter copy records. Any copies that occur between fromrev and
1978 1978 skiprev will not be duplicated, even if they appear in the set of
1979 1979 copies between fromrev and rev.
1980 1980 '''
1981 1981 exclude = {}
1982 1982 if skiprev is not None:
1983 1983 exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
1984 1984 for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
1985 1985 # copies.pathcopies returns backward renames, so dst might not
1986 1986 # actually be in the dirstate
1987 1987 if dst in exclude:
1988 1988 continue
1989 1989 if repo.dirstate[dst] in "nma":
1990 1990 repo.dirstate.copy(src, dst)
1991 1991
1992 1992 def commit(ui, repo, commitfunc, pats, opts):
1993 1993 '''commit the specified files or all outstanding changes'''
1994 1994 date = opts.get('date')
1995 1995 if date:
1996 1996 opts['date'] = util.parsedate(date)
1997 1997 message = logmessage(ui, opts)
1998 1998
1999 1999 # extract addremove carefully -- this function can be called from a command
2000 2000 # that doesn't support addremove
2001 2001 if opts.get('addremove'):
2002 2002 scmutil.addremove(repo, pats, opts)
2003 2003
2004 2004 return commitfunc(ui, repo, message,
2005 2005 scmutil.match(repo[None], pats, opts), opts)
2006 2006
2007 2007 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2008 2008 ui.note(_('amending changeset %s\n') % old)
2009 2009 base = old.p1()
2010 2010
2011 2011 wlock = lock = newid = None
2012 2012 try:
2013 2013 wlock = repo.wlock()
2014 2014 lock = repo.lock()
2015 2015 tr = repo.transaction('amend')
2016 2016 try:
2017 2017 # See if we got a message from -m or -l, if not, open the editor
2018 2018 # with the message of the changeset to amend
2019 2019 message = logmessage(ui, opts)
2020 2020 # ensure logfile does not conflict with later enforcement of the
2021 2021 # message. potential logfile content has been processed by
2022 2022 # `logmessage` anyway.
2023 2023 opts.pop('logfile')
2024 2024 # First, do a regular commit to record all changes in the working
2025 2025 # directory (if there are any)
2026 2026 ui.callhooks = False
2027 2027 currentbookmark = repo._bookmarkcurrent
2028 2028 try:
2029 2029 repo._bookmarkcurrent = None
2030 2030 opts['message'] = 'temporary amend commit for %s' % old
2031 2031 node = commit(ui, repo, commitfunc, pats, opts)
2032 2032 finally:
2033 2033 repo._bookmarkcurrent = currentbookmark
2034 2034 ui.callhooks = True
2035 2035 ctx = repo[node]
2036 2036
2037 2037 # Participating changesets:
2038 2038 #
2039 2039 # node/ctx o - new (intermediate) commit that contains changes
2040 2040 # | from working dir to go into amending commit
2041 2041 # | (or a workingctx if there were no changes)
2042 2042 # |
2043 2043 # old o - changeset to amend
2044 2044 # |
2045 2045 # base o - parent of amending changeset
2046 2046
2047 2047 # Update extra dict from amended commit (e.g. to preserve graft
2048 2048 # source)
2049 2049 extra.update(old.extra())
2050 2050
2051 2051 # Also update it from the intermediate commit or from the wctx
2052 2052 extra.update(ctx.extra())
2053 2053
2054 2054 if len(old.parents()) > 1:
2055 2055 # ctx.files() isn't reliable for merges, so fall back to the
2056 2056 # slower repo.status() method
2057 2057 files = set([fn for st in repo.status(base, old)[:3]
2058 2058 for fn in st])
2059 2059 else:
2060 2060 files = set(old.files())
2061 2061
2062 2062 # Second, we use either the commit we just did, or if there were no
2063 2063 # changes the parent of the working directory as the version of the
2064 2064 # files in the final amend commit
2065 2065 if node:
2066 2066 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2067 2067
2068 2068 user = ctx.user()
2069 2069 date = ctx.date()
2070 2070 # Recompute copies (avoid recording a -> b -> a)
2071 2071 copied = copies.pathcopies(base, ctx)
2072 2072
2073 2073 # Prune files which were reverted by the updates: if old
2074 2074 # introduced file X and our intermediate commit, node,
2075 2075 # renamed that file, then those two files are the same and
2076 2076 # we can discard X from our list of files. Likewise if X
2077 2077 # was deleted, it's no longer relevant
2078 2078 files.update(ctx.files())
2079 2079
2080 2080 def samefile(f):
2081 2081 if f in ctx.manifest():
2082 2082 a = ctx.filectx(f)
2083 2083 if f in base.manifest():
2084 2084 b = base.filectx(f)
2085 2085 return (not a.cmp(b)
2086 2086 and a.flags() == b.flags())
2087 2087 else:
2088 2088 return False
2089 2089 else:
2090 2090 return f not in base.manifest()
2091 2091 files = [f for f in files if not samefile(f)]
2092 2092
2093 2093 def filectxfn(repo, ctx_, path):
2094 2094 try:
2095 2095 fctx = ctx[path]
2096 2096 flags = fctx.flags()
2097 2097 mctx = context.memfilectx(repo,
2098 2098 fctx.path(), fctx.data(),
2099 2099 islink='l' in flags,
2100 2100 isexec='x' in flags,
2101 2101 copied=copied.get(path))
2102 2102 return mctx
2103 2103 except KeyError:
2104 2104 raise IOError
2105 2105 else:
2106 2106 ui.note(_('copying changeset %s to %s\n') % (old, base))
2107 2107
2108 2108 # Use version of files as in the old cset
2109 2109 def filectxfn(repo, ctx_, path):
2110 2110 try:
2111 2111 return old.filectx(path)
2112 2112 except KeyError:
2113 2113 raise IOError
2114 2114
2115 2115 user = opts.get('user') or old.user()
2116 2116 date = opts.get('date') or old.date()
2117 2117 editform = 'commit.amend'
2118 2118 editor = getcommiteditor(editform=editform, **opts)
2119 2119 if not message:
2120 2120 editor = getcommiteditor(edit=True, editform=editform)
2121 2121 message = old.description()
2122 2122
2123 2123 pureextra = extra.copy()
2124 2124 extra['amend_source'] = old.hex()
2125 2125
2126 2126 new = context.memctx(repo,
2127 2127 parents=[base.node(), old.p2().node()],
2128 2128 text=message,
2129 2129 files=files,
2130 2130 filectxfn=filectxfn,
2131 2131 user=user,
2132 2132 date=date,
2133 2133 extra=extra,
2134 2134 editor=editor)
2135 2135
2136 2136 newdesc = changelog.stripdesc(new.description())
2137 2137 if ((not node)
2138 2138 and newdesc == old.description()
2139 2139 and user == old.user()
2140 2140 and date == old.date()
2141 2141 and pureextra == old.extra()):
2142 2142 # nothing changed. continuing here would create a new node
2143 2143 # anyway because of the amend_source noise.
2144 2144 #
2145 2145 # This not what we expect from amend.
2146 2146 return old.node()
2147 2147
2148 2148 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2149 2149 try:
2150 2150 if opts.get('secret'):
2151 2151 commitphase = 'secret'
2152 2152 else:
2153 2153 commitphase = old.phase()
2154 2154 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2155 2155 newid = repo.commitctx(new)
2156 2156 finally:
2157 2157 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2158 2158 if newid != old.node():
2159 2159 # Reroute the working copy parent to the new changeset
2160 2160 repo.setparents(newid, nullid)
2161 2161
2162 2162 # Move bookmarks from old parent to amend commit
2163 2163 bms = repo.nodebookmarks(old.node())
2164 2164 if bms:
2165 2165 marks = repo._bookmarks
2166 2166 for bm in bms:
2167 2167 marks[bm] = newid
2168 2168 marks.write()
2169 2169 #commit the whole amend process
2170 2170 if obsolete._enabled and newid != old.node():
2171 2171 # mark the new changeset as successor of the rewritten one
2172 2172 new = repo[newid]
2173 2173 obs = [(old, (new,))]
2174 2174 if node:
2175 2175 obs.append((ctx, ()))
2176 2176
2177 2177 obsolete.createmarkers(repo, obs)
2178 2178 tr.close()
2179 2179 finally:
2180 2180 tr.release()
2181 2181 if (not obsolete._enabled) and newid != old.node():
2182 2182 # Strip the intermediate commit (if there was one) and the amended
2183 2183 # commit
2184 2184 if node:
2185 2185 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2186 2186 ui.note(_('stripping amended changeset %s\n') % old)
2187 2187 repair.strip(ui, repo, old.node(), topic='amend-backup')
2188 2188 finally:
2189 2189 if newid is None:
2190 2190 repo.dirstate.invalidate()
2191 2191 lockmod.release(lock, wlock)
2192 2192 return newid
2193 2193
2194 2194 def commiteditor(repo, ctx, subs, editform=''):
2195 2195 if ctx.description():
2196 2196 return ctx.description()
2197 2197 return commitforceeditor(repo, ctx, subs, editform=editform)
2198 2198
2199 2199 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2200 2200 editform=''):
2201 2201 if not extramsg:
2202 2202 extramsg = _("Leave message empty to abort commit.")
2203 2203
2204 2204 forms = [e for e in editform.split('.') if e]
2205 2205 forms.insert(0, 'changeset')
2206 2206 while forms:
2207 2207 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2208 2208 if tmpl:
2209 2209 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2210 2210 break
2211 2211 forms.pop()
2212 2212 else:
2213 2213 committext = buildcommittext(repo, ctx, subs, extramsg)
2214 2214
2215 2215 # run editor in the repository root
2216 2216 olddir = os.getcwd()
2217 2217 os.chdir(repo.root)
2218 2218 text = repo.ui.edit(committext, ctx.user(), ctx.extra())
2219 2219 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2220 2220 os.chdir(olddir)
2221 2221
2222 2222 if finishdesc:
2223 2223 text = finishdesc(text)
2224 2224 if not text.strip():
2225 2225 raise util.Abort(_("empty commit message"))
2226 2226
2227 2227 return text
2228 2228
2229 2229 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2230 2230 ui = repo.ui
2231 2231 tmpl, mapfile = gettemplate(ui, tmpl, None)
2232 2232
2233 2233 try:
2234 2234 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2235 2235 except SyntaxError, inst:
2236 2236 raise util.Abort(inst.args[0])
2237 2237
2238 2238 for k, v in repo.ui.configitems('committemplate'):
2239 2239 if k != 'changeset':
2240 2240 t.t.cache[k] = v
2241 2241
2242 2242 if not extramsg:
2243 2243 extramsg = '' # ensure that extramsg is string
2244 2244
2245 2245 ui.pushbuffer()
2246 2246 t.show(ctx, extramsg=extramsg)
2247 2247 return ui.popbuffer()
2248 2248
2249 2249 def buildcommittext(repo, ctx, subs, extramsg):
2250 2250 edittext = []
2251 2251 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2252 2252 if ctx.description():
2253 2253 edittext.append(ctx.description())
2254 2254 edittext.append("")
2255 2255 edittext.append("") # Empty line between message and comments.
2256 2256 edittext.append(_("HG: Enter commit message."
2257 2257 " Lines beginning with 'HG:' are removed."))
2258 2258 edittext.append("HG: %s" % extramsg)
2259 2259 edittext.append("HG: --")
2260 2260 edittext.append(_("HG: user: %s") % ctx.user())
2261 2261 if ctx.p2():
2262 2262 edittext.append(_("HG: branch merge"))
2263 2263 if ctx.branch():
2264 2264 edittext.append(_("HG: branch '%s'") % ctx.branch())
2265 2265 if bookmarks.iscurrent(repo):
2266 2266 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2267 2267 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2268 2268 edittext.extend([_("HG: added %s") % f for f in added])
2269 2269 edittext.extend([_("HG: changed %s") % f for f in modified])
2270 2270 edittext.extend([_("HG: removed %s") % f for f in removed])
2271 2271 if not added and not modified and not removed:
2272 2272 edittext.append(_("HG: no files changed"))
2273 2273 edittext.append("")
2274 2274
2275 2275 return "\n".join(edittext)
2276 2276
2277 2277 def commitstatus(repo, node, branch, bheads=None, opts={}):
2278 2278 ctx = repo[node]
2279 2279 parents = ctx.parents()
2280 2280
2281 2281 if (not opts.get('amend') and bheads and node not in bheads and not
2282 2282 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2283 2283 repo.ui.status(_('created new head\n'))
2284 2284 # The message is not printed for initial roots. For the other
2285 2285 # changesets, it is printed in the following situations:
2286 2286 #
2287 2287 # Par column: for the 2 parents with ...
2288 2288 # N: null or no parent
2289 2289 # B: parent is on another named branch
2290 2290 # C: parent is a regular non head changeset
2291 2291 # H: parent was a branch head of the current branch
2292 2292 # Msg column: whether we print "created new head" message
2293 2293 # In the following, it is assumed that there already exists some
2294 2294 # initial branch heads of the current branch, otherwise nothing is
2295 2295 # printed anyway.
2296 2296 #
2297 2297 # Par Msg Comment
2298 2298 # N N y additional topo root
2299 2299 #
2300 2300 # B N y additional branch root
2301 2301 # C N y additional topo head
2302 2302 # H N n usual case
2303 2303 #
2304 2304 # B B y weird additional branch root
2305 2305 # C B y branch merge
2306 2306 # H B n merge with named branch
2307 2307 #
2308 2308 # C C y additional head from merge
2309 2309 # C H n merge with a head
2310 2310 #
2311 2311 # H H n head merge: head count decreases
2312 2312
2313 2313 if not opts.get('close_branch'):
2314 2314 for r in parents:
2315 2315 if r.closesbranch() and r.branch() == branch:
2316 2316 repo.ui.status(_('reopening closed branch head %d\n') % r)
2317 2317
2318 2318 if repo.ui.debugflag:
2319 2319 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2320 2320 elif repo.ui.verbose:
2321 2321 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2322 2322
2323 2323 def revert(ui, repo, ctx, parents, *pats, **opts):
2324 2324 parent, p2 = parents
2325 2325 node = ctx.node()
2326 2326
2327 2327 mf = ctx.manifest()
2328 2328 if node == p2:
2329 2329 parent = p2
2330 2330 if node == parent:
2331 2331 pmf = mf
2332 2332 else:
2333 2333 pmf = None
2334 2334
2335 2335 # need all matching names in dirstate and manifest of target rev,
2336 2336 # so have to walk both. do not print errors if files exist in one
2337 2337 # but not other.
2338 2338
2339 2339 # `names` is a mapping for all elements in working copy and target revision
2340 2340 # The mapping is in the form:
2341 2341 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2342 2342 names = {}
2343 2343
2344 2344 wlock = repo.wlock()
2345 2345 try:
2346 2346 ## filling of the `names` mapping
2347 2347 # walk dirstate to fill `names`
2348 2348
2349 2349 m = scmutil.match(repo[None], pats, opts)
2350 2350 m.bad = lambda x, y: False
2351 2351 for abs in repo.walk(m):
2352 2352 names[abs] = m.rel(abs), m.exact(abs)
2353 2353
2354 2354 # walk target manifest to fill `names`
2355 2355
2356 2356 def badfn(path, msg):
2357 2357 if path in names:
2358 2358 return
2359 2359 if path in ctx.substate:
2360 2360 return
2361 2361 path_ = path + '/'
2362 2362 for f in names:
2363 2363 if f.startswith(path_):
2364 2364 return
2365 2365 ui.warn("%s: %s\n" % (m.rel(path), msg))
2366 2366
2367 2367 m = scmutil.match(ctx, pats, opts)
2368 2368 m.bad = badfn
2369 2369 for abs in ctx.walk(m):
2370 2370 if abs not in names:
2371 2371 names[abs] = m.rel(abs), m.exact(abs)
2372 2372
2373 2373 # get the list of subrepos that must be reverted
2374 2374 targetsubs = sorted(s for s in ctx.substate if m(s))
2375 2375
2376 2376 # Find status of all file in `names`.
2377 2377 m = scmutil.matchfiles(repo, names)
2378 2378
2379 2379 changes = repo.status(node1=node, match=m, clean=True)
2380 2380 modified = set(changes[0])
2381 2381 added = set(changes[1])
2382 2382 removed = set(changes[2])
2383 2383 _deleted = set(changes[3])
2384 2384 clean = set(changes[6])
2385 2385
2386 2386 # split between files known in target manifest and the others
2387 2387 smf = set(mf)
2388 2388
2389 2389 # determine the exact nature of the deleted changesets
2390 2390 _deletedadded = _deleted - smf
2391 2391 _deletedmodified = _deleted - _deletedadded
2392 2392 added |= _deletedadded
2393 2393 modified |= _deletedmodified
2394 2394
2395 2395 # We need to account for the state of file in the dirstate
2396 2396 #
2397 2397 # Even, when we revert agains something else than parent. this will
2398 2398 # slightly alter the behavior of revert (doing back up or not, delete
2399 2399 # or just forget etc)
2400 2400 if parent == node:
2401 2401 dsmodified = modified
2402 2402 dsadded = added
2403 2403 dsremoved = removed
2404 2404 modified, added, removed = set(), set(), set()
2405 2405 else:
2406 2406 changes = repo.status(node1=parent, match=m)
2407 2407 dsmodified = set(changes[0])
2408 2408 dsadded = set(changes[1])
2409 2409 dsremoved = set(changes[2])
2410 2410 dsadded |= _deletedadded
2411 dsmodified |= _deletedmodified
2412 2411
2413 2412 # only take into account for removes between wc and target
2414 2413 clean |= dsremoved - removed
2415 2414 dsremoved &= removed
2416 2415 # distinct between dirstate remove and other
2417 2416 removed -= dsremoved
2418 2417
2418 # tell newly modified apart.
2419 dsmodified &= modified
2420 dsmodified |= modified & dsadded # dirstate added may needs backup
2421 modified -= dsmodified
2422
2419 2423 # if f is a rename, update `names` to also revert the source
2420 2424 cwd = repo.getcwd()
2421 2425 for f in dsadded:
2422 2426 src = repo.dirstate.copied(f)
2423 2427 if src and src not in names and repo.dirstate[src] == 'r':
2424 2428 dsremoved.add(src)
2425 2429 names[src] = (repo.pathto(src, cwd), True)
2426 2430
2427 2431 ## computation of the action to performs on `names` content.
2428 2432
2429 2433 def removeforget(abs):
2430 2434 if repo.dirstate[abs] == 'a':
2431 2435 return _('forgetting %s\n')
2432 2436 return _('removing %s\n')
2433 2437
2434 2438 missingmodified = dsmodified - smf
2435 2439 dsmodified -= missingmodified
2436 2440 missingadded = dsadded - smf
2437 2441 dsadded -= missingadded
2438 2442
2439 2443 # action to be actually performed by revert
2440 2444 # (<list of file>, message>) tuple
2441 2445 actions = {'revert': ([], _('reverting %s\n')),
2442 2446 'add': ([], _('adding %s\n')),
2443 2447 'remove': ([], removeforget),
2444 2448 'undelete': ([], _('undeleting %s\n'))}
2445 2449
2446 2450 disptable = (
2447 2451 # dispatch table:
2448 2452 # file state
2449 2453 # action
2450 2454 # make backup
2455 (modified, (actions['revert'], False)),
2451 2456 (dsmodified, (actions['revert'], True)),
2452 2457 (missingmodified, (actions['remove'], True)),
2453 2458 (dsadded, (actions['revert'], True)),
2454 2459 (missingadded, (actions['remove'], False)),
2455 2460 (removed, (actions['add'], True)),
2456 2461 (dsremoved, (actions['undelete'], True)),
2457 2462 (clean, (None, False)),
2458 2463 )
2459 2464
2460 2465 for abs, (rel, exact) in sorted(names.items()):
2461 2466 # hash on file in target manifest (or None if missing from target)
2462 2467 mfentry = mf.get(abs)
2463 2468 # target file to be touch on disk (relative to cwd)
2464 2469 target = repo.wjoin(abs)
2465 2470 def handle(xlist, dobackup):
2466 2471 xlist[0].append(abs)
2467 2472 if (dobackup and not opts.get('no_backup') and
2468 2473 os.path.lexists(target) and
2469 2474 abs in ctx and repo[None][abs].cmp(ctx[abs])):
2470 2475 bakname = "%s.orig" % rel
2471 2476 ui.note(_('saving current version of %s as %s\n') %
2472 2477 (rel, bakname))
2473 2478 if not opts.get('dry_run'):
2474 2479 util.rename(target, bakname)
2475 2480 if ui.verbose or not exact:
2476 2481 msg = xlist[1]
2477 2482 if not isinstance(msg, basestring):
2478 2483 msg = msg(abs)
2479 2484 ui.status(msg % rel)
2480 2485 # search the entry in the dispatch table.
2481 2486 # if the file is in any of this sets, it was touched in the working
2482 2487 # directory parent and we are sure it needs to be reverted.
2483 2488 for table, (action, backup) in disptable:
2484 2489 if abs not in table:
2485 2490 continue
2486 2491 if action is None:
2487 2492 if exact:
2488 2493 ui.warn(_('no changes needed to %s\n') % rel)
2489 2494
2490 2495 else:
2491 2496 handle(action, backup)
2492 2497 break
2493 2498 else:
2494 2499 # Not touched in current dirstate.
2495 2500
2496 2501 # file is unknown in parent, restore older version or ignore.
2497 2502 if abs not in repo.dirstate:
2498 2503 if exact:
2499 2504 ui.warn(_('file not managed: %s\n') % rel)
2500 2505 continue
2501 2506
2502 2507 # parent is target, no changes mean no changes
2503 2508 if node == parent:
2504 2509 if exact:
2505 2510 ui.warn(_('no changes needed to %s\n') % rel)
2506 2511 continue
2507 2512 # no change in dirstate but parent and target may differ
2508 2513 if pmf is None:
2509 2514 # only need parent manifest in this unlikely case,
2510 2515 # so do not read by default
2511 2516 pmf = repo[parent].manifest()
2512 2517 if abs in pmf and mfentry:
2513 2518 # if version of file is same in parent and target
2514 2519 # manifests, do nothing
2515 2520 if (pmf[abs] != mfentry or
2516 2521 pmf.flags(abs) != mf.flags(abs)):
2517 2522 handle(actions['revert'], False)
2518 2523 else:
2519 2524 handle(actions['remove'], False)
2520 2525
2521 2526 if not opts.get('dry_run'):
2522 2527 _performrevert(repo, parents, ctx, actions)
2523 2528
2524 2529 if targetsubs:
2525 2530 # Revert the subrepos on the revert list
2526 2531 for sub in targetsubs:
2527 2532 ctx.sub(sub).revert(ui, ctx.substate[sub], *pats, **opts)
2528 2533 finally:
2529 2534 wlock.release()
2530 2535
2531 2536 def _performrevert(repo, parents, ctx, actions):
2532 2537 """function that actually perform all the actions computed for revert
2533 2538
2534 2539 This is an independent function to let extension to plug in and react to
2535 2540 the imminent revert.
2536 2541
2537 2542 Make sure you have the working directory locked when calling this function.
2538 2543 """
2539 2544 parent, p2 = parents
2540 2545 node = ctx.node()
2541 2546 def checkout(f):
2542 2547 fc = ctx[f]
2543 2548 repo.wwrite(f, fc.data(), fc.flags())
2544 2549
2545 2550 audit_path = pathutil.pathauditor(repo.root)
2546 2551 for f in actions['remove'][0]:
2547 2552 if repo.dirstate[f] == 'a':
2548 2553 repo.dirstate.drop(f)
2549 2554 continue
2550 2555 audit_path(f)
2551 2556 try:
2552 2557 util.unlinkpath(repo.wjoin(f))
2553 2558 except OSError:
2554 2559 pass
2555 2560 repo.dirstate.remove(f)
2556 2561
2557 2562 normal = None
2558 2563 if node == parent:
2559 2564 # We're reverting to our parent. If possible, we'd like status
2560 2565 # to report the file as clean. We have to use normallookup for
2561 2566 # merges to avoid losing information about merged/dirty files.
2562 2567 if p2 != nullid:
2563 2568 normal = repo.dirstate.normallookup
2564 2569 else:
2565 2570 normal = repo.dirstate.normal
2566 2571 for f in actions['revert'][0]:
2567 2572 checkout(f)
2568 2573 if normal:
2569 2574 normal(f)
2570 2575
2571 2576 for f in actions['add'][0]:
2572 2577 checkout(f)
2573 2578 repo.dirstate.add(f)
2574 2579
2575 2580 normal = repo.dirstate.normallookup
2576 2581 if node == parent and p2 == nullid:
2577 2582 normal = repo.dirstate.normal
2578 2583 for f in actions['undelete'][0]:
2579 2584 checkout(f)
2580 2585 normal(f)
2581 2586
2582 2587 copied = copies.pathcopies(repo[parent], ctx)
2583 2588
2584 2589 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2585 2590 if f in copied:
2586 2591 repo.dirstate.copy(copied[f], f)
2587 2592
2588 2593 def command(table):
2589 2594 """Returns a function object to be used as a decorator for making commands.
2590 2595
2591 2596 This function receives a command table as its argument. The table should
2592 2597 be a dict.
2593 2598
2594 2599 The returned function can be used as a decorator for adding commands
2595 2600 to that command table. This function accepts multiple arguments to define
2596 2601 a command.
2597 2602
2598 2603 The first argument is the command name.
2599 2604
2600 2605 The options argument is an iterable of tuples defining command arguments.
2601 2606 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2602 2607
2603 2608 The synopsis argument defines a short, one line summary of how to use the
2604 2609 command. This shows up in the help output.
2605 2610
2606 2611 The norepo argument defines whether the command does not require a
2607 2612 local repository. Most commands operate against a repository, thus the
2608 2613 default is False.
2609 2614
2610 2615 The optionalrepo argument defines whether the command optionally requires
2611 2616 a local repository.
2612 2617
2613 2618 The inferrepo argument defines whether to try to find a repository from the
2614 2619 command line arguments. If True, arguments will be examined for potential
2615 2620 repository locations. See ``findrepo()``. If a repository is found, it
2616 2621 will be used.
2617 2622 """
2618 2623 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2619 2624 inferrepo=False):
2620 2625 def decorator(func):
2621 2626 if synopsis:
2622 2627 table[name] = func, list(options), synopsis
2623 2628 else:
2624 2629 table[name] = func, list(options)
2625 2630
2626 2631 if norepo:
2627 2632 # Avoid import cycle.
2628 2633 import commands
2629 2634 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2630 2635
2631 2636 if optionalrepo:
2632 2637 import commands
2633 2638 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2634 2639
2635 2640 if inferrepo:
2636 2641 import commands
2637 2642 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2638 2643
2639 2644 return func
2640 2645 return decorator
2641 2646
2642 2647 return cmd
2643 2648
2644 2649 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2645 2650 # commands.outgoing. "missing" is "missing" of the result of
2646 2651 # "findcommonoutgoing()"
2647 2652 outgoinghooks = util.hooks()
2648 2653
2649 2654 # a list of (ui, repo) functions called by commands.summary
2650 2655 summaryhooks = util.hooks()
2651 2656
2652 2657 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2653 2658 #
2654 2659 # functions should return tuple of booleans below, if 'changes' is None:
2655 2660 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2656 2661 #
2657 2662 # otherwise, 'changes' is a tuple of tuples below:
2658 2663 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2659 2664 # - (desturl, destbranch, destpeer, outgoing)
2660 2665 summaryremotehooks = util.hooks()
2661 2666
2662 2667 # A list of state files kept by multistep operations like graft.
2663 2668 # Since graft cannot be aborted, it is considered 'clearable' by update.
2664 2669 # note: bisect is intentionally excluded
2665 2670 # (state file, clearable, allowcommit, error, hint)
2666 2671 unfinishedstates = [
2667 2672 ('graftstate', True, False, _('graft in progress'),
2668 2673 _("use 'hg graft --continue' or 'hg update' to abort")),
2669 2674 ('updatestate', True, False, _('last update was interrupted'),
2670 2675 _("use 'hg update' to get a consistent checkout"))
2671 2676 ]
2672 2677
2673 2678 def checkunfinished(repo, commit=False):
2674 2679 '''Look for an unfinished multistep operation, like graft, and abort
2675 2680 if found. It's probably good to check this right before
2676 2681 bailifchanged().
2677 2682 '''
2678 2683 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2679 2684 if commit and allowcommit:
2680 2685 continue
2681 2686 if repo.vfs.exists(f):
2682 2687 raise util.Abort(msg, hint=hint)
2683 2688
2684 2689 def clearunfinished(repo):
2685 2690 '''Check for unfinished operations (as above), and clear the ones
2686 2691 that are clearable.
2687 2692 '''
2688 2693 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2689 2694 if not clearable and repo.vfs.exists(f):
2690 2695 raise util.Abort(msg, hint=hint)
2691 2696 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2692 2697 if clearable and repo.vfs.exists(f):
2693 2698 util.unlink(repo.join(f))
@@ -1,1825 +1,1824 b''
1 1 This file used to contains all largefile tests.
2 2 Do not add any new tests in this file as it his already far too long to run.
3 3
4 4 It contains all the testing of the basic concepts of large file in a single block.
5 5
6 6 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
7 7 $ mkdir "${USERCACHE}"
8 8 $ cat >> $HGRCPATH <<EOF
9 9 > [extensions]
10 10 > largefiles=
11 11 > purge=
12 12 > rebase=
13 13 > transplant=
14 14 > [phases]
15 15 > publish=False
16 16 > [largefiles]
17 17 > minsize=2
18 18 > patterns=glob:**.dat
19 19 > usercache=${USERCACHE}
20 20 > [hooks]
21 21 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
22 22 > EOF
23 23
24 24 Create the repo with a couple of revisions of both large and normal
25 25 files.
26 26 Test status and dirstate of largefiles and that summary output is correct.
27 27
28 28 $ hg init a
29 29 $ cd a
30 30 $ mkdir sub
31 31 $ echo normal1 > normal1
32 32 $ echo normal2 > sub/normal2
33 33 $ echo large1 > large1
34 34 $ echo large2 > sub/large2
35 35 $ hg add normal1 sub/normal2
36 36 $ hg add --large large1 sub/large2
37 37 $ hg commit -m "add files"
38 38 Invoking status precommit hook
39 39 A large1
40 40 A normal1
41 41 A sub/large2
42 42 A sub/normal2
43 43 $ touch large1 sub/large2
44 44 $ sleep 1
45 45 $ hg st
46 46 $ hg debugstate --nodates
47 47 n 644 41 .hglf/large1
48 48 n 644 41 .hglf/sub/large2
49 49 n 644 8 normal1
50 50 n 644 8 sub/normal2
51 51 $ hg debugstate --large --nodates
52 52 n 644 7 large1
53 53 n 644 7 sub/large2
54 54 $ echo normal11 > normal1
55 55 $ echo normal22 > sub/normal2
56 56 $ echo large11 > large1
57 57 $ echo large22 > sub/large2
58 58 $ hg commit -m "edit files"
59 59 Invoking status precommit hook
60 60 M large1
61 61 M normal1
62 62 M sub/large2
63 63 M sub/normal2
64 64 $ hg sum --large
65 65 parent: 1:ce8896473775 tip
66 66 edit files
67 67 branch: default
68 68 commit: (clean)
69 69 update: (current)
70 70 largefiles: (no remote repo)
71 71
72 72 Commit preserved largefile contents.
73 73
74 74 $ cat normal1
75 75 normal11
76 76 $ cat large1
77 77 large11
78 78 $ cat sub/normal2
79 79 normal22
80 80 $ cat sub/large2
81 81 large22
82 82
83 83 Test status, subdir and unknown files
84 84
85 85 $ echo unknown > sub/unknown
86 86 $ hg st --all
87 87 ? sub/unknown
88 88 C large1
89 89 C normal1
90 90 C sub/large2
91 91 C sub/normal2
92 92 $ hg st --all sub
93 93 ? sub/unknown
94 94 C sub/large2
95 95 C sub/normal2
96 96 $ rm sub/unknown
97 97
98 98 Test messages and exit codes for remove warning cases
99 99
100 100 $ hg remove -A large1
101 101 not removing large1: file still exists
102 102 [1]
103 103 $ echo 'modified' > large1
104 104 $ hg remove large1
105 105 not removing large1: file is modified (use -f to force removal)
106 106 [1]
107 107 $ echo 'new' > normalnew
108 108 $ hg add normalnew
109 109 $ echo 'new' > largenew
110 110 $ hg add --large normalnew
111 111 normalnew already tracked!
112 112 $ hg remove normalnew largenew
113 113 not removing largenew: file is untracked
114 114 not removing normalnew: file has been marked for add (use forget to undo)
115 115 [1]
116 116 $ rm normalnew largenew
117 117 $ hg up -Cq
118 118
119 119 Remove both largefiles and normal files.
120 120
121 121 $ hg remove normal1 large1
122 122 $ hg status large1
123 123 R large1
124 124 $ hg commit -m "remove files"
125 125 Invoking status precommit hook
126 126 R large1
127 127 R normal1
128 128 $ ls
129 129 sub
130 130 $ echo "testlargefile" > large1-test
131 131 $ hg add --large large1-test
132 132 $ hg st
133 133 A large1-test
134 134 $ hg rm large1-test
135 135 not removing large1-test: file has been marked for add (use forget to undo)
136 136 [1]
137 137 $ hg st
138 138 A large1-test
139 139 $ hg forget large1-test
140 140 $ hg st
141 141 ? large1-test
142 142 $ hg remove large1-test
143 143 not removing large1-test: file is untracked
144 144 [1]
145 145 $ hg forget large1-test
146 146 not removing large1-test: file is already untracked
147 147 [1]
148 148 $ rm large1-test
149 149
150 150 Copy both largefiles and normal files (testing that status output is correct).
151 151
152 152 $ hg cp sub/normal2 normal1
153 153 $ hg cp sub/large2 large1
154 154 $ hg commit -m "copy files"
155 155 Invoking status precommit hook
156 156 A large1
157 157 A normal1
158 158 $ cat normal1
159 159 normal22
160 160 $ cat large1
161 161 large22
162 162
163 163 Test moving largefiles and verify that normal files are also unaffected.
164 164
165 165 $ hg mv normal1 normal3
166 166 $ hg mv large1 large3
167 167 $ hg mv sub/normal2 sub/normal4
168 168 $ hg mv sub/large2 sub/large4
169 169 $ hg commit -m "move files"
170 170 Invoking status precommit hook
171 171 A large3
172 172 A normal3
173 173 A sub/large4
174 174 A sub/normal4
175 175 R large1
176 176 R normal1
177 177 R sub/large2
178 178 R sub/normal2
179 179 $ cat normal3
180 180 normal22
181 181 $ cat large3
182 182 large22
183 183 $ cat sub/normal4
184 184 normal22
185 185 $ cat sub/large4
186 186 large22
187 187
188 188
189 189 #if serve
190 190 Test display of largefiles in hgweb
191 191
192 192 $ hg serve -d -p $HGPORT --pid-file ../hg.pid
193 193 $ cat ../hg.pid >> $DAEMON_PIDS
194 194 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/?style=raw'
195 195 200 Script output follows
196 196
197 197
198 198 drwxr-xr-x sub
199 199 -rw-r--r-- 41 large3
200 200 -rw-r--r-- 9 normal3
201 201
202 202
203 203 $ "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT 'file/tip/sub/?style=raw'
204 204 200 Script output follows
205 205
206 206
207 207 -rw-r--r-- 41 large4
208 208 -rw-r--r-- 9 normal4
209 209
210 210
211 211 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
212 212 #endif
213 213
214 214 Test archiving the various revisions. These hit corner cases known with
215 215 archiving.
216 216
217 217 $ hg archive -r 0 ../archive0
218 218 $ hg archive -r 1 ../archive1
219 219 $ hg archive -r 2 ../archive2
220 220 $ hg archive -r 3 ../archive3
221 221 $ hg archive -r 4 ../archive4
222 222 $ cd ../archive0
223 223 $ cat normal1
224 224 normal1
225 225 $ cat large1
226 226 large1
227 227 $ cat sub/normal2
228 228 normal2
229 229 $ cat sub/large2
230 230 large2
231 231 $ cd ../archive1
232 232 $ cat normal1
233 233 normal11
234 234 $ cat large1
235 235 large11
236 236 $ cat sub/normal2
237 237 normal22
238 238 $ cat sub/large2
239 239 large22
240 240 $ cd ../archive2
241 241 $ ls
242 242 sub
243 243 $ cat sub/normal2
244 244 normal22
245 245 $ cat sub/large2
246 246 large22
247 247 $ cd ../archive3
248 248 $ cat normal1
249 249 normal22
250 250 $ cat large1
251 251 large22
252 252 $ cat sub/normal2
253 253 normal22
254 254 $ cat sub/large2
255 255 large22
256 256 $ cd ../archive4
257 257 $ cat normal3
258 258 normal22
259 259 $ cat large3
260 260 large22
261 261 $ cat sub/normal4
262 262 normal22
263 263 $ cat sub/large4
264 264 large22
265 265
266 266 Commit corner case: specify files to commit.
267 267
268 268 $ cd ../a
269 269 $ echo normal3 > normal3
270 270 $ echo large3 > large3
271 271 $ echo normal4 > sub/normal4
272 272 $ echo large4 > sub/large4
273 273 $ hg commit normal3 large3 sub/normal4 sub/large4 -m "edit files again"
274 274 Invoking status precommit hook
275 275 M large3
276 276 M normal3
277 277 M sub/large4
278 278 M sub/normal4
279 279 $ cat normal3
280 280 normal3
281 281 $ cat large3
282 282 large3
283 283 $ cat sub/normal4
284 284 normal4
285 285 $ cat sub/large4
286 286 large4
287 287
288 288 One more commit corner case: commit from a subdirectory.
289 289
290 290 $ cd ../a
291 291 $ echo normal33 > normal3
292 292 $ echo large33 > large3
293 293 $ echo normal44 > sub/normal4
294 294 $ echo large44 > sub/large4
295 295 $ cd sub
296 296 $ hg commit -m "edit files yet again"
297 297 Invoking status precommit hook
298 298 M large3
299 299 M normal3
300 300 M sub/large4
301 301 M sub/normal4
302 302 $ cat ../normal3
303 303 normal33
304 304 $ cat ../large3
305 305 large33
306 306 $ cat normal4
307 307 normal44
308 308 $ cat large4
309 309 large44
310 310
311 311 Committing standins is not allowed.
312 312
313 313 $ cd ..
314 314 $ echo large3 > large3
315 315 $ hg commit .hglf/large3 -m "try to commit standin"
316 316 abort: file ".hglf/large3" is a largefile standin
317 317 (commit the largefile itself instead)
318 318 [255]
319 319
320 320 Corner cases for adding largefiles.
321 321
322 322 $ echo large5 > large5
323 323 $ hg add --large large5
324 324 $ hg add --large large5
325 325 large5 already a largefile
326 326 $ mkdir sub2
327 327 $ echo large6 > sub2/large6
328 328 $ echo large7 > sub2/large7
329 329 $ hg add --large sub2
330 330 adding sub2/large6 as a largefile (glob)
331 331 adding sub2/large7 as a largefile (glob)
332 332 $ hg st
333 333 M large3
334 334 A large5
335 335 A sub2/large6
336 336 A sub2/large7
337 337
338 338 Committing directories containing only largefiles.
339 339
340 340 $ mkdir -p z/y/x/m
341 341 $ touch z/y/x/m/large1
342 342 $ touch z/y/x/large2
343 343 $ hg add --large z/y/x/m/large1 z/y/x/large2
344 344 $ hg commit -m "Subdir with directory only containing largefiles" z
345 345 Invoking status precommit hook
346 346 M large3
347 347 A large5
348 348 A sub2/large6
349 349 A sub2/large7
350 350 A z/y/x/large2
351 351 A z/y/x/m/large1
352 352
353 353 (and a bit of log testing)
354 354
355 355 $ hg log -T '{rev}\n' z/y/x/m/large1
356 356 7
357 357 $ hg log -T '{rev}\n' z/y/x/m # with only a largefile
358 358 7
359 359
360 360 $ hg rollback --quiet
361 361 $ touch z/y/x/m/normal
362 362 $ hg add z/y/x/m/normal
363 363 $ hg commit -m "Subdir with mixed contents" z
364 364 Invoking status precommit hook
365 365 M large3
366 366 A large5
367 367 A sub2/large6
368 368 A sub2/large7
369 369 A z/y/x/large2
370 370 A z/y/x/m/large1
371 371 A z/y/x/m/normal
372 372 $ hg st
373 373 M large3
374 374 A large5
375 375 A sub2/large6
376 376 A sub2/large7
377 377 $ hg rollback --quiet
378 378 $ hg revert z/y/x/large2 z/y/x/m/large1
379 379 $ rm z/y/x/large2 z/y/x/m/large1
380 380 $ hg commit -m "Subdir with normal contents" z
381 381 Invoking status precommit hook
382 382 M large3
383 383 A large5
384 384 A sub2/large6
385 385 A sub2/large7
386 386 A z/y/x/m/normal
387 387 $ hg st
388 388 M large3
389 389 A large5
390 390 A sub2/large6
391 391 A sub2/large7
392 392 $ hg rollback --quiet
393 393 $ hg revert --quiet z
394 394 $ hg commit -m "Empty subdir" z
395 395 abort: z: no match under directory!
396 396 [255]
397 397 $ rm -rf z
398 398 $ hg ci -m "standin" .hglf
399 399 abort: file ".hglf" is a largefile standin
400 400 (commit the largefile itself instead)
401 401 [255]
402 402
403 403 Test "hg status" with combination of 'file pattern' and 'directory
404 404 pattern' for largefiles:
405 405
406 406 $ hg status sub2/large6 sub2
407 407 A sub2/large6
408 408 A sub2/large7
409 409
410 410 Config settings (pattern **.dat, minsize 2 MB) are respected.
411 411
412 412 $ echo testdata > test.dat
413 413 $ dd bs=1k count=2k if=/dev/zero of=reallylarge > /dev/null 2> /dev/null
414 414 $ hg add
415 415 adding reallylarge as a largefile
416 416 adding test.dat as a largefile
417 417
418 418 Test that minsize and --lfsize handle float values;
419 419 also tests that --lfsize overrides largefiles.minsize.
420 420 (0.250 MB = 256 kB = 262144 B)
421 421
422 422 $ dd if=/dev/zero of=ratherlarge bs=1024 count=256 > /dev/null 2> /dev/null
423 423 $ dd if=/dev/zero of=medium bs=1024 count=128 > /dev/null 2> /dev/null
424 424 $ hg --config largefiles.minsize=.25 add
425 425 adding ratherlarge as a largefile
426 426 adding medium
427 427 $ hg forget medium
428 428 $ hg --config largefiles.minsize=.25 add --lfsize=.125
429 429 adding medium as a largefile
430 430 $ dd if=/dev/zero of=notlarge bs=1024 count=127 > /dev/null 2> /dev/null
431 431 $ hg --config largefiles.minsize=.25 add --lfsize=.125
432 432 adding notlarge
433 433 $ hg forget notlarge
434 434
435 435 Test forget on largefiles.
436 436
437 437 $ hg forget large3 large5 test.dat reallylarge ratherlarge medium
438 438 $ hg commit -m "add/edit more largefiles"
439 439 Invoking status precommit hook
440 440 A sub2/large6
441 441 A sub2/large7
442 442 R large3
443 443 ? large5
444 444 ? medium
445 445 ? notlarge
446 446 ? ratherlarge
447 447 ? reallylarge
448 448 ? test.dat
449 449 $ hg st
450 450 ? large3
451 451 ? large5
452 452 ? medium
453 453 ? notlarge
454 454 ? ratherlarge
455 455 ? reallylarge
456 456 ? test.dat
457 457
458 458 Purge with largefiles: verify that largefiles are still in the working
459 459 dir after a purge.
460 460
461 461 $ hg purge --all
462 462 $ cat sub/large4
463 463 large44
464 464 $ cat sub2/large6
465 465 large6
466 466 $ cat sub2/large7
467 467 large7
468 468
469 469 Test addremove: verify that files that should be added as largefiles are added as
470 470 such and that already-existing largefiles are not added as normal files by
471 471 accident.
472 472
473 473 $ rm normal3
474 474 $ rm sub/large4
475 475 $ echo "testing addremove with patterns" > testaddremove.dat
476 476 $ echo "normaladdremove" > normaladdremove
477 477 $ hg addremove
478 478 removing sub/large4
479 479 adding testaddremove.dat as a largefile
480 480 removing normal3
481 481 adding normaladdremove
482 482
483 483 Test addremove with -R
484 484
485 485 $ hg up -C
486 486 getting changed largefiles
487 487 1 largefiles updated, 0 removed
488 488 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 489 $ rm normal3
490 490 $ rm sub/large4
491 491 $ echo "testing addremove with patterns" > testaddremove.dat
492 492 $ echo "normaladdremove" > normaladdremove
493 493 $ cd ..
494 494 $ hg -R a addremove
495 495 removing sub/large4
496 496 adding a/testaddremove.dat as a largefile (glob)
497 497 removing normal3
498 498 adding normaladdremove
499 499 $ cd a
500 500
501 501 Test 3364
502 502 $ hg clone . ../addrm
503 503 updating to branch default
504 504 getting changed largefiles
505 505 3 largefiles updated, 0 removed
506 506 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
507 507 $ cd ../addrm
508 508 $ cat >> .hg/hgrc <<EOF
509 509 > [hooks]
510 510 > post-commit.stat=sh -c "echo \\"Invoking status postcommit hook\\"; hg status -A"
511 511 > EOF
512 512 $ touch foo
513 513 $ hg add --large foo
514 514 $ hg ci -m "add foo"
515 515 Invoking status precommit hook
516 516 A foo
517 517 Invoking status postcommit hook
518 518 C foo
519 519 C normal3
520 520 C sub/large4
521 521 C sub/normal4
522 522 C sub2/large6
523 523 C sub2/large7
524 524 $ rm foo
525 525 $ hg st
526 526 ! foo
527 527 hmm.. no precommit invoked, but there is a postcommit??
528 528 $ hg ci -m "will not checkin"
529 529 nothing changed
530 530 Invoking status postcommit hook
531 531 ! foo
532 532 C normal3
533 533 C sub/large4
534 534 C sub/normal4
535 535 C sub2/large6
536 536 C sub2/large7
537 537 [1]
538 538 $ hg addremove
539 539 removing foo
540 540 $ hg st
541 541 R foo
542 542 $ hg ci -m "used to say nothing changed"
543 543 Invoking status precommit hook
544 544 R foo
545 545 Invoking status postcommit hook
546 546 C normal3
547 547 C sub/large4
548 548 C sub/normal4
549 549 C sub2/large6
550 550 C sub2/large7
551 551 $ hg st
552 552
553 553 Test 3507 (both normal files and largefiles were a problem)
554 554
555 555 $ touch normal
556 556 $ touch large
557 557 $ hg add normal
558 558 $ hg add --large large
559 559 $ hg ci -m "added"
560 560 Invoking status precommit hook
561 561 A large
562 562 A normal
563 563 Invoking status postcommit hook
564 564 C large
565 565 C normal
566 566 C normal3
567 567 C sub/large4
568 568 C sub/normal4
569 569 C sub2/large6
570 570 C sub2/large7
571 571 $ hg remove normal
572 572 $ hg addremove --traceback
573 573 $ hg ci -m "addremoved normal"
574 574 Invoking status precommit hook
575 575 R normal
576 576 Invoking status postcommit hook
577 577 C large
578 578 C normal3
579 579 C sub/large4
580 580 C sub/normal4
581 581 C sub2/large6
582 582 C sub2/large7
583 583 $ hg up -C '.^'
584 584 getting changed largefiles
585 585 0 largefiles updated, 0 removed
586 586 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 587 $ hg remove large
588 588 $ hg addremove --traceback
589 589 $ hg ci -m "removed large"
590 590 Invoking status precommit hook
591 591 R large
592 592 created new head
593 593 Invoking status postcommit hook
594 594 C normal
595 595 C normal3
596 596 C sub/large4
597 597 C sub/normal4
598 598 C sub2/large6
599 599 C sub2/large7
600 600
601 601 Test commit -A (issue3542)
602 602 $ echo large8 > large8
603 603 $ hg add --large large8
604 604 $ hg ci -Am 'this used to add large8 as normal and commit both'
605 605 Invoking status precommit hook
606 606 A large8
607 607 Invoking status postcommit hook
608 608 C large8
609 609 C normal
610 610 C normal3
611 611 C sub/large4
612 612 C sub/normal4
613 613 C sub2/large6
614 614 C sub2/large7
615 615 $ rm large8
616 616 $ hg ci -Am 'this used to not notice the rm'
617 617 removing large8
618 618 Invoking status precommit hook
619 619 R large8
620 620 Invoking status postcommit hook
621 621 C normal
622 622 C normal3
623 623 C sub/large4
624 624 C sub/normal4
625 625 C sub2/large6
626 626 C sub2/large7
627 627
628 628 Test that a standin can't be added as a large file
629 629
630 630 $ touch large
631 631 $ hg add --large large
632 632 $ hg ci -m "add"
633 633 Invoking status precommit hook
634 634 A large
635 635 Invoking status postcommit hook
636 636 C large
637 637 C normal
638 638 C normal3
639 639 C sub/large4
640 640 C sub/normal4
641 641 C sub2/large6
642 642 C sub2/large7
643 643 $ hg remove large
644 644 $ touch large
645 645 $ hg addremove --config largefiles.patterns=**large --traceback
646 646 adding large as a largefile
647 647
648 648 Test that outgoing --large works (with revsets too)
649 649 $ hg outgoing --rev '.^' --large
650 650 comparing with $TESTTMP/a (glob)
651 651 searching for changes
652 652 changeset: 8:c02fd3b77ec4
653 653 user: test
654 654 date: Thu Jan 01 00:00:00 1970 +0000
655 655 summary: add foo
656 656
657 657 changeset: 9:289dd08c9bbb
658 658 user: test
659 659 date: Thu Jan 01 00:00:00 1970 +0000
660 660 summary: used to say nothing changed
661 661
662 662 changeset: 10:34f23ac6ac12
663 663 user: test
664 664 date: Thu Jan 01 00:00:00 1970 +0000
665 665 summary: added
666 666
667 667 changeset: 12:710c1b2f523c
668 668 parent: 10:34f23ac6ac12
669 669 user: test
670 670 date: Thu Jan 01 00:00:00 1970 +0000
671 671 summary: removed large
672 672
673 673 changeset: 13:0a3e75774479
674 674 user: test
675 675 date: Thu Jan 01 00:00:00 1970 +0000
676 676 summary: this used to add large8 as normal and commit both
677 677
678 678 changeset: 14:84f3d378175c
679 679 user: test
680 680 date: Thu Jan 01 00:00:00 1970 +0000
681 681 summary: this used to not notice the rm
682 682
683 683 largefiles to upload (1 entities):
684 684 large8
685 685
686 686 $ cd ../a
687 687
688 688 Clone a largefiles repo.
689 689
690 690 $ hg clone . ../b
691 691 updating to branch default
692 692 getting changed largefiles
693 693 3 largefiles updated, 0 removed
694 694 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
695 695 $ cd ../b
696 696 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
697 697 7:daea875e9014 add/edit more largefiles
698 698 6:4355d653f84f edit files yet again
699 699 5:9d5af5072dbd edit files again
700 700 4:74c02385b94c move files
701 701 3:9e8fbc4bce62 copy files
702 702 2:51a0ae4d5864 remove files
703 703 1:ce8896473775 edit files
704 704 0:30d30fe6a5be add files
705 705 $ cat normal3
706 706 normal33
707 707
708 708 Test graph log
709 709
710 710 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
711 711 @ 7:daea875e9014 add/edit more largefiles
712 712 |
713 713 o 6:4355d653f84f edit files yet again
714 714 |
715 715 o 5:9d5af5072dbd edit files again
716 716 |
717 717 o 4:74c02385b94c move files
718 718 |
719 719 o 3:9e8fbc4bce62 copy files
720 720 |
721 721 o 2:51a0ae4d5864 remove files
722 722 |
723 723 o 1:ce8896473775 edit files
724 724 |
725 725 o 0:30d30fe6a5be add files
726 726
727 727
728 728 Test log with --patch
729 729
730 730 $ hg log --patch -r 6::7
731 731 changeset: 6:4355d653f84f
732 732 user: test
733 733 date: Thu Jan 01 00:00:00 1970 +0000
734 734 summary: edit files yet again
735 735
736 736 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
737 737 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
738 738 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
739 739 @@ -1,1 +1,1 @@
740 740 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
741 741 +7838695e10da2bb75ac1156565f40a2595fa2fa0
742 742 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
743 743 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
744 744 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
745 745 @@ -1,1 +1,1 @@
746 746 -aeb2210d19f02886dde00dac279729a48471e2f9
747 747 +971fb41e78fea4f8e0ba5244784239371cb00591
748 748 diff -r 9d5af5072dbd -r 4355d653f84f normal3
749 749 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
750 750 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
751 751 @@ -1,1 +1,1 @@
752 752 -normal3
753 753 +normal33
754 754 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
755 755 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
756 756 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
757 757 @@ -1,1 +1,1 @@
758 758 -normal4
759 759 +normal44
760 760
761 761 changeset: 7:daea875e9014
762 762 tag: tip
763 763 user: test
764 764 date: Thu Jan 01 00:00:00 1970 +0000
765 765 summary: add/edit more largefiles
766 766
767 767 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
768 768 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
769 769 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
770 770 @@ -1,1 +0,0 @@
771 771 -7838695e10da2bb75ac1156565f40a2595fa2fa0
772 772 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
773 773 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
774 774 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
775 775 @@ -0,0 +1,1 @@
776 776 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
777 777 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
778 778 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
779 779 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
780 780 @@ -0,0 +1,1 @@
781 781 +bb3151689acb10f0c3125c560d5e63df914bc1af
782 782
783 783
784 784 $ hg log --patch -r 6::7 sub/
785 785 changeset: 6:4355d653f84f
786 786 user: test
787 787 date: Thu Jan 01 00:00:00 1970 +0000
788 788 summary: edit files yet again
789 789
790 790 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
791 791 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
792 792 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
793 793 @@ -1,1 +1,1 @@
794 794 -aeb2210d19f02886dde00dac279729a48471e2f9
795 795 +971fb41e78fea4f8e0ba5244784239371cb00591
796 796 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
797 797 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
798 798 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
799 799 @@ -1,1 +1,1 @@
800 800 -normal4
801 801 +normal44
802 802
803 803
804 804 log with both --follow and --patch
805 805
806 806 $ hg log --follow --patch --limit 2
807 807 changeset: 7:daea875e9014
808 808 tag: tip
809 809 user: test
810 810 date: Thu Jan 01 00:00:00 1970 +0000
811 811 summary: add/edit more largefiles
812 812
813 813 diff -r 4355d653f84f -r daea875e9014 .hglf/large3
814 814 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
815 815 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
816 816 @@ -1,1 +0,0 @@
817 817 -7838695e10da2bb75ac1156565f40a2595fa2fa0
818 818 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large6
819 819 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
820 820 +++ b/.hglf/sub2/large6 Thu Jan 01 00:00:00 1970 +0000
821 821 @@ -0,0 +1,1 @@
822 822 +0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30
823 823 diff -r 4355d653f84f -r daea875e9014 .hglf/sub2/large7
824 824 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
825 825 +++ b/.hglf/sub2/large7 Thu Jan 01 00:00:00 1970 +0000
826 826 @@ -0,0 +1,1 @@
827 827 +bb3151689acb10f0c3125c560d5e63df914bc1af
828 828
829 829 changeset: 6:4355d653f84f
830 830 user: test
831 831 date: Thu Jan 01 00:00:00 1970 +0000
832 832 summary: edit files yet again
833 833
834 834 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/large3
835 835 --- a/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
836 836 +++ b/.hglf/large3 Thu Jan 01 00:00:00 1970 +0000
837 837 @@ -1,1 +1,1 @@
838 838 -baaf12afde9d8d67f25dab6dced0d2bf77dba47c
839 839 +7838695e10da2bb75ac1156565f40a2595fa2fa0
840 840 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
841 841 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
842 842 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
843 843 @@ -1,1 +1,1 @@
844 844 -aeb2210d19f02886dde00dac279729a48471e2f9
845 845 +971fb41e78fea4f8e0ba5244784239371cb00591
846 846 diff -r 9d5af5072dbd -r 4355d653f84f normal3
847 847 --- a/normal3 Thu Jan 01 00:00:00 1970 +0000
848 848 +++ b/normal3 Thu Jan 01 00:00:00 1970 +0000
849 849 @@ -1,1 +1,1 @@
850 850 -normal3
851 851 +normal33
852 852 diff -r 9d5af5072dbd -r 4355d653f84f sub/normal4
853 853 --- a/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
854 854 +++ b/sub/normal4 Thu Jan 01 00:00:00 1970 +0000
855 855 @@ -1,1 +1,1 @@
856 856 -normal4
857 857 +normal44
858 858
859 859 $ hg log --follow --patch sub/large4
860 860 changeset: 6:4355d653f84f
861 861 user: test
862 862 date: Thu Jan 01 00:00:00 1970 +0000
863 863 summary: edit files yet again
864 864
865 865 diff -r 9d5af5072dbd -r 4355d653f84f .hglf/sub/large4
866 866 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
867 867 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
868 868 @@ -1,1 +1,1 @@
869 869 -aeb2210d19f02886dde00dac279729a48471e2f9
870 870 +971fb41e78fea4f8e0ba5244784239371cb00591
871 871
872 872 changeset: 5:9d5af5072dbd
873 873 user: test
874 874 date: Thu Jan 01 00:00:00 1970 +0000
875 875 summary: edit files again
876 876
877 877 diff -r 74c02385b94c -r 9d5af5072dbd .hglf/sub/large4
878 878 --- a/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
879 879 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
880 880 @@ -1,1 +1,1 @@
881 881 -eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
882 882 +aeb2210d19f02886dde00dac279729a48471e2f9
883 883
884 884 changeset: 4:74c02385b94c
885 885 user: test
886 886 date: Thu Jan 01 00:00:00 1970 +0000
887 887 summary: move files
888 888
889 889 diff -r 9e8fbc4bce62 -r 74c02385b94c .hglf/sub/large4
890 890 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
891 891 +++ b/.hglf/sub/large4 Thu Jan 01 00:00:00 1970 +0000
892 892 @@ -0,0 +1,1 @@
893 893 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
894 894
895 895 changeset: 1:ce8896473775
896 896 user: test
897 897 date: Thu Jan 01 00:00:00 1970 +0000
898 898 summary: edit files
899 899
900 900 diff -r 30d30fe6a5be -r ce8896473775 .hglf/sub/large2
901 901 --- a/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
902 902 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
903 903 @@ -1,1 +1,1 @@
904 904 -1deebade43c8c498a3c8daddac0244dc55d1331d
905 905 +eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
906 906
907 907 changeset: 0:30d30fe6a5be
908 908 user: test
909 909 date: Thu Jan 01 00:00:00 1970 +0000
910 910 summary: add files
911 911
912 912 diff -r 000000000000 -r 30d30fe6a5be .hglf/sub/large2
913 913 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
914 914 +++ b/.hglf/sub/large2 Thu Jan 01 00:00:00 1970 +0000
915 915 @@ -0,0 +1,1 @@
916 916 +1deebade43c8c498a3c8daddac0244dc55d1331d
917 917
918 918 $ cat sub/normal4
919 919 normal44
920 920 $ cat sub/large4
921 921 large44
922 922 $ cat sub2/large6
923 923 large6
924 924 $ cat sub2/large7
925 925 large7
926 926 $ hg log -qf sub2/large7
927 927 7:daea875e9014
928 928 $ hg log -Gqf sub2/large7
929 929 @ 7:daea875e9014
930 930 |
931 931 $ cd ..
932 932
933 933 Test log from outside repo
934 934
935 935 $ hg log b/sub -T '{rev}:{node|short} {desc|firstline}\n'
936 936 6:4355d653f84f edit files yet again
937 937 5:9d5af5072dbd edit files again
938 938 4:74c02385b94c move files
939 939 1:ce8896473775 edit files
940 940 0:30d30fe6a5be add files
941 941
942 942 Test clone at revision
943 943
944 944 $ hg clone a -r 3 c
945 945 adding changesets
946 946 adding manifests
947 947 adding file changes
948 948 added 4 changesets with 10 changes to 4 files
949 949 updating to branch default
950 950 getting changed largefiles
951 951 2 largefiles updated, 0 removed
952 952 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
953 953 $ cd c
954 954 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
955 955 3:9e8fbc4bce62 copy files
956 956 2:51a0ae4d5864 remove files
957 957 1:ce8896473775 edit files
958 958 0:30d30fe6a5be add files
959 959 $ cat normal1
960 960 normal22
961 961 $ cat large1
962 962 large22
963 963 $ cat sub/normal2
964 964 normal22
965 965 $ cat sub/large2
966 966 large22
967 967
968 968 Old revisions of a clone have correct largefiles content (this also
969 969 tests update).
970 970
971 971 $ hg update -r 1
972 972 getting changed largefiles
973 973 1 largefiles updated, 0 removed
974 974 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
975 975 $ cat large1
976 976 large11
977 977 $ cat sub/large2
978 978 large22
979 979 $ cd ..
980 980
981 981 Test cloning with --all-largefiles flag
982 982
983 983 $ rm "${USERCACHE}"/*
984 984 $ hg clone --all-largefiles a a-backup
985 985 updating to branch default
986 986 getting changed largefiles
987 987 3 largefiles updated, 0 removed
988 988 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
989 989 8 additional largefiles cached
990 990
991 991 $ rm "${USERCACHE}"/*
992 992 $ hg clone --all-largefiles -u 0 a a-clone0
993 993 updating to branch default
994 994 getting changed largefiles
995 995 2 largefiles updated, 0 removed
996 996 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
997 997 9 additional largefiles cached
998 998 $ hg -R a-clone0 sum
999 999 parent: 0:30d30fe6a5be
1000 1000 add files
1001 1001 branch: default
1002 1002 commit: (clean)
1003 1003 update: 7 new changesets (update)
1004 1004
1005 1005 $ rm "${USERCACHE}"/*
1006 1006 $ hg clone --all-largefiles -u 1 a a-clone1
1007 1007 updating to branch default
1008 1008 getting changed largefiles
1009 1009 2 largefiles updated, 0 removed
1010 1010 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1011 1011 8 additional largefiles cached
1012 1012 $ hg -R a-clone1 verify --large --lfa --lfc
1013 1013 checking changesets
1014 1014 checking manifests
1015 1015 crosschecking files in changesets and manifests
1016 1016 checking files
1017 1017 10 files, 8 changesets, 24 total revisions
1018 1018 searching 8 changesets for largefiles
1019 1019 verified contents of 13 revisions of 6 largefiles
1020 1020 $ hg -R a-clone1 sum
1021 1021 parent: 1:ce8896473775
1022 1022 edit files
1023 1023 branch: default
1024 1024 commit: (clean)
1025 1025 update: 6 new changesets (update)
1026 1026
1027 1027 $ rm "${USERCACHE}"/*
1028 1028 $ hg clone --all-largefiles -U a a-clone-u
1029 1029 11 additional largefiles cached
1030 1030 $ hg -R a-clone-u sum
1031 1031 parent: -1:000000000000 (no revision checked out)
1032 1032 branch: default
1033 1033 commit: (clean)
1034 1034 update: 8 new changesets (update)
1035 1035
1036 1036 Show computed destination directory:
1037 1037
1038 1038 $ mkdir xyz
1039 1039 $ cd xyz
1040 1040 $ hg clone ../a
1041 1041 destination directory: a
1042 1042 updating to branch default
1043 1043 getting changed largefiles
1044 1044 3 largefiles updated, 0 removed
1045 1045 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1046 1046 $ cd ..
1047 1047
1048 1048 Clone URL without path:
1049 1049
1050 1050 $ hg clone file://
1051 1051 abort: repository / not found!
1052 1052 [255]
1053 1053
1054 1054 Ensure base clone command argument validation
1055 1055
1056 1056 $ hg clone -U -u 0 a a-clone-failure
1057 1057 abort: cannot specify both --noupdate and --updaterev
1058 1058 [255]
1059 1059
1060 1060 $ hg clone --all-largefiles a ssh://localhost/a
1061 1061 abort: --all-largefiles is incompatible with non-local destination ssh://localhost/a
1062 1062 [255]
1063 1063
1064 1064 Test pulling with --all-largefiles flag. Also test that the largefiles are
1065 1065 downloaded from 'default' instead of 'default-push' when no source is specified
1066 1066 (issue3584)
1067 1067
1068 1068 $ rm -Rf a-backup
1069 1069 $ hg clone -r 1 a a-backup
1070 1070 adding changesets
1071 1071 adding manifests
1072 1072 adding file changes
1073 1073 added 2 changesets with 8 changes to 4 files
1074 1074 updating to branch default
1075 1075 getting changed largefiles
1076 1076 2 largefiles updated, 0 removed
1077 1077 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1078 1078 $ rm "${USERCACHE}"/*
1079 1079 $ cd a-backup
1080 1080 $ hg pull --all-largefiles --config paths.default-push=bogus/path
1081 1081 pulling from $TESTTMP/a (glob)
1082 1082 searching for changes
1083 1083 adding changesets
1084 1084 adding manifests
1085 1085 adding file changes
1086 1086 added 6 changesets with 16 changes to 8 files
1087 1087 (run 'hg update' to get a working copy)
1088 1088 6 largefiles cached
1089 1089
1090 1090 redo pull with --lfrev and check it pulls largefiles for the right revs
1091 1091
1092 1092 $ hg rollback
1093 1093 repository tip rolled back to revision 1 (undo pull)
1094 1094 $ hg pull -v --lfrev 'heads(pulled())+min(pulled())'
1095 1095 pulling from $TESTTMP/a (glob)
1096 1096 searching for changes
1097 1097 all local heads known remotely
1098 1098 6 changesets found
1099 1099 adding changesets
1100 1100 adding manifests
1101 1101 adding file changes
1102 1102 added 6 changesets with 16 changes to 8 files
1103 1103 calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
1104 1104 (run 'hg update' to get a working copy)
1105 1105 pulling largefiles for revision 7
1106 1106 found 971fb41e78fea4f8e0ba5244784239371cb00591 in store
1107 1107 found 0d6d75887db61b2c7e6c74b5dd8fc6ad50c0cc30 in store
1108 1108 found bb3151689acb10f0c3125c560d5e63df914bc1af in store
1109 1109 pulling largefiles for revision 2
1110 1110 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1111 1111 0 largefiles cached
1112 1112
1113 1113 lfpull
1114 1114
1115 1115 $ hg lfpull -r : --config largefiles.usercache=usercache-lfpull
1116 1116 2 largefiles cached
1117 1117 $ hg lfpull -v -r 4+2 --config largefiles.usercache=usercache-lfpull
1118 1118 pulling largefiles for revision 4
1119 1119 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1120 1120 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1121 1121 pulling largefiles for revision 2
1122 1122 found eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 in store
1123 1123 0 largefiles cached
1124 1124
1125 1125 $ ls usercache-lfpull/* | sort
1126 1126 usercache-lfpull/1deebade43c8c498a3c8daddac0244dc55d1331d
1127 1127 usercache-lfpull/4669e532d5b2c093a78eca010077e708a071bb64
1128 1128
1129 1129 $ cd ..
1130 1130
1131 1131 Rebasing between two repositories does not revert largefiles to old
1132 1132 revisions (this was a very bad bug that took a lot of work to fix).
1133 1133
1134 1134 $ hg clone a d
1135 1135 updating to branch default
1136 1136 getting changed largefiles
1137 1137 3 largefiles updated, 0 removed
1138 1138 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1139 1139 $ cd b
1140 1140 $ echo large4-modified > sub/large4
1141 1141 $ echo normal3-modified > normal3
1142 1142 $ hg commit -m "modify normal file and largefile in repo b"
1143 1143 Invoking status precommit hook
1144 1144 M normal3
1145 1145 M sub/large4
1146 1146 $ cd ../d
1147 1147 $ echo large6-modified > sub2/large6
1148 1148 $ echo normal4-modified > sub/normal4
1149 1149 $ hg commit -m "modify normal file largefile in repo d"
1150 1150 Invoking status precommit hook
1151 1151 M sub/normal4
1152 1152 M sub2/large6
1153 1153 $ cd ..
1154 1154 $ hg clone d e
1155 1155 updating to branch default
1156 1156 getting changed largefiles
1157 1157 3 largefiles updated, 0 removed
1158 1158 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1159 1159 $ cd d
1160 1160
1161 1161 More rebase testing, but also test that the largefiles are downloaded from
1162 1162 'default-push' when no source is specified (issue3584). (The largefile from the
1163 1163 pulled revision is however not downloaded but found in the local cache.)
1164 1164 Largefiles are fetched for the new pulled revision, not for existing revisions,
1165 1165 rebased or not.
1166 1166
1167 1167 $ [ ! -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1168 1168 $ hg pull --rebase --all-largefiles --config paths.default-push=bogus/path --config paths.default=../b
1169 1169 pulling from $TESTTMP/b (glob)
1170 1170 searching for changes
1171 1171 adding changesets
1172 1172 adding manifests
1173 1173 adding file changes
1174 1174 added 1 changesets with 2 changes to 2 files (+1 heads)
1175 1175 Invoking status precommit hook
1176 1176 M sub/normal4
1177 1177 M sub2/large6
1178 1178 saved backup bundle to $TESTTMP/d/.hg/strip-backup/f574fb32bb45-backup.hg (glob)
1179 1179 0 largefiles cached
1180 1180 nothing to rebase - working directory parent is also destination
1181 1181 $ [ -f .hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 ]
1182 1182 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1183 1183 9:598410d3eb9a modify normal file largefile in repo d
1184 1184 8:a381d2c8c80e modify normal file and largefile in repo b
1185 1185 7:daea875e9014 add/edit more largefiles
1186 1186 6:4355d653f84f edit files yet again
1187 1187 5:9d5af5072dbd edit files again
1188 1188 4:74c02385b94c move files
1189 1189 3:9e8fbc4bce62 copy files
1190 1190 2:51a0ae4d5864 remove files
1191 1191 1:ce8896473775 edit files
1192 1192 0:30d30fe6a5be add files
1193 1193 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n'
1194 1194 @ 9:598410d3eb9a modify normal file largefile in repo d
1195 1195 |
1196 1196 o 8:a381d2c8c80e modify normal file and largefile in repo b
1197 1197 |
1198 1198 o 7:daea875e9014 add/edit more largefiles
1199 1199 |
1200 1200 o 6:4355d653f84f edit files yet again
1201 1201 |
1202 1202 o 5:9d5af5072dbd edit files again
1203 1203 |
1204 1204 o 4:74c02385b94c move files
1205 1205 |
1206 1206 o 3:9e8fbc4bce62 copy files
1207 1207 |
1208 1208 o 2:51a0ae4d5864 remove files
1209 1209 |
1210 1210 o 1:ce8896473775 edit files
1211 1211 |
1212 1212 o 0:30d30fe6a5be add files
1213 1213
1214 1214 $ cat normal3
1215 1215 normal3-modified
1216 1216 $ cat sub/normal4
1217 1217 normal4-modified
1218 1218 $ cat sub/large4
1219 1219 large4-modified
1220 1220 $ cat sub2/large6
1221 1221 large6-modified
1222 1222 $ cat sub2/large7
1223 1223 large7
1224 1224 $ cd ../e
1225 1225 $ hg pull ../b
1226 1226 pulling from ../b
1227 1227 searching for changes
1228 1228 adding changesets
1229 1229 adding manifests
1230 1230 adding file changes
1231 1231 added 1 changesets with 2 changes to 2 files (+1 heads)
1232 1232 (run 'hg heads' to see heads, 'hg merge' to merge)
1233 1233 $ hg rebase
1234 1234 Invoking status precommit hook
1235 1235 M sub/normal4
1236 1236 M sub2/large6
1237 1237 saved backup bundle to $TESTTMP/e/.hg/strip-backup/f574fb32bb45-backup.hg (glob)
1238 1238 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1239 1239 9:598410d3eb9a modify normal file largefile in repo d
1240 1240 8:a381d2c8c80e modify normal file and largefile in repo b
1241 1241 7:daea875e9014 add/edit more largefiles
1242 1242 6:4355d653f84f edit files yet again
1243 1243 5:9d5af5072dbd edit files again
1244 1244 4:74c02385b94c move files
1245 1245 3:9e8fbc4bce62 copy files
1246 1246 2:51a0ae4d5864 remove files
1247 1247 1:ce8896473775 edit files
1248 1248 0:30d30fe6a5be add files
1249 1249 $ cat normal3
1250 1250 normal3-modified
1251 1251 $ cat sub/normal4
1252 1252 normal4-modified
1253 1253 $ cat sub/large4
1254 1254 large4-modified
1255 1255 $ cat sub2/large6
1256 1256 large6-modified
1257 1257 $ cat sub2/large7
1258 1258 large7
1259 1259
1260 1260 Log on largefiles
1261 1261
1262 1262 - same output
1263 1263 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1264 1264 8:a381d2c8c80e modify normal file and largefile in repo b
1265 1265 6:4355d653f84f edit files yet again
1266 1266 5:9d5af5072dbd edit files again
1267 1267 4:74c02385b94c move files
1268 1268 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1269 1269 o 8:a381d2c8c80e modify normal file and largefile in repo b
1270 1270 |
1271 1271 o 6:4355d653f84f edit files yet again
1272 1272 |
1273 1273 o 5:9d5af5072dbd edit files again
1274 1274 |
1275 1275 o 4:74c02385b94c move files
1276 1276 |
1277 1277 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub/large4
1278 1278 8:a381d2c8c80e modify normal file and largefile in repo b
1279 1279 6:4355d653f84f edit files yet again
1280 1280 5:9d5af5072dbd edit files again
1281 1281 4:74c02385b94c move files
1282 1282 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub/large4
1283 1283 o 8:a381d2c8c80e modify normal file and largefile in repo b
1284 1284 |
1285 1285 o 6:4355d653f84f edit files yet again
1286 1286 |
1287 1287 o 5:9d5af5072dbd edit files again
1288 1288 |
1289 1289 o 4:74c02385b94c move files
1290 1290 |
1291 1291
1292 1292 - .hglf only matches largefiles, without .hglf it matches 9 bco sub/normal
1293 1293 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1294 1294 8:a381d2c8c80e modify normal file and largefile in repo b
1295 1295 6:4355d653f84f edit files yet again
1296 1296 5:9d5af5072dbd edit files again
1297 1297 4:74c02385b94c move files
1298 1298 1:ce8896473775 edit files
1299 1299 0:30d30fe6a5be add files
1300 1300 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' .hglf/sub
1301 1301 o 8:a381d2c8c80e modify normal file and largefile in repo b
1302 1302 |
1303 1303 o 6:4355d653f84f edit files yet again
1304 1304 |
1305 1305 o 5:9d5af5072dbd edit files again
1306 1306 |
1307 1307 o 4:74c02385b94c move files
1308 1308 |
1309 1309 o 1:ce8896473775 edit files
1310 1310 |
1311 1311 o 0:30d30fe6a5be add files
1312 1312
1313 1313 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' sub
1314 1314 9:598410d3eb9a modify normal file largefile in repo d
1315 1315 8:a381d2c8c80e modify normal file and largefile in repo b
1316 1316 6:4355d653f84f edit files yet again
1317 1317 5:9d5af5072dbd edit files again
1318 1318 4:74c02385b94c move files
1319 1319 1:ce8896473775 edit files
1320 1320 0:30d30fe6a5be add files
1321 1321 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' sub
1322 1322 @ 9:598410d3eb9a modify normal file largefile in repo d
1323 1323 |
1324 1324 o 8:a381d2c8c80e modify normal file and largefile in repo b
1325 1325 |
1326 1326 o 6:4355d653f84f edit files yet again
1327 1327 |
1328 1328 o 5:9d5af5072dbd edit files again
1329 1329 |
1330 1330 o 4:74c02385b94c move files
1331 1331 |
1332 1332 o 1:ce8896473775 edit files
1333 1333 |
1334 1334 o 0:30d30fe6a5be add files
1335 1335
1336 1336 - globbing gives same result
1337 1337 $ hg log --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1338 1338 9:598410d3eb9a modify normal file largefile in repo d
1339 1339 8:a381d2c8c80e modify normal file and largefile in repo b
1340 1340 6:4355d653f84f edit files yet again
1341 1341 5:9d5af5072dbd edit files again
1342 1342 4:74c02385b94c move files
1343 1343 1:ce8896473775 edit files
1344 1344 0:30d30fe6a5be add files
1345 1345 $ hg log -G --template '{rev}:{node|short} {desc|firstline}\n' 'glob:sub/*'
1346 1346 @ 9:598410d3eb9a modify normal file largefile in repo d
1347 1347 |
1348 1348 o 8:a381d2c8c80e modify normal file and largefile in repo b
1349 1349 |
1350 1350 o 6:4355d653f84f edit files yet again
1351 1351 |
1352 1352 o 5:9d5af5072dbd edit files again
1353 1353 |
1354 1354 o 4:74c02385b94c move files
1355 1355 |
1356 1356 o 1:ce8896473775 edit files
1357 1357 |
1358 1358 o 0:30d30fe6a5be add files
1359 1359
1360 1360 Rollback on largefiles.
1361 1361
1362 1362 $ echo large4-modified-again > sub/large4
1363 1363 $ hg commit -m "Modify large4 again"
1364 1364 Invoking status precommit hook
1365 1365 M sub/large4
1366 1366 $ hg rollback
1367 1367 repository tip rolled back to revision 9 (undo commit)
1368 1368 working directory now based on revision 9
1369 1369 $ hg st
1370 1370 M sub/large4
1371 1371 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1372 1372 9:598410d3eb9a modify normal file largefile in repo d
1373 1373 8:a381d2c8c80e modify normal file and largefile in repo b
1374 1374 7:daea875e9014 add/edit more largefiles
1375 1375 6:4355d653f84f edit files yet again
1376 1376 5:9d5af5072dbd edit files again
1377 1377 4:74c02385b94c move files
1378 1378 3:9e8fbc4bce62 copy files
1379 1379 2:51a0ae4d5864 remove files
1380 1380 1:ce8896473775 edit files
1381 1381 0:30d30fe6a5be add files
1382 1382 $ cat sub/large4
1383 1383 large4-modified-again
1384 1384
1385 1385 "update --check" refuses to update with uncommitted changes.
1386 1386 $ hg update --check 8
1387 1387 abort: uncommitted changes
1388 1388 [255]
1389 1389
1390 1390 "update --clean" leaves correct largefiles in working copy, even when there is
1391 1391 .orig files from revert in .hglf.
1392 1392
1393 1393 $ echo mistake > sub2/large7
1394 1394 $ hg revert sub2/large7
1395 1395 $ cat sub2/large7
1396 1396 large7
1397 1397 $ cat sub2/large7.orig
1398 1398 mistake
1399 1399 $ test ! -f .hglf/sub2/large7.orig
1400 1400
1401 1401 $ hg -q update --clean -r null
1402 1402 $ hg update --clean
1403 1403 getting changed largefiles
1404 1404 3 largefiles updated, 0 removed
1405 1405 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1406 1406 $ cat normal3
1407 1407 normal3-modified
1408 1408 $ cat sub/normal4
1409 1409 normal4-modified
1410 1410 $ cat sub/large4
1411 1411 large4-modified
1412 1412 $ cat sub2/large6
1413 1413 large6-modified
1414 1414 $ cat sub2/large7
1415 1415 large7
1416 1416 $ cat sub2/large7.orig
1417 1417 mistake
1418 1418 $ test ! -f .hglf/sub2/large7.orig
1419 1419
1420 1420 verify that largefile .orig file no longer is overwritten on every update -C:
1421 1421 $ hg update --clean
1422 1422 getting changed largefiles
1423 1423 0 largefiles updated, 0 removed
1424 1424 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1425 1425 $ cat sub2/large7.orig
1426 1426 mistake
1427 1427 $ rm sub2/large7.orig
1428 1428
1429 1429 Now "update check" is happy.
1430 1430 $ hg update --check 8
1431 1431 getting changed largefiles
1432 1432 1 largefiles updated, 0 removed
1433 1433 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1434 1434 $ hg update --check
1435 1435 getting changed largefiles
1436 1436 1 largefiles updated, 0 removed
1437 1437 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1438 1438
1439 1439 Test removing empty largefiles directories on update
1440 1440 $ test -d sub2 && echo "sub2 exists"
1441 1441 sub2 exists
1442 1442 $ hg update -q null
1443 1443 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1444 1444 [1]
1445 1445 $ hg update -q
1446 1446
1447 1447 Test hg remove removes empty largefiles directories
1448 1448 $ test -d sub2 && echo "sub2 exists"
1449 1449 sub2 exists
1450 1450 $ hg remove sub2/*
1451 1451 $ test -d sub2 && echo "error: sub2 should not exist anymore"
1452 1452 [1]
1453 1453 $ hg revert sub2/large6 sub2/large7
1454 1454
1455 1455 "revert" works on largefiles (and normal files too).
1456 1456 $ echo hack3 >> normal3
1457 1457 $ echo hack4 >> sub/normal4
1458 1458 $ echo hack4 >> sub/large4
1459 1459 $ rm sub2/large6
1460 1460 $ hg revert sub2/large6
1461 1461 $ hg rm sub2/large6
1462 1462 $ echo new >> sub2/large8
1463 1463 $ hg add --large sub2/large8
1464 1464 # XXX we don't really want to report that we're reverting the standin;
1465 1465 # that's just an implementation detail. But I don't see an obvious fix. ;-(
1466 1466 $ hg revert sub
1467 1467 reverting .hglf/sub/large4 (glob)
1468 1468 reverting sub/normal4 (glob)
1469 1469 $ hg status
1470 1470 M normal3
1471 1471 A sub2/large8
1472 1472 R sub2/large6
1473 1473 ? sub/large4.orig
1474 1474 ? sub/normal4.orig
1475 1475 $ cat sub/normal4
1476 1476 normal4-modified
1477 1477 $ cat sub/large4
1478 1478 large4-modified
1479 1479 $ hg revert -a --no-backup
1480 1480 undeleting .hglf/sub2/large6 (glob)
1481 1481 forgetting .hglf/sub2/large8 (glob)
1482 1482 reverting normal3
1483 1483 $ hg status
1484 1484 ? sub/large4.orig
1485 1485 ? sub/normal4.orig
1486 1486 ? sub2/large8
1487 1487 $ cat normal3
1488 1488 normal3-modified
1489 1489 $ cat sub2/large6
1490 1490 large6-modified
1491 1491 $ rm sub/*.orig sub2/large8
1492 1492
1493 1493 revert some files to an older revision
1494 1494 $ hg revert --no-backup -r 8 sub2
1495 1495 reverting .hglf/sub2/large6 (glob)
1496 1496 $ cat sub2/large6
1497 1497 large6
1498 1498 $ hg revert --no-backup -C -r '.^' sub2
1499 reverting .hglf/sub2/large6 (glob)
1500 1499 $ hg revert --no-backup sub2
1501 1500 reverting .hglf/sub2/large6 (glob)
1502 1501 $ hg status
1503 1502
1504 1503 "verify --large" actually verifies largefiles
1505 1504
1506 1505 - Where Do We Come From? What Are We? Where Are We Going?
1507 1506 $ pwd
1508 1507 $TESTTMP/e
1509 1508 $ hg paths
1510 1509 default = $TESTTMP/d (glob)
1511 1510
1512 1511 $ hg verify --large
1513 1512 checking changesets
1514 1513 checking manifests
1515 1514 crosschecking files in changesets and manifests
1516 1515 checking files
1517 1516 10 files, 10 changesets, 28 total revisions
1518 1517 searching 1 changesets for largefiles
1519 1518 verified existence of 3 revisions of 3 largefiles
1520 1519
1521 1520 - introduce missing blob in local store repo and make sure that this is caught:
1522 1521 $ mv $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 .
1523 1522 $ hg verify --large
1524 1523 checking changesets
1525 1524 checking manifests
1526 1525 crosschecking files in changesets and manifests
1527 1526 checking files
1528 1527 10 files, 10 changesets, 28 total revisions
1529 1528 searching 1 changesets for largefiles
1530 1529 changeset 9:598410d3eb9a: sub/large4 references missing $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 (glob)
1531 1530 verified existence of 3 revisions of 3 largefiles
1532 1531 [1]
1533 1532
1534 1533 - introduce corruption and make sure that it is caught when checking content:
1535 1534 $ echo '5 cents' > $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928
1536 1535 $ hg verify -q --large --lfc
1537 1536 changeset 9:598410d3eb9a: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/e166e74c7303192238d60af5a9c4ce9bef0b7928 (glob)
1538 1537 [1]
1539 1538
1540 1539 - cleanup
1541 1540 $ mv e166e74c7303192238d60af5a9c4ce9bef0b7928 $TESTTMP/d/.hg/largefiles/
1542 1541
1543 1542 - verifying all revisions will fail because we didn't clone all largefiles to d:
1544 1543 $ echo 'T-shirt' > $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1545 1544 $ hg verify -q --lfa --lfc
1546 1545 changeset 0:30d30fe6a5be: large1 references missing $TESTTMP/d/.hg/largefiles/4669e532d5b2c093a78eca010077e708a071bb64 (glob)
1547 1546 changeset 0:30d30fe6a5be: sub/large2 references missing $TESTTMP/d/.hg/largefiles/1deebade43c8c498a3c8daddac0244dc55d1331d (glob)
1548 1547 changeset 1:ce8896473775: large1 references missing $TESTTMP/d/.hg/largefiles/5f78770c0e77ba4287ad6ef3071c9bf9c379742f (glob)
1549 1548 changeset 1:ce8896473775: sub/large2 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1550 1549 changeset 3:9e8fbc4bce62: large1 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1551 1550 changeset 4:74c02385b94c: large3 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1552 1551 changeset 4:74c02385b94c: sub/large4 references corrupted $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4 (glob)
1553 1552 changeset 5:9d5af5072dbd: large3 references missing $TESTTMP/d/.hg/largefiles/baaf12afde9d8d67f25dab6dced0d2bf77dba47c (glob)
1554 1553 changeset 5:9d5af5072dbd: sub/large4 references missing $TESTTMP/d/.hg/largefiles/aeb2210d19f02886dde00dac279729a48471e2f9 (glob)
1555 1554 changeset 6:4355d653f84f: large3 references missing $TESTTMP/d/.hg/largefiles/7838695e10da2bb75ac1156565f40a2595fa2fa0 (glob)
1556 1555 [1]
1557 1556
1558 1557 - cleanup
1559 1558 $ rm $TESTTMP/d/.hg/largefiles/eb7338044dc27f9bc59b8dd5a246b065ead7a9c4
1560 1559 $ rm -f .hglf/sub/*.orig
1561 1560
1562 1561 Update to revision with missing largefile - and make sure it really is missing
1563 1562
1564 1563 $ rm ${USERCACHE}/7838695e10da2bb75ac1156565f40a2595fa2fa0
1565 1564 $ hg up -r 6
1566 1565 getting changed largefiles
1567 1566 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1568 1567 1 largefiles updated, 2 removed
1569 1568 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
1570 1569 $ rm normal3
1571 1570 $ echo >> sub/normal4
1572 1571 $ hg ci -m 'commit with missing files'
1573 1572 Invoking status precommit hook
1574 1573 M sub/normal4
1575 1574 ! large3
1576 1575 ! normal3
1577 1576 created new head
1578 1577 $ hg st
1579 1578 ! large3
1580 1579 ! normal3
1581 1580 $ hg up -r.
1582 1581 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1583 1582 $ hg st
1584 1583 ! large3
1585 1584 ! normal3
1586 1585 $ hg up -Cr.
1587 1586 getting changed largefiles
1588 1587 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1589 1588 0 largefiles updated, 0 removed
1590 1589 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1591 1590 $ hg st
1592 1591 ! large3
1593 1592 $ hg rollback
1594 1593 repository tip rolled back to revision 9 (undo commit)
1595 1594 working directory now based on revision 6
1596 1595
1597 1596 Merge with revision with missing largefile - and make sure it tries to fetch it.
1598 1597
1599 1598 $ hg up -Cqr null
1600 1599 $ echo f > f
1601 1600 $ hg ci -Am branch
1602 1601 adding f
1603 1602 Invoking status precommit hook
1604 1603 A f
1605 1604 created new head
1606 1605 $ hg merge -r 6
1607 1606 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
1608 1607 (branch merge, don't forget to commit)
1609 1608 getting changed largefiles
1610 1609 large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
1611 1610 1 largefiles updated, 0 removed
1612 1611
1613 1612 $ hg rollback -q
1614 1613 $ hg up -Cq
1615 1614
1616 1615 Pulling 0 revisions with --all-largefiles should not fetch for all revisions
1617 1616
1618 1617 $ hg pull --all-largefiles
1619 1618 pulling from $TESTTMP/d (glob)
1620 1619 searching for changes
1621 1620 no changes found
1622 1621
1623 1622 Merging does not revert to old versions of largefiles and also check
1624 1623 that merging after having pulled from a non-default remote works
1625 1624 correctly.
1626 1625
1627 1626 $ cd ..
1628 1627 $ hg clone -r 7 e temp
1629 1628 adding changesets
1630 1629 adding manifests
1631 1630 adding file changes
1632 1631 added 8 changesets with 24 changes to 10 files
1633 1632 updating to branch default
1634 1633 getting changed largefiles
1635 1634 3 largefiles updated, 0 removed
1636 1635 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1637 1636 $ hg clone temp f
1638 1637 updating to branch default
1639 1638 getting changed largefiles
1640 1639 3 largefiles updated, 0 removed
1641 1640 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1642 1641 # Delete the largefiles in the largefiles system cache so that we have an
1643 1642 # opportunity to test that caching after a pull works.
1644 1643 $ rm "${USERCACHE}"/*
1645 1644 $ cd f
1646 1645 $ echo "large4-merge-test" > sub/large4
1647 1646 $ hg commit -m "Modify large4 to test merge"
1648 1647 Invoking status precommit hook
1649 1648 M sub/large4
1650 1649 # Test --cache-largefiles flag
1651 1650 $ hg pull --lfrev 'heads(pulled())' ../e
1652 1651 pulling from ../e
1653 1652 searching for changes
1654 1653 adding changesets
1655 1654 adding manifests
1656 1655 adding file changes
1657 1656 added 2 changesets with 4 changes to 4 files (+1 heads)
1658 1657 (run 'hg heads' to see heads, 'hg merge' to merge)
1659 1658 2 largefiles cached
1660 1659 $ hg merge
1661 1660 largefile sub/large4 has a merge conflict
1662 1661 ancestor was 971fb41e78fea4f8e0ba5244784239371cb00591
1663 1662 keep (l)ocal d846f26643bfa8ec210be40cc93cc6b7ff1128ea or
1664 1663 take (o)ther e166e74c7303192238d60af5a9c4ce9bef0b7928? l
1665 1664 3 files updated, 1 files merged, 0 files removed, 0 files unresolved
1666 1665 (branch merge, don't forget to commit)
1667 1666 getting changed largefiles
1668 1667 1 largefiles updated, 0 removed
1669 1668 $ hg commit -m "Merge repos e and f"
1670 1669 Invoking status precommit hook
1671 1670 M normal3
1672 1671 M sub/normal4
1673 1672 M sub2/large6
1674 1673 $ cat normal3
1675 1674 normal3-modified
1676 1675 $ cat sub/normal4
1677 1676 normal4-modified
1678 1677 $ cat sub/large4
1679 1678 large4-merge-test
1680 1679 $ cat sub2/large6
1681 1680 large6-modified
1682 1681 $ cat sub2/large7
1683 1682 large7
1684 1683
1685 1684 Test status after merging with a branch that introduces a new largefile:
1686 1685
1687 1686 $ echo large > large
1688 1687 $ hg add --large large
1689 1688 $ hg commit -m 'add largefile'
1690 1689 Invoking status precommit hook
1691 1690 A large
1692 1691 $ hg update -q ".^"
1693 1692 $ echo change >> normal3
1694 1693 $ hg commit -m 'some change'
1695 1694 Invoking status precommit hook
1696 1695 M normal3
1697 1696 created new head
1698 1697 $ hg merge
1699 1698 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1700 1699 (branch merge, don't forget to commit)
1701 1700 getting changed largefiles
1702 1701 1 largefiles updated, 0 removed
1703 1702 $ hg status
1704 1703 M large
1705 1704
1706 1705 - make sure update of merge with removed largefiles fails as expected
1707 1706 $ hg rm sub2/large6
1708 1707 $ hg up -r.
1709 1708 abort: outstanding uncommitted merges
1710 1709 [255]
1711 1710
1712 1711 - revert should be able to revert files introduced in a pending merge
1713 1712 $ hg revert --all -r .
1714 1713 removing .hglf/large (glob)
1715 1714 undeleting .hglf/sub2/large6 (glob)
1716 1715
1717 1716 Test that a normal file and a largefile with the same name and path cannot
1718 1717 coexist.
1719 1718
1720 1719 $ rm sub2/large7
1721 1720 $ echo "largeasnormal" > sub2/large7
1722 1721 $ hg add sub2/large7
1723 1722 sub2/large7 already a largefile
1724 1723
1725 1724 Test that transplanting a largefile change works correctly.
1726 1725
1727 1726 $ cd ..
1728 1727 $ hg clone -r 8 d g
1729 1728 adding changesets
1730 1729 adding manifests
1731 1730 adding file changes
1732 1731 added 9 changesets with 26 changes to 10 files
1733 1732 updating to branch default
1734 1733 getting changed largefiles
1735 1734 3 largefiles updated, 0 removed
1736 1735 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
1737 1736 $ cd g
1738 1737 $ hg transplant -s ../d 598410d3eb9a
1739 1738 searching for changes
1740 1739 searching for changes
1741 1740 adding changesets
1742 1741 adding manifests
1743 1742 adding file changes
1744 1743 added 1 changesets with 2 changes to 2 files
1745 1744 getting changed largefiles
1746 1745 1 largefiles updated, 0 removed
1747 1746 $ hg log --template '{rev}:{node|short} {desc|firstline}\n'
1748 1747 9:598410d3eb9a modify normal file largefile in repo d
1749 1748 8:a381d2c8c80e modify normal file and largefile in repo b
1750 1749 7:daea875e9014 add/edit more largefiles
1751 1750 6:4355d653f84f edit files yet again
1752 1751 5:9d5af5072dbd edit files again
1753 1752 4:74c02385b94c move files
1754 1753 3:9e8fbc4bce62 copy files
1755 1754 2:51a0ae4d5864 remove files
1756 1755 1:ce8896473775 edit files
1757 1756 0:30d30fe6a5be add files
1758 1757 $ cat normal3
1759 1758 normal3-modified
1760 1759 $ cat sub/normal4
1761 1760 normal4-modified
1762 1761 $ cat sub/large4
1763 1762 large4-modified
1764 1763 $ cat sub2/large6
1765 1764 large6-modified
1766 1765 $ cat sub2/large7
1767 1766 large7
1768 1767
1769 1768 Cat a largefile
1770 1769 $ hg cat normal3
1771 1770 normal3-modified
1772 1771 $ hg cat sub/large4
1773 1772 large4-modified
1774 1773 $ rm "${USERCACHE}"/*
1775 1774 $ hg cat -r a381d2c8c80e -o cat.out sub/large4
1776 1775 $ cat cat.out
1777 1776 large4-modified
1778 1777 $ rm cat.out
1779 1778 $ hg cat -r a381d2c8c80e normal3
1780 1779 normal3-modified
1781 1780 $ hg cat -r '.^' normal3
1782 1781 normal3-modified
1783 1782 $ hg cat -r '.^' sub/large4 doesntexist
1784 1783 large4-modified
1785 1784 doesntexist: no such file in rev a381d2c8c80e
1786 1785 $ hg --cwd sub cat -r '.^' large4
1787 1786 large4-modified
1788 1787 $ hg --cwd sub cat -r '.^' ../normal3
1789 1788 normal3-modified
1790 1789 Cat a standin
1791 1790 $ hg cat .hglf/sub/large4
1792 1791 e166e74c7303192238d60af5a9c4ce9bef0b7928
1793 1792 $ hg cat .hglf/normal3
1794 1793 .hglf/normal3: no such file in rev 598410d3eb9a
1795 1794 [1]
1796 1795
1797 1796 Test that renaming a largefile results in correct output for status
1798 1797
1799 1798 $ hg rename sub/large4 large4-renamed
1800 1799 $ hg commit -m "test rename output"
1801 1800 Invoking status precommit hook
1802 1801 A large4-renamed
1803 1802 R sub/large4
1804 1803 $ cat large4-renamed
1805 1804 large4-modified
1806 1805 $ cd sub2
1807 1806 $ hg rename large6 large6-renamed
1808 1807 $ hg st
1809 1808 A sub2/large6-renamed
1810 1809 R sub2/large6
1811 1810 $ cd ..
1812 1811
1813 1812 Test --normal flag
1814 1813
1815 1814 $ dd if=/dev/zero bs=2k count=11k > new-largefile 2> /dev/null
1816 1815 $ hg add --normal --large new-largefile
1817 1816 abort: --normal cannot be used with --large
1818 1817 [255]
1819 1818 $ hg add --normal new-largefile
1820 1819 new-largefile: up to 69 MB of RAM may be required to manage this file
1821 1820 (use 'hg revert new-largefile' to cancel the pending addition)
1822 1821 $ cd ..
1823 1822
1824 1823
1825 1824
@@ -1,1215 +1,1213 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3 $ echo 123 > a
4 4 $ echo 123 > c
5 5 $ echo 123 > e
6 6 $ hg add a c e
7 7 $ hg commit -m "first" a c e
8 8
9 9 nothing changed
10 10
11 11 $ hg revert
12 12 abort: no files or directories specified
13 13 (use --all to revert all files)
14 14 [255]
15 15 $ hg revert --all
16 16
17 17 Introduce some changes and revert them
18 18 --------------------------------------
19 19
20 20 $ echo 123 > b
21 21
22 22 $ hg status
23 23 ? b
24 24 $ echo 12 > c
25 25
26 26 $ hg status
27 27 M c
28 28 ? b
29 29 $ hg add b
30 30
31 31 $ hg status
32 32 M c
33 33 A b
34 34 $ hg rm a
35 35
36 36 $ hg status
37 37 M c
38 38 A b
39 39 R a
40 40
41 41 revert removal of a file
42 42
43 43 $ hg revert a
44 44 $ hg status
45 45 M c
46 46 A b
47 47
48 48 revert addition of a file
49 49
50 50 $ hg revert b
51 51 $ hg status
52 52 M c
53 53 ? b
54 54
55 55 revert modification of a file (--no-backup)
56 56
57 57 $ hg revert --no-backup c
58 58 $ hg status
59 59 ? b
60 60
61 61 revert deletion (! status) of a added file
62 62 ------------------------------------------
63 63
64 64 $ hg add b
65 65
66 66 $ hg status b
67 67 A b
68 68 $ rm b
69 69 $ hg status b
70 70 ! b
71 71 $ hg revert -v b
72 72 forgetting b
73 73 $ hg status b
74 74 b: * (glob)
75 75
76 76 $ ls
77 77 a
78 78 c
79 79 e
80 80
81 81 Test creation of backup (.orig) files
82 82 -------------------------------------
83 83
84 84 $ echo z > e
85 85 $ hg revert --all -v
86 86 saving current version of e as e.orig
87 87 reverting e
88 88
89 89 revert on clean file (no change)
90 90 --------------------------------
91 91
92 92 $ hg revert a
93 93 no changes needed to a
94 94
95 95 revert on an untracked file
96 96 ---------------------------
97 97
98 98 $ echo q > q
99 99 $ hg revert q
100 100 file not managed: q
101 101 $ rm q
102 102
103 103 revert on file that does not exists
104 104 -----------------------------------
105 105
106 106 $ hg revert notfound
107 107 notfound: no such file in rev 334a9e57682c
108 108 $ touch d
109 109 $ hg add d
110 110 $ hg rm a
111 111 $ hg commit -m "second"
112 112 $ echo z > z
113 113 $ hg add z
114 114 $ hg st
115 115 A z
116 116 ? e.orig
117 117
118 118 revert to another revision (--rev)
119 119 ----------------------------------
120 120
121 121 $ hg revert --all -r0
122 122 adding a
123 123 removing d
124 124 forgetting z
125 125
126 126 revert explicitly to parent (--rev)
127 127 -----------------------------------
128 128
129 129 $ hg revert --all -rtip
130 130 forgetting a
131 131 undeleting d
132 132 $ rm a *.orig
133 133
134 134 revert to another revision (--rev) and exact match
135 135 --------------------------------------------------
136 136
137 137 exact match are more silent
138 138
139 139 $ hg revert -r0 a
140 140 $ hg st a
141 141 A a
142 142 $ hg rm d
143 143 $ hg st d
144 144 R d
145 145
146 146 should keep d removed
147 147
148 148 $ hg revert -r0 d
149 149 no changes needed to d
150 150 $ hg st d
151 151 R d
152 152
153 153 $ hg update -C
154 154 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
155 155
156 156 revert of exec bit
157 157 ------------------
158 158
159 159 #if execbit
160 160 $ chmod +x c
161 161 $ hg revert --all
162 162 reverting c
163 163
164 164 $ test -x c || echo non-executable
165 165 non-executable
166 166
167 167 $ chmod +x c
168 168 $ hg commit -m exe
169 169
170 170 $ chmod -x c
171 171 $ hg revert --all
172 172 reverting c
173 173
174 174 $ test -x c && echo executable
175 175 executable
176 176 #endif
177 177
178 178 $ cd ..
179 179
180 180
181 181 Issue241: update and revert produces inconsistent repositories
182 182 --------------------------------------------------------------
183 183
184 184 $ hg init a
185 185 $ cd a
186 186 $ echo a >> a
187 187 $ hg commit -A -d '1 0' -m a
188 188 adding a
189 189 $ echo a >> a
190 190 $ hg commit -d '2 0' -m a
191 191 $ hg update 0
192 192 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
193 193 $ mkdir b
194 194 $ echo b > b/b
195 195
196 196 call `hg revert` with no file specified
197 197 ---------------------------------------
198 198
199 199 $ hg revert -rtip
200 200 abort: no files or directories specified
201 201 (use --all to revert all files, or 'hg update 1' to update)
202 202 [255]
203 203
204 204 call `hg revert` with --all
205 205 ---------------------------
206 206
207 207 $ hg revert --all -rtip
208 208 reverting a
209 209
210 210
211 211 Issue332: confusing message when reverting directory
212 212 ----------------------------------------------------
213 213
214 214 $ hg ci -A -m b
215 215 adding b/b
216 216 created new head
217 217 $ echo foobar > b/b
218 218 $ mkdir newdir
219 219 $ echo foo > newdir/newfile
220 220 $ hg add newdir/newfile
221 221 $ hg revert b newdir
222 222 reverting b/b (glob)
223 223 forgetting newdir/newfile (glob)
224 224 $ echo foobar > b/b
225 225 $ hg revert .
226 226 reverting b/b (glob)
227 227
228 228
229 229 reverting a rename target should revert the source
230 230 --------------------------------------------------
231 231
232 232 $ hg mv a newa
233 233 $ hg revert newa
234 234 $ hg st a newa
235 235 ? newa
236 236
237 237 $ cd ..
238 238
239 239 $ hg init ignored
240 240 $ cd ignored
241 241 $ echo '^ignored$' > .hgignore
242 242 $ echo '^ignoreddir$' >> .hgignore
243 243 $ echo '^removed$' >> .hgignore
244 244
245 245 $ mkdir ignoreddir
246 246 $ touch ignoreddir/file
247 247 $ touch ignoreddir/removed
248 248 $ touch ignored
249 249 $ touch removed
250 250
251 251 4 ignored files (we will add/commit everything)
252 252
253 253 $ hg st -A -X .hgignore
254 254 I ignored
255 255 I ignoreddir/file
256 256 I ignoreddir/removed
257 257 I removed
258 258 $ hg ci -qAm 'add files' ignored ignoreddir/file ignoreddir/removed removed
259 259
260 260 $ echo >> ignored
261 261 $ echo >> ignoreddir/file
262 262 $ hg rm removed ignoreddir/removed
263 263
264 264 should revert ignored* and undelete *removed
265 265 --------------------------------------------
266 266
267 267 $ hg revert -a --no-backup
268 268 reverting ignored
269 269 reverting ignoreddir/file (glob)
270 270 undeleting ignoreddir/removed (glob)
271 271 undeleting removed
272 272 $ hg st -mardi
273 273
274 274 $ hg up -qC
275 275 $ echo >> ignored
276 276 $ hg rm removed
277 277
278 278 should silently revert the named files
279 279 --------------------------------------
280 280
281 281 $ hg revert --no-backup ignored removed
282 282 $ hg st -mardi
283 283
284 284 Reverting copy (issue3920)
285 285 --------------------------
286 286
287 287 someone set up us the copies
288 288
289 289 $ rm .hgignore
290 290 $ hg update -C
291 291 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
292 292 $ hg mv ignored allyour
293 293 $ hg copy removed base
294 294 $ hg commit -m rename
295 295
296 296 copies and renames, you have no chance to survive make your time (issue3920)
297 297
298 298 $ hg update '.^'
299 299 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
300 300 $ hg revert -rtip -a
301 301 adding allyour
302 302 adding base
303 303 removing ignored
304 304 $ hg status -C
305 305 A allyour
306 306 ignored
307 307 A base
308 308 removed
309 309 R ignored
310 310
311 311 Test revert of a file added by one side of the merge
312 312 ====================================================
313 313
314 314 remove any pending change
315 315
316 316 $ hg revert --all
317 317 forgetting allyour
318 318 forgetting base
319 319 undeleting ignored
320 320 $ hg purge --all --config extensions.purge=
321 321
322 322 Adds a new commit
323 323
324 324 $ echo foo > newadd
325 325 $ hg add newadd
326 326 $ hg commit -m 'other adds'
327 327 created new head
328 328
329 329
330 330 merge it with the other head
331 331
332 332 $ hg merge # merge 1 into 2
333 333 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
334 334 (branch merge, don't forget to commit)
335 335 $ hg summary
336 336 parent: 2:b8ec310b2d4e tip
337 337 other adds
338 338 parent: 1:f6180deb8fbe
339 339 rename
340 340 branch: default
341 341 commit: 2 modified, 1 removed (merge)
342 342 update: (current)
343 343
344 344 clarifies who added what
345 345
346 346 $ hg status
347 347 M allyour
348 348 M base
349 349 R ignored
350 350 $ hg status --change 'p1()'
351 351 A newadd
352 352 $ hg status --change 'p2()'
353 353 A allyour
354 354 A base
355 355 R ignored
356 356
357 357 revert file added by p1() to p1() state
358 358 -----------------------------------------
359 359
360 360 $ hg revert -r 'p1()' 'glob:newad?'
361 361 $ hg status
362 362 M allyour
363 363 M base
364 364 R ignored
365 365
366 366 revert file added by p1() to p2() state
367 367 ------------------------------------------
368 368
369 369 $ hg revert -r 'p2()' 'glob:newad?'
370 370 removing newadd
371 371 $ hg status
372 372 M allyour
373 373 M base
374 374 R ignored
375 375 R newadd
376 376
377 377 revert file added by p2() to p2() state
378 378 ------------------------------------------
379 379
380 380 $ hg revert -r 'p2()' 'glob:allyou?'
381 381 $ hg status
382 382 M allyour
383 383 M base
384 384 R ignored
385 385 R newadd
386 386
387 387 revert file added by p2() to p1() state
388 388 ------------------------------------------
389 389
390 390 $ hg revert -r 'p1()' 'glob:allyou?'
391 391 removing allyour
392 392 $ hg status
393 393 M base
394 394 R allyour
395 395 R ignored
396 396 R newadd
397 397
398 398 Systematic behavior validation of most possible cases
399 399 =====================================================
400 400
401 401 This section tests most of the possible combinations of working directory
402 402 changes and inter-revision changes. The number of possible cases is significant
403 403 but they all have a slighly different handling. So this section commits to
404 404 generating and testing all of them to allow safe refactoring of the revert code.
405 405
406 406 A python script is used to generate a file history for each combination of
407 407 changes between, on one side the working directory and its parent and on
408 408 the other side, changes between a revert target (--rev) and working directory
409 409 parent. The three states generated are:
410 410
411 411 - a "base" revision
412 412 - a "parent" revision
413 413 - the working directory (based on "parent")
414 414
415 415 The file generated have names of the form:
416 416
417 417 <changeset-state>_<working-copy-state>
418 418
419 419 Here, "changeset-state" conveys the state in "base" and "parent" (or the change
420 420 that happen between them), "working-copy-state" is self explanatory.
421 421
422 422 All known states are not tested yet. See inline documentation for details.
423 423 Special cases from merge and rename are not tested by this section.
424 424
425 425 There are also multiple cases where the current revert implementation is known to
426 426 slightly misbehave.
427 427
428 428 Write the python script to disk
429 429 -------------------------------
430 430
431 431 $ cat << EOF > gen-revert-cases.py
432 432 > # generate proper file state to test revert behavior
433 433 > import sys
434 434 > import os
435 435 >
436 436 > # content of the file in "base" and "parent"
437 437 > # None means no file at all
438 438 > ctxcontent = {
439 439 > # clean: no change from base to parent
440 440 > 'clean': ['base', 'base'],
441 441 > # modified: file content change from base to parent
442 442 > 'modified': ['base', 'parent'],
443 443 > # added: file is missing from base and added in parent
444 444 > 'added': [None, 'parent'],
445 445 > # removed: file exist in base but is removed from parent
446 446 > 'removed': ['base', None],
447 447 > # file exist neither in base not in parent
448 448 > 'missing': [None, None],
449 449 > }
450 450 >
451 451 > # content of file in working copy
452 452 > wccontent = {
453 453 > # clean: wc content is the same as parent
454 454 > 'clean': lambda cc: cc[1],
455 455 > # revert: wc content is the same as base
456 456 > 'revert': lambda cc: cc[0],
457 457 > # wc: file exist with a content different from base and parent
458 458 > 'wc': lambda cc: 'wc',
459 459 > # removed: file is missing and marked as untracked
460 460 > 'removed': lambda cc: None,
461 461 > # deleted: file is recorded as tracked but missing
462 462 > # rely on file deletion outside of this script
463 463 > 'deleted': lambda cc:'TOBEDELETED',
464 464 > }
465 465 > # untracked-X is a version of X where the file is not tracked (? unknown)
466 466 > wccontent['untracked-clean'] = wccontent['clean']
467 467 > wccontent['untracked-revert'] = wccontent['revert']
468 468 > wccontent['untracked-wc'] = wccontent['wc']
469 469 >
470 470 > # build the combination of possible states
471 471 > combination = []
472 472 > for ctxkey in ctxcontent:
473 473 > for wckey in wccontent:
474 474 > filename = "%s_%s" % (ctxkey, wckey)
475 475 > combination.append((filename, ctxkey, wckey))
476 476 >
477 477 > # make sure we have stable output
478 478 > combination.sort()
479 479 >
480 480 > # retrieve the state we must generate
481 481 > target = sys.argv[1]
482 482 >
483 483 > # compute file content
484 484 > content = []
485 485 > for filename, ctxkey, wckey in combination:
486 486 > cc = ctxcontent[ctxkey]
487 487 > if target == 'filelist':
488 488 > print filename
489 489 > elif target == 'base':
490 490 > content.append((filename, cc[0]))
491 491 > elif target == 'parent':
492 492 > content.append((filename, cc[1]))
493 493 > elif target == 'wc':
494 494 > content.append((filename, wccontent[wckey](cc)))
495 495 > else:
496 496 > print >> sys.stderr, "unknown target:", target
497 497 > sys.exit(1)
498 498 >
499 499 > # write actual content
500 500 > for filename, data in content:
501 501 > if data is not None:
502 502 > f = open(filename, 'w')
503 503 > f.write(data + '\n')
504 504 > f.close()
505 505 > elif os.path.exists(filename):
506 506 > os.remove(filename)
507 507 > EOF
508 508
509 509 check list of planned files
510 510
511 511 $ python gen-revert-cases.py filelist
512 512 added_clean
513 513 added_deleted
514 514 added_removed
515 515 added_revert
516 516 added_untracked-clean
517 517 added_untracked-revert
518 518 added_untracked-wc
519 519 added_wc
520 520 clean_clean
521 521 clean_deleted
522 522 clean_removed
523 523 clean_revert
524 524 clean_untracked-clean
525 525 clean_untracked-revert
526 526 clean_untracked-wc
527 527 clean_wc
528 528 missing_clean
529 529 missing_deleted
530 530 missing_removed
531 531 missing_revert
532 532 missing_untracked-clean
533 533 missing_untracked-revert
534 534 missing_untracked-wc
535 535 missing_wc
536 536 modified_clean
537 537 modified_deleted
538 538 modified_removed
539 539 modified_revert
540 540 modified_untracked-clean
541 541 modified_untracked-revert
542 542 modified_untracked-wc
543 543 modified_wc
544 544 removed_clean
545 545 removed_deleted
546 546 removed_removed
547 547 removed_revert
548 548 removed_untracked-clean
549 549 removed_untracked-revert
550 550 removed_untracked-wc
551 551 removed_wc
552 552
553 553 Script to make a simple text version of the content
554 554 ---------------------------------------------------
555 555
556 556 $ cat << EOF >> dircontent.py
557 557 > # generate a simple text view of the directory for easy comparison
558 558 > import os
559 559 > files = os.listdir('.')
560 560 > files.sort()
561 561 > for filename in files:
562 562 > if os.path.isdir(filename):
563 563 > continue
564 564 > content = open(filename).read()
565 565 > print '%-6s %s' % (content.strip(), filename)
566 566 > EOF
567 567
568 568 Generate appropriate repo state
569 569 -------------------------------
570 570
571 571 $ hg init revert-ref
572 572 $ cd revert-ref
573 573
574 574 Generate base changeset
575 575
576 576 $ python ../gen-revert-cases.py base
577 577 $ hg addremove --similarity 0
578 578 adding clean_clean
579 579 adding clean_deleted
580 580 adding clean_removed
581 581 adding clean_revert
582 582 adding clean_untracked-clean
583 583 adding clean_untracked-revert
584 584 adding clean_untracked-wc
585 585 adding clean_wc
586 586 adding modified_clean
587 587 adding modified_deleted
588 588 adding modified_removed
589 589 adding modified_revert
590 590 adding modified_untracked-clean
591 591 adding modified_untracked-revert
592 592 adding modified_untracked-wc
593 593 adding modified_wc
594 594 adding removed_clean
595 595 adding removed_deleted
596 596 adding removed_removed
597 597 adding removed_revert
598 598 adding removed_untracked-clean
599 599 adding removed_untracked-revert
600 600 adding removed_untracked-wc
601 601 adding removed_wc
602 602 $ hg status
603 603 A clean_clean
604 604 A clean_deleted
605 605 A clean_removed
606 606 A clean_revert
607 607 A clean_untracked-clean
608 608 A clean_untracked-revert
609 609 A clean_untracked-wc
610 610 A clean_wc
611 611 A modified_clean
612 612 A modified_deleted
613 613 A modified_removed
614 614 A modified_revert
615 615 A modified_untracked-clean
616 616 A modified_untracked-revert
617 617 A modified_untracked-wc
618 618 A modified_wc
619 619 A removed_clean
620 620 A removed_deleted
621 621 A removed_removed
622 622 A removed_revert
623 623 A removed_untracked-clean
624 624 A removed_untracked-revert
625 625 A removed_untracked-wc
626 626 A removed_wc
627 627 $ hg commit -m 'base'
628 628
629 629 (create a simple text version of the content)
630 630
631 631 $ python ../dircontent.py > ../content-base.txt
632 632 $ cat ../content-base.txt
633 633 base clean_clean
634 634 base clean_deleted
635 635 base clean_removed
636 636 base clean_revert
637 637 base clean_untracked-clean
638 638 base clean_untracked-revert
639 639 base clean_untracked-wc
640 640 base clean_wc
641 641 base modified_clean
642 642 base modified_deleted
643 643 base modified_removed
644 644 base modified_revert
645 645 base modified_untracked-clean
646 646 base modified_untracked-revert
647 647 base modified_untracked-wc
648 648 base modified_wc
649 649 base removed_clean
650 650 base removed_deleted
651 651 base removed_removed
652 652 base removed_revert
653 653 base removed_untracked-clean
654 654 base removed_untracked-revert
655 655 base removed_untracked-wc
656 656 base removed_wc
657 657
658 658 Create parent changeset
659 659
660 660 $ python ../gen-revert-cases.py parent
661 661 $ hg addremove --similarity 0
662 662 adding added_clean
663 663 adding added_deleted
664 664 adding added_removed
665 665 adding added_revert
666 666 adding added_untracked-clean
667 667 adding added_untracked-revert
668 668 adding added_untracked-wc
669 669 adding added_wc
670 670 removing removed_clean
671 671 removing removed_deleted
672 672 removing removed_removed
673 673 removing removed_revert
674 674 removing removed_untracked-clean
675 675 removing removed_untracked-revert
676 676 removing removed_untracked-wc
677 677 removing removed_wc
678 678 $ hg status
679 679 M modified_clean
680 680 M modified_deleted
681 681 M modified_removed
682 682 M modified_revert
683 683 M modified_untracked-clean
684 684 M modified_untracked-revert
685 685 M modified_untracked-wc
686 686 M modified_wc
687 687 A added_clean
688 688 A added_deleted
689 689 A added_removed
690 690 A added_revert
691 691 A added_untracked-clean
692 692 A added_untracked-revert
693 693 A added_untracked-wc
694 694 A added_wc
695 695 R removed_clean
696 696 R removed_deleted
697 697 R removed_removed
698 698 R removed_revert
699 699 R removed_untracked-clean
700 700 R removed_untracked-revert
701 701 R removed_untracked-wc
702 702 R removed_wc
703 703 $ hg commit -m 'parent'
704 704
705 705 (create a simple text version of the content)
706 706
707 707 $ python ../dircontent.py > ../content-parent.txt
708 708 $ cat ../content-parent.txt
709 709 parent added_clean
710 710 parent added_deleted
711 711 parent added_removed
712 712 parent added_revert
713 713 parent added_untracked-clean
714 714 parent added_untracked-revert
715 715 parent added_untracked-wc
716 716 parent added_wc
717 717 base clean_clean
718 718 base clean_deleted
719 719 base clean_removed
720 720 base clean_revert
721 721 base clean_untracked-clean
722 722 base clean_untracked-revert
723 723 base clean_untracked-wc
724 724 base clean_wc
725 725 parent modified_clean
726 726 parent modified_deleted
727 727 parent modified_removed
728 728 parent modified_revert
729 729 parent modified_untracked-clean
730 730 parent modified_untracked-revert
731 731 parent modified_untracked-wc
732 732 parent modified_wc
733 733
734 734 Setup working directory
735 735
736 736 $ python ../gen-revert-cases.py wc | cat
737 737 $ hg addremove --similarity 0
738 738 removing added_removed
739 739 removing added_revert
740 740 removing added_untracked-revert
741 741 removing clean_removed
742 742 adding missing_deleted
743 743 adding missing_untracked-wc
744 744 adding missing_wc
745 745 removing modified_removed
746 746 adding removed_deleted
747 747 adding removed_revert
748 748 adding removed_untracked-revert
749 749 adding removed_untracked-wc
750 750 adding removed_wc
751 751 $ hg forget *untracked*
752 752 $ rm *deleted*
753 753 $ hg status
754 754 M added_wc
755 755 M clean_wc
756 756 M modified_revert
757 757 M modified_wc
758 758 A missing_wc
759 759 A removed_revert
760 760 A removed_wc
761 761 R added_removed
762 762 R added_revert
763 763 R added_untracked-clean
764 764 R added_untracked-revert
765 765 R added_untracked-wc
766 766 R clean_removed
767 767 R clean_untracked-clean
768 768 R clean_untracked-revert
769 769 R clean_untracked-wc
770 770 R modified_removed
771 771 R modified_untracked-clean
772 772 R modified_untracked-revert
773 773 R modified_untracked-wc
774 774 ! added_deleted
775 775 ! clean_deleted
776 776 ! missing_deleted
777 777 ! modified_deleted
778 778 ! removed_deleted
779 779 ? missing_untracked-wc
780 780 ? removed_untracked-revert
781 781 ? removed_untracked-wc
782 782
783 783 $ hg status --rev 'desc("base")'
784 784 M clean_wc
785 785 M modified_clean
786 786 M modified_wc
787 787 M removed_wc
788 788 A added_clean
789 789 A added_wc
790 790 A missing_wc
791 791 R clean_removed
792 792 R clean_untracked-clean
793 793 R clean_untracked-revert
794 794 R clean_untracked-wc
795 795 R modified_removed
796 796 R modified_untracked-clean
797 797 R modified_untracked-revert
798 798 R modified_untracked-wc
799 799 R removed_clean
800 800 R removed_deleted
801 801 R removed_removed
802 802 R removed_untracked-clean
803 803 R removed_untracked-revert
804 804 R removed_untracked-wc
805 805 ! added_deleted
806 806 ! clean_deleted
807 807 ! missing_deleted
808 808 ! modified_deleted
809 809 ! removed_deleted
810 810 ? missing_untracked-wc
811 811
812 812 (create a simple text version of the content)
813 813
814 814 $ python ../dircontent.py > ../content-wc.txt
815 815 $ cat ../content-wc.txt
816 816 parent added_clean
817 817 parent added_untracked-clean
818 818 wc added_untracked-wc
819 819 wc added_wc
820 820 base clean_clean
821 821 base clean_revert
822 822 base clean_untracked-clean
823 823 base clean_untracked-revert
824 824 wc clean_untracked-wc
825 825 wc clean_wc
826 826 wc missing_untracked-wc
827 827 wc missing_wc
828 828 parent modified_clean
829 829 base modified_revert
830 830 parent modified_untracked-clean
831 831 base modified_untracked-revert
832 832 wc modified_untracked-wc
833 833 wc modified_wc
834 834 base removed_revert
835 835 base removed_untracked-revert
836 836 wc removed_untracked-wc
837 837 wc removed_wc
838 838
839 839 $ cd ..
840 840
841 841 Test revert --all to parent content
842 842 -----------------------------------
843 843
844 844 (setup from reference repo)
845 845
846 846 $ cp -r revert-ref revert-parent-all
847 847 $ cd revert-parent-all
848 848
849 849 check revert output
850 850
851 851 $ hg revert --all
852 852 reverting added_deleted
853 853 undeleting added_removed
854 854 undeleting added_revert
855 855 undeleting added_untracked-clean
856 856 undeleting added_untracked-revert
857 857 undeleting added_untracked-wc
858 858 reverting added_wc
859 859 reverting clean_deleted
860 860 undeleting clean_removed
861 861 undeleting clean_untracked-clean
862 862 undeleting clean_untracked-revert
863 863 undeleting clean_untracked-wc
864 864 reverting clean_wc
865 865 forgetting missing_deleted
866 866 forgetting missing_wc
867 867 reverting modified_deleted
868 868 undeleting modified_removed
869 869 reverting modified_revert
870 870 undeleting modified_untracked-clean
871 871 undeleting modified_untracked-revert
872 872 undeleting modified_untracked-wc
873 873 reverting modified_wc
874 874 forgetting removed_deleted
875 875 forgetting removed_revert
876 876 forgetting removed_wc
877 877
878 878 Compare resulting directory with revert target.
879 879
880 880 The diff is filtered to include change only. The only difference should be
881 881 additional `.orig` backup file when applicable.
882 882
883 883 $ python ../dircontent.py > ../content-parent-all.txt
884 884 $ cd ..
885 885 $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
886 886 +wc added_untracked-wc.orig
887 887 +wc added_wc.orig
888 888 +wc clean_untracked-wc.orig
889 889 +wc clean_wc.orig
890 890 +wc missing_untracked-wc
891 891 +wc missing_wc
892 892 +base modified_revert.orig
893 893 +base modified_untracked-revert.orig
894 894 +wc modified_untracked-wc.orig
895 895 +wc modified_wc.orig
896 896 +base removed_revert
897 897 +base removed_untracked-revert
898 898 +wc removed_untracked-wc
899 899 +wc removed_wc
900 900
901 901 Test revert --all to "base" content
902 902 -----------------------------------
903 903
904 904 (setup from reference repo)
905 905
906 906 $ cp -r revert-ref revert-base-all
907 907 $ cd revert-base-all
908 908
909 909 check revert output
910 910
911 911 Misbehavior:
912 912
913 913 - report "reverting" when file needs no changes
914 914 |
915 | - reverting modified_revert
916 915 | - reverting removed_revert
917 916
918 917 $ hg revert --all --rev 'desc(base)'
919 918 removing added_clean
920 919 removing added_deleted
921 920 removing added_wc
922 921 reverting clean_deleted
923 922 undeleting clean_removed
924 923 undeleting clean_untracked-clean
925 924 undeleting clean_untracked-revert
926 925 undeleting clean_untracked-wc
927 926 reverting clean_wc
928 927 forgetting missing_deleted
929 928 forgetting missing_wc
930 929 reverting modified_clean
931 930 reverting modified_deleted
932 931 undeleting modified_removed
933 reverting modified_revert
934 932 undeleting modified_untracked-clean
935 933 undeleting modified_untracked-revert
936 934 undeleting modified_untracked-wc
937 935 reverting modified_wc
938 936 adding removed_clean
939 937 reverting removed_deleted
940 938 adding removed_removed
941 939 reverting removed_revert
942 940 adding removed_untracked-clean
943 941 adding removed_untracked-revert
944 942 adding removed_untracked-wc
945 943 reverting removed_wc
946 944
947 945 Compare resulting directory with revert target.
948 946
949 947 The diff is filtered to include change only. The only difference should be
950 948 additional `.orig` backup file when applicable.
951 949
952 950 Misbehavior:
953 951
954 952 - no backup for
955 953 | - added_wc (DATA LOSS)
956 954
957 955 $ python ../dircontent.py > ../content-base-all.txt
958 956 $ cd ..
959 957 $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
960 958 +parent added_untracked-clean
961 959 +wc added_untracked-wc
962 960 +wc clean_untracked-wc.orig
963 961 +wc clean_wc.orig
964 962 +wc missing_untracked-wc
965 963 +wc missing_wc
966 964 +parent modified_untracked-clean.orig
967 965 +wc modified_untracked-wc.orig
968 966 +wc modified_wc.orig
969 967 +wc removed_untracked-wc.orig
970 968 +wc removed_wc.orig
971 969
972 970 Test revert to parent content with explicit file name
973 971 -----------------------------------------------------
974 972
975 973 (setup from reference repo)
976 974
977 975 $ cp -r revert-ref revert-parent-explicit
978 976 $ cd revert-parent-explicit
979 977
980 978 revert all files individually and check the output
981 979 (output is expected to be different than in the --all case)
982 980
983 981 $ for file in `python ../gen-revert-cases.py filelist`; do
984 982 > echo '### revert for:' $file;
985 983 > hg revert $file;
986 984 > echo
987 985 > done
988 986 ### revert for: added_clean
989 987 no changes needed to added_clean
990 988
991 989 ### revert for: added_deleted
992 990
993 991 ### revert for: added_removed
994 992
995 993 ### revert for: added_revert
996 994
997 995 ### revert for: added_untracked-clean
998 996
999 997 ### revert for: added_untracked-revert
1000 998
1001 999 ### revert for: added_untracked-wc
1002 1000
1003 1001 ### revert for: added_wc
1004 1002
1005 1003 ### revert for: clean_clean
1006 1004 no changes needed to clean_clean
1007 1005
1008 1006 ### revert for: clean_deleted
1009 1007
1010 1008 ### revert for: clean_removed
1011 1009
1012 1010 ### revert for: clean_revert
1013 1011 no changes needed to clean_revert
1014 1012
1015 1013 ### revert for: clean_untracked-clean
1016 1014
1017 1015 ### revert for: clean_untracked-revert
1018 1016
1019 1017 ### revert for: clean_untracked-wc
1020 1018
1021 1019 ### revert for: clean_wc
1022 1020
1023 1021 ### revert for: missing_clean
1024 1022 missing_clean: no such file in rev * (glob)
1025 1023
1026 1024 ### revert for: missing_deleted
1027 1025
1028 1026 ### revert for: missing_removed
1029 1027 missing_removed: no such file in rev * (glob)
1030 1028
1031 1029 ### revert for: missing_revert
1032 1030 missing_revert: no such file in rev * (glob)
1033 1031
1034 1032 ### revert for: missing_untracked-clean
1035 1033 missing_untracked-clean: no such file in rev * (glob)
1036 1034
1037 1035 ### revert for: missing_untracked-revert
1038 1036 missing_untracked-revert: no such file in rev * (glob)
1039 1037
1040 1038 ### revert for: missing_untracked-wc
1041 1039 file not managed: missing_untracked-wc
1042 1040
1043 1041 ### revert for: missing_wc
1044 1042
1045 1043 ### revert for: modified_clean
1046 1044 no changes needed to modified_clean
1047 1045
1048 1046 ### revert for: modified_deleted
1049 1047
1050 1048 ### revert for: modified_removed
1051 1049
1052 1050 ### revert for: modified_revert
1053 1051
1054 1052 ### revert for: modified_untracked-clean
1055 1053
1056 1054 ### revert for: modified_untracked-revert
1057 1055
1058 1056 ### revert for: modified_untracked-wc
1059 1057
1060 1058 ### revert for: modified_wc
1061 1059
1062 1060 ### revert for: removed_clean
1063 1061 removed_clean: no such file in rev * (glob)
1064 1062
1065 1063 ### revert for: removed_deleted
1066 1064
1067 1065 ### revert for: removed_removed
1068 1066 removed_removed: no such file in rev * (glob)
1069 1067
1070 1068 ### revert for: removed_revert
1071 1069
1072 1070 ### revert for: removed_untracked-clean
1073 1071 removed_untracked-clean: no such file in rev * (glob)
1074 1072
1075 1073 ### revert for: removed_untracked-revert
1076 1074 file not managed: removed_untracked-revert
1077 1075
1078 1076 ### revert for: removed_untracked-wc
1079 1077 file not managed: removed_untracked-wc
1080 1078
1081 1079 ### revert for: removed_wc
1082 1080
1083 1081
1084 1082 check resulting directory againt the --all run
1085 1083 (There should be no difference)
1086 1084
1087 1085 $ python ../dircontent.py > ../content-parent-explicit.txt
1088 1086 $ cd ..
1089 1087 $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
1090 1088 [1]
1091 1089
1092 1090 Test revert to "base" content with explicit file name
1093 1091 -----------------------------------------------------
1094 1092
1095 1093 (setup from reference repo)
1096 1094
1097 1095 $ cp -r revert-ref revert-base-explicit
1098 1096 $ cd revert-base-explicit
1099 1097
1100 1098 revert all files individually and check the output
1101 1099 (output is expected to be different than in the --all case)
1102 1100
1103 1101 Misbehavior:
1104 1102
1105 1103 - fails to report no change to revert for
1106 1104 |
1107 | - modified_revert
1108 1105 | - removed_revert
1109 1106
1110 1107 $ for file in `python ../gen-revert-cases.py filelist`; do
1111 1108 > echo '### revert for:' $file;
1112 1109 > hg revert $file --rev 'desc(base)';
1113 1110 > echo
1114 1111 > done
1115 1112 ### revert for: added_clean
1116 1113
1117 1114 ### revert for: added_deleted
1118 1115
1119 1116 ### revert for: added_removed
1120 1117 no changes needed to added_removed
1121 1118
1122 1119 ### revert for: added_revert
1123 1120 no changes needed to added_revert
1124 1121
1125 1122 ### revert for: added_untracked-clean
1126 1123 no changes needed to added_untracked-clean
1127 1124
1128 1125 ### revert for: added_untracked-revert
1129 1126 no changes needed to added_untracked-revert
1130 1127
1131 1128 ### revert for: added_untracked-wc
1132 1129 no changes needed to added_untracked-wc
1133 1130
1134 1131 ### revert for: added_wc
1135 1132
1136 1133 ### revert for: clean_clean
1137 1134 no changes needed to clean_clean
1138 1135
1139 1136 ### revert for: clean_deleted
1140 1137
1141 1138 ### revert for: clean_removed
1142 1139
1143 1140 ### revert for: clean_revert
1144 1141 no changes needed to clean_revert
1145 1142
1146 1143 ### revert for: clean_untracked-clean
1147 1144
1148 1145 ### revert for: clean_untracked-revert
1149 1146
1150 1147 ### revert for: clean_untracked-wc
1151 1148
1152 1149 ### revert for: clean_wc
1153 1150
1154 1151 ### revert for: missing_clean
1155 1152 missing_clean: no such file in rev * (glob)
1156 1153
1157 1154 ### revert for: missing_deleted
1158 1155
1159 1156 ### revert for: missing_removed
1160 1157 missing_removed: no such file in rev * (glob)
1161 1158
1162 1159 ### revert for: missing_revert
1163 1160 missing_revert: no such file in rev * (glob)
1164 1161
1165 1162 ### revert for: missing_untracked-clean
1166 1163 missing_untracked-clean: no such file in rev * (glob)
1167 1164
1168 1165 ### revert for: missing_untracked-revert
1169 1166 missing_untracked-revert: no such file in rev * (glob)
1170 1167
1171 1168 ### revert for: missing_untracked-wc
1172 1169 file not managed: missing_untracked-wc
1173 1170
1174 1171 ### revert for: missing_wc
1175 1172
1176 1173 ### revert for: modified_clean
1177 1174
1178 1175 ### revert for: modified_deleted
1179 1176
1180 1177 ### revert for: modified_removed
1181 1178
1182 1179 ### revert for: modified_revert
1180 no changes needed to modified_revert
1183 1181
1184 1182 ### revert for: modified_untracked-clean
1185 1183
1186 1184 ### revert for: modified_untracked-revert
1187 1185
1188 1186 ### revert for: modified_untracked-wc
1189 1187
1190 1188 ### revert for: modified_wc
1191 1189
1192 1190 ### revert for: removed_clean
1193 1191
1194 1192 ### revert for: removed_deleted
1195 1193
1196 1194 ### revert for: removed_removed
1197 1195
1198 1196 ### revert for: removed_revert
1199 1197
1200 1198 ### revert for: removed_untracked-clean
1201 1199
1202 1200 ### revert for: removed_untracked-revert
1203 1201
1204 1202 ### revert for: removed_untracked-wc
1205 1203
1206 1204 ### revert for: removed_wc
1207 1205
1208 1206
1209 1207 check resulting directory againt the --all run
1210 1208 (There should be no difference)
1211 1209
1212 1210 $ python ../dircontent.py > ../content-base-explicit.txt
1213 1211 $ cd ..
1214 1212 $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
1215 1213 [1]
General Comments 0
You need to be logged in to leave comments. Login now