##// END OF EJS Templates
changegroup: use positive logic for treemanifest changegroup3 logic...
marmoute -
r43329:4bbc9569 default
parent child Browse files
Show More
@@ -1,1441 +1,1451 b''
1 1 # changegroup.py - Mercurial changegroup manipulation functions
2 2 #
3 3 # Copyright 2006 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 os
11 11 import struct
12 12 import weakref
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 hex,
17 17 nullid,
18 18 nullrev,
19 19 short,
20 20 )
21 21
22 22 from . import (
23 23 error,
24 24 match as matchmod,
25 25 mdiff,
26 26 phases,
27 27 pycompat,
28 28 util,
29 29 )
30 30
31 31 from .interfaces import (
32 32 repository,
33 33 )
34 34
35 35 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct("20s20s20s20s")
36 36 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct("20s20s20s20s20s")
37 37 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(">20s20s20s20s20sH")
38 38
39 39 LFS_REQUIREMENT = 'lfs'
40 40
41 41 readexactly = util.readexactly
42 42
43 43 def getchunk(stream):
44 44 """return the next chunk from stream as a string"""
45 45 d = readexactly(stream, 4)
46 46 l = struct.unpack(">l", d)[0]
47 47 if l <= 4:
48 48 if l:
49 49 raise error.Abort(_("invalid chunk length %d") % l)
50 50 return ""
51 51 return readexactly(stream, l - 4)
52 52
53 53 def chunkheader(length):
54 54 """return a changegroup chunk header (string)"""
55 55 return struct.pack(">l", length + 4)
56 56
57 57 def closechunk():
58 58 """return a changegroup chunk header (string) for a zero-length chunk"""
59 59 return struct.pack(">l", 0)
60 60
61 61 def _fileheader(path):
62 62 """Obtain a changegroup chunk header for a named path."""
63 63 return chunkheader(len(path)) + path
64 64
65 65 def writechunks(ui, chunks, filename, vfs=None):
66 66 """Write chunks to a file and return its filename.
67 67
68 68 The stream is assumed to be a bundle file.
69 69 Existing files will not be overwritten.
70 70 If no filename is specified, a temporary file is created.
71 71 """
72 72 fh = None
73 73 cleanup = None
74 74 try:
75 75 if filename:
76 76 if vfs:
77 77 fh = vfs.open(filename, "wb")
78 78 else:
79 79 # Increase default buffer size because default is usually
80 80 # small (4k is common on Linux).
81 81 fh = open(filename, "wb", 131072)
82 82 else:
83 83 fd, filename = pycompat.mkstemp(prefix="hg-bundle-", suffix=".hg")
84 84 fh = os.fdopen(fd, r"wb")
85 85 cleanup = filename
86 86 for c in chunks:
87 87 fh.write(c)
88 88 cleanup = None
89 89 return filename
90 90 finally:
91 91 if fh is not None:
92 92 fh.close()
93 93 if cleanup is not None:
94 94 if filename and vfs:
95 95 vfs.unlink(cleanup)
96 96 else:
97 97 os.unlink(cleanup)
98 98
99 99 class cg1unpacker(object):
100 100 """Unpacker for cg1 changegroup streams.
101 101
102 102 A changegroup unpacker handles the framing of the revision data in
103 103 the wire format. Most consumers will want to use the apply()
104 104 method to add the changes from the changegroup to a repository.
105 105
106 106 If you're forwarding a changegroup unmodified to another consumer,
107 107 use getchunks(), which returns an iterator of changegroup
108 108 chunks. This is mostly useful for cases where you need to know the
109 109 data stream has ended by observing the end of the changegroup.
110 110
111 111 deltachunk() is useful only if you're applying delta data. Most
112 112 consumers should prefer apply() instead.
113 113
114 114 A few other public methods exist. Those are used only for
115 115 bundlerepo and some debug commands - their use is discouraged.
116 116 """
117 117 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
118 118 deltaheadersize = deltaheader.size
119 119 version = '01'
120 120 _grouplistcount = 1 # One list of files after the manifests
121 121
122 122 def __init__(self, fh, alg, extras=None):
123 123 if alg is None:
124 124 alg = 'UN'
125 125 if alg not in util.compengines.supportedbundletypes:
126 126 raise error.Abort(_('unknown stream compression type: %s')
127 127 % alg)
128 128 if alg == 'BZ':
129 129 alg = '_truncatedBZ'
130 130
131 131 compengine = util.compengines.forbundletype(alg)
132 132 self._stream = compengine.decompressorreader(fh)
133 133 self._type = alg
134 134 self.extras = extras or {}
135 135 self.callback = None
136 136
137 137 # These methods (compressed, read, seek, tell) all appear to only
138 138 # be used by bundlerepo, but it's a little hard to tell.
139 139 def compressed(self):
140 140 return self._type is not None and self._type != 'UN'
141 141 def read(self, l):
142 142 return self._stream.read(l)
143 143 def seek(self, pos):
144 144 return self._stream.seek(pos)
145 145 def tell(self):
146 146 return self._stream.tell()
147 147 def close(self):
148 148 return self._stream.close()
149 149
150 150 def _chunklength(self):
151 151 d = readexactly(self._stream, 4)
152 152 l = struct.unpack(">l", d)[0]
153 153 if l <= 4:
154 154 if l:
155 155 raise error.Abort(_("invalid chunk length %d") % l)
156 156 return 0
157 157 if self.callback:
158 158 self.callback()
159 159 return l - 4
160 160
161 161 def changelogheader(self):
162 162 """v10 does not have a changelog header chunk"""
163 163 return {}
164 164
165 165 def manifestheader(self):
166 166 """v10 does not have a manifest header chunk"""
167 167 return {}
168 168
169 169 def filelogheader(self):
170 170 """return the header of the filelogs chunk, v10 only has the filename"""
171 171 l = self._chunklength()
172 172 if not l:
173 173 return {}
174 174 fname = readexactly(self._stream, l)
175 175 return {'filename': fname}
176 176
177 177 def _deltaheader(self, headertuple, prevnode):
178 178 node, p1, p2, cs = headertuple
179 179 if prevnode is None:
180 180 deltabase = p1
181 181 else:
182 182 deltabase = prevnode
183 183 flags = 0
184 184 return node, p1, p2, deltabase, cs, flags
185 185
186 186 def deltachunk(self, prevnode):
187 187 l = self._chunklength()
188 188 if not l:
189 189 return {}
190 190 headerdata = readexactly(self._stream, self.deltaheadersize)
191 191 header = self.deltaheader.unpack(headerdata)
192 192 delta = readexactly(self._stream, l - self.deltaheadersize)
193 193 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
194 194 return (node, p1, p2, cs, deltabase, delta, flags)
195 195
196 196 def getchunks(self):
197 197 """returns all the chunks contains in the bundle
198 198
199 199 Used when you need to forward the binary stream to a file or another
200 200 network API. To do so, it parse the changegroup data, otherwise it will
201 201 block in case of sshrepo because it don't know the end of the stream.
202 202 """
203 203 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
204 204 # and a list of filelogs. For changegroup 3, we expect 4 parts:
205 205 # changelog, manifestlog, a list of tree manifestlogs, and a list of
206 206 # filelogs.
207 207 #
208 208 # Changelog and manifestlog parts are terminated with empty chunks. The
209 209 # tree and file parts are a list of entry sections. Each entry section
210 210 # is a series of chunks terminating in an empty chunk. The list of these
211 211 # entry sections is terminated in yet another empty chunk, so we know
212 212 # we've reached the end of the tree/file list when we reach an empty
213 213 # chunk that was proceeded by no non-empty chunks.
214 214
215 215 parts = 0
216 216 while parts < 2 + self._grouplistcount:
217 217 noentries = True
218 218 while True:
219 219 chunk = getchunk(self)
220 220 if not chunk:
221 221 # The first two empty chunks represent the end of the
222 222 # changelog and the manifestlog portions. The remaining
223 223 # empty chunks represent either A) the end of individual
224 224 # tree or file entries in the file list, or B) the end of
225 225 # the entire list. It's the end of the entire list if there
226 226 # were no entries (i.e. noentries is True).
227 227 if parts < 2:
228 228 parts += 1
229 229 elif noentries:
230 230 parts += 1
231 231 break
232 232 noentries = False
233 233 yield chunkheader(len(chunk))
234 234 pos = 0
235 235 while pos < len(chunk):
236 236 next = pos + 2**20
237 237 yield chunk[pos:next]
238 238 pos = next
239 239 yield closechunk()
240 240
241 241 def _unpackmanifests(self, repo, revmap, trp, prog):
242 242 self.callback = prog.increment
243 243 # no need to check for empty manifest group here:
244 244 # if the result of the merge of 1 and 2 is the same in 3 and 4,
245 245 # no new manifest will be created and the manifest group will
246 246 # be empty during the pull
247 247 self.manifestheader()
248 248 deltas = self.deltaiter()
249 249 repo.manifestlog.getstorage(b'').addgroup(deltas, revmap, trp)
250 250 prog.complete()
251 251 self.callback = None
252 252
253 253 def apply(self, repo, tr, srctype, url, targetphase=phases.draft,
254 254 expectedtotal=None):
255 255 """Add the changegroup returned by source.read() to this repo.
256 256 srctype is a string like 'push', 'pull', or 'unbundle'. url is
257 257 the URL of the repo where this changegroup is coming from.
258 258
259 259 Return an integer summarizing the change to this repo:
260 260 - nothing changed or no source: 0
261 261 - more heads than before: 1+added heads (2..n)
262 262 - fewer heads than before: -1-removed heads (-2..-n)
263 263 - number of heads stays the same: 1
264 264 """
265 265 repo = repo.unfiltered()
266 266 def csmap(x):
267 267 repo.ui.debug("add changeset %s\n" % short(x))
268 268 return len(cl)
269 269
270 270 def revmap(x):
271 271 return cl.rev(x)
272 272
273 273 changesets = 0
274 274
275 275 try:
276 276 # The transaction may already carry source information. In this
277 277 # case we use the top level data. We overwrite the argument
278 278 # because we need to use the top level value (if they exist)
279 279 # in this function.
280 280 srctype = tr.hookargs.setdefault('source', srctype)
281 281 tr.hookargs.setdefault('url', url)
282 282 repo.hook('prechangegroup',
283 283 throw=True, **pycompat.strkwargs(tr.hookargs))
284 284
285 285 # write changelog data to temp files so concurrent readers
286 286 # will not see an inconsistent view
287 287 cl = repo.changelog
288 288 cl.delayupdate(tr)
289 289 oldheads = set(cl.heads())
290 290
291 291 trp = weakref.proxy(tr)
292 292 # pull off the changeset group
293 293 repo.ui.status(_("adding changesets\n"))
294 294 clstart = len(cl)
295 295 progress = repo.ui.makeprogress(_('changesets'), unit=_('chunks'),
296 296 total=expectedtotal)
297 297 self.callback = progress.increment
298 298
299 299 efiles = set()
300 300 def onchangelog(cl, node):
301 301 efiles.update(cl.readfiles(node))
302 302
303 303 self.changelogheader()
304 304 deltas = self.deltaiter()
305 305 cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
306 306 efiles = len(efiles)
307 307
308 308 if not cgnodes:
309 309 repo.ui.develwarn('applied empty changelog from changegroup',
310 310 config='warn-empty-changegroup')
311 311 clend = len(cl)
312 312 changesets = clend - clstart
313 313 progress.complete()
314 314 self.callback = None
315 315
316 316 # pull off the manifest group
317 317 repo.ui.status(_("adding manifests\n"))
318 318 # We know that we'll never have more manifests than we had
319 319 # changesets.
320 320 progress = repo.ui.makeprogress(_('manifests'), unit=_('chunks'),
321 321 total=changesets)
322 322 self._unpackmanifests(repo, revmap, trp, progress)
323 323
324 324 needfiles = {}
325 325 if repo.ui.configbool('server', 'validate'):
326 326 cl = repo.changelog
327 327 ml = repo.manifestlog
328 328 # validate incoming csets have their manifests
329 329 for cset in pycompat.xrange(clstart, clend):
330 330 mfnode = cl.changelogrevision(cset).manifest
331 331 mfest = ml[mfnode].readdelta()
332 332 # store file cgnodes we must see
333 333 for f, n in mfest.iteritems():
334 334 needfiles.setdefault(f, set()).add(n)
335 335
336 336 # process the files
337 337 repo.ui.status(_("adding file changes\n"))
338 338 newrevs, newfiles = _addchangegroupfiles(
339 339 repo, self, revmap, trp, efiles, needfiles)
340 340
341 341 # making sure the value exists
342 342 tr.changes.setdefault('changegroup-count-changesets', 0)
343 343 tr.changes.setdefault('changegroup-count-revisions', 0)
344 344 tr.changes.setdefault('changegroup-count-files', 0)
345 345 tr.changes.setdefault('changegroup-count-heads', 0)
346 346
347 347 # some code use bundle operation for internal purpose. They usually
348 348 # set `ui.quiet` to do this outside of user sight. Size the report
349 349 # of such operation now happens at the end of the transaction, that
350 350 # ui.quiet has not direct effect on the output.
351 351 #
352 352 # To preserve this intend use an inelegant hack, we fail to report
353 353 # the change if `quiet` is set. We should probably move to
354 354 # something better, but this is a good first step to allow the "end
355 355 # of transaction report" to pass tests.
356 356 if not repo.ui.quiet:
357 357 tr.changes['changegroup-count-changesets'] += changesets
358 358 tr.changes['changegroup-count-revisions'] += newrevs
359 359 tr.changes['changegroup-count-files'] += newfiles
360 360
361 361 deltaheads = 0
362 362 if oldheads:
363 363 heads = cl.heads()
364 364 deltaheads += len(heads) - len(oldheads)
365 365 for h in heads:
366 366 if h not in oldheads and repo[h].closesbranch():
367 367 deltaheads -= 1
368 368
369 369 # see previous comment about checking ui.quiet
370 370 if not repo.ui.quiet:
371 371 tr.changes['changegroup-count-heads'] += deltaheads
372 372 repo.invalidatevolatilesets()
373 373
374 374 if changesets > 0:
375 375 if 'node' not in tr.hookargs:
376 376 tr.hookargs['node'] = hex(cl.node(clstart))
377 377 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
378 378 hookargs = dict(tr.hookargs)
379 379 else:
380 380 hookargs = dict(tr.hookargs)
381 381 hookargs['node'] = hex(cl.node(clstart))
382 382 hookargs['node_last'] = hex(cl.node(clend - 1))
383 383 repo.hook('pretxnchangegroup',
384 384 throw=True, **pycompat.strkwargs(hookargs))
385 385
386 386 added = [cl.node(r) for r in pycompat.xrange(clstart, clend)]
387 387 phaseall = None
388 388 if srctype in ('push', 'serve'):
389 389 # Old servers can not push the boundary themselves.
390 390 # New servers won't push the boundary if changeset already
391 391 # exists locally as secret
392 392 #
393 393 # We should not use added here but the list of all change in
394 394 # the bundle
395 395 if repo.publishing():
396 396 targetphase = phaseall = phases.public
397 397 else:
398 398 # closer target phase computation
399 399
400 400 # Those changesets have been pushed from the
401 401 # outside, their phases are going to be pushed
402 402 # alongside. Therefor `targetphase` is
403 403 # ignored.
404 404 targetphase = phaseall = phases.draft
405 405 if added:
406 406 phases.registernew(repo, tr, targetphase, added)
407 407 if phaseall is not None:
408 408 phases.advanceboundary(repo, tr, phaseall, cgnodes)
409 409
410 410 if changesets > 0:
411 411
412 412 def runhooks():
413 413 # These hooks run when the lock releases, not when the
414 414 # transaction closes. So it's possible for the changelog
415 415 # to have changed since we last saw it.
416 416 if clstart >= len(repo):
417 417 return
418 418
419 419 repo.hook("changegroup", **pycompat.strkwargs(hookargs))
420 420
421 421 for n in added:
422 422 args = hookargs.copy()
423 423 args['node'] = hex(n)
424 424 del args['node_last']
425 425 repo.hook("incoming", **pycompat.strkwargs(args))
426 426
427 427 newheads = [h for h in repo.heads()
428 428 if h not in oldheads]
429 429 repo.ui.log("incoming",
430 430 "%d incoming changes - new heads: %s\n",
431 431 len(added),
432 432 ', '.join([hex(c[:6]) for c in newheads]))
433 433
434 434 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
435 435 lambda tr: repo._afterlock(runhooks))
436 436 finally:
437 437 repo.ui.flush()
438 438 # never return 0 here:
439 439 if deltaheads < 0:
440 440 ret = deltaheads - 1
441 441 else:
442 442 ret = deltaheads + 1
443 443 return ret
444 444
445 445 def deltaiter(self):
446 446 """
447 447 returns an iterator of the deltas in this changegroup
448 448
449 449 Useful for passing to the underlying storage system to be stored.
450 450 """
451 451 chain = None
452 452 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
453 453 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
454 454 yield chunkdata
455 455 chain = chunkdata[0]
456 456
457 457 class cg2unpacker(cg1unpacker):
458 458 """Unpacker for cg2 streams.
459 459
460 460 cg2 streams add support for generaldelta, so the delta header
461 461 format is slightly different. All other features about the data
462 462 remain the same.
463 463 """
464 464 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
465 465 deltaheadersize = deltaheader.size
466 466 version = '02'
467 467
468 468 def _deltaheader(self, headertuple, prevnode):
469 469 node, p1, p2, deltabase, cs = headertuple
470 470 flags = 0
471 471 return node, p1, p2, deltabase, cs, flags
472 472
473 473 class cg3unpacker(cg2unpacker):
474 474 """Unpacker for cg3 streams.
475 475
476 476 cg3 streams add support for exchanging treemanifests and revlog
477 477 flags. It adds the revlog flags to the delta header and an empty chunk
478 478 separating manifests and files.
479 479 """
480 480 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
481 481 deltaheadersize = deltaheader.size
482 482 version = '03'
483 483 _grouplistcount = 2 # One list of manifests and one list of files
484 484
485 485 def _deltaheader(self, headertuple, prevnode):
486 486 node, p1, p2, deltabase, cs, flags = headertuple
487 487 return node, p1, p2, deltabase, cs, flags
488 488
489 489 def _unpackmanifests(self, repo, revmap, trp, prog):
490 490 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog)
491 491 for chunkdata in iter(self.filelogheader, {}):
492 492 # If we get here, there are directory manifests in the changegroup
493 493 d = chunkdata["filename"]
494 494 repo.ui.debug("adding %s revisions\n" % d)
495 495 deltas = self.deltaiter()
496 496 if not repo.manifestlog.getstorage(d).addgroup(deltas, revmap, trp):
497 497 raise error.Abort(_("received dir revlog group is empty"))
498 498
499 499 class headerlessfixup(object):
500 500 def __init__(self, fh, h):
501 501 self._h = h
502 502 self._fh = fh
503 503 def read(self, n):
504 504 if self._h:
505 505 d, self._h = self._h[:n], self._h[n:]
506 506 if len(d) < n:
507 507 d += readexactly(self._fh, n - len(d))
508 508 return d
509 509 return readexactly(self._fh, n)
510 510
511 511 def _revisiondeltatochunks(delta, headerfn):
512 512 """Serialize a revisiondelta to changegroup chunks."""
513 513
514 514 # The captured revision delta may be encoded as a delta against
515 515 # a base revision or as a full revision. The changegroup format
516 516 # requires that everything on the wire be deltas. So for full
517 517 # revisions, we need to invent a header that says to rewrite
518 518 # data.
519 519
520 520 if delta.delta is not None:
521 521 prefix, data = b'', delta.delta
522 522 elif delta.basenode == nullid:
523 523 data = delta.revision
524 524 prefix = mdiff.trivialdiffheader(len(data))
525 525 else:
526 526 data = delta.revision
527 527 prefix = mdiff.replacediffheader(delta.baserevisionsize,
528 528 len(data))
529 529
530 530 meta = headerfn(delta)
531 531
532 532 yield chunkheader(len(meta) + len(prefix) + len(data))
533 533 yield meta
534 534 if prefix:
535 535 yield prefix
536 536 yield data
537 537
538 538 def _sortnodesellipsis(store, nodes, cl, lookup):
539 539 """Sort nodes for changegroup generation."""
540 540 # Ellipses serving mode.
541 541 #
542 542 # In a perfect world, we'd generate better ellipsis-ified graphs
543 543 # for non-changelog revlogs. In practice, we haven't started doing
544 544 # that yet, so the resulting DAGs for the manifestlog and filelogs
545 545 # are actually full of bogus parentage on all the ellipsis
546 546 # nodes. This has the side effect that, while the contents are
547 547 # correct, the individual DAGs might be completely out of whack in
548 548 # a case like 882681bc3166 and its ancestors (back about 10
549 549 # revisions or so) in the main hg repo.
550 550 #
551 551 # The one invariant we *know* holds is that the new (potentially
552 552 # bogus) DAG shape will be valid if we order the nodes in the
553 553 # order that they're introduced in dramatis personae by the
554 554 # changelog, so what we do is we sort the non-changelog histories
555 555 # by the order in which they are used by the changelog.
556 556 key = lambda n: cl.rev(lookup(n))
557 557 return sorted(nodes, key=key)
558 558
559 559 def _resolvenarrowrevisioninfo(cl, store, ischangelog, rev, linkrev,
560 560 linknode, clrevtolocalrev, fullclnodes,
561 561 precomputedellipsis):
562 562 linkparents = precomputedellipsis[linkrev]
563 563 def local(clrev):
564 564 """Turn a changelog revnum into a local revnum.
565 565
566 566 The ellipsis dag is stored as revnums on the changelog,
567 567 but when we're producing ellipsis entries for
568 568 non-changelog revlogs, we need to turn those numbers into
569 569 something local. This does that for us, and during the
570 570 changelog sending phase will also expand the stored
571 571 mappings as needed.
572 572 """
573 573 if clrev == nullrev:
574 574 return nullrev
575 575
576 576 if ischangelog:
577 577 return clrev
578 578
579 579 # Walk the ellipsis-ized changelog breadth-first looking for a
580 580 # change that has been linked from the current revlog.
581 581 #
582 582 # For a flat manifest revlog only a single step should be necessary
583 583 # as all relevant changelog entries are relevant to the flat
584 584 # manifest.
585 585 #
586 586 # For a filelog or tree manifest dirlog however not every changelog
587 587 # entry will have been relevant, so we need to skip some changelog
588 588 # nodes even after ellipsis-izing.
589 589 walk = [clrev]
590 590 while walk:
591 591 p = walk[0]
592 592 walk = walk[1:]
593 593 if p in clrevtolocalrev:
594 594 return clrevtolocalrev[p]
595 595 elif p in fullclnodes:
596 596 walk.extend([pp for pp in cl.parentrevs(p)
597 597 if pp != nullrev])
598 598 elif p in precomputedellipsis:
599 599 walk.extend([pp for pp in precomputedellipsis[p]
600 600 if pp != nullrev])
601 601 else:
602 602 # In this case, we've got an ellipsis with parents
603 603 # outside the current bundle (likely an
604 604 # incremental pull). We "know" that we can use the
605 605 # value of this same revlog at whatever revision
606 606 # is pointed to by linknode. "Know" is in scare
607 607 # quotes because I haven't done enough examination
608 608 # of edge cases to convince myself this is really
609 609 # a fact - it works for all the (admittedly
610 610 # thorough) cases in our testsuite, but I would be
611 611 # somewhat unsurprised to find a case in the wild
612 612 # where this breaks down a bit. That said, I don't
613 613 # know if it would hurt anything.
614 614 for i in pycompat.xrange(rev, 0, -1):
615 615 if store.linkrev(i) == clrev:
616 616 return i
617 617 # We failed to resolve a parent for this node, so
618 618 # we crash the changegroup construction.
619 619 raise error.Abort(
620 620 'unable to resolve parent while packing %r %r'
621 621 ' for changeset %r' % (store.indexfile, rev, clrev))
622 622
623 623 return nullrev
624 624
625 625 if not linkparents or (
626 626 store.parentrevs(rev) == (nullrev, nullrev)):
627 627 p1, p2 = nullrev, nullrev
628 628 elif len(linkparents) == 1:
629 629 p1, = sorted(local(p) for p in linkparents)
630 630 p2 = nullrev
631 631 else:
632 632 p1, p2 = sorted(local(p) for p in linkparents)
633 633
634 634 p1node, p2node = store.node(p1), store.node(p2)
635 635
636 636 return p1node, p2node, linknode
637 637
638 638 def deltagroup(repo, store, nodes, ischangelog, lookup, forcedeltaparentprev,
639 639 topic=None,
640 640 ellipses=False, clrevtolocalrev=None, fullclnodes=None,
641 641 precomputedellipsis=None):
642 642 """Calculate deltas for a set of revisions.
643 643
644 644 Is a generator of ``revisiondelta`` instances.
645 645
646 646 If topic is not None, progress detail will be generated using this
647 647 topic name (e.g. changesets, manifests, etc).
648 648 """
649 649 if not nodes:
650 650 return
651 651
652 652 cl = repo.changelog
653 653
654 654 if ischangelog:
655 655 # `hg log` shows changesets in storage order. To preserve order
656 656 # across clones, send out changesets in storage order.
657 657 nodesorder = 'storage'
658 658 elif ellipses:
659 659 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
660 660 nodesorder = 'nodes'
661 661 else:
662 662 nodesorder = None
663 663
664 664 # Perform ellipses filtering and revision massaging. We do this before
665 665 # emitrevisions() because a) filtering out revisions creates less work
666 666 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
667 667 # assumptions about delta choices and we would possibly send a delta
668 668 # referencing a missing base revision.
669 669 #
670 670 # Also, calling lookup() has side-effects with regards to populating
671 671 # data structures. If we don't call lookup() for each node or if we call
672 672 # lookup() after the first pass through each node, things can break -
673 673 # possibly intermittently depending on the python hash seed! For that
674 674 # reason, we store a mapping of all linknodes during the initial node
675 675 # pass rather than use lookup() on the output side.
676 676 if ellipses:
677 677 filtered = []
678 678 adjustedparents = {}
679 679 linknodes = {}
680 680
681 681 for node in nodes:
682 682 rev = store.rev(node)
683 683 linknode = lookup(node)
684 684 linkrev = cl.rev(linknode)
685 685 clrevtolocalrev[linkrev] = rev
686 686
687 687 # If linknode is in fullclnodes, it means the corresponding
688 688 # changeset was a full changeset and is being sent unaltered.
689 689 if linknode in fullclnodes:
690 690 linknodes[node] = linknode
691 691
692 692 # If the corresponding changeset wasn't in the set computed
693 693 # as relevant to us, it should be dropped outright.
694 694 elif linkrev not in precomputedellipsis:
695 695 continue
696 696
697 697 else:
698 698 # We could probably do this later and avoid the dict
699 699 # holding state. But it likely doesn't matter.
700 700 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
701 701 cl, store, ischangelog, rev, linkrev, linknode,
702 702 clrevtolocalrev, fullclnodes, precomputedellipsis)
703 703
704 704 adjustedparents[node] = (p1node, p2node)
705 705 linknodes[node] = linknode
706 706
707 707 filtered.append(node)
708 708
709 709 nodes = filtered
710 710
711 711 # We expect the first pass to be fast, so we only engage the progress
712 712 # meter for constructing the revision deltas.
713 713 progress = None
714 714 if topic is not None:
715 715 progress = repo.ui.makeprogress(topic, unit=_('chunks'),
716 716 total=len(nodes))
717 717
718 718 configtarget = repo.ui.config('devel', 'bundle.delta')
719 719 if configtarget not in ('', 'p1', 'full'):
720 720 msg = _("""config "devel.bundle.delta" as unknown value: %s""")
721 721 repo.ui.warn(msg % configtarget)
722 722
723 723 deltamode = repository.CG_DELTAMODE_STD
724 724 if forcedeltaparentprev:
725 725 deltamode = repository.CG_DELTAMODE_PREV
726 726 elif configtarget == 'p1':
727 727 deltamode = repository.CG_DELTAMODE_P1
728 728 elif configtarget == 'full':
729 729 deltamode = repository.CG_DELTAMODE_FULL
730 730
731 731 revisions = store.emitrevisions(
732 732 nodes,
733 733 nodesorder=nodesorder,
734 734 revisiondata=True,
735 735 assumehaveparentrevisions=not ellipses,
736 736 deltamode=deltamode)
737 737
738 738 for i, revision in enumerate(revisions):
739 739 if progress:
740 740 progress.update(i + 1)
741 741
742 742 if ellipses:
743 743 linknode = linknodes[revision.node]
744 744
745 745 if revision.node in adjustedparents:
746 746 p1node, p2node = adjustedparents[revision.node]
747 747 revision.p1node = p1node
748 748 revision.p2node = p2node
749 749 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
750 750
751 751 else:
752 752 linknode = lookup(revision.node)
753 753
754 754 revision.linknode = linknode
755 755 yield revision
756 756
757 757 if progress:
758 758 progress.complete()
759 759
760 760 class cgpacker(object):
761 761 def __init__(self, repo, oldmatcher, matcher, version,
762 762 builddeltaheader, manifestsend,
763 763 forcedeltaparentprev=False,
764 764 bundlecaps=None, ellipses=False,
765 765 shallow=False, ellipsisroots=None, fullnodes=None):
766 766 """Given a source repo, construct a bundler.
767 767
768 768 oldmatcher is a matcher that matches on files the client already has.
769 769 These will not be included in the changegroup.
770 770
771 771 matcher is a matcher that matches on files to include in the
772 772 changegroup. Used to facilitate sparse changegroups.
773 773
774 774 forcedeltaparentprev indicates whether delta parents must be against
775 775 the previous revision in a delta group. This should only be used for
776 776 compatibility with changegroup version 1.
777 777
778 778 builddeltaheader is a callable that constructs the header for a group
779 779 delta.
780 780
781 781 manifestsend is a chunk to send after manifests have been fully emitted.
782 782
783 783 ellipses indicates whether ellipsis serving mode is enabled.
784 784
785 785 bundlecaps is optional and can be used to specify the set of
786 786 capabilities which can be used to build the bundle. While bundlecaps is
787 787 unused in core Mercurial, extensions rely on this feature to communicate
788 788 capabilities to customize the changegroup packer.
789 789
790 790 shallow indicates whether shallow data might be sent. The packer may
791 791 need to pack file contents not introduced by the changes being packed.
792 792
793 793 fullnodes is the set of changelog nodes which should not be ellipsis
794 794 nodes. We store this rather than the set of nodes that should be
795 795 ellipsis because for very large histories we expect this to be
796 796 significantly smaller.
797 797 """
798 798 assert oldmatcher
799 799 assert matcher
800 800 self._oldmatcher = oldmatcher
801 801 self._matcher = matcher
802 802
803 803 self.version = version
804 804 self._forcedeltaparentprev = forcedeltaparentprev
805 805 self._builddeltaheader = builddeltaheader
806 806 self._manifestsend = manifestsend
807 807 self._ellipses = ellipses
808 808
809 809 # Set of capabilities we can use to build the bundle.
810 810 if bundlecaps is None:
811 811 bundlecaps = set()
812 812 self._bundlecaps = bundlecaps
813 813 self._isshallow = shallow
814 814 self._fullclnodes = fullnodes
815 815
816 816 # Maps ellipsis revs to their roots at the changelog level.
817 817 self._precomputedellipsis = ellipsisroots
818 818
819 819 self._repo = repo
820 820
821 821 if self._repo.ui.verbose and not self._repo.ui.debugflag:
822 822 self._verbosenote = self._repo.ui.note
823 823 else:
824 824 self._verbosenote = lambda s: None
825 825
826 826 def generate(self, commonrevs, clnodes, fastpathlinkrev, source,
827 827 changelog=True):
828 828 """Yield a sequence of changegroup byte chunks.
829 829 If changelog is False, changelog data won't be added to changegroup
830 830 """
831 831
832 832 repo = self._repo
833 833 cl = repo.changelog
834 834
835 835 self._verbosenote(_('uncompressed size of bundle content:\n'))
836 836 size = 0
837 837
838 838 clstate, deltas = self._generatechangelog(cl, clnodes,
839 839 generate=changelog)
840 840 for delta in deltas:
841 841 for chunk in _revisiondeltatochunks(delta,
842 842 self._builddeltaheader):
843 843 size += len(chunk)
844 844 yield chunk
845 845
846 846 close = closechunk()
847 847 size += len(close)
848 848 yield closechunk()
849 849
850 850 self._verbosenote(_('%8.i (changelog)\n') % size)
851 851
852 852 clrevorder = clstate['clrevorder']
853 853 manifests = clstate['manifests']
854 854 changedfiles = clstate['changedfiles']
855 855
856 856 # We need to make sure that the linkrev in the changegroup refers to
857 857 # the first changeset that introduced the manifest or file revision.
858 858 # The fastpath is usually safer than the slowpath, because the filelogs
859 859 # are walked in revlog order.
860 860 #
861 861 # When taking the slowpath when the manifest revlog uses generaldelta,
862 862 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
863 863 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
864 864 #
865 865 # When taking the fastpath, we are only vulnerable to reordering
866 866 # of the changelog itself. The changelog never uses generaldelta and is
867 867 # never reordered. To handle this case, we simply take the slowpath,
868 868 # which already has the 'clrevorder' logic. This was also fixed in
869 869 # cc0ff93d0c0c.
870 870
871 871 # Treemanifests don't work correctly with fastpathlinkrev
872 872 # either, because we don't discover which directory nodes to
873 873 # send along with files. This could probably be fixed.
874 874 fastpathlinkrev = fastpathlinkrev and (
875 875 'treemanifest' not in repo.requirements)
876 876
877 877 fnodes = {} # needed file nodes
878 878
879 879 size = 0
880 880 it = self.generatemanifests(
881 881 commonrevs, clrevorder, fastpathlinkrev, manifests, fnodes, source,
882 882 clstate['clrevtomanifestrev'])
883 883
884 884 for tree, deltas in it:
885 885 if tree:
886 886 assert self.version == b'03'
887 887 chunk = _fileheader(tree)
888 888 size += len(chunk)
889 889 yield chunk
890 890
891 891 for delta in deltas:
892 892 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
893 893 for chunk in chunks:
894 894 size += len(chunk)
895 895 yield chunk
896 896
897 897 close = closechunk()
898 898 size += len(close)
899 899 yield close
900 900
901 901 self._verbosenote(_('%8.i (manifests)\n') % size)
902 902 yield self._manifestsend
903 903
904 904 mfdicts = None
905 905 if self._ellipses and self._isshallow:
906 906 mfdicts = [(self._repo.manifestlog[n].read(), lr)
907 907 for (n, lr) in manifests.iteritems()]
908 908
909 909 manifests.clear()
910 910 clrevs = set(cl.rev(x) for x in clnodes)
911 911
912 912 it = self.generatefiles(changedfiles, commonrevs,
913 913 source, mfdicts, fastpathlinkrev,
914 914 fnodes, clrevs)
915 915
916 916 for path, deltas in it:
917 917 h = _fileheader(path)
918 918 size = len(h)
919 919 yield h
920 920
921 921 for delta in deltas:
922 922 chunks = _revisiondeltatochunks(delta, self._builddeltaheader)
923 923 for chunk in chunks:
924 924 size += len(chunk)
925 925 yield chunk
926 926
927 927 close = closechunk()
928 928 size += len(close)
929 929 yield close
930 930
931 931 self._verbosenote(_('%8.i %s\n') % (size, path))
932 932
933 933 yield closechunk()
934 934
935 935 if clnodes:
936 936 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
937 937
938 938 def _generatechangelog(self, cl, nodes, generate=True):
939 939 """Generate data for changelog chunks.
940 940
941 941 Returns a 2-tuple of a dict containing state and an iterable of
942 942 byte chunks. The state will not be fully populated until the
943 943 chunk stream has been fully consumed.
944 944
945 945 if generate is False, the state will be fully populated and no chunk
946 946 stream will be yielded
947 947 """
948 948 clrevorder = {}
949 949 manifests = {}
950 950 mfl = self._repo.manifestlog
951 951 changedfiles = set()
952 952 clrevtomanifestrev = {}
953 953
954 954 state = {
955 955 'clrevorder': clrevorder,
956 956 'manifests': manifests,
957 957 'changedfiles': changedfiles,
958 958 'clrevtomanifestrev': clrevtomanifestrev,
959 959 }
960 960
961 961 if not (generate or self._ellipses):
962 962 # sort the nodes in storage order
963 963 nodes = sorted(nodes, key=cl.rev)
964 964 for node in nodes:
965 965 c = cl.changelogrevision(node)
966 966 clrevorder[node] = len(clrevorder)
967 967 # record the first changeset introducing this manifest version
968 968 manifests.setdefault(c.manifest, node)
969 969 # Record a complete list of potentially-changed files in
970 970 # this manifest.
971 971 changedfiles.update(c.files)
972 972
973 973 return state, ()
974 974
975 975 # Callback for the changelog, used to collect changed files and
976 976 # manifest nodes.
977 977 # Returns the linkrev node (identity in the changelog case).
978 978 def lookupcl(x):
979 979 c = cl.changelogrevision(x)
980 980 clrevorder[x] = len(clrevorder)
981 981
982 982 if self._ellipses:
983 983 # Only update manifests if x is going to be sent. Otherwise we
984 984 # end up with bogus linkrevs specified for manifests and
985 985 # we skip some manifest nodes that we should otherwise
986 986 # have sent.
987 987 if (x in self._fullclnodes
988 988 or cl.rev(x) in self._precomputedellipsis):
989 989
990 990 manifestnode = c.manifest
991 991 # Record the first changeset introducing this manifest
992 992 # version.
993 993 manifests.setdefault(manifestnode, x)
994 994 # Set this narrow-specific dict so we have the lowest
995 995 # manifest revnum to look up for this cl revnum. (Part of
996 996 # mapping changelog ellipsis parents to manifest ellipsis
997 997 # parents)
998 998 clrevtomanifestrev.setdefault(
999 999 cl.rev(x), mfl.rev(manifestnode))
1000 1000 # We can't trust the changed files list in the changeset if the
1001 1001 # client requested a shallow clone.
1002 1002 if self._isshallow:
1003 1003 changedfiles.update(mfl[c.manifest].read().keys())
1004 1004 else:
1005 1005 changedfiles.update(c.files)
1006 1006 else:
1007 1007 # record the first changeset introducing this manifest version
1008 1008 manifests.setdefault(c.manifest, x)
1009 1009 # Record a complete list of potentially-changed files in
1010 1010 # this manifest.
1011 1011 changedfiles.update(c.files)
1012 1012
1013 1013 return x
1014 1014
1015 1015 gen = deltagroup(
1016 1016 self._repo, cl, nodes, True, lookupcl,
1017 1017 self._forcedeltaparentprev,
1018 1018 ellipses=self._ellipses,
1019 1019 topic=_('changesets'),
1020 1020 clrevtolocalrev={},
1021 1021 fullclnodes=self._fullclnodes,
1022 1022 precomputedellipsis=self._precomputedellipsis)
1023 1023
1024 1024 return state, gen
1025 1025
1026 1026 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev,
1027 1027 manifests, fnodes, source, clrevtolocalrev):
1028 1028 """Returns an iterator of changegroup chunks containing manifests.
1029 1029
1030 1030 `source` is unused here, but is used by extensions like remotefilelog to
1031 1031 change what is sent based in pulls vs pushes, etc.
1032 1032 """
1033 1033 repo = self._repo
1034 1034 mfl = repo.manifestlog
1035 1035 tmfnodes = {'': manifests}
1036 1036
1037 1037 # Callback for the manifest, used to collect linkrevs for filelog
1038 1038 # revisions.
1039 1039 # Returns the linkrev node (collected in lookupcl).
1040 1040 def makelookupmflinknode(tree, nodes):
1041 1041 if fastpathlinkrev:
1042 1042 assert not tree
1043 1043 return manifests.__getitem__
1044 1044
1045 1045 def lookupmflinknode(x):
1046 1046 """Callback for looking up the linknode for manifests.
1047 1047
1048 1048 Returns the linkrev node for the specified manifest.
1049 1049
1050 1050 SIDE EFFECT:
1051 1051
1052 1052 1) fclnodes gets populated with the list of relevant
1053 1053 file nodes if we're not using fastpathlinkrev
1054 1054 2) When treemanifests are in use, collects treemanifest nodes
1055 1055 to send
1056 1056
1057 1057 Note that this means manifests must be completely sent to
1058 1058 the client before you can trust the list of files and
1059 1059 treemanifests to send.
1060 1060 """
1061 1061 clnode = nodes[x]
1062 1062 mdata = mfl.get(tree, x).readfast(shallow=True)
1063 1063 for p, n, fl in mdata.iterentries():
1064 1064 if fl == 't': # subdirectory manifest
1065 1065 subtree = tree + p + '/'
1066 1066 tmfclnodes = tmfnodes.setdefault(subtree, {})
1067 1067 tmfclnode = tmfclnodes.setdefault(n, clnode)
1068 1068 if clrevorder[clnode] < clrevorder[tmfclnode]:
1069 1069 tmfclnodes[n] = clnode
1070 1070 else:
1071 1071 f = tree + p
1072 1072 fclnodes = fnodes.setdefault(f, {})
1073 1073 fclnode = fclnodes.setdefault(n, clnode)
1074 1074 if clrevorder[clnode] < clrevorder[fclnode]:
1075 1075 fclnodes[n] = clnode
1076 1076 return clnode
1077 1077 return lookupmflinknode
1078 1078
1079 1079 while tmfnodes:
1080 1080 tree, nodes = tmfnodes.popitem()
1081 1081
1082 1082 should_visit = self._matcher.visitdir(tree[:-1])
1083 1083 if tree and not should_visit:
1084 1084 continue
1085 1085
1086 1086 store = mfl.getstorage(tree)
1087 1087
1088 1088 if not should_visit:
1089 1089 # No nodes to send because this directory is out of
1090 1090 # the client's view of the repository (probably
1091 1091 # because of narrow clones). Do this even for the root
1092 1092 # directory (tree=='')
1093 1093 prunednodes = []
1094 1094 else:
1095 1095 # Avoid sending any manifest nodes we can prove the
1096 1096 # client already has by checking linkrevs. See the
1097 1097 # related comment in generatefiles().
1098 1098 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1099 1099
1100 1100 if tree and not prunednodes:
1101 1101 continue
1102 1102
1103 1103 lookupfn = makelookupmflinknode(tree, nodes)
1104 1104
1105 1105 deltas = deltagroup(
1106 1106 self._repo, store, prunednodes, False, lookupfn,
1107 1107 self._forcedeltaparentprev,
1108 1108 ellipses=self._ellipses,
1109 1109 topic=_('manifests'),
1110 1110 clrevtolocalrev=clrevtolocalrev,
1111 1111 fullclnodes=self._fullclnodes,
1112 1112 precomputedellipsis=self._precomputedellipsis)
1113 1113
1114 1114 if not self._oldmatcher.visitdir(store.tree[:-1]):
1115 1115 yield tree, deltas
1116 1116 else:
1117 1117 # 'deltas' is a generator and we need to consume it even if
1118 1118 # we are not going to send it because a side-effect is that
1119 1119 # it updates tmdnodes (via lookupfn)
1120 1120 for d in deltas:
1121 1121 pass
1122 1122 if not tree:
1123 1123 yield tree, []
1124 1124
1125 1125 def _prunemanifests(self, store, nodes, commonrevs):
1126 1126 if not self._ellipses:
1127 1127 # In non-ellipses case and large repositories, it is better to
1128 1128 # prevent calling of store.rev and store.linkrev on a lot of
1129 1129 # nodes as compared to sending some extra data
1130 1130 return nodes.copy()
1131 1131 # This is split out as a separate method to allow filtering
1132 1132 # commonrevs in extension code.
1133 1133 #
1134 1134 # TODO(augie): this shouldn't be required, instead we should
1135 1135 # make filtering of revisions to send delegated to the store
1136 1136 # layer.
1137 1137 frev, flr = store.rev, store.linkrev
1138 1138 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1139 1139
1140 1140 # The 'source' parameter is useful for extensions
1141 1141 def generatefiles(self, changedfiles, commonrevs, source,
1142 1142 mfdicts, fastpathlinkrev, fnodes, clrevs):
1143 1143 changedfiles = [f for f in changedfiles
1144 1144 if self._matcher(f) and not self._oldmatcher(f)]
1145 1145
1146 1146 if not fastpathlinkrev:
1147 1147 def normallinknodes(unused, fname):
1148 1148 return fnodes.get(fname, {})
1149 1149 else:
1150 1150 cln = self._repo.changelog.node
1151 1151
1152 1152 def normallinknodes(store, fname):
1153 1153 flinkrev = store.linkrev
1154 1154 fnode = store.node
1155 1155 revs = ((r, flinkrev(r)) for r in store)
1156 1156 return dict((fnode(r), cln(lr))
1157 1157 for r, lr in revs if lr in clrevs)
1158 1158
1159 1159 clrevtolocalrev = {}
1160 1160
1161 1161 if self._isshallow:
1162 1162 # In a shallow clone, the linknodes callback needs to also include
1163 1163 # those file nodes that are in the manifests we sent but weren't
1164 1164 # introduced by those manifests.
1165 1165 commonctxs = [self._repo[c] for c in commonrevs]
1166 1166 clrev = self._repo.changelog.rev
1167 1167
1168 1168 def linknodes(flog, fname):
1169 1169 for c in commonctxs:
1170 1170 try:
1171 1171 fnode = c.filenode(fname)
1172 1172 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1173 1173 except error.ManifestLookupError:
1174 1174 pass
1175 1175 links = normallinknodes(flog, fname)
1176 1176 if len(links) != len(mfdicts):
1177 1177 for mf, lr in mfdicts:
1178 1178 fnode = mf.get(fname, None)
1179 1179 if fnode in links:
1180 1180 links[fnode] = min(links[fnode], lr, key=clrev)
1181 1181 elif fnode:
1182 1182 links[fnode] = lr
1183 1183 return links
1184 1184 else:
1185 1185 linknodes = normallinknodes
1186 1186
1187 1187 repo = self._repo
1188 1188 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1189 1189 total=len(changedfiles))
1190 1190 for i, fname in enumerate(sorted(changedfiles)):
1191 1191 filerevlog = repo.file(fname)
1192 1192 if not filerevlog:
1193 1193 raise error.Abort(_("empty or missing file data for %s") %
1194 1194 fname)
1195 1195
1196 1196 clrevtolocalrev.clear()
1197 1197
1198 1198 linkrevnodes = linknodes(filerevlog, fname)
1199 1199 # Lookup for filenodes, we collected the linkrev nodes above in the
1200 1200 # fastpath case and with lookupmf in the slowpath case.
1201 1201 def lookupfilelog(x):
1202 1202 return linkrevnodes[x]
1203 1203
1204 1204 frev, flr = filerevlog.rev, filerevlog.linkrev
1205 1205 # Skip sending any filenode we know the client already
1206 1206 # has. This avoids over-sending files relatively
1207 1207 # inexpensively, so it's not a problem if we under-filter
1208 1208 # here.
1209 1209 filenodes = [n for n in linkrevnodes
1210 1210 if flr(frev(n)) not in commonrevs]
1211 1211
1212 1212 if not filenodes:
1213 1213 continue
1214 1214
1215 1215 progress.update(i + 1, item=fname)
1216 1216
1217 1217 deltas = deltagroup(
1218 1218 self._repo, filerevlog, filenodes, False, lookupfilelog,
1219 1219 self._forcedeltaparentprev,
1220 1220 ellipses=self._ellipses,
1221 1221 clrevtolocalrev=clrevtolocalrev,
1222 1222 fullclnodes=self._fullclnodes,
1223 1223 precomputedellipsis=self._precomputedellipsis)
1224 1224
1225 1225 yield fname, deltas
1226 1226
1227 1227 progress.complete()
1228 1228
1229 1229 def _makecg1packer(repo, oldmatcher, matcher, bundlecaps,
1230 1230 ellipses=False, shallow=False, ellipsisroots=None,
1231 1231 fullnodes=None):
1232 1232 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1233 1233 d.node, d.p1node, d.p2node, d.linknode)
1234 1234
1235 1235 return cgpacker(repo, oldmatcher, matcher, b'01',
1236 1236 builddeltaheader=builddeltaheader,
1237 1237 manifestsend=b'',
1238 1238 forcedeltaparentprev=True,
1239 1239 bundlecaps=bundlecaps,
1240 1240 ellipses=ellipses,
1241 1241 shallow=shallow,
1242 1242 ellipsisroots=ellipsisroots,
1243 1243 fullnodes=fullnodes)
1244 1244
1245 1245 def _makecg2packer(repo, oldmatcher, matcher, bundlecaps,
1246 1246 ellipses=False, shallow=False, ellipsisroots=None,
1247 1247 fullnodes=None):
1248 1248 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1249 1249 d.node, d.p1node, d.p2node, d.basenode, d.linknode)
1250 1250
1251 1251 return cgpacker(repo, oldmatcher, matcher, b'02',
1252 1252 builddeltaheader=builddeltaheader,
1253 1253 manifestsend=b'',
1254 1254 bundlecaps=bundlecaps,
1255 1255 ellipses=ellipses,
1256 1256 shallow=shallow,
1257 1257 ellipsisroots=ellipsisroots,
1258 1258 fullnodes=fullnodes)
1259 1259
1260 1260 def _makecg3packer(repo, oldmatcher, matcher, bundlecaps,
1261 1261 ellipses=False, shallow=False, ellipsisroots=None,
1262 1262 fullnodes=None):
1263 1263 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1264 1264 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags)
1265 1265
1266 1266 return cgpacker(repo, oldmatcher, matcher, b'03',
1267 1267 builddeltaheader=builddeltaheader,
1268 1268 manifestsend=closechunk(),
1269 1269 bundlecaps=bundlecaps,
1270 1270 ellipses=ellipses,
1271 1271 shallow=shallow,
1272 1272 ellipsisroots=ellipsisroots,
1273 1273 fullnodes=fullnodes)
1274 1274
1275 1275 _packermap = {'01': (_makecg1packer, cg1unpacker),
1276 1276 # cg2 adds support for exchanging generaldelta
1277 1277 '02': (_makecg2packer, cg2unpacker),
1278 1278 # cg3 adds support for exchanging revlog flags and treemanifests
1279 1279 '03': (_makecg3packer, cg3unpacker),
1280 1280 }
1281 1281
1282 1282 def allsupportedversions(repo):
1283 1283 versions = set(_packermap.keys())
1284 if not (repo.ui.configbool('experimental', 'changegroup3') or
1285 repo.ui.configbool('experimental', 'treemanifest') or
1286 'treemanifest' in repo.requirements):
1284 needv03 = False
1285 if (repo.ui.configbool('experimental', 'changegroup3') or
1286 repo.ui.configbool('experimental', 'treemanifest') or
1287 'treemanifest' in repo.requirements):
1288 # we keep version 03 because we need to to exchange treemanifest data
1289 #
1290 # we also keep vresion 01 and 02, because it is possible for repo to
1291 # contains both normal and tree manifest at the same time. so using
1292 # older version to pull data is viable
1293 #
1294 # (or even to push subset of history)
1295 needv03 = True
1296 if not needv03:
1287 1297 versions.discard('03')
1288 1298 return versions
1289 1299
1290 1300 # Changegroup versions that can be applied to the repo
1291 1301 def supportedincomingversions(repo):
1292 1302 return allsupportedversions(repo)
1293 1303
1294 1304 # Changegroup versions that can be created from the repo
1295 1305 def supportedoutgoingversions(repo):
1296 1306 versions = allsupportedversions(repo)
1297 1307 if 'treemanifest' in repo.requirements:
1298 1308 # Versions 01 and 02 support only flat manifests and it's just too
1299 1309 # expensive to convert between the flat manifest and tree manifest on
1300 1310 # the fly. Since tree manifests are hashed differently, all of history
1301 1311 # would have to be converted. Instead, we simply don't even pretend to
1302 1312 # support versions 01 and 02.
1303 1313 versions.discard('01')
1304 1314 versions.discard('02')
1305 1315 if repository.NARROW_REQUIREMENT in repo.requirements:
1306 1316 # Versions 01 and 02 don't support revlog flags, and we need to
1307 1317 # support that for stripping and unbundling to work.
1308 1318 versions.discard('01')
1309 1319 versions.discard('02')
1310 1320 if LFS_REQUIREMENT in repo.requirements:
1311 1321 # Versions 01 and 02 don't support revlog flags, and we need to
1312 1322 # mark LFS entries with REVIDX_EXTSTORED.
1313 1323 versions.discard('01')
1314 1324 versions.discard('02')
1315 1325
1316 1326 return versions
1317 1327
1318 1328 def localversion(repo):
1319 1329 # Finds the best version to use for bundles that are meant to be used
1320 1330 # locally, such as those from strip and shelve, and temporary bundles.
1321 1331 return max(supportedoutgoingversions(repo))
1322 1332
1323 1333 def safeversion(repo):
1324 1334 # Finds the smallest version that it's safe to assume clients of the repo
1325 1335 # will support. For example, all hg versions that support generaldelta also
1326 1336 # support changegroup 02.
1327 1337 versions = supportedoutgoingversions(repo)
1328 1338 if 'generaldelta' in repo.requirements:
1329 1339 versions.discard('01')
1330 1340 assert versions
1331 1341 return min(versions)
1332 1342
1333 1343 def getbundler(version, repo, bundlecaps=None, oldmatcher=None,
1334 1344 matcher=None, ellipses=False, shallow=False,
1335 1345 ellipsisroots=None, fullnodes=None):
1336 1346 assert version in supportedoutgoingversions(repo)
1337 1347
1338 1348 if matcher is None:
1339 1349 matcher = matchmod.always()
1340 1350 if oldmatcher is None:
1341 1351 oldmatcher = matchmod.never()
1342 1352
1343 1353 if version == '01' and not matcher.always():
1344 1354 raise error.ProgrammingError('version 01 changegroups do not support '
1345 1355 'sparse file matchers')
1346 1356
1347 1357 if ellipses and version in (b'01', b'02'):
1348 1358 raise error.Abort(
1349 1359 _('ellipsis nodes require at least cg3 on client and server, '
1350 1360 'but negotiated version %s') % version)
1351 1361
1352 1362 # Requested files could include files not in the local store. So
1353 1363 # filter those out.
1354 1364 matcher = repo.narrowmatch(matcher)
1355 1365
1356 1366 fn = _packermap[version][0]
1357 1367 return fn(repo, oldmatcher, matcher, bundlecaps, ellipses=ellipses,
1358 1368 shallow=shallow, ellipsisroots=ellipsisroots,
1359 1369 fullnodes=fullnodes)
1360 1370
1361 1371 def getunbundler(version, fh, alg, extras=None):
1362 1372 return _packermap[version][1](fh, alg, extras=extras)
1363 1373
1364 1374 def _changegroupinfo(repo, nodes, source):
1365 1375 if repo.ui.verbose or source == 'bundle':
1366 1376 repo.ui.status(_("%d changesets found\n") % len(nodes))
1367 1377 if repo.ui.debugflag:
1368 1378 repo.ui.debug("list of changesets:\n")
1369 1379 for node in nodes:
1370 1380 repo.ui.debug("%s\n" % hex(node))
1371 1381
1372 1382 def makechangegroup(repo, outgoing, version, source, fastpath=False,
1373 1383 bundlecaps=None):
1374 1384 cgstream = makestream(repo, outgoing, version, source,
1375 1385 fastpath=fastpath, bundlecaps=bundlecaps)
1376 1386 return getunbundler(version, util.chunkbuffer(cgstream), None,
1377 1387 {'clcount': len(outgoing.missing) })
1378 1388
1379 1389 def makestream(repo, outgoing, version, source, fastpath=False,
1380 1390 bundlecaps=None, matcher=None):
1381 1391 bundler = getbundler(version, repo, bundlecaps=bundlecaps,
1382 1392 matcher=matcher)
1383 1393
1384 1394 repo = repo.unfiltered()
1385 1395 commonrevs = outgoing.common
1386 1396 csets = outgoing.missing
1387 1397 heads = outgoing.missingheads
1388 1398 # We go through the fast path if we get told to, or if all (unfiltered
1389 1399 # heads have been requested (since we then know there all linkrevs will
1390 1400 # be pulled by the client).
1391 1401 heads.sort()
1392 1402 fastpathlinkrev = fastpath or (
1393 1403 repo.filtername is None and heads == sorted(repo.heads()))
1394 1404
1395 1405 repo.hook('preoutgoing', throw=True, source=source)
1396 1406 _changegroupinfo(repo, csets, source)
1397 1407 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1398 1408
1399 1409 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1400 1410 revisions = 0
1401 1411 files = 0
1402 1412 progress = repo.ui.makeprogress(_('files'), unit=_('files'),
1403 1413 total=expectedfiles)
1404 1414 for chunkdata in iter(source.filelogheader, {}):
1405 1415 files += 1
1406 1416 f = chunkdata["filename"]
1407 1417 repo.ui.debug("adding %s revisions\n" % f)
1408 1418 progress.increment()
1409 1419 fl = repo.file(f)
1410 1420 o = len(fl)
1411 1421 try:
1412 1422 deltas = source.deltaiter()
1413 1423 if not fl.addgroup(deltas, revmap, trp):
1414 1424 raise error.Abort(_("received file revlog group is empty"))
1415 1425 except error.CensoredBaseError as e:
1416 1426 raise error.Abort(_("received delta base is censored: %s") % e)
1417 1427 revisions += len(fl) - o
1418 1428 if f in needfiles:
1419 1429 needs = needfiles[f]
1420 1430 for new in pycompat.xrange(o, len(fl)):
1421 1431 n = fl.node(new)
1422 1432 if n in needs:
1423 1433 needs.remove(n)
1424 1434 else:
1425 1435 raise error.Abort(
1426 1436 _("received spurious file revlog entry"))
1427 1437 if not needs:
1428 1438 del needfiles[f]
1429 1439 progress.complete()
1430 1440
1431 1441 for f, needs in needfiles.iteritems():
1432 1442 fl = repo.file(f)
1433 1443 for n in needs:
1434 1444 try:
1435 1445 fl.rev(n)
1436 1446 except error.LookupError:
1437 1447 raise error.Abort(
1438 1448 _('missing file data for %s:%s - run hg verify') %
1439 1449 (f, hex(n)))
1440 1450
1441 1451 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now