##// END OF EJS Templates
exchange: refactor APIs to obtain bundle data (API)...
Gregory Szorc -
r30187:3e86261b default
parent child Browse files
Show More
@@ -1,1962 +1,1956
1 1 # exchange.py - utility to exchange data between repos.
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
13 13 from .i18n import _
14 14 from .node import (
15 15 hex,
16 16 nullid,
17 17 )
18 18 from . import (
19 19 base85,
20 20 bookmarks as bookmod,
21 21 bundle2,
22 22 changegroup,
23 23 discovery,
24 24 error,
25 25 lock as lockmod,
26 26 obsolete,
27 27 phases,
28 28 pushkey,
29 29 scmutil,
30 30 sslutil,
31 31 streamclone,
32 32 tags,
33 33 url as urlmod,
34 34 util,
35 35 )
36 36
37 37 urlerr = util.urlerr
38 38 urlreq = util.urlreq
39 39
40 40 # Maps bundle compression human names to internal representation.
41 41 _bundlespeccompressions = {'none': None,
42 42 'bzip2': 'BZ',
43 43 'gzip': 'GZ',
44 44 }
45 45
46 46 # Maps bundle version human names to changegroup versions.
47 47 _bundlespeccgversions = {'v1': '01',
48 48 'v2': '02',
49 49 'packed1': 's1',
50 50 'bundle2': '02', #legacy
51 51 }
52 52
53 53 def parsebundlespec(repo, spec, strict=True, externalnames=False):
54 54 """Parse a bundle string specification into parts.
55 55
56 56 Bundle specifications denote a well-defined bundle/exchange format.
57 57 The content of a given specification should not change over time in
58 58 order to ensure that bundles produced by a newer version of Mercurial are
59 59 readable from an older version.
60 60
61 61 The string currently has the form:
62 62
63 63 <compression>-<type>[;<parameter0>[;<parameter1>]]
64 64
65 65 Where <compression> is one of the supported compression formats
66 66 and <type> is (currently) a version string. A ";" can follow the type and
67 67 all text afterwards is interpretted as URI encoded, ";" delimited key=value
68 68 pairs.
69 69
70 70 If ``strict`` is True (the default) <compression> is required. Otherwise,
71 71 it is optional.
72 72
73 73 If ``externalnames`` is False (the default), the human-centric names will
74 74 be converted to their internal representation.
75 75
76 76 Returns a 3-tuple of (compression, version, parameters). Compression will
77 77 be ``None`` if not in strict mode and a compression isn't defined.
78 78
79 79 An ``InvalidBundleSpecification`` is raised when the specification is
80 80 not syntactically well formed.
81 81
82 82 An ``UnsupportedBundleSpecification`` is raised when the compression or
83 83 bundle type/version is not recognized.
84 84
85 85 Note: this function will likely eventually return a more complex data
86 86 structure, including bundle2 part information.
87 87 """
88 88 def parseparams(s):
89 89 if ';' not in s:
90 90 return s, {}
91 91
92 92 params = {}
93 93 version, paramstr = s.split(';', 1)
94 94
95 95 for p in paramstr.split(';'):
96 96 if '=' not in p:
97 97 raise error.InvalidBundleSpecification(
98 98 _('invalid bundle specification: '
99 99 'missing "=" in parameter: %s') % p)
100 100
101 101 key, value = p.split('=', 1)
102 102 key = urlreq.unquote(key)
103 103 value = urlreq.unquote(value)
104 104 params[key] = value
105 105
106 106 return version, params
107 107
108 108
109 109 if strict and '-' not in spec:
110 110 raise error.InvalidBundleSpecification(
111 111 _('invalid bundle specification; '
112 112 'must be prefixed with compression: %s') % spec)
113 113
114 114 if '-' in spec:
115 115 compression, version = spec.split('-', 1)
116 116
117 117 if compression not in _bundlespeccompressions:
118 118 raise error.UnsupportedBundleSpecification(
119 119 _('%s compression is not supported') % compression)
120 120
121 121 version, params = parseparams(version)
122 122
123 123 if version not in _bundlespeccgversions:
124 124 raise error.UnsupportedBundleSpecification(
125 125 _('%s is not a recognized bundle version') % version)
126 126 else:
127 127 # Value could be just the compression or just the version, in which
128 128 # case some defaults are assumed (but only when not in strict mode).
129 129 assert not strict
130 130
131 131 spec, params = parseparams(spec)
132 132
133 133 if spec in _bundlespeccompressions:
134 134 compression = spec
135 135 version = 'v1'
136 136 if 'generaldelta' in repo.requirements:
137 137 version = 'v2'
138 138 elif spec in _bundlespeccgversions:
139 139 if spec == 'packed1':
140 140 compression = 'none'
141 141 else:
142 142 compression = 'bzip2'
143 143 version = spec
144 144 else:
145 145 raise error.UnsupportedBundleSpecification(
146 146 _('%s is not a recognized bundle specification') % spec)
147 147
148 148 # The specification for packed1 can optionally declare the data formats
149 149 # required to apply it. If we see this metadata, compare against what the
150 150 # repo supports and error if the bundle isn't compatible.
151 151 if version == 'packed1' and 'requirements' in params:
152 152 requirements = set(params['requirements'].split(','))
153 153 missingreqs = requirements - repo.supportedformats
154 154 if missingreqs:
155 155 raise error.UnsupportedBundleSpecification(
156 156 _('missing support for repository features: %s') %
157 157 ', '.join(sorted(missingreqs)))
158 158
159 159 if not externalnames:
160 160 compression = _bundlespeccompressions[compression]
161 161 version = _bundlespeccgversions[version]
162 162 return compression, version, params
163 163
164 164 def readbundle(ui, fh, fname, vfs=None):
165 165 header = changegroup.readexactly(fh, 4)
166 166
167 167 alg = None
168 168 if not fname:
169 169 fname = "stream"
170 170 if not header.startswith('HG') and header.startswith('\0'):
171 171 fh = changegroup.headerlessfixup(fh, header)
172 172 header = "HG10"
173 173 alg = 'UN'
174 174 elif vfs:
175 175 fname = vfs.join(fname)
176 176
177 177 magic, version = header[0:2], header[2:4]
178 178
179 179 if magic != 'HG':
180 180 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
181 181 if version == '10':
182 182 if alg is None:
183 183 alg = changegroup.readexactly(fh, 2)
184 184 return changegroup.cg1unpacker(fh, alg)
185 185 elif version.startswith('2'):
186 186 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
187 187 elif version == 'S1':
188 188 return streamclone.streamcloneapplier(fh)
189 189 else:
190 190 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
191 191
192 192 def getbundlespec(ui, fh):
193 193 """Infer the bundlespec from a bundle file handle.
194 194
195 195 The input file handle is seeked and the original seek position is not
196 196 restored.
197 197 """
198 198 def speccompression(alg):
199 199 for k, v in _bundlespeccompressions.items():
200 200 if v == alg:
201 201 return k
202 202 return None
203 203
204 204 b = readbundle(ui, fh, None)
205 205 if isinstance(b, changegroup.cg1unpacker):
206 206 alg = b._type
207 207 if alg == '_truncatedBZ':
208 208 alg = 'BZ'
209 209 comp = speccompression(alg)
210 210 if not comp:
211 211 raise error.Abort(_('unknown compression algorithm: %s') % alg)
212 212 return '%s-v1' % comp
213 213 elif isinstance(b, bundle2.unbundle20):
214 214 if 'Compression' in b.params:
215 215 comp = speccompression(b.params['Compression'])
216 216 if not comp:
217 217 raise error.Abort(_('unknown compression algorithm: %s') % comp)
218 218 else:
219 219 comp = 'none'
220 220
221 221 version = None
222 222 for part in b.iterparts():
223 223 if part.type == 'changegroup':
224 224 version = part.params['version']
225 225 if version in ('01', '02'):
226 226 version = 'v2'
227 227 else:
228 228 raise error.Abort(_('changegroup version %s does not have '
229 229 'a known bundlespec') % version,
230 230 hint=_('try upgrading your Mercurial '
231 231 'client'))
232 232
233 233 if not version:
234 234 raise error.Abort(_('could not identify changegroup version in '
235 235 'bundle'))
236 236
237 237 return '%s-%s' % (comp, version)
238 238 elif isinstance(b, streamclone.streamcloneapplier):
239 239 requirements = streamclone.readbundle1header(fh)[2]
240 240 params = 'requirements=%s' % ','.join(sorted(requirements))
241 241 return 'none-packed1;%s' % urlreq.quote(params)
242 242 else:
243 243 raise error.Abort(_('unknown bundle type: %s') % b)
244 244
245 245 def buildobsmarkerspart(bundler, markers):
246 246 """add an obsmarker part to the bundler with <markers>
247 247
248 248 No part is created if markers is empty.
249 249 Raises ValueError if the bundler doesn't support any known obsmarker format.
250 250 """
251 251 if markers:
252 252 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
253 253 version = obsolete.commonversion(remoteversions)
254 254 if version is None:
255 255 raise ValueError('bundler does not support common obsmarker format')
256 256 stream = obsolete.encodemarkers(markers, True, version=version)
257 257 return bundler.newpart('obsmarkers', data=stream)
258 258 return None
259 259
260 260 def _computeoutgoing(repo, heads, common):
261 261 """Computes which revs are outgoing given a set of common
262 262 and a set of heads.
263 263
264 264 This is a separate function so extensions can have access to
265 265 the logic.
266 266
267 267 Returns a discovery.outgoing object.
268 268 """
269 269 cl = repo.changelog
270 270 if common:
271 271 hasnode = cl.hasnode
272 272 common = [n for n in common if hasnode(n)]
273 273 else:
274 274 common = [nullid]
275 275 if not heads:
276 276 heads = cl.heads()
277 277 return discovery.outgoing(repo, common, heads)
278 278
279 279 def _forcebundle1(op):
280 280 """return true if a pull/push must use bundle1
281 281
282 282 This function is used to allow testing of the older bundle version"""
283 283 ui = op.repo.ui
284 284 forcebundle1 = False
285 285 # The goal is this config is to allow developper to choose the bundle
286 286 # version used during exchanged. This is especially handy during test.
287 287 # Value is a list of bundle version to be picked from, highest version
288 288 # should be used.
289 289 #
290 290 # developer config: devel.legacy.exchange
291 291 exchange = ui.configlist('devel', 'legacy.exchange')
292 292 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
293 293 return forcebundle1 or not op.remote.capable('bundle2')
294 294
295 295 class pushoperation(object):
296 296 """A object that represent a single push operation
297 297
298 298 Its purpose is to carry push related state and very common operations.
299 299
300 300 A new pushoperation should be created at the beginning of each push and
301 301 discarded afterward.
302 302 """
303 303
304 304 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
305 305 bookmarks=()):
306 306 # repo we push from
307 307 self.repo = repo
308 308 self.ui = repo.ui
309 309 # repo we push to
310 310 self.remote = remote
311 311 # force option provided
312 312 self.force = force
313 313 # revs to be pushed (None is "all")
314 314 self.revs = revs
315 315 # bookmark explicitly pushed
316 316 self.bookmarks = bookmarks
317 317 # allow push of new branch
318 318 self.newbranch = newbranch
319 319 # did a local lock get acquired?
320 320 self.locallocked = None
321 321 # step already performed
322 322 # (used to check what steps have been already performed through bundle2)
323 323 self.stepsdone = set()
324 324 # Integer version of the changegroup push result
325 325 # - None means nothing to push
326 326 # - 0 means HTTP error
327 327 # - 1 means we pushed and remote head count is unchanged *or*
328 328 # we have outgoing changesets but refused to push
329 329 # - other values as described by addchangegroup()
330 330 self.cgresult = None
331 331 # Boolean value for the bookmark push
332 332 self.bkresult = None
333 333 # discover.outgoing object (contains common and outgoing data)
334 334 self.outgoing = None
335 335 # all remote heads before the push
336 336 self.remoteheads = None
337 337 # testable as a boolean indicating if any nodes are missing locally.
338 338 self.incoming = None
339 339 # phases changes that must be pushed along side the changesets
340 340 self.outdatedphases = None
341 341 # phases changes that must be pushed if changeset push fails
342 342 self.fallbackoutdatedphases = None
343 343 # outgoing obsmarkers
344 344 self.outobsmarkers = set()
345 345 # outgoing bookmarks
346 346 self.outbookmarks = []
347 347 # transaction manager
348 348 self.trmanager = None
349 349 # map { pushkey partid -> callback handling failure}
350 350 # used to handle exception from mandatory pushkey part failure
351 351 self.pkfailcb = {}
352 352
353 353 @util.propertycache
354 354 def futureheads(self):
355 355 """future remote heads if the changeset push succeeds"""
356 356 return self.outgoing.missingheads
357 357
358 358 @util.propertycache
359 359 def fallbackheads(self):
360 360 """future remote heads if the changeset push fails"""
361 361 if self.revs is None:
362 362 # not target to push, all common are relevant
363 363 return self.outgoing.commonheads
364 364 unfi = self.repo.unfiltered()
365 365 # I want cheads = heads(::missingheads and ::commonheads)
366 366 # (missingheads is revs with secret changeset filtered out)
367 367 #
368 368 # This can be expressed as:
369 369 # cheads = ( (missingheads and ::commonheads)
370 370 # + (commonheads and ::missingheads))"
371 371 # )
372 372 #
373 373 # while trying to push we already computed the following:
374 374 # common = (::commonheads)
375 375 # missing = ((commonheads::missingheads) - commonheads)
376 376 #
377 377 # We can pick:
378 378 # * missingheads part of common (::commonheads)
379 379 common = self.outgoing.common
380 380 nm = self.repo.changelog.nodemap
381 381 cheads = [node for node in self.revs if nm[node] in common]
382 382 # and
383 383 # * commonheads parents on missing
384 384 revset = unfi.set('%ln and parents(roots(%ln))',
385 385 self.outgoing.commonheads,
386 386 self.outgoing.missing)
387 387 cheads.extend(c.node() for c in revset)
388 388 return cheads
389 389
390 390 @property
391 391 def commonheads(self):
392 392 """set of all common heads after changeset bundle push"""
393 393 if self.cgresult:
394 394 return self.futureheads
395 395 else:
396 396 return self.fallbackheads
397 397
398 398 # mapping of message used when pushing bookmark
399 399 bookmsgmap = {'update': (_("updating bookmark %s\n"),
400 400 _('updating bookmark %s failed!\n')),
401 401 'export': (_("exporting bookmark %s\n"),
402 402 _('exporting bookmark %s failed!\n')),
403 403 'delete': (_("deleting remote bookmark %s\n"),
404 404 _('deleting remote bookmark %s failed!\n')),
405 405 }
406 406
407 407
408 408 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
409 409 opargs=None):
410 410 '''Push outgoing changesets (limited by revs) from a local
411 411 repository to remote. Return an integer:
412 412 - None means nothing to push
413 413 - 0 means HTTP error
414 414 - 1 means we pushed and remote head count is unchanged *or*
415 415 we have outgoing changesets but refused to push
416 416 - other values as described by addchangegroup()
417 417 '''
418 418 if opargs is None:
419 419 opargs = {}
420 420 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
421 421 **opargs)
422 422 if pushop.remote.local():
423 423 missing = (set(pushop.repo.requirements)
424 424 - pushop.remote.local().supported)
425 425 if missing:
426 426 msg = _("required features are not"
427 427 " supported in the destination:"
428 428 " %s") % (', '.join(sorted(missing)))
429 429 raise error.Abort(msg)
430 430
431 431 # there are two ways to push to remote repo:
432 432 #
433 433 # addchangegroup assumes local user can lock remote
434 434 # repo (local filesystem, old ssh servers).
435 435 #
436 436 # unbundle assumes local user cannot lock remote repo (new ssh
437 437 # servers, http servers).
438 438
439 439 if not pushop.remote.canpush():
440 440 raise error.Abort(_("destination does not support push"))
441 441 # get local lock as we might write phase data
442 442 localwlock = locallock = None
443 443 try:
444 444 # bundle2 push may receive a reply bundle touching bookmarks or other
445 445 # things requiring the wlock. Take it now to ensure proper ordering.
446 446 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
447 447 if (not _forcebundle1(pushop)) and maypushback:
448 448 localwlock = pushop.repo.wlock()
449 449 locallock = pushop.repo.lock()
450 450 pushop.locallocked = True
451 451 except IOError as err:
452 452 pushop.locallocked = False
453 453 if err.errno != errno.EACCES:
454 454 raise
455 455 # source repo cannot be locked.
456 456 # We do not abort the push, but just disable the local phase
457 457 # synchronisation.
458 458 msg = 'cannot lock source repository: %s\n' % err
459 459 pushop.ui.debug(msg)
460 460 try:
461 461 if pushop.locallocked:
462 462 pushop.trmanager = transactionmanager(pushop.repo,
463 463 'push-response',
464 464 pushop.remote.url())
465 465 pushop.repo.checkpush(pushop)
466 466 lock = None
467 467 unbundle = pushop.remote.capable('unbundle')
468 468 if not unbundle:
469 469 lock = pushop.remote.lock()
470 470 try:
471 471 _pushdiscovery(pushop)
472 472 if not _forcebundle1(pushop):
473 473 _pushbundle2(pushop)
474 474 _pushchangeset(pushop)
475 475 _pushsyncphase(pushop)
476 476 _pushobsolete(pushop)
477 477 _pushbookmark(pushop)
478 478 finally:
479 479 if lock is not None:
480 480 lock.release()
481 481 if pushop.trmanager:
482 482 pushop.trmanager.close()
483 483 finally:
484 484 if pushop.trmanager:
485 485 pushop.trmanager.release()
486 486 if locallock is not None:
487 487 locallock.release()
488 488 if localwlock is not None:
489 489 localwlock.release()
490 490
491 491 return pushop
492 492
493 493 # list of steps to perform discovery before push
494 494 pushdiscoveryorder = []
495 495
496 496 # Mapping between step name and function
497 497 #
498 498 # This exists to help extensions wrap steps if necessary
499 499 pushdiscoverymapping = {}
500 500
501 501 def pushdiscovery(stepname):
502 502 """decorator for function performing discovery before push
503 503
504 504 The function is added to the step -> function mapping and appended to the
505 505 list of steps. Beware that decorated function will be added in order (this
506 506 may matter).
507 507
508 508 You can only use this decorator for a new step, if you want to wrap a step
509 509 from an extension, change the pushdiscovery dictionary directly."""
510 510 def dec(func):
511 511 assert stepname not in pushdiscoverymapping
512 512 pushdiscoverymapping[stepname] = func
513 513 pushdiscoveryorder.append(stepname)
514 514 return func
515 515 return dec
516 516
517 517 def _pushdiscovery(pushop):
518 518 """Run all discovery steps"""
519 519 for stepname in pushdiscoveryorder:
520 520 step = pushdiscoverymapping[stepname]
521 521 step(pushop)
522 522
523 523 @pushdiscovery('changeset')
524 524 def _pushdiscoverychangeset(pushop):
525 525 """discover the changeset that need to be pushed"""
526 526 fci = discovery.findcommonincoming
527 527 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
528 528 common, inc, remoteheads = commoninc
529 529 fco = discovery.findcommonoutgoing
530 530 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
531 531 commoninc=commoninc, force=pushop.force)
532 532 pushop.outgoing = outgoing
533 533 pushop.remoteheads = remoteheads
534 534 pushop.incoming = inc
535 535
536 536 @pushdiscovery('phase')
537 537 def _pushdiscoveryphase(pushop):
538 538 """discover the phase that needs to be pushed
539 539
540 540 (computed for both success and failure case for changesets push)"""
541 541 outgoing = pushop.outgoing
542 542 unfi = pushop.repo.unfiltered()
543 543 remotephases = pushop.remote.listkeys('phases')
544 544 publishing = remotephases.get('publishing', False)
545 545 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
546 546 and remotephases # server supports phases
547 547 and not pushop.outgoing.missing # no changesets to be pushed
548 548 and publishing):
549 549 # When:
550 550 # - this is a subrepo push
551 551 # - and remote support phase
552 552 # - and no changeset are to be pushed
553 553 # - and remote is publishing
554 554 # We may be in issue 3871 case!
555 555 # We drop the possible phase synchronisation done by
556 556 # courtesy to publish changesets possibly locally draft
557 557 # on the remote.
558 558 remotephases = {'publishing': 'True'}
559 559 ana = phases.analyzeremotephases(pushop.repo,
560 560 pushop.fallbackheads,
561 561 remotephases)
562 562 pheads, droots = ana
563 563 extracond = ''
564 564 if not publishing:
565 565 extracond = ' and public()'
566 566 revset = 'heads((%%ln::%%ln) %s)' % extracond
567 567 # Get the list of all revs draft on remote by public here.
568 568 # XXX Beware that revset break if droots is not strictly
569 569 # XXX root we may want to ensure it is but it is costly
570 570 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
571 571 if not outgoing.missing:
572 572 future = fallback
573 573 else:
574 574 # adds changeset we are going to push as draft
575 575 #
576 576 # should not be necessary for publishing server, but because of an
577 577 # issue fixed in xxxxx we have to do it anyway.
578 578 fdroots = list(unfi.set('roots(%ln + %ln::)',
579 579 outgoing.missing, droots))
580 580 fdroots = [f.node() for f in fdroots]
581 581 future = list(unfi.set(revset, fdroots, pushop.futureheads))
582 582 pushop.outdatedphases = future
583 583 pushop.fallbackoutdatedphases = fallback
584 584
585 585 @pushdiscovery('obsmarker')
586 586 def _pushdiscoveryobsmarkers(pushop):
587 587 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
588 588 and pushop.repo.obsstore
589 589 and 'obsolete' in pushop.remote.listkeys('namespaces')):
590 590 repo = pushop.repo
591 591 # very naive computation, that can be quite expensive on big repo.
592 592 # However: evolution is currently slow on them anyway.
593 593 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
594 594 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
595 595
596 596 @pushdiscovery('bookmarks')
597 597 def _pushdiscoverybookmarks(pushop):
598 598 ui = pushop.ui
599 599 repo = pushop.repo.unfiltered()
600 600 remote = pushop.remote
601 601 ui.debug("checking for updated bookmarks\n")
602 602 ancestors = ()
603 603 if pushop.revs:
604 604 revnums = map(repo.changelog.rev, pushop.revs)
605 605 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
606 606 remotebookmark = remote.listkeys('bookmarks')
607 607
608 608 explicit = set([repo._bookmarks.expandname(bookmark)
609 609 for bookmark in pushop.bookmarks])
610 610
611 611 comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
612 612 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
613 613 for b, scid, dcid in advsrc:
614 614 if b in explicit:
615 615 explicit.remove(b)
616 616 if not ancestors or repo[scid].rev() in ancestors:
617 617 pushop.outbookmarks.append((b, dcid, scid))
618 618 # search added bookmark
619 619 for b, scid, dcid in addsrc:
620 620 if b in explicit:
621 621 explicit.remove(b)
622 622 pushop.outbookmarks.append((b, '', scid))
623 623 # search for overwritten bookmark
624 624 for b, scid, dcid in advdst + diverge + differ:
625 625 if b in explicit:
626 626 explicit.remove(b)
627 627 pushop.outbookmarks.append((b, dcid, scid))
628 628 # search for bookmark to delete
629 629 for b, scid, dcid in adddst:
630 630 if b in explicit:
631 631 explicit.remove(b)
632 632 # treat as "deleted locally"
633 633 pushop.outbookmarks.append((b, dcid, ''))
634 634 # identical bookmarks shouldn't get reported
635 635 for b, scid, dcid in same:
636 636 if b in explicit:
637 637 explicit.remove(b)
638 638
639 639 if explicit:
640 640 explicit = sorted(explicit)
641 641 # we should probably list all of them
642 642 ui.warn(_('bookmark %s does not exist on the local '
643 643 'or remote repository!\n') % explicit[0])
644 644 pushop.bkresult = 2
645 645
646 646 pushop.outbookmarks.sort()
647 647
648 648 def _pushcheckoutgoing(pushop):
649 649 outgoing = pushop.outgoing
650 650 unfi = pushop.repo.unfiltered()
651 651 if not outgoing.missing:
652 652 # nothing to push
653 653 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
654 654 return False
655 655 # something to push
656 656 if not pushop.force:
657 657 # if repo.obsstore == False --> no obsolete
658 658 # then, save the iteration
659 659 if unfi.obsstore:
660 660 # this message are here for 80 char limit reason
661 661 mso = _("push includes obsolete changeset: %s!")
662 662 mst = {"unstable": _("push includes unstable changeset: %s!"),
663 663 "bumped": _("push includes bumped changeset: %s!"),
664 664 "divergent": _("push includes divergent changeset: %s!")}
665 665 # If we are to push if there is at least one
666 666 # obsolete or unstable changeset in missing, at
667 667 # least one of the missinghead will be obsolete or
668 668 # unstable. So checking heads only is ok
669 669 for node in outgoing.missingheads:
670 670 ctx = unfi[node]
671 671 if ctx.obsolete():
672 672 raise error.Abort(mso % ctx)
673 673 elif ctx.troubled():
674 674 raise error.Abort(mst[ctx.troubles()[0]] % ctx)
675 675
676 676 discovery.checkheads(pushop)
677 677 return True
678 678
679 679 # List of names of steps to perform for an outgoing bundle2, order matters.
680 680 b2partsgenorder = []
681 681
682 682 # Mapping between step name and function
683 683 #
684 684 # This exists to help extensions wrap steps if necessary
685 685 b2partsgenmapping = {}
686 686
687 687 def b2partsgenerator(stepname, idx=None):
688 688 """decorator for function generating bundle2 part
689 689
690 690 The function is added to the step -> function mapping and appended to the
691 691 list of steps. Beware that decorated functions will be added in order
692 692 (this may matter).
693 693
694 694 You can only use this decorator for new steps, if you want to wrap a step
695 695 from an extension, attack the b2partsgenmapping dictionary directly."""
696 696 def dec(func):
697 697 assert stepname not in b2partsgenmapping
698 698 b2partsgenmapping[stepname] = func
699 699 if idx is None:
700 700 b2partsgenorder.append(stepname)
701 701 else:
702 702 b2partsgenorder.insert(idx, stepname)
703 703 return func
704 704 return dec
705 705
706 706 def _pushb2ctxcheckheads(pushop, bundler):
707 707 """Generate race condition checking parts
708 708
709 709 Exists as an independent function to aid extensions
710 710 """
711 711 if not pushop.force:
712 712 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
713 713
714 714 @b2partsgenerator('changeset')
715 715 def _pushb2ctx(pushop, bundler):
716 716 """handle changegroup push through bundle2
717 717
718 718 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
719 719 """
720 720 if 'changesets' in pushop.stepsdone:
721 721 return
722 722 pushop.stepsdone.add('changesets')
723 723 # Send known heads to the server for race detection.
724 724 if not _pushcheckoutgoing(pushop):
725 725 return
726 726 pushop.repo.prepushoutgoinghooks(pushop)
727 727
728 728 _pushb2ctxcheckheads(pushop, bundler)
729 729
730 730 b2caps = bundle2.bundle2caps(pushop.remote)
731 731 version = '01'
732 732 cgversions = b2caps.get('changegroup')
733 733 if cgversions: # 3.1 and 3.2 ship with an empty value
734 734 cgversions = [v for v in cgversions
735 735 if v in changegroup.supportedoutgoingversions(
736 736 pushop.repo)]
737 737 if not cgversions:
738 738 raise ValueError(_('no common changegroup version'))
739 739 version = max(cgversions)
740 740 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
741 741 pushop.outgoing,
742 742 version=version)
743 743 cgpart = bundler.newpart('changegroup', data=cg)
744 744 if cgversions:
745 745 cgpart.addparam('version', version)
746 746 if 'treemanifest' in pushop.repo.requirements:
747 747 cgpart.addparam('treemanifest', '1')
748 748 def handlereply(op):
749 749 """extract addchangegroup returns from server reply"""
750 750 cgreplies = op.records.getreplies(cgpart.id)
751 751 assert len(cgreplies['changegroup']) == 1
752 752 pushop.cgresult = cgreplies['changegroup'][0]['return']
753 753 return handlereply
754 754
755 755 @b2partsgenerator('phase')
756 756 def _pushb2phases(pushop, bundler):
757 757 """handle phase push through bundle2"""
758 758 if 'phases' in pushop.stepsdone:
759 759 return
760 760 b2caps = bundle2.bundle2caps(pushop.remote)
761 761 if not 'pushkey' in b2caps:
762 762 return
763 763 pushop.stepsdone.add('phases')
764 764 part2node = []
765 765
766 766 def handlefailure(pushop, exc):
767 767 targetid = int(exc.partid)
768 768 for partid, node in part2node:
769 769 if partid == targetid:
770 770 raise error.Abort(_('updating %s to public failed') % node)
771 771
772 772 enc = pushkey.encode
773 773 for newremotehead in pushop.outdatedphases:
774 774 part = bundler.newpart('pushkey')
775 775 part.addparam('namespace', enc('phases'))
776 776 part.addparam('key', enc(newremotehead.hex()))
777 777 part.addparam('old', enc(str(phases.draft)))
778 778 part.addparam('new', enc(str(phases.public)))
779 779 part2node.append((part.id, newremotehead))
780 780 pushop.pkfailcb[part.id] = handlefailure
781 781
782 782 def handlereply(op):
783 783 for partid, node in part2node:
784 784 partrep = op.records.getreplies(partid)
785 785 results = partrep['pushkey']
786 786 assert len(results) <= 1
787 787 msg = None
788 788 if not results:
789 789 msg = _('server ignored update of %s to public!\n') % node
790 790 elif not int(results[0]['return']):
791 791 msg = _('updating %s to public failed!\n') % node
792 792 if msg is not None:
793 793 pushop.ui.warn(msg)
794 794 return handlereply
795 795
796 796 @b2partsgenerator('obsmarkers')
797 797 def _pushb2obsmarkers(pushop, bundler):
798 798 if 'obsmarkers' in pushop.stepsdone:
799 799 return
800 800 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
801 801 if obsolete.commonversion(remoteversions) is None:
802 802 return
803 803 pushop.stepsdone.add('obsmarkers')
804 804 if pushop.outobsmarkers:
805 805 markers = sorted(pushop.outobsmarkers)
806 806 buildobsmarkerspart(bundler, markers)
807 807
808 808 @b2partsgenerator('bookmarks')
809 809 def _pushb2bookmarks(pushop, bundler):
810 810 """handle bookmark push through bundle2"""
811 811 if 'bookmarks' in pushop.stepsdone:
812 812 return
813 813 b2caps = bundle2.bundle2caps(pushop.remote)
814 814 if 'pushkey' not in b2caps:
815 815 return
816 816 pushop.stepsdone.add('bookmarks')
817 817 part2book = []
818 818 enc = pushkey.encode
819 819
820 820 def handlefailure(pushop, exc):
821 821 targetid = int(exc.partid)
822 822 for partid, book, action in part2book:
823 823 if partid == targetid:
824 824 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
825 825 # we should not be called for part we did not generated
826 826 assert False
827 827
828 828 for book, old, new in pushop.outbookmarks:
829 829 part = bundler.newpart('pushkey')
830 830 part.addparam('namespace', enc('bookmarks'))
831 831 part.addparam('key', enc(book))
832 832 part.addparam('old', enc(old))
833 833 part.addparam('new', enc(new))
834 834 action = 'update'
835 835 if not old:
836 836 action = 'export'
837 837 elif not new:
838 838 action = 'delete'
839 839 part2book.append((part.id, book, action))
840 840 pushop.pkfailcb[part.id] = handlefailure
841 841
842 842 def handlereply(op):
843 843 ui = pushop.ui
844 844 for partid, book, action in part2book:
845 845 partrep = op.records.getreplies(partid)
846 846 results = partrep['pushkey']
847 847 assert len(results) <= 1
848 848 if not results:
849 849 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
850 850 else:
851 851 ret = int(results[0]['return'])
852 852 if ret:
853 853 ui.status(bookmsgmap[action][0] % book)
854 854 else:
855 855 ui.warn(bookmsgmap[action][1] % book)
856 856 if pushop.bkresult is not None:
857 857 pushop.bkresult = 1
858 858 return handlereply
859 859
860 860
861 861 def _pushbundle2(pushop):
862 862 """push data to the remote using bundle2
863 863
864 864 The only currently supported type of data is changegroup but this will
865 865 evolve in the future."""
866 866 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
867 867 pushback = (pushop.trmanager
868 868 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
869 869
870 870 # create reply capability
871 871 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
872 872 allowpushback=pushback))
873 873 bundler.newpart('replycaps', data=capsblob)
874 874 replyhandlers = []
875 875 for partgenname in b2partsgenorder:
876 876 partgen = b2partsgenmapping[partgenname]
877 877 ret = partgen(pushop, bundler)
878 878 if callable(ret):
879 879 replyhandlers.append(ret)
880 880 # do not push if nothing to push
881 881 if bundler.nbparts <= 1:
882 882 return
883 883 stream = util.chunkbuffer(bundler.getchunks())
884 884 try:
885 885 try:
886 886 reply = pushop.remote.unbundle(
887 887 stream, ['force'], pushop.remote.url())
888 888 except error.BundleValueError as exc:
889 889 raise error.Abort(_('missing support for %s') % exc)
890 890 try:
891 891 trgetter = None
892 892 if pushback:
893 893 trgetter = pushop.trmanager.transaction
894 894 op = bundle2.processbundle(pushop.repo, reply, trgetter)
895 895 except error.BundleValueError as exc:
896 896 raise error.Abort(_('missing support for %s') % exc)
897 897 except bundle2.AbortFromPart as exc:
898 898 pushop.ui.status(_('remote: %s\n') % exc)
899 899 raise error.Abort(_('push failed on remote'), hint=exc.hint)
900 900 except error.PushkeyFailed as exc:
901 901 partid = int(exc.partid)
902 902 if partid not in pushop.pkfailcb:
903 903 raise
904 904 pushop.pkfailcb[partid](pushop, exc)
905 905 for rephand in replyhandlers:
906 906 rephand(op)
907 907
908 908 def _pushchangeset(pushop):
909 909 """Make the actual push of changeset bundle to remote repo"""
910 910 if 'changesets' in pushop.stepsdone:
911 911 return
912 912 pushop.stepsdone.add('changesets')
913 913 if not _pushcheckoutgoing(pushop):
914 914 return
915 915 pushop.repo.prepushoutgoinghooks(pushop)
916 916 outgoing = pushop.outgoing
917 917 unbundle = pushop.remote.capable('unbundle')
918 918 # TODO: get bundlecaps from remote
919 919 bundlecaps = None
920 920 # create a changegroup from local
921 921 if pushop.revs is None and not (outgoing.excluded
922 922 or pushop.repo.changelog.filteredrevs):
923 923 # push everything,
924 924 # use the fast path, no race possible on push
925 925 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
926 926 cg = changegroup.getsubset(pushop.repo,
927 927 outgoing,
928 928 bundler,
929 929 'push',
930 930 fastpath=True)
931 931 else:
932 932 cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
933 933 bundlecaps)
934 934
935 935 # apply changegroup to remote
936 936 if unbundle:
937 937 # local repo finds heads on server, finds out what
938 938 # revs it must push. once revs transferred, if server
939 939 # finds it has different heads (someone else won
940 940 # commit/push race), server aborts.
941 941 if pushop.force:
942 942 remoteheads = ['force']
943 943 else:
944 944 remoteheads = pushop.remoteheads
945 945 # ssh: return remote's addchangegroup()
946 946 # http: return remote's addchangegroup() or 0 for error
947 947 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
948 948 pushop.repo.url())
949 949 else:
950 950 # we return an integer indicating remote head count
951 951 # change
952 952 pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
953 953 pushop.repo.url())
954 954
955 955 def _pushsyncphase(pushop):
956 956 """synchronise phase information locally and remotely"""
957 957 cheads = pushop.commonheads
958 958 # even when we don't push, exchanging phase data is useful
959 959 remotephases = pushop.remote.listkeys('phases')
960 960 if (pushop.ui.configbool('ui', '_usedassubrepo', False)
961 961 and remotephases # server supports phases
962 962 and pushop.cgresult is None # nothing was pushed
963 963 and remotephases.get('publishing', False)):
964 964 # When:
965 965 # - this is a subrepo push
966 966 # - and remote support phase
967 967 # - and no changeset was pushed
968 968 # - and remote is publishing
969 969 # We may be in issue 3871 case!
970 970 # We drop the possible phase synchronisation done by
971 971 # courtesy to publish changesets possibly locally draft
972 972 # on the remote.
973 973 remotephases = {'publishing': 'True'}
974 974 if not remotephases: # old server or public only reply from non-publishing
975 975 _localphasemove(pushop, cheads)
976 976 # don't push any phase data as there is nothing to push
977 977 else:
978 978 ana = phases.analyzeremotephases(pushop.repo, cheads,
979 979 remotephases)
980 980 pheads, droots = ana
981 981 ### Apply remote phase on local
982 982 if remotephases.get('publishing', False):
983 983 _localphasemove(pushop, cheads)
984 984 else: # publish = False
985 985 _localphasemove(pushop, pheads)
986 986 _localphasemove(pushop, cheads, phases.draft)
987 987 ### Apply local phase on remote
988 988
989 989 if pushop.cgresult:
990 990 if 'phases' in pushop.stepsdone:
991 991 # phases already pushed though bundle2
992 992 return
993 993 outdated = pushop.outdatedphases
994 994 else:
995 995 outdated = pushop.fallbackoutdatedphases
996 996
997 997 pushop.stepsdone.add('phases')
998 998
999 999 # filter heads already turned public by the push
1000 1000 outdated = [c for c in outdated if c.node() not in pheads]
1001 1001 # fallback to independent pushkey command
1002 1002 for newremotehead in outdated:
1003 1003 r = pushop.remote.pushkey('phases',
1004 1004 newremotehead.hex(),
1005 1005 str(phases.draft),
1006 1006 str(phases.public))
1007 1007 if not r:
1008 1008 pushop.ui.warn(_('updating %s to public failed!\n')
1009 1009 % newremotehead)
1010 1010
1011 1011 def _localphasemove(pushop, nodes, phase=phases.public):
1012 1012 """move <nodes> to <phase> in the local source repo"""
1013 1013 if pushop.trmanager:
1014 1014 phases.advanceboundary(pushop.repo,
1015 1015 pushop.trmanager.transaction(),
1016 1016 phase,
1017 1017 nodes)
1018 1018 else:
1019 1019 # repo is not locked, do not change any phases!
1020 1020 # Informs the user that phases should have been moved when
1021 1021 # applicable.
1022 1022 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1023 1023 phasestr = phases.phasenames[phase]
1024 1024 if actualmoves:
1025 1025 pushop.ui.status(_('cannot lock source repo, skipping '
1026 1026 'local %s phase update\n') % phasestr)
1027 1027
1028 1028 def _pushobsolete(pushop):
1029 1029 """utility function to push obsolete markers to a remote"""
1030 1030 if 'obsmarkers' in pushop.stepsdone:
1031 1031 return
1032 1032 repo = pushop.repo
1033 1033 remote = pushop.remote
1034 1034 pushop.stepsdone.add('obsmarkers')
1035 1035 if pushop.outobsmarkers:
1036 1036 pushop.ui.debug('try to push obsolete markers to remote\n')
1037 1037 rslts = []
1038 1038 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1039 1039 for key in sorted(remotedata, reverse=True):
1040 1040 # reverse sort to ensure we end with dump0
1041 1041 data = remotedata[key]
1042 1042 rslts.append(remote.pushkey('obsolete', key, '', data))
1043 1043 if [r for r in rslts if not r]:
1044 1044 msg = _('failed to push some obsolete markers!\n')
1045 1045 repo.ui.warn(msg)
1046 1046
1047 1047 def _pushbookmark(pushop):
1048 1048 """Update bookmark position on remote"""
1049 1049 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1050 1050 return
1051 1051 pushop.stepsdone.add('bookmarks')
1052 1052 ui = pushop.ui
1053 1053 remote = pushop.remote
1054 1054
1055 1055 for b, old, new in pushop.outbookmarks:
1056 1056 action = 'update'
1057 1057 if not old:
1058 1058 action = 'export'
1059 1059 elif not new:
1060 1060 action = 'delete'
1061 1061 if remote.pushkey('bookmarks', b, old, new):
1062 1062 ui.status(bookmsgmap[action][0] % b)
1063 1063 else:
1064 1064 ui.warn(bookmsgmap[action][1] % b)
1065 1065 # discovery can have set the value form invalid entry
1066 1066 if pushop.bkresult is not None:
1067 1067 pushop.bkresult = 1
1068 1068
1069 1069 class pulloperation(object):
1070 1070 """A object that represent a single pull operation
1071 1071
1072 1072 It purpose is to carry pull related state and very common operation.
1073 1073
1074 1074 A new should be created at the beginning of each pull and discarded
1075 1075 afterward.
1076 1076 """
1077 1077
1078 1078 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1079 1079 remotebookmarks=None, streamclonerequested=None):
1080 1080 # repo we pull into
1081 1081 self.repo = repo
1082 1082 # repo we pull from
1083 1083 self.remote = remote
1084 1084 # revision we try to pull (None is "all")
1085 1085 self.heads = heads
1086 1086 # bookmark pulled explicitly
1087 1087 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1088 1088 for bookmark in bookmarks]
1089 1089 # do we force pull?
1090 1090 self.force = force
1091 1091 # whether a streaming clone was requested
1092 1092 self.streamclonerequested = streamclonerequested
1093 1093 # transaction manager
1094 1094 self.trmanager = None
1095 1095 # set of common changeset between local and remote before pull
1096 1096 self.common = None
1097 1097 # set of pulled head
1098 1098 self.rheads = None
1099 1099 # list of missing changeset to fetch remotely
1100 1100 self.fetch = None
1101 1101 # remote bookmarks data
1102 1102 self.remotebookmarks = remotebookmarks
1103 1103 # result of changegroup pulling (used as return code by pull)
1104 1104 self.cgresult = None
1105 1105 # list of step already done
1106 1106 self.stepsdone = set()
1107 1107 # Whether we attempted a clone from pre-generated bundles.
1108 1108 self.clonebundleattempted = False
1109 1109
1110 1110 @util.propertycache
1111 1111 def pulledsubset(self):
1112 1112 """heads of the set of changeset target by the pull"""
1113 1113 # compute target subset
1114 1114 if self.heads is None:
1115 1115 # We pulled every thing possible
1116 1116 # sync on everything common
1117 1117 c = set(self.common)
1118 1118 ret = list(self.common)
1119 1119 for n in self.rheads:
1120 1120 if n not in c:
1121 1121 ret.append(n)
1122 1122 return ret
1123 1123 else:
1124 1124 # We pulled a specific subset
1125 1125 # sync on this subset
1126 1126 return self.heads
1127 1127
1128 1128 @util.propertycache
1129 1129 def canusebundle2(self):
1130 1130 return not _forcebundle1(self)
1131 1131
1132 1132 @util.propertycache
1133 1133 def remotebundle2caps(self):
1134 1134 return bundle2.bundle2caps(self.remote)
1135 1135
1136 1136 def gettransaction(self):
1137 1137 # deprecated; talk to trmanager directly
1138 1138 return self.trmanager.transaction()
1139 1139
1140 1140 class transactionmanager(object):
1141 1141 """An object to manage the life cycle of a transaction
1142 1142
1143 1143 It creates the transaction on demand and calls the appropriate hooks when
1144 1144 closing the transaction."""
1145 1145 def __init__(self, repo, source, url):
1146 1146 self.repo = repo
1147 1147 self.source = source
1148 1148 self.url = url
1149 1149 self._tr = None
1150 1150
1151 1151 def transaction(self):
1152 1152 """Return an open transaction object, constructing if necessary"""
1153 1153 if not self._tr:
1154 1154 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1155 1155 self._tr = self.repo.transaction(trname)
1156 1156 self._tr.hookargs['source'] = self.source
1157 1157 self._tr.hookargs['url'] = self.url
1158 1158 return self._tr
1159 1159
1160 1160 def close(self):
1161 1161 """close transaction if created"""
1162 1162 if self._tr is not None:
1163 1163 self._tr.close()
1164 1164
1165 1165 def release(self):
1166 1166 """release transaction if created"""
1167 1167 if self._tr is not None:
1168 1168 self._tr.release()
1169 1169
1170 1170 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1171 1171 streamclonerequested=None):
1172 1172 """Fetch repository data from a remote.
1173 1173
1174 1174 This is the main function used to retrieve data from a remote repository.
1175 1175
1176 1176 ``repo`` is the local repository to clone into.
1177 1177 ``remote`` is a peer instance.
1178 1178 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1179 1179 default) means to pull everything from the remote.
1180 1180 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1181 1181 default, all remote bookmarks are pulled.
1182 1182 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1183 1183 initialization.
1184 1184 ``streamclonerequested`` is a boolean indicating whether a "streaming
1185 1185 clone" is requested. A "streaming clone" is essentially a raw file copy
1186 1186 of revlogs from the server. This only works when the local repository is
1187 1187 empty. The default value of ``None`` means to respect the server
1188 1188 configuration for preferring stream clones.
1189 1189
1190 1190 Returns the ``pulloperation`` created for this pull.
1191 1191 """
1192 1192 if opargs is None:
1193 1193 opargs = {}
1194 1194 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1195 1195 streamclonerequested=streamclonerequested, **opargs)
1196 1196 if pullop.remote.local():
1197 1197 missing = set(pullop.remote.requirements) - pullop.repo.supported
1198 1198 if missing:
1199 1199 msg = _("required features are not"
1200 1200 " supported in the destination:"
1201 1201 " %s") % (', '.join(sorted(missing)))
1202 1202 raise error.Abort(msg)
1203 1203
1204 1204 wlock = lock = None
1205 1205 try:
1206 1206 wlock = pullop.repo.wlock()
1207 1207 lock = pullop.repo.lock()
1208 1208 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1209 1209 streamclone.maybeperformlegacystreamclone(pullop)
1210 1210 # This should ideally be in _pullbundle2(). However, it needs to run
1211 1211 # before discovery to avoid extra work.
1212 1212 _maybeapplyclonebundle(pullop)
1213 1213 _pulldiscovery(pullop)
1214 1214 if pullop.canusebundle2:
1215 1215 _pullbundle2(pullop)
1216 1216 _pullchangeset(pullop)
1217 1217 _pullphase(pullop)
1218 1218 _pullbookmarks(pullop)
1219 1219 _pullobsolete(pullop)
1220 1220 pullop.trmanager.close()
1221 1221 finally:
1222 1222 lockmod.release(pullop.trmanager, lock, wlock)
1223 1223
1224 1224 return pullop
1225 1225
1226 1226 # list of steps to perform discovery before pull
1227 1227 pulldiscoveryorder = []
1228 1228
1229 1229 # Mapping between step name and function
1230 1230 #
1231 1231 # This exists to help extensions wrap steps if necessary
1232 1232 pulldiscoverymapping = {}
1233 1233
1234 1234 def pulldiscovery(stepname):
1235 1235 """decorator for function performing discovery before pull
1236 1236
1237 1237 The function is added to the step -> function mapping and appended to the
1238 1238 list of steps. Beware that decorated function will be added in order (this
1239 1239 may matter).
1240 1240
1241 1241 You can only use this decorator for a new step, if you want to wrap a step
1242 1242 from an extension, change the pulldiscovery dictionary directly."""
1243 1243 def dec(func):
1244 1244 assert stepname not in pulldiscoverymapping
1245 1245 pulldiscoverymapping[stepname] = func
1246 1246 pulldiscoveryorder.append(stepname)
1247 1247 return func
1248 1248 return dec
1249 1249
1250 1250 def _pulldiscovery(pullop):
1251 1251 """Run all discovery steps"""
1252 1252 for stepname in pulldiscoveryorder:
1253 1253 step = pulldiscoverymapping[stepname]
1254 1254 step(pullop)
1255 1255
1256 1256 @pulldiscovery('b1:bookmarks')
1257 1257 def _pullbookmarkbundle1(pullop):
1258 1258 """fetch bookmark data in bundle1 case
1259 1259
1260 1260 If not using bundle2, we have to fetch bookmarks before changeset
1261 1261 discovery to reduce the chance and impact of race conditions."""
1262 1262 if pullop.remotebookmarks is not None:
1263 1263 return
1264 1264 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1265 1265 # all known bundle2 servers now support listkeys, but lets be nice with
1266 1266 # new implementation.
1267 1267 return
1268 1268 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1269 1269
1270 1270
1271 1271 @pulldiscovery('changegroup')
1272 1272 def _pulldiscoverychangegroup(pullop):
1273 1273 """discovery phase for the pull
1274 1274
1275 1275 Current handle changeset discovery only, will change handle all discovery
1276 1276 at some point."""
1277 1277 tmp = discovery.findcommonincoming(pullop.repo,
1278 1278 pullop.remote,
1279 1279 heads=pullop.heads,
1280 1280 force=pullop.force)
1281 1281 common, fetch, rheads = tmp
1282 1282 nm = pullop.repo.unfiltered().changelog.nodemap
1283 1283 if fetch and rheads:
1284 1284 # If a remote heads in filtered locally, lets drop it from the unknown
1285 1285 # remote heads and put in back in common.
1286 1286 #
1287 1287 # This is a hackish solution to catch most of "common but locally
1288 1288 # hidden situation". We do not performs discovery on unfiltered
1289 1289 # repository because it end up doing a pathological amount of round
1290 1290 # trip for w huge amount of changeset we do not care about.
1291 1291 #
1292 1292 # If a set of such "common but filtered" changeset exist on the server
1293 1293 # but are not including a remote heads, we'll not be able to detect it,
1294 1294 scommon = set(common)
1295 1295 filteredrheads = []
1296 1296 for n in rheads:
1297 1297 if n in nm:
1298 1298 if n not in scommon:
1299 1299 common.append(n)
1300 1300 else:
1301 1301 filteredrheads.append(n)
1302 1302 if not filteredrheads:
1303 1303 fetch = []
1304 1304 rheads = filteredrheads
1305 1305 pullop.common = common
1306 1306 pullop.fetch = fetch
1307 1307 pullop.rheads = rheads
1308 1308
1309 1309 def _pullbundle2(pullop):
1310 1310 """pull data using bundle2
1311 1311
1312 1312 For now, the only supported data are changegroup."""
1313 1313 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1314 1314
1315 1315 streaming, streamreqs = streamclone.canperformstreamclone(pullop)
1316 1316
1317 1317 # pulling changegroup
1318 1318 pullop.stepsdone.add('changegroup')
1319 1319
1320 1320 kwargs['common'] = pullop.common
1321 1321 kwargs['heads'] = pullop.heads or pullop.rheads
1322 1322 kwargs['cg'] = pullop.fetch
1323 1323 if 'listkeys' in pullop.remotebundle2caps:
1324 1324 kwargs['listkeys'] = ['phases']
1325 1325 if pullop.remotebookmarks is None:
1326 1326 # make sure to always includes bookmark data when migrating
1327 1327 # `hg incoming --bundle` to using this function.
1328 1328 kwargs['listkeys'].append('bookmarks')
1329 1329
1330 1330 # If this is a full pull / clone and the server supports the clone bundles
1331 1331 # feature, tell the server whether we attempted a clone bundle. The
1332 1332 # presence of this flag indicates the client supports clone bundles. This
1333 1333 # will enable the server to treat clients that support clone bundles
1334 1334 # differently from those that don't.
1335 1335 if (pullop.remote.capable('clonebundles')
1336 1336 and pullop.heads is None and list(pullop.common) == [nullid]):
1337 1337 kwargs['cbattempted'] = pullop.clonebundleattempted
1338 1338
1339 1339 if streaming:
1340 1340 pullop.repo.ui.status(_('streaming all changes\n'))
1341 1341 elif not pullop.fetch:
1342 1342 pullop.repo.ui.status(_("no changes found\n"))
1343 1343 pullop.cgresult = 0
1344 1344 else:
1345 1345 if pullop.heads is None and list(pullop.common) == [nullid]:
1346 1346 pullop.repo.ui.status(_("requesting all changes\n"))
1347 1347 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1348 1348 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1349 1349 if obsolete.commonversion(remoteversions) is not None:
1350 1350 kwargs['obsmarkers'] = True
1351 1351 pullop.stepsdone.add('obsmarkers')
1352 1352 _pullbundle2extraprepare(pullop, kwargs)
1353 1353 bundle = pullop.remote.getbundle('pull', **kwargs)
1354 1354 try:
1355 1355 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1356 1356 except error.BundleValueError as exc:
1357 1357 raise error.Abort(_('missing support for %s') % exc)
1358 1358
1359 1359 if pullop.fetch:
1360 1360 results = [cg['return'] for cg in op.records['changegroup']]
1361 1361 pullop.cgresult = changegroup.combineresults(results)
1362 1362
1363 1363 # processing phases change
1364 1364 for namespace, value in op.records['listkeys']:
1365 1365 if namespace == 'phases':
1366 1366 _pullapplyphases(pullop, value)
1367 1367
1368 1368 # processing bookmark update
1369 1369 for namespace, value in op.records['listkeys']:
1370 1370 if namespace == 'bookmarks':
1371 1371 pullop.remotebookmarks = value
1372 1372
1373 1373 # bookmark data were either already there or pulled in the bundle
1374 1374 if pullop.remotebookmarks is not None:
1375 1375 _pullbookmarks(pullop)
1376 1376
1377 1377 def _pullbundle2extraprepare(pullop, kwargs):
1378 1378 """hook function so that extensions can extend the getbundle call"""
1379 1379 pass
1380 1380
1381 1381 def _pullchangeset(pullop):
1382 1382 """pull changeset from unbundle into the local repo"""
1383 1383 # We delay the open of the transaction as late as possible so we
1384 1384 # don't open transaction for nothing or you break future useful
1385 1385 # rollback call
1386 1386 if 'changegroup' in pullop.stepsdone:
1387 1387 return
1388 1388 pullop.stepsdone.add('changegroup')
1389 1389 if not pullop.fetch:
1390 1390 pullop.repo.ui.status(_("no changes found\n"))
1391 1391 pullop.cgresult = 0
1392 1392 return
1393 1393 pullop.gettransaction()
1394 1394 if pullop.heads is None and list(pullop.common) == [nullid]:
1395 1395 pullop.repo.ui.status(_("requesting all changes\n"))
1396 1396 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1397 1397 # issue1320, avoid a race if remote changed after discovery
1398 1398 pullop.heads = pullop.rheads
1399 1399
1400 1400 if pullop.remote.capable('getbundle'):
1401 1401 # TODO: get bundlecaps from remote
1402 1402 cg = pullop.remote.getbundle('pull', common=pullop.common,
1403 1403 heads=pullop.heads or pullop.rheads)
1404 1404 elif pullop.heads is None:
1405 1405 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1406 1406 elif not pullop.remote.capable('changegroupsubset'):
1407 1407 raise error.Abort(_("partial pull cannot be done because "
1408 1408 "other repository doesn't support "
1409 1409 "changegroupsubset."))
1410 1410 else:
1411 1411 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1412 1412 pullop.cgresult = cg.apply(pullop.repo, 'pull', pullop.remote.url())
1413 1413
1414 1414 def _pullphase(pullop):
1415 1415 # Get remote phases data from remote
1416 1416 if 'phases' in pullop.stepsdone:
1417 1417 return
1418 1418 remotephases = pullop.remote.listkeys('phases')
1419 1419 _pullapplyphases(pullop, remotephases)
1420 1420
1421 1421 def _pullapplyphases(pullop, remotephases):
1422 1422 """apply phase movement from observed remote state"""
1423 1423 if 'phases' in pullop.stepsdone:
1424 1424 return
1425 1425 pullop.stepsdone.add('phases')
1426 1426 publishing = bool(remotephases.get('publishing', False))
1427 1427 if remotephases and not publishing:
1428 1428 # remote is new and unpublishing
1429 1429 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1430 1430 pullop.pulledsubset,
1431 1431 remotephases)
1432 1432 dheads = pullop.pulledsubset
1433 1433 else:
1434 1434 # Remote is old or publishing all common changesets
1435 1435 # should be seen as public
1436 1436 pheads = pullop.pulledsubset
1437 1437 dheads = []
1438 1438 unfi = pullop.repo.unfiltered()
1439 1439 phase = unfi._phasecache.phase
1440 1440 rev = unfi.changelog.nodemap.get
1441 1441 public = phases.public
1442 1442 draft = phases.draft
1443 1443
1444 1444 # exclude changesets already public locally and update the others
1445 1445 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1446 1446 if pheads:
1447 1447 tr = pullop.gettransaction()
1448 1448 phases.advanceboundary(pullop.repo, tr, public, pheads)
1449 1449
1450 1450 # exclude changesets already draft locally and update the others
1451 1451 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1452 1452 if dheads:
1453 1453 tr = pullop.gettransaction()
1454 1454 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1455 1455
1456 1456 def _pullbookmarks(pullop):
1457 1457 """process the remote bookmark information to update the local one"""
1458 1458 if 'bookmarks' in pullop.stepsdone:
1459 1459 return
1460 1460 pullop.stepsdone.add('bookmarks')
1461 1461 repo = pullop.repo
1462 1462 remotebookmarks = pullop.remotebookmarks
1463 1463 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1464 1464 pullop.remote.url(),
1465 1465 pullop.gettransaction,
1466 1466 explicit=pullop.explicitbookmarks)
1467 1467
1468 1468 def _pullobsolete(pullop):
1469 1469 """utility function to pull obsolete markers from a remote
1470 1470
1471 1471 The `gettransaction` is function that return the pull transaction, creating
1472 1472 one if necessary. We return the transaction to inform the calling code that
1473 1473 a new transaction have been created (when applicable).
1474 1474
1475 1475 Exists mostly to allow overriding for experimentation purpose"""
1476 1476 if 'obsmarkers' in pullop.stepsdone:
1477 1477 return
1478 1478 pullop.stepsdone.add('obsmarkers')
1479 1479 tr = None
1480 1480 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1481 1481 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1482 1482 remoteobs = pullop.remote.listkeys('obsolete')
1483 1483 if 'dump0' in remoteobs:
1484 1484 tr = pullop.gettransaction()
1485 1485 markers = []
1486 1486 for key in sorted(remoteobs, reverse=True):
1487 1487 if key.startswith('dump'):
1488 1488 data = base85.b85decode(remoteobs[key])
1489 1489 version, newmarks = obsolete._readmarkers(data)
1490 1490 markers += newmarks
1491 1491 if markers:
1492 1492 pullop.repo.obsstore.add(tr, markers)
1493 1493 pullop.repo.invalidatevolatilesets()
1494 1494 return tr
1495 1495
1496 1496 def caps20to10(repo):
1497 1497 """return a set with appropriate options to use bundle20 during getbundle"""
1498 1498 caps = set(['HG20'])
1499 1499 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1500 1500 caps.add('bundle2=' + urlreq.quote(capsblob))
1501 1501 return caps
1502 1502
1503 1503 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1504 1504 getbundle2partsorder = []
1505 1505
1506 1506 # Mapping between step name and function
1507 1507 #
1508 1508 # This exists to help extensions wrap steps if necessary
1509 1509 getbundle2partsmapping = {}
1510 1510
1511 1511 def getbundle2partsgenerator(stepname, idx=None):
1512 1512 """decorator for function generating bundle2 part for getbundle
1513 1513
1514 1514 The function is added to the step -> function mapping and appended to the
1515 1515 list of steps. Beware that decorated functions will be added in order
1516 1516 (this may matter).
1517 1517
1518 1518 You can only use this decorator for new steps, if you want to wrap a step
1519 1519 from an extension, attack the getbundle2partsmapping dictionary directly."""
1520 1520 def dec(func):
1521 1521 assert stepname not in getbundle2partsmapping
1522 1522 getbundle2partsmapping[stepname] = func
1523 1523 if idx is None:
1524 1524 getbundle2partsorder.append(stepname)
1525 1525 else:
1526 1526 getbundle2partsorder.insert(idx, stepname)
1527 1527 return func
1528 1528 return dec
1529 1529
1530 1530 def bundle2requested(bundlecaps):
1531 1531 if bundlecaps is not None:
1532 1532 return any(cap.startswith('HG2') for cap in bundlecaps)
1533 1533 return False
1534 1534
1535 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
1536 **kwargs):
1537 """return a full bundle (with potentially multiple kind of parts)
1535 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1536 **kwargs):
1537 """Return chunks constituting a bundle's raw data.
1538 1538
1539 1539 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1540 passed. For now, the bundle can contain only changegroup, but this will
1541 changes when more part type will be available for bundle2.
1540 passed.
1542 1541
1543 This is different from changegroup.getchangegroup that only returns an HG10
1544 changegroup bundle. They may eventually get reunited in the future when we
1545 have a clearer idea of the API we what to query different data.
1546
1547 The implementation is at a very early stage and will get massive rework
1548 when the API of bundle is refined.
1542 Returns an iterator over raw chunks (of varying sizes).
1549 1543 """
1550 1544 usebundle2 = bundle2requested(bundlecaps)
1551 1545 # bundle10 case
1552 1546 if not usebundle2:
1553 1547 if bundlecaps and not kwargs.get('cg', True):
1554 1548 raise ValueError(_('request for bundle10 must include changegroup'))
1555 1549
1556 1550 if kwargs:
1557 1551 raise ValueError(_('unsupported getbundle arguments: %s')
1558 1552 % ', '.join(sorted(kwargs.keys())))
1559 1553 outgoing = _computeoutgoing(repo, heads, common)
1560 return changegroup.getchangegroup(repo, source, outgoing,
1561 bundlecaps=bundlecaps)
1554 bundler = changegroup.getbundler('01', repo, bundlecaps)
1555 return changegroup.getsubsetraw(repo, outgoing, bundler, source)
1562 1556
1563 1557 # bundle20 case
1564 1558 b2caps = {}
1565 1559 for bcaps in bundlecaps:
1566 1560 if bcaps.startswith('bundle2='):
1567 1561 blob = urlreq.unquote(bcaps[len('bundle2='):])
1568 1562 b2caps.update(bundle2.decodecaps(blob))
1569 1563 bundler = bundle2.bundle20(repo.ui, b2caps)
1570 1564
1571 1565 kwargs['heads'] = heads
1572 1566 kwargs['common'] = common
1573 1567
1574 1568 for name in getbundle2partsorder:
1575 1569 func = getbundle2partsmapping[name]
1576 1570 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1577 1571 **kwargs)
1578 1572
1579 return util.chunkbuffer(bundler.getchunks())
1573 return bundler.getchunks()
1580 1574
1581 1575 @getbundle2partsgenerator('changegroup')
1582 1576 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1583 1577 b2caps=None, heads=None, common=None, **kwargs):
1584 1578 """add a changegroup part to the requested bundle"""
1585 1579 cg = None
1586 1580 if kwargs.get('cg', True):
1587 1581 # build changegroup bundle here.
1588 1582 version = '01'
1589 1583 cgversions = b2caps.get('changegroup')
1590 1584 if cgversions: # 3.1 and 3.2 ship with an empty value
1591 1585 cgversions = [v for v in cgversions
1592 1586 if v in changegroup.supportedoutgoingversions(repo)]
1593 1587 if not cgversions:
1594 1588 raise ValueError(_('no common changegroup version'))
1595 1589 version = max(cgversions)
1596 1590 outgoing = _computeoutgoing(repo, heads, common)
1597 1591 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1598 1592 bundlecaps=bundlecaps,
1599 1593 version=version)
1600 1594
1601 1595 if cg:
1602 1596 part = bundler.newpart('changegroup', data=cg)
1603 1597 if cgversions:
1604 1598 part.addparam('version', version)
1605 1599 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1606 1600 if 'treemanifest' in repo.requirements:
1607 1601 part.addparam('treemanifest', '1')
1608 1602
1609 1603 @getbundle2partsgenerator('listkeys')
1610 1604 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1611 1605 b2caps=None, **kwargs):
1612 1606 """add parts containing listkeys namespaces to the requested bundle"""
1613 1607 listkeys = kwargs.get('listkeys', ())
1614 1608 for namespace in listkeys:
1615 1609 part = bundler.newpart('listkeys')
1616 1610 part.addparam('namespace', namespace)
1617 1611 keys = repo.listkeys(namespace).items()
1618 1612 part.data = pushkey.encodekeys(keys)
1619 1613
1620 1614 @getbundle2partsgenerator('obsmarkers')
1621 1615 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1622 1616 b2caps=None, heads=None, **kwargs):
1623 1617 """add an obsolescence markers part to the requested bundle"""
1624 1618 if kwargs.get('obsmarkers', False):
1625 1619 if heads is None:
1626 1620 heads = repo.heads()
1627 1621 subset = [c.node() for c in repo.set('::%ln', heads)]
1628 1622 markers = repo.obsstore.relevantmarkers(subset)
1629 1623 markers = sorted(markers)
1630 1624 buildobsmarkerspart(bundler, markers)
1631 1625
1632 1626 @getbundle2partsgenerator('hgtagsfnodes')
1633 1627 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1634 1628 b2caps=None, heads=None, common=None,
1635 1629 **kwargs):
1636 1630 """Transfer the .hgtags filenodes mapping.
1637 1631
1638 1632 Only values for heads in this bundle will be transferred.
1639 1633
1640 1634 The part data consists of pairs of 20 byte changeset node and .hgtags
1641 1635 filenodes raw values.
1642 1636 """
1643 1637 # Don't send unless:
1644 1638 # - changeset are being exchanged,
1645 1639 # - the client supports it.
1646 1640 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1647 1641 return
1648 1642
1649 1643 outgoing = _computeoutgoing(repo, heads, common)
1650 1644
1651 1645 if not outgoing.missingheads:
1652 1646 return
1653 1647
1654 1648 cache = tags.hgtagsfnodescache(repo.unfiltered())
1655 1649 chunks = []
1656 1650
1657 1651 # .hgtags fnodes are only relevant for head changesets. While we could
1658 1652 # transfer values for all known nodes, there will likely be little to
1659 1653 # no benefit.
1660 1654 #
1661 1655 # We don't bother using a generator to produce output data because
1662 1656 # a) we only have 40 bytes per head and even esoteric numbers of heads
1663 1657 # consume little memory (1M heads is 40MB) b) we don't want to send the
1664 1658 # part if we don't have entries and knowing if we have entries requires
1665 1659 # cache lookups.
1666 1660 for node in outgoing.missingheads:
1667 1661 # Don't compute missing, as this may slow down serving.
1668 1662 fnode = cache.getfnode(node, computemissing=False)
1669 1663 if fnode is not None:
1670 1664 chunks.extend([node, fnode])
1671 1665
1672 1666 if chunks:
1673 1667 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1674 1668
1675 1669 def check_heads(repo, their_heads, context):
1676 1670 """check if the heads of a repo have been modified
1677 1671
1678 1672 Used by peer for unbundling.
1679 1673 """
1680 1674 heads = repo.heads()
1681 1675 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1682 1676 if not (their_heads == ['force'] or their_heads == heads or
1683 1677 their_heads == ['hashed', heads_hash]):
1684 1678 # someone else committed/pushed/unbundled while we
1685 1679 # were transferring data
1686 1680 raise error.PushRaced('repository changed while %s - '
1687 1681 'please try again' % context)
1688 1682
1689 1683 def unbundle(repo, cg, heads, source, url):
1690 1684 """Apply a bundle to a repo.
1691 1685
1692 1686 this function makes sure the repo is locked during the application and have
1693 1687 mechanism to check that no push race occurred between the creation of the
1694 1688 bundle and its application.
1695 1689
1696 1690 If the push was raced as PushRaced exception is raised."""
1697 1691 r = 0
1698 1692 # need a transaction when processing a bundle2 stream
1699 1693 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1700 1694 lockandtr = [None, None, None]
1701 1695 recordout = None
1702 1696 # quick fix for output mismatch with bundle2 in 3.4
1703 1697 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture',
1704 1698 False)
1705 1699 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1706 1700 captureoutput = True
1707 1701 try:
1708 1702 check_heads(repo, heads, 'uploading changes')
1709 1703 # push can proceed
1710 1704 if util.safehasattr(cg, 'params'):
1711 1705 r = None
1712 1706 try:
1713 1707 def gettransaction():
1714 1708 if not lockandtr[2]:
1715 1709 lockandtr[0] = repo.wlock()
1716 1710 lockandtr[1] = repo.lock()
1717 1711 lockandtr[2] = repo.transaction(source)
1718 1712 lockandtr[2].hookargs['source'] = source
1719 1713 lockandtr[2].hookargs['url'] = url
1720 1714 lockandtr[2].hookargs['bundle2'] = '1'
1721 1715 return lockandtr[2]
1722 1716
1723 1717 # Do greedy locking by default until we're satisfied with lazy
1724 1718 # locking.
1725 1719 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1726 1720 gettransaction()
1727 1721
1728 1722 op = bundle2.bundleoperation(repo, gettransaction,
1729 1723 captureoutput=captureoutput)
1730 1724 try:
1731 1725 op = bundle2.processbundle(repo, cg, op=op)
1732 1726 finally:
1733 1727 r = op.reply
1734 1728 if captureoutput and r is not None:
1735 1729 repo.ui.pushbuffer(error=True, subproc=True)
1736 1730 def recordout(output):
1737 1731 r.newpart('output', data=output, mandatory=False)
1738 1732 if lockandtr[2] is not None:
1739 1733 lockandtr[2].close()
1740 1734 except BaseException as exc:
1741 1735 exc.duringunbundle2 = True
1742 1736 if captureoutput and r is not None:
1743 1737 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1744 1738 def recordout(output):
1745 1739 part = bundle2.bundlepart('output', data=output,
1746 1740 mandatory=False)
1747 1741 parts.append(part)
1748 1742 raise
1749 1743 else:
1750 1744 lockandtr[1] = repo.lock()
1751 1745 r = cg.apply(repo, source, url)
1752 1746 finally:
1753 1747 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1754 1748 if recordout is not None:
1755 1749 recordout(repo.ui.popbuffer())
1756 1750 return r
1757 1751
1758 1752 def _maybeapplyclonebundle(pullop):
1759 1753 """Apply a clone bundle from a remote, if possible."""
1760 1754
1761 1755 repo = pullop.repo
1762 1756 remote = pullop.remote
1763 1757
1764 1758 if not repo.ui.configbool('ui', 'clonebundles', True):
1765 1759 return
1766 1760
1767 1761 # Only run if local repo is empty.
1768 1762 if len(repo):
1769 1763 return
1770 1764
1771 1765 if pullop.heads:
1772 1766 return
1773 1767
1774 1768 if not remote.capable('clonebundles'):
1775 1769 return
1776 1770
1777 1771 res = remote._call('clonebundles')
1778 1772
1779 1773 # If we call the wire protocol command, that's good enough to record the
1780 1774 # attempt.
1781 1775 pullop.clonebundleattempted = True
1782 1776
1783 1777 entries = parseclonebundlesmanifest(repo, res)
1784 1778 if not entries:
1785 1779 repo.ui.note(_('no clone bundles available on remote; '
1786 1780 'falling back to regular clone\n'))
1787 1781 return
1788 1782
1789 1783 entries = filterclonebundleentries(repo, entries)
1790 1784 if not entries:
1791 1785 # There is a thundering herd concern here. However, if a server
1792 1786 # operator doesn't advertise bundles appropriate for its clients,
1793 1787 # they deserve what's coming. Furthermore, from a client's
1794 1788 # perspective, no automatic fallback would mean not being able to
1795 1789 # clone!
1796 1790 repo.ui.warn(_('no compatible clone bundles available on server; '
1797 1791 'falling back to regular clone\n'))
1798 1792 repo.ui.warn(_('(you may want to report this to the server '
1799 1793 'operator)\n'))
1800 1794 return
1801 1795
1802 1796 entries = sortclonebundleentries(repo.ui, entries)
1803 1797
1804 1798 url = entries[0]['URL']
1805 1799 repo.ui.status(_('applying clone bundle from %s\n') % url)
1806 1800 if trypullbundlefromurl(repo.ui, repo, url):
1807 1801 repo.ui.status(_('finished applying clone bundle\n'))
1808 1802 # Bundle failed.
1809 1803 #
1810 1804 # We abort by default to avoid the thundering herd of
1811 1805 # clients flooding a server that was expecting expensive
1812 1806 # clone load to be offloaded.
1813 1807 elif repo.ui.configbool('ui', 'clonebundlefallback', False):
1814 1808 repo.ui.warn(_('falling back to normal clone\n'))
1815 1809 else:
1816 1810 raise error.Abort(_('error applying bundle'),
1817 1811 hint=_('if this error persists, consider contacting '
1818 1812 'the server operator or disable clone '
1819 1813 'bundles via '
1820 1814 '"--config ui.clonebundles=false"'))
1821 1815
1822 1816 def parseclonebundlesmanifest(repo, s):
1823 1817 """Parses the raw text of a clone bundles manifest.
1824 1818
1825 1819 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1826 1820 to the URL and other keys are the attributes for the entry.
1827 1821 """
1828 1822 m = []
1829 1823 for line in s.splitlines():
1830 1824 fields = line.split()
1831 1825 if not fields:
1832 1826 continue
1833 1827 attrs = {'URL': fields[0]}
1834 1828 for rawattr in fields[1:]:
1835 1829 key, value = rawattr.split('=', 1)
1836 1830 key = urlreq.unquote(key)
1837 1831 value = urlreq.unquote(value)
1838 1832 attrs[key] = value
1839 1833
1840 1834 # Parse BUNDLESPEC into components. This makes client-side
1841 1835 # preferences easier to specify since you can prefer a single
1842 1836 # component of the BUNDLESPEC.
1843 1837 if key == 'BUNDLESPEC':
1844 1838 try:
1845 1839 comp, version, params = parsebundlespec(repo, value,
1846 1840 externalnames=True)
1847 1841 attrs['COMPRESSION'] = comp
1848 1842 attrs['VERSION'] = version
1849 1843 except error.InvalidBundleSpecification:
1850 1844 pass
1851 1845 except error.UnsupportedBundleSpecification:
1852 1846 pass
1853 1847
1854 1848 m.append(attrs)
1855 1849
1856 1850 return m
1857 1851
1858 1852 def filterclonebundleentries(repo, entries):
1859 1853 """Remove incompatible clone bundle manifest entries.
1860 1854
1861 1855 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1862 1856 and returns a new list consisting of only the entries that this client
1863 1857 should be able to apply.
1864 1858
1865 1859 There is no guarantee we'll be able to apply all returned entries because
1866 1860 the metadata we use to filter on may be missing or wrong.
1867 1861 """
1868 1862 newentries = []
1869 1863 for entry in entries:
1870 1864 spec = entry.get('BUNDLESPEC')
1871 1865 if spec:
1872 1866 try:
1873 1867 parsebundlespec(repo, spec, strict=True)
1874 1868 except error.InvalidBundleSpecification as e:
1875 1869 repo.ui.debug(str(e) + '\n')
1876 1870 continue
1877 1871 except error.UnsupportedBundleSpecification as e:
1878 1872 repo.ui.debug('filtering %s because unsupported bundle '
1879 1873 'spec: %s\n' % (entry['URL'], str(e)))
1880 1874 continue
1881 1875
1882 1876 if 'REQUIRESNI' in entry and not sslutil.hassni:
1883 1877 repo.ui.debug('filtering %s because SNI not supported\n' %
1884 1878 entry['URL'])
1885 1879 continue
1886 1880
1887 1881 newentries.append(entry)
1888 1882
1889 1883 return newentries
1890 1884
1891 1885 def sortclonebundleentries(ui, entries):
1892 1886 prefers = ui.configlist('ui', 'clonebundleprefers', default=[])
1893 1887 if not prefers:
1894 1888 return list(entries)
1895 1889
1896 1890 prefers = [p.split('=', 1) for p in prefers]
1897 1891
1898 1892 # Our sort function.
1899 1893 def compareentry(a, b):
1900 1894 for prefkey, prefvalue in prefers:
1901 1895 avalue = a.get(prefkey)
1902 1896 bvalue = b.get(prefkey)
1903 1897
1904 1898 # Special case for b missing attribute and a matches exactly.
1905 1899 if avalue is not None and bvalue is None and avalue == prefvalue:
1906 1900 return -1
1907 1901
1908 1902 # Special case for a missing attribute and b matches exactly.
1909 1903 if bvalue is not None and avalue is None and bvalue == prefvalue:
1910 1904 return 1
1911 1905
1912 1906 # We can't compare unless attribute present on both.
1913 1907 if avalue is None or bvalue is None:
1914 1908 continue
1915 1909
1916 1910 # Same values should fall back to next attribute.
1917 1911 if avalue == bvalue:
1918 1912 continue
1919 1913
1920 1914 # Exact matches come first.
1921 1915 if avalue == prefvalue:
1922 1916 return -1
1923 1917 if bvalue == prefvalue:
1924 1918 return 1
1925 1919
1926 1920 # Fall back to next attribute.
1927 1921 continue
1928 1922
1929 1923 # If we got here we couldn't sort by attributes and prefers. Fall
1930 1924 # back to index order.
1931 1925 return 0
1932 1926
1933 1927 return sorted(entries, cmp=compareentry)
1934 1928
1935 1929 def trypullbundlefromurl(ui, repo, url):
1936 1930 """Attempt to apply a bundle from a URL."""
1937 1931 lock = repo.lock()
1938 1932 try:
1939 1933 tr = repo.transaction('bundleurl')
1940 1934 try:
1941 1935 try:
1942 1936 fh = urlmod.open(ui, url)
1943 1937 cg = readbundle(ui, fh, 'stream')
1944 1938
1945 1939 if isinstance(cg, bundle2.unbundle20):
1946 1940 bundle2.processbundle(repo, cg, lambda: tr)
1947 1941 elif isinstance(cg, streamclone.streamcloneapplier):
1948 1942 cg.apply(repo)
1949 1943 else:
1950 1944 cg.apply(repo, 'clonebundles', url)
1951 1945 tr.close()
1952 1946 return True
1953 1947 except urlerr.httperror as e:
1954 1948 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
1955 1949 except urlerr.urlerror as e:
1956 1950 ui.warn(_('error fetching bundle: %s\n') % e.reason[1])
1957 1951
1958 1952 return False
1959 1953 finally:
1960 1954 tr.release()
1961 1955 finally:
1962 1956 lock.release()
@@ -1,1996 +1,2000
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import hashlib
12 12 import inspect
13 13 import os
14 14 import random
15 15 import time
16 16 import weakref
17 17
18 18 from .i18n import _
19 19 from .node import (
20 20 hex,
21 21 nullid,
22 22 short,
23 23 wdirrev,
24 24 )
25 25 from . import (
26 26 bookmarks,
27 27 branchmap,
28 28 bundle2,
29 29 changegroup,
30 30 changelog,
31 31 cmdutil,
32 32 context,
33 33 dirstate,
34 34 encoding,
35 35 error,
36 36 exchange,
37 37 extensions,
38 38 filelog,
39 39 hook,
40 40 lock as lockmod,
41 41 manifest,
42 42 match as matchmod,
43 43 merge as mergemod,
44 44 namespaces,
45 45 obsolete,
46 46 pathutil,
47 47 peer,
48 48 phases,
49 49 pushkey,
50 50 repoview,
51 51 revset,
52 52 scmutil,
53 53 store,
54 54 subrepo,
55 55 tags as tagsmod,
56 56 transaction,
57 57 util,
58 58 )
59 59
60 60 release = lockmod.release
61 61 urlerr = util.urlerr
62 62 urlreq = util.urlreq
63 63
64 64 class repofilecache(scmutil.filecache):
65 65 """All filecache usage on repo are done for logic that should be unfiltered
66 66 """
67 67
68 68 def __get__(self, repo, type=None):
69 69 if repo is None:
70 70 return self
71 71 return super(repofilecache, self).__get__(repo.unfiltered(), type)
72 72 def __set__(self, repo, value):
73 73 return super(repofilecache, self).__set__(repo.unfiltered(), value)
74 74 def __delete__(self, repo):
75 75 return super(repofilecache, self).__delete__(repo.unfiltered())
76 76
77 77 class storecache(repofilecache):
78 78 """filecache for files in the store"""
79 79 def join(self, obj, fname):
80 80 return obj.sjoin(fname)
81 81
82 82 class unfilteredpropertycache(util.propertycache):
83 83 """propertycache that apply to unfiltered repo only"""
84 84
85 85 def __get__(self, repo, type=None):
86 86 unfi = repo.unfiltered()
87 87 if unfi is repo:
88 88 return super(unfilteredpropertycache, self).__get__(unfi)
89 89 return getattr(unfi, self.name)
90 90
91 91 class filteredpropertycache(util.propertycache):
92 92 """propertycache that must take filtering in account"""
93 93
94 94 def cachevalue(self, obj, value):
95 95 object.__setattr__(obj, self.name, value)
96 96
97 97
98 98 def hasunfilteredcache(repo, name):
99 99 """check if a repo has an unfilteredpropertycache value for <name>"""
100 100 return name in vars(repo.unfiltered())
101 101
102 102 def unfilteredmethod(orig):
103 103 """decorate method that always need to be run on unfiltered version"""
104 104 def wrapper(repo, *args, **kwargs):
105 105 return orig(repo.unfiltered(), *args, **kwargs)
106 106 return wrapper
107 107
108 108 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
109 109 'unbundle'))
110 110 legacycaps = moderncaps.union(set(['changegroupsubset']))
111 111
112 112 class localpeer(peer.peerrepository):
113 113 '''peer for a local repo; reflects only the most recent API'''
114 114
115 115 def __init__(self, repo, caps=moderncaps):
116 116 peer.peerrepository.__init__(self)
117 117 self._repo = repo.filtered('served')
118 118 self.ui = repo.ui
119 119 self._caps = repo._restrictcapabilities(caps)
120 120 self.requirements = repo.requirements
121 121 self.supportedformats = repo.supportedformats
122 122
123 123 def close(self):
124 124 self._repo.close()
125 125
126 126 def _capabilities(self):
127 127 return self._caps
128 128
129 129 def local(self):
130 130 return self._repo
131 131
132 132 def canpush(self):
133 133 return True
134 134
135 135 def url(self):
136 136 return self._repo.url()
137 137
138 138 def lookup(self, key):
139 139 return self._repo.lookup(key)
140 140
141 141 def branchmap(self):
142 142 return self._repo.branchmap()
143 143
144 144 def heads(self):
145 145 return self._repo.heads()
146 146
147 147 def known(self, nodes):
148 148 return self._repo.known(nodes)
149 149
150 150 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
151 151 **kwargs):
152 cg = exchange.getbundle(self._repo, source, heads=heads,
153 common=common, bundlecaps=bundlecaps, **kwargs)
152 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
153 common=common, bundlecaps=bundlecaps,
154 **kwargs)
155 cb = util.chunkbuffer(chunks)
156
154 157 if bundlecaps is not None and 'HG20' in bundlecaps:
155 158 # When requesting a bundle2, getbundle returns a stream to make the
156 159 # wire level function happier. We need to build a proper object
157 160 # from it in local peer.
158 cg = bundle2.getunbundler(self.ui, cg)
159 return cg
161 return bundle2.getunbundler(self.ui, cb)
162 else:
163 return changegroup.getunbundler('01', cb, None)
160 164
161 165 # TODO We might want to move the next two calls into legacypeer and add
162 166 # unbundle instead.
163 167
164 168 def unbundle(self, cg, heads, url):
165 169 """apply a bundle on a repo
166 170
167 171 This function handles the repo locking itself."""
168 172 try:
169 173 try:
170 174 cg = exchange.readbundle(self.ui, cg, None)
171 175 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
172 176 if util.safehasattr(ret, 'getchunks'):
173 177 # This is a bundle20 object, turn it into an unbundler.
174 178 # This little dance should be dropped eventually when the
175 179 # API is finally improved.
176 180 stream = util.chunkbuffer(ret.getchunks())
177 181 ret = bundle2.getunbundler(self.ui, stream)
178 182 return ret
179 183 except Exception as exc:
180 184 # If the exception contains output salvaged from a bundle2
181 185 # reply, we need to make sure it is printed before continuing
182 186 # to fail. So we build a bundle2 with such output and consume
183 187 # it directly.
184 188 #
185 189 # This is not very elegant but allows a "simple" solution for
186 190 # issue4594
187 191 output = getattr(exc, '_bundle2salvagedoutput', ())
188 192 if output:
189 193 bundler = bundle2.bundle20(self._repo.ui)
190 194 for out in output:
191 195 bundler.addpart(out)
192 196 stream = util.chunkbuffer(bundler.getchunks())
193 197 b = bundle2.getunbundler(self.ui, stream)
194 198 bundle2.processbundle(self._repo, b)
195 199 raise
196 200 except error.PushRaced as exc:
197 201 raise error.ResponseError(_('push failed:'), str(exc))
198 202
199 203 def lock(self):
200 204 return self._repo.lock()
201 205
202 206 def addchangegroup(self, cg, source, url):
203 207 return cg.apply(self._repo, source, url)
204 208
205 209 def pushkey(self, namespace, key, old, new):
206 210 return self._repo.pushkey(namespace, key, old, new)
207 211
208 212 def listkeys(self, namespace):
209 213 return self._repo.listkeys(namespace)
210 214
211 215 def debugwireargs(self, one, two, three=None, four=None, five=None):
212 216 '''used to test argument passing over the wire'''
213 217 return "%s %s %s %s %s" % (one, two, three, four, five)
214 218
215 219 class locallegacypeer(localpeer):
216 220 '''peer extension which implements legacy methods too; used for tests with
217 221 restricted capabilities'''
218 222
219 223 def __init__(self, repo):
220 224 localpeer.__init__(self, repo, caps=legacycaps)
221 225
222 226 def branches(self, nodes):
223 227 return self._repo.branches(nodes)
224 228
225 229 def between(self, pairs):
226 230 return self._repo.between(pairs)
227 231
228 232 def changegroup(self, basenodes, source):
229 233 return changegroup.changegroup(self._repo, basenodes, source)
230 234
231 235 def changegroupsubset(self, bases, heads, source):
232 236 return changegroup.changegroupsubset(self._repo, bases, heads, source)
233 237
234 238 class localrepository(object):
235 239
236 240 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
237 241 'manifestv2'))
238 242 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
239 243 'dotencode'))
240 244 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
241 245 filtername = None
242 246
243 247 # a list of (ui, featureset) functions.
244 248 # only functions defined in module of enabled extensions are invoked
245 249 featuresetupfuncs = set()
246 250
247 251 def __init__(self, baseui, path=None, create=False):
248 252 self.requirements = set()
249 253 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
250 254 self.wopener = self.wvfs
251 255 self.root = self.wvfs.base
252 256 self.path = self.wvfs.join(".hg")
253 257 self.origroot = path
254 258 self.auditor = pathutil.pathauditor(self.root, self._checknested)
255 259 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
256 260 realfs=False)
257 261 self.vfs = scmutil.vfs(self.path)
258 262 self.opener = self.vfs
259 263 self.baseui = baseui
260 264 self.ui = baseui.copy()
261 265 self.ui.copy = baseui.copy # prevent copying repo configuration
262 266 # A list of callback to shape the phase if no data were found.
263 267 # Callback are in the form: func(repo, roots) --> processed root.
264 268 # This list it to be filled by extension during repo setup
265 269 self._phasedefaults = []
266 270 try:
267 271 self.ui.readconfig(self.join("hgrc"), self.root)
268 272 extensions.loadall(self.ui)
269 273 except IOError:
270 274 pass
271 275
272 276 if self.featuresetupfuncs:
273 277 self.supported = set(self._basesupported) # use private copy
274 278 extmods = set(m.__name__ for n, m
275 279 in extensions.extensions(self.ui))
276 280 for setupfunc in self.featuresetupfuncs:
277 281 if setupfunc.__module__ in extmods:
278 282 setupfunc(self.ui, self.supported)
279 283 else:
280 284 self.supported = self._basesupported
281 285
282 286 if not self.vfs.isdir():
283 287 if create:
284 288 self.requirements = newreporequirements(self)
285 289
286 290 if not self.wvfs.exists():
287 291 self.wvfs.makedirs()
288 292 self.vfs.makedir(notindexed=True)
289 293
290 294 if 'store' in self.requirements:
291 295 self.vfs.mkdir("store")
292 296
293 297 # create an invalid changelog
294 298 self.vfs.append(
295 299 "00changelog.i",
296 300 '\0\0\0\2' # represents revlogv2
297 301 ' dummy changelog to prevent using the old repo layout'
298 302 )
299 303 else:
300 304 raise error.RepoError(_("repository %s not found") % path)
301 305 elif create:
302 306 raise error.RepoError(_("repository %s already exists") % path)
303 307 else:
304 308 try:
305 309 self.requirements = scmutil.readrequires(
306 310 self.vfs, self.supported)
307 311 except IOError as inst:
308 312 if inst.errno != errno.ENOENT:
309 313 raise
310 314
311 315 self.sharedpath = self.path
312 316 try:
313 317 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
314 318 realpath=True)
315 319 s = vfs.base
316 320 if not vfs.exists():
317 321 raise error.RepoError(
318 322 _('.hg/sharedpath points to nonexistent directory %s') % s)
319 323 self.sharedpath = s
320 324 except IOError as inst:
321 325 if inst.errno != errno.ENOENT:
322 326 raise
323 327
324 328 self.store = store.store(
325 329 self.requirements, self.sharedpath, scmutil.vfs)
326 330 self.spath = self.store.path
327 331 self.svfs = self.store.vfs
328 332 self.sjoin = self.store.join
329 333 self.vfs.createmode = self.store.createmode
330 334 self._applyopenerreqs()
331 335 if create:
332 336 self._writerequirements()
333 337
334 338 self._dirstatevalidatewarned = False
335 339
336 340 self._branchcaches = {}
337 341 self._revbranchcache = None
338 342 self.filterpats = {}
339 343 self._datafilters = {}
340 344 self._transref = self._lockref = self._wlockref = None
341 345
342 346 # A cache for various files under .hg/ that tracks file changes,
343 347 # (used by the filecache decorator)
344 348 #
345 349 # Maps a property name to its util.filecacheentry
346 350 self._filecache = {}
347 351
348 352 # hold sets of revision to be filtered
349 353 # should be cleared when something might have changed the filter value:
350 354 # - new changesets,
351 355 # - phase change,
352 356 # - new obsolescence marker,
353 357 # - working directory parent change,
354 358 # - bookmark changes
355 359 self.filteredrevcache = {}
356 360
357 361 # generic mapping between names and nodes
358 362 self.names = namespaces.namespaces()
359 363
360 364 def close(self):
361 365 self._writecaches()
362 366
363 367 def _writecaches(self):
364 368 if self._revbranchcache:
365 369 self._revbranchcache.write()
366 370
367 371 def _restrictcapabilities(self, caps):
368 372 if self.ui.configbool('experimental', 'bundle2-advertise', True):
369 373 caps = set(caps)
370 374 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
371 375 caps.add('bundle2=' + urlreq.quote(capsblob))
372 376 return caps
373 377
374 378 def _applyopenerreqs(self):
375 379 self.svfs.options = dict((r, 1) for r in self.requirements
376 380 if r in self.openerreqs)
377 381 # experimental config: format.chunkcachesize
378 382 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
379 383 if chunkcachesize is not None:
380 384 self.svfs.options['chunkcachesize'] = chunkcachesize
381 385 # experimental config: format.maxchainlen
382 386 maxchainlen = self.ui.configint('format', 'maxchainlen')
383 387 if maxchainlen is not None:
384 388 self.svfs.options['maxchainlen'] = maxchainlen
385 389 # experimental config: format.manifestcachesize
386 390 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
387 391 if manifestcachesize is not None:
388 392 self.svfs.options['manifestcachesize'] = manifestcachesize
389 393 # experimental config: format.aggressivemergedeltas
390 394 aggressivemergedeltas = self.ui.configbool('format',
391 395 'aggressivemergedeltas', False)
392 396 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
393 397 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
394 398
395 399 def _writerequirements(self):
396 400 scmutil.writerequires(self.vfs, self.requirements)
397 401
398 402 def _checknested(self, path):
399 403 """Determine if path is a legal nested repository."""
400 404 if not path.startswith(self.root):
401 405 return False
402 406 subpath = path[len(self.root) + 1:]
403 407 normsubpath = util.pconvert(subpath)
404 408
405 409 # XXX: Checking against the current working copy is wrong in
406 410 # the sense that it can reject things like
407 411 #
408 412 # $ hg cat -r 10 sub/x.txt
409 413 #
410 414 # if sub/ is no longer a subrepository in the working copy
411 415 # parent revision.
412 416 #
413 417 # However, it can of course also allow things that would have
414 418 # been rejected before, such as the above cat command if sub/
415 419 # is a subrepository now, but was a normal directory before.
416 420 # The old path auditor would have rejected by mistake since it
417 421 # panics when it sees sub/.hg/.
418 422 #
419 423 # All in all, checking against the working copy seems sensible
420 424 # since we want to prevent access to nested repositories on
421 425 # the filesystem *now*.
422 426 ctx = self[None]
423 427 parts = util.splitpath(subpath)
424 428 while parts:
425 429 prefix = '/'.join(parts)
426 430 if prefix in ctx.substate:
427 431 if prefix == normsubpath:
428 432 return True
429 433 else:
430 434 sub = ctx.sub(prefix)
431 435 return sub.checknested(subpath[len(prefix) + 1:])
432 436 else:
433 437 parts.pop()
434 438 return False
435 439
436 440 def peer(self):
437 441 return localpeer(self) # not cached to avoid reference cycle
438 442
439 443 def unfiltered(self):
440 444 """Return unfiltered version of the repository
441 445
442 446 Intended to be overwritten by filtered repo."""
443 447 return self
444 448
445 449 def filtered(self, name):
446 450 """Return a filtered version of a repository"""
447 451 # build a new class with the mixin and the current class
448 452 # (possibly subclass of the repo)
449 453 class proxycls(repoview.repoview, self.unfiltered().__class__):
450 454 pass
451 455 return proxycls(self, name)
452 456
453 457 @repofilecache('bookmarks', 'bookmarks.current')
454 458 def _bookmarks(self):
455 459 return bookmarks.bmstore(self)
456 460
457 461 @property
458 462 def _activebookmark(self):
459 463 return self._bookmarks.active
460 464
461 465 def bookmarkheads(self, bookmark):
462 466 name = bookmark.split('@', 1)[0]
463 467 heads = []
464 468 for mark, n in self._bookmarks.iteritems():
465 469 if mark.split('@', 1)[0] == name:
466 470 heads.append(n)
467 471 return heads
468 472
469 473 # _phaserevs and _phasesets depend on changelog. what we need is to
470 474 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
471 475 # can't be easily expressed in filecache mechanism.
472 476 @storecache('phaseroots', '00changelog.i')
473 477 def _phasecache(self):
474 478 return phases.phasecache(self, self._phasedefaults)
475 479
476 480 @storecache('obsstore')
477 481 def obsstore(self):
478 482 # read default format for new obsstore.
479 483 # developer config: format.obsstore-version
480 484 defaultformat = self.ui.configint('format', 'obsstore-version', None)
481 485 # rely on obsstore class default when possible.
482 486 kwargs = {}
483 487 if defaultformat is not None:
484 488 kwargs['defaultformat'] = defaultformat
485 489 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
486 490 store = obsolete.obsstore(self.svfs, readonly=readonly,
487 491 **kwargs)
488 492 if store and readonly:
489 493 self.ui.warn(
490 494 _('obsolete feature not enabled but %i markers found!\n')
491 495 % len(list(store)))
492 496 return store
493 497
494 498 @storecache('00changelog.i')
495 499 def changelog(self):
496 500 c = changelog.changelog(self.svfs)
497 501 if 'HG_PENDING' in os.environ:
498 502 p = os.environ['HG_PENDING']
499 503 if p.startswith(self.root):
500 504 c.readpending('00changelog.i.a')
501 505 return c
502 506
503 507 @storecache('00manifest.i')
504 508 def manifest(self):
505 509 return manifest.manifest(self.svfs)
506 510
507 511 @property
508 512 def manifestlog(self):
509 513 return manifest.manifestlog(self.svfs, self)
510 514
511 515 @repofilecache('dirstate')
512 516 def dirstate(self):
513 517 return dirstate.dirstate(self.vfs, self.ui, self.root,
514 518 self._dirstatevalidate)
515 519
516 520 def _dirstatevalidate(self, node):
517 521 try:
518 522 self.changelog.rev(node)
519 523 return node
520 524 except error.LookupError:
521 525 if not self._dirstatevalidatewarned:
522 526 self._dirstatevalidatewarned = True
523 527 self.ui.warn(_("warning: ignoring unknown"
524 528 " working parent %s!\n") % short(node))
525 529 return nullid
526 530
527 531 def __getitem__(self, changeid):
528 532 if changeid is None or changeid == wdirrev:
529 533 return context.workingctx(self)
530 534 if isinstance(changeid, slice):
531 535 return [context.changectx(self, i)
532 536 for i in xrange(*changeid.indices(len(self)))
533 537 if i not in self.changelog.filteredrevs]
534 538 return context.changectx(self, changeid)
535 539
536 540 def __contains__(self, changeid):
537 541 try:
538 542 self[changeid]
539 543 return True
540 544 except error.RepoLookupError:
541 545 return False
542 546
543 547 def __nonzero__(self):
544 548 return True
545 549
546 550 def __len__(self):
547 551 return len(self.changelog)
548 552
549 553 def __iter__(self):
550 554 return iter(self.changelog)
551 555
552 556 def revs(self, expr, *args):
553 557 '''Find revisions matching a revset.
554 558
555 559 The revset is specified as a string ``expr`` that may contain
556 560 %-formatting to escape certain types. See ``revset.formatspec``.
557 561
558 562 Revset aliases from the configuration are not expanded. To expand
559 563 user aliases, consider calling ``scmutil.revrange()``.
560 564
561 565 Returns a revset.abstractsmartset, which is a list-like interface
562 566 that contains integer revisions.
563 567 '''
564 568 expr = revset.formatspec(expr, *args)
565 569 m = revset.match(None, expr)
566 570 return m(self)
567 571
568 572 def set(self, expr, *args):
569 573 '''Find revisions matching a revset and emit changectx instances.
570 574
571 575 This is a convenience wrapper around ``revs()`` that iterates the
572 576 result and is a generator of changectx instances.
573 577
574 578 Revset aliases from the configuration are not expanded. To expand
575 579 user aliases, consider calling ``scmutil.revrange()``.
576 580 '''
577 581 for r in self.revs(expr, *args):
578 582 yield self[r]
579 583
580 584 def url(self):
581 585 return 'file:' + self.root
582 586
583 587 def hook(self, name, throw=False, **args):
584 588 """Call a hook, passing this repo instance.
585 589
586 590 This a convenience method to aid invoking hooks. Extensions likely
587 591 won't call this unless they have registered a custom hook or are
588 592 replacing code that is expected to call a hook.
589 593 """
590 594 return hook.hook(self.ui, self, name, throw, **args)
591 595
592 596 @unfilteredmethod
593 597 def _tag(self, names, node, message, local, user, date, extra=None,
594 598 editor=False):
595 599 if isinstance(names, str):
596 600 names = (names,)
597 601
598 602 branches = self.branchmap()
599 603 for name in names:
600 604 self.hook('pretag', throw=True, node=hex(node), tag=name,
601 605 local=local)
602 606 if name in branches:
603 607 self.ui.warn(_("warning: tag %s conflicts with existing"
604 608 " branch name\n") % name)
605 609
606 610 def writetags(fp, names, munge, prevtags):
607 611 fp.seek(0, 2)
608 612 if prevtags and prevtags[-1] != '\n':
609 613 fp.write('\n')
610 614 for name in names:
611 615 if munge:
612 616 m = munge(name)
613 617 else:
614 618 m = name
615 619
616 620 if (self._tagscache.tagtypes and
617 621 name in self._tagscache.tagtypes):
618 622 old = self.tags().get(name, nullid)
619 623 fp.write('%s %s\n' % (hex(old), m))
620 624 fp.write('%s %s\n' % (hex(node), m))
621 625 fp.close()
622 626
623 627 prevtags = ''
624 628 if local:
625 629 try:
626 630 fp = self.vfs('localtags', 'r+')
627 631 except IOError:
628 632 fp = self.vfs('localtags', 'a')
629 633 else:
630 634 prevtags = fp.read()
631 635
632 636 # local tags are stored in the current charset
633 637 writetags(fp, names, None, prevtags)
634 638 for name in names:
635 639 self.hook('tag', node=hex(node), tag=name, local=local)
636 640 return
637 641
638 642 try:
639 643 fp = self.wfile('.hgtags', 'rb+')
640 644 except IOError as e:
641 645 if e.errno != errno.ENOENT:
642 646 raise
643 647 fp = self.wfile('.hgtags', 'ab')
644 648 else:
645 649 prevtags = fp.read()
646 650
647 651 # committed tags are stored in UTF-8
648 652 writetags(fp, names, encoding.fromlocal, prevtags)
649 653
650 654 fp.close()
651 655
652 656 self.invalidatecaches()
653 657
654 658 if '.hgtags' not in self.dirstate:
655 659 self[None].add(['.hgtags'])
656 660
657 661 m = matchmod.exact(self.root, '', ['.hgtags'])
658 662 tagnode = self.commit(message, user, date, extra=extra, match=m,
659 663 editor=editor)
660 664
661 665 for name in names:
662 666 self.hook('tag', node=hex(node), tag=name, local=local)
663 667
664 668 return tagnode
665 669
666 670 def tag(self, names, node, message, local, user, date, editor=False):
667 671 '''tag a revision with one or more symbolic names.
668 672
669 673 names is a list of strings or, when adding a single tag, names may be a
670 674 string.
671 675
672 676 if local is True, the tags are stored in a per-repository file.
673 677 otherwise, they are stored in the .hgtags file, and a new
674 678 changeset is committed with the change.
675 679
676 680 keyword arguments:
677 681
678 682 local: whether to store tags in non-version-controlled file
679 683 (default False)
680 684
681 685 message: commit message to use if committing
682 686
683 687 user: name of user to use if committing
684 688
685 689 date: date tuple to use if committing'''
686 690
687 691 if not local:
688 692 m = matchmod.exact(self.root, '', ['.hgtags'])
689 693 if any(self.status(match=m, unknown=True, ignored=True)):
690 694 raise error.Abort(_('working copy of .hgtags is changed'),
691 695 hint=_('please commit .hgtags manually'))
692 696
693 697 self.tags() # instantiate the cache
694 698 self._tag(names, node, message, local, user, date, editor=editor)
695 699
696 700 @filteredpropertycache
697 701 def _tagscache(self):
698 702 '''Returns a tagscache object that contains various tags related
699 703 caches.'''
700 704
701 705 # This simplifies its cache management by having one decorated
702 706 # function (this one) and the rest simply fetch things from it.
703 707 class tagscache(object):
704 708 def __init__(self):
705 709 # These two define the set of tags for this repository. tags
706 710 # maps tag name to node; tagtypes maps tag name to 'global' or
707 711 # 'local'. (Global tags are defined by .hgtags across all
708 712 # heads, and local tags are defined in .hg/localtags.)
709 713 # They constitute the in-memory cache of tags.
710 714 self.tags = self.tagtypes = None
711 715
712 716 self.nodetagscache = self.tagslist = None
713 717
714 718 cache = tagscache()
715 719 cache.tags, cache.tagtypes = self._findtags()
716 720
717 721 return cache
718 722
719 723 def tags(self):
720 724 '''return a mapping of tag to node'''
721 725 t = {}
722 726 if self.changelog.filteredrevs:
723 727 tags, tt = self._findtags()
724 728 else:
725 729 tags = self._tagscache.tags
726 730 for k, v in tags.iteritems():
727 731 try:
728 732 # ignore tags to unknown nodes
729 733 self.changelog.rev(v)
730 734 t[k] = v
731 735 except (error.LookupError, ValueError):
732 736 pass
733 737 return t
734 738
735 739 def _findtags(self):
736 740 '''Do the hard work of finding tags. Return a pair of dicts
737 741 (tags, tagtypes) where tags maps tag name to node, and tagtypes
738 742 maps tag name to a string like \'global\' or \'local\'.
739 743 Subclasses or extensions are free to add their own tags, but
740 744 should be aware that the returned dicts will be retained for the
741 745 duration of the localrepo object.'''
742 746
743 747 # XXX what tagtype should subclasses/extensions use? Currently
744 748 # mq and bookmarks add tags, but do not set the tagtype at all.
745 749 # Should each extension invent its own tag type? Should there
746 750 # be one tagtype for all such "virtual" tags? Or is the status
747 751 # quo fine?
748 752
749 753 alltags = {} # map tag name to (node, hist)
750 754 tagtypes = {}
751 755
752 756 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
753 757 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
754 758
755 759 # Build the return dicts. Have to re-encode tag names because
756 760 # the tags module always uses UTF-8 (in order not to lose info
757 761 # writing to the cache), but the rest of Mercurial wants them in
758 762 # local encoding.
759 763 tags = {}
760 764 for (name, (node, hist)) in alltags.iteritems():
761 765 if node != nullid:
762 766 tags[encoding.tolocal(name)] = node
763 767 tags['tip'] = self.changelog.tip()
764 768 tagtypes = dict([(encoding.tolocal(name), value)
765 769 for (name, value) in tagtypes.iteritems()])
766 770 return (tags, tagtypes)
767 771
768 772 def tagtype(self, tagname):
769 773 '''
770 774 return the type of the given tag. result can be:
771 775
772 776 'local' : a local tag
773 777 'global' : a global tag
774 778 None : tag does not exist
775 779 '''
776 780
777 781 return self._tagscache.tagtypes.get(tagname)
778 782
779 783 def tagslist(self):
780 784 '''return a list of tags ordered by revision'''
781 785 if not self._tagscache.tagslist:
782 786 l = []
783 787 for t, n in self.tags().iteritems():
784 788 l.append((self.changelog.rev(n), t, n))
785 789 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
786 790
787 791 return self._tagscache.tagslist
788 792
789 793 def nodetags(self, node):
790 794 '''return the tags associated with a node'''
791 795 if not self._tagscache.nodetagscache:
792 796 nodetagscache = {}
793 797 for t, n in self._tagscache.tags.iteritems():
794 798 nodetagscache.setdefault(n, []).append(t)
795 799 for tags in nodetagscache.itervalues():
796 800 tags.sort()
797 801 self._tagscache.nodetagscache = nodetagscache
798 802 return self._tagscache.nodetagscache.get(node, [])
799 803
800 804 def nodebookmarks(self, node):
801 805 """return the list of bookmarks pointing to the specified node"""
802 806 marks = []
803 807 for bookmark, n in self._bookmarks.iteritems():
804 808 if n == node:
805 809 marks.append(bookmark)
806 810 return sorted(marks)
807 811
808 812 def branchmap(self):
809 813 '''returns a dictionary {branch: [branchheads]} with branchheads
810 814 ordered by increasing revision number'''
811 815 branchmap.updatecache(self)
812 816 return self._branchcaches[self.filtername]
813 817
814 818 @unfilteredmethod
815 819 def revbranchcache(self):
816 820 if not self._revbranchcache:
817 821 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
818 822 return self._revbranchcache
819 823
820 824 def branchtip(self, branch, ignoremissing=False):
821 825 '''return the tip node for a given branch
822 826
823 827 If ignoremissing is True, then this method will not raise an error.
824 828 This is helpful for callers that only expect None for a missing branch
825 829 (e.g. namespace).
826 830
827 831 '''
828 832 try:
829 833 return self.branchmap().branchtip(branch)
830 834 except KeyError:
831 835 if not ignoremissing:
832 836 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
833 837 else:
834 838 pass
835 839
836 840 def lookup(self, key):
837 841 return self[key].node()
838 842
839 843 def lookupbranch(self, key, remote=None):
840 844 repo = remote or self
841 845 if key in repo.branchmap():
842 846 return key
843 847
844 848 repo = (remote and remote.local()) and remote or self
845 849 return repo[key].branch()
846 850
847 851 def known(self, nodes):
848 852 cl = self.changelog
849 853 nm = cl.nodemap
850 854 filtered = cl.filteredrevs
851 855 result = []
852 856 for n in nodes:
853 857 r = nm.get(n)
854 858 resp = not (r is None or r in filtered)
855 859 result.append(resp)
856 860 return result
857 861
858 862 def local(self):
859 863 return self
860 864
861 865 def publishing(self):
862 866 # it's safe (and desirable) to trust the publish flag unconditionally
863 867 # so that we don't finalize changes shared between users via ssh or nfs
864 868 return self.ui.configbool('phases', 'publish', True, untrusted=True)
865 869
866 870 def cancopy(self):
867 871 # so statichttprepo's override of local() works
868 872 if not self.local():
869 873 return False
870 874 if not self.publishing():
871 875 return True
872 876 # if publishing we can't copy if there is filtered content
873 877 return not self.filtered('visible').changelog.filteredrevs
874 878
875 879 def shared(self):
876 880 '''the type of shared repository (None if not shared)'''
877 881 if self.sharedpath != self.path:
878 882 return 'store'
879 883 return None
880 884
881 885 def join(self, f, *insidef):
882 886 return self.vfs.join(os.path.join(f, *insidef))
883 887
884 888 def wjoin(self, f, *insidef):
885 889 return self.vfs.reljoin(self.root, f, *insidef)
886 890
887 891 def file(self, f):
888 892 if f[0] == '/':
889 893 f = f[1:]
890 894 return filelog.filelog(self.svfs, f)
891 895
892 896 def changectx(self, changeid):
893 897 return self[changeid]
894 898
895 899 def setparents(self, p1, p2=nullid):
896 900 self.dirstate.beginparentchange()
897 901 copies = self.dirstate.setparents(p1, p2)
898 902 pctx = self[p1]
899 903 if copies:
900 904 # Adjust copy records, the dirstate cannot do it, it
901 905 # requires access to parents manifests. Preserve them
902 906 # only for entries added to first parent.
903 907 for f in copies:
904 908 if f not in pctx and copies[f] in pctx:
905 909 self.dirstate.copy(copies[f], f)
906 910 if p2 == nullid:
907 911 for f, s in sorted(self.dirstate.copies().items()):
908 912 if f not in pctx and s not in pctx:
909 913 self.dirstate.copy(None, f)
910 914 self.dirstate.endparentchange()
911 915
912 916 def filectx(self, path, changeid=None, fileid=None):
913 917 """changeid can be a changeset revision, node, or tag.
914 918 fileid can be a file revision or node."""
915 919 return context.filectx(self, path, changeid, fileid)
916 920
917 921 def getcwd(self):
918 922 return self.dirstate.getcwd()
919 923
920 924 def pathto(self, f, cwd=None):
921 925 return self.dirstate.pathto(f, cwd)
922 926
923 927 def wfile(self, f, mode='r'):
924 928 return self.wvfs(f, mode)
925 929
926 930 def _link(self, f):
927 931 return self.wvfs.islink(f)
928 932
929 933 def _loadfilter(self, filter):
930 934 if filter not in self.filterpats:
931 935 l = []
932 936 for pat, cmd in self.ui.configitems(filter):
933 937 if cmd == '!':
934 938 continue
935 939 mf = matchmod.match(self.root, '', [pat])
936 940 fn = None
937 941 params = cmd
938 942 for name, filterfn in self._datafilters.iteritems():
939 943 if cmd.startswith(name):
940 944 fn = filterfn
941 945 params = cmd[len(name):].lstrip()
942 946 break
943 947 if not fn:
944 948 fn = lambda s, c, **kwargs: util.filter(s, c)
945 949 # Wrap old filters not supporting keyword arguments
946 950 if not inspect.getargspec(fn)[2]:
947 951 oldfn = fn
948 952 fn = lambda s, c, **kwargs: oldfn(s, c)
949 953 l.append((mf, fn, params))
950 954 self.filterpats[filter] = l
951 955 return self.filterpats[filter]
952 956
953 957 def _filter(self, filterpats, filename, data):
954 958 for mf, fn, cmd in filterpats:
955 959 if mf(filename):
956 960 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
957 961 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
958 962 break
959 963
960 964 return data
961 965
962 966 @unfilteredpropertycache
963 967 def _encodefilterpats(self):
964 968 return self._loadfilter('encode')
965 969
966 970 @unfilteredpropertycache
967 971 def _decodefilterpats(self):
968 972 return self._loadfilter('decode')
969 973
970 974 def adddatafilter(self, name, filter):
971 975 self._datafilters[name] = filter
972 976
973 977 def wread(self, filename):
974 978 if self._link(filename):
975 979 data = self.wvfs.readlink(filename)
976 980 else:
977 981 data = self.wvfs.read(filename)
978 982 return self._filter(self._encodefilterpats, filename, data)
979 983
980 984 def wwrite(self, filename, data, flags, backgroundclose=False):
981 985 """write ``data`` into ``filename`` in the working directory
982 986
983 987 This returns length of written (maybe decoded) data.
984 988 """
985 989 data = self._filter(self._decodefilterpats, filename, data)
986 990 if 'l' in flags:
987 991 self.wvfs.symlink(data, filename)
988 992 else:
989 993 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
990 994 if 'x' in flags:
991 995 self.wvfs.setflags(filename, False, True)
992 996 return len(data)
993 997
994 998 def wwritedata(self, filename, data):
995 999 return self._filter(self._decodefilterpats, filename, data)
996 1000
997 1001 def currenttransaction(self):
998 1002 """return the current transaction or None if non exists"""
999 1003 if self._transref:
1000 1004 tr = self._transref()
1001 1005 else:
1002 1006 tr = None
1003 1007
1004 1008 if tr and tr.running():
1005 1009 return tr
1006 1010 return None
1007 1011
1008 1012 def transaction(self, desc, report=None):
1009 1013 if (self.ui.configbool('devel', 'all-warnings')
1010 1014 or self.ui.configbool('devel', 'check-locks')):
1011 1015 if self._currentlock(self._lockref) is None:
1012 1016 raise RuntimeError('programming error: transaction requires '
1013 1017 'locking')
1014 1018 tr = self.currenttransaction()
1015 1019 if tr is not None:
1016 1020 return tr.nest()
1017 1021
1018 1022 # abort here if the journal already exists
1019 1023 if self.svfs.exists("journal"):
1020 1024 raise error.RepoError(
1021 1025 _("abandoned transaction found"),
1022 1026 hint=_("run 'hg recover' to clean up transaction"))
1023 1027
1024 1028 idbase = "%.40f#%f" % (random.random(), time.time())
1025 1029 txnid = 'TXN:' + hashlib.sha1(idbase).hexdigest()
1026 1030 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1027 1031
1028 1032 self._writejournal(desc)
1029 1033 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1030 1034 if report:
1031 1035 rp = report
1032 1036 else:
1033 1037 rp = self.ui.warn
1034 1038 vfsmap = {'plain': self.vfs} # root of .hg/
1035 1039 # we must avoid cyclic reference between repo and transaction.
1036 1040 reporef = weakref.ref(self)
1037 1041 def validate(tr):
1038 1042 """will run pre-closing hooks"""
1039 1043 reporef().hook('pretxnclose', throw=True,
1040 1044 txnname=desc, **tr.hookargs)
1041 1045 def releasefn(tr, success):
1042 1046 repo = reporef()
1043 1047 if success:
1044 1048 # this should be explicitly invoked here, because
1045 1049 # in-memory changes aren't written out at closing
1046 1050 # transaction, if tr.addfilegenerator (via
1047 1051 # dirstate.write or so) isn't invoked while
1048 1052 # transaction running
1049 1053 repo.dirstate.write(None)
1050 1054 else:
1051 1055 # discard all changes (including ones already written
1052 1056 # out) in this transaction
1053 1057 repo.dirstate.restorebackup(None, prefix='journal.')
1054 1058
1055 1059 repo.invalidate(clearfilecache=True)
1056 1060
1057 1061 tr = transaction.transaction(rp, self.svfs, vfsmap,
1058 1062 "journal",
1059 1063 "undo",
1060 1064 aftertrans(renames),
1061 1065 self.store.createmode,
1062 1066 validator=validate,
1063 1067 releasefn=releasefn)
1064 1068
1065 1069 tr.hookargs['txnid'] = txnid
1066 1070 # note: writing the fncache only during finalize mean that the file is
1067 1071 # outdated when running hooks. As fncache is used for streaming clone,
1068 1072 # this is not expected to break anything that happen during the hooks.
1069 1073 tr.addfinalize('flush-fncache', self.store.write)
1070 1074 def txnclosehook(tr2):
1071 1075 """To be run if transaction is successful, will schedule a hook run
1072 1076 """
1073 1077 # Don't reference tr2 in hook() so we don't hold a reference.
1074 1078 # This reduces memory consumption when there are multiple
1075 1079 # transactions per lock. This can likely go away if issue5045
1076 1080 # fixes the function accumulation.
1077 1081 hookargs = tr2.hookargs
1078 1082
1079 1083 def hook():
1080 1084 reporef().hook('txnclose', throw=False, txnname=desc,
1081 1085 **hookargs)
1082 1086 reporef()._afterlock(hook)
1083 1087 tr.addfinalize('txnclose-hook', txnclosehook)
1084 1088 def txnaborthook(tr2):
1085 1089 """To be run if transaction is aborted
1086 1090 """
1087 1091 reporef().hook('txnabort', throw=False, txnname=desc,
1088 1092 **tr2.hookargs)
1089 1093 tr.addabort('txnabort-hook', txnaborthook)
1090 1094 # avoid eager cache invalidation. in-memory data should be identical
1091 1095 # to stored data if transaction has no error.
1092 1096 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1093 1097 self._transref = weakref.ref(tr)
1094 1098 return tr
1095 1099
1096 1100 def _journalfiles(self):
1097 1101 return ((self.svfs, 'journal'),
1098 1102 (self.vfs, 'journal.dirstate'),
1099 1103 (self.vfs, 'journal.branch'),
1100 1104 (self.vfs, 'journal.desc'),
1101 1105 (self.vfs, 'journal.bookmarks'),
1102 1106 (self.svfs, 'journal.phaseroots'))
1103 1107
1104 1108 def undofiles(self):
1105 1109 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1106 1110
1107 1111 def _writejournal(self, desc):
1108 1112 self.dirstate.savebackup(None, prefix='journal.')
1109 1113 self.vfs.write("journal.branch",
1110 1114 encoding.fromlocal(self.dirstate.branch()))
1111 1115 self.vfs.write("journal.desc",
1112 1116 "%d\n%s\n" % (len(self), desc))
1113 1117 self.vfs.write("journal.bookmarks",
1114 1118 self.vfs.tryread("bookmarks"))
1115 1119 self.svfs.write("journal.phaseroots",
1116 1120 self.svfs.tryread("phaseroots"))
1117 1121
1118 1122 def recover(self):
1119 1123 with self.lock():
1120 1124 if self.svfs.exists("journal"):
1121 1125 self.ui.status(_("rolling back interrupted transaction\n"))
1122 1126 vfsmap = {'': self.svfs,
1123 1127 'plain': self.vfs,}
1124 1128 transaction.rollback(self.svfs, vfsmap, "journal",
1125 1129 self.ui.warn)
1126 1130 self.invalidate()
1127 1131 return True
1128 1132 else:
1129 1133 self.ui.warn(_("no interrupted transaction available\n"))
1130 1134 return False
1131 1135
1132 1136 def rollback(self, dryrun=False, force=False):
1133 1137 wlock = lock = dsguard = None
1134 1138 try:
1135 1139 wlock = self.wlock()
1136 1140 lock = self.lock()
1137 1141 if self.svfs.exists("undo"):
1138 1142 dsguard = cmdutil.dirstateguard(self, 'rollback')
1139 1143
1140 1144 return self._rollback(dryrun, force, dsguard)
1141 1145 else:
1142 1146 self.ui.warn(_("no rollback information available\n"))
1143 1147 return 1
1144 1148 finally:
1145 1149 release(dsguard, lock, wlock)
1146 1150
1147 1151 @unfilteredmethod # Until we get smarter cache management
1148 1152 def _rollback(self, dryrun, force, dsguard):
1149 1153 ui = self.ui
1150 1154 try:
1151 1155 args = self.vfs.read('undo.desc').splitlines()
1152 1156 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1153 1157 if len(args) >= 3:
1154 1158 detail = args[2]
1155 1159 oldtip = oldlen - 1
1156 1160
1157 1161 if detail and ui.verbose:
1158 1162 msg = (_('repository tip rolled back to revision %s'
1159 1163 ' (undo %s: %s)\n')
1160 1164 % (oldtip, desc, detail))
1161 1165 else:
1162 1166 msg = (_('repository tip rolled back to revision %s'
1163 1167 ' (undo %s)\n')
1164 1168 % (oldtip, desc))
1165 1169 except IOError:
1166 1170 msg = _('rolling back unknown transaction\n')
1167 1171 desc = None
1168 1172
1169 1173 if not force and self['.'] != self['tip'] and desc == 'commit':
1170 1174 raise error.Abort(
1171 1175 _('rollback of last commit while not checked out '
1172 1176 'may lose data'), hint=_('use -f to force'))
1173 1177
1174 1178 ui.status(msg)
1175 1179 if dryrun:
1176 1180 return 0
1177 1181
1178 1182 parents = self.dirstate.parents()
1179 1183 self.destroying()
1180 1184 vfsmap = {'plain': self.vfs, '': self.svfs}
1181 1185 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1182 1186 if self.vfs.exists('undo.bookmarks'):
1183 1187 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1184 1188 if self.svfs.exists('undo.phaseroots'):
1185 1189 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1186 1190 self.invalidate()
1187 1191
1188 1192 parentgone = (parents[0] not in self.changelog.nodemap or
1189 1193 parents[1] not in self.changelog.nodemap)
1190 1194 if parentgone:
1191 1195 # prevent dirstateguard from overwriting already restored one
1192 1196 dsguard.close()
1193 1197
1194 1198 self.dirstate.restorebackup(None, prefix='undo.')
1195 1199 try:
1196 1200 branch = self.vfs.read('undo.branch')
1197 1201 self.dirstate.setbranch(encoding.tolocal(branch))
1198 1202 except IOError:
1199 1203 ui.warn(_('named branch could not be reset: '
1200 1204 'current branch is still \'%s\'\n')
1201 1205 % self.dirstate.branch())
1202 1206
1203 1207 parents = tuple([p.rev() for p in self[None].parents()])
1204 1208 if len(parents) > 1:
1205 1209 ui.status(_('working directory now based on '
1206 1210 'revisions %d and %d\n') % parents)
1207 1211 else:
1208 1212 ui.status(_('working directory now based on '
1209 1213 'revision %d\n') % parents)
1210 1214 mergemod.mergestate.clean(self, self['.'].node())
1211 1215
1212 1216 # TODO: if we know which new heads may result from this rollback, pass
1213 1217 # them to destroy(), which will prevent the branchhead cache from being
1214 1218 # invalidated.
1215 1219 self.destroyed()
1216 1220 return 0
1217 1221
1218 1222 def invalidatecaches(self):
1219 1223
1220 1224 if '_tagscache' in vars(self):
1221 1225 # can't use delattr on proxy
1222 1226 del self.__dict__['_tagscache']
1223 1227
1224 1228 self.unfiltered()._branchcaches.clear()
1225 1229 self.invalidatevolatilesets()
1226 1230
1227 1231 def invalidatevolatilesets(self):
1228 1232 self.filteredrevcache.clear()
1229 1233 obsolete.clearobscaches(self)
1230 1234
1231 1235 def invalidatedirstate(self):
1232 1236 '''Invalidates the dirstate, causing the next call to dirstate
1233 1237 to check if it was modified since the last time it was read,
1234 1238 rereading it if it has.
1235 1239
1236 1240 This is different to dirstate.invalidate() that it doesn't always
1237 1241 rereads the dirstate. Use dirstate.invalidate() if you want to
1238 1242 explicitly read the dirstate again (i.e. restoring it to a previous
1239 1243 known good state).'''
1240 1244 if hasunfilteredcache(self, 'dirstate'):
1241 1245 for k in self.dirstate._filecache:
1242 1246 try:
1243 1247 delattr(self.dirstate, k)
1244 1248 except AttributeError:
1245 1249 pass
1246 1250 delattr(self.unfiltered(), 'dirstate')
1247 1251
1248 1252 def invalidate(self, clearfilecache=False):
1249 1253 '''Invalidates both store and non-store parts other than dirstate
1250 1254
1251 1255 If a transaction is running, invalidation of store is omitted,
1252 1256 because discarding in-memory changes might cause inconsistency
1253 1257 (e.g. incomplete fncache causes unintentional failure, but
1254 1258 redundant one doesn't).
1255 1259 '''
1256 1260 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1257 1261 for k in self._filecache.keys():
1258 1262 # dirstate is invalidated separately in invalidatedirstate()
1259 1263 if k == 'dirstate':
1260 1264 continue
1261 1265
1262 1266 if clearfilecache:
1263 1267 del self._filecache[k]
1264 1268 try:
1265 1269 delattr(unfiltered, k)
1266 1270 except AttributeError:
1267 1271 pass
1268 1272 self.invalidatecaches()
1269 1273 if not self.currenttransaction():
1270 1274 # TODO: Changing contents of store outside transaction
1271 1275 # causes inconsistency. We should make in-memory store
1272 1276 # changes detectable, and abort if changed.
1273 1277 self.store.invalidatecaches()
1274 1278
1275 1279 def invalidateall(self):
1276 1280 '''Fully invalidates both store and non-store parts, causing the
1277 1281 subsequent operation to reread any outside changes.'''
1278 1282 # extension should hook this to invalidate its caches
1279 1283 self.invalidate()
1280 1284 self.invalidatedirstate()
1281 1285
1282 1286 @unfilteredmethod
1283 1287 def _refreshfilecachestats(self, tr):
1284 1288 """Reload stats of cached files so that they are flagged as valid"""
1285 1289 for k, ce in self._filecache.items():
1286 1290 if k == 'dirstate' or k not in self.__dict__:
1287 1291 continue
1288 1292 ce.refresh()
1289 1293
1290 1294 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1291 1295 inheritchecker=None, parentenvvar=None):
1292 1296 parentlock = None
1293 1297 # the contents of parentenvvar are used by the underlying lock to
1294 1298 # determine whether it can be inherited
1295 1299 if parentenvvar is not None:
1296 1300 parentlock = os.environ.get(parentenvvar)
1297 1301 try:
1298 1302 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1299 1303 acquirefn=acquirefn, desc=desc,
1300 1304 inheritchecker=inheritchecker,
1301 1305 parentlock=parentlock)
1302 1306 except error.LockHeld as inst:
1303 1307 if not wait:
1304 1308 raise
1305 1309 # show more details for new-style locks
1306 1310 if ':' in inst.locker:
1307 1311 host, pid = inst.locker.split(":", 1)
1308 1312 self.ui.warn(
1309 1313 _("waiting for lock on %s held by process %r "
1310 1314 "on host %r\n") % (desc, pid, host))
1311 1315 else:
1312 1316 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1313 1317 (desc, inst.locker))
1314 1318 # default to 600 seconds timeout
1315 1319 l = lockmod.lock(vfs, lockname,
1316 1320 int(self.ui.config("ui", "timeout", "600")),
1317 1321 releasefn=releasefn, acquirefn=acquirefn,
1318 1322 desc=desc)
1319 1323 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1320 1324 return l
1321 1325
1322 1326 def _afterlock(self, callback):
1323 1327 """add a callback to be run when the repository is fully unlocked
1324 1328
1325 1329 The callback will be executed when the outermost lock is released
1326 1330 (with wlock being higher level than 'lock')."""
1327 1331 for ref in (self._wlockref, self._lockref):
1328 1332 l = ref and ref()
1329 1333 if l and l.held:
1330 1334 l.postrelease.append(callback)
1331 1335 break
1332 1336 else: # no lock have been found.
1333 1337 callback()
1334 1338
1335 1339 def lock(self, wait=True):
1336 1340 '''Lock the repository store (.hg/store) and return a weak reference
1337 1341 to the lock. Use this before modifying the store (e.g. committing or
1338 1342 stripping). If you are opening a transaction, get a lock as well.)
1339 1343
1340 1344 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1341 1345 'wlock' first to avoid a dead-lock hazard.'''
1342 1346 l = self._currentlock(self._lockref)
1343 1347 if l is not None:
1344 1348 l.lock()
1345 1349 return l
1346 1350
1347 1351 l = self._lock(self.svfs, "lock", wait, None,
1348 1352 self.invalidate, _('repository %s') % self.origroot)
1349 1353 self._lockref = weakref.ref(l)
1350 1354 return l
1351 1355
1352 1356 def _wlockchecktransaction(self):
1353 1357 if self.currenttransaction() is not None:
1354 1358 raise error.LockInheritanceContractViolation(
1355 1359 'wlock cannot be inherited in the middle of a transaction')
1356 1360
1357 1361 def wlock(self, wait=True):
1358 1362 '''Lock the non-store parts of the repository (everything under
1359 1363 .hg except .hg/store) and return a weak reference to the lock.
1360 1364
1361 1365 Use this before modifying files in .hg.
1362 1366
1363 1367 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1364 1368 'wlock' first to avoid a dead-lock hazard.'''
1365 1369 l = self._wlockref and self._wlockref()
1366 1370 if l is not None and l.held:
1367 1371 l.lock()
1368 1372 return l
1369 1373
1370 1374 # We do not need to check for non-waiting lock acquisition. Such
1371 1375 # acquisition would not cause dead-lock as they would just fail.
1372 1376 if wait and (self.ui.configbool('devel', 'all-warnings')
1373 1377 or self.ui.configbool('devel', 'check-locks')):
1374 1378 if self._currentlock(self._lockref) is not None:
1375 1379 self.ui.develwarn('"wlock" acquired after "lock"')
1376 1380
1377 1381 def unlock():
1378 1382 if self.dirstate.pendingparentchange():
1379 1383 self.dirstate.invalidate()
1380 1384 else:
1381 1385 self.dirstate.write(None)
1382 1386
1383 1387 self._filecache['dirstate'].refresh()
1384 1388
1385 1389 l = self._lock(self.vfs, "wlock", wait, unlock,
1386 1390 self.invalidatedirstate, _('working directory of %s') %
1387 1391 self.origroot,
1388 1392 inheritchecker=self._wlockchecktransaction,
1389 1393 parentenvvar='HG_WLOCK_LOCKER')
1390 1394 self._wlockref = weakref.ref(l)
1391 1395 return l
1392 1396
1393 1397 def _currentlock(self, lockref):
1394 1398 """Returns the lock if it's held, or None if it's not."""
1395 1399 if lockref is None:
1396 1400 return None
1397 1401 l = lockref()
1398 1402 if l is None or not l.held:
1399 1403 return None
1400 1404 return l
1401 1405
1402 1406 def currentwlock(self):
1403 1407 """Returns the wlock if it's held, or None if it's not."""
1404 1408 return self._currentlock(self._wlockref)
1405 1409
1406 1410 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1407 1411 """
1408 1412 commit an individual file as part of a larger transaction
1409 1413 """
1410 1414
1411 1415 fname = fctx.path()
1412 1416 fparent1 = manifest1.get(fname, nullid)
1413 1417 fparent2 = manifest2.get(fname, nullid)
1414 1418 if isinstance(fctx, context.filectx):
1415 1419 node = fctx.filenode()
1416 1420 if node in [fparent1, fparent2]:
1417 1421 self.ui.debug('reusing %s filelog entry\n' % fname)
1418 1422 if manifest1.flags(fname) != fctx.flags():
1419 1423 changelist.append(fname)
1420 1424 return node
1421 1425
1422 1426 flog = self.file(fname)
1423 1427 meta = {}
1424 1428 copy = fctx.renamed()
1425 1429 if copy and copy[0] != fname:
1426 1430 # Mark the new revision of this file as a copy of another
1427 1431 # file. This copy data will effectively act as a parent
1428 1432 # of this new revision. If this is a merge, the first
1429 1433 # parent will be the nullid (meaning "look up the copy data")
1430 1434 # and the second one will be the other parent. For example:
1431 1435 #
1432 1436 # 0 --- 1 --- 3 rev1 changes file foo
1433 1437 # \ / rev2 renames foo to bar and changes it
1434 1438 # \- 2 -/ rev3 should have bar with all changes and
1435 1439 # should record that bar descends from
1436 1440 # bar in rev2 and foo in rev1
1437 1441 #
1438 1442 # this allows this merge to succeed:
1439 1443 #
1440 1444 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1441 1445 # \ / merging rev3 and rev4 should use bar@rev2
1442 1446 # \- 2 --- 4 as the merge base
1443 1447 #
1444 1448
1445 1449 cfname = copy[0]
1446 1450 crev = manifest1.get(cfname)
1447 1451 newfparent = fparent2
1448 1452
1449 1453 if manifest2: # branch merge
1450 1454 if fparent2 == nullid or crev is None: # copied on remote side
1451 1455 if cfname in manifest2:
1452 1456 crev = manifest2[cfname]
1453 1457 newfparent = fparent1
1454 1458
1455 1459 # Here, we used to search backwards through history to try to find
1456 1460 # where the file copy came from if the source of a copy was not in
1457 1461 # the parent directory. However, this doesn't actually make sense to
1458 1462 # do (what does a copy from something not in your working copy even
1459 1463 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1460 1464 # the user that copy information was dropped, so if they didn't
1461 1465 # expect this outcome it can be fixed, but this is the correct
1462 1466 # behavior in this circumstance.
1463 1467
1464 1468 if crev:
1465 1469 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1466 1470 meta["copy"] = cfname
1467 1471 meta["copyrev"] = hex(crev)
1468 1472 fparent1, fparent2 = nullid, newfparent
1469 1473 else:
1470 1474 self.ui.warn(_("warning: can't find ancestor for '%s' "
1471 1475 "copied from '%s'!\n") % (fname, cfname))
1472 1476
1473 1477 elif fparent1 == nullid:
1474 1478 fparent1, fparent2 = fparent2, nullid
1475 1479 elif fparent2 != nullid:
1476 1480 # is one parent an ancestor of the other?
1477 1481 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1478 1482 if fparent1 in fparentancestors:
1479 1483 fparent1, fparent2 = fparent2, nullid
1480 1484 elif fparent2 in fparentancestors:
1481 1485 fparent2 = nullid
1482 1486
1483 1487 # is the file changed?
1484 1488 text = fctx.data()
1485 1489 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1486 1490 changelist.append(fname)
1487 1491 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1488 1492 # are just the flags changed during merge?
1489 1493 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1490 1494 changelist.append(fname)
1491 1495
1492 1496 return fparent1
1493 1497
1494 1498 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1495 1499 """check for commit arguments that aren't commitable"""
1496 1500 if match.isexact() or match.prefix():
1497 1501 matched = set(status.modified + status.added + status.removed)
1498 1502
1499 1503 for f in match.files():
1500 1504 f = self.dirstate.normalize(f)
1501 1505 if f == '.' or f in matched or f in wctx.substate:
1502 1506 continue
1503 1507 if f in status.deleted:
1504 1508 fail(f, _('file not found!'))
1505 1509 if f in vdirs: # visited directory
1506 1510 d = f + '/'
1507 1511 for mf in matched:
1508 1512 if mf.startswith(d):
1509 1513 break
1510 1514 else:
1511 1515 fail(f, _("no match under directory!"))
1512 1516 elif f not in self.dirstate:
1513 1517 fail(f, _("file not tracked!"))
1514 1518
1515 1519 @unfilteredmethod
1516 1520 def commit(self, text="", user=None, date=None, match=None, force=False,
1517 1521 editor=False, extra=None):
1518 1522 """Add a new revision to current repository.
1519 1523
1520 1524 Revision information is gathered from the working directory,
1521 1525 match can be used to filter the committed files. If editor is
1522 1526 supplied, it is called to get a commit message.
1523 1527 """
1524 1528 if extra is None:
1525 1529 extra = {}
1526 1530
1527 1531 def fail(f, msg):
1528 1532 raise error.Abort('%s: %s' % (f, msg))
1529 1533
1530 1534 if not match:
1531 1535 match = matchmod.always(self.root, '')
1532 1536
1533 1537 if not force:
1534 1538 vdirs = []
1535 1539 match.explicitdir = vdirs.append
1536 1540 match.bad = fail
1537 1541
1538 1542 wlock = lock = tr = None
1539 1543 try:
1540 1544 wlock = self.wlock()
1541 1545 lock = self.lock() # for recent changelog (see issue4368)
1542 1546
1543 1547 wctx = self[None]
1544 1548 merge = len(wctx.parents()) > 1
1545 1549
1546 1550 if not force and merge and match.ispartial():
1547 1551 raise error.Abort(_('cannot partially commit a merge '
1548 1552 '(do not specify files or patterns)'))
1549 1553
1550 1554 status = self.status(match=match, clean=force)
1551 1555 if force:
1552 1556 status.modified.extend(status.clean) # mq may commit clean files
1553 1557
1554 1558 # check subrepos
1555 1559 subs = []
1556 1560 commitsubs = set()
1557 1561 newstate = wctx.substate.copy()
1558 1562 # only manage subrepos and .hgsubstate if .hgsub is present
1559 1563 if '.hgsub' in wctx:
1560 1564 # we'll decide whether to track this ourselves, thanks
1561 1565 for c in status.modified, status.added, status.removed:
1562 1566 if '.hgsubstate' in c:
1563 1567 c.remove('.hgsubstate')
1564 1568
1565 1569 # compare current state to last committed state
1566 1570 # build new substate based on last committed state
1567 1571 oldstate = wctx.p1().substate
1568 1572 for s in sorted(newstate.keys()):
1569 1573 if not match(s):
1570 1574 # ignore working copy, use old state if present
1571 1575 if s in oldstate:
1572 1576 newstate[s] = oldstate[s]
1573 1577 continue
1574 1578 if not force:
1575 1579 raise error.Abort(
1576 1580 _("commit with new subrepo %s excluded") % s)
1577 1581 dirtyreason = wctx.sub(s).dirtyreason(True)
1578 1582 if dirtyreason:
1579 1583 if not self.ui.configbool('ui', 'commitsubrepos'):
1580 1584 raise error.Abort(dirtyreason,
1581 1585 hint=_("use --subrepos for recursive commit"))
1582 1586 subs.append(s)
1583 1587 commitsubs.add(s)
1584 1588 else:
1585 1589 bs = wctx.sub(s).basestate()
1586 1590 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1587 1591 if oldstate.get(s, (None, None, None))[1] != bs:
1588 1592 subs.append(s)
1589 1593
1590 1594 # check for removed subrepos
1591 1595 for p in wctx.parents():
1592 1596 r = [s for s in p.substate if s not in newstate]
1593 1597 subs += [s for s in r if match(s)]
1594 1598 if subs:
1595 1599 if (not match('.hgsub') and
1596 1600 '.hgsub' in (wctx.modified() + wctx.added())):
1597 1601 raise error.Abort(
1598 1602 _("can't commit subrepos without .hgsub"))
1599 1603 status.modified.insert(0, '.hgsubstate')
1600 1604
1601 1605 elif '.hgsub' in status.removed:
1602 1606 # clean up .hgsubstate when .hgsub is removed
1603 1607 if ('.hgsubstate' in wctx and
1604 1608 '.hgsubstate' not in (status.modified + status.added +
1605 1609 status.removed)):
1606 1610 status.removed.insert(0, '.hgsubstate')
1607 1611
1608 1612 # make sure all explicit patterns are matched
1609 1613 if not force:
1610 1614 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1611 1615
1612 1616 cctx = context.workingcommitctx(self, status,
1613 1617 text, user, date, extra)
1614 1618
1615 1619 # internal config: ui.allowemptycommit
1616 1620 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1617 1621 or extra.get('close') or merge or cctx.files()
1618 1622 or self.ui.configbool('ui', 'allowemptycommit'))
1619 1623 if not allowemptycommit:
1620 1624 return None
1621 1625
1622 1626 if merge and cctx.deleted():
1623 1627 raise error.Abort(_("cannot commit merge with missing files"))
1624 1628
1625 1629 ms = mergemod.mergestate.read(self)
1626 1630
1627 1631 if list(ms.unresolved()):
1628 1632 raise error.Abort(_("unresolved merge conflicts "
1629 1633 "(see 'hg help resolve')"))
1630 1634 if ms.mdstate() != 's' or list(ms.driverresolved()):
1631 1635 raise error.Abort(_('driver-resolved merge conflicts'),
1632 1636 hint=_('run "hg resolve --all" to resolve'))
1633 1637
1634 1638 if editor:
1635 1639 cctx._text = editor(self, cctx, subs)
1636 1640 edited = (text != cctx._text)
1637 1641
1638 1642 # Save commit message in case this transaction gets rolled back
1639 1643 # (e.g. by a pretxncommit hook). Leave the content alone on
1640 1644 # the assumption that the user will use the same editor again.
1641 1645 msgfn = self.savecommitmessage(cctx._text)
1642 1646
1643 1647 # commit subs and write new state
1644 1648 if subs:
1645 1649 for s in sorted(commitsubs):
1646 1650 sub = wctx.sub(s)
1647 1651 self.ui.status(_('committing subrepository %s\n') %
1648 1652 subrepo.subrelpath(sub))
1649 1653 sr = sub.commit(cctx._text, user, date)
1650 1654 newstate[s] = (newstate[s][0], sr)
1651 1655 subrepo.writestate(self, newstate)
1652 1656
1653 1657 p1, p2 = self.dirstate.parents()
1654 1658 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1655 1659 try:
1656 1660 self.hook("precommit", throw=True, parent1=hookp1,
1657 1661 parent2=hookp2)
1658 1662 tr = self.transaction('commit')
1659 1663 ret = self.commitctx(cctx, True)
1660 1664 except: # re-raises
1661 1665 if edited:
1662 1666 self.ui.write(
1663 1667 _('note: commit message saved in %s\n') % msgfn)
1664 1668 raise
1665 1669 # update bookmarks, dirstate and mergestate
1666 1670 bookmarks.update(self, [p1, p2], ret)
1667 1671 cctx.markcommitted(ret)
1668 1672 ms.reset()
1669 1673 tr.close()
1670 1674
1671 1675 finally:
1672 1676 lockmod.release(tr, lock, wlock)
1673 1677
1674 1678 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1675 1679 # hack for command that use a temporary commit (eg: histedit)
1676 1680 # temporary commit got stripped before hook release
1677 1681 if self.changelog.hasnode(ret):
1678 1682 self.hook("commit", node=node, parent1=parent1,
1679 1683 parent2=parent2)
1680 1684 self._afterlock(commithook)
1681 1685 return ret
1682 1686
1683 1687 @unfilteredmethod
1684 1688 def commitctx(self, ctx, error=False):
1685 1689 """Add a new revision to current repository.
1686 1690 Revision information is passed via the context argument.
1687 1691 """
1688 1692
1689 1693 tr = None
1690 1694 p1, p2 = ctx.p1(), ctx.p2()
1691 1695 user = ctx.user()
1692 1696
1693 1697 lock = self.lock()
1694 1698 try:
1695 1699 tr = self.transaction("commit")
1696 1700 trp = weakref.proxy(tr)
1697 1701
1698 1702 if ctx.files():
1699 1703 m1 = p1.manifest()
1700 1704 m2 = p2.manifest()
1701 1705 m = m1.copy()
1702 1706
1703 1707 # check in files
1704 1708 added = []
1705 1709 changed = []
1706 1710 removed = list(ctx.removed())
1707 1711 linkrev = len(self)
1708 1712 self.ui.note(_("committing files:\n"))
1709 1713 for f in sorted(ctx.modified() + ctx.added()):
1710 1714 self.ui.note(f + "\n")
1711 1715 try:
1712 1716 fctx = ctx[f]
1713 1717 if fctx is None:
1714 1718 removed.append(f)
1715 1719 else:
1716 1720 added.append(f)
1717 1721 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1718 1722 trp, changed)
1719 1723 m.setflag(f, fctx.flags())
1720 1724 except OSError as inst:
1721 1725 self.ui.warn(_("trouble committing %s!\n") % f)
1722 1726 raise
1723 1727 except IOError as inst:
1724 1728 errcode = getattr(inst, 'errno', errno.ENOENT)
1725 1729 if error or errcode and errcode != errno.ENOENT:
1726 1730 self.ui.warn(_("trouble committing %s!\n") % f)
1727 1731 raise
1728 1732
1729 1733 # update manifest
1730 1734 self.ui.note(_("committing manifest\n"))
1731 1735 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1732 1736 drop = [f for f in removed if f in m]
1733 1737 for f in drop:
1734 1738 del m[f]
1735 1739 mn = self.manifestlog.add(m, trp, linkrev,
1736 1740 p1.manifestnode(), p2.manifestnode(),
1737 1741 added, drop)
1738 1742 files = changed + removed
1739 1743 else:
1740 1744 mn = p1.manifestnode()
1741 1745 files = []
1742 1746
1743 1747 # update changelog
1744 1748 self.ui.note(_("committing changelog\n"))
1745 1749 self.changelog.delayupdate(tr)
1746 1750 n = self.changelog.add(mn, files, ctx.description(),
1747 1751 trp, p1.node(), p2.node(),
1748 1752 user, ctx.date(), ctx.extra().copy())
1749 1753 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1750 1754 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1751 1755 parent2=xp2)
1752 1756 # set the new commit is proper phase
1753 1757 targetphase = subrepo.newcommitphase(self.ui, ctx)
1754 1758 if targetphase:
1755 1759 # retract boundary do not alter parent changeset.
1756 1760 # if a parent have higher the resulting phase will
1757 1761 # be compliant anyway
1758 1762 #
1759 1763 # if minimal phase was 0 we don't need to retract anything
1760 1764 phases.retractboundary(self, tr, targetphase, [n])
1761 1765 tr.close()
1762 1766 branchmap.updatecache(self.filtered('served'))
1763 1767 return n
1764 1768 finally:
1765 1769 if tr:
1766 1770 tr.release()
1767 1771 lock.release()
1768 1772
1769 1773 @unfilteredmethod
1770 1774 def destroying(self):
1771 1775 '''Inform the repository that nodes are about to be destroyed.
1772 1776 Intended for use by strip and rollback, so there's a common
1773 1777 place for anything that has to be done before destroying history.
1774 1778
1775 1779 This is mostly useful for saving state that is in memory and waiting
1776 1780 to be flushed when the current lock is released. Because a call to
1777 1781 destroyed is imminent, the repo will be invalidated causing those
1778 1782 changes to stay in memory (waiting for the next unlock), or vanish
1779 1783 completely.
1780 1784 '''
1781 1785 # When using the same lock to commit and strip, the phasecache is left
1782 1786 # dirty after committing. Then when we strip, the repo is invalidated,
1783 1787 # causing those changes to disappear.
1784 1788 if '_phasecache' in vars(self):
1785 1789 self._phasecache.write()
1786 1790
1787 1791 @unfilteredmethod
1788 1792 def destroyed(self):
1789 1793 '''Inform the repository that nodes have been destroyed.
1790 1794 Intended for use by strip and rollback, so there's a common
1791 1795 place for anything that has to be done after destroying history.
1792 1796 '''
1793 1797 # When one tries to:
1794 1798 # 1) destroy nodes thus calling this method (e.g. strip)
1795 1799 # 2) use phasecache somewhere (e.g. commit)
1796 1800 #
1797 1801 # then 2) will fail because the phasecache contains nodes that were
1798 1802 # removed. We can either remove phasecache from the filecache,
1799 1803 # causing it to reload next time it is accessed, or simply filter
1800 1804 # the removed nodes now and write the updated cache.
1801 1805 self._phasecache.filterunknown(self)
1802 1806 self._phasecache.write()
1803 1807
1804 1808 # update the 'served' branch cache to help read only server process
1805 1809 # Thanks to branchcache collaboration this is done from the nearest
1806 1810 # filtered subset and it is expected to be fast.
1807 1811 branchmap.updatecache(self.filtered('served'))
1808 1812
1809 1813 # Ensure the persistent tag cache is updated. Doing it now
1810 1814 # means that the tag cache only has to worry about destroyed
1811 1815 # heads immediately after a strip/rollback. That in turn
1812 1816 # guarantees that "cachetip == currenttip" (comparing both rev
1813 1817 # and node) always means no nodes have been added or destroyed.
1814 1818
1815 1819 # XXX this is suboptimal when qrefresh'ing: we strip the current
1816 1820 # head, refresh the tag cache, then immediately add a new head.
1817 1821 # But I think doing it this way is necessary for the "instant
1818 1822 # tag cache retrieval" case to work.
1819 1823 self.invalidate()
1820 1824
1821 1825 def walk(self, match, node=None):
1822 1826 '''
1823 1827 walk recursively through the directory tree or a given
1824 1828 changeset, finding all files matched by the match
1825 1829 function
1826 1830 '''
1827 1831 return self[node].walk(match)
1828 1832
1829 1833 def status(self, node1='.', node2=None, match=None,
1830 1834 ignored=False, clean=False, unknown=False,
1831 1835 listsubrepos=False):
1832 1836 '''a convenience method that calls node1.status(node2)'''
1833 1837 return self[node1].status(node2, match, ignored, clean, unknown,
1834 1838 listsubrepos)
1835 1839
1836 1840 def heads(self, start=None):
1837 1841 heads = self.changelog.heads(start)
1838 1842 # sort the output in rev descending order
1839 1843 return sorted(heads, key=self.changelog.rev, reverse=True)
1840 1844
1841 1845 def branchheads(self, branch=None, start=None, closed=False):
1842 1846 '''return a (possibly filtered) list of heads for the given branch
1843 1847
1844 1848 Heads are returned in topological order, from newest to oldest.
1845 1849 If branch is None, use the dirstate branch.
1846 1850 If start is not None, return only heads reachable from start.
1847 1851 If closed is True, return heads that are marked as closed as well.
1848 1852 '''
1849 1853 if branch is None:
1850 1854 branch = self[None].branch()
1851 1855 branches = self.branchmap()
1852 1856 if branch not in branches:
1853 1857 return []
1854 1858 # the cache returns heads ordered lowest to highest
1855 1859 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1856 1860 if start is not None:
1857 1861 # filter out the heads that cannot be reached from startrev
1858 1862 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1859 1863 bheads = [h for h in bheads if h in fbheads]
1860 1864 return bheads
1861 1865
1862 1866 def branches(self, nodes):
1863 1867 if not nodes:
1864 1868 nodes = [self.changelog.tip()]
1865 1869 b = []
1866 1870 for n in nodes:
1867 1871 t = n
1868 1872 while True:
1869 1873 p = self.changelog.parents(n)
1870 1874 if p[1] != nullid or p[0] == nullid:
1871 1875 b.append((t, n, p[0], p[1]))
1872 1876 break
1873 1877 n = p[0]
1874 1878 return b
1875 1879
1876 1880 def between(self, pairs):
1877 1881 r = []
1878 1882
1879 1883 for top, bottom in pairs:
1880 1884 n, l, i = top, [], 0
1881 1885 f = 1
1882 1886
1883 1887 while n != bottom and n != nullid:
1884 1888 p = self.changelog.parents(n)[0]
1885 1889 if i == f:
1886 1890 l.append(n)
1887 1891 f = f * 2
1888 1892 n = p
1889 1893 i += 1
1890 1894
1891 1895 r.append(l)
1892 1896
1893 1897 return r
1894 1898
1895 1899 def checkpush(self, pushop):
1896 1900 """Extensions can override this function if additional checks have
1897 1901 to be performed before pushing, or call it if they override push
1898 1902 command.
1899 1903 """
1900 1904 pass
1901 1905
1902 1906 @unfilteredpropertycache
1903 1907 def prepushoutgoinghooks(self):
1904 1908 """Return util.hooks consists of a pushop with repo, remote, outgoing
1905 1909 methods, which are called before pushing changesets.
1906 1910 """
1907 1911 return util.hooks()
1908 1912
1909 1913 def pushkey(self, namespace, key, old, new):
1910 1914 try:
1911 1915 tr = self.currenttransaction()
1912 1916 hookargs = {}
1913 1917 if tr is not None:
1914 1918 hookargs.update(tr.hookargs)
1915 1919 hookargs['namespace'] = namespace
1916 1920 hookargs['key'] = key
1917 1921 hookargs['old'] = old
1918 1922 hookargs['new'] = new
1919 1923 self.hook('prepushkey', throw=True, **hookargs)
1920 1924 except error.HookAbort as exc:
1921 1925 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1922 1926 if exc.hint:
1923 1927 self.ui.write_err(_("(%s)\n") % exc.hint)
1924 1928 return False
1925 1929 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1926 1930 ret = pushkey.push(self, namespace, key, old, new)
1927 1931 def runhook():
1928 1932 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1929 1933 ret=ret)
1930 1934 self._afterlock(runhook)
1931 1935 return ret
1932 1936
1933 1937 def listkeys(self, namespace):
1934 1938 self.hook('prelistkeys', throw=True, namespace=namespace)
1935 1939 self.ui.debug('listing keys for "%s"\n' % namespace)
1936 1940 values = pushkey.list(self, namespace)
1937 1941 self.hook('listkeys', namespace=namespace, values=values)
1938 1942 return values
1939 1943
1940 1944 def debugwireargs(self, one, two, three=None, four=None, five=None):
1941 1945 '''used to test argument passing over the wire'''
1942 1946 return "%s %s %s %s %s" % (one, two, three, four, five)
1943 1947
1944 1948 def savecommitmessage(self, text):
1945 1949 fp = self.vfs('last-message.txt', 'wb')
1946 1950 try:
1947 1951 fp.write(text)
1948 1952 finally:
1949 1953 fp.close()
1950 1954 return self.pathto(fp.name[len(self.root) + 1:])
1951 1955
1952 1956 # used to avoid circular references so destructors work
1953 1957 def aftertrans(files):
1954 1958 renamefiles = [tuple(t) for t in files]
1955 1959 def a():
1956 1960 for vfs, src, dest in renamefiles:
1957 1961 try:
1958 1962 vfs.rename(src, dest)
1959 1963 except OSError: # journal file does not yet exist
1960 1964 pass
1961 1965 return a
1962 1966
1963 1967 def undoname(fn):
1964 1968 base, name = os.path.split(fn)
1965 1969 assert name.startswith('journal')
1966 1970 return os.path.join(base, name.replace('journal', 'undo', 1))
1967 1971
1968 1972 def instance(ui, path, create):
1969 1973 return localrepository(ui, util.urllocalpath(path), create)
1970 1974
1971 1975 def islocal(path):
1972 1976 return True
1973 1977
1974 1978 def newreporequirements(repo):
1975 1979 """Determine the set of requirements for a new local repository.
1976 1980
1977 1981 Extensions can wrap this function to specify custom requirements for
1978 1982 new repositories.
1979 1983 """
1980 1984 ui = repo.ui
1981 1985 requirements = set(['revlogv1'])
1982 1986 if ui.configbool('format', 'usestore', True):
1983 1987 requirements.add('store')
1984 1988 if ui.configbool('format', 'usefncache', True):
1985 1989 requirements.add('fncache')
1986 1990 if ui.configbool('format', 'dotencode', True):
1987 1991 requirements.add('dotencode')
1988 1992
1989 1993 if scmutil.gdinitconfig(ui):
1990 1994 requirements.add('generaldelta')
1991 1995 if ui.configbool('experimental', 'treemanifest', False):
1992 1996 requirements.add('treemanifest')
1993 1997 if ui.configbool('experimental', 'manifestv2', False):
1994 1998 requirements.add('manifestv2')
1995 1999
1996 2000 return requirements
@@ -1,957 +1,959
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 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 hashlib
11 11 import itertools
12 12 import os
13 13 import sys
14 14 import tempfile
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 bin,
19 19 hex,
20 20 )
21 21
22 22 from . import (
23 23 bundle2,
24 24 changegroup as changegroupmod,
25 25 encoding,
26 26 error,
27 27 exchange,
28 28 peer,
29 29 pushkey as pushkeymod,
30 30 streamclone,
31 31 util,
32 32 )
33 33
34 34 urlerr = util.urlerr
35 35 urlreq = util.urlreq
36 36
37 37 bundle2required = _(
38 38 'incompatible Mercurial client; bundle2 required\n'
39 39 '(see https://www.mercurial-scm.org/wiki/IncompatibleClient)\n')
40 40
41 41 class abstractserverproto(object):
42 42 """abstract class that summarizes the protocol API
43 43
44 44 Used as reference and documentation.
45 45 """
46 46
47 47 def getargs(self, args):
48 48 """return the value for arguments in <args>
49 49
50 50 returns a list of values (same order as <args>)"""
51 51 raise NotImplementedError()
52 52
53 53 def getfile(self, fp):
54 54 """write the whole content of a file into a file like object
55 55
56 56 The file is in the form::
57 57
58 58 (<chunk-size>\n<chunk>)+0\n
59 59
60 60 chunk size is the ascii version of the int.
61 61 """
62 62 raise NotImplementedError()
63 63
64 64 def redirect(self):
65 65 """may setup interception for stdout and stderr
66 66
67 67 See also the `restore` method."""
68 68 raise NotImplementedError()
69 69
70 70 # If the `redirect` function does install interception, the `restore`
71 71 # function MUST be defined. If interception is not used, this function
72 72 # MUST NOT be defined.
73 73 #
74 74 # left commented here on purpose
75 75 #
76 76 #def restore(self):
77 77 # """reinstall previous stdout and stderr and return intercepted stdout
78 78 # """
79 79 # raise NotImplementedError()
80 80
81 81 def groupchunks(self, fh):
82 82 """Generator of chunks to send to the client.
83 83
84 84 Some protocols may have compressed the contents.
85 85 """
86 86 raise NotImplementedError()
87 87
88 88 class remotebatch(peer.batcher):
89 89 '''batches the queued calls; uses as few roundtrips as possible'''
90 90 def __init__(self, remote):
91 91 '''remote must support _submitbatch(encbatch) and
92 92 _submitone(op, encargs)'''
93 93 peer.batcher.__init__(self)
94 94 self.remote = remote
95 95 def submit(self):
96 96 req, rsp = [], []
97 97 for name, args, opts, resref in self.calls:
98 98 mtd = getattr(self.remote, name)
99 99 batchablefn = getattr(mtd, 'batchable', None)
100 100 if batchablefn is not None:
101 101 batchable = batchablefn(mtd.im_self, *args, **opts)
102 102 encargsorres, encresref = next(batchable)
103 103 if encresref:
104 104 req.append((name, encargsorres,))
105 105 rsp.append((batchable, encresref, resref,))
106 106 else:
107 107 resref.set(encargsorres)
108 108 else:
109 109 if req:
110 110 self._submitreq(req, rsp)
111 111 req, rsp = [], []
112 112 resref.set(mtd(*args, **opts))
113 113 if req:
114 114 self._submitreq(req, rsp)
115 115 def _submitreq(self, req, rsp):
116 116 encresults = self.remote._submitbatch(req)
117 117 for encres, r in zip(encresults, rsp):
118 118 batchable, encresref, resref = r
119 119 encresref.set(encres)
120 120 resref.set(next(batchable))
121 121
122 122 class remoteiterbatcher(peer.iterbatcher):
123 123 def __init__(self, remote):
124 124 super(remoteiterbatcher, self).__init__()
125 125 self._remote = remote
126 126
127 127 def __getattr__(self, name):
128 128 if not getattr(self._remote, name, False):
129 129 raise AttributeError(
130 130 'Attempted to iterbatch non-batchable call to %r' % name)
131 131 return super(remoteiterbatcher, self).__getattr__(name)
132 132
133 133 def submit(self):
134 134 """Break the batch request into many patch calls and pipeline them.
135 135
136 136 This is mostly valuable over http where request sizes can be
137 137 limited, but can be used in other places as well.
138 138 """
139 139 req, rsp = [], []
140 140 for name, args, opts, resref in self.calls:
141 141 mtd = getattr(self._remote, name)
142 142 batchable = mtd.batchable(mtd.im_self, *args, **opts)
143 143 encargsorres, encresref = next(batchable)
144 144 assert encresref
145 145 req.append((name, encargsorres))
146 146 rsp.append((batchable, encresref))
147 147 if req:
148 148 self._resultiter = self._remote._submitbatch(req)
149 149 self._rsp = rsp
150 150
151 151 def results(self):
152 152 for (batchable, encresref), encres in itertools.izip(
153 153 self._rsp, self._resultiter):
154 154 encresref.set(encres)
155 155 yield next(batchable)
156 156
157 157 # Forward a couple of names from peer to make wireproto interactions
158 158 # slightly more sensible.
159 159 batchable = peer.batchable
160 160 future = peer.future
161 161
162 162 # list of nodes encoding / decoding
163 163
164 164 def decodelist(l, sep=' '):
165 165 if l:
166 166 return map(bin, l.split(sep))
167 167 return []
168 168
169 169 def encodelist(l, sep=' '):
170 170 try:
171 171 return sep.join(map(hex, l))
172 172 except TypeError:
173 173 raise
174 174
175 175 # batched call argument encoding
176 176
177 177 def escapearg(plain):
178 178 return (plain
179 179 .replace(':', ':c')
180 180 .replace(',', ':o')
181 181 .replace(';', ':s')
182 182 .replace('=', ':e'))
183 183
184 184 def unescapearg(escaped):
185 185 return (escaped
186 186 .replace(':e', '=')
187 187 .replace(':s', ';')
188 188 .replace(':o', ',')
189 189 .replace(':c', ':'))
190 190
191 191 def encodebatchcmds(req):
192 192 """Return a ``cmds`` argument value for the ``batch`` command."""
193 193 cmds = []
194 194 for op, argsdict in req:
195 195 # Old servers didn't properly unescape argument names. So prevent
196 196 # the sending of argument names that may not be decoded properly by
197 197 # servers.
198 198 assert all(escapearg(k) == k for k in argsdict)
199 199
200 200 args = ','.join('%s=%s' % (escapearg(k), escapearg(v))
201 201 for k, v in argsdict.iteritems())
202 202 cmds.append('%s %s' % (op, args))
203 203
204 204 return ';'.join(cmds)
205 205
206 206 # mapping of options accepted by getbundle and their types
207 207 #
208 208 # Meant to be extended by extensions. It is extensions responsibility to ensure
209 209 # such options are properly processed in exchange.getbundle.
210 210 #
211 211 # supported types are:
212 212 #
213 213 # :nodes: list of binary nodes
214 214 # :csv: list of comma-separated values
215 215 # :scsv: list of comma-separated values return as set
216 216 # :plain: string with no transformation needed.
217 217 gboptsmap = {'heads': 'nodes',
218 218 'common': 'nodes',
219 219 'obsmarkers': 'boolean',
220 220 'bundlecaps': 'scsv',
221 221 'listkeys': 'csv',
222 222 'cg': 'boolean',
223 223 'cbattempted': 'boolean'}
224 224
225 225 # client side
226 226
227 227 class wirepeer(peer.peerrepository):
228 228 """Client-side interface for communicating with a peer repository.
229 229
230 230 Methods commonly call wire protocol commands of the same name.
231 231
232 232 See also httppeer.py and sshpeer.py for protocol-specific
233 233 implementations of this interface.
234 234 """
235 235 def batch(self):
236 236 if self.capable('batch'):
237 237 return remotebatch(self)
238 238 else:
239 239 return peer.localbatch(self)
240 240 def _submitbatch(self, req):
241 241 """run batch request <req> on the server
242 242
243 243 Returns an iterator of the raw responses from the server.
244 244 """
245 245 rsp = self._callstream("batch", cmds=encodebatchcmds(req))
246 246 chunk = rsp.read(1024)
247 247 work = [chunk]
248 248 while chunk:
249 249 while ';' not in chunk and chunk:
250 250 chunk = rsp.read(1024)
251 251 work.append(chunk)
252 252 merged = ''.join(work)
253 253 while ';' in merged:
254 254 one, merged = merged.split(';', 1)
255 255 yield unescapearg(one)
256 256 chunk = rsp.read(1024)
257 257 work = [merged, chunk]
258 258 yield unescapearg(''.join(work))
259 259
260 260 def _submitone(self, op, args):
261 261 return self._call(op, **args)
262 262
263 263 def iterbatch(self):
264 264 return remoteiterbatcher(self)
265 265
266 266 @batchable
267 267 def lookup(self, key):
268 268 self.requirecap('lookup', _('look up remote revision'))
269 269 f = future()
270 270 yield {'key': encoding.fromlocal(key)}, f
271 271 d = f.value
272 272 success, data = d[:-1].split(" ", 1)
273 273 if int(success):
274 274 yield bin(data)
275 275 self._abort(error.RepoError(data))
276 276
277 277 @batchable
278 278 def heads(self):
279 279 f = future()
280 280 yield {}, f
281 281 d = f.value
282 282 try:
283 283 yield decodelist(d[:-1])
284 284 except ValueError:
285 285 self._abort(error.ResponseError(_("unexpected response:"), d))
286 286
287 287 @batchable
288 288 def known(self, nodes):
289 289 f = future()
290 290 yield {'nodes': encodelist(nodes)}, f
291 291 d = f.value
292 292 try:
293 293 yield [bool(int(b)) for b in d]
294 294 except ValueError:
295 295 self._abort(error.ResponseError(_("unexpected response:"), d))
296 296
297 297 @batchable
298 298 def branchmap(self):
299 299 f = future()
300 300 yield {}, f
301 301 d = f.value
302 302 try:
303 303 branchmap = {}
304 304 for branchpart in d.splitlines():
305 305 branchname, branchheads = branchpart.split(' ', 1)
306 306 branchname = encoding.tolocal(urlreq.unquote(branchname))
307 307 branchheads = decodelist(branchheads)
308 308 branchmap[branchname] = branchheads
309 309 yield branchmap
310 310 except TypeError:
311 311 self._abort(error.ResponseError(_("unexpected response:"), d))
312 312
313 313 def branches(self, nodes):
314 314 n = encodelist(nodes)
315 315 d = self._call("branches", nodes=n)
316 316 try:
317 317 br = [tuple(decodelist(b)) for b in d.splitlines()]
318 318 return br
319 319 except ValueError:
320 320 self._abort(error.ResponseError(_("unexpected response:"), d))
321 321
322 322 def between(self, pairs):
323 323 batch = 8 # avoid giant requests
324 324 r = []
325 325 for i in xrange(0, len(pairs), batch):
326 326 n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
327 327 d = self._call("between", pairs=n)
328 328 try:
329 329 r.extend(l and decodelist(l) or [] for l in d.splitlines())
330 330 except ValueError:
331 331 self._abort(error.ResponseError(_("unexpected response:"), d))
332 332 return r
333 333
334 334 @batchable
335 335 def pushkey(self, namespace, key, old, new):
336 336 if not self.capable('pushkey'):
337 337 yield False, None
338 338 f = future()
339 339 self.ui.debug('preparing pushkey for "%s:%s"\n' % (namespace, key))
340 340 yield {'namespace': encoding.fromlocal(namespace),
341 341 'key': encoding.fromlocal(key),
342 342 'old': encoding.fromlocal(old),
343 343 'new': encoding.fromlocal(new)}, f
344 344 d = f.value
345 345 d, output = d.split('\n', 1)
346 346 try:
347 347 d = bool(int(d))
348 348 except ValueError:
349 349 raise error.ResponseError(
350 350 _('push failed (unexpected response):'), d)
351 351 for l in output.splitlines(True):
352 352 self.ui.status(_('remote: '), l)
353 353 yield d
354 354
355 355 @batchable
356 356 def listkeys(self, namespace):
357 357 if not self.capable('pushkey'):
358 358 yield {}, None
359 359 f = future()
360 360 self.ui.debug('preparing listkeys for "%s"\n' % namespace)
361 361 yield {'namespace': encoding.fromlocal(namespace)}, f
362 362 d = f.value
363 363 self.ui.debug('received listkey for "%s": %i bytes\n'
364 364 % (namespace, len(d)))
365 365 yield pushkeymod.decodekeys(d)
366 366
367 367 def stream_out(self):
368 368 return self._callstream('stream_out')
369 369
370 370 def changegroup(self, nodes, kind):
371 371 n = encodelist(nodes)
372 372 f = self._callcompressable("changegroup", roots=n)
373 373 return changegroupmod.cg1unpacker(f, 'UN')
374 374
375 375 def changegroupsubset(self, bases, heads, kind):
376 376 self.requirecap('changegroupsubset', _('look up remote changes'))
377 377 bases = encodelist(bases)
378 378 heads = encodelist(heads)
379 379 f = self._callcompressable("changegroupsubset",
380 380 bases=bases, heads=heads)
381 381 return changegroupmod.cg1unpacker(f, 'UN')
382 382
383 383 def getbundle(self, source, **kwargs):
384 384 self.requirecap('getbundle', _('look up remote changes'))
385 385 opts = {}
386 386 bundlecaps = kwargs.get('bundlecaps')
387 387 if bundlecaps is not None:
388 388 kwargs['bundlecaps'] = sorted(bundlecaps)
389 389 else:
390 390 bundlecaps = () # kwargs could have it to None
391 391 for key, value in kwargs.iteritems():
392 392 if value is None:
393 393 continue
394 394 keytype = gboptsmap.get(key)
395 395 if keytype is None:
396 396 assert False, 'unexpected'
397 397 elif keytype == 'nodes':
398 398 value = encodelist(value)
399 399 elif keytype in ('csv', 'scsv'):
400 400 value = ','.join(value)
401 401 elif keytype == 'boolean':
402 402 value = '%i' % bool(value)
403 403 elif keytype != 'plain':
404 404 raise KeyError('unknown getbundle option type %s'
405 405 % keytype)
406 406 opts[key] = value
407 407 f = self._callcompressable("getbundle", **opts)
408 408 if any((cap.startswith('HG2') for cap in bundlecaps)):
409 409 return bundle2.getunbundler(self.ui, f)
410 410 else:
411 411 return changegroupmod.cg1unpacker(f, 'UN')
412 412
413 413 def unbundle(self, cg, heads, url):
414 414 '''Send cg (a readable file-like object representing the
415 415 changegroup to push, typically a chunkbuffer object) to the
416 416 remote server as a bundle.
417 417
418 418 When pushing a bundle10 stream, return an integer indicating the
419 419 result of the push (see localrepository.addchangegroup()).
420 420
421 421 When pushing a bundle20 stream, return a bundle20 stream.
422 422
423 423 `url` is the url the client thinks it's pushing to, which is
424 424 visible to hooks.
425 425 '''
426 426
427 427 if heads != ['force'] and self.capable('unbundlehash'):
428 428 heads = encodelist(['hashed',
429 429 hashlib.sha1(''.join(sorted(heads))).digest()])
430 430 else:
431 431 heads = encodelist(heads)
432 432
433 433 if util.safehasattr(cg, 'deltaheader'):
434 434 # this a bundle10, do the old style call sequence
435 435 ret, output = self._callpush("unbundle", cg, heads=heads)
436 436 if ret == "":
437 437 raise error.ResponseError(
438 438 _('push failed:'), output)
439 439 try:
440 440 ret = int(ret)
441 441 except ValueError:
442 442 raise error.ResponseError(
443 443 _('push failed (unexpected response):'), ret)
444 444
445 445 for l in output.splitlines(True):
446 446 self.ui.status(_('remote: '), l)
447 447 else:
448 448 # bundle2 push. Send a stream, fetch a stream.
449 449 stream = self._calltwowaystream('unbundle', cg, heads=heads)
450 450 ret = bundle2.getunbundler(self.ui, stream)
451 451 return ret
452 452
453 453 def debugwireargs(self, one, two, three=None, four=None, five=None):
454 454 # don't pass optional arguments left at their default value
455 455 opts = {}
456 456 if three is not None:
457 457 opts['three'] = three
458 458 if four is not None:
459 459 opts['four'] = four
460 460 return self._call('debugwireargs', one=one, two=two, **opts)
461 461
462 462 def _call(self, cmd, **args):
463 463 """execute <cmd> on the server
464 464
465 465 The command is expected to return a simple string.
466 466
467 467 returns the server reply as a string."""
468 468 raise NotImplementedError()
469 469
470 470 def _callstream(self, cmd, **args):
471 471 """execute <cmd> on the server
472 472
473 473 The command is expected to return a stream. Note that if the
474 474 command doesn't return a stream, _callstream behaves
475 475 differently for ssh and http peers.
476 476
477 477 returns the server reply as a file like object.
478 478 """
479 479 raise NotImplementedError()
480 480
481 481 def _callcompressable(self, cmd, **args):
482 482 """execute <cmd> on the server
483 483
484 484 The command is expected to return a stream.
485 485
486 486 The stream may have been compressed in some implementations. This
487 487 function takes care of the decompression. This is the only difference
488 488 with _callstream.
489 489
490 490 returns the server reply as a file like object.
491 491 """
492 492 raise NotImplementedError()
493 493
494 494 def _callpush(self, cmd, fp, **args):
495 495 """execute a <cmd> on server
496 496
497 497 The command is expected to be related to a push. Push has a special
498 498 return method.
499 499
500 500 returns the server reply as a (ret, output) tuple. ret is either
501 501 empty (error) or a stringified int.
502 502 """
503 503 raise NotImplementedError()
504 504
505 505 def _calltwowaystream(self, cmd, fp, **args):
506 506 """execute <cmd> on server
507 507
508 508 The command will send a stream to the server and get a stream in reply.
509 509 """
510 510 raise NotImplementedError()
511 511
512 512 def _abort(self, exception):
513 513 """clearly abort the wire protocol connection and raise the exception
514 514 """
515 515 raise NotImplementedError()
516 516
517 517 # server side
518 518
519 519 # wire protocol command can either return a string or one of these classes.
520 520 class streamres(object):
521 521 """wireproto reply: binary stream
522 522
523 523 The call was successful and the result is a stream.
524 524 Iterate on the `self.gen` attribute to retrieve chunks.
525 525 """
526 526 def __init__(self, gen):
527 527 self.gen = gen
528 528
529 529 class pushres(object):
530 530 """wireproto reply: success with simple integer return
531 531
532 532 The call was successful and returned an integer contained in `self.res`.
533 533 """
534 534 def __init__(self, res):
535 535 self.res = res
536 536
537 537 class pusherr(object):
538 538 """wireproto reply: failure
539 539
540 540 The call failed. The `self.res` attribute contains the error message.
541 541 """
542 542 def __init__(self, res):
543 543 self.res = res
544 544
545 545 class ooberror(object):
546 546 """wireproto reply: failure of a batch of operation
547 547
548 548 Something failed during a batch call. The error message is stored in
549 549 `self.message`.
550 550 """
551 551 def __init__(self, message):
552 552 self.message = message
553 553
554 554 def getdispatchrepo(repo, proto, command):
555 555 """Obtain the repo used for processing wire protocol commands.
556 556
557 557 The intent of this function is to serve as a monkeypatch point for
558 558 extensions that need commands to operate on different repo views under
559 559 specialized circumstances.
560 560 """
561 561 return repo.filtered('served')
562 562
563 563 def dispatch(repo, proto, command):
564 564 repo = getdispatchrepo(repo, proto, command)
565 565 func, spec = commands[command]
566 566 args = proto.getargs(spec)
567 567 return func(repo, proto, *args)
568 568
569 569 def options(cmd, keys, others):
570 570 opts = {}
571 571 for k in keys:
572 572 if k in others:
573 573 opts[k] = others[k]
574 574 del others[k]
575 575 if others:
576 576 sys.stderr.write("warning: %s ignored unexpected arguments %s\n"
577 577 % (cmd, ",".join(others)))
578 578 return opts
579 579
580 580 def bundle1allowed(repo, action):
581 581 """Whether a bundle1 operation is allowed from the server.
582 582
583 583 Priority is:
584 584
585 585 1. server.bundle1gd.<action> (if generaldelta active)
586 586 2. server.bundle1.<action>
587 587 3. server.bundle1gd (if generaldelta active)
588 588 4. server.bundle1
589 589 """
590 590 ui = repo.ui
591 591 gd = 'generaldelta' in repo.requirements
592 592
593 593 if gd:
594 594 v = ui.configbool('server', 'bundle1gd.%s' % action, None)
595 595 if v is not None:
596 596 return v
597 597
598 598 v = ui.configbool('server', 'bundle1.%s' % action, None)
599 599 if v is not None:
600 600 return v
601 601
602 602 if gd:
603 603 v = ui.configbool('server', 'bundle1gd', None)
604 604 if v is not None:
605 605 return v
606 606
607 607 return ui.configbool('server', 'bundle1', True)
608 608
609 609 # list of commands
610 610 commands = {}
611 611
612 612 def wireprotocommand(name, args=''):
613 613 """decorator for wire protocol command"""
614 614 def register(func):
615 615 commands[name] = (func, args)
616 616 return func
617 617 return register
618 618
619 619 @wireprotocommand('batch', 'cmds *')
620 620 def batch(repo, proto, cmds, others):
621 621 repo = repo.filtered("served")
622 622 res = []
623 623 for pair in cmds.split(';'):
624 624 op, args = pair.split(' ', 1)
625 625 vals = {}
626 626 for a in args.split(','):
627 627 if a:
628 628 n, v = a.split('=')
629 629 vals[unescapearg(n)] = unescapearg(v)
630 630 func, spec = commands[op]
631 631 if spec:
632 632 keys = spec.split()
633 633 data = {}
634 634 for k in keys:
635 635 if k == '*':
636 636 star = {}
637 637 for key in vals.keys():
638 638 if key not in keys:
639 639 star[key] = vals[key]
640 640 data['*'] = star
641 641 else:
642 642 data[k] = vals[k]
643 643 result = func(repo, proto, *[data[k] for k in keys])
644 644 else:
645 645 result = func(repo, proto)
646 646 if isinstance(result, ooberror):
647 647 return result
648 648 res.append(escapearg(result))
649 649 return ';'.join(res)
650 650
651 651 @wireprotocommand('between', 'pairs')
652 652 def between(repo, proto, pairs):
653 653 pairs = [decodelist(p, '-') for p in pairs.split(" ")]
654 654 r = []
655 655 for b in repo.between(pairs):
656 656 r.append(encodelist(b) + "\n")
657 657 return "".join(r)
658 658
659 659 @wireprotocommand('branchmap')
660 660 def branchmap(repo, proto):
661 661 branchmap = repo.branchmap()
662 662 heads = []
663 663 for branch, nodes in branchmap.iteritems():
664 664 branchname = urlreq.quote(encoding.fromlocal(branch))
665 665 branchnodes = encodelist(nodes)
666 666 heads.append('%s %s' % (branchname, branchnodes))
667 667 return '\n'.join(heads)
668 668
669 669 @wireprotocommand('branches', 'nodes')
670 670 def branches(repo, proto, nodes):
671 671 nodes = decodelist(nodes)
672 672 r = []
673 673 for b in repo.branches(nodes):
674 674 r.append(encodelist(b) + "\n")
675 675 return "".join(r)
676 676
677 677 @wireprotocommand('clonebundles', '')
678 678 def clonebundles(repo, proto):
679 679 """Server command for returning info for available bundles to seed clones.
680 680
681 681 Clients will parse this response and determine what bundle to fetch.
682 682
683 683 Extensions may wrap this command to filter or dynamically emit data
684 684 depending on the request. e.g. you could advertise URLs for the closest
685 685 data center given the client's IP address.
686 686 """
687 687 return repo.opener.tryread('clonebundles.manifest')
688 688
689 689 wireprotocaps = ['lookup', 'changegroupsubset', 'branchmap', 'pushkey',
690 690 'known', 'getbundle', 'unbundlehash', 'batch']
691 691
692 692 def _capabilities(repo, proto):
693 693 """return a list of capabilities for a repo
694 694
695 695 This function exists to allow extensions to easily wrap capabilities
696 696 computation
697 697
698 698 - returns a lists: easy to alter
699 699 - change done here will be propagated to both `capabilities` and `hello`
700 700 command without any other action needed.
701 701 """
702 702 # copy to prevent modification of the global list
703 703 caps = list(wireprotocaps)
704 704 if streamclone.allowservergeneration(repo.ui):
705 705 if repo.ui.configbool('server', 'preferuncompressed', False):
706 706 caps.append('stream-preferred')
707 707 requiredformats = repo.requirements & repo.supportedformats
708 708 # if our local revlogs are just revlogv1, add 'stream' cap
709 709 if not requiredformats - set(('revlogv1',)):
710 710 caps.append('stream')
711 711 # otherwise, add 'streamreqs' detailing our local revlog format
712 712 else:
713 713 caps.append('streamreqs=%s' % ','.join(sorted(requiredformats)))
714 714 if repo.ui.configbool('experimental', 'bundle2-advertise', True):
715 715 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
716 716 caps.append('bundle2=' + urlreq.quote(capsblob))
717 717 caps.append('unbundle=%s' % ','.join(bundle2.bundlepriority))
718 718 caps.append(
719 719 'httpheader=%d' % repo.ui.configint('server', 'maxhttpheaderlen', 1024))
720 720 if repo.ui.configbool('experimental', 'httppostargs', False):
721 721 caps.append('httppostargs')
722 722 return caps
723 723
724 724 # If you are writing an extension and consider wrapping this function. Wrap
725 725 # `_capabilities` instead.
726 726 @wireprotocommand('capabilities')
727 727 def capabilities(repo, proto):
728 728 return ' '.join(_capabilities(repo, proto))
729 729
730 730 @wireprotocommand('changegroup', 'roots')
731 731 def changegroup(repo, proto, roots):
732 732 nodes = decodelist(roots)
733 733 cg = changegroupmod.changegroup(repo, nodes, 'serve')
734 734 return streamres(proto.groupchunks(cg))
735 735
736 736 @wireprotocommand('changegroupsubset', 'bases heads')
737 737 def changegroupsubset(repo, proto, bases, heads):
738 738 bases = decodelist(bases)
739 739 heads = decodelist(heads)
740 740 cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
741 741 return streamres(proto.groupchunks(cg))
742 742
743 743 @wireprotocommand('debugwireargs', 'one two *')
744 744 def debugwireargs(repo, proto, one, two, others):
745 745 # only accept optional args from the known set
746 746 opts = options('debugwireargs', ['three', 'four'], others)
747 747 return repo.debugwireargs(one, two, **opts)
748 748
749 749 @wireprotocommand('getbundle', '*')
750 750 def getbundle(repo, proto, others):
751 751 opts = options('getbundle', gboptsmap.keys(), others)
752 752 for k, v in opts.iteritems():
753 753 keytype = gboptsmap[k]
754 754 if keytype == 'nodes':
755 755 opts[k] = decodelist(v)
756 756 elif keytype == 'csv':
757 757 opts[k] = list(v.split(','))
758 758 elif keytype == 'scsv':
759 759 opts[k] = set(v.split(','))
760 760 elif keytype == 'boolean':
761 761 # Client should serialize False as '0', which is a non-empty string
762 762 # so it evaluates as a True bool.
763 763 if v == '0':
764 764 opts[k] = False
765 765 else:
766 766 opts[k] = bool(v)
767 767 elif keytype != 'plain':
768 768 raise KeyError('unknown getbundle option type %s'
769 769 % keytype)
770 770
771 771 if not bundle1allowed(repo, 'pull'):
772 772 if not exchange.bundle2requested(opts.get('bundlecaps')):
773 773 return ooberror(bundle2required)
774 774
775 cg = exchange.getbundle(repo, 'serve', **opts)
776 return streamres(proto.groupchunks(cg))
775 chunks = exchange.getbundlechunks(repo, 'serve', **opts)
776 # TODO avoid util.chunkbuffer() here since it is adding overhead to
777 # what is fundamentally a generator proxying operation.
778 return streamres(proto.groupchunks(util.chunkbuffer(chunks)))
777 779
778 780 @wireprotocommand('heads')
779 781 def heads(repo, proto):
780 782 h = repo.heads()
781 783 return encodelist(h) + "\n"
782 784
783 785 @wireprotocommand('hello')
784 786 def hello(repo, proto):
785 787 '''the hello command returns a set of lines describing various
786 788 interesting things about the server, in an RFC822-like format.
787 789 Currently the only one defined is "capabilities", which
788 790 consists of a line in the form:
789 791
790 792 capabilities: space separated list of tokens
791 793 '''
792 794 return "capabilities: %s\n" % (capabilities(repo, proto))
793 795
794 796 @wireprotocommand('listkeys', 'namespace')
795 797 def listkeys(repo, proto, namespace):
796 798 d = repo.listkeys(encoding.tolocal(namespace)).items()
797 799 return pushkeymod.encodekeys(d)
798 800
799 801 @wireprotocommand('lookup', 'key')
800 802 def lookup(repo, proto, key):
801 803 try:
802 804 k = encoding.tolocal(key)
803 805 c = repo[k]
804 806 r = c.hex()
805 807 success = 1
806 808 except Exception as inst:
807 809 r = str(inst)
808 810 success = 0
809 811 return "%s %s\n" % (success, r)
810 812
811 813 @wireprotocommand('known', 'nodes *')
812 814 def known(repo, proto, nodes, others):
813 815 return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
814 816
815 817 @wireprotocommand('pushkey', 'namespace key old new')
816 818 def pushkey(repo, proto, namespace, key, old, new):
817 819 # compatibility with pre-1.8 clients which were accidentally
818 820 # sending raw binary nodes rather than utf-8-encoded hex
819 821 if len(new) == 20 and new.encode('string-escape') != new:
820 822 # looks like it could be a binary node
821 823 try:
822 824 new.decode('utf-8')
823 825 new = encoding.tolocal(new) # but cleanly decodes as UTF-8
824 826 except UnicodeDecodeError:
825 827 pass # binary, leave unmodified
826 828 else:
827 829 new = encoding.tolocal(new) # normal path
828 830
829 831 if util.safehasattr(proto, 'restore'):
830 832
831 833 proto.redirect()
832 834
833 835 try:
834 836 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
835 837 encoding.tolocal(old), new) or False
836 838 except error.Abort:
837 839 r = False
838 840
839 841 output = proto.restore()
840 842
841 843 return '%s\n%s' % (int(r), output)
842 844
843 845 r = repo.pushkey(encoding.tolocal(namespace), encoding.tolocal(key),
844 846 encoding.tolocal(old), new)
845 847 return '%s\n' % int(r)
846 848
847 849 @wireprotocommand('stream_out')
848 850 def stream(repo, proto):
849 851 '''If the server supports streaming clone, it advertises the "stream"
850 852 capability with a value representing the version and flags of the repo
851 853 it is serving. Client checks to see if it understands the format.
852 854 '''
853 855 if not streamclone.allowservergeneration(repo.ui):
854 856 return '1\n'
855 857
856 858 def getstream(it):
857 859 yield '0\n'
858 860 for chunk in it:
859 861 yield chunk
860 862
861 863 try:
862 864 # LockError may be raised before the first result is yielded. Don't
863 865 # emit output until we're sure we got the lock successfully.
864 866 it = streamclone.generatev1wireproto(repo)
865 867 return streamres(getstream(it))
866 868 except error.LockError:
867 869 return '2\n'
868 870
869 871 @wireprotocommand('unbundle', 'heads')
870 872 def unbundle(repo, proto, heads):
871 873 their_heads = decodelist(heads)
872 874
873 875 try:
874 876 proto.redirect()
875 877
876 878 exchange.check_heads(repo, their_heads, 'preparing changes')
877 879
878 880 # write bundle data to temporary file because it can be big
879 881 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
880 882 fp = os.fdopen(fd, 'wb+')
881 883 r = 0
882 884 try:
883 885 proto.getfile(fp)
884 886 fp.seek(0)
885 887 gen = exchange.readbundle(repo.ui, fp, None)
886 888 if (isinstance(gen, changegroupmod.cg1unpacker)
887 889 and not bundle1allowed(repo, 'push')):
888 890 return ooberror(bundle2required)
889 891
890 892 r = exchange.unbundle(repo, gen, their_heads, 'serve',
891 893 proto._client())
892 894 if util.safehasattr(r, 'addpart'):
893 895 # The return looks streamable, we are in the bundle2 case and
894 896 # should return a stream.
895 897 return streamres(r.getchunks())
896 898 return pushres(r)
897 899
898 900 finally:
899 901 fp.close()
900 902 os.unlink(tempname)
901 903
902 904 except (error.BundleValueError, error.Abort, error.PushRaced) as exc:
903 905 # handle non-bundle2 case first
904 906 if not getattr(exc, 'duringunbundle2', False):
905 907 try:
906 908 raise
907 909 except error.Abort:
908 910 # The old code we moved used sys.stderr directly.
909 911 # We did not change it to minimise code change.
910 912 # This need to be moved to something proper.
911 913 # Feel free to do it.
912 914 sys.stderr.write("abort: %s\n" % exc)
913 915 return pushres(0)
914 916 except error.PushRaced:
915 917 return pusherr(str(exc))
916 918
917 919 bundler = bundle2.bundle20(repo.ui)
918 920 for out in getattr(exc, '_bundle2salvagedoutput', ()):
919 921 bundler.addpart(out)
920 922 try:
921 923 try:
922 924 raise
923 925 except error.PushkeyFailed as exc:
924 926 # check client caps
925 927 remotecaps = getattr(exc, '_replycaps', None)
926 928 if (remotecaps is not None
927 929 and 'pushkey' not in remotecaps.get('error', ())):
928 930 # no support remote side, fallback to Abort handler.
929 931 raise
930 932 part = bundler.newpart('error:pushkey')
931 933 part.addparam('in-reply-to', exc.partid)
932 934 if exc.namespace is not None:
933 935 part.addparam('namespace', exc.namespace, mandatory=False)
934 936 if exc.key is not None:
935 937 part.addparam('key', exc.key, mandatory=False)
936 938 if exc.new is not None:
937 939 part.addparam('new', exc.new, mandatory=False)
938 940 if exc.old is not None:
939 941 part.addparam('old', exc.old, mandatory=False)
940 942 if exc.ret is not None:
941 943 part.addparam('ret', exc.ret, mandatory=False)
942 944 except error.BundleValueError as exc:
943 945 errpart = bundler.newpart('error:unsupportedcontent')
944 946 if exc.parttype is not None:
945 947 errpart.addparam('parttype', exc.parttype)
946 948 if exc.params:
947 949 errpart.addparam('params', '\0'.join(exc.params))
948 950 except error.Abort as exc:
949 951 manargs = [('message', str(exc))]
950 952 advargs = []
951 953 if exc.hint is not None:
952 954 advargs.append(('hint', exc.hint))
953 955 bundler.addpart(bundle2.bundlepart('error:abort',
954 956 manargs, advargs))
955 957 except error.PushRaced as exc:
956 958 bundler.newpart('error:pushraced', [('message', str(exc))])
957 959 return streamres(bundler.getchunks())
@@ -1,272 +1,272
1 1 #require serve
2 2
3 3 = Test the getbundle() protocol function =
4 4
5 5 Create a test repository:
6 6
7 7 $ hg init repo
8 8 $ cd repo
9 9 $ hg debugbuilddag -n -m '+2 :fork +5 :p1 *fork +6 :p2 /p1 :m1 +3' > /dev/null
10 10 $ hg log -G --template '{node}\n'
11 11 o 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da
12 12 |
13 13 o 4801a72e5d88cb515b0c7e40fae34180f3f837f2
14 14 |
15 15 o 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
16 16 |
17 17 o 8365676dbab05860ce0d9110f2af51368b961bbd
18 18 |\
19 19 | o 5686dbbd9fc46cb806599c878d02fe1cb56b83d3
20 20 | |
21 21 | o 13c0170174366b441dc68e8e33757232fa744458
22 22 | |
23 23 | o 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
24 24 | |
25 25 | o 700b7e19db54103633c4bf4a6a6b6d55f4d50c03
26 26 | |
27 27 | o 928b5f94cdb278bb536eba552de348a4e92ef24d
28 28 | |
29 29 | o f34414c64173e0ecb61b25dc55e116dbbcc89bee
30 30 | |
31 31 | o 8931463777131cd73923e560b760061f2aa8a4bc
32 32 | |
33 33 o | 6621d79f61b23ec74cf4b69464343d9e0980ec8b
34 34 | |
35 35 o | bac16991d12ff45f9dc43c52da1946dfadb83e80
36 36 | |
37 37 o | ff42371d57168345fdf1a3aac66a51f6a45d41d2
38 38 | |
39 39 o | d5f6e1ea452285324836a49d7d3c2a63cfed1d31
40 40 | |
41 41 o | 713346a995c363120712aed1aee7e04afd867638
42 42 |/
43 43 o 29a4d1f17bd3f0779ca0525bebb1cfb51067c738
44 44 |
45 45 o 7704483d56b2a7b5db54dcee7c62378ac629b348
46 46
47 47 $ cd ..
48 48
49 49
50 50 = Test locally =
51 51
52 52 Get everything:
53 53
54 54 $ hg debuggetbundle repo bundle
55 55 $ hg debugbundle bundle
56 56 7704483d56b2a7b5db54dcee7c62378ac629b348
57 57 29a4d1f17bd3f0779ca0525bebb1cfb51067c738
58 58 713346a995c363120712aed1aee7e04afd867638
59 59 d5f6e1ea452285324836a49d7d3c2a63cfed1d31
60 60 ff42371d57168345fdf1a3aac66a51f6a45d41d2
61 61 bac16991d12ff45f9dc43c52da1946dfadb83e80
62 62 6621d79f61b23ec74cf4b69464343d9e0980ec8b
63 63 8931463777131cd73923e560b760061f2aa8a4bc
64 64 f34414c64173e0ecb61b25dc55e116dbbcc89bee
65 65 928b5f94cdb278bb536eba552de348a4e92ef24d
66 66 700b7e19db54103633c4bf4a6a6b6d55f4d50c03
67 67 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
68 68 13c0170174366b441dc68e8e33757232fa744458
69 69 5686dbbd9fc46cb806599c878d02fe1cb56b83d3
70 70 8365676dbab05860ce0d9110f2af51368b961bbd
71 71 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
72 72 4801a72e5d88cb515b0c7e40fae34180f3f837f2
73 73 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da
74 74
75 75 Get part of linear run:
76 76
77 77 $ hg debuggetbundle repo bundle -H 4801a72e5d88cb515b0c7e40fae34180f3f837f2 -C 8365676dbab05860ce0d9110f2af51368b961bbd
78 78 $ hg debugbundle bundle
79 79 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
80 80 4801a72e5d88cb515b0c7e40fae34180f3f837f2
81 81
82 82 Get missing branch and merge:
83 83
84 84 $ hg debuggetbundle repo bundle -H 4801a72e5d88cb515b0c7e40fae34180f3f837f2 -C 13c0170174366b441dc68e8e33757232fa744458
85 85 $ hg debugbundle bundle
86 86 713346a995c363120712aed1aee7e04afd867638
87 87 d5f6e1ea452285324836a49d7d3c2a63cfed1d31
88 88 ff42371d57168345fdf1a3aac66a51f6a45d41d2
89 89 bac16991d12ff45f9dc43c52da1946dfadb83e80
90 90 6621d79f61b23ec74cf4b69464343d9e0980ec8b
91 91 5686dbbd9fc46cb806599c878d02fe1cb56b83d3
92 92 8365676dbab05860ce0d9110f2af51368b961bbd
93 93 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
94 94 4801a72e5d88cb515b0c7e40fae34180f3f837f2
95 95
96 96 Get from only one head:
97 97
98 98 $ hg debuggetbundle repo bundle -H 928b5f94cdb278bb536eba552de348a4e92ef24d -C 29a4d1f17bd3f0779ca0525bebb1cfb51067c738
99 99 $ hg debugbundle bundle
100 100 8931463777131cd73923e560b760061f2aa8a4bc
101 101 f34414c64173e0ecb61b25dc55e116dbbcc89bee
102 102 928b5f94cdb278bb536eba552de348a4e92ef24d
103 103
104 104 Get parts of two branches:
105 105
106 106 $ hg debuggetbundle repo bundle -H 13c0170174366b441dc68e8e33757232fa744458 -C 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 -H bac16991d12ff45f9dc43c52da1946dfadb83e80 -C d5f6e1ea452285324836a49d7d3c2a63cfed1d31
107 107 $ hg debugbundle bundle
108 108 ff42371d57168345fdf1a3aac66a51f6a45d41d2
109 109 bac16991d12ff45f9dc43c52da1946dfadb83e80
110 110 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
111 111 13c0170174366b441dc68e8e33757232fa744458
112 112
113 113 Check that we get all needed file changes:
114 114
115 115 $ hg debugbundle bundle --all
116 116 format: id, p1, p2, cset, delta base, len(delta)
117 117
118 118 changelog
119 119 ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 99
120 120 bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 99
121 121 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 bac16991d12ff45f9dc43c52da1946dfadb83e80 102
122 122 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 102
123 123
124 124 manifest
125 125 dac7984588fc4eea7acbf39693a9c1b06f5b175d 591f732a3faf1fb903815273f3c199a514a61ccb 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 591f732a3faf1fb903815273f3c199a514a61ccb 113
126 126 0772616e6b48a76afb6c1458e193cbb3dae2e4ff dac7984588fc4eea7acbf39693a9c1b06f5b175d 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 dac7984588fc4eea7acbf39693a9c1b06f5b175d 113
127 127 eb498cd9af6c44108e43041e951ce829e29f6c80 bff2f4817ced57b386caf7c4e3e36a4bc9af7e93 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0772616e6b48a76afb6c1458e193cbb3dae2e4ff 295
128 128 b15709c071ddd2d93188508ba156196ab4f19620 eb498cd9af6c44108e43041e951ce829e29f6c80 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 eb498cd9af6c44108e43041e951ce829e29f6c80 114
129 129
130 130 mf
131 131 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 17
132 132 c7b583de053293870e145f45bd2d61643563fd06 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 18
133 133 266ee3c0302a5a18f1cf96817ac79a51836179e9 edc0f6b8db80d68ae6aff2b19f7e5347ab68fa63 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 c7b583de053293870e145f45bd2d61643563fd06 149
134 134 698c6a36220548cd3903ca7dada27c59aa500c52 266ee3c0302a5a18f1cf96817ac79a51836179e9 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 266ee3c0302a5a18f1cf96817ac79a51836179e9 19
135 135
136 136 nf11
137 137 33fbc651630ffa7ccbebfe4eb91320a873e7291c 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 16
138 138
139 139 nf12
140 140 ddce0544363f037e9fb889faca058f52dc01c0a5 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 0000000000000000000000000000000000000000 16
141 141
142 142 nf4
143 143 3c1407305701051cbed9f9cb9a68bdfb5997c235 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 15
144 144
145 145 nf5
146 146 0dbd89c185f53a1727c54cd1ce256482fa23968e 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 0000000000000000000000000000000000000000 15
147 147
148 148 Get branch and merge:
149 149
150 150 $ hg debuggetbundle repo bundle -C 7704483d56b2a7b5db54dcee7c62378ac629b348 -H 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
151 151 $ hg debugbundle bundle
152 152 29a4d1f17bd3f0779ca0525bebb1cfb51067c738
153 153 713346a995c363120712aed1aee7e04afd867638
154 154 d5f6e1ea452285324836a49d7d3c2a63cfed1d31
155 155 ff42371d57168345fdf1a3aac66a51f6a45d41d2
156 156 bac16991d12ff45f9dc43c52da1946dfadb83e80
157 157 6621d79f61b23ec74cf4b69464343d9e0980ec8b
158 158 8931463777131cd73923e560b760061f2aa8a4bc
159 159 f34414c64173e0ecb61b25dc55e116dbbcc89bee
160 160 928b5f94cdb278bb536eba552de348a4e92ef24d
161 161 700b7e19db54103633c4bf4a6a6b6d55f4d50c03
162 162 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
163 163 13c0170174366b441dc68e8e33757232fa744458
164 164 5686dbbd9fc46cb806599c878d02fe1cb56b83d3
165 165 8365676dbab05860ce0d9110f2af51368b961bbd
166 166 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
167 167
168 168 = Test bundle2 =
169 169
170 170 $ hg debuggetbundle repo bundle -t bundle2
171 171 $ hg debugbundle bundle
172 172 Stream params: {}
173 changegroup -- "sortdict([('version', '01'), ('nbchanges', '18')])"
173 changegroup -- "sortdict([('version', '01')])"
174 174 7704483d56b2a7b5db54dcee7c62378ac629b348
175 175 29a4d1f17bd3f0779ca0525bebb1cfb51067c738
176 176 713346a995c363120712aed1aee7e04afd867638
177 177 d5f6e1ea452285324836a49d7d3c2a63cfed1d31
178 178 ff42371d57168345fdf1a3aac66a51f6a45d41d2
179 179 bac16991d12ff45f9dc43c52da1946dfadb83e80
180 180 6621d79f61b23ec74cf4b69464343d9e0980ec8b
181 181 8931463777131cd73923e560b760061f2aa8a4bc
182 182 f34414c64173e0ecb61b25dc55e116dbbcc89bee
183 183 928b5f94cdb278bb536eba552de348a4e92ef24d
184 184 700b7e19db54103633c4bf4a6a6b6d55f4d50c03
185 185 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
186 186 13c0170174366b441dc68e8e33757232fa744458
187 187 5686dbbd9fc46cb806599c878d02fe1cb56b83d3
188 188 8365676dbab05860ce0d9110f2af51368b961bbd
189 189 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
190 190 4801a72e5d88cb515b0c7e40fae34180f3f837f2
191 191 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da
192 192 = Test via HTTP =
193 193
194 194 Get everything:
195 195
196 196 $ hg serve -R repo -p $HGPORT -d --pid-file=hg.pid -E error.log -A access.log
197 197 $ cat hg.pid >> $DAEMON_PIDS
198 198 $ hg debuggetbundle http://localhost:$HGPORT/ bundle
199 199 $ hg debugbundle bundle
200 200 7704483d56b2a7b5db54dcee7c62378ac629b348
201 201 29a4d1f17bd3f0779ca0525bebb1cfb51067c738
202 202 713346a995c363120712aed1aee7e04afd867638
203 203 d5f6e1ea452285324836a49d7d3c2a63cfed1d31
204 204 ff42371d57168345fdf1a3aac66a51f6a45d41d2
205 205 bac16991d12ff45f9dc43c52da1946dfadb83e80
206 206 6621d79f61b23ec74cf4b69464343d9e0980ec8b
207 207 8931463777131cd73923e560b760061f2aa8a4bc
208 208 f34414c64173e0ecb61b25dc55e116dbbcc89bee
209 209 928b5f94cdb278bb536eba552de348a4e92ef24d
210 210 700b7e19db54103633c4bf4a6a6b6d55f4d50c03
211 211 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
212 212 13c0170174366b441dc68e8e33757232fa744458
213 213 5686dbbd9fc46cb806599c878d02fe1cb56b83d3
214 214 8365676dbab05860ce0d9110f2af51368b961bbd
215 215 0b2f73f04880d9cb6a5cd8a757f0db0ad01e32c3
216 216 4801a72e5d88cb515b0c7e40fae34180f3f837f2
217 217 10c14a2cc935e1d8c31f9e98587dcf27fb08a6da
218 218
219 219 Get parts of two branches:
220 220
221 221 $ hg debuggetbundle http://localhost:$HGPORT/ bundle -H 13c0170174366b441dc68e8e33757232fa744458 -C 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 -H bac16991d12ff45f9dc43c52da1946dfadb83e80 -C d5f6e1ea452285324836a49d7d3c2a63cfed1d31
222 222 $ hg debugbundle bundle
223 223 ff42371d57168345fdf1a3aac66a51f6a45d41d2
224 224 bac16991d12ff45f9dc43c52da1946dfadb83e80
225 225 63476832d8ec6558cf9bbe3cbe0c757e5cf18043
226 226 13c0170174366b441dc68e8e33757232fa744458
227 227
228 228 Check that we get all needed file changes:
229 229
230 230 $ hg debugbundle bundle --all
231 231 format: id, p1, p2, cset, delta base, len(delta)
232 232
233 233 changelog
234 234 ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 d5f6e1ea452285324836a49d7d3c2a63cfed1d31 99
235 235 bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 ff42371d57168345fdf1a3aac66a51f6a45d41d2 99
236 236 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 700b7e19db54103633c4bf4a6a6b6d55f4d50c03 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 bac16991d12ff45f9dc43c52da1946dfadb83e80 102
237 237 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 102
238 238
239 239 manifest
240 240 dac7984588fc4eea7acbf39693a9c1b06f5b175d 591f732a3faf1fb903815273f3c199a514a61ccb 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 591f732a3faf1fb903815273f3c199a514a61ccb 113
241 241 0772616e6b48a76afb6c1458e193cbb3dae2e4ff dac7984588fc4eea7acbf39693a9c1b06f5b175d 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 dac7984588fc4eea7acbf39693a9c1b06f5b175d 113
242 242 eb498cd9af6c44108e43041e951ce829e29f6c80 bff2f4817ced57b386caf7c4e3e36a4bc9af7e93 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0772616e6b48a76afb6c1458e193cbb3dae2e4ff 295
243 243 b15709c071ddd2d93188508ba156196ab4f19620 eb498cd9af6c44108e43041e951ce829e29f6c80 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 eb498cd9af6c44108e43041e951ce829e29f6c80 114
244 244
245 245 mf
246 246 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 301ca08d026bb72cb4258a9d211bdf7ca0bcd810 17
247 247 c7b583de053293870e145f45bd2d61643563fd06 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 4f73f97080266ab8e0c0561ca8d0da3eaf65b695 18
248 248 266ee3c0302a5a18f1cf96817ac79a51836179e9 edc0f6b8db80d68ae6aff2b19f7e5347ab68fa63 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 c7b583de053293870e145f45bd2d61643563fd06 149
249 249 698c6a36220548cd3903ca7dada27c59aa500c52 266ee3c0302a5a18f1cf96817ac79a51836179e9 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 266ee3c0302a5a18f1cf96817ac79a51836179e9 19
250 250
251 251 nf11
252 252 33fbc651630ffa7ccbebfe4eb91320a873e7291c 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 63476832d8ec6558cf9bbe3cbe0c757e5cf18043 0000000000000000000000000000000000000000 16
253 253
254 254 nf12
255 255 ddce0544363f037e9fb889faca058f52dc01c0a5 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 13c0170174366b441dc68e8e33757232fa744458 0000000000000000000000000000000000000000 16
256 256
257 257 nf4
258 258 3c1407305701051cbed9f9cb9a68bdfb5997c235 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 ff42371d57168345fdf1a3aac66a51f6a45d41d2 0000000000000000000000000000000000000000 15
259 259
260 260 nf5
261 261 0dbd89c185f53a1727c54cd1ce256482fa23968e 0000000000000000000000000000000000000000 0000000000000000000000000000000000000000 bac16991d12ff45f9dc43c52da1946dfadb83e80 0000000000000000000000000000000000000000 15
262 262
263 263 Verify we hit the HTTP server:
264 264
265 265 $ cat access.log
266 266 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
267 267 * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - (glob)
268 268 * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
269 269 * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=700b7e19db54103633c4bf4a6a6b6d55f4d50c03+d5f6e1ea452285324836a49d7d3c2a63cfed1d31&heads=13c0170174366b441dc68e8e33757232fa744458+bac16991d12ff45f9dc43c52da1946dfadb83e80 (glob)
270 270
271 271 $ cat error.log
272 272
General Comments 0
You need to be logged in to leave comments. Login now