##// END OF EJS Templates
Improve some docstrings relating to changegroups and prepush().
Greg Ward -
r9437:1c4e4004 default
parent child Browse files
Show More
@@ -1,140 +1,142 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, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import util
10 10 import struct, os, bz2, zlib, tempfile
11 11
12 12 def getchunk(source):
13 """get a chunk from a changegroup"""
13 """return the next chunk from changegroup 'source' as a string"""
14 14 d = source.read(4)
15 15 if not d:
16 16 return ""
17 17 l = struct.unpack(">l", d)[0]
18 18 if l <= 4:
19 19 return ""
20 20 d = source.read(l - 4)
21 21 if len(d) < l - 4:
22 22 raise util.Abort(_("premature EOF reading chunk"
23 23 " (got %d bytes, expected %d)")
24 24 % (len(d), l - 4))
25 25 return d
26 26
27 27 def chunkiter(source):
28 """iterate through the chunks in source"""
28 """iterate through the chunks in source, yielding a sequence of chunks
29 (strings)"""
29 30 while 1:
30 31 c = getchunk(source)
31 32 if not c:
32 33 break
33 34 yield c
34 35
35 36 def chunkheader(length):
36 """build a changegroup chunk header"""
37 """return a changegroup chunk header (string)"""
37 38 return struct.pack(">l", length + 4)
38 39
39 40 def closechunk():
41 """return a changegroup chunk header (string) for a zero-length chunk"""
40 42 return struct.pack(">l", 0)
41 43
42 44 class nocompress(object):
43 45 def compress(self, x):
44 46 return x
45 47 def flush(self):
46 48 return ""
47 49
48 50 bundletypes = {
49 51 "": ("", nocompress),
50 52 "HG10UN": ("HG10UN", nocompress),
51 53 "HG10BZ": ("HG10", lambda: bz2.BZ2Compressor()),
52 54 "HG10GZ": ("HG10GZ", lambda: zlib.compressobj()),
53 55 }
54 56
55 57 # hgweb uses this list to communicate its preferred type
56 58 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
57 59
58 60 def writebundle(cg, filename, bundletype):
59 61 """Write a bundle file and return its filename.
60 62
61 63 Existing files will not be overwritten.
62 64 If no filename is specified, a temporary file is created.
63 65 bz2 compression can be turned off.
64 66 The bundle file will be deleted in case of errors.
65 67 """
66 68
67 69 fh = None
68 70 cleanup = None
69 71 try:
70 72 if filename:
71 73 fh = open(filename, "wb")
72 74 else:
73 75 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
74 76 fh = os.fdopen(fd, "wb")
75 77 cleanup = filename
76 78
77 79 header, compressor = bundletypes[bundletype]
78 80 fh.write(header)
79 81 z = compressor()
80 82
81 83 # parse the changegroup data, otherwise we will block
82 84 # in case of sshrepo because we don't know the end of the stream
83 85
84 86 # an empty chunkiter is the end of the changegroup
85 87 # a changegroup has at least 2 chunkiters (changelog and manifest).
86 88 # after that, an empty chunkiter is the end of the changegroup
87 89 empty = False
88 90 count = 0
89 91 while not empty or count <= 2:
90 92 empty = True
91 93 count += 1
92 94 for chunk in chunkiter(cg):
93 95 empty = False
94 96 fh.write(z.compress(chunkheader(len(chunk))))
95 97 pos = 0
96 98 while pos < len(chunk):
97 99 next = pos + 2**20
98 100 fh.write(z.compress(chunk[pos:next]))
99 101 pos = next
100 102 fh.write(z.compress(closechunk()))
101 103 fh.write(z.flush())
102 104 cleanup = None
103 105 return filename
104 106 finally:
105 107 if fh is not None:
106 108 fh.close()
107 109 if cleanup is not None:
108 110 os.unlink(cleanup)
109 111
110 112 def unbundle(header, fh):
111 113 if header == 'HG10UN':
112 114 return fh
113 115 elif not header.startswith('HG'):
114 116 # old client with uncompressed bundle
115 117 def generator(f):
116 118 yield header
117 119 for chunk in f:
118 120 yield chunk
119 121 elif header == 'HG10GZ':
120 122 def generator(f):
121 123 zd = zlib.decompressobj()
122 124 for chunk in f:
123 125 yield zd.decompress(chunk)
124 126 elif header == 'HG10BZ':
125 127 def generator(f):
126 128 zd = bz2.BZ2Decompressor()
127 129 zd.decompress("BZ")
128 130 for chunk in util.filechunkiter(f, 4096):
129 131 yield zd.decompress(chunk)
130 132 return util.chunkbuffer(generator(fh))
131 133
132 134 def readbundle(fh, fname):
133 135 header = fh.read(6)
134 136 if not header.startswith('HG'):
135 137 raise util.Abort(_('%s: not a Mercurial bundle file') % fname)
136 138 if not header.startswith('HG10'):
137 139 raise util.Abort(_('%s: unknown bundle version') % fname)
138 140 elif header not in bundletypes:
139 141 raise util.Abort(_('%s: unknown bundle compression type') % fname)
140 142 return unbundle(header, fh)
@@ -1,2156 +1,2165 b''
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 from node import bin, hex, nullid, nullrev, short
9 9 from i18n import _
10 10 import repo, changegroup, subrepo
11 11 import changelog, dirstate, filelog, manifest, context
12 12 import lock, transaction, store, encoding
13 13 import util, extensions, hook, error
14 14 import match as match_
15 15 import merge as merge_
16 16 import tags as tags_
17 17 from lock import release
18 18 import weakref, stat, errno, os, time, inspect
19 19 propertycache = util.propertycache
20 20
21 21 class localrepository(repo.repository):
22 22 capabilities = set(('lookup', 'changegroupsubset', 'branchmap'))
23 23 supported = set('revlogv1 store fncache shared'.split())
24 24
25 25 def __init__(self, baseui, path=None, create=0):
26 26 repo.repository.__init__(self)
27 27 self.root = os.path.realpath(path)
28 28 self.path = os.path.join(self.root, ".hg")
29 29 self.origroot = path
30 30 self.opener = util.opener(self.path)
31 31 self.wopener = util.opener(self.root)
32 32 self.baseui = baseui
33 33 self.ui = baseui.copy()
34 34
35 35 try:
36 36 self.ui.readconfig(self.join("hgrc"), self.root)
37 37 extensions.loadall(self.ui)
38 38 except IOError:
39 39 pass
40 40
41 41 if not os.path.isdir(self.path):
42 42 if create:
43 43 if not os.path.exists(path):
44 44 os.mkdir(path)
45 45 os.mkdir(self.path)
46 46 requirements = ["revlogv1"]
47 47 if self.ui.configbool('format', 'usestore', True):
48 48 os.mkdir(os.path.join(self.path, "store"))
49 49 requirements.append("store")
50 50 if self.ui.configbool('format', 'usefncache', True):
51 51 requirements.append("fncache")
52 52 # create an invalid changelog
53 53 self.opener("00changelog.i", "a").write(
54 54 '\0\0\0\2' # represents revlogv2
55 55 ' dummy changelog to prevent using the old repo layout'
56 56 )
57 57 reqfile = self.opener("requires", "w")
58 58 for r in requirements:
59 59 reqfile.write("%s\n" % r)
60 60 reqfile.close()
61 61 else:
62 62 raise error.RepoError(_("repository %s not found") % path)
63 63 elif create:
64 64 raise error.RepoError(_("repository %s already exists") % path)
65 65 else:
66 66 # find requirements
67 67 requirements = set()
68 68 try:
69 69 requirements = set(self.opener("requires").read().splitlines())
70 70 except IOError, inst:
71 71 if inst.errno != errno.ENOENT:
72 72 raise
73 73 for r in requirements - self.supported:
74 74 raise error.RepoError(_("requirement '%s' not supported") % r)
75 75
76 76 self.sharedpath = self.path
77 77 try:
78 78 s = os.path.realpath(self.opener("sharedpath").read())
79 79 if not os.path.exists(s):
80 80 raise error.RepoError(
81 81 _('.hg/sharedpath points to nonexistent directory %s') % s)
82 82 self.sharedpath = s
83 83 except IOError, inst:
84 84 if inst.errno != errno.ENOENT:
85 85 raise
86 86
87 87 self.store = store.store(requirements, self.sharedpath, util.opener)
88 88 self.spath = self.store.path
89 89 self.sopener = self.store.opener
90 90 self.sjoin = self.store.join
91 91 self.opener.createmode = self.store.createmode
92 92
93 93 # These two define the set of tags for this repository. _tags
94 94 # maps tag name to node; _tagtypes maps tag name to 'global' or
95 95 # 'local'. (Global tags are defined by .hgtags across all
96 96 # heads, and local tags are defined in .hg/localtags.) They
97 97 # constitute the in-memory cache of tags.
98 98 self._tags = None
99 99 self._tagtypes = None
100 100
101 101 self.branchcache = None
102 102 self._ubranchcache = None # UTF-8 version of branchcache
103 103 self._branchcachetip = None
104 104 self.nodetagscache = None
105 105 self.filterpats = {}
106 106 self._datafilters = {}
107 107 self._transref = self._lockref = self._wlockref = None
108 108
109 109 @propertycache
110 110 def changelog(self):
111 111 c = changelog.changelog(self.sopener)
112 112 if 'HG_PENDING' in os.environ:
113 113 p = os.environ['HG_PENDING']
114 114 if p.startswith(self.root):
115 115 c.readpending('00changelog.i.a')
116 116 self.sopener.defversion = c.version
117 117 return c
118 118
119 119 @propertycache
120 120 def manifest(self):
121 121 return manifest.manifest(self.sopener)
122 122
123 123 @propertycache
124 124 def dirstate(self):
125 125 return dirstate.dirstate(self.opener, self.ui, self.root)
126 126
127 127 def __getitem__(self, changeid):
128 128 if changeid is None:
129 129 return context.workingctx(self)
130 130 return context.changectx(self, changeid)
131 131
132 132 def __nonzero__(self):
133 133 return True
134 134
135 135 def __len__(self):
136 136 return len(self.changelog)
137 137
138 138 def __iter__(self):
139 139 for i in xrange(len(self)):
140 140 yield i
141 141
142 142 def url(self):
143 143 return 'file:' + self.root
144 144
145 145 def hook(self, name, throw=False, **args):
146 146 return hook.hook(self.ui, self, name, throw, **args)
147 147
148 148 tag_disallowed = ':\r\n'
149 149
150 150 def _tag(self, names, node, message, local, user, date, extra={}):
151 151 if isinstance(names, str):
152 152 allchars = names
153 153 names = (names,)
154 154 else:
155 155 allchars = ''.join(names)
156 156 for c in self.tag_disallowed:
157 157 if c in allchars:
158 158 raise util.Abort(_('%r cannot be used in a tag name') % c)
159 159
160 160 for name in names:
161 161 self.hook('pretag', throw=True, node=hex(node), tag=name,
162 162 local=local)
163 163
164 164 def writetags(fp, names, munge, prevtags):
165 165 fp.seek(0, 2)
166 166 if prevtags and prevtags[-1] != '\n':
167 167 fp.write('\n')
168 168 for name in names:
169 169 m = munge and munge(name) or name
170 170 if self._tagtypes and name in self._tagtypes:
171 171 old = self._tags.get(name, nullid)
172 172 fp.write('%s %s\n' % (hex(old), m))
173 173 fp.write('%s %s\n' % (hex(node), m))
174 174 fp.close()
175 175
176 176 prevtags = ''
177 177 if local:
178 178 try:
179 179 fp = self.opener('localtags', 'r+')
180 180 except IOError:
181 181 fp = self.opener('localtags', 'a')
182 182 else:
183 183 prevtags = fp.read()
184 184
185 185 # local tags are stored in the current charset
186 186 writetags(fp, names, None, prevtags)
187 187 for name in names:
188 188 self.hook('tag', node=hex(node), tag=name, local=local)
189 189 return
190 190
191 191 try:
192 192 fp = self.wfile('.hgtags', 'rb+')
193 193 except IOError:
194 194 fp = self.wfile('.hgtags', 'ab')
195 195 else:
196 196 prevtags = fp.read()
197 197
198 198 # committed tags are stored in UTF-8
199 199 writetags(fp, names, encoding.fromlocal, prevtags)
200 200
201 201 if '.hgtags' not in self.dirstate:
202 202 self.add(['.hgtags'])
203 203
204 204 m = match_.exact(self.root, '', ['.hgtags'])
205 205 tagnode = self.commit(message, user, date, extra=extra, match=m)
206 206
207 207 for name in names:
208 208 self.hook('tag', node=hex(node), tag=name, local=local)
209 209
210 210 return tagnode
211 211
212 212 def tag(self, names, node, message, local, user, date):
213 213 '''tag a revision with one or more symbolic names.
214 214
215 215 names is a list of strings or, when adding a single tag, names may be a
216 216 string.
217 217
218 218 if local is True, the tags are stored in a per-repository file.
219 219 otherwise, they are stored in the .hgtags file, and a new
220 220 changeset is committed with the change.
221 221
222 222 keyword arguments:
223 223
224 224 local: whether to store tags in non-version-controlled file
225 225 (default False)
226 226
227 227 message: commit message to use if committing
228 228
229 229 user: name of user to use if committing
230 230
231 231 date: date tuple to use if committing'''
232 232
233 233 for x in self.status()[:5]:
234 234 if '.hgtags' in x:
235 235 raise util.Abort(_('working copy of .hgtags is changed '
236 236 '(please commit .hgtags manually)'))
237 237
238 238 self.tags() # instantiate the cache
239 239 self._tag(names, node, message, local, user, date)
240 240
241 241 def tags(self):
242 242 '''return a mapping of tag to node'''
243 243 if self._tags is None:
244 244 (self._tags, self._tagtypes) = self._findtags()
245 245
246 246 return self._tags
247 247
248 248 def _findtags(self):
249 249 '''Do the hard work of finding tags. Return a pair of dicts
250 250 (tags, tagtypes) where tags maps tag name to node, and tagtypes
251 251 maps tag name to a string like \'global\' or \'local\'.
252 252 Subclasses or extensions are free to add their own tags, but
253 253 should be aware that the returned dicts will be retained for the
254 254 duration of the localrepo object.'''
255 255
256 256 # XXX what tagtype should subclasses/extensions use? Currently
257 257 # mq and bookmarks add tags, but do not set the tagtype at all.
258 258 # Should each extension invent its own tag type? Should there
259 259 # be one tagtype for all such "virtual" tags? Or is the status
260 260 # quo fine?
261 261
262 262 alltags = {} # map tag name to (node, hist)
263 263 tagtypes = {}
264 264
265 265 tags_.findglobaltags(self.ui, self, alltags, tagtypes)
266 266 tags_.readlocaltags(self.ui, self, alltags, tagtypes)
267 267
268 268 # Build the return dicts. Have to re-encode tag names because
269 269 # the tags module always uses UTF-8 (in order not to lose info
270 270 # writing to the cache), but the rest of Mercurial wants them in
271 271 # local encoding.
272 272 tags = {}
273 273 for (name, (node, hist)) in alltags.iteritems():
274 274 if node != nullid:
275 275 tags[encoding.tolocal(name)] = node
276 276 tags['tip'] = self.changelog.tip()
277 277 tagtypes = dict([(encoding.tolocal(name), value)
278 278 for (name, value) in tagtypes.iteritems()])
279 279 return (tags, tagtypes)
280 280
281 281 def tagtype(self, tagname):
282 282 '''
283 283 return the type of the given tag. result can be:
284 284
285 285 'local' : a local tag
286 286 'global' : a global tag
287 287 None : tag does not exist
288 288 '''
289 289
290 290 self.tags()
291 291
292 292 return self._tagtypes.get(tagname)
293 293
294 294 def tagslist(self):
295 295 '''return a list of tags ordered by revision'''
296 296 l = []
297 297 for t, n in self.tags().iteritems():
298 298 try:
299 299 r = self.changelog.rev(n)
300 300 except:
301 301 r = -2 # sort to the beginning of the list if unknown
302 302 l.append((r, t, n))
303 303 return [(t, n) for r, t, n in sorted(l)]
304 304
305 305 def nodetags(self, node):
306 306 '''return the tags associated with a node'''
307 307 if not self.nodetagscache:
308 308 self.nodetagscache = {}
309 309 for t, n in self.tags().iteritems():
310 310 self.nodetagscache.setdefault(n, []).append(t)
311 311 return self.nodetagscache.get(node, [])
312 312
313 313 def _branchtags(self, partial, lrev):
314 314 # TODO: rename this function?
315 315 tiprev = len(self) - 1
316 316 if lrev != tiprev:
317 317 self._updatebranchcache(partial, lrev+1, tiprev+1)
318 318 self._writebranchcache(partial, self.changelog.tip(), tiprev)
319 319
320 320 return partial
321 321
322 322 def branchmap(self):
323 323 tip = self.changelog.tip()
324 324 if self.branchcache is not None and self._branchcachetip == tip:
325 325 return self.branchcache
326 326
327 327 oldtip = self._branchcachetip
328 328 self._branchcachetip = tip
329 329 if self.branchcache is None:
330 330 self.branchcache = {} # avoid recursion in changectx
331 331 else:
332 332 self.branchcache.clear() # keep using the same dict
333 333 if oldtip is None or oldtip not in self.changelog.nodemap:
334 334 partial, last, lrev = self._readbranchcache()
335 335 else:
336 336 lrev = self.changelog.rev(oldtip)
337 337 partial = self._ubranchcache
338 338
339 339 self._branchtags(partial, lrev)
340 340 # this private cache holds all heads (not just tips)
341 341 self._ubranchcache = partial
342 342
343 343 # the branch cache is stored on disk as UTF-8, but in the local
344 344 # charset internally
345 345 for k, v in partial.iteritems():
346 346 self.branchcache[encoding.tolocal(k)] = v
347 347 return self.branchcache
348 348
349 349
350 350 def branchtags(self):
351 351 '''return a dict where branch names map to the tipmost head of
352 352 the branch, open heads come before closed'''
353 353 bt = {}
354 354 for bn, heads in self.branchmap().iteritems():
355 355 head = None
356 356 for i in range(len(heads)-1, -1, -1):
357 357 h = heads[i]
358 358 if 'close' not in self.changelog.read(h)[5]:
359 359 head = h
360 360 break
361 361 # no open heads were found
362 362 if head is None:
363 363 head = heads[-1]
364 364 bt[bn] = head
365 365 return bt
366 366
367 367
368 368 def _readbranchcache(self):
369 369 partial = {}
370 370 try:
371 371 f = self.opener("branchheads.cache")
372 372 lines = f.read().split('\n')
373 373 f.close()
374 374 except (IOError, OSError):
375 375 return {}, nullid, nullrev
376 376
377 377 try:
378 378 last, lrev = lines.pop(0).split(" ", 1)
379 379 last, lrev = bin(last), int(lrev)
380 380 if lrev >= len(self) or self[lrev].node() != last:
381 381 # invalidate the cache
382 382 raise ValueError('invalidating branch cache (tip differs)')
383 383 for l in lines:
384 384 if not l: continue
385 385 node, label = l.split(" ", 1)
386 386 partial.setdefault(label.strip(), []).append(bin(node))
387 387 except KeyboardInterrupt:
388 388 raise
389 389 except Exception, inst:
390 390 if self.ui.debugflag:
391 391 self.ui.warn(str(inst), '\n')
392 392 partial, last, lrev = {}, nullid, nullrev
393 393 return partial, last, lrev
394 394
395 395 def _writebranchcache(self, branches, tip, tiprev):
396 396 try:
397 397 f = self.opener("branchheads.cache", "w", atomictemp=True)
398 398 f.write("%s %s\n" % (hex(tip), tiprev))
399 399 for label, nodes in branches.iteritems():
400 400 for node in nodes:
401 401 f.write("%s %s\n" % (hex(node), label))
402 402 f.rename()
403 403 except (IOError, OSError):
404 404 pass
405 405
406 406 def _updatebranchcache(self, partial, start, end):
407 407 # collect new branch entries
408 408 newbranches = {}
409 409 for r in xrange(start, end):
410 410 c = self[r]
411 411 newbranches.setdefault(c.branch(), []).append(c.node())
412 412 # if older branchheads are reachable from new ones, they aren't
413 413 # really branchheads. Note checking parents is insufficient:
414 414 # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
415 415 for branch, newnodes in newbranches.iteritems():
416 416 bheads = partial.setdefault(branch, [])
417 417 bheads.extend(newnodes)
418 418 if len(bheads) < 2:
419 419 continue
420 420 newbheads = []
421 421 # starting from tip means fewer passes over reachable
422 422 while newnodes:
423 423 latest = newnodes.pop()
424 424 if latest not in bheads:
425 425 continue
426 426 minbhrev = self[min([self[bh].rev() for bh in bheads])].node()
427 427 reachable = self.changelog.reachable(latest, minbhrev)
428 428 bheads = [b for b in bheads if b not in reachable]
429 429 newbheads.insert(0, latest)
430 430 bheads.extend(newbheads)
431 431 partial[branch] = bheads
432 432
433 433 def lookup(self, key):
434 434 if isinstance(key, int):
435 435 return self.changelog.node(key)
436 436 elif key == '.':
437 437 return self.dirstate.parents()[0]
438 438 elif key == 'null':
439 439 return nullid
440 440 elif key == 'tip':
441 441 return self.changelog.tip()
442 442 n = self.changelog._match(key)
443 443 if n:
444 444 return n
445 445 if key in self.tags():
446 446 return self.tags()[key]
447 447 if key in self.branchtags():
448 448 return self.branchtags()[key]
449 449 n = self.changelog._partialmatch(key)
450 450 if n:
451 451 return n
452 452
453 453 # can't find key, check if it might have come from damaged dirstate
454 454 if key in self.dirstate.parents():
455 455 raise error.Abort(_("working directory has unknown parent '%s'!")
456 456 % short(key))
457 457 try:
458 458 if len(key) == 20:
459 459 key = hex(key)
460 460 except:
461 461 pass
462 462 raise error.RepoLookupError(_("unknown revision '%s'") % key)
463 463
464 464 def local(self):
465 465 return True
466 466
467 467 def join(self, f):
468 468 return os.path.join(self.path, f)
469 469
470 470 def wjoin(self, f):
471 471 return os.path.join(self.root, f)
472 472
473 473 def rjoin(self, f):
474 474 return os.path.join(self.root, util.pconvert(f))
475 475
476 476 def file(self, f):
477 477 if f[0] == '/':
478 478 f = f[1:]
479 479 return filelog.filelog(self.sopener, f)
480 480
481 481 def changectx(self, changeid):
482 482 return self[changeid]
483 483
484 484 def parents(self, changeid=None):
485 485 '''get list of changectxs for parents of changeid'''
486 486 return self[changeid].parents()
487 487
488 488 def filectx(self, path, changeid=None, fileid=None):
489 489 """changeid can be a changeset revision, node, or tag.
490 490 fileid can be a file revision or node."""
491 491 return context.filectx(self, path, changeid, fileid)
492 492
493 493 def getcwd(self):
494 494 return self.dirstate.getcwd()
495 495
496 496 def pathto(self, f, cwd=None):
497 497 return self.dirstate.pathto(f, cwd)
498 498
499 499 def wfile(self, f, mode='r'):
500 500 return self.wopener(f, mode)
501 501
502 502 def _link(self, f):
503 503 return os.path.islink(self.wjoin(f))
504 504
505 505 def _filter(self, filter, filename, data):
506 506 if filter not in self.filterpats:
507 507 l = []
508 508 for pat, cmd in self.ui.configitems(filter):
509 509 if cmd == '!':
510 510 continue
511 511 mf = match_.match(self.root, '', [pat])
512 512 fn = None
513 513 params = cmd
514 514 for name, filterfn in self._datafilters.iteritems():
515 515 if cmd.startswith(name):
516 516 fn = filterfn
517 517 params = cmd[len(name):].lstrip()
518 518 break
519 519 if not fn:
520 520 fn = lambda s, c, **kwargs: util.filter(s, c)
521 521 # Wrap old filters not supporting keyword arguments
522 522 if not inspect.getargspec(fn)[2]:
523 523 oldfn = fn
524 524 fn = lambda s, c, **kwargs: oldfn(s, c)
525 525 l.append((mf, fn, params))
526 526 self.filterpats[filter] = l
527 527
528 528 for mf, fn, cmd in self.filterpats[filter]:
529 529 if mf(filename):
530 530 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
531 531 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
532 532 break
533 533
534 534 return data
535 535
536 536 def adddatafilter(self, name, filter):
537 537 self._datafilters[name] = filter
538 538
539 539 def wread(self, filename):
540 540 if self._link(filename):
541 541 data = os.readlink(self.wjoin(filename))
542 542 else:
543 543 data = self.wopener(filename, 'r').read()
544 544 return self._filter("encode", filename, data)
545 545
546 546 def wwrite(self, filename, data, flags):
547 547 data = self._filter("decode", filename, data)
548 548 try:
549 549 os.unlink(self.wjoin(filename))
550 550 except OSError:
551 551 pass
552 552 if 'l' in flags:
553 553 self.wopener.symlink(data, filename)
554 554 else:
555 555 self.wopener(filename, 'w').write(data)
556 556 if 'x' in flags:
557 557 util.set_flags(self.wjoin(filename), False, True)
558 558
559 559 def wwritedata(self, filename, data):
560 560 return self._filter("decode", filename, data)
561 561
562 562 def transaction(self):
563 563 tr = self._transref and self._transref() or None
564 564 if tr and tr.running():
565 565 return tr.nest()
566 566
567 567 # abort here if the journal already exists
568 568 if os.path.exists(self.sjoin("journal")):
569 569 raise error.RepoError(_("journal already exists - run hg recover"))
570 570
571 571 # save dirstate for rollback
572 572 try:
573 573 ds = self.opener("dirstate").read()
574 574 except IOError:
575 575 ds = ""
576 576 self.opener("journal.dirstate", "w").write(ds)
577 577 self.opener("journal.branch", "w").write(self.dirstate.branch())
578 578
579 579 renames = [(self.sjoin("journal"), self.sjoin("undo")),
580 580 (self.join("journal.dirstate"), self.join("undo.dirstate")),
581 581 (self.join("journal.branch"), self.join("undo.branch"))]
582 582 tr = transaction.transaction(self.ui.warn, self.sopener,
583 583 self.sjoin("journal"),
584 584 aftertrans(renames),
585 585 self.store.createmode)
586 586 self._transref = weakref.ref(tr)
587 587 return tr
588 588
589 589 def recover(self):
590 590 lock = self.lock()
591 591 try:
592 592 if os.path.exists(self.sjoin("journal")):
593 593 self.ui.status(_("rolling back interrupted transaction\n"))
594 594 transaction.rollback(self.sopener, self.sjoin("journal"), self.ui.warn)
595 595 self.invalidate()
596 596 return True
597 597 else:
598 598 self.ui.warn(_("no interrupted transaction available\n"))
599 599 return False
600 600 finally:
601 601 lock.release()
602 602
603 603 def rollback(self):
604 604 wlock = lock = None
605 605 try:
606 606 wlock = self.wlock()
607 607 lock = self.lock()
608 608 if os.path.exists(self.sjoin("undo")):
609 609 self.ui.status(_("rolling back last transaction\n"))
610 610 transaction.rollback(self.sopener, self.sjoin("undo"), self.ui.warn)
611 611 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
612 612 try:
613 613 branch = self.opener("undo.branch").read()
614 614 self.dirstate.setbranch(branch)
615 615 except IOError:
616 616 self.ui.warn(_("Named branch could not be reset, "
617 617 "current branch still is: %s\n")
618 618 % encoding.tolocal(self.dirstate.branch()))
619 619 self.invalidate()
620 620 self.dirstate.invalidate()
621 621 self.destroyed()
622 622 else:
623 623 self.ui.warn(_("no rollback information available\n"))
624 624 finally:
625 625 release(lock, wlock)
626 626
627 627 def invalidate(self):
628 628 for a in "changelog manifest".split():
629 629 if a in self.__dict__:
630 630 delattr(self, a)
631 631 self._tags = None
632 632 self._tagtypes = None
633 633 self.nodetagscache = None
634 634 self.branchcache = None
635 635 self._ubranchcache = None
636 636 self._branchcachetip = None
637 637
638 638 def _lock(self, lockname, wait, releasefn, acquirefn, desc):
639 639 try:
640 640 l = lock.lock(lockname, 0, releasefn, desc=desc)
641 641 except error.LockHeld, inst:
642 642 if not wait:
643 643 raise
644 644 self.ui.warn(_("waiting for lock on %s held by %r\n") %
645 645 (desc, inst.locker))
646 646 # default to 600 seconds timeout
647 647 l = lock.lock(lockname, int(self.ui.config("ui", "timeout", "600")),
648 648 releasefn, desc=desc)
649 649 if acquirefn:
650 650 acquirefn()
651 651 return l
652 652
653 653 def lock(self, wait=True):
654 654 '''Lock the repository store (.hg/store) and return a weak reference
655 655 to the lock. Use this before modifying the store (e.g. committing or
656 656 stripping). If you are opening a transaction, get a lock as well.)'''
657 657 l = self._lockref and self._lockref()
658 658 if l is not None and l.held:
659 659 l.lock()
660 660 return l
661 661
662 662 l = self._lock(self.sjoin("lock"), wait, None, self.invalidate,
663 663 _('repository %s') % self.origroot)
664 664 self._lockref = weakref.ref(l)
665 665 return l
666 666
667 667 def wlock(self, wait=True):
668 668 '''Lock the non-store parts of the repository (everything under
669 669 .hg except .hg/store) and return a weak reference to the lock.
670 670 Use this before modifying files in .hg.'''
671 671 l = self._wlockref and self._wlockref()
672 672 if l is not None and l.held:
673 673 l.lock()
674 674 return l
675 675
676 676 l = self._lock(self.join("wlock"), wait, self.dirstate.write,
677 677 self.dirstate.invalidate, _('working directory of %s') %
678 678 self.origroot)
679 679 self._wlockref = weakref.ref(l)
680 680 return l
681 681
682 682 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
683 683 """
684 684 commit an individual file as part of a larger transaction
685 685 """
686 686
687 687 fname = fctx.path()
688 688 text = fctx.data()
689 689 flog = self.file(fname)
690 690 fparent1 = manifest1.get(fname, nullid)
691 691 fparent2 = fparent2o = manifest2.get(fname, nullid)
692 692
693 693 meta = {}
694 694 copy = fctx.renamed()
695 695 if copy and copy[0] != fname:
696 696 # Mark the new revision of this file as a copy of another
697 697 # file. This copy data will effectively act as a parent
698 698 # of this new revision. If this is a merge, the first
699 699 # parent will be the nullid (meaning "look up the copy data")
700 700 # and the second one will be the other parent. For example:
701 701 #
702 702 # 0 --- 1 --- 3 rev1 changes file foo
703 703 # \ / rev2 renames foo to bar and changes it
704 704 # \- 2 -/ rev3 should have bar with all changes and
705 705 # should record that bar descends from
706 706 # bar in rev2 and foo in rev1
707 707 #
708 708 # this allows this merge to succeed:
709 709 #
710 710 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
711 711 # \ / merging rev3 and rev4 should use bar@rev2
712 712 # \- 2 --- 4 as the merge base
713 713 #
714 714
715 715 cfname = copy[0]
716 716 crev = manifest1.get(cfname)
717 717 newfparent = fparent2
718 718
719 719 if manifest2: # branch merge
720 720 if fparent2 == nullid or crev is None: # copied on remote side
721 721 if cfname in manifest2:
722 722 crev = manifest2[cfname]
723 723 newfparent = fparent1
724 724
725 725 # find source in nearest ancestor if we've lost track
726 726 if not crev:
727 727 self.ui.debug(_(" %s: searching for copy revision for %s\n") %
728 728 (fname, cfname))
729 729 for ancestor in self['.'].ancestors():
730 730 if cfname in ancestor:
731 731 crev = ancestor[cfname].filenode()
732 732 break
733 733
734 734 self.ui.debug(_(" %s: copy %s:%s\n") % (fname, cfname, hex(crev)))
735 735 meta["copy"] = cfname
736 736 meta["copyrev"] = hex(crev)
737 737 fparent1, fparent2 = nullid, newfparent
738 738 elif fparent2 != nullid:
739 739 # is one parent an ancestor of the other?
740 740 fparentancestor = flog.ancestor(fparent1, fparent2)
741 741 if fparentancestor == fparent1:
742 742 fparent1, fparent2 = fparent2, nullid
743 743 elif fparentancestor == fparent2:
744 744 fparent2 = nullid
745 745
746 746 # is the file changed?
747 747 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
748 748 changelist.append(fname)
749 749 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
750 750
751 751 # are just the flags changed during merge?
752 752 if fparent1 != fparent2o and manifest1.flags(fname) != fctx.flags():
753 753 changelist.append(fname)
754 754
755 755 return fparent1
756 756
757 757 def commit(self, text="", user=None, date=None, match=None, force=False,
758 758 editor=False, extra={}):
759 759 """Add a new revision to current repository.
760 760
761 761 Revision information is gathered from the working directory,
762 762 match can be used to filter the committed files. If editor is
763 763 supplied, it is called to get a commit message.
764 764 """
765 765
766 766 def fail(f, msg):
767 767 raise util.Abort('%s: %s' % (f, msg))
768 768
769 769 if not match:
770 770 match = match_.always(self.root, '')
771 771
772 772 if not force:
773 773 vdirs = []
774 774 match.dir = vdirs.append
775 775 match.bad = fail
776 776
777 777 wlock = self.wlock()
778 778 try:
779 779 p1, p2 = self.dirstate.parents()
780 780 wctx = self[None]
781 781
782 782 if (not force and p2 != nullid and match and
783 783 (match.files() or match.anypats())):
784 784 raise util.Abort(_('cannot partially commit a merge '
785 785 '(do not specify files or patterns)'))
786 786
787 787 changes = self.status(match=match, clean=force)
788 788 if force:
789 789 changes[0].extend(changes[6]) # mq may commit unchanged files
790 790
791 791 # check subrepos
792 792 subs = []
793 793 for s in wctx.substate:
794 794 if match(s) and wctx.sub(s).dirty():
795 795 subs.append(s)
796 796 if subs and '.hgsubstate' not in changes[0]:
797 797 changes[0].insert(0, '.hgsubstate')
798 798
799 799 # make sure all explicit patterns are matched
800 800 if not force and match.files():
801 801 matched = set(changes[0] + changes[1] + changes[2])
802 802
803 803 for f in match.files():
804 804 if f == '.' or f in matched or f in wctx.substate:
805 805 continue
806 806 if f in changes[3]: # missing
807 807 fail(f, _('file not found!'))
808 808 if f in vdirs: # visited directory
809 809 d = f + '/'
810 810 for mf in matched:
811 811 if mf.startswith(d):
812 812 break
813 813 else:
814 814 fail(f, _("no match under directory!"))
815 815 elif f not in self.dirstate:
816 816 fail(f, _("file not tracked!"))
817 817
818 818 if (not force and not extra.get("close") and p2 == nullid
819 819 and not (changes[0] or changes[1] or changes[2])
820 820 and self[None].branch() == self['.'].branch()):
821 821 return None
822 822
823 823 ms = merge_.mergestate(self)
824 824 for f in changes[0]:
825 825 if f in ms and ms[f] == 'u':
826 826 raise util.Abort(_("unresolved merge conflicts "
827 827 "(see hg resolve)"))
828 828
829 829 cctx = context.workingctx(self, (p1, p2), text, user, date,
830 830 extra, changes)
831 831 if editor:
832 832 cctx._text = editor(self, cctx, subs)
833 833
834 834 # commit subs
835 835 if subs:
836 836 state = wctx.substate.copy()
837 837 for s in subs:
838 838 self.ui.status(_('committing subrepository %s\n') % s)
839 839 sr = wctx.sub(s).commit(cctx._text, user, date)
840 840 state[s] = (state[s][0], sr)
841 841 subrepo.writestate(self, state)
842 842
843 843 ret = self.commitctx(cctx, True)
844 844
845 845 # update dirstate and mergestate
846 846 for f in changes[0] + changes[1]:
847 847 self.dirstate.normal(f)
848 848 for f in changes[2]:
849 849 self.dirstate.forget(f)
850 850 self.dirstate.setparents(ret)
851 851 ms.reset()
852 852
853 853 return ret
854 854
855 855 finally:
856 856 wlock.release()
857 857
858 858 def commitctx(self, ctx, error=False):
859 859 """Add a new revision to current repository.
860 860
861 861 Revision information is passed via the context argument.
862 862 """
863 863
864 864 tr = lock = None
865 865 removed = ctx.removed()
866 866 p1, p2 = ctx.p1(), ctx.p2()
867 867 m1 = p1.manifest().copy()
868 868 m2 = p2.manifest()
869 869 user = ctx.user()
870 870
871 871 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
872 872 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
873 873
874 874 lock = self.lock()
875 875 try:
876 876 tr = self.transaction()
877 877 trp = weakref.proxy(tr)
878 878
879 879 # check in files
880 880 new = {}
881 881 changed = []
882 882 linkrev = len(self)
883 883 for f in sorted(ctx.modified() + ctx.added()):
884 884 self.ui.note(f + "\n")
885 885 try:
886 886 fctx = ctx[f]
887 887 new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
888 888 changed)
889 889 m1.set(f, fctx.flags())
890 890 except (OSError, IOError):
891 891 if error:
892 892 self.ui.warn(_("trouble committing %s!\n") % f)
893 893 raise
894 894 else:
895 895 removed.append(f)
896 896
897 897 # update manifest
898 898 m1.update(new)
899 899 removed = [f for f in sorted(removed) if f in m1 or f in m2]
900 900 drop = [f for f in removed if f in m1]
901 901 for f in drop:
902 902 del m1[f]
903 903 mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
904 904 p2.manifestnode(), (new, drop))
905 905
906 906 # update changelog
907 907 self.changelog.delayupdate()
908 908 n = self.changelog.add(mn, changed + removed, ctx.description(),
909 909 trp, p1.node(), p2.node(),
910 910 user, ctx.date(), ctx.extra().copy())
911 911 p = lambda: self.changelog.writepending() and self.root or ""
912 912 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
913 913 parent2=xp2, pending=p)
914 914 self.changelog.finalize(trp)
915 915 tr.close()
916 916
917 917 if self.branchcache:
918 918 self.branchtags()
919 919
920 920 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
921 921 return n
922 922 finally:
923 923 del tr
924 924 lock.release()
925 925
926 926 def destroyed(self):
927 927 '''Inform the repository that nodes have been destroyed.
928 928 Intended for use by strip and rollback, so there's a common
929 929 place for anything that has to be done after destroying history.'''
930 930 # XXX it might be nice if we could take the list of destroyed
931 931 # nodes, but I don't see an easy way for rollback() to do that
932 932
933 933 # Ensure the persistent tag cache is updated. Doing it now
934 934 # means that the tag cache only has to worry about destroyed
935 935 # heads immediately after a strip/rollback. That in turn
936 936 # guarantees that "cachetip == currenttip" (comparing both rev
937 937 # and node) always means no nodes have been added or destroyed.
938 938
939 939 # XXX this is suboptimal when qrefresh'ing: we strip the current
940 940 # head, refresh the tag cache, then immediately add a new head.
941 941 # But I think doing it this way is necessary for the "instant
942 942 # tag cache retrieval" case to work.
943 943 tags_.findglobaltags(self.ui, self, {}, {})
944 944
945 945 def walk(self, match, node=None):
946 946 '''
947 947 walk recursively through the directory tree or a given
948 948 changeset, finding all files matched by the match
949 949 function
950 950 '''
951 951 return self[node].walk(match)
952 952
953 953 def status(self, node1='.', node2=None, match=None,
954 954 ignored=False, clean=False, unknown=False):
955 955 """return status of files between two nodes or node and working directory
956 956
957 957 If node1 is None, use the first dirstate parent instead.
958 958 If node2 is None, compare node1 with working directory.
959 959 """
960 960
961 961 def mfmatches(ctx):
962 962 mf = ctx.manifest().copy()
963 963 for fn in mf.keys():
964 964 if not match(fn):
965 965 del mf[fn]
966 966 return mf
967 967
968 968 if isinstance(node1, context.changectx):
969 969 ctx1 = node1
970 970 else:
971 971 ctx1 = self[node1]
972 972 if isinstance(node2, context.changectx):
973 973 ctx2 = node2
974 974 else:
975 975 ctx2 = self[node2]
976 976
977 977 working = ctx2.rev() is None
978 978 parentworking = working and ctx1 == self['.']
979 979 match = match or match_.always(self.root, self.getcwd())
980 980 listignored, listclean, listunknown = ignored, clean, unknown
981 981
982 982 # load earliest manifest first for caching reasons
983 983 if not working and ctx2.rev() < ctx1.rev():
984 984 ctx2.manifest()
985 985
986 986 if not parentworking:
987 987 def bad(f, msg):
988 988 if f not in ctx1:
989 989 self.ui.warn('%s: %s\n' % (self.dirstate.pathto(f), msg))
990 990 match.bad = bad
991 991
992 992 if working: # we need to scan the working dir
993 993 s = self.dirstate.status(match, listignored, listclean, listunknown)
994 994 cmp, modified, added, removed, deleted, unknown, ignored, clean = s
995 995
996 996 # check for any possibly clean files
997 997 if parentworking and cmp:
998 998 fixup = []
999 999 # do a full compare of any files that might have changed
1000 1000 for f in sorted(cmp):
1001 1001 if (f not in ctx1 or ctx2.flags(f) != ctx1.flags(f)
1002 1002 or ctx1[f].cmp(ctx2[f].data())):
1003 1003 modified.append(f)
1004 1004 else:
1005 1005 fixup.append(f)
1006 1006
1007 1007 if listclean:
1008 1008 clean += fixup
1009 1009
1010 1010 # update dirstate for files that are actually clean
1011 1011 if fixup:
1012 1012 try:
1013 1013 # updating the dirstate is optional
1014 1014 # so we don't wait on the lock
1015 1015 wlock = self.wlock(False)
1016 1016 try:
1017 1017 for f in fixup:
1018 1018 self.dirstate.normal(f)
1019 1019 finally:
1020 1020 wlock.release()
1021 1021 except error.LockError:
1022 1022 pass
1023 1023
1024 1024 if not parentworking:
1025 1025 mf1 = mfmatches(ctx1)
1026 1026 if working:
1027 1027 # we are comparing working dir against non-parent
1028 1028 # generate a pseudo-manifest for the working dir
1029 1029 mf2 = mfmatches(self['.'])
1030 1030 for f in cmp + modified + added:
1031 1031 mf2[f] = None
1032 1032 mf2.set(f, ctx2.flags(f))
1033 1033 for f in removed:
1034 1034 if f in mf2:
1035 1035 del mf2[f]
1036 1036 else:
1037 1037 # we are comparing two revisions
1038 1038 deleted, unknown, ignored = [], [], []
1039 1039 mf2 = mfmatches(ctx2)
1040 1040
1041 1041 modified, added, clean = [], [], []
1042 1042 for fn in mf2:
1043 1043 if fn in mf1:
1044 1044 if (mf1.flags(fn) != mf2.flags(fn) or
1045 1045 (mf1[fn] != mf2[fn] and
1046 1046 (mf2[fn] or ctx1[fn].cmp(ctx2[fn].data())))):
1047 1047 modified.append(fn)
1048 1048 elif listclean:
1049 1049 clean.append(fn)
1050 1050 del mf1[fn]
1051 1051 else:
1052 1052 added.append(fn)
1053 1053 removed = mf1.keys()
1054 1054
1055 1055 r = modified, added, removed, deleted, unknown, ignored, clean
1056 1056 [l.sort() for l in r]
1057 1057 return r
1058 1058
1059 1059 def add(self, list):
1060 1060 wlock = self.wlock()
1061 1061 try:
1062 1062 rejected = []
1063 1063 for f in list:
1064 1064 p = self.wjoin(f)
1065 1065 try:
1066 1066 st = os.lstat(p)
1067 1067 except:
1068 1068 self.ui.warn(_("%s does not exist!\n") % f)
1069 1069 rejected.append(f)
1070 1070 continue
1071 1071 if st.st_size > 10000000:
1072 1072 self.ui.warn(_("%s: files over 10MB may cause memory and"
1073 1073 " performance problems\n"
1074 1074 "(use 'hg revert %s' to unadd the file)\n")
1075 1075 % (f, f))
1076 1076 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1077 1077 self.ui.warn(_("%s not added: only files and symlinks "
1078 1078 "supported currently\n") % f)
1079 1079 rejected.append(p)
1080 1080 elif self.dirstate[f] in 'amn':
1081 1081 self.ui.warn(_("%s already tracked!\n") % f)
1082 1082 elif self.dirstate[f] == 'r':
1083 1083 self.dirstate.normallookup(f)
1084 1084 else:
1085 1085 self.dirstate.add(f)
1086 1086 return rejected
1087 1087 finally:
1088 1088 wlock.release()
1089 1089
1090 1090 def forget(self, list):
1091 1091 wlock = self.wlock()
1092 1092 try:
1093 1093 for f in list:
1094 1094 if self.dirstate[f] != 'a':
1095 1095 self.ui.warn(_("%s not added!\n") % f)
1096 1096 else:
1097 1097 self.dirstate.forget(f)
1098 1098 finally:
1099 1099 wlock.release()
1100 1100
1101 1101 def remove(self, list, unlink=False):
1102 1102 if unlink:
1103 1103 for f in list:
1104 1104 try:
1105 1105 util.unlink(self.wjoin(f))
1106 1106 except OSError, inst:
1107 1107 if inst.errno != errno.ENOENT:
1108 1108 raise
1109 1109 wlock = self.wlock()
1110 1110 try:
1111 1111 for f in list:
1112 1112 if unlink and os.path.exists(self.wjoin(f)):
1113 1113 self.ui.warn(_("%s still exists!\n") % f)
1114 1114 elif self.dirstate[f] == 'a':
1115 1115 self.dirstate.forget(f)
1116 1116 elif f not in self.dirstate:
1117 1117 self.ui.warn(_("%s not tracked!\n") % f)
1118 1118 else:
1119 1119 self.dirstate.remove(f)
1120 1120 finally:
1121 1121 wlock.release()
1122 1122
1123 1123 def undelete(self, list):
1124 1124 manifests = [self.manifest.read(self.changelog.read(p)[0])
1125 1125 for p in self.dirstate.parents() if p != nullid]
1126 1126 wlock = self.wlock()
1127 1127 try:
1128 1128 for f in list:
1129 1129 if self.dirstate[f] != 'r':
1130 1130 self.ui.warn(_("%s not removed!\n") % f)
1131 1131 else:
1132 1132 m = f in manifests[0] and manifests[0] or manifests[1]
1133 1133 t = self.file(f).read(m[f])
1134 1134 self.wwrite(f, t, m.flags(f))
1135 1135 self.dirstate.normal(f)
1136 1136 finally:
1137 1137 wlock.release()
1138 1138
1139 1139 def copy(self, source, dest):
1140 1140 p = self.wjoin(dest)
1141 1141 if not (os.path.exists(p) or os.path.islink(p)):
1142 1142 self.ui.warn(_("%s does not exist!\n") % dest)
1143 1143 elif not (os.path.isfile(p) or os.path.islink(p)):
1144 1144 self.ui.warn(_("copy failed: %s is not a file or a "
1145 1145 "symbolic link\n") % dest)
1146 1146 else:
1147 1147 wlock = self.wlock()
1148 1148 try:
1149 1149 if self.dirstate[dest] in '?r':
1150 1150 self.dirstate.add(dest)
1151 1151 self.dirstate.copy(source, dest)
1152 1152 finally:
1153 1153 wlock.release()
1154 1154
1155 1155 def heads(self, start=None):
1156 1156 heads = self.changelog.heads(start)
1157 1157 # sort the output in rev descending order
1158 1158 heads = [(-self.changelog.rev(h), h) for h in heads]
1159 1159 return [n for (r, n) in sorted(heads)]
1160 1160
1161 1161 def branchheads(self, branch=None, start=None, closed=False):
1162 1162 if branch is None:
1163 1163 branch = self[None].branch()
1164 1164 branches = self.branchmap()
1165 1165 if branch not in branches:
1166 1166 return []
1167 1167 bheads = branches[branch]
1168 1168 # the cache returns heads ordered lowest to highest
1169 1169 bheads.reverse()
1170 1170 if start is not None:
1171 1171 # filter out the heads that cannot be reached from startrev
1172 1172 bheads = self.changelog.nodesbetween([start], bheads)[2]
1173 1173 if not closed:
1174 1174 bheads = [h for h in bheads if
1175 1175 ('close' not in self.changelog.read(h)[5])]
1176 1176 return bheads
1177 1177
1178 1178 def branches(self, nodes):
1179 1179 if not nodes:
1180 1180 nodes = [self.changelog.tip()]
1181 1181 b = []
1182 1182 for n in nodes:
1183 1183 t = n
1184 1184 while 1:
1185 1185 p = self.changelog.parents(n)
1186 1186 if p[1] != nullid or p[0] == nullid:
1187 1187 b.append((t, n, p[0], p[1]))
1188 1188 break
1189 1189 n = p[0]
1190 1190 return b
1191 1191
1192 1192 def between(self, pairs):
1193 1193 r = []
1194 1194
1195 1195 for top, bottom in pairs:
1196 1196 n, l, i = top, [], 0
1197 1197 f = 1
1198 1198
1199 1199 while n != bottom and n != nullid:
1200 1200 p = self.changelog.parents(n)[0]
1201 1201 if i == f:
1202 1202 l.append(n)
1203 1203 f = f * 2
1204 1204 n = p
1205 1205 i += 1
1206 1206
1207 1207 r.append(l)
1208 1208
1209 1209 return r
1210 1210
1211 1211 def findincoming(self, remote, base=None, heads=None, force=False):
1212 1212 """Return list of roots of the subsets of missing nodes from remote
1213 1213
1214 1214 If base dict is specified, assume that these nodes and their parents
1215 1215 exist on the remote side and that no child of a node of base exists
1216 1216 in both remote and self.
1217 1217 Furthermore base will be updated to include the nodes that exists
1218 1218 in self and remote but no children exists in self and remote.
1219 1219 If a list of heads is specified, return only nodes which are heads
1220 1220 or ancestors of these heads.
1221 1221
1222 1222 All the ancestors of base are in self and in remote.
1223 1223 All the descendants of the list returned are missing in self.
1224 1224 (and so we know that the rest of the nodes are missing in remote, see
1225 1225 outgoing)
1226 1226 """
1227 1227 return self.findcommonincoming(remote, base, heads, force)[1]
1228 1228
1229 1229 def findcommonincoming(self, remote, base=None, heads=None, force=False):
1230 1230 """Return a tuple (common, missing roots, heads) used to identify
1231 1231 missing nodes from remote.
1232 1232
1233 1233 If base dict is specified, assume that these nodes and their parents
1234 1234 exist on the remote side and that no child of a node of base exists
1235 1235 in both remote and self.
1236 1236 Furthermore base will be updated to include the nodes that exists
1237 1237 in self and remote but no children exists in self and remote.
1238 1238 If a list of heads is specified, return only nodes which are heads
1239 1239 or ancestors of these heads.
1240 1240
1241 1241 All the ancestors of base are in self and in remote.
1242 1242 """
1243 1243 m = self.changelog.nodemap
1244 1244 search = []
1245 1245 fetch = set()
1246 1246 seen = set()
1247 1247 seenbranch = set()
1248 1248 if base is None:
1249 1249 base = {}
1250 1250
1251 1251 if not heads:
1252 1252 heads = remote.heads()
1253 1253
1254 1254 if self.changelog.tip() == nullid:
1255 1255 base[nullid] = 1
1256 1256 if heads != [nullid]:
1257 1257 return [nullid], [nullid], list(heads)
1258 1258 return [nullid], [], []
1259 1259
1260 1260 # assume we're closer to the tip than the root
1261 1261 # and start by examining the heads
1262 1262 self.ui.status(_("searching for changes\n"))
1263 1263
1264 1264 unknown = []
1265 1265 for h in heads:
1266 1266 if h not in m:
1267 1267 unknown.append(h)
1268 1268 else:
1269 1269 base[h] = 1
1270 1270
1271 1271 heads = unknown
1272 1272 if not unknown:
1273 1273 return base.keys(), [], []
1274 1274
1275 1275 req = set(unknown)
1276 1276 reqcnt = 0
1277 1277
1278 1278 # search through remote branches
1279 1279 # a 'branch' here is a linear segment of history, with four parts:
1280 1280 # head, root, first parent, second parent
1281 1281 # (a branch always has two parents (or none) by definition)
1282 1282 unknown = remote.branches(unknown)
1283 1283 while unknown:
1284 1284 r = []
1285 1285 while unknown:
1286 1286 n = unknown.pop(0)
1287 1287 if n[0] in seen:
1288 1288 continue
1289 1289
1290 1290 self.ui.debug(_("examining %s:%s\n")
1291 1291 % (short(n[0]), short(n[1])))
1292 1292 if n[0] == nullid: # found the end of the branch
1293 1293 pass
1294 1294 elif n in seenbranch:
1295 1295 self.ui.debug(_("branch already found\n"))
1296 1296 continue
1297 1297 elif n[1] and n[1] in m: # do we know the base?
1298 1298 self.ui.debug(_("found incomplete branch %s:%s\n")
1299 1299 % (short(n[0]), short(n[1])))
1300 1300 search.append(n[0:2]) # schedule branch range for scanning
1301 1301 seenbranch.add(n)
1302 1302 else:
1303 1303 if n[1] not in seen and n[1] not in fetch:
1304 1304 if n[2] in m and n[3] in m:
1305 1305 self.ui.debug(_("found new changeset %s\n") %
1306 1306 short(n[1]))
1307 1307 fetch.add(n[1]) # earliest unknown
1308 1308 for p in n[2:4]:
1309 1309 if p in m:
1310 1310 base[p] = 1 # latest known
1311 1311
1312 1312 for p in n[2:4]:
1313 1313 if p not in req and p not in m:
1314 1314 r.append(p)
1315 1315 req.add(p)
1316 1316 seen.add(n[0])
1317 1317
1318 1318 if r:
1319 1319 reqcnt += 1
1320 1320 self.ui.debug(_("request %d: %s\n") %
1321 1321 (reqcnt, " ".join(map(short, r))))
1322 1322 for p in xrange(0, len(r), 10):
1323 1323 for b in remote.branches(r[p:p+10]):
1324 1324 self.ui.debug(_("received %s:%s\n") %
1325 1325 (short(b[0]), short(b[1])))
1326 1326 unknown.append(b)
1327 1327
1328 1328 # do binary search on the branches we found
1329 1329 while search:
1330 1330 newsearch = []
1331 1331 reqcnt += 1
1332 1332 for n, l in zip(search, remote.between(search)):
1333 1333 l.append(n[1])
1334 1334 p = n[0]
1335 1335 f = 1
1336 1336 for i in l:
1337 1337 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
1338 1338 if i in m:
1339 1339 if f <= 2:
1340 1340 self.ui.debug(_("found new branch changeset %s\n") %
1341 1341 short(p))
1342 1342 fetch.add(p)
1343 1343 base[i] = 1
1344 1344 else:
1345 1345 self.ui.debug(_("narrowed branch search to %s:%s\n")
1346 1346 % (short(p), short(i)))
1347 1347 newsearch.append((p, i))
1348 1348 break
1349 1349 p, f = i, f * 2
1350 1350 search = newsearch
1351 1351
1352 1352 # sanity check our fetch list
1353 1353 for f in fetch:
1354 1354 if f in m:
1355 1355 raise error.RepoError(_("already have changeset ")
1356 1356 + short(f[:4]))
1357 1357
1358 1358 if base.keys() == [nullid]:
1359 1359 if force:
1360 1360 self.ui.warn(_("warning: repository is unrelated\n"))
1361 1361 else:
1362 1362 raise util.Abort(_("repository is unrelated"))
1363 1363
1364 1364 self.ui.debug(_("found new changesets starting at ") +
1365 1365 " ".join([short(f) for f in fetch]) + "\n")
1366 1366
1367 1367 self.ui.debug(_("%d total queries\n") % reqcnt)
1368 1368
1369 1369 return base.keys(), list(fetch), heads
1370 1370
1371 1371 def findoutgoing(self, remote, base=None, heads=None, force=False):
1372 1372 """Return list of nodes that are roots of subsets not in remote
1373 1373
1374 1374 If base dict is specified, assume that these nodes and their parents
1375 1375 exist on the remote side.
1376 1376 If a list of heads is specified, return only nodes which are heads
1377 1377 or ancestors of these heads, and return a second element which
1378 1378 contains all remote heads which get new children.
1379 1379 """
1380 1380 if base is None:
1381 1381 base = {}
1382 1382 self.findincoming(remote, base, heads, force=force)
1383 1383
1384 1384 self.ui.debug(_("common changesets up to ")
1385 1385 + " ".join(map(short, base.keys())) + "\n")
1386 1386
1387 1387 remain = set(self.changelog.nodemap)
1388 1388
1389 1389 # prune everything remote has from the tree
1390 1390 remain.remove(nullid)
1391 1391 remove = base.keys()
1392 1392 while remove:
1393 1393 n = remove.pop(0)
1394 1394 if n in remain:
1395 1395 remain.remove(n)
1396 1396 for p in self.changelog.parents(n):
1397 1397 remove.append(p)
1398 1398
1399 1399 # find every node whose parents have been pruned
1400 1400 subset = []
1401 1401 # find every remote head that will get new children
1402 1402 updated_heads = set()
1403 1403 for n in remain:
1404 1404 p1, p2 = self.changelog.parents(n)
1405 1405 if p1 not in remain and p2 not in remain:
1406 1406 subset.append(n)
1407 1407 if heads:
1408 1408 if p1 in heads:
1409 1409 updated_heads.add(p1)
1410 1410 if p2 in heads:
1411 1411 updated_heads.add(p2)
1412 1412
1413 1413 # this is the set of all roots we have to push
1414 1414 if heads:
1415 1415 return subset, list(updated_heads)
1416 1416 else:
1417 1417 return subset
1418 1418
1419 1419 def pull(self, remote, heads=None, force=False):
1420 1420 lock = self.lock()
1421 1421 try:
1422 1422 common, fetch, rheads = self.findcommonincoming(remote, heads=heads,
1423 1423 force=force)
1424 1424 if fetch == [nullid]:
1425 1425 self.ui.status(_("requesting all changes\n"))
1426 1426
1427 1427 if not fetch:
1428 1428 self.ui.status(_("no changes found\n"))
1429 1429 return 0
1430 1430
1431 1431 if heads is None and remote.capable('changegroupsubset'):
1432 1432 heads = rheads
1433 1433
1434 1434 if heads is None:
1435 1435 cg = remote.changegroup(fetch, 'pull')
1436 1436 else:
1437 1437 if not remote.capable('changegroupsubset'):
1438 1438 raise util.Abort(_("Partial pull cannot be done because "
1439 1439 "other repository doesn't support "
1440 1440 "changegroupsubset."))
1441 1441 cg = remote.changegroupsubset(fetch, heads, 'pull')
1442 1442 return self.addchangegroup(cg, 'pull', remote.url())
1443 1443 finally:
1444 1444 lock.release()
1445 1445
1446 1446 def push(self, remote, force=False, revs=None):
1447 1447 # there are two ways to push to remote repo:
1448 1448 #
1449 1449 # addchangegroup assumes local user can lock remote
1450 1450 # repo (local filesystem, old ssh servers).
1451 1451 #
1452 1452 # unbundle assumes local user cannot lock remote repo (new ssh
1453 1453 # servers, http servers).
1454 1454
1455 1455 if remote.capable('unbundle'):
1456 1456 return self.push_unbundle(remote, force, revs)
1457 1457 return self.push_addchangegroup(remote, force, revs)
1458 1458
1459 1459 def prepush(self, remote, force, revs):
1460 '''Analyze the local and remote repositories and determine which
1461 changesets need to be pushed to the remote. Return a tuple
1462 (changegroup, remoteheads). changegroup is a readable file-like
1463 object whose read() returns successive changegroup chunks ready to
1464 be sent over the wire. remoteheads is the list of remote heads.
1465 '''
1460 1466 common = {}
1461 1467 remote_heads = remote.heads()
1462 1468 inc = self.findincoming(remote, common, remote_heads, force=force)
1463 1469
1464 1470 update, updated_heads = self.findoutgoing(remote, common, remote_heads)
1465 1471 if revs is not None:
1466 1472 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1467 1473 else:
1468 1474 bases, heads = update, self.changelog.heads()
1469 1475
1470 1476 def checkbranch(lheads, rheads, updatelh):
1471 1477 '''
1472 1478 check whether there are more local heads than remote heads on
1473 1479 a specific branch.
1474 1480
1475 1481 lheads: local branch heads
1476 1482 rheads: remote branch heads
1477 1483 updatelh: outgoing local branch heads
1478 1484 '''
1479 1485
1480 1486 warn = 0
1481 1487
1482 1488 if not revs and len(lheads) > len(rheads):
1483 1489 warn = 1
1484 1490 else:
1485 1491 updatelheads = [self.changelog.heads(x, lheads)
1486 1492 for x in updatelh]
1487 1493 newheads = set(sum(updatelheads, [])) & set(lheads)
1488 1494
1489 1495 if not newheads:
1490 1496 return True
1491 1497
1492 1498 for r in rheads:
1493 1499 if r in self.changelog.nodemap:
1494 1500 desc = self.changelog.heads(r, heads)
1495 1501 l = [h for h in heads if h in desc]
1496 1502 if not l:
1497 1503 newheads.add(r)
1498 1504 else:
1499 1505 newheads.add(r)
1500 1506 if len(newheads) > len(rheads):
1501 1507 warn = 1
1502 1508
1503 1509 if warn:
1504 1510 if not rheads: # new branch requires --force
1505 1511 self.ui.warn(_("abort: push creates new"
1506 1512 " remote branch '%s'!\n") %
1507 1513 self[updatelh[0]].branch())
1508 1514 else:
1509 1515 self.ui.warn(_("abort: push creates new remote heads!\n"))
1510 1516
1511 1517 self.ui.status(_("(did you forget to merge?"
1512 1518 " use push -f to force)\n"))
1513 1519 return False
1514 1520 return True
1515 1521
1516 1522 if not bases:
1517 1523 self.ui.status(_("no changes found\n"))
1518 1524 return None, 1
1519 1525 elif not force:
1520 1526 # Check for each named branch if we're creating new remote heads.
1521 1527 # To be a remote head after push, node must be either:
1522 1528 # - unknown locally
1523 1529 # - a local outgoing head descended from update
1524 1530 # - a remote head that's known locally and not
1525 1531 # ancestral to an outgoing head
1526 1532 #
1527 1533 # New named branches cannot be created without --force.
1528 1534
1529 1535 if remote_heads != [nullid]:
1530 1536 if remote.capable('branchmap'):
1531 1537 localhds = {}
1532 1538 if not revs:
1533 1539 localhds = self.branchmap()
1534 1540 else:
1535 1541 for n in heads:
1536 1542 branch = self[n].branch()
1537 1543 if branch in localhds:
1538 1544 localhds[branch].append(n)
1539 1545 else:
1540 1546 localhds[branch] = [n]
1541 1547
1542 1548 remotehds = remote.branchmap()
1543 1549
1544 1550 for lh in localhds:
1545 1551 if lh in remotehds:
1546 1552 rheads = remotehds[lh]
1547 1553 else:
1548 1554 rheads = []
1549 1555 lheads = localhds[lh]
1550 1556 updatelh = [upd for upd in update
1551 1557 if self[upd].branch() == lh]
1552 1558 if not updatelh:
1553 1559 continue
1554 1560 if not checkbranch(lheads, rheads, updatelh):
1555 1561 return None, 0
1556 1562 else:
1557 1563 if not checkbranch(heads, remote_heads, update):
1558 1564 return None, 0
1559 1565
1560 1566 if inc:
1561 1567 self.ui.warn(_("note: unsynced remote changes!\n"))
1562 1568
1563 1569
1564 1570 if revs is None:
1565 1571 # use the fast path, no race possible on push
1566 1572 cg = self._changegroup(common.keys(), 'push')
1567 1573 else:
1568 1574 cg = self.changegroupsubset(update, revs, 'push')
1569 1575 return cg, remote_heads
1570 1576
1571 1577 def push_addchangegroup(self, remote, force, revs):
1572 1578 lock = remote.lock()
1573 1579 try:
1574 1580 ret = self.prepush(remote, force, revs)
1575 1581 if ret[0] is not None:
1576 1582 cg, remote_heads = ret
1577 1583 return remote.addchangegroup(cg, 'push', self.url())
1578 1584 return ret[1]
1579 1585 finally:
1580 1586 lock.release()
1581 1587
1582 1588 def push_unbundle(self, remote, force, revs):
1583 1589 # local repo finds heads on server, finds out what revs it
1584 1590 # must push. once revs transferred, if server finds it has
1585 1591 # different heads (someone else won commit/push race), server
1586 1592 # aborts.
1587 1593
1588 1594 ret = self.prepush(remote, force, revs)
1589 1595 if ret[0] is not None:
1590 1596 cg, remote_heads = ret
1591 1597 if force: remote_heads = ['force']
1592 1598 return remote.unbundle(cg, remote_heads, 'push')
1593 1599 return ret[1]
1594 1600
1595 1601 def changegroupinfo(self, nodes, source):
1596 1602 if self.ui.verbose or source == 'bundle':
1597 1603 self.ui.status(_("%d changesets found\n") % len(nodes))
1598 1604 if self.ui.debugflag:
1599 1605 self.ui.debug(_("list of changesets:\n"))
1600 1606 for node in nodes:
1601 1607 self.ui.debug("%s\n" % hex(node))
1602 1608
1603 1609 def changegroupsubset(self, bases, heads, source, extranodes=None):
1604 """This function generates a changegroup consisting of all the nodes
1605 that are descendents of any of the bases, and ancestors of any of
1606 the heads.
1610 """Compute a changegroup consisting of all the nodes that are
1611 descendents of any of the bases and ancestors of any of the heads.
1612 Return a chunkbuffer object whose read() method will return
1613 successive changegroup chunks.
1607 1614
1608 1615 It is fairly complex as determining which filenodes and which
1609 1616 manifest nodes need to be included for the changeset to be complete
1610 1617 is non-trivial.
1611 1618
1612 1619 Another wrinkle is doing the reverse, figuring out which changeset in
1613 1620 the changegroup a particular filenode or manifestnode belongs to.
1614 1621
1615 1622 The caller can specify some nodes that must be included in the
1616 1623 changegroup using the extranodes argument. It should be a dict
1617 1624 where the keys are the filenames (or 1 for the manifest), and the
1618 1625 values are lists of (node, linknode) tuples, where node is a wanted
1619 1626 node and linknode is the changelog node that should be transmitted as
1620 1627 the linkrev.
1621 1628 """
1622 1629
1623 1630 if extranodes is None:
1624 1631 # can we go through the fast path ?
1625 1632 heads.sort()
1626 1633 allheads = self.heads()
1627 1634 allheads.sort()
1628 1635 if heads == allheads:
1629 1636 common = []
1630 1637 # parents of bases are known from both sides
1631 1638 for n in bases:
1632 1639 for p in self.changelog.parents(n):
1633 1640 if p != nullid:
1634 1641 common.append(p)
1635 1642 return self._changegroup(common, source)
1636 1643
1637 1644 self.hook('preoutgoing', throw=True, source=source)
1638 1645
1639 1646 # Set up some initial variables
1640 1647 # Make it easy to refer to self.changelog
1641 1648 cl = self.changelog
1642 1649 # msng is short for missing - compute the list of changesets in this
1643 1650 # changegroup.
1644 1651 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1645 1652 self.changegroupinfo(msng_cl_lst, source)
1646 1653 # Some bases may turn out to be superfluous, and some heads may be
1647 1654 # too. nodesbetween will return the minimal set of bases and heads
1648 1655 # necessary to re-create the changegroup.
1649 1656
1650 1657 # Known heads are the list of heads that it is assumed the recipient
1651 1658 # of this changegroup will know about.
1652 1659 knownheads = set()
1653 1660 # We assume that all parents of bases are known heads.
1654 1661 for n in bases:
1655 1662 knownheads.update(cl.parents(n))
1656 1663 knownheads.discard(nullid)
1657 1664 knownheads = list(knownheads)
1658 1665 if knownheads:
1659 1666 # Now that we know what heads are known, we can compute which
1660 1667 # changesets are known. The recipient must know about all
1661 1668 # changesets required to reach the known heads from the null
1662 1669 # changeset.
1663 1670 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1664 1671 junk = None
1665 1672 # Transform the list into a set.
1666 1673 has_cl_set = set(has_cl_set)
1667 1674 else:
1668 1675 # If there were no known heads, the recipient cannot be assumed to
1669 1676 # know about any changesets.
1670 1677 has_cl_set = set()
1671 1678
1672 1679 # Make it easy to refer to self.manifest
1673 1680 mnfst = self.manifest
1674 1681 # We don't know which manifests are missing yet
1675 1682 msng_mnfst_set = {}
1676 1683 # Nor do we know which filenodes are missing.
1677 1684 msng_filenode_set = {}
1678 1685
1679 1686 junk = mnfst.index[len(mnfst) - 1] # Get around a bug in lazyindex
1680 1687 junk = None
1681 1688
1682 1689 # A changeset always belongs to itself, so the changenode lookup
1683 1690 # function for a changenode is identity.
1684 1691 def identity(x):
1685 1692 return x
1686 1693
1687 1694 # If we determine that a particular file or manifest node must be a
1688 1695 # node that the recipient of the changegroup will already have, we can
1689 1696 # also assume the recipient will have all the parents. This function
1690 1697 # prunes them from the set of missing nodes.
1691 1698 def prune_parents(revlog, hasset, msngset):
1692 1699 haslst = list(hasset)
1693 1700 haslst.sort(key=revlog.rev)
1694 1701 for node in haslst:
1695 1702 parentlst = [p for p in revlog.parents(node) if p != nullid]
1696 1703 while parentlst:
1697 1704 n = parentlst.pop()
1698 1705 if n not in hasset:
1699 1706 hasset.add(n)
1700 1707 p = [p for p in revlog.parents(n) if p != nullid]
1701 1708 parentlst.extend(p)
1702 1709 for n in hasset:
1703 1710 msngset.pop(n, None)
1704 1711
1705 1712 # This is a function generating function used to set up an environment
1706 1713 # for the inner function to execute in.
1707 1714 def manifest_and_file_collector(changedfileset):
1708 1715 # This is an information gathering function that gathers
1709 1716 # information from each changeset node that goes out as part of
1710 1717 # the changegroup. The information gathered is a list of which
1711 1718 # manifest nodes are potentially required (the recipient may
1712 1719 # already have them) and total list of all files which were
1713 1720 # changed in any changeset in the changegroup.
1714 1721 #
1715 1722 # We also remember the first changenode we saw any manifest
1716 1723 # referenced by so we can later determine which changenode 'owns'
1717 1724 # the manifest.
1718 1725 def collect_manifests_and_files(clnode):
1719 1726 c = cl.read(clnode)
1720 1727 for f in c[3]:
1721 1728 # This is to make sure we only have one instance of each
1722 1729 # filename string for each filename.
1723 1730 changedfileset.setdefault(f, f)
1724 1731 msng_mnfst_set.setdefault(c[0], clnode)
1725 1732 return collect_manifests_and_files
1726 1733
1727 1734 # Figure out which manifest nodes (of the ones we think might be part
1728 1735 # of the changegroup) the recipient must know about and remove them
1729 1736 # from the changegroup.
1730 1737 def prune_manifests():
1731 1738 has_mnfst_set = set()
1732 1739 for n in msng_mnfst_set:
1733 1740 # If a 'missing' manifest thinks it belongs to a changenode
1734 1741 # the recipient is assumed to have, obviously the recipient
1735 1742 # must have that manifest.
1736 1743 linknode = cl.node(mnfst.linkrev(mnfst.rev(n)))
1737 1744 if linknode in has_cl_set:
1738 1745 has_mnfst_set.add(n)
1739 1746 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1740 1747
1741 1748 # Use the information collected in collect_manifests_and_files to say
1742 1749 # which changenode any manifestnode belongs to.
1743 1750 def lookup_manifest_link(mnfstnode):
1744 1751 return msng_mnfst_set[mnfstnode]
1745 1752
1746 1753 # A function generating function that sets up the initial environment
1747 1754 # the inner function.
1748 1755 def filenode_collector(changedfiles):
1749 1756 next_rev = [0]
1750 1757 # This gathers information from each manifestnode included in the
1751 1758 # changegroup about which filenodes the manifest node references
1752 1759 # so we can include those in the changegroup too.
1753 1760 #
1754 1761 # It also remembers which changenode each filenode belongs to. It
1755 1762 # does this by assuming the a filenode belongs to the changenode
1756 1763 # the first manifest that references it belongs to.
1757 1764 def collect_msng_filenodes(mnfstnode):
1758 1765 r = mnfst.rev(mnfstnode)
1759 1766 if r == next_rev[0]:
1760 1767 # If the last rev we looked at was the one just previous,
1761 1768 # we only need to see a diff.
1762 1769 deltamf = mnfst.readdelta(mnfstnode)
1763 1770 # For each line in the delta
1764 1771 for f, fnode in deltamf.iteritems():
1765 1772 f = changedfiles.get(f, None)
1766 1773 # And if the file is in the list of files we care
1767 1774 # about.
1768 1775 if f is not None:
1769 1776 # Get the changenode this manifest belongs to
1770 1777 clnode = msng_mnfst_set[mnfstnode]
1771 1778 # Create the set of filenodes for the file if
1772 1779 # there isn't one already.
1773 1780 ndset = msng_filenode_set.setdefault(f, {})
1774 1781 # And set the filenode's changelog node to the
1775 1782 # manifest's if it hasn't been set already.
1776 1783 ndset.setdefault(fnode, clnode)
1777 1784 else:
1778 1785 # Otherwise we need a full manifest.
1779 1786 m = mnfst.read(mnfstnode)
1780 1787 # For every file in we care about.
1781 1788 for f in changedfiles:
1782 1789 fnode = m.get(f, None)
1783 1790 # If it's in the manifest
1784 1791 if fnode is not None:
1785 1792 # See comments above.
1786 1793 clnode = msng_mnfst_set[mnfstnode]
1787 1794 ndset = msng_filenode_set.setdefault(f, {})
1788 1795 ndset.setdefault(fnode, clnode)
1789 1796 # Remember the revision we hope to see next.
1790 1797 next_rev[0] = r + 1
1791 1798 return collect_msng_filenodes
1792 1799
1793 1800 # We have a list of filenodes we think we need for a file, lets remove
1794 1801 # all those we know the recipient must have.
1795 1802 def prune_filenodes(f, filerevlog):
1796 1803 msngset = msng_filenode_set[f]
1797 1804 hasset = set()
1798 1805 # If a 'missing' filenode thinks it belongs to a changenode we
1799 1806 # assume the recipient must have, then the recipient must have
1800 1807 # that filenode.
1801 1808 for n in msngset:
1802 1809 clnode = cl.node(filerevlog.linkrev(filerevlog.rev(n)))
1803 1810 if clnode in has_cl_set:
1804 1811 hasset.add(n)
1805 1812 prune_parents(filerevlog, hasset, msngset)
1806 1813
1807 1814 # A function generator function that sets up the a context for the
1808 1815 # inner function.
1809 1816 def lookup_filenode_link_func(fname):
1810 1817 msngset = msng_filenode_set[fname]
1811 1818 # Lookup the changenode the filenode belongs to.
1812 1819 def lookup_filenode_link(fnode):
1813 1820 return msngset[fnode]
1814 1821 return lookup_filenode_link
1815 1822
1816 1823 # Add the nodes that were explicitly requested.
1817 1824 def add_extra_nodes(name, nodes):
1818 1825 if not extranodes or name not in extranodes:
1819 1826 return
1820 1827
1821 1828 for node, linknode in extranodes[name]:
1822 1829 if node not in nodes:
1823 1830 nodes[node] = linknode
1824 1831
1825 1832 # Now that we have all theses utility functions to help out and
1826 1833 # logically divide up the task, generate the group.
1827 1834 def gengroup():
1828 1835 # The set of changed files starts empty.
1829 1836 changedfiles = {}
1830 1837 # Create a changenode group generator that will call our functions
1831 1838 # back to lookup the owning changenode and collect information.
1832 1839 group = cl.group(msng_cl_lst, identity,
1833 1840 manifest_and_file_collector(changedfiles))
1834 1841 for chnk in group:
1835 1842 yield chnk
1836 1843
1837 1844 # The list of manifests has been collected by the generator
1838 1845 # calling our functions back.
1839 1846 prune_manifests()
1840 1847 add_extra_nodes(1, msng_mnfst_set)
1841 1848 msng_mnfst_lst = msng_mnfst_set.keys()
1842 1849 # Sort the manifestnodes by revision number.
1843 1850 msng_mnfst_lst.sort(key=mnfst.rev)
1844 1851 # Create a generator for the manifestnodes that calls our lookup
1845 1852 # and data collection functions back.
1846 1853 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1847 1854 filenode_collector(changedfiles))
1848 1855 for chnk in group:
1849 1856 yield chnk
1850 1857
1851 1858 # These are no longer needed, dereference and toss the memory for
1852 1859 # them.
1853 1860 msng_mnfst_lst = None
1854 1861 msng_mnfst_set.clear()
1855 1862
1856 1863 if extranodes:
1857 1864 for fname in extranodes:
1858 1865 if isinstance(fname, int):
1859 1866 continue
1860 1867 msng_filenode_set.setdefault(fname, {})
1861 1868 changedfiles[fname] = 1
1862 1869 # Go through all our files in order sorted by name.
1863 1870 for fname in sorted(changedfiles):
1864 1871 filerevlog = self.file(fname)
1865 1872 if not len(filerevlog):
1866 1873 raise util.Abort(_("empty or missing revlog for %s") % fname)
1867 1874 # Toss out the filenodes that the recipient isn't really
1868 1875 # missing.
1869 1876 if fname in msng_filenode_set:
1870 1877 prune_filenodes(fname, filerevlog)
1871 1878 add_extra_nodes(fname, msng_filenode_set[fname])
1872 1879 msng_filenode_lst = msng_filenode_set[fname].keys()
1873 1880 else:
1874 1881 msng_filenode_lst = []
1875 1882 # If any filenodes are left, generate the group for them,
1876 1883 # otherwise don't bother.
1877 1884 if len(msng_filenode_lst) > 0:
1878 1885 yield changegroup.chunkheader(len(fname))
1879 1886 yield fname
1880 1887 # Sort the filenodes by their revision #
1881 1888 msng_filenode_lst.sort(key=filerevlog.rev)
1882 1889 # Create a group generator and only pass in a changenode
1883 1890 # lookup function as we need to collect no information
1884 1891 # from filenodes.
1885 1892 group = filerevlog.group(msng_filenode_lst,
1886 1893 lookup_filenode_link_func(fname))
1887 1894 for chnk in group:
1888 1895 yield chnk
1889 1896 if fname in msng_filenode_set:
1890 1897 # Don't need this anymore, toss it to free memory.
1891 1898 del msng_filenode_set[fname]
1892 1899 # Signal that no more groups are left.
1893 1900 yield changegroup.closechunk()
1894 1901
1895 1902 if msng_cl_lst:
1896 1903 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1897 1904
1898 1905 return util.chunkbuffer(gengroup())
1899 1906
1900 1907 def changegroup(self, basenodes, source):
1901 1908 # to avoid a race we use changegroupsubset() (issue1320)
1902 1909 return self.changegroupsubset(basenodes, self.heads(), source)
1903 1910
1904 1911 def _changegroup(self, common, source):
1905 """Generate a changegroup of all nodes that we have that a recipient
1906 doesn't.
1912 """Compute the changegroup of all nodes that we have that a recipient
1913 doesn't. Return a chunkbuffer object whose read() method will return
1914 successive changegroup chunks.
1907 1915
1908 1916 This is much easier than the previous function as we can assume that
1909 1917 the recipient has any changenode we aren't sending them.
1910 1918
1911 1919 common is the set of common nodes between remote and self"""
1912 1920
1913 1921 self.hook('preoutgoing', throw=True, source=source)
1914 1922
1915 1923 cl = self.changelog
1916 1924 nodes = cl.findmissing(common)
1917 1925 revset = set([cl.rev(n) for n in nodes])
1918 1926 self.changegroupinfo(nodes, source)
1919 1927
1920 1928 def identity(x):
1921 1929 return x
1922 1930
1923 1931 def gennodelst(log):
1924 1932 for r in log:
1925 1933 if log.linkrev(r) in revset:
1926 1934 yield log.node(r)
1927 1935
1928 1936 def changed_file_collector(changedfileset):
1929 1937 def collect_changed_files(clnode):
1930 1938 c = cl.read(clnode)
1931 1939 changedfileset.update(c[3])
1932 1940 return collect_changed_files
1933 1941
1934 1942 def lookuprevlink_func(revlog):
1935 1943 def lookuprevlink(n):
1936 1944 return cl.node(revlog.linkrev(revlog.rev(n)))
1937 1945 return lookuprevlink
1938 1946
1939 1947 def gengroup():
1948 '''yield a sequence of changegroup chunks (strings)'''
1940 1949 # construct a list of all changed files
1941 1950 changedfiles = set()
1942 1951
1943 1952 for chnk in cl.group(nodes, identity,
1944 1953 changed_file_collector(changedfiles)):
1945 1954 yield chnk
1946 1955
1947 1956 mnfst = self.manifest
1948 1957 nodeiter = gennodelst(mnfst)
1949 1958 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1950 1959 yield chnk
1951 1960
1952 1961 for fname in sorted(changedfiles):
1953 1962 filerevlog = self.file(fname)
1954 1963 if not len(filerevlog):
1955 1964 raise util.Abort(_("empty or missing revlog for %s") % fname)
1956 1965 nodeiter = gennodelst(filerevlog)
1957 1966 nodeiter = list(nodeiter)
1958 1967 if nodeiter:
1959 1968 yield changegroup.chunkheader(len(fname))
1960 1969 yield fname
1961 1970 lookup = lookuprevlink_func(filerevlog)
1962 1971 for chnk in filerevlog.group(nodeiter, lookup):
1963 1972 yield chnk
1964 1973
1965 1974 yield changegroup.closechunk()
1966 1975
1967 1976 if nodes:
1968 1977 self.hook('outgoing', node=hex(nodes[0]), source=source)
1969 1978
1970 1979 return util.chunkbuffer(gengroup())
1971 1980
1972 1981 def addchangegroup(self, source, srctype, url, emptyok=False):
1973 1982 """add changegroup to repo.
1974 1983
1975 1984 return values:
1976 1985 - nothing changed or no source: 0
1977 1986 - more heads than before: 1+added heads (2..n)
1978 1987 - less heads than before: -1-removed heads (-2..-n)
1979 1988 - number of heads stays the same: 1
1980 1989 """
1981 1990 def csmap(x):
1982 1991 self.ui.debug(_("add changeset %s\n") % short(x))
1983 1992 return len(cl)
1984 1993
1985 1994 def revmap(x):
1986 1995 return cl.rev(x)
1987 1996
1988 1997 if not source:
1989 1998 return 0
1990 1999
1991 2000 self.hook('prechangegroup', throw=True, source=srctype, url=url)
1992 2001
1993 2002 changesets = files = revisions = 0
1994 2003
1995 2004 # write changelog data to temp files so concurrent readers will not see
1996 2005 # inconsistent view
1997 2006 cl = self.changelog
1998 2007 cl.delayupdate()
1999 2008 oldheads = len(cl.heads())
2000 2009
2001 2010 tr = self.transaction()
2002 2011 try:
2003 2012 trp = weakref.proxy(tr)
2004 2013 # pull off the changeset group
2005 2014 self.ui.status(_("adding changesets\n"))
2006 2015 clstart = len(cl)
2007 2016 chunkiter = changegroup.chunkiter(source)
2008 2017 if cl.addgroup(chunkiter, csmap, trp) is None and not emptyok:
2009 2018 raise util.Abort(_("received changelog group is empty"))
2010 2019 clend = len(cl)
2011 2020 changesets = clend - clstart
2012 2021
2013 2022 # pull off the manifest group
2014 2023 self.ui.status(_("adding manifests\n"))
2015 2024 chunkiter = changegroup.chunkiter(source)
2016 2025 # no need to check for empty manifest group here:
2017 2026 # if the result of the merge of 1 and 2 is the same in 3 and 4,
2018 2027 # no new manifest will be created and the manifest group will
2019 2028 # be empty during the pull
2020 2029 self.manifest.addgroup(chunkiter, revmap, trp)
2021 2030
2022 2031 # process the files
2023 2032 self.ui.status(_("adding file changes\n"))
2024 2033 while 1:
2025 2034 f = changegroup.getchunk(source)
2026 2035 if not f:
2027 2036 break
2028 2037 self.ui.debug(_("adding %s revisions\n") % f)
2029 2038 fl = self.file(f)
2030 2039 o = len(fl)
2031 2040 chunkiter = changegroup.chunkiter(source)
2032 2041 if fl.addgroup(chunkiter, revmap, trp) is None:
2033 2042 raise util.Abort(_("received file revlog group is empty"))
2034 2043 revisions += len(fl) - o
2035 2044 files += 1
2036 2045
2037 2046 newheads = len(cl.heads())
2038 2047 heads = ""
2039 2048 if oldheads and newheads != oldheads:
2040 2049 heads = _(" (%+d heads)") % (newheads - oldheads)
2041 2050
2042 2051 self.ui.status(_("added %d changesets"
2043 2052 " with %d changes to %d files%s\n")
2044 2053 % (changesets, revisions, files, heads))
2045 2054
2046 2055 if changesets > 0:
2047 2056 p = lambda: cl.writepending() and self.root or ""
2048 2057 self.hook('pretxnchangegroup', throw=True,
2049 2058 node=hex(cl.node(clstart)), source=srctype,
2050 2059 url=url, pending=p)
2051 2060
2052 2061 # make changelog see real files again
2053 2062 cl.finalize(trp)
2054 2063
2055 2064 tr.close()
2056 2065 finally:
2057 2066 del tr
2058 2067
2059 2068 if changesets > 0:
2060 2069 # forcefully update the on-disk branch cache
2061 2070 self.ui.debug(_("updating the branch cache\n"))
2062 2071 self.branchtags()
2063 2072 self.hook("changegroup", node=hex(cl.node(clstart)),
2064 2073 source=srctype, url=url)
2065 2074
2066 2075 for i in xrange(clstart, clend):
2067 2076 self.hook("incoming", node=hex(cl.node(i)),
2068 2077 source=srctype, url=url)
2069 2078
2070 2079 # never return 0 here:
2071 2080 if newheads < oldheads:
2072 2081 return newheads - oldheads - 1
2073 2082 else:
2074 2083 return newheads - oldheads + 1
2075 2084
2076 2085
2077 2086 def stream_in(self, remote):
2078 2087 fp = remote.stream_out()
2079 2088 l = fp.readline()
2080 2089 try:
2081 2090 resp = int(l)
2082 2091 except ValueError:
2083 2092 raise error.ResponseError(
2084 2093 _('Unexpected response from remote server:'), l)
2085 2094 if resp == 1:
2086 2095 raise util.Abort(_('operation forbidden by server'))
2087 2096 elif resp == 2:
2088 2097 raise util.Abort(_('locking the remote repository failed'))
2089 2098 elif resp != 0:
2090 2099 raise util.Abort(_('the server sent an unknown error code'))
2091 2100 self.ui.status(_('streaming all changes\n'))
2092 2101 l = fp.readline()
2093 2102 try:
2094 2103 total_files, total_bytes = map(int, l.split(' ', 1))
2095 2104 except (ValueError, TypeError):
2096 2105 raise error.ResponseError(
2097 2106 _('Unexpected response from remote server:'), l)
2098 2107 self.ui.status(_('%d files to transfer, %s of data\n') %
2099 2108 (total_files, util.bytecount(total_bytes)))
2100 2109 start = time.time()
2101 2110 for i in xrange(total_files):
2102 2111 # XXX doesn't support '\n' or '\r' in filenames
2103 2112 l = fp.readline()
2104 2113 try:
2105 2114 name, size = l.split('\0', 1)
2106 2115 size = int(size)
2107 2116 except (ValueError, TypeError):
2108 2117 raise error.ResponseError(
2109 2118 _('Unexpected response from remote server:'), l)
2110 2119 self.ui.debug(_('adding %s (%s)\n') % (name, util.bytecount(size)))
2111 2120 # for backwards compat, name was partially encoded
2112 2121 ofp = self.sopener(store.decodedir(name), 'w')
2113 2122 for chunk in util.filechunkiter(fp, limit=size):
2114 2123 ofp.write(chunk)
2115 2124 ofp.close()
2116 2125 elapsed = time.time() - start
2117 2126 if elapsed <= 0:
2118 2127 elapsed = 0.001
2119 2128 self.ui.status(_('transferred %s in %.1f seconds (%s/sec)\n') %
2120 2129 (util.bytecount(total_bytes), elapsed,
2121 2130 util.bytecount(total_bytes / elapsed)))
2122 2131 self.invalidate()
2123 2132 return len(self.heads()) + 1
2124 2133
2125 2134 def clone(self, remote, heads=[], stream=False):
2126 2135 '''clone remote repository.
2127 2136
2128 2137 keyword arguments:
2129 2138 heads: list of revs to clone (forces use of pull)
2130 2139 stream: use streaming clone if possible'''
2131 2140
2132 2141 # now, all clients that can request uncompressed clones can
2133 2142 # read repo formats supported by all servers that can serve
2134 2143 # them.
2135 2144
2136 2145 # if revlog format changes, client will have to check version
2137 2146 # and format flags on "stream" capability, and use
2138 2147 # uncompressed only if compatible.
2139 2148
2140 2149 if stream and not heads and remote.capable('stream'):
2141 2150 return self.stream_in(remote)
2142 2151 return self.pull(remote, heads)
2143 2152
2144 2153 # used to avoid circular references so destructors work
2145 2154 def aftertrans(files):
2146 2155 renamefiles = [tuple(t) for t in files]
2147 2156 def a():
2148 2157 for src, dest in renamefiles:
2149 2158 util.rename(src, dest)
2150 2159 return a
2151 2160
2152 2161 def instance(ui, path, create):
2153 2162 return localrepository(ui, util.drop_scheme('file', path), create)
2154 2163
2155 2164 def islocal(path):
2156 2165 return True
@@ -1,1377 +1,1378 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, incorporated herein by reference.
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 # import stuff from node for others to import from revlog
15 15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 16 from i18n import _
17 17 import changegroup, ancestor, mdiff, parsers, error, util
18 18 import struct, zlib, errno
19 19
20 20 _pack = struct.pack
21 21 _unpack = struct.unpack
22 22 _compress = zlib.compress
23 23 _decompress = zlib.decompress
24 24 _sha = util.sha1
25 25
26 26 # revlog flags
27 27 REVLOGV0 = 0
28 28 REVLOGNG = 1
29 29 REVLOGNGINLINEDATA = (1 << 16)
30 30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 31 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33 33
34 34 _prereadsize = 1048576
35 35
36 36 RevlogError = error.RevlogError
37 37 LookupError = error.LookupError
38 38
39 39 def getoffset(q):
40 40 return int(q >> 16)
41 41
42 42 def gettype(q):
43 43 return int(q & 0xFFFF)
44 44
45 45 def offset_type(offset, type):
46 46 return long(long(offset) << 16 | type)
47 47
48 48 nullhash = _sha(nullid)
49 49
50 50 def hash(text, p1, p2):
51 51 """generate a hash from the given text and its parent hashes
52 52
53 53 This hash combines both the current file contents and its history
54 54 in a manner that makes it easy to distinguish nodes with the same
55 55 content in the revision graph.
56 56 """
57 57 # As of now, if one of the parent node is null, p2 is null
58 58 if p2 == nullid:
59 59 # deep copy of a hash is faster than creating one
60 60 s = nullhash.copy()
61 61 s.update(p1)
62 62 else:
63 63 # none of the parent nodes are nullid
64 64 l = [p1, p2]
65 65 l.sort()
66 66 s = _sha(l[0])
67 67 s.update(l[1])
68 68 s.update(text)
69 69 return s.digest()
70 70
71 71 def compress(text):
72 72 """ generate a possibly-compressed representation of text """
73 73 if not text:
74 74 return ("", text)
75 75 l = len(text)
76 76 bin = None
77 77 if l < 44:
78 78 pass
79 79 elif l > 1000000:
80 80 # zlib makes an internal copy, thus doubling memory usage for
81 81 # large files, so lets do this in pieces
82 82 z = zlib.compressobj()
83 83 p = []
84 84 pos = 0
85 85 while pos < l:
86 86 pos2 = pos + 2**20
87 87 p.append(z.compress(text[pos:pos2]))
88 88 pos = pos2
89 89 p.append(z.flush())
90 90 if sum(map(len, p)) < l:
91 91 bin = "".join(p)
92 92 else:
93 93 bin = _compress(text)
94 94 if bin is None or len(bin) > l:
95 95 if text[0] == '\0':
96 96 return ("", text)
97 97 return ('u', text)
98 98 return ("", bin)
99 99
100 100 def decompress(bin):
101 101 """ decompress the given input """
102 102 if not bin:
103 103 return bin
104 104 t = bin[0]
105 105 if t == '\0':
106 106 return bin
107 107 if t == 'x':
108 108 return _decompress(bin)
109 109 if t == 'u':
110 110 return bin[1:]
111 111 raise RevlogError(_("unknown compression type %r") % t)
112 112
113 113 class lazyparser(object):
114 114 """
115 115 this class avoids the need to parse the entirety of large indices
116 116 """
117 117
118 118 # lazyparser is not safe to use on windows if win32 extensions not
119 119 # available. it keeps file handle open, which make it not possible
120 120 # to break hardlinks on local cloned repos.
121 121
122 122 def __init__(self, dataf):
123 123 try:
124 124 size = util.fstat(dataf).st_size
125 125 except AttributeError:
126 126 size = 0
127 127 self.dataf = dataf
128 128 self.s = struct.calcsize(indexformatng)
129 129 self.datasize = size
130 130 self.l = size/self.s
131 131 self.index = [None] * self.l
132 132 self.map = {nullid: nullrev}
133 133 self.allmap = 0
134 134 self.all = 0
135 135 self.mapfind_count = 0
136 136
137 137 def loadmap(self):
138 138 """
139 139 during a commit, we need to make sure the rev being added is
140 140 not a duplicate. This requires loading the entire index,
141 141 which is fairly slow. loadmap can load up just the node map,
142 142 which takes much less time.
143 143 """
144 144 if self.allmap:
145 145 return
146 146 end = self.datasize
147 147 self.allmap = 1
148 148 cur = 0
149 149 count = 0
150 150 blocksize = self.s * 256
151 151 self.dataf.seek(0)
152 152 while cur < end:
153 153 data = self.dataf.read(blocksize)
154 154 off = 0
155 155 for x in xrange(256):
156 156 n = data[off + ngshaoffset:off + ngshaoffset + 20]
157 157 self.map[n] = count
158 158 count += 1
159 159 if count >= self.l:
160 160 break
161 161 off += self.s
162 162 cur += blocksize
163 163
164 164 def loadblock(self, blockstart, blocksize, data=None):
165 165 if self.all:
166 166 return
167 167 if data is None:
168 168 self.dataf.seek(blockstart)
169 169 if blockstart + blocksize > self.datasize:
170 170 # the revlog may have grown since we've started running,
171 171 # but we don't have space in self.index for more entries.
172 172 # limit blocksize so that we don't get too much data.
173 173 blocksize = max(self.datasize - blockstart, 0)
174 174 data = self.dataf.read(blocksize)
175 175 lend = len(data) / self.s
176 176 i = blockstart / self.s
177 177 off = 0
178 178 # lazyindex supports __delitem__
179 179 if lend > len(self.index) - i:
180 180 lend = len(self.index) - i
181 181 for x in xrange(lend):
182 182 if self.index[i + x] is None:
183 183 b = data[off : off + self.s]
184 184 self.index[i + x] = b
185 185 n = b[ngshaoffset:ngshaoffset + 20]
186 186 self.map[n] = i + x
187 187 off += self.s
188 188
189 189 def findnode(self, node):
190 190 """search backwards through the index file for a specific node"""
191 191 if self.allmap:
192 192 return None
193 193
194 194 # hg log will cause many many searches for the manifest
195 195 # nodes. After we get called a few times, just load the whole
196 196 # thing.
197 197 if self.mapfind_count > 8:
198 198 self.loadmap()
199 199 if node in self.map:
200 200 return node
201 201 return None
202 202 self.mapfind_count += 1
203 203 last = self.l - 1
204 204 while self.index[last] != None:
205 205 if last == 0:
206 206 self.all = 1
207 207 self.allmap = 1
208 208 return None
209 209 last -= 1
210 210 end = (last + 1) * self.s
211 211 blocksize = self.s * 256
212 212 while end >= 0:
213 213 start = max(end - blocksize, 0)
214 214 self.dataf.seek(start)
215 215 data = self.dataf.read(end - start)
216 216 findend = end - start
217 217 while True:
218 218 # we're searching backwards, so we have to make sure
219 219 # we don't find a changeset where this node is a parent
220 220 off = data.find(node, 0, findend)
221 221 findend = off
222 222 if off >= 0:
223 223 i = off / self.s
224 224 off = i * self.s
225 225 n = data[off + ngshaoffset:off + ngshaoffset + 20]
226 226 if n == node:
227 227 self.map[n] = i + start / self.s
228 228 return node
229 229 else:
230 230 break
231 231 end -= blocksize
232 232 return None
233 233
234 234 def loadindex(self, i=None, end=None):
235 235 if self.all:
236 236 return
237 237 all = False
238 238 if i is None:
239 239 blockstart = 0
240 240 blocksize = (65536 / self.s) * self.s
241 241 end = self.datasize
242 242 all = True
243 243 else:
244 244 if end:
245 245 blockstart = i * self.s
246 246 end = end * self.s
247 247 blocksize = end - blockstart
248 248 else:
249 249 blockstart = (i & ~1023) * self.s
250 250 blocksize = self.s * 1024
251 251 end = blockstart + blocksize
252 252 while blockstart < end:
253 253 self.loadblock(blockstart, blocksize)
254 254 blockstart += blocksize
255 255 if all:
256 256 self.all = True
257 257
258 258 class lazyindex(object):
259 259 """a lazy version of the index array"""
260 260 def __init__(self, parser):
261 261 self.p = parser
262 262 def __len__(self):
263 263 return len(self.p.index)
264 264 def load(self, pos):
265 265 if pos < 0:
266 266 pos += len(self.p.index)
267 267 self.p.loadindex(pos)
268 268 return self.p.index[pos]
269 269 def __getitem__(self, pos):
270 270 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
271 271 def __setitem__(self, pos, item):
272 272 self.p.index[pos] = _pack(indexformatng, *item)
273 273 def __delitem__(self, pos):
274 274 del self.p.index[pos]
275 275 def insert(self, pos, e):
276 276 self.p.index.insert(pos, _pack(indexformatng, *e))
277 277 def append(self, e):
278 278 self.p.index.append(_pack(indexformatng, *e))
279 279
280 280 class lazymap(object):
281 281 """a lazy version of the node map"""
282 282 def __init__(self, parser):
283 283 self.p = parser
284 284 def load(self, key):
285 285 n = self.p.findnode(key)
286 286 if n is None:
287 287 raise KeyError(key)
288 288 def __contains__(self, key):
289 289 if key in self.p.map:
290 290 return True
291 291 self.p.loadmap()
292 292 return key in self.p.map
293 293 def __iter__(self):
294 294 yield nullid
295 295 for i in xrange(self.p.l):
296 296 ret = self.p.index[i]
297 297 if not ret:
298 298 self.p.loadindex(i)
299 299 ret = self.p.index[i]
300 300 if isinstance(ret, str):
301 301 ret = _unpack(indexformatng, ret)
302 302 yield ret[7]
303 303 def __getitem__(self, key):
304 304 try:
305 305 return self.p.map[key]
306 306 except KeyError:
307 307 try:
308 308 self.load(key)
309 309 return self.p.map[key]
310 310 except KeyError:
311 311 raise KeyError("node " + hex(key))
312 312 def __setitem__(self, key, val):
313 313 self.p.map[key] = val
314 314 def __delitem__(self, key):
315 315 del self.p.map[key]
316 316
317 317 indexformatv0 = ">4l20s20s20s"
318 318 v0shaoffset = 56
319 319
320 320 class revlogoldio(object):
321 321 def __init__(self):
322 322 self.size = struct.calcsize(indexformatv0)
323 323
324 324 def parseindex(self, fp, data, inline):
325 325 s = self.size
326 326 index = []
327 327 nodemap = {nullid: nullrev}
328 328 n = off = 0
329 329 if len(data) == _prereadsize:
330 330 data += fp.read() # read the rest
331 331 l = len(data)
332 332 while off + s <= l:
333 333 cur = data[off:off + s]
334 334 off += s
335 335 e = _unpack(indexformatv0, cur)
336 336 # transform to revlogv1 format
337 337 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
338 338 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
339 339 index.append(e2)
340 340 nodemap[e[6]] = n
341 341 n += 1
342 342
343 343 return index, nodemap, None
344 344
345 345 def packentry(self, entry, node, version, rev):
346 346 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
347 347 node(entry[5]), node(entry[6]), entry[7])
348 348 return _pack(indexformatv0, *e2)
349 349
350 350 # index ng:
351 351 # 6 bytes offset
352 352 # 2 bytes flags
353 353 # 4 bytes compressed length
354 354 # 4 bytes uncompressed length
355 355 # 4 bytes: base rev
356 356 # 4 bytes link rev
357 357 # 4 bytes parent 1 rev
358 358 # 4 bytes parent 2 rev
359 359 # 32 bytes: nodeid
360 360 indexformatng = ">Qiiiiii20s12x"
361 361 ngshaoffset = 32
362 362 versionformat = ">I"
363 363
364 364 class revlogio(object):
365 365 def __init__(self):
366 366 self.size = struct.calcsize(indexformatng)
367 367
368 368 def parseindex(self, fp, data, inline):
369 369 if len(data) == _prereadsize:
370 370 if util.openhardlinks() and not inline:
371 371 # big index, let's parse it on demand
372 372 parser = lazyparser(fp)
373 373 index = lazyindex(parser)
374 374 nodemap = lazymap(parser)
375 375 e = list(index[0])
376 376 type = gettype(e[0])
377 377 e[0] = offset_type(0, type)
378 378 index[0] = e
379 379 return index, nodemap, None
380 380 else:
381 381 data += fp.read()
382 382
383 383 # call the C implementation to parse the index data
384 384 index, nodemap, cache = parsers.parse_index(data, inline)
385 385 return index, nodemap, cache
386 386
387 387 def packentry(self, entry, node, version, rev):
388 388 p = _pack(indexformatng, *entry)
389 389 if rev == 0:
390 390 p = _pack(versionformat, version) + p[4:]
391 391 return p
392 392
393 393 class revlog(object):
394 394 """
395 395 the underlying revision storage object
396 396
397 397 A revlog consists of two parts, an index and the revision data.
398 398
399 399 The index is a file with a fixed record size containing
400 400 information on each revision, including its nodeid (hash), the
401 401 nodeids of its parents, the position and offset of its data within
402 402 the data file, and the revision it's based on. Finally, each entry
403 403 contains a linkrev entry that can serve as a pointer to external
404 404 data.
405 405
406 406 The revision data itself is a linear collection of data chunks.
407 407 Each chunk represents a revision and is usually represented as a
408 408 delta against the previous chunk. To bound lookup time, runs of
409 409 deltas are limited to about 2 times the length of the original
410 410 version data. This makes retrieval of a version proportional to
411 411 its size, or O(1) relative to the number of revisions.
412 412
413 413 Both pieces of the revlog are written to in an append-only
414 414 fashion, which means we never need to rewrite a file to insert or
415 415 remove data, and can use some simple techniques to avoid the need
416 416 for locking while reading.
417 417 """
418 418 def __init__(self, opener, indexfile):
419 419 """
420 420 create a revlog object
421 421
422 422 opener is a function that abstracts the file opening operation
423 423 and can be used to implement COW semantics or the like.
424 424 """
425 425 self.indexfile = indexfile
426 426 self.datafile = indexfile[:-2] + ".d"
427 427 self.opener = opener
428 428 self._cache = None
429 429 self._chunkcache = (0, '')
430 430 self.nodemap = {nullid: nullrev}
431 431 self.index = []
432 432
433 433 v = REVLOG_DEFAULT_VERSION
434 434 if hasattr(opener, "defversion"):
435 435 v = opener.defversion
436 436 if v & REVLOGNG:
437 437 v |= REVLOGNGINLINEDATA
438 438
439 439 i = ''
440 440 try:
441 441 f = self.opener(self.indexfile)
442 442 i = f.read(_prereadsize)
443 443 if len(i) > 0:
444 444 v = struct.unpack(versionformat, i[:4])[0]
445 445 except IOError, inst:
446 446 if inst.errno != errno.ENOENT:
447 447 raise
448 448
449 449 self.version = v
450 450 self._inline = v & REVLOGNGINLINEDATA
451 451 flags = v & ~0xFFFF
452 452 fmt = v & 0xFFFF
453 453 if fmt == REVLOGV0 and flags:
454 454 raise RevlogError(_("index %s unknown flags %#04x for format v0")
455 455 % (self.indexfile, flags >> 16))
456 456 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
457 457 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
458 458 % (self.indexfile, flags >> 16))
459 459 elif fmt > REVLOGNG:
460 460 raise RevlogError(_("index %s unknown format %d")
461 461 % (self.indexfile, fmt))
462 462
463 463 self._io = revlogio()
464 464 if self.version == REVLOGV0:
465 465 self._io = revlogoldio()
466 466 if i:
467 467 try:
468 468 d = self._io.parseindex(f, i, self._inline)
469 469 except (ValueError, IndexError), e:
470 470 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
471 471 self.index, self.nodemap, self._chunkcache = d
472 472 if not self._chunkcache:
473 473 self._chunkclear()
474 474
475 475 # add the magic null revision at -1 (if it hasn't been done already)
476 476 if (self.index == [] or isinstance(self.index, lazyindex) or
477 477 self.index[-1][7] != nullid) :
478 478 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
479 479
480 480 def _loadindex(self, start, end):
481 481 """load a block of indexes all at once from the lazy parser"""
482 482 if isinstance(self.index, lazyindex):
483 483 self.index.p.loadindex(start, end)
484 484
485 485 def _loadindexmap(self):
486 486 """loads both the map and the index from the lazy parser"""
487 487 if isinstance(self.index, lazyindex):
488 488 p = self.index.p
489 489 p.loadindex()
490 490 self.nodemap = p.map
491 491
492 492 def _loadmap(self):
493 493 """loads the map from the lazy parser"""
494 494 if isinstance(self.nodemap, lazymap):
495 495 self.nodemap.p.loadmap()
496 496 self.nodemap = self.nodemap.p.map
497 497
498 498 def tip(self):
499 499 return self.node(len(self.index) - 2)
500 500 def __len__(self):
501 501 return len(self.index) - 1
502 502 def __iter__(self):
503 503 for i in xrange(len(self)):
504 504 yield i
505 505 def rev(self, node):
506 506 try:
507 507 return self.nodemap[node]
508 508 except KeyError:
509 509 raise LookupError(node, self.indexfile, _('no node'))
510 510 def node(self, rev):
511 511 return self.index[rev][7]
512 512 def linkrev(self, rev):
513 513 return self.index[rev][4]
514 514 def parents(self, node):
515 515 i = self.index
516 516 d = i[self.rev(node)]
517 517 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
518 518 def parentrevs(self, rev):
519 519 return self.index[rev][5:7]
520 520 def start(self, rev):
521 521 return int(self.index[rev][0] >> 16)
522 522 def end(self, rev):
523 523 return self.start(rev) + self.length(rev)
524 524 def length(self, rev):
525 525 return self.index[rev][1]
526 526 def base(self, rev):
527 527 return self.index[rev][3]
528 528
529 529 def size(self, rev):
530 530 """return the length of the uncompressed text for a given revision"""
531 531 l = self.index[rev][2]
532 532 if l >= 0:
533 533 return l
534 534
535 535 t = self.revision(self.node(rev))
536 536 return len(t)
537 537
538 538 # Alternate implementation. The advantage to this code is it
539 539 # will be faster for a single revision. However, the results
540 540 # are not cached, so finding the size of every revision will
541 541 # be slower.
542 542 #
543 543 # if self.cache and self.cache[1] == rev:
544 544 # return len(self.cache[2])
545 545 #
546 546 # base = self.base(rev)
547 547 # if self.cache and self.cache[1] >= base and self.cache[1] < rev:
548 548 # base = self.cache[1]
549 549 # text = self.cache[2]
550 550 # else:
551 551 # text = self.revision(self.node(base))
552 552 #
553 553 # l = len(text)
554 554 # for x in xrange(base + 1, rev + 1):
555 555 # l = mdiff.patchedsize(l, self._chunk(x))
556 556 # return l
557 557
558 558 def reachable(self, node, stop=None):
559 559 """return the set of all nodes ancestral to a given node, including
560 560 the node itself, stopping when stop is matched"""
561 561 reachable = set((node,))
562 562 visit = [node]
563 563 if stop:
564 564 stopn = self.rev(stop)
565 565 else:
566 566 stopn = 0
567 567 while visit:
568 568 n = visit.pop(0)
569 569 if n == stop:
570 570 continue
571 571 if n == nullid:
572 572 continue
573 573 for p in self.parents(n):
574 574 if self.rev(p) < stopn:
575 575 continue
576 576 if p not in reachable:
577 577 reachable.add(p)
578 578 visit.append(p)
579 579 return reachable
580 580
581 581 def ancestors(self, *revs):
582 582 'Generate the ancestors of revs using a breadth-first visit'
583 583 visit = list(revs)
584 584 seen = set([nullrev])
585 585 while visit:
586 586 for parent in self.parentrevs(visit.pop(0)):
587 587 if parent not in seen:
588 588 visit.append(parent)
589 589 seen.add(parent)
590 590 yield parent
591 591
592 592 def descendants(self, *revs):
593 593 'Generate the descendants of revs in topological order'
594 594 seen = set(revs)
595 595 for i in xrange(min(revs) + 1, len(self)):
596 596 for x in self.parentrevs(i):
597 597 if x != nullrev and x in seen:
598 598 seen.add(i)
599 599 yield i
600 600 break
601 601
602 602 def findmissing(self, common=None, heads=None):
603 603 '''
604 604 returns the topologically sorted list of nodes from the set:
605 605 missing = (ancestors(heads) \ ancestors(common))
606 606
607 607 where ancestors() is the set of ancestors from heads, heads included
608 608
609 609 if heads is None, the heads of the revlog are used
610 610 if common is None, nullid is assumed to be a common node
611 611 '''
612 612 if common is None:
613 613 common = [nullid]
614 614 if heads is None:
615 615 heads = self.heads()
616 616
617 617 common = [self.rev(n) for n in common]
618 618 heads = [self.rev(n) for n in heads]
619 619
620 620 # we want the ancestors, but inclusive
621 621 has = set(self.ancestors(*common))
622 622 has.add(nullrev)
623 623 has.update(common)
624 624
625 625 # take all ancestors from heads that aren't in has
626 626 missing = set()
627 627 visit = [r for r in heads if r not in has]
628 628 while visit:
629 629 r = visit.pop(0)
630 630 if r in missing:
631 631 continue
632 632 else:
633 633 missing.add(r)
634 634 for p in self.parentrevs(r):
635 635 if p not in has:
636 636 visit.append(p)
637 637 missing = list(missing)
638 638 missing.sort()
639 639 return [self.node(r) for r in missing]
640 640
641 641 def nodesbetween(self, roots=None, heads=None):
642 642 """Return a tuple containing three elements. Elements 1 and 2 contain
643 643 a final list bases and heads after all the unreachable ones have been
644 644 pruned. Element 0 contains a topologically sorted list of all
645 645
646 646 nodes that satisfy these constraints:
647 647 1. All nodes must be descended from a node in roots (the nodes on
648 648 roots are considered descended from themselves).
649 649 2. All nodes must also be ancestors of a node in heads (the nodes in
650 650 heads are considered to be their own ancestors).
651 651
652 652 If roots is unspecified, nullid is assumed as the only root.
653 653 If heads is unspecified, it is taken to be the output of the
654 654 heads method (i.e. a list of all nodes in the repository that
655 655 have no children)."""
656 656 nonodes = ([], [], [])
657 657 if roots is not None:
658 658 roots = list(roots)
659 659 if not roots:
660 660 return nonodes
661 661 lowestrev = min([self.rev(n) for n in roots])
662 662 else:
663 663 roots = [nullid] # Everybody's a descendent of nullid
664 664 lowestrev = nullrev
665 665 if (lowestrev == nullrev) and (heads is None):
666 666 # We want _all_ the nodes!
667 667 return ([self.node(r) for r in self], [nullid], list(self.heads()))
668 668 if heads is None:
669 669 # All nodes are ancestors, so the latest ancestor is the last
670 670 # node.
671 671 highestrev = len(self) - 1
672 672 # Set ancestors to None to signal that every node is an ancestor.
673 673 ancestors = None
674 674 # Set heads to an empty dictionary for later discovery of heads
675 675 heads = {}
676 676 else:
677 677 heads = list(heads)
678 678 if not heads:
679 679 return nonodes
680 680 ancestors = set()
681 681 # Turn heads into a dictionary so we can remove 'fake' heads.
682 682 # Also, later we will be using it to filter out the heads we can't
683 683 # find from roots.
684 684 heads = dict.fromkeys(heads, 0)
685 685 # Start at the top and keep marking parents until we're done.
686 686 nodestotag = set(heads)
687 687 # Remember where the top was so we can use it as a limit later.
688 688 highestrev = max([self.rev(n) for n in nodestotag])
689 689 while nodestotag:
690 690 # grab a node to tag
691 691 n = nodestotag.pop()
692 692 # Never tag nullid
693 693 if n == nullid:
694 694 continue
695 695 # A node's revision number represents its place in a
696 696 # topologically sorted list of nodes.
697 697 r = self.rev(n)
698 698 if r >= lowestrev:
699 699 if n not in ancestors:
700 700 # If we are possibly a descendent of one of the roots
701 701 # and we haven't already been marked as an ancestor
702 702 ancestors.add(n) # Mark as ancestor
703 703 # Add non-nullid parents to list of nodes to tag.
704 704 nodestotag.update([p for p in self.parents(n) if
705 705 p != nullid])
706 706 elif n in heads: # We've seen it before, is it a fake head?
707 707 # So it is, real heads should not be the ancestors of
708 708 # any other heads.
709 709 heads.pop(n)
710 710 if not ancestors:
711 711 return nonodes
712 712 # Now that we have our set of ancestors, we want to remove any
713 713 # roots that are not ancestors.
714 714
715 715 # If one of the roots was nullid, everything is included anyway.
716 716 if lowestrev > nullrev:
717 717 # But, since we weren't, let's recompute the lowest rev to not
718 718 # include roots that aren't ancestors.
719 719
720 720 # Filter out roots that aren't ancestors of heads
721 721 roots = [n for n in roots if n in ancestors]
722 722 # Recompute the lowest revision
723 723 if roots:
724 724 lowestrev = min([self.rev(n) for n in roots])
725 725 else:
726 726 # No more roots? Return empty list
727 727 return nonodes
728 728 else:
729 729 # We are descending from nullid, and don't need to care about
730 730 # any other roots.
731 731 lowestrev = nullrev
732 732 roots = [nullid]
733 733 # Transform our roots list into a set.
734 734 descendents = set(roots)
735 735 # Also, keep the original roots so we can filter out roots that aren't
736 736 # 'real' roots (i.e. are descended from other roots).
737 737 roots = descendents.copy()
738 738 # Our topologically sorted list of output nodes.
739 739 orderedout = []
740 740 # Don't start at nullid since we don't want nullid in our output list,
741 741 # and if nullid shows up in descedents, empty parents will look like
742 742 # they're descendents.
743 743 for r in xrange(max(lowestrev, 0), highestrev + 1):
744 744 n = self.node(r)
745 745 isdescendent = False
746 746 if lowestrev == nullrev: # Everybody is a descendent of nullid
747 747 isdescendent = True
748 748 elif n in descendents:
749 749 # n is already a descendent
750 750 isdescendent = True
751 751 # This check only needs to be done here because all the roots
752 752 # will start being marked is descendents before the loop.
753 753 if n in roots:
754 754 # If n was a root, check if it's a 'real' root.
755 755 p = tuple(self.parents(n))
756 756 # If any of its parents are descendents, it's not a root.
757 757 if (p[0] in descendents) or (p[1] in descendents):
758 758 roots.remove(n)
759 759 else:
760 760 p = tuple(self.parents(n))
761 761 # A node is a descendent if either of its parents are
762 762 # descendents. (We seeded the dependents list with the roots
763 763 # up there, remember?)
764 764 if (p[0] in descendents) or (p[1] in descendents):
765 765 descendents.add(n)
766 766 isdescendent = True
767 767 if isdescendent and ((ancestors is None) or (n in ancestors)):
768 768 # Only include nodes that are both descendents and ancestors.
769 769 orderedout.append(n)
770 770 if (ancestors is not None) and (n in heads):
771 771 # We're trying to figure out which heads are reachable
772 772 # from roots.
773 773 # Mark this head as having been reached
774 774 heads[n] = 1
775 775 elif ancestors is None:
776 776 # Otherwise, we're trying to discover the heads.
777 777 # Assume this is a head because if it isn't, the next step
778 778 # will eventually remove it.
779 779 heads[n] = 1
780 780 # But, obviously its parents aren't.
781 781 for p in self.parents(n):
782 782 heads.pop(p, None)
783 783 heads = [n for n in heads.iterkeys() if heads[n] != 0]
784 784 roots = list(roots)
785 785 assert orderedout
786 786 assert roots
787 787 assert heads
788 788 return (orderedout, roots, heads)
789 789
790 790 def heads(self, start=None, stop=None):
791 791 """return the list of all nodes that have no children
792 792
793 793 if start is specified, only heads that are descendants of
794 794 start will be returned
795 795 if stop is specified, it will consider all the revs from stop
796 796 as if they had no children
797 797 """
798 798 if start is None and stop is None:
799 799 count = len(self)
800 800 if not count:
801 801 return [nullid]
802 802 ishead = [1] * (count + 1)
803 803 index = self.index
804 804 for r in xrange(count):
805 805 e = index[r]
806 806 ishead[e[5]] = ishead[e[6]] = 0
807 807 return [self.node(r) for r in xrange(count) if ishead[r]]
808 808
809 809 if start is None:
810 810 start = nullid
811 811 if stop is None:
812 812 stop = []
813 813 stoprevs = set([self.rev(n) for n in stop])
814 814 startrev = self.rev(start)
815 815 reachable = set((startrev,))
816 816 heads = set((startrev,))
817 817
818 818 parentrevs = self.parentrevs
819 819 for r in xrange(startrev + 1, len(self)):
820 820 for p in parentrevs(r):
821 821 if p in reachable:
822 822 if r not in stoprevs:
823 823 reachable.add(r)
824 824 heads.add(r)
825 825 if p in heads and p not in stoprevs:
826 826 heads.remove(p)
827 827
828 828 return [self.node(r) for r in heads]
829 829
830 830 def children(self, node):
831 831 """find the children of a given node"""
832 832 c = []
833 833 p = self.rev(node)
834 834 for r in range(p + 1, len(self)):
835 835 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
836 836 if prevs:
837 837 for pr in prevs:
838 838 if pr == p:
839 839 c.append(self.node(r))
840 840 elif p == nullrev:
841 841 c.append(self.node(r))
842 842 return c
843 843
844 844 def _match(self, id):
845 845 if isinstance(id, (long, int)):
846 846 # rev
847 847 return self.node(id)
848 848 if len(id) == 20:
849 849 # possibly a binary node
850 850 # odds of a binary node being all hex in ASCII are 1 in 10**25
851 851 try:
852 852 node = id
853 853 self.rev(node) # quick search the index
854 854 return node
855 855 except LookupError:
856 856 pass # may be partial hex id
857 857 try:
858 858 # str(rev)
859 859 rev = int(id)
860 860 if str(rev) != id:
861 861 raise ValueError
862 862 if rev < 0:
863 863 rev = len(self) + rev
864 864 if rev < 0 or rev >= len(self):
865 865 raise ValueError
866 866 return self.node(rev)
867 867 except (ValueError, OverflowError):
868 868 pass
869 869 if len(id) == 40:
870 870 try:
871 871 # a full hex nodeid?
872 872 node = bin(id)
873 873 self.rev(node)
874 874 return node
875 875 except (TypeError, LookupError):
876 876 pass
877 877
878 878 def _partialmatch(self, id):
879 879 if len(id) < 40:
880 880 try:
881 881 # hex(node)[:...]
882 882 l = len(id) // 2 # grab an even number of digits
883 883 bin_id = bin(id[:l*2])
884 884 nl = [n for n in self.nodemap if n[:l] == bin_id]
885 885 nl = [n for n in nl if hex(n).startswith(id)]
886 886 if len(nl) > 0:
887 887 if len(nl) == 1:
888 888 return nl[0]
889 889 raise LookupError(id, self.indexfile,
890 890 _('ambiguous identifier'))
891 891 return None
892 892 except TypeError:
893 893 pass
894 894
895 895 def lookup(self, id):
896 896 """locate a node based on:
897 897 - revision number or str(revision number)
898 898 - nodeid or subset of hex nodeid
899 899 """
900 900 n = self._match(id)
901 901 if n is not None:
902 902 return n
903 903 n = self._partialmatch(id)
904 904 if n:
905 905 return n
906 906
907 907 raise LookupError(id, self.indexfile, _('no match found'))
908 908
909 909 def cmp(self, node, text):
910 910 """compare text with a given file revision"""
911 911 p1, p2 = self.parents(node)
912 912 return hash(text, p1, p2) != node
913 913
914 914 def _addchunk(self, offset, data):
915 915 o, d = self._chunkcache
916 916 # try to add to existing cache
917 917 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
918 918 self._chunkcache = o, d + data
919 919 else:
920 920 self._chunkcache = offset, data
921 921
922 922 def _loadchunk(self, offset, length):
923 923 if self._inline:
924 924 df = self.opener(self.indexfile)
925 925 else:
926 926 df = self.opener(self.datafile)
927 927
928 928 readahead = max(65536, length)
929 929 df.seek(offset)
930 930 d = df.read(readahead)
931 931 self._addchunk(offset, d)
932 932 if readahead > length:
933 933 return d[:length]
934 934 return d
935 935
936 936 def _getchunk(self, offset, length):
937 937 o, d = self._chunkcache
938 938 l = len(d)
939 939
940 940 # is it in the cache?
941 941 cachestart = offset - o
942 942 cacheend = cachestart + length
943 943 if cachestart >= 0 and cacheend <= l:
944 944 if cachestart == 0 and cacheend == l:
945 945 return d # avoid a copy
946 946 return d[cachestart:cacheend]
947 947
948 948 return self._loadchunk(offset, length)
949 949
950 950 def _chunkraw(self, startrev, endrev):
951 951 start = self.start(startrev)
952 952 length = self.end(endrev) - start
953 953 if self._inline:
954 954 start += (startrev + 1) * self._io.size
955 955 return self._getchunk(start, length)
956 956
957 957 def _chunk(self, rev):
958 958 return decompress(self._chunkraw(rev, rev))
959 959
960 960 def _chunkclear(self):
961 961 self._chunkcache = (0, '')
962 962
963 963 def revdiff(self, rev1, rev2):
964 964 """return or calculate a delta between two revisions"""
965 965 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
966 966 return self._chunk(rev2)
967 967
968 968 return mdiff.textdiff(self.revision(self.node(rev1)),
969 969 self.revision(self.node(rev2)))
970 970
971 971 def revision(self, node):
972 972 """return an uncompressed revision of a given node"""
973 973 if node == nullid:
974 974 return ""
975 975 if self._cache and self._cache[0] == node:
976 976 return self._cache[2]
977 977
978 978 # look up what we need to read
979 979 text = None
980 980 rev = self.rev(node)
981 981 base = self.base(rev)
982 982
983 983 # check rev flags
984 984 if self.index[rev][0] & 0xFFFF:
985 985 raise RevlogError(_('incompatible revision flag %x') %
986 986 (self.index[rev][0] & 0xFFFF))
987 987
988 988 # do we have useful data cached?
989 989 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
990 990 base = self._cache[1]
991 991 text = self._cache[2]
992 992
993 993 self._loadindex(base, rev + 1)
994 994 self._chunkraw(base, rev)
995 995 if text is None:
996 996 text = self._chunk(base)
997 997
998 998 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
999 999 text = mdiff.patches(text, bins)
1000 1000 p1, p2 = self.parents(node)
1001 1001 if node != hash(text, p1, p2):
1002 1002 raise RevlogError(_("integrity check failed on %s:%d")
1003 1003 % (self.indexfile, rev))
1004 1004
1005 1005 self._cache = (node, rev, text)
1006 1006 return text
1007 1007
1008 1008 def checkinlinesize(self, tr, fp=None):
1009 1009 if not self._inline or (self.start(-2) + self.length(-2)) < 131072:
1010 1010 return
1011 1011
1012 1012 trinfo = tr.find(self.indexfile)
1013 1013 if trinfo is None:
1014 1014 raise RevlogError(_("%s not found in the transaction")
1015 1015 % self.indexfile)
1016 1016
1017 1017 trindex = trinfo[2]
1018 1018 dataoff = self.start(trindex)
1019 1019
1020 1020 tr.add(self.datafile, dataoff)
1021 1021
1022 1022 if fp:
1023 1023 fp.flush()
1024 1024 fp.close()
1025 1025
1026 1026 df = self.opener(self.datafile, 'w')
1027 1027 try:
1028 1028 for r in self:
1029 1029 df.write(self._chunkraw(r, r))
1030 1030 finally:
1031 1031 df.close()
1032 1032
1033 1033 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1034 1034 self.version &= ~(REVLOGNGINLINEDATA)
1035 1035 self._inline = False
1036 1036 for i in self:
1037 1037 e = self._io.packentry(self.index[i], self.node, self.version, i)
1038 1038 fp.write(e)
1039 1039
1040 1040 # if we don't call rename, the temp file will never replace the
1041 1041 # real index
1042 1042 fp.rename()
1043 1043
1044 1044 tr.replace(self.indexfile, trindex * self._io.size)
1045 1045 self._chunkclear()
1046 1046
1047 1047 def addrevision(self, text, transaction, link, p1, p2, d=None):
1048 1048 """add a revision to the log
1049 1049
1050 1050 text - the revision data to add
1051 1051 transaction - the transaction object used for rollback
1052 1052 link - the linkrev data to add
1053 1053 p1, p2 - the parent nodeids of the revision
1054 1054 d - an optional precomputed delta
1055 1055 """
1056 1056 dfh = None
1057 1057 if not self._inline:
1058 1058 dfh = self.opener(self.datafile, "a")
1059 1059 ifh = self.opener(self.indexfile, "a+")
1060 1060 try:
1061 1061 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1062 1062 finally:
1063 1063 if dfh:
1064 1064 dfh.close()
1065 1065 ifh.close()
1066 1066
1067 1067 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1068 1068 node = hash(text, p1, p2)
1069 1069 if node in self.nodemap:
1070 1070 return node
1071 1071
1072 1072 curr = len(self)
1073 1073 prev = curr - 1
1074 1074 base = self.base(prev)
1075 1075 offset = self.end(prev)
1076 1076
1077 1077 if curr:
1078 1078 if not d:
1079 1079 ptext = self.revision(self.node(prev))
1080 1080 d = mdiff.textdiff(ptext, text)
1081 1081 data = compress(d)
1082 1082 l = len(data[1]) + len(data[0])
1083 1083 dist = l + offset - self.start(base)
1084 1084
1085 1085 # full versions are inserted when the needed deltas
1086 1086 # become comparable to the uncompressed text
1087 1087 if not curr or dist > len(text) * 2:
1088 1088 data = compress(text)
1089 1089 l = len(data[1]) + len(data[0])
1090 1090 base = curr
1091 1091
1092 1092 e = (offset_type(offset, 0), l, len(text),
1093 1093 base, link, self.rev(p1), self.rev(p2), node)
1094 1094 self.index.insert(-1, e)
1095 1095 self.nodemap[node] = curr
1096 1096
1097 1097 entry = self._io.packentry(e, self.node, self.version, curr)
1098 1098 if not self._inline:
1099 1099 transaction.add(self.datafile, offset)
1100 1100 transaction.add(self.indexfile, curr * len(entry))
1101 1101 if data[0]:
1102 1102 dfh.write(data[0])
1103 1103 dfh.write(data[1])
1104 1104 dfh.flush()
1105 1105 ifh.write(entry)
1106 1106 else:
1107 1107 offset += curr * self._io.size
1108 1108 transaction.add(self.indexfile, offset, curr)
1109 1109 ifh.write(entry)
1110 1110 ifh.write(data[0])
1111 1111 ifh.write(data[1])
1112 1112 self.checkinlinesize(transaction, ifh)
1113 1113
1114 1114 if type(text) == str: # only accept immutable objects
1115 1115 self._cache = (node, curr, text)
1116 1116 return node
1117 1117
1118 1118 def ancestor(self, a, b):
1119 1119 """calculate the least common ancestor of nodes a and b"""
1120 1120
1121 1121 def parents(rev):
1122 1122 return [p for p in self.parentrevs(rev) if p != nullrev]
1123 1123
1124 1124 c = ancestor.ancestor(self.rev(a), self.rev(b), parents)
1125 1125 if c is None:
1126 1126 return nullid
1127 1127
1128 1128 return self.node(c)
1129 1129
1130 1130 def group(self, nodelist, lookup, infocollect=None):
1131 """calculate a delta group
1131 """Calculate a delta group, yielding a sequence of changegroup chunks
1132 (strings).
1132 1133
1133 1134 Given a list of changeset revs, return a set of deltas and
1134 1135 metadata corresponding to nodes. the first delta is
1135 1136 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1136 1137 have this parent as it has all history before these
1137 1138 changesets. parent is parent[0]
1138 1139 """
1139 1140
1140 1141 revs = [self.rev(n) for n in nodelist]
1141 1142
1142 1143 # if we don't have any revisions touched by these changesets, bail
1143 1144 if not revs:
1144 1145 yield changegroup.closechunk()
1145 1146 return
1146 1147
1147 1148 # add the parent of the first rev
1148 1149 p = self.parentrevs(revs[0])[0]
1149 1150 revs.insert(0, p)
1150 1151
1151 1152 # build deltas
1152 1153 for d in xrange(len(revs) - 1):
1153 1154 a, b = revs[d], revs[d + 1]
1154 1155 nb = self.node(b)
1155 1156
1156 1157 if infocollect is not None:
1157 1158 infocollect(nb)
1158 1159
1159 1160 p = self.parents(nb)
1160 1161 meta = nb + p[0] + p[1] + lookup(nb)
1161 1162 if a == -1:
1162 1163 d = self.revision(nb)
1163 1164 meta += mdiff.trivialdiffheader(len(d))
1164 1165 else:
1165 1166 d = self.revdiff(a, b)
1166 1167 yield changegroup.chunkheader(len(meta) + len(d))
1167 1168 yield meta
1168 1169 if len(d) > 2**20:
1169 1170 pos = 0
1170 1171 while pos < len(d):
1171 1172 pos2 = pos + 2 ** 18
1172 1173 yield d[pos:pos2]
1173 1174 pos = pos2
1174 1175 else:
1175 1176 yield d
1176 1177
1177 1178 yield changegroup.closechunk()
1178 1179
1179 1180 def addgroup(self, revs, linkmapper, transaction):
1180 1181 """
1181 1182 add a delta group
1182 1183
1183 1184 given a set of deltas, add them to the revision log. the
1184 1185 first delta is against its parent, which should be in our
1185 1186 log, the rest are against the previous delta.
1186 1187 """
1187 1188
1188 1189 #track the base of the current delta log
1189 1190 r = len(self)
1190 1191 t = r - 1
1191 1192 node = None
1192 1193
1193 1194 base = prev = nullrev
1194 1195 start = end = textlen = 0
1195 1196 if r:
1196 1197 end = self.end(t)
1197 1198
1198 1199 ifh = self.opener(self.indexfile, "a+")
1199 1200 isize = r * self._io.size
1200 1201 if self._inline:
1201 1202 transaction.add(self.indexfile, end + isize, r)
1202 1203 dfh = None
1203 1204 else:
1204 1205 transaction.add(self.indexfile, isize, r)
1205 1206 transaction.add(self.datafile, end)
1206 1207 dfh = self.opener(self.datafile, "a")
1207 1208
1208 1209 try:
1209 1210 # loop through our set of deltas
1210 1211 chain = None
1211 1212 for chunk in revs:
1212 1213 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1213 1214 link = linkmapper(cs)
1214 1215 if node in self.nodemap:
1215 1216 # this can happen if two branches make the same change
1216 1217 chain = node
1217 1218 continue
1218 1219 delta = buffer(chunk, 80)
1219 1220 del chunk
1220 1221
1221 1222 for p in (p1, p2):
1222 1223 if not p in self.nodemap:
1223 1224 raise LookupError(p, self.indexfile, _('unknown parent'))
1224 1225
1225 1226 if not chain:
1226 1227 # retrieve the parent revision of the delta chain
1227 1228 chain = p1
1228 1229 if not chain in self.nodemap:
1229 1230 raise LookupError(chain, self.indexfile, _('unknown base'))
1230 1231
1231 1232 # full versions are inserted when the needed deltas become
1232 1233 # comparable to the uncompressed text or when the previous
1233 1234 # version is not the one we have a delta against. We use
1234 1235 # the size of the previous full rev as a proxy for the
1235 1236 # current size.
1236 1237
1237 1238 if chain == prev:
1238 1239 cdelta = compress(delta)
1239 1240 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1240 1241 textlen = mdiff.patchedsize(textlen, delta)
1241 1242
1242 1243 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1243 1244 # flush our writes here so we can read it in revision
1244 1245 if dfh:
1245 1246 dfh.flush()
1246 1247 ifh.flush()
1247 1248 text = self.revision(chain)
1248 1249 if len(text) == 0:
1249 1250 # skip over trivial delta header
1250 1251 text = buffer(delta, 12)
1251 1252 else:
1252 1253 text = mdiff.patches(text, [delta])
1253 1254 del delta
1254 1255 chk = self._addrevision(text, transaction, link, p1, p2, None,
1255 1256 ifh, dfh)
1256 1257 if not dfh and not self._inline:
1257 1258 # addrevision switched from inline to conventional
1258 1259 # reopen the index
1259 1260 dfh = self.opener(self.datafile, "a")
1260 1261 ifh = self.opener(self.indexfile, "a")
1261 1262 if chk != node:
1262 1263 raise RevlogError(_("consistency error adding group"))
1263 1264 textlen = len(text)
1264 1265 else:
1265 1266 e = (offset_type(end, 0), cdeltalen, textlen, base,
1266 1267 link, self.rev(p1), self.rev(p2), node)
1267 1268 self.index.insert(-1, e)
1268 1269 self.nodemap[node] = r
1269 1270 entry = self._io.packentry(e, self.node, self.version, r)
1270 1271 if self._inline:
1271 1272 ifh.write(entry)
1272 1273 ifh.write(cdelta[0])
1273 1274 ifh.write(cdelta[1])
1274 1275 self.checkinlinesize(transaction, ifh)
1275 1276 if not self._inline:
1276 1277 dfh = self.opener(self.datafile, "a")
1277 1278 ifh = self.opener(self.indexfile, "a")
1278 1279 else:
1279 1280 dfh.write(cdelta[0])
1280 1281 dfh.write(cdelta[1])
1281 1282 ifh.write(entry)
1282 1283
1283 1284 t, r, chain, prev = r, r + 1, node, node
1284 1285 base = self.base(t)
1285 1286 start = self.start(base)
1286 1287 end = self.end(t)
1287 1288 finally:
1288 1289 if dfh:
1289 1290 dfh.close()
1290 1291 ifh.close()
1291 1292
1292 1293 return node
1293 1294
1294 1295 def strip(self, minlink, transaction):
1295 1296 """truncate the revlog on the first revision with a linkrev >= minlink
1296 1297
1297 1298 This function is called when we're stripping revision minlink and
1298 1299 its descendants from the repository.
1299 1300
1300 1301 We have to remove all revisions with linkrev >= minlink, because
1301 1302 the equivalent changelog revisions will be renumbered after the
1302 1303 strip.
1303 1304
1304 1305 So we truncate the revlog on the first of these revisions, and
1305 1306 trust that the caller has saved the revisions that shouldn't be
1306 1307 removed and that it'll readd them after this truncation.
1307 1308 """
1308 1309 if len(self) == 0:
1309 1310 return
1310 1311
1311 1312 if isinstance(self.index, lazyindex):
1312 1313 self._loadindexmap()
1313 1314
1314 1315 for rev in self:
1315 1316 if self.index[rev][4] >= minlink:
1316 1317 break
1317 1318 else:
1318 1319 return
1319 1320
1320 1321 # first truncate the files on disk
1321 1322 end = self.start(rev)
1322 1323 if not self._inline:
1323 1324 transaction.add(self.datafile, end)
1324 1325 end = rev * self._io.size
1325 1326 else:
1326 1327 end += rev * self._io.size
1327 1328
1328 1329 transaction.add(self.indexfile, end)
1329 1330
1330 1331 # then reset internal state in memory to forget those revisions
1331 1332 self._cache = None
1332 1333 self._chunkclear()
1333 1334 for x in xrange(rev, len(self)):
1334 1335 del self.nodemap[self.node(x)]
1335 1336
1336 1337 del self.index[rev:-1]
1337 1338
1338 1339 def checksize(self):
1339 1340 expected = 0
1340 1341 if len(self):
1341 1342 expected = max(0, self.end(len(self) - 1))
1342 1343
1343 1344 try:
1344 1345 f = self.opener(self.datafile)
1345 1346 f.seek(0, 2)
1346 1347 actual = f.tell()
1347 1348 dd = actual - expected
1348 1349 except IOError, inst:
1349 1350 if inst.errno != errno.ENOENT:
1350 1351 raise
1351 1352 dd = 0
1352 1353
1353 1354 try:
1354 1355 f = self.opener(self.indexfile)
1355 1356 f.seek(0, 2)
1356 1357 actual = f.tell()
1357 1358 s = self._io.size
1358 1359 i = max(0, actual // s)
1359 1360 di = actual - (i * s)
1360 1361 if self._inline:
1361 1362 databytes = 0
1362 1363 for r in self:
1363 1364 databytes += max(0, self.length(r))
1364 1365 dd = 0
1365 1366 di = actual - len(self) * s - databytes
1366 1367 except IOError, inst:
1367 1368 if inst.errno != errno.ENOENT:
1368 1369 raise
1369 1370 di = 0
1370 1371
1371 1372 return (dd, di)
1372 1373
1373 1374 def files(self):
1374 1375 res = [ self.indexfile ]
1375 1376 if not self._inline:
1376 1377 res.append(self.datafile)
1377 1378 return res
General Comments 0
You need to be logged in to leave comments. Login now