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