##// END OF EJS Templates
transaction: avoid file stat ambiguity only for files in blacklist...
FUJIWARA Katsunori -
r33278:87bca10a default
parent child Browse files
Show More
@@ -1,2128 +1,2131 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import inspect
13 13 import os
14 14 import random
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 hex,
21 21 nullid,
22 22 short,
23 23 )
24 24 from . import (
25 25 bookmarks,
26 26 branchmap,
27 27 bundle2,
28 28 changegroup,
29 29 changelog,
30 30 color,
31 31 context,
32 32 dirstate,
33 33 dirstateguard,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 filelog,
39 39 hook,
40 40 lock as lockmod,
41 41 manifest,
42 42 match as matchmod,
43 43 merge as mergemod,
44 44 mergeutil,
45 45 namespaces,
46 46 obsolete,
47 47 pathutil,
48 48 peer,
49 49 phases,
50 50 pushkey,
51 51 pycompat,
52 52 repoview,
53 53 revset,
54 54 revsetlang,
55 55 scmutil,
56 56 store,
57 57 subrepo,
58 58 tags as tagsmod,
59 59 transaction,
60 60 txnutil,
61 61 util,
62 62 vfs as vfsmod,
63 63 )
64 64
65 65 release = lockmod.release
66 66 urlerr = util.urlerr
67 67 urlreq = util.urlreq
68 68
69 69 # set of (path, vfs-location) tuples. vfs-location is:
70 70 # - 'plain for vfs relative paths
71 71 # - '' for svfs relative paths
72 72 _cachedfiles = set()
73 73
74 74 class _basefilecache(scmutil.filecache):
75 75 """All filecache usage on repo are done for logic that should be unfiltered
76 76 """
77 77 def __get__(self, repo, type=None):
78 78 if repo is None:
79 79 return self
80 80 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
81 81 def __set__(self, repo, value):
82 82 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
83 83 def __delete__(self, repo):
84 84 return super(_basefilecache, self).__delete__(repo.unfiltered())
85 85
86 86 class repofilecache(_basefilecache):
87 87 """filecache for files in .hg but outside of .hg/store"""
88 88 def __init__(self, *paths):
89 89 super(repofilecache, self).__init__(*paths)
90 90 for path in paths:
91 91 _cachedfiles.add((path, 'plain'))
92 92
93 93 def join(self, obj, fname):
94 94 return obj.vfs.join(fname)
95 95
96 96 class storecache(_basefilecache):
97 97 """filecache for files in the store"""
98 98 def __init__(self, *paths):
99 99 super(storecache, self).__init__(*paths)
100 100 for path in paths:
101 101 _cachedfiles.add((path, ''))
102 102
103 103 def join(self, obj, fname):
104 104 return obj.sjoin(fname)
105 105
106 106 class unfilteredpropertycache(util.propertycache):
107 107 """propertycache that apply to unfiltered repo only"""
108 108
109 109 def __get__(self, repo, type=None):
110 110 unfi = repo.unfiltered()
111 111 if unfi is repo:
112 112 return super(unfilteredpropertycache, self).__get__(unfi)
113 113 return getattr(unfi, self.name)
114 114
115 115 class filteredpropertycache(util.propertycache):
116 116 """propertycache that must take filtering in account"""
117 117
118 118 def cachevalue(self, obj, value):
119 119 object.__setattr__(obj, self.name, value)
120 120
121 121
122 122 def hasunfilteredcache(repo, name):
123 123 """check if a repo has an unfilteredpropertycache value for <name>"""
124 124 return name in vars(repo.unfiltered())
125 125
126 126 def unfilteredmethod(orig):
127 127 """decorate method that always need to be run on unfiltered version"""
128 128 def wrapper(repo, *args, **kwargs):
129 129 return orig(repo.unfiltered(), *args, **kwargs)
130 130 return wrapper
131 131
132 132 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
133 133 'unbundle'}
134 134 legacycaps = moderncaps.union({'changegroupsubset'})
135 135
136 136 class localpeer(peer.peerrepository):
137 137 '''peer for a local repo; reflects only the most recent API'''
138 138
139 139 def __init__(self, repo, caps=None):
140 140 if caps is None:
141 141 caps = moderncaps.copy()
142 142 peer.peerrepository.__init__(self)
143 143 self._repo = repo.filtered('served')
144 144 self.ui = repo.ui
145 145 self._caps = repo._restrictcapabilities(caps)
146 146 self.requirements = repo.requirements
147 147 self.supportedformats = repo.supportedformats
148 148
149 149 def close(self):
150 150 self._repo.close()
151 151
152 152 def _capabilities(self):
153 153 return self._caps
154 154
155 155 def local(self):
156 156 return self._repo
157 157
158 158 def canpush(self):
159 159 return True
160 160
161 161 def url(self):
162 162 return self._repo.url()
163 163
164 164 def lookup(self, key):
165 165 return self._repo.lookup(key)
166 166
167 167 def branchmap(self):
168 168 return self._repo.branchmap()
169 169
170 170 def heads(self):
171 171 return self._repo.heads()
172 172
173 173 def known(self, nodes):
174 174 return self._repo.known(nodes)
175 175
176 176 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
177 177 **kwargs):
178 178 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
179 179 common=common, bundlecaps=bundlecaps,
180 180 **kwargs)
181 181 cb = util.chunkbuffer(chunks)
182 182
183 183 if exchange.bundle2requested(bundlecaps):
184 184 # When requesting a bundle2, getbundle returns a stream to make the
185 185 # wire level function happier. We need to build a proper object
186 186 # from it in local peer.
187 187 return bundle2.getunbundler(self.ui, cb)
188 188 else:
189 189 return changegroup.getunbundler('01', cb, None)
190 190
191 191 # TODO We might want to move the next two calls into legacypeer and add
192 192 # unbundle instead.
193 193
194 194 def unbundle(self, cg, heads, url):
195 195 """apply a bundle on a repo
196 196
197 197 This function handles the repo locking itself."""
198 198 try:
199 199 try:
200 200 cg = exchange.readbundle(self.ui, cg, None)
201 201 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
202 202 if util.safehasattr(ret, 'getchunks'):
203 203 # This is a bundle20 object, turn it into an unbundler.
204 204 # This little dance should be dropped eventually when the
205 205 # API is finally improved.
206 206 stream = util.chunkbuffer(ret.getchunks())
207 207 ret = bundle2.getunbundler(self.ui, stream)
208 208 return ret
209 209 except Exception as exc:
210 210 # If the exception contains output salvaged from a bundle2
211 211 # reply, we need to make sure it is printed before continuing
212 212 # to fail. So we build a bundle2 with such output and consume
213 213 # it directly.
214 214 #
215 215 # This is not very elegant but allows a "simple" solution for
216 216 # issue4594
217 217 output = getattr(exc, '_bundle2salvagedoutput', ())
218 218 if output:
219 219 bundler = bundle2.bundle20(self._repo.ui)
220 220 for out in output:
221 221 bundler.addpart(out)
222 222 stream = util.chunkbuffer(bundler.getchunks())
223 223 b = bundle2.getunbundler(self.ui, stream)
224 224 bundle2.processbundle(self._repo, b)
225 225 raise
226 226 except error.PushRaced as exc:
227 227 raise error.ResponseError(_('push failed:'), str(exc))
228 228
229 229 def lock(self):
230 230 return self._repo.lock()
231 231
232 232 def pushkey(self, namespace, key, old, new):
233 233 return self._repo.pushkey(namespace, key, old, new)
234 234
235 235 def listkeys(self, namespace):
236 236 return self._repo.listkeys(namespace)
237 237
238 238 def debugwireargs(self, one, two, three=None, four=None, five=None):
239 239 '''used to test argument passing over the wire'''
240 240 return "%s %s %s %s %s" % (one, two, three, four, five)
241 241
242 242 class locallegacypeer(localpeer):
243 243 '''peer extension which implements legacy methods too; used for tests with
244 244 restricted capabilities'''
245 245
246 246 def __init__(self, repo):
247 247 localpeer.__init__(self, repo, caps=legacycaps)
248 248
249 249 def branches(self, nodes):
250 250 return self._repo.branches(nodes)
251 251
252 252 def between(self, pairs):
253 253 return self._repo.between(pairs)
254 254
255 255 def changegroup(self, basenodes, source):
256 256 return changegroup.changegroup(self._repo, basenodes, source)
257 257
258 258 def changegroupsubset(self, bases, heads, source):
259 259 return changegroup.changegroupsubset(self._repo, bases, heads, source)
260 260
261 261 # Increment the sub-version when the revlog v2 format changes to lock out old
262 262 # clients.
263 263 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
264 264
265 265 class localrepository(object):
266 266
267 267 supportedformats = {
268 268 'revlogv1',
269 269 'generaldelta',
270 270 'treemanifest',
271 271 'manifestv2',
272 272 REVLOGV2_REQUIREMENT,
273 273 }
274 274 _basesupported = supportedformats | {
275 275 'store',
276 276 'fncache',
277 277 'shared',
278 278 'relshared',
279 279 'dotencode',
280 280 }
281 281 openerreqs = {
282 282 'revlogv1',
283 283 'generaldelta',
284 284 'treemanifest',
285 285 'manifestv2',
286 286 }
287 287
288 288 # a list of (ui, featureset) functions.
289 289 # only functions defined in module of enabled extensions are invoked
290 290 featuresetupfuncs = set()
291 291
292 292 def __init__(self, baseui, path, create=False):
293 293 self.requirements = set()
294 294 self.filtername = None
295 295 # wvfs: rooted at the repository root, used to access the working copy
296 296 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
297 297 # vfs: rooted at .hg, used to access repo files outside of .hg/store
298 298 self.vfs = None
299 299 # svfs: usually rooted at .hg/store, used to access repository history
300 300 # If this is a shared repository, this vfs may point to another
301 301 # repository's .hg/store directory.
302 302 self.svfs = None
303 303 self.root = self.wvfs.base
304 304 self.path = self.wvfs.join(".hg")
305 305 self.origroot = path
306 306 # These auditor are not used by the vfs,
307 307 # only used when writing this comment: basectx.match
308 308 self.auditor = pathutil.pathauditor(self.root, self._checknested)
309 309 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
310 310 realfs=False)
311 311 self.vfs = vfsmod.vfs(self.path)
312 312 self.baseui = baseui
313 313 self.ui = baseui.copy()
314 314 self.ui.copy = baseui.copy # prevent copying repo configuration
315 315 # A list of callback to shape the phase if no data were found.
316 316 # Callback are in the form: func(repo, roots) --> processed root.
317 317 # This list it to be filled by extension during repo setup
318 318 self._phasedefaults = []
319 319 try:
320 320 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
321 321 self._loadextensions()
322 322 except IOError:
323 323 pass
324 324
325 325 if self.featuresetupfuncs:
326 326 self.supported = set(self._basesupported) # use private copy
327 327 extmods = set(m.__name__ for n, m
328 328 in extensions.extensions(self.ui))
329 329 for setupfunc in self.featuresetupfuncs:
330 330 if setupfunc.__module__ in extmods:
331 331 setupfunc(self.ui, self.supported)
332 332 else:
333 333 self.supported = self._basesupported
334 334 color.setup(self.ui)
335 335
336 336 # Add compression engines.
337 337 for name in util.compengines:
338 338 engine = util.compengines[name]
339 339 if engine.revlogheader():
340 340 self.supported.add('exp-compression-%s' % name)
341 341
342 342 if not self.vfs.isdir():
343 343 if create:
344 344 self.requirements = newreporequirements(self)
345 345
346 346 if not self.wvfs.exists():
347 347 self.wvfs.makedirs()
348 348 self.vfs.makedir(notindexed=True)
349 349
350 350 if 'store' in self.requirements:
351 351 self.vfs.mkdir("store")
352 352
353 353 # create an invalid changelog
354 354 self.vfs.append(
355 355 "00changelog.i",
356 356 '\0\0\0\2' # represents revlogv2
357 357 ' dummy changelog to prevent using the old repo layout'
358 358 )
359 359 else:
360 360 raise error.RepoError(_("repository %s not found") % path)
361 361 elif create:
362 362 raise error.RepoError(_("repository %s already exists") % path)
363 363 else:
364 364 try:
365 365 self.requirements = scmutil.readrequires(
366 366 self.vfs, self.supported)
367 367 except IOError as inst:
368 368 if inst.errno != errno.ENOENT:
369 369 raise
370 370
371 371 self.sharedpath = self.path
372 372 try:
373 373 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
374 374 if 'relshared' in self.requirements:
375 375 sharedpath = self.vfs.join(sharedpath)
376 376 vfs = vfsmod.vfs(sharedpath, realpath=True)
377 377 s = vfs.base
378 378 if not vfs.exists():
379 379 raise error.RepoError(
380 380 _('.hg/sharedpath points to nonexistent directory %s') % s)
381 381 self.sharedpath = s
382 382 except IOError as inst:
383 383 if inst.errno != errno.ENOENT:
384 384 raise
385 385
386 386 self.store = store.store(
387 387 self.requirements, self.sharedpath, vfsmod.vfs)
388 388 self.spath = self.store.path
389 389 self.svfs = self.store.vfs
390 390 self.sjoin = self.store.join
391 391 self.vfs.createmode = self.store.createmode
392 392 self._applyopenerreqs()
393 393 if create:
394 394 self._writerequirements()
395 395
396 396 self._dirstatevalidatewarned = False
397 397
398 398 self._branchcaches = {}
399 399 self._revbranchcache = None
400 400 self.filterpats = {}
401 401 self._datafilters = {}
402 402 self._transref = self._lockref = self._wlockref = None
403 403
404 404 # A cache for various files under .hg/ that tracks file changes,
405 405 # (used by the filecache decorator)
406 406 #
407 407 # Maps a property name to its util.filecacheentry
408 408 self._filecache = {}
409 409
410 410 # hold sets of revision to be filtered
411 411 # should be cleared when something might have changed the filter value:
412 412 # - new changesets,
413 413 # - phase change,
414 414 # - new obsolescence marker,
415 415 # - working directory parent change,
416 416 # - bookmark changes
417 417 self.filteredrevcache = {}
418 418
419 419 # post-dirstate-status hooks
420 420 self._postdsstatus = []
421 421
422 422 # generic mapping between names and nodes
423 423 self.names = namespaces.namespaces()
424 424
425 425 def close(self):
426 426 self._writecaches()
427 427
428 428 def _loadextensions(self):
429 429 extensions.loadall(self.ui)
430 430
431 431 def _writecaches(self):
432 432 if self._revbranchcache:
433 433 self._revbranchcache.write()
434 434
435 435 def _restrictcapabilities(self, caps):
436 436 if self.ui.configbool('experimental', 'bundle2-advertise', True):
437 437 caps = set(caps)
438 438 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
439 439 caps.add('bundle2=' + urlreq.quote(capsblob))
440 440 return caps
441 441
442 442 def _applyopenerreqs(self):
443 443 self.svfs.options = dict((r, 1) for r in self.requirements
444 444 if r in self.openerreqs)
445 445 # experimental config: format.chunkcachesize
446 446 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
447 447 if chunkcachesize is not None:
448 448 self.svfs.options['chunkcachesize'] = chunkcachesize
449 449 # experimental config: format.maxchainlen
450 450 maxchainlen = self.ui.configint('format', 'maxchainlen')
451 451 if maxchainlen is not None:
452 452 self.svfs.options['maxchainlen'] = maxchainlen
453 453 # experimental config: format.manifestcachesize
454 454 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
455 455 if manifestcachesize is not None:
456 456 self.svfs.options['manifestcachesize'] = manifestcachesize
457 457 # experimental config: format.aggressivemergedeltas
458 458 aggressivemergedeltas = self.ui.configbool('format',
459 459 'aggressivemergedeltas')
460 460 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
461 461 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
462 462 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
463 463 if 0 <= chainspan:
464 464 self.svfs.options['maxdeltachainspan'] = chainspan
465 465
466 466 for r in self.requirements:
467 467 if r.startswith('exp-compression-'):
468 468 self.svfs.options['compengine'] = r[len('exp-compression-'):]
469 469
470 470 # TODO move "revlogv2" to openerreqs once finalized.
471 471 if REVLOGV2_REQUIREMENT in self.requirements:
472 472 self.svfs.options['revlogv2'] = True
473 473
474 474 def _writerequirements(self):
475 475 scmutil.writerequires(self.vfs, self.requirements)
476 476
477 477 def _checknested(self, path):
478 478 """Determine if path is a legal nested repository."""
479 479 if not path.startswith(self.root):
480 480 return False
481 481 subpath = path[len(self.root) + 1:]
482 482 normsubpath = util.pconvert(subpath)
483 483
484 484 # XXX: Checking against the current working copy is wrong in
485 485 # the sense that it can reject things like
486 486 #
487 487 # $ hg cat -r 10 sub/x.txt
488 488 #
489 489 # if sub/ is no longer a subrepository in the working copy
490 490 # parent revision.
491 491 #
492 492 # However, it can of course also allow things that would have
493 493 # been rejected before, such as the above cat command if sub/
494 494 # is a subrepository now, but was a normal directory before.
495 495 # The old path auditor would have rejected by mistake since it
496 496 # panics when it sees sub/.hg/.
497 497 #
498 498 # All in all, checking against the working copy seems sensible
499 499 # since we want to prevent access to nested repositories on
500 500 # the filesystem *now*.
501 501 ctx = self[None]
502 502 parts = util.splitpath(subpath)
503 503 while parts:
504 504 prefix = '/'.join(parts)
505 505 if prefix in ctx.substate:
506 506 if prefix == normsubpath:
507 507 return True
508 508 else:
509 509 sub = ctx.sub(prefix)
510 510 return sub.checknested(subpath[len(prefix) + 1:])
511 511 else:
512 512 parts.pop()
513 513 return False
514 514
515 515 def peer(self):
516 516 return localpeer(self) # not cached to avoid reference cycle
517 517
518 518 def unfiltered(self):
519 519 """Return unfiltered version of the repository
520 520
521 521 Intended to be overwritten by filtered repo."""
522 522 return self
523 523
524 524 def filtered(self, name):
525 525 """Return a filtered version of a repository"""
526 526 # build a new class with the mixin and the current class
527 527 # (possibly subclass of the repo)
528 528 class filteredrepo(repoview.repoview, self.unfiltered().__class__):
529 529 pass
530 530 return filteredrepo(self, name)
531 531
532 532 @repofilecache('bookmarks', 'bookmarks.current')
533 533 def _bookmarks(self):
534 534 return bookmarks.bmstore(self)
535 535
536 536 @property
537 537 def _activebookmark(self):
538 538 return self._bookmarks.active
539 539
540 540 # _phaserevs and _phasesets depend on changelog. what we need is to
541 541 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
542 542 # can't be easily expressed in filecache mechanism.
543 543 @storecache('phaseroots', '00changelog.i')
544 544 def _phasecache(self):
545 545 return phases.phasecache(self, self._phasedefaults)
546 546
547 547 @storecache('obsstore')
548 548 def obsstore(self):
549 549 return obsolete.makestore(self.ui, self)
550 550
551 551 @storecache('00changelog.i')
552 552 def changelog(self):
553 553 return changelog.changelog(self.svfs,
554 554 trypending=txnutil.mayhavepending(self.root))
555 555
556 556 def _constructmanifest(self):
557 557 # This is a temporary function while we migrate from manifest to
558 558 # manifestlog. It allows bundlerepo and unionrepo to intercept the
559 559 # manifest creation.
560 560 return manifest.manifestrevlog(self.svfs)
561 561
562 562 @storecache('00manifest.i')
563 563 def manifestlog(self):
564 564 return manifest.manifestlog(self.svfs, self)
565 565
566 566 @repofilecache('dirstate')
567 567 def dirstate(self):
568 568 return dirstate.dirstate(self.vfs, self.ui, self.root,
569 569 self._dirstatevalidate)
570 570
571 571 def _dirstatevalidate(self, node):
572 572 try:
573 573 self.changelog.rev(node)
574 574 return node
575 575 except error.LookupError:
576 576 if not self._dirstatevalidatewarned:
577 577 self._dirstatevalidatewarned = True
578 578 self.ui.warn(_("warning: ignoring unknown"
579 579 " working parent %s!\n") % short(node))
580 580 return nullid
581 581
582 582 def __getitem__(self, changeid):
583 583 if changeid is None:
584 584 return context.workingctx(self)
585 585 if isinstance(changeid, slice):
586 586 # wdirrev isn't contiguous so the slice shouldn't include it
587 587 return [context.changectx(self, i)
588 588 for i in xrange(*changeid.indices(len(self)))
589 589 if i not in self.changelog.filteredrevs]
590 590 try:
591 591 return context.changectx(self, changeid)
592 592 except error.WdirUnsupported:
593 593 return context.workingctx(self)
594 594
595 595 def __contains__(self, changeid):
596 596 """True if the given changeid exists
597 597
598 598 error.LookupError is raised if an ambiguous node specified.
599 599 """
600 600 try:
601 601 self[changeid]
602 602 return True
603 603 except error.RepoLookupError:
604 604 return False
605 605
606 606 def __nonzero__(self):
607 607 return True
608 608
609 609 __bool__ = __nonzero__
610 610
611 611 def __len__(self):
612 612 return len(self.changelog)
613 613
614 614 def __iter__(self):
615 615 return iter(self.changelog)
616 616
617 617 def revs(self, expr, *args):
618 618 '''Find revisions matching a revset.
619 619
620 620 The revset is specified as a string ``expr`` that may contain
621 621 %-formatting to escape certain types. See ``revsetlang.formatspec``.
622 622
623 623 Revset aliases from the configuration are not expanded. To expand
624 624 user aliases, consider calling ``scmutil.revrange()`` or
625 625 ``repo.anyrevs([expr], user=True)``.
626 626
627 627 Returns a revset.abstractsmartset, which is a list-like interface
628 628 that contains integer revisions.
629 629 '''
630 630 expr = revsetlang.formatspec(expr, *args)
631 631 m = revset.match(None, expr)
632 632 return m(self)
633 633
634 634 def set(self, expr, *args):
635 635 '''Find revisions matching a revset and emit changectx instances.
636 636
637 637 This is a convenience wrapper around ``revs()`` that iterates the
638 638 result and is a generator of changectx instances.
639 639
640 640 Revset aliases from the configuration are not expanded. To expand
641 641 user aliases, consider calling ``scmutil.revrange()``.
642 642 '''
643 643 for r in self.revs(expr, *args):
644 644 yield self[r]
645 645
646 646 def anyrevs(self, specs, user=False):
647 647 '''Find revisions matching one of the given revsets.
648 648
649 649 Revset aliases from the configuration are not expanded by default. To
650 650 expand user aliases, specify ``user=True``.
651 651 '''
652 652 if user:
653 653 m = revset.matchany(self.ui, specs, repo=self)
654 654 else:
655 655 m = revset.matchany(None, specs)
656 656 return m(self)
657 657
658 658 def url(self):
659 659 return 'file:' + self.root
660 660
661 661 def hook(self, name, throw=False, **args):
662 662 """Call a hook, passing this repo instance.
663 663
664 664 This a convenience method to aid invoking hooks. Extensions likely
665 665 won't call this unless they have registered a custom hook or are
666 666 replacing code that is expected to call a hook.
667 667 """
668 668 return hook.hook(self.ui, self, name, throw, **args)
669 669
670 670 @filteredpropertycache
671 671 def _tagscache(self):
672 672 '''Returns a tagscache object that contains various tags related
673 673 caches.'''
674 674
675 675 # This simplifies its cache management by having one decorated
676 676 # function (this one) and the rest simply fetch things from it.
677 677 class tagscache(object):
678 678 def __init__(self):
679 679 # These two define the set of tags for this repository. tags
680 680 # maps tag name to node; tagtypes maps tag name to 'global' or
681 681 # 'local'. (Global tags are defined by .hgtags across all
682 682 # heads, and local tags are defined in .hg/localtags.)
683 683 # They constitute the in-memory cache of tags.
684 684 self.tags = self.tagtypes = None
685 685
686 686 self.nodetagscache = self.tagslist = None
687 687
688 688 cache = tagscache()
689 689 cache.tags, cache.tagtypes = self._findtags()
690 690
691 691 return cache
692 692
693 693 def tags(self):
694 694 '''return a mapping of tag to node'''
695 695 t = {}
696 696 if self.changelog.filteredrevs:
697 697 tags, tt = self._findtags()
698 698 else:
699 699 tags = self._tagscache.tags
700 700 for k, v in tags.iteritems():
701 701 try:
702 702 # ignore tags to unknown nodes
703 703 self.changelog.rev(v)
704 704 t[k] = v
705 705 except (error.LookupError, ValueError):
706 706 pass
707 707 return t
708 708
709 709 def _findtags(self):
710 710 '''Do the hard work of finding tags. Return a pair of dicts
711 711 (tags, tagtypes) where tags maps tag name to node, and tagtypes
712 712 maps tag name to a string like \'global\' or \'local\'.
713 713 Subclasses or extensions are free to add their own tags, but
714 714 should be aware that the returned dicts will be retained for the
715 715 duration of the localrepo object.'''
716 716
717 717 # XXX what tagtype should subclasses/extensions use? Currently
718 718 # mq and bookmarks add tags, but do not set the tagtype at all.
719 719 # Should each extension invent its own tag type? Should there
720 720 # be one tagtype for all such "virtual" tags? Or is the status
721 721 # quo fine?
722 722
723 723
724 724 # map tag name to (node, hist)
725 725 alltags = tagsmod.findglobaltags(self.ui, self)
726 726 # map tag name to tag type
727 727 tagtypes = dict((tag, 'global') for tag in alltags)
728 728
729 729 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
730 730
731 731 # Build the return dicts. Have to re-encode tag names because
732 732 # the tags module always uses UTF-8 (in order not to lose info
733 733 # writing to the cache), but the rest of Mercurial wants them in
734 734 # local encoding.
735 735 tags = {}
736 736 for (name, (node, hist)) in alltags.iteritems():
737 737 if node != nullid:
738 738 tags[encoding.tolocal(name)] = node
739 739 tags['tip'] = self.changelog.tip()
740 740 tagtypes = dict([(encoding.tolocal(name), value)
741 741 for (name, value) in tagtypes.iteritems()])
742 742 return (tags, tagtypes)
743 743
744 744 def tagtype(self, tagname):
745 745 '''
746 746 return the type of the given tag. result can be:
747 747
748 748 'local' : a local tag
749 749 'global' : a global tag
750 750 None : tag does not exist
751 751 '''
752 752
753 753 return self._tagscache.tagtypes.get(tagname)
754 754
755 755 def tagslist(self):
756 756 '''return a list of tags ordered by revision'''
757 757 if not self._tagscache.tagslist:
758 758 l = []
759 759 for t, n in self.tags().iteritems():
760 760 l.append((self.changelog.rev(n), t, n))
761 761 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
762 762
763 763 return self._tagscache.tagslist
764 764
765 765 def nodetags(self, node):
766 766 '''return the tags associated with a node'''
767 767 if not self._tagscache.nodetagscache:
768 768 nodetagscache = {}
769 769 for t, n in self._tagscache.tags.iteritems():
770 770 nodetagscache.setdefault(n, []).append(t)
771 771 for tags in nodetagscache.itervalues():
772 772 tags.sort()
773 773 self._tagscache.nodetagscache = nodetagscache
774 774 return self._tagscache.nodetagscache.get(node, [])
775 775
776 776 def nodebookmarks(self, node):
777 777 """return the list of bookmarks pointing to the specified node"""
778 778 marks = []
779 779 for bookmark, n in self._bookmarks.iteritems():
780 780 if n == node:
781 781 marks.append(bookmark)
782 782 return sorted(marks)
783 783
784 784 def branchmap(self):
785 785 '''returns a dictionary {branch: [branchheads]} with branchheads
786 786 ordered by increasing revision number'''
787 787 branchmap.updatecache(self)
788 788 return self._branchcaches[self.filtername]
789 789
790 790 @unfilteredmethod
791 791 def revbranchcache(self):
792 792 if not self._revbranchcache:
793 793 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
794 794 return self._revbranchcache
795 795
796 796 def branchtip(self, branch, ignoremissing=False):
797 797 '''return the tip node for a given branch
798 798
799 799 If ignoremissing is True, then this method will not raise an error.
800 800 This is helpful for callers that only expect None for a missing branch
801 801 (e.g. namespace).
802 802
803 803 '''
804 804 try:
805 805 return self.branchmap().branchtip(branch)
806 806 except KeyError:
807 807 if not ignoremissing:
808 808 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
809 809 else:
810 810 pass
811 811
812 812 def lookup(self, key):
813 813 return self[key].node()
814 814
815 815 def lookupbranch(self, key, remote=None):
816 816 repo = remote or self
817 817 if key in repo.branchmap():
818 818 return key
819 819
820 820 repo = (remote and remote.local()) and remote or self
821 821 return repo[key].branch()
822 822
823 823 def known(self, nodes):
824 824 cl = self.changelog
825 825 nm = cl.nodemap
826 826 filtered = cl.filteredrevs
827 827 result = []
828 828 for n in nodes:
829 829 r = nm.get(n)
830 830 resp = not (r is None or r in filtered)
831 831 result.append(resp)
832 832 return result
833 833
834 834 def local(self):
835 835 return self
836 836
837 837 def publishing(self):
838 838 # it's safe (and desirable) to trust the publish flag unconditionally
839 839 # so that we don't finalize changes shared between users via ssh or nfs
840 840 return self.ui.configbool('phases', 'publish', True, untrusted=True)
841 841
842 842 def cancopy(self):
843 843 # so statichttprepo's override of local() works
844 844 if not self.local():
845 845 return False
846 846 if not self.publishing():
847 847 return True
848 848 # if publishing we can't copy if there is filtered content
849 849 return not self.filtered('visible').changelog.filteredrevs
850 850
851 851 def shared(self):
852 852 '''the type of shared repository (None if not shared)'''
853 853 if self.sharedpath != self.path:
854 854 return 'store'
855 855 return None
856 856
857 857 def wjoin(self, f, *insidef):
858 858 return self.vfs.reljoin(self.root, f, *insidef)
859 859
860 860 def file(self, f):
861 861 if f[0] == '/':
862 862 f = f[1:]
863 863 return filelog.filelog(self.svfs, f)
864 864
865 865 def changectx(self, changeid):
866 866 return self[changeid]
867 867
868 868 def setparents(self, p1, p2=nullid):
869 869 with self.dirstate.parentchange():
870 870 copies = self.dirstate.setparents(p1, p2)
871 871 pctx = self[p1]
872 872 if copies:
873 873 # Adjust copy records, the dirstate cannot do it, it
874 874 # requires access to parents manifests. Preserve them
875 875 # only for entries added to first parent.
876 876 for f in copies:
877 877 if f not in pctx and copies[f] in pctx:
878 878 self.dirstate.copy(copies[f], f)
879 879 if p2 == nullid:
880 880 for f, s in sorted(self.dirstate.copies().items()):
881 881 if f not in pctx and s not in pctx:
882 882 self.dirstate.copy(None, f)
883 883
884 884 def filectx(self, path, changeid=None, fileid=None):
885 885 """changeid can be a changeset revision, node, or tag.
886 886 fileid can be a file revision or node."""
887 887 return context.filectx(self, path, changeid, fileid)
888 888
889 889 def getcwd(self):
890 890 return self.dirstate.getcwd()
891 891
892 892 def pathto(self, f, cwd=None):
893 893 return self.dirstate.pathto(f, cwd)
894 894
895 895 def _loadfilter(self, filter):
896 896 if filter not in self.filterpats:
897 897 l = []
898 898 for pat, cmd in self.ui.configitems(filter):
899 899 if cmd == '!':
900 900 continue
901 901 mf = matchmod.match(self.root, '', [pat])
902 902 fn = None
903 903 params = cmd
904 904 for name, filterfn in self._datafilters.iteritems():
905 905 if cmd.startswith(name):
906 906 fn = filterfn
907 907 params = cmd[len(name):].lstrip()
908 908 break
909 909 if not fn:
910 910 fn = lambda s, c, **kwargs: util.filter(s, c)
911 911 # Wrap old filters not supporting keyword arguments
912 912 if not inspect.getargspec(fn)[2]:
913 913 oldfn = fn
914 914 fn = lambda s, c, **kwargs: oldfn(s, c)
915 915 l.append((mf, fn, params))
916 916 self.filterpats[filter] = l
917 917 return self.filterpats[filter]
918 918
919 919 def _filter(self, filterpats, filename, data):
920 920 for mf, fn, cmd in filterpats:
921 921 if mf(filename):
922 922 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
923 923 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
924 924 break
925 925
926 926 return data
927 927
928 928 @unfilteredpropertycache
929 929 def _encodefilterpats(self):
930 930 return self._loadfilter('encode')
931 931
932 932 @unfilteredpropertycache
933 933 def _decodefilterpats(self):
934 934 return self._loadfilter('decode')
935 935
936 936 def adddatafilter(self, name, filter):
937 937 self._datafilters[name] = filter
938 938
939 939 def wread(self, filename):
940 940 if self.wvfs.islink(filename):
941 941 data = self.wvfs.readlink(filename)
942 942 else:
943 943 data = self.wvfs.read(filename)
944 944 return self._filter(self._encodefilterpats, filename, data)
945 945
946 946 def wwrite(self, filename, data, flags, backgroundclose=False):
947 947 """write ``data`` into ``filename`` in the working directory
948 948
949 949 This returns length of written (maybe decoded) data.
950 950 """
951 951 data = self._filter(self._decodefilterpats, filename, data)
952 952 if 'l' in flags:
953 953 self.wvfs.symlink(data, filename)
954 954 else:
955 955 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
956 956 if 'x' in flags:
957 957 self.wvfs.setflags(filename, False, True)
958 958 return len(data)
959 959
960 960 def wwritedata(self, filename, data):
961 961 return self._filter(self._decodefilterpats, filename, data)
962 962
963 963 def currenttransaction(self):
964 964 """return the current transaction or None if non exists"""
965 965 if self._transref:
966 966 tr = self._transref()
967 967 else:
968 968 tr = None
969 969
970 970 if tr and tr.running():
971 971 return tr
972 972 return None
973 973
974 974 def transaction(self, desc, report=None):
975 975 if (self.ui.configbool('devel', 'all-warnings')
976 976 or self.ui.configbool('devel', 'check-locks')):
977 977 if self._currentlock(self._lockref) is None:
978 978 raise error.ProgrammingError('transaction requires locking')
979 979 tr = self.currenttransaction()
980 980 if tr is not None:
981 981 return tr.nest()
982 982
983 983 # abort here if the journal already exists
984 984 if self.svfs.exists("journal"):
985 985 raise error.RepoError(
986 986 _("abandoned transaction found"),
987 987 hint=_("run 'hg recover' to clean up transaction"))
988 988
989 989 idbase = "%.40f#%f" % (random.random(), time.time())
990 990 ha = hex(hashlib.sha1(idbase).digest())
991 991 txnid = 'TXN:' + ha
992 992 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
993 993
994 994 self._writejournal(desc)
995 995 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
996 996 if report:
997 997 rp = report
998 998 else:
999 999 rp = self.ui.warn
1000 1000 vfsmap = {'plain': self.vfs} # root of .hg/
1001 1001 # we must avoid cyclic reference between repo and transaction.
1002 1002 reporef = weakref.ref(self)
1003 1003 # Code to track tag movement
1004 1004 #
1005 1005 # Since tags are all handled as file content, it is actually quite hard
1006 1006 # to track these movement from a code perspective. So we fallback to a
1007 1007 # tracking at the repository level. One could envision to track changes
1008 1008 # to the '.hgtags' file through changegroup apply but that fails to
1009 1009 # cope with case where transaction expose new heads without changegroup
1010 1010 # being involved (eg: phase movement).
1011 1011 #
1012 1012 # For now, We gate the feature behind a flag since this likely comes
1013 1013 # with performance impacts. The current code run more often than needed
1014 1014 # and do not use caches as much as it could. The current focus is on
1015 1015 # the behavior of the feature so we disable it by default. The flag
1016 1016 # will be removed when we are happy with the performance impact.
1017 1017 #
1018 1018 # Once this feature is no longer experimental move the following
1019 1019 # documentation to the appropriate help section:
1020 1020 #
1021 1021 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1022 1022 # tags (new or changed or deleted tags). In addition the details of
1023 1023 # these changes are made available in a file at:
1024 1024 # ``REPOROOT/.hg/changes/tags.changes``.
1025 1025 # Make sure you check for HG_TAG_MOVED before reading that file as it
1026 1026 # might exist from a previous transaction even if no tag were touched
1027 1027 # in this one. Changes are recorded in a line base format::
1028 1028 #
1029 1029 # <action> <hex-node> <tag-name>\n
1030 1030 #
1031 1031 # Actions are defined as follow:
1032 1032 # "-R": tag is removed,
1033 1033 # "+A": tag is added,
1034 1034 # "-M": tag is moved (old value),
1035 1035 # "+M": tag is moved (new value),
1036 1036 tracktags = lambda x: None
1037 1037 # experimental config: experimental.hook-track-tags
1038 1038 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags',
1039 1039 False)
1040 1040 if desc != 'strip' and shouldtracktags:
1041 1041 oldheads = self.changelog.headrevs()
1042 1042 def tracktags(tr2):
1043 1043 repo = reporef()
1044 1044 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1045 1045 newheads = repo.changelog.headrevs()
1046 1046 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1047 1047 # notes: we compare lists here.
1048 1048 # As we do it only once buiding set would not be cheaper
1049 1049 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1050 1050 if changes:
1051 1051 tr2.hookargs['tag_moved'] = '1'
1052 1052 with repo.vfs('changes/tags.changes', 'w',
1053 1053 atomictemp=True) as changesfile:
1054 1054 # note: we do not register the file to the transaction
1055 1055 # because we needs it to still exist on the transaction
1056 1056 # is close (for txnclose hooks)
1057 1057 tagsmod.writediff(changesfile, changes)
1058 1058 def validate(tr2):
1059 1059 """will run pre-closing hooks"""
1060 1060 # XXX the transaction API is a bit lacking here so we take a hacky
1061 1061 # path for now
1062 1062 #
1063 1063 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1064 1064 # dict is copied before these run. In addition we needs the data
1065 1065 # available to in memory hooks too.
1066 1066 #
1067 1067 # Moreover, we also need to make sure this runs before txnclose
1068 1068 # hooks and there is no "pending" mechanism that would execute
1069 1069 # logic only if hooks are about to run.
1070 1070 #
1071 1071 # Fixing this limitation of the transaction is also needed to track
1072 1072 # other families of changes (bookmarks, phases, obsolescence).
1073 1073 #
1074 1074 # This will have to be fixed before we remove the experimental
1075 1075 # gating.
1076 1076 tracktags(tr2)
1077 1077 reporef().hook('pretxnclose', throw=True,
1078 1078 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1079 1079 def releasefn(tr, success):
1080 1080 repo = reporef()
1081 1081 if success:
1082 1082 # this should be explicitly invoked here, because
1083 1083 # in-memory changes aren't written out at closing
1084 1084 # transaction, if tr.addfilegenerator (via
1085 1085 # dirstate.write or so) isn't invoked while
1086 1086 # transaction running
1087 1087 repo.dirstate.write(None)
1088 1088 else:
1089 1089 # discard all changes (including ones already written
1090 1090 # out) in this transaction
1091 1091 repo.dirstate.restorebackup(None, prefix='journal.')
1092 1092
1093 1093 repo.invalidate(clearfilecache=True)
1094 1094
1095 1095 tr = transaction.transaction(rp, self.svfs, vfsmap,
1096 1096 "journal",
1097 1097 "undo",
1098 1098 aftertrans(renames),
1099 1099 self.store.createmode,
1100 1100 validator=validate,
1101 releasefn=releasefn)
1101 releasefn=releasefn,
1102 checkambigfiles=_cachedfiles)
1102 1103 tr.changes['revs'] = set()
1103 1104 tr.changes['obsmarkers'] = set()
1104 1105
1105 1106 tr.hookargs['txnid'] = txnid
1106 1107 # note: writing the fncache only during finalize mean that the file is
1107 1108 # outdated when running hooks. As fncache is used for streaming clone,
1108 1109 # this is not expected to break anything that happen during the hooks.
1109 1110 tr.addfinalize('flush-fncache', self.store.write)
1110 1111 def txnclosehook(tr2):
1111 1112 """To be run if transaction is successful, will schedule a hook run
1112 1113 """
1113 1114 # Don't reference tr2 in hook() so we don't hold a reference.
1114 1115 # This reduces memory consumption when there are multiple
1115 1116 # transactions per lock. This can likely go away if issue5045
1116 1117 # fixes the function accumulation.
1117 1118 hookargs = tr2.hookargs
1118 1119
1119 1120 def hook():
1120 1121 reporef().hook('txnclose', throw=False, txnname=desc,
1121 1122 **pycompat.strkwargs(hookargs))
1122 1123 reporef()._afterlock(hook)
1123 1124 tr.addfinalize('txnclose-hook', txnclosehook)
1124 1125 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1125 1126 def txnaborthook(tr2):
1126 1127 """To be run if transaction is aborted
1127 1128 """
1128 1129 reporef().hook('txnabort', throw=False, txnname=desc,
1129 1130 **tr2.hookargs)
1130 1131 tr.addabort('txnabort-hook', txnaborthook)
1131 1132 # avoid eager cache invalidation. in-memory data should be identical
1132 1133 # to stored data if transaction has no error.
1133 1134 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1134 1135 self._transref = weakref.ref(tr)
1135 1136 return tr
1136 1137
1137 1138 def _journalfiles(self):
1138 1139 return ((self.svfs, 'journal'),
1139 1140 (self.vfs, 'journal.dirstate'),
1140 1141 (self.vfs, 'journal.branch'),
1141 1142 (self.vfs, 'journal.desc'),
1142 1143 (self.vfs, 'journal.bookmarks'),
1143 1144 (self.svfs, 'journal.phaseroots'))
1144 1145
1145 1146 def undofiles(self):
1146 1147 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1147 1148
1148 1149 @unfilteredmethod
1149 1150 def _writejournal(self, desc):
1150 1151 self.dirstate.savebackup(None, prefix='journal.')
1151 1152 self.vfs.write("journal.branch",
1152 1153 encoding.fromlocal(self.dirstate.branch()))
1153 1154 self.vfs.write("journal.desc",
1154 1155 "%d\n%s\n" % (len(self), desc))
1155 1156 self.vfs.write("journal.bookmarks",
1156 1157 self.vfs.tryread("bookmarks"))
1157 1158 self.svfs.write("journal.phaseroots",
1158 1159 self.svfs.tryread("phaseroots"))
1159 1160
1160 1161 def recover(self):
1161 1162 with self.lock():
1162 1163 if self.svfs.exists("journal"):
1163 1164 self.ui.status(_("rolling back interrupted transaction\n"))
1164 1165 vfsmap = {'': self.svfs,
1165 1166 'plain': self.vfs,}
1166 1167 transaction.rollback(self.svfs, vfsmap, "journal",
1167 self.ui.warn)
1168 self.ui.warn,
1169 checkambigfiles=_cachedfiles)
1168 1170 self.invalidate()
1169 1171 return True
1170 1172 else:
1171 1173 self.ui.warn(_("no interrupted transaction available\n"))
1172 1174 return False
1173 1175
1174 1176 def rollback(self, dryrun=False, force=False):
1175 1177 wlock = lock = dsguard = None
1176 1178 try:
1177 1179 wlock = self.wlock()
1178 1180 lock = self.lock()
1179 1181 if self.svfs.exists("undo"):
1180 1182 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1181 1183
1182 1184 return self._rollback(dryrun, force, dsguard)
1183 1185 else:
1184 1186 self.ui.warn(_("no rollback information available\n"))
1185 1187 return 1
1186 1188 finally:
1187 1189 release(dsguard, lock, wlock)
1188 1190
1189 1191 @unfilteredmethod # Until we get smarter cache management
1190 1192 def _rollback(self, dryrun, force, dsguard):
1191 1193 ui = self.ui
1192 1194 try:
1193 1195 args = self.vfs.read('undo.desc').splitlines()
1194 1196 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1195 1197 if len(args) >= 3:
1196 1198 detail = args[2]
1197 1199 oldtip = oldlen - 1
1198 1200
1199 1201 if detail and ui.verbose:
1200 1202 msg = (_('repository tip rolled back to revision %d'
1201 1203 ' (undo %s: %s)\n')
1202 1204 % (oldtip, desc, detail))
1203 1205 else:
1204 1206 msg = (_('repository tip rolled back to revision %d'
1205 1207 ' (undo %s)\n')
1206 1208 % (oldtip, desc))
1207 1209 except IOError:
1208 1210 msg = _('rolling back unknown transaction\n')
1209 1211 desc = None
1210 1212
1211 1213 if not force and self['.'] != self['tip'] and desc == 'commit':
1212 1214 raise error.Abort(
1213 1215 _('rollback of last commit while not checked out '
1214 1216 'may lose data'), hint=_('use -f to force'))
1215 1217
1216 1218 ui.status(msg)
1217 1219 if dryrun:
1218 1220 return 0
1219 1221
1220 1222 parents = self.dirstate.parents()
1221 1223 self.destroying()
1222 1224 vfsmap = {'plain': self.vfs, '': self.svfs}
1223 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1225 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1226 checkambigfiles=_cachedfiles)
1224 1227 if self.vfs.exists('undo.bookmarks'):
1225 1228 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1226 1229 if self.svfs.exists('undo.phaseroots'):
1227 1230 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1228 1231 self.invalidate()
1229 1232
1230 1233 parentgone = (parents[0] not in self.changelog.nodemap or
1231 1234 parents[1] not in self.changelog.nodemap)
1232 1235 if parentgone:
1233 1236 # prevent dirstateguard from overwriting already restored one
1234 1237 dsguard.close()
1235 1238
1236 1239 self.dirstate.restorebackup(None, prefix='undo.')
1237 1240 try:
1238 1241 branch = self.vfs.read('undo.branch')
1239 1242 self.dirstate.setbranch(encoding.tolocal(branch))
1240 1243 except IOError:
1241 1244 ui.warn(_('named branch could not be reset: '
1242 1245 'current branch is still \'%s\'\n')
1243 1246 % self.dirstate.branch())
1244 1247
1245 1248 parents = tuple([p.rev() for p in self[None].parents()])
1246 1249 if len(parents) > 1:
1247 1250 ui.status(_('working directory now based on '
1248 1251 'revisions %d and %d\n') % parents)
1249 1252 else:
1250 1253 ui.status(_('working directory now based on '
1251 1254 'revision %d\n') % parents)
1252 1255 mergemod.mergestate.clean(self, self['.'].node())
1253 1256
1254 1257 # TODO: if we know which new heads may result from this rollback, pass
1255 1258 # them to destroy(), which will prevent the branchhead cache from being
1256 1259 # invalidated.
1257 1260 self.destroyed()
1258 1261 return 0
1259 1262
1260 1263 def _buildcacheupdater(self, newtransaction):
1261 1264 """called during transaction to build the callback updating cache
1262 1265
1263 1266 Lives on the repository to help extension who might want to augment
1264 1267 this logic. For this purpose, the created transaction is passed to the
1265 1268 method.
1266 1269 """
1267 1270 # we must avoid cyclic reference between repo and transaction.
1268 1271 reporef = weakref.ref(self)
1269 1272 def updater(tr):
1270 1273 repo = reporef()
1271 1274 repo.updatecaches(tr)
1272 1275 return updater
1273 1276
1274 1277 @unfilteredmethod
1275 1278 def updatecaches(self, tr=None):
1276 1279 """warm appropriate caches
1277 1280
1278 1281 If this function is called after a transaction closed. The transaction
1279 1282 will be available in the 'tr' argument. This can be used to selectively
1280 1283 update caches relevant to the changes in that transaction.
1281 1284 """
1282 1285 if tr is not None and tr.hookargs.get('source') == 'strip':
1283 1286 # During strip, many caches are invalid but
1284 1287 # later call to `destroyed` will refresh them.
1285 1288 return
1286 1289
1287 1290 if tr is None or tr.changes['revs']:
1288 1291 # updating the unfiltered branchmap should refresh all the others,
1289 1292 self.ui.debug('updating the branch cache\n')
1290 1293 branchmap.updatecache(self.filtered('served'))
1291 1294
1292 1295 def invalidatecaches(self):
1293 1296
1294 1297 if '_tagscache' in vars(self):
1295 1298 # can't use delattr on proxy
1296 1299 del self.__dict__['_tagscache']
1297 1300
1298 1301 self.unfiltered()._branchcaches.clear()
1299 1302 self.invalidatevolatilesets()
1300 1303
1301 1304 def invalidatevolatilesets(self):
1302 1305 self.filteredrevcache.clear()
1303 1306 obsolete.clearobscaches(self)
1304 1307
1305 1308 def invalidatedirstate(self):
1306 1309 '''Invalidates the dirstate, causing the next call to dirstate
1307 1310 to check if it was modified since the last time it was read,
1308 1311 rereading it if it has.
1309 1312
1310 1313 This is different to dirstate.invalidate() that it doesn't always
1311 1314 rereads the dirstate. Use dirstate.invalidate() if you want to
1312 1315 explicitly read the dirstate again (i.e. restoring it to a previous
1313 1316 known good state).'''
1314 1317 if hasunfilteredcache(self, 'dirstate'):
1315 1318 for k in self.dirstate._filecache:
1316 1319 try:
1317 1320 delattr(self.dirstate, k)
1318 1321 except AttributeError:
1319 1322 pass
1320 1323 delattr(self.unfiltered(), 'dirstate')
1321 1324
1322 1325 def invalidate(self, clearfilecache=False):
1323 1326 '''Invalidates both store and non-store parts other than dirstate
1324 1327
1325 1328 If a transaction is running, invalidation of store is omitted,
1326 1329 because discarding in-memory changes might cause inconsistency
1327 1330 (e.g. incomplete fncache causes unintentional failure, but
1328 1331 redundant one doesn't).
1329 1332 '''
1330 1333 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1331 1334 for k in list(self._filecache.keys()):
1332 1335 # dirstate is invalidated separately in invalidatedirstate()
1333 1336 if k == 'dirstate':
1334 1337 continue
1335 1338
1336 1339 if clearfilecache:
1337 1340 del self._filecache[k]
1338 1341 try:
1339 1342 delattr(unfiltered, k)
1340 1343 except AttributeError:
1341 1344 pass
1342 1345 self.invalidatecaches()
1343 1346 if not self.currenttransaction():
1344 1347 # TODO: Changing contents of store outside transaction
1345 1348 # causes inconsistency. We should make in-memory store
1346 1349 # changes detectable, and abort if changed.
1347 1350 self.store.invalidatecaches()
1348 1351
1349 1352 def invalidateall(self):
1350 1353 '''Fully invalidates both store and non-store parts, causing the
1351 1354 subsequent operation to reread any outside changes.'''
1352 1355 # extension should hook this to invalidate its caches
1353 1356 self.invalidate()
1354 1357 self.invalidatedirstate()
1355 1358
1356 1359 @unfilteredmethod
1357 1360 def _refreshfilecachestats(self, tr):
1358 1361 """Reload stats of cached files so that they are flagged as valid"""
1359 1362 for k, ce in self._filecache.items():
1360 1363 if k == 'dirstate' or k not in self.__dict__:
1361 1364 continue
1362 1365 ce.refresh()
1363 1366
1364 1367 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1365 1368 inheritchecker=None, parentenvvar=None):
1366 1369 parentlock = None
1367 1370 # the contents of parentenvvar are used by the underlying lock to
1368 1371 # determine whether it can be inherited
1369 1372 if parentenvvar is not None:
1370 1373 parentlock = encoding.environ.get(parentenvvar)
1371 1374 try:
1372 1375 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1373 1376 acquirefn=acquirefn, desc=desc,
1374 1377 inheritchecker=inheritchecker,
1375 1378 parentlock=parentlock)
1376 1379 except error.LockHeld as inst:
1377 1380 if not wait:
1378 1381 raise
1379 1382 # show more details for new-style locks
1380 1383 if ':' in inst.locker:
1381 1384 host, pid = inst.locker.split(":", 1)
1382 1385 self.ui.warn(
1383 1386 _("waiting for lock on %s held by process %r "
1384 1387 "on host %r\n") % (desc, pid, host))
1385 1388 else:
1386 1389 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1387 1390 (desc, inst.locker))
1388 1391 # default to 600 seconds timeout
1389 1392 l = lockmod.lock(vfs, lockname,
1390 1393 int(self.ui.config("ui", "timeout", "600")),
1391 1394 releasefn=releasefn, acquirefn=acquirefn,
1392 1395 desc=desc)
1393 1396 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1394 1397 return l
1395 1398
1396 1399 def _afterlock(self, callback):
1397 1400 """add a callback to be run when the repository is fully unlocked
1398 1401
1399 1402 The callback will be executed when the outermost lock is released
1400 1403 (with wlock being higher level than 'lock')."""
1401 1404 for ref in (self._wlockref, self._lockref):
1402 1405 l = ref and ref()
1403 1406 if l and l.held:
1404 1407 l.postrelease.append(callback)
1405 1408 break
1406 1409 else: # no lock have been found.
1407 1410 callback()
1408 1411
1409 1412 def lock(self, wait=True):
1410 1413 '''Lock the repository store (.hg/store) and return a weak reference
1411 1414 to the lock. Use this before modifying the store (e.g. committing or
1412 1415 stripping). If you are opening a transaction, get a lock as well.)
1413 1416
1414 1417 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1415 1418 'wlock' first to avoid a dead-lock hazard.'''
1416 1419 l = self._currentlock(self._lockref)
1417 1420 if l is not None:
1418 1421 l.lock()
1419 1422 return l
1420 1423
1421 1424 l = self._lock(self.svfs, "lock", wait, None,
1422 1425 self.invalidate, _('repository %s') % self.origroot)
1423 1426 self._lockref = weakref.ref(l)
1424 1427 return l
1425 1428
1426 1429 def _wlockchecktransaction(self):
1427 1430 if self.currenttransaction() is not None:
1428 1431 raise error.LockInheritanceContractViolation(
1429 1432 'wlock cannot be inherited in the middle of a transaction')
1430 1433
1431 1434 def wlock(self, wait=True):
1432 1435 '''Lock the non-store parts of the repository (everything under
1433 1436 .hg except .hg/store) and return a weak reference to the lock.
1434 1437
1435 1438 Use this before modifying files in .hg.
1436 1439
1437 1440 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1438 1441 'wlock' first to avoid a dead-lock hazard.'''
1439 1442 l = self._wlockref and self._wlockref()
1440 1443 if l is not None and l.held:
1441 1444 l.lock()
1442 1445 return l
1443 1446
1444 1447 # We do not need to check for non-waiting lock acquisition. Such
1445 1448 # acquisition would not cause dead-lock as they would just fail.
1446 1449 if wait and (self.ui.configbool('devel', 'all-warnings')
1447 1450 or self.ui.configbool('devel', 'check-locks')):
1448 1451 if self._currentlock(self._lockref) is not None:
1449 1452 self.ui.develwarn('"wlock" acquired after "lock"')
1450 1453
1451 1454 def unlock():
1452 1455 if self.dirstate.pendingparentchange():
1453 1456 self.dirstate.invalidate()
1454 1457 else:
1455 1458 self.dirstate.write(None)
1456 1459
1457 1460 self._filecache['dirstate'].refresh()
1458 1461
1459 1462 l = self._lock(self.vfs, "wlock", wait, unlock,
1460 1463 self.invalidatedirstate, _('working directory of %s') %
1461 1464 self.origroot,
1462 1465 inheritchecker=self._wlockchecktransaction,
1463 1466 parentenvvar='HG_WLOCK_LOCKER')
1464 1467 self._wlockref = weakref.ref(l)
1465 1468 return l
1466 1469
1467 1470 def _currentlock(self, lockref):
1468 1471 """Returns the lock if it's held, or None if it's not."""
1469 1472 if lockref is None:
1470 1473 return None
1471 1474 l = lockref()
1472 1475 if l is None or not l.held:
1473 1476 return None
1474 1477 return l
1475 1478
1476 1479 def currentwlock(self):
1477 1480 """Returns the wlock if it's held, or None if it's not."""
1478 1481 return self._currentlock(self._wlockref)
1479 1482
1480 1483 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1481 1484 """
1482 1485 commit an individual file as part of a larger transaction
1483 1486 """
1484 1487
1485 1488 fname = fctx.path()
1486 1489 fparent1 = manifest1.get(fname, nullid)
1487 1490 fparent2 = manifest2.get(fname, nullid)
1488 1491 if isinstance(fctx, context.filectx):
1489 1492 node = fctx.filenode()
1490 1493 if node in [fparent1, fparent2]:
1491 1494 self.ui.debug('reusing %s filelog entry\n' % fname)
1492 1495 if manifest1.flags(fname) != fctx.flags():
1493 1496 changelist.append(fname)
1494 1497 return node
1495 1498
1496 1499 flog = self.file(fname)
1497 1500 meta = {}
1498 1501 copy = fctx.renamed()
1499 1502 if copy and copy[0] != fname:
1500 1503 # Mark the new revision of this file as a copy of another
1501 1504 # file. This copy data will effectively act as a parent
1502 1505 # of this new revision. If this is a merge, the first
1503 1506 # parent will be the nullid (meaning "look up the copy data")
1504 1507 # and the second one will be the other parent. For example:
1505 1508 #
1506 1509 # 0 --- 1 --- 3 rev1 changes file foo
1507 1510 # \ / rev2 renames foo to bar and changes it
1508 1511 # \- 2 -/ rev3 should have bar with all changes and
1509 1512 # should record that bar descends from
1510 1513 # bar in rev2 and foo in rev1
1511 1514 #
1512 1515 # this allows this merge to succeed:
1513 1516 #
1514 1517 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1515 1518 # \ / merging rev3 and rev4 should use bar@rev2
1516 1519 # \- 2 --- 4 as the merge base
1517 1520 #
1518 1521
1519 1522 cfname = copy[0]
1520 1523 crev = manifest1.get(cfname)
1521 1524 newfparent = fparent2
1522 1525
1523 1526 if manifest2: # branch merge
1524 1527 if fparent2 == nullid or crev is None: # copied on remote side
1525 1528 if cfname in manifest2:
1526 1529 crev = manifest2[cfname]
1527 1530 newfparent = fparent1
1528 1531
1529 1532 # Here, we used to search backwards through history to try to find
1530 1533 # where the file copy came from if the source of a copy was not in
1531 1534 # the parent directory. However, this doesn't actually make sense to
1532 1535 # do (what does a copy from something not in your working copy even
1533 1536 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1534 1537 # the user that copy information was dropped, so if they didn't
1535 1538 # expect this outcome it can be fixed, but this is the correct
1536 1539 # behavior in this circumstance.
1537 1540
1538 1541 if crev:
1539 1542 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1540 1543 meta["copy"] = cfname
1541 1544 meta["copyrev"] = hex(crev)
1542 1545 fparent1, fparent2 = nullid, newfparent
1543 1546 else:
1544 1547 self.ui.warn(_("warning: can't find ancestor for '%s' "
1545 1548 "copied from '%s'!\n") % (fname, cfname))
1546 1549
1547 1550 elif fparent1 == nullid:
1548 1551 fparent1, fparent2 = fparent2, nullid
1549 1552 elif fparent2 != nullid:
1550 1553 # is one parent an ancestor of the other?
1551 1554 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1552 1555 if fparent1 in fparentancestors:
1553 1556 fparent1, fparent2 = fparent2, nullid
1554 1557 elif fparent2 in fparentancestors:
1555 1558 fparent2 = nullid
1556 1559
1557 1560 # is the file changed?
1558 1561 text = fctx.data()
1559 1562 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1560 1563 changelist.append(fname)
1561 1564 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1562 1565 # are just the flags changed during merge?
1563 1566 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1564 1567 changelist.append(fname)
1565 1568
1566 1569 return fparent1
1567 1570
1568 1571 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1569 1572 """check for commit arguments that aren't committable"""
1570 1573 if match.isexact() or match.prefix():
1571 1574 matched = set(status.modified + status.added + status.removed)
1572 1575
1573 1576 for f in match.files():
1574 1577 f = self.dirstate.normalize(f)
1575 1578 if f == '.' or f in matched or f in wctx.substate:
1576 1579 continue
1577 1580 if f in status.deleted:
1578 1581 fail(f, _('file not found!'))
1579 1582 if f in vdirs: # visited directory
1580 1583 d = f + '/'
1581 1584 for mf in matched:
1582 1585 if mf.startswith(d):
1583 1586 break
1584 1587 else:
1585 1588 fail(f, _("no match under directory!"))
1586 1589 elif f not in self.dirstate:
1587 1590 fail(f, _("file not tracked!"))
1588 1591
1589 1592 @unfilteredmethod
1590 1593 def commit(self, text="", user=None, date=None, match=None, force=False,
1591 1594 editor=False, extra=None):
1592 1595 """Add a new revision to current repository.
1593 1596
1594 1597 Revision information is gathered from the working directory,
1595 1598 match can be used to filter the committed files. If editor is
1596 1599 supplied, it is called to get a commit message.
1597 1600 """
1598 1601 if extra is None:
1599 1602 extra = {}
1600 1603
1601 1604 def fail(f, msg):
1602 1605 raise error.Abort('%s: %s' % (f, msg))
1603 1606
1604 1607 if not match:
1605 1608 match = matchmod.always(self.root, '')
1606 1609
1607 1610 if not force:
1608 1611 vdirs = []
1609 1612 match.explicitdir = vdirs.append
1610 1613 match.bad = fail
1611 1614
1612 1615 wlock = lock = tr = None
1613 1616 try:
1614 1617 wlock = self.wlock()
1615 1618 lock = self.lock() # for recent changelog (see issue4368)
1616 1619
1617 1620 wctx = self[None]
1618 1621 merge = len(wctx.parents()) > 1
1619 1622
1620 1623 if not force and merge and not match.always():
1621 1624 raise error.Abort(_('cannot partially commit a merge '
1622 1625 '(do not specify files or patterns)'))
1623 1626
1624 1627 status = self.status(match=match, clean=force)
1625 1628 if force:
1626 1629 status.modified.extend(status.clean) # mq may commit clean files
1627 1630
1628 1631 # check subrepos
1629 1632 subs = []
1630 1633 commitsubs = set()
1631 1634 newstate = wctx.substate.copy()
1632 1635 # only manage subrepos and .hgsubstate if .hgsub is present
1633 1636 if '.hgsub' in wctx:
1634 1637 # we'll decide whether to track this ourselves, thanks
1635 1638 for c in status.modified, status.added, status.removed:
1636 1639 if '.hgsubstate' in c:
1637 1640 c.remove('.hgsubstate')
1638 1641
1639 1642 # compare current state to last committed state
1640 1643 # build new substate based on last committed state
1641 1644 oldstate = wctx.p1().substate
1642 1645 for s in sorted(newstate.keys()):
1643 1646 if not match(s):
1644 1647 # ignore working copy, use old state if present
1645 1648 if s in oldstate:
1646 1649 newstate[s] = oldstate[s]
1647 1650 continue
1648 1651 if not force:
1649 1652 raise error.Abort(
1650 1653 _("commit with new subrepo %s excluded") % s)
1651 1654 dirtyreason = wctx.sub(s).dirtyreason(True)
1652 1655 if dirtyreason:
1653 1656 if not self.ui.configbool('ui', 'commitsubrepos'):
1654 1657 raise error.Abort(dirtyreason,
1655 1658 hint=_("use --subrepos for recursive commit"))
1656 1659 subs.append(s)
1657 1660 commitsubs.add(s)
1658 1661 else:
1659 1662 bs = wctx.sub(s).basestate()
1660 1663 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1661 1664 if oldstate.get(s, (None, None, None))[1] != bs:
1662 1665 subs.append(s)
1663 1666
1664 1667 # check for removed subrepos
1665 1668 for p in wctx.parents():
1666 1669 r = [s for s in p.substate if s not in newstate]
1667 1670 subs += [s for s in r if match(s)]
1668 1671 if subs:
1669 1672 if (not match('.hgsub') and
1670 1673 '.hgsub' in (wctx.modified() + wctx.added())):
1671 1674 raise error.Abort(
1672 1675 _("can't commit subrepos without .hgsub"))
1673 1676 status.modified.insert(0, '.hgsubstate')
1674 1677
1675 1678 elif '.hgsub' in status.removed:
1676 1679 # clean up .hgsubstate when .hgsub is removed
1677 1680 if ('.hgsubstate' in wctx and
1678 1681 '.hgsubstate' not in (status.modified + status.added +
1679 1682 status.removed)):
1680 1683 status.removed.insert(0, '.hgsubstate')
1681 1684
1682 1685 # make sure all explicit patterns are matched
1683 1686 if not force:
1684 1687 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1685 1688
1686 1689 cctx = context.workingcommitctx(self, status,
1687 1690 text, user, date, extra)
1688 1691
1689 1692 # internal config: ui.allowemptycommit
1690 1693 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1691 1694 or extra.get('close') or merge or cctx.files()
1692 1695 or self.ui.configbool('ui', 'allowemptycommit'))
1693 1696 if not allowemptycommit:
1694 1697 return None
1695 1698
1696 1699 if merge and cctx.deleted():
1697 1700 raise error.Abort(_("cannot commit merge with missing files"))
1698 1701
1699 1702 ms = mergemod.mergestate.read(self)
1700 1703 mergeutil.checkunresolved(ms)
1701 1704
1702 1705 if editor:
1703 1706 cctx._text = editor(self, cctx, subs)
1704 1707 edited = (text != cctx._text)
1705 1708
1706 1709 # Save commit message in case this transaction gets rolled back
1707 1710 # (e.g. by a pretxncommit hook). Leave the content alone on
1708 1711 # the assumption that the user will use the same editor again.
1709 1712 msgfn = self.savecommitmessage(cctx._text)
1710 1713
1711 1714 # commit subs and write new state
1712 1715 if subs:
1713 1716 for s in sorted(commitsubs):
1714 1717 sub = wctx.sub(s)
1715 1718 self.ui.status(_('committing subrepository %s\n') %
1716 1719 subrepo.subrelpath(sub))
1717 1720 sr = sub.commit(cctx._text, user, date)
1718 1721 newstate[s] = (newstate[s][0], sr)
1719 1722 subrepo.writestate(self, newstate)
1720 1723
1721 1724 p1, p2 = self.dirstate.parents()
1722 1725 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1723 1726 try:
1724 1727 self.hook("precommit", throw=True, parent1=hookp1,
1725 1728 parent2=hookp2)
1726 1729 tr = self.transaction('commit')
1727 1730 ret = self.commitctx(cctx, True)
1728 1731 except: # re-raises
1729 1732 if edited:
1730 1733 self.ui.write(
1731 1734 _('note: commit message saved in %s\n') % msgfn)
1732 1735 raise
1733 1736 # update bookmarks, dirstate and mergestate
1734 1737 bookmarks.update(self, [p1, p2], ret)
1735 1738 cctx.markcommitted(ret)
1736 1739 ms.reset()
1737 1740 tr.close()
1738 1741
1739 1742 finally:
1740 1743 lockmod.release(tr, lock, wlock)
1741 1744
1742 1745 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1743 1746 # hack for command that use a temporary commit (eg: histedit)
1744 1747 # temporary commit got stripped before hook release
1745 1748 if self.changelog.hasnode(ret):
1746 1749 self.hook("commit", node=node, parent1=parent1,
1747 1750 parent2=parent2)
1748 1751 self._afterlock(commithook)
1749 1752 return ret
1750 1753
1751 1754 @unfilteredmethod
1752 1755 def commitctx(self, ctx, error=False):
1753 1756 """Add a new revision to current repository.
1754 1757 Revision information is passed via the context argument.
1755 1758 """
1756 1759
1757 1760 tr = None
1758 1761 p1, p2 = ctx.p1(), ctx.p2()
1759 1762 user = ctx.user()
1760 1763
1761 1764 lock = self.lock()
1762 1765 try:
1763 1766 tr = self.transaction("commit")
1764 1767 trp = weakref.proxy(tr)
1765 1768
1766 1769 if ctx.manifestnode():
1767 1770 # reuse an existing manifest revision
1768 1771 mn = ctx.manifestnode()
1769 1772 files = ctx.files()
1770 1773 elif ctx.files():
1771 1774 m1ctx = p1.manifestctx()
1772 1775 m2ctx = p2.manifestctx()
1773 1776 mctx = m1ctx.copy()
1774 1777
1775 1778 m = mctx.read()
1776 1779 m1 = m1ctx.read()
1777 1780 m2 = m2ctx.read()
1778 1781
1779 1782 # check in files
1780 1783 added = []
1781 1784 changed = []
1782 1785 removed = list(ctx.removed())
1783 1786 linkrev = len(self)
1784 1787 self.ui.note(_("committing files:\n"))
1785 1788 for f in sorted(ctx.modified() + ctx.added()):
1786 1789 self.ui.note(f + "\n")
1787 1790 try:
1788 1791 fctx = ctx[f]
1789 1792 if fctx is None:
1790 1793 removed.append(f)
1791 1794 else:
1792 1795 added.append(f)
1793 1796 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1794 1797 trp, changed)
1795 1798 m.setflag(f, fctx.flags())
1796 1799 except OSError as inst:
1797 1800 self.ui.warn(_("trouble committing %s!\n") % f)
1798 1801 raise
1799 1802 except IOError as inst:
1800 1803 errcode = getattr(inst, 'errno', errno.ENOENT)
1801 1804 if error or errcode and errcode != errno.ENOENT:
1802 1805 self.ui.warn(_("trouble committing %s!\n") % f)
1803 1806 raise
1804 1807
1805 1808 # update manifest
1806 1809 self.ui.note(_("committing manifest\n"))
1807 1810 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1808 1811 drop = [f for f in removed if f in m]
1809 1812 for f in drop:
1810 1813 del m[f]
1811 1814 mn = mctx.write(trp, linkrev,
1812 1815 p1.manifestnode(), p2.manifestnode(),
1813 1816 added, drop)
1814 1817 files = changed + removed
1815 1818 else:
1816 1819 mn = p1.manifestnode()
1817 1820 files = []
1818 1821
1819 1822 # update changelog
1820 1823 self.ui.note(_("committing changelog\n"))
1821 1824 self.changelog.delayupdate(tr)
1822 1825 n = self.changelog.add(mn, files, ctx.description(),
1823 1826 trp, p1.node(), p2.node(),
1824 1827 user, ctx.date(), ctx.extra().copy())
1825 1828 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1826 1829 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1827 1830 parent2=xp2)
1828 1831 # set the new commit is proper phase
1829 1832 targetphase = subrepo.newcommitphase(self.ui, ctx)
1830 1833 if targetphase:
1831 1834 # retract boundary do not alter parent changeset.
1832 1835 # if a parent have higher the resulting phase will
1833 1836 # be compliant anyway
1834 1837 #
1835 1838 # if minimal phase was 0 we don't need to retract anything
1836 1839 phases.retractboundary(self, tr, targetphase, [n])
1837 1840 tr.close()
1838 1841 return n
1839 1842 finally:
1840 1843 if tr:
1841 1844 tr.release()
1842 1845 lock.release()
1843 1846
1844 1847 @unfilteredmethod
1845 1848 def destroying(self):
1846 1849 '''Inform the repository that nodes are about to be destroyed.
1847 1850 Intended for use by strip and rollback, so there's a common
1848 1851 place for anything that has to be done before destroying history.
1849 1852
1850 1853 This is mostly useful for saving state that is in memory and waiting
1851 1854 to be flushed when the current lock is released. Because a call to
1852 1855 destroyed is imminent, the repo will be invalidated causing those
1853 1856 changes to stay in memory (waiting for the next unlock), or vanish
1854 1857 completely.
1855 1858 '''
1856 1859 # When using the same lock to commit and strip, the phasecache is left
1857 1860 # dirty after committing. Then when we strip, the repo is invalidated,
1858 1861 # causing those changes to disappear.
1859 1862 if '_phasecache' in vars(self):
1860 1863 self._phasecache.write()
1861 1864
1862 1865 @unfilteredmethod
1863 1866 def destroyed(self):
1864 1867 '''Inform the repository that nodes have been destroyed.
1865 1868 Intended for use by strip and rollback, so there's a common
1866 1869 place for anything that has to be done after destroying history.
1867 1870 '''
1868 1871 # When one tries to:
1869 1872 # 1) destroy nodes thus calling this method (e.g. strip)
1870 1873 # 2) use phasecache somewhere (e.g. commit)
1871 1874 #
1872 1875 # then 2) will fail because the phasecache contains nodes that were
1873 1876 # removed. We can either remove phasecache from the filecache,
1874 1877 # causing it to reload next time it is accessed, or simply filter
1875 1878 # the removed nodes now and write the updated cache.
1876 1879 self._phasecache.filterunknown(self)
1877 1880 self._phasecache.write()
1878 1881
1879 1882 # refresh all repository caches
1880 1883 self.updatecaches()
1881 1884
1882 1885 # Ensure the persistent tag cache is updated. Doing it now
1883 1886 # means that the tag cache only has to worry about destroyed
1884 1887 # heads immediately after a strip/rollback. That in turn
1885 1888 # guarantees that "cachetip == currenttip" (comparing both rev
1886 1889 # and node) always means no nodes have been added or destroyed.
1887 1890
1888 1891 # XXX this is suboptimal when qrefresh'ing: we strip the current
1889 1892 # head, refresh the tag cache, then immediately add a new head.
1890 1893 # But I think doing it this way is necessary for the "instant
1891 1894 # tag cache retrieval" case to work.
1892 1895 self.invalidate()
1893 1896
1894 1897 def walk(self, match, node=None):
1895 1898 '''
1896 1899 walk recursively through the directory tree or a given
1897 1900 changeset, finding all files matched by the match
1898 1901 function
1899 1902 '''
1900 1903 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
1901 1904 return self[node].walk(match)
1902 1905
1903 1906 def status(self, node1='.', node2=None, match=None,
1904 1907 ignored=False, clean=False, unknown=False,
1905 1908 listsubrepos=False):
1906 1909 '''a convenience method that calls node1.status(node2)'''
1907 1910 return self[node1].status(node2, match, ignored, clean, unknown,
1908 1911 listsubrepos)
1909 1912
1910 1913 def addpostdsstatus(self, ps):
1911 1914 """Add a callback to run within the wlock, at the point at which status
1912 1915 fixups happen.
1913 1916
1914 1917 On status completion, callback(wctx, status) will be called with the
1915 1918 wlock held, unless the dirstate has changed from underneath or the wlock
1916 1919 couldn't be grabbed.
1917 1920
1918 1921 Callbacks should not capture and use a cached copy of the dirstate --
1919 1922 it might change in the meanwhile. Instead, they should access the
1920 1923 dirstate via wctx.repo().dirstate.
1921 1924
1922 1925 This list is emptied out after each status run -- extensions should
1923 1926 make sure it adds to this list each time dirstate.status is called.
1924 1927 Extensions should also make sure they don't call this for statuses
1925 1928 that don't involve the dirstate.
1926 1929 """
1927 1930
1928 1931 # The list is located here for uniqueness reasons -- it is actually
1929 1932 # managed by the workingctx, but that isn't unique per-repo.
1930 1933 self._postdsstatus.append(ps)
1931 1934
1932 1935 def postdsstatus(self):
1933 1936 """Used by workingctx to get the list of post-dirstate-status hooks."""
1934 1937 return self._postdsstatus
1935 1938
1936 1939 def clearpostdsstatus(self):
1937 1940 """Used by workingctx to clear post-dirstate-status hooks."""
1938 1941 del self._postdsstatus[:]
1939 1942
1940 1943 def heads(self, start=None):
1941 1944 if start is None:
1942 1945 cl = self.changelog
1943 1946 headrevs = reversed(cl.headrevs())
1944 1947 return [cl.node(rev) for rev in headrevs]
1945 1948
1946 1949 heads = self.changelog.heads(start)
1947 1950 # sort the output in rev descending order
1948 1951 return sorted(heads, key=self.changelog.rev, reverse=True)
1949 1952
1950 1953 def branchheads(self, branch=None, start=None, closed=False):
1951 1954 '''return a (possibly filtered) list of heads for the given branch
1952 1955
1953 1956 Heads are returned in topological order, from newest to oldest.
1954 1957 If branch is None, use the dirstate branch.
1955 1958 If start is not None, return only heads reachable from start.
1956 1959 If closed is True, return heads that are marked as closed as well.
1957 1960 '''
1958 1961 if branch is None:
1959 1962 branch = self[None].branch()
1960 1963 branches = self.branchmap()
1961 1964 if branch not in branches:
1962 1965 return []
1963 1966 # the cache returns heads ordered lowest to highest
1964 1967 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1965 1968 if start is not None:
1966 1969 # filter out the heads that cannot be reached from startrev
1967 1970 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1968 1971 bheads = [h for h in bheads if h in fbheads]
1969 1972 return bheads
1970 1973
1971 1974 def branches(self, nodes):
1972 1975 if not nodes:
1973 1976 nodes = [self.changelog.tip()]
1974 1977 b = []
1975 1978 for n in nodes:
1976 1979 t = n
1977 1980 while True:
1978 1981 p = self.changelog.parents(n)
1979 1982 if p[1] != nullid or p[0] == nullid:
1980 1983 b.append((t, n, p[0], p[1]))
1981 1984 break
1982 1985 n = p[0]
1983 1986 return b
1984 1987
1985 1988 def between(self, pairs):
1986 1989 r = []
1987 1990
1988 1991 for top, bottom in pairs:
1989 1992 n, l, i = top, [], 0
1990 1993 f = 1
1991 1994
1992 1995 while n != bottom and n != nullid:
1993 1996 p = self.changelog.parents(n)[0]
1994 1997 if i == f:
1995 1998 l.append(n)
1996 1999 f = f * 2
1997 2000 n = p
1998 2001 i += 1
1999 2002
2000 2003 r.append(l)
2001 2004
2002 2005 return r
2003 2006
2004 2007 def checkpush(self, pushop):
2005 2008 """Extensions can override this function if additional checks have
2006 2009 to be performed before pushing, or call it if they override push
2007 2010 command.
2008 2011 """
2009 2012 pass
2010 2013
2011 2014 @unfilteredpropertycache
2012 2015 def prepushoutgoinghooks(self):
2013 2016 """Return util.hooks consists of a pushop with repo, remote, outgoing
2014 2017 methods, which are called before pushing changesets.
2015 2018 """
2016 2019 return util.hooks()
2017 2020
2018 2021 def pushkey(self, namespace, key, old, new):
2019 2022 try:
2020 2023 tr = self.currenttransaction()
2021 2024 hookargs = {}
2022 2025 if tr is not None:
2023 2026 hookargs.update(tr.hookargs)
2024 2027 hookargs['namespace'] = namespace
2025 2028 hookargs['key'] = key
2026 2029 hookargs['old'] = old
2027 2030 hookargs['new'] = new
2028 2031 self.hook('prepushkey', throw=True, **hookargs)
2029 2032 except error.HookAbort as exc:
2030 2033 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2031 2034 if exc.hint:
2032 2035 self.ui.write_err(_("(%s)\n") % exc.hint)
2033 2036 return False
2034 2037 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2035 2038 ret = pushkey.push(self, namespace, key, old, new)
2036 2039 def runhook():
2037 2040 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2038 2041 ret=ret)
2039 2042 self._afterlock(runhook)
2040 2043 return ret
2041 2044
2042 2045 def listkeys(self, namespace):
2043 2046 self.hook('prelistkeys', throw=True, namespace=namespace)
2044 2047 self.ui.debug('listing keys for "%s"\n' % namespace)
2045 2048 values = pushkey.list(self, namespace)
2046 2049 self.hook('listkeys', namespace=namespace, values=values)
2047 2050 return values
2048 2051
2049 2052 def debugwireargs(self, one, two, three=None, four=None, five=None):
2050 2053 '''used to test argument passing over the wire'''
2051 2054 return "%s %s %s %s %s" % (one, two, three, four, five)
2052 2055
2053 2056 def savecommitmessage(self, text):
2054 2057 fp = self.vfs('last-message.txt', 'wb')
2055 2058 try:
2056 2059 fp.write(text)
2057 2060 finally:
2058 2061 fp.close()
2059 2062 return self.pathto(fp.name[len(self.root) + 1:])
2060 2063
2061 2064 # used to avoid circular references so destructors work
2062 2065 def aftertrans(files):
2063 2066 renamefiles = [tuple(t) for t in files]
2064 2067 def a():
2065 2068 for vfs, src, dest in renamefiles:
2066 2069 # if src and dest refer to a same file, vfs.rename is a no-op,
2067 2070 # leaving both src and dest on disk. delete dest to make sure
2068 2071 # the rename couldn't be such a no-op.
2069 2072 vfs.tryunlink(dest)
2070 2073 try:
2071 2074 vfs.rename(src, dest)
2072 2075 except OSError: # journal file does not yet exist
2073 2076 pass
2074 2077 return a
2075 2078
2076 2079 def undoname(fn):
2077 2080 base, name = os.path.split(fn)
2078 2081 assert name.startswith('journal')
2079 2082 return os.path.join(base, name.replace('journal', 'undo', 1))
2080 2083
2081 2084 def instance(ui, path, create):
2082 2085 return localrepository(ui, util.urllocalpath(path), create)
2083 2086
2084 2087 def islocal(path):
2085 2088 return True
2086 2089
2087 2090 def newreporequirements(repo):
2088 2091 """Determine the set of requirements for a new local repository.
2089 2092
2090 2093 Extensions can wrap this function to specify custom requirements for
2091 2094 new repositories.
2092 2095 """
2093 2096 ui = repo.ui
2094 2097 requirements = {'revlogv1'}
2095 2098 if ui.configbool('format', 'usestore'):
2096 2099 requirements.add('store')
2097 2100 if ui.configbool('format', 'usefncache'):
2098 2101 requirements.add('fncache')
2099 2102 if ui.configbool('format', 'dotencode'):
2100 2103 requirements.add('dotencode')
2101 2104
2102 2105 compengine = ui.config('experimental', 'format.compression', 'zlib')
2103 2106 if compengine not in util.compengines:
2104 2107 raise error.Abort(_('compression engine %s defined by '
2105 2108 'experimental.format.compression not available') %
2106 2109 compengine,
2107 2110 hint=_('run "hg debuginstall" to list available '
2108 2111 'compression engines'))
2109 2112
2110 2113 # zlib is the historical default and doesn't need an explicit requirement.
2111 2114 if compengine != 'zlib':
2112 2115 requirements.add('exp-compression-%s' % compengine)
2113 2116
2114 2117 if scmutil.gdinitconfig(ui):
2115 2118 requirements.add('generaldelta')
2116 2119 if ui.configbool('experimental', 'treemanifest', False):
2117 2120 requirements.add('treemanifest')
2118 2121 if ui.configbool('experimental', 'manifestv2', False):
2119 2122 requirements.add('manifestv2')
2120 2123
2121 2124 revlogv2 = ui.config('experimental', 'revlogv2')
2122 2125 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2123 2126 requirements.remove('revlogv1')
2124 2127 # generaldelta is implied by revlogv2.
2125 2128 requirements.discard('generaldelta')
2126 2129 requirements.add(REVLOGV2_REQUIREMENT)
2127 2130
2128 2131 return requirements
@@ -1,618 +1,634 b''
1 1 # transaction.py - simple journaling scheme for mercurial
2 2 #
3 3 # This transaction scheme is intended to gracefully handle program
4 4 # errors and interruptions. More serious failures like system crashes
5 5 # can be recovered with an fsck-like tool. As the whole repository is
6 6 # effectively log-structured, this should amount to simply truncating
7 7 # anything that isn't referenced in the changelog.
8 8 #
9 9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
10 10 #
11 11 # This software may be used and distributed according to the terms of the
12 12 # GNU General Public License version 2 or any later version.
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import errno
17 17
18 18 from .i18n import _
19 19 from . import (
20 20 error,
21 21 util,
22 22 )
23 23
24 24 version = 2
25 25
26 26 # These are the file generators that should only be executed after the
27 27 # finalizers are done, since they rely on the output of the finalizers (like
28 28 # the changelog having been written).
29 29 postfinalizegenerators = {
30 30 'bookmarks',
31 31 'dirstate'
32 32 }
33 33
34 34 gengroupall='all'
35 35 gengroupprefinalize='prefinalize'
36 36 gengrouppostfinalize='postfinalize'
37 37
38 38 def active(func):
39 39 def _active(self, *args, **kwds):
40 40 if self.count == 0:
41 41 raise error.Abort(_(
42 42 'cannot use transaction when it is already committed/aborted'))
43 43 return func(self, *args, **kwds)
44 44 return _active
45 45
46 46 def _playback(journal, report, opener, vfsmap, entries, backupentries,
47 unlink=True):
47 unlink=True, checkambigfiles=None):
48 48 for f, o, _ignore in entries:
49 49 if o or not unlink:
50 checkambig = checkambigfiles and (f, '') in checkambigfiles
50 51 try:
51 fp = opener(f, 'a', checkambig=True)
52 fp = opener(f, 'a', checkambig=checkambig)
52 53 fp.truncate(o)
53 54 fp.close()
54 55 except IOError:
55 56 report(_("failed to truncate %s\n") % f)
56 57 raise
57 58 else:
58 59 try:
59 60 opener.unlink(f)
60 61 except (IOError, OSError) as inst:
61 62 if inst.errno != errno.ENOENT:
62 63 raise
63 64
64 65 backupfiles = []
65 66 for l, f, b, c in backupentries:
66 67 if l not in vfsmap and c:
67 68 report("couldn't handle %s: unknown cache location %s\n"
68 69 % (b, l))
69 70 vfs = vfsmap[l]
70 71 try:
71 72 if f and b:
72 73 filepath = vfs.join(f)
73 74 backuppath = vfs.join(b)
74 75 try:
75 76 util.copyfile(backuppath, filepath, checkambig=True)
76 77 backupfiles.append(b)
77 78 except IOError:
78 79 report(_("failed to recover %s\n") % f)
79 80 else:
80 81 target = f or b
81 82 try:
82 83 vfs.unlink(target)
83 84 except (IOError, OSError) as inst:
84 85 if inst.errno != errno.ENOENT:
85 86 raise
86 87 except (IOError, OSError, error.Abort) as inst:
87 88 if not c:
88 89 raise
89 90
90 91 backuppath = "%s.backupfiles" % journal
91 92 if opener.exists(backuppath):
92 93 opener.unlink(backuppath)
93 94 opener.unlink(journal)
94 95 try:
95 96 for f in backupfiles:
96 97 if opener.exists(f):
97 98 opener.unlink(f)
98 99 except (IOError, OSError, error.Abort) as inst:
99 100 # only pure backup file remains, it is sage to ignore any error
100 101 pass
101 102
102 103 class transaction(object):
103 104 def __init__(self, report, opener, vfsmap, journalname, undoname=None,
104 after=None, createmode=None, validator=None, releasefn=None):
105 after=None, createmode=None, validator=None, releasefn=None,
106 checkambigfiles=None):
105 107 """Begin a new transaction
106 108
107 109 Begins a new transaction that allows rolling back writes in the event of
108 110 an exception.
109 111
110 112 * `after`: called after the transaction has been committed
111 113 * `createmode`: the mode of the journal file that will be created
112 114 * `releasefn`: called after releasing (with transaction and result)
115
116 `checkambigfiles` is a set of (path, vfs-location) tuples,
117 which determine whether file stat ambiguity should be avoided
118 for corresponded files.
113 119 """
114 120 self.count = 1
115 121 self.usages = 1
116 122 self.report = report
117 123 # a vfs to the store content
118 124 self.opener = opener
119 125 # a map to access file in various {location -> vfs}
120 126 vfsmap = vfsmap.copy()
121 127 vfsmap[''] = opener # set default value
122 128 self._vfsmap = vfsmap
123 129 self.after = after
124 130 self.entries = []
125 131 self.map = {}
126 132 self.journal = journalname
127 133 self.undoname = undoname
128 134 self._queue = []
129 135 # A callback to validate transaction content before closing it.
130 136 # should raise exception is anything is wrong.
131 137 # target user is repository hooks.
132 138 if validator is None:
133 139 validator = lambda tr: None
134 140 self.validator = validator
135 141 # A callback to do something just after releasing transaction.
136 142 if releasefn is None:
137 143 releasefn = lambda tr, success: None
138 144 self.releasefn = releasefn
139 145
146 self.checkambigfiles = set()
147 if checkambigfiles:
148 self.checkambigfiles.update(checkambigfiles)
149
140 150 # A dict dedicated to precisely tracking the changes introduced in the
141 151 # transaction.
142 152 self.changes = {}
143 153
144 154 # a dict of arguments to be passed to hooks
145 155 self.hookargs = {}
146 156 self.file = opener.open(self.journal, "w")
147 157
148 158 # a list of ('location', 'path', 'backuppath', cache) entries.
149 159 # - if 'backuppath' is empty, no file existed at backup time
150 160 # - if 'path' is empty, this is a temporary transaction file
151 161 # - if 'location' is not empty, the path is outside main opener reach.
152 162 # use 'location' value as a key in a vfsmap to find the right 'vfs'
153 163 # (cache is currently unused)
154 164 self._backupentries = []
155 165 self._backupmap = {}
156 166 self._backupjournal = "%s.backupfiles" % self.journal
157 167 self._backupsfile = opener.open(self._backupjournal, 'w')
158 168 self._backupsfile.write('%d\n' % version)
159 169
160 170 if createmode is not None:
161 171 opener.chmod(self.journal, createmode & 0o666)
162 172 opener.chmod(self._backupjournal, createmode & 0o666)
163 173
164 174 # hold file generations to be performed on commit
165 175 self._filegenerators = {}
166 176 # hold callback to write pending data for hooks
167 177 self._pendingcallback = {}
168 178 # True is any pending data have been written ever
169 179 self._anypending = False
170 180 # holds callback to call when writing the transaction
171 181 self._finalizecallback = {}
172 182 # hold callback for post transaction close
173 183 self._postclosecallback = {}
174 184 # holds callbacks to call during abort
175 185 self._abortcallback = {}
176 186
177 187 def __del__(self):
178 188 if self.journal:
179 189 self._abort()
180 190
181 191 @active
182 192 def startgroup(self):
183 193 """delay registration of file entry
184 194
185 195 This is used by strip to delay vision of strip offset. The transaction
186 196 sees either none or all of the strip actions to be done."""
187 197 self._queue.append([])
188 198
189 199 @active
190 200 def endgroup(self):
191 201 """apply delayed registration of file entry.
192 202
193 203 This is used by strip to delay vision of strip offset. The transaction
194 204 sees either none or all of the strip actions to be done."""
195 205 q = self._queue.pop()
196 206 for f, o, data in q:
197 207 self._addentry(f, o, data)
198 208
199 209 @active
200 210 def add(self, file, offset, data=None):
201 211 """record the state of an append-only file before update"""
202 212 if file in self.map or file in self._backupmap:
203 213 return
204 214 if self._queue:
205 215 self._queue[-1].append((file, offset, data))
206 216 return
207 217
208 218 self._addentry(file, offset, data)
209 219
210 220 def _addentry(self, file, offset, data):
211 221 """add a append-only entry to memory and on-disk state"""
212 222 if file in self.map or file in self._backupmap:
213 223 return
214 224 self.entries.append((file, offset, data))
215 225 self.map[file] = len(self.entries) - 1
216 226 # add enough data to the journal to do the truncate
217 227 self.file.write("%s\0%d\n" % (file, offset))
218 228 self.file.flush()
219 229
220 230 @active
221 231 def addbackup(self, file, hardlink=True, location=''):
222 232 """Adds a backup of the file to the transaction
223 233
224 234 Calling addbackup() creates a hardlink backup of the specified file
225 235 that is used to recover the file in the event of the transaction
226 236 aborting.
227 237
228 238 * `file`: the file path, relative to .hg/store
229 239 * `hardlink`: use a hardlink to quickly create the backup
230 240 """
231 241 if self._queue:
232 242 msg = 'cannot use transaction.addbackup inside "group"'
233 243 raise error.ProgrammingError(msg)
234 244
235 245 if file in self.map or file in self._backupmap:
236 246 return
237 247 vfs = self._vfsmap[location]
238 248 dirname, filename = vfs.split(file)
239 249 backupfilename = "%s.backup.%s" % (self.journal, filename)
240 250 backupfile = vfs.reljoin(dirname, backupfilename)
241 251 if vfs.exists(file):
242 252 filepath = vfs.join(file)
243 253 backuppath = vfs.join(backupfile)
244 254 util.copyfile(filepath, backuppath, hardlink=hardlink)
245 255 else:
246 256 backupfile = ''
247 257
248 258 self._addbackupentry((location, file, backupfile, False))
249 259
250 260 def _addbackupentry(self, entry):
251 261 """register a new backup entry and write it to disk"""
252 262 self._backupentries.append(entry)
253 263 self._backupmap[entry[1]] = len(self._backupentries) - 1
254 264 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
255 265 self._backupsfile.flush()
256 266
257 267 @active
258 268 def registertmp(self, tmpfile, location=''):
259 269 """register a temporary transaction file
260 270
261 271 Such files will be deleted when the transaction exits (on both
262 272 failure and success).
263 273 """
264 274 self._addbackupentry((location, '', tmpfile, False))
265 275
266 276 @active
267 277 def addfilegenerator(self, genid, filenames, genfunc, order=0,
268 278 location=''):
269 279 """add a function to generates some files at transaction commit
270 280
271 281 The `genfunc` argument is a function capable of generating proper
272 282 content of each entry in the `filename` tuple.
273 283
274 284 At transaction close time, `genfunc` will be called with one file
275 285 object argument per entries in `filenames`.
276 286
277 287 The transaction itself is responsible for the backup, creation and
278 288 final write of such file.
279 289
280 290 The `genid` argument is used to ensure the same set of file is only
281 291 generated once. Call to `addfilegenerator` for a `genid` already
282 292 present will overwrite the old entry.
283 293
284 294 The `order` argument may be used to control the order in which multiple
285 295 generator will be executed.
286 296
287 297 The `location` arguments may be used to indicate the files are located
288 298 outside of the the standard directory for transaction. It should match
289 299 one of the key of the `transaction.vfsmap` dictionary.
290 300 """
291 301 # For now, we are unable to do proper backup and restore of custom vfs
292 302 # but for bookmarks that are handled outside this mechanism.
293 303 self._filegenerators[genid] = (order, filenames, genfunc, location)
294 304
295 305 @active
296 306 def removefilegenerator(self, genid):
297 307 """reverse of addfilegenerator, remove a file generator function"""
298 308 if genid in self._filegenerators:
299 309 del self._filegenerators[genid]
300 310
301 311 def _generatefiles(self, suffix='', group=gengroupall):
302 312 # write files registered for generation
303 313 any = False
304 314 for id, entry in sorted(self._filegenerators.iteritems()):
305 315 any = True
306 316 order, filenames, genfunc, location = entry
307 317
308 318 # for generation at closing, check if it's before or after finalize
309 319 postfinalize = group == gengrouppostfinalize
310 320 if (group != gengroupall and
311 321 (id in postfinalizegenerators) != (postfinalize)):
312 322 continue
313 323
314 324 vfs = self._vfsmap[location]
315 325 files = []
316 326 try:
317 327 for name in filenames:
318 328 name += suffix
319 329 if suffix:
320 330 self.registertmp(name, location=location)
321 331 else:
322 332 self.addbackup(name, location=location)
323 333 files.append(vfs(name, 'w', atomictemp=True,
324 334 checkambig=not suffix))
325 335 genfunc(*files)
326 336 finally:
327 337 for f in files:
328 338 f.close()
329 339 return any
330 340
331 341 @active
332 342 def find(self, file):
333 343 if file in self.map:
334 344 return self.entries[self.map[file]]
335 345 if file in self._backupmap:
336 346 return self._backupentries[self._backupmap[file]]
337 347 return None
338 348
339 349 @active
340 350 def replace(self, file, offset, data=None):
341 351 '''
342 352 replace can only replace already committed entries
343 353 that are not pending in the queue
344 354 '''
345 355
346 356 if file not in self.map:
347 357 raise KeyError(file)
348 358 index = self.map[file]
349 359 self.entries[index] = (file, offset, data)
350 360 self.file.write("%s\0%d\n" % (file, offset))
351 361 self.file.flush()
352 362
353 363 @active
354 364 def nest(self):
355 365 self.count += 1
356 366 self.usages += 1
357 367 return self
358 368
359 369 def release(self):
360 370 if self.count > 0:
361 371 self.usages -= 1
362 372 # if the transaction scopes are left without being closed, fail
363 373 if self.count > 0 and self.usages == 0:
364 374 self._abort()
365 375
366 376 def __enter__(self):
367 377 return self
368 378
369 379 def __exit__(self, exc_type, exc_val, exc_tb):
370 380 try:
371 381 if exc_type is None:
372 382 self.close()
373 383 finally:
374 384 self.release()
375 385
376 386 def running(self):
377 387 return self.count > 0
378 388
379 389 def addpending(self, category, callback):
380 390 """add a callback to be called when the transaction is pending
381 391
382 392 The transaction will be given as callback's first argument.
383 393
384 394 Category is a unique identifier to allow overwriting an old callback
385 395 with a newer callback.
386 396 """
387 397 self._pendingcallback[category] = callback
388 398
389 399 @active
390 400 def writepending(self):
391 401 '''write pending file to temporary version
392 402
393 403 This is used to allow hooks to view a transaction before commit'''
394 404 categories = sorted(self._pendingcallback)
395 405 for cat in categories:
396 406 # remove callback since the data will have been flushed
397 407 any = self._pendingcallback.pop(cat)(self)
398 408 self._anypending = self._anypending or any
399 409 self._anypending |= self._generatefiles(suffix='.pending')
400 410 return self._anypending
401 411
402 412 @active
403 413 def addfinalize(self, category, callback):
404 414 """add a callback to be called when the transaction is closed
405 415
406 416 The transaction will be given as callback's first argument.
407 417
408 418 Category is a unique identifier to allow overwriting old callbacks with
409 419 newer callbacks.
410 420 """
411 421 self._finalizecallback[category] = callback
412 422
413 423 @active
414 424 def addpostclose(self, category, callback):
415 425 """add or replace a callback to be called after the transaction closed
416 426
417 427 The transaction will be given as callback's first argument.
418 428
419 429 Category is a unique identifier to allow overwriting an old callback
420 430 with a newer callback.
421 431 """
422 432 self._postclosecallback[category] = callback
423 433
424 434 @active
425 435 def getpostclose(self, category):
426 436 """return a postclose callback added before, or None"""
427 437 return self._postclosecallback.get(category, None)
428 438
429 439 @active
430 440 def addabort(self, category, callback):
431 441 """add a callback to be called when the transaction is aborted.
432 442
433 443 The transaction will be given as the first argument to the callback.
434 444
435 445 Category is a unique identifier to allow overwriting an old callback
436 446 with a newer callback.
437 447 """
438 448 self._abortcallback[category] = callback
439 449
440 450 @active
441 451 def close(self):
442 452 '''commit the transaction'''
443 453 if self.count == 1:
444 454 self.validator(self) # will raise exception if needed
445 455 self.validator = None # Help prevent cycles.
446 456 self._generatefiles(group=gengroupprefinalize)
447 457 categories = sorted(self._finalizecallback)
448 458 for cat in categories:
449 459 self._finalizecallback[cat](self)
450 460 # Prevent double usage and help clear cycles.
451 461 self._finalizecallback = None
452 462 self._generatefiles(group=gengrouppostfinalize)
453 463
454 464 self.count -= 1
455 465 if self.count != 0:
456 466 return
457 467 self.file.close()
458 468 self._backupsfile.close()
459 469 # cleanup temporary files
460 470 for l, f, b, c in self._backupentries:
461 471 if l not in self._vfsmap and c:
462 472 self.report("couldn't remove %s: unknown cache location %s\n"
463 473 % (b, l))
464 474 continue
465 475 vfs = self._vfsmap[l]
466 476 if not f and b and vfs.exists(b):
467 477 try:
468 478 vfs.unlink(b)
469 479 except (IOError, OSError, error.Abort) as inst:
470 480 if not c:
471 481 raise
472 482 # Abort may be raise by read only opener
473 483 self.report("couldn't remove %s: %s\n"
474 484 % (vfs.join(b), inst))
475 485 self.entries = []
476 486 self._writeundo()
477 487 if self.after:
478 488 self.after()
479 489 self.after = None # Help prevent cycles.
480 490 if self.opener.isfile(self._backupjournal):
481 491 self.opener.unlink(self._backupjournal)
482 492 if self.opener.isfile(self.journal):
483 493 self.opener.unlink(self.journal)
484 494 for l, _f, b, c in self._backupentries:
485 495 if l not in self._vfsmap and c:
486 496 self.report("couldn't remove %s: unknown cache location"
487 497 "%s\n" % (b, l))
488 498 continue
489 499 vfs = self._vfsmap[l]
490 500 if b and vfs.exists(b):
491 501 try:
492 502 vfs.unlink(b)
493 503 except (IOError, OSError, error.Abort) as inst:
494 504 if not c:
495 505 raise
496 506 # Abort may be raise by read only opener
497 507 self.report("couldn't remove %s: %s\n"
498 508 % (vfs.join(b), inst))
499 509 self._backupentries = []
500 510 self.journal = None
501 511
502 512 self.releasefn(self, True) # notify success of closing transaction
503 513 self.releasefn = None # Help prevent cycles.
504 514
505 515 # run post close action
506 516 categories = sorted(self._postclosecallback)
507 517 for cat in categories:
508 518 self._postclosecallback[cat](self)
509 519 # Prevent double usage and help clear cycles.
510 520 self._postclosecallback = None
511 521
512 522 @active
513 523 def abort(self):
514 524 '''abort the transaction (generally called on error, or when the
515 525 transaction is not explicitly committed before going out of
516 526 scope)'''
517 527 self._abort()
518 528
519 529 def _writeundo(self):
520 530 """write transaction data for possible future undo call"""
521 531 if self.undoname is None:
522 532 return
523 533 undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w')
524 534 undobackupfile.write('%d\n' % version)
525 535 for l, f, b, c in self._backupentries:
526 536 if not f: # temporary file
527 537 continue
528 538 if not b:
529 539 u = ''
530 540 else:
531 541 if l not in self._vfsmap and c:
532 542 self.report("couldn't remove %s: unknown cache location"
533 543 "%s\n" % (b, l))
534 544 continue
535 545 vfs = self._vfsmap[l]
536 546 base, name = vfs.split(b)
537 547 assert name.startswith(self.journal), name
538 548 uname = name.replace(self.journal, self.undoname, 1)
539 549 u = vfs.reljoin(base, uname)
540 550 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
541 551 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
542 552 undobackupfile.close()
543 553
544 554
545 555 def _abort(self):
546 556 self.count = 0
547 557 self.usages = 0
548 558 self.file.close()
549 559 self._backupsfile.close()
550 560
551 561 try:
552 562 if not self.entries and not self._backupentries:
553 563 if self._backupjournal:
554 564 self.opener.unlink(self._backupjournal)
555 565 if self.journal:
556 566 self.opener.unlink(self.journal)
557 567 return
558 568
559 569 self.report(_("transaction abort!\n"))
560 570
561 571 try:
562 572 for cat in sorted(self._abortcallback):
563 573 self._abortcallback[cat](self)
564 574 # Prevent double usage and help clear cycles.
565 575 self._abortcallback = None
566 576 _playback(self.journal, self.report, self.opener, self._vfsmap,
567 self.entries, self._backupentries, False)
577 self.entries, self._backupentries, False,
578 checkambigfiles=self.checkambigfiles)
568 579 self.report(_("rollback completed\n"))
569 580 except BaseException:
570 581 self.report(_("rollback failed - please run hg recover\n"))
571 582 finally:
572 583 self.journal = None
573 584 self.releasefn(self, False) # notify failure of transaction
574 585 self.releasefn = None # Help prevent cycles.
575 586
576 def rollback(opener, vfsmap, file, report):
587 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
577 588 """Rolls back the transaction contained in the given file
578 589
579 590 Reads the entries in the specified file, and the corresponding
580 591 '*.backupfiles' file, to recover from an incomplete transaction.
581 592
582 593 * `file`: a file containing a list of entries, specifying where
583 594 to truncate each file. The file should contain a list of
584 595 file\0offset pairs, delimited by newlines. The corresponding
585 596 '*.backupfiles' file should contain a list of file\0backupfile
586 597 pairs, delimited by \0.
598
599 `checkambigfiles` is a set of (path, vfs-location) tuples,
600 which determine whether file stat ambiguity should be avoided at
601 restoring corresponded files.
587 602 """
588 603 entries = []
589 604 backupentries = []
590 605
591 606 fp = opener.open(file)
592 607 lines = fp.readlines()
593 608 fp.close()
594 609 for l in lines:
595 610 try:
596 611 f, o = l.split('\0')
597 612 entries.append((f, int(o), None))
598 613 except ValueError:
599 614 report(_("couldn't read journal entry %r!\n") % l)
600 615
601 616 backupjournal = "%s.backupfiles" % file
602 617 if opener.exists(backupjournal):
603 618 fp = opener.open(backupjournal)
604 619 lines = fp.readlines()
605 620 if lines:
606 621 ver = lines[0][:-1]
607 622 if ver == str(version):
608 623 for line in lines[1:]:
609 624 if line:
610 625 # Shave off the trailing newline
611 626 line = line[:-1]
612 627 l, f, b, c = line.split('\0')
613 628 backupentries.append((l, f, b, bool(c)))
614 629 else:
615 630 report(_("journal was created by a different version of "
616 631 "Mercurial\n"))
617 632
618 _playback(file, report, opener, vfsmap, entries, backupentries)
633 _playback(file, report, opener, vfsmap, entries, backupentries,
634 checkambigfiles=checkambigfiles)
@@ -1,470 +1,471 b''
1 1 Test that qpush cleans things up if it doesn't complete
2 2
3 3 $ echo "[extensions]" >> $HGRCPATH
4 4 $ echo "mq=" >> $HGRCPATH
5 5 $ hg init repo
6 6 $ cd repo
7 7 $ echo foo > foo
8 8 $ hg ci -Am 'add foo'
9 9 adding foo
10 10 $ touch untracked-file
11 11 $ echo 'syntax: glob' > .hgignore
12 12 $ echo '.hgignore' >> .hgignore
13 13 $ hg qinit
14 14
15 15 test qpush on empty series
16 16
17 17 $ hg qpush
18 18 no patches in series
19 19 $ hg qnew patch1
20 20 $ echo >> foo
21 21 $ hg qrefresh -m 'patch 1'
22 22 $ hg qnew patch2
23 23 $ echo bar > bar
24 24 $ hg add bar
25 25 $ hg qrefresh -m 'patch 2'
26 26 $ hg qnew --config 'mq.plain=true' -U bad-patch
27 27 $ echo >> foo
28 28 $ hg qrefresh
29 29 $ hg qpop -a
30 30 popping bad-patch
31 31 popping patch2
32 32 popping patch1
33 33 patch queue now empty
34 34 $ $PYTHON -c 'print "\xe9"' > message
35 35 $ cat .hg/patches/bad-patch >> message
36 36 $ mv message .hg/patches/bad-patch
37 37 $ cat > $TESTTMP/wrapplayback.py <<EOF
38 38 > import os
39 39 > from mercurial import extensions, transaction
40 40 > def wrapplayback(orig,
41 41 > journal, report, opener, vfsmap, entries, backupentries,
42 > unlink=True):
43 > orig(journal, report, opener, vfsmap, entries, backupentries, unlink)
42 > unlink=True, checkambigfiles=None):
43 > orig(journal, report, opener, vfsmap, entries, backupentries, unlink,
44 > checkambigfiles)
44 45 > # Touching files truncated at "transaction.abort" causes
45 46 > # forcible re-loading invalidated filecache properties
46 47 > # (including repo.changelog)
47 48 > for f, o, _ignore in entries:
48 49 > if o or not unlink:
49 50 > os.utime(opener.join(f), (0.0, 0.0))
50 51 > def extsetup(ui):
51 52 > extensions.wrapfunction(transaction, '_playback', wrapplayback)
52 53 > EOF
53 54 $ hg qpush -a --config extensions.wrapplayback=$TESTTMP/wrapplayback.py && echo 'qpush succeeded?!'
54 55 applying patch1
55 56 applying patch2
56 57 applying bad-patch
57 58 transaction abort!
58 59 rollback completed
59 60 cleaning up working directory...
60 61 reverting foo
61 62 done
62 63 abort: decoding near '\xe9': 'ascii' codec can't decode byte 0xe9 in position 0: ordinal not in range(128)! (esc)
63 64 [255]
64 65 $ hg parents
65 66 changeset: 0:bbd179dfa0a7
66 67 tag: tip
67 68 user: test
68 69 date: Thu Jan 01 00:00:00 1970 +0000
69 70 summary: add foo
70 71
71 72
72 73 test corrupt status file
73 74 $ hg qpush
74 75 applying patch1
75 76 now at: patch1
76 77 $ cp .hg/patches/status .hg/patches/status.orig
77 78 $ hg qpop
78 79 popping patch1
79 80 patch queue now empty
80 81 $ cp .hg/patches/status.orig .hg/patches/status
81 82 $ hg qpush
82 83 abort: working directory revision is not qtip
83 84 [255]
84 85 $ rm .hg/patches/status .hg/patches/status.orig
85 86
86 87
87 88 bar should be gone; other unknown/ignored files should still be around
88 89
89 90 $ hg status -A
90 91 ? untracked-file
91 92 I .hgignore
92 93 C foo
93 94
94 95 preparing qpush of a missing patch
95 96
96 97 $ hg qpop -a
97 98 no patches applied
98 99 $ hg qpush
99 100 applying patch1
100 101 now at: patch1
101 102 $ rm .hg/patches/patch2
102 103
103 104 now we expect the push to fail, but it should NOT complain about patch1
104 105
105 106 $ hg qpush
106 107 applying patch2
107 108 unable to read patch2
108 109 now at: patch1
109 110 [1]
110 111
111 112 preparing qpush of missing patch with no patch applied
112 113
113 114 $ hg qpop -a
114 115 popping patch1
115 116 patch queue now empty
116 117 $ rm .hg/patches/patch1
117 118
118 119 qpush should fail the same way as below
119 120
120 121 $ hg qpush
121 122 applying patch1
122 123 unable to read patch1
123 124 [1]
124 125
125 126 Test qpush to a patch below the currently applied patch.
126 127
127 128 $ hg qq -c guardedseriesorder
128 129 $ hg qnew a
129 130 $ hg qguard +block
130 131 $ hg qnew b
131 132 $ hg qnew c
132 133
133 134 $ hg qpop -a
134 135 popping c
135 136 popping b
136 137 popping a
137 138 patch queue now empty
138 139
139 140 try to push and pop while a is guarded
140 141
141 142 $ hg qpush a
142 143 cannot push 'a' - guarded by '+block'
143 144 [1]
144 145 $ hg qpush -a
145 146 applying b
146 147 patch b is empty
147 148 applying c
148 149 patch c is empty
149 150 now at: c
150 151
151 152 now try it when a is unguarded, and we're at the top of the queue
152 153
153 154 $ hg qapplied -v
154 155 0 G a
155 156 1 A b
156 157 2 A c
157 158 $ hg qsel block
158 159 $ hg qpush b
159 160 abort: cannot push to a previous patch: b
160 161 [255]
161 162 $ hg qpush a
162 163 abort: cannot push to a previous patch: a
163 164 [255]
164 165
165 166 and now we try it one more time with a unguarded, while we're not at the top of the queue
166 167
167 168 $ hg qpop b
168 169 popping c
169 170 now at: b
170 171 $ hg qpush a
171 172 abort: cannot push to a previous patch: a
172 173 [255]
173 174
174 175 test qpop --force and backup files
175 176
176 177 $ hg qpop -a
177 178 popping b
178 179 patch queue now empty
179 180 $ hg qq --create force
180 181 $ echo a > a
181 182 $ echo b > b
182 183 $ echo c > c
183 184 $ hg ci -Am add a b c
184 185 $ echo a >> a
185 186 $ hg rm b
186 187 $ hg rm c
187 188 $ hg qnew p1
188 189 $ echo a >> a
189 190 $ echo bb > b
190 191 $ hg add b
191 192 $ echo cc > c
192 193 $ hg add c
193 194 $ hg qpop --force --verbose
194 195 saving current version of a as a.orig
195 196 saving current version of b as b.orig
196 197 saving current version of c as c.orig
197 198 popping p1
198 199 patch queue now empty
199 200 $ hg st
200 201 ? a.orig
201 202 ? b.orig
202 203 ? c.orig
203 204 ? untracked-file
204 205 $ cat a.orig
205 206 a
206 207 a
207 208 a
208 209 $ cat b.orig
209 210 bb
210 211 $ cat c.orig
211 212 cc
212 213
213 214 test qpop --force --no-backup
214 215
215 216 $ hg qpush
216 217 applying p1
217 218 now at: p1
218 219 $ rm a.orig
219 220 $ echo a >> a
220 221 $ hg qpop --force --no-backup --verbose
221 222 popping p1
222 223 patch queue now empty
223 224 $ test -f a.orig && echo 'error: backup with --no-backup'
224 225 [1]
225 226
226 227 test qpop --keep-changes
227 228
228 229 $ hg qpush
229 230 applying p1
230 231 now at: p1
231 232 $ hg qpop --keep-changes --force
232 233 abort: cannot use both --force and --keep-changes
233 234 [255]
234 235 $ echo a >> a
235 236 $ hg qpop --keep-changes
236 237 abort: local changes found, qrefresh first
237 238 [255]
238 239 $ hg revert -qa a
239 240 $ rm a
240 241 $ hg qpop --keep-changes
241 242 abort: local changes found, qrefresh first
242 243 [255]
243 244 $ hg rm -A a
244 245 $ hg qpop --keep-changes
245 246 abort: local changes found, qrefresh first
246 247 [255]
247 248 $ hg revert -qa a
248 249 $ echo b > b
249 250 $ hg add b
250 251 $ hg qpop --keep-changes
251 252 abort: local changes found, qrefresh first
252 253 [255]
253 254 $ hg forget b
254 255 $ echo d > d
255 256 $ hg add d
256 257 $ hg qpop --keep-changes
257 258 popping p1
258 259 patch queue now empty
259 260 $ hg forget d
260 261 $ rm d
261 262
262 263 test qpush --force and backup files
263 264
264 265 $ echo a >> a
265 266 $ hg qnew p2
266 267 $ echo b >> b
267 268 $ echo d > d
268 269 $ echo e > e
269 270 $ hg add d e
270 271 $ hg rm c
271 272 $ hg qnew p3
272 273 $ hg qpop -a
273 274 popping p3
274 275 popping p2
275 276 patch queue now empty
276 277 $ echo a >> a
277 278 $ echo b1 >> b
278 279 $ echo d1 > d
279 280 $ hg add d
280 281 $ echo e1 > e
281 282 $ hg qpush -a --force --verbose
282 283 applying p2
283 284 saving current version of a as a.orig
284 285 patching file a
285 286 committing files:
286 287 a
287 288 committing manifest
288 289 committing changelog
289 290 applying p3
290 291 saving current version of b as b.orig
291 292 saving current version of d as d.orig
292 293 patching file b
293 294 patching file c
294 295 patching file d
295 296 file d already exists
296 297 1 out of 1 hunks FAILED -- saving rejects to file d.rej
297 298 patching file e
298 299 file e already exists
299 300 1 out of 1 hunks FAILED -- saving rejects to file e.rej
300 301 patch failed to apply
301 302 committing files:
302 303 b
303 304 committing manifest
304 305 committing changelog
305 306 patch failed, rejects left in working directory
306 307 errors during apply, please fix and qrefresh p3
307 308 [2]
308 309 $ cat a.orig
309 310 a
310 311 a
311 312 $ cat b.orig
312 313 b
313 314 b1
314 315 $ cat d.orig
315 316 d1
316 317
317 318 test qpush --force --no-backup
318 319
319 320 $ hg revert -qa
320 321 $ hg qpop -a
321 322 popping p3
322 323 popping p2
323 324 patch queue now empty
324 325 $ echo a >> a
325 326 $ rm a.orig
326 327 $ hg qpush --force --no-backup --verbose
327 328 applying p2
328 329 patching file a
329 330 committing files:
330 331 a
331 332 committing manifest
332 333 committing changelog
333 334 now at: p2
334 335 $ test -f a.orig && echo 'error: backup with --no-backup'
335 336 [1]
336 337
337 338 test qgoto --force --no-backup
338 339
339 340 $ hg qpop
340 341 popping p2
341 342 patch queue now empty
342 343 $ echo a >> a
343 344 $ hg qgoto --force --no-backup p2 --verbose
344 345 applying p2
345 346 patching file a
346 347 committing files:
347 348 a
348 349 committing manifest
349 350 committing changelog
350 351 now at: p2
351 352 $ test -f a.orig && echo 'error: backup with --no-backup'
352 353 [1]
353 354
354 355 test qpush --keep-changes
355 356
356 357 $ hg qpush --keep-changes --force
357 358 abort: cannot use both --force and --keep-changes
358 359 [255]
359 360 $ hg qpush --keep-changes --exact
360 361 abort: cannot use --exact and --keep-changes together
361 362 [255]
362 363 $ echo b >> b
363 364 $ hg qpush --keep-changes
364 365 applying p3
365 366 abort: conflicting local changes found
366 367 (did you forget to qrefresh?)
367 368 [255]
368 369 $ rm b
369 370 $ hg qpush --keep-changes
370 371 applying p3
371 372 abort: conflicting local changes found
372 373 (did you forget to qrefresh?)
373 374 [255]
374 375 $ hg rm -A b
375 376 $ hg qpush --keep-changes
376 377 applying p3
377 378 abort: conflicting local changes found
378 379 (did you forget to qrefresh?)
379 380 [255]
380 381 $ hg revert -aq b
381 382 $ echo d > d
382 383 $ hg add d
383 384 $ hg qpush --keep-changes
384 385 applying p3
385 386 abort: conflicting local changes found
386 387 (did you forget to qrefresh?)
387 388 [255]
388 389 $ hg forget d
389 390 $ rm d
390 391 $ hg qpop
391 392 popping p2
392 393 patch queue now empty
393 394 $ echo b >> b
394 395 $ hg qpush -a --keep-changes
395 396 applying p2
396 397 applying p3
397 398 abort: conflicting local changes found
398 399 (did you forget to qrefresh?)
399 400 [255]
400 401 $ hg qtop
401 402 p2
402 403 $ hg parents --template "{rev} {desc}\n"
403 404 2 imported patch p2
404 405 $ hg st b
405 406 M b
406 407 $ cat b
407 408 b
408 409 b
409 410
410 411 test qgoto --keep-changes
411 412
412 413 $ hg revert -aq b
413 414 $ rm e
414 415 $ hg qgoto --keep-changes --force p3
415 416 abort: cannot use both --force and --keep-changes
416 417 [255]
417 418 $ echo a >> a
418 419 $ hg qgoto --keep-changes p3
419 420 applying p3
420 421 now at: p3
421 422 $ hg st a
422 423 M a
423 424 $ hg qgoto --keep-changes p2
424 425 popping p3
425 426 now at: p2
426 427 $ hg st a
427 428 M a
428 429
429 430 test mq.keepchanges setting
430 431
431 432 $ hg --config mq.keepchanges=1 qpush
432 433 applying p3
433 434 now at: p3
434 435 $ hg st a
435 436 M a
436 437 $ hg --config mq.keepchanges=1 qpop
437 438 popping p3
438 439 now at: p2
439 440 $ hg st a
440 441 M a
441 442 $ hg --config mq.keepchanges=1 qgoto p3
442 443 applying p3
443 444 now at: p3
444 445 $ hg st a
445 446 M a
446 447 $ echo b >> b
447 448 $ hg --config mq.keepchanges=1 qpop --force --config 'ui.origbackuppath=.hg/origbackups'
448 449 popping p3
449 450 now at: p2
450 451 $ hg st b
451 452 $ hg --config mq.keepchanges=1 qpush --exact
452 453 abort: local changes found, qrefresh first
453 454 [255]
454 455 $ hg revert -qa a
455 456 $ hg qpop
456 457 popping p2
457 458 patch queue now empty
458 459 $ echo a >> a
459 460 $ hg --config mq.keepchanges=1 qpush --force
460 461 applying p2
461 462 now at: p2
462 463 $ hg st a
463 464
464 465 test previous qpop (with --force and --config) saved .orig files to where user
465 466 wants them
466 467 $ ls .hg/origbackups
467 468 b.orig
468 469 $ rm -rf .hg/origbackups
469 470
470 471 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now