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