##// END OF EJS Templates
add 'm' state to dirstates...
mpm@selenic.com -
r231:15e7c6ce default
parent child Browse files
Show More
@@ -1,1139 +1,1142 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, tempfile
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 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 dirstate:
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 self.pl = None
159 159
160 160 def __del__(self):
161 161 if self.dirty:
162 162 self.write()
163 163
164 164 def __getitem__(self, key):
165 165 try:
166 166 return self.map[key]
167 167 except TypeError:
168 168 self.read()
169 169 return self[key]
170 170
171 171 def __contains__(self, key):
172 172 if not self.map: self.read()
173 173 return key in self.map
174 174
175 175 def parents(self):
176 176 if not self.pl:
177 177 self.read()
178 178 return self.pl
179 179
180 180 def setparents(self, p1, p2 = nullid):
181 181 self.dirty = 1
182 182 self.pl = p1, p2
183 183
184 184 def state(self, key):
185 185 try:
186 186 return self[key][0]
187 187 except KeyError:
188 188 return "?"
189 189
190 190 def read(self):
191 191 if self.map is not None: return self.map
192 192
193 193 self.map = {}
194 194 self.pl = [nullid, nullid]
195 195 try:
196 196 st = self.opener("dirstate").read()
197 197 except: return
198 198
199 199 self.pl = [st[:20], st[20: 40]]
200 200
201 201 pos = 40
202 202 while pos < len(st):
203 203 e = struct.unpack(">cllll", st[pos:pos+17])
204 204 l = e[4]
205 205 pos += 17
206 206 f = st[pos:pos + l]
207 207 self.map[f] = e[:4]
208 208 pos += l
209 209
210 210 def update(self, files, state):
211 211 ''' current states:
212 212 n normal
213 m needs merging
213 214 i invalid
214 215 r marked for removal
215 216 a marked for addition'''
216 217
217 218 if not files: return
218 219 self.read()
219 220 self.dirty = 1
220 221 for f in files:
221 222 if state == "r":
222 223 self.map[f] = ('r', 0, 0, 0)
223 224 else:
224 225 try:
225 226 s = os.stat(f)
226 227 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
227 228 except OSError:
228 229 if state != "i": raise
229 230 self.map[f] = ('r', 0, 0, 0)
230 231
231 232 def forget(self, files):
232 233 if not files: return
233 234 self.read()
234 235 self.dirty = 1
235 236 for f in files:
236 237 try:
237 238 del self.map[f]
238 239 except KeyError:
239 240 self.ui.warn("not in dirstate: %s!\n" % f)
240 241 pass
241 242
242 243 def clear(self):
243 244 self.map = {}
244 245 self.dirty = 1
245 246
246 247 def write(self):
247 248 st = self.opener("dirstate", "w")
248 249 st.write("".join(self.pl))
249 250 for f, e in self.map.items():
250 251 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
251 252 st.write(e + f)
252 253 self.dirty = 0
253 254
254 255 def copy(self):
255 256 self.read()
256 257 return self.map.copy()
257 258
258 259 # used to avoid circular references so destructors work
259 260 def opener(base):
260 261 p = base
261 262 def o(path, mode="r"):
262 263 if p[:7] == "http://":
263 264 f = os.path.join(p, urllib.quote(path))
264 265 return httprangereader(f)
265 266
266 267 f = os.path.join(p, path)
267 268
268 269 if mode != "r":
269 270 try:
270 271 s = os.stat(f)
271 272 except OSError:
272 273 d = os.path.dirname(f)
273 274 if not os.path.isdir(d):
274 275 os.makedirs(d)
275 276 else:
276 277 if s.st_nlink > 1:
277 278 file(f + ".tmp", "w").write(file(f).read())
278 279 os.rename(f+".tmp", f)
279 280
280 281 return file(f, mode)
281 282
282 283 return o
283 284
284 285 class localrepository:
285 286 def __init__(self, ui, path=None, create=0):
286 287 self.remote = 0
287 288 if path and path[:7] == "http://":
288 289 self.remote = 1
289 290 self.path = path
290 291 else:
291 292 if not path:
292 293 p = os.getcwd()
293 294 while not os.path.isdir(os.path.join(p, ".hg")):
294 295 p = os.path.dirname(p)
295 296 if p == "/": raise "No repo found"
296 297 path = p
297 298 self.path = os.path.join(path, ".hg")
298 299
299 300 self.root = path
300 301 self.ui = ui
301 302
302 303 if create:
303 304 os.mkdir(self.path)
304 305 os.mkdir(self.join("data"))
305 306
306 307 self.opener = opener(self.path)
307 308 self.manifest = manifest(self.opener)
308 309 self.changelog = changelog(self.opener)
309 310 self.ignorelist = None
310 311 self.tags = None
311 312
312 313 if not self.remote:
313 314 self.dirstate = dirstate(self.opener, ui)
314 315
315 316 def ignore(self, f):
316 317 if self.ignorelist is None:
317 318 self.ignorelist = []
318 319 try:
319 320 l = open(os.path.join(self.root, ".hgignore"))
320 321 for pat in l:
321 322 if pat != "\n":
322 323 self.ignorelist.append(re.compile(pat[:-1]))
323 324 except IOError: pass
324 325 for pat in self.ignorelist:
325 326 if pat.search(f): return True
326 327 return False
327 328
328 329 def lookup(self, key):
329 330 if self.tags is None:
330 331 self.tags = {}
331 332 try:
332 333 fl = self.file(".hgtags")
333 334 for l in fl.revision(fl.tip()).splitlines():
334 335 if l:
335 336 n, k = l.split(" ")
336 337 self.tags[k] = bin(n)
337 338 except KeyError: pass
338 339 try:
339 340 return self.tags[key]
340 341 except KeyError:
341 342 return self.changelog.lookup(key)
342 343
343 344 def join(self, f):
344 345 return os.path.join(self.path, f)
345 346
346 347 def file(self, f):
347 348 if f[0] == '/': f = f[1:]
348 349 return filelog(self.opener, f)
349 350
350 351 def transaction(self):
351 352 return transaction(self.opener, self.join("journal"),
352 353 self.join("undo"))
353 354
354 355 def recover(self):
355 356 lock = self.lock()
356 357 if os.path.exists(self.join("recover")):
357 358 self.ui.status("attempting to rollback interrupted transaction\n")
358 359 return rollback(self.opener, self.join("recover"))
359 360 else:
360 361 self.ui.warn("no interrupted transaction available\n")
361 362
362 363 def undo(self):
363 364 lock = self.lock()
364 365 if os.path.exists(self.join("undo")):
365 366 f = self.changelog.read(self.changelog.tip())[3]
366 367 self.ui.status("attempting to rollback last transaction\n")
367 368 rollback(self.opener, self.join("undo"))
368 369 self.manifest = manifest(self.opener)
369 370 self.changelog = changelog(self.opener)
370 371
371 372 self.ui.status("discarding dirstate\n")
372 373 node = self.changelog.tip()
373 374 f.sort()
374 375
375 376 self.dirstate.setparents(node)
376 377 self.dirstate.update(f, 'i')
377 378
378 379 else:
379 380 self.ui.warn("no undo information available\n")
380 381
381 382 def lock(self, wait = 1):
382 383 try:
383 384 return lock.lock(self.join("lock"), 0)
384 385 except lock.LockHeld, inst:
385 386 if wait:
386 387 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
387 388 return lock.lock(self.join("lock"), wait)
388 389 raise inst
389 390
390 391 def rawcommit(self, files, text, user, date, p1=None, p2=None):
391 392 p1 = p1 or self.dirstate.parents()[0] or nullid
392 393 p2 = p2 or self.dirstate.parents()[1] or nullid
393 394 pchange = self.changelog.read(p1)
394 395 pmmap = self.manifest.read(pchange[0])
395 396 tr = self.transaction()
396 397 mmap = {}
397 398 linkrev = self.changelog.count()
398 399 for f in files:
399 400 try:
400 401 t = file(f).read()
401 402 except IOError:
402 403 self.ui.warn("Read file %s error, skipped\n" % f)
403 404 continue
404 405 r = self.file(f)
405 406 # FIXME - need to find both parents properly
406 407 prev = pmmap.get(f, nullid)
407 408 mmap[f] = r.add(t, tr, linkrev, prev)
408 409
409 410 mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
410 411 n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
411 412 tr.close()
412 413 self.dirstate.setparents(p1, p2)
413 414 self.dirstate.clear()
414 415 self.dirstate.update(mmap.keys(), "n")
415 416
416 417 def commit(self, files = None, text = ""):
417 418 commit = []
418 419 remove = []
419 420 if files:
420 421 for f in files:
421 422 s = self.dirstate.state(f)
422 423 if s in 'cai':
423 424 commit.append(f)
424 425 elif s == 'r':
425 426 remove.append(f)
426 427 else:
427 428 self.warn("%s not tracked!\n")
428 429 else:
429 430 (c, a, d, u) = self.diffdir(self.root)
430 431 commit = c + a
431 432 remove = d
432 433
433 434 if not commit and not remove:
434 435 self.ui.status("nothing changed\n")
435 436 return
436 437
437 438 p1, p2 = self.dirstate.parents()
438 439 c1 = self.changelog.read(p1)
439 440 c2 = self.changelog.read(p2)
440 441 m1 = self.manifest.read(c1[0])
441 442 m2 = self.manifest.read(c2[0])
442 443 lock = self.lock()
443 444 tr = self.transaction()
444 445
445 446 # check in files
446 447 new = {}
447 448 linkrev = self.changelog.count()
448 449 commit.sort()
449 450 for f in commit:
450 451 self.ui.note(f + "\n")
451 452 try:
452 453 t = file(f).read()
453 454 except IOError:
454 455 self.warn("trouble committing %s!\n" % f)
455 456 raise
456 457
457 458 r = self.file(f)
458 459 fp1 = m1.get(f, nullid)
459 460 fp2 = m2.get(f, nullid)
460 461 new[f] = r.add(t, tr, linkrev, fp1, fp2)
461 462
462 463 # update manifest
463 464 m1.update(new)
464 465 for f in remove: del m1[f]
465 466 mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
466 467
467 468 # add changeset
468 469 new = new.keys()
469 470 new.sort()
470 471
471 472 edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
472 473 edittext += "".join(["HG: changed %s\n" % f for f in new])
473 474 edittext += "".join(["HG: removed %s\n" % f for f in remove])
474 475 edittext = self.ui.edit(edittext)
475 476
476 477 n = self.changelog.add(mn, new, edittext, tr, p1, p2)
477 478 tr.close()
478 479
479 480 self.dirstate.setparents(n)
480 481 self.dirstate.update(new, "n")
481 482 self.dirstate.forget(remove)
482 483
483 484 def checkout(self, node):
484 485 # checkout is really dumb at the moment
485 486 # it ought to basically merge
486 487 change = self.changelog.read(node)
487 488 l = self.manifest.read(change[0]).items()
488 489 l.sort()
489 490
490 491 for f,n in l:
491 492 if f[0] == "/": continue
492 493 self.ui.note(f, "\n")
493 494 t = self.file(f).revision(n)
494 495 try:
495 496 file(f, "w").write(t)
496 497 except IOError:
497 498 os.makedirs(os.path.dirname(f))
498 499 file(f, "w").write(t)
499 500
500 501 self.dirstate.setparents(node)
501 502 self.dirstate.clear()
502 503 self.dirstate.update([f for f,n in l], "n")
503 504
504 505 def diffdir(self, path, changeset = None):
505 506 changed = []
506 507 added = []
507 508 unknown = []
508 509 mf = {}
509 510
510 511 if changeset:
511 512 change = self.changelog.read(changeset)
512 513 mf = self.manifest.read(change[0])
513 514 dc = dict.fromkeys(mf)
514 515 else:
515 516 changeset = self.dirstate.parents()[0]
516 517 change = self.changelog.read(changeset)
517 518 mf = self.manifest.read(change[0])
518 519 dc = self.dirstate.copy()
519 520
520 521 def fcmp(fn):
521 522 t1 = file(os.path.join(self.root, fn)).read()
522 523 t2 = self.file(fn).revision(mf[fn])
523 524 return cmp(t1, t2)
524 525
525 526 for dir, subdirs, files in os.walk(self.root):
526 527 d = dir[len(self.root)+1:]
527 528 if ".hg" in subdirs: subdirs.remove(".hg")
528 529
529 530 for f in files:
530 531 fn = os.path.join(d, f)
531 532 try: s = os.stat(os.path.join(self.root, fn))
532 533 except: continue
533 534 if fn in dc:
534 535 c = dc[fn]
535 536 del dc[fn]
536 537 if not c:
537 538 if fcmp(fn):
538 539 changed.append(fn)
539 540 elif c[0] == 'i':
540 541 if fn not in mf:
541 542 added.append(fn)
542 543 elif fcmp(fn):
543 544 changed.append(fn)
545 elif c[0] == 'm':
546 changed.append(fn)
544 547 elif c[0] == 'a':
545 548 added.append(fn)
546 549 elif c[0] == 'r':
547 550 unknown.append(fn)
548 551 elif c[2] != s.st_size:
549 552 changed.append(fn)
550 553 elif c[1] != s.st_mode or c[3] != s.st_mtime:
551 554 if fcmp(fn):
552 555 changed.append(fn)
553 556 else:
554 557 if self.ignore(fn): continue
555 558 unknown.append(fn)
556 559
557 560 deleted = dc.keys()
558 561 deleted.sort()
559 562
560 563 return (changed, added, deleted, unknown)
561 564
562 565 def diffrevs(self, node1, node2):
563 566 changed, added = [], []
564 567
565 568 change = self.changelog.read(node1)
566 569 mf1 = self.manifest.read(change[0])
567 570 change = self.changelog.read(node2)
568 571 mf2 = self.manifest.read(change[0])
569 572
570 573 for fn in mf2:
571 574 if mf1.has_key(fn):
572 575 if mf1[fn] != mf2[fn]:
573 576 changed.append(fn)
574 577 del mf1[fn]
575 578 else:
576 579 added.append(fn)
577 580
578 581 deleted = mf1.keys()
579 582 deleted.sort()
580 583
581 584 return (changed, added, deleted)
582 585
583 586 def add(self, list):
584 587 for f in list:
585 588 p = os.path.join(self.root, f)
586 589 if not os.path.isfile(p):
587 590 self.ui.warn("%s does not exist!\n" % f)
588 591 elif self.dirstate.state(f) == 'n':
589 592 self.ui.warn("%s already tracked!\n" % f)
590 593 else:
591 594 self.dirstate.update([f], "a")
592 595
593 596 def forget(self, list):
594 597 for f in list:
595 598 if self.dirstate.state(f) not in 'ai':
596 599 self.ui.warn("%s not added!\n" % f)
597 600 else:
598 601 self.dirstate.forget([f])
599 602
600 603 def remove(self, list):
601 604 for f in list:
602 605 p = os.path.join(self.root, f)
603 606 if os.path.isfile(p):
604 607 self.ui.warn("%s still exists!\n" % f)
605 608 elif f not in self.dirstate:
606 609 self.ui.warn("%s not tracked!\n" % f)
607 610 else:
608 611 self.dirstate.update([f], "r")
609 612
610 613 def heads(self):
611 614 return self.changelog.heads()
612 615
613 616 def branches(self, nodes):
614 617 if not nodes: nodes = [self.changelog.tip()]
615 618 b = []
616 619 for n in nodes:
617 620 t = n
618 621 while n:
619 622 p = self.changelog.parents(n)
620 623 if p[1] != nullid or p[0] == nullid:
621 624 b.append((t, n, p[0], p[1]))
622 625 break
623 626 n = p[0]
624 627 return b
625 628
626 629 def between(self, pairs):
627 630 r = []
628 631
629 632 for top, bottom in pairs:
630 633 n, l, i = top, [], 0
631 634 f = 1
632 635
633 636 while n != bottom:
634 637 p = self.changelog.parents(n)[0]
635 638 if i == f:
636 639 l.append(n)
637 640 f = f * 2
638 641 n = p
639 642 i += 1
640 643
641 644 r.append(l)
642 645
643 646 return r
644 647
645 648 def newer(self, nodes):
646 649 m = {}
647 650 nl = []
648 651 pm = {}
649 652 cl = self.changelog
650 653 t = l = cl.count()
651 654
652 655 # find the lowest numbered node
653 656 for n in nodes:
654 657 l = min(l, cl.rev(n))
655 658 m[n] = 1
656 659
657 660 for i in xrange(l, t):
658 661 n = cl.node(i)
659 662 if n in m: # explicitly listed
660 663 pm[n] = 1
661 664 nl.append(n)
662 665 continue
663 666 for p in cl.parents(n):
664 667 if p in pm: # parent listed
665 668 pm[n] = 1
666 669 nl.append(n)
667 670 break
668 671
669 672 return nl
670 673
671 674 def getchangegroup(self, remote):
672 675 m = self.changelog.nodemap
673 676 search = []
674 677 fetch = []
675 678 seen = {}
676 679 seenbranch = {}
677 680
678 681 # if we have an empty repo, fetch everything
679 682 if self.changelog.tip() == nullid:
680 683 self.ui.status("requesting all changes\n")
681 684 return remote.changegroup([nullid])
682 685
683 686 # otherwise, assume we're closer to the tip than the root
684 687 self.ui.status("searching for changes\n")
685 688 heads = remote.heads()
686 689 unknown = []
687 690 for h in heads:
688 691 if h not in m:
689 692 unknown.append(h)
690 693
691 694 if not unknown:
692 695 self.ui.status("nothing to do!\n")
693 696 return None
694 697
695 698 unknown = remote.branches(unknown)
696 699 while unknown:
697 700 n = unknown.pop(0)
698 701 seen[n[0]] = 1
699 702
700 703 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
701 704 if n == nullid: break
702 705 if n in seenbranch:
703 706 self.ui.debug("branch already found\n")
704 707 continue
705 708 if n[1] and n[1] in m: # do we know the base?
706 709 self.ui.debug("found incomplete branch %s:%s\n"
707 710 % (short(n[0]), short(n[1])))
708 711 search.append(n) # schedule branch range for scanning
709 712 seenbranch[n] = 1
710 713 else:
711 714 if n[2] in m and n[3] in m:
712 715 if n[1] not in fetch:
713 716 self.ui.debug("found new changeset %s\n" %
714 717 short(n[1]))
715 718 fetch.append(n[1]) # earliest unknown
716 719 continue
717 720
718 721 r = []
719 722 for a in n[2:4]:
720 723 if a not in seen: r.append(a)
721 724
722 725 if r:
723 726 self.ui.debug("requesting %s\n" %
724 727 " ".join(map(short, r)))
725 728 for b in remote.branches(r):
726 729 self.ui.debug("received %s:%s\n" %
727 730 (short(b[0]), short(b[1])))
728 731 if b[0] not in m and b[0] not in seen:
729 732 unknown.append(b)
730 733
731 734 while search:
732 735 n = search.pop(0)
733 736 l = remote.between([(n[0], n[1])])[0]
734 737 p = n[0]
735 738 f = 1
736 739 for i in l + [n[1]]:
737 740 if i in m:
738 741 if f <= 2:
739 742 self.ui.debug("found new branch changeset %s\n" %
740 743 short(p))
741 744 fetch.append(p)
742 745 else:
743 746 self.ui.debug("narrowed branch search to %s:%s\n"
744 747 % (short(p), short(i)))
745 748 search.append((p, i))
746 749 break
747 750 p, f = i, f * 2
748 751
749 752 for f in fetch:
750 753 if f in m:
751 754 raise "already have", short(f[:4])
752 755
753 756 self.ui.note("adding new changesets starting at " +
754 757 " ".join([short(f) for f in fetch]) + "\n")
755 758
756 759 return remote.changegroup(fetch)
757 760
758 761 def changegroup(self, basenodes):
759 762 nodes = self.newer(basenodes)
760 763
761 764 # construct the link map
762 765 linkmap = {}
763 766 for n in nodes:
764 767 linkmap[self.changelog.rev(n)] = n
765 768
766 769 # construct a list of all changed files
767 770 changed = {}
768 771 for n in nodes:
769 772 c = self.changelog.read(n)
770 773 for f in c[3]:
771 774 changed[f] = 1
772 775 changed = changed.keys()
773 776 changed.sort()
774 777
775 778 # the changegroup is changesets + manifests + all file revs
776 779 revs = [ self.changelog.rev(n) for n in nodes ]
777 780
778 781 for y in self.changelog.group(linkmap): yield y
779 782 for y in self.manifest.group(linkmap): yield y
780 783 for f in changed:
781 784 yield struct.pack(">l", len(f) + 4) + f
782 785 g = self.file(f).group(linkmap)
783 786 for y in g:
784 787 yield y
785 788
786 789 def addchangegroup(self, generator):
787 790
788 791 class genread:
789 792 def __init__(self, generator):
790 793 self.g = generator
791 794 self.buf = ""
792 795 def read(self, l):
793 796 while l > len(self.buf):
794 797 try:
795 798 self.buf += self.g.next()
796 799 except StopIteration:
797 800 break
798 801 d, self.buf = self.buf[:l], self.buf[l:]
799 802 return d
800 803
801 804 def getchunk():
802 805 d = source.read(4)
803 806 if not d: return ""
804 807 l = struct.unpack(">l", d)[0]
805 808 if l <= 4: return ""
806 809 return source.read(l - 4)
807 810
808 811 def getgroup():
809 812 while 1:
810 813 c = getchunk()
811 814 if not c: break
812 815 yield c
813 816
814 817 def csmap(x):
815 818 self.ui.debug("add changeset %s\n" % short(x))
816 819 return self.changelog.count()
817 820
818 821 def revmap(x):
819 822 return self.changelog.rev(x)
820 823
821 824 if not generator: return
822 825 changesets = files = revisions = 0
823 826
824 827 source = genread(generator)
825 828 lock = self.lock()
826 829 tr = self.transaction()
827 830
828 831 # pull off the changeset group
829 832 self.ui.status("adding changesets\n")
830 833 co = self.changelog.tip()
831 834 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
832 835 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
833 836
834 837 # pull off the manifest group
835 838 self.ui.status("adding manifests\n")
836 839 mm = self.manifest.tip()
837 840 mo = self.manifest.addgroup(getgroup(), revmap, tr)
838 841
839 842 # process the files
840 843 self.ui.status("adding file revisions\n")
841 844 while 1:
842 845 f = getchunk()
843 846 if not f: break
844 847 self.ui.debug("adding %s revisions\n" % f)
845 848 fl = self.file(f)
846 849 o = fl.tip()
847 850 n = fl.addgroup(getgroup(), revmap, tr)
848 851 revisions += fl.rev(n) - fl.rev(o)
849 852 files += 1
850 853
851 854 self.ui.status(("modified %d files, added %d changesets" +
852 855 " and %d new revisions\n")
853 856 % (files, changesets, revisions))
854 857
855 858 tr.close()
856 859 return
857 860
858 861 def merge(self, generator):
859 862 changesets = files = revisions = 0
860 863
861 864 self.lock()
862 865 class genread:
863 866 def __init__(self, generator):
864 867 self.g = generator
865 868 self.buf = ""
866 869 def read(self, l):
867 870 while l > len(self.buf):
868 871 try:
869 872 self.buf += self.g.next()
870 873 except StopIteration:
871 874 break
872 875 d, self.buf = self.buf[:l], self.buf[l:]
873 876 return d
874 877
875 878 if not generator: return
876 879 source = genread(generator)
877 880
878 881 def getchunk():
879 882 d = source.read(4)
880 883 if not d: return ""
881 884 l = struct.unpack(">l", d)[0]
882 885 if l <= 4: return ""
883 886 return source.read(l - 4)
884 887
885 888 def getgroup():
886 889 while 1:
887 890 c = getchunk()
888 891 if not c: break
889 892 yield c
890 893
891 894 tr = self.transaction()
892 895 simple = True
893 896 need = {}
894 897
895 898 self.ui.status("adding changesets\n")
896 899 # pull off the changeset group
897 900 def report(x):
898 901 self.ui.debug("add changeset %s\n" % short(x))
899 902 return self.changelog.count()
900 903
901 904 co = self.changelog.tip()
902 905 cn = self.changelog.addgroup(getgroup(), report, tr)
903 906
904 907 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
905 908
906 909 self.ui.status("adding manifests\n")
907 910 # pull off the manifest group
908 911 mm = self.manifest.tip()
909 912 mo = self.manifest.addgroup(getgroup(),
910 913 lambda x: self.changelog.rev(x), tr)
911 914
912 915 # do we need a resolve?
913 916 if self.changelog.ancestor(co, cn) != co:
914 917 simple = False
915 918 resolverev = self.changelog.count()
916 919
917 920 # resolve the manifest to determine which files
918 921 # we care about merging
919 922 self.ui.status("resolving manifests\n")
920 923 ma = self.manifest.ancestor(mm, mo)
921 924 omap = self.manifest.read(mo) # other
922 925 amap = self.manifest.read(ma) # ancestor
923 926 mmap = self.manifest.read(mm) # mine
924 927 nmap = {}
925 928
926 929 self.ui.debug(" ancestor %s local %s remote %s\n" %
927 930 (short(ma), short(mm), short(mo)))
928 931
929 932 for f, mid in mmap.iteritems():
930 933 if f in omap:
931 934 if mid != omap[f]:
932 935 self.ui.debug(" %s versions differ, do resolve\n" % f)
933 936 need[f] = mid # use merged version or local version
934 937 else:
935 938 nmap[f] = mid # keep ours
936 939 del omap[f]
937 940 elif f in amap:
938 941 if mid != amap[f]:
939 942 r = self.ui.prompt(
940 943 (" local changed %s which remote deleted\n" % f) +
941 944 "(k)eep or (d)elete?", "[kd]", "k")
942 945 if r == "k": nmap[f] = mid
943 946 else:
944 947 self.ui.debug("other deleted %s\n" % f)
945 948 pass # other deleted it
946 949 else:
947 950 self.ui.debug("local created %s\n" %f)
948 951 nmap[f] = mid # we created it
949 952
950 953 del mmap
951 954
952 955 for f, oid in omap.iteritems():
953 956 if f in amap:
954 957 if oid != amap[f]:
955 958 r = self.ui.prompt(
956 959 ("remote changed %s which local deleted\n" % f) +
957 960 "(k)eep or (d)elete?", "[kd]", "k")
958 961 if r == "k": nmap[f] = oid
959 962 else:
960 963 pass # probably safe
961 964 else:
962 965 self.ui.debug("remote created %s, do resolve\n" % f)
963 966 need[f] = oid
964 967
965 968 del omap
966 969 del amap
967 970
968 971 new = need.keys()
969 972 new.sort()
970 973
971 974 # process the files
972 975 self.ui.status("adding files\n")
973 976 while 1:
974 977 f = getchunk()
975 978 if not f: break
976 979 self.ui.debug("adding %s revisions\n" % f)
977 980 fl = self.file(f)
978 981 o = fl.tip()
979 982 n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr)
980 983 revisions += fl.rev(n) - fl.rev(o)
981 984 files += 1
982 985 if f in need:
983 986 del need[f]
984 987 # manifest resolve determined we need to merge the tips
985 988 nmap[f] = self.merge3(fl, f, o, n, tr, resolverev)
986 989
987 990 if need:
988 991 # we need to do trivial merges on local files
989 992 for f in new:
990 993 if f not in need: continue
991 994 fl = self.file(f)
992 995 nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev)
993 996 revisions += 1
994 997
995 998 # For simple merges, we don't need to resolve manifests or changesets
996 999 if simple:
997 1000 self.ui.debug("simple merge, skipping resolve\n")
998 1001 self.ui.status(("modified %d files, added %d changesets" +
999 1002 " and %d new revisions\n")
1000 1003 % (files, changesets, revisions))
1001 1004 tr.close()
1002 1005 return
1003 1006
1004 1007 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
1005 1008 revisions += 1
1006 1009
1007 1010 # Now all files and manifests are merged, we add the changed files
1008 1011 # and manifest id to the changelog
1009 1012 self.ui.status("committing merge changeset\n")
1010 1013 if co == cn: cn = -1
1011 1014
1012 1015 edittext = "\nHG: merge resolve\n" + \
1013 1016 "HG: manifest hash %s\n" % hex(node) + \
1014 1017 "".join(["HG: changed %s\n" % f for f in new])
1015 1018 edittext = self.ui.edit(edittext)
1016 1019 n = self.changelog.add(node, new, edittext, tr, co, cn)
1017 1020 revisions += 1
1018 1021
1019 1022 self.ui.status("added %d changesets, %d files, and %d new revisions\n"
1020 1023 % (changesets, files, revisions))
1021 1024
1022 1025 tr.close()
1023 1026
1024 1027 def merge3(self, fl, fn, my, other, transaction, link):
1025 1028 """perform a 3-way merge and append the result"""
1026 1029
1027 1030 def temp(prefix, node):
1028 1031 pre = "%s~%s." % (os.path.basename(fn), prefix)
1029 1032 (fd, name) = tempfile.mkstemp("", pre)
1030 1033 f = os.fdopen(fd, "w")
1031 1034 f.write(fl.revision(node))
1032 1035 f.close()
1033 1036 return name
1034 1037
1035 1038 base = fl.ancestor(my, other)
1036 1039 self.ui.note("resolving %s\n" % fn)
1037 1040 self.ui.debug("local %s remote %s ancestor %s\n" %
1038 1041 (short(my), short(other), short(base)))
1039 1042
1040 1043 if my == base:
1041 1044 text = fl.revision(other)
1042 1045 else:
1043 1046 a = temp("local", my)
1044 1047 b = temp("remote", other)
1045 1048 c = temp("parent", base)
1046 1049
1047 1050 cmd = os.environ["HGMERGE"]
1048 1051 self.ui.debug("invoking merge with %s\n" % cmd)
1049 1052 r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
1050 1053 if r:
1051 1054 raise "Merge failed!"
1052 1055
1053 1056 text = open(a).read()
1054 1057 os.unlink(a)
1055 1058 os.unlink(b)
1056 1059 os.unlink(c)
1057 1060
1058 1061 return fl.add(text, transaction, link, my, other)
1059 1062
1060 1063 class remoterepository:
1061 1064 def __init__(self, ui, path):
1062 1065 self.url = path
1063 1066 self.ui = ui
1064 1067
1065 1068 def do_cmd(self, cmd, **args):
1066 1069 self.ui.debug("sending %s command\n" % cmd)
1067 1070 q = {"cmd": cmd}
1068 1071 q.update(args)
1069 1072 qs = urllib.urlencode(q)
1070 1073 cu = "%s?%s" % (self.url, qs)
1071 1074 return urllib.urlopen(cu)
1072 1075
1073 1076 def heads(self):
1074 1077 d = self.do_cmd("heads").read()
1075 1078 try:
1076 1079 return map(bin, d[:-1].split(" "))
1077 1080 except:
1078 1081 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1079 1082 raise
1080 1083
1081 1084 def branches(self, nodes):
1082 1085 n = " ".join(map(hex, nodes))
1083 1086 d = self.do_cmd("branches", nodes=n).read()
1084 1087 try:
1085 1088 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1086 1089 return br
1087 1090 except:
1088 1091 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1089 1092 raise
1090 1093
1091 1094 def between(self, pairs):
1092 1095 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1093 1096 d = self.do_cmd("between", pairs=n).read()
1094 1097 try:
1095 1098 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1096 1099 return p
1097 1100 except:
1098 1101 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1099 1102 raise
1100 1103
1101 1104 def changegroup(self, nodes):
1102 1105 n = " ".join(map(hex, nodes))
1103 1106 zd = zlib.decompressobj()
1104 1107 f = self.do_cmd("changegroup", roots=n)
1105 1108 bytes = 0
1106 1109 while 1:
1107 1110 d = f.read(4096)
1108 1111 bytes += len(d)
1109 1112 if not d:
1110 1113 yield zd.flush()
1111 1114 break
1112 1115 yield zd.decompress(d)
1113 1116 self.ui.note("%d bytes of data transfered\n" % bytes)
1114 1117
1115 1118 def repository(ui, path=None, create=0):
1116 1119 if path and path[:7] == "http://":
1117 1120 return remoterepository(ui, path)
1118 1121 if path and path[:5] == "hg://":
1119 1122 return remoterepository(ui, path.replace("hg://", "http://"))
1120 1123 if path and path[:11] == "old-http://":
1121 1124 return localrepository(ui, path.replace("old-http://", "http://"))
1122 1125 else:
1123 1126 return localrepository(ui, path, create)
1124 1127
1125 1128 class httprangereader:
1126 1129 def __init__(self, url):
1127 1130 self.url = url
1128 1131 self.pos = 0
1129 1132 def seek(self, pos):
1130 1133 self.pos = pos
1131 1134 def read(self, bytes=None):
1132 1135 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
1133 1136 urllib2.install_opener(opener)
1134 1137 req = urllib2.Request(self.url)
1135 1138 end = ''
1136 1139 if bytes: end = self.pos + bytes
1137 1140 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
1138 1141 f = urllib2.urlopen(req)
1139 1142 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now