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