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