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