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