##// END OF EJS Templates
merge: Fix bug where we overwrote local when local was newer...
mpm@selenic.com -
r273:4f817438 default
parent child Browse files
Show More
@@ -1,1189 +1,1190 b''
1 1 # hg.py - repository classes 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 sys, struct, os
9 9 from revlog import *
10 10 from demandload import *
11 11 demandload(globals(), "re lock urllib urllib2 transaction time socket")
12 12 demandload(globals(), "tempfile byterange difflib")
13 13
14 14 class filelog(revlog):
15 15 def __init__(self, opener, path):
16 16 revlog.__init__(self, opener,
17 17 os.path.join("data", path + ".i"),
18 18 os.path.join("data", path + ".d"))
19 19
20 20 def read(self, node):
21 21 return self.revision(node)
22 22 def add(self, text, transaction, link, p1=None, p2=None):
23 23 return self.addrevision(text, transaction, link, p1, p2)
24 24
25 25 def annotate(self, node):
26 26
27 27 def decorate(text, rev):
28 28 return [(rev, l) for l in text.splitlines(1)]
29 29
30 30 def strip(annotation):
31 31 return [e[1] for e in annotation]
32 32
33 33 def pair(parent, child):
34 34 new = []
35 35 sm = difflib.SequenceMatcher(None, strip(parent), strip(child))
36 36 for o, m, n, s, t in sm.get_opcodes():
37 37 if o == 'equal':
38 38 new += parent[m:n]
39 39 else:
40 40 new += child[s:t]
41 41 return new
42 42
43 43 # find all ancestors
44 44 needed = {node:1}
45 45 visit = [node]
46 46 while visit:
47 47 n = visit.pop(0)
48 48 for p in self.parents(n):
49 49 if p not in needed:
50 50 needed[p] = 1
51 51 visit.append(p)
52 52 else:
53 53 # count how many times we'll use this
54 54 needed[p] += 1
55 55
56 56 # sort by revision which is a topological order
57 57 visit = needed.keys()
58 58 visit = [ (self.rev(n), n) for n in visit ]
59 59 visit.sort()
60 60 visit = [ p[1] for p in visit ]
61 61 hist = {}
62 62
63 63 for n in visit:
64 64 curr = decorate(self.read(n), self.linkrev(n))
65 65 for p in self.parents(n):
66 66 if p != nullid:
67 67 curr = pair(hist[p], curr)
68 68 # trim the history of unneeded revs
69 69 needed[p] -= 1
70 70 if not needed[p]:
71 71 del hist[p]
72 72 hist[n] = curr
73 73
74 74 return hist[n]
75 75
76 76 class manifest(revlog):
77 77 def __init__(self, opener):
78 78 self.mapcache = None
79 79 self.listcache = None
80 80 self.addlist = None
81 81 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
82 82
83 83 def read(self, node):
84 84 if self.mapcache and self.mapcache[0] == node:
85 85 return self.mapcache[1].copy()
86 86 text = self.revision(node)
87 87 map = {}
88 88 self.listcache = (text, text.splitlines(1))
89 89 for l in self.listcache[1]:
90 90 (f, n) = l.split('\0')
91 91 map[f] = bin(n[:40])
92 92 self.mapcache = (node, map)
93 93 return map
94 94
95 95 def diff(self, a, b):
96 96 # this is sneaky, as we're not actually using a and b
97 97 if self.listcache and self.addlist and self.listcache[0] == a:
98 98 d = mdiff.diff(self.listcache[1], self.addlist, 1)
99 99 if mdiff.patch(a, d) != b:
100 100 sys.stderr.write("*** sortdiff failed, falling back ***\n")
101 101 return mdiff.textdiff(a, b)
102 102 return d
103 103 else:
104 104 return mdiff.textdiff(a, b)
105 105
106 106 def add(self, map, transaction, link, p1=None, p2=None):
107 107 files = map.keys()
108 108 files.sort()
109 109
110 110 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
111 111 text = "".join(self.addlist)
112 112
113 113 n = self.addrevision(text, transaction, link, p1, p2)
114 114 self.mapcache = (n, map)
115 115 self.listcache = (text, self.addlist)
116 116 self.addlist = None
117 117
118 118 return n
119 119
120 120 class changelog(revlog):
121 121 def __init__(self, opener):
122 122 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
123 123
124 124 def extract(self, text):
125 125 if not text:
126 126 return (nullid, "", "0", [], "")
127 127 last = text.index("\n\n")
128 128 desc = text[last + 2:]
129 129 l = text[:last].splitlines()
130 130 manifest = bin(l[0])
131 131 user = l[1]
132 132 date = l[2]
133 133 files = l[3:]
134 134 return (manifest, user, date, files, desc)
135 135
136 136 def read(self, node):
137 137 return self.extract(self.revision(node))
138 138
139 139 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
140 140 user=None, date=None):
141 141 user = (user or
142 142 os.environ.get("HGUSER") or
143 143 os.environ.get("EMAIL") or
144 144 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
145 145 date = date or "%d %d" % (time.time(), time.timezone)
146 146 list.sort()
147 147 l = [hex(manifest), user, date] + list + ["", desc]
148 148 text = "\n".join(l)
149 149 return self.addrevision(text, transaction, self.count(), p1, p2)
150 150
151 151 class dirstate:
152 152 def __init__(self, opener, ui, root):
153 153 self.opener = opener
154 154 self.root = root
155 155 self.dirty = 0
156 156 self.ui = ui
157 157 self.map = None
158 158 self.pl = None
159 159
160 160 def __del__(self):
161 161 if self.dirty:
162 162 self.write()
163 163
164 164 def __getitem__(self, key):
165 165 try:
166 166 return self.map[key]
167 167 except TypeError:
168 168 self.read()
169 169 return self[key]
170 170
171 171 def __contains__(self, key):
172 172 if not self.map: self.read()
173 173 return key in self.map
174 174
175 175 def parents(self):
176 176 if not self.pl:
177 177 self.read()
178 178 return self.pl
179 179
180 180 def setparents(self, p1, p2 = nullid):
181 181 self.dirty = 1
182 182 self.pl = p1, p2
183 183
184 184 def state(self, key):
185 185 try:
186 186 return self[key][0]
187 187 except KeyError:
188 188 return "?"
189 189
190 190 def read(self):
191 191 if self.map is not None: return self.map
192 192
193 193 self.map = {}
194 194 self.pl = [nullid, nullid]
195 195 try:
196 196 st = self.opener("dirstate").read()
197 197 except: return
198 198
199 199 self.pl = [st[:20], st[20: 40]]
200 200
201 201 pos = 40
202 202 while pos < len(st):
203 203 e = struct.unpack(">cllll", st[pos:pos+17])
204 204 l = e[4]
205 205 pos += 17
206 206 f = st[pos:pos + l]
207 207 self.map[f] = e[:4]
208 208 pos += l
209 209
210 210 def update(self, files, state):
211 211 ''' current states:
212 212 n normal
213 213 m needs merging
214 214 r marked for removal
215 215 a marked for addition'''
216 216
217 217 if not files: return
218 218 self.read()
219 219 self.dirty = 1
220 220 for f in files:
221 221 if state == "r":
222 222 self.map[f] = ('r', 0, 0, 0)
223 223 else:
224 224 s = os.stat(os.path.join(self.root, f))
225 225 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
226 226
227 227 def forget(self, files):
228 228 if not files: return
229 229 self.read()
230 230 self.dirty = 1
231 231 for f in files:
232 232 try:
233 233 del self.map[f]
234 234 except KeyError:
235 235 self.ui.warn("not in dirstate: %s!\n" % f)
236 236 pass
237 237
238 238 def clear(self):
239 239 self.map = {}
240 240 self.dirty = 1
241 241
242 242 def write(self):
243 243 st = self.opener("dirstate", "w")
244 244 st.write("".join(self.pl))
245 245 for f, e in self.map.items():
246 246 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
247 247 st.write(e + f)
248 248 self.dirty = 0
249 249
250 250 def copy(self):
251 251 self.read()
252 252 return self.map.copy()
253 253
254 254 # used to avoid circular references so destructors work
255 255 def opener(base):
256 256 p = base
257 257 def o(path, mode="r"):
258 258 if p[:7] == "http://":
259 259 f = os.path.join(p, urllib.quote(path))
260 260 return httprangereader(f)
261 261
262 262 f = os.path.join(p, path)
263 263
264 264 if mode != "r":
265 265 try:
266 266 s = os.stat(f)
267 267 except OSError:
268 268 d = os.path.dirname(f)
269 269 if not os.path.isdir(d):
270 270 os.makedirs(d)
271 271 else:
272 272 if s.st_nlink > 1:
273 273 file(f + ".tmp", "w").write(file(f).read())
274 274 os.rename(f+".tmp", f)
275 275
276 276 return file(f, mode)
277 277
278 278 return o
279 279
280 280 class localrepository:
281 281 def __init__(self, ui, path=None, create=0):
282 282 self.remote = 0
283 283 if path and path[:7] == "http://":
284 284 self.remote = 1
285 285 self.path = path
286 286 else:
287 287 if not path:
288 288 p = os.getcwd()
289 289 while not os.path.isdir(os.path.join(p, ".hg")):
290 290 p = os.path.dirname(p)
291 291 if p == "/": raise "No repo found"
292 292 path = p
293 293 self.path = os.path.join(path, ".hg")
294 294
295 295 self.root = path
296 296 self.ui = ui
297 297
298 298 if create:
299 299 os.mkdir(self.path)
300 300 os.mkdir(self.join("data"))
301 301
302 302 self.opener = opener(self.path)
303 303 self.manifest = manifest(self.opener)
304 304 self.changelog = changelog(self.opener)
305 305 self.ignorelist = None
306 306 self.tags = None
307 307
308 308 if not self.remote:
309 309 self.dirstate = dirstate(self.opener, ui, self.root)
310 310
311 311 def ignore(self, f):
312 312 if self.ignorelist is None:
313 313 self.ignorelist = []
314 314 try:
315 315 l = open(os.path.join(self.root, ".hgignore"))
316 316 for pat in l:
317 317 if pat != "\n":
318 318 self.ignorelist.append(re.compile(pat[:-1]))
319 319 except IOError: pass
320 320 for pat in self.ignorelist:
321 321 if pat.search(f): return True
322 322 return False
323 323
324 324 def lookup(self, key):
325 325 if self.tags is None:
326 326 self.tags = {}
327 327 try:
328 328 # read each head of the tags file, ending with the tip
329 329 # and add each tag found to the map, with "newer" ones
330 330 # taking precedence
331 331 fl = self.file(".hgtags")
332 332 h = fl.heads()
333 333 h.reverse()
334 334 for r in h:
335 335 for l in fl.revision(r).splitlines():
336 336 if l:
337 337 n, k = l.split(" ")
338 338 self.tags[k] = bin(n)
339 339 except KeyError: pass
340 340 try:
341 341 return self.tags[key]
342 342 except KeyError:
343 343 return self.changelog.lookup(key)
344 344
345 345 def join(self, f):
346 346 return os.path.join(self.path, f)
347 347
348 348 def wjoin(self, f):
349 349 return os.path.join(self.root, f)
350 350
351 351 def file(self, f):
352 352 if f[0] == '/': f = f[1:]
353 353 return filelog(self.opener, f)
354 354
355 355 def transaction(self):
356 356 # save dirstate for undo
357 357 try:
358 358 ds = self.opener("dirstate").read()
359 359 except IOError:
360 360 ds = ""
361 361 self.opener("undo.dirstate", "w").write(ds)
362 362
363 363 return transaction.transaction(self.opener, self.join("journal"),
364 364 self.join("undo"))
365 365
366 366 def recover(self):
367 367 lock = self.lock()
368 368 if os.path.exists(self.join("recover")):
369 369 self.ui.status("attempting to rollback interrupted transaction\n")
370 370 return transaction.rollback(self.opener, self.join("recover"))
371 371 else:
372 372 self.ui.warn("no interrupted transaction available\n")
373 373
374 374 def undo(self):
375 375 lock = self.lock()
376 376 if os.path.exists(self.join("undo")):
377 377 self.ui.status("attempting to rollback last transaction\n")
378 378 transaction.rollback(self.opener, self.join("undo"))
379 379 self.dirstate = None
380 380 os.rename(self.join("undo.dirstate"), self.join("dirstate"))
381 381 self.dirstate = dirstate(self.opener, self.ui, self.root)
382 382 else:
383 383 self.ui.warn("no undo information available\n")
384 384
385 385 def lock(self, wait = 1):
386 386 try:
387 387 return lock.lock(self.join("lock"), 0)
388 388 except lock.LockHeld, inst:
389 389 if wait:
390 390 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
391 391 return lock.lock(self.join("lock"), wait)
392 392 raise inst
393 393
394 394 def rawcommit(self, files, text, user, date, p1=None, p2=None):
395 395 p1 = p1 or self.dirstate.parents()[0] or nullid
396 396 p2 = p2 or self.dirstate.parents()[1] or nullid
397 397 pchange = self.changelog.read(p1)
398 398 pmmap = self.manifest.read(pchange[0])
399 399 tr = self.transaction()
400 400 mmap = {}
401 401 linkrev = self.changelog.count()
402 402 for f in files:
403 403 try:
404 404 t = file(f).read()
405 405 except IOError:
406 406 self.ui.warn("Read file %s error, skipped\n" % f)
407 407 continue
408 408 r = self.file(f)
409 409 # FIXME - need to find both parents properly
410 410 prev = pmmap.get(f, nullid)
411 411 mmap[f] = r.add(t, tr, linkrev, prev)
412 412
413 413 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
414 414 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
415 415 tr.close()
416 416 self.dirstate.setparents(p1, p2)
417 417 self.dirstate.clear()
418 418 self.dirstate.update(mmap.keys(), "n")
419 419
420 420 def commit(self, files = None, text = ""):
421 421 commit = []
422 422 remove = []
423 423 if files:
424 424 for f in files:
425 425 s = self.dirstate.state(f)
426 426 if s in 'nmai':
427 427 commit.append(f)
428 428 elif s == 'r':
429 429 remove.append(f)
430 430 else:
431 431 self.ui.warn("%s not tracked!\n" % f)
432 432 else:
433 433 (c, a, d, u) = self.diffdir(self.root)
434 434 commit = c + a
435 435 remove = d
436 436
437 437 if not commit and not remove:
438 438 self.ui.status("nothing changed\n")
439 439 return
440 440
441 441 p1, p2 = self.dirstate.parents()
442 442 c1 = self.changelog.read(p1)
443 443 c2 = self.changelog.read(p2)
444 444 m1 = self.manifest.read(c1[0])
445 445 m2 = self.manifest.read(c2[0])
446 446 lock = self.lock()
447 447 tr = self.transaction()
448 448
449 449 # check in files
450 450 new = {}
451 451 linkrev = self.changelog.count()
452 452 commit.sort()
453 453 for f in commit:
454 454 self.ui.note(f + "\n")
455 455 try:
456 456 t = file(self.wjoin(f)).read()
457 457 except IOError:
458 458 self.warn("trouble committing %s!\n" % f)
459 459 raise
460 460
461 461 r = self.file(f)
462 462 fp1 = m1.get(f, nullid)
463 463 fp2 = m2.get(f, nullid)
464 464 new[f] = r.add(t, tr, linkrev, fp1, fp2)
465 465
466 466 # update manifest
467 467 m1.update(new)
468 468 for f in remove: del m1[f]
469 469 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
470 470
471 471 # add changeset
472 472 new = new.keys()
473 473 new.sort()
474 474
475 475 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
476 476 edittext += "".join(["HG: changed %s\n" % f for f in new])
477 477 edittext += "".join(["HG: removed %s\n" % f for f in remove])
478 478 edittext = self.ui.edit(edittext)
479 479
480 480 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
481 481 tr.close()
482 482
483 483 self.dirstate.setparents(n)
484 484 self.dirstate.update(new, "n")
485 485 self.dirstate.forget(remove)
486 486
487 487 def diffdir(self, path, changeset = None):
488 488 changed = []
489 489 added = []
490 490 unknown = []
491 491 mf = {}
492 492
493 493 if changeset:
494 494 change = self.changelog.read(changeset)
495 495 mf = self.manifest.read(change[0])
496 496 dc = dict.fromkeys(mf)
497 497 else:
498 498 changeset = self.dirstate.parents()[0]
499 499 change = self.changelog.read(changeset)
500 500 mf = self.manifest.read(change[0])
501 501 dc = self.dirstate.copy()
502 502
503 503 def fcmp(fn):
504 504 t1 = file(self.wjoin(fn)).read()
505 505 t2 = self.file(fn).revision(mf[fn])
506 506 return cmp(t1, t2)
507 507
508 508 for dir, subdirs, files in os.walk(self.root):
509 509 d = dir[len(self.root)+1:]
510 510 if ".hg" in subdirs: subdirs.remove(".hg")
511 511
512 512 for f in files:
513 513 fn = os.path.join(d, f)
514 514 try: s = os.stat(os.path.join(self.root, fn))
515 515 except: continue
516 516 if fn in dc:
517 517 c = dc[fn]
518 518 del dc[fn]
519 519 if not c:
520 520 if fcmp(fn):
521 521 changed.append(fn)
522 522 elif c[0] == 'm':
523 523 changed.append(fn)
524 524 elif c[0] == 'a':
525 525 added.append(fn)
526 526 elif c[0] == 'r':
527 527 unknown.append(fn)
528 528 elif c[2] != s.st_size:
529 529 changed.append(fn)
530 530 elif c[1] != s.st_mode or c[3] != s.st_mtime:
531 531 if fcmp(fn):
532 532 changed.append(fn)
533 533 else:
534 534 if self.ignore(fn): continue
535 535 unknown.append(fn)
536 536
537 537 deleted = dc.keys()
538 538 deleted.sort()
539 539
540 540 return (changed, added, deleted, unknown)
541 541
542 542 def diffrevs(self, node1, node2):
543 543 changed, added = [], []
544 544
545 545 change = self.changelog.read(node1)
546 546 mf1 = self.manifest.read(change[0])
547 547 change = self.changelog.read(node2)
548 548 mf2 = self.manifest.read(change[0])
549 549
550 550 for fn in mf2:
551 551 if mf1.has_key(fn):
552 552 if mf1[fn] != mf2[fn]:
553 553 changed.append(fn)
554 554 del mf1[fn]
555 555 else:
556 556 added.append(fn)
557 557
558 558 deleted = mf1.keys()
559 559 deleted.sort()
560 560
561 561 return (changed, added, deleted)
562 562
563 563 def add(self, list):
564 564 for f in list:
565 565 p = self.wjoin(f)
566 566 if not os.path.isfile(p):
567 567 self.ui.warn("%s does not exist!\n" % f)
568 568 elif self.dirstate.state(f) == 'n':
569 569 self.ui.warn("%s already tracked!\n" % f)
570 570 else:
571 571 self.dirstate.update([f], "a")
572 572
573 573 def forget(self, list):
574 574 for f in list:
575 575 if self.dirstate.state(f) not in 'ai':
576 576 self.ui.warn("%s not added!\n" % f)
577 577 else:
578 578 self.dirstate.forget([f])
579 579
580 580 def remove(self, list):
581 581 for f in list:
582 582 p = self.wjoin(f)
583 583 if os.path.isfile(p):
584 584 self.ui.warn("%s still exists!\n" % f)
585 585 elif f not in self.dirstate:
586 586 self.ui.warn("%s not tracked!\n" % f)
587 587 else:
588 588 self.dirstate.update([f], "r")
589 589
590 590 def heads(self):
591 591 return self.changelog.heads()
592 592
593 593 def branches(self, nodes):
594 594 if not nodes: nodes = [self.changelog.tip()]
595 595 b = []
596 596 for n in nodes:
597 597 t = n
598 598 while n:
599 599 p = self.changelog.parents(n)
600 600 if p[1] != nullid or p[0] == nullid:
601 601 b.append((t, n, p[0], p[1]))
602 602 break
603 603 n = p[0]
604 604 return b
605 605
606 606 def between(self, pairs):
607 607 r = []
608 608
609 609 for top, bottom in pairs:
610 610 n, l, i = top, [], 0
611 611 f = 1
612 612
613 613 while n != bottom:
614 614 p = self.changelog.parents(n)[0]
615 615 if i == f:
616 616 l.append(n)
617 617 f = f * 2
618 618 n = p
619 619 i += 1
620 620
621 621 r.append(l)
622 622
623 623 return r
624 624
625 625 def newer(self, nodes):
626 626 m = {}
627 627 nl = []
628 628 pm = {}
629 629 cl = self.changelog
630 630 t = l = cl.count()
631 631
632 632 # find the lowest numbered node
633 633 for n in nodes:
634 634 l = min(l, cl.rev(n))
635 635 m[n] = 1
636 636
637 637 for i in xrange(l, t):
638 638 n = cl.node(i)
639 639 if n in m: # explicitly listed
640 640 pm[n] = 1
641 641 nl.append(n)
642 642 continue
643 643 for p in cl.parents(n):
644 644 if p in pm: # parent listed
645 645 pm[n] = 1
646 646 nl.append(n)
647 647 break
648 648
649 649 return nl
650 650
651 651 def getchangegroup(self, remote):
652 652 m = self.changelog.nodemap
653 653 search = []
654 654 fetch = []
655 655 seen = {}
656 656 seenbranch = {}
657 657
658 658 # if we have an empty repo, fetch everything
659 659 if self.changelog.tip() == nullid:
660 660 self.ui.status("requesting all changes\n")
661 661 return remote.changegroup([nullid])
662 662
663 663 # otherwise, assume we're closer to the tip than the root
664 664 self.ui.status("searching for changes\n")
665 665 heads = remote.heads()
666 666 unknown = []
667 667 for h in heads:
668 668 if h not in m:
669 669 unknown.append(h)
670 670
671 671 if not unknown:
672 672 self.ui.status("nothing to do!\n")
673 673 return None
674 674
675 675 unknown = remote.branches(unknown)
676 676 while unknown:
677 677 n = unknown.pop(0)
678 678 seen[n[0]] = 1
679 679
680 680 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
681 681 if n == nullid: break
682 682 if n in seenbranch:
683 683 self.ui.debug("branch already found\n")
684 684 continue
685 685 if n[1] and n[1] in m: # do we know the base?
686 686 self.ui.debug("found incomplete branch %s:%s\n"
687 687 % (short(n[0]), short(n[1])))
688 688 search.append(n) # schedule branch range for scanning
689 689 seenbranch[n] = 1
690 690 else:
691 691 if n[2] in m and n[3] in m:
692 692 if n[1] not in fetch:
693 693 self.ui.debug("found new changeset %s\n" %
694 694 short(n[1]))
695 695 fetch.append(n[1]) # earliest unknown
696 696 continue
697 697
698 698 r = []
699 699 for a in n[2:4]:
700 700 if a not in seen: r.append(a)
701 701
702 702 if r:
703 703 self.ui.debug("requesting %s\n" %
704 704 " ".join(map(short, r)))
705 705 for b in remote.branches(r):
706 706 self.ui.debug("received %s:%s\n" %
707 707 (short(b[0]), short(b[1])))
708 708 if b[0] not in m and b[0] not in seen:
709 709 unknown.append(b)
710 710
711 711 while search:
712 712 n = search.pop(0)
713 713 l = remote.between([(n[0], n[1])])[0]
714 714 p = n[0]
715 715 f = 1
716 716 for i in l + [n[1]]:
717 717 if i in m:
718 718 if f <= 2:
719 719 self.ui.debug("found new branch changeset %s\n" %
720 720 short(p))
721 721 fetch.append(p)
722 722 else:
723 723 self.ui.debug("narrowed branch search to %s:%s\n"
724 724 % (short(p), short(i)))
725 725 search.append((p, i))
726 726 break
727 727 p, f = i, f * 2
728 728
729 729 for f in fetch:
730 730 if f in m:
731 731 raise "already have", short(f[:4])
732 732
733 733 self.ui.note("adding new changesets starting at " +
734 734 " ".join([short(f) for f in fetch]) + "\n")
735 735
736 736 return remote.changegroup(fetch)
737 737
738 738 def changegroup(self, basenodes):
739 739 nodes = self.newer(basenodes)
740 740
741 741 # construct the link map
742 742 linkmap = {}
743 743 for n in nodes:
744 744 linkmap[self.changelog.rev(n)] = n
745 745
746 746 # construct a list of all changed files
747 747 changed = {}
748 748 for n in nodes:
749 749 c = self.changelog.read(n)
750 750 for f in c[3]:
751 751 changed[f] = 1
752 752 changed = changed.keys()
753 753 changed.sort()
754 754
755 755 # the changegroup is changesets + manifests + all file revs
756 756 revs = [ self.changelog.rev(n) for n in nodes ]
757 757
758 758 for y in self.changelog.group(linkmap): yield y
759 759 for y in self.manifest.group(linkmap): yield y
760 760 for f in changed:
761 761 yield struct.pack(">l", len(f) + 4) + f
762 762 g = self.file(f).group(linkmap)
763 763 for y in g:
764 764 yield y
765 765
766 766 def addchangegroup(self, generator):
767 767
768 768 class genread:
769 769 def __init__(self, generator):
770 770 self.g = generator
771 771 self.buf = ""
772 772 def read(self, l):
773 773 while l > len(self.buf):
774 774 try:
775 775 self.buf += self.g.next()
776 776 except StopIteration:
777 777 break
778 778 d, self.buf = self.buf[:l], self.buf[l:]
779 779 return d
780 780
781 781 def getchunk():
782 782 d = source.read(4)
783 783 if not d: return ""
784 784 l = struct.unpack(">l", d)[0]
785 785 if l <= 4: return ""
786 786 return source.read(l - 4)
787 787
788 788 def getgroup():
789 789 while 1:
790 790 c = getchunk()
791 791 if not c: break
792 792 yield c
793 793
794 794 def csmap(x):
795 795 self.ui.debug("add changeset %s\n" % short(x))
796 796 return self.changelog.count()
797 797
798 798 def revmap(x):
799 799 return self.changelog.rev(x)
800 800
801 801 if not generator: return
802 802 changesets = files = revisions = 0
803 803
804 804 source = genread(generator)
805 805 lock = self.lock()
806 806 tr = self.transaction()
807 807
808 808 # pull off the changeset group
809 809 self.ui.status("adding changesets\n")
810 810 co = self.changelog.tip()
811 811 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
812 812 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
813 813
814 814 # pull off the manifest group
815 815 self.ui.status("adding manifests\n")
816 816 mm = self.manifest.tip()
817 817 mo = self.manifest.addgroup(getgroup(), revmap, tr)
818 818
819 819 # process the files
820 820 self.ui.status("adding file revisions\n")
821 821 while 1:
822 822 f = getchunk()
823 823 if not f: break
824 824 self.ui.debug("adding %s revisions\n" % f)
825 825 fl = self.file(f)
826 826 o = fl.tip()
827 827 n = fl.addgroup(getgroup(), revmap, tr)
828 828 revisions += fl.rev(n) - fl.rev(o)
829 829 files += 1
830 830
831 831 self.ui.status(("modified %d files, added %d changesets" +
832 832 " and %d new revisions\n")
833 833 % (files, changesets, revisions))
834 834
835 835 tr.close()
836 836 return
837 837
838 838 def update(self, node):
839 839 pl = self.dirstate.parents()
840 840 if pl[1] != nullid:
841 841 self.ui.warn("aborting: outstanding uncommitted merges\n")
842 842 return
843 843
844 844 p1, p2 = pl[0], node
845 845 m1n = self.changelog.read(p1)[0]
846 846 m2n = self.changelog.read(p2)[0]
847 847 man = self.manifest.ancestor(m1n, m2n)
848 848 m1 = self.manifest.read(m1n)
849 849 m2 = self.manifest.read(m2n)
850 850 ma = self.manifest.read(man)
851 851
852 852 (c, a, d, u) = self.diffdir(self.root)
853 853
854 854 # resolve the manifest to determine which files
855 855 # we care about merging
856 856 self.ui.note("resolving manifests\n")
857 857 self.ui.debug(" ancestor %s local %s remote %s\n" %
858 858 (short(man), short(m1n), short(m2n)))
859 859
860 860 merge = {}
861 861 get = {}
862 862 remove = []
863 863
864 864 # construct a working dir manifest
865 865 mw = m1.copy()
866 866 for f in a + c + u:
867 867 mw[f] = ""
868 868 for f in d:
869 869 if f in mw: del mw[f]
870 870
871 871 for f, n in mw.iteritems():
872 872 if f in m2:
873 873 if n != m2[f]:
874 874 a = ma.get(f, nullid)
875 875 if n != a and m2[f] != a:
876 self.ui.debug(" %s versions differ, do resolve\n" % f)
876 self.ui.debug(" %s versions differ, resolve\n" % f)
877 877 merge[f] = (m1.get(f, nullid), m2[f])
878 else:
878 elif m2[f] != a:
879 self.ui.debug(" remote %s is newer, get\n" % f)
879 880 get[f] = m2[f]
880 881 del m2[f]
881 882 elif f in ma:
882 883 if n != ma[f]:
883 884 r = self.ui.prompt(
884 885 (" local changed %s which remote deleted\n" % f) +
885 886 "(k)eep or (d)elete?", "[kd]", "k")
886 887 if r == "d":
887 888 remove.append(f)
888 889 else:
889 890 self.ui.debug("other deleted %s\n" % f)
890 891 remove.append(f) # other deleted it
891 892 else:
892 893 if n == m1.get(f, nullid): # same as parent
893 894 self.ui.debug("remote deleted %s\n" % f)
894 895 remove.append(f)
895 896 else:
896 897 self.ui.debug("working dir created %s, keeping\n" % f)
897 898
898 899 for f, n in m2.iteritems():
899 900 if f[0] == "/": continue
900 901 if f in ma and n != ma[f]:
901 902 r = self.ui.prompt(
902 903 ("remote changed %s which local deleted\n" % f) +
903 904 "(k)eep or (d)elete?", "[kd]", "k")
904 905 if r == "d": remove.append(f)
905 906 else:
906 907 self.ui.debug("remote created %s\n" % f)
907 908 get[f] = n
908 909
909 910 del mw, m1, m2, ma
910 911
911 912 if not merge:
912 913 # we don't need to do any magic, just jump to the new rev
913 914 mode = 'n'
914 915 p1, p2 = p2, nullid
915 916 else:
916 917 # we have to remember what files we needed to get/change
917 918 # because any file that's different from either one of its
918 919 # parents must be in the changeset
919 920 mode = 'm'
920 921
921 922 self.dirstate.setparents(p1, p2)
922 923
923 924 # get the files we don't need to change
924 925 files = get.keys()
925 926 files.sort()
926 927 for f in files:
927 928 if f[0] == "/": continue
928 self.ui.note(f, "\n")
929 self.ui.note("getting %s\n" % f)
929 930 t = self.file(f).revision(get[f])
930 931 try:
931 932 file(self.wjoin(f), "w").write(t)
932 933 except IOError:
933 934 os.makedirs(os.path.dirname(f))
934 935 file(self.wjoin(f), "w").write(t)
935 936 self.dirstate.update([f], mode)
936 937
937 938 # merge the tricky bits
938 939 files = merge.keys()
939 940 files.sort()
940 941 for f in files:
941 942 self.ui.status("merging %s\n" % f)
942 943 m, o = merge[f]
943 944 self.merge3(f, m, o)
944 945 self.dirstate.update([f], 'm')
945 946
946 947 for f in remove:
947 948 self.ui.note("removing %s\n" % f)
948 949 os.unlink(f)
949 950 if mode == 'n':
950 951 self.dirstate.forget(remove)
951 952 else:
952 953 self.dirstate.update(remove, 'r')
953 954
954 955 def merge3(self, fn, my, other):
955 956 """perform a 3-way merge in the working directory"""
956 957
957 958 def temp(prefix, node):
958 959 pre = "%s~%s." % (os.path.basename(fn), prefix)
959 960 (fd, name) = tempfile.mkstemp("", pre)
960 961 f = os.fdopen(fd, "w")
961 962 f.write(fl.revision(node))
962 963 f.close()
963 964 return name
964 965
965 966 fl = self.file(fn)
966 967 base = fl.ancestor(my, other)
967 968 a = self.wjoin(fn)
968 969 b = temp("other", other)
969 970 c = temp("base", base)
970 971
971 972 self.ui.note("resolving %s\n" % fn)
972 973 self.ui.debug("file %s: other %s ancestor %s\n" %
973 974 (fn, short(other), short(base)))
974 975
975 976 cmd = os.environ.get("HGMERGE", "hgmerge")
976 977 r = os.system("%s %s %s %s" % (cmd, a, b, c))
977 978 if r:
978 979 self.ui.warn("merging %s failed!\n" % f)
979 980
980 981 os.unlink(b)
981 982 os.unlink(c)
982 983
983 984 def verify(self):
984 985 filelinkrevs = {}
985 986 filenodes = {}
986 987 manifestchangeset = {}
987 988 changesets = revisions = files = 0
988 989 errors = 0
989 990
990 991 self.ui.status("checking changesets\n")
991 992 for i in range(self.changelog.count()):
992 993 changesets += 1
993 994 n = self.changelog.node(i)
994 995 for p in self.changelog.parents(n):
995 996 if p not in self.changelog.nodemap:
996 997 self.ui.warn("changeset %s has unknown parent %s\n" %
997 998 (short(n), short(p)))
998 999 errors += 1
999 1000 try:
1000 1001 changes = self.changelog.read(n)
1001 1002 except Exception, inst:
1002 1003 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1003 1004 errors += 1
1004 1005
1005 1006 manifestchangeset[changes[0]] = n
1006 1007 for f in changes[3]:
1007 1008 filelinkrevs.setdefault(f, []).append(i)
1008 1009
1009 1010 self.ui.status("checking manifests\n")
1010 1011 for i in range(self.manifest.count()):
1011 1012 n = self.manifest.node(i)
1012 1013 for p in self.manifest.parents(n):
1013 1014 if p not in self.manifest.nodemap:
1014 1015 self.ui.warn("manifest %s has unknown parent %s\n" %
1015 1016 (short(n), short(p)))
1016 1017 errors += 1
1017 1018 ca = self.changelog.node(self.manifest.linkrev(n))
1018 1019 cc = manifestchangeset[n]
1019 1020 if ca != cc:
1020 1021 self.ui.warn("manifest %s points to %s, not %s\n" %
1021 1022 (hex(n), hex(ca), hex(cc)))
1022 1023 errors += 1
1023 1024
1024 1025 try:
1025 1026 delta = mdiff.patchtext(self.manifest.delta(n))
1026 1027 except KeyboardInterrupt:
1027 1028 print "aborted"
1028 1029 sys.exit(0)
1029 1030 except Exception, inst:
1030 1031 self.ui.warn("unpacking manifest %s: %s\n"
1031 1032 % (short(n), inst))
1032 1033 errors += 1
1033 1034
1034 1035 ff = [ l.split('\0') for l in delta.splitlines() ]
1035 1036 for f, fn in ff:
1036 1037 filenodes.setdefault(f, {})[bin(fn)] = 1
1037 1038
1038 1039 self.ui.status("crosschecking files in changesets and manifests\n")
1039 1040 for f in filenodes:
1040 1041 if f not in filelinkrevs:
1041 1042 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1042 1043 errors += 1
1043 1044
1044 1045 for f in filelinkrevs:
1045 1046 if f not in filenodes:
1046 1047 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1047 1048 errors += 1
1048 1049
1049 1050 self.ui.status("checking files\n")
1050 1051 ff = filenodes.keys()
1051 1052 ff.sort()
1052 1053 for f in ff:
1053 1054 if f == "/dev/null": continue
1054 1055 files += 1
1055 1056 fl = self.file(f)
1056 1057 nodes = { nullid: 1 }
1057 1058 for i in range(fl.count()):
1058 1059 revisions += 1
1059 1060 n = fl.node(i)
1060 1061
1061 1062 if n not in filenodes[f]:
1062 1063 self.ui.warn("%s: %d:%s not in manifests\n"
1063 1064 % (f, i, short(n)))
1064 1065 print len(filenodes[f].keys()), fl.count(), f
1065 1066 errors += 1
1066 1067 else:
1067 1068 del filenodes[f][n]
1068 1069
1069 1070 flr = fl.linkrev(n)
1070 1071 if flr not in filelinkrevs[f]:
1071 1072 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1072 1073 % (f, short(n), fl.linkrev(n)))
1073 1074 errors += 1
1074 1075 else:
1075 1076 filelinkrevs[f].remove(flr)
1076 1077
1077 1078 # verify contents
1078 1079 try:
1079 1080 t = fl.read(n)
1080 1081 except Exception, inst:
1081 1082 self.ui.warn("unpacking file %s %s: %s\n"
1082 1083 % (f, short(n), inst))
1083 1084 errors += 1
1084 1085
1085 1086 # verify parents
1086 1087 (p1, p2) = fl.parents(n)
1087 1088 if p1 not in nodes:
1088 1089 self.ui.warn("file %s:%s unknown parent 1 %s" %
1089 1090 (f, short(n), short(p1)))
1090 1091 errors += 1
1091 1092 if p2 not in nodes:
1092 1093 self.ui.warn("file %s:%s unknown parent 2 %s" %
1093 1094 (f, short(n), short(p1)))
1094 1095 errors += 1
1095 1096 nodes[n] = 1
1096 1097
1097 1098 # cross-check
1098 1099 for node in filenodes[f]:
1099 1100 self.ui.warn("node %s in manifests not in %s\n"
1100 1101 % (hex(n), f))
1101 1102 errors += 1
1102 1103
1103 1104 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1104 1105 (files, changesets, revisions))
1105 1106
1106 1107 if errors:
1107 1108 self.ui.warn("%d integrity errors encountered!\n" % errors)
1108 1109 return 1
1109 1110
1110 1111 class remoterepository:
1111 1112 def __init__(self, ui, path):
1112 1113 self.url = path
1113 1114 self.ui = ui
1114 1115
1115 1116 def do_cmd(self, cmd, **args):
1116 1117 self.ui.debug("sending %s command\n" % cmd)
1117 1118 q = {"cmd": cmd}
1118 1119 q.update(args)
1119 1120 qs = urllib.urlencode(q)
1120 1121 cu = "%s?%s" % (self.url, qs)
1121 1122 return urllib.urlopen(cu)
1122 1123
1123 1124 def heads(self):
1124 1125 d = self.do_cmd("heads").read()
1125 1126 try:
1126 1127 return map(bin, d[:-1].split(" "))
1127 1128 except:
1128 1129 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1129 1130 raise
1130 1131
1131 1132 def branches(self, nodes):
1132 1133 n = " ".join(map(hex, nodes))
1133 1134 d = self.do_cmd("branches", nodes=n).read()
1134 1135 try:
1135 1136 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1136 1137 return br
1137 1138 except:
1138 1139 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1139 1140 raise
1140 1141
1141 1142 def between(self, pairs):
1142 1143 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1143 1144 d = self.do_cmd("between", pairs=n).read()
1144 1145 try:
1145 1146 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1146 1147 return p
1147 1148 except:
1148 1149 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1149 1150 raise
1150 1151
1151 1152 def changegroup(self, nodes):
1152 1153 n = " ".join(map(hex, nodes))
1153 1154 zd = zlib.decompressobj()
1154 1155 f = self.do_cmd("changegroup", roots=n)
1155 1156 bytes = 0
1156 1157 while 1:
1157 1158 d = f.read(4096)
1158 1159 bytes += len(d)
1159 1160 if not d:
1160 1161 yield zd.flush()
1161 1162 break
1162 1163 yield zd.decompress(d)
1163 1164 self.ui.note("%d bytes of data transfered\n" % bytes)
1164 1165
1165 1166 def repository(ui, path=None, create=0):
1166 1167 if path and path[:7] == "http://":
1167 1168 return remoterepository(ui, path)
1168 1169 if path and path[:5] == "hg://":
1169 1170 return remoterepository(ui, path.replace("hg://", "http://"))
1170 1171 if path and path[:11] == "old-http://":
1171 1172 return localrepository(ui, path.replace("old-http://", "http://"))
1172 1173 else:
1173 1174 return localrepository(ui, path, create)
1174 1175
1175 1176 class httprangereader:
1176 1177 def __init__(self, url):
1177 1178 self.url = url
1178 1179 self.pos = 0
1179 1180 def seek(self, pos):
1180 1181 self.pos = pos
1181 1182 def read(self, bytes=None):
1182 1183 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1183 1184 urllib2.install_opener(opener)
1184 1185 req = urllib2.Request(self.url)
1185 1186 end = ''
1186 1187 if bytes: end = self.pos + bytes
1187 1188 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1188 1189 f = urllib2.urlopen(req)
1189 1190 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now