##// END OF EJS Templates
cmp: document the fact that we return True if content is different...
Nicolas Dumazet -
r11539:a463e3c5 stable
parent child Browse files
Show More
@@ -1,1078 +1,1086 b''
1 # context.py - changeset and file context objects for mercurial
1 # context.py - changeset and file context objects for mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 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 nullid, nullrev, short, hex
8 from node import nullid, nullrev, short, hex
9 from i18n import _
9 from i18n import _
10 import ancestor, bdiff, error, util, subrepo, patch
10 import ancestor, bdiff, error, util, subrepo, patch
11 import os, errno, stat
11 import os, errno, stat
12
12
13 propertycache = util.propertycache
13 propertycache = util.propertycache
14
14
15 class changectx(object):
15 class changectx(object):
16 """A changecontext object makes access to data related to a particular
16 """A changecontext object makes access to data related to a particular
17 changeset convenient."""
17 changeset convenient."""
18 def __init__(self, repo, changeid=''):
18 def __init__(self, repo, changeid=''):
19 """changeid is a revision number, node, or tag"""
19 """changeid is a revision number, node, or tag"""
20 if changeid == '':
20 if changeid == '':
21 changeid = '.'
21 changeid = '.'
22 self._repo = repo
22 self._repo = repo
23 if isinstance(changeid, (long, int)):
23 if isinstance(changeid, (long, int)):
24 self._rev = changeid
24 self._rev = changeid
25 self._node = self._repo.changelog.node(changeid)
25 self._node = self._repo.changelog.node(changeid)
26 else:
26 else:
27 self._node = self._repo.lookup(changeid)
27 self._node = self._repo.lookup(changeid)
28 self._rev = self._repo.changelog.rev(self._node)
28 self._rev = self._repo.changelog.rev(self._node)
29
29
30 def __str__(self):
30 def __str__(self):
31 return short(self.node())
31 return short(self.node())
32
32
33 def __int__(self):
33 def __int__(self):
34 return self.rev()
34 return self.rev()
35
35
36 def __repr__(self):
36 def __repr__(self):
37 return "<changectx %s>" % str(self)
37 return "<changectx %s>" % str(self)
38
38
39 def __hash__(self):
39 def __hash__(self):
40 try:
40 try:
41 return hash(self._rev)
41 return hash(self._rev)
42 except AttributeError:
42 except AttributeError:
43 return id(self)
43 return id(self)
44
44
45 def __eq__(self, other):
45 def __eq__(self, other):
46 try:
46 try:
47 return self._rev == other._rev
47 return self._rev == other._rev
48 except AttributeError:
48 except AttributeError:
49 return False
49 return False
50
50
51 def __ne__(self, other):
51 def __ne__(self, other):
52 return not (self == other)
52 return not (self == other)
53
53
54 def __nonzero__(self):
54 def __nonzero__(self):
55 return self._rev != nullrev
55 return self._rev != nullrev
56
56
57 @propertycache
57 @propertycache
58 def _changeset(self):
58 def _changeset(self):
59 return self._repo.changelog.read(self.node())
59 return self._repo.changelog.read(self.node())
60
60
61 @propertycache
61 @propertycache
62 def _manifest(self):
62 def _manifest(self):
63 return self._repo.manifest.read(self._changeset[0])
63 return self._repo.manifest.read(self._changeset[0])
64
64
65 @propertycache
65 @propertycache
66 def _manifestdelta(self):
66 def _manifestdelta(self):
67 return self._repo.manifest.readdelta(self._changeset[0])
67 return self._repo.manifest.readdelta(self._changeset[0])
68
68
69 @propertycache
69 @propertycache
70 def _parents(self):
70 def _parents(self):
71 p = self._repo.changelog.parentrevs(self._rev)
71 p = self._repo.changelog.parentrevs(self._rev)
72 if p[1] == nullrev:
72 if p[1] == nullrev:
73 p = p[:-1]
73 p = p[:-1]
74 return [changectx(self._repo, x) for x in p]
74 return [changectx(self._repo, x) for x in p]
75
75
76 @propertycache
76 @propertycache
77 def substate(self):
77 def substate(self):
78 return subrepo.state(self)
78 return subrepo.state(self)
79
79
80 def __contains__(self, key):
80 def __contains__(self, key):
81 return key in self._manifest
81 return key in self._manifest
82
82
83 def __getitem__(self, key):
83 def __getitem__(self, key):
84 return self.filectx(key)
84 return self.filectx(key)
85
85
86 def __iter__(self):
86 def __iter__(self):
87 for f in sorted(self._manifest):
87 for f in sorted(self._manifest):
88 yield f
88 yield f
89
89
90 def changeset(self):
90 def changeset(self):
91 return self._changeset
91 return self._changeset
92 def manifest(self):
92 def manifest(self):
93 return self._manifest
93 return self._manifest
94 def manifestnode(self):
94 def manifestnode(self):
95 return self._changeset[0]
95 return self._changeset[0]
96
96
97 def rev(self):
97 def rev(self):
98 return self._rev
98 return self._rev
99 def node(self):
99 def node(self):
100 return self._node
100 return self._node
101 def hex(self):
101 def hex(self):
102 return hex(self._node)
102 return hex(self._node)
103 def user(self):
103 def user(self):
104 return self._changeset[1]
104 return self._changeset[1]
105 def date(self):
105 def date(self):
106 return self._changeset[2]
106 return self._changeset[2]
107 def files(self):
107 def files(self):
108 return self._changeset[3]
108 return self._changeset[3]
109 def description(self):
109 def description(self):
110 return self._changeset[4]
110 return self._changeset[4]
111 def branch(self):
111 def branch(self):
112 return self._changeset[5].get("branch")
112 return self._changeset[5].get("branch")
113 def extra(self):
113 def extra(self):
114 return self._changeset[5]
114 return self._changeset[5]
115 def tags(self):
115 def tags(self):
116 return self._repo.nodetags(self._node)
116 return self._repo.nodetags(self._node)
117
117
118 def parents(self):
118 def parents(self):
119 """return contexts for each parent changeset"""
119 """return contexts for each parent changeset"""
120 return self._parents
120 return self._parents
121
121
122 def p1(self):
122 def p1(self):
123 return self._parents[0]
123 return self._parents[0]
124
124
125 def p2(self):
125 def p2(self):
126 if len(self._parents) == 2:
126 if len(self._parents) == 2:
127 return self._parents[1]
127 return self._parents[1]
128 return changectx(self._repo, -1)
128 return changectx(self._repo, -1)
129
129
130 def children(self):
130 def children(self):
131 """return contexts for each child changeset"""
131 """return contexts for each child changeset"""
132 c = self._repo.changelog.children(self._node)
132 c = self._repo.changelog.children(self._node)
133 return [changectx(self._repo, x) for x in c]
133 return [changectx(self._repo, x) for x in c]
134
134
135 def ancestors(self):
135 def ancestors(self):
136 for a in self._repo.changelog.ancestors(self._rev):
136 for a in self._repo.changelog.ancestors(self._rev):
137 yield changectx(self._repo, a)
137 yield changectx(self._repo, a)
138
138
139 def descendants(self):
139 def descendants(self):
140 for d in self._repo.changelog.descendants(self._rev):
140 for d in self._repo.changelog.descendants(self._rev):
141 yield changectx(self._repo, d)
141 yield changectx(self._repo, d)
142
142
143 def _fileinfo(self, path):
143 def _fileinfo(self, path):
144 if '_manifest' in self.__dict__:
144 if '_manifest' in self.__dict__:
145 try:
145 try:
146 return self._manifest[path], self._manifest.flags(path)
146 return self._manifest[path], self._manifest.flags(path)
147 except KeyError:
147 except KeyError:
148 raise error.LookupError(self._node, path,
148 raise error.LookupError(self._node, path,
149 _('not found in manifest'))
149 _('not found in manifest'))
150 if '_manifestdelta' in self.__dict__ or path in self.files():
150 if '_manifestdelta' in self.__dict__ or path in self.files():
151 if path in self._manifestdelta:
151 if path in self._manifestdelta:
152 return self._manifestdelta[path], self._manifestdelta.flags(path)
152 return self._manifestdelta[path], self._manifestdelta.flags(path)
153 node, flag = self._repo.manifest.find(self._changeset[0], path)
153 node, flag = self._repo.manifest.find(self._changeset[0], path)
154 if not node:
154 if not node:
155 raise error.LookupError(self._node, path,
155 raise error.LookupError(self._node, path,
156 _('not found in manifest'))
156 _('not found in manifest'))
157
157
158 return node, flag
158 return node, flag
159
159
160 def filenode(self, path):
160 def filenode(self, path):
161 return self._fileinfo(path)[0]
161 return self._fileinfo(path)[0]
162
162
163 def flags(self, path):
163 def flags(self, path):
164 try:
164 try:
165 return self._fileinfo(path)[1]
165 return self._fileinfo(path)[1]
166 except error.LookupError:
166 except error.LookupError:
167 return ''
167 return ''
168
168
169 def filectx(self, path, fileid=None, filelog=None):
169 def filectx(self, path, fileid=None, filelog=None):
170 """get a file context from this changeset"""
170 """get a file context from this changeset"""
171 if fileid is None:
171 if fileid is None:
172 fileid = self.filenode(path)
172 fileid = self.filenode(path)
173 return filectx(self._repo, path, fileid=fileid,
173 return filectx(self._repo, path, fileid=fileid,
174 changectx=self, filelog=filelog)
174 changectx=self, filelog=filelog)
175
175
176 def ancestor(self, c2):
176 def ancestor(self, c2):
177 """
177 """
178 return the ancestor context of self and c2
178 return the ancestor context of self and c2
179 """
179 """
180 # deal with workingctxs
180 # deal with workingctxs
181 n2 = c2._node
181 n2 = c2._node
182 if n2 == None:
182 if n2 == None:
183 n2 = c2._parents[0]._node
183 n2 = c2._parents[0]._node
184 n = self._repo.changelog.ancestor(self._node, n2)
184 n = self._repo.changelog.ancestor(self._node, n2)
185 return changectx(self._repo, n)
185 return changectx(self._repo, n)
186
186
187 def walk(self, match):
187 def walk(self, match):
188 fset = set(match.files())
188 fset = set(match.files())
189 # for dirstate.walk, files=['.'] means "walk the whole tree".
189 # for dirstate.walk, files=['.'] means "walk the whole tree".
190 # follow that here, too
190 # follow that here, too
191 fset.discard('.')
191 fset.discard('.')
192 for fn in self:
192 for fn in self:
193 for ffn in fset:
193 for ffn in fset:
194 # match if the file is the exact name or a directory
194 # match if the file is the exact name or a directory
195 if ffn == fn or fn.startswith("%s/" % ffn):
195 if ffn == fn or fn.startswith("%s/" % ffn):
196 fset.remove(ffn)
196 fset.remove(ffn)
197 break
197 break
198 if match(fn):
198 if match(fn):
199 yield fn
199 yield fn
200 for fn in sorted(fset):
200 for fn in sorted(fset):
201 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
201 if match.bad(fn, 'No such file in rev ' + str(self)) and match(fn):
202 yield fn
202 yield fn
203
203
204 def sub(self, path):
204 def sub(self, path):
205 return subrepo.subrepo(self, path)
205 return subrepo.subrepo(self, path)
206
206
207 def diff(self, ctx2=None, match=None, **opts):
207 def diff(self, ctx2=None, match=None, **opts):
208 """Returns a diff generator for the given contexts and matcher"""
208 """Returns a diff generator for the given contexts and matcher"""
209 if ctx2 is None:
209 if ctx2 is None:
210 ctx2 = self.p1()
210 ctx2 = self.p1()
211 if ctx2 is not None and not isinstance(ctx2, changectx):
211 if ctx2 is not None and not isinstance(ctx2, changectx):
212 ctx2 = self._repo[ctx2]
212 ctx2 = self._repo[ctx2]
213 diffopts = patch.diffopts(self._repo.ui, opts)
213 diffopts = patch.diffopts(self._repo.ui, opts)
214 return patch.diff(self._repo, ctx2.node(), self.node(),
214 return patch.diff(self._repo, ctx2.node(), self.node(),
215 match=match, opts=diffopts)
215 match=match, opts=diffopts)
216
216
217 class filectx(object):
217 class filectx(object):
218 """A filecontext object makes access to data related to a particular
218 """A filecontext object makes access to data related to a particular
219 filerevision convenient."""
219 filerevision convenient."""
220 def __init__(self, repo, path, changeid=None, fileid=None,
220 def __init__(self, repo, path, changeid=None, fileid=None,
221 filelog=None, changectx=None):
221 filelog=None, changectx=None):
222 """changeid can be a changeset revision, node, or tag.
222 """changeid can be a changeset revision, node, or tag.
223 fileid can be a file revision or node."""
223 fileid can be a file revision or node."""
224 self._repo = repo
224 self._repo = repo
225 self._path = path
225 self._path = path
226
226
227 assert (changeid is not None
227 assert (changeid is not None
228 or fileid is not None
228 or fileid is not None
229 or changectx is not None), \
229 or changectx is not None), \
230 ("bad args: changeid=%r, fileid=%r, changectx=%r"
230 ("bad args: changeid=%r, fileid=%r, changectx=%r"
231 % (changeid, fileid, changectx))
231 % (changeid, fileid, changectx))
232
232
233 if filelog:
233 if filelog:
234 self._filelog = filelog
234 self._filelog = filelog
235
235
236 if changeid is not None:
236 if changeid is not None:
237 self._changeid = changeid
237 self._changeid = changeid
238 if changectx is not None:
238 if changectx is not None:
239 self._changectx = changectx
239 self._changectx = changectx
240 if fileid is not None:
240 if fileid is not None:
241 self._fileid = fileid
241 self._fileid = fileid
242
242
243 @propertycache
243 @propertycache
244 def _changectx(self):
244 def _changectx(self):
245 return changectx(self._repo, self._changeid)
245 return changectx(self._repo, self._changeid)
246
246
247 @propertycache
247 @propertycache
248 def _filelog(self):
248 def _filelog(self):
249 return self._repo.file(self._path)
249 return self._repo.file(self._path)
250
250
251 @propertycache
251 @propertycache
252 def _changeid(self):
252 def _changeid(self):
253 if '_changectx' in self.__dict__:
253 if '_changectx' in self.__dict__:
254 return self._changectx.rev()
254 return self._changectx.rev()
255 else:
255 else:
256 return self._filelog.linkrev(self._filerev)
256 return self._filelog.linkrev(self._filerev)
257
257
258 @propertycache
258 @propertycache
259 def _filenode(self):
259 def _filenode(self):
260 if '_fileid' in self.__dict__:
260 if '_fileid' in self.__dict__:
261 return self._filelog.lookup(self._fileid)
261 return self._filelog.lookup(self._fileid)
262 else:
262 else:
263 return self._changectx.filenode(self._path)
263 return self._changectx.filenode(self._path)
264
264
265 @propertycache
265 @propertycache
266 def _filerev(self):
266 def _filerev(self):
267 return self._filelog.rev(self._filenode)
267 return self._filelog.rev(self._filenode)
268
268
269 @propertycache
269 @propertycache
270 def _repopath(self):
270 def _repopath(self):
271 return self._path
271 return self._path
272
272
273 def __nonzero__(self):
273 def __nonzero__(self):
274 try:
274 try:
275 self._filenode
275 self._filenode
276 return True
276 return True
277 except error.LookupError:
277 except error.LookupError:
278 # file is missing
278 # file is missing
279 return False
279 return False
280
280
281 def __str__(self):
281 def __str__(self):
282 return "%s@%s" % (self.path(), short(self.node()))
282 return "%s@%s" % (self.path(), short(self.node()))
283
283
284 def __repr__(self):
284 def __repr__(self):
285 return "<filectx %s>" % str(self)
285 return "<filectx %s>" % str(self)
286
286
287 def __hash__(self):
287 def __hash__(self):
288 try:
288 try:
289 return hash((self._path, self._filenode))
289 return hash((self._path, self._filenode))
290 except AttributeError:
290 except AttributeError:
291 return id(self)
291 return id(self)
292
292
293 def __eq__(self, other):
293 def __eq__(self, other):
294 try:
294 try:
295 return (self._path == other._path
295 return (self._path == other._path
296 and self._filenode == other._filenode)
296 and self._filenode == other._filenode)
297 except AttributeError:
297 except AttributeError:
298 return False
298 return False
299
299
300 def __ne__(self, other):
300 def __ne__(self, other):
301 return not (self == other)
301 return not (self == other)
302
302
303 def filectx(self, fileid):
303 def filectx(self, fileid):
304 '''opens an arbitrary revision of the file without
304 '''opens an arbitrary revision of the file without
305 opening a new filelog'''
305 opening a new filelog'''
306 return filectx(self._repo, self._path, fileid=fileid,
306 return filectx(self._repo, self._path, fileid=fileid,
307 filelog=self._filelog)
307 filelog=self._filelog)
308
308
309 def filerev(self):
309 def filerev(self):
310 return self._filerev
310 return self._filerev
311 def filenode(self):
311 def filenode(self):
312 return self._filenode
312 return self._filenode
313 def flags(self):
313 def flags(self):
314 return self._changectx.flags(self._path)
314 return self._changectx.flags(self._path)
315 def filelog(self):
315 def filelog(self):
316 return self._filelog
316 return self._filelog
317
317
318 def rev(self):
318 def rev(self):
319 if '_changectx' in self.__dict__:
319 if '_changectx' in self.__dict__:
320 return self._changectx.rev()
320 return self._changectx.rev()
321 if '_changeid' in self.__dict__:
321 if '_changeid' in self.__dict__:
322 return self._changectx.rev()
322 return self._changectx.rev()
323 return self._filelog.linkrev(self._filerev)
323 return self._filelog.linkrev(self._filerev)
324
324
325 def linkrev(self):
325 def linkrev(self):
326 return self._filelog.linkrev(self._filerev)
326 return self._filelog.linkrev(self._filerev)
327 def node(self):
327 def node(self):
328 return self._changectx.node()
328 return self._changectx.node()
329 def hex(self):
329 def hex(self):
330 return hex(self.node())
330 return hex(self.node())
331 def user(self):
331 def user(self):
332 return self._changectx.user()
332 return self._changectx.user()
333 def date(self):
333 def date(self):
334 return self._changectx.date()
334 return self._changectx.date()
335 def files(self):
335 def files(self):
336 return self._changectx.files()
336 return self._changectx.files()
337 def description(self):
337 def description(self):
338 return self._changectx.description()
338 return self._changectx.description()
339 def branch(self):
339 def branch(self):
340 return self._changectx.branch()
340 return self._changectx.branch()
341 def extra(self):
341 def extra(self):
342 return self._changectx.extra()
342 return self._changectx.extra()
343 def manifest(self):
343 def manifest(self):
344 return self._changectx.manifest()
344 return self._changectx.manifest()
345 def changectx(self):
345 def changectx(self):
346 return self._changectx
346 return self._changectx
347
347
348 def data(self):
348 def data(self):
349 return self._filelog.read(self._filenode)
349 return self._filelog.read(self._filenode)
350 def path(self):
350 def path(self):
351 return self._path
351 return self._path
352 def size(self):
352 def size(self):
353 return self._filelog.size(self._filerev)
353 return self._filelog.size(self._filerev)
354
354
355 def cmp(self, text):
355 def cmp(self, text):
356 """compare text with stored file revision
357
358 returns True if text is different than what is stored.
359 """
356 return self._filelog.cmp(self._filenode, text)
360 return self._filelog.cmp(self._filenode, text)
357
361
358 def renamed(self):
362 def renamed(self):
359 """check if file was actually renamed in this changeset revision
363 """check if file was actually renamed in this changeset revision
360
364
361 If rename logged in file revision, we report copy for changeset only
365 If rename logged in file revision, we report copy for changeset only
362 if file revisions linkrev points back to the changeset in question
366 if file revisions linkrev points back to the changeset in question
363 or both changeset parents contain different file revisions.
367 or both changeset parents contain different file revisions.
364 """
368 """
365
369
366 renamed = self._filelog.renamed(self._filenode)
370 renamed = self._filelog.renamed(self._filenode)
367 if not renamed:
371 if not renamed:
368 return renamed
372 return renamed
369
373
370 if self.rev() == self.linkrev():
374 if self.rev() == self.linkrev():
371 return renamed
375 return renamed
372
376
373 name = self.path()
377 name = self.path()
374 fnode = self._filenode
378 fnode = self._filenode
375 for p in self._changectx.parents():
379 for p in self._changectx.parents():
376 try:
380 try:
377 if fnode == p.filenode(name):
381 if fnode == p.filenode(name):
378 return None
382 return None
379 except error.LookupError:
383 except error.LookupError:
380 pass
384 pass
381 return renamed
385 return renamed
382
386
383 def parents(self):
387 def parents(self):
384 p = self._path
388 p = self._path
385 fl = self._filelog
389 fl = self._filelog
386 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
390 pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
387
391
388 r = self._filelog.renamed(self._filenode)
392 r = self._filelog.renamed(self._filenode)
389 if r:
393 if r:
390 pl[0] = (r[0], r[1], None)
394 pl[0] = (r[0], r[1], None)
391
395
392 return [filectx(self._repo, p, fileid=n, filelog=l)
396 return [filectx(self._repo, p, fileid=n, filelog=l)
393 for p, n, l in pl if n != nullid]
397 for p, n, l in pl if n != nullid]
394
398
395 def children(self):
399 def children(self):
396 # hard for renames
400 # hard for renames
397 c = self._filelog.children(self._filenode)
401 c = self._filelog.children(self._filenode)
398 return [filectx(self._repo, self._path, fileid=x,
402 return [filectx(self._repo, self._path, fileid=x,
399 filelog=self._filelog) for x in c]
403 filelog=self._filelog) for x in c]
400
404
401 def annotate(self, follow=False, linenumber=None):
405 def annotate(self, follow=False, linenumber=None):
402 '''returns a list of tuples of (ctx, line) for each line
406 '''returns a list of tuples of (ctx, line) for each line
403 in the file, where ctx is the filectx of the node where
407 in the file, where ctx is the filectx of the node where
404 that line was last changed.
408 that line was last changed.
405 This returns tuples of ((ctx, linenumber), line) for each line,
409 This returns tuples of ((ctx, linenumber), line) for each line,
406 if "linenumber" parameter is NOT "None".
410 if "linenumber" parameter is NOT "None".
407 In such tuples, linenumber means one at the first appearance
411 In such tuples, linenumber means one at the first appearance
408 in the managed file.
412 in the managed file.
409 To reduce annotation cost,
413 To reduce annotation cost,
410 this returns fixed value(False is used) as linenumber,
414 this returns fixed value(False is used) as linenumber,
411 if "linenumber" parameter is "False".'''
415 if "linenumber" parameter is "False".'''
412
416
413 def decorate_compat(text, rev):
417 def decorate_compat(text, rev):
414 return ([rev] * len(text.splitlines()), text)
418 return ([rev] * len(text.splitlines()), text)
415
419
416 def without_linenumber(text, rev):
420 def without_linenumber(text, rev):
417 return ([(rev, False)] * len(text.splitlines()), text)
421 return ([(rev, False)] * len(text.splitlines()), text)
418
422
419 def with_linenumber(text, rev):
423 def with_linenumber(text, rev):
420 size = len(text.splitlines())
424 size = len(text.splitlines())
421 return ([(rev, i) for i in xrange(1, size + 1)], text)
425 return ([(rev, i) for i in xrange(1, size + 1)], text)
422
426
423 decorate = (((linenumber is None) and decorate_compat) or
427 decorate = (((linenumber is None) and decorate_compat) or
424 (linenumber and with_linenumber) or
428 (linenumber and with_linenumber) or
425 without_linenumber)
429 without_linenumber)
426
430
427 def pair(parent, child):
431 def pair(parent, child):
428 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
432 for a1, a2, b1, b2 in bdiff.blocks(parent[1], child[1]):
429 child[0][b1:b2] = parent[0][a1:a2]
433 child[0][b1:b2] = parent[0][a1:a2]
430 return child
434 return child
431
435
432 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
436 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
433 def getctx(path, fileid):
437 def getctx(path, fileid):
434 log = path == self._path and self._filelog or getlog(path)
438 log = path == self._path and self._filelog or getlog(path)
435 return filectx(self._repo, path, fileid=fileid, filelog=log)
439 return filectx(self._repo, path, fileid=fileid, filelog=log)
436 getctx = util.lrucachefunc(getctx)
440 getctx = util.lrucachefunc(getctx)
437
441
438 def parents(f):
442 def parents(f):
439 # we want to reuse filectx objects as much as possible
443 # we want to reuse filectx objects as much as possible
440 p = f._path
444 p = f._path
441 if f._filerev is None: # working dir
445 if f._filerev is None: # working dir
442 pl = [(n.path(), n.filerev()) for n in f.parents()]
446 pl = [(n.path(), n.filerev()) for n in f.parents()]
443 else:
447 else:
444 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
448 pl = [(p, n) for n in f._filelog.parentrevs(f._filerev)]
445
449
446 if follow:
450 if follow:
447 r = f.renamed()
451 r = f.renamed()
448 if r:
452 if r:
449 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
453 pl[0] = (r[0], getlog(r[0]).rev(r[1]))
450
454
451 return [getctx(p, n) for p, n in pl if n != nullrev]
455 return [getctx(p, n) for p, n in pl if n != nullrev]
452
456
453 # use linkrev to find the first changeset where self appeared
457 # use linkrev to find the first changeset where self appeared
454 if self.rev() != self.linkrev():
458 if self.rev() != self.linkrev():
455 base = self.filectx(self.filerev())
459 base = self.filectx(self.filerev())
456 else:
460 else:
457 base = self
461 base = self
458
462
459 # find all ancestors
463 # find all ancestors
460 needed = {base: 1}
464 needed = {base: 1}
461 visit = [base]
465 visit = [base]
462 files = [base._path]
466 files = [base._path]
463 while visit:
467 while visit:
464 f = visit.pop(0)
468 f = visit.pop(0)
465 for p in parents(f):
469 for p in parents(f):
466 if p not in needed:
470 if p not in needed:
467 needed[p] = 1
471 needed[p] = 1
468 visit.append(p)
472 visit.append(p)
469 if p._path not in files:
473 if p._path not in files:
470 files.append(p._path)
474 files.append(p._path)
471 else:
475 else:
472 # count how many times we'll use this
476 # count how many times we'll use this
473 needed[p] += 1
477 needed[p] += 1
474
478
475 # sort by revision (per file) which is a topological order
479 # sort by revision (per file) which is a topological order
476 visit = []
480 visit = []
477 for f in files:
481 for f in files:
478 visit.extend(n for n in needed if n._path == f)
482 visit.extend(n for n in needed if n._path == f)
479
483
480 hist = {}
484 hist = {}
481 for f in sorted(visit, key=lambda x: x.rev()):
485 for f in sorted(visit, key=lambda x: x.rev()):
482 curr = decorate(f.data(), f)
486 curr = decorate(f.data(), f)
483 for p in parents(f):
487 for p in parents(f):
484 curr = pair(hist[p], curr)
488 curr = pair(hist[p], curr)
485 # trim the history of unneeded revs
489 # trim the history of unneeded revs
486 needed[p] -= 1
490 needed[p] -= 1
487 if not needed[p]:
491 if not needed[p]:
488 del hist[p]
492 del hist[p]
489 hist[f] = curr
493 hist[f] = curr
490
494
491 return zip(hist[f][0], hist[f][1].splitlines(True))
495 return zip(hist[f][0], hist[f][1].splitlines(True))
492
496
493 def ancestor(self, fc2, actx=None):
497 def ancestor(self, fc2, actx=None):
494 """
498 """
495 find the common ancestor file context, if any, of self, and fc2
499 find the common ancestor file context, if any, of self, and fc2
496
500
497 If actx is given, it must be the changectx of the common ancestor
501 If actx is given, it must be the changectx of the common ancestor
498 of self's and fc2's respective changesets.
502 of self's and fc2's respective changesets.
499 """
503 """
500
504
501 if actx is None:
505 if actx is None:
502 actx = self.changectx().ancestor(fc2.changectx())
506 actx = self.changectx().ancestor(fc2.changectx())
503
507
504 # the trivial case: changesets are unrelated, files must be too
508 # the trivial case: changesets are unrelated, files must be too
505 if not actx:
509 if not actx:
506 return None
510 return None
507
511
508 # the easy case: no (relevant) renames
512 # the easy case: no (relevant) renames
509 if fc2.path() == self.path() and self.path() in actx:
513 if fc2.path() == self.path() and self.path() in actx:
510 return actx[self.path()]
514 return actx[self.path()]
511 acache = {}
515 acache = {}
512
516
513 # prime the ancestor cache for the working directory
517 # prime the ancestor cache for the working directory
514 for c in (self, fc2):
518 for c in (self, fc2):
515 if c._filerev is None:
519 if c._filerev is None:
516 pl = [(n.path(), n.filenode()) for n in c.parents()]
520 pl = [(n.path(), n.filenode()) for n in c.parents()]
517 acache[(c._path, None)] = pl
521 acache[(c._path, None)] = pl
518
522
519 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
523 flcache = {self._repopath:self._filelog, fc2._repopath:fc2._filelog}
520 def parents(vertex):
524 def parents(vertex):
521 if vertex in acache:
525 if vertex in acache:
522 return acache[vertex]
526 return acache[vertex]
523 f, n = vertex
527 f, n = vertex
524 if f not in flcache:
528 if f not in flcache:
525 flcache[f] = self._repo.file(f)
529 flcache[f] = self._repo.file(f)
526 fl = flcache[f]
530 fl = flcache[f]
527 pl = [(f, p) for p in fl.parents(n) if p != nullid]
531 pl = [(f, p) for p in fl.parents(n) if p != nullid]
528 re = fl.renamed(n)
532 re = fl.renamed(n)
529 if re:
533 if re:
530 pl.append(re)
534 pl.append(re)
531 acache[vertex] = pl
535 acache[vertex] = pl
532 return pl
536 return pl
533
537
534 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
538 a, b = (self._path, self._filenode), (fc2._path, fc2._filenode)
535 v = ancestor.ancestor(a, b, parents)
539 v = ancestor.ancestor(a, b, parents)
536 if v:
540 if v:
537 f, n = v
541 f, n = v
538 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
542 return filectx(self._repo, f, fileid=n, filelog=flcache[f])
539
543
540 return None
544 return None
541
545
542 def ancestors(self):
546 def ancestors(self):
543 seen = set(str(self))
547 seen = set(str(self))
544 visit = [self]
548 visit = [self]
545 while visit:
549 while visit:
546 for parent in visit.pop(0).parents():
550 for parent in visit.pop(0).parents():
547 s = str(parent)
551 s = str(parent)
548 if s not in seen:
552 if s not in seen:
549 visit.append(parent)
553 visit.append(parent)
550 seen.add(s)
554 seen.add(s)
551 yield parent
555 yield parent
552
556
553 class workingctx(changectx):
557 class workingctx(changectx):
554 """A workingctx object makes access to data related to
558 """A workingctx object makes access to data related to
555 the current working directory convenient.
559 the current working directory convenient.
556 date - any valid date string or (unixtime, offset), or None.
560 date - any valid date string or (unixtime, offset), or None.
557 user - username string, or None.
561 user - username string, or None.
558 extra - a dictionary of extra values, or None.
562 extra - a dictionary of extra values, or None.
559 changes - a list of file lists as returned by localrepo.status()
563 changes - a list of file lists as returned by localrepo.status()
560 or None to use the repository status.
564 or None to use the repository status.
561 """
565 """
562 def __init__(self, repo, text="", user=None, date=None, extra=None,
566 def __init__(self, repo, text="", user=None, date=None, extra=None,
563 changes=None):
567 changes=None):
564 self._repo = repo
568 self._repo = repo
565 self._rev = None
569 self._rev = None
566 self._node = None
570 self._node = None
567 self._text = text
571 self._text = text
568 if date:
572 if date:
569 self._date = util.parsedate(date)
573 self._date = util.parsedate(date)
570 if user:
574 if user:
571 self._user = user
575 self._user = user
572 if changes:
576 if changes:
573 self._status = list(changes[:4])
577 self._status = list(changes[:4])
574 self._unknown = changes[4]
578 self._unknown = changes[4]
575 self._ignored = changes[5]
579 self._ignored = changes[5]
576 self._clean = changes[6]
580 self._clean = changes[6]
577 else:
581 else:
578 self._unknown = None
582 self._unknown = None
579 self._ignored = None
583 self._ignored = None
580 self._clean = None
584 self._clean = None
581
585
582 self._extra = {}
586 self._extra = {}
583 if extra:
587 if extra:
584 self._extra = extra.copy()
588 self._extra = extra.copy()
585 if 'branch' not in self._extra:
589 if 'branch' not in self._extra:
586 branch = self._repo.dirstate.branch()
590 branch = self._repo.dirstate.branch()
587 try:
591 try:
588 branch = branch.decode('UTF-8').encode('UTF-8')
592 branch = branch.decode('UTF-8').encode('UTF-8')
589 except UnicodeDecodeError:
593 except UnicodeDecodeError:
590 raise util.Abort(_('branch name not in UTF-8!'))
594 raise util.Abort(_('branch name not in UTF-8!'))
591 self._extra['branch'] = branch
595 self._extra['branch'] = branch
592 if self._extra['branch'] == '':
596 if self._extra['branch'] == '':
593 self._extra['branch'] = 'default'
597 self._extra['branch'] = 'default'
594
598
595 def __str__(self):
599 def __str__(self):
596 return str(self._parents[0]) + "+"
600 return str(self._parents[0]) + "+"
597
601
598 def __nonzero__(self):
602 def __nonzero__(self):
599 return True
603 return True
600
604
601 def __contains__(self, key):
605 def __contains__(self, key):
602 return self._repo.dirstate[key] not in "?r"
606 return self._repo.dirstate[key] not in "?r"
603
607
604 @propertycache
608 @propertycache
605 def _manifest(self):
609 def _manifest(self):
606 """generate a manifest corresponding to the working directory"""
610 """generate a manifest corresponding to the working directory"""
607
611
608 if self._unknown is None:
612 if self._unknown is None:
609 self.status(unknown=True)
613 self.status(unknown=True)
610
614
611 man = self._parents[0].manifest().copy()
615 man = self._parents[0].manifest().copy()
612 copied = self._repo.dirstate.copies()
616 copied = self._repo.dirstate.copies()
613 if len(self._parents) > 1:
617 if len(self._parents) > 1:
614 man2 = self.p2().manifest()
618 man2 = self.p2().manifest()
615 def getman(f):
619 def getman(f):
616 if f in man:
620 if f in man:
617 return man
621 return man
618 return man2
622 return man2
619 else:
623 else:
620 getman = lambda f: man
624 getman = lambda f: man
621 def cf(f):
625 def cf(f):
622 f = copied.get(f, f)
626 f = copied.get(f, f)
623 return getman(f).flags(f)
627 return getman(f).flags(f)
624 ff = self._repo.dirstate.flagfunc(cf)
628 ff = self._repo.dirstate.flagfunc(cf)
625 modified, added, removed, deleted = self._status
629 modified, added, removed, deleted = self._status
626 unknown = self._unknown
630 unknown = self._unknown
627 for i, l in (("a", added), ("m", modified), ("u", unknown)):
631 for i, l in (("a", added), ("m", modified), ("u", unknown)):
628 for f in l:
632 for f in l:
629 orig = copied.get(f, f)
633 orig = copied.get(f, f)
630 man[f] = getman(orig).get(orig, nullid) + i
634 man[f] = getman(orig).get(orig, nullid) + i
631 try:
635 try:
632 man.set(f, ff(f))
636 man.set(f, ff(f))
633 except OSError:
637 except OSError:
634 pass
638 pass
635
639
636 for f in deleted + removed:
640 for f in deleted + removed:
637 if f in man:
641 if f in man:
638 del man[f]
642 del man[f]
639
643
640 return man
644 return man
641
645
642 @propertycache
646 @propertycache
643 def _status(self):
647 def _status(self):
644 return self._repo.status()[:4]
648 return self._repo.status()[:4]
645
649
646 @propertycache
650 @propertycache
647 def _user(self):
651 def _user(self):
648 return self._repo.ui.username()
652 return self._repo.ui.username()
649
653
650 @propertycache
654 @propertycache
651 def _date(self):
655 def _date(self):
652 return util.makedate()
656 return util.makedate()
653
657
654 @propertycache
658 @propertycache
655 def _parents(self):
659 def _parents(self):
656 p = self._repo.dirstate.parents()
660 p = self._repo.dirstate.parents()
657 if p[1] == nullid:
661 if p[1] == nullid:
658 p = p[:-1]
662 p = p[:-1]
659 self._parents = [changectx(self._repo, x) for x in p]
663 self._parents = [changectx(self._repo, x) for x in p]
660 return self._parents
664 return self._parents
661
665
662 def status(self, ignored=False, clean=False, unknown=False):
666 def status(self, ignored=False, clean=False, unknown=False):
663 """Explicit status query
667 """Explicit status query
664 Unless this method is used to query the working copy status, the
668 Unless this method is used to query the working copy status, the
665 _status property will implicitly read the status using its default
669 _status property will implicitly read the status using its default
666 arguments."""
670 arguments."""
667 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
671 stat = self._repo.status(ignored=ignored, clean=clean, unknown=unknown)
668 self._unknown = self._ignored = self._clean = None
672 self._unknown = self._ignored = self._clean = None
669 if unknown:
673 if unknown:
670 self._unknown = stat[4]
674 self._unknown = stat[4]
671 if ignored:
675 if ignored:
672 self._ignored = stat[5]
676 self._ignored = stat[5]
673 if clean:
677 if clean:
674 self._clean = stat[6]
678 self._clean = stat[6]
675 self._status = stat[:4]
679 self._status = stat[:4]
676 return stat
680 return stat
677
681
678 def manifest(self):
682 def manifest(self):
679 return self._manifest
683 return self._manifest
680 def user(self):
684 def user(self):
681 return self._user or self._repo.ui.username()
685 return self._user or self._repo.ui.username()
682 def date(self):
686 def date(self):
683 return self._date
687 return self._date
684 def description(self):
688 def description(self):
685 return self._text
689 return self._text
686 def files(self):
690 def files(self):
687 return sorted(self._status[0] + self._status[1] + self._status[2])
691 return sorted(self._status[0] + self._status[1] + self._status[2])
688
692
689 def modified(self):
693 def modified(self):
690 return self._status[0]
694 return self._status[0]
691 def added(self):
695 def added(self):
692 return self._status[1]
696 return self._status[1]
693 def removed(self):
697 def removed(self):
694 return self._status[2]
698 return self._status[2]
695 def deleted(self):
699 def deleted(self):
696 return self._status[3]
700 return self._status[3]
697 def unknown(self):
701 def unknown(self):
698 assert self._unknown is not None # must call status first
702 assert self._unknown is not None # must call status first
699 return self._unknown
703 return self._unknown
700 def ignored(self):
704 def ignored(self):
701 assert self._ignored is not None # must call status first
705 assert self._ignored is not None # must call status first
702 return self._ignored
706 return self._ignored
703 def clean(self):
707 def clean(self):
704 assert self._clean is not None # must call status first
708 assert self._clean is not None # must call status first
705 return self._clean
709 return self._clean
706 def branch(self):
710 def branch(self):
707 return self._extra['branch']
711 return self._extra['branch']
708 def extra(self):
712 def extra(self):
709 return self._extra
713 return self._extra
710
714
711 def tags(self):
715 def tags(self):
712 t = []
716 t = []
713 [t.extend(p.tags()) for p in self.parents()]
717 [t.extend(p.tags()) for p in self.parents()]
714 return t
718 return t
715
719
716 def children(self):
720 def children(self):
717 return []
721 return []
718
722
719 def flags(self, path):
723 def flags(self, path):
720 if '_manifest' in self.__dict__:
724 if '_manifest' in self.__dict__:
721 try:
725 try:
722 return self._manifest.flags(path)
726 return self._manifest.flags(path)
723 except KeyError:
727 except KeyError:
724 return ''
728 return ''
725
729
726 orig = self._repo.dirstate.copies().get(path, path)
730 orig = self._repo.dirstate.copies().get(path, path)
727
731
728 def findflag(ctx):
732 def findflag(ctx):
729 mnode = ctx.changeset()[0]
733 mnode = ctx.changeset()[0]
730 node, flag = self._repo.manifest.find(mnode, orig)
734 node, flag = self._repo.manifest.find(mnode, orig)
731 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
735 ff = self._repo.dirstate.flagfunc(lambda x: flag or '')
732 try:
736 try:
733 return ff(path)
737 return ff(path)
734 except OSError:
738 except OSError:
735 pass
739 pass
736
740
737 flag = findflag(self._parents[0])
741 flag = findflag(self._parents[0])
738 if flag is None and len(self.parents()) > 1:
742 if flag is None and len(self.parents()) > 1:
739 flag = findflag(self._parents[1])
743 flag = findflag(self._parents[1])
740 if flag is None or self._repo.dirstate[path] == 'r':
744 if flag is None or self._repo.dirstate[path] == 'r':
741 return ''
745 return ''
742 return flag
746 return flag
743
747
744 def filectx(self, path, filelog=None):
748 def filectx(self, path, filelog=None):
745 """get a file context from the working directory"""
749 """get a file context from the working directory"""
746 return workingfilectx(self._repo, path, workingctx=self,
750 return workingfilectx(self._repo, path, workingctx=self,
747 filelog=filelog)
751 filelog=filelog)
748
752
749 def ancestor(self, c2):
753 def ancestor(self, c2):
750 """return the ancestor context of self and c2"""
754 """return the ancestor context of self and c2"""
751 return self._parents[0].ancestor(c2) # punt on two parents for now
755 return self._parents[0].ancestor(c2) # punt on two parents for now
752
756
753 def walk(self, match):
757 def walk(self, match):
754 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
758 return sorted(self._repo.dirstate.walk(match, self.substate.keys(),
755 True, False))
759 True, False))
756
760
757 def dirty(self, missing=False):
761 def dirty(self, missing=False):
758 "check whether a working directory is modified"
762 "check whether a working directory is modified"
759 # check subrepos first
763 # check subrepos first
760 for s in self.substate:
764 for s in self.substate:
761 if self.sub(s).dirty():
765 if self.sub(s).dirty():
762 return True
766 return True
763 # check current working dir
767 # check current working dir
764 return (self.p2() or self.branch() != self.p1().branch() or
768 return (self.p2() or self.branch() != self.p1().branch() or
765 self.modified() or self.added() or self.removed() or
769 self.modified() or self.added() or self.removed() or
766 (missing and self.deleted()))
770 (missing and self.deleted()))
767
771
768 def add(self, list):
772 def add(self, list):
769 wlock = self._repo.wlock()
773 wlock = self._repo.wlock()
770 ui, ds = self._repo.ui, self._repo.dirstate
774 ui, ds = self._repo.ui, self._repo.dirstate
771 try:
775 try:
772 rejected = []
776 rejected = []
773 for f in list:
777 for f in list:
774 p = self._repo.wjoin(f)
778 p = self._repo.wjoin(f)
775 try:
779 try:
776 st = os.lstat(p)
780 st = os.lstat(p)
777 except:
781 except:
778 ui.warn(_("%s does not exist!\n") % f)
782 ui.warn(_("%s does not exist!\n") % f)
779 rejected.append(f)
783 rejected.append(f)
780 continue
784 continue
781 if st.st_size > 10000000:
785 if st.st_size > 10000000:
782 ui.warn(_("%s: up to %d MB of RAM may be required "
786 ui.warn(_("%s: up to %d MB of RAM may be required "
783 "to manage this file\n"
787 "to manage this file\n"
784 "(use 'hg revert %s' to cancel the "
788 "(use 'hg revert %s' to cancel the "
785 "pending addition)\n")
789 "pending addition)\n")
786 % (f, 3 * st.st_size // 1000000, f))
790 % (f, 3 * st.st_size // 1000000, f))
787 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
791 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
788 ui.warn(_("%s not added: only files and symlinks "
792 ui.warn(_("%s not added: only files and symlinks "
789 "supported currently\n") % f)
793 "supported currently\n") % f)
790 rejected.append(p)
794 rejected.append(p)
791 elif ds[f] in 'amn':
795 elif ds[f] in 'amn':
792 ui.warn(_("%s already tracked!\n") % f)
796 ui.warn(_("%s already tracked!\n") % f)
793 elif ds[f] == 'r':
797 elif ds[f] == 'r':
794 ds.normallookup(f)
798 ds.normallookup(f)
795 else:
799 else:
796 ds.add(f)
800 ds.add(f)
797 return rejected
801 return rejected
798 finally:
802 finally:
799 wlock.release()
803 wlock.release()
800
804
801 def forget(self, list):
805 def forget(self, list):
802 wlock = self._repo.wlock()
806 wlock = self._repo.wlock()
803 try:
807 try:
804 for f in list:
808 for f in list:
805 if self._repo.dirstate[f] != 'a':
809 if self._repo.dirstate[f] != 'a':
806 self._repo.ui.warn(_("%s not added!\n") % f)
810 self._repo.ui.warn(_("%s not added!\n") % f)
807 else:
811 else:
808 self._repo.dirstate.forget(f)
812 self._repo.dirstate.forget(f)
809 finally:
813 finally:
810 wlock.release()
814 wlock.release()
811
815
812 def remove(self, list, unlink=False):
816 def remove(self, list, unlink=False):
813 if unlink:
817 if unlink:
814 for f in list:
818 for f in list:
815 try:
819 try:
816 util.unlink(self._repo.wjoin(f))
820 util.unlink(self._repo.wjoin(f))
817 except OSError, inst:
821 except OSError, inst:
818 if inst.errno != errno.ENOENT:
822 if inst.errno != errno.ENOENT:
819 raise
823 raise
820 wlock = self._repo.wlock()
824 wlock = self._repo.wlock()
821 try:
825 try:
822 for f in list:
826 for f in list:
823 if unlink and os.path.exists(self._repo.wjoin(f)):
827 if unlink and os.path.exists(self._repo.wjoin(f)):
824 self._repo.ui.warn(_("%s still exists!\n") % f)
828 self._repo.ui.warn(_("%s still exists!\n") % f)
825 elif self._repo.dirstate[f] == 'a':
829 elif self._repo.dirstate[f] == 'a':
826 self._repo.dirstate.forget(f)
830 self._repo.dirstate.forget(f)
827 elif f not in self._repo.dirstate:
831 elif f not in self._repo.dirstate:
828 self._repo.ui.warn(_("%s not tracked!\n") % f)
832 self._repo.ui.warn(_("%s not tracked!\n") % f)
829 else:
833 else:
830 self._repo.dirstate.remove(f)
834 self._repo.dirstate.remove(f)
831 finally:
835 finally:
832 wlock.release()
836 wlock.release()
833
837
834 def undelete(self, list):
838 def undelete(self, list):
835 pctxs = self.parents()
839 pctxs = self.parents()
836 wlock = self._repo.wlock()
840 wlock = self._repo.wlock()
837 try:
841 try:
838 for f in list:
842 for f in list:
839 if self._repo.dirstate[f] != 'r':
843 if self._repo.dirstate[f] != 'r':
840 self._repo.ui.warn(_("%s not removed!\n") % f)
844 self._repo.ui.warn(_("%s not removed!\n") % f)
841 else:
845 else:
842 fctx = f in pctxs[0] and pctxs[0] or pctxs[1]
846 fctx = f in pctxs[0] and pctxs[0] or pctxs[1]
843 t = fctx.data()
847 t = fctx.data()
844 self._repo.wwrite(f, t, fctx.flags())
848 self._repo.wwrite(f, t, fctx.flags())
845 self._repo.dirstate.normal(f)
849 self._repo.dirstate.normal(f)
846 finally:
850 finally:
847 wlock.release()
851 wlock.release()
848
852
849 def copy(self, source, dest):
853 def copy(self, source, dest):
850 p = self._repo.wjoin(dest)
854 p = self._repo.wjoin(dest)
851 if not (os.path.exists(p) or os.path.islink(p)):
855 if not (os.path.exists(p) or os.path.islink(p)):
852 self._repo.ui.warn(_("%s does not exist!\n") % dest)
856 self._repo.ui.warn(_("%s does not exist!\n") % dest)
853 elif not (os.path.isfile(p) or os.path.islink(p)):
857 elif not (os.path.isfile(p) or os.path.islink(p)):
854 self._repo.ui.warn(_("copy failed: %s is not a file or a "
858 self._repo.ui.warn(_("copy failed: %s is not a file or a "
855 "symbolic link\n") % dest)
859 "symbolic link\n") % dest)
856 else:
860 else:
857 wlock = self._repo.wlock()
861 wlock = self._repo.wlock()
858 try:
862 try:
859 if self._repo.dirstate[dest] in '?r':
863 if self._repo.dirstate[dest] in '?r':
860 self._repo.dirstate.add(dest)
864 self._repo.dirstate.add(dest)
861 self._repo.dirstate.copy(source, dest)
865 self._repo.dirstate.copy(source, dest)
862 finally:
866 finally:
863 wlock.release()
867 wlock.release()
864
868
865 class workingfilectx(filectx):
869 class workingfilectx(filectx):
866 """A workingfilectx object makes access to data related to a particular
870 """A workingfilectx object makes access to data related to a particular
867 file in the working directory convenient."""
871 file in the working directory convenient."""
868 def __init__(self, repo, path, filelog=None, workingctx=None):
872 def __init__(self, repo, path, filelog=None, workingctx=None):
869 """changeid can be a changeset revision, node, or tag.
873 """changeid can be a changeset revision, node, or tag.
870 fileid can be a file revision or node."""
874 fileid can be a file revision or node."""
871 self._repo = repo
875 self._repo = repo
872 self._path = path
876 self._path = path
873 self._changeid = None
877 self._changeid = None
874 self._filerev = self._filenode = None
878 self._filerev = self._filenode = None
875
879
876 if filelog:
880 if filelog:
877 self._filelog = filelog
881 self._filelog = filelog
878 if workingctx:
882 if workingctx:
879 self._changectx = workingctx
883 self._changectx = workingctx
880
884
881 @propertycache
885 @propertycache
882 def _changectx(self):
886 def _changectx(self):
883 return workingctx(self._repo)
887 return workingctx(self._repo)
884
888
885 def __nonzero__(self):
889 def __nonzero__(self):
886 return True
890 return True
887
891
888 def __str__(self):
892 def __str__(self):
889 return "%s@%s" % (self.path(), self._changectx)
893 return "%s@%s" % (self.path(), self._changectx)
890
894
891 def data(self):
895 def data(self):
892 return self._repo.wread(self._path)
896 return self._repo.wread(self._path)
893 def renamed(self):
897 def renamed(self):
894 rp = self._repo.dirstate.copied(self._path)
898 rp = self._repo.dirstate.copied(self._path)
895 if not rp:
899 if not rp:
896 return None
900 return None
897 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
901 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
898
902
899 def parents(self):
903 def parents(self):
900 '''return parent filectxs, following copies if necessary'''
904 '''return parent filectxs, following copies if necessary'''
901 def filenode(ctx, path):
905 def filenode(ctx, path):
902 return ctx._manifest.get(path, nullid)
906 return ctx._manifest.get(path, nullid)
903
907
904 path = self._path
908 path = self._path
905 fl = self._filelog
909 fl = self._filelog
906 pcl = self._changectx._parents
910 pcl = self._changectx._parents
907 renamed = self.renamed()
911 renamed = self.renamed()
908
912
909 if renamed:
913 if renamed:
910 pl = [renamed + (None,)]
914 pl = [renamed + (None,)]
911 else:
915 else:
912 pl = [(path, filenode(pcl[0], path), fl)]
916 pl = [(path, filenode(pcl[0], path), fl)]
913
917
914 for pc in pcl[1:]:
918 for pc in pcl[1:]:
915 pl.append((path, filenode(pc, path), fl))
919 pl.append((path, filenode(pc, path), fl))
916
920
917 return [filectx(self._repo, p, fileid=n, filelog=l)
921 return [filectx(self._repo, p, fileid=n, filelog=l)
918 for p, n, l in pl if n != nullid]
922 for p, n, l in pl if n != nullid]
919
923
920 def children(self):
924 def children(self):
921 return []
925 return []
922
926
923 def size(self):
927 def size(self):
924 return os.stat(self._repo.wjoin(self._path)).st_size
928 return os.stat(self._repo.wjoin(self._path)).st_size
925 def date(self):
929 def date(self):
926 t, tz = self._changectx.date()
930 t, tz = self._changectx.date()
927 try:
931 try:
928 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
932 return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
929 except OSError, err:
933 except OSError, err:
930 if err.errno != errno.ENOENT:
934 if err.errno != errno.ENOENT:
931 raise
935 raise
932 return (t, tz)
936 return (t, tz)
933
937
934 def cmp(self, text):
938 def cmp(self, text):
939 """compare text with disk content
940
941 returns True if text is different than what is on disk.
942 """
935 return self._repo.wread(self._path) != text
943 return self._repo.wread(self._path) != text
936
944
937 class memctx(object):
945 class memctx(object):
938 """Use memctx to perform in-memory commits via localrepo.commitctx().
946 """Use memctx to perform in-memory commits via localrepo.commitctx().
939
947
940 Revision information is supplied at initialization time while
948 Revision information is supplied at initialization time while
941 related files data and is made available through a callback
949 related files data and is made available through a callback
942 mechanism. 'repo' is the current localrepo, 'parents' is a
950 mechanism. 'repo' is the current localrepo, 'parents' is a
943 sequence of two parent revisions identifiers (pass None for every
951 sequence of two parent revisions identifiers (pass None for every
944 missing parent), 'text' is the commit message and 'files' lists
952 missing parent), 'text' is the commit message and 'files' lists
945 names of files touched by the revision (normalized and relative to
953 names of files touched by the revision (normalized and relative to
946 repository root).
954 repository root).
947
955
948 filectxfn(repo, memctx, path) is a callable receiving the
956 filectxfn(repo, memctx, path) is a callable receiving the
949 repository, the current memctx object and the normalized path of
957 repository, the current memctx object and the normalized path of
950 requested file, relative to repository root. It is fired by the
958 requested file, relative to repository root. It is fired by the
951 commit function for every file in 'files', but calls order is
959 commit function for every file in 'files', but calls order is
952 undefined. If the file is available in the revision being
960 undefined. If the file is available in the revision being
953 committed (updated or added), filectxfn returns a memfilectx
961 committed (updated or added), filectxfn returns a memfilectx
954 object. If the file was removed, filectxfn raises an
962 object. If the file was removed, filectxfn raises an
955 IOError. Moved files are represented by marking the source file
963 IOError. Moved files are represented by marking the source file
956 removed and the new file added with copy information (see
964 removed and the new file added with copy information (see
957 memfilectx).
965 memfilectx).
958
966
959 user receives the committer name and defaults to current
967 user receives the committer name and defaults to current
960 repository username, date is the commit date in any format
968 repository username, date is the commit date in any format
961 supported by util.parsedate() and defaults to current date, extra
969 supported by util.parsedate() and defaults to current date, extra
962 is a dictionary of metadata or is left empty.
970 is a dictionary of metadata or is left empty.
963 """
971 """
964 def __init__(self, repo, parents, text, files, filectxfn, user=None,
972 def __init__(self, repo, parents, text, files, filectxfn, user=None,
965 date=None, extra=None):
973 date=None, extra=None):
966 self._repo = repo
974 self._repo = repo
967 self._rev = None
975 self._rev = None
968 self._node = None
976 self._node = None
969 self._text = text
977 self._text = text
970 self._date = date and util.parsedate(date) or util.makedate()
978 self._date = date and util.parsedate(date) or util.makedate()
971 self._user = user
979 self._user = user
972 parents = [(p or nullid) for p in parents]
980 parents = [(p or nullid) for p in parents]
973 p1, p2 = parents
981 p1, p2 = parents
974 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
982 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
975 files = sorted(set(files))
983 files = sorted(set(files))
976 self._status = [files, [], [], [], []]
984 self._status = [files, [], [], [], []]
977 self._filectxfn = filectxfn
985 self._filectxfn = filectxfn
978
986
979 self._extra = extra and extra.copy() or {}
987 self._extra = extra and extra.copy() or {}
980 if 'branch' not in self._extra:
988 if 'branch' not in self._extra:
981 self._extra['branch'] = 'default'
989 self._extra['branch'] = 'default'
982 elif self._extra.get('branch') == '':
990 elif self._extra.get('branch') == '':
983 self._extra['branch'] = 'default'
991 self._extra['branch'] = 'default'
984
992
985 def __str__(self):
993 def __str__(self):
986 return str(self._parents[0]) + "+"
994 return str(self._parents[0]) + "+"
987
995
988 def __int__(self):
996 def __int__(self):
989 return self._rev
997 return self._rev
990
998
991 def __nonzero__(self):
999 def __nonzero__(self):
992 return True
1000 return True
993
1001
994 def __getitem__(self, key):
1002 def __getitem__(self, key):
995 return self.filectx(key)
1003 return self.filectx(key)
996
1004
997 def p1(self):
1005 def p1(self):
998 return self._parents[0]
1006 return self._parents[0]
999 def p2(self):
1007 def p2(self):
1000 return self._parents[1]
1008 return self._parents[1]
1001
1009
1002 def user(self):
1010 def user(self):
1003 return self._user or self._repo.ui.username()
1011 return self._user or self._repo.ui.username()
1004 def date(self):
1012 def date(self):
1005 return self._date
1013 return self._date
1006 def description(self):
1014 def description(self):
1007 return self._text
1015 return self._text
1008 def files(self):
1016 def files(self):
1009 return self.modified()
1017 return self.modified()
1010 def modified(self):
1018 def modified(self):
1011 return self._status[0]
1019 return self._status[0]
1012 def added(self):
1020 def added(self):
1013 return self._status[1]
1021 return self._status[1]
1014 def removed(self):
1022 def removed(self):
1015 return self._status[2]
1023 return self._status[2]
1016 def deleted(self):
1024 def deleted(self):
1017 return self._status[3]
1025 return self._status[3]
1018 def unknown(self):
1026 def unknown(self):
1019 return self._status[4]
1027 return self._status[4]
1020 def ignored(self):
1028 def ignored(self):
1021 return self._status[5]
1029 return self._status[5]
1022 def clean(self):
1030 def clean(self):
1023 return self._status[6]
1031 return self._status[6]
1024 def branch(self):
1032 def branch(self):
1025 return self._extra['branch']
1033 return self._extra['branch']
1026 def extra(self):
1034 def extra(self):
1027 return self._extra
1035 return self._extra
1028 def flags(self, f):
1036 def flags(self, f):
1029 return self[f].flags()
1037 return self[f].flags()
1030
1038
1031 def parents(self):
1039 def parents(self):
1032 """return contexts for each parent changeset"""
1040 """return contexts for each parent changeset"""
1033 return self._parents
1041 return self._parents
1034
1042
1035 def filectx(self, path, filelog=None):
1043 def filectx(self, path, filelog=None):
1036 """get a file context from the working directory"""
1044 """get a file context from the working directory"""
1037 return self._filectxfn(self._repo, self, path)
1045 return self._filectxfn(self._repo, self, path)
1038
1046
1039 def commit(self):
1047 def commit(self):
1040 """commit context to the repo"""
1048 """commit context to the repo"""
1041 return self._repo.commitctx(self)
1049 return self._repo.commitctx(self)
1042
1050
1043 class memfilectx(object):
1051 class memfilectx(object):
1044 """memfilectx represents an in-memory file to commit.
1052 """memfilectx represents an in-memory file to commit.
1045
1053
1046 See memctx for more details.
1054 See memctx for more details.
1047 """
1055 """
1048 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1056 def __init__(self, path, data, islink=False, isexec=False, copied=None):
1049 """
1057 """
1050 path is the normalized file path relative to repository root.
1058 path is the normalized file path relative to repository root.
1051 data is the file content as a string.
1059 data is the file content as a string.
1052 islink is True if the file is a symbolic link.
1060 islink is True if the file is a symbolic link.
1053 isexec is True if the file is executable.
1061 isexec is True if the file is executable.
1054 copied is the source file path if current file was copied in the
1062 copied is the source file path if current file was copied in the
1055 revision being committed, or None."""
1063 revision being committed, or None."""
1056 self._path = path
1064 self._path = path
1057 self._data = data
1065 self._data = data
1058 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1066 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1059 self._copied = None
1067 self._copied = None
1060 if copied:
1068 if copied:
1061 self._copied = (copied, nullid)
1069 self._copied = (copied, nullid)
1062
1070
1063 def __nonzero__(self):
1071 def __nonzero__(self):
1064 return True
1072 return True
1065 def __str__(self):
1073 def __str__(self):
1066 return "%s@%s" % (self.path(), self._changectx)
1074 return "%s@%s" % (self.path(), self._changectx)
1067 def path(self):
1075 def path(self):
1068 return self._path
1076 return self._path
1069 def data(self):
1077 def data(self):
1070 return self._data
1078 return self._data
1071 def flags(self):
1079 def flags(self):
1072 return self._flags
1080 return self._flags
1073 def isexec(self):
1081 def isexec(self):
1074 return 'x' in self._flags
1082 return 'x' in self._flags
1075 def islink(self):
1083 def islink(self):
1076 return 'l' in self._flags
1084 return 'l' in self._flags
1077 def renamed(self):
1085 def renamed(self):
1078 return self._copied
1086 return self._copied
@@ -1,66 +1,69 b''
1 # filelog.py - file history class for mercurial
1 # filelog.py - file history 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 import revlog
8 import revlog
9
9
10 class filelog(revlog.revlog):
10 class filelog(revlog.revlog):
11 def __init__(self, opener, path):
11 def __init__(self, opener, path):
12 revlog.revlog.__init__(self, opener,
12 revlog.revlog.__init__(self, opener,
13 "/".join(("data", path + ".i")))
13 "/".join(("data", path + ".i")))
14
14
15 def read(self, node):
15 def read(self, node):
16 t = self.revision(node)
16 t = self.revision(node)
17 if not t.startswith('\1\n'):
17 if not t.startswith('\1\n'):
18 return t
18 return t
19 s = t.index('\1\n', 2)
19 s = t.index('\1\n', 2)
20 return t[s + 2:]
20 return t[s + 2:]
21
21
22 def _readmeta(self, node):
22 def _readmeta(self, node):
23 t = self.revision(node)
23 t = self.revision(node)
24 if not t.startswith('\1\n'):
24 if not t.startswith('\1\n'):
25 return {}
25 return {}
26 s = t.index('\1\n', 2)
26 s = t.index('\1\n', 2)
27 mt = t[2:s]
27 mt = t[2:s]
28 m = {}
28 m = {}
29 for l in mt.splitlines():
29 for l in mt.splitlines():
30 k, v = l.split(": ", 1)
30 k, v = l.split(": ", 1)
31 m[k] = v
31 m[k] = v
32 return m
32 return m
33
33
34 def add(self, text, meta, transaction, link, p1=None, p2=None):
34 def add(self, text, meta, transaction, link, p1=None, p2=None):
35 if meta or text.startswith('\1\n'):
35 if meta or text.startswith('\1\n'):
36 mt = ["%s: %s\n" % (k, v) for k, v in sorted(meta.iteritems())]
36 mt = ["%s: %s\n" % (k, v) for k, v in sorted(meta.iteritems())]
37 text = "\1\n%s\1\n%s" % ("".join(mt), text)
37 text = "\1\n%s\1\n%s" % ("".join(mt), text)
38 return self.addrevision(text, transaction, link, p1, p2)
38 return self.addrevision(text, transaction, link, p1, p2)
39
39
40 def renamed(self, node):
40 def renamed(self, node):
41 if self.parents(node)[0] != revlog.nullid:
41 if self.parents(node)[0] != revlog.nullid:
42 return False
42 return False
43 m = self._readmeta(node)
43 m = self._readmeta(node)
44 if m and "copy" in m:
44 if m and "copy" in m:
45 return (m["copy"], revlog.bin(m["copyrev"]))
45 return (m["copy"], revlog.bin(m["copyrev"]))
46 return False
46 return False
47
47
48 def size(self, rev):
48 def size(self, rev):
49 """return the size of a given revision"""
49 """return the size of a given revision"""
50
50
51 # for revisions with renames, we have to go the slow way
51 # for revisions with renames, we have to go the slow way
52 node = self.node(rev)
52 node = self.node(rev)
53 if self.renamed(node):
53 if self.renamed(node):
54 return len(self.read(node))
54 return len(self.read(node))
55
55
56 return revlog.revlog.size(self, rev)
56 return revlog.revlog.size(self, rev)
57
57
58 def cmp(self, node, text):
58 def cmp(self, node, text):
59 """compare text with a given file revision"""
59 """compare text with a given file revision
60
61 returns True if text is different than what is stored.
62 """
60
63
61 # for renames, we have to go the slow way
64 # for renames, we have to go the slow way
62 if text.startswith('\1\n') or self.renamed(node):
65 if text.startswith('\1\n') or self.renamed(node):
63 t2 = self.read(node)
66 t2 = self.read(node)
64 return t2 != text
67 return t2 != text
65
68
66 return revlog.revlog.cmp(self, node, text)
69 return revlog.revlog.cmp(self, node, text)
@@ -1,1402 +1,1405 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 changegroup, ancestor, mdiff, parsers, error, util
17 import changegroup, 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 flags
26 # revlog flags
27 REVLOGV0 = 0
27 REVLOGV0 = 0
28 REVLOGNG = 1
28 REVLOGNG = 1
29 REVLOGNGINLINEDATA = (1 << 16)
29 REVLOGNGINLINEDATA = (1 << 16)
30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
30 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
31 REVLOG_DEFAULT_FORMAT = REVLOGNG
31 REVLOG_DEFAULT_FORMAT = REVLOGNG
32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
32 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
33
33
34 # amount of data read unconditionally, should be >= 4
34 # amount of data read unconditionally, should be >= 4
35 # when not inline: threshold for using lazy index
35 # when not inline: threshold for using lazy index
36 _prereadsize = 1048576
36 _prereadsize = 1048576
37 # max size of revlog with inline data
37 # max size of revlog with inline data
38 _maxinline = 131072
38 _maxinline = 131072
39
39
40 RevlogError = error.RevlogError
40 RevlogError = error.RevlogError
41 LookupError = error.LookupError
41 LookupError = error.LookupError
42
42
43 def getoffset(q):
43 def getoffset(q):
44 return int(q >> 16)
44 return int(q >> 16)
45
45
46 def gettype(q):
46 def gettype(q):
47 return int(q & 0xFFFF)
47 return int(q & 0xFFFF)
48
48
49 def offset_type(offset, type):
49 def offset_type(offset, type):
50 return long(long(offset) << 16 | type)
50 return long(long(offset) << 16 | type)
51
51
52 nullhash = _sha(nullid)
52 nullhash = _sha(nullid)
53
53
54 def hash(text, p1, p2):
54 def hash(text, p1, p2):
55 """generate a hash from the given text and its parent hashes
55 """generate a hash from the given text and its parent hashes
56
56
57 This hash combines both the current file contents and its history
57 This hash combines both the current file contents and its history
58 in a manner that makes it easy to distinguish nodes with the same
58 in a manner that makes it easy to distinguish nodes with the same
59 content in the revision graph.
59 content in the revision graph.
60 """
60 """
61 # As of now, if one of the parent node is null, p2 is null
61 # As of now, if one of the parent node is null, p2 is null
62 if p2 == nullid:
62 if p2 == nullid:
63 # deep copy of a hash is faster than creating one
63 # deep copy of a hash is faster than creating one
64 s = nullhash.copy()
64 s = nullhash.copy()
65 s.update(p1)
65 s.update(p1)
66 else:
66 else:
67 # none of the parent nodes are nullid
67 # none of the parent nodes are nullid
68 l = [p1, p2]
68 l = [p1, p2]
69 l.sort()
69 l.sort()
70 s = _sha(l[0])
70 s = _sha(l[0])
71 s.update(l[1])
71 s.update(l[1])
72 s.update(text)
72 s.update(text)
73 return s.digest()
73 return s.digest()
74
74
75 def compress(text):
75 def compress(text):
76 """ generate a possibly-compressed representation of text """
76 """ generate a possibly-compressed representation of text """
77 if not text:
77 if not text:
78 return ("", text)
78 return ("", text)
79 l = len(text)
79 l = len(text)
80 bin = None
80 bin = None
81 if l < 44:
81 if l < 44:
82 pass
82 pass
83 elif l > 1000000:
83 elif l > 1000000:
84 # zlib makes an internal copy, thus doubling memory usage for
84 # zlib makes an internal copy, thus doubling memory usage for
85 # large files, so lets do this in pieces
85 # large files, so lets do this in pieces
86 z = zlib.compressobj()
86 z = zlib.compressobj()
87 p = []
87 p = []
88 pos = 0
88 pos = 0
89 while pos < l:
89 while pos < l:
90 pos2 = pos + 2**20
90 pos2 = pos + 2**20
91 p.append(z.compress(text[pos:pos2]))
91 p.append(z.compress(text[pos:pos2]))
92 pos = pos2
92 pos = pos2
93 p.append(z.flush())
93 p.append(z.flush())
94 if sum(map(len, p)) < l:
94 if sum(map(len, p)) < l:
95 bin = "".join(p)
95 bin = "".join(p)
96 else:
96 else:
97 bin = _compress(text)
97 bin = _compress(text)
98 if bin is None or len(bin) > l:
98 if bin is None or len(bin) > l:
99 if text[0] == '\0':
99 if text[0] == '\0':
100 return ("", text)
100 return ("", text)
101 return ('u', text)
101 return ('u', text)
102 return ("", bin)
102 return ("", bin)
103
103
104 def decompress(bin):
104 def decompress(bin):
105 """ decompress the given input """
105 """ decompress the given input """
106 if not bin:
106 if not bin:
107 return bin
107 return bin
108 t = bin[0]
108 t = bin[0]
109 if t == '\0':
109 if t == '\0':
110 return bin
110 return bin
111 if t == 'x':
111 if t == 'x':
112 return _decompress(bin)
112 return _decompress(bin)
113 if t == 'u':
113 if t == 'u':
114 return bin[1:]
114 return bin[1:]
115 raise RevlogError(_("unknown compression type %r") % t)
115 raise RevlogError(_("unknown compression type %r") % t)
116
116
117 class lazyparser(object):
117 class lazyparser(object):
118 """
118 """
119 this class avoids the need to parse the entirety of large indices
119 this class avoids the need to parse the entirety of large indices
120 """
120 """
121
121
122 # lazyparser is not safe to use on windows if win32 extensions not
122 # lazyparser is not safe to use on windows if win32 extensions not
123 # available. it keeps file handle open, which make it not possible
123 # available. it keeps file handle open, which make it not possible
124 # to break hardlinks on local cloned repos.
124 # to break hardlinks on local cloned repos.
125
125
126 def __init__(self, dataf):
126 def __init__(self, dataf):
127 try:
127 try:
128 size = util.fstat(dataf).st_size
128 size = util.fstat(dataf).st_size
129 except AttributeError:
129 except AttributeError:
130 size = 0
130 size = 0
131 self.dataf = dataf
131 self.dataf = dataf
132 self.s = struct.calcsize(indexformatng)
132 self.s = struct.calcsize(indexformatng)
133 self.datasize = size
133 self.datasize = size
134 self.l = size / self.s
134 self.l = size / self.s
135 self.index = [None] * self.l
135 self.index = [None] * self.l
136 self.map = {nullid: nullrev}
136 self.map = {nullid: nullrev}
137 self.allmap = 0
137 self.allmap = 0
138 self.all = 0
138 self.all = 0
139 self.mapfind_count = 0
139 self.mapfind_count = 0
140
140
141 def loadmap(self):
141 def loadmap(self):
142 """
142 """
143 during a commit, we need to make sure the rev being added is
143 during a commit, we need to make sure the rev being added is
144 not a duplicate. This requires loading the entire index,
144 not a duplicate. This requires loading the entire index,
145 which is fairly slow. loadmap can load up just the node map,
145 which is fairly slow. loadmap can load up just the node map,
146 which takes much less time.
146 which takes much less time.
147 """
147 """
148 if self.allmap:
148 if self.allmap:
149 return
149 return
150 end = self.datasize
150 end = self.datasize
151 self.allmap = 1
151 self.allmap = 1
152 cur = 0
152 cur = 0
153 count = 0
153 count = 0
154 blocksize = self.s * 256
154 blocksize = self.s * 256
155 self.dataf.seek(0)
155 self.dataf.seek(0)
156 while cur < end:
156 while cur < end:
157 data = self.dataf.read(blocksize)
157 data = self.dataf.read(blocksize)
158 off = 0
158 off = 0
159 for x in xrange(256):
159 for x in xrange(256):
160 n = data[off + ngshaoffset:off + ngshaoffset + 20]
160 n = data[off + ngshaoffset:off + ngshaoffset + 20]
161 self.map[n] = count
161 self.map[n] = count
162 count += 1
162 count += 1
163 if count >= self.l:
163 if count >= self.l:
164 break
164 break
165 off += self.s
165 off += self.s
166 cur += blocksize
166 cur += blocksize
167
167
168 def loadblock(self, blockstart, blocksize, data=None):
168 def loadblock(self, blockstart, blocksize, data=None):
169 if self.all:
169 if self.all:
170 return
170 return
171 if data is None:
171 if data is None:
172 self.dataf.seek(blockstart)
172 self.dataf.seek(blockstart)
173 if blockstart + blocksize > self.datasize:
173 if blockstart + blocksize > self.datasize:
174 # the revlog may have grown since we've started running,
174 # the revlog may have grown since we've started running,
175 # but we don't have space in self.index for more entries.
175 # but we don't have space in self.index for more entries.
176 # limit blocksize so that we don't get too much data.
176 # limit blocksize so that we don't get too much data.
177 blocksize = max(self.datasize - blockstart, 0)
177 blocksize = max(self.datasize - blockstart, 0)
178 data = self.dataf.read(blocksize)
178 data = self.dataf.read(blocksize)
179 lend = len(data) / self.s
179 lend = len(data) / self.s
180 i = blockstart / self.s
180 i = blockstart / self.s
181 off = 0
181 off = 0
182 # lazyindex supports __delitem__
182 # lazyindex supports __delitem__
183 if lend > len(self.index) - i:
183 if lend > len(self.index) - i:
184 lend = len(self.index) - i
184 lend = len(self.index) - i
185 for x in xrange(lend):
185 for x in xrange(lend):
186 if self.index[i + x] is None:
186 if self.index[i + x] is None:
187 b = data[off : off + self.s]
187 b = data[off : off + self.s]
188 self.index[i + x] = b
188 self.index[i + x] = b
189 n = b[ngshaoffset:ngshaoffset + 20]
189 n = b[ngshaoffset:ngshaoffset + 20]
190 self.map[n] = i + x
190 self.map[n] = i + x
191 off += self.s
191 off += self.s
192
192
193 def findnode(self, node):
193 def findnode(self, node):
194 """search backwards through the index file for a specific node"""
194 """search backwards through the index file for a specific node"""
195 if self.allmap:
195 if self.allmap:
196 return None
196 return None
197
197
198 # hg log will cause many many searches for the manifest
198 # hg log will cause many many searches for the manifest
199 # nodes. After we get called a few times, just load the whole
199 # nodes. After we get called a few times, just load the whole
200 # thing.
200 # thing.
201 if self.mapfind_count > 8:
201 if self.mapfind_count > 8:
202 self.loadmap()
202 self.loadmap()
203 if node in self.map:
203 if node in self.map:
204 return node
204 return node
205 return None
205 return None
206 self.mapfind_count += 1
206 self.mapfind_count += 1
207 last = self.l - 1
207 last = self.l - 1
208 while self.index[last] != None:
208 while self.index[last] != None:
209 if last == 0:
209 if last == 0:
210 self.all = 1
210 self.all = 1
211 self.allmap = 1
211 self.allmap = 1
212 return None
212 return None
213 last -= 1
213 last -= 1
214 end = (last + 1) * self.s
214 end = (last + 1) * self.s
215 blocksize = self.s * 256
215 blocksize = self.s * 256
216 while end >= 0:
216 while end >= 0:
217 start = max(end - blocksize, 0)
217 start = max(end - blocksize, 0)
218 self.dataf.seek(start)
218 self.dataf.seek(start)
219 data = self.dataf.read(end - start)
219 data = self.dataf.read(end - start)
220 findend = end - start
220 findend = end - start
221 while True:
221 while True:
222 # we're searching backwards, so we have to make sure
222 # we're searching backwards, so we have to make sure
223 # we don't find a changeset where this node is a parent
223 # we don't find a changeset where this node is a parent
224 off = data.find(node, 0, findend)
224 off = data.find(node, 0, findend)
225 findend = off
225 findend = off
226 if off >= 0:
226 if off >= 0:
227 i = off / self.s
227 i = off / self.s
228 off = i * self.s
228 off = i * self.s
229 n = data[off + ngshaoffset:off + ngshaoffset + 20]
229 n = data[off + ngshaoffset:off + ngshaoffset + 20]
230 if n == node:
230 if n == node:
231 self.map[n] = i + start / self.s
231 self.map[n] = i + start / self.s
232 return node
232 return node
233 else:
233 else:
234 break
234 break
235 end -= blocksize
235 end -= blocksize
236 return None
236 return None
237
237
238 def loadindex(self, i=None, end=None):
238 def loadindex(self, i=None, end=None):
239 if self.all:
239 if self.all:
240 return
240 return
241 all = False
241 all = False
242 if i is None:
242 if i is None:
243 blockstart = 0
243 blockstart = 0
244 blocksize = (65536 / self.s) * self.s
244 blocksize = (65536 / self.s) * self.s
245 end = self.datasize
245 end = self.datasize
246 all = True
246 all = True
247 else:
247 else:
248 if end:
248 if end:
249 blockstart = i * self.s
249 blockstart = i * self.s
250 end = end * self.s
250 end = end * self.s
251 blocksize = end - blockstart
251 blocksize = end - blockstart
252 else:
252 else:
253 blockstart = (i & ~1023) * self.s
253 blockstart = (i & ~1023) * self.s
254 blocksize = self.s * 1024
254 blocksize = self.s * 1024
255 end = blockstart + blocksize
255 end = blockstart + blocksize
256 while blockstart < end:
256 while blockstart < end:
257 self.loadblock(blockstart, blocksize)
257 self.loadblock(blockstart, blocksize)
258 blockstart += blocksize
258 blockstart += blocksize
259 if all:
259 if all:
260 self.all = True
260 self.all = True
261
261
262 class lazyindex(object):
262 class lazyindex(object):
263 """a lazy version of the index array"""
263 """a lazy version of the index array"""
264 def __init__(self, parser):
264 def __init__(self, parser):
265 self.p = parser
265 self.p = parser
266 def __len__(self):
266 def __len__(self):
267 return len(self.p.index)
267 return len(self.p.index)
268 def load(self, pos):
268 def load(self, pos):
269 if pos < 0:
269 if pos < 0:
270 pos += len(self.p.index)
270 pos += len(self.p.index)
271 self.p.loadindex(pos)
271 self.p.loadindex(pos)
272 return self.p.index[pos]
272 return self.p.index[pos]
273 def __getitem__(self, pos):
273 def __getitem__(self, pos):
274 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
274 return _unpack(indexformatng, self.p.index[pos] or self.load(pos))
275 def __setitem__(self, pos, item):
275 def __setitem__(self, pos, item):
276 self.p.index[pos] = _pack(indexformatng, *item)
276 self.p.index[pos] = _pack(indexformatng, *item)
277 def __delitem__(self, pos):
277 def __delitem__(self, pos):
278 del self.p.index[pos]
278 del self.p.index[pos]
279 def insert(self, pos, e):
279 def insert(self, pos, e):
280 self.p.index.insert(pos, _pack(indexformatng, *e))
280 self.p.index.insert(pos, _pack(indexformatng, *e))
281 def append(self, e):
281 def append(self, e):
282 self.p.index.append(_pack(indexformatng, *e))
282 self.p.index.append(_pack(indexformatng, *e))
283
283
284 class lazymap(object):
284 class lazymap(object):
285 """a lazy version of the node map"""
285 """a lazy version of the node map"""
286 def __init__(self, parser):
286 def __init__(self, parser):
287 self.p = parser
287 self.p = parser
288 def load(self, key):
288 def load(self, key):
289 n = self.p.findnode(key)
289 n = self.p.findnode(key)
290 if n is None:
290 if n is None:
291 raise KeyError(key)
291 raise KeyError(key)
292 def __contains__(self, key):
292 def __contains__(self, key):
293 if key in self.p.map:
293 if key in self.p.map:
294 return True
294 return True
295 self.p.loadmap()
295 self.p.loadmap()
296 return key in self.p.map
296 return key in self.p.map
297 def __iter__(self):
297 def __iter__(self):
298 yield nullid
298 yield nullid
299 for i, ret in enumerate(self.p.index):
299 for i, ret in enumerate(self.p.index):
300 if not ret:
300 if not ret:
301 self.p.loadindex(i)
301 self.p.loadindex(i)
302 ret = self.p.index[i]
302 ret = self.p.index[i]
303 if isinstance(ret, str):
303 if isinstance(ret, str):
304 ret = _unpack(indexformatng, ret)
304 ret = _unpack(indexformatng, ret)
305 yield ret[7]
305 yield ret[7]
306 def __getitem__(self, key):
306 def __getitem__(self, key):
307 try:
307 try:
308 return self.p.map[key]
308 return self.p.map[key]
309 except KeyError:
309 except KeyError:
310 try:
310 try:
311 self.load(key)
311 self.load(key)
312 return self.p.map[key]
312 return self.p.map[key]
313 except KeyError:
313 except KeyError:
314 raise KeyError("node " + hex(key))
314 raise KeyError("node " + hex(key))
315 def __setitem__(self, key, val):
315 def __setitem__(self, key, val):
316 self.p.map[key] = val
316 self.p.map[key] = val
317 def __delitem__(self, key):
317 def __delitem__(self, key):
318 del self.p.map[key]
318 del self.p.map[key]
319
319
320 indexformatv0 = ">4l20s20s20s"
320 indexformatv0 = ">4l20s20s20s"
321 v0shaoffset = 56
321 v0shaoffset = 56
322
322
323 class revlogoldio(object):
323 class revlogoldio(object):
324 def __init__(self):
324 def __init__(self):
325 self.size = struct.calcsize(indexformatv0)
325 self.size = struct.calcsize(indexformatv0)
326
326
327 def parseindex(self, fp, data, inline):
327 def parseindex(self, fp, data, inline):
328 s = self.size
328 s = self.size
329 index = []
329 index = []
330 nodemap = {nullid: nullrev}
330 nodemap = {nullid: nullrev}
331 n = off = 0
331 n = off = 0
332 if len(data) == _prereadsize:
332 if len(data) == _prereadsize:
333 data += fp.read() # read the rest
333 data += fp.read() # read the rest
334 l = len(data)
334 l = len(data)
335 while off + s <= l:
335 while off + s <= l:
336 cur = data[off:off + s]
336 cur = data[off:off + s]
337 off += s
337 off += s
338 e = _unpack(indexformatv0, cur)
338 e = _unpack(indexformatv0, cur)
339 # transform to revlogv1 format
339 # transform to revlogv1 format
340 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
340 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
341 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
341 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
342 index.append(e2)
342 index.append(e2)
343 nodemap[e[6]] = n
343 nodemap[e[6]] = n
344 n += 1
344 n += 1
345
345
346 return index, nodemap, None
346 return index, nodemap, None
347
347
348 def packentry(self, entry, node, version, rev):
348 def packentry(self, entry, node, version, rev):
349 if gettype(entry[0]):
349 if gettype(entry[0]):
350 raise RevlogError(_("index entry flags need RevlogNG"))
350 raise RevlogError(_("index entry flags need RevlogNG"))
351 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
351 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
352 node(entry[5]), node(entry[6]), entry[7])
352 node(entry[5]), node(entry[6]), entry[7])
353 return _pack(indexformatv0, *e2)
353 return _pack(indexformatv0, *e2)
354
354
355 # index ng:
355 # index ng:
356 # 6 bytes: offset
356 # 6 bytes: offset
357 # 2 bytes: flags
357 # 2 bytes: flags
358 # 4 bytes: compressed length
358 # 4 bytes: compressed length
359 # 4 bytes: uncompressed length
359 # 4 bytes: uncompressed length
360 # 4 bytes: base rev
360 # 4 bytes: base rev
361 # 4 bytes: link rev
361 # 4 bytes: link rev
362 # 4 bytes: parent 1 rev
362 # 4 bytes: parent 1 rev
363 # 4 bytes: parent 2 rev
363 # 4 bytes: parent 2 rev
364 # 32 bytes: nodeid
364 # 32 bytes: nodeid
365 indexformatng = ">Qiiiiii20s12x"
365 indexformatng = ">Qiiiiii20s12x"
366 ngshaoffset = 32
366 ngshaoffset = 32
367 versionformat = ">I"
367 versionformat = ">I"
368
368
369 class revlogio(object):
369 class revlogio(object):
370 def __init__(self):
370 def __init__(self):
371 self.size = struct.calcsize(indexformatng)
371 self.size = struct.calcsize(indexformatng)
372
372
373 def parseindex(self, fp, data, inline):
373 def parseindex(self, fp, data, inline):
374 if len(data) == _prereadsize:
374 if len(data) == _prereadsize:
375 if util.openhardlinks() and not inline:
375 if util.openhardlinks() and not inline:
376 # big index, let's parse it on demand
376 # big index, let's parse it on demand
377 parser = lazyparser(fp)
377 parser = lazyparser(fp)
378 index = lazyindex(parser)
378 index = lazyindex(parser)
379 nodemap = lazymap(parser)
379 nodemap = lazymap(parser)
380 e = list(index[0])
380 e = list(index[0])
381 type = gettype(e[0])
381 type = gettype(e[0])
382 e[0] = offset_type(0, type)
382 e[0] = offset_type(0, type)
383 index[0] = e
383 index[0] = e
384 return index, nodemap, None
384 return index, nodemap, None
385 else:
385 else:
386 data += fp.read()
386 data += fp.read()
387
387
388 # call the C implementation to parse the index data
388 # call the C implementation to parse the index data
389 index, nodemap, cache = parsers.parse_index(data, inline)
389 index, nodemap, cache = parsers.parse_index(data, inline)
390 return index, nodemap, cache
390 return index, nodemap, cache
391
391
392 def packentry(self, entry, node, version, rev):
392 def packentry(self, entry, node, version, rev):
393 p = _pack(indexformatng, *entry)
393 p = _pack(indexformatng, *entry)
394 if rev == 0:
394 if rev == 0:
395 p = _pack(versionformat, version) + p[4:]
395 p = _pack(versionformat, version) + p[4:]
396 return p
396 return p
397
397
398 class revlog(object):
398 class revlog(object):
399 """
399 """
400 the underlying revision storage object
400 the underlying revision storage object
401
401
402 A revlog consists of two parts, an index and the revision data.
402 A revlog consists of two parts, an index and the revision data.
403
403
404 The index is a file with a fixed record size containing
404 The index is a file with a fixed record size containing
405 information on each revision, including its nodeid (hash), the
405 information on each revision, including its nodeid (hash), the
406 nodeids of its parents, the position and offset of its data within
406 nodeids of its parents, the position and offset of its data within
407 the data file, and the revision it's based on. Finally, each entry
407 the data file, and the revision it's based on. Finally, each entry
408 contains a linkrev entry that can serve as a pointer to external
408 contains a linkrev entry that can serve as a pointer to external
409 data.
409 data.
410
410
411 The revision data itself is a linear collection of data chunks.
411 The revision data itself is a linear collection of data chunks.
412 Each chunk represents a revision and is usually represented as a
412 Each chunk represents a revision and is usually represented as a
413 delta against the previous chunk. To bound lookup time, runs of
413 delta against the previous chunk. To bound lookup time, runs of
414 deltas are limited to about 2 times the length of the original
414 deltas are limited to about 2 times the length of the original
415 version data. This makes retrieval of a version proportional to
415 version data. This makes retrieval of a version proportional to
416 its size, or O(1) relative to the number of revisions.
416 its size, or O(1) relative to the number of revisions.
417
417
418 Both pieces of the revlog are written to in an append-only
418 Both pieces of the revlog are written to in an append-only
419 fashion, which means we never need to rewrite a file to insert or
419 fashion, which means we never need to rewrite a file to insert or
420 remove data, and can use some simple techniques to avoid the need
420 remove data, and can use some simple techniques to avoid the need
421 for locking while reading.
421 for locking while reading.
422 """
422 """
423 def __init__(self, opener, indexfile):
423 def __init__(self, opener, indexfile):
424 """
424 """
425 create a revlog object
425 create a revlog object
426
426
427 opener is a function that abstracts the file opening operation
427 opener is a function that abstracts the file opening operation
428 and can be used to implement COW semantics or the like.
428 and can be used to implement COW semantics or the like.
429 """
429 """
430 self.indexfile = indexfile
430 self.indexfile = indexfile
431 self.datafile = indexfile[:-2] + ".d"
431 self.datafile = indexfile[:-2] + ".d"
432 self.opener = opener
432 self.opener = opener
433 self._cache = None
433 self._cache = None
434 self._chunkcache = (0, '')
434 self._chunkcache = (0, '')
435 self.nodemap = {nullid: nullrev}
435 self.nodemap = {nullid: nullrev}
436 self.index = []
436 self.index = []
437
437
438 v = REVLOG_DEFAULT_VERSION
438 v = REVLOG_DEFAULT_VERSION
439 if hasattr(opener, 'options') and 'defversion' in opener.options:
439 if hasattr(opener, 'options') and 'defversion' in opener.options:
440 v = opener.options['defversion']
440 v = opener.options['defversion']
441 if v & REVLOGNG:
441 if v & REVLOGNG:
442 v |= REVLOGNGINLINEDATA
442 v |= REVLOGNGINLINEDATA
443
443
444 i = ''
444 i = ''
445 try:
445 try:
446 f = self.opener(self.indexfile)
446 f = self.opener(self.indexfile)
447 if "nonlazy" in getattr(self.opener, 'options', {}):
447 if "nonlazy" in getattr(self.opener, 'options', {}):
448 i = f.read()
448 i = f.read()
449 else:
449 else:
450 i = f.read(_prereadsize)
450 i = f.read(_prereadsize)
451 if len(i) > 0:
451 if len(i) > 0:
452 v = struct.unpack(versionformat, i[:4])[0]
452 v = struct.unpack(versionformat, i[:4])[0]
453 except IOError, inst:
453 except IOError, inst:
454 if inst.errno != errno.ENOENT:
454 if inst.errno != errno.ENOENT:
455 raise
455 raise
456
456
457 self.version = v
457 self.version = v
458 self._inline = v & REVLOGNGINLINEDATA
458 self._inline = v & REVLOGNGINLINEDATA
459 flags = v & ~0xFFFF
459 flags = v & ~0xFFFF
460 fmt = v & 0xFFFF
460 fmt = v & 0xFFFF
461 if fmt == REVLOGV0 and flags:
461 if fmt == REVLOGV0 and flags:
462 raise RevlogError(_("index %s unknown flags %#04x for format v0")
462 raise RevlogError(_("index %s unknown flags %#04x for format v0")
463 % (self.indexfile, flags >> 16))
463 % (self.indexfile, flags >> 16))
464 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
464 elif fmt == REVLOGNG and flags & ~REVLOGNGINLINEDATA:
465 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
465 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
466 % (self.indexfile, flags >> 16))
466 % (self.indexfile, flags >> 16))
467 elif fmt > REVLOGNG:
467 elif fmt > REVLOGNG:
468 raise RevlogError(_("index %s unknown format %d")
468 raise RevlogError(_("index %s unknown format %d")
469 % (self.indexfile, fmt))
469 % (self.indexfile, fmt))
470
470
471 self._io = revlogio()
471 self._io = revlogio()
472 if self.version == REVLOGV0:
472 if self.version == REVLOGV0:
473 self._io = revlogoldio()
473 self._io = revlogoldio()
474 if i:
474 if i:
475 try:
475 try:
476 d = self._io.parseindex(f, i, self._inline)
476 d = self._io.parseindex(f, i, self._inline)
477 except (ValueError, IndexError):
477 except (ValueError, IndexError):
478 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
478 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
479 self.index, self.nodemap, self._chunkcache = d
479 self.index, self.nodemap, self._chunkcache = d
480 if not self._chunkcache:
480 if not self._chunkcache:
481 self._chunkclear()
481 self._chunkclear()
482
482
483 # add the magic null revision at -1 (if it hasn't been done already)
483 # add the magic null revision at -1 (if it hasn't been done already)
484 if (self.index == [] or isinstance(self.index, lazyindex) or
484 if (self.index == [] or isinstance(self.index, lazyindex) or
485 self.index[-1][7] != nullid) :
485 self.index[-1][7] != nullid) :
486 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
486 self.index.append((0, 0, 0, -1, -1, -1, -1, nullid))
487
487
488 def _loadindex(self, start, end):
488 def _loadindex(self, start, end):
489 """load a block of indexes all at once from the lazy parser"""
489 """load a block of indexes all at once from the lazy parser"""
490 if isinstance(self.index, lazyindex):
490 if isinstance(self.index, lazyindex):
491 self.index.p.loadindex(start, end)
491 self.index.p.loadindex(start, end)
492
492
493 def _loadindexmap(self):
493 def _loadindexmap(self):
494 """loads both the map and the index from the lazy parser"""
494 """loads both the map and the index from the lazy parser"""
495 if isinstance(self.index, lazyindex):
495 if isinstance(self.index, lazyindex):
496 p = self.index.p
496 p = self.index.p
497 p.loadindex()
497 p.loadindex()
498 self.nodemap = p.map
498 self.nodemap = p.map
499
499
500 def _loadmap(self):
500 def _loadmap(self):
501 """loads the map from the lazy parser"""
501 """loads the map from the lazy parser"""
502 if isinstance(self.nodemap, lazymap):
502 if isinstance(self.nodemap, lazymap):
503 self.nodemap.p.loadmap()
503 self.nodemap.p.loadmap()
504 self.nodemap = self.nodemap.p.map
504 self.nodemap = self.nodemap.p.map
505
505
506 def tip(self):
506 def tip(self):
507 return self.node(len(self.index) - 2)
507 return self.node(len(self.index) - 2)
508 def __len__(self):
508 def __len__(self):
509 return len(self.index) - 1
509 return len(self.index) - 1
510 def __iter__(self):
510 def __iter__(self):
511 for i in xrange(len(self)):
511 for i in xrange(len(self)):
512 yield i
512 yield i
513 def rev(self, node):
513 def rev(self, node):
514 try:
514 try:
515 return self.nodemap[node]
515 return self.nodemap[node]
516 except KeyError:
516 except KeyError:
517 raise LookupError(node, self.indexfile, _('no node'))
517 raise LookupError(node, self.indexfile, _('no node'))
518 def node(self, rev):
518 def node(self, rev):
519 return self.index[rev][7]
519 return self.index[rev][7]
520 def linkrev(self, rev):
520 def linkrev(self, rev):
521 return self.index[rev][4]
521 return self.index[rev][4]
522 def parents(self, node):
522 def parents(self, node):
523 i = self.index
523 i = self.index
524 d = i[self.rev(node)]
524 d = i[self.rev(node)]
525 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
525 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
526 def parentrevs(self, rev):
526 def parentrevs(self, rev):
527 return self.index[rev][5:7]
527 return self.index[rev][5:7]
528 def start(self, rev):
528 def start(self, rev):
529 return int(self.index[rev][0] >> 16)
529 return int(self.index[rev][0] >> 16)
530 def end(self, rev):
530 def end(self, rev):
531 return self.start(rev) + self.length(rev)
531 return self.start(rev) + self.length(rev)
532 def length(self, rev):
532 def length(self, rev):
533 return self.index[rev][1]
533 return self.index[rev][1]
534 def base(self, rev):
534 def base(self, rev):
535 return self.index[rev][3]
535 return self.index[rev][3]
536
536
537 def size(self, rev):
537 def size(self, rev):
538 """return the length of the uncompressed text for a given revision"""
538 """return the length of the uncompressed text for a given revision"""
539 l = self.index[rev][2]
539 l = self.index[rev][2]
540 if l >= 0:
540 if l >= 0:
541 return l
541 return l
542
542
543 t = self.revision(self.node(rev))
543 t = self.revision(self.node(rev))
544 return len(t)
544 return len(t)
545
545
546 def reachable(self, node, stop=None):
546 def reachable(self, node, stop=None):
547 """return the set of all nodes ancestral to a given node, including
547 """return the set of all nodes ancestral to a given node, including
548 the node itself, stopping when stop is matched"""
548 the node itself, stopping when stop is matched"""
549 reachable = set((node,))
549 reachable = set((node,))
550 visit = [node]
550 visit = [node]
551 if stop:
551 if stop:
552 stopn = self.rev(stop)
552 stopn = self.rev(stop)
553 else:
553 else:
554 stopn = 0
554 stopn = 0
555 while visit:
555 while visit:
556 n = visit.pop(0)
556 n = visit.pop(0)
557 if n == stop:
557 if n == stop:
558 continue
558 continue
559 if n == nullid:
559 if n == nullid:
560 continue
560 continue
561 for p in self.parents(n):
561 for p in self.parents(n):
562 if self.rev(p) < stopn:
562 if self.rev(p) < stopn:
563 continue
563 continue
564 if p not in reachable:
564 if p not in reachable:
565 reachable.add(p)
565 reachable.add(p)
566 visit.append(p)
566 visit.append(p)
567 return reachable
567 return reachable
568
568
569 def ancestors(self, *revs):
569 def ancestors(self, *revs):
570 """Generate the ancestors of 'revs' in reverse topological order.
570 """Generate the ancestors of 'revs' in reverse topological order.
571
571
572 Yield a sequence of revision numbers starting with the parents
572 Yield a sequence of revision numbers starting with the parents
573 of each revision in revs, i.e., each revision is *not* considered
573 of each revision in revs, i.e., each revision is *not* considered
574 an ancestor of itself. Results are in breadth-first order:
574 an ancestor of itself. Results are in breadth-first order:
575 parents of each rev in revs, then parents of those, etc. Result
575 parents of each rev in revs, then parents of those, etc. Result
576 does not include the null revision."""
576 does not include the null revision."""
577 visit = list(revs)
577 visit = list(revs)
578 seen = set([nullrev])
578 seen = set([nullrev])
579 while visit:
579 while visit:
580 for parent in self.parentrevs(visit.pop(0)):
580 for parent in self.parentrevs(visit.pop(0)):
581 if parent not in seen:
581 if parent not in seen:
582 visit.append(parent)
582 visit.append(parent)
583 seen.add(parent)
583 seen.add(parent)
584 yield parent
584 yield parent
585
585
586 def descendants(self, *revs):
586 def descendants(self, *revs):
587 """Generate the descendants of 'revs' in revision order.
587 """Generate the descendants of 'revs' in revision order.
588
588
589 Yield a sequence of revision numbers starting with a child of
589 Yield a sequence of revision numbers starting with a child of
590 some rev in revs, i.e., each revision is *not* considered a
590 some rev in revs, i.e., each revision is *not* considered a
591 descendant of itself. Results are ordered by revision number (a
591 descendant of itself. Results are ordered by revision number (a
592 topological sort)."""
592 topological sort)."""
593 seen = set(revs)
593 seen = set(revs)
594 for i in xrange(min(revs) + 1, len(self)):
594 for i in xrange(min(revs) + 1, len(self)):
595 for x in self.parentrevs(i):
595 for x in self.parentrevs(i):
596 if x != nullrev and x in seen:
596 if x != nullrev and x in seen:
597 seen.add(i)
597 seen.add(i)
598 yield i
598 yield i
599 break
599 break
600
600
601 def findmissing(self, common=None, heads=None):
601 def findmissing(self, common=None, heads=None):
602 """Return the ancestors of heads that are not ancestors of common.
602 """Return the ancestors of heads that are not ancestors of common.
603
603
604 More specifically, return a list of nodes N such that every N
604 More specifically, return a list of nodes N such that every N
605 satisfies the following constraints:
605 satisfies the following constraints:
606
606
607 1. N is an ancestor of some node in 'heads'
607 1. N is an ancestor of some node in 'heads'
608 2. N is not an ancestor of any node in 'common'
608 2. N is not an ancestor of any node in 'common'
609
609
610 The list is sorted by revision number, meaning it is
610 The list is sorted by revision number, meaning it is
611 topologically sorted.
611 topologically sorted.
612
612
613 'heads' and 'common' are both lists of node IDs. If heads is
613 'heads' and 'common' are both lists of node IDs. If heads is
614 not supplied, uses all of the revlog's heads. If common is not
614 not supplied, uses all of the revlog's heads. If common is not
615 supplied, uses nullid."""
615 supplied, uses nullid."""
616 if common is None:
616 if common is None:
617 common = [nullid]
617 common = [nullid]
618 if heads is None:
618 if heads is None:
619 heads = self.heads()
619 heads = self.heads()
620
620
621 common = [self.rev(n) for n in common]
621 common = [self.rev(n) for n in common]
622 heads = [self.rev(n) for n in heads]
622 heads = [self.rev(n) for n in heads]
623
623
624 # we want the ancestors, but inclusive
624 # we want the ancestors, but inclusive
625 has = set(self.ancestors(*common))
625 has = set(self.ancestors(*common))
626 has.add(nullrev)
626 has.add(nullrev)
627 has.update(common)
627 has.update(common)
628
628
629 # take all ancestors from heads that aren't in has
629 # take all ancestors from heads that aren't in has
630 missing = set()
630 missing = set()
631 visit = [r for r in heads if r not in has]
631 visit = [r for r in heads if r not in has]
632 while visit:
632 while visit:
633 r = visit.pop(0)
633 r = visit.pop(0)
634 if r in missing:
634 if r in missing:
635 continue
635 continue
636 else:
636 else:
637 missing.add(r)
637 missing.add(r)
638 for p in self.parentrevs(r):
638 for p in self.parentrevs(r):
639 if p not in has:
639 if p not in has:
640 visit.append(p)
640 visit.append(p)
641 missing = list(missing)
641 missing = list(missing)
642 missing.sort()
642 missing.sort()
643 return [self.node(r) for r in missing]
643 return [self.node(r) for r in missing]
644
644
645 def nodesbetween(self, roots=None, heads=None):
645 def nodesbetween(self, roots=None, heads=None):
646 """Return a topological path from 'roots' to 'heads'.
646 """Return a topological path from 'roots' to 'heads'.
647
647
648 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
648 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
649 topologically sorted list of all nodes N that satisfy both of
649 topologically sorted list of all nodes N that satisfy both of
650 these constraints:
650 these constraints:
651
651
652 1. N is a descendant of some node in 'roots'
652 1. N is a descendant of some node in 'roots'
653 2. N is an ancestor of some node in 'heads'
653 2. N is an ancestor of some node in 'heads'
654
654
655 Every node is considered to be both a descendant and an ancestor
655 Every node is considered to be both a descendant and an ancestor
656 of itself, so every reachable node in 'roots' and 'heads' will be
656 of itself, so every reachable node in 'roots' and 'heads' will be
657 included in 'nodes'.
657 included in 'nodes'.
658
658
659 'outroots' is the list of reachable nodes in 'roots', i.e., the
659 'outroots' is the list of reachable nodes in 'roots', i.e., the
660 subset of 'roots' that is returned in 'nodes'. Likewise,
660 subset of 'roots' that is returned in 'nodes'. Likewise,
661 'outheads' is the subset of 'heads' that is also in 'nodes'.
661 'outheads' is the subset of 'heads' that is also in 'nodes'.
662
662
663 'roots' and 'heads' are both lists of node IDs. If 'roots' is
663 'roots' and 'heads' are both lists of node IDs. If 'roots' is
664 unspecified, uses nullid as the only root. If 'heads' is
664 unspecified, uses nullid as the only root. If 'heads' is
665 unspecified, uses list of all of the revlog's heads."""
665 unspecified, uses list of all of the revlog's heads."""
666 nonodes = ([], [], [])
666 nonodes = ([], [], [])
667 if roots is not None:
667 if roots is not None:
668 roots = list(roots)
668 roots = list(roots)
669 if not roots:
669 if not roots:
670 return nonodes
670 return nonodes
671 lowestrev = min([self.rev(n) for n in roots])
671 lowestrev = min([self.rev(n) for n in roots])
672 else:
672 else:
673 roots = [nullid] # Everybody's a descendent of nullid
673 roots = [nullid] # Everybody's a descendent of nullid
674 lowestrev = nullrev
674 lowestrev = nullrev
675 if (lowestrev == nullrev) and (heads is None):
675 if (lowestrev == nullrev) and (heads is None):
676 # We want _all_ the nodes!
676 # We want _all_ the nodes!
677 return ([self.node(r) for r in self], [nullid], list(self.heads()))
677 return ([self.node(r) for r in self], [nullid], list(self.heads()))
678 if heads is None:
678 if heads is None:
679 # All nodes are ancestors, so the latest ancestor is the last
679 # All nodes are ancestors, so the latest ancestor is the last
680 # node.
680 # node.
681 highestrev = len(self) - 1
681 highestrev = len(self) - 1
682 # Set ancestors to None to signal that every node is an ancestor.
682 # Set ancestors to None to signal that every node is an ancestor.
683 ancestors = None
683 ancestors = None
684 # Set heads to an empty dictionary for later discovery of heads
684 # Set heads to an empty dictionary for later discovery of heads
685 heads = {}
685 heads = {}
686 else:
686 else:
687 heads = list(heads)
687 heads = list(heads)
688 if not heads:
688 if not heads:
689 return nonodes
689 return nonodes
690 ancestors = set()
690 ancestors = set()
691 # Turn heads into a dictionary so we can remove 'fake' heads.
691 # Turn heads into a dictionary so we can remove 'fake' heads.
692 # Also, later we will be using it to filter out the heads we can't
692 # Also, later we will be using it to filter out the heads we can't
693 # find from roots.
693 # find from roots.
694 heads = dict.fromkeys(heads, 0)
694 heads = dict.fromkeys(heads, 0)
695 # Start at the top and keep marking parents until we're done.
695 # Start at the top and keep marking parents until we're done.
696 nodestotag = set(heads)
696 nodestotag = set(heads)
697 # Remember where the top was so we can use it as a limit later.
697 # Remember where the top was so we can use it as a limit later.
698 highestrev = max([self.rev(n) for n in nodestotag])
698 highestrev = max([self.rev(n) for n in nodestotag])
699 while nodestotag:
699 while nodestotag:
700 # grab a node to tag
700 # grab a node to tag
701 n = nodestotag.pop()
701 n = nodestotag.pop()
702 # Never tag nullid
702 # Never tag nullid
703 if n == nullid:
703 if n == nullid:
704 continue
704 continue
705 # A node's revision number represents its place in a
705 # A node's revision number represents its place in a
706 # topologically sorted list of nodes.
706 # topologically sorted list of nodes.
707 r = self.rev(n)
707 r = self.rev(n)
708 if r >= lowestrev:
708 if r >= lowestrev:
709 if n not in ancestors:
709 if n not in ancestors:
710 # If we are possibly a descendent of one of the roots
710 # If we are possibly a descendent of one of the roots
711 # and we haven't already been marked as an ancestor
711 # and we haven't already been marked as an ancestor
712 ancestors.add(n) # Mark as ancestor
712 ancestors.add(n) # Mark as ancestor
713 # Add non-nullid parents to list of nodes to tag.
713 # Add non-nullid parents to list of nodes to tag.
714 nodestotag.update([p for p in self.parents(n) if
714 nodestotag.update([p for p in self.parents(n) if
715 p != nullid])
715 p != nullid])
716 elif n in heads: # We've seen it before, is it a fake head?
716 elif n in heads: # We've seen it before, is it a fake head?
717 # So it is, real heads should not be the ancestors of
717 # So it is, real heads should not be the ancestors of
718 # any other heads.
718 # any other heads.
719 heads.pop(n)
719 heads.pop(n)
720 if not ancestors:
720 if not ancestors:
721 return nonodes
721 return nonodes
722 # Now that we have our set of ancestors, we want to remove any
722 # Now that we have our set of ancestors, we want to remove any
723 # roots that are not ancestors.
723 # roots that are not ancestors.
724
724
725 # If one of the roots was nullid, everything is included anyway.
725 # If one of the roots was nullid, everything is included anyway.
726 if lowestrev > nullrev:
726 if lowestrev > nullrev:
727 # But, since we weren't, let's recompute the lowest rev to not
727 # But, since we weren't, let's recompute the lowest rev to not
728 # include roots that aren't ancestors.
728 # include roots that aren't ancestors.
729
729
730 # Filter out roots that aren't ancestors of heads
730 # Filter out roots that aren't ancestors of heads
731 roots = [n for n in roots if n in ancestors]
731 roots = [n for n in roots if n in ancestors]
732 # Recompute the lowest revision
732 # Recompute the lowest revision
733 if roots:
733 if roots:
734 lowestrev = min([self.rev(n) for n in roots])
734 lowestrev = min([self.rev(n) for n in roots])
735 else:
735 else:
736 # No more roots? Return empty list
736 # No more roots? Return empty list
737 return nonodes
737 return nonodes
738 else:
738 else:
739 # We are descending from nullid, and don't need to care about
739 # We are descending from nullid, and don't need to care about
740 # any other roots.
740 # any other roots.
741 lowestrev = nullrev
741 lowestrev = nullrev
742 roots = [nullid]
742 roots = [nullid]
743 # Transform our roots list into a set.
743 # Transform our roots list into a set.
744 descendents = set(roots)
744 descendents = set(roots)
745 # Also, keep the original roots so we can filter out roots that aren't
745 # Also, keep the original roots so we can filter out roots that aren't
746 # 'real' roots (i.e. are descended from other roots).
746 # 'real' roots (i.e. are descended from other roots).
747 roots = descendents.copy()
747 roots = descendents.copy()
748 # Our topologically sorted list of output nodes.
748 # Our topologically sorted list of output nodes.
749 orderedout = []
749 orderedout = []
750 # Don't start at nullid since we don't want nullid in our output list,
750 # Don't start at nullid since we don't want nullid in our output list,
751 # and if nullid shows up in descedents, empty parents will look like
751 # and if nullid shows up in descedents, empty parents will look like
752 # they're descendents.
752 # they're descendents.
753 for r in xrange(max(lowestrev, 0), highestrev + 1):
753 for r in xrange(max(lowestrev, 0), highestrev + 1):
754 n = self.node(r)
754 n = self.node(r)
755 isdescendent = False
755 isdescendent = False
756 if lowestrev == nullrev: # Everybody is a descendent of nullid
756 if lowestrev == nullrev: # Everybody is a descendent of nullid
757 isdescendent = True
757 isdescendent = True
758 elif n in descendents:
758 elif n in descendents:
759 # n is already a descendent
759 # n is already a descendent
760 isdescendent = True
760 isdescendent = True
761 # This check only needs to be done here because all the roots
761 # This check only needs to be done here because all the roots
762 # will start being marked is descendents before the loop.
762 # will start being marked is descendents before the loop.
763 if n in roots:
763 if n in roots:
764 # If n was a root, check if it's a 'real' root.
764 # If n was a root, check if it's a 'real' root.
765 p = tuple(self.parents(n))
765 p = tuple(self.parents(n))
766 # If any of its parents are descendents, it's not a root.
766 # If any of its parents are descendents, it's not a root.
767 if (p[0] in descendents) or (p[1] in descendents):
767 if (p[0] in descendents) or (p[1] in descendents):
768 roots.remove(n)
768 roots.remove(n)
769 else:
769 else:
770 p = tuple(self.parents(n))
770 p = tuple(self.parents(n))
771 # A node is a descendent if either of its parents are
771 # A node is a descendent if either of its parents are
772 # descendents. (We seeded the dependents list with the roots
772 # descendents. (We seeded the dependents list with the roots
773 # up there, remember?)
773 # up there, remember?)
774 if (p[0] in descendents) or (p[1] in descendents):
774 if (p[0] in descendents) or (p[1] in descendents):
775 descendents.add(n)
775 descendents.add(n)
776 isdescendent = True
776 isdescendent = True
777 if isdescendent and ((ancestors is None) or (n in ancestors)):
777 if isdescendent and ((ancestors is None) or (n in ancestors)):
778 # Only include nodes that are both descendents and ancestors.
778 # Only include nodes that are both descendents and ancestors.
779 orderedout.append(n)
779 orderedout.append(n)
780 if (ancestors is not None) and (n in heads):
780 if (ancestors is not None) and (n in heads):
781 # We're trying to figure out which heads are reachable
781 # We're trying to figure out which heads are reachable
782 # from roots.
782 # from roots.
783 # Mark this head as having been reached
783 # Mark this head as having been reached
784 heads[n] = 1
784 heads[n] = 1
785 elif ancestors is None:
785 elif ancestors is None:
786 # Otherwise, we're trying to discover the heads.
786 # Otherwise, we're trying to discover the heads.
787 # Assume this is a head because if it isn't, the next step
787 # Assume this is a head because if it isn't, the next step
788 # will eventually remove it.
788 # will eventually remove it.
789 heads[n] = 1
789 heads[n] = 1
790 # But, obviously its parents aren't.
790 # But, obviously its parents aren't.
791 for p in self.parents(n):
791 for p in self.parents(n):
792 heads.pop(p, None)
792 heads.pop(p, None)
793 heads = [n for n in heads.iterkeys() if heads[n] != 0]
793 heads = [n for n in heads.iterkeys() if heads[n] != 0]
794 roots = list(roots)
794 roots = list(roots)
795 assert orderedout
795 assert orderedout
796 assert roots
796 assert roots
797 assert heads
797 assert heads
798 return (orderedout, roots, heads)
798 return (orderedout, roots, heads)
799
799
800 def heads(self, start=None, stop=None):
800 def heads(self, start=None, stop=None):
801 """return the list of all nodes that have no children
801 """return the list of all nodes that have no children
802
802
803 if start is specified, only heads that are descendants of
803 if start is specified, only heads that are descendants of
804 start will be returned
804 start will be returned
805 if stop is specified, it will consider all the revs from stop
805 if stop is specified, it will consider all the revs from stop
806 as if they had no children
806 as if they had no children
807 """
807 """
808 if start is None and stop is None:
808 if start is None and stop is None:
809 count = len(self)
809 count = len(self)
810 if not count:
810 if not count:
811 return [nullid]
811 return [nullid]
812 ishead = [1] * (count + 1)
812 ishead = [1] * (count + 1)
813 index = self.index
813 index = self.index
814 for r in xrange(count):
814 for r in xrange(count):
815 e = index[r]
815 e = index[r]
816 ishead[e[5]] = ishead[e[6]] = 0
816 ishead[e[5]] = ishead[e[6]] = 0
817 return [self.node(r) for r in xrange(count) if ishead[r]]
817 return [self.node(r) for r in xrange(count) if ishead[r]]
818
818
819 if start is None:
819 if start is None:
820 start = nullid
820 start = nullid
821 if stop is None:
821 if stop is None:
822 stop = []
822 stop = []
823 stoprevs = set([self.rev(n) for n in stop])
823 stoprevs = set([self.rev(n) for n in stop])
824 startrev = self.rev(start)
824 startrev = self.rev(start)
825 reachable = set((startrev,))
825 reachable = set((startrev,))
826 heads = set((startrev,))
826 heads = set((startrev,))
827
827
828 parentrevs = self.parentrevs
828 parentrevs = self.parentrevs
829 for r in xrange(startrev + 1, len(self)):
829 for r in xrange(startrev + 1, len(self)):
830 for p in parentrevs(r):
830 for p in parentrevs(r):
831 if p in reachable:
831 if p in reachable:
832 if r not in stoprevs:
832 if r not in stoprevs:
833 reachable.add(r)
833 reachable.add(r)
834 heads.add(r)
834 heads.add(r)
835 if p in heads and p not in stoprevs:
835 if p in heads and p not in stoprevs:
836 heads.remove(p)
836 heads.remove(p)
837
837
838 return [self.node(r) for r in heads]
838 return [self.node(r) for r in heads]
839
839
840 def children(self, node):
840 def children(self, node):
841 """find the children of a given node"""
841 """find the children of a given node"""
842 c = []
842 c = []
843 p = self.rev(node)
843 p = self.rev(node)
844 for r in range(p + 1, len(self)):
844 for r in range(p + 1, len(self)):
845 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
845 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
846 if prevs:
846 if prevs:
847 for pr in prevs:
847 for pr in prevs:
848 if pr == p:
848 if pr == p:
849 c.append(self.node(r))
849 c.append(self.node(r))
850 elif p == nullrev:
850 elif p == nullrev:
851 c.append(self.node(r))
851 c.append(self.node(r))
852 return c
852 return c
853
853
854 def descendant(self, start, end):
854 def descendant(self, start, end):
855 for i in self.descendants(start):
855 for i in self.descendants(start):
856 if i == end:
856 if i == end:
857 return True
857 return True
858 elif i > end:
858 elif i > end:
859 break
859 break
860 return False
860 return False
861
861
862 def ancestor(self, a, b):
862 def ancestor(self, a, b):
863 """calculate the least common ancestor of nodes a and b"""
863 """calculate the least common ancestor of nodes a and b"""
864
864
865 # fast path, check if it is a descendant
865 # fast path, check if it is a descendant
866 a, b = self.rev(a), self.rev(b)
866 a, b = self.rev(a), self.rev(b)
867 start, end = sorted((a, b))
867 start, end = sorted((a, b))
868 if self.descendant(start, end):
868 if self.descendant(start, end):
869 return self.node(start)
869 return self.node(start)
870
870
871 def parents(rev):
871 def parents(rev):
872 return [p for p in self.parentrevs(rev) if p != nullrev]
872 return [p for p in self.parentrevs(rev) if p != nullrev]
873
873
874 c = ancestor.ancestor(a, b, parents)
874 c = ancestor.ancestor(a, b, parents)
875 if c is None:
875 if c is None:
876 return nullid
876 return nullid
877
877
878 return self.node(c)
878 return self.node(c)
879
879
880 def _match(self, id):
880 def _match(self, id):
881 if isinstance(id, (long, int)):
881 if isinstance(id, (long, int)):
882 # rev
882 # rev
883 return self.node(id)
883 return self.node(id)
884 if len(id) == 20:
884 if len(id) == 20:
885 # possibly a binary node
885 # possibly a binary node
886 # odds of a binary node being all hex in ASCII are 1 in 10**25
886 # odds of a binary node being all hex in ASCII are 1 in 10**25
887 try:
887 try:
888 node = id
888 node = id
889 self.rev(node) # quick search the index
889 self.rev(node) # quick search the index
890 return node
890 return node
891 except LookupError:
891 except LookupError:
892 pass # may be partial hex id
892 pass # may be partial hex id
893 try:
893 try:
894 # str(rev)
894 # str(rev)
895 rev = int(id)
895 rev = int(id)
896 if str(rev) != id:
896 if str(rev) != id:
897 raise ValueError
897 raise ValueError
898 if rev < 0:
898 if rev < 0:
899 rev = len(self) + rev
899 rev = len(self) + rev
900 if rev < 0 or rev >= len(self):
900 if rev < 0 or rev >= len(self):
901 raise ValueError
901 raise ValueError
902 return self.node(rev)
902 return self.node(rev)
903 except (ValueError, OverflowError):
903 except (ValueError, OverflowError):
904 pass
904 pass
905 if len(id) == 40:
905 if len(id) == 40:
906 try:
906 try:
907 # a full hex nodeid?
907 # a full hex nodeid?
908 node = bin(id)
908 node = bin(id)
909 self.rev(node)
909 self.rev(node)
910 return node
910 return node
911 except (TypeError, LookupError):
911 except (TypeError, LookupError):
912 pass
912 pass
913
913
914 def _partialmatch(self, id):
914 def _partialmatch(self, id):
915 if len(id) < 40:
915 if len(id) < 40:
916 try:
916 try:
917 # hex(node)[:...]
917 # hex(node)[:...]
918 l = len(id) // 2 # grab an even number of digits
918 l = len(id) // 2 # grab an even number of digits
919 bin_id = bin(id[:l * 2])
919 bin_id = bin(id[:l * 2])
920 nl = [n for n in self.nodemap if n[:l] == bin_id]
920 nl = [n for n in self.nodemap if n[:l] == bin_id]
921 nl = [n for n in nl if hex(n).startswith(id)]
921 nl = [n for n in nl if hex(n).startswith(id)]
922 if len(nl) > 0:
922 if len(nl) > 0:
923 if len(nl) == 1:
923 if len(nl) == 1:
924 return nl[0]
924 return nl[0]
925 raise LookupError(id, self.indexfile,
925 raise LookupError(id, self.indexfile,
926 _('ambiguous identifier'))
926 _('ambiguous identifier'))
927 return None
927 return None
928 except TypeError:
928 except TypeError:
929 pass
929 pass
930
930
931 def lookup(self, id):
931 def lookup(self, id):
932 """locate a node based on:
932 """locate a node based on:
933 - revision number or str(revision number)
933 - revision number or str(revision number)
934 - nodeid or subset of hex nodeid
934 - nodeid or subset of hex nodeid
935 """
935 """
936 n = self._match(id)
936 n = self._match(id)
937 if n is not None:
937 if n is not None:
938 return n
938 return n
939 n = self._partialmatch(id)
939 n = self._partialmatch(id)
940 if n:
940 if n:
941 return n
941 return n
942
942
943 raise LookupError(id, self.indexfile, _('no match found'))
943 raise LookupError(id, self.indexfile, _('no match found'))
944
944
945 def cmp(self, node, text):
945 def cmp(self, node, text):
946 """compare text with a given file revision"""
946 """compare text with a given file revision
947
948 returns True if text is different than what is stored.
949 """
947 p1, p2 = self.parents(node)
950 p1, p2 = self.parents(node)
948 return hash(text, p1, p2) != node
951 return hash(text, p1, p2) != node
949
952
950 def _addchunk(self, offset, data):
953 def _addchunk(self, offset, data):
951 o, d = self._chunkcache
954 o, d = self._chunkcache
952 # try to add to existing cache
955 # try to add to existing cache
953 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
956 if o + len(d) == offset and len(d) + len(data) < _prereadsize:
954 self._chunkcache = o, d + data
957 self._chunkcache = o, d + data
955 else:
958 else:
956 self._chunkcache = offset, data
959 self._chunkcache = offset, data
957
960
958 def _loadchunk(self, offset, length):
961 def _loadchunk(self, offset, length):
959 if self._inline:
962 if self._inline:
960 df = self.opener(self.indexfile)
963 df = self.opener(self.indexfile)
961 else:
964 else:
962 df = self.opener(self.datafile)
965 df = self.opener(self.datafile)
963
966
964 readahead = max(65536, length)
967 readahead = max(65536, length)
965 df.seek(offset)
968 df.seek(offset)
966 d = df.read(readahead)
969 d = df.read(readahead)
967 self._addchunk(offset, d)
970 self._addchunk(offset, d)
968 if readahead > length:
971 if readahead > length:
969 return d[:length]
972 return d[:length]
970 return d
973 return d
971
974
972 def _getchunk(self, offset, length):
975 def _getchunk(self, offset, length):
973 o, d = self._chunkcache
976 o, d = self._chunkcache
974 l = len(d)
977 l = len(d)
975
978
976 # is it in the cache?
979 # is it in the cache?
977 cachestart = offset - o
980 cachestart = offset - o
978 cacheend = cachestart + length
981 cacheend = cachestart + length
979 if cachestart >= 0 and cacheend <= l:
982 if cachestart >= 0 and cacheend <= l:
980 if cachestart == 0 and cacheend == l:
983 if cachestart == 0 and cacheend == l:
981 return d # avoid a copy
984 return d # avoid a copy
982 return d[cachestart:cacheend]
985 return d[cachestart:cacheend]
983
986
984 return self._loadchunk(offset, length)
987 return self._loadchunk(offset, length)
985
988
986 def _chunkraw(self, startrev, endrev):
989 def _chunkraw(self, startrev, endrev):
987 start = self.start(startrev)
990 start = self.start(startrev)
988 length = self.end(endrev) - start
991 length = self.end(endrev) - start
989 if self._inline:
992 if self._inline:
990 start += (startrev + 1) * self._io.size
993 start += (startrev + 1) * self._io.size
991 return self._getchunk(start, length)
994 return self._getchunk(start, length)
992
995
993 def _chunk(self, rev):
996 def _chunk(self, rev):
994 return decompress(self._chunkraw(rev, rev))
997 return decompress(self._chunkraw(rev, rev))
995
998
996 def _chunkclear(self):
999 def _chunkclear(self):
997 self._chunkcache = (0, '')
1000 self._chunkcache = (0, '')
998
1001
999 def revdiff(self, rev1, rev2):
1002 def revdiff(self, rev1, rev2):
1000 """return or calculate a delta between two revisions"""
1003 """return or calculate a delta between two revisions"""
1001 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
1004 if rev1 + 1 == rev2 and self.base(rev1) == self.base(rev2):
1002 return self._chunk(rev2)
1005 return self._chunk(rev2)
1003
1006
1004 return mdiff.textdiff(self.revision(self.node(rev1)),
1007 return mdiff.textdiff(self.revision(self.node(rev1)),
1005 self.revision(self.node(rev2)))
1008 self.revision(self.node(rev2)))
1006
1009
1007 def revision(self, node):
1010 def revision(self, node):
1008 """return an uncompressed revision of a given node"""
1011 """return an uncompressed revision of a given node"""
1009 if node == nullid:
1012 if node == nullid:
1010 return ""
1013 return ""
1011 if self._cache and self._cache[0] == node:
1014 if self._cache and self._cache[0] == node:
1012 return self._cache[2]
1015 return self._cache[2]
1013
1016
1014 # look up what we need to read
1017 # look up what we need to read
1015 text = None
1018 text = None
1016 rev = self.rev(node)
1019 rev = self.rev(node)
1017 base = self.base(rev)
1020 base = self.base(rev)
1018
1021
1019 # check rev flags
1022 # check rev flags
1020 if self.index[rev][0] & 0xFFFF:
1023 if self.index[rev][0] & 0xFFFF:
1021 raise RevlogError(_('incompatible revision flag %x') %
1024 raise RevlogError(_('incompatible revision flag %x') %
1022 (self.index[rev][0] & 0xFFFF))
1025 (self.index[rev][0] & 0xFFFF))
1023
1026
1024 # do we have useful data cached?
1027 # do we have useful data cached?
1025 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
1028 if self._cache and self._cache[1] >= base and self._cache[1] < rev:
1026 base = self._cache[1]
1029 base = self._cache[1]
1027 text = self._cache[2]
1030 text = self._cache[2]
1028
1031
1029 self._loadindex(base, rev + 1)
1032 self._loadindex(base, rev + 1)
1030 self._chunkraw(base, rev)
1033 self._chunkraw(base, rev)
1031 if text is None:
1034 if text is None:
1032 text = self._chunk(base)
1035 text = self._chunk(base)
1033
1036
1034 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
1037 bins = [self._chunk(r) for r in xrange(base + 1, rev + 1)]
1035 text = mdiff.patches(text, bins)
1038 text = mdiff.patches(text, bins)
1036 p1, p2 = self.parents(node)
1039 p1, p2 = self.parents(node)
1037 if node != hash(text, p1, p2):
1040 if node != hash(text, p1, p2):
1038 raise RevlogError(_("integrity check failed on %s:%d")
1041 raise RevlogError(_("integrity check failed on %s:%d")
1039 % (self.indexfile, rev))
1042 % (self.indexfile, rev))
1040
1043
1041 self._cache = (node, rev, text)
1044 self._cache = (node, rev, text)
1042 return text
1045 return text
1043
1046
1044 def checkinlinesize(self, tr, fp=None):
1047 def checkinlinesize(self, tr, fp=None):
1045 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1048 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1046 return
1049 return
1047
1050
1048 trinfo = tr.find(self.indexfile)
1051 trinfo = tr.find(self.indexfile)
1049 if trinfo is None:
1052 if trinfo is None:
1050 raise RevlogError(_("%s not found in the transaction")
1053 raise RevlogError(_("%s not found in the transaction")
1051 % self.indexfile)
1054 % self.indexfile)
1052
1055
1053 trindex = trinfo[2]
1056 trindex = trinfo[2]
1054 dataoff = self.start(trindex)
1057 dataoff = self.start(trindex)
1055
1058
1056 tr.add(self.datafile, dataoff)
1059 tr.add(self.datafile, dataoff)
1057
1060
1058 if fp:
1061 if fp:
1059 fp.flush()
1062 fp.flush()
1060 fp.close()
1063 fp.close()
1061
1064
1062 df = self.opener(self.datafile, 'w')
1065 df = self.opener(self.datafile, 'w')
1063 try:
1066 try:
1064 for r in self:
1067 for r in self:
1065 df.write(self._chunkraw(r, r))
1068 df.write(self._chunkraw(r, r))
1066 finally:
1069 finally:
1067 df.close()
1070 df.close()
1068
1071
1069 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1072 fp = self.opener(self.indexfile, 'w', atomictemp=True)
1070 self.version &= ~(REVLOGNGINLINEDATA)
1073 self.version &= ~(REVLOGNGINLINEDATA)
1071 self._inline = False
1074 self._inline = False
1072 for i in self:
1075 for i in self:
1073 e = self._io.packentry(self.index[i], self.node, self.version, i)
1076 e = self._io.packentry(self.index[i], self.node, self.version, i)
1074 fp.write(e)
1077 fp.write(e)
1075
1078
1076 # if we don't call rename, the temp file will never replace the
1079 # if we don't call rename, the temp file will never replace the
1077 # real index
1080 # real index
1078 fp.rename()
1081 fp.rename()
1079
1082
1080 tr.replace(self.indexfile, trindex * self._io.size)
1083 tr.replace(self.indexfile, trindex * self._io.size)
1081 self._chunkclear()
1084 self._chunkclear()
1082
1085
1083 def addrevision(self, text, transaction, link, p1, p2, d=None):
1086 def addrevision(self, text, transaction, link, p1, p2, d=None):
1084 """add a revision to the log
1087 """add a revision to the log
1085
1088
1086 text - the revision data to add
1089 text - the revision data to add
1087 transaction - the transaction object used for rollback
1090 transaction - the transaction object used for rollback
1088 link - the linkrev data to add
1091 link - the linkrev data to add
1089 p1, p2 - the parent nodeids of the revision
1092 p1, p2 - the parent nodeids of the revision
1090 d - an optional precomputed delta
1093 d - an optional precomputed delta
1091 """
1094 """
1092 dfh = None
1095 dfh = None
1093 if not self._inline:
1096 if not self._inline:
1094 dfh = self.opener(self.datafile, "a")
1097 dfh = self.opener(self.datafile, "a")
1095 ifh = self.opener(self.indexfile, "a+")
1098 ifh = self.opener(self.indexfile, "a+")
1096 try:
1099 try:
1097 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1100 return self._addrevision(text, transaction, link, p1, p2, d, ifh, dfh)
1098 finally:
1101 finally:
1099 if dfh:
1102 if dfh:
1100 dfh.close()
1103 dfh.close()
1101 ifh.close()
1104 ifh.close()
1102
1105
1103 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1106 def _addrevision(self, text, transaction, link, p1, p2, d, ifh, dfh):
1104 node = hash(text, p1, p2)
1107 node = hash(text, p1, p2)
1105 if node in self.nodemap:
1108 if node in self.nodemap:
1106 return node
1109 return node
1107
1110
1108 curr = len(self)
1111 curr = len(self)
1109 prev = curr - 1
1112 prev = curr - 1
1110 base = self.base(prev)
1113 base = self.base(prev)
1111 offset = self.end(prev)
1114 offset = self.end(prev)
1112
1115
1113 if curr:
1116 if curr:
1114 if not d:
1117 if not d:
1115 ptext = self.revision(self.node(prev))
1118 ptext = self.revision(self.node(prev))
1116 d = mdiff.textdiff(ptext, text)
1119 d = mdiff.textdiff(ptext, text)
1117 data = compress(d)
1120 data = compress(d)
1118 l = len(data[1]) + len(data[0])
1121 l = len(data[1]) + len(data[0])
1119 dist = l + offset - self.start(base)
1122 dist = l + offset - self.start(base)
1120
1123
1121 # full versions are inserted when the needed deltas
1124 # full versions are inserted when the needed deltas
1122 # become comparable to the uncompressed text
1125 # become comparable to the uncompressed text
1123 if not curr or dist > len(text) * 2:
1126 if not curr or dist > len(text) * 2:
1124 data = compress(text)
1127 data = compress(text)
1125 l = len(data[1]) + len(data[0])
1128 l = len(data[1]) + len(data[0])
1126 base = curr
1129 base = curr
1127
1130
1128 e = (offset_type(offset, 0), l, len(text),
1131 e = (offset_type(offset, 0), l, len(text),
1129 base, link, self.rev(p1), self.rev(p2), node)
1132 base, link, self.rev(p1), self.rev(p2), node)
1130 self.index.insert(-1, e)
1133 self.index.insert(-1, e)
1131 self.nodemap[node] = curr
1134 self.nodemap[node] = curr
1132
1135
1133 entry = self._io.packentry(e, self.node, self.version, curr)
1136 entry = self._io.packentry(e, self.node, self.version, curr)
1134 if not self._inline:
1137 if not self._inline:
1135 transaction.add(self.datafile, offset)
1138 transaction.add(self.datafile, offset)
1136 transaction.add(self.indexfile, curr * len(entry))
1139 transaction.add(self.indexfile, curr * len(entry))
1137 if data[0]:
1140 if data[0]:
1138 dfh.write(data[0])
1141 dfh.write(data[0])
1139 dfh.write(data[1])
1142 dfh.write(data[1])
1140 dfh.flush()
1143 dfh.flush()
1141 ifh.write(entry)
1144 ifh.write(entry)
1142 else:
1145 else:
1143 offset += curr * self._io.size
1146 offset += curr * self._io.size
1144 transaction.add(self.indexfile, offset, curr)
1147 transaction.add(self.indexfile, offset, curr)
1145 ifh.write(entry)
1148 ifh.write(entry)
1146 ifh.write(data[0])
1149 ifh.write(data[0])
1147 ifh.write(data[1])
1150 ifh.write(data[1])
1148 self.checkinlinesize(transaction, ifh)
1151 self.checkinlinesize(transaction, ifh)
1149
1152
1150 if type(text) == str: # only accept immutable objects
1153 if type(text) == str: # only accept immutable objects
1151 self._cache = (node, curr, text)
1154 self._cache = (node, curr, text)
1152 return node
1155 return node
1153
1156
1154 def group(self, nodelist, lookup, infocollect=None):
1157 def group(self, nodelist, lookup, infocollect=None):
1155 """Calculate a delta group, yielding a sequence of changegroup chunks
1158 """Calculate a delta group, yielding a sequence of changegroup chunks
1156 (strings).
1159 (strings).
1157
1160
1158 Given a list of changeset revs, return a set of deltas and
1161 Given a list of changeset revs, return a set of deltas and
1159 metadata corresponding to nodes. the first delta is
1162 metadata corresponding to nodes. the first delta is
1160 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1163 parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
1161 have this parent as it has all history before these
1164 have this parent as it has all history before these
1162 changesets. parent is parent[0]
1165 changesets. parent is parent[0]
1163 """
1166 """
1164
1167
1165 revs = [self.rev(n) for n in nodelist]
1168 revs = [self.rev(n) for n in nodelist]
1166
1169
1167 # if we don't have any revisions touched by these changesets, bail
1170 # if we don't have any revisions touched by these changesets, bail
1168 if not revs:
1171 if not revs:
1169 yield changegroup.closechunk()
1172 yield changegroup.closechunk()
1170 return
1173 return
1171
1174
1172 # add the parent of the first rev
1175 # add the parent of the first rev
1173 p = self.parentrevs(revs[0])[0]
1176 p = self.parentrevs(revs[0])[0]
1174 revs.insert(0, p)
1177 revs.insert(0, p)
1175
1178
1176 # build deltas
1179 # build deltas
1177 for d in xrange(len(revs) - 1):
1180 for d in xrange(len(revs) - 1):
1178 a, b = revs[d], revs[d + 1]
1181 a, b = revs[d], revs[d + 1]
1179 nb = self.node(b)
1182 nb = self.node(b)
1180
1183
1181 if infocollect is not None:
1184 if infocollect is not None:
1182 infocollect(nb)
1185 infocollect(nb)
1183
1186
1184 p = self.parents(nb)
1187 p = self.parents(nb)
1185 meta = nb + p[0] + p[1] + lookup(nb)
1188 meta = nb + p[0] + p[1] + lookup(nb)
1186 if a == -1:
1189 if a == -1:
1187 d = self.revision(nb)
1190 d = self.revision(nb)
1188 meta += mdiff.trivialdiffheader(len(d))
1191 meta += mdiff.trivialdiffheader(len(d))
1189 else:
1192 else:
1190 d = self.revdiff(a, b)
1193 d = self.revdiff(a, b)
1191 yield changegroup.chunkheader(len(meta) + len(d))
1194 yield changegroup.chunkheader(len(meta) + len(d))
1192 yield meta
1195 yield meta
1193 if len(d) > 2**20:
1196 if len(d) > 2**20:
1194 pos = 0
1197 pos = 0
1195 while pos < len(d):
1198 while pos < len(d):
1196 pos2 = pos + 2 ** 18
1199 pos2 = pos + 2 ** 18
1197 yield d[pos:pos2]
1200 yield d[pos:pos2]
1198 pos = pos2
1201 pos = pos2
1199 else:
1202 else:
1200 yield d
1203 yield d
1201
1204
1202 yield changegroup.closechunk()
1205 yield changegroup.closechunk()
1203
1206
1204 def addgroup(self, revs, linkmapper, transaction):
1207 def addgroup(self, revs, linkmapper, transaction):
1205 """
1208 """
1206 add a delta group
1209 add a delta group
1207
1210
1208 given a set of deltas, add them to the revision log. the
1211 given a set of deltas, add them to the revision log. the
1209 first delta is against its parent, which should be in our
1212 first delta is against its parent, which should be in our
1210 log, the rest are against the previous delta.
1213 log, the rest are against the previous delta.
1211 """
1214 """
1212
1215
1213 #track the base of the current delta log
1216 #track the base of the current delta log
1214 r = len(self)
1217 r = len(self)
1215 t = r - 1
1218 t = r - 1
1216 node = None
1219 node = None
1217
1220
1218 base = prev = nullrev
1221 base = prev = nullrev
1219 start = end = textlen = 0
1222 start = end = textlen = 0
1220 if r:
1223 if r:
1221 end = self.end(t)
1224 end = self.end(t)
1222
1225
1223 ifh = self.opener(self.indexfile, "a+")
1226 ifh = self.opener(self.indexfile, "a+")
1224 isize = r * self._io.size
1227 isize = r * self._io.size
1225 if self._inline:
1228 if self._inline:
1226 transaction.add(self.indexfile, end + isize, r)
1229 transaction.add(self.indexfile, end + isize, r)
1227 dfh = None
1230 dfh = None
1228 else:
1231 else:
1229 transaction.add(self.indexfile, isize, r)
1232 transaction.add(self.indexfile, isize, r)
1230 transaction.add(self.datafile, end)
1233 transaction.add(self.datafile, end)
1231 dfh = self.opener(self.datafile, "a")
1234 dfh = self.opener(self.datafile, "a")
1232
1235
1233 try:
1236 try:
1234 # loop through our set of deltas
1237 # loop through our set of deltas
1235 chain = None
1238 chain = None
1236 for chunk in revs:
1239 for chunk in revs:
1237 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1240 node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80])
1238 link = linkmapper(cs)
1241 link = linkmapper(cs)
1239 if node in self.nodemap:
1242 if node in self.nodemap:
1240 # this can happen if two branches make the same change
1243 # this can happen if two branches make the same change
1241 chain = node
1244 chain = node
1242 continue
1245 continue
1243 delta = buffer(chunk, 80)
1246 delta = buffer(chunk, 80)
1244 del chunk
1247 del chunk
1245
1248
1246 for p in (p1, p2):
1249 for p in (p1, p2):
1247 if not p in self.nodemap:
1250 if not p in self.nodemap:
1248 raise LookupError(p, self.indexfile, _('unknown parent'))
1251 raise LookupError(p, self.indexfile, _('unknown parent'))
1249
1252
1250 if not chain:
1253 if not chain:
1251 # retrieve the parent revision of the delta chain
1254 # retrieve the parent revision of the delta chain
1252 chain = p1
1255 chain = p1
1253 if not chain in self.nodemap:
1256 if not chain in self.nodemap:
1254 raise LookupError(chain, self.indexfile, _('unknown base'))
1257 raise LookupError(chain, self.indexfile, _('unknown base'))
1255
1258
1256 # full versions are inserted when the needed deltas become
1259 # full versions are inserted when the needed deltas become
1257 # comparable to the uncompressed text or when the previous
1260 # comparable to the uncompressed text or when the previous
1258 # version is not the one we have a delta against. We use
1261 # version is not the one we have a delta against. We use
1259 # the size of the previous full rev as a proxy for the
1262 # the size of the previous full rev as a proxy for the
1260 # current size.
1263 # current size.
1261
1264
1262 if chain == prev:
1265 if chain == prev:
1263 cdelta = compress(delta)
1266 cdelta = compress(delta)
1264 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1267 cdeltalen = len(cdelta[0]) + len(cdelta[1])
1265 textlen = mdiff.patchedsize(textlen, delta)
1268 textlen = mdiff.patchedsize(textlen, delta)
1266
1269
1267 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1270 if chain != prev or (end - start + cdeltalen) > textlen * 2:
1268 # flush our writes here so we can read it in revision
1271 # flush our writes here so we can read it in revision
1269 if dfh:
1272 if dfh:
1270 dfh.flush()
1273 dfh.flush()
1271 ifh.flush()
1274 ifh.flush()
1272 text = self.revision(chain)
1275 text = self.revision(chain)
1273 if len(text) == 0:
1276 if len(text) == 0:
1274 # skip over trivial delta header
1277 # skip over trivial delta header
1275 text = buffer(delta, 12)
1278 text = buffer(delta, 12)
1276 else:
1279 else:
1277 text = mdiff.patches(text, [delta])
1280 text = mdiff.patches(text, [delta])
1278 del delta
1281 del delta
1279 chk = self._addrevision(text, transaction, link, p1, p2, None,
1282 chk = self._addrevision(text, transaction, link, p1, p2, None,
1280 ifh, dfh)
1283 ifh, dfh)
1281 if not dfh and not self._inline:
1284 if not dfh and not self._inline:
1282 # addrevision switched from inline to conventional
1285 # addrevision switched from inline to conventional
1283 # reopen the index
1286 # reopen the index
1284 dfh = self.opener(self.datafile, "a")
1287 dfh = self.opener(self.datafile, "a")
1285 ifh = self.opener(self.indexfile, "a")
1288 ifh = self.opener(self.indexfile, "a")
1286 if chk != node:
1289 if chk != node:
1287 raise RevlogError(_("consistency error adding group"))
1290 raise RevlogError(_("consistency error adding group"))
1288 textlen = len(text)
1291 textlen = len(text)
1289 else:
1292 else:
1290 e = (offset_type(end, 0), cdeltalen, textlen, base,
1293 e = (offset_type(end, 0), cdeltalen, textlen, base,
1291 link, self.rev(p1), self.rev(p2), node)
1294 link, self.rev(p1), self.rev(p2), node)
1292 self.index.insert(-1, e)
1295 self.index.insert(-1, e)
1293 self.nodemap[node] = r
1296 self.nodemap[node] = r
1294 entry = self._io.packentry(e, self.node, self.version, r)
1297 entry = self._io.packentry(e, self.node, self.version, r)
1295 if self._inline:
1298 if self._inline:
1296 ifh.write(entry)
1299 ifh.write(entry)
1297 ifh.write(cdelta[0])
1300 ifh.write(cdelta[0])
1298 ifh.write(cdelta[1])
1301 ifh.write(cdelta[1])
1299 self.checkinlinesize(transaction, ifh)
1302 self.checkinlinesize(transaction, ifh)
1300 if not self._inline:
1303 if not self._inline:
1301 dfh = self.opener(self.datafile, "a")
1304 dfh = self.opener(self.datafile, "a")
1302 ifh = self.opener(self.indexfile, "a")
1305 ifh = self.opener(self.indexfile, "a")
1303 else:
1306 else:
1304 dfh.write(cdelta[0])
1307 dfh.write(cdelta[0])
1305 dfh.write(cdelta[1])
1308 dfh.write(cdelta[1])
1306 ifh.write(entry)
1309 ifh.write(entry)
1307
1310
1308 t, r, chain, prev = r, r + 1, node, node
1311 t, r, chain, prev = r, r + 1, node, node
1309 base = self.base(t)
1312 base = self.base(t)
1310 start = self.start(base)
1313 start = self.start(base)
1311 end = self.end(t)
1314 end = self.end(t)
1312 finally:
1315 finally:
1313 if dfh:
1316 if dfh:
1314 dfh.close()
1317 dfh.close()
1315 ifh.close()
1318 ifh.close()
1316
1319
1317 return node
1320 return node
1318
1321
1319 def strip(self, minlink, transaction):
1322 def strip(self, minlink, transaction):
1320 """truncate the revlog on the first revision with a linkrev >= minlink
1323 """truncate the revlog on the first revision with a linkrev >= minlink
1321
1324
1322 This function is called when we're stripping revision minlink and
1325 This function is called when we're stripping revision minlink and
1323 its descendants from the repository.
1326 its descendants from the repository.
1324
1327
1325 We have to remove all revisions with linkrev >= minlink, because
1328 We have to remove all revisions with linkrev >= minlink, because
1326 the equivalent changelog revisions will be renumbered after the
1329 the equivalent changelog revisions will be renumbered after the
1327 strip.
1330 strip.
1328
1331
1329 So we truncate the revlog on the first of these revisions, and
1332 So we truncate the revlog on the first of these revisions, and
1330 trust that the caller has saved the revisions that shouldn't be
1333 trust that the caller has saved the revisions that shouldn't be
1331 removed and that it'll readd them after this truncation.
1334 removed and that it'll readd them after this truncation.
1332 """
1335 """
1333 if len(self) == 0:
1336 if len(self) == 0:
1334 return
1337 return
1335
1338
1336 if isinstance(self.index, lazyindex):
1339 if isinstance(self.index, lazyindex):
1337 self._loadindexmap()
1340 self._loadindexmap()
1338
1341
1339 for rev in self:
1342 for rev in self:
1340 if self.index[rev][4] >= minlink:
1343 if self.index[rev][4] >= minlink:
1341 break
1344 break
1342 else:
1345 else:
1343 return
1346 return
1344
1347
1345 # first truncate the files on disk
1348 # first truncate the files on disk
1346 end = self.start(rev)
1349 end = self.start(rev)
1347 if not self._inline:
1350 if not self._inline:
1348 transaction.add(self.datafile, end)
1351 transaction.add(self.datafile, end)
1349 end = rev * self._io.size
1352 end = rev * self._io.size
1350 else:
1353 else:
1351 end += rev * self._io.size
1354 end += rev * self._io.size
1352
1355
1353 transaction.add(self.indexfile, end)
1356 transaction.add(self.indexfile, end)
1354
1357
1355 # then reset internal state in memory to forget those revisions
1358 # then reset internal state in memory to forget those revisions
1356 self._cache = None
1359 self._cache = None
1357 self._chunkclear()
1360 self._chunkclear()
1358 for x in xrange(rev, len(self)):
1361 for x in xrange(rev, len(self)):
1359 del self.nodemap[self.node(x)]
1362 del self.nodemap[self.node(x)]
1360
1363
1361 del self.index[rev:-1]
1364 del self.index[rev:-1]
1362
1365
1363 def checksize(self):
1366 def checksize(self):
1364 expected = 0
1367 expected = 0
1365 if len(self):
1368 if len(self):
1366 expected = max(0, self.end(len(self) - 1))
1369 expected = max(0, self.end(len(self) - 1))
1367
1370
1368 try:
1371 try:
1369 f = self.opener(self.datafile)
1372 f = self.opener(self.datafile)
1370 f.seek(0, 2)
1373 f.seek(0, 2)
1371 actual = f.tell()
1374 actual = f.tell()
1372 dd = actual - expected
1375 dd = actual - expected
1373 except IOError, inst:
1376 except IOError, inst:
1374 if inst.errno != errno.ENOENT:
1377 if inst.errno != errno.ENOENT:
1375 raise
1378 raise
1376 dd = 0
1379 dd = 0
1377
1380
1378 try:
1381 try:
1379 f = self.opener(self.indexfile)
1382 f = self.opener(self.indexfile)
1380 f.seek(0, 2)
1383 f.seek(0, 2)
1381 actual = f.tell()
1384 actual = f.tell()
1382 s = self._io.size
1385 s = self._io.size
1383 i = max(0, actual // s)
1386 i = max(0, actual // s)
1384 di = actual - (i * s)
1387 di = actual - (i * s)
1385 if self._inline:
1388 if self._inline:
1386 databytes = 0
1389 databytes = 0
1387 for r in self:
1390 for r in self:
1388 databytes += max(0, self.length(r))
1391 databytes += max(0, self.length(r))
1389 dd = 0
1392 dd = 0
1390 di = actual - len(self) * s - databytes
1393 di = actual - len(self) * s - databytes
1391 except IOError, inst:
1394 except IOError, inst:
1392 if inst.errno != errno.ENOENT:
1395 if inst.errno != errno.ENOENT:
1393 raise
1396 raise
1394 di = 0
1397 di = 0
1395
1398
1396 return (dd, di)
1399 return (dd, di)
1397
1400
1398 def files(self):
1401 def files(self):
1399 res = [self.indexfile]
1402 res = [self.indexfile]
1400 if not self._inline:
1403 if not self._inline:
1401 res.append(self.datafile)
1404 res.append(self.datafile)
1402 return res
1405 return res
General Comments 0
You need to be logged in to leave comments. Login now