##// END OF EJS Templates
cache: create `wcache` directory at init time...
Boris Feld -
r40825:d5622dfe default
parent child Browse files
Show More
@@ -1,3064 +1,3065 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 os
13 13 import random
14 14 import sys
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 bin,
21 21 hex,
22 22 nullid,
23 23 nullrev,
24 24 short,
25 25 )
26 26 from . import (
27 27 bookmarks,
28 28 branchmap,
29 29 bundle2,
30 30 changegroup,
31 31 changelog,
32 32 color,
33 33 context,
34 34 dirstate,
35 35 dirstateguard,
36 36 discovery,
37 37 encoding,
38 38 error,
39 39 exchange,
40 40 extensions,
41 41 filelog,
42 42 hook,
43 43 lock as lockmod,
44 44 manifest,
45 45 match as matchmod,
46 46 merge as mergemod,
47 47 mergeutil,
48 48 namespaces,
49 49 narrowspec,
50 50 obsolete,
51 51 pathutil,
52 52 phases,
53 53 pushkey,
54 54 pycompat,
55 55 repository,
56 56 repoview,
57 57 revset,
58 58 revsetlang,
59 59 scmutil,
60 60 sparse,
61 61 store as storemod,
62 62 subrepoutil,
63 63 tags as tagsmod,
64 64 transaction,
65 65 txnutil,
66 66 util,
67 67 vfs as vfsmod,
68 68 )
69 69 from .utils import (
70 70 interfaceutil,
71 71 procutil,
72 72 stringutil,
73 73 )
74 74
75 75 from .revlogutils import (
76 76 constants as revlogconst,
77 77 )
78 78
79 79 release = lockmod.release
80 80 urlerr = util.urlerr
81 81 urlreq = util.urlreq
82 82
83 83 # set of (path, vfs-location) tuples. vfs-location is:
84 84 # - 'plain for vfs relative paths
85 85 # - '' for svfs relative paths
86 86 _cachedfiles = set()
87 87
88 88 class _basefilecache(scmutil.filecache):
89 89 """All filecache usage on repo are done for logic that should be unfiltered
90 90 """
91 91 def __get__(self, repo, type=None):
92 92 if repo is None:
93 93 return self
94 94 # proxy to unfiltered __dict__ since filtered repo has no entry
95 95 unfi = repo.unfiltered()
96 96 try:
97 97 return unfi.__dict__[self.sname]
98 98 except KeyError:
99 99 pass
100 100 return super(_basefilecache, self).__get__(unfi, type)
101 101
102 102 def set(self, repo, value):
103 103 return super(_basefilecache, self).set(repo.unfiltered(), value)
104 104
105 105 class repofilecache(_basefilecache):
106 106 """filecache for files in .hg but outside of .hg/store"""
107 107 def __init__(self, *paths):
108 108 super(repofilecache, self).__init__(*paths)
109 109 for path in paths:
110 110 _cachedfiles.add((path, 'plain'))
111 111
112 112 def join(self, obj, fname):
113 113 return obj.vfs.join(fname)
114 114
115 115 class storecache(_basefilecache):
116 116 """filecache for files in the store"""
117 117 def __init__(self, *paths):
118 118 super(storecache, self).__init__(*paths)
119 119 for path in paths:
120 120 _cachedfiles.add((path, ''))
121 121
122 122 def join(self, obj, fname):
123 123 return obj.sjoin(fname)
124 124
125 125 def isfilecached(repo, name):
126 126 """check if a repo has already cached "name" filecache-ed property
127 127
128 128 This returns (cachedobj-or-None, iscached) tuple.
129 129 """
130 130 cacheentry = repo.unfiltered()._filecache.get(name, None)
131 131 if not cacheentry:
132 132 return None, False
133 133 return cacheentry.obj, True
134 134
135 135 class unfilteredpropertycache(util.propertycache):
136 136 """propertycache that apply to unfiltered repo only"""
137 137
138 138 def __get__(self, repo, type=None):
139 139 unfi = repo.unfiltered()
140 140 if unfi is repo:
141 141 return super(unfilteredpropertycache, self).__get__(unfi)
142 142 return getattr(unfi, self.name)
143 143
144 144 class filteredpropertycache(util.propertycache):
145 145 """propertycache that must take filtering in account"""
146 146
147 147 def cachevalue(self, obj, value):
148 148 object.__setattr__(obj, self.name, value)
149 149
150 150
151 151 def hasunfilteredcache(repo, name):
152 152 """check if a repo has an unfilteredpropertycache value for <name>"""
153 153 return name in vars(repo.unfiltered())
154 154
155 155 def unfilteredmethod(orig):
156 156 """decorate method that always need to be run on unfiltered version"""
157 157 def wrapper(repo, *args, **kwargs):
158 158 return orig(repo.unfiltered(), *args, **kwargs)
159 159 return wrapper
160 160
161 161 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
162 162 'unbundle'}
163 163 legacycaps = moderncaps.union({'changegroupsubset'})
164 164
165 165 @interfaceutil.implementer(repository.ipeercommandexecutor)
166 166 class localcommandexecutor(object):
167 167 def __init__(self, peer):
168 168 self._peer = peer
169 169 self._sent = False
170 170 self._closed = False
171 171
172 172 def __enter__(self):
173 173 return self
174 174
175 175 def __exit__(self, exctype, excvalue, exctb):
176 176 self.close()
177 177
178 178 def callcommand(self, command, args):
179 179 if self._sent:
180 180 raise error.ProgrammingError('callcommand() cannot be used after '
181 181 'sendcommands()')
182 182
183 183 if self._closed:
184 184 raise error.ProgrammingError('callcommand() cannot be used after '
185 185 'close()')
186 186
187 187 # We don't need to support anything fancy. Just call the named
188 188 # method on the peer and return a resolved future.
189 189 fn = getattr(self._peer, pycompat.sysstr(command))
190 190
191 191 f = pycompat.futures.Future()
192 192
193 193 try:
194 194 result = fn(**pycompat.strkwargs(args))
195 195 except Exception:
196 196 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
197 197 else:
198 198 f.set_result(result)
199 199
200 200 return f
201 201
202 202 def sendcommands(self):
203 203 self._sent = True
204 204
205 205 def close(self):
206 206 self._closed = True
207 207
208 208 @interfaceutil.implementer(repository.ipeercommands)
209 209 class localpeer(repository.peer):
210 210 '''peer for a local repo; reflects only the most recent API'''
211 211
212 212 def __init__(self, repo, caps=None):
213 213 super(localpeer, self).__init__()
214 214
215 215 if caps is None:
216 216 caps = moderncaps.copy()
217 217 self._repo = repo.filtered('served')
218 218 self.ui = repo.ui
219 219 self._caps = repo._restrictcapabilities(caps)
220 220
221 221 # Begin of _basepeer interface.
222 222
223 223 def url(self):
224 224 return self._repo.url()
225 225
226 226 def local(self):
227 227 return self._repo
228 228
229 229 def peer(self):
230 230 return self
231 231
232 232 def canpush(self):
233 233 return True
234 234
235 235 def close(self):
236 236 self._repo.close()
237 237
238 238 # End of _basepeer interface.
239 239
240 240 # Begin of _basewirecommands interface.
241 241
242 242 def branchmap(self):
243 243 return self._repo.branchmap()
244 244
245 245 def capabilities(self):
246 246 return self._caps
247 247
248 248 def clonebundles(self):
249 249 return self._repo.tryread('clonebundles.manifest')
250 250
251 251 def debugwireargs(self, one, two, three=None, four=None, five=None):
252 252 """Used to test argument passing over the wire"""
253 253 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
254 254 pycompat.bytestr(four),
255 255 pycompat.bytestr(five))
256 256
257 257 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
258 258 **kwargs):
259 259 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
260 260 common=common, bundlecaps=bundlecaps,
261 261 **kwargs)[1]
262 262 cb = util.chunkbuffer(chunks)
263 263
264 264 if exchange.bundle2requested(bundlecaps):
265 265 # When requesting a bundle2, getbundle returns a stream to make the
266 266 # wire level function happier. We need to build a proper object
267 267 # from it in local peer.
268 268 return bundle2.getunbundler(self.ui, cb)
269 269 else:
270 270 return changegroup.getunbundler('01', cb, None)
271 271
272 272 def heads(self):
273 273 return self._repo.heads()
274 274
275 275 def known(self, nodes):
276 276 return self._repo.known(nodes)
277 277
278 278 def listkeys(self, namespace):
279 279 return self._repo.listkeys(namespace)
280 280
281 281 def lookup(self, key):
282 282 return self._repo.lookup(key)
283 283
284 284 def pushkey(self, namespace, key, old, new):
285 285 return self._repo.pushkey(namespace, key, old, new)
286 286
287 287 def stream_out(self):
288 288 raise error.Abort(_('cannot perform stream clone against local '
289 289 'peer'))
290 290
291 291 def unbundle(self, bundle, heads, url):
292 292 """apply a bundle on a repo
293 293
294 294 This function handles the repo locking itself."""
295 295 try:
296 296 try:
297 297 bundle = exchange.readbundle(self.ui, bundle, None)
298 298 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
299 299 if util.safehasattr(ret, 'getchunks'):
300 300 # This is a bundle20 object, turn it into an unbundler.
301 301 # This little dance should be dropped eventually when the
302 302 # API is finally improved.
303 303 stream = util.chunkbuffer(ret.getchunks())
304 304 ret = bundle2.getunbundler(self.ui, stream)
305 305 return ret
306 306 except Exception as exc:
307 307 # If the exception contains output salvaged from a bundle2
308 308 # reply, we need to make sure it is printed before continuing
309 309 # to fail. So we build a bundle2 with such output and consume
310 310 # it directly.
311 311 #
312 312 # This is not very elegant but allows a "simple" solution for
313 313 # issue4594
314 314 output = getattr(exc, '_bundle2salvagedoutput', ())
315 315 if output:
316 316 bundler = bundle2.bundle20(self._repo.ui)
317 317 for out in output:
318 318 bundler.addpart(out)
319 319 stream = util.chunkbuffer(bundler.getchunks())
320 320 b = bundle2.getunbundler(self.ui, stream)
321 321 bundle2.processbundle(self._repo, b)
322 322 raise
323 323 except error.PushRaced as exc:
324 324 raise error.ResponseError(_('push failed:'),
325 325 stringutil.forcebytestr(exc))
326 326
327 327 # End of _basewirecommands interface.
328 328
329 329 # Begin of peer interface.
330 330
331 331 def commandexecutor(self):
332 332 return localcommandexecutor(self)
333 333
334 334 # End of peer interface.
335 335
336 336 @interfaceutil.implementer(repository.ipeerlegacycommands)
337 337 class locallegacypeer(localpeer):
338 338 '''peer extension which implements legacy methods too; used for tests with
339 339 restricted capabilities'''
340 340
341 341 def __init__(self, repo):
342 342 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
343 343
344 344 # Begin of baselegacywirecommands interface.
345 345
346 346 def between(self, pairs):
347 347 return self._repo.between(pairs)
348 348
349 349 def branches(self, nodes):
350 350 return self._repo.branches(nodes)
351 351
352 352 def changegroup(self, nodes, source):
353 353 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
354 354 missingheads=self._repo.heads())
355 355 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
356 356
357 357 def changegroupsubset(self, bases, heads, source):
358 358 outgoing = discovery.outgoing(self._repo, missingroots=bases,
359 359 missingheads=heads)
360 360 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
361 361
362 362 # End of baselegacywirecommands interface.
363 363
364 364 # Increment the sub-version when the revlog v2 format changes to lock out old
365 365 # clients.
366 366 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
367 367
368 368 # A repository with the sparserevlog feature will have delta chains that
369 369 # can spread over a larger span. Sparse reading cuts these large spans into
370 370 # pieces, so that each piece isn't too big.
371 371 # Without the sparserevlog capability, reading from the repository could use
372 372 # huge amounts of memory, because the whole span would be read at once,
373 373 # including all the intermediate revisions that aren't pertinent for the chain.
374 374 # This is why once a repository has enabled sparse-read, it becomes required.
375 375 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
376 376
377 377 # Functions receiving (ui, features) that extensions can register to impact
378 378 # the ability to load repositories with custom requirements. Only
379 379 # functions defined in loaded extensions are called.
380 380 #
381 381 # The function receives a set of requirement strings that the repository
382 382 # is capable of opening. Functions will typically add elements to the
383 383 # set to reflect that the extension knows how to handle that requirements.
384 384 featuresetupfuncs = set()
385 385
386 386 def makelocalrepository(baseui, path, intents=None):
387 387 """Create a local repository object.
388 388
389 389 Given arguments needed to construct a local repository, this function
390 390 performs various early repository loading functionality (such as
391 391 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
392 392 the repository can be opened, derives a type suitable for representing
393 393 that repository, and returns an instance of it.
394 394
395 395 The returned object conforms to the ``repository.completelocalrepository``
396 396 interface.
397 397
398 398 The repository type is derived by calling a series of factory functions
399 399 for each aspect/interface of the final repository. These are defined by
400 400 ``REPO_INTERFACES``.
401 401
402 402 Each factory function is called to produce a type implementing a specific
403 403 interface. The cumulative list of returned types will be combined into a
404 404 new type and that type will be instantiated to represent the local
405 405 repository.
406 406
407 407 The factory functions each receive various state that may be consulted
408 408 as part of deriving a type.
409 409
410 410 Extensions should wrap these factory functions to customize repository type
411 411 creation. Note that an extension's wrapped function may be called even if
412 412 that extension is not loaded for the repo being constructed. Extensions
413 413 should check if their ``__name__`` appears in the
414 414 ``extensionmodulenames`` set passed to the factory function and no-op if
415 415 not.
416 416 """
417 417 ui = baseui.copy()
418 418 # Prevent copying repo configuration.
419 419 ui.copy = baseui.copy
420 420
421 421 # Working directory VFS rooted at repository root.
422 422 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
423 423
424 424 # Main VFS for .hg/ directory.
425 425 hgpath = wdirvfs.join(b'.hg')
426 426 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
427 427
428 428 # The .hg/ path should exist and should be a directory. All other
429 429 # cases are errors.
430 430 if not hgvfs.isdir():
431 431 try:
432 432 hgvfs.stat()
433 433 except OSError as e:
434 434 if e.errno != errno.ENOENT:
435 435 raise
436 436
437 437 raise error.RepoError(_(b'repository %s not found') % path)
438 438
439 439 # .hg/requires file contains a newline-delimited list of
440 440 # features/capabilities the opener (us) must have in order to use
441 441 # the repository. This file was introduced in Mercurial 0.9.2,
442 442 # which means very old repositories may not have one. We assume
443 443 # a missing file translates to no requirements.
444 444 try:
445 445 requirements = set(hgvfs.read(b'requires').splitlines())
446 446 except IOError as e:
447 447 if e.errno != errno.ENOENT:
448 448 raise
449 449 requirements = set()
450 450
451 451 # The .hg/hgrc file may load extensions or contain config options
452 452 # that influence repository construction. Attempt to load it and
453 453 # process any new extensions that it may have pulled in.
454 454 if loadhgrc(ui, wdirvfs, hgvfs, requirements):
455 455 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
456 456 extensions.loadall(ui)
457 457 extensions.populateui(ui)
458 458
459 459 # Set of module names of extensions loaded for this repository.
460 460 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
461 461
462 462 supportedrequirements = gathersupportedrequirements(ui)
463 463
464 464 # We first validate the requirements are known.
465 465 ensurerequirementsrecognized(requirements, supportedrequirements)
466 466
467 467 # Then we validate that the known set is reasonable to use together.
468 468 ensurerequirementscompatible(ui, requirements)
469 469
470 470 # TODO there are unhandled edge cases related to opening repositories with
471 471 # shared storage. If storage is shared, we should also test for requirements
472 472 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
473 473 # that repo, as that repo may load extensions needed to open it. This is a
474 474 # bit complicated because we don't want the other hgrc to overwrite settings
475 475 # in this hgrc.
476 476 #
477 477 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
478 478 # file when sharing repos. But if a requirement is added after the share is
479 479 # performed, thereby introducing a new requirement for the opener, we may
480 480 # will not see that and could encounter a run-time error interacting with
481 481 # that shared store since it has an unknown-to-us requirement.
482 482
483 483 # At this point, we know we should be capable of opening the repository.
484 484 # Now get on with doing that.
485 485
486 486 features = set()
487 487
488 488 # The "store" part of the repository holds versioned data. How it is
489 489 # accessed is determined by various requirements. The ``shared`` or
490 490 # ``relshared`` requirements indicate the store lives in the path contained
491 491 # in the ``.hg/sharedpath`` file. This is an absolute path for
492 492 # ``shared`` and relative to ``.hg/`` for ``relshared``.
493 493 if b'shared' in requirements or b'relshared' in requirements:
494 494 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
495 495 if b'relshared' in requirements:
496 496 sharedpath = hgvfs.join(sharedpath)
497 497
498 498 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
499 499
500 500 if not sharedvfs.exists():
501 501 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
502 502 b'directory %s') % sharedvfs.base)
503 503
504 504 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
505 505
506 506 storebasepath = sharedvfs.base
507 507 cachepath = sharedvfs.join(b'cache')
508 508 else:
509 509 storebasepath = hgvfs.base
510 510 cachepath = hgvfs.join(b'cache')
511 511
512 512 # The store has changed over time and the exact layout is dictated by
513 513 # requirements. The store interface abstracts differences across all
514 514 # of them.
515 515 store = makestore(requirements, storebasepath,
516 516 lambda base: vfsmod.vfs(base, cacheaudited=True))
517 517 hgvfs.createmode = store.createmode
518 518
519 519 storevfs = store.vfs
520 520 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
521 521
522 522 # The cache vfs is used to manage cache files.
523 523 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
524 524 cachevfs.createmode = store.createmode
525 525
526 526 # Now resolve the type for the repository object. We do this by repeatedly
527 527 # calling a factory function to produces types for specific aspects of the
528 528 # repo's operation. The aggregate returned types are used as base classes
529 529 # for a dynamically-derived type, which will represent our new repository.
530 530
531 531 bases = []
532 532 extrastate = {}
533 533
534 534 for iface, fn in REPO_INTERFACES:
535 535 # We pass all potentially useful state to give extensions tons of
536 536 # flexibility.
537 537 typ = fn()(ui=ui,
538 538 intents=intents,
539 539 requirements=requirements,
540 540 features=features,
541 541 wdirvfs=wdirvfs,
542 542 hgvfs=hgvfs,
543 543 store=store,
544 544 storevfs=storevfs,
545 545 storeoptions=storevfs.options,
546 546 cachevfs=cachevfs,
547 547 extensionmodulenames=extensionmodulenames,
548 548 extrastate=extrastate,
549 549 baseclasses=bases)
550 550
551 551 if not isinstance(typ, type):
552 552 raise error.ProgrammingError('unable to construct type for %s' %
553 553 iface)
554 554
555 555 bases.append(typ)
556 556
557 557 # type() allows you to use characters in type names that wouldn't be
558 558 # recognized as Python symbols in source code. We abuse that to add
559 559 # rich information about our constructed repo.
560 560 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
561 561 wdirvfs.base,
562 562 b','.join(sorted(requirements))))
563 563
564 564 cls = type(name, tuple(bases), {})
565 565
566 566 return cls(
567 567 baseui=baseui,
568 568 ui=ui,
569 569 origroot=path,
570 570 wdirvfs=wdirvfs,
571 571 hgvfs=hgvfs,
572 572 requirements=requirements,
573 573 supportedrequirements=supportedrequirements,
574 574 sharedpath=storebasepath,
575 575 store=store,
576 576 cachevfs=cachevfs,
577 577 features=features,
578 578 intents=intents)
579 579
580 580 def loadhgrc(ui, wdirvfs, hgvfs, requirements):
581 581 """Load hgrc files/content into a ui instance.
582 582
583 583 This is called during repository opening to load any additional
584 584 config files or settings relevant to the current repository.
585 585
586 586 Returns a bool indicating whether any additional configs were loaded.
587 587
588 588 Extensions should monkeypatch this function to modify how per-repo
589 589 configs are loaded. For example, an extension may wish to pull in
590 590 configs from alternate files or sources.
591 591 """
592 592 try:
593 593 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
594 594 return True
595 595 except IOError:
596 596 return False
597 597
598 598 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
599 599 """Perform additional actions after .hg/hgrc is loaded.
600 600
601 601 This function is called during repository loading immediately after
602 602 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
603 603
604 604 The function can be used to validate configs, automatically add
605 605 options (including extensions) based on requirements, etc.
606 606 """
607 607
608 608 # Map of requirements to list of extensions to load automatically when
609 609 # requirement is present.
610 610 autoextensions = {
611 611 b'largefiles': [b'largefiles'],
612 612 b'lfs': [b'lfs'],
613 613 }
614 614
615 615 for requirement, names in sorted(autoextensions.items()):
616 616 if requirement not in requirements:
617 617 continue
618 618
619 619 for name in names:
620 620 if not ui.hasconfig(b'extensions', name):
621 621 ui.setconfig(b'extensions', name, b'', source='autoload')
622 622
623 623 def gathersupportedrequirements(ui):
624 624 """Determine the complete set of recognized requirements."""
625 625 # Start with all requirements supported by this file.
626 626 supported = set(localrepository._basesupported)
627 627
628 628 # Execute ``featuresetupfuncs`` entries if they belong to an extension
629 629 # relevant to this ui instance.
630 630 modules = {m.__name__ for n, m in extensions.extensions(ui)}
631 631
632 632 for fn in featuresetupfuncs:
633 633 if fn.__module__ in modules:
634 634 fn(ui, supported)
635 635
636 636 # Add derived requirements from registered compression engines.
637 637 for name in util.compengines:
638 638 engine = util.compengines[name]
639 639 if engine.revlogheader():
640 640 supported.add(b'exp-compression-%s' % name)
641 641
642 642 return supported
643 643
644 644 def ensurerequirementsrecognized(requirements, supported):
645 645 """Validate that a set of local requirements is recognized.
646 646
647 647 Receives a set of requirements. Raises an ``error.RepoError`` if there
648 648 exists any requirement in that set that currently loaded code doesn't
649 649 recognize.
650 650
651 651 Returns a set of supported requirements.
652 652 """
653 653 missing = set()
654 654
655 655 for requirement in requirements:
656 656 if requirement in supported:
657 657 continue
658 658
659 659 if not requirement or not requirement[0:1].isalnum():
660 660 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
661 661
662 662 missing.add(requirement)
663 663
664 664 if missing:
665 665 raise error.RequirementError(
666 666 _(b'repository requires features unknown to this Mercurial: %s') %
667 667 b' '.join(sorted(missing)),
668 668 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
669 669 b'for more information'))
670 670
671 671 def ensurerequirementscompatible(ui, requirements):
672 672 """Validates that a set of recognized requirements is mutually compatible.
673 673
674 674 Some requirements may not be compatible with others or require
675 675 config options that aren't enabled. This function is called during
676 676 repository opening to ensure that the set of requirements needed
677 677 to open a repository is sane and compatible with config options.
678 678
679 679 Extensions can monkeypatch this function to perform additional
680 680 checking.
681 681
682 682 ``error.RepoError`` should be raised on failure.
683 683 """
684 684 if b'exp-sparse' in requirements and not sparse.enabled:
685 685 raise error.RepoError(_(b'repository is using sparse feature but '
686 686 b'sparse is not enabled; enable the '
687 687 b'"sparse" extensions to access'))
688 688
689 689 def makestore(requirements, path, vfstype):
690 690 """Construct a storage object for a repository."""
691 691 if b'store' in requirements:
692 692 if b'fncache' in requirements:
693 693 return storemod.fncachestore(path, vfstype,
694 694 b'dotencode' in requirements)
695 695
696 696 return storemod.encodedstore(path, vfstype)
697 697
698 698 return storemod.basicstore(path, vfstype)
699 699
700 700 def resolvestorevfsoptions(ui, requirements, features):
701 701 """Resolve the options to pass to the store vfs opener.
702 702
703 703 The returned dict is used to influence behavior of the storage layer.
704 704 """
705 705 options = {}
706 706
707 707 if b'treemanifest' in requirements:
708 708 options[b'treemanifest'] = True
709 709
710 710 # experimental config: format.manifestcachesize
711 711 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
712 712 if manifestcachesize is not None:
713 713 options[b'manifestcachesize'] = manifestcachesize
714 714
715 715 # In the absence of another requirement superseding a revlog-related
716 716 # requirement, we have to assume the repo is using revlog version 0.
717 717 # This revlog format is super old and we don't bother trying to parse
718 718 # opener options for it because those options wouldn't do anything
719 719 # meaningful on such old repos.
720 720 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
721 721 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
722 722
723 723 return options
724 724
725 725 def resolverevlogstorevfsoptions(ui, requirements, features):
726 726 """Resolve opener options specific to revlogs."""
727 727
728 728 options = {}
729 729 options[b'flagprocessors'] = {}
730 730
731 731 if b'revlogv1' in requirements:
732 732 options[b'revlogv1'] = True
733 733 if REVLOGV2_REQUIREMENT in requirements:
734 734 options[b'revlogv2'] = True
735 735
736 736 if b'generaldelta' in requirements:
737 737 options[b'generaldelta'] = True
738 738
739 739 # experimental config: format.chunkcachesize
740 740 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
741 741 if chunkcachesize is not None:
742 742 options[b'chunkcachesize'] = chunkcachesize
743 743
744 744 deltabothparents = ui.configbool(b'storage',
745 745 b'revlog.optimize-delta-parent-choice')
746 746 options[b'deltabothparents'] = deltabothparents
747 747
748 748 options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
749 749
750 750 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
751 751 if 0 <= chainspan:
752 752 options[b'maxdeltachainspan'] = chainspan
753 753
754 754 mmapindexthreshold = ui.configbytes(b'storage', b'mmap-threshold')
755 755 if mmapindexthreshold is not None:
756 756 options[b'mmapindexthreshold'] = mmapindexthreshold
757 757
758 758 withsparseread = ui.configbool(b'experimental', b'sparse-read')
759 759 srdensitythres = float(ui.config(b'experimental',
760 760 b'sparse-read.density-threshold'))
761 761 srmingapsize = ui.configbytes(b'experimental',
762 762 b'sparse-read.min-gap-size')
763 763 options[b'with-sparse-read'] = withsparseread
764 764 options[b'sparse-read-density-threshold'] = srdensitythres
765 765 options[b'sparse-read-min-gap-size'] = srmingapsize
766 766
767 767 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
768 768 options[b'sparse-revlog'] = sparserevlog
769 769 if sparserevlog:
770 770 options[b'generaldelta'] = True
771 771
772 772 maxchainlen = None
773 773 if sparserevlog:
774 774 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
775 775 # experimental config: format.maxchainlen
776 776 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
777 777 if maxchainlen is not None:
778 778 options[b'maxchainlen'] = maxchainlen
779 779
780 780 for r in requirements:
781 781 if r.startswith(b'exp-compression-'):
782 782 options[b'compengine'] = r[len(b'exp-compression-'):]
783 783
784 784 if repository.NARROW_REQUIREMENT in requirements:
785 785 options[b'enableellipsis'] = True
786 786
787 787 return options
788 788
789 789 def makemain(**kwargs):
790 790 """Produce a type conforming to ``ilocalrepositorymain``."""
791 791 return localrepository
792 792
793 793 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
794 794 class revlogfilestorage(object):
795 795 """File storage when using revlogs."""
796 796
797 797 def file(self, path):
798 798 if path[0] == b'/':
799 799 path = path[1:]
800 800
801 801 return filelog.filelog(self.svfs, path)
802 802
803 803 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
804 804 class revlognarrowfilestorage(object):
805 805 """File storage when using revlogs and narrow files."""
806 806
807 807 def file(self, path):
808 808 if path[0] == b'/':
809 809 path = path[1:]
810 810
811 811 return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
812 812
813 813 def makefilestorage(requirements, features, **kwargs):
814 814 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
815 815 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
816 816 features.add(repository.REPO_FEATURE_STREAM_CLONE)
817 817
818 818 if repository.NARROW_REQUIREMENT in requirements:
819 819 return revlognarrowfilestorage
820 820 else:
821 821 return revlogfilestorage
822 822
823 823 # List of repository interfaces and factory functions for them. Each
824 824 # will be called in order during ``makelocalrepository()`` to iteratively
825 825 # derive the final type for a local repository instance. We capture the
826 826 # function as a lambda so we don't hold a reference and the module-level
827 827 # functions can be wrapped.
828 828 REPO_INTERFACES = [
829 829 (repository.ilocalrepositorymain, lambda: makemain),
830 830 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
831 831 ]
832 832
833 833 @interfaceutil.implementer(repository.ilocalrepositorymain)
834 834 class localrepository(object):
835 835 """Main class for representing local repositories.
836 836
837 837 All local repositories are instances of this class.
838 838
839 839 Constructed on its own, instances of this class are not usable as
840 840 repository objects. To obtain a usable repository object, call
841 841 ``hg.repository()``, ``localrepo.instance()``, or
842 842 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
843 843 ``instance()`` adds support for creating new repositories.
844 844 ``hg.repository()`` adds more extension integration, including calling
845 845 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
846 846 used.
847 847 """
848 848
849 849 # obsolete experimental requirements:
850 850 # - manifestv2: An experimental new manifest format that allowed
851 851 # for stem compression of long paths. Experiment ended up not
852 852 # being successful (repository sizes went up due to worse delta
853 853 # chains), and the code was deleted in 4.6.
854 854 supportedformats = {
855 855 'revlogv1',
856 856 'generaldelta',
857 857 'treemanifest',
858 858 REVLOGV2_REQUIREMENT,
859 859 SPARSEREVLOG_REQUIREMENT,
860 860 }
861 861 _basesupported = supportedformats | {
862 862 'store',
863 863 'fncache',
864 864 'shared',
865 865 'relshared',
866 866 'dotencode',
867 867 'exp-sparse',
868 868 'internal-phase'
869 869 }
870 870
871 871 # list of prefix for file which can be written without 'wlock'
872 872 # Extensions should extend this list when needed
873 873 _wlockfreeprefix = {
874 874 # We migh consider requiring 'wlock' for the next
875 875 # two, but pretty much all the existing code assume
876 876 # wlock is not needed so we keep them excluded for
877 877 # now.
878 878 'hgrc',
879 879 'requires',
880 880 # XXX cache is a complicatged business someone
881 881 # should investigate this in depth at some point
882 882 'cache/',
883 883 # XXX shouldn't be dirstate covered by the wlock?
884 884 'dirstate',
885 885 # XXX bisect was still a bit too messy at the time
886 886 # this changeset was introduced. Someone should fix
887 887 # the remainig bit and drop this line
888 888 'bisect.state',
889 889 }
890 890
891 891 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
892 892 supportedrequirements, sharedpath, store, cachevfs,
893 893 features, intents=None):
894 894 """Create a new local repository instance.
895 895
896 896 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
897 897 or ``localrepo.makelocalrepository()`` for obtaining a new repository
898 898 object.
899 899
900 900 Arguments:
901 901
902 902 baseui
903 903 ``ui.ui`` instance that ``ui`` argument was based off of.
904 904
905 905 ui
906 906 ``ui.ui`` instance for use by the repository.
907 907
908 908 origroot
909 909 ``bytes`` path to working directory root of this repository.
910 910
911 911 wdirvfs
912 912 ``vfs.vfs`` rooted at the working directory.
913 913
914 914 hgvfs
915 915 ``vfs.vfs`` rooted at .hg/
916 916
917 917 requirements
918 918 ``set`` of bytestrings representing repository opening requirements.
919 919
920 920 supportedrequirements
921 921 ``set`` of bytestrings representing repository requirements that we
922 922 know how to open. May be a supetset of ``requirements``.
923 923
924 924 sharedpath
925 925 ``bytes`` Defining path to storage base directory. Points to a
926 926 ``.hg/`` directory somewhere.
927 927
928 928 store
929 929 ``store.basicstore`` (or derived) instance providing access to
930 930 versioned storage.
931 931
932 932 cachevfs
933 933 ``vfs.vfs`` used for cache files.
934 934
935 935 features
936 936 ``set`` of bytestrings defining features/capabilities of this
937 937 instance.
938 938
939 939 intents
940 940 ``set`` of system strings indicating what this repo will be used
941 941 for.
942 942 """
943 943 self.baseui = baseui
944 944 self.ui = ui
945 945 self.origroot = origroot
946 946 # vfs rooted at working directory.
947 947 self.wvfs = wdirvfs
948 948 self.root = wdirvfs.base
949 949 # vfs rooted at .hg/. Used to access most non-store paths.
950 950 self.vfs = hgvfs
951 951 self.path = hgvfs.base
952 952 self.requirements = requirements
953 953 self.supported = supportedrequirements
954 954 self.sharedpath = sharedpath
955 955 self.store = store
956 956 self.cachevfs = cachevfs
957 957 self.features = features
958 958
959 959 self.filtername = None
960 960
961 961 if (self.ui.configbool('devel', 'all-warnings') or
962 962 self.ui.configbool('devel', 'check-locks')):
963 963 self.vfs.audit = self._getvfsward(self.vfs.audit)
964 964 # A list of callback to shape the phase if no data were found.
965 965 # Callback are in the form: func(repo, roots) --> processed root.
966 966 # This list it to be filled by extension during repo setup
967 967 self._phasedefaults = []
968 968
969 969 color.setup(self.ui)
970 970
971 971 self.spath = self.store.path
972 972 self.svfs = self.store.vfs
973 973 self.sjoin = self.store.join
974 974 if (self.ui.configbool('devel', 'all-warnings') or
975 975 self.ui.configbool('devel', 'check-locks')):
976 976 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
977 977 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
978 978 else: # standard vfs
979 979 self.svfs.audit = self._getsvfsward(self.svfs.audit)
980 980
981 981 self._dirstatevalidatewarned = False
982 982
983 983 self._branchcaches = {}
984 984 self._revbranchcache = None
985 985 self._filterpats = {}
986 986 self._datafilters = {}
987 987 self._transref = self._lockref = self._wlockref = None
988 988
989 989 # A cache for various files under .hg/ that tracks file changes,
990 990 # (used by the filecache decorator)
991 991 #
992 992 # Maps a property name to its util.filecacheentry
993 993 self._filecache = {}
994 994
995 995 # hold sets of revision to be filtered
996 996 # should be cleared when something might have changed the filter value:
997 997 # - new changesets,
998 998 # - phase change,
999 999 # - new obsolescence marker,
1000 1000 # - working directory parent change,
1001 1001 # - bookmark changes
1002 1002 self.filteredrevcache = {}
1003 1003
1004 1004 # post-dirstate-status hooks
1005 1005 self._postdsstatus = []
1006 1006
1007 1007 # generic mapping between names and nodes
1008 1008 self.names = namespaces.namespaces()
1009 1009
1010 1010 # Key to signature value.
1011 1011 self._sparsesignaturecache = {}
1012 1012 # Signature to cached matcher instance.
1013 1013 self._sparsematchercache = {}
1014 1014
1015 1015 def _getvfsward(self, origfunc):
1016 1016 """build a ward for self.vfs"""
1017 1017 rref = weakref.ref(self)
1018 1018 def checkvfs(path, mode=None):
1019 1019 ret = origfunc(path, mode=mode)
1020 1020 repo = rref()
1021 1021 if (repo is None
1022 1022 or not util.safehasattr(repo, '_wlockref')
1023 1023 or not util.safehasattr(repo, '_lockref')):
1024 1024 return
1025 1025 if mode in (None, 'r', 'rb'):
1026 1026 return
1027 1027 if path.startswith(repo.path):
1028 1028 # truncate name relative to the repository (.hg)
1029 1029 path = path[len(repo.path) + 1:]
1030 1030 if path.startswith('cache/'):
1031 1031 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1032 1032 repo.ui.develwarn(msg % path, stacklevel=3, config="cache-vfs")
1033 1033 if path.startswith('journal.') or path.startswith('undo.'):
1034 1034 # journal is covered by 'lock'
1035 1035 if repo._currentlock(repo._lockref) is None:
1036 1036 repo.ui.develwarn('write with no lock: "%s"' % path,
1037 1037 stacklevel=3, config='check-locks')
1038 1038 elif repo._currentlock(repo._wlockref) is None:
1039 1039 # rest of vfs files are covered by 'wlock'
1040 1040 #
1041 1041 # exclude special files
1042 1042 for prefix in self._wlockfreeprefix:
1043 1043 if path.startswith(prefix):
1044 1044 return
1045 1045 repo.ui.develwarn('write with no wlock: "%s"' % path,
1046 1046 stacklevel=3, config='check-locks')
1047 1047 return ret
1048 1048 return checkvfs
1049 1049
1050 1050 def _getsvfsward(self, origfunc):
1051 1051 """build a ward for self.svfs"""
1052 1052 rref = weakref.ref(self)
1053 1053 def checksvfs(path, mode=None):
1054 1054 ret = origfunc(path, mode=mode)
1055 1055 repo = rref()
1056 1056 if repo is None or not util.safehasattr(repo, '_lockref'):
1057 1057 return
1058 1058 if mode in (None, 'r', 'rb'):
1059 1059 return
1060 1060 if path.startswith(repo.sharedpath):
1061 1061 # truncate name relative to the repository (.hg)
1062 1062 path = path[len(repo.sharedpath) + 1:]
1063 1063 if repo._currentlock(repo._lockref) is None:
1064 1064 repo.ui.develwarn('write with no lock: "%s"' % path,
1065 1065 stacklevel=4)
1066 1066 return ret
1067 1067 return checksvfs
1068 1068
1069 1069 def close(self):
1070 1070 self._writecaches()
1071 1071
1072 1072 def _writecaches(self):
1073 1073 if self._revbranchcache:
1074 1074 self._revbranchcache.write()
1075 1075
1076 1076 def _restrictcapabilities(self, caps):
1077 1077 if self.ui.configbool('experimental', 'bundle2-advertise'):
1078 1078 caps = set(caps)
1079 1079 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1080 1080 role='client'))
1081 1081 caps.add('bundle2=' + urlreq.quote(capsblob))
1082 1082 return caps
1083 1083
1084 1084 def _writerequirements(self):
1085 1085 scmutil.writerequires(self.vfs, self.requirements)
1086 1086
1087 1087 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1088 1088 # self -> auditor -> self._checknested -> self
1089 1089
1090 1090 @property
1091 1091 def auditor(self):
1092 1092 # This is only used by context.workingctx.match in order to
1093 1093 # detect files in subrepos.
1094 1094 return pathutil.pathauditor(self.root, callback=self._checknested)
1095 1095
1096 1096 @property
1097 1097 def nofsauditor(self):
1098 1098 # This is only used by context.basectx.match in order to detect
1099 1099 # files in subrepos.
1100 1100 return pathutil.pathauditor(self.root, callback=self._checknested,
1101 1101 realfs=False, cached=True)
1102 1102
1103 1103 def _checknested(self, path):
1104 1104 """Determine if path is a legal nested repository."""
1105 1105 if not path.startswith(self.root):
1106 1106 return False
1107 1107 subpath = path[len(self.root) + 1:]
1108 1108 normsubpath = util.pconvert(subpath)
1109 1109
1110 1110 # XXX: Checking against the current working copy is wrong in
1111 1111 # the sense that it can reject things like
1112 1112 #
1113 1113 # $ hg cat -r 10 sub/x.txt
1114 1114 #
1115 1115 # if sub/ is no longer a subrepository in the working copy
1116 1116 # parent revision.
1117 1117 #
1118 1118 # However, it can of course also allow things that would have
1119 1119 # been rejected before, such as the above cat command if sub/
1120 1120 # is a subrepository now, but was a normal directory before.
1121 1121 # The old path auditor would have rejected by mistake since it
1122 1122 # panics when it sees sub/.hg/.
1123 1123 #
1124 1124 # All in all, checking against the working copy seems sensible
1125 1125 # since we want to prevent access to nested repositories on
1126 1126 # the filesystem *now*.
1127 1127 ctx = self[None]
1128 1128 parts = util.splitpath(subpath)
1129 1129 while parts:
1130 1130 prefix = '/'.join(parts)
1131 1131 if prefix in ctx.substate:
1132 1132 if prefix == normsubpath:
1133 1133 return True
1134 1134 else:
1135 1135 sub = ctx.sub(prefix)
1136 1136 return sub.checknested(subpath[len(prefix) + 1:])
1137 1137 else:
1138 1138 parts.pop()
1139 1139 return False
1140 1140
1141 1141 def peer(self):
1142 1142 return localpeer(self) # not cached to avoid reference cycle
1143 1143
1144 1144 def unfiltered(self):
1145 1145 """Return unfiltered version of the repository
1146 1146
1147 1147 Intended to be overwritten by filtered repo."""
1148 1148 return self
1149 1149
1150 1150 def filtered(self, name, visibilityexceptions=None):
1151 1151 """Return a filtered version of a repository"""
1152 1152 cls = repoview.newtype(self.unfiltered().__class__)
1153 1153 return cls(self, name, visibilityexceptions)
1154 1154
1155 1155 @repofilecache('bookmarks', 'bookmarks.current')
1156 1156 def _bookmarks(self):
1157 1157 return bookmarks.bmstore(self)
1158 1158
1159 1159 @property
1160 1160 def _activebookmark(self):
1161 1161 return self._bookmarks.active
1162 1162
1163 1163 # _phasesets depend on changelog. what we need is to call
1164 1164 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1165 1165 # can't be easily expressed in filecache mechanism.
1166 1166 @storecache('phaseroots', '00changelog.i')
1167 1167 def _phasecache(self):
1168 1168 return phases.phasecache(self, self._phasedefaults)
1169 1169
1170 1170 @storecache('obsstore')
1171 1171 def obsstore(self):
1172 1172 return obsolete.makestore(self.ui, self)
1173 1173
1174 1174 @storecache('00changelog.i')
1175 1175 def changelog(self):
1176 1176 return changelog.changelog(self.svfs,
1177 1177 trypending=txnutil.mayhavepending(self.root))
1178 1178
1179 1179 @storecache('00manifest.i')
1180 1180 def manifestlog(self):
1181 1181 rootstore = manifest.manifestrevlog(self.svfs)
1182 1182 return manifest.manifestlog(self.svfs, self, rootstore)
1183 1183
1184 1184 @repofilecache('dirstate')
1185 1185 def dirstate(self):
1186 1186 return self._makedirstate()
1187 1187
1188 1188 def _makedirstate(self):
1189 1189 """Extension point for wrapping the dirstate per-repo."""
1190 1190 sparsematchfn = lambda: sparse.matcher(self)
1191 1191
1192 1192 return dirstate.dirstate(self.vfs, self.ui, self.root,
1193 1193 self._dirstatevalidate, sparsematchfn)
1194 1194
1195 1195 def _dirstatevalidate(self, node):
1196 1196 try:
1197 1197 self.changelog.rev(node)
1198 1198 return node
1199 1199 except error.LookupError:
1200 1200 if not self._dirstatevalidatewarned:
1201 1201 self._dirstatevalidatewarned = True
1202 1202 self.ui.warn(_("warning: ignoring unknown"
1203 1203 " working parent %s!\n") % short(node))
1204 1204 return nullid
1205 1205
1206 1206 @storecache(narrowspec.FILENAME)
1207 1207 def narrowpats(self):
1208 1208 """matcher patterns for this repository's narrowspec
1209 1209
1210 1210 A tuple of (includes, excludes).
1211 1211 """
1212 1212 return narrowspec.load(self)
1213 1213
1214 1214 @storecache(narrowspec.FILENAME)
1215 1215 def _narrowmatch(self):
1216 1216 if repository.NARROW_REQUIREMENT not in self.requirements:
1217 1217 return matchmod.always(self.root, '')
1218 1218 include, exclude = self.narrowpats
1219 1219 return narrowspec.match(self.root, include=include, exclude=exclude)
1220 1220
1221 1221 def narrowmatch(self, match=None, includeexact=False):
1222 1222 """matcher corresponding the the repo's narrowspec
1223 1223
1224 1224 If `match` is given, then that will be intersected with the narrow
1225 1225 matcher.
1226 1226
1227 1227 If `includeexact` is True, then any exact matches from `match` will
1228 1228 be included even if they're outside the narrowspec.
1229 1229 """
1230 1230 if match:
1231 1231 if includeexact and not self._narrowmatch.always():
1232 1232 # do not exclude explicitly-specified paths so that they can
1233 1233 # be warned later on
1234 1234 em = matchmod.exact(match._root, match._cwd, match.files())
1235 1235 nm = matchmod.unionmatcher([self._narrowmatch, em])
1236 1236 return matchmod.intersectmatchers(match, nm)
1237 1237 return matchmod.intersectmatchers(match, self._narrowmatch)
1238 1238 return self._narrowmatch
1239 1239
1240 1240 def setnarrowpats(self, newincludes, newexcludes):
1241 1241 narrowspec.save(self, newincludes, newexcludes)
1242 1242 self.invalidate(clearfilecache=True)
1243 1243
1244 1244 def __getitem__(self, changeid):
1245 1245 if changeid is None:
1246 1246 return context.workingctx(self)
1247 1247 if isinstance(changeid, context.basectx):
1248 1248 return changeid
1249 1249 if isinstance(changeid, slice):
1250 1250 # wdirrev isn't contiguous so the slice shouldn't include it
1251 1251 return [self[i]
1252 1252 for i in pycompat.xrange(*changeid.indices(len(self)))
1253 1253 if i not in self.changelog.filteredrevs]
1254 1254 try:
1255 1255 if isinstance(changeid, int):
1256 1256 node = self.changelog.node(changeid)
1257 1257 rev = changeid
1258 1258 elif changeid == 'null':
1259 1259 node = nullid
1260 1260 rev = nullrev
1261 1261 elif changeid == 'tip':
1262 1262 node = self.changelog.tip()
1263 1263 rev = self.changelog.rev(node)
1264 1264 elif changeid == '.':
1265 1265 # this is a hack to delay/avoid loading obsmarkers
1266 1266 # when we know that '.' won't be hidden
1267 1267 node = self.dirstate.p1()
1268 1268 rev = self.unfiltered().changelog.rev(node)
1269 1269 elif len(changeid) == 20:
1270 1270 try:
1271 1271 node = changeid
1272 1272 rev = self.changelog.rev(changeid)
1273 1273 except error.FilteredLookupError:
1274 1274 changeid = hex(changeid) # for the error message
1275 1275 raise
1276 1276 except LookupError:
1277 1277 # check if it might have come from damaged dirstate
1278 1278 #
1279 1279 # XXX we could avoid the unfiltered if we had a recognizable
1280 1280 # exception for filtered changeset access
1281 1281 if (self.local()
1282 1282 and changeid in self.unfiltered().dirstate.parents()):
1283 1283 msg = _("working directory has unknown parent '%s'!")
1284 1284 raise error.Abort(msg % short(changeid))
1285 1285 changeid = hex(changeid) # for the error message
1286 1286 raise
1287 1287
1288 1288 elif len(changeid) == 40:
1289 1289 node = bin(changeid)
1290 1290 rev = self.changelog.rev(node)
1291 1291 else:
1292 1292 raise error.ProgrammingError(
1293 1293 "unsupported changeid '%s' of type %s" %
1294 1294 (changeid, type(changeid)))
1295 1295
1296 1296 return context.changectx(self, rev, node)
1297 1297
1298 1298 except (error.FilteredIndexError, error.FilteredLookupError):
1299 1299 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1300 1300 % pycompat.bytestr(changeid))
1301 1301 except (IndexError, LookupError):
1302 1302 raise error.RepoLookupError(
1303 1303 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1304 1304 except error.WdirUnsupported:
1305 1305 return context.workingctx(self)
1306 1306
1307 1307 def __contains__(self, changeid):
1308 1308 """True if the given changeid exists
1309 1309
1310 1310 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1311 1311 specified.
1312 1312 """
1313 1313 try:
1314 1314 self[changeid]
1315 1315 return True
1316 1316 except error.RepoLookupError:
1317 1317 return False
1318 1318
1319 1319 def __nonzero__(self):
1320 1320 return True
1321 1321
1322 1322 __bool__ = __nonzero__
1323 1323
1324 1324 def __len__(self):
1325 1325 # no need to pay the cost of repoview.changelog
1326 1326 unfi = self.unfiltered()
1327 1327 return len(unfi.changelog)
1328 1328
1329 1329 def __iter__(self):
1330 1330 return iter(self.changelog)
1331 1331
1332 1332 def revs(self, expr, *args):
1333 1333 '''Find revisions matching a revset.
1334 1334
1335 1335 The revset is specified as a string ``expr`` that may contain
1336 1336 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1337 1337
1338 1338 Revset aliases from the configuration are not expanded. To expand
1339 1339 user aliases, consider calling ``scmutil.revrange()`` or
1340 1340 ``repo.anyrevs([expr], user=True)``.
1341 1341
1342 1342 Returns a revset.abstractsmartset, which is a list-like interface
1343 1343 that contains integer revisions.
1344 1344 '''
1345 1345 expr = revsetlang.formatspec(expr, *args)
1346 1346 m = revset.match(None, expr)
1347 1347 return m(self)
1348 1348
1349 1349 def set(self, expr, *args):
1350 1350 '''Find revisions matching a revset and emit changectx instances.
1351 1351
1352 1352 This is a convenience wrapper around ``revs()`` that iterates the
1353 1353 result and is a generator of changectx instances.
1354 1354
1355 1355 Revset aliases from the configuration are not expanded. To expand
1356 1356 user aliases, consider calling ``scmutil.revrange()``.
1357 1357 '''
1358 1358 for r in self.revs(expr, *args):
1359 1359 yield self[r]
1360 1360
1361 1361 def anyrevs(self, specs, user=False, localalias=None):
1362 1362 '''Find revisions matching one of the given revsets.
1363 1363
1364 1364 Revset aliases from the configuration are not expanded by default. To
1365 1365 expand user aliases, specify ``user=True``. To provide some local
1366 1366 definitions overriding user aliases, set ``localalias`` to
1367 1367 ``{name: definitionstring}``.
1368 1368 '''
1369 1369 if user:
1370 1370 m = revset.matchany(self.ui, specs,
1371 1371 lookup=revset.lookupfn(self),
1372 1372 localalias=localalias)
1373 1373 else:
1374 1374 m = revset.matchany(None, specs, localalias=localalias)
1375 1375 return m(self)
1376 1376
1377 1377 def url(self):
1378 1378 return 'file:' + self.root
1379 1379
1380 1380 def hook(self, name, throw=False, **args):
1381 1381 """Call a hook, passing this repo instance.
1382 1382
1383 1383 This a convenience method to aid invoking hooks. Extensions likely
1384 1384 won't call this unless they have registered a custom hook or are
1385 1385 replacing code that is expected to call a hook.
1386 1386 """
1387 1387 return hook.hook(self.ui, self, name, throw, **args)
1388 1388
1389 1389 @filteredpropertycache
1390 1390 def _tagscache(self):
1391 1391 '''Returns a tagscache object that contains various tags related
1392 1392 caches.'''
1393 1393
1394 1394 # This simplifies its cache management by having one decorated
1395 1395 # function (this one) and the rest simply fetch things from it.
1396 1396 class tagscache(object):
1397 1397 def __init__(self):
1398 1398 # These two define the set of tags for this repository. tags
1399 1399 # maps tag name to node; tagtypes maps tag name to 'global' or
1400 1400 # 'local'. (Global tags are defined by .hgtags across all
1401 1401 # heads, and local tags are defined in .hg/localtags.)
1402 1402 # They constitute the in-memory cache of tags.
1403 1403 self.tags = self.tagtypes = None
1404 1404
1405 1405 self.nodetagscache = self.tagslist = None
1406 1406
1407 1407 cache = tagscache()
1408 1408 cache.tags, cache.tagtypes = self._findtags()
1409 1409
1410 1410 return cache
1411 1411
1412 1412 def tags(self):
1413 1413 '''return a mapping of tag to node'''
1414 1414 t = {}
1415 1415 if self.changelog.filteredrevs:
1416 1416 tags, tt = self._findtags()
1417 1417 else:
1418 1418 tags = self._tagscache.tags
1419 1419 rev = self.changelog.rev
1420 1420 for k, v in tags.iteritems():
1421 1421 try:
1422 1422 # ignore tags to unknown nodes
1423 1423 rev(v)
1424 1424 t[k] = v
1425 1425 except (error.LookupError, ValueError):
1426 1426 pass
1427 1427 return t
1428 1428
1429 1429 def _findtags(self):
1430 1430 '''Do the hard work of finding tags. Return a pair of dicts
1431 1431 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1432 1432 maps tag name to a string like \'global\' or \'local\'.
1433 1433 Subclasses or extensions are free to add their own tags, but
1434 1434 should be aware that the returned dicts will be retained for the
1435 1435 duration of the localrepo object.'''
1436 1436
1437 1437 # XXX what tagtype should subclasses/extensions use? Currently
1438 1438 # mq and bookmarks add tags, but do not set the tagtype at all.
1439 1439 # Should each extension invent its own tag type? Should there
1440 1440 # be one tagtype for all such "virtual" tags? Or is the status
1441 1441 # quo fine?
1442 1442
1443 1443
1444 1444 # map tag name to (node, hist)
1445 1445 alltags = tagsmod.findglobaltags(self.ui, self)
1446 1446 # map tag name to tag type
1447 1447 tagtypes = dict((tag, 'global') for tag in alltags)
1448 1448
1449 1449 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1450 1450
1451 1451 # Build the return dicts. Have to re-encode tag names because
1452 1452 # the tags module always uses UTF-8 (in order not to lose info
1453 1453 # writing to the cache), but the rest of Mercurial wants them in
1454 1454 # local encoding.
1455 1455 tags = {}
1456 1456 for (name, (node, hist)) in alltags.iteritems():
1457 1457 if node != nullid:
1458 1458 tags[encoding.tolocal(name)] = node
1459 1459 tags['tip'] = self.changelog.tip()
1460 1460 tagtypes = dict([(encoding.tolocal(name), value)
1461 1461 for (name, value) in tagtypes.iteritems()])
1462 1462 return (tags, tagtypes)
1463 1463
1464 1464 def tagtype(self, tagname):
1465 1465 '''
1466 1466 return the type of the given tag. result can be:
1467 1467
1468 1468 'local' : a local tag
1469 1469 'global' : a global tag
1470 1470 None : tag does not exist
1471 1471 '''
1472 1472
1473 1473 return self._tagscache.tagtypes.get(tagname)
1474 1474
1475 1475 def tagslist(self):
1476 1476 '''return a list of tags ordered by revision'''
1477 1477 if not self._tagscache.tagslist:
1478 1478 l = []
1479 1479 for t, n in self.tags().iteritems():
1480 1480 l.append((self.changelog.rev(n), t, n))
1481 1481 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1482 1482
1483 1483 return self._tagscache.tagslist
1484 1484
1485 1485 def nodetags(self, node):
1486 1486 '''return the tags associated with a node'''
1487 1487 if not self._tagscache.nodetagscache:
1488 1488 nodetagscache = {}
1489 1489 for t, n in self._tagscache.tags.iteritems():
1490 1490 nodetagscache.setdefault(n, []).append(t)
1491 1491 for tags in nodetagscache.itervalues():
1492 1492 tags.sort()
1493 1493 self._tagscache.nodetagscache = nodetagscache
1494 1494 return self._tagscache.nodetagscache.get(node, [])
1495 1495
1496 1496 def nodebookmarks(self, node):
1497 1497 """return the list of bookmarks pointing to the specified node"""
1498 1498 return self._bookmarks.names(node)
1499 1499
1500 1500 def branchmap(self):
1501 1501 '''returns a dictionary {branch: [branchheads]} with branchheads
1502 1502 ordered by increasing revision number'''
1503 1503 branchmap.updatecache(self)
1504 1504 return self._branchcaches[self.filtername]
1505 1505
1506 1506 @unfilteredmethod
1507 1507 def revbranchcache(self):
1508 1508 if not self._revbranchcache:
1509 1509 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1510 1510 return self._revbranchcache
1511 1511
1512 1512 def branchtip(self, branch, ignoremissing=False):
1513 1513 '''return the tip node for a given branch
1514 1514
1515 1515 If ignoremissing is True, then this method will not raise an error.
1516 1516 This is helpful for callers that only expect None for a missing branch
1517 1517 (e.g. namespace).
1518 1518
1519 1519 '''
1520 1520 try:
1521 1521 return self.branchmap().branchtip(branch)
1522 1522 except KeyError:
1523 1523 if not ignoremissing:
1524 1524 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1525 1525 else:
1526 1526 pass
1527 1527
1528 1528 def lookup(self, key):
1529 1529 return scmutil.revsymbol(self, key).node()
1530 1530
1531 1531 def lookupbranch(self, key):
1532 1532 if key in self.branchmap():
1533 1533 return key
1534 1534
1535 1535 return scmutil.revsymbol(self, key).branch()
1536 1536
1537 1537 def known(self, nodes):
1538 1538 cl = self.changelog
1539 1539 nm = cl.nodemap
1540 1540 filtered = cl.filteredrevs
1541 1541 result = []
1542 1542 for n in nodes:
1543 1543 r = nm.get(n)
1544 1544 resp = not (r is None or r in filtered)
1545 1545 result.append(resp)
1546 1546 return result
1547 1547
1548 1548 def local(self):
1549 1549 return self
1550 1550
1551 1551 def publishing(self):
1552 1552 # it's safe (and desirable) to trust the publish flag unconditionally
1553 1553 # so that we don't finalize changes shared between users via ssh or nfs
1554 1554 return self.ui.configbool('phases', 'publish', untrusted=True)
1555 1555
1556 1556 def cancopy(self):
1557 1557 # so statichttprepo's override of local() works
1558 1558 if not self.local():
1559 1559 return False
1560 1560 if not self.publishing():
1561 1561 return True
1562 1562 # if publishing we can't copy if there is filtered content
1563 1563 return not self.filtered('visible').changelog.filteredrevs
1564 1564
1565 1565 def shared(self):
1566 1566 '''the type of shared repository (None if not shared)'''
1567 1567 if self.sharedpath != self.path:
1568 1568 return 'store'
1569 1569 return None
1570 1570
1571 1571 def wjoin(self, f, *insidef):
1572 1572 return self.vfs.reljoin(self.root, f, *insidef)
1573 1573
1574 1574 def setparents(self, p1, p2=nullid):
1575 1575 with self.dirstate.parentchange():
1576 1576 copies = self.dirstate.setparents(p1, p2)
1577 1577 pctx = self[p1]
1578 1578 if copies:
1579 1579 # Adjust copy records, the dirstate cannot do it, it
1580 1580 # requires access to parents manifests. Preserve them
1581 1581 # only for entries added to first parent.
1582 1582 for f in copies:
1583 1583 if f not in pctx and copies[f] in pctx:
1584 1584 self.dirstate.copy(copies[f], f)
1585 1585 if p2 == nullid:
1586 1586 for f, s in sorted(self.dirstate.copies().items()):
1587 1587 if f not in pctx and s not in pctx:
1588 1588 self.dirstate.copy(None, f)
1589 1589
1590 1590 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1591 1591 """changeid must be a changeset revision, if specified.
1592 1592 fileid can be a file revision or node."""
1593 1593 return context.filectx(self, path, changeid, fileid,
1594 1594 changectx=changectx)
1595 1595
1596 1596 def getcwd(self):
1597 1597 return self.dirstate.getcwd()
1598 1598
1599 1599 def pathto(self, f, cwd=None):
1600 1600 return self.dirstate.pathto(f, cwd)
1601 1601
1602 1602 def _loadfilter(self, filter):
1603 1603 if filter not in self._filterpats:
1604 1604 l = []
1605 1605 for pat, cmd in self.ui.configitems(filter):
1606 1606 if cmd == '!':
1607 1607 continue
1608 1608 mf = matchmod.match(self.root, '', [pat])
1609 1609 fn = None
1610 1610 params = cmd
1611 1611 for name, filterfn in self._datafilters.iteritems():
1612 1612 if cmd.startswith(name):
1613 1613 fn = filterfn
1614 1614 params = cmd[len(name):].lstrip()
1615 1615 break
1616 1616 if not fn:
1617 1617 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1618 1618 # Wrap old filters not supporting keyword arguments
1619 1619 if not pycompat.getargspec(fn)[2]:
1620 1620 oldfn = fn
1621 1621 fn = lambda s, c, **kwargs: oldfn(s, c)
1622 1622 l.append((mf, fn, params))
1623 1623 self._filterpats[filter] = l
1624 1624 return self._filterpats[filter]
1625 1625
1626 1626 def _filter(self, filterpats, filename, data):
1627 1627 for mf, fn, cmd in filterpats:
1628 1628 if mf(filename):
1629 1629 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1630 1630 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1631 1631 break
1632 1632
1633 1633 return data
1634 1634
1635 1635 @unfilteredpropertycache
1636 1636 def _encodefilterpats(self):
1637 1637 return self._loadfilter('encode')
1638 1638
1639 1639 @unfilteredpropertycache
1640 1640 def _decodefilterpats(self):
1641 1641 return self._loadfilter('decode')
1642 1642
1643 1643 def adddatafilter(self, name, filter):
1644 1644 self._datafilters[name] = filter
1645 1645
1646 1646 def wread(self, filename):
1647 1647 if self.wvfs.islink(filename):
1648 1648 data = self.wvfs.readlink(filename)
1649 1649 else:
1650 1650 data = self.wvfs.read(filename)
1651 1651 return self._filter(self._encodefilterpats, filename, data)
1652 1652
1653 1653 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1654 1654 """write ``data`` into ``filename`` in the working directory
1655 1655
1656 1656 This returns length of written (maybe decoded) data.
1657 1657 """
1658 1658 data = self._filter(self._decodefilterpats, filename, data)
1659 1659 if 'l' in flags:
1660 1660 self.wvfs.symlink(data, filename)
1661 1661 else:
1662 1662 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1663 1663 **kwargs)
1664 1664 if 'x' in flags:
1665 1665 self.wvfs.setflags(filename, False, True)
1666 1666 else:
1667 1667 self.wvfs.setflags(filename, False, False)
1668 1668 return len(data)
1669 1669
1670 1670 def wwritedata(self, filename, data):
1671 1671 return self._filter(self._decodefilterpats, filename, data)
1672 1672
1673 1673 def currenttransaction(self):
1674 1674 """return the current transaction or None if non exists"""
1675 1675 if self._transref:
1676 1676 tr = self._transref()
1677 1677 else:
1678 1678 tr = None
1679 1679
1680 1680 if tr and tr.running():
1681 1681 return tr
1682 1682 return None
1683 1683
1684 1684 def transaction(self, desc, report=None):
1685 1685 if (self.ui.configbool('devel', 'all-warnings')
1686 1686 or self.ui.configbool('devel', 'check-locks')):
1687 1687 if self._currentlock(self._lockref) is None:
1688 1688 raise error.ProgrammingError('transaction requires locking')
1689 1689 tr = self.currenttransaction()
1690 1690 if tr is not None:
1691 1691 return tr.nest(name=desc)
1692 1692
1693 1693 # abort here if the journal already exists
1694 1694 if self.svfs.exists("journal"):
1695 1695 raise error.RepoError(
1696 1696 _("abandoned transaction found"),
1697 1697 hint=_("run 'hg recover' to clean up transaction"))
1698 1698
1699 1699 idbase = "%.40f#%f" % (random.random(), time.time())
1700 1700 ha = hex(hashlib.sha1(idbase).digest())
1701 1701 txnid = 'TXN:' + ha
1702 1702 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1703 1703
1704 1704 self._writejournal(desc)
1705 1705 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1706 1706 if report:
1707 1707 rp = report
1708 1708 else:
1709 1709 rp = self.ui.warn
1710 1710 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1711 1711 # we must avoid cyclic reference between repo and transaction.
1712 1712 reporef = weakref.ref(self)
1713 1713 # Code to track tag movement
1714 1714 #
1715 1715 # Since tags are all handled as file content, it is actually quite hard
1716 1716 # to track these movement from a code perspective. So we fallback to a
1717 1717 # tracking at the repository level. One could envision to track changes
1718 1718 # to the '.hgtags' file through changegroup apply but that fails to
1719 1719 # cope with case where transaction expose new heads without changegroup
1720 1720 # being involved (eg: phase movement).
1721 1721 #
1722 1722 # For now, We gate the feature behind a flag since this likely comes
1723 1723 # with performance impacts. The current code run more often than needed
1724 1724 # and do not use caches as much as it could. The current focus is on
1725 1725 # the behavior of the feature so we disable it by default. The flag
1726 1726 # will be removed when we are happy with the performance impact.
1727 1727 #
1728 1728 # Once this feature is no longer experimental move the following
1729 1729 # documentation to the appropriate help section:
1730 1730 #
1731 1731 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1732 1732 # tags (new or changed or deleted tags). In addition the details of
1733 1733 # these changes are made available in a file at:
1734 1734 # ``REPOROOT/.hg/changes/tags.changes``.
1735 1735 # Make sure you check for HG_TAG_MOVED before reading that file as it
1736 1736 # might exist from a previous transaction even if no tag were touched
1737 1737 # in this one. Changes are recorded in a line base format::
1738 1738 #
1739 1739 # <action> <hex-node> <tag-name>\n
1740 1740 #
1741 1741 # Actions are defined as follow:
1742 1742 # "-R": tag is removed,
1743 1743 # "+A": tag is added,
1744 1744 # "-M": tag is moved (old value),
1745 1745 # "+M": tag is moved (new value),
1746 1746 tracktags = lambda x: None
1747 1747 # experimental config: experimental.hook-track-tags
1748 1748 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1749 1749 if desc != 'strip' and shouldtracktags:
1750 1750 oldheads = self.changelog.headrevs()
1751 1751 def tracktags(tr2):
1752 1752 repo = reporef()
1753 1753 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1754 1754 newheads = repo.changelog.headrevs()
1755 1755 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1756 1756 # notes: we compare lists here.
1757 1757 # As we do it only once buiding set would not be cheaper
1758 1758 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1759 1759 if changes:
1760 1760 tr2.hookargs['tag_moved'] = '1'
1761 1761 with repo.vfs('changes/tags.changes', 'w',
1762 1762 atomictemp=True) as changesfile:
1763 1763 # note: we do not register the file to the transaction
1764 1764 # because we needs it to still exist on the transaction
1765 1765 # is close (for txnclose hooks)
1766 1766 tagsmod.writediff(changesfile, changes)
1767 1767 def validate(tr2):
1768 1768 """will run pre-closing hooks"""
1769 1769 # XXX the transaction API is a bit lacking here so we take a hacky
1770 1770 # path for now
1771 1771 #
1772 1772 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1773 1773 # dict is copied before these run. In addition we needs the data
1774 1774 # available to in memory hooks too.
1775 1775 #
1776 1776 # Moreover, we also need to make sure this runs before txnclose
1777 1777 # hooks and there is no "pending" mechanism that would execute
1778 1778 # logic only if hooks are about to run.
1779 1779 #
1780 1780 # Fixing this limitation of the transaction is also needed to track
1781 1781 # other families of changes (bookmarks, phases, obsolescence).
1782 1782 #
1783 1783 # This will have to be fixed before we remove the experimental
1784 1784 # gating.
1785 1785 tracktags(tr2)
1786 1786 repo = reporef()
1787 1787 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1788 1788 scmutil.enforcesinglehead(repo, tr2, desc)
1789 1789 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1790 1790 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1791 1791 args = tr.hookargs.copy()
1792 1792 args.update(bookmarks.preparehookargs(name, old, new))
1793 1793 repo.hook('pretxnclose-bookmark', throw=True,
1794 1794 txnname=desc,
1795 1795 **pycompat.strkwargs(args))
1796 1796 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1797 1797 cl = repo.unfiltered().changelog
1798 1798 for rev, (old, new) in tr.changes['phases'].items():
1799 1799 args = tr.hookargs.copy()
1800 1800 node = hex(cl.node(rev))
1801 1801 args.update(phases.preparehookargs(node, old, new))
1802 1802 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1803 1803 **pycompat.strkwargs(args))
1804 1804
1805 1805 repo.hook('pretxnclose', throw=True,
1806 1806 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1807 1807 def releasefn(tr, success):
1808 1808 repo = reporef()
1809 1809 if success:
1810 1810 # this should be explicitly invoked here, because
1811 1811 # in-memory changes aren't written out at closing
1812 1812 # transaction, if tr.addfilegenerator (via
1813 1813 # dirstate.write or so) isn't invoked while
1814 1814 # transaction running
1815 1815 repo.dirstate.write(None)
1816 1816 else:
1817 1817 # discard all changes (including ones already written
1818 1818 # out) in this transaction
1819 1819 narrowspec.restorebackup(self, 'journal.narrowspec')
1820 1820 repo.dirstate.restorebackup(None, 'journal.dirstate')
1821 1821
1822 1822 repo.invalidate(clearfilecache=True)
1823 1823
1824 1824 tr = transaction.transaction(rp, self.svfs, vfsmap,
1825 1825 "journal",
1826 1826 "undo",
1827 1827 aftertrans(renames),
1828 1828 self.store.createmode,
1829 1829 validator=validate,
1830 1830 releasefn=releasefn,
1831 1831 checkambigfiles=_cachedfiles,
1832 1832 name=desc)
1833 1833 tr.changes['origrepolen'] = len(self)
1834 1834 tr.changes['obsmarkers'] = set()
1835 1835 tr.changes['phases'] = {}
1836 1836 tr.changes['bookmarks'] = {}
1837 1837
1838 1838 tr.hookargs['txnid'] = txnid
1839 1839 # note: writing the fncache only during finalize mean that the file is
1840 1840 # outdated when running hooks. As fncache is used for streaming clone,
1841 1841 # this is not expected to break anything that happen during the hooks.
1842 1842 tr.addfinalize('flush-fncache', self.store.write)
1843 1843 def txnclosehook(tr2):
1844 1844 """To be run if transaction is successful, will schedule a hook run
1845 1845 """
1846 1846 # Don't reference tr2 in hook() so we don't hold a reference.
1847 1847 # This reduces memory consumption when there are multiple
1848 1848 # transactions per lock. This can likely go away if issue5045
1849 1849 # fixes the function accumulation.
1850 1850 hookargs = tr2.hookargs
1851 1851
1852 1852 def hookfunc():
1853 1853 repo = reporef()
1854 1854 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1855 1855 bmchanges = sorted(tr.changes['bookmarks'].items())
1856 1856 for name, (old, new) in bmchanges:
1857 1857 args = tr.hookargs.copy()
1858 1858 args.update(bookmarks.preparehookargs(name, old, new))
1859 1859 repo.hook('txnclose-bookmark', throw=False,
1860 1860 txnname=desc, **pycompat.strkwargs(args))
1861 1861
1862 1862 if hook.hashook(repo.ui, 'txnclose-phase'):
1863 1863 cl = repo.unfiltered().changelog
1864 1864 phasemv = sorted(tr.changes['phases'].items())
1865 1865 for rev, (old, new) in phasemv:
1866 1866 args = tr.hookargs.copy()
1867 1867 node = hex(cl.node(rev))
1868 1868 args.update(phases.preparehookargs(node, old, new))
1869 1869 repo.hook('txnclose-phase', throw=False, txnname=desc,
1870 1870 **pycompat.strkwargs(args))
1871 1871
1872 1872 repo.hook('txnclose', throw=False, txnname=desc,
1873 1873 **pycompat.strkwargs(hookargs))
1874 1874 reporef()._afterlock(hookfunc)
1875 1875 tr.addfinalize('txnclose-hook', txnclosehook)
1876 1876 # Include a leading "-" to make it happen before the transaction summary
1877 1877 # reports registered via scmutil.registersummarycallback() whose names
1878 1878 # are 00-txnreport etc. That way, the caches will be warm when the
1879 1879 # callbacks run.
1880 1880 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1881 1881 def txnaborthook(tr2):
1882 1882 """To be run if transaction is aborted
1883 1883 """
1884 1884 reporef().hook('txnabort', throw=False, txnname=desc,
1885 1885 **pycompat.strkwargs(tr2.hookargs))
1886 1886 tr.addabort('txnabort-hook', txnaborthook)
1887 1887 # avoid eager cache invalidation. in-memory data should be identical
1888 1888 # to stored data if transaction has no error.
1889 1889 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1890 1890 self._transref = weakref.ref(tr)
1891 1891 scmutil.registersummarycallback(self, tr, desc)
1892 1892 return tr
1893 1893
1894 1894 def _journalfiles(self):
1895 1895 return ((self.svfs, 'journal'),
1896 1896 (self.vfs, 'journal.dirstate'),
1897 1897 (self.vfs, 'journal.branch'),
1898 1898 (self.vfs, 'journal.desc'),
1899 1899 (self.vfs, 'journal.bookmarks'),
1900 1900 (self.svfs, 'journal.phaseroots'))
1901 1901
1902 1902 def undofiles(self):
1903 1903 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1904 1904
1905 1905 @unfilteredmethod
1906 1906 def _writejournal(self, desc):
1907 1907 self.dirstate.savebackup(None, 'journal.dirstate')
1908 1908 narrowspec.savebackup(self, 'journal.narrowspec')
1909 1909 self.vfs.write("journal.branch",
1910 1910 encoding.fromlocal(self.dirstate.branch()))
1911 1911 self.vfs.write("journal.desc",
1912 1912 "%d\n%s\n" % (len(self), desc))
1913 1913 self.vfs.write("journal.bookmarks",
1914 1914 self.vfs.tryread("bookmarks"))
1915 1915 self.svfs.write("journal.phaseroots",
1916 1916 self.svfs.tryread("phaseroots"))
1917 1917
1918 1918 def recover(self):
1919 1919 with self.lock():
1920 1920 if self.svfs.exists("journal"):
1921 1921 self.ui.status(_("rolling back interrupted transaction\n"))
1922 1922 vfsmap = {'': self.svfs,
1923 1923 'plain': self.vfs,}
1924 1924 transaction.rollback(self.svfs, vfsmap, "journal",
1925 1925 self.ui.warn,
1926 1926 checkambigfiles=_cachedfiles)
1927 1927 self.invalidate()
1928 1928 return True
1929 1929 else:
1930 1930 self.ui.warn(_("no interrupted transaction available\n"))
1931 1931 return False
1932 1932
1933 1933 def rollback(self, dryrun=False, force=False):
1934 1934 wlock = lock = dsguard = None
1935 1935 try:
1936 1936 wlock = self.wlock()
1937 1937 lock = self.lock()
1938 1938 if self.svfs.exists("undo"):
1939 1939 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1940 1940
1941 1941 return self._rollback(dryrun, force, dsguard)
1942 1942 else:
1943 1943 self.ui.warn(_("no rollback information available\n"))
1944 1944 return 1
1945 1945 finally:
1946 1946 release(dsguard, lock, wlock)
1947 1947
1948 1948 @unfilteredmethod # Until we get smarter cache management
1949 1949 def _rollback(self, dryrun, force, dsguard):
1950 1950 ui = self.ui
1951 1951 try:
1952 1952 args = self.vfs.read('undo.desc').splitlines()
1953 1953 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1954 1954 if len(args) >= 3:
1955 1955 detail = args[2]
1956 1956 oldtip = oldlen - 1
1957 1957
1958 1958 if detail and ui.verbose:
1959 1959 msg = (_('repository tip rolled back to revision %d'
1960 1960 ' (undo %s: %s)\n')
1961 1961 % (oldtip, desc, detail))
1962 1962 else:
1963 1963 msg = (_('repository tip rolled back to revision %d'
1964 1964 ' (undo %s)\n')
1965 1965 % (oldtip, desc))
1966 1966 except IOError:
1967 1967 msg = _('rolling back unknown transaction\n')
1968 1968 desc = None
1969 1969
1970 1970 if not force and self['.'] != self['tip'] and desc == 'commit':
1971 1971 raise error.Abort(
1972 1972 _('rollback of last commit while not checked out '
1973 1973 'may lose data'), hint=_('use -f to force'))
1974 1974
1975 1975 ui.status(msg)
1976 1976 if dryrun:
1977 1977 return 0
1978 1978
1979 1979 parents = self.dirstate.parents()
1980 1980 self.destroying()
1981 1981 vfsmap = {'plain': self.vfs, '': self.svfs}
1982 1982 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1983 1983 checkambigfiles=_cachedfiles)
1984 1984 if self.vfs.exists('undo.bookmarks'):
1985 1985 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1986 1986 if self.svfs.exists('undo.phaseroots'):
1987 1987 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1988 1988 self.invalidate()
1989 1989
1990 1990 parentgone = (parents[0] not in self.changelog.nodemap or
1991 1991 parents[1] not in self.changelog.nodemap)
1992 1992 if parentgone:
1993 1993 # prevent dirstateguard from overwriting already restored one
1994 1994 dsguard.close()
1995 1995
1996 1996 narrowspec.restorebackup(self, 'undo.narrowspec')
1997 1997 self.dirstate.restorebackup(None, 'undo.dirstate')
1998 1998 try:
1999 1999 branch = self.vfs.read('undo.branch')
2000 2000 self.dirstate.setbranch(encoding.tolocal(branch))
2001 2001 except IOError:
2002 2002 ui.warn(_('named branch could not be reset: '
2003 2003 'current branch is still \'%s\'\n')
2004 2004 % self.dirstate.branch())
2005 2005
2006 2006 parents = tuple([p.rev() for p in self[None].parents()])
2007 2007 if len(parents) > 1:
2008 2008 ui.status(_('working directory now based on '
2009 2009 'revisions %d and %d\n') % parents)
2010 2010 else:
2011 2011 ui.status(_('working directory now based on '
2012 2012 'revision %d\n') % parents)
2013 2013 mergemod.mergestate.clean(self, self['.'].node())
2014 2014
2015 2015 # TODO: if we know which new heads may result from this rollback, pass
2016 2016 # them to destroy(), which will prevent the branchhead cache from being
2017 2017 # invalidated.
2018 2018 self.destroyed()
2019 2019 return 0
2020 2020
2021 2021 def _buildcacheupdater(self, newtransaction):
2022 2022 """called during transaction to build the callback updating cache
2023 2023
2024 2024 Lives on the repository to help extension who might want to augment
2025 2025 this logic. For this purpose, the created transaction is passed to the
2026 2026 method.
2027 2027 """
2028 2028 # we must avoid cyclic reference between repo and transaction.
2029 2029 reporef = weakref.ref(self)
2030 2030 def updater(tr):
2031 2031 repo = reporef()
2032 2032 repo.updatecaches(tr)
2033 2033 return updater
2034 2034
2035 2035 @unfilteredmethod
2036 2036 def updatecaches(self, tr=None, full=False):
2037 2037 """warm appropriate caches
2038 2038
2039 2039 If this function is called after a transaction closed. The transaction
2040 2040 will be available in the 'tr' argument. This can be used to selectively
2041 2041 update caches relevant to the changes in that transaction.
2042 2042
2043 2043 If 'full' is set, make sure all caches the function knows about have
2044 2044 up-to-date data. Even the ones usually loaded more lazily.
2045 2045 """
2046 2046 if tr is not None and tr.hookargs.get('source') == 'strip':
2047 2047 # During strip, many caches are invalid but
2048 2048 # later call to `destroyed` will refresh them.
2049 2049 return
2050 2050
2051 2051 if tr is None or tr.changes['origrepolen'] < len(self):
2052 2052 # updating the unfiltered branchmap should refresh all the others,
2053 2053 self.ui.debug('updating the branch cache\n')
2054 2054 branchmap.updatecache(self.filtered('served'))
2055 2055
2056 2056 if full:
2057 2057 rbc = self.revbranchcache()
2058 2058 for r in self.changelog:
2059 2059 rbc.branchinfo(r)
2060 2060 rbc.write()
2061 2061
2062 2062 # ensure the working copy parents are in the manifestfulltextcache
2063 2063 for ctx in self['.'].parents():
2064 2064 ctx.manifest() # accessing the manifest is enough
2065 2065
2066 2066 def invalidatecaches(self):
2067 2067
2068 2068 if r'_tagscache' in vars(self):
2069 2069 # can't use delattr on proxy
2070 2070 del self.__dict__[r'_tagscache']
2071 2071
2072 2072 self.unfiltered()._branchcaches.clear()
2073 2073 self.invalidatevolatilesets()
2074 2074 self._sparsesignaturecache.clear()
2075 2075
2076 2076 def invalidatevolatilesets(self):
2077 2077 self.filteredrevcache.clear()
2078 2078 obsolete.clearobscaches(self)
2079 2079
2080 2080 def invalidatedirstate(self):
2081 2081 '''Invalidates the dirstate, causing the next call to dirstate
2082 2082 to check if it was modified since the last time it was read,
2083 2083 rereading it if it has.
2084 2084
2085 2085 This is different to dirstate.invalidate() that it doesn't always
2086 2086 rereads the dirstate. Use dirstate.invalidate() if you want to
2087 2087 explicitly read the dirstate again (i.e. restoring it to a previous
2088 2088 known good state).'''
2089 2089 if hasunfilteredcache(self, r'dirstate'):
2090 2090 for k in self.dirstate._filecache:
2091 2091 try:
2092 2092 delattr(self.dirstate, k)
2093 2093 except AttributeError:
2094 2094 pass
2095 2095 delattr(self.unfiltered(), r'dirstate')
2096 2096
2097 2097 def invalidate(self, clearfilecache=False):
2098 2098 '''Invalidates both store and non-store parts other than dirstate
2099 2099
2100 2100 If a transaction is running, invalidation of store is omitted,
2101 2101 because discarding in-memory changes might cause inconsistency
2102 2102 (e.g. incomplete fncache causes unintentional failure, but
2103 2103 redundant one doesn't).
2104 2104 '''
2105 2105 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2106 2106 for k in list(self._filecache.keys()):
2107 2107 # dirstate is invalidated separately in invalidatedirstate()
2108 2108 if k == 'dirstate':
2109 2109 continue
2110 2110 if (k == 'changelog' and
2111 2111 self.currenttransaction() and
2112 2112 self.changelog._delayed):
2113 2113 # The changelog object may store unwritten revisions. We don't
2114 2114 # want to lose them.
2115 2115 # TODO: Solve the problem instead of working around it.
2116 2116 continue
2117 2117
2118 2118 if clearfilecache:
2119 2119 del self._filecache[k]
2120 2120 try:
2121 2121 delattr(unfiltered, k)
2122 2122 except AttributeError:
2123 2123 pass
2124 2124 self.invalidatecaches()
2125 2125 if not self.currenttransaction():
2126 2126 # TODO: Changing contents of store outside transaction
2127 2127 # causes inconsistency. We should make in-memory store
2128 2128 # changes detectable, and abort if changed.
2129 2129 self.store.invalidatecaches()
2130 2130
2131 2131 def invalidateall(self):
2132 2132 '''Fully invalidates both store and non-store parts, causing the
2133 2133 subsequent operation to reread any outside changes.'''
2134 2134 # extension should hook this to invalidate its caches
2135 2135 self.invalidate()
2136 2136 self.invalidatedirstate()
2137 2137
2138 2138 @unfilteredmethod
2139 2139 def _refreshfilecachestats(self, tr):
2140 2140 """Reload stats of cached files so that they are flagged as valid"""
2141 2141 for k, ce in self._filecache.items():
2142 2142 k = pycompat.sysstr(k)
2143 2143 if k == r'dirstate' or k not in self.__dict__:
2144 2144 continue
2145 2145 ce.refresh()
2146 2146
2147 2147 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2148 2148 inheritchecker=None, parentenvvar=None):
2149 2149 parentlock = None
2150 2150 # the contents of parentenvvar are used by the underlying lock to
2151 2151 # determine whether it can be inherited
2152 2152 if parentenvvar is not None:
2153 2153 parentlock = encoding.environ.get(parentenvvar)
2154 2154
2155 2155 timeout = 0
2156 2156 warntimeout = 0
2157 2157 if wait:
2158 2158 timeout = self.ui.configint("ui", "timeout")
2159 2159 warntimeout = self.ui.configint("ui", "timeout.warn")
2160 2160 # internal config: ui.signal-safe-lock
2161 2161 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2162 2162
2163 2163 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2164 2164 releasefn=releasefn,
2165 2165 acquirefn=acquirefn, desc=desc,
2166 2166 inheritchecker=inheritchecker,
2167 2167 parentlock=parentlock,
2168 2168 signalsafe=signalsafe)
2169 2169 return l
2170 2170
2171 2171 def _afterlock(self, callback):
2172 2172 """add a callback to be run when the repository is fully unlocked
2173 2173
2174 2174 The callback will be executed when the outermost lock is released
2175 2175 (with wlock being higher level than 'lock')."""
2176 2176 for ref in (self._wlockref, self._lockref):
2177 2177 l = ref and ref()
2178 2178 if l and l.held:
2179 2179 l.postrelease.append(callback)
2180 2180 break
2181 2181 else: # no lock have been found.
2182 2182 callback()
2183 2183
2184 2184 def lock(self, wait=True):
2185 2185 '''Lock the repository store (.hg/store) and return a weak reference
2186 2186 to the lock. Use this before modifying the store (e.g. committing or
2187 2187 stripping). If you are opening a transaction, get a lock as well.)
2188 2188
2189 2189 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2190 2190 'wlock' first to avoid a dead-lock hazard.'''
2191 2191 l = self._currentlock(self._lockref)
2192 2192 if l is not None:
2193 2193 l.lock()
2194 2194 return l
2195 2195
2196 2196 l = self._lock(self.svfs, "lock", wait, None,
2197 2197 self.invalidate, _('repository %s') % self.origroot)
2198 2198 self._lockref = weakref.ref(l)
2199 2199 return l
2200 2200
2201 2201 def _wlockchecktransaction(self):
2202 2202 if self.currenttransaction() is not None:
2203 2203 raise error.LockInheritanceContractViolation(
2204 2204 'wlock cannot be inherited in the middle of a transaction')
2205 2205
2206 2206 def wlock(self, wait=True):
2207 2207 '''Lock the non-store parts of the repository (everything under
2208 2208 .hg except .hg/store) and return a weak reference to the lock.
2209 2209
2210 2210 Use this before modifying files in .hg.
2211 2211
2212 2212 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2213 2213 'wlock' first to avoid a dead-lock hazard.'''
2214 2214 l = self._wlockref and self._wlockref()
2215 2215 if l is not None and l.held:
2216 2216 l.lock()
2217 2217 return l
2218 2218
2219 2219 # We do not need to check for non-waiting lock acquisition. Such
2220 2220 # acquisition would not cause dead-lock as they would just fail.
2221 2221 if wait and (self.ui.configbool('devel', 'all-warnings')
2222 2222 or self.ui.configbool('devel', 'check-locks')):
2223 2223 if self._currentlock(self._lockref) is not None:
2224 2224 self.ui.develwarn('"wlock" acquired after "lock"')
2225 2225
2226 2226 def unlock():
2227 2227 if self.dirstate.pendingparentchange():
2228 2228 self.dirstate.invalidate()
2229 2229 else:
2230 2230 self.dirstate.write(None)
2231 2231
2232 2232 self._filecache['dirstate'].refresh()
2233 2233
2234 2234 l = self._lock(self.vfs, "wlock", wait, unlock,
2235 2235 self.invalidatedirstate, _('working directory of %s') %
2236 2236 self.origroot,
2237 2237 inheritchecker=self._wlockchecktransaction,
2238 2238 parentenvvar='HG_WLOCK_LOCKER')
2239 2239 self._wlockref = weakref.ref(l)
2240 2240 return l
2241 2241
2242 2242 def _currentlock(self, lockref):
2243 2243 """Returns the lock if it's held, or None if it's not."""
2244 2244 if lockref is None:
2245 2245 return None
2246 2246 l = lockref()
2247 2247 if l is None or not l.held:
2248 2248 return None
2249 2249 return l
2250 2250
2251 2251 def currentwlock(self):
2252 2252 """Returns the wlock if it's held, or None if it's not."""
2253 2253 return self._currentlock(self._wlockref)
2254 2254
2255 2255 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
2256 2256 """
2257 2257 commit an individual file as part of a larger transaction
2258 2258 """
2259 2259
2260 2260 fname = fctx.path()
2261 2261 fparent1 = manifest1.get(fname, nullid)
2262 2262 fparent2 = manifest2.get(fname, nullid)
2263 2263 if isinstance(fctx, context.filectx):
2264 2264 node = fctx.filenode()
2265 2265 if node in [fparent1, fparent2]:
2266 2266 self.ui.debug('reusing %s filelog entry\n' % fname)
2267 2267 if manifest1.flags(fname) != fctx.flags():
2268 2268 changelist.append(fname)
2269 2269 return node
2270 2270
2271 2271 flog = self.file(fname)
2272 2272 meta = {}
2273 2273 copy = fctx.renamed()
2274 2274 if copy and copy[0] != fname:
2275 2275 # Mark the new revision of this file as a copy of another
2276 2276 # file. This copy data will effectively act as a parent
2277 2277 # of this new revision. If this is a merge, the first
2278 2278 # parent will be the nullid (meaning "look up the copy data")
2279 2279 # and the second one will be the other parent. For example:
2280 2280 #
2281 2281 # 0 --- 1 --- 3 rev1 changes file foo
2282 2282 # \ / rev2 renames foo to bar and changes it
2283 2283 # \- 2 -/ rev3 should have bar with all changes and
2284 2284 # should record that bar descends from
2285 2285 # bar in rev2 and foo in rev1
2286 2286 #
2287 2287 # this allows this merge to succeed:
2288 2288 #
2289 2289 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2290 2290 # \ / merging rev3 and rev4 should use bar@rev2
2291 2291 # \- 2 --- 4 as the merge base
2292 2292 #
2293 2293
2294 2294 cfname = copy[0]
2295 2295 crev = manifest1.get(cfname)
2296 2296 newfparent = fparent2
2297 2297
2298 2298 if manifest2: # branch merge
2299 2299 if fparent2 == nullid or crev is None: # copied on remote side
2300 2300 if cfname in manifest2:
2301 2301 crev = manifest2[cfname]
2302 2302 newfparent = fparent1
2303 2303
2304 2304 # Here, we used to search backwards through history to try to find
2305 2305 # where the file copy came from if the source of a copy was not in
2306 2306 # the parent directory. However, this doesn't actually make sense to
2307 2307 # do (what does a copy from something not in your working copy even
2308 2308 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2309 2309 # the user that copy information was dropped, so if they didn't
2310 2310 # expect this outcome it can be fixed, but this is the correct
2311 2311 # behavior in this circumstance.
2312 2312
2313 2313 if crev:
2314 2314 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
2315 2315 meta["copy"] = cfname
2316 2316 meta["copyrev"] = hex(crev)
2317 2317 fparent1, fparent2 = nullid, newfparent
2318 2318 else:
2319 2319 self.ui.warn(_("warning: can't find ancestor for '%s' "
2320 2320 "copied from '%s'!\n") % (fname, cfname))
2321 2321
2322 2322 elif fparent1 == nullid:
2323 2323 fparent1, fparent2 = fparent2, nullid
2324 2324 elif fparent2 != nullid:
2325 2325 # is one parent an ancestor of the other?
2326 2326 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2327 2327 if fparent1 in fparentancestors:
2328 2328 fparent1, fparent2 = fparent2, nullid
2329 2329 elif fparent2 in fparentancestors:
2330 2330 fparent2 = nullid
2331 2331
2332 2332 # is the file changed?
2333 2333 text = fctx.data()
2334 2334 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2335 2335 changelist.append(fname)
2336 2336 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2337 2337 # are just the flags changed during merge?
2338 2338 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2339 2339 changelist.append(fname)
2340 2340
2341 2341 return fparent1
2342 2342
2343 2343 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2344 2344 """check for commit arguments that aren't committable"""
2345 2345 if match.isexact() or match.prefix():
2346 2346 matched = set(status.modified + status.added + status.removed)
2347 2347
2348 2348 for f in match.files():
2349 2349 f = self.dirstate.normalize(f)
2350 2350 if f == '.' or f in matched or f in wctx.substate:
2351 2351 continue
2352 2352 if f in status.deleted:
2353 2353 fail(f, _('file not found!'))
2354 2354 if f in vdirs: # visited directory
2355 2355 d = f + '/'
2356 2356 for mf in matched:
2357 2357 if mf.startswith(d):
2358 2358 break
2359 2359 else:
2360 2360 fail(f, _("no match under directory!"))
2361 2361 elif f not in self.dirstate:
2362 2362 fail(f, _("file not tracked!"))
2363 2363
2364 2364 @unfilteredmethod
2365 2365 def commit(self, text="", user=None, date=None, match=None, force=False,
2366 2366 editor=False, extra=None):
2367 2367 """Add a new revision to current repository.
2368 2368
2369 2369 Revision information is gathered from the working directory,
2370 2370 match can be used to filter the committed files. If editor is
2371 2371 supplied, it is called to get a commit message.
2372 2372 """
2373 2373 if extra is None:
2374 2374 extra = {}
2375 2375
2376 2376 def fail(f, msg):
2377 2377 raise error.Abort('%s: %s' % (f, msg))
2378 2378
2379 2379 if not match:
2380 2380 match = matchmod.always(self.root, '')
2381 2381
2382 2382 if not force:
2383 2383 vdirs = []
2384 2384 match.explicitdir = vdirs.append
2385 2385 match.bad = fail
2386 2386
2387 2387 wlock = lock = tr = None
2388 2388 try:
2389 2389 wlock = self.wlock()
2390 2390 lock = self.lock() # for recent changelog (see issue4368)
2391 2391
2392 2392 wctx = self[None]
2393 2393 merge = len(wctx.parents()) > 1
2394 2394
2395 2395 if not force and merge and not match.always():
2396 2396 raise error.Abort(_('cannot partially commit a merge '
2397 2397 '(do not specify files or patterns)'))
2398 2398
2399 2399 status = self.status(match=match, clean=force)
2400 2400 if force:
2401 2401 status.modified.extend(status.clean) # mq may commit clean files
2402 2402
2403 2403 # check subrepos
2404 2404 subs, commitsubs, newstate = subrepoutil.precommit(
2405 2405 self.ui, wctx, status, match, force=force)
2406 2406
2407 2407 # make sure all explicit patterns are matched
2408 2408 if not force:
2409 2409 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2410 2410
2411 2411 cctx = context.workingcommitctx(self, status,
2412 2412 text, user, date, extra)
2413 2413
2414 2414 # internal config: ui.allowemptycommit
2415 2415 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2416 2416 or extra.get('close') or merge or cctx.files()
2417 2417 or self.ui.configbool('ui', 'allowemptycommit'))
2418 2418 if not allowemptycommit:
2419 2419 return None
2420 2420
2421 2421 if merge and cctx.deleted():
2422 2422 raise error.Abort(_("cannot commit merge with missing files"))
2423 2423
2424 2424 ms = mergemod.mergestate.read(self)
2425 2425 mergeutil.checkunresolved(ms)
2426 2426
2427 2427 if editor:
2428 2428 cctx._text = editor(self, cctx, subs)
2429 2429 edited = (text != cctx._text)
2430 2430
2431 2431 # Save commit message in case this transaction gets rolled back
2432 2432 # (e.g. by a pretxncommit hook). Leave the content alone on
2433 2433 # the assumption that the user will use the same editor again.
2434 2434 msgfn = self.savecommitmessage(cctx._text)
2435 2435
2436 2436 # commit subs and write new state
2437 2437 if subs:
2438 2438 for s in sorted(commitsubs):
2439 2439 sub = wctx.sub(s)
2440 2440 self.ui.status(_('committing subrepository %s\n') %
2441 2441 subrepoutil.subrelpath(sub))
2442 2442 sr = sub.commit(cctx._text, user, date)
2443 2443 newstate[s] = (newstate[s][0], sr)
2444 2444 subrepoutil.writestate(self, newstate)
2445 2445
2446 2446 p1, p2 = self.dirstate.parents()
2447 2447 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2448 2448 try:
2449 2449 self.hook("precommit", throw=True, parent1=hookp1,
2450 2450 parent2=hookp2)
2451 2451 tr = self.transaction('commit')
2452 2452 ret = self.commitctx(cctx, True)
2453 2453 except: # re-raises
2454 2454 if edited:
2455 2455 self.ui.write(
2456 2456 _('note: commit message saved in %s\n') % msgfn)
2457 2457 raise
2458 2458 # update bookmarks, dirstate and mergestate
2459 2459 bookmarks.update(self, [p1, p2], ret)
2460 2460 cctx.markcommitted(ret)
2461 2461 ms.reset()
2462 2462 tr.close()
2463 2463
2464 2464 finally:
2465 2465 lockmod.release(tr, lock, wlock)
2466 2466
2467 2467 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2468 2468 # hack for command that use a temporary commit (eg: histedit)
2469 2469 # temporary commit got stripped before hook release
2470 2470 if self.changelog.hasnode(ret):
2471 2471 self.hook("commit", node=node, parent1=parent1,
2472 2472 parent2=parent2)
2473 2473 self._afterlock(commithook)
2474 2474 return ret
2475 2475
2476 2476 @unfilteredmethod
2477 2477 def commitctx(self, ctx, error=False):
2478 2478 """Add a new revision to current repository.
2479 2479 Revision information is passed via the context argument.
2480 2480
2481 2481 ctx.files() should list all files involved in this commit, i.e.
2482 2482 modified/added/removed files. On merge, it may be wider than the
2483 2483 ctx.files() to be committed, since any file nodes derived directly
2484 2484 from p1 or p2 are excluded from the committed ctx.files().
2485 2485 """
2486 2486
2487 2487 tr = None
2488 2488 p1, p2 = ctx.p1(), ctx.p2()
2489 2489 user = ctx.user()
2490 2490
2491 2491 lock = self.lock()
2492 2492 try:
2493 2493 tr = self.transaction("commit")
2494 2494 trp = weakref.proxy(tr)
2495 2495
2496 2496 if ctx.manifestnode():
2497 2497 # reuse an existing manifest revision
2498 2498 self.ui.debug('reusing known manifest\n')
2499 2499 mn = ctx.manifestnode()
2500 2500 files = ctx.files()
2501 2501 elif ctx.files():
2502 2502 m1ctx = p1.manifestctx()
2503 2503 m2ctx = p2.manifestctx()
2504 2504 mctx = m1ctx.copy()
2505 2505
2506 2506 m = mctx.read()
2507 2507 m1 = m1ctx.read()
2508 2508 m2 = m2ctx.read()
2509 2509
2510 2510 # check in files
2511 2511 added = []
2512 2512 changed = []
2513 2513 removed = list(ctx.removed())
2514 2514 linkrev = len(self)
2515 2515 self.ui.note(_("committing files:\n"))
2516 2516 for f in sorted(ctx.modified() + ctx.added()):
2517 2517 self.ui.note(f + "\n")
2518 2518 try:
2519 2519 fctx = ctx[f]
2520 2520 if fctx is None:
2521 2521 removed.append(f)
2522 2522 else:
2523 2523 added.append(f)
2524 2524 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2525 2525 trp, changed)
2526 2526 m.setflag(f, fctx.flags())
2527 2527 except OSError as inst:
2528 2528 self.ui.warn(_("trouble committing %s!\n") % f)
2529 2529 raise
2530 2530 except IOError as inst:
2531 2531 errcode = getattr(inst, 'errno', errno.ENOENT)
2532 2532 if error or errcode and errcode != errno.ENOENT:
2533 2533 self.ui.warn(_("trouble committing %s!\n") % f)
2534 2534 raise
2535 2535
2536 2536 # update manifest
2537 2537 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2538 2538 drop = [f for f in removed if f in m]
2539 2539 for f in drop:
2540 2540 del m[f]
2541 2541 files = changed + removed
2542 2542 md = None
2543 2543 if not files:
2544 2544 # if no "files" actually changed in terms of the changelog,
2545 2545 # try hard to detect unmodified manifest entry so that the
2546 2546 # exact same commit can be reproduced later on convert.
2547 2547 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2548 2548 if not files and md:
2549 2549 self.ui.debug('not reusing manifest (no file change in '
2550 2550 'changelog, but manifest differs)\n')
2551 2551 if files or md:
2552 2552 self.ui.note(_("committing manifest\n"))
2553 2553 # we're using narrowmatch here since it's already applied at
2554 2554 # other stages (such as dirstate.walk), so we're already
2555 2555 # ignoring things outside of narrowspec in most cases. The
2556 2556 # one case where we might have files outside the narrowspec
2557 2557 # at this point is merges, and we already error out in the
2558 2558 # case where the merge has files outside of the narrowspec,
2559 2559 # so this is safe.
2560 2560 mn = mctx.write(trp, linkrev,
2561 2561 p1.manifestnode(), p2.manifestnode(),
2562 2562 added, drop, match=self.narrowmatch())
2563 2563 else:
2564 2564 self.ui.debug('reusing manifest form p1 (listed files '
2565 2565 'actually unchanged)\n')
2566 2566 mn = p1.manifestnode()
2567 2567 else:
2568 2568 self.ui.debug('reusing manifest from p1 (no file change)\n')
2569 2569 mn = p1.manifestnode()
2570 2570 files = []
2571 2571
2572 2572 # update changelog
2573 2573 self.ui.note(_("committing changelog\n"))
2574 2574 self.changelog.delayupdate(tr)
2575 2575 n = self.changelog.add(mn, files, ctx.description(),
2576 2576 trp, p1.node(), p2.node(),
2577 2577 user, ctx.date(), ctx.extra().copy())
2578 2578 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2579 2579 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2580 2580 parent2=xp2)
2581 2581 # set the new commit is proper phase
2582 2582 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2583 2583 if targetphase:
2584 2584 # retract boundary do not alter parent changeset.
2585 2585 # if a parent have higher the resulting phase will
2586 2586 # be compliant anyway
2587 2587 #
2588 2588 # if minimal phase was 0 we don't need to retract anything
2589 2589 phases.registernew(self, tr, targetphase, [n])
2590 2590 tr.close()
2591 2591 return n
2592 2592 finally:
2593 2593 if tr:
2594 2594 tr.release()
2595 2595 lock.release()
2596 2596
2597 2597 @unfilteredmethod
2598 2598 def destroying(self):
2599 2599 '''Inform the repository that nodes are about to be destroyed.
2600 2600 Intended for use by strip and rollback, so there's a common
2601 2601 place for anything that has to be done before destroying history.
2602 2602
2603 2603 This is mostly useful for saving state that is in memory and waiting
2604 2604 to be flushed when the current lock is released. Because a call to
2605 2605 destroyed is imminent, the repo will be invalidated causing those
2606 2606 changes to stay in memory (waiting for the next unlock), or vanish
2607 2607 completely.
2608 2608 '''
2609 2609 # When using the same lock to commit and strip, the phasecache is left
2610 2610 # dirty after committing. Then when we strip, the repo is invalidated,
2611 2611 # causing those changes to disappear.
2612 2612 if '_phasecache' in vars(self):
2613 2613 self._phasecache.write()
2614 2614
2615 2615 @unfilteredmethod
2616 2616 def destroyed(self):
2617 2617 '''Inform the repository that nodes have been destroyed.
2618 2618 Intended for use by strip and rollback, so there's a common
2619 2619 place for anything that has to be done after destroying history.
2620 2620 '''
2621 2621 # When one tries to:
2622 2622 # 1) destroy nodes thus calling this method (e.g. strip)
2623 2623 # 2) use phasecache somewhere (e.g. commit)
2624 2624 #
2625 2625 # then 2) will fail because the phasecache contains nodes that were
2626 2626 # removed. We can either remove phasecache from the filecache,
2627 2627 # causing it to reload next time it is accessed, or simply filter
2628 2628 # the removed nodes now and write the updated cache.
2629 2629 self._phasecache.filterunknown(self)
2630 2630 self._phasecache.write()
2631 2631
2632 2632 # refresh all repository caches
2633 2633 self.updatecaches()
2634 2634
2635 2635 # Ensure the persistent tag cache is updated. Doing it now
2636 2636 # means that the tag cache only has to worry about destroyed
2637 2637 # heads immediately after a strip/rollback. That in turn
2638 2638 # guarantees that "cachetip == currenttip" (comparing both rev
2639 2639 # and node) always means no nodes have been added or destroyed.
2640 2640
2641 2641 # XXX this is suboptimal when qrefresh'ing: we strip the current
2642 2642 # head, refresh the tag cache, then immediately add a new head.
2643 2643 # But I think doing it this way is necessary for the "instant
2644 2644 # tag cache retrieval" case to work.
2645 2645 self.invalidate()
2646 2646
2647 2647 def status(self, node1='.', node2=None, match=None,
2648 2648 ignored=False, clean=False, unknown=False,
2649 2649 listsubrepos=False):
2650 2650 '''a convenience method that calls node1.status(node2)'''
2651 2651 return self[node1].status(node2, match, ignored, clean, unknown,
2652 2652 listsubrepos)
2653 2653
2654 2654 def addpostdsstatus(self, ps):
2655 2655 """Add a callback to run within the wlock, at the point at which status
2656 2656 fixups happen.
2657 2657
2658 2658 On status completion, callback(wctx, status) will be called with the
2659 2659 wlock held, unless the dirstate has changed from underneath or the wlock
2660 2660 couldn't be grabbed.
2661 2661
2662 2662 Callbacks should not capture and use a cached copy of the dirstate --
2663 2663 it might change in the meanwhile. Instead, they should access the
2664 2664 dirstate via wctx.repo().dirstate.
2665 2665
2666 2666 This list is emptied out after each status run -- extensions should
2667 2667 make sure it adds to this list each time dirstate.status is called.
2668 2668 Extensions should also make sure they don't call this for statuses
2669 2669 that don't involve the dirstate.
2670 2670 """
2671 2671
2672 2672 # The list is located here for uniqueness reasons -- it is actually
2673 2673 # managed by the workingctx, but that isn't unique per-repo.
2674 2674 self._postdsstatus.append(ps)
2675 2675
2676 2676 def postdsstatus(self):
2677 2677 """Used by workingctx to get the list of post-dirstate-status hooks."""
2678 2678 return self._postdsstatus
2679 2679
2680 2680 def clearpostdsstatus(self):
2681 2681 """Used by workingctx to clear post-dirstate-status hooks."""
2682 2682 del self._postdsstatus[:]
2683 2683
2684 2684 def heads(self, start=None):
2685 2685 if start is None:
2686 2686 cl = self.changelog
2687 2687 headrevs = reversed(cl.headrevs())
2688 2688 return [cl.node(rev) for rev in headrevs]
2689 2689
2690 2690 heads = self.changelog.heads(start)
2691 2691 # sort the output in rev descending order
2692 2692 return sorted(heads, key=self.changelog.rev, reverse=True)
2693 2693
2694 2694 def branchheads(self, branch=None, start=None, closed=False):
2695 2695 '''return a (possibly filtered) list of heads for the given branch
2696 2696
2697 2697 Heads are returned in topological order, from newest to oldest.
2698 2698 If branch is None, use the dirstate branch.
2699 2699 If start is not None, return only heads reachable from start.
2700 2700 If closed is True, return heads that are marked as closed as well.
2701 2701 '''
2702 2702 if branch is None:
2703 2703 branch = self[None].branch()
2704 2704 branches = self.branchmap()
2705 2705 if branch not in branches:
2706 2706 return []
2707 2707 # the cache returns heads ordered lowest to highest
2708 2708 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2709 2709 if start is not None:
2710 2710 # filter out the heads that cannot be reached from startrev
2711 2711 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2712 2712 bheads = [h for h in bheads if h in fbheads]
2713 2713 return bheads
2714 2714
2715 2715 def branches(self, nodes):
2716 2716 if not nodes:
2717 2717 nodes = [self.changelog.tip()]
2718 2718 b = []
2719 2719 for n in nodes:
2720 2720 t = n
2721 2721 while True:
2722 2722 p = self.changelog.parents(n)
2723 2723 if p[1] != nullid or p[0] == nullid:
2724 2724 b.append((t, n, p[0], p[1]))
2725 2725 break
2726 2726 n = p[0]
2727 2727 return b
2728 2728
2729 2729 def between(self, pairs):
2730 2730 r = []
2731 2731
2732 2732 for top, bottom in pairs:
2733 2733 n, l, i = top, [], 0
2734 2734 f = 1
2735 2735
2736 2736 while n != bottom and n != nullid:
2737 2737 p = self.changelog.parents(n)[0]
2738 2738 if i == f:
2739 2739 l.append(n)
2740 2740 f = f * 2
2741 2741 n = p
2742 2742 i += 1
2743 2743
2744 2744 r.append(l)
2745 2745
2746 2746 return r
2747 2747
2748 2748 def checkpush(self, pushop):
2749 2749 """Extensions can override this function if additional checks have
2750 2750 to be performed before pushing, or call it if they override push
2751 2751 command.
2752 2752 """
2753 2753
2754 2754 @unfilteredpropertycache
2755 2755 def prepushoutgoinghooks(self):
2756 2756 """Return util.hooks consists of a pushop with repo, remote, outgoing
2757 2757 methods, which are called before pushing changesets.
2758 2758 """
2759 2759 return util.hooks()
2760 2760
2761 2761 def pushkey(self, namespace, key, old, new):
2762 2762 try:
2763 2763 tr = self.currenttransaction()
2764 2764 hookargs = {}
2765 2765 if tr is not None:
2766 2766 hookargs.update(tr.hookargs)
2767 2767 hookargs = pycompat.strkwargs(hookargs)
2768 2768 hookargs[r'namespace'] = namespace
2769 2769 hookargs[r'key'] = key
2770 2770 hookargs[r'old'] = old
2771 2771 hookargs[r'new'] = new
2772 2772 self.hook('prepushkey', throw=True, **hookargs)
2773 2773 except error.HookAbort as exc:
2774 2774 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2775 2775 if exc.hint:
2776 2776 self.ui.write_err(_("(%s)\n") % exc.hint)
2777 2777 return False
2778 2778 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2779 2779 ret = pushkey.push(self, namespace, key, old, new)
2780 2780 def runhook():
2781 2781 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2782 2782 ret=ret)
2783 2783 self._afterlock(runhook)
2784 2784 return ret
2785 2785
2786 2786 def listkeys(self, namespace):
2787 2787 self.hook('prelistkeys', throw=True, namespace=namespace)
2788 2788 self.ui.debug('listing keys for "%s"\n' % namespace)
2789 2789 values = pushkey.list(self, namespace)
2790 2790 self.hook('listkeys', namespace=namespace, values=values)
2791 2791 return values
2792 2792
2793 2793 def debugwireargs(self, one, two, three=None, four=None, five=None):
2794 2794 '''used to test argument passing over the wire'''
2795 2795 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2796 2796 pycompat.bytestr(four),
2797 2797 pycompat.bytestr(five))
2798 2798
2799 2799 def savecommitmessage(self, text):
2800 2800 fp = self.vfs('last-message.txt', 'wb')
2801 2801 try:
2802 2802 fp.write(text)
2803 2803 finally:
2804 2804 fp.close()
2805 2805 return self.pathto(fp.name[len(self.root) + 1:])
2806 2806
2807 2807 # used to avoid circular references so destructors work
2808 2808 def aftertrans(files):
2809 2809 renamefiles = [tuple(t) for t in files]
2810 2810 def a():
2811 2811 for vfs, src, dest in renamefiles:
2812 2812 # if src and dest refer to a same file, vfs.rename is a no-op,
2813 2813 # leaving both src and dest on disk. delete dest to make sure
2814 2814 # the rename couldn't be such a no-op.
2815 2815 vfs.tryunlink(dest)
2816 2816 try:
2817 2817 vfs.rename(src, dest)
2818 2818 except OSError: # journal file does not yet exist
2819 2819 pass
2820 2820 return a
2821 2821
2822 2822 def undoname(fn):
2823 2823 base, name = os.path.split(fn)
2824 2824 assert name.startswith('journal')
2825 2825 return os.path.join(base, name.replace('journal', 'undo', 1))
2826 2826
2827 2827 def instance(ui, path, create, intents=None, createopts=None):
2828 2828 localpath = util.urllocalpath(path)
2829 2829 if create:
2830 2830 createrepository(ui, localpath, createopts=createopts)
2831 2831
2832 2832 return makelocalrepository(ui, localpath, intents=intents)
2833 2833
2834 2834 def islocal(path):
2835 2835 return True
2836 2836
2837 2837 def defaultcreateopts(ui, createopts=None):
2838 2838 """Populate the default creation options for a repository.
2839 2839
2840 2840 A dictionary of explicitly requested creation options can be passed
2841 2841 in. Missing keys will be populated.
2842 2842 """
2843 2843 createopts = dict(createopts or {})
2844 2844
2845 2845 if 'backend' not in createopts:
2846 2846 # experimental config: storage.new-repo-backend
2847 2847 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2848 2848
2849 2849 return createopts
2850 2850
2851 2851 def newreporequirements(ui, createopts):
2852 2852 """Determine the set of requirements for a new local repository.
2853 2853
2854 2854 Extensions can wrap this function to specify custom requirements for
2855 2855 new repositories.
2856 2856 """
2857 2857 # If the repo is being created from a shared repository, we copy
2858 2858 # its requirements.
2859 2859 if 'sharedrepo' in createopts:
2860 2860 requirements = set(createopts['sharedrepo'].requirements)
2861 2861 if createopts.get('sharedrelative'):
2862 2862 requirements.add('relshared')
2863 2863 else:
2864 2864 requirements.add('shared')
2865 2865
2866 2866 return requirements
2867 2867
2868 2868 if 'backend' not in createopts:
2869 2869 raise error.ProgrammingError('backend key not present in createopts; '
2870 2870 'was defaultcreateopts() called?')
2871 2871
2872 2872 if createopts['backend'] != 'revlogv1':
2873 2873 raise error.Abort(_('unable to determine repository requirements for '
2874 2874 'storage backend: %s') % createopts['backend'])
2875 2875
2876 2876 requirements = {'revlogv1'}
2877 2877 if ui.configbool('format', 'usestore'):
2878 2878 requirements.add('store')
2879 2879 if ui.configbool('format', 'usefncache'):
2880 2880 requirements.add('fncache')
2881 2881 if ui.configbool('format', 'dotencode'):
2882 2882 requirements.add('dotencode')
2883 2883
2884 2884 compengine = ui.config('experimental', 'format.compression')
2885 2885 if compengine not in util.compengines:
2886 2886 raise error.Abort(_('compression engine %s defined by '
2887 2887 'experimental.format.compression not available') %
2888 2888 compengine,
2889 2889 hint=_('run "hg debuginstall" to list available '
2890 2890 'compression engines'))
2891 2891
2892 2892 # zlib is the historical default and doesn't need an explicit requirement.
2893 2893 if compengine != 'zlib':
2894 2894 requirements.add('exp-compression-%s' % compengine)
2895 2895
2896 2896 if scmutil.gdinitconfig(ui):
2897 2897 requirements.add('generaldelta')
2898 2898 if ui.configbool('experimental', 'treemanifest'):
2899 2899 requirements.add('treemanifest')
2900 2900 # experimental config: format.sparse-revlog
2901 2901 if ui.configbool('format', 'sparse-revlog'):
2902 2902 requirements.add(SPARSEREVLOG_REQUIREMENT)
2903 2903
2904 2904 revlogv2 = ui.config('experimental', 'revlogv2')
2905 2905 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2906 2906 requirements.remove('revlogv1')
2907 2907 # generaldelta is implied by revlogv2.
2908 2908 requirements.discard('generaldelta')
2909 2909 requirements.add(REVLOGV2_REQUIREMENT)
2910 2910 # experimental config: format.internal-phase
2911 2911 if ui.configbool('format', 'internal-phase'):
2912 2912 requirements.add('internal-phase')
2913 2913
2914 2914 if createopts.get('narrowfiles'):
2915 2915 requirements.add(repository.NARROW_REQUIREMENT)
2916 2916
2917 2917 if createopts.get('lfs'):
2918 2918 requirements.add('lfs')
2919 2919
2920 2920 return requirements
2921 2921
2922 2922 def filterknowncreateopts(ui, createopts):
2923 2923 """Filters a dict of repo creation options against options that are known.
2924 2924
2925 2925 Receives a dict of repo creation options and returns a dict of those
2926 2926 options that we don't know how to handle.
2927 2927
2928 2928 This function is called as part of repository creation. If the
2929 2929 returned dict contains any items, repository creation will not
2930 2930 be allowed, as it means there was a request to create a repository
2931 2931 with options not recognized by loaded code.
2932 2932
2933 2933 Extensions can wrap this function to filter out creation options
2934 2934 they know how to handle.
2935 2935 """
2936 2936 known = {
2937 2937 'backend',
2938 2938 'lfs',
2939 2939 'narrowfiles',
2940 2940 'sharedrepo',
2941 2941 'sharedrelative',
2942 2942 'shareditems',
2943 2943 'shallowfilestore',
2944 2944 }
2945 2945
2946 2946 return {k: v for k, v in createopts.items() if k not in known}
2947 2947
2948 2948 def createrepository(ui, path, createopts=None):
2949 2949 """Create a new repository in a vfs.
2950 2950
2951 2951 ``path`` path to the new repo's working directory.
2952 2952 ``createopts`` options for the new repository.
2953 2953
2954 2954 The following keys for ``createopts`` are recognized:
2955 2955
2956 2956 backend
2957 2957 The storage backend to use.
2958 2958 lfs
2959 2959 Repository will be created with ``lfs`` requirement. The lfs extension
2960 2960 will automatically be loaded when the repository is accessed.
2961 2961 narrowfiles
2962 2962 Set up repository to support narrow file storage.
2963 2963 sharedrepo
2964 2964 Repository object from which storage should be shared.
2965 2965 sharedrelative
2966 2966 Boolean indicating if the path to the shared repo should be
2967 2967 stored as relative. By default, the pointer to the "parent" repo
2968 2968 is stored as an absolute path.
2969 2969 shareditems
2970 2970 Set of items to share to the new repository (in addition to storage).
2971 2971 shallowfilestore
2972 2972 Indicates that storage for files should be shallow (not all ancestor
2973 2973 revisions are known).
2974 2974 """
2975 2975 createopts = defaultcreateopts(ui, createopts=createopts)
2976 2976
2977 2977 unknownopts = filterknowncreateopts(ui, createopts)
2978 2978
2979 2979 if not isinstance(unknownopts, dict):
2980 2980 raise error.ProgrammingError('filterknowncreateopts() did not return '
2981 2981 'a dict')
2982 2982
2983 2983 if unknownopts:
2984 2984 raise error.Abort(_('unable to create repository because of unknown '
2985 2985 'creation option: %s') %
2986 2986 ', '.join(sorted(unknownopts)),
2987 2987 hint=_('is a required extension not loaded?'))
2988 2988
2989 2989 requirements = newreporequirements(ui, createopts=createopts)
2990 2990
2991 2991 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
2992 2992
2993 2993 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
2994 2994 if hgvfs.exists():
2995 2995 raise error.RepoError(_('repository %s already exists') % path)
2996 2996
2997 2997 if 'sharedrepo' in createopts:
2998 2998 sharedpath = createopts['sharedrepo'].sharedpath
2999 2999
3000 3000 if createopts.get('sharedrelative'):
3001 3001 try:
3002 3002 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
3003 3003 except (IOError, ValueError) as e:
3004 3004 # ValueError is raised on Windows if the drive letters differ
3005 3005 # on each path.
3006 3006 raise error.Abort(_('cannot calculate relative path'),
3007 3007 hint=stringutil.forcebytestr(e))
3008 3008
3009 3009 if not wdirvfs.exists():
3010 3010 wdirvfs.makedirs()
3011 3011
3012 3012 hgvfs.makedir(notindexed=True)
3013 3013 if 'sharedrepo' not in createopts:
3014 3014 hgvfs.mkdir(b'cache')
3015 hgvfs.mkdir(b'wcache')
3015 3016
3016 3017 if b'store' in requirements and 'sharedrepo' not in createopts:
3017 3018 hgvfs.mkdir(b'store')
3018 3019
3019 3020 # We create an invalid changelog outside the store so very old
3020 3021 # Mercurial versions (which didn't know about the requirements
3021 3022 # file) encounter an error on reading the changelog. This
3022 3023 # effectively locks out old clients and prevents them from
3023 3024 # mucking with a repo in an unknown format.
3024 3025 #
3025 3026 # The revlog header has version 2, which won't be recognized by
3026 3027 # such old clients.
3027 3028 hgvfs.append(b'00changelog.i',
3028 3029 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3029 3030 b'layout')
3030 3031
3031 3032 scmutil.writerequires(hgvfs, requirements)
3032 3033
3033 3034 # Write out file telling readers where to find the shared store.
3034 3035 if 'sharedrepo' in createopts:
3035 3036 hgvfs.write(b'sharedpath', sharedpath)
3036 3037
3037 3038 if createopts.get('shareditems'):
3038 3039 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3039 3040 hgvfs.write(b'shared', shared)
3040 3041
3041 3042 def poisonrepository(repo):
3042 3043 """Poison a repository instance so it can no longer be used."""
3043 3044 # Perform any cleanup on the instance.
3044 3045 repo.close()
3045 3046
3046 3047 # Our strategy is to replace the type of the object with one that
3047 3048 # has all attribute lookups result in error.
3048 3049 #
3049 3050 # But we have to allow the close() method because some constructors
3050 3051 # of repos call close() on repo references.
3051 3052 class poisonedrepository(object):
3052 3053 def __getattribute__(self, item):
3053 3054 if item == r'close':
3054 3055 return object.__getattribute__(self, item)
3055 3056
3056 3057 raise error.ProgrammingError('repo instances should not be used '
3057 3058 'after unshare')
3058 3059
3059 3060 def close(self):
3060 3061 pass
3061 3062
3062 3063 # We may have a repoview, which intercepts __setattr__. So be sure
3063 3064 # we operate at the lowest level possible.
3064 3065 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,911 +1,912 b''
1 1 Setting up test
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo 0 > afile
6 6 $ hg add afile
7 7 $ hg commit -m "0.0"
8 8 $ echo 1 >> afile
9 9 $ hg commit -m "0.1"
10 10 $ echo 2 >> afile
11 11 $ hg commit -m "0.2"
12 12 $ echo 3 >> afile
13 13 $ hg commit -m "0.3"
14 14 $ hg update -C 0
15 15 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 16 $ echo 1 >> afile
17 17 $ hg commit -m "1.1"
18 18 created new head
19 19 $ echo 2 >> afile
20 20 $ hg commit -m "1.2"
21 21 $ echo "a line" > fred
22 22 $ echo 3 >> afile
23 23 $ hg add fred
24 24 $ hg commit -m "1.3"
25 25 $ hg mv afile adifferentfile
26 26 $ hg commit -m "1.3m"
27 27 $ hg update -C 3
28 28 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
29 29 $ hg mv afile anotherfile
30 30 $ hg commit -m "0.3m"
31 31 $ hg verify
32 32 checking changesets
33 33 checking manifests
34 34 crosschecking files in changesets and manifests
35 35 checking files
36 36 checked 9 changesets with 7 changes to 4 files
37 37 $ cd ..
38 38 $ hg init empty
39 39
40 40 Bundle and phase
41 41
42 42 $ hg -R test phase --force --secret 0
43 43 $ hg -R test bundle phase.hg empty
44 44 searching for changes
45 45 no changes found (ignored 9 secret changesets)
46 46 [1]
47 47 $ hg -R test phase --draft -r 'head()'
48 48
49 49 Bundle --all
50 50
51 51 $ hg -R test bundle --all all.hg
52 52 9 changesets found
53 53
54 54 Bundle test to full.hg
55 55
56 56 $ hg -R test bundle full.hg empty
57 57 searching for changes
58 58 9 changesets found
59 59
60 60 Unbundle full.hg in test
61 61
62 62 $ hg -R test unbundle full.hg
63 63 adding changesets
64 64 adding manifests
65 65 adding file changes
66 66 added 0 changesets with 0 changes to 4 files
67 67 (run 'hg update' to get a working copy)
68 68
69 69 Verify empty
70 70
71 71 $ hg -R empty heads
72 72 [1]
73 73 $ hg -R empty verify
74 74 checking changesets
75 75 checking manifests
76 76 crosschecking files in changesets and manifests
77 77 checking files
78 78 checked 0 changesets with 0 changes to 0 files
79 79
80 80 #if repobundlerepo
81 81
82 82 Pull full.hg into test (using --cwd)
83 83
84 84 $ hg --cwd test pull ../full.hg
85 85 pulling from ../full.hg
86 86 searching for changes
87 87 no changes found
88 88
89 89 Verify that there are no leaked temporary files after pull (issue2797)
90 90
91 91 $ ls test/.hg | grep .hg10un
92 92 [1]
93 93
94 94 Pull full.hg into empty (using --cwd)
95 95
96 96 $ hg --cwd empty pull ../full.hg
97 97 pulling from ../full.hg
98 98 requesting all changes
99 99 adding changesets
100 100 adding manifests
101 101 adding file changes
102 102 added 9 changesets with 7 changes to 4 files (+1 heads)
103 103 new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
104 104 (run 'hg heads' to see heads, 'hg merge' to merge)
105 105
106 106 Rollback empty
107 107
108 108 $ hg -R empty rollback
109 109 repository tip rolled back to revision -1 (undo pull)
110 110
111 111 Pull full.hg into empty again (using --cwd)
112 112
113 113 $ hg --cwd empty pull ../full.hg
114 114 pulling from ../full.hg
115 115 requesting all changes
116 116 adding changesets
117 117 adding manifests
118 118 adding file changes
119 119 added 9 changesets with 7 changes to 4 files (+1 heads)
120 120 new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
121 121 (run 'hg heads' to see heads, 'hg merge' to merge)
122 122
123 123 Pull full.hg into test (using -R)
124 124
125 125 $ hg -R test pull full.hg
126 126 pulling from full.hg
127 127 searching for changes
128 128 no changes found
129 129
130 130 Pull full.hg into empty (using -R)
131 131
132 132 $ hg -R empty pull full.hg
133 133 pulling from full.hg
134 134 searching for changes
135 135 no changes found
136 136
137 137 Rollback empty
138 138
139 139 $ hg -R empty rollback
140 140 repository tip rolled back to revision -1 (undo pull)
141 141
142 142 Pull full.hg into empty again (using -R)
143 143
144 144 $ hg -R empty pull full.hg
145 145 pulling from full.hg
146 146 requesting all changes
147 147 adding changesets
148 148 adding manifests
149 149 adding file changes
150 150 added 9 changesets with 7 changes to 4 files (+1 heads)
151 151 new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
152 152 (run 'hg heads' to see heads, 'hg merge' to merge)
153 153
154 154 Log -R full.hg in fresh empty
155 155
156 156 $ rm -r empty
157 157 $ hg init empty
158 158 $ cd empty
159 159 $ hg -R bundle://../full.hg log
160 160 changeset: 8:aa35859c02ea
161 161 tag: tip
162 162 parent: 3:eebf5a27f8ca
163 163 user: test
164 164 date: Thu Jan 01 00:00:00 1970 +0000
165 165 summary: 0.3m
166 166
167 167 changeset: 7:a6a34bfa0076
168 168 user: test
169 169 date: Thu Jan 01 00:00:00 1970 +0000
170 170 summary: 1.3m
171 171
172 172 changeset: 6:7373c1169842
173 173 user: test
174 174 date: Thu Jan 01 00:00:00 1970 +0000
175 175 summary: 1.3
176 176
177 177 changeset: 5:1bb50a9436a7
178 178 user: test
179 179 date: Thu Jan 01 00:00:00 1970 +0000
180 180 summary: 1.2
181 181
182 182 changeset: 4:095197eb4973
183 183 parent: 0:f9ee2f85a263
184 184 user: test
185 185 date: Thu Jan 01 00:00:00 1970 +0000
186 186 summary: 1.1
187 187
188 188 changeset: 3:eebf5a27f8ca
189 189 user: test
190 190 date: Thu Jan 01 00:00:00 1970 +0000
191 191 summary: 0.3
192 192
193 193 changeset: 2:e38ba6f5b7e0
194 194 user: test
195 195 date: Thu Jan 01 00:00:00 1970 +0000
196 196 summary: 0.2
197 197
198 198 changeset: 1:34c2bf6b0626
199 199 user: test
200 200 date: Thu Jan 01 00:00:00 1970 +0000
201 201 summary: 0.1
202 202
203 203 changeset: 0:f9ee2f85a263
204 204 user: test
205 205 date: Thu Jan 01 00:00:00 1970 +0000
206 206 summary: 0.0
207 207
208 208 Make sure bundlerepo doesn't leak tempfiles (issue2491)
209 209
210 210 $ ls .hg
211 211 00changelog.i
212 212 cache
213 213 requires
214 214 store
215 wcache
215 216
216 217 Pull ../full.hg into empty (with hook)
217 218
218 219 $ cat >> .hg/hgrc <<EOF
219 220 > [hooks]
220 221 > changegroup = sh -c "printenv.py changegroup"
221 222 > EOF
222 223
223 224 doesn't work (yet ?)
224 225
225 226 hg -R bundle://../full.hg verify
226 227
227 228 $ hg pull bundle://../full.hg
228 229 pulling from bundle:../full.hg
229 230 requesting all changes
230 231 adding changesets
231 232 adding manifests
232 233 adding file changes
233 234 added 9 changesets with 7 changes to 4 files (+1 heads)
234 235 new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
235 236 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle*../full.hg (glob)
236 237 (run 'hg heads' to see heads, 'hg merge' to merge)
237 238
238 239 Rollback empty
239 240
240 241 $ hg rollback
241 242 repository tip rolled back to revision -1 (undo pull)
242 243 $ cd ..
243 244
244 245 Log -R bundle:empty+full.hg
245 246
246 247 $ hg -R bundle:empty+full.hg log --template="{rev} "; echo ""
247 248 8 7 6 5 4 3 2 1 0
248 249
249 250 Pull full.hg into empty again (using -R; with hook)
250 251
251 252 $ hg -R empty pull full.hg
252 253 pulling from full.hg
253 254 requesting all changes
254 255 adding changesets
255 256 adding manifests
256 257 adding file changes
257 258 added 9 changesets with 7 changes to 4 files (+1 heads)
258 259 new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
259 260 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle:empty+full.hg
260 261 (run 'hg heads' to see heads, 'hg merge' to merge)
261 262
262 263 #endif
263 264
264 265 Cannot produce streaming clone bundles with "hg bundle"
265 266
266 267 $ hg -R test bundle -t packed1 packed.hg
267 268 abort: packed bundles cannot be produced by "hg bundle"
268 269 (use 'hg debugcreatestreamclonebundle')
269 270 [255]
270 271
271 272 packed1 is produced properly
272 273
273 274 #if reporevlogstore
274 275
275 276 $ hg -R test debugcreatestreamclonebundle packed.hg
276 277 writing 2664 bytes for 6 files
277 278 bundle requirements: generaldelta, revlogv1
278 279
279 280 $ f -B 64 --size --sha1 --hexdump packed.hg
280 281 packed.hg: size=2827, sha1=9d14cb90c66a21462d915ab33656f38b9deed686
281 282 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 06 00 00 |HGS1UN..........|
282 283 0010: 00 00 00 00 0a 68 00 16 67 65 6e 65 72 61 6c 64 |.....h..generald|
283 284 0020: 65 6c 74 61 2c 72 65 76 6c 6f 67 76 31 00 64 61 |elta,revlogv1.da|
284 285 0030: 74 61 2f 61 64 69 66 66 65 72 65 6e 74 66 69 6c |ta/adifferentfil|
285 286
286 287 $ hg debugbundle --spec packed.hg
287 288 none-packed1;requirements%3Dgeneraldelta%2Crevlogv1
288 289
289 290 generaldelta requirement is not listed in stream clone bundles unless used
290 291
291 292 $ hg --config format.usegeneraldelta=false init testnongd
292 293 $ cd testnongd
293 294 $ touch foo
294 295 $ hg -q commit -A -m initial
295 296 $ cd ..
296 297 $ hg -R testnongd debugcreatestreamclonebundle packednongd.hg
297 298 writing 301 bytes for 3 files
298 299 bundle requirements: revlogv1
299 300
300 301 $ f -B 64 --size --sha1 --hexdump packednongd.hg
301 302 packednongd.hg: size=383, sha1=1d9c230238edd5d38907100b729ba72b1831fe6f
302 303 0000: 48 47 53 31 55 4e 00 00 00 00 00 00 00 03 00 00 |HGS1UN..........|
303 304 0010: 00 00 00 00 01 2d 00 09 72 65 76 6c 6f 67 76 31 |.....-..revlogv1|
304 305 0020: 00 64 61 74 61 2f 66 6f 6f 2e 69 00 36 34 0a 00 |.data/foo.i.64..|
305 306 0030: 01 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
306 307
307 308 $ hg debugbundle --spec packednongd.hg
308 309 none-packed1;requirements%3Drevlogv1
309 310
310 311 Warning emitted when packed bundles contain secret changesets
311 312
312 313 $ hg init testsecret
313 314 $ cd testsecret
314 315 $ touch foo
315 316 $ hg -q commit -A -m initial
316 317 $ hg phase --force --secret -r .
317 318 $ cd ..
318 319
319 320 $ hg -R testsecret debugcreatestreamclonebundle packedsecret.hg
320 321 (warning: stream clone bundle will contain secret revisions)
321 322 writing 301 bytes for 3 files
322 323 bundle requirements: generaldelta, revlogv1
323 324
324 325 Unpacking packed1 bundles with "hg unbundle" isn't allowed
325 326
326 327 $ hg init packed
327 328 $ hg -R packed unbundle packed.hg
328 329 abort: packed bundles cannot be applied with "hg unbundle"
329 330 (use "hg debugapplystreamclonebundle")
330 331 [255]
331 332
332 333 packed1 can be consumed from debug command
333 334
334 335 (this also confirms that streamclone-ed changes are visible via
335 336 @filecache properties to in-process procedures before closing
336 337 transaction)
337 338
338 339 $ cat > $TESTTMP/showtip.py <<EOF
339 340 > from __future__ import absolute_import
340 341 >
341 342 > def showtip(ui, repo, hooktype, **kwargs):
342 343 > ui.warn(b'%s: %s\n' % (hooktype, repo[b'tip'].hex()[:12]))
343 344 >
344 345 > def reposetup(ui, repo):
345 346 > # this confirms (and ensures) that (empty) 00changelog.i
346 347 > # before streamclone is already cached as repo.changelog
347 348 > ui.setconfig(b'hooks', b'pretxnopen.showtip', showtip)
348 349 >
349 350 > # this confirms that streamclone-ed changes are visible to
350 351 > # in-process procedures before closing transaction
351 352 > ui.setconfig(b'hooks', b'pretxnclose.showtip', showtip)
352 353 >
353 354 > # this confirms that streamclone-ed changes are still visible
354 355 > # after closing transaction
355 356 > ui.setconfig(b'hooks', b'txnclose.showtip', showtip)
356 357 > EOF
357 358 $ cat >> $HGRCPATH <<EOF
358 359 > [extensions]
359 360 > showtip = $TESTTMP/showtip.py
360 361 > EOF
361 362
362 363 $ hg -R packed debugapplystreamclonebundle packed.hg
363 364 6 files to transfer, 2.60 KB of data
364 365 pretxnopen: 000000000000
365 366 pretxnclose: aa35859c02ea
366 367 transferred 2.60 KB in *.* seconds (* */sec) (glob)
367 368 txnclose: aa35859c02ea
368 369
369 370 (for safety, confirm visibility of streamclone-ed changes by another
370 371 process, too)
371 372
372 373 $ hg -R packed tip -T "{node|short}\n"
373 374 aa35859c02ea
374 375
375 376 $ cat >> $HGRCPATH <<EOF
376 377 > [extensions]
377 378 > showtip = !
378 379 > EOF
379 380
380 381 Does not work on non-empty repo
381 382
382 383 $ hg -R packed debugapplystreamclonebundle packed.hg
383 384 abort: cannot apply stream clone bundle on non-empty repo
384 385 [255]
385 386
386 387 #endif
387 388
388 389 Create partial clones
389 390
390 391 $ rm -r empty
391 392 $ hg init empty
392 393 $ hg clone -r 3 test partial
393 394 adding changesets
394 395 adding manifests
395 396 adding file changes
396 397 added 4 changesets with 4 changes to 1 files
397 398 new changesets f9ee2f85a263:eebf5a27f8ca
398 399 updating to branch default
399 400 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
400 401 $ hg clone partial partial2
401 402 updating to branch default
402 403 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
403 404 $ cd partial
404 405
405 406 #if repobundlerepo
406 407
407 408 Log -R full.hg in partial
408 409
409 410 $ hg -R bundle://../full.hg log -T phases
410 411 changeset: 8:aa35859c02ea
411 412 tag: tip
412 413 phase: draft
413 414 parent: 3:eebf5a27f8ca
414 415 user: test
415 416 date: Thu Jan 01 00:00:00 1970 +0000
416 417 summary: 0.3m
417 418
418 419 changeset: 7:a6a34bfa0076
419 420 phase: draft
420 421 user: test
421 422 date: Thu Jan 01 00:00:00 1970 +0000
422 423 summary: 1.3m
423 424
424 425 changeset: 6:7373c1169842
425 426 phase: draft
426 427 user: test
427 428 date: Thu Jan 01 00:00:00 1970 +0000
428 429 summary: 1.3
429 430
430 431 changeset: 5:1bb50a9436a7
431 432 phase: draft
432 433 user: test
433 434 date: Thu Jan 01 00:00:00 1970 +0000
434 435 summary: 1.2
435 436
436 437 changeset: 4:095197eb4973
437 438 phase: draft
438 439 parent: 0:f9ee2f85a263
439 440 user: test
440 441 date: Thu Jan 01 00:00:00 1970 +0000
441 442 summary: 1.1
442 443
443 444 changeset: 3:eebf5a27f8ca
444 445 phase: public
445 446 user: test
446 447 date: Thu Jan 01 00:00:00 1970 +0000
447 448 summary: 0.3
448 449
449 450 changeset: 2:e38ba6f5b7e0
450 451 phase: public
451 452 user: test
452 453 date: Thu Jan 01 00:00:00 1970 +0000
453 454 summary: 0.2
454 455
455 456 changeset: 1:34c2bf6b0626
456 457 phase: public
457 458 user: test
458 459 date: Thu Jan 01 00:00:00 1970 +0000
459 460 summary: 0.1
460 461
461 462 changeset: 0:f9ee2f85a263
462 463 phase: public
463 464 user: test
464 465 date: Thu Jan 01 00:00:00 1970 +0000
465 466 summary: 0.0
466 467
467 468
468 469 Incoming full.hg in partial
469 470
470 471 $ hg incoming bundle://../full.hg
471 472 comparing with bundle:../full.hg
472 473 searching for changes
473 474 changeset: 4:095197eb4973
474 475 parent: 0:f9ee2f85a263
475 476 user: test
476 477 date: Thu Jan 01 00:00:00 1970 +0000
477 478 summary: 1.1
478 479
479 480 changeset: 5:1bb50a9436a7
480 481 user: test
481 482 date: Thu Jan 01 00:00:00 1970 +0000
482 483 summary: 1.2
483 484
484 485 changeset: 6:7373c1169842
485 486 user: test
486 487 date: Thu Jan 01 00:00:00 1970 +0000
487 488 summary: 1.3
488 489
489 490 changeset: 7:a6a34bfa0076
490 491 user: test
491 492 date: Thu Jan 01 00:00:00 1970 +0000
492 493 summary: 1.3m
493 494
494 495 changeset: 8:aa35859c02ea
495 496 tag: tip
496 497 parent: 3:eebf5a27f8ca
497 498 user: test
498 499 date: Thu Jan 01 00:00:00 1970 +0000
499 500 summary: 0.3m
500 501
501 502
502 503 Outgoing -R full.hg vs partial2 in partial
503 504
504 505 $ hg -R bundle://../full.hg outgoing ../partial2
505 506 comparing with ../partial2
506 507 searching for changes
507 508 changeset: 4:095197eb4973
508 509 parent: 0:f9ee2f85a263
509 510 user: test
510 511 date: Thu Jan 01 00:00:00 1970 +0000
511 512 summary: 1.1
512 513
513 514 changeset: 5:1bb50a9436a7
514 515 user: test
515 516 date: Thu Jan 01 00:00:00 1970 +0000
516 517 summary: 1.2
517 518
518 519 changeset: 6:7373c1169842
519 520 user: test
520 521 date: Thu Jan 01 00:00:00 1970 +0000
521 522 summary: 1.3
522 523
523 524 changeset: 7:a6a34bfa0076
524 525 user: test
525 526 date: Thu Jan 01 00:00:00 1970 +0000
526 527 summary: 1.3m
527 528
528 529 changeset: 8:aa35859c02ea
529 530 tag: tip
530 531 parent: 3:eebf5a27f8ca
531 532 user: test
532 533 date: Thu Jan 01 00:00:00 1970 +0000
533 534 summary: 0.3m
534 535
535 536
536 537 Outgoing -R does-not-exist.hg vs partial2 in partial
537 538
538 539 $ hg -R bundle://../does-not-exist.hg outgoing ../partial2
539 540 abort: *../does-not-exist.hg* (glob)
540 541 [255]
541 542
542 543 #endif
543 544
544 545 $ cd ..
545 546
546 547 hide outer repo
547 548 $ hg init
548 549
549 550 Direct clone from bundle (all-history)
550 551
551 552 #if repobundlerepo
552 553
553 554 $ hg clone full.hg full-clone
554 555 requesting all changes
555 556 adding changesets
556 557 adding manifests
557 558 adding file changes
558 559 added 9 changesets with 7 changes to 4 files (+1 heads)
559 560 new changesets f9ee2f85a263:aa35859c02ea (9 drafts)
560 561 updating to branch default
561 562 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
562 563 $ hg -R full-clone heads
563 564 changeset: 8:aa35859c02ea
564 565 tag: tip
565 566 parent: 3:eebf5a27f8ca
566 567 user: test
567 568 date: Thu Jan 01 00:00:00 1970 +0000
568 569 summary: 0.3m
569 570
570 571 changeset: 7:a6a34bfa0076
571 572 user: test
572 573 date: Thu Jan 01 00:00:00 1970 +0000
573 574 summary: 1.3m
574 575
575 576 $ rm -r full-clone
576 577
577 578 When cloning from a non-copiable repository into '', do not
578 579 recurse infinitely (issue2528)
579 580
580 581 $ hg clone full.hg ''
581 582 abort: empty destination path is not valid
582 583 [255]
583 584
584 585 test for https://bz.mercurial-scm.org/216
585 586
586 587 Unbundle incremental bundles into fresh empty in one go
587 588
588 589 $ rm -r empty
589 590 $ hg init empty
590 591 $ hg -R test bundle --base null -r 0 ../0.hg
591 592 1 changesets found
592 593 $ hg -R test bundle --base 0 -r 1 ../1.hg
593 594 1 changesets found
594 595 $ hg -R empty unbundle -u ../0.hg ../1.hg
595 596 adding changesets
596 597 adding manifests
597 598 adding file changes
598 599 added 1 changesets with 1 changes to 1 files
599 600 new changesets f9ee2f85a263 (1 drafts)
600 601 adding changesets
601 602 adding manifests
602 603 adding file changes
603 604 added 1 changesets with 1 changes to 1 files
604 605 new changesets 34c2bf6b0626 (1 drafts)
605 606 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
606 607
607 608 View full contents of the bundle
608 609 $ hg -R test bundle --base null -r 3 ../partial.hg
609 610 4 changesets found
610 611 $ cd test
611 612 $ hg -R ../../partial.hg log -r "bundle()"
612 613 changeset: 0:f9ee2f85a263
613 614 user: test
614 615 date: Thu Jan 01 00:00:00 1970 +0000
615 616 summary: 0.0
616 617
617 618 changeset: 1:34c2bf6b0626
618 619 user: test
619 620 date: Thu Jan 01 00:00:00 1970 +0000
620 621 summary: 0.1
621 622
622 623 changeset: 2:e38ba6f5b7e0
623 624 user: test
624 625 date: Thu Jan 01 00:00:00 1970 +0000
625 626 summary: 0.2
626 627
627 628 changeset: 3:eebf5a27f8ca
628 629 user: test
629 630 date: Thu Jan 01 00:00:00 1970 +0000
630 631 summary: 0.3
631 632
632 633 $ cd ..
633 634
634 635 #endif
635 636
636 637 test for 540d1059c802
637 638
638 639 $ hg init orig
639 640 $ cd orig
640 641 $ echo foo > foo
641 642 $ hg add foo
642 643 $ hg ci -m 'add foo'
643 644
644 645 $ hg clone . ../copy
645 646 updating to branch default
646 647 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
647 648 $ hg tag foo
648 649
649 650 $ cd ../copy
650 651 $ echo >> foo
651 652 $ hg ci -m 'change foo'
652 653 $ hg bundle ../bundle.hg ../orig
653 654 searching for changes
654 655 1 changesets found
655 656
656 657 $ cd ..
657 658
658 659 #if repobundlerepo
659 660 $ cd orig
660 661 $ hg incoming ../bundle.hg
661 662 comparing with ../bundle.hg
662 663 searching for changes
663 664 changeset: 2:ed1b79f46b9a
664 665 tag: tip
665 666 parent: 0:bbd179dfa0a7
666 667 user: test
667 668 date: Thu Jan 01 00:00:00 1970 +0000
668 669 summary: change foo
669 670
670 671 $ cd ..
671 672
672 673 test bundle with # in the filename (issue2154):
673 674
674 675 $ cp bundle.hg 'test#bundle.hg'
675 676 $ cd orig
676 677 $ hg incoming '../test#bundle.hg'
677 678 comparing with ../test
678 679 abort: unknown revision 'bundle.hg'!
679 680 [255]
680 681
681 682 note that percent encoding is not handled:
682 683
683 684 $ hg incoming ../test%23bundle.hg
684 685 abort: repository ../test%23bundle.hg not found!
685 686 [255]
686 687 $ cd ..
687 688
688 689 #endif
689 690
690 691 test to bundle revisions on the newly created branch (issue3828):
691 692
692 693 $ hg -q clone -U test test-clone
693 694 $ cd test
694 695
695 696 $ hg -q branch foo
696 697 $ hg commit -m "create foo branch"
697 698 $ hg -q outgoing ../test-clone
698 699 9:b4f5acb1ee27
699 700 $ hg -q bundle --branch foo foo.hg ../test-clone
700 701 #if repobundlerepo
701 702 $ hg -R foo.hg -q log -r "bundle()"
702 703 9:b4f5acb1ee27
703 704 #endif
704 705
705 706 $ cd ..
706 707
707 708 test for https://bz.mercurial-scm.org/1144
708 709
709 710 test that verify bundle does not traceback
710 711
711 712 partial history bundle, fails w/ unknown parent
712 713
713 714 $ hg -R bundle.hg verify
714 715 abort: 00changelog.i@bbd179dfa0a7: unknown parent!
715 716 [255]
716 717
717 718 full history bundle, refuses to verify non-local repo
718 719
719 720 #if repobundlerepo
720 721 $ hg -R all.hg verify
721 722 abort: cannot verify bundle or remote repos
722 723 [255]
723 724 #endif
724 725
725 726 but, regular verify must continue to work
726 727
727 728 $ hg -R orig verify
728 729 checking changesets
729 730 checking manifests
730 731 crosschecking files in changesets and manifests
731 732 checking files
732 733 checked 2 changesets with 2 changes to 2 files
733 734
734 735 #if repobundlerepo
735 736 diff against bundle
736 737
737 738 $ hg init b
738 739 $ cd b
739 740 $ hg -R ../all.hg diff -r tip
740 741 diff -r aa35859c02ea anotherfile
741 742 --- a/anotherfile Thu Jan 01 00:00:00 1970 +0000
742 743 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
743 744 @@ -1,4 +0,0 @@
744 745 -0
745 746 -1
746 747 -2
747 748 -3
748 749 $ cd ..
749 750 #endif
750 751
751 752 bundle single branch
752 753
753 754 $ hg init branchy
754 755 $ cd branchy
755 756 $ echo a >a
756 757 $ echo x >x
757 758 $ hg ci -Ama
758 759 adding a
759 760 adding x
760 761 $ echo c >c
761 762 $ echo xx >x
762 763 $ hg ci -Amc
763 764 adding c
764 765 $ echo c1 >c1
765 766 $ hg ci -Amc1
766 767 adding c1
767 768 $ hg up 0
768 769 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
769 770 $ echo b >b
770 771 $ hg ci -Amb
771 772 adding b
772 773 created new head
773 774 $ echo b1 >b1
774 775 $ echo xx >x
775 776 $ hg ci -Amb1
776 777 adding b1
777 778 $ hg clone -q -r2 . part
778 779
779 780 == bundling via incoming
780 781
781 782 $ hg in -R part --bundle incoming.hg --template "{node}\n" .
782 783 comparing with .
783 784 searching for changes
784 785 1a38c1b849e8b70c756d2d80b0b9a3ac0b7ea11a
785 786 057f4db07f61970e1c11e83be79e9d08adc4dc31
786 787
787 788 == bundling
788 789
789 790 $ hg bundle bundle.hg part --debug --config progress.debug=true
790 791 query 1; heads
791 792 searching for changes
792 793 all remote heads known locally
793 794 2 changesets found
794 795 list of changesets:
795 796 1a38c1b849e8b70c756d2d80b0b9a3ac0b7ea11a
796 797 057f4db07f61970e1c11e83be79e9d08adc4dc31
797 798 bundle2-output-bundle: "HG20", (1 params) 2 parts total
798 799 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
799 800 changesets: 1/2 chunks (50.00%)
800 801 changesets: 2/2 chunks (100.00%)
801 802 manifests: 1/2 chunks (50.00%)
802 803 manifests: 2/2 chunks (100.00%)
803 804 files: b 1/3 files (33.33%)
804 805 files: b1 2/3 files (66.67%)
805 806 files: x 3/3 files (100.00%)
806 807 bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload
807 808
808 809 #if repobundlerepo
809 810 == Test for issue3441
810 811
811 812 $ hg clone -q -r0 . part2
812 813 $ hg -q -R part2 pull bundle.hg
813 814 $ hg -R part2 verify
814 815 checking changesets
815 816 checking manifests
816 817 crosschecking files in changesets and manifests
817 818 checking files
818 819 checked 3 changesets with 5 changes to 4 files
819 820 #endif
820 821
821 822 == Test bundling no commits
822 823
823 824 $ hg bundle -r 'public()' no-output.hg
824 825 abort: no commits to bundle
825 826 [255]
826 827
827 828 $ cd ..
828 829
829 830 When user merges to the revision existing only in the bundle,
830 831 it should show warning that second parent of the working
831 832 directory does not exist
832 833
833 834 $ hg init update2bundled
834 835 $ cd update2bundled
835 836 $ cat <<EOF >> .hg/hgrc
836 837 > [extensions]
837 838 > strip =
838 839 > EOF
839 840 $ echo "aaa" >> a
840 841 $ hg commit -A -m 0
841 842 adding a
842 843 $ echo "bbb" >> b
843 844 $ hg commit -A -m 1
844 845 adding b
845 846 $ echo "ccc" >> c
846 847 $ hg commit -A -m 2
847 848 adding c
848 849 $ hg update -r 1
849 850 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
850 851 $ echo "ddd" >> d
851 852 $ hg commit -A -m 3
852 853 adding d
853 854 created new head
854 855 $ hg update -r 2
855 856 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
856 857 $ hg log -G
857 858 o changeset: 3:8bd3e1f196af
858 859 | tag: tip
859 860 | parent: 1:a01eca7af26d
860 861 | user: test
861 862 | date: Thu Jan 01 00:00:00 1970 +0000
862 863 | summary: 3
863 864 |
864 865 | @ changeset: 2:4652c276ac4f
865 866 |/ user: test
866 867 | date: Thu Jan 01 00:00:00 1970 +0000
867 868 | summary: 2
868 869 |
869 870 o changeset: 1:a01eca7af26d
870 871 | user: test
871 872 | date: Thu Jan 01 00:00:00 1970 +0000
872 873 | summary: 1
873 874 |
874 875 o changeset: 0:4fe08cd4693e
875 876 user: test
876 877 date: Thu Jan 01 00:00:00 1970 +0000
877 878 summary: 0
878 879
879 880
880 881 #if repobundlerepo
881 882 $ hg bundle --base 1 -r 3 ../update2bundled.hg
882 883 1 changesets found
883 884 $ hg strip -r 3
884 885 saved backup bundle to $TESTTMP/update2bundled/.hg/strip-backup/8bd3e1f196af-017e56d8-backup.hg
885 886 $ hg merge -R ../update2bundled.hg -r 3
886 887 setting parent to node 8bd3e1f196af289b2b121be08031e76d7ae92098 that only exists in the bundle
887 888 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
888 889 (branch merge, don't forget to commit)
889 890
890 891 When user updates to the revision existing only in the bundle,
891 892 it should show warning
892 893
893 894 $ hg update -R ../update2bundled.hg --clean -r 3
894 895 setting parent to node 8bd3e1f196af289b2b121be08031e76d7ae92098 that only exists in the bundle
895 896 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
896 897
897 898 When user updates to the revision existing in the local repository
898 899 the warning shouldn't be emitted
899 900
900 901 $ hg update -R ../update2bundled.hg -r 0
901 902 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
902 903 #endif
903 904
904 905 Test the option that create slim bundle
905 906
906 907 $ hg bundle -a --config devel.bundle.delta=p1 ./slim.hg
907 908 3 changesets found
908 909
909 910 Test the option that create and no-delta's bundle
910 911 $ hg bundle -a --config devel.bundle.delta=full ./full.hg
911 912 3 changesets found
@@ -1,54 +1,55 b''
1 1 Create an empty repo:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5
6 6 Try some commands:
7 7
8 8 $ hg log
9 9 $ hg grep wah
10 10 [1]
11 11 $ hg manifest
12 12 $ hg verify
13 13 checking changesets
14 14 checking manifests
15 15 crosschecking files in changesets and manifests
16 16 checking files
17 17 checked 0 changesets with 0 changes to 0 files
18 18
19 19 Check the basic files created:
20 20
21 21 $ ls .hg
22 22 00changelog.i
23 23 cache
24 24 requires
25 25 store
26 wcache
26 27
27 28 Should be empty:
28 29
29 30 $ ls .hg/store
30 31
31 32 Poke at a clone:
32 33
33 34 $ cd ..
34 35 $ hg clone a b
35 36 updating to branch default
36 37 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 38 $ cd b
38 39 $ hg verify
39 40 checking changesets
40 41 checking manifests
41 42 crosschecking files in changesets and manifests
42 43 checking files
43 44 checked 0 changesets with 0 changes to 0 files
44 45 $ ls .hg
45 46 00changelog.i
46 47 hgrc
47 48 requires
48 49 store
49 50
50 51 Should be empty:
51 52
52 53 $ ls .hg/store
53 54
54 55 $ cd ..
@@ -1,179 +1,182 b''
1 1 #require unix-permissions
2 2
3 3 test that new files created in .hg inherit the permissions from .hg/store
4 4
5 5 $ mkdir dir
6 6
7 7 just in case somebody has a strange $TMPDIR
8 8
9 9 $ chmod g-s dir
10 10 $ cd dir
11 11
12 12 $ cat >printmodes.py <<EOF
13 13 > from __future__ import absolute_import, print_function
14 14 > import os
15 15 > import sys
16 16 >
17 17 > allnames = []
18 18 > isdir = {}
19 19 > for root, dirs, files in os.walk(sys.argv[1]):
20 20 > for d in dirs:
21 21 > name = os.path.join(root, d)
22 22 > isdir[name] = 1
23 23 > allnames.append(name)
24 24 > for f in files:
25 25 > name = os.path.join(root, f)
26 26 > allnames.append(name)
27 27 > allnames.sort()
28 28 > for name in allnames:
29 29 > suffix = name in isdir and '/' or ''
30 30 > print('%05o %s%s' % (os.lstat(name).st_mode & 0o7777, name, suffix))
31 31 > EOF
32 32
33 33 $ cat >mode.py <<EOF
34 34 > from __future__ import absolute_import, print_function
35 35 > import os
36 36 > import sys
37 37 > print('%05o' % os.lstat(sys.argv[1]).st_mode)
38 38 > EOF
39 39
40 40 $ umask 077
41 41
42 42 $ hg init repo
43 43 $ cd repo
44 44
45 $ chmod 0770 .hg/store .hg/cache
45 $ chmod 0770 .hg/store .hg/cache .hg/wcache
46 46
47 47 before commit
48 48 store can be written by the group, other files cannot
49 49 store is setgid
50 50
51 51 $ "$PYTHON" ../printmodes.py .
52 52 00700 ./.hg/
53 53 00600 ./.hg/00changelog.i
54 54 00770 ./.hg/cache/
55 55 00600 ./.hg/requires
56 56 00770 ./.hg/store/
57 00770 ./.hg/wcache/
57 58
58 59 $ mkdir dir
59 60 $ touch foo dir/bar
60 61 $ hg ci -qAm 'add files'
61 62
62 63 after commit
63 64 working dir files can only be written by the owner
64 65 files created in .hg can be written by the group
65 66 (in particular, store/**, dirstate, branch cache file, undo files)
66 67 new directories are setgid
67 68
68 69 $ "$PYTHON" ../printmodes.py .
69 70 00700 ./.hg/
70 71 00600 ./.hg/00changelog.i
71 72 00770 ./.hg/cache/
72 73 00660 ./.hg/cache/branch2-served
73 74 00660 ./.hg/cache/manifestfulltextcache (reporevlogstore !)
74 75 00660 ./.hg/cache/rbc-names-v1
75 76 00660 ./.hg/cache/rbc-revs-v1
76 77 00660 ./.hg/dirstate
77 78 00660 ./.hg/fsmonitor.state (fsmonitor !)
78 79 00660 ./.hg/last-message.txt
79 80 00600 ./.hg/requires
80 81 00770 ./.hg/store/
81 82 00660 ./.hg/store/00changelog.i
82 83 00660 ./.hg/store/00manifest.i
83 84 00770 ./.hg/store/data/
84 85 00770 ./.hg/store/data/dir/
85 86 00660 ./.hg/store/data/dir/bar.i (reporevlogstore !)
86 87 00660 ./.hg/store/data/foo.i (reporevlogstore !)
87 88 00770 ./.hg/store/data/dir/bar/ (reposimplestore !)
88 89 00660 ./.hg/store/data/dir/bar/b80de5d138758541c5f05265ad144ab9fa86d1db (reposimplestore !)
89 90 00660 ./.hg/store/data/dir/bar/index (reposimplestore !)
90 91 00770 ./.hg/store/data/foo/ (reposimplestore !)
91 92 00660 ./.hg/store/data/foo/b80de5d138758541c5f05265ad144ab9fa86d1db (reposimplestore !)
92 93 00660 ./.hg/store/data/foo/index (reposimplestore !)
93 94 00660 ./.hg/store/fncache (repofncache !)
94 95 00660 ./.hg/store/phaseroots
95 96 00660 ./.hg/store/undo
96 97 00660 ./.hg/store/undo.backupfiles
97 98 00660 ./.hg/store/undo.phaseroots
98 99 00660 ./.hg/undo.backup.dirstate
99 100 00660 ./.hg/undo.bookmarks
100 101 00660 ./.hg/undo.branch
101 102 00660 ./.hg/undo.desc
102 103 00660 ./.hg/undo.dirstate
103 104 00770 ./.hg/wcache/
104 105 00711 ./.hg/wcache/checkisexec
105 106 00777 ./.hg/wcache/checklink
106 107 00600 ./.hg/wcache/checklink-target
107 108 00700 ./dir/
108 109 00600 ./dir/bar
109 110 00600 ./foo
110 111
111 112 $ umask 007
112 113 $ hg init ../push
113 114
114 115 before push
115 116 group can write everything
116 117
117 118 $ "$PYTHON" ../printmodes.py ../push
118 119 00770 ../push/.hg/
119 120 00660 ../push/.hg/00changelog.i
120 121 00770 ../push/.hg/cache/
121 122 00660 ../push/.hg/requires
122 123 00770 ../push/.hg/store/
124 00770 ../push/.hg/wcache/
123 125
124 126 $ umask 077
125 127 $ hg -q push ../push
126 128
127 129 after push
128 130 group can still write everything
129 131
130 132 $ "$PYTHON" ../printmodes.py ../push
131 133 00770 ../push/.hg/
132 134 00660 ../push/.hg/00changelog.i
133 135 00770 ../push/.hg/cache/
134 136 00660 ../push/.hg/cache/branch2-base
135 137 00660 ../push/.hg/dirstate
136 138 00660 ../push/.hg/requires
137 139 00770 ../push/.hg/store/
138 140 00660 ../push/.hg/store/00changelog.i
139 141 00660 ../push/.hg/store/00manifest.i
140 142 00770 ../push/.hg/store/data/
141 143 00770 ../push/.hg/store/data/dir/
142 144 00660 ../push/.hg/store/data/dir/bar.i (reporevlogstore !)
143 145 00660 ../push/.hg/store/data/foo.i (reporevlogstore !)
144 146 00770 ../push/.hg/store/data/dir/bar/ (reposimplestore !)
145 147 00660 ../push/.hg/store/data/dir/bar/b80de5d138758541c5f05265ad144ab9fa86d1db (reposimplestore !)
146 148 00660 ../push/.hg/store/data/dir/bar/index (reposimplestore !)
147 149 00770 ../push/.hg/store/data/foo/ (reposimplestore !)
148 150 00660 ../push/.hg/store/data/foo/b80de5d138758541c5f05265ad144ab9fa86d1db (reposimplestore !)
149 151 00660 ../push/.hg/store/data/foo/index (reposimplestore !)
150 152 00660 ../push/.hg/store/fncache (repofncache !)
151 153 00660 ../push/.hg/store/undo
152 154 00660 ../push/.hg/store/undo.backupfiles
153 155 00660 ../push/.hg/store/undo.phaseroots
154 156 00660 ../push/.hg/undo.bookmarks
155 157 00660 ../push/.hg/undo.branch
156 158 00660 ../push/.hg/undo.desc
157 159 00660 ../push/.hg/undo.dirstate
160 00770 ../push/.hg/wcache/
158 161
159 162
160 163 Test that we don't lose the setgid bit when we call chmod.
161 164 Not all systems support setgid directories (e.g. HFS+), so
162 165 just check that directories have the same mode.
163 166
164 167 $ cd ..
165 168 $ hg init setgid
166 169 $ cd setgid
167 170 $ chmod g+rwx .hg/store
168 171 $ chmod g+s .hg/store 2> /dev/null || true
169 172 $ mkdir dir
170 173 $ touch dir/file
171 174 $ hg ci -qAm 'add dir/file'
172 175 $ storemode=`"$PYTHON" ../mode.py .hg/store`
173 176 $ dirmode=`"$PYTHON" ../mode.py .hg/store/data/dir`
174 177 $ if [ "$storemode" != "$dirmode" ]; then
175 178 > echo "$storemode != $dirmode"
176 179 > fi
177 180 $ cd ..
178 181
179 182 $ cd .. # g-s dir
@@ -1,1988 +1,1989 b''
1 1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2 2
3 3 $ echo "[ui]" >> $HGRCPATH
4 4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5 5
6 6 $ hg init t
7 7 $ cd t
8 8
9 9 first revision, no sub
10 10
11 11 $ echo a > a
12 12 $ hg ci -Am0
13 13 adding a
14 14
15 15 add first sub
16 16
17 17 $ echo s = s > .hgsub
18 18 $ hg add .hgsub
19 19 $ hg init s
20 20 $ echo a > s/a
21 21
22 22 Issue2232: committing a subrepo without .hgsub
23 23
24 24 $ hg ci -mbad s
25 25 abort: can't commit subrepos without .hgsub
26 26 [255]
27 27
28 28 $ hg -R s add s/a
29 29 $ hg files -S
30 30 .hgsub
31 31 a
32 32 s/a
33 33
34 34 $ hg -R s ci -Ams0
35 35 $ hg sum
36 36 parent: 0:f7b1eb17ad24 tip
37 37 0
38 38 branch: default
39 39 commit: 1 added, 1 subrepos
40 40 update: (current)
41 41 phases: 1 draft
42 42 $ hg ci -m1
43 43
44 44 test handling .hgsubstate "added" explicitly.
45 45
46 46 $ hg parents --template '{node}\n{files}\n'
47 47 7cf8cfea66e410e8e3336508dfeec07b3192de51
48 48 .hgsub .hgsubstate
49 49 $ hg rollback -q
50 50 $ hg add .hgsubstate
51 51 $ hg ci -m1
52 52 $ hg parents --template '{node}\n{files}\n'
53 53 7cf8cfea66e410e8e3336508dfeec07b3192de51
54 54 .hgsub .hgsubstate
55 55
56 56 Subrepopath which overlaps with filepath, does not change warnings in remove()
57 57
58 58 $ mkdir snot
59 59 $ touch snot/file
60 60 $ hg remove -S snot/file
61 61 not removing snot/file: file is untracked
62 62 [1]
63 63 $ hg cat snot/filenot
64 64 snot/filenot: no such file in rev 7cf8cfea66e4
65 65 [1]
66 66 $ rm -r snot
67 67
68 68 Revert subrepo and test subrepo fileset keyword:
69 69
70 70 $ echo b > s/a
71 71 $ hg revert --dry-run "set:subrepo('glob:s*')"
72 72 reverting subrepo s
73 73 reverting s/a
74 74 $ cat s/a
75 75 b
76 76 $ hg revert "set:subrepo('glob:s*')"
77 77 reverting subrepo s
78 78 reverting s/a
79 79 $ cat s/a
80 80 a
81 81 $ rm s/a.orig
82 82
83 83 Revert subrepo with no backup. The "reverting s/a" line is gone since
84 84 we're really running 'hg update' in the subrepo:
85 85
86 86 $ echo b > s/a
87 87 $ hg revert --no-backup s
88 88 reverting subrepo s
89 89
90 90 Issue2022: update -C
91 91
92 92 $ echo b > s/a
93 93 $ hg sum
94 94 parent: 1:7cf8cfea66e4 tip
95 95 1
96 96 branch: default
97 97 commit: 1 subrepos
98 98 update: (current)
99 99 phases: 2 draft
100 100 $ hg co -C 1
101 101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 102 $ hg sum
103 103 parent: 1:7cf8cfea66e4 tip
104 104 1
105 105 branch: default
106 106 commit: (clean)
107 107 update: (current)
108 108 phases: 2 draft
109 109
110 110 commands that require a clean repo should respect subrepos
111 111
112 112 $ echo b >> s/a
113 113 $ hg backout tip
114 114 abort: uncommitted changes in subrepository "s"
115 115 [255]
116 116 $ hg revert -C -R s s/a
117 117
118 118 add sub sub
119 119
120 120 $ echo ss = ss > s/.hgsub
121 121 $ hg init s/ss
122 122 $ echo a > s/ss/a
123 123 $ hg -R s add s/.hgsub
124 124 $ hg -R s/ss add s/ss/a
125 125 $ hg sum
126 126 parent: 1:7cf8cfea66e4 tip
127 127 1
128 128 branch: default
129 129 commit: 1 subrepos
130 130 update: (current)
131 131 phases: 2 draft
132 132 $ hg ci -m2
133 133 committing subrepository s
134 134 committing subrepository s/ss
135 135 $ hg sum
136 136 parent: 2:df30734270ae tip
137 137 2
138 138 branch: default
139 139 commit: (clean)
140 140 update: (current)
141 141 phases: 3 draft
142 142
143 143 test handling .hgsubstate "modified" explicitly.
144 144
145 145 $ hg parents --template '{node}\n{files}\n'
146 146 df30734270ae757feb35e643b7018e818e78a9aa
147 147 .hgsubstate
148 148 $ hg rollback -q
149 149 $ hg status -A .hgsubstate
150 150 M .hgsubstate
151 151 $ hg ci -m2
152 152 $ hg parents --template '{node}\n{files}\n'
153 153 df30734270ae757feb35e643b7018e818e78a9aa
154 154 .hgsubstate
155 155
156 156 bump sub rev (and check it is ignored by ui.commitsubrepos)
157 157
158 158 $ echo b > s/a
159 159 $ hg -R s ci -ms1
160 160 $ hg --config ui.commitsubrepos=no ci -m3
161 161
162 162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
163 163
164 164 $ echo c > s/a
165 165 $ hg --config ui.commitsubrepos=no ci -m4
166 166 abort: uncommitted changes in subrepository "s"
167 167 (use --subrepos for recursive commit)
168 168 [255]
169 169 $ hg id
170 170 f6affe3fbfaa+ tip
171 171 $ hg -R s ci -mc
172 172 $ hg id
173 173 f6affe3fbfaa+ tip
174 174 $ echo d > s/a
175 175 $ hg ci -m4
176 176 committing subrepository s
177 177 $ hg tip -R s
178 178 changeset: 4:02dcf1d70411
179 179 tag: tip
180 180 user: test
181 181 date: Thu Jan 01 00:00:00 1970 +0000
182 182 summary: 4
183 183
184 184
185 185 check caching
186 186
187 187 $ hg co 0
188 188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
189 189 $ hg debugsub
190 190
191 191 restore
192 192
193 193 $ hg co
194 194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 195 $ hg debugsub
196 196 path s
197 197 source s
198 198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
199 199
200 200 new branch for merge tests
201 201
202 202 $ hg co 1
203 203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
204 204 $ echo t = t >> .hgsub
205 205 $ hg init t
206 206 $ echo t > t/t
207 207 $ hg -R t add t
208 208 adding t/t
209 209
210 210 5
211 211
212 212 $ hg ci -m5 # add sub
213 213 committing subrepository t
214 214 created new head
215 215 $ echo t2 > t/t
216 216
217 217 6
218 218
219 219 $ hg st -R s
220 220 $ hg ci -m6 # change sub
221 221 committing subrepository t
222 222 $ hg debugsub
223 223 path s
224 224 source s
225 225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
226 226 path t
227 227 source t
228 228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
229 229 $ echo t3 > t/t
230 230
231 231 7
232 232
233 233 $ hg ci -m7 # change sub again for conflict test
234 234 committing subrepository t
235 235 $ hg rm .hgsub
236 236
237 237 8
238 238
239 239 $ hg ci -m8 # remove sub
240 240
241 241 test handling .hgsubstate "removed" explicitly.
242 242
243 243 $ hg parents --template '{node}\n{files}\n'
244 244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
245 245 .hgsub .hgsubstate
246 246 $ hg rollback -q
247 247 $ hg remove .hgsubstate
248 248 $ hg ci -m8
249 249 $ hg parents --template '{node}\n{files}\n'
250 250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
251 251 .hgsub .hgsubstate
252 252
253 253 merge tests
254 254
255 255 $ hg co -C 3
256 256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 257 $ hg merge 5 # test adding
258 258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 259 (branch merge, don't forget to commit)
260 260 $ hg debugsub
261 261 path s
262 262 source s
263 263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
264 264 path t
265 265 source t
266 266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
267 267 $ hg ci -m9
268 268 created new head
269 269 $ hg merge 6 --debug # test change
270 270 searching for copies back to rev 2
271 271 resolving manifests
272 272 branchmerge: True, force: False, partial: False
273 273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
274 274 starting 4 threads for background file closing (?)
275 275 .hgsubstate: versions differ -> m (premerge)
276 276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
277 277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
278 278 getting subrepo t
279 279 resolving manifests
280 280 branchmerge: False, force: False, partial: False
281 281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
282 282 t: remote is newer -> g
283 283 getting t
284 284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 285 (branch merge, don't forget to commit)
286 286 $ hg debugsub
287 287 path s
288 288 source s
289 289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
290 290 path t
291 291 source t
292 292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
293 293 $ echo conflict > t/t
294 294 $ hg ci -m10
295 295 committing subrepository t
296 296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
297 297 searching for copies back to rev 2
298 298 resolving manifests
299 299 branchmerge: True, force: False, partial: False
300 300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
301 301 starting 4 threads for background file closing (?)
302 302 .hgsubstate: versions differ -> m (premerge)
303 303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
304 304 subrepo t: both sides changed
305 305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
306 306 starting 4 threads for background file closing (?)
307 307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
308 308 merging subrepository "t"
309 309 searching for copies back to rev 2
310 310 resolving manifests
311 311 branchmerge: True, force: False, partial: False
312 312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
313 313 preserving t for resolve of t
314 314 starting 4 threads for background file closing (?)
315 315 t: versions differ -> m (premerge)
316 316 picked tool ':merge' for t (binary False symlink False changedelete False)
317 317 merging t
318 318 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
319 319 t: versions differ -> m (merge)
320 320 picked tool ':merge' for t (binary False symlink False changedelete False)
321 321 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
322 322 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
323 323 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
324 324 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
325 325 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
326 326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 327 (branch merge, don't forget to commit)
328 328
329 329 should conflict
330 330
331 331 $ cat t/t
332 332 <<<<<<< local: 20a0db6fbf6c - test: 10
333 333 conflict
334 334 =======
335 335 t3
336 336 >>>>>>> other: 7af322bc1198 - test: 7
337 337
338 338 11: remove subrepo t
339 339
340 340 $ hg co -C 5
341 341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 342 $ hg revert -r 4 .hgsub # remove t
343 343 $ hg ci -m11
344 344 created new head
345 345 $ hg debugsub
346 346 path s
347 347 source s
348 348 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
349 349
350 350 local removed, remote changed, keep changed
351 351
352 352 $ hg merge 6
353 353 remote [merge rev] changed subrepository t which local [working copy] removed
354 354 use (c)hanged version or (d)elete? c
355 355 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 356 (branch merge, don't forget to commit)
357 357 BROKEN: should include subrepo t
358 358 $ hg debugsub
359 359 path s
360 360 source s
361 361 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
362 362 $ cat .hgsubstate
363 363 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
364 364 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
365 365 $ hg ci -m 'local removed, remote changed, keep changed'
366 366 BROKEN: should include subrepo t
367 367 $ hg debugsub
368 368 path s
369 369 source s
370 370 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
371 371 BROKEN: should include subrepo t
372 372 $ cat .hgsubstate
373 373 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
374 374 $ cat t/t
375 375 t2
376 376
377 377 local removed, remote changed, keep removed
378 378
379 379 $ hg co -C 11
380 380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 381 $ hg merge --config ui.interactive=true 6 <<EOF
382 382 > d
383 383 > EOF
384 384 remote [merge rev] changed subrepository t which local [working copy] removed
385 385 use (c)hanged version or (d)elete? d
386 386 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 387 (branch merge, don't forget to commit)
388 388 $ hg debugsub
389 389 path s
390 390 source s
391 391 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
392 392 $ cat .hgsubstate
393 393 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
394 394 $ hg ci -m 'local removed, remote changed, keep removed'
395 395 created new head
396 396 $ hg debugsub
397 397 path s
398 398 source s
399 399 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
400 400 $ cat .hgsubstate
401 401 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
402 402
403 403 local changed, remote removed, keep changed
404 404
405 405 $ hg co -C 6
406 406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 407 $ hg merge 11
408 408 local [working copy] changed subrepository t which remote [merge rev] removed
409 409 use (c)hanged version or (d)elete? c
410 410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 411 (branch merge, don't forget to commit)
412 412 BROKEN: should include subrepo t
413 413 $ hg debugsub
414 414 path s
415 415 source s
416 416 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
417 417 BROKEN: should include subrepo t
418 418 $ cat .hgsubstate
419 419 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
420 420 $ hg ci -m 'local changed, remote removed, keep changed'
421 421 created new head
422 422 BROKEN: should include subrepo t
423 423 $ hg debugsub
424 424 path s
425 425 source s
426 426 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
427 427 BROKEN: should include subrepo t
428 428 $ cat .hgsubstate
429 429 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
430 430 $ cat t/t
431 431 t2
432 432
433 433 local changed, remote removed, keep removed
434 434
435 435 $ hg co -C 6
436 436 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 437 $ hg merge --config ui.interactive=true 11 <<EOF
438 438 > d
439 439 > EOF
440 440 local [working copy] changed subrepository t which remote [merge rev] removed
441 441 use (c)hanged version or (d)elete? d
442 442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 443 (branch merge, don't forget to commit)
444 444 $ hg debugsub
445 445 path s
446 446 source s
447 447 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
448 448 $ cat .hgsubstate
449 449 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
450 450 $ hg ci -m 'local changed, remote removed, keep removed'
451 451 created new head
452 452 $ hg debugsub
453 453 path s
454 454 source s
455 455 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
456 456 $ cat .hgsubstate
457 457 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
458 458
459 459 clean up to avoid having to fix up the tests below
460 460
461 461 $ hg co -C 10
462 462 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 463 $ cat >> $HGRCPATH <<EOF
464 464 > [extensions]
465 465 > strip=
466 466 > EOF
467 467 $ hg strip -r 11:15
468 468 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
469 469
470 470 clone
471 471
472 472 $ cd ..
473 473 $ hg clone t tc
474 474 updating to branch default
475 475 cloning subrepo s from $TESTTMP/t/s
476 476 cloning subrepo s/ss from $TESTTMP/t/s/ss
477 477 cloning subrepo t from $TESTTMP/t/t
478 478 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 479 $ cd tc
480 480 $ hg debugsub
481 481 path s
482 482 source s
483 483 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
484 484 path t
485 485 source t
486 486 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
487 487 $ cd ..
488 488
489 489 clone with subrepo disabled (update should fail)
490 490
491 491 $ hg clone t -U tc2 --config subrepos.allowed=false
492 492 $ hg update -R tc2 --config subrepos.allowed=false
493 493 abort: subrepos not enabled
494 494 (see 'hg help config.subrepos' for details)
495 495 [255]
496 496 $ ls tc2
497 497 a
498 498
499 499 $ hg clone t tc3 --config subrepos.allowed=false
500 500 updating to branch default
501 501 abort: subrepos not enabled
502 502 (see 'hg help config.subrepos' for details)
503 503 [255]
504 504 $ ls tc3
505 505 a
506 506
507 507 And again with just the hg type disabled
508 508
509 509 $ hg clone t -U tc4 --config subrepos.hg:allowed=false
510 510 $ hg update -R tc4 --config subrepos.hg:allowed=false
511 511 abort: hg subrepos not allowed
512 512 (see 'hg help config.subrepos' for details)
513 513 [255]
514 514 $ ls tc4
515 515 a
516 516
517 517 $ hg clone t tc5 --config subrepos.hg:allowed=false
518 518 updating to branch default
519 519 abort: hg subrepos not allowed
520 520 (see 'hg help config.subrepos' for details)
521 521 [255]
522 522 $ ls tc5
523 523 a
524 524
525 525 push
526 526
527 527 $ cd tc
528 528 $ echo bah > t/t
529 529 $ hg ci -m11
530 530 committing subrepository t
531 531 $ hg push
532 532 pushing to $TESTTMP/t
533 533 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
534 534 no changes made to subrepo s since last push to $TESTTMP/t/s
535 535 pushing subrepo t to $TESTTMP/t/t
536 536 searching for changes
537 537 adding changesets
538 538 adding manifests
539 539 adding file changes
540 540 added 1 changesets with 1 changes to 1 files
541 541 searching for changes
542 542 adding changesets
543 543 adding manifests
544 544 adding file changes
545 545 added 1 changesets with 1 changes to 1 files
546 546
547 547 push -f
548 548
549 549 $ echo bah > s/a
550 550 $ hg ci -m12
551 551 committing subrepository s
552 552 $ hg push
553 553 pushing to $TESTTMP/t
554 554 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
555 555 pushing subrepo s to $TESTTMP/t/s
556 556 searching for changes
557 557 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
558 558 (merge or see 'hg help push' for details about pushing new heads)
559 559 [255]
560 560 $ hg push -f
561 561 pushing to $TESTTMP/t
562 562 pushing subrepo s/ss to $TESTTMP/t/s/ss
563 563 searching for changes
564 564 no changes found
565 565 pushing subrepo s to $TESTTMP/t/s
566 566 searching for changes
567 567 adding changesets
568 568 adding manifests
569 569 adding file changes
570 570 added 1 changesets with 1 changes to 1 files (+1 heads)
571 571 pushing subrepo t to $TESTTMP/t/t
572 572 searching for changes
573 573 no changes found
574 574 searching for changes
575 575 adding changesets
576 576 adding manifests
577 577 adding file changes
578 578 added 1 changesets with 1 changes to 1 files
579 579
580 580 check that unmodified subrepos are not pushed
581 581
582 582 $ hg clone . ../tcc
583 583 updating to branch default
584 584 cloning subrepo s from $TESTTMP/tc/s
585 585 cloning subrepo s/ss from $TESTTMP/tc/s/ss
586 586 cloning subrepo t from $TESTTMP/tc/t
587 587 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
588 588
589 589 the subrepos on the new clone have nothing to push to its source
590 590
591 591 $ hg push -R ../tcc .
592 592 pushing to .
593 593 no changes made to subrepo s/ss since last push to s/ss
594 594 no changes made to subrepo s since last push to s
595 595 no changes made to subrepo t since last push to t
596 596 searching for changes
597 597 no changes found
598 598 [1]
599 599
600 600 the subrepos on the source do not have a clean store versus the clone target
601 601 because they were never explicitly pushed to the source
602 602
603 603 $ hg push ../tcc
604 604 pushing to ../tcc
605 605 pushing subrepo s/ss to ../tcc/s/ss
606 606 searching for changes
607 607 no changes found
608 608 pushing subrepo s to ../tcc/s
609 609 searching for changes
610 610 no changes found
611 611 pushing subrepo t to ../tcc/t
612 612 searching for changes
613 613 no changes found
614 614 searching for changes
615 615 no changes found
616 616 [1]
617 617
618 618 after push their stores become clean
619 619
620 620 $ hg push ../tcc
621 621 pushing to ../tcc
622 622 no changes made to subrepo s/ss since last push to ../tcc/s/ss
623 623 no changes made to subrepo s since last push to ../tcc/s
624 624 no changes made to subrepo t since last push to ../tcc/t
625 625 searching for changes
626 626 no changes found
627 627 [1]
628 628
629 629 updating a subrepo to a different revision or changing
630 630 its working directory does not make its store dirty
631 631
632 632 $ hg -R s update '.^'
633 633 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
634 634 $ hg push
635 635 pushing to $TESTTMP/t
636 636 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
637 637 no changes made to subrepo s since last push to $TESTTMP/t/s
638 638 no changes made to subrepo t since last push to $TESTTMP/t/t
639 639 searching for changes
640 640 no changes found
641 641 [1]
642 642 $ echo foo >> s/a
643 643 $ hg push
644 644 pushing to $TESTTMP/t
645 645 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
646 646 no changes made to subrepo s since last push to $TESTTMP/t/s
647 647 no changes made to subrepo t since last push to $TESTTMP/t/t
648 648 searching for changes
649 649 no changes found
650 650 [1]
651 651 $ hg -R s update -C tip
652 652 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
653 653
654 654 committing into a subrepo makes its store (but not its parent's store) dirty
655 655
656 656 $ echo foo >> s/ss/a
657 657 $ hg -R s/ss commit -m 'test dirty store detection'
658 658
659 659 $ hg out -S -r `hg log -r tip -T "{node|short}"`
660 660 comparing with $TESTTMP/t
661 661 searching for changes
662 662 no changes found
663 663 comparing with $TESTTMP/t/s
664 664 searching for changes
665 665 no changes found
666 666 comparing with $TESTTMP/t/s/ss
667 667 searching for changes
668 668 changeset: 1:79ea5566a333
669 669 tag: tip
670 670 user: test
671 671 date: Thu Jan 01 00:00:00 1970 +0000
672 672 summary: test dirty store detection
673 673
674 674 comparing with $TESTTMP/t/t
675 675 searching for changes
676 676 no changes found
677 677
678 678 $ hg push
679 679 pushing to $TESTTMP/t
680 680 pushing subrepo s/ss to $TESTTMP/t/s/ss
681 681 searching for changes
682 682 adding changesets
683 683 adding manifests
684 684 adding file changes
685 685 added 1 changesets with 1 changes to 1 files
686 686 no changes made to subrepo s since last push to $TESTTMP/t/s
687 687 no changes made to subrepo t since last push to $TESTTMP/t/t
688 688 searching for changes
689 689 no changes found
690 690 [1]
691 691
692 692 a subrepo store may be clean versus one repo but not versus another
693 693
694 694 $ hg push
695 695 pushing to $TESTTMP/t
696 696 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
697 697 no changes made to subrepo s since last push to $TESTTMP/t/s
698 698 no changes made to subrepo t since last push to $TESTTMP/t/t
699 699 searching for changes
700 700 no changes found
701 701 [1]
702 702 $ hg push ../tcc
703 703 pushing to ../tcc
704 704 pushing subrepo s/ss to ../tcc/s/ss
705 705 searching for changes
706 706 adding changesets
707 707 adding manifests
708 708 adding file changes
709 709 added 1 changesets with 1 changes to 1 files
710 710 no changes made to subrepo s since last push to ../tcc/s
711 711 no changes made to subrepo t since last push to ../tcc/t
712 712 searching for changes
713 713 no changes found
714 714 [1]
715 715
716 716 update
717 717
718 718 $ cd ../t
719 719 $ hg up -C # discard our earlier merge
720 720 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
721 721 updated to "c373c8102e68: 12"
722 722 2 other heads for branch "default"
723 723 $ echo blah > t/t
724 724 $ hg ci -m13
725 725 committing subrepository t
726 726
727 727 backout calls revert internally with minimal opts, which should not raise
728 728 KeyError
729 729
730 730 $ hg backout ".^" --no-commit
731 731 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
732 732 changeset c373c8102e68 backed out, don't forget to commit.
733 733
734 734 $ hg up -C # discard changes
735 735 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
736 736 updated to "925c17564ef8: 13"
737 737 2 other heads for branch "default"
738 738
739 739 pull
740 740
741 741 $ cd ../tc
742 742 $ hg pull
743 743 pulling from $TESTTMP/t
744 744 searching for changes
745 745 adding changesets
746 746 adding manifests
747 747 adding file changes
748 748 added 1 changesets with 1 changes to 1 files
749 749 new changesets 925c17564ef8
750 750 (run 'hg update' to get a working copy)
751 751
752 752 should pull t
753 753
754 754 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
755 755 comparing with $TESTTMP/t
756 756 no changes found
757 757 comparing with $TESTTMP/t/s
758 758 searching for changes
759 759 no changes found
760 760 comparing with $TESTTMP/t/s/ss
761 761 searching for changes
762 762 no changes found
763 763 comparing with $TESTTMP/t/t
764 764 searching for changes
765 765 changeset: 5:52c0adc0515a
766 766 tag: tip
767 767 user: test
768 768 date: Thu Jan 01 00:00:00 1970 +0000
769 769 summary: 13
770 770
771 771
772 772 $ hg up
773 773 pulling subrepo t from $TESTTMP/t/t
774 774 searching for changes
775 775 adding changesets
776 776 adding manifests
777 777 adding file changes
778 778 added 1 changesets with 1 changes to 1 files
779 779 new changesets 52c0adc0515a
780 780 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
781 781 updated to "925c17564ef8: 13"
782 782 2 other heads for branch "default"
783 783 $ cat t/t
784 784 blah
785 785
786 786 bogus subrepo path aborts
787 787
788 788 $ echo 'bogus=[boguspath' >> .hgsub
789 789 $ hg ci -m 'bogus subrepo path'
790 790 abort: missing ] in subrepository source
791 791 [255]
792 792
793 793 Issue1986: merge aborts when trying to merge a subrepo that
794 794 shouldn't need merging
795 795
796 796 # subrepo layout
797 797 #
798 798 # o 5 br
799 799 # /|
800 800 # o | 4 default
801 801 # | |
802 802 # | o 3 br
803 803 # |/|
804 804 # o | 2 default
805 805 # | |
806 806 # | o 1 br
807 807 # |/
808 808 # o 0 default
809 809
810 810 $ cd ..
811 811 $ rm -rf sub
812 812 $ hg init main
813 813 $ cd main
814 814 $ hg init s
815 815 $ cd s
816 816 $ echo a > a
817 817 $ hg ci -Am1
818 818 adding a
819 819 $ hg branch br
820 820 marked working directory as branch br
821 821 (branches are permanent and global, did you want a bookmark?)
822 822 $ echo a >> a
823 823 $ hg ci -m1
824 824 $ hg up default
825 825 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
826 826 $ echo b > b
827 827 $ hg ci -Am1
828 828 adding b
829 829 $ hg up br
830 830 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
831 831 $ hg merge tip
832 832 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
833 833 (branch merge, don't forget to commit)
834 834 $ hg ci -m1
835 835 $ hg up 2
836 836 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
837 837 $ echo c > c
838 838 $ hg ci -Am1
839 839 adding c
840 840 $ hg up 3
841 841 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
842 842 $ hg merge 4
843 843 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
844 844 (branch merge, don't forget to commit)
845 845 $ hg ci -m1
846 846
847 847 # main repo layout:
848 848 #
849 849 # * <-- try to merge default into br again
850 850 # .`|
851 851 # . o 5 br --> substate = 5
852 852 # . |
853 853 # o | 4 default --> substate = 4
854 854 # | |
855 855 # | o 3 br --> substate = 2
856 856 # |/|
857 857 # o | 2 default --> substate = 2
858 858 # | |
859 859 # | o 1 br --> substate = 3
860 860 # |/
861 861 # o 0 default --> substate = 2
862 862
863 863 $ cd ..
864 864 $ echo 's = s' > .hgsub
865 865 $ hg -R s up 2
866 866 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
867 867 $ hg ci -Am1
868 868 adding .hgsub
869 869 $ hg branch br
870 870 marked working directory as branch br
871 871 (branches are permanent and global, did you want a bookmark?)
872 872 $ echo b > b
873 873 $ hg -R s up 3
874 874 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
875 875 $ hg ci -Am1
876 876 adding b
877 877 $ hg up default
878 878 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
879 879 $ echo c > c
880 880 $ hg ci -Am1
881 881 adding c
882 882 $ hg up 1
883 883 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
884 884 $ hg merge 2
885 885 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
886 886 (branch merge, don't forget to commit)
887 887 $ hg ci -m1
888 888 $ hg up 2
889 889 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
890 890 $ hg -R s up 4
891 891 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
892 892 $ echo d > d
893 893 $ hg ci -Am1
894 894 adding d
895 895 $ hg up 3
896 896 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
897 897 $ hg -R s up 5
898 898 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
899 899 $ echo e > e
900 900 $ hg ci -Am1
901 901 adding e
902 902
903 903 $ hg up 5
904 904 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
905 905 $ hg merge 4 # try to merge default into br again
906 906 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
907 907 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
908 908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
909 909 (branch merge, don't forget to commit)
910 910 $ cd ..
911 911
912 912 test subrepo delete from .hgsubstate
913 913
914 914 $ hg init testdelete
915 915 $ mkdir testdelete/nested testdelete/nested2
916 916 $ hg init testdelete/nested
917 917 $ hg init testdelete/nested2
918 918 $ echo test > testdelete/nested/foo
919 919 $ echo test > testdelete/nested2/foo
920 920 $ hg -R testdelete/nested add
921 921 adding testdelete/nested/foo
922 922 $ hg -R testdelete/nested2 add
923 923 adding testdelete/nested2/foo
924 924 $ hg -R testdelete/nested ci -m test
925 925 $ hg -R testdelete/nested2 ci -m test
926 926 $ echo nested = nested > testdelete/.hgsub
927 927 $ echo nested2 = nested2 >> testdelete/.hgsub
928 928 $ hg -R testdelete add
929 929 adding testdelete/.hgsub
930 930 $ hg -R testdelete ci -m "nested 1 & 2 added"
931 931 $ echo nested = nested > testdelete/.hgsub
932 932 $ hg -R testdelete ci -m "nested 2 deleted"
933 933 $ cat testdelete/.hgsubstate
934 934 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
935 935 $ hg -R testdelete remove testdelete/.hgsub
936 936 $ hg -R testdelete ci -m ".hgsub deleted"
937 937 $ cat testdelete/.hgsubstate
938 938 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
939 939
940 940 test repository cloning
941 941
942 942 $ mkdir mercurial mercurial2
943 943 $ hg init nested_absolute
944 944 $ echo test > nested_absolute/foo
945 945 $ hg -R nested_absolute add
946 946 adding nested_absolute/foo
947 947 $ hg -R nested_absolute ci -mtest
948 948 $ cd mercurial
949 949 $ hg init nested_relative
950 950 $ echo test2 > nested_relative/foo2
951 951 $ hg -R nested_relative add
952 952 adding nested_relative/foo2
953 953 $ hg -R nested_relative ci -mtest2
954 954 $ hg init main
955 955 $ echo "nested_relative = ../nested_relative" > main/.hgsub
956 956 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
957 957 $ hg -R main add
958 958 adding main/.hgsub
959 959 $ hg -R main ci -m "add subrepos"
960 960 $ cd ..
961 961 $ hg clone mercurial/main mercurial2/main
962 962 updating to branch default
963 963 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
964 964 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
965 965 > mercurial2/main/nested_relative/.hg/hgrc
966 966 [paths]
967 967 default = $TESTTMP/mercurial/nested_absolute
968 968 [paths]
969 969 default = $TESTTMP/mercurial/nested_relative
970 970 $ rm -rf mercurial mercurial2
971 971
972 972 Issue1977: multirepo push should fail if subrepo push fails
973 973
974 974 $ hg init repo
975 975 $ hg init repo/s
976 976 $ echo a > repo/s/a
977 977 $ hg -R repo/s ci -Am0
978 978 adding a
979 979 $ echo s = s > repo/.hgsub
980 980 $ hg -R repo ci -Am1
981 981 adding .hgsub
982 982 $ hg clone repo repo2
983 983 updating to branch default
984 984 cloning subrepo s from $TESTTMP/repo/s
985 985 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
986 986 $ hg -q -R repo2 pull -u
987 987 $ echo 1 > repo2/s/a
988 988 $ hg -R repo2/s ci -m2
989 989 $ hg -q -R repo2/s push
990 990 $ hg -R repo2/s up -C 0
991 991 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
992 992 $ echo 2 > repo2/s/b
993 993 $ hg -R repo2/s ci -m3 -A
994 994 adding b
995 995 created new head
996 996 $ hg -R repo2 ci -m3
997 997 $ hg -q -R repo2 push
998 998 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
999 999 (merge or see 'hg help push' for details about pushing new heads)
1000 1000 [255]
1001 1001 $ hg -R repo update
1002 1002 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1003 1003
1004 1004 test if untracked file is not overwritten
1005 1005
1006 1006 (this also tests that updated .hgsubstate is treated as "modified",
1007 1007 when 'merge.update()' is aborted before 'merge.recordupdates()', even
1008 1008 if none of mode, size and timestamp of it isn't changed on the
1009 1009 filesystem (see also issue4583))
1010 1010
1011 1011 $ echo issue3276_ok > repo/s/b
1012 1012 $ hg -R repo2 push -f -q
1013 1013 $ touch -t 200001010000 repo/.hgsubstate
1014 1014
1015 1015 $ cat >> repo/.hg/hgrc <<EOF
1016 1016 > [fakedirstatewritetime]
1017 1017 > # emulate invoking dirstate.write() via repo.status()
1018 1018 > # at 2000-01-01 00:00
1019 1019 > fakenow = 200001010000
1020 1020 >
1021 1021 > [extensions]
1022 1022 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1023 1023 > EOF
1024 1024 $ hg -R repo update
1025 1025 b: untracked file differs
1026 1026 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
1027 1027 [255]
1028 1028 $ cat >> repo/.hg/hgrc <<EOF
1029 1029 > [extensions]
1030 1030 > fakedirstatewritetime = !
1031 1031 > EOF
1032 1032
1033 1033 $ cat repo/s/b
1034 1034 issue3276_ok
1035 1035 $ rm repo/s/b
1036 1036 $ touch -t 200001010000 repo/.hgsubstate
1037 1037 $ hg -R repo revert --all
1038 1038 reverting repo/.hgsubstate
1039 1039 reverting subrepo s
1040 1040 $ hg -R repo update
1041 1041 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1042 1042 $ cat repo/s/b
1043 1043 2
1044 1044 $ rm -rf repo2 repo
1045 1045
1046 1046
1047 1047 Issue1852 subrepos with relative paths always push/pull relative to default
1048 1048
1049 1049 Prepare a repo with subrepo
1050 1050
1051 1051 $ hg init issue1852a
1052 1052 $ cd issue1852a
1053 1053 $ hg init sub/repo
1054 1054 $ echo test > sub/repo/foo
1055 1055 $ hg -R sub/repo add sub/repo/foo
1056 1056 $ echo sub/repo = sub/repo > .hgsub
1057 1057 $ hg add .hgsub
1058 1058 $ hg ci -mtest
1059 1059 committing subrepository sub/repo
1060 1060 $ echo test >> sub/repo/foo
1061 1061 $ hg ci -mtest
1062 1062 committing subrepository sub/repo
1063 1063 $ hg cat sub/repo/foo
1064 1064 test
1065 1065 test
1066 1066 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1067 1067 [
1068 1068 {
1069 1069 "data": "test\ntest\n",
1070 1070 "path": "foo"
1071 1071 }
1072 1072 ]
1073 1073
1074 1074 non-exact match:
1075 1075
1076 1076 $ hg cat -T '{path|relpath}\n' 'glob:**'
1077 1077 .hgsub
1078 1078 .hgsubstate
1079 1079 sub/repo/foo
1080 1080 $ hg cat -T '{path|relpath}\n' 're:^sub'
1081 1081 sub/repo/foo
1082 1082
1083 1083 missing subrepos in working directory:
1084 1084
1085 1085 $ mkdir -p tmp/sub/repo
1086 1086 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1087 1087 $ cat tmp/sub/repo/foo_p
1088 1088 test
1089 1089 $ mv sub/repo sub_
1090 1090 $ hg cat sub/repo/baz
1091 1091 skipping missing subrepository: sub/repo
1092 1092 [1]
1093 1093 $ rm -rf sub/repo
1094 1094 $ mv sub_ sub/repo
1095 1095 $ cd ..
1096 1096
1097 1097 Create repo without default path, pull top repo, and see what happens on update
1098 1098
1099 1099 $ hg init issue1852b
1100 1100 $ hg -R issue1852b pull issue1852a
1101 1101 pulling from issue1852a
1102 1102 requesting all changes
1103 1103 adding changesets
1104 1104 adding manifests
1105 1105 adding file changes
1106 1106 added 2 changesets with 3 changes to 2 files
1107 1107 new changesets 19487b456929:be5eb94e7215
1108 1108 (run 'hg update' to get a working copy)
1109 1109 $ hg -R issue1852b update
1110 1110 abort: default path for subrepository not found (in subrepository "sub/repo")
1111 1111 [255]
1112 1112
1113 1113 Ensure a full traceback, not just the SubrepoAbort part
1114 1114
1115 1115 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1116 1116 raise error.Abort(_("default path for subrepository not found"))
1117 1117
1118 1118 Pull -u now doesn't help
1119 1119
1120 1120 $ hg -R issue1852b pull -u issue1852a
1121 1121 pulling from issue1852a
1122 1122 searching for changes
1123 1123 no changes found
1124 1124
1125 1125 Try the same, but with pull -u
1126 1126
1127 1127 $ hg init issue1852c
1128 1128 $ hg -R issue1852c pull -r0 -u issue1852a
1129 1129 pulling from issue1852a
1130 1130 adding changesets
1131 1131 adding manifests
1132 1132 adding file changes
1133 1133 added 1 changesets with 2 changes to 2 files
1134 1134 new changesets 19487b456929
1135 1135 cloning subrepo sub/repo from issue1852a/sub/repo
1136 1136 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1137 1137
1138 1138 Try to push from the other side
1139 1139
1140 1140 $ hg -R issue1852a push `pwd`/issue1852c
1141 1141 pushing to $TESTTMP/issue1852c
1142 1142 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo
1143 1143 searching for changes
1144 1144 no changes found
1145 1145 searching for changes
1146 1146 adding changesets
1147 1147 adding manifests
1148 1148 adding file changes
1149 1149 added 1 changesets with 1 changes to 1 files
1150 1150
1151 1151 Incoming and outgoing should not use the default path:
1152 1152
1153 1153 $ hg clone -q issue1852a issue1852d
1154 1154 $ hg -R issue1852d outgoing --subrepos issue1852c
1155 1155 comparing with issue1852c
1156 1156 searching for changes
1157 1157 no changes found
1158 1158 comparing with issue1852c/sub/repo
1159 1159 searching for changes
1160 1160 no changes found
1161 1161 [1]
1162 1162 $ hg -R issue1852d incoming --subrepos issue1852c
1163 1163 comparing with issue1852c
1164 1164 searching for changes
1165 1165 no changes found
1166 1166 comparing with issue1852c/sub/repo
1167 1167 searching for changes
1168 1168 no changes found
1169 1169 [1]
1170 1170
1171 1171 Check that merge of a new subrepo doesn't write the uncommitted state to
1172 1172 .hgsubstate (issue4622)
1173 1173
1174 1174 $ hg init issue1852a/addedsub
1175 1175 $ echo zzz > issue1852a/addedsub/zz.txt
1176 1176 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1177 1177
1178 1178 $ hg clone issue1852a/addedsub issue1852d/addedsub
1179 1179 updating to branch default
1180 1180 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1181 1181
1182 1182 $ echo def > issue1852a/sub/repo/foo
1183 1183 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1184 1184 adding tmp/sub/repo/foo_p
1185 1185 committing subrepository sub/repo
1186 1186
1187 1187 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1188 1188 $ echo xyz > issue1852d/sub/repo/foo
1189 1189 $ hg -R issue1852d pull -u
1190 1190 pulling from $TESTTMP/issue1852a
1191 1191 searching for changes
1192 1192 adding changesets
1193 1193 adding manifests
1194 1194 adding file changes
1195 1195 added 1 changesets with 2 changes to 2 files
1196 1196 new changesets c82b79fdcc5b
1197 1197 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1198 1198 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1199 1199 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo
1200 1200 searching for changes
1201 1201 adding changesets
1202 1202 adding manifests
1203 1203 adding file changes
1204 1204 added 1 changesets with 1 changes to 1 files
1205 1205 new changesets 46cd4aac504c
1206 1206 subrepository sources for sub/repo differ
1207 1207 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1208 1208 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1209 1209 $ cat issue1852d/.hgsubstate
1210 1210 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1211 1211
1212 1212 Check status of files when none of them belong to the first
1213 1213 subrepository:
1214 1214
1215 1215 $ hg init subrepo-status
1216 1216 $ cd subrepo-status
1217 1217 $ hg init subrepo-1
1218 1218 $ hg init subrepo-2
1219 1219 $ cd subrepo-2
1220 1220 $ touch file
1221 1221 $ hg add file
1222 1222 $ cd ..
1223 1223 $ echo subrepo-1 = subrepo-1 > .hgsub
1224 1224 $ echo subrepo-2 = subrepo-2 >> .hgsub
1225 1225 $ hg add .hgsub
1226 1226 $ hg ci -m 'Added subrepos'
1227 1227 committing subrepository subrepo-2
1228 1228 $ hg st subrepo-2/file
1229 1229
1230 1230 Check that share works with subrepo
1231 1231 $ hg --config extensions.share= share . ../shared
1232 1232 updating working directory
1233 1233 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1234 1234 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1235 1235 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1236 1236 $ find ../shared/* | sort
1237 1237 ../shared/subrepo-1
1238 1238 ../shared/subrepo-1/.hg
1239 1239 ../shared/subrepo-1/.hg/cache
1240 1240 ../shared/subrepo-1/.hg/cache/storehash
1241 1241 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1242 1242 ../shared/subrepo-1/.hg/hgrc
1243 1243 ../shared/subrepo-1/.hg/requires
1244 1244 ../shared/subrepo-1/.hg/sharedpath
1245 ../shared/subrepo-1/.hg/wcache
1245 1246 ../shared/subrepo-2
1246 1247 ../shared/subrepo-2/.hg
1247 1248 ../shared/subrepo-2/.hg/branch
1248 1249 ../shared/subrepo-2/.hg/cache
1249 1250 ../shared/subrepo-2/.hg/cache/storehash
1250 1251 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1251 1252 ../shared/subrepo-2/.hg/dirstate
1252 1253 ../shared/subrepo-2/.hg/hgrc
1253 1254 ../shared/subrepo-2/.hg/requires
1254 1255 ../shared/subrepo-2/.hg/sharedpath
1255 1256 ../shared/subrepo-2/.hg/wcache
1256 1257 ../shared/subrepo-2/.hg/wcache/checkisexec
1257 1258 ../shared/subrepo-2/.hg/wcache/checklink
1258 1259 ../shared/subrepo-2/.hg/wcache/checklink-target
1259 1260 ../shared/subrepo-2/file
1260 1261 $ hg -R ../shared in
1261 1262 abort: repository default not found!
1262 1263 [255]
1263 1264 $ hg -R ../shared/subrepo-2 showconfig paths
1264 1265 paths.default=$TESTTMP/subrepo-status/subrepo-2
1265 1266 $ hg -R ../shared/subrepo-1 sum --remote
1266 1267 parent: -1:000000000000 tip (empty repository)
1267 1268 branch: default
1268 1269 commit: (clean)
1269 1270 update: (current)
1270 1271 remote: (synced)
1271 1272
1272 1273 Check hg update --clean
1273 1274 $ cd $TESTTMP/t
1274 1275 $ rm -r t/t.orig
1275 1276 $ hg status -S --all
1276 1277 C .hgsub
1277 1278 C .hgsubstate
1278 1279 C a
1279 1280 C s/.hgsub
1280 1281 C s/.hgsubstate
1281 1282 C s/a
1282 1283 C s/ss/a
1283 1284 C t/t
1284 1285 $ echo c1 > s/a
1285 1286 $ cd s
1286 1287 $ echo c1 > b
1287 1288 $ echo c1 > c
1288 1289 $ hg add b
1289 1290 $ cd ..
1290 1291 $ hg status -S
1291 1292 M s/a
1292 1293 A s/b
1293 1294 ? s/c
1294 1295 $ hg update -C
1295 1296 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1296 1297 updated to "925c17564ef8: 13"
1297 1298 2 other heads for branch "default"
1298 1299 $ hg status -S
1299 1300 ? s/b
1300 1301 ? s/c
1301 1302
1302 1303 Sticky subrepositories, no changes
1303 1304 $ cd $TESTTMP/t
1304 1305 $ hg id
1305 1306 925c17564ef8 tip
1306 1307 $ hg -R s id
1307 1308 12a213df6fa9 tip
1308 1309 $ hg -R t id
1309 1310 52c0adc0515a tip
1310 1311 $ hg update 11
1311 1312 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1312 1313 $ hg id
1313 1314 365661e5936a
1314 1315 $ hg -R s id
1315 1316 fc627a69481f
1316 1317 $ hg -R t id
1317 1318 e95bcfa18a35
1318 1319
1319 1320 Sticky subrepositories, file changes
1320 1321 $ touch s/f1
1321 1322 $ touch t/f1
1322 1323 $ hg add -S s/f1
1323 1324 $ hg add -S t/f1
1324 1325 $ hg id
1325 1326 365661e5936a+
1326 1327 $ hg -R s id
1327 1328 fc627a69481f+
1328 1329 $ hg -R t id
1329 1330 e95bcfa18a35+
1330 1331 $ hg update tip
1331 1332 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1332 1333 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1333 1334 subrepository sources for s differ
1334 1335 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1335 1336 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1336 1337 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1337 1338 subrepository sources for t differ
1338 1339 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1339 1340 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1340 1341 $ hg id
1341 1342 925c17564ef8+ tip
1342 1343 $ hg -R s id
1343 1344 fc627a69481f+
1344 1345 $ hg -R t id
1345 1346 e95bcfa18a35+
1346 1347 $ hg update --clean tip
1347 1348 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348 1349
1349 1350 Sticky subrepository, revision updates
1350 1351 $ hg id
1351 1352 925c17564ef8 tip
1352 1353 $ hg -R s id
1353 1354 12a213df6fa9 tip
1354 1355 $ hg -R t id
1355 1356 52c0adc0515a tip
1356 1357 $ cd s
1357 1358 $ hg update -r -2
1358 1359 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1359 1360 $ cd ../t
1360 1361 $ hg update -r 2
1361 1362 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1362 1363 $ cd ..
1363 1364 $ hg update 10
1364 1365 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1365 1366 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1366 1367 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1367 1368 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1368 1369 subrepository sources for t differ (in checked out version)
1369 1370 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1370 1371 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1371 1372 $ hg id
1372 1373 e45c8b14af55+
1373 1374 $ hg -R s id
1374 1375 02dcf1d70411
1375 1376 $ hg -R t id
1376 1377 7af322bc1198
1377 1378
1378 1379 Sticky subrepository, file changes and revision updates
1379 1380 $ touch s/f1
1380 1381 $ touch t/f1
1381 1382 $ hg add -S s/f1
1382 1383 $ hg add -S t/f1
1383 1384 $ hg id
1384 1385 e45c8b14af55+
1385 1386 $ hg -R s id
1386 1387 02dcf1d70411+
1387 1388 $ hg -R t id
1388 1389 7af322bc1198+
1389 1390 $ hg update tip
1390 1391 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1391 1392 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1392 1393 subrepository sources for s differ
1393 1394 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1394 1395 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1395 1396 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1396 1397 subrepository sources for t differ
1397 1398 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1398 1399 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1399 1400 $ hg id
1400 1401 925c17564ef8+ tip
1401 1402 $ hg -R s id
1402 1403 02dcf1d70411+
1403 1404 $ hg -R t id
1404 1405 7af322bc1198+
1405 1406
1406 1407 Sticky repository, update --clean
1407 1408 $ hg update --clean tip
1408 1409 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1409 1410 $ hg id
1410 1411 925c17564ef8 tip
1411 1412 $ hg -R s id
1412 1413 12a213df6fa9 tip
1413 1414 $ hg -R t id
1414 1415 52c0adc0515a tip
1415 1416
1416 1417 Test subrepo already at intended revision:
1417 1418 $ cd s
1418 1419 $ hg update fc627a69481f
1419 1420 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1420 1421 $ cd ..
1421 1422 $ hg update 11
1422 1423 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1423 1424 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1424 1425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1425 1426 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1426 1427 $ hg id -n
1427 1428 11+
1428 1429 $ hg -R s id
1429 1430 fc627a69481f
1430 1431 $ hg -R t id
1431 1432 e95bcfa18a35
1432 1433
1433 1434 Test that removing .hgsubstate doesn't break anything:
1434 1435
1435 1436 $ hg rm -f .hgsubstate
1436 1437 $ hg ci -mrm
1437 1438 nothing changed
1438 1439 [1]
1439 1440 $ hg log -vr tip
1440 1441 changeset: 13:925c17564ef8
1441 1442 tag: tip
1442 1443 user: test
1443 1444 date: Thu Jan 01 00:00:00 1970 +0000
1444 1445 files: .hgsubstate
1445 1446 description:
1446 1447 13
1447 1448
1448 1449
1449 1450
1450 1451 Test that removing .hgsub removes .hgsubstate:
1451 1452
1452 1453 $ hg rm .hgsub
1453 1454 $ hg ci -mrm2
1454 1455 created new head
1455 1456 $ hg log -vr tip
1456 1457 changeset: 14:2400bccd50af
1457 1458 tag: tip
1458 1459 parent: 11:365661e5936a
1459 1460 user: test
1460 1461 date: Thu Jan 01 00:00:00 1970 +0000
1461 1462 files: .hgsub .hgsubstate
1462 1463 description:
1463 1464 rm2
1464 1465
1465 1466
1466 1467 Test issue3153: diff -S with deleted subrepos
1467 1468
1468 1469 $ hg diff --nodates -S -c .
1469 1470 diff -r 365661e5936a -r 2400bccd50af .hgsub
1470 1471 --- a/.hgsub
1471 1472 +++ /dev/null
1472 1473 @@ -1,2 +0,0 @@
1473 1474 -s = s
1474 1475 -t = t
1475 1476 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1476 1477 --- a/.hgsubstate
1477 1478 +++ /dev/null
1478 1479 @@ -1,2 +0,0 @@
1479 1480 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1480 1481 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1481 1482
1482 1483 Test behavior of add for explicit path in subrepo:
1483 1484 $ cd ..
1484 1485 $ hg init explicit
1485 1486 $ cd explicit
1486 1487 $ echo s = s > .hgsub
1487 1488 $ hg add .hgsub
1488 1489 $ hg init s
1489 1490 $ hg ci -m0
1490 1491 Adding with an explicit path in a subrepo adds the file
1491 1492 $ echo c1 > f1
1492 1493 $ echo c2 > s/f2
1493 1494 $ hg st -S
1494 1495 ? f1
1495 1496 ? s/f2
1496 1497 $ hg add s/f2
1497 1498 $ hg st -S
1498 1499 A s/f2
1499 1500 ? f1
1500 1501 $ hg ci -R s -m0
1501 1502 $ hg ci -Am1
1502 1503 adding f1
1503 1504 Adding with an explicit path in a subrepo with -S has the same behavior
1504 1505 $ echo c3 > f3
1505 1506 $ echo c4 > s/f4
1506 1507 $ hg st -S
1507 1508 ? f3
1508 1509 ? s/f4
1509 1510 $ hg add -S s/f4
1510 1511 $ hg st -S
1511 1512 A s/f4
1512 1513 ? f3
1513 1514 $ hg ci -R s -m1
1514 1515 $ hg ci -Ama2
1515 1516 adding f3
1516 1517 Adding without a path or pattern silently ignores subrepos
1517 1518 $ echo c5 > f5
1518 1519 $ echo c6 > s/f6
1519 1520 $ echo c7 > s/f7
1520 1521 $ hg st -S
1521 1522 ? f5
1522 1523 ? s/f6
1523 1524 ? s/f7
1524 1525 $ hg add
1525 1526 adding f5
1526 1527 $ hg st -S
1527 1528 A f5
1528 1529 ? s/f6
1529 1530 ? s/f7
1530 1531 $ hg ci -R s -Am2
1531 1532 adding f6
1532 1533 adding f7
1533 1534 $ hg ci -m3
1534 1535 Adding without a path or pattern with -S also adds files in subrepos
1535 1536 $ echo c8 > f8
1536 1537 $ echo c9 > s/f9
1537 1538 $ echo c10 > s/f10
1538 1539 $ hg st -S
1539 1540 ? f8
1540 1541 ? s/f10
1541 1542 ? s/f9
1542 1543 $ hg add -S
1543 1544 adding f8
1544 1545 adding s/f10
1545 1546 adding s/f9
1546 1547 $ hg st -S
1547 1548 A f8
1548 1549 A s/f10
1549 1550 A s/f9
1550 1551 $ hg ci -R s -m3
1551 1552 $ hg ci -m4
1552 1553 Adding with a pattern silently ignores subrepos
1553 1554 $ echo c11 > fm11
1554 1555 $ echo c12 > fn12
1555 1556 $ echo c13 > s/fm13
1556 1557 $ echo c14 > s/fn14
1557 1558 $ hg st -S
1558 1559 ? fm11
1559 1560 ? fn12
1560 1561 ? s/fm13
1561 1562 ? s/fn14
1562 1563 $ hg add 'glob:**fm*'
1563 1564 adding fm11
1564 1565 $ hg st -S
1565 1566 A fm11
1566 1567 ? fn12
1567 1568 ? s/fm13
1568 1569 ? s/fn14
1569 1570 $ hg ci -R s -Am4
1570 1571 adding fm13
1571 1572 adding fn14
1572 1573 $ hg ci -Am5
1573 1574 adding fn12
1574 1575 Adding with a pattern with -S also adds matches in subrepos
1575 1576 $ echo c15 > fm15
1576 1577 $ echo c16 > fn16
1577 1578 $ echo c17 > s/fm17
1578 1579 $ echo c18 > s/fn18
1579 1580 $ hg st -S
1580 1581 ? fm15
1581 1582 ? fn16
1582 1583 ? s/fm17
1583 1584 ? s/fn18
1584 1585 $ hg add -S 'glob:**fm*'
1585 1586 adding fm15
1586 1587 adding s/fm17
1587 1588 $ hg st -S
1588 1589 A fm15
1589 1590 A s/fm17
1590 1591 ? fn16
1591 1592 ? s/fn18
1592 1593 $ hg ci -R s -Am5
1593 1594 adding fn18
1594 1595 $ hg ci -Am6
1595 1596 adding fn16
1596 1597
1597 1598 Test behavior of forget for explicit path in subrepo:
1598 1599 Forgetting an explicit path in a subrepo untracks the file
1599 1600 $ echo c19 > s/f19
1600 1601 $ hg add s/f19
1601 1602 $ hg st -S
1602 1603 A s/f19
1603 1604 $ hg forget s/f19
1604 1605 $ hg st -S
1605 1606 ? s/f19
1606 1607 $ rm s/f19
1607 1608 $ cd ..
1608 1609
1609 1610 Courtesy phases synchronisation to publishing server does not block the push
1610 1611 (issue3781)
1611 1612
1612 1613 $ cp -R main issue3781
1613 1614 $ cp -R main issue3781-dest
1614 1615 $ cd issue3781-dest/s
1615 1616 $ hg phase tip # show we have draft changeset
1616 1617 5: draft
1617 1618 $ chmod a-w .hg/store/phaseroots # prevent phase push
1618 1619 $ cd ../../issue3781
1619 1620 $ cat >> .hg/hgrc << EOF
1620 1621 > [paths]
1621 1622 > default=../issue3781-dest/
1622 1623 > EOF
1623 1624 $ hg push --config devel.legacy.exchange=bundle1
1624 1625 pushing to $TESTTMP/issue3781-dest
1625 1626 pushing subrepo s to $TESTTMP/issue3781-dest/s
1626 1627 searching for changes
1627 1628 no changes found
1628 1629 searching for changes
1629 1630 no changes found
1630 1631 [1]
1631 1632 # clean the push cache
1632 1633 $ rm s/.hg/cache/storehash/*
1633 1634 $ hg push # bundle2+
1634 1635 pushing to $TESTTMP/issue3781-dest
1635 1636 pushing subrepo s to $TESTTMP/issue3781-dest/s
1636 1637 searching for changes
1637 1638 no changes found
1638 1639 searching for changes
1639 1640 no changes found
1640 1641 [1]
1641 1642 $ cd ..
1642 1643
1643 1644 Test phase choice for newly created commit with "phases.subrepochecks"
1644 1645 configuration
1645 1646
1646 1647 $ cd t
1647 1648 $ hg update -q -r 12
1648 1649
1649 1650 $ cat >> s/ss/.hg/hgrc <<EOF
1650 1651 > [phases]
1651 1652 > new-commit = secret
1652 1653 > EOF
1653 1654 $ cat >> s/.hg/hgrc <<EOF
1654 1655 > [phases]
1655 1656 > new-commit = draft
1656 1657 > EOF
1657 1658 $ echo phasecheck1 >> s/ss/a
1658 1659 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1659 1660 committing subrepository ss
1660 1661 transaction abort!
1661 1662 rollback completed
1662 1663 abort: can't commit in draft phase conflicting secret from subrepository ss
1663 1664 [255]
1664 1665 $ echo phasecheck2 >> s/ss/a
1665 1666 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1666 1667 committing subrepository ss
1667 1668 $ hg -R s/ss phase tip
1668 1669 3: secret
1669 1670 $ hg -R s phase tip
1670 1671 6: draft
1671 1672 $ echo phasecheck3 >> s/ss/a
1672 1673 $ hg -R s commit -S -m phasecheck3
1673 1674 committing subrepository ss
1674 1675 warning: changes are committed in secret phase from subrepository ss
1675 1676 $ hg -R s/ss phase tip
1676 1677 4: secret
1677 1678 $ hg -R s phase tip
1678 1679 7: secret
1679 1680
1680 1681 $ cat >> t/.hg/hgrc <<EOF
1681 1682 > [phases]
1682 1683 > new-commit = draft
1683 1684 > EOF
1684 1685 $ cat >> .hg/hgrc <<EOF
1685 1686 > [phases]
1686 1687 > new-commit = public
1687 1688 > EOF
1688 1689 $ echo phasecheck4 >> s/ss/a
1689 1690 $ echo phasecheck4 >> t/t
1690 1691 $ hg commit -S -m phasecheck4
1691 1692 committing subrepository s
1692 1693 committing subrepository s/ss
1693 1694 warning: changes are committed in secret phase from subrepository ss
1694 1695 committing subrepository t
1695 1696 warning: changes are committed in secret phase from subrepository s
1696 1697 created new head
1697 1698 $ hg -R s/ss phase tip
1698 1699 5: secret
1699 1700 $ hg -R s phase tip
1700 1701 8: secret
1701 1702 $ hg -R t phase tip
1702 1703 6: draft
1703 1704 $ hg phase tip
1704 1705 15: secret
1705 1706
1706 1707 $ cd ..
1707 1708
1708 1709
1709 1710 Test that commit --secret works on both repo and subrepo (issue4182)
1710 1711
1711 1712 $ cd main
1712 1713 $ echo secret >> b
1713 1714 $ echo secret >> s/b
1714 1715 $ hg commit --secret --subrepo -m "secret"
1715 1716 committing subrepository s
1716 1717 $ hg phase -r .
1717 1718 6: secret
1718 1719 $ cd s
1719 1720 $ hg phase -r .
1720 1721 6: secret
1721 1722 $ cd ../../
1722 1723
1723 1724 Test "subrepos" template keyword
1724 1725
1725 1726 $ cd t
1726 1727 $ hg update -q 15
1727 1728 $ cat > .hgsub <<EOF
1728 1729 > s = s
1729 1730 > EOF
1730 1731 $ hg commit -m "16"
1731 1732 warning: changes are committed in secret phase from subrepository s
1732 1733
1733 1734 (addition of ".hgsub" itself)
1734 1735
1735 1736 $ hg diff --nodates -c 1 .hgsubstate
1736 1737 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1737 1738 --- /dev/null
1738 1739 +++ b/.hgsubstate
1739 1740 @@ -0,0 +1,1 @@
1740 1741 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1741 1742 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1742 1743 f7b1eb17ad24 000000000000
1743 1744 s
1744 1745
1745 1746 (modification of existing entry)
1746 1747
1747 1748 $ hg diff --nodates -c 2 .hgsubstate
1748 1749 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1749 1750 --- a/.hgsubstate
1750 1751 +++ b/.hgsubstate
1751 1752 @@ -1,1 +1,1 @@
1752 1753 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1753 1754 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1754 1755 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1755 1756 7cf8cfea66e4 000000000000
1756 1757 s
1757 1758
1758 1759 (addition of entry)
1759 1760
1760 1761 $ hg diff --nodates -c 5 .hgsubstate
1761 1762 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1762 1763 --- a/.hgsubstate
1763 1764 +++ b/.hgsubstate
1764 1765 @@ -1,1 +1,2 @@
1765 1766 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1766 1767 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1767 1768 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1768 1769 7cf8cfea66e4 000000000000
1769 1770 t
1770 1771
1771 1772 (removal of existing entry)
1772 1773
1773 1774 $ hg diff --nodates -c 16 .hgsubstate
1774 1775 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1775 1776 --- a/.hgsubstate
1776 1777 +++ b/.hgsubstate
1777 1778 @@ -1,2 +1,1 @@
1778 1779 0731af8ca9423976d3743119d0865097c07bdc1b s
1779 1780 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1780 1781 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1781 1782 8bec38d2bd0b 000000000000
1782 1783 t
1783 1784
1784 1785 (merging)
1785 1786
1786 1787 $ hg diff --nodates -c 9 .hgsubstate
1787 1788 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1788 1789 --- a/.hgsubstate
1789 1790 +++ b/.hgsubstate
1790 1791 @@ -1,1 +1,2 @@
1791 1792 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1792 1793 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1793 1794 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1794 1795 f6affe3fbfaa 1f14a2e2d3ec
1795 1796 t
1796 1797
1797 1798 (removal of ".hgsub" itself)
1798 1799
1799 1800 $ hg diff --nodates -c 8 .hgsubstate
1800 1801 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1801 1802 --- a/.hgsubstate
1802 1803 +++ /dev/null
1803 1804 @@ -1,2 +0,0 @@
1804 1805 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1805 1806 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1806 1807 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1807 1808 f94576341bcf 000000000000
1808 1809
1809 1810 Test that '[paths]' is configured correctly at subrepo creation
1810 1811
1811 1812 $ cd $TESTTMP/tc
1812 1813 $ cat > .hgsub <<EOF
1813 1814 > # to clear bogus subrepo path 'bogus=[boguspath'
1814 1815 > s = s
1815 1816 > t = t
1816 1817 > EOF
1817 1818 $ hg update -q --clean null
1818 1819 $ rm -rf s t
1819 1820 $ cat >> .hg/hgrc <<EOF
1820 1821 > [paths]
1821 1822 > default-push = /foo/bar
1822 1823 > EOF
1823 1824 $ hg update -q
1824 1825 $ cat s/.hg/hgrc
1825 1826 [paths]
1826 1827 default = $TESTTMP/t/s
1827 1828 default-push = /foo/bar/s
1828 1829 $ cat s/ss/.hg/hgrc
1829 1830 [paths]
1830 1831 default = $TESTTMP/t/s/ss
1831 1832 default-push = /foo/bar/s/ss
1832 1833 $ cat t/.hg/hgrc
1833 1834 [paths]
1834 1835 default = $TESTTMP/t/t
1835 1836 default-push = /foo/bar/t
1836 1837
1837 1838 $ cd $TESTTMP/t
1838 1839 $ hg up -qC 0
1839 1840 $ echo 'bar' > bar.txt
1840 1841 $ hg ci -Am 'branch before subrepo add'
1841 1842 adding bar.txt
1842 1843 created new head
1843 1844 $ hg merge -r "first(subrepo('s'))"
1844 1845 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1845 1846 (branch merge, don't forget to commit)
1846 1847 $ hg status -S -X '.hgsub*'
1847 1848 A s/a
1848 1849 ? s/b
1849 1850 ? s/c
1850 1851 ? s/f1
1851 1852 $ hg status -S --rev 'p2()'
1852 1853 A bar.txt
1853 1854 ? s/b
1854 1855 ? s/c
1855 1856 ? s/f1
1856 1857 $ hg diff -S -X '.hgsub*' --nodates
1857 1858 diff -r 000000000000 s/a
1858 1859 --- /dev/null
1859 1860 +++ b/s/a
1860 1861 @@ -0,0 +1,1 @@
1861 1862 +a
1862 1863 $ hg diff -S --rev 'p2()' --nodates
1863 1864 diff -r 7cf8cfea66e4 bar.txt
1864 1865 --- /dev/null
1865 1866 +++ b/bar.txt
1866 1867 @@ -0,0 +1,1 @@
1867 1868 +bar
1868 1869
1869 1870 $ cd ..
1870 1871
1871 1872 test for ssh exploit 2017-07-25
1872 1873
1873 1874 $ cat >> $HGRCPATH << EOF
1874 1875 > [ui]
1875 1876 > ssh = sh -c "read l; read l; read l"
1876 1877 > EOF
1877 1878
1878 1879 $ hg init malicious-proxycommand
1879 1880 $ cd malicious-proxycommand
1880 1881 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1881 1882 $ hg init s
1882 1883 $ cd s
1883 1884 $ echo init > init
1884 1885 $ hg add
1885 1886 adding init
1886 1887 $ hg commit -m init
1887 1888 $ cd ..
1888 1889 $ hg add .hgsub
1889 1890 $ hg ci -m 'add subrepo'
1890 1891 $ cd ..
1891 1892 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1892 1893 updating to branch default
1893 1894 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1894 1895 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1895 1896 [255]
1896 1897
1897 1898 also check that a percent encoded '-' (%2D) doesn't work
1898 1899
1899 1900 $ cd malicious-proxycommand
1900 1901 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1901 1902 $ hg ci -m 'change url to percent encoded'
1902 1903 $ cd ..
1903 1904 $ rm -r malicious-proxycommand-clone
1904 1905 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1905 1906 updating to branch default
1906 1907 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
1907 1908 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1908 1909 [255]
1909 1910
1910 1911 also check for a pipe
1911 1912
1912 1913 $ cd malicious-proxycommand
1913 1914 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1914 1915 $ hg ci -m 'change url to pipe'
1915 1916 $ cd ..
1916 1917 $ rm -r malicious-proxycommand-clone
1917 1918 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1918 1919 updating to branch default
1919 1920 cloning subrepo s from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
1920 1921 abort: no suitable response from remote hg!
1921 1922 [255]
1922 1923 $ [ ! -f owned ] || echo 'you got owned'
1923 1924
1924 1925 also check that a percent encoded '|' (%7C) doesn't work
1925 1926
1926 1927 $ cd malicious-proxycommand
1927 1928 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1928 1929 $ hg ci -m 'change url to percent encoded pipe'
1929 1930 $ cd ..
1930 1931 $ rm -r malicious-proxycommand-clone
1931 1932 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1932 1933 updating to branch default
1933 1934 cloning subrepo s from ssh://fakehost%7Ctouch%20owned/path
1934 1935 abort: no suitable response from remote hg!
1935 1936 [255]
1936 1937 $ [ ! -f owned ] || echo 'you got owned'
1937 1938
1938 1939 and bad usernames:
1939 1940 $ cd malicious-proxycommand
1940 1941 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1941 1942 $ hg ci -m 'owned username'
1942 1943 $ cd ..
1943 1944 $ rm -r malicious-proxycommand-clone
1944 1945 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1945 1946 updating to branch default
1946 1947 cloning subrepo s from ssh://-oProxyCommand%3Dtouch%20owned@example.com/path
1947 1948 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1948 1949 [255]
1949 1950
1950 1951 Test convert subrepositories including merge (issue5526):
1951 1952
1952 1953 $ hg init tconv
1953 1954 $ hg convert --config extensions.convert= -q t/s tconv/s
1954 1955 $ hg convert --config extensions.convert= -q t/s/ss tconv/s/ss
1955 1956 $ hg convert --config extensions.convert= -q t/t tconv/t
1956 1957
1957 1958 convert shouldn't fail because of pseudo filenode:
1958 1959
1959 1960 $ hg convert --config extensions.convert= t tconv
1960 1961 scanning source...
1961 1962 sorting...
1962 1963 converting...
1963 1964 17 0
1964 1965 16 1
1965 1966 15 2
1966 1967 14 3
1967 1968 13 4
1968 1969 12 5
1969 1970 11 6
1970 1971 10 7
1971 1972 9 8
1972 1973 8 9
1973 1974 7 10
1974 1975 6 11
1975 1976 5 12
1976 1977 4 13
1977 1978 3 rm2
1978 1979 2 phasecheck4
1979 1980 1 16
1980 1981 0 branch before subrepo add
1981 1982
1982 1983 converted .hgsubstate should point to valid nodes:
1983 1984
1984 1985 $ hg up -R tconv 9
1985 1986 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1986 1987 $ cat tconv/.hgsubstate
1987 1988 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1988 1989 60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
General Comments 0
You need to be logged in to leave comments. Login now