##// END OF EJS Templates
hg verify: add some bin to hex conversions
Matt Mackall -
r1384:d729850d default
parent child Browse files
Show More
@@ -1,1445 +1,1446 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 newer(self, nodes):
704 704 m = {}
705 705 nl = []
706 706 pm = {}
707 707 cl = self.changelog
708 708 t = l = cl.count()
709 709
710 710 # find the lowest numbered node
711 711 for n in nodes:
712 712 l = min(l, cl.rev(n))
713 713 m[n] = 1
714 714
715 715 for i in xrange(l, t):
716 716 n = cl.node(i)
717 717 if n in m: # explicitly listed
718 718 pm[n] = 1
719 719 nl.append(n)
720 720 continue
721 721 for p in cl.parents(n):
722 722 if p in pm: # parent listed
723 723 pm[n] = 1
724 724 nl.append(n)
725 725 break
726 726
727 727 return nl
728 728
729 729 def findincoming(self, remote, base=None, heads=None):
730 730 m = self.changelog.nodemap
731 731 search = []
732 732 fetch = {}
733 733 seen = {}
734 734 seenbranch = {}
735 735 if base == None:
736 736 base = {}
737 737
738 738 # assume we're closer to the tip than the root
739 739 # and start by examining the heads
740 740 self.ui.status("searching for changes\n")
741 741
742 742 if not heads:
743 743 heads = remote.heads()
744 744
745 745 unknown = []
746 746 for h in heads:
747 747 if h not in m:
748 748 unknown.append(h)
749 749 else:
750 750 base[h] = 1
751 751
752 752 if not unknown:
753 753 return None
754 754
755 755 rep = {}
756 756 reqcnt = 0
757 757
758 758 # search through remote branches
759 759 # a 'branch' here is a linear segment of history, with four parts:
760 760 # head, root, first parent, second parent
761 761 # (a branch always has two parents (or none) by definition)
762 762 unknown = remote.branches(unknown)
763 763 while unknown:
764 764 r = []
765 765 while unknown:
766 766 n = unknown.pop(0)
767 767 if n[0] in seen:
768 768 continue
769 769
770 770 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
771 771 if n[0] == nullid:
772 772 break
773 773 if n in seenbranch:
774 774 self.ui.debug("branch already found\n")
775 775 continue
776 776 if n[1] and n[1] in m: # do we know the base?
777 777 self.ui.debug("found incomplete branch %s:%s\n"
778 778 % (short(n[0]), short(n[1])))
779 779 search.append(n) # schedule branch range for scanning
780 780 seenbranch[n] = 1
781 781 else:
782 782 if n[1] not in seen and n[1] not in fetch:
783 783 if n[2] in m and n[3] in m:
784 784 self.ui.debug("found new changeset %s\n" %
785 785 short(n[1]))
786 786 fetch[n[1]] = 1 # earliest unknown
787 787 base[n[2]] = 1 # latest known
788 788 continue
789 789
790 790 for a in n[2:4]:
791 791 if a not in rep:
792 792 r.append(a)
793 793 rep[a] = 1
794 794
795 795 seen[n[0]] = 1
796 796
797 797 if r:
798 798 reqcnt += 1
799 799 self.ui.debug("request %d: %s\n" %
800 800 (reqcnt, " ".join(map(short, r))))
801 801 for p in range(0, len(r), 10):
802 802 for b in remote.branches(r[p:p+10]):
803 803 self.ui.debug("received %s:%s\n" %
804 804 (short(b[0]), short(b[1])))
805 805 if b[0] in m:
806 806 self.ui.debug("found base node %s\n" % short(b[0]))
807 807 base[b[0]] = 1
808 808 elif b[0] not in seen:
809 809 unknown.append(b)
810 810
811 811 # do binary search on the branches we found
812 812 while search:
813 813 n = search.pop(0)
814 814 reqcnt += 1
815 815 l = remote.between([(n[0], n[1])])[0]
816 816 l.append(n[1])
817 817 p = n[0]
818 818 f = 1
819 819 for i in l:
820 820 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
821 821 if i in m:
822 822 if f <= 2:
823 823 self.ui.debug("found new branch changeset %s\n" %
824 824 short(p))
825 825 fetch[p] = 1
826 826 base[i] = 1
827 827 else:
828 828 self.ui.debug("narrowed branch search to %s:%s\n"
829 829 % (short(p), short(i)))
830 830 search.append((p, i))
831 831 break
832 832 p, f = i, f * 2
833 833
834 834 # sanity check our fetch list
835 835 for f in fetch.keys():
836 836 if f in m:
837 837 raise repo.RepoError("already have changeset " + short(f[:4]))
838 838
839 839 if base.keys() == [nullid]:
840 840 self.ui.warn("warning: pulling from an unrelated repository!\n")
841 841
842 842 self.ui.note("found new changesets starting at " +
843 843 " ".join([short(f) for f in fetch]) + "\n")
844 844
845 845 self.ui.debug("%d total queries\n" % reqcnt)
846 846
847 847 return fetch.keys()
848 848
849 849 def findoutgoing(self, remote, base=None, heads=None):
850 850 if base == None:
851 851 base = {}
852 852 self.findincoming(remote, base, heads)
853 853
854 854 self.ui.debug("common changesets up to "
855 855 + " ".join(map(short, base.keys())) + "\n")
856 856
857 857 remain = dict.fromkeys(self.changelog.nodemap)
858 858
859 859 # prune everything remote has from the tree
860 860 del remain[nullid]
861 861 remove = base.keys()
862 862 while remove:
863 863 n = remove.pop(0)
864 864 if n in remain:
865 865 del remain[n]
866 866 for p in self.changelog.parents(n):
867 867 remove.append(p)
868 868
869 869 # find every node whose parents have been pruned
870 870 subset = []
871 871 for n in remain:
872 872 p1, p2 = self.changelog.parents(n)
873 873 if p1 not in remain and p2 not in remain:
874 874 subset.append(n)
875 875
876 876 # this is the set of all roots we have to push
877 877 return subset
878 878
879 879 def pull(self, remote):
880 880 lock = self.lock()
881 881
882 882 # if we have an empty repo, fetch everything
883 883 if self.changelog.tip() == nullid:
884 884 self.ui.status("requesting all changes\n")
885 885 fetch = [nullid]
886 886 else:
887 887 fetch = self.findincoming(remote)
888 888
889 889 if not fetch:
890 890 self.ui.status("no changes found\n")
891 891 return 1
892 892
893 893 cg = remote.changegroup(fetch)
894 894 return self.addchangegroup(cg)
895 895
896 896 def push(self, remote, force=False):
897 897 lock = remote.lock()
898 898
899 899 base = {}
900 900 heads = remote.heads()
901 901 inc = self.findincoming(remote, base, heads)
902 902 if not force and inc:
903 903 self.ui.warn("abort: unsynced remote changes!\n")
904 904 self.ui.status("(did you forget to sync? use push -f to force)\n")
905 905 return 1
906 906
907 907 update = self.findoutgoing(remote, base)
908 908 if not update:
909 909 self.ui.status("no changes found\n")
910 910 return 1
911 911 elif not force:
912 912 if len(heads) < len(self.changelog.heads()):
913 913 self.ui.warn("abort: push creates new remote branches!\n")
914 914 self.ui.status("(did you forget to merge?" +
915 915 " use push -f to force)\n")
916 916 return 1
917 917
918 918 cg = self.changegroup(update)
919 919 return remote.addchangegroup(cg)
920 920
921 921 def changegroup(self, basenodes):
922 922 genread = util.chunkbuffer
923 923
924 924 def gengroup():
925 925 nodes = self.newer(basenodes)
926 926
927 927 # construct the link map
928 928 linkmap = {}
929 929 for n in nodes:
930 930 linkmap[self.changelog.rev(n)] = n
931 931
932 932 # construct a list of all changed files
933 933 changed = {}
934 934 for n in nodes:
935 935 c = self.changelog.read(n)
936 936 for f in c[3]:
937 937 changed[f] = 1
938 938 changed = changed.keys()
939 939 changed.sort()
940 940
941 941 # the changegroup is changesets + manifests + all file revs
942 942 revs = [ self.changelog.rev(n) for n in nodes ]
943 943
944 944 for y in self.changelog.group(linkmap): yield y
945 945 for y in self.manifest.group(linkmap): yield y
946 946 for f in changed:
947 947 yield struct.pack(">l", len(f) + 4) + f
948 948 g = self.file(f).group(linkmap)
949 949 for y in g:
950 950 yield y
951 951
952 952 yield struct.pack(">l", 0)
953 953
954 954 return genread(gengroup())
955 955
956 956 def addchangegroup(self, source):
957 957
958 958 def getchunk():
959 959 d = source.read(4)
960 960 if not d: return ""
961 961 l = struct.unpack(">l", d)[0]
962 962 if l <= 4: return ""
963 963 d = source.read(l - 4)
964 964 if len(d) < l - 4:
965 965 raise repo.RepoError("premature EOF reading chunk" +
966 966 " (got %d bytes, expected %d)"
967 967 % (len(d), l - 4))
968 968 return d
969 969
970 970 def getgroup():
971 971 while 1:
972 972 c = getchunk()
973 973 if not c: break
974 974 yield c
975 975
976 976 def csmap(x):
977 977 self.ui.debug("add changeset %s\n" % short(x))
978 978 return self.changelog.count()
979 979
980 980 def revmap(x):
981 981 return self.changelog.rev(x)
982 982
983 983 if not source: return
984 984 changesets = files = revisions = 0
985 985
986 986 tr = self.transaction()
987 987
988 988 oldheads = len(self.changelog.heads())
989 989
990 990 # pull off the changeset group
991 991 self.ui.status("adding changesets\n")
992 992 co = self.changelog.tip()
993 993 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
994 994 cnr, cor = map(self.changelog.rev, (cn, co))
995 995 if cn == nullid:
996 996 cnr = cor
997 997 changesets = cnr - cor
998 998
999 999 # pull off the manifest group
1000 1000 self.ui.status("adding manifests\n")
1001 1001 mm = self.manifest.tip()
1002 1002 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1003 1003
1004 1004 # process the files
1005 1005 self.ui.status("adding file changes\n")
1006 1006 while 1:
1007 1007 f = getchunk()
1008 1008 if not f: break
1009 1009 self.ui.debug("adding %s revisions\n" % f)
1010 1010 fl = self.file(f)
1011 1011 o = fl.count()
1012 1012 n = fl.addgroup(getgroup(), revmap, tr)
1013 1013 revisions += fl.count() - o
1014 1014 files += 1
1015 1015
1016 1016 newheads = len(self.changelog.heads())
1017 1017 heads = ""
1018 1018 if oldheads and newheads > oldheads:
1019 1019 heads = " (+%d heads)" % (newheads - oldheads)
1020 1020
1021 1021 self.ui.status(("added %d changesets" +
1022 1022 " with %d changes to %d files%s\n")
1023 1023 % (changesets, revisions, files, heads))
1024 1024
1025 1025 tr.close()
1026 1026
1027 1027 if changesets > 0:
1028 1028 if not self.hook("changegroup",
1029 1029 node=hex(self.changelog.node(cor+1))):
1030 1030 self.ui.warn("abort: changegroup hook returned failure!\n")
1031 1031 return 1
1032 1032
1033 1033 for i in range(cor + 1, cnr + 1):
1034 1034 self.hook("commit", node=hex(self.changelog.node(i)))
1035 1035
1036 1036 return
1037 1037
1038 1038 def update(self, node, allow=False, force=False, choose=None,
1039 1039 moddirstate=True):
1040 1040 pl = self.dirstate.parents()
1041 1041 if not force and pl[1] != nullid:
1042 1042 self.ui.warn("aborting: outstanding uncommitted merges\n")
1043 1043 return 1
1044 1044
1045 1045 p1, p2 = pl[0], node
1046 1046 pa = self.changelog.ancestor(p1, p2)
1047 1047 m1n = self.changelog.read(p1)[0]
1048 1048 m2n = self.changelog.read(p2)[0]
1049 1049 man = self.manifest.ancestor(m1n, m2n)
1050 1050 m1 = self.manifest.read(m1n)
1051 1051 mf1 = self.manifest.readflags(m1n)
1052 1052 m2 = self.manifest.read(m2n)
1053 1053 mf2 = self.manifest.readflags(m2n)
1054 1054 ma = self.manifest.read(man)
1055 1055 mfa = self.manifest.readflags(man)
1056 1056
1057 1057 (c, a, d, u) = self.changes()
1058 1058
1059 1059 # is this a jump, or a merge? i.e. is there a linear path
1060 1060 # from p1 to p2?
1061 1061 linear_path = (pa == p1 or pa == p2)
1062 1062
1063 1063 # resolve the manifest to determine which files
1064 1064 # we care about merging
1065 1065 self.ui.note("resolving manifests\n")
1066 1066 self.ui.debug(" force %s allow %s moddirstate %s linear %s\n" %
1067 1067 (force, allow, moddirstate, linear_path))
1068 1068 self.ui.debug(" ancestor %s local %s remote %s\n" %
1069 1069 (short(man), short(m1n), short(m2n)))
1070 1070
1071 1071 merge = {}
1072 1072 get = {}
1073 1073 remove = []
1074 1074
1075 1075 # construct a working dir manifest
1076 1076 mw = m1.copy()
1077 1077 mfw = mf1.copy()
1078 1078 umap = dict.fromkeys(u)
1079 1079
1080 1080 for f in a + c + u:
1081 1081 mw[f] = ""
1082 1082 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1083 1083
1084 1084 for f in d:
1085 1085 if f in mw: del mw[f]
1086 1086
1087 1087 # If we're jumping between revisions (as opposed to merging),
1088 1088 # and if neither the working directory nor the target rev has
1089 1089 # the file, then we need to remove it from the dirstate, to
1090 1090 # prevent the dirstate from listing the file when it is no
1091 1091 # longer in the manifest.
1092 1092 if moddirstate and linear_path and f not in m2:
1093 1093 self.dirstate.forget((f,))
1094 1094
1095 1095 # Compare manifests
1096 1096 for f, n in mw.iteritems():
1097 1097 if choose and not choose(f): continue
1098 1098 if f in m2:
1099 1099 s = 0
1100 1100
1101 1101 # is the wfile new since m1, and match m2?
1102 1102 if f not in m1:
1103 1103 t1 = self.wread(f)
1104 1104 t2 = self.file(f).read(m2[f])
1105 1105 if cmp(t1, t2) == 0:
1106 1106 n = m2[f]
1107 1107 del t1, t2
1108 1108
1109 1109 # are files different?
1110 1110 if n != m2[f]:
1111 1111 a = ma.get(f, nullid)
1112 1112 # are both different from the ancestor?
1113 1113 if n != a and m2[f] != a:
1114 1114 self.ui.debug(" %s versions differ, resolve\n" % f)
1115 1115 # merge executable bits
1116 1116 # "if we changed or they changed, change in merge"
1117 1117 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1118 1118 mode = ((a^b) | (a^c)) ^ a
1119 1119 merge[f] = (m1.get(f, nullid), m2[f], mode)
1120 1120 s = 1
1121 1121 # are we clobbering?
1122 1122 # is remote's version newer?
1123 1123 # or are we going back in time?
1124 1124 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1125 1125 self.ui.debug(" remote %s is newer, get\n" % f)
1126 1126 get[f] = m2[f]
1127 1127 s = 1
1128 1128 elif f in umap:
1129 1129 # this unknown file is the same as the checkout
1130 1130 get[f] = m2[f]
1131 1131
1132 1132 if not s and mfw[f] != mf2[f]:
1133 1133 if force:
1134 1134 self.ui.debug(" updating permissions for %s\n" % f)
1135 1135 util.set_exec(self.wjoin(f), mf2[f])
1136 1136 else:
1137 1137 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1138 1138 mode = ((a^b) | (a^c)) ^ a
1139 1139 if mode != b:
1140 1140 self.ui.debug(" updating permissions for %s\n" % f)
1141 1141 util.set_exec(self.wjoin(f), mode)
1142 1142 del m2[f]
1143 1143 elif f in ma:
1144 1144 if n != ma[f]:
1145 1145 r = "d"
1146 1146 if not force and (linear_path or allow):
1147 1147 r = self.ui.prompt(
1148 1148 (" local changed %s which remote deleted\n" % f) +
1149 1149 "(k)eep or (d)elete?", "[kd]", "k")
1150 1150 if r == "d":
1151 1151 remove.append(f)
1152 1152 else:
1153 1153 self.ui.debug("other deleted %s\n" % f)
1154 1154 remove.append(f) # other deleted it
1155 1155 else:
1156 1156 # file is created on branch or in working directory
1157 1157 if force and f not in umap:
1158 1158 self.ui.debug("remote deleted %s, clobbering\n" % f)
1159 1159 remove.append(f)
1160 1160 elif n == m1.get(f, nullid): # same as parent
1161 1161 if p2 == pa: # going backwards?
1162 1162 self.ui.debug("remote deleted %s\n" % f)
1163 1163 remove.append(f)
1164 1164 else:
1165 1165 self.ui.debug("local modified %s, keeping\n" % f)
1166 1166 else:
1167 1167 self.ui.debug("working dir created %s, keeping\n" % f)
1168 1168
1169 1169 for f, n in m2.iteritems():
1170 1170 if choose and not choose(f): continue
1171 1171 if f[0] == "/": continue
1172 1172 if f in ma and n != ma[f]:
1173 1173 r = "k"
1174 1174 if not force and (linear_path or allow):
1175 1175 r = self.ui.prompt(
1176 1176 ("remote changed %s which local deleted\n" % f) +
1177 1177 "(k)eep or (d)elete?", "[kd]", "k")
1178 1178 if r == "k": get[f] = n
1179 1179 elif f not in ma:
1180 1180 self.ui.debug("remote created %s\n" % f)
1181 1181 get[f] = n
1182 1182 else:
1183 1183 if force or p2 == pa: # going backwards?
1184 1184 self.ui.debug("local deleted %s, recreating\n" % f)
1185 1185 get[f] = n
1186 1186 else:
1187 1187 self.ui.debug("local deleted %s\n" % f)
1188 1188
1189 1189 del mw, m1, m2, ma
1190 1190
1191 1191 if force:
1192 1192 for f in merge:
1193 1193 get[f] = merge[f][1]
1194 1194 merge = {}
1195 1195
1196 1196 if linear_path or force:
1197 1197 # we don't need to do any magic, just jump to the new rev
1198 1198 branch_merge = False
1199 1199 p1, p2 = p2, nullid
1200 1200 else:
1201 1201 if not allow:
1202 1202 self.ui.status("this update spans a branch" +
1203 1203 " affecting the following files:\n")
1204 1204 fl = merge.keys() + get.keys()
1205 1205 fl.sort()
1206 1206 for f in fl:
1207 1207 cf = ""
1208 1208 if f in merge: cf = " (resolve)"
1209 1209 self.ui.status(" %s%s\n" % (f, cf))
1210 1210 self.ui.warn("aborting update spanning branches!\n")
1211 1211 self.ui.status("(use update -m to merge across branches" +
1212 1212 " or -C to lose changes)\n")
1213 1213 return 1
1214 1214 branch_merge = True
1215 1215
1216 1216 if moddirstate:
1217 1217 self.dirstate.setparents(p1, p2)
1218 1218
1219 1219 # get the files we don't need to change
1220 1220 files = get.keys()
1221 1221 files.sort()
1222 1222 for f in files:
1223 1223 if f[0] == "/": continue
1224 1224 self.ui.note("getting %s\n" % f)
1225 1225 t = self.file(f).read(get[f])
1226 1226 try:
1227 1227 self.wwrite(f, t)
1228 1228 except IOError, e:
1229 1229 if e.errno != errno.ENOENT:
1230 1230 raise
1231 1231 os.makedirs(os.path.dirname(self.wjoin(f)))
1232 1232 self.wwrite(f, t)
1233 1233 util.set_exec(self.wjoin(f), mf2[f])
1234 1234 if moddirstate:
1235 1235 if branch_merge:
1236 1236 self.dirstate.update([f], 'n', st_mtime=-1)
1237 1237 else:
1238 1238 self.dirstate.update([f], 'n')
1239 1239
1240 1240 # merge the tricky bits
1241 1241 files = merge.keys()
1242 1242 files.sort()
1243 1243 for f in files:
1244 1244 self.ui.status("merging %s\n" % f)
1245 1245 my, other, flag = merge[f]
1246 1246 self.merge3(f, my, other)
1247 1247 util.set_exec(self.wjoin(f), flag)
1248 1248 if moddirstate:
1249 1249 if branch_merge:
1250 1250 # We've done a branch merge, mark this file as merged
1251 1251 # so that we properly record the merger later
1252 1252 self.dirstate.update([f], 'm')
1253 1253 else:
1254 1254 # We've update-merged a locally modified file, so
1255 1255 # we set the dirstate to emulate a normal checkout
1256 1256 # of that file some time in the past. Thus our
1257 1257 # merge will appear as a normal local file
1258 1258 # modification.
1259 1259 f_len = len(self.file(f).read(other))
1260 1260 self.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
1261 1261
1262 1262 remove.sort()
1263 1263 for f in remove:
1264 1264 self.ui.note("removing %s\n" % f)
1265 1265 try:
1266 1266 os.unlink(self.wjoin(f))
1267 1267 except OSError, inst:
1268 1268 self.ui.warn("update failed to remove %s: %s!\n" % (f, inst))
1269 1269 # try removing directories that might now be empty
1270 1270 try: os.removedirs(os.path.dirname(self.wjoin(f)))
1271 1271 except: pass
1272 1272 if moddirstate:
1273 1273 if branch_merge:
1274 1274 self.dirstate.update(remove, 'r')
1275 1275 else:
1276 1276 self.dirstate.forget(remove)
1277 1277
1278 1278 def merge3(self, fn, my, other):
1279 1279 """perform a 3-way merge in the working directory"""
1280 1280
1281 1281 def temp(prefix, node):
1282 1282 pre = "%s~%s." % (os.path.basename(fn), prefix)
1283 1283 (fd, name) = tempfile.mkstemp("", pre)
1284 1284 f = os.fdopen(fd, "wb")
1285 1285 self.wwrite(fn, fl.read(node), f)
1286 1286 f.close()
1287 1287 return name
1288 1288
1289 1289 fl = self.file(fn)
1290 1290 base = fl.ancestor(my, other)
1291 1291 a = self.wjoin(fn)
1292 1292 b = temp("base", base)
1293 1293 c = temp("other", other)
1294 1294
1295 1295 self.ui.note("resolving %s\n" % fn)
1296 1296 self.ui.debug("file %s: my %s other %s ancestor %s\n" %
1297 1297 (fn, short(my), short(other), short(base)))
1298 1298
1299 1299 cmd = (os.environ.get("HGMERGE") or self.ui.config("ui", "merge")
1300 1300 or "hgmerge")
1301 1301 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1302 1302 if r:
1303 1303 self.ui.warn("merging %s failed!\n" % fn)
1304 1304
1305 1305 os.unlink(b)
1306 1306 os.unlink(c)
1307 1307
1308 1308 def verify(self):
1309 1309 filelinkrevs = {}
1310 1310 filenodes = {}
1311 1311 changesets = revisions = files = 0
1312 1312 errors = [0]
1313 1313 neededmanifests = {}
1314 1314
1315 1315 def err(msg):
1316 1316 self.ui.warn(msg + "\n")
1317 1317 errors[0] += 1
1318 1318
1319 1319 seen = {}
1320 1320 self.ui.status("checking changesets\n")
1321 1321 for i in range(self.changelog.count()):
1322 1322 changesets += 1
1323 1323 n = self.changelog.node(i)
1324 1324 l = self.changelog.linkrev(n)
1325 1325 if l != i:
1326 1326 err("incorrect link (%d) for changeset revision %d" % (l, i))
1327 1327 if n in seen:
1328 1328 err("duplicate changeset at revision %d" % i)
1329 1329 seen[n] = 1
1330 1330
1331 1331 for p in self.changelog.parents(n):
1332 1332 if p not in self.changelog.nodemap:
1333 1333 err("changeset %s has unknown parent %s" %
1334 1334 (short(n), short(p)))
1335 1335 try:
1336 1336 changes = self.changelog.read(n)
1337 1337 except Exception, inst:
1338 1338 err("unpacking changeset %s: %s" % (short(n), inst))
1339 1339
1340 1340 neededmanifests[changes[0]] = n
1341 1341
1342 1342 for f in changes[3]:
1343 1343 filelinkrevs.setdefault(f, []).append(i)
1344 1344
1345 1345 seen = {}
1346 1346 self.ui.status("checking manifests\n")
1347 1347 for i in range(self.manifest.count()):
1348 1348 n = self.manifest.node(i)
1349 1349 l = self.manifest.linkrev(n)
1350 1350
1351 1351 if l < 0 or l >= self.changelog.count():
1352 1352 err("bad manifest link (%d) at revision %d" % (l, i))
1353 1353
1354 1354 if n in neededmanifests:
1355 1355 del neededmanifests[n]
1356 1356
1357 1357 if n in seen:
1358 1358 err("duplicate manifest at revision %d" % i)
1359 1359
1360 1360 seen[n] = 1
1361 1361
1362 1362 for p in self.manifest.parents(n):
1363 1363 if p not in self.manifest.nodemap:
1364 1364 err("manifest %s has unknown parent %s" %
1365 1365 (short(n), short(p)))
1366 1366
1367 1367 try:
1368 1368 delta = mdiff.patchtext(self.manifest.delta(n))
1369 1369 except KeyboardInterrupt:
1370 1370 self.ui.warn("interrupted")
1371 1371 raise
1372 1372 except Exception, inst:
1373 1373 err("unpacking manifest %s: %s" % (short(n), inst))
1374 1374
1375 1375 ff = [ l.split('\0') for l in delta.splitlines() ]
1376 1376 for f, fn in ff:
1377 1377 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1378 1378
1379 1379 self.ui.status("crosschecking files in changesets and manifests\n")
1380 1380
1381 1381 for m,c in neededmanifests.items():
1382 err("Changeset %s refers to unknown manifest %s" % (m, c))
1382 err("Changeset %s refers to unknown manifest %s" %
1383 (short(m), short(c)))
1383 1384 del neededmanifests
1384 1385
1385 1386 for f in filenodes:
1386 1387 if f not in filelinkrevs:
1387 1388 err("file %s in manifest but not in changesets" % f)
1388 1389
1389 1390 for f in filelinkrevs:
1390 1391 if f not in filenodes:
1391 1392 err("file %s in changeset but not in manifest" % f)
1392 1393
1393 1394 self.ui.status("checking files\n")
1394 1395 ff = filenodes.keys()
1395 1396 ff.sort()
1396 1397 for f in ff:
1397 1398 if f == "/dev/null": continue
1398 1399 files += 1
1399 1400 fl = self.file(f)
1400 1401 nodes = { nullid: 1 }
1401 1402 seen = {}
1402 1403 for i in range(fl.count()):
1403 1404 revisions += 1
1404 1405 n = fl.node(i)
1405 1406
1406 1407 if n in seen:
1407 1408 err("%s: duplicate revision %d" % (f, i))
1408 1409 if n not in filenodes[f]:
1409 1410 err("%s: %d:%s not in manifests" % (f, i, short(n)))
1410 1411 else:
1411 1412 del filenodes[f][n]
1412 1413
1413 1414 flr = fl.linkrev(n)
1414 1415 if flr not in filelinkrevs[f]:
1415 1416 err("%s:%s points to unexpected changeset %d"
1416 1417 % (f, short(n), flr))
1417 1418 else:
1418 1419 filelinkrevs[f].remove(flr)
1419 1420
1420 1421 # verify contents
1421 1422 try:
1422 1423 t = fl.read(n)
1423 1424 except Exception, inst:
1424 1425 err("unpacking file %s %s: %s" % (f, short(n), inst))
1425 1426
1426 1427 # verify parents
1427 1428 (p1, p2) = fl.parents(n)
1428 1429 if p1 not in nodes:
1429 1430 err("file %s:%s unknown parent 1 %s" %
1430 1431 (f, short(n), short(p1)))
1431 1432 if p2 not in nodes:
1432 1433 err("file %s:%s unknown parent 2 %s" %
1433 1434 (f, short(n), short(p1)))
1434 1435 nodes[n] = 1
1435 1436
1436 1437 # cross-check
1437 1438 for node in filenodes[f]:
1438 1439 err("node %s in manifests not in %s" % (hex(node), f))
1439 1440
1440 1441 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1441 1442 (files, changesets, revisions))
1442 1443
1443 1444 if errors[0]:
1444 1445 self.ui.warn("%d integrity errors encountered!\n" % errors[0])
1445 1446 return 1
General Comments 0
You need to be logged in to leave comments. Login now