##// END OF EJS Templates
revlog: add 'raw' argument to revision and _addrevision...
Remi Chaintron -
r30743:2df98312 default
parent child Browse files
Show More
@@ -1,554 +1,554 b''
1 # bundlerepo.py - repository class for viewing uncompressed bundles
1 # bundlerepo.py - repository class for viewing uncompressed bundles
2 #
2 #
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
3 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.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 """Repository class for viewing uncompressed bundles.
8 """Repository class for viewing uncompressed bundles.
9
9
10 This provides a read-only repository interface to bundles as if they
10 This provides a read-only repository interface to bundles as if they
11 were part of the actual repository.
11 were part of the actual repository.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import os
16 import os
17 import shutil
17 import shutil
18 import tempfile
18 import tempfile
19
19
20 from .i18n import _
20 from .i18n import _
21 from .node import nullid
21 from .node import nullid
22
22
23 from . import (
23 from . import (
24 bundle2,
24 bundle2,
25 changegroup,
25 changegroup,
26 changelog,
26 changelog,
27 cmdutil,
27 cmdutil,
28 discovery,
28 discovery,
29 error,
29 error,
30 exchange,
30 exchange,
31 filelog,
31 filelog,
32 localrepo,
32 localrepo,
33 manifest,
33 manifest,
34 mdiff,
34 mdiff,
35 node as nodemod,
35 node as nodemod,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 pycompat,
38 pycompat,
39 revlog,
39 revlog,
40 scmutil,
40 scmutil,
41 util,
41 util,
42 )
42 )
43
43
44 class bundlerevlog(revlog.revlog):
44 class bundlerevlog(revlog.revlog):
45 def __init__(self, opener, indexfile, bundle, linkmapper):
45 def __init__(self, opener, indexfile, bundle, linkmapper):
46 # How it works:
46 # How it works:
47 # To retrieve a revision, we need to know the offset of the revision in
47 # To retrieve a revision, we need to know the offset of the revision in
48 # the bundle (an unbundle object). We store this offset in the index
48 # the bundle (an unbundle object). We store this offset in the index
49 # (start). The base of the delta is stored in the base field.
49 # (start). The base of the delta is stored in the base field.
50 #
50 #
51 # To differentiate a rev in the bundle from a rev in the revlog, we
51 # To differentiate a rev in the bundle from a rev in the revlog, we
52 # check revision against repotiprev.
52 # check revision against repotiprev.
53 opener = scmutil.readonlyvfs(opener)
53 opener = scmutil.readonlyvfs(opener)
54 revlog.revlog.__init__(self, opener, indexfile)
54 revlog.revlog.__init__(self, opener, indexfile)
55 self.bundle = bundle
55 self.bundle = bundle
56 n = len(self)
56 n = len(self)
57 self.repotiprev = n - 1
57 self.repotiprev = n - 1
58 chain = None
58 chain = None
59 self.bundlerevs = set() # used by 'bundle()' revset expression
59 self.bundlerevs = set() # used by 'bundle()' revset expression
60 getchunk = lambda: bundle.deltachunk(chain)
60 getchunk = lambda: bundle.deltachunk(chain)
61 for chunkdata in iter(getchunk, {}):
61 for chunkdata in iter(getchunk, {}):
62 node = chunkdata['node']
62 node = chunkdata['node']
63 p1 = chunkdata['p1']
63 p1 = chunkdata['p1']
64 p2 = chunkdata['p2']
64 p2 = chunkdata['p2']
65 cs = chunkdata['cs']
65 cs = chunkdata['cs']
66 deltabase = chunkdata['deltabase']
66 deltabase = chunkdata['deltabase']
67 delta = chunkdata['delta']
67 delta = chunkdata['delta']
68
68
69 size = len(delta)
69 size = len(delta)
70 start = bundle.tell() - size
70 start = bundle.tell() - size
71
71
72 link = linkmapper(cs)
72 link = linkmapper(cs)
73 if node in self.nodemap:
73 if node in self.nodemap:
74 # this can happen if two branches make the same change
74 # this can happen if two branches make the same change
75 chain = node
75 chain = node
76 self.bundlerevs.add(self.nodemap[node])
76 self.bundlerevs.add(self.nodemap[node])
77 continue
77 continue
78
78
79 for p in (p1, p2):
79 for p in (p1, p2):
80 if p not in self.nodemap:
80 if p not in self.nodemap:
81 raise error.LookupError(p, self.indexfile,
81 raise error.LookupError(p, self.indexfile,
82 _("unknown parent"))
82 _("unknown parent"))
83
83
84 if deltabase not in self.nodemap:
84 if deltabase not in self.nodemap:
85 raise LookupError(deltabase, self.indexfile,
85 raise LookupError(deltabase, self.indexfile,
86 _('unknown delta base'))
86 _('unknown delta base'))
87
87
88 baserev = self.rev(deltabase)
88 baserev = self.rev(deltabase)
89 # start, size, full unc. size, base (unused), link, p1, p2, node
89 # start, size, full unc. size, base (unused), link, p1, p2, node
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
90 e = (revlog.offset_type(start, 0), size, -1, baserev, link,
91 self.rev(p1), self.rev(p2), node)
91 self.rev(p1), self.rev(p2), node)
92 self.index.insert(-1, e)
92 self.index.insert(-1, e)
93 self.nodemap[node] = n
93 self.nodemap[node] = n
94 self.bundlerevs.add(n)
94 self.bundlerevs.add(n)
95 chain = node
95 chain = node
96 n += 1
96 n += 1
97
97
98 def _chunk(self, rev):
98 def _chunk(self, rev):
99 # Warning: in case of bundle, the diff is against what we stored as
99 # Warning: in case of bundle, the diff is against what we stored as
100 # delta base, not against rev - 1
100 # delta base, not against rev - 1
101 # XXX: could use some caching
101 # XXX: could use some caching
102 if rev <= self.repotiprev:
102 if rev <= self.repotiprev:
103 return revlog.revlog._chunk(self, rev)
103 return revlog.revlog._chunk(self, rev)
104 self.bundle.seek(self.start(rev))
104 self.bundle.seek(self.start(rev))
105 return self.bundle.read(self.length(rev))
105 return self.bundle.read(self.length(rev))
106
106
107 def revdiff(self, rev1, rev2):
107 def revdiff(self, rev1, rev2):
108 """return or calculate a delta between two revisions"""
108 """return or calculate a delta between two revisions"""
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
109 if rev1 > self.repotiprev and rev2 > self.repotiprev:
110 # hot path for bundle
110 # hot path for bundle
111 revb = self.index[rev2][3]
111 revb = self.index[rev2][3]
112 if revb == rev1:
112 if revb == rev1:
113 return self._chunk(rev2)
113 return self._chunk(rev2)
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
114 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
115 return revlog.revlog.revdiff(self, rev1, rev2)
115 return revlog.revlog.revdiff(self, rev1, rev2)
116
116
117 return mdiff.textdiff(self.revision(self.node(rev1)),
117 return mdiff.textdiff(self.revision(self.node(rev1)),
118 self.revision(self.node(rev2)))
118 self.revision(self.node(rev2)))
119
119
120 def revision(self, nodeorrev):
120 def revision(self, nodeorrev, raw=False):
121 """return an uncompressed revision of a given node or revision
121 """return an uncompressed revision of a given node or revision
122 number.
122 number.
123 """
123 """
124 if isinstance(nodeorrev, int):
124 if isinstance(nodeorrev, int):
125 rev = nodeorrev
125 rev = nodeorrev
126 node = self.node(rev)
126 node = self.node(rev)
127 else:
127 else:
128 node = nodeorrev
128 node = nodeorrev
129 rev = self.rev(node)
129 rev = self.rev(node)
130
130
131 if node == nullid:
131 if node == nullid:
132 return ""
132 return ""
133
133
134 text = None
134 text = None
135 chain = []
135 chain = []
136 iterrev = rev
136 iterrev = rev
137 # reconstruct the revision if it is from a changegroup
137 # reconstruct the revision if it is from a changegroup
138 while iterrev > self.repotiprev:
138 while iterrev > self.repotiprev:
139 if self._cache and self._cache[1] == iterrev:
139 if self._cache and self._cache[1] == iterrev:
140 text = self._cache[2]
140 text = self._cache[2]
141 break
141 break
142 chain.append(iterrev)
142 chain.append(iterrev)
143 iterrev = self.index[iterrev][3]
143 iterrev = self.index[iterrev][3]
144 if text is None:
144 if text is None:
145 text = self.baserevision(iterrev)
145 text = self.baserevision(iterrev)
146
146
147 while chain:
147 while chain:
148 delta = self._chunk(chain.pop())
148 delta = self._chunk(chain.pop())
149 text = mdiff.patches(text, [delta])
149 text = mdiff.patches(text, [delta])
150
150
151 self.checkhash(text, node, rev=rev)
151 self.checkhash(text, node, rev=rev)
152 self._cache = (node, rev, text)
152 self._cache = (node, rev, text)
153 return text
153 return text
154
154
155 def baserevision(self, nodeorrev):
155 def baserevision(self, nodeorrev):
156 # Revlog subclasses may override 'revision' method to modify format of
156 # Revlog subclasses may override 'revision' method to modify format of
157 # content retrieved from revlog. To use bundlerevlog with such class one
157 # content retrieved from revlog. To use bundlerevlog with such class one
158 # needs to override 'baserevision' and make more specific call here.
158 # needs to override 'baserevision' and make more specific call here.
159 return revlog.revlog.revision(self, nodeorrev)
159 return revlog.revlog.revision(self, nodeorrev)
160
160
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
161 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
162 raise NotImplementedError
162 raise NotImplementedError
163 def addgroup(self, revs, linkmapper, transaction):
163 def addgroup(self, revs, linkmapper, transaction):
164 raise NotImplementedError
164 raise NotImplementedError
165 def strip(self, rev, minlink):
165 def strip(self, rev, minlink):
166 raise NotImplementedError
166 raise NotImplementedError
167 def checksize(self):
167 def checksize(self):
168 raise NotImplementedError
168 raise NotImplementedError
169
169
170 class bundlechangelog(bundlerevlog, changelog.changelog):
170 class bundlechangelog(bundlerevlog, changelog.changelog):
171 def __init__(self, opener, bundle):
171 def __init__(self, opener, bundle):
172 changelog.changelog.__init__(self, opener)
172 changelog.changelog.__init__(self, opener)
173 linkmapper = lambda x: x
173 linkmapper = lambda x: x
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
174 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
175 linkmapper)
175 linkmapper)
176
176
177 def baserevision(self, nodeorrev):
177 def baserevision(self, nodeorrev):
178 # Although changelog doesn't override 'revision' method, some extensions
178 # Although changelog doesn't override 'revision' method, some extensions
179 # may replace this class with another that does. Same story with
179 # may replace this class with another that does. Same story with
180 # manifest and filelog classes.
180 # manifest and filelog classes.
181
181
182 # This bypasses filtering on changelog.node() and rev() because we need
182 # This bypasses filtering on changelog.node() and rev() because we need
183 # revision text of the bundle base even if it is hidden.
183 # revision text of the bundle base even if it is hidden.
184 oldfilter = self.filteredrevs
184 oldfilter = self.filteredrevs
185 try:
185 try:
186 self.filteredrevs = ()
186 self.filteredrevs = ()
187 return changelog.changelog.revision(self, nodeorrev)
187 return changelog.changelog.revision(self, nodeorrev)
188 finally:
188 finally:
189 self.filteredrevs = oldfilter
189 self.filteredrevs = oldfilter
190
190
191 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
191 class bundlemanifest(bundlerevlog, manifest.manifestrevlog):
192 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
192 def __init__(self, opener, bundle, linkmapper, dirlogstarts=None, dir=''):
193 manifest.manifestrevlog.__init__(self, opener, dir=dir)
193 manifest.manifestrevlog.__init__(self, opener, dir=dir)
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
194 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
195 linkmapper)
195 linkmapper)
196 if dirlogstarts is None:
196 if dirlogstarts is None:
197 dirlogstarts = {}
197 dirlogstarts = {}
198 if self.bundle.version == "03":
198 if self.bundle.version == "03":
199 dirlogstarts = _getfilestarts(self.bundle)
199 dirlogstarts = _getfilestarts(self.bundle)
200 self._dirlogstarts = dirlogstarts
200 self._dirlogstarts = dirlogstarts
201 self._linkmapper = linkmapper
201 self._linkmapper = linkmapper
202
202
203 def baserevision(self, nodeorrev):
203 def baserevision(self, nodeorrev):
204 node = nodeorrev
204 node = nodeorrev
205 if isinstance(node, int):
205 if isinstance(node, int):
206 node = self.node(node)
206 node = self.node(node)
207
207
208 if node in self.fulltextcache:
208 if node in self.fulltextcache:
209 result = self.fulltextcache[node].tostring()
209 result = self.fulltextcache[node].tostring()
210 else:
210 else:
211 result = manifest.manifestrevlog.revision(self, nodeorrev)
211 result = manifest.manifestrevlog.revision(self, nodeorrev)
212 return result
212 return result
213
213
214 def dirlog(self, d):
214 def dirlog(self, d):
215 if d in self._dirlogstarts:
215 if d in self._dirlogstarts:
216 self.bundle.seek(self._dirlogstarts[d])
216 self.bundle.seek(self._dirlogstarts[d])
217 return bundlemanifest(
217 return bundlemanifest(
218 self.opener, self.bundle, self._linkmapper,
218 self.opener, self.bundle, self._linkmapper,
219 self._dirlogstarts, dir=d)
219 self._dirlogstarts, dir=d)
220 return super(bundlemanifest, self).dirlog(d)
220 return super(bundlemanifest, self).dirlog(d)
221
221
222 class bundlefilelog(bundlerevlog, filelog.filelog):
222 class bundlefilelog(bundlerevlog, filelog.filelog):
223 def __init__(self, opener, path, bundle, linkmapper):
223 def __init__(self, opener, path, bundle, linkmapper):
224 filelog.filelog.__init__(self, opener, path)
224 filelog.filelog.__init__(self, opener, path)
225 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
225 bundlerevlog.__init__(self, opener, self.indexfile, bundle,
226 linkmapper)
226 linkmapper)
227
227
228 def baserevision(self, nodeorrev):
228 def baserevision(self, nodeorrev):
229 return filelog.filelog.revision(self, nodeorrev)
229 return filelog.filelog.revision(self, nodeorrev)
230
230
231 class bundlepeer(localrepo.localpeer):
231 class bundlepeer(localrepo.localpeer):
232 def canpush(self):
232 def canpush(self):
233 return False
233 return False
234
234
235 class bundlephasecache(phases.phasecache):
235 class bundlephasecache(phases.phasecache):
236 def __init__(self, *args, **kwargs):
236 def __init__(self, *args, **kwargs):
237 super(bundlephasecache, self).__init__(*args, **kwargs)
237 super(bundlephasecache, self).__init__(*args, **kwargs)
238 if util.safehasattr(self, 'opener'):
238 if util.safehasattr(self, 'opener'):
239 self.opener = scmutil.readonlyvfs(self.opener)
239 self.opener = scmutil.readonlyvfs(self.opener)
240
240
241 def write(self):
241 def write(self):
242 raise NotImplementedError
242 raise NotImplementedError
243
243
244 def _write(self, fp):
244 def _write(self, fp):
245 raise NotImplementedError
245 raise NotImplementedError
246
246
247 def _updateroots(self, phase, newroots, tr):
247 def _updateroots(self, phase, newroots, tr):
248 self.phaseroots[phase] = newroots
248 self.phaseroots[phase] = newroots
249 self.invalidate()
249 self.invalidate()
250 self.dirty = True
250 self.dirty = True
251
251
252 def _getfilestarts(bundle):
252 def _getfilestarts(bundle):
253 bundlefilespos = {}
253 bundlefilespos = {}
254 for chunkdata in iter(bundle.filelogheader, {}):
254 for chunkdata in iter(bundle.filelogheader, {}):
255 fname = chunkdata['filename']
255 fname = chunkdata['filename']
256 bundlefilespos[fname] = bundle.tell()
256 bundlefilespos[fname] = bundle.tell()
257 for chunk in iter(lambda: bundle.deltachunk(None), {}):
257 for chunk in iter(lambda: bundle.deltachunk(None), {}):
258 pass
258 pass
259 return bundlefilespos
259 return bundlefilespos
260
260
261 class bundlerepository(localrepo.localrepository):
261 class bundlerepository(localrepo.localrepository):
262 def __init__(self, ui, path, bundlename):
262 def __init__(self, ui, path, bundlename):
263 def _writetempbundle(read, suffix, header=''):
263 def _writetempbundle(read, suffix, header=''):
264 """Write a temporary file to disk
264 """Write a temporary file to disk
265
265
266 This is closure because we need to make sure this tracked by
266 This is closure because we need to make sure this tracked by
267 self.tempfile for cleanup purposes."""
267 self.tempfile for cleanup purposes."""
268 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
268 fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
269 suffix=".hg10un")
269 suffix=".hg10un")
270 self.tempfile = temp
270 self.tempfile = temp
271
271
272 with os.fdopen(fdtemp, 'wb') as fptemp:
272 with os.fdopen(fdtemp, 'wb') as fptemp:
273 fptemp.write(header)
273 fptemp.write(header)
274 while True:
274 while True:
275 chunk = read(2**18)
275 chunk = read(2**18)
276 if not chunk:
276 if not chunk:
277 break
277 break
278 fptemp.write(chunk)
278 fptemp.write(chunk)
279
279
280 return self.vfs.open(self.tempfile, mode="rb")
280 return self.vfs.open(self.tempfile, mode="rb")
281 self._tempparent = None
281 self._tempparent = None
282 try:
282 try:
283 localrepo.localrepository.__init__(self, ui, path)
283 localrepo.localrepository.__init__(self, ui, path)
284 except error.RepoError:
284 except error.RepoError:
285 self._tempparent = tempfile.mkdtemp()
285 self._tempparent = tempfile.mkdtemp()
286 localrepo.instance(ui, self._tempparent, 1)
286 localrepo.instance(ui, self._tempparent, 1)
287 localrepo.localrepository.__init__(self, ui, self._tempparent)
287 localrepo.localrepository.__init__(self, ui, self._tempparent)
288 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
288 self.ui.setconfig('phases', 'publish', False, 'bundlerepo')
289
289
290 if path:
290 if path:
291 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
291 self._url = 'bundle:' + util.expandpath(path) + '+' + bundlename
292 else:
292 else:
293 self._url = 'bundle:' + bundlename
293 self._url = 'bundle:' + bundlename
294
294
295 self.tempfile = None
295 self.tempfile = None
296 f = util.posixfile(bundlename, "rb")
296 f = util.posixfile(bundlename, "rb")
297 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
297 self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
298
298
299 if isinstance(self.bundle, bundle2.unbundle20):
299 if isinstance(self.bundle, bundle2.unbundle20):
300 cgstream = None
300 cgstream = None
301 for part in self.bundle.iterparts():
301 for part in self.bundle.iterparts():
302 if part.type == 'changegroup':
302 if part.type == 'changegroup':
303 if cgstream is not None:
303 if cgstream is not None:
304 raise NotImplementedError("can't process "
304 raise NotImplementedError("can't process "
305 "multiple changegroups")
305 "multiple changegroups")
306 cgstream = part
306 cgstream = part
307 version = part.params.get('version', '01')
307 version = part.params.get('version', '01')
308 legalcgvers = changegroup.supportedincomingversions(self)
308 legalcgvers = changegroup.supportedincomingversions(self)
309 if version not in legalcgvers:
309 if version not in legalcgvers:
310 msg = _('Unsupported changegroup version: %s')
310 msg = _('Unsupported changegroup version: %s')
311 raise error.Abort(msg % version)
311 raise error.Abort(msg % version)
312 if self.bundle.compressed():
312 if self.bundle.compressed():
313 cgstream = _writetempbundle(part.read,
313 cgstream = _writetempbundle(part.read,
314 ".cg%sun" % version)
314 ".cg%sun" % version)
315
315
316 if cgstream is None:
316 if cgstream is None:
317 raise error.Abort(_('No changegroups found'))
317 raise error.Abort(_('No changegroups found'))
318 cgstream.seek(0)
318 cgstream.seek(0)
319
319
320 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
320 self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
321
321
322 elif self.bundle.compressed():
322 elif self.bundle.compressed():
323 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
323 f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
324 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
324 self.bundlefile = self.bundle = exchange.readbundle(ui, f,
325 bundlename,
325 bundlename,
326 self.vfs)
326 self.vfs)
327
327
328 # dict with the mapping 'filename' -> position in the bundle
328 # dict with the mapping 'filename' -> position in the bundle
329 self.bundlefilespos = {}
329 self.bundlefilespos = {}
330
330
331 self.firstnewrev = self.changelog.repotiprev + 1
331 self.firstnewrev = self.changelog.repotiprev + 1
332 phases.retractboundary(self, None, phases.draft,
332 phases.retractboundary(self, None, phases.draft,
333 [ctx.node() for ctx in self[self.firstnewrev:]])
333 [ctx.node() for ctx in self[self.firstnewrev:]])
334
334
335 @localrepo.unfilteredpropertycache
335 @localrepo.unfilteredpropertycache
336 def _phasecache(self):
336 def _phasecache(self):
337 return bundlephasecache(self, self._phasedefaults)
337 return bundlephasecache(self, self._phasedefaults)
338
338
339 @localrepo.unfilteredpropertycache
339 @localrepo.unfilteredpropertycache
340 def changelog(self):
340 def changelog(self):
341 # consume the header if it exists
341 # consume the header if it exists
342 self.bundle.changelogheader()
342 self.bundle.changelogheader()
343 c = bundlechangelog(self.svfs, self.bundle)
343 c = bundlechangelog(self.svfs, self.bundle)
344 self.manstart = self.bundle.tell()
344 self.manstart = self.bundle.tell()
345 return c
345 return c
346
346
347 def _constructmanifest(self):
347 def _constructmanifest(self):
348 self.bundle.seek(self.manstart)
348 self.bundle.seek(self.manstart)
349 # consume the header if it exists
349 # consume the header if it exists
350 self.bundle.manifestheader()
350 self.bundle.manifestheader()
351 linkmapper = self.unfiltered().changelog.rev
351 linkmapper = self.unfiltered().changelog.rev
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
352 m = bundlemanifest(self.svfs, self.bundle, linkmapper)
353 self.filestart = self.bundle.tell()
353 self.filestart = self.bundle.tell()
354 return m
354 return m
355
355
356 @localrepo.unfilteredpropertycache
356 @localrepo.unfilteredpropertycache
357 def manstart(self):
357 def manstart(self):
358 self.changelog
358 self.changelog
359 return self.manstart
359 return self.manstart
360
360
361 @localrepo.unfilteredpropertycache
361 @localrepo.unfilteredpropertycache
362 def filestart(self):
362 def filestart(self):
363 self.manifestlog
363 self.manifestlog
364 return self.filestart
364 return self.filestart
365
365
366 def url(self):
366 def url(self):
367 return self._url
367 return self._url
368
368
369 def file(self, f):
369 def file(self, f):
370 if not self.bundlefilespos:
370 if not self.bundlefilespos:
371 self.bundle.seek(self.filestart)
371 self.bundle.seek(self.filestart)
372 self.bundlefilespos = _getfilestarts(self.bundle)
372 self.bundlefilespos = _getfilestarts(self.bundle)
373
373
374 if f in self.bundlefilespos:
374 if f in self.bundlefilespos:
375 self.bundle.seek(self.bundlefilespos[f])
375 self.bundle.seek(self.bundlefilespos[f])
376 linkmapper = self.unfiltered().changelog.rev
376 linkmapper = self.unfiltered().changelog.rev
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
377 return bundlefilelog(self.svfs, f, self.bundle, linkmapper)
378 else:
378 else:
379 return filelog.filelog(self.svfs, f)
379 return filelog.filelog(self.svfs, f)
380
380
381 def close(self):
381 def close(self):
382 """Close assigned bundle file immediately."""
382 """Close assigned bundle file immediately."""
383 self.bundlefile.close()
383 self.bundlefile.close()
384 if self.tempfile is not None:
384 if self.tempfile is not None:
385 self.vfs.unlink(self.tempfile)
385 self.vfs.unlink(self.tempfile)
386 if self._tempparent:
386 if self._tempparent:
387 shutil.rmtree(self._tempparent, True)
387 shutil.rmtree(self._tempparent, True)
388
388
389 def cancopy(self):
389 def cancopy(self):
390 return False
390 return False
391
391
392 def peer(self):
392 def peer(self):
393 return bundlepeer(self)
393 return bundlepeer(self)
394
394
395 def getcwd(self):
395 def getcwd(self):
396 return pycompat.getcwd() # always outside the repo
396 return pycompat.getcwd() # always outside the repo
397
397
398 # Check if parents exist in localrepo before setting
398 # Check if parents exist in localrepo before setting
399 def setparents(self, p1, p2=nullid):
399 def setparents(self, p1, p2=nullid):
400 p1rev = self.changelog.rev(p1)
400 p1rev = self.changelog.rev(p1)
401 p2rev = self.changelog.rev(p2)
401 p2rev = self.changelog.rev(p2)
402 msg = _("setting parent to node %s that only exists in the bundle\n")
402 msg = _("setting parent to node %s that only exists in the bundle\n")
403 if self.changelog.repotiprev < p1rev:
403 if self.changelog.repotiprev < p1rev:
404 self.ui.warn(msg % nodemod.hex(p1))
404 self.ui.warn(msg % nodemod.hex(p1))
405 if self.changelog.repotiprev < p2rev:
405 if self.changelog.repotiprev < p2rev:
406 self.ui.warn(msg % nodemod.hex(p2))
406 self.ui.warn(msg % nodemod.hex(p2))
407 return super(bundlerepository, self).setparents(p1, p2)
407 return super(bundlerepository, self).setparents(p1, p2)
408
408
409 def instance(ui, path, create):
409 def instance(ui, path, create):
410 if create:
410 if create:
411 raise error.Abort(_('cannot create new bundle repository'))
411 raise error.Abort(_('cannot create new bundle repository'))
412 # internal config: bundle.mainreporoot
412 # internal config: bundle.mainreporoot
413 parentpath = ui.config("bundle", "mainreporoot", "")
413 parentpath = ui.config("bundle", "mainreporoot", "")
414 if not parentpath:
414 if not parentpath:
415 # try to find the correct path to the working directory repo
415 # try to find the correct path to the working directory repo
416 parentpath = cmdutil.findrepo(pycompat.getcwd())
416 parentpath = cmdutil.findrepo(pycompat.getcwd())
417 if parentpath is None:
417 if parentpath is None:
418 parentpath = ''
418 parentpath = ''
419 if parentpath:
419 if parentpath:
420 # Try to make the full path relative so we get a nice, short URL.
420 # Try to make the full path relative so we get a nice, short URL.
421 # In particular, we don't want temp dir names in test outputs.
421 # In particular, we don't want temp dir names in test outputs.
422 cwd = pycompat.getcwd()
422 cwd = pycompat.getcwd()
423 if parentpath == cwd:
423 if parentpath == cwd:
424 parentpath = ''
424 parentpath = ''
425 else:
425 else:
426 cwd = pathutil.normasprefix(cwd)
426 cwd = pathutil.normasprefix(cwd)
427 if parentpath.startswith(cwd):
427 if parentpath.startswith(cwd):
428 parentpath = parentpath[len(cwd):]
428 parentpath = parentpath[len(cwd):]
429 u = util.url(path)
429 u = util.url(path)
430 path = u.localpath()
430 path = u.localpath()
431 if u.scheme == 'bundle':
431 if u.scheme == 'bundle':
432 s = path.split("+", 1)
432 s = path.split("+", 1)
433 if len(s) == 1:
433 if len(s) == 1:
434 repopath, bundlename = parentpath, s[0]
434 repopath, bundlename = parentpath, s[0]
435 else:
435 else:
436 repopath, bundlename = s
436 repopath, bundlename = s
437 else:
437 else:
438 repopath, bundlename = parentpath, path
438 repopath, bundlename = parentpath, path
439 return bundlerepository(ui, repopath, bundlename)
439 return bundlerepository(ui, repopath, bundlename)
440
440
441 class bundletransactionmanager(object):
441 class bundletransactionmanager(object):
442 def transaction(self):
442 def transaction(self):
443 return None
443 return None
444
444
445 def close(self):
445 def close(self):
446 raise NotImplementedError
446 raise NotImplementedError
447
447
448 def release(self):
448 def release(self):
449 raise NotImplementedError
449 raise NotImplementedError
450
450
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
451 def getremotechanges(ui, repo, other, onlyheads=None, bundlename=None,
452 force=False):
452 force=False):
453 '''obtains a bundle of changes incoming from other
453 '''obtains a bundle of changes incoming from other
454
454
455 "onlyheads" restricts the returned changes to those reachable from the
455 "onlyheads" restricts the returned changes to those reachable from the
456 specified heads.
456 specified heads.
457 "bundlename", if given, stores the bundle to this file path permanently;
457 "bundlename", if given, stores the bundle to this file path permanently;
458 otherwise it's stored to a temp file and gets deleted again when you call
458 otherwise it's stored to a temp file and gets deleted again when you call
459 the returned "cleanupfn".
459 the returned "cleanupfn".
460 "force" indicates whether to proceed on unrelated repos.
460 "force" indicates whether to proceed on unrelated repos.
461
461
462 Returns a tuple (local, csets, cleanupfn):
462 Returns a tuple (local, csets, cleanupfn):
463
463
464 "local" is a local repo from which to obtain the actual incoming
464 "local" is a local repo from which to obtain the actual incoming
465 changesets; it is a bundlerepo for the obtained bundle when the
465 changesets; it is a bundlerepo for the obtained bundle when the
466 original "other" is remote.
466 original "other" is remote.
467 "csets" lists the incoming changeset node ids.
467 "csets" lists the incoming changeset node ids.
468 "cleanupfn" must be called without arguments when you're done processing
468 "cleanupfn" must be called without arguments when you're done processing
469 the changes; it closes both the original "other" and the one returned
469 the changes; it closes both the original "other" and the one returned
470 here.
470 here.
471 '''
471 '''
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
472 tmp = discovery.findcommonincoming(repo, other, heads=onlyheads,
473 force=force)
473 force=force)
474 common, incoming, rheads = tmp
474 common, incoming, rheads = tmp
475 if not incoming:
475 if not incoming:
476 try:
476 try:
477 if bundlename:
477 if bundlename:
478 os.unlink(bundlename)
478 os.unlink(bundlename)
479 except OSError:
479 except OSError:
480 pass
480 pass
481 return repo, [], other.close
481 return repo, [], other.close
482
482
483 commonset = set(common)
483 commonset = set(common)
484 rheads = [x for x in rheads if x not in commonset]
484 rheads = [x for x in rheads if x not in commonset]
485
485
486 bundle = None
486 bundle = None
487 bundlerepo = None
487 bundlerepo = None
488 localrepo = other.local()
488 localrepo = other.local()
489 if bundlename or not localrepo:
489 if bundlename or not localrepo:
490 # create a bundle (uncompressed if other repo is not local)
490 # create a bundle (uncompressed if other repo is not local)
491
491
492 # developer config: devel.legacy.exchange
492 # developer config: devel.legacy.exchange
493 legexc = ui.configlist('devel', 'legacy.exchange')
493 legexc = ui.configlist('devel', 'legacy.exchange')
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
494 forcebundle1 = 'bundle2' not in legexc and 'bundle1' in legexc
495 canbundle2 = (not forcebundle1
495 canbundle2 = (not forcebundle1
496 and other.capable('getbundle')
496 and other.capable('getbundle')
497 and other.capable('bundle2'))
497 and other.capable('bundle2'))
498 if canbundle2:
498 if canbundle2:
499 kwargs = {}
499 kwargs = {}
500 kwargs['common'] = common
500 kwargs['common'] = common
501 kwargs['heads'] = rheads
501 kwargs['heads'] = rheads
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
502 kwargs['bundlecaps'] = exchange.caps20to10(repo)
503 kwargs['cg'] = True
503 kwargs['cg'] = True
504 b2 = other.getbundle('incoming', **kwargs)
504 b2 = other.getbundle('incoming', **kwargs)
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
505 fname = bundle = changegroup.writechunks(ui, b2._forwardchunks(),
506 bundlename)
506 bundlename)
507 else:
507 else:
508 if other.capable('getbundle'):
508 if other.capable('getbundle'):
509 cg = other.getbundle('incoming', common=common, heads=rheads)
509 cg = other.getbundle('incoming', common=common, heads=rheads)
510 elif onlyheads is None and not other.capable('changegroupsubset'):
510 elif onlyheads is None and not other.capable('changegroupsubset'):
511 # compat with older servers when pulling all remote heads
511 # compat with older servers when pulling all remote heads
512 cg = other.changegroup(incoming, "incoming")
512 cg = other.changegroup(incoming, "incoming")
513 rheads = None
513 rheads = None
514 else:
514 else:
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
515 cg = other.changegroupsubset(incoming, rheads, 'incoming')
516 if localrepo:
516 if localrepo:
517 bundletype = "HG10BZ"
517 bundletype = "HG10BZ"
518 else:
518 else:
519 bundletype = "HG10UN"
519 bundletype = "HG10UN"
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
520 fname = bundle = bundle2.writebundle(ui, cg, bundlename,
521 bundletype)
521 bundletype)
522 # keep written bundle?
522 # keep written bundle?
523 if bundlename:
523 if bundlename:
524 bundle = None
524 bundle = None
525 if not localrepo:
525 if not localrepo:
526 # use the created uncompressed bundlerepo
526 # use the created uncompressed bundlerepo
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
527 localrepo = bundlerepo = bundlerepository(repo.baseui, repo.root,
528 fname)
528 fname)
529 # this repo contains local and other now, so filter out local again
529 # this repo contains local and other now, so filter out local again
530 common = repo.heads()
530 common = repo.heads()
531 if localrepo:
531 if localrepo:
532 # Part of common may be remotely filtered
532 # Part of common may be remotely filtered
533 # So use an unfiltered version
533 # So use an unfiltered version
534 # The discovery process probably need cleanup to avoid that
534 # The discovery process probably need cleanup to avoid that
535 localrepo = localrepo.unfiltered()
535 localrepo = localrepo.unfiltered()
536
536
537 csets = localrepo.changelog.findmissing(common, rheads)
537 csets = localrepo.changelog.findmissing(common, rheads)
538
538
539 if bundlerepo:
539 if bundlerepo:
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
540 reponodes = [ctx.node() for ctx in bundlerepo[bundlerepo.firstnewrev:]]
541 remotephases = other.listkeys('phases')
541 remotephases = other.listkeys('phases')
542
542
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
543 pullop = exchange.pulloperation(bundlerepo, other, heads=reponodes)
544 pullop.trmanager = bundletransactionmanager()
544 pullop.trmanager = bundletransactionmanager()
545 exchange._pullapplyphases(pullop, remotephases)
545 exchange._pullapplyphases(pullop, remotephases)
546
546
547 def cleanup():
547 def cleanup():
548 if bundlerepo:
548 if bundlerepo:
549 bundlerepo.close()
549 bundlerepo.close()
550 if bundle:
550 if bundle:
551 os.unlink(bundle)
551 os.unlink(bundle)
552 other.close()
552 other.close()
553
553
554 return (localrepo, csets, cleanup)
554 return (localrepo, csets, cleanup)
@@ -1,1044 +1,1044 b''
1 # changegroup.py - Mercurial changegroup manipulation functions
1 # changegroup.py - Mercurial changegroup manipulation functions
2 #
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11 import struct
11 import struct
12 import tempfile
12 import tempfile
13 import weakref
13 import weakref
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 hex,
17 hex,
18 nullrev,
18 nullrev,
19 short,
19 short,
20 )
20 )
21
21
22 from . import (
22 from . import (
23 branchmap,
23 branchmap,
24 dagutil,
24 dagutil,
25 discovery,
25 discovery,
26 error,
26 error,
27 mdiff,
27 mdiff,
28 phases,
28 phases,
29 util,
29 util,
30 )
30 )
31
31
32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
32 _CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
33 _CHANGEGROUPV2_DELTA_HEADER = "20s20s20s20s20s"
34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
34 _CHANGEGROUPV3_DELTA_HEADER = ">20s20s20s20s20sH"
35
35
36 def readexactly(stream, n):
36 def readexactly(stream, n):
37 '''read n bytes from stream.read and abort if less was available'''
37 '''read n bytes from stream.read and abort if less was available'''
38 s = stream.read(n)
38 s = stream.read(n)
39 if len(s) < n:
39 if len(s) < n:
40 raise error.Abort(_("stream ended unexpectedly"
40 raise error.Abort(_("stream ended unexpectedly"
41 " (got %d bytes, expected %d)")
41 " (got %d bytes, expected %d)")
42 % (len(s), n))
42 % (len(s), n))
43 return s
43 return s
44
44
45 def getchunk(stream):
45 def getchunk(stream):
46 """return the next chunk from stream as a string"""
46 """return the next chunk from stream as a string"""
47 d = readexactly(stream, 4)
47 d = readexactly(stream, 4)
48 l = struct.unpack(">l", d)[0]
48 l = struct.unpack(">l", d)[0]
49 if l <= 4:
49 if l <= 4:
50 if l:
50 if l:
51 raise error.Abort(_("invalid chunk length %d") % l)
51 raise error.Abort(_("invalid chunk length %d") % l)
52 return ""
52 return ""
53 return readexactly(stream, l - 4)
53 return readexactly(stream, l - 4)
54
54
55 def chunkheader(length):
55 def chunkheader(length):
56 """return a changegroup chunk header (string)"""
56 """return a changegroup chunk header (string)"""
57 return struct.pack(">l", length + 4)
57 return struct.pack(">l", length + 4)
58
58
59 def closechunk():
59 def closechunk():
60 """return a changegroup chunk header (string) for a zero-length chunk"""
60 """return a changegroup chunk header (string) for a zero-length chunk"""
61 return struct.pack(">l", 0)
61 return struct.pack(">l", 0)
62
62
63 def combineresults(results):
63 def combineresults(results):
64 """logic to combine 0 or more addchangegroup results into one"""
64 """logic to combine 0 or more addchangegroup results into one"""
65 changedheads = 0
65 changedheads = 0
66 result = 1
66 result = 1
67 for ret in results:
67 for ret in results:
68 # If any changegroup result is 0, return 0
68 # If any changegroup result is 0, return 0
69 if ret == 0:
69 if ret == 0:
70 result = 0
70 result = 0
71 break
71 break
72 if ret < -1:
72 if ret < -1:
73 changedheads += ret + 1
73 changedheads += ret + 1
74 elif ret > 1:
74 elif ret > 1:
75 changedheads += ret - 1
75 changedheads += ret - 1
76 if changedheads > 0:
76 if changedheads > 0:
77 result = 1 + changedheads
77 result = 1 + changedheads
78 elif changedheads < 0:
78 elif changedheads < 0:
79 result = -1 + changedheads
79 result = -1 + changedheads
80 return result
80 return result
81
81
82 def writechunks(ui, chunks, filename, vfs=None):
82 def writechunks(ui, chunks, filename, vfs=None):
83 """Write chunks to a file and return its filename.
83 """Write chunks to a file and return its filename.
84
84
85 The stream is assumed to be a bundle file.
85 The stream is assumed to be a bundle file.
86 Existing files will not be overwritten.
86 Existing files will not be overwritten.
87 If no filename is specified, a temporary file is created.
87 If no filename is specified, a temporary file is created.
88 """
88 """
89 fh = None
89 fh = None
90 cleanup = None
90 cleanup = None
91 try:
91 try:
92 if filename:
92 if filename:
93 if vfs:
93 if vfs:
94 fh = vfs.open(filename, "wb")
94 fh = vfs.open(filename, "wb")
95 else:
95 else:
96 # Increase default buffer size because default is usually
96 # Increase default buffer size because default is usually
97 # small (4k is common on Linux).
97 # small (4k is common on Linux).
98 fh = open(filename, "wb", 131072)
98 fh = open(filename, "wb", 131072)
99 else:
99 else:
100 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
100 fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg")
101 fh = os.fdopen(fd, "wb")
101 fh = os.fdopen(fd, "wb")
102 cleanup = filename
102 cleanup = filename
103 for c in chunks:
103 for c in chunks:
104 fh.write(c)
104 fh.write(c)
105 cleanup = None
105 cleanup = None
106 return filename
106 return filename
107 finally:
107 finally:
108 if fh is not None:
108 if fh is not None:
109 fh.close()
109 fh.close()
110 if cleanup is not None:
110 if cleanup is not None:
111 if filename and vfs:
111 if filename and vfs:
112 vfs.unlink(cleanup)
112 vfs.unlink(cleanup)
113 else:
113 else:
114 os.unlink(cleanup)
114 os.unlink(cleanup)
115
115
116 class cg1unpacker(object):
116 class cg1unpacker(object):
117 """Unpacker for cg1 changegroup streams.
117 """Unpacker for cg1 changegroup streams.
118
118
119 A changegroup unpacker handles the framing of the revision data in
119 A changegroup unpacker handles the framing of the revision data in
120 the wire format. Most consumers will want to use the apply()
120 the wire format. Most consumers will want to use the apply()
121 method to add the changes from the changegroup to a repository.
121 method to add the changes from the changegroup to a repository.
122
122
123 If you're forwarding a changegroup unmodified to another consumer,
123 If you're forwarding a changegroup unmodified to another consumer,
124 use getchunks(), which returns an iterator of changegroup
124 use getchunks(), which returns an iterator of changegroup
125 chunks. This is mostly useful for cases where you need to know the
125 chunks. This is mostly useful for cases where you need to know the
126 data stream has ended by observing the end of the changegroup.
126 data stream has ended by observing the end of the changegroup.
127
127
128 deltachunk() is useful only if you're applying delta data. Most
128 deltachunk() is useful only if you're applying delta data. Most
129 consumers should prefer apply() instead.
129 consumers should prefer apply() instead.
130
130
131 A few other public methods exist. Those are used only for
131 A few other public methods exist. Those are used only for
132 bundlerepo and some debug commands - their use is discouraged.
132 bundlerepo and some debug commands - their use is discouraged.
133 """
133 """
134 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
134 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
135 deltaheadersize = struct.calcsize(deltaheader)
135 deltaheadersize = struct.calcsize(deltaheader)
136 version = '01'
136 version = '01'
137 _grouplistcount = 1 # One list of files after the manifests
137 _grouplistcount = 1 # One list of files after the manifests
138
138
139 def __init__(self, fh, alg, extras=None):
139 def __init__(self, fh, alg, extras=None):
140 if alg is None:
140 if alg is None:
141 alg = 'UN'
141 alg = 'UN'
142 if alg not in util.compengines.supportedbundletypes:
142 if alg not in util.compengines.supportedbundletypes:
143 raise error.Abort(_('unknown stream compression type: %s')
143 raise error.Abort(_('unknown stream compression type: %s')
144 % alg)
144 % alg)
145 if alg == 'BZ':
145 if alg == 'BZ':
146 alg = '_truncatedBZ'
146 alg = '_truncatedBZ'
147
147
148 compengine = util.compengines.forbundletype(alg)
148 compengine = util.compengines.forbundletype(alg)
149 self._stream = compengine.decompressorreader(fh)
149 self._stream = compengine.decompressorreader(fh)
150 self._type = alg
150 self._type = alg
151 self.extras = extras or {}
151 self.extras = extras or {}
152 self.callback = None
152 self.callback = None
153
153
154 # These methods (compressed, read, seek, tell) all appear to only
154 # These methods (compressed, read, seek, tell) all appear to only
155 # be used by bundlerepo, but it's a little hard to tell.
155 # be used by bundlerepo, but it's a little hard to tell.
156 def compressed(self):
156 def compressed(self):
157 return self._type is not None and self._type != 'UN'
157 return self._type is not None and self._type != 'UN'
158 def read(self, l):
158 def read(self, l):
159 return self._stream.read(l)
159 return self._stream.read(l)
160 def seek(self, pos):
160 def seek(self, pos):
161 return self._stream.seek(pos)
161 return self._stream.seek(pos)
162 def tell(self):
162 def tell(self):
163 return self._stream.tell()
163 return self._stream.tell()
164 def close(self):
164 def close(self):
165 return self._stream.close()
165 return self._stream.close()
166
166
167 def _chunklength(self):
167 def _chunklength(self):
168 d = readexactly(self._stream, 4)
168 d = readexactly(self._stream, 4)
169 l = struct.unpack(">l", d)[0]
169 l = struct.unpack(">l", d)[0]
170 if l <= 4:
170 if l <= 4:
171 if l:
171 if l:
172 raise error.Abort(_("invalid chunk length %d") % l)
172 raise error.Abort(_("invalid chunk length %d") % l)
173 return 0
173 return 0
174 if self.callback:
174 if self.callback:
175 self.callback()
175 self.callback()
176 return l - 4
176 return l - 4
177
177
178 def changelogheader(self):
178 def changelogheader(self):
179 """v10 does not have a changelog header chunk"""
179 """v10 does not have a changelog header chunk"""
180 return {}
180 return {}
181
181
182 def manifestheader(self):
182 def manifestheader(self):
183 """v10 does not have a manifest header chunk"""
183 """v10 does not have a manifest header chunk"""
184 return {}
184 return {}
185
185
186 def filelogheader(self):
186 def filelogheader(self):
187 """return the header of the filelogs chunk, v10 only has the filename"""
187 """return the header of the filelogs chunk, v10 only has the filename"""
188 l = self._chunklength()
188 l = self._chunklength()
189 if not l:
189 if not l:
190 return {}
190 return {}
191 fname = readexactly(self._stream, l)
191 fname = readexactly(self._stream, l)
192 return {'filename': fname}
192 return {'filename': fname}
193
193
194 def _deltaheader(self, headertuple, prevnode):
194 def _deltaheader(self, headertuple, prevnode):
195 node, p1, p2, cs = headertuple
195 node, p1, p2, cs = headertuple
196 if prevnode is None:
196 if prevnode is None:
197 deltabase = p1
197 deltabase = p1
198 else:
198 else:
199 deltabase = prevnode
199 deltabase = prevnode
200 flags = 0
200 flags = 0
201 return node, p1, p2, deltabase, cs, flags
201 return node, p1, p2, deltabase, cs, flags
202
202
203 def deltachunk(self, prevnode):
203 def deltachunk(self, prevnode):
204 l = self._chunklength()
204 l = self._chunklength()
205 if not l:
205 if not l:
206 return {}
206 return {}
207 headerdata = readexactly(self._stream, self.deltaheadersize)
207 headerdata = readexactly(self._stream, self.deltaheadersize)
208 header = struct.unpack(self.deltaheader, headerdata)
208 header = struct.unpack(self.deltaheader, headerdata)
209 delta = readexactly(self._stream, l - self.deltaheadersize)
209 delta = readexactly(self._stream, l - self.deltaheadersize)
210 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
210 node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
211 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
211 return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
212 'deltabase': deltabase, 'delta': delta, 'flags': flags}
212 'deltabase': deltabase, 'delta': delta, 'flags': flags}
213
213
214 def getchunks(self):
214 def getchunks(self):
215 """returns all the chunks contains in the bundle
215 """returns all the chunks contains in the bundle
216
216
217 Used when you need to forward the binary stream to a file or another
217 Used when you need to forward the binary stream to a file or another
218 network API. To do so, it parse the changegroup data, otherwise it will
218 network API. To do so, it parse the changegroup data, otherwise it will
219 block in case of sshrepo because it don't know the end of the stream.
219 block in case of sshrepo because it don't know the end of the stream.
220 """
220 """
221 # an empty chunkgroup is the end of the changegroup
221 # an empty chunkgroup is the end of the changegroup
222 # a changegroup has at least 2 chunkgroups (changelog and manifest).
222 # a changegroup has at least 2 chunkgroups (changelog and manifest).
223 # after that, changegroup versions 1 and 2 have a series of groups
223 # after that, changegroup versions 1 and 2 have a series of groups
224 # with one group per file. changegroup 3 has a series of directory
224 # with one group per file. changegroup 3 has a series of directory
225 # manifests before the files.
225 # manifests before the files.
226 count = 0
226 count = 0
227 emptycount = 0
227 emptycount = 0
228 while emptycount < self._grouplistcount:
228 while emptycount < self._grouplistcount:
229 empty = True
229 empty = True
230 count += 1
230 count += 1
231 while True:
231 while True:
232 chunk = getchunk(self)
232 chunk = getchunk(self)
233 if not chunk:
233 if not chunk:
234 if empty and count > 2:
234 if empty and count > 2:
235 emptycount += 1
235 emptycount += 1
236 break
236 break
237 empty = False
237 empty = False
238 yield chunkheader(len(chunk))
238 yield chunkheader(len(chunk))
239 pos = 0
239 pos = 0
240 while pos < len(chunk):
240 while pos < len(chunk):
241 next = pos + 2**20
241 next = pos + 2**20
242 yield chunk[pos:next]
242 yield chunk[pos:next]
243 pos = next
243 pos = next
244 yield closechunk()
244 yield closechunk()
245
245
246 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
246 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
247 # We know that we'll never have more manifests than we had
247 # We know that we'll never have more manifests than we had
248 # changesets.
248 # changesets.
249 self.callback = prog(_('manifests'), numchanges)
249 self.callback = prog(_('manifests'), numchanges)
250 # no need to check for empty manifest group here:
250 # no need to check for empty manifest group here:
251 # if the result of the merge of 1 and 2 is the same in 3 and 4,
251 # if the result of the merge of 1 and 2 is the same in 3 and 4,
252 # no new manifest will be created and the manifest group will
252 # no new manifest will be created and the manifest group will
253 # be empty during the pull
253 # be empty during the pull
254 self.manifestheader()
254 self.manifestheader()
255 repo.manifestlog._revlog.addgroup(self, revmap, trp)
255 repo.manifestlog._revlog.addgroup(self, revmap, trp)
256 repo.ui.progress(_('manifests'), None)
256 repo.ui.progress(_('manifests'), None)
257 self.callback = None
257 self.callback = None
258
258
259 def apply(self, repo, srctype, url, emptyok=False,
259 def apply(self, repo, srctype, url, emptyok=False,
260 targetphase=phases.draft, expectedtotal=None):
260 targetphase=phases.draft, expectedtotal=None):
261 """Add the changegroup returned by source.read() to this repo.
261 """Add the changegroup returned by source.read() to this repo.
262 srctype is a string like 'push', 'pull', or 'unbundle'. url is
262 srctype is a string like 'push', 'pull', or 'unbundle'. url is
263 the URL of the repo where this changegroup is coming from.
263 the URL of the repo where this changegroup is coming from.
264
264
265 Return an integer summarizing the change to this repo:
265 Return an integer summarizing the change to this repo:
266 - nothing changed or no source: 0
266 - nothing changed or no source: 0
267 - more heads than before: 1+added heads (2..n)
267 - more heads than before: 1+added heads (2..n)
268 - fewer heads than before: -1-removed heads (-2..-n)
268 - fewer heads than before: -1-removed heads (-2..-n)
269 - number of heads stays the same: 1
269 - number of heads stays the same: 1
270 """
270 """
271 repo = repo.unfiltered()
271 repo = repo.unfiltered()
272 def csmap(x):
272 def csmap(x):
273 repo.ui.debug("add changeset %s\n" % short(x))
273 repo.ui.debug("add changeset %s\n" % short(x))
274 return len(cl)
274 return len(cl)
275
275
276 def revmap(x):
276 def revmap(x):
277 return cl.rev(x)
277 return cl.rev(x)
278
278
279 changesets = files = revisions = 0
279 changesets = files = revisions = 0
280
280
281 try:
281 try:
282 with repo.transaction("\n".join([srctype,
282 with repo.transaction("\n".join([srctype,
283 util.hidepassword(url)])) as tr:
283 util.hidepassword(url)])) as tr:
284 # The transaction could have been created before and already
284 # The transaction could have been created before and already
285 # carries source information. In this case we use the top
285 # carries source information. In this case we use the top
286 # level data. We overwrite the argument because we need to use
286 # level data. We overwrite the argument because we need to use
287 # the top level value (if they exist) in this function.
287 # the top level value (if they exist) in this function.
288 srctype = tr.hookargs.setdefault('source', srctype)
288 srctype = tr.hookargs.setdefault('source', srctype)
289 url = tr.hookargs.setdefault('url', url)
289 url = tr.hookargs.setdefault('url', url)
290 repo.hook('prechangegroup', throw=True, **tr.hookargs)
290 repo.hook('prechangegroup', throw=True, **tr.hookargs)
291
291
292 # write changelog data to temp files so concurrent readers
292 # write changelog data to temp files so concurrent readers
293 # will not see an inconsistent view
293 # will not see an inconsistent view
294 cl = repo.changelog
294 cl = repo.changelog
295 cl.delayupdate(tr)
295 cl.delayupdate(tr)
296 oldheads = cl.heads()
296 oldheads = cl.heads()
297
297
298 trp = weakref.proxy(tr)
298 trp = weakref.proxy(tr)
299 # pull off the changeset group
299 # pull off the changeset group
300 repo.ui.status(_("adding changesets\n"))
300 repo.ui.status(_("adding changesets\n"))
301 clstart = len(cl)
301 clstart = len(cl)
302 class prog(object):
302 class prog(object):
303 def __init__(self, step, total):
303 def __init__(self, step, total):
304 self._step = step
304 self._step = step
305 self._total = total
305 self._total = total
306 self._count = 1
306 self._count = 1
307 def __call__(self):
307 def __call__(self):
308 repo.ui.progress(self._step, self._count,
308 repo.ui.progress(self._step, self._count,
309 unit=_('chunks'), total=self._total)
309 unit=_('chunks'), total=self._total)
310 self._count += 1
310 self._count += 1
311 self.callback = prog(_('changesets'), expectedtotal)
311 self.callback = prog(_('changesets'), expectedtotal)
312
312
313 efiles = set()
313 efiles = set()
314 def onchangelog(cl, node):
314 def onchangelog(cl, node):
315 efiles.update(cl.readfiles(node))
315 efiles.update(cl.readfiles(node))
316
316
317 self.changelogheader()
317 self.changelogheader()
318 srccontent = cl.addgroup(self, csmap, trp,
318 srccontent = cl.addgroup(self, csmap, trp,
319 addrevisioncb=onchangelog)
319 addrevisioncb=onchangelog)
320 efiles = len(efiles)
320 efiles = len(efiles)
321
321
322 if not (srccontent or emptyok):
322 if not (srccontent or emptyok):
323 raise error.Abort(_("received changelog group is empty"))
323 raise error.Abort(_("received changelog group is empty"))
324 clend = len(cl)
324 clend = len(cl)
325 changesets = clend - clstart
325 changesets = clend - clstart
326 repo.ui.progress(_('changesets'), None)
326 repo.ui.progress(_('changesets'), None)
327 self.callback = None
327 self.callback = None
328
328
329 # pull off the manifest group
329 # pull off the manifest group
330 repo.ui.status(_("adding manifests\n"))
330 repo.ui.status(_("adding manifests\n"))
331 self._unpackmanifests(repo, revmap, trp, prog, changesets)
331 self._unpackmanifests(repo, revmap, trp, prog, changesets)
332
332
333 needfiles = {}
333 needfiles = {}
334 if repo.ui.configbool('server', 'validate', default=False):
334 if repo.ui.configbool('server', 'validate', default=False):
335 cl = repo.changelog
335 cl = repo.changelog
336 ml = repo.manifestlog
336 ml = repo.manifestlog
337 # validate incoming csets have their manifests
337 # validate incoming csets have their manifests
338 for cset in xrange(clstart, clend):
338 for cset in xrange(clstart, clend):
339 mfnode = cl.changelogrevision(cset).manifest
339 mfnode = cl.changelogrevision(cset).manifest
340 mfest = ml[mfnode].readdelta()
340 mfest = ml[mfnode].readdelta()
341 # store file nodes we must see
341 # store file nodes we must see
342 for f, n in mfest.iteritems():
342 for f, n in mfest.iteritems():
343 needfiles.setdefault(f, set()).add(n)
343 needfiles.setdefault(f, set()).add(n)
344
344
345 # process the files
345 # process the files
346 repo.ui.status(_("adding file changes\n"))
346 repo.ui.status(_("adding file changes\n"))
347 newrevs, newfiles = _addchangegroupfiles(
347 newrevs, newfiles = _addchangegroupfiles(
348 repo, self, revmap, trp, efiles, needfiles)
348 repo, self, revmap, trp, efiles, needfiles)
349 revisions += newrevs
349 revisions += newrevs
350 files += newfiles
350 files += newfiles
351
351
352 dh = 0
352 dh = 0
353 if oldheads:
353 if oldheads:
354 heads = cl.heads()
354 heads = cl.heads()
355 dh = len(heads) - len(oldheads)
355 dh = len(heads) - len(oldheads)
356 for h in heads:
356 for h in heads:
357 if h not in oldheads and repo[h].closesbranch():
357 if h not in oldheads and repo[h].closesbranch():
358 dh -= 1
358 dh -= 1
359 htext = ""
359 htext = ""
360 if dh:
360 if dh:
361 htext = _(" (%+d heads)") % dh
361 htext = _(" (%+d heads)") % dh
362
362
363 repo.ui.status(_("added %d changesets"
363 repo.ui.status(_("added %d changesets"
364 " with %d changes to %d files%s\n")
364 " with %d changes to %d files%s\n")
365 % (changesets, revisions, files, htext))
365 % (changesets, revisions, files, htext))
366 repo.invalidatevolatilesets()
366 repo.invalidatevolatilesets()
367
367
368 if changesets > 0:
368 if changesets > 0:
369 if 'node' not in tr.hookargs:
369 if 'node' not in tr.hookargs:
370 tr.hookargs['node'] = hex(cl.node(clstart))
370 tr.hookargs['node'] = hex(cl.node(clstart))
371 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
371 tr.hookargs['node_last'] = hex(cl.node(clend - 1))
372 hookargs = dict(tr.hookargs)
372 hookargs = dict(tr.hookargs)
373 else:
373 else:
374 hookargs = dict(tr.hookargs)
374 hookargs = dict(tr.hookargs)
375 hookargs['node'] = hex(cl.node(clstart))
375 hookargs['node'] = hex(cl.node(clstart))
376 hookargs['node_last'] = hex(cl.node(clend - 1))
376 hookargs['node_last'] = hex(cl.node(clend - 1))
377 repo.hook('pretxnchangegroup', throw=True, **hookargs)
377 repo.hook('pretxnchangegroup', throw=True, **hookargs)
378
378
379 added = [cl.node(r) for r in xrange(clstart, clend)]
379 added = [cl.node(r) for r in xrange(clstart, clend)]
380 publishing = repo.publishing()
380 publishing = repo.publishing()
381 if srctype in ('push', 'serve'):
381 if srctype in ('push', 'serve'):
382 # Old servers can not push the boundary themselves.
382 # Old servers can not push the boundary themselves.
383 # New servers won't push the boundary if changeset already
383 # New servers won't push the boundary if changeset already
384 # exists locally as secret
384 # exists locally as secret
385 #
385 #
386 # We should not use added here but the list of all change in
386 # We should not use added here but the list of all change in
387 # the bundle
387 # the bundle
388 if publishing:
388 if publishing:
389 phases.advanceboundary(repo, tr, phases.public,
389 phases.advanceboundary(repo, tr, phases.public,
390 srccontent)
390 srccontent)
391 else:
391 else:
392 # Those changesets have been pushed from the
392 # Those changesets have been pushed from the
393 # outside, their phases are going to be pushed
393 # outside, their phases are going to be pushed
394 # alongside. Therefor `targetphase` is
394 # alongside. Therefor `targetphase` is
395 # ignored.
395 # ignored.
396 phases.advanceboundary(repo, tr, phases.draft,
396 phases.advanceboundary(repo, tr, phases.draft,
397 srccontent)
397 srccontent)
398 phases.retractboundary(repo, tr, phases.draft, added)
398 phases.retractboundary(repo, tr, phases.draft, added)
399 elif srctype != 'strip':
399 elif srctype != 'strip':
400 # publishing only alter behavior during push
400 # publishing only alter behavior during push
401 #
401 #
402 # strip should not touch boundary at all
402 # strip should not touch boundary at all
403 phases.retractboundary(repo, tr, targetphase, added)
403 phases.retractboundary(repo, tr, targetphase, added)
404
404
405 if changesets > 0:
405 if changesets > 0:
406 if srctype != 'strip':
406 if srctype != 'strip':
407 # During strip, branchcache is invalid but
407 # During strip, branchcache is invalid but
408 # coming call to `destroyed` will repair it.
408 # coming call to `destroyed` will repair it.
409 # In other case we can safely update cache on
409 # In other case we can safely update cache on
410 # disk.
410 # disk.
411 repo.ui.debug('updating the branch cache\n')
411 repo.ui.debug('updating the branch cache\n')
412 branchmap.updatecache(repo.filtered('served'))
412 branchmap.updatecache(repo.filtered('served'))
413
413
414 def runhooks():
414 def runhooks():
415 # These hooks run when the lock releases, not when the
415 # These hooks run when the lock releases, not when the
416 # transaction closes. So it's possible for the changelog
416 # transaction closes. So it's possible for the changelog
417 # to have changed since we last saw it.
417 # to have changed since we last saw it.
418 if clstart >= len(repo):
418 if clstart >= len(repo):
419 return
419 return
420
420
421 repo.hook("changegroup", **hookargs)
421 repo.hook("changegroup", **hookargs)
422
422
423 for n in added:
423 for n in added:
424 args = hookargs.copy()
424 args = hookargs.copy()
425 args['node'] = hex(n)
425 args['node'] = hex(n)
426 del args['node_last']
426 del args['node_last']
427 repo.hook("incoming", **args)
427 repo.hook("incoming", **args)
428
428
429 newheads = [h for h in repo.heads()
429 newheads = [h for h in repo.heads()
430 if h not in oldheads]
430 if h not in oldheads]
431 repo.ui.log("incoming",
431 repo.ui.log("incoming",
432 "%s incoming changes - new heads: %s\n",
432 "%s incoming changes - new heads: %s\n",
433 len(added),
433 len(added),
434 ', '.join([hex(c[:6]) for c in newheads]))
434 ', '.join([hex(c[:6]) for c in newheads]))
435
435
436 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
436 tr.addpostclose('changegroup-runhooks-%020i' % clstart,
437 lambda tr: repo._afterlock(runhooks))
437 lambda tr: repo._afterlock(runhooks))
438 finally:
438 finally:
439 repo.ui.flush()
439 repo.ui.flush()
440 # never return 0 here:
440 # never return 0 here:
441 if dh < 0:
441 if dh < 0:
442 return dh - 1
442 return dh - 1
443 else:
443 else:
444 return dh + 1
444 return dh + 1
445
445
446 class cg2unpacker(cg1unpacker):
446 class cg2unpacker(cg1unpacker):
447 """Unpacker for cg2 streams.
447 """Unpacker for cg2 streams.
448
448
449 cg2 streams add support for generaldelta, so the delta header
449 cg2 streams add support for generaldelta, so the delta header
450 format is slightly different. All other features about the data
450 format is slightly different. All other features about the data
451 remain the same.
451 remain the same.
452 """
452 """
453 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
453 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
454 deltaheadersize = struct.calcsize(deltaheader)
454 deltaheadersize = struct.calcsize(deltaheader)
455 version = '02'
455 version = '02'
456
456
457 def _deltaheader(self, headertuple, prevnode):
457 def _deltaheader(self, headertuple, prevnode):
458 node, p1, p2, deltabase, cs = headertuple
458 node, p1, p2, deltabase, cs = headertuple
459 flags = 0
459 flags = 0
460 return node, p1, p2, deltabase, cs, flags
460 return node, p1, p2, deltabase, cs, flags
461
461
462 class cg3unpacker(cg2unpacker):
462 class cg3unpacker(cg2unpacker):
463 """Unpacker for cg3 streams.
463 """Unpacker for cg3 streams.
464
464
465 cg3 streams add support for exchanging treemanifests and revlog
465 cg3 streams add support for exchanging treemanifests and revlog
466 flags. It adds the revlog flags to the delta header and an empty chunk
466 flags. It adds the revlog flags to the delta header and an empty chunk
467 separating manifests and files.
467 separating manifests and files.
468 """
468 """
469 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
469 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
470 deltaheadersize = struct.calcsize(deltaheader)
470 deltaheadersize = struct.calcsize(deltaheader)
471 version = '03'
471 version = '03'
472 _grouplistcount = 2 # One list of manifests and one list of files
472 _grouplistcount = 2 # One list of manifests and one list of files
473
473
474 def _deltaheader(self, headertuple, prevnode):
474 def _deltaheader(self, headertuple, prevnode):
475 node, p1, p2, deltabase, cs, flags = headertuple
475 node, p1, p2, deltabase, cs, flags = headertuple
476 return node, p1, p2, deltabase, cs, flags
476 return node, p1, p2, deltabase, cs, flags
477
477
478 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
478 def _unpackmanifests(self, repo, revmap, trp, prog, numchanges):
479 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
479 super(cg3unpacker, self)._unpackmanifests(repo, revmap, trp, prog,
480 numchanges)
480 numchanges)
481 for chunkdata in iter(self.filelogheader, {}):
481 for chunkdata in iter(self.filelogheader, {}):
482 # If we get here, there are directory manifests in the changegroup
482 # If we get here, there are directory manifests in the changegroup
483 d = chunkdata["filename"]
483 d = chunkdata["filename"]
484 repo.ui.debug("adding %s revisions\n" % d)
484 repo.ui.debug("adding %s revisions\n" % d)
485 dirlog = repo.manifestlog._revlog.dirlog(d)
485 dirlog = repo.manifestlog._revlog.dirlog(d)
486 if not dirlog.addgroup(self, revmap, trp):
486 if not dirlog.addgroup(self, revmap, trp):
487 raise error.Abort(_("received dir revlog group is empty"))
487 raise error.Abort(_("received dir revlog group is empty"))
488
488
489 class headerlessfixup(object):
489 class headerlessfixup(object):
490 def __init__(self, fh, h):
490 def __init__(self, fh, h):
491 self._h = h
491 self._h = h
492 self._fh = fh
492 self._fh = fh
493 def read(self, n):
493 def read(self, n):
494 if self._h:
494 if self._h:
495 d, self._h = self._h[:n], self._h[n:]
495 d, self._h = self._h[:n], self._h[n:]
496 if len(d) < n:
496 if len(d) < n:
497 d += readexactly(self._fh, n - len(d))
497 d += readexactly(self._fh, n - len(d))
498 return d
498 return d
499 return readexactly(self._fh, n)
499 return readexactly(self._fh, n)
500
500
501 class cg1packer(object):
501 class cg1packer(object):
502 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
502 deltaheader = _CHANGEGROUPV1_DELTA_HEADER
503 version = '01'
503 version = '01'
504 def __init__(self, repo, bundlecaps=None):
504 def __init__(self, repo, bundlecaps=None):
505 """Given a source repo, construct a bundler.
505 """Given a source repo, construct a bundler.
506
506
507 bundlecaps is optional and can be used to specify the set of
507 bundlecaps is optional and can be used to specify the set of
508 capabilities which can be used to build the bundle.
508 capabilities which can be used to build the bundle.
509 """
509 """
510 # Set of capabilities we can use to build the bundle.
510 # Set of capabilities we can use to build the bundle.
511 if bundlecaps is None:
511 if bundlecaps is None:
512 bundlecaps = set()
512 bundlecaps = set()
513 self._bundlecaps = bundlecaps
513 self._bundlecaps = bundlecaps
514 # experimental config: bundle.reorder
514 # experimental config: bundle.reorder
515 reorder = repo.ui.config('bundle', 'reorder', 'auto')
515 reorder = repo.ui.config('bundle', 'reorder', 'auto')
516 if reorder == 'auto':
516 if reorder == 'auto':
517 reorder = None
517 reorder = None
518 else:
518 else:
519 reorder = util.parsebool(reorder)
519 reorder = util.parsebool(reorder)
520 self._repo = repo
520 self._repo = repo
521 self._reorder = reorder
521 self._reorder = reorder
522 self._progress = repo.ui.progress
522 self._progress = repo.ui.progress
523 if self._repo.ui.verbose and not self._repo.ui.debugflag:
523 if self._repo.ui.verbose and not self._repo.ui.debugflag:
524 self._verbosenote = self._repo.ui.note
524 self._verbosenote = self._repo.ui.note
525 else:
525 else:
526 self._verbosenote = lambda s: None
526 self._verbosenote = lambda s: None
527
527
528 def close(self):
528 def close(self):
529 return closechunk()
529 return closechunk()
530
530
531 def fileheader(self, fname):
531 def fileheader(self, fname):
532 return chunkheader(len(fname)) + fname
532 return chunkheader(len(fname)) + fname
533
533
534 # Extracted both for clarity and for overriding in extensions.
534 # Extracted both for clarity and for overriding in extensions.
535 def _sortgroup(self, revlog, nodelist, lookup):
535 def _sortgroup(self, revlog, nodelist, lookup):
536 """Sort nodes for change group and turn them into revnums."""
536 """Sort nodes for change group and turn them into revnums."""
537 # for generaldelta revlogs, we linearize the revs; this will both be
537 # for generaldelta revlogs, we linearize the revs; this will both be
538 # much quicker and generate a much smaller bundle
538 # much quicker and generate a much smaller bundle
539 if (revlog._generaldelta and self._reorder is None) or self._reorder:
539 if (revlog._generaldelta and self._reorder is None) or self._reorder:
540 dag = dagutil.revlogdag(revlog)
540 dag = dagutil.revlogdag(revlog)
541 return dag.linearize(set(revlog.rev(n) for n in nodelist))
541 return dag.linearize(set(revlog.rev(n) for n in nodelist))
542 else:
542 else:
543 return sorted([revlog.rev(n) for n in nodelist])
543 return sorted([revlog.rev(n) for n in nodelist])
544
544
545 def group(self, nodelist, revlog, lookup, units=None):
545 def group(self, nodelist, revlog, lookup, units=None):
546 """Calculate a delta group, yielding a sequence of changegroup chunks
546 """Calculate a delta group, yielding a sequence of changegroup chunks
547 (strings).
547 (strings).
548
548
549 Given a list of changeset revs, return a set of deltas and
549 Given a list of changeset revs, return a set of deltas and
550 metadata corresponding to nodes. The first delta is
550 metadata corresponding to nodes. The first delta is
551 first parent(nodelist[0]) -> nodelist[0], the receiver is
551 first parent(nodelist[0]) -> nodelist[0], the receiver is
552 guaranteed to have this parent as it has all history before
552 guaranteed to have this parent as it has all history before
553 these changesets. In the case firstparent is nullrev the
553 these changesets. In the case firstparent is nullrev the
554 changegroup starts with a full revision.
554 changegroup starts with a full revision.
555
555
556 If units is not None, progress detail will be generated, units specifies
556 If units is not None, progress detail will be generated, units specifies
557 the type of revlog that is touched (changelog, manifest, etc.).
557 the type of revlog that is touched (changelog, manifest, etc.).
558 """
558 """
559 # if we don't have any revisions touched by these changesets, bail
559 # if we don't have any revisions touched by these changesets, bail
560 if len(nodelist) == 0:
560 if len(nodelist) == 0:
561 yield self.close()
561 yield self.close()
562 return
562 return
563
563
564 revs = self._sortgroup(revlog, nodelist, lookup)
564 revs = self._sortgroup(revlog, nodelist, lookup)
565
565
566 # add the parent of the first rev
566 # add the parent of the first rev
567 p = revlog.parentrevs(revs[0])[0]
567 p = revlog.parentrevs(revs[0])[0]
568 revs.insert(0, p)
568 revs.insert(0, p)
569
569
570 # build deltas
570 # build deltas
571 total = len(revs) - 1
571 total = len(revs) - 1
572 msgbundling = _('bundling')
572 msgbundling = _('bundling')
573 for r in xrange(len(revs) - 1):
573 for r in xrange(len(revs) - 1):
574 if units is not None:
574 if units is not None:
575 self._progress(msgbundling, r + 1, unit=units, total=total)
575 self._progress(msgbundling, r + 1, unit=units, total=total)
576 prev, curr = revs[r], revs[r + 1]
576 prev, curr = revs[r], revs[r + 1]
577 linknode = lookup(revlog.node(curr))
577 linknode = lookup(revlog.node(curr))
578 for c in self.revchunk(revlog, curr, prev, linknode):
578 for c in self.revchunk(revlog, curr, prev, linknode):
579 yield c
579 yield c
580
580
581 if units is not None:
581 if units is not None:
582 self._progress(msgbundling, None)
582 self._progress(msgbundling, None)
583 yield self.close()
583 yield self.close()
584
584
585 # filter any nodes that claim to be part of the known set
585 # filter any nodes that claim to be part of the known set
586 def prune(self, revlog, missing, commonrevs):
586 def prune(self, revlog, missing, commonrevs):
587 rr, rl = revlog.rev, revlog.linkrev
587 rr, rl = revlog.rev, revlog.linkrev
588 return [n for n in missing if rl(rr(n)) not in commonrevs]
588 return [n for n in missing if rl(rr(n)) not in commonrevs]
589
589
590 def _packmanifests(self, dir, mfnodes, lookuplinknode):
590 def _packmanifests(self, dir, mfnodes, lookuplinknode):
591 """Pack flat manifests into a changegroup stream."""
591 """Pack flat manifests into a changegroup stream."""
592 assert not dir
592 assert not dir
593 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
593 for chunk in self.group(mfnodes, self._repo.manifestlog._revlog,
594 lookuplinknode, units=_('manifests')):
594 lookuplinknode, units=_('manifests')):
595 yield chunk
595 yield chunk
596
596
597 def _manifestsdone(self):
597 def _manifestsdone(self):
598 return ''
598 return ''
599
599
600 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
600 def generate(self, commonrevs, clnodes, fastpathlinkrev, source):
601 '''yield a sequence of changegroup chunks (strings)'''
601 '''yield a sequence of changegroup chunks (strings)'''
602 repo = self._repo
602 repo = self._repo
603 cl = repo.changelog
603 cl = repo.changelog
604
604
605 clrevorder = {}
605 clrevorder = {}
606 mfs = {} # needed manifests
606 mfs = {} # needed manifests
607 fnodes = {} # needed file nodes
607 fnodes = {} # needed file nodes
608 changedfiles = set()
608 changedfiles = set()
609
609
610 # Callback for the changelog, used to collect changed files and manifest
610 # Callback for the changelog, used to collect changed files and manifest
611 # nodes.
611 # nodes.
612 # Returns the linkrev node (identity in the changelog case).
612 # Returns the linkrev node (identity in the changelog case).
613 def lookupcl(x):
613 def lookupcl(x):
614 c = cl.read(x)
614 c = cl.read(x)
615 clrevorder[x] = len(clrevorder)
615 clrevorder[x] = len(clrevorder)
616 n = c[0]
616 n = c[0]
617 # record the first changeset introducing this manifest version
617 # record the first changeset introducing this manifest version
618 mfs.setdefault(n, x)
618 mfs.setdefault(n, x)
619 # Record a complete list of potentially-changed files in
619 # Record a complete list of potentially-changed files in
620 # this manifest.
620 # this manifest.
621 changedfiles.update(c[3])
621 changedfiles.update(c[3])
622 return x
622 return x
623
623
624 self._verbosenote(_('uncompressed size of bundle content:\n'))
624 self._verbosenote(_('uncompressed size of bundle content:\n'))
625 size = 0
625 size = 0
626 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
626 for chunk in self.group(clnodes, cl, lookupcl, units=_('changesets')):
627 size += len(chunk)
627 size += len(chunk)
628 yield chunk
628 yield chunk
629 self._verbosenote(_('%8.i (changelog)\n') % size)
629 self._verbosenote(_('%8.i (changelog)\n') % size)
630
630
631 # We need to make sure that the linkrev in the changegroup refers to
631 # We need to make sure that the linkrev in the changegroup refers to
632 # the first changeset that introduced the manifest or file revision.
632 # the first changeset that introduced the manifest or file revision.
633 # The fastpath is usually safer than the slowpath, because the filelogs
633 # The fastpath is usually safer than the slowpath, because the filelogs
634 # are walked in revlog order.
634 # are walked in revlog order.
635 #
635 #
636 # When taking the slowpath with reorder=None and the manifest revlog
636 # When taking the slowpath with reorder=None and the manifest revlog
637 # uses generaldelta, the manifest may be walked in the "wrong" order.
637 # uses generaldelta, the manifest may be walked in the "wrong" order.
638 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
638 # Without 'clrevorder', we would get an incorrect linkrev (see fix in
639 # cc0ff93d0c0c).
639 # cc0ff93d0c0c).
640 #
640 #
641 # When taking the fastpath, we are only vulnerable to reordering
641 # When taking the fastpath, we are only vulnerable to reordering
642 # of the changelog itself. The changelog never uses generaldelta, so
642 # of the changelog itself. The changelog never uses generaldelta, so
643 # it is only reordered when reorder=True. To handle this case, we
643 # it is only reordered when reorder=True. To handle this case, we
644 # simply take the slowpath, which already has the 'clrevorder' logic.
644 # simply take the slowpath, which already has the 'clrevorder' logic.
645 # This was also fixed in cc0ff93d0c0c.
645 # This was also fixed in cc0ff93d0c0c.
646 fastpathlinkrev = fastpathlinkrev and not self._reorder
646 fastpathlinkrev = fastpathlinkrev and not self._reorder
647 # Treemanifests don't work correctly with fastpathlinkrev
647 # Treemanifests don't work correctly with fastpathlinkrev
648 # either, because we don't discover which directory nodes to
648 # either, because we don't discover which directory nodes to
649 # send along with files. This could probably be fixed.
649 # send along with files. This could probably be fixed.
650 fastpathlinkrev = fastpathlinkrev and (
650 fastpathlinkrev = fastpathlinkrev and (
651 'treemanifest' not in repo.requirements)
651 'treemanifest' not in repo.requirements)
652
652
653 for chunk in self.generatemanifests(commonrevs, clrevorder,
653 for chunk in self.generatemanifests(commonrevs, clrevorder,
654 fastpathlinkrev, mfs, fnodes):
654 fastpathlinkrev, mfs, fnodes):
655 yield chunk
655 yield chunk
656 mfs.clear()
656 mfs.clear()
657 clrevs = set(cl.rev(x) for x in clnodes)
657 clrevs = set(cl.rev(x) for x in clnodes)
658
658
659 if not fastpathlinkrev:
659 if not fastpathlinkrev:
660 def linknodes(unused, fname):
660 def linknodes(unused, fname):
661 return fnodes.get(fname, {})
661 return fnodes.get(fname, {})
662 else:
662 else:
663 cln = cl.node
663 cln = cl.node
664 def linknodes(filerevlog, fname):
664 def linknodes(filerevlog, fname):
665 llr = filerevlog.linkrev
665 llr = filerevlog.linkrev
666 fln = filerevlog.node
666 fln = filerevlog.node
667 revs = ((r, llr(r)) for r in filerevlog)
667 revs = ((r, llr(r)) for r in filerevlog)
668 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
668 return dict((fln(r), cln(lr)) for r, lr in revs if lr in clrevs)
669
669
670 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
670 for chunk in self.generatefiles(changedfiles, linknodes, commonrevs,
671 source):
671 source):
672 yield chunk
672 yield chunk
673
673
674 yield self.close()
674 yield self.close()
675
675
676 if clnodes:
676 if clnodes:
677 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
677 repo.hook('outgoing', node=hex(clnodes[0]), source=source)
678
678
679 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
679 def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
680 fnodes):
680 fnodes):
681 repo = self._repo
681 repo = self._repo
682 mfl = repo.manifestlog
682 mfl = repo.manifestlog
683 dirlog = mfl._revlog.dirlog
683 dirlog = mfl._revlog.dirlog
684 tmfnodes = {'': mfs}
684 tmfnodes = {'': mfs}
685
685
686 # Callback for the manifest, used to collect linkrevs for filelog
686 # Callback for the manifest, used to collect linkrevs for filelog
687 # revisions.
687 # revisions.
688 # Returns the linkrev node (collected in lookupcl).
688 # Returns the linkrev node (collected in lookupcl).
689 def makelookupmflinknode(dir):
689 def makelookupmflinknode(dir):
690 if fastpathlinkrev:
690 if fastpathlinkrev:
691 assert not dir
691 assert not dir
692 return mfs.__getitem__
692 return mfs.__getitem__
693
693
694 def lookupmflinknode(x):
694 def lookupmflinknode(x):
695 """Callback for looking up the linknode for manifests.
695 """Callback for looking up the linknode for manifests.
696
696
697 Returns the linkrev node for the specified manifest.
697 Returns the linkrev node for the specified manifest.
698
698
699 SIDE EFFECT:
699 SIDE EFFECT:
700
700
701 1) fclnodes gets populated with the list of relevant
701 1) fclnodes gets populated with the list of relevant
702 file nodes if we're not using fastpathlinkrev
702 file nodes if we're not using fastpathlinkrev
703 2) When treemanifests are in use, collects treemanifest nodes
703 2) When treemanifests are in use, collects treemanifest nodes
704 to send
704 to send
705
705
706 Note that this means manifests must be completely sent to
706 Note that this means manifests must be completely sent to
707 the client before you can trust the list of files and
707 the client before you can trust the list of files and
708 treemanifests to send.
708 treemanifests to send.
709 """
709 """
710 clnode = tmfnodes[dir][x]
710 clnode = tmfnodes[dir][x]
711 mdata = mfl.get(dir, x).readfast(shallow=True)
711 mdata = mfl.get(dir, x).readfast(shallow=True)
712 for p, n, fl in mdata.iterentries():
712 for p, n, fl in mdata.iterentries():
713 if fl == 't': # subdirectory manifest
713 if fl == 't': # subdirectory manifest
714 subdir = dir + p + '/'
714 subdir = dir + p + '/'
715 tmfclnodes = tmfnodes.setdefault(subdir, {})
715 tmfclnodes = tmfnodes.setdefault(subdir, {})
716 tmfclnode = tmfclnodes.setdefault(n, clnode)
716 tmfclnode = tmfclnodes.setdefault(n, clnode)
717 if clrevorder[clnode] < clrevorder[tmfclnode]:
717 if clrevorder[clnode] < clrevorder[tmfclnode]:
718 tmfclnodes[n] = clnode
718 tmfclnodes[n] = clnode
719 else:
719 else:
720 f = dir + p
720 f = dir + p
721 fclnodes = fnodes.setdefault(f, {})
721 fclnodes = fnodes.setdefault(f, {})
722 fclnode = fclnodes.setdefault(n, clnode)
722 fclnode = fclnodes.setdefault(n, clnode)
723 if clrevorder[clnode] < clrevorder[fclnode]:
723 if clrevorder[clnode] < clrevorder[fclnode]:
724 fclnodes[n] = clnode
724 fclnodes[n] = clnode
725 return clnode
725 return clnode
726 return lookupmflinknode
726 return lookupmflinknode
727
727
728 size = 0
728 size = 0
729 while tmfnodes:
729 while tmfnodes:
730 dir = min(tmfnodes)
730 dir = min(tmfnodes)
731 nodes = tmfnodes[dir]
731 nodes = tmfnodes[dir]
732 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
732 prunednodes = self.prune(dirlog(dir), nodes, commonrevs)
733 if not dir or prunednodes:
733 if not dir or prunednodes:
734 for x in self._packmanifests(dir, prunednodes,
734 for x in self._packmanifests(dir, prunednodes,
735 makelookupmflinknode(dir)):
735 makelookupmflinknode(dir)):
736 size += len(x)
736 size += len(x)
737 yield x
737 yield x
738 del tmfnodes[dir]
738 del tmfnodes[dir]
739 self._verbosenote(_('%8.i (manifests)\n') % size)
739 self._verbosenote(_('%8.i (manifests)\n') % size)
740 yield self._manifestsdone()
740 yield self._manifestsdone()
741
741
742 # The 'source' parameter is useful for extensions
742 # The 'source' parameter is useful for extensions
743 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
743 def generatefiles(self, changedfiles, linknodes, commonrevs, source):
744 repo = self._repo
744 repo = self._repo
745 progress = self._progress
745 progress = self._progress
746 msgbundling = _('bundling')
746 msgbundling = _('bundling')
747
747
748 total = len(changedfiles)
748 total = len(changedfiles)
749 # for progress output
749 # for progress output
750 msgfiles = _('files')
750 msgfiles = _('files')
751 for i, fname in enumerate(sorted(changedfiles)):
751 for i, fname in enumerate(sorted(changedfiles)):
752 filerevlog = repo.file(fname)
752 filerevlog = repo.file(fname)
753 if not filerevlog:
753 if not filerevlog:
754 raise error.Abort(_("empty or missing revlog for %s") % fname)
754 raise error.Abort(_("empty or missing revlog for %s") % fname)
755
755
756 linkrevnodes = linknodes(filerevlog, fname)
756 linkrevnodes = linknodes(filerevlog, fname)
757 # Lookup for filenodes, we collected the linkrev nodes above in the
757 # Lookup for filenodes, we collected the linkrev nodes above in the
758 # fastpath case and with lookupmf in the slowpath case.
758 # fastpath case and with lookupmf in the slowpath case.
759 def lookupfilelog(x):
759 def lookupfilelog(x):
760 return linkrevnodes[x]
760 return linkrevnodes[x]
761
761
762 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
762 filenodes = self.prune(filerevlog, linkrevnodes, commonrevs)
763 if filenodes:
763 if filenodes:
764 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
764 progress(msgbundling, i + 1, item=fname, unit=msgfiles,
765 total=total)
765 total=total)
766 h = self.fileheader(fname)
766 h = self.fileheader(fname)
767 size = len(h)
767 size = len(h)
768 yield h
768 yield h
769 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
769 for chunk in self.group(filenodes, filerevlog, lookupfilelog):
770 size += len(chunk)
770 size += len(chunk)
771 yield chunk
771 yield chunk
772 self._verbosenote(_('%8.i %s\n') % (size, fname))
772 self._verbosenote(_('%8.i %s\n') % (size, fname))
773 progress(msgbundling, None)
773 progress(msgbundling, None)
774
774
775 def deltaparent(self, revlog, rev, p1, p2, prev):
775 def deltaparent(self, revlog, rev, p1, p2, prev):
776 return prev
776 return prev
777
777
778 def revchunk(self, revlog, rev, prev, linknode):
778 def revchunk(self, revlog, rev, prev, linknode):
779 node = revlog.node(rev)
779 node = revlog.node(rev)
780 p1, p2 = revlog.parentrevs(rev)
780 p1, p2 = revlog.parentrevs(rev)
781 base = self.deltaparent(revlog, rev, p1, p2, prev)
781 base = self.deltaparent(revlog, rev, p1, p2, prev)
782
782
783 prefix = ''
783 prefix = ''
784 if revlog.iscensored(base) or revlog.iscensored(rev):
784 if revlog.iscensored(base) or revlog.iscensored(rev):
785 try:
785 try:
786 delta = revlog.revision(node)
786 delta = revlog.revision(node, raw=True)
787 except error.CensoredNodeError as e:
787 except error.CensoredNodeError as e:
788 delta = e.tombstone
788 delta = e.tombstone
789 if base == nullrev:
789 if base == nullrev:
790 prefix = mdiff.trivialdiffheader(len(delta))
790 prefix = mdiff.trivialdiffheader(len(delta))
791 else:
791 else:
792 baselen = revlog.rawsize(base)
792 baselen = revlog.rawsize(base)
793 prefix = mdiff.replacediffheader(baselen, len(delta))
793 prefix = mdiff.replacediffheader(baselen, len(delta))
794 elif base == nullrev:
794 elif base == nullrev:
795 delta = revlog.revision(node)
795 delta = revlog.revision(node, raw=True)
796 prefix = mdiff.trivialdiffheader(len(delta))
796 prefix = mdiff.trivialdiffheader(len(delta))
797 else:
797 else:
798 delta = revlog.revdiff(base, rev)
798 delta = revlog.revdiff(base, rev)
799 p1n, p2n = revlog.parents(node)
799 p1n, p2n = revlog.parents(node)
800 basenode = revlog.node(base)
800 basenode = revlog.node(base)
801 flags = revlog.flags(rev)
801 flags = revlog.flags(rev)
802 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
802 meta = self.builddeltaheader(node, p1n, p2n, basenode, linknode, flags)
803 meta += prefix
803 meta += prefix
804 l = len(meta) + len(delta)
804 l = len(meta) + len(delta)
805 yield chunkheader(l)
805 yield chunkheader(l)
806 yield meta
806 yield meta
807 yield delta
807 yield delta
808 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
808 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
809 # do nothing with basenode, it is implicitly the previous one in HG10
809 # do nothing with basenode, it is implicitly the previous one in HG10
810 # do nothing with flags, it is implicitly 0 for cg1 and cg2
810 # do nothing with flags, it is implicitly 0 for cg1 and cg2
811 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
811 return struct.pack(self.deltaheader, node, p1n, p2n, linknode)
812
812
813 class cg2packer(cg1packer):
813 class cg2packer(cg1packer):
814 version = '02'
814 version = '02'
815 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
815 deltaheader = _CHANGEGROUPV2_DELTA_HEADER
816
816
817 def __init__(self, repo, bundlecaps=None):
817 def __init__(self, repo, bundlecaps=None):
818 super(cg2packer, self).__init__(repo, bundlecaps)
818 super(cg2packer, self).__init__(repo, bundlecaps)
819 if self._reorder is None:
819 if self._reorder is None:
820 # Since generaldelta is directly supported by cg2, reordering
820 # Since generaldelta is directly supported by cg2, reordering
821 # generally doesn't help, so we disable it by default (treating
821 # generally doesn't help, so we disable it by default (treating
822 # bundle.reorder=auto just like bundle.reorder=False).
822 # bundle.reorder=auto just like bundle.reorder=False).
823 self._reorder = False
823 self._reorder = False
824
824
825 def deltaparent(self, revlog, rev, p1, p2, prev):
825 def deltaparent(self, revlog, rev, p1, p2, prev):
826 dp = revlog.deltaparent(rev)
826 dp = revlog.deltaparent(rev)
827 if dp == nullrev and revlog.storedeltachains:
827 if dp == nullrev and revlog.storedeltachains:
828 # Avoid sending full revisions when delta parent is null. Pick prev
828 # Avoid sending full revisions when delta parent is null. Pick prev
829 # in that case. It's tempting to pick p1 in this case, as p1 will
829 # in that case. It's tempting to pick p1 in this case, as p1 will
830 # be smaller in the common case. However, computing a delta against
830 # be smaller in the common case. However, computing a delta against
831 # p1 may require resolving the raw text of p1, which could be
831 # p1 may require resolving the raw text of p1, which could be
832 # expensive. The revlog caches should have prev cached, meaning
832 # expensive. The revlog caches should have prev cached, meaning
833 # less CPU for changegroup generation. There is likely room to add
833 # less CPU for changegroup generation. There is likely room to add
834 # a flag and/or config option to control this behavior.
834 # a flag and/or config option to control this behavior.
835 return prev
835 return prev
836 elif dp == nullrev:
836 elif dp == nullrev:
837 # revlog is configured to use full snapshot for a reason,
837 # revlog is configured to use full snapshot for a reason,
838 # stick to full snapshot.
838 # stick to full snapshot.
839 return nullrev
839 return nullrev
840 elif dp not in (p1, p2, prev):
840 elif dp not in (p1, p2, prev):
841 # Pick prev when we can't be sure remote has the base revision.
841 # Pick prev when we can't be sure remote has the base revision.
842 return prev
842 return prev
843 else:
843 else:
844 return dp
844 return dp
845
845
846 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
846 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
847 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
847 # Do nothing with flags, it is implicitly 0 in cg1 and cg2
848 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
848 return struct.pack(self.deltaheader, node, p1n, p2n, basenode, linknode)
849
849
850 class cg3packer(cg2packer):
850 class cg3packer(cg2packer):
851 version = '03'
851 version = '03'
852 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
852 deltaheader = _CHANGEGROUPV3_DELTA_HEADER
853
853
854 def _packmanifests(self, dir, mfnodes, lookuplinknode):
854 def _packmanifests(self, dir, mfnodes, lookuplinknode):
855 if dir:
855 if dir:
856 yield self.fileheader(dir)
856 yield self.fileheader(dir)
857
857
858 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
858 dirlog = self._repo.manifestlog._revlog.dirlog(dir)
859 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
859 for chunk in self.group(mfnodes, dirlog, lookuplinknode,
860 units=_('manifests')):
860 units=_('manifests')):
861 yield chunk
861 yield chunk
862
862
863 def _manifestsdone(self):
863 def _manifestsdone(self):
864 return self.close()
864 return self.close()
865
865
866 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
866 def builddeltaheader(self, node, p1n, p2n, basenode, linknode, flags):
867 return struct.pack(
867 return struct.pack(
868 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
868 self.deltaheader, node, p1n, p2n, basenode, linknode, flags)
869
869
870 _packermap = {'01': (cg1packer, cg1unpacker),
870 _packermap = {'01': (cg1packer, cg1unpacker),
871 # cg2 adds support for exchanging generaldelta
871 # cg2 adds support for exchanging generaldelta
872 '02': (cg2packer, cg2unpacker),
872 '02': (cg2packer, cg2unpacker),
873 # cg3 adds support for exchanging revlog flags and treemanifests
873 # cg3 adds support for exchanging revlog flags and treemanifests
874 '03': (cg3packer, cg3unpacker),
874 '03': (cg3packer, cg3unpacker),
875 }
875 }
876
876
877 def allsupportedversions(repo):
877 def allsupportedversions(repo):
878 versions = set(_packermap.keys())
878 versions = set(_packermap.keys())
879 if not (repo.ui.configbool('experimental', 'changegroup3') or
879 if not (repo.ui.configbool('experimental', 'changegroup3') or
880 repo.ui.configbool('experimental', 'treemanifest') or
880 repo.ui.configbool('experimental', 'treemanifest') or
881 'treemanifest' in repo.requirements):
881 'treemanifest' in repo.requirements):
882 versions.discard('03')
882 versions.discard('03')
883 return versions
883 return versions
884
884
885 # Changegroup versions that can be applied to the repo
885 # Changegroup versions that can be applied to the repo
886 def supportedincomingversions(repo):
886 def supportedincomingversions(repo):
887 return allsupportedversions(repo)
887 return allsupportedversions(repo)
888
888
889 # Changegroup versions that can be created from the repo
889 # Changegroup versions that can be created from the repo
890 def supportedoutgoingversions(repo):
890 def supportedoutgoingversions(repo):
891 versions = allsupportedversions(repo)
891 versions = allsupportedversions(repo)
892 if 'treemanifest' in repo.requirements:
892 if 'treemanifest' in repo.requirements:
893 # Versions 01 and 02 support only flat manifests and it's just too
893 # Versions 01 and 02 support only flat manifests and it's just too
894 # expensive to convert between the flat manifest and tree manifest on
894 # expensive to convert between the flat manifest and tree manifest on
895 # the fly. Since tree manifests are hashed differently, all of history
895 # the fly. Since tree manifests are hashed differently, all of history
896 # would have to be converted. Instead, we simply don't even pretend to
896 # would have to be converted. Instead, we simply don't even pretend to
897 # support versions 01 and 02.
897 # support versions 01 and 02.
898 versions.discard('01')
898 versions.discard('01')
899 versions.discard('02')
899 versions.discard('02')
900 return versions
900 return versions
901
901
902 def safeversion(repo):
902 def safeversion(repo):
903 # Finds the smallest version that it's safe to assume clients of the repo
903 # Finds the smallest version that it's safe to assume clients of the repo
904 # will support. For example, all hg versions that support generaldelta also
904 # will support. For example, all hg versions that support generaldelta also
905 # support changegroup 02.
905 # support changegroup 02.
906 versions = supportedoutgoingversions(repo)
906 versions = supportedoutgoingversions(repo)
907 if 'generaldelta' in repo.requirements:
907 if 'generaldelta' in repo.requirements:
908 versions.discard('01')
908 versions.discard('01')
909 assert versions
909 assert versions
910 return min(versions)
910 return min(versions)
911
911
912 def getbundler(version, repo, bundlecaps=None):
912 def getbundler(version, repo, bundlecaps=None):
913 assert version in supportedoutgoingversions(repo)
913 assert version in supportedoutgoingversions(repo)
914 return _packermap[version][0](repo, bundlecaps)
914 return _packermap[version][0](repo, bundlecaps)
915
915
916 def getunbundler(version, fh, alg, extras=None):
916 def getunbundler(version, fh, alg, extras=None):
917 return _packermap[version][1](fh, alg, extras=extras)
917 return _packermap[version][1](fh, alg, extras=extras)
918
918
919 def _changegroupinfo(repo, nodes, source):
919 def _changegroupinfo(repo, nodes, source):
920 if repo.ui.verbose or source == 'bundle':
920 if repo.ui.verbose or source == 'bundle':
921 repo.ui.status(_("%d changesets found\n") % len(nodes))
921 repo.ui.status(_("%d changesets found\n") % len(nodes))
922 if repo.ui.debugflag:
922 if repo.ui.debugflag:
923 repo.ui.debug("list of changesets:\n")
923 repo.ui.debug("list of changesets:\n")
924 for node in nodes:
924 for node in nodes:
925 repo.ui.debug("%s\n" % hex(node))
925 repo.ui.debug("%s\n" % hex(node))
926
926
927 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
927 def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
928 repo = repo.unfiltered()
928 repo = repo.unfiltered()
929 commonrevs = outgoing.common
929 commonrevs = outgoing.common
930 csets = outgoing.missing
930 csets = outgoing.missing
931 heads = outgoing.missingheads
931 heads = outgoing.missingheads
932 # We go through the fast path if we get told to, or if all (unfiltered
932 # We go through the fast path if we get told to, or if all (unfiltered
933 # heads have been requested (since we then know there all linkrevs will
933 # heads have been requested (since we then know there all linkrevs will
934 # be pulled by the client).
934 # be pulled by the client).
935 heads.sort()
935 heads.sort()
936 fastpathlinkrev = fastpath or (
936 fastpathlinkrev = fastpath or (
937 repo.filtername is None and heads == sorted(repo.heads()))
937 repo.filtername is None and heads == sorted(repo.heads()))
938
938
939 repo.hook('preoutgoing', throw=True, source=source)
939 repo.hook('preoutgoing', throw=True, source=source)
940 _changegroupinfo(repo, csets, source)
940 _changegroupinfo(repo, csets, source)
941 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
941 return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
942
942
943 def getsubset(repo, outgoing, bundler, source, fastpath=False):
943 def getsubset(repo, outgoing, bundler, source, fastpath=False):
944 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
944 gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
945 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
945 return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
946 {'clcount': len(outgoing.missing)})
946 {'clcount': len(outgoing.missing)})
947
947
948 def changegroupsubset(repo, roots, heads, source, version='01'):
948 def changegroupsubset(repo, roots, heads, source, version='01'):
949 """Compute a changegroup consisting of all the nodes that are
949 """Compute a changegroup consisting of all the nodes that are
950 descendants of any of the roots and ancestors of any of the heads.
950 descendants of any of the roots and ancestors of any of the heads.
951 Return a chunkbuffer object whose read() method will return
951 Return a chunkbuffer object whose read() method will return
952 successive changegroup chunks.
952 successive changegroup chunks.
953
953
954 It is fairly complex as determining which filenodes and which
954 It is fairly complex as determining which filenodes and which
955 manifest nodes need to be included for the changeset to be complete
955 manifest nodes need to be included for the changeset to be complete
956 is non-trivial.
956 is non-trivial.
957
957
958 Another wrinkle is doing the reverse, figuring out which changeset in
958 Another wrinkle is doing the reverse, figuring out which changeset in
959 the changegroup a particular filenode or manifestnode belongs to.
959 the changegroup a particular filenode or manifestnode belongs to.
960 """
960 """
961 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
961 outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
962 bundler = getbundler(version, repo)
962 bundler = getbundler(version, repo)
963 return getsubset(repo, outgoing, bundler, source)
963 return getsubset(repo, outgoing, bundler, source)
964
964
965 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
965 def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
966 version='01'):
966 version='01'):
967 """Like getbundle, but taking a discovery.outgoing as an argument.
967 """Like getbundle, but taking a discovery.outgoing as an argument.
968
968
969 This is only implemented for local repos and reuses potentially
969 This is only implemented for local repos and reuses potentially
970 precomputed sets in outgoing. Returns a raw changegroup generator."""
970 precomputed sets in outgoing. Returns a raw changegroup generator."""
971 if not outgoing.missing:
971 if not outgoing.missing:
972 return None
972 return None
973 bundler = getbundler(version, repo, bundlecaps)
973 bundler = getbundler(version, repo, bundlecaps)
974 return getsubsetraw(repo, outgoing, bundler, source)
974 return getsubsetraw(repo, outgoing, bundler, source)
975
975
976 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
976 def getlocalchangegroup(repo, source, outgoing, bundlecaps=None,
977 version='01'):
977 version='01'):
978 """Like getbundle, but taking a discovery.outgoing as an argument.
978 """Like getbundle, but taking a discovery.outgoing as an argument.
979
979
980 This is only implemented for local repos and reuses potentially
980 This is only implemented for local repos and reuses potentially
981 precomputed sets in outgoing."""
981 precomputed sets in outgoing."""
982 if not outgoing.missing:
982 if not outgoing.missing:
983 return None
983 return None
984 bundler = getbundler(version, repo, bundlecaps)
984 bundler = getbundler(version, repo, bundlecaps)
985 return getsubset(repo, outgoing, bundler, source)
985 return getsubset(repo, outgoing, bundler, source)
986
986
987 def getchangegroup(repo, source, outgoing, bundlecaps=None,
987 def getchangegroup(repo, source, outgoing, bundlecaps=None,
988 version='01'):
988 version='01'):
989 """Like changegroupsubset, but returns the set difference between the
989 """Like changegroupsubset, but returns the set difference between the
990 ancestors of heads and the ancestors common.
990 ancestors of heads and the ancestors common.
991
991
992 If heads is None, use the local heads. If common is None, use [nullid].
992 If heads is None, use the local heads. If common is None, use [nullid].
993
993
994 The nodes in common might not all be known locally due to the way the
994 The nodes in common might not all be known locally due to the way the
995 current discovery protocol works.
995 current discovery protocol works.
996 """
996 """
997 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
997 return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps,
998 version=version)
998 version=version)
999
999
1000 def changegroup(repo, basenodes, source):
1000 def changegroup(repo, basenodes, source):
1001 # to avoid a race we use changegroupsubset() (issue1320)
1001 # to avoid a race we use changegroupsubset() (issue1320)
1002 return changegroupsubset(repo, basenodes, repo.heads(), source)
1002 return changegroupsubset(repo, basenodes, repo.heads(), source)
1003
1003
1004 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1004 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
1005 revisions = 0
1005 revisions = 0
1006 files = 0
1006 files = 0
1007 for chunkdata in iter(source.filelogheader, {}):
1007 for chunkdata in iter(source.filelogheader, {}):
1008 files += 1
1008 files += 1
1009 f = chunkdata["filename"]
1009 f = chunkdata["filename"]
1010 repo.ui.debug("adding %s revisions\n" % f)
1010 repo.ui.debug("adding %s revisions\n" % f)
1011 repo.ui.progress(_('files'), files, unit=_('files'),
1011 repo.ui.progress(_('files'), files, unit=_('files'),
1012 total=expectedfiles)
1012 total=expectedfiles)
1013 fl = repo.file(f)
1013 fl = repo.file(f)
1014 o = len(fl)
1014 o = len(fl)
1015 try:
1015 try:
1016 if not fl.addgroup(source, revmap, trp):
1016 if not fl.addgroup(source, revmap, trp):
1017 raise error.Abort(_("received file revlog group is empty"))
1017 raise error.Abort(_("received file revlog group is empty"))
1018 except error.CensoredBaseError as e:
1018 except error.CensoredBaseError as e:
1019 raise error.Abort(_("received delta base is censored: %s") % e)
1019 raise error.Abort(_("received delta base is censored: %s") % e)
1020 revisions += len(fl) - o
1020 revisions += len(fl) - o
1021 if f in needfiles:
1021 if f in needfiles:
1022 needs = needfiles[f]
1022 needs = needfiles[f]
1023 for new in xrange(o, len(fl)):
1023 for new in xrange(o, len(fl)):
1024 n = fl.node(new)
1024 n = fl.node(new)
1025 if n in needs:
1025 if n in needs:
1026 needs.remove(n)
1026 needs.remove(n)
1027 else:
1027 else:
1028 raise error.Abort(
1028 raise error.Abort(
1029 _("received spurious file revlog entry"))
1029 _("received spurious file revlog entry"))
1030 if not needs:
1030 if not needs:
1031 del needfiles[f]
1031 del needfiles[f]
1032 repo.ui.progress(_('files'), None)
1032 repo.ui.progress(_('files'), None)
1033
1033
1034 for f, needs in needfiles.iteritems():
1034 for f, needs in needfiles.iteritems():
1035 fl = repo.file(f)
1035 fl = repo.file(f)
1036 for n in needs:
1036 for n in needs:
1037 try:
1037 try:
1038 fl.rev(n)
1038 fl.rev(n)
1039 except error.LookupError:
1039 except error.LookupError:
1040 raise error.Abort(
1040 raise error.Abort(
1041 _('missing file data for %s:%s - run hg verify') %
1041 _('missing file data for %s:%s - run hg verify') %
1042 (f, hex(n)))
1042 (f, hex(n)))
1043
1043
1044 return revisions, files
1044 return revisions, files
@@ -1,2111 +1,2114 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import os
11 import os
12 import re
12 import re
13 import stat
13 import stat
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 newnodeid,
21 newnodeid,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 short,
24 short,
25 wdirid,
25 wdirid,
26 )
26 )
27 from . import (
27 from . import (
28 encoding,
28 encoding,
29 error,
29 error,
30 fileset,
30 fileset,
31 match as matchmod,
31 match as matchmod,
32 mdiff,
32 mdiff,
33 obsolete as obsmod,
33 obsolete as obsmod,
34 patch,
34 patch,
35 phases,
35 phases,
36 repoview,
36 repoview,
37 revlog,
37 revlog,
38 scmutil,
38 scmutil,
39 subrepo,
39 subrepo,
40 util,
40 util,
41 )
41 )
42
42
43 propertycache = util.propertycache
43 propertycache = util.propertycache
44
44
45 nonascii = re.compile(r'[^\x21-\x7f]').search
45 nonascii = re.compile(r'[^\x21-\x7f]').search
46
46
47 class basectx(object):
47 class basectx(object):
48 """A basectx object represents the common logic for its children:
48 """A basectx object represents the common logic for its children:
49 changectx: read-only context that is already present in the repo,
49 changectx: read-only context that is already present in the repo,
50 workingctx: a context that represents the working directory and can
50 workingctx: a context that represents the working directory and can
51 be committed,
51 be committed,
52 memctx: a context that represents changes in-memory and can also
52 memctx: a context that represents changes in-memory and can also
53 be committed."""
53 be committed."""
54 def __new__(cls, repo, changeid='', *args, **kwargs):
54 def __new__(cls, repo, changeid='', *args, **kwargs):
55 if isinstance(changeid, basectx):
55 if isinstance(changeid, basectx):
56 return changeid
56 return changeid
57
57
58 o = super(basectx, cls).__new__(cls)
58 o = super(basectx, cls).__new__(cls)
59
59
60 o._repo = repo
60 o._repo = repo
61 o._rev = nullrev
61 o._rev = nullrev
62 o._node = nullid
62 o._node = nullid
63
63
64 return o
64 return o
65
65
66 def __str__(self):
66 def __str__(self):
67 return short(self.node())
67 return short(self.node())
68
68
69 def __int__(self):
69 def __int__(self):
70 return self.rev()
70 return self.rev()
71
71
72 def __repr__(self):
72 def __repr__(self):
73 return "<%s %s>" % (type(self).__name__, str(self))
73 return "<%s %s>" % (type(self).__name__, str(self))
74
74
75 def __eq__(self, other):
75 def __eq__(self, other):
76 try:
76 try:
77 return type(self) == type(other) and self._rev == other._rev
77 return type(self) == type(other) and self._rev == other._rev
78 except AttributeError:
78 except AttributeError:
79 return False
79 return False
80
80
81 def __ne__(self, other):
81 def __ne__(self, other):
82 return not (self == other)
82 return not (self == other)
83
83
84 def __contains__(self, key):
84 def __contains__(self, key):
85 return key in self._manifest
85 return key in self._manifest
86
86
87 def __getitem__(self, key):
87 def __getitem__(self, key):
88 return self.filectx(key)
88 return self.filectx(key)
89
89
90 def __iter__(self):
90 def __iter__(self):
91 return iter(self._manifest)
91 return iter(self._manifest)
92
92
93 def _manifestmatches(self, match, s):
93 def _manifestmatches(self, match, s):
94 """generate a new manifest filtered by the match argument
94 """generate a new manifest filtered by the match argument
95
95
96 This method is for internal use only and mainly exists to provide an
96 This method is for internal use only and mainly exists to provide an
97 object oriented way for other contexts to customize the manifest
97 object oriented way for other contexts to customize the manifest
98 generation.
98 generation.
99 """
99 """
100 return self.manifest().matches(match)
100 return self.manifest().matches(match)
101
101
102 def _matchstatus(self, other, match):
102 def _matchstatus(self, other, match):
103 """return match.always if match is none
103 """return match.always if match is none
104
104
105 This internal method provides a way for child objects to override the
105 This internal method provides a way for child objects to override the
106 match operator.
106 match operator.
107 """
107 """
108 return match or matchmod.always(self._repo.root, self._repo.getcwd())
108 return match or matchmod.always(self._repo.root, self._repo.getcwd())
109
109
110 def _buildstatus(self, other, s, match, listignored, listclean,
110 def _buildstatus(self, other, s, match, listignored, listclean,
111 listunknown):
111 listunknown):
112 """build a status with respect to another context"""
112 """build a status with respect to another context"""
113 # Load earliest manifest first for caching reasons. More specifically,
113 # Load earliest manifest first for caching reasons. More specifically,
114 # if you have revisions 1000 and 1001, 1001 is probably stored as a
114 # if you have revisions 1000 and 1001, 1001 is probably stored as a
115 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
115 # delta against 1000. Thus, if you read 1000 first, we'll reconstruct
116 # 1000 and cache it so that when you read 1001, we just need to apply a
116 # 1000 and cache it so that when you read 1001, we just need to apply a
117 # delta to what's in the cache. So that's one full reconstruction + one
117 # delta to what's in the cache. So that's one full reconstruction + one
118 # delta application.
118 # delta application.
119 if self.rev() is not None and self.rev() < other.rev():
119 if self.rev() is not None and self.rev() < other.rev():
120 self.manifest()
120 self.manifest()
121 mf1 = other._manifestmatches(match, s)
121 mf1 = other._manifestmatches(match, s)
122 mf2 = self._manifestmatches(match, s)
122 mf2 = self._manifestmatches(match, s)
123
123
124 modified, added = [], []
124 modified, added = [], []
125 removed = []
125 removed = []
126 clean = []
126 clean = []
127 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
127 deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
128 deletedset = set(deleted)
128 deletedset = set(deleted)
129 d = mf1.diff(mf2, clean=listclean)
129 d = mf1.diff(mf2, clean=listclean)
130 for fn, value in d.iteritems():
130 for fn, value in d.iteritems():
131 if fn in deletedset:
131 if fn in deletedset:
132 continue
132 continue
133 if value is None:
133 if value is None:
134 clean.append(fn)
134 clean.append(fn)
135 continue
135 continue
136 (node1, flag1), (node2, flag2) = value
136 (node1, flag1), (node2, flag2) = value
137 if node1 is None:
137 if node1 is None:
138 added.append(fn)
138 added.append(fn)
139 elif node2 is None:
139 elif node2 is None:
140 removed.append(fn)
140 removed.append(fn)
141 elif flag1 != flag2:
141 elif flag1 != flag2:
142 modified.append(fn)
142 modified.append(fn)
143 elif node2 != newnodeid:
143 elif node2 != newnodeid:
144 # When comparing files between two commits, we save time by
144 # When comparing files between two commits, we save time by
145 # not comparing the file contents when the nodeids differ.
145 # not comparing the file contents when the nodeids differ.
146 # Note that this means we incorrectly report a reverted change
146 # Note that this means we incorrectly report a reverted change
147 # to a file as a modification.
147 # to a file as a modification.
148 modified.append(fn)
148 modified.append(fn)
149 elif self[fn].cmp(other[fn]):
149 elif self[fn].cmp(other[fn]):
150 modified.append(fn)
150 modified.append(fn)
151 else:
151 else:
152 clean.append(fn)
152 clean.append(fn)
153
153
154 if removed:
154 if removed:
155 # need to filter files if they are already reported as removed
155 # need to filter files if they are already reported as removed
156 unknown = [fn for fn in unknown if fn not in mf1]
156 unknown = [fn for fn in unknown if fn not in mf1]
157 ignored = [fn for fn in ignored if fn not in mf1]
157 ignored = [fn for fn in ignored if fn not in mf1]
158 # if they're deleted, don't report them as removed
158 # if they're deleted, don't report them as removed
159 removed = [fn for fn in removed if fn not in deletedset]
159 removed = [fn for fn in removed if fn not in deletedset]
160
160
161 return scmutil.status(modified, added, removed, deleted, unknown,
161 return scmutil.status(modified, added, removed, deleted, unknown,
162 ignored, clean)
162 ignored, clean)
163
163
164 @propertycache
164 @propertycache
165 def substate(self):
165 def substate(self):
166 return subrepo.state(self, self._repo.ui)
166 return subrepo.state(self, self._repo.ui)
167
167
168 def subrev(self, subpath):
168 def subrev(self, subpath):
169 return self.substate[subpath][1]
169 return self.substate[subpath][1]
170
170
171 def rev(self):
171 def rev(self):
172 return self._rev
172 return self._rev
173 def node(self):
173 def node(self):
174 return self._node
174 return self._node
175 def hex(self):
175 def hex(self):
176 return hex(self.node())
176 return hex(self.node())
177 def manifest(self):
177 def manifest(self):
178 return self._manifest
178 return self._manifest
179 def manifestctx(self):
179 def manifestctx(self):
180 return self._manifestctx
180 return self._manifestctx
181 def repo(self):
181 def repo(self):
182 return self._repo
182 return self._repo
183 def phasestr(self):
183 def phasestr(self):
184 return phases.phasenames[self.phase()]
184 return phases.phasenames[self.phase()]
185 def mutable(self):
185 def mutable(self):
186 return self.phase() > phases.public
186 return self.phase() > phases.public
187
187
188 def getfileset(self, expr):
188 def getfileset(self, expr):
189 return fileset.getfileset(self, expr)
189 return fileset.getfileset(self, expr)
190
190
191 def obsolete(self):
191 def obsolete(self):
192 """True if the changeset is obsolete"""
192 """True if the changeset is obsolete"""
193 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
193 return self.rev() in obsmod.getrevs(self._repo, 'obsolete')
194
194
195 def extinct(self):
195 def extinct(self):
196 """True if the changeset is extinct"""
196 """True if the changeset is extinct"""
197 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
197 return self.rev() in obsmod.getrevs(self._repo, 'extinct')
198
198
199 def unstable(self):
199 def unstable(self):
200 """True if the changeset is not obsolete but it's ancestor are"""
200 """True if the changeset is not obsolete but it's ancestor are"""
201 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
201 return self.rev() in obsmod.getrevs(self._repo, 'unstable')
202
202
203 def bumped(self):
203 def bumped(self):
204 """True if the changeset try to be a successor of a public changeset
204 """True if the changeset try to be a successor of a public changeset
205
205
206 Only non-public and non-obsolete changesets may be bumped.
206 Only non-public and non-obsolete changesets may be bumped.
207 """
207 """
208 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
208 return self.rev() in obsmod.getrevs(self._repo, 'bumped')
209
209
210 def divergent(self):
210 def divergent(self):
211 """Is a successors of a changeset with multiple possible successors set
211 """Is a successors of a changeset with multiple possible successors set
212
212
213 Only non-public and non-obsolete changesets may be divergent.
213 Only non-public and non-obsolete changesets may be divergent.
214 """
214 """
215 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
215 return self.rev() in obsmod.getrevs(self._repo, 'divergent')
216
216
217 def troubled(self):
217 def troubled(self):
218 """True if the changeset is either unstable, bumped or divergent"""
218 """True if the changeset is either unstable, bumped or divergent"""
219 return self.unstable() or self.bumped() or self.divergent()
219 return self.unstable() or self.bumped() or self.divergent()
220
220
221 def troubles(self):
221 def troubles(self):
222 """return the list of troubles affecting this changesets.
222 """return the list of troubles affecting this changesets.
223
223
224 Troubles are returned as strings. possible values are:
224 Troubles are returned as strings. possible values are:
225 - unstable,
225 - unstable,
226 - bumped,
226 - bumped,
227 - divergent.
227 - divergent.
228 """
228 """
229 troubles = []
229 troubles = []
230 if self.unstable():
230 if self.unstable():
231 troubles.append('unstable')
231 troubles.append('unstable')
232 if self.bumped():
232 if self.bumped():
233 troubles.append('bumped')
233 troubles.append('bumped')
234 if self.divergent():
234 if self.divergent():
235 troubles.append('divergent')
235 troubles.append('divergent')
236 return troubles
236 return troubles
237
237
238 def parents(self):
238 def parents(self):
239 """return contexts for each parent changeset"""
239 """return contexts for each parent changeset"""
240 return self._parents
240 return self._parents
241
241
242 def p1(self):
242 def p1(self):
243 return self._parents[0]
243 return self._parents[0]
244
244
245 def p2(self):
245 def p2(self):
246 parents = self._parents
246 parents = self._parents
247 if len(parents) == 2:
247 if len(parents) == 2:
248 return parents[1]
248 return parents[1]
249 return changectx(self._repo, nullrev)
249 return changectx(self._repo, nullrev)
250
250
251 def _fileinfo(self, path):
251 def _fileinfo(self, path):
252 if '_manifest' in self.__dict__:
252 if '_manifest' in self.__dict__:
253 try:
253 try:
254 return self._manifest[path], self._manifest.flags(path)
254 return self._manifest[path], self._manifest.flags(path)
255 except KeyError:
255 except KeyError:
256 raise error.ManifestLookupError(self._node, path,
256 raise error.ManifestLookupError(self._node, path,
257 _('not found in manifest'))
257 _('not found in manifest'))
258 if '_manifestdelta' in self.__dict__ or path in self.files():
258 if '_manifestdelta' in self.__dict__ or path in self.files():
259 if path in self._manifestdelta:
259 if path in self._manifestdelta:
260 return (self._manifestdelta[path],
260 return (self._manifestdelta[path],
261 self._manifestdelta.flags(path))
261 self._manifestdelta.flags(path))
262 mfl = self._repo.manifestlog
262 mfl = self._repo.manifestlog
263 try:
263 try:
264 node, flag = mfl[self._changeset.manifest].find(path)
264 node, flag = mfl[self._changeset.manifest].find(path)
265 except KeyError:
265 except KeyError:
266 raise error.ManifestLookupError(self._node, path,
266 raise error.ManifestLookupError(self._node, path,
267 _('not found in manifest'))
267 _('not found in manifest'))
268
268
269 return node, flag
269 return node, flag
270
270
271 def filenode(self, path):
271 def filenode(self, path):
272 return self._fileinfo(path)[0]
272 return self._fileinfo(path)[0]
273
273
274 def flags(self, path):
274 def flags(self, path):
275 try:
275 try:
276 return self._fileinfo(path)[1]
276 return self._fileinfo(path)[1]
277 except error.LookupError:
277 except error.LookupError:
278 return ''
278 return ''
279
279
280 def sub(self, path, allowcreate=True):
280 def sub(self, path, allowcreate=True):
281 '''return a subrepo for the stored revision of path, never wdir()'''
281 '''return a subrepo for the stored revision of path, never wdir()'''
282 return subrepo.subrepo(self, path, allowcreate=allowcreate)
282 return subrepo.subrepo(self, path, allowcreate=allowcreate)
283
283
284 def nullsub(self, path, pctx):
284 def nullsub(self, path, pctx):
285 return subrepo.nullsubrepo(self, path, pctx)
285 return subrepo.nullsubrepo(self, path, pctx)
286
286
287 def workingsub(self, path):
287 def workingsub(self, path):
288 '''return a subrepo for the stored revision, or wdir if this is a wdir
288 '''return a subrepo for the stored revision, or wdir if this is a wdir
289 context.
289 context.
290 '''
290 '''
291 return subrepo.subrepo(self, path, allowwdir=True)
291 return subrepo.subrepo(self, path, allowwdir=True)
292
292
293 def match(self, pats=[], include=None, exclude=None, default='glob',
293 def match(self, pats=[], include=None, exclude=None, default='glob',
294 listsubrepos=False, badfn=None):
294 listsubrepos=False, badfn=None):
295 r = self._repo
295 r = self._repo
296 return matchmod.match(r.root, r.getcwd(), pats,
296 return matchmod.match(r.root, r.getcwd(), pats,
297 include, exclude, default,
297 include, exclude, default,
298 auditor=r.nofsauditor, ctx=self,
298 auditor=r.nofsauditor, ctx=self,
299 listsubrepos=listsubrepos, badfn=badfn)
299 listsubrepos=listsubrepos, badfn=badfn)
300
300
301 def diff(self, ctx2=None, match=None, **opts):
301 def diff(self, ctx2=None, match=None, **opts):
302 """Returns a diff generator for the given contexts and matcher"""
302 """Returns a diff generator for the given contexts and matcher"""
303 if ctx2 is None:
303 if ctx2 is None:
304 ctx2 = self.p1()
304 ctx2 = self.p1()
305 if ctx2 is not None:
305 if ctx2 is not None:
306 ctx2 = self._repo[ctx2]
306 ctx2 = self._repo[ctx2]
307 diffopts = patch.diffopts(self._repo.ui, opts)
307 diffopts = patch.diffopts(self._repo.ui, opts)
308 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
308 return patch.diff(self._repo, ctx2, self, match=match, opts=diffopts)
309
309
310 def dirs(self):
310 def dirs(self):
311 return self._manifest.dirs()
311 return self._manifest.dirs()
312
312
313 def hasdir(self, dir):
313 def hasdir(self, dir):
314 return self._manifest.hasdir(dir)
314 return self._manifest.hasdir(dir)
315
315
316 def dirty(self, missing=False, merge=True, branch=True):
316 def dirty(self, missing=False, merge=True, branch=True):
317 return False
317 return False
318
318
319 def status(self, other=None, match=None, listignored=False,
319 def status(self, other=None, match=None, listignored=False,
320 listclean=False, listunknown=False, listsubrepos=False):
320 listclean=False, listunknown=False, listsubrepos=False):
321 """return status of files between two nodes or node and working
321 """return status of files between two nodes or node and working
322 directory.
322 directory.
323
323
324 If other is None, compare this node with working directory.
324 If other is None, compare this node with working directory.
325
325
326 returns (modified, added, removed, deleted, unknown, ignored, clean)
326 returns (modified, added, removed, deleted, unknown, ignored, clean)
327 """
327 """
328
328
329 ctx1 = self
329 ctx1 = self
330 ctx2 = self._repo[other]
330 ctx2 = self._repo[other]
331
331
332 # This next code block is, admittedly, fragile logic that tests for
332 # This next code block is, admittedly, fragile logic that tests for
333 # reversing the contexts and wouldn't need to exist if it weren't for
333 # reversing the contexts and wouldn't need to exist if it weren't for
334 # the fast (and common) code path of comparing the working directory
334 # the fast (and common) code path of comparing the working directory
335 # with its first parent.
335 # with its first parent.
336 #
336 #
337 # What we're aiming for here is the ability to call:
337 # What we're aiming for here is the ability to call:
338 #
338 #
339 # workingctx.status(parentctx)
339 # workingctx.status(parentctx)
340 #
340 #
341 # If we always built the manifest for each context and compared those,
341 # If we always built the manifest for each context and compared those,
342 # then we'd be done. But the special case of the above call means we
342 # then we'd be done. But the special case of the above call means we
343 # just copy the manifest of the parent.
343 # just copy the manifest of the parent.
344 reversed = False
344 reversed = False
345 if (not isinstance(ctx1, changectx)
345 if (not isinstance(ctx1, changectx)
346 and isinstance(ctx2, changectx)):
346 and isinstance(ctx2, changectx)):
347 reversed = True
347 reversed = True
348 ctx1, ctx2 = ctx2, ctx1
348 ctx1, ctx2 = ctx2, ctx1
349
349
350 match = ctx2._matchstatus(ctx1, match)
350 match = ctx2._matchstatus(ctx1, match)
351 r = scmutil.status([], [], [], [], [], [], [])
351 r = scmutil.status([], [], [], [], [], [], [])
352 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
352 r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
353 listunknown)
353 listunknown)
354
354
355 if reversed:
355 if reversed:
356 # Reverse added and removed. Clear deleted, unknown and ignored as
356 # Reverse added and removed. Clear deleted, unknown and ignored as
357 # these make no sense to reverse.
357 # these make no sense to reverse.
358 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
358 r = scmutil.status(r.modified, r.removed, r.added, [], [], [],
359 r.clean)
359 r.clean)
360
360
361 if listsubrepos:
361 if listsubrepos:
362 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
362 for subpath, sub in scmutil.itersubrepos(ctx1, ctx2):
363 try:
363 try:
364 rev2 = ctx2.subrev(subpath)
364 rev2 = ctx2.subrev(subpath)
365 except KeyError:
365 except KeyError:
366 # A subrepo that existed in node1 was deleted between
366 # A subrepo that existed in node1 was deleted between
367 # node1 and node2 (inclusive). Thus, ctx2's substate
367 # node1 and node2 (inclusive). Thus, ctx2's substate
368 # won't contain that subpath. The best we can do ignore it.
368 # won't contain that subpath. The best we can do ignore it.
369 rev2 = None
369 rev2 = None
370 submatch = matchmod.subdirmatcher(subpath, match)
370 submatch = matchmod.subdirmatcher(subpath, match)
371 s = sub.status(rev2, match=submatch, ignored=listignored,
371 s = sub.status(rev2, match=submatch, ignored=listignored,
372 clean=listclean, unknown=listunknown,
372 clean=listclean, unknown=listunknown,
373 listsubrepos=True)
373 listsubrepos=True)
374 for rfiles, sfiles in zip(r, s):
374 for rfiles, sfiles in zip(r, s):
375 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
375 rfiles.extend("%s/%s" % (subpath, f) for f in sfiles)
376
376
377 for l in r:
377 for l in r:
378 l.sort()
378 l.sort()
379
379
380 return r
380 return r
381
381
382
382
383 def makememctx(repo, parents, text, user, date, branch, files, store,
383 def makememctx(repo, parents, text, user, date, branch, files, store,
384 editor=None, extra=None):
384 editor=None, extra=None):
385 def getfilectx(repo, memctx, path):
385 def getfilectx(repo, memctx, path):
386 data, mode, copied = store.getfile(path)
386 data, mode, copied = store.getfile(path)
387 if data is None:
387 if data is None:
388 return None
388 return None
389 islink, isexec = mode
389 islink, isexec = mode
390 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
390 return memfilectx(repo, path, data, islink=islink, isexec=isexec,
391 copied=copied, memctx=memctx)
391 copied=copied, memctx=memctx)
392 if extra is None:
392 if extra is None:
393 extra = {}
393 extra = {}
394 if branch:
394 if branch:
395 extra['branch'] = encoding.fromlocal(branch)
395 extra['branch'] = encoding.fromlocal(branch)
396 ctx = memctx(repo, parents, text, files, getfilectx, user,
396 ctx = memctx(repo, parents, text, files, getfilectx, user,
397 date, extra, editor)
397 date, extra, editor)
398 return ctx
398 return ctx
399
399
400 class changectx(basectx):
400 class changectx(basectx):
401 """A changecontext object makes access to data related to a particular
401 """A changecontext object makes access to data related to a particular
402 changeset convenient. It represents a read-only context already present in
402 changeset convenient. It represents a read-only context already present in
403 the repo."""
403 the repo."""
404 def __init__(self, repo, changeid=''):
404 def __init__(self, repo, changeid=''):
405 """changeid is a revision number, node, or tag"""
405 """changeid is a revision number, node, or tag"""
406
406
407 # since basectx.__new__ already took care of copying the object, we
407 # since basectx.__new__ already took care of copying the object, we
408 # don't need to do anything in __init__, so we just exit here
408 # don't need to do anything in __init__, so we just exit here
409 if isinstance(changeid, basectx):
409 if isinstance(changeid, basectx):
410 return
410 return
411
411
412 if changeid == '':
412 if changeid == '':
413 changeid = '.'
413 changeid = '.'
414 self._repo = repo
414 self._repo = repo
415
415
416 try:
416 try:
417 if isinstance(changeid, int):
417 if isinstance(changeid, int):
418 self._node = repo.changelog.node(changeid)
418 self._node = repo.changelog.node(changeid)
419 self._rev = changeid
419 self._rev = changeid
420 return
420 return
421 if isinstance(changeid, long):
421 if isinstance(changeid, long):
422 changeid = str(changeid)
422 changeid = str(changeid)
423 if changeid == 'null':
423 if changeid == 'null':
424 self._node = nullid
424 self._node = nullid
425 self._rev = nullrev
425 self._rev = nullrev
426 return
426 return
427 if changeid == 'tip':
427 if changeid == 'tip':
428 self._node = repo.changelog.tip()
428 self._node = repo.changelog.tip()
429 self._rev = repo.changelog.rev(self._node)
429 self._rev = repo.changelog.rev(self._node)
430 return
430 return
431 if changeid == '.' or changeid == repo.dirstate.p1():
431 if changeid == '.' or changeid == repo.dirstate.p1():
432 # this is a hack to delay/avoid loading obsmarkers
432 # this is a hack to delay/avoid loading obsmarkers
433 # when we know that '.' won't be hidden
433 # when we know that '.' won't be hidden
434 self._node = repo.dirstate.p1()
434 self._node = repo.dirstate.p1()
435 self._rev = repo.unfiltered().changelog.rev(self._node)
435 self._rev = repo.unfiltered().changelog.rev(self._node)
436 return
436 return
437 if len(changeid) == 20:
437 if len(changeid) == 20:
438 try:
438 try:
439 self._node = changeid
439 self._node = changeid
440 self._rev = repo.changelog.rev(changeid)
440 self._rev = repo.changelog.rev(changeid)
441 return
441 return
442 except error.FilteredRepoLookupError:
442 except error.FilteredRepoLookupError:
443 raise
443 raise
444 except LookupError:
444 except LookupError:
445 pass
445 pass
446
446
447 try:
447 try:
448 r = int(changeid)
448 r = int(changeid)
449 if str(r) != changeid:
449 if str(r) != changeid:
450 raise ValueError
450 raise ValueError
451 l = len(repo.changelog)
451 l = len(repo.changelog)
452 if r < 0:
452 if r < 0:
453 r += l
453 r += l
454 if r < 0 or r >= l:
454 if r < 0 or r >= l:
455 raise ValueError
455 raise ValueError
456 self._rev = r
456 self._rev = r
457 self._node = repo.changelog.node(r)
457 self._node = repo.changelog.node(r)
458 return
458 return
459 except error.FilteredIndexError:
459 except error.FilteredIndexError:
460 raise
460 raise
461 except (ValueError, OverflowError, IndexError):
461 except (ValueError, OverflowError, IndexError):
462 pass
462 pass
463
463
464 if len(changeid) == 40:
464 if len(changeid) == 40:
465 try:
465 try:
466 self._node = bin(changeid)
466 self._node = bin(changeid)
467 self._rev = repo.changelog.rev(self._node)
467 self._rev = repo.changelog.rev(self._node)
468 return
468 return
469 except error.FilteredLookupError:
469 except error.FilteredLookupError:
470 raise
470 raise
471 except (TypeError, LookupError):
471 except (TypeError, LookupError):
472 pass
472 pass
473
473
474 # lookup bookmarks through the name interface
474 # lookup bookmarks through the name interface
475 try:
475 try:
476 self._node = repo.names.singlenode(repo, changeid)
476 self._node = repo.names.singlenode(repo, changeid)
477 self._rev = repo.changelog.rev(self._node)
477 self._rev = repo.changelog.rev(self._node)
478 return
478 return
479 except KeyError:
479 except KeyError:
480 pass
480 pass
481 except error.FilteredRepoLookupError:
481 except error.FilteredRepoLookupError:
482 raise
482 raise
483 except error.RepoLookupError:
483 except error.RepoLookupError:
484 pass
484 pass
485
485
486 self._node = repo.unfiltered().changelog._partialmatch(changeid)
486 self._node = repo.unfiltered().changelog._partialmatch(changeid)
487 if self._node is not None:
487 if self._node is not None:
488 self._rev = repo.changelog.rev(self._node)
488 self._rev = repo.changelog.rev(self._node)
489 return
489 return
490
490
491 # lookup failed
491 # lookup failed
492 # check if it might have come from damaged dirstate
492 # check if it might have come from damaged dirstate
493 #
493 #
494 # XXX we could avoid the unfiltered if we had a recognizable
494 # XXX we could avoid the unfiltered if we had a recognizable
495 # exception for filtered changeset access
495 # exception for filtered changeset access
496 if changeid in repo.unfiltered().dirstate.parents():
496 if changeid in repo.unfiltered().dirstate.parents():
497 msg = _("working directory has unknown parent '%s'!")
497 msg = _("working directory has unknown parent '%s'!")
498 raise error.Abort(msg % short(changeid))
498 raise error.Abort(msg % short(changeid))
499 try:
499 try:
500 if len(changeid) == 20 and nonascii(changeid):
500 if len(changeid) == 20 and nonascii(changeid):
501 changeid = hex(changeid)
501 changeid = hex(changeid)
502 except TypeError:
502 except TypeError:
503 pass
503 pass
504 except (error.FilteredIndexError, error.FilteredLookupError,
504 except (error.FilteredIndexError, error.FilteredLookupError,
505 error.FilteredRepoLookupError):
505 error.FilteredRepoLookupError):
506 if repo.filtername.startswith('visible'):
506 if repo.filtername.startswith('visible'):
507 msg = _("hidden revision '%s'") % changeid
507 msg = _("hidden revision '%s'") % changeid
508 hint = _('use --hidden to access hidden revisions')
508 hint = _('use --hidden to access hidden revisions')
509 raise error.FilteredRepoLookupError(msg, hint=hint)
509 raise error.FilteredRepoLookupError(msg, hint=hint)
510 msg = _("filtered revision '%s' (not in '%s' subset)")
510 msg = _("filtered revision '%s' (not in '%s' subset)")
511 msg %= (changeid, repo.filtername)
511 msg %= (changeid, repo.filtername)
512 raise error.FilteredRepoLookupError(msg)
512 raise error.FilteredRepoLookupError(msg)
513 except IndexError:
513 except IndexError:
514 pass
514 pass
515 raise error.RepoLookupError(
515 raise error.RepoLookupError(
516 _("unknown revision '%s'") % changeid)
516 _("unknown revision '%s'") % changeid)
517
517
518 def __hash__(self):
518 def __hash__(self):
519 try:
519 try:
520 return hash(self._rev)
520 return hash(self._rev)
521 except AttributeError:
521 except AttributeError:
522 return id(self)
522 return id(self)
523
523
524 def __nonzero__(self):
524 def __nonzero__(self):
525 return self._rev != nullrev
525 return self._rev != nullrev
526
526
527 @propertycache
527 @propertycache
528 def _changeset(self):
528 def _changeset(self):
529 return self._repo.changelog.changelogrevision(self.rev())
529 return self._repo.changelog.changelogrevision(self.rev())
530
530
531 @propertycache
531 @propertycache
532 def _manifest(self):
532 def _manifest(self):
533 return self._manifestctx.read()
533 return self._manifestctx.read()
534
534
535 @propertycache
535 @propertycache
536 def _manifestctx(self):
536 def _manifestctx(self):
537 return self._repo.manifestlog[self._changeset.manifest]
537 return self._repo.manifestlog[self._changeset.manifest]
538
538
539 @propertycache
539 @propertycache
540 def _manifestdelta(self):
540 def _manifestdelta(self):
541 return self._manifestctx.readdelta()
541 return self._manifestctx.readdelta()
542
542
543 @propertycache
543 @propertycache
544 def _parents(self):
544 def _parents(self):
545 repo = self._repo
545 repo = self._repo
546 p1, p2 = repo.changelog.parentrevs(self._rev)
546 p1, p2 = repo.changelog.parentrevs(self._rev)
547 if p2 == nullrev:
547 if p2 == nullrev:
548 return [changectx(repo, p1)]
548 return [changectx(repo, p1)]
549 return [changectx(repo, p1), changectx(repo, p2)]
549 return [changectx(repo, p1), changectx(repo, p2)]
550
550
551 def changeset(self):
551 def changeset(self):
552 c = self._changeset
552 c = self._changeset
553 return (
553 return (
554 c.manifest,
554 c.manifest,
555 c.user,
555 c.user,
556 c.date,
556 c.date,
557 c.files,
557 c.files,
558 c.description,
558 c.description,
559 c.extra,
559 c.extra,
560 )
560 )
561 def manifestnode(self):
561 def manifestnode(self):
562 return self._changeset.manifest
562 return self._changeset.manifest
563
563
564 def user(self):
564 def user(self):
565 return self._changeset.user
565 return self._changeset.user
566 def date(self):
566 def date(self):
567 return self._changeset.date
567 return self._changeset.date
568 def files(self):
568 def files(self):
569 return self._changeset.files
569 return self._changeset.files
570 def description(self):
570 def description(self):
571 return self._changeset.description
571 return self._changeset.description
572 def branch(self):
572 def branch(self):
573 return encoding.tolocal(self._changeset.extra.get("branch"))
573 return encoding.tolocal(self._changeset.extra.get("branch"))
574 def closesbranch(self):
574 def closesbranch(self):
575 return 'close' in self._changeset.extra
575 return 'close' in self._changeset.extra
576 def extra(self):
576 def extra(self):
577 return self._changeset.extra
577 return self._changeset.extra
578 def tags(self):
578 def tags(self):
579 return self._repo.nodetags(self._node)
579 return self._repo.nodetags(self._node)
580 def bookmarks(self):
580 def bookmarks(self):
581 return self._repo.nodebookmarks(self._node)
581 return self._repo.nodebookmarks(self._node)
582 def phase(self):
582 def phase(self):
583 return self._repo._phasecache.phase(self._repo, self._rev)
583 return self._repo._phasecache.phase(self._repo, self._rev)
584 def hidden(self):
584 def hidden(self):
585 return self._rev in repoview.filterrevs(self._repo, 'visible')
585 return self._rev in repoview.filterrevs(self._repo, 'visible')
586
586
587 def children(self):
587 def children(self):
588 """return contexts for each child changeset"""
588 """return contexts for each child changeset"""
589 c = self._repo.changelog.children(self._node)
589 c = self._repo.changelog.children(self._node)
590 return [changectx(self._repo, x) for x in c]
590 return [changectx(self._repo, x) for x in c]
591
591
592 def ancestors(self):
592 def ancestors(self):
593 for a in self._repo.changelog.ancestors([self._rev]):
593 for a in self._repo.changelog.ancestors([self._rev]):
594 yield changectx(self._repo, a)
594 yield changectx(self._repo, a)
595
595
596 def descendants(self):
596 def descendants(self):
597 for d in self._repo.changelog.descendants([self._rev]):
597 for d in self._repo.changelog.descendants([self._rev]):
598 yield changectx(self._repo, d)
598 yield changectx(self._repo, d)
599
599
600 def filectx(self, path, fileid=None, filelog=None):
600 def filectx(self, path, fileid=None, filelog=None):
601 """get a file context from this changeset"""
601 """get a file context from this changeset"""
602 if fileid is None:
602 if fileid is None:
603 fileid = self.filenode(path)
603 fileid = self.filenode(path)
604 return filectx(self._repo, path, fileid=fileid,
604 return filectx(self._repo, path, fileid=fileid,
605 changectx=self, filelog=filelog)
605 changectx=self, filelog=filelog)
606
606
607 def ancestor(self, c2, warn=False):
607 def ancestor(self, c2, warn=False):
608 """return the "best" ancestor context of self and c2
608 """return the "best" ancestor context of self and c2
609
609
610 If there are multiple candidates, it will show a message and check
610 If there are multiple candidates, it will show a message and check
611 merge.preferancestor configuration before falling back to the
611 merge.preferancestor configuration before falling back to the
612 revlog ancestor."""
612 revlog ancestor."""
613 # deal with workingctxs
613 # deal with workingctxs
614 n2 = c2._node
614 n2 = c2._node
615 if n2 is None:
615 if n2 is None:
616 n2 = c2._parents[0]._node
616 n2 = c2._parents[0]._node
617 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
617 cahs = self._repo.changelog.commonancestorsheads(self._node, n2)
618 if not cahs:
618 if not cahs:
619 anc = nullid
619 anc = nullid
620 elif len(cahs) == 1:
620 elif len(cahs) == 1:
621 anc = cahs[0]
621 anc = cahs[0]
622 else:
622 else:
623 # experimental config: merge.preferancestor
623 # experimental config: merge.preferancestor
624 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
624 for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
625 try:
625 try:
626 ctx = changectx(self._repo, r)
626 ctx = changectx(self._repo, r)
627 except error.RepoLookupError:
627 except error.RepoLookupError:
628 continue
628 continue
629 anc = ctx.node()
629 anc = ctx.node()
630 if anc in cahs:
630 if anc in cahs:
631 break
631 break
632 else:
632 else:
633 anc = self._repo.changelog.ancestor(self._node, n2)
633 anc = self._repo.changelog.ancestor(self._node, n2)
634 if warn:
634 if warn:
635 self._repo.ui.status(
635 self._repo.ui.status(
636 (_("note: using %s as ancestor of %s and %s\n") %
636 (_("note: using %s as ancestor of %s and %s\n") %
637 (short(anc), short(self._node), short(n2))) +
637 (short(anc), short(self._node), short(n2))) +
638 ''.join(_(" alternatively, use --config "
638 ''.join(_(" alternatively, use --config "
639 "merge.preferancestor=%s\n") %
639 "merge.preferancestor=%s\n") %
640 short(n) for n in sorted(cahs) if n != anc))
640 short(n) for n in sorted(cahs) if n != anc))
641 return changectx(self._repo, anc)
641 return changectx(self._repo, anc)
642
642
643 def descendant(self, other):
643 def descendant(self, other):
644 """True if other is descendant of this changeset"""
644 """True if other is descendant of this changeset"""
645 return self._repo.changelog.descendant(self._rev, other._rev)
645 return self._repo.changelog.descendant(self._rev, other._rev)
646
646
647 def walk(self, match):
647 def walk(self, match):
648 '''Generates matching file names.'''
648 '''Generates matching file names.'''
649
649
650 # Wrap match.bad method to have message with nodeid
650 # Wrap match.bad method to have message with nodeid
651 def bad(fn, msg):
651 def bad(fn, msg):
652 # The manifest doesn't know about subrepos, so don't complain about
652 # The manifest doesn't know about subrepos, so don't complain about
653 # paths into valid subrepos.
653 # paths into valid subrepos.
654 if any(fn == s or fn.startswith(s + '/')
654 if any(fn == s or fn.startswith(s + '/')
655 for s in self.substate):
655 for s in self.substate):
656 return
656 return
657 match.bad(fn, _('no such file in rev %s') % self)
657 match.bad(fn, _('no such file in rev %s') % self)
658
658
659 m = matchmod.badmatch(match, bad)
659 m = matchmod.badmatch(match, bad)
660 return self._manifest.walk(m)
660 return self._manifest.walk(m)
661
661
662 def matches(self, match):
662 def matches(self, match):
663 return self.walk(match)
663 return self.walk(match)
664
664
665 class basefilectx(object):
665 class basefilectx(object):
666 """A filecontext object represents the common logic for its children:
666 """A filecontext object represents the common logic for its children:
667 filectx: read-only access to a filerevision that is already present
667 filectx: read-only access to a filerevision that is already present
668 in the repo,
668 in the repo,
669 workingfilectx: a filecontext that represents files from the working
669 workingfilectx: a filecontext that represents files from the working
670 directory,
670 directory,
671 memfilectx: a filecontext that represents files in-memory."""
671 memfilectx: a filecontext that represents files in-memory."""
672 def __new__(cls, repo, path, *args, **kwargs):
672 def __new__(cls, repo, path, *args, **kwargs):
673 return super(basefilectx, cls).__new__(cls)
673 return super(basefilectx, cls).__new__(cls)
674
674
675 @propertycache
675 @propertycache
676 def _filelog(self):
676 def _filelog(self):
677 return self._repo.file(self._path)
677 return self._repo.file(self._path)
678
678
679 @propertycache
679 @propertycache
680 def _changeid(self):
680 def _changeid(self):
681 if '_changeid' in self.__dict__:
681 if '_changeid' in self.__dict__:
682 return self._changeid
682 return self._changeid
683 elif '_changectx' in self.__dict__:
683 elif '_changectx' in self.__dict__:
684 return self._changectx.rev()
684 return self._changectx.rev()
685 elif '_descendantrev' in self.__dict__:
685 elif '_descendantrev' in self.__dict__:
686 # this file context was created from a revision with a known
686 # this file context was created from a revision with a known
687 # descendant, we can (lazily) correct for linkrev aliases
687 # descendant, we can (lazily) correct for linkrev aliases
688 return self._adjustlinkrev(self._descendantrev)
688 return self._adjustlinkrev(self._descendantrev)
689 else:
689 else:
690 return self._filelog.linkrev(self._filerev)
690 return self._filelog.linkrev(self._filerev)
691
691
692 @propertycache
692 @propertycache
693 def _filenode(self):
693 def _filenode(self):
694 if '_fileid' in self.__dict__:
694 if '_fileid' in self.__dict__:
695 return self._filelog.lookup(self._fileid)
695 return self._filelog.lookup(self._fileid)
696 else:
696 else:
697 return self._changectx.filenode(self._path)
697 return self._changectx.filenode(self._path)
698
698
699 @propertycache
699 @propertycache
700 def _filerev(self):
700 def _filerev(self):
701 return self._filelog.rev(self._filenode)
701 return self._filelog.rev(self._filenode)
702
702
703 @propertycache
703 @propertycache
704 def _repopath(self):
704 def _repopath(self):
705 return self._path
705 return self._path
706
706
707 def __nonzero__(self):
707 def __nonzero__(self):
708 try:
708 try:
709 self._filenode
709 self._filenode
710 return True
710 return True
711 except error.LookupError:
711 except error.LookupError:
712 # file is missing
712 # file is missing
713 return False
713 return False
714
714
715 def __str__(self):
715 def __str__(self):
716 try:
716 try:
717 return "%s@%s" % (self.path(), self._changectx)
717 return "%s@%s" % (self.path(), self._changectx)
718 except error.LookupError:
718 except error.LookupError:
719 return "%s@???" % self.path()
719 return "%s@???" % self.path()
720
720
721 def __repr__(self):
721 def __repr__(self):
722 return "<%s %s>" % (type(self).__name__, str(self))
722 return "<%s %s>" % (type(self).__name__, str(self))
723
723
724 def __hash__(self):
724 def __hash__(self):
725 try:
725 try:
726 return hash((self._path, self._filenode))
726 return hash((self._path, self._filenode))
727 except AttributeError:
727 except AttributeError:
728 return id(self)
728 return id(self)
729
729
730 def __eq__(self, other):
730 def __eq__(self, other):
731 try:
731 try:
732 return (type(self) == type(other) and self._path == other._path
732 return (type(self) == type(other) and self._path == other._path
733 and self._filenode == other._filenode)
733 and self._filenode == other._filenode)
734 except AttributeError:
734 except AttributeError:
735 return False
735 return False
736
736
737 def __ne__(self, other):
737 def __ne__(self, other):
738 return not (self == other)
738 return not (self == other)
739
739
740 def filerev(self):
740 def filerev(self):
741 return self._filerev
741 return self._filerev
742 def filenode(self):
742 def filenode(self):
743 return self._filenode
743 return self._filenode
744 def flags(self):
744 def flags(self):
745 return self._changectx.flags(self._path)
745 return self._changectx.flags(self._path)
746 def filelog(self):
746 def filelog(self):
747 return self._filelog
747 return self._filelog
748 def rev(self):
748 def rev(self):
749 return self._changeid
749 return self._changeid
750 def linkrev(self):
750 def linkrev(self):
751 return self._filelog.linkrev(self._filerev)
751 return self._filelog.linkrev(self._filerev)
752 def node(self):
752 def node(self):
753 return self._changectx.node()
753 return self._changectx.node()
754 def hex(self):
754 def hex(self):
755 return self._changectx.hex()
755 return self._changectx.hex()
756 def user(self):
756 def user(self):
757 return self._changectx.user()
757 return self._changectx.user()
758 def date(self):
758 def date(self):
759 return self._changectx.date()
759 return self._changectx.date()
760 def files(self):
760 def files(self):
761 return self._changectx.files()
761 return self._changectx.files()
762 def description(self):
762 def description(self):
763 return self._changectx.description()
763 return self._changectx.description()
764 def branch(self):
764 def branch(self):
765 return self._changectx.branch()
765 return self._changectx.branch()
766 def extra(self):
766 def extra(self):
767 return self._changectx.extra()
767 return self._changectx.extra()
768 def phase(self):
768 def phase(self):
769 return self._changectx.phase()
769 return self._changectx.phase()
770 def phasestr(self):
770 def phasestr(self):
771 return self._changectx.phasestr()
771 return self._changectx.phasestr()
772 def manifest(self):
772 def manifest(self):
773 return self._changectx.manifest()
773 return self._changectx.manifest()
774 def changectx(self):
774 def changectx(self):
775 return self._changectx
775 return self._changectx
776 def repo(self):
776 def repo(self):
777 return self._repo
777 return self._repo
778
778
779 def path(self):
779 def path(self):
780 return self._path
780 return self._path
781
781
782 def isbinary(self):
782 def isbinary(self):
783 try:
783 try:
784 return util.binary(self.data())
784 return util.binary(self.data())
785 except IOError:
785 except IOError:
786 return False
786 return False
787 def isexec(self):
787 def isexec(self):
788 return 'x' in self.flags()
788 return 'x' in self.flags()
789 def islink(self):
789 def islink(self):
790 return 'l' in self.flags()
790 return 'l' in self.flags()
791
791
792 def isabsent(self):
792 def isabsent(self):
793 """whether this filectx represents a file not in self._changectx
793 """whether this filectx represents a file not in self._changectx
794
794
795 This is mainly for merge code to detect change/delete conflicts. This is
795 This is mainly for merge code to detect change/delete conflicts. This is
796 expected to be True for all subclasses of basectx."""
796 expected to be True for all subclasses of basectx."""
797 return False
797 return False
798
798
799 _customcmp = False
799 _customcmp = False
800 def cmp(self, fctx):
800 def cmp(self, fctx):
801 """compare with other file context
801 """compare with other file context
802
802
803 returns True if different than fctx.
803 returns True if different than fctx.
804 """
804 """
805 if fctx._customcmp:
805 if fctx._customcmp:
806 return fctx.cmp(self)
806 return fctx.cmp(self)
807
807
808 if (fctx._filenode is None
808 if (fctx._filenode is None
809 and (self._repo._encodefilterpats
809 and (self._repo._encodefilterpats
810 # if file data starts with '\1\n', empty metadata block is
810 # if file data starts with '\1\n', empty metadata block is
811 # prepended, which adds 4 bytes to filelog.size().
811 # prepended, which adds 4 bytes to filelog.size().
812 or self.size() - 4 == fctx.size())
812 or self.size() - 4 == fctx.size())
813 or self.size() == fctx.size()):
813 or self.size() == fctx.size()):
814 return self._filelog.cmp(self._filenode, fctx.data())
814 return self._filelog.cmp(self._filenode, fctx.data())
815
815
816 return True
816 return True
817
817
818 def _adjustlinkrev(self, srcrev, inclusive=False):
818 def _adjustlinkrev(self, srcrev, inclusive=False):
819 """return the first ancestor of <srcrev> introducing <fnode>
819 """return the first ancestor of <srcrev> introducing <fnode>
820
820
821 If the linkrev of the file revision does not point to an ancestor of
821 If the linkrev of the file revision does not point to an ancestor of
822 srcrev, we'll walk down the ancestors until we find one introducing
822 srcrev, we'll walk down the ancestors until we find one introducing
823 this file revision.
823 this file revision.
824
824
825 :srcrev: the changeset revision we search ancestors from
825 :srcrev: the changeset revision we search ancestors from
826 :inclusive: if true, the src revision will also be checked
826 :inclusive: if true, the src revision will also be checked
827 """
827 """
828 repo = self._repo
828 repo = self._repo
829 cl = repo.unfiltered().changelog
829 cl = repo.unfiltered().changelog
830 mfl = repo.manifestlog
830 mfl = repo.manifestlog
831 # fetch the linkrev
831 # fetch the linkrev
832 lkr = self.linkrev()
832 lkr = self.linkrev()
833 # hack to reuse ancestor computation when searching for renames
833 # hack to reuse ancestor computation when searching for renames
834 memberanc = getattr(self, '_ancestrycontext', None)
834 memberanc = getattr(self, '_ancestrycontext', None)
835 iteranc = None
835 iteranc = None
836 if srcrev is None:
836 if srcrev is None:
837 # wctx case, used by workingfilectx during mergecopy
837 # wctx case, used by workingfilectx during mergecopy
838 revs = [p.rev() for p in self._repo[None].parents()]
838 revs = [p.rev() for p in self._repo[None].parents()]
839 inclusive = True # we skipped the real (revless) source
839 inclusive = True # we skipped the real (revless) source
840 else:
840 else:
841 revs = [srcrev]
841 revs = [srcrev]
842 if memberanc is None:
842 if memberanc is None:
843 memberanc = iteranc = cl.ancestors(revs, lkr,
843 memberanc = iteranc = cl.ancestors(revs, lkr,
844 inclusive=inclusive)
844 inclusive=inclusive)
845 # check if this linkrev is an ancestor of srcrev
845 # check if this linkrev is an ancestor of srcrev
846 if lkr not in memberanc:
846 if lkr not in memberanc:
847 if iteranc is None:
847 if iteranc is None:
848 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
848 iteranc = cl.ancestors(revs, lkr, inclusive=inclusive)
849 fnode = self._filenode
849 fnode = self._filenode
850 path = self._path
850 path = self._path
851 for a in iteranc:
851 for a in iteranc:
852 ac = cl.read(a) # get changeset data (we avoid object creation)
852 ac = cl.read(a) # get changeset data (we avoid object creation)
853 if path in ac[3]: # checking the 'files' field.
853 if path in ac[3]: # checking the 'files' field.
854 # The file has been touched, check if the content is
854 # The file has been touched, check if the content is
855 # similar to the one we search for.
855 # similar to the one we search for.
856 if fnode == mfl[ac[0]].readfast().get(path):
856 if fnode == mfl[ac[0]].readfast().get(path):
857 return a
857 return a
858 # In theory, we should never get out of that loop without a result.
858 # In theory, we should never get out of that loop without a result.
859 # But if manifest uses a buggy file revision (not children of the
859 # But if manifest uses a buggy file revision (not children of the
860 # one it replaces) we could. Such a buggy situation will likely
860 # one it replaces) we could. Such a buggy situation will likely
861 # result is crash somewhere else at to some point.
861 # result is crash somewhere else at to some point.
862 return lkr
862 return lkr
863
863
864 def introrev(self):
864 def introrev(self):
865 """return the rev of the changeset which introduced this file revision
865 """return the rev of the changeset which introduced this file revision
866
866
867 This method is different from linkrev because it take into account the
867 This method is different from linkrev because it take into account the
868 changeset the filectx was created from. It ensures the returned
868 changeset the filectx was created from. It ensures the returned
869 revision is one of its ancestors. This prevents bugs from
869 revision is one of its ancestors. This prevents bugs from
870 'linkrev-shadowing' when a file revision is used by multiple
870 'linkrev-shadowing' when a file revision is used by multiple
871 changesets.
871 changesets.
872 """
872 """
873 lkr = self.linkrev()
873 lkr = self.linkrev()
874 attrs = vars(self)
874 attrs = vars(self)
875 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
875 noctx = not ('_changeid' in attrs or '_changectx' in attrs)
876 if noctx or self.rev() == lkr:
876 if noctx or self.rev() == lkr:
877 return self.linkrev()
877 return self.linkrev()
878 return self._adjustlinkrev(self.rev(), inclusive=True)
878 return self._adjustlinkrev(self.rev(), inclusive=True)
879
879
880 def _parentfilectx(self, path, fileid, filelog):
880 def _parentfilectx(self, path, fileid, filelog):
881 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
881 """create parent filectx keeping ancestry info for _adjustlinkrev()"""
882 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
882 fctx = filectx(self._repo, path, fileid=fileid, filelog=filelog)
883 if '_changeid' in vars(self) or '_changectx' in vars(self):
883 if '_changeid' in vars(self) or '_changectx' in vars(self):
884 # If self is associated with a changeset (probably explicitly
884 # If self is associated with a changeset (probably explicitly
885 # fed), ensure the created filectx is associated with a
885 # fed), ensure the created filectx is associated with a
886 # changeset that is an ancestor of self.changectx.
886 # changeset that is an ancestor of self.changectx.
887 # This lets us later use _adjustlinkrev to get a correct link.
887 # This lets us later use _adjustlinkrev to get a correct link.
888 fctx._descendantrev = self.rev()
888 fctx._descendantrev = self.rev()
889 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
889 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
890 elif '_descendantrev' in vars(self):
890 elif '_descendantrev' in vars(self):
891 # Otherwise propagate _descendantrev if we have one associated.
891 # Otherwise propagate _descendantrev if we have one associated.
892 fctx._descendantrev = self._descendantrev
892 fctx._descendantrev = self._descendantrev
893 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
893 fctx._ancestrycontext = getattr(self, '_ancestrycontext', None)
894 return fctx
894 return fctx
895
895
896 def parents(self):
896 def parents(self):
897 _path = self._path
897 _path = self._path
898 fl = self._filelog
898 fl = self._filelog
899 parents = self._filelog.parents(self._filenode)
899 parents = self._filelog.parents(self._filenode)
900 pl = [(_path, node, fl) for node in parents if node != nullid]
900 pl = [(_path, node, fl) for node in parents if node != nullid]
901
901
902 r = fl.renamed(self._filenode)
902 r = fl.renamed(self._filenode)
903 if r:
903 if r:
904 # - In the simple rename case, both parent are nullid, pl is empty.
904 # - In the simple rename case, both parent are nullid, pl is empty.
905 # - In case of merge, only one of the parent is null id and should
905 # - In case of merge, only one of the parent is null id and should
906 # be replaced with the rename information. This parent is -always-
906 # be replaced with the rename information. This parent is -always-
907 # the first one.
907 # the first one.
908 #
908 #
909 # As null id have always been filtered out in the previous list
909 # As null id have always been filtered out in the previous list
910 # comprehension, inserting to 0 will always result in "replacing
910 # comprehension, inserting to 0 will always result in "replacing
911 # first nullid parent with rename information.
911 # first nullid parent with rename information.
912 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
912 pl.insert(0, (r[0], r[1], self._repo.file(r[0])))
913
913
914 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
914 return [self._parentfilectx(path, fnode, l) for path, fnode, l in pl]
915
915
916 def p1(self):
916 def p1(self):
917 return self.parents()[0]
917 return self.parents()[0]
918
918
919 def p2(self):
919 def p2(self):
920 p = self.parents()
920 p = self.parents()
921 if len(p) == 2:
921 if len(p) == 2:
922 return p[1]
922 return p[1]
923 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
923 return filectx(self._repo, self._path, fileid=-1, filelog=self._filelog)
924
924
925 def annotate(self, follow=False, linenumber=False, diffopts=None):
925 def annotate(self, follow=False, linenumber=False, diffopts=None):
926 '''returns a list of tuples of ((ctx, number), line) for each line
926 '''returns a list of tuples of ((ctx, number), line) for each line
927 in the file, where ctx is the filectx of the node where
927 in the file, where ctx is the filectx of the node where
928 that line was last changed; if linenumber parameter is true, number is
928 that line was last changed; if linenumber parameter is true, number is
929 the line number at the first appearance in the managed file, otherwise,
929 the line number at the first appearance in the managed file, otherwise,
930 number has a fixed value of False.
930 number has a fixed value of False.
931 '''
931 '''
932
932
933 def lines(text):
933 def lines(text):
934 if text.endswith("\n"):
934 if text.endswith("\n"):
935 return text.count("\n")
935 return text.count("\n")
936 return text.count("\n") + int(bool(text))
936 return text.count("\n") + int(bool(text))
937
937
938 if linenumber:
938 if linenumber:
939 def decorate(text, rev):
939 def decorate(text, rev):
940 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
940 return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
941 else:
941 else:
942 def decorate(text, rev):
942 def decorate(text, rev):
943 return ([(rev, False)] * lines(text), text)
943 return ([(rev, False)] * lines(text), text)
944
944
945 def pair(parent, child):
945 def pair(parent, child):
946 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts)
946 blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts)
947 for (a1, a2, b1, b2), t in blocks:
947 for (a1, a2, b1, b2), t in blocks:
948 # Changed blocks ('!') or blocks made only of blank lines ('~')
948 # Changed blocks ('!') or blocks made only of blank lines ('~')
949 # belong to the child.
949 # belong to the child.
950 if t == '=':
950 if t == '=':
951 child[0][b1:b2] = parent[0][a1:a2]
951 child[0][b1:b2] = parent[0][a1:a2]
952 return child
952 return child
953
953
954 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
954 getlog = util.lrucachefunc(lambda x: self._repo.file(x))
955
955
956 def parents(f):
956 def parents(f):
957 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
957 # Cut _descendantrev here to mitigate the penalty of lazy linkrev
958 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
958 # adjustment. Otherwise, p._adjustlinkrev() would walk changelog
959 # from the topmost introrev (= srcrev) down to p.linkrev() if it
959 # from the topmost introrev (= srcrev) down to p.linkrev() if it
960 # isn't an ancestor of the srcrev.
960 # isn't an ancestor of the srcrev.
961 f._changeid
961 f._changeid
962 pl = f.parents()
962 pl = f.parents()
963
963
964 # Don't return renamed parents if we aren't following.
964 # Don't return renamed parents if we aren't following.
965 if not follow:
965 if not follow:
966 pl = [p for p in pl if p.path() == f.path()]
966 pl = [p for p in pl if p.path() == f.path()]
967
967
968 # renamed filectx won't have a filelog yet, so set it
968 # renamed filectx won't have a filelog yet, so set it
969 # from the cache to save time
969 # from the cache to save time
970 for p in pl:
970 for p in pl:
971 if not '_filelog' in p.__dict__:
971 if not '_filelog' in p.__dict__:
972 p._filelog = getlog(p.path())
972 p._filelog = getlog(p.path())
973
973
974 return pl
974 return pl
975
975
976 # use linkrev to find the first changeset where self appeared
976 # use linkrev to find the first changeset where self appeared
977 base = self
977 base = self
978 introrev = self.introrev()
978 introrev = self.introrev()
979 if self.rev() != introrev:
979 if self.rev() != introrev:
980 base = self.filectx(self.filenode(), changeid=introrev)
980 base = self.filectx(self.filenode(), changeid=introrev)
981 if getattr(base, '_ancestrycontext', None) is None:
981 if getattr(base, '_ancestrycontext', None) is None:
982 cl = self._repo.changelog
982 cl = self._repo.changelog
983 if introrev is None:
983 if introrev is None:
984 # wctx is not inclusive, but works because _ancestrycontext
984 # wctx is not inclusive, but works because _ancestrycontext
985 # is used to test filelog revisions
985 # is used to test filelog revisions
986 ac = cl.ancestors([p.rev() for p in base.parents()],
986 ac = cl.ancestors([p.rev() for p in base.parents()],
987 inclusive=True)
987 inclusive=True)
988 else:
988 else:
989 ac = cl.ancestors([introrev], inclusive=True)
989 ac = cl.ancestors([introrev], inclusive=True)
990 base._ancestrycontext = ac
990 base._ancestrycontext = ac
991
991
992 # This algorithm would prefer to be recursive, but Python is a
992 # This algorithm would prefer to be recursive, but Python is a
993 # bit recursion-hostile. Instead we do an iterative
993 # bit recursion-hostile. Instead we do an iterative
994 # depth-first search.
994 # depth-first search.
995
995
996 # 1st DFS pre-calculates pcache and needed
996 # 1st DFS pre-calculates pcache and needed
997 visit = [base]
997 visit = [base]
998 pcache = {}
998 pcache = {}
999 needed = {base: 1}
999 needed = {base: 1}
1000 while visit:
1000 while visit:
1001 f = visit.pop()
1001 f = visit.pop()
1002 if f in pcache:
1002 if f in pcache:
1003 continue
1003 continue
1004 pl = parents(f)
1004 pl = parents(f)
1005 pcache[f] = pl
1005 pcache[f] = pl
1006 for p in pl:
1006 for p in pl:
1007 needed[p] = needed.get(p, 0) + 1
1007 needed[p] = needed.get(p, 0) + 1
1008 if p not in pcache:
1008 if p not in pcache:
1009 visit.append(p)
1009 visit.append(p)
1010
1010
1011 # 2nd DFS does the actual annotate
1011 # 2nd DFS does the actual annotate
1012 visit[:] = [base]
1012 visit[:] = [base]
1013 hist = {}
1013 hist = {}
1014 while visit:
1014 while visit:
1015 f = visit[-1]
1015 f = visit[-1]
1016 if f in hist:
1016 if f in hist:
1017 visit.pop()
1017 visit.pop()
1018 continue
1018 continue
1019
1019
1020 ready = True
1020 ready = True
1021 pl = pcache[f]
1021 pl = pcache[f]
1022 for p in pl:
1022 for p in pl:
1023 if p not in hist:
1023 if p not in hist:
1024 ready = False
1024 ready = False
1025 visit.append(p)
1025 visit.append(p)
1026 if ready:
1026 if ready:
1027 visit.pop()
1027 visit.pop()
1028 curr = decorate(f.data(), f)
1028 curr = decorate(f.data(), f)
1029 for p in pl:
1029 for p in pl:
1030 curr = pair(hist[p], curr)
1030 curr = pair(hist[p], curr)
1031 if needed[p] == 1:
1031 if needed[p] == 1:
1032 del hist[p]
1032 del hist[p]
1033 del needed[p]
1033 del needed[p]
1034 else:
1034 else:
1035 needed[p] -= 1
1035 needed[p] -= 1
1036
1036
1037 hist[f] = curr
1037 hist[f] = curr
1038 del pcache[f]
1038 del pcache[f]
1039
1039
1040 return zip(hist[base][0], hist[base][1].splitlines(True))
1040 return zip(hist[base][0], hist[base][1].splitlines(True))
1041
1041
1042 def ancestors(self, followfirst=False):
1042 def ancestors(self, followfirst=False):
1043 visit = {}
1043 visit = {}
1044 c = self
1044 c = self
1045 if followfirst:
1045 if followfirst:
1046 cut = 1
1046 cut = 1
1047 else:
1047 else:
1048 cut = None
1048 cut = None
1049
1049
1050 while True:
1050 while True:
1051 for parent in c.parents()[:cut]:
1051 for parent in c.parents()[:cut]:
1052 visit[(parent.linkrev(), parent.filenode())] = parent
1052 visit[(parent.linkrev(), parent.filenode())] = parent
1053 if not visit:
1053 if not visit:
1054 break
1054 break
1055 c = visit.pop(max(visit))
1055 c = visit.pop(max(visit))
1056 yield c
1056 yield c
1057
1057
1058 class filectx(basefilectx):
1058 class filectx(basefilectx):
1059 """A filecontext object makes access to data related to a particular
1059 """A filecontext object makes access to data related to a particular
1060 filerevision convenient."""
1060 filerevision convenient."""
1061 def __init__(self, repo, path, changeid=None, fileid=None,
1061 def __init__(self, repo, path, changeid=None, fileid=None,
1062 filelog=None, changectx=None):
1062 filelog=None, changectx=None):
1063 """changeid can be a changeset revision, node, or tag.
1063 """changeid can be a changeset revision, node, or tag.
1064 fileid can be a file revision or node."""
1064 fileid can be a file revision or node."""
1065 self._repo = repo
1065 self._repo = repo
1066 self._path = path
1066 self._path = path
1067
1067
1068 assert (changeid is not None
1068 assert (changeid is not None
1069 or fileid is not None
1069 or fileid is not None
1070 or changectx is not None), \
1070 or changectx is not None), \
1071 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1071 ("bad args: changeid=%r, fileid=%r, changectx=%r"
1072 % (changeid, fileid, changectx))
1072 % (changeid, fileid, changectx))
1073
1073
1074 if filelog is not None:
1074 if filelog is not None:
1075 self._filelog = filelog
1075 self._filelog = filelog
1076
1076
1077 if changeid is not None:
1077 if changeid is not None:
1078 self._changeid = changeid
1078 self._changeid = changeid
1079 if changectx is not None:
1079 if changectx is not None:
1080 self._changectx = changectx
1080 self._changectx = changectx
1081 if fileid is not None:
1081 if fileid is not None:
1082 self._fileid = fileid
1082 self._fileid = fileid
1083
1083
1084 @propertycache
1084 @propertycache
1085 def _changectx(self):
1085 def _changectx(self):
1086 try:
1086 try:
1087 return changectx(self._repo, self._changeid)
1087 return changectx(self._repo, self._changeid)
1088 except error.FilteredRepoLookupError:
1088 except error.FilteredRepoLookupError:
1089 # Linkrev may point to any revision in the repository. When the
1089 # Linkrev may point to any revision in the repository. When the
1090 # repository is filtered this may lead to `filectx` trying to build
1090 # repository is filtered this may lead to `filectx` trying to build
1091 # `changectx` for filtered revision. In such case we fallback to
1091 # `changectx` for filtered revision. In such case we fallback to
1092 # creating `changectx` on the unfiltered version of the reposition.
1092 # creating `changectx` on the unfiltered version of the reposition.
1093 # This fallback should not be an issue because `changectx` from
1093 # This fallback should not be an issue because `changectx` from
1094 # `filectx` are not used in complex operations that care about
1094 # `filectx` are not used in complex operations that care about
1095 # filtering.
1095 # filtering.
1096 #
1096 #
1097 # This fallback is a cheap and dirty fix that prevent several
1097 # This fallback is a cheap and dirty fix that prevent several
1098 # crashes. It does not ensure the behavior is correct. However the
1098 # crashes. It does not ensure the behavior is correct. However the
1099 # behavior was not correct before filtering either and "incorrect
1099 # behavior was not correct before filtering either and "incorrect
1100 # behavior" is seen as better as "crash"
1100 # behavior" is seen as better as "crash"
1101 #
1101 #
1102 # Linkrevs have several serious troubles with filtering that are
1102 # Linkrevs have several serious troubles with filtering that are
1103 # complicated to solve. Proper handling of the issue here should be
1103 # complicated to solve. Proper handling of the issue here should be
1104 # considered when solving linkrev issue are on the table.
1104 # considered when solving linkrev issue are on the table.
1105 return changectx(self._repo.unfiltered(), self._changeid)
1105 return changectx(self._repo.unfiltered(), self._changeid)
1106
1106
1107 def filectx(self, fileid, changeid=None):
1107 def filectx(self, fileid, changeid=None):
1108 '''opens an arbitrary revision of the file without
1108 '''opens an arbitrary revision of the file without
1109 opening a new filelog'''
1109 opening a new filelog'''
1110 return filectx(self._repo, self._path, fileid=fileid,
1110 return filectx(self._repo, self._path, fileid=fileid,
1111 filelog=self._filelog, changeid=changeid)
1111 filelog=self._filelog, changeid=changeid)
1112
1112
1113 def rawdata(self):
1114 return self._filelog.revision(self._filenode, raw=True)
1115
1113 def data(self):
1116 def data(self):
1114 try:
1117 try:
1115 return self._filelog.read(self._filenode)
1118 return self._filelog.read(self._filenode)
1116 except error.CensoredNodeError:
1119 except error.CensoredNodeError:
1117 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1120 if self._repo.ui.config("censor", "policy", "abort") == "ignore":
1118 return ""
1121 return ""
1119 raise error.Abort(_("censored node: %s") % short(self._filenode),
1122 raise error.Abort(_("censored node: %s") % short(self._filenode),
1120 hint=_("set censor.policy to ignore errors"))
1123 hint=_("set censor.policy to ignore errors"))
1121
1124
1122 def size(self):
1125 def size(self):
1123 return self._filelog.size(self._filerev)
1126 return self._filelog.size(self._filerev)
1124
1127
1125 def renamed(self):
1128 def renamed(self):
1126 """check if file was actually renamed in this changeset revision
1129 """check if file was actually renamed in this changeset revision
1127
1130
1128 If rename logged in file revision, we report copy for changeset only
1131 If rename logged in file revision, we report copy for changeset only
1129 if file revisions linkrev points back to the changeset in question
1132 if file revisions linkrev points back to the changeset in question
1130 or both changeset parents contain different file revisions.
1133 or both changeset parents contain different file revisions.
1131 """
1134 """
1132
1135
1133 renamed = self._filelog.renamed(self._filenode)
1136 renamed = self._filelog.renamed(self._filenode)
1134 if not renamed:
1137 if not renamed:
1135 return renamed
1138 return renamed
1136
1139
1137 if self.rev() == self.linkrev():
1140 if self.rev() == self.linkrev():
1138 return renamed
1141 return renamed
1139
1142
1140 name = self.path()
1143 name = self.path()
1141 fnode = self._filenode
1144 fnode = self._filenode
1142 for p in self._changectx.parents():
1145 for p in self._changectx.parents():
1143 try:
1146 try:
1144 if fnode == p.filenode(name):
1147 if fnode == p.filenode(name):
1145 return None
1148 return None
1146 except error.LookupError:
1149 except error.LookupError:
1147 pass
1150 pass
1148 return renamed
1151 return renamed
1149
1152
1150 def children(self):
1153 def children(self):
1151 # hard for renames
1154 # hard for renames
1152 c = self._filelog.children(self._filenode)
1155 c = self._filelog.children(self._filenode)
1153 return [filectx(self._repo, self._path, fileid=x,
1156 return [filectx(self._repo, self._path, fileid=x,
1154 filelog=self._filelog) for x in c]
1157 filelog=self._filelog) for x in c]
1155
1158
1156 def blockancestors(fctx, fromline, toline):
1159 def blockancestors(fctx, fromline, toline):
1157 """Yield ancestors of `fctx` with respect to the block of lines within
1160 """Yield ancestors of `fctx` with respect to the block of lines within
1158 `fromline`-`toline` range.
1161 `fromline`-`toline` range.
1159 """
1162 """
1160 def changesrange(fctx1, fctx2, linerange2):
1163 def changesrange(fctx1, fctx2, linerange2):
1161 """Return `(diffinrange, linerange1)` where `diffinrange` is True
1164 """Return `(diffinrange, linerange1)` where `diffinrange` is True
1162 if diff from fctx2 to fctx1 has changes in linerange2 and
1165 if diff from fctx2 to fctx1 has changes in linerange2 and
1163 `linerange1` is the new line range for fctx1.
1166 `linerange1` is the new line range for fctx1.
1164 """
1167 """
1165 diffopts = patch.diffopts(fctx._repo.ui)
1168 diffopts = patch.diffopts(fctx._repo.ui)
1166 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
1169 blocks = mdiff.allblocks(fctx1.data(), fctx2.data(), diffopts)
1167 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
1170 filteredblocks, linerange1 = mdiff.blocksinrange(blocks, linerange2)
1168 diffinrange = any(stype == '!' for _, stype in filteredblocks)
1171 diffinrange = any(stype == '!' for _, stype in filteredblocks)
1169 return diffinrange, linerange1
1172 return diffinrange, linerange1
1170
1173
1171 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
1174 visit = {(fctx.linkrev(), fctx.filenode()): (fctx, (fromline, toline))}
1172 while visit:
1175 while visit:
1173 c, linerange2 = visit.pop(max(visit))
1176 c, linerange2 = visit.pop(max(visit))
1174 pl = c.parents()
1177 pl = c.parents()
1175 if not pl:
1178 if not pl:
1176 # The block originates from the initial revision.
1179 # The block originates from the initial revision.
1177 yield c
1180 yield c
1178 continue
1181 continue
1179 inrange = False
1182 inrange = False
1180 for p in pl:
1183 for p in pl:
1181 inrangep, linerange1 = changesrange(p, c, linerange2)
1184 inrangep, linerange1 = changesrange(p, c, linerange2)
1182 inrange = inrange or inrangep
1185 inrange = inrange or inrangep
1183 if linerange1[0] == linerange1[1]:
1186 if linerange1[0] == linerange1[1]:
1184 # Parent's linerange is empty, meaning that the block got
1187 # Parent's linerange is empty, meaning that the block got
1185 # introduced in this revision; no need to go futher in this
1188 # introduced in this revision; no need to go futher in this
1186 # branch.
1189 # branch.
1187 continue
1190 continue
1188 visit[p.linkrev(), p.filenode()] = p, linerange1
1191 visit[p.linkrev(), p.filenode()] = p, linerange1
1189 if inrange:
1192 if inrange:
1190 yield c
1193 yield c
1191
1194
1192 class committablectx(basectx):
1195 class committablectx(basectx):
1193 """A committablectx object provides common functionality for a context that
1196 """A committablectx object provides common functionality for a context that
1194 wants the ability to commit, e.g. workingctx or memctx."""
1197 wants the ability to commit, e.g. workingctx or memctx."""
1195 def __init__(self, repo, text="", user=None, date=None, extra=None,
1198 def __init__(self, repo, text="", user=None, date=None, extra=None,
1196 changes=None):
1199 changes=None):
1197 self._repo = repo
1200 self._repo = repo
1198 self._rev = None
1201 self._rev = None
1199 self._node = None
1202 self._node = None
1200 self._text = text
1203 self._text = text
1201 if date:
1204 if date:
1202 self._date = util.parsedate(date)
1205 self._date = util.parsedate(date)
1203 if user:
1206 if user:
1204 self._user = user
1207 self._user = user
1205 if changes:
1208 if changes:
1206 self._status = changes
1209 self._status = changes
1207
1210
1208 self._extra = {}
1211 self._extra = {}
1209 if extra:
1212 if extra:
1210 self._extra = extra.copy()
1213 self._extra = extra.copy()
1211 if 'branch' not in self._extra:
1214 if 'branch' not in self._extra:
1212 try:
1215 try:
1213 branch = encoding.fromlocal(self._repo.dirstate.branch())
1216 branch = encoding.fromlocal(self._repo.dirstate.branch())
1214 except UnicodeDecodeError:
1217 except UnicodeDecodeError:
1215 raise error.Abort(_('branch name not in UTF-8!'))
1218 raise error.Abort(_('branch name not in UTF-8!'))
1216 self._extra['branch'] = branch
1219 self._extra['branch'] = branch
1217 if self._extra['branch'] == '':
1220 if self._extra['branch'] == '':
1218 self._extra['branch'] = 'default'
1221 self._extra['branch'] = 'default'
1219
1222
1220 def __str__(self):
1223 def __str__(self):
1221 return str(self._parents[0]) + "+"
1224 return str(self._parents[0]) + "+"
1222
1225
1223 def __nonzero__(self):
1226 def __nonzero__(self):
1224 return True
1227 return True
1225
1228
1226 def _buildflagfunc(self):
1229 def _buildflagfunc(self):
1227 # Create a fallback function for getting file flags when the
1230 # Create a fallback function for getting file flags when the
1228 # filesystem doesn't support them
1231 # filesystem doesn't support them
1229
1232
1230 copiesget = self._repo.dirstate.copies().get
1233 copiesget = self._repo.dirstate.copies().get
1231 parents = self.parents()
1234 parents = self.parents()
1232 if len(parents) < 2:
1235 if len(parents) < 2:
1233 # when we have one parent, it's easy: copy from parent
1236 # when we have one parent, it's easy: copy from parent
1234 man = parents[0].manifest()
1237 man = parents[0].manifest()
1235 def func(f):
1238 def func(f):
1236 f = copiesget(f, f)
1239 f = copiesget(f, f)
1237 return man.flags(f)
1240 return man.flags(f)
1238 else:
1241 else:
1239 # merges are tricky: we try to reconstruct the unstored
1242 # merges are tricky: we try to reconstruct the unstored
1240 # result from the merge (issue1802)
1243 # result from the merge (issue1802)
1241 p1, p2 = parents
1244 p1, p2 = parents
1242 pa = p1.ancestor(p2)
1245 pa = p1.ancestor(p2)
1243 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1246 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
1244
1247
1245 def func(f):
1248 def func(f):
1246 f = copiesget(f, f) # may be wrong for merges with copies
1249 f = copiesget(f, f) # may be wrong for merges with copies
1247 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1250 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
1248 if fl1 == fl2:
1251 if fl1 == fl2:
1249 return fl1
1252 return fl1
1250 if fl1 == fla:
1253 if fl1 == fla:
1251 return fl2
1254 return fl2
1252 if fl2 == fla:
1255 if fl2 == fla:
1253 return fl1
1256 return fl1
1254 return '' # punt for conflicts
1257 return '' # punt for conflicts
1255
1258
1256 return func
1259 return func
1257
1260
1258 @propertycache
1261 @propertycache
1259 def _flagfunc(self):
1262 def _flagfunc(self):
1260 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1263 return self._repo.dirstate.flagfunc(self._buildflagfunc)
1261
1264
1262 @propertycache
1265 @propertycache
1263 def _manifest(self):
1266 def _manifest(self):
1264 """generate a manifest corresponding to the values in self._status
1267 """generate a manifest corresponding to the values in self._status
1265
1268
1266 This reuse the file nodeid from parent, but we append an extra letter
1269 This reuse the file nodeid from parent, but we append an extra letter
1267 when modified. Modified files get an extra 'm' while added files get
1270 when modified. Modified files get an extra 'm' while added files get
1268 an extra 'a'. This is used by manifests merge to see that files
1271 an extra 'a'. This is used by manifests merge to see that files
1269 are different and by update logic to avoid deleting newly added files.
1272 are different and by update logic to avoid deleting newly added files.
1270 """
1273 """
1271 parents = self.parents()
1274 parents = self.parents()
1272
1275
1273 man = parents[0].manifest().copy()
1276 man = parents[0].manifest().copy()
1274
1277
1275 ff = self._flagfunc
1278 ff = self._flagfunc
1276 for i, l in ((addednodeid, self._status.added),
1279 for i, l in ((addednodeid, self._status.added),
1277 (modifiednodeid, self._status.modified)):
1280 (modifiednodeid, self._status.modified)):
1278 for f in l:
1281 for f in l:
1279 man[f] = i
1282 man[f] = i
1280 try:
1283 try:
1281 man.setflag(f, ff(f))
1284 man.setflag(f, ff(f))
1282 except OSError:
1285 except OSError:
1283 pass
1286 pass
1284
1287
1285 for f in self._status.deleted + self._status.removed:
1288 for f in self._status.deleted + self._status.removed:
1286 if f in man:
1289 if f in man:
1287 del man[f]
1290 del man[f]
1288
1291
1289 return man
1292 return man
1290
1293
1291 @propertycache
1294 @propertycache
1292 def _status(self):
1295 def _status(self):
1293 return self._repo.status()
1296 return self._repo.status()
1294
1297
1295 @propertycache
1298 @propertycache
1296 def _user(self):
1299 def _user(self):
1297 return self._repo.ui.username()
1300 return self._repo.ui.username()
1298
1301
1299 @propertycache
1302 @propertycache
1300 def _date(self):
1303 def _date(self):
1301 return util.makedate()
1304 return util.makedate()
1302
1305
1303 def subrev(self, subpath):
1306 def subrev(self, subpath):
1304 return None
1307 return None
1305
1308
1306 def manifestnode(self):
1309 def manifestnode(self):
1307 return None
1310 return None
1308 def user(self):
1311 def user(self):
1309 return self._user or self._repo.ui.username()
1312 return self._user or self._repo.ui.username()
1310 def date(self):
1313 def date(self):
1311 return self._date
1314 return self._date
1312 def description(self):
1315 def description(self):
1313 return self._text
1316 return self._text
1314 def files(self):
1317 def files(self):
1315 return sorted(self._status.modified + self._status.added +
1318 return sorted(self._status.modified + self._status.added +
1316 self._status.removed)
1319 self._status.removed)
1317
1320
1318 def modified(self):
1321 def modified(self):
1319 return self._status.modified
1322 return self._status.modified
1320 def added(self):
1323 def added(self):
1321 return self._status.added
1324 return self._status.added
1322 def removed(self):
1325 def removed(self):
1323 return self._status.removed
1326 return self._status.removed
1324 def deleted(self):
1327 def deleted(self):
1325 return self._status.deleted
1328 return self._status.deleted
1326 def branch(self):
1329 def branch(self):
1327 return encoding.tolocal(self._extra['branch'])
1330 return encoding.tolocal(self._extra['branch'])
1328 def closesbranch(self):
1331 def closesbranch(self):
1329 return 'close' in self._extra
1332 return 'close' in self._extra
1330 def extra(self):
1333 def extra(self):
1331 return self._extra
1334 return self._extra
1332
1335
1333 def tags(self):
1336 def tags(self):
1334 return []
1337 return []
1335
1338
1336 def bookmarks(self):
1339 def bookmarks(self):
1337 b = []
1340 b = []
1338 for p in self.parents():
1341 for p in self.parents():
1339 b.extend(p.bookmarks())
1342 b.extend(p.bookmarks())
1340 return b
1343 return b
1341
1344
1342 def phase(self):
1345 def phase(self):
1343 phase = phases.draft # default phase to draft
1346 phase = phases.draft # default phase to draft
1344 for p in self.parents():
1347 for p in self.parents():
1345 phase = max(phase, p.phase())
1348 phase = max(phase, p.phase())
1346 return phase
1349 return phase
1347
1350
1348 def hidden(self):
1351 def hidden(self):
1349 return False
1352 return False
1350
1353
1351 def children(self):
1354 def children(self):
1352 return []
1355 return []
1353
1356
1354 def flags(self, path):
1357 def flags(self, path):
1355 if '_manifest' in self.__dict__:
1358 if '_manifest' in self.__dict__:
1356 try:
1359 try:
1357 return self._manifest.flags(path)
1360 return self._manifest.flags(path)
1358 except KeyError:
1361 except KeyError:
1359 return ''
1362 return ''
1360
1363
1361 try:
1364 try:
1362 return self._flagfunc(path)
1365 return self._flagfunc(path)
1363 except OSError:
1366 except OSError:
1364 return ''
1367 return ''
1365
1368
1366 def ancestor(self, c2):
1369 def ancestor(self, c2):
1367 """return the "best" ancestor context of self and c2"""
1370 """return the "best" ancestor context of self and c2"""
1368 return self._parents[0].ancestor(c2) # punt on two parents for now
1371 return self._parents[0].ancestor(c2) # punt on two parents for now
1369
1372
1370 def walk(self, match):
1373 def walk(self, match):
1371 '''Generates matching file names.'''
1374 '''Generates matching file names.'''
1372 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1375 return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
1373 True, False))
1376 True, False))
1374
1377
1375 def matches(self, match):
1378 def matches(self, match):
1376 return sorted(self._repo.dirstate.matches(match))
1379 return sorted(self._repo.dirstate.matches(match))
1377
1380
1378 def ancestors(self):
1381 def ancestors(self):
1379 for p in self._parents:
1382 for p in self._parents:
1380 yield p
1383 yield p
1381 for a in self._repo.changelog.ancestors(
1384 for a in self._repo.changelog.ancestors(
1382 [p.rev() for p in self._parents]):
1385 [p.rev() for p in self._parents]):
1383 yield changectx(self._repo, a)
1386 yield changectx(self._repo, a)
1384
1387
1385 def markcommitted(self, node):
1388 def markcommitted(self, node):
1386 """Perform post-commit cleanup necessary after committing this ctx
1389 """Perform post-commit cleanup necessary after committing this ctx
1387
1390
1388 Specifically, this updates backing stores this working context
1391 Specifically, this updates backing stores this working context
1389 wraps to reflect the fact that the changes reflected by this
1392 wraps to reflect the fact that the changes reflected by this
1390 workingctx have been committed. For example, it marks
1393 workingctx have been committed. For example, it marks
1391 modified and added files as normal in the dirstate.
1394 modified and added files as normal in the dirstate.
1392
1395
1393 """
1396 """
1394
1397
1395 self._repo.dirstate.beginparentchange()
1398 self._repo.dirstate.beginparentchange()
1396 for f in self.modified() + self.added():
1399 for f in self.modified() + self.added():
1397 self._repo.dirstate.normal(f)
1400 self._repo.dirstate.normal(f)
1398 for f in self.removed():
1401 for f in self.removed():
1399 self._repo.dirstate.drop(f)
1402 self._repo.dirstate.drop(f)
1400 self._repo.dirstate.setparents(node)
1403 self._repo.dirstate.setparents(node)
1401 self._repo.dirstate.endparentchange()
1404 self._repo.dirstate.endparentchange()
1402
1405
1403 # write changes out explicitly, because nesting wlock at
1406 # write changes out explicitly, because nesting wlock at
1404 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1407 # runtime may prevent 'wlock.release()' in 'repo.commit()'
1405 # from immediately doing so for subsequent changing files
1408 # from immediately doing so for subsequent changing files
1406 self._repo.dirstate.write(self._repo.currenttransaction())
1409 self._repo.dirstate.write(self._repo.currenttransaction())
1407
1410
1408 class workingctx(committablectx):
1411 class workingctx(committablectx):
1409 """A workingctx object makes access to data related to
1412 """A workingctx object makes access to data related to
1410 the current working directory convenient.
1413 the current working directory convenient.
1411 date - any valid date string or (unixtime, offset), or None.
1414 date - any valid date string or (unixtime, offset), or None.
1412 user - username string, or None.
1415 user - username string, or None.
1413 extra - a dictionary of extra values, or None.
1416 extra - a dictionary of extra values, or None.
1414 changes - a list of file lists as returned by localrepo.status()
1417 changes - a list of file lists as returned by localrepo.status()
1415 or None to use the repository status.
1418 or None to use the repository status.
1416 """
1419 """
1417 def __init__(self, repo, text="", user=None, date=None, extra=None,
1420 def __init__(self, repo, text="", user=None, date=None, extra=None,
1418 changes=None):
1421 changes=None):
1419 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1422 super(workingctx, self).__init__(repo, text, user, date, extra, changes)
1420
1423
1421 def __iter__(self):
1424 def __iter__(self):
1422 d = self._repo.dirstate
1425 d = self._repo.dirstate
1423 for f in d:
1426 for f in d:
1424 if d[f] != 'r':
1427 if d[f] != 'r':
1425 yield f
1428 yield f
1426
1429
1427 def __contains__(self, key):
1430 def __contains__(self, key):
1428 return self._repo.dirstate[key] not in "?r"
1431 return self._repo.dirstate[key] not in "?r"
1429
1432
1430 def hex(self):
1433 def hex(self):
1431 return hex(wdirid)
1434 return hex(wdirid)
1432
1435
1433 @propertycache
1436 @propertycache
1434 def _parents(self):
1437 def _parents(self):
1435 p = self._repo.dirstate.parents()
1438 p = self._repo.dirstate.parents()
1436 if p[1] == nullid:
1439 if p[1] == nullid:
1437 p = p[:-1]
1440 p = p[:-1]
1438 return [changectx(self._repo, x) for x in p]
1441 return [changectx(self._repo, x) for x in p]
1439
1442
1440 def filectx(self, path, filelog=None):
1443 def filectx(self, path, filelog=None):
1441 """get a file context from the working directory"""
1444 """get a file context from the working directory"""
1442 return workingfilectx(self._repo, path, workingctx=self,
1445 return workingfilectx(self._repo, path, workingctx=self,
1443 filelog=filelog)
1446 filelog=filelog)
1444
1447
1445 def dirty(self, missing=False, merge=True, branch=True):
1448 def dirty(self, missing=False, merge=True, branch=True):
1446 "check whether a working directory is modified"
1449 "check whether a working directory is modified"
1447 # check subrepos first
1450 # check subrepos first
1448 for s in sorted(self.substate):
1451 for s in sorted(self.substate):
1449 if self.sub(s).dirty():
1452 if self.sub(s).dirty():
1450 return True
1453 return True
1451 # check current working dir
1454 # check current working dir
1452 return ((merge and self.p2()) or
1455 return ((merge and self.p2()) or
1453 (branch and self.branch() != self.p1().branch()) or
1456 (branch and self.branch() != self.p1().branch()) or
1454 self.modified() or self.added() or self.removed() or
1457 self.modified() or self.added() or self.removed() or
1455 (missing and self.deleted()))
1458 (missing and self.deleted()))
1456
1459
1457 def add(self, list, prefix=""):
1460 def add(self, list, prefix=""):
1458 join = lambda f: os.path.join(prefix, f)
1461 join = lambda f: os.path.join(prefix, f)
1459 with self._repo.wlock():
1462 with self._repo.wlock():
1460 ui, ds = self._repo.ui, self._repo.dirstate
1463 ui, ds = self._repo.ui, self._repo.dirstate
1461 rejected = []
1464 rejected = []
1462 lstat = self._repo.wvfs.lstat
1465 lstat = self._repo.wvfs.lstat
1463 for f in list:
1466 for f in list:
1464 scmutil.checkportable(ui, join(f))
1467 scmutil.checkportable(ui, join(f))
1465 try:
1468 try:
1466 st = lstat(f)
1469 st = lstat(f)
1467 except OSError:
1470 except OSError:
1468 ui.warn(_("%s does not exist!\n") % join(f))
1471 ui.warn(_("%s does not exist!\n") % join(f))
1469 rejected.append(f)
1472 rejected.append(f)
1470 continue
1473 continue
1471 if st.st_size > 10000000:
1474 if st.st_size > 10000000:
1472 ui.warn(_("%s: up to %d MB of RAM may be required "
1475 ui.warn(_("%s: up to %d MB of RAM may be required "
1473 "to manage this file\n"
1476 "to manage this file\n"
1474 "(use 'hg revert %s' to cancel the "
1477 "(use 'hg revert %s' to cancel the "
1475 "pending addition)\n")
1478 "pending addition)\n")
1476 % (f, 3 * st.st_size // 1000000, join(f)))
1479 % (f, 3 * st.st_size // 1000000, join(f)))
1477 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1480 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1478 ui.warn(_("%s not added: only files and symlinks "
1481 ui.warn(_("%s not added: only files and symlinks "
1479 "supported currently\n") % join(f))
1482 "supported currently\n") % join(f))
1480 rejected.append(f)
1483 rejected.append(f)
1481 elif ds[f] in 'amn':
1484 elif ds[f] in 'amn':
1482 ui.warn(_("%s already tracked!\n") % join(f))
1485 ui.warn(_("%s already tracked!\n") % join(f))
1483 elif ds[f] == 'r':
1486 elif ds[f] == 'r':
1484 ds.normallookup(f)
1487 ds.normallookup(f)
1485 else:
1488 else:
1486 ds.add(f)
1489 ds.add(f)
1487 return rejected
1490 return rejected
1488
1491
1489 def forget(self, files, prefix=""):
1492 def forget(self, files, prefix=""):
1490 join = lambda f: os.path.join(prefix, f)
1493 join = lambda f: os.path.join(prefix, f)
1491 with self._repo.wlock():
1494 with self._repo.wlock():
1492 rejected = []
1495 rejected = []
1493 for f in files:
1496 for f in files:
1494 if f not in self._repo.dirstate:
1497 if f not in self._repo.dirstate:
1495 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1498 self._repo.ui.warn(_("%s not tracked!\n") % join(f))
1496 rejected.append(f)
1499 rejected.append(f)
1497 elif self._repo.dirstate[f] != 'a':
1500 elif self._repo.dirstate[f] != 'a':
1498 self._repo.dirstate.remove(f)
1501 self._repo.dirstate.remove(f)
1499 else:
1502 else:
1500 self._repo.dirstate.drop(f)
1503 self._repo.dirstate.drop(f)
1501 return rejected
1504 return rejected
1502
1505
1503 def undelete(self, list):
1506 def undelete(self, list):
1504 pctxs = self.parents()
1507 pctxs = self.parents()
1505 with self._repo.wlock():
1508 with self._repo.wlock():
1506 for f in list:
1509 for f in list:
1507 if self._repo.dirstate[f] != 'r':
1510 if self._repo.dirstate[f] != 'r':
1508 self._repo.ui.warn(_("%s not removed!\n") % f)
1511 self._repo.ui.warn(_("%s not removed!\n") % f)
1509 else:
1512 else:
1510 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1513 fctx = f in pctxs[0] and pctxs[0][f] or pctxs[1][f]
1511 t = fctx.data()
1514 t = fctx.data()
1512 self._repo.wwrite(f, t, fctx.flags())
1515 self._repo.wwrite(f, t, fctx.flags())
1513 self._repo.dirstate.normal(f)
1516 self._repo.dirstate.normal(f)
1514
1517
1515 def copy(self, source, dest):
1518 def copy(self, source, dest):
1516 try:
1519 try:
1517 st = self._repo.wvfs.lstat(dest)
1520 st = self._repo.wvfs.lstat(dest)
1518 except OSError as err:
1521 except OSError as err:
1519 if err.errno != errno.ENOENT:
1522 if err.errno != errno.ENOENT:
1520 raise
1523 raise
1521 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1524 self._repo.ui.warn(_("%s does not exist!\n") % dest)
1522 return
1525 return
1523 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1526 if not (stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode)):
1524 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1527 self._repo.ui.warn(_("copy failed: %s is not a file or a "
1525 "symbolic link\n") % dest)
1528 "symbolic link\n") % dest)
1526 else:
1529 else:
1527 with self._repo.wlock():
1530 with self._repo.wlock():
1528 if self._repo.dirstate[dest] in '?':
1531 if self._repo.dirstate[dest] in '?':
1529 self._repo.dirstate.add(dest)
1532 self._repo.dirstate.add(dest)
1530 elif self._repo.dirstate[dest] in 'r':
1533 elif self._repo.dirstate[dest] in 'r':
1531 self._repo.dirstate.normallookup(dest)
1534 self._repo.dirstate.normallookup(dest)
1532 self._repo.dirstate.copy(source, dest)
1535 self._repo.dirstate.copy(source, dest)
1533
1536
1534 def match(self, pats=[], include=None, exclude=None, default='glob',
1537 def match(self, pats=[], include=None, exclude=None, default='glob',
1535 listsubrepos=False, badfn=None):
1538 listsubrepos=False, badfn=None):
1536 r = self._repo
1539 r = self._repo
1537
1540
1538 # Only a case insensitive filesystem needs magic to translate user input
1541 # Only a case insensitive filesystem needs magic to translate user input
1539 # to actual case in the filesystem.
1542 # to actual case in the filesystem.
1540 if not util.fscasesensitive(r.root):
1543 if not util.fscasesensitive(r.root):
1541 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1544 return matchmod.icasefsmatcher(r.root, r.getcwd(), pats, include,
1542 exclude, default, r.auditor, self,
1545 exclude, default, r.auditor, self,
1543 listsubrepos=listsubrepos,
1546 listsubrepos=listsubrepos,
1544 badfn=badfn)
1547 badfn=badfn)
1545 return matchmod.match(r.root, r.getcwd(), pats,
1548 return matchmod.match(r.root, r.getcwd(), pats,
1546 include, exclude, default,
1549 include, exclude, default,
1547 auditor=r.auditor, ctx=self,
1550 auditor=r.auditor, ctx=self,
1548 listsubrepos=listsubrepos, badfn=badfn)
1551 listsubrepos=listsubrepos, badfn=badfn)
1549
1552
1550 def _filtersuspectsymlink(self, files):
1553 def _filtersuspectsymlink(self, files):
1551 if not files or self._repo.dirstate._checklink:
1554 if not files or self._repo.dirstate._checklink:
1552 return files
1555 return files
1553
1556
1554 # Symlink placeholders may get non-symlink-like contents
1557 # Symlink placeholders may get non-symlink-like contents
1555 # via user error or dereferencing by NFS or Samba servers,
1558 # via user error or dereferencing by NFS or Samba servers,
1556 # so we filter out any placeholders that don't look like a
1559 # so we filter out any placeholders that don't look like a
1557 # symlink
1560 # symlink
1558 sane = []
1561 sane = []
1559 for f in files:
1562 for f in files:
1560 if self.flags(f) == 'l':
1563 if self.flags(f) == 'l':
1561 d = self[f].data()
1564 d = self[f].data()
1562 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1565 if d == '' or len(d) >= 1024 or '\n' in d or util.binary(d):
1563 self._repo.ui.debug('ignoring suspect symlink placeholder'
1566 self._repo.ui.debug('ignoring suspect symlink placeholder'
1564 ' "%s"\n' % f)
1567 ' "%s"\n' % f)
1565 continue
1568 continue
1566 sane.append(f)
1569 sane.append(f)
1567 return sane
1570 return sane
1568
1571
1569 def _checklookup(self, files):
1572 def _checklookup(self, files):
1570 # check for any possibly clean files
1573 # check for any possibly clean files
1571 if not files:
1574 if not files:
1572 return [], []
1575 return [], []
1573
1576
1574 modified = []
1577 modified = []
1575 fixup = []
1578 fixup = []
1576 pctx = self._parents[0]
1579 pctx = self._parents[0]
1577 # do a full compare of any files that might have changed
1580 # do a full compare of any files that might have changed
1578 for f in sorted(files):
1581 for f in sorted(files):
1579 if (f not in pctx or self.flags(f) != pctx.flags(f)
1582 if (f not in pctx or self.flags(f) != pctx.flags(f)
1580 or pctx[f].cmp(self[f])):
1583 or pctx[f].cmp(self[f])):
1581 modified.append(f)
1584 modified.append(f)
1582 else:
1585 else:
1583 fixup.append(f)
1586 fixup.append(f)
1584
1587
1585 # update dirstate for files that are actually clean
1588 # update dirstate for files that are actually clean
1586 if fixup:
1589 if fixup:
1587 try:
1590 try:
1588 # updating the dirstate is optional
1591 # updating the dirstate is optional
1589 # so we don't wait on the lock
1592 # so we don't wait on the lock
1590 # wlock can invalidate the dirstate, so cache normal _after_
1593 # wlock can invalidate the dirstate, so cache normal _after_
1591 # taking the lock
1594 # taking the lock
1592 with self._repo.wlock(False):
1595 with self._repo.wlock(False):
1593 normal = self._repo.dirstate.normal
1596 normal = self._repo.dirstate.normal
1594 for f in fixup:
1597 for f in fixup:
1595 normal(f)
1598 normal(f)
1596 # write changes out explicitly, because nesting
1599 # write changes out explicitly, because nesting
1597 # wlock at runtime may prevent 'wlock.release()'
1600 # wlock at runtime may prevent 'wlock.release()'
1598 # after this block from doing so for subsequent
1601 # after this block from doing so for subsequent
1599 # changing files
1602 # changing files
1600 self._repo.dirstate.write(self._repo.currenttransaction())
1603 self._repo.dirstate.write(self._repo.currenttransaction())
1601 except error.LockError:
1604 except error.LockError:
1602 pass
1605 pass
1603 return modified, fixup
1606 return modified, fixup
1604
1607
1605 def _manifestmatches(self, match, s):
1608 def _manifestmatches(self, match, s):
1606 """Slow path for workingctx
1609 """Slow path for workingctx
1607
1610
1608 The fast path is when we compare the working directory to its parent
1611 The fast path is when we compare the working directory to its parent
1609 which means this function is comparing with a non-parent; therefore we
1612 which means this function is comparing with a non-parent; therefore we
1610 need to build a manifest and return what matches.
1613 need to build a manifest and return what matches.
1611 """
1614 """
1612 mf = self._repo['.']._manifestmatches(match, s)
1615 mf = self._repo['.']._manifestmatches(match, s)
1613 for f in s.modified + s.added:
1616 for f in s.modified + s.added:
1614 mf[f] = newnodeid
1617 mf[f] = newnodeid
1615 mf.setflag(f, self.flags(f))
1618 mf.setflag(f, self.flags(f))
1616 for f in s.removed:
1619 for f in s.removed:
1617 if f in mf:
1620 if f in mf:
1618 del mf[f]
1621 del mf[f]
1619 return mf
1622 return mf
1620
1623
1621 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1624 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1622 unknown=False):
1625 unknown=False):
1623 '''Gets the status from the dirstate -- internal use only.'''
1626 '''Gets the status from the dirstate -- internal use only.'''
1624 listignored, listclean, listunknown = ignored, clean, unknown
1627 listignored, listclean, listunknown = ignored, clean, unknown
1625 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1628 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1626 subrepos = []
1629 subrepos = []
1627 if '.hgsub' in self:
1630 if '.hgsub' in self:
1628 subrepos = sorted(self.substate)
1631 subrepos = sorted(self.substate)
1629 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1632 cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
1630 listclean, listunknown)
1633 listclean, listunknown)
1631
1634
1632 # check for any possibly clean files
1635 # check for any possibly clean files
1633 if cmp:
1636 if cmp:
1634 modified2, fixup = self._checklookup(cmp)
1637 modified2, fixup = self._checklookup(cmp)
1635 s.modified.extend(modified2)
1638 s.modified.extend(modified2)
1636
1639
1637 # update dirstate for files that are actually clean
1640 # update dirstate for files that are actually clean
1638 if fixup and listclean:
1641 if fixup and listclean:
1639 s.clean.extend(fixup)
1642 s.clean.extend(fixup)
1640
1643
1641 if match.always():
1644 if match.always():
1642 # cache for performance
1645 # cache for performance
1643 if s.unknown or s.ignored or s.clean:
1646 if s.unknown or s.ignored or s.clean:
1644 # "_status" is cached with list*=False in the normal route
1647 # "_status" is cached with list*=False in the normal route
1645 self._status = scmutil.status(s.modified, s.added, s.removed,
1648 self._status = scmutil.status(s.modified, s.added, s.removed,
1646 s.deleted, [], [], [])
1649 s.deleted, [], [], [])
1647 else:
1650 else:
1648 self._status = s
1651 self._status = s
1649
1652
1650 return s
1653 return s
1651
1654
1652 def _buildstatus(self, other, s, match, listignored, listclean,
1655 def _buildstatus(self, other, s, match, listignored, listclean,
1653 listunknown):
1656 listunknown):
1654 """build a status with respect to another context
1657 """build a status with respect to another context
1655
1658
1656 This includes logic for maintaining the fast path of status when
1659 This includes logic for maintaining the fast path of status when
1657 comparing the working directory against its parent, which is to skip
1660 comparing the working directory against its parent, which is to skip
1658 building a new manifest if self (working directory) is not comparing
1661 building a new manifest if self (working directory) is not comparing
1659 against its parent (repo['.']).
1662 against its parent (repo['.']).
1660 """
1663 """
1661 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1664 s = self._dirstatestatus(match, listignored, listclean, listunknown)
1662 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1665 # Filter out symlinks that, in the case of FAT32 and NTFS filesystems,
1663 # might have accidentally ended up with the entire contents of the file
1666 # might have accidentally ended up with the entire contents of the file
1664 # they are supposed to be linking to.
1667 # they are supposed to be linking to.
1665 s.modified[:] = self._filtersuspectsymlink(s.modified)
1668 s.modified[:] = self._filtersuspectsymlink(s.modified)
1666 if other != self._repo['.']:
1669 if other != self._repo['.']:
1667 s = super(workingctx, self)._buildstatus(other, s, match,
1670 s = super(workingctx, self)._buildstatus(other, s, match,
1668 listignored, listclean,
1671 listignored, listclean,
1669 listunknown)
1672 listunknown)
1670 return s
1673 return s
1671
1674
1672 def _matchstatus(self, other, match):
1675 def _matchstatus(self, other, match):
1673 """override the match method with a filter for directory patterns
1676 """override the match method with a filter for directory patterns
1674
1677
1675 We use inheritance to customize the match.bad method only in cases of
1678 We use inheritance to customize the match.bad method only in cases of
1676 workingctx since it belongs only to the working directory when
1679 workingctx since it belongs only to the working directory when
1677 comparing against the parent changeset.
1680 comparing against the parent changeset.
1678
1681
1679 If we aren't comparing against the working directory's parent, then we
1682 If we aren't comparing against the working directory's parent, then we
1680 just use the default match object sent to us.
1683 just use the default match object sent to us.
1681 """
1684 """
1682 superself = super(workingctx, self)
1685 superself = super(workingctx, self)
1683 match = superself._matchstatus(other, match)
1686 match = superself._matchstatus(other, match)
1684 if other != self._repo['.']:
1687 if other != self._repo['.']:
1685 def bad(f, msg):
1688 def bad(f, msg):
1686 # 'f' may be a directory pattern from 'match.files()',
1689 # 'f' may be a directory pattern from 'match.files()',
1687 # so 'f not in ctx1' is not enough
1690 # so 'f not in ctx1' is not enough
1688 if f not in other and not other.hasdir(f):
1691 if f not in other and not other.hasdir(f):
1689 self._repo.ui.warn('%s: %s\n' %
1692 self._repo.ui.warn('%s: %s\n' %
1690 (self._repo.dirstate.pathto(f), msg))
1693 (self._repo.dirstate.pathto(f), msg))
1691 match.bad = bad
1694 match.bad = bad
1692 return match
1695 return match
1693
1696
1694 class committablefilectx(basefilectx):
1697 class committablefilectx(basefilectx):
1695 """A committablefilectx provides common functionality for a file context
1698 """A committablefilectx provides common functionality for a file context
1696 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1699 that wants the ability to commit, e.g. workingfilectx or memfilectx."""
1697 def __init__(self, repo, path, filelog=None, ctx=None):
1700 def __init__(self, repo, path, filelog=None, ctx=None):
1698 self._repo = repo
1701 self._repo = repo
1699 self._path = path
1702 self._path = path
1700 self._changeid = None
1703 self._changeid = None
1701 self._filerev = self._filenode = None
1704 self._filerev = self._filenode = None
1702
1705
1703 if filelog is not None:
1706 if filelog is not None:
1704 self._filelog = filelog
1707 self._filelog = filelog
1705 if ctx:
1708 if ctx:
1706 self._changectx = ctx
1709 self._changectx = ctx
1707
1710
1708 def __nonzero__(self):
1711 def __nonzero__(self):
1709 return True
1712 return True
1710
1713
1711 def linkrev(self):
1714 def linkrev(self):
1712 # linked to self._changectx no matter if file is modified or not
1715 # linked to self._changectx no matter if file is modified or not
1713 return self.rev()
1716 return self.rev()
1714
1717
1715 def parents(self):
1718 def parents(self):
1716 '''return parent filectxs, following copies if necessary'''
1719 '''return parent filectxs, following copies if necessary'''
1717 def filenode(ctx, path):
1720 def filenode(ctx, path):
1718 return ctx._manifest.get(path, nullid)
1721 return ctx._manifest.get(path, nullid)
1719
1722
1720 path = self._path
1723 path = self._path
1721 fl = self._filelog
1724 fl = self._filelog
1722 pcl = self._changectx._parents
1725 pcl = self._changectx._parents
1723 renamed = self.renamed()
1726 renamed = self.renamed()
1724
1727
1725 if renamed:
1728 if renamed:
1726 pl = [renamed + (None,)]
1729 pl = [renamed + (None,)]
1727 else:
1730 else:
1728 pl = [(path, filenode(pcl[0], path), fl)]
1731 pl = [(path, filenode(pcl[0], path), fl)]
1729
1732
1730 for pc in pcl[1:]:
1733 for pc in pcl[1:]:
1731 pl.append((path, filenode(pc, path), fl))
1734 pl.append((path, filenode(pc, path), fl))
1732
1735
1733 return [self._parentfilectx(p, fileid=n, filelog=l)
1736 return [self._parentfilectx(p, fileid=n, filelog=l)
1734 for p, n, l in pl if n != nullid]
1737 for p, n, l in pl if n != nullid]
1735
1738
1736 def children(self):
1739 def children(self):
1737 return []
1740 return []
1738
1741
1739 class workingfilectx(committablefilectx):
1742 class workingfilectx(committablefilectx):
1740 """A workingfilectx object makes access to data related to a particular
1743 """A workingfilectx object makes access to data related to a particular
1741 file in the working directory convenient."""
1744 file in the working directory convenient."""
1742 def __init__(self, repo, path, filelog=None, workingctx=None):
1745 def __init__(self, repo, path, filelog=None, workingctx=None):
1743 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1746 super(workingfilectx, self).__init__(repo, path, filelog, workingctx)
1744
1747
1745 @propertycache
1748 @propertycache
1746 def _changectx(self):
1749 def _changectx(self):
1747 return workingctx(self._repo)
1750 return workingctx(self._repo)
1748
1751
1749 def data(self):
1752 def data(self):
1750 return self._repo.wread(self._path)
1753 return self._repo.wread(self._path)
1751 def renamed(self):
1754 def renamed(self):
1752 rp = self._repo.dirstate.copied(self._path)
1755 rp = self._repo.dirstate.copied(self._path)
1753 if not rp:
1756 if not rp:
1754 return None
1757 return None
1755 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1758 return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
1756
1759
1757 def size(self):
1760 def size(self):
1758 return self._repo.wvfs.lstat(self._path).st_size
1761 return self._repo.wvfs.lstat(self._path).st_size
1759 def date(self):
1762 def date(self):
1760 t, tz = self._changectx.date()
1763 t, tz = self._changectx.date()
1761 try:
1764 try:
1762 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1765 return (self._repo.wvfs.lstat(self._path).st_mtime, tz)
1763 except OSError as err:
1766 except OSError as err:
1764 if err.errno != errno.ENOENT:
1767 if err.errno != errno.ENOENT:
1765 raise
1768 raise
1766 return (t, tz)
1769 return (t, tz)
1767
1770
1768 def cmp(self, fctx):
1771 def cmp(self, fctx):
1769 """compare with other file context
1772 """compare with other file context
1770
1773
1771 returns True if different than fctx.
1774 returns True if different than fctx.
1772 """
1775 """
1773 # fctx should be a filectx (not a workingfilectx)
1776 # fctx should be a filectx (not a workingfilectx)
1774 # invert comparison to reuse the same code path
1777 # invert comparison to reuse the same code path
1775 return fctx.cmp(self)
1778 return fctx.cmp(self)
1776
1779
1777 def remove(self, ignoremissing=False):
1780 def remove(self, ignoremissing=False):
1778 """wraps unlink for a repo's working directory"""
1781 """wraps unlink for a repo's working directory"""
1779 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1782 util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
1780
1783
1781 def write(self, data, flags):
1784 def write(self, data, flags):
1782 """wraps repo.wwrite"""
1785 """wraps repo.wwrite"""
1783 self._repo.wwrite(self._path, data, flags)
1786 self._repo.wwrite(self._path, data, flags)
1784
1787
1785 class workingcommitctx(workingctx):
1788 class workingcommitctx(workingctx):
1786 """A workingcommitctx object makes access to data related to
1789 """A workingcommitctx object makes access to data related to
1787 the revision being committed convenient.
1790 the revision being committed convenient.
1788
1791
1789 This hides changes in the working directory, if they aren't
1792 This hides changes in the working directory, if they aren't
1790 committed in this context.
1793 committed in this context.
1791 """
1794 """
1792 def __init__(self, repo, changes,
1795 def __init__(self, repo, changes,
1793 text="", user=None, date=None, extra=None):
1796 text="", user=None, date=None, extra=None):
1794 super(workingctx, self).__init__(repo, text, user, date, extra,
1797 super(workingctx, self).__init__(repo, text, user, date, extra,
1795 changes)
1798 changes)
1796
1799
1797 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1800 def _dirstatestatus(self, match=None, ignored=False, clean=False,
1798 unknown=False):
1801 unknown=False):
1799 """Return matched files only in ``self._status``
1802 """Return matched files only in ``self._status``
1800
1803
1801 Uncommitted files appear "clean" via this context, even if
1804 Uncommitted files appear "clean" via this context, even if
1802 they aren't actually so in the working directory.
1805 they aren't actually so in the working directory.
1803 """
1806 """
1804 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1807 match = match or matchmod.always(self._repo.root, self._repo.getcwd())
1805 if clean:
1808 if clean:
1806 clean = [f for f in self._manifest if f not in self._changedset]
1809 clean = [f for f in self._manifest if f not in self._changedset]
1807 else:
1810 else:
1808 clean = []
1811 clean = []
1809 return scmutil.status([f for f in self._status.modified if match(f)],
1812 return scmutil.status([f for f in self._status.modified if match(f)],
1810 [f for f in self._status.added if match(f)],
1813 [f for f in self._status.added if match(f)],
1811 [f for f in self._status.removed if match(f)],
1814 [f for f in self._status.removed if match(f)],
1812 [], [], [], clean)
1815 [], [], [], clean)
1813
1816
1814 @propertycache
1817 @propertycache
1815 def _changedset(self):
1818 def _changedset(self):
1816 """Return the set of files changed in this context
1819 """Return the set of files changed in this context
1817 """
1820 """
1818 changed = set(self._status.modified)
1821 changed = set(self._status.modified)
1819 changed.update(self._status.added)
1822 changed.update(self._status.added)
1820 changed.update(self._status.removed)
1823 changed.update(self._status.removed)
1821 return changed
1824 return changed
1822
1825
1823 def makecachingfilectxfn(func):
1826 def makecachingfilectxfn(func):
1824 """Create a filectxfn that caches based on the path.
1827 """Create a filectxfn that caches based on the path.
1825
1828
1826 We can't use util.cachefunc because it uses all arguments as the cache
1829 We can't use util.cachefunc because it uses all arguments as the cache
1827 key and this creates a cycle since the arguments include the repo and
1830 key and this creates a cycle since the arguments include the repo and
1828 memctx.
1831 memctx.
1829 """
1832 """
1830 cache = {}
1833 cache = {}
1831
1834
1832 def getfilectx(repo, memctx, path):
1835 def getfilectx(repo, memctx, path):
1833 if path not in cache:
1836 if path not in cache:
1834 cache[path] = func(repo, memctx, path)
1837 cache[path] = func(repo, memctx, path)
1835 return cache[path]
1838 return cache[path]
1836
1839
1837 return getfilectx
1840 return getfilectx
1838
1841
1839 class memctx(committablectx):
1842 class memctx(committablectx):
1840 """Use memctx to perform in-memory commits via localrepo.commitctx().
1843 """Use memctx to perform in-memory commits via localrepo.commitctx().
1841
1844
1842 Revision information is supplied at initialization time while
1845 Revision information is supplied at initialization time while
1843 related files data and is made available through a callback
1846 related files data and is made available through a callback
1844 mechanism. 'repo' is the current localrepo, 'parents' is a
1847 mechanism. 'repo' is the current localrepo, 'parents' is a
1845 sequence of two parent revisions identifiers (pass None for every
1848 sequence of two parent revisions identifiers (pass None for every
1846 missing parent), 'text' is the commit message and 'files' lists
1849 missing parent), 'text' is the commit message and 'files' lists
1847 names of files touched by the revision (normalized and relative to
1850 names of files touched by the revision (normalized and relative to
1848 repository root).
1851 repository root).
1849
1852
1850 filectxfn(repo, memctx, path) is a callable receiving the
1853 filectxfn(repo, memctx, path) is a callable receiving the
1851 repository, the current memctx object and the normalized path of
1854 repository, the current memctx object and the normalized path of
1852 requested file, relative to repository root. It is fired by the
1855 requested file, relative to repository root. It is fired by the
1853 commit function for every file in 'files', but calls order is
1856 commit function for every file in 'files', but calls order is
1854 undefined. If the file is available in the revision being
1857 undefined. If the file is available in the revision being
1855 committed (updated or added), filectxfn returns a memfilectx
1858 committed (updated or added), filectxfn returns a memfilectx
1856 object. If the file was removed, filectxfn raises an
1859 object. If the file was removed, filectxfn raises an
1857 IOError. Moved files are represented by marking the source file
1860 IOError. Moved files are represented by marking the source file
1858 removed and the new file added with copy information (see
1861 removed and the new file added with copy information (see
1859 memfilectx).
1862 memfilectx).
1860
1863
1861 user receives the committer name and defaults to current
1864 user receives the committer name and defaults to current
1862 repository username, date is the commit date in any format
1865 repository username, date is the commit date in any format
1863 supported by util.parsedate() and defaults to current date, extra
1866 supported by util.parsedate() and defaults to current date, extra
1864 is a dictionary of metadata or is left empty.
1867 is a dictionary of metadata or is left empty.
1865 """
1868 """
1866
1869
1867 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1870 # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
1868 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1871 # Extensions that need to retain compatibility across Mercurial 3.1 can use
1869 # this field to determine what to do in filectxfn.
1872 # this field to determine what to do in filectxfn.
1870 _returnnoneformissingfiles = True
1873 _returnnoneformissingfiles = True
1871
1874
1872 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1875 def __init__(self, repo, parents, text, files, filectxfn, user=None,
1873 date=None, extra=None, editor=False):
1876 date=None, extra=None, editor=False):
1874 super(memctx, self).__init__(repo, text, user, date, extra)
1877 super(memctx, self).__init__(repo, text, user, date, extra)
1875 self._rev = None
1878 self._rev = None
1876 self._node = None
1879 self._node = None
1877 parents = [(p or nullid) for p in parents]
1880 parents = [(p or nullid) for p in parents]
1878 p1, p2 = parents
1881 p1, p2 = parents
1879 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1882 self._parents = [changectx(self._repo, p) for p in (p1, p2)]
1880 files = sorted(set(files))
1883 files = sorted(set(files))
1881 self._files = files
1884 self._files = files
1882 self.substate = {}
1885 self.substate = {}
1883
1886
1884 # if store is not callable, wrap it in a function
1887 # if store is not callable, wrap it in a function
1885 if not callable(filectxfn):
1888 if not callable(filectxfn):
1886 def getfilectx(repo, memctx, path):
1889 def getfilectx(repo, memctx, path):
1887 fctx = filectxfn[path]
1890 fctx = filectxfn[path]
1888 # this is weird but apparently we only keep track of one parent
1891 # this is weird but apparently we only keep track of one parent
1889 # (why not only store that instead of a tuple?)
1892 # (why not only store that instead of a tuple?)
1890 copied = fctx.renamed()
1893 copied = fctx.renamed()
1891 if copied:
1894 if copied:
1892 copied = copied[0]
1895 copied = copied[0]
1893 return memfilectx(repo, path, fctx.data(),
1896 return memfilectx(repo, path, fctx.data(),
1894 islink=fctx.islink(), isexec=fctx.isexec(),
1897 islink=fctx.islink(), isexec=fctx.isexec(),
1895 copied=copied, memctx=memctx)
1898 copied=copied, memctx=memctx)
1896 self._filectxfn = getfilectx
1899 self._filectxfn = getfilectx
1897 else:
1900 else:
1898 # memoizing increases performance for e.g. vcs convert scenarios.
1901 # memoizing increases performance for e.g. vcs convert scenarios.
1899 self._filectxfn = makecachingfilectxfn(filectxfn)
1902 self._filectxfn = makecachingfilectxfn(filectxfn)
1900
1903
1901 if extra:
1904 if extra:
1902 self._extra = extra.copy()
1905 self._extra = extra.copy()
1903 else:
1906 else:
1904 self._extra = {}
1907 self._extra = {}
1905
1908
1906 if self._extra.get('branch', '') == '':
1909 if self._extra.get('branch', '') == '':
1907 self._extra['branch'] = 'default'
1910 self._extra['branch'] = 'default'
1908
1911
1909 if editor:
1912 if editor:
1910 self._text = editor(self._repo, self, [])
1913 self._text = editor(self._repo, self, [])
1911 self._repo.savecommitmessage(self._text)
1914 self._repo.savecommitmessage(self._text)
1912
1915
1913 def filectx(self, path, filelog=None):
1916 def filectx(self, path, filelog=None):
1914 """get a file context from the working directory
1917 """get a file context from the working directory
1915
1918
1916 Returns None if file doesn't exist and should be removed."""
1919 Returns None if file doesn't exist and should be removed."""
1917 return self._filectxfn(self._repo, self, path)
1920 return self._filectxfn(self._repo, self, path)
1918
1921
1919 def commit(self):
1922 def commit(self):
1920 """commit context to the repo"""
1923 """commit context to the repo"""
1921 return self._repo.commitctx(self)
1924 return self._repo.commitctx(self)
1922
1925
1923 @propertycache
1926 @propertycache
1924 def _manifest(self):
1927 def _manifest(self):
1925 """generate a manifest based on the return values of filectxfn"""
1928 """generate a manifest based on the return values of filectxfn"""
1926
1929
1927 # keep this simple for now; just worry about p1
1930 # keep this simple for now; just worry about p1
1928 pctx = self._parents[0]
1931 pctx = self._parents[0]
1929 man = pctx.manifest().copy()
1932 man = pctx.manifest().copy()
1930
1933
1931 for f in self._status.modified:
1934 for f in self._status.modified:
1932 p1node = nullid
1935 p1node = nullid
1933 p2node = nullid
1936 p2node = nullid
1934 p = pctx[f].parents() # if file isn't in pctx, check p2?
1937 p = pctx[f].parents() # if file isn't in pctx, check p2?
1935 if len(p) > 0:
1938 if len(p) > 0:
1936 p1node = p[0].filenode()
1939 p1node = p[0].filenode()
1937 if len(p) > 1:
1940 if len(p) > 1:
1938 p2node = p[1].filenode()
1941 p2node = p[1].filenode()
1939 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1942 man[f] = revlog.hash(self[f].data(), p1node, p2node)
1940
1943
1941 for f in self._status.added:
1944 for f in self._status.added:
1942 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1945 man[f] = revlog.hash(self[f].data(), nullid, nullid)
1943
1946
1944 for f in self._status.removed:
1947 for f in self._status.removed:
1945 if f in man:
1948 if f in man:
1946 del man[f]
1949 del man[f]
1947
1950
1948 return man
1951 return man
1949
1952
1950 @propertycache
1953 @propertycache
1951 def _status(self):
1954 def _status(self):
1952 """Calculate exact status from ``files`` specified at construction
1955 """Calculate exact status from ``files`` specified at construction
1953 """
1956 """
1954 man1 = self.p1().manifest()
1957 man1 = self.p1().manifest()
1955 p2 = self._parents[1]
1958 p2 = self._parents[1]
1956 # "1 < len(self._parents)" can't be used for checking
1959 # "1 < len(self._parents)" can't be used for checking
1957 # existence of the 2nd parent, because "memctx._parents" is
1960 # existence of the 2nd parent, because "memctx._parents" is
1958 # explicitly initialized by the list, of which length is 2.
1961 # explicitly initialized by the list, of which length is 2.
1959 if p2.node() != nullid:
1962 if p2.node() != nullid:
1960 man2 = p2.manifest()
1963 man2 = p2.manifest()
1961 managing = lambda f: f in man1 or f in man2
1964 managing = lambda f: f in man1 or f in man2
1962 else:
1965 else:
1963 managing = lambda f: f in man1
1966 managing = lambda f: f in man1
1964
1967
1965 modified, added, removed = [], [], []
1968 modified, added, removed = [], [], []
1966 for f in self._files:
1969 for f in self._files:
1967 if not managing(f):
1970 if not managing(f):
1968 added.append(f)
1971 added.append(f)
1969 elif self[f]:
1972 elif self[f]:
1970 modified.append(f)
1973 modified.append(f)
1971 else:
1974 else:
1972 removed.append(f)
1975 removed.append(f)
1973
1976
1974 return scmutil.status(modified, added, removed, [], [], [], [])
1977 return scmutil.status(modified, added, removed, [], [], [], [])
1975
1978
1976 class memfilectx(committablefilectx):
1979 class memfilectx(committablefilectx):
1977 """memfilectx represents an in-memory file to commit.
1980 """memfilectx represents an in-memory file to commit.
1978
1981
1979 See memctx and committablefilectx for more details.
1982 See memctx and committablefilectx for more details.
1980 """
1983 """
1981 def __init__(self, repo, path, data, islink=False,
1984 def __init__(self, repo, path, data, islink=False,
1982 isexec=False, copied=None, memctx=None):
1985 isexec=False, copied=None, memctx=None):
1983 """
1986 """
1984 path is the normalized file path relative to repository root.
1987 path is the normalized file path relative to repository root.
1985 data is the file content as a string.
1988 data is the file content as a string.
1986 islink is True if the file is a symbolic link.
1989 islink is True if the file is a symbolic link.
1987 isexec is True if the file is executable.
1990 isexec is True if the file is executable.
1988 copied is the source file path if current file was copied in the
1991 copied is the source file path if current file was copied in the
1989 revision being committed, or None."""
1992 revision being committed, or None."""
1990 super(memfilectx, self).__init__(repo, path, None, memctx)
1993 super(memfilectx, self).__init__(repo, path, None, memctx)
1991 self._data = data
1994 self._data = data
1992 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1995 self._flags = (islink and 'l' or '') + (isexec and 'x' or '')
1993 self._copied = None
1996 self._copied = None
1994 if copied:
1997 if copied:
1995 self._copied = (copied, nullid)
1998 self._copied = (copied, nullid)
1996
1999
1997 def data(self):
2000 def data(self):
1998 return self._data
2001 return self._data
1999 def size(self):
2002 def size(self):
2000 return len(self.data())
2003 return len(self.data())
2001 def flags(self):
2004 def flags(self):
2002 return self._flags
2005 return self._flags
2003 def renamed(self):
2006 def renamed(self):
2004 return self._copied
2007 return self._copied
2005
2008
2006 def remove(self, ignoremissing=False):
2009 def remove(self, ignoremissing=False):
2007 """wraps unlink for a repo's working directory"""
2010 """wraps unlink for a repo's working directory"""
2008 # need to figure out what to do here
2011 # need to figure out what to do here
2009 del self._changectx[self._path]
2012 del self._changectx[self._path]
2010
2013
2011 def write(self, data, flags):
2014 def write(self, data, flags):
2012 """wraps repo.wwrite"""
2015 """wraps repo.wwrite"""
2013 self._data = data
2016 self._data = data
2014
2017
2015 class metadataonlyctx(committablectx):
2018 class metadataonlyctx(committablectx):
2016 """Like memctx but it's reusing the manifest of different commit.
2019 """Like memctx but it's reusing the manifest of different commit.
2017 Intended to be used by lightweight operations that are creating
2020 Intended to be used by lightweight operations that are creating
2018 metadata-only changes.
2021 metadata-only changes.
2019
2022
2020 Revision information is supplied at initialization time. 'repo' is the
2023 Revision information is supplied at initialization time. 'repo' is the
2021 current localrepo, 'ctx' is original revision which manifest we're reuisng
2024 current localrepo, 'ctx' is original revision which manifest we're reuisng
2022 'parents' is a sequence of two parent revisions identifiers (pass None for
2025 'parents' is a sequence of two parent revisions identifiers (pass None for
2023 every missing parent), 'text' is the commit.
2026 every missing parent), 'text' is the commit.
2024
2027
2025 user receives the committer name and defaults to current repository
2028 user receives the committer name and defaults to current repository
2026 username, date is the commit date in any format supported by
2029 username, date is the commit date in any format supported by
2027 util.parsedate() and defaults to current date, extra is a dictionary of
2030 util.parsedate() and defaults to current date, extra is a dictionary of
2028 metadata or is left empty.
2031 metadata or is left empty.
2029 """
2032 """
2030 def __new__(cls, repo, originalctx, *args, **kwargs):
2033 def __new__(cls, repo, originalctx, *args, **kwargs):
2031 return super(metadataonlyctx, cls).__new__(cls, repo)
2034 return super(metadataonlyctx, cls).__new__(cls, repo)
2032
2035
2033 def __init__(self, repo, originalctx, parents, text, user=None, date=None,
2036 def __init__(self, repo, originalctx, parents, text, user=None, date=None,
2034 extra=None, editor=False):
2037 extra=None, editor=False):
2035 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2038 super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
2036 self._rev = None
2039 self._rev = None
2037 self._node = None
2040 self._node = None
2038 self._originalctx = originalctx
2041 self._originalctx = originalctx
2039 self._manifestnode = originalctx.manifestnode()
2042 self._manifestnode = originalctx.manifestnode()
2040 parents = [(p or nullid) for p in parents]
2043 parents = [(p or nullid) for p in parents]
2041 p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
2044 p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
2042
2045
2043 # sanity check to ensure that the reused manifest parents are
2046 # sanity check to ensure that the reused manifest parents are
2044 # manifests of our commit parents
2047 # manifests of our commit parents
2045 mp1, mp2 = self.manifestctx().parents
2048 mp1, mp2 = self.manifestctx().parents
2046 if p1 != nullid and p1.manifestctx().node() != mp1:
2049 if p1 != nullid and p1.manifestctx().node() != mp1:
2047 raise RuntimeError('can\'t reuse the manifest: '
2050 raise RuntimeError('can\'t reuse the manifest: '
2048 'its p1 doesn\'t match the new ctx p1')
2051 'its p1 doesn\'t match the new ctx p1')
2049 if p2 != nullid and p2.manifestctx().node() != mp2:
2052 if p2 != nullid and p2.manifestctx().node() != mp2:
2050 raise RuntimeError('can\'t reuse the manifest: '
2053 raise RuntimeError('can\'t reuse the manifest: '
2051 'its p2 doesn\'t match the new ctx p2')
2054 'its p2 doesn\'t match the new ctx p2')
2052
2055
2053 self._files = originalctx.files()
2056 self._files = originalctx.files()
2054 self.substate = {}
2057 self.substate = {}
2055
2058
2056 if extra:
2059 if extra:
2057 self._extra = extra.copy()
2060 self._extra = extra.copy()
2058 else:
2061 else:
2059 self._extra = {}
2062 self._extra = {}
2060
2063
2061 if self._extra.get('branch', '') == '':
2064 if self._extra.get('branch', '') == '':
2062 self._extra['branch'] = 'default'
2065 self._extra['branch'] = 'default'
2063
2066
2064 if editor:
2067 if editor:
2065 self._text = editor(self._repo, self, [])
2068 self._text = editor(self._repo, self, [])
2066 self._repo.savecommitmessage(self._text)
2069 self._repo.savecommitmessage(self._text)
2067
2070
2068 def manifestnode(self):
2071 def manifestnode(self):
2069 return self._manifestnode
2072 return self._manifestnode
2070
2073
2071 @propertycache
2074 @propertycache
2072 def _manifestctx(self):
2075 def _manifestctx(self):
2073 return self._repo.manifestlog[self._manifestnode]
2076 return self._repo.manifestlog[self._manifestnode]
2074
2077
2075 def filectx(self, path, filelog=None):
2078 def filectx(self, path, filelog=None):
2076 return self._originalctx.filectx(path, filelog=filelog)
2079 return self._originalctx.filectx(path, filelog=filelog)
2077
2080
2078 def commit(self):
2081 def commit(self):
2079 """commit context to the repo"""
2082 """commit context to the repo"""
2080 return self._repo.commitctx(self)
2083 return self._repo.commitctx(self)
2081
2084
2082 @property
2085 @property
2083 def _manifest(self):
2086 def _manifest(self):
2084 return self._originalctx.manifest()
2087 return self._originalctx.manifest()
2085
2088
2086 @propertycache
2089 @propertycache
2087 def _status(self):
2090 def _status(self):
2088 """Calculate exact status from ``files`` specified in the ``origctx``
2091 """Calculate exact status from ``files`` specified in the ``origctx``
2089 and parents manifests.
2092 and parents manifests.
2090 """
2093 """
2091 man1 = self.p1().manifest()
2094 man1 = self.p1().manifest()
2092 p2 = self._parents[1]
2095 p2 = self._parents[1]
2093 # "1 < len(self._parents)" can't be used for checking
2096 # "1 < len(self._parents)" can't be used for checking
2094 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2097 # existence of the 2nd parent, because "metadataonlyctx._parents" is
2095 # explicitly initialized by the list, of which length is 2.
2098 # explicitly initialized by the list, of which length is 2.
2096 if p2.node() != nullid:
2099 if p2.node() != nullid:
2097 man2 = p2.manifest()
2100 man2 = p2.manifest()
2098 managing = lambda f: f in man1 or f in man2
2101 managing = lambda f: f in man1 or f in man2
2099 else:
2102 else:
2100 managing = lambda f: f in man1
2103 managing = lambda f: f in man1
2101
2104
2102 modified, added, removed = [], [], []
2105 modified, added, removed = [], [], []
2103 for f in self._files:
2106 for f in self._files:
2104 if not managing(f):
2107 if not managing(f):
2105 added.append(f)
2108 added.append(f)
2106 elif self[f]:
2109 elif self[f]:
2107 modified.append(f)
2110 modified.append(f)
2108 else:
2111 else:
2109 removed.append(f)
2112 removed.append(f)
2110
2113
2111 return scmutil.status(modified, added, removed, [], [], [], [])
2114 return scmutil.status(modified, added, removed, [], [], [], [])
@@ -1,851 +1,851 b''
1 # debugcommands.py - command processing for debug* commands
1 # debugcommands.py - command processing for debug* commands
2 #
2 #
3 # Copyright 2005-2016 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2016 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import operator
10 import operator
11 import os
11 import os
12 import random
12 import random
13
13
14 from .i18n import _
14 from .i18n import _
15 from .node import (
15 from .node import (
16 bin,
16 bin,
17 hex,
17 hex,
18 nullid,
18 nullid,
19 short,
19 short,
20 )
20 )
21 from . import (
21 from . import (
22 bundle2,
22 bundle2,
23 changegroup,
23 changegroup,
24 cmdutil,
24 cmdutil,
25 commands,
25 commands,
26 context,
26 context,
27 dagparser,
27 dagparser,
28 dagutil,
28 dagutil,
29 error,
29 error,
30 exchange,
30 exchange,
31 extensions,
31 extensions,
32 fileset,
32 fileset,
33 hg,
33 hg,
34 localrepo,
34 localrepo,
35 lock as lockmod,
35 lock as lockmod,
36 pycompat,
36 pycompat,
37 revlog,
37 revlog,
38 scmutil,
38 scmutil,
39 setdiscovery,
39 setdiscovery,
40 simplemerge,
40 simplemerge,
41 streamclone,
41 streamclone,
42 treediscovery,
42 treediscovery,
43 util,
43 util,
44 )
44 )
45
45
46 release = lockmod.release
46 release = lockmod.release
47
47
48 # We reuse the command table from commands because it is easier than
48 # We reuse the command table from commands because it is easier than
49 # teaching dispatch about multiple tables.
49 # teaching dispatch about multiple tables.
50 command = cmdutil.command(commands.table)
50 command = cmdutil.command(commands.table)
51
51
52 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
52 @command('debugancestor', [], _('[INDEX] REV1 REV2'), optionalrepo=True)
53 def debugancestor(ui, repo, *args):
53 def debugancestor(ui, repo, *args):
54 """find the ancestor revision of two revisions in a given index"""
54 """find the ancestor revision of two revisions in a given index"""
55 if len(args) == 3:
55 if len(args) == 3:
56 index, rev1, rev2 = args
56 index, rev1, rev2 = args
57 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False), index)
57 r = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False), index)
58 lookup = r.lookup
58 lookup = r.lookup
59 elif len(args) == 2:
59 elif len(args) == 2:
60 if not repo:
60 if not repo:
61 raise error.Abort(_('there is no Mercurial repository here '
61 raise error.Abort(_('there is no Mercurial repository here '
62 '(.hg not found)'))
62 '(.hg not found)'))
63 rev1, rev2 = args
63 rev1, rev2 = args
64 r = repo.changelog
64 r = repo.changelog
65 lookup = repo.lookup
65 lookup = repo.lookup
66 else:
66 else:
67 raise error.Abort(_('either two or three arguments required'))
67 raise error.Abort(_('either two or three arguments required'))
68 a = r.ancestor(lookup(rev1), lookup(rev2))
68 a = r.ancestor(lookup(rev1), lookup(rev2))
69 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
69 ui.write('%d:%s\n' % (r.rev(a), hex(a)))
70
70
71 @command('debugapplystreamclonebundle', [], 'FILE')
71 @command('debugapplystreamclonebundle', [], 'FILE')
72 def debugapplystreamclonebundle(ui, repo, fname):
72 def debugapplystreamclonebundle(ui, repo, fname):
73 """apply a stream clone bundle file"""
73 """apply a stream clone bundle file"""
74 f = hg.openpath(ui, fname)
74 f = hg.openpath(ui, fname)
75 gen = exchange.readbundle(ui, f, fname)
75 gen = exchange.readbundle(ui, f, fname)
76 gen.apply(repo)
76 gen.apply(repo)
77
77
78 @command('debugbuilddag',
78 @command('debugbuilddag',
79 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
79 [('m', 'mergeable-file', None, _('add single file mergeable changes')),
80 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
80 ('o', 'overwritten-file', None, _('add single file all revs overwrite')),
81 ('n', 'new-file', None, _('add new file at each rev'))],
81 ('n', 'new-file', None, _('add new file at each rev'))],
82 _('[OPTION]... [TEXT]'))
82 _('[OPTION]... [TEXT]'))
83 def debugbuilddag(ui, repo, text=None,
83 def debugbuilddag(ui, repo, text=None,
84 mergeable_file=False,
84 mergeable_file=False,
85 overwritten_file=False,
85 overwritten_file=False,
86 new_file=False):
86 new_file=False):
87 """builds a repo with a given DAG from scratch in the current empty repo
87 """builds a repo with a given DAG from scratch in the current empty repo
88
88
89 The description of the DAG is read from stdin if not given on the
89 The description of the DAG is read from stdin if not given on the
90 command line.
90 command line.
91
91
92 Elements:
92 Elements:
93
93
94 - "+n" is a linear run of n nodes based on the current default parent
94 - "+n" is a linear run of n nodes based on the current default parent
95 - "." is a single node based on the current default parent
95 - "." is a single node based on the current default parent
96 - "$" resets the default parent to null (implied at the start);
96 - "$" resets the default parent to null (implied at the start);
97 otherwise the default parent is always the last node created
97 otherwise the default parent is always the last node created
98 - "<p" sets the default parent to the backref p
98 - "<p" sets the default parent to the backref p
99 - "*p" is a fork at parent p, which is a backref
99 - "*p" is a fork at parent p, which is a backref
100 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
100 - "*p1/p2" is a merge of parents p1 and p2, which are backrefs
101 - "/p2" is a merge of the preceding node and p2
101 - "/p2" is a merge of the preceding node and p2
102 - ":tag" defines a local tag for the preceding node
102 - ":tag" defines a local tag for the preceding node
103 - "@branch" sets the named branch for subsequent nodes
103 - "@branch" sets the named branch for subsequent nodes
104 - "#...\\n" is a comment up to the end of the line
104 - "#...\\n" is a comment up to the end of the line
105
105
106 Whitespace between the above elements is ignored.
106 Whitespace between the above elements is ignored.
107
107
108 A backref is either
108 A backref is either
109
109
110 - a number n, which references the node curr-n, where curr is the current
110 - a number n, which references the node curr-n, where curr is the current
111 node, or
111 node, or
112 - the name of a local tag you placed earlier using ":tag", or
112 - the name of a local tag you placed earlier using ":tag", or
113 - empty to denote the default parent.
113 - empty to denote the default parent.
114
114
115 All string valued-elements are either strictly alphanumeric, or must
115 All string valued-elements are either strictly alphanumeric, or must
116 be enclosed in double quotes ("..."), with "\\" as escape character.
116 be enclosed in double quotes ("..."), with "\\" as escape character.
117 """
117 """
118
118
119 if text is None:
119 if text is None:
120 ui.status(_("reading DAG from stdin\n"))
120 ui.status(_("reading DAG from stdin\n"))
121 text = ui.fin.read()
121 text = ui.fin.read()
122
122
123 cl = repo.changelog
123 cl = repo.changelog
124 if len(cl) > 0:
124 if len(cl) > 0:
125 raise error.Abort(_('repository is not empty'))
125 raise error.Abort(_('repository is not empty'))
126
126
127 # determine number of revs in DAG
127 # determine number of revs in DAG
128 total = 0
128 total = 0
129 for type, data in dagparser.parsedag(text):
129 for type, data in dagparser.parsedag(text):
130 if type == 'n':
130 if type == 'n':
131 total += 1
131 total += 1
132
132
133 if mergeable_file:
133 if mergeable_file:
134 linesperrev = 2
134 linesperrev = 2
135 # make a file with k lines per rev
135 # make a file with k lines per rev
136 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
136 initialmergedlines = [str(i) for i in xrange(0, total * linesperrev)]
137 initialmergedlines.append("")
137 initialmergedlines.append("")
138
138
139 tags = []
139 tags = []
140
140
141 wlock = lock = tr = None
141 wlock = lock = tr = None
142 try:
142 try:
143 wlock = repo.wlock()
143 wlock = repo.wlock()
144 lock = repo.lock()
144 lock = repo.lock()
145 tr = repo.transaction("builddag")
145 tr = repo.transaction("builddag")
146
146
147 at = -1
147 at = -1
148 atbranch = 'default'
148 atbranch = 'default'
149 nodeids = []
149 nodeids = []
150 id = 0
150 id = 0
151 ui.progress(_('building'), id, unit=_('revisions'), total=total)
151 ui.progress(_('building'), id, unit=_('revisions'), total=total)
152 for type, data in dagparser.parsedag(text):
152 for type, data in dagparser.parsedag(text):
153 if type == 'n':
153 if type == 'n':
154 ui.note(('node %s\n' % str(data)))
154 ui.note(('node %s\n' % str(data)))
155 id, ps = data
155 id, ps = data
156
156
157 files = []
157 files = []
158 fctxs = {}
158 fctxs = {}
159
159
160 p2 = None
160 p2 = None
161 if mergeable_file:
161 if mergeable_file:
162 fn = "mf"
162 fn = "mf"
163 p1 = repo[ps[0]]
163 p1 = repo[ps[0]]
164 if len(ps) > 1:
164 if len(ps) > 1:
165 p2 = repo[ps[1]]
165 p2 = repo[ps[1]]
166 pa = p1.ancestor(p2)
166 pa = p1.ancestor(p2)
167 base, local, other = [x[fn].data() for x in (pa, p1,
167 base, local, other = [x[fn].data() for x in (pa, p1,
168 p2)]
168 p2)]
169 m3 = simplemerge.Merge3Text(base, local, other)
169 m3 = simplemerge.Merge3Text(base, local, other)
170 ml = [l.strip() for l in m3.merge_lines()]
170 ml = [l.strip() for l in m3.merge_lines()]
171 ml.append("")
171 ml.append("")
172 elif at > 0:
172 elif at > 0:
173 ml = p1[fn].data().split("\n")
173 ml = p1[fn].data().split("\n")
174 else:
174 else:
175 ml = initialmergedlines
175 ml = initialmergedlines
176 ml[id * linesperrev] += " r%i" % id
176 ml[id * linesperrev] += " r%i" % id
177 mergedtext = "\n".join(ml)
177 mergedtext = "\n".join(ml)
178 files.append(fn)
178 files.append(fn)
179 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
179 fctxs[fn] = context.memfilectx(repo, fn, mergedtext)
180
180
181 if overwritten_file:
181 if overwritten_file:
182 fn = "of"
182 fn = "of"
183 files.append(fn)
183 files.append(fn)
184 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
184 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
185
185
186 if new_file:
186 if new_file:
187 fn = "nf%i" % id
187 fn = "nf%i" % id
188 files.append(fn)
188 files.append(fn)
189 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
189 fctxs[fn] = context.memfilectx(repo, fn, "r%i\n" % id)
190 if len(ps) > 1:
190 if len(ps) > 1:
191 if not p2:
191 if not p2:
192 p2 = repo[ps[1]]
192 p2 = repo[ps[1]]
193 for fn in p2:
193 for fn in p2:
194 if fn.startswith("nf"):
194 if fn.startswith("nf"):
195 files.append(fn)
195 files.append(fn)
196 fctxs[fn] = p2[fn]
196 fctxs[fn] = p2[fn]
197
197
198 def fctxfn(repo, cx, path):
198 def fctxfn(repo, cx, path):
199 return fctxs.get(path)
199 return fctxs.get(path)
200
200
201 if len(ps) == 0 or ps[0] < 0:
201 if len(ps) == 0 or ps[0] < 0:
202 pars = [None, None]
202 pars = [None, None]
203 elif len(ps) == 1:
203 elif len(ps) == 1:
204 pars = [nodeids[ps[0]], None]
204 pars = [nodeids[ps[0]], None]
205 else:
205 else:
206 pars = [nodeids[p] for p in ps]
206 pars = [nodeids[p] for p in ps]
207 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
207 cx = context.memctx(repo, pars, "r%i" % id, files, fctxfn,
208 date=(id, 0),
208 date=(id, 0),
209 user="debugbuilddag",
209 user="debugbuilddag",
210 extra={'branch': atbranch})
210 extra={'branch': atbranch})
211 nodeid = repo.commitctx(cx)
211 nodeid = repo.commitctx(cx)
212 nodeids.append(nodeid)
212 nodeids.append(nodeid)
213 at = id
213 at = id
214 elif type == 'l':
214 elif type == 'l':
215 id, name = data
215 id, name = data
216 ui.note(('tag %s\n' % name))
216 ui.note(('tag %s\n' % name))
217 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
217 tags.append("%s %s\n" % (hex(repo.changelog.node(id)), name))
218 elif type == 'a':
218 elif type == 'a':
219 ui.note(('branch %s\n' % data))
219 ui.note(('branch %s\n' % data))
220 atbranch = data
220 atbranch = data
221 ui.progress(_('building'), id, unit=_('revisions'), total=total)
221 ui.progress(_('building'), id, unit=_('revisions'), total=total)
222 tr.close()
222 tr.close()
223
223
224 if tags:
224 if tags:
225 repo.vfs.write("localtags", "".join(tags))
225 repo.vfs.write("localtags", "".join(tags))
226 finally:
226 finally:
227 ui.progress(_('building'), None)
227 ui.progress(_('building'), None)
228 release(tr, lock, wlock)
228 release(tr, lock, wlock)
229
229
230 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
230 def _debugchangegroup(ui, gen, all=None, indent=0, **opts):
231 indent_string = ' ' * indent
231 indent_string = ' ' * indent
232 if all:
232 if all:
233 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
233 ui.write(("%sformat: id, p1, p2, cset, delta base, len(delta)\n")
234 % indent_string)
234 % indent_string)
235
235
236 def showchunks(named):
236 def showchunks(named):
237 ui.write("\n%s%s\n" % (indent_string, named))
237 ui.write("\n%s%s\n" % (indent_string, named))
238 chain = None
238 chain = None
239 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
239 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
240 node = chunkdata['node']
240 node = chunkdata['node']
241 p1 = chunkdata['p1']
241 p1 = chunkdata['p1']
242 p2 = chunkdata['p2']
242 p2 = chunkdata['p2']
243 cs = chunkdata['cs']
243 cs = chunkdata['cs']
244 deltabase = chunkdata['deltabase']
244 deltabase = chunkdata['deltabase']
245 delta = chunkdata['delta']
245 delta = chunkdata['delta']
246 ui.write("%s%s %s %s %s %s %s\n" %
246 ui.write("%s%s %s %s %s %s %s\n" %
247 (indent_string, hex(node), hex(p1), hex(p2),
247 (indent_string, hex(node), hex(p1), hex(p2),
248 hex(cs), hex(deltabase), len(delta)))
248 hex(cs), hex(deltabase), len(delta)))
249 chain = node
249 chain = node
250
250
251 chunkdata = gen.changelogheader()
251 chunkdata = gen.changelogheader()
252 showchunks("changelog")
252 showchunks("changelog")
253 chunkdata = gen.manifestheader()
253 chunkdata = gen.manifestheader()
254 showchunks("manifest")
254 showchunks("manifest")
255 for chunkdata in iter(gen.filelogheader, {}):
255 for chunkdata in iter(gen.filelogheader, {}):
256 fname = chunkdata['filename']
256 fname = chunkdata['filename']
257 showchunks(fname)
257 showchunks(fname)
258 else:
258 else:
259 if isinstance(gen, bundle2.unbundle20):
259 if isinstance(gen, bundle2.unbundle20):
260 raise error.Abort(_('use debugbundle2 for this file'))
260 raise error.Abort(_('use debugbundle2 for this file'))
261 chunkdata = gen.changelogheader()
261 chunkdata = gen.changelogheader()
262 chain = None
262 chain = None
263 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
263 for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
264 node = chunkdata['node']
264 node = chunkdata['node']
265 ui.write("%s%s\n" % (indent_string, hex(node)))
265 ui.write("%s%s\n" % (indent_string, hex(node)))
266 chain = node
266 chain = node
267
267
268 def _debugbundle2(ui, gen, all=None, **opts):
268 def _debugbundle2(ui, gen, all=None, **opts):
269 """lists the contents of a bundle2"""
269 """lists the contents of a bundle2"""
270 if not isinstance(gen, bundle2.unbundle20):
270 if not isinstance(gen, bundle2.unbundle20):
271 raise error.Abort(_('not a bundle2 file'))
271 raise error.Abort(_('not a bundle2 file'))
272 ui.write(('Stream params: %s\n' % repr(gen.params)))
272 ui.write(('Stream params: %s\n' % repr(gen.params)))
273 for part in gen.iterparts():
273 for part in gen.iterparts():
274 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
274 ui.write('%s -- %r\n' % (part.type, repr(part.params)))
275 if part.type == 'changegroup':
275 if part.type == 'changegroup':
276 version = part.params.get('version', '01')
276 version = part.params.get('version', '01')
277 cg = changegroup.getunbundler(version, part, 'UN')
277 cg = changegroup.getunbundler(version, part, 'UN')
278 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
278 _debugchangegroup(ui, cg, all=all, indent=4, **opts)
279
279
280 @command('debugbundle',
280 @command('debugbundle',
281 [('a', 'all', None, _('show all details')),
281 [('a', 'all', None, _('show all details')),
282 ('', 'spec', None, _('print the bundlespec of the bundle'))],
282 ('', 'spec', None, _('print the bundlespec of the bundle'))],
283 _('FILE'),
283 _('FILE'),
284 norepo=True)
284 norepo=True)
285 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
285 def debugbundle(ui, bundlepath, all=None, spec=None, **opts):
286 """lists the contents of a bundle"""
286 """lists the contents of a bundle"""
287 with hg.openpath(ui, bundlepath) as f:
287 with hg.openpath(ui, bundlepath) as f:
288 if spec:
288 if spec:
289 spec = exchange.getbundlespec(ui, f)
289 spec = exchange.getbundlespec(ui, f)
290 ui.write('%s\n' % spec)
290 ui.write('%s\n' % spec)
291 return
291 return
292
292
293 gen = exchange.readbundle(ui, f, bundlepath)
293 gen = exchange.readbundle(ui, f, bundlepath)
294 if isinstance(gen, bundle2.unbundle20):
294 if isinstance(gen, bundle2.unbundle20):
295 return _debugbundle2(ui, gen, all=all, **opts)
295 return _debugbundle2(ui, gen, all=all, **opts)
296 _debugchangegroup(ui, gen, all=all, **opts)
296 _debugchangegroup(ui, gen, all=all, **opts)
297
297
298 @command('debugcheckstate', [], '')
298 @command('debugcheckstate', [], '')
299 def debugcheckstate(ui, repo):
299 def debugcheckstate(ui, repo):
300 """validate the correctness of the current dirstate"""
300 """validate the correctness of the current dirstate"""
301 parent1, parent2 = repo.dirstate.parents()
301 parent1, parent2 = repo.dirstate.parents()
302 m1 = repo[parent1].manifest()
302 m1 = repo[parent1].manifest()
303 m2 = repo[parent2].manifest()
303 m2 = repo[parent2].manifest()
304 errors = 0
304 errors = 0
305 for f in repo.dirstate:
305 for f in repo.dirstate:
306 state = repo.dirstate[f]
306 state = repo.dirstate[f]
307 if state in "nr" and f not in m1:
307 if state in "nr" and f not in m1:
308 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
308 ui.warn(_("%s in state %s, but not in manifest1\n") % (f, state))
309 errors += 1
309 errors += 1
310 if state in "a" and f in m1:
310 if state in "a" and f in m1:
311 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
311 ui.warn(_("%s in state %s, but also in manifest1\n") % (f, state))
312 errors += 1
312 errors += 1
313 if state in "m" and f not in m1 and f not in m2:
313 if state in "m" and f not in m1 and f not in m2:
314 ui.warn(_("%s in state %s, but not in either manifest\n") %
314 ui.warn(_("%s in state %s, but not in either manifest\n") %
315 (f, state))
315 (f, state))
316 errors += 1
316 errors += 1
317 for f in m1:
317 for f in m1:
318 state = repo.dirstate[f]
318 state = repo.dirstate[f]
319 if state not in "nrm":
319 if state not in "nrm":
320 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
320 ui.warn(_("%s in manifest1, but listed as state %s") % (f, state))
321 errors += 1
321 errors += 1
322 if errors:
322 if errors:
323 error = _(".hg/dirstate inconsistent with current parent's manifest")
323 error = _(".hg/dirstate inconsistent with current parent's manifest")
324 raise error.Abort(error)
324 raise error.Abort(error)
325
325
326 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
326 @command('debugcommands', [], _('[COMMAND]'), norepo=True)
327 def debugcommands(ui, cmd='', *args):
327 def debugcommands(ui, cmd='', *args):
328 """list all available commands and options"""
328 """list all available commands and options"""
329 for cmd, vals in sorted(commands.table.iteritems()):
329 for cmd, vals in sorted(commands.table.iteritems()):
330 cmd = cmd.split('|')[0].strip('^')
330 cmd = cmd.split('|')[0].strip('^')
331 opts = ', '.join([i[1] for i in vals[1]])
331 opts = ', '.join([i[1] for i in vals[1]])
332 ui.write('%s: %s\n' % (cmd, opts))
332 ui.write('%s: %s\n' % (cmd, opts))
333
333
334 @command('debugcomplete',
334 @command('debugcomplete',
335 [('o', 'options', None, _('show the command options'))],
335 [('o', 'options', None, _('show the command options'))],
336 _('[-o] CMD'),
336 _('[-o] CMD'),
337 norepo=True)
337 norepo=True)
338 def debugcomplete(ui, cmd='', **opts):
338 def debugcomplete(ui, cmd='', **opts):
339 """returns the completion list associated with the given command"""
339 """returns the completion list associated with the given command"""
340
340
341 if opts.get('options'):
341 if opts.get('options'):
342 options = []
342 options = []
343 otables = [commands.globalopts]
343 otables = [commands.globalopts]
344 if cmd:
344 if cmd:
345 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
345 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
346 otables.append(entry[1])
346 otables.append(entry[1])
347 for t in otables:
347 for t in otables:
348 for o in t:
348 for o in t:
349 if "(DEPRECATED)" in o[3]:
349 if "(DEPRECATED)" in o[3]:
350 continue
350 continue
351 if o[0]:
351 if o[0]:
352 options.append('-%s' % o[0])
352 options.append('-%s' % o[0])
353 options.append('--%s' % o[1])
353 options.append('--%s' % o[1])
354 ui.write("%s\n" % "\n".join(options))
354 ui.write("%s\n" % "\n".join(options))
355 return
355 return
356
356
357 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, commands.table)
357 cmdlist, unused_allcmds = cmdutil.findpossible(cmd, commands.table)
358 if ui.verbose:
358 if ui.verbose:
359 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
359 cmdlist = [' '.join(c[0]) for c in cmdlist.values()]
360 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
360 ui.write("%s\n" % "\n".join(sorted(cmdlist)))
361
361
362 @command('debugcreatestreamclonebundle', [], 'FILE')
362 @command('debugcreatestreamclonebundle', [], 'FILE')
363 def debugcreatestreamclonebundle(ui, repo, fname):
363 def debugcreatestreamclonebundle(ui, repo, fname):
364 """create a stream clone bundle file
364 """create a stream clone bundle file
365
365
366 Stream bundles are special bundles that are essentially archives of
366 Stream bundles are special bundles that are essentially archives of
367 revlog files. They are commonly used for cloning very quickly.
367 revlog files. They are commonly used for cloning very quickly.
368 """
368 """
369 requirements, gen = streamclone.generatebundlev1(repo)
369 requirements, gen = streamclone.generatebundlev1(repo)
370 changegroup.writechunks(ui, gen, fname)
370 changegroup.writechunks(ui, gen, fname)
371
371
372 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
372 ui.write(_('bundle requirements: %s\n') % ', '.join(sorted(requirements)))
373
373
374 @command('debugdag',
374 @command('debugdag',
375 [('t', 'tags', None, _('use tags as labels')),
375 [('t', 'tags', None, _('use tags as labels')),
376 ('b', 'branches', None, _('annotate with branch names')),
376 ('b', 'branches', None, _('annotate with branch names')),
377 ('', 'dots', None, _('use dots for runs')),
377 ('', 'dots', None, _('use dots for runs')),
378 ('s', 'spaces', None, _('separate elements by spaces'))],
378 ('s', 'spaces', None, _('separate elements by spaces'))],
379 _('[OPTION]... [FILE [REV]...]'),
379 _('[OPTION]... [FILE [REV]...]'),
380 optionalrepo=True)
380 optionalrepo=True)
381 def debugdag(ui, repo, file_=None, *revs, **opts):
381 def debugdag(ui, repo, file_=None, *revs, **opts):
382 """format the changelog or an index DAG as a concise textual description
382 """format the changelog or an index DAG as a concise textual description
383
383
384 If you pass a revlog index, the revlog's DAG is emitted. If you list
384 If you pass a revlog index, the revlog's DAG is emitted. If you list
385 revision numbers, they get labeled in the output as rN.
385 revision numbers, they get labeled in the output as rN.
386
386
387 Otherwise, the changelog DAG of the current repo is emitted.
387 Otherwise, the changelog DAG of the current repo is emitted.
388 """
388 """
389 spaces = opts.get('spaces')
389 spaces = opts.get('spaces')
390 dots = opts.get('dots')
390 dots = opts.get('dots')
391 if file_:
391 if file_:
392 rlog = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
392 rlog = revlog.revlog(scmutil.opener(pycompat.getcwd(), audit=False),
393 file_)
393 file_)
394 revs = set((int(r) for r in revs))
394 revs = set((int(r) for r in revs))
395 def events():
395 def events():
396 for r in rlog:
396 for r in rlog:
397 yield 'n', (r, list(p for p in rlog.parentrevs(r)
397 yield 'n', (r, list(p for p in rlog.parentrevs(r)
398 if p != -1))
398 if p != -1))
399 if r in revs:
399 if r in revs:
400 yield 'l', (r, "r%i" % r)
400 yield 'l', (r, "r%i" % r)
401 elif repo:
401 elif repo:
402 cl = repo.changelog
402 cl = repo.changelog
403 tags = opts.get('tags')
403 tags = opts.get('tags')
404 branches = opts.get('branches')
404 branches = opts.get('branches')
405 if tags:
405 if tags:
406 labels = {}
406 labels = {}
407 for l, n in repo.tags().items():
407 for l, n in repo.tags().items():
408 labels.setdefault(cl.rev(n), []).append(l)
408 labels.setdefault(cl.rev(n), []).append(l)
409 def events():
409 def events():
410 b = "default"
410 b = "default"
411 for r in cl:
411 for r in cl:
412 if branches:
412 if branches:
413 newb = cl.read(cl.node(r))[5]['branch']
413 newb = cl.read(cl.node(r))[5]['branch']
414 if newb != b:
414 if newb != b:
415 yield 'a', newb
415 yield 'a', newb
416 b = newb
416 b = newb
417 yield 'n', (r, list(p for p in cl.parentrevs(r)
417 yield 'n', (r, list(p for p in cl.parentrevs(r)
418 if p != -1))
418 if p != -1))
419 if tags:
419 if tags:
420 ls = labels.get(r)
420 ls = labels.get(r)
421 if ls:
421 if ls:
422 for l in ls:
422 for l in ls:
423 yield 'l', (r, l)
423 yield 'l', (r, l)
424 else:
424 else:
425 raise error.Abort(_('need repo for changelog dag'))
425 raise error.Abort(_('need repo for changelog dag'))
426
426
427 for line in dagparser.dagtextlines(events(),
427 for line in dagparser.dagtextlines(events(),
428 addspaces=spaces,
428 addspaces=spaces,
429 wraplabels=True,
429 wraplabels=True,
430 wrapannotations=True,
430 wrapannotations=True,
431 wrapnonlinear=dots,
431 wrapnonlinear=dots,
432 usedots=dots,
432 usedots=dots,
433 maxlinewidth=70):
433 maxlinewidth=70):
434 ui.write(line)
434 ui.write(line)
435 ui.write("\n")
435 ui.write("\n")
436
436
437 @command('debugdata', commands.debugrevlogopts, _('-c|-m|FILE REV'))
437 @command('debugdata', commands.debugrevlogopts, _('-c|-m|FILE REV'))
438 def debugdata(ui, repo, file_, rev=None, **opts):
438 def debugdata(ui, repo, file_, rev=None, **opts):
439 """dump the contents of a data file revision"""
439 """dump the contents of a data file revision"""
440 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
440 if opts.get('changelog') or opts.get('manifest') or opts.get('dir'):
441 if rev is not None:
441 if rev is not None:
442 raise error.CommandError('debugdata', _('invalid arguments'))
442 raise error.CommandError('debugdata', _('invalid arguments'))
443 file_, rev = None, file_
443 file_, rev = None, file_
444 elif rev is None:
444 elif rev is None:
445 raise error.CommandError('debugdata', _('invalid arguments'))
445 raise error.CommandError('debugdata', _('invalid arguments'))
446 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
446 r = cmdutil.openrevlog(repo, 'debugdata', file_, opts)
447 try:
447 try:
448 ui.write(r.revision(r.lookup(rev)))
448 ui.write(r.revision(r.lookup(rev), raw=True))
449 except KeyError:
449 except KeyError:
450 raise error.Abort(_('invalid revision identifier %s') % rev)
450 raise error.Abort(_('invalid revision identifier %s') % rev)
451
451
452 @command('debugdate',
452 @command('debugdate',
453 [('e', 'extended', None, _('try extended date formats'))],
453 [('e', 'extended', None, _('try extended date formats'))],
454 _('[-e] DATE [RANGE]'),
454 _('[-e] DATE [RANGE]'),
455 norepo=True, optionalrepo=True)
455 norepo=True, optionalrepo=True)
456 def debugdate(ui, date, range=None, **opts):
456 def debugdate(ui, date, range=None, **opts):
457 """parse and display a date"""
457 """parse and display a date"""
458 if opts["extended"]:
458 if opts["extended"]:
459 d = util.parsedate(date, util.extendeddateformats)
459 d = util.parsedate(date, util.extendeddateformats)
460 else:
460 else:
461 d = util.parsedate(date)
461 d = util.parsedate(date)
462 ui.write(("internal: %s %s\n") % d)
462 ui.write(("internal: %s %s\n") % d)
463 ui.write(("standard: %s\n") % util.datestr(d))
463 ui.write(("standard: %s\n") % util.datestr(d))
464 if range:
464 if range:
465 m = util.matchdate(range)
465 m = util.matchdate(range)
466 ui.write(("match: %s\n") % m(d[0]))
466 ui.write(("match: %s\n") % m(d[0]))
467
467
468 @command('debugdeltachain',
468 @command('debugdeltachain',
469 commands.debugrevlogopts + commands.formatteropts,
469 commands.debugrevlogopts + commands.formatteropts,
470 _('-c|-m|FILE'),
470 _('-c|-m|FILE'),
471 optionalrepo=True)
471 optionalrepo=True)
472 def debugdeltachain(ui, repo, file_=None, **opts):
472 def debugdeltachain(ui, repo, file_=None, **opts):
473 """dump information about delta chains in a revlog
473 """dump information about delta chains in a revlog
474
474
475 Output can be templatized. Available template keywords are:
475 Output can be templatized. Available template keywords are:
476
476
477 :``rev``: revision number
477 :``rev``: revision number
478 :``chainid``: delta chain identifier (numbered by unique base)
478 :``chainid``: delta chain identifier (numbered by unique base)
479 :``chainlen``: delta chain length to this revision
479 :``chainlen``: delta chain length to this revision
480 :``prevrev``: previous revision in delta chain
480 :``prevrev``: previous revision in delta chain
481 :``deltatype``: role of delta / how it was computed
481 :``deltatype``: role of delta / how it was computed
482 :``compsize``: compressed size of revision
482 :``compsize``: compressed size of revision
483 :``uncompsize``: uncompressed size of revision
483 :``uncompsize``: uncompressed size of revision
484 :``chainsize``: total size of compressed revisions in chain
484 :``chainsize``: total size of compressed revisions in chain
485 :``chainratio``: total chain size divided by uncompressed revision size
485 :``chainratio``: total chain size divided by uncompressed revision size
486 (new delta chains typically start at ratio 2.00)
486 (new delta chains typically start at ratio 2.00)
487 :``lindist``: linear distance from base revision in delta chain to end
487 :``lindist``: linear distance from base revision in delta chain to end
488 of this revision
488 of this revision
489 :``extradist``: total size of revisions not part of this delta chain from
489 :``extradist``: total size of revisions not part of this delta chain from
490 base of delta chain to end of this revision; a measurement
490 base of delta chain to end of this revision; a measurement
491 of how much extra data we need to read/seek across to read
491 of how much extra data we need to read/seek across to read
492 the delta chain for this revision
492 the delta chain for this revision
493 :``extraratio``: extradist divided by chainsize; another representation of
493 :``extraratio``: extradist divided by chainsize; another representation of
494 how much unrelated data is needed to load this delta chain
494 how much unrelated data is needed to load this delta chain
495 """
495 """
496 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
496 r = cmdutil.openrevlog(repo, 'debugdeltachain', file_, opts)
497 index = r.index
497 index = r.index
498 generaldelta = r.version & revlog.REVLOGGENERALDELTA
498 generaldelta = r.version & revlog.REVLOGGENERALDELTA
499
499
500 def revinfo(rev):
500 def revinfo(rev):
501 e = index[rev]
501 e = index[rev]
502 compsize = e[1]
502 compsize = e[1]
503 uncompsize = e[2]
503 uncompsize = e[2]
504 chainsize = 0
504 chainsize = 0
505
505
506 if generaldelta:
506 if generaldelta:
507 if e[3] == e[5]:
507 if e[3] == e[5]:
508 deltatype = 'p1'
508 deltatype = 'p1'
509 elif e[3] == e[6]:
509 elif e[3] == e[6]:
510 deltatype = 'p2'
510 deltatype = 'p2'
511 elif e[3] == rev - 1:
511 elif e[3] == rev - 1:
512 deltatype = 'prev'
512 deltatype = 'prev'
513 elif e[3] == rev:
513 elif e[3] == rev:
514 deltatype = 'base'
514 deltatype = 'base'
515 else:
515 else:
516 deltatype = 'other'
516 deltatype = 'other'
517 else:
517 else:
518 if e[3] == rev:
518 if e[3] == rev:
519 deltatype = 'base'
519 deltatype = 'base'
520 else:
520 else:
521 deltatype = 'prev'
521 deltatype = 'prev'
522
522
523 chain = r._deltachain(rev)[0]
523 chain = r._deltachain(rev)[0]
524 for iterrev in chain:
524 for iterrev in chain:
525 e = index[iterrev]
525 e = index[iterrev]
526 chainsize += e[1]
526 chainsize += e[1]
527
527
528 return compsize, uncompsize, deltatype, chain, chainsize
528 return compsize, uncompsize, deltatype, chain, chainsize
529
529
530 fm = ui.formatter('debugdeltachain', opts)
530 fm = ui.formatter('debugdeltachain', opts)
531
531
532 fm.plain(' rev chain# chainlen prev delta '
532 fm.plain(' rev chain# chainlen prev delta '
533 'size rawsize chainsize ratio lindist extradist '
533 'size rawsize chainsize ratio lindist extradist '
534 'extraratio\n')
534 'extraratio\n')
535
535
536 chainbases = {}
536 chainbases = {}
537 for rev in r:
537 for rev in r:
538 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
538 comp, uncomp, deltatype, chain, chainsize = revinfo(rev)
539 chainbase = chain[0]
539 chainbase = chain[0]
540 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
540 chainid = chainbases.setdefault(chainbase, len(chainbases) + 1)
541 basestart = r.start(chainbase)
541 basestart = r.start(chainbase)
542 revstart = r.start(rev)
542 revstart = r.start(rev)
543 lineardist = revstart + comp - basestart
543 lineardist = revstart + comp - basestart
544 extradist = lineardist - chainsize
544 extradist = lineardist - chainsize
545 try:
545 try:
546 prevrev = chain[-2]
546 prevrev = chain[-2]
547 except IndexError:
547 except IndexError:
548 prevrev = -1
548 prevrev = -1
549
549
550 chainratio = float(chainsize) / float(uncomp)
550 chainratio = float(chainsize) / float(uncomp)
551 extraratio = float(extradist) / float(chainsize)
551 extraratio = float(extradist) / float(chainsize)
552
552
553 fm.startitem()
553 fm.startitem()
554 fm.write('rev chainid chainlen prevrev deltatype compsize '
554 fm.write('rev chainid chainlen prevrev deltatype compsize '
555 'uncompsize chainsize chainratio lindist extradist '
555 'uncompsize chainsize chainratio lindist extradist '
556 'extraratio',
556 'extraratio',
557 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
557 '%7d %7d %8d %8d %7s %10d %10d %10d %9.5f %9d %9d %10.5f\n',
558 rev, chainid, len(chain), prevrev, deltatype, comp,
558 rev, chainid, len(chain), prevrev, deltatype, comp,
559 uncomp, chainsize, chainratio, lineardist, extradist,
559 uncomp, chainsize, chainratio, lineardist, extradist,
560 extraratio,
560 extraratio,
561 rev=rev, chainid=chainid, chainlen=len(chain),
561 rev=rev, chainid=chainid, chainlen=len(chain),
562 prevrev=prevrev, deltatype=deltatype, compsize=comp,
562 prevrev=prevrev, deltatype=deltatype, compsize=comp,
563 uncompsize=uncomp, chainsize=chainsize,
563 uncompsize=uncomp, chainsize=chainsize,
564 chainratio=chainratio, lindist=lineardist,
564 chainratio=chainratio, lindist=lineardist,
565 extradist=extradist, extraratio=extraratio)
565 extradist=extradist, extraratio=extraratio)
566
566
567 fm.end()
567 fm.end()
568
568
569 @command('debugdiscovery',
569 @command('debugdiscovery',
570 [('', 'old', None, _('use old-style discovery')),
570 [('', 'old', None, _('use old-style discovery')),
571 ('', 'nonheads', None,
571 ('', 'nonheads', None,
572 _('use old-style discovery with non-heads included')),
572 _('use old-style discovery with non-heads included')),
573 ] + commands.remoteopts,
573 ] + commands.remoteopts,
574 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
574 _('[-l REV] [-r REV] [-b BRANCH]... [OTHER]'))
575 def debugdiscovery(ui, repo, remoteurl="default", **opts):
575 def debugdiscovery(ui, repo, remoteurl="default", **opts):
576 """runs the changeset discovery protocol in isolation"""
576 """runs the changeset discovery protocol in isolation"""
577 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
577 remoteurl, branches = hg.parseurl(ui.expandpath(remoteurl),
578 opts.get('branch'))
578 opts.get('branch'))
579 remote = hg.peer(repo, opts, remoteurl)
579 remote = hg.peer(repo, opts, remoteurl)
580 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
580 ui.status(_('comparing with %s\n') % util.hidepassword(remoteurl))
581
581
582 # make sure tests are repeatable
582 # make sure tests are repeatable
583 random.seed(12323)
583 random.seed(12323)
584
584
585 def doit(localheads, remoteheads, remote=remote):
585 def doit(localheads, remoteheads, remote=remote):
586 if opts.get('old'):
586 if opts.get('old'):
587 if localheads:
587 if localheads:
588 raise error.Abort('cannot use localheads with old style '
588 raise error.Abort('cannot use localheads with old style '
589 'discovery')
589 'discovery')
590 if not util.safehasattr(remote, 'branches'):
590 if not util.safehasattr(remote, 'branches'):
591 # enable in-client legacy support
591 # enable in-client legacy support
592 remote = localrepo.locallegacypeer(remote.local())
592 remote = localrepo.locallegacypeer(remote.local())
593 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
593 common, _in, hds = treediscovery.findcommonincoming(repo, remote,
594 force=True)
594 force=True)
595 common = set(common)
595 common = set(common)
596 if not opts.get('nonheads'):
596 if not opts.get('nonheads'):
597 ui.write(("unpruned common: %s\n") %
597 ui.write(("unpruned common: %s\n") %
598 " ".join(sorted(short(n) for n in common)))
598 " ".join(sorted(short(n) for n in common)))
599 dag = dagutil.revlogdag(repo.changelog)
599 dag = dagutil.revlogdag(repo.changelog)
600 all = dag.ancestorset(dag.internalizeall(common))
600 all = dag.ancestorset(dag.internalizeall(common))
601 common = dag.externalizeall(dag.headsetofconnecteds(all))
601 common = dag.externalizeall(dag.headsetofconnecteds(all))
602 else:
602 else:
603 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
603 common, any, hds = setdiscovery.findcommonheads(ui, repo, remote)
604 common = set(common)
604 common = set(common)
605 rheads = set(hds)
605 rheads = set(hds)
606 lheads = set(repo.heads())
606 lheads = set(repo.heads())
607 ui.write(("common heads: %s\n") %
607 ui.write(("common heads: %s\n") %
608 " ".join(sorted(short(n) for n in common)))
608 " ".join(sorted(short(n) for n in common)))
609 if lheads <= common:
609 if lheads <= common:
610 ui.write(("local is subset\n"))
610 ui.write(("local is subset\n"))
611 elif rheads <= common:
611 elif rheads <= common:
612 ui.write(("remote is subset\n"))
612 ui.write(("remote is subset\n"))
613
613
614 serverlogs = opts.get('serverlog')
614 serverlogs = opts.get('serverlog')
615 if serverlogs:
615 if serverlogs:
616 for filename in serverlogs:
616 for filename in serverlogs:
617 with open(filename, 'r') as logfile:
617 with open(filename, 'r') as logfile:
618 line = logfile.readline()
618 line = logfile.readline()
619 while line:
619 while line:
620 parts = line.strip().split(';')
620 parts = line.strip().split(';')
621 op = parts[1]
621 op = parts[1]
622 if op == 'cg':
622 if op == 'cg':
623 pass
623 pass
624 elif op == 'cgss':
624 elif op == 'cgss':
625 doit(parts[2].split(' '), parts[3].split(' '))
625 doit(parts[2].split(' '), parts[3].split(' '))
626 elif op == 'unb':
626 elif op == 'unb':
627 doit(parts[3].split(' '), parts[2].split(' '))
627 doit(parts[3].split(' '), parts[2].split(' '))
628 line = logfile.readline()
628 line = logfile.readline()
629 else:
629 else:
630 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
630 remoterevs, _checkout = hg.addbranchrevs(repo, remote, branches,
631 opts.get('remote_head'))
631 opts.get('remote_head'))
632 localrevs = opts.get('local_head')
632 localrevs = opts.get('local_head')
633 doit(localrevs, remoterevs)
633 doit(localrevs, remoterevs)
634
634
635 @command('debugextensions', commands.formatteropts, [], norepo=True)
635 @command('debugextensions', commands.formatteropts, [], norepo=True)
636 def debugextensions(ui, **opts):
636 def debugextensions(ui, **opts):
637 '''show information about active extensions'''
637 '''show information about active extensions'''
638 exts = extensions.extensions(ui)
638 exts = extensions.extensions(ui)
639 hgver = util.version()
639 hgver = util.version()
640 fm = ui.formatter('debugextensions', opts)
640 fm = ui.formatter('debugextensions', opts)
641 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
641 for extname, extmod in sorted(exts, key=operator.itemgetter(0)):
642 isinternal = extensions.ismoduleinternal(extmod)
642 isinternal = extensions.ismoduleinternal(extmod)
643 extsource = extmod.__file__
643 extsource = extmod.__file__
644 if isinternal:
644 if isinternal:
645 exttestedwith = [] # never expose magic string to users
645 exttestedwith = [] # never expose magic string to users
646 else:
646 else:
647 exttestedwith = getattr(extmod, 'testedwith', '').split()
647 exttestedwith = getattr(extmod, 'testedwith', '').split()
648 extbuglink = getattr(extmod, 'buglink', None)
648 extbuglink = getattr(extmod, 'buglink', None)
649
649
650 fm.startitem()
650 fm.startitem()
651
651
652 if ui.quiet or ui.verbose:
652 if ui.quiet or ui.verbose:
653 fm.write('name', '%s\n', extname)
653 fm.write('name', '%s\n', extname)
654 else:
654 else:
655 fm.write('name', '%s', extname)
655 fm.write('name', '%s', extname)
656 if isinternal or hgver in exttestedwith:
656 if isinternal or hgver in exttestedwith:
657 fm.plain('\n')
657 fm.plain('\n')
658 elif not exttestedwith:
658 elif not exttestedwith:
659 fm.plain(_(' (untested!)\n'))
659 fm.plain(_(' (untested!)\n'))
660 else:
660 else:
661 lasttestedversion = exttestedwith[-1]
661 lasttestedversion = exttestedwith[-1]
662 fm.plain(' (%s!)\n' % lasttestedversion)
662 fm.plain(' (%s!)\n' % lasttestedversion)
663
663
664 fm.condwrite(ui.verbose and extsource, 'source',
664 fm.condwrite(ui.verbose and extsource, 'source',
665 _(' location: %s\n'), extsource or "")
665 _(' location: %s\n'), extsource or "")
666
666
667 if ui.verbose:
667 if ui.verbose:
668 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
668 fm.plain(_(' bundled: %s\n') % ['no', 'yes'][isinternal])
669 fm.data(bundled=isinternal)
669 fm.data(bundled=isinternal)
670
670
671 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
671 fm.condwrite(ui.verbose and exttestedwith, 'testedwith',
672 _(' tested with: %s\n'),
672 _(' tested with: %s\n'),
673 fm.formatlist(exttestedwith, name='ver'))
673 fm.formatlist(exttestedwith, name='ver'))
674
674
675 fm.condwrite(ui.verbose and extbuglink, 'buglink',
675 fm.condwrite(ui.verbose and extbuglink, 'buglink',
676 _(' bug reporting: %s\n'), extbuglink or "")
676 _(' bug reporting: %s\n'), extbuglink or "")
677
677
678 fm.end()
678 fm.end()
679
679
680 @command('debugfileset',
680 @command('debugfileset',
681 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
681 [('r', 'rev', '', _('apply the filespec on this revision'), _('REV'))],
682 _('[-r REV] FILESPEC'))
682 _('[-r REV] FILESPEC'))
683 def debugfileset(ui, repo, expr, **opts):
683 def debugfileset(ui, repo, expr, **opts):
684 '''parse and apply a fileset specification'''
684 '''parse and apply a fileset specification'''
685 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
685 ctx = scmutil.revsingle(repo, opts.get('rev'), None)
686 if ui.verbose:
686 if ui.verbose:
687 tree = fileset.parse(expr)
687 tree = fileset.parse(expr)
688 ui.note(fileset.prettyformat(tree), "\n")
688 ui.note(fileset.prettyformat(tree), "\n")
689
689
690 for f in ctx.getfileset(expr):
690 for f in ctx.getfileset(expr):
691 ui.write("%s\n" % f)
691 ui.write("%s\n" % f)
692
692
693 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
693 @command('debugfsinfo', [], _('[PATH]'), norepo=True)
694 def debugfsinfo(ui, path="."):
694 def debugfsinfo(ui, path="."):
695 """show information detected about current filesystem"""
695 """show information detected about current filesystem"""
696 util.writefile('.debugfsinfo', '')
696 util.writefile('.debugfsinfo', '')
697 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
697 ui.write(('exec: %s\n') % (util.checkexec(path) and 'yes' or 'no'))
698 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
698 ui.write(('symlink: %s\n') % (util.checklink(path) and 'yes' or 'no'))
699 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
699 ui.write(('hardlink: %s\n') % (util.checknlink(path) and 'yes' or 'no'))
700 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
700 ui.write(('case-sensitive: %s\n') % (util.fscasesensitive('.debugfsinfo')
701 and 'yes' or 'no'))
701 and 'yes' or 'no'))
702 os.unlink('.debugfsinfo')
702 os.unlink('.debugfsinfo')
703
703
704 @command('debuggetbundle',
704 @command('debuggetbundle',
705 [('H', 'head', [], _('id of head node'), _('ID')),
705 [('H', 'head', [], _('id of head node'), _('ID')),
706 ('C', 'common', [], _('id of common node'), _('ID')),
706 ('C', 'common', [], _('id of common node'), _('ID')),
707 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
707 ('t', 'type', 'bzip2', _('bundle compression type to use'), _('TYPE'))],
708 _('REPO FILE [-H|-C ID]...'),
708 _('REPO FILE [-H|-C ID]...'),
709 norepo=True)
709 norepo=True)
710 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
710 def debuggetbundle(ui, repopath, bundlepath, head=None, common=None, **opts):
711 """retrieves a bundle from a repo
711 """retrieves a bundle from a repo
712
712
713 Every ID must be a full-length hex node id string. Saves the bundle to the
713 Every ID must be a full-length hex node id string. Saves the bundle to the
714 given file.
714 given file.
715 """
715 """
716 repo = hg.peer(ui, opts, repopath)
716 repo = hg.peer(ui, opts, repopath)
717 if not repo.capable('getbundle'):
717 if not repo.capable('getbundle'):
718 raise error.Abort("getbundle() not supported by target repository")
718 raise error.Abort("getbundle() not supported by target repository")
719 args = {}
719 args = {}
720 if common:
720 if common:
721 args['common'] = [bin(s) for s in common]
721 args['common'] = [bin(s) for s in common]
722 if head:
722 if head:
723 args['heads'] = [bin(s) for s in head]
723 args['heads'] = [bin(s) for s in head]
724 # TODO: get desired bundlecaps from command line.
724 # TODO: get desired bundlecaps from command line.
725 args['bundlecaps'] = None
725 args['bundlecaps'] = None
726 bundle = repo.getbundle('debug', **args)
726 bundle = repo.getbundle('debug', **args)
727
727
728 bundletype = opts.get('type', 'bzip2').lower()
728 bundletype = opts.get('type', 'bzip2').lower()
729 btypes = {'none': 'HG10UN',
729 btypes = {'none': 'HG10UN',
730 'bzip2': 'HG10BZ',
730 'bzip2': 'HG10BZ',
731 'gzip': 'HG10GZ',
731 'gzip': 'HG10GZ',
732 'bundle2': 'HG20'}
732 'bundle2': 'HG20'}
733 bundletype = btypes.get(bundletype)
733 bundletype = btypes.get(bundletype)
734 if bundletype not in bundle2.bundletypes:
734 if bundletype not in bundle2.bundletypes:
735 raise error.Abort(_('unknown bundle type specified with --type'))
735 raise error.Abort(_('unknown bundle type specified with --type'))
736 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
736 bundle2.writebundle(ui, bundle, bundlepath, bundletype)
737
737
738 @command('debugignore', [], '[FILE]')
738 @command('debugignore', [], '[FILE]')
739 def debugignore(ui, repo, *files, **opts):
739 def debugignore(ui, repo, *files, **opts):
740 """display the combined ignore pattern and information about ignored files
740 """display the combined ignore pattern and information about ignored files
741
741
742 With no argument display the combined ignore pattern.
742 With no argument display the combined ignore pattern.
743
743
744 Given space separated file names, shows if the given file is ignored and
744 Given space separated file names, shows if the given file is ignored and
745 if so, show the ignore rule (file and line number) that matched it.
745 if so, show the ignore rule (file and line number) that matched it.
746 """
746 """
747 ignore = repo.dirstate._ignore
747 ignore = repo.dirstate._ignore
748 if not files:
748 if not files:
749 # Show all the patterns
749 # Show all the patterns
750 includepat = getattr(ignore, 'includepat', None)
750 includepat = getattr(ignore, 'includepat', None)
751 if includepat is not None:
751 if includepat is not None:
752 ui.write("%s\n" % includepat)
752 ui.write("%s\n" % includepat)
753 else:
753 else:
754 raise error.Abort(_("no ignore patterns found"))
754 raise error.Abort(_("no ignore patterns found"))
755 else:
755 else:
756 for f in files:
756 for f in files:
757 nf = util.normpath(f)
757 nf = util.normpath(f)
758 ignored = None
758 ignored = None
759 ignoredata = None
759 ignoredata = None
760 if nf != '.':
760 if nf != '.':
761 if ignore(nf):
761 if ignore(nf):
762 ignored = nf
762 ignored = nf
763 ignoredata = repo.dirstate._ignorefileandline(nf)
763 ignoredata = repo.dirstate._ignorefileandline(nf)
764 else:
764 else:
765 for p in util.finddirs(nf):
765 for p in util.finddirs(nf):
766 if ignore(p):
766 if ignore(p):
767 ignored = p
767 ignored = p
768 ignoredata = repo.dirstate._ignorefileandline(p)
768 ignoredata = repo.dirstate._ignorefileandline(p)
769 break
769 break
770 if ignored:
770 if ignored:
771 if ignored == nf:
771 if ignored == nf:
772 ui.write(_("%s is ignored\n") % f)
772 ui.write(_("%s is ignored\n") % f)
773 else:
773 else:
774 ui.write(_("%s is ignored because of "
774 ui.write(_("%s is ignored because of "
775 "containing folder %s\n")
775 "containing folder %s\n")
776 % (f, ignored))
776 % (f, ignored))
777 ignorefile, lineno, line = ignoredata
777 ignorefile, lineno, line = ignoredata
778 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
778 ui.write(_("(ignore rule in %s, line %d: '%s')\n")
779 % (ignorefile, lineno, line))
779 % (ignorefile, lineno, line))
780 else:
780 else:
781 ui.write(_("%s is not ignored\n") % f)
781 ui.write(_("%s is not ignored\n") % f)
782
782
783 @command('debugindex', commands.debugrevlogopts +
783 @command('debugindex', commands.debugrevlogopts +
784 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
784 [('f', 'format', 0, _('revlog format'), _('FORMAT'))],
785 _('[-f FORMAT] -c|-m|FILE'),
785 _('[-f FORMAT] -c|-m|FILE'),
786 optionalrepo=True)
786 optionalrepo=True)
787 def debugindex(ui, repo, file_=None, **opts):
787 def debugindex(ui, repo, file_=None, **opts):
788 """dump the contents of an index file"""
788 """dump the contents of an index file"""
789 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
789 r = cmdutil.openrevlog(repo, 'debugindex', file_, opts)
790 format = opts.get('format', 0)
790 format = opts.get('format', 0)
791 if format not in (0, 1):
791 if format not in (0, 1):
792 raise error.Abort(_("unknown format %d") % format)
792 raise error.Abort(_("unknown format %d") % format)
793
793
794 generaldelta = r.version & revlog.REVLOGGENERALDELTA
794 generaldelta = r.version & revlog.REVLOGGENERALDELTA
795 if generaldelta:
795 if generaldelta:
796 basehdr = ' delta'
796 basehdr = ' delta'
797 else:
797 else:
798 basehdr = ' base'
798 basehdr = ' base'
799
799
800 if ui.debugflag:
800 if ui.debugflag:
801 shortfn = hex
801 shortfn = hex
802 else:
802 else:
803 shortfn = short
803 shortfn = short
804
804
805 # There might not be anything in r, so have a sane default
805 # There might not be anything in r, so have a sane default
806 idlen = 12
806 idlen = 12
807 for i in r:
807 for i in r:
808 idlen = len(shortfn(r.node(i)))
808 idlen = len(shortfn(r.node(i)))
809 break
809 break
810
810
811 if format == 0:
811 if format == 0:
812 ui.write((" rev offset length " + basehdr + " linkrev"
812 ui.write((" rev offset length " + basehdr + " linkrev"
813 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
813 " %s %s p2\n") % ("nodeid".ljust(idlen), "p1".ljust(idlen)))
814 elif format == 1:
814 elif format == 1:
815 ui.write((" rev flag offset length"
815 ui.write((" rev flag offset length"
816 " size " + basehdr + " link p1 p2"
816 " size " + basehdr + " link p1 p2"
817 " %s\n") % "nodeid".rjust(idlen))
817 " %s\n") % "nodeid".rjust(idlen))
818
818
819 for i in r:
819 for i in r:
820 node = r.node(i)
820 node = r.node(i)
821 if generaldelta:
821 if generaldelta:
822 base = r.deltaparent(i)
822 base = r.deltaparent(i)
823 else:
823 else:
824 base = r.chainbase(i)
824 base = r.chainbase(i)
825 if format == 0:
825 if format == 0:
826 try:
826 try:
827 pp = r.parents(node)
827 pp = r.parents(node)
828 except Exception:
828 except Exception:
829 pp = [nullid, nullid]
829 pp = [nullid, nullid]
830 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
830 ui.write("% 6d % 9d % 7d % 6d % 7d %s %s %s\n" % (
831 i, r.start(i), r.length(i), base, r.linkrev(i),
831 i, r.start(i), r.length(i), base, r.linkrev(i),
832 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
832 shortfn(node), shortfn(pp[0]), shortfn(pp[1])))
833 elif format == 1:
833 elif format == 1:
834 pr = r.parentrevs(i)
834 pr = r.parentrevs(i)
835 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
835 ui.write("% 6d %04x % 8d % 8d % 8d % 6d % 6d % 6d % 6d %s\n" % (
836 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
836 i, r.flags(i), r.start(i), r.length(i), r.rawsize(i),
837 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
837 base, r.linkrev(i), pr[0], pr[1], shortfn(node)))
838
838
839 @command('debugindexdot', commands.debugrevlogopts,
839 @command('debugindexdot', commands.debugrevlogopts,
840 _('-c|-m|FILE'), optionalrepo=True)
840 _('-c|-m|FILE'), optionalrepo=True)
841 def debugindexdot(ui, repo, file_=None, **opts):
841 def debugindexdot(ui, repo, file_=None, **opts):
842 """dump an index DAG as a graphviz dot file"""
842 """dump an index DAG as a graphviz dot file"""
843 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
843 r = cmdutil.openrevlog(repo, 'debugindexdot', file_, opts)
844 ui.write(("digraph G {\n"))
844 ui.write(("digraph G {\n"))
845 for i in r:
845 for i in r:
846 node = r.node(i)
846 node = r.node(i)
847 pp = r.parents(node)
847 pp = r.parents(node)
848 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
848 ui.write("\t%d -> %d\n" % (r.rev(pp[0]), i))
849 if pp[1] != nullid:
849 if pp[1] != nullid:
850 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
850 ui.write("\t%d -> %d\n" % (r.rev(pp[1]), i))
851 ui.write("}\n")
851 ui.write("}\n")
@@ -1,1822 +1,1832 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 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import errno
17 import errno
18 import hashlib
18 import hashlib
19 import os
19 import os
20 import struct
20 import struct
21 import zlib
21 import zlib
22
22
23 # import stuff from node for others to import from revlog
23 # import stuff from node for others to import from revlog
24 from .node import (
24 from .node import (
25 bin,
25 bin,
26 hex,
26 hex,
27 nullid,
27 nullid,
28 nullrev,
28 nullrev,
29 )
29 )
30 from .i18n import _
30 from .i18n import _
31 from . import (
31 from . import (
32 ancestor,
32 ancestor,
33 error,
33 error,
34 mdiff,
34 mdiff,
35 parsers,
35 parsers,
36 templatefilters,
36 templatefilters,
37 util,
37 util,
38 )
38 )
39
39
40 _pack = struct.pack
40 _pack = struct.pack
41 _unpack = struct.unpack
41 _unpack = struct.unpack
42 _compress = zlib.compress
42 _compress = zlib.compress
43 _decompress = zlib.decompress
43 _decompress = zlib.decompress
44
44
45 # revlog header flags
45 # revlog header flags
46 REVLOGV0 = 0
46 REVLOGV0 = 0
47 REVLOGNG = 1
47 REVLOGNG = 1
48 REVLOGNGINLINEDATA = (1 << 16)
48 REVLOGNGINLINEDATA = (1 << 16)
49 REVLOGGENERALDELTA = (1 << 17)
49 REVLOGGENERALDELTA = (1 << 17)
50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
50 REVLOG_DEFAULT_FLAGS = REVLOGNGINLINEDATA
51 REVLOG_DEFAULT_FORMAT = REVLOGNG
51 REVLOG_DEFAULT_FORMAT = REVLOGNG
52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
52 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
53 REVLOGNG_FLAGS = REVLOGNGINLINEDATA | REVLOGGENERALDELTA
54
54
55 # revlog index flags
55 # revlog index flags
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
56 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
57 REVIDX_DEFAULT_FLAGS = 0
57 REVIDX_DEFAULT_FLAGS = 0
58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
58 REVIDX_KNOWN_FLAGS = REVIDX_ISCENSORED
59
59
60 # max size of revlog with inline data
60 # max size of revlog with inline data
61 _maxinline = 131072
61 _maxinline = 131072
62 _chunksize = 1048576
62 _chunksize = 1048576
63
63
64 RevlogError = error.RevlogError
64 RevlogError = error.RevlogError
65 LookupError = error.LookupError
65 LookupError = error.LookupError
66 CensoredNodeError = error.CensoredNodeError
66 CensoredNodeError = error.CensoredNodeError
67
67
68 def getoffset(q):
68 def getoffset(q):
69 return int(q >> 16)
69 return int(q >> 16)
70
70
71 def gettype(q):
71 def gettype(q):
72 return int(q & 0xFFFF)
72 return int(q & 0xFFFF)
73
73
74 def offset_type(offset, type):
74 def offset_type(offset, type):
75 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
75 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
76 raise ValueError('unknown revlog index flags')
76 raise ValueError('unknown revlog index flags')
77 return long(long(offset) << 16 | type)
77 return long(long(offset) << 16 | type)
78
78
79 _nullhash = hashlib.sha1(nullid)
79 _nullhash = hashlib.sha1(nullid)
80
80
81 def hash(text, p1, p2):
81 def hash(text, p1, p2):
82 """generate a hash from the given text and its parent hashes
82 """generate a hash from the given text and its parent hashes
83
83
84 This hash combines both the current file contents and its history
84 This hash combines both the current file contents and its history
85 in a manner that makes it easy to distinguish nodes with the same
85 in a manner that makes it easy to distinguish nodes with the same
86 content in the revision graph.
86 content in the revision graph.
87 """
87 """
88 # As of now, if one of the parent node is null, p2 is null
88 # As of now, if one of the parent node is null, p2 is null
89 if p2 == nullid:
89 if p2 == nullid:
90 # deep copy of a hash is faster than creating one
90 # deep copy of a hash is faster than creating one
91 s = _nullhash.copy()
91 s = _nullhash.copy()
92 s.update(p1)
92 s.update(p1)
93 else:
93 else:
94 # none of the parent nodes are nullid
94 # none of the parent nodes are nullid
95 l = [p1, p2]
95 l = [p1, p2]
96 l.sort()
96 l.sort()
97 s = hashlib.sha1(l[0])
97 s = hashlib.sha1(l[0])
98 s.update(l[1])
98 s.update(l[1])
99 s.update(text)
99 s.update(text)
100 return s.digest()
100 return s.digest()
101
101
102 def decompress(bin):
102 def decompress(bin):
103 """ decompress the given input """
103 """ decompress the given input """
104 if not bin:
104 if not bin:
105 return bin
105 return bin
106 t = bin[0]
106 t = bin[0]
107 if t == '\0':
107 if t == '\0':
108 return bin
108 return bin
109 if t == 'x':
109 if t == 'x':
110 try:
110 try:
111 return _decompress(bin)
111 return _decompress(bin)
112 except zlib.error as e:
112 except zlib.error as e:
113 raise RevlogError(_("revlog decompress error: %s") % str(e))
113 raise RevlogError(_("revlog decompress error: %s") % str(e))
114 if t == 'u':
114 if t == 'u':
115 return util.buffer(bin, 1)
115 return util.buffer(bin, 1)
116 raise RevlogError(_("unknown compression type %r") % t)
116 raise RevlogError(_("unknown compression type %r") % t)
117
117
118 # index v0:
118 # index v0:
119 # 4 bytes: offset
119 # 4 bytes: offset
120 # 4 bytes: compressed length
120 # 4 bytes: compressed length
121 # 4 bytes: base rev
121 # 4 bytes: base rev
122 # 4 bytes: link rev
122 # 4 bytes: link rev
123 # 20 bytes: parent 1 nodeid
123 # 20 bytes: parent 1 nodeid
124 # 20 bytes: parent 2 nodeid
124 # 20 bytes: parent 2 nodeid
125 # 20 bytes: nodeid
125 # 20 bytes: nodeid
126 indexformatv0 = ">4l20s20s20s"
126 indexformatv0 = ">4l20s20s20s"
127
127
128 class revlogoldio(object):
128 class revlogoldio(object):
129 def __init__(self):
129 def __init__(self):
130 self.size = struct.calcsize(indexformatv0)
130 self.size = struct.calcsize(indexformatv0)
131
131
132 def parseindex(self, data, inline):
132 def parseindex(self, data, inline):
133 s = self.size
133 s = self.size
134 index = []
134 index = []
135 nodemap = {nullid: nullrev}
135 nodemap = {nullid: nullrev}
136 n = off = 0
136 n = off = 0
137 l = len(data)
137 l = len(data)
138 while off + s <= l:
138 while off + s <= l:
139 cur = data[off:off + s]
139 cur = data[off:off + s]
140 off += s
140 off += s
141 e = _unpack(indexformatv0, cur)
141 e = _unpack(indexformatv0, cur)
142 # transform to revlogv1 format
142 # transform to revlogv1 format
143 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
143 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
144 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
144 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
145 index.append(e2)
145 index.append(e2)
146 nodemap[e[6]] = n
146 nodemap[e[6]] = n
147 n += 1
147 n += 1
148
148
149 # add the magic null revision at -1
149 # add the magic null revision at -1
150 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
150 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
151
151
152 return index, nodemap, None
152 return index, nodemap, None
153
153
154 def packentry(self, entry, node, version, rev):
154 def packentry(self, entry, node, version, rev):
155 if gettype(entry[0]):
155 if gettype(entry[0]):
156 raise RevlogError(_("index entry flags need RevlogNG"))
156 raise RevlogError(_("index entry flags need RevlogNG"))
157 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
157 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
158 node(entry[5]), node(entry[6]), entry[7])
158 node(entry[5]), node(entry[6]), entry[7])
159 return _pack(indexformatv0, *e2)
159 return _pack(indexformatv0, *e2)
160
160
161 # index ng:
161 # index ng:
162 # 6 bytes: offset
162 # 6 bytes: offset
163 # 2 bytes: flags
163 # 2 bytes: flags
164 # 4 bytes: compressed length
164 # 4 bytes: compressed length
165 # 4 bytes: uncompressed length
165 # 4 bytes: uncompressed length
166 # 4 bytes: base rev
166 # 4 bytes: base rev
167 # 4 bytes: link rev
167 # 4 bytes: link rev
168 # 4 bytes: parent 1 rev
168 # 4 bytes: parent 1 rev
169 # 4 bytes: parent 2 rev
169 # 4 bytes: parent 2 rev
170 # 32 bytes: nodeid
170 # 32 bytes: nodeid
171 indexformatng = ">Qiiiiii20s12x"
171 indexformatng = ">Qiiiiii20s12x"
172 versionformat = ">I"
172 versionformat = ">I"
173
173
174 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
174 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
175 # signed integer)
175 # signed integer)
176 _maxentrysize = 0x7fffffff
176 _maxentrysize = 0x7fffffff
177
177
178 class revlogio(object):
178 class revlogio(object):
179 def __init__(self):
179 def __init__(self):
180 self.size = struct.calcsize(indexformatng)
180 self.size = struct.calcsize(indexformatng)
181
181
182 def parseindex(self, data, inline):
182 def parseindex(self, data, inline):
183 # call the C implementation to parse the index data
183 # call the C implementation to parse the index data
184 index, cache = parsers.parse_index2(data, inline)
184 index, cache = parsers.parse_index2(data, inline)
185 return index, getattr(index, 'nodemap', None), cache
185 return index, getattr(index, 'nodemap', None), cache
186
186
187 def packentry(self, entry, node, version, rev):
187 def packentry(self, entry, node, version, rev):
188 p = _pack(indexformatng, *entry)
188 p = _pack(indexformatng, *entry)
189 if rev == 0:
189 if rev == 0:
190 p = _pack(versionformat, version) + p[4:]
190 p = _pack(versionformat, version) + p[4:]
191 return p
191 return p
192
192
193 class revlog(object):
193 class revlog(object):
194 """
194 """
195 the underlying revision storage object
195 the underlying revision storage object
196
196
197 A revlog consists of two parts, an index and the revision data.
197 A revlog consists of two parts, an index and the revision data.
198
198
199 The index is a file with a fixed record size containing
199 The index is a file with a fixed record size containing
200 information on each revision, including its nodeid (hash), the
200 information on each revision, including its nodeid (hash), the
201 nodeids of its parents, the position and offset of its data within
201 nodeids of its parents, the position and offset of its data within
202 the data file, and the revision it's based on. Finally, each entry
202 the data file, and the revision it's based on. Finally, each entry
203 contains a linkrev entry that can serve as a pointer to external
203 contains a linkrev entry that can serve as a pointer to external
204 data.
204 data.
205
205
206 The revision data itself is a linear collection of data chunks.
206 The revision data itself is a linear collection of data chunks.
207 Each chunk represents a revision and is usually represented as a
207 Each chunk represents a revision and is usually represented as a
208 delta against the previous chunk. To bound lookup time, runs of
208 delta against the previous chunk. To bound lookup time, runs of
209 deltas are limited to about 2 times the length of the original
209 deltas are limited to about 2 times the length of the original
210 version data. This makes retrieval of a version proportional to
210 version data. This makes retrieval of a version proportional to
211 its size, or O(1) relative to the number of revisions.
211 its size, or O(1) relative to the number of revisions.
212
212
213 Both pieces of the revlog are written to in an append-only
213 Both pieces of the revlog are written to in an append-only
214 fashion, which means we never need to rewrite a file to insert or
214 fashion, which means we never need to rewrite a file to insert or
215 remove data, and can use some simple techniques to avoid the need
215 remove data, and can use some simple techniques to avoid the need
216 for locking while reading.
216 for locking while reading.
217
217
218 If checkambig, indexfile is opened with checkambig=True at
218 If checkambig, indexfile is opened with checkambig=True at
219 writing, to avoid file stat ambiguity.
219 writing, to avoid file stat ambiguity.
220 """
220 """
221 def __init__(self, opener, indexfile, checkambig=False):
221 def __init__(self, opener, indexfile, checkambig=False):
222 """
222 """
223 create a revlog object
223 create a revlog object
224
224
225 opener is a function that abstracts the file opening operation
225 opener is a function that abstracts the file opening operation
226 and can be used to implement COW semantics or the like.
226 and can be used to implement COW semantics or the like.
227 """
227 """
228 self.indexfile = indexfile
228 self.indexfile = indexfile
229 self.datafile = indexfile[:-2] + ".d"
229 self.datafile = indexfile[:-2] + ".d"
230 self.opener = opener
230 self.opener = opener
231 # When True, indexfile is opened with checkambig=True at writing, to
231 # When True, indexfile is opened with checkambig=True at writing, to
232 # avoid file stat ambiguity.
232 # avoid file stat ambiguity.
233 self._checkambig = checkambig
233 self._checkambig = checkambig
234 # 3-tuple of (node, rev, text) for a raw revision.
234 # 3-tuple of (node, rev, text) for a raw revision.
235 self._cache = None
235 self._cache = None
236 # Maps rev to chain base rev.
236 # Maps rev to chain base rev.
237 self._chainbasecache = util.lrucachedict(100)
237 self._chainbasecache = util.lrucachedict(100)
238 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
238 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
239 self._chunkcache = (0, '')
239 self._chunkcache = (0, '')
240 # How much data to read and cache into the raw revlog data cache.
240 # How much data to read and cache into the raw revlog data cache.
241 self._chunkcachesize = 65536
241 self._chunkcachesize = 65536
242 self._maxchainlen = None
242 self._maxchainlen = None
243 self._aggressivemergedeltas = False
243 self._aggressivemergedeltas = False
244 self.index = []
244 self.index = []
245 # Mapping of partial identifiers to full nodes.
245 # Mapping of partial identifiers to full nodes.
246 self._pcache = {}
246 self._pcache = {}
247 # Mapping of revision integer to full node.
247 # Mapping of revision integer to full node.
248 self._nodecache = {nullid: nullrev}
248 self._nodecache = {nullid: nullrev}
249 self._nodepos = None
249 self._nodepos = None
250
250
251 v = REVLOG_DEFAULT_VERSION
251 v = REVLOG_DEFAULT_VERSION
252 opts = getattr(opener, 'options', None)
252 opts = getattr(opener, 'options', None)
253 if opts is not None:
253 if opts is not None:
254 if 'revlogv1' in opts:
254 if 'revlogv1' in opts:
255 if 'generaldelta' in opts:
255 if 'generaldelta' in opts:
256 v |= REVLOGGENERALDELTA
256 v |= REVLOGGENERALDELTA
257 else:
257 else:
258 v = 0
258 v = 0
259 if 'chunkcachesize' in opts:
259 if 'chunkcachesize' in opts:
260 self._chunkcachesize = opts['chunkcachesize']
260 self._chunkcachesize = opts['chunkcachesize']
261 if 'maxchainlen' in opts:
261 if 'maxchainlen' in opts:
262 self._maxchainlen = opts['maxchainlen']
262 self._maxchainlen = opts['maxchainlen']
263 if 'aggressivemergedeltas' in opts:
263 if 'aggressivemergedeltas' in opts:
264 self._aggressivemergedeltas = opts['aggressivemergedeltas']
264 self._aggressivemergedeltas = opts['aggressivemergedeltas']
265 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
265 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
266
266
267 if self._chunkcachesize <= 0:
267 if self._chunkcachesize <= 0:
268 raise RevlogError(_('revlog chunk cache size %r is not greater '
268 raise RevlogError(_('revlog chunk cache size %r is not greater '
269 'than 0') % self._chunkcachesize)
269 'than 0') % self._chunkcachesize)
270 elif self._chunkcachesize & (self._chunkcachesize - 1):
270 elif self._chunkcachesize & (self._chunkcachesize - 1):
271 raise RevlogError(_('revlog chunk cache size %r is not a power '
271 raise RevlogError(_('revlog chunk cache size %r is not a power '
272 'of 2') % self._chunkcachesize)
272 'of 2') % self._chunkcachesize)
273
273
274 indexdata = ''
274 indexdata = ''
275 self._initempty = True
275 self._initempty = True
276 try:
276 try:
277 f = self.opener(self.indexfile)
277 f = self.opener(self.indexfile)
278 indexdata = f.read()
278 indexdata = f.read()
279 f.close()
279 f.close()
280 if len(indexdata) > 0:
280 if len(indexdata) > 0:
281 v = struct.unpack(versionformat, indexdata[:4])[0]
281 v = struct.unpack(versionformat, indexdata[:4])[0]
282 self._initempty = False
282 self._initempty = False
283 except IOError as inst:
283 except IOError as inst:
284 if inst.errno != errno.ENOENT:
284 if inst.errno != errno.ENOENT:
285 raise
285 raise
286
286
287 self.version = v
287 self.version = v
288 self._inline = v & REVLOGNGINLINEDATA
288 self._inline = v & REVLOGNGINLINEDATA
289 self._generaldelta = v & REVLOGGENERALDELTA
289 self._generaldelta = v & REVLOGGENERALDELTA
290 flags = v & ~0xFFFF
290 flags = v & ~0xFFFF
291 fmt = v & 0xFFFF
291 fmt = v & 0xFFFF
292 if fmt == REVLOGV0 and flags:
292 if fmt == REVLOGV0 and flags:
293 raise RevlogError(_("index %s unknown flags %#04x for format v0")
293 raise RevlogError(_("index %s unknown flags %#04x for format v0")
294 % (self.indexfile, flags >> 16))
294 % (self.indexfile, flags >> 16))
295 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
295 elif fmt == REVLOGNG and flags & ~REVLOGNG_FLAGS:
296 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
296 raise RevlogError(_("index %s unknown flags %#04x for revlogng")
297 % (self.indexfile, flags >> 16))
297 % (self.indexfile, flags >> 16))
298 elif fmt > REVLOGNG:
298 elif fmt > REVLOGNG:
299 raise RevlogError(_("index %s unknown format %d")
299 raise RevlogError(_("index %s unknown format %d")
300 % (self.indexfile, fmt))
300 % (self.indexfile, fmt))
301
301
302 self.storedeltachains = True
302 self.storedeltachains = True
303
303
304 self._io = revlogio()
304 self._io = revlogio()
305 if self.version == REVLOGV0:
305 if self.version == REVLOGV0:
306 self._io = revlogoldio()
306 self._io = revlogoldio()
307 try:
307 try:
308 d = self._io.parseindex(indexdata, self._inline)
308 d = self._io.parseindex(indexdata, self._inline)
309 except (ValueError, IndexError):
309 except (ValueError, IndexError):
310 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
310 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
311 self.index, nodemap, self._chunkcache = d
311 self.index, nodemap, self._chunkcache = d
312 if nodemap is not None:
312 if nodemap is not None:
313 self.nodemap = self._nodecache = nodemap
313 self.nodemap = self._nodecache = nodemap
314 if not self._chunkcache:
314 if not self._chunkcache:
315 self._chunkclear()
315 self._chunkclear()
316 # revnum -> (chain-length, sum-delta-length)
316 # revnum -> (chain-length, sum-delta-length)
317 self._chaininfocache = {}
317 self._chaininfocache = {}
318
318
319 def tip(self):
319 def tip(self):
320 return self.node(len(self.index) - 2)
320 return self.node(len(self.index) - 2)
321 def __contains__(self, rev):
321 def __contains__(self, rev):
322 return 0 <= rev < len(self)
322 return 0 <= rev < len(self)
323 def __len__(self):
323 def __len__(self):
324 return len(self.index) - 1
324 return len(self.index) - 1
325 def __iter__(self):
325 def __iter__(self):
326 return iter(xrange(len(self)))
326 return iter(xrange(len(self)))
327 def revs(self, start=0, stop=None):
327 def revs(self, start=0, stop=None):
328 """iterate over all rev in this revlog (from start to stop)"""
328 """iterate over all rev in this revlog (from start to stop)"""
329 step = 1
329 step = 1
330 if stop is not None:
330 if stop is not None:
331 if start > stop:
331 if start > stop:
332 step = -1
332 step = -1
333 stop += step
333 stop += step
334 else:
334 else:
335 stop = len(self)
335 stop = len(self)
336 return xrange(start, stop, step)
336 return xrange(start, stop, step)
337
337
338 @util.propertycache
338 @util.propertycache
339 def nodemap(self):
339 def nodemap(self):
340 self.rev(self.node(0))
340 self.rev(self.node(0))
341 return self._nodecache
341 return self._nodecache
342
342
343 def hasnode(self, node):
343 def hasnode(self, node):
344 try:
344 try:
345 self.rev(node)
345 self.rev(node)
346 return True
346 return True
347 except KeyError:
347 except KeyError:
348 return False
348 return False
349
349
350 def clearcaches(self):
350 def clearcaches(self):
351 self._cache = None
351 self._cache = None
352 self._chainbasecache.clear()
352 self._chainbasecache.clear()
353 self._chunkcache = (0, '')
353 self._chunkcache = (0, '')
354 self._pcache = {}
354 self._pcache = {}
355
355
356 try:
356 try:
357 self._nodecache.clearcaches()
357 self._nodecache.clearcaches()
358 except AttributeError:
358 except AttributeError:
359 self._nodecache = {nullid: nullrev}
359 self._nodecache = {nullid: nullrev}
360 self._nodepos = None
360 self._nodepos = None
361
361
362 def rev(self, node):
362 def rev(self, node):
363 try:
363 try:
364 return self._nodecache[node]
364 return self._nodecache[node]
365 except TypeError:
365 except TypeError:
366 raise
366 raise
367 except RevlogError:
367 except RevlogError:
368 # parsers.c radix tree lookup failed
368 # parsers.c radix tree lookup failed
369 raise LookupError(node, self.indexfile, _('no node'))
369 raise LookupError(node, self.indexfile, _('no node'))
370 except KeyError:
370 except KeyError:
371 # pure python cache lookup failed
371 # pure python cache lookup failed
372 n = self._nodecache
372 n = self._nodecache
373 i = self.index
373 i = self.index
374 p = self._nodepos
374 p = self._nodepos
375 if p is None:
375 if p is None:
376 p = len(i) - 2
376 p = len(i) - 2
377 for r in xrange(p, -1, -1):
377 for r in xrange(p, -1, -1):
378 v = i[r][7]
378 v = i[r][7]
379 n[v] = r
379 n[v] = r
380 if v == node:
380 if v == node:
381 self._nodepos = r - 1
381 self._nodepos = r - 1
382 return r
382 return r
383 raise LookupError(node, self.indexfile, _('no node'))
383 raise LookupError(node, self.indexfile, _('no node'))
384
384
385 # Accessors for index entries.
385 # Accessors for index entries.
386
386
387 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
387 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
388 # are flags.
388 # are flags.
389 def start(self, rev):
389 def start(self, rev):
390 return int(self.index[rev][0] >> 16)
390 return int(self.index[rev][0] >> 16)
391
391
392 def flags(self, rev):
392 def flags(self, rev):
393 return self.index[rev][0] & 0xFFFF
393 return self.index[rev][0] & 0xFFFF
394
394
395 def length(self, rev):
395 def length(self, rev):
396 return self.index[rev][1]
396 return self.index[rev][1]
397
397
398 def rawsize(self, rev):
398 def rawsize(self, rev):
399 """return the length of the uncompressed text for a given revision"""
399 """return the length of the uncompressed text for a given revision"""
400 l = self.index[rev][2]
400 l = self.index[rev][2]
401 if l >= 0:
401 if l >= 0:
402 return l
402 return l
403
403
404 t = self.revision(self.node(rev))
404 t = self.revision(self.node(rev))
405 return len(t)
405 return len(t)
406 size = rawsize
406 size = rawsize
407
407
408 def chainbase(self, rev):
408 def chainbase(self, rev):
409 base = self._chainbasecache.get(rev)
409 base = self._chainbasecache.get(rev)
410 if base is not None:
410 if base is not None:
411 return base
411 return base
412
412
413 index = self.index
413 index = self.index
414 base = index[rev][3]
414 base = index[rev][3]
415 while base != rev:
415 while base != rev:
416 rev = base
416 rev = base
417 base = index[rev][3]
417 base = index[rev][3]
418
418
419 self._chainbasecache[rev] = base
419 self._chainbasecache[rev] = base
420 return base
420 return base
421
421
422 def linkrev(self, rev):
422 def linkrev(self, rev):
423 return self.index[rev][4]
423 return self.index[rev][4]
424
424
425 def parentrevs(self, rev):
425 def parentrevs(self, rev):
426 return self.index[rev][5:7]
426 return self.index[rev][5:7]
427
427
428 def node(self, rev):
428 def node(self, rev):
429 return self.index[rev][7]
429 return self.index[rev][7]
430
430
431 # Derived from index values.
431 # Derived from index values.
432
432
433 def end(self, rev):
433 def end(self, rev):
434 return self.start(rev) + self.length(rev)
434 return self.start(rev) + self.length(rev)
435
435
436 def parents(self, node):
436 def parents(self, node):
437 i = self.index
437 i = self.index
438 d = i[self.rev(node)]
438 d = i[self.rev(node)]
439 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
439 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
440
440
441 def chainlen(self, rev):
441 def chainlen(self, rev):
442 return self._chaininfo(rev)[0]
442 return self._chaininfo(rev)[0]
443
443
444 def _chaininfo(self, rev):
444 def _chaininfo(self, rev):
445 chaininfocache = self._chaininfocache
445 chaininfocache = self._chaininfocache
446 if rev in chaininfocache:
446 if rev in chaininfocache:
447 return chaininfocache[rev]
447 return chaininfocache[rev]
448 index = self.index
448 index = self.index
449 generaldelta = self._generaldelta
449 generaldelta = self._generaldelta
450 iterrev = rev
450 iterrev = rev
451 e = index[iterrev]
451 e = index[iterrev]
452 clen = 0
452 clen = 0
453 compresseddeltalen = 0
453 compresseddeltalen = 0
454 while iterrev != e[3]:
454 while iterrev != e[3]:
455 clen += 1
455 clen += 1
456 compresseddeltalen += e[1]
456 compresseddeltalen += e[1]
457 if generaldelta:
457 if generaldelta:
458 iterrev = e[3]
458 iterrev = e[3]
459 else:
459 else:
460 iterrev -= 1
460 iterrev -= 1
461 if iterrev in chaininfocache:
461 if iterrev in chaininfocache:
462 t = chaininfocache[iterrev]
462 t = chaininfocache[iterrev]
463 clen += t[0]
463 clen += t[0]
464 compresseddeltalen += t[1]
464 compresseddeltalen += t[1]
465 break
465 break
466 e = index[iterrev]
466 e = index[iterrev]
467 else:
467 else:
468 # Add text length of base since decompressing that also takes
468 # Add text length of base since decompressing that also takes
469 # work. For cache hits the length is already included.
469 # work. For cache hits the length is already included.
470 compresseddeltalen += e[1]
470 compresseddeltalen += e[1]
471 r = (clen, compresseddeltalen)
471 r = (clen, compresseddeltalen)
472 chaininfocache[rev] = r
472 chaininfocache[rev] = r
473 return r
473 return r
474
474
475 def _deltachain(self, rev, stoprev=None):
475 def _deltachain(self, rev, stoprev=None):
476 """Obtain the delta chain for a revision.
476 """Obtain the delta chain for a revision.
477
477
478 ``stoprev`` specifies a revision to stop at. If not specified, we
478 ``stoprev`` specifies a revision to stop at. If not specified, we
479 stop at the base of the chain.
479 stop at the base of the chain.
480
480
481 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
481 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
482 revs in ascending order and ``stopped`` is a bool indicating whether
482 revs in ascending order and ``stopped`` is a bool indicating whether
483 ``stoprev`` was hit.
483 ``stoprev`` was hit.
484 """
484 """
485 chain = []
485 chain = []
486
486
487 # Alias to prevent attribute lookup in tight loop.
487 # Alias to prevent attribute lookup in tight loop.
488 index = self.index
488 index = self.index
489 generaldelta = self._generaldelta
489 generaldelta = self._generaldelta
490
490
491 iterrev = rev
491 iterrev = rev
492 e = index[iterrev]
492 e = index[iterrev]
493 while iterrev != e[3] and iterrev != stoprev:
493 while iterrev != e[3] and iterrev != stoprev:
494 chain.append(iterrev)
494 chain.append(iterrev)
495 if generaldelta:
495 if generaldelta:
496 iterrev = e[3]
496 iterrev = e[3]
497 else:
497 else:
498 iterrev -= 1
498 iterrev -= 1
499 e = index[iterrev]
499 e = index[iterrev]
500
500
501 if iterrev == stoprev:
501 if iterrev == stoprev:
502 stopped = True
502 stopped = True
503 else:
503 else:
504 chain.append(iterrev)
504 chain.append(iterrev)
505 stopped = False
505 stopped = False
506
506
507 chain.reverse()
507 chain.reverse()
508 return chain, stopped
508 return chain, stopped
509
509
510 def ancestors(self, revs, stoprev=0, inclusive=False):
510 def ancestors(self, revs, stoprev=0, inclusive=False):
511 """Generate the ancestors of 'revs' in reverse topological order.
511 """Generate the ancestors of 'revs' in reverse topological order.
512 Does not generate revs lower than stoprev.
512 Does not generate revs lower than stoprev.
513
513
514 See the documentation for ancestor.lazyancestors for more details."""
514 See the documentation for ancestor.lazyancestors for more details."""
515
515
516 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
516 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
517 inclusive=inclusive)
517 inclusive=inclusive)
518
518
519 def descendants(self, revs):
519 def descendants(self, revs):
520 """Generate the descendants of 'revs' in revision order.
520 """Generate the descendants of 'revs' in revision order.
521
521
522 Yield a sequence of revision numbers starting with a child of
522 Yield a sequence of revision numbers starting with a child of
523 some rev in revs, i.e., each revision is *not* considered a
523 some rev in revs, i.e., each revision is *not* considered a
524 descendant of itself. Results are ordered by revision number (a
524 descendant of itself. Results are ordered by revision number (a
525 topological sort)."""
525 topological sort)."""
526 first = min(revs)
526 first = min(revs)
527 if first == nullrev:
527 if first == nullrev:
528 for i in self:
528 for i in self:
529 yield i
529 yield i
530 return
530 return
531
531
532 seen = set(revs)
532 seen = set(revs)
533 for i in self.revs(start=first + 1):
533 for i in self.revs(start=first + 1):
534 for x in self.parentrevs(i):
534 for x in self.parentrevs(i):
535 if x != nullrev and x in seen:
535 if x != nullrev and x in seen:
536 seen.add(i)
536 seen.add(i)
537 yield i
537 yield i
538 break
538 break
539
539
540 def findcommonmissing(self, common=None, heads=None):
540 def findcommonmissing(self, common=None, heads=None):
541 """Return a tuple of the ancestors of common and the ancestors of heads
541 """Return a tuple of the ancestors of common and the ancestors of heads
542 that are not ancestors of common. In revset terminology, we return the
542 that are not ancestors of common. In revset terminology, we return the
543 tuple:
543 tuple:
544
544
545 ::common, (::heads) - (::common)
545 ::common, (::heads) - (::common)
546
546
547 The list is sorted by revision number, meaning it is
547 The list is sorted by revision number, meaning it is
548 topologically sorted.
548 topologically sorted.
549
549
550 'heads' and 'common' are both lists of node IDs. If heads is
550 'heads' and 'common' are both lists of node IDs. If heads is
551 not supplied, uses all of the revlog's heads. If common is not
551 not supplied, uses all of the revlog's heads. If common is not
552 supplied, uses nullid."""
552 supplied, uses nullid."""
553 if common is None:
553 if common is None:
554 common = [nullid]
554 common = [nullid]
555 if heads is None:
555 if heads is None:
556 heads = self.heads()
556 heads = self.heads()
557
557
558 common = [self.rev(n) for n in common]
558 common = [self.rev(n) for n in common]
559 heads = [self.rev(n) for n in heads]
559 heads = [self.rev(n) for n in heads]
560
560
561 # we want the ancestors, but inclusive
561 # we want the ancestors, but inclusive
562 class lazyset(object):
562 class lazyset(object):
563 def __init__(self, lazyvalues):
563 def __init__(self, lazyvalues):
564 self.addedvalues = set()
564 self.addedvalues = set()
565 self.lazyvalues = lazyvalues
565 self.lazyvalues = lazyvalues
566
566
567 def __contains__(self, value):
567 def __contains__(self, value):
568 return value in self.addedvalues or value in self.lazyvalues
568 return value in self.addedvalues or value in self.lazyvalues
569
569
570 def __iter__(self):
570 def __iter__(self):
571 added = self.addedvalues
571 added = self.addedvalues
572 for r in added:
572 for r in added:
573 yield r
573 yield r
574 for r in self.lazyvalues:
574 for r in self.lazyvalues:
575 if not r in added:
575 if not r in added:
576 yield r
576 yield r
577
577
578 def add(self, value):
578 def add(self, value):
579 self.addedvalues.add(value)
579 self.addedvalues.add(value)
580
580
581 def update(self, values):
581 def update(self, values):
582 self.addedvalues.update(values)
582 self.addedvalues.update(values)
583
583
584 has = lazyset(self.ancestors(common))
584 has = lazyset(self.ancestors(common))
585 has.add(nullrev)
585 has.add(nullrev)
586 has.update(common)
586 has.update(common)
587
587
588 # take all ancestors from heads that aren't in has
588 # take all ancestors from heads that aren't in has
589 missing = set()
589 missing = set()
590 visit = collections.deque(r for r in heads if r not in has)
590 visit = collections.deque(r for r in heads if r not in has)
591 while visit:
591 while visit:
592 r = visit.popleft()
592 r = visit.popleft()
593 if r in missing:
593 if r in missing:
594 continue
594 continue
595 else:
595 else:
596 missing.add(r)
596 missing.add(r)
597 for p in self.parentrevs(r):
597 for p in self.parentrevs(r):
598 if p not in has:
598 if p not in has:
599 visit.append(p)
599 visit.append(p)
600 missing = list(missing)
600 missing = list(missing)
601 missing.sort()
601 missing.sort()
602 return has, [self.node(miss) for miss in missing]
602 return has, [self.node(miss) for miss in missing]
603
603
604 def incrementalmissingrevs(self, common=None):
604 def incrementalmissingrevs(self, common=None):
605 """Return an object that can be used to incrementally compute the
605 """Return an object that can be used to incrementally compute the
606 revision numbers of the ancestors of arbitrary sets that are not
606 revision numbers of the ancestors of arbitrary sets that are not
607 ancestors of common. This is an ancestor.incrementalmissingancestors
607 ancestors of common. This is an ancestor.incrementalmissingancestors
608 object.
608 object.
609
609
610 'common' is a list of revision numbers. If common is not supplied, uses
610 'common' is a list of revision numbers. If common is not supplied, uses
611 nullrev.
611 nullrev.
612 """
612 """
613 if common is None:
613 if common is None:
614 common = [nullrev]
614 common = [nullrev]
615
615
616 return ancestor.incrementalmissingancestors(self.parentrevs, common)
616 return ancestor.incrementalmissingancestors(self.parentrevs, common)
617
617
618 def findmissingrevs(self, common=None, heads=None):
618 def findmissingrevs(self, common=None, heads=None):
619 """Return the revision numbers of the ancestors of heads that
619 """Return the revision numbers of the ancestors of heads that
620 are not ancestors of common.
620 are not ancestors of common.
621
621
622 More specifically, return a list of revision numbers corresponding to
622 More specifically, return a list of revision numbers corresponding to
623 nodes N such that every N satisfies the following constraints:
623 nodes N such that every N satisfies the following constraints:
624
624
625 1. N is an ancestor of some node in 'heads'
625 1. N is an ancestor of some node in 'heads'
626 2. N is not an ancestor of any node in 'common'
626 2. N is not an ancestor of any node in 'common'
627
627
628 The list is sorted by revision number, meaning it is
628 The list is sorted by revision number, meaning it is
629 topologically sorted.
629 topologically sorted.
630
630
631 'heads' and 'common' are both lists of revision numbers. If heads is
631 'heads' and 'common' are both lists of revision numbers. If heads is
632 not supplied, uses all of the revlog's heads. If common is not
632 not supplied, uses all of the revlog's heads. If common is not
633 supplied, uses nullid."""
633 supplied, uses nullid."""
634 if common is None:
634 if common is None:
635 common = [nullrev]
635 common = [nullrev]
636 if heads is None:
636 if heads is None:
637 heads = self.headrevs()
637 heads = self.headrevs()
638
638
639 inc = self.incrementalmissingrevs(common=common)
639 inc = self.incrementalmissingrevs(common=common)
640 return inc.missingancestors(heads)
640 return inc.missingancestors(heads)
641
641
642 def findmissing(self, common=None, heads=None):
642 def findmissing(self, common=None, heads=None):
643 """Return the ancestors of heads that are not ancestors of common.
643 """Return the ancestors of heads that are not ancestors of common.
644
644
645 More specifically, return a list of nodes N such that every N
645 More specifically, return a list of nodes N such that every N
646 satisfies the following constraints:
646 satisfies the following constraints:
647
647
648 1. N is an ancestor of some node in 'heads'
648 1. N is an ancestor of some node in 'heads'
649 2. N is not an ancestor of any node in 'common'
649 2. N is not an ancestor of any node in 'common'
650
650
651 The list is sorted by revision number, meaning it is
651 The list is sorted by revision number, meaning it is
652 topologically sorted.
652 topologically sorted.
653
653
654 'heads' and 'common' are both lists of node IDs. If heads is
654 'heads' and 'common' are both lists of node IDs. If heads is
655 not supplied, uses all of the revlog's heads. If common is not
655 not supplied, uses all of the revlog's heads. If common is not
656 supplied, uses nullid."""
656 supplied, uses nullid."""
657 if common is None:
657 if common is None:
658 common = [nullid]
658 common = [nullid]
659 if heads is None:
659 if heads is None:
660 heads = self.heads()
660 heads = self.heads()
661
661
662 common = [self.rev(n) for n in common]
662 common = [self.rev(n) for n in common]
663 heads = [self.rev(n) for n in heads]
663 heads = [self.rev(n) for n in heads]
664
664
665 inc = self.incrementalmissingrevs(common=common)
665 inc = self.incrementalmissingrevs(common=common)
666 return [self.node(r) for r in inc.missingancestors(heads)]
666 return [self.node(r) for r in inc.missingancestors(heads)]
667
667
668 def nodesbetween(self, roots=None, heads=None):
668 def nodesbetween(self, roots=None, heads=None):
669 """Return a topological path from 'roots' to 'heads'.
669 """Return a topological path from 'roots' to 'heads'.
670
670
671 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
671 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
672 topologically sorted list of all nodes N that satisfy both of
672 topologically sorted list of all nodes N that satisfy both of
673 these constraints:
673 these constraints:
674
674
675 1. N is a descendant of some node in 'roots'
675 1. N is a descendant of some node in 'roots'
676 2. N is an ancestor of some node in 'heads'
676 2. N is an ancestor of some node in 'heads'
677
677
678 Every node is considered to be both a descendant and an ancestor
678 Every node is considered to be both a descendant and an ancestor
679 of itself, so every reachable node in 'roots' and 'heads' will be
679 of itself, so every reachable node in 'roots' and 'heads' will be
680 included in 'nodes'.
680 included in 'nodes'.
681
681
682 'outroots' is the list of reachable nodes in 'roots', i.e., the
682 'outroots' is the list of reachable nodes in 'roots', i.e., the
683 subset of 'roots' that is returned in 'nodes'. Likewise,
683 subset of 'roots' that is returned in 'nodes'. Likewise,
684 'outheads' is the subset of 'heads' that is also in 'nodes'.
684 'outheads' is the subset of 'heads' that is also in 'nodes'.
685
685
686 'roots' and 'heads' are both lists of node IDs. If 'roots' is
686 'roots' and 'heads' are both lists of node IDs. If 'roots' is
687 unspecified, uses nullid as the only root. If 'heads' is
687 unspecified, uses nullid as the only root. If 'heads' is
688 unspecified, uses list of all of the revlog's heads."""
688 unspecified, uses list of all of the revlog's heads."""
689 nonodes = ([], [], [])
689 nonodes = ([], [], [])
690 if roots is not None:
690 if roots is not None:
691 roots = list(roots)
691 roots = list(roots)
692 if not roots:
692 if not roots:
693 return nonodes
693 return nonodes
694 lowestrev = min([self.rev(n) for n in roots])
694 lowestrev = min([self.rev(n) for n in roots])
695 else:
695 else:
696 roots = [nullid] # Everybody's a descendant of nullid
696 roots = [nullid] # Everybody's a descendant of nullid
697 lowestrev = nullrev
697 lowestrev = nullrev
698 if (lowestrev == nullrev) and (heads is None):
698 if (lowestrev == nullrev) and (heads is None):
699 # We want _all_ the nodes!
699 # We want _all_ the nodes!
700 return ([self.node(r) for r in self], [nullid], list(self.heads()))
700 return ([self.node(r) for r in self], [nullid], list(self.heads()))
701 if heads is None:
701 if heads is None:
702 # All nodes are ancestors, so the latest ancestor is the last
702 # All nodes are ancestors, so the latest ancestor is the last
703 # node.
703 # node.
704 highestrev = len(self) - 1
704 highestrev = len(self) - 1
705 # Set ancestors to None to signal that every node is an ancestor.
705 # Set ancestors to None to signal that every node is an ancestor.
706 ancestors = None
706 ancestors = None
707 # Set heads to an empty dictionary for later discovery of heads
707 # Set heads to an empty dictionary for later discovery of heads
708 heads = {}
708 heads = {}
709 else:
709 else:
710 heads = list(heads)
710 heads = list(heads)
711 if not heads:
711 if not heads:
712 return nonodes
712 return nonodes
713 ancestors = set()
713 ancestors = set()
714 # Turn heads into a dictionary so we can remove 'fake' heads.
714 # Turn heads into a dictionary so we can remove 'fake' heads.
715 # Also, later we will be using it to filter out the heads we can't
715 # Also, later we will be using it to filter out the heads we can't
716 # find from roots.
716 # find from roots.
717 heads = dict.fromkeys(heads, False)
717 heads = dict.fromkeys(heads, False)
718 # Start at the top and keep marking parents until we're done.
718 # Start at the top and keep marking parents until we're done.
719 nodestotag = set(heads)
719 nodestotag = set(heads)
720 # Remember where the top was so we can use it as a limit later.
720 # Remember where the top was so we can use it as a limit later.
721 highestrev = max([self.rev(n) for n in nodestotag])
721 highestrev = max([self.rev(n) for n in nodestotag])
722 while nodestotag:
722 while nodestotag:
723 # grab a node to tag
723 # grab a node to tag
724 n = nodestotag.pop()
724 n = nodestotag.pop()
725 # Never tag nullid
725 # Never tag nullid
726 if n == nullid:
726 if n == nullid:
727 continue
727 continue
728 # A node's revision number represents its place in a
728 # A node's revision number represents its place in a
729 # topologically sorted list of nodes.
729 # topologically sorted list of nodes.
730 r = self.rev(n)
730 r = self.rev(n)
731 if r >= lowestrev:
731 if r >= lowestrev:
732 if n not in ancestors:
732 if n not in ancestors:
733 # If we are possibly a descendant of one of the roots
733 # If we are possibly a descendant of one of the roots
734 # and we haven't already been marked as an ancestor
734 # and we haven't already been marked as an ancestor
735 ancestors.add(n) # Mark as ancestor
735 ancestors.add(n) # Mark as ancestor
736 # Add non-nullid parents to list of nodes to tag.
736 # Add non-nullid parents to list of nodes to tag.
737 nodestotag.update([p for p in self.parents(n) if
737 nodestotag.update([p for p in self.parents(n) if
738 p != nullid])
738 p != nullid])
739 elif n in heads: # We've seen it before, is it a fake head?
739 elif n in heads: # We've seen it before, is it a fake head?
740 # So it is, real heads should not be the ancestors of
740 # So it is, real heads should not be the ancestors of
741 # any other heads.
741 # any other heads.
742 heads.pop(n)
742 heads.pop(n)
743 if not ancestors:
743 if not ancestors:
744 return nonodes
744 return nonodes
745 # Now that we have our set of ancestors, we want to remove any
745 # Now that we have our set of ancestors, we want to remove any
746 # roots that are not ancestors.
746 # roots that are not ancestors.
747
747
748 # If one of the roots was nullid, everything is included anyway.
748 # If one of the roots was nullid, everything is included anyway.
749 if lowestrev > nullrev:
749 if lowestrev > nullrev:
750 # But, since we weren't, let's recompute the lowest rev to not
750 # But, since we weren't, let's recompute the lowest rev to not
751 # include roots that aren't ancestors.
751 # include roots that aren't ancestors.
752
752
753 # Filter out roots that aren't ancestors of heads
753 # Filter out roots that aren't ancestors of heads
754 roots = [root for root in roots if root in ancestors]
754 roots = [root for root in roots if root in ancestors]
755 # Recompute the lowest revision
755 # Recompute the lowest revision
756 if roots:
756 if roots:
757 lowestrev = min([self.rev(root) for root in roots])
757 lowestrev = min([self.rev(root) for root in roots])
758 else:
758 else:
759 # No more roots? Return empty list
759 # No more roots? Return empty list
760 return nonodes
760 return nonodes
761 else:
761 else:
762 # We are descending from nullid, and don't need to care about
762 # We are descending from nullid, and don't need to care about
763 # any other roots.
763 # any other roots.
764 lowestrev = nullrev
764 lowestrev = nullrev
765 roots = [nullid]
765 roots = [nullid]
766 # Transform our roots list into a set.
766 # Transform our roots list into a set.
767 descendants = set(roots)
767 descendants = set(roots)
768 # Also, keep the original roots so we can filter out roots that aren't
768 # Also, keep the original roots so we can filter out roots that aren't
769 # 'real' roots (i.e. are descended from other roots).
769 # 'real' roots (i.e. are descended from other roots).
770 roots = descendants.copy()
770 roots = descendants.copy()
771 # Our topologically sorted list of output nodes.
771 # Our topologically sorted list of output nodes.
772 orderedout = []
772 orderedout = []
773 # Don't start at nullid since we don't want nullid in our output list,
773 # Don't start at nullid since we don't want nullid in our output list,
774 # and if nullid shows up in descendants, empty parents will look like
774 # and if nullid shows up in descendants, empty parents will look like
775 # they're descendants.
775 # they're descendants.
776 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
776 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
777 n = self.node(r)
777 n = self.node(r)
778 isdescendant = False
778 isdescendant = False
779 if lowestrev == nullrev: # Everybody is a descendant of nullid
779 if lowestrev == nullrev: # Everybody is a descendant of nullid
780 isdescendant = True
780 isdescendant = True
781 elif n in descendants:
781 elif n in descendants:
782 # n is already a descendant
782 # n is already a descendant
783 isdescendant = True
783 isdescendant = True
784 # This check only needs to be done here because all the roots
784 # This check only needs to be done here because all the roots
785 # will start being marked is descendants before the loop.
785 # will start being marked is descendants before the loop.
786 if n in roots:
786 if n in roots:
787 # If n was a root, check if it's a 'real' root.
787 # If n was a root, check if it's a 'real' root.
788 p = tuple(self.parents(n))
788 p = tuple(self.parents(n))
789 # If any of its parents are descendants, it's not a root.
789 # If any of its parents are descendants, it's not a root.
790 if (p[0] in descendants) or (p[1] in descendants):
790 if (p[0] in descendants) or (p[1] in descendants):
791 roots.remove(n)
791 roots.remove(n)
792 else:
792 else:
793 p = tuple(self.parents(n))
793 p = tuple(self.parents(n))
794 # A node is a descendant if either of its parents are
794 # A node is a descendant if either of its parents are
795 # descendants. (We seeded the dependents list with the roots
795 # descendants. (We seeded the dependents list with the roots
796 # up there, remember?)
796 # up there, remember?)
797 if (p[0] in descendants) or (p[1] in descendants):
797 if (p[0] in descendants) or (p[1] in descendants):
798 descendants.add(n)
798 descendants.add(n)
799 isdescendant = True
799 isdescendant = True
800 if isdescendant and ((ancestors is None) or (n in ancestors)):
800 if isdescendant and ((ancestors is None) or (n in ancestors)):
801 # Only include nodes that are both descendants and ancestors.
801 # Only include nodes that are both descendants and ancestors.
802 orderedout.append(n)
802 orderedout.append(n)
803 if (ancestors is not None) and (n in heads):
803 if (ancestors is not None) and (n in heads):
804 # We're trying to figure out which heads are reachable
804 # We're trying to figure out which heads are reachable
805 # from roots.
805 # from roots.
806 # Mark this head as having been reached
806 # Mark this head as having been reached
807 heads[n] = True
807 heads[n] = True
808 elif ancestors is None:
808 elif ancestors is None:
809 # Otherwise, we're trying to discover the heads.
809 # Otherwise, we're trying to discover the heads.
810 # Assume this is a head because if it isn't, the next step
810 # Assume this is a head because if it isn't, the next step
811 # will eventually remove it.
811 # will eventually remove it.
812 heads[n] = True
812 heads[n] = True
813 # But, obviously its parents aren't.
813 # But, obviously its parents aren't.
814 for p in self.parents(n):
814 for p in self.parents(n):
815 heads.pop(p, None)
815 heads.pop(p, None)
816 heads = [head for head, flag in heads.iteritems() if flag]
816 heads = [head for head, flag in heads.iteritems() if flag]
817 roots = list(roots)
817 roots = list(roots)
818 assert orderedout
818 assert orderedout
819 assert roots
819 assert roots
820 assert heads
820 assert heads
821 return (orderedout, roots, heads)
821 return (orderedout, roots, heads)
822
822
823 def headrevs(self):
823 def headrevs(self):
824 try:
824 try:
825 return self.index.headrevs()
825 return self.index.headrevs()
826 except AttributeError:
826 except AttributeError:
827 return self._headrevs()
827 return self._headrevs()
828
828
829 def computephases(self, roots):
829 def computephases(self, roots):
830 return self.index.computephasesmapsets(roots)
830 return self.index.computephasesmapsets(roots)
831
831
832 def _headrevs(self):
832 def _headrevs(self):
833 count = len(self)
833 count = len(self)
834 if not count:
834 if not count:
835 return [nullrev]
835 return [nullrev]
836 # we won't iter over filtered rev so nobody is a head at start
836 # we won't iter over filtered rev so nobody is a head at start
837 ishead = [0] * (count + 1)
837 ishead = [0] * (count + 1)
838 index = self.index
838 index = self.index
839 for r in self:
839 for r in self:
840 ishead[r] = 1 # I may be an head
840 ishead[r] = 1 # I may be an head
841 e = index[r]
841 e = index[r]
842 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
842 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
843 return [r for r, val in enumerate(ishead) if val]
843 return [r for r, val in enumerate(ishead) if val]
844
844
845 def heads(self, start=None, stop=None):
845 def heads(self, start=None, stop=None):
846 """return the list of all nodes that have no children
846 """return the list of all nodes that have no children
847
847
848 if start is specified, only heads that are descendants of
848 if start is specified, only heads that are descendants of
849 start will be returned
849 start will be returned
850 if stop is specified, it will consider all the revs from stop
850 if stop is specified, it will consider all the revs from stop
851 as if they had no children
851 as if they had no children
852 """
852 """
853 if start is None and stop is None:
853 if start is None and stop is None:
854 if not len(self):
854 if not len(self):
855 return [nullid]
855 return [nullid]
856 return [self.node(r) for r in self.headrevs()]
856 return [self.node(r) for r in self.headrevs()]
857
857
858 if start is None:
858 if start is None:
859 start = nullid
859 start = nullid
860 if stop is None:
860 if stop is None:
861 stop = []
861 stop = []
862 stoprevs = set([self.rev(n) for n in stop])
862 stoprevs = set([self.rev(n) for n in stop])
863 startrev = self.rev(start)
863 startrev = self.rev(start)
864 reachable = set((startrev,))
864 reachable = set((startrev,))
865 heads = set((startrev,))
865 heads = set((startrev,))
866
866
867 parentrevs = self.parentrevs
867 parentrevs = self.parentrevs
868 for r in self.revs(start=startrev + 1):
868 for r in self.revs(start=startrev + 1):
869 for p in parentrevs(r):
869 for p in parentrevs(r):
870 if p in reachable:
870 if p in reachable:
871 if r not in stoprevs:
871 if r not in stoprevs:
872 reachable.add(r)
872 reachable.add(r)
873 heads.add(r)
873 heads.add(r)
874 if p in heads and p not in stoprevs:
874 if p in heads and p not in stoprevs:
875 heads.remove(p)
875 heads.remove(p)
876
876
877 return [self.node(r) for r in heads]
877 return [self.node(r) for r in heads]
878
878
879 def children(self, node):
879 def children(self, node):
880 """find the children of a given node"""
880 """find the children of a given node"""
881 c = []
881 c = []
882 p = self.rev(node)
882 p = self.rev(node)
883 for r in self.revs(start=p + 1):
883 for r in self.revs(start=p + 1):
884 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
884 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
885 if prevs:
885 if prevs:
886 for pr in prevs:
886 for pr in prevs:
887 if pr == p:
887 if pr == p:
888 c.append(self.node(r))
888 c.append(self.node(r))
889 elif p == nullrev:
889 elif p == nullrev:
890 c.append(self.node(r))
890 c.append(self.node(r))
891 return c
891 return c
892
892
893 def descendant(self, start, end):
893 def descendant(self, start, end):
894 if start == nullrev:
894 if start == nullrev:
895 return True
895 return True
896 for i in self.descendants([start]):
896 for i in self.descendants([start]):
897 if i == end:
897 if i == end:
898 return True
898 return True
899 elif i > end:
899 elif i > end:
900 break
900 break
901 return False
901 return False
902
902
903 def commonancestorsheads(self, a, b):
903 def commonancestorsheads(self, a, b):
904 """calculate all the heads of the common ancestors of nodes a and b"""
904 """calculate all the heads of the common ancestors of nodes a and b"""
905 a, b = self.rev(a), self.rev(b)
905 a, b = self.rev(a), self.rev(b)
906 try:
906 try:
907 ancs = self.index.commonancestorsheads(a, b)
907 ancs = self.index.commonancestorsheads(a, b)
908 except (AttributeError, OverflowError): # C implementation failed
908 except (AttributeError, OverflowError): # C implementation failed
909 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
909 ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
910 return map(self.node, ancs)
910 return map(self.node, ancs)
911
911
912 def isancestor(self, a, b):
912 def isancestor(self, a, b):
913 """return True if node a is an ancestor of node b
913 """return True if node a is an ancestor of node b
914
914
915 The implementation of this is trivial but the use of
915 The implementation of this is trivial but the use of
916 commonancestorsheads is not."""
916 commonancestorsheads is not."""
917 return a in self.commonancestorsheads(a, b)
917 return a in self.commonancestorsheads(a, b)
918
918
919 def ancestor(self, a, b):
919 def ancestor(self, a, b):
920 """calculate the "best" common ancestor of nodes a and b"""
920 """calculate the "best" common ancestor of nodes a and b"""
921
921
922 a, b = self.rev(a), self.rev(b)
922 a, b = self.rev(a), self.rev(b)
923 try:
923 try:
924 ancs = self.index.ancestors(a, b)
924 ancs = self.index.ancestors(a, b)
925 except (AttributeError, OverflowError):
925 except (AttributeError, OverflowError):
926 ancs = ancestor.ancestors(self.parentrevs, a, b)
926 ancs = ancestor.ancestors(self.parentrevs, a, b)
927 if ancs:
927 if ancs:
928 # choose a consistent winner when there's a tie
928 # choose a consistent winner when there's a tie
929 return min(map(self.node, ancs))
929 return min(map(self.node, ancs))
930 return nullid
930 return nullid
931
931
932 def _match(self, id):
932 def _match(self, id):
933 if isinstance(id, int):
933 if isinstance(id, int):
934 # rev
934 # rev
935 return self.node(id)
935 return self.node(id)
936 if len(id) == 20:
936 if len(id) == 20:
937 # possibly a binary node
937 # possibly a binary node
938 # odds of a binary node being all hex in ASCII are 1 in 10**25
938 # odds of a binary node being all hex in ASCII are 1 in 10**25
939 try:
939 try:
940 node = id
940 node = id
941 self.rev(node) # quick search the index
941 self.rev(node) # quick search the index
942 return node
942 return node
943 except LookupError:
943 except LookupError:
944 pass # may be partial hex id
944 pass # may be partial hex id
945 try:
945 try:
946 # str(rev)
946 # str(rev)
947 rev = int(id)
947 rev = int(id)
948 if str(rev) != id:
948 if str(rev) != id:
949 raise ValueError
949 raise ValueError
950 if rev < 0:
950 if rev < 0:
951 rev = len(self) + rev
951 rev = len(self) + rev
952 if rev < 0 or rev >= len(self):
952 if rev < 0 or rev >= len(self):
953 raise ValueError
953 raise ValueError
954 return self.node(rev)
954 return self.node(rev)
955 except (ValueError, OverflowError):
955 except (ValueError, OverflowError):
956 pass
956 pass
957 if len(id) == 40:
957 if len(id) == 40:
958 try:
958 try:
959 # a full hex nodeid?
959 # a full hex nodeid?
960 node = bin(id)
960 node = bin(id)
961 self.rev(node)
961 self.rev(node)
962 return node
962 return node
963 except (TypeError, LookupError):
963 except (TypeError, LookupError):
964 pass
964 pass
965
965
966 def _partialmatch(self, id):
966 def _partialmatch(self, id):
967 try:
967 try:
968 partial = self.index.partialmatch(id)
968 partial = self.index.partialmatch(id)
969 if partial and self.hasnode(partial):
969 if partial and self.hasnode(partial):
970 return partial
970 return partial
971 return None
971 return None
972 except RevlogError:
972 except RevlogError:
973 # parsers.c radix tree lookup gave multiple matches
973 # parsers.c radix tree lookup gave multiple matches
974 # fast path: for unfiltered changelog, radix tree is accurate
974 # fast path: for unfiltered changelog, radix tree is accurate
975 if not getattr(self, 'filteredrevs', None):
975 if not getattr(self, 'filteredrevs', None):
976 raise LookupError(id, self.indexfile,
976 raise LookupError(id, self.indexfile,
977 _('ambiguous identifier'))
977 _('ambiguous identifier'))
978 # fall through to slow path that filters hidden revisions
978 # fall through to slow path that filters hidden revisions
979 except (AttributeError, ValueError):
979 except (AttributeError, ValueError):
980 # we are pure python, or key was too short to search radix tree
980 # we are pure python, or key was too short to search radix tree
981 pass
981 pass
982
982
983 if id in self._pcache:
983 if id in self._pcache:
984 return self._pcache[id]
984 return self._pcache[id]
985
985
986 if len(id) < 40:
986 if len(id) < 40:
987 try:
987 try:
988 # hex(node)[:...]
988 # hex(node)[:...]
989 l = len(id) // 2 # grab an even number of digits
989 l = len(id) // 2 # grab an even number of digits
990 prefix = bin(id[:l * 2])
990 prefix = bin(id[:l * 2])
991 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
991 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
992 nl = [n for n in nl if hex(n).startswith(id) and
992 nl = [n for n in nl if hex(n).startswith(id) and
993 self.hasnode(n)]
993 self.hasnode(n)]
994 if len(nl) > 0:
994 if len(nl) > 0:
995 if len(nl) == 1:
995 if len(nl) == 1:
996 self._pcache[id] = nl[0]
996 self._pcache[id] = nl[0]
997 return nl[0]
997 return nl[0]
998 raise LookupError(id, self.indexfile,
998 raise LookupError(id, self.indexfile,
999 _('ambiguous identifier'))
999 _('ambiguous identifier'))
1000 return None
1000 return None
1001 except TypeError:
1001 except TypeError:
1002 pass
1002 pass
1003
1003
1004 def lookup(self, id):
1004 def lookup(self, id):
1005 """locate a node based on:
1005 """locate a node based on:
1006 - revision number or str(revision number)
1006 - revision number or str(revision number)
1007 - nodeid or subset of hex nodeid
1007 - nodeid or subset of hex nodeid
1008 """
1008 """
1009 n = self._match(id)
1009 n = self._match(id)
1010 if n is not None:
1010 if n is not None:
1011 return n
1011 return n
1012 n = self._partialmatch(id)
1012 n = self._partialmatch(id)
1013 if n:
1013 if n:
1014 return n
1014 return n
1015
1015
1016 raise LookupError(id, self.indexfile, _('no match found'))
1016 raise LookupError(id, self.indexfile, _('no match found'))
1017
1017
1018 def cmp(self, node, text):
1018 def cmp(self, node, text):
1019 """compare text with a given file revision
1019 """compare text with a given file revision
1020
1020
1021 returns True if text is different than what is stored.
1021 returns True if text is different than what is stored.
1022 """
1022 """
1023 p1, p2 = self.parents(node)
1023 p1, p2 = self.parents(node)
1024 return hash(text, p1, p2) != node
1024 return hash(text, p1, p2) != node
1025
1025
1026 def _addchunk(self, offset, data):
1026 def _addchunk(self, offset, data):
1027 """Add a segment to the revlog cache.
1027 """Add a segment to the revlog cache.
1028
1028
1029 Accepts an absolute offset and the data that is at that location.
1029 Accepts an absolute offset and the data that is at that location.
1030 """
1030 """
1031 o, d = self._chunkcache
1031 o, d = self._chunkcache
1032 # try to add to existing cache
1032 # try to add to existing cache
1033 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1033 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1034 self._chunkcache = o, d + data
1034 self._chunkcache = o, d + data
1035 else:
1035 else:
1036 self._chunkcache = offset, data
1036 self._chunkcache = offset, data
1037
1037
1038 def _loadchunk(self, offset, length, df=None):
1038 def _loadchunk(self, offset, length, df=None):
1039 """Load a segment of raw data from the revlog.
1039 """Load a segment of raw data from the revlog.
1040
1040
1041 Accepts an absolute offset, length to read, and an optional existing
1041 Accepts an absolute offset, length to read, and an optional existing
1042 file handle to read from.
1042 file handle to read from.
1043
1043
1044 If an existing file handle is passed, it will be seeked and the
1044 If an existing file handle is passed, it will be seeked and the
1045 original seek position will NOT be restored.
1045 original seek position will NOT be restored.
1046
1046
1047 Returns a str or buffer of raw byte data.
1047 Returns a str or buffer of raw byte data.
1048 """
1048 """
1049 if df is not None:
1049 if df is not None:
1050 closehandle = False
1050 closehandle = False
1051 else:
1051 else:
1052 if self._inline:
1052 if self._inline:
1053 df = self.opener(self.indexfile)
1053 df = self.opener(self.indexfile)
1054 else:
1054 else:
1055 df = self.opener(self.datafile)
1055 df = self.opener(self.datafile)
1056 closehandle = True
1056 closehandle = True
1057
1057
1058 # Cache data both forward and backward around the requested
1058 # Cache data both forward and backward around the requested
1059 # data, in a fixed size window. This helps speed up operations
1059 # data, in a fixed size window. This helps speed up operations
1060 # involving reading the revlog backwards.
1060 # involving reading the revlog backwards.
1061 cachesize = self._chunkcachesize
1061 cachesize = self._chunkcachesize
1062 realoffset = offset & ~(cachesize - 1)
1062 realoffset = offset & ~(cachesize - 1)
1063 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1063 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1064 - realoffset)
1064 - realoffset)
1065 df.seek(realoffset)
1065 df.seek(realoffset)
1066 d = df.read(reallength)
1066 d = df.read(reallength)
1067 if closehandle:
1067 if closehandle:
1068 df.close()
1068 df.close()
1069 self._addchunk(realoffset, d)
1069 self._addchunk(realoffset, d)
1070 if offset != realoffset or reallength != length:
1070 if offset != realoffset or reallength != length:
1071 return util.buffer(d, offset - realoffset, length)
1071 return util.buffer(d, offset - realoffset, length)
1072 return d
1072 return d
1073
1073
1074 def _getchunk(self, offset, length, df=None):
1074 def _getchunk(self, offset, length, df=None):
1075 """Obtain a segment of raw data from the revlog.
1075 """Obtain a segment of raw data from the revlog.
1076
1076
1077 Accepts an absolute offset, length of bytes to obtain, and an
1077 Accepts an absolute offset, length of bytes to obtain, and an
1078 optional file handle to the already-opened revlog. If the file
1078 optional file handle to the already-opened revlog. If the file
1079 handle is used, it's original seek position will not be preserved.
1079 handle is used, it's original seek position will not be preserved.
1080
1080
1081 Requests for data may be returned from a cache.
1081 Requests for data may be returned from a cache.
1082
1082
1083 Returns a str or a buffer instance of raw byte data.
1083 Returns a str or a buffer instance of raw byte data.
1084 """
1084 """
1085 o, d = self._chunkcache
1085 o, d = self._chunkcache
1086 l = len(d)
1086 l = len(d)
1087
1087
1088 # is it in the cache?
1088 # is it in the cache?
1089 cachestart = offset - o
1089 cachestart = offset - o
1090 cacheend = cachestart + length
1090 cacheend = cachestart + length
1091 if cachestart >= 0 and cacheend <= l:
1091 if cachestart >= 0 and cacheend <= l:
1092 if cachestart == 0 and cacheend == l:
1092 if cachestart == 0 and cacheend == l:
1093 return d # avoid a copy
1093 return d # avoid a copy
1094 return util.buffer(d, cachestart, cacheend - cachestart)
1094 return util.buffer(d, cachestart, cacheend - cachestart)
1095
1095
1096 return self._loadchunk(offset, length, df=df)
1096 return self._loadchunk(offset, length, df=df)
1097
1097
1098 def _chunkraw(self, startrev, endrev, df=None):
1098 def _chunkraw(self, startrev, endrev, df=None):
1099 """Obtain a segment of raw data corresponding to a range of revisions.
1099 """Obtain a segment of raw data corresponding to a range of revisions.
1100
1100
1101 Accepts the start and end revisions and an optional already-open
1101 Accepts the start and end revisions and an optional already-open
1102 file handle to be used for reading. If the file handle is read, its
1102 file handle to be used for reading. If the file handle is read, its
1103 seek position will not be preserved.
1103 seek position will not be preserved.
1104
1104
1105 Requests for data may be satisfied by a cache.
1105 Requests for data may be satisfied by a cache.
1106
1106
1107 Returns a 2-tuple of (offset, data) for the requested range of
1107 Returns a 2-tuple of (offset, data) for the requested range of
1108 revisions. Offset is the integer offset from the beginning of the
1108 revisions. Offset is the integer offset from the beginning of the
1109 revlog and data is a str or buffer of the raw byte data.
1109 revlog and data is a str or buffer of the raw byte data.
1110
1110
1111 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1111 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1112 to determine where each revision's data begins and ends.
1112 to determine where each revision's data begins and ends.
1113 """
1113 """
1114 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1114 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1115 # (functions are expensive).
1115 # (functions are expensive).
1116 index = self.index
1116 index = self.index
1117 istart = index[startrev]
1117 istart = index[startrev]
1118 start = int(istart[0] >> 16)
1118 start = int(istart[0] >> 16)
1119 if startrev == endrev:
1119 if startrev == endrev:
1120 end = start + istart[1]
1120 end = start + istart[1]
1121 else:
1121 else:
1122 iend = index[endrev]
1122 iend = index[endrev]
1123 end = int(iend[0] >> 16) + iend[1]
1123 end = int(iend[0] >> 16) + iend[1]
1124
1124
1125 if self._inline:
1125 if self._inline:
1126 start += (startrev + 1) * self._io.size
1126 start += (startrev + 1) * self._io.size
1127 end += (endrev + 1) * self._io.size
1127 end += (endrev + 1) * self._io.size
1128 length = end - start
1128 length = end - start
1129
1129
1130 return start, self._getchunk(start, length, df=df)
1130 return start, self._getchunk(start, length, df=df)
1131
1131
1132 def _chunk(self, rev, df=None):
1132 def _chunk(self, rev, df=None):
1133 """Obtain a single decompressed chunk for a revision.
1133 """Obtain a single decompressed chunk for a revision.
1134
1134
1135 Accepts an integer revision and an optional already-open file handle
1135 Accepts an integer revision and an optional already-open file handle
1136 to be used for reading. If used, the seek position of the file will not
1136 to be used for reading. If used, the seek position of the file will not
1137 be preserved.
1137 be preserved.
1138
1138
1139 Returns a str holding uncompressed data for the requested revision.
1139 Returns a str holding uncompressed data for the requested revision.
1140 """
1140 """
1141 return decompress(self._chunkraw(rev, rev, df=df)[1])
1141 return decompress(self._chunkraw(rev, rev, df=df)[1])
1142
1142
1143 def _chunks(self, revs, df=None):
1143 def _chunks(self, revs, df=None):
1144 """Obtain decompressed chunks for the specified revisions.
1144 """Obtain decompressed chunks for the specified revisions.
1145
1145
1146 Accepts an iterable of numeric revisions that are assumed to be in
1146 Accepts an iterable of numeric revisions that are assumed to be in
1147 ascending order. Also accepts an optional already-open file handle
1147 ascending order. Also accepts an optional already-open file handle
1148 to be used for reading. If used, the seek position of the file will
1148 to be used for reading. If used, the seek position of the file will
1149 not be preserved.
1149 not be preserved.
1150
1150
1151 This function is similar to calling ``self._chunk()`` multiple times,
1151 This function is similar to calling ``self._chunk()`` multiple times,
1152 but is faster.
1152 but is faster.
1153
1153
1154 Returns a list with decompressed data for each requested revision.
1154 Returns a list with decompressed data for each requested revision.
1155 """
1155 """
1156 if not revs:
1156 if not revs:
1157 return []
1157 return []
1158 start = self.start
1158 start = self.start
1159 length = self.length
1159 length = self.length
1160 inline = self._inline
1160 inline = self._inline
1161 iosize = self._io.size
1161 iosize = self._io.size
1162 buffer = util.buffer
1162 buffer = util.buffer
1163
1163
1164 l = []
1164 l = []
1165 ladd = l.append
1165 ladd = l.append
1166
1166
1167 try:
1167 try:
1168 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1168 offset, data = self._chunkraw(revs[0], revs[-1], df=df)
1169 except OverflowError:
1169 except OverflowError:
1170 # issue4215 - we can't cache a run of chunks greater than
1170 # issue4215 - we can't cache a run of chunks greater than
1171 # 2G on Windows
1171 # 2G on Windows
1172 return [self._chunk(rev, df=df) for rev in revs]
1172 return [self._chunk(rev, df=df) for rev in revs]
1173
1173
1174 for rev in revs:
1174 for rev in revs:
1175 chunkstart = start(rev)
1175 chunkstart = start(rev)
1176 if inline:
1176 if inline:
1177 chunkstart += (rev + 1) * iosize
1177 chunkstart += (rev + 1) * iosize
1178 chunklength = length(rev)
1178 chunklength = length(rev)
1179 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1179 ladd(decompress(buffer(data, chunkstart - offset, chunklength)))
1180
1180
1181 return l
1181 return l
1182
1182
1183 def _chunkclear(self):
1183 def _chunkclear(self):
1184 """Clear the raw chunk cache."""
1184 """Clear the raw chunk cache."""
1185 self._chunkcache = (0, '')
1185 self._chunkcache = (0, '')
1186
1186
1187 def deltaparent(self, rev):
1187 def deltaparent(self, rev):
1188 """return deltaparent of the given revision"""
1188 """return deltaparent of the given revision"""
1189 base = self.index[rev][3]
1189 base = self.index[rev][3]
1190 if base == rev:
1190 if base == rev:
1191 return nullrev
1191 return nullrev
1192 elif self._generaldelta:
1192 elif self._generaldelta:
1193 return base
1193 return base
1194 else:
1194 else:
1195 return rev - 1
1195 return rev - 1
1196
1196
1197 def revdiff(self, rev1, rev2):
1197 def revdiff(self, rev1, rev2):
1198 """return or calculate a delta between two revisions"""
1198 """return or calculate a delta between two revisions"""
1199 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1199 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
1200 return str(self._chunk(rev2))
1200 return str(self._chunk(rev2))
1201
1201
1202 return mdiff.textdiff(self.revision(rev1),
1202 return mdiff.textdiff(self.revision(rev1),
1203 self.revision(rev2))
1203 self.revision(rev2))
1204
1204
1205 def revision(self, nodeorrev, _df=None):
1205 def revision(self, nodeorrev, _df=None, raw=False):
1206 """return an uncompressed revision of a given node or revision
1206 """return an uncompressed revision of a given node or revision
1207 number.
1207 number.
1208
1208
1209 _df is an existing file handle to read from. It is meant to only be
1209 _df - an existing file handle to read from. (internal-only)
1210 used internally.
1210 raw - an optional argument specifying if the revision data is to be
1211 treated as raw data when applying flag transforms. 'raw' should be set
1212 to True when generating changegroups or in debug commands.
1211 """
1213 """
1212 if isinstance(nodeorrev, int):
1214 if isinstance(nodeorrev, int):
1213 rev = nodeorrev
1215 rev = nodeorrev
1214 node = self.node(rev)
1216 node = self.node(rev)
1215 else:
1217 else:
1216 node = nodeorrev
1218 node = nodeorrev
1217 rev = None
1219 rev = None
1218
1220
1219 cachedrev = None
1221 cachedrev = None
1220 if node == nullid:
1222 if node == nullid:
1221 return ""
1223 return ""
1222 if self._cache:
1224 if self._cache:
1223 if self._cache[0] == node:
1225 if self._cache[0] == node:
1224 return self._cache[2]
1226 return self._cache[2]
1225 cachedrev = self._cache[1]
1227 cachedrev = self._cache[1]
1226
1228
1227 # look up what we need to read
1229 # look up what we need to read
1228 text = None
1230 text = None
1229 if rev is None:
1231 if rev is None:
1230 rev = self.rev(node)
1232 rev = self.rev(node)
1231
1233
1232 # check rev flags
1234 # check rev flags
1233 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1235 if self.flags(rev) & ~REVIDX_KNOWN_FLAGS:
1234 raise RevlogError(_('incompatible revision flag %x') %
1236 raise RevlogError(_('incompatible revision flag %x') %
1235 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1237 (self.flags(rev) & ~REVIDX_KNOWN_FLAGS))
1236
1238
1237 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1239 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
1238 if stopped:
1240 if stopped:
1239 text = self._cache[2]
1241 text = self._cache[2]
1240
1242
1241 # drop cache to save memory
1243 # drop cache to save memory
1242 self._cache = None
1244 self._cache = None
1243
1245
1244 bins = self._chunks(chain, df=_df)
1246 bins = self._chunks(chain, df=_df)
1245 if text is None:
1247 if text is None:
1246 text = str(bins[0])
1248 text = str(bins[0])
1247 bins = bins[1:]
1249 bins = bins[1:]
1248
1250
1249 text = mdiff.patches(text, bins)
1251 text = mdiff.patches(text, bins)
1250 self.checkhash(text, node, rev=rev)
1252 self.checkhash(text, node, rev=rev)
1251 self._cache = (node, rev, text)
1253 self._cache = (node, rev, text)
1252 return text
1254 return text
1253
1255
1254 def hash(self, text, p1, p2):
1256 def hash(self, text, p1, p2):
1255 """Compute a node hash.
1257 """Compute a node hash.
1256
1258
1257 Available as a function so that subclasses can replace the hash
1259 Available as a function so that subclasses can replace the hash
1258 as needed.
1260 as needed.
1259 """
1261 """
1260 return hash(text, p1, p2)
1262 return hash(text, p1, p2)
1261
1263
1262 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1264 def checkhash(self, text, node, p1=None, p2=None, rev=None):
1263 """Check node hash integrity.
1265 """Check node hash integrity.
1264
1266
1265 Available as a function so that subclasses can extend hash mismatch
1267 Available as a function so that subclasses can extend hash mismatch
1266 behaviors as needed.
1268 behaviors as needed.
1267 """
1269 """
1268 if p1 is None and p2 is None:
1270 if p1 is None and p2 is None:
1269 p1, p2 = self.parents(node)
1271 p1, p2 = self.parents(node)
1270 if node != self.hash(text, p1, p2):
1272 if node != self.hash(text, p1, p2):
1271 revornode = rev
1273 revornode = rev
1272 if revornode is None:
1274 if revornode is None:
1273 revornode = templatefilters.short(hex(node))
1275 revornode = templatefilters.short(hex(node))
1274 raise RevlogError(_("integrity check failed on %s:%s")
1276 raise RevlogError(_("integrity check failed on %s:%s")
1275 % (self.indexfile, revornode))
1277 % (self.indexfile, revornode))
1276
1278
1277 def checkinlinesize(self, tr, fp=None):
1279 def checkinlinesize(self, tr, fp=None):
1278 """Check if the revlog is too big for inline and convert if so.
1280 """Check if the revlog is too big for inline and convert if so.
1279
1281
1280 This should be called after revisions are added to the revlog. If the
1282 This should be called after revisions are added to the revlog. If the
1281 revlog has grown too large to be an inline revlog, it will convert it
1283 revlog has grown too large to be an inline revlog, it will convert it
1282 to use multiple index and data files.
1284 to use multiple index and data files.
1283 """
1285 """
1284 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1286 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
1285 return
1287 return
1286
1288
1287 trinfo = tr.find(self.indexfile)
1289 trinfo = tr.find(self.indexfile)
1288 if trinfo is None:
1290 if trinfo is None:
1289 raise RevlogError(_("%s not found in the transaction")
1291 raise RevlogError(_("%s not found in the transaction")
1290 % self.indexfile)
1292 % self.indexfile)
1291
1293
1292 trindex = trinfo[2]
1294 trindex = trinfo[2]
1293 if trindex is not None:
1295 if trindex is not None:
1294 dataoff = self.start(trindex)
1296 dataoff = self.start(trindex)
1295 else:
1297 else:
1296 # revlog was stripped at start of transaction, use all leftover data
1298 # revlog was stripped at start of transaction, use all leftover data
1297 trindex = len(self) - 1
1299 trindex = len(self) - 1
1298 dataoff = self.end(-2)
1300 dataoff = self.end(-2)
1299
1301
1300 tr.add(self.datafile, dataoff)
1302 tr.add(self.datafile, dataoff)
1301
1303
1302 if fp:
1304 if fp:
1303 fp.flush()
1305 fp.flush()
1304 fp.close()
1306 fp.close()
1305
1307
1306 df = self.opener(self.datafile, 'w')
1308 df = self.opener(self.datafile, 'w')
1307 try:
1309 try:
1308 for r in self:
1310 for r in self:
1309 df.write(self._chunkraw(r, r)[1])
1311 df.write(self._chunkraw(r, r)[1])
1310 finally:
1312 finally:
1311 df.close()
1313 df.close()
1312
1314
1313 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1315 fp = self.opener(self.indexfile, 'w', atomictemp=True,
1314 checkambig=self._checkambig)
1316 checkambig=self._checkambig)
1315 self.version &= ~(REVLOGNGINLINEDATA)
1317 self.version &= ~(REVLOGNGINLINEDATA)
1316 self._inline = False
1318 self._inline = False
1317 for i in self:
1319 for i in self:
1318 e = self._io.packentry(self.index[i], self.node, self.version, i)
1320 e = self._io.packentry(self.index[i], self.node, self.version, i)
1319 fp.write(e)
1321 fp.write(e)
1320
1322
1321 # if we don't call close, the temp file will never replace the
1323 # if we don't call close, the temp file will never replace the
1322 # real index
1324 # real index
1323 fp.close()
1325 fp.close()
1324
1326
1325 tr.replace(self.indexfile, trindex * self._io.size)
1327 tr.replace(self.indexfile, trindex * self._io.size)
1326 self._chunkclear()
1328 self._chunkclear()
1327
1329
1328 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1330 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
1329 node=None):
1331 node=None):
1330 """add a revision to the log
1332 """add a revision to the log
1331
1333
1332 text - the revision data to add
1334 text - the revision data to add
1333 transaction - the transaction object used for rollback
1335 transaction - the transaction object used for rollback
1334 link - the linkrev data to add
1336 link - the linkrev data to add
1335 p1, p2 - the parent nodeids of the revision
1337 p1, p2 - the parent nodeids of the revision
1336 cachedelta - an optional precomputed delta
1338 cachedelta - an optional precomputed delta
1337 node - nodeid of revision; typically node is not specified, and it is
1339 node - nodeid of revision; typically node is not specified, and it is
1338 computed by default as hash(text, p1, p2), however subclasses might
1340 computed by default as hash(text, p1, p2), however subclasses might
1339 use different hashing method (and override checkhash() in such case)
1341 use different hashing method (and override checkhash() in such case)
1340 """
1342 """
1341 if link == nullrev:
1343 if link == nullrev:
1342 raise RevlogError(_("attempted to add linkrev -1 to %s")
1344 raise RevlogError(_("attempted to add linkrev -1 to %s")
1343 % self.indexfile)
1345 % self.indexfile)
1344
1346
1345 if len(text) > _maxentrysize:
1347 if len(text) > _maxentrysize:
1346 raise RevlogError(
1348 raise RevlogError(
1347 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1349 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
1348 % (self.indexfile, len(text)))
1350 % (self.indexfile, len(text)))
1349
1351
1350 node = node or self.hash(text, p1, p2)
1352 node = node or self.hash(text, p1, p2)
1351 if node in self.nodemap:
1353 if node in self.nodemap:
1352 return node
1354 return node
1353
1355
1354 dfh = None
1356 dfh = None
1355 if not self._inline:
1357 if not self._inline:
1356 dfh = self.opener(self.datafile, "a+")
1358 dfh = self.opener(self.datafile, "a+")
1357 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1359 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1358 try:
1360 try:
1359 return self._addrevision(node, text, transaction, link, p1, p2,
1361 return self._addrevision(node, text, transaction, link, p1, p2,
1360 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1362 REVIDX_DEFAULT_FLAGS, cachedelta, ifh, dfh)
1361 finally:
1363 finally:
1362 if dfh:
1364 if dfh:
1363 dfh.close()
1365 dfh.close()
1364 ifh.close()
1366 ifh.close()
1365
1367
1366 def compress(self, text):
1368 def compress(self, text):
1367 """ generate a possibly-compressed representation of text """
1369 """ generate a possibly-compressed representation of text """
1368 if not text:
1370 if not text:
1369 return ("", text)
1371 return ("", text)
1370 l = len(text)
1372 l = len(text)
1371 bin = None
1373 bin = None
1372 if l < 44:
1374 if l < 44:
1373 pass
1375 pass
1374 elif l > 1000000:
1376 elif l > 1000000:
1375 # zlib makes an internal copy, thus doubling memory usage for
1377 # zlib makes an internal copy, thus doubling memory usage for
1376 # large files, so lets do this in pieces
1378 # large files, so lets do this in pieces
1377 z = zlib.compressobj()
1379 z = zlib.compressobj()
1378 p = []
1380 p = []
1379 pos = 0
1381 pos = 0
1380 while pos < l:
1382 while pos < l:
1381 pos2 = pos + 2**20
1383 pos2 = pos + 2**20
1382 p.append(z.compress(text[pos:pos2]))
1384 p.append(z.compress(text[pos:pos2]))
1383 pos = pos2
1385 pos = pos2
1384 p.append(z.flush())
1386 p.append(z.flush())
1385 if sum(map(len, p)) < l:
1387 if sum(map(len, p)) < l:
1386 bin = "".join(p)
1388 bin = "".join(p)
1387 else:
1389 else:
1388 bin = _compress(text)
1390 bin = _compress(text)
1389 if bin is None or len(bin) > l:
1391 if bin is None or len(bin) > l:
1390 if text[0] == '\0':
1392 if text[0] == '\0':
1391 return ("", text)
1393 return ("", text)
1392 return ('u', text)
1394 return ('u', text)
1393 return ("", bin)
1395 return ("", bin)
1394
1396
1395 def _isgooddelta(self, d, textlen):
1397 def _isgooddelta(self, d, textlen):
1396 """Returns True if the given delta is good. Good means that it is within
1398 """Returns True if the given delta is good. Good means that it is within
1397 the disk span, disk size, and chain length bounds that we know to be
1399 the disk span, disk size, and chain length bounds that we know to be
1398 performant."""
1400 performant."""
1399 if d is None:
1401 if d is None:
1400 return False
1402 return False
1401
1403
1402 # - 'dist' is the distance from the base revision -- bounding it limits
1404 # - 'dist' is the distance from the base revision -- bounding it limits
1403 # the amount of I/O we need to do.
1405 # the amount of I/O we need to do.
1404 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1406 # - 'compresseddeltalen' is the sum of the total size of deltas we need
1405 # to apply -- bounding it limits the amount of CPU we consume.
1407 # to apply -- bounding it limits the amount of CPU we consume.
1406 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1408 dist, l, data, base, chainbase, chainlen, compresseddeltalen = d
1407 if (dist > textlen * 4 or l > textlen or
1409 if (dist > textlen * 4 or l > textlen or
1408 compresseddeltalen > textlen * 2 or
1410 compresseddeltalen > textlen * 2 or
1409 (self._maxchainlen and chainlen > self._maxchainlen)):
1411 (self._maxchainlen and chainlen > self._maxchainlen)):
1410 return False
1412 return False
1411
1413
1412 return True
1414 return True
1413
1415
1414 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1416 def _addrevision(self, node, text, transaction, link, p1, p2, flags,
1415 cachedelta, ifh, dfh, alwayscache=False):
1417 cachedelta, ifh, dfh, alwayscache=False, raw=False):
1416 """internal function to add revisions to the log
1418 """internal function to add revisions to the log
1417
1419
1418 see addrevision for argument descriptions.
1420 see addrevision for argument descriptions.
1419 invariants:
1421 invariants:
1420 - text is optional (can be None); if not set, cachedelta must be set.
1422 - text is optional (can be None); if not set, cachedelta must be set.
1421 if both are set, they must correspond to each other.
1423 if both are set, they must correspond to each other.
1424 - raw is optional; if set to True, it indicates the revision data is to
1425 be treated by _processflags() as raw. It is usually set by changegroup
1426 generation and debug commands.
1422 """
1427 """
1423 btext = [text]
1428 btext = [text]
1424 def buildtext():
1429 def buildtext():
1425 if btext[0] is not None:
1430 if btext[0] is not None:
1426 return btext[0]
1431 return btext[0]
1427 baserev = cachedelta[0]
1432 baserev = cachedelta[0]
1428 delta = cachedelta[1]
1433 delta = cachedelta[1]
1429 # special case deltas which replace entire base; no need to decode
1434 # special case deltas which replace entire base; no need to decode
1430 # base revision. this neatly avoids censored bases, which throw when
1435 # base revision. this neatly avoids censored bases, which throw when
1431 # they're decoded.
1436 # they're decoded.
1432 hlen = struct.calcsize(">lll")
1437 hlen = struct.calcsize(">lll")
1433 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1438 if delta[:hlen] == mdiff.replacediffheader(self.rawsize(baserev),
1434 len(delta) - hlen):
1439 len(delta) - hlen):
1435 btext[0] = delta[hlen:]
1440 btext[0] = delta[hlen:]
1436 else:
1441 else:
1437 if self._inline:
1442 if self._inline:
1438 fh = ifh
1443 fh = ifh
1439 else:
1444 else:
1440 fh = dfh
1445 fh = dfh
1441 basetext = self.revision(self.node(baserev), _df=fh)
1446 basetext = self.revision(self.node(baserev), _df=fh, raw=raw)
1442 btext[0] = mdiff.patch(basetext, delta)
1447 btext[0] = mdiff.patch(basetext, delta)
1448
1443 try:
1449 try:
1444 self.checkhash(btext[0], node, p1=p1, p2=p2)
1450 self.checkhash(btext[0], node, p1=p1, p2=p2)
1445 if flags & REVIDX_ISCENSORED:
1451 if flags & REVIDX_ISCENSORED:
1446 raise RevlogError(_('node %s is not censored') % node)
1452 raise RevlogError(_('node %s is not censored') % node)
1447 except CensoredNodeError:
1453 except CensoredNodeError:
1448 # must pass the censored index flag to add censored revisions
1454 # must pass the censored index flag to add censored revisions
1449 if not flags & REVIDX_ISCENSORED:
1455 if not flags & REVIDX_ISCENSORED:
1450 raise
1456 raise
1451 return btext[0]
1457 return btext[0]
1452
1458
1453 def builddelta(rev):
1459 def builddelta(rev):
1454 # can we use the cached delta?
1460 # can we use the cached delta?
1455 if cachedelta and cachedelta[0] == rev:
1461 if cachedelta and cachedelta[0] == rev:
1456 delta = cachedelta[1]
1462 delta = cachedelta[1]
1457 else:
1463 else:
1458 t = buildtext()
1464 t = buildtext()
1459 if self.iscensored(rev):
1465 if self.iscensored(rev):
1460 # deltas based on a censored revision must replace the
1466 # deltas based on a censored revision must replace the
1461 # full content in one patch, so delta works everywhere
1467 # full content in one patch, so delta works everywhere
1462 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1468 header = mdiff.replacediffheader(self.rawsize(rev), len(t))
1463 delta = header + t
1469 delta = header + t
1464 else:
1470 else:
1465 if self._inline:
1471 if self._inline:
1466 fh = ifh
1472 fh = ifh
1467 else:
1473 else:
1468 fh = dfh
1474 fh = dfh
1469 ptext = self.revision(self.node(rev), _df=fh)
1475 ptext = self.revision(self.node(rev), _df=fh)
1470 delta = mdiff.textdiff(ptext, t)
1476 delta = mdiff.textdiff(ptext, t)
1471 header, data = self.compress(delta)
1477 header, data = self.compress(delta)
1472 deltalen = len(header) + len(data)
1478 deltalen = len(header) + len(data)
1473 chainbase = self.chainbase(rev)
1479 chainbase = self.chainbase(rev)
1474 dist = deltalen + offset - self.start(chainbase)
1480 dist = deltalen + offset - self.start(chainbase)
1475 if self._generaldelta:
1481 if self._generaldelta:
1476 base = rev
1482 base = rev
1477 else:
1483 else:
1478 base = chainbase
1484 base = chainbase
1479 chainlen, compresseddeltalen = self._chaininfo(rev)
1485 chainlen, compresseddeltalen = self._chaininfo(rev)
1480 chainlen += 1
1486 chainlen += 1
1481 compresseddeltalen += deltalen
1487 compresseddeltalen += deltalen
1482 return (dist, deltalen, (header, data), base,
1488 return (dist, deltalen, (header, data), base,
1483 chainbase, chainlen, compresseddeltalen)
1489 chainbase, chainlen, compresseddeltalen)
1484
1490
1485 curr = len(self)
1491 curr = len(self)
1486 prev = curr - 1
1492 prev = curr - 1
1487 offset = self.end(prev)
1493 offset = self.end(prev)
1488 delta = None
1494 delta = None
1489 p1r, p2r = self.rev(p1), self.rev(p2)
1495 p1r, p2r = self.rev(p1), self.rev(p2)
1490
1496
1491 # full versions are inserted when the needed deltas
1497 # full versions are inserted when the needed deltas
1492 # become comparable to the uncompressed text
1498 # become comparable to the uncompressed text
1493 if text is None:
1499 if text is None:
1494 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1500 textlen = mdiff.patchedsize(self.rawsize(cachedelta[0]),
1495 cachedelta[1])
1501 cachedelta[1])
1496 else:
1502 else:
1497 textlen = len(text)
1503 textlen = len(text)
1498
1504
1499 # should we try to build a delta?
1505 # should we try to build a delta?
1500 if prev != nullrev and self.storedeltachains:
1506 if prev != nullrev and self.storedeltachains:
1501 tested = set()
1507 tested = set()
1502 # This condition is true most of the time when processing
1508 # This condition is true most of the time when processing
1503 # changegroup data into a generaldelta repo. The only time it
1509 # changegroup data into a generaldelta repo. The only time it
1504 # isn't true is if this is the first revision in a delta chain
1510 # isn't true is if this is the first revision in a delta chain
1505 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1511 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
1506 if cachedelta and self._generaldelta and self._lazydeltabase:
1512 if cachedelta and self._generaldelta and self._lazydeltabase:
1507 # Assume what we received from the server is a good choice
1513 # Assume what we received from the server is a good choice
1508 # build delta will reuse the cache
1514 # build delta will reuse the cache
1509 candidatedelta = builddelta(cachedelta[0])
1515 candidatedelta = builddelta(cachedelta[0])
1510 tested.add(cachedelta[0])
1516 tested.add(cachedelta[0])
1511 if self._isgooddelta(candidatedelta, textlen):
1517 if self._isgooddelta(candidatedelta, textlen):
1512 delta = candidatedelta
1518 delta = candidatedelta
1513 if delta is None and self._generaldelta:
1519 if delta is None and self._generaldelta:
1514 # exclude already lazy tested base if any
1520 # exclude already lazy tested base if any
1515 parents = [p for p in (p1r, p2r)
1521 parents = [p for p in (p1r, p2r)
1516 if p != nullrev and p not in tested]
1522 if p != nullrev and p not in tested]
1517 if parents and not self._aggressivemergedeltas:
1523 if parents and not self._aggressivemergedeltas:
1518 # Pick whichever parent is closer to us (to minimize the
1524 # Pick whichever parent is closer to us (to minimize the
1519 # chance of having to build a fulltext).
1525 # chance of having to build a fulltext).
1520 parents = [max(parents)]
1526 parents = [max(parents)]
1521 tested.update(parents)
1527 tested.update(parents)
1522 pdeltas = []
1528 pdeltas = []
1523 for p in parents:
1529 for p in parents:
1524 pd = builddelta(p)
1530 pd = builddelta(p)
1525 if self._isgooddelta(pd, textlen):
1531 if self._isgooddelta(pd, textlen):
1526 pdeltas.append(pd)
1532 pdeltas.append(pd)
1527 if pdeltas:
1533 if pdeltas:
1528 delta = min(pdeltas, key=lambda x: x[1])
1534 delta = min(pdeltas, key=lambda x: x[1])
1529 if delta is None and prev not in tested:
1535 if delta is None and prev not in tested:
1530 # other approach failed try against prev to hopefully save us a
1536 # other approach failed try against prev to hopefully save us a
1531 # fulltext.
1537 # fulltext.
1532 candidatedelta = builddelta(prev)
1538 candidatedelta = builddelta(prev)
1533 if self._isgooddelta(candidatedelta, textlen):
1539 if self._isgooddelta(candidatedelta, textlen):
1534 delta = candidatedelta
1540 delta = candidatedelta
1535 if delta is not None:
1541 if delta is not None:
1536 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1542 dist, l, data, base, chainbase, chainlen, compresseddeltalen = delta
1537 else:
1543 else:
1538 text = buildtext()
1544 text = buildtext()
1539 data = self.compress(text)
1545 data = self.compress(text)
1540 l = len(data[1]) + len(data[0])
1546 l = len(data[1]) + len(data[0])
1541 base = chainbase = curr
1547 base = chainbase = curr
1542
1548
1543 e = (offset_type(offset, flags), l, textlen,
1549 e = (offset_type(offset, flags), l, textlen,
1544 base, link, p1r, p2r, node)
1550 base, link, p1r, p2r, node)
1545 self.index.insert(-1, e)
1551 self.index.insert(-1, e)
1546 self.nodemap[node] = curr
1552 self.nodemap[node] = curr
1547
1553
1548 entry = self._io.packentry(e, self.node, self.version, curr)
1554 entry = self._io.packentry(e, self.node, self.version, curr)
1549 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1555 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
1550
1556
1551 if alwayscache and text is None:
1557 if alwayscache and text is None:
1552 text = buildtext()
1558 text = buildtext()
1553
1559
1554 if type(text) == str: # only accept immutable objects
1560 if type(text) == str: # only accept immutable objects
1555 self._cache = (node, curr, text)
1561 self._cache = (node, curr, text)
1556 self._chainbasecache[curr] = chainbase
1562 self._chainbasecache[curr] = chainbase
1557 return node
1563 return node
1558
1564
1559 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1565 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
1560 # Files opened in a+ mode have inconsistent behavior on various
1566 # Files opened in a+ mode have inconsistent behavior on various
1561 # platforms. Windows requires that a file positioning call be made
1567 # platforms. Windows requires that a file positioning call be made
1562 # when the file handle transitions between reads and writes. See
1568 # when the file handle transitions between reads and writes. See
1563 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1569 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
1564 # platforms, Python or the platform itself can be buggy. Some versions
1570 # platforms, Python or the platform itself can be buggy. Some versions
1565 # of Solaris have been observed to not append at the end of the file
1571 # of Solaris have been observed to not append at the end of the file
1566 # if the file was seeked to before the end. See issue4943 for more.
1572 # if the file was seeked to before the end. See issue4943 for more.
1567 #
1573 #
1568 # We work around this issue by inserting a seek() before writing.
1574 # We work around this issue by inserting a seek() before writing.
1569 # Note: This is likely not necessary on Python 3.
1575 # Note: This is likely not necessary on Python 3.
1570 ifh.seek(0, os.SEEK_END)
1576 ifh.seek(0, os.SEEK_END)
1571 if dfh:
1577 if dfh:
1572 dfh.seek(0, os.SEEK_END)
1578 dfh.seek(0, os.SEEK_END)
1573
1579
1574 curr = len(self) - 1
1580 curr = len(self) - 1
1575 if not self._inline:
1581 if not self._inline:
1576 transaction.add(self.datafile, offset)
1582 transaction.add(self.datafile, offset)
1577 transaction.add(self.indexfile, curr * len(entry))
1583 transaction.add(self.indexfile, curr * len(entry))
1578 if data[0]:
1584 if data[0]:
1579 dfh.write(data[0])
1585 dfh.write(data[0])
1580 dfh.write(data[1])
1586 dfh.write(data[1])
1581 ifh.write(entry)
1587 ifh.write(entry)
1582 else:
1588 else:
1583 offset += curr * self._io.size
1589 offset += curr * self._io.size
1584 transaction.add(self.indexfile, offset, curr)
1590 transaction.add(self.indexfile, offset, curr)
1585 ifh.write(entry)
1591 ifh.write(entry)
1586 ifh.write(data[0])
1592 ifh.write(data[0])
1587 ifh.write(data[1])
1593 ifh.write(data[1])
1588 self.checkinlinesize(transaction, ifh)
1594 self.checkinlinesize(transaction, ifh)
1589
1595
1590 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1596 def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
1591 """
1597 """
1592 add a delta group
1598 add a delta group
1593
1599
1594 given a set of deltas, add them to the revision log. the
1600 given a set of deltas, add them to the revision log. the
1595 first delta is against its parent, which should be in our
1601 first delta is against its parent, which should be in our
1596 log, the rest are against the previous delta.
1602 log, the rest are against the previous delta.
1597
1603
1598 If ``addrevisioncb`` is defined, it will be called with arguments of
1604 If ``addrevisioncb`` is defined, it will be called with arguments of
1599 this revlog and the node that was added.
1605 this revlog and the node that was added.
1600 """
1606 """
1601
1607
1602 # track the base of the current delta log
1608 # track the base of the current delta log
1603 content = []
1609 content = []
1604 node = None
1610 node = None
1605
1611
1606 r = len(self)
1612 r = len(self)
1607 end = 0
1613 end = 0
1608 if r:
1614 if r:
1609 end = self.end(r - 1)
1615 end = self.end(r - 1)
1610 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1616 ifh = self.opener(self.indexfile, "a+", checkambig=self._checkambig)
1611 isize = r * self._io.size
1617 isize = r * self._io.size
1612 if self._inline:
1618 if self._inline:
1613 transaction.add(self.indexfile, end + isize, r)
1619 transaction.add(self.indexfile, end + isize, r)
1614 dfh = None
1620 dfh = None
1615 else:
1621 else:
1616 transaction.add(self.indexfile, isize, r)
1622 transaction.add(self.indexfile, isize, r)
1617 transaction.add(self.datafile, end)
1623 transaction.add(self.datafile, end)
1618 dfh = self.opener(self.datafile, "a+")
1624 dfh = self.opener(self.datafile, "a+")
1619 def flush():
1625 def flush():
1620 if dfh:
1626 if dfh:
1621 dfh.flush()
1627 dfh.flush()
1622 ifh.flush()
1628 ifh.flush()
1623 try:
1629 try:
1624 # loop through our set of deltas
1630 # loop through our set of deltas
1625 chain = None
1631 chain = None
1626 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1632 for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
1627 node = chunkdata['node']
1633 node = chunkdata['node']
1628 p1 = chunkdata['p1']
1634 p1 = chunkdata['p1']
1629 p2 = chunkdata['p2']
1635 p2 = chunkdata['p2']
1630 cs = chunkdata['cs']
1636 cs = chunkdata['cs']
1631 deltabase = chunkdata['deltabase']
1637 deltabase = chunkdata['deltabase']
1632 delta = chunkdata['delta']
1638 delta = chunkdata['delta']
1633 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1639 flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
1634
1640
1635 content.append(node)
1641 content.append(node)
1636
1642
1637 link = linkmapper(cs)
1643 link = linkmapper(cs)
1638 if node in self.nodemap:
1644 if node in self.nodemap:
1639 # this can happen if two branches make the same change
1645 # this can happen if two branches make the same change
1640 chain = node
1646 chain = node
1641 continue
1647 continue
1642
1648
1643 for p in (p1, p2):
1649 for p in (p1, p2):
1644 if p not in self.nodemap:
1650 if p not in self.nodemap:
1645 raise LookupError(p, self.indexfile,
1651 raise LookupError(p, self.indexfile,
1646 _('unknown parent'))
1652 _('unknown parent'))
1647
1653
1648 if deltabase not in self.nodemap:
1654 if deltabase not in self.nodemap:
1649 raise LookupError(deltabase, self.indexfile,
1655 raise LookupError(deltabase, self.indexfile,
1650 _('unknown delta base'))
1656 _('unknown delta base'))
1651
1657
1652 baserev = self.rev(deltabase)
1658 baserev = self.rev(deltabase)
1653
1659
1654 if baserev != nullrev and self.iscensored(baserev):
1660 if baserev != nullrev and self.iscensored(baserev):
1655 # if base is censored, delta must be full replacement in a
1661 # if base is censored, delta must be full replacement in a
1656 # single patch operation
1662 # single patch operation
1657 hlen = struct.calcsize(">lll")
1663 hlen = struct.calcsize(">lll")
1658 oldlen = self.rawsize(baserev)
1664 oldlen = self.rawsize(baserev)
1659 newlen = len(delta) - hlen
1665 newlen = len(delta) - hlen
1660 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1666 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
1661 raise error.CensoredBaseError(self.indexfile,
1667 raise error.CensoredBaseError(self.indexfile,
1662 self.node(baserev))
1668 self.node(baserev))
1663
1669
1664 if not flags and self._peek_iscensored(baserev, delta, flush):
1670 if not flags and self._peek_iscensored(baserev, delta, flush):
1665 flags |= REVIDX_ISCENSORED
1671 flags |= REVIDX_ISCENSORED
1666
1672
1667 # We assume consumers of addrevisioncb will want to retrieve
1673 # We assume consumers of addrevisioncb will want to retrieve
1668 # the added revision, which will require a call to
1674 # the added revision, which will require a call to
1669 # revision(). revision() will fast path if there is a cache
1675 # revision(). revision() will fast path if there is a cache
1670 # hit. So, we tell _addrevision() to always cache in this case.
1676 # hit. So, we tell _addrevision() to always cache in this case.
1677 # We're only using addgroup() in the context of changegroup
1678 # generation so the revision data can always be handled as raw
1679 # by the flagprocessor.
1671 chain = self._addrevision(node, None, transaction, link,
1680 chain = self._addrevision(node, None, transaction, link,
1672 p1, p2, flags, (baserev, delta),
1681 p1, p2, flags, (baserev, delta),
1673 ifh, dfh,
1682 ifh, dfh,
1674 alwayscache=bool(addrevisioncb))
1683 alwayscache=bool(addrevisioncb),
1684 raw=True)
1675
1685
1676 if addrevisioncb:
1686 if addrevisioncb:
1677 addrevisioncb(self, chain)
1687 addrevisioncb(self, chain)
1678
1688
1679 if not dfh and not self._inline:
1689 if not dfh and not self._inline:
1680 # addrevision switched from inline to conventional
1690 # addrevision switched from inline to conventional
1681 # reopen the index
1691 # reopen the index
1682 ifh.close()
1692 ifh.close()
1683 dfh = self.opener(self.datafile, "a+")
1693 dfh = self.opener(self.datafile, "a+")
1684 ifh = self.opener(self.indexfile, "a+",
1694 ifh = self.opener(self.indexfile, "a+",
1685 checkambig=self._checkambig)
1695 checkambig=self._checkambig)
1686 finally:
1696 finally:
1687 if dfh:
1697 if dfh:
1688 dfh.close()
1698 dfh.close()
1689 ifh.close()
1699 ifh.close()
1690
1700
1691 return content
1701 return content
1692
1702
1693 def iscensored(self, rev):
1703 def iscensored(self, rev):
1694 """Check if a file revision is censored."""
1704 """Check if a file revision is censored."""
1695 return False
1705 return False
1696
1706
1697 def _peek_iscensored(self, baserev, delta, flush):
1707 def _peek_iscensored(self, baserev, delta, flush):
1698 """Quickly check if a delta produces a censored revision."""
1708 """Quickly check if a delta produces a censored revision."""
1699 return False
1709 return False
1700
1710
1701 def getstrippoint(self, minlink):
1711 def getstrippoint(self, minlink):
1702 """find the minimum rev that must be stripped to strip the linkrev
1712 """find the minimum rev that must be stripped to strip the linkrev
1703
1713
1704 Returns a tuple containing the minimum rev and a set of all revs that
1714 Returns a tuple containing the minimum rev and a set of all revs that
1705 have linkrevs that will be broken by this strip.
1715 have linkrevs that will be broken by this strip.
1706 """
1716 """
1707 brokenrevs = set()
1717 brokenrevs = set()
1708 strippoint = len(self)
1718 strippoint = len(self)
1709
1719
1710 heads = {}
1720 heads = {}
1711 futurelargelinkrevs = set()
1721 futurelargelinkrevs = set()
1712 for head in self.headrevs():
1722 for head in self.headrevs():
1713 headlinkrev = self.linkrev(head)
1723 headlinkrev = self.linkrev(head)
1714 heads[head] = headlinkrev
1724 heads[head] = headlinkrev
1715 if headlinkrev >= minlink:
1725 if headlinkrev >= minlink:
1716 futurelargelinkrevs.add(headlinkrev)
1726 futurelargelinkrevs.add(headlinkrev)
1717
1727
1718 # This algorithm involves walking down the rev graph, starting at the
1728 # This algorithm involves walking down the rev graph, starting at the
1719 # heads. Since the revs are topologically sorted according to linkrev,
1729 # heads. Since the revs are topologically sorted according to linkrev,
1720 # once all head linkrevs are below the minlink, we know there are
1730 # once all head linkrevs are below the minlink, we know there are
1721 # no more revs that could have a linkrev greater than minlink.
1731 # no more revs that could have a linkrev greater than minlink.
1722 # So we can stop walking.
1732 # So we can stop walking.
1723 while futurelargelinkrevs:
1733 while futurelargelinkrevs:
1724 strippoint -= 1
1734 strippoint -= 1
1725 linkrev = heads.pop(strippoint)
1735 linkrev = heads.pop(strippoint)
1726
1736
1727 if linkrev < minlink:
1737 if linkrev < minlink:
1728 brokenrevs.add(strippoint)
1738 brokenrevs.add(strippoint)
1729 else:
1739 else:
1730 futurelargelinkrevs.remove(linkrev)
1740 futurelargelinkrevs.remove(linkrev)
1731
1741
1732 for p in self.parentrevs(strippoint):
1742 for p in self.parentrevs(strippoint):
1733 if p != nullrev:
1743 if p != nullrev:
1734 plinkrev = self.linkrev(p)
1744 plinkrev = self.linkrev(p)
1735 heads[p] = plinkrev
1745 heads[p] = plinkrev
1736 if plinkrev >= minlink:
1746 if plinkrev >= minlink:
1737 futurelargelinkrevs.add(plinkrev)
1747 futurelargelinkrevs.add(plinkrev)
1738
1748
1739 return strippoint, brokenrevs
1749 return strippoint, brokenrevs
1740
1750
1741 def strip(self, minlink, transaction):
1751 def strip(self, minlink, transaction):
1742 """truncate the revlog on the first revision with a linkrev >= minlink
1752 """truncate the revlog on the first revision with a linkrev >= minlink
1743
1753
1744 This function is called when we're stripping revision minlink and
1754 This function is called when we're stripping revision minlink and
1745 its descendants from the repository.
1755 its descendants from the repository.
1746
1756
1747 We have to remove all revisions with linkrev >= minlink, because
1757 We have to remove all revisions with linkrev >= minlink, because
1748 the equivalent changelog revisions will be renumbered after the
1758 the equivalent changelog revisions will be renumbered after the
1749 strip.
1759 strip.
1750
1760
1751 So we truncate the revlog on the first of these revisions, and
1761 So we truncate the revlog on the first of these revisions, and
1752 trust that the caller has saved the revisions that shouldn't be
1762 trust that the caller has saved the revisions that shouldn't be
1753 removed and that it'll re-add them after this truncation.
1763 removed and that it'll re-add them after this truncation.
1754 """
1764 """
1755 if len(self) == 0:
1765 if len(self) == 0:
1756 return
1766 return
1757
1767
1758 rev, _ = self.getstrippoint(minlink)
1768 rev, _ = self.getstrippoint(minlink)
1759 if rev == len(self):
1769 if rev == len(self):
1760 return
1770 return
1761
1771
1762 # first truncate the files on disk
1772 # first truncate the files on disk
1763 end = self.start(rev)
1773 end = self.start(rev)
1764 if not self._inline:
1774 if not self._inline:
1765 transaction.add(self.datafile, end)
1775 transaction.add(self.datafile, end)
1766 end = rev * self._io.size
1776 end = rev * self._io.size
1767 else:
1777 else:
1768 end += rev * self._io.size
1778 end += rev * self._io.size
1769
1779
1770 transaction.add(self.indexfile, end)
1780 transaction.add(self.indexfile, end)
1771
1781
1772 # then reset internal state in memory to forget those revisions
1782 # then reset internal state in memory to forget those revisions
1773 self._cache = None
1783 self._cache = None
1774 self._chaininfocache = {}
1784 self._chaininfocache = {}
1775 self._chunkclear()
1785 self._chunkclear()
1776 for x in xrange(rev, len(self)):
1786 for x in xrange(rev, len(self)):
1777 del self.nodemap[self.node(x)]
1787 del self.nodemap[self.node(x)]
1778
1788
1779 del self.index[rev:-1]
1789 del self.index[rev:-1]
1780
1790
1781 def checksize(self):
1791 def checksize(self):
1782 expected = 0
1792 expected = 0
1783 if len(self):
1793 if len(self):
1784 expected = max(0, self.end(len(self) - 1))
1794 expected = max(0, self.end(len(self) - 1))
1785
1795
1786 try:
1796 try:
1787 f = self.opener(self.datafile)
1797 f = self.opener(self.datafile)
1788 f.seek(0, 2)
1798 f.seek(0, 2)
1789 actual = f.tell()
1799 actual = f.tell()
1790 f.close()
1800 f.close()
1791 dd = actual - expected
1801 dd = actual - expected
1792 except IOError as inst:
1802 except IOError as inst:
1793 if inst.errno != errno.ENOENT:
1803 if inst.errno != errno.ENOENT:
1794 raise
1804 raise
1795 dd = 0
1805 dd = 0
1796
1806
1797 try:
1807 try:
1798 f = self.opener(self.indexfile)
1808 f = self.opener(self.indexfile)
1799 f.seek(0, 2)
1809 f.seek(0, 2)
1800 actual = f.tell()
1810 actual = f.tell()
1801 f.close()
1811 f.close()
1802 s = self._io.size
1812 s = self._io.size
1803 i = max(0, actual // s)
1813 i = max(0, actual // s)
1804 di = actual - (i * s)
1814 di = actual - (i * s)
1805 if self._inline:
1815 if self._inline:
1806 databytes = 0
1816 databytes = 0
1807 for r in self:
1817 for r in self:
1808 databytes += max(0, self.length(r))
1818 databytes += max(0, self.length(r))
1809 dd = 0
1819 dd = 0
1810 di = actual - len(self) * s - databytes
1820 di = actual - len(self) * s - databytes
1811 except IOError as inst:
1821 except IOError as inst:
1812 if inst.errno != errno.ENOENT:
1822 if inst.errno != errno.ENOENT:
1813 raise
1823 raise
1814 di = 0
1824 di = 0
1815
1825
1816 return (dd, di)
1826 return (dd, di)
1817
1827
1818 def files(self):
1828 def files(self):
1819 res = [self.indexfile]
1829 res = [self.indexfile]
1820 if not self._inline:
1830 if not self._inline:
1821 res.append(self.datafile)
1831 res.append(self.datafile)
1822 return res
1832 return res
@@ -1,260 +1,260 b''
1 # unionrepo.py - repository class for viewing union of repository changesets
1 # unionrepo.py - repository class for viewing union of repository changesets
2 #
2 #
3 # Derived from bundlerepo.py
3 # Derived from bundlerepo.py
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
4 # Copyright 2006, 2007 Benoit Boissinot <bboissin@gmail.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
5 # Copyright 2013 Unity Technologies, Mads Kiilerich <madski@unity3d.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Repository class for "in-memory pull" of one local repository to another,
10 """Repository class for "in-memory pull" of one local repository to another,
11 allowing operations like diff and log with revsets.
11 allowing operations like diff and log with revsets.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18
18
19 from . import (
19 from . import (
20 changelog,
20 changelog,
21 cmdutil,
21 cmdutil,
22 error,
22 error,
23 filelog,
23 filelog,
24 localrepo,
24 localrepo,
25 manifest,
25 manifest,
26 mdiff,
26 mdiff,
27 pathutil,
27 pathutil,
28 pycompat,
28 pycompat,
29 revlog,
29 revlog,
30 scmutil,
30 scmutil,
31 util,
31 util,
32 )
32 )
33
33
34 class unionrevlog(revlog.revlog):
34 class unionrevlog(revlog.revlog):
35 def __init__(self, opener, indexfile, revlog2, linkmapper):
35 def __init__(self, opener, indexfile, revlog2, linkmapper):
36 # How it works:
36 # How it works:
37 # To retrieve a revision, we just need to know the node id so we can
37 # To retrieve a revision, we just need to know the node id so we can
38 # look it up in revlog2.
38 # look it up in revlog2.
39 #
39 #
40 # To differentiate a rev in the second revlog from a rev in the revlog,
40 # To differentiate a rev in the second revlog from a rev in the revlog,
41 # we check revision against repotiprev.
41 # we check revision against repotiprev.
42 opener = scmutil.readonlyvfs(opener)
42 opener = scmutil.readonlyvfs(opener)
43 revlog.revlog.__init__(self, opener, indexfile)
43 revlog.revlog.__init__(self, opener, indexfile)
44 self.revlog2 = revlog2
44 self.revlog2 = revlog2
45
45
46 n = len(self)
46 n = len(self)
47 self.repotiprev = n - 1
47 self.repotiprev = n - 1
48 self.bundlerevs = set() # used by 'bundle()' revset expression
48 self.bundlerevs = set() # used by 'bundle()' revset expression
49 for rev2 in self.revlog2:
49 for rev2 in self.revlog2:
50 rev = self.revlog2.index[rev2]
50 rev = self.revlog2.index[rev2]
51 # rev numbers - in revlog2, very different from self.rev
51 # rev numbers - in revlog2, very different from self.rev
52 _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
52 _start, _csize, _rsize, base, linkrev, p1rev, p2rev, node = rev
53 flags = _start & 0xFFFF
53 flags = _start & 0xFFFF
54
54
55 if linkmapper is None: # link is to same revlog
55 if linkmapper is None: # link is to same revlog
56 assert linkrev == rev2 # we never link back
56 assert linkrev == rev2 # we never link back
57 link = n
57 link = n
58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
58 else: # rev must be mapped from repo2 cl to unified cl by linkmapper
59 link = linkmapper(linkrev)
59 link = linkmapper(linkrev)
60
60
61 if linkmapper is not None: # link is to same revlog
61 if linkmapper is not None: # link is to same revlog
62 base = linkmapper(base)
62 base = linkmapper(base)
63
63
64 if node in self.nodemap:
64 if node in self.nodemap:
65 # this happens for the common revlog revisions
65 # this happens for the common revlog revisions
66 self.bundlerevs.add(self.nodemap[node])
66 self.bundlerevs.add(self.nodemap[node])
67 continue
67 continue
68
68
69 p1node = self.revlog2.node(p1rev)
69 p1node = self.revlog2.node(p1rev)
70 p2node = self.revlog2.node(p2rev)
70 p2node = self.revlog2.node(p2rev)
71
71
72 e = (flags, None, None, base,
72 e = (flags, None, None, base,
73 link, self.rev(p1node), self.rev(p2node), node)
73 link, self.rev(p1node), self.rev(p2node), node)
74 self.index.insert(-1, e)
74 self.index.insert(-1, e)
75 self.nodemap[node] = n
75 self.nodemap[node] = n
76 self.bundlerevs.add(n)
76 self.bundlerevs.add(n)
77 n += 1
77 n += 1
78
78
79 def _chunk(self, rev):
79 def _chunk(self, rev):
80 if rev <= self.repotiprev:
80 if rev <= self.repotiprev:
81 return revlog.revlog._chunk(self, rev)
81 return revlog.revlog._chunk(self, rev)
82 return self.revlog2._chunk(self.node(rev))
82 return self.revlog2._chunk(self.node(rev))
83
83
84 def revdiff(self, rev1, rev2):
84 def revdiff(self, rev1, rev2):
85 """return or calculate a delta between two revisions"""
85 """return or calculate a delta between two revisions"""
86 if rev1 > self.repotiprev and rev2 > self.repotiprev:
86 if rev1 > self.repotiprev and rev2 > self.repotiprev:
87 return self.revlog2.revdiff(
87 return self.revlog2.revdiff(
88 self.revlog2.rev(self.node(rev1)),
88 self.revlog2.rev(self.node(rev1)),
89 self.revlog2.rev(self.node(rev2)))
89 self.revlog2.rev(self.node(rev2)))
90 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
90 elif rev1 <= self.repotiprev and rev2 <= self.repotiprev:
91 return self.baserevdiff(rev1, rev2)
91 return self.baserevdiff(rev1, rev2)
92
92
93 return mdiff.textdiff(self.revision(self.node(rev1)),
93 return mdiff.textdiff(self.revision(self.node(rev1)),
94 self.revision(self.node(rev2)))
94 self.revision(self.node(rev2)))
95
95
96 def revision(self, nodeorrev):
96 def revision(self, nodeorrev, raw=False):
97 """return an uncompressed revision of a given node or revision
97 """return an uncompressed revision of a given node or revision
98 number.
98 number.
99 """
99 """
100 if isinstance(nodeorrev, int):
100 if isinstance(nodeorrev, int):
101 rev = nodeorrev
101 rev = nodeorrev
102 node = self.node(rev)
102 node = self.node(rev)
103 else:
103 else:
104 node = nodeorrev
104 node = nodeorrev
105 rev = self.rev(node)
105 rev = self.rev(node)
106
106
107 if node == nullid:
107 if node == nullid:
108 return ""
108 return ""
109
109
110 if rev > self.repotiprev:
110 if rev > self.repotiprev:
111 text = self.revlog2.revision(node)
111 text = self.revlog2.revision(node)
112 self._cache = (node, rev, text)
112 self._cache = (node, rev, text)
113 else:
113 else:
114 text = self.baserevision(rev)
114 text = self.baserevision(rev)
115 # already cached
115 # already cached
116 return text
116 return text
117
117
118 def baserevision(self, nodeorrev):
118 def baserevision(self, nodeorrev):
119 # Revlog subclasses may override 'revision' method to modify format of
119 # Revlog subclasses may override 'revision' method to modify format of
120 # content retrieved from revlog. To use unionrevlog with such class one
120 # content retrieved from revlog. To use unionrevlog with such class one
121 # needs to override 'baserevision' and make more specific call here.
121 # needs to override 'baserevision' and make more specific call here.
122 return revlog.revlog.revision(self, nodeorrev)
122 return revlog.revlog.revision(self, nodeorrev)
123
123
124 def baserevdiff(self, rev1, rev2):
124 def baserevdiff(self, rev1, rev2):
125 # Exists for the same purpose as baserevision.
125 # Exists for the same purpose as baserevision.
126 return revlog.revlog.revdiff(self, rev1, rev2)
126 return revlog.revlog.revdiff(self, rev1, rev2)
127
127
128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
128 def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
129 raise NotImplementedError
129 raise NotImplementedError
130 def addgroup(self, revs, linkmapper, transaction):
130 def addgroup(self, revs, linkmapper, transaction):
131 raise NotImplementedError
131 raise NotImplementedError
132 def strip(self, rev, minlink):
132 def strip(self, rev, minlink):
133 raise NotImplementedError
133 raise NotImplementedError
134 def checksize(self):
134 def checksize(self):
135 raise NotImplementedError
135 raise NotImplementedError
136
136
137 class unionchangelog(unionrevlog, changelog.changelog):
137 class unionchangelog(unionrevlog, changelog.changelog):
138 def __init__(self, opener, opener2):
138 def __init__(self, opener, opener2):
139 changelog.changelog.__init__(self, opener)
139 changelog.changelog.__init__(self, opener)
140 linkmapper = None
140 linkmapper = None
141 changelog2 = changelog.changelog(opener2)
141 changelog2 = changelog.changelog(opener2)
142 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
142 unionrevlog.__init__(self, opener, self.indexfile, changelog2,
143 linkmapper)
143 linkmapper)
144
144
145 def baserevision(self, nodeorrev):
145 def baserevision(self, nodeorrev):
146 # Although changelog doesn't override 'revision' method, some extensions
146 # Although changelog doesn't override 'revision' method, some extensions
147 # may replace this class with another that does. Same story with
147 # may replace this class with another that does. Same story with
148 # manifest and filelog classes.
148 # manifest and filelog classes.
149 return changelog.changelog.revision(self, nodeorrev)
149 return changelog.changelog.revision(self, nodeorrev)
150
150
151 def baserevdiff(self, rev1, rev2):
151 def baserevdiff(self, rev1, rev2):
152 return changelog.changelog.revdiff(self, rev1, rev2)
152 return changelog.changelog.revdiff(self, rev1, rev2)
153
153
154 class unionmanifest(unionrevlog, manifest.manifestrevlog):
154 class unionmanifest(unionrevlog, manifest.manifestrevlog):
155 def __init__(self, opener, opener2, linkmapper):
155 def __init__(self, opener, opener2, linkmapper):
156 manifest.manifestrevlog.__init__(self, opener)
156 manifest.manifestrevlog.__init__(self, opener)
157 manifest2 = manifest.manifestrevlog(opener2)
157 manifest2 = manifest.manifestrevlog(opener2)
158 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
158 unionrevlog.__init__(self, opener, self.indexfile, manifest2,
159 linkmapper)
159 linkmapper)
160
160
161 def baserevision(self, nodeorrev):
161 def baserevision(self, nodeorrev):
162 return manifest.manifestrevlog.revision(self, nodeorrev)
162 return manifest.manifestrevlog.revision(self, nodeorrev)
163
163
164 def baserevdiff(self, rev1, rev2):
164 def baserevdiff(self, rev1, rev2):
165 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
165 return manifest.manifestrevlog.revdiff(self, rev1, rev2)
166
166
167 class unionfilelog(unionrevlog, filelog.filelog):
167 class unionfilelog(unionrevlog, filelog.filelog):
168 def __init__(self, opener, path, opener2, linkmapper, repo):
168 def __init__(self, opener, path, opener2, linkmapper, repo):
169 filelog.filelog.__init__(self, opener, path)
169 filelog.filelog.__init__(self, opener, path)
170 filelog2 = filelog.filelog(opener2, path)
170 filelog2 = filelog.filelog(opener2, path)
171 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
171 unionrevlog.__init__(self, opener, self.indexfile, filelog2,
172 linkmapper)
172 linkmapper)
173 self._repo = repo
173 self._repo = repo
174
174
175 def baserevision(self, nodeorrev):
175 def baserevision(self, nodeorrev):
176 return filelog.filelog.revision(self, nodeorrev)
176 return filelog.filelog.revision(self, nodeorrev)
177
177
178 def baserevdiff(self, rev1, rev2):
178 def baserevdiff(self, rev1, rev2):
179 return filelog.filelog.revdiff(self, rev1, rev2)
179 return filelog.filelog.revdiff(self, rev1, rev2)
180
180
181 def iscensored(self, rev):
181 def iscensored(self, rev):
182 """Check if a revision is censored."""
182 """Check if a revision is censored."""
183 if rev <= self.repotiprev:
183 if rev <= self.repotiprev:
184 return filelog.filelog.iscensored(self, rev)
184 return filelog.filelog.iscensored(self, rev)
185 node = self.node(rev)
185 node = self.node(rev)
186 return self.revlog2.iscensored(self.revlog2.rev(node))
186 return self.revlog2.iscensored(self.revlog2.rev(node))
187
187
188 class unionpeer(localrepo.localpeer):
188 class unionpeer(localrepo.localpeer):
189 def canpush(self):
189 def canpush(self):
190 return False
190 return False
191
191
192 class unionrepository(localrepo.localrepository):
192 class unionrepository(localrepo.localrepository):
193 def __init__(self, ui, path, path2):
193 def __init__(self, ui, path, path2):
194 localrepo.localrepository.__init__(self, ui, path)
194 localrepo.localrepository.__init__(self, ui, path)
195 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
195 self.ui.setconfig('phases', 'publish', False, 'unionrepo')
196
196
197 self._url = 'union:%s+%s' % (util.expandpath(path),
197 self._url = 'union:%s+%s' % (util.expandpath(path),
198 util.expandpath(path2))
198 util.expandpath(path2))
199 self.repo2 = localrepo.localrepository(ui, path2)
199 self.repo2 = localrepo.localrepository(ui, path2)
200
200
201 @localrepo.unfilteredpropertycache
201 @localrepo.unfilteredpropertycache
202 def changelog(self):
202 def changelog(self):
203 return unionchangelog(self.svfs, self.repo2.svfs)
203 return unionchangelog(self.svfs, self.repo2.svfs)
204
204
205 def _clrev(self, rev2):
205 def _clrev(self, rev2):
206 """map from repo2 changelog rev to temporary rev in self.changelog"""
206 """map from repo2 changelog rev to temporary rev in self.changelog"""
207 node = self.repo2.changelog.node(rev2)
207 node = self.repo2.changelog.node(rev2)
208 return self.changelog.rev(node)
208 return self.changelog.rev(node)
209
209
210 def _constructmanifest(self):
210 def _constructmanifest(self):
211 return unionmanifest(self.svfs, self.repo2.svfs,
211 return unionmanifest(self.svfs, self.repo2.svfs,
212 self.unfiltered()._clrev)
212 self.unfiltered()._clrev)
213
213
214 def url(self):
214 def url(self):
215 return self._url
215 return self._url
216
216
217 def file(self, f):
217 def file(self, f):
218 return unionfilelog(self.svfs, f, self.repo2.svfs,
218 return unionfilelog(self.svfs, f, self.repo2.svfs,
219 self.unfiltered()._clrev, self)
219 self.unfiltered()._clrev, self)
220
220
221 def close(self):
221 def close(self):
222 self.repo2.close()
222 self.repo2.close()
223
223
224 def cancopy(self):
224 def cancopy(self):
225 return False
225 return False
226
226
227 def peer(self):
227 def peer(self):
228 return unionpeer(self)
228 return unionpeer(self)
229
229
230 def getcwd(self):
230 def getcwd(self):
231 return pycompat.getcwd() # always outside the repo
231 return pycompat.getcwd() # always outside the repo
232
232
233 def instance(ui, path, create):
233 def instance(ui, path, create):
234 if create:
234 if create:
235 raise error.Abort(_('cannot create new union repository'))
235 raise error.Abort(_('cannot create new union repository'))
236 parentpath = ui.config("bundle", "mainreporoot", "")
236 parentpath = ui.config("bundle", "mainreporoot", "")
237 if not parentpath:
237 if not parentpath:
238 # try to find the correct path to the working directory repo
238 # try to find the correct path to the working directory repo
239 parentpath = cmdutil.findrepo(pycompat.getcwd())
239 parentpath = cmdutil.findrepo(pycompat.getcwd())
240 if parentpath is None:
240 if parentpath is None:
241 parentpath = ''
241 parentpath = ''
242 if parentpath:
242 if parentpath:
243 # Try to make the full path relative so we get a nice, short URL.
243 # Try to make the full path relative so we get a nice, short URL.
244 # In particular, we don't want temp dir names in test outputs.
244 # In particular, we don't want temp dir names in test outputs.
245 cwd = pycompat.getcwd()
245 cwd = pycompat.getcwd()
246 if parentpath == cwd:
246 if parentpath == cwd:
247 parentpath = ''
247 parentpath = ''
248 else:
248 else:
249 cwd = pathutil.normasprefix(cwd)
249 cwd = pathutil.normasprefix(cwd)
250 if parentpath.startswith(cwd):
250 if parentpath.startswith(cwd):
251 parentpath = parentpath[len(cwd):]
251 parentpath = parentpath[len(cwd):]
252 if path.startswith('union:'):
252 if path.startswith('union:'):
253 s = path.split(":", 1)[1].split("+", 1)
253 s = path.split(":", 1)[1].split("+", 1)
254 if len(s) == 1:
254 if len(s) == 1:
255 repopath, repopath2 = parentpath, s[0]
255 repopath, repopath2 = parentpath, s[0]
256 else:
256 else:
257 repopath, repopath2 = s
257 repopath, repopath2 = s
258 else:
258 else:
259 repopath, repopath2 = parentpath, path
259 repopath, repopath2 = parentpath, path
260 return unionrepository(ui, repopath, repopath2)
260 return unionrepository(ui, repopath, repopath2)
General Comments 0
You need to be logged in to leave comments. Login now