##// END OF EJS Templates
Make the appendfile class inline-data index friendly...
mason@suse.com -
r2075:343aeefb default
parent child Browse files
Show More
@@ -1,172 +1,194
1 1 # appendfile.py - special classes to make repo updates atomic
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import *
9 9 demandload(globals(), "cStringIO changelog manifest os tempfile")
10 10
11 11 # writes to metadata files are ordered. reads: changelog, manifest,
12 12 # normal files. writes: normal files, manifest, changelog.
13 13
14 14 # manifest contains pointers to offsets in normal files. changelog
15 15 # contains pointers to offsets in manifest. if reader reads old
16 16 # changelog while manifest or normal files are written, it has no
17 17 # pointers into new parts of those files that are maybe not consistent
18 18 # yet, so will not read them.
19 19
20 20 # localrepo.addchangegroup thinks it writes changelog first, then
21 21 # manifest, then normal files (this is order they are available, and
22 22 # needed for computing linkrev fields), but uses appendfile to hide
23 23 # updates from readers. data not written to manifest or changelog
24 24 # until all normal files updated. write manifest first, then
25 25 # changelog.
26 26
27 27 # with this write ordering, readers cannot see inconsistent view of
28 28 # repo during update.
29 29
30 30 class appendfile(object):
31 31 '''implement enough of file protocol to append to revlog file.
32 32 appended data is written to temp file. reads and seeks span real
33 33 file and temp file. readers cannot see appended data until
34 34 writedata called.'''
35 35
36 36 def __init__(self, fp):
37 37 fd, self.tmpname = tempfile.mkstemp()
38 38 self.tmpfp = os.fdopen(fd, 'ab+')
39 39 self.realfp = fp
40 40 self.offset = fp.tell()
41 41 # real file is not written by anyone else. cache its size so
42 42 # seek and read can be fast.
43 43 self.fpsize = os.fstat(fp.fileno()).st_size
44 44
45 def seek(self, offset):
45 def end(self):
46 self.tmpfp.flush() # make sure the stat is correct
47 return self.fpsize + os.fstat(self.tmpfp.fileno()).st_size
48
49 def seek(self, offset, whence=0):
46 50 '''virtual file offset spans real file and temp file.'''
47 self.offset = offset
51 if whence == 0:
52 self.offset = offset
53 elif whence == 1:
54 self.offset += offset
55 elif whence == 2:
56 self.offset = self.end() + offset
57
48 58 if self.offset < self.fpsize:
49 59 self.realfp.seek(self.offset)
50 60 else:
51 61 self.tmpfp.seek(self.offset - self.fpsize)
52 62
53 63 def read(self, count=-1):
54 64 '''only trick here is reads that span real file and temp file.'''
55 65 fp = cStringIO.StringIO()
56 66 old_offset = self.offset
57 67 if self.offset < self.fpsize:
58 68 s = self.realfp.read(count)
59 69 fp.write(s)
60 70 self.offset += len(s)
61 71 if count > 0:
62 72 count -= len(s)
63 73 if count != 0:
64 74 if old_offset != self.offset:
65 75 self.tmpfp.seek(self.offset - self.fpsize)
66 76 s = self.tmpfp.read(count)
67 77 fp.write(s)
68 78 self.offset += len(s)
69 79 return fp.getvalue()
70 80
71 81 def write(self, s):
72 82 '''append to temp file.'''
73 83 self.tmpfp.seek(0, 2)
74 84 self.tmpfp.write(s)
75 85 # all writes are appends, so offset must go to end of file.
76 86 self.offset = self.fpsize + self.tmpfp.tell()
77 87
78 88 def writedata(self):
79 89 '''copy data from temp file to real file.'''
80 90 self.tmpfp.seek(0)
81 91 s = self.tmpfp.read()
82 92 self.tmpfp.close()
83 93 self.realfp.seek(0, 2)
84 94 # small race here. we write all new data in one call, but
85 95 # reader can see partial update due to python or os. file
86 96 # locking no help: slow, not portable, not reliable over nfs.
87 97 # only safe thing is write to temp file every time and rename,
88 98 # but performance bad when manifest or changelog gets big.
89 99 self.realfp.write(s)
90 100 self.realfp.close()
91 101
92 102 def __del__(self):
93 103 '''delete temp file even if exception raised.'''
94 104 try: os.unlink(self.tmpname)
95 105 except: pass
96 106
97 107 class sharedfile(object):
98 108 '''let file objects share a single appendfile safely. each
99 109 sharedfile has own offset, syncs up with appendfile offset before
100 110 read and after read and write.'''
101 111
102 112 def __init__(self, fp):
103 113 self.fp = fp
104 114 self.offset = 0
105 115
106 def seek(self, offset):
107 self.offset = offset
116 def tell(self):
117 return self.offset
118
119 def seek(self, offset, whence=0):
120 if whence == 0:
121 self.offset = offset
122 elif whence == 1:
123 self.offset += offset
124 elif whence == 2:
125 self.offset = self.fp.end() + offset
108 126
109 127 def read(self, count=-1):
110 128 try:
111 129 if self.offset != self.fp.offset:
112 130 self.fp.seek(self.offset)
113 131 return self.fp.read(count)
114 132 finally:
115 133 self.offset = self.fp.offset
116 134
117 135 def write(self, s):
118 136 try:
119 137 return self.fp.write(s)
120 138 finally:
121 139 self.offset = self.fp.offset
122 140
123 141 def close(self):
124 142 # revlog wants this.
125 143 pass
126 144
127 145 def flush(self):
128 146 # revlog wants this.
129 147 pass
130 148
131 149 def writedata(self):
132 150 self.fp.writedata()
133 151
134 152 class appendopener(object):
135 153 '''special opener for files that only read or append.'''
136 154
137 155 def __init__(self, opener):
138 156 self.realopener = opener
139 157 # key: file name, value: appendfile object
140 158 self.fps = {}
141 159
142 160 def __call__(self, name, mode='r'):
143 161 '''open file. return same cached appendfile object for every
144 162 later call.'''
145 163
146 assert mode in 'ra'
164 assert mode in 'ra+'
147 165 fp = self.fps.get(name)
148 166 if fp is None:
149 167 fp = appendfile(self.realopener(name, 'a+'))
150 168 self.fps[name] = fp
151 169 return sharedfile(fp)
152 170
153 171 def writedata(self):
154 172 '''copy data from temp files to real files.'''
155 173 # write .d file before .i file.
156 174 fps = self.fps.items()
157 175 fps.sort()
158 176 for name, fp in fps:
159 177 fp.writedata()
160 178
161 179 # files for changelog and manifest are in different appendopeners, so
162 180 # not mixed up together.
163 181
164 182 class appendchangelog(changelog.changelog, appendopener):
165 183 def __init__(self, opener):
166 184 appendopener.__init__(self, opener)
167 185 changelog.changelog.__init__(self, self)
186 def checkinlinesize(self, fp, tr):
187 return
168 188
169 189 class appendmanifest(manifest.manifest, appendopener):
170 190 def __init__(self, opener):
171 191 appendopener.__init__(self, opener)
172 192 manifest.manifest.__init__(self, self)
193 def checkinlinesize(self, fp, tr):
194 return
@@ -1,1974 +1,1977
1 1 # localrepo.py - read/write repository class for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import os, util
9 9 import filelog, manifest, changelog, dirstate, repo
10 10 from node import *
11 11 from i18n import gettext as _
12 12 from demandload import *
13 13 demandload(globals(), "appendfile changegroup")
14 14 demandload(globals(), "re lock transaction tempfile stat mdiff errno ui revlog")
15 15
16 16 class localrepository(object):
17 17 def __del__(self):
18 18 self.transhandle = None
19 19 def __init__(self, parentui, path=None, create=0):
20 20 if not path:
21 21 p = os.getcwd()
22 22 while not os.path.isdir(os.path.join(p, ".hg")):
23 23 oldp = p
24 24 p = os.path.dirname(p)
25 25 if p == oldp:
26 26 raise repo.RepoError(_("no repo found"))
27 27 path = p
28 28 self.path = os.path.join(path, ".hg")
29 29
30 30 if not create and not os.path.isdir(self.path):
31 31 raise repo.RepoError(_("repository %s not found") % path)
32 32
33 33 self.root = os.path.abspath(path)
34 34 self.origroot = path
35 35 self.ui = ui.ui(parentui=parentui)
36 36 self.opener = util.opener(self.path)
37 37 self.wopener = util.opener(self.root)
38 38
39 39 try:
40 40 self.ui.readconfig(self.join("hgrc"), self.root)
41 41 except IOError:
42 42 pass
43 43
44 44 v = self.ui.revlogopts
45 45 self.revlogversion = int(v.get('format', 0))
46 46 flags = 0
47 47 for x in v.get('flags', "").split():
48 48 flags |= revlog.flagstr(x)
49 49
50 50 v = self.revlogversion | flags
51 51 self.manifest = manifest.manifest(self.opener, v)
52 52 self.changelog = changelog.changelog(self.opener, v)
53 53
54 54 # the changelog might not have the inline index flag
55 55 # on. If the format of the changelog is the same as found in
56 56 # .hgrc, apply any flags found in the .hgrc as well.
57 57 # Otherwise, just version from the changelog
58 58 v = self.changelog.version
59 59 if v == self.revlogversion:
60 60 v |= flags
61 61 self.revlogversion = v
62 62
63 63 self.tagscache = None
64 64 self.nodetagscache = None
65 65 self.encodepats = None
66 66 self.decodepats = None
67 67 self.transhandle = None
68 68
69 69 if create:
70 70 os.mkdir(self.path)
71 71 os.mkdir(self.join("data"))
72 72
73 73 self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root)
74 74 def hook(self, name, throw=False, **args):
75 75 def runhook(name, cmd):
76 76 self.ui.note(_("running hook %s: %s\n") % (name, cmd))
77 77 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()] +
78 78 [(k.upper(), v) for k, v in args.iteritems()])
79 79 r = util.system(cmd, environ=env, cwd=self.root)
80 80 if r:
81 81 desc, r = util.explain_exit(r)
82 82 if throw:
83 83 raise util.Abort(_('%s hook %s') % (name, desc))
84 84 self.ui.warn(_('error: %s hook %s\n') % (name, desc))
85 85 return False
86 86 return True
87 87
88 88 r = True
89 89 hooks = [(hname, cmd) for hname, cmd in self.ui.configitems("hooks")
90 90 if hname.split(".", 1)[0] == name and cmd]
91 91 hooks.sort()
92 92 for hname, cmd in hooks:
93 93 r = runhook(hname, cmd) and r
94 94 return r
95 95
96 96 def tags(self):
97 97 '''return a mapping of tag to node'''
98 98 if not self.tagscache:
99 99 self.tagscache = {}
100 100
101 101 def parsetag(line, context):
102 102 if not line:
103 103 return
104 104 s = l.split(" ", 1)
105 105 if len(s) != 2:
106 106 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
107 107 return
108 108 node, key = s
109 109 try:
110 110 bin_n = bin(node)
111 111 except TypeError:
112 112 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
113 113 return
114 114 if bin_n not in self.changelog.nodemap:
115 115 self.ui.warn(_("%s: ignoring invalid tag\n") % context)
116 116 return
117 117 self.tagscache[key.strip()] = bin_n
118 118
119 119 # read each head of the tags file, ending with the tip
120 120 # and add each tag found to the map, with "newer" ones
121 121 # taking precedence
122 122 fl = self.file(".hgtags")
123 123 h = fl.heads()
124 124 h.reverse()
125 125 for r in h:
126 126 count = 0
127 127 for l in fl.read(r).splitlines():
128 128 count += 1
129 129 parsetag(l, ".hgtags:%d" % count)
130 130
131 131 try:
132 132 f = self.opener("localtags")
133 133 count = 0
134 134 for l in f:
135 135 count += 1
136 136 parsetag(l, "localtags:%d" % count)
137 137 except IOError:
138 138 pass
139 139
140 140 self.tagscache['tip'] = self.changelog.tip()
141 141
142 142 return self.tagscache
143 143
144 144 def tagslist(self):
145 145 '''return a list of tags ordered by revision'''
146 146 l = []
147 147 for t, n in self.tags().items():
148 148 try:
149 149 r = self.changelog.rev(n)
150 150 except:
151 151 r = -2 # sort to the beginning of the list if unknown
152 152 l.append((r, t, n))
153 153 l.sort()
154 154 return [(t, n) for r, t, n in l]
155 155
156 156 def nodetags(self, node):
157 157 '''return the tags associated with a node'''
158 158 if not self.nodetagscache:
159 159 self.nodetagscache = {}
160 160 for t, n in self.tags().items():
161 161 self.nodetagscache.setdefault(n, []).append(t)
162 162 return self.nodetagscache.get(node, [])
163 163
164 164 def lookup(self, key):
165 165 try:
166 166 return self.tags()[key]
167 167 except KeyError:
168 168 try:
169 169 return self.changelog.lookup(key)
170 170 except:
171 raise
171 172 raise repo.RepoError(_("unknown revision '%s'") % key)
172 173
173 174 def dev(self):
174 175 return os.stat(self.path).st_dev
175 176
176 177 def local(self):
177 178 return True
178 179
179 180 def join(self, f):
180 181 return os.path.join(self.path, f)
181 182
182 183 def wjoin(self, f):
183 184 return os.path.join(self.root, f)
184 185
185 186 def file(self, f):
186 187 if f[0] == '/':
187 188 f = f[1:]
188 189 return filelog.filelog(self.opener, f, self.revlogversion)
189 190
190 191 def getcwd(self):
191 192 return self.dirstate.getcwd()
192 193
193 194 def wfile(self, f, mode='r'):
194 195 return self.wopener(f, mode)
195 196
196 197 def wread(self, filename):
197 198 if self.encodepats == None:
198 199 l = []
199 200 for pat, cmd in self.ui.configitems("encode"):
200 201 mf = util.matcher(self.root, "", [pat], [], [])[1]
201 202 l.append((mf, cmd))
202 203 self.encodepats = l
203 204
204 205 data = self.wopener(filename, 'r').read()
205 206
206 207 for mf, cmd in self.encodepats:
207 208 if mf(filename):
208 209 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
209 210 data = util.filter(data, cmd)
210 211 break
211 212
212 213 return data
213 214
214 215 def wwrite(self, filename, data, fd=None):
215 216 if self.decodepats == None:
216 217 l = []
217 218 for pat, cmd in self.ui.configitems("decode"):
218 219 mf = util.matcher(self.root, "", [pat], [], [])[1]
219 220 l.append((mf, cmd))
220 221 self.decodepats = l
221 222
222 223 for mf, cmd in self.decodepats:
223 224 if mf(filename):
224 225 self.ui.debug(_("filtering %s through %s\n") % (filename, cmd))
225 226 data = util.filter(data, cmd)
226 227 break
227 228
228 229 if fd:
229 230 return fd.write(data)
230 231 return self.wopener(filename, 'w').write(data)
231 232
232 233 def transaction(self):
233 234 tr = self.transhandle
234 235 if tr != None and tr.running():
235 236 return tr.nest()
236 237
237 238 # save dirstate for undo
238 239 try:
239 240 ds = self.opener("dirstate").read()
240 241 except IOError:
241 242 ds = ""
242 243 self.opener("journal.dirstate", "w").write(ds)
243 244
244 245 tr = transaction.transaction(self.ui.warn, self.opener,
245 246 self.join("journal"),
246 247 aftertrans(self.path))
247 248 self.transhandle = tr
248 249 return tr
249 250
250 251 def recover(self):
251 252 l = self.lock()
252 253 if os.path.exists(self.join("journal")):
253 254 self.ui.status(_("rolling back interrupted transaction\n"))
254 255 transaction.rollback(self.opener, self.join("journal"))
255 256 self.reload()
256 257 return True
257 258 else:
258 259 self.ui.warn(_("no interrupted transaction available\n"))
259 260 return False
260 261
261 262 def undo(self, wlock=None):
262 263 if not wlock:
263 264 wlock = self.wlock()
264 265 l = self.lock()
265 266 if os.path.exists(self.join("undo")):
266 267 self.ui.status(_("rolling back last transaction\n"))
267 268 transaction.rollback(self.opener, self.join("undo"))
268 269 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
269 270 self.reload()
270 271 self.wreload()
271 272 else:
272 273 self.ui.warn(_("no undo information available\n"))
273 274
274 275 def wreload(self):
275 276 self.dirstate.read()
276 277
277 278 def reload(self):
278 279 self.changelog.load()
279 280 self.manifest.load()
280 281 self.tagscache = None
281 282 self.nodetagscache = None
282 283
283 284 def do_lock(self, lockname, wait, releasefn=None, acquirefn=None,
284 285 desc=None):
285 286 try:
286 287 l = lock.lock(self.join(lockname), 0, releasefn, desc=desc)
287 288 except lock.LockHeld, inst:
288 289 if not wait:
289 290 raise
290 291 self.ui.warn(_("waiting for lock on %s held by %s\n") %
291 292 (desc, inst.args[0]))
292 293 # default to 600 seconds timeout
293 294 l = lock.lock(self.join(lockname),
294 295 int(self.ui.config("ui", "timeout") or 600),
295 296 releasefn, desc=desc)
296 297 if acquirefn:
297 298 acquirefn()
298 299 return l
299 300
300 301 def lock(self, wait=1):
301 302 return self.do_lock("lock", wait, acquirefn=self.reload,
302 303 desc=_('repository %s') % self.origroot)
303 304
304 305 def wlock(self, wait=1):
305 306 return self.do_lock("wlock", wait, self.dirstate.write,
306 307 self.wreload,
307 308 desc=_('working directory of %s') % self.origroot)
308 309
309 310 def checkfilemerge(self, filename, text, filelog, manifest1, manifest2):
310 311 "determine whether a new filenode is needed"
311 312 fp1 = manifest1.get(filename, nullid)
312 313 fp2 = manifest2.get(filename, nullid)
313 314
314 315 if fp2 != nullid:
315 316 # is one parent an ancestor of the other?
316 317 fpa = filelog.ancestor(fp1, fp2)
317 318 if fpa == fp1:
318 319 fp1, fp2 = fp2, nullid
319 320 elif fpa == fp2:
320 321 fp2 = nullid
321 322
322 323 # is the file unmodified from the parent? report existing entry
323 324 if fp2 == nullid and text == filelog.read(fp1):
324 325 return (fp1, None, None)
325 326
326 327 return (None, fp1, fp2)
327 328
328 329 def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None):
329 330 orig_parent = self.dirstate.parents()[0] or nullid
330 331 p1 = p1 or self.dirstate.parents()[0] or nullid
331 332 p2 = p2 or self.dirstate.parents()[1] or nullid
332 333 c1 = self.changelog.read(p1)
333 334 c2 = self.changelog.read(p2)
334 335 m1 = self.manifest.read(c1[0])
335 336 mf1 = self.manifest.readflags(c1[0])
336 337 m2 = self.manifest.read(c2[0])
337 338 changed = []
338 339
339 340 if orig_parent == p1:
340 341 update_dirstate = 1
341 342 else:
342 343 update_dirstate = 0
343 344
344 345 if not wlock:
345 346 wlock = self.wlock()
346 347 l = self.lock()
347 348 tr = self.transaction()
348 349 mm = m1.copy()
349 350 mfm = mf1.copy()
350 351 linkrev = self.changelog.count()
351 352 for f in files:
352 353 try:
353 354 t = self.wread(f)
354 355 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
355 356 r = self.file(f)
356 357 mfm[f] = tm
357 358
358 359 (entry, fp1, fp2) = self.checkfilemerge(f, t, r, m1, m2)
359 360 if entry:
360 361 mm[f] = entry
361 362 continue
362 363
363 364 mm[f] = r.add(t, {}, tr, linkrev, fp1, fp2)
364 365 changed.append(f)
365 366 if update_dirstate:
366 367 self.dirstate.update([f], "n")
367 368 except IOError:
368 369 try:
369 370 del mm[f]
370 371 del mfm[f]
371 372 if update_dirstate:
372 373 self.dirstate.forget([f])
373 374 except:
374 375 # deleted from p2?
375 376 pass
376 377
377 378 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
378 379 user = user or self.ui.username()
379 380 n = self.changelog.add(mnode, changed, text, tr, p1, p2, user, date)
380 381 tr.close()
381 382 if update_dirstate:
382 383 self.dirstate.setparents(n, nullid)
383 384
384 385 def commit(self, files=None, text="", user=None, date=None,
385 386 match=util.always, force=False, lock=None, wlock=None):
386 387 commit = []
387 388 remove = []
388 389 changed = []
389 390
390 391 if files:
391 392 for f in files:
392 393 s = self.dirstate.state(f)
393 394 if s in 'nmai':
394 395 commit.append(f)
395 396 elif s == 'r':
396 397 remove.append(f)
397 398 else:
398 399 self.ui.warn(_("%s not tracked!\n") % f)
399 400 else:
400 401 modified, added, removed, deleted, unknown = self.changes(match=match)
401 402 commit = modified + added
402 403 remove = removed
403 404
404 405 p1, p2 = self.dirstate.parents()
405 406 c1 = self.changelog.read(p1)
406 407 c2 = self.changelog.read(p2)
407 408 m1 = self.manifest.read(c1[0])
408 409 mf1 = self.manifest.readflags(c1[0])
409 410 m2 = self.manifest.read(c2[0])
410 411
411 412 if not commit and not remove and not force and p2 == nullid:
412 413 self.ui.status(_("nothing changed\n"))
413 414 return None
414 415
415 416 xp1 = hex(p1)
416 417 if p2 == nullid: xp2 = ''
417 418 else: xp2 = hex(p2)
418 419
419 420 self.hook("precommit", throw=True, parent1=xp1, parent2=xp2)
420 421
421 422 if not wlock:
422 423 wlock = self.wlock()
423 424 if not lock:
424 425 lock = self.lock()
425 426 tr = self.transaction()
426 427
427 428 # check in files
428 429 new = {}
429 430 linkrev = self.changelog.count()
430 431 commit.sort()
431 432 for f in commit:
432 433 self.ui.note(f + "\n")
433 434 try:
434 435 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
435 436 t = self.wread(f)
436 437 except IOError:
437 438 self.ui.warn(_("trouble committing %s!\n") % f)
438 439 raise
439 440
440 441 r = self.file(f)
441 442
442 443 meta = {}
443 444 cp = self.dirstate.copied(f)
444 445 if cp:
445 446 meta["copy"] = cp
446 447 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
447 448 self.ui.debug(_(" %s: copy %s:%s\n") % (f, cp, meta["copyrev"]))
448 449 fp1, fp2 = nullid, nullid
449 450 else:
450 451 entry, fp1, fp2 = self.checkfilemerge(f, t, r, m1, m2)
451 452 if entry:
452 453 new[f] = entry
453 454 continue
454 455
455 456 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
456 457 # remember what we've added so that we can later calculate
457 458 # the files to pull from a set of changesets
458 459 changed.append(f)
459 460
460 461 # update manifest
461 462 m1 = m1.copy()
462 463 m1.update(new)
463 464 for f in remove:
464 465 if f in m1:
465 466 del m1[f]
466 467 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0],
467 468 (new, remove))
468 469
469 470 # add changeset
470 471 new = new.keys()
471 472 new.sort()
472 473
473 474 user = user or self.ui.username()
474 475 if not text:
475 476 edittext = [""]
476 477 if p2 != nullid:
477 478 edittext.append("HG: branch merge")
478 479 edittext.extend(["HG: changed %s" % f for f in changed])
479 480 edittext.extend(["HG: removed %s" % f for f in remove])
480 481 if not changed and not remove:
481 482 edittext.append("HG: no files changed")
482 483 edittext.append("")
483 484 # run editor in the repository root
484 485 olddir = os.getcwd()
485 486 os.chdir(self.root)
486 487 edittext = self.ui.edit("\n".join(edittext), user)
487 488 os.chdir(olddir)
488 489 if not edittext.rstrip():
489 490 return None
490 491 text = edittext
491 492
492 493 n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, user, date)
493 494 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
494 495 parent2=xp2)
495 496 tr.close()
496 497
497 498 self.dirstate.setparents(n)
498 499 self.dirstate.update(new, "n")
499 500 self.dirstate.forget(remove)
500 501
501 502 self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2)
502 503 return n
503 504
504 505 def walk(self, node=None, files=[], match=util.always, badmatch=None):
505 506 if node:
506 507 fdict = dict.fromkeys(files)
507 508 for fn in self.manifest.read(self.changelog.read(node)[0]):
508 509 fdict.pop(fn, None)
509 510 if match(fn):
510 511 yield 'm', fn
511 512 for fn in fdict:
512 513 if badmatch and badmatch(fn):
513 514 if match(fn):
514 515 yield 'b', fn
515 516 else:
516 517 self.ui.warn(_('%s: No such file in rev %s\n') % (
517 518 util.pathto(self.getcwd(), fn), short(node)))
518 519 else:
519 520 for src, fn in self.dirstate.walk(files, match, badmatch=badmatch):
520 521 yield src, fn
521 522
522 523 def changes(self, node1=None, node2=None, files=[], match=util.always,
523 524 wlock=None, show_ignored=None):
524 525 """return changes between two nodes or node and working directory
525 526
526 527 If node1 is None, use the first dirstate parent instead.
527 528 If node2 is None, compare node1 with working directory.
528 529 """
529 530
530 531 def fcmp(fn, mf):
531 532 t1 = self.wread(fn)
532 533 t2 = self.file(fn).read(mf.get(fn, nullid))
533 534 return cmp(t1, t2)
534 535
535 536 def mfmatches(node):
536 537 change = self.changelog.read(node)
537 538 mf = dict(self.manifest.read(change[0]))
538 539 for fn in mf.keys():
539 540 if not match(fn):
540 541 del mf[fn]
541 542 return mf
542 543
543 544 if node1:
544 545 # read the manifest from node1 before the manifest from node2,
545 546 # so that we'll hit the manifest cache if we're going through
546 547 # all the revisions in parent->child order.
547 548 mf1 = mfmatches(node1)
548 549
549 550 # are we comparing the working directory?
550 551 if not node2:
551 552 if not wlock:
552 553 try:
553 554 wlock = self.wlock(wait=0)
554 555 except lock.LockException:
555 556 wlock = None
556 557 lookup, modified, added, removed, deleted, unknown, ignored = (
557 558 self.dirstate.changes(files, match, show_ignored))
558 559
559 560 # are we comparing working dir against its parent?
560 561 if not node1:
561 562 if lookup:
562 563 # do a full compare of any files that might have changed
563 564 mf2 = mfmatches(self.dirstate.parents()[0])
564 565 for f in lookup:
565 566 if fcmp(f, mf2):
566 567 modified.append(f)
567 568 elif wlock is not None:
568 569 self.dirstate.update([f], "n")
569 570 else:
570 571 # we are comparing working dir against non-parent
571 572 # generate a pseudo-manifest for the working dir
572 573 mf2 = mfmatches(self.dirstate.parents()[0])
573 574 for f in lookup + modified + added:
574 575 mf2[f] = ""
575 576 for f in removed:
576 577 if f in mf2:
577 578 del mf2[f]
578 579 else:
579 580 # we are comparing two revisions
580 581 deleted, unknown, ignored = [], [], []
581 582 mf2 = mfmatches(node2)
582 583
583 584 if node1:
584 585 # flush lists from dirstate before comparing manifests
585 586 modified, added = [], []
586 587
587 588 for fn in mf2:
588 589 if mf1.has_key(fn):
589 590 if mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1)):
590 591 modified.append(fn)
591 592 del mf1[fn]
592 593 else:
593 594 added.append(fn)
594 595
595 596 removed = mf1.keys()
596 597
597 598 # sort and return results:
598 599 for l in modified, added, removed, deleted, unknown, ignored:
599 600 l.sort()
600 601 if show_ignored is None:
601 602 return (modified, added, removed, deleted, unknown)
602 603 else:
603 604 return (modified, added, removed, deleted, unknown, ignored)
604 605
605 606 def add(self, list, wlock=None):
606 607 if not wlock:
607 608 wlock = self.wlock()
608 609 for f in list:
609 610 p = self.wjoin(f)
610 611 if not os.path.exists(p):
611 612 self.ui.warn(_("%s does not exist!\n") % f)
612 613 elif not os.path.isfile(p):
613 614 self.ui.warn(_("%s not added: only files supported currently\n")
614 615 % f)
615 616 elif self.dirstate.state(f) in 'an':
616 617 self.ui.warn(_("%s already tracked!\n") % f)
617 618 else:
618 619 self.dirstate.update([f], "a")
619 620
620 621 def forget(self, list, wlock=None):
621 622 if not wlock:
622 623 wlock = self.wlock()
623 624 for f in list:
624 625 if self.dirstate.state(f) not in 'ai':
625 626 self.ui.warn(_("%s not added!\n") % f)
626 627 else:
627 628 self.dirstate.forget([f])
628 629
629 630 def remove(self, list, unlink=False, wlock=None):
630 631 if unlink:
631 632 for f in list:
632 633 try:
633 634 util.unlink(self.wjoin(f))
634 635 except OSError, inst:
635 636 if inst.errno != errno.ENOENT:
636 637 raise
637 638 if not wlock:
638 639 wlock = self.wlock()
639 640 for f in list:
640 641 p = self.wjoin(f)
641 642 if os.path.exists(p):
642 643 self.ui.warn(_("%s still exists!\n") % f)
643 644 elif self.dirstate.state(f) == 'a':
644 645 self.dirstate.forget([f])
645 646 elif f not in self.dirstate:
646 647 self.ui.warn(_("%s not tracked!\n") % f)
647 648 else:
648 649 self.dirstate.update([f], "r")
649 650
650 651 def undelete(self, list, wlock=None):
651 652 p = self.dirstate.parents()[0]
652 653 mn = self.changelog.read(p)[0]
653 654 mf = self.manifest.readflags(mn)
654 655 m = self.manifest.read(mn)
655 656 if not wlock:
656 657 wlock = self.wlock()
657 658 for f in list:
658 659 if self.dirstate.state(f) not in "r":
659 660 self.ui.warn("%s not removed!\n" % f)
660 661 else:
661 662 t = self.file(f).read(m[f])
662 663 self.wwrite(f, t)
663 664 util.set_exec(self.wjoin(f), mf[f])
664 665 self.dirstate.update([f], "n")
665 666
666 667 def copy(self, source, dest, wlock=None):
667 668 p = self.wjoin(dest)
668 669 if not os.path.exists(p):
669 670 self.ui.warn(_("%s does not exist!\n") % dest)
670 671 elif not os.path.isfile(p):
671 672 self.ui.warn(_("copy failed: %s is not a file\n") % dest)
672 673 else:
673 674 if not wlock:
674 675 wlock = self.wlock()
675 676 if self.dirstate.state(dest) == '?':
676 677 self.dirstate.update([dest], "a")
677 678 self.dirstate.copy(source, dest)
678 679
679 680 def heads(self, start=None):
680 681 heads = self.changelog.heads(start)
681 682 # sort the output in rev descending order
682 683 heads = [(-self.changelog.rev(h), h) for h in heads]
683 684 heads.sort()
684 685 return [n for (r, n) in heads]
685 686
686 687 # branchlookup returns a dict giving a list of branches for
687 688 # each head. A branch is defined as the tag of a node or
688 689 # the branch of the node's parents. If a node has multiple
689 690 # branch tags, tags are eliminated if they are visible from other
690 691 # branch tags.
691 692 #
692 693 # So, for this graph: a->b->c->d->e
693 694 # \ /
694 695 # aa -----/
695 696 # a has tag 2.6.12
696 697 # d has tag 2.6.13
697 698 # e would have branch tags for 2.6.12 and 2.6.13. Because the node
698 699 # for 2.6.12 can be reached from the node 2.6.13, that is eliminated
699 700 # from the list.
700 701 #
701 702 # It is possible that more than one head will have the same branch tag.
702 703 # callers need to check the result for multiple heads under the same
703 704 # branch tag if that is a problem for them (ie checkout of a specific
704 705 # branch).
705 706 #
706 707 # passing in a specific branch will limit the depth of the search
707 708 # through the parents. It won't limit the branches returned in the
708 709 # result though.
709 710 def branchlookup(self, heads=None, branch=None):
710 711 if not heads:
711 712 heads = self.heads()
712 713 headt = [ h for h in heads ]
713 714 chlog = self.changelog
714 715 branches = {}
715 716 merges = []
716 717 seenmerge = {}
717 718
718 719 # traverse the tree once for each head, recording in the branches
719 720 # dict which tags are visible from this head. The branches
720 721 # dict also records which tags are visible from each tag
721 722 # while we traverse.
722 723 while headt or merges:
723 724 if merges:
724 725 n, found = merges.pop()
725 726 visit = [n]
726 727 else:
727 728 h = headt.pop()
728 729 visit = [h]
729 730 found = [h]
730 731 seen = {}
731 732 while visit:
732 733 n = visit.pop()
733 734 if n in seen:
734 735 continue
735 736 pp = chlog.parents(n)
736 737 tags = self.nodetags(n)
737 738 if tags:
738 739 for x in tags:
739 740 if x == 'tip':
740 741 continue
741 742 for f in found:
742 743 branches.setdefault(f, {})[n] = 1
743 744 branches.setdefault(n, {})[n] = 1
744 745 break
745 746 if n not in found:
746 747 found.append(n)
747 748 if branch in tags:
748 749 continue
749 750 seen[n] = 1
750 751 if pp[1] != nullid and n not in seenmerge:
751 752 merges.append((pp[1], [x for x in found]))
752 753 seenmerge[n] = 1
753 754 if pp[0] != nullid:
754 755 visit.append(pp[0])
755 756 # traverse the branches dict, eliminating branch tags from each
756 757 # head that are visible from another branch tag for that head.
757 758 out = {}
758 759 viscache = {}
759 760 for h in heads:
760 761 def visible(node):
761 762 if node in viscache:
762 763 return viscache[node]
763 764 ret = {}
764 765 visit = [node]
765 766 while visit:
766 767 x = visit.pop()
767 768 if x in viscache:
768 769 ret.update(viscache[x])
769 770 elif x not in ret:
770 771 ret[x] = 1
771 772 if x in branches:
772 773 visit[len(visit):] = branches[x].keys()
773 774 viscache[node] = ret
774 775 return ret
775 776 if h not in branches:
776 777 continue
777 778 # O(n^2), but somewhat limited. This only searches the
778 779 # tags visible from a specific head, not all the tags in the
779 780 # whole repo.
780 781 for b in branches[h]:
781 782 vis = False
782 783 for bb in branches[h].keys():
783 784 if b != bb:
784 785 if b in visible(bb):
785 786 vis = True
786 787 break
787 788 if not vis:
788 789 l = out.setdefault(h, [])
789 790 l[len(l):] = self.nodetags(b)
790 791 return out
791 792
792 793 def branches(self, nodes):
793 794 if not nodes:
794 795 nodes = [self.changelog.tip()]
795 796 b = []
796 797 for n in nodes:
797 798 t = n
798 799 while n:
799 800 p = self.changelog.parents(n)
800 801 if p[1] != nullid or p[0] == nullid:
801 802 b.append((t, n, p[0], p[1]))
802 803 break
803 804 n = p[0]
804 805 return b
805 806
806 807 def between(self, pairs):
807 808 r = []
808 809
809 810 for top, bottom in pairs:
810 811 n, l, i = top, [], 0
811 812 f = 1
812 813
813 814 while n != bottom:
814 815 p = self.changelog.parents(n)[0]
815 816 if i == f:
816 817 l.append(n)
817 818 f = f * 2
818 819 n = p
819 820 i += 1
820 821
821 822 r.append(l)
822 823
823 824 return r
824 825
825 826 def findincoming(self, remote, base=None, heads=None, force=False):
826 827 m = self.changelog.nodemap
827 828 search = []
828 829 fetch = {}
829 830 seen = {}
830 831 seenbranch = {}
831 832 if base == None:
832 833 base = {}
833 834
834 835 # assume we're closer to the tip than the root
835 836 # and start by examining the heads
836 837 self.ui.status(_("searching for changes\n"))
837 838
838 839 if not heads:
839 840 heads = remote.heads()
840 841
841 842 unknown = []
842 843 for h in heads:
843 844 if h not in m:
844 845 unknown.append(h)
845 846 else:
846 847 base[h] = 1
847 848
848 849 if not unknown:
849 850 return []
850 851
851 852 rep = {}
852 853 reqcnt = 0
853 854
854 855 # search through remote branches
855 856 # a 'branch' here is a linear segment of history, with four parts:
856 857 # head, root, first parent, second parent
857 858 # (a branch always has two parents (or none) by definition)
858 859 unknown = remote.branches(unknown)
859 860 while unknown:
860 861 r = []
861 862 while unknown:
862 863 n = unknown.pop(0)
863 864 if n[0] in seen:
864 865 continue
865 866
866 867 self.ui.debug(_("examining %s:%s\n")
867 868 % (short(n[0]), short(n[1])))
868 869 if n[0] == nullid:
869 870 break
870 871 if n in seenbranch:
871 872 self.ui.debug(_("branch already found\n"))
872 873 continue
873 874 if n[1] and n[1] in m: # do we know the base?
874 875 self.ui.debug(_("found incomplete branch %s:%s\n")
875 876 % (short(n[0]), short(n[1])))
876 877 search.append(n) # schedule branch range for scanning
877 878 seenbranch[n] = 1
878 879 else:
879 880 if n[1] not in seen and n[1] not in fetch:
880 881 if n[2] in m and n[3] in m:
881 882 self.ui.debug(_("found new changeset %s\n") %
882 883 short(n[1]))
883 884 fetch[n[1]] = 1 # earliest unknown
884 885 base[n[2]] = 1 # latest known
885 886 continue
886 887
887 888 for a in n[2:4]:
888 889 if a not in rep:
889 890 r.append(a)
890 891 rep[a] = 1
891 892
892 893 seen[n[0]] = 1
893 894
894 895 if r:
895 896 reqcnt += 1
896 897 self.ui.debug(_("request %d: %s\n") %
897 898 (reqcnt, " ".join(map(short, r))))
898 899 for p in range(0, len(r), 10):
899 900 for b in remote.branches(r[p:p+10]):
900 901 self.ui.debug(_("received %s:%s\n") %
901 902 (short(b[0]), short(b[1])))
902 903 if b[0] in m:
903 904 self.ui.debug(_("found base node %s\n")
904 905 % short(b[0]))
905 906 base[b[0]] = 1
906 907 elif b[0] not in seen:
907 908 unknown.append(b)
908 909
909 910 # do binary search on the branches we found
910 911 while search:
911 912 n = search.pop(0)
912 913 reqcnt += 1
913 914 l = remote.between([(n[0], n[1])])[0]
914 915 l.append(n[1])
915 916 p = n[0]
916 917 f = 1
917 918 for i in l:
918 919 self.ui.debug(_("narrowing %d:%d %s\n") % (f, len(l), short(i)))
919 920 if i in m:
920 921 if f <= 2:
921 922 self.ui.debug(_("found new branch changeset %s\n") %
922 923 short(p))
923 924 fetch[p] = 1
924 925 base[i] = 1
925 926 else:
926 927 self.ui.debug(_("narrowed branch search to %s:%s\n")
927 928 % (short(p), short(i)))
928 929 search.append((p, i))
929 930 break
930 931 p, f = i, f * 2
931 932
932 933 # sanity check our fetch list
933 934 for f in fetch.keys():
934 935 if f in m:
935 936 raise repo.RepoError(_("already have changeset ") + short(f[:4]))
936 937
937 938 if base.keys() == [nullid]:
938 939 if force:
939 940 self.ui.warn(_("warning: repository is unrelated\n"))
940 941 else:
941 942 raise util.Abort(_("repository is unrelated"))
942 943
943 944 self.ui.note(_("found new changesets starting at ") +
944 945 " ".join([short(f) for f in fetch]) + "\n")
945 946
946 947 self.ui.debug(_("%d total queries\n") % reqcnt)
947 948
948 949 return fetch.keys()
949 950
950 951 def findoutgoing(self, remote, base=None, heads=None, force=False):
951 952 """Return list of nodes that are roots of subsets not in remote
952 953
953 954 If base dict is specified, assume that these nodes and their parents
954 955 exist on the remote side.
955 956 If a list of heads is specified, return only nodes which are heads
956 957 or ancestors of these heads, and return a second element which
957 958 contains all remote heads which get new children.
958 959 """
959 960 if base == None:
960 961 base = {}
961 962 self.findincoming(remote, base, heads, force=force)
962 963
963 964 self.ui.debug(_("common changesets up to ")
964 965 + " ".join(map(short, base.keys())) + "\n")
965 966
966 967 remain = dict.fromkeys(self.changelog.nodemap)
967 968
968 969 # prune everything remote has from the tree
969 970 del remain[nullid]
970 971 remove = base.keys()
971 972 while remove:
972 973 n = remove.pop(0)
973 974 if n in remain:
974 975 del remain[n]
975 976 for p in self.changelog.parents(n):
976 977 remove.append(p)
977 978
978 979 # find every node whose parents have been pruned
979 980 subset = []
980 981 # find every remote head that will get new children
981 982 updated_heads = {}
982 983 for n in remain:
983 984 p1, p2 = self.changelog.parents(n)
984 985 if p1 not in remain and p2 not in remain:
985 986 subset.append(n)
986 987 if heads:
987 988 if p1 in heads:
988 989 updated_heads[p1] = True
989 990 if p2 in heads:
990 991 updated_heads[p2] = True
991 992
992 993 # this is the set of all roots we have to push
993 994 if heads:
994 995 return subset, updated_heads.keys()
995 996 else:
996 997 return subset
997 998
998 999 def pull(self, remote, heads=None, force=False):
999 1000 l = self.lock()
1000 1001
1001 1002 # if we have an empty repo, fetch everything
1002 1003 if self.changelog.tip() == nullid:
1003 1004 self.ui.status(_("requesting all changes\n"))
1004 1005 fetch = [nullid]
1005 1006 else:
1006 1007 fetch = self.findincoming(remote, force=force)
1007 1008
1008 1009 if not fetch:
1009 1010 self.ui.status(_("no changes found\n"))
1010 1011 return 0
1011 1012
1012 1013 if heads is None:
1013 1014 cg = remote.changegroup(fetch, 'pull')
1014 1015 else:
1015 1016 cg = remote.changegroupsubset(fetch, heads, 'pull')
1016 1017 return self.addchangegroup(cg)
1017 1018
1018 1019 def push(self, remote, force=False, revs=None):
1019 1020 lock = remote.lock()
1020 1021
1021 1022 base = {}
1022 1023 remote_heads = remote.heads()
1023 1024 inc = self.findincoming(remote, base, remote_heads, force=force)
1024 1025 if not force and inc:
1025 1026 self.ui.warn(_("abort: unsynced remote changes!\n"))
1026 1027 self.ui.status(_("(did you forget to sync?"
1027 1028 " use push -f to force)\n"))
1028 1029 return 1
1029 1030
1030 1031 update, updated_heads = self.findoutgoing(remote, base, remote_heads)
1031 1032 if revs is not None:
1032 1033 msng_cl, bases, heads = self.changelog.nodesbetween(update, revs)
1033 1034 else:
1034 1035 bases, heads = update, self.changelog.heads()
1035 1036
1036 1037 if not bases:
1037 1038 self.ui.status(_("no changes found\n"))
1038 1039 return 1
1039 1040 elif not force:
1040 1041 if revs is not None:
1041 1042 updated_heads = {}
1042 1043 for base in msng_cl:
1043 1044 for parent in self.changelog.parents(base):
1044 1045 if parent in remote_heads:
1045 1046 updated_heads[parent] = True
1046 1047 updated_heads = updated_heads.keys()
1047 1048 if len(updated_heads) < len(heads):
1048 1049 self.ui.warn(_("abort: push creates new remote branches!\n"))
1049 1050 self.ui.status(_("(did you forget to merge?"
1050 1051 " use push -f to force)\n"))
1051 1052 return 1
1052 1053
1053 1054 if revs is None:
1054 1055 cg = self.changegroup(update, 'push')
1055 1056 else:
1056 1057 cg = self.changegroupsubset(update, revs, 'push')
1057 1058 return remote.addchangegroup(cg)
1058 1059
1059 1060 def changegroupsubset(self, bases, heads, source):
1060 1061 """This function generates a changegroup consisting of all the nodes
1061 1062 that are descendents of any of the bases, and ancestors of any of
1062 1063 the heads.
1063 1064
1064 1065 It is fairly complex as determining which filenodes and which
1065 1066 manifest nodes need to be included for the changeset to be complete
1066 1067 is non-trivial.
1067 1068
1068 1069 Another wrinkle is doing the reverse, figuring out which changeset in
1069 1070 the changegroup a particular filenode or manifestnode belongs to."""
1070 1071
1071 1072 self.hook('preoutgoing', throw=True, source=source)
1072 1073
1073 1074 # Set up some initial variables
1074 1075 # Make it easy to refer to self.changelog
1075 1076 cl = self.changelog
1076 1077 # msng is short for missing - compute the list of changesets in this
1077 1078 # changegroup.
1078 1079 msng_cl_lst, bases, heads = cl.nodesbetween(bases, heads)
1079 1080 # Some bases may turn out to be superfluous, and some heads may be
1080 1081 # too. nodesbetween will return the minimal set of bases and heads
1081 1082 # necessary to re-create the changegroup.
1082 1083
1083 1084 # Known heads are the list of heads that it is assumed the recipient
1084 1085 # of this changegroup will know about.
1085 1086 knownheads = {}
1086 1087 # We assume that all parents of bases are known heads.
1087 1088 for n in bases:
1088 1089 for p in cl.parents(n):
1089 1090 if p != nullid:
1090 1091 knownheads[p] = 1
1091 1092 knownheads = knownheads.keys()
1092 1093 if knownheads:
1093 1094 # Now that we know what heads are known, we can compute which
1094 1095 # changesets are known. The recipient must know about all
1095 1096 # changesets required to reach the known heads from the null
1096 1097 # changeset.
1097 1098 has_cl_set, junk, junk = cl.nodesbetween(None, knownheads)
1098 1099 junk = None
1099 1100 # Transform the list into an ersatz set.
1100 1101 has_cl_set = dict.fromkeys(has_cl_set)
1101 1102 else:
1102 1103 # If there were no known heads, the recipient cannot be assumed to
1103 1104 # know about any changesets.
1104 1105 has_cl_set = {}
1105 1106
1106 1107 # Make it easy to refer to self.manifest
1107 1108 mnfst = self.manifest
1108 1109 # We don't know which manifests are missing yet
1109 1110 msng_mnfst_set = {}
1110 1111 # Nor do we know which filenodes are missing.
1111 1112 msng_filenode_set = {}
1112 1113
1113 1114 junk = mnfst.index[mnfst.count() - 1] # Get around a bug in lazyindex
1114 1115 junk = None
1115 1116
1116 1117 # A changeset always belongs to itself, so the changenode lookup
1117 1118 # function for a changenode is identity.
1118 1119 def identity(x):
1119 1120 return x
1120 1121
1121 1122 # A function generating function. Sets up an environment for the
1122 1123 # inner function.
1123 1124 def cmp_by_rev_func(revlog):
1124 1125 # Compare two nodes by their revision number in the environment's
1125 1126 # revision history. Since the revision number both represents the
1126 1127 # most efficient order to read the nodes in, and represents a
1127 1128 # topological sorting of the nodes, this function is often useful.
1128 1129 def cmp_by_rev(a, b):
1129 1130 return cmp(revlog.rev(a), revlog.rev(b))
1130 1131 return cmp_by_rev
1131 1132
1132 1133 # If we determine that a particular file or manifest node must be a
1133 1134 # node that the recipient of the changegroup will already have, we can
1134 1135 # also assume the recipient will have all the parents. This function
1135 1136 # prunes them from the set of missing nodes.
1136 1137 def prune_parents(revlog, hasset, msngset):
1137 1138 haslst = hasset.keys()
1138 1139 haslst.sort(cmp_by_rev_func(revlog))
1139 1140 for node in haslst:
1140 1141 parentlst = [p for p in revlog.parents(node) if p != nullid]
1141 1142 while parentlst:
1142 1143 n = parentlst.pop()
1143 1144 if n not in hasset:
1144 1145 hasset[n] = 1
1145 1146 p = [p for p in revlog.parents(n) if p != nullid]
1146 1147 parentlst.extend(p)
1147 1148 for n in hasset:
1148 1149 msngset.pop(n, None)
1149 1150
1150 1151 # This is a function generating function used to set up an environment
1151 1152 # for the inner function to execute in.
1152 1153 def manifest_and_file_collector(changedfileset):
1153 1154 # This is an information gathering function that gathers
1154 1155 # information from each changeset node that goes out as part of
1155 1156 # the changegroup. The information gathered is a list of which
1156 1157 # manifest nodes are potentially required (the recipient may
1157 1158 # already have them) and total list of all files which were
1158 1159 # changed in any changeset in the changegroup.
1159 1160 #
1160 1161 # We also remember the first changenode we saw any manifest
1161 1162 # referenced by so we can later determine which changenode 'owns'
1162 1163 # the manifest.
1163 1164 def collect_manifests_and_files(clnode):
1164 1165 c = cl.read(clnode)
1165 1166 for f in c[3]:
1166 1167 # This is to make sure we only have one instance of each
1167 1168 # filename string for each filename.
1168 1169 changedfileset.setdefault(f, f)
1169 1170 msng_mnfst_set.setdefault(c[0], clnode)
1170 1171 return collect_manifests_and_files
1171 1172
1172 1173 # Figure out which manifest nodes (of the ones we think might be part
1173 1174 # of the changegroup) the recipient must know about and remove them
1174 1175 # from the changegroup.
1175 1176 def prune_manifests():
1176 1177 has_mnfst_set = {}
1177 1178 for n in msng_mnfst_set:
1178 1179 # If a 'missing' manifest thinks it belongs to a changenode
1179 1180 # the recipient is assumed to have, obviously the recipient
1180 1181 # must have that manifest.
1181 1182 linknode = cl.node(mnfst.linkrev(n))
1182 1183 if linknode in has_cl_set:
1183 1184 has_mnfst_set[n] = 1
1184 1185 prune_parents(mnfst, has_mnfst_set, msng_mnfst_set)
1185 1186
1186 1187 # Use the information collected in collect_manifests_and_files to say
1187 1188 # which changenode any manifestnode belongs to.
1188 1189 def lookup_manifest_link(mnfstnode):
1189 1190 return msng_mnfst_set[mnfstnode]
1190 1191
1191 1192 # A function generating function that sets up the initial environment
1192 1193 # the inner function.
1193 1194 def filenode_collector(changedfiles):
1194 1195 next_rev = [0]
1195 1196 # This gathers information from each manifestnode included in the
1196 1197 # changegroup about which filenodes the manifest node references
1197 1198 # so we can include those in the changegroup too.
1198 1199 #
1199 1200 # It also remembers which changenode each filenode belongs to. It
1200 1201 # does this by assuming the a filenode belongs to the changenode
1201 1202 # the first manifest that references it belongs to.
1202 1203 def collect_msng_filenodes(mnfstnode):
1203 1204 r = mnfst.rev(mnfstnode)
1204 1205 if r == next_rev[0]:
1205 1206 # If the last rev we looked at was the one just previous,
1206 1207 # we only need to see a diff.
1207 1208 delta = mdiff.patchtext(mnfst.delta(mnfstnode))
1208 1209 # For each line in the delta
1209 1210 for dline in delta.splitlines():
1210 1211 # get the filename and filenode for that line
1211 1212 f, fnode = dline.split('\0')
1212 1213 fnode = bin(fnode[:40])
1213 1214 f = changedfiles.get(f, None)
1214 1215 # And if the file is in the list of files we care
1215 1216 # about.
1216 1217 if f is not None:
1217 1218 # Get the changenode this manifest belongs to
1218 1219 clnode = msng_mnfst_set[mnfstnode]
1219 1220 # Create the set of filenodes for the file if
1220 1221 # there isn't one already.
1221 1222 ndset = msng_filenode_set.setdefault(f, {})
1222 1223 # And set the filenode's changelog node to the
1223 1224 # manifest's if it hasn't been set already.
1224 1225 ndset.setdefault(fnode, clnode)
1225 1226 else:
1226 1227 # Otherwise we need a full manifest.
1227 1228 m = mnfst.read(mnfstnode)
1228 1229 # For every file in we care about.
1229 1230 for f in changedfiles:
1230 1231 fnode = m.get(f, None)
1231 1232 # If it's in the manifest
1232 1233 if fnode is not None:
1233 1234 # See comments above.
1234 1235 clnode = msng_mnfst_set[mnfstnode]
1235 1236 ndset = msng_filenode_set.setdefault(f, {})
1236 1237 ndset.setdefault(fnode, clnode)
1237 1238 # Remember the revision we hope to see next.
1238 1239 next_rev[0] = r + 1
1239 1240 return collect_msng_filenodes
1240 1241
1241 1242 # We have a list of filenodes we think we need for a file, lets remove
1242 1243 # all those we now the recipient must have.
1243 1244 def prune_filenodes(f, filerevlog):
1244 1245 msngset = msng_filenode_set[f]
1245 1246 hasset = {}
1246 1247 # If a 'missing' filenode thinks it belongs to a changenode we
1247 1248 # assume the recipient must have, then the recipient must have
1248 1249 # that filenode.
1249 1250 for n in msngset:
1250 1251 clnode = cl.node(filerevlog.linkrev(n))
1251 1252 if clnode in has_cl_set:
1252 1253 hasset[n] = 1
1253 1254 prune_parents(filerevlog, hasset, msngset)
1254 1255
1255 1256 # A function generator function that sets up the a context for the
1256 1257 # inner function.
1257 1258 def lookup_filenode_link_func(fname):
1258 1259 msngset = msng_filenode_set[fname]
1259 1260 # Lookup the changenode the filenode belongs to.
1260 1261 def lookup_filenode_link(fnode):
1261 1262 return msngset[fnode]
1262 1263 return lookup_filenode_link
1263 1264
1264 1265 # Now that we have all theses utility functions to help out and
1265 1266 # logically divide up the task, generate the group.
1266 1267 def gengroup():
1267 1268 # The set of changed files starts empty.
1268 1269 changedfiles = {}
1269 1270 # Create a changenode group generator that will call our functions
1270 1271 # back to lookup the owning changenode and collect information.
1271 1272 group = cl.group(msng_cl_lst, identity,
1272 1273 manifest_and_file_collector(changedfiles))
1273 1274 for chnk in group:
1274 1275 yield chnk
1275 1276
1276 1277 # The list of manifests has been collected by the generator
1277 1278 # calling our functions back.
1278 1279 prune_manifests()
1279 1280 msng_mnfst_lst = msng_mnfst_set.keys()
1280 1281 # Sort the manifestnodes by revision number.
1281 1282 msng_mnfst_lst.sort(cmp_by_rev_func(mnfst))
1282 1283 # Create a generator for the manifestnodes that calls our lookup
1283 1284 # and data collection functions back.
1284 1285 group = mnfst.group(msng_mnfst_lst, lookup_manifest_link,
1285 1286 filenode_collector(changedfiles))
1286 1287 for chnk in group:
1287 1288 yield chnk
1288 1289
1289 1290 # These are no longer needed, dereference and toss the memory for
1290 1291 # them.
1291 1292 msng_mnfst_lst = None
1292 1293 msng_mnfst_set.clear()
1293 1294
1294 1295 changedfiles = changedfiles.keys()
1295 1296 changedfiles.sort()
1296 1297 # Go through all our files in order sorted by name.
1297 1298 for fname in changedfiles:
1298 1299 filerevlog = self.file(fname)
1299 1300 # Toss out the filenodes that the recipient isn't really
1300 1301 # missing.
1301 1302 if msng_filenode_set.has_key(fname):
1302 1303 prune_filenodes(fname, filerevlog)
1303 1304 msng_filenode_lst = msng_filenode_set[fname].keys()
1304 1305 else:
1305 1306 msng_filenode_lst = []
1306 1307 # If any filenodes are left, generate the group for them,
1307 1308 # otherwise don't bother.
1308 1309 if len(msng_filenode_lst) > 0:
1309 1310 yield changegroup.genchunk(fname)
1310 1311 # Sort the filenodes by their revision #
1311 1312 msng_filenode_lst.sort(cmp_by_rev_func(filerevlog))
1312 1313 # Create a group generator and only pass in a changenode
1313 1314 # lookup function as we need to collect no information
1314 1315 # from filenodes.
1315 1316 group = filerevlog.group(msng_filenode_lst,
1316 1317 lookup_filenode_link_func(fname))
1317 1318 for chnk in group:
1318 1319 yield chnk
1319 1320 if msng_filenode_set.has_key(fname):
1320 1321 # Don't need this anymore, toss it to free memory.
1321 1322 del msng_filenode_set[fname]
1322 1323 # Signal that no more groups are left.
1323 1324 yield changegroup.closechunk()
1324 1325
1325 1326 self.hook('outgoing', node=hex(msng_cl_lst[0]), source=source)
1326 1327
1327 1328 return util.chunkbuffer(gengroup())
1328 1329
1329 1330 def changegroup(self, basenodes, source):
1330 1331 """Generate a changegroup of all nodes that we have that a recipient
1331 1332 doesn't.
1332 1333
1333 1334 This is much easier than the previous function as we can assume that
1334 1335 the recipient has any changenode we aren't sending them."""
1335 1336
1336 1337 self.hook('preoutgoing', throw=True, source=source)
1337 1338
1338 1339 cl = self.changelog
1339 1340 nodes = cl.nodesbetween(basenodes, None)[0]
1340 1341 revset = dict.fromkeys([cl.rev(n) for n in nodes])
1341 1342
1342 1343 def identity(x):
1343 1344 return x
1344 1345
1345 1346 def gennodelst(revlog):
1346 1347 for r in xrange(0, revlog.count()):
1347 1348 n = revlog.node(r)
1348 1349 if revlog.linkrev(n) in revset:
1349 1350 yield n
1350 1351
1351 1352 def changed_file_collector(changedfileset):
1352 1353 def collect_changed_files(clnode):
1353 1354 c = cl.read(clnode)
1354 1355 for fname in c[3]:
1355 1356 changedfileset[fname] = 1
1356 1357 return collect_changed_files
1357 1358
1358 1359 def lookuprevlink_func(revlog):
1359 1360 def lookuprevlink(n):
1360 1361 return cl.node(revlog.linkrev(n))
1361 1362 return lookuprevlink
1362 1363
1363 1364 def gengroup():
1364 1365 # construct a list of all changed files
1365 1366 changedfiles = {}
1366 1367
1367 1368 for chnk in cl.group(nodes, identity,
1368 1369 changed_file_collector(changedfiles)):
1369 1370 yield chnk
1370 1371 changedfiles = changedfiles.keys()
1371 1372 changedfiles.sort()
1372 1373
1373 1374 mnfst = self.manifest
1374 1375 nodeiter = gennodelst(mnfst)
1375 1376 for chnk in mnfst.group(nodeiter, lookuprevlink_func(mnfst)):
1376 1377 yield chnk
1377 1378
1378 1379 for fname in changedfiles:
1379 1380 filerevlog = self.file(fname)
1380 1381 nodeiter = gennodelst(filerevlog)
1381 1382 nodeiter = list(nodeiter)
1382 1383 if nodeiter:
1383 1384 yield changegroup.genchunk(fname)
1384 1385 lookup = lookuprevlink_func(filerevlog)
1385 1386 for chnk in filerevlog.group(nodeiter, lookup):
1386 1387 yield chnk
1387 1388
1388 1389 yield changegroup.closechunk()
1389 1390 self.hook('outgoing', node=hex(nodes[0]), source=source)
1390 1391
1391 1392 return util.chunkbuffer(gengroup())
1392 1393
1393 1394 def addchangegroup(self, source):
1394 1395 """add changegroup to repo.
1395 1396 returns number of heads modified or added + 1."""
1396 1397
1397 1398 def csmap(x):
1398 1399 self.ui.debug(_("add changeset %s\n") % short(x))
1399 1400 return cl.count()
1400 1401
1401 1402 def revmap(x):
1402 1403 return cl.rev(x)
1403 1404
1404 1405 if not source:
1405 1406 return 0
1406 1407
1407 1408 self.hook('prechangegroup', throw=True)
1408 1409
1409 1410 changesets = files = revisions = 0
1410 1411
1411 1412 tr = self.transaction()
1412 1413
1413 1414 # write changelog and manifest data to temp files so
1414 1415 # concurrent readers will not see inconsistent view
1415 1416 cl = appendfile.appendchangelog(self.opener)
1416 1417
1417 1418 oldheads = len(cl.heads())
1418 1419
1419 1420 # pull off the changeset group
1420 1421 self.ui.status(_("adding changesets\n"))
1421 1422 co = cl.tip()
1422 1423 chunkiter = changegroup.chunkiter(source)
1423 1424 cn = cl.addgroup(chunkiter, csmap, tr, 1) # unique
1424 1425 cnr, cor = map(cl.rev, (cn, co))
1425 1426 if cn == nullid:
1426 1427 cnr = cor
1427 1428 changesets = cnr - cor
1428 1429
1429 1430 mf = appendfile.appendmanifest(self.opener)
1430 1431
1431 1432 # pull off the manifest group
1432 1433 self.ui.status(_("adding manifests\n"))
1433 1434 mm = mf.tip()
1434 1435 chunkiter = changegroup.chunkiter(source)
1435 1436 mo = mf.addgroup(chunkiter, revmap, tr)
1436 1437
1437 1438 # process the files
1438 1439 self.ui.status(_("adding file changes\n"))
1439 1440 while 1:
1440 1441 f = changegroup.getchunk(source)
1441 1442 if not f:
1442 1443 break
1443 1444 self.ui.debug(_("adding %s revisions\n") % f)
1444 1445 fl = self.file(f)
1445 1446 o = fl.count()
1446 1447 chunkiter = changegroup.chunkiter(source)
1447 1448 n = fl.addgroup(chunkiter, revmap, tr)
1448 1449 revisions += fl.count() - o
1449 1450 files += 1
1450 1451
1451 1452 # write order here is important so concurrent readers will see
1452 1453 # consistent view of repo
1453 1454 mf.writedata()
1454 1455 cl.writedata()
1455 1456
1456 1457 # make changelog and manifest see real files again
1457 1458 self.changelog = changelog.changelog(self.opener)
1458 1459 self.manifest = manifest.manifest(self.opener)
1460 self.changelog.checkinlinesize(tr)
1461 self.changelog.checkinlinesize(tr)
1459 1462
1460 1463 newheads = len(self.changelog.heads())
1461 1464 heads = ""
1462 1465 if oldheads and newheads > oldheads:
1463 1466 heads = _(" (+%d heads)") % (newheads - oldheads)
1464 1467
1465 1468 self.ui.status(_("added %d changesets"
1466 1469 " with %d changes to %d files%s\n")
1467 1470 % (changesets, revisions, files, heads))
1468 1471
1469 1472 self.hook('pretxnchangegroup', throw=True,
1470 1473 node=hex(self.changelog.node(cor+1)))
1471 1474
1472 1475 tr.close()
1473 1476
1474 1477 if changesets > 0:
1475 1478 self.hook("changegroup", node=hex(self.changelog.node(cor+1)))
1476 1479
1477 1480 for i in range(cor + 1, cnr + 1):
1478 1481 self.hook("incoming", node=hex(self.changelog.node(i)))
1479 1482
1480 1483 return newheads - oldheads + 1
1481 1484
1482 1485 def update(self, node, allow=False, force=False, choose=None,
1483 1486 moddirstate=True, forcemerge=False, wlock=None):
1484 1487 pl = self.dirstate.parents()
1485 1488 if not force and pl[1] != nullid:
1486 1489 self.ui.warn(_("aborting: outstanding uncommitted merges\n"))
1487 1490 return 1
1488 1491
1489 1492 err = False
1490 1493
1491 1494 p1, p2 = pl[0], node
1492 1495 pa = self.changelog.ancestor(p1, p2)
1493 1496 m1n = self.changelog.read(p1)[0]
1494 1497 m2n = self.changelog.read(p2)[0]
1495 1498 man = self.manifest.ancestor(m1n, m2n)
1496 1499 m1 = self.manifest.read(m1n)
1497 1500 mf1 = self.manifest.readflags(m1n)
1498 1501 m2 = self.manifest.read(m2n).copy()
1499 1502 mf2 = self.manifest.readflags(m2n)
1500 1503 ma = self.manifest.read(man)
1501 1504 mfa = self.manifest.readflags(man)
1502 1505
1503 1506 modified, added, removed, deleted, unknown = self.changes()
1504 1507
1505 1508 # is this a jump, or a merge? i.e. is there a linear path
1506 1509 # from p1 to p2?
1507 1510 linear_path = (pa == p1 or pa == p2)
1508 1511
1509 1512 if allow and linear_path:
1510 1513 raise util.Abort(_("there is nothing to merge, "
1511 1514 "just use 'hg update'"))
1512 1515 if allow and not forcemerge:
1513 1516 if modified or added or removed:
1514 1517 raise util.Abort(_("outstanding uncommitted changes"))
1515 1518 if not forcemerge and not force:
1516 1519 for f in unknown:
1517 1520 if f in m2:
1518 1521 t1 = self.wread(f)
1519 1522 t2 = self.file(f).read(m2[f])
1520 1523 if cmp(t1, t2) != 0:
1521 1524 raise util.Abort(_("'%s' already exists in the working"
1522 1525 " dir and differs from remote") % f)
1523 1526
1524 1527 # resolve the manifest to determine which files
1525 1528 # we care about merging
1526 1529 self.ui.note(_("resolving manifests\n"))
1527 1530 self.ui.debug(_(" force %s allow %s moddirstate %s linear %s\n") %
1528 1531 (force, allow, moddirstate, linear_path))
1529 1532 self.ui.debug(_(" ancestor %s local %s remote %s\n") %
1530 1533 (short(man), short(m1n), short(m2n)))
1531 1534
1532 1535 merge = {}
1533 1536 get = {}
1534 1537 remove = []
1535 1538
1536 1539 # construct a working dir manifest
1537 1540 mw = m1.copy()
1538 1541 mfw = mf1.copy()
1539 1542 umap = dict.fromkeys(unknown)
1540 1543
1541 1544 for f in added + modified + unknown:
1542 1545 mw[f] = ""
1543 1546 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1544 1547
1545 1548 if moddirstate and not wlock:
1546 1549 wlock = self.wlock()
1547 1550
1548 1551 for f in deleted + removed:
1549 1552 if f in mw:
1550 1553 del mw[f]
1551 1554
1552 1555 # If we're jumping between revisions (as opposed to merging),
1553 1556 # and if neither the working directory nor the target rev has
1554 1557 # the file, then we need to remove it from the dirstate, to
1555 1558 # prevent the dirstate from listing the file when it is no
1556 1559 # longer in the manifest.
1557 1560 if moddirstate and linear_path and f not in m2:
1558 1561 self.dirstate.forget((f,))
1559 1562
1560 1563 # Compare manifests
1561 1564 for f, n in mw.iteritems():
1562 1565 if choose and not choose(f):
1563 1566 continue
1564 1567 if f in m2:
1565 1568 s = 0
1566 1569
1567 1570 # is the wfile new since m1, and match m2?
1568 1571 if f not in m1:
1569 1572 t1 = self.wread(f)
1570 1573 t2 = self.file(f).read(m2[f])
1571 1574 if cmp(t1, t2) == 0:
1572 1575 n = m2[f]
1573 1576 del t1, t2
1574 1577
1575 1578 # are files different?
1576 1579 if n != m2[f]:
1577 1580 a = ma.get(f, nullid)
1578 1581 # are both different from the ancestor?
1579 1582 if n != a and m2[f] != a:
1580 1583 self.ui.debug(_(" %s versions differ, resolve\n") % f)
1581 1584 # merge executable bits
1582 1585 # "if we changed or they changed, change in merge"
1583 1586 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1584 1587 mode = ((a^b) | (a^c)) ^ a
1585 1588 merge[f] = (m1.get(f, nullid), m2[f], mode)
1586 1589 s = 1
1587 1590 # are we clobbering?
1588 1591 # is remote's version newer?
1589 1592 # or are we going back in time?
1590 1593 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1591 1594 self.ui.debug(_(" remote %s is newer, get\n") % f)
1592 1595 get[f] = m2[f]
1593 1596 s = 1
1594 1597 elif f in umap:
1595 1598 # this unknown file is the same as the checkout
1596 1599 get[f] = m2[f]
1597 1600
1598 1601 if not s and mfw[f] != mf2[f]:
1599 1602 if force:
1600 1603 self.ui.debug(_(" updating permissions for %s\n") % f)
1601 1604 util.set_exec(self.wjoin(f), mf2[f])
1602 1605 else:
1603 1606 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1604 1607 mode = ((a^b) | (a^c)) ^ a
1605 1608 if mode != b:
1606 1609 self.ui.debug(_(" updating permissions for %s\n")
1607 1610 % f)
1608 1611 util.set_exec(self.wjoin(f), mode)
1609 1612 del m2[f]
1610 1613 elif f in ma:
1611 1614 if n != ma[f]:
1612 1615 r = _("d")
1613 1616 if not force and (linear_path or allow):
1614 1617 r = self.ui.prompt(
1615 1618 (_(" local changed %s which remote deleted\n") % f) +
1616 1619 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1617 1620 if r == _("d"):
1618 1621 remove.append(f)
1619 1622 else:
1620 1623 self.ui.debug(_("other deleted %s\n") % f)
1621 1624 remove.append(f) # other deleted it
1622 1625 else:
1623 1626 # file is created on branch or in working directory
1624 1627 if force and f not in umap:
1625 1628 self.ui.debug(_("remote deleted %s, clobbering\n") % f)
1626 1629 remove.append(f)
1627 1630 elif n == m1.get(f, nullid): # same as parent
1628 1631 if p2 == pa: # going backwards?
1629 1632 self.ui.debug(_("remote deleted %s\n") % f)
1630 1633 remove.append(f)
1631 1634 else:
1632 1635 self.ui.debug(_("local modified %s, keeping\n") % f)
1633 1636 else:
1634 1637 self.ui.debug(_("working dir created %s, keeping\n") % f)
1635 1638
1636 1639 for f, n in m2.iteritems():
1637 1640 if choose and not choose(f):
1638 1641 continue
1639 1642 if f[0] == "/":
1640 1643 continue
1641 1644 if f in ma and n != ma[f]:
1642 1645 r = _("k")
1643 1646 if not force and (linear_path or allow):
1644 1647 r = self.ui.prompt(
1645 1648 (_("remote changed %s which local deleted\n") % f) +
1646 1649 _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
1647 1650 if r == _("k"):
1648 1651 get[f] = n
1649 1652 elif f not in ma:
1650 1653 self.ui.debug(_("remote created %s\n") % f)
1651 1654 get[f] = n
1652 1655 else:
1653 1656 if force or p2 == pa: # going backwards?
1654 1657 self.ui.debug(_("local deleted %s, recreating\n") % f)
1655 1658 get[f] = n
1656 1659 else:
1657 1660 self.ui.debug(_("local deleted %s\n") % f)
1658 1661
1659 1662 del mw, m1, m2, ma
1660 1663
1661 1664 if force:
1662 1665 for f in merge:
1663 1666 get[f] = merge[f][1]
1664 1667 merge = {}
1665 1668
1666 1669 if linear_path or force:
1667 1670 # we don't need to do any magic, just jump to the new rev
1668 1671 branch_merge = False
1669 1672 p1, p2 = p2, nullid
1670 1673 else:
1671 1674 if not allow:
1672 1675 self.ui.status(_("this update spans a branch"
1673 1676 " affecting the following files:\n"))
1674 1677 fl = merge.keys() + get.keys()
1675 1678 fl.sort()
1676 1679 for f in fl:
1677 1680 cf = ""
1678 1681 if f in merge:
1679 1682 cf = _(" (resolve)")
1680 1683 self.ui.status(" %s%s\n" % (f, cf))
1681 1684 self.ui.warn(_("aborting update spanning branches!\n"))
1682 1685 self.ui.status(_("(use 'hg merge' to merge across branches"
1683 1686 " or 'hg update -C' to lose changes)\n"))
1684 1687 return 1
1685 1688 branch_merge = True
1686 1689
1687 1690 # get the files we don't need to change
1688 1691 files = get.keys()
1689 1692 files.sort()
1690 1693 for f in files:
1691 1694 if f[0] == "/":
1692 1695 continue
1693 1696 self.ui.note(_("getting %s\n") % f)
1694 1697 t = self.file(f).read(get[f])
1695 1698 self.wwrite(f, t)
1696 1699 util.set_exec(self.wjoin(f), mf2[f])
1697 1700 if moddirstate:
1698 1701 if branch_merge:
1699 1702 self.dirstate.update([f], 'n', st_mtime=-1)
1700 1703 else:
1701 1704 self.dirstate.update([f], 'n')
1702 1705
1703 1706 # merge the tricky bits
1704 1707 failedmerge = []
1705 1708 files = merge.keys()
1706 1709 files.sort()
1707 1710 xp1 = hex(p1)
1708 1711 xp2 = hex(p2)
1709 1712 for f in files:
1710 1713 self.ui.status(_("merging %s\n") % f)
1711 1714 my, other, flag = merge[f]
1712 1715 ret = self.merge3(f, my, other, xp1, xp2)
1713 1716 if ret:
1714 1717 err = True
1715 1718 failedmerge.append(f)
1716 1719 util.set_exec(self.wjoin(f), flag)
1717 1720 if moddirstate:
1718 1721 if branch_merge:
1719 1722 # We've done a branch merge, mark this file as merged
1720 1723 # so that we properly record the merger later
1721 1724 self.dirstate.update([f], 'm')
1722 1725 else:
1723 1726 # We've update-merged a locally modified file, so
1724 1727 # we set the dirstate to emulate a normal checkout
1725 1728 # of that file some time in the past. Thus our
1726 1729 # merge will appear as a normal local file
1727 1730 # modification.
1728 1731 f_len = len(self.file(f).read(other))
1729 1732 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1730 1733
1731 1734 remove.sort()
1732 1735 for f in remove:
1733 1736 self.ui.note(_("removing %s\n") % f)
1734 1737 util.audit_path(f)
1735 1738 try:
1736 1739 util.unlink(self.wjoin(f))
1737 1740 except OSError, inst:
1738 1741 if inst.errno != errno.ENOENT:
1739 1742 self.ui.warn(_("update failed to remove %s: %s!\n") %
1740 1743 (f, inst.strerror))
1741 1744 if moddirstate:
1742 1745 if branch_merge:
1743 1746 self.dirstate.update(remove, 'r')
1744 1747 else:
1745 1748 self.dirstate.forget(remove)
1746 1749
1747 1750 if moddirstate:
1748 1751 self.dirstate.setparents(p1, p2)
1749 1752
1750 1753 stat = ((len(get), _("updated")),
1751 1754 (len(merge) - len(failedmerge), _("merged")),
1752 1755 (len(remove), _("removed")),
1753 1756 (len(failedmerge), _("unresolved")))
1754 1757 note = ", ".join([_("%d files %s") % s for s in stat])
1755 1758 self.ui.note("%s\n" % note)
1756 1759 if moddirstate and branch_merge:
1757 1760 self.ui.note(_("(branch merge, don't forget to commit)\n"))
1758 1761
1759 1762 return err
1760 1763
1761 1764 def merge3(self, fn, my, other, p1, p2):
1762 1765 """perform a 3-way merge in the working directory"""
1763 1766
1764 1767 def temp(prefix, node):
1765 1768 pre = "%s~%s." % (os.path.basename(fn), prefix)
1766 1769 (fd, name) = tempfile.mkstemp("", pre)
1767 1770 f = os.fdopen(fd, "wb")
1768 1771 self.wwrite(fn, fl.read(node), f)
1769 1772 f.close()
1770 1773 return name
1771 1774
1772 1775 fl = self.file(fn)
1773 1776 base = fl.ancestor(my, other)
1774 1777 a = self.wjoin(fn)
1775 1778 b = temp("base", base)
1776 1779 c = temp("other", other)
1777 1780
1778 1781 self.ui.note(_("resolving %s\n") % fn)
1779 1782 self.ui.debug(_("file %s: my %s other %s ancestor %s\n") %
1780 1783 (fn, short(my), short(other), short(base)))
1781 1784
1782 1785 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1783 1786 or "hgmerge")
1784 1787 r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=self.root,
1785 1788 environ={'HG_FILE': fn,
1786 1789 'HG_MY_NODE': p1,
1787 1790 'HG_OTHER_NODE': p2,
1788 1791 'HG_FILE_MY_NODE': hex(my),
1789 1792 'HG_FILE_OTHER_NODE': hex(other),
1790 1793 'HG_FILE_BASE_NODE': hex(base)})
1791 1794 if r:
1792 1795 self.ui.warn(_("merging %s failed!\n") % fn)
1793 1796
1794 1797 os.unlink(b)
1795 1798 os.unlink(c)
1796 1799 return r
1797 1800
1798 1801 def verify(self):
1799 1802 filelinkrevs = {}
1800 1803 filenodes = {}
1801 1804 changesets = revisions = files = 0
1802 1805 errors = [0]
1803 1806 neededmanifests = {}
1804 1807
1805 1808 def err(msg):
1806 1809 self.ui.warn(msg + "\n")
1807 1810 errors[0] += 1
1808 1811
1809 1812 def checksize(obj, name):
1810 1813 d = obj.checksize()
1811 1814 if d[0]:
1812 1815 err(_("%s data length off by %d bytes") % (name, d[0]))
1813 1816 if d[1]:
1814 1817 err(_("%s index contains %d extra bytes") % (name, d[1]))
1815 1818
1816 1819 seen = {}
1817 1820 self.ui.status(_("checking changesets\n"))
1818 1821 checksize(self.changelog, "changelog")
1819 1822
1820 1823 for i in range(self.changelog.count()):
1821 1824 changesets += 1
1822 1825 n = self.changelog.node(i)
1823 1826 l = self.changelog.linkrev(n)
1824 1827 if l != i:
1825 1828 err(_("incorrect link (%d) for changeset revision %d") %(l, i))
1826 1829 if n in seen:
1827 1830 err(_("duplicate changeset at revision %d") % i)
1828 1831 seen[n] = 1
1829 1832
1830 1833 for p in self.changelog.parents(n):
1831 1834 if p not in self.changelog.nodemap:
1832 1835 err(_("changeset %s has unknown parent %s") %
1833 1836 (short(n), short(p)))
1834 1837 try:
1835 1838 changes = self.changelog.read(n)
1836 1839 except KeyboardInterrupt:
1837 1840 self.ui.warn(_("interrupted"))
1838 1841 raise
1839 1842 except Exception, inst:
1840 1843 err(_("unpacking changeset %s: %s") % (short(n), inst))
1841 1844 continue
1842 1845
1843 1846 neededmanifests[changes[0]] = n
1844 1847
1845 1848 for f in changes[3]:
1846 1849 filelinkrevs.setdefault(f, []).append(i)
1847 1850
1848 1851 seen = {}
1849 1852 self.ui.status(_("checking manifests\n"))
1850 1853 checksize(self.manifest, "manifest")
1851 1854
1852 1855 for i in range(self.manifest.count()):
1853 1856 n = self.manifest.node(i)
1854 1857 l = self.manifest.linkrev(n)
1855 1858
1856 1859 if l < 0 or l >= self.changelog.count():
1857 1860 err(_("bad manifest link (%d) at revision %d") % (l, i))
1858 1861
1859 1862 if n in neededmanifests:
1860 1863 del neededmanifests[n]
1861 1864
1862 1865 if n in seen:
1863 1866 err(_("duplicate manifest at revision %d") % i)
1864 1867
1865 1868 seen[n] = 1
1866 1869
1867 1870 for p in self.manifest.parents(n):
1868 1871 if p not in self.manifest.nodemap:
1869 1872 err(_("manifest %s has unknown parent %s") %
1870 1873 (short(n), short(p)))
1871 1874
1872 1875 try:
1873 1876 delta = mdiff.patchtext(self.manifest.delta(n))
1874 1877 except KeyboardInterrupt:
1875 1878 self.ui.warn(_("interrupted"))
1876 1879 raise
1877 1880 except Exception, inst:
1878 1881 err(_("unpacking manifest %s: %s") % (short(n), inst))
1879 1882 continue
1880 1883
1881 1884 try:
1882 1885 ff = [ l.split('\0') for l in delta.splitlines() ]
1883 1886 for f, fn in ff:
1884 1887 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1885 1888 except (ValueError, TypeError), inst:
1886 1889 err(_("broken delta in manifest %s: %s") % (short(n), inst))
1887 1890
1888 1891 self.ui.status(_("crosschecking files in changesets and manifests\n"))
1889 1892
1890 1893 for m, c in neededmanifests.items():
1891 1894 err(_("Changeset %s refers to unknown manifest %s") %
1892 1895 (short(m), short(c)))
1893 1896 del neededmanifests
1894 1897
1895 1898 for f in filenodes:
1896 1899 if f not in filelinkrevs:
1897 1900 err(_("file %s in manifest but not in changesets") % f)
1898 1901
1899 1902 for f in filelinkrevs:
1900 1903 if f not in filenodes:
1901 1904 err(_("file %s in changeset but not in manifest") % f)
1902 1905
1903 1906 self.ui.status(_("checking files\n"))
1904 1907 ff = filenodes.keys()
1905 1908 ff.sort()
1906 1909 for f in ff:
1907 1910 if f == "/dev/null":
1908 1911 continue
1909 1912 files += 1
1910 1913 if not f:
1911 1914 err(_("file without name in manifest %s") % short(n))
1912 1915 continue
1913 1916 fl = self.file(f)
1914 1917 checksize(fl, f)
1915 1918
1916 1919 nodes = {nullid: 1}
1917 1920 seen = {}
1918 1921 for i in range(fl.count()):
1919 1922 revisions += 1
1920 1923 n = fl.node(i)
1921 1924
1922 1925 if n in seen:
1923 1926 err(_("%s: duplicate revision %d") % (f, i))
1924 1927 if n not in filenodes[f]:
1925 1928 err(_("%s: %d:%s not in manifests") % (f, i, short(n)))
1926 1929 else:
1927 1930 del filenodes[f][n]
1928 1931
1929 1932 flr = fl.linkrev(n)
1930 1933 if flr not in filelinkrevs.get(f, []):
1931 1934 err(_("%s:%s points to unexpected changeset %d")
1932 1935 % (f, short(n), flr))
1933 1936 else:
1934 1937 filelinkrevs[f].remove(flr)
1935 1938
1936 1939 # verify contents
1937 1940 try:
1938 1941 t = fl.read(n)
1939 1942 except KeyboardInterrupt:
1940 1943 self.ui.warn(_("interrupted"))
1941 1944 raise
1942 1945 except Exception, inst:
1943 1946 err(_("unpacking file %s %s: %s") % (f, short(n), inst))
1944 1947
1945 1948 # verify parents
1946 1949 (p1, p2) = fl.parents(n)
1947 1950 if p1 not in nodes:
1948 1951 err(_("file %s:%s unknown parent 1 %s") %
1949 1952 (f, short(n), short(p1)))
1950 1953 if p2 not in nodes:
1951 1954 err(_("file %s:%s unknown parent 2 %s") %
1952 1955 (f, short(n), short(p1)))
1953 1956 nodes[n] = 1
1954 1957
1955 1958 # cross-check
1956 1959 for node in filenodes[f]:
1957 1960 err(_("node %s in manifests not in %s") % (hex(node), f))
1958 1961
1959 1962 self.ui.status(_("%d files, %d changesets, %d total revisions\n") %
1960 1963 (files, changesets, revisions))
1961 1964
1962 1965 if errors[0]:
1963 1966 self.ui.warn(_("%d integrity errors encountered!\n") % errors[0])
1964 1967 return 1
1965 1968
1966 1969 # used to avoid circular references so destructors work
1967 1970 def aftertrans(base):
1968 1971 p = base
1969 1972 def a():
1970 1973 util.rename(os.path.join(p, "journal"), os.path.join(p, "undo"))
1971 1974 util.rename(os.path.join(p, "journal.dirstate"),
1972 1975 os.path.join(p, "undo.dirstate"))
1973 1976 return a
1974 1977
@@ -1,1062 +1,1064
1 1 """
2 2 revlog.py - storage back-end for mercurial
3 3
4 4 This provides efficient delta storage with O(1) retrieve and append
5 5 and O(changes) merge between branches
6 6
7 7 Copyright 2005 Matt Mackall <mpm@selenic.com>
8 8
9 9 This software may be used and distributed according to the terms
10 10 of the GNU General Public License, incorporated herein by reference.
11 11 """
12 12
13 13 from node import *
14 14 from i18n import gettext as _
15 15 from demandload import demandload
16 16 demandload(globals(), "binascii changegroup errno heapq mdiff os")
17 17 demandload(globals(), "sha struct zlib")
18 18
19 19 # revlog version strings
20 20 REVLOGV0 = 0
21 21 REVLOGNG = 1
22 22
23 23 # revlog flags
24 24 REVLOGNGINLINEDATA = (1 << 16)
25 25
26 26 def flagstr(flag):
27 27 if flag == "inline":
28 28 return REVLOGNGINLINEDATA
29 29 raise RevlogError(_("unknown revlog flag %s" % flag))
30 30
31 31 def hash(text, p1, p2):
32 32 """generate a hash from the given text and its parent hashes
33 33
34 34 This hash combines both the current file contents and its history
35 35 in a manner that makes it easy to distinguish nodes with the same
36 36 content in the revision graph.
37 37 """
38 38 l = [p1, p2]
39 39 l.sort()
40 40 s = sha.new(l[0])
41 41 s.update(l[1])
42 42 s.update(text)
43 43 return s.digest()
44 44
45 45 def compress(text):
46 46 """ generate a possibly-compressed representation of text """
47 47 if not text: return ("", text)
48 48 if len(text) < 44:
49 49 if text[0] == '\0': return ("", text)
50 50 return ('u', text)
51 51 bin = zlib.compress(text)
52 52 if len(bin) > len(text):
53 53 if text[0] == '\0': return ("", text)
54 54 return ('u', text)
55 55 return ("", bin)
56 56
57 57 def decompress(bin):
58 58 """ decompress the given input """
59 59 if not bin: return bin
60 60 t = bin[0]
61 61 if t == '\0': return bin
62 62 if t == 'x': return zlib.decompress(bin)
63 63 if t == 'u': return bin[1:]
64 64 raise RevlogError(_("unknown compression type %r") % t)
65 65
66 66 indexformatv0 = ">4l20s20s20s"
67 67 # index ng:
68 68 # 6 bytes offset
69 69 # 2 bytes flags
70 70 # 4 bytes compressed length
71 71 # 4 bytes uncompressed length
72 72 # 4 bytes: base rev
73 73 # 4 bytes link rev
74 74 # 4 bytes parent 1 rev
75 75 # 4 bytes parent 2 rev
76 76 # 32 bytes: nodeid
77 77 indexformatng = ">Qiiiiii20s12x"
78 78 versionformat = ">i"
79 79
80 80 class lazyparser(object):
81 81 """
82 82 this class avoids the need to parse the entirety of large indices
83 83
84 84 By default we parse and load 1000 entries at a time.
85 85
86 86 If no position is specified, we load the whole index, and replace
87 87 the lazy objects in revlog with the underlying objects for
88 88 efficiency in cases where we look at most of the nodes.
89 89 """
90 90 def __init__(self, data, revlog, indexformat):
91 91 self.data = data
92 92 self.s = struct.calcsize(indexformat)
93 93 self.indexformat = indexformat
94 94 self.l = len(data)/self.s
95 95 self.index = [None] * self.l
96 96 self.map = {nullid: -1}
97 97 self.all = 0
98 98 self.revlog = revlog
99 99
100 100 def load(self, pos=None):
101 101 if self.all: return
102 102 if pos is not None:
103 103 block = pos / 1000
104 104 i = block * 1000
105 105 end = min(self.l, i + 1000)
106 106 else:
107 107 self.all = 1
108 108 i = 0
109 109 end = self.l
110 110 self.revlog.index = self.index
111 111 self.revlog.nodemap = self.map
112 112
113 113 while i < end:
114 114 if not self.index[i]:
115 115 d = self.data[i * self.s: (i + 1) * self.s]
116 116 e = struct.unpack(self.indexformat, d)
117 117 self.index[i] = e
118 118 self.map[e[-1]] = i
119 119 i += 1
120 120
121 121 class lazyindex(object):
122 122 """a lazy version of the index array"""
123 123 def __init__(self, parser):
124 124 self.p = parser
125 125 def __len__(self):
126 126 return len(self.p.index)
127 127 def load(self, pos):
128 128 if pos < 0:
129 129 pos += len(self.p.index)
130 130 self.p.load(pos)
131 131 return self.p.index[pos]
132 132 def __getitem__(self, pos):
133 133 return self.p.index[pos] or self.load(pos)
134 134 def __setitem__(self, pos, item):
135 135 self.p.index[pos] = item
136 136 def __delitem__(self, pos):
137 137 del self.p.index[pos]
138 138 def append(self, e):
139 139 self.p.index.append(e)
140 140
141 141 class lazymap(object):
142 142 """a lazy version of the node map"""
143 143 def __init__(self, parser):
144 144 self.p = parser
145 145 def load(self, key):
146 146 if self.p.all: return
147 147 n = self.p.data.find(key)
148 148 if n < 0:
149 149 raise KeyError(key)
150 150 pos = n / self.p.s
151 151 self.p.load(pos)
152 152 def __contains__(self, key):
153 153 self.p.load()
154 154 return key in self.p.map
155 155 def __iter__(self):
156 156 yield nullid
157 157 for i in xrange(self.p.l):
158 158 try:
159 159 yield self.p.index[i][-1]
160 160 except:
161 161 self.p.load(i)
162 162 yield self.p.index[i][-1]
163 163 def __getitem__(self, key):
164 164 try:
165 165 return self.p.map[key]
166 166 except KeyError:
167 167 try:
168 168 self.load(key)
169 169 return self.p.map[key]
170 170 except KeyError:
171 171 raise KeyError("node " + hex(key))
172 172 def __setitem__(self, key, val):
173 173 self.p.map[key] = val
174 174 def __delitem__(self, key):
175 175 del self.p.map[key]
176 176
177 177 class RevlogError(Exception): pass
178 178
179 179 class revlog(object):
180 180 """
181 181 the underlying revision storage object
182 182
183 183 A revlog consists of two parts, an index and the revision data.
184 184
185 185 The index is a file with a fixed record size containing
186 186 information on each revision, includings its nodeid (hash), the
187 187 nodeids of its parents, the position and offset of its data within
188 188 the data file, and the revision it's based on. Finally, each entry
189 189 contains a linkrev entry that can serve as a pointer to external
190 190 data.
191 191
192 192 The revision data itself is a linear collection of data chunks.
193 193 Each chunk represents a revision and is usually represented as a
194 194 delta against the previous chunk. To bound lookup time, runs of
195 195 deltas are limited to about 2 times the length of the original
196 196 version data. This makes retrieval of a version proportional to
197 197 its size, or O(1) relative to the number of revisions.
198 198
199 199 Both pieces of the revlog are written to in an append-only
200 200 fashion, which means we never need to rewrite a file to insert or
201 201 remove data, and can use some simple techniques to avoid the need
202 202 for locking while reading.
203 203 """
204 204 def __init__(self, opener, indexfile, datafile, defversion=0):
205 205 """
206 206 create a revlog object
207 207
208 208 opener is a function that abstracts the file opening operation
209 209 and can be used to implement COW semantics or the like.
210 210 """
211 211 self.indexfile = indexfile
212 212 self.datafile = datafile
213 213 self.opener = opener
214 214
215 215 self.indexstat = None
216 216 self.cache = None
217 217 self.chunkcache = None
218 218 self.defversion = defversion
219 219 self.load()
220 220
221 221 def load(self):
222 222 v = self.defversion
223 223 try:
224 224 f = self.opener(self.indexfile)
225 225 i = f.read()
226 226 except IOError, inst:
227 227 if inst.errno != errno.ENOENT:
228 228 raise
229 229 i = ""
230 230 else:
231 231 try:
232 232 st = os.fstat(f.fileno())
233 233 except AttributeError, inst:
234 234 st = None
235 235 else:
236 236 oldst = self.indexstat
237 237 if (oldst and st.st_dev == oldst.st_dev
238 238 and st.st_ino == oldst.st_ino
239 239 and st.st_mtime == oldst.st_mtime
240 240 and st.st_ctime == oldst.st_ctime):
241 241 return
242 242 self.indexstat = st
243 243 if len(i) > 0:
244 244 v = struct.unpack(versionformat, i[:4])[0]
245 245 flags = v & ~0xFFFF
246 246 fmt = v & 0xFFFF
247 247 if fmt == 0:
248 248 if flags:
249 249 raise RevlogError(_("index %s invalid flags %x for format v0" %
250 250 (self.indexfile, flags)))
251 251 elif fmt == REVLOGNG:
252 252 if flags & ~REVLOGNGINLINEDATA:
253 253 raise RevlogError(_("index %s invalid flags %x for revlogng" %
254 254 (self.indexfile, flags)))
255 255 else:
256 256 raise RevlogError(_("index %s invalid format %d" %
257 257 (self.indexfile, fmt)))
258 258 self.version = v
259 259 if v == 0:
260 260 self.indexformat = indexformatv0
261 261 else:
262 262 self.indexformat = indexformatng
263 263
264 264 if i:
265 265 if not self.inlinedata() and st and st.st_size > 10000:
266 266 # big index, let's parse it on demand
267 267 parser = lazyparser(i, self, self.indexformat)
268 268 self.index = lazyindex(parser)
269 269 self.nodemap = lazymap(parser)
270 270 else:
271 271 self.parseindex(i)
272 272 if self.inlinedata():
273 273 # we've already got the entire data file read in, save it
274 274 # in the chunk data
275 275 self.chunkcache = (0, i)
276 276 if self.version != 0:
277 277 e = list(self.index[0])
278 278 type = self.ngtype(e[0])
279 279 e[0] = self.offset_type(0, type)
280 280 self.index[0] = e
281 281 else:
282 282 self.nodemap = { nullid: -1}
283 283 self.index = []
284 284
285 285
286 286 def parseindex(self, data):
287 287 s = struct.calcsize(self.indexformat)
288 288 l = len(data)
289 289 self.index = []
290 290 self.nodemap = {nullid: -1}
291 291 inline = self.inlinedata()
292 292 off = 0
293 293 n = 0
294 294 while off < l:
295 295 e = struct.unpack(self.indexformat, data[off:off + s])
296 296 self.index.append(e)
297 297 self.nodemap[e[-1]] = n
298 298 n += 1
299 299 off += s
300 300 if inline:
301 301 off += e[1]
302 302
303 303 def ngoffset(self, q):
304 304 if q & 0xFFFF:
305 305 raise RevlogError(_('%s: incompatible revision flag %x') %
306 306 (self.indexfile, type))
307 307 return long(q >> 16)
308 308
309 309 def ngtype(self, q):
310 310 return int(q & 0xFFFF)
311 311
312 312 def offset_type(self, offset, type):
313 313 return long(long(offset) << 16 | type)
314 314
315 315 def loadindexmap(self):
316 316 """loads both the map and the index from the lazy parser"""
317 317 if isinstance(self.index, lazyindex):
318 318 p = self.index.p
319 319 p.load()
320 320
321 321 def inlinedata(self): return self.version & REVLOGNGINLINEDATA
322 322 def tip(self): return self.node(len(self.index) - 1)
323 323 def count(self): return len(self.index)
324 324 def node(self, rev):
325 325 return (rev < 0) and nullid or self.index[rev][-1]
326 326 def rev(self, node):
327 327 try:
328 328 return self.nodemap[node]
329 329 except KeyError:
330 330 raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node)))
331 331 def linkrev(self, node): return self.index[self.rev(node)][-4]
332 332 def parents(self, node):
333 333 if node == nullid: return (nullid, nullid)
334 334 r = self.rev(node)
335 335 d = self.index[r][-3:-1]
336 336 if self.version == 0:
337 337 return d
338 338 return [ self.node(x) for x in d ]
339 339 def start(self, rev):
340 340 if rev < 0:
341 341 return -1
342 342 if self.version != 0:
343 343 return self.ngoffset(self.index[rev][0])
344 344 return self.index[rev][0]
345 345 def end(self, rev): return self.start(rev) + self.length(rev)
346 346
347 347 def length(self, rev):
348 348 if rev < 0:
349 349 return 0
350 350 else:
351 351 return self.index[rev][1]
352 352 def base(self, rev): return (rev < 0) and rev or self.index[rev][-5]
353 353
354 354 def reachable(self, rev, stop=None):
355 355 reachable = {}
356 356 visit = [rev]
357 357 reachable[rev] = 1
358 358 if stop:
359 359 stopn = self.rev(stop)
360 360 else:
361 361 stopn = 0
362 362 while visit:
363 363 n = visit.pop(0)
364 364 if n == stop:
365 365 continue
366 366 if n == nullid:
367 367 continue
368 368 for p in self.parents(n):
369 369 if self.rev(p) < stopn:
370 370 continue
371 371 if p not in reachable:
372 372 reachable[p] = 1
373 373 visit.append(p)
374 374 return reachable
375 375
376 376 def nodesbetween(self, roots=None, heads=None):
377 377 """Return a tuple containing three elements. Elements 1 and 2 contain
378 378 a final list bases and heads after all the unreachable ones have been
379 379 pruned. Element 0 contains a topologically sorted list of all
380 380
381 381 nodes that satisfy these constraints:
382 382 1. All nodes must be descended from a node in roots (the nodes on
383 383 roots are considered descended from themselves).
384 384 2. All nodes must also be ancestors of a node in heads (the nodes in
385 385 heads are considered to be their own ancestors).
386 386
387 387 If roots is unspecified, nullid is assumed as the only root.
388 388 If heads is unspecified, it is taken to be the output of the
389 389 heads method (i.e. a list of all nodes in the repository that
390 390 have no children)."""
391 391 nonodes = ([], [], [])
392 392 if roots is not None:
393 393 roots = list(roots)
394 394 if not roots:
395 395 return nonodes
396 396 lowestrev = min([self.rev(n) for n in roots])
397 397 else:
398 398 roots = [nullid] # Everybody's a descendent of nullid
399 399 lowestrev = -1
400 400 if (lowestrev == -1) and (heads is None):
401 401 # We want _all_ the nodes!
402 402 return ([self.node(r) for r in xrange(0, self.count())],
403 403 [nullid], list(self.heads()))
404 404 if heads is None:
405 405 # All nodes are ancestors, so the latest ancestor is the last
406 406 # node.
407 407 highestrev = self.count() - 1
408 408 # Set ancestors to None to signal that every node is an ancestor.
409 409 ancestors = None
410 410 # Set heads to an empty dictionary for later discovery of heads
411 411 heads = {}
412 412 else:
413 413 heads = list(heads)
414 414 if not heads:
415 415 return nonodes
416 416 ancestors = {}
417 417 # Start at the top and keep marking parents until we're done.
418 418 nodestotag = heads[:]
419 419 # Turn heads into a dictionary so we can remove 'fake' heads.
420 420 # Also, later we will be using it to filter out the heads we can't
421 421 # find from roots.
422 422 heads = dict.fromkeys(heads, 0)
423 423 # Remember where the top was so we can use it as a limit later.
424 424 highestrev = max([self.rev(n) for n in nodestotag])
425 425 while nodestotag:
426 426 # grab a node to tag
427 427 n = nodestotag.pop()
428 428 # Never tag nullid
429 429 if n == nullid:
430 430 continue
431 431 # A node's revision number represents its place in a
432 432 # topologically sorted list of nodes.
433 433 r = self.rev(n)
434 434 if r >= lowestrev:
435 435 if n not in ancestors:
436 436 # If we are possibly a descendent of one of the roots
437 437 # and we haven't already been marked as an ancestor
438 438 ancestors[n] = 1 # Mark as ancestor
439 439 # Add non-nullid parents to list of nodes to tag.
440 440 nodestotag.extend([p for p in self.parents(n) if
441 441 p != nullid])
442 442 elif n in heads: # We've seen it before, is it a fake head?
443 443 # So it is, real heads should not be the ancestors of
444 444 # any other heads.
445 445 heads.pop(n)
446 446 if not ancestors:
447 447 return nonodes
448 448 # Now that we have our set of ancestors, we want to remove any
449 449 # roots that are not ancestors.
450 450
451 451 # If one of the roots was nullid, everything is included anyway.
452 452 if lowestrev > -1:
453 453 # But, since we weren't, let's recompute the lowest rev to not
454 454 # include roots that aren't ancestors.
455 455
456 456 # Filter out roots that aren't ancestors of heads
457 457 roots = [n for n in roots if n in ancestors]
458 458 # Recompute the lowest revision
459 459 if roots:
460 460 lowestrev = min([self.rev(n) for n in roots])
461 461 else:
462 462 # No more roots? Return empty list
463 463 return nonodes
464 464 else:
465 465 # We are descending from nullid, and don't need to care about
466 466 # any other roots.
467 467 lowestrev = -1
468 468 roots = [nullid]
469 469 # Transform our roots list into a 'set' (i.e. a dictionary where the
470 470 # values don't matter.
471 471 descendents = dict.fromkeys(roots, 1)
472 472 # Also, keep the original roots so we can filter out roots that aren't
473 473 # 'real' roots (i.e. are descended from other roots).
474 474 roots = descendents.copy()
475 475 # Our topologically sorted list of output nodes.
476 476 orderedout = []
477 477 # Don't start at nullid since we don't want nullid in our output list,
478 478 # and if nullid shows up in descedents, empty parents will look like
479 479 # they're descendents.
480 480 for r in xrange(max(lowestrev, 0), highestrev + 1):
481 481 n = self.node(r)
482 482 isdescendent = False
483 483 if lowestrev == -1: # Everybody is a descendent of nullid
484 484 isdescendent = True
485 485 elif n in descendents:
486 486 # n is already a descendent
487 487 isdescendent = True
488 488 # This check only needs to be done here because all the roots
489 489 # will start being marked is descendents before the loop.
490 490 if n in roots:
491 491 # If n was a root, check if it's a 'real' root.
492 492 p = tuple(self.parents(n))
493 493 # If any of its parents are descendents, it's not a root.
494 494 if (p[0] in descendents) or (p[1] in descendents):
495 495 roots.pop(n)
496 496 else:
497 497 p = tuple(self.parents(n))
498 498 # A node is a descendent if either of its parents are
499 499 # descendents. (We seeded the dependents list with the roots
500 500 # up there, remember?)
501 501 if (p[0] in descendents) or (p[1] in descendents):
502 502 descendents[n] = 1
503 503 isdescendent = True
504 504 if isdescendent and ((ancestors is None) or (n in ancestors)):
505 505 # Only include nodes that are both descendents and ancestors.
506 506 orderedout.append(n)
507 507 if (ancestors is not None) and (n in heads):
508 508 # We're trying to figure out which heads are reachable
509 509 # from roots.
510 510 # Mark this head as having been reached
511 511 heads[n] = 1
512 512 elif ancestors is None:
513 513 # Otherwise, we're trying to discover the heads.
514 514 # Assume this is a head because if it isn't, the next step
515 515 # will eventually remove it.
516 516 heads[n] = 1
517 517 # But, obviously its parents aren't.
518 518 for p in self.parents(n):
519 519 heads.pop(p, None)
520 520 heads = [n for n in heads.iterkeys() if heads[n] != 0]
521 521 roots = roots.keys()
522 522 assert orderedout
523 523 assert roots
524 524 assert heads
525 525 return (orderedout, roots, heads)
526 526
527 527 def heads(self, start=None):
528 528 """return the list of all nodes that have no children
529 529
530 530 if start is specified, only heads that are descendants of
531 531 start will be returned
532 532
533 533 """
534 534 if start is None:
535 535 start = nullid
536 536 reachable = {start: 1}
537 537 heads = {start: 1}
538 538 startrev = self.rev(start)
539 539
540 540 for r in xrange(startrev + 1, self.count()):
541 541 n = self.node(r)
542 542 for pn in self.parents(n):
543 543 if pn in reachable:
544 544 reachable[n] = 1
545 545 heads[n] = 1
546 546 if pn in heads:
547 547 del heads[pn]
548 548 return heads.keys()
549 549
550 550 def children(self, node):
551 551 """find the children of a given node"""
552 552 c = []
553 553 p = self.rev(node)
554 554 for r in range(p + 1, self.count()):
555 555 n = self.node(r)
556 556 for pn in self.parents(n):
557 557 if pn == node:
558 558 c.append(n)
559 559 continue
560 560 elif pn == nullid:
561 561 continue
562 562 return c
563 563
564 564 def lookup(self, id):
565 565 """locate a node based on revision number or subset of hex nodeid"""
566 566 try:
567 567 rev = int(id)
568 568 if str(rev) != id: raise ValueError
569 569 if rev < 0: rev = self.count() + rev
570 570 if rev < 0 or rev >= self.count(): raise ValueError
571 571 return self.node(rev)
572 572 except (ValueError, OverflowError):
573 573 c = []
574 574 for n in self.nodemap:
575 575 if hex(n).startswith(id):
576 576 c.append(n)
577 577 if len(c) > 1: raise RevlogError(_("Ambiguous identifier"))
578 578 if len(c) < 1: raise RevlogError(_("No match found"))
579 579 return c[0]
580 580
581 581 return None
582 582
583 583 def diff(self, a, b):
584 584 """return a delta between two revisions"""
585 585 return mdiff.textdiff(a, b)
586 586
587 587 def patches(self, t, pl):
588 588 """apply a list of patches to a string"""
589 589 return mdiff.patches(t, pl)
590 590
591 591 def chunk(self, rev, df=None, cachelen=4096):
592 592 start, length = self.start(rev), self.length(rev)
593 593 inline = self.inlinedata()
594 594 if inline:
595 595 start += (rev + 1) * struct.calcsize(self.indexformat)
596 596 end = start + length
597 597 def loadcache(df):
598 598 cache_length = max(cachelen, length) # 4k
599 599 if not df:
600 600 if inline:
601 601 df = self.opener(self.indexfile)
602 602 else:
603 603 df = self.opener(self.datafile)
604 604 df.seek(start)
605 605 self.chunkcache = (start, df.read(cache_length))
606 606
607 607 if not self.chunkcache:
608 608 loadcache(df)
609 609
610 610 cache_start = self.chunkcache[0]
611 611 cache_end = cache_start + len(self.chunkcache[1])
612 612 if start >= cache_start and end <= cache_end:
613 613 # it is cached
614 614 offset = start - cache_start
615 615 else:
616 616 loadcache(df)
617 617 offset = 0
618 618
619 619 #def checkchunk():
620 620 # df = self.opener(self.datafile)
621 621 # df.seek(start)
622 622 # return df.read(length)
623 623 #assert s == checkchunk()
624 624 return decompress(self.chunkcache[1][offset:offset + length])
625 625
626 626 def delta(self, node):
627 627 """return or calculate a delta between a node and its predecessor"""
628 628 r = self.rev(node)
629 629 return self.revdiff(r - 1, r)
630 630
631 631 def revdiff(self, rev1, rev2):
632 632 """return or calculate a delta between two revisions"""
633 633 b1 = self.base(rev1)
634 634 b2 = self.base(rev2)
635 635 if b1 == b2 and rev1 + 1 == rev2:
636 636 return self.chunk(rev2)
637 637 else:
638 638 return self.diff(self.revision(self.node(rev1)),
639 639 self.revision(self.node(rev2)))
640 640
641 641 def revision(self, node):
642 642 """return an uncompressed revision of a given"""
643 643 if node == nullid: return ""
644 644 if self.cache and self.cache[0] == node: return self.cache[2]
645 645
646 646 # look up what we need to read
647 647 text = None
648 648 rev = self.rev(node)
649 649 base = self.base(rev)
650 650
651 651 if self.inlinedata():
652 652 # we probably have the whole chunk cached
653 653 df = None
654 654 else:
655 655 df = self.opener(self.datafile)
656 656
657 657 # do we have useful data cached?
658 658 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
659 659 base = self.cache[1]
660 660 text = self.cache[2]
661 661 else:
662 662 text = self.chunk(base, df=df)
663 663
664 664 bins = []
665 665 for r in xrange(base + 1, rev + 1):
666 666 bins.append(self.chunk(r, df=df))
667 667
668 668 text = self.patches(text, bins)
669 669
670 670 p1, p2 = self.parents(node)
671 671 if node != hash(text, p1, p2):
672 672 raise RevlogError(_("integrity check failed on %s:%d")
673 673 % (self.datafile, rev))
674 674
675 675 self.cache = (node, rev, text)
676 676 return text
677 677
678 def checkinlinesize(self, fp, tr):
678 def checkinlinesize(self, tr, fp=None):
679 679 if not self.inlinedata():
680 680 return
681 if not fp:
682 fp = self.opener(self.indexfile, 'r')
681 683 size = fp.tell()
682 684 if size < 131072:
683 685 return
684 686 tr.add(self.datafile, 0)
685 687 df = self.opener(self.datafile, 'w')
686 688 calc = struct.calcsize(self.indexformat)
687 689 for r in xrange(self.count()):
688 690 start = self.start(r) + (r + 1) * calc
689 691 length = self.length(r)
690 692 fp.seek(start)
691 693 d = fp.read(length)
692 694 df.write(d)
693 695 fp.close()
694 696 df.close()
695 697 fp = self.opener(self.indexfile, 'w', atomic=True)
696 698 self.version &= ~(REVLOGNGINLINEDATA)
697 699 if self.count():
698 700 x = self.index[0]
699 701 e = struct.pack(self.indexformat, *x)[4:]
700 702 l = struct.pack(versionformat, self.version)
701 703 fp.write(l)
702 704 fp.write(e)
703 705
704 706 for i in xrange(1, self.count()):
705 707 x = self.index[i]
706 708 e = struct.pack(self.indexformat, *x)
707 709 fp.write(e)
708 710
709 711 fp.close()
710 712 self.chunkcache = None
711 713
712 714 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
713 715 """add a revision to the log
714 716
715 717 text - the revision data to add
716 718 transaction - the transaction object used for rollback
717 719 link - the linkrev data to add
718 720 p1, p2 - the parent nodeids of the revision
719 721 d - an optional precomputed delta
720 722 """
721 723 if text is None: text = ""
722 724 if p1 is None: p1 = self.tip()
723 725 if p2 is None: p2 = nullid
724 726
725 727 node = hash(text, p1, p2)
726 728
727 729 if node in self.nodemap:
728 730 return node
729 731
730 732 n = self.count()
731 733 t = n - 1
732 734
733 735 if n:
734 736 base = self.base(t)
735 737 start = self.start(base)
736 738 end = self.end(t)
737 739 if not d:
738 740 prev = self.revision(self.tip())
739 741 d = self.diff(prev, str(text))
740 742 data = compress(d)
741 743 l = len(data[1]) + len(data[0])
742 744 dist = end - start + l
743 745
744 746 # full versions are inserted when the needed deltas
745 747 # become comparable to the uncompressed text
746 748 if not n or dist > len(text) * 2:
747 749 data = compress(text)
748 750 l = len(data[1]) + len(data[0])
749 751 base = n
750 752 else:
751 753 base = self.base(t)
752 754
753 755 offset = 0
754 756 if t >= 0:
755 757 offset = self.end(t)
756 758
757 759 if self.version == 0:
758 760 e = (offset, l, base, link, p1, p2, node)
759 761 else:
760 762 e = (self.offset_type(offset, 0), l, len(text),
761 763 base, link, self.rev(p1), self.rev(p2), node)
762 764
763 765 self.index.append(e)
764 766 self.nodemap[node] = n
765 767 entry = struct.pack(self.indexformat, *e)
766 768
767 769 if not self.inlinedata():
768 770 transaction.add(self.datafile, offset)
769 771 transaction.add(self.indexfile, n * len(entry))
770 772 f = self.opener(self.datafile, "a")
771 773 if data[0]:
772 774 f.write(data[0])
773 775 f.write(data[1])
774 776 f = self.opener(self.indexfile, "a")
775 777 else:
776 778 f = self.opener(self.indexfile, "a+")
777 779 transaction.add(self.indexfile, f.tell())
778 780
779 781 if len(self.index) == 1 and self.version != 0:
780 782 l = struct.pack(versionformat, self.version)
781 783 f.write(l)
782 784 entry = entry[4:]
783 785
784 786 f.write(entry)
785 787
786 788 if self.inlinedata():
787 789 f.write(data[0])
788 790 f.write(data[1])
789 self.checkinlinesize(f, transaction)
791 self.checkinlinesize(transaction, f)
790 792
791 793 self.cache = (node, n, text)
792 794 return node
793 795
794 796 def ancestor(self, a, b):
795 797 """calculate the least common ancestor of nodes a and b"""
796 798 # calculate the distance of every node from root
797 799 dist = {nullid: 0}
798 800 for i in xrange(self.count()):
799 801 n = self.node(i)
800 802 p1, p2 = self.parents(n)
801 803 dist[n] = max(dist[p1], dist[p2]) + 1
802 804
803 805 # traverse ancestors in order of decreasing distance from root
804 806 def ancestors(node):
805 807 # we store negative distances because heap returns smallest member
806 808 h = [(-dist[node], node)]
807 809 seen = {}
808 810 while h:
809 811 d, n = heapq.heappop(h)
810 812 if n not in seen:
811 813 seen[n] = 1
812 814 yield (-d, n)
813 815 for p in self.parents(n):
814 816 heapq.heappush(h, (-dist[p], p))
815 817
816 818 def generations(node):
817 819 sg, s = None, {}
818 820 for g,n in ancestors(node):
819 821 if g != sg:
820 822 if sg:
821 823 yield sg, s
822 824 sg, s = g, {n:1}
823 825 else:
824 826 s[n] = 1
825 827 yield sg, s
826 828
827 829 x = generations(a)
828 830 y = generations(b)
829 831 gx = x.next()
830 832 gy = y.next()
831 833
832 834 # increment each ancestor list until it is closer to root than
833 835 # the other, or they match
834 836 while 1:
835 837 #print "ancestor gen %s %s" % (gx[0], gy[0])
836 838 if gx[0] == gy[0]:
837 839 # find the intersection
838 840 i = [ n for n in gx[1] if n in gy[1] ]
839 841 if i:
840 842 return i[0]
841 843 else:
842 844 #print "next"
843 845 gy = y.next()
844 846 gx = x.next()
845 847 elif gx[0] < gy[0]:
846 848 #print "next y"
847 849 gy = y.next()
848 850 else:
849 851 #print "next x"
850 852 gx = x.next()
851 853
852 854 def group(self, nodelist, lookup, infocollect=None):
853 855 """calculate a delta group
854 856
855 857 Given a list of changeset revs, return a set of deltas and
856 858 metadata corresponding to nodes. the first delta is
857 859 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
858 860 have this parent as it has all history before these
859 861 changesets. parent is parent[0]
860 862 """
861 863 revs = [self.rev(n) for n in nodelist]
862 864
863 865 # if we don't have any revisions touched by these changesets, bail
864 866 if not revs:
865 867 yield changegroup.closechunk()
866 868 return
867 869
868 870 # add the parent of the first rev
869 871 p = self.parents(self.node(revs[0]))[0]
870 872 revs.insert(0, self.rev(p))
871 873
872 874 # build deltas
873 875 for d in xrange(0, len(revs) - 1):
874 876 a, b = revs[d], revs[d + 1]
875 877 nb = self.node(b)
876 878
877 879 if infocollect is not None:
878 880 infocollect(nb)
879 881
880 882 d = self.revdiff(a, b)
881 883 p = self.parents(nb)
882 884 meta = nb + p[0] + p[1] + lookup(nb)
883 885 yield changegroup.genchunk("%s%s" % (meta, d))
884 886
885 887 yield changegroup.closechunk()
886 888
887 889 def addgroup(self, revs, linkmapper, transaction, unique=0):
888 890 """
889 891 add a delta group
890 892
891 893 given a set of deltas, add them to the revision log. the
892 894 first delta is against its parent, which should be in our
893 895 log, the rest are against the previous delta.
894 896 """
895 897
896 898 #track the base of the current delta log
897 899 r = self.count()
898 900 t = r - 1
899 901 node = None
900 902
901 903 base = prev = -1
902 904 start = end = measure = 0
903 905 if r:
904 906 end = self.end(t)
905 907
906 908 ifh = self.opener(self.indexfile, "a+")
907 909 transaction.add(self.indexfile, ifh.tell())
908 910 if self.inlinedata():
909 911 dfh = None
910 912 else:
911 913 transaction.add(self.datafile, end)
912 914 dfh = self.opener(self.datafile, "a")
913 915
914 916 # loop through our set of deltas
915 917 chain = None
916 918 for chunk in revs:
917 919 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
918 920 link = linkmapper(cs)
919 921 if node in self.nodemap:
920 922 # this can happen if two branches make the same change
921 923 # if unique:
922 924 # raise RevlogError(_("already have %s") % hex(node[:4]))
923 925 chain = node
924 926 continue
925 927 delta = chunk[80:]
926 928
927 929 for p in (p1, p2):
928 930 if not p in self.nodemap:
929 931 raise RevlogError(_("unknown parent %s") % short(p1))
930 932
931 933 if not chain:
932 934 # retrieve the parent revision of the delta chain
933 935 chain = p1
934 936 if not chain in self.nodemap:
935 937 raise RevlogError(_("unknown base %s") % short(chain[:4]))
936 938
937 939 # full versions are inserted when the needed deltas become
938 940 # comparable to the uncompressed text or when the previous
939 941 # version is not the one we have a delta against. We use
940 942 # the size of the previous full rev as a proxy for the
941 943 # current size.
942 944
943 945 if chain == prev:
944 946 tempd = compress(delta)
945 947 cdelta = tempd[0] + tempd[1]
946 948
947 949 if chain != prev or (end - start + len(cdelta)) > measure * 2:
948 950 # flush our writes here so we can read it in revision
949 951 if dfh:
950 952 dfh.flush()
951 953 ifh.flush()
952 954 text = self.revision(chain)
953 955 text = self.patches(text, [delta])
954 956 chk = self.addrevision(text, transaction, link, p1, p2)
955 957 if chk != node:
956 958 raise RevlogError(_("consistency error adding group"))
957 959 measure = len(text)
958 960 else:
959 961 if self.version == 0:
960 962 e = (end, len(cdelta), base, link, p1, p2, node)
961 963 else:
962 964 e = (self.offset_type(end, 0), len(cdelta), -1, base,
963 965 link, self.rev(p1), self.rev(p2), node)
964 966 self.index.append(e)
965 967 self.nodemap[node] = r
966 968 if self.inlinedata():
967 969 ifh.write(struct.pack(self.indexformat, *e))
968 970 ifh.write(cdelta)
969 self.checkinlinesize(ifh, transaction)
971 self.checkinlinesize(transaction, ifh)
970 972 if not self.inlinedata():
971 973 dfh = self.opener(self.datafile, "a")
972 974 ifh = self.opener(self.indexfile, "a")
973 975 else:
974 976 if not dfh:
975 977 # addrevision switched from inline to conventional
976 978 # reopen the index
977 979 dfh = self.opener(self.datafile, "a")
978 980 ifh = self.opener(self.indexfile, "a")
979 981 dfh.write(cdelta)
980 982 ifh.write(struct.pack(self.indexformat, *e))
981 983
982 984 t, r, chain, prev = r, r + 1, node, node
983 985 base = self.base(t)
984 986 start = self.start(base)
985 987 end = self.end(t)
986 988
987 989 if node is None:
988 990 raise RevlogError(_("group to be added is empty"))
989 991 return node
990 992
991 993 def strip(self, rev, minlink):
992 994 if self.count() == 0 or rev >= self.count():
993 995 return
994 996
995 997 if isinstance(self.index, lazyindex):
996 998 self.loadindexmap()
997 999
998 1000 # When stripping away a revision, we need to make sure it
999 1001 # does not actually belong to an older changeset.
1000 1002 # The minlink parameter defines the oldest revision
1001 1003 # we're allowed to strip away.
1002 1004 while minlink > self.index[rev][-4]:
1003 1005 rev += 1
1004 1006 if rev >= self.count():
1005 1007 return
1006 1008
1007 1009 # first truncate the files on disk
1008 1010 end = self.start(rev)
1009 1011 if not self.inlinedata():
1010 1012 df = self.opener(self.datafile, "a")
1011 1013 df.truncate(end)
1012 1014 end = rev * struct.calcsize(self.indexformat)
1013 1015 else:
1014 1016 end += rev * struct.calcsize(self.indexformat)
1015 1017
1016 1018 indexf = self.opener(self.indexfile, "a")
1017 1019 indexf.truncate(end)
1018 1020
1019 1021 # then reset internal state in memory to forget those revisions
1020 1022 self.cache = None
1021 1023 self.chunkcache = None
1022 1024 for x in xrange(rev, self.count()):
1023 1025 del self.nodemap[self.node(x)]
1024 1026
1025 1027 del self.index[rev:]
1026 1028
1027 1029 def checksize(self):
1028 1030 expected = 0
1029 1031 if self.count():
1030 1032 expected = self.end(self.count() - 1)
1031 1033
1032 1034 try:
1033 1035 f = self.opener(self.datafile)
1034 1036 f.seek(0, 2)
1035 1037 actual = f.tell()
1036 1038 dd = actual - expected
1037 1039 except IOError, inst:
1038 1040 if inst.errno != errno.ENOENT:
1039 1041 raise
1040 1042 dd = 0
1041 1043
1042 1044 try:
1043 1045 f = self.opener(self.indexfile)
1044 1046 f.seek(0, 2)
1045 1047 actual = f.tell()
1046 1048 s = struct.calcsize(self.indexformat)
1047 1049 i = actual / s
1048 1050 di = actual - (i * s)
1049 1051 if self.inlinedata():
1050 1052 databytes = 0
1051 1053 for r in xrange(self.count()):
1052 1054 databytes += self.length(r)
1053 1055 dd = 0
1054 1056 di = actual - self.count() * s - databytes
1055 1057 except IOError, inst:
1056 1058 if inst.errno != errno.ENOENT:
1057 1059 raise
1058 1060 di = 0
1059 1061
1060 1062 return (dd, di)
1061 1063
1062 1064
General Comments 0
You need to be logged in to leave comments. Login now