##// END OF EJS Templates
revlog: use revlog.display_id in narrow error message...
marmoute -
r47927:89e11a6d default
parent child Browse files
Show More
@@ -1,1952 +1,1952 b''
1 1 # changegroup.py - Mercurial changegroup manipulation functions
2 2 #
3 3 # Copyright 2006 Olivia Mackall <olivia@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 nullrev,
18 18 short,
19 19 )
20 20 from .pycompat import open
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 requirements,
29 29 scmutil,
30 30 util,
31 31 )
32 32
33 33 from .interfaces import repository
34 34 from .revlogutils import sidedata as sidedatamod
35 35 from .revlogutils import constants as revlog_constants
36 36 from .utils import storageutil
37 37
38 38 _CHANGEGROUPV1_DELTA_HEADER = struct.Struct(b"20s20s20s20s")
39 39 _CHANGEGROUPV2_DELTA_HEADER = struct.Struct(b"20s20s20s20s20s")
40 40 _CHANGEGROUPV3_DELTA_HEADER = struct.Struct(b">20s20s20s20s20sH")
41 41 _CHANGEGROUPV4_DELTA_HEADER = struct.Struct(b">B20s20s20s20s20sH")
42 42
43 43 LFS_REQUIREMENT = b'lfs'
44 44
45 45 readexactly = util.readexactly
46 46
47 47
48 48 def getchunk(stream):
49 49 """return the next chunk from stream as a string"""
50 50 d = readexactly(stream, 4)
51 51 l = struct.unpack(b">l", d)[0]
52 52 if l <= 4:
53 53 if l:
54 54 raise error.Abort(_(b"invalid chunk length %d") % l)
55 55 return b""
56 56 return readexactly(stream, l - 4)
57 57
58 58
59 59 def chunkheader(length):
60 60 """return a changegroup chunk header (string)"""
61 61 return struct.pack(b">l", length + 4)
62 62
63 63
64 64 def closechunk():
65 65 """return a changegroup chunk header (string) for a zero-length chunk"""
66 66 return struct.pack(b">l", 0)
67 67
68 68
69 69 def _fileheader(path):
70 70 """Obtain a changegroup chunk header for a named path."""
71 71 return chunkheader(len(path)) + path
72 72
73 73
74 74 def writechunks(ui, chunks, filename, vfs=None):
75 75 """Write chunks to a file and return its filename.
76 76
77 77 The stream is assumed to be a bundle file.
78 78 Existing files will not be overwritten.
79 79 If no filename is specified, a temporary file is created.
80 80 """
81 81 fh = None
82 82 cleanup = None
83 83 try:
84 84 if filename:
85 85 if vfs:
86 86 fh = vfs.open(filename, b"wb")
87 87 else:
88 88 # Increase default buffer size because default is usually
89 89 # small (4k is common on Linux).
90 90 fh = open(filename, b"wb", 131072)
91 91 else:
92 92 fd, filename = pycompat.mkstemp(prefix=b"hg-bundle-", suffix=b".hg")
93 93 fh = os.fdopen(fd, "wb")
94 94 cleanup = filename
95 95 for c in chunks:
96 96 fh.write(c)
97 97 cleanup = None
98 98 return filename
99 99 finally:
100 100 if fh is not None:
101 101 fh.close()
102 102 if cleanup is not None:
103 103 if filename and vfs:
104 104 vfs.unlink(cleanup)
105 105 else:
106 106 os.unlink(cleanup)
107 107
108 108
109 109 class cg1unpacker(object):
110 110 """Unpacker for cg1 changegroup streams.
111 111
112 112 A changegroup unpacker handles the framing of the revision data in
113 113 the wire format. Most consumers will want to use the apply()
114 114 method to add the changes from the changegroup to a repository.
115 115
116 116 If you're forwarding a changegroup unmodified to another consumer,
117 117 use getchunks(), which returns an iterator of changegroup
118 118 chunks. This is mostly useful for cases where you need to know the
119 119 data stream has ended by observing the end of the changegroup.
120 120
121 121 deltachunk() is useful only if you're applying delta data. Most
122 122 consumers should prefer apply() instead.
123 123
124 124 A few other public methods exist. Those are used only for
125 125 bundlerepo and some debug commands - their use is discouraged.
126 126 """
127 127
128 128 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
129 129 deltaheadersize = deltaheader.size
130 130 version = b'01'
131 131 _grouplistcount = 1 # One list of files after the manifests
132 132
133 133 def __init__(self, fh, alg, extras=None):
134 134 if alg is None:
135 135 alg = b'UN'
136 136 if alg not in util.compengines.supportedbundletypes:
137 137 raise error.Abort(_(b'unknown stream compression type: %s') % alg)
138 138 if alg == b'BZ':
139 139 alg = b'_truncatedBZ'
140 140
141 141 compengine = util.compengines.forbundletype(alg)
142 142 self._stream = compengine.decompressorreader(fh)
143 143 self._type = alg
144 144 self.extras = extras or {}
145 145 self.callback = None
146 146
147 147 # These methods (compressed, read, seek, tell) all appear to only
148 148 # be used by bundlerepo, but it's a little hard to tell.
149 149 def compressed(self):
150 150 return self._type is not None and self._type != b'UN'
151 151
152 152 def read(self, l):
153 153 return self._stream.read(l)
154 154
155 155 def seek(self, pos):
156 156 return self._stream.seek(pos)
157 157
158 158 def tell(self):
159 159 return self._stream.tell()
160 160
161 161 def close(self):
162 162 return self._stream.close()
163 163
164 164 def _chunklength(self):
165 165 d = readexactly(self._stream, 4)
166 166 l = struct.unpack(b">l", d)[0]
167 167 if l <= 4:
168 168 if l:
169 169 raise error.Abort(_(b"invalid chunk length %d") % l)
170 170 return 0
171 171 if self.callback:
172 172 self.callback()
173 173 return l - 4
174 174
175 175 def changelogheader(self):
176 176 """v10 does not have a changelog header chunk"""
177 177 return {}
178 178
179 179 def manifestheader(self):
180 180 """v10 does not have a manifest header chunk"""
181 181 return {}
182 182
183 183 def filelogheader(self):
184 184 """return the header of the filelogs chunk, v10 only has the filename"""
185 185 l = self._chunklength()
186 186 if not l:
187 187 return {}
188 188 fname = readexactly(self._stream, l)
189 189 return {b'filename': fname}
190 190
191 191 def _deltaheader(self, headertuple, prevnode):
192 192 node, p1, p2, cs = headertuple
193 193 if prevnode is None:
194 194 deltabase = p1
195 195 else:
196 196 deltabase = prevnode
197 197 flags = 0
198 198 protocol_flags = 0
199 199 return node, p1, p2, deltabase, cs, flags, protocol_flags
200 200
201 201 def deltachunk(self, prevnode):
202 202 l = self._chunklength()
203 203 if not l:
204 204 return {}
205 205 headerdata = readexactly(self._stream, self.deltaheadersize)
206 206 header = self.deltaheader.unpack(headerdata)
207 207 delta = readexactly(self._stream, l - self.deltaheadersize)
208 208 header = self._deltaheader(header, prevnode)
209 209 node, p1, p2, deltabase, cs, flags, protocol_flags = header
210 210 return node, p1, p2, cs, deltabase, delta, flags, protocol_flags
211 211
212 212 def getchunks(self):
213 213 """returns all the chunks contains in the bundle
214 214
215 215 Used when you need to forward the binary stream to a file or another
216 216 network API. To do so, it parse the changegroup data, otherwise it will
217 217 block in case of sshrepo because it don't know the end of the stream.
218 218 """
219 219 # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
220 220 # and a list of filelogs. For changegroup 3, we expect 4 parts:
221 221 # changelog, manifestlog, a list of tree manifestlogs, and a list of
222 222 # filelogs.
223 223 #
224 224 # Changelog and manifestlog parts are terminated with empty chunks. The
225 225 # tree and file parts are a list of entry sections. Each entry section
226 226 # is a series of chunks terminating in an empty chunk. The list of these
227 227 # entry sections is terminated in yet another empty chunk, so we know
228 228 # we've reached the end of the tree/file list when we reach an empty
229 229 # chunk that was proceeded by no non-empty chunks.
230 230
231 231 parts = 0
232 232 while parts < 2 + self._grouplistcount:
233 233 noentries = True
234 234 while True:
235 235 chunk = getchunk(self)
236 236 if not chunk:
237 237 # The first two empty chunks represent the end of the
238 238 # changelog and the manifestlog portions. The remaining
239 239 # empty chunks represent either A) the end of individual
240 240 # tree or file entries in the file list, or B) the end of
241 241 # the entire list. It's the end of the entire list if there
242 242 # were no entries (i.e. noentries is True).
243 243 if parts < 2:
244 244 parts += 1
245 245 elif noentries:
246 246 parts += 1
247 247 break
248 248 noentries = False
249 249 yield chunkheader(len(chunk))
250 250 pos = 0
251 251 while pos < len(chunk):
252 252 next = pos + 2 ** 20
253 253 yield chunk[pos:next]
254 254 pos = next
255 255 yield closechunk()
256 256
257 257 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
258 258 self.callback = prog.increment
259 259 # no need to check for empty manifest group here:
260 260 # if the result of the merge of 1 and 2 is the same in 3 and 4,
261 261 # no new manifest will be created and the manifest group will
262 262 # be empty during the pull
263 263 self.manifestheader()
264 264 deltas = self.deltaiter()
265 265 storage = repo.manifestlog.getstorage(b'')
266 266 storage.addgroup(deltas, revmap, trp, addrevisioncb=addrevisioncb)
267 267 prog.complete()
268 268 self.callback = None
269 269
270 270 def apply(
271 271 self,
272 272 repo,
273 273 tr,
274 274 srctype,
275 275 url,
276 276 targetphase=phases.draft,
277 277 expectedtotal=None,
278 278 sidedata_categories=None,
279 279 ):
280 280 """Add the changegroup returned by source.read() to this repo.
281 281 srctype is a string like 'push', 'pull', or 'unbundle'. url is
282 282 the URL of the repo where this changegroup is coming from.
283 283
284 284 Return an integer summarizing the change to this repo:
285 285 - nothing changed or no source: 0
286 286 - more heads than before: 1+added heads (2..n)
287 287 - fewer heads than before: -1-removed heads (-2..-n)
288 288 - number of heads stays the same: 1
289 289
290 290 `sidedata_categories` is an optional set of the remote's sidedata wanted
291 291 categories.
292 292 """
293 293 repo = repo.unfiltered()
294 294
295 295 # Only useful if we're adding sidedata categories. If both peers have
296 296 # the same categories, then we simply don't do anything.
297 297 adding_sidedata = (
298 298 requirements.REVLOGV2_REQUIREMENT in repo.requirements
299 299 and self.version == b'04'
300 300 and srctype == b'pull'
301 301 )
302 302 if adding_sidedata:
303 303 sidedata_helpers = sidedatamod.get_sidedata_helpers(
304 304 repo,
305 305 sidedata_categories or set(),
306 306 pull=True,
307 307 )
308 308 else:
309 309 sidedata_helpers = None
310 310
311 311 def csmap(x):
312 312 repo.ui.debug(b"add changeset %s\n" % short(x))
313 313 return len(cl)
314 314
315 315 def revmap(x):
316 316 return cl.rev(x)
317 317
318 318 try:
319 319 # The transaction may already carry source information. In this
320 320 # case we use the top level data. We overwrite the argument
321 321 # because we need to use the top level value (if they exist)
322 322 # in this function.
323 323 srctype = tr.hookargs.setdefault(b'source', srctype)
324 324 tr.hookargs.setdefault(b'url', url)
325 325 repo.hook(
326 326 b'prechangegroup', throw=True, **pycompat.strkwargs(tr.hookargs)
327 327 )
328 328
329 329 # write changelog data to temp files so concurrent readers
330 330 # will not see an inconsistent view
331 331 cl = repo.changelog
332 332 cl.delayupdate(tr)
333 333 oldheads = set(cl.heads())
334 334
335 335 trp = weakref.proxy(tr)
336 336 # pull off the changeset group
337 337 repo.ui.status(_(b"adding changesets\n"))
338 338 clstart = len(cl)
339 339 progress = repo.ui.makeprogress(
340 340 _(b'changesets'), unit=_(b'chunks'), total=expectedtotal
341 341 )
342 342 self.callback = progress.increment
343 343
344 344 efilesset = set()
345 345 duprevs = []
346 346
347 347 def ondupchangelog(cl, rev):
348 348 if rev < clstart:
349 349 duprevs.append(rev)
350 350
351 351 def onchangelog(cl, rev):
352 352 ctx = cl.changelogrevision(rev)
353 353 efilesset.update(ctx.files)
354 354 repo.register_changeset(rev, ctx)
355 355
356 356 self.changelogheader()
357 357 deltas = self.deltaiter()
358 358 if not cl.addgroup(
359 359 deltas,
360 360 csmap,
361 361 trp,
362 362 alwayscache=True,
363 363 addrevisioncb=onchangelog,
364 364 duplicaterevisioncb=ondupchangelog,
365 365 ):
366 366 repo.ui.develwarn(
367 367 b'applied empty changelog from changegroup',
368 368 config=b'warn-empty-changegroup',
369 369 )
370 370 efiles = len(efilesset)
371 371 clend = len(cl)
372 372 changesets = clend - clstart
373 373 progress.complete()
374 374 del deltas
375 375 # TODO Python 2.7 removal
376 376 # del efilesset
377 377 efilesset = None
378 378 self.callback = None
379 379
380 380 # Keep track of the (non-changelog) revlogs we've updated and their
381 381 # range of new revisions for sidedata rewrite.
382 382 # TODO do something more efficient than keeping the reference to
383 383 # the revlogs, especially memory-wise.
384 384 touched_manifests = {}
385 385 touched_filelogs = {}
386 386
387 387 # pull off the manifest group
388 388 repo.ui.status(_(b"adding manifests\n"))
389 389 # We know that we'll never have more manifests than we had
390 390 # changesets.
391 391 progress = repo.ui.makeprogress(
392 392 _(b'manifests'), unit=_(b'chunks'), total=changesets
393 393 )
394 394 on_manifest_rev = None
395 395 if sidedata_helpers:
396 396 if revlog_constants.KIND_MANIFESTLOG in sidedata_helpers[1]:
397 397
398 398 def on_manifest_rev(manifest, rev):
399 399 range = touched_manifests.get(manifest)
400 400 if not range:
401 401 touched_manifests[manifest] = (rev, rev)
402 402 else:
403 403 assert rev == range[1] + 1
404 404 touched_manifests[manifest] = (range[0], rev)
405 405
406 406 self._unpackmanifests(
407 407 repo,
408 408 revmap,
409 409 trp,
410 410 progress,
411 411 addrevisioncb=on_manifest_rev,
412 412 )
413 413
414 414 needfiles = {}
415 415 if repo.ui.configbool(b'server', b'validate'):
416 416 cl = repo.changelog
417 417 ml = repo.manifestlog
418 418 # validate incoming csets have their manifests
419 419 for cset in pycompat.xrange(clstart, clend):
420 420 mfnode = cl.changelogrevision(cset).manifest
421 421 mfest = ml[mfnode].readdelta()
422 422 # store file nodes we must see
423 423 for f, n in pycompat.iteritems(mfest):
424 424 needfiles.setdefault(f, set()).add(n)
425 425
426 426 on_filelog_rev = None
427 427 if sidedata_helpers:
428 428 if revlog_constants.KIND_FILELOG in sidedata_helpers[1]:
429 429
430 430 def on_filelog_rev(filelog, rev):
431 431 range = touched_filelogs.get(filelog)
432 432 if not range:
433 433 touched_filelogs[filelog] = (rev, rev)
434 434 else:
435 435 assert rev == range[1] + 1
436 436 touched_filelogs[filelog] = (range[0], rev)
437 437
438 438 # process the files
439 439 repo.ui.status(_(b"adding file changes\n"))
440 440 newrevs, newfiles = _addchangegroupfiles(
441 441 repo,
442 442 self,
443 443 revmap,
444 444 trp,
445 445 efiles,
446 446 needfiles,
447 447 addrevisioncb=on_filelog_rev,
448 448 )
449 449
450 450 if sidedata_helpers:
451 451 if revlog_constants.KIND_CHANGELOG in sidedata_helpers[1]:
452 452 cl.rewrite_sidedata(sidedata_helpers, clstart, clend - 1)
453 453 for mf, (startrev, endrev) in touched_manifests.items():
454 454 mf.rewrite_sidedata(sidedata_helpers, startrev, endrev)
455 455 for fl, (startrev, endrev) in touched_filelogs.items():
456 456 fl.rewrite_sidedata(sidedata_helpers, startrev, endrev)
457 457
458 458 # making sure the value exists
459 459 tr.changes.setdefault(b'changegroup-count-changesets', 0)
460 460 tr.changes.setdefault(b'changegroup-count-revisions', 0)
461 461 tr.changes.setdefault(b'changegroup-count-files', 0)
462 462 tr.changes.setdefault(b'changegroup-count-heads', 0)
463 463
464 464 # some code use bundle operation for internal purpose. They usually
465 465 # set `ui.quiet` to do this outside of user sight. Size the report
466 466 # of such operation now happens at the end of the transaction, that
467 467 # ui.quiet has not direct effect on the output.
468 468 #
469 469 # To preserve this intend use an inelegant hack, we fail to report
470 470 # the change if `quiet` is set. We should probably move to
471 471 # something better, but this is a good first step to allow the "end
472 472 # of transaction report" to pass tests.
473 473 if not repo.ui.quiet:
474 474 tr.changes[b'changegroup-count-changesets'] += changesets
475 475 tr.changes[b'changegroup-count-revisions'] += newrevs
476 476 tr.changes[b'changegroup-count-files'] += newfiles
477 477
478 478 deltaheads = 0
479 479 if oldheads:
480 480 heads = cl.heads()
481 481 deltaheads += len(heads) - len(oldheads)
482 482 for h in heads:
483 483 if h not in oldheads and repo[h].closesbranch():
484 484 deltaheads -= 1
485 485
486 486 # see previous comment about checking ui.quiet
487 487 if not repo.ui.quiet:
488 488 tr.changes[b'changegroup-count-heads'] += deltaheads
489 489 repo.invalidatevolatilesets()
490 490
491 491 if changesets > 0:
492 492 if b'node' not in tr.hookargs:
493 493 tr.hookargs[b'node'] = hex(cl.node(clstart))
494 494 tr.hookargs[b'node_last'] = hex(cl.node(clend - 1))
495 495 hookargs = dict(tr.hookargs)
496 496 else:
497 497 hookargs = dict(tr.hookargs)
498 498 hookargs[b'node'] = hex(cl.node(clstart))
499 499 hookargs[b'node_last'] = hex(cl.node(clend - 1))
500 500 repo.hook(
501 501 b'pretxnchangegroup',
502 502 throw=True,
503 503 **pycompat.strkwargs(hookargs)
504 504 )
505 505
506 506 added = pycompat.xrange(clstart, clend)
507 507 phaseall = None
508 508 if srctype in (b'push', b'serve'):
509 509 # Old servers can not push the boundary themselves.
510 510 # New servers won't push the boundary if changeset already
511 511 # exists locally as secret
512 512 #
513 513 # We should not use added here but the list of all change in
514 514 # the bundle
515 515 if repo.publishing():
516 516 targetphase = phaseall = phases.public
517 517 else:
518 518 # closer target phase computation
519 519
520 520 # Those changesets have been pushed from the
521 521 # outside, their phases are going to be pushed
522 522 # alongside. Therefor `targetphase` is
523 523 # ignored.
524 524 targetphase = phaseall = phases.draft
525 525 if added:
526 526 phases.registernew(repo, tr, targetphase, added)
527 527 if phaseall is not None:
528 528 if duprevs:
529 529 duprevs.extend(added)
530 530 else:
531 531 duprevs = added
532 532 phases.advanceboundary(repo, tr, phaseall, [], revs=duprevs)
533 533 duprevs = []
534 534
535 535 if changesets > 0:
536 536
537 537 def runhooks(unused_success):
538 538 # These hooks run when the lock releases, not when the
539 539 # transaction closes. So it's possible for the changelog
540 540 # to have changed since we last saw it.
541 541 if clstart >= len(repo):
542 542 return
543 543
544 544 repo.hook(b"changegroup", **pycompat.strkwargs(hookargs))
545 545
546 546 for rev in added:
547 547 args = hookargs.copy()
548 548 args[b'node'] = hex(cl.node(rev))
549 549 del args[b'node_last']
550 550 repo.hook(b"incoming", **pycompat.strkwargs(args))
551 551
552 552 newheads = [h for h in repo.heads() if h not in oldheads]
553 553 repo.ui.log(
554 554 b"incoming",
555 555 b"%d incoming changes - new heads: %s\n",
556 556 len(added),
557 557 b', '.join([hex(c[:6]) for c in newheads]),
558 558 )
559 559
560 560 tr.addpostclose(
561 561 b'changegroup-runhooks-%020i' % clstart,
562 562 lambda tr: repo._afterlock(runhooks),
563 563 )
564 564 finally:
565 565 repo.ui.flush()
566 566 # never return 0 here:
567 567 if deltaheads < 0:
568 568 ret = deltaheads - 1
569 569 else:
570 570 ret = deltaheads + 1
571 571 return ret
572 572
573 573 def deltaiter(self):
574 574 """
575 575 returns an iterator of the deltas in this changegroup
576 576
577 577 Useful for passing to the underlying storage system to be stored.
578 578 """
579 579 chain = None
580 580 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
581 581 # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags, sidedata)
582 582 yield chunkdata
583 583 chain = chunkdata[0]
584 584
585 585
586 586 class cg2unpacker(cg1unpacker):
587 587 """Unpacker for cg2 streams.
588 588
589 589 cg2 streams add support for generaldelta, so the delta header
590 590 format is slightly different. All other features about the data
591 591 remain the same.
592 592 """
593 593
594 594 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
595 595 deltaheadersize = deltaheader.size
596 596 version = b'02'
597 597
598 598 def _deltaheader(self, headertuple, prevnode):
599 599 node, p1, p2, deltabase, cs = headertuple
600 600 flags = 0
601 601 protocol_flags = 0
602 602 return node, p1, p2, deltabase, cs, flags, protocol_flags
603 603
604 604
605 605 class cg3unpacker(cg2unpacker):
606 606 """Unpacker for cg3 streams.
607 607
608 608 cg3 streams add support for exchanging treemanifests and revlog
609 609 flags. It adds the revlog flags to the delta header and an empty chunk
610 610 separating manifests and files.
611 611 """
612 612
613 613 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
614 614 deltaheadersize = deltaheader.size
615 615 version = b'03'
616 616 _grouplistcount = 2 # One list of manifests and one list of files
617 617
618 618 def _deltaheader(self, headertuple, prevnode):
619 619 node, p1, p2, deltabase, cs, flags = headertuple
620 620 protocol_flags = 0
621 621 return node, p1, p2, deltabase, cs, flags, protocol_flags
622 622
623 623 def _unpackmanifests(self, repo, revmap, trp, prog, addrevisioncb=None):
624 624 super(cg3unpacker, self)._unpackmanifests(
625 625 repo, revmap, trp, prog, addrevisioncb=addrevisioncb
626 626 )
627 627 for chunkdata in iter(self.filelogheader, {}):
628 628 # If we get here, there are directory manifests in the changegroup
629 629 d = chunkdata[b"filename"]
630 630 repo.ui.debug(b"adding %s revisions\n" % d)
631 631 deltas = self.deltaiter()
632 632 if not repo.manifestlog.getstorage(d).addgroup(
633 633 deltas, revmap, trp, addrevisioncb=addrevisioncb
634 634 ):
635 635 raise error.Abort(_(b"received dir revlog group is empty"))
636 636
637 637
638 638 class cg4unpacker(cg3unpacker):
639 639 """Unpacker for cg4 streams.
640 640
641 641 cg4 streams add support for exchanging sidedata.
642 642 """
643 643
644 644 deltaheader = _CHANGEGROUPV4_DELTA_HEADER
645 645 deltaheadersize = deltaheader.size
646 646 version = b'04'
647 647
648 648 def _deltaheader(self, headertuple, prevnode):
649 649 protocol_flags, node, p1, p2, deltabase, cs, flags = headertuple
650 650 return node, p1, p2, deltabase, cs, flags, protocol_flags
651 651
652 652 def deltachunk(self, prevnode):
653 653 res = super(cg4unpacker, self).deltachunk(prevnode)
654 654 if not res:
655 655 return res
656 656
657 657 (node, p1, p2, cs, deltabase, delta, flags, protocol_flags) = res
658 658
659 659 sidedata = {}
660 660 if protocol_flags & storageutil.CG_FLAG_SIDEDATA:
661 661 sidedata_raw = getchunk(self._stream)
662 662 sidedata = sidedatamod.deserialize_sidedata(sidedata_raw)
663 663
664 664 return node, p1, p2, cs, deltabase, delta, flags, sidedata
665 665
666 666
667 667 class headerlessfixup(object):
668 668 def __init__(self, fh, h):
669 669 self._h = h
670 670 self._fh = fh
671 671
672 672 def read(self, n):
673 673 if self._h:
674 674 d, self._h = self._h[:n], self._h[n:]
675 675 if len(d) < n:
676 676 d += readexactly(self._fh, n - len(d))
677 677 return d
678 678 return readexactly(self._fh, n)
679 679
680 680
681 681 def _revisiondeltatochunks(repo, delta, headerfn):
682 682 """Serialize a revisiondelta to changegroup chunks."""
683 683
684 684 # The captured revision delta may be encoded as a delta against
685 685 # a base revision or as a full revision. The changegroup format
686 686 # requires that everything on the wire be deltas. So for full
687 687 # revisions, we need to invent a header that says to rewrite
688 688 # data.
689 689
690 690 if delta.delta is not None:
691 691 prefix, data = b'', delta.delta
692 692 elif delta.basenode == repo.nullid:
693 693 data = delta.revision
694 694 prefix = mdiff.trivialdiffheader(len(data))
695 695 else:
696 696 data = delta.revision
697 697 prefix = mdiff.replacediffheader(delta.baserevisionsize, len(data))
698 698
699 699 meta = headerfn(delta)
700 700
701 701 yield chunkheader(len(meta) + len(prefix) + len(data))
702 702 yield meta
703 703 if prefix:
704 704 yield prefix
705 705 yield data
706 706
707 707 if delta.protocol_flags & storageutil.CG_FLAG_SIDEDATA:
708 708 # Need a separate chunk for sidedata to be able to differentiate
709 709 # "raw delta" length and sidedata length
710 710 sidedata = delta.sidedata
711 711 yield chunkheader(len(sidedata))
712 712 yield sidedata
713 713
714 714
715 715 def _sortnodesellipsis(store, nodes, cl, lookup):
716 716 """Sort nodes for changegroup generation."""
717 717 # Ellipses serving mode.
718 718 #
719 719 # In a perfect world, we'd generate better ellipsis-ified graphs
720 720 # for non-changelog revlogs. In practice, we haven't started doing
721 721 # that yet, so the resulting DAGs for the manifestlog and filelogs
722 722 # are actually full of bogus parentage on all the ellipsis
723 723 # nodes. This has the side effect that, while the contents are
724 724 # correct, the individual DAGs might be completely out of whack in
725 725 # a case like 882681bc3166 and its ancestors (back about 10
726 726 # revisions or so) in the main hg repo.
727 727 #
728 728 # The one invariant we *know* holds is that the new (potentially
729 729 # bogus) DAG shape will be valid if we order the nodes in the
730 730 # order that they're introduced in dramatis personae by the
731 731 # changelog, so what we do is we sort the non-changelog histories
732 732 # by the order in which they are used by the changelog.
733 733 key = lambda n: cl.rev(lookup(n))
734 734 return sorted(nodes, key=key)
735 735
736 736
737 737 def _resolvenarrowrevisioninfo(
738 738 cl,
739 739 store,
740 740 ischangelog,
741 741 rev,
742 742 linkrev,
743 743 linknode,
744 744 clrevtolocalrev,
745 745 fullclnodes,
746 746 precomputedellipsis,
747 747 ):
748 748 linkparents = precomputedellipsis[linkrev]
749 749
750 750 def local(clrev):
751 751 """Turn a changelog revnum into a local revnum.
752 752
753 753 The ellipsis dag is stored as revnums on the changelog,
754 754 but when we're producing ellipsis entries for
755 755 non-changelog revlogs, we need to turn those numbers into
756 756 something local. This does that for us, and during the
757 757 changelog sending phase will also expand the stored
758 758 mappings as needed.
759 759 """
760 760 if clrev == nullrev:
761 761 return nullrev
762 762
763 763 if ischangelog:
764 764 return clrev
765 765
766 766 # Walk the ellipsis-ized changelog breadth-first looking for a
767 767 # change that has been linked from the current revlog.
768 768 #
769 769 # For a flat manifest revlog only a single step should be necessary
770 770 # as all relevant changelog entries are relevant to the flat
771 771 # manifest.
772 772 #
773 773 # For a filelog or tree manifest dirlog however not every changelog
774 774 # entry will have been relevant, so we need to skip some changelog
775 775 # nodes even after ellipsis-izing.
776 776 walk = [clrev]
777 777 while walk:
778 778 p = walk[0]
779 779 walk = walk[1:]
780 780 if p in clrevtolocalrev:
781 781 return clrevtolocalrev[p]
782 782 elif p in fullclnodes:
783 783 walk.extend([pp for pp in cl.parentrevs(p) if pp != nullrev])
784 784 elif p in precomputedellipsis:
785 785 walk.extend(
786 786 [pp for pp in precomputedellipsis[p] if pp != nullrev]
787 787 )
788 788 else:
789 789 # In this case, we've got an ellipsis with parents
790 790 # outside the current bundle (likely an
791 791 # incremental pull). We "know" that we can use the
792 792 # value of this same revlog at whatever revision
793 793 # is pointed to by linknode. "Know" is in scare
794 794 # quotes because I haven't done enough examination
795 795 # of edge cases to convince myself this is really
796 796 # a fact - it works for all the (admittedly
797 797 # thorough) cases in our testsuite, but I would be
798 798 # somewhat unsurprised to find a case in the wild
799 799 # where this breaks down a bit. That said, I don't
800 800 # know if it would hurt anything.
801 801 for i in pycompat.xrange(rev, 0, -1):
802 802 if store.linkrev(i) == clrev:
803 803 return i
804 804 # We failed to resolve a parent for this node, so
805 805 # we crash the changegroup construction.
806 806 if util.safehasattr(store, 'target'):
807 target = store._indexfile
807 target = store.display_id
808 808 else:
809 809 # some revlog not actually a revlog
810 target = store._revlog._indexfile
810 target = store._revlog.display_id
811 811
812 812 raise error.Abort(
813 813 b"unable to resolve parent while packing '%s' %r"
814 814 b' for changeset %r' % (target, rev, clrev)
815 815 )
816 816
817 817 return nullrev
818 818
819 819 if not linkparents or (store.parentrevs(rev) == (nullrev, nullrev)):
820 820 p1, p2 = nullrev, nullrev
821 821 elif len(linkparents) == 1:
822 822 (p1,) = sorted(local(p) for p in linkparents)
823 823 p2 = nullrev
824 824 else:
825 825 p1, p2 = sorted(local(p) for p in linkparents)
826 826
827 827 p1node, p2node = store.node(p1), store.node(p2)
828 828
829 829 return p1node, p2node, linknode
830 830
831 831
832 832 def deltagroup(
833 833 repo,
834 834 store,
835 835 nodes,
836 836 ischangelog,
837 837 lookup,
838 838 forcedeltaparentprev,
839 839 topic=None,
840 840 ellipses=False,
841 841 clrevtolocalrev=None,
842 842 fullclnodes=None,
843 843 precomputedellipsis=None,
844 844 sidedata_helpers=None,
845 845 ):
846 846 """Calculate deltas for a set of revisions.
847 847
848 848 Is a generator of ``revisiondelta`` instances.
849 849
850 850 If topic is not None, progress detail will be generated using this
851 851 topic name (e.g. changesets, manifests, etc).
852 852
853 853 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
854 854 `sidedata_helpers`.
855 855 """
856 856 if not nodes:
857 857 return
858 858
859 859 cl = repo.changelog
860 860
861 861 if ischangelog:
862 862 # `hg log` shows changesets in storage order. To preserve order
863 863 # across clones, send out changesets in storage order.
864 864 nodesorder = b'storage'
865 865 elif ellipses:
866 866 nodes = _sortnodesellipsis(store, nodes, cl, lookup)
867 867 nodesorder = b'nodes'
868 868 else:
869 869 nodesorder = None
870 870
871 871 # Perform ellipses filtering and revision massaging. We do this before
872 872 # emitrevisions() because a) filtering out revisions creates less work
873 873 # for emitrevisions() b) dropping revisions would break emitrevisions()'s
874 874 # assumptions about delta choices and we would possibly send a delta
875 875 # referencing a missing base revision.
876 876 #
877 877 # Also, calling lookup() has side-effects with regards to populating
878 878 # data structures. If we don't call lookup() for each node or if we call
879 879 # lookup() after the first pass through each node, things can break -
880 880 # possibly intermittently depending on the python hash seed! For that
881 881 # reason, we store a mapping of all linknodes during the initial node
882 882 # pass rather than use lookup() on the output side.
883 883 if ellipses:
884 884 filtered = []
885 885 adjustedparents = {}
886 886 linknodes = {}
887 887
888 888 for node in nodes:
889 889 rev = store.rev(node)
890 890 linknode = lookup(node)
891 891 linkrev = cl.rev(linknode)
892 892 clrevtolocalrev[linkrev] = rev
893 893
894 894 # If linknode is in fullclnodes, it means the corresponding
895 895 # changeset was a full changeset and is being sent unaltered.
896 896 if linknode in fullclnodes:
897 897 linknodes[node] = linknode
898 898
899 899 # If the corresponding changeset wasn't in the set computed
900 900 # as relevant to us, it should be dropped outright.
901 901 elif linkrev not in precomputedellipsis:
902 902 continue
903 903
904 904 else:
905 905 # We could probably do this later and avoid the dict
906 906 # holding state. But it likely doesn't matter.
907 907 p1node, p2node, linknode = _resolvenarrowrevisioninfo(
908 908 cl,
909 909 store,
910 910 ischangelog,
911 911 rev,
912 912 linkrev,
913 913 linknode,
914 914 clrevtolocalrev,
915 915 fullclnodes,
916 916 precomputedellipsis,
917 917 )
918 918
919 919 adjustedparents[node] = (p1node, p2node)
920 920 linknodes[node] = linknode
921 921
922 922 filtered.append(node)
923 923
924 924 nodes = filtered
925 925
926 926 # We expect the first pass to be fast, so we only engage the progress
927 927 # meter for constructing the revision deltas.
928 928 progress = None
929 929 if topic is not None:
930 930 progress = repo.ui.makeprogress(
931 931 topic, unit=_(b'chunks'), total=len(nodes)
932 932 )
933 933
934 934 configtarget = repo.ui.config(b'devel', b'bundle.delta')
935 935 if configtarget not in (b'', b'p1', b'full'):
936 936 msg = _(b"""config "devel.bundle.delta" as unknown value: %s""")
937 937 repo.ui.warn(msg % configtarget)
938 938
939 939 deltamode = repository.CG_DELTAMODE_STD
940 940 if forcedeltaparentprev:
941 941 deltamode = repository.CG_DELTAMODE_PREV
942 942 elif configtarget == b'p1':
943 943 deltamode = repository.CG_DELTAMODE_P1
944 944 elif configtarget == b'full':
945 945 deltamode = repository.CG_DELTAMODE_FULL
946 946
947 947 revisions = store.emitrevisions(
948 948 nodes,
949 949 nodesorder=nodesorder,
950 950 revisiondata=True,
951 951 assumehaveparentrevisions=not ellipses,
952 952 deltamode=deltamode,
953 953 sidedata_helpers=sidedata_helpers,
954 954 )
955 955
956 956 for i, revision in enumerate(revisions):
957 957 if progress:
958 958 progress.update(i + 1)
959 959
960 960 if ellipses:
961 961 linknode = linknodes[revision.node]
962 962
963 963 if revision.node in adjustedparents:
964 964 p1node, p2node = adjustedparents[revision.node]
965 965 revision.p1node = p1node
966 966 revision.p2node = p2node
967 967 revision.flags |= repository.REVISION_FLAG_ELLIPSIS
968 968
969 969 else:
970 970 linknode = lookup(revision.node)
971 971
972 972 revision.linknode = linknode
973 973 yield revision
974 974
975 975 if progress:
976 976 progress.complete()
977 977
978 978
979 979 class cgpacker(object):
980 980 def __init__(
981 981 self,
982 982 repo,
983 983 oldmatcher,
984 984 matcher,
985 985 version,
986 986 builddeltaheader,
987 987 manifestsend,
988 988 forcedeltaparentprev=False,
989 989 bundlecaps=None,
990 990 ellipses=False,
991 991 shallow=False,
992 992 ellipsisroots=None,
993 993 fullnodes=None,
994 994 remote_sidedata=None,
995 995 ):
996 996 """Given a source repo, construct a bundler.
997 997
998 998 oldmatcher is a matcher that matches on files the client already has.
999 999 These will not be included in the changegroup.
1000 1000
1001 1001 matcher is a matcher that matches on files to include in the
1002 1002 changegroup. Used to facilitate sparse changegroups.
1003 1003
1004 1004 forcedeltaparentprev indicates whether delta parents must be against
1005 1005 the previous revision in a delta group. This should only be used for
1006 1006 compatibility with changegroup version 1.
1007 1007
1008 1008 builddeltaheader is a callable that constructs the header for a group
1009 1009 delta.
1010 1010
1011 1011 manifestsend is a chunk to send after manifests have been fully emitted.
1012 1012
1013 1013 ellipses indicates whether ellipsis serving mode is enabled.
1014 1014
1015 1015 bundlecaps is optional and can be used to specify the set of
1016 1016 capabilities which can be used to build the bundle. While bundlecaps is
1017 1017 unused in core Mercurial, extensions rely on this feature to communicate
1018 1018 capabilities to customize the changegroup packer.
1019 1019
1020 1020 shallow indicates whether shallow data might be sent. The packer may
1021 1021 need to pack file contents not introduced by the changes being packed.
1022 1022
1023 1023 fullnodes is the set of changelog nodes which should not be ellipsis
1024 1024 nodes. We store this rather than the set of nodes that should be
1025 1025 ellipsis because for very large histories we expect this to be
1026 1026 significantly smaller.
1027 1027
1028 1028 remote_sidedata is the set of sidedata categories wanted by the remote.
1029 1029 """
1030 1030 assert oldmatcher
1031 1031 assert matcher
1032 1032 self._oldmatcher = oldmatcher
1033 1033 self._matcher = matcher
1034 1034
1035 1035 self.version = version
1036 1036 self._forcedeltaparentprev = forcedeltaparentprev
1037 1037 self._builddeltaheader = builddeltaheader
1038 1038 self._manifestsend = manifestsend
1039 1039 self._ellipses = ellipses
1040 1040
1041 1041 # Set of capabilities we can use to build the bundle.
1042 1042 if bundlecaps is None:
1043 1043 bundlecaps = set()
1044 1044 self._bundlecaps = bundlecaps
1045 1045 if remote_sidedata is None:
1046 1046 remote_sidedata = set()
1047 1047 self._remote_sidedata = remote_sidedata
1048 1048 self._isshallow = shallow
1049 1049 self._fullclnodes = fullnodes
1050 1050
1051 1051 # Maps ellipsis revs to their roots at the changelog level.
1052 1052 self._precomputedellipsis = ellipsisroots
1053 1053
1054 1054 self._repo = repo
1055 1055
1056 1056 if self._repo.ui.verbose and not self._repo.ui.debugflag:
1057 1057 self._verbosenote = self._repo.ui.note
1058 1058 else:
1059 1059 self._verbosenote = lambda s: None
1060 1060
1061 1061 def generate(
1062 1062 self, commonrevs, clnodes, fastpathlinkrev, source, changelog=True
1063 1063 ):
1064 1064 """Yield a sequence of changegroup byte chunks.
1065 1065 If changelog is False, changelog data won't be added to changegroup
1066 1066 """
1067 1067
1068 1068 repo = self._repo
1069 1069 cl = repo.changelog
1070 1070
1071 1071 self._verbosenote(_(b'uncompressed size of bundle content:\n'))
1072 1072 size = 0
1073 1073
1074 1074 sidedata_helpers = None
1075 1075 if self.version == b'04':
1076 1076 remote_sidedata = self._remote_sidedata
1077 1077 if source == b'strip':
1078 1078 # We're our own remote when stripping, get the no-op helpers
1079 1079 # TODO a better approach would be for the strip bundle to
1080 1080 # correctly advertise its sidedata categories directly.
1081 1081 remote_sidedata = repo._wanted_sidedata
1082 1082 sidedata_helpers = sidedatamod.get_sidedata_helpers(
1083 1083 repo, remote_sidedata
1084 1084 )
1085 1085
1086 1086 clstate, deltas = self._generatechangelog(
1087 1087 cl,
1088 1088 clnodes,
1089 1089 generate=changelog,
1090 1090 sidedata_helpers=sidedata_helpers,
1091 1091 )
1092 1092 for delta in deltas:
1093 1093 for chunk in _revisiondeltatochunks(
1094 1094 self._repo, delta, self._builddeltaheader
1095 1095 ):
1096 1096 size += len(chunk)
1097 1097 yield chunk
1098 1098
1099 1099 close = closechunk()
1100 1100 size += len(close)
1101 1101 yield closechunk()
1102 1102
1103 1103 self._verbosenote(_(b'%8.i (changelog)\n') % size)
1104 1104
1105 1105 clrevorder = clstate[b'clrevorder']
1106 1106 manifests = clstate[b'manifests']
1107 1107 changedfiles = clstate[b'changedfiles']
1108 1108
1109 1109 # We need to make sure that the linkrev in the changegroup refers to
1110 1110 # the first changeset that introduced the manifest or file revision.
1111 1111 # The fastpath is usually safer than the slowpath, because the filelogs
1112 1112 # are walked in revlog order.
1113 1113 #
1114 1114 # When taking the slowpath when the manifest revlog uses generaldelta,
1115 1115 # the manifest may be walked in the "wrong" order. Without 'clrevorder',
1116 1116 # we would get an incorrect linkrev (see fix in cc0ff93d0c0c).
1117 1117 #
1118 1118 # When taking the fastpath, we are only vulnerable to reordering
1119 1119 # of the changelog itself. The changelog never uses generaldelta and is
1120 1120 # never reordered. To handle this case, we simply take the slowpath,
1121 1121 # which already has the 'clrevorder' logic. This was also fixed in
1122 1122 # cc0ff93d0c0c.
1123 1123
1124 1124 # Treemanifests don't work correctly with fastpathlinkrev
1125 1125 # either, because we don't discover which directory nodes to
1126 1126 # send along with files. This could probably be fixed.
1127 1127 fastpathlinkrev = fastpathlinkrev and not scmutil.istreemanifest(repo)
1128 1128
1129 1129 fnodes = {} # needed file nodes
1130 1130
1131 1131 size = 0
1132 1132 it = self.generatemanifests(
1133 1133 commonrevs,
1134 1134 clrevorder,
1135 1135 fastpathlinkrev,
1136 1136 manifests,
1137 1137 fnodes,
1138 1138 source,
1139 1139 clstate[b'clrevtomanifestrev'],
1140 1140 sidedata_helpers=sidedata_helpers,
1141 1141 )
1142 1142
1143 1143 for tree, deltas in it:
1144 1144 if tree:
1145 1145 assert self.version in (b'03', b'04')
1146 1146 chunk = _fileheader(tree)
1147 1147 size += len(chunk)
1148 1148 yield chunk
1149 1149
1150 1150 for delta in deltas:
1151 1151 chunks = _revisiondeltatochunks(
1152 1152 self._repo, delta, self._builddeltaheader
1153 1153 )
1154 1154 for chunk in chunks:
1155 1155 size += len(chunk)
1156 1156 yield chunk
1157 1157
1158 1158 close = closechunk()
1159 1159 size += len(close)
1160 1160 yield close
1161 1161
1162 1162 self._verbosenote(_(b'%8.i (manifests)\n') % size)
1163 1163 yield self._manifestsend
1164 1164
1165 1165 mfdicts = None
1166 1166 if self._ellipses and self._isshallow:
1167 1167 mfdicts = [
1168 1168 (repo.manifestlog[n].read(), lr)
1169 1169 for (n, lr) in pycompat.iteritems(manifests)
1170 1170 ]
1171 1171
1172 1172 manifests.clear()
1173 1173 clrevs = {cl.rev(x) for x in clnodes}
1174 1174
1175 1175 it = self.generatefiles(
1176 1176 changedfiles,
1177 1177 commonrevs,
1178 1178 source,
1179 1179 mfdicts,
1180 1180 fastpathlinkrev,
1181 1181 fnodes,
1182 1182 clrevs,
1183 1183 sidedata_helpers=sidedata_helpers,
1184 1184 )
1185 1185
1186 1186 for path, deltas in it:
1187 1187 h = _fileheader(path)
1188 1188 size = len(h)
1189 1189 yield h
1190 1190
1191 1191 for delta in deltas:
1192 1192 chunks = _revisiondeltatochunks(
1193 1193 self._repo, delta, self._builddeltaheader
1194 1194 )
1195 1195 for chunk in chunks:
1196 1196 size += len(chunk)
1197 1197 yield chunk
1198 1198
1199 1199 close = closechunk()
1200 1200 size += len(close)
1201 1201 yield close
1202 1202
1203 1203 self._verbosenote(_(b'%8.i %s\n') % (size, path))
1204 1204
1205 1205 yield closechunk()
1206 1206
1207 1207 if clnodes:
1208 1208 repo.hook(b'outgoing', node=hex(clnodes[0]), source=source)
1209 1209
1210 1210 def _generatechangelog(
1211 1211 self, cl, nodes, generate=True, sidedata_helpers=None
1212 1212 ):
1213 1213 """Generate data for changelog chunks.
1214 1214
1215 1215 Returns a 2-tuple of a dict containing state and an iterable of
1216 1216 byte chunks. The state will not be fully populated until the
1217 1217 chunk stream has been fully consumed.
1218 1218
1219 1219 if generate is False, the state will be fully populated and no chunk
1220 1220 stream will be yielded
1221 1221
1222 1222 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1223 1223 `sidedata_helpers`.
1224 1224 """
1225 1225 clrevorder = {}
1226 1226 manifests = {}
1227 1227 mfl = self._repo.manifestlog
1228 1228 changedfiles = set()
1229 1229 clrevtomanifestrev = {}
1230 1230
1231 1231 state = {
1232 1232 b'clrevorder': clrevorder,
1233 1233 b'manifests': manifests,
1234 1234 b'changedfiles': changedfiles,
1235 1235 b'clrevtomanifestrev': clrevtomanifestrev,
1236 1236 }
1237 1237
1238 1238 if not (generate or self._ellipses):
1239 1239 # sort the nodes in storage order
1240 1240 nodes = sorted(nodes, key=cl.rev)
1241 1241 for node in nodes:
1242 1242 c = cl.changelogrevision(node)
1243 1243 clrevorder[node] = len(clrevorder)
1244 1244 # record the first changeset introducing this manifest version
1245 1245 manifests.setdefault(c.manifest, node)
1246 1246 # Record a complete list of potentially-changed files in
1247 1247 # this manifest.
1248 1248 changedfiles.update(c.files)
1249 1249
1250 1250 return state, ()
1251 1251
1252 1252 # Callback for the changelog, used to collect changed files and
1253 1253 # manifest nodes.
1254 1254 # Returns the linkrev node (identity in the changelog case).
1255 1255 def lookupcl(x):
1256 1256 c = cl.changelogrevision(x)
1257 1257 clrevorder[x] = len(clrevorder)
1258 1258
1259 1259 if self._ellipses:
1260 1260 # Only update manifests if x is going to be sent. Otherwise we
1261 1261 # end up with bogus linkrevs specified for manifests and
1262 1262 # we skip some manifest nodes that we should otherwise
1263 1263 # have sent.
1264 1264 if (
1265 1265 x in self._fullclnodes
1266 1266 or cl.rev(x) in self._precomputedellipsis
1267 1267 ):
1268 1268
1269 1269 manifestnode = c.manifest
1270 1270 # Record the first changeset introducing this manifest
1271 1271 # version.
1272 1272 manifests.setdefault(manifestnode, x)
1273 1273 # Set this narrow-specific dict so we have the lowest
1274 1274 # manifest revnum to look up for this cl revnum. (Part of
1275 1275 # mapping changelog ellipsis parents to manifest ellipsis
1276 1276 # parents)
1277 1277 clrevtomanifestrev.setdefault(
1278 1278 cl.rev(x), mfl.rev(manifestnode)
1279 1279 )
1280 1280 # We can't trust the changed files list in the changeset if the
1281 1281 # client requested a shallow clone.
1282 1282 if self._isshallow:
1283 1283 changedfiles.update(mfl[c.manifest].read().keys())
1284 1284 else:
1285 1285 changedfiles.update(c.files)
1286 1286 else:
1287 1287 # record the first changeset introducing this manifest version
1288 1288 manifests.setdefault(c.manifest, x)
1289 1289 # Record a complete list of potentially-changed files in
1290 1290 # this manifest.
1291 1291 changedfiles.update(c.files)
1292 1292
1293 1293 return x
1294 1294
1295 1295 gen = deltagroup(
1296 1296 self._repo,
1297 1297 cl,
1298 1298 nodes,
1299 1299 True,
1300 1300 lookupcl,
1301 1301 self._forcedeltaparentprev,
1302 1302 ellipses=self._ellipses,
1303 1303 topic=_(b'changesets'),
1304 1304 clrevtolocalrev={},
1305 1305 fullclnodes=self._fullclnodes,
1306 1306 precomputedellipsis=self._precomputedellipsis,
1307 1307 sidedata_helpers=sidedata_helpers,
1308 1308 )
1309 1309
1310 1310 return state, gen
1311 1311
1312 1312 def generatemanifests(
1313 1313 self,
1314 1314 commonrevs,
1315 1315 clrevorder,
1316 1316 fastpathlinkrev,
1317 1317 manifests,
1318 1318 fnodes,
1319 1319 source,
1320 1320 clrevtolocalrev,
1321 1321 sidedata_helpers=None,
1322 1322 ):
1323 1323 """Returns an iterator of changegroup chunks containing manifests.
1324 1324
1325 1325 `source` is unused here, but is used by extensions like remotefilelog to
1326 1326 change what is sent based in pulls vs pushes, etc.
1327 1327
1328 1328 See `revlogutil.sidedata.get_sidedata_helpers` for the doc on
1329 1329 `sidedata_helpers`.
1330 1330 """
1331 1331 repo = self._repo
1332 1332 mfl = repo.manifestlog
1333 1333 tmfnodes = {b'': manifests}
1334 1334
1335 1335 # Callback for the manifest, used to collect linkrevs for filelog
1336 1336 # revisions.
1337 1337 # Returns the linkrev node (collected in lookupcl).
1338 1338 def makelookupmflinknode(tree, nodes):
1339 1339 if fastpathlinkrev:
1340 1340 assert not tree
1341 1341
1342 1342 # pytype: disable=unsupported-operands
1343 1343 return manifests.__getitem__
1344 1344 # pytype: enable=unsupported-operands
1345 1345
1346 1346 def lookupmflinknode(x):
1347 1347 """Callback for looking up the linknode for manifests.
1348 1348
1349 1349 Returns the linkrev node for the specified manifest.
1350 1350
1351 1351 SIDE EFFECT:
1352 1352
1353 1353 1) fclnodes gets populated with the list of relevant
1354 1354 file nodes if we're not using fastpathlinkrev
1355 1355 2) When treemanifests are in use, collects treemanifest nodes
1356 1356 to send
1357 1357
1358 1358 Note that this means manifests must be completely sent to
1359 1359 the client before you can trust the list of files and
1360 1360 treemanifests to send.
1361 1361 """
1362 1362 clnode = nodes[x]
1363 1363 mdata = mfl.get(tree, x).readfast(shallow=True)
1364 1364 for p, n, fl in mdata.iterentries():
1365 1365 if fl == b't': # subdirectory manifest
1366 1366 subtree = tree + p + b'/'
1367 1367 tmfclnodes = tmfnodes.setdefault(subtree, {})
1368 1368 tmfclnode = tmfclnodes.setdefault(n, clnode)
1369 1369 if clrevorder[clnode] < clrevorder[tmfclnode]:
1370 1370 tmfclnodes[n] = clnode
1371 1371 else:
1372 1372 f = tree + p
1373 1373 fclnodes = fnodes.setdefault(f, {})
1374 1374 fclnode = fclnodes.setdefault(n, clnode)
1375 1375 if clrevorder[clnode] < clrevorder[fclnode]:
1376 1376 fclnodes[n] = clnode
1377 1377 return clnode
1378 1378
1379 1379 return lookupmflinknode
1380 1380
1381 1381 while tmfnodes:
1382 1382 tree, nodes = tmfnodes.popitem()
1383 1383
1384 1384 should_visit = self._matcher.visitdir(tree[:-1])
1385 1385 if tree and not should_visit:
1386 1386 continue
1387 1387
1388 1388 store = mfl.getstorage(tree)
1389 1389
1390 1390 if not should_visit:
1391 1391 # No nodes to send because this directory is out of
1392 1392 # the client's view of the repository (probably
1393 1393 # because of narrow clones). Do this even for the root
1394 1394 # directory (tree=='')
1395 1395 prunednodes = []
1396 1396 else:
1397 1397 # Avoid sending any manifest nodes we can prove the
1398 1398 # client already has by checking linkrevs. See the
1399 1399 # related comment in generatefiles().
1400 1400 prunednodes = self._prunemanifests(store, nodes, commonrevs)
1401 1401
1402 1402 if tree and not prunednodes:
1403 1403 continue
1404 1404
1405 1405 lookupfn = makelookupmflinknode(tree, nodes)
1406 1406
1407 1407 deltas = deltagroup(
1408 1408 self._repo,
1409 1409 store,
1410 1410 prunednodes,
1411 1411 False,
1412 1412 lookupfn,
1413 1413 self._forcedeltaparentprev,
1414 1414 ellipses=self._ellipses,
1415 1415 topic=_(b'manifests'),
1416 1416 clrevtolocalrev=clrevtolocalrev,
1417 1417 fullclnodes=self._fullclnodes,
1418 1418 precomputedellipsis=self._precomputedellipsis,
1419 1419 sidedata_helpers=sidedata_helpers,
1420 1420 )
1421 1421
1422 1422 if not self._oldmatcher.visitdir(store.tree[:-1]):
1423 1423 yield tree, deltas
1424 1424 else:
1425 1425 # 'deltas' is a generator and we need to consume it even if
1426 1426 # we are not going to send it because a side-effect is that
1427 1427 # it updates tmdnodes (via lookupfn)
1428 1428 for d in deltas:
1429 1429 pass
1430 1430 if not tree:
1431 1431 yield tree, []
1432 1432
1433 1433 def _prunemanifests(self, store, nodes, commonrevs):
1434 1434 if not self._ellipses:
1435 1435 # In non-ellipses case and large repositories, it is better to
1436 1436 # prevent calling of store.rev and store.linkrev on a lot of
1437 1437 # nodes as compared to sending some extra data
1438 1438 return nodes.copy()
1439 1439 # This is split out as a separate method to allow filtering
1440 1440 # commonrevs in extension code.
1441 1441 #
1442 1442 # TODO(augie): this shouldn't be required, instead we should
1443 1443 # make filtering of revisions to send delegated to the store
1444 1444 # layer.
1445 1445 frev, flr = store.rev, store.linkrev
1446 1446 return [n for n in nodes if flr(frev(n)) not in commonrevs]
1447 1447
1448 1448 # The 'source' parameter is useful for extensions
1449 1449 def generatefiles(
1450 1450 self,
1451 1451 changedfiles,
1452 1452 commonrevs,
1453 1453 source,
1454 1454 mfdicts,
1455 1455 fastpathlinkrev,
1456 1456 fnodes,
1457 1457 clrevs,
1458 1458 sidedata_helpers=None,
1459 1459 ):
1460 1460 changedfiles = [
1461 1461 f
1462 1462 for f in changedfiles
1463 1463 if self._matcher(f) and not self._oldmatcher(f)
1464 1464 ]
1465 1465
1466 1466 if not fastpathlinkrev:
1467 1467
1468 1468 def normallinknodes(unused, fname):
1469 1469 return fnodes.get(fname, {})
1470 1470
1471 1471 else:
1472 1472 cln = self._repo.changelog.node
1473 1473
1474 1474 def normallinknodes(store, fname):
1475 1475 flinkrev = store.linkrev
1476 1476 fnode = store.node
1477 1477 revs = ((r, flinkrev(r)) for r in store)
1478 1478 return {fnode(r): cln(lr) for r, lr in revs if lr in clrevs}
1479 1479
1480 1480 clrevtolocalrev = {}
1481 1481
1482 1482 if self._isshallow:
1483 1483 # In a shallow clone, the linknodes callback needs to also include
1484 1484 # those file nodes that are in the manifests we sent but weren't
1485 1485 # introduced by those manifests.
1486 1486 commonctxs = [self._repo[c] for c in commonrevs]
1487 1487 clrev = self._repo.changelog.rev
1488 1488
1489 1489 def linknodes(flog, fname):
1490 1490 for c in commonctxs:
1491 1491 try:
1492 1492 fnode = c.filenode(fname)
1493 1493 clrevtolocalrev[c.rev()] = flog.rev(fnode)
1494 1494 except error.ManifestLookupError:
1495 1495 pass
1496 1496 links = normallinknodes(flog, fname)
1497 1497 if len(links) != len(mfdicts):
1498 1498 for mf, lr in mfdicts:
1499 1499 fnode = mf.get(fname, None)
1500 1500 if fnode in links:
1501 1501 links[fnode] = min(links[fnode], lr, key=clrev)
1502 1502 elif fnode:
1503 1503 links[fnode] = lr
1504 1504 return links
1505 1505
1506 1506 else:
1507 1507 linknodes = normallinknodes
1508 1508
1509 1509 repo = self._repo
1510 1510 progress = repo.ui.makeprogress(
1511 1511 _(b'files'), unit=_(b'files'), total=len(changedfiles)
1512 1512 )
1513 1513 for i, fname in enumerate(sorted(changedfiles)):
1514 1514 filerevlog = repo.file(fname)
1515 1515 if not filerevlog:
1516 1516 raise error.Abort(
1517 1517 _(b"empty or missing file data for %s") % fname
1518 1518 )
1519 1519
1520 1520 clrevtolocalrev.clear()
1521 1521
1522 1522 linkrevnodes = linknodes(filerevlog, fname)
1523 1523 # Lookup for filenodes, we collected the linkrev nodes above in the
1524 1524 # fastpath case and with lookupmf in the slowpath case.
1525 1525 def lookupfilelog(x):
1526 1526 return linkrevnodes[x]
1527 1527
1528 1528 frev, flr = filerevlog.rev, filerevlog.linkrev
1529 1529 # Skip sending any filenode we know the client already
1530 1530 # has. This avoids over-sending files relatively
1531 1531 # inexpensively, so it's not a problem if we under-filter
1532 1532 # here.
1533 1533 filenodes = [
1534 1534 n for n in linkrevnodes if flr(frev(n)) not in commonrevs
1535 1535 ]
1536 1536
1537 1537 if not filenodes:
1538 1538 continue
1539 1539
1540 1540 progress.update(i + 1, item=fname)
1541 1541
1542 1542 deltas = deltagroup(
1543 1543 self._repo,
1544 1544 filerevlog,
1545 1545 filenodes,
1546 1546 False,
1547 1547 lookupfilelog,
1548 1548 self._forcedeltaparentprev,
1549 1549 ellipses=self._ellipses,
1550 1550 clrevtolocalrev=clrevtolocalrev,
1551 1551 fullclnodes=self._fullclnodes,
1552 1552 precomputedellipsis=self._precomputedellipsis,
1553 1553 sidedata_helpers=sidedata_helpers,
1554 1554 )
1555 1555
1556 1556 yield fname, deltas
1557 1557
1558 1558 progress.complete()
1559 1559
1560 1560
1561 1561 def _makecg1packer(
1562 1562 repo,
1563 1563 oldmatcher,
1564 1564 matcher,
1565 1565 bundlecaps,
1566 1566 ellipses=False,
1567 1567 shallow=False,
1568 1568 ellipsisroots=None,
1569 1569 fullnodes=None,
1570 1570 remote_sidedata=None,
1571 1571 ):
1572 1572 builddeltaheader = lambda d: _CHANGEGROUPV1_DELTA_HEADER.pack(
1573 1573 d.node, d.p1node, d.p2node, d.linknode
1574 1574 )
1575 1575
1576 1576 return cgpacker(
1577 1577 repo,
1578 1578 oldmatcher,
1579 1579 matcher,
1580 1580 b'01',
1581 1581 builddeltaheader=builddeltaheader,
1582 1582 manifestsend=b'',
1583 1583 forcedeltaparentprev=True,
1584 1584 bundlecaps=bundlecaps,
1585 1585 ellipses=ellipses,
1586 1586 shallow=shallow,
1587 1587 ellipsisroots=ellipsisroots,
1588 1588 fullnodes=fullnodes,
1589 1589 )
1590 1590
1591 1591
1592 1592 def _makecg2packer(
1593 1593 repo,
1594 1594 oldmatcher,
1595 1595 matcher,
1596 1596 bundlecaps,
1597 1597 ellipses=False,
1598 1598 shallow=False,
1599 1599 ellipsisroots=None,
1600 1600 fullnodes=None,
1601 1601 remote_sidedata=None,
1602 1602 ):
1603 1603 builddeltaheader = lambda d: _CHANGEGROUPV2_DELTA_HEADER.pack(
1604 1604 d.node, d.p1node, d.p2node, d.basenode, d.linknode
1605 1605 )
1606 1606
1607 1607 return cgpacker(
1608 1608 repo,
1609 1609 oldmatcher,
1610 1610 matcher,
1611 1611 b'02',
1612 1612 builddeltaheader=builddeltaheader,
1613 1613 manifestsend=b'',
1614 1614 bundlecaps=bundlecaps,
1615 1615 ellipses=ellipses,
1616 1616 shallow=shallow,
1617 1617 ellipsisroots=ellipsisroots,
1618 1618 fullnodes=fullnodes,
1619 1619 )
1620 1620
1621 1621
1622 1622 def _makecg3packer(
1623 1623 repo,
1624 1624 oldmatcher,
1625 1625 matcher,
1626 1626 bundlecaps,
1627 1627 ellipses=False,
1628 1628 shallow=False,
1629 1629 ellipsisroots=None,
1630 1630 fullnodes=None,
1631 1631 remote_sidedata=None,
1632 1632 ):
1633 1633 builddeltaheader = lambda d: _CHANGEGROUPV3_DELTA_HEADER.pack(
1634 1634 d.node, d.p1node, d.p2node, d.basenode, d.linknode, d.flags
1635 1635 )
1636 1636
1637 1637 return cgpacker(
1638 1638 repo,
1639 1639 oldmatcher,
1640 1640 matcher,
1641 1641 b'03',
1642 1642 builddeltaheader=builddeltaheader,
1643 1643 manifestsend=closechunk(),
1644 1644 bundlecaps=bundlecaps,
1645 1645 ellipses=ellipses,
1646 1646 shallow=shallow,
1647 1647 ellipsisroots=ellipsisroots,
1648 1648 fullnodes=fullnodes,
1649 1649 )
1650 1650
1651 1651
1652 1652 def _makecg4packer(
1653 1653 repo,
1654 1654 oldmatcher,
1655 1655 matcher,
1656 1656 bundlecaps,
1657 1657 ellipses=False,
1658 1658 shallow=False,
1659 1659 ellipsisroots=None,
1660 1660 fullnodes=None,
1661 1661 remote_sidedata=None,
1662 1662 ):
1663 1663 # Sidedata is in a separate chunk from the delta to differentiate
1664 1664 # "raw delta" and sidedata.
1665 1665 def builddeltaheader(d):
1666 1666 return _CHANGEGROUPV4_DELTA_HEADER.pack(
1667 1667 d.protocol_flags,
1668 1668 d.node,
1669 1669 d.p1node,
1670 1670 d.p2node,
1671 1671 d.basenode,
1672 1672 d.linknode,
1673 1673 d.flags,
1674 1674 )
1675 1675
1676 1676 return cgpacker(
1677 1677 repo,
1678 1678 oldmatcher,
1679 1679 matcher,
1680 1680 b'04',
1681 1681 builddeltaheader=builddeltaheader,
1682 1682 manifestsend=closechunk(),
1683 1683 bundlecaps=bundlecaps,
1684 1684 ellipses=ellipses,
1685 1685 shallow=shallow,
1686 1686 ellipsisroots=ellipsisroots,
1687 1687 fullnodes=fullnodes,
1688 1688 remote_sidedata=remote_sidedata,
1689 1689 )
1690 1690
1691 1691
1692 1692 _packermap = {
1693 1693 b'01': (_makecg1packer, cg1unpacker),
1694 1694 # cg2 adds support for exchanging generaldelta
1695 1695 b'02': (_makecg2packer, cg2unpacker),
1696 1696 # cg3 adds support for exchanging revlog flags and treemanifests
1697 1697 b'03': (_makecg3packer, cg3unpacker),
1698 1698 # ch4 adds support for exchanging sidedata
1699 1699 b'04': (_makecg4packer, cg4unpacker),
1700 1700 }
1701 1701
1702 1702
1703 1703 def allsupportedversions(repo):
1704 1704 versions = set(_packermap.keys())
1705 1705 needv03 = False
1706 1706 if (
1707 1707 repo.ui.configbool(b'experimental', b'changegroup3')
1708 1708 or repo.ui.configbool(b'experimental', b'treemanifest')
1709 1709 or scmutil.istreemanifest(repo)
1710 1710 ):
1711 1711 # we keep version 03 because we need to to exchange treemanifest data
1712 1712 #
1713 1713 # we also keep vresion 01 and 02, because it is possible for repo to
1714 1714 # contains both normal and tree manifest at the same time. so using
1715 1715 # older version to pull data is viable
1716 1716 #
1717 1717 # (or even to push subset of history)
1718 1718 needv03 = True
1719 1719 if not needv03:
1720 1720 versions.discard(b'03')
1721 1721 want_v4 = (
1722 1722 repo.ui.configbool(b'experimental', b'changegroup4')
1723 1723 or requirements.REVLOGV2_REQUIREMENT in repo.requirements
1724 1724 )
1725 1725 if not want_v4:
1726 1726 versions.discard(b'04')
1727 1727 return versions
1728 1728
1729 1729
1730 1730 # Changegroup versions that can be applied to the repo
1731 1731 def supportedincomingversions(repo):
1732 1732 return allsupportedversions(repo)
1733 1733
1734 1734
1735 1735 # Changegroup versions that can be created from the repo
1736 1736 def supportedoutgoingversions(repo):
1737 1737 versions = allsupportedversions(repo)
1738 1738 if scmutil.istreemanifest(repo):
1739 1739 # Versions 01 and 02 support only flat manifests and it's just too
1740 1740 # expensive to convert between the flat manifest and tree manifest on
1741 1741 # the fly. Since tree manifests are hashed differently, all of history
1742 1742 # would have to be converted. Instead, we simply don't even pretend to
1743 1743 # support versions 01 and 02.
1744 1744 versions.discard(b'01')
1745 1745 versions.discard(b'02')
1746 1746 if requirements.NARROW_REQUIREMENT in repo.requirements:
1747 1747 # Versions 01 and 02 don't support revlog flags, and we need to
1748 1748 # support that for stripping and unbundling to work.
1749 1749 versions.discard(b'01')
1750 1750 versions.discard(b'02')
1751 1751 if LFS_REQUIREMENT in repo.requirements:
1752 1752 # Versions 01 and 02 don't support revlog flags, and we need to
1753 1753 # mark LFS entries with REVIDX_EXTSTORED.
1754 1754 versions.discard(b'01')
1755 1755 versions.discard(b'02')
1756 1756
1757 1757 return versions
1758 1758
1759 1759
1760 1760 def localversion(repo):
1761 1761 # Finds the best version to use for bundles that are meant to be used
1762 1762 # locally, such as those from strip and shelve, and temporary bundles.
1763 1763 return max(supportedoutgoingversions(repo))
1764 1764
1765 1765
1766 1766 def safeversion(repo):
1767 1767 # Finds the smallest version that it's safe to assume clients of the repo
1768 1768 # will support. For example, all hg versions that support generaldelta also
1769 1769 # support changegroup 02.
1770 1770 versions = supportedoutgoingversions(repo)
1771 1771 if requirements.GENERALDELTA_REQUIREMENT in repo.requirements:
1772 1772 versions.discard(b'01')
1773 1773 assert versions
1774 1774 return min(versions)
1775 1775
1776 1776
1777 1777 def getbundler(
1778 1778 version,
1779 1779 repo,
1780 1780 bundlecaps=None,
1781 1781 oldmatcher=None,
1782 1782 matcher=None,
1783 1783 ellipses=False,
1784 1784 shallow=False,
1785 1785 ellipsisroots=None,
1786 1786 fullnodes=None,
1787 1787 remote_sidedata=None,
1788 1788 ):
1789 1789 assert version in supportedoutgoingversions(repo)
1790 1790
1791 1791 if matcher is None:
1792 1792 matcher = matchmod.always()
1793 1793 if oldmatcher is None:
1794 1794 oldmatcher = matchmod.never()
1795 1795
1796 1796 if version == b'01' and not matcher.always():
1797 1797 raise error.ProgrammingError(
1798 1798 b'version 01 changegroups do not support sparse file matchers'
1799 1799 )
1800 1800
1801 1801 if ellipses and version in (b'01', b'02'):
1802 1802 raise error.Abort(
1803 1803 _(
1804 1804 b'ellipsis nodes require at least cg3 on client and server, '
1805 1805 b'but negotiated version %s'
1806 1806 )
1807 1807 % version
1808 1808 )
1809 1809
1810 1810 # Requested files could include files not in the local store. So
1811 1811 # filter those out.
1812 1812 matcher = repo.narrowmatch(matcher)
1813 1813
1814 1814 fn = _packermap[version][0]
1815 1815 return fn(
1816 1816 repo,
1817 1817 oldmatcher,
1818 1818 matcher,
1819 1819 bundlecaps,
1820 1820 ellipses=ellipses,
1821 1821 shallow=shallow,
1822 1822 ellipsisroots=ellipsisroots,
1823 1823 fullnodes=fullnodes,
1824 1824 remote_sidedata=remote_sidedata,
1825 1825 )
1826 1826
1827 1827
1828 1828 def getunbundler(version, fh, alg, extras=None):
1829 1829 return _packermap[version][1](fh, alg, extras=extras)
1830 1830
1831 1831
1832 1832 def _changegroupinfo(repo, nodes, source):
1833 1833 if repo.ui.verbose or source == b'bundle':
1834 1834 repo.ui.status(_(b"%d changesets found\n") % len(nodes))
1835 1835 if repo.ui.debugflag:
1836 1836 repo.ui.debug(b"list of changesets:\n")
1837 1837 for node in nodes:
1838 1838 repo.ui.debug(b"%s\n" % hex(node))
1839 1839
1840 1840
1841 1841 def makechangegroup(
1842 1842 repo, outgoing, version, source, fastpath=False, bundlecaps=None
1843 1843 ):
1844 1844 cgstream = makestream(
1845 1845 repo,
1846 1846 outgoing,
1847 1847 version,
1848 1848 source,
1849 1849 fastpath=fastpath,
1850 1850 bundlecaps=bundlecaps,
1851 1851 )
1852 1852 return getunbundler(
1853 1853 version,
1854 1854 util.chunkbuffer(cgstream),
1855 1855 None,
1856 1856 {b'clcount': len(outgoing.missing)},
1857 1857 )
1858 1858
1859 1859
1860 1860 def makestream(
1861 1861 repo,
1862 1862 outgoing,
1863 1863 version,
1864 1864 source,
1865 1865 fastpath=False,
1866 1866 bundlecaps=None,
1867 1867 matcher=None,
1868 1868 remote_sidedata=None,
1869 1869 ):
1870 1870 bundler = getbundler(
1871 1871 version,
1872 1872 repo,
1873 1873 bundlecaps=bundlecaps,
1874 1874 matcher=matcher,
1875 1875 remote_sidedata=remote_sidedata,
1876 1876 )
1877 1877
1878 1878 repo = repo.unfiltered()
1879 1879 commonrevs = outgoing.common
1880 1880 csets = outgoing.missing
1881 1881 heads = outgoing.ancestorsof
1882 1882 # We go through the fast path if we get told to, or if all (unfiltered
1883 1883 # heads have been requested (since we then know there all linkrevs will
1884 1884 # be pulled by the client).
1885 1885 heads.sort()
1886 1886 fastpathlinkrev = fastpath or (
1887 1887 repo.filtername is None and heads == sorted(repo.heads())
1888 1888 )
1889 1889
1890 1890 repo.hook(b'preoutgoing', throw=True, source=source)
1891 1891 _changegroupinfo(repo, csets, source)
1892 1892 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1893 1893
1894 1894
1895 1895 def _addchangegroupfiles(
1896 1896 repo,
1897 1897 source,
1898 1898 revmap,
1899 1899 trp,
1900 1900 expectedfiles,
1901 1901 needfiles,
1902 1902 addrevisioncb=None,
1903 1903 ):
1904 1904 revisions = 0
1905 1905 files = 0
1906 1906 progress = repo.ui.makeprogress(
1907 1907 _(b'files'), unit=_(b'files'), total=expectedfiles
1908 1908 )
1909 1909 for chunkdata in iter(source.filelogheader, {}):
1910 1910 files += 1
1911 1911 f = chunkdata[b"filename"]
1912 1912 repo.ui.debug(b"adding %s revisions\n" % f)
1913 1913 progress.increment()
1914 1914 fl = repo.file(f)
1915 1915 o = len(fl)
1916 1916 try:
1917 1917 deltas = source.deltaiter()
1918 1918 added = fl.addgroup(
1919 1919 deltas,
1920 1920 revmap,
1921 1921 trp,
1922 1922 addrevisioncb=addrevisioncb,
1923 1923 )
1924 1924 if not added:
1925 1925 raise error.Abort(_(b"received file revlog group is empty"))
1926 1926 except error.CensoredBaseError as e:
1927 1927 raise error.Abort(_(b"received delta base is censored: %s") % e)
1928 1928 revisions += len(fl) - o
1929 1929 if f in needfiles:
1930 1930 needs = needfiles[f]
1931 1931 for new in pycompat.xrange(o, len(fl)):
1932 1932 n = fl.node(new)
1933 1933 if n in needs:
1934 1934 needs.remove(n)
1935 1935 else:
1936 1936 raise error.Abort(_(b"received spurious file revlog entry"))
1937 1937 if not needs:
1938 1938 del needfiles[f]
1939 1939 progress.complete()
1940 1940
1941 1941 for f, needs in pycompat.iteritems(needfiles):
1942 1942 fl = repo.file(f)
1943 1943 for n in needs:
1944 1944 try:
1945 1945 fl.rev(n)
1946 1946 except error.LookupError:
1947 1947 raise error.Abort(
1948 1948 _(b'missing file data for %s:%s - run hg verify')
1949 1949 % (f, hex(n))
1950 1950 )
1951 1951
1952 1952 return revisions, files
@@ -1,154 +1,154 b''
1 1 $ . "$TESTDIR/narrow-library.sh"
2 2
3 3 $ hg init master
4 4 $ cd master
5 5 $ cat >> .hg/hgrc <<EOF
6 6 > [narrow]
7 7 > serveellipses=True
8 8 > EOF
9 9 $ for x in `$TESTDIR/seq.py 10`
10 10 > do
11 11 > echo $x > "f$x"
12 12 > hg add "f$x"
13 13 > hg commit -m "Commit f$x"
14 14 > done
15 15 $ cd ..
16 16
17 17 narrow clone a couple files, f2 and f8
18 18
19 19 $ hg clone --narrow ssh://user@dummy/master narrow --include "f2" --include "f8"
20 20 requesting all changes
21 21 adding changesets
22 22 adding manifests
23 23 adding file changes
24 24 added 5 changesets with 2 changes to 2 files
25 25 new changesets *:* (glob)
26 26 updating to branch default
27 27 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 28 $ cd narrow
29 29 $ ls -A
30 30 .hg
31 31 f2
32 32 f8
33 33 $ cat f2 f8
34 34 2
35 35 8
36 36
37 37 $ cd ..
38 38
39 39 change every upstream file twice
40 40
41 41 $ cd master
42 42 $ for x in `$TESTDIR/seq.py 10`
43 43 > do
44 44 > echo "update#1 $x" >> "f$x"
45 45 > hg commit -m "Update#1 to f$x" "f$x"
46 46 > done
47 47 $ for x in `$TESTDIR/seq.py 10`
48 48 > do
49 49 > echo "update#2 $x" >> "f$x"
50 50 > hg commit -m "Update#2 to f$x" "f$x"
51 51 > done
52 52 $ cd ..
53 53
54 54 look for incoming changes
55 55
56 56 $ cd narrow
57 57 $ hg incoming --limit 3
58 58 comparing with ssh://user@dummy/master
59 59 searching for changes
60 60 changeset: 5:ddc055582556
61 61 user: test
62 62 date: Thu Jan 01 00:00:00 1970 +0000
63 63 summary: Update#1 to f1
64 64
65 65 changeset: 6:f66eb5ad621d
66 66 user: test
67 67 date: Thu Jan 01 00:00:00 1970 +0000
68 68 summary: Update#1 to f2
69 69
70 70 changeset: 7:c42ecff04e99
71 71 user: test
72 72 date: Thu Jan 01 00:00:00 1970 +0000
73 73 summary: Update#1 to f3
74 74
75 75
76 76 Interrupting the pull is safe
77 77 $ hg --config hooks.pretxnchangegroup.bad=false pull -q
78 78 transaction abort!
79 79 rollback completed
80 80 abort: pretxnchangegroup.bad hook exited with status 1
81 81 [40]
82 82 $ hg id
83 83 223311e70a6f tip
84 84
85 85 pull new changes down to the narrow clone. Should get 8 new changesets: 4
86 86 relevant to the narrow spec, and 4 ellipsis nodes gluing them all together.
87 87
88 88 $ hg pull
89 89 pulling from ssh://user@dummy/master
90 90 searching for changes
91 91 adding changesets
92 92 adding manifests
93 93 adding file changes
94 94 added 9 changesets with 4 changes to 2 files
95 95 new changesets *:* (glob)
96 96 (run 'hg update' to get a working copy)
97 97 $ hg log -T '{rev}: {desc}\n'
98 98 13: Update#2 to f10
99 99 12: Update#2 to f8
100 100 11: Update#2 to f7
101 101 10: Update#2 to f2
102 102 9: Update#2 to f1
103 103 8: Update#1 to f8
104 104 7: Update#1 to f7
105 105 6: Update#1 to f2
106 106 5: Update#1 to f1
107 107 4: Commit f10
108 108 3: Commit f8
109 109 2: Commit f7
110 110 1: Commit f2
111 111 0: Commit f1
112 112 $ hg update tip
113 113 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
114 114
115 115 add a change and push it
116 116
117 117 $ echo "update#3 2" >> f2
118 118 $ hg commit -m "Update#3 to f2" f2
119 119 $ hg log f2 -T '{rev}: {desc}\n'
120 120 14: Update#3 to f2
121 121 10: Update#2 to f2
122 122 6: Update#1 to f2
123 123 1: Commit f2
124 124 $ hg push
125 125 pushing to ssh://user@dummy/master
126 126 searching for changes
127 127 remote: adding changesets
128 128 remote: adding manifests
129 129 remote: adding file changes
130 130 remote: added 1 changesets with 1 changes to 1 files
131 131 $ cd ..
132 132
133 133 $ cd master
134 134 $ hg log f2 -T '{rev}: {desc}\n'
135 135 30: Update#3 to f2
136 136 21: Update#2 to f2
137 137 11: Update#1 to f2
138 138 1: Commit f2
139 139 $ hg log -l 3 -T '{rev}: {desc}\n'
140 140 30: Update#3 to f2
141 141 29: Update#2 to f10
142 142 28: Update#2 to f9
143 143
144 144 Can pull into repo with a single commit
145 145
146 146 $ cd ..
147 147 $ hg clone -q --narrow ssh://user@dummy/master narrow2 --include "f1" -r 0
148 148 $ cd narrow2
149 149 $ hg pull -q -r 1
150 remote: abort: unexpected error: unable to resolve parent while packing '00manifest.i' 1 for changeset 0
150 remote: abort: unexpected error: unable to resolve parent while packing '00manifest' 1 for changeset 0
151 151 transaction abort!
152 152 rollback completed
153 153 abort: pull failed on remote
154 154 [100]
General Comments 0
You need to be logged in to leave comments. Login now