##// END OF EJS Templates
changegroup: fix treemanifests on merges...
Martin von Zweigbergk -
r28240:1ac8ce13 default
parent child Browse files
Show More
@@ -1,1163 +1,1130 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 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
36 36
37 37 def readexactly(stream, n):
38 38 '''read n bytes from stream.read and abort if less was available'''
39 39 s = stream.read(n)
40 40 if len(s) < n:
41 41 raise error.Abort(_("stream ended unexpectedly"
42 42 " (got %d bytes, expected %d)")
43 43 % (len(s), n))
44 44 return s
45 45
46 46 def getchunk(stream):
47 47 """return the next chunk from stream as a string"""
48 48 d = readexactly(stream, 4)
49 49 l = struct.unpack(">l", d)[0]
50 50 if l <= 4:
51 51 if l:
52 52 raise error.Abort(_("invalid chunk length %d") % l)
53 53 return ""
54 54 return readexactly(stream, l - 4)
55 55
56 56 def chunkheader(length):
57 57 """return a changegroup chunk header (string)"""
58 58 return struct.pack(">l", length + 4)
59 59
60 60 def closechunk():
61 61 """return a changegroup chunk header (string) for a zero-length chunk"""
62 62 return struct.pack(">l", 0)
63 63
64 64 def combineresults(results):
65 65 """logic to combine 0 or more addchangegroup results into one"""
66 66 changedheads = 0
67 67 result = 1
68 68 for ret in results:
69 69 # If any changegroup result is 0, return 0
70 70 if ret == 0:
71 71 result = 0
72 72 break
73 73 if ret < -1:
74 74 changedheads += ret + 1
75 75 elif ret > 1:
76 76 changedheads += ret - 1
77 77 if changedheads > 0:
78 78 result = 1 + changedheads
79 79 elif changedheads < 0:
80 80 result = -1 + changedheads
81 81 return result
82 82
83 83 bundletypes = {
84 84 "": ("", None), # only when using unbundle on ssh and old http servers
85 85 # since the unification ssh accepts a header but there
86 86 # is no capability signaling it.
87 87 "HG20": (), # special-cased below
88 88 "HG10UN": ("HG10UN", None),
89 89 "HG10BZ": ("HG10", 'BZ'),
90 90 "HG10GZ": ("HG10GZ", 'GZ'),
91 91 }
92 92
93 93 # hgweb uses this list to communicate its preferred type
94 94 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
95 95
96 96 def writechunks(ui, chunks, filename, vfs=None):
97 97 """Write chunks to a file and return its filename.
98 98
99 99 The stream is assumed to be a bundle file.
100 100 Existing files will not be overwritten.
101 101 If no filename is specified, a temporary file is created.
102 102 """
103 103 fh = None
104 104 cleanup = None
105 105 try:
106 106 if filename:
107 107 if vfs:
108 108 fh = vfs.open(filename, "wb")
109 109 else:
110 110 fh = open(filename, "wb")
111 111 else:
112 112 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
113 113 fh = os.fdopen(fd, "wb")
114 114 cleanup = filename
115 115 for c in chunks:
116 116 fh.write(c)
117 117 cleanup = None
118 118 return filename
119 119 finally:
120 120 if fh is not None:
121 121 fh.close()
122 122 if cleanup is not None:
123 123 if filename and vfs:
124 124 vfs.unlink(cleanup)
125 125 else:
126 126 os.unlink(cleanup)
127 127
128 128 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None):
129 129 """Write a bundle file and return its filename.
130 130
131 131 Existing files will not be overwritten.
132 132 If no filename is specified, a temporary file is created.
133 133 bz2 compression can be turned off.
134 134 The bundle file will be deleted in case of errors.
135 135 """
136 136
137 137 if bundletype == "HG20":
138 138 from . import bundle2
139 139 bundle = bundle2.bundle20(ui)
140 140 bundle.setcompression(compression)
141 141 part = bundle.newpart('changegroup', data=cg.getchunks())
142 142 part.addparam('version', cg.version)
143 143 chunkiter = bundle.getchunks()
144 144 else:
145 145 # compression argument is only for the bundle2 case
146 146 assert compression is None
147 147 if cg.version != '01':
148 148 raise error.Abort(_('old bundle types only supports v1 '
149 149 'changegroups'))
150 150 header, comp = bundletypes[bundletype]
151 151 if comp not in util.compressors:
152 152 raise error.Abort(_('unknown stream compression type: %s')
153 153 % comp)
154 154 z = util.compressors[comp]()
155 155 subchunkiter = cg.getchunks()
156 156 def chunkiter():
157 157 yield header
158 158 for chunk in subchunkiter:
159 159 yield z.compress(chunk)
160 160 yield z.flush()
161 161 chunkiter = chunkiter()
162 162
163 163 # parse the changegroup data, otherwise we will block
164 164 # in case of sshrepo because we don't know the end of the stream
165 165
166 166 # an empty chunkgroup is the end of the changegroup
167 167 # a changegroup has at least 2 chunkgroups (changelog and manifest).
168 168 # after that, an empty chunkgroup is the end of the changegroup
169 169 return writechunks(ui, chunkiter, filename, vfs=vfs)
170 170
171 171 class cg1unpacker(object):
172 172 """Unpacker for cg1 changegroup streams.
173 173
174 174 A changegroup unpacker handles the framing of the revision data in
175 175 the wire format. Most consumers will want to use the apply()
176 176 method to add the changes from the changegroup to a repository.
177 177
178 178 If you're forwarding a changegroup unmodified to another consumer,
179 179 use getchunks(), which returns an iterator of changegroup
180 180 chunks. This is mostly useful for cases where you need to know the
181 181 data stream has ended by observing the end of the changegroup.
182 182
183 183 deltachunk() is useful only if you're applying delta data. Most
184 184 consumers should prefer apply() instead.
185 185
186 186 A few other public methods exist. Those are used only for
187 187 bundlerepo and some debug commands - their use is discouraged.
188 188 """
189 189 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
190 190 deltaheadersize = struct.calcsize(deltaheader)
191 191 version = '01'
192 192 _grouplistcount = 1 # One list of files after the manifests
193 193
194 194 def __init__(self, fh, alg):
195 195 if alg == 'UN':
196 196 alg = None # get more modern without breaking too much
197 197 if not alg in util.decompressors:
198 198 raise error.Abort(_('unknown stream compression type: %s')
199 199 % alg)
200 200 if alg == 'BZ':
201 201 alg = '_truncatedBZ'
202 202 self._stream = util.decompressors[alg](fh)
203 203 self._type = alg
204 204 self.callback = None
205 205
206 206 # These methods (compressed, read, seek, tell) all appear to only
207 207 # be used by bundlerepo, but it's a little hard to tell.
208 208 def compressed(self):
209 209 return self._type is not None
210 210 def read(self, l):
211 211 return self._stream.read(l)
212 212 def seek(self, pos):
213 213 return self._stream.seek(pos)
214 214 def tell(self):
215 215 return self._stream.tell()
216 216 def close(self):
217 217 return self._stream.close()
218 218
219 219 def _chunklength(self):
220 220 d = readexactly(self._stream, 4)
221 221 l = struct.unpack(">l", d)[0]
222 222 if l <= 4:
223 223 if l:
224 224 raise error.Abort(_("invalid chunk length %d") % l)
225 225 return 0
226 226 if self.callback:
227 227 self.callback()
228 228 return l - 4
229 229
230 230 def changelogheader(self):
231 231 """v10 does not have a changelog header chunk"""
232 232 return {}
233 233
234 234 def manifestheader(self):
235 235 """v10 does not have a manifest header chunk"""
236 236 return {}
237 237
238 238 def filelogheader(self):
239 239 """return the header of the filelogs chunk, v10 only has the filename"""
240 240 l = self._chunklength()
241 241 if not l:
242 242 return {}
243 243 fname = readexactly(self._stream, l)
244 244 return {'filename': fname}
245 245
246 246 def _deltaheader(self, headertuple, prevnode):
247 247 node, p1, p2, cs = headertuple
248 248 if prevnode is None:
249 249 deltabase = p1
250 250 else:
251 251 deltabase = prevnode
252 252 flags = 0
253 253 return node, p1, p2, deltabase, cs, flags
254 254
255 255 def deltachunk(self, prevnode):
256 256 l = self._chunklength()
257 257 if not l:
258 258 return {}
259 259 headerdata = readexactly(self._stream, self.deltaheadersize)
260 260 header = struct.unpack(self.deltaheader, headerdata)
261 261 delta = readexactly(self._stream, l - self.deltaheadersize)
262 262 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
263 263 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
264 264 'deltabase': deltabase, 'delta': delta, 'flags': flags}
265 265
266 266 def getchunks(self):
267 267 """returns all the chunks contains in the bundle
268 268
269 269 Used when you need to forward the binary stream to a file or another
270 270 network API. To do so, it parse the changegroup data, otherwise it will
271 271 block in case of sshrepo because it don't know the end of the stream.
272 272 """
273 273 # an empty chunkgroup is the end of the changegroup
274 274 # a changegroup has at least 2 chunkgroups (changelog and manifest).
275 275 # after that, changegroup versions 1 and 2 have a series of groups
276 276 # with one group per file. changegroup 3 has a series of directory
277 277 # manifests before the files.
278 278 count = 0
279 279 emptycount = 0
280 280 while emptycount < self._grouplistcount:
281 281 empty = True
282 282 count += 1
283 283 while True:
284 284 chunk = getchunk(self)
285 285 if not chunk:
286 286 if empty and count > 2:
287 287 emptycount += 1
288 288 break
289 289 empty = False
290 290 yield chunkheader(len(chunk))
291 291 pos = 0
292 292 while pos < len(chunk):
293 293 next = pos + 2**20
294 294 yield chunk[pos:next]
295 295 pos = next
296 296 yield closechunk()
297 297
298 298 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
299 299 # We know that we'll never have more manifests than we had
300 300 # changesets.
301 301 self.callback = prog(_('manifests'), numchanges)
302 302 # no need to check for empty manifest group here:
303 303 # if the result of the merge of 1 and 2 is the same in 3 and 4,
304 304 # no new manifest will be created and the manifest group will
305 305 # be empty during the pull
306 306 self.manifestheader()
307 307 repo.manifest.addgroup(self, revmap, trp)
308 308 repo.ui.progress(_('manifests'), None)
309 309
310 310 def apply(self, repo, srctype, url, emptyok=False,
311 311 targetphase=phases.draft, expectedtotal=None):
312 312 """Add the changegroup returned by source.read() to this repo.
313 313 srctype is a string like 'push', 'pull', or 'unbundle'. url is
314 314 the URL of the repo where this changegroup is coming from.
315 315
316 316 Return an integer summarizing the change to this repo:
317 317 - nothing changed or no source: 0
318 318 - more heads than before: 1+added heads (2..n)
319 319 - fewer heads than before: -1-removed heads (-2..-n)
320 320 - number of heads stays the same: 1
321 321 """
322 322 repo = repo.unfiltered()
323 323 def csmap(x):
324 324 repo.ui.debug("add changeset %s\n" % short(x))
325 325 return len(cl)
326 326
327 327 def revmap(x):
328 328 return cl.rev(x)
329 329
330 330 changesets = files = revisions = 0
331 331
332 332 try:
333 333 with repo.transaction("\n".join([srctype,
334 334 util.hidepassword(url)])) as tr:
335 335 # The transaction could have been created before and already
336 336 # carries source information. In this case we use the top
337 337 # level data. We overwrite the argument because we need to use
338 338 # the top level value (if they exist) in this function.
339 339 srctype = tr.hookargs.setdefault('source', srctype)
340 340 url = tr.hookargs.setdefault('url', url)
341 341 repo.hook('prechangegroup', throw=True, **tr.hookargs)
342 342
343 343 # write changelog data to temp files so concurrent readers
344 344 # will not see an inconsistent view
345 345 cl = repo.changelog
346 346 cl.delayupdate(tr)
347 347 oldheads = cl.heads()
348 348
349 349 trp = weakref.proxy(tr)
350 350 # pull off the changeset group
351 351 repo.ui.status(_("adding changesets\n"))
352 352 clstart = len(cl)
353 353 class prog(object):
354 354 def __init__(self, step, total):
355 355 self._step = step
356 356 self._total = total
357 357 self._count = 1
358 358 def __call__(self):
359 359 repo.ui.progress(self._step, self._count,
360 360 unit=_('chunks'), total=self._total)
361 361 self._count += 1
362 362 self.callback = prog(_('changesets'), expectedtotal)
363 363
364 364 efiles = set()
365 365 def onchangelog(cl, node):
366 366 efiles.update(cl.read(node)[3])
367 367
368 368 self.changelogheader()
369 369 srccontent = cl.addgroup(self, csmap, trp,
370 370 addrevisioncb=onchangelog)
371 371 efiles = len(efiles)
372 372
373 373 if not (srccontent or emptyok):
374 374 raise error.Abort(_("received changelog group is empty"))
375 375 clend = len(cl)
376 376 changesets = clend - clstart
377 377 repo.ui.progress(_('changesets'), None)
378 378
379 379 # pull off the manifest group
380 380 repo.ui.status(_("adding manifests\n"))
381 381 self._unpackmanifests(repo, revmap, trp, prog, changesets)
382 382
383 383 needfiles = {}
384 384 if repo.ui.configbool('server', 'validate', default=False):
385 385 # validate incoming csets have their manifests
386 386 for cset in xrange(clstart, clend):
387 387 mfnode = repo.changelog.read(
388 388 repo.changelog.node(cset))[0]
389 389 mfest = repo.manifest.readdelta(mfnode)
390 390 # store file nodes we must see
391 391 for f, n in mfest.iteritems():
392 392 needfiles.setdefault(f, set()).add(n)
393 393
394 394 # process the files
395 395 repo.ui.status(_("adding file changes\n"))
396 396 self.callback = None
397 397 pr = prog(_('files'), efiles)
398 398 newrevs, newfiles = _addchangegroupfiles(
399 399 repo, self, revmap, trp, pr, needfiles)
400 400 revisions += newrevs
401 401 files += newfiles
402 402
403 403 dh = 0
404 404 if oldheads:
405 405 heads = cl.heads()
406 406 dh = len(heads) - len(oldheads)
407 407 for h in heads:
408 408 if h not in oldheads and repo[h].closesbranch():
409 409 dh -= 1
410 410 htext = ""
411 411 if dh:
412 412 htext = _(" (%+d heads)") % dh
413 413
414 414 repo.ui.status(_("added %d changesets"
415 415 " with %d changes to %d files%s\n")
416 416 % (changesets, revisions, files, htext))
417 417 repo.invalidatevolatilesets()
418 418
419 419 if changesets > 0:
420 420 if 'node' not in tr.hookargs:
421 421 tr.hookargs['node'] = hex(cl.node(clstart))
422 422 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
423 423 hookargs = dict(tr.hookargs)
424 424 else:
425 425 hookargs = dict(tr.hookargs)
426 426 hookargs['node'] = hex(cl.node(clstart))
427 427 hookargs['node_last'] = hex(cl.node(clend - 1))
428 428 repo.hook('pretxnchangegroup', throw=True, **hookargs)
429 429
430 430 added = [cl.node(r) for r in xrange(clstart, clend)]
431 431 publishing = repo.publishing()
432 432 if srctype in ('push', 'serve'):
433 433 # Old servers can not push the boundary themselves.
434 434 # New servers won't push the boundary if changeset already
435 435 # exists locally as secret
436 436 #
437 437 # We should not use added here but the list of all change in
438 438 # the bundle
439 439 if publishing:
440 440 phases.advanceboundary(repo, tr, phases.public,
441 441 srccontent)
442 442 else:
443 443 # Those changesets have been pushed from the
444 444 # outside, their phases are going to be pushed
445 445 # alongside. Therefor `targetphase` is
446 446 # ignored.
447 447 phases.advanceboundary(repo, tr, phases.draft,
448 448 srccontent)
449 449 phases.retractboundary(repo, tr, phases.draft, added)
450 450 elif srctype != 'strip':
451 451 # publishing only alter behavior during push
452 452 #
453 453 # strip should not touch boundary at all
454 454 phases.retractboundary(repo, tr, targetphase, added)
455 455
456 456 if changesets > 0:
457 457 if srctype != 'strip':
458 458 # During strip, branchcache is invalid but
459 459 # coming call to `destroyed` will repair it.
460 460 # In other case we can safely update cache on
461 461 # disk.
462 462 branchmap.updatecache(repo.filtered('served'))
463 463
464 464 def runhooks():
465 465 # These hooks run when the lock releases, not when the
466 466 # transaction closes. So it's possible for the changelog
467 467 # to have changed since we last saw it.
468 468 if clstart >= len(repo):
469 469 return
470 470
471 471 # forcefully update the on-disk branch cache
472 472 repo.ui.debug("updating the branch cache\n")
473 473 repo.hook("changegroup", **hookargs)
474 474
475 475 for n in added:
476 476 args = hookargs.copy()
477 477 args['node'] = hex(n)
478 478 del args['node_last']
479 479 repo.hook("incoming", **args)
480 480
481 481 newheads = [h for h in repo.heads()
482 482 if h not in oldheads]
483 483 repo.ui.log("incoming",
484 484 "%s incoming changes - new heads: %s\n",
485 485 len(added),
486 486 ', '.join([hex(c[:6]) for c in newheads]))
487 487
488 488 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
489 489 lambda tr: repo._afterlock(runhooks))
490 490 finally:
491 491 repo.ui.flush()
492 492 # never return 0 here:
493 493 if dh < 0:
494 494 return dh - 1
495 495 else:
496 496 return dh + 1
497 497
498 498 class cg2unpacker(cg1unpacker):
499 499 """Unpacker for cg2 streams.
500 500
501 501 cg2 streams add support for generaldelta, so the delta header
502 502 format is slightly different. All other features about the data
503 503 remain the same.
504 504 """
505 505 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
506 506 deltaheadersize = struct.calcsize(deltaheader)
507 507 version = '02'
508 508
509 509 def _deltaheader(self, headertuple, prevnode):
510 510 node, p1, p2, deltabase, cs = headertuple
511 511 flags = 0
512 512 return node, p1, p2, deltabase, cs, flags
513 513
514 514 class cg3unpacker(cg2unpacker):
515 515 """Unpacker for cg3 streams.
516 516
517 517 cg3 streams add support for exchanging treemanifests and revlog
518 518 flags. It adds the revlog flags to the delta header and an empty chunk
519 519 separating manifests and files.
520 520 """
521 521 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
522 522 deltaheadersize = struct.calcsize(deltaheader)
523 523 version = '03'
524 524 _grouplistcount = 2 # One list of manifests and one list of files
525 525
526 526 def _deltaheader(self, headertuple, prevnode):
527 527 node, p1, p2, deltabase, cs, flags = headertuple
528 528 return node, p1, p2, deltabase, cs, flags
529 529
530 530 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
531 531 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
532 532 numchanges)
533 533 while True:
534 534 chunkdata = self.filelogheader()
535 535 if not chunkdata:
536 536 break
537 537 # If we get here, there are directory manifests in the changegroup
538 538 d = chunkdata["filename"]
539 539 repo.ui.debug("adding %s revisions\n" % d)
540 540 dirlog = repo.manifest.dirlog(d)
541 541 if not dirlog.addgroup(self, revmap, trp):
542 542 raise error.Abort(_("received dir revlog group is empty"))
543 543
544 544 class headerlessfixup(object):
545 545 def __init__(self, fh, h):
546 546 self._h = h
547 547 self._fh = fh
548 548 def read(self, n):
549 549 if self._h:
550 550 d, self._h = self._h[:n], self._h[n:]
551 551 if len(d) < n:
552 552 d += readexactly(self._fh, n - len(d))
553 553 return d
554 554 return readexactly(self._fh, n)
555 555
556 def _moddirs(files):
557 """Given a set of modified files, find the list of modified directories.
558
559 This returns a list of (path to changed dir, changed dir) tuples,
560 as that's what the one client needs anyway.
561
562 >>> _moddirs(['a/b/c.py', 'a/b/c.txt', 'a/d/e/f/g.txt', 'i.txt', ])
563 [('/', 'a/'), ('a/', 'b/'), ('a/', 'd/'), ('a/d/', 'e/'), ('a/d/e/', 'f/')]
564
565 """
566 alldirs = set()
567 for f in files:
568 path = f.split('/')[:-1]
569 for i in xrange(len(path) - 1, -1, -1):
570 dn = '/'.join(path[:i])
571 current = dn + '/', path[i] + '/'
572 if current in alldirs:
573 break
574 alldirs.add(current)
575 return sorted(alldirs)
576
577 556 class cg1packer(object):
578 557 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
579 558 version = '01'
580 559 def __init__(self, repo, bundlecaps=None):
581 560 """Given a source repo, construct a bundler.
582 561
583 562 bundlecaps is optional and can be used to specify the set of
584 563 capabilities which can be used to build the bundle.
585 564 """
586 565 # Set of capabilities we can use to build the bundle.
587 566 if bundlecaps is None:
588 567 bundlecaps = set()
589 568 self._bundlecaps = bundlecaps
590 569 # experimental config: bundle.reorder
591 570 reorder = repo.ui.config('bundle', 'reorder', 'auto')
592 571 if reorder == 'auto':
593 572 reorder = None
594 573 else:
595 574 reorder = util.parsebool(reorder)
596 575 self._repo = repo
597 576 self._reorder = reorder
598 577 self._progress = repo.ui.progress
599 578 if self._repo.ui.verbose and not self._repo.ui.debugflag:
600 579 self._verbosenote = self._repo.ui.note
601 580 else:
602 581 self._verbosenote = lambda s: None
603 582
604 583 def close(self):
605 584 return closechunk()
606 585
607 586 def fileheader(self, fname):
608 587 return chunkheader(len(fname)) + fname
609 588
610 589 def group(self, nodelist, revlog, lookup, units=None):
611 590 """Calculate a delta group, yielding a sequence of changegroup chunks
612 591 (strings).
613 592
614 593 Given a list of changeset revs, return a set of deltas and
615 594 metadata corresponding to nodes. The first delta is
616 595 first parent(nodelist[0]) -> nodelist[0], the receiver is
617 596 guaranteed to have this parent as it has all history before
618 597 these changesets. In the case firstparent is nullrev the
619 598 changegroup starts with a full revision.
620 599
621 600 If units is not None, progress detail will be generated, units specifies
622 601 the type of revlog that is touched (changelog, manifest, etc.).
623 602 """
624 603 # if we don't have any revisions touched by these changesets, bail
625 604 if len(nodelist) == 0:
626 605 yield self.close()
627 606 return
628 607
629 608 # for generaldelta revlogs, we linearize the revs; this will both be
630 609 # much quicker and generate a much smaller bundle
631 610 if (revlog._generaldelta and self._reorder is None) or self._reorder:
632 611 dag = dagutil.revlogdag(revlog)
633 612 revs = set(revlog.rev(n) for n in nodelist)
634 613 revs = dag.linearize(revs)
635 614 else:
636 615 revs = sorted([revlog.rev(n) for n in nodelist])
637 616
638 617 # add the parent of the first rev
639 618 p = revlog.parentrevs(revs[0])[0]
640 619 revs.insert(0, p)
641 620
642 621 # build deltas
643 622 total = len(revs) - 1
644 623 msgbundling = _('bundling')
645 624 for r in xrange(len(revs) - 1):
646 625 if units is not None:
647 626 self._progress(msgbundling, r + 1, unit=units, total=total)
648 627 prev, curr = revs[r], revs[r + 1]
649 628 linknode = lookup(revlog.node(curr))
650 629 for c in self.revchunk(revlog, curr, prev, linknode):
651 630 yield c
652 631
653 632 if units is not None:
654 633 self._progress(msgbundling, None)
655 634 yield self.close()
656 635
657 636 # filter any nodes that claim to be part of the known set
658 637 def prune(self, revlog, missing, commonrevs):
659 638 rr, rl = revlog.rev, revlog.linkrev
660 639 return [n for n in missing if rl(rr(n)) not in commonrevs]
661 640
662 641 def _packmanifests(self, dir, mfnodes, lookuplinknode):
663 642 """Pack flat manifests into a changegroup stream."""
664 643 assert not dir
665 644 for chunk in self.group(mfnodes, self._repo.manifest,
666 645 lookuplinknode, units=_('manifests')):
667 646 yield chunk
668 647
669 648 def _manifestsdone(self):
670 649 return ''
671 650
672 651 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
673 652 '''yield a sequence of changegroup chunks (strings)'''
674 653 repo = self._repo
675 654 cl = repo.changelog
676 655
677 656 clrevorder = {}
678 657 mfs = {} # needed manifests
679 658 fnodes = {} # needed file nodes
680 659 # maps manifest node id -> set(changed files)
681 660 mfchangedfiles = {}
682 661
683 662 # Callback for the changelog, used to collect changed files and manifest
684 663 # nodes.
685 664 # Returns the linkrev node (identity in the changelog case).
686 665 def lookupcl(x):
687 666 c = cl.read(x)
688 667 clrevorder[x] = len(clrevorder)
689 668 n = c[0]
690 669 # record the first changeset introducing this manifest version
691 670 mfs.setdefault(n, x)
692 671 # Record a complete list of potentially-changed files in
693 672 # this manifest.
694 673 mfchangedfiles.setdefault(n, set()).update(c[3])
695 674 return x
696 675
697 676 self._verbosenote(_('uncompressed size of bundle content:\n'))
698 677 size = 0
699 678 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
700 679 size += len(chunk)
701 680 yield chunk
702 681 self._verbosenote(_('%8.i (changelog)\n') % size)
703 682
704 683 # We need to make sure that the linkrev in the changegroup refers to
705 684 # the first changeset that introduced the manifest or file revision.
706 685 # The fastpath is usually safer than the slowpath, because the filelogs
707 686 # are walked in revlog order.
708 687 #
709 688 # When taking the slowpath with reorder=None and the manifest revlog
710 689 # uses generaldelta, the manifest may be walked in the "wrong" order.
711 690 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
712 691 # cc0ff93d0c0c).
713 692 #
714 693 # When taking the fastpath, we are only vulnerable to reordering
715 694 # of the changelog itself. The changelog never uses generaldelta, so
716 695 # it is only reordered when reorder=True. To handle this case, we
717 696 # simply take the slowpath, which already has the 'clrevorder' logic.
718 697 # This was also fixed in cc0ff93d0c0c.
719 698 fastpathlinkrev = fastpathlinkrev and not self._reorder
720 699 # Treemanifests don't work correctly with fastpathlinkrev
721 700 # either, because we don't discover which directory nodes to
722 701 # send along with files. This could probably be fixed.
723 702 fastpathlinkrev = fastpathlinkrev and (
724 703 'treemanifest' not in repo.requirements)
725 704
726 705 for chunk in self.generatemanifests(commonrevs, clrevorder,
727 706 fastpathlinkrev, mfs, mfchangedfiles, fnodes):
728 707 yield chunk
729 708 mfs.clear()
730 709 clrevs = set(cl.rev(x) for x in clnodes)
731 710
732 711 if not fastpathlinkrev:
733 712 def linknodes(unused, fname):
734 713 return fnodes.get(fname, {})
735 714 else:
736 715 cln = cl.node
737 716 def linknodes(filerevlog, fname):
738 717 llr = filerevlog.linkrev
739 718 fln = filerevlog.node
740 719 revs = ((r, llr(r)) for r in filerevlog)
741 720 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
742 721
743 722 changedfiles = set()
744 723 for x in mfchangedfiles.itervalues():
745 724 changedfiles.update(x)
746 725 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
747 726 source):
748 727 yield chunk
749 728
750 729 yield self.close()
751 730
752 731 if clnodes:
753 732 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
754 733
755 734 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
756 735 mfchangedfiles, fnodes):
757 736 repo = self._repo
758 ml = repo.manifest
737 dirlog = repo.manifest.dirlog
759 738 tmfnodes = {'': mfs}
760 739
761 740 # Callback for the manifest, used to collect linkrevs for filelog
762 741 # revisions.
763 742 # Returns the linkrev node (collected in lookupcl).
764 743 def makelookupmflinknode(dir):
765 744 if fastpathlinkrev:
766 745 assert not dir
767 746 return mfs.__getitem__
768 747
769 if dir:
770 return tmfnodes[dir].get
771
772 748 def lookupmflinknode(x):
773 749 """Callback for looking up the linknode for manifests.
774 750
775 751 Returns the linkrev node for the specified manifest.
776 752
777 753 SIDE EFFECT:
778 754
779 755 1) fclnodes gets populated with the list of relevant
780 756 file nodes if we're not using fastpathlinkrev
781 757 2) When treemanifests are in use, collects treemanifest nodes
782 758 to send
783 759
784 760 Note that this means manifests must be completely sent to
785 761 the client before you can trust the list of files and
786 762 treemanifests to send.
787 763 """
788 clnode = mfs[x]
789 # We no longer actually care about reading deltas of
790 # the manifest here, because we already know the list
791 # of changed files, so for treemanifests (which
792 # lazily-load anyway to *generate* a readdelta) we can
793 # just load them with read() and then we'll actually
794 # be able to correctly load node IDs from the
795 # submanifest entries.
764 clnode = tmfnodes[dir][x]
765 mdata = dirlog(dir).readshallowfast(x)
796 766 if 'treemanifest' in repo.requirements:
797 mdata = ml.read(x)
767 for p, n, fl in mdata.iterentries():
768 if fl == 't': # subdirectory manifest
769 subdir = dir + p + '/'
770 tmfclnodes = tmfnodes.setdefault(subdir, {})
771 tmfclnode = tmfclnodes.setdefault(n, clnode)
772 if clrevorder[clnode] < clrevorder[tmfclnode]:
773 tmfclnodes[n] = clnode
774 else:
775 f = dir + p
776 fclnodes = fnodes.setdefault(f, {})
777 fclnode = fclnodes.setdefault(n, clnode)
778 if clrevorder[clnode] < clrevorder[fclnode]:
779 fclnodes[n] = clnode
798 780 else:
799 mdata = ml.readfast(x)
800 for f in mfchangedfiles[x]:
801 try:
802 n = mdata[f]
803 except KeyError:
804 continue
805 # record the first changeset introducing this filelog
806 # version
807 fclnodes = fnodes.setdefault(f, {})
808 fclnode = fclnodes.setdefault(n, clnode)
809 if clrevorder[clnode] < clrevorder[fclnode]:
810 fclnodes[n] = clnode
811 # gather list of changed treemanifest nodes
812 if 'treemanifest' in repo.requirements:
813 submfs = {'/': mdata}
814 for dn, bn in _moddirs(mfchangedfiles[x]):
781 for f in mfchangedfiles[x]:
815 782 try:
816 submf = submfs[dn]
817 submf = submf._dirs[bn]
783 n = mdata[f]
818 784 except KeyError:
819 continue # deleted directory, so nothing to send
820 submfs[submf.dir()] = submf
821 tmfclnodes = tmfnodes.setdefault(submf.dir(), {})
822 tmfclnode = tmfclnodes.setdefault(submf._node, clnode)
823 if clrevorder[clnode] < clrevorder[tmfclnode]:
824 tmfclnodes[n] = clnode
785 continue
786 # record the first changeset introducing this filelog
787 # version
788 fclnodes = fnodes.setdefault(f, {})
789 fclnode = fclnodes.setdefault(n, clnode)
790 if clrevorder[clnode] < clrevorder[fclnode]:
791 fclnodes[n] = clnode
825 792 return clnode
826 793 return lookupmflinknode
827 794
828 795 size = 0
829 796 while tmfnodes:
830 797 dir = min(tmfnodes)
831 798 nodes = tmfnodes[dir]
832 prunednodes = self.prune(ml.dirlog(dir), nodes, commonrevs)
799 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
833 800 for x in self._packmanifests(dir, prunednodes,
834 801 makelookupmflinknode(dir)):
835 802 size += len(x)
836 803 yield x
837 804 del tmfnodes[dir]
838 805 self._verbosenote(_('%8.i (manifests)\n') % size)
839 806 yield self._manifestsdone()
840 807
841 808 # The 'source' parameter is useful for extensions
842 809 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
843 810 repo = self._repo
844 811 progress = self._progress
845 812 msgbundling = _('bundling')
846 813
847 814 total = len(changedfiles)
848 815 # for progress output
849 816 msgfiles = _('files')
850 817 for i, fname in enumerate(sorted(changedfiles)):
851 818 filerevlog = repo.file(fname)
852 819 if not filerevlog:
853 820 raise error.Abort(_("empty or missing revlog for %s") % fname)
854 821
855 822 linkrevnodes = linknodes(filerevlog, fname)
856 823 # Lookup for filenodes, we collected the linkrev nodes above in the
857 824 # fastpath case and with lookupmf in the slowpath case.
858 825 def lookupfilelog(x):
859 826 return linkrevnodes[x]
860 827
861 828 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
862 829 if filenodes:
863 830 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
864 831 total=total)
865 832 h = self.fileheader(fname)
866 833 size = len(h)
867 834 yield h
868 835 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
869 836 size += len(chunk)
870 837 yield chunk
871 838 self._verbosenote(_('%8.i %s\n') % (size, fname))
872 839 progress(msgbundling, None)
873 840
874 841 def deltaparent(self, revlog, rev, p1, p2, prev):
875 842 return prev
876 843
877 844 def revchunk(self, revlog, rev, prev, linknode):
878 845 node = revlog.node(rev)
879 846 p1, p2 = revlog.parentrevs(rev)
880 847 base = self.deltaparent(revlog, rev, p1, p2, prev)
881 848
882 849 prefix = ''
883 850 if revlog.iscensored(base) or revlog.iscensored(rev):
884 851 try:
885 852 delta = revlog.revision(node)
886 853 except error.CensoredNodeError as e:
887 854 delta = e.tombstone
888 855 if base == nullrev:
889 856 prefix = mdiff.trivialdiffheader(len(delta))
890 857 else:
891 858 baselen = revlog.rawsize(base)
892 859 prefix = mdiff.replacediffheader(baselen, len(delta))
893 860 elif base == nullrev:
894 861 delta = revlog.revision(node)
895 862 prefix = mdiff.trivialdiffheader(len(delta))
896 863 else:
897 864 delta = revlog.revdiff(base, rev)
898 865 p1n, p2n = revlog.parents(node)
899 866 basenode = revlog.node(base)
900 867 flags = revlog.flags(rev)
901 868 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
902 869 meta += prefix
903 870 l = len(meta) + len(delta)
904 871 yield chunkheader(l)
905 872 yield meta
906 873 yield delta
907 874 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
908 875 # do nothing with basenode, it is implicitly the previous one in HG10
909 876 # do nothing with flags, it is implicitly 0 for cg1 and cg2
910 877 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
911 878
912 879 class cg2packer(cg1packer):
913 880 version = '02'
914 881 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
915 882
916 883 def __init__(self, repo, bundlecaps=None):
917 884 super(cg2packer, self).__init__(repo, bundlecaps)
918 885 if self._reorder is None:
919 886 # Since generaldelta is directly supported by cg2, reordering
920 887 # generally doesn't help, so we disable it by default (treating
921 888 # bundle.reorder=auto just like bundle.reorder=False).
922 889 self._reorder = False
923 890
924 891 def deltaparent(self, revlog, rev, p1, p2, prev):
925 892 dp = revlog.deltaparent(rev)
926 893 # avoid storing full revisions; pick prev in those cases
927 894 # also pick prev when we can't be sure remote has dp
928 895 if dp == nullrev or (dp != p1 and dp != p2 and dp != prev):
929 896 return prev
930 897 return dp
931 898
932 899 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
933 900 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
934 901 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
935 902
936 903 class cg3packer(cg2packer):
937 904 version = '03'
938 905 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
939 906
940 907 def _packmanifests(self, dir, mfnodes, lookuplinknode):
941 908 if dir:
942 909 yield self.fileheader(dir)
943 910 for chunk in self.group(mfnodes, self._repo.manifest.dirlog(dir),
944 911 lookuplinknode, units=_('manifests')):
945 912 yield chunk
946 913
947 914 def _manifestsdone(self):
948 915 return self.close()
949 916
950 917 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
951 918 return struct.pack(
952 919 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
953 920
954 921 _packermap = {'01': (cg1packer, cg1unpacker),
955 922 # cg2 adds support for exchanging generaldelta
956 923 '02': (cg2packer, cg2unpacker),
957 924 # cg3 adds support for exchanging revlog flags and treemanifests
958 925 '03': (cg3packer, cg3unpacker),
959 926 }
960 927
961 928 def allsupportedversions(ui):
962 929 versions = set(_packermap.keys())
963 930 versions.discard('03')
964 931 if (ui.configbool('experimental', 'changegroup3') or
965 932 ui.configbool('experimental', 'treemanifest')):
966 933 versions.add('03')
967 934 return versions
968 935
969 936 # Changegroup versions that can be applied to the repo
970 937 def supportedincomingversions(repo):
971 938 versions = allsupportedversions(repo.ui)
972 939 if 'treemanifest' in repo.requirements:
973 940 versions.add('03')
974 941 return versions
975 942
976 943 # Changegroup versions that can be created from the repo
977 944 def supportedoutgoingversions(repo):
978 945 versions = allsupportedversions(repo.ui)
979 946 if 'treemanifest' in repo.requirements:
980 947 # Versions 01 and 02 support only flat manifests and it's just too
981 948 # expensive to convert between the flat manifest and tree manifest on
982 949 # the fly. Since tree manifests are hashed differently, all of history
983 950 # would have to be converted. Instead, we simply don't even pretend to
984 951 # support versions 01 and 02.
985 952 versions.discard('01')
986 953 versions.discard('02')
987 954 versions.add('03')
988 955 return versions
989 956
990 957 def safeversion(repo):
991 958 # Finds the smallest version that it's safe to assume clients of the repo
992 959 # will support. For example, all hg versions that support generaldelta also
993 960 # support changegroup 02.
994 961 versions = supportedoutgoingversions(repo)
995 962 if 'generaldelta' in repo.requirements:
996 963 versions.discard('01')
997 964 assert versions
998 965 return min(versions)
999 966
1000 967 def getbundler(version, repo, bundlecaps=None):
1001 968 assert version in supportedoutgoingversions(repo)
1002 969 return _packermap[version][0](repo, bundlecaps)
1003 970
1004 971 def getunbundler(version, fh, alg):
1005 972 return _packermap[version][1](fh, alg)
1006 973
1007 974 def _changegroupinfo(repo, nodes, source):
1008 975 if repo.ui.verbose or source == 'bundle':
1009 976 repo.ui.status(_("%d changesets found\n") % len(nodes))
1010 977 if repo.ui.debugflag:
1011 978 repo.ui.debug("list of changesets:\n")
1012 979 for node in nodes:
1013 980 repo.ui.debug("%s\n" % hex(node))
1014 981
1015 982 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
1016 983 repo = repo.unfiltered()
1017 984 commonrevs = outgoing.common
1018 985 csets = outgoing.missing
1019 986 heads = outgoing.missingheads
1020 987 # We go through the fast path if we get told to, or if all (unfiltered
1021 988 # heads have been requested (since we then know there all linkrevs will
1022 989 # be pulled by the client).
1023 990 heads.sort()
1024 991 fastpathlinkrev = fastpath or (
1025 992 repo.filtername is None and heads == sorted(repo.heads()))
1026 993
1027 994 repo.hook('preoutgoing', throw=True, source=source)
1028 995 _changegroupinfo(repo, csets, source)
1029 996 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
1030 997
1031 998 def getsubset(repo, outgoing, bundler, source, fastpath=False):
1032 999 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
1033 1000 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None)
1034 1001
1035 1002 def changegroupsubset(repo, roots, heads, source, version='01'):
1036 1003 """Compute a changegroup consisting of all the nodes that are
1037 1004 descendants of any of the roots and ancestors of any of the heads.
1038 1005 Return a chunkbuffer object whose read() method will return
1039 1006 successive changegroup chunks.
1040 1007
1041 1008 It is fairly complex as determining which filenodes and which
1042 1009 manifest nodes need to be included for the changeset to be complete
1043 1010 is non-trivial.
1044 1011
1045 1012 Another wrinkle is doing the reverse, figuring out which changeset in
1046 1013 the changegroup a particular filenode or manifestnode belongs to.
1047 1014 """
1048 1015 cl = repo.changelog
1049 1016 if not roots:
1050 1017 roots = [nullid]
1051 1018 discbases = []
1052 1019 for n in roots:
1053 1020 discbases.extend([p for p in cl.parents(n) if p != nullid])
1054 1021 # TODO: remove call to nodesbetween.
1055 1022 csets, roots, heads = cl.nodesbetween(roots, heads)
1056 1023 included = set(csets)
1057 1024 discbases = [n for n in discbases if n not in included]
1058 1025 outgoing = discovery.outgoing(cl, discbases, heads)
1059 1026 bundler = getbundler(version, repo)
1060 1027 return getsubset(repo, outgoing, bundler, source)
1061 1028
1062 1029 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
1063 1030 version='01'):
1064 1031 """Like getbundle, but taking a discovery.outgoing as an argument.
1065 1032
1066 1033 This is only implemented for local repos and reuses potentially
1067 1034 precomputed sets in outgoing. Returns a raw changegroup generator."""
1068 1035 if not outgoing.missing:
1069 1036 return None
1070 1037 bundler = getbundler(version, repo, bundlecaps)
1071 1038 return getsubsetraw(repo, outgoing, bundler, source)
1072 1039
1073 1040 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
1074 1041 version='01'):
1075 1042 """Like getbundle, but taking a discovery.outgoing as an argument.
1076 1043
1077 1044 This is only implemented for local repos and reuses potentially
1078 1045 precomputed sets in outgoing."""
1079 1046 if not outgoing.missing:
1080 1047 return None
1081 1048 bundler = getbundler(version, repo, bundlecaps)
1082 1049 return getsubset(repo, outgoing, bundler, source)
1083 1050
1084 1051 def computeoutgoing(repo, heads, common):
1085 1052 """Computes which revs are outgoing given a set of common
1086 1053 and a set of heads.
1087 1054
1088 1055 This is a separate function so extensions can have access to
1089 1056 the logic.
1090 1057
1091 1058 Returns a discovery.outgoing object.
1092 1059 """
1093 1060 cl = repo.changelog
1094 1061 if common:
1095 1062 hasnode = cl.hasnode
1096 1063 common = [n for n in common if hasnode(n)]
1097 1064 else:
1098 1065 common = [nullid]
1099 1066 if not heads:
1100 1067 heads = cl.heads()
1101 1068 return discovery.outgoing(cl, common, heads)
1102 1069
1103 1070 def getchangegroup(repo, source, heads=None, common=None, bundlecaps=None,
1104 1071 version='01'):
1105 1072 """Like changegroupsubset, but returns the set difference between the
1106 1073 ancestors of heads and the ancestors common.
1107 1074
1108 1075 If heads is None, use the local heads. If common is None, use [nullid].
1109 1076
1110 1077 The nodes in common might not all be known locally due to the way the
1111 1078 current discovery protocol works.
1112 1079 """
1113 1080 outgoing = computeoutgoing(repo, heads, common)
1114 1081 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
1115 1082 version=version)
1116 1083
1117 1084 def changegroup(repo, basenodes, source):
1118 1085 # to avoid a race we use changegroupsubset() (issue1320)
1119 1086 return changegroupsubset(repo, basenodes, repo.heads(), source)
1120 1087
1121 1088 def _addchangegroupfiles(repo, source, revmap, trp, pr, needfiles):
1122 1089 revisions = 0
1123 1090 files = 0
1124 1091 while True:
1125 1092 chunkdata = source.filelogheader()
1126 1093 if not chunkdata:
1127 1094 break
1128 1095 f = chunkdata["filename"]
1129 1096 repo.ui.debug("adding %s revisions\n" % f)
1130 1097 pr()
1131 1098 fl = repo.file(f)
1132 1099 o = len(fl)
1133 1100 try:
1134 1101 if not fl.addgroup(source, revmap, trp):
1135 1102 raise error.Abort(_("received file revlog group is empty"))
1136 1103 except error.CensoredBaseError as e:
1137 1104 raise error.Abort(_("received delta base is censored: %s") % e)
1138 1105 revisions += len(fl) - o
1139 1106 files += 1
1140 1107 if f in needfiles:
1141 1108 needs = needfiles[f]
1142 1109 for new in xrange(o, len(fl)):
1143 1110 n = fl.node(new)
1144 1111 if n in needs:
1145 1112 needs.remove(n)
1146 1113 else:
1147 1114 raise error.Abort(
1148 1115 _("received spurious file revlog entry"))
1149 1116 if not needs:
1150 1117 del needfiles[f]
1151 1118 repo.ui.progress(_('files'), None)
1152 1119
1153 1120 for f, needs in needfiles.iteritems():
1154 1121 fl = repo.file(f)
1155 1122 for n in needs:
1156 1123 try:
1157 1124 fl.rev(n)
1158 1125 except error.LookupError:
1159 1126 raise error.Abort(
1160 1127 _('missing file data for %s:%s - run hg verify') %
1161 1128 (f, hex(n)))
1162 1129
1163 1130 return revisions, files
@@ -1,1078 +1,1094 b''
1 1 # manifest.py - manifest revision class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import array
11 11 import heapq
12 12 import os
13 13 import struct
14 14
15 15 from .i18n import _
16 16 from . import (
17 17 error,
18 18 mdiff,
19 19 parsers,
20 20 revlog,
21 21 util,
22 22 )
23 23
24 24 propertycache = util.propertycache
25 25
26 26 def _parsev1(data):
27 27 # This method does a little bit of excessive-looking
28 28 # precondition checking. This is so that the behavior of this
29 29 # class exactly matches its C counterpart to try and help
30 30 # prevent surprise breakage for anyone that develops against
31 31 # the pure version.
32 32 if data and data[-1] != '\n':
33 33 raise ValueError('Manifest did not end in a newline.')
34 34 prev = None
35 35 for l in data.splitlines():
36 36 if prev is not None and prev > l:
37 37 raise ValueError('Manifest lines not in sorted order.')
38 38 prev = l
39 39 f, n = l.split('\0')
40 40 if len(n) > 40:
41 41 yield f, revlog.bin(n[:40]), n[40:]
42 42 else:
43 43 yield f, revlog.bin(n), ''
44 44
45 45 def _parsev2(data):
46 46 metadataend = data.find('\n')
47 47 # Just ignore metadata for now
48 48 pos = metadataend + 1
49 49 prevf = ''
50 50 while pos < len(data):
51 51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 52 if end == -1:
53 53 raise ValueError('Manifest ended with incomplete file entry.')
54 54 stemlen = ord(data[pos])
55 55 items = data[pos + 1:end].split('\0')
56 56 f = prevf[:stemlen] + items[0]
57 57 if prevf > f:
58 58 raise ValueError('Manifest entries not in sorted order.')
59 59 fl = items[1]
60 60 # Just ignore metadata (items[2:] for now)
61 61 n = data[end + 1:end + 21]
62 62 yield f, n, fl
63 63 pos = end + 22
64 64 prevf = f
65 65
66 66 def _parse(data):
67 67 """Generates (path, node, flags) tuples from a manifest text"""
68 68 if data.startswith('\0'):
69 69 return iter(_parsev2(data))
70 70 else:
71 71 return iter(_parsev1(data))
72 72
73 73 def _text(it, usemanifestv2):
74 74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 75 text"""
76 76 if usemanifestv2:
77 77 return _textv2(it)
78 78 else:
79 79 return _textv1(it)
80 80
81 81 def _textv1(it):
82 82 files = []
83 83 lines = []
84 84 _hex = revlog.hex
85 85 for f, n, fl in it:
86 86 files.append(f)
87 87 # if this is changed to support newlines in filenames,
88 88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90 90
91 91 _checkforbidden(files)
92 92 return ''.join(lines)
93 93
94 94 def _textv2(it):
95 95 files = []
96 96 lines = ['\0\n']
97 97 prevf = ''
98 98 for f, n, fl in it:
99 99 files.append(f)
100 100 stem = os.path.commonprefix([prevf, f])
101 101 stemlen = min(len(stem), 255)
102 102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 103 prevf = f
104 104 _checkforbidden(files)
105 105 return ''.join(lines)
106 106
107 107 class _lazymanifest(dict):
108 108 """This is the pure implementation of lazymanifest.
109 109
110 110 It has not been optimized *at all* and is not lazy.
111 111 """
112 112
113 113 def __init__(self, data):
114 114 dict.__init__(self)
115 115 for f, n, fl in _parse(data):
116 116 self[f] = n, fl
117 117
118 118 def __setitem__(self, k, v):
119 119 node, flag = v
120 120 assert node is not None
121 121 if len(node) > 21:
122 122 node = node[:21] # match c implementation behavior
123 123 dict.__setitem__(self, k, (node, flag))
124 124
125 125 def __iter__(self):
126 126 return iter(sorted(dict.keys(self)))
127 127
128 128 def iterkeys(self):
129 129 return iter(sorted(dict.keys(self)))
130 130
131 131 def iterentries(self):
132 132 return ((f, e[0], e[1]) for f, e in sorted(self.iteritems()))
133 133
134 134 def copy(self):
135 135 c = _lazymanifest('')
136 136 c.update(self)
137 137 return c
138 138
139 139 def diff(self, m2, clean=False):
140 140 '''Finds changes between the current manifest and m2.'''
141 141 diff = {}
142 142
143 143 for fn, e1 in self.iteritems():
144 144 if fn not in m2:
145 145 diff[fn] = e1, (None, '')
146 146 else:
147 147 e2 = m2[fn]
148 148 if e1 != e2:
149 149 diff[fn] = e1, e2
150 150 elif clean:
151 151 diff[fn] = None
152 152
153 153 for fn, e2 in m2.iteritems():
154 154 if fn not in self:
155 155 diff[fn] = (None, ''), e2
156 156
157 157 return diff
158 158
159 159 def filtercopy(self, filterfn):
160 160 c = _lazymanifest('')
161 161 for f, n, fl in self.iterentries():
162 162 if filterfn(f):
163 163 c[f] = n, fl
164 164 return c
165 165
166 166 def text(self):
167 167 """Get the full data of this manifest as a bytestring."""
168 168 return _textv1(self.iterentries())
169 169
170 170 try:
171 171 _lazymanifest = parsers.lazymanifest
172 172 except AttributeError:
173 173 pass
174 174
175 175 class manifestdict(object):
176 176 def __init__(self, data=''):
177 177 if data.startswith('\0'):
178 178 #_lazymanifest can not parse v2
179 179 self._lm = _lazymanifest('')
180 180 for f, n, fl in _parsev2(data):
181 181 self._lm[f] = n, fl
182 182 else:
183 183 self._lm = _lazymanifest(data)
184 184
185 185 def __getitem__(self, key):
186 186 return self._lm[key][0]
187 187
188 188 def find(self, key):
189 189 return self._lm[key]
190 190
191 191 def __len__(self):
192 192 return len(self._lm)
193 193
194 194 def __setitem__(self, key, node):
195 195 self._lm[key] = node, self.flags(key, '')
196 196
197 197 def __contains__(self, key):
198 198 return key in self._lm
199 199
200 200 def __delitem__(self, key):
201 201 del self._lm[key]
202 202
203 203 def __iter__(self):
204 204 return self._lm.__iter__()
205 205
206 206 def iterkeys(self):
207 207 return self._lm.iterkeys()
208 208
209 209 def keys(self):
210 210 return list(self.iterkeys())
211 211
212 212 def filesnotin(self, m2):
213 213 '''Set of files in this manifest that are not in the other'''
214 214 files = set(self)
215 215 files.difference_update(m2)
216 216 return files
217 217
218 218 @propertycache
219 219 def _dirs(self):
220 220 return util.dirs(self)
221 221
222 222 def dirs(self):
223 223 return self._dirs
224 224
225 225 def hasdir(self, dir):
226 226 return dir in self._dirs
227 227
228 228 def _filesfastpath(self, match):
229 229 '''Checks whether we can correctly and quickly iterate over matcher
230 230 files instead of over manifest files.'''
231 231 files = match.files()
232 232 return (len(files) < 100 and (match.isexact() or
233 233 (match.prefix() and all(fn in self for fn in files))))
234 234
235 235 def walk(self, match):
236 236 '''Generates matching file names.
237 237
238 238 Equivalent to manifest.matches(match).iterkeys(), but without creating
239 239 an entirely new manifest.
240 240
241 241 It also reports nonexistent files by marking them bad with match.bad().
242 242 '''
243 243 if match.always():
244 244 for f in iter(self):
245 245 yield f
246 246 return
247 247
248 248 fset = set(match.files())
249 249
250 250 # avoid the entire walk if we're only looking for specific files
251 251 if self._filesfastpath(match):
252 252 for fn in sorted(fset):
253 253 yield fn
254 254 return
255 255
256 256 for fn in self:
257 257 if fn in fset:
258 258 # specified pattern is the exact name
259 259 fset.remove(fn)
260 260 if match(fn):
261 261 yield fn
262 262
263 263 # for dirstate.walk, files=['.'] means "walk the whole tree".
264 264 # follow that here, too
265 265 fset.discard('.')
266 266
267 267 for fn in sorted(fset):
268 268 if not self.hasdir(fn):
269 269 match.bad(fn, None)
270 270
271 271 def matches(self, match):
272 272 '''generate a new manifest filtered by the match argument'''
273 273 if match.always():
274 274 return self.copy()
275 275
276 276 if self._filesfastpath(match):
277 277 m = manifestdict()
278 278 lm = self._lm
279 279 for fn in match.files():
280 280 if fn in lm:
281 281 m._lm[fn] = lm[fn]
282 282 return m
283 283
284 284 m = manifestdict()
285 285 m._lm = self._lm.filtercopy(match)
286 286 return m
287 287
288 288 def diff(self, m2, clean=False):
289 289 '''Finds changes between the current manifest and m2.
290 290
291 291 Args:
292 292 m2: the manifest to which this manifest should be compared.
293 293 clean: if true, include files unchanged between these manifests
294 294 with a None value in the returned dictionary.
295 295
296 296 The result is returned as a dict with filename as key and
297 297 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
298 298 nodeid in the current/other manifest and fl1/fl2 is the flag
299 299 in the current/other manifest. Where the file does not exist,
300 300 the nodeid will be None and the flags will be the empty
301 301 string.
302 302 '''
303 303 return self._lm.diff(m2._lm, clean)
304 304
305 305 def setflag(self, key, flag):
306 306 self._lm[key] = self[key], flag
307 307
308 308 def get(self, key, default=None):
309 309 try:
310 310 return self._lm[key][0]
311 311 except KeyError:
312 312 return default
313 313
314 314 def flags(self, key, default=''):
315 315 try:
316 316 return self._lm[key][1]
317 317 except KeyError:
318 318 return default
319 319
320 320 def copy(self):
321 321 c = manifestdict()
322 322 c._lm = self._lm.copy()
323 323 return c
324 324
325 325 def iteritems(self):
326 326 return (x[:2] for x in self._lm.iterentries())
327 327
328 328 def iterentries(self):
329 329 return self._lm.iterentries()
330 330
331 331 def text(self, usemanifestv2=False):
332 332 if usemanifestv2:
333 333 return _textv2(self._lm.iterentries())
334 334 else:
335 335 # use (probably) native version for v1
336 336 return self._lm.text()
337 337
338 338 def fastdelta(self, base, changes):
339 339 """Given a base manifest text as an array.array and a list of changes
340 340 relative to that text, compute a delta that can be used by revlog.
341 341 """
342 342 delta = []
343 343 dstart = None
344 344 dend = None
345 345 dline = [""]
346 346 start = 0
347 347 # zero copy representation of base as a buffer
348 348 addbuf = util.buffer(base)
349 349
350 350 changes = list(changes)
351 351 if len(changes) < 1000:
352 352 # start with a readonly loop that finds the offset of
353 353 # each line and creates the deltas
354 354 for f, todelete in changes:
355 355 # bs will either be the index of the item or the insert point
356 356 start, end = _msearch(addbuf, f, start)
357 357 if not todelete:
358 358 h, fl = self._lm[f]
359 359 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
360 360 else:
361 361 if start == end:
362 362 # item we want to delete was not found, error out
363 363 raise AssertionError(
364 364 _("failed to remove %s from manifest") % f)
365 365 l = ""
366 366 if dstart is not None and dstart <= start and dend >= start:
367 367 if dend < end:
368 368 dend = end
369 369 if l:
370 370 dline.append(l)
371 371 else:
372 372 if dstart is not None:
373 373 delta.append([dstart, dend, "".join(dline)])
374 374 dstart = start
375 375 dend = end
376 376 dline = [l]
377 377
378 378 if dstart is not None:
379 379 delta.append([dstart, dend, "".join(dline)])
380 380 # apply the delta to the base, and get a delta for addrevision
381 381 deltatext, arraytext = _addlistdelta(base, delta)
382 382 else:
383 383 # For large changes, it's much cheaper to just build the text and
384 384 # diff it.
385 385 arraytext = array.array('c', self.text())
386 386 deltatext = mdiff.textdiff(base, arraytext)
387 387
388 388 return arraytext, deltatext
389 389
390 390 def _msearch(m, s, lo=0, hi=None):
391 391 '''return a tuple (start, end) that says where to find s within m.
392 392
393 393 If the string is found m[start:end] are the line containing
394 394 that string. If start == end the string was not found and
395 395 they indicate the proper sorted insertion point.
396 396
397 397 m should be a buffer or a string
398 398 s is a string'''
399 399 def advance(i, c):
400 400 while i < lenm and m[i] != c:
401 401 i += 1
402 402 return i
403 403 if not s:
404 404 return (lo, lo)
405 405 lenm = len(m)
406 406 if not hi:
407 407 hi = lenm
408 408 while lo < hi:
409 409 mid = (lo + hi) // 2
410 410 start = mid
411 411 while start > 0 and m[start - 1] != '\n':
412 412 start -= 1
413 413 end = advance(start, '\0')
414 414 if m[start:end] < s:
415 415 # we know that after the null there are 40 bytes of sha1
416 416 # this translates to the bisect lo = mid + 1
417 417 lo = advance(end + 40, '\n') + 1
418 418 else:
419 419 # this translates to the bisect hi = mid
420 420 hi = start
421 421 end = advance(lo, '\0')
422 422 found = m[lo:end]
423 423 if s == found:
424 424 # we know that after the null there are 40 bytes of sha1
425 425 end = advance(end + 40, '\n')
426 426 return (lo, end + 1)
427 427 else:
428 428 return (lo, lo)
429 429
430 430 def _checkforbidden(l):
431 431 """Check filenames for illegal characters."""
432 432 for f in l:
433 433 if '\n' in f or '\r' in f:
434 434 raise error.RevlogError(
435 435 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
436 436
437 437
438 438 # apply the changes collected during the bisect loop to our addlist
439 439 # return a delta suitable for addrevision
440 440 def _addlistdelta(addlist, x):
441 441 # for large addlist arrays, building a new array is cheaper
442 442 # than repeatedly modifying the existing one
443 443 currentposition = 0
444 444 newaddlist = array.array('c')
445 445
446 446 for start, end, content in x:
447 447 newaddlist += addlist[currentposition:start]
448 448 if content:
449 449 newaddlist += array.array('c', content)
450 450
451 451 currentposition = end
452 452
453 453 newaddlist += addlist[currentposition:]
454 454
455 455 deltatext = "".join(struct.pack(">lll", start, end, len(content))
456 456 + content for start, end, content in x)
457 457 return deltatext, newaddlist
458 458
459 459 def _splittopdir(f):
460 460 if '/' in f:
461 461 dir, subpath = f.split('/', 1)
462 462 return dir + '/', subpath
463 463 else:
464 464 return '', f
465 465
466 466 _noop = lambda s: None
467 467
468 468 class treemanifest(object):
469 469 def __init__(self, dir='', text=''):
470 470 self._dir = dir
471 471 self._node = revlog.nullid
472 472 self._loadfunc = _noop
473 473 self._copyfunc = _noop
474 474 self._dirty = False
475 475 self._dirs = {}
476 476 # Using _lazymanifest here is a little slower than plain old dicts
477 477 self._files = {}
478 478 self._flags = {}
479 479 if text:
480 480 def readsubtree(subdir, subm):
481 481 raise AssertionError('treemanifest constructor only accepts '
482 482 'flat manifests')
483 483 self.parse(text, readsubtree)
484 484 self._dirty = True # Mark flat manifest dirty after parsing
485 485
486 486 def _subpath(self, path):
487 487 return self._dir + path
488 488
489 489 def __len__(self):
490 490 self._load()
491 491 size = len(self._files)
492 492 for m in self._dirs.values():
493 493 size += m.__len__()
494 494 return size
495 495
496 496 def _isempty(self):
497 497 self._load() # for consistency; already loaded by all callers
498 498 return (not self._files and (not self._dirs or
499 499 all(m._isempty() for m in self._dirs.values())))
500 500
501 501 def __repr__(self):
502 502 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
503 503 (self._dir, revlog.hex(self._node),
504 504 bool(self._loadfunc is _noop),
505 505 self._dirty, id(self)))
506 506
507 507 def dir(self):
508 508 '''The directory that this tree manifest represents, including a
509 509 trailing '/'. Empty string for the repo root directory.'''
510 510 return self._dir
511 511
512 512 def node(self):
513 513 '''This node of this instance. nullid for unsaved instances. Should
514 514 be updated when the instance is read or written from a revlog.
515 515 '''
516 516 assert not self._dirty
517 517 return self._node
518 518
519 519 def setnode(self, node):
520 520 self._node = node
521 521 self._dirty = False
522 522
523 523 def iterentries(self):
524 524 self._load()
525 525 for p, n in sorted(self._dirs.items() + self._files.items()):
526 526 if p in self._files:
527 527 yield self._subpath(p), n, self._flags.get(p, '')
528 528 else:
529 529 for x in n.iterentries():
530 530 yield x
531 531
532 532 def iteritems(self):
533 533 self._load()
534 534 for p, n in sorted(self._dirs.items() + self._files.items()):
535 535 if p in self._files:
536 536 yield self._subpath(p), n
537 537 else:
538 538 for f, sn in n.iteritems():
539 539 yield f, sn
540 540
541 541 def iterkeys(self):
542 542 self._load()
543 543 for p in sorted(self._dirs.keys() + self._files.keys()):
544 544 if p in self._files:
545 545 yield self._subpath(p)
546 546 else:
547 547 for f in self._dirs[p].iterkeys():
548 548 yield f
549 549
550 550 def keys(self):
551 551 return list(self.iterkeys())
552 552
553 553 def __iter__(self):
554 554 return self.iterkeys()
555 555
556 556 def __contains__(self, f):
557 557 if f is None:
558 558 return False
559 559 self._load()
560 560 dir, subpath = _splittopdir(f)
561 561 if dir:
562 562 if dir not in self._dirs:
563 563 return False
564 564 return self._dirs[dir].__contains__(subpath)
565 565 else:
566 566 return f in self._files
567 567
568 568 def get(self, f, default=None):
569 569 self._load()
570 570 dir, subpath = _splittopdir(f)
571 571 if dir:
572 572 if dir not in self._dirs:
573 573 return default
574 574 return self._dirs[dir].get(subpath, default)
575 575 else:
576 576 return self._files.get(f, default)
577 577
578 578 def __getitem__(self, f):
579 579 self._load()
580 580 dir, subpath = _splittopdir(f)
581 581 if dir:
582 582 return self._dirs[dir].__getitem__(subpath)
583 583 else:
584 584 return self._files[f]
585 585
586 586 def flags(self, f):
587 587 self._load()
588 588 dir, subpath = _splittopdir(f)
589 589 if dir:
590 590 if dir not in self._dirs:
591 591 return ''
592 592 return self._dirs[dir].flags(subpath)
593 593 else:
594 594 if f in self._dirs:
595 595 return ''
596 596 return self._flags.get(f, '')
597 597
598 598 def find(self, f):
599 599 self._load()
600 600 dir, subpath = _splittopdir(f)
601 601 if dir:
602 602 return self._dirs[dir].find(subpath)
603 603 else:
604 604 return self._files[f], self._flags.get(f, '')
605 605
606 606 def __delitem__(self, f):
607 607 self._load()
608 608 dir, subpath = _splittopdir(f)
609 609 if dir:
610 610 self._dirs[dir].__delitem__(subpath)
611 611 # If the directory is now empty, remove it
612 612 if self._dirs[dir]._isempty():
613 613 del self._dirs[dir]
614 614 else:
615 615 del self._files[f]
616 616 if f in self._flags:
617 617 del self._flags[f]
618 618 self._dirty = True
619 619
620 620 def __setitem__(self, f, n):
621 621 assert n is not None
622 622 self._load()
623 623 dir, subpath = _splittopdir(f)
624 624 if dir:
625 625 if dir not in self._dirs:
626 626 self._dirs[dir] = treemanifest(self._subpath(dir))
627 627 self._dirs[dir].__setitem__(subpath, n)
628 628 else:
629 629 self._files[f] = n[:21] # to match manifestdict's behavior
630 630 self._dirty = True
631 631
632 632 def _load(self):
633 633 if self._loadfunc is not _noop:
634 634 lf, self._loadfunc = self._loadfunc, _noop
635 635 lf(self)
636 636 elif self._copyfunc is not _noop:
637 637 cf, self._copyfunc = self._copyfunc, _noop
638 638 cf(self)
639 639
640 640 def setflag(self, f, flags):
641 641 """Set the flags (symlink, executable) for path f."""
642 642 self._load()
643 643 dir, subpath = _splittopdir(f)
644 644 if dir:
645 645 if dir not in self._dirs:
646 646 self._dirs[dir] = treemanifest(self._subpath(dir))
647 647 self._dirs[dir].setflag(subpath, flags)
648 648 else:
649 649 self._flags[f] = flags
650 650 self._dirty = True
651 651
652 652 def copy(self):
653 653 copy = treemanifest(self._dir)
654 654 copy._node = self._node
655 655 copy._dirty = self._dirty
656 656 if self._copyfunc is _noop:
657 657 def _copyfunc(s):
658 658 self._load()
659 659 for d in self._dirs:
660 660 s._dirs[d] = self._dirs[d].copy()
661 661 s._files = dict.copy(self._files)
662 662 s._flags = dict.copy(self._flags)
663 663 if self._loadfunc is _noop:
664 664 _copyfunc(copy)
665 665 else:
666 666 copy._copyfunc = _copyfunc
667 667 else:
668 668 copy._copyfunc = self._copyfunc
669 669 return copy
670 670
671 671 def filesnotin(self, m2):
672 672 '''Set of files in this manifest that are not in the other'''
673 673 files = set()
674 674 def _filesnotin(t1, t2):
675 675 if t1._node == t2._node and not t1._dirty and not t2._dirty:
676 676 return
677 677 t1._load()
678 678 t2._load()
679 679 for d, m1 in t1._dirs.iteritems():
680 680 if d in t2._dirs:
681 681 m2 = t2._dirs[d]
682 682 _filesnotin(m1, m2)
683 683 else:
684 684 files.update(m1.iterkeys())
685 685
686 686 for fn in t1._files.iterkeys():
687 687 if fn not in t2._files:
688 688 files.add(t1._subpath(fn))
689 689
690 690 _filesnotin(self, m2)
691 691 return files
692 692
693 693 @propertycache
694 694 def _alldirs(self):
695 695 return util.dirs(self)
696 696
697 697 def dirs(self):
698 698 return self._alldirs
699 699
700 700 def hasdir(self, dir):
701 701 self._load()
702 702 topdir, subdir = _splittopdir(dir)
703 703 if topdir:
704 704 if topdir in self._dirs:
705 705 return self._dirs[topdir].hasdir(subdir)
706 706 return False
707 707 return (dir + '/') in self._dirs
708 708
709 709 def walk(self, match):
710 710 '''Generates matching file names.
711 711
712 712 Equivalent to manifest.matches(match).iterkeys(), but without creating
713 713 an entirely new manifest.
714 714
715 715 It also reports nonexistent files by marking them bad with match.bad().
716 716 '''
717 717 if match.always():
718 718 for f in iter(self):
719 719 yield f
720 720 return
721 721
722 722 fset = set(match.files())
723 723
724 724 for fn in self._walk(match):
725 725 if fn in fset:
726 726 # specified pattern is the exact name
727 727 fset.remove(fn)
728 728 yield fn
729 729
730 730 # for dirstate.walk, files=['.'] means "walk the whole tree".
731 731 # follow that here, too
732 732 fset.discard('.')
733 733
734 734 for fn in sorted(fset):
735 735 if not self.hasdir(fn):
736 736 match.bad(fn, None)
737 737
738 738 def _walk(self, match):
739 739 '''Recursively generates matching file names for walk().'''
740 740 if not match.visitdir(self._dir[:-1] or '.'):
741 741 return
742 742
743 743 # yield this dir's files and walk its submanifests
744 744 self._load()
745 745 for p in sorted(self._dirs.keys() + self._files.keys()):
746 746 if p in self._files:
747 747 fullp = self._subpath(p)
748 748 if match(fullp):
749 749 yield fullp
750 750 else:
751 751 for f in self._dirs[p]._walk(match):
752 752 yield f
753 753
754 754 def matches(self, match):
755 755 '''generate a new manifest filtered by the match argument'''
756 756 if match.always():
757 757 return self.copy()
758 758
759 759 return self._matches(match)
760 760
761 761 def _matches(self, match):
762 762 '''recursively generate a new manifest filtered by the match argument.
763 763 '''
764 764
765 765 visit = match.visitdir(self._dir[:-1] or '.')
766 766 if visit == 'all':
767 767 return self.copy()
768 768 ret = treemanifest(self._dir)
769 769 if not visit:
770 770 return ret
771 771
772 772 self._load()
773 773 for fn in self._files:
774 774 fullp = self._subpath(fn)
775 775 if not match(fullp):
776 776 continue
777 777 ret._files[fn] = self._files[fn]
778 778 if fn in self._flags:
779 779 ret._flags[fn] = self._flags[fn]
780 780
781 781 for dir, subm in self._dirs.iteritems():
782 782 m = subm._matches(match)
783 783 if not m._isempty():
784 784 ret._dirs[dir] = m
785 785
786 786 if not ret._isempty():
787 787 ret._dirty = True
788 788 return ret
789 789
790 790 def diff(self, m2, clean=False):
791 791 '''Finds changes between the current manifest and m2.
792 792
793 793 Args:
794 794 m2: the manifest to which this manifest should be compared.
795 795 clean: if true, include files unchanged between these manifests
796 796 with a None value in the returned dictionary.
797 797
798 798 The result is returned as a dict with filename as key and
799 799 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
800 800 nodeid in the current/other manifest and fl1/fl2 is the flag
801 801 in the current/other manifest. Where the file does not exist,
802 802 the nodeid will be None and the flags will be the empty
803 803 string.
804 804 '''
805 805 result = {}
806 806 emptytree = treemanifest()
807 807 def _diff(t1, t2):
808 808 if t1._node == t2._node and not t1._dirty and not t2._dirty:
809 809 return
810 810 t1._load()
811 811 t2._load()
812 812 for d, m1 in t1._dirs.iteritems():
813 813 m2 = t2._dirs.get(d, emptytree)
814 814 _diff(m1, m2)
815 815
816 816 for d, m2 in t2._dirs.iteritems():
817 817 if d not in t1._dirs:
818 818 _diff(emptytree, m2)
819 819
820 820 for fn, n1 in t1._files.iteritems():
821 821 fl1 = t1._flags.get(fn, '')
822 822 n2 = t2._files.get(fn, None)
823 823 fl2 = t2._flags.get(fn, '')
824 824 if n1 != n2 or fl1 != fl2:
825 825 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
826 826 elif clean:
827 827 result[t1._subpath(fn)] = None
828 828
829 829 for fn, n2 in t2._files.iteritems():
830 830 if fn not in t1._files:
831 831 fl2 = t2._flags.get(fn, '')
832 832 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
833 833
834 834 _diff(self, m2)
835 835 return result
836 836
837 837 def unmodifiedsince(self, m2):
838 838 return not self._dirty and not m2._dirty and self._node == m2._node
839 839
840 840 def parse(self, text, readsubtree):
841 841 for f, n, fl in _parse(text):
842 842 if fl == 't':
843 843 f = f + '/'
844 844 self._dirs[f] = readsubtree(self._subpath(f), n)
845 845 elif '/' in f:
846 846 # This is a flat manifest, so use __setitem__ and setflag rather
847 847 # than assigning directly to _files and _flags, so we can
848 848 # assign a path in a subdirectory, and to mark dirty (compared
849 849 # to nullid).
850 850 self[f] = n
851 851 if fl:
852 852 self.setflag(f, fl)
853 853 else:
854 854 # Assigning to _files and _flags avoids marking as dirty,
855 855 # and should be a little faster.
856 856 self._files[f] = n
857 857 if fl:
858 858 self._flags[f] = fl
859 859
860 860 def text(self, usemanifestv2=False):
861 861 """Get the full data of this manifest as a bytestring."""
862 862 self._load()
863 863 return _text(self.iterentries(), usemanifestv2)
864 864
865 865 def dirtext(self, usemanifestv2=False):
866 866 """Get the full data of this directory as a bytestring. Make sure that
867 867 any submanifests have been written first, so their nodeids are correct.
868 868 """
869 869 self._load()
870 870 flags = self.flags
871 871 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
872 872 files = [(f, self._files[f], flags(f)) for f in self._files]
873 873 return _text(sorted(dirs + files), usemanifestv2)
874 874
875 875 def read(self, gettext, readsubtree):
876 876 def _load_for_read(s):
877 877 s.parse(gettext(), readsubtree)
878 878 s._dirty = False
879 879 self._loadfunc = _load_for_read
880 880
881 881 def writesubtrees(self, m1, m2, writesubtree):
882 882 self._load() # for consistency; should never have any effect here
883 883 emptytree = treemanifest()
884 884 for d, subm in self._dirs.iteritems():
885 885 subp1 = m1._dirs.get(d, emptytree)._node
886 886 subp2 = m2._dirs.get(d, emptytree)._node
887 887 if subp1 == revlog.nullid:
888 888 subp1, subp2 = subp2, subp1
889 889 writesubtree(subm, subp1, subp2)
890 890
891 891 class manifest(revlog.revlog):
892 892 def __init__(self, opener, dir='', dirlogcache=None):
893 893 '''The 'dir' and 'dirlogcache' arguments are for internal use by
894 894 manifest.manifest only. External users should create a root manifest
895 895 log with manifest.manifest(opener) and call dirlog() on it.
896 896 '''
897 897 # During normal operations, we expect to deal with not more than four
898 898 # revs at a time (such as during commit --amend). When rebasing large
899 899 # stacks of commits, the number can go up, hence the config knob below.
900 900 cachesize = 4
901 901 usetreemanifest = False
902 902 usemanifestv2 = False
903 903 opts = getattr(opener, 'options', None)
904 904 if opts is not None:
905 905 cachesize = opts.get('manifestcachesize', cachesize)
906 906 usetreemanifest = opts.get('treemanifest', usetreemanifest)
907 907 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
908 908 self._mancache = util.lrucachedict(cachesize)
909 909 self._treeinmem = usetreemanifest
910 910 self._treeondisk = usetreemanifest
911 911 self._usemanifestv2 = usemanifestv2
912 912 indexfile = "00manifest.i"
913 913 if dir:
914 914 assert self._treeondisk
915 915 if not dir.endswith('/'):
916 916 dir = dir + '/'
917 917 indexfile = "meta/" + dir + "00manifest.i"
918 918 revlog.revlog.__init__(self, opener, indexfile)
919 919 self._dir = dir
920 920 # The dirlogcache is kept on the root manifest log
921 921 if dir:
922 922 self._dirlogcache = dirlogcache
923 923 else:
924 924 self._dirlogcache = {'': self}
925 925
926 926 def _newmanifest(self, data=''):
927 927 if self._treeinmem:
928 928 return treemanifest(self._dir, data)
929 929 return manifestdict(data)
930 930
931 931 def dirlog(self, dir):
932 932 if dir:
933 933 assert self._treeondisk
934 934 if dir not in self._dirlogcache:
935 935 self._dirlogcache[dir] = manifest(self.opener, dir,
936 936 self._dirlogcache)
937 937 return self._dirlogcache[dir]
938 938
939 939 def _slowreaddelta(self, node):
940 940 r0 = self.deltaparent(self.rev(node))
941 941 m0 = self.read(self.node(r0))
942 942 m1 = self.read(node)
943 943 md = self._newmanifest()
944 944 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
945 945 if n1:
946 946 md[f] = n1
947 947 if fl1:
948 948 md.setflag(f, fl1)
949 949 return md
950 950
951 951 def readdelta(self, node):
952 952 if self._usemanifestv2 or self._treeondisk:
953 953 return self._slowreaddelta(node)
954 954 r = self.rev(node)
955 955 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
956 956 return self._newmanifest(d)
957 957
958 958 def readshallowdelta(self, node):
959 959 '''For flat manifests, this is the same as readdelta(). For
960 960 treemanifests, this will read the delta for this revlog's directory,
961 961 without recursively reading subdirectory manifests. Instead, any
962 962 subdirectory entry will be reported as it appears in the manifests, i.e.
963 963 the subdirectory will be reported among files and distinguished only by
964 964 its 't' flag.'''
965 965 if not self._treeondisk:
966 966 return self.readdelta(node)
967 967 if self._usemanifestv2:
968 968 raise error.Abort(
969 969 "readshallowdelta() not implemented for manifestv2")
970 970 r = self.rev(node)
971 971 d = mdiff.patchtext(self.revdiff(self.deltaparent(r), r))
972 972 return manifestdict(d)
973 973
974 974 def readfast(self, node):
975 975 '''use the faster of readdelta or read
976 976
977 977 This will return a manifest which is either only the files
978 978 added/modified relative to p1, or all files in the
979 979 manifest. Which one is returned depends on the codepath used
980 980 to retrieve the data.
981 981 '''
982 982 r = self.rev(node)
983 983 deltaparent = self.deltaparent(r)
984 984 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
985 985 return self.readdelta(node)
986 986 return self.read(node)
987 987
988 def readshallowfast(self, node):
989 '''like readfast(), but calls readshallowdelta() instead of readdelta()
990 '''
991 r = self.rev(node)
992 deltaparent = self.deltaparent(r)
993 if deltaparent != revlog.nullrev and deltaparent in self.parentrevs(r):
994 return self.readshallowdelta(node)
995 return self.readshallow(node)
996
988 997 def read(self, node):
989 998 if node == revlog.nullid:
990 999 return self._newmanifest() # don't upset local cache
991 1000 if node in self._mancache:
992 1001 return self._mancache[node][0]
993 1002 if self._treeondisk:
994 1003 def gettext():
995 1004 return self.revision(node)
996 1005 def readsubtree(dir, subm):
997 1006 return self.dirlog(dir).read(subm)
998 1007 m = self._newmanifest()
999 1008 m.read(gettext, readsubtree)
1000 1009 m.setnode(node)
1001 1010 arraytext = None
1002 1011 else:
1003 1012 text = self.revision(node)
1004 1013 m = self._newmanifest(text)
1005 1014 arraytext = array.array('c', text)
1006 1015 self._mancache[node] = (m, arraytext)
1007 1016 return m
1008 1017
1018 def readshallow(self, node):
1019 '''Reads the manifest in this directory. When using flat manifests,
1020 this manifest will generally have files in subdirectories in it. Does
1021 not cache the manifest as the callers generally do not read the same
1022 version twice.'''
1023 return manifestdict(self.revision(node))
1024
1009 1025 def find(self, node, f):
1010 1026 '''look up entry for a single file efficiently.
1011 1027 return (node, flags) pair if found, (None, None) if not.'''
1012 1028 m = self.read(node)
1013 1029 try:
1014 1030 return m.find(f)
1015 1031 except KeyError:
1016 1032 return None, None
1017 1033
1018 1034 def add(self, m, transaction, link, p1, p2, added, removed):
1019 1035 if (p1 in self._mancache and not self._treeinmem
1020 1036 and not self._usemanifestv2):
1021 1037 # If our first parent is in the manifest cache, we can
1022 1038 # compute a delta here using properties we know about the
1023 1039 # manifest up-front, which may save time later for the
1024 1040 # revlog layer.
1025 1041
1026 1042 _checkforbidden(added)
1027 1043 # combine the changed lists into one sorted iterator
1028 1044 work = heapq.merge([(x, False) for x in added],
1029 1045 [(x, True) for x in removed])
1030 1046
1031 1047 arraytext, deltatext = m.fastdelta(self._mancache[p1][1], work)
1032 1048 cachedelta = self.rev(p1), deltatext
1033 1049 text = util.buffer(arraytext)
1034 1050 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1035 1051 else:
1036 1052 # The first parent manifest isn't already loaded, so we'll
1037 1053 # just encode a fulltext of the manifest and pass that
1038 1054 # through to the revlog layer, and let it handle the delta
1039 1055 # process.
1040 1056 if self._treeondisk:
1041 1057 m1 = self.read(p1)
1042 1058 m2 = self.read(p2)
1043 1059 n = self._addtree(m, transaction, link, m1, m2)
1044 1060 arraytext = None
1045 1061 else:
1046 1062 text = m.text(self._usemanifestv2)
1047 1063 n = self.addrevision(text, transaction, link, p1, p2)
1048 1064 arraytext = array.array('c', text)
1049 1065
1050 1066 self._mancache[n] = (m, arraytext)
1051 1067
1052 1068 return n
1053 1069
1054 1070 def _addtree(self, m, transaction, link, m1, m2):
1055 1071 # If the manifest is unchanged compared to one parent,
1056 1072 # don't write a new revision
1057 1073 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1058 1074 return m.node()
1059 1075 def writesubtree(subm, subp1, subp2):
1060 1076 sublog = self.dirlog(subm.dir())
1061 1077 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1062 1078 m.writesubtrees(m1, m2, writesubtree)
1063 1079 text = m.dirtext(self._usemanifestv2)
1064 1080 # Double-check whether contents are unchanged to one parent
1065 1081 if text == m1.dirtext(self._usemanifestv2):
1066 1082 n = m1.node()
1067 1083 elif text == m2.dirtext(self._usemanifestv2):
1068 1084 n = m2.node()
1069 1085 else:
1070 1086 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1071 1087 # Save nodeid so parent manifest can calculate its nodeid
1072 1088 m.setnode(n)
1073 1089 return n
1074 1090
1075 1091 def clearcaches(self):
1076 1092 super(manifest, self).clearcaches()
1077 1093 self._mancache.clear()
1078 1094 self._dirlogcache = {'': self}
@@ -1,731 +1,738 b''
1 1 #require killdaemons
2 2
3 3 $ cat << EOF >> $HGRCPATH
4 4 > [format]
5 5 > usegeneraldelta=yes
6 6 > [ui]
7 7 > ssh=python "$TESTDIR/dummyssh"
8 8 > EOF
9 9
10 10 Set up repo
11 11
12 12 $ hg --config experimental.treemanifest=True init repo
13 13 $ cd repo
14 14
15 15 Requirements get set on init
16 16
17 17 $ grep treemanifest .hg/requires
18 18 treemanifest
19 19
20 20 Without directories, looks like any other repo
21 21
22 22 $ echo 0 > a
23 23 $ echo 0 > b
24 24 $ hg ci -Aqm initial
25 25 $ hg debugdata -m 0
26 26 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
27 27 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
28 28
29 29 Submanifest is stored in separate revlog
30 30
31 31 $ mkdir dir1
32 32 $ echo 1 > dir1/a
33 33 $ echo 1 > dir1/b
34 34 $ echo 1 > e
35 35 $ hg ci -Aqm 'add dir1'
36 36 $ hg debugdata -m 1
37 37 a\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
38 38 b\x00362fef284ce2ca02aecc8de6d5e8a1c3af0556fe (esc)
39 39 dir1\x008b3ffd73f901e83304c83d33132c8e774ceac44et (esc)
40 40 e\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
41 41 $ hg debugdata --dir dir1 0
42 42 a\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
43 43 b\x00b8e02f6433738021a065f94175c7cd23db5f05be (esc)
44 44
45 45 Can add nested directories
46 46
47 47 $ mkdir dir1/dir1
48 48 $ echo 2 > dir1/dir1/a
49 49 $ echo 2 > dir1/dir1/b
50 50 $ mkdir dir1/dir2
51 51 $ echo 2 > dir1/dir2/a
52 52 $ echo 2 > dir1/dir2/b
53 53 $ hg ci -Aqm 'add dir1/dir1'
54 54 $ hg files -r .
55 55 a
56 56 b
57 57 dir1/a (glob)
58 58 dir1/b (glob)
59 59 dir1/dir1/a (glob)
60 60 dir1/dir1/b (glob)
61 61 dir1/dir2/a (glob)
62 62 dir1/dir2/b (glob)
63 63 e
64 64
65 65 Revision is not created for unchanged directory
66 66
67 67 $ mkdir dir2
68 68 $ echo 3 > dir2/a
69 69 $ hg add dir2
70 70 adding dir2/a (glob)
71 71 $ hg debugindex --dir dir1 > before
72 72 $ hg ci -qm 'add dir2'
73 73 $ hg debugindex --dir dir1 > after
74 74 $ diff before after
75 75 $ rm before after
76 76
77 77 Removing directory does not create an revlog entry
78 78
79 79 $ hg rm dir1/dir1
80 80 removing dir1/dir1/a (glob)
81 81 removing dir1/dir1/b (glob)
82 82 $ hg debugindex --dir dir1/dir1 > before
83 83 $ hg ci -qm 'remove dir1/dir1'
84 84 $ hg debugindex --dir dir1/dir1 > after
85 85 $ diff before after
86 86 $ rm before after
87 87
88 88 Check that hg files (calls treemanifest.walk()) works
89 89 without loading all directory revlogs
90 90
91 91 $ hg co 'desc("add dir2")'
92 92 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
93 93 $ mv .hg/store/meta/dir2 .hg/store/meta/dir2-backup
94 94 $ hg files -r . dir1
95 95 dir1/a (glob)
96 96 dir1/b (glob)
97 97 dir1/dir1/a (glob)
98 98 dir1/dir1/b (glob)
99 99 dir1/dir2/a (glob)
100 100 dir1/dir2/b (glob)
101 101
102 102 Check that status between revisions works (calls treemanifest.matches())
103 103 without loading all directory revlogs
104 104
105 105 $ hg status --rev 'desc("add dir1")' --rev . dir1
106 106 A dir1/dir1/a
107 107 A dir1/dir1/b
108 108 A dir1/dir2/a
109 109 A dir1/dir2/b
110 110 $ mv .hg/store/meta/dir2-backup .hg/store/meta/dir2
111 111
112 112 Merge creates 2-parent revision of directory revlog
113 113
114 114 $ echo 5 > dir1/a
115 115 $ hg ci -Aqm 'modify dir1/a'
116 116 $ hg co '.^'
117 117 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
118 118 $ echo 6 > dir1/b
119 119 $ hg ci -Aqm 'modify dir1/b'
120 120 $ hg merge 'desc("modify dir1/a")'
121 121 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
122 122 (branch merge, don't forget to commit)
123 123 $ hg ci -m 'conflict-free merge involving dir1/'
124 124 $ cat dir1/a
125 125 5
126 126 $ cat dir1/b
127 127 6
128 128 $ hg debugindex --dir dir1
129 129 rev offset length delta linkrev nodeid p1 p2
130 130 0 0 54 -1 1 8b3ffd73f901 000000000000 000000000000
131 131 1 54 68 0 2 68e9d057c5a8 8b3ffd73f901 000000000000
132 132 2 122 12 1 4 4698198d2624 68e9d057c5a8 000000000000
133 133 3 134 55 1 5 44844058ccce 68e9d057c5a8 000000000000
134 134 4 189 55 1 6 bf3d9b744927 68e9d057c5a8 000000000000
135 135 5 244 55 4 7 dde7c0af2a03 bf3d9b744927 44844058ccce
136 136
137 137 Merge keeping directory from parent 1 does not create revlog entry. (Note that
138 138 dir1's manifest does change, but only because dir1/a's filelog changes.)
139 139
140 140 $ hg co 'desc("add dir2")'
141 141 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
142 142 $ echo 8 > dir2/a
143 143 $ hg ci -m 'modify dir2/a'
144 144 created new head
145 145
146 146 $ hg debugindex --dir dir2 > before
147 147 $ hg merge 'desc("modify dir1/a")'
148 148 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
149 149 (branch merge, don't forget to commit)
150 150 $ hg revert -r 'desc("modify dir2/a")' .
151 151 reverting dir1/a (glob)
152 152 $ hg ci -m 'merge, keeping parent 1'
153 153 $ hg debugindex --dir dir2 > after
154 154 $ diff before after
155 155 $ rm before after
156 156
157 157 Merge keeping directory from parent 2 does not create revlog entry. (Note that
158 158 dir2's manifest does change, but only because dir2/a's filelog changes.)
159 159
160 160 $ hg co 'desc("modify dir2/a")'
161 161 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
162 162 $ hg debugindex --dir dir1 > before
163 163 $ hg merge 'desc("modify dir1/a")'
164 164 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
165 165 (branch merge, don't forget to commit)
166 166 $ hg revert -r 'desc("modify dir1/a")' .
167 167 reverting dir2/a (glob)
168 168 $ hg ci -m 'merge, keeping parent 2'
169 169 created new head
170 170 $ hg debugindex --dir dir1 > after
171 171 $ diff before after
172 172 $ rm before after
173 173
174 174 Create flat source repo for tests with mixed flat/tree manifests
175 175
176 176 $ cd ..
177 177 $ hg init repo-flat
178 178 $ cd repo-flat
179 179
180 180 Create a few commits with flat manifest
181 181
182 182 $ echo 0 > a
183 183 $ echo 0 > b
184 184 $ echo 0 > e
185 185 $ for d in dir1 dir1/dir1 dir1/dir2 dir2
186 186 > do
187 187 > mkdir $d
188 188 > echo 0 > $d/a
189 189 > echo 0 > $d/b
190 190 > done
191 191 $ hg ci -Aqm initial
192 192
193 193 $ echo 1 > a
194 194 $ echo 1 > dir1/a
195 195 $ echo 1 > dir1/dir1/a
196 196 $ hg ci -Aqm 'modify on branch 1'
197 197
198 198 $ hg co 0
199 199 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 200 $ echo 2 > b
201 201 $ echo 2 > dir1/b
202 202 $ echo 2 > dir1/dir1/b
203 203 $ hg ci -Aqm 'modify on branch 2'
204 204
205 205 $ hg merge 1
206 206 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
207 207 (branch merge, don't forget to commit)
208 208 $ hg ci -m 'merge of flat manifests to new flat manifest'
209 209
210 210 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
211 211 $ cat hg.pid >> $DAEMON_PIDS
212 212
213 213 Create clone with tree manifests enabled
214 214
215 215 $ cd ..
216 216 $ hg clone --config experimental.treemanifest=1 \
217 217 > http://localhost:$HGPORT repo-mixed -r 1
218 218 adding changesets
219 219 adding manifests
220 220 adding file changes
221 221 added 2 changesets with 14 changes to 11 files
222 222 updating to branch default
223 223 11 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 224 $ cd repo-mixed
225 225 $ test -d .hg/store/meta
226 226 [1]
227 227 $ grep treemanifest .hg/requires
228 228 treemanifest
229 229
230 230 Should be possible to push updates from flat to tree manifest repo
231 231
232 232 $ hg -R ../repo-flat push ssh://user@dummy/repo-mixed
233 233 pushing to ssh://user@dummy/repo-mixed
234 234 searching for changes
235 235 remote: adding changesets
236 236 remote: adding manifests
237 237 remote: adding file changes
238 238 remote: added 2 changesets with 3 changes to 3 files
239 239
240 240 Commit should store revlog per directory
241 241
242 242 $ hg co 1
243 243 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 244 $ echo 3 > a
245 245 $ echo 3 > dir1/a
246 246 $ echo 3 > dir1/dir1/a
247 247 $ hg ci -m 'first tree'
248 248 created new head
249 249 $ find .hg/store/meta | sort
250 250 .hg/store/meta
251 251 .hg/store/meta/dir1
252 252 .hg/store/meta/dir1/00manifest.i
253 253 .hg/store/meta/dir1/dir1
254 254 .hg/store/meta/dir1/dir1/00manifest.i
255 255 .hg/store/meta/dir1/dir2
256 256 .hg/store/meta/dir1/dir2/00manifest.i
257 257 .hg/store/meta/dir2
258 258 .hg/store/meta/dir2/00manifest.i
259 259
260 260 Merge of two trees
261 261
262 262 $ hg co 2
263 263 6 files updated, 0 files merged, 0 files removed, 0 files unresolved
264 264 $ hg merge 1
265 265 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
266 266 (branch merge, don't forget to commit)
267 267 $ hg ci -m 'merge of flat manifests to new tree manifest'
268 268 created new head
269 269 $ hg diff -r 3
270 270
271 271 Parent of tree root manifest should be flat manifest, and two for merge
272 272
273 273 $ hg debugindex -m
274 274 rev offset length delta linkrev nodeid p1 p2
275 275 0 0 80 -1 0 40536115ed9e 000000000000 000000000000
276 276 1 80 83 0 1 f3376063c255 40536115ed9e 000000000000
277 277 2 163 89 0 2 5d9b9da231a2 40536115ed9e 000000000000
278 278 3 252 83 2 3 d17d663cbd8a 5d9b9da231a2 f3376063c255
279 279 4 335 124 1 4 51e32a8c60ee f3376063c255 000000000000
280 280 5 459 126 2 5 cc5baa78b230 5d9b9da231a2 f3376063c255
281 281
282 282
283 283 Status across flat/tree boundary should work
284 284
285 285 $ hg status --rev '.^' --rev .
286 286 M a
287 287 M dir1/a
288 288 M dir1/dir1/a
289 289
290 290
291 291 Turning off treemanifest config has no effect
292 292
293 293 $ hg debugindex --dir dir1
294 294 rev offset length delta linkrev nodeid p1 p2
295 295 0 0 127 -1 4 064927a0648a 000000000000 000000000000
296 296 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
297 297 $ echo 2 > dir1/a
298 298 $ hg --config experimental.treemanifest=False ci -qm 'modify dir1/a'
299 299 $ hg debugindex --dir dir1
300 300 rev offset length delta linkrev nodeid p1 p2
301 301 0 0 127 -1 4 064927a0648a 000000000000 000000000000
302 302 1 127 111 0 5 25ecb8cb8618 000000000000 000000000000
303 303 2 238 55 1 6 5b16163a30c6 25ecb8cb8618 000000000000
304 304
305 305 Stripping and recovering changes should work
306 306
307 307 $ hg st --change tip
308 308 M dir1/a
309 309 $ hg --config extensions.strip= strip tip
310 310 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
311 311 saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/51cfd7b1e13b-78a2f3ed-backup.hg (glob)
312 312 $ hg unbundle -q .hg/strip-backup/*
313 313 $ hg st --change tip
314 314 M dir1/a
315 315
316 316 Shelving and unshelving should work
317 317
318 318 $ echo foo >> dir1/a
319 319 $ hg --config extensions.shelve= shelve
320 320 shelved as default
321 321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 322 $ hg --config extensions.shelve= unshelve
323 323 unshelving change 'default'
324 324 $ hg diff --nodates
325 325 diff -r 708a273da119 dir1/a
326 326 --- a/dir1/a
327 327 +++ b/dir1/a
328 328 @@ -1,1 +1,2 @@
329 329 1
330 330 +foo
331 331
332 332 Pushing from treemanifest repo to an empty repo makes that a treemanifest repo
333 333
334 334 $ cd ..
335 335 $ hg init empty-repo
336 336 $ cat << EOF >> empty-repo/.hg/hgrc
337 337 > [experimental]
338 338 > changegroup3=yes
339 339 > EOF
340 340 $ grep treemanifest empty-repo/.hg/requires
341 341 [1]
342 342 $ hg push -R repo -r 0 empty-repo
343 343 pushing to empty-repo
344 344 searching for changes
345 345 adding changesets
346 346 adding manifests
347 347 adding file changes
348 348 added 1 changesets with 2 changes to 2 files
349 349 $ grep treemanifest empty-repo/.hg/requires
350 350 treemanifest
351 351
352 352 Pushing to an empty repo works
353 353
354 354 $ hg --config experimental.treemanifest=1 init clone
355 355 $ grep treemanifest clone/.hg/requires
356 356 treemanifest
357 357 $ hg push -R repo clone
358 358 pushing to clone
359 359 searching for changes
360 360 adding changesets
361 361 adding manifests
362 362 adding file changes
363 363 added 11 changesets with 15 changes to 10 files (+3 heads)
364 364 $ grep treemanifest clone/.hg/requires
365 365 treemanifest
366 $ hg -R clone verify
367 checking changesets
368 checking manifests
369 checking directory manifests
370 crosschecking files in changesets and manifests
371 checking files
372 10 files, 11 changesets, 15 total revisions
366 373
367 374 Create deeper repo with tree manifests.
368 375
369 376 $ hg --config experimental.treemanifest=True init deeprepo
370 377 $ cd deeprepo
371 378
372 379 $ mkdir .A
373 380 $ mkdir b
374 381 $ mkdir b/bar
375 382 $ mkdir b/bar/orange
376 383 $ mkdir b/bar/orange/fly
377 384 $ mkdir b/foo
378 385 $ mkdir b/foo/apple
379 386 $ mkdir b/foo/apple/bees
380 387
381 388 $ touch .A/one.txt
382 389 $ touch .A/two.txt
383 390 $ touch b/bar/fruits.txt
384 391 $ touch b/bar/orange/fly/gnat.py
385 392 $ touch b/bar/orange/fly/housefly.txt
386 393 $ touch b/foo/apple/bees/flower.py
387 394 $ touch c.txt
388 395 $ touch d.py
389 396
390 397 $ hg ci -Aqm 'initial'
391 398
392 399 We'll see that visitdir works by removing some treemanifest revlogs and running
393 400 the files command with various parameters.
394 401
395 402 Test files from the root.
396 403
397 404 $ hg files -r .
398 405 .A/one.txt (glob)
399 406 .A/two.txt (glob)
400 407 b/bar/fruits.txt (glob)
401 408 b/bar/orange/fly/gnat.py (glob)
402 409 b/bar/orange/fly/housefly.txt (glob)
403 410 b/foo/apple/bees/flower.py (glob)
404 411 c.txt
405 412 d.py
406 413
407 414 Excludes with a glob should not exclude everything from the glob's root
408 415
409 416 $ hg files -r . -X 'b/fo?' b
410 417 b/bar/fruits.txt (glob)
411 418 b/bar/orange/fly/gnat.py (glob)
412 419 b/bar/orange/fly/housefly.txt (glob)
413 420 $ cp -r .hg/store .hg/store-copy
414 421
415 422 Test files for a subdirectory.
416 423
417 424 $ rm -r .hg/store/meta/~2e_a
418 425 $ hg files -r . b
419 426 b/bar/fruits.txt (glob)
420 427 b/bar/orange/fly/gnat.py (glob)
421 428 b/bar/orange/fly/housefly.txt (glob)
422 429 b/foo/apple/bees/flower.py (glob)
423 430 $ cp -r .hg/store-copy/. .hg/store
424 431
425 432 Test files with just includes and excludes.
426 433
427 434 $ rm -r .hg/store/meta/~2e_a
428 435 $ rm -r .hg/store/meta/b/bar/orange/fly
429 436 $ rm -r .hg/store/meta/b/foo/apple/bees
430 437 $ hg files -r . -I path:b/bar -X path:b/bar/orange/fly -I path:b/foo -X path:b/foo/apple/bees
431 438 b/bar/fruits.txt (glob)
432 439 $ cp -r .hg/store-copy/. .hg/store
433 440
434 441 Test files for a subdirectory, excluding a directory within it.
435 442
436 443 $ rm -r .hg/store/meta/~2e_a
437 444 $ rm -r .hg/store/meta/b/foo
438 445 $ hg files -r . -X path:b/foo b
439 446 b/bar/fruits.txt (glob)
440 447 b/bar/orange/fly/gnat.py (glob)
441 448 b/bar/orange/fly/housefly.txt (glob)
442 449 $ cp -r .hg/store-copy/. .hg/store
443 450
444 451 Test files for a sub directory, including only a directory within it, and
445 452 including an unrelated directory.
446 453
447 454 $ rm -r .hg/store/meta/~2e_a
448 455 $ rm -r .hg/store/meta/b/foo
449 456 $ hg files -r . -I path:b/bar/orange -I path:a b
450 457 b/bar/orange/fly/gnat.py (glob)
451 458 b/bar/orange/fly/housefly.txt (glob)
452 459 $ cp -r .hg/store-copy/. .hg/store
453 460
454 461 Test files for a pattern, including a directory, and excluding a directory
455 462 within that.
456 463
457 464 $ rm -r .hg/store/meta/~2e_a
458 465 $ rm -r .hg/store/meta/b/foo
459 466 $ rm -r .hg/store/meta/b/bar/orange
460 467 $ hg files -r . glob:**.txt -I path:b/bar -X path:b/bar/orange
461 468 b/bar/fruits.txt (glob)
462 469 $ cp -r .hg/store-copy/. .hg/store
463 470
464 471 Add some more changes to the deep repo
465 472 $ echo narf >> b/bar/fruits.txt
466 473 $ hg ci -m narf
467 474 $ echo troz >> b/bar/orange/fly/gnat.py
468 475 $ hg ci -m troz
469 476
470 477 Verify works
471 478 $ hg verify
472 479 checking changesets
473 480 checking manifests
474 481 checking directory manifests
475 482 crosschecking files in changesets and manifests
476 483 checking files
477 484 8 files, 3 changesets, 10 total revisions
478 485
479 486 Dirlogs are included in fncache
480 487 $ grep meta/.A/00manifest.i .hg/store/fncache
481 488 meta/.A/00manifest.i
482 489
483 490 Rebuilt fncache includes dirlogs
484 491 $ rm .hg/store/fncache
485 492 $ hg debugrebuildfncache
486 493 adding data/.A/one.txt.i
487 494 adding data/.A/two.txt.i
488 495 adding data/b/bar/fruits.txt.i
489 496 adding data/b/bar/orange/fly/gnat.py.i
490 497 adding data/b/bar/orange/fly/housefly.txt.i
491 498 adding data/b/foo/apple/bees/flower.py.i
492 499 adding data/c.txt.i
493 500 adding data/d.py.i
494 501 adding meta/.A/00manifest.i
495 502 adding meta/b/00manifest.i
496 503 adding meta/b/bar/00manifest.i
497 504 adding meta/b/bar/orange/00manifest.i
498 505 adding meta/b/bar/orange/fly/00manifest.i
499 506 adding meta/b/foo/00manifest.i
500 507 adding meta/b/foo/apple/00manifest.i
501 508 adding meta/b/foo/apple/bees/00manifest.i
502 509 16 items added, 0 removed from fncache
503 510
504 511 Finish first server
505 512 $ killdaemons.py
506 513
507 514 Back up the recently added revlogs
508 515 $ cp -r .hg/store .hg/store-newcopy
509 516
510 517 Verify reports missing dirlog
511 518 $ rm .hg/store/meta/b/00manifest.*
512 519 $ hg verify
513 520 checking changesets
514 521 checking manifests
515 522 checking directory manifests
516 523 0: empty or missing b/
517 524 b/@0: parent-directory manifest refers to unknown revision 67688a370455
518 525 b/@1: parent-directory manifest refers to unknown revision f38e85d334c5
519 526 b/@2: parent-directory manifest refers to unknown revision 99c9792fd4b0
520 527 warning: orphan revlog 'meta/b/bar/00manifest.i'
521 528 warning: orphan revlog 'meta/b/bar/orange/00manifest.i'
522 529 warning: orphan revlog 'meta/b/bar/orange/fly/00manifest.i'
523 530 warning: orphan revlog 'meta/b/foo/00manifest.i'
524 531 warning: orphan revlog 'meta/b/foo/apple/00manifest.i'
525 532 warning: orphan revlog 'meta/b/foo/apple/bees/00manifest.i'
526 533 crosschecking files in changesets and manifests
527 534 b/bar/fruits.txt@0: in changeset but not in manifest
528 535 b/bar/orange/fly/gnat.py@0: in changeset but not in manifest
529 536 b/bar/orange/fly/housefly.txt@0: in changeset but not in manifest
530 537 b/foo/apple/bees/flower.py@0: in changeset but not in manifest
531 538 checking files
532 539 8 files, 3 changesets, 10 total revisions
533 540 6 warnings encountered!
534 541 8 integrity errors encountered!
535 542 (first damaged changeset appears to be 0)
536 543 [1]
537 544 $ cp -rT .hg/store-newcopy .hg/store
538 545
539 546 Verify reports missing dirlog entry
540 547 $ mv -f .hg/store-copy/meta/b/00manifest.* .hg/store/meta/b/
541 548 $ hg verify
542 549 checking changesets
543 550 checking manifests
544 551 checking directory manifests
545 552 b/@1: parent-directory manifest refers to unknown revision f38e85d334c5
546 553 b/@2: parent-directory manifest refers to unknown revision 99c9792fd4b0
547 554 b/bar/@?: rev 1 points to unexpected changeset 1
548 555 b/bar/@?: 5e03c4ee5e4a not in parent-directory manifest
549 556 b/bar/@?: rev 2 points to unexpected changeset 2
550 557 b/bar/@?: 1b16940d66d6 not in parent-directory manifest
551 558 b/bar/orange/@?: rev 1 points to unexpected changeset 2
552 559 (expected None)
553 560 b/bar/orange/fly/@?: rev 1 points to unexpected changeset 2
554 561 (expected None)
555 562 crosschecking files in changesets and manifests
556 563 checking files
557 564 8 files, 3 changesets, 10 total revisions
558 565 2 warnings encountered!
559 566 8 integrity errors encountered!
560 567 (first damaged changeset appears to be 1)
561 568 [1]
562 569 $ cp -rT .hg/store-newcopy .hg/store
563 570
564 571 Test cloning a treemanifest repo over http.
565 572 $ hg serve -p $HGPORT -d --pid-file=hg.pid --errorlog=errors.log
566 573 $ cat hg.pid >> $DAEMON_PIDS
567 574 $ cd ..
568 575 We can clone even with the knob turned off and we'll get a treemanifest repo.
569 576 $ hg clone --config experimental.treemanifest=False \
570 577 > --config experimental.changegroup3=True \
571 578 > http://localhost:$HGPORT deepclone
572 579 requesting all changes
573 580 adding changesets
574 581 adding manifests
575 582 adding file changes
576 583 added 3 changesets with 10 changes to 8 files
577 584 updating to branch default
578 585 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
579 586 No server errors.
580 587 $ cat deeprepo/errors.log
581 588 requires got updated to include treemanifest
582 589 $ cat deepclone/.hg/requires | grep treemanifest
583 590 treemanifest
584 591 Tree manifest revlogs exist.
585 592 $ find deepclone/.hg/store/meta | sort
586 593 deepclone/.hg/store/meta
587 594 deepclone/.hg/store/meta/b
588 595 deepclone/.hg/store/meta/b/00manifest.i
589 596 deepclone/.hg/store/meta/b/bar
590 597 deepclone/.hg/store/meta/b/bar/00manifest.i
591 598 deepclone/.hg/store/meta/b/bar/orange
592 599 deepclone/.hg/store/meta/b/bar/orange/00manifest.i
593 600 deepclone/.hg/store/meta/b/bar/orange/fly
594 601 deepclone/.hg/store/meta/b/bar/orange/fly/00manifest.i
595 602 deepclone/.hg/store/meta/b/foo
596 603 deepclone/.hg/store/meta/b/foo/00manifest.i
597 604 deepclone/.hg/store/meta/b/foo/apple
598 605 deepclone/.hg/store/meta/b/foo/apple/00manifest.i
599 606 deepclone/.hg/store/meta/b/foo/apple/bees
600 607 deepclone/.hg/store/meta/b/foo/apple/bees/00manifest.i
601 608 deepclone/.hg/store/meta/~2e_a
602 609 deepclone/.hg/store/meta/~2e_a/00manifest.i
603 610 Verify passes.
604 611 $ cd deepclone
605 612 $ hg verify
606 613 checking changesets
607 614 checking manifests
608 615 checking directory manifests
609 616 crosschecking files in changesets and manifests
610 617 checking files
611 618 8 files, 3 changesets, 10 total revisions
612 619 $ cd ..
613 620
614 621 Create clones using old repo formats to use in later tests
615 622 $ hg clone --config format.usestore=False \
616 623 > --config experimental.changegroup3=True \
617 624 > http://localhost:$HGPORT deeprepo-basicstore
618 625 requesting all changes
619 626 adding changesets
620 627 adding manifests
621 628 adding file changes
622 629 added 3 changesets with 10 changes to 8 files
623 630 updating to branch default
624 631 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
625 632 $ cd deeprepo-basicstore
626 633 $ grep store .hg/requires
627 634 [1]
628 635 $ hg serve -p $HGPORT1 -d --pid-file=hg.pid --errorlog=errors.log
629 636 $ cat hg.pid >> $DAEMON_PIDS
630 637 $ cd ..
631 638 $ hg clone --config format.usefncache=False \
632 639 > --config experimental.changegroup3=True \
633 640 > http://localhost:$HGPORT deeprepo-encodedstore
634 641 requesting all changes
635 642 adding changesets
636 643 adding manifests
637 644 adding file changes
638 645 added 3 changesets with 10 changes to 8 files
639 646 updating to branch default
640 647 8 files updated, 0 files merged, 0 files removed, 0 files unresolved
641 648 $ cd deeprepo-encodedstore
642 649 $ grep fncache .hg/requires
643 650 [1]
644 651 $ hg serve -p $HGPORT2 -d --pid-file=hg.pid --errorlog=errors.log
645 652 $ cat hg.pid >> $DAEMON_PIDS
646 653 $ cd ..
647 654
648 655 Local clone with basicstore
649 656 $ hg clone -U deeprepo-basicstore local-clone-basicstore
650 657 $ hg -R local-clone-basicstore verify
651 658 checking changesets
652 659 checking manifests
653 660 checking directory manifests
654 661 crosschecking files in changesets and manifests
655 662 checking files
656 663 8 files, 3 changesets, 10 total revisions
657 664
658 665 Local clone with encodedstore
659 666 $ hg clone -U deeprepo-encodedstore local-clone-encodedstore
660 667 $ hg -R local-clone-encodedstore verify
661 668 checking changesets
662 669 checking manifests
663 670 checking directory manifests
664 671 crosschecking files in changesets and manifests
665 672 checking files
666 673 8 files, 3 changesets, 10 total revisions
667 674
668 675 Local clone with fncachestore
669 676 $ hg clone -U deeprepo local-clone-fncachestore
670 677 $ hg -R local-clone-fncachestore verify
671 678 checking changesets
672 679 checking manifests
673 680 checking directory manifests
674 681 crosschecking files in changesets and manifests
675 682 checking files
676 683 8 files, 3 changesets, 10 total revisions
677 684
678 685 Stream clone with basicstore
679 686 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
680 687 > http://localhost:$HGPORT1 stream-clone-basicstore
681 688 streaming all changes
682 689 18 files to transfer, * of data (glob)
683 690 transferred * in * seconds (*) (glob)
684 691 searching for changes
685 692 no changes found
686 693 $ hg -R stream-clone-basicstore verify
687 694 checking changesets
688 695 checking manifests
689 696 checking directory manifests
690 697 crosschecking files in changesets and manifests
691 698 checking files
692 699 8 files, 3 changesets, 10 total revisions
693 700
694 701 Stream clone with encodedstore
695 702 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
696 703 > http://localhost:$HGPORT2 stream-clone-encodedstore
697 704 streaming all changes
698 705 18 files to transfer, * of data (glob)
699 706 transferred * in * seconds (*) (glob)
700 707 searching for changes
701 708 no changes found
702 709 $ hg -R stream-clone-encodedstore verify
703 710 checking changesets
704 711 checking manifests
705 712 checking directory manifests
706 713 crosschecking files in changesets and manifests
707 714 checking files
708 715 8 files, 3 changesets, 10 total revisions
709 716
710 717 Stream clone with fncachestore
711 718 $ hg clone --config experimental.changegroup3=True --uncompressed -U \
712 719 > http://localhost:$HGPORT stream-clone-fncachestore
713 720 streaming all changes
714 721 18 files to transfer, * of data (glob)
715 722 transferred * in * seconds (*) (glob)
716 723 searching for changes
717 724 no changes found
718 725 $ hg -R stream-clone-fncachestore verify
719 726 checking changesets
720 727 checking manifests
721 728 checking directory manifests
722 729 crosschecking files in changesets and manifests
723 730 checking files
724 731 8 files, 3 changesets, 10 total revisions
725 732
726 733 Packed bundle
727 734 $ hg -R deeprepo debugcreatestreamclonebundle repo-packed.hg
728 735 writing 3349 bytes for 18 files
729 736 bundle requirements: generaldelta, revlogv1, treemanifest
730 737 $ hg debugbundle --spec repo-packed.hg
731 738 none-packed1;requirements%3Dgeneraldelta%2Crevlogv1%2Ctreemanifest
General Comments 0
You need to be logged in to leave comments. Login now