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