##// END OF EJS Templates
Move hex/bin bits to revlog...
mpm@selenic.com -
r37:a8811676 default
parent child Browse files
Show More
@@ -1,614 +1,613 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 import sys, struct, sha, socket, os, time, base64, re, urllib2, binascii
8 import sys, struct, sha, socket, os, time, base64, re, urllib2
9 9 import urllib
10 10 from mercurial import byterange
11 11 from mercurial.transaction import *
12 12 from mercurial.revlog import *
13 13
14 def hex(node): return binascii.hexlify(node)
15 def bin(node): return binascii.unhexlify(node)
16
17 14 class filelog(revlog):
18 15 def __init__(self, opener, path):
19 16 s = self.encodepath(path)
20 17 revlog.__init__(self, opener, os.path.join("data", s + "i"),
21 18 os.path.join("data", s))
22 19
23 20 def encodepath(self, path):
24 21 s = sha.sha(path).digest()
25 22 s = base64.encodestring(s)[:-3]
26 23 s = re.sub("\+", "%", s)
27 24 s = re.sub("/", "_", s)
28 25 return s
29 26
30 27 def read(self, node):
31 28 return self.revision(node)
32 29 def add(self, text, transaction, link, p1=None, p2=None):
33 30 return self.addrevision(text, transaction, link, p1, p2)
34 31
35 32 def resolvedag(self, old, new, transaction, link):
36 33 """resolve unmerged heads in our DAG"""
37 34 if old == new: return None
38 35 a = self.ancestor(old, new)
39 36 if old == a: return new
40 37 return self.merge3(old, new, a, transaction, link)
41 38
42 39 def merge3(self, my, other, base, transaction, link):
43 40 """perform a 3-way merge and append the result"""
44 41 def temp(prefix, node):
45 42 (fd, name) = tempfile.mkstemp(prefix)
46 43 f = os.fdopen(fd, "w")
47 44 f.write(self.revision(node))
48 45 f.close()
49 46 return name
50 47
51 48 a = temp("local", my)
52 49 b = temp("remote", other)
53 50 c = temp("parent", base)
54 51
55 52 cmd = os.environ["HGMERGE"]
56 53 r = os.system("%s %s %s %s" % (cmd, a, b, c))
57 54 if r:
58 55 raise "Merge failed, implement rollback!"
59 56
60 57 t = open(a).read()
61 58 os.unlink(a)
62 59 os.unlink(b)
63 60 os.unlink(c)
64 61 return self.addrevision(t, transaction, link, my, other)
65 62
66 63 def merge(self, other, transaction, linkseq, link):
67 64 """perform a merge and resolve resulting heads"""
68 65 (o, n) = self.mergedag(other, transaction, linkseq)
69 66 return self.resolvedag(o, n, transaction, link)
70 67
71 68 class manifest(revlog):
72 69 def __init__(self, opener):
73 70 self.mapcache = None
74 71 self.listcache = None
75 72 self.addlist = None
76 73 revlog.__init__(self, opener, "00manifest.i", "00manifest.d")
77 74
78 75 def read(self, node):
79 76 if self.mapcache and self.mapcache[0] == node:
80 77 return self.mapcache[1]
81 78 text = self.revision(node)
82 79 map = {}
83 80 self.listcache = (text, text.splitlines(1))
84 81 for l in self.listcache[1]:
85 82 (f, n) = l.split('\0')
86 83 map[f] = bin(n[:40])
87 84 self.mapcache = (node, map)
88 85 return map
89 86
90 87 def diff(self, a, b):
91 88 # this is sneaky, as we're not actually using a and b
92 89 if self.listcache and len(self.listcache[0]) == len(a):
93 90 return mdiff.diff(self.listcache[1], self.addlist, 1)
94 91 else:
95 92 return mdiff.diff(a, b)
96 93
97 94 def add(self, map, transaction, link, p1=None, p2=None):
98 95 files = map.keys()
99 96 files.sort()
100 97
101 98 self.addlist = ["%s\000%s\n" % (f, hex(map[f])) for f in files]
102 99 text = "".join(self.addlist)
103 100
104 101 n = self.addrevision(text, transaction, link, p1, p2)
105 102 self.mapcache = (n, map)
106 103 self.listcache = (text, self.addlist)
107 104
108 105 return n
109 106
110 107 class changelog(revlog):
111 108 def __init__(self, opener):
112 109 revlog.__init__(self, opener, "00changelog.i", "00changelog.d")
113 110
114 111 def extract(self, text):
112 if not text:
113 return (nullid, "", 0, [], "")
115 114 last = text.index("\n\n")
116 115 desc = text[last + 2:]
117 116 l = text[:last].splitlines()
118 117 manifest = bin(l[0])
119 118 user = l[1]
120 119 date = l[2]
121 120 files = l[3:]
122 121 return (manifest, user, date, files, desc)
123 122
124 123 def read(self, node):
125 124 return self.extract(self.revision(node))
126 125
127 126 def add(self, manifest, list, desc, transaction, p1=None, p2=None):
128 127 try: user = os.environ["HGUSER"]
129 128 except: user = os.environ["LOGNAME"] + '@' + socket.getfqdn()
130 129 date = "%d %d" % (time.time(), time.timezone)
131 130 list.sort()
132 131 l = [hex(manifest), user, date] + list + ["", desc]
133 132 text = "\n".join(l)
134 133 return self.addrevision(text, transaction, self.count(), p1, p2)
135 134
136 135 def merge3(self, my, other, base):
137 136 pass
138 137
139 138 class dircache:
140 139 def __init__(self, opener, ui):
141 140 self.opener = opener
142 141 self.dirty = 0
143 142 self.ui = ui
144 143 self.map = None
145 144 def __del__(self):
146 145 if self.dirty: self.write()
147 146 def __getitem__(self, key):
148 147 try:
149 148 return self.map[key]
150 149 except TypeError:
151 150 self.read()
152 151 return self[key]
153 152
154 153 def read(self):
155 154 if self.map is not None: return self.map
156 155
157 156 self.map = {}
158 157 try:
159 158 st = self.opener("dircache").read()
160 159 except: return
161 160
162 161 pos = 0
163 162 while pos < len(st):
164 163 e = struct.unpack(">llll", st[pos:pos+16])
165 164 l = e[3]
166 165 pos += 16
167 166 f = st[pos:pos + l]
168 167 self.map[f] = e[:3]
169 168 pos += l
170 169
171 170 def update(self, files):
172 171 if not files: return
173 172 self.read()
174 173 self.dirty = 1
175 174 for f in files:
176 175 try:
177 176 s = os.stat(f)
178 177 self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
179 178 except IOError:
180 179 self.remove(f)
181 180
182 181 def taint(self, files):
183 182 if not files: return
184 183 self.read()
185 184 self.dirty = 1
186 185 for f in files:
187 186 self.map[f] = (0, -1, 0)
188 187
189 188 def remove(self, files):
190 189 if not files: return
191 190 self.read()
192 191 self.dirty = 1
193 192 for f in files:
194 193 try:
195 194 del self.map[f]
196 195 except KeyError:
197 196 self.ui.warn("Not in dircache: %s\n" % f)
198 197 pass
199 198
200 199 def clear(self):
201 200 self.map = {}
202 201 self.dirty = 1
203 202
204 203 def write(self):
205 204 st = self.opener("dircache", "w")
206 205 for f, e in self.map.items():
207 206 e = struct.pack(">llll", e[0], e[1], e[2], len(f))
208 207 st.write(e + f)
209 208 self.dirty = 0
210 209
211 210 def copy(self):
212 211 self.read()
213 212 return self.map.copy()
214 213
215 214 # used to avoid circular references so destructors work
216 215 def opener(base):
217 216 p = base
218 217 def o(path, mode="r"):
219 218 if p[:7] == "http://":
220 219 f = os.path.join(p, urllib.quote(path))
221 220 return httprangereader(f)
222 221
223 222 f = os.path.join(p, path)
224 223
225 224 if mode != "r" and os.path.isfile(f):
226 225 s = os.stat(f)
227 226 if s.st_nlink > 1:
228 227 file(f + ".tmp", "w").write(file(f).read())
229 228 os.rename(f+".tmp", f)
230 229
231 230 return file(f, mode)
232 231
233 232 return o
234 233
235 234 class repository:
236 235 def __init__(self, ui, path=None, create=0):
237 236 self.remote = 0
238 237 if path and path[:7] == "http://":
239 238 self.remote = 1
240 239 self.path = path
241 240 else:
242 241 if not path:
243 242 p = os.getcwd()
244 243 while not os.path.isdir(os.path.join(p, ".hg")):
245 244 p = os.path.dirname(p)
246 245 if p == "/": raise "No repo found"
247 246 path = p
248 247 self.path = os.path.join(path, ".hg")
249 248
250 249 self.root = path
251 250 self.ui = ui
252 251
253 252 if create:
254 253 os.mkdir(self.path)
255 254 os.mkdir(self.join("data"))
256 255
257 256 self.opener = opener(self.path)
258 257 self.manifest = manifest(self.opener)
259 258 self.changelog = changelog(self.opener)
260 259 self.ignorelist = None
261 260
262 261 if not self.remote:
263 262 self.dircache = dircache(self.opener, ui)
264 263 try:
265 264 self.current = bin(self.opener("current").read())
266 265 except IOError:
267 266 self.current = None
268 267
269 268 def setcurrent(self, node):
270 269 self.current = node
271 270 self.opener("current", "w").write(hex(node))
272 271
273 272 def ignore(self, f):
274 273 if self.ignorelist is None:
275 274 self.ignorelist = []
276 275 try:
277 276 l = open(os.path.join(self.root, ".hgignore")).readlines()
278 277 for pat in l:
279 278 if pat != "\n":
280 279 self.ignorelist.append(re.compile(pat[:-1]))
281 280 except IOError: pass
282 281 for pat in self.ignorelist:
283 282 if pat.search(f): return True
284 283 return False
285 284
286 285 def join(self, f):
287 286 return os.path.join(self.path, f)
288 287
289 288 def file(self, f):
290 289 return filelog(self.opener, f)
291 290
292 291 def transaction(self):
293 292 return transaction(self.opener, self.join("journal"))
294 293
295 294 def merge(self, other):
296 295 tr = self.transaction()
297 296 changed = {}
298 297 new = {}
299 298 seqrev = self.changelog.count()
300 299 # some magic to allow fiddling in nested scope
301 300 nextrev = [seqrev]
302 301
303 302 # helpers for back-linking file revisions to local changeset
304 303 # revisions so we can immediately get to changeset from annotate
305 304 def accumulate(text):
306 305 # track which files are added in which changeset and the
307 306 # corresponding _local_ changeset revision
308 307 files = self.changelog.extract(text)[3]
309 308 for f in files:
310 309 changed.setdefault(f, []).append(nextrev[0])
311 310 nextrev[0] += 1
312 311
313 312 def seq(start):
314 313 while 1:
315 314 yield start
316 315 start += 1
317 316
318 317 def lseq(l):
319 318 for r in l:
320 319 yield r
321 320
322 321 # begin the import/merge of changesets
323 322 self.ui.status("merging new changesets\n")
324 323 (co, cn) = self.changelog.mergedag(other.changelog, tr,
325 324 seq(seqrev), accumulate)
326 325 resolverev = self.changelog.count()
327 326
328 327 # is there anything to do?
329 328 if co == cn:
330 329 tr.close()
331 330 return
332 331
333 332 # do we need to resolve?
334 333 simple = (co == self.changelog.ancestor(co, cn))
335 334
336 335 # merge all files changed by the changesets,
337 336 # keeping track of the new tips
338 337 changelist = changed.keys()
339 338 changelist.sort()
340 339 for f in changelist:
341 340 sys.stdout.write(".")
342 341 sys.stdout.flush()
343 342 r = self.file(f)
344 343 node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
345 344 if node:
346 345 new[f] = node
347 346 sys.stdout.write("\n")
348 347
349 348 # begin the merge of the manifest
350 349 self.ui.status("merging manifests\n")
351 350 (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
352 351
353 352 # For simple merges, we don't need to resolve manifests or changesets
354 353 if simple:
355 354 tr.close()
356 355 return
357 356
358 357 ma = self.manifest.ancestor(mm, mo)
359 358
360 359 # resolve the manifest to point to all the merged files
361 360 self.ui.status("resolving manifests\n")
362 361 mmap = self.manifest.read(mm) # mine
363 362 omap = self.manifest.read(mo) # other
364 363 amap = self.manifest.read(ma) # ancestor
365 364 nmap = {}
366 365
367 366 for f, mid in mmap.iteritems():
368 367 if f in omap:
369 368 if mid != omap[f]:
370 369 nmap[f] = new.get(f, mid) # use merged version
371 370 else:
372 371 nmap[f] = new.get(f, mid) # they're the same
373 372 del omap[f]
374 373 elif f in amap:
375 374 if mid != amap[f]:
376 375 pass # we should prompt here
377 376 else:
378 377 pass # other deleted it
379 378 else:
380 379 nmap[f] = new.get(f, mid) # we created it
381 380
382 381 del mmap
383 382
384 383 for f, oid in omap.iteritems():
385 384 if f in amap:
386 385 if oid != amap[f]:
387 386 pass # this is the nasty case, we should prompt
388 387 else:
389 388 pass # probably safe
390 389 else:
391 390 nmap[f] = new.get(f, oid) # remote created it
392 391
393 392 del omap
394 393 del amap
395 394
396 395 node = self.manifest.add(nmap, tr, resolverev, mm, mo)
397 396
398 397 # Now all files and manifests are merged, we add the changed files
399 398 # and manifest id to the changelog
400 399 self.ui.status("committing merge changeset\n")
401 400 new = new.keys()
402 401 new.sort()
403 402 if co == cn: cn = -1
404 403
405 404 edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
406 405 edittext = self.ui.edit(edittext)
407 406 n = self.changelog.add(node, new, edittext, tr, co, cn)
408 407
409 408 tr.close()
410 409
411 410 def commit(self, parent, update = None, text = ""):
412 411 tr = self.transaction()
413 412
414 413 try:
415 414 remove = [ l[:-1] for l in self.opener("to-remove") ]
416 415 os.unlink(self.join("to-remove"))
417 416
418 417 except IOError:
419 418 remove = []
420 419
421 420 if update == None:
422 421 update = self.diffdir(self.root, parent)[0]
423 422
424 423 # check in files
425 424 new = {}
426 425 linkrev = self.changelog.count()
427 426 for f in update:
428 427 try:
429 428 t = file(f).read()
430 429 except IOError:
431 430 remove.append(f)
432 431 continue
433 432 r = self.file(f)
434 433 new[f] = r.add(t, tr, linkrev)
435 434
436 435 # update manifest
437 436 mmap = self.manifest.read(self.manifest.tip())
438 437 mmap.update(new)
439 438 for f in remove:
440 439 del mmap[f]
441 440 mnode = self.manifest.add(mmap, tr, linkrev)
442 441
443 442 # add changeset
444 443 new = new.keys()
445 444 new.sort()
446 445
447 446 edittext = text + "\n"+"".join(["HG: changed %s\n" % f for f in new])
448 447 edittext += "".join(["HG: removed %s\n" % f for f in remove])
449 448 edittext = self.ui.edit(edittext)
450 449
451 450 n = self.changelog.add(mnode, new, edittext, tr)
452 451 tr.close()
453 452
454 453 self.setcurrent(n)
455 454 self.dircache.update(new)
456 455 self.dircache.remove(remove)
457 456
458 457 def checkdir(self, path):
459 458 d = os.path.dirname(path)
460 459 if not d: return
461 460 if not os.path.isdir(d):
462 461 self.checkdir(d)
463 462 os.mkdir(d)
464 463
465 464 def checkout(self, node):
466 465 # checkout is really dumb at the moment
467 466 # it ought to basically merge
468 467 change = self.changelog.read(node)
469 468 mmap = self.manifest.read(change[0])
470 469
471 470 l = mmap.keys()
472 471 l.sort()
473 472 stats = []
474 473 for f in l:
475 474 r = self.file(f)
476 475 t = r.revision(mmap[f])
477 476 try:
478 477 file(f, "w").write(t)
479 478 except:
480 479 self.checkdir(f)
481 480 file(f, "w").write(t)
482 481
483 482 self.setcurrent(node)
484 483 self.dircache.clear()
485 484 self.dircache.update(l)
486 485
487 486 def diffdir(self, path, changeset):
488 487 changed = []
489 488 mf = {}
490 489 added = []
491 490
492 491 if changeset:
493 492 change = self.changelog.read(changeset)
494 493 mf = self.manifest.read(change[0])
495 494
496 495 if changeset == self.current:
497 496 dc = self.dircache.copy()
498 497 else:
499 498 dc = dict.fromkeys(mf)
500 499
501 500 def fcmp(fn):
502 501 t1 = file(fn).read()
503 502 t2 = self.file(fn).revision(mf[fn])
504 503 return cmp(t1, t2)
505 504
506 505 for dir, subdirs, files in os.walk(self.root):
507 506 d = dir[len(self.root)+1:]
508 507 if ".hg" in subdirs: subdirs.remove(".hg")
509 508
510 509 for f in files:
511 510 fn = os.path.join(d, f)
512 511 try: s = os.stat(fn)
513 512 except: continue
514 513 if fn in dc:
515 514 c = dc[fn]
516 515 del dc[fn]
517 516 if not c:
518 517 if fcmp(fn):
519 518 changed.append(fn)
520 519 elif c[1] != s.st_size:
521 520 changed.append(fn)
522 521 elif c[0] != s.st_mode or c[2] != s.st_mtime:
523 522 if fcmp(fn):
524 523 changed.append(fn)
525 524 else:
526 525 if self.ignore(fn): continue
527 526 added.append(fn)
528 527
529 528 deleted = dc.keys()
530 529 deleted.sort()
531 530
532 531 return (changed, added, deleted)
533 532
534 533 def diffrevs(self, node1, node2):
535 534 changed, added = [], []
536 535
537 536 change = self.changelog.read(node1)
538 537 mf1 = self.manifest.read(change[0])
539 538 change = self.changelog.read(node2)
540 539 mf2 = self.manifest.read(change[0])
541 540
542 541 for fn in mf2:
543 542 if mf1.has_key(fn):
544 543 if mf1[fn] != mf2[fn]:
545 544 changed.append(fn)
546 545 del mf1[fn]
547 546 else:
548 547 added.append(fn)
549 548
550 549 deleted = mf1.keys()
551 550 deleted.sort()
552 551
553 552 return (changed, added, deleted)
554 553
555 554 def add(self, list):
556 555 self.dircache.taint(list)
557 556
558 557 def remove(self, list):
559 558 dl = self.opener("to-remove", "a")
560 559 for f in list:
561 560 dl.write(f + "\n")
562 561
563 562 class ui:
564 563 def __init__(self, verbose=False, debug=False):
565 564 self.verbose = verbose
566 565 def write(self, *args):
567 566 for a in args:
568 567 sys.stdout.write(str(a))
569 568 def prompt(self, msg, pat):
570 569 while 1:
571 570 sys.stdout.write(msg)
572 571 r = sys.stdin.readline()[:-1]
573 572 if re.match(pat, r):
574 573 return r
575 574 def status(self, *msg):
576 575 self.write(*msg)
577 576 def warn(self, msg):
578 577 self.write(*msg)
579 578 def note(self, msg):
580 579 if self.verbose: self.write(*msg)
581 580 def debug(self, msg):
582 581 if self.debug: self.write(*msg)
583 582 def edit(self, text):
584 583 (fd, name) = tempfile.mkstemp("hg")
585 584 f = os.fdopen(fd, "w")
586 585 f.write(text)
587 586 f.close()
588 587
589 588 editor = os.environ.get("EDITOR", "vi")
590 589 r = os.system("%s %s" % (editor, name))
591 590 if r:
592 591 raise "Edit failed!"
593 592
594 593 t = open(name).read()
595 594 t = re.sub("(?m)^HG:.*\n", "", t)
596 595
597 596 return t
598 597
599 598
600 599 class httprangereader:
601 600 def __init__(self, url):
602 601 self.url = url
603 602 self.pos = 0
604 603 def seek(self, pos):
605 604 self.pos = pos
606 605 def read(self, bytes=None):
607 606 opener = urllib2.build_opener(byterange.HTTPRangeHandler())
608 607 urllib2.install_opener(opener)
609 608 req = urllib2.Request(self.url)
610 609 end = ''
611 610 if bytes: end = self.pos + bytes
612 611 req.add_header('Range', 'bytes=%d-%s' % (self.pos, end))
613 612 f = urllib2.urlopen(req)
614 613 return f.read()
General Comments 0
You need to be logged in to leave comments. Login now