##// END OF EJS Templates
revlog: add 'raw' argument to revision and _addrevision...
Remi Chaintron -
r30743:2df98312 default
parent child Browse files
Show More
@@ -1,554 +1,554 b''
1 1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 2 #
3 3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.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 """Repository class for viewing uncompressed bundles.
9 9
10 10 This provides a read-only repository interface to bundles as if they
11 11 were part of the actual repository.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import os
17 17 import shutil
18 18 import tempfile
19 19
20 20 from .i18n import _
21 21 from .node import nullid
22 22
23 23 from . import (
24 24 bundle2,
25 25 changegroup,
26 26 changelog,
27 27 cmdutil,
28 28 discovery,
29 29 error,
30 30 exchange,
31 31 filelog,
32 32 localrepo,
33 33 manifest,
34 34 mdiff,
35 35 node as nodemod,
36 36 pathutil,
37 37 phases,
38 38 pycompat,
39 39 revlog,
40 40 scmutil,
41 41 util,
42 42 )
43 43
44 44 class bundlerevlog(revlog.revlog):
45 45 def __init__(self, opener, indexfile, bundle, linkmapper):
46 46 # How it works:
47 47 # To retrieve a revision, we need to know the offset of the revision in
48 48 # the bundle (an unbundle object). We store this offset in the index
49 49 # (start). The base of the delta is stored in the base field.
50 50 #
51 51 # To differentiate a rev in the bundle from a rev in the revlog, we
52 52 # check revision against repotiprev.
53 53 opener = scmutil.readonlyvfs(opener)
54 54 revlog.revlog.__init__(self, opener, indexfile)
55 55 self.bundle = bundle
56 56 n = len(self)
57 57 self.repotiprev = n - 1
58 58 chain = None
59 59 self.bundlerevs = set() # used by 'bundle()' revset expression
60 60 getchunk = lambda: bundle.deltachunk(chain)
61 61 for chunkdata in iter(getchunk, {}):
62 62 node = chunkdata['node']
63 63 p1 = chunkdata['p1']
64 64 p2 = chunkdata['p2']
65 65 cs = chunkdata['cs']
66 66 deltabase = chunkdata['deltabase']
67 67 delta = chunkdata['delta']
68 68
69 69 size = len(delta)
70 70 start = bundle.tell() - size
71 71
72 72 link = linkmapper(cs)
73 73 if node in self.nodemap:
74 74 # this can happen if two branches make the same change
75 75 chain = node
76 76 self.bundlerevs.add(self.nodemap[node])
77 77 continue
78 78
79 79 for p in (p1, p2):
80 80 if p not in self.nodemap:
81 81 raise error.LookupError(p, self.indexfile,
82 82 _("unknown parent"))
83 83
84 84 if deltabase not in self.nodemap:
85 85 raise LookupError(deltabase, self.indexfile,
86 86 _('unknown delta base'))
87 87
88 88 baserev = self.rev(deltabase)
89 89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 91 self.rev(p1), self.rev(p2), node)
92 92 self.index.insert(-1, e)
93 93 self.nodemap[node] = n
94 94 self.bundlerevs.add(n)
95 95 chain = node
96 96 n += 1
97 97
98 98 def _chunk(self, rev):
99 99 # Warning: in case of bundle, the diff is against what we stored as
100 100 # delta base, not against rev - 1
101 101 # XXX: could use some caching
102 102 if rev <= self.repotiprev:
103 103 return revlog.revlog._chunk(self, rev)
104 104 self.bundle.seek(self.start(rev))
105 105 return self.bundle.read(self.length(rev))
106 106
107 107 def revdiff(self, rev1, rev2):
108 108 """return or calculate a delta between two revisions"""
109 109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 110 # hot path for bundle
111 111 revb = self.index[rev2][3]
112 112 if revb == rev1:
113 113 return self._chunk(rev2)
114 114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 115 return revlog.revlog.revdiff(self, rev1, rev2)
116 116
117 117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 118 self.revision(self.node(rev2)))
119 119
120 def revision(self, nodeorrev):
120 def revision(self, nodeorrev, raw=False):
121 121 """return an uncompressed revision of a given node or revision
122 122 number.
123 123 """
124 124 if isinstance(nodeorrev, int):
125 125 rev = nodeorrev
126 126 node = self.node(rev)
127 127 else:
128 128 node = nodeorrev
129 129 rev = self.rev(node)
130 130
131 131 if node == nullid:
132 132 return ""
133 133
134 134 text = None
135 135 chain = []
136 136 iterrev = rev
137 137 # reconstruct the revision if it is from a changegroup
138 138 while iterrev > self.repotiprev:
139 139 if self._cache and self._cache[1] == iterrev:
140 140 text = self._cache[2]
141 141 break
142 142 chain.append(iterrev)
143 143 iterrev = self.index[iterrev][3]
144 144 if text is None:
145 145 text = self.baserevision(iterrev)
146 146
147 147 while chain:
148 148 delta = self._chunk(chain.pop())
149 149 text = mdiff.patches(text, [delta])
150 150
151 151 self.checkhash(text, node, rev=rev)
152 152 self._cache = (node, rev, text)
153 153 return text
154 154
155 155 def baserevision(self, nodeorrev):
156 156 # Revlog subclasses may override 'revision' method to modify format of
157 157 # content retrieved from revlog. To use bundlerevlog with such class one
158 158 # needs to override 'baserevision' and make more specific call here.
159 159 return revlog.revlog.revision(self, nodeorrev)
160 160
161 161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 162 raise NotImplementedError
163 163 def addgroup(self, revs, linkmapper, transaction):
164 164 raise NotImplementedError
165 165 def strip(self, rev, minlink):
166 166 raise NotImplementedError
167 167 def checksize(self):
168 168 raise NotImplementedError
169 169
170 170 class bundlechangelog(bundlerevlog, changelog.changelog):
171 171 def __init__(self, opener, bundle):
172 172 changelog.changelog.__init__(self, opener)
173 173 linkmapper = lambda x: x
174 174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 175 linkmapper)
176 176
177 177 def baserevision(self, nodeorrev):
178 178 # Although changelog doesn't override 'revision' method, some extensions
179 179 # may replace this class with another that does. Same story with
180 180 # manifest and filelog classes.
181 181
182 182 # This bypasses filtering on changelog.node() and rev() because we need
183 183 # revision text of the bundle base even if it is hidden.
184 184 oldfilter = self.filteredrevs
185 185 try:
186 186 self.filteredrevs = ()
187 187 return changelog.changelog.revision(self, nodeorrev)
188 188 finally:
189 189 self.filteredrevs = oldfilter
190 190
191 191 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
192 192 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
193 193 manifest.manifestrevlog.__init__(self, opener, dir=dir)
194 194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 195 linkmapper)
196 196 if dirlogstarts is None:
197 197 dirlogstarts = {}
198 198 if self.bundle.version == "03":
199 199 dirlogstarts = _getfilestarts(self.bundle)
200 200 self._dirlogstarts = dirlogstarts
201 201 self._linkmapper = linkmapper
202 202
203 203 def baserevision(self, nodeorrev):
204 204 node = nodeorrev
205 205 if isinstance(node, int):
206 206 node = self.node(node)
207 207
208 208 if node in self.fulltextcache:
209 209 result = self.fulltextcache[node].tostring()
210 210 else:
211 211 result = manifest.manifestrevlog.revision(self, nodeorrev)
212 212 return result
213 213
214 214 def dirlog(self, d):
215 215 if d in self._dirlogstarts:
216 216 self.bundle.seek(self._dirlogstarts[d])
217 217 return bundlemanifest(
218 218 self.opener, self.bundle, self._linkmapper,
219 219 self._dirlogstarts, dir=d)
220 220 return super(bundlemanifest, self).dirlog(d)
221 221
222 222 class bundlefilelog(bundlerevlog, filelog.filelog):
223 223 def __init__(self, opener, path, bundle, linkmapper):
224 224 filelog.filelog.__init__(self, opener, path)
225 225 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
226 226 linkmapper)
227 227
228 228 def baserevision(self, nodeorrev):
229 229 return filelog.filelog.revision(self, nodeorrev)
230 230
231 231 class bundlepeer(localrepo.localpeer):
232 232 def canpush(self):
233 233 return False
234 234
235 235 class bundlephasecache(phases.phasecache):
236 236 def __init__(self, *args, **kwargs):
237 237 super(bundlephasecache, self).__init__(*args, **kwargs)
238 238 if util.safehasattr(self, 'opener'):
239 239 self.opener = scmutil.readonlyvfs(self.opener)
240 240
241 241 def write(self):
242 242 raise NotImplementedError
243 243
244 244 def _write(self, fp):
245 245 raise NotImplementedError
246 246
247 247 def _updateroots(self, phase, newroots, tr):
248 248 self.phaseroots[phase] = newroots
249 249 self.invalidate()
250 250 self.dirty = True
251 251
252 252 def _getfilestarts(bundle):
253 253 bundlefilespos = {}
254 254 for chunkdata in iter(bundle.filelogheader, {}):
255 255 fname = chunkdata['filename']
256 256 bundlefilespos[fname] = bundle.tell()
257 257 for chunk in iter(lambda: bundle.deltachunk(None), {}):
258 258 pass
259 259 return bundlefilespos
260 260
261 261 class bundlerepository(localrepo.localrepository):
262 262 def __init__(self, ui, path, bundlename):
263 263 def _writetempbundle(read, suffix, header=''):
264 264 """Write a temporary file to disk
265 265
266 266 This is closure because we need to make sure this tracked by
267 267 self.tempfile for cleanup purposes."""
268 268 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
269 269 suffix=".hg10un")
270 270 self.tempfile = temp
271 271
272 272 with os.fdopen(fdtemp, 'wb') as fptemp:
273 273 fptemp.write(header)
274 274 while True:
275 275 chunk = read(2**18)
276 276 if not chunk:
277 277 break
278 278 fptemp.write(chunk)
279 279
280 280 return self.vfs.open(self.tempfile, mode="rb")
281 281 self._tempparent = None
282 282 try:
283 283 localrepo.localrepository.__init__(self, ui, path)
284 284 except error.RepoError:
285 285 self._tempparent = tempfile.mkdtemp()
286 286 localrepo.instance(ui, self._tempparent, 1)
287 287 localrepo.localrepository.__init__(self, ui, self._tempparent)
288 288 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
289 289
290 290 if path:
291 291 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
292 292 else:
293 293 self._url = 'bundle:' + bundlename
294 294
295 295 self.tempfile = None
296 296 f = util.posixfile(bundlename, "rb")
297 297 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
298 298
299 299 if isinstance(self.bundle, bundle2.unbundle20):
300 300 cgstream = None
301 301 for part in self.bundle.iterparts():
302 302 if part.type == 'changegroup':
303 303 if cgstream is not None:
304 304 raise NotImplementedError("can't process "
305 305 "multiple changegroups")
306 306 cgstream = part
307 307 version = part.params.get('version', '01')
308 308 legalcgvers = changegroup.supportedincomingversions(self)
309 309 if version not in legalcgvers:
310 310 msg = _('Unsupported changegroup version: %s')
311 311 raise error.Abort(msg % version)
312 312 if self.bundle.compressed():
313 313 cgstream = _writetempbundle(part.read,
314 314 ".cg%sun" % version)
315 315
316 316 if cgstream is None:
317 317 raise error.Abort(_('No changegroups found'))
318 318 cgstream.seek(0)
319 319
320 320 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
321 321
322 322 elif self.bundle.compressed():
323 323 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
324 324 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
325 325 bundlename,
326 326 self.vfs)
327 327
328 328 # dict with the mapping 'filename' -> position in the bundle
329 329 self.bundlefilespos = {}
330 330
331 331 self.firstnewrev = self.changelog.repotiprev + 1
332 332 phases.retractboundary(self, None, phases.draft,
333 333 [ctx.node() for ctx in self[self.firstnewrev:]])
334 334
335 335 @localrepo.unfilteredpropertycache
336 336 def _phasecache(self):
337 337 return bundlephasecache(self, self._phasedefaults)
338 338
339 339 @localrepo.unfilteredpropertycache
340 340 def changelog(self):
341 341 # consume the header if it exists
342 342 self.bundle.changelogheader()
343 343 c = bundlechangelog(self.svfs, self.bundle)
344 344 self.manstart = self.bundle.tell()
345 345 return c
346 346
347 347 def _constructmanifest(self):
348 348 self.bundle.seek(self.manstart)
349 349 # consume the header if it exists
350 350 self.bundle.manifestheader()
351 351 linkmapper = self.unfiltered().changelog.rev
352 352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
353 353 self.filestart = self.bundle.tell()
354 354 return m
355 355
356 356 @localrepo.unfilteredpropertycache
357 357 def manstart(self):
358 358 self.changelog
359 359 return self.manstart
360 360
361 361 @localrepo.unfilteredpropertycache
362 362 def filestart(self):
363 363 self.manifestlog
364 364 return self.filestart
365 365
366 366 def url(self):
367 367 return self._url
368 368
369 369 def file(self, f):
370 370 if not self.bundlefilespos:
371 371 self.bundle.seek(self.filestart)
372 372 self.bundlefilespos = _getfilestarts(self.bundle)
373 373
374 374 if f in self.bundlefilespos:
375 375 self.bundle.seek(self.bundlefilespos[f])
376 376 linkmapper = self.unfiltered().changelog.rev
377 377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
378 378 else:
379 379 return filelog.filelog(self.svfs, f)
380 380
381 381 def close(self):
382 382 """Close assigned bundle file immediately."""
383 383 self.bundlefile.close()
384 384 if self.tempfile is not None:
385 385 self.vfs.unlink(self.tempfile)
386 386 if self._tempparent:
387 387 shutil.rmtree(self._tempparent, True)
388 388
389 389 def cancopy(self):
390 390 return False
391 391
392 392 def peer(self):
393 393 return bundlepeer(self)
394 394
395 395 def getcwd(self):
396 396 return pycompat.getcwd() # always outside the repo
397 397
398 398 # Check if parents exist in localrepo before setting
399 399 def setparents(self, p1, p2=nullid):
400 400 p1rev = self.changelog.rev(p1)
401 401 p2rev = self.changelog.rev(p2)
402 402 msg = _("setting parent to node %s that only exists in the bundle\n")
403 403 if self.changelog.repotiprev < p1rev:
404 404 self.ui.warn(msg % nodemod.hex(p1))
405 405 if self.changelog.repotiprev < p2rev:
406 406 self.ui.warn(msg % nodemod.hex(p2))
407 407 return super(bundlerepository, self).setparents(p1, p2)
408 408
409 409 def instance(ui, path, create):
410 410 if create:
411 411 raise error.Abort(_('cannot create new bundle repository'))
412 412 # internal config: bundle.mainreporoot
413 413 parentpath = ui.config("bundle", "mainreporoot", "")
414 414 if not parentpath:
415 415 # try to find the correct path to the working directory repo
416 416 parentpath = cmdutil.findrepo(pycompat.getcwd())
417 417 if parentpath is None:
418 418 parentpath = ''
419 419 if parentpath:
420 420 # Try to make the full path relative so we get a nice, short URL.
421 421 # In particular, we don't want temp dir names in test outputs.
422 422 cwd = pycompat.getcwd()
423 423 if parentpath == cwd:
424 424 parentpath = ''
425 425 else:
426 426 cwd = pathutil.normasprefix(cwd)
427 427 if parentpath.startswith(cwd):
428 428 parentpath = parentpath[len(cwd):]
429 429 u = util.url(path)
430 430 path = u.localpath()
431 431 if u.scheme == 'bundle':
432 432 s = path.split("+", 1)
433 433 if len(s) == 1:
434 434 repopath, bundlename = parentpath, s[0]
435 435 else:
436 436 repopath, bundlename = s
437 437 else:
438 438 repopath, bundlename = parentpath, path
439 439 return bundlerepository(ui, repopath, bundlename)
440 440
441 441 class bundletransactionmanager(object):
442 442 def transaction(self):
443 443 return None
444 444
445 445 def close(self):
446 446 raise NotImplementedError
447 447
448 448 def release(self):
449 449 raise NotImplementedError
450 450
451 451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
452 452 force=False):
453 453 '''obtains a bundle of changes incoming from other
454 454
455 455 "onlyheads" restricts the returned changes to those reachable from the
456 456 specified heads.
457 457 "bundlename", if given, stores the bundle to this file path permanently;
458 458 otherwise it's stored to a temp file and gets deleted again when you call
459 459 the returned "cleanupfn".
460 460 "force" indicates whether to proceed on unrelated repos.
461 461
462 462 Returns a tuple (local, csets, cleanupfn):
463 463
464 464 "local" is a local repo from which to obtain the actual incoming
465 465 changesets; it is a bundlerepo for the obtained bundle when the
466 466 original "other" is remote.
467 467 "csets" lists the incoming changeset node ids.
468 468 "cleanupfn" must be called without arguments when you're done processing
469 469 the changes; it closes both the original "other" and the one returned
470 470 here.
471 471 '''
472 472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
473 473 force=force)
474 474 common, incoming, rheads = tmp
475 475 if not incoming:
476 476 try:
477 477 if bundlename:
478 478 os.unlink(bundlename)
479 479 except OSError:
480 480 pass
481 481 return repo, [], other.close
482 482
483 483 commonset = set(common)
484 484 rheads = [x for x in rheads if x not in commonset]
485 485
486 486 bundle = None
487 487 bundlerepo = None
488 488 localrepo = other.local()
489 489 if bundlename or not localrepo:
490 490 # create a bundle (uncompressed if other repo is not local)
491 491
492 492 # developer config: devel.legacy.exchange
493 493 legexc = ui.configlist('devel', 'legacy.exchange')
494 494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
495 495 canbundle2 = (not forcebundle1
496 496 and other.capable('getbundle')
497 497 and other.capable('bundle2'))
498 498 if canbundle2:
499 499 kwargs = {}
500 500 kwargs['common'] = common
501 501 kwargs['heads'] = rheads
502 502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
503 503 kwargs['cg'] = True
504 504 b2 = other.getbundle('incoming', **kwargs)
505 505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
506 506 bundlename)
507 507 else:
508 508 if other.capable('getbundle'):
509 509 cg = other.getbundle('incoming', common=common, heads=rheads)
510 510 elif onlyheads is None and not other.capable('changegroupsubset'):
511 511 # compat with older servers when pulling all remote heads
512 512 cg = other.changegroup(incoming, "incoming")
513 513 rheads = None
514 514 else:
515 515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
516 516 if localrepo:
517 517 bundletype = "HG10BZ"
518 518 else:
519 519 bundletype = "HG10UN"
520 520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
521 521 bundletype)
522 522 # keep written bundle?
523 523 if bundlename:
524 524 bundle = None
525 525 if not localrepo:
526 526 # use the created uncompressed bundlerepo
527 527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
528 528 fname)
529 529 # this repo contains local and other now, so filter out local again
530 530 common = repo.heads()
531 531 if localrepo:
532 532 # Part of common may be remotely filtered
533 533 # So use an unfiltered version
534 534 # The discovery process probably need cleanup to avoid that
535 535 localrepo = localrepo.unfiltered()
536 536
537 537 csets = localrepo.changelog.findmissing(common, rheads)
538 538
539 539 if bundlerepo:
540 540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
541 541 remotephases = other.listkeys('phases')
542 542
543 543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
544 544 pullop.trmanager = bundletransactionmanager()
545 545 exchange._pullapplyphases(pullop, remotephases)
546 546
547 547 def cleanup():
548 548 if bundlerepo:
549 549 bundlerepo.close()
550 550 if bundle:
551 551 os.unlink(bundle)
552 552 other.close()
553 553
554 554 return (localrepo, csets, cleanup)
@@ -1,1044 +1,1044 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 nullrev,
19 19 short,
20 20 )
21 21
22 22 from . import (
23 23 branchmap,
24 24 dagutil,
25 25 discovery,
26 26 error,
27 27 mdiff,
28 28 phases,
29 29 util,
30 30 )
31 31
32 32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
35 35
36 36 def readexactly(stream, n):
37 37 '''read n bytes from stream.read and abort if less was available'''
38 38 s = stream.read(n)
39 39 if len(s) < n:
40 40 raise error.Abort(_("stream ended unexpectedly"
41 41 " (got %d bytes, expected %d)")
42 42 % (len(s), n))
43 43 return s
44 44
45 45 def getchunk(stream):
46 46 """return the next chunk from stream as a string"""
47 47 d = readexactly(stream, 4)
48 48 l = struct.unpack(">l", d)[0]
49 49 if l <= 4:
50 50 if l:
51 51 raise error.Abort(_("invalid chunk length %d") % l)
52 52 return ""
53 53 return readexactly(stream, l - 4)
54 54
55 55 def chunkheader(length):
56 56 """return a changegroup chunk header (string)"""
57 57 return struct.pack(">l", length + 4)
58 58
59 59 def closechunk():
60 60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 61 return struct.pack(">l", 0)
62 62
63 63 def combineresults(results):
64 64 """logic to combine 0 or more addchangegroup results into one"""
65 65 changedheads = 0
66 66 result = 1
67 67 for ret in results:
68 68 # If any changegroup result is 0, return 0
69 69 if ret == 0:
70 70 result = 0
71 71 break
72 72 if ret < -1:
73 73 changedheads += ret + 1
74 74 elif ret > 1:
75 75 changedheads += ret - 1
76 76 if changedheads > 0:
77 77 result = 1 + changedheads
78 78 elif changedheads < 0:
79 79 result = -1 + changedheads
80 80 return result
81 81
82 82 def writechunks(ui, chunks, filename, vfs=None):
83 83 """Write chunks to a file and return its filename.
84 84
85 85 The stream is assumed to be a bundle file.
86 86 Existing files will not be overwritten.
87 87 If no filename is specified, a temporary file is created.
88 88 """
89 89 fh = None
90 90 cleanup = None
91 91 try:
92 92 if filename:
93 93 if vfs:
94 94 fh = vfs.open(filename, "wb")
95 95 else:
96 96 # Increase default buffer size because default is usually
97 97 # small (4k is common on Linux).
98 98 fh = open(filename, "wb", 131072)
99 99 else:
100 100 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
101 101 fh = os.fdopen(fd, "wb")
102 102 cleanup = filename
103 103 for c in chunks:
104 104 fh.write(c)
105 105 cleanup = None
106 106 return filename
107 107 finally:
108 108 if fh is not None:
109 109 fh.close()
110 110 if cleanup is not None:
111 111 if filename and vfs:
112 112 vfs.unlink(cleanup)
113 113 else:
114 114 os.unlink(cleanup)
115 115
116 116 class cg1unpacker(object):
117 117 """Unpacker for cg1 changegroup streams.
118 118
119 119 A changegroup unpacker handles the framing of the revision data in
120 120 the wire format. Most consumers will want to use the apply()
121 121 method to add the changes from the changegroup to a repository.
122 122
123 123 If you're forwarding a changegroup unmodified to another consumer,
124 124 use getchunks(), which returns an iterator of changegroup
125 125 chunks. This is mostly useful for cases where you need to know the
126 126 data stream has ended by observing the end of the changegroup.
127 127
128 128 deltachunk() is useful only if you're applying delta data. Most
129 129 consumers should prefer apply() instead.
130 130
131 131 A few other public methods exist. Those are used only for
132 132 bundlerepo and some debug commands - their use is discouraged.
133 133 """
134 134 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
135 135 deltaheadersize = struct.calcsize(deltaheader)
136 136 version = '01'
137 137 _grouplistcount = 1 # One list of files after the manifests
138 138
139 139 def __init__(self, fh, alg, extras=None):
140 140 if alg is None:
141 141 alg = 'UN'
142 142 if alg not in util.compengines.supportedbundletypes:
143 143 raise error.Abort(_('unknown stream compression type: %s')
144 144 % alg)
145 145 if alg == 'BZ':
146 146 alg = '_truncatedBZ'
147 147
148 148 compengine = util.compengines.forbundletype(alg)
149 149 self._stream = compengine.decompressorreader(fh)
150 150 self._type = alg
151 151 self.extras = extras or {}
152 152 self.callback = None
153 153
154 154 # These methods (compressed, read, seek, tell) all appear to only
155 155 # be used by bundlerepo, but it's a little hard to tell.
156 156 def compressed(self):
157 157 return self._type is not None and self._type != 'UN'
158 158 def read(self, l):
159 159 return self._stream.read(l)
160 160 def seek(self, pos):
161 161 return self._stream.seek(pos)
162 162 def tell(self):
163 163 return self._stream.tell()
164 164 def close(self):
165 165 return self._stream.close()
166 166
167 167 def _chunklength(self):
168 168 d = readexactly(self._stream, 4)
169 169 l = struct.unpack(">l", d)[0]
170 170 if l <= 4:
171 171 if l:
172 172 raise error.Abort(_("invalid chunk length %d") % l)
173 173 return 0
174 174 if self.callback:
175 175 self.callback()
176 176 return l - 4
177 177
178 178 def changelogheader(self):
179 179 """v10 does not have a changelog header chunk"""
180 180 return {}
181 181
182 182 def manifestheader(self):
183 183 """v10 does not have a manifest header chunk"""
184 184 return {}
185 185
186 186 def filelogheader(self):
187 187 """return the header of the filelogs chunk, v10 only has the filename"""
188 188 l = self._chunklength()
189 189 if not l:
190 190 return {}
191 191 fname = readexactly(self._stream, l)
192 192 return {'filename': fname}
193 193
194 194 def _deltaheader(self, headertuple, prevnode):
195 195 node, p1, p2, cs = headertuple
196 196 if prevnode is None:
197 197 deltabase = p1
198 198 else:
199 199 deltabase = prevnode
200 200 flags = 0
201 201 return node, p1, p2, deltabase, cs, flags
202 202
203 203 def deltachunk(self, prevnode):
204 204 l = self._chunklength()
205 205 if not l:
206 206 return {}
207 207 headerdata = readexactly(self._stream, self.deltaheadersize)
208 208 header = struct.unpack(self.deltaheader, headerdata)
209 209 delta = readexactly(self._stream, l - self.deltaheadersize)
210 210 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
211 211 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
212 212 'deltabase': deltabase, 'delta': delta, 'flags': flags}
213 213
214 214 def getchunks(self):
215 215 """returns all the chunks contains in the bundle
216 216
217 217 Used when you need to forward the binary stream to a file or another
218 218 network API. To do so, it parse the changegroup data, otherwise it will
219 219 block in case of sshrepo because it don't know the end of the stream.
220 220 """
221 221 # an empty chunkgroup is the end of the changegroup
222 222 # a changegroup has at least 2 chunkgroups (changelog and manifest).
223 223 # after that, changegroup versions 1 and 2 have a series of groups
224 224 # with one group per file. changegroup 3 has a series of directory
225 225 # manifests before the files.
226 226 count = 0
227 227 emptycount = 0
228 228 while emptycount < self._grouplistcount:
229 229 empty = True
230 230 count += 1
231 231 while True:
232 232 chunk = getchunk(self)
233 233 if not chunk:
234 234 if empty and count > 2:
235 235 emptycount += 1
236 236 break
237 237 empty = False
238 238 yield chunkheader(len(chunk))
239 239 pos = 0
240 240 while pos < len(chunk):
241 241 next = pos + 2**20
242 242 yield chunk[pos:next]
243 243 pos = next
244 244 yield closechunk()
245 245
246 246 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
247 247 # We know that we'll never have more manifests than we had
248 248 # changesets.
249 249 self.callback = prog(_('manifests'), numchanges)
250 250 # no need to check for empty manifest group here:
251 251 # if the result of the merge of 1 and 2 is the same in 3 and 4,
252 252 # no new manifest will be created and the manifest group will
253 253 # be empty during the pull
254 254 self.manifestheader()
255 255 repo.manifestlog._revlog.addgroup(self, revmap, trp)
256 256 repo.ui.progress(_('manifests'), None)
257 257 self.callback = None
258 258
259 259 def apply(self, repo, srctype, url, emptyok=False,
260 260 targetphase=phases.draft, expectedtotal=None):
261 261 """Add the changegroup returned by source.read() to this repo.
262 262 srctype is a string like 'push', 'pull', or 'unbundle'. url is
263 263 the URL of the repo where this changegroup is coming from.
264 264
265 265 Return an integer summarizing the change to this repo:
266 266 - nothing changed or no source: 0
267 267 - more heads than before: 1+added heads (2..n)
268 268 - fewer heads than before: -1-removed heads (-2..-n)
269 269 - number of heads stays the same: 1
270 270 """
271 271 repo = repo.unfiltered()
272 272 def csmap(x):
273 273 repo.ui.debug("add changeset %s\n" % short(x))
274 274 return len(cl)
275 275
276 276 def revmap(x):
277 277 return cl.rev(x)
278 278
279 279 changesets = files = revisions = 0
280 280
281 281 try:
282 282 with repo.transaction("\n".join([srctype,
283 283 util.hidepassword(url)])) as tr:
284 284 # The transaction could have been created before and already
285 285 # carries source information. In this case we use the top
286 286 # level data. We overwrite the argument because we need to use
287 287 # the top level value (if they exist) in this function.
288 288 srctype = tr.hookargs.setdefault('source', srctype)
289 289 url = tr.hookargs.setdefault('url', url)
290 290 repo.hook('prechangegroup', throw=True, **tr.hookargs)
291 291
292 292 # write changelog data to temp files so concurrent readers
293 293 # will not see an inconsistent view
294 294 cl = repo.changelog
295 295 cl.delayupdate(tr)
296 296 oldheads = cl.heads()
297 297
298 298 trp = weakref.proxy(tr)
299 299 # pull off the changeset group
300 300 repo.ui.status(_("adding changesets\n"))
301 301 clstart = len(cl)
302 302 class prog(object):
303 303 def __init__(self, step, total):
304 304 self._step = step
305 305 self._total = total
306 306 self._count = 1
307 307 def __call__(self):
308 308 repo.ui.progress(self._step, self._count,
309 309 unit=_('chunks'), total=self._total)
310 310 self._count += 1
311 311 self.callback = prog(_('changesets'), expectedtotal)
312 312
313 313 efiles = set()
314 314 def onchangelog(cl, node):
315 315 efiles.update(cl.readfiles(node))
316 316
317 317 self.changelogheader()
318 318 srccontent = cl.addgroup(self, csmap, trp,
319 319 addrevisioncb=onchangelog)
320 320 efiles = len(efiles)
321 321
322 322 if not (srccontent or emptyok):
323 323 raise error.Abort(_("received changelog group is empty"))
324 324 clend = len(cl)
325 325 changesets = clend - clstart
326 326 repo.ui.progress(_('changesets'), None)
327 327 self.callback = None
328 328
329 329 # pull off the manifest group
330 330 repo.ui.status(_("adding manifests\n"))
331 331 self._unpackmanifests(repo, revmap, trp, prog, changesets)
332 332
333 333 needfiles = {}
334 334 if repo.ui.configbool('server', 'validate', default=False):
335 335 cl = repo.changelog
336 336 ml = repo.manifestlog
337 337 # validate incoming csets have their manifests
338 338 for cset in xrange(clstart, clend):
339 339 mfnode = cl.changelogrevision(cset).manifest
340 340 mfest = ml[mfnode].readdelta()
341 341 # store file nodes we must see
342 342 for f, n in mfest.iteritems():
343 343 needfiles.setdefault(f, set()).add(n)
344 344
345 345 # process the files
346 346 repo.ui.status(_("adding file changes\n"))
347 347 newrevs, newfiles = _addchangegroupfiles(
348 348 repo, self, revmap, trp, efiles, needfiles)
349 349 revisions += newrevs
350 350 files += newfiles
351 351
352 352 dh = 0
353 353 if oldheads:
354 354 heads = cl.heads()
355 355 dh = len(heads) - len(oldheads)
356 356 for h in heads:
357 357 if h not in oldheads and repo[h].closesbranch():
358 358 dh -= 1
359 359 htext = ""
360 360 if dh:
361 361 htext = _(" (%+d heads)") % dh
362 362
363 363 repo.ui.status(_("added %d changesets"
364 364 " with %d changes to %d files%s\n")
365 365 % (changesets, revisions, files, htext))
366 366 repo.invalidatevolatilesets()
367 367
368 368 if changesets > 0:
369 369 if 'node' not in tr.hookargs:
370 370 tr.hookargs['node'] = hex(cl.node(clstart))
371 371 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
372 372 hookargs = dict(tr.hookargs)
373 373 else:
374 374 hookargs = dict(tr.hookargs)
375 375 hookargs['node'] = hex(cl.node(clstart))
376 376 hookargs['node_last'] = hex(cl.node(clend - 1))
377 377 repo.hook('pretxnchangegroup', throw=True, **hookargs)
378 378
379 379 added = [cl.node(r) for r in xrange(clstart, clend)]
380 380 publishing = repo.publishing()
381 381 if srctype in ('push', 'serve'):
382 382 # Old servers can not push the boundary themselves.
383 383 # New servers won't push the boundary if changeset already
384 384 # exists locally as secret
385 385 #
386 386 # We should not use added here but the list of all change in
387 387 # the bundle
388 388 if publishing:
389 389 phases.advanceboundary(repo, tr, phases.public,
390 390 srccontent)
391 391 else:
392 392 # Those changesets have been pushed from the
393 393 # outside, their phases are going to be pushed
394 394 # alongside. Therefor `targetphase` is
395 395 # ignored.
396 396 phases.advanceboundary(repo, tr, phases.draft,
397 397 srccontent)
398 398 phases.retractboundary(repo, tr, phases.draft, added)
399 399 elif srctype != 'strip':
400 400 # publishing only alter behavior during push
401 401 #
402 402 # strip should not touch boundary at all
403 403 phases.retractboundary(repo, tr, targetphase, added)
404 404
405 405 if changesets > 0:
406 406 if srctype != 'strip':
407 407 # During strip, branchcache is invalid but
408 408 # coming call to `destroyed` will repair it.
409 409 # In other case we can safely update cache on
410 410 # disk.
411 411 repo.ui.debug('updating the branch cache\n')
412 412 branchmap.updatecache(repo.filtered('served'))
413 413
414 414 def runhooks():
415 415 # These hooks run when the lock releases, not when the
416 416 # transaction closes. So it's possible for the changelog
417 417 # to have changed since we last saw it.
418 418 if clstart >= len(repo):
419 419 return
420 420
421 421 repo.hook("changegroup", **hookargs)
422 422
423 423 for n in added:
424 424 args = hookargs.copy()
425 425 args['node'] = hex(n)
426 426 del args['node_last']
427 427 repo.hook("incoming", **args)
428 428
429 429 newheads = [h for h in repo.heads()
430 430 if h not in oldheads]
431 431 repo.ui.log("incoming",
432 432 "%s incoming changes - new heads: %s\n",
433 433 len(added),
434 434 ', '.join([hex(c[:6]) for c in newheads]))
435 435
436 436 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
437 437 lambda tr: repo._afterlock(runhooks))
438 438 finally:
439 439 repo.ui.flush()
440 440 # never return 0 here:
441 441 if dh < 0:
442 442 return dh - 1
443 443 else:
444 444 return dh + 1
445 445
446 446 class cg2unpacker(cg1unpacker):
447 447 """Unpacker for cg2 streams.
448 448
449 449 cg2 streams add support for generaldelta, so the delta header
450 450 format is slightly different. All other features about the data
451 451 remain the same.
452 452 """
453 453 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
454 454 deltaheadersize = struct.calcsize(deltaheader)
455 455 version = '02'
456 456
457 457 def _deltaheader(self, headertuple, prevnode):
458 458 node, p1, p2, deltabase, cs = headertuple
459 459 flags = 0
460 460 return node, p1, p2, deltabase, cs, flags
461 461
462 462 class cg3unpacker(cg2unpacker):
463 463 """Unpacker for cg3 streams.
464 464
465 465 cg3 streams add support for exchanging treemanifests and revlog
466 466 flags. It adds the revlog flags to the delta header and an empty chunk
467 467 separating manifests and files.
468 468 """
469 469 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
470 470 deltaheadersize = struct.calcsize(deltaheader)
471 471 version = '03'
472 472 _grouplistcount = 2 # One list of manifests and one list of files
473 473
474 474 def _deltaheader(self, headertuple, prevnode):
475 475 node, p1, p2, deltabase, cs, flags = headertuple
476 476 return node, p1, p2, deltabase, cs, flags
477 477
478 478 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
479 479 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
480 480 numchanges)
481 481 for chunkdata in iter(self.filelogheader, {}):
482 482 # If we get here, there are directory manifests in the changegroup
483 483 d = chunkdata["filename"]
484 484 repo.ui.debug("adding %s revisions\n" % d)
485 485 dirlog = repo.manifestlog._revlog.dirlog(d)
486 486 if not dirlog.addgroup(self, revmap, trp):
487 487 raise error.Abort(_("received dir revlog group is empty"))
488 488
489 489 class headerlessfixup(object):
490 490 def __init__(self, fh, h):
491 491 self._h = h
492 492 self._fh = fh
493 493 def read(self, n):
494 494 if self._h:
495 495 d, self._h = self._h[:n], self._h[n:]
496 496 if len(d) < n:
497 497 d += readexactly(self._fh, n - len(d))
498 498 return d
499 499 return readexactly(self._fh, n)
500 500
501 501 class cg1packer(object):
502 502 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
503 503 version = '01'
504 504 def __init__(self, repo, bundlecaps=None):
505 505 """Given a source repo, construct a bundler.
506 506
507 507 bundlecaps is optional and can be used to specify the set of
508 508 capabilities which can be used to build the bundle.
509 509 """
510 510 # Set of capabilities we can use to build the bundle.
511 511 if bundlecaps is None:
512 512 bundlecaps = set()
513 513 self._bundlecaps = bundlecaps
514 514 # experimental config: bundle.reorder
515 515 reorder = repo.ui.config('bundle', 'reorder', 'auto')
516 516 if reorder == 'auto':
517 517 reorder = None
518 518 else:
519 519 reorder = util.parsebool(reorder)
520 520 self._repo = repo
521 521 self._reorder = reorder
522 522 self._progress = repo.ui.progress
523 523 if self._repo.ui.verbose and not self._repo.ui.debugflag:
524 524 self._verbosenote = self._repo.ui.note
525 525 else:
526 526 self._verbosenote = lambda s: None
527 527
528 528 def close(self):
529 529 return closechunk()
530 530
531 531 def fileheader(self, fname):
532 532 return chunkheader(len(fname)) + fname
533 533
534 534 # Extracted both for clarity and for overriding in extensions.
535 535 def _sortgroup(self, revlog, nodelist, lookup):
536 536 """Sort nodes for change group and turn them into revnums."""
537 537 # for generaldelta revlogs, we linearize the revs; this will both be
538 538 # much quicker and generate a much smaller bundle
539 539 if (revlog._generaldelta and self._reorder is None) or self._reorder:
540 540 dag = dagutil.revlogdag(revlog)
541 541 return dag.linearize(set(revlog.rev(n) for n in nodelist))
542 542 else:
543 543 return sorted([revlog.rev(n) for n in nodelist])
544 544
545 545 def group(self, nodelist, revlog, lookup, units=None):
546 546 """Calculate a delta group, yielding a sequence of changegroup chunks
547 547 (strings).
548 548
549 549 Given a list of changeset revs, return a set of deltas and
550 550 metadata corresponding to nodes. The first delta is
551 551 first parent(nodelist[0]) -> nodelist[0], the receiver is
552 552 guaranteed to have this parent as it has all history before
553 553 these changesets. In the case firstparent is nullrev the
554 554 changegroup starts with a full revision.
555 555
556 556 If units is not None, progress detail will be generated, units specifies
557 557 the type of revlog that is touched (changelog, manifest, etc.).
558 558 """
559 559 # if we don't have any revisions touched by these changesets, bail
560 560 if len(nodelist) == 0:
561 561 yield self.close()
562 562 return
563 563
564 564 revs = self._sortgroup(revlog, nodelist, lookup)
565 565
566 566 # add the parent of the first rev
567 567 p = revlog.parentrevs(revs[0])[0]
568 568 revs.insert(0, p)
569 569
570 570 # build deltas
571 571 total = len(revs) - 1
572 572 msgbundling = _('bundling')
573 573 for r in xrange(len(revs) - 1):
574 574 if units is not None:
575 575 self._progress(msgbundling, r + 1, unit=units, total=total)
576 576 prev, curr = revs[r], revs[r + 1]
577 577 linknode = lookup(revlog.node(curr))
578 578 for c in self.revchunk(revlog, curr, prev, linknode):
579 579 yield c
580 580
581 581 if units is not None:
582 582 self._progress(msgbundling, None)
583 583 yield self.close()
584 584
585 585 # filter any nodes that claim to be part of the known set
586 586 def prune(self, revlog, missing, commonrevs):
587 587 rr, rl = revlog.rev, revlog.linkrev
588 588 return [n for n in missing if rl(rr(n)) not in commonrevs]
589 589
590 590 def _packmanifests(self, dir, mfnodes, lookuplinknode):
591 591 """Pack flat manifests into a changegroup stream."""
592 592 assert not dir
593 593 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
594 594 lookuplinknode, units=_('manifests')):
595 595 yield chunk
596 596
597 597 def _manifestsdone(self):
598 598 return ''
599 599
600 600 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
601 601 '''yield a sequence of changegroup chunks (strings)'''
602 602 repo = self._repo
603 603 cl = repo.changelog
604 604
605 605 clrevorder = {}
606 606 mfs = {} # needed manifests
607 607 fnodes = {} # needed file nodes
608 608 changedfiles = set()
609 609
610 610 # Callback for the changelog, used to collect changed files and manifest
611 611 # nodes.
612 612 # Returns the linkrev node (identity in the changelog case).
613 613 def lookupcl(x):
614 614 c = cl.read(x)
615 615 clrevorder[x] = len(clrevorder)
616 616 n = c[0]
617 617 # record the first changeset introducing this manifest version
618 618 mfs.setdefault(n, x)
619 619 # Record a complete list of potentially-changed files in
620 620 # this manifest.
621 621 changedfiles.update(c[3])
622 622 return x
623 623
624 624 self._verbosenote(_('uncompressed size of bundle content:\n'))
625 625 size = 0
626 626 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
627 627 size += len(chunk)
628 628 yield chunk
629 629 self._verbosenote(_('%8.i (changelog)\n') % size)
630 630
631 631 # We need to make sure that the linkrev in the changegroup refers to
632 632 # the first changeset that introduced the manifest or file revision.
633 633 # The fastpath is usually safer than the slowpath, because the filelogs
634 634 # are walked in revlog order.
635 635 #
636 636 # When taking the slowpath with reorder=None and the manifest revlog
637 637 # uses generaldelta, the manifest may be walked in the "wrong" order.
638 638 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
639 639 # cc0ff93d0c0c).
640 640 #
641 641 # When taking the fastpath, we are only vulnerable to reordering
642 642 # of the changelog itself. The changelog never uses generaldelta, so
643 643 # it is only reordered when reorder=True. To handle this case, we
644 644 # simply take the slowpath, which already has the 'clrevorder' logic.
645 645 # This was also fixed in cc0ff93d0c0c.
646 646 fastpathlinkrev = fastpathlinkrev and not self._reorder
647 647 # Treemanifests don't work correctly with fastpathlinkrev
648 648 # either, because we don't discover which directory nodes to
649 649 # send along with files. This could probably be fixed.
650 650 fastpathlinkrev = fastpathlinkrev and (
651 651 'treemanifest' not in repo.requirements)
652 652
653 653 for chunk in self.generatemanifests(commonrevs, clrevorder,
654 654 fastpathlinkrev, mfs, fnodes):
655 655 yield chunk
656 656 mfs.clear()
657 657 clrevs = set(cl.rev(x) for x in clnodes)
658 658
659 659 if not fastpathlinkrev:
660 660 def linknodes(unused, fname):
661 661 return fnodes.get(fname, {})
662 662 else:
663 663 cln = cl.node
664 664 def linknodes(filerevlog, fname):
665 665 llr = filerevlog.linkrev
666 666 fln = filerevlog.node
667 667 revs = ((r, llr(r)) for r in filerevlog)
668 668 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
669 669
670 670 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
671 671 source):
672 672 yield chunk
673 673
674 674 yield self.close()
675 675
676 676 if clnodes:
677 677 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
678 678
679 679 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
680 680 fnodes):
681 681 repo = self._repo
682 682 mfl = repo.manifestlog
683 683 dirlog = mfl._revlog.dirlog
684 684 tmfnodes = {'': mfs}
685 685
686 686 # Callback for the manifest, used to collect linkrevs for filelog
687 687 # revisions.
688 688 # Returns the linkrev node (collected in lookupcl).
689 689 def makelookupmflinknode(dir):
690 690 if fastpathlinkrev:
691 691 assert not dir
692 692 return mfs.__getitem__
693 693
694 694 def lookupmflinknode(x):
695 695 """Callback for looking up the linknode for manifests.
696 696
697 697 Returns the linkrev node for the specified manifest.
698 698
699 699 SIDE EFFECT:
700 700
701 701 1) fclnodes gets populated with the list of relevant
702 702 file nodes if we're not using fastpathlinkrev
703 703 2) When treemanifests are in use, collects treemanifest nodes
704 704 to send
705 705
706 706 Note that this means manifests must be completely sent to
707 707 the client before you can trust the list of files and
708 708 treemanifests to send.
709 709 """
710 710 clnode = tmfnodes[dir][x]
711 711 mdata = mfl.get(dir, x).readfast(shallow=True)
712 712 for p, n, fl in mdata.iterentries():
713 713 if fl == 't': # subdirectory manifest
714 714 subdir = dir + p + '/'
715 715 tmfclnodes = tmfnodes.setdefault(subdir, {})
716 716 tmfclnode = tmfclnodes.setdefault(n, clnode)
717 717 if clrevorder[clnode] < clrevorder[tmfclnode]:
718 718 tmfclnodes[n] = clnode
719 719 else:
720 720 f = dir + p
721 721 fclnodes = fnodes.setdefault(f, {})
722 722 fclnode = fclnodes.setdefault(n, clnode)
723 723 if clrevorder[clnode] < clrevorder[fclnode]:
724 724 fclnodes[n] = clnode
725 725 return clnode
726 726 return lookupmflinknode
727 727
728 728 size = 0
729 729 while tmfnodes:
730 730 dir = min(tmfnodes)
731 731 nodes = tmfnodes[dir]
732 732 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
733 733 if not dir or prunednodes:
734 734 for x in self._packmanifests(dir, prunednodes,
735 735 makelookupmflinknode(dir)):
736 736 size += len(x)
737 737 yield x
738 738 del tmfnodes[dir]
739 739 self._verbosenote(_('%8.i (manifests)\n') % size)
740 740 yield self._manifestsdone()
741 741
742 742 # The 'source' parameter is useful for extensions
743 743 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
744 744 repo = self._repo
745 745 progress = self._progress
746 746 msgbundling = _('bundling')
747 747
748 748 total = len(changedfiles)
749 749 # for progress output
750 750 msgfiles = _('files')
751 751 for i, fname in enumerate(sorted(changedfiles)):
752 752 filerevlog = repo.file(fname)
753 753 if not filerevlog:
754 754 raise error.Abort(_("empty or missing revlog for %s") % fname)
755 755
756 756 linkrevnodes = linknodes(filerevlog, fname)
757 757 # Lookup for filenodes, we collected the linkrev nodes above in the
758 758 # fastpath case and with lookupmf in the slowpath case.
759 759 def lookupfilelog(x):
760 760 return linkrevnodes[x]
761 761
762 762 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
763 763 if filenodes:
764 764 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
765 765 total=total)
766 766 h = self.fileheader(fname)
767 767 size = len(h)
768 768 yield h
769 769 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
770 770 size += len(chunk)
771 771 yield chunk
772 772 self._verbosenote(_('%8.i %s\n') % (size, fname))
773 773 progress(msgbundling, None)
774 774
775 775 def deltaparent(self, revlog, rev, p1, p2, prev):
776 776 return prev
777 777
778 778 def revchunk(self, revlog, rev, prev, linknode):
779 779 node = revlog.node(rev)
780 780 p1, p2 = revlog.parentrevs(rev)
781 781 base = self.deltaparent(revlog, rev, p1, p2, prev)
782 782
783 783 prefix = ''
784 784 if revlog.iscensored(base) or revlog.iscensored(rev):
785 785 try:
786 delta = revlog.revision(node)
786 delta = revlog.revision(node, raw=True)
787 787 except error.CensoredNodeError as e:
788 788 delta = e.tombstone
789 789 if base == nullrev:
790 790 prefix = mdiff.trivialdiffheader(len(delta))
791 791 else:
792 792 baselen = revlog.rawsize(base)
793 793 prefix = mdiff.replacediffheader(baselen, len(delta))
794 794 elif base == nullrev:
795 delta = revlog.revision(node)
795 delta = revlog.revision(node, raw=True)
796 796 prefix = mdiff.trivialdiffheader(len(delta))
797 797 else:
798 798 delta = revlog.revdiff(base, rev)
799 799 p1n, p2n = revlog.parents(node)
800 800 basenode = revlog.node(base)
801 801 flags = revlog.flags(rev)
802 802 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
803 803 meta += prefix
804 804 l = len(meta) + len(delta)
805 805 yield chunkheader(l)
806 806 yield meta
807 807 yield delta
808 808 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
809 809 # do nothing with basenode, it is implicitly the previous one in HG10
810 810 # do nothing with flags, it is implicitly 0 for cg1 and cg2
811 811 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
812 812
813 813 class cg2packer(cg1packer):
814 814 version = '02'
815 815 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
816 816
817 817 def __init__(self, repo, bundlecaps=None):
818 818 super(cg2packer, self).__init__(repo, bundlecaps)
819 819 if self._reorder is None:
820 820 # Since generaldelta is directly supported by cg2, reordering
821 821 # generally doesn't help, so we disable it by default (treating
822 822 # bundle.reorder=auto just like bundle.reorder=False).
823 823 self._reorder = False
824 824
825 825 def deltaparent(self, revlog, rev, p1, p2, prev):
826 826 dp = revlog.deltaparent(rev)
827 827 if dp == nullrev and revlog.storedeltachains:
828 828 # Avoid sending full revisions when delta parent is null. Pick prev
829 829 # in that case. It's tempting to pick p1 in this case, as p1 will
830 830 # be smaller in the common case. However, computing a delta against
831 831 # p1 may require resolving the raw text of p1, which could be
832 832 # expensive. The revlog caches should have prev cached, meaning
833 833 # less CPU for changegroup generation. There is likely room to add
834 834 # a flag and/or config option to control this behavior.
835 835 return prev
836 836 elif dp == nullrev:
837 837 # revlog is configured to use full snapshot for a reason,
838 838 # stick to full snapshot.
839 839 return nullrev
840 840 elif dp not in (p1, p2, prev):
841 841 # Pick prev when we can't be sure remote has the base revision.
842 842 return prev
843 843 else:
844 844 return dp
845 845
846 846 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
847 847 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
848 848 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
849 849
850 850 class cg3packer(cg2packer):
851 851 version = '03'
852 852 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
853 853
854 854 def _packmanifests(self, dir, mfnodes, lookuplinknode):
855 855 if dir:
856 856 yield self.fileheader(dir)
857 857
858 858 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
859 859 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
860 860 units=_('manifests')):
861 861 yield chunk
862 862
863 863 def _manifestsdone(self):
864 864 return self.close()
865 865
866 866 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
867 867 return struct.pack(
868 868 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
869 869
870 870 _packermap = {'01': (cg1packer, cg1unpacker),
871 871 # cg2 adds support for exchanging generaldelta
872 872 '02': (cg2packer, cg2unpacker),
873 873 # cg3 adds support for exchanging revlog flags and treemanifests
874 874 '03': (cg3packer, cg3unpacker),
875 875 }
876 876
877 877 def allsupportedversions(repo):
878 878 versions = set(_packermap.keys())
879 879 if not (repo.ui.configbool('experimental', 'changegroup3') or
880 880 repo.ui.configbool('experimental', 'treemanifest') or
881 881 'treemanifest' in repo.requirements):
882 882 versions.discard('03')
883 883 return versions
884 884
885 885 # Changegroup versions that can be applied to the repo
886 886 def supportedincomingversions(repo):
887 887 return allsupportedversions(repo)
888 888
889 889 # Changegroup versions that can be created from the repo
890 890 def supportedoutgoingversions(repo):
891 891 versions = allsupportedversions(repo)
892 892 if 'treemanifest' in repo.requirements:
893 893 # Versions 01 and 02 support only flat manifests and it's just too
894 894 # expensive to convert between the flat manifest and tree manifest on
895 895 # the fly. Since tree manifests are hashed differently, all of history
896 896 # would have to be converted. Instead, we simply don't even pretend to
897 897 # support versions 01 and 02.
898 898 versions.discard('01')
899 899 versions.discard('02')
900 900 return versions
901 901
902 902 def safeversion(repo):
903 903 # Finds the smallest version that it's safe to assume clients of the repo
904 904 # will support. For example, all hg versions that support generaldelta also
905 905 # support changegroup 02.
906 906 versions = supportedoutgoingversions(repo)
907 907 if 'generaldelta' in repo.requirements:
908 908 versions.discard('01')
909 909 assert versions
910 910 return min(versions)
911 911
912 912 def getbundler(version, repo, bundlecaps=None):
913 913 assert version in supportedoutgoingversions(repo)
914 914 return _packermap[version][0](repo, bundlecaps)
915 915
916 916 def getunbundler(version, fh, alg, extras=None):
917 917 return _packermap[version][1](fh, alg, extras=extras)
918 918
919 919 def _changegroupinfo(repo, nodes, source):
920 920 if repo.ui.verbose or source == 'bundle':
921 921 repo.ui.status(_("%d changesets found\n") % len(nodes))
922 922 if repo.ui.debugflag:
923 923 repo.ui.debug("list of changesets:\n")
924 924 for node in nodes:
925 925 repo.ui.debug("%s\n" % hex(node))
926 926
927 927 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
928 928 repo = repo.unfiltered()
929 929 commonrevs = outgoing.common
930 930 csets = outgoing.missing
931 931 heads = outgoing.missingheads
932 932 # We go through the fast path if we get told to, or if all (unfiltered
933 933 # heads have been requested (since we then know there all linkrevs will
934 934 # be pulled by the client).
935 935 heads.sort()
936 936 fastpathlinkrev = fastpath or (
937 937 repo.filtername is None and heads == sorted(repo.heads()))
938 938
939 939 repo.hook('preoutgoing', throw=True, source=source)
940 940 _changegroupinfo(repo, csets, source)
941 941 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
942 942
943 943 def getsubset(repo, outgoing, bundler, source, fastpath=False):
944 944 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
945 945 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
946 946 {'clcount': len(outgoing.missing)})
947 947
948 948 def changegroupsubset(repo, roots, heads, source, version='01'):
949 949 """Compute a changegroup consisting of all the nodes that are
950 950 descendants of any of the roots and ancestors of any of the heads.
951 951 Return a chunkbuffer object whose read() method will return
952 952 successive changegroup chunks.
953 953
954 954 It is fairly complex as determining which filenodes and which
955 955 manifest nodes need to be included for the changeset to be complete
956 956 is non-trivial.
957 957
958 958 Another wrinkle is doing the reverse, figuring out which changeset in
959 959 the changegroup a particular filenode or manifestnode belongs to.
960 960 """
961 961 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
962 962 bundler = getbundler(version, repo)
963 963 return getsubset(repo, outgoing, bundler, source)
964 964
965 965 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
966 966 version='01'):
967 967 """Like getbundle, but taking a discovery.outgoing as an argument.
968 968
969 969 This is only implemented for local repos and reuses potentially
970 970 precomputed sets in outgoing. Returns a raw changegroup generator."""
971 971 if not outgoing.missing:
972 972 return None
973 973 bundler = getbundler(version, repo, bundlecaps)
974 974 return getsubsetraw(repo, outgoing, bundler, source)
975 975
976 976 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
977 977 version='01'):
978 978 """Like getbundle, but taking a discovery.outgoing as an argument.
979 979
980 980 This is only implemented for local repos and reuses potentially
981 981 precomputed sets in outgoing."""
982 982 if not outgoing.missing:
983 983 return None
984 984 bundler = getbundler(version, repo, bundlecaps)
985 985 return getsubset(repo, outgoing, bundler, source)
986 986
987 987 def getchangegroup(repo, source, outgoing, bundlecaps=None,
988 988 version='01'):
989 989 """Like changegroupsubset, but returns the set difference between the
990 990 ancestors of heads and the ancestors common.
991 991
992 992 If heads is None, use the local heads. If common is None, use [nullid].
993 993
994 994 The nodes in common might not all be known locally due to the way the
995 995 current discovery protocol works.
996 996 """
997 997 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
998 998 version=version)
999 999
1000 1000 def changegroup(repo, basenodes, source):
1001 1001 # to avoid a race we use changegroupsubset() (issue1320)
1002 1002 return changegroupsubset(repo, basenodes, repo.heads(), source)
1003 1003
1004 1004 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1005 1005 revisions = 0
1006 1006 files = 0
1007 1007 for chunkdata in iter(source.filelogheader, {}):
1008 1008 files += 1
1009 1009 f = chunkdata["filename"]
1010 1010 repo.ui.debug("adding %s revisions\n" % f)
1011 1011 repo.ui.progress(_('files'), files, unit=_('files'),
1012 1012 total=expectedfiles)
1013 1013 fl = repo.file(f)
1014 1014 o = len(fl)
1015 1015 try:
1016 1016 if not fl.addgroup(source, revmap, trp):
1017 1017 raise error.Abort(_("received file revlog group is empty"))
1018 1018 except error.CensoredBaseError as e:
1019 1019 raise error.Abort(_("received delta base is censored: %s") % e)
1020 1020 revisions += len(fl) - o
1021 1021 if f in needfiles:
1022 1022 needs = needfiles[f]
1023 1023 for new in xrange(o, len(fl)):
1024 1024 n = fl.node(new)
1025 1025 if n in needs:
1026 1026 needs.remove(n)
1027 1027 else:
1028 1028 raise error.Abort(
1029 1029 _("received spurious file revlog entry"))
1030 1030 if not needs:
1031 1031 del needfiles[f]
1032 1032 repo.ui.progress(_('files'), None)
1033 1033
1034 1034 for f, needs in needfiles.iteritems():
1035 1035 fl = repo.file(f)
1036 1036 for n in needs:
1037 1037 try:
1038 1038 fl.rev(n)
1039 1039 except error.LookupError:
1040 1040 raise error.Abort(
1041 1041 _('missing file data for %s:%s - run hg verify') %
1042 1042 (f, hex(n)))
1043 1043
1044 1044 return revisions, files
@@ -1,2111 +1,2114 b''
1 1 # context.py - changeset and file context objects for mercurial
2 2 #
3 3 # Copyright 2006, 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 errno
11 11 import os
12 12 import re
13 13 import stat
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 addednodeid,
18 18 bin,
19 19 hex,
20 20 modifiednodeid,
21 21 newnodeid,
22 22 nullid,
23 23 nullrev,
24 24 short,
25 25 wdirid,
26 26 )
27 27 from . import (
28 28 encoding,
29 29 error,
30 30 fileset,
31 31 match as matchmod,
32 32 mdiff,
33 33 obsolete as obsmod,
34 34 patch,
35 35 phases,
36 36 repoview,
37 37 revlog,
38 38 scmutil,
39 39 subrepo,
40 40 util,
41 41 )
42 42
43 43 propertycache = util.propertycache
44 44
45 45 nonascii = re.compile(r'[^\x21-\x7f]').search
46 46
47 47 class basectx(object):
48 48 """A basectx object represents the common logic for its children:
49 49 changectx: read-only context that is already present in the repo,
50 50 workingctx: a context that represents the working directory and can
51 51 be committed,
52 52 memctx: a context that represents changes in-memory and can also
53 53 be committed."""
54 54 def __new__(cls, repo, changeid='', *args, **kwargs):
55 55 if isinstance(changeid, basectx):
56 56 return changeid
57 57
58 58 o = super(basectx, cls).__new__(cls)
59 59
60 60 o._repo = repo
61 61 o._rev = nullrev
62 62 o._node = nullid
63 63
64 64 return o
65 65
66 66 def __str__(self):
67 67 return short(self.node())
68 68
69 69 def __int__(self):
70 70 return self.rev()
71 71
72 72 def __repr__(self):
73 73 return "<%s %s>" % (type(self).__name__, str(self))
74 74
75 75 def __eq__(self, other):
76 76 try:
77 77 return type(self) == type(other) and self._rev == other._rev
78 78 except AttributeError:
79 79 return False
80 80
81 81 def __ne__(self, other):
82 82 return not (self == other)
83 83
84 84 def __contains__(self, key):
85 85 return key in self._manifest
86 86
87 87 def __getitem__(self, key):
88 88 return self.filectx(key)
89 89
90 90 def __iter__(self):
91 91 return iter(self._manifest)
92 92
93 93 def _manifestmatches(self, match, s):
94 94 """generate a new manifest filtered by the match argument
95 95
96 96 This method is for internal use only and mainly exists to provide an
97 97 object oriented way for other contexts to customize the manifest
98 98 generation.
99 99 """
100 100 return self.manifest().matches(match)
101 101
102 102 def _matchstatus(self, other, match):
103 103 """return match.always if match is none
104 104
105 105 This internal method provides a way for child objects to override the
106 106 match operator.
107 107 """
108 108 return match or matchmod.always(self._repo.root, self._repo.getcwd())
109 109
110 110 def _buildstatus(self, other, s, match, listignored, listclean,
111 111 listunknown):
112 112 """build a status with respect to another context"""
113 113 # Load earliest manifest first for caching reasons. More specifically,
114 114 # if you have revisions 1000 and 1001, 1001 is probably stored as a
115 115 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
116 116 # 1000 and cache it so that when you read 1001, we just need to apply a
117 117 # delta to what's in the cache. So that's one full reconstruction + one
118 118 # delta application.
119 119 if self.rev() is not None and self.rev() < other.rev():
120 120 self.manifest()
121 121 mf1 = other._manifestmatches(match, s)
122 122 mf2 = self._manifestmatches(match, s)
123 123
124 124 modified, added = [], []
125 125 removed = []
126 126 clean = []
127 127 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
128 128 deletedset = set(deleted)
129 129 d = mf1.diff(mf2, clean=listclean)
130 130 for fn, value in d.iteritems():
131 131 if fn in deletedset:
132 132 continue
133 133 if value is None:
134 134 clean.append(fn)
135 135 continue
136 136 (node1, flag1), (node2, flag2) = value
137 137 if node1 is None:
138 138 added.append(fn)
139 139 elif node2 is None:
140 140 removed.append(fn)
141 141 elif flag1 != flag2:
142 142 modified.append(fn)
143 143 elif node2 != newnodeid:
144 144 # When comparing files between two commits, we save time by
145 145 # not comparing the file contents when the nodeids differ.
146 146 # Note that this means we incorrectly report a reverted change
147 147 # to a file as a modification.
148 148 modified.append(fn)
149 149 elif self[fn].cmp(other[fn]):
150 150 modified.append(fn)
151 151 else:
152 152 clean.append(fn)
153 153
154 154 if removed:
155 155 # need to filter files if they are already reported as removed
156 156 unknown = [fn for fn in unknown if fn not in mf1]
157 157 ignored = [fn for fn in ignored if fn not in mf1]
158 158 # if they're deleted, don't report them as removed
159 159 removed = [fn for fn in removed if fn not in deletedset]
160 160
161 161 return scmutil.status(modified, added, removed, deleted, unknown,
162 162 ignored, clean)
163 163
164 164 @propertycache
165 165 def substate(self):
166 166 return subrepo.state(self, self._repo.ui)
167 167
168 168 def subrev(self, subpath):
169 169 return self.substate[subpath][1]
170 170
171 171 def rev(self):
172 172 return self._rev
173 173 def node(self):
174 174 return self._node
175 175 def hex(self):
176 176 return hex(self.node())
177 177 def manifest(self):
178 178 return self._manifest
179 179 def manifestctx(self):
180 180 return self._manifestctx
181 181 def repo(self):
182 182 return self._repo
183 183 def phasestr(self):
184 184 return phases.phasenames[self.phase()]
185 185 def mutable(self):
186 186 return self.phase() > phases.public
187 187
188 188 def getfileset(self, expr):
189 189 return fileset.getfileset(self, expr)
190 190
191 191 def obsolete(self):
192 192 """True if the changeset is obsolete"""
193 193 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
194 194
195 195 def extinct(self):
196 196 """True if the changeset is extinct"""
197 197 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
198 198
199 199 def unstable(self):
200 200 """True if the changeset is not obsolete but it's ancestor are"""
201 201 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
202 202
203 203 def bumped(self):
204 204 """True if the changeset try to be a successor of a public changeset
205 205
206 206 Only non-public and non-obsolete changesets may be bumped.
207 207 """
208 208 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
209 209
210 210 def divergent(self):
211 211 """Is a successors of a changeset with multiple possible successors set
212 212
213 213 Only non-public and non-obsolete changesets may be divergent.
214 214 """
215 215 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
216 216
217 217 def troubled(self):
218 218 """True if the changeset is either unstable, bumped or divergent"""
219 219 return self.unstable() or self.bumped() or self.divergent()
220 220
221 221 def troubles(self):
222 222 """return the list of troubles affecting this changesets.
223 223
224 224 Troubles are returned as strings. possible values are:
225 225 - unstable,
226 226 - bumped,
227 227 - divergent.
228 228 """
229 229 troubles = []
230 230 if self.unstable():
231 231 troubles.append('unstable')
232 232 if self.bumped():
233 233 troubles.append('bumped')
234 234 if self.divergent():
235 235 troubles.append('divergent')
236 236 return troubles
237 237
238 238 def parents(self):
239 239 """return contexts for each parent changeset"""
240 240 return self._parents
241 241
242 242 def p1(self):
243 243 return self._parents[0]
244 244
245 245 def p2(self):
246 246 parents = self._parents
247 247 if len(parents) == 2:
248 248 return parents[1]
249 249 return changectx(self._repo, nullrev)
250 250
251 251 def _fileinfo(self, path):
252 252 if '_manifest' in self.__dict__:
253 253 try:
254 254 return self._manifest[path], self._manifest.flags(path)
255 255 except KeyError:
256 256 raise error.ManifestLookupError(self._node, path,
257 257 _('not found in manifest'))
258 258 if '_manifestdelta' in self.__dict__ or path in self.files():
259 259 if path in self._manifestdelta:
260 260 return (self._manifestdelta[path],
261 261 self._manifestdelta.flags(path))
262 262 mfl = self._repo.manifestlog
263 263 try:
264 264 node, flag = mfl[self._changeset.manifest].find(path)
265 265 except KeyError:
266 266 raise error.ManifestLookupError(self._node, path,
267 267 _('not found in manifest'))
268 268
269 269 return node, flag
270 270
271 271 def filenode(self, path):
272 272 return self._fileinfo(path)[0]
273 273
274 274 def flags(self, path):
275 275 try:
276 276 return self._fileinfo(path)[1]
277 277 except error.LookupError:
278 278 return ''
279 279
280 280 def sub(self, path, allowcreate=True):
281 281 '''return a subrepo for the stored revision of path, never wdir()'''
282 282 return subrepo.subrepo(self, path, allowcreate=allowcreate)
283 283
284 284 def nullsub(self, path, pctx):
285 285 return subrepo.nullsubrepo(self, path, pctx)
286 286
287 287 def workingsub(self, path):
288 288 '''return a subrepo for the stored revision, or wdir if this is a wdir
289 289 context.
290 290 '''
291 291 return subrepo.subrepo(self, path, allowwdir=True)
292 292
293 293 def match(self, pats=[], include=None, exclude=None, default='glob',
294 294 listsubrepos=False, badfn=None):
295 295 r = self._repo
296 296 return matchmod.match(r.root, r.getcwd(), pats,
297 297 include, exclude, default,
298 298 auditor=r.nofsauditor, ctx=self,
299 299 listsubrepos=listsubrepos, badfn=badfn)
300 300
301 301 def diff(self, ctx2=None, match=None, **opts):
302 302 """Returns a diff generator for the given contexts and matcher"""
303 303 if ctx2 is None:
304 304 ctx2 = self.p1()
305 305 if ctx2 is not None:
306 306 ctx2 = self._repo[ctx2]
307 307 diffopts = patch.diffopts(self._repo.ui, opts)
308 308 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
309 309
310 310 def dirs(self):
311 311 return self._manifest.dirs()
312 312
313 313 def hasdir(self, dir):
314 314 return self._manifest.hasdir(dir)
315 315
316 316 def dirty(self, missing=False, merge=True, branch=True):
317 317 return False
318 318
319 319 def status(self, other=None, match=None, listignored=False,
320 320 listclean=False, listunknown=False, listsubrepos=False):
321 321 """return status of files between two nodes or node and working
322 322 directory.
323 323
324 324 If other is None, compare this node with working directory.
325 325
326 326 returns (modified, added, removed, deleted, unknown, ignored, clean)
327 327 """
328 328
329 329 ctx1 = self
330 330 ctx2 = self._repo[other]
331 331
332 332 # This next code block is, admittedly, fragile logic that tests for
333 333 # reversing the contexts and wouldn't need to exist if it weren't for
334 334 # the fast (and common) code path of comparing the working directory
335 335 # with its first parent.
336 336 #
337 337 # What we're aiming for here is the ability to call:
338 338 #
339 339 # workingctx.status(parentctx)
340 340 #
341 341 # If we always built the manifest for each context and compared those,
342 342 # then we'd be done. But the special case of the above call means we
343 343 # just copy the manifest of the parent.
344 344 reversed = False
345 345 if (not isinstance(ctx1, changectx)
346 346 and isinstance(ctx2, changectx)):
347 347 reversed = True
348 348 ctx1, ctx2 = ctx2, ctx1
349 349
350 350 match = ctx2._matchstatus(ctx1, match)
351 351 r = scmutil.status([], [], [], [], [], [], [])
352 352 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
353 353 listunknown)
354 354
355 355 if reversed:
356 356 # Reverse added and removed. Clear deleted, unknown and ignored as
357 357 # these make no sense to reverse.
358 358 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
359 359 r.clean)
360 360
361 361 if listsubrepos:
362 362 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
363 363 try:
364 364 rev2 = ctx2.subrev(subpath)
365 365 except KeyError:
366 366 # A subrepo that existed in node1 was deleted between
367 367 # node1 and node2 (inclusive). Thus, ctx2's substate
368 368 # won't contain that subpath. The best we can do ignore it.
369 369 rev2 = None
370 370 submatch = matchmod.subdirmatcher(subpath, match)
371 371 s = sub.status(rev2, match=submatch, ignored=listignored,
372 372 clean=listclean, unknown=listunknown,
373 373 listsubrepos=True)
374 374 for rfiles, sfiles in zip(r, s):
375 375 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
376 376
377 377 for l in r:
378 378 l.sort()
379 379
380 380 return r
381 381
382 382
383 383 def makememctx(repo, parents, text, user, date, branch, files, store,
384 384 editor=None, extra=None):
385 385 def getfilectx(repo, memctx, path):
386 386 data, mode, copied = store.getfile(path)
387 387 if data is None:
388 388 return None
389 389 islink, isexec = mode
390 390 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
391 391 copied=copied, memctx=memctx)
392 392 if extra is None:
393 393 extra = {}
394 394 if branch:
395 395 extra['branch'] = encoding.fromlocal(branch)
396 396 ctx = memctx(repo, parents, text, files, getfilectx, user,
397 397 date, extra, editor)
398 398 return ctx
399 399
400 400 class changectx(basectx):
401 401 """A changecontext object makes access to data related to a particular
402 402 changeset convenient. It represents a read-only context already present in
403 403 the repo."""
404 404 def __init__(self, repo, changeid=''):
405 405 """changeid is a revision number, node, or tag"""
406 406
407 407 # since basectx.__new__ already took care of copying the object, we
408 408 # don't need to do anything in __init__, so we just exit here
409 409 if isinstance(changeid, basectx):
410 410 return
411 411
412 412 if changeid == '':
413 413 changeid = '.'
414 414 self._repo = repo
415 415
416 416 try:
417 417 if isinstance(changeid, int):
418 418 self._node = repo.changelog.node(changeid)
419 419 self._rev = changeid
420 420 return
421 421 if isinstance(changeid, long):
422 422 changeid = str(changeid)
423 423 if changeid == 'null':
424 424 self._node = nullid
425 425 self._rev = nullrev
426 426 return
427 427 if changeid == 'tip':
428 428 self._node = repo.changelog.tip()
429 429 self._rev = repo.changelog.rev(self._node)
430 430 return
431 431 if changeid == '.' or changeid == repo.dirstate.p1():
432 432 # this is a hack to delay/avoid loading obsmarkers
433 433 # when we know that '.' won't be hidden
434 434 self._node = repo.dirstate.p1()
435 435 self._rev = repo.unfiltered().changelog.rev(self._node)
436 436 return
437 437 if len(changeid) == 20:
438 438 try:
439 439 self._node = changeid
440 440 self._rev = repo.changelog.rev(changeid)
441 441 return
442 442 except error.FilteredRepoLookupError:
443 443 raise
444 444 except LookupError:
445 445 pass
446 446
447 447 try:
448 448 r = int(changeid)
449 449 if str(r) != changeid:
450 450 raise ValueError
451 451 l = len(repo.changelog)
452 452 if r < 0:
453 453 r += l
454 454 if r < 0 or r >= l:
455 455 raise ValueError
456 456 self._rev = r
457 457 self._node = repo.changelog.node(r)
458 458 return
459 459 except error.FilteredIndexError:
460 460 raise
461 461 except (ValueError, OverflowError, IndexError):
462 462 pass
463 463
464 464 if len(changeid) == 40:
465 465 try:
466 466 self._node = bin(changeid)
467 467 self._rev = repo.changelog.rev(self._node)
468 468 return
469 469 except error.FilteredLookupError:
470 470 raise
471 471 except (TypeError, LookupError):
472 472 pass
473 473
474 474 # lookup bookmarks through the name interface
475 475 try:
476 476 self._node = repo.names.singlenode(repo, changeid)
477 477 self._rev = repo.changelog.rev(self._node)
478 478 return
479 479 except KeyError:
480 480 pass
481 481 except error.FilteredRepoLookupError:
482 482 raise
483 483 except error.RepoLookupError:
484 484 pass
485 485
486 486 self._node = repo.unfiltered().changelog._partialmatch(changeid)
487 487 if self._node is not None:
488 488 self._rev = repo.changelog.rev(self._node)
489 489 return
490 490
491 491 # lookup failed
492 492 # check if it might have come from damaged dirstate
493 493 #
494 494 # XXX we could avoid the unfiltered if we had a recognizable
495 495 # exception for filtered changeset access
496 496 if changeid in repo.unfiltered().dirstate.parents():
497 497 msg = _("working directory has unknown parent '%s'!")
498 498 raise error.Abort(msg % short(changeid))
499 499 try:
500 500 if len(changeid) == 20 and nonascii(changeid):
501 501 changeid = hex(changeid)
502 502 except TypeError:
503 503 pass
504 504 except (error.FilteredIndexError, error.FilteredLookupError,
505 505 error.FilteredRepoLookupError):
506 506 if repo.filtername.startswith('visible'):
507 507 msg = _("hidden revision '%s'") % changeid
508 508 hint = _('use --hidden to access hidden revisions')
509 509 raise error.FilteredRepoLookupError(msg, hint=hint)
510 510 msg = _("filtered revision '%s' (not in '%s' subset)")
511 511 msg %= (changeid, repo.filtername)
512 512 raise error.FilteredRepoLookupError(msg)
513 513 except IndexError:
514 514 pass
515 515 raise error.RepoLookupError(
516 516 _("unknown revision '%s'") % changeid)
517 517
518 518 def __hash__(self):
519 519 try:
520 520 return hash(self._rev)
521 521 except AttributeError:
522 522 return id(self)
523 523
524 524 def __nonzero__(self):
525 525 return self._rev != nullrev
526 526
527 527 @propertycache
528 528 def _changeset(self):
529 529 return self._repo.changelog.changelogrevision(self.rev())
530 530
531 531 @propertycache
532 532 def _manifest(self):
533 533 return self._manifestctx.read()
534 534
535 535 @propertycache
536 536 def _manifestctx(self):
537 537 return self._repo.manifestlog[self._changeset.manifest]
538 538
539 539 @propertycache
540 540 def _manifestdelta(self):
541 541 return self._manifestctx.readdelta()
542 542
543 543 @propertycache
544 544 def _parents(self):
545 545 repo = self._repo
546 546 p1, p2 = repo.changelog.parentrevs(self._rev)
547 547 if p2 == nullrev:
548 548 return [changectx(repo, p1)]
549 549 return [changectx(repo, p1), changectx(repo, p2)]
550 550
551 551 def changeset(self):
552 552 c = self._changeset
553 553 return (
554 554 c.manifest,
555 555 c.user,
556 556 c.date,
557 557 c.files,
558 558 c.description,
559 559 c.extra,
560 560 )
561 561 def manifestnode(self):
562 562 return self._changeset.manifest
563 563
564 564 def user(self):
565 565 return self._changeset.user
566 566 def date(self):
567 567 return self._changeset.date
568 568 def files(self):
569 569 return self._changeset.files
570 570 def description(self):
571 571 return self._changeset.description
572 572 def branch(self):
573 573 return encoding.tolocal(self._changeset.extra.get("branch"))
574 574 def closesbranch(self):
575 575 return 'close' in self._changeset.extra
576 576 def extra(self):
577 577 return self._changeset.extra
578 578 def tags(self):
579 579 return self._repo.nodetags(self._node)
580 580 def bookmarks(self):
581 581 return self._repo.nodebookmarks(self._node)
582 582 def phase(self):
583 583 return self._repo._phasecache.phase(self._repo, self._rev)
584 584 def hidden(self):
585 585 return self._rev in repoview.filterrevs(self._repo, 'visible')
586 586
587 587 def children(self):
588 588 """return contexts for each child changeset"""
589 589 c = self._repo.changelog.children(self._node)
590 590 return [changectx(self._repo, x) for x in c]
591 591
592 592 def ancestors(self):
593 593 for a in self._repo.changelog.ancestors([self._rev]):
594 594 yield changectx(self._repo, a)
595 595
596 596 def descendants(self):
597 597 for d in self._repo.changelog.descendants([self._rev]):
598 598 yield changectx(self._repo, d)
599 599
600 600 def filectx(self, path, fileid=None, filelog=None):
601 601 """get a file context from this changeset"""
602 602 if fileid is None:
603 603 fileid = self.filenode(path)
604 604 return filectx(self._repo, path, fileid=fileid,
605 605 changectx=self, filelog=filelog)
606 606
607 607 def ancestor(self, c2, warn=False):
608 608 """return the "best" ancestor context of self and c2
609 609
610 610 If there are multiple candidates, it will show a message and check
611 611 merge.preferancestor configuration before falling back to the
612 612 revlog ancestor."""
613 613 # deal with workingctxs
614 614 n2 = c2._node
615 615 if n2 is None:
616 616 n2 = c2._parents[0]._node
617 617 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
618 618 if not cahs:
619 619 anc = nullid
620 620 elif len(cahs) == 1:
621 621 anc = cahs[0]
622 622 else:
623 623 # experimental config: merge.preferancestor
624 624 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
625 625 try:
626 626 ctx = changectx(self._repo, r)
627 627 except error.RepoLookupError:
628 628 continue
629 629 anc = ctx.node()
630 630 if anc in cahs:
631 631 break
632 632 else:
633 633 anc = self._repo.changelog.ancestor(self._node, n2)
634 634 if warn:
635 635 self._repo.ui.status(
636 636 (_("note: using %s as ancestor of %s and %s\n") %
637 637 (short(anc), short(self._node), short(n2))) +
638 638 ''.join(_(" alternatively, use --config "
639 639 "merge.preferancestor=%s\n") %
640 640 short(n) for n in sorted(cahs) if n != anc))
641 641 return changectx(self._repo, anc)
642 642
643 643 def descendant(self, other):
644 644 """True if other is descendant of this changeset"""
645 645 return self._repo.changelog.descendant(self._rev, other._rev)
646 646
647 647 def walk(self, match):
648 648 '''Generates matching file names.'''
649 649
650 650 # Wrap match.bad method to have message with nodeid
651 651 def bad(fn, msg):
652 652 # The manifest doesn't know about subrepos, so don't complain about
653 653 # paths into valid subrepos.
654 654 if any(fn == s or fn.startswith(s + '/')
655 655 for s in self.substate):
656 656 return
657 657 match.bad(fn, _('no such file in rev %s') % self)
658 658
659 659 m = matchmod.badmatch(match, bad)
660 660 return self._manifest.walk(m)
661 661
662 662 def matches(self, match):
663 663 return self.walk(match)
664 664
665 665 class basefilectx(object):
666 666 """A filecontext object represents the common logic for its children:
667 667 filectx: read-only access to a filerevision that is already present
668 668 in the repo,
669 669 workingfilectx: a filecontext that represents files from the working
670 670 directory,
671 671 memfilectx: a filecontext that represents files in-memory."""
672 672 def __new__(cls, repo, path, *args, **kwargs):
673 673 return super(basefilectx, cls).__new__(cls)
674 674
675 675 @propertycache
676 676 def _filelog(self):
677 677 return self._repo.file(self._path)
678 678
679 679 @propertycache
680 680 def _changeid(self):
681 681 if '_changeid' in self.__dict__:
682 682 return self._changeid
683 683 elif '_changectx' in self.__dict__:
684 684 return self._changectx.rev()
685 685 elif '_descendantrev' in self.__dict__:
686 686 # this file context was created from a revision with a known
687 687 # descendant, we can (lazily) correct for linkrev aliases
688 688 return self._adjustlinkrev(self._descendantrev)
689 689 else:
690 690 return self._filelog.linkrev(self._filerev)
691 691
692 692 @propertycache
693 693 def _filenode(self):
694 694 if '_fileid' in self.__dict__:
695 695 return self._filelog.lookup(self._fileid)
696 696 else:
697 697 return self._changectx.filenode(self._path)
698 698
699 699 @propertycache
700 700 def _filerev(self):
701 701 return self._filelog.rev(self._filenode)
702 702
703 703 @propertycache
704 704 def _repopath(self):
705 705 return self._path
706 706
707 707 def __nonzero__(self):
708 708 try:
709 709 self._filenode
710 710 return True
711 711 except error.LookupError:
712 712 # file is missing
713 713 return False
714 714
715 715 def __str__(self):
716 716 try:
717 717 return "%s@%s" % (self.path(), self._changectx)
718 718 except error.LookupError:
719 719 return "%s@???" % self.path()
720 720
721 721 def __repr__(self):
722 722 return "<%s %s>" % (type(self).__name__, str(self))
723 723
724 724 def __hash__(self):
725 725 try:
726 726 return hash((self._path, self._filenode))
727 727 except AttributeError:
728 728 return id(self)
729 729
730 730 def __eq__(self, other):
731 731 try:
732 732 return (type(self) == type(other) and self._path == other._path
733 733 and self._filenode == other._filenode)
734 734 except AttributeError:
735 735 return False
736 736
737 737 def __ne__(self, other):
738 738 return not (self == other)
739 739
740 740 def filerev(self):
741 741 return self._filerev
742 742 def filenode(self):
743 743 return self._filenode
744 744 def flags(self):
745 745 return self._changectx.flags(self._path)
746 746 def filelog(self):
747 747 return self._filelog
748 748 def rev(self):
749 749 return self._changeid
750 750 def linkrev(self):
751 751 return self._filelog.linkrev(self._filerev)
752 752 def node(self):
753 753 return self._changectx.node()
754 754 def hex(self):
755 755 return self._changectx.hex()
756 756 def user(self):
757 757 return self._changectx.user()
758 758 def date(self):
759 759 return self._changectx.date()
760 760 def files(self):
761 761 return self._changectx.files()
762 762 def description(self):
763 763 return self._changectx.description()
764 764 def branch(self):
765 765 return self._changectx.branch()
766 766 def extra(self):
767 767 return self._changectx.extra()
768 768 def phase(self):
769 769 return self._changectx.phase()
770 770 def phasestr(self):
771 771 return self._changectx.phasestr()
772 772 def manifest(self):
773 773 return self._changectx.manifest()
774 774 def changectx(self):
775 775 return self._changectx
776 776 def repo(self):
777 777 return self._repo
778 778
779 779 def path(self):
780 780 return self._path
781 781
782 782 def isbinary(self):
783 783 try:
784 784 return util.binary(self.data())
785 785 except IOError:
786 786 return False
787 787 def isexec(self):
788 788 return 'x' in self.flags()
789 789 def islink(self):
790 790 return 'l' in self.flags()
791 791
792 792 def isabsent(self):
793 793 """whether this filectx represents a file not in self._changectx
794 794
795 795 This is mainly for merge code to detect change/delete conflicts. This is
796 796 expected to be True for all subclasses of basectx."""
797 797 return False
798 798
799 799 _customcmp = False
800 800 def cmp(self, fctx):
801 801 """compare with other file context
802 802
803 803 returns True if different than fctx.
804 804 """
805 805 if fctx._customcmp:
806 806 return fctx.cmp(self)
807 807
808 808 if (fctx._filenode is None
809 809 and (self._repo._encodefilterpats
810 810 # if file data starts with '\1\n', empty metadata block is
811 811 # prepended, which adds 4 bytes to filelog.size().
812 812 or self.size() - 4 == fctx.size())
813 813 or self.size() == fctx.size()):
814 814 return self._filelog.cmp(self._filenode, fctx.data())
815 815
816 816 return True
817 817
818 818 def _adjustlinkrev(self, srcrev, inclusive=False):
819 819 """return the first ancestor of <srcrev> introducing <fnode>
820 820
821 821 If the linkrev of the file revision does not point to an ancestor of
822 822 srcrev, we'll walk down the ancestors until we find one introducing
823 823 this file revision.
824 824
825 825 :srcrev: the changeset revision we search ancestors from
826 826 :inclusive: if true, the src revision will also be checked
827 827 """
828 828 repo = self._repo
829 829 cl = repo.unfiltered().changelog
830 830 mfl = repo.manifestlog
831 831 # fetch the linkrev
832 832 lkr = self.linkrev()
833 833 # hack to reuse ancestor computation when searching for renames
834 834 memberanc = getattr(self, '_ancestrycontext', None)
835 835 iteranc = None
836 836 if srcrev is None:
837 837 # wctx case, used by workingfilectx during mergecopy
838 838 revs = [p.rev() for p in self._repo[None].parents()]
839 839 inclusive = True # we skipped the real (revless) source
840 840 else:
841 841 revs = [srcrev]
842 842 if memberanc is None:
843 843 memberanc = iteranc = cl.ancestors(revs, lkr,
844 844 inclusive=inclusive)
845 845 # check if this linkrev is an ancestor of srcrev
846 846 if lkr not in memberanc:
847 847 if iteranc is None:
848 848 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
849 849 fnode = self._filenode
850 850 path = self._path
851 851 for a in iteranc:
852 852 ac = cl.read(a) # get changeset data (we avoid object creation)
853 853 if path in ac[3]: # checking the 'files' field.
854 854 # The file has been touched, check if the content is
855 855 # similar to the one we search for.
856 856 if fnode == mfl[ac[0]].readfast().get(path):
857 857 return a
858 858 # In theory, we should never get out of that loop without a result.
859 859 # But if manifest uses a buggy file revision (not children of the
860 860 # one it replaces) we could. Such a buggy situation will likely
861 861 # result is crash somewhere else at to some point.
862 862 return lkr
863 863
864 864 def introrev(self):
865 865 """return the rev of the changeset which introduced this file revision
866 866
867 867 This method is different from linkrev because it take into account the
868 868 changeset the filectx was created from. It ensures the returned
869 869 revision is one of its ancestors. This prevents bugs from
870 870 'linkrev-shadowing' when a file revision is used by multiple
871 871 changesets.
872 872 """
873 873 lkr = self.linkrev()
874 874 attrs = vars(self)
875 875 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
876 876 if noctx or self.rev() == lkr:
877 877 return self.linkrev()
878 878 return self._adjustlinkrev(self.rev(), inclusive=True)
879 879
880 880 def _parentfilectx(self, path, fileid, filelog):
881 881 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
882 882 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
883 883 if '_changeid' in vars(self) or '_changectx' in vars(self):
884 884 # If self is associated with a changeset (probably explicitly
885 885 # fed), ensure the created filectx is associated with a
886 886 # changeset that is an ancestor of self.changectx.
887 887 # This lets us later use _adjustlinkrev to get a correct link.
888 888 fctx._descendantrev = self.rev()
889 889 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
890 890 elif '_descendantrev' in vars(self):
891 891 # Otherwise propagate _descendantrev if we have one associated.
892 892 fctx._descendantrev = self._descendantrev
893 893 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
894 894 return fctx
895 895
896 896 def parents(self):
897 897 _path = self._path
898 898 fl = self._filelog
899 899 parents = self._filelog.parents(self._filenode)
900 900 pl = [(_path, node, fl) for node in parents if node != nullid]
901 901
902 902 r = fl.renamed(self._filenode)
903 903 if r:
904 904 # - In the simple rename case, both parent are nullid, pl is empty.
905 905 # - In case of merge, only one of the parent is null id and should
906 906 # be replaced with the rename information. This parent is -always-
907 907 # the first one.
908 908 #
909 909 # As null id have always been filtered out in the previous list
910 910 # comprehension, inserting to 0 will always result in "replacing
911 911 # first nullid parent with rename information.
912 912 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
913 913
914 914 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
915 915
916 916 def p1(self):
917 917 return self.parents()[0]
918 918
919 919 def p2(self):
920 920 p = self.parents()
921 921 if len(p) == 2:
922 922 return p[1]
923 923 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
924 924
925 925 def annotate(self, follow=False, linenumber=False, diffopts=None):
926 926 '''returns a list of tuples of ((ctx, number), line) for each line
927 927 in the file, where ctx is the filectx of the node where
928 928 that line was last changed; if linenumber parameter is true, number is
929 929 the line number at the first appearance in the managed file, otherwise,
930 930 number has a fixed value of False.
931 931 '''
932 932
933 933 def lines(text):
934 934 if text.endswith("\n"):
935 935 return text.count("\n")
936 936 return text.count("\n") + int(bool(text))
937 937
938 938 if linenumber:
939 939 def decorate(text, rev):
940 940 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
941 941 else:
942 942 def decorate(text, rev):
943 943 return ([(rev, False)] * lines(text), text)
944 944
945 945 def pair(parent, child):
946 946 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts)
947 947 for (a1, a2, b1, b2), t in blocks:
948 948 # Changed blocks ('!') or blocks made only of blank lines ('~')
949 949 # belong to the child.
950 950 if t == '=':
951 951 child[0][b1:b2] = parent[0][a1:a2]
952 952 return child
953 953
954 954 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
955 955
956 956 def parents(f):
957 957 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
958 958 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
959 959 # from the topmost introrev (= srcrev) down to p.linkrev() if it
960 960 # isn't an ancestor of the srcrev.
961 961 f._changeid
962 962 pl = f.parents()
963 963
964 964 # Don't return renamed parents if we aren't following.
965 965 if not follow:
966 966 pl = [p for p in pl if p.path() == f.path()]
967 967
968 968 # renamed filectx won't have a filelog yet, so set it
969 969 # from the cache to save time
970 970 for p in pl:
971 971 if not '_filelog' in p.__dict__:
972 972 p._filelog = getlog(p.path())
973 973
974 974 return pl
975 975
976 976 # use linkrev to find the first changeset where self appeared
977 977 base = self
978 978 introrev = self.introrev()
979 979 if self.rev() != introrev:
980 980 base = self.filectx(self.filenode(), changeid=introrev)
981 981 if getattr(base, '_ancestrycontext', None) is None:
982 982 cl = self._repo.changelog
983 983 if introrev is None:
984 984 # wctx is not inclusive, but works because _ancestrycontext
985 985 # is used to test filelog revisions
986 986 ac = cl.ancestors([p.rev() for p in base.parents()],
987 987 inclusive=True)
988 988 else:
989 989 ac = cl.ancestors([introrev], inclusive=True)
990 990 base._ancestrycontext = ac
991 991
992 992 # This algorithm would prefer to be recursive, but Python is a
993 993 # bit recursion-hostile. Instead we do an iterative
994 994 # depth-first search.
995 995
996 996 # 1st DFS pre-calculates pcache and needed
997 997 visit = [base]
998 998 pcache = {}
999 999 needed = {base: 1}
1000 1000 while visit:
1001 1001 f = visit.pop()
1002 1002 if f in pcache:
1003 1003 continue
1004 1004 pl = parents(f)
1005 1005 pcache[f] = pl
1006 1006 for p in pl:
1007 1007 needed[p] = needed.get(p, 0) + 1
1008 1008 if p not in pcache:
1009 1009 visit.append(p)
1010 1010
1011 1011 # 2nd DFS does the actual annotate
1012 1012 visit[:] = [base]
1013 1013 hist = {}
1014 1014 while visit:
1015 1015 f = visit[-1]
1016 1016 if f in hist:
1017 1017 visit.pop()
1018 1018 continue
1019 1019
1020 1020 ready = True
1021 1021 pl = pcache[f]
1022 1022 for p in pl:
1023 1023 if p not in hist:
1024 1024 ready = False
1025 1025 visit.append(p)
1026 1026 if ready:
1027 1027 visit.pop()
1028 1028 curr = decorate(f.data(), f)
1029 1029 for p in pl:
1030 1030 curr = pair(hist[p], curr)
1031 1031 if needed[p] == 1:
1032 1032 del hist[p]
1033 1033 del needed[p]
1034 1034 else:
1035 1035 needed[p] -= 1
1036 1036
1037 1037 hist[f] = curr
1038 1038 del pcache[f]
1039 1039
1040 1040 return zip(hist[base][0], hist[base][1].splitlines(True))
1041 1041
1042 1042 def ancestors(self, followfirst=False):
1043 1043 visit = {}
1044 1044 c = self
1045 1045 if followfirst:
1046 1046 cut = 1
1047 1047 else:
1048 1048 cut = None
1049 1049
1050 1050 while True:
1051 1051 for parent in c.parents()[:cut]:
1052 1052 visit[(parent.linkrev(), parent.filenode())] = parent
1053 1053 if not visit:
1054 1054 break
1055 1055 c = visit.pop(max(visit))
1056 1056 yield c
1057 1057
1058 1058 class filectx(basefilectx):
1059 1059 """A filecontext object makes access to data related to a particular
1060 1060 filerevision convenient."""
1061 1061 def __init__(self, repo, path, changeid=None, fileid=None,
1062 1062 filelog=None, changectx=None):
1063 1063 """changeid can be a changeset revision, node, or tag.
1064 1064 fileid can be a file revision or node."""
1065 1065 self._repo = repo
1066 1066 self._path = path
1067 1067
1068 1068 assert (changeid is not None
1069 1069 or fileid is not None
1070 1070 or changectx is not None), \
1071 1071 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1072 1072 % (changeid, fileid, changectx))
1073 1073
1074 1074 if filelog is not None:
1075 1075 self._filelog = filelog
1076 1076
1077 1077 if changeid is not None:
1078 1078 self._changeid = changeid
1079 1079 if changectx is not None:
1080 1080 self._changectx = changectx
1081 1081 if fileid is not None:
1082 1082 self._fileid = fileid
1083 1083
1084 1084 @propertycache
1085 1085 def _changectx(self):
1086 1086 try:
1087 1087 return changectx(self._repo, self._changeid)
1088 1088 except error.FilteredRepoLookupError:
1089 1089 # Linkrev may point to any revision in the repository. When the
1090 1090 # repository is filtered this may lead to `filectx` trying to build
1091 1091 # `changectx` for filtered revision. In such case we fallback to
1092 1092 # creating `changectx` on the unfiltered version of the reposition.
1093 1093 # This fallback should not be an issue because `changectx` from
1094 1094 # `filectx` are not used in complex operations that care about
1095 1095 # filtering.
1096 1096 #
1097 1097 # This fallback is a cheap and dirty fix that prevent several
1098 1098 # crashes. It does not ensure the behavior is correct. However the
1099 1099 # behavior was not correct before filtering either and "incorrect
1100 1100 # behavior" is seen as better as "crash"
1101 1101 #
1102 1102 # Linkrevs have several serious troubles with filtering that are
1103 1103 # complicated to solve. Proper handling of the issue here should be
1104 1104 # considered when solving linkrev issue are on the table.
1105 1105 return changectx(self._repo.unfiltered(), self._changeid)
1106 1106
1107 1107 def filectx(self, fileid, changeid=None):
1108 1108 '''opens an arbitrary revision of the file without
1109 1109 opening a new filelog'''
1110 1110 return filectx(self._repo, self._path, fileid=fileid,
1111 1111 filelog=self._filelog, changeid=changeid)
1112 1112
1113 def rawdata(self):
1114 return self._filelog.revision(self._filenode, raw=True)
1115
1113 1116 def data(self):
1114 1117 try:
1115 1118 return self._filelog.read(self._filenode)
1116 1119 except error.CensoredNodeError:
1117 1120 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1118 1121 return ""
1119 1122 raise error.Abort(_("censored node: %s") % short(self._filenode),
1120 1123 hint=_("set censor.policy to ignore errors"))
1121 1124
1122 1125 def size(self):
1123 1126 return self._filelog.size(self._filerev)
1124 1127
1125 1128 def renamed(self):
1126 1129 """check if file was actually renamed in this changeset revision
1127 1130
1128 1131 If rename logged in file revision, we report copy for changeset only
1129 1132 if file revisions linkrev points back to the changeset in question
1130 1133 or both changeset parents contain different file revisions.
1131 1134 """
1132 1135
1133 1136 renamed = self._filelog.renamed(self._filenode)
1134 1137 if not renamed:
1135 1138 return renamed
1136 1139
1137 1140 if self.rev() == self.linkrev():
1138 1141 return renamed
1139 1142
1140 1143 name = self.path()
1141 1144 fnode = self._filenode
1142 1145 for p in self._changectx.parents():
1143 1146 try:
1144 1147 if fnode == p.filenode(name):
1145 1148 return None
1146 1149 except error.LookupError:
1147 1150 pass
1148 1151 return renamed
1149 1152
1150 1153 def children(self):
1151 1154 # hard for renames
1152 1155 c = self._filelog.children(self._filenode)
1153 1156 return [filectx(self._repo, self._path, fileid=x,
1154 1157 filelog=self._filelog) for x in c]
1155 1158
1156 1159 def blockancestors(fctx, fromline, toline):
1157 1160 """Yield ancestors of `fctx` with respect to the block of lines within
1158 1161 `fromline`-`toline` range.
1159 1162 """
1160 1163 def changesrange(fctx1, fctx2, linerange2):
1161 1164 """Return `(diffinrange, linerange1)` where `diffinrange` is True
1162 1165 if diff from fctx2 to fctx1 has changes in linerange2 and
1163 1166 `linerange1` is the new line range for fctx1.
1164 1167 """
1165 1168 diffopts = patch.diffopts(fctx._repo.ui)
1166 1169 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
1167 1170 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
1168 1171 diffinrange = any(stype == '!' for _, stype in filteredblocks)
1169 1172 return diffinrange, linerange1
1170 1173
1171 1174 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
1172 1175 while visit:
1173 1176 c, linerange2 = visit.pop(max(visit))
1174 1177 pl = c.parents()
1175 1178 if not pl:
1176 1179 # The block originates from the initial revision.
1177 1180 yield c
1178 1181 continue
1179 1182 inrange = False
1180 1183 for p in pl:
1181 1184 inrangep, linerange1 = changesrange(p, c, linerange2)
1182 1185 inrange = inrange or inrangep
1183 1186 if linerange1[0] == linerange1[1]:
1184 1187 # Parent's linerange is empty, meaning that the block got
1185 1188 # introduced in this revision; no need to go futher in this
1186 1189 # branch.
1187 1190 continue
1188 1191 visit[p.linkrev(), p.filenode()] = p, linerange1
1189 1192 if inrange:
1190 1193 yield c
1191 1194
1192 1195 class committablectx(basectx):
1193 1196 """A committablectx object provides common functionality for a context that
1194 1197 wants the ability to commit, e.g. workingctx or memctx."""
1195 1198 def __init__(self, repo, text="", user=None, date=None, extra=None,
1196 1199 changes=None):
1197 1200 self._repo = repo
1198 1201 self._rev = None
1199 1202 self._node = None
1200 1203 self._text = text
1201 1204 if date:
1202 1205 self._date = util.parsedate(date)
1203 1206 if user:
1204 1207 self._user = user
1205 1208 if changes:
1206 1209 self._status = changes
1207 1210
1208 1211 self._extra = {}
1209 1212 if extra:
1210 1213 self._extra = extra.copy()
1211 1214 if 'branch' not in self._extra:
1212 1215 try:
1213 1216 branch = encoding.fromlocal(self._repo.dirstate.branch())
1214 1217 except UnicodeDecodeError:
1215 1218 raise error.Abort(_('branch name not in UTF-8!'))
1216 1219 self._extra['branch'] = branch
1217 1220 if self._extra['branch'] == '':
1218 1221 self._extra['branch'] = 'default'
1219 1222
1220 1223 def __str__(self):
1221 1224 return str(self._parents[0]) + "+"
1222 1225
1223 1226 def __nonzero__(self):
1224 1227 return True
1225 1228
1226 1229 def _buildflagfunc(self):
1227 1230 # Create a fallback function for getting file flags when the
1228 1231 # filesystem doesn't support them
1229 1232
1230 1233 copiesget = self._repo.dirstate.copies().get
1231 1234 parents = self.parents()
1232 1235 if len(parents) < 2:
1233 1236 # when we have one parent, it's easy: copy from parent
1234 1237 man = parents[0].manifest()
1235 1238 def func(f):
1236 1239 f = copiesget(f, f)
1237 1240 return man.flags(f)
1238 1241 else:
1239 1242 # merges are tricky: we try to reconstruct the unstored
1240 1243 # result from the merge (issue1802)
1241 1244 p1, p2 = parents
1242 1245 pa = p1.ancestor(p2)
1243 1246 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1244 1247
1245 1248 def func(f):
1246 1249 f = copiesget(f, f) # may be wrong for merges with copies
1247 1250 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1248 1251 if fl1 == fl2:
1249 1252 return fl1
1250 1253 if fl1 == fla:
1251 1254 return fl2
1252 1255 if fl2 == fla:
1253 1256 return fl1
1254 1257 return '' # punt for conflicts
1255 1258
1256 1259 return func
1257 1260
1258 1261 @propertycache
1259 1262 def _flagfunc(self):
1260 1263 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1261 1264
1262 1265 @propertycache
1263 1266 def _manifest(self):
1264 1267 """generate a manifest corresponding to the values in self._status
1265 1268
1266 1269 This reuse the file nodeid from parent, but we append an extra letter
1267 1270 when modified. Modified files get an extra 'm' while added files get
1268 1271 an extra 'a'. This is used by manifests merge to see that files
1269 1272 are different and by update logic to avoid deleting newly added files.
1270 1273 """
1271 1274 parents = self.parents()
1272 1275
1273 1276 man = parents[0].manifest().copy()
1274 1277
1275 1278 ff = self._flagfunc
1276 1279 for i, l in ((addednodeid, self._status.added),
1277 1280 (modifiednodeid, self._status.modified)):
1278 1281 for f in l:
1279 1282 man[f] = i
1280 1283 try:
1281 1284 man.setflag(f, ff(f))
1282 1285 except OSError:
1283 1286 pass
1284 1287
1285 1288 for f in self._status.deleted + self._status.removed:
1286 1289 if f in man:
1287 1290 del man[f]
1288 1291
1289 1292 return man
1290 1293
1291 1294 @propertycache
1292 1295 def _status(self):
1293 1296 return self._repo.status()
1294 1297
1295 1298 @propertycache
1296 1299 def _user(self):
1297 1300 return self._repo.ui.username()
1298 1301
1299 1302 @propertycache
1300 1303 def _date(self):
1301 1304 return util.makedate()
1302 1305
1303 1306 def subrev(self, subpath):
1304 1307 return None
1305 1308
1306 1309 def manifestnode(self):
1307 1310 return None
1308 1311 def user(self):
1309 1312 return self._user or self._repo.ui.username()
1310 1313 def date(self):
1311 1314 return self._date
1312 1315 def description(self):
1313 1316 return self._text
1314 1317 def files(self):
1315 1318 return sorted(self._status.modified + self._status.added +
1316 1319 self._status.removed)
1317 1320
1318 1321 def modified(self):
1319 1322 return self._status.modified
1320 1323 def added(self):
1321 1324 return self._status.added
1322 1325 def removed(self):
1323 1326 return self._status.removed
1324 1327 def deleted(self):
1325 1328 return self._status.deleted
1326 1329 def branch(self):
1327 1330 return encoding.tolocal(self._extra['branch'])
1328 1331 def closesbranch(self):
1329 1332 return 'close' in self._extra
1330 1333 def extra(self):
1331 1334 return self._extra
1332 1335
1333 1336 def tags(self):
1334 1337 return []
1335 1338
1336 1339 def bookmarks(self):
1337 1340 b = []
1338 1341 for p in self.parents():
1339 1342 b.extend(p.bookmarks())
1340 1343 return b
1341 1344
1342 1345 def phase(self):
1343 1346 phase = phases.draft # default phase to draft
1344 1347 for p in self.parents():
1345 1348 phase = max(phase, p.phase())
1346 1349 return phase
1347 1350
1348 1351 def hidden(self):
1349 1352 return False
1350 1353
1351 1354 def children(self):
1352 1355 return []
1353 1356
1354 1357 def flags(self, path):
1355 1358 if '_manifest' in self.__dict__:
1356 1359 try:
1357 1360 return self._manifest.flags(path)
1358 1361 except KeyError:
1359 1362 return ''
1360 1363
1361 1364 try:
1362 1365 return self._flagfunc(path)
1363 1366 except OSError:
1364 1367 return ''
1365 1368
1366 1369 def ancestor(self, c2):
1367 1370 """return the "best" ancestor context of self and c2"""
1368 1371 return self._parents[0].ancestor(c2) # punt on two parents for now
1369 1372
1370 1373 def walk(self, match):
1371 1374 '''Generates matching file names.'''
1372 1375 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1373 1376 True, False))
1374 1377
1375 1378 def matches(self, match):
1376 1379 return sorted(self._repo.dirstate.matches(match))
1377 1380
1378 1381 def ancestors(self):
1379 1382 for p in self._parents:
1380 1383 yield p
1381 1384 for a in self._repo.changelog.ancestors(
1382 1385 [p.rev() for p in self._parents]):
1383 1386 yield changectx(self._repo, a)
1384 1387
1385 1388 def markcommitted(self, node):
1386 1389 """Perform post-commit cleanup necessary after committing this ctx
1387 1390
1388 1391 Specifically, this updates backing stores this working context
1389 1392 wraps to reflect the fact that the changes reflected by this
1390 1393 workingctx have been committed. For example, it marks
1391 1394 modified and added files as normal in the dirstate.
1392 1395
1393 1396 """
1394 1397
1395 1398 self._repo.dirstate.beginparentchange()
1396 1399 for f in self.modified() + self.added():
1397 1400 self._repo.dirstate.normal(f)
1398 1401 for f in self.removed():
1399 1402 self._repo.dirstate.drop(f)
1400 1403 self._repo.dirstate.setparents(node)
1401 1404 self._repo.dirstate.endparentchange()
1402 1405
1403 1406 # write changes out explicitly, because nesting wlock at
1404 1407 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1405 1408 # from immediately doing so for subsequent changing files
1406 1409 self._repo.dirstate.write(self._repo.currenttransaction())
1407 1410
1408 1411 class workingctx(committablectx):
1409 1412 """A workingctx object makes access to data related to
1410 1413 the current working directory convenient.
1411 1414 date - any valid date string or (unixtime, offset), or None.
1412 1415 user - username string, or None.
1413 1416 extra - a dictionary of extra values, or None.
1414 1417 changes - a list of file lists as returned by localrepo.status()
1415 1418 or None to use the repository status.
1416 1419 """
1417 1420 def __init__(self, repo, text="", user=None, date=None, extra=None,
1418 1421 changes=None):
1419 1422 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1420 1423
1421 1424 def __iter__(self):
1422 1425 d = self._repo.dirstate
1423 1426 for f in d:
1424 1427 if d[f] != 'r':
1425 1428 yield f
1426 1429
1427 1430 def __contains__(self, key):
1428 1431 return self._repo.dirstate[key] not in "?r"
1429 1432
1430 1433 def hex(self):
1431 1434 return hex(wdirid)
1432 1435
1433 1436 @propertycache
1434 1437 def _parents(self):
1435 1438 p = self._repo.dirstate.parents()
1436 1439 if p[1] == nullid:
1437 1440 p = p[:-1]
1438 1441 return [changectx(self._repo, x) for x in p]
1439 1442
1440 1443 def filectx(self, path, filelog=None):
1441 1444 """get a file context from the working directory"""
1442 1445 return workingfilectx(self._repo, path, workingctx=self,
1443 1446 filelog=filelog)
1444 1447
1445 1448 def dirty(self, missing=False, merge=True, branch=True):
1446 1449 "check whether a working directory is modified"
1447 1450 # check subrepos first
1448 1451 for s in sorted(self.substate):
1449 1452 if self.sub(s).dirty():
1450 1453 return True
1451 1454 # check current working dir
1452 1455 return ((merge and self.p2()) or
1453 1456 (branch and self.branch() != self.p1().branch()) or
1454 1457 self.modified() or self.added() or self.removed() or
1455 1458 (missing and self.deleted()))
1456 1459
1457 1460 def add(self, list, prefix=""):
1458 1461 join = lambda f: os.path.join(prefix, f)
1459 1462 with self._repo.wlock():
1460 1463 ui, ds = self._repo.ui, self._repo.dirstate
1461 1464 rejected = []
1462 1465 lstat = self._repo.wvfs.lstat
1463 1466 for f in list:
1464 1467 scmutil.checkportable(ui, join(f))
1465 1468 try:
1466 1469 st = lstat(f)
1467 1470 except OSError:
1468 1471 ui.warn(_("%s does not exist!\n") % join(f))
1469 1472 rejected.append(f)
1470 1473 continue
1471 1474 if st.st_size > 10000000:
1472 1475 ui.warn(_("%s: up to %d MB of RAM may be required "
1473 1476 "to manage this file\n"
1474 1477 "(use 'hg revert %s' to cancel the "
1475 1478 "pending addition)\n")
1476 1479 % (f, 3 * st.st_size // 1000000, join(f)))
1477 1480 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1478 1481 ui.warn(_("%s not added: only files and symlinks "
1479 1482 "supported currently\n") % join(f))
1480 1483 rejected.append(f)
1481 1484 elif ds[f] in 'amn':
1482 1485 ui.warn(_("%s already tracked!\n") % join(f))
1483 1486 elif ds[f] == 'r':
1484 1487 ds.normallookup(f)
1485 1488 else:
1486 1489 ds.add(f)
1487 1490 return rejected
1488 1491
1489 1492 def forget(self, files, prefix=""):
1490 1493 join = lambda f: os.path.join(prefix, f)
1491 1494 with self._repo.wlock():
1492 1495 rejected = []
1493 1496 for f in files:
1494 1497 if f not in self._repo.dirstate:
1495 1498 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1496 1499 rejected.append(f)
1497 1500 elif self._repo.dirstate[f] != 'a':
1498 1501 self._repo.dirstate.remove(f)
1499 1502 else:
1500 1503 self._repo.dirstate.drop(f)
1501 1504 return rejected
1502 1505
1503 1506 def undelete(self, list):
1504 1507 pctxs = self.parents()
1505 1508 with self._repo.wlock():
1506 1509 for f in list:
1507 1510 if self._repo.dirstate[f] != 'r':
1508 1511 self._repo.ui.warn(_("%s not removed!\n") % f)
1509 1512 else:
1510 1513 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1511 1514 t = fctx.data()
1512 1515 self._repo.wwrite(f, t, fctx.flags())
1513 1516 self._repo.dirstate.normal(f)
1514 1517
1515 1518 def copy(self, source, dest):
1516 1519 try:
1517 1520 st = self._repo.wvfs.lstat(dest)
1518 1521 except OSError as err:
1519 1522 if err.errno != errno.ENOENT:
1520 1523 raise
1521 1524 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1522 1525 return
1523 1526 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1524 1527 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1525 1528 "symbolic link\n") % dest)
1526 1529 else:
1527 1530 with self._repo.wlock():
1528 1531 if self._repo.dirstate[dest] in '?':
1529 1532 self._repo.dirstate.add(dest)
1530 1533 elif self._repo.dirstate[dest] in 'r':
1531 1534 self._repo.dirstate.normallookup(dest)
1532 1535 self._repo.dirstate.copy(source, dest)
1533 1536
1534 1537 def match(self, pats=[], include=None, exclude=None, default='glob',
1535 1538 listsubrepos=False, badfn=None):
1536 1539 r = self._repo
1537 1540
1538 1541 # Only a case insensitive filesystem needs magic to translate user input
1539 1542 # to actual case in the filesystem.
1540 1543 if not util.fscasesensitive(r.root):
1541 1544 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1542 1545 exclude, default, r.auditor, self,
1543 1546 listsubrepos=listsubrepos,
1544 1547 badfn=badfn)
1545 1548 return matchmod.match(r.root, r.getcwd(), pats,
1546 1549 include, exclude, default,
1547 1550 auditor=r.auditor, ctx=self,
1548 1551 listsubrepos=listsubrepos, badfn=badfn)
1549 1552
1550 1553 def _filtersuspectsymlink(self, files):
1551 1554 if not files or self._repo.dirstate._checklink:
1552 1555 return files
1553 1556
1554 1557 # Symlink placeholders may get non-symlink-like contents
1555 1558 # via user error or dereferencing by NFS or Samba servers,
1556 1559 # so we filter out any placeholders that don't look like a
1557 1560 # symlink
1558 1561 sane = []
1559 1562 for f in files:
1560 1563 if self.flags(f) == 'l':
1561 1564 d = self[f].data()
1562 1565 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1563 1566 self._repo.ui.debug('ignoring suspect symlink placeholder'
1564 1567 ' "%s"\n' % f)
1565 1568 continue
1566 1569 sane.append(f)
1567 1570 return sane
1568 1571
1569 1572 def _checklookup(self, files):
1570 1573 # check for any possibly clean files
1571 1574 if not files:
1572 1575 return [], []
1573 1576
1574 1577 modified = []
1575 1578 fixup = []
1576 1579 pctx = self._parents[0]
1577 1580 # do a full compare of any files that might have changed
1578 1581 for f in sorted(files):
1579 1582 if (f not in pctx or self.flags(f) != pctx.flags(f)
1580 1583 or pctx[f].cmp(self[f])):
1581 1584 modified.append(f)
1582 1585 else:
1583 1586 fixup.append(f)
1584 1587
1585 1588 # update dirstate for files that are actually clean
1586 1589 if fixup:
1587 1590 try:
1588 1591 # updating the dirstate is optional
1589 1592 # so we don't wait on the lock
1590 1593 # wlock can invalidate the dirstate, so cache normal _after_
1591 1594 # taking the lock
1592 1595 with self._repo.wlock(False):
1593 1596 normal = self._repo.dirstate.normal
1594 1597 for f in fixup:
1595 1598 normal(f)
1596 1599 # write changes out explicitly, because nesting
1597 1600 # wlock at runtime may prevent 'wlock.release()'
1598 1601 # after this block from doing so for subsequent
1599 1602 # changing files
1600 1603 self._repo.dirstate.write(self._repo.currenttransaction())
1601 1604 except error.LockError:
1602 1605 pass
1603 1606 return modified, fixup
1604 1607
1605 1608 def _manifestmatches(self, match, s):
1606 1609 """Slow path for workingctx
1607 1610
1608 1611 The fast path is when we compare the working directory to its parent
1609 1612 which means this function is comparing with a non-parent; therefore we
1610 1613 need to build a manifest and return what matches.
1611 1614 """
1612 1615 mf = self._repo['.']._manifestmatches(match, s)
1613 1616 for f in s.modified + s.added:
1614 1617 mf[f] = newnodeid
1615 1618 mf.setflag(f, self.flags(f))
1616 1619 for f in s.removed:
1617 1620 if f in mf:
1618 1621 del mf[f]
1619 1622 return mf
1620 1623
1621 1624 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1622 1625 unknown=False):
1623 1626 '''Gets the status from the dirstate -- internal use only.'''
1624 1627 listignored, listclean, listunknown = ignored, clean, unknown
1625 1628 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1626 1629 subrepos = []
1627 1630 if '.hgsub' in self:
1628 1631 subrepos = sorted(self.substate)
1629 1632 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1630 1633 listclean, listunknown)
1631 1634
1632 1635 # check for any possibly clean files
1633 1636 if cmp:
1634 1637 modified2, fixup = self._checklookup(cmp)
1635 1638 s.modified.extend(modified2)
1636 1639
1637 1640 # update dirstate for files that are actually clean
1638 1641 if fixup and listclean:
1639 1642 s.clean.extend(fixup)
1640 1643
1641 1644 if match.always():
1642 1645 # cache for performance
1643 1646 if s.unknown or s.ignored or s.clean:
1644 1647 # "_status" is cached with list*=False in the normal route
1645 1648 self._status = scmutil.status(s.modified, s.added, s.removed,
1646 1649 s.deleted, [], [], [])
1647 1650 else:
1648 1651 self._status = s
1649 1652
1650 1653 return s
1651 1654
1652 1655 def _buildstatus(self, other, s, match, listignored, listclean,
1653 1656 listunknown):
1654 1657 """build a status with respect to another context
1655 1658
1656 1659 This includes logic for maintaining the fast path of status when
1657 1660 comparing the working directory against its parent, which is to skip
1658 1661 building a new manifest if self (working directory) is not comparing
1659 1662 against its parent (repo['.']).
1660 1663 """
1661 1664 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1662 1665 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1663 1666 # might have accidentally ended up with the entire contents of the file
1664 1667 # they are supposed to be linking to.
1665 1668 s.modified[:] = self._filtersuspectsymlink(s.modified)
1666 1669 if other != self._repo['.']:
1667 1670 s = super(workingctx, self)._buildstatus(other, s, match,
1668 1671 listignored, listclean,
1669 1672 listunknown)
1670 1673 return s
1671 1674
1672 1675 def _matchstatus(self, other, match):
1673 1676 """override the match method with a filter for directory patterns
1674 1677
1675 1678 We use inheritance to customize the match.bad method only in cases of
1676 1679 workingctx since it belongs only to the working directory when
1677 1680 comparing against the parent changeset.
1678 1681
1679 1682 If we aren't comparing against the working directory's parent, then we
1680 1683 just use the default match object sent to us.
1681 1684 """
1682 1685 superself = super(workingctx, self)
1683 1686 match = superself._matchstatus(other, match)
1684 1687 if other != self._repo['.']:
1685 1688 def bad(f, msg):
1686 1689 # 'f' may be a directory pattern from 'match.files()',
1687 1690 # so 'f not in ctx1' is not enough
1688 1691 if f not in other and not other.hasdir(f):
1689 1692 self._repo.ui.warn('%s: %s\n' %
1690 1693 (self._repo.dirstate.pathto(f), msg))
1691 1694 match.bad = bad
1692 1695 return match
1693 1696
1694 1697 class committablefilectx(basefilectx):
1695 1698 """A committablefilectx provides common functionality for a file context
1696 1699 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1697 1700 def __init__(self, repo, path, filelog=None, ctx=None):
1698 1701 self._repo = repo
1699 1702 self._path = path
1700 1703 self._changeid = None
1701 1704 self._filerev = self._filenode = None
1702 1705
1703 1706 if filelog is not None:
1704 1707 self._filelog = filelog
1705 1708 if ctx:
1706 1709 self._changectx = ctx
1707 1710
1708 1711 def __nonzero__(self):
1709 1712 return True
1710 1713
1711 1714 def linkrev(self):
1712 1715 # linked to self._changectx no matter if file is modified or not
1713 1716 return self.rev()
1714 1717
1715 1718 def parents(self):
1716 1719 '''return parent filectxs, following copies if necessary'''
1717 1720 def filenode(ctx, path):
1718 1721 return ctx._manifest.get(path, nullid)
1719 1722
1720 1723 path = self._path
1721 1724 fl = self._filelog
1722 1725 pcl = self._changectx._parents
1723 1726 renamed = self.renamed()
1724 1727
1725 1728 if renamed:
1726 1729 pl = [renamed + (None,)]
1727 1730 else:
1728 1731 pl = [(path, filenode(pcl[0], path), fl)]
1729 1732
1730 1733 for pc in pcl[1:]:
1731 1734 pl.append((path, filenode(pc, path), fl))
1732 1735
1733 1736 return [self._parentfilectx(p, fileid=n, filelog=l)
1734 1737 for p, n, l in pl if n != nullid]
1735 1738
1736 1739 def children(self):
1737 1740 return []
1738 1741
1739 1742 class workingfilectx(committablefilectx):
1740 1743 """A workingfilectx object makes access to data related to a particular
1741 1744 file in the working directory convenient."""
1742 1745 def __init__(self, repo, path, filelog=None, workingctx=None):
1743 1746 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1744 1747
1745 1748 @propertycache
1746 1749 def _changectx(self):
1747 1750 return workingctx(self._repo)
1748 1751
1749 1752 def data(self):
1750 1753 return self._repo.wread(self._path)
1751 1754 def renamed(self):
1752 1755 rp = self._repo.dirstate.copied(self._path)
1753 1756 if not rp:
1754 1757 return None
1755 1758 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1756 1759
1757 1760 def size(self):
1758 1761 return self._repo.wvfs.lstat(self._path).st_size
1759 1762 def date(self):
1760 1763 t, tz = self._changectx.date()
1761 1764 try:
1762 1765 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1763 1766 except OSError as err:
1764 1767 if err.errno != errno.ENOENT:
1765 1768 raise
1766 1769 return (t, tz)
1767 1770
1768 1771 def cmp(self, fctx):
1769 1772 """compare with other file context
1770 1773
1771 1774 returns True if different than fctx.
1772 1775 """
1773 1776 # fctx should be a filectx (not a workingfilectx)
1774 1777 # invert comparison to reuse the same code path
1775 1778 return fctx.cmp(self)
1776 1779
1777 1780 def remove(self, ignoremissing=False):
1778 1781 """wraps unlink for a repo's working directory"""
1779 1782 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1780 1783
1781 1784 def write(self, data, flags):
1782 1785 """wraps repo.wwrite"""
1783 1786 self._repo.wwrite(self._path, data, flags)
1784 1787
1785 1788 class workingcommitctx(workingctx):
1786 1789 """A workingcommitctx object makes access to data related to
1787 1790 the revision being committed convenient.
1788 1791
1789 1792 This hides changes in the working directory, if they aren't
1790 1793 committed in this context.
1791 1794 """
1792 1795 def __init__(self, repo, changes,
1793 1796 text="", user=None, date=None, extra=None):
1794 1797 super(workingctx, self).__init__(repo, text, user, date, extra,
1795 1798 changes)
1796 1799
1797 1800 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1798 1801 unknown=False):
1799 1802 """Return matched files only in ``self._status``
1800 1803
1801 1804 Uncommitted files appear "clean" via this context, even if
1802 1805 they aren't actually so in the working directory.
1803 1806 """
1804 1807 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1805 1808 if clean:
1806 1809 clean = [f for f in self._manifest if f not in self._changedset]
1807 1810 else:
1808 1811 clean = []
1809 1812 return scmutil.status([f for f in self._status.modified if match(f)],
1810 1813 [f for f in self._status.added if match(f)],
1811 1814 [f for f in self._status.removed if match(f)],
1812 1815 [], [], [], clean)
1813 1816
1814 1817 @propertycache
1815 1818 def _changedset(self):
1816 1819 """Return the set of files changed in this context
1817 1820 """
1818 1821 changed = set(self._status.modified)
1819 1822 changed.update(self._status.added)
1820 1823 changed.update(self._status.removed)
1821 1824 return changed
1822 1825
1823 1826 def makecachingfilectxfn(func):
1824 1827 """Create a filectxfn that caches based on the path.
1825 1828
1826 1829 We can't use util.cachefunc because it uses all arguments as the cache
1827 1830 key and this creates a cycle since the arguments include the repo and
1828 1831 memctx.
1829 1832 """
1830 1833 cache = {}
1831 1834
1832 1835 def getfilectx(repo, memctx, path):
1833 1836 if path not in cache:
1834 1837 cache[path] = func(repo, memctx, path)
1835 1838 return cache[path]
1836 1839
1837 1840 return getfilectx
1838 1841
1839 1842 class memctx(committablectx):
1840 1843 """Use memctx to perform in-memory commits via localrepo.commitctx().
1841 1844
1842 1845 Revision information is supplied at initialization time while
1843 1846 related files data and is made available through a callback
1844 1847 mechanism. 'repo' is the current localrepo, 'parents' is a
1845 1848 sequence of two parent revisions identifiers (pass None for every
1846 1849 missing parent), 'text' is the commit message and 'files' lists
1847 1850 names of files touched by the revision (normalized and relative to
1848 1851 repository root).
1849 1852
1850 1853 filectxfn(repo, memctx, path) is a callable receiving the
1851 1854 repository, the current memctx object and the normalized path of
1852 1855 requested file, relative to repository root. It is fired by the
1853 1856 commit function for every file in 'files', but calls order is
1854 1857 undefined. If the file is available in the revision being
1855 1858 committed (updated or added), filectxfn returns a memfilectx
1856 1859 object. If the file was removed, filectxfn raises an
1857 1860 IOError. Moved files are represented by marking the source file
1858 1861 removed and the new file added with copy information (see
1859 1862 memfilectx).
1860 1863
1861 1864 user receives the committer name and defaults to current
1862 1865 repository username, date is the commit date in any format
1863 1866 supported by util.parsedate() and defaults to current date, extra
1864 1867 is a dictionary of metadata or is left empty.
1865 1868 """
1866 1869
1867 1870 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1868 1871 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1869 1872 # this field to determine what to do in filectxfn.
1870 1873 _returnnoneformissingfiles = True
1871 1874
1872 1875 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1873 1876 date=None, extra=None, editor=False):
1874 1877 super(memctx, self).__init__(repo, text, user, date, extra)
1875 1878 self._rev = None
1876 1879 self._node = None
1877 1880 parents = [(p or nullid) for p in parents]
1878 1881 p1, p2 = parents
1879 1882 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1880 1883 files = sorted(set(files))
1881 1884 self._files = files
1882 1885 self.substate = {}
1883 1886
1884 1887 # if store is not callable, wrap it in a function
1885 1888 if not callable(filectxfn):
1886 1889 def getfilectx(repo, memctx, path):
1887 1890 fctx = filectxfn[path]
1888 1891 # this is weird but apparently we only keep track of one parent
1889 1892 # (why not only store that instead of a tuple?)
1890 1893 copied = fctx.renamed()
1891 1894 if copied:
1892 1895 copied = copied[0]
1893 1896 return memfilectx(repo, path, fctx.data(),
1894 1897 islink=fctx.islink(), isexec=fctx.isexec(),
1895 1898 copied=copied, memctx=memctx)
1896 1899 self._filectxfn = getfilectx
1897 1900 else:
1898 1901 # memoizing increases performance for e.g. vcs convert scenarios.
1899 1902 self._filectxfn = makecachingfilectxfn(filectxfn)
1900 1903
1901 1904 if extra:
1902 1905 self._extra = extra.copy()
1903 1906 else:
1904 1907 self._extra = {}
1905 1908
1906 1909 if self._extra.get('branch', '') == '':
1907 1910 self._extra['branch'] = 'default'
1908 1911
1909 1912 if editor:
1910 1913 self._text = editor(self._repo, self, [])
1911 1914 self._repo.savecommitmessage(self._text)
1912 1915
1913 1916 def filectx(self, path, filelog=None):
1914 1917 """get a file context from the working directory
1915 1918
1916 1919 Returns None if file doesn't exist and should be removed."""
1917 1920 return self._filectxfn(self._repo, self, path)
1918 1921
1919 1922 def commit(self):
1920 1923 """commit context to the repo"""
1921 1924 return self._repo.commitctx(self)
1922 1925
1923 1926 @propertycache
1924 1927 def _manifest(self):
1925 1928 """generate a manifest based on the return values of filectxfn"""
1926 1929
1927 1930 # keep this simple for now; just worry about p1
1928 1931 pctx = self._parents[0]
1929 1932 man = pctx.manifest().copy()
1930 1933
1931 1934 for f in self._status.modified:
1932 1935 p1node = nullid
1933 1936 p2node = nullid
1934 1937 p = pctx[f].parents() # if file isn't in pctx, check p2?
1935 1938 if len(p) > 0:
1936 1939 p1node = p[0].filenode()
1937 1940 if len(p) > 1:
1938 1941 p2node = p[1].filenode()
1939 1942 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1940 1943
1941 1944 for f in self._status.added:
1942 1945 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1943 1946
1944 1947 for f in self._status.removed:
1945 1948 if f in man:
1946 1949 del man[f]
1947 1950
1948 1951 return man
1949 1952
1950 1953 @propertycache
1951 1954 def _status(self):
1952 1955 """Calculate exact status from ``files`` specified at construction
1953 1956 """
1954 1957 man1 = self.p1().manifest()
1955 1958 p2 = self._parents[1]
1956 1959 # "1 < len(self._parents)" can't be used for checking
1957 1960 # existence of the 2nd parent, because "memctx._parents" is
1958 1961 # explicitly initialized by the list, of which length is 2.
1959 1962 if p2.node() != nullid:
1960 1963 man2 = p2.manifest()
1961 1964 managing = lambda f: f in man1 or f in man2
1962 1965 else:
1963 1966 managing = lambda f: f in man1
1964 1967
1965 1968 modified, added, removed = [], [], []
1966 1969 for f in self._files:
1967 1970 if not managing(f):
1968 1971 added.append(f)
1969 1972 elif self[f]:
1970 1973 modified.append(f)
1971 1974 else:
1972 1975 removed.append(f)
1973 1976
1974 1977 return scmutil.status(modified, added, removed, [], [], [], [])
1975 1978
1976 1979 class memfilectx(committablefilectx):
1977 1980 """memfilectx represents an in-memory file to commit.
1978 1981
1979 1982 See memctx and committablefilectx for more details.
1980 1983 """
1981 1984 def __init__(self, repo, path, data, islink=False,
1982 1985 isexec=False, copied=None, memctx=None):
1983 1986 """
1984 1987 path is the normalized file path relative to repository root.
1985 1988 data is the file content as a string.
1986 1989 islink is True if the file is a symbolic link.
1987 1990 isexec is True if the file is executable.
1988 1991 copied is the source file path if current file was copied in the
1989 1992 revision being committed, or None."""
1990 1993 super(memfilectx, self).__init__(repo, path, None, memctx)
1991 1994 self._data = data
1992 1995 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1993 1996 self._copied = None
1994 1997 if copied:
1995 1998 self._copied = (copied, nullid)
1996 1999
1997 2000 def data(self):
1998 2001 return self._data
1999 2002 def size(self):
2000 2003 return len(self.data())
2001 2004 def flags(self):
2002 2005 return self._flags
2003 2006 def renamed(self):
2004 2007 return self._copied
2005 2008
2006 2009 def remove(self, ignoremissing=False):
2007 2010 """wraps unlink for a repo's working directory"""
2008 2011 # need to figure out what to do here
2009 2012 del self._changectx[self._path]
2010 2013
2011 2014 def write(self, data, flags):
2012 2015 """wraps repo.wwrite"""
2013 2016 self._data = data
2014 2017
2015 2018 class metadataonlyctx(committablectx):
2016 2019 """Like memctx but it's reusing the manifest of different commit.
2017 2020 Intended to be used by lightweight operations that are creating
2018 2021 metadata-only changes.
2019 2022
2020 2023 Revision information is supplied at initialization time. 'repo' is the
2021 2024 current localrepo, 'ctx' is original revision which manifest we're reuisng
2022 2025 'parents' is a sequence of two parent revisions identifiers (pass None for
2023 2026 every missing parent), 'text' is the commit.
2024 2027
2025 2028 user receives the committer name and defaults to current repository
2026 2029 username, date is the commit date in any format supported by
2027 2030 util.parsedate() and defaults to current date, extra is a dictionary of
2028 2031 metadata or is left empty.
2029 2032 """
2030 2033 def __new__(cls, repo, originalctx, *args, **kwargs):
2031 2034 return super(metadataonlyctx, cls).__new__(cls, repo)
2032 2035
2033 2036 def __init__(self, repo, originalctx, parents, text, user=None, date=None,
2034 2037 extra=None, editor=False):
2035 2038 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2036 2039 self._rev = None
2037 2040 self._node = None
2038 2041 self._originalctx = originalctx
2039 2042 self._manifestnode = originalctx.manifestnode()
2040 2043 parents = [(p or nullid) for p in parents]
2041 2044 p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
2042 2045
2043 2046 # sanity check to ensure that the reused manifest parents are
2044 2047 # manifests of our commit parents
2045 2048 mp1, mp2 = self.manifestctx().parents
2046 2049 if p1 != nullid and p1.manifestctx().node() != mp1:
2047 2050 raise RuntimeError('can\'t reuse the manifest: '
2048 2051 'its p1 doesn\'t match the new ctx p1')
2049 2052 if p2 != nullid and p2.manifestctx().node() != mp2:
2050 2053 raise RuntimeError('can\'t reuse the manifest: '
2051 2054 'its p2 doesn\'t match the new ctx p2')
2052 2055
2053 2056 self._files = originalctx.files()
2054 2057 self.substate = {}
2055 2058
2056 2059 if extra:
2057 2060 self._extra = extra.copy()
2058 2061 else:
2059 2062 self._extra = {}
2060 2063
2061 2064 if self._extra.get('branch', '') == '':
2062 2065 self._extra['branch'] = 'default'
2063 2066
2064 2067 if editor:
2065 2068 self._text = editor(self._repo, self, [])
2066 2069 self._repo.savecommitmessage(self._text)
2067 2070
2068 2071 def manifestnode(self):
2069 2072 return self._manifestnode
2070 2073
2071 2074 @propertycache
2072 2075 def _manifestctx(self):
2073 2076 return self._repo.manifestlog[self._manifestnode]
2074 2077
2075 2078 def filectx(self, path, filelog=None):
2076 2079 return self._originalctx.filectx(path, filelog=filelog)
2077 2080
2078 2081 def commit(self):
2079 2082 """commit context to the repo"""
2080 2083 return self._repo.commitctx(self)
2081 2084
2082 2085 @property
2083 2086 def _manifest(self):
2084 2087 return self._originalctx.manifest()
2085 2088
2086 2089 @propertycache
2087 2090 def _status(self):
2088 2091 """Calculate exact status from ``files`` specified in the ``origctx``
2089 2092 and parents manifests.
2090 2093 """
2091 2094 man1 = self.p1().manifest()
2092 2095 p2 = self._parents[1]
2093 2096 # "1 < len(self._parents)" can't be used for checking
2094 2097 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2095 2098 # explicitly initialized by the list, of which length is 2.
2096 2099 if p2.node() != nullid:
2097 2100 man2 = p2.manifest()
2098 2101 managing = lambda f: f in man1 or f in man2
2099 2102 else:
2100 2103 managing = lambda f: f in man1
2101 2104
2102 2105 modified, added, removed = [], [], []
2103 2106 for f in self._files:
2104 2107 if not managing(f):
2105 2108 added.append(f)
2106 2109 elif self[f]:
2107 2110 modified.append(f)
2108 2111 else:
2109 2112 removed.append(f)
2110 2113
2111 2114 return scmutil.status(modified, added, removed, [], [], [], [])
@@ -1,851 +1,851 b''
1 1 # debugcommands.py - command processing for debug* commands
2 2 #
3 3 # Copyright 2005-2016 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 operator
11 11 import os
12 12 import random
13 13
14 14 from .i18n import _
15 15 from .node import (
16 16 bin,
17 17 hex,
18 18 nullid,
19 19 short,
20 20 )
21 21 from . import (
22 22 bundle2,
23 23 changegroup,
24 24 cmdutil,
25 25 commands,
26 26 context,
27 27 dagparser,
28 28 dagutil,
29 29 error,
30 30 exchange,
31 31 extensions,
32 32 fileset,
33 33 hg,
34 34 localrepo,
35 35 lock as lockmod,
36 36 pycompat,
37 37 revlog,
38 38 scmutil,
39 39 setdiscovery,
40 40 simplemerge,
41 41 streamclone,
42 42 treediscovery,
43 43 util,
44 44 )
45 45
46 46 release = lockmod.release
47 47
48 48 # We reuse the command table from commands because it is easier than
49 49 # teaching dispatch about multiple tables.
50 50 command = cmdutil.command(commands.table)
51 51
52 52 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
53 53 def debugancestor(ui, repo, *args):
54 54 """find the ancestor revision of two revisions in a given index"""
55 55 if len(args) == 3:
56 56 index, rev1, rev2 = args
57 57 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False), index)
58 58 lookup = r.lookup
59 59 elif len(args) == 2:
60 60 if not repo:
61 61 raise error.Abort(_('there is no Mercurial repository here '
62 62 '(.hg not found)'))
63 63 rev1, rev2 = args
64 64 r = repo.changelog
65 65 lookup = repo.lookup
66 66 else:
67 67 raise error.Abort(_('either two or three arguments required'))
68 68 a = r.ancestor(lookup(rev1), lookup(rev2))
69 69 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
70 70
71 71 @command('debugapplystreamclonebundle', [], 'FILE')
72 72 def debugapplystreamclonebundle(ui, repo, fname):
73 73 """apply a stream clone bundle file"""
74 74 f = hg.openpath(ui, fname)
75 75 gen = exchange.readbundle(ui, f, fname)
76 76 gen.apply(repo)
77 77
78 78 @command('debugbuilddag',
79 79 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
80 80 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
81 81 ('n', 'new-file', None, _('add new file at each rev'))],
82 82 _('[OPTION]... [TEXT]'))
83 83 def debugbuilddag(ui, repo, text=None,
84 84 mergeable_file=False,
85 85 overwritten_file=False,
86 86 new_file=False):
87 87 """builds a repo with a given DAG from scratch in the current empty repo
88 88
89 89 The description of the DAG is read from stdin if not given on the
90 90 command line.
91 91
92 92 Elements:
93 93
94 94 - "+n" is a linear run of n nodes based on the current default parent
95 95 - "." is a single node based on the current default parent
96 96 - "$" resets the default parent to null (implied at the start);
97 97 otherwise the default parent is always the last node created
98 98 - "<p" sets the default parent to the backref p
99 99 - "*p" is a fork at parent p, which is a backref
100 100 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
101 101 - "/p2" is a merge of the preceding node and p2
102 102 - ":tag" defines a local tag for the preceding node
103 103 - "@branch" sets the named branch for subsequent nodes
104 104 - "#...\\n" is a comment up to the end of the line
105 105
106 106 Whitespace between the above elements is ignored.
107 107
108 108 A backref is either
109 109
110 110 - a number n, which references the node curr-n, where curr is the current
111 111 node, or
112 112 - the name of a local tag you placed earlier using ":tag", or
113 113 - empty to denote the default parent.
114 114
115 115 All string valued-elements are either strictly alphanumeric, or must
116 116 be enclosed in double quotes ("..."), with "\\" as escape character.
117 117 """
118 118
119 119 if text is None:
120 120 ui.status(_("reading DAG from stdin\n"))
121 121 text = ui.fin.read()
122 122
123 123 cl = repo.changelog
124 124 if len(cl) > 0:
125 125 raise error.Abort(_('repository is not empty'))
126 126
127 127 # determine number of revs in DAG
128 128 total = 0
129 129 for type, data in dagparser.parsedag(text):
130 130 if type == 'n':
131 131 total += 1
132 132
133 133 if mergeable_file:
134 134 linesperrev = 2
135 135 # make a file with k lines per rev
136 136 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
137 137 initialmergedlines.append("")
138 138
139 139 tags = []
140 140
141 141 wlock = lock = tr = None
142 142 try:
143 143 wlock = repo.wlock()
144 144 lock = repo.lock()
145 145 tr = repo.transaction("builddag")
146 146
147 147 at = -1
148 148 atbranch = 'default'
149 149 nodeids = []
150 150 id = 0
151 151 ui.progress(_('building'), id, unit=_('revisions'), total=total)
152 152 for type, data in dagparser.parsedag(text):
153 153 if type == 'n':
154 154 ui.note(('node %s\n' % str(data)))
155 155 id, ps = data
156 156
157 157 files = []
158 158 fctxs = {}
159 159
160 160 p2 = None
161 161 if mergeable_file:
162 162 fn = "mf"
163 163 p1 = repo[ps[0]]
164 164 if len(ps) > 1:
165 165 p2 = repo[ps[1]]
166 166 pa = p1.ancestor(p2)
167 167 base, local, other = [x[fn].data() for x in (pa, p1,
168 168 p2)]
169 169 m3 = simplemerge.Merge3Text(base, local, other)
170 170 ml = [l.strip() for l in m3.merge_lines()]
171 171 ml.append("")
172 172 elif at > 0:
173 173 ml = p1[fn].data().split("\n")
174 174 else:
175 175 ml = initialmergedlines
176 176 ml[id * linesperrev] += " r%i" % id
177 177 mergedtext = "\n".join(ml)
178 178 files.append(fn)
179 179 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
180 180
181 181 if overwritten_file:
182 182 fn = "of"
183 183 files.append(fn)
184 184 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
185 185
186 186 if new_file:
187 187 fn = "nf%i" % id
188 188 files.append(fn)
189 189 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
190 190 if len(ps) > 1:
191 191 if not p2:
192 192 p2 = repo[ps[1]]
193 193 for fn in p2:
194 194 if fn.startswith("nf"):
195 195 files.append(fn)
196 196 fctxs[fn] = p2[fn]
197 197
198 198 def fctxfn(repo, cx, path):
199 199 return fctxs.get(path)
200 200
201 201 if len(ps) == 0 or ps[0] < 0:
202 202 pars = [None, None]
203 203 elif len(ps) == 1:
204 204 pars = [nodeids[ps[0]], None]
205 205 else:
206 206 pars = [nodeids[p] for p in ps]
207 207 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
208 208 date=(id, 0),
209 209 user="debugbuilddag",
210 210 extra={'branch': atbranch})
211 211 nodeid = repo.commitctx(cx)
212 212 nodeids.append(nodeid)
213 213 at = id
214 214 elif type == 'l':
215 215 id, name = data
216 216 ui.note(('tag %s\n' % name))
217 217 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
218 218 elif type == 'a':
219 219 ui.note(('branch %s\n' % data))
220 220 atbranch = data
221 221 ui.progress(_('building'), id, unit=_('revisions'), total=total)
222 222 tr.close()
223 223
224 224 if tags:
225 225 repo.vfs.write("localtags", "".join(tags))
226 226 finally:
227 227 ui.progress(_('building'), None)
228 228 release(tr, lock, wlock)
229 229
230 230 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
231 231 indent_string = ' ' * indent
232 232 if all:
233 233 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
234 234 % indent_string)
235 235
236 236 def showchunks(named):
237 237 ui.write("\n%s%s\n" % (indent_string, named))
238 238 chain = None
239 239 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
240 240 node = chunkdata['node']
241 241 p1 = chunkdata['p1']
242 242 p2 = chunkdata['p2']
243 243 cs = chunkdata['cs']
244 244 deltabase = chunkdata['deltabase']
245 245 delta = chunkdata['delta']
246 246 ui.write("%s%s %s %s %s %s %s\n" %
247 247 (indent_string, hex(node), hex(p1), hex(p2),
248 248 hex(cs), hex(deltabase), len(delta)))
249 249 chain = node
250 250
251 251 chunkdata = gen.changelogheader()
252 252 showchunks("changelog")
253 253 chunkdata = gen.manifestheader()
254 254 showchunks("manifest")
255 255 for chunkdata in iter(gen.filelogheader, {}):
256 256 fname = chunkdata['filename']
257 257 showchunks(fname)
258 258 else:
259 259 if isinstance(gen, bundle2.unbundle20):
260 260 raise error.Abort(_('use debugbundle2 for this file'))
261 261 chunkdata = gen.changelogheader()
262 262 chain = None
263 263 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
264 264 node = chunkdata['node']
265 265 ui.write("%s%s\n" % (indent_string, hex(node)))
266 266 chain = node
267 267
268 268 def _debugbundle2(ui, gen, all=None, **opts):
269 269 """lists the contents of a bundle2"""
270 270 if not isinstance(gen, bundle2.unbundle20):
271 271 raise error.Abort(_('not a bundle2 file'))
272 272 ui.write(('Stream params: %s\n' % repr(gen.params)))
273 273 for part in gen.iterparts():
274 274 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
275 275 if part.type == 'changegroup':
276 276 version = part.params.get('version', '01')
277 277 cg = changegroup.getunbundler(version, part, 'UN')
278 278 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
279 279
280 280 @command('debugbundle',
281 281 [('a', 'all', None, _('show all details')),
282 282 ('', 'spec', None, _('print the bundlespec of the bundle'))],
283 283 _('FILE'),
284 284 norepo=True)
285 285 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
286 286 """lists the contents of a bundle"""
287 287 with hg.openpath(ui, bundlepath) as f:
288 288 if spec:
289 289 spec = exchange.getbundlespec(ui, f)
290 290 ui.write('%s\n' % spec)
291 291 return
292 292
293 293 gen = exchange.readbundle(ui, f, bundlepath)
294 294 if isinstance(gen, bundle2.unbundle20):
295 295 return _debugbundle2(ui, gen, all=all, **opts)
296 296 _debugchangegroup(ui, gen, all=all, **opts)
297 297
298 298 @command('debugcheckstate', [], '')
299 299 def debugcheckstate(ui, repo):
300 300 """validate the correctness of the current dirstate"""
301 301 parent1, parent2 = repo.dirstate.parents()
302 302 m1 = repo[parent1].manifest()
303 303 m2 = repo[parent2].manifest()
304 304 errors = 0
305 305 for f in repo.dirstate:
306 306 state = repo.dirstate[f]
307 307 if state in "nr" and f not in m1:
308 308 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
309 309 errors += 1
310 310 if state in "a" and f in m1:
311 311 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
312 312 errors += 1
313 313 if state in "m" and f not in m1 and f not in m2:
314 314 ui.warn(_("%s in state %s, but not in either manifest\n") %
315 315 (f, state))
316 316 errors += 1
317 317 for f in m1:
318 318 state = repo.dirstate[f]
319 319 if state not in "nrm":
320 320 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
321 321 errors += 1
322 322 if errors:
323 323 error = _(".hg/dirstate inconsistent with current parent's manifest")
324 324 raise error.Abort(error)
325 325
326 326 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
327 327 def debugcommands(ui, cmd='', *args):
328 328 """list all available commands and options"""
329 329 for cmd, vals in sorted(commands.table.iteritems()):
330 330 cmd = cmd.split('|')[0].strip('^')
331 331 opts = ', '.join([i[1] for i in vals[1]])
332 332 ui.write('%s: %s\n' % (cmd, opts))
333 333
334 334 @command('debugcomplete',
335 335 [('o', 'options', None, _('show the command options'))],
336 336 _('[-o] CMD'),
337 337 norepo=True)
338 338 def debugcomplete(ui, cmd='', **opts):
339 339 """returns the completion list associated with the given command"""
340 340
341 341 if opts.get('options'):
342 342 options = []
343 343 otables = [commands.globalopts]
344 344 if cmd:
345 345 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
346 346 otables.append(entry[1])
347 347 for t in otables:
348 348 for o in t:
349 349 if "(DEPRECATED)" in o[3]:
350 350 continue
351 351 if o[0]:
352 352 options.append('-%s' % o[0])
353 353 options.append('--%s' % o[1])
354 354 ui.write("%s\n" % "\n".join(options))
355 355 return
356 356
357 357 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, commands.table)
358 358 if ui.verbose:
359 359 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
360 360 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
361 361
362 362 @command('debugcreatestreamclonebundle', [], 'FILE')
363 363 def debugcreatestreamclonebundle(ui, repo, fname):
364 364 """create a stream clone bundle file
365 365
366 366 Stream bundles are special bundles that are essentially archives of
367 367 revlog files. They are commonly used for cloning very quickly.
368 368 """
369 369 requirements, gen = streamclone.generatebundlev1(repo)
370 370 changegroup.writechunks(ui, gen, fname)
371 371
372 372 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
373 373
374 374 @command('debugdag',
375 375 [('t', 'tags', None, _('use tags as labels')),
376 376 ('b', 'branches', None, _('annotate with branch names')),
377 377 ('', 'dots', None, _('use dots for runs')),
378 378 ('s', 'spaces', None, _('separate elements by spaces'))],
379 379 _('[OPTION]... [FILE [REV]...]'),
380 380 optionalrepo=True)
381 381 def debugdag(ui, repo, file_=None, *revs, **opts):
382 382 """format the changelog or an index DAG as a concise textual description
383 383
384 384 If you pass a revlog index, the revlog's DAG is emitted. If you list
385 385 revision numbers, they get labeled in the output as rN.
386 386
387 387 Otherwise, the changelog DAG of the current repo is emitted.
388 388 """
389 389 spaces = opts.get('spaces')
390 390 dots = opts.get('dots')
391 391 if file_:
392 392 rlog = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
393 393 file_)
394 394 revs = set((int(r) for r in revs))
395 395 def events():
396 396 for r in rlog:
397 397 yield 'n', (r, list(p for p in rlog.parentrevs(r)
398 398 if p != -1))
399 399 if r in revs:
400 400 yield 'l', (r, "r%i" % r)
401 401 elif repo:
402 402 cl = repo.changelog
403 403 tags = opts.get('tags')
404 404 branches = opts.get('branches')
405 405 if tags:
406 406 labels = {}
407 407 for l, n in repo.tags().items():
408 408 labels.setdefault(cl.rev(n), []).append(l)
409 409 def events():
410 410 b = "default"
411 411 for r in cl:
412 412 if branches:
413 413 newb = cl.read(cl.node(r))[5]['branch']
414 414 if newb != b:
415 415 yield 'a', newb
416 416 b = newb
417 417 yield 'n', (r, list(p for p in cl.parentrevs(r)
418 418 if p != -1))
419 419 if tags:
420 420 ls = labels.get(r)
421 421 if ls:
422 422 for l in ls:
423 423 yield 'l', (r, l)
424 424 else:
425 425 raise error.Abort(_('need repo for changelog dag'))
426 426
427 427 for line in dagparser.dagtextlines(events(),
428 428 addspaces=spaces,
429 429 wraplabels=True,
430 430 wrapannotations=True,
431 431 wrapnonlinear=dots,
432 432 usedots=dots,
433 433 maxlinewidth=70):
434 434 ui.write(line)
435 435 ui.write("\n")
436 436
437 437 @command('debugdata', commands.debugrevlogopts, _('-c|-m|FILE REV'))
438 438 def debugdata(ui, repo, file_, rev=None, **opts):
439 439 """dump the contents of a data file revision"""
440 440 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
441 441 if rev is not None:
442 442 raise error.CommandError('debugdata', _('invalid arguments'))
443 443 file_, rev = None, file_
444 444 elif rev is None:
445 445 raise error.CommandError('debugdata', _('invalid arguments'))
446 446 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
447 447 try:
448 ui.write(r.revision(r.lookup(rev)))
448 ui.write(r.revision(r.lookup(rev), raw=True))
449 449 except KeyError:
450 450 raise error.Abort(_('invalid revision identifier %s') % rev)
451 451
452 452 @command('debugdate',
453 453 [('e', 'extended', None, _('try extended date formats'))],
454 454 _('[-e] DATE [RANGE]'),
455 455 norepo=True, optionalrepo=True)
456 456 def debugdate(ui, date, range=None, **opts):
457 457 """parse and display a date"""
458 458 if opts["extended"]:
459 459 d = util.parsedate(date, util.extendeddateformats)
460 460 else:
461 461 d = util.parsedate(date)
462 462 ui.write(("internal: %s %s\n") % d)
463 463 ui.write(("standard: %s\n") % util.datestr(d))
464 464 if range:
465 465 m = util.matchdate(range)
466 466 ui.write(("match: %s\n") % m(d[0]))
467 467
468 468 @command('debugdeltachain',
469 469 commands.debugrevlogopts + commands.formatteropts,
470 470 _('-c|-m|FILE'),
471 471 optionalrepo=True)
472 472 def debugdeltachain(ui, repo, file_=None, **opts):
473 473 """dump information about delta chains in a revlog
474 474
475 475 Output can be templatized. Available template keywords are:
476 476
477 477 :``rev``: revision number
478 478 :``chainid``: delta chain identifier (numbered by unique base)
479 479 :``chainlen``: delta chain length to this revision
480 480 :``prevrev``: previous revision in delta chain
481 481 :``deltatype``: role of delta / how it was computed
482 482 :``compsize``: compressed size of revision
483 483 :``uncompsize``: uncompressed size of revision
484 484 :``chainsize``: total size of compressed revisions in chain
485 485 :``chainratio``: total chain size divided by uncompressed revision size
486 486 (new delta chains typically start at ratio 2.00)
487 487 :``lindist``: linear distance from base revision in delta chain to end
488 488 of this revision
489 489 :``extradist``: total size of revisions not part of this delta chain from
490 490 base of delta chain to end of this revision; a measurement
491 491 of how much extra data we need to read/seek across to read
492 492 the delta chain for this revision
493 493 :``extraratio``: extradist divided by chainsize; another representation of
494 494 how much unrelated data is needed to load this delta chain
495 495 """
496 496 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
497 497 index = r.index
498 498 generaldelta = r.version & revlog.REVLOGGENERALDELTA
499 499
500 500 def revinfo(rev):
501 501 e = index[rev]
502 502 compsize = e[1]
503 503 uncompsize = e[2]
504 504 chainsize = 0
505 505
506 506 if generaldelta:
507 507 if e[3] == e[5]:
508 508 deltatype = 'p1'
509 509 elif e[3] == e[6]:
510 510 deltatype = 'p2'
511 511 elif e[3] == rev - 1:
512 512 deltatype = 'prev'
513 513 elif e[3] == rev:
514 514 deltatype = 'base'
515 515 else:
516 516 deltatype = 'other'
517 517 else:
518 518 if e[3] == rev:
519 519 deltatype = 'base'
520 520 else:
521 521 deltatype = 'prev'
522 522
523 523 chain = r._deltachain(rev)[0]
524 524 for iterrev in chain:
525 525 e = index[iterrev]
526 526 chainsize += e[1]
527 527
528 528 return compsize, uncompsize, deltatype, chain, chainsize
529 529
530 530 fm = ui.formatter('debugdeltachain', opts)
531 531
532 532 fm.plain(' rev chain# chainlen prev delta '
533 533 'size rawsize chainsize ratio lindist extradist '
534 534 'extraratio\n')
535 535
536 536 chainbases = {}
537 537 for rev in r:
538 538 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
539 539 chainbase = chain[0]
540 540 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
541 541 basestart = r.start(chainbase)
542 542 revstart = r.start(rev)
543 543 lineardist = revstart + comp - basestart
544 544 extradist = lineardist - chainsize
545 545 try:
546 546 prevrev = chain[-2]
547 547 except IndexError:
548 548 prevrev = -1
549 549
550 550 chainratio = float(chainsize) / float(uncomp)
551 551 extraratio = float(extradist) / float(chainsize)
552 552
553 553 fm.startitem()
554 554 fm.write('rev chainid chainlen prevrev deltatype compsize '
555 555 'uncompsize chainsize chainratio lindist extradist '
556 556 'extraratio',
557 557 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
558 558 rev, chainid, len(chain), prevrev, deltatype, comp,
559 559 uncomp, chainsize, chainratio, lineardist, extradist,
560 560 extraratio,
561 561 rev=rev, chainid=chainid, chainlen=len(chain),
562 562 prevrev=prevrev, deltatype=deltatype, compsize=comp,
563 563 uncompsize=uncomp, chainsize=chainsize,
564 564 chainratio=chainratio, lindist=lineardist,
565 565 extradist=extradist, extraratio=extraratio)
566 566
567 567 fm.end()
568 568
569 569 @command('debugdiscovery',
570 570 [('', 'old', None, _('use old-style discovery')),
571 571 ('', 'nonheads', None,
572 572 _('use old-style discovery with non-heads included')),
573 573 ] + commands.remoteopts,
574 574 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
575 575 def debugdiscovery(ui, repo, remoteurl="default", **opts):
576 576 """runs the changeset discovery protocol in isolation"""
577 577 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
578 578 opts.get('branch'))
579 579 remote = hg.peer(repo, opts, remoteurl)
580 580 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
581 581
582 582 # make sure tests are repeatable
583 583 random.seed(12323)
584 584
585 585 def doit(localheads, remoteheads, remote=remote):
586 586 if opts.get('old'):
587 587 if localheads:
588 588 raise error.Abort('cannot use localheads with old style '
589 589 'discovery')
590 590 if not util.safehasattr(remote, 'branches'):
591 591 # enable in-client legacy support
592 592 remote = localrepo.locallegacypeer(remote.local())
593 593 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
594 594 force=True)
595 595 common = set(common)
596 596 if not opts.get('nonheads'):
597 597 ui.write(("unpruned common: %s\n") %
598 598 " ".join(sorted(short(n) for n in common)))
599 599 dag = dagutil.revlogdag(repo.changelog)
600 600 all = dag.ancestorset(dag.internalizeall(common))
601 601 common = dag.externalizeall(dag.headsetofconnecteds(all))
602 602 else:
603 603 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
604 604 common = set(common)
605 605 rheads = set(hds)
606 606 lheads = set(repo.heads())
607 607 ui.write(("common heads: %s\n") %
608 608 " ".join(sorted(short(n) for n in common)))
609 609 if lheads <= common:
610 610 ui.write(("local is subset\n"))
611 611 elif rheads <= common:
612 612 ui.write(("remote is subset\n"))
613 613
614 614 serverlogs = opts.get('serverlog')
615 615 if serverlogs:
616 616 for filename in serverlogs:
617 617 with open(filename, 'r') as logfile:
618 618 line = logfile.readline()
619 619 while line:
620 620 parts = line.strip().split(';')
621 621 op = parts[1]
622 622 if op == 'cg':
623 623 pass
624 624 elif op == 'cgss':
625 625 doit(parts[2].split(' '), parts[3].split(' '))
626 626 elif op == 'unb':
627 627 doit(parts[3].split(' '), parts[2].split(' '))
628 628 line = logfile.readline()
629 629 else:
630 630 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
631 631 opts.get('remote_head'))
632 632 localrevs = opts.get('local_head')
633 633 doit(localrevs, remoterevs)
634 634
635 635 @command('debugextensions', commands.formatteropts, [], norepo=True)
636 636 def debugextensions(ui, **opts):
637 637 '''show information about active extensions'''
638 638 exts = extensions.extensions(ui)
639 639 hgver = util.version()
640 640 fm = ui.formatter('debugextensions', opts)
641 641 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
642 642 isinternal = extensions.ismoduleinternal(extmod)
643 643 extsource = extmod.__file__
644 644 if isinternal:
645 645 exttestedwith = [] # never expose magic string to users
646 646 else:
647 647 exttestedwith = getattr(extmod, 'testedwith', '').split()
648 648 extbuglink = getattr(extmod, 'buglink', None)
649 649
650 650 fm.startitem()
651 651
652 652 if ui.quiet or ui.verbose:
653 653 fm.write('name', '%s\n', extname)
654 654 else:
655 655 fm.write('name', '%s', extname)
656 656 if isinternal or hgver in exttestedwith:
657 657 fm.plain('\n')
658 658 elif not exttestedwith:
659 659 fm.plain(_(' (untested!)\n'))
660 660 else:
661 661 lasttestedversion = exttestedwith[-1]
662 662 fm.plain(' (%s!)\n' % lasttestedversion)
663 663
664 664 fm.condwrite(ui.verbose and extsource, 'source',
665 665 _(' location: %s\n'), extsource or "")
666 666
667 667 if ui.verbose:
668 668 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
669 669 fm.data(bundled=isinternal)
670 670
671 671 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
672 672 _(' tested with: %s\n'),
673 673 fm.formatlist(exttestedwith, name='ver'))
674 674
675 675 fm.condwrite(ui.verbose and extbuglink, 'buglink',
676 676 _(' bug reporting: %s\n'), extbuglink or "")
677 677
678 678 fm.end()
679 679
680 680 @command('debugfileset',
681 681 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
682 682 _('[-r REV] FILESPEC'))
683 683 def debugfileset(ui, repo, expr, **opts):
684 684 '''parse and apply a fileset specification'''
685 685 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
686 686 if ui.verbose:
687 687 tree = fileset.parse(expr)
688 688 ui.note(fileset.prettyformat(tree), "\n")
689 689
690 690 for f in ctx.getfileset(expr):
691 691 ui.write("%s\n" % f)
692 692
693 693 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
694 694 def debugfsinfo(ui, path="."):
695 695 """show information detected about current filesystem"""
696 696 util.writefile('.debugfsinfo', '')
697 697 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
698 698 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
699 699 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
700 700 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
701 701 and 'yes' or 'no'))
702 702 os.unlink('.debugfsinfo')
703 703
704 704 @command('debuggetbundle',
705 705 [('H', 'head', [], _('id of head node'), _('ID')),
706 706 ('C', 'common', [], _('id of common node'), _('ID')),
707 707 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
708 708 _('REPO FILE [-H|-C ID]...'),
709 709 norepo=True)
710 710 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
711 711 """retrieves a bundle from a repo
712 712
713 713 Every ID must be a full-length hex node id string. Saves the bundle to the
714 714 given file.
715 715 """
716 716 repo = hg.peer(ui, opts, repopath)
717 717 if not repo.capable('getbundle'):
718 718 raise error.Abort("getbundle() not supported by target repository")
719 719 args = {}
720 720 if common:
721 721 args['common'] = [bin(s) for s in common]
722 722 if head:
723 723 args['heads'] = [bin(s) for s in head]
724 724 # TODO: get desired bundlecaps from command line.
725 725 args['bundlecaps'] = None
726 726 bundle = repo.getbundle('debug', **args)
727 727
728 728 bundletype = opts.get('type', 'bzip2').lower()
729 729 btypes = {'none': 'HG10UN',
730 730 'bzip2': 'HG10BZ',
731 731 'gzip': 'HG10GZ',
732 732 'bundle2': 'HG20'}
733 733 bundletype = btypes.get(bundletype)
734 734 if bundletype not in bundle2.bundletypes:
735 735 raise error.Abort(_('unknown bundle type specified with --type'))
736 736 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
737 737
738 738 @command('debugignore', [], '[FILE]')
739 739 def debugignore(ui, repo, *files, **opts):
740 740 """display the combined ignore pattern and information about ignored files
741 741
742 742 With no argument display the combined ignore pattern.
743 743
744 744 Given space separated file names, shows if the given file is ignored and
745 745 if so, show the ignore rule (file and line number) that matched it.
746 746 """
747 747 ignore = repo.dirstate._ignore
748 748 if not files:
749 749 # Show all the patterns
750 750 includepat = getattr(ignore, 'includepat', None)
751 751 if includepat is not None:
752 752 ui.write("%s\n" % includepat)
753 753 else:
754 754 raise error.Abort(_("no ignore patterns found"))
755 755 else:
756 756 for f in files:
757 757 nf = util.normpath(f)
758 758 ignored = None
759 759 ignoredata = None
760 760 if nf != '.':
761 761 if ignore(nf):
762 762 ignored = nf
763 763 ignoredata = repo.dirstate._ignorefileandline(nf)
764 764 else:
765 765 for p in util.finddirs(nf):
766 766 if ignore(p):
767 767 ignored = p
768 768 ignoredata = repo.dirstate._ignorefileandline(p)
769 769 break
770 770 if ignored:
771 771 if ignored == nf:
772 772 ui.write(_("%s is ignored\n") % f)
773 773 else:
774 774 ui.write(_("%s is ignored because of "
775 775 "containing folder %s\n")
776 776 % (f, ignored))
777 777 ignorefile, lineno, line = ignoredata
778 778 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
779 779 % (ignorefile, lineno, line))
780 780 else:
781 781 ui.write(_("%s is not ignored\n") % f)
782 782
783 783 @command('debugindex', commands.debugrevlogopts +
784 784 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
785 785 _('[-f FORMAT] -c|-m|FILE'),
786 786 optionalrepo=True)
787 787 def debugindex(ui, repo, file_=None, **opts):
788 788 """dump the contents of an index file"""
789 789 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
790 790 format = opts.get('format', 0)
791 791 if format not in (0, 1):
792 792 raise error.Abort(_("unknown format %d") % format)
793 793
794 794 generaldelta = r.version & revlog.REVLOGGENERALDELTA
795 795 if generaldelta:
796 796 basehdr = ' delta'
797 797 else:
798 798 basehdr = ' base'
799 799
800 800 if ui.debugflag:
801 801 shortfn = hex
802 802 else:
803 803 shortfn = short
804 804
805 805 # There might not be anything in r, so have a sane default
806 806 idlen = 12
807 807 for i in r:
808 808 idlen = len(shortfn(r.node(i)))
809 809 break
810 810
811 811 if format == 0:
812 812 ui.write((" rev offset length " + basehdr + " linkrev"
813 813 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
814 814 elif format == 1:
815 815 ui.write((" rev flag offset length"
816 816 " size " + basehdr + " link p1 p2"
817 817 " %s\n") % "nodeid".rjust(idlen))
818 818
819 819 for i in r:
820 820 node = r.node(i)
821 821 if generaldelta:
822 822 base = r.deltaparent(i)
823 823 else:
824 824 base = r.chainbase(i)
825 825 if format == 0:
826 826 try:
827 827 pp = r.parents(node)
828 828 except Exception:
829 829 pp = [nullid, nullid]
830 830 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
831 831 i, r.start(i), r.length(i), base, r.linkrev(i),
832 832 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
833 833 elif format == 1:
834 834 pr = r.parentrevs(i)
835 835 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
836 836 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
837 837 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
838 838
839 839 @command('debugindexdot', commands.debugrevlogopts,
840 840 _('-c|-m|FILE'), optionalrepo=True)
841 841 def debugindexdot(ui, repo, file_=None, **opts):
842 842 """dump an index DAG as a graphviz dot file"""
843 843 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
844 844 ui.write(("digraph G {\n"))
845 845 for i in r:
846 846 node = r.node(i)
847 847 pp = r.parents(node)
848 848 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
849 849 if pp[1] != nullid:
850 850 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
851 851 ui.write("}\n")
@@ -1,1822 +1,1832 b''
1 1 # revlog.py - storage back-end 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 """Storage back-end for Mercurial.
9 9
10 10 This provides efficient delta storage with O(1) retrieve and append
11 11 and O(changes) merge between branches.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 import collections
17 17 import errno
18 18 import hashlib
19 19 import os
20 20 import struct
21 21 import zlib
22 22
23 23 # import stuff from node for others to import from revlog
24 24 from .node import (
25 25 bin,
26 26 hex,
27 27 nullid,
28 28 nullrev,
29 29 )
30 30 from .i18n import _
31 31 from . import (
32 32 ancestor,
33 33 error,
34 34 mdiff,
35 35 parsers,
36 36 templatefilters,
37 37 util,
38 38 )
39 39
40 40 _pack = struct.pack
41 41 _unpack = struct.unpack
42 42 _compress = zlib.compress
43 43 _decompress = zlib.decompress
44 44
45 45 # revlog header flags
46 46 REVLOGV0 = 0
47 47 REVLOGNG = 1
48 48 REVLOGNGINLINEDATA = (1 << 16)
49 49 REVLOGGENERALDELTA = (1 << 17)
50 50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
51 51 REVLOG_DEFAULT_FORMAT = REVLOGNG
52 52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
53 53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
54 54
55 55 # revlog index flags
56 56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
57 57 REVIDX_DEFAULT_FLAGS = 0
58 58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
59 59
60 60 # max size of revlog with inline data
61 61 _maxinline = 131072
62 62 _chunksize = 1048576
63 63
64 64 RevlogError = error.RevlogError
65 65 LookupError = error.LookupError
66 66 CensoredNodeError = error.CensoredNodeError
67 67
68 68 def getoffset(q):
69 69 return int(q >> 16)
70 70
71 71 def gettype(q):
72 72 return int(q & 0xFFFF)
73 73
74 74 def offset_type(offset, type):
75 75 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
76 76 raise ValueError('unknown revlog index flags')
77 77 return long(long(offset) << 16 | type)
78 78
79 79 _nullhash = hashlib.sha1(nullid)
80 80
81 81 def hash(text, p1, p2):
82 82 """generate a hash from the given text and its parent hashes
83 83
84 84 This hash combines both the current file contents and its history
85 85 in a manner that makes it easy to distinguish nodes with the same
86 86 content in the revision graph.
87 87 """
88 88 # As of now, if one of the parent node is null, p2 is null
89 89 if p2 == nullid:
90 90 # deep copy of a hash is faster than creating one
91 91 s = _nullhash.copy()
92 92 s.update(p1)
93 93 else:
94 94 # none of the parent nodes are nullid
95 95 l = [p1, p2]
96 96 l.sort()
97 97 s = hashlib.sha1(l[0])
98 98 s.update(l[1])
99 99 s.update(text)
100 100 return s.digest()
101 101
102 102 def decompress(bin):
103 103 """ decompress the given input """
104 104 if not bin:
105 105 return bin
106 106 t = bin[0]
107 107 if t == '\0':
108 108 return bin
109 109 if t == 'x':
110 110 try:
111 111 return _decompress(bin)
112 112 except zlib.error as e:
113 113 raise RevlogError(_("revlog decompress error: %s") % str(e))
114 114 if t == 'u':
115 115 return util.buffer(bin, 1)
116 116 raise RevlogError(_("unknown compression type %r") % t)
117 117
118 118 # index v0:
119 119 # 4 bytes: offset
120 120 # 4 bytes: compressed length
121 121 # 4 bytes: base rev
122 122 # 4 bytes: link rev
123 123 # 20 bytes: parent 1 nodeid
124 124 # 20 bytes: parent 2 nodeid
125 125 # 20 bytes: nodeid
126 126 indexformatv0 = ">4l20s20s20s"
127 127
128 128 class revlogoldio(object):
129 129 def __init__(self):
130 130 self.size = struct.calcsize(indexformatv0)
131 131
132 132 def parseindex(self, data, inline):
133 133 s = self.size
134 134 index = []
135 135 nodemap = {nullid: nullrev}
136 136 n = off = 0
137 137 l = len(data)
138 138 while off + s <= l:
139 139 cur = data[off:off + s]
140 140 off += s
141 141 e = _unpack(indexformatv0, cur)
142 142 # transform to revlogv1 format
143 143 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
144 144 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
145 145 index.append(e2)
146 146 nodemap[e[6]] = n
147 147 n += 1
148 148
149 149 # add the magic null revision at -1
150 150 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
151 151
152 152 return index, nodemap, None
153 153
154 154 def packentry(self, entry, node, version, rev):
155 155 if gettype(entry[0]):
156 156 raise RevlogError(_("index entry flags need RevlogNG"))
157 157 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
158 158 node(entry[5]), node(entry[6]), entry[7])
159 159 return _pack(indexformatv0, *e2)
160 160
161 161 # index ng:
162 162 # 6 bytes: offset
163 163 # 2 bytes: flags
164 164 # 4 bytes: compressed length
165 165 # 4 bytes: uncompressed length
166 166 # 4 bytes: base rev
167 167 # 4 bytes: link rev
168 168 # 4 bytes: parent 1 rev
169 169 # 4 bytes: parent 2 rev
170 170 # 32 bytes: nodeid
171 171 indexformatng = ">Qiiiiii20s12x"
172 172 versionformat = ">I"
173 173
174 174 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
175 175 # signed integer)
176 176 _maxentrysize = 0x7fffffff
177 177
178 178 class revlogio(object):
179 179 def __init__(self):
180 180 self.size = struct.calcsize(indexformatng)
181 181
182 182 def parseindex(self, data, inline):
183 183 # call the C implementation to parse the index data
184 184 index, cache = parsers.parse_index2(data, inline)
185 185 return index, getattr(index, 'nodemap', None), cache
186 186
187 187 def packentry(self, entry, node, version, rev):
188 188 p = _pack(indexformatng, *entry)
189 189 if rev == 0:
190 190 p = _pack(versionformat, version) + p[4:]
191 191 return p
192 192
193 193 class revlog(object):
194 194 """
195 195 the underlying revision storage object
196 196
197 197 A revlog consists of two parts, an index and the revision data.
198 198
199 199 The index is a file with a fixed record size containing
200 200 information on each revision, including its nodeid (hash), the
201 201 nodeids of its parents, the position and offset of its data within
202 202 the data file, and the revision it's based on. Finally, each entry
203 203 contains a linkrev entry that can serve as a pointer to external
204 204 data.
205 205
206 206 The revision data itself is a linear collection of data chunks.
207 207 Each chunk represents a revision and is usually represented as a
208 208 delta against the previous chunk. To bound lookup time, runs of
209 209 deltas are limited to about 2 times the length of the original
210 210 version data. This makes retrieval of a version proportional to
211 211 its size, or O(1) relative to the number of revisions.
212 212
213 213 Both pieces of the revlog are written to in an append-only
214 214 fashion, which means we never need to rewrite a file to insert or
215 215 remove data, and can use some simple techniques to avoid the need
216 216 for locking while reading.
217 217
218 218 If checkambig, indexfile is opened with checkambig=True at
219 219 writing, to avoid file stat ambiguity.
220 220 """
221 221 def __init__(self, opener, indexfile, checkambig=False):
222 222 """
223 223 create a revlog object
224 224
225 225 opener is a function that abstracts the file opening operation
226 226 and can be used to implement COW semantics or the like.
227 227 """
228 228 self.indexfile = indexfile
229 229 self.datafile = indexfile[:-2] + ".d"
230 230 self.opener = opener
231 231 # When True, indexfile is opened with checkambig=True at writing, to
232 232 # avoid file stat ambiguity.
233 233 self._checkambig = checkambig
234 234 # 3-tuple of (node, rev, text) for a raw revision.
235 235 self._cache = None
236 236 # Maps rev to chain base rev.
237 237 self._chainbasecache = util.lrucachedict(100)
238 238 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
239 239 self._chunkcache = (0, '')
240 240 # How much data to read and cache into the raw revlog data cache.
241 241 self._chunkcachesize = 65536
242 242 self._maxchainlen = None
243 243 self._aggressivemergedeltas = False
244 244 self.index = []
245 245 # Mapping of partial identifiers to full nodes.
246 246 self._pcache = {}
247 247 # Mapping of revision integer to full node.
248 248 self._nodecache = {nullid: nullrev}
249 249 self._nodepos = None
250 250
251 251 v = REVLOG_DEFAULT_VERSION
252 252 opts = getattr(opener, 'options', None)
253 253 if opts is not None:
254 254 if 'revlogv1' in opts:
255 255 if 'generaldelta' in opts:
256 256 v |= REVLOGGENERALDELTA
257 257 else:
258 258 v = 0
259 259 if 'chunkcachesize' in opts:
260 260 self._chunkcachesize = opts['chunkcachesize']
261 261 if 'maxchainlen' in opts:
262 262 self._maxchainlen = opts['maxchainlen']
263 263 if 'aggressivemergedeltas' in opts:
264 264 self._aggressivemergedeltas = opts['aggressivemergedeltas']
265 265 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
266 266
267 267 if self._chunkcachesize <= 0:
268 268 raise RevlogError(_('revlog chunk cache size %r is not greater '
269 269 'than 0') % self._chunkcachesize)
270 270 elif self._chunkcachesize & (self._chunkcachesize - 1):
271 271 raise RevlogError(_('revlog chunk cache size %r is not a power '
272 272 'of 2') % self._chunkcachesize)
273 273
274 274 indexdata = ''
275 275 self._initempty = True
276 276 try:
277 277 f = self.opener(self.indexfile)
278 278 indexdata = f.read()
279 279 f.close()
280 280 if len(indexdata) > 0:
281 281 v = struct.unpack(versionformat, indexdata[:4])[0]
282 282 self._initempty = False
283 283 except IOError as inst:
284 284 if inst.errno != errno.ENOENT:
285 285 raise
286 286
287 287 self.version = v
288 288 self._inline = v & REVLOGNGINLINEDATA
289 289 self._generaldelta = v & REVLOGGENERALDELTA
290 290 flags = v & ~0xFFFF
291 291 fmt = v & 0xFFFF
292 292 if fmt == REVLOGV0 and flags:
293 293 raise RevlogError(_("index %s unknown flags %#04x for format v0")
294 294 % (self.indexfile, flags >> 16))
295 295 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
296 296 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
297 297 % (self.indexfile, flags >> 16))
298 298 elif fmt > REVLOGNG:
299 299 raise RevlogError(_("index %s unknown format %d")
300 300 % (self.indexfile, fmt))
301 301
302 302 self.storedeltachains = True
303 303
304 304 self._io = revlogio()
305 305 if self.version == REVLOGV0:
306 306 self._io = revlogoldio()
307 307 try:
308 308 d = self._io.parseindex(indexdata, self._inline)
309 309 except (ValueError, IndexError):
310 310 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
311 311 self.index, nodemap, self._chunkcache = d
312 312 if nodemap is not None:
313 313 self.nodemap = self._nodecache = nodemap
314 314 if not self._chunkcache:
315 315 self._chunkclear()
316 316 # revnum -> (chain-length, sum-delta-length)
317 317 self._chaininfocache = {}
318 318
319 319 def tip(self):
320 320 return self.node(len(self.index) - 2)
321 321 def __contains__(self, rev):
322 322 return 0 <= rev < len(self)
323 323 def __len__(self):
324 324 return len(self.index) - 1
325 325 def __iter__(self):
326 326 return iter(xrange(len(self)))
327 327 def revs(self, start=0, stop=None):
328 328 """iterate over all rev in this revlog (from start to stop)"""
329 329 step = 1
330 330 if stop is not None:
331 331 if start > stop:
332 332 step = -1
333 333 stop += step
334 334 else:
335 335 stop = len(self)
336 336 return xrange(start, stop, step)
337 337
338 338 @util.propertycache
339 339 def nodemap(self):
340 340 self.rev(self.node(0))
341 341 return self._nodecache
342 342
343 343 def hasnode(self, node):
344 344 try:
345 345 self.rev(node)
346 346 return True
347 347 except KeyError:
348 348 return False
349 349
350 350 def clearcaches(self):
351 351 self._cache = None
352 352 self._chainbasecache.clear()
353 353 self._chunkcache = (0, '')
354 354 self._pcache = {}
355 355
356 356 try:
357 357 self._nodecache.clearcaches()
358 358 except AttributeError:
359 359 self._nodecache = {nullid: nullrev}
360 360 self._nodepos = None
361 361
362 362 def rev(self, node):
363 363 try:
364 364 return self._nodecache[node]
365 365 except TypeError:
366 366 raise
367 367 except RevlogError:
368 368 # parsers.c radix tree lookup failed
369 369 raise LookupError(node, self.indexfile, _('no node'))
370 370 except KeyError:
371 371 # pure python cache lookup failed
372 372 n = self._nodecache
373 373 i = self.index
374 374 p = self._nodepos
375 375 if p is None:
376 376 p = len(i) - 2
377 377 for r in xrange(p, -1, -1):
378 378 v = i[r][7]
379 379 n[v] = r
380 380 if v == node:
381 381 self._nodepos = r - 1
382 382 return r
383 383 raise LookupError(node, self.indexfile, _('no node'))
384 384
385 385 # Accessors for index entries.
386 386
387 387 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
388 388 # are flags.
389 389 def start(self, rev):
390 390 return int(self.index[rev][0] >> 16)
391 391
392 392 def flags(self, rev):
393 393 return self.index[rev][0] & 0xFFFF
394 394
395 395 def length(self, rev):
396 396 return self.index[rev][1]
397 397
398 398 def rawsize(self, rev):
399 399 """return the length of the uncompressed text for a given revision"""
400 400 l = self.index[rev][2]
401 401 if l >= 0:
402 402 return l
403 403
404 404 t = self.revision(self.node(rev))
405 405 return len(t)
406 406 size = rawsize
407 407
408 408 def chainbase(self, rev):
409 409 base = self._chainbasecache.get(rev)
410 410 if base is not None:
411 411 return base
412 412
413 413 index = self.index
414 414 base = index[rev][3]
415 415 while base != rev:
416 416 rev = base
417 417 base = index[rev][3]
418 418
419 419 self._chainbasecache[rev] = base
420 420 return base
421 421
422 422 def linkrev(self, rev):
423 423 return self.index[rev][4]
424 424
425 425 def parentrevs(self, rev):
426 426 return self.index[rev][5:7]
427 427
428 428 def node(self, rev):
429 429 return self.index[rev][7]
430 430
431 431 # Derived from index values.
432 432
433 433 def end(self, rev):
434 434 return self.start(rev) + self.length(rev)
435 435
436 436 def parents(self, node):
437 437 i = self.index
438 438 d = i[self.rev(node)]
439 439 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
440 440
441 441 def chainlen(self, rev):
442 442 return self._chaininfo(rev)[0]
443 443
444 444 def _chaininfo(self, rev):
445 445 chaininfocache = self._chaininfocache
446 446 if rev in chaininfocache:
447 447 return chaininfocache[rev]
448 448 index = self.index
449 449 generaldelta = self._generaldelta
450 450 iterrev = rev
451 451 e = index[iterrev]
452 452 clen = 0
453 453 compresseddeltalen = 0
454 454 while iterrev != e[3]:
455 455 clen += 1
456 456 compresseddeltalen += e[1]
457 457 if generaldelta:
458 458 iterrev = e[3]
459 459 else:
460 460 iterrev -= 1
461 461 if iterrev in chaininfocache:
462 462 t = chaininfocache[iterrev]
463 463 clen += t[0]
464 464 compresseddeltalen += t[1]
465 465 break
466 466 e = index[iterrev]
467 467 else:
468 468 # Add text length of base since decompressing that also takes
469 469 # work. For cache hits the length is already included.
470 470 compresseddeltalen += e[1]
471 471 r = (clen, compresseddeltalen)
472 472 chaininfocache[rev] = r
473 473 return r
474 474
475 475 def _deltachain(self, rev, stoprev=None):
476 476 """Obtain the delta chain for a revision.
477 477
478 478 ``stoprev`` specifies a revision to stop at. If not specified, we
479 479 stop at the base of the chain.
480 480
481 481 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
482 482 revs in ascending order and ``stopped`` is a bool indicating whether
483 483 ``stoprev`` was hit.
484 484 """
485 485 chain = []
486 486
487 487 # Alias to prevent attribute lookup in tight loop.
488 488 index = self.index
489 489 generaldelta = self._generaldelta
490 490
491 491 iterrev = rev
492 492 e = index[iterrev]
493 493 while iterrev != e[3] and iterrev != stoprev:
494 494 chain.append(iterrev)
495 495 if generaldelta:
496 496 iterrev = e[3]
497 497 else:
498 498 iterrev -= 1
499 499 e = index[iterrev]
500 500
501 501 if iterrev == stoprev:
502 502 stopped = True
503 503 else:
504 504 chain.append(iterrev)
505 505 stopped = False
506 506
507 507 chain.reverse()
508 508 return chain, stopped
509 509
510 510 def ancestors(self, revs, stoprev=0, inclusive=False):
511 511 """Generate the ancestors of 'revs' in reverse topological order.
512 512 Does not generate revs lower than stoprev.
513 513
514 514 See the documentation for ancestor.lazyancestors for more details."""
515 515
516 516 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
517 517 inclusive=inclusive)
518 518
519 519 def descendants(self, revs):
520 520 """Generate the descendants of 'revs' in revision order.
521 521
522 522 Yield a sequence of revision numbers starting with a child of
523 523 some rev in revs, i.e., each revision is *not* considered a
524 524 descendant of itself. Results are ordered by revision number (a
525 525 topological sort)."""
526 526 first = min(revs)
527 527 if first == nullrev:
528 528 for i in self:
529 529 yield i
530 530 return
531 531
532 532 seen = set(revs)
533 533 for i in self.revs(start=first + 1):
534 534 for x in self.parentrevs(i):
535 535 if x != nullrev and x in seen:
536 536 seen.add(i)
537 537 yield i
538 538 break
539 539
540 540 def findcommonmissing(self, common=None, heads=None):
541 541 """Return a tuple of the ancestors of common and the ancestors of heads
542 542 that are not ancestors of common. In revset terminology, we return the
543 543 tuple:
544 544
545 545 ::common, (::heads) - (::common)
546 546
547 547 The list is sorted by revision number, meaning it is
548 548 topologically sorted.
549 549
550 550 'heads' and 'common' are both lists of node IDs. If heads is
551 551 not supplied, uses all of the revlog's heads. If common is not
552 552 supplied, uses nullid."""
553 553 if common is None:
554 554 common = [nullid]
555 555 if heads is None:
556 556 heads = self.heads()
557 557
558 558 common = [self.rev(n) for n in common]
559 559 heads = [self.rev(n) for n in heads]
560 560
561 561 # we want the ancestors, but inclusive
562 562 class lazyset(object):
563 563 def __init__(self, lazyvalues):
564 564 self.addedvalues = set()
565 565 self.lazyvalues = lazyvalues
566 566
567 567 def __contains__(self, value):
568 568 return value in self.addedvalues or value in self.lazyvalues
569 569
570 570 def __iter__(self):
571 571 added = self.addedvalues
572 572 for r in added:
573 573 yield r
574 574 for r in self.lazyvalues:
575 575 if not r in added:
576 576 yield r
577 577
578 578 def add(self, value):
579 579 self.addedvalues.add(value)
580 580
581 581 def update(self, values):
582 582 self.addedvalues.update(values)
583 583
584 584 has = lazyset(self.ancestors(common))
585 585 has.add(nullrev)
586 586 has.update(common)
587 587
588 588 # take all ancestors from heads that aren't in has
589 589 missing = set()
590 590 visit = collections.deque(r for r in heads if r not in has)
591 591 while visit:
592 592 r = visit.popleft()
593 593 if r in missing:
594 594 continue
595 595 else:
596 596 missing.add(r)
597 597 for p in self.parentrevs(r):
598 598 if p not in has:
599 599 visit.append(p)
600 600 missing = list(missing)
601 601 missing.sort()
602 602 return has, [self.node(miss) for miss in missing]
603 603
604 604 def incrementalmissingrevs(self, common=None):
605 605 """Return an object that can be used to incrementally compute the
606 606 revision numbers of the ancestors of arbitrary sets that are not
607 607 ancestors of common. This is an ancestor.incrementalmissingancestors
608 608 object.
609 609
610 610 'common' is a list of revision numbers. If common is not supplied, uses
611 611 nullrev.
612 612 """
613 613 if common is None:
614 614 common = [nullrev]
615 615
616 616 return ancestor.incrementalmissingancestors(self.parentrevs, common)
617 617
618 618 def findmissingrevs(self, common=None, heads=None):
619 619 """Return the revision numbers of the ancestors of heads that
620 620 are not ancestors of common.
621 621
622 622 More specifically, return a list of revision numbers corresponding to
623 623 nodes N such that every N satisfies the following constraints:
624 624
625 625 1. N is an ancestor of some node in 'heads'
626 626 2. N is not an ancestor of any node in 'common'
627 627
628 628 The list is sorted by revision number, meaning it is
629 629 topologically sorted.
630 630
631 631 'heads' and 'common' are both lists of revision numbers. If heads is
632 632 not supplied, uses all of the revlog's heads. If common is not
633 633 supplied, uses nullid."""
634 634 if common is None:
635 635 common = [nullrev]
636 636 if heads is None:
637 637 heads = self.headrevs()
638 638
639 639 inc = self.incrementalmissingrevs(common=common)
640 640 return inc.missingancestors(heads)
641 641
642 642 def findmissing(self, common=None, heads=None):
643 643 """Return the ancestors of heads that are not ancestors of common.
644 644
645 645 More specifically, return a list of nodes N such that every N
646 646 satisfies the following constraints:
647 647
648 648 1. N is an ancestor of some node in 'heads'
649 649 2. N is not an ancestor of any node in 'common'
650 650
651 651 The list is sorted by revision number, meaning it is
652 652 topologically sorted.
653 653
654 654 'heads' and 'common' are both lists of node IDs. If heads is
655 655 not supplied, uses all of the revlog's heads. If common is not
656 656 supplied, uses nullid."""
657 657 if common is None:
658 658 common = [nullid]
659 659 if heads is None:
660 660 heads = self.heads()
661 661
662 662 common = [self.rev(n) for n in common]
663 663 heads = [self.rev(n) for n in heads]
664 664
665 665 inc = self.incrementalmissingrevs(common=common)
666 666 return [self.node(r) for r in inc.missingancestors(heads)]
667 667
668 668 def nodesbetween(self, roots=None, heads=None):
669 669 """Return a topological path from 'roots' to 'heads'.
670 670
671 671 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
672 672 topologically sorted list of all nodes N that satisfy both of
673 673 these constraints:
674 674
675 675 1. N is a descendant of some node in 'roots'
676 676 2. N is an ancestor of some node in 'heads'
677 677
678 678 Every node is considered to be both a descendant and an ancestor
679 679 of itself, so every reachable node in 'roots' and 'heads' will be
680 680 included in 'nodes'.
681 681
682 682 'outroots' is the list of reachable nodes in 'roots', i.e., the
683 683 subset of 'roots' that is returned in 'nodes'. Likewise,
684 684 'outheads' is the subset of 'heads' that is also in 'nodes'.
685 685
686 686 'roots' and 'heads' are both lists of node IDs. If 'roots' is
687 687 unspecified, uses nullid as the only root. If 'heads' is
688 688 unspecified, uses list of all of the revlog's heads."""
689 689 nonodes = ([], [], [])
690 690 if roots is not None:
691 691 roots = list(roots)
692 692 if not roots:
693 693 return nonodes
694 694 lowestrev = min([self.rev(n) for n in roots])
695 695 else:
696 696 roots = [nullid] # Everybody's a descendant of nullid
697 697 lowestrev = nullrev
698 698 if (lowestrev == nullrev) and (heads is None):
699 699 # We want _all_ the nodes!
700 700 return ([self.node(r) for r in self], [nullid], list(self.heads()))
701 701 if heads is None:
702 702 # All nodes are ancestors, so the latest ancestor is the last
703 703 # node.
704 704 highestrev = len(self) - 1
705 705 # Set ancestors to None to signal that every node is an ancestor.
706 706 ancestors = None
707 707 # Set heads to an empty dictionary for later discovery of heads
708 708 heads = {}
709 709 else:
710 710 heads = list(heads)
711 711 if not heads:
712 712 return nonodes
713 713 ancestors = set()
714 714 # Turn heads into a dictionary so we can remove 'fake' heads.
715 715 # Also, later we will be using it to filter out the heads we can't
716 716 # find from roots.
717 717 heads = dict.fromkeys(heads, False)
718 718 # Start at the top and keep marking parents until we're done.
719 719 nodestotag = set(heads)
720 720 # Remember where the top was so we can use it as a limit later.
721 721 highestrev = max([self.rev(n) for n in nodestotag])
722 722 while nodestotag:
723 723 # grab a node to tag
724 724 n = nodestotag.pop()
725 725 # Never tag nullid
726 726 if n == nullid:
727 727 continue
728 728 # A node's revision number represents its place in a
729 729 # topologically sorted list of nodes.
730 730 r = self.rev(n)
731 731 if r >= lowestrev:
732 732 if n not in ancestors:
733 733 # If we are possibly a descendant of one of the roots
734 734 # and we haven't already been marked as an ancestor
735 735 ancestors.add(n) # Mark as ancestor
736 736 # Add non-nullid parents to list of nodes to tag.
737 737 nodestotag.update([p for p in self.parents(n) if
738 738 p != nullid])
739 739 elif n in heads: # We've seen it before, is it a fake head?
740 740 # So it is, real heads should not be the ancestors of
741 741 # any other heads.
742 742 heads.pop(n)
743 743 if not ancestors:
744 744 return nonodes
745 745 # Now that we have our set of ancestors, we want to remove any
746 746 # roots that are not ancestors.
747 747
748 748 # If one of the roots was nullid, everything is included anyway.
749 749 if lowestrev > nullrev:
750 750 # But, since we weren't, let's recompute the lowest rev to not
751 751 # include roots that aren't ancestors.
752 752
753 753 # Filter out roots that aren't ancestors of heads
754 754 roots = [root for root in roots if root in ancestors]
755 755 # Recompute the lowest revision
756 756 if roots:
757 757 lowestrev = min([self.rev(root) for root in roots])
758 758 else:
759 759 # No more roots? Return empty list
760 760 return nonodes
761 761 else:
762 762 # We are descending from nullid, and don't need to care about
763 763 # any other roots.
764 764 lowestrev = nullrev
765 765 roots = [nullid]
766 766 # Transform our roots list into a set.
767 767 descendants = set(roots)
768 768 # Also, keep the original roots so we can filter out roots that aren't
769 769 # 'real' roots (i.e. are descended from other roots).
770 770 roots = descendants.copy()
771 771 # Our topologically sorted list of output nodes.
772 772 orderedout = []
773 773 # Don't start at nullid since we don't want nullid in our output list,
774 774 # and if nullid shows up in descendants, empty parents will look like
775 775 # they're descendants.
776 776 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
777 777 n = self.node(r)
778 778 isdescendant = False
779 779 if lowestrev == nullrev: # Everybody is a descendant of nullid
780 780 isdescendant = True
781 781 elif n in descendants:
782 782 # n is already a descendant
783 783 isdescendant = True
784 784 # This check only needs to be done here because all the roots
785 785 # will start being marked is descendants before the loop.
786 786 if n in roots:
787 787 # If n was a root, check if it's a 'real' root.
788 788 p = tuple(self.parents(n))
789 789 # If any of its parents are descendants, it's not a root.
790 790 if (p[0] in descendants) or (p[1] in descendants):
791 791 roots.remove(n)
792 792 else:
793 793 p = tuple(self.parents(n))
794 794 # A node is a descendant if either of its parents are
795 795 # descendants. (We seeded the dependents list with the roots
796 796 # up there, remember?)
797 797 if (p[0] in descendants) or (p[1] in descendants):
798 798 descendants.add(n)
799 799 isdescendant = True
800 800 if isdescendant and ((ancestors is None) or (n in ancestors)):
801 801 # Only include nodes that are both descendants and ancestors.
802 802 orderedout.append(n)
803 803 if (ancestors is not None) and (n in heads):
804 804 # We're trying to figure out which heads are reachable
805 805 # from roots.
806 806 # Mark this head as having been reached
807 807 heads[n] = True
808 808 elif ancestors is None:
809 809 # Otherwise, we're trying to discover the heads.
810 810 # Assume this is a head because if it isn't, the next step
811 811 # will eventually remove it.
812 812 heads[n] = True
813 813 # But, obviously its parents aren't.
814 814 for p in self.parents(n):
815 815 heads.pop(p, None)
816 816 heads = [head for head, flag in heads.iteritems() if flag]
817 817 roots = list(roots)
818 818 assert orderedout
819 819 assert roots
820 820 assert heads
821 821 return (orderedout, roots, heads)
822 822
823 823 def headrevs(self):
824 824 try:
825 825 return self.index.headrevs()
826 826 except AttributeError:
827 827 return self._headrevs()
828 828
829 829 def computephases(self, roots):
830 830 return self.index.computephasesmapsets(roots)
831 831
832 832 def _headrevs(self):
833 833 count = len(self)
834 834 if not count:
835 835 return [nullrev]
836 836 # we won't iter over filtered rev so nobody is a head at start
837 837 ishead = [0] * (count + 1)
838 838 index = self.index
839 839 for r in self:
840 840 ishead[r] = 1 # I may be an head
841 841 e = index[r]
842 842 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
843 843 return [r for r, val in enumerate(ishead) if val]
844 844
845 845 def heads(self, start=None, stop=None):
846 846 """return the list of all nodes that have no children
847 847
848 848 if start is specified, only heads that are descendants of
849 849 start will be returned
850 850 if stop is specified, it will consider all the revs from stop
851 851 as if they had no children
852 852 """
853 853 if start is None and stop is None:
854 854 if not len(self):
855 855 return [nullid]
856 856 return [self.node(r) for r in self.headrevs()]
857 857
858 858 if start is None:
859 859 start = nullid
860 860 if stop is None:
861 861 stop = []
862 862 stoprevs = set([self.rev(n) for n in stop])
863 863 startrev = self.rev(start)
864 864 reachable = set((startrev,))
865 865 heads = set((startrev,))
866 866
867 867 parentrevs = self.parentrevs
868 868 for r in self.revs(start=startrev + 1):
869 869 for p in parentrevs(r):
870 870 if p in reachable:
871 871 if r not in stoprevs:
872 872 reachable.add(r)
873 873 heads.add(r)
874 874 if p in heads and p not in stoprevs:
875 875 heads.remove(p)
876 876
877 877 return [self.node(r) for r in heads]
878 878
879 879 def children(self, node):
880 880 """find the children of a given node"""
881 881 c = []
882 882 p = self.rev(node)
883 883 for r in self.revs(start=p + 1):
884 884 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
885 885 if prevs:
886 886 for pr in prevs:
887 887 if pr == p:
888 888 c.append(self.node(r))
889 889 elif p == nullrev:
890 890 c.append(self.node(r))
891 891 return c
892 892
893 893 def descendant(self, start, end):
894 894 if start == nullrev:
895 895 return True
896 896 for i in self.descendants([start]):
897 897 if i == end:
898 898 return True
899 899 elif i > end:
900 900 break
901 901 return False
902 902
903 903 def commonancestorsheads(self, a, b):
904 904 """calculate all the heads of the common ancestors of nodes a and b"""
905 905 a, b = self.rev(a), self.rev(b)
906 906 try:
907 907 ancs = self.index.commonancestorsheads(a, b)
908 908 except (AttributeError, OverflowError): # C implementation failed
909 909 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
910 910 return map(self.node, ancs)
911 911
912 912 def isancestor(self, a, b):
913 913 """return True if node a is an ancestor of node b
914 914
915 915 The implementation of this is trivial but the use of
916 916 commonancestorsheads is not."""
917 917 return a in self.commonancestorsheads(a, b)
918 918
919 919 def ancestor(self, a, b):
920 920 """calculate the "best" common ancestor of nodes a and b"""
921 921
922 922 a, b = self.rev(a), self.rev(b)
923 923 try:
924 924 ancs = self.index.ancestors(a, b)
925 925 except (AttributeError, OverflowError):
926 926 ancs = ancestor.ancestors(self.parentrevs, a, b)
927 927 if ancs:
928 928 # choose a consistent winner when there's a tie
929 929 return min(map(self.node, ancs))
930 930 return nullid
931 931
932 932 def _match(self, id):
933 933 if isinstance(id, int):
934 934 # rev
935 935 return self.node(id)
936 936 if len(id) == 20:
937 937 # possibly a binary node
938 938 # odds of a binary node being all hex in ASCII are 1 in 10**25
939 939 try:
940 940 node = id
941 941 self.rev(node) # quick search the index
942 942 return node
943 943 except LookupError:
944 944 pass # may be partial hex id
945 945 try:
946 946 # str(rev)
947 947 rev = int(id)
948 948 if str(rev) != id:
949 949 raise ValueError
950 950 if rev < 0:
951 951 rev = len(self) + rev
952 952 if rev < 0 or rev >= len(self):
953 953 raise ValueError
954 954 return self.node(rev)
955 955 except (ValueError, OverflowError):
956 956 pass
957 957 if len(id) == 40:
958 958 try:
959 959 # a full hex nodeid?
960 960 node = bin(id)
961 961 self.rev(node)
962 962 return node
963 963 except (TypeError, LookupError):
964 964 pass
965 965
966 966 def _partialmatch(self, id):
967 967 try:
968 968 partial = self.index.partialmatch(id)
969 969 if partial and self.hasnode(partial):
970 970 return partial
971 971 return None
972 972 except RevlogError:
973 973 # parsers.c radix tree lookup gave multiple matches
974 974 # fast path: for unfiltered changelog, radix tree is accurate
975 975 if not getattr(self, 'filteredrevs', None):
976 976 raise LookupError(id, self.indexfile,
977 977 _('ambiguous identifier'))
978 978 # fall through to slow path that filters hidden revisions
979 979 except (AttributeError, ValueError):
980 980 # we are pure python, or key was too short to search radix tree
981 981 pass
982 982
983 983 if id in self._pcache:
984 984 return self._pcache[id]
985 985
986 986 if len(id) < 40:
987 987 try:
988 988 # hex(node)[:...]
989 989 l = len(id) // 2 # grab an even number of digits
990 990 prefix = bin(id[:l * 2])
991 991 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
992 992 nl = [n for n in nl if hex(n).startswith(id) and
993 993 self.hasnode(n)]
994 994 if len(nl) > 0:
995 995 if len(nl) == 1:
996 996 self._pcache[id] = nl[0]
997 997 return nl[0]
998 998 raise LookupError(id, self.indexfile,
999 999 _('ambiguous identifier'))
1000 1000 return None
1001 1001 except TypeError:
1002 1002 pass
1003 1003
1004 1004 def lookup(self, id):
1005 1005 """locate a node based on:
1006 1006 - revision number or str(revision number)
1007 1007 - nodeid or subset of hex nodeid
1008 1008 """
1009 1009 n = self._match(id)
1010 1010 if n is not None:
1011 1011 return n
1012 1012 n = self._partialmatch(id)
1013 1013 if n:
1014 1014 return n
1015 1015
1016 1016 raise LookupError(id, self.indexfile, _('no match found'))
1017 1017
1018 1018 def cmp(self, node, text):
1019 1019 """compare text with a given file revision
1020 1020
1021 1021 returns True if text is different than what is stored.
1022 1022 """
1023 1023 p1, p2 = self.parents(node)
1024 1024 return hash(text, p1, p2) != node
1025 1025
1026 1026 def _addchunk(self, offset, data):
1027 1027 """Add a segment to the revlog cache.
1028 1028
1029 1029 Accepts an absolute offset and the data that is at that location.
1030 1030 """
1031 1031 o, d = self._chunkcache
1032 1032 # try to add to existing cache
1033 1033 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1034 1034 self._chunkcache = o, d + data
1035 1035 else:
1036 1036 self._chunkcache = offset, data
1037 1037
1038 1038 def _loadchunk(self, offset, length, df=None):
1039 1039 """Load a segment of raw data from the revlog.
1040 1040
1041 1041 Accepts an absolute offset, length to read, and an optional existing
1042 1042 file handle to read from.
1043 1043
1044 1044 If an existing file handle is passed, it will be seeked and the
1045 1045 original seek position will NOT be restored.
1046 1046
1047 1047 Returns a str or buffer of raw byte data.
1048 1048 """
1049 1049 if df is not None:
1050 1050 closehandle = False
1051 1051 else:
1052 1052 if self._inline:
1053 1053 df = self.opener(self.indexfile)
1054 1054 else:
1055 1055 df = self.opener(self.datafile)
1056 1056 closehandle = True
1057 1057
1058 1058 # Cache data both forward and backward around the requested
1059 1059 # data, in a fixed size window. This helps speed up operations
1060 1060 # involving reading the revlog backwards.
1061 1061 cachesize = self._chunkcachesize
1062 1062 realoffset = offset & ~(cachesize - 1)
1063 1063 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1064 1064 - realoffset)
1065 1065 df.seek(realoffset)
1066 1066 d = df.read(reallength)
1067 1067 if closehandle:
1068 1068 df.close()
1069 1069 self._addchunk(realoffset, d)
1070 1070 if offset != realoffset or reallength != length:
1071 1071 return util.buffer(d, offset - realoffset, length)
1072 1072 return d
1073 1073
1074 1074 def _getchunk(self, offset, length, df=None):
1075 1075 """Obtain a segment of raw data from the revlog.
1076 1076
1077 1077 Accepts an absolute offset, length of bytes to obtain, and an
1078 1078 optional file handle to the already-opened revlog. If the file
1079 1079 handle is used, it's original seek position will not be preserved.
1080 1080
1081 1081 Requests for data may be returned from a cache.
1082 1082
1083 1083 Returns a str or a buffer instance of raw byte data.
1084 1084 """
1085 1085 o, d = self._chunkcache
1086 1086 l = len(d)
1087 1087
1088 1088 # is it in the cache?
1089 1089 cachestart = offset - o
1090 1090 cacheend = cachestart + length
1091 1091 if cachestart >= 0 and cacheend <= l:
1092 1092 if cachestart == 0 and cacheend == l:
1093 1093 return d # avoid a copy
1094 1094 return util.buffer(d, cachestart, cacheend - cachestart)
1095 1095
1096 1096 return self._loadchunk(offset, length, df=df)
1097 1097
1098 1098 def _chunkraw(self, startrev, endrev, df=None):
1099 1099 """Obtain a segment of raw data corresponding to a range of revisions.
1100 1100
1101 1101 Accepts the start and end revisions and an optional already-open
1102 1102 file handle to be used for reading. If the file handle is read, its
1103 1103 seek position will not be preserved.
1104 1104
1105 1105 Requests for data may be satisfied by a cache.
1106 1106
1107 1107 Returns a 2-tuple of (offset, data) for the requested range of
1108 1108 revisions. Offset is the integer offset from the beginning of the
1109 1109 revlog and data is a str or buffer of the raw byte data.
1110 1110
1111 1111 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1112 1112 to determine where each revision's data begins and ends.
1113 1113 """
1114 1114 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1115 1115 # (functions are expensive).
1116 1116 index = self.index
1117 1117 istart = index[startrev]
1118 1118 start = int(istart[0] >> 16)
1119 1119 if startrev == endrev:
1120 1120 end = start + istart[1]
1121 1121 else:
1122 1122 iend = index[endrev]
1123 1123 end = int(iend[0] >> 16) + iend[1]
1124 1124
1125 1125 if self._inline:
1126 1126 start += (startrev + 1) * self._io.size
1127 1127 end += (endrev + 1) * self._io.size
1128 1128 length = end - start
1129 1129
1130 1130 return start, self._getchunk(start, length, df=df)
1131 1131
1132 1132 def _chunk(self, rev, df=None):
1133 1133 """Obtain a single decompressed chunk for a revision.
1134 1134
1135 1135 Accepts an integer revision and an optional already-open file handle
1136 1136 to be used for reading. If used, the seek position of the file will not
1137 1137 be preserved.
1138 1138
1139 1139 Returns a str holding uncompressed data for the requested revision.
1140 1140 """
1141 1141 return decompress(self._chunkraw(rev, rev, df=df)[1])
1142 1142
1143 1143 def _chunks(self, revs, df=None):
1144 1144 """Obtain decompressed chunks for the specified revisions.
1145 1145
1146 1146 Accepts an iterable of numeric revisions that are assumed to be in
1147 1147 ascending order. Also accepts an optional already-open file handle
1148 1148 to be used for reading. If used, the seek position of the file will
1149 1149 not be preserved.
1150 1150
1151 1151 This function is similar to calling ``self._chunk()`` multiple times,
1152 1152 but is faster.
1153 1153
1154 1154 Returns a list with decompressed data for each requested revision.
1155 1155 """
1156 1156 if not revs:
1157 1157 return []
1158 1158 start = self.start
1159 1159 length = self.length
1160 1160 inline = self._inline
1161 1161 iosize = self._io.size
1162 1162 buffer = util.buffer
1163 1163
1164 1164 l = []
1165 1165 ladd = l.append
1166 1166
1167 1167 try:
1168 1168 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1169 1169 except OverflowError:
1170 1170 # issue4215 - we can't cache a run of chunks greater than
1171 1171 # 2G on Windows
1172 1172 return [self._chunk(rev, df=df) for rev in revs]
1173 1173
1174 1174 for rev in revs:
1175 1175 chunkstart = start(rev)
1176 1176 if inline:
1177 1177 chunkstart += (rev + 1) * iosize
1178 1178 chunklength = length(rev)
1179 1179 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1180 1180
1181 1181 return l
1182 1182
1183 1183 def _chunkclear(self):
1184 1184 """Clear the raw chunk cache."""
1185 1185 self._chunkcache = (0, '')
1186 1186
1187 1187 def deltaparent(self, rev):
1188 1188 """return deltaparent of the given revision"""
1189 1189 base = self.index[rev][3]
1190 1190 if base == rev:
1191 1191 return nullrev
1192 1192 elif self._generaldelta:
1193 1193 return base
1194 1194 else:
1195 1195 return rev - 1
1196 1196
1197 1197 def revdiff(self, rev1, rev2):
1198 1198 """return or calculate a delta between two revisions"""
1199 1199 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1200 1200 return str(self._chunk(rev2))
1201 1201
1202 1202 return mdiff.textdiff(self.revision(rev1),
1203 1203 self.revision(rev2))
1204 1204
1205 def revision(self, nodeorrev, _df=None):
1205 def revision(self, nodeorrev, _df=None, raw=False):
1206 1206 """return an uncompressed revision of a given node or revision
1207 1207 number.
1208 1208
1209 _df is an existing file handle to read from. It is meant to only be
1210 used internally.
1209 _df - an existing file handle to read from. (internal-only)
1210 raw - an optional argument specifying if the revision data is to be
1211 treated as raw data when applying flag transforms. 'raw' should be set
1212 to True when generating changegroups or in debug commands.
1211 1213 """
1212 1214 if isinstance(nodeorrev, int):
1213 1215 rev = nodeorrev
1214 1216 node = self.node(rev)
1215 1217 else:
1216 1218 node = nodeorrev
1217 1219 rev = None
1218 1220
1219 1221 cachedrev = None
1220 1222 if node == nullid:
1221 1223 return ""
1222 1224 if self._cache:
1223 1225 if self._cache[0] == node:
1224 1226 return self._cache[2]
1225 1227 cachedrev = self._cache[1]
1226 1228
1227 1229 # look up what we need to read
1228 1230 text = None
1229 1231 if rev is None:
1230 1232 rev = self.rev(node)
1231 1233
1232 1234 # check rev flags
1233 1235 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1234 1236 raise RevlogError(_('incompatible revision flag %x') %
1235 1237 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1236 1238
1237 1239 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1238 1240 if stopped:
1239 1241 text = self._cache[2]
1240 1242
1241 1243 # drop cache to save memory
1242 1244 self._cache = None
1243 1245
1244 1246 bins = self._chunks(chain, df=_df)
1245 1247 if text is None:
1246 1248 text = str(bins[0])
1247 1249 bins = bins[1:]
1248 1250
1249 1251 text = mdiff.patches(text, bins)
1250 1252 self.checkhash(text, node, rev=rev)
1251 1253 self._cache = (node, rev, text)
1252 1254 return text
1253 1255
1254 1256 def hash(self, text, p1, p2):
1255 1257 """Compute a node hash.
1256 1258
1257 1259 Available as a function so that subclasses can replace the hash
1258 1260 as needed.
1259 1261 """
1260 1262 return hash(text, p1, p2)
1261 1263
1262 1264 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1263 1265 """Check node hash integrity.
1264 1266
1265 1267 Available as a function so that subclasses can extend hash mismatch
1266 1268 behaviors as needed.
1267 1269 """
1268 1270 if p1 is None and p2 is None:
1269 1271 p1, p2 = self.parents(node)
1270 1272 if node != self.hash(text, p1, p2):
1271 1273 revornode = rev
1272 1274 if revornode is None:
1273 1275 revornode = templatefilters.short(hex(node))
1274 1276 raise RevlogError(_("integrity check failed on %s:%s")
1275 1277 % (self.indexfile, revornode))
1276 1278
1277 1279 def checkinlinesize(self, tr, fp=None):
1278 1280 """Check if the revlog is too big for inline and convert if so.
1279 1281
1280 1282 This should be called after revisions are added to the revlog. If the
1281 1283 revlog has grown too large to be an inline revlog, it will convert it
1282 1284 to use multiple index and data files.
1283 1285 """
1284 1286 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1285 1287 return
1286 1288
1287 1289 trinfo = tr.find(self.indexfile)
1288 1290 if trinfo is None:
1289 1291 raise RevlogError(_("%s not found in the transaction")
1290 1292 % self.indexfile)
1291 1293
1292 1294 trindex = trinfo[2]
1293 1295 if trindex is not None:
1294 1296 dataoff = self.start(trindex)
1295 1297 else:
1296 1298 # revlog was stripped at start of transaction, use all leftover data
1297 1299 trindex = len(self) - 1
1298 1300 dataoff = self.end(-2)
1299 1301
1300 1302 tr.add(self.datafile, dataoff)
1301 1303
1302 1304 if fp:
1303 1305 fp.flush()
1304 1306 fp.close()
1305 1307
1306 1308 df = self.opener(self.datafile, 'w')
1307 1309 try:
1308 1310 for r in self:
1309 1311 df.write(self._chunkraw(r, r)[1])
1310 1312 finally:
1311 1313 df.close()
1312 1314
1313 1315 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1314 1316 checkambig=self._checkambig)
1315 1317 self.version &= ~(REVLOGNGINLINEDATA)
1316 1318 self._inline = False
1317 1319 for i in self:
1318 1320 e = self._io.packentry(self.index[i], self.node, self.version, i)
1319 1321 fp.write(e)
1320 1322
1321 1323 # if we don't call close, the temp file will never replace the
1322 1324 # real index
1323 1325 fp.close()
1324 1326
1325 1327 tr.replace(self.indexfile, trindex * self._io.size)
1326 1328 self._chunkclear()
1327 1329
1328 1330 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1329 1331 node=None):
1330 1332 """add a revision to the log
1331 1333
1332 1334 text - the revision data to add
1333 1335 transaction - the transaction object used for rollback
1334 1336 link - the linkrev data to add
1335 1337 p1, p2 - the parent nodeids of the revision
1336 1338 cachedelta - an optional precomputed delta
1337 1339 node - nodeid of revision; typically node is not specified, and it is
1338 1340 computed by default as hash(text, p1, p2), however subclasses might
1339 1341 use different hashing method (and override checkhash() in such case)
1340 1342 """
1341 1343 if link == nullrev:
1342 1344 raise RevlogError(_("attempted to add linkrev -1 to %s")
1343 1345 % self.indexfile)
1344 1346
1345 1347 if len(text) > _maxentrysize:
1346 1348 raise RevlogError(
1347 1349 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1348 1350 % (self.indexfile, len(text)))
1349 1351
1350 1352 node = node or self.hash(text, p1, p2)
1351 1353 if node in self.nodemap:
1352 1354 return node
1353 1355
1354 1356 dfh = None
1355 1357 if not self._inline:
1356 1358 dfh = self.opener(self.datafile, "a+")
1357 1359 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1358 1360 try:
1359 1361 return self._addrevision(node, text, transaction, link, p1, p2,
1360 1362 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1361 1363 finally:
1362 1364 if dfh:
1363 1365 dfh.close()
1364 1366 ifh.close()
1365 1367
1366 1368 def compress(self, text):
1367 1369 """ generate a possibly-compressed representation of text """
1368 1370 if not text:
1369 1371 return ("", text)
1370 1372 l = len(text)
1371 1373 bin = None
1372 1374 if l < 44:
1373 1375 pass
1374 1376 elif l > 1000000:
1375 1377 # zlib makes an internal copy, thus doubling memory usage for
1376 1378 # large files, so lets do this in pieces
1377 1379 z = zlib.compressobj()
1378 1380 p = []
1379 1381 pos = 0
1380 1382 while pos < l:
1381 1383 pos2 = pos + 2**20
1382 1384 p.append(z.compress(text[pos:pos2]))
1383 1385 pos = pos2
1384 1386 p.append(z.flush())
1385 1387 if sum(map(len, p)) < l:
1386 1388 bin = "".join(p)
1387 1389 else:
1388 1390 bin = _compress(text)
1389 1391 if bin is None or len(bin) > l:
1390 1392 if text[0] == '\0':
1391 1393 return ("", text)
1392 1394 return ('u', text)
1393 1395 return ("", bin)
1394 1396
1395 1397 def _isgooddelta(self, d, textlen):
1396 1398 """Returns True if the given delta is good. Good means that it is within
1397 1399 the disk span, disk size, and chain length bounds that we know to be
1398 1400 performant."""
1399 1401 if d is None:
1400 1402 return False
1401 1403
1402 1404 # - 'dist' is the distance from the base revision -- bounding it limits
1403 1405 # the amount of I/O we need to do.
1404 1406 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1405 1407 # to apply -- bounding it limits the amount of CPU we consume.
1406 1408 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1407 1409 if (dist > textlen * 4 or l > textlen or
1408 1410 compresseddeltalen > textlen * 2 or
1409 1411 (self._maxchainlen and chainlen > self._maxchainlen)):
1410 1412 return False
1411 1413
1412 1414 return True
1413 1415
1414 1416 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1415 cachedelta, ifh, dfh, alwayscache=False):
1417 cachedelta, ifh, dfh, alwayscache=False, raw=False):
1416 1418 """internal function to add revisions to the log
1417 1419
1418 1420 see addrevision for argument descriptions.
1419 1421 invariants:
1420 1422 - text is optional (can be None); if not set, cachedelta must be set.
1421 1423 if both are set, they must correspond to each other.
1424 - raw is optional; if set to True, it indicates the revision data is to
1425 be treated by _processflags() as raw. It is usually set by changegroup
1426 generation and debug commands.
1422 1427 """
1423 1428 btext = [text]
1424 1429 def buildtext():
1425 1430 if btext[0] is not None:
1426 1431 return btext[0]
1427 1432 baserev = cachedelta[0]
1428 1433 delta = cachedelta[1]
1429 1434 # special case deltas which replace entire base; no need to decode
1430 1435 # base revision. this neatly avoids censored bases, which throw when
1431 1436 # they're decoded.
1432 1437 hlen = struct.calcsize(">lll")
1433 1438 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1434 1439 len(delta) - hlen):
1435 1440 btext[0] = delta[hlen:]
1436 1441 else:
1437 1442 if self._inline:
1438 1443 fh = ifh
1439 1444 else:
1440 1445 fh = dfh
1441 basetext = self.revision(self.node(baserev), _df=fh)
1446 basetext = self.revision(self.node(baserev), _df=fh, raw=raw)
1442 1447 btext[0] = mdiff.patch(basetext, delta)
1448
1443 1449 try:
1444 1450 self.checkhash(btext[0], node, p1=p1, p2=p2)
1445 1451 if flags & REVIDX_ISCENSORED:
1446 1452 raise RevlogError(_('node %s is not censored') % node)
1447 1453 except CensoredNodeError:
1448 1454 # must pass the censored index flag to add censored revisions
1449 1455 if not flags & REVIDX_ISCENSORED:
1450 1456 raise
1451 1457 return btext[0]
1452 1458
1453 1459 def builddelta(rev):
1454 1460 # can we use the cached delta?
1455 1461 if cachedelta and cachedelta[0] == rev:
1456 1462 delta = cachedelta[1]
1457 1463 else:
1458 1464 t = buildtext()
1459 1465 if self.iscensored(rev):
1460 1466 # deltas based on a censored revision must replace the
1461 1467 # full content in one patch, so delta works everywhere
1462 1468 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1463 1469 delta = header + t
1464 1470 else:
1465 1471 if self._inline:
1466 1472 fh = ifh
1467 1473 else:
1468 1474 fh = dfh
1469 1475 ptext = self.revision(self.node(rev), _df=fh)
1470 1476 delta = mdiff.textdiff(ptext, t)
1471 1477 header, data = self.compress(delta)
1472 1478 deltalen = len(header) + len(data)
1473 1479 chainbase = self.chainbase(rev)
1474 1480 dist = deltalen + offset - self.start(chainbase)
1475 1481 if self._generaldelta:
1476 1482 base = rev
1477 1483 else:
1478 1484 base = chainbase
1479 1485 chainlen, compresseddeltalen = self._chaininfo(rev)
1480 1486 chainlen += 1
1481 1487 compresseddeltalen += deltalen
1482 1488 return (dist, deltalen, (header, data), base,
1483 1489 chainbase, chainlen, compresseddeltalen)
1484 1490
1485 1491 curr = len(self)
1486 1492 prev = curr - 1
1487 1493 offset = self.end(prev)
1488 1494 delta = None
1489 1495 p1r, p2r = self.rev(p1), self.rev(p2)
1490 1496
1491 1497 # full versions are inserted when the needed deltas
1492 1498 # become comparable to the uncompressed text
1493 1499 if text is None:
1494 1500 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1495 1501 cachedelta[1])
1496 1502 else:
1497 1503 textlen = len(text)
1498 1504
1499 1505 # should we try to build a delta?
1500 1506 if prev != nullrev and self.storedeltachains:
1501 1507 tested = set()
1502 1508 # This condition is true most of the time when processing
1503 1509 # changegroup data into a generaldelta repo. The only time it
1504 1510 # isn't true is if this is the first revision in a delta chain
1505 1511 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1506 1512 if cachedelta and self._generaldelta and self._lazydeltabase:
1507 1513 # Assume what we received from the server is a good choice
1508 1514 # build delta will reuse the cache
1509 1515 candidatedelta = builddelta(cachedelta[0])
1510 1516 tested.add(cachedelta[0])
1511 1517 if self._isgooddelta(candidatedelta, textlen):
1512 1518 delta = candidatedelta
1513 1519 if delta is None and self._generaldelta:
1514 1520 # exclude already lazy tested base if any
1515 1521 parents = [p for p in (p1r, p2r)
1516 1522 if p != nullrev and p not in tested]
1517 1523 if parents and not self._aggressivemergedeltas:
1518 1524 # Pick whichever parent is closer to us (to minimize the
1519 1525 # chance of having to build a fulltext).
1520 1526 parents = [max(parents)]
1521 1527 tested.update(parents)
1522 1528 pdeltas = []
1523 1529 for p in parents:
1524 1530 pd = builddelta(p)
1525 1531 if self._isgooddelta(pd, textlen):
1526 1532 pdeltas.append(pd)
1527 1533 if pdeltas:
1528 1534 delta = min(pdeltas, key=lambda x: x[1])
1529 1535 if delta is None and prev not in tested:
1530 1536 # other approach failed try against prev to hopefully save us a
1531 1537 # fulltext.
1532 1538 candidatedelta = builddelta(prev)
1533 1539 if self._isgooddelta(candidatedelta, textlen):
1534 1540 delta = candidatedelta
1535 1541 if delta is not None:
1536 1542 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1537 1543 else:
1538 1544 text = buildtext()
1539 1545 data = self.compress(text)
1540 1546 l = len(data[1]) + len(data[0])
1541 1547 base = chainbase = curr
1542 1548
1543 1549 e = (offset_type(offset, flags), l, textlen,
1544 1550 base, link, p1r, p2r, node)
1545 1551 self.index.insert(-1, e)
1546 1552 self.nodemap[node] = curr
1547 1553
1548 1554 entry = self._io.packentry(e, self.node, self.version, curr)
1549 1555 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1550 1556
1551 1557 if alwayscache and text is None:
1552 1558 text = buildtext()
1553 1559
1554 1560 if type(text) == str: # only accept immutable objects
1555 1561 self._cache = (node, curr, text)
1556 1562 self._chainbasecache[curr] = chainbase
1557 1563 return node
1558 1564
1559 1565 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1560 1566 # Files opened in a+ mode have inconsistent behavior on various
1561 1567 # platforms. Windows requires that a file positioning call be made
1562 1568 # when the file handle transitions between reads and writes. See
1563 1569 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1564 1570 # platforms, Python or the platform itself can be buggy. Some versions
1565 1571 # of Solaris have been observed to not append at the end of the file
1566 1572 # if the file was seeked to before the end. See issue4943 for more.
1567 1573 #
1568 1574 # We work around this issue by inserting a seek() before writing.
1569 1575 # Note: This is likely not necessary on Python 3.
1570 1576 ifh.seek(0, os.SEEK_END)
1571 1577 if dfh:
1572 1578 dfh.seek(0, os.SEEK_END)
1573 1579
1574 1580 curr = len(self) - 1
1575 1581 if not self._inline:
1576 1582 transaction.add(self.datafile, offset)
1577 1583 transaction.add(self.indexfile, curr * len(entry))
1578 1584 if data[0]:
1579 1585 dfh.write(data[0])
1580 1586 dfh.write(data[1])
1581 1587 ifh.write(entry)
1582 1588 else:
1583 1589 offset += curr * self._io.size
1584 1590 transaction.add(self.indexfile, offset, curr)
1585 1591 ifh.write(entry)
1586 1592 ifh.write(data[0])
1587 1593 ifh.write(data[1])
1588 1594 self.checkinlinesize(transaction, ifh)
1589 1595
1590 1596 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1591 1597 """
1592 1598 add a delta group
1593 1599
1594 1600 given a set of deltas, add them to the revision log. the
1595 1601 first delta is against its parent, which should be in our
1596 1602 log, the rest are against the previous delta.
1597 1603
1598 1604 If ``addrevisioncb`` is defined, it will be called with arguments of
1599 1605 this revlog and the node that was added.
1600 1606 """
1601 1607
1602 1608 # track the base of the current delta log
1603 1609 content = []
1604 1610 node = None
1605 1611
1606 1612 r = len(self)
1607 1613 end = 0
1608 1614 if r:
1609 1615 end = self.end(r - 1)
1610 1616 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1611 1617 isize = r * self._io.size
1612 1618 if self._inline:
1613 1619 transaction.add(self.indexfile, end + isize, r)
1614 1620 dfh = None
1615 1621 else:
1616 1622 transaction.add(self.indexfile, isize, r)
1617 1623 transaction.add(self.datafile, end)
1618 1624 dfh = self.opener(self.datafile, "a+")
1619 1625 def flush():
1620 1626 if dfh:
1621 1627 dfh.flush()
1622 1628 ifh.flush()
1623 1629 try:
1624 1630 # loop through our set of deltas
1625 1631 chain = None
1626 1632 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1627 1633 node = chunkdata['node']
1628 1634 p1 = chunkdata['p1']
1629 1635 p2 = chunkdata['p2']
1630 1636 cs = chunkdata['cs']
1631 1637 deltabase = chunkdata['deltabase']
1632 1638 delta = chunkdata['delta']
1633 1639 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1634 1640
1635 1641 content.append(node)
1636 1642
1637 1643 link = linkmapper(cs)
1638 1644 if node in self.nodemap:
1639 1645 # this can happen if two branches make the same change
1640 1646 chain = node
1641 1647 continue
1642 1648
1643 1649 for p in (p1, p2):
1644 1650 if p not in self.nodemap:
1645 1651 raise LookupError(p, self.indexfile,
1646 1652 _('unknown parent'))
1647 1653
1648 1654 if deltabase not in self.nodemap:
1649 1655 raise LookupError(deltabase, self.indexfile,
1650 1656 _('unknown delta base'))
1651 1657
1652 1658 baserev = self.rev(deltabase)
1653 1659
1654 1660 if baserev != nullrev and self.iscensored(baserev):
1655 1661 # if base is censored, delta must be full replacement in a
1656 1662 # single patch operation
1657 1663 hlen = struct.calcsize(">lll")
1658 1664 oldlen = self.rawsize(baserev)
1659 1665 newlen = len(delta) - hlen
1660 1666 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1661 1667 raise error.CensoredBaseError(self.indexfile,
1662 1668 self.node(baserev))
1663 1669
1664 1670 if not flags and self._peek_iscensored(baserev, delta, flush):
1665 1671 flags |= REVIDX_ISCENSORED
1666 1672
1667 1673 # We assume consumers of addrevisioncb will want to retrieve
1668 1674 # the added revision, which will require a call to
1669 1675 # revision(). revision() will fast path if there is a cache
1670 1676 # hit. So, we tell _addrevision() to always cache in this case.
1677 # We're only using addgroup() in the context of changegroup
1678 # generation so the revision data can always be handled as raw
1679 # by the flagprocessor.
1671 1680 chain = self._addrevision(node, None, transaction, link,
1672 1681 p1, p2, flags, (baserev, delta),
1673 1682 ifh, dfh,
1674 alwayscache=bool(addrevisioncb))
1683 alwayscache=bool(addrevisioncb),
1684 raw=True)
1675 1685
1676 1686 if addrevisioncb:
1677 1687 addrevisioncb(self, chain)
1678 1688
1679 1689 if not dfh and not self._inline:
1680 1690 # addrevision switched from inline to conventional
1681 1691 # reopen the index
1682 1692 ifh.close()
1683 1693 dfh = self.opener(self.datafile, "a+")
1684 1694 ifh = self.opener(self.indexfile, "a+",
1685 1695 checkambig=self._checkambig)
1686 1696 finally:
1687 1697 if dfh:
1688 1698 dfh.close()
1689 1699 ifh.close()
1690 1700
1691 1701 return content
1692 1702
1693 1703 def iscensored(self, rev):
1694 1704 """Check if a file revision is censored."""
1695 1705 return False
1696 1706
1697 1707 def _peek_iscensored(self, baserev, delta, flush):
1698 1708 """Quickly check if a delta produces a censored revision."""
1699 1709 return False
1700 1710
1701 1711 def getstrippoint(self, minlink):
1702 1712 """find the minimum rev that must be stripped to strip the linkrev
1703 1713
1704 1714 Returns a tuple containing the minimum rev and a set of all revs that
1705 1715 have linkrevs that will be broken by this strip.
1706 1716 """
1707 1717 brokenrevs = set()
1708 1718 strippoint = len(self)
1709 1719
1710 1720 heads = {}
1711 1721 futurelargelinkrevs = set()
1712 1722 for head in self.headrevs():
1713 1723 headlinkrev = self.linkrev(head)
1714 1724 heads[head] = headlinkrev
1715 1725 if headlinkrev >= minlink:
1716 1726 futurelargelinkrevs.add(headlinkrev)
1717 1727
1718 1728 # This algorithm involves walking down the rev graph, starting at the
1719 1729 # heads. Since the revs are topologically sorted according to linkrev,
1720 1730 # once all head linkrevs are below the minlink, we know there are
1721 1731 # no more revs that could have a linkrev greater than minlink.
1722 1732 # So we can stop walking.
1723 1733 while futurelargelinkrevs:
1724 1734 strippoint -= 1
1725 1735 linkrev = heads.pop(strippoint)
1726 1736
1727 1737 if linkrev < minlink:
1728 1738 brokenrevs.add(strippoint)
1729 1739 else:
1730 1740 futurelargelinkrevs.remove(linkrev)
1731 1741
1732 1742 for p in self.parentrevs(strippoint):
1733 1743 if p != nullrev:
1734 1744 plinkrev = self.linkrev(p)
1735 1745 heads[p] = plinkrev
1736 1746 if plinkrev >= minlink:
1737 1747 futurelargelinkrevs.add(plinkrev)
1738 1748
1739 1749 return strippoint, brokenrevs
1740 1750
1741 1751 def strip(self, minlink, transaction):
1742 1752 """truncate the revlog on the first revision with a linkrev >= minlink
1743 1753
1744 1754 This function is called when we're stripping revision minlink and
1745 1755 its descendants from the repository.
1746 1756
1747 1757 We have to remove all revisions with linkrev >= minlink, because
1748 1758 the equivalent changelog revisions will be renumbered after the
1749 1759 strip.
1750 1760
1751 1761 So we truncate the revlog on the first of these revisions, and
1752 1762 trust that the caller has saved the revisions that shouldn't be
1753 1763 removed and that it'll re-add them after this truncation.
1754 1764 """
1755 1765 if len(self) == 0:
1756 1766 return
1757 1767
1758 1768 rev, _ = self.getstrippoint(minlink)
1759 1769 if rev == len(self):
1760 1770 return
1761 1771
1762 1772 # first truncate the files on disk
1763 1773 end = self.start(rev)
1764 1774 if not self._inline:
1765 1775 transaction.add(self.datafile, end)
1766 1776 end = rev * self._io.size
1767 1777 else:
1768 1778 end += rev * self._io.size
1769 1779
1770 1780 transaction.add(self.indexfile, end)
1771 1781
1772 1782 # then reset internal state in memory to forget those revisions
1773 1783 self._cache = None
1774 1784 self._chaininfocache = {}
1775 1785 self._chunkclear()
1776 1786 for x in xrange(rev, len(self)):
1777 1787 del self.nodemap[self.node(x)]
1778 1788
1779 1789 del self.index[rev:-1]
1780 1790
1781 1791 def checksize(self):
1782 1792 expected = 0
1783 1793 if len(self):
1784 1794 expected = max(0, self.end(len(self) - 1))
1785 1795
1786 1796 try:
1787 1797 f = self.opener(self.datafile)
1788 1798 f.seek(0, 2)
1789 1799 actual = f.tell()
1790 1800 f.close()
1791 1801 dd = actual - expected
1792 1802 except IOError as inst:
1793 1803 if inst.errno != errno.ENOENT:
1794 1804 raise
1795 1805 dd = 0
1796 1806
1797 1807 try:
1798 1808 f = self.opener(self.indexfile)
1799 1809 f.seek(0, 2)
1800 1810 actual = f.tell()
1801 1811 f.close()
1802 1812 s = self._io.size
1803 1813 i = max(0, actual // s)
1804 1814 di = actual - (i * s)
1805 1815 if self._inline:
1806 1816 databytes = 0
1807 1817 for r in self:
1808 1818 databytes += max(0, self.length(r))
1809 1819 dd = 0
1810 1820 di = actual - len(self) * s - databytes
1811 1821 except IOError as inst:
1812 1822 if inst.errno != errno.ENOENT:
1813 1823 raise
1814 1824 di = 0
1815 1825
1816 1826 return (dd, di)
1817 1827
1818 1828 def files(self):
1819 1829 res = [self.indexfile]
1820 1830 if not self._inline:
1821 1831 res.append(self.datafile)
1822 1832 return res
@@ -1,260 +1,260 b''
1 1 # unionrepo.py - repository class for viewing union of repository changesets
2 2 #
3 3 # Derived from bundlerepo.py
4 4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Repository class for "in-memory pull" of one local repository to another,
11 11 allowing operations like diff and log with revsets.
12 12 """
13 13
14 14 from __future__ import absolute_import
15 15
16 16 from .i18n import _
17 17 from .node import nullid
18 18
19 19 from . import (
20 20 changelog,
21 21 cmdutil,
22 22 error,
23 23 filelog,
24 24 localrepo,
25 25 manifest,
26 26 mdiff,
27 27 pathutil,
28 28 pycompat,
29 29 revlog,
30 30 scmutil,
31 31 util,
32 32 )
33 33
34 34 class unionrevlog(revlog.revlog):
35 35 def __init__(self, opener, indexfile, revlog2, linkmapper):
36 36 # How it works:
37 37 # To retrieve a revision, we just need to know the node id so we can
38 38 # look it up in revlog2.
39 39 #
40 40 # To differentiate a rev in the second revlog from a rev in the revlog,
41 41 # we check revision against repotiprev.
42 42 opener = scmutil.readonlyvfs(opener)
43 43 revlog.revlog.__init__(self, opener, indexfile)
44 44 self.revlog2 = revlog2
45 45
46 46 n = len(self)
47 47 self.repotiprev = n - 1
48 48 self.bundlerevs = set() # used by 'bundle()' revset expression
49 49 for rev2 in self.revlog2:
50 50 rev = self.revlog2.index[rev2]
51 51 # rev numbers - in revlog2, very different from self.rev
52 52 _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
53 53 flags = _start & 0xFFFF
54 54
55 55 if linkmapper is None: # link is to same revlog
56 56 assert linkrev == rev2 # we never link back
57 57 link = n
58 58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
59 59 link = linkmapper(linkrev)
60 60
61 61 if linkmapper is not None: # link is to same revlog
62 62 base = linkmapper(base)
63 63
64 64 if node in self.nodemap:
65 65 # this happens for the common revlog revisions
66 66 self.bundlerevs.add(self.nodemap[node])
67 67 continue
68 68
69 69 p1node = self.revlog2.node(p1rev)
70 70 p2node = self.revlog2.node(p2rev)
71 71
72 72 e = (flags, None, None, base,
73 73 link, self.rev(p1node), self.rev(p2node), node)
74 74 self.index.insert(-1, e)
75 75 self.nodemap[node] = n
76 76 self.bundlerevs.add(n)
77 77 n += 1
78 78
79 79 def _chunk(self, rev):
80 80 if rev <= self.repotiprev:
81 81 return revlog.revlog._chunk(self, rev)
82 82 return self.revlog2._chunk(self.node(rev))
83 83
84 84 def revdiff(self, rev1, rev2):
85 85 """return or calculate a delta between two revisions"""
86 86 if rev1 > self.repotiprev and rev2 > self.repotiprev:
87 87 return self.revlog2.revdiff(
88 88 self.revlog2.rev(self.node(rev1)),
89 89 self.revlog2.rev(self.node(rev2)))
90 90 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
91 91 return self.baserevdiff(rev1, rev2)
92 92
93 93 return mdiff.textdiff(self.revision(self.node(rev1)),
94 94 self.revision(self.node(rev2)))
95 95
96 def revision(self, nodeorrev):
96 def revision(self, nodeorrev, raw=False):
97 97 """return an uncompressed revision of a given node or revision
98 98 number.
99 99 """
100 100 if isinstance(nodeorrev, int):
101 101 rev = nodeorrev
102 102 node = self.node(rev)
103 103 else:
104 104 node = nodeorrev
105 105 rev = self.rev(node)
106 106
107 107 if node == nullid:
108 108 return ""
109 109
110 110 if rev > self.repotiprev:
111 111 text = self.revlog2.revision(node)
112 112 self._cache = (node, rev, text)
113 113 else:
114 114 text = self.baserevision(rev)
115 115 # already cached
116 116 return text
117 117
118 118 def baserevision(self, nodeorrev):
119 119 # Revlog subclasses may override 'revision' method to modify format of
120 120 # content retrieved from revlog. To use unionrevlog with such class one
121 121 # needs to override 'baserevision' and make more specific call here.
122 122 return revlog.revlog.revision(self, nodeorrev)
123 123
124 124 def baserevdiff(self, rev1, rev2):
125 125 # Exists for the same purpose as baserevision.
126 126 return revlog.revlog.revdiff(self, rev1, rev2)
127 127
128 128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 129 raise NotImplementedError
130 130 def addgroup(self, revs, linkmapper, transaction):
131 131 raise NotImplementedError
132 132 def strip(self, rev, minlink):
133 133 raise NotImplementedError
134 134 def checksize(self):
135 135 raise NotImplementedError
136 136
137 137 class unionchangelog(unionrevlog, changelog.changelog):
138 138 def __init__(self, opener, opener2):
139 139 changelog.changelog.__init__(self, opener)
140 140 linkmapper = None
141 141 changelog2 = changelog.changelog(opener2)
142 142 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
143 143 linkmapper)
144 144
145 145 def baserevision(self, nodeorrev):
146 146 # Although changelog doesn't override 'revision' method, some extensions
147 147 # may replace this class with another that does. Same story with
148 148 # manifest and filelog classes.
149 149 return changelog.changelog.revision(self, nodeorrev)
150 150
151 151 def baserevdiff(self, rev1, rev2):
152 152 return changelog.changelog.revdiff(self, rev1, rev2)
153 153
154 154 class unionmanifest(unionrevlog, manifest.manifestrevlog):
155 155 def __init__(self, opener, opener2, linkmapper):
156 156 manifest.manifestrevlog.__init__(self, opener)
157 157 manifest2 = manifest.manifestrevlog(opener2)
158 158 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
159 159 linkmapper)
160 160
161 161 def baserevision(self, nodeorrev):
162 162 return manifest.manifestrevlog.revision(self, nodeorrev)
163 163
164 164 def baserevdiff(self, rev1, rev2):
165 165 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
166 166
167 167 class unionfilelog(unionrevlog, filelog.filelog):
168 168 def __init__(self, opener, path, opener2, linkmapper, repo):
169 169 filelog.filelog.__init__(self, opener, path)
170 170 filelog2 = filelog.filelog(opener2, path)
171 171 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
172 172 linkmapper)
173 173 self._repo = repo
174 174
175 175 def baserevision(self, nodeorrev):
176 176 return filelog.filelog.revision(self, nodeorrev)
177 177
178 178 def baserevdiff(self, rev1, rev2):
179 179 return filelog.filelog.revdiff(self, rev1, rev2)
180 180
181 181 def iscensored(self, rev):
182 182 """Check if a revision is censored."""
183 183 if rev <= self.repotiprev:
184 184 return filelog.filelog.iscensored(self, rev)
185 185 node = self.node(rev)
186 186 return self.revlog2.iscensored(self.revlog2.rev(node))
187 187
188 188 class unionpeer(localrepo.localpeer):
189 189 def canpush(self):
190 190 return False
191 191
192 192 class unionrepository(localrepo.localrepository):
193 193 def __init__(self, ui, path, path2):
194 194 localrepo.localrepository.__init__(self, ui, path)
195 195 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
196 196
197 197 self._url = 'union:%s+%s' % (util.expandpath(path),
198 198 util.expandpath(path2))
199 199 self.repo2 = localrepo.localrepository(ui, path2)
200 200
201 201 @localrepo.unfilteredpropertycache
202 202 def changelog(self):
203 203 return unionchangelog(self.svfs, self.repo2.svfs)
204 204
205 205 def _clrev(self, rev2):
206 206 """map from repo2 changelog rev to temporary rev in self.changelog"""
207 207 node = self.repo2.changelog.node(rev2)
208 208 return self.changelog.rev(node)
209 209
210 210 def _constructmanifest(self):
211 211 return unionmanifest(self.svfs, self.repo2.svfs,
212 212 self.unfiltered()._clrev)
213 213
214 214 def url(self):
215 215 return self._url
216 216
217 217 def file(self, f):
218 218 return unionfilelog(self.svfs, f, self.repo2.svfs,
219 219 self.unfiltered()._clrev, self)
220 220
221 221 def close(self):
222 222 self.repo2.close()
223 223
224 224 def cancopy(self):
225 225 return False
226 226
227 227 def peer(self):
228 228 return unionpeer(self)
229 229
230 230 def getcwd(self):
231 231 return pycompat.getcwd() # always outside the repo
232 232
233 233 def instance(ui, path, create):
234 234 if create:
235 235 raise error.Abort(_('cannot create new union repository'))
236 236 parentpath = ui.config("bundle", "mainreporoot", "")
237 237 if not parentpath:
238 238 # try to find the correct path to the working directory repo
239 239 parentpath = cmdutil.findrepo(pycompat.getcwd())
240 240 if parentpath is None:
241 241 parentpath = ''
242 242 if parentpath:
243 243 # Try to make the full path relative so we get a nice, short URL.
244 244 # In particular, we don't want temp dir names in test outputs.
245 245 cwd = pycompat.getcwd()
246 246 if parentpath == cwd:
247 247 parentpath = ''
248 248 else:
249 249 cwd = pathutil.normasprefix(cwd)
250 250 if parentpath.startswith(cwd):
251 251 parentpath = parentpath[len(cwd):]
252 252 if path.startswith('union:'):
253 253 s = path.split(":", 1)[1].split("+", 1)
254 254 if len(s) == 1:
255 255 repopath, repopath2 = parentpath, s[0]
256 256 else:
257 257 repopath, repopath2 = s
258 258 else:
259 259 repopath, repopath2 = parentpath, path
260 260 return unionrepository(ui, repopath, repopath2)
General Comments 0
You need to be logged in to leave comments. Login now