##// END OF EJS Templates
changegroup: reformat packermap and add comment...
Augie Fackler -
r26709:42733e95 default
parent child Browse files
Show More
@@ -1,937 +1,939 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 tempfile
13 13 import weakref
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 nullrev,
20 20 short,
21 21 )
22 22
23 23 from . import (
24 24 branchmap,
25 25 dagutil,
26 26 discovery,
27 27 error,
28 28 mdiff,
29 29 phases,
30 30 util,
31 31 )
32 32
33 33 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
34 34 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
35 35
36 36 def readexactly(stream, n):
37 37 '''read n bytes from stream.read and abort if less was available'''
38 38 s = stream.read(n)
39 39 if len(s) < n:
40 40 raise error.Abort(_("stream ended unexpectedly"
41 41 " (got %d bytes, expected %d)")
42 42 % (len(s), n))
43 43 return s
44 44
45 45 def getchunk(stream):
46 46 """return the next chunk from stream as a string"""
47 47 d = readexactly(stream, 4)
48 48 l = struct.unpack(">l", d)[0]
49 49 if l <= 4:
50 50 if l:
51 51 raise error.Abort(_("invalid chunk length %d") % l)
52 52 return ""
53 53 return readexactly(stream, l - 4)
54 54
55 55 def chunkheader(length):
56 56 """return a changegroup chunk header (string)"""
57 57 return struct.pack(">l", length + 4)
58 58
59 59 def closechunk():
60 60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 61 return struct.pack(">l", 0)
62 62
63 63 def combineresults(results):
64 64 """logic to combine 0 or more addchangegroup results into one"""
65 65 changedheads = 0
66 66 result = 1
67 67 for ret in results:
68 68 # If any changegroup result is 0, return 0
69 69 if ret == 0:
70 70 result = 0
71 71 break
72 72 if ret < -1:
73 73 changedheads += ret + 1
74 74 elif ret > 1:
75 75 changedheads += ret - 1
76 76 if changedheads > 0:
77 77 result = 1 + changedheads
78 78 elif changedheads < 0:
79 79 result = -1 + changedheads
80 80 return result
81 81
82 82 bundletypes = {
83 83 "": ("", None), # only when using unbundle on ssh and old http servers
84 84 # since the unification ssh accepts a header but there
85 85 # is no capability signaling it.
86 86 "HG20": (), # special-cased below
87 87 "HG10UN": ("HG10UN", None),
88 88 "HG10BZ": ("HG10", 'BZ'),
89 89 "HG10GZ": ("HG10GZ", 'GZ'),
90 90 }
91 91
92 92 # hgweb uses this list to communicate its preferred type
93 93 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
94 94
95 95 def writechunks(ui, chunks, filename, vfs=None):
96 96 """Write chunks to a file and return its filename.
97 97
98 98 The stream is assumed to be a bundle file.
99 99 Existing files will not be overwritten.
100 100 If no filename is specified, a temporary file is created.
101 101 """
102 102 fh = None
103 103 cleanup = None
104 104 try:
105 105 if filename:
106 106 if vfs:
107 107 fh = vfs.open(filename, "wb")
108 108 else:
109 109 fh = open(filename, "wb")
110 110 else:
111 111 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
112 112 fh = os.fdopen(fd, "wb")
113 113 cleanup = filename
114 114 for c in chunks:
115 115 fh.write(c)
116 116 cleanup = None
117 117 return filename
118 118 finally:
119 119 if fh is not None:
120 120 fh.close()
121 121 if cleanup is not None:
122 122 if filename and vfs:
123 123 vfs.unlink(cleanup)
124 124 else:
125 125 os.unlink(cleanup)
126 126
127 127 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None):
128 128 """Write a bundle file and return its filename.
129 129
130 130 Existing files will not be overwritten.
131 131 If no filename is specified, a temporary file is created.
132 132 bz2 compression can be turned off.
133 133 The bundle file will be deleted in case of errors.
134 134 """
135 135
136 136 if bundletype == "HG20":
137 137 from . import bundle2
138 138 bundle = bundle2.bundle20(ui)
139 139 bundle.setcompression(compression)
140 140 part = bundle.newpart('changegroup', data=cg.getchunks())
141 141 part.addparam('version', cg.version)
142 142 chunkiter = bundle.getchunks()
143 143 else:
144 144 # compression argument is only for the bundle2 case
145 145 assert compression is None
146 146 if cg.version != '01':
147 147 raise error.Abort(_('old bundle types only supports v1 '
148 148 'changegroups'))
149 149 header, comp = bundletypes[bundletype]
150 150 if comp not in util.compressors:
151 151 raise error.Abort(_('unknown stream compression type: %s')
152 152 % comp)
153 153 z = util.compressors[comp]()
154 154 subchunkiter = cg.getchunks()
155 155 def chunkiter():
156 156 yield header
157 157 for chunk in subchunkiter:
158 158 yield z.compress(chunk)
159 159 yield z.flush()
160 160 chunkiter = chunkiter()
161 161
162 162 # parse the changegroup data, otherwise we will block
163 163 # in case of sshrepo because we don't know the end of the stream
164 164
165 165 # an empty chunkgroup is the end of the changegroup
166 166 # a changegroup has at least 2 chunkgroups (changelog and manifest).
167 167 # after that, an empty chunkgroup is the end of the changegroup
168 168 return writechunks(ui, chunkiter, filename, vfs=vfs)
169 169
170 170 class cg1unpacker(object):
171 171 """Unpacker for cg1 changegroup streams.
172 172
173 173 A changegroup unpacker handles the framing of the revision data in
174 174 the wire format. Most consumers will want to use the apply()
175 175 method to add the changes from the changegroup to a repository.
176 176
177 177 If you're forwarding a changegroup unmodified to another consumer,
178 178 use getchunks(), which returns an iterator of changegroup
179 179 chunks. This is mostly useful for cases where you need to know the
180 180 data stream has ended by observing the end of the changegroup.
181 181
182 182 deltachunk() is useful only if you're applying delta data. Most
183 183 consumers should prefer apply() instead.
184 184
185 185 A few other public methods exist. Those are used only for
186 186 bundlerepo and some debug commands - their use is discouraged.
187 187 """
188 188 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
189 189 deltaheadersize = struct.calcsize(deltaheader)
190 190 version = '01'
191 191 def __init__(self, fh, alg):
192 192 if alg == 'UN':
193 193 alg = None # get more modern without breaking too much
194 194 if not alg in util.decompressors:
195 195 raise error.Abort(_('unknown stream compression type: %s')
196 196 % alg)
197 197 if alg == 'BZ':
198 198 alg = '_truncatedBZ'
199 199 self._stream = util.decompressors[alg](fh)
200 200 self._type = alg
201 201 self.callback = None
202 202
203 203 # These methods (compressed, read, seek, tell) all appear to only
204 204 # be used by bundlerepo, but it's a little hard to tell.
205 205 def compressed(self):
206 206 return self._type is not None
207 207 def read(self, l):
208 208 return self._stream.read(l)
209 209 def seek(self, pos):
210 210 return self._stream.seek(pos)
211 211 def tell(self):
212 212 return self._stream.tell()
213 213 def close(self):
214 214 return self._stream.close()
215 215
216 216 def _chunklength(self):
217 217 d = readexactly(self._stream, 4)
218 218 l = struct.unpack(">l", d)[0]
219 219 if l <= 4:
220 220 if l:
221 221 raise error.Abort(_("invalid chunk length %d") % l)
222 222 return 0
223 223 if self.callback:
224 224 self.callback()
225 225 return l - 4
226 226
227 227 def changelogheader(self):
228 228 """v10 does not have a changelog header chunk"""
229 229 return {}
230 230
231 231 def manifestheader(self):
232 232 """v10 does not have a manifest header chunk"""
233 233 return {}
234 234
235 235 def filelogheader(self):
236 236 """return the header of the filelogs chunk, v10 only has the filename"""
237 237 l = self._chunklength()
238 238 if not l:
239 239 return {}
240 240 fname = readexactly(self._stream, l)
241 241 return {'filename': fname}
242 242
243 243 def _deltaheader(self, headertuple, prevnode):
244 244 node, p1, p2, cs = headertuple
245 245 if prevnode is None:
246 246 deltabase = p1
247 247 else:
248 248 deltabase = prevnode
249 249 return node, p1, p2, deltabase, cs
250 250
251 251 def deltachunk(self, prevnode):
252 252 l = self._chunklength()
253 253 if not l:
254 254 return {}
255 255 headerdata = readexactly(self._stream, self.deltaheadersize)
256 256 header = struct.unpack(self.deltaheader, headerdata)
257 257 delta = readexactly(self._stream, l - self.deltaheadersize)
258 258 node, p1, p2, deltabase, cs = self._deltaheader(header, prevnode)
259 259 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
260 260 'deltabase': deltabase, 'delta': delta}
261 261
262 262 def getchunks(self):
263 263 """returns all the chunks contains in the bundle
264 264
265 265 Used when you need to forward the binary stream to a file or another
266 266 network API. To do so, it parse the changegroup data, otherwise it will
267 267 block in case of sshrepo because it don't know the end of the stream.
268 268 """
269 269 # an empty chunkgroup is the end of the changegroup
270 270 # a changegroup has at least 2 chunkgroups (changelog and manifest).
271 271 # after that, an empty chunkgroup is the end of the changegroup
272 272 empty = False
273 273 count = 0
274 274 while not empty or count <= 2:
275 275 empty = True
276 276 count += 1
277 277 while True:
278 278 chunk = getchunk(self)
279 279 if not chunk:
280 280 break
281 281 empty = False
282 282 yield chunkheader(len(chunk))
283 283 pos = 0
284 284 while pos < len(chunk):
285 285 next = pos + 2**20
286 286 yield chunk[pos:next]
287 287 pos = next
288 288 yield closechunk()
289 289
290 290 def apply(self, repo, srctype, url, emptyok=False,
291 291 targetphase=phases.draft, expectedtotal=None):
292 292 """Add the changegroup returned by source.read() to this repo.
293 293 srctype is a string like 'push', 'pull', or 'unbundle'. url is
294 294 the URL of the repo where this changegroup is coming from.
295 295
296 296 Return an integer summarizing the change to this repo:
297 297 - nothing changed or no source: 0
298 298 - more heads than before: 1+added heads (2..n)
299 299 - fewer heads than before: -1-removed heads (-2..-n)
300 300 - number of heads stays the same: 1
301 301 """
302 302 repo = repo.unfiltered()
303 303 def csmap(x):
304 304 repo.ui.debug("add changeset %s\n" % short(x))
305 305 return len(cl)
306 306
307 307 def revmap(x):
308 308 return cl.rev(x)
309 309
310 310 changesets = files = revisions = 0
311 311
312 312 tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
313 313 # The transaction could have been created before and already
314 314 # carries source information. In this case we use the top
315 315 # level data. We overwrite the argument because we need to use
316 316 # the top level value (if they exist) in this function.
317 317 srctype = tr.hookargs.setdefault('source', srctype)
318 318 url = tr.hookargs.setdefault('url', url)
319 319
320 320 # write changelog data to temp files so concurrent readers will not see
321 321 # inconsistent view
322 322 cl = repo.changelog
323 323 cl.delayupdate(tr)
324 324 oldheads = cl.heads()
325 325 try:
326 326 repo.hook('prechangegroup', throw=True, **tr.hookargs)
327 327
328 328 trp = weakref.proxy(tr)
329 329 # pull off the changeset group
330 330 repo.ui.status(_("adding changesets\n"))
331 331 clstart = len(cl)
332 332 class prog(object):
333 333 def __init__(self, step, total):
334 334 self._step = step
335 335 self._total = total
336 336 self._count = 1
337 337 def __call__(self):
338 338 repo.ui.progress(self._step, self._count, unit=_('chunks'),
339 339 total=self._total)
340 340 self._count += 1
341 341 self.callback = prog(_('changesets'), expectedtotal)
342 342
343 343 efiles = set()
344 344 def onchangelog(cl, node):
345 345 efiles.update(cl.read(node)[3])
346 346
347 347 self.changelogheader()
348 348 srccontent = cl.addgroup(self, csmap, trp,
349 349 addrevisioncb=onchangelog)
350 350 efiles = len(efiles)
351 351
352 352 if not (srccontent or emptyok):
353 353 raise error.Abort(_("received changelog group is empty"))
354 354 clend = len(cl)
355 355 changesets = clend - clstart
356 356 repo.ui.progress(_('changesets'), None)
357 357
358 358 # pull off the manifest group
359 359 repo.ui.status(_("adding manifests\n"))
360 360 # manifests <= changesets
361 361 self.callback = prog(_('manifests'), changesets)
362 362 # no need to check for empty manifest group here:
363 363 # if the result of the merge of 1 and 2 is the same in 3 and 4,
364 364 # no new manifest will be created and the manifest group will
365 365 # be empty during the pull
366 366 self.manifestheader()
367 367 repo.manifest.addgroup(self, revmap, trp)
368 368 repo.ui.progress(_('manifests'), None)
369 369
370 370 needfiles = {}
371 371 if repo.ui.configbool('server', 'validate', default=False):
372 372 # validate incoming csets have their manifests
373 373 for cset in xrange(clstart, clend):
374 374 mfnode = repo.changelog.read(repo.changelog.node(cset))[0]
375 375 mfest = repo.manifest.readdelta(mfnode)
376 376 # store file nodes we must see
377 377 for f, n in mfest.iteritems():
378 378 needfiles.setdefault(f, set()).add(n)
379 379
380 380 # process the files
381 381 repo.ui.status(_("adding file changes\n"))
382 382 self.callback = None
383 383 pr = prog(_('files'), efiles)
384 384 newrevs, newfiles = _addchangegroupfiles(
385 385 repo, self, revmap, trp, pr, needfiles)
386 386 revisions += newrevs
387 387 files += newfiles
388 388
389 389 dh = 0
390 390 if oldheads:
391 391 heads = cl.heads()
392 392 dh = len(heads) - len(oldheads)
393 393 for h in heads:
394 394 if h not in oldheads and repo[h].closesbranch():
395 395 dh -= 1
396 396 htext = ""
397 397 if dh:
398 398 htext = _(" (%+d heads)") % dh
399 399
400 400 repo.ui.status(_("added %d changesets"
401 401 " with %d changes to %d files%s\n")
402 402 % (changesets, revisions, files, htext))
403 403 repo.invalidatevolatilesets()
404 404
405 405 if changesets > 0:
406 406 p = lambda: tr.writepending() and repo.root or ""
407 407 if 'node' not in tr.hookargs:
408 408 tr.hookargs['node'] = hex(cl.node(clstart))
409 409 hookargs = dict(tr.hookargs)
410 410 else:
411 411 hookargs = dict(tr.hookargs)
412 412 hookargs['node'] = hex(cl.node(clstart))
413 413 repo.hook('pretxnchangegroup', throw=True, pending=p,
414 414 **hookargs)
415 415
416 416 added = [cl.node(r) for r in xrange(clstart, clend)]
417 417 publishing = repo.publishing()
418 418 if srctype in ('push', 'serve'):
419 419 # Old servers can not push the boundary themselves.
420 420 # New servers won't push the boundary if changeset already
421 421 # exists locally as secret
422 422 #
423 423 # We should not use added here but the list of all change in
424 424 # the bundle
425 425 if publishing:
426 426 phases.advanceboundary(repo, tr, phases.public, srccontent)
427 427 else:
428 428 # Those changesets have been pushed from the outside, their
429 429 # phases are going to be pushed alongside. Therefor
430 430 # `targetphase` is ignored.
431 431 phases.advanceboundary(repo, tr, phases.draft, srccontent)
432 432 phases.retractboundary(repo, tr, phases.draft, added)
433 433 elif srctype != 'strip':
434 434 # publishing only alter behavior during push
435 435 #
436 436 # strip should not touch boundary at all
437 437 phases.retractboundary(repo, tr, targetphase, added)
438 438
439 439 if changesets > 0:
440 440 if srctype != 'strip':
441 441 # During strip, branchcache is invalid but coming call to
442 442 # `destroyed` will repair it.
443 443 # In other case we can safely update cache on disk.
444 444 branchmap.updatecache(repo.filtered('served'))
445 445
446 446 def runhooks():
447 447 # These hooks run when the lock releases, not when the
448 448 # transaction closes. So it's possible for the changelog
449 449 # to have changed since we last saw it.
450 450 if clstart >= len(repo):
451 451 return
452 452
453 453 # forcefully update the on-disk branch cache
454 454 repo.ui.debug("updating the branch cache\n")
455 455 repo.hook("changegroup", **hookargs)
456 456
457 457 for n in added:
458 458 args = hookargs.copy()
459 459 args['node'] = hex(n)
460 460 repo.hook("incoming", **args)
461 461
462 462 newheads = [h for h in repo.heads() if h not in oldheads]
463 463 repo.ui.log("incoming",
464 464 "%s incoming changes - new heads: %s\n",
465 465 len(added),
466 466 ', '.join([hex(c[:6]) for c in newheads]))
467 467
468 468 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
469 469 lambda tr: repo._afterlock(runhooks))
470 470
471 471 tr.close()
472 472
473 473 finally:
474 474 tr.release()
475 475 repo.ui.flush()
476 476 # never return 0 here:
477 477 if dh < 0:
478 478 return dh - 1
479 479 else:
480 480 return dh + 1
481 481
482 482 class cg2unpacker(cg1unpacker):
483 483 """Unpacker for cg2 streams.
484 484
485 485 cg2 streams add support for generaldelta, so the delta header
486 486 format is slightly different. All other features about the data
487 487 remain the same.
488 488 """
489 489 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
490 490 deltaheadersize = struct.calcsize(deltaheader)
491 491 version = '02'
492 492
493 493 def _deltaheader(self, headertuple, prevnode):
494 494 node, p1, p2, deltabase, cs = headertuple
495 495 return node, p1, p2, deltabase, cs
496 496
497 497 class headerlessfixup(object):
498 498 def __init__(self, fh, h):
499 499 self._h = h
500 500 self._fh = fh
501 501 def read(self, n):
502 502 if self._h:
503 503 d, self._h = self._h[:n], self._h[n:]
504 504 if len(d) < n:
505 505 d += readexactly(self._fh, n - len(d))
506 506 return d
507 507 return readexactly(self._fh, n)
508 508
509 509 class cg1packer(object):
510 510 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
511 511 version = '01'
512 512 def __init__(self, repo, bundlecaps=None):
513 513 """Given a source repo, construct a bundler.
514 514
515 515 bundlecaps is optional and can be used to specify the set of
516 516 capabilities which can be used to build the bundle.
517 517 """
518 518 # Set of capabilities we can use to build the bundle.
519 519 if bundlecaps is None:
520 520 bundlecaps = set()
521 521 self._bundlecaps = bundlecaps
522 522 # experimental config: bundle.reorder
523 523 reorder = repo.ui.config('bundle', 'reorder', 'auto')
524 524 if reorder == 'auto':
525 525 reorder = None
526 526 else:
527 527 reorder = util.parsebool(reorder)
528 528 self._repo = repo
529 529 self._reorder = reorder
530 530 self._progress = repo.ui.progress
531 531 if self._repo.ui.verbose and not self._repo.ui.debugflag:
532 532 self._verbosenote = self._repo.ui.note
533 533 else:
534 534 self._verbosenote = lambda s: None
535 535
536 536 def close(self):
537 537 return closechunk()
538 538
539 539 def fileheader(self, fname):
540 540 return chunkheader(len(fname)) + fname
541 541
542 542 def group(self, nodelist, revlog, lookup, units=None):
543 543 """Calculate a delta group, yielding a sequence of changegroup chunks
544 544 (strings).
545 545
546 546 Given a list of changeset revs, return a set of deltas and
547 547 metadata corresponding to nodes. The first delta is
548 548 first parent(nodelist[0]) -> nodelist[0], the receiver is
549 549 guaranteed to have this parent as it has all history before
550 550 these changesets. In the case firstparent is nullrev the
551 551 changegroup starts with a full revision.
552 552
553 553 If units is not None, progress detail will be generated, units specifies
554 554 the type of revlog that is touched (changelog, manifest, etc.).
555 555 """
556 556 # if we don't have any revisions touched by these changesets, bail
557 557 if len(nodelist) == 0:
558 558 yield self.close()
559 559 return
560 560
561 561 # for generaldelta revlogs, we linearize the revs; this will both be
562 562 # much quicker and generate a much smaller bundle
563 563 if (revlog._generaldelta and self._reorder is None) or self._reorder:
564 564 dag = dagutil.revlogdag(revlog)
565 565 revs = set(revlog.rev(n) for n in nodelist)
566 566 revs = dag.linearize(revs)
567 567 else:
568 568 revs = sorted([revlog.rev(n) for n in nodelist])
569 569
570 570 # add the parent of the first rev
571 571 p = revlog.parentrevs(revs[0])[0]
572 572 revs.insert(0, p)
573 573
574 574 # build deltas
575 575 total = len(revs) - 1
576 576 msgbundling = _('bundling')
577 577 for r in xrange(len(revs) - 1):
578 578 if units is not None:
579 579 self._progress(msgbundling, r + 1, unit=units, total=total)
580 580 prev, curr = revs[r], revs[r + 1]
581 581 linknode = lookup(revlog.node(curr))
582 582 for c in self.revchunk(revlog, curr, prev, linknode):
583 583 yield c
584 584
585 585 if units is not None:
586 586 self._progress(msgbundling, None)
587 587 yield self.close()
588 588
589 589 # filter any nodes that claim to be part of the known set
590 590 def prune(self, revlog, missing, commonrevs):
591 591 rr, rl = revlog.rev, revlog.linkrev
592 592 return [n for n in missing if rl(rr(n)) not in commonrevs]
593 593
594 594 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
595 595 '''yield a sequence of changegroup chunks (strings)'''
596 596 repo = self._repo
597 597 cl = repo.changelog
598 598 ml = repo.manifest
599 599
600 600 clrevorder = {}
601 601 mfs = {} # needed manifests
602 602 fnodes = {} # needed file nodes
603 603 changedfiles = set()
604 604
605 605 # Callback for the changelog, used to collect changed files and manifest
606 606 # nodes.
607 607 # Returns the linkrev node (identity in the changelog case).
608 608 def lookupcl(x):
609 609 c = cl.read(x)
610 610 clrevorder[x] = len(clrevorder)
611 611 changedfiles.update(c[3])
612 612 # record the first changeset introducing this manifest version
613 613 mfs.setdefault(c[0], x)
614 614 return x
615 615
616 616 self._verbosenote(_('uncompressed size of bundle content:\n'))
617 617 size = 0
618 618 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
619 619 size += len(chunk)
620 620 yield chunk
621 621 self._verbosenote(_('%8.i (changelog)\n') % size)
622 622
623 623 # We need to make sure that the linkrev in the changegroup refers to
624 624 # the first changeset that introduced the manifest or file revision.
625 625 # The fastpath is usually safer than the slowpath, because the filelogs
626 626 # are walked in revlog order.
627 627 #
628 628 # When taking the slowpath with reorder=None and the manifest revlog
629 629 # uses generaldelta, the manifest may be walked in the "wrong" order.
630 630 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
631 631 # cc0ff93d0c0c).
632 632 #
633 633 # When taking the fastpath, we are only vulnerable to reordering
634 634 # of the changelog itself. The changelog never uses generaldelta, so
635 635 # it is only reordered when reorder=True. To handle this case, we
636 636 # simply take the slowpath, which already has the 'clrevorder' logic.
637 637 # This was also fixed in cc0ff93d0c0c.
638 638 fastpathlinkrev = fastpathlinkrev and not self._reorder
639 639 # Callback for the manifest, used to collect linkrevs for filelog
640 640 # revisions.
641 641 # Returns the linkrev node (collected in lookupcl).
642 642 def lookupmf(x):
643 643 clnode = mfs[x]
644 644 if not fastpathlinkrev:
645 645 mdata = ml.readfast(x)
646 646 for f, n in mdata.iteritems():
647 647 if f in changedfiles:
648 648 # record the first changeset introducing this filelog
649 649 # version
650 650 fclnodes = fnodes.setdefault(f, {})
651 651 fclnode = fclnodes.setdefault(n, clnode)
652 652 if clrevorder[clnode] < clrevorder[fclnode]:
653 653 fclnodes[n] = clnode
654 654 return clnode
655 655
656 656 mfnodes = self.prune(ml, mfs, commonrevs)
657 657 size = 0
658 658 for chunk in self.group(mfnodes, ml, lookupmf, units=_('manifests')):
659 659 size += len(chunk)
660 660 yield chunk
661 661 self._verbosenote(_('%8.i (manifests)\n') % size)
662 662
663 663 mfs.clear()
664 664 clrevs = set(cl.rev(x) for x in clnodes)
665 665
666 666 def linknodes(filerevlog, fname):
667 667 if fastpathlinkrev:
668 668 llr = filerevlog.linkrev
669 669 def genfilenodes():
670 670 for r in filerevlog:
671 671 linkrev = llr(r)
672 672 if linkrev in clrevs:
673 673 yield filerevlog.node(r), cl.node(linkrev)
674 674 return dict(genfilenodes())
675 675 return fnodes.get(fname, {})
676 676
677 677 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
678 678 source):
679 679 yield chunk
680 680
681 681 yield self.close()
682 682
683 683 if clnodes:
684 684 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
685 685
686 686 # The 'source' parameter is useful for extensions
687 687 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
688 688 repo = self._repo
689 689 progress = self._progress
690 690 msgbundling = _('bundling')
691 691
692 692 total = len(changedfiles)
693 693 # for progress output
694 694 msgfiles = _('files')
695 695 for i, fname in enumerate(sorted(changedfiles)):
696 696 filerevlog = repo.file(fname)
697 697 if not filerevlog:
698 698 raise error.Abort(_("empty or missing revlog for %s") % fname)
699 699
700 700 linkrevnodes = linknodes(filerevlog, fname)
701 701 # Lookup for filenodes, we collected the linkrev nodes above in the
702 702 # fastpath case and with lookupmf in the slowpath case.
703 703 def lookupfilelog(x):
704 704 return linkrevnodes[x]
705 705
706 706 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
707 707 if filenodes:
708 708 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
709 709 total=total)
710 710 h = self.fileheader(fname)
711 711 size = len(h)
712 712 yield h
713 713 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
714 714 size += len(chunk)
715 715 yield chunk
716 716 self._verbosenote(_('%8.i %s\n') % (size, fname))
717 717 progress(msgbundling, None)
718 718
719 719 def deltaparent(self, revlog, rev, p1, p2, prev):
720 720 return prev
721 721
722 722 def revchunk(self, revlog, rev, prev, linknode):
723 723 node = revlog.node(rev)
724 724 p1, p2 = revlog.parentrevs(rev)
725 725 base = self.deltaparent(revlog, rev, p1, p2, prev)
726 726
727 727 prefix = ''
728 728 if revlog.iscensored(base) or revlog.iscensored(rev):
729 729 try:
730 730 delta = revlog.revision(node)
731 731 except error.CensoredNodeError as e:
732 732 delta = e.tombstone
733 733 if base == nullrev:
734 734 prefix = mdiff.trivialdiffheader(len(delta))
735 735 else:
736 736 baselen = revlog.rawsize(base)
737 737 prefix = mdiff.replacediffheader(baselen, len(delta))
738 738 elif base == nullrev:
739 739 delta = revlog.revision(node)
740 740 prefix = mdiff.trivialdiffheader(len(delta))
741 741 else:
742 742 delta = revlog.revdiff(base, rev)
743 743 p1n, p2n = revlog.parents(node)
744 744 basenode = revlog.node(base)
745 745 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode)
746 746 meta += prefix
747 747 l = len(meta) + len(delta)
748 748 yield chunkheader(l)
749 749 yield meta
750 750 yield delta
751 751 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
752 752 # do nothing with basenode, it is implicitly the previous one in HG10
753 753 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
754 754
755 755 class cg2packer(cg1packer):
756 756 version = '02'
757 757 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
758 758
759 759 def __init__(self, repo, bundlecaps=None):
760 760 super(cg2packer, self).__init__(repo, bundlecaps)
761 761 if self._reorder is None:
762 762 # Since generaldelta is directly supported by cg2, reordering
763 763 # generally doesn't help, so we disable it by default (treating
764 764 # bundle.reorder=auto just like bundle.reorder=False).
765 765 self._reorder = False
766 766
767 767 def deltaparent(self, revlog, rev, p1, p2, prev):
768 768 dp = revlog.deltaparent(rev)
769 769 # avoid storing full revisions; pick prev in those cases
770 770 # also pick prev when we can't be sure remote has dp
771 771 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
772 772 return prev
773 773 return dp
774 774
775 775 def builddeltaheader(self, node, p1n, p2n, basenode, linknode):
776 776 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
777 777
778 778 packermap = {'01': (cg1packer, cg1unpacker),
779 '02': (cg2packer, cg2unpacker)}
779 # cg2 adds support for exchanging generaldelta
780 '02': (cg2packer, cg2unpacker),
781 }
780 782
781 783 def _changegroupinfo(repo, nodes, source):
782 784 if repo.ui.verbose or source == 'bundle':
783 785 repo.ui.status(_("%d changesets found\n") % len(nodes))
784 786 if repo.ui.debugflag:
785 787 repo.ui.debug("list of changesets:\n")
786 788 for node in nodes:
787 789 repo.ui.debug("%s\n" % hex(node))
788 790
789 791 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
790 792 repo = repo.unfiltered()
791 793 commonrevs = outgoing.common
792 794 csets = outgoing.missing
793 795 heads = outgoing.missingheads
794 796 # We go through the fast path if we get told to, or if all (unfiltered
795 797 # heads have been requested (since we then know there all linkrevs will
796 798 # be pulled by the client).
797 799 heads.sort()
798 800 fastpathlinkrev = fastpath or (
799 801 repo.filtername is None and heads == sorted(repo.heads()))
800 802
801 803 repo.hook('preoutgoing', throw=True, source=source)
802 804 _changegroupinfo(repo, csets, source)
803 805 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
804 806
805 807 def getsubset(repo, outgoing, bundler, source, fastpath=False):
806 808 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
807 809 return packermap[bundler.version][1](util.chunkbuffer(gengroup), None)
808 810
809 811 def changegroupsubset(repo, roots, heads, source, version='01'):
810 812 """Compute a changegroup consisting of all the nodes that are
811 813 descendants of any of the roots and ancestors of any of the heads.
812 814 Return a chunkbuffer object whose read() method will return
813 815 successive changegroup chunks.
814 816
815 817 It is fairly complex as determining which filenodes and which
816 818 manifest nodes need to be included for the changeset to be complete
817 819 is non-trivial.
818 820
819 821 Another wrinkle is doing the reverse, figuring out which changeset in
820 822 the changegroup a particular filenode or manifestnode belongs to.
821 823 """
822 824 cl = repo.changelog
823 825 if not roots:
824 826 roots = [nullid]
825 827 discbases = []
826 828 for n in roots:
827 829 discbases.extend([p for p in cl.parents(n) if p != nullid])
828 830 # TODO: remove call to nodesbetween.
829 831 csets, roots, heads = cl.nodesbetween(roots, heads)
830 832 included = set(csets)
831 833 discbases = [n for n in discbases if n not in included]
832 834 outgoing = discovery.outgoing(cl, discbases, heads)
833 835 bundler = packermap[version][0](repo)
834 836 return getsubset(repo, outgoing, bundler, source)
835 837
836 838 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
837 839 version='01'):
838 840 """Like getbundle, but taking a discovery.outgoing as an argument.
839 841
840 842 This is only implemented for local repos and reuses potentially
841 843 precomputed sets in outgoing. Returns a raw changegroup generator."""
842 844 if not outgoing.missing:
843 845 return None
844 846 bundler = packermap[version][0](repo, bundlecaps)
845 847 return getsubsetraw(repo, outgoing, bundler, source)
846 848
847 849 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
848 850 version='01'):
849 851 """Like getbundle, but taking a discovery.outgoing as an argument.
850 852
851 853 This is only implemented for local repos and reuses potentially
852 854 precomputed sets in outgoing."""
853 855 if not outgoing.missing:
854 856 return None
855 857 bundler = packermap[version][0](repo, bundlecaps)
856 858 return getsubset(repo, outgoing, bundler, source)
857 859
858 860 def computeoutgoing(repo, heads, common):
859 861 """Computes which revs are outgoing given a set of common
860 862 and a set of heads.
861 863
862 864 This is a separate function so extensions can have access to
863 865 the logic.
864 866
865 867 Returns a discovery.outgoing object.
866 868 """
867 869 cl = repo.changelog
868 870 if common:
869 871 hasnode = cl.hasnode
870 872 common = [n for n in common if hasnode(n)]
871 873 else:
872 874 common = [nullid]
873 875 if not heads:
874 876 heads = cl.heads()
875 877 return discovery.outgoing(cl, common, heads)
876 878
877 879 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
878 880 version='01'):
879 881 """Like changegroupsubset, but returns the set difference between the
880 882 ancestors of heads and the ancestors common.
881 883
882 884 If heads is None, use the local heads. If common is None, use [nullid].
883 885
884 886 The nodes in common might not all be known locally due to the way the
885 887 current discovery protocol works.
886 888 """
887 889 outgoing = computeoutgoing(repo, heads, common)
888 890 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
889 891 version=version)
890 892
891 893 def changegroup(repo, basenodes, source):
892 894 # to avoid a race we use changegroupsubset() (issue1320)
893 895 return changegroupsubset(repo, basenodes, repo.heads(), source)
894 896
895 897 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
896 898 revisions = 0
897 899 files = 0
898 900 while True:
899 901 chunkdata = source.filelogheader()
900 902 if not chunkdata:
901 903 break
902 904 f = chunkdata["filename"]
903 905 repo.ui.debug("adding %s revisions\n" % f)
904 906 pr()
905 907 fl = repo.file(f)
906 908 o = len(fl)
907 909 try:
908 910 if not fl.addgroup(source, revmap, trp):
909 911 raise error.Abort(_("received file revlog group is empty"))
910 912 except error.CensoredBaseError as e:
911 913 raise error.Abort(_("received delta base is censored: %s") % e)
912 914 revisions += len(fl) - o
913 915 files += 1
914 916 if f in needfiles:
915 917 needs = needfiles[f]
916 918 for new in xrange(o, len(fl)):
917 919 n = fl.node(new)
918 920 if n in needs:
919 921 needs.remove(n)
920 922 else:
921 923 raise error.Abort(
922 924 _("received spurious file revlog entry"))
923 925 if not needs:
924 926 del needfiles[f]
925 927 repo.ui.progress(_('files'), None)
926 928
927 929 for f, needs in needfiles.iteritems():
928 930 fl = repo.file(f)
929 931 for n in needs:
930 932 try:
931 933 fl.rev(n)
932 934 except error.LookupError:
933 935 raise error.Abort(
934 936 _('missing file data for %s:%s - run hg verify') %
935 937 (f, hex(n)))
936 938
937 939 return revisions, files
General Comments 0
You need to be logged in to leave comments. Login now