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