##// END OF EJS Templates
Performance enhancements for manifest.add()...
mason@suse.com -
r644:6ebe1182 default
parent child Browse files
Show More
@@ -1,1744 +1,1844 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, os
9 9 import util
10 10 from revlog import *
11 11 from demandload import *
12 12 demandload(globals(), "re lock urllib urllib2 transaction time socket")
13 13 demandload(globals(), "tempfile httprangereader bdiff")
14 demandload(globals(), "bisect")
14 15
15 16 class filelog(revlog):
16 17 def __init__(self, opener, path):
17 18 revlog.__init__(self, opener,
18 19 os.path.join("data", path + ".i"),
19 20 os.path.join("data", path + ".d"))
20 21
21 22 def read(self, node):
22 23 t = self.revision(node)
23 24 if t[:2] != '\1\n':
24 25 return t
25 26 s = t.find('\1\n', 2)
26 27 return t[s+2:]
27 28
28 29 def readmeta(self, node):
29 30 t = self.revision(node)
30 31 if t[:2] != '\1\n':
31 32 return t
32 33 s = t.find('\1\n', 2)
33 34 mt = t[2:s]
34 35 for l in mt.splitlines():
35 36 k, v = l.split(": ", 1)
36 37 m[k] = v
37 38 return m
38 39
39 40 def add(self, text, meta, transaction, link, p1=None, p2=None):
40 41 if meta or text[:2] == '\1\n':
41 42 mt = ""
42 43 if meta:
43 44 mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ]
44 45 text = "\1\n" + "".join(mt) + "\1\n" + text
45 46 return self.addrevision(text, transaction, link, p1, p2)
46 47
47 48 def annotate(self, node):
48 49
49 50 def decorate(text, rev):
50 51 return ([rev] * len(text.splitlines()), text)
51 52
52 53 def pair(parent, child):
53 54 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
54 55 child[0][b1:b2] = parent[0][a1:a2]
55 56 return child
56 57
57 58 # find all ancestors
58 59 needed = {node:1}
59 60 visit = [node]
60 61 while visit:
61 62 n = visit.pop(0)
62 63 for p in self.parents(n):
63 64 if p not in needed:
64 65 needed[p] = 1
65 66 visit.append(p)
66 67 else:
67 68 # count how many times we'll use this
68 69 needed[p] += 1
69 70
70 71 # sort by revision which is a topological order
71 72 visit = [ (self.rev(n), n) for n in needed.keys() ]
72 73 visit.sort()
73 74 hist = {}
74 75
75 76 for r,n in visit:
76 77 curr = decorate(self.read(n), self.linkrev(n))
77 78 for p in self.parents(n):
78 79 if p != nullid:
79 80 curr = pair(hist[p], curr)
80 81 # trim the history of unneeded revs
81 82 needed[p] -= 1
82 83 if not needed[p]:
83 84 del hist[p]
84 85 hist[n] = curr
85 86
86 87 return zip(hist[n][0], hist[n][1].splitlines(1))
87 88
88 89 class manifest(revlog):
89 90 def __init__(self, opener):
90 91 self.mapcache = None
91 92 self.listcache = None
92 93 self.addlist = None
93 94 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
94 95
95 96 def read(self, node):
96 97 if node == nullid: return {} # don't upset local cache
97 98 if self.mapcache and self.mapcache[0] == node:
98 99 return self.mapcache[1]
99 100 text = self.revision(node)
100 101 map = {}
101 102 flag = {}
102 103 self.listcache = (text, text.splitlines(1))
103 104 for l in self.listcache[1]:
104 105 (f, n) = l.split('\0')
105 106 map[f] = bin(n[:40])
106 107 flag[f] = (n[40:-1] == "x")
107 108 self.mapcache = (node, map, flag)
108 109 return map
109 110
110 111 def readflags(self, node):
111 112 if node == nullid: return {} # don't upset local cache
112 113 if not self.mapcache or self.mapcache[0] != node:
113 114 self.read(node)
114 115 return self.mapcache[2]
115 116
116 117 def diff(self, a, b):
117 118 # this is sneaky, as we're not actually using a and b
118 119 if self.listcache and self.addlist and self.listcache[0] == a:
119 120 d = mdiff.diff(self.listcache[1], self.addlist, 1)
120 121 if mdiff.patch(a, d) != b:
121 122 sys.stderr.write("*** sortdiff failed, falling back ***\n")
122 123 return mdiff.textdiff(a, b)
123 124 return d
124 125 else:
125 126 return mdiff.textdiff(a, b)
126 127
127 def add(self, map, flags, transaction, link, p1=None, p2=None):
128 files = map.keys()
129 files.sort()
128 def add(self, map, flags, transaction, link, p1=None, p2=None,changed=None):
129 # directly generate the mdiff delta from the data collected during
130 # the bisect loop below
131 def gendelta(delta):
132 i = 0
133 result = []
134 while i < len(delta):
135 start = delta[i][2]
136 end = delta[i][3]
137 l = delta[i][4]
138 if l == None:
139 l = ""
140 while i < len(delta) - 1 and start <= delta[i+1][2] and end >= delta[i+1][2]:
141 if delta[i+1][3] > end:
142 end = delta[i+1][3]
143 if delta[i+1][4]:
144 l += delta[i+1][4]
145 i += 1
146 result.append(struct.pack(">lll", start, end, len(l)) + l)
147 i += 1
148 return result
149
150 # apply the changes collected during the bisect loop to our addlist
151 def addlistdelta(addlist, delta):
152 # apply the deltas to the addlist. start from the bottom up
153 # so changes to the offsets don't mess things up.
154 i = len(delta)
155 while i > 0:
156 i -= 1
157 start = delta[i][0]
158 end = delta[i][1]
159 if delta[i][4]:
160 addlist[start:end] = [delta[i][4]]
161 else:
162 del addlist[start:end]
163 return addlist
164
165 # calculate the byte offset of the start of each line in the
166 # manifest
167 def calcoffsets(addlist):
168 offsets = [0] * (len(addlist) + 1)
169 offset = 0
170 i = 0
171 while i < len(addlist):
172 offsets[i] = offset
173 offset += len(addlist[i])
174 i += 1
175 offsets[i] = offset
176 return offsets
130 177
131 self.addlist = ["%s\000%s%s\n" %
132 (f, hex(map[f]), flags[f] and "x" or '')
133 for f in files]
178 # if we're using the listcache, make sure it is valid and
179 # parented by the same node we're diffing against
180 if not changed or not self.listcache or not p1 or self.mapcache[0] != p1:
181 files = map.keys()
182 files.sort()
183
184 self.addlist = ["%s\000%s%s\n" %
185 (f, hex(map[f]), flags[f] and "x" or '')
186 for f in files]
187 cachedelta = None
188 else:
189 addlist = self.listcache[1]
190
191 # find the starting offset for each line in the add list
192 offsets = calcoffsets(addlist)
193
194 # combine the changed lists into one list for sorting
195 work = [[x, 0] for x in changed[0]]
196 work[len(work):] = [[x, 1] for x in changed[1]]
197 work.sort()
198
199 delta = []
200 bs = 0
201
202 for w in work:
203 f = w[0]
204 # bs will either be the index of the item or the insertion point
205 bs = bisect.bisect(addlist, f, bs)
206 if bs < len(addlist):
207 fn = addlist[bs][:addlist[bs].index('\0')]
208 else:
209 fn = None
210 if w[1] == 0:
211 l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '')
212 else:
213 l = None
214 start = bs
215 if fn != f:
216 # item not found, insert a new one
217 end = bs
218 if w[1] == 1:
219 sys.stderr.write("failed to remove %s from manifest" % f)
220 sys.exit(1)
221 else:
222 # item is found, replace/delete the existing line
223 end = bs + 1
224 delta.append([start, end, offsets[start], offsets[end], l])
225
226 self.addlist = addlistdelta(addlist, delta)
227 if self.mapcache[0] == self.tip():
228 cachedelta = "".join(gendelta(delta))
229 else:
230 cachedelta = None
231
134 232 text = "".join(self.addlist)
135
136 n = self.addrevision(text, transaction, link, p1, p2)
233 if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text:
234 sys.stderr.write("manifest delta failure")
235 sys.exit(1)
236 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
137 237 self.mapcache = (n, map, flags)
138 238 self.listcache = (text, self.addlist)
139 239 self.addlist = None
140 240
141 241 return n
142 242
143 243 class changelog(revlog):
144 244 def __init__(self, opener):
145 245 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
146 246
147 247 def extract(self, text):
148 248 if not text:
149 249 return (nullid, "", "0", [], "")
150 250 last = text.index("\n\n")
151 251 desc = text[last + 2:]
152 252 l = text[:last].splitlines()
153 253 manifest = bin(l[0])
154 254 user = l[1]
155 255 date = l[2]
156 256 files = l[3:]
157 257 return (manifest, user, date, files, desc)
158 258
159 259 def read(self, node):
160 260 return self.extract(self.revision(node))
161 261
162 262 def add(self, manifest, list, desc, transaction, p1=None, p2=None,
163 263 user=None, date=None):
164 264 date = date or "%d %d" % (time.time(), time.timezone)
165 265 list.sort()
166 266 l = [hex(manifest), user, date] + list + ["", desc]
167 267 text = "\n".join(l)
168 268 return self.addrevision(text, transaction, self.count(), p1, p2)
169 269
170 270 class dirstate:
171 271 def __init__(self, opener, ui, root):
172 272 self.opener = opener
173 273 self.root = root
174 274 self.dirty = 0
175 275 self.ui = ui
176 276 self.map = None
177 277 self.pl = None
178 278 self.copies = {}
179 279
180 280 def __del__(self):
181 281 if self.dirty:
182 282 self.write()
183 283
184 284 def __getitem__(self, key):
185 285 try:
186 286 return self.map[key]
187 287 except TypeError:
188 288 self.read()
189 289 return self[key]
190 290
191 291 def __contains__(self, key):
192 292 if not self.map: self.read()
193 293 return key in self.map
194 294
195 295 def parents(self):
196 296 if not self.pl:
197 297 self.read()
198 298 return self.pl
199 299
200 300 def setparents(self, p1, p2 = nullid):
201 301 self.dirty = 1
202 302 self.pl = p1, p2
203 303
204 304 def state(self, key):
205 305 try:
206 306 return self[key][0]
207 307 except KeyError:
208 308 return "?"
209 309
210 310 def read(self):
211 311 if self.map is not None: return self.map
212 312
213 313 self.map = {}
214 314 self.pl = [nullid, nullid]
215 315 try:
216 316 st = self.opener("dirstate").read()
217 317 if not st: return
218 318 except: return
219 319
220 320 self.pl = [st[:20], st[20: 40]]
221 321
222 322 pos = 40
223 323 while pos < len(st):
224 324 e = struct.unpack(">cllll", st[pos:pos+17])
225 325 l = e[4]
226 326 pos += 17
227 327 f = st[pos:pos + l]
228 328 if '\0' in f:
229 329 f, c = f.split('\0')
230 330 self.copies[f] = c
231 331 self.map[f] = e[:4]
232 332 pos += l
233 333
234 334 def copy(self, source, dest):
235 335 self.read()
236 336 self.dirty = 1
237 337 self.copies[dest] = source
238 338
239 339 def copied(self, file):
240 340 return self.copies.get(file, None)
241 341
242 342 def update(self, files, state):
243 343 ''' current states:
244 344 n normal
245 345 m needs merging
246 346 r marked for removal
247 347 a marked for addition'''
248 348
249 349 if not files: return
250 350 self.read()
251 351 self.dirty = 1
252 352 for f in files:
253 353 if state == "r":
254 354 self.map[f] = ('r', 0, 0, 0)
255 355 else:
256 356 s = os.stat(os.path.join(self.root, f))
257 357 self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
258 358
259 359 def forget(self, files):
260 360 if not files: return
261 361 self.read()
262 362 self.dirty = 1
263 363 for f in files:
264 364 try:
265 365 del self.map[f]
266 366 except KeyError:
267 367 self.ui.warn("not in dirstate: %s!\n" % f)
268 368 pass
269 369
270 370 def clear(self):
271 371 self.map = {}
272 372 self.dirty = 1
273 373
274 374 def write(self):
275 375 st = self.opener("dirstate", "w")
276 376 st.write("".join(self.pl))
277 377 for f, e in self.map.items():
278 378 c = self.copied(f)
279 379 if c:
280 380 f = f + "\0" + c
281 381 e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
282 382 st.write(e + f)
283 383 self.dirty = 0
284 384
285 385 def changes(self, files, ignore):
286 386 self.read()
287 387 dc = self.map.copy()
288 388 lookup, changed, added, unknown = [], [], [], []
289 389
290 390 # compare all files by default
291 391 if not files: files = [self.root]
292 392
293 393 # recursive generator of all files listed
294 394 def walk(files):
295 395 for f in util.unique(files):
296 396 f = os.path.join(self.root, f)
297 397 if os.path.isdir(f):
298 398 for dir, subdirs, fl in os.walk(f):
299 399 d = dir[len(self.root) + 1:]
300 400 if ".hg" in subdirs: subdirs.remove(".hg")
301 401 for fn in fl:
302 402 fn = util.pconvert(os.path.join(d, fn))
303 403 yield fn
304 404 else:
305 405 yield f[len(self.root) + 1:]
306 406
307 407 for fn in util.unique(walk(files)):
308 408 try: s = os.stat(os.path.join(self.root, fn))
309 409 except: continue
310 410
311 411 if fn in dc:
312 412 c = dc[fn]
313 413 del dc[fn]
314 414
315 415 if c[0] == 'm':
316 416 changed.append(fn)
317 417 elif c[0] == 'a':
318 418 added.append(fn)
319 419 elif c[0] == 'r':
320 420 unknown.append(fn)
321 421 elif c[2] != s.st_size or (c[1] ^ s.st_mode) & 0100:
322 422 changed.append(fn)
323 423 elif c[1] != s.st_mode or c[3] != s.st_mtime:
324 424 lookup.append(fn)
325 425 else:
326 426 if not ignore(fn): unknown.append(fn)
327 427
328 428 return (lookup, changed, added, dc.keys(), unknown)
329 429
330 430 # used to avoid circular references so destructors work
331 431 def opener(base):
332 432 p = base
333 433 def o(path, mode="r"):
334 434 if p[:7] == "http://":
335 435 f = os.path.join(p, urllib.quote(path))
336 436 return httprangereader.httprangereader(f)
337 437
338 438 f = os.path.join(p, path)
339 439
340 440 mode += "b" # for that other OS
341 441
342 442 if mode[0] != "r":
343 443 try:
344 444 s = os.stat(f)
345 445 except OSError:
346 446 d = os.path.dirname(f)
347 447 if not os.path.isdir(d):
348 448 os.makedirs(d)
349 449 else:
350 450 if s.st_nlink > 1:
351 451 file(f + ".tmp", "wb").write(file(f, "rb").read())
352 452 util.rename(f+".tmp", f)
353 453
354 454 return file(f, mode)
355 455
356 456 return o
357 457
358 458 class RepoError(Exception): pass
359 459
360 460 class localrepository:
361 461 def __init__(self, ui, path=None, create=0):
362 462 self.remote = 0
363 463 if path and path[:7] == "http://":
364 464 self.remote = 1
365 465 self.path = path
366 466 else:
367 467 if not path:
368 468 p = os.getcwd()
369 469 while not os.path.isdir(os.path.join(p, ".hg")):
370 470 oldp = p
371 471 p = os.path.dirname(p)
372 472 if p == oldp: raise RepoError("no repo found")
373 473 path = p
374 474 self.path = os.path.join(path, ".hg")
375 475
376 476 if not create and not os.path.isdir(self.path):
377 477 raise RepoError("repository %s not found" % self.path)
378 478
379 479 self.root = path
380 480 self.ui = ui
381 481
382 482 if create:
383 483 os.mkdir(self.path)
384 484 os.mkdir(self.join("data"))
385 485
386 486 self.opener = opener(self.path)
387 487 self.wopener = opener(self.root)
388 488 self.manifest = manifest(self.opener)
389 489 self.changelog = changelog(self.opener)
390 490 self.ignorelist = None
391 491 self.tagscache = None
392 492 self.nodetagscache = None
393 493
394 494 if not self.remote:
395 495 self.dirstate = dirstate(self.opener, ui, self.root)
396 496 try:
397 497 self.ui.readconfig(self.opener("hgrc"))
398 498 except IOError: pass
399 499
400 500 def ignore(self, f):
401 501 if self.ignorelist is None:
402 502 self.ignorelist = []
403 503 try:
404 504 l = file(self.wjoin(".hgignore"))
405 505 for pat in l:
406 506 if pat != "\n":
407 507 self.ignorelist.append(re.compile(util.pconvert(pat[:-1])))
408 508 except IOError: pass
409 509 for pat in self.ignorelist:
410 510 if pat.search(f): return True
411 511 return False
412 512
413 513 def hook(self, name, **args):
414 514 s = self.ui.config("hooks", name)
415 515 if s:
416 516 self.ui.note("running hook %s: %s\n" % (name, s))
417 517 old = {}
418 518 for k, v in args.items():
419 519 k = k.upper()
420 520 old[k] = os.environ.get(k, None)
421 521 os.environ[k] = v
422 522
423 523 r = os.system(s)
424 524
425 525 for k, v in old.items():
426 526 if v != None:
427 527 os.environ[k] = v
428 528 else:
429 529 del os.environ[k]
430 530
431 531 if r:
432 532 self.ui.warn("abort: %s hook failed with status %d!\n" %
433 533 (name, r))
434 534 return False
435 535 return True
436 536
437 537 def tags(self):
438 538 '''return a mapping of tag to node'''
439 539 if not self.tagscache:
440 540 self.tagscache = {}
441 541 def addtag(self, k, n):
442 542 try:
443 543 bin_n = bin(n)
444 544 except TypeError:
445 545 bin_n = ''
446 546 self.tagscache[k.strip()] = bin_n
447 547
448 548 try:
449 549 # read each head of the tags file, ending with the tip
450 550 # and add each tag found to the map, with "newer" ones
451 551 # taking precedence
452 552 fl = self.file(".hgtags")
453 553 h = fl.heads()
454 554 h.reverse()
455 555 for r in h:
456 556 for l in fl.revision(r).splitlines():
457 557 if l:
458 558 n, k = l.split(" ", 1)
459 559 addtag(self, k, n)
460 560 except KeyError:
461 561 pass
462 562
463 563 try:
464 564 f = self.opener("localtags")
465 565 for l in f:
466 566 n, k = l.split(" ", 1)
467 567 addtag(self, k, n)
468 568 except IOError:
469 569 pass
470 570
471 571 self.tagscache['tip'] = self.changelog.tip()
472 572
473 573 return self.tagscache
474 574
475 575 def tagslist(self):
476 576 '''return a list of tags ordered by revision'''
477 577 l = []
478 578 for t, n in self.tags().items():
479 579 try:
480 580 r = self.changelog.rev(n)
481 581 except:
482 582 r = -2 # sort to the beginning of the list if unknown
483 583 l.append((r,t,n))
484 584 l.sort()
485 585 return [(t,n) for r,t,n in l]
486 586
487 587 def nodetags(self, node):
488 588 '''return the tags associated with a node'''
489 589 if not self.nodetagscache:
490 590 self.nodetagscache = {}
491 591 for t,n in self.tags().items():
492 592 self.nodetagscache.setdefault(n,[]).append(t)
493 593 return self.nodetagscache.get(node, [])
494 594
495 595 def lookup(self, key):
496 596 try:
497 597 return self.tags()[key]
498 598 except KeyError:
499 599 return self.changelog.lookup(key)
500 600
501 601 def dev(self):
502 602 if self.remote: return -1
503 603 return os.stat(self.path).st_dev
504 604
505 605 def join(self, f):
506 606 return os.path.join(self.path, f)
507 607
508 608 def wjoin(self, f):
509 609 return os.path.join(self.root, f)
510 610
511 611 def file(self, f):
512 612 if f[0] == '/': f = f[1:]
513 613 return filelog(self.opener, f)
514 614
515 615 def getcwd(self):
516 616 cwd = os.getcwd()
517 617 if cwd == self.root: return ''
518 618 return cwd[len(self.root) + 1:]
519 619
520 620 def wfile(self, f, mode='r'):
521 621 return self.wopener(f, mode)
522 622
523 623 def transaction(self):
524 624 # save dirstate for undo
525 625 try:
526 626 ds = self.opener("dirstate").read()
527 627 except IOError:
528 628 ds = ""
529 629 self.opener("undo.dirstate", "w").write(ds)
530 630
531 631 return transaction.transaction(self.ui.warn,
532 632 self.opener, self.join("journal"),
533 633 self.join("undo"))
534 634
535 635 def recover(self):
536 636 lock = self.lock()
537 637 if os.path.exists(self.join("journal")):
538 638 self.ui.status("rolling back interrupted transaction\n")
539 639 return transaction.rollback(self.opener, self.join("journal"))
540 640 else:
541 641 self.ui.warn("no interrupted transaction available\n")
542 642
543 643 def undo(self):
544 644 lock = self.lock()
545 645 if os.path.exists(self.join("undo")):
546 646 self.ui.status("rolling back last transaction\n")
547 647 transaction.rollback(self.opener, self.join("undo"))
548 648 self.dirstate = None
549 649 util.rename(self.join("undo.dirstate"), self.join("dirstate"))
550 650 self.dirstate = dirstate(self.opener, self.ui, self.root)
551 651 else:
552 652 self.ui.warn("no undo information available\n")
553 653
554 654 def lock(self, wait = 1):
555 655 try:
556 656 return lock.lock(self.join("lock"), 0)
557 657 except lock.LockHeld, inst:
558 658 if wait:
559 659 self.ui.warn("waiting for lock held by %s\n" % inst.args[0])
560 660 return lock.lock(self.join("lock"), wait)
561 661 raise inst
562 662
563 663 def rawcommit(self, files, text, user, date, p1=None, p2=None):
564 664 orig_parent = self.dirstate.parents()[0] or nullid
565 665 p1 = p1 or self.dirstate.parents()[0] or nullid
566 666 p2 = p2 or self.dirstate.parents()[1] or nullid
567 667 c1 = self.changelog.read(p1)
568 668 c2 = self.changelog.read(p2)
569 669 m1 = self.manifest.read(c1[0])
570 670 mf1 = self.manifest.readflags(c1[0])
571 671 m2 = self.manifest.read(c2[0])
572 672
573 673 if orig_parent == p1:
574 674 update_dirstate = 1
575 675 else:
576 676 update_dirstate = 0
577 677
578 678 tr = self.transaction()
579 679 mm = m1.copy()
580 680 mfm = mf1.copy()
581 681 linkrev = self.changelog.count()
582 682 for f in files:
583 683 try:
584 684 t = self.wfile(f).read()
585 685 tm = util.is_exec(self.wjoin(f), mfm.get(f, False))
586 686 r = self.file(f)
587 687 mfm[f] = tm
588 688 mm[f] = r.add(t, {}, tr, linkrev,
589 689 m1.get(f, nullid), m2.get(f, nullid))
590 690 if update_dirstate:
591 691 self.dirstate.update([f], "n")
592 692 except IOError:
593 693 try:
594 694 del mm[f]
595 695 del mfm[f]
596 696 if update_dirstate:
597 697 self.dirstate.forget([f])
598 698 except:
599 699 # deleted from p2?
600 700 pass
601 701
602 702 mnode = self.manifest.add(mm, mfm, tr, linkrev, c1[0], c2[0])
603 703 user = user or self.ui.username()
604 704 n = self.changelog.add(mnode, files, text, tr, p1, p2, user, date)
605 705 tr.close()
606 706 if update_dirstate:
607 707 self.dirstate.setparents(n, nullid)
608 708
609 709 def commit(self, files = None, text = "", user = None, date = None):
610 710 commit = []
611 711 remove = []
612 712 if files:
613 713 for f in files:
614 714 s = self.dirstate.state(f)
615 715 if s in 'nmai':
616 716 commit.append(f)
617 717 elif s == 'r':
618 718 remove.append(f)
619 719 else:
620 720 self.ui.warn("%s not tracked!\n" % f)
621 721 else:
622 722 (c, a, d, u) = self.changes(None, None)
623 723 commit = c + a
624 724 remove = d
625 725
626 726 if not commit and not remove:
627 727 self.ui.status("nothing changed\n")
628 728 return
629 729
630 730 if not self.hook("precommit"):
631 731 return 1
632 732
633 733 p1, p2 = self.dirstate.parents()
634 734 c1 = self.changelog.read(p1)
635 735 c2 = self.changelog.read(p2)
636 736 m1 = self.manifest.read(c1[0])
637 737 mf1 = self.manifest.readflags(c1[0])
638 738 m2 = self.manifest.read(c2[0])
639 739 lock = self.lock()
640 740 tr = self.transaction()
641 741
642 742 # check in files
643 743 new = {}
644 744 linkrev = self.changelog.count()
645 745 commit.sort()
646 746 for f in commit:
647 747 self.ui.note(f + "\n")
648 748 try:
649 749 mf1[f] = util.is_exec(self.wjoin(f), mf1.get(f, False))
650 750 t = self.wfile(f).read()
651 751 except IOError:
652 752 self.warn("trouble committing %s!\n" % f)
653 753 raise
654 754
655 755 meta = {}
656 756 cp = self.dirstate.copied(f)
657 757 if cp:
658 758 meta["copy"] = cp
659 759 meta["copyrev"] = hex(m1.get(cp, m2.get(cp, nullid)))
660 760 self.ui.debug(" %s: copy %s:%s\n" % (f, cp, meta["copyrev"]))
661 761
662 762 r = self.file(f)
663 763 fp1 = m1.get(f, nullid)
664 764 fp2 = m2.get(f, nullid)
665 765 new[f] = r.add(t, meta, tr, linkrev, fp1, fp2)
666 766
667 767 # update manifest
668 768 m1.update(new)
669 769 for f in remove:
670 770 if f in m1:
671 771 del m1[f]
672 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0])
772 mn = self.manifest.add(m1, mf1, tr, linkrev, c1[0], c2[0], (new,remove))
673 773
674 774 # add changeset
675 775 new = new.keys()
676 776 new.sort()
677 777
678 778 if not text:
679 779 edittext = "\n" + "HG: manifest hash %s\n" % hex(mn)
680 780 edittext += "".join(["HG: changed %s\n" % f for f in new])
681 781 edittext += "".join(["HG: removed %s\n" % f for f in remove])
682 782 edittext = self.ui.edit(edittext)
683 783 if not edittext.rstrip():
684 784 return 1
685 785 text = edittext
686 786
687 787 user = user or self.ui.username()
688 788 n = self.changelog.add(mn, new, text, tr, p1, p2, user, date)
689 789
690 790 if not self.hook("commit", node=hex(n)):
691 791 return 1
692 792
693 793 tr.close()
694 794
695 795 self.dirstate.setparents(n)
696 796 self.dirstate.update(new, "n")
697 797 self.dirstate.forget(remove)
698 798
699 799 def changes(self, node1, node2, files=None):
700 800 mf2, u = None, []
701 801
702 802 def fcmp(fn, mf):
703 803 t1 = self.wfile(fn).read()
704 804 t2 = self.file(fn).revision(mf[fn])
705 805 return cmp(t1, t2)
706 806
707 807 # are we comparing the working directory?
708 808 if not node2:
709 809 l, c, a, d, u = self.dirstate.changes(files, self.ignore)
710 810
711 811 # are we comparing working dir against its parent?
712 812 if not node1:
713 813 if l:
714 814 # do a full compare of any files that might have changed
715 815 change = self.changelog.read(self.dirstate.parents()[0])
716 816 mf2 = self.manifest.read(change[0])
717 817 for f in l:
718 818 if fcmp(f, mf2):
719 819 c.append(f)
720 820
721 821 for l in c, a, d, u:
722 822 l.sort()
723 823
724 824 return (c, a, d, u)
725 825
726 826 # are we comparing working dir against non-tip?
727 827 # generate a pseudo-manifest for the working dir
728 828 if not node2:
729 829 if not mf2:
730 830 change = self.changelog.read(self.dirstate.parents()[0])
731 831 mf2 = self.manifest.read(change[0]).copy()
732 832 for f in a + c + l:
733 833 mf2[f] = ""
734 834 for f in d:
735 835 if f in mf2: del mf2[f]
736 836 else:
737 837 change = self.changelog.read(node2)
738 838 mf2 = self.manifest.read(change[0])
739 839
740 840 # flush lists from dirstate before comparing manifests
741 841 c, a = [], []
742 842
743 843 change = self.changelog.read(node1)
744 844 mf1 = self.manifest.read(change[0]).copy()
745 845
746 846 for fn in mf2:
747 847 if mf1.has_key(fn):
748 848 if mf1[fn] != mf2[fn]:
749 849 if mf2[fn] != "" or fcmp(fn, mf1):
750 850 c.append(fn)
751 851 del mf1[fn]
752 852 else:
753 853 a.append(fn)
754 854
755 855 d = mf1.keys()
756 856
757 857 for l in c, a, d, u:
758 858 l.sort()
759 859
760 860 return (c, a, d, u)
761 861
762 862 def add(self, list):
763 863 for f in list:
764 864 p = self.wjoin(f)
765 865 if not os.path.exists(p):
766 866 self.ui.warn("%s does not exist!\n" % f)
767 867 elif not os.path.isfile(p):
768 868 self.ui.warn("%s not added: mercurial only supports files currently\n" % f)
769 869 elif self.dirstate.state(f) == 'n':
770 870 self.ui.warn("%s already tracked!\n" % f)
771 871 else:
772 872 self.dirstate.update([f], "a")
773 873
774 874 def forget(self, list):
775 875 for f in list:
776 876 if self.dirstate.state(f) not in 'ai':
777 877 self.ui.warn("%s not added!\n" % f)
778 878 else:
779 879 self.dirstate.forget([f])
780 880
781 881 def remove(self, list):
782 882 for f in list:
783 883 p = self.wjoin(f)
784 884 if os.path.exists(p):
785 885 self.ui.warn("%s still exists!\n" % f)
786 886 elif self.dirstate.state(f) == 'a':
787 887 self.ui.warn("%s never committed!\n" % f)
788 888 self.dirstate.forget(f)
789 889 elif f not in self.dirstate:
790 890 self.ui.warn("%s not tracked!\n" % f)
791 891 else:
792 892 self.dirstate.update([f], "r")
793 893
794 894 def copy(self, source, dest):
795 895 p = self.wjoin(dest)
796 896 if not os.path.exists(dest):
797 897 self.ui.warn("%s does not exist!\n" % dest)
798 898 elif not os.path.isfile(dest):
799 899 self.ui.warn("copy failed: %s is not a file\n" % dest)
800 900 else:
801 901 if self.dirstate.state(dest) == '?':
802 902 self.dirstate.update([dest], "a")
803 903 self.dirstate.copy(source, dest)
804 904
805 905 def heads(self):
806 906 return self.changelog.heads()
807 907
808 908 def branches(self, nodes):
809 909 if not nodes: nodes = [self.changelog.tip()]
810 910 b = []
811 911 for n in nodes:
812 912 t = n
813 913 while n:
814 914 p = self.changelog.parents(n)
815 915 if p[1] != nullid or p[0] == nullid:
816 916 b.append((t, n, p[0], p[1]))
817 917 break
818 918 n = p[0]
819 919 return b
820 920
821 921 def between(self, pairs):
822 922 r = []
823 923
824 924 for top, bottom in pairs:
825 925 n, l, i = top, [], 0
826 926 f = 1
827 927
828 928 while n != bottom:
829 929 p = self.changelog.parents(n)[0]
830 930 if i == f:
831 931 l.append(n)
832 932 f = f * 2
833 933 n = p
834 934 i += 1
835 935
836 936 r.append(l)
837 937
838 938 return r
839 939
840 940 def newer(self, nodes):
841 941 m = {}
842 942 nl = []
843 943 pm = {}
844 944 cl = self.changelog
845 945 t = l = cl.count()
846 946
847 947 # find the lowest numbered node
848 948 for n in nodes:
849 949 l = min(l, cl.rev(n))
850 950 m[n] = 1
851 951
852 952 for i in xrange(l, t):
853 953 n = cl.node(i)
854 954 if n in m: # explicitly listed
855 955 pm[n] = 1
856 956 nl.append(n)
857 957 continue
858 958 for p in cl.parents(n):
859 959 if p in pm: # parent listed
860 960 pm[n] = 1
861 961 nl.append(n)
862 962 break
863 963
864 964 return nl
865 965
866 966 def findincoming(self, remote, base={}):
867 967 m = self.changelog.nodemap
868 968 search = []
869 969 fetch = []
870 970 seen = {}
871 971 seenbranch = {}
872 972
873 973 # assume we're closer to the tip than the root
874 974 # and start by examining the heads
875 975 self.ui.status("searching for changes\n")
876 976 heads = remote.heads()
877 977 unknown = []
878 978 for h in heads:
879 979 if h not in m:
880 980 unknown.append(h)
881 981 else:
882 982 base[h] = 1
883 983
884 984 if not unknown:
885 985 return None
886 986
887 987 rep = {}
888 988 reqcnt = 0
889 989
890 990 # search through remote branches
891 991 # a 'branch' here is a linear segment of history, with four parts:
892 992 # head, root, first parent, second parent
893 993 # (a branch always has two parents (or none) by definition)
894 994 unknown = remote.branches(unknown)
895 995 while unknown:
896 996 r = []
897 997 while unknown:
898 998 n = unknown.pop(0)
899 999 if n[0] in seen:
900 1000 continue
901 1001
902 1002 self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1])))
903 1003 if n[0] == nullid:
904 1004 break
905 1005 if n in seenbranch:
906 1006 self.ui.debug("branch already found\n")
907 1007 continue
908 1008 if n[1] and n[1] in m: # do we know the base?
909 1009 self.ui.debug("found incomplete branch %s:%s\n"
910 1010 % (short(n[0]), short(n[1])))
911 1011 search.append(n) # schedule branch range for scanning
912 1012 seenbranch[n] = 1
913 1013 else:
914 1014 if n[1] not in seen and n[1] not in fetch:
915 1015 if n[2] in m and n[3] in m:
916 1016 self.ui.debug("found new changeset %s\n" %
917 1017 short(n[1]))
918 1018 fetch.append(n[1]) # earliest unknown
919 1019 base[n[2]] = 1 # latest known
920 1020 continue
921 1021
922 1022 for a in n[2:4]:
923 1023 if a not in rep:
924 1024 r.append(a)
925 1025 rep[a] = 1
926 1026
927 1027 seen[n[0]] = 1
928 1028
929 1029 if r:
930 1030 reqcnt += 1
931 1031 self.ui.debug("request %d: %s\n" %
932 1032 (reqcnt, " ".join(map(short, r))))
933 1033 for p in range(0, len(r), 10):
934 1034 for b in remote.branches(r[p:p+10]):
935 1035 self.ui.debug("received %s:%s\n" %
936 1036 (short(b[0]), short(b[1])))
937 1037 if b[0] not in m and b[0] not in seen:
938 1038 unknown.append(b)
939 1039
940 1040 # do binary search on the branches we found
941 1041 while search:
942 1042 n = search.pop(0)
943 1043 reqcnt += 1
944 1044 l = remote.between([(n[0], n[1])])[0]
945 1045 l.append(n[1])
946 1046 p = n[0]
947 1047 f = 1
948 1048 for i in l:
949 1049 self.ui.debug("narrowing %d:%d %s\n" % (f, len(l), short(i)))
950 1050 if i in m:
951 1051 if f <= 2:
952 1052 self.ui.debug("found new branch changeset %s\n" %
953 1053 short(p))
954 1054 fetch.append(p)
955 1055 base[i] = 1
956 1056 else:
957 1057 self.ui.debug("narrowed branch search to %s:%s\n"
958 1058 % (short(p), short(i)))
959 1059 search.append((p, i))
960 1060 break
961 1061 p, f = i, f * 2
962 1062
963 1063 # sanity check our fetch list
964 1064 for f in fetch:
965 1065 if f in m:
966 1066 raise RepoError("already have changeset " + short(f[:4]))
967 1067
968 1068 if base.keys() == [nullid]:
969 1069 self.ui.warn("warning: pulling from an unrelated repository!\n")
970 1070
971 1071 self.ui.note("adding new changesets starting at " +
972 1072 " ".join([short(f) for f in fetch]) + "\n")
973 1073
974 1074 self.ui.debug("%d total queries\n" % reqcnt)
975 1075
976 1076 return fetch
977 1077
978 1078 def findoutgoing(self, remote):
979 1079 base = {}
980 1080 self.findincoming(remote, base)
981 1081 remain = dict.fromkeys(self.changelog.nodemap)
982 1082
983 1083 # prune everything remote has from the tree
984 1084 del remain[nullid]
985 1085 remove = base.keys()
986 1086 while remove:
987 1087 n = remove.pop(0)
988 1088 if n in remain:
989 1089 del remain[n]
990 1090 for p in self.changelog.parents(n):
991 1091 remove.append(p)
992 1092
993 1093 # find every node whose parents have been pruned
994 1094 subset = []
995 1095 for n in remain:
996 1096 p1, p2 = self.changelog.parents(n)
997 1097 if p1 not in remain and p2 not in remain:
998 1098 subset.append(n)
999 1099
1000 1100 # this is the set of all roots we have to push
1001 1101 return subset
1002 1102
1003 1103 def pull(self, remote):
1004 1104 lock = self.lock()
1005 1105
1006 1106 # if we have an empty repo, fetch everything
1007 1107 if self.changelog.tip() == nullid:
1008 1108 self.ui.status("requesting all changes\n")
1009 1109 fetch = [nullid]
1010 1110 else:
1011 1111 fetch = self.findincoming(remote)
1012 1112
1013 1113 if not fetch:
1014 1114 self.ui.status("no changes found\n")
1015 1115 return 1
1016 1116
1017 1117 cg = remote.changegroup(fetch)
1018 1118 return self.addchangegroup(cg)
1019 1119
1020 1120 def push(self, remote):
1021 1121 lock = remote.lock()
1022 1122 update = self.findoutgoing(remote)
1023 1123 if not update:
1024 1124 self.ui.status("no changes found\n")
1025 1125 return 1
1026 1126
1027 1127 cg = self.changegroup(update)
1028 1128 return remote.addchangegroup(cg)
1029 1129
1030 1130 def changegroup(self, basenodes):
1031 1131 class genread:
1032 1132 def __init__(self, generator):
1033 1133 self.g = generator
1034 1134 self.buf = ""
1035 1135 def read(self, l):
1036 1136 while l > len(self.buf):
1037 1137 try:
1038 1138 self.buf += self.g.next()
1039 1139 except StopIteration:
1040 1140 break
1041 1141 d, self.buf = self.buf[:l], self.buf[l:]
1042 1142 return d
1043 1143
1044 1144 def gengroup():
1045 1145 nodes = self.newer(basenodes)
1046 1146
1047 1147 # construct the link map
1048 1148 linkmap = {}
1049 1149 for n in nodes:
1050 1150 linkmap[self.changelog.rev(n)] = n
1051 1151
1052 1152 # construct a list of all changed files
1053 1153 changed = {}
1054 1154 for n in nodes:
1055 1155 c = self.changelog.read(n)
1056 1156 for f in c[3]:
1057 1157 changed[f] = 1
1058 1158 changed = changed.keys()
1059 1159 changed.sort()
1060 1160
1061 1161 # the changegroup is changesets + manifests + all file revs
1062 1162 revs = [ self.changelog.rev(n) for n in nodes ]
1063 1163
1064 1164 for y in self.changelog.group(linkmap): yield y
1065 1165 for y in self.manifest.group(linkmap): yield y
1066 1166 for f in changed:
1067 1167 yield struct.pack(">l", len(f) + 4) + f
1068 1168 g = self.file(f).group(linkmap)
1069 1169 for y in g:
1070 1170 yield y
1071 1171
1072 1172 yield struct.pack(">l", 0)
1073 1173
1074 1174 return genread(gengroup())
1075 1175
1076 1176 def addchangegroup(self, source):
1077 1177
1078 1178 def getchunk():
1079 1179 d = source.read(4)
1080 1180 if not d: return ""
1081 1181 l = struct.unpack(">l", d)[0]
1082 1182 if l <= 4: return ""
1083 1183 return source.read(l - 4)
1084 1184
1085 1185 def getgroup():
1086 1186 while 1:
1087 1187 c = getchunk()
1088 1188 if not c: break
1089 1189 yield c
1090 1190
1091 1191 def csmap(x):
1092 1192 self.ui.debug("add changeset %s\n" % short(x))
1093 1193 return self.changelog.count()
1094 1194
1095 1195 def revmap(x):
1096 1196 return self.changelog.rev(x)
1097 1197
1098 1198 if not source: return
1099 1199 changesets = files = revisions = 0
1100 1200
1101 1201 tr = self.transaction()
1102 1202
1103 1203 # pull off the changeset group
1104 1204 self.ui.status("adding changesets\n")
1105 1205 co = self.changelog.tip()
1106 1206 cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
1107 1207 changesets = self.changelog.rev(cn) - self.changelog.rev(co)
1108 1208
1109 1209 # pull off the manifest group
1110 1210 self.ui.status("adding manifests\n")
1111 1211 mm = self.manifest.tip()
1112 1212 mo = self.manifest.addgroup(getgroup(), revmap, tr)
1113 1213
1114 1214 # process the files
1115 1215 self.ui.status("adding file revisions\n")
1116 1216 while 1:
1117 1217 f = getchunk()
1118 1218 if not f: break
1119 1219 self.ui.debug("adding %s revisions\n" % f)
1120 1220 fl = self.file(f)
1121 1221 o = fl.count()
1122 1222 n = fl.addgroup(getgroup(), revmap, tr)
1123 1223 revisions += fl.count() - o
1124 1224 files += 1
1125 1225
1126 1226 self.ui.status(("modified %d files, added %d changesets" +
1127 1227 " and %d new revisions\n")
1128 1228 % (files, changesets, revisions))
1129 1229
1130 1230 tr.close()
1131 1231 return
1132 1232
1133 1233 def update(self, node, allow=False, force=False, choose=None,
1134 1234 moddirstate=True):
1135 1235 pl = self.dirstate.parents()
1136 1236 if not force and pl[1] != nullid:
1137 1237 self.ui.warn("aborting: outstanding uncommitted merges\n")
1138 1238 return
1139 1239
1140 1240 p1, p2 = pl[0], node
1141 1241 pa = self.changelog.ancestor(p1, p2)
1142 1242 m1n = self.changelog.read(p1)[0]
1143 1243 m2n = self.changelog.read(p2)[0]
1144 1244 man = self.manifest.ancestor(m1n, m2n)
1145 1245 m1 = self.manifest.read(m1n)
1146 1246 mf1 = self.manifest.readflags(m1n)
1147 1247 m2 = self.manifest.read(m2n)
1148 1248 mf2 = self.manifest.readflags(m2n)
1149 1249 ma = self.manifest.read(man)
1150 1250 mfa = self.manifest.readflags(man)
1151 1251
1152 1252 (c, a, d, u) = self.changes(None, None)
1153 1253
1154 1254 # is this a jump, or a merge? i.e. is there a linear path
1155 1255 # from p1 to p2?
1156 1256 linear_path = (pa == p1 or pa == p2)
1157 1257
1158 1258 # resolve the manifest to determine which files
1159 1259 # we care about merging
1160 1260 self.ui.note("resolving manifests\n")
1161 1261 self.ui.debug(" ancestor %s local %s remote %s\n" %
1162 1262 (short(man), short(m1n), short(m2n)))
1163 1263
1164 1264 merge = {}
1165 1265 get = {}
1166 1266 remove = []
1167 1267 mark = {}
1168 1268
1169 1269 # construct a working dir manifest
1170 1270 mw = m1.copy()
1171 1271 mfw = mf1.copy()
1172 1272 umap = dict.fromkeys(u)
1173 1273
1174 1274 for f in a + c + u:
1175 1275 mw[f] = ""
1176 1276 mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False))
1177 1277
1178 1278 for f in d:
1179 1279 if f in mw: del mw[f]
1180 1280
1181 1281 # If we're jumping between revisions (as opposed to merging),
1182 1282 # and if neither the working directory nor the target rev has
1183 1283 # the file, then we need to remove it from the dirstate, to
1184 1284 # prevent the dirstate from listing the file when it is no
1185 1285 # longer in the manifest.
1186 1286 if moddirstate and linear_path and f not in m2:
1187 1287 self.dirstate.forget((f,))
1188 1288
1189 1289 # Compare manifests
1190 1290 for f, n in mw.iteritems():
1191 1291 if choose and not choose(f): continue
1192 1292 if f in m2:
1193 1293 s = 0
1194 1294
1195 1295 # is the wfile new since m1, and match m2?
1196 1296 if f not in m1:
1197 1297 t1 = self.wfile(f).read()
1198 1298 t2 = self.file(f).revision(m2[f])
1199 1299 if cmp(t1, t2) == 0:
1200 1300 mark[f] = 1
1201 1301 n = m2[f]
1202 1302 del t1, t2
1203 1303
1204 1304 # are files different?
1205 1305 if n != m2[f]:
1206 1306 a = ma.get(f, nullid)
1207 1307 # are both different from the ancestor?
1208 1308 if n != a and m2[f] != a:
1209 1309 self.ui.debug(" %s versions differ, resolve\n" % f)
1210 1310 # merge executable bits
1211 1311 # "if we changed or they changed, change in merge"
1212 1312 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1213 1313 mode = ((a^b) | (a^c)) ^ a
1214 1314 merge[f] = (m1.get(f, nullid), m2[f], mode)
1215 1315 s = 1
1216 1316 # are we clobbering?
1217 1317 # is remote's version newer?
1218 1318 # or are we going back in time?
1219 1319 elif force or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
1220 1320 self.ui.debug(" remote %s is newer, get\n" % f)
1221 1321 get[f] = m2[f]
1222 1322 s = 1
1223 1323 else:
1224 1324 mark[f] = 1
1225 1325 elif f in umap:
1226 1326 # this unknown file is the same as the checkout
1227 1327 get[f] = m2[f]
1228 1328
1229 1329 if not s and mfw[f] != mf2[f]:
1230 1330 if force:
1231 1331 self.ui.debug(" updating permissions for %s\n" % f)
1232 1332 util.set_exec(self.wjoin(f), mf2[f])
1233 1333 else:
1234 1334 a, b, c = mfa.get(f, 0), mfw[f], mf2[f]
1235 1335 mode = ((a^b) | (a^c)) ^ a
1236 1336 if mode != b:
1237 1337 self.ui.debug(" updating permissions for %s\n" % f)
1238 1338 util.set_exec(self.wjoin(f), mode)
1239 1339 mark[f] = 1
1240 1340 del m2[f]
1241 1341 elif f in ma:
1242 1342 if n != ma[f]:
1243 1343 r = "d"
1244 1344 if not force and (linear_path or allow):
1245 1345 r = self.ui.prompt(
1246 1346 (" local changed %s which remote deleted\n" % f) +
1247 1347 "(k)eep or (d)elete?", "[kd]", "k")
1248 1348 if r == "d":
1249 1349 remove.append(f)
1250 1350 else:
1251 1351 self.ui.debug("other deleted %s\n" % f)
1252 1352 remove.append(f) # other deleted it
1253 1353 else:
1254 1354 if n == m1.get(f, nullid): # same as parent
1255 1355 if p2 == pa: # going backwards?
1256 1356 self.ui.debug("remote deleted %s\n" % f)
1257 1357 remove.append(f)
1258 1358 else:
1259 1359 self.ui.debug("local created %s, keeping\n" % f)
1260 1360 else:
1261 1361 self.ui.debug("working dir created %s, keeping\n" % f)
1262 1362
1263 1363 for f, n in m2.iteritems():
1264 1364 if choose and not choose(f): continue
1265 1365 if f[0] == "/": continue
1266 1366 if f in ma and n != ma[f]:
1267 1367 r = "k"
1268 1368 if not force and (linear_path or allow):
1269 1369 r = self.ui.prompt(
1270 1370 ("remote changed %s which local deleted\n" % f) +
1271 1371 "(k)eep or (d)elete?", "[kd]", "k")
1272 1372 if r == "k": get[f] = n
1273 1373 elif f not in ma:
1274 1374 self.ui.debug("remote created %s\n" % f)
1275 1375 get[f] = n
1276 1376 else:
1277 1377 self.ui.debug("local deleted %s\n" % f)
1278 1378
1279 1379 del mw, m1, m2, ma
1280 1380
1281 1381 if force:
1282 1382 for f in merge:
1283 1383 get[f] = merge[f][1]
1284 1384 merge = {}
1285 1385
1286 1386 if linear_path:
1287 1387 # we don't need to do any magic, just jump to the new rev
1288 1388 mode = 'n'
1289 1389 p1, p2 = p2, nullid
1290 1390 else:
1291 1391 if not allow:
1292 1392 self.ui.status("this update spans a branch" +
1293 1393 " affecting the following files:\n")
1294 1394 fl = merge.keys() + get.keys()
1295 1395 fl.sort()
1296 1396 for f in fl:
1297 1397 cf = ""
1298 1398 if f in merge: cf = " (resolve)"
1299 1399 self.ui.status(" %s%s\n" % (f, cf))
1300 1400 self.ui.warn("aborting update spanning branches!\n")
1301 1401 self.ui.status("(use update -m to perform a branch merge)\n")
1302 1402 return 1
1303 1403 # we have to remember what files we needed to get/change
1304 1404 # because any file that's different from either one of its
1305 1405 # parents must be in the changeset
1306 1406 mode = 'm'
1307 1407 if moddirstate:
1308 1408 self.dirstate.update(mark.keys(), "m")
1309 1409
1310 1410 if moddirstate:
1311 1411 self.dirstate.setparents(p1, p2)
1312 1412
1313 1413 # get the files we don't need to change
1314 1414 files = get.keys()
1315 1415 files.sort()
1316 1416 for f in files:
1317 1417 if f[0] == "/": continue
1318 1418 self.ui.note("getting %s\n" % f)
1319 1419 t = self.file(f).read(get[f])
1320 1420 try:
1321 1421 self.wfile(f, "w").write(t)
1322 1422 except IOError:
1323 1423 os.makedirs(os.path.dirname(self.wjoin(f)))
1324 1424 self.wfile(f, "w").write(t)
1325 1425 util.set_exec(self.wjoin(f), mf2[f])
1326 1426 if moddirstate:
1327 1427 self.dirstate.update([f], mode)
1328 1428
1329 1429 # merge the tricky bits
1330 1430 files = merge.keys()
1331 1431 files.sort()
1332 1432 for f in files:
1333 1433 self.ui.status("merging %s\n" % f)
1334 1434 m, o, flag = merge[f]
1335 1435 self.merge3(f, m, o)
1336 1436 util.set_exec(self.wjoin(f), flag)
1337 1437 if moddirstate:
1338 1438 self.dirstate.update([f], 'm')
1339 1439
1340 1440 for f in remove:
1341 1441 self.ui.note("removing %s\n" % f)
1342 1442 os.unlink(f)
1343 1443 # try removing directories that might now be empty
1344 1444 try: os.removedirs(os.path.dirname(f))
1345 1445 except: pass
1346 1446 if moddirstate:
1347 1447 if mode == 'n':
1348 1448 self.dirstate.forget(remove)
1349 1449 else:
1350 1450 self.dirstate.update(remove, 'r')
1351 1451
1352 1452 def merge3(self, fn, my, other):
1353 1453 """perform a 3-way merge in the working directory"""
1354 1454
1355 1455 def temp(prefix, node):
1356 1456 pre = "%s~%s." % (os.path.basename(fn), prefix)
1357 1457 (fd, name) = tempfile.mkstemp("", pre)
1358 1458 f = os.fdopen(fd, "wb")
1359 1459 f.write(fl.revision(node))
1360 1460 f.close()
1361 1461 return name
1362 1462
1363 1463 fl = self.file(fn)
1364 1464 base = fl.ancestor(my, other)
1365 1465 a = self.wjoin(fn)
1366 1466 b = temp("base", base)
1367 1467 c = temp("other", other)
1368 1468
1369 1469 self.ui.note("resolving %s\n" % fn)
1370 1470 self.ui.debug("file %s: other %s ancestor %s\n" %
1371 1471 (fn, short(other), short(base)))
1372 1472
1373 1473 cmd = self.ui.config("ui", "merge") or \
1374 1474 os.environ.get("HGMERGE", "hgmerge")
1375 1475 r = os.system("%s %s %s %s" % (cmd, a, b, c))
1376 1476 if r:
1377 1477 self.ui.warn("merging %s failed!\n" % fn)
1378 1478
1379 1479 os.unlink(b)
1380 1480 os.unlink(c)
1381 1481
1382 1482 def verify(self):
1383 1483 filelinkrevs = {}
1384 1484 filenodes = {}
1385 1485 changesets = revisions = files = 0
1386 1486 errors = 0
1387 1487
1388 1488 seen = {}
1389 1489 self.ui.status("checking changesets\n")
1390 1490 for i in range(self.changelog.count()):
1391 1491 changesets += 1
1392 1492 n = self.changelog.node(i)
1393 1493 if n in seen:
1394 1494 self.ui.warn("duplicate changeset at revision %d\n" % i)
1395 1495 errors += 1
1396 1496 seen[n] = 1
1397 1497
1398 1498 for p in self.changelog.parents(n):
1399 1499 if p not in self.changelog.nodemap:
1400 1500 self.ui.warn("changeset %s has unknown parent %s\n" %
1401 1501 (short(n), short(p)))
1402 1502 errors += 1
1403 1503 try:
1404 1504 changes = self.changelog.read(n)
1405 1505 except Exception, inst:
1406 1506 self.ui.warn("unpacking changeset %s: %s\n" % (short(n), inst))
1407 1507 errors += 1
1408 1508
1409 1509 for f in changes[3]:
1410 1510 filelinkrevs.setdefault(f, []).append(i)
1411 1511
1412 1512 seen = {}
1413 1513 self.ui.status("checking manifests\n")
1414 1514 for i in range(self.manifest.count()):
1415 1515 n = self.manifest.node(i)
1416 1516 if n in seen:
1417 1517 self.ui.warn("duplicate manifest at revision %d\n" % i)
1418 1518 errors += 1
1419 1519 seen[n] = 1
1420 1520
1421 1521 for p in self.manifest.parents(n):
1422 1522 if p not in self.manifest.nodemap:
1423 1523 self.ui.warn("manifest %s has unknown parent %s\n" %
1424 1524 (short(n), short(p)))
1425 1525 errors += 1
1426 1526
1427 1527 try:
1428 1528 delta = mdiff.patchtext(self.manifest.delta(n))
1429 1529 except KeyboardInterrupt:
1430 1530 self.ui.warn("aborted")
1431 1531 sys.exit(0)
1432 1532 except Exception, inst:
1433 1533 self.ui.warn("unpacking manifest %s: %s\n"
1434 1534 % (short(n), inst))
1435 1535 errors += 1
1436 1536
1437 1537 ff = [ l.split('\0') for l in delta.splitlines() ]
1438 1538 for f, fn in ff:
1439 1539 filenodes.setdefault(f, {})[bin(fn[:40])] = 1
1440 1540
1441 1541 self.ui.status("crosschecking files in changesets and manifests\n")
1442 1542 for f in filenodes:
1443 1543 if f not in filelinkrevs:
1444 1544 self.ui.warn("file %s in manifest but not in changesets\n" % f)
1445 1545 errors += 1
1446 1546
1447 1547 for f in filelinkrevs:
1448 1548 if f not in filenodes:
1449 1549 self.ui.warn("file %s in changeset but not in manifest\n" % f)
1450 1550 errors += 1
1451 1551
1452 1552 self.ui.status("checking files\n")
1453 1553 ff = filenodes.keys()
1454 1554 ff.sort()
1455 1555 for f in ff:
1456 1556 if f == "/dev/null": continue
1457 1557 files += 1
1458 1558 fl = self.file(f)
1459 1559 nodes = { nullid: 1 }
1460 1560 seen = {}
1461 1561 for i in range(fl.count()):
1462 1562 revisions += 1
1463 1563 n = fl.node(i)
1464 1564
1465 1565 if n in seen:
1466 1566 self.ui.warn("%s: duplicate revision %d\n" % (f, i))
1467 1567 errors += 1
1468 1568
1469 1569 if n not in filenodes[f]:
1470 1570 self.ui.warn("%s: %d:%s not in manifests\n"
1471 1571 % (f, i, short(n)))
1472 1572 errors += 1
1473 1573 else:
1474 1574 del filenodes[f][n]
1475 1575
1476 1576 flr = fl.linkrev(n)
1477 1577 if flr not in filelinkrevs[f]:
1478 1578 self.ui.warn("%s:%s points to unexpected changeset %d\n"
1479 1579 % (f, short(n), fl.linkrev(n)))
1480 1580 errors += 1
1481 1581 else:
1482 1582 filelinkrevs[f].remove(flr)
1483 1583
1484 1584 # verify contents
1485 1585 try:
1486 1586 t = fl.read(n)
1487 1587 except Exception, inst:
1488 1588 self.ui.warn("unpacking file %s %s: %s\n"
1489 1589 % (f, short(n), inst))
1490 1590 errors += 1
1491 1591
1492 1592 # verify parents
1493 1593 (p1, p2) = fl.parents(n)
1494 1594 if p1 not in nodes:
1495 1595 self.ui.warn("file %s:%s unknown parent 1 %s" %
1496 1596 (f, short(n), short(p1)))
1497 1597 errors += 1
1498 1598 if p2 not in nodes:
1499 1599 self.ui.warn("file %s:%s unknown parent 2 %s" %
1500 1600 (f, short(n), short(p1)))
1501 1601 errors += 1
1502 1602 nodes[n] = 1
1503 1603
1504 1604 # cross-check
1505 1605 for node in filenodes[f]:
1506 1606 self.ui.warn("node %s in manifests not in %s\n"
1507 1607 % (hex(n), f))
1508 1608 errors += 1
1509 1609
1510 1610 self.ui.status("%d files, %d changesets, %d total revisions\n" %
1511 1611 (files, changesets, revisions))
1512 1612
1513 1613 if errors:
1514 1614 self.ui.warn("%d integrity errors encountered!\n" % errors)
1515 1615 return 1
1516 1616
1517 1617 class httprepository:
1518 1618 def __init__(self, ui, path):
1519 1619 self.url = path
1520 1620 self.ui = ui
1521 1621 no_list = [ "localhost", "127.0.0.1" ]
1522 1622 host = ui.config("http_proxy", "host")
1523 1623 if host is None:
1524 1624 host = os.environ.get("http_proxy")
1525 1625 if host and host.startswith('http://'):
1526 1626 host = host[7:]
1527 1627 user = ui.config("http_proxy", "user")
1528 1628 passwd = ui.config("http_proxy", "passwd")
1529 1629 no = ui.config("http_proxy", "no")
1530 1630 if no is None:
1531 1631 no = os.environ.get("no_proxy")
1532 1632 if no:
1533 1633 no_list = no_list + no.split(",")
1534 1634
1535 1635 no_proxy = 0
1536 1636 for h in no_list:
1537 1637 if (path.startswith("http://" + h + "/") or
1538 1638 path.startswith("http://" + h + ":") or
1539 1639 path == "http://" + h):
1540 1640 no_proxy = 1
1541 1641
1542 1642 # Note: urllib2 takes proxy values from the environment and those will
1543 1643 # take precedence
1544 1644 for env in ["HTTP_PROXY", "http_proxy", "no_proxy"]:
1545 1645 if os.environ.has_key(env):
1546 1646 del os.environ[env]
1547 1647
1548 1648 proxy_handler = urllib2.BaseHandler()
1549 1649 if host and not no_proxy:
1550 1650 proxy_handler = urllib2.ProxyHandler({"http" : "http://" + host})
1551 1651
1552 1652 authinfo = None
1553 1653 if user and passwd:
1554 1654 passmgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
1555 1655 passmgr.add_password(None, host, user, passwd)
1556 1656 authinfo = urllib2.ProxyBasicAuthHandler(passmgr)
1557 1657
1558 1658 opener = urllib2.build_opener(proxy_handler, authinfo)
1559 1659 urllib2.install_opener(opener)
1560 1660
1561 1661 def dev(self):
1562 1662 return -1
1563 1663
1564 1664 def do_cmd(self, cmd, **args):
1565 1665 self.ui.debug("sending %s command\n" % cmd)
1566 1666 q = {"cmd": cmd}
1567 1667 q.update(args)
1568 1668 qs = urllib.urlencode(q)
1569 1669 cu = "%s?%s" % (self.url, qs)
1570 1670 return urllib2.urlopen(cu)
1571 1671
1572 1672 def heads(self):
1573 1673 d = self.do_cmd("heads").read()
1574 1674 try:
1575 1675 return map(bin, d[:-1].split(" "))
1576 1676 except:
1577 1677 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1578 1678 raise
1579 1679
1580 1680 def branches(self, nodes):
1581 1681 n = " ".join(map(hex, nodes))
1582 1682 d = self.do_cmd("branches", nodes=n).read()
1583 1683 try:
1584 1684 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1585 1685 return br
1586 1686 except:
1587 1687 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1588 1688 raise
1589 1689
1590 1690 def between(self, pairs):
1591 1691 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1592 1692 d = self.do_cmd("between", pairs=n).read()
1593 1693 try:
1594 1694 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1595 1695 return p
1596 1696 except:
1597 1697 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1598 1698 raise
1599 1699
1600 1700 def changegroup(self, nodes):
1601 1701 n = " ".join(map(hex, nodes))
1602 1702 f = self.do_cmd("changegroup", roots=n)
1603 1703 bytes = 0
1604 1704
1605 1705 class zread:
1606 1706 def __init__(self, f):
1607 1707 self.zd = zlib.decompressobj()
1608 1708 self.f = f
1609 1709 self.buf = ""
1610 1710 def read(self, l):
1611 1711 while l > len(self.buf):
1612 1712 r = f.read(4096)
1613 1713 if r:
1614 1714 self.buf += self.zd.decompress(r)
1615 1715 else:
1616 1716 self.buf += self.zd.flush()
1617 1717 break
1618 1718 d, self.buf = self.buf[:l], self.buf[l:]
1619 1719 return d
1620 1720
1621 1721 return zread(f)
1622 1722
1623 1723 class remotelock:
1624 1724 def __init__(self, repo):
1625 1725 self.repo = repo
1626 1726 def release(self):
1627 1727 self.repo.unlock()
1628 1728 self.repo = None
1629 1729 def __del__(self):
1630 1730 if self.repo:
1631 1731 self.release()
1632 1732
1633 1733 class sshrepository:
1634 1734 def __init__(self, ui, path):
1635 1735 self.url = path
1636 1736 self.ui = ui
1637 1737
1638 1738 m = re.match(r'ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?', path)
1639 1739 if not m:
1640 1740 raise RepoError("couldn't parse destination %s\n" % path)
1641 1741
1642 1742 self.user = m.group(2)
1643 1743 self.host = m.group(3)
1644 1744 self.port = m.group(5)
1645 1745 self.path = m.group(7)
1646 1746
1647 1747 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
1648 1748 args = self.port and ("%s -p %s") % (args, self.port) or args
1649 1749 path = self.path or ""
1650 1750
1651 1751 cmd = "ssh %s 'hg -R %s serve --stdio'"
1652 1752 cmd = cmd % (args, path)
1653 1753
1654 1754 self.pipeo, self.pipei = os.popen2(cmd)
1655 1755
1656 1756 def __del__(self):
1657 1757 self.pipeo.close()
1658 1758 self.pipei.close()
1659 1759
1660 1760 def dev(self):
1661 1761 return -1
1662 1762
1663 1763 def do_cmd(self, cmd, **args):
1664 1764 self.ui.debug("sending %s command\n" % cmd)
1665 1765 self.pipeo.write("%s\n" % cmd)
1666 1766 for k, v in args.items():
1667 1767 self.pipeo.write("%s %d\n" % (k, len(v)))
1668 1768 self.pipeo.write(v)
1669 1769 self.pipeo.flush()
1670 1770
1671 1771 return self.pipei
1672 1772
1673 1773 def call(self, cmd, **args):
1674 1774 r = self.do_cmd(cmd, **args)
1675 1775 l = int(r.readline())
1676 1776 return r.read(l)
1677 1777
1678 1778 def lock(self):
1679 1779 self.call("lock")
1680 1780 return remotelock(self)
1681 1781
1682 1782 def unlock(self):
1683 1783 self.call("unlock")
1684 1784
1685 1785 def heads(self):
1686 1786 d = self.call("heads")
1687 1787 try:
1688 1788 return map(bin, d[:-1].split(" "))
1689 1789 except:
1690 1790 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1691 1791 raise
1692 1792
1693 1793 def branches(self, nodes):
1694 1794 n = " ".join(map(hex, nodes))
1695 1795 d = self.call("branches", nodes=n)
1696 1796 try:
1697 1797 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
1698 1798 return br
1699 1799 except:
1700 1800 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1701 1801 raise
1702 1802
1703 1803 def between(self, pairs):
1704 1804 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
1705 1805 d = self.call("between", pairs=n)
1706 1806 try:
1707 1807 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
1708 1808 return p
1709 1809 except:
1710 1810 self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
1711 1811 raise
1712 1812
1713 1813 def changegroup(self, nodes):
1714 1814 n = " ".join(map(hex, nodes))
1715 1815 f = self.do_cmd("changegroup", roots=n)
1716 1816 return self.pipei
1717 1817
1718 1818 def addchangegroup(self, cg):
1719 1819 d = self.call("addchangegroup")
1720 1820 if d:
1721 1821 raise RepoError("push refused: %s", d)
1722 1822
1723 1823 while 1:
1724 1824 d = cg.read(4096)
1725 1825 if not d: break
1726 1826 self.pipeo.write(d)
1727 1827
1728 1828 self.pipeo.flush()
1729 1829
1730 1830 l = int(self.pipei.readline())
1731 1831 return self.pipei.read(l)
1732 1832
1733 1833 def repository(ui, path=None, create=0):
1734 1834 if path:
1735 1835 if path.startswith("http://"):
1736 1836 return httprepository(ui, path)
1737 1837 if path.startswith("hg://"):
1738 1838 return httprepository(ui, path.replace("hg://", "http://"))
1739 1839 if path.startswith("old-http://"):
1740 1840 return localrepository(ui, path.replace("old-http://", "http://"))
1741 1841 if path.startswith("ssh://"):
1742 1842 return sshrepository(ui, path)
1743 1843
1744 1844 return localrepository(ui, path, create)
@@ -1,541 +1,542 b''
1 1 # revlog.py - storage back-end for mercurial
2 2 #
3 3 # This provides efficient delta storage with O(1) retrieve and append
4 4 # and O(changes) merge between branches
5 5 #
6 6 # Copyright 2005 Matt Mackall <mpm@selenic.com>
7 7 #
8 8 # This software may be used and distributed according to the terms
9 9 # of the GNU General Public License, incorporated herein by reference.
10 10
11 11 import zlib, struct, sha, binascii, heapq
12 12 from mercurial import mdiff
13 13
14 14 def hex(node): return binascii.hexlify(node)
15 15 def bin(node): return binascii.unhexlify(node)
16 16 def short(node): return hex(node[:6])
17 17
18 18 def compress(text):
19 19 if not text: return text
20 20 if len(text) < 44:
21 21 if text[0] == '\0': return text
22 22 return 'u' + text
23 23 bin = zlib.compress(text)
24 24 if len(bin) > len(text):
25 25 if text[0] == '\0': return text
26 26 return 'u' + text
27 27 return bin
28 28
29 29 def decompress(bin):
30 30 if not bin: return bin
31 31 t = bin[0]
32 32 if t == '\0': return bin
33 33 if t == 'x': return zlib.decompress(bin)
34 34 if t == 'u': return bin[1:]
35 35 raise "unknown compression type %s" % t
36 36
37 37 def hash(text, p1, p2):
38 38 l = [p1, p2]
39 39 l.sort()
40 40 s = sha.new(l[0])
41 41 s.update(l[1])
42 42 s.update(text)
43 43 return s.digest()
44 44
45 45 nullid = "\0" * 20
46 46 indexformat = ">4l20s20s20s"
47 47
48 48 class lazyparser:
49 49 def __init__(self, data, revlog):
50 50 self.data = data
51 51 self.s = struct.calcsize(indexformat)
52 52 self.l = len(data)/self.s
53 53 self.index = [None] * self.l
54 54 self.map = {nullid: -1}
55 55 self.all = 0
56 56 self.revlog = revlog
57 57
58 58 def load(self, pos=None):
59 59 if self.all: return
60 60 if pos is not None:
61 61 block = pos / 1000
62 62 i = block * 1000
63 63 end = min(self.l, i + 1000)
64 64 else:
65 65 self.all = 1
66 66 i = 0
67 67 end = self.l
68 68 self.revlog.index = self.index
69 69 self.revlog.nodemap = self.map
70 70
71 71 while i < end:
72 72 d = self.data[i * self.s: (i + 1) * self.s]
73 73 e = struct.unpack(indexformat, d)
74 74 self.index[i] = e
75 75 self.map[e[6]] = i
76 76 i += 1
77 77
78 78 class lazyindex:
79 79 def __init__(self, parser):
80 80 self.p = parser
81 81 def __len__(self):
82 82 return len(self.p.index)
83 83 def load(self, pos):
84 84 self.p.load(pos)
85 85 return self.p.index[pos]
86 86 def __getitem__(self, pos):
87 87 return self.p.index[pos] or self.load(pos)
88 88 def append(self, e):
89 89 self.p.index.append(e)
90 90
91 91 class lazymap:
92 92 def __init__(self, parser):
93 93 self.p = parser
94 94 def load(self, key):
95 95 if self.p.all: return
96 96 n = self.p.data.find(key)
97 97 if n < 0: raise KeyError("node " + hex(key))
98 98 pos = n / self.p.s
99 99 self.p.load(pos)
100 100 def __contains__(self, key):
101 101 self.p.load()
102 102 return key in self.p.map
103 103 def __iter__(self):
104 104 yield nullid
105 105 for i in xrange(self.p.l):
106 106 try:
107 107 yield self.p.index[i][6]
108 108 except:
109 109 self.p.load(i)
110 110 yield self.p.index[i][6]
111 111 def __getitem__(self, key):
112 112 try:
113 113 return self.p.map[key]
114 114 except KeyError:
115 115 try:
116 116 self.load(key)
117 117 return self.p.map[key]
118 118 except KeyError:
119 119 raise KeyError("node " + hex(key))
120 120 def __setitem__(self, key, val):
121 121 self.p.map[key] = val
122 122
123 123 class revlog:
124 124 def __init__(self, opener, indexfile, datafile):
125 125 self.indexfile = indexfile
126 126 self.datafile = datafile
127 127 self.opener = opener
128 128 self.cache = None
129 129
130 130 try:
131 131 i = self.opener(self.indexfile).read()
132 132 except IOError:
133 133 i = ""
134 134
135 135 if len(i) > 10000:
136 136 # big index, let's parse it on demand
137 137 parser = lazyparser(i, self)
138 138 self.index = lazyindex(parser)
139 139 self.nodemap = lazymap(parser)
140 140 else:
141 141 s = struct.calcsize(indexformat)
142 142 l = len(i) / s
143 143 self.index = [None] * l
144 144 m = [None] * l
145 145
146 146 n = 0
147 147 for f in xrange(0, len(i), s):
148 148 # offset, size, base, linkrev, p1, p2, nodeid
149 149 e = struct.unpack(indexformat, i[f:f + s])
150 150 m[n] = (e[6], n)
151 151 self.index[n] = e
152 152 n += 1
153 153
154 154 self.nodemap = dict(m)
155 155 self.nodemap[nullid] = -1
156 156
157 157 def tip(self): return self.node(len(self.index) - 1)
158 158 def count(self): return len(self.index)
159 159 def node(self, rev): return (rev < 0) and nullid or self.index[rev][6]
160 160 def rev(self, node): return self.nodemap[node]
161 161 def linkrev(self, node): return self.index[self.nodemap[node]][3]
162 162 def parents(self, node):
163 163 if node == nullid: return (nullid, nullid)
164 164 return self.index[self.nodemap[node]][4:6]
165 165
166 166 def start(self, rev): return self.index[rev][0]
167 167 def length(self, rev): return self.index[rev][1]
168 168 def end(self, rev): return self.start(rev) + self.length(rev)
169 169 def base(self, rev): return self.index[rev][2]
170 170
171 171 def heads(self):
172 172 p = {}
173 173 h = []
174 174 for r in range(self.count() - 1, -1, -1):
175 175 n = self.node(r)
176 176 if n not in p:
177 177 h.append(n)
178 178 for pn in self.parents(n):
179 179 p[pn] = 1
180 180 return h
181 181
182 182 def children(self, node):
183 183 c = []
184 184 p = self.rev(node)
185 185 for r in range(p + 1, self.count()):
186 186 n = self.node(r)
187 187 for pn in self.parents(n):
188 188 if pn == p:
189 189 c.append(p)
190 190 continue
191 191 elif pn == nullid:
192 192 continue
193 193 return c
194 194
195 195 def lookup(self, id):
196 196 try:
197 197 rev = int(id)
198 198 if str(rev) != id: raise ValueError
199 199 if rev < 0: rev = self.count() + rev
200 200 if rev < 0 or rev >= self.count(): raise ValueError
201 201 return self.node(rev)
202 202 except (ValueError, OverflowError):
203 203 c = []
204 204 for n in self.nodemap:
205 205 if hex(n).startswith(id):
206 206 c.append(n)
207 207 if len(c) > 1: raise KeyError("Ambiguous identifier")
208 208 if len(c) < 1: raise KeyError("No match found")
209 209 return c[0]
210 210
211 211 return None
212 212
213 213 def diff(self, a, b):
214 214 return mdiff.textdiff(a, b)
215 215
216 216 def patches(self, t, pl):
217 217 return mdiff.patches(t, pl)
218 218
219 219 def delta(self, node):
220 220 r = self.rev(node)
221 221 b = self.base(r)
222 222 if r == b:
223 223 return self.diff(self.revision(self.node(r - 1)),
224 224 self.revision(node))
225 225 else:
226 226 f = self.opener(self.datafile)
227 227 f.seek(self.start(r))
228 228 data = f.read(self.length(r))
229 229 return decompress(data)
230 230
231 231 def revision(self, node):
232 232 if node == nullid: return ""
233 233 if self.cache and self.cache[0] == node: return self.cache[2]
234 234
235 235 text = None
236 236 rev = self.rev(node)
237 237 start, length, base, link, p1, p2, node = self.index[rev]
238 238 end = start + length
239 239 if base != rev: start = self.start(base)
240 240
241 241 if self.cache and self.cache[1] >= base and self.cache[1] < rev:
242 242 base = self.cache[1]
243 243 start = self.start(base + 1)
244 244 text = self.cache[2]
245 245 last = 0
246 246
247 247 f = self.opener(self.datafile)
248 248 f.seek(start)
249 249 data = f.read(end - start)
250 250
251 251 if not text:
252 252 last = self.length(base)
253 253 text = decompress(data[:last])
254 254
255 255 bins = []
256 256 for r in xrange(base + 1, rev + 1):
257 257 s = self.length(r)
258 258 bins.append(decompress(data[last:last + s]))
259 259 last = last + s
260 260
261 261 text = mdiff.patches(text, bins)
262 262
263 263 if node != hash(text, p1, p2):
264 264 raise IOError("integrity check failed on %s:%d"
265 265 % (self.datafile, rev))
266 266
267 267 self.cache = (node, rev, text)
268 268 return text
269 269
270 def addrevision(self, text, transaction, link, p1=None, p2=None):
270 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
271 271 if text is None: text = ""
272 272 if p1 is None: p1 = self.tip()
273 273 if p2 is None: p2 = nullid
274 274
275 275 node = hash(text, p1, p2)
276 276
277 277 if node in self.nodemap:
278 278 return node
279 279
280 280 n = self.count()
281 281 t = n - 1
282 282
283 283 if n:
284 284 base = self.base(t)
285 285 start = self.start(base)
286 286 end = self.end(t)
287 prev = self.revision(self.tip())
288 d = self.diff(prev, text)
287 if not d:
288 prev = self.revision(self.tip())
289 d = self.diff(prev, text)
289 290 data = compress(d)
290 291 dist = end - start + len(data)
291 292
292 293 # full versions are inserted when the needed deltas
293 294 # become comparable to the uncompressed text
294 295 if not n or dist > len(text) * 2:
295 296 data = compress(text)
296 297 base = n
297 298 else:
298 299 base = self.base(t)
299 300
300 301 offset = 0
301 302 if t >= 0:
302 303 offset = self.end(t)
303 304
304 305 e = (offset, len(data), base, link, p1, p2, node)
305 306
306 307 self.index.append(e)
307 308 self.nodemap[node] = n
308 309 entry = struct.pack(indexformat, *e)
309 310
310 311 transaction.add(self.datafile, e[0])
311 312 self.opener(self.datafile, "a").write(data)
312 313 transaction.add(self.indexfile, n * len(entry))
313 314 self.opener(self.indexfile, "a").write(entry)
314 315
315 316 self.cache = (node, n, text)
316 317 return node
317 318
318 319 def ancestor(self, a, b):
319 320 # calculate the distance of every node from root
320 321 dist = {nullid: 0}
321 322 for i in xrange(self.count()):
322 323 n = self.node(i)
323 324 p1, p2 = self.parents(n)
324 325 dist[n] = max(dist[p1], dist[p2]) + 1
325 326
326 327 # traverse ancestors in order of decreasing distance from root
327 328 def ancestors(node):
328 329 # we store negative distances because heap returns smallest member
329 330 h = [(-dist[node], node)]
330 331 seen = {}
331 332 earliest = self.count()
332 333 while h:
333 334 d, n = heapq.heappop(h)
334 335 if n not in seen:
335 336 seen[n] = 1
336 337 r = self.rev(n)
337 338 yield (-d, r, n)
338 339 for p in self.parents(n):
339 340 heapq.heappush(h, (-dist[p], p))
340 341
341 342 x = ancestors(a)
342 343 y = ancestors(b)
343 344 lx = x.next()
344 345 ly = y.next()
345 346
346 347 # increment each ancestor list until it is closer to root than
347 348 # the other, or they match
348 349 while 1:
349 350 if lx == ly:
350 351 return lx[2]
351 352 elif lx < ly:
352 353 ly = y.next()
353 354 elif lx > ly:
354 355 lx = x.next()
355 356
356 357 def group(self, linkmap):
357 358 # given a list of changeset revs, return a set of deltas and
358 359 # metadata corresponding to nodes. the first delta is
359 360 # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
360 361 # have this parent as it has all history before these
361 362 # changesets. parent is parent[0]
362 363
363 364 revs = []
364 365 needed = {}
365 366
366 367 # find file nodes/revs that match changeset revs
367 368 for i in xrange(0, self.count()):
368 369 if self.index[i][3] in linkmap:
369 370 revs.append(i)
370 371 needed[i] = 1
371 372
372 373 # if we don't have any revisions touched by these changesets, bail
373 374 if not revs:
374 375 yield struct.pack(">l", 0)
375 376 return
376 377
377 378 # add the parent of the first rev
378 379 p = self.parents(self.node(revs[0]))[0]
379 380 revs.insert(0, self.rev(p))
380 381
381 382 # for each delta that isn't contiguous in the log, we need to
382 383 # reconstruct the base, reconstruct the result, and then
383 384 # calculate the delta. We also need to do this where we've
384 385 # stored a full version and not a delta
385 386 for i in xrange(0, len(revs) - 1):
386 387 a, b = revs[i], revs[i + 1]
387 388 if a + 1 != b or self.base(b) == b:
388 389 for j in xrange(self.base(a), a + 1):
389 390 needed[j] = 1
390 391 for j in xrange(self.base(b), b + 1):
391 392 needed[j] = 1
392 393
393 394 # calculate spans to retrieve from datafile
394 395 needed = needed.keys()
395 396 needed.sort()
396 397 spans = []
397 398 oo = -1
398 399 ol = 0
399 400 for n in needed:
400 401 if n < 0: continue
401 402 o = self.start(n)
402 403 l = self.length(n)
403 404 if oo + ol == o: # can we merge with the previous?
404 405 nl = spans[-1][2]
405 406 nl.append((n, l))
406 407 ol += l
407 408 spans[-1] = (oo, ol, nl)
408 409 else:
409 410 oo = o
410 411 ol = l
411 412 spans.append((oo, ol, [(n, l)]))
412 413
413 414 # read spans in, divide up chunks
414 415 chunks = {}
415 416 for span in spans:
416 417 # we reopen the file for each span to make http happy for now
417 418 f = self.opener(self.datafile)
418 419 f.seek(span[0])
419 420 data = f.read(span[1])
420 421
421 422 # divide up the span
422 423 pos = 0
423 424 for r, l in span[2]:
424 425 chunks[r] = decompress(data[pos: pos + l])
425 426 pos += l
426 427
427 428 # helper to reconstruct intermediate versions
428 429 def construct(text, base, rev):
429 430 bins = [chunks[r] for r in xrange(base + 1, rev + 1)]
430 431 return mdiff.patches(text, bins)
431 432
432 433 # build deltas
433 434 deltas = []
434 435 for d in xrange(0, len(revs) - 1):
435 436 a, b = revs[d], revs[d + 1]
436 437 n = self.node(b)
437 438
438 439 # do we need to construct a new delta?
439 440 if a + 1 != b or self.base(b) == b:
440 441 if a >= 0:
441 442 base = self.base(a)
442 443 ta = chunks[self.base(a)]
443 444 ta = construct(ta, base, a)
444 445 else:
445 446 ta = ""
446 447
447 448 base = self.base(b)
448 449 if a > base:
449 450 base = a
450 451 tb = ta
451 452 else:
452 453 tb = chunks[self.base(b)]
453 454 tb = construct(tb, base, b)
454 455 d = self.diff(ta, tb)
455 456 else:
456 457 d = chunks[b]
457 458
458 459 p = self.parents(n)
459 460 meta = n + p[0] + p[1] + linkmap[self.linkrev(n)]
460 461 l = struct.pack(">l", len(meta) + len(d) + 4)
461 462 yield l
462 463 yield meta
463 464 yield d
464 465
465 466 yield struct.pack(">l", 0)
466 467
467 468 def addgroup(self, revs, linkmapper, transaction, unique = 0):
468 469 # given a set of deltas, add them to the revision log. the
469 470 # first delta is against its parent, which should be in our
470 471 # log, the rest are against the previous delta.
471 472
472 473 # track the base of the current delta log
473 474 r = self.count()
474 475 t = r - 1
475 476 node = nullid
476 477
477 478 base = prev = -1
478 479 start = end = 0
479 480 if r:
480 481 start = self.start(self.base(t))
481 482 end = self.end(t)
482 483 measure = self.length(self.base(t))
483 484 base = self.base(t)
484 485 prev = self.tip()
485 486
486 487 transaction.add(self.datafile, end)
487 488 transaction.add(self.indexfile, r * struct.calcsize(indexformat))
488 489 dfh = self.opener(self.datafile, "a")
489 490 ifh = self.opener(self.indexfile, "a")
490 491
491 492 # loop through our set of deltas
492 493 chain = None
493 494 for chunk in revs:
494 495 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
495 496 link = linkmapper(cs)
496 497 if node in self.nodemap:
497 498 # this can happen if two branches make the same change
498 499 if unique:
499 500 raise "already have %s" % hex(node[:4])
500 501 continue
501 502 delta = chunk[80:]
502 503
503 504 if not chain:
504 505 # retrieve the parent revision of the delta chain
505 506 chain = p1
506 507 if not chain in self.nodemap:
507 508 raise "unknown base %s" % short(chain[:4])
508 509
509 510 # full versions are inserted when the needed deltas become
510 511 # comparable to the uncompressed text or when the previous
511 512 # version is not the one we have a delta against. We use
512 513 # the size of the previous full rev as a proxy for the
513 514 # current size.
514 515
515 516 if chain == prev:
516 517 cdelta = compress(delta)
517 518
518 519 if chain != prev or (end - start + len(cdelta)) > measure * 2:
519 520 # flush our writes here so we can read it in revision
520 521 dfh.flush()
521 522 ifh.flush()
522 523 text = self.revision(chain)
523 524 text = self.patches(text, [delta])
524 525 chk = self.addrevision(text, transaction, link, p1, p2)
525 526 if chk != node:
526 527 raise "consistency error adding group"
527 528 measure = len(text)
528 529 else:
529 530 e = (end, len(cdelta), self.base(t), link, p1, p2, node)
530 531 self.index.append(e)
531 532 self.nodemap[node] = r
532 533 dfh.write(cdelta)
533 534 ifh.write(struct.pack(indexformat, *e))
534 535
535 536 t, r, chain, prev = r, r + 1, node, node
536 537 start = self.start(self.base(t))
537 538 end = self.end(t)
538 539
539 540 dfh.close()
540 541 ifh.close()
541 542 return node
General Comments 0
You need to be logged in to leave comments. Login now