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