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