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