##// END OF EJS Templates
annotate: memory efficiency...
mpm@selenic.com -
r200:8450c18f default
parent child Browse files
Show More
@@ -1,972 +1,981 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, sha, socket, os, time, re, urllib2
9 9 import urllib
10 10 from mercurial import byterange, lock
11 11 from mercurial.transaction import *
12 12 from mercurial.revlog import *
13 13 from difflib import SequenceMatcher
14 14
15 15 class filelog(revlog):
16 16 def __init__(self, opener, path):
17 17 revlog.__init__(self, opener,
18 18 os.path.join("data", path + ".i"),
19 19 os.path.join("data", path + ".d"))
20 20
21 21 def read(self, node):
22 22 return self.revision(node)
23 23 def add(self, text, transaction, link, p1=None, p2=None):
24 24 return self.addrevision(text, transaction, link, p1, p2)
25 25
26 26 def annotate(self, node):
27 27
28 28 def decorate(text, rev):
29 29 return [(rev, l) for l in text.splitlines(1)]
30 30
31 31 def strip(annotation):
32 32 return [e[1] for e in annotation]
33 33
34 34 def pair(parent, child):
35 35 new = []
36 36 sm = SequenceMatcher(None, strip(parent), strip(child))
37 37 for o, m, n, s, t in sm.get_opcodes():
38 38 if o == 'equal':
39 39 new += parent[m:n]
40 40 else:
41 41 new += child[s:t]
42 42 return new
43 43
44 # find all ancestors
44 45 needed = {}
45 46 visit = [node]
46 47 while visit:
47 48 n = visit.pop(0)
48 49 for p in self.parents(n):
49 50 if p not in needed:
50 51 needed[p] = 1
51 52 visit.append(p)
53 else:
54 # count how many times we'll use this
55 needed[p] += 1
52 56
57 # sort by revision which is a topological order
53 58 visit = needed.keys()
54 59 visit = [ (self.rev(n), n) for n in visit ]
55 60 visit.sort()
56 61 visit = [ p[1] for p in visit ]
57 62 hist = {}
58 63
59 64 for n in visit:
60 65 curr = decorate(self.read(n), self.linkrev(n))
61 66 for p in self.parents(n):
62 67 if p != nullid:
63 68 curr = pair(hist[p], curr)
69 # trim the history of unneeded revs
70 needed[p] -= 1
71 if not needed[p]:
72 del hist[p]
64 73 hist[n] = curr
65 74
66 75 return hist[n]
67 76
68 77 class manifest(revlog):
69 78 def __init__(self, opener):
70 79 self.mapcache = None
71 80 self.listcache = None
72 81 self.addlist = None
73 82 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
74 83
75 84 def read(self, node):
76 85 if self.mapcache and self.mapcache[0] == node:
77 86 return self.mapcache[1].copy()
78 87 text = self.revision(node)
79 88 map = {}
80 89 self.listcache = (text, text.splitlines(1))
81 90 for l in self.listcache[1]:
82 91 (f, n) = l.split('\0')
83 92 map[f] = bin(n[:40])
84 93 self.mapcache = (node, map)
85 94 return map
86 95
87 96 def diff(self, a, b):
88 97 # this is sneaky, as we're not actually using a and b
89 98 if self.listcache and self.addlist and self.listcache[0] == a:
90 99 d = mdiff.diff(self.listcache[1], self.addlist, 1)
91 100 if mdiff.patch(a, d) != b:
92 101 sys.stderr.write("*** sortdiff failed, falling back ***\n")
93 102 return mdiff.textdiff(a, b)
94 103 return d
95 104 else:
96 105 return mdiff.textdiff(a, b)
97 106
98 107 def add(self, map, transaction, link, p1=None, p2=None):
99 108 files = map.keys()
100 109 files.sort()
101 110
102 111 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
103 112 text = "".join(self.addlist)
104 113
105 114 n = self.addrevision(text, transaction, link, p1, p2)
106 115 self.mapcache = (n, map)
107 116 self.listcache = (text, self.addlist)
108 117 self.addlist = None
109 118
110 119 return n
111 120
112 121 class changelog(revlog):
113 122 def __init__(self, opener):
114 123 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
115 124
116 125 def extract(self, text):
117 126 if not text:
118 127 return (nullid, "", "0", [], "")
119 128 last = text.index("\n\n")
120 129 desc = text[last + 2:]
121 130 l = text[:last].splitlines()
122 131 manifest = bin(l[0])
123 132 user = l[1]
124 133 date = l[2]
125 134 files = l[3:]
126 135 return (manifest, user, date, files, desc)
127 136
128 137 def read(self, node):
129 138 return self.extract(self.revision(node))
130 139
131 140 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
132 141 user = (os.environ.get("HGUSER") or
133 142 os.environ.get("EMAIL") or
134 143 os.environ.get("LOGNAME", "unknown") + '@' + socket.getfqdn())
135 144 date = "%d %d" % (time.time(), time.timezone)
136 145 list.sort()
137 146 l = [hex(manifest), user, date] + list + ["", desc]
138 147 text = "\n".join(l)
139 148 return self.addrevision(text, transaction, self.count(), p1, p2)
140 149
141 150 class dircache:
142 151 def __init__(self, opener, ui):
143 152 self.opener = opener
144 153 self.dirty = 0
145 154 self.ui = ui
146 155 self.map = None
147 156 def __del__(self):
148 157 if self.dirty: self.write()
149 158 def __getitem__(self, key):
150 159 try:
151 160 return self.map[key]
152 161 except TypeError:
153 162 self.read()
154 163 return self[key]
155 164
156 165 def read(self):
157 166 if self.map is not None: return self.map
158 167
159 168 self.map = {}
160 169 try:
161 170 st = self.opener("dircache").read()
162 171 except: return
163 172
164 173 pos = 0
165 174 while pos < len(st):
166 175 e = struct.unpack(">llll", st[pos:pos+16])
167 176 l = e[3]
168 177 pos += 16
169 178 f = st[pos:pos + l]
170 179 self.map[f] = e[:3]
171 180 pos += l
172 181
173 182 def update(self, files):
174 183 if not files: return
175 184 self.read()
176 185 self.dirty = 1
177 186 for f in files:
178 187 try:
179 188 s = os.stat(f)
180 189 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
181 190 except IOError:
182 191 self.remove(f)
183 192
184 193 def taint(self, files):
185 194 if not files: return
186 195 self.read()
187 196 self.dirty = 1
188 197 for f in files:
189 198 self.map[f] = (0, -1, 0)
190 199
191 200 def remove(self, files):
192 201 if not files: return
193 202 self.read()
194 203 self.dirty = 1
195 204 for f in files:
196 205 try:
197 206 del self.map[f]
198 207 except KeyError:
199 208 self.ui.warn("Not in dircache: %s\n" % f)
200 209 pass
201 210
202 211 def clear(self):
203 212 self.map = {}
204 213 self.dirty = 1
205 214
206 215 def write(self):
207 216 st = self.opener("dircache", "w")
208 217 for f, e in self.map.items():
209 218 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
210 219 st.write(e + f)
211 220 self.dirty = 0
212 221
213 222 def copy(self):
214 223 self.read()
215 224 return self.map.copy()
216 225
217 226 # used to avoid circular references so destructors work
218 227 def opener(base):
219 228 p = base
220 229 def o(path, mode="r"):
221 230 if p[:7] == "http://":
222 231 f = os.path.join(p, urllib.quote(path))
223 232 return httprangereader(f)
224 233
225 234 f = os.path.join(p, path)
226 235
227 236 if mode != "r":
228 237 try:
229 238 s = os.stat(f)
230 239 except OSError:
231 240 d = os.path.dirname(f)
232 241 if not os.path.isdir(d):
233 242 os.makedirs(d)
234 243 else:
235 244 if s.st_nlink > 1:
236 245 file(f + ".tmp", "w").write(file(f).read())
237 246 os.rename(f+".tmp", f)
238 247
239 248 return file(f, mode)
240 249
241 250 return o
242 251
243 252 class localrepository:
244 253 def __init__(self, ui, path=None, create=0):
245 254 self.remote = 0
246 255 if path and path[:7] == "http://":
247 256 self.remote = 1
248 257 self.path = path
249 258 else:
250 259 if not path:
251 260 p = os.getcwd()
252 261 while not os.path.isdir(os.path.join(p, ".hg")):
253 262 p = os.path.dirname(p)
254 263 if p == "/": raise "No repo found"
255 264 path = p
256 265 self.path = os.path.join(path, ".hg")
257 266
258 267 self.root = path
259 268 self.ui = ui
260 269
261 270 if create:
262 271 os.mkdir(self.path)
263 272 os.mkdir(self.join("data"))
264 273
265 274 self.opener = opener(self.path)
266 275 self.manifest = manifest(self.opener)
267 276 self.changelog = changelog(self.opener)
268 277 self.ignorelist = None
269 278 self.tags = None
270 279
271 280 if not self.remote:
272 281 self.dircache = dircache(self.opener, ui)
273 282 try:
274 283 self.current = bin(self.opener("current").read())
275 284 except IOError:
276 285 self.current = None
277 286
278 287 def setcurrent(self, node):
279 288 self.current = node
280 289 self.opener("current", "w").write(hex(node))
281 290
282 291 def ignore(self, f):
283 292 if self.ignorelist is None:
284 293 self.ignorelist = []
285 294 try:
286 295 l = open(os.path.join(self.root, ".hgignore"))
287 296 for pat in l:
288 297 if pat != "\n":
289 298 self.ignorelist.append(re.compile(pat[:-1]))
290 299 except IOError: pass
291 300 for pat in self.ignorelist:
292 301 if pat.search(f): return True
293 302 return False
294 303
295 304 def lookup(self, key):
296 305 if self.tags is None:
297 306 self.tags = {}
298 307 try:
299 308 fl = self.file(".hgtags")
300 309 for l in fl.revision(fl.tip()).splitlines():
301 310 if l:
302 311 n, k = l.split(" ")
303 312 self.tags[k] = bin(n)
304 313 except KeyError: pass
305 314 try:
306 315 return self.tags[key]
307 316 except KeyError:
308 317 return self.changelog.lookup(key)
309 318
310 319 def join(self, f):
311 320 return os.path.join(self.path, f)
312 321
313 322 def file(self, f):
314 323 if f[0] == '/': f = f[1:]
315 324 return filelog(self.opener, f)
316 325
317 326 def transaction(self):
318 327 return transaction(self.opener, self.join("journal"),
319 328 self.join("undo"))
320 329
321 330 def recover(self, f = "journal"):
322 331 self.lock()
323 332 if os.path.exists(self.join(f)):
324 333 self.ui.status("attempting to rollback %s information\n" % f)
325 334 return rollback(self.opener, self.join(f))
326 335 else:
327 336 self.ui.warn("no %s information available\n" % f)
328 337
329 338 def lock(self, wait = 1):
330 339 try:
331 340 return lock.lock(self.join("lock"), 0)
332 341 except lock.LockHeld, inst:
333 342 if wait:
334 343 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
335 344 return lock.lock(self.join("lock"), wait)
336 345 raise inst
337 346
338 347 def commit(self, parent, update = None, text = ""):
339 348 self.lock()
340 349 try:
341 350 remove = [ l[:-1] for l in self.opener("to-remove") ]
342 351 os.unlink(self.join("to-remove"))
343 352
344 353 except IOError:
345 354 remove = []
346 355
347 356 if update == None:
348 357 update = self.diffdir(self.root, parent)[0]
349 358
350 359 if not update:
351 360 self.ui.status("nothing changed\n")
352 361 return
353 362
354 363 tr = self.transaction()
355 364
356 365 # check in files
357 366 new = {}
358 367 linkrev = self.changelog.count()
359 368 update.sort()
360 369 for f in update:
361 370 self.ui.note(f + "\n")
362 371 try:
363 372 t = file(f).read()
364 373 except IOError:
365 374 remove.append(f)
366 375 continue
367 376 r = self.file(f)
368 377 new[f] = r.add(t, tr, linkrev)
369 378
370 379 # update manifest
371 380 mmap = self.manifest.read(self.manifest.tip())
372 381 mmap.update(new)
373 382 for f in remove:
374 383 del mmap[f]
375 384 mnode = self.manifest.add(mmap, tr, linkrev)
376 385
377 386 # add changeset
378 387 new = new.keys()
379 388 new.sort()
380 389
381 390 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mnode)
382 391 edittext += "".join(["HG: changed %s\n" % f for f in new])
383 392 edittext += "".join(["HG: removed %s\n" % f for f in remove])
384 393 edittext = self.ui.edit(edittext)
385 394
386 395 n = self.changelog.add(mnode, new, edittext, tr)
387 396 tr.close()
388 397
389 398 self.setcurrent(n)
390 399 self.dircache.update(new)
391 400 self.dircache.remove(remove)
392 401
393 402 def checkout(self, node):
394 403 # checkout is really dumb at the moment
395 404 # it ought to basically merge
396 405 change = self.changelog.read(node)
397 406 l = self.manifest.read(change[0]).items()
398 407 l.sort()
399 408
400 409 for f,n in l:
401 410 if f[0] == "/": continue
402 411 self.ui.note(f, "\n")
403 412 t = self.file(f).revision(n)
404 413 try:
405 414 file(f, "w").write(t)
406 415 except IOError:
407 416 os.makedirs(os.path.dirname(f))
408 417 file(f, "w").write(t)
409 418
410 419 self.setcurrent(node)
411 420 self.dircache.clear()
412 421 self.dircache.update([f for f,n in l])
413 422
414 423 def diffdir(self, path, changeset):
415 424 changed = []
416 425 mf = {}
417 426 added = []
418 427
419 428 if changeset:
420 429 change = self.changelog.read(changeset)
421 430 mf = self.manifest.read(change[0])
422 431
423 432 if changeset == self.current:
424 433 dc = self.dircache.copy()
425 434 else:
426 435 dc = dict.fromkeys(mf)
427 436
428 437 def fcmp(fn):
429 438 t1 = file(os.path.join(self.root, fn)).read()
430 439 t2 = self.file(fn).revision(mf[fn])
431 440 return cmp(t1, t2)
432 441
433 442 for dir, subdirs, files in os.walk(self.root):
434 443 d = dir[len(self.root)+1:]
435 444 if ".hg" in subdirs: subdirs.remove(".hg")
436 445
437 446 for f in files:
438 447 fn = os.path.join(d, f)
439 448 try: s = os.stat(os.path.join(self.root, fn))
440 449 except: continue
441 450 if fn in dc:
442 451 c = dc[fn]
443 452 del dc[fn]
444 453 if not c:
445 454 if fcmp(fn):
446 455 changed.append(fn)
447 456 elif c[1] != s.st_size:
448 457 changed.append(fn)
449 458 elif c[0] != s.st_mode or c[2] != s.st_mtime:
450 459 if fcmp(fn):
451 460 changed.append(fn)
452 461 else:
453 462 if self.ignore(fn): continue
454 463 added.append(fn)
455 464
456 465 deleted = dc.keys()
457 466 deleted.sort()
458 467
459 468 return (changed, added, deleted)
460 469
461 470 def diffrevs(self, node1, node2):
462 471 changed, added = [], []
463 472
464 473 change = self.changelog.read(node1)
465 474 mf1 = self.manifest.read(change[0])
466 475 change = self.changelog.read(node2)
467 476 mf2 = self.manifest.read(change[0])
468 477
469 478 for fn in mf2:
470 479 if mf1.has_key(fn):
471 480 if mf1[fn] != mf2[fn]:
472 481 changed.append(fn)
473 482 del mf1[fn]
474 483 else:
475 484 added.append(fn)
476 485
477 486 deleted = mf1.keys()
478 487 deleted.sort()
479 488
480 489 return (changed, added, deleted)
481 490
482 491 def add(self, list):
483 492 self.dircache.taint(list)
484 493
485 494 def remove(self, list):
486 495 dl = self.opener("to-remove", "a")
487 496 for f in list:
488 497 dl.write(f + "\n")
489 498
490 499 def branches(self, nodes):
491 500 if not nodes: nodes = [self.changelog.tip()]
492 501 b = []
493 502 for n in nodes:
494 503 t = n
495 504 while n:
496 505 p = self.changelog.parents(n)
497 506 if p[1] != nullid or p[0] == nullid:
498 507 b.append((t, n, p[0], p[1]))
499 508 break
500 509 n = p[0]
501 510 return b
502 511
503 512 def between(self, pairs):
504 513 r = []
505 514
506 515 for top, bottom in pairs:
507 516 n, l, i = top, [], 0
508 517 f = 1
509 518
510 519 while n != bottom:
511 520 p = self.changelog.parents(n)[0]
512 521 if i == f:
513 522 l.append(n)
514 523 f = f * 2
515 524 n = p
516 525 i += 1
517 526
518 527 r.append(l)
519 528
520 529 return r
521 530
522 531 def newer(self, nodes):
523 532 m = {}
524 533 nl = []
525 534 pm = {}
526 535 cl = self.changelog
527 536 t = l = cl.count()
528 537
529 538 # find the lowest numbered node
530 539 for n in nodes:
531 540 l = min(l, cl.rev(n))
532 541 m[n] = 1
533 542
534 543 for i in xrange(l, t):
535 544 n = cl.node(i)
536 545 if n in m: # explicitly listed
537 546 pm[n] = 1
538 547 nl.append(n)
539 548 continue
540 549 for p in cl.parents(n):
541 550 if p in pm: # parent listed
542 551 pm[n] = 1
543 552 nl.append(n)
544 553 break
545 554
546 555 return nl
547 556
548 557 def getchangegroup(self, remote):
549 558 m = self.changelog.nodemap
550 559 search = []
551 560 fetch = []
552 561 seen = {}
553 562 seenbranch = {}
554 563
555 564 self.ui.status("searching for changes\n")
556 565 tip = remote.branches([])[0]
557 566 self.ui.debug("remote tip branch is %s:%s\n" %
558 567 (short(tip[0]), short(tip[1])))
559 568
560 569 # if we have an empty repo, fetch everything
561 570 if self.changelog.tip() == nullid:
562 571 return remote.changegroup([nullid])
563 572
564 573 # otherwise, assume we're closer to the tip than the root
565 574 unknown = [tip]
566 575
567 576 if tip[0] in m:
568 577 self.ui.status("nothing to do!\n")
569 578 return None
570 579
571 580 while unknown:
572 581 n = unknown.pop(0)
573 582 seen[n[0]] = 1
574 583
575 584 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
576 585 if n == nullid: break
577 586 if n in seenbranch:
578 587 self.ui.debug("branch already found\n")
579 588 continue
580 589 if n[1] and n[1] in m: # do we know the base?
581 590 self.ui.debug("found incomplete branch %s:%s\n"
582 591 % (short(n[0]), short(n[1])))
583 592 search.append(n) # schedule branch range for scanning
584 593 seenbranch[n] = 1
585 594 else:
586 595 if n[2] in m and n[3] in m:
587 596 if n[1] not in fetch:
588 597 self.ui.debug("found new changeset %s\n" %
589 598 short(n[1]))
590 599 fetch.append(n[1]) # earliest unknown
591 600 continue
592 601
593 602 r = []
594 603 for a in n[2:4]:
595 604 if a not in seen: r.append(a)
596 605
597 606 if r:
598 607 self.ui.debug("requesting %s\n" %
599 608 " ".join(map(short, r)))
600 609 for b in remote.branches(r):
601 610 self.ui.debug("received %s:%s\n" %
602 611 (short(b[0]), short(b[1])))
603 612 if b[0] not in m and b[0] not in seen:
604 613 unknown.append(b)
605 614
606 615 while search:
607 616 n = search.pop(0)
608 617 l = remote.between([(n[0], n[1])])[0]
609 618 p = n[0]
610 619 f = 1
611 620 for i in l + [n[1]]:
612 621 if i in m:
613 622 if f <= 2:
614 623 self.ui.debug("found new branch changeset %s\n" %
615 624 short(p))
616 625 fetch.append(p)
617 626 else:
618 627 self.ui.debug("narrowed branch search to %s:%s\n"
619 628 % (short(p), short(i)))
620 629 search.append((p, i))
621 630 break
622 631 p, f = i, f * 2
623 632
624 633 for f in fetch:
625 634 if f in m:
626 635 raise "already have", short(f[:4])
627 636
628 637 self.ui.note("adding new changesets starting at " +
629 638 " ".join([short(f) for f in fetch]) + "\n")
630 639
631 640 return remote.changegroup(fetch)
632 641
633 642 def changegroup(self, basenodes):
634 643 nodes = self.newer(basenodes)
635 644
636 645 # construct the link map
637 646 linkmap = {}
638 647 for n in nodes:
639 648 linkmap[self.changelog.rev(n)] = n
640 649
641 650 # construct a list of all changed files
642 651 changed = {}
643 652 for n in nodes:
644 653 c = self.changelog.read(n)
645 654 for f in c[3]:
646 655 changed[f] = 1
647 656 changed = changed.keys()
648 657 changed.sort()
649 658
650 659 # the changegroup is changesets + manifests + all file revs
651 660 revs = [ self.changelog.rev(n) for n in nodes ]
652 661
653 662 for y in self.changelog.group(linkmap): yield y
654 663 for y in self.manifest.group(linkmap): yield y
655 664 for f in changed:
656 665 yield struct.pack(">l", len(f) + 4) + f
657 666 g = self.file(f).group(linkmap)
658 667 for y in g:
659 668 yield y
660 669
661 670 def addchangegroup(self, generator):
662 671 changesets = files = revisions = 0
663 672
664 673 self.lock()
665 674 class genread:
666 675 def __init__(self, generator):
667 676 self.g = generator
668 677 self.buf = ""
669 678 def read(self, l):
670 679 while l > len(self.buf):
671 680 try:
672 681 self.buf += self.g.next()
673 682 except StopIteration:
674 683 break
675 684 d, self.buf = self.buf[:l], self.buf[l:]
676 685 return d
677 686
678 687 if not generator: return
679 688 source = genread(generator)
680 689
681 690 def getchunk():
682 691 d = source.read(4)
683 692 if not d: return ""
684 693 l = struct.unpack(">l", d)[0]
685 694 if l <= 4: return ""
686 695 return source.read(l - 4)
687 696
688 697 def getgroup():
689 698 while 1:
690 699 c = getchunk()
691 700 if not c: break
692 701 yield c
693 702
694 703 tr = self.transaction()
695 704 simple = True
696 705 need = {}
697 706
698 707 self.ui.status("adding changesets\n")
699 708 # pull off the changeset group
700 709 def report(x):
701 710 self.ui.debug("add changeset %s\n" % short(x))
702 711 return self.changelog.count()
703 712
704 713 co = self.changelog.tip()
705 714 cn = self.changelog.addgroup(getgroup(), report, tr)
706 715
707 716 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
708 717
709 718 self.ui.status("adding manifests\n")
710 719 # pull off the manifest group
711 720 mm = self.manifest.tip()
712 721 mo = self.manifest.addgroup(getgroup(),
713 722 lambda x: self.changelog.rev(x), tr)
714 723
715 724 # do we need a resolve?
716 725 if self.changelog.ancestor(co, cn) != co:
717 726 simple = False
718 727 resolverev = self.changelog.count()
719 728
720 729 # resolve the manifest to determine which files
721 730 # we care about merging
722 731 self.ui.status("resolving manifests\n")
723 732 ma = self.manifest.ancestor(mm, mo)
724 733 omap = self.manifest.read(mo) # other
725 734 amap = self.manifest.read(ma) # ancestor
726 735 mmap = self.manifest.read(mm) # mine
727 736 nmap = {}
728 737
729 738 self.ui.debug(" ancestor %s local %s remote %s\n" %
730 739 (short(ma), short(mm), short(mo)))
731 740
732 741 for f, mid in mmap.iteritems():
733 742 if f in omap:
734 743 if mid != omap[f]:
735 744 self.ui.debug(" %s versions differ, do resolve\n" % f)
736 745 need[f] = mid # use merged version or local version
737 746 else:
738 747 nmap[f] = mid # keep ours
739 748 del omap[f]
740 749 elif f in amap:
741 750 if mid != amap[f]:
742 751 r = self.ui.prompt(
743 752 (" local changed %s which remote deleted\n" % f) +
744 753 "(k)eep or (d)elete?", "[kd]", "k")
745 754 if r == "k": nmap[f] = mid
746 755 else:
747 756 self.ui.debug("other deleted %s\n" % f)
748 757 pass # other deleted it
749 758 else:
750 759 self.ui.debug("local created %s\n" %f)
751 760 nmap[f] = mid # we created it
752 761
753 762 del mmap
754 763
755 764 for f, oid in omap.iteritems():
756 765 if f in amap:
757 766 if oid != amap[f]:
758 767 r = self.ui.prompt(
759 768 ("remote changed %s which local deleted\n" % f) +
760 769 "(k)eep or (d)elete?", "[kd]", "k")
761 770 if r == "k": nmap[f] = oid
762 771 else:
763 772 pass # probably safe
764 773 else:
765 774 self.ui.debug("remote created %s, do resolve\n" % f)
766 775 need[f] = oid
767 776
768 777 del omap
769 778 del amap
770 779
771 780 new = need.keys()
772 781 new.sort()
773 782
774 783 # process the files
775 784 self.ui.status("adding files\n")
776 785 while 1:
777 786 f = getchunk()
778 787 if not f: break
779 788 self.ui.debug("adding %s revisions\n" % f)
780 789 fl = self.file(f)
781 790 o = fl.tip()
782 791 n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr)
783 792 revisions += fl.rev(n) - fl.rev(o)
784 793 files += 1
785 794 if f in need:
786 795 del need[f]
787 796 # manifest resolve determined we need to merge the tips
788 797 nmap[f] = self.merge3(fl, f, o, n, tr, resolverev)
789 798
790 799 if need:
791 800 # we need to do trivial merges on local files
792 801 for f in new:
793 802 if f not in need: continue
794 803 fl = self.file(f)
795 804 nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev)
796 805 revisions += 1
797 806
798 807 # For simple merges, we don't need to resolve manifests or changesets
799 808 if simple:
800 809 self.ui.debug("simple merge, skipping resolve\n")
801 810 self.ui.status(("modified %d files, added %d changesets" +
802 811 " and %d new revisions\n")
803 812 % (files, changesets, revisions))
804 813 tr.close()
805 814 return
806 815
807 816 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
808 817 revisions += 1
809 818
810 819 # Now all files and manifests are merged, we add the changed files
811 820 # and manifest id to the changelog
812 821 self.ui.status("committing merge changeset\n")
813 822 if co == cn: cn = -1
814 823
815 824 edittext = "\nHG: merge resolve\n" + \
816 825 "HG: manifest hash %s\n" % hex(node) + \
817 826 "".join(["HG: changed %s\n" % f for f in new])
818 827 edittext = self.ui.edit(edittext)
819 828 n = self.changelog.add(node, new, edittext, tr, co, cn)
820 829 revisions += 1
821 830
822 831 self.ui.status("added %d changesets, %d files, and %d new revisions\n"
823 832 % (changesets, files, revisions))
824 833
825 834 tr.close()
826 835
827 836 def merge3(self, fl, fn, my, other, transaction, link):
828 837 """perform a 3-way merge and append the result"""
829 838
830 839 def temp(prefix, node):
831 840 pre = "%s~%s." % (os.path.basename(fn), prefix)
832 841 (fd, name) = tempfile.mkstemp("", pre)
833 842 f = os.fdopen(fd, "w")
834 843 f.write(fl.revision(node))
835 844 f.close()
836 845 return name
837 846
838 847 base = fl.ancestor(my, other)
839 848 self.ui.note("resolving %s\n" % fn)
840 849 self.ui.debug("local %s remote %s ancestor %s\n" %
841 850 (short(my), short(other), short(base)))
842 851
843 852 if my == base:
844 853 text = fl.revision(other)
845 854 else:
846 855 a = temp("local", my)
847 856 b = temp("remote", other)
848 857 c = temp("parent", base)
849 858
850 859 cmd = os.environ["HGMERGE"]
851 860 self.ui.debug("invoking merge with %s\n" % cmd)
852 861 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
853 862 if r:
854 863 raise "Merge failed!"
855 864
856 865 text = open(a).read()
857 866 os.unlink(a)
858 867 os.unlink(b)
859 868 os.unlink(c)
860 869
861 870 return fl.add(text, transaction, link, my, other)
862 871
863 872 class remoterepository:
864 873 def __init__(self, ui, path):
865 874 self.url = path
866 875 self.ui = ui
867 876
868 877 def do_cmd(self, cmd, **args):
869 878 self.ui.debug("sending %s command\n" % cmd)
870 879 q = {"cmd": cmd}
871 880 q.update(args)
872 881 qs = urllib.urlencode(q)
873 882 cu = "%s?%s" % (self.url, qs)
874 883 return urllib.urlopen(cu)
875 884
876 885 def branches(self, nodes):
877 886 n = " ".join(map(hex, nodes))
878 887 d = self.do_cmd("branches", nodes=n).read()
879 888 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
880 889 return br
881 890
882 891 def between(self, pairs):
883 892 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
884 893 d = self.do_cmd("between", pairs=n).read()
885 894 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
886 895 return p
887 896
888 897 def changegroup(self, nodes):
889 898 n = " ".join(map(hex, nodes))
890 899 zd = zlib.decompressobj()
891 900 f = self.do_cmd("changegroup", roots=n)
892 901 bytes = 0
893 902 while 1:
894 903 d = f.read(4096)
895 904 bytes += len(d)
896 905 if not d:
897 906 yield zd.flush()
898 907 break
899 908 yield zd.decompress(d)
900 909 self.ui.note("%d bytes of data transfered\n" % bytes)
901 910
902 911 def repository(ui, path=None, create=0):
903 912 if path and path[:7] == "http://":
904 913 return remoterepository(ui, path)
905 914 if path and path[:5] == "hg://":
906 915 return remoterepository(ui, path.replace("hg://", "http://"))
907 916 if path and path[:11] == "old-http://":
908 917 return localrepository(ui, path.replace("old-http://", "http://"))
909 918 else:
910 919 return localrepository(ui, path, create)
911 920
912 921 class ui:
913 922 def __init__(self, verbose=False, debug=False, quiet=False,
914 923 interactive=True):
915 924 self.quiet = quiet and not verbose and not debug
916 925 self.verbose = verbose or debug
917 926 self.debugflag = debug
918 927 self.interactive = interactive
919 928 def write(self, *args):
920 929 for a in args:
921 930 sys.stdout.write(str(a))
922 931 def readline(self):
923 932 return sys.stdin.readline()[:-1]
924 933 def prompt(self, msg, pat, default = "y"):
925 934 if not self.interactive: return default
926 935 while 1:
927 936 self.write(msg, " ")
928 937 r = self.readline()
929 938 if re.match(pat, r):
930 939 return r
931 940 else:
932 941 self.write("unrecognized response\n")
933 942 def status(self, *msg):
934 943 if not self.quiet: self.write(*msg)
935 944 def warn(self, msg):
936 945 self.write(*msg)
937 946 def note(self, *msg):
938 947 if self.verbose: self.write(*msg)
939 948 def debug(self, *msg):
940 949 if self.debugflag: self.write(*msg)
941 950 def edit(self, text):
942 951 (fd, name) = tempfile.mkstemp("hg")
943 952 f = os.fdopen(fd, "w")
944 953 f.write(text)
945 954 f.close()
946 955
947 956 editor = os.environ.get("HGEDITOR") or os.environ.get("EDITOR", "vi")
948 957 r = os.system("%s %s" % (editor, name))
949 958
950 959 if r:
951 960 raise "Edit failed!"
952 961
953 962 t = open(name).read()
954 963 t = re.sub("(?m)^HG:.*\n", "", t)
955 964
956 965 return t
957 966
958 967 class httprangereader:
959 968 def __init__(self, url):
960 969 self.url = url
961 970 self.pos = 0
962 971 def seek(self, pos):
963 972 self.pos = pos
964 973 def read(self, bytes=None):
965 974 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
966 975 urllib2.install_opener(opener)
967 976 req = urllib2.Request(self.url)
968 977 end = ''
969 978 if bytes: end = self.pos + bytes
970 979 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
971 980 f = urllib2.urlopen(req)
972 981 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now