##// END OF EJS Templates
rebase: use one dirstateguard for when using rebase.singletransaction...
Durham Goode -
r33621:609606d2 default
parent child Browse files
Show More
@@ -1,1540 +1,1550 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 hex,
25 25 nullid,
26 26 nullrev,
27 27 short,
28 28 )
29 29 from mercurial import (
30 30 bookmarks,
31 31 cmdutil,
32 32 commands,
33 33 copies,
34 34 destutil,
35 35 dirstateguard,
36 36 error,
37 37 extensions,
38 38 hg,
39 39 lock,
40 40 merge as mergemod,
41 41 mergeutil,
42 42 obsolete,
43 43 obsutil,
44 44 patch,
45 45 phases,
46 46 registrar,
47 47 repair,
48 48 repoview,
49 49 revset,
50 50 scmutil,
51 51 smartset,
52 52 util,
53 53 )
54 54
55 55 release = lock.release
56 56 templateopts = cmdutil.templateopts
57 57
58 58 # The following constants are used throughout the rebase module. The ordering of
59 59 # their values must be maintained.
60 60
61 61 # Indicates that a revision needs to be rebased
62 62 revtodo = -1
63 63 nullmerge = -2
64 64 revignored = -3
65 65 # successor in rebase destination
66 66 revprecursor = -4
67 67 # plain prune (no successor)
68 68 revpruned = -5
69 69 revskipped = (revignored, revprecursor, revpruned)
70 70
71 71 cmdtable = {}
72 72 command = registrar.command(cmdtable)
73 73 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
74 74 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
75 75 # be specifying the version(s) of Mercurial they are tested with, or
76 76 # leave the attribute unspecified.
77 77 testedwith = 'ships-with-hg-core'
78 78
79 79 def _nothingtorebase():
80 80 return 1
81 81
82 82 def _savegraft(ctx, extra):
83 83 s = ctx.extra().get('source', None)
84 84 if s is not None:
85 85 extra['source'] = s
86 86 s = ctx.extra().get('intermediate-source', None)
87 87 if s is not None:
88 88 extra['intermediate-source'] = s
89 89
90 90 def _savebranch(ctx, extra):
91 91 extra['branch'] = ctx.branch()
92 92
93 93 def _makeextrafn(copiers):
94 94 """make an extrafn out of the given copy-functions.
95 95
96 96 A copy function takes a context and an extra dict, and mutates the
97 97 extra dict as needed based on the given context.
98 98 """
99 99 def extrafn(ctx, extra):
100 100 for c in copiers:
101 101 c(ctx, extra)
102 102 return extrafn
103 103
104 104 def _destrebase(repo, sourceset, destspace=None):
105 105 """small wrapper around destmerge to pass the right extra args
106 106
107 107 Please wrap destutil.destmerge instead."""
108 108 return destutil.destmerge(repo, action='rebase', sourceset=sourceset,
109 109 onheadcheck=False, destspace=destspace)
110 110
111 111 revsetpredicate = registrar.revsetpredicate()
112 112
113 113 @revsetpredicate('_destrebase')
114 114 def _revsetdestrebase(repo, subset, x):
115 115 # ``_rebasedefaultdest()``
116 116
117 117 # default destination for rebase.
118 118 # # XXX: Currently private because I expect the signature to change.
119 119 # # XXX: - bailing out in case of ambiguity vs returning all data.
120 120 # i18n: "_rebasedefaultdest" is a keyword
121 121 sourceset = None
122 122 if x is not None:
123 123 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
124 124 return subset & smartset.baseset([_destrebase(repo, sourceset)])
125 125
126 126 class rebaseruntime(object):
127 127 """This class is a container for rebase runtime state"""
128 128 def __init__(self, repo, ui, opts=None):
129 129 if opts is None:
130 130 opts = {}
131 131
132 132 self.repo = repo
133 133 self.ui = ui
134 134 self.opts = opts
135 135 self.originalwd = None
136 136 self.external = nullrev
137 137 # Mapping between the old revision id and either what is the new rebased
138 138 # revision or what needs to be done with the old revision. The state
139 139 # dict will be what contains most of the rebase progress state.
140 140 self.state = {}
141 141 self.activebookmark = None
142 142 self.dest = None
143 143 self.skipped = set()
144 144 self.destancestors = set()
145 145
146 146 self.collapsef = opts.get('collapse', False)
147 147 self.collapsemsg = cmdutil.logmessage(ui, opts)
148 148 self.date = opts.get('date', None)
149 149
150 150 e = opts.get('extrafn') # internal, used by e.g. hgsubversion
151 151 self.extrafns = [_savegraft]
152 152 if e:
153 153 self.extrafns = [e]
154 154
155 155 self.keepf = opts.get('keep', False)
156 156 self.keepbranchesf = opts.get('keepbranches', False)
157 157 # keepopen is not meant for use on the command line, but by
158 158 # other extensions
159 159 self.keepopen = opts.get('keepopen', False)
160 160 self.obsoletenotrebased = {}
161 161
162 162 def storestatus(self, tr=None):
163 163 """Store the current status to allow recovery"""
164 164 if tr:
165 165 tr.addfilegenerator('rebasestate', ('rebasestate',),
166 166 self._writestatus, location='plain')
167 167 else:
168 168 with self.repo.vfs("rebasestate", "w") as f:
169 169 self._writestatus(f)
170 170
171 171 def _writestatus(self, f):
172 172 repo = self.repo.unfiltered()
173 173 f.write(repo[self.originalwd].hex() + '\n')
174 174 f.write(repo[self.dest].hex() + '\n')
175 175 f.write(repo[self.external].hex() + '\n')
176 176 f.write('%d\n' % int(self.collapsef))
177 177 f.write('%d\n' % int(self.keepf))
178 178 f.write('%d\n' % int(self.keepbranchesf))
179 179 f.write('%s\n' % (self.activebookmark or ''))
180 180 for d, v in self.state.iteritems():
181 181 oldrev = repo[d].hex()
182 182 if v >= 0:
183 183 newrev = repo[v].hex()
184 184 elif v == revtodo:
185 185 # To maintain format compatibility, we have to use nullid.
186 186 # Please do remove this special case when upgrading the format.
187 187 newrev = hex(nullid)
188 188 else:
189 189 newrev = v
190 190 f.write("%s:%s\n" % (oldrev, newrev))
191 191 repo.ui.debug('rebase status stored\n')
192 192
193 193 def restorestatus(self):
194 194 """Restore a previously stored status"""
195 195 repo = self.repo
196 196 keepbranches = None
197 197 dest = None
198 198 collapse = False
199 199 external = nullrev
200 200 activebookmark = None
201 201 state = {}
202 202
203 203 try:
204 204 f = repo.vfs("rebasestate")
205 205 for i, l in enumerate(f.read().splitlines()):
206 206 if i == 0:
207 207 originalwd = repo[l].rev()
208 208 elif i == 1:
209 209 dest = repo[l].rev()
210 210 elif i == 2:
211 211 external = repo[l].rev()
212 212 elif i == 3:
213 213 collapse = bool(int(l))
214 214 elif i == 4:
215 215 keep = bool(int(l))
216 216 elif i == 5:
217 217 keepbranches = bool(int(l))
218 218 elif i == 6 and not (len(l) == 81 and ':' in l):
219 219 # line 6 is a recent addition, so for backwards
220 220 # compatibility check that the line doesn't look like the
221 221 # oldrev:newrev lines
222 222 activebookmark = l
223 223 else:
224 224 oldrev, newrev = l.split(':')
225 225 if newrev in (str(nullmerge), str(revignored),
226 226 str(revprecursor), str(revpruned)):
227 227 state[repo[oldrev].rev()] = int(newrev)
228 228 elif newrev == nullid:
229 229 state[repo[oldrev].rev()] = revtodo
230 230 # Legacy compat special case
231 231 else:
232 232 state[repo[oldrev].rev()] = repo[newrev].rev()
233 233
234 234 except IOError as err:
235 235 if err.errno != errno.ENOENT:
236 236 raise
237 237 cmdutil.wrongtooltocontinue(repo, _('rebase'))
238 238
239 239 if keepbranches is None:
240 240 raise error.Abort(_('.hg/rebasestate is incomplete'))
241 241
242 242 skipped = set()
243 243 # recompute the set of skipped revs
244 244 if not collapse:
245 245 seen = {dest}
246 246 for old, new in sorted(state.items()):
247 247 if new != revtodo and new in seen:
248 248 skipped.add(old)
249 249 seen.add(new)
250 250 repo.ui.debug('computed skipped revs: %s\n' %
251 251 (' '.join(str(r) for r in sorted(skipped)) or None))
252 252 repo.ui.debug('rebase status resumed\n')
253 253 _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
254 254
255 255 self.originalwd = originalwd
256 256 self.dest = dest
257 257 self.state = state
258 258 self.skipped = skipped
259 259 self.collapsef = collapse
260 260 self.keepf = keep
261 261 self.keepbranchesf = keepbranches
262 262 self.external = external
263 263 self.activebookmark = activebookmark
264 264
265 265 def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
266 266 """Compute structures necessary for skipping obsolete revisions
267 267
268 268 rebaserevs: iterable of all revisions that are to be rebased
269 269 obsoleterevs: iterable of all obsolete revisions in rebaseset
270 270 dest: a destination revision for the rebase operation
271 271 """
272 272 self.obsoletenotrebased = {}
273 273 if not self.ui.configbool('experimental', 'rebaseskipobsolete',
274 274 default=True):
275 275 return
276 276 rebaseset = set(rebaserevs)
277 277 obsoleteset = set(obsoleterevs)
278 278 self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
279 279 obsoleteset, dest)
280 280 skippedset = set(self.obsoletenotrebased)
281 281 _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
282 282
283 283 def _prepareabortorcontinue(self, isabort):
284 284 try:
285 285 self.restorestatus()
286 286 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
287 287 except error.RepoLookupError:
288 288 if isabort:
289 289 clearstatus(self.repo)
290 290 clearcollapsemsg(self.repo)
291 291 self.repo.ui.warn(_('rebase aborted (no revision is removed,'
292 292 ' only broken state is cleared)\n'))
293 293 return 0
294 294 else:
295 295 msg = _('cannot continue inconsistent rebase')
296 296 hint = _('use "hg rebase --abort" to clear broken state')
297 297 raise error.Abort(msg, hint=hint)
298 298 if isabort:
299 299 return abort(self.repo, self.originalwd, self.dest,
300 300 self.state, activebookmark=self.activebookmark)
301 301
302 302 obsrevs = (r for r, st in self.state.items() if st == revprecursor)
303 303 self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
304 304
305 305 def _preparenewrebase(self, dest, rebaseset):
306 306 if dest is None:
307 307 return _nothingtorebase()
308 308
309 309 allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
310 310 if (not (self.keepf or allowunstable)
311 311 and self.repo.revs('first(children(%ld) - %ld)',
312 312 rebaseset, rebaseset)):
313 313 raise error.Abort(
314 314 _("can't remove original changesets with"
315 315 " unrebased descendants"),
316 316 hint=_('use --keep to keep original changesets'))
317 317
318 318 obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
319 319 self._handleskippingobsolete(rebaseset, obsrevs, dest.rev())
320 320
321 321 result = buildstate(self.repo, dest, rebaseset, self.collapsef,
322 322 self.obsoletenotrebased)
323 323
324 324 if not result:
325 325 # Empty state built, nothing to rebase
326 326 self.ui.status(_('nothing to rebase\n'))
327 327 return _nothingtorebase()
328 328
329 329 for root in self.repo.set('roots(%ld)', rebaseset):
330 330 if not self.keepf and not root.mutable():
331 331 raise error.Abort(_("can't rebase public changeset %s")
332 332 % root,
333 333 hint=_("see 'hg help phases' for details"))
334 334
335 335 (self.originalwd, self.dest, self.state) = result
336 336 if self.collapsef:
337 337 self.destancestors = self.repo.changelog.ancestors(
338 338 [self.dest],
339 339 inclusive=True)
340 340 self.external = externalparent(self.repo, self.state,
341 341 self.destancestors)
342 342
343 343 if dest.closesbranch() and not self.keepbranchesf:
344 344 self.ui.status(_('reopening closed branch head %s\n') % dest)
345 345
346 346 def _performrebase(self, tr):
347 347 repo, ui, opts = self.repo, self.ui, self.opts
348 348 if self.keepbranchesf:
349 349 # insert _savebranch at the start of extrafns so if
350 350 # there's a user-provided extrafn it can clobber branch if
351 351 # desired
352 352 self.extrafns.insert(0, _savebranch)
353 353 if self.collapsef:
354 354 branches = set()
355 355 for rev in self.state:
356 356 branches.add(repo[rev].branch())
357 357 if len(branches) > 1:
358 358 raise error.Abort(_('cannot collapse multiple named '
359 359 'branches'))
360 360
361 361 # Rebase
362 362 if not self.destancestors:
363 363 self.destancestors = repo.changelog.ancestors([self.dest],
364 364 inclusive=True)
365 365
366 366 # Keep track of the active bookmarks in order to reset them later
367 367 self.activebookmark = self.activebookmark or repo._activebookmark
368 368 if self.activebookmark:
369 369 bookmarks.deactivate(repo)
370 370
371 371 # Store the state before we begin so users can run 'hg rebase --abort'
372 372 # if we fail before the transaction closes.
373 373 self.storestatus()
374 374
375 375 sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
376 376 cands = [k for k, v in self.state.iteritems() if v == revtodo]
377 377 total = len(cands)
378 378 pos = 0
379 379 for rev in sortedrevs:
380 380 ctx = repo[rev]
381 381 desc = '%d:%s "%s"' % (ctx.rev(), ctx,
382 382 ctx.description().split('\n', 1)[0])
383 383 names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
384 384 if names:
385 385 desc += ' (%s)' % ' '.join(names)
386 386 if self.state[rev] == rev:
387 387 ui.status(_('already rebased %s\n') % desc)
388 388 elif self.state[rev] == revtodo:
389 389 pos += 1
390 390 ui.status(_('rebasing %s\n') % desc)
391 391 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
392 392 _('changesets'), total)
393 393 p1, p2, base = defineparents(repo, rev, self.dest,
394 394 self.state,
395 395 self.destancestors,
396 396 self.obsoletenotrebased)
397 397 self.storestatus(tr=tr)
398 398 storecollapsemsg(repo, self.collapsemsg)
399 399 if len(repo[None].parents()) == 2:
400 400 repo.ui.debug('resuming interrupted rebase\n')
401 401 else:
402 402 try:
403 403 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
404 404 'rebase')
405 405 stats = rebasenode(repo, rev, p1, base, self.state,
406 406 self.collapsef, self.dest)
407 407 if stats and stats[3] > 0:
408 408 raise error.InterventionRequired(
409 409 _('unresolved conflicts (see hg '
410 410 'resolve, then hg rebase --continue)'))
411 411 finally:
412 412 ui.setconfig('ui', 'forcemerge', '', 'rebase')
413 413 if not self.collapsef:
414 414 merging = p2 != nullrev
415 415 editform = cmdutil.mergeeditform(merging, 'rebase')
416 416 editor = cmdutil.getcommiteditor(editform=editform, **opts)
417 417 newnode = concludenode(repo, rev, p1, p2,
418 418 extrafn=_makeextrafn(self.extrafns),
419 419 editor=editor,
420 420 keepbranches=self.keepbranchesf,
421 421 date=self.date)
422 422 if newnode is None:
423 423 # If it ended up being a no-op commit, then the normal
424 424 # merge state clean-up path doesn't happen, so do it
425 425 # here. Fix issue5494
426 426 mergemod.mergestate.clean(repo)
427 427 else:
428 428 # Skip commit if we are collapsing
429 429 repo.setparents(repo[p1].node())
430 430 newnode = None
431 431 # Update the state
432 432 if newnode is not None:
433 433 self.state[rev] = repo[newnode].rev()
434 434 ui.debug('rebased as %s\n' % short(newnode))
435 435 else:
436 436 if not self.collapsef:
437 437 ui.warn(_('note: rebase of %d:%s created no changes '
438 438 'to commit\n') % (rev, ctx))
439 439 self.skipped.add(rev)
440 440 self.state[rev] = p1
441 441 ui.debug('next revision set to %s\n' % p1)
442 442 elif self.state[rev] == nullmerge:
443 443 ui.debug('ignoring null merge rebase of %s\n' % rev)
444 444 elif self.state[rev] == revignored:
445 445 ui.status(_('not rebasing ignored %s\n') % desc)
446 446 elif self.state[rev] == revprecursor:
447 447 destctx = repo[self.obsoletenotrebased[rev]]
448 448 descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
449 449 destctx.description().split('\n', 1)[0])
450 450 msg = _('note: not rebasing %s, already in destination as %s\n')
451 451 ui.status(msg % (desc, descdest))
452 452 elif self.state[rev] == revpruned:
453 453 msg = _('note: not rebasing %s, it has no successor\n')
454 454 ui.status(msg % desc)
455 455 else:
456 456 ui.status(_('already rebased %s as %s\n') %
457 457 (desc, repo[self.state[rev]]))
458 458
459 459 ui.progress(_('rebasing'), None)
460 460 ui.note(_('rebase merging completed\n'))
461 461
462 462 def _finishrebase(self):
463 463 repo, ui, opts = self.repo, self.ui, self.opts
464 464 if self.collapsef and not self.keepopen:
465 465 p1, p2, _base = defineparents(repo, min(self.state),
466 466 self.dest, self.state,
467 467 self.destancestors,
468 468 self.obsoletenotrebased)
469 469 editopt = opts.get('edit')
470 470 editform = 'rebase.collapse'
471 471 if self.collapsemsg:
472 472 commitmsg = self.collapsemsg
473 473 else:
474 474 commitmsg = 'Collapsed revision'
475 475 for rebased in self.state:
476 476 if rebased not in self.skipped and\
477 477 self.state[rebased] > nullmerge:
478 478 commitmsg += '\n* %s' % repo[rebased].description()
479 479 editopt = True
480 480 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
481 481 revtoreuse = max(self.state)
482 newnode = concludenode(repo, revtoreuse, p1, self.external,
483 commitmsg=commitmsg,
484 extrafn=_makeextrafn(self.extrafns),
485 editor=editor,
486 keepbranches=self.keepbranchesf,
487 date=self.date)
482
483 dsguard = None
484 if ui.configbool('rebase', 'singletransaction'):
485 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
486 with util.acceptintervention(dsguard):
487 newnode = concludenode(repo, revtoreuse, p1, self.external,
488 commitmsg=commitmsg,
489 extrafn=_makeextrafn(self.extrafns),
490 editor=editor,
491 keepbranches=self.keepbranchesf,
492 date=self.date)
488 493 if newnode is None:
489 494 newrev = self.dest
490 495 else:
491 496 newrev = repo[newnode].rev()
492 497 for oldrev in self.state.iterkeys():
493 498 if self.state[oldrev] > nullmerge:
494 499 self.state[oldrev] = newrev
495 500
496 501 if 'qtip' in repo.tags():
497 502 updatemq(repo, self.state, self.skipped, **opts)
498 503
499 504 # restore original working directory
500 505 # (we do this before stripping)
501 506 newwd = self.state.get(self.originalwd, self.originalwd)
502 507 if newwd == revprecursor:
503 508 newwd = self.obsoletenotrebased[self.originalwd]
504 509 elif newwd < 0:
505 510 # original directory is a parent of rebase set root or ignored
506 511 newwd = self.originalwd
507 512 if newwd not in [c.rev() for c in repo[None].parents()]:
508 513 ui.note(_("update back to initial working directory parent\n"))
509 514 hg.updaterepo(repo, newwd, False)
510 515
511 516 if not self.keepf:
512 517 collapsedas = None
513 518 if self.collapsef:
514 519 collapsedas = newnode
515 520 clearrebased(ui, repo, self.dest, self.state, self.skipped,
516 521 collapsedas)
517 522
518 523 clearstatus(repo)
519 524 clearcollapsemsg(repo)
520 525
521 526 ui.note(_("rebase completed\n"))
522 527 util.unlinkpath(repo.sjoin('undo'), ignoremissing=True)
523 528 if self.skipped:
524 529 skippedlen = len(self.skipped)
525 530 ui.note(_("%d revisions have been skipped\n") % skippedlen)
526 531
527 532 if (self.activebookmark and self.activebookmark in repo._bookmarks and
528 533 repo['.'].node() == repo._bookmarks[self.activebookmark]):
529 534 bookmarks.activate(repo, self.activebookmark)
530 535
531 536 @command('rebase',
532 537 [('s', 'source', '',
533 538 _('rebase the specified changeset and descendants'), _('REV')),
534 539 ('b', 'base', '',
535 540 _('rebase everything from branching point of specified changeset'),
536 541 _('REV')),
537 542 ('r', 'rev', [],
538 543 _('rebase these revisions'),
539 544 _('REV')),
540 545 ('d', 'dest', '',
541 546 _('rebase onto the specified changeset'), _('REV')),
542 547 ('', 'collapse', False, _('collapse the rebased changesets')),
543 548 ('m', 'message', '',
544 549 _('use text as collapse commit message'), _('TEXT')),
545 550 ('e', 'edit', False, _('invoke editor on commit messages')),
546 551 ('l', 'logfile', '',
547 552 _('read collapse commit message from file'), _('FILE')),
548 553 ('k', 'keep', False, _('keep original changesets')),
549 554 ('', 'keepbranches', False, _('keep original branch names')),
550 555 ('D', 'detach', False, _('(DEPRECATED)')),
551 556 ('i', 'interactive', False, _('(DEPRECATED)')),
552 557 ('t', 'tool', '', _('specify merge tool')),
553 558 ('c', 'continue', False, _('continue an interrupted rebase')),
554 559 ('a', 'abort', False, _('abort an interrupted rebase'))] +
555 560 templateopts,
556 561 _('[-s REV | -b REV] [-d REV] [OPTION]'))
557 562 def rebase(ui, repo, **opts):
558 563 """move changeset (and descendants) to a different branch
559 564
560 565 Rebase uses repeated merging to graft changesets from one part of
561 566 history (the source) onto another (the destination). This can be
562 567 useful for linearizing *local* changes relative to a master
563 568 development tree.
564 569
565 570 Published commits cannot be rebased (see :hg:`help phases`).
566 571 To copy commits, see :hg:`help graft`.
567 572
568 573 If you don't specify a destination changeset (``-d/--dest``), rebase
569 574 will use the same logic as :hg:`merge` to pick a destination. if
570 575 the current branch contains exactly one other head, the other head
571 576 is merged with by default. Otherwise, an explicit revision with
572 577 which to merge with must be provided. (destination changeset is not
573 578 modified by rebasing, but new changesets are added as its
574 579 descendants.)
575 580
576 581 Here are the ways to select changesets:
577 582
578 583 1. Explicitly select them using ``--rev``.
579 584
580 585 2. Use ``--source`` to select a root changeset and include all of its
581 586 descendants.
582 587
583 588 3. Use ``--base`` to select a changeset; rebase will find ancestors
584 589 and their descendants which are not also ancestors of the destination.
585 590
586 591 4. If you do not specify any of ``--rev``, ``source``, or ``--base``,
587 592 rebase will use ``--base .`` as above.
588 593
589 594 Rebase will destroy original changesets unless you use ``--keep``.
590 595 It will also move your bookmarks (even if you do).
591 596
592 597 Some changesets may be dropped if they do not contribute changes
593 598 (e.g. merges from the destination branch).
594 599
595 600 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
596 601 a named branch with two heads. You will need to explicitly specify source
597 602 and/or destination.
598 603
599 604 If you need to use a tool to automate merge/conflict decisions, you
600 605 can specify one with ``--tool``, see :hg:`help merge-tools`.
601 606 As a caveat: the tool will not be used to mediate when a file was
602 607 deleted, there is no hook presently available for this.
603 608
604 609 If a rebase is interrupted to manually resolve a conflict, it can be
605 610 continued with --continue/-c or aborted with --abort/-a.
606 611
607 612 .. container:: verbose
608 613
609 614 Examples:
610 615
611 616 - move "local changes" (current commit back to branching point)
612 617 to the current branch tip after a pull::
613 618
614 619 hg rebase
615 620
616 621 - move a single changeset to the stable branch::
617 622
618 623 hg rebase -r 5f493448 -d stable
619 624
620 625 - splice a commit and all its descendants onto another part of history::
621 626
622 627 hg rebase --source c0c3 --dest 4cf9
623 628
624 629 - rebase everything on a branch marked by a bookmark onto the
625 630 default branch::
626 631
627 632 hg rebase --base myfeature --dest default
628 633
629 634 - collapse a sequence of changes into a single commit::
630 635
631 636 hg rebase --collapse -r 1520:1525 -d .
632 637
633 638 - move a named branch while preserving its name::
634 639
635 640 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
636 641
637 642 Configuration Options:
638 643
639 644 You can make rebase require a destination if you set the following config
640 645 option::
641 646
642 647 [commands]
643 648 rebase.requiredest = True
644 649
645 650 By default, rebase will close the transaction after each commit. For
646 651 performance purposes, you can configure rebase to use a single transaction
647 652 across the entire rebase. WARNING: This setting introduces a significant
648 653 risk of losing the work you've done in a rebase if the rebase aborts
649 654 unexpectedly::
650 655
651 656 [rebase]
652 657 singletransaction = True
653 658
654 659 Return Values:
655 660
656 661 Returns 0 on success, 1 if nothing to rebase or there are
657 662 unresolved conflicts.
658 663
659 664 """
660 665 rbsrt = rebaseruntime(repo, ui, opts)
661 666
662 667 with repo.wlock(), repo.lock():
663 668 # Validate input and define rebasing points
664 669 destf = opts.get('dest', None)
665 670 srcf = opts.get('source', None)
666 671 basef = opts.get('base', None)
667 672 revf = opts.get('rev', [])
668 673 # search default destination in this space
669 674 # used in the 'hg pull --rebase' case, see issue 5214.
670 675 destspace = opts.get('_destspace')
671 676 contf = opts.get('continue')
672 677 abortf = opts.get('abort')
673 678 if opts.get('interactive'):
674 679 try:
675 680 if extensions.find('histedit'):
676 681 enablehistedit = ''
677 682 except KeyError:
678 683 enablehistedit = " --config extensions.histedit="
679 684 help = "hg%s help -e histedit" % enablehistedit
680 685 msg = _("interactive history editing is supported by the "
681 686 "'histedit' extension (see \"%s\")") % help
682 687 raise error.Abort(msg)
683 688
684 689 if rbsrt.collapsemsg and not rbsrt.collapsef:
685 690 raise error.Abort(
686 691 _('message can only be specified with collapse'))
687 692
688 693 if contf or abortf:
689 694 if contf and abortf:
690 695 raise error.Abort(_('cannot use both abort and continue'))
691 696 if rbsrt.collapsef:
692 697 raise error.Abort(
693 698 _('cannot use collapse with continue or abort'))
694 699 if srcf or basef or destf:
695 700 raise error.Abort(
696 701 _('abort and continue do not allow specifying revisions'))
697 702 if abortf and opts.get('tool', False):
698 703 ui.warn(_('tool option will be ignored\n'))
699 704 if contf:
700 705 ms = mergemod.mergestate.read(repo)
701 706 mergeutil.checkunresolved(ms)
702 707
703 708 retcode = rbsrt._prepareabortorcontinue(abortf)
704 709 if retcode is not None:
705 710 return retcode
706 711 else:
707 712 dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
708 713 destspace=destspace)
709 714 retcode = rbsrt._preparenewrebase(dest, rebaseset)
710 715 if retcode is not None:
711 716 return retcode
712 717
713 718 tr = None
714 if ui.configbool('rebase', 'singletransaction'):
719 dsguard = None
720
721 singletr = ui.configbool('rebase', 'singletransaction')
722 if singletr:
715 723 tr = repo.transaction('rebase')
716 724 with util.acceptintervention(tr):
717 rbsrt._performrebase(tr)
725 if singletr:
726 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
727 with util.acceptintervention(dsguard):
728 rbsrt._performrebase(tr)
718 729
719 730 rbsrt._finishrebase()
720 731
721 732 def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
722 733 destspace=None):
723 734 """use revisions argument to define destination and rebase set
724 735 """
725 736 if revf is None:
726 737 revf = []
727 738
728 739 # destspace is here to work around issues with `hg pull --rebase` see
729 740 # issue5214 for details
730 741 if srcf and basef:
731 742 raise error.Abort(_('cannot specify both a source and a base'))
732 743 if revf and basef:
733 744 raise error.Abort(_('cannot specify both a revision and a base'))
734 745 if revf and srcf:
735 746 raise error.Abort(_('cannot specify both a revision and a source'))
736 747
737 748 cmdutil.checkunfinished(repo)
738 749 cmdutil.bailifchanged(repo)
739 750
740 751 if ui.configbool('commands', 'rebase.requiredest') and not destf:
741 752 raise error.Abort(_('you must specify a destination'),
742 753 hint=_('use: hg rebase -d REV'))
743 754
744 755 if destf:
745 756 dest = scmutil.revsingle(repo, destf)
746 757
747 758 if revf:
748 759 rebaseset = scmutil.revrange(repo, revf)
749 760 if not rebaseset:
750 761 ui.status(_('empty "rev" revision set - nothing to rebase\n'))
751 762 return None, None
752 763 elif srcf:
753 764 src = scmutil.revrange(repo, [srcf])
754 765 if not src:
755 766 ui.status(_('empty "source" revision set - nothing to rebase\n'))
756 767 return None, None
757 768 rebaseset = repo.revs('(%ld)::', src)
758 769 assert rebaseset
759 770 else:
760 771 base = scmutil.revrange(repo, [basef or '.'])
761 772 if not base:
762 773 ui.status(_('empty "base" revision set - '
763 774 "can't compute rebase set\n"))
764 775 return None, None
765 776 if not destf:
766 777 dest = repo[_destrebase(repo, base, destspace=destspace)]
767 778 destf = str(dest)
768 779
769 780 roots = [] # selected children of branching points
770 781 bpbase = {} # {branchingpoint: [origbase]}
771 782 for b in base: # group bases by branching points
772 783 bp = repo.revs('ancestor(%d, %d)', b, dest).first()
773 784 bpbase[bp] = bpbase.get(bp, []) + [b]
774 785 if None in bpbase:
775 786 # emulate the old behavior, showing "nothing to rebase" (a better
776 787 # behavior may be abort with "cannot find branching point" error)
777 788 bpbase.clear()
778 789 for bp, bs in bpbase.iteritems(): # calculate roots
779 790 roots += list(repo.revs('children(%d) & ancestors(%ld)', bp, bs))
780 791
781 792 rebaseset = repo.revs('%ld::', roots)
782 793
783 794 if not rebaseset:
784 795 # transform to list because smartsets are not comparable to
785 796 # lists. This should be improved to honor laziness of
786 797 # smartset.
787 798 if list(base) == [dest.rev()]:
788 799 if basef:
789 800 ui.status(_('nothing to rebase - %s is both "base"'
790 801 ' and destination\n') % dest)
791 802 else:
792 803 ui.status(_('nothing to rebase - working directory '
793 804 'parent is also destination\n'))
794 805 elif not repo.revs('%ld - ::%d', base, dest):
795 806 if basef:
796 807 ui.status(_('nothing to rebase - "base" %s is '
797 808 'already an ancestor of destination '
798 809 '%s\n') %
799 810 ('+'.join(str(repo[r]) for r in base),
800 811 dest))
801 812 else:
802 813 ui.status(_('nothing to rebase - working '
803 814 'directory parent is already an '
804 815 'ancestor of destination %s\n') % dest)
805 816 else: # can it happen?
806 817 ui.status(_('nothing to rebase from %s to %s\n') %
807 818 ('+'.join(str(repo[r]) for r in base), dest))
808 819 return None, None
809 820
810 821 if not destf:
811 822 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
812 823 destf = str(dest)
813 824
814 825 return dest, rebaseset
815 826
816 827 def externalparent(repo, state, destancestors):
817 828 """Return the revision that should be used as the second parent
818 829 when the revisions in state is collapsed on top of destancestors.
819 830 Abort if there is more than one parent.
820 831 """
821 832 parents = set()
822 833 source = min(state)
823 834 for rev in state:
824 835 if rev == source:
825 836 continue
826 837 for p in repo[rev].parents():
827 838 if (p.rev() not in state
828 839 and p.rev() not in destancestors):
829 840 parents.add(p.rev())
830 841 if not parents:
831 842 return nullrev
832 843 if len(parents) == 1:
833 844 return parents.pop()
834 845 raise error.Abort(_('unable to collapse on top of %s, there is more '
835 846 'than one external parent: %s') %
836 847 (max(destancestors),
837 848 ', '.join(str(p) for p in sorted(parents))))
838 849
839 850 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
840 851 keepbranches=False, date=None):
841 852 '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
842 853 but also store useful information in extra.
843 854 Return node of committed revision.'''
844 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
845 try:
855 dsguard = util.nullcontextmanager()
856 if not repo.ui.configbool('rebase', 'singletransaction'):
857 dsguard = dirstateguard.dirstateguard(repo, 'rebase')
858 with dsguard:
846 859 repo.setparents(repo[p1].node(), repo[p2].node())
847 860 ctx = repo[rev]
848 861 if commitmsg is None:
849 862 commitmsg = ctx.description()
850 863 keepbranch = keepbranches and repo[p1].branch() != ctx.branch()
851 864 extra = {'rebase_source': ctx.hex()}
852 865 if extrafn:
853 866 extrafn(ctx, extra)
854 867
855 868 destphase = max(ctx.phase(), phases.draft)
856 869 overrides = {('phases', 'new-commit'): destphase}
857 870 with repo.ui.configoverride(overrides, 'rebase'):
858 871 if keepbranch:
859 872 repo.ui.setconfig('ui', 'allowemptycommit', True)
860 873 # Commit might fail if unresolved files exist
861 874 if date is None:
862 875 date = ctx.date()
863 876 newnode = repo.commit(text=commitmsg, user=ctx.user(),
864 877 date=date, extra=extra, editor=editor)
865 878
866 879 repo.dirstate.setbranch(repo[newnode].branch())
867 dsguard.close()
868 880 return newnode
869 finally:
870 release(dsguard)
871 881
872 882 def rebasenode(repo, rev, p1, base, state, collapse, dest):
873 883 'Rebase a single revision rev on top of p1 using base as merge ancestor'
874 884 # Merge phase
875 885 # Update to destination and merge it with local
876 886 if repo['.'].rev() != p1:
877 887 repo.ui.debug(" update to %d:%s\n" % (p1, repo[p1]))
878 888 mergemod.update(repo, p1, False, True)
879 889 else:
880 890 repo.ui.debug(" already in destination\n")
881 891 repo.dirstate.write(repo.currenttransaction())
882 892 repo.ui.debug(" merge against %d:%s\n" % (rev, repo[rev]))
883 893 if base is not None:
884 894 repo.ui.debug(" detach base %d:%s\n" % (base, repo[base]))
885 895 # When collapsing in-place, the parent is the common ancestor, we
886 896 # have to allow merging with it.
887 897 stats = mergemod.update(repo, rev, True, True, base, collapse,
888 898 labels=['dest', 'source'])
889 899 if collapse:
890 900 copies.duplicatecopies(repo, rev, dest)
891 901 else:
892 902 # If we're not using --collapse, we need to
893 903 # duplicate copies between the revision we're
894 904 # rebasing and its first parent, but *not*
895 905 # duplicate any copies that have already been
896 906 # performed in the destination.
897 907 p1rev = repo[rev].p1().rev()
898 908 copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
899 909 return stats
900 910
901 911 def adjustdest(repo, rev, dest, state):
902 912 """adjust rebase destination given the current rebase state
903 913
904 914 rev is what is being rebased. Return a list of two revs, which are the
905 915 adjusted destinations for rev's p1 and p2, respectively. If a parent is
906 916 nullrev, return dest without adjustment for it.
907 917
908 918 For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
909 919 and E's destination will be adjusted from F to B1.
910 920
911 921 B1 <- written during rebasing B
912 922 |
913 923 F <- original destination of B, E
914 924 |
915 925 | E <- rev, which is being rebased
916 926 | |
917 927 | D <- prev, one parent of rev being checked
918 928 | |
919 929 | x <- skipped, ex. no successor or successor in (::dest)
920 930 | |
921 931 | C
922 932 | |
923 933 | B <- rebased as B1
924 934 |/
925 935 A
926 936
927 937 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
928 938 first move C to C1, G to G1, and when it's checking H, the adjusted
929 939 destinations will be [C1, G1].
930 940
931 941 H C1 G1
932 942 /| | /
933 943 F G |/
934 944 K | | -> K
935 945 | C D |
936 946 | |/ |
937 947 | B | ...
938 948 |/ |/
939 949 A A
940 950 """
941 951 result = []
942 952 for prev in repo.changelog.parentrevs(rev):
943 953 adjusted = dest
944 954 if prev != nullrev:
945 955 # pick already rebased revs from state
946 956 source = [s for s, d in state.items() if d > 0]
947 957 candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
948 958 if candidate is not None:
949 959 adjusted = state[candidate]
950 960 result.append(adjusted)
951 961 return result
952 962
953 963 def nearestrebased(repo, rev, state):
954 964 """return the nearest ancestors of rev in the rebase result"""
955 965 rebased = [r for r in state if state[r] > nullmerge]
956 966 candidates = repo.revs('max(%ld and (::%d))', rebased, rev)
957 967 if candidates:
958 968 return state[candidates.first()]
959 969 else:
960 970 return None
961 971
962 972 def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
963 973 """
964 974 Abort if rebase will create divergence or rebase is noop because of markers
965 975
966 976 `rebaseobsrevs`: set of obsolete revision in source
967 977 `rebasesetrevs`: set of revisions to be rebased from source
968 978 `rebaseobsskipped`: set of revisions from source skipped because they have
969 979 successors in destination
970 980 """
971 981 # Obsolete node with successors not in dest leads to divergence
972 982 divergenceok = ui.configbool('experimental',
973 983 'allowdivergence')
974 984 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
975 985
976 986 if divergencebasecandidates and not divergenceok:
977 987 divhashes = (str(repo[r])
978 988 for r in divergencebasecandidates)
979 989 msg = _("this rebase will cause "
980 990 "divergences from: %s")
981 991 h = _("to force the rebase please set "
982 992 "experimental.allowdivergence=True")
983 993 raise error.Abort(msg % (",".join(divhashes),), hint=h)
984 994
985 995 def defineparents(repo, rev, dest, state, destancestors,
986 996 obsoletenotrebased):
987 997 'Return the new parent relationship of the revision that will be rebased'
988 998 parents = repo[rev].parents()
989 999 p1 = p2 = nullrev
990 1000 rp1 = None
991 1001
992 1002 p1n = parents[0].rev()
993 1003 if p1n in destancestors:
994 1004 p1 = dest
995 1005 elif p1n in state:
996 1006 if state[p1n] == nullmerge:
997 1007 p1 = dest
998 1008 elif state[p1n] in revskipped:
999 1009 p1 = nearestrebased(repo, p1n, state)
1000 1010 if p1 is None:
1001 1011 p1 = dest
1002 1012 else:
1003 1013 p1 = state[p1n]
1004 1014 else: # p1n external
1005 1015 p1 = dest
1006 1016 p2 = p1n
1007 1017
1008 1018 if len(parents) == 2 and parents[1].rev() not in destancestors:
1009 1019 p2n = parents[1].rev()
1010 1020 # interesting second parent
1011 1021 if p2n in state:
1012 1022 if p1 == dest: # p1n in destancestors or external
1013 1023 p1 = state[p2n]
1014 1024 if p1 == revprecursor:
1015 1025 rp1 = obsoletenotrebased[p2n]
1016 1026 elif state[p2n] in revskipped:
1017 1027 p2 = nearestrebased(repo, p2n, state)
1018 1028 if p2 is None:
1019 1029 # no ancestors rebased yet, detach
1020 1030 p2 = dest
1021 1031 else:
1022 1032 p2 = state[p2n]
1023 1033 else: # p2n external
1024 1034 if p2 != nullrev: # p1n external too => rev is a merged revision
1025 1035 raise error.Abort(_('cannot use revision %d as base, result '
1026 1036 'would have 3 parents') % rev)
1027 1037 p2 = p2n
1028 1038 repo.ui.debug(" future parents are %d and %d\n" %
1029 1039 (repo[rp1 or p1].rev(), repo[p2].rev()))
1030 1040
1031 1041 if not any(p.rev() in state for p in parents):
1032 1042 # Case (1) root changeset of a non-detaching rebase set.
1033 1043 # Let the merge mechanism find the base itself.
1034 1044 base = None
1035 1045 elif not repo[rev].p2():
1036 1046 # Case (2) detaching the node with a single parent, use this parent
1037 1047 base = repo[rev].p1().rev()
1038 1048 else:
1039 1049 # Assuming there is a p1, this is the case where there also is a p2.
1040 1050 # We are thus rebasing a merge and need to pick the right merge base.
1041 1051 #
1042 1052 # Imagine we have:
1043 1053 # - M: current rebase revision in this step
1044 1054 # - A: one parent of M
1045 1055 # - B: other parent of M
1046 1056 # - D: destination of this merge step (p1 var)
1047 1057 #
1048 1058 # Consider the case where D is a descendant of A or B and the other is
1049 1059 # 'outside'. In this case, the right merge base is the D ancestor.
1050 1060 #
1051 1061 # An informal proof, assuming A is 'outside' and B is the D ancestor:
1052 1062 #
1053 1063 # If we pick B as the base, the merge involves:
1054 1064 # - changes from B to M (actual changeset payload)
1055 1065 # - changes from B to D (induced by rebase) as D is a rebased
1056 1066 # version of B)
1057 1067 # Which exactly represent the rebase operation.
1058 1068 #
1059 1069 # If we pick A as the base, the merge involves:
1060 1070 # - changes from A to M (actual changeset payload)
1061 1071 # - changes from A to D (with include changes between unrelated A and B
1062 1072 # plus changes induced by rebase)
1063 1073 # Which does not represent anything sensible and creates a lot of
1064 1074 # conflicts. A is thus not the right choice - B is.
1065 1075 #
1066 1076 # Note: The base found in this 'proof' is only correct in the specified
1067 1077 # case. This base does not make sense if is not D a descendant of A or B
1068 1078 # or if the other is not parent 'outside' (especially not if the other
1069 1079 # parent has been rebased). The current implementation does not
1070 1080 # make it feasible to consider different cases separately. In these
1071 1081 # other cases we currently just leave it to the user to correctly
1072 1082 # resolve an impossible merge using a wrong ancestor.
1073 1083 #
1074 1084 # xx, p1 could be -4, and both parents could probably be -4...
1075 1085 for p in repo[rev].parents():
1076 1086 if state.get(p.rev()) == p1:
1077 1087 base = p.rev()
1078 1088 break
1079 1089 else: # fallback when base not found
1080 1090 base = None
1081 1091
1082 1092 # Raise because this function is called wrong (see issue 4106)
1083 1093 raise AssertionError('no base found to rebase on '
1084 1094 '(defineparents called wrong)')
1085 1095 return rp1 or p1, p2, base
1086 1096
1087 1097 def isagitpatch(repo, patchname):
1088 1098 'Return true if the given patch is in git format'
1089 1099 mqpatch = os.path.join(repo.mq.path, patchname)
1090 1100 for line in patch.linereader(file(mqpatch, 'rb')):
1091 1101 if line.startswith('diff --git'):
1092 1102 return True
1093 1103 return False
1094 1104
1095 1105 def updatemq(repo, state, skipped, **opts):
1096 1106 'Update rebased mq patches - finalize and then import them'
1097 1107 mqrebase = {}
1098 1108 mq = repo.mq
1099 1109 original_series = mq.fullseries[:]
1100 1110 skippedpatches = set()
1101 1111
1102 1112 for p in mq.applied:
1103 1113 rev = repo[p.node].rev()
1104 1114 if rev in state:
1105 1115 repo.ui.debug('revision %d is an mq patch (%s), finalize it.\n' %
1106 1116 (rev, p.name))
1107 1117 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1108 1118 else:
1109 1119 # Applied but not rebased, not sure this should happen
1110 1120 skippedpatches.add(p.name)
1111 1121
1112 1122 if mqrebase:
1113 1123 mq.finish(repo, mqrebase.keys())
1114 1124
1115 1125 # We must start import from the newest revision
1116 1126 for rev in sorted(mqrebase, reverse=True):
1117 1127 if rev not in skipped:
1118 1128 name, isgit = mqrebase[rev]
1119 1129 repo.ui.note(_('updating mq patch %s to %s:%s\n') %
1120 1130 (name, state[rev], repo[state[rev]]))
1121 1131 mq.qimport(repo, (), patchname=name, git=isgit,
1122 1132 rev=[str(state[rev])])
1123 1133 else:
1124 1134 # Rebased and skipped
1125 1135 skippedpatches.add(mqrebase[rev][0])
1126 1136
1127 1137 # Patches were either applied and rebased and imported in
1128 1138 # order, applied and removed or unapplied. Discard the removed
1129 1139 # ones while preserving the original series order and guards.
1130 1140 newseries = [s for s in original_series
1131 1141 if mq.guard_re.split(s, 1)[0] not in skippedpatches]
1132 1142 mq.fullseries[:] = newseries
1133 1143 mq.seriesdirty = True
1134 1144 mq.savedirty()
1135 1145
1136 1146 def storecollapsemsg(repo, collapsemsg):
1137 1147 'Store the collapse message to allow recovery'
1138 1148 collapsemsg = collapsemsg or ''
1139 1149 f = repo.vfs("last-message.txt", "w")
1140 1150 f.write("%s\n" % collapsemsg)
1141 1151 f.close()
1142 1152
1143 1153 def clearcollapsemsg(repo):
1144 1154 'Remove collapse message file'
1145 1155 repo.vfs.unlinkpath("last-message.txt", ignoremissing=True)
1146 1156
1147 1157 def restorecollapsemsg(repo, isabort):
1148 1158 'Restore previously stored collapse message'
1149 1159 try:
1150 1160 f = repo.vfs("last-message.txt")
1151 1161 collapsemsg = f.readline().strip()
1152 1162 f.close()
1153 1163 except IOError as err:
1154 1164 if err.errno != errno.ENOENT:
1155 1165 raise
1156 1166 if isabort:
1157 1167 # Oh well, just abort like normal
1158 1168 collapsemsg = ''
1159 1169 else:
1160 1170 raise error.Abort(_('missing .hg/last-message.txt for rebase'))
1161 1171 return collapsemsg
1162 1172
1163 1173 def clearstatus(repo):
1164 1174 'Remove the status files'
1165 1175 _clearrebasesetvisibiliy(repo)
1166 1176 # Make sure the active transaction won't write the state file
1167 1177 tr = repo.currenttransaction()
1168 1178 if tr:
1169 1179 tr.removefilegenerator('rebasestate')
1170 1180 repo.vfs.unlinkpath("rebasestate", ignoremissing=True)
1171 1181
1172 1182 def needupdate(repo, state):
1173 1183 '''check whether we should `update --clean` away from a merge, or if
1174 1184 somehow the working dir got forcibly updated, e.g. by older hg'''
1175 1185 parents = [p.rev() for p in repo[None].parents()]
1176 1186
1177 1187 # Are we in a merge state at all?
1178 1188 if len(parents) < 2:
1179 1189 return False
1180 1190
1181 1191 # We should be standing on the first as-of-yet unrebased commit.
1182 1192 firstunrebased = min([old for old, new in state.iteritems()
1183 1193 if new == nullrev])
1184 1194 if firstunrebased in parents:
1185 1195 return True
1186 1196
1187 1197 return False
1188 1198
1189 1199 def abort(repo, originalwd, dest, state, activebookmark=None):
1190 1200 '''Restore the repository to its original state. Additional args:
1191 1201
1192 1202 activebookmark: the name of the bookmark that should be active after the
1193 1203 restore'''
1194 1204
1195 1205 try:
1196 1206 # If the first commits in the rebased set get skipped during the rebase,
1197 1207 # their values within the state mapping will be the dest rev id. The
1198 1208 # dstates list must must not contain the dest rev (issue4896)
1199 1209 dstates = [s for s in state.values() if s >= 0 and s != dest]
1200 1210 immutable = [d for d in dstates if not repo[d].mutable()]
1201 1211 cleanup = True
1202 1212 if immutable:
1203 1213 repo.ui.warn(_("warning: can't clean up public changesets %s\n")
1204 1214 % ', '.join(str(repo[r]) for r in immutable),
1205 1215 hint=_("see 'hg help phases' for details"))
1206 1216 cleanup = False
1207 1217
1208 1218 descendants = set()
1209 1219 if dstates:
1210 1220 descendants = set(repo.changelog.descendants(dstates))
1211 1221 if descendants - set(dstates):
1212 1222 repo.ui.warn(_("warning: new changesets detected on destination "
1213 1223 "branch, can't strip\n"))
1214 1224 cleanup = False
1215 1225
1216 1226 if cleanup:
1217 1227 shouldupdate = False
1218 1228 rebased = filter(lambda x: x >= 0 and x != dest, state.values())
1219 1229 if rebased:
1220 1230 strippoints = [
1221 1231 c.node() for c in repo.set('roots(%ld)', rebased)]
1222 1232
1223 1233 updateifonnodes = set(rebased)
1224 1234 updateifonnodes.add(dest)
1225 1235 updateifonnodes.add(originalwd)
1226 1236 shouldupdate = repo['.'].rev() in updateifonnodes
1227 1237
1228 1238 # Update away from the rebase if necessary
1229 1239 if shouldupdate or needupdate(repo, state):
1230 1240 mergemod.update(repo, originalwd, False, True)
1231 1241
1232 1242 # Strip from the first rebased revision
1233 1243 if rebased:
1234 1244 # no backup of rebased cset versions needed
1235 1245 repair.strip(repo.ui, repo, strippoints)
1236 1246
1237 1247 if activebookmark and activebookmark in repo._bookmarks:
1238 1248 bookmarks.activate(repo, activebookmark)
1239 1249
1240 1250 finally:
1241 1251 clearstatus(repo)
1242 1252 clearcollapsemsg(repo)
1243 1253 repo.ui.warn(_('rebase aborted\n'))
1244 1254 return 0
1245 1255
1246 1256 def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
1247 1257 '''Define which revisions are going to be rebased and where
1248 1258
1249 1259 repo: repo
1250 1260 dest: context
1251 1261 rebaseset: set of rev
1252 1262 '''
1253 1263 originalwd = repo['.'].rev()
1254 1264 _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
1255 1265
1256 1266 # This check isn't strictly necessary, since mq detects commits over an
1257 1267 # applied patch. But it prevents messing up the working directory when
1258 1268 # a partially completed rebase is blocked by mq.
1259 1269 if 'qtip' in repo.tags() and (dest.node() in
1260 1270 [s.node for s in repo.mq.applied]):
1261 1271 raise error.Abort(_('cannot rebase onto an applied mq patch'))
1262 1272
1263 1273 roots = list(repo.set('roots(%ld)', rebaseset))
1264 1274 if not roots:
1265 1275 raise error.Abort(_('no matching revisions'))
1266 1276 roots.sort()
1267 1277 state = dict.fromkeys(rebaseset, revtodo)
1268 1278 detachset = set()
1269 1279 emptyrebase = True
1270 1280 for root in roots:
1271 1281 commonbase = root.ancestor(dest)
1272 1282 if commonbase == root:
1273 1283 raise error.Abort(_('source is ancestor of destination'))
1274 1284 if commonbase == dest:
1275 1285 wctx = repo[None]
1276 1286 if dest == wctx.p1():
1277 1287 # when rebasing to '.', it will use the current wd branch name
1278 1288 samebranch = root.branch() == wctx.branch()
1279 1289 else:
1280 1290 samebranch = root.branch() == dest.branch()
1281 1291 if not collapse and samebranch and dest in root.parents():
1282 1292 # mark the revision as done by setting its new revision
1283 1293 # equal to its old (current) revisions
1284 1294 state[root.rev()] = root.rev()
1285 1295 repo.ui.debug('source is a child of destination\n')
1286 1296 continue
1287 1297
1288 1298 emptyrebase = False
1289 1299 repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
1290 1300 # Rebase tries to turn <dest> into a parent of <root> while
1291 1301 # preserving the number of parents of rebased changesets:
1292 1302 #
1293 1303 # - A changeset with a single parent will always be rebased as a
1294 1304 # changeset with a single parent.
1295 1305 #
1296 1306 # - A merge will be rebased as merge unless its parents are both
1297 1307 # ancestors of <dest> or are themselves in the rebased set and
1298 1308 # pruned while rebased.
1299 1309 #
1300 1310 # If one parent of <root> is an ancestor of <dest>, the rebased
1301 1311 # version of this parent will be <dest>. This is always true with
1302 1312 # --base option.
1303 1313 #
1304 1314 # Otherwise, we need to *replace* the original parents with
1305 1315 # <dest>. This "detaches" the rebased set from its former location
1306 1316 # and rebases it onto <dest>. Changes introduced by ancestors of
1307 1317 # <root> not common with <dest> (the detachset, marked as
1308 1318 # nullmerge) are "removed" from the rebased changesets.
1309 1319 #
1310 1320 # - If <root> has a single parent, set it to <dest>.
1311 1321 #
1312 1322 # - If <root> is a merge, we cannot decide which parent to
1313 1323 # replace, the rebase operation is not clearly defined.
1314 1324 #
1315 1325 # The table below sums up this behavior:
1316 1326 #
1317 1327 # +------------------+----------------------+-------------------------+
1318 1328 # | | one parent | merge |
1319 1329 # +------------------+----------------------+-------------------------+
1320 1330 # | parent in | new parent is <dest> | parents in ::<dest> are |
1321 1331 # | ::<dest> | | remapped to <dest> |
1322 1332 # +------------------+----------------------+-------------------------+
1323 1333 # | unrelated source | new parent is <dest> | ambiguous, abort |
1324 1334 # +------------------+----------------------+-------------------------+
1325 1335 #
1326 1336 # The actual abort is handled by `defineparents`
1327 1337 if len(root.parents()) <= 1:
1328 1338 # ancestors of <root> not ancestors of <dest>
1329 1339 detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
1330 1340 [root.rev()]))
1331 1341 if emptyrebase:
1332 1342 return None
1333 1343 for rev in sorted(state):
1334 1344 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
1335 1345 # if all parents of this revision are done, then so is this revision
1336 1346 if parents and all((state.get(p) == p for p in parents)):
1337 1347 state[rev] = rev
1338 1348 for r in detachset:
1339 1349 if r not in state:
1340 1350 state[r] = nullmerge
1341 1351 if len(roots) > 1:
1342 1352 # If we have multiple roots, we may have "hole" in the rebase set.
1343 1353 # Rebase roots that descend from those "hole" should not be detached as
1344 1354 # other root are. We use the special `revignored` to inform rebase that
1345 1355 # the revision should be ignored but that `defineparents` should search
1346 1356 # a rebase destination that make sense regarding rebased topology.
1347 1357 rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
1348 1358 for ignored in set(rebasedomain) - set(rebaseset):
1349 1359 state[ignored] = revignored
1350 1360 for r in obsoletenotrebased:
1351 1361 if obsoletenotrebased[r] is None:
1352 1362 state[r] = revpruned
1353 1363 else:
1354 1364 state[r] = revprecursor
1355 1365 return originalwd, dest.rev(), state
1356 1366
1357 1367 def clearrebased(ui, repo, dest, state, skipped, collapsedas=None):
1358 1368 """dispose of rebased revision at the end of the rebase
1359 1369
1360 1370 If `collapsedas` is not None, the rebase was a collapse whose result if the
1361 1371 `collapsedas` node."""
1362 1372 tonode = repo.changelog.node
1363 1373 # Move bookmark of skipped nodes to destination. This cannot be handled
1364 1374 # by scmutil.cleanupnodes since it will treat rev as removed (no successor)
1365 1375 # and move bookmark backwards.
1366 1376 bmchanges = [(name, tonode(max(adjustdest(repo, rev, dest, state))))
1367 1377 for rev in skipped
1368 1378 for name in repo.nodebookmarks(tonode(rev))]
1369 1379 if bmchanges:
1370 1380 with repo.transaction('rebase') as tr:
1371 1381 repo._bookmarks.applychanges(repo, tr, bmchanges)
1372 1382 mapping = {}
1373 1383 for rev, newrev in sorted(state.items()):
1374 1384 if newrev >= 0 and newrev != rev:
1375 1385 if rev in skipped:
1376 1386 succs = ()
1377 1387 elif collapsedas is not None:
1378 1388 succs = (collapsedas,)
1379 1389 else:
1380 1390 succs = (tonode(newrev),)
1381 1391 mapping[tonode(rev)] = succs
1382 1392 scmutil.cleanupnodes(repo, mapping, 'rebase')
1383 1393
1384 1394 def pullrebase(orig, ui, repo, *args, **opts):
1385 1395 'Call rebase after pull if the latter has been invoked with --rebase'
1386 1396 ret = None
1387 1397 if opts.get('rebase'):
1388 1398 if ui.configbool('commands', 'rebase.requiredest'):
1389 1399 msg = _('rebase destination required by configuration')
1390 1400 hint = _('use hg pull followed by hg rebase -d DEST')
1391 1401 raise error.Abort(msg, hint=hint)
1392 1402
1393 1403 with repo.wlock(), repo.lock():
1394 1404 if opts.get('update'):
1395 1405 del opts['update']
1396 1406 ui.debug('--update and --rebase are not compatible, ignoring '
1397 1407 'the update flag\n')
1398 1408
1399 1409 cmdutil.checkunfinished(repo)
1400 1410 cmdutil.bailifchanged(repo, hint=_('cannot pull with rebase: '
1401 1411 'please commit or shelve your changes first'))
1402 1412
1403 1413 revsprepull = len(repo)
1404 1414 origpostincoming = commands.postincoming
1405 1415 def _dummy(*args, **kwargs):
1406 1416 pass
1407 1417 commands.postincoming = _dummy
1408 1418 try:
1409 1419 ret = orig(ui, repo, *args, **opts)
1410 1420 finally:
1411 1421 commands.postincoming = origpostincoming
1412 1422 revspostpull = len(repo)
1413 1423 if revspostpull > revsprepull:
1414 1424 # --rev option from pull conflict with rebase own --rev
1415 1425 # dropping it
1416 1426 if 'rev' in opts:
1417 1427 del opts['rev']
1418 1428 # positional argument from pull conflicts with rebase's own
1419 1429 # --source.
1420 1430 if 'source' in opts:
1421 1431 del opts['source']
1422 1432 # revsprepull is the len of the repo, not revnum of tip.
1423 1433 destspace = list(repo.changelog.revs(start=revsprepull))
1424 1434 opts['_destspace'] = destspace
1425 1435 try:
1426 1436 rebase(ui, repo, **opts)
1427 1437 except error.NoMergeDestAbort:
1428 1438 # we can maybe update instead
1429 1439 rev, _a, _b = destutil.destupdate(repo)
1430 1440 if rev == repo['.'].rev():
1431 1441 ui.status(_('nothing to rebase\n'))
1432 1442 else:
1433 1443 ui.status(_('nothing to rebase - updating instead\n'))
1434 1444 # not passing argument to get the bare update behavior
1435 1445 # with warning and trumpets
1436 1446 commands.update(ui, repo)
1437 1447 else:
1438 1448 if opts.get('tool'):
1439 1449 raise error.Abort(_('--tool can only be used with --rebase'))
1440 1450 ret = orig(ui, repo, *args, **opts)
1441 1451
1442 1452 return ret
1443 1453
1444 1454 def _setrebasesetvisibility(repo, revs):
1445 1455 """store the currently rebased set on the repo object
1446 1456
1447 1457 This is used by another function to prevent rebased revision to because
1448 1458 hidden (see issue4504)"""
1449 1459 repo = repo.unfiltered()
1450 1460 repo._rebaseset = revs
1451 1461 # invalidate cache if visibility changes
1452 1462 hiddens = repo.filteredrevcache.get('visible', set())
1453 1463 if revs & hiddens:
1454 1464 repo.invalidatevolatilesets()
1455 1465
1456 1466 def _clearrebasesetvisibiliy(repo):
1457 1467 """remove rebaseset data from the repo"""
1458 1468 repo = repo.unfiltered()
1459 1469 if '_rebaseset' in vars(repo):
1460 1470 del repo._rebaseset
1461 1471
1462 1472 def _rebasedvisible(orig, repo):
1463 1473 """ensure rebased revs stay visible (see issue4504)"""
1464 1474 blockers = orig(repo)
1465 1475 blockers.update(getattr(repo, '_rebaseset', ()))
1466 1476 return blockers
1467 1477
1468 1478 def _filterobsoleterevs(repo, revs):
1469 1479 """returns a set of the obsolete revisions in revs"""
1470 1480 return set(r for r in revs if repo[r].obsolete())
1471 1481
1472 1482 def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
1473 1483 """return a mapping obsolete => successor for all obsolete nodes to be
1474 1484 rebased that have a successors in the destination
1475 1485
1476 1486 obsolete => None entries in the mapping indicate nodes with no successor"""
1477 1487 obsoletenotrebased = {}
1478 1488
1479 1489 # Build a mapping successor => obsolete nodes for the obsolete
1480 1490 # nodes to be rebased
1481 1491 allsuccessors = {}
1482 1492 cl = repo.changelog
1483 1493 for r in rebaseobsrevs:
1484 1494 node = cl.node(r)
1485 1495 for s in obsutil.allsuccessors(repo.obsstore, [node]):
1486 1496 try:
1487 1497 allsuccessors[cl.rev(s)] = cl.rev(node)
1488 1498 except LookupError:
1489 1499 pass
1490 1500
1491 1501 if allsuccessors:
1492 1502 # Look for successors of obsolete nodes to be rebased among
1493 1503 # the ancestors of dest
1494 1504 ancs = cl.ancestors([dest],
1495 1505 stoprev=min(allsuccessors),
1496 1506 inclusive=True)
1497 1507 for s in allsuccessors:
1498 1508 if s in ancs:
1499 1509 obsoletenotrebased[allsuccessors[s]] = s
1500 1510 elif (s == allsuccessors[s] and
1501 1511 allsuccessors.values().count(s) == 1):
1502 1512 # plain prune
1503 1513 obsoletenotrebased[s] = None
1504 1514
1505 1515 return obsoletenotrebased
1506 1516
1507 1517 def summaryhook(ui, repo):
1508 1518 if not repo.vfs.exists('rebasestate'):
1509 1519 return
1510 1520 try:
1511 1521 rbsrt = rebaseruntime(repo, ui, {})
1512 1522 rbsrt.restorestatus()
1513 1523 state = rbsrt.state
1514 1524 except error.RepoLookupError:
1515 1525 # i18n: column positioning for "hg summary"
1516 1526 msg = _('rebase: (use "hg rebase --abort" to clear broken state)\n')
1517 1527 ui.write(msg)
1518 1528 return
1519 1529 numrebased = len([i for i in state.itervalues() if i >= 0])
1520 1530 # i18n: column positioning for "hg summary"
1521 1531 ui.write(_('rebase: %s, %s (rebase --continue)\n') %
1522 1532 (ui.label(_('%d rebased'), 'rebase.rebased') % numrebased,
1523 1533 ui.label(_('%d remaining'), 'rebase.remaining') %
1524 1534 (len(state) - numrebased)))
1525 1535
1526 1536 def uisetup(ui):
1527 1537 #Replace pull with a decorator to provide --rebase option
1528 1538 entry = extensions.wrapcommand(commands.table, 'pull', pullrebase)
1529 1539 entry[1].append(('', 'rebase', None,
1530 1540 _("rebase working directory to branch head")))
1531 1541 entry[1].append(('t', 'tool', '',
1532 1542 _("specify merge tool for rebase")))
1533 1543 cmdutil.summaryhooks.add('rebase', summaryhook)
1534 1544 cmdutil.unfinishedstates.append(
1535 1545 ['rebasestate', False, False, _('rebase in progress'),
1536 1546 _("use 'hg rebase --continue' or 'hg rebase --abort'")])
1537 1547 cmdutil.afterresolvedstates.append(
1538 1548 ['rebasestate', _('hg rebase --continue')])
1539 1549 # ensure rebased rev are not hidden
1540 1550 extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
@@ -1,68 +1,78 b''
1 1 # dirstateguard.py - class to allow restoring dirstate after failure
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 __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11
12 12 from . import (
13 13 error,
14 14 )
15 15
16 16 class dirstateguard(object):
17 17 '''Restore dirstate at unexpected failure.
18 18
19 19 At the construction, this class does:
20 20
21 21 - write current ``repo.dirstate`` out, and
22 22 - save ``.hg/dirstate`` into the backup file
23 23
24 24 This restores ``.hg/dirstate`` from backup file, if ``release()``
25 25 is invoked before ``close()``.
26 26
27 27 This just removes the backup file at ``close()`` before ``release()``.
28 28 '''
29 29
30 30 def __init__(self, repo, name):
31 31 self._repo = repo
32 32 self._active = False
33 33 self._closed = False
34 34 self._backupname = 'dirstate.backup.%s.%d' % (name, id(self))
35 35 repo.dirstate.savebackup(repo.currenttransaction(), self._backupname)
36 36 self._active = True
37 37
38 38 def __del__(self):
39 39 if self._active: # still active
40 40 # this may occur, even if this class is used correctly:
41 41 # for example, releasing other resources like transaction
42 42 # may raise exception before ``dirstateguard.release`` in
43 43 # ``release(tr, ....)``.
44 44 self._abort()
45 45
46 def __enter__(self):
47 return self
48
49 def __exit__(self, exc_type, exc_val, exc_tb):
50 try:
51 if exc_type is None:
52 self.close()
53 finally:
54 self.release()
55
46 56 def close(self):
47 57 if not self._active: # already inactivated
48 58 msg = (_("can't close already inactivated backup: %s")
49 59 % self._backupname)
50 60 raise error.Abort(msg)
51 61
52 62 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
53 63 self._backupname)
54 64 self._active = False
55 65 self._closed = True
56 66
57 67 def _abort(self):
58 68 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
59 69 self._backupname)
60 70 self._active = False
61 71
62 72 def release(self):
63 73 if not self._closed:
64 74 if not self._active: # already inactivated
65 75 msg = (_("can't release already inactivated backup: %s")
66 76 % self._backupname)
67 77 raise error.Abort(msg)
68 78 self._abort()
@@ -1,3696 +1,3700 b''
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import codecs
21 21 import collections
22 22 import contextlib
23 23 import datetime
24 24 import errno
25 25 import gc
26 26 import hashlib
27 27 import imp
28 28 import os
29 29 import platform as pyplatform
30 30 import re as remod
31 31 import shutil
32 32 import signal
33 33 import socket
34 34 import stat
35 35 import string
36 36 import subprocess
37 37 import sys
38 38 import tempfile
39 39 import textwrap
40 40 import time
41 41 import traceback
42 42 import warnings
43 43 import zlib
44 44
45 45 from . import (
46 46 encoding,
47 47 error,
48 48 i18n,
49 49 policy,
50 50 pycompat,
51 51 )
52 52
53 53 base85 = policy.importmod(r'base85')
54 54 osutil = policy.importmod(r'osutil')
55 55 parsers = policy.importmod(r'parsers')
56 56
57 57 b85decode = base85.b85decode
58 58 b85encode = base85.b85encode
59 59
60 60 cookielib = pycompat.cookielib
61 61 empty = pycompat.empty
62 62 httplib = pycompat.httplib
63 63 httpserver = pycompat.httpserver
64 64 pickle = pycompat.pickle
65 65 queue = pycompat.queue
66 66 socketserver = pycompat.socketserver
67 67 stderr = pycompat.stderr
68 68 stdin = pycompat.stdin
69 69 stdout = pycompat.stdout
70 70 stringio = pycompat.stringio
71 71 urlerr = pycompat.urlerr
72 72 urlreq = pycompat.urlreq
73 73 xmlrpclib = pycompat.xmlrpclib
74 74
75 75 # workaround for win32mbcs
76 76 _filenamebytestr = pycompat.bytestr
77 77
78 78 def isatty(fp):
79 79 try:
80 80 return fp.isatty()
81 81 except AttributeError:
82 82 return False
83 83
84 84 # glibc determines buffering on first write to stdout - if we replace a TTY
85 85 # destined stdout with a pipe destined stdout (e.g. pager), we want line
86 86 # buffering
87 87 if isatty(stdout):
88 88 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
89 89
90 90 if pycompat.osname == 'nt':
91 91 from . import windows as platform
92 92 stdout = platform.winstdout(stdout)
93 93 else:
94 94 from . import posix as platform
95 95
96 96 _ = i18n._
97 97
98 98 bindunixsocket = platform.bindunixsocket
99 99 cachestat = platform.cachestat
100 100 checkexec = platform.checkexec
101 101 checklink = platform.checklink
102 102 copymode = platform.copymode
103 103 executablepath = platform.executablepath
104 104 expandglobs = platform.expandglobs
105 105 explainexit = platform.explainexit
106 106 findexe = platform.findexe
107 107 gethgcmd = platform.gethgcmd
108 108 getuser = platform.getuser
109 109 getpid = os.getpid
110 110 groupmembers = platform.groupmembers
111 111 groupname = platform.groupname
112 112 hidewindow = platform.hidewindow
113 113 isexec = platform.isexec
114 114 isowner = platform.isowner
115 115 listdir = osutil.listdir
116 116 localpath = platform.localpath
117 117 lookupreg = platform.lookupreg
118 118 makedir = platform.makedir
119 119 nlinks = platform.nlinks
120 120 normpath = platform.normpath
121 121 normcase = platform.normcase
122 122 normcasespec = platform.normcasespec
123 123 normcasefallback = platform.normcasefallback
124 124 openhardlinks = platform.openhardlinks
125 125 oslink = platform.oslink
126 126 parsepatchoutput = platform.parsepatchoutput
127 127 pconvert = platform.pconvert
128 128 poll = platform.poll
129 129 popen = platform.popen
130 130 posixfile = platform.posixfile
131 131 quotecommand = platform.quotecommand
132 132 readpipe = platform.readpipe
133 133 rename = platform.rename
134 134 removedirs = platform.removedirs
135 135 samedevice = platform.samedevice
136 136 samefile = platform.samefile
137 137 samestat = platform.samestat
138 138 setbinary = platform.setbinary
139 139 setflags = platform.setflags
140 140 setsignalhandler = platform.setsignalhandler
141 141 shellquote = platform.shellquote
142 142 spawndetached = platform.spawndetached
143 143 split = platform.split
144 144 sshargs = platform.sshargs
145 145 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
146 146 statisexec = platform.statisexec
147 147 statislink = platform.statislink
148 148 testpid = platform.testpid
149 149 umask = platform.umask
150 150 unlink = platform.unlink
151 151 username = platform.username
152 152
153 153 try:
154 154 recvfds = osutil.recvfds
155 155 except AttributeError:
156 156 pass
157 157 try:
158 158 setprocname = osutil.setprocname
159 159 except AttributeError:
160 160 pass
161 161
162 162 # Python compatibility
163 163
164 164 _notset = object()
165 165
166 166 # disable Python's problematic floating point timestamps (issue4836)
167 167 # (Python hypocritically says you shouldn't change this behavior in
168 168 # libraries, and sure enough Mercurial is not a library.)
169 169 os.stat_float_times(False)
170 170
171 171 def safehasattr(thing, attr):
172 172 return getattr(thing, attr, _notset) is not _notset
173 173
174 174 def bitsfrom(container):
175 175 bits = 0
176 176 for bit in container:
177 177 bits |= bit
178 178 return bits
179 179
180 180 # python 2.6 still have deprecation warning enabled by default. We do not want
181 181 # to display anything to standard user so detect if we are running test and
182 182 # only use python deprecation warning in this case.
183 183 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
184 184 if _dowarn:
185 185 # explicitly unfilter our warning for python 2.7
186 186 #
187 187 # The option of setting PYTHONWARNINGS in the test runner was investigated.
188 188 # However, module name set through PYTHONWARNINGS was exactly matched, so
189 189 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
190 190 # makes the whole PYTHONWARNINGS thing useless for our usecase.
191 191 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
192 192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
193 193 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
194 194
195 195 def nouideprecwarn(msg, version, stacklevel=1):
196 196 """Issue an python native deprecation warning
197 197
198 198 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
199 199 """
200 200 if _dowarn:
201 201 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
202 202 " update your code.)") % version
203 203 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
204 204
205 205 DIGESTS = {
206 206 'md5': hashlib.md5,
207 207 'sha1': hashlib.sha1,
208 208 'sha512': hashlib.sha512,
209 209 }
210 210 # List of digest types from strongest to weakest
211 211 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
212 212
213 213 for k in DIGESTS_BY_STRENGTH:
214 214 assert k in DIGESTS
215 215
216 216 class digester(object):
217 217 """helper to compute digests.
218 218
219 219 This helper can be used to compute one or more digests given their name.
220 220
221 221 >>> d = digester(['md5', 'sha1'])
222 222 >>> d.update('foo')
223 223 >>> [k for k in sorted(d)]
224 224 ['md5', 'sha1']
225 225 >>> d['md5']
226 226 'acbd18db4cc2f85cedef654fccc4a4d8'
227 227 >>> d['sha1']
228 228 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
229 229 >>> digester.preferred(['md5', 'sha1'])
230 230 'sha1'
231 231 """
232 232
233 233 def __init__(self, digests, s=''):
234 234 self._hashes = {}
235 235 for k in digests:
236 236 if k not in DIGESTS:
237 237 raise Abort(_('unknown digest type: %s') % k)
238 238 self._hashes[k] = DIGESTS[k]()
239 239 if s:
240 240 self.update(s)
241 241
242 242 def update(self, data):
243 243 for h in self._hashes.values():
244 244 h.update(data)
245 245
246 246 def __getitem__(self, key):
247 247 if key not in DIGESTS:
248 248 raise Abort(_('unknown digest type: %s') % k)
249 249 return self._hashes[key].hexdigest()
250 250
251 251 def __iter__(self):
252 252 return iter(self._hashes)
253 253
254 254 @staticmethod
255 255 def preferred(supported):
256 256 """returns the strongest digest type in both supported and DIGESTS."""
257 257
258 258 for k in DIGESTS_BY_STRENGTH:
259 259 if k in supported:
260 260 return k
261 261 return None
262 262
263 263 class digestchecker(object):
264 264 """file handle wrapper that additionally checks content against a given
265 265 size and digests.
266 266
267 267 d = digestchecker(fh, size, {'md5': '...'})
268 268
269 269 When multiple digests are given, all of them are validated.
270 270 """
271 271
272 272 def __init__(self, fh, size, digests):
273 273 self._fh = fh
274 274 self._size = size
275 275 self._got = 0
276 276 self._digests = dict(digests)
277 277 self._digester = digester(self._digests.keys())
278 278
279 279 def read(self, length=-1):
280 280 content = self._fh.read(length)
281 281 self._digester.update(content)
282 282 self._got += len(content)
283 283 return content
284 284
285 285 def validate(self):
286 286 if self._size != self._got:
287 287 raise Abort(_('size mismatch: expected %d, got %d') %
288 288 (self._size, self._got))
289 289 for k, v in self._digests.items():
290 290 if v != self._digester[k]:
291 291 # i18n: first parameter is a digest name
292 292 raise Abort(_('%s mismatch: expected %s, got %s') %
293 293 (k, v, self._digester[k]))
294 294
295 295 try:
296 296 buffer = buffer
297 297 except NameError:
298 298 def buffer(sliceable, offset=0, length=None):
299 299 if length is not None:
300 300 return memoryview(sliceable)[offset:offset + length]
301 301 return memoryview(sliceable)[offset:]
302 302
303 303 closefds = pycompat.osname == 'posix'
304 304
305 305 _chunksize = 4096
306 306
307 307 class bufferedinputpipe(object):
308 308 """a manually buffered input pipe
309 309
310 310 Python will not let us use buffered IO and lazy reading with 'polling' at
311 311 the same time. We cannot probe the buffer state and select will not detect
312 312 that data are ready to read if they are already buffered.
313 313
314 314 This class let us work around that by implementing its own buffering
315 315 (allowing efficient readline) while offering a way to know if the buffer is
316 316 empty from the output (allowing collaboration of the buffer with polling).
317 317
318 318 This class lives in the 'util' module because it makes use of the 'os'
319 319 module from the python stdlib.
320 320 """
321 321
322 322 def __init__(self, input):
323 323 self._input = input
324 324 self._buffer = []
325 325 self._eof = False
326 326 self._lenbuf = 0
327 327
328 328 @property
329 329 def hasbuffer(self):
330 330 """True is any data is currently buffered
331 331
332 332 This will be used externally a pre-step for polling IO. If there is
333 333 already data then no polling should be set in place."""
334 334 return bool(self._buffer)
335 335
336 336 @property
337 337 def closed(self):
338 338 return self._input.closed
339 339
340 340 def fileno(self):
341 341 return self._input.fileno()
342 342
343 343 def close(self):
344 344 return self._input.close()
345 345
346 346 def read(self, size):
347 347 while (not self._eof) and (self._lenbuf < size):
348 348 self._fillbuffer()
349 349 return self._frombuffer(size)
350 350
351 351 def readline(self, *args, **kwargs):
352 352 if 1 < len(self._buffer):
353 353 # this should not happen because both read and readline end with a
354 354 # _frombuffer call that collapse it.
355 355 self._buffer = [''.join(self._buffer)]
356 356 self._lenbuf = len(self._buffer[0])
357 357 lfi = -1
358 358 if self._buffer:
359 359 lfi = self._buffer[-1].find('\n')
360 360 while (not self._eof) and lfi < 0:
361 361 self._fillbuffer()
362 362 if self._buffer:
363 363 lfi = self._buffer[-1].find('\n')
364 364 size = lfi + 1
365 365 if lfi < 0: # end of file
366 366 size = self._lenbuf
367 367 elif 1 < len(self._buffer):
368 368 # we need to take previous chunks into account
369 369 size += self._lenbuf - len(self._buffer[-1])
370 370 return self._frombuffer(size)
371 371
372 372 def _frombuffer(self, size):
373 373 """return at most 'size' data from the buffer
374 374
375 375 The data are removed from the buffer."""
376 376 if size == 0 or not self._buffer:
377 377 return ''
378 378 buf = self._buffer[0]
379 379 if 1 < len(self._buffer):
380 380 buf = ''.join(self._buffer)
381 381
382 382 data = buf[:size]
383 383 buf = buf[len(data):]
384 384 if buf:
385 385 self._buffer = [buf]
386 386 self._lenbuf = len(buf)
387 387 else:
388 388 self._buffer = []
389 389 self._lenbuf = 0
390 390 return data
391 391
392 392 def _fillbuffer(self):
393 393 """read data to the buffer"""
394 394 data = os.read(self._input.fileno(), _chunksize)
395 395 if not data:
396 396 self._eof = True
397 397 else:
398 398 self._lenbuf += len(data)
399 399 self._buffer.append(data)
400 400
401 401 def popen2(cmd, env=None, newlines=False):
402 402 # Setting bufsize to -1 lets the system decide the buffer size.
403 403 # The default for bufsize is 0, meaning unbuffered. This leads to
404 404 # poor performance on Mac OS X: http://bugs.python.org/issue4194
405 405 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
406 406 close_fds=closefds,
407 407 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
408 408 universal_newlines=newlines,
409 409 env=env)
410 410 return p.stdin, p.stdout
411 411
412 412 def popen3(cmd, env=None, newlines=False):
413 413 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
414 414 return stdin, stdout, stderr
415 415
416 416 def popen4(cmd, env=None, newlines=False, bufsize=-1):
417 417 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
418 418 close_fds=closefds,
419 419 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
420 420 stderr=subprocess.PIPE,
421 421 universal_newlines=newlines,
422 422 env=env)
423 423 return p.stdin, p.stdout, p.stderr, p
424 424
425 425 def version():
426 426 """Return version information if available."""
427 427 try:
428 428 from . import __version__
429 429 return __version__.version
430 430 except ImportError:
431 431 return 'unknown'
432 432
433 433 def versiontuple(v=None, n=4):
434 434 """Parses a Mercurial version string into an N-tuple.
435 435
436 436 The version string to be parsed is specified with the ``v`` argument.
437 437 If it isn't defined, the current Mercurial version string will be parsed.
438 438
439 439 ``n`` can be 2, 3, or 4. Here is how some version strings map to
440 440 returned values:
441 441
442 442 >>> v = '3.6.1+190-df9b73d2d444'
443 443 >>> versiontuple(v, 2)
444 444 (3, 6)
445 445 >>> versiontuple(v, 3)
446 446 (3, 6, 1)
447 447 >>> versiontuple(v, 4)
448 448 (3, 6, 1, '190-df9b73d2d444')
449 449
450 450 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
451 451 (3, 6, 1, '190-df9b73d2d444+20151118')
452 452
453 453 >>> v = '3.6'
454 454 >>> versiontuple(v, 2)
455 455 (3, 6)
456 456 >>> versiontuple(v, 3)
457 457 (3, 6, None)
458 458 >>> versiontuple(v, 4)
459 459 (3, 6, None, None)
460 460
461 461 >>> v = '3.9-rc'
462 462 >>> versiontuple(v, 2)
463 463 (3, 9)
464 464 >>> versiontuple(v, 3)
465 465 (3, 9, None)
466 466 >>> versiontuple(v, 4)
467 467 (3, 9, None, 'rc')
468 468
469 469 >>> v = '3.9-rc+2-02a8fea4289b'
470 470 >>> versiontuple(v, 2)
471 471 (3, 9)
472 472 >>> versiontuple(v, 3)
473 473 (3, 9, None)
474 474 >>> versiontuple(v, 4)
475 475 (3, 9, None, 'rc+2-02a8fea4289b')
476 476 """
477 477 if not v:
478 478 v = version()
479 479 parts = remod.split('[\+-]', v, 1)
480 480 if len(parts) == 1:
481 481 vparts, extra = parts[0], None
482 482 else:
483 483 vparts, extra = parts
484 484
485 485 vints = []
486 486 for i in vparts.split('.'):
487 487 try:
488 488 vints.append(int(i))
489 489 except ValueError:
490 490 break
491 491 # (3, 6) -> (3, 6, None)
492 492 while len(vints) < 3:
493 493 vints.append(None)
494 494
495 495 if n == 2:
496 496 return (vints[0], vints[1])
497 497 if n == 3:
498 498 return (vints[0], vints[1], vints[2])
499 499 if n == 4:
500 500 return (vints[0], vints[1], vints[2], extra)
501 501
502 502 # used by parsedate
503 503 defaultdateformats = (
504 504 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
505 505 '%Y-%m-%dT%H:%M', # without seconds
506 506 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
507 507 '%Y-%m-%dT%H%M', # without seconds
508 508 '%Y-%m-%d %H:%M:%S', # our common legal variant
509 509 '%Y-%m-%d %H:%M', # without seconds
510 510 '%Y-%m-%d %H%M%S', # without :
511 511 '%Y-%m-%d %H%M', # without seconds
512 512 '%Y-%m-%d %I:%M:%S%p',
513 513 '%Y-%m-%d %H:%M',
514 514 '%Y-%m-%d %I:%M%p',
515 515 '%Y-%m-%d',
516 516 '%m-%d',
517 517 '%m/%d',
518 518 '%m/%d/%y',
519 519 '%m/%d/%Y',
520 520 '%a %b %d %H:%M:%S %Y',
521 521 '%a %b %d %I:%M:%S%p %Y',
522 522 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
523 523 '%b %d %H:%M:%S %Y',
524 524 '%b %d %I:%M:%S%p %Y',
525 525 '%b %d %H:%M:%S',
526 526 '%b %d %I:%M:%S%p',
527 527 '%b %d %H:%M',
528 528 '%b %d %I:%M%p',
529 529 '%b %d %Y',
530 530 '%b %d',
531 531 '%H:%M:%S',
532 532 '%I:%M:%S%p',
533 533 '%H:%M',
534 534 '%I:%M%p',
535 535 )
536 536
537 537 extendeddateformats = defaultdateformats + (
538 538 "%Y",
539 539 "%Y-%m",
540 540 "%b",
541 541 "%b %Y",
542 542 )
543 543
544 544 def cachefunc(func):
545 545 '''cache the result of function calls'''
546 546 # XXX doesn't handle keywords args
547 547 if func.__code__.co_argcount == 0:
548 548 cache = []
549 549 def f():
550 550 if len(cache) == 0:
551 551 cache.append(func())
552 552 return cache[0]
553 553 return f
554 554 cache = {}
555 555 if func.__code__.co_argcount == 1:
556 556 # we gain a small amount of time because
557 557 # we don't need to pack/unpack the list
558 558 def f(arg):
559 559 if arg not in cache:
560 560 cache[arg] = func(arg)
561 561 return cache[arg]
562 562 else:
563 563 def f(*args):
564 564 if args not in cache:
565 565 cache[args] = func(*args)
566 566 return cache[args]
567 567
568 568 return f
569 569
570 570 class sortdict(collections.OrderedDict):
571 571 '''a simple sorted dictionary
572 572
573 573 >>> d1 = sortdict([('a', 0), ('b', 1)])
574 574 >>> d2 = d1.copy()
575 575 >>> d2
576 576 sortdict([('a', 0), ('b', 1)])
577 577 >>> d2.update([('a', 2)])
578 578 >>> d2.keys() # should still be in last-set order
579 579 ['b', 'a']
580 580 '''
581 581
582 582 def __setitem__(self, key, value):
583 583 if key in self:
584 584 del self[key]
585 585 super(sortdict, self).__setitem__(key, value)
586 586
587 587 @contextlib.contextmanager
588 588 def acceptintervention(tr=None):
589 589 """A context manager that closes the transaction on InterventionRequired
590 590
591 591 If no transaction was provided, this simply runs the body and returns
592 592 """
593 593 if not tr:
594 594 yield
595 595 return
596 596 try:
597 597 yield
598 598 tr.close()
599 599 except error.InterventionRequired:
600 600 tr.close()
601 601 raise
602 602 finally:
603 603 tr.release()
604 604
605 @contextlib.contextmanager
606 def nullcontextmanager():
607 yield
608
605 609 class _lrucachenode(object):
606 610 """A node in a doubly linked list.
607 611
608 612 Holds a reference to nodes on either side as well as a key-value
609 613 pair for the dictionary entry.
610 614 """
611 615 __slots__ = (u'next', u'prev', u'key', u'value')
612 616
613 617 def __init__(self):
614 618 self.next = None
615 619 self.prev = None
616 620
617 621 self.key = _notset
618 622 self.value = None
619 623
620 624 def markempty(self):
621 625 """Mark the node as emptied."""
622 626 self.key = _notset
623 627
624 628 class lrucachedict(object):
625 629 """Dict that caches most recent accesses and sets.
626 630
627 631 The dict consists of an actual backing dict - indexed by original
628 632 key - and a doubly linked circular list defining the order of entries in
629 633 the cache.
630 634
631 635 The head node is the newest entry in the cache. If the cache is full,
632 636 we recycle head.prev and make it the new head. Cache accesses result in
633 637 the node being moved to before the existing head and being marked as the
634 638 new head node.
635 639 """
636 640 def __init__(self, max):
637 641 self._cache = {}
638 642
639 643 self._head = head = _lrucachenode()
640 644 head.prev = head
641 645 head.next = head
642 646 self._size = 1
643 647 self._capacity = max
644 648
645 649 def __len__(self):
646 650 return len(self._cache)
647 651
648 652 def __contains__(self, k):
649 653 return k in self._cache
650 654
651 655 def __iter__(self):
652 656 # We don't have to iterate in cache order, but why not.
653 657 n = self._head
654 658 for i in range(len(self._cache)):
655 659 yield n.key
656 660 n = n.next
657 661
658 662 def __getitem__(self, k):
659 663 node = self._cache[k]
660 664 self._movetohead(node)
661 665 return node.value
662 666
663 667 def __setitem__(self, k, v):
664 668 node = self._cache.get(k)
665 669 # Replace existing value and mark as newest.
666 670 if node is not None:
667 671 node.value = v
668 672 self._movetohead(node)
669 673 return
670 674
671 675 if self._size < self._capacity:
672 676 node = self._addcapacity()
673 677 else:
674 678 # Grab the last/oldest item.
675 679 node = self._head.prev
676 680
677 681 # At capacity. Kill the old entry.
678 682 if node.key is not _notset:
679 683 del self._cache[node.key]
680 684
681 685 node.key = k
682 686 node.value = v
683 687 self._cache[k] = node
684 688 # And mark it as newest entry. No need to adjust order since it
685 689 # is already self._head.prev.
686 690 self._head = node
687 691
688 692 def __delitem__(self, k):
689 693 node = self._cache.pop(k)
690 694 node.markempty()
691 695
692 696 # Temporarily mark as newest item before re-adjusting head to make
693 697 # this node the oldest item.
694 698 self._movetohead(node)
695 699 self._head = node.next
696 700
697 701 # Additional dict methods.
698 702
699 703 def get(self, k, default=None):
700 704 try:
701 705 return self._cache[k].value
702 706 except KeyError:
703 707 return default
704 708
705 709 def clear(self):
706 710 n = self._head
707 711 while n.key is not _notset:
708 712 n.markempty()
709 713 n = n.next
710 714
711 715 self._cache.clear()
712 716
713 717 def copy(self):
714 718 result = lrucachedict(self._capacity)
715 719 n = self._head.prev
716 720 # Iterate in oldest-to-newest order, so the copy has the right ordering
717 721 for i in range(len(self._cache)):
718 722 result[n.key] = n.value
719 723 n = n.prev
720 724 return result
721 725
722 726 def _movetohead(self, node):
723 727 """Mark a node as the newest, making it the new head.
724 728
725 729 When a node is accessed, it becomes the freshest entry in the LRU
726 730 list, which is denoted by self._head.
727 731
728 732 Visually, let's make ``N`` the new head node (* denotes head):
729 733
730 734 previous/oldest <-> head <-> next/next newest
731 735
732 736 ----<->--- A* ---<->-----
733 737 | |
734 738 E <-> D <-> N <-> C <-> B
735 739
736 740 To:
737 741
738 742 ----<->--- N* ---<->-----
739 743 | |
740 744 E <-> D <-> C <-> B <-> A
741 745
742 746 This requires the following moves:
743 747
744 748 C.next = D (node.prev.next = node.next)
745 749 D.prev = C (node.next.prev = node.prev)
746 750 E.next = N (head.prev.next = node)
747 751 N.prev = E (node.prev = head.prev)
748 752 N.next = A (node.next = head)
749 753 A.prev = N (head.prev = node)
750 754 """
751 755 head = self._head
752 756 # C.next = D
753 757 node.prev.next = node.next
754 758 # D.prev = C
755 759 node.next.prev = node.prev
756 760 # N.prev = E
757 761 node.prev = head.prev
758 762 # N.next = A
759 763 # It is tempting to do just "head" here, however if node is
760 764 # adjacent to head, this will do bad things.
761 765 node.next = head.prev.next
762 766 # E.next = N
763 767 node.next.prev = node
764 768 # A.prev = N
765 769 node.prev.next = node
766 770
767 771 self._head = node
768 772
769 773 def _addcapacity(self):
770 774 """Add a node to the circular linked list.
771 775
772 776 The new node is inserted before the head node.
773 777 """
774 778 head = self._head
775 779 node = _lrucachenode()
776 780 head.prev.next = node
777 781 node.prev = head.prev
778 782 node.next = head
779 783 head.prev = node
780 784 self._size += 1
781 785 return node
782 786
783 787 def lrucachefunc(func):
784 788 '''cache most recent results of function calls'''
785 789 cache = {}
786 790 order = collections.deque()
787 791 if func.__code__.co_argcount == 1:
788 792 def f(arg):
789 793 if arg not in cache:
790 794 if len(cache) > 20:
791 795 del cache[order.popleft()]
792 796 cache[arg] = func(arg)
793 797 else:
794 798 order.remove(arg)
795 799 order.append(arg)
796 800 return cache[arg]
797 801 else:
798 802 def f(*args):
799 803 if args not in cache:
800 804 if len(cache) > 20:
801 805 del cache[order.popleft()]
802 806 cache[args] = func(*args)
803 807 else:
804 808 order.remove(args)
805 809 order.append(args)
806 810 return cache[args]
807 811
808 812 return f
809 813
810 814 class propertycache(object):
811 815 def __init__(self, func):
812 816 self.func = func
813 817 self.name = func.__name__
814 818 def __get__(self, obj, type=None):
815 819 result = self.func(obj)
816 820 self.cachevalue(obj, result)
817 821 return result
818 822
819 823 def cachevalue(self, obj, value):
820 824 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
821 825 obj.__dict__[self.name] = value
822 826
823 827 def pipefilter(s, cmd):
824 828 '''filter string S through command CMD, returning its output'''
825 829 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
826 830 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
827 831 pout, perr = p.communicate(s)
828 832 return pout
829 833
830 834 def tempfilter(s, cmd):
831 835 '''filter string S through a pair of temporary files with CMD.
832 836 CMD is used as a template to create the real command to be run,
833 837 with the strings INFILE and OUTFILE replaced by the real names of
834 838 the temporary files generated.'''
835 839 inname, outname = None, None
836 840 try:
837 841 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
838 842 fp = os.fdopen(infd, pycompat.sysstr('wb'))
839 843 fp.write(s)
840 844 fp.close()
841 845 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
842 846 os.close(outfd)
843 847 cmd = cmd.replace('INFILE', inname)
844 848 cmd = cmd.replace('OUTFILE', outname)
845 849 code = os.system(cmd)
846 850 if pycompat.sysplatform == 'OpenVMS' and code & 1:
847 851 code = 0
848 852 if code:
849 853 raise Abort(_("command '%s' failed: %s") %
850 854 (cmd, explainexit(code)))
851 855 return readfile(outname)
852 856 finally:
853 857 try:
854 858 if inname:
855 859 os.unlink(inname)
856 860 except OSError:
857 861 pass
858 862 try:
859 863 if outname:
860 864 os.unlink(outname)
861 865 except OSError:
862 866 pass
863 867
864 868 filtertable = {
865 869 'tempfile:': tempfilter,
866 870 'pipe:': pipefilter,
867 871 }
868 872
869 873 def filter(s, cmd):
870 874 "filter a string through a command that transforms its input to its output"
871 875 for name, fn in filtertable.iteritems():
872 876 if cmd.startswith(name):
873 877 return fn(s, cmd[len(name):].lstrip())
874 878 return pipefilter(s, cmd)
875 879
876 880 def binary(s):
877 881 """return true if a string is binary data"""
878 882 return bool(s and '\0' in s)
879 883
880 884 def increasingchunks(source, min=1024, max=65536):
881 885 '''return no less than min bytes per chunk while data remains,
882 886 doubling min after each chunk until it reaches max'''
883 887 def log2(x):
884 888 if not x:
885 889 return 0
886 890 i = 0
887 891 while x:
888 892 x >>= 1
889 893 i += 1
890 894 return i - 1
891 895
892 896 buf = []
893 897 blen = 0
894 898 for chunk in source:
895 899 buf.append(chunk)
896 900 blen += len(chunk)
897 901 if blen >= min:
898 902 if min < max:
899 903 min = min << 1
900 904 nmin = 1 << log2(blen)
901 905 if nmin > min:
902 906 min = nmin
903 907 if min > max:
904 908 min = max
905 909 yield ''.join(buf)
906 910 blen = 0
907 911 buf = []
908 912 if buf:
909 913 yield ''.join(buf)
910 914
911 915 Abort = error.Abort
912 916
913 917 def always(fn):
914 918 return True
915 919
916 920 def never(fn):
917 921 return False
918 922
919 923 def nogc(func):
920 924 """disable garbage collector
921 925
922 926 Python's garbage collector triggers a GC each time a certain number of
923 927 container objects (the number being defined by gc.get_threshold()) are
924 928 allocated even when marked not to be tracked by the collector. Tracking has
925 929 no effect on when GCs are triggered, only on what objects the GC looks
926 930 into. As a workaround, disable GC while building complex (huge)
927 931 containers.
928 932
929 933 This garbage collector issue have been fixed in 2.7.
930 934 """
931 935 if sys.version_info >= (2, 7):
932 936 return func
933 937 def wrapper(*args, **kwargs):
934 938 gcenabled = gc.isenabled()
935 939 gc.disable()
936 940 try:
937 941 return func(*args, **kwargs)
938 942 finally:
939 943 if gcenabled:
940 944 gc.enable()
941 945 return wrapper
942 946
943 947 def pathto(root, n1, n2):
944 948 '''return the relative path from one place to another.
945 949 root should use os.sep to separate directories
946 950 n1 should use os.sep to separate directories
947 951 n2 should use "/" to separate directories
948 952 returns an os.sep-separated path.
949 953
950 954 If n1 is a relative path, it's assumed it's
951 955 relative to root.
952 956 n2 should always be relative to root.
953 957 '''
954 958 if not n1:
955 959 return localpath(n2)
956 960 if os.path.isabs(n1):
957 961 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
958 962 return os.path.join(root, localpath(n2))
959 963 n2 = '/'.join((pconvert(root), n2))
960 964 a, b = splitpath(n1), n2.split('/')
961 965 a.reverse()
962 966 b.reverse()
963 967 while a and b and a[-1] == b[-1]:
964 968 a.pop()
965 969 b.pop()
966 970 b.reverse()
967 971 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
968 972
969 973 def mainfrozen():
970 974 """return True if we are a frozen executable.
971 975
972 976 The code supports py2exe (most common, Windows only) and tools/freeze
973 977 (portable, not much used).
974 978 """
975 979 return (safehasattr(sys, "frozen") or # new py2exe
976 980 safehasattr(sys, "importers") or # old py2exe
977 981 imp.is_frozen(u"__main__")) # tools/freeze
978 982
979 983 # the location of data files matching the source code
980 984 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
981 985 # executable version (py2exe) doesn't support __file__
982 986 datapath = os.path.dirname(pycompat.sysexecutable)
983 987 else:
984 988 datapath = os.path.dirname(pycompat.fsencode(__file__))
985 989
986 990 i18n.setdatapath(datapath)
987 991
988 992 _hgexecutable = None
989 993
990 994 def hgexecutable():
991 995 """return location of the 'hg' executable.
992 996
993 997 Defaults to $HG or 'hg' in the search path.
994 998 """
995 999 if _hgexecutable is None:
996 1000 hg = encoding.environ.get('HG')
997 1001 mainmod = sys.modules[pycompat.sysstr('__main__')]
998 1002 if hg:
999 1003 _sethgexecutable(hg)
1000 1004 elif mainfrozen():
1001 1005 if getattr(sys, 'frozen', None) == 'macosx_app':
1002 1006 # Env variable set by py2app
1003 1007 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1004 1008 else:
1005 1009 _sethgexecutable(pycompat.sysexecutable)
1006 1010 elif (os.path.basename(
1007 1011 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1008 1012 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1009 1013 else:
1010 1014 exe = findexe('hg') or os.path.basename(sys.argv[0])
1011 1015 _sethgexecutable(exe)
1012 1016 return _hgexecutable
1013 1017
1014 1018 def _sethgexecutable(path):
1015 1019 """set location of the 'hg' executable"""
1016 1020 global _hgexecutable
1017 1021 _hgexecutable = path
1018 1022
1019 1023 def _isstdout(f):
1020 1024 fileno = getattr(f, 'fileno', None)
1021 1025 return fileno and fileno() == sys.__stdout__.fileno()
1022 1026
1023 1027 def shellenviron(environ=None):
1024 1028 """return environ with optional override, useful for shelling out"""
1025 1029 def py2shell(val):
1026 1030 'convert python object into string that is useful to shell'
1027 1031 if val is None or val is False:
1028 1032 return '0'
1029 1033 if val is True:
1030 1034 return '1'
1031 1035 return str(val)
1032 1036 env = dict(encoding.environ)
1033 1037 if environ:
1034 1038 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1035 1039 env['HG'] = hgexecutable()
1036 1040 return env
1037 1041
1038 1042 def system(cmd, environ=None, cwd=None, out=None):
1039 1043 '''enhanced shell command execution.
1040 1044 run with environment maybe modified, maybe in different dir.
1041 1045
1042 1046 if out is specified, it is assumed to be a file-like object that has a
1043 1047 write() method. stdout and stderr will be redirected to out.'''
1044 1048 try:
1045 1049 stdout.flush()
1046 1050 except Exception:
1047 1051 pass
1048 1052 cmd = quotecommand(cmd)
1049 1053 env = shellenviron(environ)
1050 1054 if out is None or _isstdout(out):
1051 1055 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1052 1056 env=env, cwd=cwd)
1053 1057 else:
1054 1058 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1055 1059 env=env, cwd=cwd, stdout=subprocess.PIPE,
1056 1060 stderr=subprocess.STDOUT)
1057 1061 for line in iter(proc.stdout.readline, ''):
1058 1062 out.write(line)
1059 1063 proc.wait()
1060 1064 rc = proc.returncode
1061 1065 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1062 1066 rc = 0
1063 1067 return rc
1064 1068
1065 1069 def checksignature(func):
1066 1070 '''wrap a function with code to check for calling errors'''
1067 1071 def check(*args, **kwargs):
1068 1072 try:
1069 1073 return func(*args, **kwargs)
1070 1074 except TypeError:
1071 1075 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1072 1076 raise error.SignatureError
1073 1077 raise
1074 1078
1075 1079 return check
1076 1080
1077 1081 # a whilelist of known filesystems where hardlink works reliably
1078 1082 _hardlinkfswhitelist = {
1079 1083 'btrfs',
1080 1084 'ext2',
1081 1085 'ext3',
1082 1086 'ext4',
1083 1087 'hfs',
1084 1088 'jfs',
1085 1089 'reiserfs',
1086 1090 'tmpfs',
1087 1091 'ufs',
1088 1092 'xfs',
1089 1093 'zfs',
1090 1094 }
1091 1095
1092 1096 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1093 1097 '''copy a file, preserving mode and optionally other stat info like
1094 1098 atime/mtime
1095 1099
1096 1100 checkambig argument is used with filestat, and is useful only if
1097 1101 destination file is guarded by any lock (e.g. repo.lock or
1098 1102 repo.wlock).
1099 1103
1100 1104 copystat and checkambig should be exclusive.
1101 1105 '''
1102 1106 assert not (copystat and checkambig)
1103 1107 oldstat = None
1104 1108 if os.path.lexists(dest):
1105 1109 if checkambig:
1106 1110 oldstat = checkambig and filestat.frompath(dest)
1107 1111 unlink(dest)
1108 1112 if hardlink:
1109 1113 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1110 1114 # unless we are confident that dest is on a whitelisted filesystem.
1111 1115 try:
1112 1116 fstype = getfstype(os.path.dirname(dest))
1113 1117 except OSError:
1114 1118 fstype = None
1115 1119 if fstype not in _hardlinkfswhitelist:
1116 1120 hardlink = False
1117 1121 if hardlink:
1118 1122 try:
1119 1123 oslink(src, dest)
1120 1124 return
1121 1125 except (IOError, OSError):
1122 1126 pass # fall back to normal copy
1123 1127 if os.path.islink(src):
1124 1128 os.symlink(os.readlink(src), dest)
1125 1129 # copytime is ignored for symlinks, but in general copytime isn't needed
1126 1130 # for them anyway
1127 1131 else:
1128 1132 try:
1129 1133 shutil.copyfile(src, dest)
1130 1134 if copystat:
1131 1135 # copystat also copies mode
1132 1136 shutil.copystat(src, dest)
1133 1137 else:
1134 1138 shutil.copymode(src, dest)
1135 1139 if oldstat and oldstat.stat:
1136 1140 newstat = filestat.frompath(dest)
1137 1141 if newstat.isambig(oldstat):
1138 1142 # stat of copied file is ambiguous to original one
1139 1143 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1140 1144 os.utime(dest, (advanced, advanced))
1141 1145 except shutil.Error as inst:
1142 1146 raise Abort(str(inst))
1143 1147
1144 1148 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1145 1149 """Copy a directory tree using hardlinks if possible."""
1146 1150 num = 0
1147 1151
1148 1152 gettopic = lambda: hardlink and _('linking') or _('copying')
1149 1153
1150 1154 if os.path.isdir(src):
1151 1155 if hardlink is None:
1152 1156 hardlink = (os.stat(src).st_dev ==
1153 1157 os.stat(os.path.dirname(dst)).st_dev)
1154 1158 topic = gettopic()
1155 1159 os.mkdir(dst)
1156 1160 for name, kind in listdir(src):
1157 1161 srcname = os.path.join(src, name)
1158 1162 dstname = os.path.join(dst, name)
1159 1163 def nprog(t, pos):
1160 1164 if pos is not None:
1161 1165 return progress(t, pos + num)
1162 1166 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1163 1167 num += n
1164 1168 else:
1165 1169 if hardlink is None:
1166 1170 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1167 1171 os.stat(os.path.dirname(dst)).st_dev)
1168 1172 topic = gettopic()
1169 1173
1170 1174 if hardlink:
1171 1175 try:
1172 1176 oslink(src, dst)
1173 1177 except (IOError, OSError):
1174 1178 hardlink = False
1175 1179 shutil.copy(src, dst)
1176 1180 else:
1177 1181 shutil.copy(src, dst)
1178 1182 num += 1
1179 1183 progress(topic, num)
1180 1184 progress(topic, None)
1181 1185
1182 1186 return hardlink, num
1183 1187
1184 1188 _winreservednames = b'''con prn aux nul
1185 1189 com1 com2 com3 com4 com5 com6 com7 com8 com9
1186 1190 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1187 1191 _winreservedchars = ':*?"<>|'
1188 1192 def checkwinfilename(path):
1189 1193 r'''Check that the base-relative path is a valid filename on Windows.
1190 1194 Returns None if the path is ok, or a UI string describing the problem.
1191 1195
1192 1196 >>> checkwinfilename("just/a/normal/path")
1193 1197 >>> checkwinfilename("foo/bar/con.xml")
1194 1198 "filename contains 'con', which is reserved on Windows"
1195 1199 >>> checkwinfilename("foo/con.xml/bar")
1196 1200 "filename contains 'con', which is reserved on Windows"
1197 1201 >>> checkwinfilename("foo/bar/xml.con")
1198 1202 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1199 1203 "filename contains 'AUX', which is reserved on Windows"
1200 1204 >>> checkwinfilename("foo/bar/bla:.txt")
1201 1205 "filename contains ':', which is reserved on Windows"
1202 1206 >>> checkwinfilename("foo/bar/b\07la.txt")
1203 1207 "filename contains '\\x07', which is invalid on Windows"
1204 1208 >>> checkwinfilename("foo/bar/bla ")
1205 1209 "filename ends with ' ', which is not allowed on Windows"
1206 1210 >>> checkwinfilename("../bar")
1207 1211 >>> checkwinfilename("foo\\")
1208 1212 "filename ends with '\\', which is invalid on Windows"
1209 1213 >>> checkwinfilename("foo\\/bar")
1210 1214 "directory name ends with '\\', which is invalid on Windows"
1211 1215 '''
1212 1216 if path.endswith('\\'):
1213 1217 return _("filename ends with '\\', which is invalid on Windows")
1214 1218 if '\\/' in path:
1215 1219 return _("directory name ends with '\\', which is invalid on Windows")
1216 1220 for n in path.replace('\\', '/').split('/'):
1217 1221 if not n:
1218 1222 continue
1219 1223 for c in _filenamebytestr(n):
1220 1224 if c in _winreservedchars:
1221 1225 return _("filename contains '%s', which is reserved "
1222 1226 "on Windows") % c
1223 1227 if ord(c) <= 31:
1224 1228 return _("filename contains %r, which is invalid "
1225 1229 "on Windows") % c
1226 1230 base = n.split('.')[0]
1227 1231 if base and base.lower() in _winreservednames:
1228 1232 return _("filename contains '%s', which is reserved "
1229 1233 "on Windows") % base
1230 1234 t = n[-1]
1231 1235 if t in '. ' and n not in '..':
1232 1236 return _("filename ends with '%s', which is not allowed "
1233 1237 "on Windows") % t
1234 1238
1235 1239 if pycompat.osname == 'nt':
1236 1240 checkosfilename = checkwinfilename
1237 1241 timer = time.clock
1238 1242 else:
1239 1243 checkosfilename = platform.checkosfilename
1240 1244 timer = time.time
1241 1245
1242 1246 if safehasattr(time, "perf_counter"):
1243 1247 timer = time.perf_counter
1244 1248
1245 1249 def makelock(info, pathname):
1246 1250 try:
1247 1251 return os.symlink(info, pathname)
1248 1252 except OSError as why:
1249 1253 if why.errno == errno.EEXIST:
1250 1254 raise
1251 1255 except AttributeError: # no symlink in os
1252 1256 pass
1253 1257
1254 1258 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1255 1259 os.write(ld, info)
1256 1260 os.close(ld)
1257 1261
1258 1262 def readlock(pathname):
1259 1263 try:
1260 1264 return os.readlink(pathname)
1261 1265 except OSError as why:
1262 1266 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1263 1267 raise
1264 1268 except AttributeError: # no symlink in os
1265 1269 pass
1266 1270 fp = posixfile(pathname)
1267 1271 r = fp.read()
1268 1272 fp.close()
1269 1273 return r
1270 1274
1271 1275 def fstat(fp):
1272 1276 '''stat file object that may not have fileno method.'''
1273 1277 try:
1274 1278 return os.fstat(fp.fileno())
1275 1279 except AttributeError:
1276 1280 return os.stat(fp.name)
1277 1281
1278 1282 # File system features
1279 1283
1280 1284 def fscasesensitive(path):
1281 1285 """
1282 1286 Return true if the given path is on a case-sensitive filesystem
1283 1287
1284 1288 Requires a path (like /foo/.hg) ending with a foldable final
1285 1289 directory component.
1286 1290 """
1287 1291 s1 = os.lstat(path)
1288 1292 d, b = os.path.split(path)
1289 1293 b2 = b.upper()
1290 1294 if b == b2:
1291 1295 b2 = b.lower()
1292 1296 if b == b2:
1293 1297 return True # no evidence against case sensitivity
1294 1298 p2 = os.path.join(d, b2)
1295 1299 try:
1296 1300 s2 = os.lstat(p2)
1297 1301 if s2 == s1:
1298 1302 return False
1299 1303 return True
1300 1304 except OSError:
1301 1305 return True
1302 1306
1303 1307 try:
1304 1308 import re2
1305 1309 _re2 = None
1306 1310 except ImportError:
1307 1311 _re2 = False
1308 1312
1309 1313 class _re(object):
1310 1314 def _checkre2(self):
1311 1315 global _re2
1312 1316 try:
1313 1317 # check if match works, see issue3964
1314 1318 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1315 1319 except ImportError:
1316 1320 _re2 = False
1317 1321
1318 1322 def compile(self, pat, flags=0):
1319 1323 '''Compile a regular expression, using re2 if possible
1320 1324
1321 1325 For best performance, use only re2-compatible regexp features. The
1322 1326 only flags from the re module that are re2-compatible are
1323 1327 IGNORECASE and MULTILINE.'''
1324 1328 if _re2 is None:
1325 1329 self._checkre2()
1326 1330 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1327 1331 if flags & remod.IGNORECASE:
1328 1332 pat = '(?i)' + pat
1329 1333 if flags & remod.MULTILINE:
1330 1334 pat = '(?m)' + pat
1331 1335 try:
1332 1336 return re2.compile(pat)
1333 1337 except re2.error:
1334 1338 pass
1335 1339 return remod.compile(pat, flags)
1336 1340
1337 1341 @propertycache
1338 1342 def escape(self):
1339 1343 '''Return the version of escape corresponding to self.compile.
1340 1344
1341 1345 This is imperfect because whether re2 or re is used for a particular
1342 1346 function depends on the flags, etc, but it's the best we can do.
1343 1347 '''
1344 1348 global _re2
1345 1349 if _re2 is None:
1346 1350 self._checkre2()
1347 1351 if _re2:
1348 1352 return re2.escape
1349 1353 else:
1350 1354 return remod.escape
1351 1355
1352 1356 re = _re()
1353 1357
1354 1358 _fspathcache = {}
1355 1359 def fspath(name, root):
1356 1360 '''Get name in the case stored in the filesystem
1357 1361
1358 1362 The name should be relative to root, and be normcase-ed for efficiency.
1359 1363
1360 1364 Note that this function is unnecessary, and should not be
1361 1365 called, for case-sensitive filesystems (simply because it's expensive).
1362 1366
1363 1367 The root should be normcase-ed, too.
1364 1368 '''
1365 1369 def _makefspathcacheentry(dir):
1366 1370 return dict((normcase(n), n) for n in os.listdir(dir))
1367 1371
1368 1372 seps = pycompat.ossep
1369 1373 if pycompat.osaltsep:
1370 1374 seps = seps + pycompat.osaltsep
1371 1375 # Protect backslashes. This gets silly very quickly.
1372 1376 seps.replace('\\','\\\\')
1373 1377 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1374 1378 dir = os.path.normpath(root)
1375 1379 result = []
1376 1380 for part, sep in pattern.findall(name):
1377 1381 if sep:
1378 1382 result.append(sep)
1379 1383 continue
1380 1384
1381 1385 if dir not in _fspathcache:
1382 1386 _fspathcache[dir] = _makefspathcacheentry(dir)
1383 1387 contents = _fspathcache[dir]
1384 1388
1385 1389 found = contents.get(part)
1386 1390 if not found:
1387 1391 # retry "once per directory" per "dirstate.walk" which
1388 1392 # may take place for each patches of "hg qpush", for example
1389 1393 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1390 1394 found = contents.get(part)
1391 1395
1392 1396 result.append(found or part)
1393 1397 dir = os.path.join(dir, part)
1394 1398
1395 1399 return ''.join(result)
1396 1400
1397 1401 def getfstype(dirpath):
1398 1402 '''Get the filesystem type name from a directory (best-effort)
1399 1403
1400 1404 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1401 1405 '''
1402 1406 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1403 1407
1404 1408 def checknlink(testfile):
1405 1409 '''check whether hardlink count reporting works properly'''
1406 1410
1407 1411 # testfile may be open, so we need a separate file for checking to
1408 1412 # work around issue2543 (or testfile may get lost on Samba shares)
1409 1413 f1 = testfile + ".hgtmp1"
1410 1414 if os.path.lexists(f1):
1411 1415 return False
1412 1416 try:
1413 1417 posixfile(f1, 'w').close()
1414 1418 except IOError:
1415 1419 try:
1416 1420 os.unlink(f1)
1417 1421 except OSError:
1418 1422 pass
1419 1423 return False
1420 1424
1421 1425 f2 = testfile + ".hgtmp2"
1422 1426 fd = None
1423 1427 try:
1424 1428 oslink(f1, f2)
1425 1429 # nlinks() may behave differently for files on Windows shares if
1426 1430 # the file is open.
1427 1431 fd = posixfile(f2)
1428 1432 return nlinks(f2) > 1
1429 1433 except OSError:
1430 1434 return False
1431 1435 finally:
1432 1436 if fd is not None:
1433 1437 fd.close()
1434 1438 for f in (f1, f2):
1435 1439 try:
1436 1440 os.unlink(f)
1437 1441 except OSError:
1438 1442 pass
1439 1443
1440 1444 def endswithsep(path):
1441 1445 '''Check path ends with os.sep or os.altsep.'''
1442 1446 return (path.endswith(pycompat.ossep)
1443 1447 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1444 1448
1445 1449 def splitpath(path):
1446 1450 '''Split path by os.sep.
1447 1451 Note that this function does not use os.altsep because this is
1448 1452 an alternative of simple "xxx.split(os.sep)".
1449 1453 It is recommended to use os.path.normpath() before using this
1450 1454 function if need.'''
1451 1455 return path.split(pycompat.ossep)
1452 1456
1453 1457 def gui():
1454 1458 '''Are we running in a GUI?'''
1455 1459 if pycompat.sysplatform == 'darwin':
1456 1460 if 'SSH_CONNECTION' in encoding.environ:
1457 1461 # handle SSH access to a box where the user is logged in
1458 1462 return False
1459 1463 elif getattr(osutil, 'isgui', None):
1460 1464 # check if a CoreGraphics session is available
1461 1465 return osutil.isgui()
1462 1466 else:
1463 1467 # pure build; use a safe default
1464 1468 return True
1465 1469 else:
1466 1470 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1467 1471
1468 1472 def mktempcopy(name, emptyok=False, createmode=None):
1469 1473 """Create a temporary file with the same contents from name
1470 1474
1471 1475 The permission bits are copied from the original file.
1472 1476
1473 1477 If the temporary file is going to be truncated immediately, you
1474 1478 can use emptyok=True as an optimization.
1475 1479
1476 1480 Returns the name of the temporary file.
1477 1481 """
1478 1482 d, fn = os.path.split(name)
1479 1483 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1480 1484 os.close(fd)
1481 1485 # Temporary files are created with mode 0600, which is usually not
1482 1486 # what we want. If the original file already exists, just copy
1483 1487 # its mode. Otherwise, manually obey umask.
1484 1488 copymode(name, temp, createmode)
1485 1489 if emptyok:
1486 1490 return temp
1487 1491 try:
1488 1492 try:
1489 1493 ifp = posixfile(name, "rb")
1490 1494 except IOError as inst:
1491 1495 if inst.errno == errno.ENOENT:
1492 1496 return temp
1493 1497 if not getattr(inst, 'filename', None):
1494 1498 inst.filename = name
1495 1499 raise
1496 1500 ofp = posixfile(temp, "wb")
1497 1501 for chunk in filechunkiter(ifp):
1498 1502 ofp.write(chunk)
1499 1503 ifp.close()
1500 1504 ofp.close()
1501 1505 except: # re-raises
1502 1506 try: os.unlink(temp)
1503 1507 except OSError: pass
1504 1508 raise
1505 1509 return temp
1506 1510
1507 1511 class filestat(object):
1508 1512 """help to exactly detect change of a file
1509 1513
1510 1514 'stat' attribute is result of 'os.stat()' if specified 'path'
1511 1515 exists. Otherwise, it is None. This can avoid preparative
1512 1516 'exists()' examination on client side of this class.
1513 1517 """
1514 1518 def __init__(self, stat):
1515 1519 self.stat = stat
1516 1520
1517 1521 @classmethod
1518 1522 def frompath(cls, path):
1519 1523 try:
1520 1524 stat = os.stat(path)
1521 1525 except OSError as err:
1522 1526 if err.errno != errno.ENOENT:
1523 1527 raise
1524 1528 stat = None
1525 1529 return cls(stat)
1526 1530
1527 1531 @classmethod
1528 1532 def fromfp(cls, fp):
1529 1533 stat = os.fstat(fp.fileno())
1530 1534 return cls(stat)
1531 1535
1532 1536 __hash__ = object.__hash__
1533 1537
1534 1538 def __eq__(self, old):
1535 1539 try:
1536 1540 # if ambiguity between stat of new and old file is
1537 1541 # avoided, comparison of size, ctime and mtime is enough
1538 1542 # to exactly detect change of a file regardless of platform
1539 1543 return (self.stat.st_size == old.stat.st_size and
1540 1544 self.stat.st_ctime == old.stat.st_ctime and
1541 1545 self.stat.st_mtime == old.stat.st_mtime)
1542 1546 except AttributeError:
1543 1547 pass
1544 1548 try:
1545 1549 return self.stat is None and old.stat is None
1546 1550 except AttributeError:
1547 1551 return False
1548 1552
1549 1553 def isambig(self, old):
1550 1554 """Examine whether new (= self) stat is ambiguous against old one
1551 1555
1552 1556 "S[N]" below means stat of a file at N-th change:
1553 1557
1554 1558 - S[n-1].ctime < S[n].ctime: can detect change of a file
1555 1559 - S[n-1].ctime == S[n].ctime
1556 1560 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1557 1561 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1558 1562 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1559 1563 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1560 1564
1561 1565 Case (*2) above means that a file was changed twice or more at
1562 1566 same time in sec (= S[n-1].ctime), and comparison of timestamp
1563 1567 is ambiguous.
1564 1568
1565 1569 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1566 1570 timestamp is ambiguous".
1567 1571
1568 1572 But advancing mtime only in case (*2) doesn't work as
1569 1573 expected, because naturally advanced S[n].mtime in case (*1)
1570 1574 might be equal to manually advanced S[n-1 or earlier].mtime.
1571 1575
1572 1576 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1573 1577 treated as ambiguous regardless of mtime, to avoid overlooking
1574 1578 by confliction between such mtime.
1575 1579
1576 1580 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1577 1581 S[n].mtime", even if size of a file isn't changed.
1578 1582 """
1579 1583 try:
1580 1584 return (self.stat.st_ctime == old.stat.st_ctime)
1581 1585 except AttributeError:
1582 1586 return False
1583 1587
1584 1588 def avoidambig(self, path, old):
1585 1589 """Change file stat of specified path to avoid ambiguity
1586 1590
1587 1591 'old' should be previous filestat of 'path'.
1588 1592
1589 1593 This skips avoiding ambiguity, if a process doesn't have
1590 1594 appropriate privileges for 'path'. This returns False in this
1591 1595 case.
1592 1596
1593 1597 Otherwise, this returns True, as "ambiguity is avoided".
1594 1598 """
1595 1599 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1596 1600 try:
1597 1601 os.utime(path, (advanced, advanced))
1598 1602 except OSError as inst:
1599 1603 if inst.errno == errno.EPERM:
1600 1604 # utime() on the file created by another user causes EPERM,
1601 1605 # if a process doesn't have appropriate privileges
1602 1606 return False
1603 1607 raise
1604 1608 return True
1605 1609
1606 1610 def __ne__(self, other):
1607 1611 return not self == other
1608 1612
1609 1613 class atomictempfile(object):
1610 1614 '''writable file object that atomically updates a file
1611 1615
1612 1616 All writes will go to a temporary copy of the original file. Call
1613 1617 close() when you are done writing, and atomictempfile will rename
1614 1618 the temporary copy to the original name, making the changes
1615 1619 visible. If the object is destroyed without being closed, all your
1616 1620 writes are discarded.
1617 1621
1618 1622 checkambig argument of constructor is used with filestat, and is
1619 1623 useful only if target file is guarded by any lock (e.g. repo.lock
1620 1624 or repo.wlock).
1621 1625 '''
1622 1626 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1623 1627 self.__name = name # permanent name
1624 1628 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1625 1629 createmode=createmode)
1626 1630 self._fp = posixfile(self._tempname, mode)
1627 1631 self._checkambig = checkambig
1628 1632
1629 1633 # delegated methods
1630 1634 self.read = self._fp.read
1631 1635 self.write = self._fp.write
1632 1636 self.seek = self._fp.seek
1633 1637 self.tell = self._fp.tell
1634 1638 self.fileno = self._fp.fileno
1635 1639
1636 1640 def close(self):
1637 1641 if not self._fp.closed:
1638 1642 self._fp.close()
1639 1643 filename = localpath(self.__name)
1640 1644 oldstat = self._checkambig and filestat.frompath(filename)
1641 1645 if oldstat and oldstat.stat:
1642 1646 rename(self._tempname, filename)
1643 1647 newstat = filestat.frompath(filename)
1644 1648 if newstat.isambig(oldstat):
1645 1649 # stat of changed file is ambiguous to original one
1646 1650 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1647 1651 os.utime(filename, (advanced, advanced))
1648 1652 else:
1649 1653 rename(self._tempname, filename)
1650 1654
1651 1655 def discard(self):
1652 1656 if not self._fp.closed:
1653 1657 try:
1654 1658 os.unlink(self._tempname)
1655 1659 except OSError:
1656 1660 pass
1657 1661 self._fp.close()
1658 1662
1659 1663 def __del__(self):
1660 1664 if safehasattr(self, '_fp'): # constructor actually did something
1661 1665 self.discard()
1662 1666
1663 1667 def __enter__(self):
1664 1668 return self
1665 1669
1666 1670 def __exit__(self, exctype, excvalue, traceback):
1667 1671 if exctype is not None:
1668 1672 self.discard()
1669 1673 else:
1670 1674 self.close()
1671 1675
1672 1676 def unlinkpath(f, ignoremissing=False):
1673 1677 """unlink and remove the directory if it is empty"""
1674 1678 if ignoremissing:
1675 1679 tryunlink(f)
1676 1680 else:
1677 1681 unlink(f)
1678 1682 # try removing directories that might now be empty
1679 1683 try:
1680 1684 removedirs(os.path.dirname(f))
1681 1685 except OSError:
1682 1686 pass
1683 1687
1684 1688 def tryunlink(f):
1685 1689 """Attempt to remove a file, ignoring ENOENT errors."""
1686 1690 try:
1687 1691 unlink(f)
1688 1692 except OSError as e:
1689 1693 if e.errno != errno.ENOENT:
1690 1694 raise
1691 1695
1692 1696 def makedirs(name, mode=None, notindexed=False):
1693 1697 """recursive directory creation with parent mode inheritance
1694 1698
1695 1699 Newly created directories are marked as "not to be indexed by
1696 1700 the content indexing service", if ``notindexed`` is specified
1697 1701 for "write" mode access.
1698 1702 """
1699 1703 try:
1700 1704 makedir(name, notindexed)
1701 1705 except OSError as err:
1702 1706 if err.errno == errno.EEXIST:
1703 1707 return
1704 1708 if err.errno != errno.ENOENT or not name:
1705 1709 raise
1706 1710 parent = os.path.dirname(os.path.abspath(name))
1707 1711 if parent == name:
1708 1712 raise
1709 1713 makedirs(parent, mode, notindexed)
1710 1714 try:
1711 1715 makedir(name, notindexed)
1712 1716 except OSError as err:
1713 1717 # Catch EEXIST to handle races
1714 1718 if err.errno == errno.EEXIST:
1715 1719 return
1716 1720 raise
1717 1721 if mode is not None:
1718 1722 os.chmod(name, mode)
1719 1723
1720 1724 def readfile(path):
1721 1725 with open(path, 'rb') as fp:
1722 1726 return fp.read()
1723 1727
1724 1728 def writefile(path, text):
1725 1729 with open(path, 'wb') as fp:
1726 1730 fp.write(text)
1727 1731
1728 1732 def appendfile(path, text):
1729 1733 with open(path, 'ab') as fp:
1730 1734 fp.write(text)
1731 1735
1732 1736 class chunkbuffer(object):
1733 1737 """Allow arbitrary sized chunks of data to be efficiently read from an
1734 1738 iterator over chunks of arbitrary size."""
1735 1739
1736 1740 def __init__(self, in_iter):
1737 1741 """in_iter is the iterator that's iterating over the input chunks."""
1738 1742 def splitbig(chunks):
1739 1743 for chunk in chunks:
1740 1744 if len(chunk) > 2**20:
1741 1745 pos = 0
1742 1746 while pos < len(chunk):
1743 1747 end = pos + 2 ** 18
1744 1748 yield chunk[pos:end]
1745 1749 pos = end
1746 1750 else:
1747 1751 yield chunk
1748 1752 self.iter = splitbig(in_iter)
1749 1753 self._queue = collections.deque()
1750 1754 self._chunkoffset = 0
1751 1755
1752 1756 def read(self, l=None):
1753 1757 """Read L bytes of data from the iterator of chunks of data.
1754 1758 Returns less than L bytes if the iterator runs dry.
1755 1759
1756 1760 If size parameter is omitted, read everything"""
1757 1761 if l is None:
1758 1762 return ''.join(self.iter)
1759 1763
1760 1764 left = l
1761 1765 buf = []
1762 1766 queue = self._queue
1763 1767 while left > 0:
1764 1768 # refill the queue
1765 1769 if not queue:
1766 1770 target = 2**18
1767 1771 for chunk in self.iter:
1768 1772 queue.append(chunk)
1769 1773 target -= len(chunk)
1770 1774 if target <= 0:
1771 1775 break
1772 1776 if not queue:
1773 1777 break
1774 1778
1775 1779 # The easy way to do this would be to queue.popleft(), modify the
1776 1780 # chunk (if necessary), then queue.appendleft(). However, for cases
1777 1781 # where we read partial chunk content, this incurs 2 dequeue
1778 1782 # mutations and creates a new str for the remaining chunk in the
1779 1783 # queue. Our code below avoids this overhead.
1780 1784
1781 1785 chunk = queue[0]
1782 1786 chunkl = len(chunk)
1783 1787 offset = self._chunkoffset
1784 1788
1785 1789 # Use full chunk.
1786 1790 if offset == 0 and left >= chunkl:
1787 1791 left -= chunkl
1788 1792 queue.popleft()
1789 1793 buf.append(chunk)
1790 1794 # self._chunkoffset remains at 0.
1791 1795 continue
1792 1796
1793 1797 chunkremaining = chunkl - offset
1794 1798
1795 1799 # Use all of unconsumed part of chunk.
1796 1800 if left >= chunkremaining:
1797 1801 left -= chunkremaining
1798 1802 queue.popleft()
1799 1803 # offset == 0 is enabled by block above, so this won't merely
1800 1804 # copy via ``chunk[0:]``.
1801 1805 buf.append(chunk[offset:])
1802 1806 self._chunkoffset = 0
1803 1807
1804 1808 # Partial chunk needed.
1805 1809 else:
1806 1810 buf.append(chunk[offset:offset + left])
1807 1811 self._chunkoffset += left
1808 1812 left -= chunkremaining
1809 1813
1810 1814 return ''.join(buf)
1811 1815
1812 1816 def filechunkiter(f, size=131072, limit=None):
1813 1817 """Create a generator that produces the data in the file size
1814 1818 (default 131072) bytes at a time, up to optional limit (default is
1815 1819 to read all data). Chunks may be less than size bytes if the
1816 1820 chunk is the last chunk in the file, or the file is a socket or
1817 1821 some other type of file that sometimes reads less data than is
1818 1822 requested."""
1819 1823 assert size >= 0
1820 1824 assert limit is None or limit >= 0
1821 1825 while True:
1822 1826 if limit is None:
1823 1827 nbytes = size
1824 1828 else:
1825 1829 nbytes = min(limit, size)
1826 1830 s = nbytes and f.read(nbytes)
1827 1831 if not s:
1828 1832 break
1829 1833 if limit:
1830 1834 limit -= len(s)
1831 1835 yield s
1832 1836
1833 1837 def makedate(timestamp=None):
1834 1838 '''Return a unix timestamp (or the current time) as a (unixtime,
1835 1839 offset) tuple based off the local timezone.'''
1836 1840 if timestamp is None:
1837 1841 timestamp = time.time()
1838 1842 if timestamp < 0:
1839 1843 hint = _("check your clock")
1840 1844 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1841 1845 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1842 1846 datetime.datetime.fromtimestamp(timestamp))
1843 1847 tz = delta.days * 86400 + delta.seconds
1844 1848 return timestamp, tz
1845 1849
1846 1850 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1847 1851 """represent a (unixtime, offset) tuple as a localized time.
1848 1852 unixtime is seconds since the epoch, and offset is the time zone's
1849 1853 number of seconds away from UTC.
1850 1854
1851 1855 >>> datestr((0, 0))
1852 1856 'Thu Jan 01 00:00:00 1970 +0000'
1853 1857 >>> datestr((42, 0))
1854 1858 'Thu Jan 01 00:00:42 1970 +0000'
1855 1859 >>> datestr((-42, 0))
1856 1860 'Wed Dec 31 23:59:18 1969 +0000'
1857 1861 >>> datestr((0x7fffffff, 0))
1858 1862 'Tue Jan 19 03:14:07 2038 +0000'
1859 1863 >>> datestr((-0x80000000, 0))
1860 1864 'Fri Dec 13 20:45:52 1901 +0000'
1861 1865 """
1862 1866 t, tz = date or makedate()
1863 1867 if "%1" in format or "%2" in format or "%z" in format:
1864 1868 sign = (tz > 0) and "-" or "+"
1865 1869 minutes = abs(tz) // 60
1866 1870 q, r = divmod(minutes, 60)
1867 1871 format = format.replace("%z", "%1%2")
1868 1872 format = format.replace("%1", "%c%02d" % (sign, q))
1869 1873 format = format.replace("%2", "%02d" % r)
1870 1874 d = t - tz
1871 1875 if d > 0x7fffffff:
1872 1876 d = 0x7fffffff
1873 1877 elif d < -0x80000000:
1874 1878 d = -0x80000000
1875 1879 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1876 1880 # because they use the gmtime() system call which is buggy on Windows
1877 1881 # for negative values.
1878 1882 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1879 1883 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1880 1884 return s
1881 1885
1882 1886 def shortdate(date=None):
1883 1887 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1884 1888 return datestr(date, format='%Y-%m-%d')
1885 1889
1886 1890 def parsetimezone(s):
1887 1891 """find a trailing timezone, if any, in string, and return a
1888 1892 (offset, remainder) pair"""
1889 1893
1890 1894 if s.endswith("GMT") or s.endswith("UTC"):
1891 1895 return 0, s[:-3].rstrip()
1892 1896
1893 1897 # Unix-style timezones [+-]hhmm
1894 1898 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1895 1899 sign = (s[-5] == "+") and 1 or -1
1896 1900 hours = int(s[-4:-2])
1897 1901 minutes = int(s[-2:])
1898 1902 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1899 1903
1900 1904 # ISO8601 trailing Z
1901 1905 if s.endswith("Z") and s[-2:-1].isdigit():
1902 1906 return 0, s[:-1]
1903 1907
1904 1908 # ISO8601-style [+-]hh:mm
1905 1909 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1906 1910 s[-5:-3].isdigit() and s[-2:].isdigit()):
1907 1911 sign = (s[-6] == "+") and 1 or -1
1908 1912 hours = int(s[-5:-3])
1909 1913 minutes = int(s[-2:])
1910 1914 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1911 1915
1912 1916 return None, s
1913 1917
1914 1918 def strdate(string, format, defaults=None):
1915 1919 """parse a localized time string and return a (unixtime, offset) tuple.
1916 1920 if the string cannot be parsed, ValueError is raised."""
1917 1921 if defaults is None:
1918 1922 defaults = {}
1919 1923
1920 1924 # NOTE: unixtime = localunixtime + offset
1921 1925 offset, date = parsetimezone(string)
1922 1926
1923 1927 # add missing elements from defaults
1924 1928 usenow = False # default to using biased defaults
1925 1929 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1926 1930 part = pycompat.bytestr(part)
1927 1931 found = [True for p in part if ("%"+p) in format]
1928 1932 if not found:
1929 1933 date += "@" + defaults[part][usenow]
1930 1934 format += "@%" + part[0]
1931 1935 else:
1932 1936 # We've found a specific time element, less specific time
1933 1937 # elements are relative to today
1934 1938 usenow = True
1935 1939
1936 1940 timetuple = time.strptime(encoding.strfromlocal(date),
1937 1941 encoding.strfromlocal(format))
1938 1942 localunixtime = int(calendar.timegm(timetuple))
1939 1943 if offset is None:
1940 1944 # local timezone
1941 1945 unixtime = int(time.mktime(timetuple))
1942 1946 offset = unixtime - localunixtime
1943 1947 else:
1944 1948 unixtime = localunixtime + offset
1945 1949 return unixtime, offset
1946 1950
1947 1951 def parsedate(date, formats=None, bias=None):
1948 1952 """parse a localized date/time and return a (unixtime, offset) tuple.
1949 1953
1950 1954 The date may be a "unixtime offset" string or in one of the specified
1951 1955 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1952 1956
1953 1957 >>> parsedate(' today ') == parsedate(\
1954 1958 datetime.date.today().strftime('%b %d'))
1955 1959 True
1956 1960 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1957 1961 datetime.timedelta(days=1)\
1958 1962 ).strftime('%b %d'))
1959 1963 True
1960 1964 >>> now, tz = makedate()
1961 1965 >>> strnow, strtz = parsedate('now')
1962 1966 >>> (strnow - now) < 1
1963 1967 True
1964 1968 >>> tz == strtz
1965 1969 True
1966 1970 """
1967 1971 if bias is None:
1968 1972 bias = {}
1969 1973 if not date:
1970 1974 return 0, 0
1971 1975 if isinstance(date, tuple) and len(date) == 2:
1972 1976 return date
1973 1977 if not formats:
1974 1978 formats = defaultdateformats
1975 1979 date = date.strip()
1976 1980
1977 1981 if date == 'now' or date == _('now'):
1978 1982 return makedate()
1979 1983 if date == 'today' or date == _('today'):
1980 1984 date = datetime.date.today().strftime('%b %d')
1981 1985 elif date == 'yesterday' or date == _('yesterday'):
1982 1986 date = (datetime.date.today() -
1983 1987 datetime.timedelta(days=1)).strftime('%b %d')
1984 1988
1985 1989 try:
1986 1990 when, offset = map(int, date.split(' '))
1987 1991 except ValueError:
1988 1992 # fill out defaults
1989 1993 now = makedate()
1990 1994 defaults = {}
1991 1995 for part in ("d", "mb", "yY", "HI", "M", "S"):
1992 1996 # this piece is for rounding the specific end of unknowns
1993 1997 b = bias.get(part)
1994 1998 if b is None:
1995 1999 if part[0:1] in "HMS":
1996 2000 b = "00"
1997 2001 else:
1998 2002 b = "0"
1999 2003
2000 2004 # this piece is for matching the generic end to today's date
2001 2005 n = datestr(now, "%" + part[0:1])
2002 2006
2003 2007 defaults[part] = (b, n)
2004 2008
2005 2009 for format in formats:
2006 2010 try:
2007 2011 when, offset = strdate(date, format, defaults)
2008 2012 except (ValueError, OverflowError):
2009 2013 pass
2010 2014 else:
2011 2015 break
2012 2016 else:
2013 2017 raise error.ParseError(_('invalid date: %r') % date)
2014 2018 # validate explicit (probably user-specified) date and
2015 2019 # time zone offset. values must fit in signed 32 bits for
2016 2020 # current 32-bit linux runtimes. timezones go from UTC-12
2017 2021 # to UTC+14
2018 2022 if when < -0x80000000 or when > 0x7fffffff:
2019 2023 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2020 2024 if offset < -50400 or offset > 43200:
2021 2025 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2022 2026 return when, offset
2023 2027
2024 2028 def matchdate(date):
2025 2029 """Return a function that matches a given date match specifier
2026 2030
2027 2031 Formats include:
2028 2032
2029 2033 '{date}' match a given date to the accuracy provided
2030 2034
2031 2035 '<{date}' on or before a given date
2032 2036
2033 2037 '>{date}' on or after a given date
2034 2038
2035 2039 >>> p1 = parsedate("10:29:59")
2036 2040 >>> p2 = parsedate("10:30:00")
2037 2041 >>> p3 = parsedate("10:30:59")
2038 2042 >>> p4 = parsedate("10:31:00")
2039 2043 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2040 2044 >>> f = matchdate("10:30")
2041 2045 >>> f(p1[0])
2042 2046 False
2043 2047 >>> f(p2[0])
2044 2048 True
2045 2049 >>> f(p3[0])
2046 2050 True
2047 2051 >>> f(p4[0])
2048 2052 False
2049 2053 >>> f(p5[0])
2050 2054 False
2051 2055 """
2052 2056
2053 2057 def lower(date):
2054 2058 d = {'mb': "1", 'd': "1"}
2055 2059 return parsedate(date, extendeddateformats, d)[0]
2056 2060
2057 2061 def upper(date):
2058 2062 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2059 2063 for days in ("31", "30", "29"):
2060 2064 try:
2061 2065 d["d"] = days
2062 2066 return parsedate(date, extendeddateformats, d)[0]
2063 2067 except Abort:
2064 2068 pass
2065 2069 d["d"] = "28"
2066 2070 return parsedate(date, extendeddateformats, d)[0]
2067 2071
2068 2072 date = date.strip()
2069 2073
2070 2074 if not date:
2071 2075 raise Abort(_("dates cannot consist entirely of whitespace"))
2072 2076 elif date[0] == "<":
2073 2077 if not date[1:]:
2074 2078 raise Abort(_("invalid day spec, use '<DATE'"))
2075 2079 when = upper(date[1:])
2076 2080 return lambda x: x <= when
2077 2081 elif date[0] == ">":
2078 2082 if not date[1:]:
2079 2083 raise Abort(_("invalid day spec, use '>DATE'"))
2080 2084 when = lower(date[1:])
2081 2085 return lambda x: x >= when
2082 2086 elif date[0] == "-":
2083 2087 try:
2084 2088 days = int(date[1:])
2085 2089 except ValueError:
2086 2090 raise Abort(_("invalid day spec: %s") % date[1:])
2087 2091 if days < 0:
2088 2092 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2089 2093 % date[1:])
2090 2094 when = makedate()[0] - days * 3600 * 24
2091 2095 return lambda x: x >= when
2092 2096 elif " to " in date:
2093 2097 a, b = date.split(" to ")
2094 2098 start, stop = lower(a), upper(b)
2095 2099 return lambda x: x >= start and x <= stop
2096 2100 else:
2097 2101 start, stop = lower(date), upper(date)
2098 2102 return lambda x: x >= start and x <= stop
2099 2103
2100 2104 def stringmatcher(pattern, casesensitive=True):
2101 2105 """
2102 2106 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2103 2107 returns the matcher name, pattern, and matcher function.
2104 2108 missing or unknown prefixes are treated as literal matches.
2105 2109
2106 2110 helper for tests:
2107 2111 >>> def test(pattern, *tests):
2108 2112 ... kind, pattern, matcher = stringmatcher(pattern)
2109 2113 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2110 2114 >>> def itest(pattern, *tests):
2111 2115 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2112 2116 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2113 2117
2114 2118 exact matching (no prefix):
2115 2119 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2116 2120 ('literal', 'abcdefg', [False, False, True])
2117 2121
2118 2122 regex matching ('re:' prefix)
2119 2123 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2120 2124 ('re', 'a.+b', [False, False, True])
2121 2125
2122 2126 force exact matches ('literal:' prefix)
2123 2127 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2124 2128 ('literal', 're:foobar', [False, True])
2125 2129
2126 2130 unknown prefixes are ignored and treated as literals
2127 2131 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2128 2132 ('literal', 'foo:bar', [False, False, True])
2129 2133
2130 2134 case insensitive regex matches
2131 2135 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2132 2136 ('re', 'A.+b', [False, False, True])
2133 2137
2134 2138 case insensitive literal matches
2135 2139 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2136 2140 ('literal', 'ABCDEFG', [False, False, True])
2137 2141 """
2138 2142 if pattern.startswith('re:'):
2139 2143 pattern = pattern[3:]
2140 2144 try:
2141 2145 flags = 0
2142 2146 if not casesensitive:
2143 2147 flags = remod.I
2144 2148 regex = remod.compile(pattern, flags)
2145 2149 except remod.error as e:
2146 2150 raise error.ParseError(_('invalid regular expression: %s')
2147 2151 % e)
2148 2152 return 're', pattern, regex.search
2149 2153 elif pattern.startswith('literal:'):
2150 2154 pattern = pattern[8:]
2151 2155
2152 2156 match = pattern.__eq__
2153 2157
2154 2158 if not casesensitive:
2155 2159 ipat = encoding.lower(pattern)
2156 2160 match = lambda s: ipat == encoding.lower(s)
2157 2161 return 'literal', pattern, match
2158 2162
2159 2163 def shortuser(user):
2160 2164 """Return a short representation of a user name or email address."""
2161 2165 f = user.find('@')
2162 2166 if f >= 0:
2163 2167 user = user[:f]
2164 2168 f = user.find('<')
2165 2169 if f >= 0:
2166 2170 user = user[f + 1:]
2167 2171 f = user.find(' ')
2168 2172 if f >= 0:
2169 2173 user = user[:f]
2170 2174 f = user.find('.')
2171 2175 if f >= 0:
2172 2176 user = user[:f]
2173 2177 return user
2174 2178
2175 2179 def emailuser(user):
2176 2180 """Return the user portion of an email address."""
2177 2181 f = user.find('@')
2178 2182 if f >= 0:
2179 2183 user = user[:f]
2180 2184 f = user.find('<')
2181 2185 if f >= 0:
2182 2186 user = user[f + 1:]
2183 2187 return user
2184 2188
2185 2189 def email(author):
2186 2190 '''get email of author.'''
2187 2191 r = author.find('>')
2188 2192 if r == -1:
2189 2193 r = None
2190 2194 return author[author.find('<') + 1:r]
2191 2195
2192 2196 def ellipsis(text, maxlength=400):
2193 2197 """Trim string to at most maxlength (default: 400) columns in display."""
2194 2198 return encoding.trim(text, maxlength, ellipsis='...')
2195 2199
2196 2200 def unitcountfn(*unittable):
2197 2201 '''return a function that renders a readable count of some quantity'''
2198 2202
2199 2203 def go(count):
2200 2204 for multiplier, divisor, format in unittable:
2201 2205 if abs(count) >= divisor * multiplier:
2202 2206 return format % (count / float(divisor))
2203 2207 return unittable[-1][2] % count
2204 2208
2205 2209 return go
2206 2210
2207 2211 def processlinerange(fromline, toline):
2208 2212 """Check that linerange <fromline>:<toline> makes sense and return a
2209 2213 0-based range.
2210 2214
2211 2215 >>> processlinerange(10, 20)
2212 2216 (9, 20)
2213 2217 >>> processlinerange(2, 1)
2214 2218 Traceback (most recent call last):
2215 2219 ...
2216 2220 ParseError: line range must be positive
2217 2221 >>> processlinerange(0, 5)
2218 2222 Traceback (most recent call last):
2219 2223 ...
2220 2224 ParseError: fromline must be strictly positive
2221 2225 """
2222 2226 if toline - fromline < 0:
2223 2227 raise error.ParseError(_("line range must be positive"))
2224 2228 if fromline < 1:
2225 2229 raise error.ParseError(_("fromline must be strictly positive"))
2226 2230 return fromline - 1, toline
2227 2231
2228 2232 bytecount = unitcountfn(
2229 2233 (100, 1 << 30, _('%.0f GB')),
2230 2234 (10, 1 << 30, _('%.1f GB')),
2231 2235 (1, 1 << 30, _('%.2f GB')),
2232 2236 (100, 1 << 20, _('%.0f MB')),
2233 2237 (10, 1 << 20, _('%.1f MB')),
2234 2238 (1, 1 << 20, _('%.2f MB')),
2235 2239 (100, 1 << 10, _('%.0f KB')),
2236 2240 (10, 1 << 10, _('%.1f KB')),
2237 2241 (1, 1 << 10, _('%.2f KB')),
2238 2242 (1, 1, _('%.0f bytes')),
2239 2243 )
2240 2244
2241 2245 # Matches a single EOL which can either be a CRLF where repeated CR
2242 2246 # are removed or a LF. We do not care about old Macintosh files, so a
2243 2247 # stray CR is an error.
2244 2248 _eolre = remod.compile(br'\r*\n')
2245 2249
2246 2250 def tolf(s):
2247 2251 return _eolre.sub('\n', s)
2248 2252
2249 2253 def tocrlf(s):
2250 2254 return _eolre.sub('\r\n', s)
2251 2255
2252 2256 if pycompat.oslinesep == '\r\n':
2253 2257 tonativeeol = tocrlf
2254 2258 fromnativeeol = tolf
2255 2259 else:
2256 2260 tonativeeol = pycompat.identity
2257 2261 fromnativeeol = pycompat.identity
2258 2262
2259 2263 def escapestr(s):
2260 2264 # call underlying function of s.encode('string_escape') directly for
2261 2265 # Python 3 compatibility
2262 2266 return codecs.escape_encode(s)[0]
2263 2267
2264 2268 def unescapestr(s):
2265 2269 return codecs.escape_decode(s)[0]
2266 2270
2267 2271 def uirepr(s):
2268 2272 # Avoid double backslash in Windows path repr()
2269 2273 return repr(s).replace('\\\\', '\\')
2270 2274
2271 2275 # delay import of textwrap
2272 2276 def MBTextWrapper(**kwargs):
2273 2277 class tw(textwrap.TextWrapper):
2274 2278 """
2275 2279 Extend TextWrapper for width-awareness.
2276 2280
2277 2281 Neither number of 'bytes' in any encoding nor 'characters' is
2278 2282 appropriate to calculate terminal columns for specified string.
2279 2283
2280 2284 Original TextWrapper implementation uses built-in 'len()' directly,
2281 2285 so overriding is needed to use width information of each characters.
2282 2286
2283 2287 In addition, characters classified into 'ambiguous' width are
2284 2288 treated as wide in East Asian area, but as narrow in other.
2285 2289
2286 2290 This requires use decision to determine width of such characters.
2287 2291 """
2288 2292 def _cutdown(self, ucstr, space_left):
2289 2293 l = 0
2290 2294 colwidth = encoding.ucolwidth
2291 2295 for i in xrange(len(ucstr)):
2292 2296 l += colwidth(ucstr[i])
2293 2297 if space_left < l:
2294 2298 return (ucstr[:i], ucstr[i:])
2295 2299 return ucstr, ''
2296 2300
2297 2301 # overriding of base class
2298 2302 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2299 2303 space_left = max(width - cur_len, 1)
2300 2304
2301 2305 if self.break_long_words:
2302 2306 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2303 2307 cur_line.append(cut)
2304 2308 reversed_chunks[-1] = res
2305 2309 elif not cur_line:
2306 2310 cur_line.append(reversed_chunks.pop())
2307 2311
2308 2312 # this overriding code is imported from TextWrapper of Python 2.6
2309 2313 # to calculate columns of string by 'encoding.ucolwidth()'
2310 2314 def _wrap_chunks(self, chunks):
2311 2315 colwidth = encoding.ucolwidth
2312 2316
2313 2317 lines = []
2314 2318 if self.width <= 0:
2315 2319 raise ValueError("invalid width %r (must be > 0)" % self.width)
2316 2320
2317 2321 # Arrange in reverse order so items can be efficiently popped
2318 2322 # from a stack of chucks.
2319 2323 chunks.reverse()
2320 2324
2321 2325 while chunks:
2322 2326
2323 2327 # Start the list of chunks that will make up the current line.
2324 2328 # cur_len is just the length of all the chunks in cur_line.
2325 2329 cur_line = []
2326 2330 cur_len = 0
2327 2331
2328 2332 # Figure out which static string will prefix this line.
2329 2333 if lines:
2330 2334 indent = self.subsequent_indent
2331 2335 else:
2332 2336 indent = self.initial_indent
2333 2337
2334 2338 # Maximum width for this line.
2335 2339 width = self.width - len(indent)
2336 2340
2337 2341 # First chunk on line is whitespace -- drop it, unless this
2338 2342 # is the very beginning of the text (i.e. no lines started yet).
2339 2343 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2340 2344 del chunks[-1]
2341 2345
2342 2346 while chunks:
2343 2347 l = colwidth(chunks[-1])
2344 2348
2345 2349 # Can at least squeeze this chunk onto the current line.
2346 2350 if cur_len + l <= width:
2347 2351 cur_line.append(chunks.pop())
2348 2352 cur_len += l
2349 2353
2350 2354 # Nope, this line is full.
2351 2355 else:
2352 2356 break
2353 2357
2354 2358 # The current line is full, and the next chunk is too big to
2355 2359 # fit on *any* line (not just this one).
2356 2360 if chunks and colwidth(chunks[-1]) > width:
2357 2361 self._handle_long_word(chunks, cur_line, cur_len, width)
2358 2362
2359 2363 # If the last chunk on this line is all whitespace, drop it.
2360 2364 if (self.drop_whitespace and
2361 2365 cur_line and cur_line[-1].strip() == r''):
2362 2366 del cur_line[-1]
2363 2367
2364 2368 # Convert current line back to a string and store it in list
2365 2369 # of all lines (return value).
2366 2370 if cur_line:
2367 2371 lines.append(indent + r''.join(cur_line))
2368 2372
2369 2373 return lines
2370 2374
2371 2375 global MBTextWrapper
2372 2376 MBTextWrapper = tw
2373 2377 return tw(**kwargs)
2374 2378
2375 2379 def wrap(line, width, initindent='', hangindent=''):
2376 2380 maxindent = max(len(hangindent), len(initindent))
2377 2381 if width <= maxindent:
2378 2382 # adjust for weird terminal size
2379 2383 width = max(78, maxindent + 1)
2380 2384 line = line.decode(pycompat.sysstr(encoding.encoding),
2381 2385 pycompat.sysstr(encoding.encodingmode))
2382 2386 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2383 2387 pycompat.sysstr(encoding.encodingmode))
2384 2388 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2385 2389 pycompat.sysstr(encoding.encodingmode))
2386 2390 wrapper = MBTextWrapper(width=width,
2387 2391 initial_indent=initindent,
2388 2392 subsequent_indent=hangindent)
2389 2393 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2390 2394
2391 2395 if (pyplatform.python_implementation() == 'CPython' and
2392 2396 sys.version_info < (3, 0)):
2393 2397 # There is an issue in CPython that some IO methods do not handle EINTR
2394 2398 # correctly. The following table shows what CPython version (and functions)
2395 2399 # are affected (buggy: has the EINTR bug, okay: otherwise):
2396 2400 #
2397 2401 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2398 2402 # --------------------------------------------------
2399 2403 # fp.__iter__ | buggy | buggy | okay
2400 2404 # fp.read* | buggy | okay [1] | okay
2401 2405 #
2402 2406 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2403 2407 #
2404 2408 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2405 2409 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2406 2410 #
2407 2411 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2408 2412 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2409 2413 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2410 2414 # fp.__iter__ but not other fp.read* methods.
2411 2415 #
2412 2416 # On modern systems like Linux, the "read" syscall cannot be interrupted
2413 2417 # when reading "fast" files like on-disk files. So the EINTR issue only
2414 2418 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2415 2419 # files approximately as "fast" files and use the fast (unsafe) code path,
2416 2420 # to minimize the performance impact.
2417 2421 if sys.version_info >= (2, 7, 4):
2418 2422 # fp.readline deals with EINTR correctly, use it as a workaround.
2419 2423 def _safeiterfile(fp):
2420 2424 return iter(fp.readline, '')
2421 2425 else:
2422 2426 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2423 2427 # note: this may block longer than necessary because of bufsize.
2424 2428 def _safeiterfile(fp, bufsize=4096):
2425 2429 fd = fp.fileno()
2426 2430 line = ''
2427 2431 while True:
2428 2432 try:
2429 2433 buf = os.read(fd, bufsize)
2430 2434 except OSError as ex:
2431 2435 # os.read only raises EINTR before any data is read
2432 2436 if ex.errno == errno.EINTR:
2433 2437 continue
2434 2438 else:
2435 2439 raise
2436 2440 line += buf
2437 2441 if '\n' in buf:
2438 2442 splitted = line.splitlines(True)
2439 2443 line = ''
2440 2444 for l in splitted:
2441 2445 if l[-1] == '\n':
2442 2446 yield l
2443 2447 else:
2444 2448 line = l
2445 2449 if not buf:
2446 2450 break
2447 2451 if line:
2448 2452 yield line
2449 2453
2450 2454 def iterfile(fp):
2451 2455 fastpath = True
2452 2456 if type(fp) is file:
2453 2457 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2454 2458 if fastpath:
2455 2459 return fp
2456 2460 else:
2457 2461 return _safeiterfile(fp)
2458 2462 else:
2459 2463 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2460 2464 def iterfile(fp):
2461 2465 return fp
2462 2466
2463 2467 def iterlines(iterator):
2464 2468 for chunk in iterator:
2465 2469 for line in chunk.splitlines():
2466 2470 yield line
2467 2471
2468 2472 def expandpath(path):
2469 2473 return os.path.expanduser(os.path.expandvars(path))
2470 2474
2471 2475 def hgcmd():
2472 2476 """Return the command used to execute current hg
2473 2477
2474 2478 This is different from hgexecutable() because on Windows we want
2475 2479 to avoid things opening new shell windows like batch files, so we
2476 2480 get either the python call or current executable.
2477 2481 """
2478 2482 if mainfrozen():
2479 2483 if getattr(sys, 'frozen', None) == 'macosx_app':
2480 2484 # Env variable set by py2app
2481 2485 return [encoding.environ['EXECUTABLEPATH']]
2482 2486 else:
2483 2487 return [pycompat.sysexecutable]
2484 2488 return gethgcmd()
2485 2489
2486 2490 def rundetached(args, condfn):
2487 2491 """Execute the argument list in a detached process.
2488 2492
2489 2493 condfn is a callable which is called repeatedly and should return
2490 2494 True once the child process is known to have started successfully.
2491 2495 At this point, the child process PID is returned. If the child
2492 2496 process fails to start or finishes before condfn() evaluates to
2493 2497 True, return -1.
2494 2498 """
2495 2499 # Windows case is easier because the child process is either
2496 2500 # successfully starting and validating the condition or exiting
2497 2501 # on failure. We just poll on its PID. On Unix, if the child
2498 2502 # process fails to start, it will be left in a zombie state until
2499 2503 # the parent wait on it, which we cannot do since we expect a long
2500 2504 # running process on success. Instead we listen for SIGCHLD telling
2501 2505 # us our child process terminated.
2502 2506 terminated = set()
2503 2507 def handler(signum, frame):
2504 2508 terminated.add(os.wait())
2505 2509 prevhandler = None
2506 2510 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2507 2511 if SIGCHLD is not None:
2508 2512 prevhandler = signal.signal(SIGCHLD, handler)
2509 2513 try:
2510 2514 pid = spawndetached(args)
2511 2515 while not condfn():
2512 2516 if ((pid in terminated or not testpid(pid))
2513 2517 and not condfn()):
2514 2518 return -1
2515 2519 time.sleep(0.1)
2516 2520 return pid
2517 2521 finally:
2518 2522 if prevhandler is not None:
2519 2523 signal.signal(signal.SIGCHLD, prevhandler)
2520 2524
2521 2525 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2522 2526 """Return the result of interpolating items in the mapping into string s.
2523 2527
2524 2528 prefix is a single character string, or a two character string with
2525 2529 a backslash as the first character if the prefix needs to be escaped in
2526 2530 a regular expression.
2527 2531
2528 2532 fn is an optional function that will be applied to the replacement text
2529 2533 just before replacement.
2530 2534
2531 2535 escape_prefix is an optional flag that allows using doubled prefix for
2532 2536 its escaping.
2533 2537 """
2534 2538 fn = fn or (lambda s: s)
2535 2539 patterns = '|'.join(mapping.keys())
2536 2540 if escape_prefix:
2537 2541 patterns += '|' + prefix
2538 2542 if len(prefix) > 1:
2539 2543 prefix_char = prefix[1:]
2540 2544 else:
2541 2545 prefix_char = prefix
2542 2546 mapping[prefix_char] = prefix_char
2543 2547 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2544 2548 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2545 2549
2546 2550 def getport(port):
2547 2551 """Return the port for a given network service.
2548 2552
2549 2553 If port is an integer, it's returned as is. If it's a string, it's
2550 2554 looked up using socket.getservbyname(). If there's no matching
2551 2555 service, error.Abort is raised.
2552 2556 """
2553 2557 try:
2554 2558 return int(port)
2555 2559 except ValueError:
2556 2560 pass
2557 2561
2558 2562 try:
2559 2563 return socket.getservbyname(port)
2560 2564 except socket.error:
2561 2565 raise Abort(_("no port number associated with service '%s'") % port)
2562 2566
2563 2567 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2564 2568 '0': False, 'no': False, 'false': False, 'off': False,
2565 2569 'never': False}
2566 2570
2567 2571 def parsebool(s):
2568 2572 """Parse s into a boolean.
2569 2573
2570 2574 If s is not a valid boolean, returns None.
2571 2575 """
2572 2576 return _booleans.get(s.lower(), None)
2573 2577
2574 2578 _hextochr = dict((a + b, chr(int(a + b, 16)))
2575 2579 for a in string.hexdigits for b in string.hexdigits)
2576 2580
2577 2581 class url(object):
2578 2582 r"""Reliable URL parser.
2579 2583
2580 2584 This parses URLs and provides attributes for the following
2581 2585 components:
2582 2586
2583 2587 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2584 2588
2585 2589 Missing components are set to None. The only exception is
2586 2590 fragment, which is set to '' if present but empty.
2587 2591
2588 2592 If parsefragment is False, fragment is included in query. If
2589 2593 parsequery is False, query is included in path. If both are
2590 2594 False, both fragment and query are included in path.
2591 2595
2592 2596 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2593 2597
2594 2598 Note that for backward compatibility reasons, bundle URLs do not
2595 2599 take host names. That means 'bundle://../' has a path of '../'.
2596 2600
2597 2601 Examples:
2598 2602
2599 2603 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2600 2604 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2601 2605 >>> url('ssh://[::1]:2200//home/joe/repo')
2602 2606 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2603 2607 >>> url('file:///home/joe/repo')
2604 2608 <url scheme: 'file', path: '/home/joe/repo'>
2605 2609 >>> url('file:///c:/temp/foo/')
2606 2610 <url scheme: 'file', path: 'c:/temp/foo/'>
2607 2611 >>> url('bundle:foo')
2608 2612 <url scheme: 'bundle', path: 'foo'>
2609 2613 >>> url('bundle://../foo')
2610 2614 <url scheme: 'bundle', path: '../foo'>
2611 2615 >>> url(r'c:\foo\bar')
2612 2616 <url path: 'c:\\foo\\bar'>
2613 2617 >>> url(r'\\blah\blah\blah')
2614 2618 <url path: '\\\\blah\\blah\\blah'>
2615 2619 >>> url(r'\\blah\blah\blah#baz')
2616 2620 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2617 2621 >>> url(r'file:///C:\users\me')
2618 2622 <url scheme: 'file', path: 'C:\\users\\me'>
2619 2623
2620 2624 Authentication credentials:
2621 2625
2622 2626 >>> url('ssh://joe:xyz@x/repo')
2623 2627 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2624 2628 >>> url('ssh://joe@x/repo')
2625 2629 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2626 2630
2627 2631 Query strings and fragments:
2628 2632
2629 2633 >>> url('http://host/a?b#c')
2630 2634 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2631 2635 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2632 2636 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2633 2637
2634 2638 Empty path:
2635 2639
2636 2640 >>> url('')
2637 2641 <url path: ''>
2638 2642 >>> url('#a')
2639 2643 <url path: '', fragment: 'a'>
2640 2644 >>> url('http://host/')
2641 2645 <url scheme: 'http', host: 'host', path: ''>
2642 2646 >>> url('http://host/#a')
2643 2647 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2644 2648
2645 2649 Only scheme:
2646 2650
2647 2651 >>> url('http:')
2648 2652 <url scheme: 'http'>
2649 2653 """
2650 2654
2651 2655 _safechars = "!~*'()+"
2652 2656 _safepchars = "/!~*'()+:\\"
2653 2657 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2654 2658
2655 2659 def __init__(self, path, parsequery=True, parsefragment=True):
2656 2660 # We slowly chomp away at path until we have only the path left
2657 2661 self.scheme = self.user = self.passwd = self.host = None
2658 2662 self.port = self.path = self.query = self.fragment = None
2659 2663 self._localpath = True
2660 2664 self._hostport = ''
2661 2665 self._origpath = path
2662 2666
2663 2667 if parsefragment and '#' in path:
2664 2668 path, self.fragment = path.split('#', 1)
2665 2669
2666 2670 # special case for Windows drive letters and UNC paths
2667 2671 if hasdriveletter(path) or path.startswith('\\\\'):
2668 2672 self.path = path
2669 2673 return
2670 2674
2671 2675 # For compatibility reasons, we can't handle bundle paths as
2672 2676 # normal URLS
2673 2677 if path.startswith('bundle:'):
2674 2678 self.scheme = 'bundle'
2675 2679 path = path[7:]
2676 2680 if path.startswith('//'):
2677 2681 path = path[2:]
2678 2682 self.path = path
2679 2683 return
2680 2684
2681 2685 if self._matchscheme(path):
2682 2686 parts = path.split(':', 1)
2683 2687 if parts[0]:
2684 2688 self.scheme, path = parts
2685 2689 self._localpath = False
2686 2690
2687 2691 if not path:
2688 2692 path = None
2689 2693 if self._localpath:
2690 2694 self.path = ''
2691 2695 return
2692 2696 else:
2693 2697 if self._localpath:
2694 2698 self.path = path
2695 2699 return
2696 2700
2697 2701 if parsequery and '?' in path:
2698 2702 path, self.query = path.split('?', 1)
2699 2703 if not path:
2700 2704 path = None
2701 2705 if not self.query:
2702 2706 self.query = None
2703 2707
2704 2708 # // is required to specify a host/authority
2705 2709 if path and path.startswith('//'):
2706 2710 parts = path[2:].split('/', 1)
2707 2711 if len(parts) > 1:
2708 2712 self.host, path = parts
2709 2713 else:
2710 2714 self.host = parts[0]
2711 2715 path = None
2712 2716 if not self.host:
2713 2717 self.host = None
2714 2718 # path of file:///d is /d
2715 2719 # path of file:///d:/ is d:/, not /d:/
2716 2720 if path and not hasdriveletter(path):
2717 2721 path = '/' + path
2718 2722
2719 2723 if self.host and '@' in self.host:
2720 2724 self.user, self.host = self.host.rsplit('@', 1)
2721 2725 if ':' in self.user:
2722 2726 self.user, self.passwd = self.user.split(':', 1)
2723 2727 if not self.host:
2724 2728 self.host = None
2725 2729
2726 2730 # Don't split on colons in IPv6 addresses without ports
2727 2731 if (self.host and ':' in self.host and
2728 2732 not (self.host.startswith('[') and self.host.endswith(']'))):
2729 2733 self._hostport = self.host
2730 2734 self.host, self.port = self.host.rsplit(':', 1)
2731 2735 if not self.host:
2732 2736 self.host = None
2733 2737
2734 2738 if (self.host and self.scheme == 'file' and
2735 2739 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2736 2740 raise Abort(_('file:// URLs can only refer to localhost'))
2737 2741
2738 2742 self.path = path
2739 2743
2740 2744 # leave the query string escaped
2741 2745 for a in ('user', 'passwd', 'host', 'port',
2742 2746 'path', 'fragment'):
2743 2747 v = getattr(self, a)
2744 2748 if v is not None:
2745 2749 setattr(self, a, urlreq.unquote(v))
2746 2750
2747 2751 def __repr__(self):
2748 2752 attrs = []
2749 2753 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2750 2754 'query', 'fragment'):
2751 2755 v = getattr(self, a)
2752 2756 if v is not None:
2753 2757 attrs.append('%s: %r' % (a, v))
2754 2758 return '<url %s>' % ', '.join(attrs)
2755 2759
2756 2760 def __bytes__(self):
2757 2761 r"""Join the URL's components back into a URL string.
2758 2762
2759 2763 Examples:
2760 2764
2761 2765 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2762 2766 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2763 2767 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2764 2768 'http://user:pw@host:80/?foo=bar&baz=42'
2765 2769 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2766 2770 'http://user:pw@host:80/?foo=bar%3dbaz'
2767 2771 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2768 2772 'ssh://user:pw@[::1]:2200//home/joe#'
2769 2773 >>> str(url('http://localhost:80//'))
2770 2774 'http://localhost:80//'
2771 2775 >>> str(url('http://localhost:80/'))
2772 2776 'http://localhost:80/'
2773 2777 >>> str(url('http://localhost:80'))
2774 2778 'http://localhost:80/'
2775 2779 >>> str(url('bundle:foo'))
2776 2780 'bundle:foo'
2777 2781 >>> str(url('bundle://../foo'))
2778 2782 'bundle:../foo'
2779 2783 >>> str(url('path'))
2780 2784 'path'
2781 2785 >>> str(url('file:///tmp/foo/bar'))
2782 2786 'file:///tmp/foo/bar'
2783 2787 >>> str(url('file:///c:/tmp/foo/bar'))
2784 2788 'file:///c:/tmp/foo/bar'
2785 2789 >>> print url(r'bundle:foo\bar')
2786 2790 bundle:foo\bar
2787 2791 >>> print url(r'file:///D:\data\hg')
2788 2792 file:///D:\data\hg
2789 2793 """
2790 2794 if self._localpath:
2791 2795 s = self.path
2792 2796 if self.scheme == 'bundle':
2793 2797 s = 'bundle:' + s
2794 2798 if self.fragment:
2795 2799 s += '#' + self.fragment
2796 2800 return s
2797 2801
2798 2802 s = self.scheme + ':'
2799 2803 if self.user or self.passwd or self.host:
2800 2804 s += '//'
2801 2805 elif self.scheme and (not self.path or self.path.startswith('/')
2802 2806 or hasdriveletter(self.path)):
2803 2807 s += '//'
2804 2808 if hasdriveletter(self.path):
2805 2809 s += '/'
2806 2810 if self.user:
2807 2811 s += urlreq.quote(self.user, safe=self._safechars)
2808 2812 if self.passwd:
2809 2813 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2810 2814 if self.user or self.passwd:
2811 2815 s += '@'
2812 2816 if self.host:
2813 2817 if not (self.host.startswith('[') and self.host.endswith(']')):
2814 2818 s += urlreq.quote(self.host)
2815 2819 else:
2816 2820 s += self.host
2817 2821 if self.port:
2818 2822 s += ':' + urlreq.quote(self.port)
2819 2823 if self.host:
2820 2824 s += '/'
2821 2825 if self.path:
2822 2826 # TODO: similar to the query string, we should not unescape the
2823 2827 # path when we store it, the path might contain '%2f' = '/',
2824 2828 # which we should *not* escape.
2825 2829 s += urlreq.quote(self.path, safe=self._safepchars)
2826 2830 if self.query:
2827 2831 # we store the query in escaped form.
2828 2832 s += '?' + self.query
2829 2833 if self.fragment is not None:
2830 2834 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2831 2835 return s
2832 2836
2833 2837 __str__ = encoding.strmethod(__bytes__)
2834 2838
2835 2839 def authinfo(self):
2836 2840 user, passwd = self.user, self.passwd
2837 2841 try:
2838 2842 self.user, self.passwd = None, None
2839 2843 s = bytes(self)
2840 2844 finally:
2841 2845 self.user, self.passwd = user, passwd
2842 2846 if not self.user:
2843 2847 return (s, None)
2844 2848 # authinfo[1] is passed to urllib2 password manager, and its
2845 2849 # URIs must not contain credentials. The host is passed in the
2846 2850 # URIs list because Python < 2.4.3 uses only that to search for
2847 2851 # a password.
2848 2852 return (s, (None, (s, self.host),
2849 2853 self.user, self.passwd or ''))
2850 2854
2851 2855 def isabs(self):
2852 2856 if self.scheme and self.scheme != 'file':
2853 2857 return True # remote URL
2854 2858 if hasdriveletter(self.path):
2855 2859 return True # absolute for our purposes - can't be joined()
2856 2860 if self.path.startswith(br'\\'):
2857 2861 return True # Windows UNC path
2858 2862 if self.path.startswith('/'):
2859 2863 return True # POSIX-style
2860 2864 return False
2861 2865
2862 2866 def localpath(self):
2863 2867 if self.scheme == 'file' or self.scheme == 'bundle':
2864 2868 path = self.path or '/'
2865 2869 # For Windows, we need to promote hosts containing drive
2866 2870 # letters to paths with drive letters.
2867 2871 if hasdriveletter(self._hostport):
2868 2872 path = self._hostport + '/' + self.path
2869 2873 elif (self.host is not None and self.path
2870 2874 and not hasdriveletter(path)):
2871 2875 path = '/' + path
2872 2876 return path
2873 2877 return self._origpath
2874 2878
2875 2879 def islocal(self):
2876 2880 '''whether localpath will return something that posixfile can open'''
2877 2881 return (not self.scheme or self.scheme == 'file'
2878 2882 or self.scheme == 'bundle')
2879 2883
2880 2884 def hasscheme(path):
2881 2885 return bool(url(path).scheme)
2882 2886
2883 2887 def hasdriveletter(path):
2884 2888 return path and path[1:2] == ':' and path[0:1].isalpha()
2885 2889
2886 2890 def urllocalpath(path):
2887 2891 return url(path, parsequery=False, parsefragment=False).localpath()
2888 2892
2889 2893 def hidepassword(u):
2890 2894 '''hide user credential in a url string'''
2891 2895 u = url(u)
2892 2896 if u.passwd:
2893 2897 u.passwd = '***'
2894 2898 return bytes(u)
2895 2899
2896 2900 def removeauth(u):
2897 2901 '''remove all authentication information from a url string'''
2898 2902 u = url(u)
2899 2903 u.user = u.passwd = None
2900 2904 return str(u)
2901 2905
2902 2906 timecount = unitcountfn(
2903 2907 (1, 1e3, _('%.0f s')),
2904 2908 (100, 1, _('%.1f s')),
2905 2909 (10, 1, _('%.2f s')),
2906 2910 (1, 1, _('%.3f s')),
2907 2911 (100, 0.001, _('%.1f ms')),
2908 2912 (10, 0.001, _('%.2f ms')),
2909 2913 (1, 0.001, _('%.3f ms')),
2910 2914 (100, 0.000001, _('%.1f us')),
2911 2915 (10, 0.000001, _('%.2f us')),
2912 2916 (1, 0.000001, _('%.3f us')),
2913 2917 (100, 0.000000001, _('%.1f ns')),
2914 2918 (10, 0.000000001, _('%.2f ns')),
2915 2919 (1, 0.000000001, _('%.3f ns')),
2916 2920 )
2917 2921
2918 2922 _timenesting = [0]
2919 2923
2920 2924 def timed(func):
2921 2925 '''Report the execution time of a function call to stderr.
2922 2926
2923 2927 During development, use as a decorator when you need to measure
2924 2928 the cost of a function, e.g. as follows:
2925 2929
2926 2930 @util.timed
2927 2931 def foo(a, b, c):
2928 2932 pass
2929 2933 '''
2930 2934
2931 2935 def wrapper(*args, **kwargs):
2932 2936 start = timer()
2933 2937 indent = 2
2934 2938 _timenesting[0] += indent
2935 2939 try:
2936 2940 return func(*args, **kwargs)
2937 2941 finally:
2938 2942 elapsed = timer() - start
2939 2943 _timenesting[0] -= indent
2940 2944 stderr.write('%s%s: %s\n' %
2941 2945 (' ' * _timenesting[0], func.__name__,
2942 2946 timecount(elapsed)))
2943 2947 return wrapper
2944 2948
2945 2949 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2946 2950 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2947 2951
2948 2952 def sizetoint(s):
2949 2953 '''Convert a space specifier to a byte count.
2950 2954
2951 2955 >>> sizetoint('30')
2952 2956 30
2953 2957 >>> sizetoint('2.2kb')
2954 2958 2252
2955 2959 >>> sizetoint('6M')
2956 2960 6291456
2957 2961 '''
2958 2962 t = s.strip().lower()
2959 2963 try:
2960 2964 for k, u in _sizeunits:
2961 2965 if t.endswith(k):
2962 2966 return int(float(t[:-len(k)]) * u)
2963 2967 return int(t)
2964 2968 except ValueError:
2965 2969 raise error.ParseError(_("couldn't parse size: %s") % s)
2966 2970
2967 2971 class hooks(object):
2968 2972 '''A collection of hook functions that can be used to extend a
2969 2973 function's behavior. Hooks are called in lexicographic order,
2970 2974 based on the names of their sources.'''
2971 2975
2972 2976 def __init__(self):
2973 2977 self._hooks = []
2974 2978
2975 2979 def add(self, source, hook):
2976 2980 self._hooks.append((source, hook))
2977 2981
2978 2982 def __call__(self, *args):
2979 2983 self._hooks.sort(key=lambda x: x[0])
2980 2984 results = []
2981 2985 for source, hook in self._hooks:
2982 2986 results.append(hook(*args))
2983 2987 return results
2984 2988
2985 2989 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2986 2990 '''Yields lines for a nicely formatted stacktrace.
2987 2991 Skips the 'skip' last entries, then return the last 'depth' entries.
2988 2992 Each file+linenumber is formatted according to fileline.
2989 2993 Each line is formatted according to line.
2990 2994 If line is None, it yields:
2991 2995 length of longest filepath+line number,
2992 2996 filepath+linenumber,
2993 2997 function
2994 2998
2995 2999 Not be used in production code but very convenient while developing.
2996 3000 '''
2997 3001 entries = [(fileline % (fn, ln), func)
2998 3002 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
2999 3003 ][-depth:]
3000 3004 if entries:
3001 3005 fnmax = max(len(entry[0]) for entry in entries)
3002 3006 for fnln, func in entries:
3003 3007 if line is None:
3004 3008 yield (fnmax, fnln, func)
3005 3009 else:
3006 3010 yield line % (fnmax, fnln, func)
3007 3011
3008 3012 def debugstacktrace(msg='stacktrace', skip=0,
3009 3013 f=stderr, otherf=stdout, depth=0):
3010 3014 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3011 3015 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3012 3016 By default it will flush stdout first.
3013 3017 It can be used everywhere and intentionally does not require an ui object.
3014 3018 Not be used in production code but very convenient while developing.
3015 3019 '''
3016 3020 if otherf:
3017 3021 otherf.flush()
3018 3022 f.write('%s at:\n' % msg.rstrip())
3019 3023 for line in getstackframes(skip + 1, depth=depth):
3020 3024 f.write(line)
3021 3025 f.flush()
3022 3026
3023 3027 class dirs(object):
3024 3028 '''a multiset of directory names from a dirstate or manifest'''
3025 3029
3026 3030 def __init__(self, map, skip=None):
3027 3031 self._dirs = {}
3028 3032 addpath = self.addpath
3029 3033 if safehasattr(map, 'iteritems') and skip is not None:
3030 3034 for f, s in map.iteritems():
3031 3035 if s[0] != skip:
3032 3036 addpath(f)
3033 3037 else:
3034 3038 for f in map:
3035 3039 addpath(f)
3036 3040
3037 3041 def addpath(self, path):
3038 3042 dirs = self._dirs
3039 3043 for base in finddirs(path):
3040 3044 if base in dirs:
3041 3045 dirs[base] += 1
3042 3046 return
3043 3047 dirs[base] = 1
3044 3048
3045 3049 def delpath(self, path):
3046 3050 dirs = self._dirs
3047 3051 for base in finddirs(path):
3048 3052 if dirs[base] > 1:
3049 3053 dirs[base] -= 1
3050 3054 return
3051 3055 del dirs[base]
3052 3056
3053 3057 def __iter__(self):
3054 3058 return iter(self._dirs)
3055 3059
3056 3060 def __contains__(self, d):
3057 3061 return d in self._dirs
3058 3062
3059 3063 if safehasattr(parsers, 'dirs'):
3060 3064 dirs = parsers.dirs
3061 3065
3062 3066 def finddirs(path):
3063 3067 pos = path.rfind('/')
3064 3068 while pos != -1:
3065 3069 yield path[:pos]
3066 3070 pos = path.rfind('/', 0, pos)
3067 3071
3068 3072 # compression code
3069 3073
3070 3074 SERVERROLE = 'server'
3071 3075 CLIENTROLE = 'client'
3072 3076
3073 3077 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3074 3078 (u'name', u'serverpriority',
3075 3079 u'clientpriority'))
3076 3080
3077 3081 class compressormanager(object):
3078 3082 """Holds registrations of various compression engines.
3079 3083
3080 3084 This class essentially abstracts the differences between compression
3081 3085 engines to allow new compression formats to be added easily, possibly from
3082 3086 extensions.
3083 3087
3084 3088 Compressors are registered against the global instance by calling its
3085 3089 ``register()`` method.
3086 3090 """
3087 3091 def __init__(self):
3088 3092 self._engines = {}
3089 3093 # Bundle spec human name to engine name.
3090 3094 self._bundlenames = {}
3091 3095 # Internal bundle identifier to engine name.
3092 3096 self._bundletypes = {}
3093 3097 # Revlog header to engine name.
3094 3098 self._revlogheaders = {}
3095 3099 # Wire proto identifier to engine name.
3096 3100 self._wiretypes = {}
3097 3101
3098 3102 def __getitem__(self, key):
3099 3103 return self._engines[key]
3100 3104
3101 3105 def __contains__(self, key):
3102 3106 return key in self._engines
3103 3107
3104 3108 def __iter__(self):
3105 3109 return iter(self._engines.keys())
3106 3110
3107 3111 def register(self, engine):
3108 3112 """Register a compression engine with the manager.
3109 3113
3110 3114 The argument must be a ``compressionengine`` instance.
3111 3115 """
3112 3116 if not isinstance(engine, compressionengine):
3113 3117 raise ValueError(_('argument must be a compressionengine'))
3114 3118
3115 3119 name = engine.name()
3116 3120
3117 3121 if name in self._engines:
3118 3122 raise error.Abort(_('compression engine %s already registered') %
3119 3123 name)
3120 3124
3121 3125 bundleinfo = engine.bundletype()
3122 3126 if bundleinfo:
3123 3127 bundlename, bundletype = bundleinfo
3124 3128
3125 3129 if bundlename in self._bundlenames:
3126 3130 raise error.Abort(_('bundle name %s already registered') %
3127 3131 bundlename)
3128 3132 if bundletype in self._bundletypes:
3129 3133 raise error.Abort(_('bundle type %s already registered by %s') %
3130 3134 (bundletype, self._bundletypes[bundletype]))
3131 3135
3132 3136 # No external facing name declared.
3133 3137 if bundlename:
3134 3138 self._bundlenames[bundlename] = name
3135 3139
3136 3140 self._bundletypes[bundletype] = name
3137 3141
3138 3142 wiresupport = engine.wireprotosupport()
3139 3143 if wiresupport:
3140 3144 wiretype = wiresupport.name
3141 3145 if wiretype in self._wiretypes:
3142 3146 raise error.Abort(_('wire protocol compression %s already '
3143 3147 'registered by %s') %
3144 3148 (wiretype, self._wiretypes[wiretype]))
3145 3149
3146 3150 self._wiretypes[wiretype] = name
3147 3151
3148 3152 revlogheader = engine.revlogheader()
3149 3153 if revlogheader and revlogheader in self._revlogheaders:
3150 3154 raise error.Abort(_('revlog header %s already registered by %s') %
3151 3155 (revlogheader, self._revlogheaders[revlogheader]))
3152 3156
3153 3157 if revlogheader:
3154 3158 self._revlogheaders[revlogheader] = name
3155 3159
3156 3160 self._engines[name] = engine
3157 3161
3158 3162 @property
3159 3163 def supportedbundlenames(self):
3160 3164 return set(self._bundlenames.keys())
3161 3165
3162 3166 @property
3163 3167 def supportedbundletypes(self):
3164 3168 return set(self._bundletypes.keys())
3165 3169
3166 3170 def forbundlename(self, bundlename):
3167 3171 """Obtain a compression engine registered to a bundle name.
3168 3172
3169 3173 Will raise KeyError if the bundle type isn't registered.
3170 3174
3171 3175 Will abort if the engine is known but not available.
3172 3176 """
3173 3177 engine = self._engines[self._bundlenames[bundlename]]
3174 3178 if not engine.available():
3175 3179 raise error.Abort(_('compression engine %s could not be loaded') %
3176 3180 engine.name())
3177 3181 return engine
3178 3182
3179 3183 def forbundletype(self, bundletype):
3180 3184 """Obtain a compression engine registered to a bundle type.
3181 3185
3182 3186 Will raise KeyError if the bundle type isn't registered.
3183 3187
3184 3188 Will abort if the engine is known but not available.
3185 3189 """
3186 3190 engine = self._engines[self._bundletypes[bundletype]]
3187 3191 if not engine.available():
3188 3192 raise error.Abort(_('compression engine %s could not be loaded') %
3189 3193 engine.name())
3190 3194 return engine
3191 3195
3192 3196 def supportedwireengines(self, role, onlyavailable=True):
3193 3197 """Obtain compression engines that support the wire protocol.
3194 3198
3195 3199 Returns a list of engines in prioritized order, most desired first.
3196 3200
3197 3201 If ``onlyavailable`` is set, filter out engines that can't be
3198 3202 loaded.
3199 3203 """
3200 3204 assert role in (SERVERROLE, CLIENTROLE)
3201 3205
3202 3206 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3203 3207
3204 3208 engines = [self._engines[e] for e in self._wiretypes.values()]
3205 3209 if onlyavailable:
3206 3210 engines = [e for e in engines if e.available()]
3207 3211
3208 3212 def getkey(e):
3209 3213 # Sort first by priority, highest first. In case of tie, sort
3210 3214 # alphabetically. This is arbitrary, but ensures output is
3211 3215 # stable.
3212 3216 w = e.wireprotosupport()
3213 3217 return -1 * getattr(w, attr), w.name
3214 3218
3215 3219 return list(sorted(engines, key=getkey))
3216 3220
3217 3221 def forwiretype(self, wiretype):
3218 3222 engine = self._engines[self._wiretypes[wiretype]]
3219 3223 if not engine.available():
3220 3224 raise error.Abort(_('compression engine %s could not be loaded') %
3221 3225 engine.name())
3222 3226 return engine
3223 3227
3224 3228 def forrevlogheader(self, header):
3225 3229 """Obtain a compression engine registered to a revlog header.
3226 3230
3227 3231 Will raise KeyError if the revlog header value isn't registered.
3228 3232 """
3229 3233 return self._engines[self._revlogheaders[header]]
3230 3234
3231 3235 compengines = compressormanager()
3232 3236
3233 3237 class compressionengine(object):
3234 3238 """Base class for compression engines.
3235 3239
3236 3240 Compression engines must implement the interface defined by this class.
3237 3241 """
3238 3242 def name(self):
3239 3243 """Returns the name of the compression engine.
3240 3244
3241 3245 This is the key the engine is registered under.
3242 3246
3243 3247 This method must be implemented.
3244 3248 """
3245 3249 raise NotImplementedError()
3246 3250
3247 3251 def available(self):
3248 3252 """Whether the compression engine is available.
3249 3253
3250 3254 The intent of this method is to allow optional compression engines
3251 3255 that may not be available in all installations (such as engines relying
3252 3256 on C extensions that may not be present).
3253 3257 """
3254 3258 return True
3255 3259
3256 3260 def bundletype(self):
3257 3261 """Describes bundle identifiers for this engine.
3258 3262
3259 3263 If this compression engine isn't supported for bundles, returns None.
3260 3264
3261 3265 If this engine can be used for bundles, returns a 2-tuple of strings of
3262 3266 the user-facing "bundle spec" compression name and an internal
3263 3267 identifier used to denote the compression format within bundles. To
3264 3268 exclude the name from external usage, set the first element to ``None``.
3265 3269
3266 3270 If bundle compression is supported, the class must also implement
3267 3271 ``compressstream`` and `decompressorreader``.
3268 3272
3269 3273 The docstring of this method is used in the help system to tell users
3270 3274 about this engine.
3271 3275 """
3272 3276 return None
3273 3277
3274 3278 def wireprotosupport(self):
3275 3279 """Declare support for this compression format on the wire protocol.
3276 3280
3277 3281 If this compression engine isn't supported for compressing wire
3278 3282 protocol payloads, returns None.
3279 3283
3280 3284 Otherwise, returns ``compenginewireprotosupport`` with the following
3281 3285 fields:
3282 3286
3283 3287 * String format identifier
3284 3288 * Integer priority for the server
3285 3289 * Integer priority for the client
3286 3290
3287 3291 The integer priorities are used to order the advertisement of format
3288 3292 support by server and client. The highest integer is advertised
3289 3293 first. Integers with non-positive values aren't advertised.
3290 3294
3291 3295 The priority values are somewhat arbitrary and only used for default
3292 3296 ordering. The relative order can be changed via config options.
3293 3297
3294 3298 If wire protocol compression is supported, the class must also implement
3295 3299 ``compressstream`` and ``decompressorreader``.
3296 3300 """
3297 3301 return None
3298 3302
3299 3303 def revlogheader(self):
3300 3304 """Header added to revlog chunks that identifies this engine.
3301 3305
3302 3306 If this engine can be used to compress revlogs, this method should
3303 3307 return the bytes used to identify chunks compressed with this engine.
3304 3308 Else, the method should return ``None`` to indicate it does not
3305 3309 participate in revlog compression.
3306 3310 """
3307 3311 return None
3308 3312
3309 3313 def compressstream(self, it, opts=None):
3310 3314 """Compress an iterator of chunks.
3311 3315
3312 3316 The method receives an iterator (ideally a generator) of chunks of
3313 3317 bytes to be compressed. It returns an iterator (ideally a generator)
3314 3318 of bytes of chunks representing the compressed output.
3315 3319
3316 3320 Optionally accepts an argument defining how to perform compression.
3317 3321 Each engine treats this argument differently.
3318 3322 """
3319 3323 raise NotImplementedError()
3320 3324
3321 3325 def decompressorreader(self, fh):
3322 3326 """Perform decompression on a file object.
3323 3327
3324 3328 Argument is an object with a ``read(size)`` method that returns
3325 3329 compressed data. Return value is an object with a ``read(size)`` that
3326 3330 returns uncompressed data.
3327 3331 """
3328 3332 raise NotImplementedError()
3329 3333
3330 3334 def revlogcompressor(self, opts=None):
3331 3335 """Obtain an object that can be used to compress revlog entries.
3332 3336
3333 3337 The object has a ``compress(data)`` method that compresses binary
3334 3338 data. This method returns compressed binary data or ``None`` if
3335 3339 the data could not be compressed (too small, not compressible, etc).
3336 3340 The returned data should have a header uniquely identifying this
3337 3341 compression format so decompression can be routed to this engine.
3338 3342 This header should be identified by the ``revlogheader()`` return
3339 3343 value.
3340 3344
3341 3345 The object has a ``decompress(data)`` method that decompresses
3342 3346 data. The method will only be called if ``data`` begins with
3343 3347 ``revlogheader()``. The method should return the raw, uncompressed
3344 3348 data or raise a ``RevlogError``.
3345 3349
3346 3350 The object is reusable but is not thread safe.
3347 3351 """
3348 3352 raise NotImplementedError()
3349 3353
3350 3354 class _zlibengine(compressionengine):
3351 3355 def name(self):
3352 3356 return 'zlib'
3353 3357
3354 3358 def bundletype(self):
3355 3359 """zlib compression using the DEFLATE algorithm.
3356 3360
3357 3361 All Mercurial clients should support this format. The compression
3358 3362 algorithm strikes a reasonable balance between compression ratio
3359 3363 and size.
3360 3364 """
3361 3365 return 'gzip', 'GZ'
3362 3366
3363 3367 def wireprotosupport(self):
3364 3368 return compewireprotosupport('zlib', 20, 20)
3365 3369
3366 3370 def revlogheader(self):
3367 3371 return 'x'
3368 3372
3369 3373 def compressstream(self, it, opts=None):
3370 3374 opts = opts or {}
3371 3375
3372 3376 z = zlib.compressobj(opts.get('level', -1))
3373 3377 for chunk in it:
3374 3378 data = z.compress(chunk)
3375 3379 # Not all calls to compress emit data. It is cheaper to inspect
3376 3380 # here than to feed empty chunks through generator.
3377 3381 if data:
3378 3382 yield data
3379 3383
3380 3384 yield z.flush()
3381 3385
3382 3386 def decompressorreader(self, fh):
3383 3387 def gen():
3384 3388 d = zlib.decompressobj()
3385 3389 for chunk in filechunkiter(fh):
3386 3390 while chunk:
3387 3391 # Limit output size to limit memory.
3388 3392 yield d.decompress(chunk, 2 ** 18)
3389 3393 chunk = d.unconsumed_tail
3390 3394
3391 3395 return chunkbuffer(gen())
3392 3396
3393 3397 class zlibrevlogcompressor(object):
3394 3398 def compress(self, data):
3395 3399 insize = len(data)
3396 3400 # Caller handles empty input case.
3397 3401 assert insize > 0
3398 3402
3399 3403 if insize < 44:
3400 3404 return None
3401 3405
3402 3406 elif insize <= 1000000:
3403 3407 compressed = zlib.compress(data)
3404 3408 if len(compressed) < insize:
3405 3409 return compressed
3406 3410 return None
3407 3411
3408 3412 # zlib makes an internal copy of the input buffer, doubling
3409 3413 # memory usage for large inputs. So do streaming compression
3410 3414 # on large inputs.
3411 3415 else:
3412 3416 z = zlib.compressobj()
3413 3417 parts = []
3414 3418 pos = 0
3415 3419 while pos < insize:
3416 3420 pos2 = pos + 2**20
3417 3421 parts.append(z.compress(data[pos:pos2]))
3418 3422 pos = pos2
3419 3423 parts.append(z.flush())
3420 3424
3421 3425 if sum(map(len, parts)) < insize:
3422 3426 return ''.join(parts)
3423 3427 return None
3424 3428
3425 3429 def decompress(self, data):
3426 3430 try:
3427 3431 return zlib.decompress(data)
3428 3432 except zlib.error as e:
3429 3433 raise error.RevlogError(_('revlog decompress error: %s') %
3430 3434 str(e))
3431 3435
3432 3436 def revlogcompressor(self, opts=None):
3433 3437 return self.zlibrevlogcompressor()
3434 3438
3435 3439 compengines.register(_zlibengine())
3436 3440
3437 3441 class _bz2engine(compressionengine):
3438 3442 def name(self):
3439 3443 return 'bz2'
3440 3444
3441 3445 def bundletype(self):
3442 3446 """An algorithm that produces smaller bundles than ``gzip``.
3443 3447
3444 3448 All Mercurial clients should support this format.
3445 3449
3446 3450 This engine will likely produce smaller bundles than ``gzip`` but
3447 3451 will be significantly slower, both during compression and
3448 3452 decompression.
3449 3453
3450 3454 If available, the ``zstd`` engine can yield similar or better
3451 3455 compression at much higher speeds.
3452 3456 """
3453 3457 return 'bzip2', 'BZ'
3454 3458
3455 3459 # We declare a protocol name but don't advertise by default because
3456 3460 # it is slow.
3457 3461 def wireprotosupport(self):
3458 3462 return compewireprotosupport('bzip2', 0, 0)
3459 3463
3460 3464 def compressstream(self, it, opts=None):
3461 3465 opts = opts or {}
3462 3466 z = bz2.BZ2Compressor(opts.get('level', 9))
3463 3467 for chunk in it:
3464 3468 data = z.compress(chunk)
3465 3469 if data:
3466 3470 yield data
3467 3471
3468 3472 yield z.flush()
3469 3473
3470 3474 def decompressorreader(self, fh):
3471 3475 def gen():
3472 3476 d = bz2.BZ2Decompressor()
3473 3477 for chunk in filechunkiter(fh):
3474 3478 yield d.decompress(chunk)
3475 3479
3476 3480 return chunkbuffer(gen())
3477 3481
3478 3482 compengines.register(_bz2engine())
3479 3483
3480 3484 class _truncatedbz2engine(compressionengine):
3481 3485 def name(self):
3482 3486 return 'bz2truncated'
3483 3487
3484 3488 def bundletype(self):
3485 3489 return None, '_truncatedBZ'
3486 3490
3487 3491 # We don't implement compressstream because it is hackily handled elsewhere.
3488 3492
3489 3493 def decompressorreader(self, fh):
3490 3494 def gen():
3491 3495 # The input stream doesn't have the 'BZ' header. So add it back.
3492 3496 d = bz2.BZ2Decompressor()
3493 3497 d.decompress('BZ')
3494 3498 for chunk in filechunkiter(fh):
3495 3499 yield d.decompress(chunk)
3496 3500
3497 3501 return chunkbuffer(gen())
3498 3502
3499 3503 compengines.register(_truncatedbz2engine())
3500 3504
3501 3505 class _noopengine(compressionengine):
3502 3506 def name(self):
3503 3507 return 'none'
3504 3508
3505 3509 def bundletype(self):
3506 3510 """No compression is performed.
3507 3511
3508 3512 Use this compression engine to explicitly disable compression.
3509 3513 """
3510 3514 return 'none', 'UN'
3511 3515
3512 3516 # Clients always support uncompressed payloads. Servers don't because
3513 3517 # unless you are on a fast network, uncompressed payloads can easily
3514 3518 # saturate your network pipe.
3515 3519 def wireprotosupport(self):
3516 3520 return compewireprotosupport('none', 0, 10)
3517 3521
3518 3522 # We don't implement revlogheader because it is handled specially
3519 3523 # in the revlog class.
3520 3524
3521 3525 def compressstream(self, it, opts=None):
3522 3526 return it
3523 3527
3524 3528 def decompressorreader(self, fh):
3525 3529 return fh
3526 3530
3527 3531 class nooprevlogcompressor(object):
3528 3532 def compress(self, data):
3529 3533 return None
3530 3534
3531 3535 def revlogcompressor(self, opts=None):
3532 3536 return self.nooprevlogcompressor()
3533 3537
3534 3538 compengines.register(_noopengine())
3535 3539
3536 3540 class _zstdengine(compressionengine):
3537 3541 def name(self):
3538 3542 return 'zstd'
3539 3543
3540 3544 @propertycache
3541 3545 def _module(self):
3542 3546 # Not all installs have the zstd module available. So defer importing
3543 3547 # until first access.
3544 3548 try:
3545 3549 from . import zstd
3546 3550 # Force delayed import.
3547 3551 zstd.__version__
3548 3552 return zstd
3549 3553 except ImportError:
3550 3554 return None
3551 3555
3552 3556 def available(self):
3553 3557 return bool(self._module)
3554 3558
3555 3559 def bundletype(self):
3556 3560 """A modern compression algorithm that is fast and highly flexible.
3557 3561
3558 3562 Only supported by Mercurial 4.1 and newer clients.
3559 3563
3560 3564 With the default settings, zstd compression is both faster and yields
3561 3565 better compression than ``gzip``. It also frequently yields better
3562 3566 compression than ``bzip2`` while operating at much higher speeds.
3563 3567
3564 3568 If this engine is available and backwards compatibility is not a
3565 3569 concern, it is likely the best available engine.
3566 3570 """
3567 3571 return 'zstd', 'ZS'
3568 3572
3569 3573 def wireprotosupport(self):
3570 3574 return compewireprotosupport('zstd', 50, 50)
3571 3575
3572 3576 def revlogheader(self):
3573 3577 return '\x28'
3574 3578
3575 3579 def compressstream(self, it, opts=None):
3576 3580 opts = opts or {}
3577 3581 # zstd level 3 is almost always significantly faster than zlib
3578 3582 # while providing no worse compression. It strikes a good balance
3579 3583 # between speed and compression.
3580 3584 level = opts.get('level', 3)
3581 3585
3582 3586 zstd = self._module
3583 3587 z = zstd.ZstdCompressor(level=level).compressobj()
3584 3588 for chunk in it:
3585 3589 data = z.compress(chunk)
3586 3590 if data:
3587 3591 yield data
3588 3592
3589 3593 yield z.flush()
3590 3594
3591 3595 def decompressorreader(self, fh):
3592 3596 zstd = self._module
3593 3597 dctx = zstd.ZstdDecompressor()
3594 3598 return chunkbuffer(dctx.read_from(fh))
3595 3599
3596 3600 class zstdrevlogcompressor(object):
3597 3601 def __init__(self, zstd, level=3):
3598 3602 # Writing the content size adds a few bytes to the output. However,
3599 3603 # it allows decompression to be more optimal since we can
3600 3604 # pre-allocate a buffer to hold the result.
3601 3605 self._cctx = zstd.ZstdCompressor(level=level,
3602 3606 write_content_size=True)
3603 3607 self._dctx = zstd.ZstdDecompressor()
3604 3608 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3605 3609 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3606 3610
3607 3611 def compress(self, data):
3608 3612 insize = len(data)
3609 3613 # Caller handles empty input case.
3610 3614 assert insize > 0
3611 3615
3612 3616 if insize < 50:
3613 3617 return None
3614 3618
3615 3619 elif insize <= 1000000:
3616 3620 compressed = self._cctx.compress(data)
3617 3621 if len(compressed) < insize:
3618 3622 return compressed
3619 3623 return None
3620 3624 else:
3621 3625 z = self._cctx.compressobj()
3622 3626 chunks = []
3623 3627 pos = 0
3624 3628 while pos < insize:
3625 3629 pos2 = pos + self._compinsize
3626 3630 chunk = z.compress(data[pos:pos2])
3627 3631 if chunk:
3628 3632 chunks.append(chunk)
3629 3633 pos = pos2
3630 3634 chunks.append(z.flush())
3631 3635
3632 3636 if sum(map(len, chunks)) < insize:
3633 3637 return ''.join(chunks)
3634 3638 return None
3635 3639
3636 3640 def decompress(self, data):
3637 3641 insize = len(data)
3638 3642
3639 3643 try:
3640 3644 # This was measured to be faster than other streaming
3641 3645 # decompressors.
3642 3646 dobj = self._dctx.decompressobj()
3643 3647 chunks = []
3644 3648 pos = 0
3645 3649 while pos < insize:
3646 3650 pos2 = pos + self._decompinsize
3647 3651 chunk = dobj.decompress(data[pos:pos2])
3648 3652 if chunk:
3649 3653 chunks.append(chunk)
3650 3654 pos = pos2
3651 3655 # Frame should be exhausted, so no finish() API.
3652 3656
3653 3657 return ''.join(chunks)
3654 3658 except Exception as e:
3655 3659 raise error.RevlogError(_('revlog decompress error: %s') %
3656 3660 str(e))
3657 3661
3658 3662 def revlogcompressor(self, opts=None):
3659 3663 opts = opts or {}
3660 3664 return self.zstdrevlogcompressor(self._module,
3661 3665 level=opts.get('level', 3))
3662 3666
3663 3667 compengines.register(_zstdengine())
3664 3668
3665 3669 def bundlecompressiontopics():
3666 3670 """Obtains a list of available bundle compressions for use in help."""
3667 3671 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3668 3672 items = {}
3669 3673
3670 3674 # We need to format the docstring. So use a dummy object/type to hold it
3671 3675 # rather than mutating the original.
3672 3676 class docobject(object):
3673 3677 pass
3674 3678
3675 3679 for name in compengines:
3676 3680 engine = compengines[name]
3677 3681
3678 3682 if not engine.available():
3679 3683 continue
3680 3684
3681 3685 bt = engine.bundletype()
3682 3686 if not bt or not bt[0]:
3683 3687 continue
3684 3688
3685 3689 doc = pycompat.sysstr('``%s``\n %s') % (
3686 3690 bt[0], engine.bundletype.__doc__)
3687 3691
3688 3692 value = docobject()
3689 3693 value.__doc__ = doc
3690 3694
3691 3695 items[bt[0]] = value
3692 3696
3693 3697 return items
3694 3698
3695 3699 # convenient shortcut
3696 3700 dst = debugstacktrace
@@ -1,381 +1,418 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > drawdag=$TESTDIR/drawdag.py
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: {desc}"
11 11 > EOF
12 12
13 13 $ rebasewithdag() {
14 14 > N=`$PYTHON -c "print($N+1)"`
15 15 > hg init repo$N && cd repo$N
16 16 > hg debugdrawdag
17 17 > hg rebase "$@" > _rebasetmp
18 18 > r=$?
19 19 > grep -v 'saved backup bundle' _rebasetmp
20 20 > [ $r -eq 0 ] && hg tglog
21 21 > cd ..
22 22 > return $r
23 23 > }
24 24
25 25 Single branching point, without merge:
26 26
27 27 $ rebasewithdag -b D -d Z <<'EOS'
28 28 > D E
29 29 > |/
30 30 > Z B C # C: branching point, E should be picked
31 31 > \|/ # B should not be picked
32 32 > A
33 33 > |
34 34 > R
35 35 > EOS
36 36 rebasing 3:d6003a550c2c "C" (C)
37 37 rebasing 5:4526cf523425 "D" (D)
38 38 rebasing 6:b296604d9846 "E" (E tip)
39 39 o 6: E
40 40 |
41 41 | o 5: D
42 42 |/
43 43 o 4: C
44 44 |
45 45 o 3: Z
46 46 |
47 47 | o 2: B
48 48 |/
49 49 o 1: A
50 50 |
51 51 o 0: R
52 52
53 53 Multiple branching points caused by selecting a single merge changeset:
54 54
55 55 $ rebasewithdag -b E -d Z <<'EOS'
56 56 > E
57 57 > /|
58 58 > B C D # B, C: multiple branching points
59 59 > | |/ # D should not be picked
60 60 > Z | /
61 61 > \|/
62 62 > A
63 63 > |
64 64 > R
65 65 > EOS
66 66 rebasing 2:c1e6b162678d "B" (B)
67 67 rebasing 3:d6003a550c2c "C" (C)
68 68 rebasing 6:54c8f00cb91c "E" (E tip)
69 69 o 6: E
70 70 |\
71 71 | o 5: C
72 72 | |
73 73 o | 4: B
74 74 |/
75 75 o 3: Z
76 76 |
77 77 | o 2: D
78 78 |/
79 79 o 1: A
80 80 |
81 81 o 0: R
82 82
83 83 Rebase should not extend the "--base" revset using "descendants":
84 84
85 85 $ rebasewithdag -b B -d Z <<'EOS'
86 86 > E
87 87 > /|
88 88 > Z B C # descendants(B) = B+E. With E, C will be included incorrectly
89 89 > \|/
90 90 > A
91 91 > |
92 92 > R
93 93 > EOS
94 94 rebasing 2:c1e6b162678d "B" (B)
95 95 rebasing 5:54c8f00cb91c "E" (E tip)
96 96 o 5: E
97 97 |\
98 98 | o 4: B
99 99 | |
100 100 | o 3: Z
101 101 | |
102 102 o | 2: C
103 103 |/
104 104 o 1: A
105 105 |
106 106 o 0: R
107 107
108 108 Rebase should not simplify the "--base" revset using "roots":
109 109
110 110 $ rebasewithdag -b B+E -d Z <<'EOS'
111 111 > E
112 112 > /|
113 113 > Z B C # roots(B+E) = B. Without E, C will be missed incorrectly
114 114 > \|/
115 115 > A
116 116 > |
117 117 > R
118 118 > EOS
119 119 rebasing 2:c1e6b162678d "B" (B)
120 120 rebasing 3:d6003a550c2c "C" (C)
121 121 rebasing 5:54c8f00cb91c "E" (E tip)
122 122 o 5: E
123 123 |\
124 124 | o 4: C
125 125 | |
126 126 o | 3: B
127 127 |/
128 128 o 2: Z
129 129 |
130 130 o 1: A
131 131 |
132 132 o 0: R
133 133
134 134 The destination is one of the two branching points of a merge:
135 135
136 136 $ rebasewithdag -b F -d Z <<'EOS'
137 137 > F
138 138 > / \
139 139 > E D
140 140 > / /
141 141 > Z C
142 142 > \ /
143 143 > B
144 144 > |
145 145 > A
146 146 > EOS
147 147 nothing to rebase
148 148 [1]
149 149
150 150 Multiple branching points caused by multiple bases (issue5420):
151 151
152 152 $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
153 153 > Z E2
154 154 > | /
155 155 > F E1 C2
156 156 > |/ /
157 157 > E C1 B2
158 158 > |/ /
159 159 > C B1
160 160 > |/
161 161 > B
162 162 > |
163 163 > A
164 164 > |
165 165 > R
166 166 > EOS
167 167 rebasing 3:a113dbaa660a "B1" (B1)
168 168 rebasing 5:06ce7b1cc8c2 "B2" (B2)
169 169 rebasing 6:0ac98cce32d3 "C1" (C1)
170 170 rebasing 8:781512f5e33d "C2" (C2)
171 171 rebasing 9:428d8c18f641 "E1" (E1)
172 172 rebasing 11:e1bf82f6b6df "E2" (E2)
173 173 o 12: E2
174 174 |
175 175 o 11: E1
176 176 |
177 177 | o 10: C2
178 178 | |
179 179 | o 9: C1
180 180 |/
181 181 | o 8: B2
182 182 | |
183 183 | o 7: B1
184 184 |/
185 185 o 6: Z
186 186 |
187 187 o 5: F
188 188 |
189 189 o 4: E
190 190 |
191 191 o 3: C
192 192 |
193 193 o 2: B
194 194 |
195 195 o 1: A
196 196 |
197 197 o 0: R
198 198
199 199 Multiple branching points with multiple merges:
200 200
201 201 $ rebasewithdag -b G+P -d Z <<'EOS'
202 202 > G H P
203 203 > |\ /| |\
204 204 > F E D M N
205 205 > \|/| /| |\
206 206 > Z C B I J K L
207 207 > \|/ |/ |/
208 208 > A A A
209 209 > EOS
210 210 rebasing 2:dc0947a82db8 "C" (C)
211 211 rebasing 8:4e4f9194f9f1 "D" (D)
212 212 rebasing 9:03ca77807e91 "E" (E)
213 213 rebasing 10:afc707c82df0 "F" (F)
214 214 rebasing 13:690dfff91e9e "G" (G)
215 215 rebasing 14:2893b886bb10 "H" (H)
216 216 rebasing 3:08ebfeb61bac "I" (I)
217 217 rebasing 4:a0a5005cec67 "J" (J)
218 218 rebasing 5:83780307a7e8 "K" (K)
219 219 rebasing 6:e131637a1cb6 "L" (L)
220 220 rebasing 11:d1f6d0c3c7e4 "M" (M)
221 221 rebasing 12:7aaec6f81888 "N" (N)
222 222 rebasing 15:325bc8f1760d "P" (P tip)
223 223 o 15: P
224 224 |\
225 225 | o 14: N
226 226 | |\
227 227 o \ \ 13: M
228 228 |\ \ \
229 229 | | | o 12: L
230 230 | | | |
231 231 | | o | 11: K
232 232 | | |/
233 233 | o / 10: J
234 234 | |/
235 235 o / 9: I
236 236 |/
237 237 | o 8: H
238 238 | |\
239 239 | | | o 7: G
240 240 | | |/|
241 241 | | | o 6: F
242 242 | | | |
243 243 | | o | 5: E
244 244 | | |/
245 245 | o | 4: D
246 246 | |\|
247 247 +---o 3: C
248 248 | |
249 249 o | 2: Z
250 250 | |
251 251 | o 1: B
252 252 |/
253 253 o 0: A
254 254
255 255 Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
256 256
257 257 $ rebasewithdag -b A3+B3 -d Z <<'EOF'
258 258 > Z C1 A3 B3
259 259 > | / / \ / \
260 260 > M3 C0 A1 A2 B1 B2
261 261 > | / | | | |
262 262 > M2 M1 C1 C1 M3
263 263 > |
264 264 > M1
265 265 > |
266 266 > M0
267 267 > EOF
268 268 rebasing 4:8817fae53c94 "C0" (C0)
269 269 rebasing 6:06ca5dfe3b5b "B2" (B2)
270 270 rebasing 7:73508237b032 "C1" (C1)
271 271 rebasing 9:fdb955e2faed "A2" (A2)
272 272 rebasing 11:4e449bd1a643 "A3" (A3)
273 273 rebasing 10:0a33b0519128 "B1" (B1)
274 274 rebasing 12:209327807c3a "B3" (B3 tip)
275 275 o 12: B3
276 276 |\
277 277 | o 11: B1
278 278 | |
279 279 | | o 10: A3
280 280 | | |\
281 281 | +---o 9: A2
282 282 | | |
283 283 | o | 8: C1
284 284 | | |
285 285 o | | 7: B2
286 286 | | |
287 287 | o | 6: C0
288 288 |/ /
289 289 o | 5: Z
290 290 | |
291 291 o | 4: M3
292 292 | |
293 293 o | 3: M2
294 294 | |
295 295 | o 2: A1
296 296 |/
297 297 o 1: M1
298 298 |
299 299 o 0: M0
300 300
301 301 Disconnected graph:
302 302
303 303 $ rebasewithdag -b B -d Z <<'EOS'
304 304 > B
305 305 > |
306 306 > Z A
307 307 > EOS
308 308 nothing to rebase from 112478962961 to 48b9aae0607f
309 309 [1]
310 310
311 311 Multiple roots. Roots are ancestors of dest:
312 312
313 313 $ rebasewithdag -b B+D -d Z <<'EOF'
314 314 > D Z B
315 315 > \|\|
316 316 > C A
317 317 > EOF
318 318 rebasing 2:112478962961 "B" (B)
319 319 rebasing 3:b70f76719894 "D" (D)
320 320 o 4: D
321 321 |
322 322 | o 3: B
323 323 |/
324 324 o 2: Z
325 325 |\
326 326 | o 1: C
327 327 |
328 328 o 0: A
329 329
330 330 Multiple roots. One root is not an ancestor of dest:
331 331
332 332 $ rebasewithdag -b B+D -d Z <<'EOF'
333 333 > Z B D
334 334 > \|\|
335 335 > A C
336 336 > EOF
337 337 nothing to rebase from f675d5a1c6a4+b70f76719894 to 262e37e34f63
338 338 [1]
339 339
340 340 Multiple roots. One root is not an ancestor of dest. Select using a merge:
341 341
342 342 $ rebasewithdag -b E -d Z <<'EOF'
343 343 > E
344 344 > |\
345 345 > Z B D
346 346 > \|\|
347 347 > A C
348 348 > EOF
349 349 rebasing 2:f675d5a1c6a4 "B" (B)
350 350 rebasing 5:f68696fe6af8 "E" (E tip)
351 351 o 5: E
352 352 |\
353 353 | o 4: B
354 354 | |\
355 355 | | o 3: Z
356 356 | | |
357 357 o | | 2: D
358 358 |/ /
359 359 o / 1: C
360 360 /
361 361 o 0: A
362 362
363 363 Multiple roots. Two children share two parents while dest has only one parent:
364 364
365 365 $ rebasewithdag -b B+D -d Z <<'EOF'
366 366 > Z B D
367 367 > \|\|\
368 368 > A C A
369 369 > EOF
370 370 rebasing 2:f675d5a1c6a4 "B" (B)
371 371 rebasing 3:c2a779e13b56 "D" (D)
372 372 o 4: D
373 373 |\
374 374 +---o 3: B
375 375 | |/
376 376 | o 2: Z
377 377 | |
378 378 o | 1: C
379 379 /
380 380 o 0: A
381 381
382 Rebasing using a single transaction
383
384 $ hg init singletr && cd singletr
385 $ cat >> .hg/hgrc <<EOF
386 > [rebase]
387 > singletransaction=True
388 > EOF
389 $ hg debugdrawdag <<'EOF'
390 > Z
391 > |
392 > | D
393 > | |
394 > | C
395 > | |
396 > Y B
397 > |/
398 > A
399 > EOF
400 - We should only see two status stored messages. One from the start, one from
401 - the end.
402 $ hg rebase --debug -b D -d Z | grep 'status stored'
403 rebase status stored
404 rebase status stored
405 $ hg tglog
406 o 5: D
407 |
408 o 4: C
409 |
410 o 3: B
411 |
412 o 2: Z
413 |
414 o 1: Y
415 |
416 o 0: A
417
418 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now