##// END OF EJS Templates
changelog: don't use generaldelta
Sune Foldager -
r14334:85c82ebc default
parent child Browse files
Show More
@@ -1,235 +1,239 b''
1 # changelog.py - changelog class for mercurial
1 # changelog.py - changelog class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import bin, hex, nullid
8 from node import bin, hex, nullid
9 from i18n import _
9 from i18n import _
10 import util, error, revlog, encoding
10 import util, error, revlog, encoding
11
11
12 def _string_escape(text):
12 def _string_escape(text):
13 """
13 """
14 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
14 >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
15 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
15 >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
16 >>> s
16 >>> s
17 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
17 'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
18 >>> res = _string_escape(s)
18 >>> res = _string_escape(s)
19 >>> s == res.decode('string_escape')
19 >>> s == res.decode('string_escape')
20 True
20 True
21 """
21 """
22 # subset of the string_escape codec
22 # subset of the string_escape codec
23 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
23 text = text.replace('\\', '\\\\').replace('\n', '\\n').replace('\r', '\\r')
24 return text.replace('\0', '\\0')
24 return text.replace('\0', '\\0')
25
25
26 def decodeextra(text):
26 def decodeextra(text):
27 extra = {}
27 extra = {}
28 for l in text.split('\0'):
28 for l in text.split('\0'):
29 if l:
29 if l:
30 k, v = l.decode('string_escape').split(':', 1)
30 k, v = l.decode('string_escape').split(':', 1)
31 extra[k] = v
31 extra[k] = v
32 return extra
32 return extra
33
33
34 def encodeextra(d):
34 def encodeextra(d):
35 # keys must be sorted to produce a deterministic changelog entry
35 # keys must be sorted to produce a deterministic changelog entry
36 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
36 items = [_string_escape('%s:%s' % (k, d[k])) for k in sorted(d)]
37 return "\0".join(items)
37 return "\0".join(items)
38
38
39 class appender(object):
39 class appender(object):
40 '''the changelog index must be updated last on disk, so we use this class
40 '''the changelog index must be updated last on disk, so we use this class
41 to delay writes to it'''
41 to delay writes to it'''
42 def __init__(self, fp, buf):
42 def __init__(self, fp, buf):
43 self.data = buf
43 self.data = buf
44 self.fp = fp
44 self.fp = fp
45 self.offset = fp.tell()
45 self.offset = fp.tell()
46 self.size = util.fstat(fp).st_size
46 self.size = util.fstat(fp).st_size
47
47
48 def end(self):
48 def end(self):
49 return self.size + len("".join(self.data))
49 return self.size + len("".join(self.data))
50 def tell(self):
50 def tell(self):
51 return self.offset
51 return self.offset
52 def flush(self):
52 def flush(self):
53 pass
53 pass
54 def close(self):
54 def close(self):
55 self.fp.close()
55 self.fp.close()
56
56
57 def seek(self, offset, whence=0):
57 def seek(self, offset, whence=0):
58 '''virtual file offset spans real file and data'''
58 '''virtual file offset spans real file and data'''
59 if whence == 0:
59 if whence == 0:
60 self.offset = offset
60 self.offset = offset
61 elif whence == 1:
61 elif whence == 1:
62 self.offset += offset
62 self.offset += offset
63 elif whence == 2:
63 elif whence == 2:
64 self.offset = self.end() + offset
64 self.offset = self.end() + offset
65 if self.offset < self.size:
65 if self.offset < self.size:
66 self.fp.seek(self.offset)
66 self.fp.seek(self.offset)
67
67
68 def read(self, count=-1):
68 def read(self, count=-1):
69 '''only trick here is reads that span real file and data'''
69 '''only trick here is reads that span real file and data'''
70 ret = ""
70 ret = ""
71 if self.offset < self.size:
71 if self.offset < self.size:
72 s = self.fp.read(count)
72 s = self.fp.read(count)
73 ret = s
73 ret = s
74 self.offset += len(s)
74 self.offset += len(s)
75 if count > 0:
75 if count > 0:
76 count -= len(s)
76 count -= len(s)
77 if count != 0:
77 if count != 0:
78 doff = self.offset - self.size
78 doff = self.offset - self.size
79 self.data.insert(0, "".join(self.data))
79 self.data.insert(0, "".join(self.data))
80 del self.data[1:]
80 del self.data[1:]
81 s = self.data[0][doff:doff + count]
81 s = self.data[0][doff:doff + count]
82 self.offset += len(s)
82 self.offset += len(s)
83 ret += s
83 ret += s
84 return ret
84 return ret
85
85
86 def write(self, s):
86 def write(self, s):
87 self.data.append(str(s))
87 self.data.append(str(s))
88 self.offset += len(s)
88 self.offset += len(s)
89
89
90 def delayopener(opener, target, divert, buf):
90 def delayopener(opener, target, divert, buf):
91 def o(name, mode='r'):
91 def o(name, mode='r'):
92 if name != target:
92 if name != target:
93 return opener(name, mode)
93 return opener(name, mode)
94 if divert:
94 if divert:
95 return opener(name + ".a", mode.replace('a', 'w'))
95 return opener(name + ".a", mode.replace('a', 'w'))
96 # otherwise, divert to memory
96 # otherwise, divert to memory
97 return appender(opener(name, mode), buf)
97 return appender(opener(name, mode), buf)
98 return o
98 return o
99
99
100 class changelog(revlog.revlog):
100 class changelog(revlog.revlog):
101 def __init__(self, opener):
101 def __init__(self, opener):
102 revlog.revlog.__init__(self, opener, "00changelog.i")
102 revlog.revlog.__init__(self, opener, "00changelog.i")
103 if self._initempty:
104 # changelogs don't benefit from generaldelta
105 self.version &= ~revlog.REVLOGGENERALDELTA
106 self._generaldelta = False
103 self._realopener = opener
107 self._realopener = opener
104 self._delayed = False
108 self._delayed = False
105 self._divert = False
109 self._divert = False
106
110
107 def delayupdate(self):
111 def delayupdate(self):
108 "delay visibility of index updates to other readers"
112 "delay visibility of index updates to other readers"
109 self._delayed = True
113 self._delayed = True
110 self._divert = (len(self) == 0)
114 self._divert = (len(self) == 0)
111 self._delaybuf = []
115 self._delaybuf = []
112 self.opener = delayopener(self._realopener, self.indexfile,
116 self.opener = delayopener(self._realopener, self.indexfile,
113 self._divert, self._delaybuf)
117 self._divert, self._delaybuf)
114
118
115 def finalize(self, tr):
119 def finalize(self, tr):
116 "finalize index updates"
120 "finalize index updates"
117 self._delayed = False
121 self._delayed = False
118 self.opener = self._realopener
122 self.opener = self._realopener
119 # move redirected index data back into place
123 # move redirected index data back into place
120 if self._divert:
124 if self._divert:
121 nfile = self.opener(self.indexfile + ".a")
125 nfile = self.opener(self.indexfile + ".a")
122 n = nfile.name
126 n = nfile.name
123 nfile.close()
127 nfile.close()
124 util.rename(n, n[:-2])
128 util.rename(n, n[:-2])
125 elif self._delaybuf:
129 elif self._delaybuf:
126 fp = self.opener(self.indexfile, 'a')
130 fp = self.opener(self.indexfile, 'a')
127 fp.write("".join(self._delaybuf))
131 fp.write("".join(self._delaybuf))
128 fp.close()
132 fp.close()
129 self._delaybuf = []
133 self._delaybuf = []
130 # split when we're done
134 # split when we're done
131 self.checkinlinesize(tr)
135 self.checkinlinesize(tr)
132
136
133 def readpending(self, file):
137 def readpending(self, file):
134 r = revlog.revlog(self.opener, file)
138 r = revlog.revlog(self.opener, file)
135 self.index = r.index
139 self.index = r.index
136 self.nodemap = r.nodemap
140 self.nodemap = r.nodemap
137 self._chunkcache = r._chunkcache
141 self._chunkcache = r._chunkcache
138
142
139 def writepending(self):
143 def writepending(self):
140 "create a file containing the unfinalized state for pretxnchangegroup"
144 "create a file containing the unfinalized state for pretxnchangegroup"
141 if self._delaybuf:
145 if self._delaybuf:
142 # make a temporary copy of the index
146 # make a temporary copy of the index
143 fp1 = self._realopener(self.indexfile)
147 fp1 = self._realopener(self.indexfile)
144 fp2 = self._realopener(self.indexfile + ".a", "w")
148 fp2 = self._realopener(self.indexfile + ".a", "w")
145 fp2.write(fp1.read())
149 fp2.write(fp1.read())
146 # add pending data
150 # add pending data
147 fp2.write("".join(self._delaybuf))
151 fp2.write("".join(self._delaybuf))
148 fp2.close()
152 fp2.close()
149 # switch modes so finalize can simply rename
153 # switch modes so finalize can simply rename
150 self._delaybuf = []
154 self._delaybuf = []
151 self._divert = True
155 self._divert = True
152
156
153 if self._divert:
157 if self._divert:
154 return True
158 return True
155
159
156 return False
160 return False
157
161
158 def checkinlinesize(self, tr, fp=None):
162 def checkinlinesize(self, tr, fp=None):
159 if not self._delayed:
163 if not self._delayed:
160 revlog.revlog.checkinlinesize(self, tr, fp)
164 revlog.revlog.checkinlinesize(self, tr, fp)
161
165
162 def read(self, node):
166 def read(self, node):
163 """
167 """
164 format used:
168 format used:
165 nodeid\n : manifest node in ascii
169 nodeid\n : manifest node in ascii
166 user\n : user, no \n or \r allowed
170 user\n : user, no \n or \r allowed
167 time tz extra\n : date (time is int or float, timezone is int)
171 time tz extra\n : date (time is int or float, timezone is int)
168 : extra is metadatas, encoded and separated by '\0'
172 : extra is metadatas, encoded and separated by '\0'
169 : older versions ignore it
173 : older versions ignore it
170 files\n\n : files modified by the cset, no \n or \r allowed
174 files\n\n : files modified by the cset, no \n or \r allowed
171 (.*) : comment (free text, ideally utf-8)
175 (.*) : comment (free text, ideally utf-8)
172
176
173 changelog v0 doesn't use extra
177 changelog v0 doesn't use extra
174 """
178 """
175 text = self.revision(node)
179 text = self.revision(node)
176 if not text:
180 if not text:
177 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
181 return (nullid, "", (0, 0), [], "", {'branch': 'default'})
178 last = text.index("\n\n")
182 last = text.index("\n\n")
179 desc = encoding.tolocal(text[last + 2:])
183 desc = encoding.tolocal(text[last + 2:])
180 l = text[:last].split('\n')
184 l = text[:last].split('\n')
181 manifest = bin(l[0])
185 manifest = bin(l[0])
182 user = encoding.tolocal(l[1])
186 user = encoding.tolocal(l[1])
183
187
184 extra_data = l[2].split(' ', 2)
188 extra_data = l[2].split(' ', 2)
185 if len(extra_data) != 3:
189 if len(extra_data) != 3:
186 time = float(extra_data.pop(0))
190 time = float(extra_data.pop(0))
187 try:
191 try:
188 # various tools did silly things with the time zone field.
192 # various tools did silly things with the time zone field.
189 timezone = int(extra_data[0])
193 timezone = int(extra_data[0])
190 except ValueError:
194 except ValueError:
191 timezone = 0
195 timezone = 0
192 extra = {}
196 extra = {}
193 else:
197 else:
194 time, timezone, extra = extra_data
198 time, timezone, extra = extra_data
195 time, timezone = float(time), int(timezone)
199 time, timezone = float(time), int(timezone)
196 extra = decodeextra(extra)
200 extra = decodeextra(extra)
197 if not extra.get('branch'):
201 if not extra.get('branch'):
198 extra['branch'] = 'default'
202 extra['branch'] = 'default'
199 files = l[3:]
203 files = l[3:]
200 return (manifest, user, (time, timezone), files, desc, extra)
204 return (manifest, user, (time, timezone), files, desc, extra)
201
205
202 def add(self, manifest, files, desc, transaction, p1, p2,
206 def add(self, manifest, files, desc, transaction, p1, p2,
203 user, date=None, extra=None):
207 user, date=None, extra=None):
204 user = user.strip()
208 user = user.strip()
205 # An empty username or a username with a "\n" will make the
209 # An empty username or a username with a "\n" will make the
206 # revision text contain two "\n\n" sequences -> corrupt
210 # revision text contain two "\n\n" sequences -> corrupt
207 # repository since read cannot unpack the revision.
211 # repository since read cannot unpack the revision.
208 if not user:
212 if not user:
209 raise error.RevlogError(_("empty username"))
213 raise error.RevlogError(_("empty username"))
210 if "\n" in user:
214 if "\n" in user:
211 raise error.RevlogError(_("username %s contains a newline")
215 raise error.RevlogError(_("username %s contains a newline")
212 % repr(user))
216 % repr(user))
213
217
214 # strip trailing whitespace and leading and trailing empty lines
218 # strip trailing whitespace and leading and trailing empty lines
215 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
219 desc = '\n'.join([l.rstrip() for l in desc.splitlines()]).strip('\n')
216
220
217 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
221 user, desc = encoding.fromlocal(user), encoding.fromlocal(desc)
218
222
219 if date:
223 if date:
220 parseddate = "%d %d" % util.parsedate(date)
224 parseddate = "%d %d" % util.parsedate(date)
221 else:
225 else:
222 parseddate = "%d %d" % util.makedate()
226 parseddate = "%d %d" % util.makedate()
223 if extra:
227 if extra:
224 branch = extra.get("branch")
228 branch = extra.get("branch")
225 if branch in ("default", ""):
229 if branch in ("default", ""):
226 del extra["branch"]
230 del extra["branch"]
227 elif branch in (".", "null", "tip"):
231 elif branch in (".", "null", "tip"):
228 raise error.RevlogError(_('the name \'%s\' is reserved')
232 raise error.RevlogError(_('the name \'%s\' is reserved')
229 % branch)
233 % branch)
230 if extra:
234 if extra:
231 extra = encodeextra(extra)
235 extra = encodeextra(extra)
232 parseddate = "%s %s" % (parseddate, extra)
236 parseddate = "%s %s" % (parseddate, extra)
233 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
237 l = [hex(manifest), user, parseddate] + sorted(files) + ["", desc]
234 text = "\n".join(l)
238 text = "\n".join(l)
235 return self.addrevision(text, transaction, len(self), p1, p2)
239 return self.addrevision(text, transaction, len(self), p1, p2)
@@ -1,1271 +1,1273 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 # import stuff from node for others to import from revlog
14 # import stuff from node for others to import from revlog
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
15 from node import bin, hex, nullid, nullrev, short #@UnusedImport
16 from i18n import _
16 from i18n import _
17 import ancestor, mdiff, parsers, error, util
17 import ancestor, mdiff, parsers, error, util
18 import struct, zlib, errno
18 import struct, zlib, errno
19
19
20 _pack = struct.pack
20 _pack = struct.pack
21 _unpack = struct.unpack
21 _unpack = struct.unpack
22 _compress = zlib.compress
22 _compress = zlib.compress
23 _decompress = zlib.decompress
23 _decompress = zlib.decompress
24 _sha = util.sha1
24 _sha = util.sha1
25
25
26 # revlog header flags
26 # revlog header flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOGGENERALDELTA = (1 << 17)
30 REVLOGGENERALDELTA = (1 << 17)
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_FORMAT = REVLOGNG
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
34 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
35
35
36 # revlog index flags
36 # revlog index flags
37 REVIDX_KNOWN_FLAGS = 0
37 REVIDX_KNOWN_FLAGS = 0
38
38
39 # max size of revlog with inline data
39 # max size of revlog with inline data
40 _maxinline = 131072
40 _maxinline = 131072
41 _chunksize = 1048576
41 _chunksize = 1048576
42
42
43 RevlogError = error.RevlogError
43 RevlogError = error.RevlogError
44 LookupError = error.LookupError
44 LookupError = error.LookupError
45
45
46 def getoffset(q):
46 def getoffset(q):
47 return int(q >> 16)
47 return int(q >> 16)
48
48
49 def gettype(q):
49 def gettype(q):
50 return int(q & 0xFFFF)
50 return int(q & 0xFFFF)
51
51
52 def offset_type(offset, type):
52 def offset_type(offset, type):
53 return long(long(offset) << 16 | type)
53 return long(long(offset) << 16 | type)
54
54
55 nullhash = _sha(nullid)
55 nullhash = _sha(nullid)
56
56
57 def hash(text, p1, p2):
57 def hash(text, p1, p2):
58 """generate a hash from the given text and its parent hashes
58 """generate a hash from the given text and its parent hashes
59
59
60 This hash combines both the current file contents and its history
60 This hash combines both the current file contents and its history
61 in a manner that makes it easy to distinguish nodes with the same
61 in a manner that makes it easy to distinguish nodes with the same
62 content in the revision graph.
62 content in the revision graph.
63 """
63 """
64 # As of now, if one of the parent node is null, p2 is null
64 # As of now, if one of the parent node is null, p2 is null
65 if p2 == nullid:
65 if p2 == nullid:
66 # deep copy of a hash is faster than creating one
66 # deep copy of a hash is faster than creating one
67 s = nullhash.copy()
67 s = nullhash.copy()
68 s.update(p1)
68 s.update(p1)
69 else:
69 else:
70 # none of the parent nodes are nullid
70 # none of the parent nodes are nullid
71 l = [p1, p2]
71 l = [p1, p2]
72 l.sort()
72 l.sort()
73 s = _sha(l[0])
73 s = _sha(l[0])
74 s.update(l[1])
74 s.update(l[1])
75 s.update(text)
75 s.update(text)
76 return s.digest()
76 return s.digest()
77
77
78 def compress(text):
78 def compress(text):
79 """ generate a possibly-compressed representation of text """
79 """ generate a possibly-compressed representation of text """
80 if not text:
80 if not text:
81 return ("", text)
81 return ("", text)
82 l = len(text)
82 l = len(text)
83 bin = None
83 bin = None
84 if l < 44:
84 if l < 44:
85 pass
85 pass
86 elif l > 1000000:
86 elif l > 1000000:
87 # zlib makes an internal copy, thus doubling memory usage for
87 # zlib makes an internal copy, thus doubling memory usage for
88 # large files, so lets do this in pieces
88 # large files, so lets do this in pieces
89 z = zlib.compressobj()
89 z = zlib.compressobj()
90 p = []
90 p = []
91 pos = 0
91 pos = 0
92 while pos < l:
92 while pos < l:
93 pos2 = pos + 2**20
93 pos2 = pos + 2**20
94 p.append(z.compress(text[pos:pos2]))
94 p.append(z.compress(text[pos:pos2]))
95 pos = pos2
95 pos = pos2
96 p.append(z.flush())
96 p.append(z.flush())
97 if sum(map(len, p)) < l:
97 if sum(map(len, p)) < l:
98 bin = "".join(p)
98 bin = "".join(p)
99 else:
99 else:
100 bin = _compress(text)
100 bin = _compress(text)
101 if bin is None or len(bin) > l:
101 if bin is None or len(bin) > l:
102 if text[0] == '\0':
102 if text[0] == '\0':
103 return ("", text)
103 return ("", text)
104 return ('u', text)
104 return ('u', text)
105 return ("", bin)
105 return ("", bin)
106
106
107 def decompress(bin):
107 def decompress(bin):
108 """ decompress the given input """
108 """ decompress the given input """
109 if not bin:
109 if not bin:
110 return bin
110 return bin
111 t = bin[0]
111 t = bin[0]
112 if t == '\0':
112 if t == '\0':
113 return bin
113 return bin
114 if t == 'x':
114 if t == 'x':
115 return _decompress(bin)
115 return _decompress(bin)
116 if t == 'u':
116 if t == 'u':
117 return bin[1:]
117 return bin[1:]
118 raise RevlogError(_("unknown compression type %r") % t)
118 raise RevlogError(_("unknown compression type %r") % t)
119
119
120 indexformatv0 = ">4l20s20s20s"
120 indexformatv0 = ">4l20s20s20s"
121 v0shaoffset = 56
121 v0shaoffset = 56
122
122
123 class revlogoldio(object):
123 class revlogoldio(object):
124 def __init__(self):
124 def __init__(self):
125 self.size = struct.calcsize(indexformatv0)
125 self.size = struct.calcsize(indexformatv0)
126
126
127 def parseindex(self, data, inline):
127 def parseindex(self, data, inline):
128 s = self.size
128 s = self.size
129 index = []
129 index = []
130 nodemap = {nullid: nullrev}
130 nodemap = {nullid: nullrev}
131 n = off = 0
131 n = off = 0
132 l = len(data)
132 l = len(data)
133 while off + s <= l:
133 while off + s <= l:
134 cur = data[off:off + s]
134 cur = data[off:off + s]
135 off += s
135 off += s
136 e = _unpack(indexformatv0, cur)
136 e = _unpack(indexformatv0, cur)
137 # transform to revlogv1 format
137 # transform to revlogv1 format
138 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
138 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
139 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
139 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
140 index.append(e2)
140 index.append(e2)
141 nodemap[e[6]] = n
141 nodemap[e[6]] = n
142 n += 1
142 n += 1
143
143
144 # add the magic null revision at -1
144 # add the magic null revision at -1
145 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
145 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
146
146
147 return index, nodemap, None
147 return index, nodemap, None
148
148
149 def packentry(self, entry, node, version, rev):
149 def packentry(self, entry, node, version, rev):
150 if gettype(entry[0]):
150 if gettype(entry[0]):
151 raise RevlogError(_("index entry flags need RevlogNG"))
151 raise RevlogError(_("index entry flags need RevlogNG"))
152 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
152 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
153 node(entry[5]), node(entry[6]), entry[7])
153 node(entry[5]), node(entry[6]), entry[7])
154 return _pack(indexformatv0, *e2)
154 return _pack(indexformatv0, *e2)
155
155
156 # index ng:
156 # index ng:
157 # 6 bytes: offset
157 # 6 bytes: offset
158 # 2 bytes: flags
158 # 2 bytes: flags
159 # 4 bytes: compressed length
159 # 4 bytes: compressed length
160 # 4 bytes: uncompressed length
160 # 4 bytes: uncompressed length
161 # 4 bytes: base rev
161 # 4 bytes: base rev
162 # 4 bytes: link rev
162 # 4 bytes: link rev
163 # 4 bytes: parent 1 rev
163 # 4 bytes: parent 1 rev
164 # 4 bytes: parent 2 rev
164 # 4 bytes: parent 2 rev
165 # 32 bytes: nodeid
165 # 32 bytes: nodeid
166 indexformatng = ">Qiiiiii20s12x"
166 indexformatng = ">Qiiiiii20s12x"
167 ngshaoffset = 32
167 ngshaoffset = 32
168 versionformat = ">I"
168 versionformat = ">I"
169
169
170 class revlogio(object):
170 class revlogio(object):
171 def __init__(self):
171 def __init__(self):
172 self.size = struct.calcsize(indexformatng)
172 self.size = struct.calcsize(indexformatng)
173
173
174 def parseindex(self, data, inline):
174 def parseindex(self, data, inline):
175 # call the C implementation to parse the index data
175 # call the C implementation to parse the index data
176 index, cache = parsers.parse_index2(data, inline)
176 index, cache = parsers.parse_index2(data, inline)
177 return index, None, cache
177 return index, None, cache
178
178
179 def packentry(self, entry, node, version, rev):
179 def packentry(self, entry, node, version, rev):
180 p = _pack(indexformatng, *entry)
180 p = _pack(indexformatng, *entry)
181 if rev == 0:
181 if rev == 0:
182 p = _pack(versionformat, version) + p[4:]
182 p = _pack(versionformat, version) + p[4:]
183 return p
183 return p
184
184
185 class revlog(object):
185 class revlog(object):
186 """
186 """
187 the underlying revision storage object
187 the underlying revision storage object
188
188
189 A revlog consists of two parts, an index and the revision data.
189 A revlog consists of two parts, an index and the revision data.
190
190
191 The index is a file with a fixed record size containing
191 The index is a file with a fixed record size containing
192 information on each revision, including its nodeid (hash), the
192 information on each revision, including its nodeid (hash), the
193 nodeids of its parents, the position and offset of its data within
193 nodeids of its parents, the position and offset of its data within
194 the data file, and the revision it's based on. Finally, each entry
194 the data file, and the revision it's based on. Finally, each entry
195 contains a linkrev entry that can serve as a pointer to external
195 contains a linkrev entry that can serve as a pointer to external
196 data.
196 data.
197
197
198 The revision data itself is a linear collection of data chunks.
198 The revision data itself is a linear collection of data chunks.
199 Each chunk represents a revision and is usually represented as a
199 Each chunk represents a revision and is usually represented as a
200 delta against the previous chunk. To bound lookup time, runs of
200 delta against the previous chunk. To bound lookup time, runs of
201 deltas are limited to about 2 times the length of the original
201 deltas are limited to about 2 times the length of the original
202 version data. This makes retrieval of a version proportional to
202 version data. This makes retrieval of a version proportional to
203 its size, or O(1) relative to the number of revisions.
203 its size, or O(1) relative to the number of revisions.
204
204
205 Both pieces of the revlog are written to in an append-only
205 Both pieces of the revlog are written to in an append-only
206 fashion, which means we never need to rewrite a file to insert or
206 fashion, which means we never need to rewrite a file to insert or
207 remove data, and can use some simple techniques to avoid the need
207 remove data, and can use some simple techniques to avoid the need
208 for locking while reading.
208 for locking while reading.
209 """
209 """
210 def __init__(self, opener, indexfile):
210 def __init__(self, opener, indexfile):
211 """
211 """
212 create a revlog object
212 create a revlog object
213
213
214 opener is a function that abstracts the file opening operation
214 opener is a function that abstracts the file opening operation
215 and can be used to implement COW semantics or the like.
215 and can be used to implement COW semantics or the like.
216 """
216 """
217 self.indexfile = indexfile
217 self.indexfile = indexfile
218 self.datafile = indexfile[:-2] + ".d"
218 self.datafile = indexfile[:-2] + ".d"
219 self.opener = opener
219 self.opener = opener
220 self._cache = None
220 self._cache = None
221 self._basecache = (0, 0)
221 self._basecache = (0, 0)
222 self._chunkcache = (0, '')
222 self._chunkcache = (0, '')
223 self.index = []
223 self.index = []
224 self._pcache = {}
224 self._pcache = {}
225 self._nodecache = {nullid: nullrev}
225 self._nodecache = {nullid: nullrev}
226 self._nodepos = None
226 self._nodepos = None
227
227
228 v = REVLOG_DEFAULT_VERSION
228 v = REVLOG_DEFAULT_VERSION
229 if hasattr(opener, 'options'):
229 if hasattr(opener, 'options'):
230 if 'revlogv1' in opener.options:
230 if 'revlogv1' in opener.options:
231 if 'generaldelta' in opener.options:
231 if 'generaldelta' in opener.options:
232 v |= REVLOGGENERALDELTA
232 v |= REVLOGGENERALDELTA
233 else:
233 else:
234 v = 0
234 v = 0
235
235
236 i = ''
236 i = ''
237 self._initempty = True
237 try:
238 try:
238 f = self.opener(self.indexfile)
239 f = self.opener(self.indexfile)
239 i = f.read()
240 i = f.read()
240 f.close()
241 f.close()
241 if len(i) > 0:
242 if len(i) > 0:
242 v = struct.unpack(versionformat, i[:4])[0]
243 v = struct.unpack(versionformat, i[:4])[0]
244 self._initempty = False
243 except IOError, inst:
245 except IOError, inst:
244 if inst.errno != errno.ENOENT:
246 if inst.errno != errno.ENOENT:
245 raise
247 raise
246
248
247 self.version = v
249 self.version = v
248 self._inline = v & REVLOGNGINLINEDATA
250 self._inline = v & REVLOGNGINLINEDATA
249 self._generaldelta = v & REVLOGGENERALDELTA
251 self._generaldelta = v & REVLOGGENERALDELTA
250 flags = v & ~0xFFFF
252 flags = v & ~0xFFFF
251 fmt = v & 0xFFFF
253 fmt = v & 0xFFFF
252 if fmt == REVLOGV0 and flags:
254 if fmt == REVLOGV0 and flags:
253 raise RevlogError(_("index %s unknown flags %#04x for format v0")
255 raise RevlogError(_("index %s unknown flags %#04x for format v0")
254 % (self.indexfile, flags >> 16))
256 % (self.indexfile, flags >> 16))
255 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
257 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
256 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
258 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
257 % (self.indexfile, flags >> 16))
259 % (self.indexfile, flags >> 16))
258 elif fmt > REVLOGNG:
260 elif fmt > REVLOGNG:
259 raise RevlogError(_("index %s unknown format %d")
261 raise RevlogError(_("index %s unknown format %d")
260 % (self.indexfile, fmt))
262 % (self.indexfile, fmt))
261
263
262 self._io = revlogio()
264 self._io = revlogio()
263 if self.version == REVLOGV0:
265 if self.version == REVLOGV0:
264 self._io = revlogoldio()
266 self._io = revlogoldio()
265 try:
267 try:
266 d = self._io.parseindex(i, self._inline)
268 d = self._io.parseindex(i, self._inline)
267 except (ValueError, IndexError):
269 except (ValueError, IndexError):
268 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
270 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
269 self.index, nodemap, self._chunkcache = d
271 self.index, nodemap, self._chunkcache = d
270 if nodemap is not None:
272 if nodemap is not None:
271 self.nodemap = self._nodecache = nodemap
273 self.nodemap = self._nodecache = nodemap
272 if not self._chunkcache:
274 if not self._chunkcache:
273 self._chunkclear()
275 self._chunkclear()
274
276
275 def tip(self):
277 def tip(self):
276 return self.node(len(self.index) - 2)
278 return self.node(len(self.index) - 2)
277 def __len__(self):
279 def __len__(self):
278 return len(self.index) - 1
280 return len(self.index) - 1
279 def __iter__(self):
281 def __iter__(self):
280 for i in xrange(len(self)):
282 for i in xrange(len(self)):
281 yield i
283 yield i
282
284
283 @util.propertycache
285 @util.propertycache
284 def nodemap(self):
286 def nodemap(self):
285 self.rev(self.node(0))
287 self.rev(self.node(0))
286 return self._nodecache
288 return self._nodecache
287
289
288 def rev(self, node):
290 def rev(self, node):
289 try:
291 try:
290 return self._nodecache[node]
292 return self._nodecache[node]
291 except KeyError:
293 except KeyError:
292 n = self._nodecache
294 n = self._nodecache
293 i = self.index
295 i = self.index
294 p = self._nodepos
296 p = self._nodepos
295 if p is None:
297 if p is None:
296 p = len(i) - 2
298 p = len(i) - 2
297 for r in xrange(p, -1, -1):
299 for r in xrange(p, -1, -1):
298 v = i[r][7]
300 v = i[r][7]
299 n[v] = r
301 n[v] = r
300 if v == node:
302 if v == node:
301 self._nodepos = r - 1
303 self._nodepos = r - 1
302 return r
304 return r
303 raise LookupError(node, self.indexfile, _('no node'))
305 raise LookupError(node, self.indexfile, _('no node'))
304
306
305 def node(self, rev):
307 def node(self, rev):
306 return self.index[rev][7]
308 return self.index[rev][7]
307 def linkrev(self, rev):
309 def linkrev(self, rev):
308 return self.index[rev][4]
310 return self.index[rev][4]
309 def parents(self, node):
311 def parents(self, node):
310 i = self.index
312 i = self.index
311 d = i[self.rev(node)]
313 d = i[self.rev(node)]
312 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
314 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
313 def parentrevs(self, rev):
315 def parentrevs(self, rev):
314 return self.index[rev][5:7]
316 return self.index[rev][5:7]
315 def start(self, rev):
317 def start(self, rev):
316 return int(self.index[rev][0] >> 16)
318 return int(self.index[rev][0] >> 16)
317 def end(self, rev):
319 def end(self, rev):
318 return self.start(rev) + self.length(rev)
320 return self.start(rev) + self.length(rev)
319 def length(self, rev):
321 def length(self, rev):
320 return self.index[rev][1]
322 return self.index[rev][1]
321 def base(self, rev):
323 def base(self, rev):
322 return self.index[rev][3]
324 return self.index[rev][3]
323 def chainbase(self, rev):
325 def chainbase(self, rev):
324 index = self.index
326 index = self.index
325 base = index[rev][3]
327 base = index[rev][3]
326 while base != rev:
328 while base != rev:
327 rev = base
329 rev = base
328 base = index[rev][3]
330 base = index[rev][3]
329 return base
331 return base
330 def flags(self, rev):
332 def flags(self, rev):
331 return self.index[rev][0] & 0xFFFF
333 return self.index[rev][0] & 0xFFFF
332 def rawsize(self, rev):
334 def rawsize(self, rev):
333 """return the length of the uncompressed text for a given revision"""
335 """return the length of the uncompressed text for a given revision"""
334 l = self.index[rev][2]
336 l = self.index[rev][2]
335 if l >= 0:
337 if l >= 0:
336 return l
338 return l
337
339
338 t = self.revision(self.node(rev))
340 t = self.revision(self.node(rev))
339 return len(t)
341 return len(t)
340 size = rawsize
342 size = rawsize
341
343
342 def reachable(self, node, stop=None):
344 def reachable(self, node, stop=None):
343 """return the set of all nodes ancestral to a given node, including
345 """return the set of all nodes ancestral to a given node, including
344 the node itself, stopping when stop is matched"""
346 the node itself, stopping when stop is matched"""
345 reachable = set((node,))
347 reachable = set((node,))
346 visit = [node]
348 visit = [node]
347 if stop:
349 if stop:
348 stopn = self.rev(stop)
350 stopn = self.rev(stop)
349 else:
351 else:
350 stopn = 0
352 stopn = 0
351 while visit:
353 while visit:
352 n = visit.pop(0)
354 n = visit.pop(0)
353 if n == stop:
355 if n == stop:
354 continue
356 continue
355 if n == nullid:
357 if n == nullid:
356 continue
358 continue
357 for p in self.parents(n):
359 for p in self.parents(n):
358 if self.rev(p) < stopn:
360 if self.rev(p) < stopn:
359 continue
361 continue
360 if p not in reachable:
362 if p not in reachable:
361 reachable.add(p)
363 reachable.add(p)
362 visit.append(p)
364 visit.append(p)
363 return reachable
365 return reachable
364
366
365 def ancestors(self, *revs):
367 def ancestors(self, *revs):
366 """Generate the ancestors of 'revs' in reverse topological order.
368 """Generate the ancestors of 'revs' in reverse topological order.
367
369
368 Yield a sequence of revision numbers starting with the parents
370 Yield a sequence of revision numbers starting with the parents
369 of each revision in revs, i.e., each revision is *not* considered
371 of each revision in revs, i.e., each revision is *not* considered
370 an ancestor of itself. Results are in breadth-first order:
372 an ancestor of itself. Results are in breadth-first order:
371 parents of each rev in revs, then parents of those, etc. Result
373 parents of each rev in revs, then parents of those, etc. Result
372 does not include the null revision."""
374 does not include the null revision."""
373 visit = list(revs)
375 visit = list(revs)
374 seen = set([nullrev])
376 seen = set([nullrev])
375 while visit:
377 while visit:
376 for parent in self.parentrevs(visit.pop(0)):
378 for parent in self.parentrevs(visit.pop(0)):
377 if parent not in seen:
379 if parent not in seen:
378 visit.append(parent)
380 visit.append(parent)
379 seen.add(parent)
381 seen.add(parent)
380 yield parent
382 yield parent
381
383
382 def descendants(self, *revs):
384 def descendants(self, *revs):
383 """Generate the descendants of 'revs' in revision order.
385 """Generate the descendants of 'revs' in revision order.
384
386
385 Yield a sequence of revision numbers starting with a child of
387 Yield a sequence of revision numbers starting with a child of
386 some rev in revs, i.e., each revision is *not* considered a
388 some rev in revs, i.e., each revision is *not* considered a
387 descendant of itself. Results are ordered by revision number (a
389 descendant of itself. Results are ordered by revision number (a
388 topological sort)."""
390 topological sort)."""
389 first = min(revs)
391 first = min(revs)
390 if first == nullrev:
392 if first == nullrev:
391 for i in self:
393 for i in self:
392 yield i
394 yield i
393 return
395 return
394
396
395 seen = set(revs)
397 seen = set(revs)
396 for i in xrange(first + 1, len(self)):
398 for i in xrange(first + 1, len(self)):
397 for x in self.parentrevs(i):
399 for x in self.parentrevs(i):
398 if x != nullrev and x in seen:
400 if x != nullrev and x in seen:
399 seen.add(i)
401 seen.add(i)
400 yield i
402 yield i
401 break
403 break
402
404
403 def findcommonmissing(self, common=None, heads=None):
405 def findcommonmissing(self, common=None, heads=None):
404 """Return a tuple of the ancestors of common and the ancestors of heads
406 """Return a tuple of the ancestors of common and the ancestors of heads
405 that are not ancestors of common.
407 that are not ancestors of common.
406
408
407 More specifically, the second element is a list of nodes N such that
409 More specifically, the second element is a list of nodes N such that
408 every N satisfies the following constraints:
410 every N satisfies the following constraints:
409
411
410 1. N is an ancestor of some node in 'heads'
412 1. N is an ancestor of some node in 'heads'
411 2. N is not an ancestor of any node in 'common'
413 2. N is not an ancestor of any node in 'common'
412
414
413 The list is sorted by revision number, meaning it is
415 The list is sorted by revision number, meaning it is
414 topologically sorted.
416 topologically sorted.
415
417
416 'heads' and 'common' are both lists of node IDs. If heads is
418 'heads' and 'common' are both lists of node IDs. If heads is
417 not supplied, uses all of the revlog's heads. If common is not
419 not supplied, uses all of the revlog's heads. If common is not
418 supplied, uses nullid."""
420 supplied, uses nullid."""
419 if common is None:
421 if common is None:
420 common = [nullid]
422 common = [nullid]
421 if heads is None:
423 if heads is None:
422 heads = self.heads()
424 heads = self.heads()
423
425
424 common = [self.rev(n) for n in common]
426 common = [self.rev(n) for n in common]
425 heads = [self.rev(n) for n in heads]
427 heads = [self.rev(n) for n in heads]
426
428
427 # we want the ancestors, but inclusive
429 # we want the ancestors, but inclusive
428 has = set(self.ancestors(*common))
430 has = set(self.ancestors(*common))
429 has.add(nullrev)
431 has.add(nullrev)
430 has.update(common)
432 has.update(common)
431
433
432 # take all ancestors from heads that aren't in has
434 # take all ancestors from heads that aren't in has
433 missing = set()
435 missing = set()
434 visit = [r for r in heads if r not in has]
436 visit = [r for r in heads if r not in has]
435 while visit:
437 while visit:
436 r = visit.pop(0)
438 r = visit.pop(0)
437 if r in missing:
439 if r in missing:
438 continue
440 continue
439 else:
441 else:
440 missing.add(r)
442 missing.add(r)
441 for p in self.parentrevs(r):
443 for p in self.parentrevs(r):
442 if p not in has:
444 if p not in has:
443 visit.append(p)
445 visit.append(p)
444 missing = list(missing)
446 missing = list(missing)
445 missing.sort()
447 missing.sort()
446 return has, [self.node(r) for r in missing]
448 return has, [self.node(r) for r in missing]
447
449
448 def findmissing(self, common=None, heads=None):
450 def findmissing(self, common=None, heads=None):
449 """Return the ancestors of heads that are not ancestors of common.
451 """Return the ancestors of heads that are not ancestors of common.
450
452
451 More specifically, return a list of nodes N such that every N
453 More specifically, return a list of nodes N such that every N
452 satisfies the following constraints:
454 satisfies the following constraints:
453
455
454 1. N is an ancestor of some node in 'heads'
456 1. N is an ancestor of some node in 'heads'
455 2. N is not an ancestor of any node in 'common'
457 2. N is not an ancestor of any node in 'common'
456
458
457 The list is sorted by revision number, meaning it is
459 The list is sorted by revision number, meaning it is
458 topologically sorted.
460 topologically sorted.
459
461
460 'heads' and 'common' are both lists of node IDs. If heads is
462 'heads' and 'common' are both lists of node IDs. If heads is
461 not supplied, uses all of the revlog's heads. If common is not
463 not supplied, uses all of the revlog's heads. If common is not
462 supplied, uses nullid."""
464 supplied, uses nullid."""
463 _common, missing = self.findcommonmissing(common, heads)
465 _common, missing = self.findcommonmissing(common, heads)
464 return missing
466 return missing
465
467
466 def nodesbetween(self, roots=None, heads=None):
468 def nodesbetween(self, roots=None, heads=None):
467 """Return a topological path from 'roots' to 'heads'.
469 """Return a topological path from 'roots' to 'heads'.
468
470
469 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
471 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
470 topologically sorted list of all nodes N that satisfy both of
472 topologically sorted list of all nodes N that satisfy both of
471 these constraints:
473 these constraints:
472
474
473 1. N is a descendant of some node in 'roots'
475 1. N is a descendant of some node in 'roots'
474 2. N is an ancestor of some node in 'heads'
476 2. N is an ancestor of some node in 'heads'
475
477
476 Every node is considered to be both a descendant and an ancestor
478 Every node is considered to be both a descendant and an ancestor
477 of itself, so every reachable node in 'roots' and 'heads' will be
479 of itself, so every reachable node in 'roots' and 'heads' will be
478 included in 'nodes'.
480 included in 'nodes'.
479
481
480 'outroots' is the list of reachable nodes in 'roots', i.e., the
482 'outroots' is the list of reachable nodes in 'roots', i.e., the
481 subset of 'roots' that is returned in 'nodes'. Likewise,
483 subset of 'roots' that is returned in 'nodes'. Likewise,
482 'outheads' is the subset of 'heads' that is also in 'nodes'.
484 'outheads' is the subset of 'heads' that is also in 'nodes'.
483
485
484 'roots' and 'heads' are both lists of node IDs. If 'roots' is
486 'roots' and 'heads' are both lists of node IDs. If 'roots' is
485 unspecified, uses nullid as the only root. If 'heads' is
487 unspecified, uses nullid as the only root. If 'heads' is
486 unspecified, uses list of all of the revlog's heads."""
488 unspecified, uses list of all of the revlog's heads."""
487 nonodes = ([], [], [])
489 nonodes = ([], [], [])
488 if roots is not None:
490 if roots is not None:
489 roots = list(roots)
491 roots = list(roots)
490 if not roots:
492 if not roots:
491 return nonodes
493 return nonodes
492 lowestrev = min([self.rev(n) for n in roots])
494 lowestrev = min([self.rev(n) for n in roots])
493 else:
495 else:
494 roots = [nullid] # Everybody's a descendent of nullid
496 roots = [nullid] # Everybody's a descendent of nullid
495 lowestrev = nullrev
497 lowestrev = nullrev
496 if (lowestrev == nullrev) and (heads is None):
498 if (lowestrev == nullrev) and (heads is None):
497 # We want _all_ the nodes!
499 # We want _all_ the nodes!
498 return ([self.node(r) for r in self], [nullid], list(self.heads()))
500 return ([self.node(r) for r in self], [nullid], list(self.heads()))
499 if heads is None:
501 if heads is None:
500 # All nodes are ancestors, so the latest ancestor is the last
502 # All nodes are ancestors, so the latest ancestor is the last
501 # node.
503 # node.
502 highestrev = len(self) - 1
504 highestrev = len(self) - 1
503 # Set ancestors to None to signal that every node is an ancestor.
505 # Set ancestors to None to signal that every node is an ancestor.
504 ancestors = None
506 ancestors = None
505 # Set heads to an empty dictionary for later discovery of heads
507 # Set heads to an empty dictionary for later discovery of heads
506 heads = {}
508 heads = {}
507 else:
509 else:
508 heads = list(heads)
510 heads = list(heads)
509 if not heads:
511 if not heads:
510 return nonodes
512 return nonodes
511 ancestors = set()
513 ancestors = set()
512 # Turn heads into a dictionary so we can remove 'fake' heads.
514 # Turn heads into a dictionary so we can remove 'fake' heads.
513 # Also, later we will be using it to filter out the heads we can't
515 # Also, later we will be using it to filter out the heads we can't
514 # find from roots.
516 # find from roots.
515 heads = dict.fromkeys(heads, False)
517 heads = dict.fromkeys(heads, False)
516 # Start at the top and keep marking parents until we're done.
518 # Start at the top and keep marking parents until we're done.
517 nodestotag = set(heads)
519 nodestotag = set(heads)
518 # Remember where the top was so we can use it as a limit later.
520 # Remember where the top was so we can use it as a limit later.
519 highestrev = max([self.rev(n) for n in nodestotag])
521 highestrev = max([self.rev(n) for n in nodestotag])
520 while nodestotag:
522 while nodestotag:
521 # grab a node to tag
523 # grab a node to tag
522 n = nodestotag.pop()
524 n = nodestotag.pop()
523 # Never tag nullid
525 # Never tag nullid
524 if n == nullid:
526 if n == nullid:
525 continue
527 continue
526 # A node's revision number represents its place in a
528 # A node's revision number represents its place in a
527 # topologically sorted list of nodes.
529 # topologically sorted list of nodes.
528 r = self.rev(n)
530 r = self.rev(n)
529 if r >= lowestrev:
531 if r >= lowestrev:
530 if n not in ancestors:
532 if n not in ancestors:
531 # If we are possibly a descendent of one of the roots
533 # If we are possibly a descendent of one of the roots
532 # and we haven't already been marked as an ancestor
534 # and we haven't already been marked as an ancestor
533 ancestors.add(n) # Mark as ancestor
535 ancestors.add(n) # Mark as ancestor
534 # Add non-nullid parents to list of nodes to tag.
536 # Add non-nullid parents to list of nodes to tag.
535 nodestotag.update([p for p in self.parents(n) if
537 nodestotag.update([p for p in self.parents(n) if
536 p != nullid])
538 p != nullid])
537 elif n in heads: # We've seen it before, is it a fake head?
539 elif n in heads: # We've seen it before, is it a fake head?
538 # So it is, real heads should not be the ancestors of
540 # So it is, real heads should not be the ancestors of
539 # any other heads.
541 # any other heads.
540 heads.pop(n)
542 heads.pop(n)
541 if not ancestors:
543 if not ancestors:
542 return nonodes
544 return nonodes
543 # Now that we have our set of ancestors, we want to remove any
545 # Now that we have our set of ancestors, we want to remove any
544 # roots that are not ancestors.
546 # roots that are not ancestors.
545
547
546 # If one of the roots was nullid, everything is included anyway.
548 # If one of the roots was nullid, everything is included anyway.
547 if lowestrev > nullrev:
549 if lowestrev > nullrev:
548 # But, since we weren't, let's recompute the lowest rev to not
550 # But, since we weren't, let's recompute the lowest rev to not
549 # include roots that aren't ancestors.
551 # include roots that aren't ancestors.
550
552
551 # Filter out roots that aren't ancestors of heads
553 # Filter out roots that aren't ancestors of heads
552 roots = [n for n in roots if n in ancestors]
554 roots = [n for n in roots if n in ancestors]
553 # Recompute the lowest revision
555 # Recompute the lowest revision
554 if roots:
556 if roots:
555 lowestrev = min([self.rev(n) for n in roots])
557 lowestrev = min([self.rev(n) for n in roots])
556 else:
558 else:
557 # No more roots? Return empty list
559 # No more roots? Return empty list
558 return nonodes
560 return nonodes
559 else:
561 else:
560 # We are descending from nullid, and don't need to care about
562 # We are descending from nullid, and don't need to care about
561 # any other roots.
563 # any other roots.
562 lowestrev = nullrev
564 lowestrev = nullrev
563 roots = [nullid]
565 roots = [nullid]
564 # Transform our roots list into a set.
566 # Transform our roots list into a set.
565 descendents = set(roots)
567 descendents = set(roots)
566 # Also, keep the original roots so we can filter out roots that aren't
568 # Also, keep the original roots so we can filter out roots that aren't
567 # 'real' roots (i.e. are descended from other roots).
569 # 'real' roots (i.e. are descended from other roots).
568 roots = descendents.copy()
570 roots = descendents.copy()
569 # Our topologically sorted list of output nodes.
571 # Our topologically sorted list of output nodes.
570 orderedout = []
572 orderedout = []
571 # Don't start at nullid since we don't want nullid in our output list,
573 # Don't start at nullid since we don't want nullid in our output list,
572 # and if nullid shows up in descedents, empty parents will look like
574 # and if nullid shows up in descedents, empty parents will look like
573 # they're descendents.
575 # they're descendents.
574 for r in xrange(max(lowestrev, 0), highestrev + 1):
576 for r in xrange(max(lowestrev, 0), highestrev + 1):
575 n = self.node(r)
577 n = self.node(r)
576 isdescendent = False
578 isdescendent = False
577 if lowestrev == nullrev: # Everybody is a descendent of nullid
579 if lowestrev == nullrev: # Everybody is a descendent of nullid
578 isdescendent = True
580 isdescendent = True
579 elif n in descendents:
581 elif n in descendents:
580 # n is already a descendent
582 # n is already a descendent
581 isdescendent = True
583 isdescendent = True
582 # This check only needs to be done here because all the roots
584 # This check only needs to be done here because all the roots
583 # will start being marked is descendents before the loop.
585 # will start being marked is descendents before the loop.
584 if n in roots:
586 if n in roots:
585 # If n was a root, check if it's a 'real' root.
587 # If n was a root, check if it's a 'real' root.
586 p = tuple(self.parents(n))
588 p = tuple(self.parents(n))
587 # If any of its parents are descendents, it's not a root.
589 # If any of its parents are descendents, it's not a root.
588 if (p[0] in descendents) or (p[1] in descendents):
590 if (p[0] in descendents) or (p[1] in descendents):
589 roots.remove(n)
591 roots.remove(n)
590 else:
592 else:
591 p = tuple(self.parents(n))
593 p = tuple(self.parents(n))
592 # A node is a descendent if either of its parents are
594 # A node is a descendent if either of its parents are
593 # descendents. (We seeded the dependents list with the roots
595 # descendents. (We seeded the dependents list with the roots
594 # up there, remember?)
596 # up there, remember?)
595 if (p[0] in descendents) or (p[1] in descendents):
597 if (p[0] in descendents) or (p[1] in descendents):
596 descendents.add(n)
598 descendents.add(n)
597 isdescendent = True
599 isdescendent = True
598 if isdescendent and ((ancestors is None) or (n in ancestors)):
600 if isdescendent and ((ancestors is None) or (n in ancestors)):
599 # Only include nodes that are both descendents and ancestors.
601 # Only include nodes that are both descendents and ancestors.
600 orderedout.append(n)
602 orderedout.append(n)
601 if (ancestors is not None) and (n in heads):
603 if (ancestors is not None) and (n in heads):
602 # We're trying to figure out which heads are reachable
604 # We're trying to figure out which heads are reachable
603 # from roots.
605 # from roots.
604 # Mark this head as having been reached
606 # Mark this head as having been reached
605 heads[n] = True
607 heads[n] = True
606 elif ancestors is None:
608 elif ancestors is None:
607 # Otherwise, we're trying to discover the heads.
609 # Otherwise, we're trying to discover the heads.
608 # Assume this is a head because if it isn't, the next step
610 # Assume this is a head because if it isn't, the next step
609 # will eventually remove it.
611 # will eventually remove it.
610 heads[n] = True
612 heads[n] = True
611 # But, obviously its parents aren't.
613 # But, obviously its parents aren't.
612 for p in self.parents(n):
614 for p in self.parents(n):
613 heads.pop(p, None)
615 heads.pop(p, None)
614 heads = [n for n, flag in heads.iteritems() if flag]
616 heads = [n for n, flag in heads.iteritems() if flag]
615 roots = list(roots)
617 roots = list(roots)
616 assert orderedout
618 assert orderedout
617 assert roots
619 assert roots
618 assert heads
620 assert heads
619 return (orderedout, roots, heads)
621 return (orderedout, roots, heads)
620
622
621 def headrevs(self):
623 def headrevs(self):
622 count = len(self)
624 count = len(self)
623 if not count:
625 if not count:
624 return [nullrev]
626 return [nullrev]
625 ishead = [1] * (count + 1)
627 ishead = [1] * (count + 1)
626 index = self.index
628 index = self.index
627 for r in xrange(count):
629 for r in xrange(count):
628 e = index[r]
630 e = index[r]
629 ishead[e[5]] = ishead[e[6]] = 0
631 ishead[e[5]] = ishead[e[6]] = 0
630 return [r for r in xrange(count) if ishead[r]]
632 return [r for r in xrange(count) if ishead[r]]
631
633
632 def heads(self, start=None, stop=None):
634 def heads(self, start=None, stop=None):
633 """return the list of all nodes that have no children
635 """return the list of all nodes that have no children
634
636
635 if start is specified, only heads that are descendants of
637 if start is specified, only heads that are descendants of
636 start will be returned
638 start will be returned
637 if stop is specified, it will consider all the revs from stop
639 if stop is specified, it will consider all the revs from stop
638 as if they had no children
640 as if they had no children
639 """
641 """
640 if start is None and stop is None:
642 if start is None and stop is None:
641 if not len(self):
643 if not len(self):
642 return [nullid]
644 return [nullid]
643 return [self.node(r) for r in self.headrevs()]
645 return [self.node(r) for r in self.headrevs()]
644
646
645 if start is None:
647 if start is None:
646 start = nullid
648 start = nullid
647 if stop is None:
649 if stop is None:
648 stop = []
650 stop = []
649 stoprevs = set([self.rev(n) for n in stop])
651 stoprevs = set([self.rev(n) for n in stop])
650 startrev = self.rev(start)
652 startrev = self.rev(start)
651 reachable = set((startrev,))
653 reachable = set((startrev,))
652 heads = set((startrev,))
654 heads = set((startrev,))
653
655
654 parentrevs = self.parentrevs
656 parentrevs = self.parentrevs
655 for r in xrange(startrev + 1, len(self)):
657 for r in xrange(startrev + 1, len(self)):
656 for p in parentrevs(r):
658 for p in parentrevs(r):
657 if p in reachable:
659 if p in reachable:
658 if r not in stoprevs:
660 if r not in stoprevs:
659 reachable.add(r)
661 reachable.add(r)
660 heads.add(r)
662 heads.add(r)
661 if p in heads and p not in stoprevs:
663 if p in heads and p not in stoprevs:
662 heads.remove(p)
664 heads.remove(p)
663
665
664 return [self.node(r) for r in heads]
666 return [self.node(r) for r in heads]
665
667
666 def children(self, node):
668 def children(self, node):
667 """find the children of a given node"""
669 """find the children of a given node"""
668 c = []
670 c = []
669 p = self.rev(node)
671 p = self.rev(node)
670 for r in range(p + 1, len(self)):
672 for r in range(p + 1, len(self)):
671 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
673 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
672 if prevs:
674 if prevs:
673 for pr in prevs:
675 for pr in prevs:
674 if pr == p:
676 if pr == p:
675 c.append(self.node(r))
677 c.append(self.node(r))
676 elif p == nullrev:
678 elif p == nullrev:
677 c.append(self.node(r))
679 c.append(self.node(r))
678 return c
680 return c
679
681
680 def descendant(self, start, end):
682 def descendant(self, start, end):
681 if start == nullrev:
683 if start == nullrev:
682 return True
684 return True
683 for i in self.descendants(start):
685 for i in self.descendants(start):
684 if i == end:
686 if i == end:
685 return True
687 return True
686 elif i > end:
688 elif i > end:
687 break
689 break
688 return False
690 return False
689
691
690 def ancestor(self, a, b):
692 def ancestor(self, a, b):
691 """calculate the least common ancestor of nodes a and b"""
693 """calculate the least common ancestor of nodes a and b"""
692
694
693 # fast path, check if it is a descendant
695 # fast path, check if it is a descendant
694 a, b = self.rev(a), self.rev(b)
696 a, b = self.rev(a), self.rev(b)
695 start, end = sorted((a, b))
697 start, end = sorted((a, b))
696 if self.descendant(start, end):
698 if self.descendant(start, end):
697 return self.node(start)
699 return self.node(start)
698
700
699 def parents(rev):
701 def parents(rev):
700 return [p for p in self.parentrevs(rev) if p != nullrev]
702 return [p for p in self.parentrevs(rev) if p != nullrev]
701
703
702 c = ancestor.ancestor(a, b, parents)
704 c = ancestor.ancestor(a, b, parents)
703 if c is None:
705 if c is None:
704 return nullid
706 return nullid
705
707
706 return self.node(c)
708 return self.node(c)
707
709
708 def _match(self, id):
710 def _match(self, id):
709 if isinstance(id, (long, int)):
711 if isinstance(id, (long, int)):
710 # rev
712 # rev
711 return self.node(id)
713 return self.node(id)
712 if len(id) == 20:
714 if len(id) == 20:
713 # possibly a binary node
715 # possibly a binary node
714 # odds of a binary node being all hex in ASCII are 1 in 10**25
716 # odds of a binary node being all hex in ASCII are 1 in 10**25
715 try:
717 try:
716 node = id
718 node = id
717 self.rev(node) # quick search the index
719 self.rev(node) # quick search the index
718 return node
720 return node
719 except LookupError:
721 except LookupError:
720 pass # may be partial hex id
722 pass # may be partial hex id
721 try:
723 try:
722 # str(rev)
724 # str(rev)
723 rev = int(id)
725 rev = int(id)
724 if str(rev) != id:
726 if str(rev) != id:
725 raise ValueError
727 raise ValueError
726 if rev < 0:
728 if rev < 0:
727 rev = len(self) + rev
729 rev = len(self) + rev
728 if rev < 0 or rev >= len(self):
730 if rev < 0 or rev >= len(self):
729 raise ValueError
731 raise ValueError
730 return self.node(rev)
732 return self.node(rev)
731 except (ValueError, OverflowError):
733 except (ValueError, OverflowError):
732 pass
734 pass
733 if len(id) == 40:
735 if len(id) == 40:
734 try:
736 try:
735 # a full hex nodeid?
737 # a full hex nodeid?
736 node = bin(id)
738 node = bin(id)
737 self.rev(node)
739 self.rev(node)
738 return node
740 return node
739 except (TypeError, LookupError):
741 except (TypeError, LookupError):
740 pass
742 pass
741
743
742 def _partialmatch(self, id):
744 def _partialmatch(self, id):
743 if id in self._pcache:
745 if id in self._pcache:
744 return self._pcache[id]
746 return self._pcache[id]
745
747
746 if len(id) < 40:
748 if len(id) < 40:
747 try:
749 try:
748 # hex(node)[:...]
750 # hex(node)[:...]
749 l = len(id) // 2 # grab an even number of digits
751 l = len(id) // 2 # grab an even number of digits
750 prefix = bin(id[:l * 2])
752 prefix = bin(id[:l * 2])
751 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
753 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
752 nl = [n for n in nl if hex(n).startswith(id)]
754 nl = [n for n in nl if hex(n).startswith(id)]
753 if len(nl) > 0:
755 if len(nl) > 0:
754 if len(nl) == 1:
756 if len(nl) == 1:
755 self._pcache[id] = nl[0]
757 self._pcache[id] = nl[0]
756 return nl[0]
758 return nl[0]
757 raise LookupError(id, self.indexfile,
759 raise LookupError(id, self.indexfile,
758 _('ambiguous identifier'))
760 _('ambiguous identifier'))
759 return None
761 return None
760 except TypeError:
762 except TypeError:
761 pass
763 pass
762
764
763 def lookup(self, id):
765 def lookup(self, id):
764 """locate a node based on:
766 """locate a node based on:
765 - revision number or str(revision number)
767 - revision number or str(revision number)
766 - nodeid or subset of hex nodeid
768 - nodeid or subset of hex nodeid
767 """
769 """
768 n = self._match(id)
770 n = self._match(id)
769 if n is not None:
771 if n is not None:
770 return n
772 return n
771 n = self._partialmatch(id)
773 n = self._partialmatch(id)
772 if n:
774 if n:
773 return n
775 return n
774
776
775 raise LookupError(id, self.indexfile, _('no match found'))
777 raise LookupError(id, self.indexfile, _('no match found'))
776
778
777 def cmp(self, node, text):
779 def cmp(self, node, text):
778 """compare text with a given file revision
780 """compare text with a given file revision
779
781
780 returns True if text is different than what is stored.
782 returns True if text is different than what is stored.
781 """
783 """
782 p1, p2 = self.parents(node)
784 p1, p2 = self.parents(node)
783 return hash(text, p1, p2) != node
785 return hash(text, p1, p2) != node
784
786
785 def _addchunk(self, offset, data):
787 def _addchunk(self, offset, data):
786 o, d = self._chunkcache
788 o, d = self._chunkcache
787 # try to add to existing cache
789 # try to add to existing cache
788 if o + len(d) == offset and len(d) + len(data) < _chunksize:
790 if o + len(d) == offset and len(d) + len(data) < _chunksize:
789 self._chunkcache = o, d + data
791 self._chunkcache = o, d + data
790 else:
792 else:
791 self._chunkcache = offset, data
793 self._chunkcache = offset, data
792
794
793 def _loadchunk(self, offset, length):
795 def _loadchunk(self, offset, length):
794 if self._inline:
796 if self._inline:
795 df = self.opener(self.indexfile)
797 df = self.opener(self.indexfile)
796 else:
798 else:
797 df = self.opener(self.datafile)
799 df = self.opener(self.datafile)
798
800
799 readahead = max(65536, length)
801 readahead = max(65536, length)
800 df.seek(offset)
802 df.seek(offset)
801 d = df.read(readahead)
803 d = df.read(readahead)
802 self._addchunk(offset, d)
804 self._addchunk(offset, d)
803 if readahead > length:
805 if readahead > length:
804 return d[:length]
806 return d[:length]
805 return d
807 return d
806
808
807 def _getchunk(self, offset, length):
809 def _getchunk(self, offset, length):
808 o, d = self._chunkcache
810 o, d = self._chunkcache
809 l = len(d)
811 l = len(d)
810
812
811 # is it in the cache?
813 # is it in the cache?
812 cachestart = offset - o
814 cachestart = offset - o
813 cacheend = cachestart + length
815 cacheend = cachestart + length
814 if cachestart >= 0 and cacheend <= l:
816 if cachestart >= 0 and cacheend <= l:
815 if cachestart == 0 and cacheend == l:
817 if cachestart == 0 and cacheend == l:
816 return d # avoid a copy
818 return d # avoid a copy
817 return d[cachestart:cacheend]
819 return d[cachestart:cacheend]
818
820
819 return self._loadchunk(offset, length)
821 return self._loadchunk(offset, length)
820
822
821 def _chunkraw(self, startrev, endrev):
823 def _chunkraw(self, startrev, endrev):
822 start = self.start(startrev)
824 start = self.start(startrev)
823 length = self.end(endrev) - start
825 length = self.end(endrev) - start
824 if self._inline:
826 if self._inline:
825 start += (startrev + 1) * self._io.size
827 start += (startrev + 1) * self._io.size
826 return self._getchunk(start, length)
828 return self._getchunk(start, length)
827
829
828 def _chunk(self, rev):
830 def _chunk(self, rev):
829 return decompress(self._chunkraw(rev, rev))
831 return decompress(self._chunkraw(rev, rev))
830
832
831 def _chunkbase(self, rev):
833 def _chunkbase(self, rev):
832 return self._chunk(rev)
834 return self._chunk(rev)
833
835
834 def _chunkclear(self):
836 def _chunkclear(self):
835 self._chunkcache = (0, '')
837 self._chunkcache = (0, '')
836
838
837 def deltaparent(self, rev):
839 def deltaparent(self, rev):
838 """return deltaparent of the given revision"""
840 """return deltaparent of the given revision"""
839 base = self.index[rev][3]
841 base = self.index[rev][3]
840 if base == rev:
842 if base == rev:
841 return nullrev
843 return nullrev
842 elif self._generaldelta:
844 elif self._generaldelta:
843 return base
845 return base
844 else:
846 else:
845 return rev - 1
847 return rev - 1
846
848
847 def revdiff(self, rev1, rev2):
849 def revdiff(self, rev1, rev2):
848 """return or calculate a delta between two revisions"""
850 """return or calculate a delta between two revisions"""
849 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
851 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
850 return self._chunk(rev2)
852 return self._chunk(rev2)
851
853
852 return mdiff.textdiff(self.revision(self.node(rev1)),
854 return mdiff.textdiff(self.revision(self.node(rev1)),
853 self.revision(self.node(rev2)))
855 self.revision(self.node(rev2)))
854
856
855 def revision(self, node):
857 def revision(self, node):
856 """return an uncompressed revision of a given node"""
858 """return an uncompressed revision of a given node"""
857 cachedrev = None
859 cachedrev = None
858 if node == nullid:
860 if node == nullid:
859 return ""
861 return ""
860 if self._cache:
862 if self._cache:
861 if self._cache[0] == node:
863 if self._cache[0] == node:
862 return self._cache[2]
864 return self._cache[2]
863 cachedrev = self._cache[1]
865 cachedrev = self._cache[1]
864
866
865 # look up what we need to read
867 # look up what we need to read
866 text = None
868 text = None
867 rev = self.rev(node)
869 rev = self.rev(node)
868
870
869 # check rev flags
871 # check rev flags
870 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
872 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
871 raise RevlogError(_('incompatible revision flag %x') %
873 raise RevlogError(_('incompatible revision flag %x') %
872 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
874 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
873
875
874 # build delta chain
876 # build delta chain
875 chain = []
877 chain = []
876 index = self.index # for performance
878 index = self.index # for performance
877 generaldelta = self._generaldelta
879 generaldelta = self._generaldelta
878 iterrev = rev
880 iterrev = rev
879 e = index[iterrev]
881 e = index[iterrev]
880 while iterrev != e[3] and iterrev != cachedrev:
882 while iterrev != e[3] and iterrev != cachedrev:
881 chain.append(iterrev)
883 chain.append(iterrev)
882 if generaldelta:
884 if generaldelta:
883 iterrev = e[3]
885 iterrev = e[3]
884 else:
886 else:
885 iterrev -= 1
887 iterrev -= 1
886 e = index[iterrev]
888 e = index[iterrev]
887 chain.reverse()
889 chain.reverse()
888 base = iterrev
890 base = iterrev
889
891
890 if iterrev == cachedrev:
892 if iterrev == cachedrev:
891 # cache hit
893 # cache hit
892 text = self._cache[2]
894 text = self._cache[2]
893
895
894 # drop cache to save memory
896 # drop cache to save memory
895 self._cache = None
897 self._cache = None
896
898
897 self._chunkraw(base, rev)
899 self._chunkraw(base, rev)
898 if text is None:
900 if text is None:
899 text = self._chunkbase(base)
901 text = self._chunkbase(base)
900
902
901 bins = [self._chunk(r) for r in chain]
903 bins = [self._chunk(r) for r in chain]
902 text = mdiff.patches(text, bins)
904 text = mdiff.patches(text, bins)
903
905
904 text = self._checkhash(text, node, rev)
906 text = self._checkhash(text, node, rev)
905
907
906 self._cache = (node, rev, text)
908 self._cache = (node, rev, text)
907 return text
909 return text
908
910
909 def _checkhash(self, text, node, rev):
911 def _checkhash(self, text, node, rev):
910 p1, p2 = self.parents(node)
912 p1, p2 = self.parents(node)
911 if node != hash(text, p1, p2):
913 if node != hash(text, p1, p2):
912 raise RevlogError(_("integrity check failed on %s:%d")
914 raise RevlogError(_("integrity check failed on %s:%d")
913 % (self.indexfile, rev))
915 % (self.indexfile, rev))
914 return text
916 return text
915
917
916 def checkinlinesize(self, tr, fp=None):
918 def checkinlinesize(self, tr, fp=None):
917 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
919 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
918 return
920 return
919
921
920 trinfo = tr.find(self.indexfile)
922 trinfo = tr.find(self.indexfile)
921 if trinfo is None:
923 if trinfo is None:
922 raise RevlogError(_("%s not found in the transaction")
924 raise RevlogError(_("%s not found in the transaction")
923 % self.indexfile)
925 % self.indexfile)
924
926
925 trindex = trinfo[2]
927 trindex = trinfo[2]
926 dataoff = self.start(trindex)
928 dataoff = self.start(trindex)
927
929
928 tr.add(self.datafile, dataoff)
930 tr.add(self.datafile, dataoff)
929
931
930 if fp:
932 if fp:
931 fp.flush()
933 fp.flush()
932 fp.close()
934 fp.close()
933
935
934 df = self.opener(self.datafile, 'w')
936 df = self.opener(self.datafile, 'w')
935 try:
937 try:
936 for r in self:
938 for r in self:
937 df.write(self._chunkraw(r, r))
939 df.write(self._chunkraw(r, r))
938 finally:
940 finally:
939 df.close()
941 df.close()
940
942
941 fp = self.opener(self.indexfile, 'w', atomictemp=True)
943 fp = self.opener(self.indexfile, 'w', atomictemp=True)
942 self.version &= ~(REVLOGNGINLINEDATA)
944 self.version &= ~(REVLOGNGINLINEDATA)
943 self._inline = False
945 self._inline = False
944 for i in self:
946 for i in self:
945 e = self._io.packentry(self.index[i], self.node, self.version, i)
947 e = self._io.packentry(self.index[i], self.node, self.version, i)
946 fp.write(e)
948 fp.write(e)
947
949
948 # if we don't call rename, the temp file will never replace the
950 # if we don't call rename, the temp file will never replace the
949 # real index
951 # real index
950 fp.rename()
952 fp.rename()
951
953
952 tr.replace(self.indexfile, trindex * self._io.size)
954 tr.replace(self.indexfile, trindex * self._io.size)
953 self._chunkclear()
955 self._chunkclear()
954
956
955 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
957 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None):
956 """add a revision to the log
958 """add a revision to the log
957
959
958 text - the revision data to add
960 text - the revision data to add
959 transaction - the transaction object used for rollback
961 transaction - the transaction object used for rollback
960 link - the linkrev data to add
962 link - the linkrev data to add
961 p1, p2 - the parent nodeids of the revision
963 p1, p2 - the parent nodeids of the revision
962 cachedelta - an optional precomputed delta
964 cachedelta - an optional precomputed delta
963 """
965 """
964 node = hash(text, p1, p2)
966 node = hash(text, p1, p2)
965 if node in self.nodemap:
967 if node in self.nodemap:
966 return node
968 return node
967
969
968 dfh = None
970 dfh = None
969 if not self._inline:
971 if not self._inline:
970 dfh = self.opener(self.datafile, "a")
972 dfh = self.opener(self.datafile, "a")
971 ifh = self.opener(self.indexfile, "a+")
973 ifh = self.opener(self.indexfile, "a+")
972 try:
974 try:
973 return self._addrevision(node, text, transaction, link, p1, p2,
975 return self._addrevision(node, text, transaction, link, p1, p2,
974 cachedelta, ifh, dfh)
976 cachedelta, ifh, dfh)
975 finally:
977 finally:
976 if dfh:
978 if dfh:
977 dfh.close()
979 dfh.close()
978 ifh.close()
980 ifh.close()
979
981
980 def _addrevision(self, node, text, transaction, link, p1, p2,
982 def _addrevision(self, node, text, transaction, link, p1, p2,
981 cachedelta, ifh, dfh):
983 cachedelta, ifh, dfh):
982 """internal function to add revisions to the log
984 """internal function to add revisions to the log
983
985
984 see addrevision for argument descriptions.
986 see addrevision for argument descriptions.
985 invariants:
987 invariants:
986 - text is optional (can be None); if not set, cachedelta must be set.
988 - text is optional (can be None); if not set, cachedelta must be set.
987 if both are set, they must correspond to eachother.
989 if both are set, they must correspond to eachother.
988 """
990 """
989 btext = [text]
991 btext = [text]
990 def buildtext():
992 def buildtext():
991 if btext[0] is not None:
993 if btext[0] is not None:
992 return btext[0]
994 return btext[0]
993 # flush any pending writes here so we can read it in revision
995 # flush any pending writes here so we can read it in revision
994 if dfh:
996 if dfh:
995 dfh.flush()
997 dfh.flush()
996 ifh.flush()
998 ifh.flush()
997 basetext = self.revision(self.node(cachedelta[0]))
999 basetext = self.revision(self.node(cachedelta[0]))
998 btext[0] = mdiff.patch(basetext, cachedelta[1])
1000 btext[0] = mdiff.patch(basetext, cachedelta[1])
999 chk = hash(btext[0], p1, p2)
1001 chk = hash(btext[0], p1, p2)
1000 if chk != node:
1002 if chk != node:
1001 raise RevlogError(_("consistency error in delta"))
1003 raise RevlogError(_("consistency error in delta"))
1002 return btext[0]
1004 return btext[0]
1003
1005
1004 def builddelta(rev):
1006 def builddelta(rev):
1005 # can we use the cached delta?
1007 # can we use the cached delta?
1006 if cachedelta and cachedelta[0] == rev:
1008 if cachedelta and cachedelta[0] == rev:
1007 delta = cachedelta[1]
1009 delta = cachedelta[1]
1008 else:
1010 else:
1009 t = buildtext()
1011 t = buildtext()
1010 ptext = self.revision(self.node(rev))
1012 ptext = self.revision(self.node(rev))
1011 delta = mdiff.textdiff(ptext, t)
1013 delta = mdiff.textdiff(ptext, t)
1012 data = compress(delta)
1014 data = compress(delta)
1013 l = len(data[1]) + len(data[0])
1015 l = len(data[1]) + len(data[0])
1014 if basecache[0] == rev:
1016 if basecache[0] == rev:
1015 chainbase = basecache[1]
1017 chainbase = basecache[1]
1016 else:
1018 else:
1017 chainbase = self.chainbase(rev)
1019 chainbase = self.chainbase(rev)
1018 dist = l + offset - self.start(chainbase)
1020 dist = l + offset - self.start(chainbase)
1019 if self._generaldelta:
1021 if self._generaldelta:
1020 base = rev
1022 base = rev
1021 else:
1023 else:
1022 base = chainbase
1024 base = chainbase
1023 return dist, l, data, base, chainbase
1025 return dist, l, data, base, chainbase
1024
1026
1025 curr = len(self)
1027 curr = len(self)
1026 prev = curr - 1
1028 prev = curr - 1
1027 base = chainbase = curr
1029 base = chainbase = curr
1028 offset = self.end(prev)
1030 offset = self.end(prev)
1029 flags = 0
1031 flags = 0
1030 d = None
1032 d = None
1031 basecache = self._basecache
1033 basecache = self._basecache
1032 p1r, p2r = self.rev(p1), self.rev(p2)
1034 p1r, p2r = self.rev(p1), self.rev(p2)
1033
1035
1034 # should we try to build a delta?
1036 # should we try to build a delta?
1035 if prev != nullrev:
1037 if prev != nullrev:
1036 if self._generaldelta:
1038 if self._generaldelta:
1037 if p1r >= basecache[1]:
1039 if p1r >= basecache[1]:
1038 d = builddelta(p1r)
1040 d = builddelta(p1r)
1039 elif p2r >= basecache[1]:
1041 elif p2r >= basecache[1]:
1040 d = builddelta(p2r)
1042 d = builddelta(p2r)
1041 else:
1043 else:
1042 d = builddelta(prev)
1044 d = builddelta(prev)
1043 else:
1045 else:
1044 d = builddelta(prev)
1046 d = builddelta(prev)
1045 dist, l, data, base, chainbase = d
1047 dist, l, data, base, chainbase = d
1046
1048
1047 # full versions are inserted when the needed deltas
1049 # full versions are inserted when the needed deltas
1048 # become comparable to the uncompressed text
1050 # become comparable to the uncompressed text
1049 if text is None:
1051 if text is None:
1050 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1052 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1051 cachedelta[1])
1053 cachedelta[1])
1052 else:
1054 else:
1053 textlen = len(text)
1055 textlen = len(text)
1054 if d is None or dist > textlen * 2:
1056 if d is None or dist > textlen * 2:
1055 text = buildtext()
1057 text = buildtext()
1056 data = compress(text)
1058 data = compress(text)
1057 l = len(data[1]) + len(data[0])
1059 l = len(data[1]) + len(data[0])
1058 base = chainbase = curr
1060 base = chainbase = curr
1059
1061
1060 e = (offset_type(offset, flags), l, textlen,
1062 e = (offset_type(offset, flags), l, textlen,
1061 base, link, p1r, p2r, node)
1063 base, link, p1r, p2r, node)
1062 self.index.insert(-1, e)
1064 self.index.insert(-1, e)
1063 self.nodemap[node] = curr
1065 self.nodemap[node] = curr
1064
1066
1065 entry = self._io.packentry(e, self.node, self.version, curr)
1067 entry = self._io.packentry(e, self.node, self.version, curr)
1066 if not self._inline:
1068 if not self._inline:
1067 transaction.add(self.datafile, offset)
1069 transaction.add(self.datafile, offset)
1068 transaction.add(self.indexfile, curr * len(entry))
1070 transaction.add(self.indexfile, curr * len(entry))
1069 if data[0]:
1071 if data[0]:
1070 dfh.write(data[0])
1072 dfh.write(data[0])
1071 dfh.write(data[1])
1073 dfh.write(data[1])
1072 dfh.flush()
1074 dfh.flush()
1073 ifh.write(entry)
1075 ifh.write(entry)
1074 else:
1076 else:
1075 offset += curr * self._io.size
1077 offset += curr * self._io.size
1076 transaction.add(self.indexfile, offset, curr)
1078 transaction.add(self.indexfile, offset, curr)
1077 ifh.write(entry)
1079 ifh.write(entry)
1078 ifh.write(data[0])
1080 ifh.write(data[0])
1079 ifh.write(data[1])
1081 ifh.write(data[1])
1080 self.checkinlinesize(transaction, ifh)
1082 self.checkinlinesize(transaction, ifh)
1081
1083
1082 if type(text) == str: # only accept immutable objects
1084 if type(text) == str: # only accept immutable objects
1083 self._cache = (node, curr, text)
1085 self._cache = (node, curr, text)
1084 self._basecache = (curr, chainbase)
1086 self._basecache = (curr, chainbase)
1085 return node
1087 return node
1086
1088
1087 def group(self, nodelist, bundler):
1089 def group(self, nodelist, bundler):
1088 """Calculate a delta group, yielding a sequence of changegroup chunks
1090 """Calculate a delta group, yielding a sequence of changegroup chunks
1089 (strings).
1091 (strings).
1090
1092
1091 Given a list of changeset revs, return a set of deltas and
1093 Given a list of changeset revs, return a set of deltas and
1092 metadata corresponding to nodes. The first delta is
1094 metadata corresponding to nodes. The first delta is
1093 first parent(nodelist[0]) -> nodelist[0], the receiver is
1095 first parent(nodelist[0]) -> nodelist[0], the receiver is
1094 guaranteed to have this parent as it has all history before
1096 guaranteed to have this parent as it has all history before
1095 these changesets. In the case firstparent is nullrev the
1097 these changesets. In the case firstparent is nullrev the
1096 changegroup starts with a full revision.
1098 changegroup starts with a full revision.
1097 """
1099 """
1098
1100
1099 revs = sorted([self.rev(n) for n in nodelist])
1101 revs = sorted([self.rev(n) for n in nodelist])
1100
1102
1101 # if we don't have any revisions touched by these changesets, bail
1103 # if we don't have any revisions touched by these changesets, bail
1102 if not revs:
1104 if not revs:
1103 yield bundler.close()
1105 yield bundler.close()
1104 return
1106 return
1105
1107
1106 # add the parent of the first rev
1108 # add the parent of the first rev
1107 p = self.parentrevs(revs[0])[0]
1109 p = self.parentrevs(revs[0])[0]
1108 revs.insert(0, p)
1110 revs.insert(0, p)
1109
1111
1110 # build deltas
1112 # build deltas
1111 for r in xrange(len(revs) - 1):
1113 for r in xrange(len(revs) - 1):
1112 prev, curr = revs[r], revs[r + 1]
1114 prev, curr = revs[r], revs[r + 1]
1113 for c in bundler.revchunk(self, curr, prev):
1115 for c in bundler.revchunk(self, curr, prev):
1114 yield c
1116 yield c
1115
1117
1116 yield bundler.close()
1118 yield bundler.close()
1117
1119
1118 def addgroup(self, bundle, linkmapper, transaction):
1120 def addgroup(self, bundle, linkmapper, transaction):
1119 """
1121 """
1120 add a delta group
1122 add a delta group
1121
1123
1122 given a set of deltas, add them to the revision log. the
1124 given a set of deltas, add them to the revision log. the
1123 first delta is against its parent, which should be in our
1125 first delta is against its parent, which should be in our
1124 log, the rest are against the previous delta.
1126 log, the rest are against the previous delta.
1125 """
1127 """
1126
1128
1127 # track the base of the current delta log
1129 # track the base of the current delta log
1128 node = None
1130 node = None
1129
1131
1130 r = len(self)
1132 r = len(self)
1131 end = 0
1133 end = 0
1132 if r:
1134 if r:
1133 end = self.end(r - 1)
1135 end = self.end(r - 1)
1134 ifh = self.opener(self.indexfile, "a+")
1136 ifh = self.opener(self.indexfile, "a+")
1135 isize = r * self._io.size
1137 isize = r * self._io.size
1136 if self._inline:
1138 if self._inline:
1137 transaction.add(self.indexfile, end + isize, r)
1139 transaction.add(self.indexfile, end + isize, r)
1138 dfh = None
1140 dfh = None
1139 else:
1141 else:
1140 transaction.add(self.indexfile, isize, r)
1142 transaction.add(self.indexfile, isize, r)
1141 transaction.add(self.datafile, end)
1143 transaction.add(self.datafile, end)
1142 dfh = self.opener(self.datafile, "a")
1144 dfh = self.opener(self.datafile, "a")
1143
1145
1144 try:
1146 try:
1145 # loop through our set of deltas
1147 # loop through our set of deltas
1146 chain = None
1148 chain = None
1147 while 1:
1149 while 1:
1148 chunkdata = bundle.deltachunk(chain)
1150 chunkdata = bundle.deltachunk(chain)
1149 if not chunkdata:
1151 if not chunkdata:
1150 break
1152 break
1151 node = chunkdata['node']
1153 node = chunkdata['node']
1152 p1 = chunkdata['p1']
1154 p1 = chunkdata['p1']
1153 p2 = chunkdata['p2']
1155 p2 = chunkdata['p2']
1154 cs = chunkdata['cs']
1156 cs = chunkdata['cs']
1155 deltabase = chunkdata['deltabase']
1157 deltabase = chunkdata['deltabase']
1156 delta = chunkdata['delta']
1158 delta = chunkdata['delta']
1157
1159
1158 link = linkmapper(cs)
1160 link = linkmapper(cs)
1159 if node in self.nodemap:
1161 if node in self.nodemap:
1160 # this can happen if two branches make the same change
1162 # this can happen if two branches make the same change
1161 chain = node
1163 chain = node
1162 continue
1164 continue
1163
1165
1164 for p in (p1, p2):
1166 for p in (p1, p2):
1165 if not p in self.nodemap:
1167 if not p in self.nodemap:
1166 raise LookupError(p, self.indexfile,
1168 raise LookupError(p, self.indexfile,
1167 _('unknown parent'))
1169 _('unknown parent'))
1168
1170
1169 if deltabase not in self.nodemap:
1171 if deltabase not in self.nodemap:
1170 raise LookupError(deltabase, self.indexfile,
1172 raise LookupError(deltabase, self.indexfile,
1171 _('unknown delta base'))
1173 _('unknown delta base'))
1172
1174
1173 baserev = self.rev(deltabase)
1175 baserev = self.rev(deltabase)
1174 chain = self._addrevision(node, None, transaction, link,
1176 chain = self._addrevision(node, None, transaction, link,
1175 p1, p2, (baserev, delta), ifh, dfh)
1177 p1, p2, (baserev, delta), ifh, dfh)
1176 if not dfh and not self._inline:
1178 if not dfh and not self._inline:
1177 # addrevision switched from inline to conventional
1179 # addrevision switched from inline to conventional
1178 # reopen the index
1180 # reopen the index
1179 ifh.close()
1181 ifh.close()
1180 dfh = self.opener(self.datafile, "a")
1182 dfh = self.opener(self.datafile, "a")
1181 ifh = self.opener(self.indexfile, "a")
1183 ifh = self.opener(self.indexfile, "a")
1182 finally:
1184 finally:
1183 if dfh:
1185 if dfh:
1184 dfh.close()
1186 dfh.close()
1185 ifh.close()
1187 ifh.close()
1186
1188
1187 return node
1189 return node
1188
1190
1189 def strip(self, minlink, transaction):
1191 def strip(self, minlink, transaction):
1190 """truncate the revlog on the first revision with a linkrev >= minlink
1192 """truncate the revlog on the first revision with a linkrev >= minlink
1191
1193
1192 This function is called when we're stripping revision minlink and
1194 This function is called when we're stripping revision minlink and
1193 its descendants from the repository.
1195 its descendants from the repository.
1194
1196
1195 We have to remove all revisions with linkrev >= minlink, because
1197 We have to remove all revisions with linkrev >= minlink, because
1196 the equivalent changelog revisions will be renumbered after the
1198 the equivalent changelog revisions will be renumbered after the
1197 strip.
1199 strip.
1198
1200
1199 So we truncate the revlog on the first of these revisions, and
1201 So we truncate the revlog on the first of these revisions, and
1200 trust that the caller has saved the revisions that shouldn't be
1202 trust that the caller has saved the revisions that shouldn't be
1201 removed and that it'll readd them after this truncation.
1203 removed and that it'll readd them after this truncation.
1202 """
1204 """
1203 if len(self) == 0:
1205 if len(self) == 0:
1204 return
1206 return
1205
1207
1206 for rev in self:
1208 for rev in self:
1207 if self.index[rev][4] >= minlink:
1209 if self.index[rev][4] >= minlink:
1208 break
1210 break
1209 else:
1211 else:
1210 return
1212 return
1211
1213
1212 # first truncate the files on disk
1214 # first truncate the files on disk
1213 end = self.start(rev)
1215 end = self.start(rev)
1214 if not self._inline:
1216 if not self._inline:
1215 transaction.add(self.datafile, end)
1217 transaction.add(self.datafile, end)
1216 end = rev * self._io.size
1218 end = rev * self._io.size
1217 else:
1219 else:
1218 end += rev * self._io.size
1220 end += rev * self._io.size
1219
1221
1220 transaction.add(self.indexfile, end)
1222 transaction.add(self.indexfile, end)
1221
1223
1222 # then reset internal state in memory to forget those revisions
1224 # then reset internal state in memory to forget those revisions
1223 self._cache = None
1225 self._cache = None
1224 self._chunkclear()
1226 self._chunkclear()
1225 for x in xrange(rev, len(self)):
1227 for x in xrange(rev, len(self)):
1226 del self.nodemap[self.node(x)]
1228 del self.nodemap[self.node(x)]
1227
1229
1228 del self.index[rev:-1]
1230 del self.index[rev:-1]
1229
1231
1230 def checksize(self):
1232 def checksize(self):
1231 expected = 0
1233 expected = 0
1232 if len(self):
1234 if len(self):
1233 expected = max(0, self.end(len(self) - 1))
1235 expected = max(0, self.end(len(self) - 1))
1234
1236
1235 try:
1237 try:
1236 f = self.opener(self.datafile)
1238 f = self.opener(self.datafile)
1237 f.seek(0, 2)
1239 f.seek(0, 2)
1238 actual = f.tell()
1240 actual = f.tell()
1239 f.close()
1241 f.close()
1240 dd = actual - expected
1242 dd = actual - expected
1241 except IOError, inst:
1243 except IOError, inst:
1242 if inst.errno != errno.ENOENT:
1244 if inst.errno != errno.ENOENT:
1243 raise
1245 raise
1244 dd = 0
1246 dd = 0
1245
1247
1246 try:
1248 try:
1247 f = self.opener(self.indexfile)
1249 f = self.opener(self.indexfile)
1248 f.seek(0, 2)
1250 f.seek(0, 2)
1249 actual = f.tell()
1251 actual = f.tell()
1250 f.close()
1252 f.close()
1251 s = self._io.size
1253 s = self._io.size
1252 i = max(0, actual // s)
1254 i = max(0, actual // s)
1253 di = actual - (i * s)
1255 di = actual - (i * s)
1254 if self._inline:
1256 if self._inline:
1255 databytes = 0
1257 databytes = 0
1256 for r in self:
1258 for r in self:
1257 databytes += max(0, self.length(r))
1259 databytes += max(0, self.length(r))
1258 dd = 0
1260 dd = 0
1259 di = actual - len(self) * s - databytes
1261 di = actual - len(self) * s - databytes
1260 except IOError, inst:
1262 except IOError, inst:
1261 if inst.errno != errno.ENOENT:
1263 if inst.errno != errno.ENOENT:
1262 raise
1264 raise
1263 di = 0
1265 di = 0
1264
1266
1265 return (dd, di)
1267 return (dd, di)
1266
1268
1267 def files(self):
1269 def files(self):
1268 res = [self.indexfile]
1270 res = [self.indexfile]
1269 if not self._inline:
1271 if not self._inline:
1270 res.append(self.datafile)
1272 res.append(self.datafile)
1271 return res
1273 return res
General Comments 0
You need to be logged in to leave comments. Login now