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