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