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