##// END OF EJS Templates
cmdutil.tryimportone: allow importing relative patches into the working dir...
Siddharth Agarwal -
r24259:5ac8ce04 default
parent child Browse files
Show More
@@ -1,2969 +1,2970 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 encoding
17 17 import lock as lockmod
18 18
19 19 def parsealiases(cmd):
20 20 return cmd.lstrip("^").split("|")
21 21
22 22 def findpossible(cmd, table, strict=False):
23 23 """
24 24 Return cmd -> (aliases, command table entry)
25 25 for each matching command.
26 26 Return debug commands (or their aliases) only if no normal command matches.
27 27 """
28 28 choice = {}
29 29 debugchoice = {}
30 30
31 31 if cmd in table:
32 32 # short-circuit exact matches, "log" alias beats "^log|history"
33 33 keys = [cmd]
34 34 else:
35 35 keys = table.keys()
36 36
37 37 allcmds = []
38 38 for e in keys:
39 39 aliases = parsealiases(e)
40 40 allcmds.extend(aliases)
41 41 found = None
42 42 if cmd in aliases:
43 43 found = cmd
44 44 elif not strict:
45 45 for a in aliases:
46 46 if a.startswith(cmd):
47 47 found = a
48 48 break
49 49 if found is not None:
50 50 if aliases[0].startswith("debug") or found.startswith("debug"):
51 51 debugchoice[found] = (aliases, table[e])
52 52 else:
53 53 choice[found] = (aliases, table[e])
54 54
55 55 if not choice and debugchoice:
56 56 choice = debugchoice
57 57
58 58 return choice, allcmds
59 59
60 60 def findcmd(cmd, table, strict=True):
61 61 """Return (aliases, command table entry) for command string."""
62 62 choice, allcmds = findpossible(cmd, table, strict)
63 63
64 64 if cmd in choice:
65 65 return choice[cmd]
66 66
67 67 if len(choice) > 1:
68 68 clist = choice.keys()
69 69 clist.sort()
70 70 raise error.AmbiguousCommand(cmd, clist)
71 71
72 72 if choice:
73 73 return choice.values()[0]
74 74
75 75 raise error.UnknownCommand(cmd, allcmds)
76 76
77 77 def findrepo(p):
78 78 while not os.path.isdir(os.path.join(p, ".hg")):
79 79 oldp, p = p, os.path.dirname(p)
80 80 if p == oldp:
81 81 return None
82 82
83 83 return p
84 84
85 85 def bailifchanged(repo):
86 86 if repo.dirstate.p2() != nullid:
87 87 raise util.Abort(_('outstanding uncommitted merge'))
88 88 modified, added, removed, deleted = repo.status()[:4]
89 89 if modified or added or removed or deleted:
90 90 raise util.Abort(_('uncommitted changes'))
91 91 ctx = repo[None]
92 92 for s in sorted(ctx.substate):
93 93 if ctx.sub(s).dirty():
94 94 raise util.Abort(_("uncommitted changes in subrepo %s") % s)
95 95
96 96 def logmessage(ui, opts):
97 97 """ get the log message according to -m and -l option """
98 98 message = opts.get('message')
99 99 logfile = opts.get('logfile')
100 100
101 101 if message and logfile:
102 102 raise util.Abort(_('options --message and --logfile are mutually '
103 103 'exclusive'))
104 104 if not message and logfile:
105 105 try:
106 106 if logfile == '-':
107 107 message = ui.fin.read()
108 108 else:
109 109 message = '\n'.join(util.readfile(logfile).splitlines())
110 110 except IOError, inst:
111 111 raise util.Abort(_("can't read commit message '%s': %s") %
112 112 (logfile, inst.strerror))
113 113 return message
114 114
115 115 def mergeeditform(ctxorbool, baseformname):
116 116 """return appropriate editform name (referencing a committemplate)
117 117
118 118 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
119 119 merging is committed.
120 120
121 121 This returns baseformname with '.merge' appended if it is a merge,
122 122 otherwise '.normal' is appended.
123 123 """
124 124 if isinstance(ctxorbool, bool):
125 125 if ctxorbool:
126 126 return baseformname + ".merge"
127 127 elif 1 < len(ctxorbool.parents()):
128 128 return baseformname + ".merge"
129 129
130 130 return baseformname + ".normal"
131 131
132 132 def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
133 133 editform='', **opts):
134 134 """get appropriate commit message editor according to '--edit' option
135 135
136 136 'finishdesc' is a function to be called with edited commit message
137 137 (= 'description' of the new changeset) just after editing, but
138 138 before checking empty-ness. It should return actual text to be
139 139 stored into history. This allows to change description before
140 140 storing.
141 141
142 142 'extramsg' is a extra message to be shown in the editor instead of
143 143 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
144 144 is automatically added.
145 145
146 146 'editform' is a dot-separated list of names, to distinguish
147 147 the purpose of commit text editing.
148 148
149 149 'getcommiteditor' returns 'commitforceeditor' regardless of
150 150 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
151 151 they are specific for usage in MQ.
152 152 """
153 153 if edit or finishdesc or extramsg:
154 154 return lambda r, c, s: commitforceeditor(r, c, s,
155 155 finishdesc=finishdesc,
156 156 extramsg=extramsg,
157 157 editform=editform)
158 158 elif editform:
159 159 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
160 160 else:
161 161 return commiteditor
162 162
163 163 def loglimit(opts):
164 164 """get the log limit according to option -l/--limit"""
165 165 limit = opts.get('limit')
166 166 if limit:
167 167 try:
168 168 limit = int(limit)
169 169 except ValueError:
170 170 raise util.Abort(_('limit must be a positive integer'))
171 171 if limit <= 0:
172 172 raise util.Abort(_('limit must be positive'))
173 173 else:
174 174 limit = None
175 175 return limit
176 176
177 177 def makefilename(repo, pat, node, desc=None,
178 178 total=None, seqno=None, revwidth=None, pathname=None):
179 179 node_expander = {
180 180 'H': lambda: hex(node),
181 181 'R': lambda: str(repo.changelog.rev(node)),
182 182 'h': lambda: short(node),
183 183 'm': lambda: re.sub('[^\w]', '_', str(desc))
184 184 }
185 185 expander = {
186 186 '%': lambda: '%',
187 187 'b': lambda: os.path.basename(repo.root),
188 188 }
189 189
190 190 try:
191 191 if node:
192 192 expander.update(node_expander)
193 193 if node:
194 194 expander['r'] = (lambda:
195 195 str(repo.changelog.rev(node)).zfill(revwidth or 0))
196 196 if total is not None:
197 197 expander['N'] = lambda: str(total)
198 198 if seqno is not None:
199 199 expander['n'] = lambda: str(seqno)
200 200 if total is not None and seqno is not None:
201 201 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
202 202 if pathname is not None:
203 203 expander['s'] = lambda: os.path.basename(pathname)
204 204 expander['d'] = lambda: os.path.dirname(pathname) or '.'
205 205 expander['p'] = lambda: pathname
206 206
207 207 newname = []
208 208 patlen = len(pat)
209 209 i = 0
210 210 while i < patlen:
211 211 c = pat[i]
212 212 if c == '%':
213 213 i += 1
214 214 c = pat[i]
215 215 c = expander[c]()
216 216 newname.append(c)
217 217 i += 1
218 218 return ''.join(newname)
219 219 except KeyError, inst:
220 220 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
221 221 inst.args[0])
222 222
223 223 def makefileobj(repo, pat, node=None, desc=None, total=None,
224 224 seqno=None, revwidth=None, mode='wb', modemap=None,
225 225 pathname=None):
226 226
227 227 writable = mode not in ('r', 'rb')
228 228
229 229 if not pat or pat == '-':
230 230 fp = writable and repo.ui.fout or repo.ui.fin
231 231 if util.safehasattr(fp, 'fileno'):
232 232 return os.fdopen(os.dup(fp.fileno()), mode)
233 233 else:
234 234 # if this fp can't be duped properly, return
235 235 # a dummy object that can be closed
236 236 class wrappedfileobj(object):
237 237 noop = lambda x: None
238 238 def __init__(self, f):
239 239 self.f = f
240 240 def __getattr__(self, attr):
241 241 if attr == 'close':
242 242 return self.noop
243 243 else:
244 244 return getattr(self.f, attr)
245 245
246 246 return wrappedfileobj(fp)
247 247 if util.safehasattr(pat, 'write') and writable:
248 248 return pat
249 249 if util.safehasattr(pat, 'read') and 'r' in mode:
250 250 return pat
251 251 fn = makefilename(repo, pat, node, desc, total, seqno, revwidth, pathname)
252 252 if modemap is not None:
253 253 mode = modemap.get(fn, mode)
254 254 if mode == 'wb':
255 255 modemap[fn] = 'ab'
256 256 return open(fn, mode)
257 257
258 258 def openrevlog(repo, cmd, file_, opts):
259 259 """opens the changelog, manifest, a filelog or a given revlog"""
260 260 cl = opts['changelog']
261 261 mf = opts['manifest']
262 262 msg = None
263 263 if cl and mf:
264 264 msg = _('cannot specify --changelog and --manifest at the same time')
265 265 elif cl or mf:
266 266 if file_:
267 267 msg = _('cannot specify filename with --changelog or --manifest')
268 268 elif not repo:
269 269 msg = _('cannot specify --changelog or --manifest '
270 270 'without a repository')
271 271 if msg:
272 272 raise util.Abort(msg)
273 273
274 274 r = None
275 275 if repo:
276 276 if cl:
277 277 r = repo.unfiltered().changelog
278 278 elif mf:
279 279 r = repo.manifest
280 280 elif file_:
281 281 filelog = repo.file(file_)
282 282 if len(filelog):
283 283 r = filelog
284 284 if not r:
285 285 if not file_:
286 286 raise error.CommandError(cmd, _('invalid arguments'))
287 287 if not os.path.isfile(file_):
288 288 raise util.Abort(_("revlog '%s' not found") % file_)
289 289 r = revlog.revlog(scmutil.opener(os.getcwd(), audit=False),
290 290 file_[:-2] + ".i")
291 291 return r
292 292
293 293 def copy(ui, repo, pats, opts, rename=False):
294 294 # called with the repo lock held
295 295 #
296 296 # hgsep => pathname that uses "/" to separate directories
297 297 # ossep => pathname that uses os.sep to separate directories
298 298 cwd = repo.getcwd()
299 299 targets = {}
300 300 after = opts.get("after")
301 301 dryrun = opts.get("dry_run")
302 302 wctx = repo[None]
303 303
304 304 def walkpat(pat):
305 305 srcs = []
306 306 badstates = after and '?' or '?r'
307 307 m = scmutil.match(repo[None], [pat], opts, globbed=True)
308 308 for abs in repo.walk(m):
309 309 state = repo.dirstate[abs]
310 310 rel = m.rel(abs)
311 311 exact = m.exact(abs)
312 312 if state in badstates:
313 313 if exact and state == '?':
314 314 ui.warn(_('%s: not copying - file is not managed\n') % rel)
315 315 if exact and state == 'r':
316 316 ui.warn(_('%s: not copying - file has been marked for'
317 317 ' remove\n') % rel)
318 318 continue
319 319 # abs: hgsep
320 320 # rel: ossep
321 321 srcs.append((abs, rel, exact))
322 322 return srcs
323 323
324 324 # abssrc: hgsep
325 325 # relsrc: ossep
326 326 # otarget: ossep
327 327 def copyfile(abssrc, relsrc, otarget, exact):
328 328 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
329 329 if '/' in abstarget:
330 330 # We cannot normalize abstarget itself, this would prevent
331 331 # case only renames, like a => A.
332 332 abspath, absname = abstarget.rsplit('/', 1)
333 333 abstarget = repo.dirstate.normalize(abspath) + '/' + absname
334 334 reltarget = repo.pathto(abstarget, cwd)
335 335 target = repo.wjoin(abstarget)
336 336 src = repo.wjoin(abssrc)
337 337 state = repo.dirstate[abstarget]
338 338
339 339 scmutil.checkportable(ui, abstarget)
340 340
341 341 # check for collisions
342 342 prevsrc = targets.get(abstarget)
343 343 if prevsrc is not None:
344 344 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
345 345 (reltarget, repo.pathto(abssrc, cwd),
346 346 repo.pathto(prevsrc, cwd)))
347 347 return
348 348
349 349 # check for overwrites
350 350 exists = os.path.lexists(target)
351 351 samefile = False
352 352 if exists and abssrc != abstarget:
353 353 if (repo.dirstate.normalize(abssrc) ==
354 354 repo.dirstate.normalize(abstarget)):
355 355 if not rename:
356 356 ui.warn(_("%s: can't copy - same file\n") % reltarget)
357 357 return
358 358 exists = False
359 359 samefile = True
360 360
361 361 if not after and exists or after and state in 'mn':
362 362 if not opts['force']:
363 363 ui.warn(_('%s: not overwriting - file exists\n') %
364 364 reltarget)
365 365 return
366 366
367 367 if after:
368 368 if not exists:
369 369 if rename:
370 370 ui.warn(_('%s: not recording move - %s does not exist\n') %
371 371 (relsrc, reltarget))
372 372 else:
373 373 ui.warn(_('%s: not recording copy - %s does not exist\n') %
374 374 (relsrc, reltarget))
375 375 return
376 376 elif not dryrun:
377 377 try:
378 378 if exists:
379 379 os.unlink(target)
380 380 targetdir = os.path.dirname(target) or '.'
381 381 if not os.path.isdir(targetdir):
382 382 os.makedirs(targetdir)
383 383 if samefile:
384 384 tmp = target + "~hgrename"
385 385 os.rename(src, tmp)
386 386 os.rename(tmp, target)
387 387 else:
388 388 util.copyfile(src, target)
389 389 srcexists = True
390 390 except IOError, inst:
391 391 if inst.errno == errno.ENOENT:
392 392 ui.warn(_('%s: deleted in working copy\n') % relsrc)
393 393 srcexists = False
394 394 else:
395 395 ui.warn(_('%s: cannot copy - %s\n') %
396 396 (relsrc, inst.strerror))
397 397 return True # report a failure
398 398
399 399 if ui.verbose or not exact:
400 400 if rename:
401 401 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
402 402 else:
403 403 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
404 404
405 405 targets[abstarget] = abssrc
406 406
407 407 # fix up dirstate
408 408 scmutil.dirstatecopy(ui, repo, wctx, abssrc, abstarget,
409 409 dryrun=dryrun, cwd=cwd)
410 410 if rename and not dryrun:
411 411 if not after and srcexists and not samefile:
412 412 util.unlinkpath(repo.wjoin(abssrc))
413 413 wctx.forget([abssrc])
414 414
415 415 # pat: ossep
416 416 # dest ossep
417 417 # srcs: list of (hgsep, hgsep, ossep, bool)
418 418 # return: function that takes hgsep and returns ossep
419 419 def targetpathfn(pat, dest, srcs):
420 420 if os.path.isdir(pat):
421 421 abspfx = pathutil.canonpath(repo.root, cwd, pat)
422 422 abspfx = util.localpath(abspfx)
423 423 if destdirexists:
424 424 striplen = len(os.path.split(abspfx)[0])
425 425 else:
426 426 striplen = len(abspfx)
427 427 if striplen:
428 428 striplen += len(os.sep)
429 429 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
430 430 elif destdirexists:
431 431 res = lambda p: os.path.join(dest,
432 432 os.path.basename(util.localpath(p)))
433 433 else:
434 434 res = lambda p: dest
435 435 return res
436 436
437 437 # pat: ossep
438 438 # dest ossep
439 439 # srcs: list of (hgsep, hgsep, ossep, bool)
440 440 # return: function that takes hgsep and returns ossep
441 441 def targetpathafterfn(pat, dest, srcs):
442 442 if matchmod.patkind(pat):
443 443 # a mercurial pattern
444 444 res = lambda p: os.path.join(dest,
445 445 os.path.basename(util.localpath(p)))
446 446 else:
447 447 abspfx = pathutil.canonpath(repo.root, cwd, pat)
448 448 if len(abspfx) < len(srcs[0][0]):
449 449 # A directory. Either the target path contains the last
450 450 # component of the source path or it does not.
451 451 def evalpath(striplen):
452 452 score = 0
453 453 for s in srcs:
454 454 t = os.path.join(dest, util.localpath(s[0])[striplen:])
455 455 if os.path.lexists(t):
456 456 score += 1
457 457 return score
458 458
459 459 abspfx = util.localpath(abspfx)
460 460 striplen = len(abspfx)
461 461 if striplen:
462 462 striplen += len(os.sep)
463 463 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
464 464 score = evalpath(striplen)
465 465 striplen1 = len(os.path.split(abspfx)[0])
466 466 if striplen1:
467 467 striplen1 += len(os.sep)
468 468 if evalpath(striplen1) > score:
469 469 striplen = striplen1
470 470 res = lambda p: os.path.join(dest,
471 471 util.localpath(p)[striplen:])
472 472 else:
473 473 # a file
474 474 if destdirexists:
475 475 res = lambda p: os.path.join(dest,
476 476 os.path.basename(util.localpath(p)))
477 477 else:
478 478 res = lambda p: dest
479 479 return res
480 480
481 481
482 482 pats = scmutil.expandpats(pats)
483 483 if not pats:
484 484 raise util.Abort(_('no source or destination specified'))
485 485 if len(pats) == 1:
486 486 raise util.Abort(_('no destination specified'))
487 487 dest = pats.pop()
488 488 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
489 489 if not destdirexists:
490 490 if len(pats) > 1 or matchmod.patkind(pats[0]):
491 491 raise util.Abort(_('with multiple sources, destination must be an '
492 492 'existing directory'))
493 493 if util.endswithsep(dest):
494 494 raise util.Abort(_('destination %s is not a directory') % dest)
495 495
496 496 tfn = targetpathfn
497 497 if after:
498 498 tfn = targetpathafterfn
499 499 copylist = []
500 500 for pat in pats:
501 501 srcs = walkpat(pat)
502 502 if not srcs:
503 503 continue
504 504 copylist.append((tfn(pat, dest, srcs), srcs))
505 505 if not copylist:
506 506 raise util.Abort(_('no files to copy'))
507 507
508 508 errors = 0
509 509 for targetpath, srcs in copylist:
510 510 for abssrc, relsrc, exact in srcs:
511 511 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
512 512 errors += 1
513 513
514 514 if errors:
515 515 ui.warn(_('(consider using --after)\n'))
516 516
517 517 return errors != 0
518 518
519 519 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
520 520 runargs=None, appendpid=False):
521 521 '''Run a command as a service.'''
522 522
523 523 def writepid(pid):
524 524 if opts['pid_file']:
525 525 mode = appendpid and 'a' or 'w'
526 526 fp = open(opts['pid_file'], mode)
527 527 fp.write(str(pid) + '\n')
528 528 fp.close()
529 529
530 530 if opts['daemon'] and not opts['daemon_pipefds']:
531 531 # Signal child process startup with file removal
532 532 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
533 533 os.close(lockfd)
534 534 try:
535 535 if not runargs:
536 536 runargs = util.hgcmd() + sys.argv[1:]
537 537 runargs.append('--daemon-pipefds=%s' % lockpath)
538 538 # Don't pass --cwd to the child process, because we've already
539 539 # changed directory.
540 540 for i in xrange(1, len(runargs)):
541 541 if runargs[i].startswith('--cwd='):
542 542 del runargs[i]
543 543 break
544 544 elif runargs[i].startswith('--cwd'):
545 545 del runargs[i:i + 2]
546 546 break
547 547 def condfn():
548 548 return not os.path.exists(lockpath)
549 549 pid = util.rundetached(runargs, condfn)
550 550 if pid < 0:
551 551 raise util.Abort(_('child process failed to start'))
552 552 writepid(pid)
553 553 finally:
554 554 try:
555 555 os.unlink(lockpath)
556 556 except OSError, e:
557 557 if e.errno != errno.ENOENT:
558 558 raise
559 559 if parentfn:
560 560 return parentfn(pid)
561 561 else:
562 562 return
563 563
564 564 if initfn:
565 565 initfn()
566 566
567 567 if not opts['daemon']:
568 568 writepid(os.getpid())
569 569
570 570 if opts['daemon_pipefds']:
571 571 lockpath = opts['daemon_pipefds']
572 572 try:
573 573 os.setsid()
574 574 except AttributeError:
575 575 pass
576 576 os.unlink(lockpath)
577 577 util.hidewindow()
578 578 sys.stdout.flush()
579 579 sys.stderr.flush()
580 580
581 581 nullfd = os.open(os.devnull, os.O_RDWR)
582 582 logfilefd = nullfd
583 583 if logfile:
584 584 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
585 585 os.dup2(nullfd, 0)
586 586 os.dup2(logfilefd, 1)
587 587 os.dup2(logfilefd, 2)
588 588 if nullfd not in (0, 1, 2):
589 589 os.close(nullfd)
590 590 if logfile and logfilefd not in (0, 1, 2):
591 591 os.close(logfilefd)
592 592
593 593 if runfn:
594 594 return runfn()
595 595
596 596 def tryimportone(ui, repo, hunk, parents, opts, msgs, updatefunc):
597 597 """Utility function used by commands.import to import a single patch
598 598
599 599 This function is explicitly defined here to help the evolve extension to
600 600 wrap this part of the import logic.
601 601
602 602 The API is currently a bit ugly because it a simple code translation from
603 603 the import command. Feel free to make it better.
604 604
605 605 :hunk: a patch (as a binary string)
606 606 :parents: nodes that will be parent of the created commit
607 607 :opts: the full dict of option passed to the import command
608 608 :msgs: list to save commit message to.
609 609 (used in case we need to save it when failing)
610 610 :updatefunc: a function that update a repo to a given node
611 611 updatefunc(<repo>, <node>)
612 612 """
613 613 tmpname, message, user, date, branch, nodeid, p1, p2 = \
614 614 patch.extract(ui, hunk)
615 615
616 616 update = not opts.get('bypass')
617 617 strip = opts["strip"]
618 prefix = opts["prefix"]
618 619 sim = float(opts.get('similarity') or 0)
619 620 if not tmpname:
620 621 return (None, None, False)
621 622 msg = _('applied to working directory')
622 623
623 624 rejects = False
624 625
625 626 try:
626 627 cmdline_message = logmessage(ui, opts)
627 628 if cmdline_message:
628 629 # pickup the cmdline msg
629 630 message = cmdline_message
630 631 elif message:
631 632 # pickup the patch msg
632 633 message = message.strip()
633 634 else:
634 635 # launch the editor
635 636 message = None
636 637 ui.debug('message:\n%s\n' % message)
637 638
638 639 if len(parents) == 1:
639 640 parents.append(repo[nullid])
640 641 if opts.get('exact'):
641 642 if not nodeid or not p1:
642 643 raise util.Abort(_('not a Mercurial patch'))
643 644 p1 = repo[p1]
644 645 p2 = repo[p2 or nullid]
645 646 elif p2:
646 647 try:
647 648 p1 = repo[p1]
648 649 p2 = repo[p2]
649 650 # Without any options, consider p2 only if the
650 651 # patch is being applied on top of the recorded
651 652 # first parent.
652 653 if p1 != parents[0]:
653 654 p1 = parents[0]
654 655 p2 = repo[nullid]
655 656 except error.RepoError:
656 657 p1, p2 = parents
657 658 if p2.node() == nullid:
658 659 ui.warn(_("warning: import the patch as a normal revision\n"
659 660 "(use --exact to import the patch as a merge)\n"))
660 661 else:
661 662 p1, p2 = parents
662 663
663 664 n = None
664 665 if update:
665 666 repo.dirstate.beginparentchange()
666 667 if p1 != parents[0]:
667 668 updatefunc(repo, p1.node())
668 669 if p2 != parents[1]:
669 670 repo.setparents(p1.node(), p2.node())
670 671
671 672 if opts.get('exact') or opts.get('import_branch'):
672 673 repo.dirstate.setbranch(branch or 'default')
673 674
674 675 partial = opts.get('partial', False)
675 676 files = set()
676 677 try:
677 patch.patch(ui, repo, tmpname, strip=strip, files=files,
678 eolmode=None, similarity=sim / 100.0)
678 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
679 files=files, eolmode=None, similarity=sim / 100.0)
679 680 except patch.PatchError, e:
680 681 if not partial:
681 682 raise util.Abort(str(e))
682 683 if partial:
683 684 rejects = True
684 685
685 686 files = list(files)
686 687 if opts.get('no_commit'):
687 688 if message:
688 689 msgs.append(message)
689 690 else:
690 691 if opts.get('exact') or p2:
691 692 # If you got here, you either use --force and know what
692 693 # you are doing or used --exact or a merge patch while
693 694 # being updated to its first parent.
694 695 m = None
695 696 else:
696 697 m = scmutil.matchfiles(repo, files or [])
697 698 editform = mergeeditform(repo[None], 'import.normal')
698 699 if opts.get('exact'):
699 700 editor = None
700 701 else:
701 702 editor = getcommiteditor(editform=editform, **opts)
702 703 n = repo.commit(message, opts.get('user') or user,
703 704 opts.get('date') or date, match=m,
704 705 editor=editor, force=partial)
705 706 repo.dirstate.endparentchange()
706 707 else:
707 708 if opts.get('exact') or opts.get('import_branch'):
708 709 branch = branch or 'default'
709 710 else:
710 711 branch = p1.branch()
711 712 store = patch.filestore()
712 713 try:
713 714 files = set()
714 715 try:
715 716 patch.patchrepo(ui, repo, p1, store, tmpname, strip,
716 717 files, eolmode=None)
717 718 except patch.PatchError, e:
718 719 raise util.Abort(str(e))
719 720 if opts.get('exact'):
720 721 editor = None
721 722 else:
722 723 editor = getcommiteditor(editform='import.bypass')
723 724 memctx = context.makememctx(repo, (p1.node(), p2.node()),
724 725 message,
725 726 opts.get('user') or user,
726 727 opts.get('date') or date,
727 728 branch, files, store,
728 729 editor=editor)
729 730 n = memctx.commit()
730 731 finally:
731 732 store.close()
732 733 if opts.get('exact') and opts.get('no_commit'):
733 734 # --exact with --no-commit is still useful in that it does merge
734 735 # and branch bits
735 736 ui.warn(_("warning: can't check exact import with --no-commit\n"))
736 737 elif opts.get('exact') and hex(n) != nodeid:
737 738 raise util.Abort(_('patch is damaged or loses information'))
738 739 if n:
739 740 # i18n: refers to a short changeset id
740 741 msg = _('created %s') % short(n)
741 742 return (msg, n, rejects)
742 743 finally:
743 744 os.unlink(tmpname)
744 745
745 746 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
746 747 opts=None):
747 748 '''export changesets as hg patches.'''
748 749
749 750 total = len(revs)
750 751 revwidth = max([len(str(rev)) for rev in revs])
751 752 filemode = {}
752 753
753 754 def single(rev, seqno, fp):
754 755 ctx = repo[rev]
755 756 node = ctx.node()
756 757 parents = [p.node() for p in ctx.parents() if p]
757 758 branch = ctx.branch()
758 759 if switch_parent:
759 760 parents.reverse()
760 761 prev = (parents and parents[0]) or nullid
761 762
762 763 shouldclose = False
763 764 if not fp and len(template) > 0:
764 765 desc_lines = ctx.description().rstrip().split('\n')
765 766 desc = desc_lines[0] #Commit always has a first line.
766 767 fp = makefileobj(repo, template, node, desc=desc, total=total,
767 768 seqno=seqno, revwidth=revwidth, mode='wb',
768 769 modemap=filemode)
769 770 if fp != template:
770 771 shouldclose = True
771 772 if fp and fp != sys.stdout and util.safehasattr(fp, 'name'):
772 773 repo.ui.note("%s\n" % fp.name)
773 774
774 775 if not fp:
775 776 write = repo.ui.write
776 777 else:
777 778 def write(s, **kw):
778 779 fp.write(s)
779 780
780 781
781 782 write("# HG changeset patch\n")
782 783 write("# User %s\n" % ctx.user())
783 784 write("# Date %d %d\n" % ctx.date())
784 785 write("# %s\n" % util.datestr(ctx.date()))
785 786 if branch and branch != 'default':
786 787 write("# Branch %s\n" % branch)
787 788 write("# Node ID %s\n" % hex(node))
788 789 write("# Parent %s\n" % hex(prev))
789 790 if len(parents) > 1:
790 791 write("# Parent %s\n" % hex(parents[1]))
791 792 write(ctx.description().rstrip())
792 793 write("\n\n")
793 794
794 795 for chunk, label in patch.diffui(repo, prev, node, opts=opts):
795 796 write(chunk, label=label)
796 797
797 798 if shouldclose:
798 799 fp.close()
799 800
800 801 for seqno, rev in enumerate(revs):
801 802 single(rev, seqno + 1, fp)
802 803
803 804 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
804 805 changes=None, stat=False, fp=None, prefix='',
805 806 listsubrepos=False):
806 807 '''show diff or diffstat.'''
807 808 if fp is None:
808 809 write = ui.write
809 810 else:
810 811 def write(s, **kw):
811 812 fp.write(s)
812 813
813 814 if stat:
814 815 diffopts = diffopts.copy(context=0)
815 816 width = 80
816 817 if not ui.plain():
817 818 width = ui.termwidth()
818 819 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
819 820 prefix=prefix)
820 821 for chunk, label in patch.diffstatui(util.iterlines(chunks),
821 822 width=width,
822 823 git=diffopts.git):
823 824 write(chunk, label=label)
824 825 else:
825 826 for chunk, label in patch.diffui(repo, node1, node2, match,
826 827 changes, diffopts, prefix=prefix):
827 828 write(chunk, label=label)
828 829
829 830 if listsubrepos:
830 831 ctx1 = repo[node1]
831 832 ctx2 = repo[node2]
832 833 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
833 834 tempnode2 = node2
834 835 try:
835 836 if node2 is not None:
836 837 tempnode2 = ctx2.substate[subpath][1]
837 838 except KeyError:
838 839 # A subrepo that existed in node1 was deleted between node1 and
839 840 # node2 (inclusive). Thus, ctx2's substate won't contain that
840 841 # subpath. The best we can do is to ignore it.
841 842 tempnode2 = None
842 843 submatch = matchmod.narrowmatcher(subpath, match)
843 844 sub.diff(ui, diffopts, tempnode2, submatch, changes=changes,
844 845 stat=stat, fp=fp, prefix=prefix)
845 846
846 847 class changeset_printer(object):
847 848 '''show changeset information when templating not requested.'''
848 849
849 850 def __init__(self, ui, repo, matchfn, diffopts, buffered):
850 851 self.ui = ui
851 852 self.repo = repo
852 853 self.buffered = buffered
853 854 self.matchfn = matchfn
854 855 self.diffopts = diffopts
855 856 self.header = {}
856 857 self.hunk = {}
857 858 self.lastheader = None
858 859 self.footer = None
859 860
860 861 def flush(self, rev):
861 862 if rev in self.header:
862 863 h = self.header[rev]
863 864 if h != self.lastheader:
864 865 self.lastheader = h
865 866 self.ui.write(h)
866 867 del self.header[rev]
867 868 if rev in self.hunk:
868 869 self.ui.write(self.hunk[rev])
869 870 del self.hunk[rev]
870 871 return 1
871 872 return 0
872 873
873 874 def close(self):
874 875 if self.footer:
875 876 self.ui.write(self.footer)
876 877
877 878 def show(self, ctx, copies=None, matchfn=None, **props):
878 879 if self.buffered:
879 880 self.ui.pushbuffer()
880 881 self._show(ctx, copies, matchfn, props)
881 882 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
882 883 else:
883 884 self._show(ctx, copies, matchfn, props)
884 885
885 886 def _show(self, ctx, copies, matchfn, props):
886 887 '''show a single changeset or file revision'''
887 888 changenode = ctx.node()
888 889 rev = ctx.rev()
889 890
890 891 if self.ui.quiet:
891 892 self.ui.write("%d:%s\n" % (rev, short(changenode)),
892 893 label='log.node')
893 894 return
894 895
895 896 log = self.repo.changelog
896 897 date = util.datestr(ctx.date())
897 898
898 899 hexfunc = self.ui.debugflag and hex or short
899 900
900 901 parents = [(p, hexfunc(log.node(p)))
901 902 for p in self._meaningful_parentrevs(log, rev)]
902 903
903 904 # i18n: column positioning for "hg log"
904 905 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
905 906 label='log.changeset changeset.%s' % ctx.phasestr())
906 907
907 908 # branches are shown first before any other names due to backwards
908 909 # compatibility
909 910 branch = ctx.branch()
910 911 # don't show the default branch name
911 912 if branch != 'default':
912 913 # i18n: column positioning for "hg log"
913 914 self.ui.write(_("branch: %s\n") % branch,
914 915 label='log.branch')
915 916
916 917 for name, ns in self.repo.names.iteritems():
917 918 # branches has special logic already handled above, so here we just
918 919 # skip it
919 920 if name == 'branches':
920 921 continue
921 922 # we will use the templatename as the color name since those two
922 923 # should be the same
923 924 for name in ns.names(self.repo, changenode):
924 925 self.ui.write(ns.logfmt % name,
925 926 label='log.%s' % ns.colorname)
926 927 if self.ui.debugflag:
927 928 # i18n: column positioning for "hg log"
928 929 self.ui.write(_("phase: %s\n") % _(ctx.phasestr()),
929 930 label='log.phase')
930 931 for parent in parents:
931 932 label = 'log.parent changeset.%s' % self.repo[parent[0]].phasestr()
932 933 # i18n: column positioning for "hg log"
933 934 self.ui.write(_("parent: %d:%s\n") % parent,
934 935 label=label)
935 936
936 937 if self.ui.debugflag:
937 938 mnode = ctx.manifestnode()
938 939 # i18n: column positioning for "hg log"
939 940 self.ui.write(_("manifest: %d:%s\n") %
940 941 (self.repo.manifest.rev(mnode), hex(mnode)),
941 942 label='ui.debug log.manifest')
942 943 # i18n: column positioning for "hg log"
943 944 self.ui.write(_("user: %s\n") % ctx.user(),
944 945 label='log.user')
945 946 # i18n: column positioning for "hg log"
946 947 self.ui.write(_("date: %s\n") % date,
947 948 label='log.date')
948 949
949 950 if self.ui.debugflag:
950 951 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
951 952 for key, value in zip([# i18n: column positioning for "hg log"
952 953 _("files:"),
953 954 # i18n: column positioning for "hg log"
954 955 _("files+:"),
955 956 # i18n: column positioning for "hg log"
956 957 _("files-:")], files):
957 958 if value:
958 959 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
959 960 label='ui.debug log.files')
960 961 elif ctx.files() and self.ui.verbose:
961 962 # i18n: column positioning for "hg log"
962 963 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
963 964 label='ui.note log.files')
964 965 if copies and self.ui.verbose:
965 966 copies = ['%s (%s)' % c for c in copies]
966 967 # i18n: column positioning for "hg log"
967 968 self.ui.write(_("copies: %s\n") % ' '.join(copies),
968 969 label='ui.note log.copies')
969 970
970 971 extra = ctx.extra()
971 972 if extra and self.ui.debugflag:
972 973 for key, value in sorted(extra.items()):
973 974 # i18n: column positioning for "hg log"
974 975 self.ui.write(_("extra: %s=%s\n")
975 976 % (key, value.encode('string_escape')),
976 977 label='ui.debug log.extra')
977 978
978 979 description = ctx.description().strip()
979 980 if description:
980 981 if self.ui.verbose:
981 982 self.ui.write(_("description:\n"),
982 983 label='ui.note log.description')
983 984 self.ui.write(description,
984 985 label='ui.note log.description')
985 986 self.ui.write("\n\n")
986 987 else:
987 988 # i18n: column positioning for "hg log"
988 989 self.ui.write(_("summary: %s\n") %
989 990 description.splitlines()[0],
990 991 label='log.summary')
991 992 self.ui.write("\n")
992 993
993 994 self.showpatch(changenode, matchfn)
994 995
995 996 def showpatch(self, node, matchfn):
996 997 if not matchfn:
997 998 matchfn = self.matchfn
998 999 if matchfn:
999 1000 stat = self.diffopts.get('stat')
1000 1001 diff = self.diffopts.get('patch')
1001 1002 diffopts = patch.diffallopts(self.ui, self.diffopts)
1002 1003 prev = self.repo.changelog.parents(node)[0]
1003 1004 if stat:
1004 1005 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1005 1006 match=matchfn, stat=True)
1006 1007 if diff:
1007 1008 if stat:
1008 1009 self.ui.write("\n")
1009 1010 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1010 1011 match=matchfn, stat=False)
1011 1012 self.ui.write("\n")
1012 1013
1013 1014 def _meaningful_parentrevs(self, log, rev):
1014 1015 """Return list of meaningful (or all if debug) parentrevs for rev.
1015 1016
1016 1017 For merges (two non-nullrev revisions) both parents are meaningful.
1017 1018 Otherwise the first parent revision is considered meaningful if it
1018 1019 is not the preceding revision.
1019 1020 """
1020 1021 parents = log.parentrevs(rev)
1021 1022 if not self.ui.debugflag and parents[1] == nullrev:
1022 1023 if parents[0] >= rev - 1:
1023 1024 parents = []
1024 1025 else:
1025 1026 parents = [parents[0]]
1026 1027 return parents
1027 1028
1028 1029 class jsonchangeset(changeset_printer):
1029 1030 '''format changeset information.'''
1030 1031
1031 1032 def __init__(self, ui, repo, matchfn, diffopts, buffered):
1032 1033 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1033 1034 self.cache = {}
1034 1035 self._first = True
1035 1036
1036 1037 def close(self):
1037 1038 if not self._first:
1038 1039 self.ui.write("\n]\n")
1039 1040 else:
1040 1041 self.ui.write("[]\n")
1041 1042
1042 1043 def _show(self, ctx, copies, matchfn, props):
1043 1044 '''show a single changeset or file revision'''
1044 1045 hexnode = hex(ctx.node())
1045 1046 rev = ctx.rev()
1046 1047 j = encoding.jsonescape
1047 1048
1048 1049 if self._first:
1049 1050 self.ui.write("[\n {")
1050 1051 self._first = False
1051 1052 else:
1052 1053 self.ui.write(",\n {")
1053 1054
1054 1055 if self.ui.quiet:
1055 1056 self.ui.write('\n "rev": %d' % rev)
1056 1057 self.ui.write(',\n "node": "%s"' % hexnode)
1057 1058 self.ui.write('\n }')
1058 1059 return
1059 1060
1060 1061 self.ui.write('\n "rev": %d' % rev)
1061 1062 self.ui.write(',\n "node": "%s"' % hexnode)
1062 1063 self.ui.write(',\n "branch": "%s"' % j(ctx.branch()))
1063 1064 self.ui.write(',\n "phase": "%s"' % ctx.phasestr())
1064 1065 self.ui.write(',\n "user": "%s"' % j(ctx.user()))
1065 1066 self.ui.write(',\n "date": [%d, %d]' % ctx.date())
1066 1067 self.ui.write(',\n "desc": "%s"' % j(ctx.description()))
1067 1068
1068 1069 self.ui.write(',\n "bookmarks": [%s]' %
1069 1070 ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
1070 1071 self.ui.write(',\n "tags": [%s]' %
1071 1072 ", ".join('"%s"' % j(t) for t in ctx.tags()))
1072 1073 self.ui.write(',\n "parents": [%s]' %
1073 1074 ", ".join('"%s"' % c.hex() for c in ctx.parents()))
1074 1075
1075 1076 if self.ui.debugflag:
1076 1077 self.ui.write(',\n "manifest": "%s"' % hex(ctx.manifestnode()))
1077 1078
1078 1079 self.ui.write(',\n "extra": {%s}' %
1079 1080 ", ".join('"%s": "%s"' % (j(k), j(v))
1080 1081 for k, v in ctx.extra().items()))
1081 1082
1082 1083 files = ctx.p1().status(ctx)
1083 1084 self.ui.write(',\n "modified": [%s]' %
1084 1085 ", ".join('"%s"' % j(f) for f in files[0]))
1085 1086 self.ui.write(',\n "added": [%s]' %
1086 1087 ", ".join('"%s"' % j(f) for f in files[1]))
1087 1088 self.ui.write(',\n "removed": [%s]' %
1088 1089 ", ".join('"%s"' % j(f) for f in files[2]))
1089 1090
1090 1091 elif self.ui.verbose:
1091 1092 self.ui.write(',\n "files": [%s]' %
1092 1093 ", ".join('"%s"' % j(f) for f in ctx.files()))
1093 1094
1094 1095 if copies:
1095 1096 self.ui.write(',\n "copies": {%s}' %
1096 1097 ", ".join('"%s": "%s"' % (j(k), j(v))
1097 1098 for k, v in copies))
1098 1099
1099 1100 matchfn = self.matchfn
1100 1101 if matchfn:
1101 1102 stat = self.diffopts.get('stat')
1102 1103 diff = self.diffopts.get('patch')
1103 1104 diffopts = patch.difffeatureopts(self.ui, self.diffopts, git=True)
1104 1105 node, prev = ctx.node(), ctx.p1().node()
1105 1106 if stat:
1106 1107 self.ui.pushbuffer()
1107 1108 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1108 1109 match=matchfn, stat=True)
1109 1110 self.ui.write(',\n "diffstat": "%s"' % j(self.ui.popbuffer()))
1110 1111 if diff:
1111 1112 self.ui.pushbuffer()
1112 1113 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
1113 1114 match=matchfn, stat=False)
1114 1115 self.ui.write(',\n "diff": "%s"' % j(self.ui.popbuffer()))
1115 1116
1116 1117 self.ui.write("\n }")
1117 1118
1118 1119 class changeset_templater(changeset_printer):
1119 1120 '''format changeset information.'''
1120 1121
1121 1122 def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
1122 1123 changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
1123 1124 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
1124 1125 defaulttempl = {
1125 1126 'parent': '{rev}:{node|formatnode} ',
1126 1127 'manifest': '{rev}:{node|formatnode}',
1127 1128 'file_copy': '{name} ({source})',
1128 1129 'extra': '{key}={value|stringescape}'
1129 1130 }
1130 1131 # filecopy is preserved for compatibility reasons
1131 1132 defaulttempl['filecopy'] = defaulttempl['file_copy']
1132 1133 self.t = templater.templater(mapfile, {'formatnode': formatnode},
1133 1134 cache=defaulttempl)
1134 1135 if tmpl:
1135 1136 self.t.cache['changeset'] = tmpl
1136 1137
1137 1138 self.cache = {}
1138 1139
1139 1140 def _meaningful_parentrevs(self, ctx):
1140 1141 """Return list of meaningful (or all if debug) parentrevs for rev.
1141 1142 """
1142 1143 parents = ctx.parents()
1143 1144 if len(parents) > 1:
1144 1145 return parents
1145 1146 if self.ui.debugflag:
1146 1147 return [parents[0], self.repo['null']]
1147 1148 if parents[0].rev() >= ctx.rev() - 1:
1148 1149 return []
1149 1150 return parents
1150 1151
1151 1152 def _show(self, ctx, copies, matchfn, props):
1152 1153 '''show a single changeset or file revision'''
1153 1154
1154 1155 showlist = templatekw.showlist
1155 1156
1156 1157 # showparents() behaviour depends on ui trace level which
1157 1158 # causes unexpected behaviours at templating level and makes
1158 1159 # it harder to extract it in a standalone function. Its
1159 1160 # behaviour cannot be changed so leave it here for now.
1160 1161 def showparents(**args):
1161 1162 ctx = args['ctx']
1162 1163 parents = [[('rev', p.rev()),
1163 1164 ('node', p.hex()),
1164 1165 ('phase', p.phasestr())]
1165 1166 for p in self._meaningful_parentrevs(ctx)]
1166 1167 return showlist('parent', parents, **args)
1167 1168
1168 1169 props = props.copy()
1169 1170 props.update(templatekw.keywords)
1170 1171 props['parents'] = showparents
1171 1172 props['templ'] = self.t
1172 1173 props['ctx'] = ctx
1173 1174 props['repo'] = self.repo
1174 1175 props['revcache'] = {'copies': copies}
1175 1176 props['cache'] = self.cache
1176 1177
1177 1178 # find correct templates for current mode
1178 1179
1179 1180 tmplmodes = [
1180 1181 (True, None),
1181 1182 (self.ui.verbose, 'verbose'),
1182 1183 (self.ui.quiet, 'quiet'),
1183 1184 (self.ui.debugflag, 'debug'),
1184 1185 ]
1185 1186
1186 1187 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
1187 1188 for mode, postfix in tmplmodes:
1188 1189 for type in types:
1189 1190 cur = postfix and ('%s_%s' % (type, postfix)) or type
1190 1191 if mode and cur in self.t:
1191 1192 types[type] = cur
1192 1193
1193 1194 try:
1194 1195
1195 1196 # write header
1196 1197 if types['header']:
1197 1198 h = templater.stringify(self.t(types['header'], **props))
1198 1199 if self.buffered:
1199 1200 self.header[ctx.rev()] = h
1200 1201 else:
1201 1202 if self.lastheader != h:
1202 1203 self.lastheader = h
1203 1204 self.ui.write(h)
1204 1205
1205 1206 # write changeset metadata, then patch if requested
1206 1207 key = types['changeset']
1207 1208 self.ui.write(templater.stringify(self.t(key, **props)))
1208 1209 self.showpatch(ctx.node(), matchfn)
1209 1210
1210 1211 if types['footer']:
1211 1212 if not self.footer:
1212 1213 self.footer = templater.stringify(self.t(types['footer'],
1213 1214 **props))
1214 1215
1215 1216 except KeyError, inst:
1216 1217 msg = _("%s: no key named '%s'")
1217 1218 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1218 1219 except SyntaxError, inst:
1219 1220 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1220 1221
1221 1222 def gettemplate(ui, tmpl, style):
1222 1223 """
1223 1224 Find the template matching the given template spec or style.
1224 1225 """
1225 1226
1226 1227 # ui settings
1227 1228 if not tmpl and not style: # template are stronger than style
1228 1229 tmpl = ui.config('ui', 'logtemplate')
1229 1230 if tmpl:
1230 1231 try:
1231 1232 tmpl = templater.parsestring(tmpl)
1232 1233 except SyntaxError:
1233 1234 tmpl = templater.parsestring(tmpl, quoted=False)
1234 1235 return tmpl, None
1235 1236 else:
1236 1237 style = util.expandpath(ui.config('ui', 'style', ''))
1237 1238
1238 1239 if not tmpl and style:
1239 1240 mapfile = style
1240 1241 if not os.path.split(mapfile)[0]:
1241 1242 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1242 1243 or templater.templatepath(mapfile))
1243 1244 if mapname:
1244 1245 mapfile = mapname
1245 1246 return None, mapfile
1246 1247
1247 1248 if not tmpl:
1248 1249 return None, None
1249 1250
1250 1251 # looks like a literal template?
1251 1252 if '{' in tmpl:
1252 1253 return tmpl, None
1253 1254
1254 1255 # perhaps a stock style?
1255 1256 if not os.path.split(tmpl)[0]:
1256 1257 mapname = (templater.templatepath('map-cmdline.' + tmpl)
1257 1258 or templater.templatepath(tmpl))
1258 1259 if mapname and os.path.isfile(mapname):
1259 1260 return None, mapname
1260 1261
1261 1262 # perhaps it's a reference to [templates]
1262 1263 t = ui.config('templates', tmpl)
1263 1264 if t:
1264 1265 try:
1265 1266 tmpl = templater.parsestring(t)
1266 1267 except SyntaxError:
1267 1268 tmpl = templater.parsestring(t, quoted=False)
1268 1269 return tmpl, None
1269 1270
1270 1271 if tmpl == 'list':
1271 1272 ui.write(_("available styles: %s\n") % templater.stylelist())
1272 1273 raise util.Abort(_("specify a template"))
1273 1274
1274 1275 # perhaps it's a path to a map or a template
1275 1276 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
1276 1277 # is it a mapfile for a style?
1277 1278 if os.path.basename(tmpl).startswith("map-"):
1278 1279 return None, os.path.realpath(tmpl)
1279 1280 tmpl = open(tmpl).read()
1280 1281 return tmpl, None
1281 1282
1282 1283 # constant string?
1283 1284 return tmpl, None
1284 1285
1285 1286 def show_changeset(ui, repo, opts, buffered=False):
1286 1287 """show one changeset using template or regular display.
1287 1288
1288 1289 Display format will be the first non-empty hit of:
1289 1290 1. option 'template'
1290 1291 2. option 'style'
1291 1292 3. [ui] setting 'logtemplate'
1292 1293 4. [ui] setting 'style'
1293 1294 If all of these values are either the unset or the empty string,
1294 1295 regular display via changeset_printer() is done.
1295 1296 """
1296 1297 # options
1297 1298 matchfn = None
1298 1299 if opts.get('patch') or opts.get('stat'):
1299 1300 matchfn = scmutil.matchall(repo)
1300 1301
1301 1302 if opts.get('template') == 'json':
1302 1303 return jsonchangeset(ui, repo, matchfn, opts, buffered)
1303 1304
1304 1305 tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
1305 1306
1306 1307 if not tmpl and not mapfile:
1307 1308 return changeset_printer(ui, repo, matchfn, opts, buffered)
1308 1309
1309 1310 try:
1310 1311 t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
1311 1312 buffered)
1312 1313 except SyntaxError, inst:
1313 1314 raise util.Abort(inst.args[0])
1314 1315 return t
1315 1316
1316 1317 def showmarker(ui, marker):
1317 1318 """utility function to display obsolescence marker in a readable way
1318 1319
1319 1320 To be used by debug function."""
1320 1321 ui.write(hex(marker.precnode()))
1321 1322 for repl in marker.succnodes():
1322 1323 ui.write(' ')
1323 1324 ui.write(hex(repl))
1324 1325 ui.write(' %X ' % marker.flags())
1325 1326 parents = marker.parentnodes()
1326 1327 if parents is not None:
1327 1328 ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
1328 1329 ui.write('(%s) ' % util.datestr(marker.date()))
1329 1330 ui.write('{%s}' % (', '.join('%r: %r' % t for t in
1330 1331 sorted(marker.metadata().items())
1331 1332 if t[0] != 'date')))
1332 1333 ui.write('\n')
1333 1334
1334 1335 def finddate(ui, repo, date):
1335 1336 """Find the tipmost changeset that matches the given date spec"""
1336 1337
1337 1338 df = util.matchdate(date)
1338 1339 m = scmutil.matchall(repo)
1339 1340 results = {}
1340 1341
1341 1342 def prep(ctx, fns):
1342 1343 d = ctx.date()
1343 1344 if df(d[0]):
1344 1345 results[ctx.rev()] = d
1345 1346
1346 1347 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1347 1348 rev = ctx.rev()
1348 1349 if rev in results:
1349 1350 ui.status(_("found revision %s from %s\n") %
1350 1351 (rev, util.datestr(results[rev])))
1351 1352 return str(rev)
1352 1353
1353 1354 raise util.Abort(_("revision matching date not found"))
1354 1355
1355 1356 def increasingwindows(windowsize=8, sizelimit=512):
1356 1357 while True:
1357 1358 yield windowsize
1358 1359 if windowsize < sizelimit:
1359 1360 windowsize *= 2
1360 1361
1361 1362 class FileWalkError(Exception):
1362 1363 pass
1363 1364
1364 1365 def walkfilerevs(repo, match, follow, revs, fncache):
1365 1366 '''Walks the file history for the matched files.
1366 1367
1367 1368 Returns the changeset revs that are involved in the file history.
1368 1369
1369 1370 Throws FileWalkError if the file history can't be walked using
1370 1371 filelogs alone.
1371 1372 '''
1372 1373 wanted = set()
1373 1374 copies = []
1374 1375 minrev, maxrev = min(revs), max(revs)
1375 1376 def filerevgen(filelog, last):
1376 1377 """
1377 1378 Only files, no patterns. Check the history of each file.
1378 1379
1379 1380 Examines filelog entries within minrev, maxrev linkrev range
1380 1381 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1381 1382 tuples in backwards order
1382 1383 """
1383 1384 cl_count = len(repo)
1384 1385 revs = []
1385 1386 for j in xrange(0, last + 1):
1386 1387 linkrev = filelog.linkrev(j)
1387 1388 if linkrev < minrev:
1388 1389 continue
1389 1390 # only yield rev for which we have the changelog, it can
1390 1391 # happen while doing "hg log" during a pull or commit
1391 1392 if linkrev >= cl_count:
1392 1393 break
1393 1394
1394 1395 parentlinkrevs = []
1395 1396 for p in filelog.parentrevs(j):
1396 1397 if p != nullrev:
1397 1398 parentlinkrevs.append(filelog.linkrev(p))
1398 1399 n = filelog.node(j)
1399 1400 revs.append((linkrev, parentlinkrevs,
1400 1401 follow and filelog.renamed(n)))
1401 1402
1402 1403 return reversed(revs)
1403 1404 def iterfiles():
1404 1405 pctx = repo['.']
1405 1406 for filename in match.files():
1406 1407 if follow:
1407 1408 if filename not in pctx:
1408 1409 raise util.Abort(_('cannot follow file not in parent '
1409 1410 'revision: "%s"') % filename)
1410 1411 yield filename, pctx[filename].filenode()
1411 1412 else:
1412 1413 yield filename, None
1413 1414 for filename_node in copies:
1414 1415 yield filename_node
1415 1416
1416 1417 for file_, node in iterfiles():
1417 1418 filelog = repo.file(file_)
1418 1419 if not len(filelog):
1419 1420 if node is None:
1420 1421 # A zero count may be a directory or deleted file, so
1421 1422 # try to find matching entries on the slow path.
1422 1423 if follow:
1423 1424 raise util.Abort(
1424 1425 _('cannot follow nonexistent file: "%s"') % file_)
1425 1426 raise FileWalkError("Cannot walk via filelog")
1426 1427 else:
1427 1428 continue
1428 1429
1429 1430 if node is None:
1430 1431 last = len(filelog) - 1
1431 1432 else:
1432 1433 last = filelog.rev(node)
1433 1434
1434 1435
1435 1436 # keep track of all ancestors of the file
1436 1437 ancestors = set([filelog.linkrev(last)])
1437 1438
1438 1439 # iterate from latest to oldest revision
1439 1440 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1440 1441 if not follow:
1441 1442 if rev > maxrev:
1442 1443 continue
1443 1444 else:
1444 1445 # Note that last might not be the first interesting
1445 1446 # rev to us:
1446 1447 # if the file has been changed after maxrev, we'll
1447 1448 # have linkrev(last) > maxrev, and we still need
1448 1449 # to explore the file graph
1449 1450 if rev not in ancestors:
1450 1451 continue
1451 1452 # XXX insert 1327 fix here
1452 1453 if flparentlinkrevs:
1453 1454 ancestors.update(flparentlinkrevs)
1454 1455
1455 1456 fncache.setdefault(rev, []).append(file_)
1456 1457 wanted.add(rev)
1457 1458 if copied:
1458 1459 copies.append(copied)
1459 1460
1460 1461 return wanted
1461 1462
1462 1463 def walkchangerevs(repo, match, opts, prepare):
1463 1464 '''Iterate over files and the revs in which they changed.
1464 1465
1465 1466 Callers most commonly need to iterate backwards over the history
1466 1467 in which they are interested. Doing so has awful (quadratic-looking)
1467 1468 performance, so we use iterators in a "windowed" way.
1468 1469
1469 1470 We walk a window of revisions in the desired order. Within the
1470 1471 window, we first walk forwards to gather data, then in the desired
1471 1472 order (usually backwards) to display it.
1472 1473
1473 1474 This function returns an iterator yielding contexts. Before
1474 1475 yielding each context, the iterator will first call the prepare
1475 1476 function on each context in the window in forward order.'''
1476 1477
1477 1478 follow = opts.get('follow') or opts.get('follow_first')
1478 1479 revs = _logrevs(repo, opts)
1479 1480 if not revs:
1480 1481 return []
1481 1482 wanted = set()
1482 1483 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1483 1484 fncache = {}
1484 1485 change = repo.changectx
1485 1486
1486 1487 # First step is to fill wanted, the set of revisions that we want to yield.
1487 1488 # When it does not induce extra cost, we also fill fncache for revisions in
1488 1489 # wanted: a cache of filenames that were changed (ctx.files()) and that
1489 1490 # match the file filtering conditions.
1490 1491
1491 1492 if not slowpath and not match.files():
1492 1493 # No files, no patterns. Display all revs.
1493 1494 wanted = revs
1494 1495
1495 1496 if not slowpath and match.files():
1496 1497 # We only have to read through the filelog to find wanted revisions
1497 1498
1498 1499 try:
1499 1500 wanted = walkfilerevs(repo, match, follow, revs, fncache)
1500 1501 except FileWalkError:
1501 1502 slowpath = True
1502 1503
1503 1504 # We decided to fall back to the slowpath because at least one
1504 1505 # of the paths was not a file. Check to see if at least one of them
1505 1506 # existed in history, otherwise simply return
1506 1507 for path in match.files():
1507 1508 if path == '.' or path in repo.store:
1508 1509 break
1509 1510 else:
1510 1511 return []
1511 1512
1512 1513 if slowpath:
1513 1514 # We have to read the changelog to match filenames against
1514 1515 # changed files
1515 1516
1516 1517 if follow:
1517 1518 raise util.Abort(_('can only follow copies/renames for explicit '
1518 1519 'filenames'))
1519 1520
1520 1521 # The slow path checks files modified in every changeset.
1521 1522 # This is really slow on large repos, so compute the set lazily.
1522 1523 class lazywantedset(object):
1523 1524 def __init__(self):
1524 1525 self.set = set()
1525 1526 self.revs = set(revs)
1526 1527
1527 1528 # No need to worry about locality here because it will be accessed
1528 1529 # in the same order as the increasing window below.
1529 1530 def __contains__(self, value):
1530 1531 if value in self.set:
1531 1532 return True
1532 1533 elif not value in self.revs:
1533 1534 return False
1534 1535 else:
1535 1536 self.revs.discard(value)
1536 1537 ctx = change(value)
1537 1538 matches = filter(match, ctx.files())
1538 1539 if matches:
1539 1540 fncache[value] = matches
1540 1541 self.set.add(value)
1541 1542 return True
1542 1543 return False
1543 1544
1544 1545 def discard(self, value):
1545 1546 self.revs.discard(value)
1546 1547 self.set.discard(value)
1547 1548
1548 1549 wanted = lazywantedset()
1549 1550
1550 1551 class followfilter(object):
1551 1552 def __init__(self, onlyfirst=False):
1552 1553 self.startrev = nullrev
1553 1554 self.roots = set()
1554 1555 self.onlyfirst = onlyfirst
1555 1556
1556 1557 def match(self, rev):
1557 1558 def realparents(rev):
1558 1559 if self.onlyfirst:
1559 1560 return repo.changelog.parentrevs(rev)[0:1]
1560 1561 else:
1561 1562 return filter(lambda x: x != nullrev,
1562 1563 repo.changelog.parentrevs(rev))
1563 1564
1564 1565 if self.startrev == nullrev:
1565 1566 self.startrev = rev
1566 1567 return True
1567 1568
1568 1569 if rev > self.startrev:
1569 1570 # forward: all descendants
1570 1571 if not self.roots:
1571 1572 self.roots.add(self.startrev)
1572 1573 for parent in realparents(rev):
1573 1574 if parent in self.roots:
1574 1575 self.roots.add(rev)
1575 1576 return True
1576 1577 else:
1577 1578 # backwards: all parents
1578 1579 if not self.roots:
1579 1580 self.roots.update(realparents(self.startrev))
1580 1581 if rev in self.roots:
1581 1582 self.roots.remove(rev)
1582 1583 self.roots.update(realparents(rev))
1583 1584 return True
1584 1585
1585 1586 return False
1586 1587
1587 1588 # it might be worthwhile to do this in the iterator if the rev range
1588 1589 # is descending and the prune args are all within that range
1589 1590 for rev in opts.get('prune', ()):
1590 1591 rev = repo[rev].rev()
1591 1592 ff = followfilter()
1592 1593 stop = min(revs[0], revs[-1])
1593 1594 for x in xrange(rev, stop - 1, -1):
1594 1595 if ff.match(x):
1595 1596 wanted = wanted - [x]
1596 1597
1597 1598 # Now that wanted is correctly initialized, we can iterate over the
1598 1599 # revision range, yielding only revisions in wanted.
1599 1600 def iterate():
1600 1601 if follow and not match.files():
1601 1602 ff = followfilter(onlyfirst=opts.get('follow_first'))
1602 1603 def want(rev):
1603 1604 return ff.match(rev) and rev in wanted
1604 1605 else:
1605 1606 def want(rev):
1606 1607 return rev in wanted
1607 1608
1608 1609 it = iter(revs)
1609 1610 stopiteration = False
1610 1611 for windowsize in increasingwindows():
1611 1612 nrevs = []
1612 1613 for i in xrange(windowsize):
1613 1614 try:
1614 1615 rev = it.next()
1615 1616 if want(rev):
1616 1617 nrevs.append(rev)
1617 1618 except (StopIteration):
1618 1619 stopiteration = True
1619 1620 break
1620 1621 for rev in sorted(nrevs):
1621 1622 fns = fncache.get(rev)
1622 1623 ctx = change(rev)
1623 1624 if not fns:
1624 1625 def fns_generator():
1625 1626 for f in ctx.files():
1626 1627 if match(f):
1627 1628 yield f
1628 1629 fns = fns_generator()
1629 1630 prepare(ctx, fns)
1630 1631 for rev in nrevs:
1631 1632 yield change(rev)
1632 1633
1633 1634 if stopiteration:
1634 1635 break
1635 1636
1636 1637 return iterate()
1637 1638
1638 1639 def _makefollowlogfilematcher(repo, files, followfirst):
1639 1640 # When displaying a revision with --patch --follow FILE, we have
1640 1641 # to know which file of the revision must be diffed. With
1641 1642 # --follow, we want the names of the ancestors of FILE in the
1642 1643 # revision, stored in "fcache". "fcache" is populated by
1643 1644 # reproducing the graph traversal already done by --follow revset
1644 1645 # and relating linkrevs to file names (which is not "correct" but
1645 1646 # good enough).
1646 1647 fcache = {}
1647 1648 fcacheready = [False]
1648 1649 pctx = repo['.']
1649 1650
1650 1651 def populate():
1651 1652 for fn in files:
1652 1653 for i in ((pctx[fn],), pctx[fn].ancestors(followfirst=followfirst)):
1653 1654 for c in i:
1654 1655 fcache.setdefault(c.linkrev(), set()).add(c.path())
1655 1656
1656 1657 def filematcher(rev):
1657 1658 if not fcacheready[0]:
1658 1659 # Lazy initialization
1659 1660 fcacheready[0] = True
1660 1661 populate()
1661 1662 return scmutil.matchfiles(repo, fcache.get(rev, []))
1662 1663
1663 1664 return filematcher
1664 1665
1665 1666 def _makenofollowlogfilematcher(repo, pats, opts):
1666 1667 '''hook for extensions to override the filematcher for non-follow cases'''
1667 1668 return None
1668 1669
1669 1670 def _makelogrevset(repo, pats, opts, revs):
1670 1671 """Return (expr, filematcher) where expr is a revset string built
1671 1672 from log options and file patterns or None. If --stat or --patch
1672 1673 are not passed filematcher is None. Otherwise it is a callable
1673 1674 taking a revision number and returning a match objects filtering
1674 1675 the files to be detailed when displaying the revision.
1675 1676 """
1676 1677 opt2revset = {
1677 1678 'no_merges': ('not merge()', None),
1678 1679 'only_merges': ('merge()', None),
1679 1680 '_ancestors': ('ancestors(%(val)s)', None),
1680 1681 '_fancestors': ('_firstancestors(%(val)s)', None),
1681 1682 '_descendants': ('descendants(%(val)s)', None),
1682 1683 '_fdescendants': ('_firstdescendants(%(val)s)', None),
1683 1684 '_matchfiles': ('_matchfiles(%(val)s)', None),
1684 1685 'date': ('date(%(val)r)', None),
1685 1686 'branch': ('branch(%(val)r)', ' or '),
1686 1687 '_patslog': ('filelog(%(val)r)', ' or '),
1687 1688 '_patsfollow': ('follow(%(val)r)', ' or '),
1688 1689 '_patsfollowfirst': ('_followfirst(%(val)r)', ' or '),
1689 1690 'keyword': ('keyword(%(val)r)', ' or '),
1690 1691 'prune': ('not (%(val)r or ancestors(%(val)r))', ' and '),
1691 1692 'user': ('user(%(val)r)', ' or '),
1692 1693 }
1693 1694
1694 1695 opts = dict(opts)
1695 1696 # follow or not follow?
1696 1697 follow = opts.get('follow') or opts.get('follow_first')
1697 1698 followfirst = opts.get('follow_first') and 1 or 0
1698 1699 # --follow with FILE behaviour depends on revs...
1699 1700 it = iter(revs)
1700 1701 startrev = it.next()
1701 1702 try:
1702 1703 followdescendants = startrev < it.next()
1703 1704 except (StopIteration):
1704 1705 followdescendants = False
1705 1706
1706 1707 # branch and only_branch are really aliases and must be handled at
1707 1708 # the same time
1708 1709 opts['branch'] = opts.get('branch', []) + opts.get('only_branch', [])
1709 1710 opts['branch'] = [repo.lookupbranch(b) for b in opts['branch']]
1710 1711 # pats/include/exclude are passed to match.match() directly in
1711 1712 # _matchfiles() revset but walkchangerevs() builds its matcher with
1712 1713 # scmutil.match(). The difference is input pats are globbed on
1713 1714 # platforms without shell expansion (windows).
1714 1715 pctx = repo[None]
1715 1716 match, pats = scmutil.matchandpats(pctx, pats, opts)
1716 1717 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1717 1718 if not slowpath:
1718 1719 for f in match.files():
1719 1720 if follow and f not in pctx:
1720 1721 # If the file exists, it may be a directory, so let it
1721 1722 # take the slow path.
1722 1723 if os.path.exists(repo.wjoin(f)):
1723 1724 slowpath = True
1724 1725 continue
1725 1726 else:
1726 1727 raise util.Abort(_('cannot follow file not in parent '
1727 1728 'revision: "%s"') % f)
1728 1729 filelog = repo.file(f)
1729 1730 if not filelog:
1730 1731 # A zero count may be a directory or deleted file, so
1731 1732 # try to find matching entries on the slow path.
1732 1733 if follow:
1733 1734 raise util.Abort(
1734 1735 _('cannot follow nonexistent file: "%s"') % f)
1735 1736 slowpath = True
1736 1737
1737 1738 # We decided to fall back to the slowpath because at least one
1738 1739 # of the paths was not a file. Check to see if at least one of them
1739 1740 # existed in history - in that case, we'll continue down the
1740 1741 # slowpath; otherwise, we can turn off the slowpath
1741 1742 if slowpath:
1742 1743 for path in match.files():
1743 1744 if path == '.' or path in repo.store:
1744 1745 break
1745 1746 else:
1746 1747 slowpath = False
1747 1748
1748 1749 fpats = ('_patsfollow', '_patsfollowfirst')
1749 1750 fnopats = (('_ancestors', '_fancestors'),
1750 1751 ('_descendants', '_fdescendants'))
1751 1752 if slowpath:
1752 1753 # See walkchangerevs() slow path.
1753 1754 #
1754 1755 # pats/include/exclude cannot be represented as separate
1755 1756 # revset expressions as their filtering logic applies at file
1756 1757 # level. For instance "-I a -X a" matches a revision touching
1757 1758 # "a" and "b" while "file(a) and not file(b)" does
1758 1759 # not. Besides, filesets are evaluated against the working
1759 1760 # directory.
1760 1761 matchargs = ['r:', 'd:relpath']
1761 1762 for p in pats:
1762 1763 matchargs.append('p:' + p)
1763 1764 for p in opts.get('include', []):
1764 1765 matchargs.append('i:' + p)
1765 1766 for p in opts.get('exclude', []):
1766 1767 matchargs.append('x:' + p)
1767 1768 matchargs = ','.join(('%r' % p) for p in matchargs)
1768 1769 opts['_matchfiles'] = matchargs
1769 1770 if follow:
1770 1771 opts[fnopats[0][followfirst]] = '.'
1771 1772 else:
1772 1773 if follow:
1773 1774 if pats:
1774 1775 # follow() revset interprets its file argument as a
1775 1776 # manifest entry, so use match.files(), not pats.
1776 1777 opts[fpats[followfirst]] = list(match.files())
1777 1778 else:
1778 1779 op = fnopats[followdescendants][followfirst]
1779 1780 opts[op] = 'rev(%d)' % startrev
1780 1781 else:
1781 1782 opts['_patslog'] = list(pats)
1782 1783
1783 1784 filematcher = None
1784 1785 if opts.get('patch') or opts.get('stat'):
1785 1786 # When following files, track renames via a special matcher.
1786 1787 # If we're forced to take the slowpath it means we're following
1787 1788 # at least one pattern/directory, so don't bother with rename tracking.
1788 1789 if follow and not match.always() and not slowpath:
1789 1790 # _makefollowlogfilematcher expects its files argument to be
1790 1791 # relative to the repo root, so use match.files(), not pats.
1791 1792 filematcher = _makefollowlogfilematcher(repo, match.files(),
1792 1793 followfirst)
1793 1794 else:
1794 1795 filematcher = _makenofollowlogfilematcher(repo, pats, opts)
1795 1796 if filematcher is None:
1796 1797 filematcher = lambda rev: match
1797 1798
1798 1799 expr = []
1799 1800 for op, val in sorted(opts.iteritems()):
1800 1801 if not val:
1801 1802 continue
1802 1803 if op not in opt2revset:
1803 1804 continue
1804 1805 revop, andor = opt2revset[op]
1805 1806 if '%(val)' not in revop:
1806 1807 expr.append(revop)
1807 1808 else:
1808 1809 if not isinstance(val, list):
1809 1810 e = revop % {'val': val}
1810 1811 else:
1811 1812 e = '(' + andor.join((revop % {'val': v}) for v in val) + ')'
1812 1813 expr.append(e)
1813 1814
1814 1815 if expr:
1815 1816 expr = '(' + ' and '.join(expr) + ')'
1816 1817 else:
1817 1818 expr = None
1818 1819 return expr, filematcher
1819 1820
1820 1821 def _logrevs(repo, opts):
1821 1822 # Default --rev value depends on --follow but --follow behaviour
1822 1823 # depends on revisions resolved from --rev...
1823 1824 follow = opts.get('follow') or opts.get('follow_first')
1824 1825 if opts.get('rev'):
1825 1826 revs = scmutil.revrange(repo, opts['rev'])
1826 1827 elif follow and repo.dirstate.p1() == nullid:
1827 1828 revs = revset.baseset()
1828 1829 elif follow:
1829 1830 revs = repo.revs('reverse(:.)')
1830 1831 else:
1831 1832 revs = revset.spanset(repo)
1832 1833 revs.reverse()
1833 1834 return revs
1834 1835
1835 1836 def getgraphlogrevs(repo, pats, opts):
1836 1837 """Return (revs, expr, filematcher) where revs is an iterable of
1837 1838 revision numbers, expr is a revset string built from log options
1838 1839 and file patterns or None, and used to filter 'revs'. If --stat or
1839 1840 --patch are not passed filematcher is None. Otherwise it is a
1840 1841 callable taking a revision number and returning a match objects
1841 1842 filtering the files to be detailed when displaying the revision.
1842 1843 """
1843 1844 limit = loglimit(opts)
1844 1845 revs = _logrevs(repo, opts)
1845 1846 if not revs:
1846 1847 return revset.baseset(), None, None
1847 1848 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1848 1849 if opts.get('rev'):
1849 1850 # User-specified revs might be unsorted, but don't sort before
1850 1851 # _makelogrevset because it might depend on the order of revs
1851 1852 revs.sort(reverse=True)
1852 1853 if expr:
1853 1854 # Revset matchers often operate faster on revisions in changelog
1854 1855 # order, because most filters deal with the changelog.
1855 1856 revs.reverse()
1856 1857 matcher = revset.match(repo.ui, expr)
1857 1858 # Revset matches can reorder revisions. "A or B" typically returns
1858 1859 # returns the revision matching A then the revision matching B. Sort
1859 1860 # again to fix that.
1860 1861 revs = matcher(repo, revs)
1861 1862 revs.sort(reverse=True)
1862 1863 if limit is not None:
1863 1864 limitedrevs = []
1864 1865 for idx, rev in enumerate(revs):
1865 1866 if idx >= limit:
1866 1867 break
1867 1868 limitedrevs.append(rev)
1868 1869 revs = revset.baseset(limitedrevs)
1869 1870
1870 1871 return revs, expr, filematcher
1871 1872
1872 1873 def getlogrevs(repo, pats, opts):
1873 1874 """Return (revs, expr, filematcher) where revs is an iterable of
1874 1875 revision numbers, expr is a revset string built from log options
1875 1876 and file patterns or None, and used to filter 'revs'. If --stat or
1876 1877 --patch are not passed filematcher is None. Otherwise it is a
1877 1878 callable taking a revision number and returning a match objects
1878 1879 filtering the files to be detailed when displaying the revision.
1879 1880 """
1880 1881 limit = loglimit(opts)
1881 1882 revs = _logrevs(repo, opts)
1882 1883 if not revs:
1883 1884 return revset.baseset([]), None, None
1884 1885 expr, filematcher = _makelogrevset(repo, pats, opts, revs)
1885 1886 if expr:
1886 1887 # Revset matchers often operate faster on revisions in changelog
1887 1888 # order, because most filters deal with the changelog.
1888 1889 if not opts.get('rev'):
1889 1890 revs.reverse()
1890 1891 matcher = revset.match(repo.ui, expr)
1891 1892 # Revset matches can reorder revisions. "A or B" typically returns
1892 1893 # returns the revision matching A then the revision matching B. Sort
1893 1894 # again to fix that.
1894 1895 revs = matcher(repo, revs)
1895 1896 if not opts.get('rev'):
1896 1897 revs.sort(reverse=True)
1897 1898 if limit is not None:
1898 1899 count = 0
1899 1900 limitedrevs = []
1900 1901 it = iter(revs)
1901 1902 while count < limit:
1902 1903 try:
1903 1904 limitedrevs.append(it.next())
1904 1905 except (StopIteration):
1905 1906 break
1906 1907 count += 1
1907 1908 revs = revset.baseset(limitedrevs)
1908 1909
1909 1910 return revs, expr, filematcher
1910 1911
1911 1912 def displaygraph(ui, dag, displayer, showparents, edgefn, getrenamed=None,
1912 1913 filematcher=None):
1913 1914 seen, state = [], graphmod.asciistate()
1914 1915 for rev, type, ctx, parents in dag:
1915 1916 char = 'o'
1916 1917 if ctx.node() in showparents:
1917 1918 char = '@'
1918 1919 elif ctx.obsolete():
1919 1920 char = 'x'
1920 1921 elif ctx.closesbranch():
1921 1922 char = '_'
1922 1923 copies = None
1923 1924 if getrenamed and ctx.rev():
1924 1925 copies = []
1925 1926 for fn in ctx.files():
1926 1927 rename = getrenamed(fn, ctx.rev())
1927 1928 if rename:
1928 1929 copies.append((fn, rename[0]))
1929 1930 revmatchfn = None
1930 1931 if filematcher is not None:
1931 1932 revmatchfn = filematcher(ctx.rev())
1932 1933 displayer.show(ctx, copies=copies, matchfn=revmatchfn)
1933 1934 lines = displayer.hunk.pop(rev).split('\n')
1934 1935 if not lines[-1]:
1935 1936 del lines[-1]
1936 1937 displayer.flush(rev)
1937 1938 edges = edgefn(type, char, lines, seen, rev, parents)
1938 1939 for type, char, lines, coldata in edges:
1939 1940 graphmod.ascii(ui, state, type, char, lines, coldata)
1940 1941 displayer.close()
1941 1942
1942 1943 def graphlog(ui, repo, *pats, **opts):
1943 1944 # Parameters are identical to log command ones
1944 1945 revs, expr, filematcher = getgraphlogrevs(repo, pats, opts)
1945 1946 revdag = graphmod.dagwalker(repo, revs)
1946 1947
1947 1948 getrenamed = None
1948 1949 if opts.get('copies'):
1949 1950 endrev = None
1950 1951 if opts.get('rev'):
1951 1952 endrev = scmutil.revrange(repo, opts.get('rev')).max() + 1
1952 1953 getrenamed = templatekw.getrenamedfn(repo, endrev=endrev)
1953 1954 displayer = show_changeset(ui, repo, opts, buffered=True)
1954 1955 showparents = [ctx.node() for ctx in repo[None].parents()]
1955 1956 displaygraph(ui, revdag, displayer, showparents,
1956 1957 graphmod.asciiedges, getrenamed, filematcher)
1957 1958
1958 1959 def checkunsupportedgraphflags(pats, opts):
1959 1960 for op in ["newest_first"]:
1960 1961 if op in opts and opts[op]:
1961 1962 raise util.Abort(_("-G/--graph option is incompatible with --%s")
1962 1963 % op.replace("_", "-"))
1963 1964
1964 1965 def graphrevs(repo, nodes, opts):
1965 1966 limit = loglimit(opts)
1966 1967 nodes.reverse()
1967 1968 if limit is not None:
1968 1969 nodes = nodes[:limit]
1969 1970 return graphmod.nodes(repo, nodes)
1970 1971
1971 1972 def add(ui, repo, match, prefix, explicitonly, **opts):
1972 1973 join = lambda f: os.path.join(prefix, f)
1973 1974 bad = []
1974 1975 oldbad = match.bad
1975 1976 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1976 1977 names = []
1977 1978 wctx = repo[None]
1978 1979 cca = None
1979 1980 abort, warn = scmutil.checkportabilityalert(ui)
1980 1981 if abort or warn:
1981 1982 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
1982 1983 for f in wctx.walk(match):
1983 1984 exact = match.exact(f)
1984 1985 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
1985 1986 if cca:
1986 1987 cca(f)
1987 1988 names.append(f)
1988 1989 if ui.verbose or not exact:
1989 1990 ui.status(_('adding %s\n') % match.rel(f))
1990 1991
1991 1992 for subpath in sorted(wctx.substate):
1992 1993 sub = wctx.sub(subpath)
1993 1994 try:
1994 1995 submatch = matchmod.narrowmatcher(subpath, match)
1995 1996 if opts.get('subrepos'):
1996 1997 bad.extend(sub.add(ui, submatch, prefix, False, **opts))
1997 1998 else:
1998 1999 bad.extend(sub.add(ui, submatch, prefix, True, **opts))
1999 2000 except error.LookupError:
2000 2001 ui.status(_("skipping missing subrepository: %s\n")
2001 2002 % join(subpath))
2002 2003
2003 2004 if not opts.get('dry_run'):
2004 2005 rejected = wctx.add(names, prefix)
2005 2006 bad.extend(f for f in rejected if f in match.files())
2006 2007 return bad
2007 2008
2008 2009 def forget(ui, repo, match, prefix, explicitonly):
2009 2010 join = lambda f: os.path.join(prefix, f)
2010 2011 bad = []
2011 2012 oldbad = match.bad
2012 2013 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
2013 2014 wctx = repo[None]
2014 2015 forgot = []
2015 2016 s = repo.status(match=match, clean=True)
2016 2017 forget = sorted(s[0] + s[1] + s[3] + s[6])
2017 2018 if explicitonly:
2018 2019 forget = [f for f in forget if match.exact(f)]
2019 2020
2020 2021 for subpath in sorted(wctx.substate):
2021 2022 sub = wctx.sub(subpath)
2022 2023 try:
2023 2024 submatch = matchmod.narrowmatcher(subpath, match)
2024 2025 subbad, subforgot = sub.forget(submatch, prefix)
2025 2026 bad.extend([subpath + '/' + f for f in subbad])
2026 2027 forgot.extend([subpath + '/' + f for f in subforgot])
2027 2028 except error.LookupError:
2028 2029 ui.status(_("skipping missing subrepository: %s\n")
2029 2030 % join(subpath))
2030 2031
2031 2032 if not explicitonly:
2032 2033 for f in match.files():
2033 2034 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2034 2035 if f not in forgot:
2035 2036 if repo.wvfs.exists(f):
2036 2037 ui.warn(_('not removing %s: '
2037 2038 'file is already untracked\n')
2038 2039 % match.rel(f))
2039 2040 bad.append(f)
2040 2041
2041 2042 for f in forget:
2042 2043 if ui.verbose or not match.exact(f):
2043 2044 ui.status(_('removing %s\n') % match.rel(f))
2044 2045
2045 2046 rejected = wctx.forget(forget, prefix)
2046 2047 bad.extend(f for f in rejected if f in match.files())
2047 2048 forgot.extend(f for f in forget if f not in rejected)
2048 2049 return bad, forgot
2049 2050
2050 2051 def remove(ui, repo, m, prefix, after, force, subrepos):
2051 2052 join = lambda f: os.path.join(prefix, f)
2052 2053 ret = 0
2053 2054 s = repo.status(match=m, clean=True)
2054 2055 modified, added, deleted, clean = s[0], s[1], s[3], s[6]
2055 2056
2056 2057 wctx = repo[None]
2057 2058
2058 2059 for subpath in sorted(wctx.substate):
2059 2060 def matchessubrepo(matcher, subpath):
2060 2061 if matcher.exact(subpath):
2061 2062 return True
2062 2063 for f in matcher.files():
2063 2064 if f.startswith(subpath):
2064 2065 return True
2065 2066 return False
2066 2067
2067 2068 if subrepos or matchessubrepo(m, subpath):
2068 2069 sub = wctx.sub(subpath)
2069 2070 try:
2070 2071 submatch = matchmod.narrowmatcher(subpath, m)
2071 2072 if sub.removefiles(submatch, prefix, after, force, subrepos):
2072 2073 ret = 1
2073 2074 except error.LookupError:
2074 2075 ui.status(_("skipping missing subrepository: %s\n")
2075 2076 % join(subpath))
2076 2077
2077 2078 # warn about failure to delete explicit files/dirs
2078 2079 deleteddirs = scmutil.dirs(deleted)
2079 2080 for f in m.files():
2080 2081 def insubrepo():
2081 2082 for subpath in wctx.substate:
2082 2083 if f.startswith(subpath):
2083 2084 return True
2084 2085 return False
2085 2086
2086 2087 isdir = f in deleteddirs or f in wctx.dirs()
2087 2088 if f in repo.dirstate or isdir or f == '.' or insubrepo():
2088 2089 continue
2089 2090
2090 2091 if repo.wvfs.exists(f):
2091 2092 if repo.wvfs.isdir(f):
2092 2093 ui.warn(_('not removing %s: no tracked files\n')
2093 2094 % m.rel(f))
2094 2095 else:
2095 2096 ui.warn(_('not removing %s: file is untracked\n')
2096 2097 % m.rel(f))
2097 2098 # missing files will generate a warning elsewhere
2098 2099 ret = 1
2099 2100
2100 2101 if force:
2101 2102 list = modified + deleted + clean + added
2102 2103 elif after:
2103 2104 list = deleted
2104 2105 for f in modified + added + clean:
2105 2106 ui.warn(_('not removing %s: file still exists\n') % m.rel(f))
2106 2107 ret = 1
2107 2108 else:
2108 2109 list = deleted + clean
2109 2110 for f in modified:
2110 2111 ui.warn(_('not removing %s: file is modified (use -f'
2111 2112 ' to force removal)\n') % m.rel(f))
2112 2113 ret = 1
2113 2114 for f in added:
2114 2115 ui.warn(_('not removing %s: file has been marked for add'
2115 2116 ' (use forget to undo)\n') % m.rel(f))
2116 2117 ret = 1
2117 2118
2118 2119 for f in sorted(list):
2119 2120 if ui.verbose or not m.exact(f):
2120 2121 ui.status(_('removing %s\n') % m.rel(f))
2121 2122
2122 2123 wlock = repo.wlock()
2123 2124 try:
2124 2125 if not after:
2125 2126 for f in list:
2126 2127 if f in added:
2127 2128 continue # we never unlink added files on remove
2128 2129 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
2129 2130 repo[None].forget(list)
2130 2131 finally:
2131 2132 wlock.release()
2132 2133
2133 2134 return ret
2134 2135
2135 2136 def cat(ui, repo, ctx, matcher, prefix, **opts):
2136 2137 err = 1
2137 2138
2138 2139 def write(path):
2139 2140 fp = makefileobj(repo, opts.get('output'), ctx.node(),
2140 2141 pathname=os.path.join(prefix, path))
2141 2142 data = ctx[path].data()
2142 2143 if opts.get('decode'):
2143 2144 data = repo.wwritedata(path, data)
2144 2145 fp.write(data)
2145 2146 fp.close()
2146 2147
2147 2148 # Automation often uses hg cat on single files, so special case it
2148 2149 # for performance to avoid the cost of parsing the manifest.
2149 2150 if len(matcher.files()) == 1 and not matcher.anypats():
2150 2151 file = matcher.files()[0]
2151 2152 mf = repo.manifest
2152 2153 mfnode = ctx._changeset[0]
2153 2154 if mf.find(mfnode, file)[0]:
2154 2155 write(file)
2155 2156 return 0
2156 2157
2157 2158 # Don't warn about "missing" files that are really in subrepos
2158 2159 bad = matcher.bad
2159 2160
2160 2161 def badfn(path, msg):
2161 2162 for subpath in ctx.substate:
2162 2163 if path.startswith(subpath):
2163 2164 return
2164 2165 bad(path, msg)
2165 2166
2166 2167 matcher.bad = badfn
2167 2168
2168 2169 for abs in ctx.walk(matcher):
2169 2170 write(abs)
2170 2171 err = 0
2171 2172
2172 2173 matcher.bad = bad
2173 2174
2174 2175 for subpath in sorted(ctx.substate):
2175 2176 sub = ctx.sub(subpath)
2176 2177 try:
2177 2178 submatch = matchmod.narrowmatcher(subpath, matcher)
2178 2179
2179 2180 if not sub.cat(submatch, os.path.join(prefix, sub._path),
2180 2181 **opts):
2181 2182 err = 0
2182 2183 except error.RepoLookupError:
2183 2184 ui.status(_("skipping missing subrepository: %s\n")
2184 2185 % os.path.join(prefix, subpath))
2185 2186
2186 2187 return err
2187 2188
2188 2189 def commit(ui, repo, commitfunc, pats, opts):
2189 2190 '''commit the specified files or all outstanding changes'''
2190 2191 date = opts.get('date')
2191 2192 if date:
2192 2193 opts['date'] = util.parsedate(date)
2193 2194 message = logmessage(ui, opts)
2194 2195 matcher = scmutil.match(repo[None], pats, opts)
2195 2196
2196 2197 # extract addremove carefully -- this function can be called from a command
2197 2198 # that doesn't support addremove
2198 2199 if opts.get('addremove'):
2199 2200 if scmutil.addremove(repo, matcher, "", opts) != 0:
2200 2201 raise util.Abort(
2201 2202 _("failed to mark all new/missing files as added/removed"))
2202 2203
2203 2204 return commitfunc(ui, repo, message, matcher, opts)
2204 2205
2205 2206 def amend(ui, repo, commitfunc, old, extra, pats, opts):
2206 2207 # amend will reuse the existing user if not specified, but the obsolete
2207 2208 # marker creation requires that the current user's name is specified.
2208 2209 if obsolete._enabled:
2209 2210 ui.username() # raise exception if username not set
2210 2211
2211 2212 ui.note(_('amending changeset %s\n') % old)
2212 2213 base = old.p1()
2213 2214
2214 2215 wlock = lock = newid = None
2215 2216 try:
2216 2217 wlock = repo.wlock()
2217 2218 lock = repo.lock()
2218 2219 tr = repo.transaction('amend')
2219 2220 try:
2220 2221 # See if we got a message from -m or -l, if not, open the editor
2221 2222 # with the message of the changeset to amend
2222 2223 message = logmessage(ui, opts)
2223 2224 # ensure logfile does not conflict with later enforcement of the
2224 2225 # message. potential logfile content has been processed by
2225 2226 # `logmessage` anyway.
2226 2227 opts.pop('logfile')
2227 2228 # First, do a regular commit to record all changes in the working
2228 2229 # directory (if there are any)
2229 2230 ui.callhooks = False
2230 2231 currentbookmark = repo._bookmarkcurrent
2231 2232 try:
2232 2233 repo._bookmarkcurrent = None
2233 2234 opts['message'] = 'temporary amend commit for %s' % old
2234 2235 node = commit(ui, repo, commitfunc, pats, opts)
2235 2236 finally:
2236 2237 repo._bookmarkcurrent = currentbookmark
2237 2238 ui.callhooks = True
2238 2239 ctx = repo[node]
2239 2240
2240 2241 # Participating changesets:
2241 2242 #
2242 2243 # node/ctx o - new (intermediate) commit that contains changes
2243 2244 # | from working dir to go into amending commit
2244 2245 # | (or a workingctx if there were no changes)
2245 2246 # |
2246 2247 # old o - changeset to amend
2247 2248 # |
2248 2249 # base o - parent of amending changeset
2249 2250
2250 2251 # Update extra dict from amended commit (e.g. to preserve graft
2251 2252 # source)
2252 2253 extra.update(old.extra())
2253 2254
2254 2255 # Also update it from the intermediate commit or from the wctx
2255 2256 extra.update(ctx.extra())
2256 2257
2257 2258 if len(old.parents()) > 1:
2258 2259 # ctx.files() isn't reliable for merges, so fall back to the
2259 2260 # slower repo.status() method
2260 2261 files = set([fn for st in repo.status(base, old)[:3]
2261 2262 for fn in st])
2262 2263 else:
2263 2264 files = set(old.files())
2264 2265
2265 2266 # Second, we use either the commit we just did, or if there were no
2266 2267 # changes the parent of the working directory as the version of the
2267 2268 # files in the final amend commit
2268 2269 if node:
2269 2270 ui.note(_('copying changeset %s to %s\n') % (ctx, base))
2270 2271
2271 2272 user = ctx.user()
2272 2273 date = ctx.date()
2273 2274 # Recompute copies (avoid recording a -> b -> a)
2274 2275 copied = copies.pathcopies(base, ctx)
2275 2276 if old.p2:
2276 2277 copied.update(copies.pathcopies(old.p2(), ctx))
2277 2278
2278 2279 # Prune files which were reverted by the updates: if old
2279 2280 # introduced file X and our intermediate commit, node,
2280 2281 # renamed that file, then those two files are the same and
2281 2282 # we can discard X from our list of files. Likewise if X
2282 2283 # was deleted, it's no longer relevant
2283 2284 files.update(ctx.files())
2284 2285
2285 2286 def samefile(f):
2286 2287 if f in ctx.manifest():
2287 2288 a = ctx.filectx(f)
2288 2289 if f in base.manifest():
2289 2290 b = base.filectx(f)
2290 2291 return (not a.cmp(b)
2291 2292 and a.flags() == b.flags())
2292 2293 else:
2293 2294 return False
2294 2295 else:
2295 2296 return f not in base.manifest()
2296 2297 files = [f for f in files if not samefile(f)]
2297 2298
2298 2299 def filectxfn(repo, ctx_, path):
2299 2300 try:
2300 2301 fctx = ctx[path]
2301 2302 flags = fctx.flags()
2302 2303 mctx = context.memfilectx(repo,
2303 2304 fctx.path(), fctx.data(),
2304 2305 islink='l' in flags,
2305 2306 isexec='x' in flags,
2306 2307 copied=copied.get(path))
2307 2308 return mctx
2308 2309 except KeyError:
2309 2310 return None
2310 2311 else:
2311 2312 ui.note(_('copying changeset %s to %s\n') % (old, base))
2312 2313
2313 2314 # Use version of files as in the old cset
2314 2315 def filectxfn(repo, ctx_, path):
2315 2316 try:
2316 2317 return old.filectx(path)
2317 2318 except KeyError:
2318 2319 return None
2319 2320
2320 2321 user = opts.get('user') or old.user()
2321 2322 date = opts.get('date') or old.date()
2322 2323 editform = mergeeditform(old, 'commit.amend')
2323 2324 editor = getcommiteditor(editform=editform, **opts)
2324 2325 if not message:
2325 2326 editor = getcommiteditor(edit=True, editform=editform)
2326 2327 message = old.description()
2327 2328
2328 2329 pureextra = extra.copy()
2329 2330 extra['amend_source'] = old.hex()
2330 2331
2331 2332 new = context.memctx(repo,
2332 2333 parents=[base.node(), old.p2().node()],
2333 2334 text=message,
2334 2335 files=files,
2335 2336 filectxfn=filectxfn,
2336 2337 user=user,
2337 2338 date=date,
2338 2339 extra=extra,
2339 2340 editor=editor)
2340 2341
2341 2342 newdesc = changelog.stripdesc(new.description())
2342 2343 if ((not node)
2343 2344 and newdesc == old.description()
2344 2345 and user == old.user()
2345 2346 and date == old.date()
2346 2347 and pureextra == old.extra()):
2347 2348 # nothing changed. continuing here would create a new node
2348 2349 # anyway because of the amend_source noise.
2349 2350 #
2350 2351 # This not what we expect from amend.
2351 2352 return old.node()
2352 2353
2353 2354 ph = repo.ui.config('phases', 'new-commit', phases.draft)
2354 2355 try:
2355 2356 if opts.get('secret'):
2356 2357 commitphase = 'secret'
2357 2358 else:
2358 2359 commitphase = old.phase()
2359 2360 repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
2360 2361 newid = repo.commitctx(new)
2361 2362 finally:
2362 2363 repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
2363 2364 if newid != old.node():
2364 2365 # Reroute the working copy parent to the new changeset
2365 2366 repo.setparents(newid, nullid)
2366 2367
2367 2368 # Move bookmarks from old parent to amend commit
2368 2369 bms = repo.nodebookmarks(old.node())
2369 2370 if bms:
2370 2371 marks = repo._bookmarks
2371 2372 for bm in bms:
2372 2373 marks[bm] = newid
2373 2374 marks.write()
2374 2375 #commit the whole amend process
2375 2376 createmarkers = obsolete.isenabled(repo, obsolete.createmarkersopt)
2376 2377 if createmarkers and newid != old.node():
2377 2378 # mark the new changeset as successor of the rewritten one
2378 2379 new = repo[newid]
2379 2380 obs = [(old, (new,))]
2380 2381 if node:
2381 2382 obs.append((ctx, ()))
2382 2383
2383 2384 obsolete.createmarkers(repo, obs)
2384 2385 tr.close()
2385 2386 finally:
2386 2387 tr.release()
2387 2388 if not createmarkers and newid != old.node():
2388 2389 # Strip the intermediate commit (if there was one) and the amended
2389 2390 # commit
2390 2391 if node:
2391 2392 ui.note(_('stripping intermediate changeset %s\n') % ctx)
2392 2393 ui.note(_('stripping amended changeset %s\n') % old)
2393 2394 repair.strip(ui, repo, old.node(), topic='amend-backup')
2394 2395 finally:
2395 2396 if newid is None:
2396 2397 repo.dirstate.invalidate()
2397 2398 lockmod.release(lock, wlock)
2398 2399 return newid
2399 2400
2400 2401 def commiteditor(repo, ctx, subs, editform=''):
2401 2402 if ctx.description():
2402 2403 return ctx.description()
2403 2404 return commitforceeditor(repo, ctx, subs, editform=editform)
2404 2405
2405 2406 def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
2406 2407 editform=''):
2407 2408 if not extramsg:
2408 2409 extramsg = _("Leave message empty to abort commit.")
2409 2410
2410 2411 forms = [e for e in editform.split('.') if e]
2411 2412 forms.insert(0, 'changeset')
2412 2413 while forms:
2413 2414 tmpl = repo.ui.config('committemplate', '.'.join(forms))
2414 2415 if tmpl:
2415 2416 committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
2416 2417 break
2417 2418 forms.pop()
2418 2419 else:
2419 2420 committext = buildcommittext(repo, ctx, subs, extramsg)
2420 2421
2421 2422 # run editor in the repository root
2422 2423 olddir = os.getcwd()
2423 2424 os.chdir(repo.root)
2424 2425 text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
2425 2426 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
2426 2427 os.chdir(olddir)
2427 2428
2428 2429 if finishdesc:
2429 2430 text = finishdesc(text)
2430 2431 if not text.strip():
2431 2432 raise util.Abort(_("empty commit message"))
2432 2433
2433 2434 return text
2434 2435
2435 2436 def buildcommittemplate(repo, ctx, subs, extramsg, tmpl):
2436 2437 ui = repo.ui
2437 2438 tmpl, mapfile = gettemplate(ui, tmpl, None)
2438 2439
2439 2440 try:
2440 2441 t = changeset_templater(ui, repo, None, {}, tmpl, mapfile, False)
2441 2442 except SyntaxError, inst:
2442 2443 raise util.Abort(inst.args[0])
2443 2444
2444 2445 for k, v in repo.ui.configitems('committemplate'):
2445 2446 if k != 'changeset':
2446 2447 t.t.cache[k] = v
2447 2448
2448 2449 if not extramsg:
2449 2450 extramsg = '' # ensure that extramsg is string
2450 2451
2451 2452 ui.pushbuffer()
2452 2453 t.show(ctx, extramsg=extramsg)
2453 2454 return ui.popbuffer()
2454 2455
2455 2456 def buildcommittext(repo, ctx, subs, extramsg):
2456 2457 edittext = []
2457 2458 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
2458 2459 if ctx.description():
2459 2460 edittext.append(ctx.description())
2460 2461 edittext.append("")
2461 2462 edittext.append("") # Empty line between message and comments.
2462 2463 edittext.append(_("HG: Enter commit message."
2463 2464 " Lines beginning with 'HG:' are removed."))
2464 2465 edittext.append("HG: %s" % extramsg)
2465 2466 edittext.append("HG: --")
2466 2467 edittext.append(_("HG: user: %s") % ctx.user())
2467 2468 if ctx.p2():
2468 2469 edittext.append(_("HG: branch merge"))
2469 2470 if ctx.branch():
2470 2471 edittext.append(_("HG: branch '%s'") % ctx.branch())
2471 2472 if bookmarks.iscurrent(repo):
2472 2473 edittext.append(_("HG: bookmark '%s'") % repo._bookmarkcurrent)
2473 2474 edittext.extend([_("HG: subrepo %s") % s for s in subs])
2474 2475 edittext.extend([_("HG: added %s") % f for f in added])
2475 2476 edittext.extend([_("HG: changed %s") % f for f in modified])
2476 2477 edittext.extend([_("HG: removed %s") % f for f in removed])
2477 2478 if not added and not modified and not removed:
2478 2479 edittext.append(_("HG: no files changed"))
2479 2480 edittext.append("")
2480 2481
2481 2482 return "\n".join(edittext)
2482 2483
2483 2484 def commitstatus(repo, node, branch, bheads=None, opts={}):
2484 2485 ctx = repo[node]
2485 2486 parents = ctx.parents()
2486 2487
2487 2488 if (not opts.get('amend') and bheads and node not in bheads and not
2488 2489 [x for x in parents if x.node() in bheads and x.branch() == branch]):
2489 2490 repo.ui.status(_('created new head\n'))
2490 2491 # The message is not printed for initial roots. For the other
2491 2492 # changesets, it is printed in the following situations:
2492 2493 #
2493 2494 # Par column: for the 2 parents with ...
2494 2495 # N: null or no parent
2495 2496 # B: parent is on another named branch
2496 2497 # C: parent is a regular non head changeset
2497 2498 # H: parent was a branch head of the current branch
2498 2499 # Msg column: whether we print "created new head" message
2499 2500 # In the following, it is assumed that there already exists some
2500 2501 # initial branch heads of the current branch, otherwise nothing is
2501 2502 # printed anyway.
2502 2503 #
2503 2504 # Par Msg Comment
2504 2505 # N N y additional topo root
2505 2506 #
2506 2507 # B N y additional branch root
2507 2508 # C N y additional topo head
2508 2509 # H N n usual case
2509 2510 #
2510 2511 # B B y weird additional branch root
2511 2512 # C B y branch merge
2512 2513 # H B n merge with named branch
2513 2514 #
2514 2515 # C C y additional head from merge
2515 2516 # C H n merge with a head
2516 2517 #
2517 2518 # H H n head merge: head count decreases
2518 2519
2519 2520 if not opts.get('close_branch'):
2520 2521 for r in parents:
2521 2522 if r.closesbranch() and r.branch() == branch:
2522 2523 repo.ui.status(_('reopening closed branch head %d\n') % r)
2523 2524
2524 2525 if repo.ui.debugflag:
2525 2526 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx.hex()))
2526 2527 elif repo.ui.verbose:
2527 2528 repo.ui.write(_('committed changeset %d:%s\n') % (int(ctx), ctx))
2528 2529
2529 2530 def revert(ui, repo, ctx, parents, *pats, **opts):
2530 2531 parent, p2 = parents
2531 2532 node = ctx.node()
2532 2533
2533 2534 mf = ctx.manifest()
2534 2535 if node == p2:
2535 2536 parent = p2
2536 2537 if node == parent:
2537 2538 pmf = mf
2538 2539 else:
2539 2540 pmf = None
2540 2541
2541 2542 # need all matching names in dirstate and manifest of target rev,
2542 2543 # so have to walk both. do not print errors if files exist in one
2543 2544 # but not other.
2544 2545
2545 2546 # `names` is a mapping for all elements in working copy and target revision
2546 2547 # The mapping is in the form:
2547 2548 # <asb path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
2548 2549 names = {}
2549 2550
2550 2551 wlock = repo.wlock()
2551 2552 try:
2552 2553 ## filling of the `names` mapping
2553 2554 # walk dirstate to fill `names`
2554 2555
2555 2556 m = scmutil.match(repo[None], pats, opts)
2556 2557 if not m.always() or node != parent:
2557 2558 m.bad = lambda x, y: False
2558 2559 for abs in repo.walk(m):
2559 2560 names[abs] = m.rel(abs), m.exact(abs)
2560 2561
2561 2562 # walk target manifest to fill `names`
2562 2563
2563 2564 def badfn(path, msg):
2564 2565 if path in names:
2565 2566 return
2566 2567 if path in ctx.substate:
2567 2568 return
2568 2569 path_ = path + '/'
2569 2570 for f in names:
2570 2571 if f.startswith(path_):
2571 2572 return
2572 2573 ui.warn("%s: %s\n" % (m.rel(path), msg))
2573 2574
2574 2575 m = scmutil.match(ctx, pats, opts)
2575 2576 m.bad = badfn
2576 2577 for abs in ctx.walk(m):
2577 2578 if abs not in names:
2578 2579 names[abs] = m.rel(abs), m.exact(abs)
2579 2580
2580 2581 # Find status of all file in `names`.
2581 2582 m = scmutil.matchfiles(repo, names)
2582 2583
2583 2584 changes = repo.status(node1=node, match=m,
2584 2585 unknown=True, ignored=True, clean=True)
2585 2586 else:
2586 2587 changes = repo.status(match=m)
2587 2588 for kind in changes:
2588 2589 for abs in kind:
2589 2590 names[abs] = m.rel(abs), m.exact(abs)
2590 2591
2591 2592 m = scmutil.matchfiles(repo, names)
2592 2593
2593 2594 modified = set(changes.modified)
2594 2595 added = set(changes.added)
2595 2596 removed = set(changes.removed)
2596 2597 _deleted = set(changes.deleted)
2597 2598 unknown = set(changes.unknown)
2598 2599 unknown.update(changes.ignored)
2599 2600 clean = set(changes.clean)
2600 2601 modadded = set()
2601 2602
2602 2603 # split between files known in target manifest and the others
2603 2604 smf = set(mf)
2604 2605
2605 2606 # determine the exact nature of the deleted changesets
2606 2607 deladded = _deleted - smf
2607 2608 deleted = _deleted - deladded
2608 2609
2609 2610 # We need to account for the state of the file in the dirstate,
2610 2611 # even when we revert against something else than parent. This will
2611 2612 # slightly alter the behavior of revert (doing back up or not, delete
2612 2613 # or just forget etc).
2613 2614 if parent == node:
2614 2615 dsmodified = modified
2615 2616 dsadded = added
2616 2617 dsremoved = removed
2617 2618 # store all local modifications, useful later for rename detection
2618 2619 localchanges = dsmodified | dsadded
2619 2620 modified, added, removed = set(), set(), set()
2620 2621 else:
2621 2622 changes = repo.status(node1=parent, match=m)
2622 2623 dsmodified = set(changes.modified)
2623 2624 dsadded = set(changes.added)
2624 2625 dsremoved = set(changes.removed)
2625 2626 # store all local modifications, useful later for rename detection
2626 2627 localchanges = dsmodified | dsadded
2627 2628
2628 2629 # only take into account for removes between wc and target
2629 2630 clean |= dsremoved - removed
2630 2631 dsremoved &= removed
2631 2632 # distinct between dirstate remove and other
2632 2633 removed -= dsremoved
2633 2634
2634 2635 modadded = added & dsmodified
2635 2636 added -= modadded
2636 2637
2637 2638 # tell newly modified apart.
2638 2639 dsmodified &= modified
2639 2640 dsmodified |= modified & dsadded # dirstate added may needs backup
2640 2641 modified -= dsmodified
2641 2642
2642 2643 # We need to wait for some post-processing to update this set
2643 2644 # before making the distinction. The dirstate will be used for
2644 2645 # that purpose.
2645 2646 dsadded = added
2646 2647
2647 2648 # in case of merge, files that are actually added can be reported as
2648 2649 # modified, we need to post process the result
2649 2650 if p2 != nullid:
2650 2651 if pmf is None:
2651 2652 # only need parent manifest in the merge case,
2652 2653 # so do not read by default
2653 2654 pmf = repo[parent].manifest()
2654 2655 mergeadd = dsmodified - set(pmf)
2655 2656 dsadded |= mergeadd
2656 2657 dsmodified -= mergeadd
2657 2658
2658 2659 # if f is a rename, update `names` to also revert the source
2659 2660 cwd = repo.getcwd()
2660 2661 for f in localchanges:
2661 2662 src = repo.dirstate.copied(f)
2662 2663 # XXX should we check for rename down to target node?
2663 2664 if src and src not in names and repo.dirstate[src] == 'r':
2664 2665 dsremoved.add(src)
2665 2666 names[src] = (repo.pathto(src, cwd), True)
2666 2667
2667 2668 # distinguish between file to forget and the other
2668 2669 added = set()
2669 2670 for abs in dsadded:
2670 2671 if repo.dirstate[abs] != 'a':
2671 2672 added.add(abs)
2672 2673 dsadded -= added
2673 2674
2674 2675 for abs in deladded:
2675 2676 if repo.dirstate[abs] == 'a':
2676 2677 dsadded.add(abs)
2677 2678 deladded -= dsadded
2678 2679
2679 2680 # For files marked as removed, we check if an unknown file is present at
2680 2681 # the same path. If a such file exists it may need to be backed up.
2681 2682 # Making the distinction at this stage helps have simpler backup
2682 2683 # logic.
2683 2684 removunk = set()
2684 2685 for abs in removed:
2685 2686 target = repo.wjoin(abs)
2686 2687 if os.path.lexists(target):
2687 2688 removunk.add(abs)
2688 2689 removed -= removunk
2689 2690
2690 2691 dsremovunk = set()
2691 2692 for abs in dsremoved:
2692 2693 target = repo.wjoin(abs)
2693 2694 if os.path.lexists(target):
2694 2695 dsremovunk.add(abs)
2695 2696 dsremoved -= dsremovunk
2696 2697
2697 2698 # action to be actually performed by revert
2698 2699 # (<list of file>, message>) tuple
2699 2700 actions = {'revert': ([], _('reverting %s\n')),
2700 2701 'add': ([], _('adding %s\n')),
2701 2702 'remove': ([], _('removing %s\n')),
2702 2703 'drop': ([], _('removing %s\n')),
2703 2704 'forget': ([], _('forgetting %s\n')),
2704 2705 'undelete': ([], _('undeleting %s\n')),
2705 2706 'noop': (None, _('no changes needed to %s\n')),
2706 2707 'unknown': (None, _('file not managed: %s\n')),
2707 2708 }
2708 2709
2709 2710 # "constant" that convey the backup strategy.
2710 2711 # All set to `discard` if `no-backup` is set do avoid checking
2711 2712 # no_backup lower in the code.
2712 2713 # These values are ordered for comparison purposes
2713 2714 backup = 2 # unconditionally do backup
2714 2715 check = 1 # check if the existing file differs from target
2715 2716 discard = 0 # never do backup
2716 2717 if opts.get('no_backup'):
2717 2718 backup = check = discard
2718 2719
2719 2720 backupanddel = actions['remove']
2720 2721 if not opts.get('no_backup'):
2721 2722 backupanddel = actions['drop']
2722 2723
2723 2724 disptable = (
2724 2725 # dispatch table:
2725 2726 # file state
2726 2727 # action
2727 2728 # make backup
2728 2729
2729 2730 ## Sets that results that will change file on disk
2730 2731 # Modified compared to target, no local change
2731 2732 (modified, actions['revert'], discard),
2732 2733 # Modified compared to target, but local file is deleted
2733 2734 (deleted, actions['revert'], discard),
2734 2735 # Modified compared to target, local change
2735 2736 (dsmodified, actions['revert'], backup),
2736 2737 # Added since target
2737 2738 (added, actions['remove'], discard),
2738 2739 # Added in working directory
2739 2740 (dsadded, actions['forget'], discard),
2740 2741 # Added since target, have local modification
2741 2742 (modadded, backupanddel, backup),
2742 2743 # Added since target but file is missing in working directory
2743 2744 (deladded, actions['drop'], discard),
2744 2745 # Removed since target, before working copy parent
2745 2746 (removed, actions['add'], discard),
2746 2747 # Same as `removed` but an unknown file exists at the same path
2747 2748 (removunk, actions['add'], check),
2748 2749 # Removed since targe, marked as such in working copy parent
2749 2750 (dsremoved, actions['undelete'], discard),
2750 2751 # Same as `dsremoved` but an unknown file exists at the same path
2751 2752 (dsremovunk, actions['undelete'], check),
2752 2753 ## the following sets does not result in any file changes
2753 2754 # File with no modification
2754 2755 (clean, actions['noop'], discard),
2755 2756 # Existing file, not tracked anywhere
2756 2757 (unknown, actions['unknown'], discard),
2757 2758 )
2758 2759
2759 2760 wctx = repo[None]
2760 2761 for abs, (rel, exact) in sorted(names.items()):
2761 2762 # target file to be touch on disk (relative to cwd)
2762 2763 target = repo.wjoin(abs)
2763 2764 # search the entry in the dispatch table.
2764 2765 # if the file is in any of these sets, it was touched in the working
2765 2766 # directory parent and we are sure it needs to be reverted.
2766 2767 for table, (xlist, msg), dobackup in disptable:
2767 2768 if abs not in table:
2768 2769 continue
2769 2770 if xlist is not None:
2770 2771 xlist.append(abs)
2771 2772 if dobackup and (backup <= dobackup
2772 2773 or wctx[abs].cmp(ctx[abs])):
2773 2774 bakname = "%s.orig" % rel
2774 2775 ui.note(_('saving current version of %s as %s\n') %
2775 2776 (rel, bakname))
2776 2777 if not opts.get('dry_run'):
2777 2778 util.rename(target, bakname)
2778 2779 if ui.verbose or not exact:
2779 2780 if not isinstance(msg, basestring):
2780 2781 msg = msg(abs)
2781 2782 ui.status(msg % rel)
2782 2783 elif exact:
2783 2784 ui.warn(msg % rel)
2784 2785 break
2785 2786
2786 2787
2787 2788 if not opts.get('dry_run'):
2788 2789 needdata = ('revert', 'add', 'undelete')
2789 2790 _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
2790 2791
2791 2792 _performrevert(repo, parents, ctx, actions)
2792 2793
2793 2794 # get the list of subrepos that must be reverted
2794 2795 subrepomatch = scmutil.match(ctx, pats, opts)
2795 2796 targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
2796 2797
2797 2798 if targetsubs:
2798 2799 # Revert the subrepos on the revert list
2799 2800 for sub in targetsubs:
2800 2801 ctx.sub(sub).revert(ctx.substate[sub], *pats, **opts)
2801 2802 finally:
2802 2803 wlock.release()
2803 2804
2804 2805 def _revertprefetch(repo, ctx, *files):
2805 2806 """Let extension changing the storage layer prefetch content"""
2806 2807 pass
2807 2808
2808 2809 def _performrevert(repo, parents, ctx, actions):
2809 2810 """function that actually perform all the actions computed for revert
2810 2811
2811 2812 This is an independent function to let extension to plug in and react to
2812 2813 the imminent revert.
2813 2814
2814 2815 Make sure you have the working directory locked when calling this function.
2815 2816 """
2816 2817 parent, p2 = parents
2817 2818 node = ctx.node()
2818 2819 def checkout(f):
2819 2820 fc = ctx[f]
2820 2821 repo.wwrite(f, fc.data(), fc.flags())
2821 2822
2822 2823 audit_path = pathutil.pathauditor(repo.root)
2823 2824 for f in actions['forget'][0]:
2824 2825 repo.dirstate.drop(f)
2825 2826 for f in actions['remove'][0]:
2826 2827 audit_path(f)
2827 2828 util.unlinkpath(repo.wjoin(f))
2828 2829 repo.dirstate.remove(f)
2829 2830 for f in actions['drop'][0]:
2830 2831 audit_path(f)
2831 2832 repo.dirstate.remove(f)
2832 2833
2833 2834 normal = None
2834 2835 if node == parent:
2835 2836 # We're reverting to our parent. If possible, we'd like status
2836 2837 # to report the file as clean. We have to use normallookup for
2837 2838 # merges to avoid losing information about merged/dirty files.
2838 2839 if p2 != nullid:
2839 2840 normal = repo.dirstate.normallookup
2840 2841 else:
2841 2842 normal = repo.dirstate.normal
2842 2843 for f in actions['revert'][0]:
2843 2844 checkout(f)
2844 2845 if normal:
2845 2846 normal(f)
2846 2847
2847 2848 for f in actions['add'][0]:
2848 2849 checkout(f)
2849 2850 repo.dirstate.add(f)
2850 2851
2851 2852 normal = repo.dirstate.normallookup
2852 2853 if node == parent and p2 == nullid:
2853 2854 normal = repo.dirstate.normal
2854 2855 for f in actions['undelete'][0]:
2855 2856 checkout(f)
2856 2857 normal(f)
2857 2858
2858 2859 copied = copies.pathcopies(repo[parent], ctx)
2859 2860
2860 2861 for f in actions['add'][0] + actions['undelete'][0] + actions['revert'][0]:
2861 2862 if f in copied:
2862 2863 repo.dirstate.copy(copied[f], f)
2863 2864
2864 2865 def command(table):
2865 2866 """Returns a function object to be used as a decorator for making commands.
2866 2867
2867 2868 This function receives a command table as its argument. The table should
2868 2869 be a dict.
2869 2870
2870 2871 The returned function can be used as a decorator for adding commands
2871 2872 to that command table. This function accepts multiple arguments to define
2872 2873 a command.
2873 2874
2874 2875 The first argument is the command name.
2875 2876
2876 2877 The options argument is an iterable of tuples defining command arguments.
2877 2878 See ``mercurial.fancyopts.fancyopts()`` for the format of each tuple.
2878 2879
2879 2880 The synopsis argument defines a short, one line summary of how to use the
2880 2881 command. This shows up in the help output.
2881 2882
2882 2883 The norepo argument defines whether the command does not require a
2883 2884 local repository. Most commands operate against a repository, thus the
2884 2885 default is False.
2885 2886
2886 2887 The optionalrepo argument defines whether the command optionally requires
2887 2888 a local repository.
2888 2889
2889 2890 The inferrepo argument defines whether to try to find a repository from the
2890 2891 command line arguments. If True, arguments will be examined for potential
2891 2892 repository locations. See ``findrepo()``. If a repository is found, it
2892 2893 will be used.
2893 2894 """
2894 2895 def cmd(name, options=(), synopsis=None, norepo=False, optionalrepo=False,
2895 2896 inferrepo=False):
2896 2897 def decorator(func):
2897 2898 if synopsis:
2898 2899 table[name] = func, list(options), synopsis
2899 2900 else:
2900 2901 table[name] = func, list(options)
2901 2902
2902 2903 if norepo:
2903 2904 # Avoid import cycle.
2904 2905 import commands
2905 2906 commands.norepo += ' %s' % ' '.join(parsealiases(name))
2906 2907
2907 2908 if optionalrepo:
2908 2909 import commands
2909 2910 commands.optionalrepo += ' %s' % ' '.join(parsealiases(name))
2910 2911
2911 2912 if inferrepo:
2912 2913 import commands
2913 2914 commands.inferrepo += ' %s' % ' '.join(parsealiases(name))
2914 2915
2915 2916 return func
2916 2917 return decorator
2917 2918
2918 2919 return cmd
2919 2920
2920 2921 # a list of (ui, repo, otherpeer, opts, missing) functions called by
2921 2922 # commands.outgoing. "missing" is "missing" of the result of
2922 2923 # "findcommonoutgoing()"
2923 2924 outgoinghooks = util.hooks()
2924 2925
2925 2926 # a list of (ui, repo) functions called by commands.summary
2926 2927 summaryhooks = util.hooks()
2927 2928
2928 2929 # a list of (ui, repo, opts, changes) functions called by commands.summary.
2929 2930 #
2930 2931 # functions should return tuple of booleans below, if 'changes' is None:
2931 2932 # (whether-incomings-are-needed, whether-outgoings-are-needed)
2932 2933 #
2933 2934 # otherwise, 'changes' is a tuple of tuples below:
2934 2935 # - (sourceurl, sourcebranch, sourcepeer, incoming)
2935 2936 # - (desturl, destbranch, destpeer, outgoing)
2936 2937 summaryremotehooks = util.hooks()
2937 2938
2938 2939 # A list of state files kept by multistep operations like graft.
2939 2940 # Since graft cannot be aborted, it is considered 'clearable' by update.
2940 2941 # note: bisect is intentionally excluded
2941 2942 # (state file, clearable, allowcommit, error, hint)
2942 2943 unfinishedstates = [
2943 2944 ('graftstate', True, False, _('graft in progress'),
2944 2945 _("use 'hg graft --continue' or 'hg update' to abort")),
2945 2946 ('updatestate', True, False, _('last update was interrupted'),
2946 2947 _("use 'hg update' to get a consistent checkout"))
2947 2948 ]
2948 2949
2949 2950 def checkunfinished(repo, commit=False):
2950 2951 '''Look for an unfinished multistep operation, like graft, and abort
2951 2952 if found. It's probably good to check this right before
2952 2953 bailifchanged().
2953 2954 '''
2954 2955 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2955 2956 if commit and allowcommit:
2956 2957 continue
2957 2958 if repo.vfs.exists(f):
2958 2959 raise util.Abort(msg, hint=hint)
2959 2960
2960 2961 def clearunfinished(repo):
2961 2962 '''Check for unfinished operations (as above), and clear the ones
2962 2963 that are clearable.
2963 2964 '''
2964 2965 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2965 2966 if not clearable and repo.vfs.exists(f):
2966 2967 raise util.Abort(msg, hint=hint)
2967 2968 for f, clearable, allowcommit, msg, hint in unfinishedstates:
2968 2969 if clearable and repo.vfs.exists(f):
2969 2970 util.unlink(repo.join(f))
@@ -1,1990 +1,1990 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 # Copyright 2007 Chris Mason <chris.mason@oracle.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import cStringIO, email, os, errno, re, posixpath
10 10 import tempfile, zlib, shutil
11 11 # On python2.4 you have to import these by name or they fail to
12 12 # load. This was not a problem on Python 2.7.
13 13 import email.Generator
14 14 import email.Parser
15 15
16 16 from i18n import _
17 17 from node import hex, short
18 18 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
19 19
20 20 gitre = re.compile('diff --git a/(.*) b/(.*)')
21 21 tabsplitter = re.compile(r'(\t+|[^\t]+)')
22 22
23 23 class PatchError(Exception):
24 24 pass
25 25
26 26
27 27 # public functions
28 28
29 29 def split(stream):
30 30 '''return an iterator of individual patches from a stream'''
31 31 def isheader(line, inheader):
32 32 if inheader and line[0] in (' ', '\t'):
33 33 # continuation
34 34 return True
35 35 if line[0] in (' ', '-', '+'):
36 36 # diff line - don't check for header pattern in there
37 37 return False
38 38 l = line.split(': ', 1)
39 39 return len(l) == 2 and ' ' not in l[0]
40 40
41 41 def chunk(lines):
42 42 return cStringIO.StringIO(''.join(lines))
43 43
44 44 def hgsplit(stream, cur):
45 45 inheader = True
46 46
47 47 for line in stream:
48 48 if not line.strip():
49 49 inheader = False
50 50 if not inheader and line.startswith('# HG changeset patch'):
51 51 yield chunk(cur)
52 52 cur = []
53 53 inheader = True
54 54
55 55 cur.append(line)
56 56
57 57 if cur:
58 58 yield chunk(cur)
59 59
60 60 def mboxsplit(stream, cur):
61 61 for line in stream:
62 62 if line.startswith('From '):
63 63 for c in split(chunk(cur[1:])):
64 64 yield c
65 65 cur = []
66 66
67 67 cur.append(line)
68 68
69 69 if cur:
70 70 for c in split(chunk(cur[1:])):
71 71 yield c
72 72
73 73 def mimesplit(stream, cur):
74 74 def msgfp(m):
75 75 fp = cStringIO.StringIO()
76 76 g = email.Generator.Generator(fp, mangle_from_=False)
77 77 g.flatten(m)
78 78 fp.seek(0)
79 79 return fp
80 80
81 81 for line in stream:
82 82 cur.append(line)
83 83 c = chunk(cur)
84 84
85 85 m = email.Parser.Parser().parse(c)
86 86 if not m.is_multipart():
87 87 yield msgfp(m)
88 88 else:
89 89 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
90 90 for part in m.walk():
91 91 ct = part.get_content_type()
92 92 if ct not in ok_types:
93 93 continue
94 94 yield msgfp(part)
95 95
96 96 def headersplit(stream, cur):
97 97 inheader = False
98 98
99 99 for line in stream:
100 100 if not inheader and isheader(line, inheader):
101 101 yield chunk(cur)
102 102 cur = []
103 103 inheader = True
104 104 if inheader and not isheader(line, inheader):
105 105 inheader = False
106 106
107 107 cur.append(line)
108 108
109 109 if cur:
110 110 yield chunk(cur)
111 111
112 112 def remainder(cur):
113 113 yield chunk(cur)
114 114
115 115 class fiter(object):
116 116 def __init__(self, fp):
117 117 self.fp = fp
118 118
119 119 def __iter__(self):
120 120 return self
121 121
122 122 def next(self):
123 123 l = self.fp.readline()
124 124 if not l:
125 125 raise StopIteration
126 126 return l
127 127
128 128 inheader = False
129 129 cur = []
130 130
131 131 mimeheaders = ['content-type']
132 132
133 133 if not util.safehasattr(stream, 'next'):
134 134 # http responses, for example, have readline but not next
135 135 stream = fiter(stream)
136 136
137 137 for line in stream:
138 138 cur.append(line)
139 139 if line.startswith('# HG changeset patch'):
140 140 return hgsplit(stream, cur)
141 141 elif line.startswith('From '):
142 142 return mboxsplit(stream, cur)
143 143 elif isheader(line, inheader):
144 144 inheader = True
145 145 if line.split(':', 1)[0].lower() in mimeheaders:
146 146 # let email parser handle this
147 147 return mimesplit(stream, cur)
148 148 elif line.startswith('--- ') and inheader:
149 149 # No evil headers seen by diff start, split by hand
150 150 return headersplit(stream, cur)
151 151 # Not enough info, keep reading
152 152
153 153 # if we are here, we have a very plain patch
154 154 return remainder(cur)
155 155
156 156 def extract(ui, fileobj):
157 157 '''extract patch from data read from fileobj.
158 158
159 159 patch can be a normal patch or contained in an email message.
160 160
161 161 return tuple (filename, message, user, date, branch, node, p1, p2).
162 162 Any item in the returned tuple can be None. If filename is None,
163 163 fileobj did not contain a patch. Caller must unlink filename when done.'''
164 164
165 165 # attempt to detect the start of a patch
166 166 # (this heuristic is borrowed from quilt)
167 167 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
168 168 r'retrieving revision [0-9]+(\.[0-9]+)*$|'
169 169 r'---[ \t].*?^\+\+\+[ \t]|'
170 170 r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
171 171
172 172 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
173 173 tmpfp = os.fdopen(fd, 'w')
174 174 try:
175 175 msg = email.Parser.Parser().parse(fileobj)
176 176
177 177 subject = msg['Subject']
178 178 user = msg['From']
179 179 if not subject and not user:
180 180 # Not an email, restore parsed headers if any
181 181 subject = '\n'.join(': '.join(h) for h in msg.items()) + '\n'
182 182
183 183 # should try to parse msg['Date']
184 184 date = None
185 185 nodeid = None
186 186 branch = None
187 187 parents = []
188 188
189 189 if subject:
190 190 if subject.startswith('[PATCH'):
191 191 pend = subject.find(']')
192 192 if pend >= 0:
193 193 subject = subject[pend + 1:].lstrip()
194 194 subject = re.sub(r'\n[ \t]+', ' ', subject)
195 195 ui.debug('Subject: %s\n' % subject)
196 196 if user:
197 197 ui.debug('From: %s\n' % user)
198 198 diffs_seen = 0
199 199 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
200 200 message = ''
201 201 for part in msg.walk():
202 202 content_type = part.get_content_type()
203 203 ui.debug('Content-Type: %s\n' % content_type)
204 204 if content_type not in ok_types:
205 205 continue
206 206 payload = part.get_payload(decode=True)
207 207 m = diffre.search(payload)
208 208 if m:
209 209 hgpatch = False
210 210 hgpatchheader = False
211 211 ignoretext = False
212 212
213 213 ui.debug('found patch at byte %d\n' % m.start(0))
214 214 diffs_seen += 1
215 215 cfp = cStringIO.StringIO()
216 216 for line in payload[:m.start(0)].splitlines():
217 217 if line.startswith('# HG changeset patch') and not hgpatch:
218 218 ui.debug('patch generated by hg export\n')
219 219 hgpatch = True
220 220 hgpatchheader = True
221 221 # drop earlier commit message content
222 222 cfp.seek(0)
223 223 cfp.truncate()
224 224 subject = None
225 225 elif hgpatchheader:
226 226 if line.startswith('# User '):
227 227 user = line[7:]
228 228 ui.debug('From: %s\n' % user)
229 229 elif line.startswith("# Date "):
230 230 date = line[7:]
231 231 elif line.startswith("# Branch "):
232 232 branch = line[9:]
233 233 elif line.startswith("# Node ID "):
234 234 nodeid = line[10:]
235 235 elif line.startswith("# Parent "):
236 236 parents.append(line[9:].lstrip())
237 237 elif not line.startswith("# "):
238 238 hgpatchheader = False
239 239 elif line == '---':
240 240 ignoretext = True
241 241 if not hgpatchheader and not ignoretext:
242 242 cfp.write(line)
243 243 cfp.write('\n')
244 244 message = cfp.getvalue()
245 245 if tmpfp:
246 246 tmpfp.write(payload)
247 247 if not payload.endswith('\n'):
248 248 tmpfp.write('\n')
249 249 elif not diffs_seen and message and content_type == 'text/plain':
250 250 message += '\n' + payload
251 251 except: # re-raises
252 252 tmpfp.close()
253 253 os.unlink(tmpname)
254 254 raise
255 255
256 256 if subject and not message.startswith(subject):
257 257 message = '%s\n%s' % (subject, message)
258 258 tmpfp.close()
259 259 if not diffs_seen:
260 260 os.unlink(tmpname)
261 261 return None, message, user, date, branch, None, None, None
262 262 p1 = parents and parents.pop(0) or None
263 263 p2 = parents and parents.pop(0) or None
264 264 return tmpname, message, user, date, branch, nodeid, p1, p2
265 265
266 266 class patchmeta(object):
267 267 """Patched file metadata
268 268
269 269 'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
270 270 or COPY. 'path' is patched file path. 'oldpath' is set to the
271 271 origin file when 'op' is either COPY or RENAME, None otherwise. If
272 272 file mode is changed, 'mode' is a tuple (islink, isexec) where
273 273 'islink' is True if the file is a symlink and 'isexec' is True if
274 274 the file is executable. Otherwise, 'mode' is None.
275 275 """
276 276 def __init__(self, path):
277 277 self.path = path
278 278 self.oldpath = None
279 279 self.mode = None
280 280 self.op = 'MODIFY'
281 281 self.binary = False
282 282
283 283 def setmode(self, mode):
284 284 islink = mode & 020000
285 285 isexec = mode & 0100
286 286 self.mode = (islink, isexec)
287 287
288 288 def copy(self):
289 289 other = patchmeta(self.path)
290 290 other.oldpath = self.oldpath
291 291 other.mode = self.mode
292 292 other.op = self.op
293 293 other.binary = self.binary
294 294 return other
295 295
296 296 def _ispatchinga(self, afile):
297 297 if afile == '/dev/null':
298 298 return self.op == 'ADD'
299 299 return afile == 'a/' + (self.oldpath or self.path)
300 300
301 301 def _ispatchingb(self, bfile):
302 302 if bfile == '/dev/null':
303 303 return self.op == 'DELETE'
304 304 return bfile == 'b/' + self.path
305 305
306 306 def ispatching(self, afile, bfile):
307 307 return self._ispatchinga(afile) and self._ispatchingb(bfile)
308 308
309 309 def __repr__(self):
310 310 return "<patchmeta %s %r>" % (self.op, self.path)
311 311
312 312 def readgitpatch(lr):
313 313 """extract git-style metadata about patches from <patchname>"""
314 314
315 315 # Filter patch for git information
316 316 gp = None
317 317 gitpatches = []
318 318 for line in lr:
319 319 line = line.rstrip(' \r\n')
320 320 if line.startswith('diff --git a/'):
321 321 m = gitre.match(line)
322 322 if m:
323 323 if gp:
324 324 gitpatches.append(gp)
325 325 dst = m.group(2)
326 326 gp = patchmeta(dst)
327 327 elif gp:
328 328 if line.startswith('--- '):
329 329 gitpatches.append(gp)
330 330 gp = None
331 331 continue
332 332 if line.startswith('rename from '):
333 333 gp.op = 'RENAME'
334 334 gp.oldpath = line[12:]
335 335 elif line.startswith('rename to '):
336 336 gp.path = line[10:]
337 337 elif line.startswith('copy from '):
338 338 gp.op = 'COPY'
339 339 gp.oldpath = line[10:]
340 340 elif line.startswith('copy to '):
341 341 gp.path = line[8:]
342 342 elif line.startswith('deleted file'):
343 343 gp.op = 'DELETE'
344 344 elif line.startswith('new file mode '):
345 345 gp.op = 'ADD'
346 346 gp.setmode(int(line[-6:], 8))
347 347 elif line.startswith('new mode '):
348 348 gp.setmode(int(line[-6:], 8))
349 349 elif line.startswith('GIT binary patch'):
350 350 gp.binary = True
351 351 if gp:
352 352 gitpatches.append(gp)
353 353
354 354 return gitpatches
355 355
356 356 class linereader(object):
357 357 # simple class to allow pushing lines back into the input stream
358 358 def __init__(self, fp):
359 359 self.fp = fp
360 360 self.buf = []
361 361
362 362 def push(self, line):
363 363 if line is not None:
364 364 self.buf.append(line)
365 365
366 366 def readline(self):
367 367 if self.buf:
368 368 l = self.buf[0]
369 369 del self.buf[0]
370 370 return l
371 371 return self.fp.readline()
372 372
373 373 def __iter__(self):
374 374 while True:
375 375 l = self.readline()
376 376 if not l:
377 377 break
378 378 yield l
379 379
380 380 class abstractbackend(object):
381 381 def __init__(self, ui):
382 382 self.ui = ui
383 383
384 384 def getfile(self, fname):
385 385 """Return target file data and flags as a (data, (islink,
386 386 isexec)) tuple. Data is None if file is missing/deleted.
387 387 """
388 388 raise NotImplementedError
389 389
390 390 def setfile(self, fname, data, mode, copysource):
391 391 """Write data to target file fname and set its mode. mode is a
392 392 (islink, isexec) tuple. If data is None, the file content should
393 393 be left unchanged. If the file is modified after being copied,
394 394 copysource is set to the original file name.
395 395 """
396 396 raise NotImplementedError
397 397
398 398 def unlink(self, fname):
399 399 """Unlink target file."""
400 400 raise NotImplementedError
401 401
402 402 def writerej(self, fname, failed, total, lines):
403 403 """Write rejected lines for fname. total is the number of hunks
404 404 which failed to apply and total the total number of hunks for this
405 405 files.
406 406 """
407 407 pass
408 408
409 409 def exists(self, fname):
410 410 raise NotImplementedError
411 411
412 412 class fsbackend(abstractbackend):
413 413 def __init__(self, ui, basedir):
414 414 super(fsbackend, self).__init__(ui)
415 415 self.opener = scmutil.opener(basedir)
416 416
417 417 def _join(self, f):
418 418 return os.path.join(self.opener.base, f)
419 419
420 420 def getfile(self, fname):
421 421 if self.opener.islink(fname):
422 422 return (self.opener.readlink(fname), (True, False))
423 423
424 424 isexec = False
425 425 try:
426 426 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
427 427 except OSError, e:
428 428 if e.errno != errno.ENOENT:
429 429 raise
430 430 try:
431 431 return (self.opener.read(fname), (False, isexec))
432 432 except IOError, e:
433 433 if e.errno != errno.ENOENT:
434 434 raise
435 435 return None, None
436 436
437 437 def setfile(self, fname, data, mode, copysource):
438 438 islink, isexec = mode
439 439 if data is None:
440 440 self.opener.setflags(fname, islink, isexec)
441 441 return
442 442 if islink:
443 443 self.opener.symlink(data, fname)
444 444 else:
445 445 self.opener.write(fname, data)
446 446 if isexec:
447 447 self.opener.setflags(fname, False, True)
448 448
449 449 def unlink(self, fname):
450 450 self.opener.unlinkpath(fname, ignoremissing=True)
451 451
452 452 def writerej(self, fname, failed, total, lines):
453 453 fname = fname + ".rej"
454 454 self.ui.warn(
455 455 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") %
456 456 (failed, total, fname))
457 457 fp = self.opener(fname, 'w')
458 458 fp.writelines(lines)
459 459 fp.close()
460 460
461 461 def exists(self, fname):
462 462 return self.opener.lexists(fname)
463 463
464 464 class workingbackend(fsbackend):
465 465 def __init__(self, ui, repo, similarity):
466 466 super(workingbackend, self).__init__(ui, repo.root)
467 467 self.repo = repo
468 468 self.similarity = similarity
469 469 self.removed = set()
470 470 self.changed = set()
471 471 self.copied = []
472 472
473 473 def _checkknown(self, fname):
474 474 if self.repo.dirstate[fname] == '?' and self.exists(fname):
475 475 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
476 476
477 477 def setfile(self, fname, data, mode, copysource):
478 478 self._checkknown(fname)
479 479 super(workingbackend, self).setfile(fname, data, mode, copysource)
480 480 if copysource is not None:
481 481 self.copied.append((copysource, fname))
482 482 self.changed.add(fname)
483 483
484 484 def unlink(self, fname):
485 485 self._checkknown(fname)
486 486 super(workingbackend, self).unlink(fname)
487 487 self.removed.add(fname)
488 488 self.changed.add(fname)
489 489
490 490 def close(self):
491 491 wctx = self.repo[None]
492 492 changed = set(self.changed)
493 493 for src, dst in self.copied:
494 494 scmutil.dirstatecopy(self.ui, self.repo, wctx, src, dst)
495 495 if self.removed:
496 496 wctx.forget(sorted(self.removed))
497 497 for f in self.removed:
498 498 if f not in self.repo.dirstate:
499 499 # File was deleted and no longer belongs to the
500 500 # dirstate, it was probably marked added then
501 501 # deleted, and should not be considered by
502 502 # marktouched().
503 503 changed.discard(f)
504 504 if changed:
505 505 scmutil.marktouched(self.repo, changed, self.similarity)
506 506 return sorted(self.changed)
507 507
508 508 class filestore(object):
509 509 def __init__(self, maxsize=None):
510 510 self.opener = None
511 511 self.files = {}
512 512 self.created = 0
513 513 self.maxsize = maxsize
514 514 if self.maxsize is None:
515 515 self.maxsize = 4*(2**20)
516 516 self.size = 0
517 517 self.data = {}
518 518
519 519 def setfile(self, fname, data, mode, copied=None):
520 520 if self.maxsize < 0 or (len(data) + self.size) <= self.maxsize:
521 521 self.data[fname] = (data, mode, copied)
522 522 self.size += len(data)
523 523 else:
524 524 if self.opener is None:
525 525 root = tempfile.mkdtemp(prefix='hg-patch-')
526 526 self.opener = scmutil.opener(root)
527 527 # Avoid filename issues with these simple names
528 528 fn = str(self.created)
529 529 self.opener.write(fn, data)
530 530 self.created += 1
531 531 self.files[fname] = (fn, mode, copied)
532 532
533 533 def getfile(self, fname):
534 534 if fname in self.data:
535 535 return self.data[fname]
536 536 if not self.opener or fname not in self.files:
537 537 return None, None, None
538 538 fn, mode, copied = self.files[fname]
539 539 return self.opener.read(fn), mode, copied
540 540
541 541 def close(self):
542 542 if self.opener:
543 543 shutil.rmtree(self.opener.base)
544 544
545 545 class repobackend(abstractbackend):
546 546 def __init__(self, ui, repo, ctx, store):
547 547 super(repobackend, self).__init__(ui)
548 548 self.repo = repo
549 549 self.ctx = ctx
550 550 self.store = store
551 551 self.changed = set()
552 552 self.removed = set()
553 553 self.copied = {}
554 554
555 555 def _checkknown(self, fname):
556 556 if fname not in self.ctx:
557 557 raise PatchError(_('cannot patch %s: file is not tracked') % fname)
558 558
559 559 def getfile(self, fname):
560 560 try:
561 561 fctx = self.ctx[fname]
562 562 except error.LookupError:
563 563 return None, None
564 564 flags = fctx.flags()
565 565 return fctx.data(), ('l' in flags, 'x' in flags)
566 566
567 567 def setfile(self, fname, data, mode, copysource):
568 568 if copysource:
569 569 self._checkknown(copysource)
570 570 if data is None:
571 571 data = self.ctx[fname].data()
572 572 self.store.setfile(fname, data, mode, copysource)
573 573 self.changed.add(fname)
574 574 if copysource:
575 575 self.copied[fname] = copysource
576 576
577 577 def unlink(self, fname):
578 578 self._checkknown(fname)
579 579 self.removed.add(fname)
580 580
581 581 def exists(self, fname):
582 582 return fname in self.ctx
583 583
584 584 def close(self):
585 585 return self.changed | self.removed
586 586
587 587 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
588 588 unidesc = re.compile('@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@')
589 589 contextdesc = re.compile('(?:---|\*\*\*) (\d+)(?:,(\d+))? (?:---|\*\*\*)')
590 590 eolmodes = ['strict', 'crlf', 'lf', 'auto']
591 591
592 592 class patchfile(object):
593 593 def __init__(self, ui, gp, backend, store, eolmode='strict'):
594 594 self.fname = gp.path
595 595 self.eolmode = eolmode
596 596 self.eol = None
597 597 self.backend = backend
598 598 self.ui = ui
599 599 self.lines = []
600 600 self.exists = False
601 601 self.missing = True
602 602 self.mode = gp.mode
603 603 self.copysource = gp.oldpath
604 604 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
605 605 self.remove = gp.op == 'DELETE'
606 606 if self.copysource is None:
607 607 data, mode = backend.getfile(self.fname)
608 608 else:
609 609 data, mode = store.getfile(self.copysource)[:2]
610 610 if data is not None:
611 611 self.exists = self.copysource is None or backend.exists(self.fname)
612 612 self.missing = False
613 613 if data:
614 614 self.lines = mdiff.splitnewlines(data)
615 615 if self.mode is None:
616 616 self.mode = mode
617 617 if self.lines:
618 618 # Normalize line endings
619 619 if self.lines[0].endswith('\r\n'):
620 620 self.eol = '\r\n'
621 621 elif self.lines[0].endswith('\n'):
622 622 self.eol = '\n'
623 623 if eolmode != 'strict':
624 624 nlines = []
625 625 for l in self.lines:
626 626 if l.endswith('\r\n'):
627 627 l = l[:-2] + '\n'
628 628 nlines.append(l)
629 629 self.lines = nlines
630 630 else:
631 631 if self.create:
632 632 self.missing = False
633 633 if self.mode is None:
634 634 self.mode = (False, False)
635 635 if self.missing:
636 636 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
637 637
638 638 self.hash = {}
639 639 self.dirty = 0
640 640 self.offset = 0
641 641 self.skew = 0
642 642 self.rej = []
643 643 self.fileprinted = False
644 644 self.printfile(False)
645 645 self.hunks = 0
646 646
647 647 def writelines(self, fname, lines, mode):
648 648 if self.eolmode == 'auto':
649 649 eol = self.eol
650 650 elif self.eolmode == 'crlf':
651 651 eol = '\r\n'
652 652 else:
653 653 eol = '\n'
654 654
655 655 if self.eolmode != 'strict' and eol and eol != '\n':
656 656 rawlines = []
657 657 for l in lines:
658 658 if l and l[-1] == '\n':
659 659 l = l[:-1] + eol
660 660 rawlines.append(l)
661 661 lines = rawlines
662 662
663 663 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
664 664
665 665 def printfile(self, warn):
666 666 if self.fileprinted:
667 667 return
668 668 if warn or self.ui.verbose:
669 669 self.fileprinted = True
670 670 s = _("patching file %s\n") % self.fname
671 671 if warn:
672 672 self.ui.warn(s)
673 673 else:
674 674 self.ui.note(s)
675 675
676 676
677 677 def findlines(self, l, linenum):
678 678 # looks through the hash and finds candidate lines. The
679 679 # result is a list of line numbers sorted based on distance
680 680 # from linenum
681 681
682 682 cand = self.hash.get(l, [])
683 683 if len(cand) > 1:
684 684 # resort our list of potentials forward then back.
685 685 cand.sort(key=lambda x: abs(x - linenum))
686 686 return cand
687 687
688 688 def write_rej(self):
689 689 # our rejects are a little different from patch(1). This always
690 690 # creates rejects in the same form as the original patch. A file
691 691 # header is inserted so that you can run the reject through patch again
692 692 # without having to type the filename.
693 693 if not self.rej:
694 694 return
695 695 base = os.path.basename(self.fname)
696 696 lines = ["--- %s\n+++ %s\n" % (base, base)]
697 697 for x in self.rej:
698 698 for l in x.hunk:
699 699 lines.append(l)
700 700 if l[-1] != '\n':
701 701 lines.append("\n\ No newline at end of file\n")
702 702 self.backend.writerej(self.fname, len(self.rej), self.hunks, lines)
703 703
704 704 def apply(self, h):
705 705 if not h.complete():
706 706 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") %
707 707 (h.number, h.desc, len(h.a), h.lena, len(h.b),
708 708 h.lenb))
709 709
710 710 self.hunks += 1
711 711
712 712 if self.missing:
713 713 self.rej.append(h)
714 714 return -1
715 715
716 716 if self.exists and self.create:
717 717 if self.copysource:
718 718 self.ui.warn(_("cannot create %s: destination already "
719 719 "exists\n") % self.fname)
720 720 else:
721 721 self.ui.warn(_("file %s already exists\n") % self.fname)
722 722 self.rej.append(h)
723 723 return -1
724 724
725 725 if isinstance(h, binhunk):
726 726 if self.remove:
727 727 self.backend.unlink(self.fname)
728 728 else:
729 729 l = h.new(self.lines)
730 730 self.lines[:] = l
731 731 self.offset += len(l)
732 732 self.dirty = True
733 733 return 0
734 734
735 735 horig = h
736 736 if (self.eolmode in ('crlf', 'lf')
737 737 or self.eolmode == 'auto' and self.eol):
738 738 # If new eols are going to be normalized, then normalize
739 739 # hunk data before patching. Otherwise, preserve input
740 740 # line-endings.
741 741 h = h.getnormalized()
742 742
743 743 # fast case first, no offsets, no fuzz
744 744 old, oldstart, new, newstart = h.fuzzit(0, False)
745 745 oldstart += self.offset
746 746 orig_start = oldstart
747 747 # if there's skew we want to emit the "(offset %d lines)" even
748 748 # when the hunk cleanly applies at start + skew, so skip the
749 749 # fast case code
750 750 if (self.skew == 0 and
751 751 diffhelpers.testhunk(old, self.lines, oldstart) == 0):
752 752 if self.remove:
753 753 self.backend.unlink(self.fname)
754 754 else:
755 755 self.lines[oldstart:oldstart + len(old)] = new
756 756 self.offset += len(new) - len(old)
757 757 self.dirty = True
758 758 return 0
759 759
760 760 # ok, we couldn't match the hunk. Lets look for offsets and fuzz it
761 761 self.hash = {}
762 762 for x, s in enumerate(self.lines):
763 763 self.hash.setdefault(s, []).append(x)
764 764
765 765 for fuzzlen in xrange(3):
766 766 for toponly in [True, False]:
767 767 old, oldstart, new, newstart = h.fuzzit(fuzzlen, toponly)
768 768 oldstart = oldstart + self.offset + self.skew
769 769 oldstart = min(oldstart, len(self.lines))
770 770 if old:
771 771 cand = self.findlines(old[0][1:], oldstart)
772 772 else:
773 773 # Only adding lines with no or fuzzed context, just
774 774 # take the skew in account
775 775 cand = [oldstart]
776 776
777 777 for l in cand:
778 778 if not old or diffhelpers.testhunk(old, self.lines, l) == 0:
779 779 self.lines[l : l + len(old)] = new
780 780 self.offset += len(new) - len(old)
781 781 self.skew = l - orig_start
782 782 self.dirty = True
783 783 offset = l - orig_start - fuzzlen
784 784 if fuzzlen:
785 785 msg = _("Hunk #%d succeeded at %d "
786 786 "with fuzz %d "
787 787 "(offset %d lines).\n")
788 788 self.printfile(True)
789 789 self.ui.warn(msg %
790 790 (h.number, l + 1, fuzzlen, offset))
791 791 else:
792 792 msg = _("Hunk #%d succeeded at %d "
793 793 "(offset %d lines).\n")
794 794 self.ui.note(msg % (h.number, l + 1, offset))
795 795 return fuzzlen
796 796 self.printfile(True)
797 797 self.ui.warn(_("Hunk #%d FAILED at %d\n") % (h.number, orig_start))
798 798 self.rej.append(horig)
799 799 return -1
800 800
801 801 def close(self):
802 802 if self.dirty:
803 803 self.writelines(self.fname, self.lines, self.mode)
804 804 self.write_rej()
805 805 return len(self.rej)
806 806
807 807 class hunk(object):
808 808 def __init__(self, desc, num, lr, context):
809 809 self.number = num
810 810 self.desc = desc
811 811 self.hunk = [desc]
812 812 self.a = []
813 813 self.b = []
814 814 self.starta = self.lena = None
815 815 self.startb = self.lenb = None
816 816 if lr is not None:
817 817 if context:
818 818 self.read_context_hunk(lr)
819 819 else:
820 820 self.read_unified_hunk(lr)
821 821
822 822 def getnormalized(self):
823 823 """Return a copy with line endings normalized to LF."""
824 824
825 825 def normalize(lines):
826 826 nlines = []
827 827 for line in lines:
828 828 if line.endswith('\r\n'):
829 829 line = line[:-2] + '\n'
830 830 nlines.append(line)
831 831 return nlines
832 832
833 833 # Dummy object, it is rebuilt manually
834 834 nh = hunk(self.desc, self.number, None, None)
835 835 nh.number = self.number
836 836 nh.desc = self.desc
837 837 nh.hunk = self.hunk
838 838 nh.a = normalize(self.a)
839 839 nh.b = normalize(self.b)
840 840 nh.starta = self.starta
841 841 nh.startb = self.startb
842 842 nh.lena = self.lena
843 843 nh.lenb = self.lenb
844 844 return nh
845 845
846 846 def read_unified_hunk(self, lr):
847 847 m = unidesc.match(self.desc)
848 848 if not m:
849 849 raise PatchError(_("bad hunk #%d") % self.number)
850 850 self.starta, self.lena, self.startb, self.lenb = m.groups()
851 851 if self.lena is None:
852 852 self.lena = 1
853 853 else:
854 854 self.lena = int(self.lena)
855 855 if self.lenb is None:
856 856 self.lenb = 1
857 857 else:
858 858 self.lenb = int(self.lenb)
859 859 self.starta = int(self.starta)
860 860 self.startb = int(self.startb)
861 861 diffhelpers.addlines(lr, self.hunk, self.lena, self.lenb, self.a,
862 862 self.b)
863 863 # if we hit eof before finishing out the hunk, the last line will
864 864 # be zero length. Lets try to fix it up.
865 865 while len(self.hunk[-1]) == 0:
866 866 del self.hunk[-1]
867 867 del self.a[-1]
868 868 del self.b[-1]
869 869 self.lena -= 1
870 870 self.lenb -= 1
871 871 self._fixnewline(lr)
872 872
873 873 def read_context_hunk(self, lr):
874 874 self.desc = lr.readline()
875 875 m = contextdesc.match(self.desc)
876 876 if not m:
877 877 raise PatchError(_("bad hunk #%d") % self.number)
878 878 self.starta, aend = m.groups()
879 879 self.starta = int(self.starta)
880 880 if aend is None:
881 881 aend = self.starta
882 882 self.lena = int(aend) - self.starta
883 883 if self.starta:
884 884 self.lena += 1
885 885 for x in xrange(self.lena):
886 886 l = lr.readline()
887 887 if l.startswith('---'):
888 888 # lines addition, old block is empty
889 889 lr.push(l)
890 890 break
891 891 s = l[2:]
892 892 if l.startswith('- ') or l.startswith('! '):
893 893 u = '-' + s
894 894 elif l.startswith(' '):
895 895 u = ' ' + s
896 896 else:
897 897 raise PatchError(_("bad hunk #%d old text line %d") %
898 898 (self.number, x))
899 899 self.a.append(u)
900 900 self.hunk.append(u)
901 901
902 902 l = lr.readline()
903 903 if l.startswith('\ '):
904 904 s = self.a[-1][:-1]
905 905 self.a[-1] = s
906 906 self.hunk[-1] = s
907 907 l = lr.readline()
908 908 m = contextdesc.match(l)
909 909 if not m:
910 910 raise PatchError(_("bad hunk #%d") % self.number)
911 911 self.startb, bend = m.groups()
912 912 self.startb = int(self.startb)
913 913 if bend is None:
914 914 bend = self.startb
915 915 self.lenb = int(bend) - self.startb
916 916 if self.startb:
917 917 self.lenb += 1
918 918 hunki = 1
919 919 for x in xrange(self.lenb):
920 920 l = lr.readline()
921 921 if l.startswith('\ '):
922 922 # XXX: the only way to hit this is with an invalid line range.
923 923 # The no-eol marker is not counted in the line range, but I
924 924 # guess there are diff(1) out there which behave differently.
925 925 s = self.b[-1][:-1]
926 926 self.b[-1] = s
927 927 self.hunk[hunki - 1] = s
928 928 continue
929 929 if not l:
930 930 # line deletions, new block is empty and we hit EOF
931 931 lr.push(l)
932 932 break
933 933 s = l[2:]
934 934 if l.startswith('+ ') or l.startswith('! '):
935 935 u = '+' + s
936 936 elif l.startswith(' '):
937 937 u = ' ' + s
938 938 elif len(self.b) == 0:
939 939 # line deletions, new block is empty
940 940 lr.push(l)
941 941 break
942 942 else:
943 943 raise PatchError(_("bad hunk #%d old text line %d") %
944 944 (self.number, x))
945 945 self.b.append(s)
946 946 while True:
947 947 if hunki >= len(self.hunk):
948 948 h = ""
949 949 else:
950 950 h = self.hunk[hunki]
951 951 hunki += 1
952 952 if h == u:
953 953 break
954 954 elif h.startswith('-'):
955 955 continue
956 956 else:
957 957 self.hunk.insert(hunki - 1, u)
958 958 break
959 959
960 960 if not self.a:
961 961 # this happens when lines were only added to the hunk
962 962 for x in self.hunk:
963 963 if x.startswith('-') or x.startswith(' '):
964 964 self.a.append(x)
965 965 if not self.b:
966 966 # this happens when lines were only deleted from the hunk
967 967 for x in self.hunk:
968 968 if x.startswith('+') or x.startswith(' '):
969 969 self.b.append(x[1:])
970 970 # @@ -start,len +start,len @@
971 971 self.desc = "@@ -%d,%d +%d,%d @@\n" % (self.starta, self.lena,
972 972 self.startb, self.lenb)
973 973 self.hunk[0] = self.desc
974 974 self._fixnewline(lr)
975 975
976 976 def _fixnewline(self, lr):
977 977 l = lr.readline()
978 978 if l.startswith('\ '):
979 979 diffhelpers.fix_newline(self.hunk, self.a, self.b)
980 980 else:
981 981 lr.push(l)
982 982
983 983 def complete(self):
984 984 return len(self.a) == self.lena and len(self.b) == self.lenb
985 985
986 986 def _fuzzit(self, old, new, fuzz, toponly):
987 987 # this removes context lines from the top and bottom of list 'l'. It
988 988 # checks the hunk to make sure only context lines are removed, and then
989 989 # returns a new shortened list of lines.
990 990 fuzz = min(fuzz, len(old))
991 991 if fuzz:
992 992 top = 0
993 993 bot = 0
994 994 hlen = len(self.hunk)
995 995 for x in xrange(hlen - 1):
996 996 # the hunk starts with the @@ line, so use x+1
997 997 if self.hunk[x + 1][0] == ' ':
998 998 top += 1
999 999 else:
1000 1000 break
1001 1001 if not toponly:
1002 1002 for x in xrange(hlen - 1):
1003 1003 if self.hunk[hlen - bot - 1][0] == ' ':
1004 1004 bot += 1
1005 1005 else:
1006 1006 break
1007 1007
1008 1008 bot = min(fuzz, bot)
1009 1009 top = min(fuzz, top)
1010 1010 return old[top:len(old) - bot], new[top:len(new) - bot], top
1011 1011 return old, new, 0
1012 1012
1013 1013 def fuzzit(self, fuzz, toponly):
1014 1014 old, new, top = self._fuzzit(self.a, self.b, fuzz, toponly)
1015 1015 oldstart = self.starta + top
1016 1016 newstart = self.startb + top
1017 1017 # zero length hunk ranges already have their start decremented
1018 1018 if self.lena and oldstart > 0:
1019 1019 oldstart -= 1
1020 1020 if self.lenb and newstart > 0:
1021 1021 newstart -= 1
1022 1022 return old, oldstart, new, newstart
1023 1023
1024 1024 class binhunk(object):
1025 1025 'A binary patch file.'
1026 1026 def __init__(self, lr, fname):
1027 1027 self.text = None
1028 1028 self.delta = False
1029 1029 self.hunk = ['GIT binary patch\n']
1030 1030 self._fname = fname
1031 1031 self._read(lr)
1032 1032
1033 1033 def complete(self):
1034 1034 return self.text is not None
1035 1035
1036 1036 def new(self, lines):
1037 1037 if self.delta:
1038 1038 return [applybindelta(self.text, ''.join(lines))]
1039 1039 return [self.text]
1040 1040
1041 1041 def _read(self, lr):
1042 1042 def getline(lr, hunk):
1043 1043 l = lr.readline()
1044 1044 hunk.append(l)
1045 1045 return l.rstrip('\r\n')
1046 1046
1047 1047 size = 0
1048 1048 while True:
1049 1049 line = getline(lr, self.hunk)
1050 1050 if not line:
1051 1051 raise PatchError(_('could not extract "%s" binary data')
1052 1052 % self._fname)
1053 1053 if line.startswith('literal '):
1054 1054 size = int(line[8:].rstrip())
1055 1055 break
1056 1056 if line.startswith('delta '):
1057 1057 size = int(line[6:].rstrip())
1058 1058 self.delta = True
1059 1059 break
1060 1060 dec = []
1061 1061 line = getline(lr, self.hunk)
1062 1062 while len(line) > 1:
1063 1063 l = line[0]
1064 1064 if l <= 'Z' and l >= 'A':
1065 1065 l = ord(l) - ord('A') + 1
1066 1066 else:
1067 1067 l = ord(l) - ord('a') + 27
1068 1068 try:
1069 1069 dec.append(base85.b85decode(line[1:])[:l])
1070 1070 except ValueError, e:
1071 1071 raise PatchError(_('could not decode "%s" binary patch: %s')
1072 1072 % (self._fname, str(e)))
1073 1073 line = getline(lr, self.hunk)
1074 1074 text = zlib.decompress(''.join(dec))
1075 1075 if len(text) != size:
1076 1076 raise PatchError(_('"%s" length is %d bytes, should be %d')
1077 1077 % (self._fname, len(text), size))
1078 1078 self.text = text
1079 1079
1080 1080 def parsefilename(str):
1081 1081 # --- filename \t|space stuff
1082 1082 s = str[4:].rstrip('\r\n')
1083 1083 i = s.find('\t')
1084 1084 if i < 0:
1085 1085 i = s.find(' ')
1086 1086 if i < 0:
1087 1087 return s
1088 1088 return s[:i]
1089 1089
1090 1090 def pathtransform(path, strip, prefix):
1091 1091 '''turn a path from a patch into a path suitable for the repository
1092 1092
1093 1093 prefix, if not empty, is expected to be normalized with a / at the end.
1094 1094
1095 1095 Returns (stripped components, path in repository).
1096 1096
1097 1097 >>> pathtransform('a/b/c', 0, '')
1098 1098 ('', 'a/b/c')
1099 1099 >>> pathtransform(' a/b/c ', 0, '')
1100 1100 ('', ' a/b/c')
1101 1101 >>> pathtransform(' a/b/c ', 2, '')
1102 1102 ('a/b/', 'c')
1103 1103 >>> pathtransform(' a//b/c ', 2, 'd/e/')
1104 1104 ('a//b/', 'd/e/c')
1105 1105 >>> pathtransform('a/b/c', 3, '')
1106 1106 Traceback (most recent call last):
1107 1107 PatchError: unable to strip away 1 of 3 dirs from a/b/c
1108 1108 '''
1109 1109 pathlen = len(path)
1110 1110 i = 0
1111 1111 if strip == 0:
1112 1112 return '', path.rstrip()
1113 1113 count = strip
1114 1114 while count > 0:
1115 1115 i = path.find('/', i)
1116 1116 if i == -1:
1117 1117 raise PatchError(_("unable to strip away %d of %d dirs from %s") %
1118 1118 (count, strip, path))
1119 1119 i += 1
1120 1120 # consume '//' in the path
1121 1121 while i < pathlen - 1 and path[i] == '/':
1122 1122 i += 1
1123 1123 count -= 1
1124 1124 return path[:i].lstrip(), prefix + path[i:].rstrip()
1125 1125
1126 1126 def makepatchmeta(backend, afile_orig, bfile_orig, hunk, strip, prefix):
1127 1127 nulla = afile_orig == "/dev/null"
1128 1128 nullb = bfile_orig == "/dev/null"
1129 1129 create = nulla and hunk.starta == 0 and hunk.lena == 0
1130 1130 remove = nullb and hunk.startb == 0 and hunk.lenb == 0
1131 1131 abase, afile = pathtransform(afile_orig, strip, prefix)
1132 1132 gooda = not nulla and backend.exists(afile)
1133 1133 bbase, bfile = pathtransform(bfile_orig, strip, prefix)
1134 1134 if afile == bfile:
1135 1135 goodb = gooda
1136 1136 else:
1137 1137 goodb = not nullb and backend.exists(bfile)
1138 1138 missing = not goodb and not gooda and not create
1139 1139
1140 1140 # some diff programs apparently produce patches where the afile is
1141 1141 # not /dev/null, but afile starts with bfile
1142 1142 abasedir = afile[:afile.rfind('/') + 1]
1143 1143 bbasedir = bfile[:bfile.rfind('/') + 1]
1144 1144 if (missing and abasedir == bbasedir and afile.startswith(bfile)
1145 1145 and hunk.starta == 0 and hunk.lena == 0):
1146 1146 create = True
1147 1147 missing = False
1148 1148
1149 1149 # If afile is "a/b/foo" and bfile is "a/b/foo.orig" we assume the
1150 1150 # diff is between a file and its backup. In this case, the original
1151 1151 # file should be patched (see original mpatch code).
1152 1152 isbackup = (abase == bbase and bfile.startswith(afile))
1153 1153 fname = None
1154 1154 if not missing:
1155 1155 if gooda and goodb:
1156 1156 fname = isbackup and afile or bfile
1157 1157 elif gooda:
1158 1158 fname = afile
1159 1159
1160 1160 if not fname:
1161 1161 if not nullb:
1162 1162 fname = isbackup and afile or bfile
1163 1163 elif not nulla:
1164 1164 fname = afile
1165 1165 else:
1166 1166 raise PatchError(_("undefined source and destination files"))
1167 1167
1168 1168 gp = patchmeta(fname)
1169 1169 if create:
1170 1170 gp.op = 'ADD'
1171 1171 elif remove:
1172 1172 gp.op = 'DELETE'
1173 1173 return gp
1174 1174
1175 1175 def scangitpatch(lr, firstline):
1176 1176 """
1177 1177 Git patches can emit:
1178 1178 - rename a to b
1179 1179 - change b
1180 1180 - copy a to c
1181 1181 - change c
1182 1182
1183 1183 We cannot apply this sequence as-is, the renamed 'a' could not be
1184 1184 found for it would have been renamed already. And we cannot copy
1185 1185 from 'b' instead because 'b' would have been changed already. So
1186 1186 we scan the git patch for copy and rename commands so we can
1187 1187 perform the copies ahead of time.
1188 1188 """
1189 1189 pos = 0
1190 1190 try:
1191 1191 pos = lr.fp.tell()
1192 1192 fp = lr.fp
1193 1193 except IOError:
1194 1194 fp = cStringIO.StringIO(lr.fp.read())
1195 1195 gitlr = linereader(fp)
1196 1196 gitlr.push(firstline)
1197 1197 gitpatches = readgitpatch(gitlr)
1198 1198 fp.seek(pos)
1199 1199 return gitpatches
1200 1200
1201 1201 def iterhunks(fp):
1202 1202 """Read a patch and yield the following events:
1203 1203 - ("file", afile, bfile, firsthunk): select a new target file.
1204 1204 - ("hunk", hunk): a new hunk is ready to be applied, follows a
1205 1205 "file" event.
1206 1206 - ("git", gitchanges): current diff is in git format, gitchanges
1207 1207 maps filenames to gitpatch records. Unique event.
1208 1208 """
1209 1209 afile = ""
1210 1210 bfile = ""
1211 1211 state = None
1212 1212 hunknum = 0
1213 1213 emitfile = newfile = False
1214 1214 gitpatches = None
1215 1215
1216 1216 # our states
1217 1217 BFILE = 1
1218 1218 context = None
1219 1219 lr = linereader(fp)
1220 1220
1221 1221 while True:
1222 1222 x = lr.readline()
1223 1223 if not x:
1224 1224 break
1225 1225 if state == BFILE and (
1226 1226 (not context and x[0] == '@')
1227 1227 or (context is not False and x.startswith('***************'))
1228 1228 or x.startswith('GIT binary patch')):
1229 1229 gp = None
1230 1230 if (gitpatches and
1231 1231 gitpatches[-1].ispatching(afile, bfile)):
1232 1232 gp = gitpatches.pop()
1233 1233 if x.startswith('GIT binary patch'):
1234 1234 h = binhunk(lr, gp.path)
1235 1235 else:
1236 1236 if context is None and x.startswith('***************'):
1237 1237 context = True
1238 1238 h = hunk(x, hunknum + 1, lr, context)
1239 1239 hunknum += 1
1240 1240 if emitfile:
1241 1241 emitfile = False
1242 1242 yield 'file', (afile, bfile, h, gp and gp.copy() or None)
1243 1243 yield 'hunk', h
1244 1244 elif x.startswith('diff --git a/'):
1245 1245 m = gitre.match(x.rstrip(' \r\n'))
1246 1246 if not m:
1247 1247 continue
1248 1248 if gitpatches is None:
1249 1249 # scan whole input for git metadata
1250 1250 gitpatches = scangitpatch(lr, x)
1251 1251 yield 'git', [g.copy() for g in gitpatches
1252 1252 if g.op in ('COPY', 'RENAME')]
1253 1253 gitpatches.reverse()
1254 1254 afile = 'a/' + m.group(1)
1255 1255 bfile = 'b/' + m.group(2)
1256 1256 while gitpatches and not gitpatches[-1].ispatching(afile, bfile):
1257 1257 gp = gitpatches.pop()
1258 1258 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1259 1259 if not gitpatches:
1260 1260 raise PatchError(_('failed to synchronize metadata for "%s"')
1261 1261 % afile[2:])
1262 1262 gp = gitpatches[-1]
1263 1263 newfile = True
1264 1264 elif x.startswith('---'):
1265 1265 # check for a unified diff
1266 1266 l2 = lr.readline()
1267 1267 if not l2.startswith('+++'):
1268 1268 lr.push(l2)
1269 1269 continue
1270 1270 newfile = True
1271 1271 context = False
1272 1272 afile = parsefilename(x)
1273 1273 bfile = parsefilename(l2)
1274 1274 elif x.startswith('***'):
1275 1275 # check for a context diff
1276 1276 l2 = lr.readline()
1277 1277 if not l2.startswith('---'):
1278 1278 lr.push(l2)
1279 1279 continue
1280 1280 l3 = lr.readline()
1281 1281 lr.push(l3)
1282 1282 if not l3.startswith("***************"):
1283 1283 lr.push(l2)
1284 1284 continue
1285 1285 newfile = True
1286 1286 context = True
1287 1287 afile = parsefilename(x)
1288 1288 bfile = parsefilename(l2)
1289 1289
1290 1290 if newfile:
1291 1291 newfile = False
1292 1292 emitfile = True
1293 1293 state = BFILE
1294 1294 hunknum = 0
1295 1295
1296 1296 while gitpatches:
1297 1297 gp = gitpatches.pop()
1298 1298 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp.copy())
1299 1299
1300 1300 def applybindelta(binchunk, data):
1301 1301 """Apply a binary delta hunk
1302 1302 The algorithm used is the algorithm from git's patch-delta.c
1303 1303 """
1304 1304 def deltahead(binchunk):
1305 1305 i = 0
1306 1306 for c in binchunk:
1307 1307 i += 1
1308 1308 if not (ord(c) & 0x80):
1309 1309 return i
1310 1310 return i
1311 1311 out = ""
1312 1312 s = deltahead(binchunk)
1313 1313 binchunk = binchunk[s:]
1314 1314 s = deltahead(binchunk)
1315 1315 binchunk = binchunk[s:]
1316 1316 i = 0
1317 1317 while i < len(binchunk):
1318 1318 cmd = ord(binchunk[i])
1319 1319 i += 1
1320 1320 if (cmd & 0x80):
1321 1321 offset = 0
1322 1322 size = 0
1323 1323 if (cmd & 0x01):
1324 1324 offset = ord(binchunk[i])
1325 1325 i += 1
1326 1326 if (cmd & 0x02):
1327 1327 offset |= ord(binchunk[i]) << 8
1328 1328 i += 1
1329 1329 if (cmd & 0x04):
1330 1330 offset |= ord(binchunk[i]) << 16
1331 1331 i += 1
1332 1332 if (cmd & 0x08):
1333 1333 offset |= ord(binchunk[i]) << 24
1334 1334 i += 1
1335 1335 if (cmd & 0x10):
1336 1336 size = ord(binchunk[i])
1337 1337 i += 1
1338 1338 if (cmd & 0x20):
1339 1339 size |= ord(binchunk[i]) << 8
1340 1340 i += 1
1341 1341 if (cmd & 0x40):
1342 1342 size |= ord(binchunk[i]) << 16
1343 1343 i += 1
1344 1344 if size == 0:
1345 1345 size = 0x10000
1346 1346 offset_end = offset + size
1347 1347 out += data[offset:offset_end]
1348 1348 elif cmd != 0:
1349 1349 offset_end = i + cmd
1350 1350 out += binchunk[i:offset_end]
1351 1351 i += cmd
1352 1352 else:
1353 1353 raise PatchError(_('unexpected delta opcode 0'))
1354 1354 return out
1355 1355
1356 1356 def applydiff(ui, fp, backend, store, strip=1, prefix='', eolmode='strict'):
1357 1357 """Reads a patch from fp and tries to apply it.
1358 1358
1359 1359 Returns 0 for a clean patch, -1 if any rejects were found and 1 if
1360 1360 there was any fuzz.
1361 1361
1362 1362 If 'eolmode' is 'strict', the patch content and patched file are
1363 1363 read in binary mode. Otherwise, line endings are ignored when
1364 1364 patching then normalized according to 'eolmode'.
1365 1365 """
1366 1366 return _applydiff(ui, fp, patchfile, backend, store, strip=strip,
1367 1367 prefix=prefix, eolmode=eolmode)
1368 1368
1369 1369 def _applydiff(ui, fp, patcher, backend, store, strip=1, prefix='',
1370 1370 eolmode='strict'):
1371 1371
1372 1372 if prefix:
1373 1373 # clean up double slashes, lack of trailing slashes, etc
1374 1374 prefix = util.normpath(prefix) + '/'
1375 1375 def pstrip(p):
1376 1376 return pathtransform(p, strip - 1, prefix)[1]
1377 1377
1378 1378 rejects = 0
1379 1379 err = 0
1380 1380 current_file = None
1381 1381
1382 1382 for state, values in iterhunks(fp):
1383 1383 if state == 'hunk':
1384 1384 if not current_file:
1385 1385 continue
1386 1386 ret = current_file.apply(values)
1387 1387 if ret > 0:
1388 1388 err = 1
1389 1389 elif state == 'file':
1390 1390 if current_file:
1391 1391 rejects += current_file.close()
1392 1392 current_file = None
1393 1393 afile, bfile, first_hunk, gp = values
1394 1394 if gp:
1395 1395 gp.path = pstrip(gp.path)
1396 1396 if gp.oldpath:
1397 1397 gp.oldpath = pstrip(gp.oldpath)
1398 1398 else:
1399 1399 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1400 1400 prefix)
1401 1401 if gp.op == 'RENAME':
1402 1402 backend.unlink(gp.oldpath)
1403 1403 if not first_hunk:
1404 1404 if gp.op == 'DELETE':
1405 1405 backend.unlink(gp.path)
1406 1406 continue
1407 1407 data, mode = None, None
1408 1408 if gp.op in ('RENAME', 'COPY'):
1409 1409 data, mode = store.getfile(gp.oldpath)[:2]
1410 1410 # FIXME: failing getfile has never been handled here
1411 1411 assert data is not None
1412 1412 if gp.mode:
1413 1413 mode = gp.mode
1414 1414 if gp.op == 'ADD':
1415 1415 # Added files without content have no hunk and
1416 1416 # must be created
1417 1417 data = ''
1418 1418 if data or mode:
1419 1419 if (gp.op in ('ADD', 'RENAME', 'COPY')
1420 1420 and backend.exists(gp.path)):
1421 1421 raise PatchError(_("cannot create %s: destination "
1422 1422 "already exists") % gp.path)
1423 1423 backend.setfile(gp.path, data, mode, gp.oldpath)
1424 1424 continue
1425 1425 try:
1426 1426 current_file = patcher(ui, gp, backend, store,
1427 1427 eolmode=eolmode)
1428 1428 except PatchError, inst:
1429 1429 ui.warn(str(inst) + '\n')
1430 1430 current_file = None
1431 1431 rejects += 1
1432 1432 continue
1433 1433 elif state == 'git':
1434 1434 for gp in values:
1435 1435 path = pstrip(gp.oldpath)
1436 1436 data, mode = backend.getfile(path)
1437 1437 if data is None:
1438 1438 # The error ignored here will trigger a getfile()
1439 1439 # error in a place more appropriate for error
1440 1440 # handling, and will not interrupt the patching
1441 1441 # process.
1442 1442 pass
1443 1443 else:
1444 1444 store.setfile(path, data, mode)
1445 1445 else:
1446 1446 raise util.Abort(_('unsupported parser state: %s') % state)
1447 1447
1448 1448 if current_file:
1449 1449 rejects += current_file.close()
1450 1450
1451 1451 if rejects:
1452 1452 return -1
1453 1453 return err
1454 1454
1455 1455 def _externalpatch(ui, repo, patcher, patchname, strip, files,
1456 1456 similarity):
1457 1457 """use <patcher> to apply <patchname> to the working directory.
1458 1458 returns whether patch was applied with fuzz factor."""
1459 1459
1460 1460 fuzz = False
1461 1461 args = []
1462 1462 cwd = repo.root
1463 1463 if cwd:
1464 1464 args.append('-d %s' % util.shellquote(cwd))
1465 1465 fp = util.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
1466 1466 util.shellquote(patchname)))
1467 1467 try:
1468 1468 for line in fp:
1469 1469 line = line.rstrip()
1470 1470 ui.note(line + '\n')
1471 1471 if line.startswith('patching file '):
1472 1472 pf = util.parsepatchoutput(line)
1473 1473 printed_file = False
1474 1474 files.add(pf)
1475 1475 elif line.find('with fuzz') >= 0:
1476 1476 fuzz = True
1477 1477 if not printed_file:
1478 1478 ui.warn(pf + '\n')
1479 1479 printed_file = True
1480 1480 ui.warn(line + '\n')
1481 1481 elif line.find('saving rejects to file') >= 0:
1482 1482 ui.warn(line + '\n')
1483 1483 elif line.find('FAILED') >= 0:
1484 1484 if not printed_file:
1485 1485 ui.warn(pf + '\n')
1486 1486 printed_file = True
1487 1487 ui.warn(line + '\n')
1488 1488 finally:
1489 1489 if files:
1490 1490 scmutil.marktouched(repo, files, similarity)
1491 1491 code = fp.close()
1492 1492 if code:
1493 1493 raise PatchError(_("patch command failed: %s") %
1494 1494 util.explainexit(code)[0])
1495 1495 return fuzz
1496 1496
1497 1497 def patchbackend(ui, backend, patchobj, strip, prefix, files=None,
1498 1498 eolmode='strict'):
1499 1499 if files is None:
1500 1500 files = set()
1501 1501 if eolmode is None:
1502 1502 eolmode = ui.config('patch', 'eol', 'strict')
1503 1503 if eolmode.lower() not in eolmodes:
1504 1504 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1505 1505 eolmode = eolmode.lower()
1506 1506
1507 1507 store = filestore()
1508 1508 try:
1509 1509 fp = open(patchobj, 'rb')
1510 1510 except TypeError:
1511 1511 fp = patchobj
1512 1512 try:
1513 1513 ret = applydiff(ui, fp, backend, store, strip=strip, prefix=prefix,
1514 1514 eolmode=eolmode)
1515 1515 finally:
1516 1516 if fp != patchobj:
1517 1517 fp.close()
1518 1518 files.update(backend.close())
1519 1519 store.close()
1520 1520 if ret < 0:
1521 1521 raise PatchError(_('patch failed to apply'))
1522 1522 return ret > 0
1523 1523
1524 1524 def internalpatch(ui, repo, patchobj, strip, prefix, files=None,
1525 1525 eolmode='strict', similarity=0):
1526 1526 """use builtin patch to apply <patchobj> to the working directory.
1527 1527 returns whether patch was applied with fuzz factor."""
1528 1528 backend = workingbackend(ui, repo, similarity)
1529 1529 return patchbackend(ui, backend, patchobj, strip, prefix, files, eolmode)
1530 1530
1531 1531 def patchrepo(ui, repo, ctx, store, patchobj, strip, files=None,
1532 1532 eolmode='strict'):
1533 1533 backend = repobackend(ui, repo, ctx, store)
1534 1534 return patchbackend(ui, backend, patchobj, strip, '', files, eolmode)
1535 1535
1536 def patch(ui, repo, patchname, strip=1, files=None, eolmode='strict',
1536 def patch(ui, repo, patchname, strip=1, prefix='', files=None, eolmode='strict',
1537 1537 similarity=0):
1538 1538 """Apply <patchname> to the working directory.
1539 1539
1540 1540 'eolmode' specifies how end of lines should be handled. It can be:
1541 1541 - 'strict': inputs are read in binary mode, EOLs are preserved
1542 1542 - 'crlf': EOLs are ignored when patching and reset to CRLF
1543 1543 - 'lf': EOLs are ignored when patching and reset to LF
1544 1544 - None: get it from user settings, default to 'strict'
1545 1545 'eolmode' is ignored when using an external patcher program.
1546 1546
1547 1547 Returns whether patch was applied with fuzz factor.
1548 1548 """
1549 1549 patcher = ui.config('ui', 'patch')
1550 1550 if files is None:
1551 1551 files = set()
1552 1552 if patcher:
1553 1553 return _externalpatch(ui, repo, patcher, patchname, strip,
1554 1554 files, similarity)
1555 return internalpatch(ui, repo, patchname, strip, '', files, eolmode,
1555 return internalpatch(ui, repo, patchname, strip, prefix, files, eolmode,
1556 1556 similarity)
1557 1557
1558 1558 def changedfiles(ui, repo, patchpath, strip=1):
1559 1559 backend = fsbackend(ui, repo.root)
1560 1560 fp = open(patchpath, 'rb')
1561 1561 try:
1562 1562 changed = set()
1563 1563 for state, values in iterhunks(fp):
1564 1564 if state == 'file':
1565 1565 afile, bfile, first_hunk, gp = values
1566 1566 if gp:
1567 1567 gp.path = pathtransform(gp.path, strip - 1, '')[1]
1568 1568 if gp.oldpath:
1569 1569 gp.oldpath = pathtransform(gp.oldpath, strip - 1, '')[1]
1570 1570 else:
1571 1571 gp = makepatchmeta(backend, afile, bfile, first_hunk, strip,
1572 1572 '')
1573 1573 changed.add(gp.path)
1574 1574 if gp.op == 'RENAME':
1575 1575 changed.add(gp.oldpath)
1576 1576 elif state not in ('hunk', 'git'):
1577 1577 raise util.Abort(_('unsupported parser state: %s') % state)
1578 1578 return changed
1579 1579 finally:
1580 1580 fp.close()
1581 1581
1582 1582 class GitDiffRequired(Exception):
1583 1583 pass
1584 1584
1585 1585 def diffallopts(ui, opts=None, untrusted=False, section='diff'):
1586 1586 '''return diffopts with all features supported and parsed'''
1587 1587 return difffeatureopts(ui, opts=opts, untrusted=untrusted, section=section,
1588 1588 git=True, whitespace=True, formatchanging=True)
1589 1589
1590 1590 diffopts = diffallopts
1591 1591
1592 1592 def difffeatureopts(ui, opts=None, untrusted=False, section='diff', git=False,
1593 1593 whitespace=False, formatchanging=False):
1594 1594 '''return diffopts with only opted-in features parsed
1595 1595
1596 1596 Features:
1597 1597 - git: git-style diffs
1598 1598 - whitespace: whitespace options like ignoreblanklines and ignorews
1599 1599 - formatchanging: options that will likely break or cause correctness issues
1600 1600 with most diff parsers
1601 1601 '''
1602 1602 def get(key, name=None, getter=ui.configbool, forceplain=None):
1603 1603 if opts:
1604 1604 v = opts.get(key)
1605 1605 if v:
1606 1606 return v
1607 1607 if forceplain is not None and ui.plain():
1608 1608 return forceplain
1609 1609 return getter(section, name or key, None, untrusted=untrusted)
1610 1610
1611 1611 # core options, expected to be understood by every diff parser
1612 1612 buildopts = {
1613 1613 'nodates': get('nodates'),
1614 1614 'showfunc': get('show_function', 'showfunc'),
1615 1615 'context': get('unified', getter=ui.config),
1616 1616 }
1617 1617
1618 1618 if git:
1619 1619 buildopts['git'] = get('git')
1620 1620 if whitespace:
1621 1621 buildopts['ignorews'] = get('ignore_all_space', 'ignorews')
1622 1622 buildopts['ignorewsamount'] = get('ignore_space_change',
1623 1623 'ignorewsamount')
1624 1624 buildopts['ignoreblanklines'] = get('ignore_blank_lines',
1625 1625 'ignoreblanklines')
1626 1626 if formatchanging:
1627 1627 buildopts['text'] = opts and opts.get('text')
1628 1628 buildopts['nobinary'] = get('nobinary')
1629 1629 buildopts['noprefix'] = get('noprefix', forceplain=False)
1630 1630
1631 1631 return mdiff.diffopts(**buildopts)
1632 1632
1633 1633 def diff(repo, node1=None, node2=None, match=None, changes=None, opts=None,
1634 1634 losedatafn=None, prefix=''):
1635 1635 '''yields diff of changes to files between two nodes, or node and
1636 1636 working directory.
1637 1637
1638 1638 if node1 is None, use first dirstate parent instead.
1639 1639 if node2 is None, compare node1 with working directory.
1640 1640
1641 1641 losedatafn(**kwarg) is a callable run when opts.upgrade=True and
1642 1642 every time some change cannot be represented with the current
1643 1643 patch format. Return False to upgrade to git patch format, True to
1644 1644 accept the loss or raise an exception to abort the diff. It is
1645 1645 called with the name of current file being diffed as 'fn'. If set
1646 1646 to None, patches will always be upgraded to git format when
1647 1647 necessary.
1648 1648
1649 1649 prefix is a filename prefix that is prepended to all filenames on
1650 1650 display (used for subrepos).
1651 1651 '''
1652 1652
1653 1653 if opts is None:
1654 1654 opts = mdiff.defaultopts
1655 1655
1656 1656 if not node1 and not node2:
1657 1657 node1 = repo.dirstate.p1()
1658 1658
1659 1659 def lrugetfilectx():
1660 1660 cache = {}
1661 1661 order = util.deque()
1662 1662 def getfilectx(f, ctx):
1663 1663 fctx = ctx.filectx(f, filelog=cache.get(f))
1664 1664 if f not in cache:
1665 1665 if len(cache) > 20:
1666 1666 del cache[order.popleft()]
1667 1667 cache[f] = fctx.filelog()
1668 1668 else:
1669 1669 order.remove(f)
1670 1670 order.append(f)
1671 1671 return fctx
1672 1672 return getfilectx
1673 1673 getfilectx = lrugetfilectx()
1674 1674
1675 1675 ctx1 = repo[node1]
1676 1676 ctx2 = repo[node2]
1677 1677
1678 1678 if not changes:
1679 1679 changes = repo.status(ctx1, ctx2, match=match)
1680 1680 modified, added, removed = changes[:3]
1681 1681
1682 1682 if not modified and not added and not removed:
1683 1683 return []
1684 1684
1685 1685 hexfunc = repo.ui.debugflag and hex or short
1686 1686 revs = [hexfunc(node) for node in [ctx1.node(), ctx2.node()] if node]
1687 1687
1688 1688 copy = {}
1689 1689 if opts.git or opts.upgrade:
1690 1690 copy = copies.pathcopies(ctx1, ctx2)
1691 1691
1692 1692 def difffn(opts, losedata):
1693 1693 return trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1694 1694 copy, getfilectx, opts, losedata, prefix)
1695 1695 if opts.upgrade and not opts.git:
1696 1696 try:
1697 1697 def losedata(fn):
1698 1698 if not losedatafn or not losedatafn(fn=fn):
1699 1699 raise GitDiffRequired
1700 1700 # Buffer the whole output until we are sure it can be generated
1701 1701 return list(difffn(opts.copy(git=False), losedata))
1702 1702 except GitDiffRequired:
1703 1703 return difffn(opts.copy(git=True), None)
1704 1704 else:
1705 1705 return difffn(opts, None)
1706 1706
1707 1707 def difflabel(func, *args, **kw):
1708 1708 '''yields 2-tuples of (output, label) based on the output of func()'''
1709 1709 headprefixes = [('diff', 'diff.diffline'),
1710 1710 ('copy', 'diff.extended'),
1711 1711 ('rename', 'diff.extended'),
1712 1712 ('old', 'diff.extended'),
1713 1713 ('new', 'diff.extended'),
1714 1714 ('deleted', 'diff.extended'),
1715 1715 ('---', 'diff.file_a'),
1716 1716 ('+++', 'diff.file_b')]
1717 1717 textprefixes = [('@', 'diff.hunk'),
1718 1718 ('-', 'diff.deleted'),
1719 1719 ('+', 'diff.inserted')]
1720 1720 head = False
1721 1721 for chunk in func(*args, **kw):
1722 1722 lines = chunk.split('\n')
1723 1723 for i, line in enumerate(lines):
1724 1724 if i != 0:
1725 1725 yield ('\n', '')
1726 1726 if head:
1727 1727 if line.startswith('@'):
1728 1728 head = False
1729 1729 else:
1730 1730 if line and line[0] not in ' +-@\\':
1731 1731 head = True
1732 1732 stripline = line
1733 1733 diffline = False
1734 1734 if not head and line and line[0] in '+-':
1735 1735 # highlight tabs and trailing whitespace, but only in
1736 1736 # changed lines
1737 1737 stripline = line.rstrip()
1738 1738 diffline = True
1739 1739
1740 1740 prefixes = textprefixes
1741 1741 if head:
1742 1742 prefixes = headprefixes
1743 1743 for prefix, label in prefixes:
1744 1744 if stripline.startswith(prefix):
1745 1745 if diffline:
1746 1746 for token in tabsplitter.findall(stripline):
1747 1747 if '\t' == token[0]:
1748 1748 yield (token, 'diff.tab')
1749 1749 else:
1750 1750 yield (token, label)
1751 1751 else:
1752 1752 yield (stripline, label)
1753 1753 break
1754 1754 else:
1755 1755 yield (line, '')
1756 1756 if line != stripline:
1757 1757 yield (line[len(stripline):], 'diff.trailingwhitespace')
1758 1758
1759 1759 def diffui(*args, **kw):
1760 1760 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
1761 1761 return difflabel(diff, *args, **kw)
1762 1762
1763 1763 def _filepairs(ctx1, modified, added, removed, copy, opts):
1764 1764 '''generates tuples (f1, f2, copyop), where f1 is the name of the file
1765 1765 before and f2 is the the name after. For added files, f1 will be None,
1766 1766 and for removed files, f2 will be None. copyop may be set to None, 'copy'
1767 1767 or 'rename' (the latter two only if opts.git is set).'''
1768 1768 gone = set()
1769 1769
1770 1770 copyto = dict([(v, k) for k, v in copy.items()])
1771 1771
1772 1772 addedset, removedset = set(added), set(removed)
1773 1773 # Fix up added, since merged-in additions appear as
1774 1774 # modifications during merges
1775 1775 for f in modified:
1776 1776 if f not in ctx1:
1777 1777 addedset.add(f)
1778 1778
1779 1779 for f in sorted(modified + added + removed):
1780 1780 copyop = None
1781 1781 f1, f2 = f, f
1782 1782 if f in addedset:
1783 1783 f1 = None
1784 1784 if f in copy:
1785 1785 if opts.git:
1786 1786 f1 = copy[f]
1787 1787 if f1 in removedset and f1 not in gone:
1788 1788 copyop = 'rename'
1789 1789 gone.add(f1)
1790 1790 else:
1791 1791 copyop = 'copy'
1792 1792 elif f in removedset:
1793 1793 f2 = None
1794 1794 if opts.git:
1795 1795 # have we already reported a copy above?
1796 1796 if (f in copyto and copyto[f] in addedset
1797 1797 and copy[copyto[f]] == f):
1798 1798 continue
1799 1799 yield f1, f2, copyop
1800 1800
1801 1801 def trydiff(repo, revs, ctx1, ctx2, modified, added, removed,
1802 1802 copy, getfilectx, opts, losedatafn, prefix):
1803 1803
1804 1804 def gitindex(text):
1805 1805 if not text:
1806 1806 text = ""
1807 1807 l = len(text)
1808 1808 s = util.sha1('blob %d\0' % l)
1809 1809 s.update(text)
1810 1810 return s.hexdigest()
1811 1811
1812 1812 if opts.noprefix:
1813 1813 aprefix = bprefix = ''
1814 1814 else:
1815 1815 aprefix = 'a/'
1816 1816 bprefix = 'b/'
1817 1817
1818 1818 def diffline(f, revs):
1819 1819 revinfo = ' '.join(["-r %s" % rev for rev in revs])
1820 1820 return 'diff %s %s' % (revinfo, f)
1821 1821
1822 1822 date1 = util.datestr(ctx1.date())
1823 1823 date2 = util.datestr(ctx2.date())
1824 1824
1825 1825 gitmode = {'l': '120000', 'x': '100755', '': '100644'}
1826 1826
1827 1827 for f1, f2, copyop in _filepairs(
1828 1828 ctx1, modified, added, removed, copy, opts):
1829 1829 content1 = None
1830 1830 content2 = None
1831 1831 flag1 = None
1832 1832 flag2 = None
1833 1833 if f1:
1834 1834 content1 = getfilectx(f1, ctx1).data()
1835 1835 if opts.git or losedatafn:
1836 1836 flag1 = ctx1.flags(f1)
1837 1837 if f2:
1838 1838 content2 = getfilectx(f2, ctx2).data()
1839 1839 if opts.git or losedatafn:
1840 1840 flag2 = ctx2.flags(f2)
1841 1841 binary = False
1842 1842 if opts.git or losedatafn:
1843 1843 binary = util.binary(content1) or util.binary(content2)
1844 1844
1845 1845 if losedatafn and not opts.git:
1846 1846 if (binary or
1847 1847 # copy/rename
1848 1848 f2 in copy or
1849 1849 # empty file creation
1850 1850 (not f1 and not content2) or
1851 1851 # empty file deletion
1852 1852 (not content1 and not f2) or
1853 1853 # create with flags
1854 1854 (not f1 and flag2) or
1855 1855 # change flags
1856 1856 (f1 and f2 and flag1 != flag2)):
1857 1857 losedatafn(f2 or f1)
1858 1858
1859 1859 path1 = posixpath.join(prefix, f1 or f2)
1860 1860 path2 = posixpath.join(prefix, f2 or f1)
1861 1861 header = []
1862 1862 if opts.git:
1863 1863 header.append('diff --git %s%s %s%s' %
1864 1864 (aprefix, path1, bprefix, path2))
1865 1865 if not f1: # added
1866 1866 header.append('new file mode %s' % gitmode[flag2])
1867 1867 elif not f2: # removed
1868 1868 header.append('deleted file mode %s' % gitmode[flag1])
1869 1869 else: # modified/copied/renamed
1870 1870 mode1, mode2 = gitmode[flag1], gitmode[flag2]
1871 1871 if mode1 != mode2:
1872 1872 header.append('old mode %s' % mode1)
1873 1873 header.append('new mode %s' % mode2)
1874 1874 if copyop is not None:
1875 1875 header.append('%s from %s' % (copyop, path1))
1876 1876 header.append('%s to %s' % (copyop, path2))
1877 1877 elif revs and not repo.ui.quiet:
1878 1878 header.append(diffline(path1, revs))
1879 1879
1880 1880 if binary and opts.git and not opts.nobinary:
1881 1881 text = mdiff.b85diff(content1, content2)
1882 1882 if text:
1883 1883 header.append('index %s..%s' %
1884 1884 (gitindex(content1), gitindex(content2)))
1885 1885 else:
1886 1886 text = mdiff.unidiff(content1, date1,
1887 1887 content2, date2,
1888 1888 path1, path2, opts=opts)
1889 1889 if header and (text or len(header) > 1):
1890 1890 yield '\n'.join(header) + '\n'
1891 1891 if text:
1892 1892 yield text
1893 1893
1894 1894 def diffstatsum(stats):
1895 1895 maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
1896 1896 for f, a, r, b in stats:
1897 1897 maxfile = max(maxfile, encoding.colwidth(f))
1898 1898 maxtotal = max(maxtotal, a + r)
1899 1899 addtotal += a
1900 1900 removetotal += r
1901 1901 binary = binary or b
1902 1902
1903 1903 return maxfile, maxtotal, addtotal, removetotal, binary
1904 1904
1905 1905 def diffstatdata(lines):
1906 1906 diffre = re.compile('^diff .*-r [a-z0-9]+\s(.*)$')
1907 1907
1908 1908 results = []
1909 1909 filename, adds, removes, isbinary = None, 0, 0, False
1910 1910
1911 1911 def addresult():
1912 1912 if filename:
1913 1913 results.append((filename, adds, removes, isbinary))
1914 1914
1915 1915 for line in lines:
1916 1916 if line.startswith('diff'):
1917 1917 addresult()
1918 1918 # set numbers to 0 anyway when starting new file
1919 1919 adds, removes, isbinary = 0, 0, False
1920 1920 if line.startswith('diff --git a/'):
1921 1921 filename = gitre.search(line).group(2)
1922 1922 elif line.startswith('diff -r'):
1923 1923 # format: "diff -r ... -r ... filename"
1924 1924 filename = diffre.search(line).group(1)
1925 1925 elif line.startswith('+') and not line.startswith('+++ '):
1926 1926 adds += 1
1927 1927 elif line.startswith('-') and not line.startswith('--- '):
1928 1928 removes += 1
1929 1929 elif (line.startswith('GIT binary patch') or
1930 1930 line.startswith('Binary file')):
1931 1931 isbinary = True
1932 1932 addresult()
1933 1933 return results
1934 1934
1935 1935 def diffstat(lines, width=80, git=False):
1936 1936 output = []
1937 1937 stats = diffstatdata(lines)
1938 1938 maxname, maxtotal, totaladds, totalremoves, hasbinary = diffstatsum(stats)
1939 1939
1940 1940 countwidth = len(str(maxtotal))
1941 1941 if hasbinary and countwidth < 3:
1942 1942 countwidth = 3
1943 1943 graphwidth = width - countwidth - maxname - 6
1944 1944 if graphwidth < 10:
1945 1945 graphwidth = 10
1946 1946
1947 1947 def scale(i):
1948 1948 if maxtotal <= graphwidth:
1949 1949 return i
1950 1950 # If diffstat runs out of room it doesn't print anything,
1951 1951 # which isn't very useful, so always print at least one + or -
1952 1952 # if there were at least some changes.
1953 1953 return max(i * graphwidth // maxtotal, int(bool(i)))
1954 1954
1955 1955 for filename, adds, removes, isbinary in stats:
1956 1956 if isbinary:
1957 1957 count = 'Bin'
1958 1958 else:
1959 1959 count = adds + removes
1960 1960 pluses = '+' * scale(adds)
1961 1961 minuses = '-' * scale(removes)
1962 1962 output.append(' %s%s | %*s %s%s\n' %
1963 1963 (filename, ' ' * (maxname - encoding.colwidth(filename)),
1964 1964 countwidth, count, pluses, minuses))
1965 1965
1966 1966 if stats:
1967 1967 output.append(_(' %d files changed, %d insertions(+), '
1968 1968 '%d deletions(-)\n')
1969 1969 % (len(stats), totaladds, totalremoves))
1970 1970
1971 1971 return ''.join(output)
1972 1972
1973 1973 def diffstatui(*args, **kw):
1974 1974 '''like diffstat(), but yields 2-tuples of (output, label) for
1975 1975 ui.write()
1976 1976 '''
1977 1977
1978 1978 for line in diffstat(*args, **kw).splitlines():
1979 1979 if line and line[-1] in '+-':
1980 1980 name, graph = line.rsplit(' ', 1)
1981 1981 yield (name + ' ', '')
1982 1982 m = re.search(r'\++', graph)
1983 1983 if m:
1984 1984 yield (m.group(0), 'diffstat.inserted')
1985 1985 m = re.search(r'-+', graph)
1986 1986 if m:
1987 1987 yield (m.group(0), 'diffstat.deleted')
1988 1988 else:
1989 1989 yield (line, '')
1990 1990 yield ('\n', '')
@@ -1,723 +1,775 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3
4 4 New file:
5 5
6 6 $ hg import -d "1000000 0" -mnew - <<EOF
7 7 > diff --git a/new b/new
8 8 > new file mode 100644
9 9 > index 0000000..7898192
10 10 > --- /dev/null
11 11 > +++ b/new
12 12 > @@ -0,0 +1 @@
13 13 > +a
14 14 > EOF
15 15 applying patch from stdin
16 16
17 17 $ hg tip -q
18 18 0:ae3ee40d2079
19 19
20 20 New empty file:
21 21
22 22 $ hg import -d "1000000 0" -mempty - <<EOF
23 23 > diff --git a/empty b/empty
24 24 > new file mode 100644
25 25 > EOF
26 26 applying patch from stdin
27 27
28 28 $ hg tip -q
29 29 1:ab199dc869b5
30 30
31 31 $ hg locate empty
32 32 empty
33 33
34 34 chmod +x:
35 35
36 36 $ hg import -d "1000000 0" -msetx - <<EOF
37 37 > diff --git a/new b/new
38 38 > old mode 100644
39 39 > new mode 100755
40 40 > EOF
41 41 applying patch from stdin
42 42
43 43 #if execbit
44 44 $ hg tip -q
45 45 2:3a34410f282e
46 46 $ test -x new
47 47 $ hg rollback -q
48 48 #else
49 49 $ hg tip -q
50 50 1:ab199dc869b5
51 51 #endif
52 52
53 53 Copy and removing x bit:
54 54
55 55 $ hg import -f -d "1000000 0" -mcopy - <<EOF
56 56 > diff --git a/new b/copy
57 57 > old mode 100755
58 58 > new mode 100644
59 59 > similarity index 100%
60 60 > copy from new
61 61 > copy to copy
62 62 > diff --git a/new b/copyx
63 63 > similarity index 100%
64 64 > copy from new
65 65 > copy to copyx
66 66 > EOF
67 67 applying patch from stdin
68 68
69 69 $ test -f copy
70 70 #if execbit
71 71 $ test ! -x copy
72 72 $ test -x copyx
73 73 $ hg tip -q
74 74 2:21dfaae65c71
75 75 #else
76 76 $ hg tip -q
77 77 2:0efdaa8e3bf3
78 78 #endif
79 79
80 80 $ hg up -qCr1
81 81 $ hg rollback -q
82 82
83 83 Copy (like above but independent of execbit):
84 84
85 85 $ hg import -d "1000000 0" -mcopy - <<EOF
86 86 > diff --git a/new b/copy
87 87 > similarity index 100%
88 88 > copy from new
89 89 > copy to copy
90 90 > diff --git a/new b/copyx
91 91 > similarity index 100%
92 92 > copy from new
93 93 > copy to copyx
94 94 > EOF
95 95 applying patch from stdin
96 96
97 97 $ hg tip -q
98 98 2:0efdaa8e3bf3
99 99 $ test -f copy
100 100
101 101 $ cat copy
102 102 a
103 103
104 104 $ hg cat copy
105 105 a
106 106
107 107 Rename:
108 108
109 109 $ hg import -d "1000000 0" -mrename - <<EOF
110 110 > diff --git a/copy b/rename
111 111 > similarity index 100%
112 112 > rename from copy
113 113 > rename to rename
114 114 > EOF
115 115 applying patch from stdin
116 116
117 117 $ hg tip -q
118 118 3:b1f57753fad2
119 119
120 120 $ hg locate
121 121 copyx
122 122 empty
123 123 new
124 124 rename
125 125
126 126 Delete:
127 127
128 128 $ hg import -d "1000000 0" -mdelete - <<EOF
129 129 > diff --git a/copyx b/copyx
130 130 > deleted file mode 100755
131 131 > index 7898192..0000000
132 132 > --- a/copyx
133 133 > +++ /dev/null
134 134 > @@ -1 +0,0 @@
135 135 > -a
136 136 > EOF
137 137 applying patch from stdin
138 138
139 139 $ hg tip -q
140 140 4:1bd1da94b9b2
141 141
142 142 $ hg locate
143 143 empty
144 144 new
145 145 rename
146 146
147 147 $ test -f copyx
148 148 [1]
149 149
150 150 Regular diff:
151 151
152 152 $ hg import -d "1000000 0" -mregular - <<EOF
153 153 > diff --git a/rename b/rename
154 154 > index 7898192..72e1fe3 100644
155 155 > --- a/rename
156 156 > +++ b/rename
157 157 > @@ -1 +1,5 @@
158 158 > a
159 159 > +a
160 160 > +a
161 161 > +a
162 162 > +a
163 163 > EOF
164 164 applying patch from stdin
165 165
166 166 $ hg tip -q
167 167 5:46fe99cb3035
168 168
169 169 Copy and modify:
170 170
171 171 $ hg import -d "1000000 0" -mcopymod - <<EOF
172 172 > diff --git a/rename b/copy2
173 173 > similarity index 80%
174 174 > copy from rename
175 175 > copy to copy2
176 176 > index 72e1fe3..b53c148 100644
177 177 > --- a/rename
178 178 > +++ b/copy2
179 179 > @@ -1,5 +1,5 @@
180 180 > a
181 181 > a
182 182 > -a
183 183 > +b
184 184 > a
185 185 > a
186 186 > EOF
187 187 applying patch from stdin
188 188
189 189 $ hg tip -q
190 190 6:ffeb3197c12d
191 191
192 192 $ hg cat copy2
193 193 a
194 194 a
195 195 b
196 196 a
197 197 a
198 198
199 199 Rename and modify:
200 200
201 201 $ hg import -d "1000000 0" -mrenamemod - <<EOF
202 202 > diff --git a/copy2 b/rename2
203 203 > similarity index 80%
204 204 > rename from copy2
205 205 > rename to rename2
206 206 > index b53c148..8f81e29 100644
207 207 > --- a/copy2
208 208 > +++ b/rename2
209 209 > @@ -1,5 +1,5 @@
210 210 > a
211 211 > a
212 212 > b
213 213 > -a
214 214 > +c
215 215 > a
216 216 > EOF
217 217 applying patch from stdin
218 218
219 219 $ hg tip -q
220 220 7:401aede9e6bb
221 221
222 222 $ hg locate copy2
223 223 [1]
224 224 $ hg cat rename2
225 225 a
226 226 a
227 227 b
228 228 c
229 229 a
230 230
231 231 One file renamed multiple times:
232 232
233 233 $ hg import -d "1000000 0" -mmultirenames - <<EOF
234 234 > diff --git a/rename2 b/rename3
235 235 > rename from rename2
236 236 > rename to rename3
237 237 > diff --git a/rename2 b/rename3-2
238 238 > rename from rename2
239 239 > rename to rename3-2
240 240 > EOF
241 241 applying patch from stdin
242 242
243 243 $ hg tip -q
244 244 8:2ef727e684e8
245 245
246 246 $ hg log -vr. --template '{rev} {files} / {file_copies}\n'
247 247 8 rename2 rename3 rename3-2 / rename3 (rename2)rename3-2 (rename2)
248 248
249 249 $ hg locate rename2 rename3 rename3-2
250 250 rename3
251 251 rename3-2
252 252
253 253 $ hg cat rename3
254 254 a
255 255 a
256 256 b
257 257 c
258 258 a
259 259
260 260 $ hg cat rename3-2
261 261 a
262 262 a
263 263 b
264 264 c
265 265 a
266 266
267 267 $ echo foo > foo
268 268 $ hg add foo
269 269 $ hg ci -m 'add foo'
270 270
271 271 Binary files and regular patch hunks:
272 272
273 273 $ hg import -d "1000000 0" -m binaryregular - <<EOF
274 274 > diff --git a/binary b/binary
275 275 > new file mode 100644
276 276 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
277 277 > GIT binary patch
278 278 > literal 4
279 279 > Lc\${NkU|;|M00aO5
280 280 >
281 281 > diff --git a/foo b/foo2
282 282 > rename from foo
283 283 > rename to foo2
284 284 > EOF
285 285 applying patch from stdin
286 286
287 287 $ hg tip -q
288 288 10:27377172366e
289 289
290 290 $ cat foo2
291 291 foo
292 292
293 293 $ hg manifest --debug | grep binary
294 294 045c85ba38952325e126c70962cc0f9d9077bc67 644 binary
295 295
296 296 Multiple binary files:
297 297
298 298 $ hg import -d "1000000 0" -m multibinary - <<EOF
299 299 > diff --git a/mbinary1 b/mbinary1
300 300 > new file mode 100644
301 301 > index 0000000000000000000000000000000000000000..593f4708db84ac8fd0f5cc47c634f38c013fe9e4
302 302 > GIT binary patch
303 303 > literal 4
304 304 > Lc\${NkU|;|M00aO5
305 305 >
306 306 > diff --git a/mbinary2 b/mbinary2
307 307 > new file mode 100644
308 308 > index 0000000000000000000000000000000000000000..112363ac1917b417ffbd7f376ca786a1e5fa7490
309 309 > GIT binary patch
310 310 > literal 5
311 311 > Mc\${NkU|\`?^000jF3jhEB
312 312 >
313 313 > EOF
314 314 applying patch from stdin
315 315
316 316 $ hg tip -q
317 317 11:18b73a84b4ab
318 318
319 319 $ hg manifest --debug | grep mbinary
320 320 045c85ba38952325e126c70962cc0f9d9077bc67 644 mbinary1
321 321 a874b471193996e7cb034bb301cac7bdaf3e3f46 644 mbinary2
322 322
323 323 Binary file and delta hunk (we build the patch using this sed hack to
324 324 avoid an unquoted ^, which check-code says breaks sh on Solaris):
325 325
326 326 $ sed 's/ caret /^/g;s/ dollarparen /$(/g' > quote-hack.patch <<'EOF'
327 327 > diff --git a/delta b/delta
328 328 > new file mode 100644
329 329 > index 0000000000000000000000000000000000000000..8c9b7831b231c2600843e303e66b521353a200b3
330 330 > GIT binary patch
331 331 > literal 3749
332 332 > zcmV;W4qEYvP)<h;3K|Lk000e1NJLTq006iE002D*0ssI2kt{U(0000PbVXQnQ*UN;
333 333 > zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU=M@d9MRCwC#oC!>o#}>x{(W-y~UN*tK
334 334 > z%A%sxiUy2Ys)0Vm#ueArYKoYqX;GuiqZpgirM6nCVoYk?YNAz3G~z;BZ~@~&OQEe4
335 335 > zmGvS5isFJI;Pd_7J+EKxyHZeu` caret t4r2>F;h-+VK3{_{WoGv8dSpFDYDrA%3UX03pt
336 336 > zOaVoi0*W#P6lDr1$`nwPDWE7*rhuYM0Y#YtiZTThWeO<D6i}2YpqR<%$s>bRRaI42
337 337 > zS3iFIxJ8Q=EnBv1Z7?pBw_bLjJb3V+tgP(Tty_2R-mR#p04x78n2n7MSOFyt4i1iv
338 338 > zjxH`PPEJmgD7U?IK&h;(EGQ@_DJc<@01=4fiNXHcKZ8LhZQ8T}E3U4tUS3}OrcgQW
339 339 > zWdX{K8#l7Ev&#$ysR)G#0*rC+<WGZ3?CtG4bm-ve>Dj$|_qJ`@D*stNP_AFUe&x!Q
340 340 > zJ9q9B7Z=ym)MyZ?Tg1ROunUYr81nV?B@!tYS~5_|%gfW#(_s<4UN1!Q?Dv8d>g#m6
341 341 > z%*@R2@bI2JdnzxQ!EDU`$eQY!tgI~Zn$prz;gaXNod5*5p(1Bz=P$qfvZ$y?dC@X~
342 342 > zlAD+NAKhB{=;6bMwzjqn>9mavvKOGd`s%A+fBiL>Q;xJWpa72C+}u{JTHUX>{~}Qj
343 343 > zUb%hyHgN~c?cBLjInvUALMD9g-aXt54ZL8AOCvXL-V6!~ijR*kEG$&Mv?!pE61OlI
344 344 > z8nzMSPE8F7bH|Py*RNl1VUCggq<V)>@_6gkEeiz7{rmTeuNTW6+KVS#0FG%IHf-3L
345 345 > zGiS21vn>WCCr+GLx caret !uNetzB6u3o(w6&1C2?_LW8ij$+$sZ*zZ`|US3H@8N~%&V%Z
346 346 > zAeA0HdhFS=$6|nzn3%YH`SN<>DQRO;Qc caret )dfdvA caret 5u`Xf;Zzu<ZQHgG?28V-#s<;T
347 347 > zzkh#LA)v7gpoE5ou3o*GoUUF%b#iht&kl9d0)><$FE1}ACr68;uCA`6DrGmz_U+rp
348 348 > zL>Rx;X_yhk$fP_yJrTCQ|NgsW0A<985g&c@k-NKly<>mgU8n||ZPPV<`SN8#%$+-T
349 349 > zfP$T!ou8jypFVwnzqhxyUvIxXd-wF~*U!ht=hCH1wzjqn9x#)IrhDa;S0JbK caret z_$W
350 350 > zd(8rX@;7|t*;GJ5h$SZ{v(}+UBEs$4w~?{@9%`_Z<P<kox5bMWuUWH(sF9hONgd$Q
351 351 > zunCgwT@1|CU9+;X caret 4z&|M~@yw23Ay50NFWn=FqF%yLZEUty;AT2??1oV@B)Nt))J7
352 352 > zh>{5j2@f7T=-an%L_`E)h;mZ4D_5>?7tjQtVPRo2XU-&;mX(!l-MSTJP4XWY82JAC
353 353 > z@57+y&!1=P{Mn{W8)-HzEsgAtd63}Cazc>O6vGb>51%@9DzbyI3?4j~$ijmT95_IS
354 354 > zS#r!LCDW%*4-O7CGnkr$xXR1RQ&UrA<CQt} caret 73NL%zk`)Jk!yxUAt-1r}ggLn-Zq}
355 355 > z*s){8pw68;i+kiG%CpBKYSJLLFyq&*U8}qDp+kpe&6<Vp(Z58%l#~>ZK?&s7y?b}i
356 356 > zuwcOgO%x-27A;y785zknl_{sU;E6v$8{pWmVS{KaJPpu`i;HP$#flY@u~Ua~K3%tN
357 357 > z-LhrNh{9SoHgDd%WXTc$$~Dq{?AWou3!H&?V8K{ caret {P9Ot5vecD?%1&-E-ntBFj87(
358 358 > zy5`QE%QRX7qcHC%1{Ua}M~}L6=`wQUNEQ=I;qc+ZMMXtK2T+0os;jEco;}OV9z1w3
359 359 > zARqv caret bm-85xnRCng3OT|MyVSmR3ND7 caret ?KaQGG! caret (aTbo1N;Nz;X3Q9FJbwK6`0?Yp
360 360 > zj*X2ac;Pw3!I2|JShDaF>-gJmzm1NLj){rk&o|$E caret WAsfrK=x&@B!`w7Hik81sPz4
361 361 > zuJTaiCppM>-+c!wPzcUw)5@?J4U-u|pJ~xbWUe-C+60k caret 7>9!)56DbjmA~`OJJ40v
362 362 > zu3hCA7eJXZWeN|1iJLu87$;+fS8+Kq6O`aT)*_x@sY#t7LxwoEcVw*)cWhhQW@l%!
363 363 > z{#Z=y+qcK@%z{p*D=8_Fcg278AnH3fI5;~yGu?9TscxXaaP*4$f<LIv! caret 5Lfr%vKg
364 364 > zpxmunH#%=+ICMvZA~wyNH%~eMl!-g caret R!cYJ#WmLq5N8viz#J%%LPtkO?V)tZ81cp>
365 365 > z{ALK?fNPePmd;289&M8Q3>YwgZX5GcGY&n>K1<x)!`;Qjg&}bb!Lrnl@xH#kS~VYE
366 366 > zpJmIJO`A3iy+Y3X`k>cY-@}Iw2Onq`=!ba3eATgs3yg3Wej=+P-Z8WF#w=RXvS@J3
367 367 > zEyhVTj-gO?kfDu1g9afo<RkPrYzG#_yF41IFxF%Ylg>9lx6<clPweR-b7Hn+r)e1l
368 368 > zO6c6FbNt@;;*w$z;N|H>h{czme)_4V6UC4hv**kX2@L caret Bgds dollarparen &P7M4dhfmWe)!=B
369 369 > zR3X=Y{P9N}p@-##@1ZNW1YbVaiP~D@8m&<dzEP&cO|87Ju#j*=;wH~Exr>i*Hpp&@
370 370 > z`9!Sj+O;byD~s8qZ>6QB8uv7Bpn&&?xe;;e<M4F8KEID&pT7QmqoSgq&06adp5T=U
371 371 > z6DH*4=AB7C1D9Amu?ia-wtxSAlmTEO96XHx)-+rKP;ip$pukuSJGW3P1aUmc2yo%)
372 372 > z&<t3F>d1X+1qzaag-%x+eKHx{?Afz3GBQSw9u0lw<mB+I#v11TKRpKWQS+lvVL7=u
373 373 > zHr6)1ynEF<i3kO6A8&ppPMo-F=PnWfXkSj@i*7J6C<F}wR?s(O0niC?t+6;+k}pPq
374 374 > zrok&TPU40rL0ZYDwenNrrmPZ`gjo@DEF`7 caret cKP||pUr;+r)hyn9O37=xA`3%Bj-ih
375 375 > z+1usk<%5G-y+R?tA`qY=)6&vNjL{P?QzHg%P%>`ZxP=QB%DHY6L26?36V_p caret {}n$q
376 376 > z3@9W=KmGI*Ng_Q#AzA%-z|Z caret |#oW(hkfgpuS$RKRhlrarX%efMMCs}GLChec5+y{6
377 377 > z1Qnxim_C-fmQuaAK_NUHUBV&;1c0V)wji<RcdZ*aAWTwyt>hVnlt caret asFCe0&a@tqp
378 378 > zEEy;$L}D$X6)wfQNl8gu6Z>oB3_RrP=gTyK2@@w#LbQfLNHj>Q&z(C5wUFhK+}0aV
379 379 > zSohlc=7K+spN<ctf}5KgKqNyJDNP9;LZd)nTE=9|6Xdr9%Hzk63-tL2c9FD*rsyYY
380 380 > z!}t+Yljq7-p$X;4_YL?6d;mdY3R##o1e%rlPxrsMh8|;sKTr~ caret QD#sw3&vS$FwlTk
381 381 > zp1#Gw!Qo-$LtvpXt#ApV0g) caret F=qFB`VB!W297x=$mr<$>rco3v$QKih_xN!k6;M=@
382 382 > zCr?gDNQj7tm@;JwD;Ty&NlBSCYZk(b3dZeN8D4h2{r20dSFc7;(>E&r`s=TVtzpB4
383 383 > zk+ caret N&zCAiRns(?p6iBlk9v&h{1ve(FNtc)td51M>)TkXhc6{>5C)`fS$&)A1*CP1%
384 384 > zld+peue4aYbg3C0!+4mu+}vE caret j_feX+ZijvffBI7Ofh#RZ*U3<3J5(+nfRCzexqQ5
385 385 > zgM&##Y4Dd{e%ZKjqrbm@|Ni}l4jo!AqtFynj3Xsd$o caret ?yV4$|UQ(j&UWCH>M=o_&N
386 386 > zmclXc3i|Q#<;#EoG>~V}4unTHbUK}u=y4;rA3S&vzC3 caret aJP!&D4RvvGfoyo(>C>la
387 387 > zijP<=v>X{3Ne&2BXo}DV8l0V-jdv`$am0ubG{Wuh%CTd|l9Q7m;G&|U@#Dvbhlj(d
388 388 > zg6W{3ATxYt#T?)3;SmIgOP4M|Dki~I_TX7SxP0x}wI~DQI7Lhm2BI7gph(aPIFAd;
389 389 > zQ&UsF`Q{rOz+z=87c5v%@5u~d6dWV5OlX`oH3cAH&UlvsZUEo(Q(P|lKs17rXvaiU
390 390 > zQcj}IEufi1+Bnh6&(EhF{7O3vLHp`jjlp0J<M1kh$+$2xGm~Zk7OY7(q=&Rdhq*RG
391 391 > zwrmcd5MnP}xByB_)P@{J>DR9x6;`cUwPM8z){yooNiXPOc9_{W-gtwxE5TUg0vJk6
392 392 > zO#JGruV&1cL6VGK2?+_YQr4`+EY8;Sm$9U$uuGRN=uj3k7?O9b+R~J7t_y*K64ZnI
393 393 > zM+{aE<b(v?vSmw;9zFP!aE266zHIhlmdI@ caret xa6o2jwdRk54a$>pcRbC29ZyG!Cfdp
394 394 > zutFf`Q`vljgo!(wHf=)F#m2_MIuj;L(2ja2YsQRX+rswV{d<H`Ar;(@%aNa9VPU8Z
395 395 > z;tq*`y}dm#NDJHKlV}uTIm!_vAq5E7!X-p{P=Z=Sh668>PuVS1*6e}OwOiMc;u3OQ
396 396 > z@Bs)w3=lzfKoufH$SFuPG@uZ4NOnM#+=8LnQ2Q4zUd+nM+OT26;lqbN{P07dhH{jH
397 397 > zManE8 caret dLms-Q2;1kB<*Q1a3f8kZr;xX=!Qro@`~@xN*Qj>gx;i;0Z24!~i2uLb`}v
398 398 > zA?R$|wvC+m caret Ups=*(4lDh*=UN8{5h(A?p#D caret 2N$8u4Z55!q?ZAh(iEEng9_Zi>IgO
399 399 > z#~**JC8hE4@n{hO&8btT5F*?nC_%LhA3i)PDhh-pB_&1wGrDIl caret *=8x3n&;akBf caret -
400 400 > zJd&86kq$%%907v caret tgWoQdwI`|oNK%VvU~S#C<o caret F?6c48?Cjj#-4P<>HFD%&|Ni~t
401 401 > zKJ(|#H`$<5W+6ZkBb213rXonKZLB+X> caret L}J@W6osP3piLD_5?R!`S}*{xLBzFiL4@
402 402 > zX+}l{`A%?f@T5tT%ztu60p;)be`fWC`tP@WpO=?cpf8Xuf1OSj6d3f@Ki(ovDYq%0
403 403 > z{4ZSe`kOay5@=lAT!}vFzxyemC{sXDrhuYM0Y#ZI1r%ipD9W11{w=@&xgJ}t2x;ep
404 404 > P00000NkvXXu0mjfZ5|Er
405 405 >
406 406 > literal 0
407 407 > HcmV?d00001
408 408 >
409 409 > EOF
410 410 $ hg import -d "1000000 0" -m delta quote-hack.patch
411 411 applying quote-hack.patch
412 412 $ rm quote-hack.patch
413 413
414 414 $ hg manifest --debug | grep delta
415 415 9600f98bb60ce732634d126aaa4ac1ec959c573e 644 delta
416 416
417 417 $ hg import -d "1000000 0" -m delta - <<'EOF'
418 418 > diff --git a/delta b/delta
419 419 > index 8c9b7831b231c2600843e303e66b521353a200b3..0021dd95bc0dba53c39ce81377126d43731d68df 100644
420 420 > GIT binary patch
421 421 > delta 49
422 422 > zcmZ1~yHs|=21Z8J$r~9bFdA-lVv=EEw4WT$qRf2QSa5SIOAHI6(&k4T8H|kLo4vWB
423 423 > FSO9ZT4bA`n
424 424 >
425 425 > delta 49
426 426 > zcmV-10M7rV9i<(xumJ(}ld%Di0Xefm0vrMXpOaq%BLm9I%d>?9Tm%6Vv*HM70RcC&
427 427 > HOA1;9yU-AD
428 428 >
429 429 > EOF
430 430 applying patch from stdin
431 431
432 432 $ hg manifest --debug | grep delta
433 433 56094bbea136dcf8dbd4088f6af469bde1a98b75 644 delta
434 434
435 435 Filenames with spaces:
436 436
437 437 $ sed 's,EOL$,,g' <<EOF | hg import -d "1000000 0" -m spaces -
438 438 > diff --git a/foo bar b/foo bar
439 439 > new file mode 100644
440 440 > index 0000000..257cc56
441 441 > --- /dev/null
442 442 > +++ b/foo bar EOL
443 443 > @@ -0,0 +1 @@
444 444 > +foo
445 445 > EOF
446 446 applying patch from stdin
447 447
448 448 $ hg tip -q
449 449 14:4b79479c9a6d
450 450
451 451 $ cat "foo bar"
452 452 foo
453 453
454 454 Copy then modify the original file:
455 455
456 456 $ hg import -d "1000000 0" -m copy-mod-orig - <<EOF
457 457 > diff --git a/foo2 b/foo2
458 458 > index 257cc56..fe08ec6 100644
459 459 > --- a/foo2
460 460 > +++ b/foo2
461 461 > @@ -1 +1,2 @@
462 462 > foo
463 463 > +new line
464 464 > diff --git a/foo2 b/foo3
465 465 > similarity index 100%
466 466 > copy from foo2
467 467 > copy to foo3
468 468 > EOF
469 469 applying patch from stdin
470 470
471 471 $ hg tip -q
472 472 15:9cbe44af4ae9
473 473
474 474 $ cat foo3
475 475 foo
476 476
477 477 Move text file and patch as binary
478 478
479 479 $ echo a > text2
480 480 $ hg ci -Am0
481 481 adding text2
482 482 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
483 483 > diff --git a/text2 b/binary2
484 484 > rename from text2
485 485 > rename to binary2
486 486 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
487 487 > GIT binary patch
488 488 > literal 5
489 489 > Mc$`b*O5$Pw00T?_*Z=?k
490 490 >
491 491 > EOF
492 492 applying patch from stdin
493 493
494 494 $ cat binary2
495 495 a
496 496 b
497 497 \x00 (no-eol) (esc)
498 498
499 499 $ hg st --copies --change .
500 500 A binary2
501 501 text2
502 502 R text2
503 503
504 504 Invalid base85 content
505 505
506 506 $ hg rollback
507 507 repository tip rolled back to revision 16 (undo import)
508 508 working directory now based on revision 16
509 509 $ hg revert -aq
510 510 $ hg import -d "1000000 0" -m invalid-binary - <<"EOF"
511 511 > diff --git a/text2 b/binary2
512 512 > rename from text2
513 513 > rename to binary2
514 514 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
515 515 > GIT binary patch
516 516 > literal 5
517 517 > Mc$`b*O.$Pw00T?_*Z=?k
518 518 >
519 519 > EOF
520 520 applying patch from stdin
521 521 abort: could not decode "binary2" binary patch: bad base85 character at position 6
522 522 [255]
523 523
524 524 $ hg revert -aq
525 525 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
526 526 > diff --git a/text2 b/binary2
527 527 > rename from text2
528 528 > rename to binary2
529 529 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
530 530 > GIT binary patch
531 531 > literal 6
532 532 > Mc$`b*O5$Pw00T?_*Z=?k
533 533 >
534 534 > EOF
535 535 applying patch from stdin
536 536 abort: "binary2" length is 5 bytes, should be 6
537 537 [255]
538 538
539 539 $ hg revert -aq
540 540 $ hg import -d "1000000 0" -m rename-as-binary - <<"EOF"
541 541 > diff --git a/text2 b/binary2
542 542 > rename from text2
543 543 > rename to binary2
544 544 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
545 545 > GIT binary patch
546 546 > Mc$`b*O5$Pw00T?_*Z=?k
547 547 >
548 548 > EOF
549 549 applying patch from stdin
550 550 abort: could not extract "binary2" binary data
551 551 [255]
552 552
553 553 Simulate a copy/paste turning LF into CRLF (issue2870)
554 554
555 555 $ hg revert -aq
556 556 $ cat > binary.diff <<"EOF"
557 557 > diff --git a/text2 b/binary2
558 558 > rename from text2
559 559 > rename to binary2
560 560 > index 78981922613b2afb6025042ff6bd878ac1994e85..10efcb362e9f3b3420fcfbfc0e37f3dc16e29757
561 561 > GIT binary patch
562 562 > literal 5
563 563 > Mc$`b*O5$Pw00T?_*Z=?k
564 564 >
565 565 > EOF
566 566 >>> fp = file('binary.diff', 'rb')
567 567 >>> data = fp.read()
568 568 >>> fp.close()
569 569 >>> file('binary.diff', 'wb').write(data.replace('\n', '\r\n'))
570 570 $ rm binary2
571 571 $ hg import --no-commit binary.diff
572 572 applying binary.diff
573 573
574 574 $ cd ..
575 575
576 576 Consecutive import with renames (issue2459)
577 577
578 578 $ hg init issue2459
579 579 $ cd issue2459
580 580 $ hg import --no-commit --force - <<EOF
581 581 > diff --git a/a b/a
582 582 > new file mode 100644
583 583 > EOF
584 584 applying patch from stdin
585 585 $ hg import --no-commit --force - <<EOF
586 586 > diff --git a/a b/b
587 587 > rename from a
588 588 > rename to b
589 589 > EOF
590 590 applying patch from stdin
591 591 a has not been committed yet, so no copy data will be stored for b.
592 592 $ hg debugstate
593 593 a 0 -1 unset b
594 594 $ hg ci -m done
595 595 $ cd ..
596 596
597 597 Renames and strip
598 598
599 599 $ hg init renameandstrip
600 600 $ cd renameandstrip
601 601 $ echo a > a
602 602 $ hg ci -Am adda
603 603 adding a
604 604 $ hg import --no-commit -p2 - <<EOF
605 605 > diff --git a/foo/a b/foo/b
606 606 > rename from foo/a
607 607 > rename to foo/b
608 608 > EOF
609 609 applying patch from stdin
610 610 $ hg st --copies
611 611 A b
612 612 a
613 613 R a
614 614
615 Renames, similarity and git diff
615 Prefix with strip, renames, creates etc
616 616
617 617 $ hg revert -aC
618 618 undeleting a
619 619 forgetting b
620 620 $ rm b
621 $ mkdir -p dir/dir2
622 $ echo b > dir/dir2/b
623 $ echo c > dir/dir2/c
624 $ echo d > dir/d
625 $ hg ci -Am addbcd
626 adding dir/d
627 adding dir/dir2/b
628 adding dir/dir2/c
629 (test that prefixes are relative to the root)
630 $ mkdir tmpdir
631 $ cd tmpdir
632 $ hg import --no-commit -p2 --prefix dir/ - <<EOF
633 > diff --git a/foo/a b/foo/a
634 > new file mode 100644
635 > --- /dev/null
636 > +++ b/foo/a
637 > @@ -0,0 +1 @@
638 > +a
639 > diff --git a/foo/dir2/b b/foo/dir2/b2
640 > rename from foo/dir2/b
641 > rename to foo/dir2/b2
642 > diff --git a/foo/dir2/c b/foo/dir2/c
643 > --- a/foo/dir2/c
644 > +++ b/foo/dir2/c
645 > @@ -0,0 +1 @@
646 > +cc
647 > diff --git a/foo/d b/foo/d
648 > deleted file mode 100644
649 > --- a/foo/d
650 > +++ /dev/null
651 > @@ -1,1 +0,0 @@
652 > -d
653 > EOF
654 applying patch from stdin
655 $ hg st --copies
656 M dir/dir2/c
657 A dir/a
658 A dir/dir2/b2
659 dir/dir2/b
660 R dir/d
661 R dir/dir2/b
662 $ cd ..
663
664 Renames, similarity and git diff
665
666 $ hg revert -aC
667 forgetting dir/a (glob)
668 undeleting dir/d (glob)
669 undeleting dir/dir2/b (glob)
670 forgetting dir/dir2/b2 (glob)
671 reverting dir/dir2/c (glob)
672 $ rm dir/a dir/dir2/b2
621 673 $ hg import --similarity 90 --no-commit - <<EOF
622 674 > diff --git a/a b/b
623 675 > rename from a
624 676 > rename to b
625 677 > EOF
626 678 applying patch from stdin
627 679 $ hg st --copies
628 680 A b
629 681 a
630 682 R a
631 683 $ cd ..
632 684
633 685 Pure copy with existing destination
634 686
635 687 $ hg init copytoexisting
636 688 $ cd copytoexisting
637 689 $ echo a > a
638 690 $ echo b > b
639 691 $ hg ci -Am add
640 692 adding a
641 693 adding b
642 694 $ hg import --no-commit - <<EOF
643 695 > diff --git a/a b/b
644 696 > copy from a
645 697 > copy to b
646 698 > EOF
647 699 applying patch from stdin
648 700 abort: cannot create b: destination already exists
649 701 [255]
650 702 $ cat b
651 703 b
652 704
653 705 Copy and changes with existing destination
654 706
655 707 $ hg import --no-commit - <<EOF
656 708 > diff --git a/a b/b
657 709 > copy from a
658 710 > copy to b
659 711 > --- a/a
660 712 > +++ b/b
661 713 > @@ -1,1 +1,2 @@
662 714 > a
663 715 > +b
664 716 > EOF
665 717 applying patch from stdin
666 718 cannot create b: destination already exists
667 719 1 out of 1 hunks FAILED -- saving rejects to file b.rej
668 720 abort: patch failed to apply
669 721 [255]
670 722 $ cat b
671 723 b
672 724
673 725 #if symlink
674 726
675 727 $ ln -s b linkb
676 728 $ hg add linkb
677 729 $ hg ci -m addlinkb
678 730 $ hg import --no-commit - <<EOF
679 731 > diff --git a/linkb b/linkb
680 732 > deleted file mode 120000
681 733 > --- a/linkb
682 734 > +++ /dev/null
683 735 > @@ -1,1 +0,0 @@
684 736 > -badhunk
685 737 > \ No newline at end of file
686 738 > EOF
687 739 applying patch from stdin
688 740 patching file linkb
689 741 Hunk #1 FAILED at 0
690 742 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
691 743 abort: patch failed to apply
692 744 [255]
693 745 $ hg st
694 746 ? b.rej
695 747 ? linkb.rej
696 748
697 749 #endif
698 750
699 751 Test corner case involving copies and multiple hunks (issue3384)
700 752
701 753 $ hg revert -qa
702 754 $ hg import --no-commit - <<EOF
703 755 > diff --git a/a b/c
704 756 > copy from a
705 757 > copy to c
706 758 > --- a/a
707 759 > +++ b/c
708 760 > @@ -1,1 +1,2 @@
709 761 > a
710 762 > +a
711 763 > @@ -2,1 +2,2 @@
712 764 > a
713 765 > +a
714 766 > diff --git a/a b/a
715 767 > --- a/a
716 768 > +++ b/a
717 769 > @@ -1,1 +1,2 @@
718 770 > a
719 771 > +b
720 772 > EOF
721 773 applying patch from stdin
722 774
723 775 $ cd ..
@@ -1,1479 +1,1498 b''
1 1 $ hg init a
2 2 $ mkdir a/d1
3 3 $ mkdir a/d1/d2
4 4 $ echo line 1 > a/a
5 5 $ echo line 1 > a/d1/d2/a
6 6 $ hg --cwd a ci -Ama
7 7 adding a
8 8 adding d1/d2/a
9 9
10 10 $ echo line 2 >> a/a
11 11 $ hg --cwd a ci -u someone -d '1 0' -m'second change'
12 12
13 13 import with no args:
14 14
15 15 $ hg --cwd a import
16 16 abort: need at least one patch to import
17 17 [255]
18 18
19 19 generate patches for the test
20 20
21 21 $ hg --cwd a export tip > exported-tip.patch
22 22 $ hg --cwd a diff -r0:1 > diffed-tip.patch
23 23
24 24
25 25 import exported patch
26 26 (this also tests that editor is not invoked, if the patch contains the
27 27 commit message and '--edit' is not specified)
28 28
29 29 $ hg clone -r0 a b
30 30 adding changesets
31 31 adding manifests
32 32 adding file changes
33 33 added 1 changesets with 2 changes to 2 files
34 34 updating to branch default
35 35 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
36 36 $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
37 37 applying ../exported-tip.patch
38 38
39 39 message and committer and date should be same
40 40
41 41 $ hg --cwd b tip
42 42 changeset: 1:1d4bd90af0e4
43 43 tag: tip
44 44 user: someone
45 45 date: Thu Jan 01 00:00:01 1970 +0000
46 46 summary: second change
47 47
48 48 $ rm -r b
49 49
50 50
51 51 import exported patch with external patcher
52 52 (this also tests that editor is invoked, if the '--edit' is specified,
53 53 regardless of the commit message in the patch)
54 54
55 55 $ cat > dummypatch.py <<EOF
56 56 > print 'patching file a'
57 57 > file('a', 'wb').write('line2\n')
58 58 > EOF
59 59 $ hg clone -r0 a b
60 60 adding changesets
61 61 adding manifests
62 62 adding file changes
63 63 added 1 changesets with 2 changes to 2 files
64 64 updating to branch default
65 65 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
66 66 $ HGEDITOR=cat hg --config ui.patch='python ../dummypatch.py' --cwd b import --edit ../exported-tip.patch
67 67 applying ../exported-tip.patch
68 68 second change
69 69
70 70
71 71 HG: Enter commit message. Lines beginning with 'HG:' are removed.
72 72 HG: Leave message empty to abort commit.
73 73 HG: --
74 74 HG: user: someone
75 75 HG: branch 'default'
76 76 HG: changed a
77 77 $ cat b/a
78 78 line2
79 79 $ rm -r b
80 80
81 81
82 82 import of plain diff should fail without message
83 83 (this also tests that editor is invoked, if the patch doesn't contain
84 84 the commit message, regardless of '--edit')
85 85
86 86 $ hg clone -r0 a b
87 87 adding changesets
88 88 adding manifests
89 89 adding file changes
90 90 added 1 changesets with 2 changes to 2 files
91 91 updating to branch default
92 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 $ cat > $TESTTMP/editor.sh <<EOF
94 94 > env | grep HGEDITFORM
95 95 > cat \$1
96 96 > EOF
97 97 $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
98 98 applying ../diffed-tip.patch
99 99 HGEDITFORM=import.normal.normal
100 100
101 101
102 102 HG: Enter commit message. Lines beginning with 'HG:' are removed.
103 103 HG: Leave message empty to abort commit.
104 104 HG: --
105 105 HG: user: test
106 106 HG: branch 'default'
107 107 HG: changed a
108 108 abort: empty commit message
109 109 [255]
110 110
111 111 Test avoiding editor invocation at applying the patch with --exact,
112 112 even if commit message is empty
113 113
114 114 $ echo a >> b/a
115 115 $ hg --cwd b commit -m ' '
116 116 $ hg --cwd b tip -T "{node}\n"
117 117 d8804f3f5396d800812f579c8452796a5993bdb2
118 118 $ hg --cwd b export -o ../empty-log.diff .
119 119 $ hg --cwd b update -q -C ".^1"
120 120 $ hg --cwd b --config extensions.strip= strip -q tip
121 121 $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
122 122 applying ../empty-log.diff
123 123 $ hg --cwd b tip -T "{node}\n"
124 124 d8804f3f5396d800812f579c8452796a5993bdb2
125 125
126 126 $ rm -r b
127 127
128 128
129 129 import of plain diff should be ok with message
130 130
131 131 $ hg clone -r0 a b
132 132 adding changesets
133 133 adding manifests
134 134 adding file changes
135 135 added 1 changesets with 2 changes to 2 files
136 136 updating to branch default
137 137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 138 $ hg --cwd b import -mpatch ../diffed-tip.patch
139 139 applying ../diffed-tip.patch
140 140 $ rm -r b
141 141
142 142
143 143 import of plain diff with specific date and user
144 144 (this also tests that editor is not invoked, if
145 145 '--message'/'--logfile' is specified and '--edit' is not)
146 146
147 147 $ hg clone -r0 a b
148 148 adding changesets
149 149 adding manifests
150 150 adding file changes
151 151 added 1 changesets with 2 changes to 2 files
152 152 updating to branch default
153 153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 154 $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
155 155 applying ../diffed-tip.patch
156 156 $ hg -R b tip -pv
157 157 changeset: 1:ca68f19f3a40
158 158 tag: tip
159 159 user: user@nowhere.net
160 160 date: Thu Jan 01 00:00:01 1970 +0000
161 161 files: a
162 162 description:
163 163 patch
164 164
165 165
166 166 diff -r 80971e65b431 -r ca68f19f3a40 a
167 167 --- a/a Thu Jan 01 00:00:00 1970 +0000
168 168 +++ b/a Thu Jan 01 00:00:01 1970 +0000
169 169 @@ -1,1 +1,2 @@
170 170 line 1
171 171 +line 2
172 172
173 173 $ rm -r b
174 174
175 175
176 176 import of plain diff should be ok with --no-commit
177 177 (this also tests that editor is not invoked, if '--no-commit' is
178 178 specified, regardless of '--edit')
179 179
180 180 $ hg clone -r0 a b
181 181 adding changesets
182 182 adding manifests
183 183 adding file changes
184 184 added 1 changesets with 2 changes to 2 files
185 185 updating to branch default
186 186 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
187 187 $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
188 188 applying ../diffed-tip.patch
189 189 $ hg --cwd b diff --nodates
190 190 diff -r 80971e65b431 a
191 191 --- a/a
192 192 +++ b/a
193 193 @@ -1,1 +1,2 @@
194 194 line 1
195 195 +line 2
196 196 $ rm -r b
197 197
198 198
199 199 import of malformed plain diff should fail
200 200
201 201 $ hg clone -r0 a b
202 202 adding changesets
203 203 adding manifests
204 204 adding file changes
205 205 added 1 changesets with 2 changes to 2 files
206 206 updating to branch default
207 207 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 208 $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
209 209 $ hg --cwd b import -mpatch ../broken.patch
210 210 applying ../broken.patch
211 211 abort: bad hunk #1
212 212 [255]
213 213 $ rm -r b
214 214
215 215
216 216 hg -R repo import
217 217 put the clone in a subdir - having a directory named "a"
218 218 used to hide a bug.
219 219
220 220 $ mkdir dir
221 221 $ hg clone -r0 a dir/b
222 222 adding changesets
223 223 adding manifests
224 224 adding file changes
225 225 added 1 changesets with 2 changes to 2 files
226 226 updating to branch default
227 227 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
228 228 $ cd dir
229 229 $ hg -R b import ../exported-tip.patch
230 230 applying ../exported-tip.patch
231 231 $ cd ..
232 232 $ rm -r dir
233 233
234 234
235 235 import from stdin
236 236
237 237 $ hg clone -r0 a b
238 238 adding changesets
239 239 adding manifests
240 240 adding file changes
241 241 added 1 changesets with 2 changes to 2 files
242 242 updating to branch default
243 243 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 244 $ hg --cwd b import - < exported-tip.patch
245 245 applying patch from stdin
246 246 $ rm -r b
247 247
248 248
249 249 import two patches in one stream
250 250
251 251 $ hg init b
252 252 $ hg --cwd a export 0:tip | hg --cwd b import -
253 253 applying patch from stdin
254 254 $ hg --cwd a id
255 255 1d4bd90af0e4 tip
256 256 $ hg --cwd b id
257 257 1d4bd90af0e4 tip
258 258 $ rm -r b
259 259
260 260
261 261 override commit message
262 262
263 263 $ hg clone -r0 a b
264 264 adding changesets
265 265 adding manifests
266 266 adding file changes
267 267 added 1 changesets with 2 changes to 2 files
268 268 updating to branch default
269 269 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
270 270 $ hg --cwd b import -m 'override' - < exported-tip.patch
271 271 applying patch from stdin
272 272 $ hg --cwd b tip | grep override
273 273 summary: override
274 274 $ rm -r b
275 275
276 276 $ cat > mkmsg.py <<EOF
277 277 > import email.Message, sys
278 278 > msg = email.Message.Message()
279 279 > patch = open(sys.argv[1], 'rb').read()
280 280 > msg.set_payload('email commit message\n' + patch)
281 281 > msg['Subject'] = 'email patch'
282 282 > msg['From'] = 'email patcher'
283 283 > file(sys.argv[2], 'wb').write(msg.as_string())
284 284 > EOF
285 285
286 286
287 287 plain diff in email, subject, message body
288 288
289 289 $ hg clone -r0 a b
290 290 adding changesets
291 291 adding manifests
292 292 adding file changes
293 293 added 1 changesets with 2 changes to 2 files
294 294 updating to branch default
295 295 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
296 296 $ python mkmsg.py diffed-tip.patch msg.patch
297 297 $ hg --cwd b import ../msg.patch
298 298 applying ../msg.patch
299 299 $ hg --cwd b tip | grep email
300 300 user: email patcher
301 301 summary: email patch
302 302 $ rm -r b
303 303
304 304
305 305 plain diff in email, no subject, message body
306 306
307 307 $ hg clone -r0 a b
308 308 adding changesets
309 309 adding manifests
310 310 adding file changes
311 311 added 1 changesets with 2 changes to 2 files
312 312 updating to branch default
313 313 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
314 314 $ grep -v '^Subject:' msg.patch | hg --cwd b import -
315 315 applying patch from stdin
316 316 $ rm -r b
317 317
318 318
319 319 plain diff in email, subject, no message body
320 320
321 321 $ hg clone -r0 a b
322 322 adding changesets
323 323 adding manifests
324 324 adding file changes
325 325 added 1 changesets with 2 changes to 2 files
326 326 updating to branch default
327 327 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
328 328 $ grep -v '^email ' msg.patch | hg --cwd b import -
329 329 applying patch from stdin
330 330 $ rm -r b
331 331
332 332
333 333 plain diff in email, no subject, no message body, should fail
334 334
335 335 $ hg clone -r0 a b
336 336 adding changesets
337 337 adding manifests
338 338 adding file changes
339 339 added 1 changesets with 2 changes to 2 files
340 340 updating to branch default
341 341 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 342 $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
343 343 applying patch from stdin
344 344 abort: empty commit message
345 345 [255]
346 346 $ rm -r b
347 347
348 348
349 349 hg export in email, should use patch header
350 350
351 351 $ hg clone -r0 a b
352 352 adding changesets
353 353 adding manifests
354 354 adding file changes
355 355 added 1 changesets with 2 changes to 2 files
356 356 updating to branch default
357 357 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
358 358 $ python mkmsg.py exported-tip.patch msg.patch
359 359 $ cat msg.patch | hg --cwd b import -
360 360 applying patch from stdin
361 361 $ hg --cwd b tip | grep second
362 362 summary: second change
363 363 $ rm -r b
364 364
365 365
366 366 subject: duplicate detection, removal of [PATCH]
367 367 The '---' tests the gitsendmail handling without proper mail headers
368 368
369 369 $ cat > mkmsg2.py <<EOF
370 370 > import email.Message, sys
371 371 > msg = email.Message.Message()
372 372 > patch = open(sys.argv[1], 'rb').read()
373 373 > msg.set_payload('email patch\n\nnext line\n---\n' + patch)
374 374 > msg['Subject'] = '[PATCH] email patch'
375 375 > msg['From'] = 'email patcher'
376 376 > file(sys.argv[2], 'wb').write(msg.as_string())
377 377 > EOF
378 378
379 379
380 380 plain diff in email, [PATCH] subject, message body with subject
381 381
382 382 $ hg clone -r0 a b
383 383 adding changesets
384 384 adding manifests
385 385 adding file changes
386 386 added 1 changesets with 2 changes to 2 files
387 387 updating to branch default
388 388 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
389 389 $ python mkmsg2.py diffed-tip.patch msg.patch
390 390 $ cat msg.patch | hg --cwd b import -
391 391 applying patch from stdin
392 392 $ hg --cwd b tip --template '{desc}\n'
393 393 email patch
394 394
395 395 next line
396 396 $ rm -r b
397 397
398 398
399 399 Issue963: Parent of working dir incorrect after import of multiple
400 400 patches and rollback
401 401
402 402 We weren't backing up the correct dirstate file when importing many
403 403 patches: import patch1 patch2; rollback
404 404
405 405 $ echo line 3 >> a/a
406 406 $ hg --cwd a ci -m'third change'
407 407 $ hg --cwd a export -o '../patch%R' 1 2
408 408 $ hg clone -qr0 a b
409 409 $ hg --cwd b parents --template 'parent: {rev}\n'
410 410 parent: 0
411 411 $ hg --cwd b import -v ../patch1 ../patch2
412 412 applying ../patch1
413 413 patching file a
414 414 committing files:
415 415 a
416 416 committing manifest
417 417 committing changelog
418 418 created 1d4bd90af0e4
419 419 applying ../patch2
420 420 patching file a
421 421 committing files:
422 422 a
423 423 committing manifest
424 424 committing changelog
425 425 created 6d019af21222
426 426 $ hg --cwd b rollback
427 427 repository tip rolled back to revision 0 (undo import)
428 428 working directory now based on revision 0
429 429 $ hg --cwd b parents --template 'parent: {rev}\n'
430 430 parent: 0
431 431 $ rm -r b
432 432
433 433
434 434 importing a patch in a subdirectory failed at the commit stage
435 435
436 436 $ echo line 2 >> a/d1/d2/a
437 437 $ hg --cwd a ci -u someoneelse -d '1 0' -m'subdir change'
438 438
439 439 hg import in a subdirectory
440 440
441 441 $ hg clone -r0 a b
442 442 adding changesets
443 443 adding manifests
444 444 adding file changes
445 445 added 1 changesets with 2 changes to 2 files
446 446 updating to branch default
447 447 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
448 448 $ hg --cwd a export tip > tmp
449 449 $ sed -e 's/d1\/d2\///' < tmp > subdir-tip.patch
450 450 $ dir=`pwd`
451 451 $ cd b/d1/d2 2>&1 > /dev/null
452 452 $ hg import ../../../subdir-tip.patch
453 453 applying ../../../subdir-tip.patch
454 454 $ cd "$dir"
455 455
456 456 message should be 'subdir change'
457 457 committer should be 'someoneelse'
458 458
459 459 $ hg --cwd b tip
460 460 changeset: 1:3577f5aea227
461 461 tag: tip
462 462 user: someoneelse
463 463 date: Thu Jan 01 00:00:01 1970 +0000
464 464 summary: subdir change
465 465
466 466
467 467 should be empty
468 468
469 469 $ hg --cwd b status
470 470
471 471
472 472 Test fuzziness (ambiguous patch location, fuzz=2)
473 473
474 474 $ hg init fuzzy
475 475 $ cd fuzzy
476 476 $ echo line1 > a
477 477 $ echo line0 >> a
478 478 $ echo line3 >> a
479 479 $ hg ci -Am adda
480 480 adding a
481 481 $ echo line1 > a
482 482 $ echo line2 >> a
483 483 $ echo line0 >> a
484 484 $ echo line3 >> a
485 485 $ hg ci -m change a
486 486 $ hg export tip > fuzzy-tip.patch
487 487 $ hg up -C 0
488 488 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
489 489 $ echo line1 > a
490 490 $ echo line0 >> a
491 491 $ echo line1 >> a
492 492 $ echo line0 >> a
493 493 $ hg ci -m brancha
494 494 created new head
495 495 $ hg import --no-commit -v fuzzy-tip.patch
496 496 applying fuzzy-tip.patch
497 497 patching file a
498 498 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
499 499 applied to working directory
500 500 $ hg revert -a
501 501 reverting a
502 502
503 503
504 504 import with --no-commit should have written .hg/last-message.txt
505 505
506 506 $ cat .hg/last-message.txt
507 507 change (no-eol)
508 508
509 509
510 510 test fuzziness with eol=auto
511 511
512 512 $ hg --config patch.eol=auto import --no-commit -v fuzzy-tip.patch
513 513 applying fuzzy-tip.patch
514 514 patching file a
515 515 Hunk #1 succeeded at 2 with fuzz 1 (offset 0 lines).
516 516 applied to working directory
517 517 $ cd ..
518 518
519 519
520 520 Test hunk touching empty files (issue906)
521 521
522 522 $ hg init empty
523 523 $ cd empty
524 524 $ touch a
525 525 $ touch b1
526 526 $ touch c1
527 527 $ echo d > d
528 528 $ hg ci -Am init
529 529 adding a
530 530 adding b1
531 531 adding c1
532 532 adding d
533 533 $ echo a > a
534 534 $ echo b > b1
535 535 $ hg mv b1 b2
536 536 $ echo c > c1
537 537 $ hg copy c1 c2
538 538 $ rm d
539 539 $ touch d
540 540 $ hg diff --git
541 541 diff --git a/a b/a
542 542 --- a/a
543 543 +++ b/a
544 544 @@ -0,0 +1,1 @@
545 545 +a
546 546 diff --git a/b1 b/b2
547 547 rename from b1
548 548 rename to b2
549 549 --- a/b1
550 550 +++ b/b2
551 551 @@ -0,0 +1,1 @@
552 552 +b
553 553 diff --git a/c1 b/c1
554 554 --- a/c1
555 555 +++ b/c1
556 556 @@ -0,0 +1,1 @@
557 557 +c
558 558 diff --git a/c1 b/c2
559 559 copy from c1
560 560 copy to c2
561 561 --- a/c1
562 562 +++ b/c2
563 563 @@ -0,0 +1,1 @@
564 564 +c
565 565 diff --git a/d b/d
566 566 --- a/d
567 567 +++ b/d
568 568 @@ -1,1 +0,0 @@
569 569 -d
570 570 $ hg ci -m empty
571 571 $ hg export --git tip > empty.diff
572 572 $ hg up -C 0
573 573 4 files updated, 0 files merged, 2 files removed, 0 files unresolved
574 574 $ hg import empty.diff
575 575 applying empty.diff
576 576 $ for name in a b1 b2 c1 c2 d; do
577 577 > echo % $name file
578 578 > test -f $name && cat $name
579 579 > done
580 580 % a file
581 581 a
582 582 % b1 file
583 583 % b2 file
584 584 b
585 585 % c1 file
586 586 c
587 587 % c2 file
588 588 c
589 589 % d file
590 590 $ cd ..
591 591
592 592
593 593 Test importing a patch ending with a binary file removal
594 594
595 595 $ hg init binaryremoval
596 596 $ cd binaryremoval
597 597 $ echo a > a
598 598 $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
599 599 $ hg ci -Am addall
600 600 adding a
601 601 adding b
602 602 $ hg rm a
603 603 $ hg rm b
604 604 $ hg st
605 605 R a
606 606 R b
607 607 $ hg ci -m remove
608 608 $ hg export --git . > remove.diff
609 609 $ cat remove.diff | grep git
610 610 diff --git a/a b/a
611 611 diff --git a/b b/b
612 612 $ hg up -C 0
613 613 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
614 614 $ hg import remove.diff
615 615 applying remove.diff
616 616 $ hg manifest
617 617 $ cd ..
618 618
619 619
620 620 Issue927: test update+rename with common name
621 621
622 622 $ hg init t
623 623 $ cd t
624 624 $ touch a
625 625 $ hg ci -Am t
626 626 adding a
627 627 $ echo a > a
628 628
629 629 Here, bfile.startswith(afile)
630 630
631 631 $ hg copy a a2
632 632 $ hg ci -m copya
633 633 $ hg export --git tip > copy.diff
634 634 $ hg up -C 0
635 635 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
636 636 $ hg import copy.diff
637 637 applying copy.diff
638 638
639 639 a should contain an 'a'
640 640
641 641 $ cat a
642 642 a
643 643
644 644 and a2 should have duplicated it
645 645
646 646 $ cat a2
647 647 a
648 648 $ cd ..
649 649
650 650
651 651 test -p0
652 652
653 653 $ hg init p0
654 654 $ cd p0
655 655 $ echo a > a
656 656 $ hg ci -Am t
657 657 adding a
658 658 $ hg import -p foo
659 659 abort: invalid value 'foo' for option -p, expected int
660 660 [255]
661 661 $ hg import -p0 - << EOF
662 662 > foobar
663 663 > --- a Sat Apr 12 22:43:58 2008 -0400
664 664 > +++ a Sat Apr 12 22:44:05 2008 -0400
665 665 > @@ -1,1 +1,1 @@
666 666 > -a
667 667 > +bb
668 668 > EOF
669 669 applying patch from stdin
670 670 $ hg status
671 671 $ cat a
672 672 bb
673
674 test --prefix
675
676 $ mkdir -p dir/dir2
677 $ echo b > dir/dir2/b
678 $ hg ci -Am b
679 adding dir/dir2/b
680 $ hg import -p2 --prefix dir - << EOF
681 > foobar
682 > --- drop1/drop2/dir2/b
683 > +++ drop1/drop2/dir2/b
684 > @@ -1,1 +1,1 @@
685 > -b
686 > +cc
687 > EOF
688 applying patch from stdin
689 $ hg status
690 $ cat dir/dir2/b
691 cc
673 692 $ cd ..
674 693
675 694
676 695 test paths outside repo root
677 696
678 697 $ mkdir outside
679 698 $ touch outside/foo
680 699 $ hg init inside
681 700 $ cd inside
682 701 $ hg import - <<EOF
683 702 > diff --git a/a b/b
684 703 > rename from ../outside/foo
685 704 > rename to bar
686 705 > EOF
687 706 applying patch from stdin
688 707 abort: path contains illegal component: ../outside/foo (glob)
689 708 [255]
690 709 $ cd ..
691 710
692 711
693 712 test import with similarity and git and strip (issue295 et al.)
694 713
695 714 $ hg init sim
696 715 $ cd sim
697 716 $ echo 'this is a test' > a
698 717 $ hg ci -Ama
699 718 adding a
700 719 $ cat > ../rename.diff <<EOF
701 720 > diff --git a/foo/a b/foo/a
702 721 > deleted file mode 100644
703 722 > --- a/foo/a
704 723 > +++ /dev/null
705 724 > @@ -1,1 +0,0 @@
706 725 > -this is a test
707 726 > diff --git a/foo/b b/foo/b
708 727 > new file mode 100644
709 728 > --- /dev/null
710 729 > +++ b/foo/b
711 730 > @@ -0,0 +1,2 @@
712 731 > +this is a test
713 732 > +foo
714 733 > EOF
715 734 $ hg import --no-commit -v -s 1 ../rename.diff -p2
716 735 applying ../rename.diff
717 736 patching file a
718 737 patching file b
719 738 adding b
720 739 recording removal of a as rename to b (88% similar)
721 740 applied to working directory
722 741 $ hg st -C
723 742 A b
724 743 a
725 744 R a
726 745 $ hg revert -a
727 746 undeleting a
728 747 forgetting b
729 748 $ rm b
730 749 $ hg import --no-commit -v -s 100 ../rename.diff -p2
731 750 applying ../rename.diff
732 751 patching file a
733 752 patching file b
734 753 adding b
735 754 applied to working directory
736 755 $ hg st -C
737 756 A b
738 757 R a
739 758 $ cd ..
740 759
741 760
742 761 Issue1495: add empty file from the end of patch
743 762
744 763 $ hg init addemptyend
745 764 $ cd addemptyend
746 765 $ touch a
747 766 $ hg addremove
748 767 adding a
749 768 $ hg ci -m "commit"
750 769 $ cat > a.patch <<EOF
751 770 > add a, b
752 771 > diff --git a/a b/a
753 772 > --- a/a
754 773 > +++ b/a
755 774 > @@ -0,0 +1,1 @@
756 775 > +a
757 776 > diff --git a/b b/b
758 777 > new file mode 100644
759 778 > EOF
760 779 $ hg import --no-commit a.patch
761 780 applying a.patch
762 781
763 782 apply a good patch followed by an empty patch (mainly to ensure
764 783 that dirstate is *not* updated when import crashes)
765 784 $ hg update -q -C .
766 785 $ rm b
767 786 $ touch empty.patch
768 787 $ hg import a.patch empty.patch
769 788 applying a.patch
770 789 applying empty.patch
771 790 transaction abort!
772 791 rollback completed
773 792 abort: empty.patch: no diffs found
774 793 [255]
775 794 $ hg tip --template '{rev} {desc|firstline}\n'
776 795 0 commit
777 796 $ hg -q status
778 797 M a
779 798 $ cd ..
780 799
781 800 create file when source is not /dev/null
782 801
783 802 $ cat > create.patch <<EOF
784 803 > diff -Naur proj-orig/foo proj-new/foo
785 804 > --- proj-orig/foo 1969-12-31 16:00:00.000000000 -0800
786 805 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
787 806 > @@ -0,0 +1,1 @@
788 807 > +a
789 808 > EOF
790 809
791 810 some people have patches like the following too
792 811
793 812 $ cat > create2.patch <<EOF
794 813 > diff -Naur proj-orig/foo proj-new/foo
795 814 > --- proj-orig/foo.orig 1969-12-31 16:00:00.000000000 -0800
796 815 > +++ proj-new/foo 2009-07-17 16:50:45.801368000 -0700
797 816 > @@ -0,0 +1,1 @@
798 817 > +a
799 818 > EOF
800 819 $ hg init oddcreate
801 820 $ cd oddcreate
802 821 $ hg import --no-commit ../create.patch
803 822 applying ../create.patch
804 823 $ cat foo
805 824 a
806 825 $ rm foo
807 826 $ hg revert foo
808 827 $ hg import --no-commit ../create2.patch
809 828 applying ../create2.patch
810 829 $ cat foo
811 830 a
812 831
813 832 $ cd ..
814 833
815 834 Issue1859: first line mistaken for email headers
816 835
817 836 $ hg init emailconfusion
818 837 $ cd emailconfusion
819 838 $ cat > a.patch <<EOF
820 839 > module: summary
821 840 >
822 841 > description
823 842 >
824 843 >
825 844 > diff -r 000000000000 -r 9b4c1e343b55 test.txt
826 845 > --- /dev/null
827 846 > +++ b/a
828 847 > @@ -0,0 +1,1 @@
829 848 > +a
830 849 > EOF
831 850 $ hg import -d '0 0' a.patch
832 851 applying a.patch
833 852 $ hg parents -v
834 853 changeset: 0:5a681217c0ad
835 854 tag: tip
836 855 user: test
837 856 date: Thu Jan 01 00:00:00 1970 +0000
838 857 files: a
839 858 description:
840 859 module: summary
841 860
842 861 description
843 862
844 863
845 864 $ cd ..
846 865
847 866
848 867 in commit message
849 868
850 869 $ hg init commitconfusion
851 870 $ cd commitconfusion
852 871 $ cat > a.patch <<EOF
853 872 > module: summary
854 873 >
855 874 > --- description
856 875 >
857 876 > diff --git a/a b/a
858 877 > new file mode 100644
859 878 > --- /dev/null
860 879 > +++ b/a
861 880 > @@ -0,0 +1,1 @@
862 881 > +a
863 882 > EOF
864 883 > hg import -d '0 0' a.patch
865 884 > hg parents -v
866 885 > cd ..
867 886 >
868 887 > echo '% tricky header splitting'
869 888 > cat > trickyheaders.patch <<EOF
870 889 > From: User A <user@a>
871 890 > Subject: [PATCH] from: tricky!
872 891 >
873 892 > # HG changeset patch
874 893 > # User User B
875 894 > # Date 1266264441 18000
876 895 > # Branch stable
877 896 > # Node ID f2be6a1170ac83bf31cb4ae0bad00d7678115bc0
878 897 > # Parent 0000000000000000000000000000000000000000
879 898 > from: tricky!
880 899 >
881 900 > That is not a header.
882 901 >
883 902 > diff -r 000000000000 -r f2be6a1170ac foo
884 903 > --- /dev/null
885 904 > +++ b/foo
886 905 > @@ -0,0 +1,1 @@
887 906 > +foo
888 907 > EOF
889 908 applying a.patch
890 909 changeset: 0:f34d9187897d
891 910 tag: tip
892 911 user: test
893 912 date: Thu Jan 01 00:00:00 1970 +0000
894 913 files: a
895 914 description:
896 915 module: summary
897 916
898 917
899 918 % tricky header splitting
900 919
901 920 $ hg init trickyheaders
902 921 $ cd trickyheaders
903 922 $ hg import -d '0 0' ../trickyheaders.patch
904 923 applying ../trickyheaders.patch
905 924 $ hg export --git tip
906 925 # HG changeset patch
907 926 # User User B
908 927 # Date 0 0
909 928 # Thu Jan 01 00:00:00 1970 +0000
910 929 # Node ID eb56ab91903632294ac504838508cb370c0901d2
911 930 # Parent 0000000000000000000000000000000000000000
912 931 from: tricky!
913 932
914 933 That is not a header.
915 934
916 935 diff --git a/foo b/foo
917 936 new file mode 100644
918 937 --- /dev/null
919 938 +++ b/foo
920 939 @@ -0,0 +1,1 @@
921 940 +foo
922 941 $ cd ..
923 942
924 943
925 944 Issue2102: hg export and hg import speak different languages
926 945
927 946 $ hg init issue2102
928 947 $ cd issue2102
929 948 $ mkdir -p src/cmd/gc
930 949 $ touch src/cmd/gc/mksys.bash
931 950 $ hg ci -Am init
932 951 adding src/cmd/gc/mksys.bash
933 952 $ hg import - <<EOF
934 953 > # HG changeset patch
935 954 > # User Rob Pike
936 955 > # Date 1216685449 25200
937 956 > # Node ID 03aa2b206f499ad6eb50e6e207b9e710d6409c98
938 957 > # Parent 93d10138ad8df586827ca90b4ddb5033e21a3a84
939 958 > help management of empty pkg and lib directories in perforce
940 959 >
941 960 > R=gri
942 961 > DELTA=4 (4 added, 0 deleted, 0 changed)
943 962 > OCL=13328
944 963 > CL=13328
945 964 >
946 965 > diff --git a/lib/place-holder b/lib/place-holder
947 966 > new file mode 100644
948 967 > --- /dev/null
949 968 > +++ b/lib/place-holder
950 969 > @@ -0,0 +1,2 @@
951 970 > +perforce does not maintain empty directories.
952 971 > +this file helps.
953 972 > diff --git a/pkg/place-holder b/pkg/place-holder
954 973 > new file mode 100644
955 974 > --- /dev/null
956 975 > +++ b/pkg/place-holder
957 976 > @@ -0,0 +1,2 @@
958 977 > +perforce does not maintain empty directories.
959 978 > +this file helps.
960 979 > diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
961 980 > old mode 100644
962 981 > new mode 100755
963 982 > EOF
964 983 applying patch from stdin
965 984
966 985 #if execbit
967 986
968 987 $ hg sum
969 988 parent: 1:d59915696727 tip
970 989 help management of empty pkg and lib directories in perforce
971 990 branch: default
972 991 commit: (clean)
973 992 update: (current)
974 993
975 994 $ hg diff --git -c tip
976 995 diff --git a/lib/place-holder b/lib/place-holder
977 996 new file mode 100644
978 997 --- /dev/null
979 998 +++ b/lib/place-holder
980 999 @@ -0,0 +1,2 @@
981 1000 +perforce does not maintain empty directories.
982 1001 +this file helps.
983 1002 diff --git a/pkg/place-holder b/pkg/place-holder
984 1003 new file mode 100644
985 1004 --- /dev/null
986 1005 +++ b/pkg/place-holder
987 1006 @@ -0,0 +1,2 @@
988 1007 +perforce does not maintain empty directories.
989 1008 +this file helps.
990 1009 diff --git a/src/cmd/gc/mksys.bash b/src/cmd/gc/mksys.bash
991 1010 old mode 100644
992 1011 new mode 100755
993 1012
994 1013 #else
995 1014
996 1015 $ hg sum
997 1016 parent: 1:28f089cc9ccc tip
998 1017 help management of empty pkg and lib directories in perforce
999 1018 branch: default
1000 1019 commit: (clean)
1001 1020 update: (current)
1002 1021
1003 1022 $ hg diff --git -c tip
1004 1023 diff --git a/lib/place-holder b/lib/place-holder
1005 1024 new file mode 100644
1006 1025 --- /dev/null
1007 1026 +++ b/lib/place-holder
1008 1027 @@ -0,0 +1,2 @@
1009 1028 +perforce does not maintain empty directories.
1010 1029 +this file helps.
1011 1030 diff --git a/pkg/place-holder b/pkg/place-holder
1012 1031 new file mode 100644
1013 1032 --- /dev/null
1014 1033 +++ b/pkg/place-holder
1015 1034 @@ -0,0 +1,2 @@
1016 1035 +perforce does not maintain empty directories.
1017 1036 +this file helps.
1018 1037
1019 1038 /* The mode change for mksys.bash is missing here, because on platforms */
1020 1039 /* that don't support execbits, mode changes in patches are ignored when */
1021 1040 /* they are imported. This is obviously also the reason for why the hash */
1022 1041 /* in the created changeset is different to the one you see above the */
1023 1042 /* #else clause */
1024 1043
1025 1044 #endif
1026 1045 $ cd ..
1027 1046
1028 1047
1029 1048 diff lines looking like headers
1030 1049
1031 1050 $ hg init difflineslikeheaders
1032 1051 $ cd difflineslikeheaders
1033 1052 $ echo a >a
1034 1053 $ echo b >b
1035 1054 $ echo c >c
1036 1055 $ hg ci -Am1
1037 1056 adding a
1038 1057 adding b
1039 1058 adding c
1040 1059
1041 1060 $ echo "key: value" >>a
1042 1061 $ echo "key: value" >>b
1043 1062 $ echo "foo" >>c
1044 1063 $ hg ci -m2
1045 1064
1046 1065 $ hg up -C 0
1047 1066 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1048 1067 $ hg diff --git -c1 >want
1049 1068 $ hg diff -c1 | hg import --no-commit -
1050 1069 applying patch from stdin
1051 1070 $ hg diff --git >have
1052 1071 $ diff want have
1053 1072 $ cd ..
1054 1073
1055 1074 import a unified diff with no lines of context (diff -U0)
1056 1075
1057 1076 $ hg init diffzero
1058 1077 $ cd diffzero
1059 1078 $ cat > f << EOF
1060 1079 > c2
1061 1080 > c4
1062 1081 > c5
1063 1082 > EOF
1064 1083 $ hg commit -Am0
1065 1084 adding f
1066 1085
1067 1086 $ hg import --no-commit - << EOF
1068 1087 > # HG changeset patch
1069 1088 > # User test
1070 1089 > # Date 0 0
1071 1090 > # Node ID f4974ab632f3dee767567b0576c0ec9a4508575c
1072 1091 > # Parent 8679a12a975b819fae5f7ad3853a2886d143d794
1073 1092 > 1
1074 1093 > diff -r 8679a12a975b -r f4974ab632f3 f
1075 1094 > --- a/f Thu Jan 01 00:00:00 1970 +0000
1076 1095 > +++ b/f Thu Jan 01 00:00:00 1970 +0000
1077 1096 > @@ -0,0 +1,1 @@
1078 1097 > +c1
1079 1098 > @@ -1,0 +3,1 @@
1080 1099 > +c3
1081 1100 > @@ -3,1 +4,0 @@
1082 1101 > -c5
1083 1102 > EOF
1084 1103 applying patch from stdin
1085 1104
1086 1105 $ cat f
1087 1106 c1
1088 1107 c2
1089 1108 c3
1090 1109 c4
1091 1110
1092 1111 $ cd ..
1093 1112
1094 1113 no segfault while importing a unified diff which start line is zero but chunk
1095 1114 size is non-zero
1096 1115
1097 1116 $ hg init startlinezero
1098 1117 $ cd startlinezero
1099 1118 $ echo foo > foo
1100 1119 $ hg commit -Amfoo
1101 1120 adding foo
1102 1121
1103 1122 $ hg import --no-commit - << EOF
1104 1123 > diff a/foo b/foo
1105 1124 > --- a/foo
1106 1125 > +++ b/foo
1107 1126 > @@ -0,1 +0,1 @@
1108 1127 > foo
1109 1128 > EOF
1110 1129 applying patch from stdin
1111 1130
1112 1131 $ cd ..
1113 1132
1114 1133 Test corner case involving fuzz and skew
1115 1134
1116 1135 $ hg init morecornercases
1117 1136 $ cd morecornercases
1118 1137
1119 1138 $ cat > 01-no-context-beginning-of-file.diff <<EOF
1120 1139 > diff --git a/a b/a
1121 1140 > --- a/a
1122 1141 > +++ b/a
1123 1142 > @@ -1,0 +1,1 @@
1124 1143 > +line
1125 1144 > EOF
1126 1145
1127 1146 $ cat > 02-no-context-middle-of-file.diff <<EOF
1128 1147 > diff --git a/a b/a
1129 1148 > --- a/a
1130 1149 > +++ b/a
1131 1150 > @@ -1,1 +1,1 @@
1132 1151 > -2
1133 1152 > +add some skew
1134 1153 > @@ -2,0 +2,1 @@
1135 1154 > +line
1136 1155 > EOF
1137 1156
1138 1157 $ cat > 03-no-context-end-of-file.diff <<EOF
1139 1158 > diff --git a/a b/a
1140 1159 > --- a/a
1141 1160 > +++ b/a
1142 1161 > @@ -10,0 +10,1 @@
1143 1162 > +line
1144 1163 > EOF
1145 1164
1146 1165 $ cat > 04-middle-of-file-completely-fuzzed.diff <<EOF
1147 1166 > diff --git a/a b/a
1148 1167 > --- a/a
1149 1168 > +++ b/a
1150 1169 > @@ -1,1 +1,1 @@
1151 1170 > -2
1152 1171 > +add some skew
1153 1172 > @@ -2,2 +2,3 @@
1154 1173 > not matching, should fuzz
1155 1174 > ... a bit
1156 1175 > +line
1157 1176 > EOF
1158 1177
1159 1178 $ cat > a <<EOF
1160 1179 > 1
1161 1180 > 2
1162 1181 > 3
1163 1182 > 4
1164 1183 > EOF
1165 1184 $ hg ci -Am adda a
1166 1185 $ for p in *.diff; do
1167 1186 > hg import -v --no-commit $p
1168 1187 > cat a
1169 1188 > hg revert -aqC a
1170 1189 > # patch -p1 < $p
1171 1190 > # cat a
1172 1191 > # hg revert -aC a
1173 1192 > done
1174 1193 applying 01-no-context-beginning-of-file.diff
1175 1194 patching file a
1176 1195 applied to working directory
1177 1196 1
1178 1197 line
1179 1198 2
1180 1199 3
1181 1200 4
1182 1201 applying 02-no-context-middle-of-file.diff
1183 1202 patching file a
1184 1203 Hunk #1 succeeded at 2 (offset 1 lines).
1185 1204 Hunk #2 succeeded at 4 (offset 1 lines).
1186 1205 applied to working directory
1187 1206 1
1188 1207 add some skew
1189 1208 3
1190 1209 line
1191 1210 4
1192 1211 applying 03-no-context-end-of-file.diff
1193 1212 patching file a
1194 1213 Hunk #1 succeeded at 5 (offset -6 lines).
1195 1214 applied to working directory
1196 1215 1
1197 1216 2
1198 1217 3
1199 1218 4
1200 1219 line
1201 1220 applying 04-middle-of-file-completely-fuzzed.diff
1202 1221 patching file a
1203 1222 Hunk #1 succeeded at 2 (offset 1 lines).
1204 1223 Hunk #2 succeeded at 5 with fuzz 2 (offset 1 lines).
1205 1224 applied to working directory
1206 1225 1
1207 1226 add some skew
1208 1227 3
1209 1228 4
1210 1229 line
1211 1230 $ cd ..
1212 1231
1213 1232 Test partial application
1214 1233 ------------------------
1215 1234
1216 1235 prepare a stack of patches depending on each other
1217 1236
1218 1237 $ hg init partial
1219 1238 $ cd partial
1220 1239 $ cat << EOF > a
1221 1240 > one
1222 1241 > two
1223 1242 > three
1224 1243 > four
1225 1244 > five
1226 1245 > six
1227 1246 > seven
1228 1247 > EOF
1229 1248 $ hg add a
1230 1249 $ echo 'b' > b
1231 1250 $ hg add b
1232 1251 $ hg commit -m 'initial' -u Babar
1233 1252 $ cat << EOF > a
1234 1253 > one
1235 1254 > two
1236 1255 > 3
1237 1256 > four
1238 1257 > five
1239 1258 > six
1240 1259 > seven
1241 1260 > EOF
1242 1261 $ hg commit -m 'three' -u Celeste
1243 1262 $ cat << EOF > a
1244 1263 > one
1245 1264 > two
1246 1265 > 3
1247 1266 > 4
1248 1267 > five
1249 1268 > six
1250 1269 > seven
1251 1270 > EOF
1252 1271 $ hg commit -m 'four' -u Rataxes
1253 1272 $ cat << EOF > a
1254 1273 > one
1255 1274 > two
1256 1275 > 3
1257 1276 > 4
1258 1277 > 5
1259 1278 > six
1260 1279 > seven
1261 1280 > EOF
1262 1281 $ echo bb >> b
1263 1282 $ hg commit -m 'five' -u Arthur
1264 1283 $ echo 'Babar' > jungle
1265 1284 $ hg add jungle
1266 1285 $ hg ci -m 'jungle' -u Zephir
1267 1286 $ echo 'Celeste' >> jungle
1268 1287 $ hg ci -m 'extended jungle' -u Cornelius
1269 1288 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1270 1289 @ extended jungle [Cornelius] 1: +1/-0
1271 1290 |
1272 1291 o jungle [Zephir] 1: +1/-0
1273 1292 |
1274 1293 o five [Arthur] 2: +2/-1
1275 1294 |
1276 1295 o four [Rataxes] 1: +1/-1
1277 1296 |
1278 1297 o three [Celeste] 1: +1/-1
1279 1298 |
1280 1299 o initial [Babar] 2: +8/-0
1281 1300
1282 1301
1283 1302 Importing with some success and some errors:
1284 1303
1285 1304 $ hg update --rev 'desc(initial)'
1286 1305 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
1287 1306 $ hg export --rev 'desc(five)' | hg import --partial -
1288 1307 applying patch from stdin
1289 1308 patching file a
1290 1309 Hunk #1 FAILED at 1
1291 1310 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1292 1311 patch applied partially
1293 1312 (fix the .rej files and run `hg commit --amend`)
1294 1313 [1]
1295 1314
1296 1315 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1297 1316 @ five [Arthur] 1: +1/-0
1298 1317 |
1299 1318 | o extended jungle [Cornelius] 1: +1/-0
1300 1319 | |
1301 1320 | o jungle [Zephir] 1: +1/-0
1302 1321 | |
1303 1322 | o five [Arthur] 2: +2/-1
1304 1323 | |
1305 1324 | o four [Rataxes] 1: +1/-1
1306 1325 | |
1307 1326 | o three [Celeste] 1: +1/-1
1308 1327 |/
1309 1328 o initial [Babar] 2: +8/-0
1310 1329
1311 1330 $ hg export
1312 1331 # HG changeset patch
1313 1332 # User Arthur
1314 1333 # Date 0 0
1315 1334 # Thu Jan 01 00:00:00 1970 +0000
1316 1335 # Node ID 26e6446bb2526e2be1037935f5fca2b2706f1509
1317 1336 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1318 1337 five
1319 1338
1320 1339 diff -r 8e4f0351909e -r 26e6446bb252 b
1321 1340 --- a/b Thu Jan 01 00:00:00 1970 +0000
1322 1341 +++ b/b Thu Jan 01 00:00:00 1970 +0000
1323 1342 @@ -1,1 +1,2 @@
1324 1343 b
1325 1344 +bb
1326 1345 $ hg status -c .
1327 1346 C a
1328 1347 C b
1329 1348 $ ls
1330 1349 a
1331 1350 a.rej
1332 1351 b
1333 1352
1334 1353 Importing with zero success:
1335 1354
1336 1355 $ hg update --rev 'desc(initial)'
1337 1356 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1338 1357 $ hg export --rev 'desc(four)' | hg import --partial -
1339 1358 applying patch from stdin
1340 1359 patching file a
1341 1360 Hunk #1 FAILED at 0
1342 1361 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1343 1362 patch applied partially
1344 1363 (fix the .rej files and run `hg commit --amend`)
1345 1364 [1]
1346 1365
1347 1366 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1348 1367 @ four [Rataxes] 0: +0/-0
1349 1368 |
1350 1369 | o five [Arthur] 1: +1/-0
1351 1370 |/
1352 1371 | o extended jungle [Cornelius] 1: +1/-0
1353 1372 | |
1354 1373 | o jungle [Zephir] 1: +1/-0
1355 1374 | |
1356 1375 | o five [Arthur] 2: +2/-1
1357 1376 | |
1358 1377 | o four [Rataxes] 1: +1/-1
1359 1378 | |
1360 1379 | o three [Celeste] 1: +1/-1
1361 1380 |/
1362 1381 o initial [Babar] 2: +8/-0
1363 1382
1364 1383 $ hg export
1365 1384 # HG changeset patch
1366 1385 # User Rataxes
1367 1386 # Date 0 0
1368 1387 # Thu Jan 01 00:00:00 1970 +0000
1369 1388 # Node ID cb9b1847a74d9ad52e93becaf14b98dbcc274e1e
1370 1389 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1371 1390 four
1372 1391
1373 1392 $ hg status -c .
1374 1393 C a
1375 1394 C b
1376 1395 $ ls
1377 1396 a
1378 1397 a.rej
1379 1398 b
1380 1399
1381 1400 Importing with unknown file:
1382 1401
1383 1402 $ hg update --rev 'desc(initial)'
1384 1403 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1385 1404 $ hg export --rev 'desc("extended jungle")' | hg import --partial -
1386 1405 applying patch from stdin
1387 1406 unable to find 'jungle' for patching
1388 1407 1 out of 1 hunks FAILED -- saving rejects to file jungle.rej
1389 1408 patch applied partially
1390 1409 (fix the .rej files and run `hg commit --amend`)
1391 1410 [1]
1392 1411
1393 1412 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1394 1413 @ extended jungle [Cornelius] 0: +0/-0
1395 1414 |
1396 1415 | o four [Rataxes] 0: +0/-0
1397 1416 |/
1398 1417 | o five [Arthur] 1: +1/-0
1399 1418 |/
1400 1419 | o extended jungle [Cornelius] 1: +1/-0
1401 1420 | |
1402 1421 | o jungle [Zephir] 1: +1/-0
1403 1422 | |
1404 1423 | o five [Arthur] 2: +2/-1
1405 1424 | |
1406 1425 | o four [Rataxes] 1: +1/-1
1407 1426 | |
1408 1427 | o three [Celeste] 1: +1/-1
1409 1428 |/
1410 1429 o initial [Babar] 2: +8/-0
1411 1430
1412 1431 $ hg export
1413 1432 # HG changeset patch
1414 1433 # User Cornelius
1415 1434 # Date 0 0
1416 1435 # Thu Jan 01 00:00:00 1970 +0000
1417 1436 # Node ID 1fb1f86bef43c5a75918178f8d23c29fb0a7398d
1418 1437 # Parent 8e4f0351909eae6b9cf68c2c076cb54c42b54b2e
1419 1438 extended jungle
1420 1439
1421 1440 $ hg status -c .
1422 1441 C a
1423 1442 C b
1424 1443 $ ls
1425 1444 a
1426 1445 a.rej
1427 1446 b
1428 1447 jungle.rej
1429 1448
1430 1449 Importing multiple failing patches:
1431 1450
1432 1451 $ hg update --rev 'desc(initial)'
1433 1452 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1434 1453 $ echo 'B' > b # just to make another commit
1435 1454 $ hg commit -m "a new base"
1436 1455 created new head
1437 1456 $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
1438 1457 applying patch from stdin
1439 1458 patching file a
1440 1459 Hunk #1 FAILED at 0
1441 1460 1 out of 1 hunks FAILED -- saving rejects to file a.rej
1442 1461 patch applied partially
1443 1462 (fix the .rej files and run `hg commit --amend`)
1444 1463 [1]
1445 1464 $ hg log -G --template '{desc|firstline} [{author}] {diffstat}\n'
1446 1465 @ four [Rataxes] 0: +0/-0
1447 1466 |
1448 1467 o a new base [test] 1: +1/-1
1449 1468 |
1450 1469 | o extended jungle [Cornelius] 0: +0/-0
1451 1470 |/
1452 1471 | o four [Rataxes] 0: +0/-0
1453 1472 |/
1454 1473 | o five [Arthur] 1: +1/-0
1455 1474 |/
1456 1475 | o extended jungle [Cornelius] 1: +1/-0
1457 1476 | |
1458 1477 | o jungle [Zephir] 1: +1/-0
1459 1478 | |
1460 1479 | o five [Arthur] 2: +2/-1
1461 1480 | |
1462 1481 | o four [Rataxes] 1: +1/-1
1463 1482 | |
1464 1483 | o three [Celeste] 1: +1/-1
1465 1484 |/
1466 1485 o initial [Babar] 2: +8/-0
1467 1486
1468 1487 $ hg export
1469 1488 # HG changeset patch
1470 1489 # User Rataxes
1471 1490 # Date 0 0
1472 1491 # Thu Jan 01 00:00:00 1970 +0000
1473 1492 # Node ID a9d7b6d0ffbb4eb12b7d5939250fcd42e8930a1d
1474 1493 # Parent f59f8d2e95a8ca5b1b4ca64320140da85f3b44fd
1475 1494 four
1476 1495
1477 1496 $ hg status -c .
1478 1497 C a
1479 1498 C b
General Comments 0
You need to be logged in to leave comments. Login now