##// END OF EJS Templates
manifest: remove manifest.add and add memmfctx.write...
Durham Goode -
r30345:fa54f7ad default
parent child Browse files
Show More
@@ -1,2000 +1,2004 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import inspect
12 import inspect
13 import os
13 import os
14 import random
14 import random
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 hex,
20 hex,
21 nullid,
21 nullid,
22 short,
22 short,
23 wdirrev,
23 wdirrev,
24 )
24 )
25 from . import (
25 from . import (
26 bookmarks,
26 bookmarks,
27 branchmap,
27 branchmap,
28 bundle2,
28 bundle2,
29 changegroup,
29 changegroup,
30 changelog,
30 changelog,
31 cmdutil,
31 cmdutil,
32 context,
32 context,
33 dirstate,
33 dirstate,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 filelog,
38 filelog,
39 hook,
39 hook,
40 lock as lockmod,
40 lock as lockmod,
41 manifest,
41 manifest,
42 match as matchmod,
42 match as matchmod,
43 merge as mergemod,
43 merge as mergemod,
44 namespaces,
44 namespaces,
45 obsolete,
45 obsolete,
46 pathutil,
46 pathutil,
47 peer,
47 peer,
48 phases,
48 phases,
49 pushkey,
49 pushkey,
50 repoview,
50 repoview,
51 revset,
51 revset,
52 scmutil,
52 scmutil,
53 store,
53 store,
54 subrepo,
54 subrepo,
55 tags as tagsmod,
55 tags as tagsmod,
56 transaction,
56 transaction,
57 util,
57 util,
58 )
58 )
59
59
60 release = lockmod.release
60 release = lockmod.release
61 urlerr = util.urlerr
61 urlerr = util.urlerr
62 urlreq = util.urlreq
62 urlreq = util.urlreq
63
63
64 class repofilecache(scmutil.filecache):
64 class repofilecache(scmutil.filecache):
65 """All filecache usage on repo are done for logic that should be unfiltered
65 """All filecache usage on repo are done for logic that should be unfiltered
66 """
66 """
67
67
68 def __get__(self, repo, type=None):
68 def __get__(self, repo, type=None):
69 if repo is None:
69 if repo is None:
70 return self
70 return self
71 return super(repofilecache, self).__get__(repo.unfiltered(), type)
71 return super(repofilecache, self).__get__(repo.unfiltered(), type)
72 def __set__(self, repo, value):
72 def __set__(self, repo, value):
73 return super(repofilecache, self).__set__(repo.unfiltered(), value)
73 return super(repofilecache, self).__set__(repo.unfiltered(), value)
74 def __delete__(self, repo):
74 def __delete__(self, repo):
75 return super(repofilecache, self).__delete__(repo.unfiltered())
75 return super(repofilecache, self).__delete__(repo.unfiltered())
76
76
77 class storecache(repofilecache):
77 class storecache(repofilecache):
78 """filecache for files in the store"""
78 """filecache for files in the store"""
79 def join(self, obj, fname):
79 def join(self, obj, fname):
80 return obj.sjoin(fname)
80 return obj.sjoin(fname)
81
81
82 class unfilteredpropertycache(util.propertycache):
82 class unfilteredpropertycache(util.propertycache):
83 """propertycache that apply to unfiltered repo only"""
83 """propertycache that apply to unfiltered repo only"""
84
84
85 def __get__(self, repo, type=None):
85 def __get__(self, repo, type=None):
86 unfi = repo.unfiltered()
86 unfi = repo.unfiltered()
87 if unfi is repo:
87 if unfi is repo:
88 return super(unfilteredpropertycache, self).__get__(unfi)
88 return super(unfilteredpropertycache, self).__get__(unfi)
89 return getattr(unfi, self.name)
89 return getattr(unfi, self.name)
90
90
91 class filteredpropertycache(util.propertycache):
91 class filteredpropertycache(util.propertycache):
92 """propertycache that must take filtering in account"""
92 """propertycache that must take filtering in account"""
93
93
94 def cachevalue(self, obj, value):
94 def cachevalue(self, obj, value):
95 object.__setattr__(obj, self.name, value)
95 object.__setattr__(obj, self.name, value)
96
96
97
97
98 def hasunfilteredcache(repo, name):
98 def hasunfilteredcache(repo, name):
99 """check if a repo has an unfilteredpropertycache value for <name>"""
99 """check if a repo has an unfilteredpropertycache value for <name>"""
100 return name in vars(repo.unfiltered())
100 return name in vars(repo.unfiltered())
101
101
102 def unfilteredmethod(orig):
102 def unfilteredmethod(orig):
103 """decorate method that always need to be run on unfiltered version"""
103 """decorate method that always need to be run on unfiltered version"""
104 def wrapper(repo, *args, **kwargs):
104 def wrapper(repo, *args, **kwargs):
105 return orig(repo.unfiltered(), *args, **kwargs)
105 return orig(repo.unfiltered(), *args, **kwargs)
106 return wrapper
106 return wrapper
107
107
108 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
108 moderncaps = set(('lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
109 'unbundle'))
109 'unbundle'))
110 legacycaps = moderncaps.union(set(['changegroupsubset']))
110 legacycaps = moderncaps.union(set(['changegroupsubset']))
111
111
112 class localpeer(peer.peerrepository):
112 class localpeer(peer.peerrepository):
113 '''peer for a local repo; reflects only the most recent API'''
113 '''peer for a local repo; reflects only the most recent API'''
114
114
115 def __init__(self, repo, caps=moderncaps):
115 def __init__(self, repo, caps=moderncaps):
116 peer.peerrepository.__init__(self)
116 peer.peerrepository.__init__(self)
117 self._repo = repo.filtered('served')
117 self._repo = repo.filtered('served')
118 self.ui = repo.ui
118 self.ui = repo.ui
119 self._caps = repo._restrictcapabilities(caps)
119 self._caps = repo._restrictcapabilities(caps)
120 self.requirements = repo.requirements
120 self.requirements = repo.requirements
121 self.supportedformats = repo.supportedformats
121 self.supportedformats = repo.supportedformats
122
122
123 def close(self):
123 def close(self):
124 self._repo.close()
124 self._repo.close()
125
125
126 def _capabilities(self):
126 def _capabilities(self):
127 return self._caps
127 return self._caps
128
128
129 def local(self):
129 def local(self):
130 return self._repo
130 return self._repo
131
131
132 def canpush(self):
132 def canpush(self):
133 return True
133 return True
134
134
135 def url(self):
135 def url(self):
136 return self._repo.url()
136 return self._repo.url()
137
137
138 def lookup(self, key):
138 def lookup(self, key):
139 return self._repo.lookup(key)
139 return self._repo.lookup(key)
140
140
141 def branchmap(self):
141 def branchmap(self):
142 return self._repo.branchmap()
142 return self._repo.branchmap()
143
143
144 def heads(self):
144 def heads(self):
145 return self._repo.heads()
145 return self._repo.heads()
146
146
147 def known(self, nodes):
147 def known(self, nodes):
148 return self._repo.known(nodes)
148 return self._repo.known(nodes)
149
149
150 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
150 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
151 **kwargs):
151 **kwargs):
152 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
152 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
153 common=common, bundlecaps=bundlecaps,
153 common=common, bundlecaps=bundlecaps,
154 **kwargs)
154 **kwargs)
155 cb = util.chunkbuffer(chunks)
155 cb = util.chunkbuffer(chunks)
156
156
157 if bundlecaps is not None and 'HG20' in bundlecaps:
157 if bundlecaps is not None and 'HG20' in bundlecaps:
158 # When requesting a bundle2, getbundle returns a stream to make the
158 # When requesting a bundle2, getbundle returns a stream to make the
159 # wire level function happier. We need to build a proper object
159 # wire level function happier. We need to build a proper object
160 # from it in local peer.
160 # from it in local peer.
161 return bundle2.getunbundler(self.ui, cb)
161 return bundle2.getunbundler(self.ui, cb)
162 else:
162 else:
163 return changegroup.getunbundler('01', cb, None)
163 return changegroup.getunbundler('01', cb, None)
164
164
165 # TODO We might want to move the next two calls into legacypeer and add
165 # TODO We might want to move the next two calls into legacypeer and add
166 # unbundle instead.
166 # unbundle instead.
167
167
168 def unbundle(self, cg, heads, url):
168 def unbundle(self, cg, heads, url):
169 """apply a bundle on a repo
169 """apply a bundle on a repo
170
170
171 This function handles the repo locking itself."""
171 This function handles the repo locking itself."""
172 try:
172 try:
173 try:
173 try:
174 cg = exchange.readbundle(self.ui, cg, None)
174 cg = exchange.readbundle(self.ui, cg, None)
175 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
175 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
176 if util.safehasattr(ret, 'getchunks'):
176 if util.safehasattr(ret, 'getchunks'):
177 # This is a bundle20 object, turn it into an unbundler.
177 # This is a bundle20 object, turn it into an unbundler.
178 # This little dance should be dropped eventually when the
178 # This little dance should be dropped eventually when the
179 # API is finally improved.
179 # API is finally improved.
180 stream = util.chunkbuffer(ret.getchunks())
180 stream = util.chunkbuffer(ret.getchunks())
181 ret = bundle2.getunbundler(self.ui, stream)
181 ret = bundle2.getunbundler(self.ui, stream)
182 return ret
182 return ret
183 except Exception as exc:
183 except Exception as exc:
184 # If the exception contains output salvaged from a bundle2
184 # If the exception contains output salvaged from a bundle2
185 # reply, we need to make sure it is printed before continuing
185 # reply, we need to make sure it is printed before continuing
186 # to fail. So we build a bundle2 with such output and consume
186 # to fail. So we build a bundle2 with such output and consume
187 # it directly.
187 # it directly.
188 #
188 #
189 # This is not very elegant but allows a "simple" solution for
189 # This is not very elegant but allows a "simple" solution for
190 # issue4594
190 # issue4594
191 output = getattr(exc, '_bundle2salvagedoutput', ())
191 output = getattr(exc, '_bundle2salvagedoutput', ())
192 if output:
192 if output:
193 bundler = bundle2.bundle20(self._repo.ui)
193 bundler = bundle2.bundle20(self._repo.ui)
194 for out in output:
194 for out in output:
195 bundler.addpart(out)
195 bundler.addpart(out)
196 stream = util.chunkbuffer(bundler.getchunks())
196 stream = util.chunkbuffer(bundler.getchunks())
197 b = bundle2.getunbundler(self.ui, stream)
197 b = bundle2.getunbundler(self.ui, stream)
198 bundle2.processbundle(self._repo, b)
198 bundle2.processbundle(self._repo, b)
199 raise
199 raise
200 except error.PushRaced as exc:
200 except error.PushRaced as exc:
201 raise error.ResponseError(_('push failed:'), str(exc))
201 raise error.ResponseError(_('push failed:'), str(exc))
202
202
203 def lock(self):
203 def lock(self):
204 return self._repo.lock()
204 return self._repo.lock()
205
205
206 def addchangegroup(self, cg, source, url):
206 def addchangegroup(self, cg, source, url):
207 return cg.apply(self._repo, source, url)
207 return cg.apply(self._repo, source, url)
208
208
209 def pushkey(self, namespace, key, old, new):
209 def pushkey(self, namespace, key, old, new):
210 return self._repo.pushkey(namespace, key, old, new)
210 return self._repo.pushkey(namespace, key, old, new)
211
211
212 def listkeys(self, namespace):
212 def listkeys(self, namespace):
213 return self._repo.listkeys(namespace)
213 return self._repo.listkeys(namespace)
214
214
215 def debugwireargs(self, one, two, three=None, four=None, five=None):
215 def debugwireargs(self, one, two, three=None, four=None, five=None):
216 '''used to test argument passing over the wire'''
216 '''used to test argument passing over the wire'''
217 return "%s %s %s %s %s" % (one, two, three, four, five)
217 return "%s %s %s %s %s" % (one, two, three, four, five)
218
218
219 class locallegacypeer(localpeer):
219 class locallegacypeer(localpeer):
220 '''peer extension which implements legacy methods too; used for tests with
220 '''peer extension which implements legacy methods too; used for tests with
221 restricted capabilities'''
221 restricted capabilities'''
222
222
223 def __init__(self, repo):
223 def __init__(self, repo):
224 localpeer.__init__(self, repo, caps=legacycaps)
224 localpeer.__init__(self, repo, caps=legacycaps)
225
225
226 def branches(self, nodes):
226 def branches(self, nodes):
227 return self._repo.branches(nodes)
227 return self._repo.branches(nodes)
228
228
229 def between(self, pairs):
229 def between(self, pairs):
230 return self._repo.between(pairs)
230 return self._repo.between(pairs)
231
231
232 def changegroup(self, basenodes, source):
232 def changegroup(self, basenodes, source):
233 return changegroup.changegroup(self._repo, basenodes, source)
233 return changegroup.changegroup(self._repo, basenodes, source)
234
234
235 def changegroupsubset(self, bases, heads, source):
235 def changegroupsubset(self, bases, heads, source):
236 return changegroup.changegroupsubset(self._repo, bases, heads, source)
236 return changegroup.changegroupsubset(self._repo, bases, heads, source)
237
237
238 class localrepository(object):
238 class localrepository(object):
239
239
240 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
240 supportedformats = set(('revlogv1', 'generaldelta', 'treemanifest',
241 'manifestv2'))
241 'manifestv2'))
242 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
242 _basesupported = supportedformats | set(('store', 'fncache', 'shared',
243 'dotencode'))
243 'dotencode'))
244 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
244 openerreqs = set(('revlogv1', 'generaldelta', 'treemanifest', 'manifestv2'))
245 filtername = None
245 filtername = None
246
246
247 # a list of (ui, featureset) functions.
247 # a list of (ui, featureset) functions.
248 # only functions defined in module of enabled extensions are invoked
248 # only functions defined in module of enabled extensions are invoked
249 featuresetupfuncs = set()
249 featuresetupfuncs = set()
250
250
251 def __init__(self, baseui, path=None, create=False):
251 def __init__(self, baseui, path=None, create=False):
252 self.requirements = set()
252 self.requirements = set()
253 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
253 self.wvfs = scmutil.vfs(path, expandpath=True, realpath=True)
254 self.wopener = self.wvfs
254 self.wopener = self.wvfs
255 self.root = self.wvfs.base
255 self.root = self.wvfs.base
256 self.path = self.wvfs.join(".hg")
256 self.path = self.wvfs.join(".hg")
257 self.origroot = path
257 self.origroot = path
258 self.auditor = pathutil.pathauditor(self.root, self._checknested)
258 self.auditor = pathutil.pathauditor(self.root, self._checknested)
259 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
259 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
260 realfs=False)
260 realfs=False)
261 self.vfs = scmutil.vfs(self.path)
261 self.vfs = scmutil.vfs(self.path)
262 self.opener = self.vfs
262 self.opener = self.vfs
263 self.baseui = baseui
263 self.baseui = baseui
264 self.ui = baseui.copy()
264 self.ui = baseui.copy()
265 self.ui.copy = baseui.copy # prevent copying repo configuration
265 self.ui.copy = baseui.copy # prevent copying repo configuration
266 # A list of callback to shape the phase if no data were found.
266 # A list of callback to shape the phase if no data were found.
267 # Callback are in the form: func(repo, roots) --> processed root.
267 # Callback are in the form: func(repo, roots) --> processed root.
268 # This list it to be filled by extension during repo setup
268 # This list it to be filled by extension during repo setup
269 self._phasedefaults = []
269 self._phasedefaults = []
270 try:
270 try:
271 self.ui.readconfig(self.join("hgrc"), self.root)
271 self.ui.readconfig(self.join("hgrc"), self.root)
272 extensions.loadall(self.ui)
272 extensions.loadall(self.ui)
273 except IOError:
273 except IOError:
274 pass
274 pass
275
275
276 if self.featuresetupfuncs:
276 if self.featuresetupfuncs:
277 self.supported = set(self._basesupported) # use private copy
277 self.supported = set(self._basesupported) # use private copy
278 extmods = set(m.__name__ for n, m
278 extmods = set(m.__name__ for n, m
279 in extensions.extensions(self.ui))
279 in extensions.extensions(self.ui))
280 for setupfunc in self.featuresetupfuncs:
280 for setupfunc in self.featuresetupfuncs:
281 if setupfunc.__module__ in extmods:
281 if setupfunc.__module__ in extmods:
282 setupfunc(self.ui, self.supported)
282 setupfunc(self.ui, self.supported)
283 else:
283 else:
284 self.supported = self._basesupported
284 self.supported = self._basesupported
285
285
286 if not self.vfs.isdir():
286 if not self.vfs.isdir():
287 if create:
287 if create:
288 self.requirements = newreporequirements(self)
288 self.requirements = newreporequirements(self)
289
289
290 if not self.wvfs.exists():
290 if not self.wvfs.exists():
291 self.wvfs.makedirs()
291 self.wvfs.makedirs()
292 self.vfs.makedir(notindexed=True)
292 self.vfs.makedir(notindexed=True)
293
293
294 if 'store' in self.requirements:
294 if 'store' in self.requirements:
295 self.vfs.mkdir("store")
295 self.vfs.mkdir("store")
296
296
297 # create an invalid changelog
297 # create an invalid changelog
298 self.vfs.append(
298 self.vfs.append(
299 "00changelog.i",
299 "00changelog.i",
300 '\0\0\0\2' # represents revlogv2
300 '\0\0\0\2' # represents revlogv2
301 ' dummy changelog to prevent using the old repo layout'
301 ' dummy changelog to prevent using the old repo layout'
302 )
302 )
303 else:
303 else:
304 raise error.RepoError(_("repository %s not found") % path)
304 raise error.RepoError(_("repository %s not found") % path)
305 elif create:
305 elif create:
306 raise error.RepoError(_("repository %s already exists") % path)
306 raise error.RepoError(_("repository %s already exists") % path)
307 else:
307 else:
308 try:
308 try:
309 self.requirements = scmutil.readrequires(
309 self.requirements = scmutil.readrequires(
310 self.vfs, self.supported)
310 self.vfs, self.supported)
311 except IOError as inst:
311 except IOError as inst:
312 if inst.errno != errno.ENOENT:
312 if inst.errno != errno.ENOENT:
313 raise
313 raise
314
314
315 self.sharedpath = self.path
315 self.sharedpath = self.path
316 try:
316 try:
317 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
317 vfs = scmutil.vfs(self.vfs.read("sharedpath").rstrip('\n'),
318 realpath=True)
318 realpath=True)
319 s = vfs.base
319 s = vfs.base
320 if not vfs.exists():
320 if not vfs.exists():
321 raise error.RepoError(
321 raise error.RepoError(
322 _('.hg/sharedpath points to nonexistent directory %s') % s)
322 _('.hg/sharedpath points to nonexistent directory %s') % s)
323 self.sharedpath = s
323 self.sharedpath = s
324 except IOError as inst:
324 except IOError as inst:
325 if inst.errno != errno.ENOENT:
325 if inst.errno != errno.ENOENT:
326 raise
326 raise
327
327
328 self.store = store.store(
328 self.store = store.store(
329 self.requirements, self.sharedpath, scmutil.vfs)
329 self.requirements, self.sharedpath, scmutil.vfs)
330 self.spath = self.store.path
330 self.spath = self.store.path
331 self.svfs = self.store.vfs
331 self.svfs = self.store.vfs
332 self.sjoin = self.store.join
332 self.sjoin = self.store.join
333 self.vfs.createmode = self.store.createmode
333 self.vfs.createmode = self.store.createmode
334 self._applyopenerreqs()
334 self._applyopenerreqs()
335 if create:
335 if create:
336 self._writerequirements()
336 self._writerequirements()
337
337
338 self._dirstatevalidatewarned = False
338 self._dirstatevalidatewarned = False
339
339
340 self._branchcaches = {}
340 self._branchcaches = {}
341 self._revbranchcache = None
341 self._revbranchcache = None
342 self.filterpats = {}
342 self.filterpats = {}
343 self._datafilters = {}
343 self._datafilters = {}
344 self._transref = self._lockref = self._wlockref = None
344 self._transref = self._lockref = self._wlockref = None
345
345
346 # A cache for various files under .hg/ that tracks file changes,
346 # A cache for various files under .hg/ that tracks file changes,
347 # (used by the filecache decorator)
347 # (used by the filecache decorator)
348 #
348 #
349 # Maps a property name to its util.filecacheentry
349 # Maps a property name to its util.filecacheentry
350 self._filecache = {}
350 self._filecache = {}
351
351
352 # hold sets of revision to be filtered
352 # hold sets of revision to be filtered
353 # should be cleared when something might have changed the filter value:
353 # should be cleared when something might have changed the filter value:
354 # - new changesets,
354 # - new changesets,
355 # - phase change,
355 # - phase change,
356 # - new obsolescence marker,
356 # - new obsolescence marker,
357 # - working directory parent change,
357 # - working directory parent change,
358 # - bookmark changes
358 # - bookmark changes
359 self.filteredrevcache = {}
359 self.filteredrevcache = {}
360
360
361 # generic mapping between names and nodes
361 # generic mapping between names and nodes
362 self.names = namespaces.namespaces()
362 self.names = namespaces.namespaces()
363
363
364 def close(self):
364 def close(self):
365 self._writecaches()
365 self._writecaches()
366
366
367 def _writecaches(self):
367 def _writecaches(self):
368 if self._revbranchcache:
368 if self._revbranchcache:
369 self._revbranchcache.write()
369 self._revbranchcache.write()
370
370
371 def _restrictcapabilities(self, caps):
371 def _restrictcapabilities(self, caps):
372 if self.ui.configbool('experimental', 'bundle2-advertise', True):
372 if self.ui.configbool('experimental', 'bundle2-advertise', True):
373 caps = set(caps)
373 caps = set(caps)
374 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
374 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
375 caps.add('bundle2=' + urlreq.quote(capsblob))
375 caps.add('bundle2=' + urlreq.quote(capsblob))
376 return caps
376 return caps
377
377
378 def _applyopenerreqs(self):
378 def _applyopenerreqs(self):
379 self.svfs.options = dict((r, 1) for r in self.requirements
379 self.svfs.options = dict((r, 1) for r in self.requirements
380 if r in self.openerreqs)
380 if r in self.openerreqs)
381 # experimental config: format.chunkcachesize
381 # experimental config: format.chunkcachesize
382 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
382 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
383 if chunkcachesize is not None:
383 if chunkcachesize is not None:
384 self.svfs.options['chunkcachesize'] = chunkcachesize
384 self.svfs.options['chunkcachesize'] = chunkcachesize
385 # experimental config: format.maxchainlen
385 # experimental config: format.maxchainlen
386 maxchainlen = self.ui.configint('format', 'maxchainlen')
386 maxchainlen = self.ui.configint('format', 'maxchainlen')
387 if maxchainlen is not None:
387 if maxchainlen is not None:
388 self.svfs.options['maxchainlen'] = maxchainlen
388 self.svfs.options['maxchainlen'] = maxchainlen
389 # experimental config: format.manifestcachesize
389 # experimental config: format.manifestcachesize
390 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
390 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
391 if manifestcachesize is not None:
391 if manifestcachesize is not None:
392 self.svfs.options['manifestcachesize'] = manifestcachesize
392 self.svfs.options['manifestcachesize'] = manifestcachesize
393 # experimental config: format.aggressivemergedeltas
393 # experimental config: format.aggressivemergedeltas
394 aggressivemergedeltas = self.ui.configbool('format',
394 aggressivemergedeltas = self.ui.configbool('format',
395 'aggressivemergedeltas', False)
395 'aggressivemergedeltas', False)
396 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
396 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
397 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
397 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
398
398
399 def _writerequirements(self):
399 def _writerequirements(self):
400 scmutil.writerequires(self.vfs, self.requirements)
400 scmutil.writerequires(self.vfs, self.requirements)
401
401
402 def _checknested(self, path):
402 def _checknested(self, path):
403 """Determine if path is a legal nested repository."""
403 """Determine if path is a legal nested repository."""
404 if not path.startswith(self.root):
404 if not path.startswith(self.root):
405 return False
405 return False
406 subpath = path[len(self.root) + 1:]
406 subpath = path[len(self.root) + 1:]
407 normsubpath = util.pconvert(subpath)
407 normsubpath = util.pconvert(subpath)
408
408
409 # XXX: Checking against the current working copy is wrong in
409 # XXX: Checking against the current working copy is wrong in
410 # the sense that it can reject things like
410 # the sense that it can reject things like
411 #
411 #
412 # $ hg cat -r 10 sub/x.txt
412 # $ hg cat -r 10 sub/x.txt
413 #
413 #
414 # if sub/ is no longer a subrepository in the working copy
414 # if sub/ is no longer a subrepository in the working copy
415 # parent revision.
415 # parent revision.
416 #
416 #
417 # However, it can of course also allow things that would have
417 # However, it can of course also allow things that would have
418 # been rejected before, such as the above cat command if sub/
418 # been rejected before, such as the above cat command if sub/
419 # is a subrepository now, but was a normal directory before.
419 # is a subrepository now, but was a normal directory before.
420 # The old path auditor would have rejected by mistake since it
420 # The old path auditor would have rejected by mistake since it
421 # panics when it sees sub/.hg/.
421 # panics when it sees sub/.hg/.
422 #
422 #
423 # All in all, checking against the working copy seems sensible
423 # All in all, checking against the working copy seems sensible
424 # since we want to prevent access to nested repositories on
424 # since we want to prevent access to nested repositories on
425 # the filesystem *now*.
425 # the filesystem *now*.
426 ctx = self[None]
426 ctx = self[None]
427 parts = util.splitpath(subpath)
427 parts = util.splitpath(subpath)
428 while parts:
428 while parts:
429 prefix = '/'.join(parts)
429 prefix = '/'.join(parts)
430 if prefix in ctx.substate:
430 if prefix in ctx.substate:
431 if prefix == normsubpath:
431 if prefix == normsubpath:
432 return True
432 return True
433 else:
433 else:
434 sub = ctx.sub(prefix)
434 sub = ctx.sub(prefix)
435 return sub.checknested(subpath[len(prefix) + 1:])
435 return sub.checknested(subpath[len(prefix) + 1:])
436 else:
436 else:
437 parts.pop()
437 parts.pop()
438 return False
438 return False
439
439
440 def peer(self):
440 def peer(self):
441 return localpeer(self) # not cached to avoid reference cycle
441 return localpeer(self) # not cached to avoid reference cycle
442
442
443 def unfiltered(self):
443 def unfiltered(self):
444 """Return unfiltered version of the repository
444 """Return unfiltered version of the repository
445
445
446 Intended to be overwritten by filtered repo."""
446 Intended to be overwritten by filtered repo."""
447 return self
447 return self
448
448
449 def filtered(self, name):
449 def filtered(self, name):
450 """Return a filtered version of a repository"""
450 """Return a filtered version of a repository"""
451 # build a new class with the mixin and the current class
451 # build a new class with the mixin and the current class
452 # (possibly subclass of the repo)
452 # (possibly subclass of the repo)
453 class proxycls(repoview.repoview, self.unfiltered().__class__):
453 class proxycls(repoview.repoview, self.unfiltered().__class__):
454 pass
454 pass
455 return proxycls(self, name)
455 return proxycls(self, name)
456
456
457 @repofilecache('bookmarks', 'bookmarks.current')
457 @repofilecache('bookmarks', 'bookmarks.current')
458 def _bookmarks(self):
458 def _bookmarks(self):
459 return bookmarks.bmstore(self)
459 return bookmarks.bmstore(self)
460
460
461 @property
461 @property
462 def _activebookmark(self):
462 def _activebookmark(self):
463 return self._bookmarks.active
463 return self._bookmarks.active
464
464
465 def bookmarkheads(self, bookmark):
465 def bookmarkheads(self, bookmark):
466 name = bookmark.split('@', 1)[0]
466 name = bookmark.split('@', 1)[0]
467 heads = []
467 heads = []
468 for mark, n in self._bookmarks.iteritems():
468 for mark, n in self._bookmarks.iteritems():
469 if mark.split('@', 1)[0] == name:
469 if mark.split('@', 1)[0] == name:
470 heads.append(n)
470 heads.append(n)
471 return heads
471 return heads
472
472
473 # _phaserevs and _phasesets depend on changelog. what we need is to
473 # _phaserevs and _phasesets depend on changelog. what we need is to
474 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
474 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
475 # can't be easily expressed in filecache mechanism.
475 # can't be easily expressed in filecache mechanism.
476 @storecache('phaseroots', '00changelog.i')
476 @storecache('phaseroots', '00changelog.i')
477 def _phasecache(self):
477 def _phasecache(self):
478 return phases.phasecache(self, self._phasedefaults)
478 return phases.phasecache(self, self._phasedefaults)
479
479
480 @storecache('obsstore')
480 @storecache('obsstore')
481 def obsstore(self):
481 def obsstore(self):
482 # read default format for new obsstore.
482 # read default format for new obsstore.
483 # developer config: format.obsstore-version
483 # developer config: format.obsstore-version
484 defaultformat = self.ui.configint('format', 'obsstore-version', None)
484 defaultformat = self.ui.configint('format', 'obsstore-version', None)
485 # rely on obsstore class default when possible.
485 # rely on obsstore class default when possible.
486 kwargs = {}
486 kwargs = {}
487 if defaultformat is not None:
487 if defaultformat is not None:
488 kwargs['defaultformat'] = defaultformat
488 kwargs['defaultformat'] = defaultformat
489 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
489 readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
490 store = obsolete.obsstore(self.svfs, readonly=readonly,
490 store = obsolete.obsstore(self.svfs, readonly=readonly,
491 **kwargs)
491 **kwargs)
492 if store and readonly:
492 if store and readonly:
493 self.ui.warn(
493 self.ui.warn(
494 _('obsolete feature not enabled but %i markers found!\n')
494 _('obsolete feature not enabled but %i markers found!\n')
495 % len(list(store)))
495 % len(list(store)))
496 return store
496 return store
497
497
498 @storecache('00changelog.i')
498 @storecache('00changelog.i')
499 def changelog(self):
499 def changelog(self):
500 c = changelog.changelog(self.svfs)
500 c = changelog.changelog(self.svfs)
501 if 'HG_PENDING' in os.environ:
501 if 'HG_PENDING' in os.environ:
502 p = os.environ['HG_PENDING']
502 p = os.environ['HG_PENDING']
503 if p.startswith(self.root):
503 if p.startswith(self.root):
504 c.readpending('00changelog.i.a')
504 c.readpending('00changelog.i.a')
505 return c
505 return c
506
506
507 @property
507 @property
508 def manifest(self):
508 def manifest(self):
509 return self.manifestlog._oldmanifest
509 return self.manifestlog._oldmanifest
510
510
511 def _constructmanifest(self):
511 def _constructmanifest(self):
512 # This is a temporary function while we migrate from manifest to
512 # This is a temporary function while we migrate from manifest to
513 # manifestlog. It allows bundlerepo and unionrepo to intercept the
513 # manifestlog. It allows bundlerepo and unionrepo to intercept the
514 # manifest creation.
514 # manifest creation.
515 return manifest.manifest(self.svfs)
515 return manifest.manifest(self.svfs)
516
516
517 @storecache('00manifest.i')
517 @storecache('00manifest.i')
518 def manifestlog(self):
518 def manifestlog(self):
519 return manifest.manifestlog(self.svfs, self)
519 return manifest.manifestlog(self.svfs, self)
520
520
521 @repofilecache('dirstate')
521 @repofilecache('dirstate')
522 def dirstate(self):
522 def dirstate(self):
523 return dirstate.dirstate(self.vfs, self.ui, self.root,
523 return dirstate.dirstate(self.vfs, self.ui, self.root,
524 self._dirstatevalidate)
524 self._dirstatevalidate)
525
525
526 def _dirstatevalidate(self, node):
526 def _dirstatevalidate(self, node):
527 try:
527 try:
528 self.changelog.rev(node)
528 self.changelog.rev(node)
529 return node
529 return node
530 except error.LookupError:
530 except error.LookupError:
531 if not self._dirstatevalidatewarned:
531 if not self._dirstatevalidatewarned:
532 self._dirstatevalidatewarned = True
532 self._dirstatevalidatewarned = True
533 self.ui.warn(_("warning: ignoring unknown"
533 self.ui.warn(_("warning: ignoring unknown"
534 " working parent %s!\n") % short(node))
534 " working parent %s!\n") % short(node))
535 return nullid
535 return nullid
536
536
537 def __getitem__(self, changeid):
537 def __getitem__(self, changeid):
538 if changeid is None or changeid == wdirrev:
538 if changeid is None or changeid == wdirrev:
539 return context.workingctx(self)
539 return context.workingctx(self)
540 if isinstance(changeid, slice):
540 if isinstance(changeid, slice):
541 return [context.changectx(self, i)
541 return [context.changectx(self, i)
542 for i in xrange(*changeid.indices(len(self)))
542 for i in xrange(*changeid.indices(len(self)))
543 if i not in self.changelog.filteredrevs]
543 if i not in self.changelog.filteredrevs]
544 return context.changectx(self, changeid)
544 return context.changectx(self, changeid)
545
545
546 def __contains__(self, changeid):
546 def __contains__(self, changeid):
547 try:
547 try:
548 self[changeid]
548 self[changeid]
549 return True
549 return True
550 except error.RepoLookupError:
550 except error.RepoLookupError:
551 return False
551 return False
552
552
553 def __nonzero__(self):
553 def __nonzero__(self):
554 return True
554 return True
555
555
556 def __len__(self):
556 def __len__(self):
557 return len(self.changelog)
557 return len(self.changelog)
558
558
559 def __iter__(self):
559 def __iter__(self):
560 return iter(self.changelog)
560 return iter(self.changelog)
561
561
562 def revs(self, expr, *args):
562 def revs(self, expr, *args):
563 '''Find revisions matching a revset.
563 '''Find revisions matching a revset.
564
564
565 The revset is specified as a string ``expr`` that may contain
565 The revset is specified as a string ``expr`` that may contain
566 %-formatting to escape certain types. See ``revset.formatspec``.
566 %-formatting to escape certain types. See ``revset.formatspec``.
567
567
568 Revset aliases from the configuration are not expanded. To expand
568 Revset aliases from the configuration are not expanded. To expand
569 user aliases, consider calling ``scmutil.revrange()``.
569 user aliases, consider calling ``scmutil.revrange()``.
570
570
571 Returns a revset.abstractsmartset, which is a list-like interface
571 Returns a revset.abstractsmartset, which is a list-like interface
572 that contains integer revisions.
572 that contains integer revisions.
573 '''
573 '''
574 expr = revset.formatspec(expr, *args)
574 expr = revset.formatspec(expr, *args)
575 m = revset.match(None, expr)
575 m = revset.match(None, expr)
576 return m(self)
576 return m(self)
577
577
578 def set(self, expr, *args):
578 def set(self, expr, *args):
579 '''Find revisions matching a revset and emit changectx instances.
579 '''Find revisions matching a revset and emit changectx instances.
580
580
581 This is a convenience wrapper around ``revs()`` that iterates the
581 This is a convenience wrapper around ``revs()`` that iterates the
582 result and is a generator of changectx instances.
582 result and is a generator of changectx instances.
583
583
584 Revset aliases from the configuration are not expanded. To expand
584 Revset aliases from the configuration are not expanded. To expand
585 user aliases, consider calling ``scmutil.revrange()``.
585 user aliases, consider calling ``scmutil.revrange()``.
586 '''
586 '''
587 for r in self.revs(expr, *args):
587 for r in self.revs(expr, *args):
588 yield self[r]
588 yield self[r]
589
589
590 def url(self):
590 def url(self):
591 return 'file:' + self.root
591 return 'file:' + self.root
592
592
593 def hook(self, name, throw=False, **args):
593 def hook(self, name, throw=False, **args):
594 """Call a hook, passing this repo instance.
594 """Call a hook, passing this repo instance.
595
595
596 This a convenience method to aid invoking hooks. Extensions likely
596 This a convenience method to aid invoking hooks. Extensions likely
597 won't call this unless they have registered a custom hook or are
597 won't call this unless they have registered a custom hook or are
598 replacing code that is expected to call a hook.
598 replacing code that is expected to call a hook.
599 """
599 """
600 return hook.hook(self.ui, self, name, throw, **args)
600 return hook.hook(self.ui, self, name, throw, **args)
601
601
602 @unfilteredmethod
602 @unfilteredmethod
603 def _tag(self, names, node, message, local, user, date, extra=None,
603 def _tag(self, names, node, message, local, user, date, extra=None,
604 editor=False):
604 editor=False):
605 if isinstance(names, str):
605 if isinstance(names, str):
606 names = (names,)
606 names = (names,)
607
607
608 branches = self.branchmap()
608 branches = self.branchmap()
609 for name in names:
609 for name in names:
610 self.hook('pretag', throw=True, node=hex(node), tag=name,
610 self.hook('pretag', throw=True, node=hex(node), tag=name,
611 local=local)
611 local=local)
612 if name in branches:
612 if name in branches:
613 self.ui.warn(_("warning: tag %s conflicts with existing"
613 self.ui.warn(_("warning: tag %s conflicts with existing"
614 " branch name\n") % name)
614 " branch name\n") % name)
615
615
616 def writetags(fp, names, munge, prevtags):
616 def writetags(fp, names, munge, prevtags):
617 fp.seek(0, 2)
617 fp.seek(0, 2)
618 if prevtags and prevtags[-1] != '\n':
618 if prevtags and prevtags[-1] != '\n':
619 fp.write('\n')
619 fp.write('\n')
620 for name in names:
620 for name in names:
621 if munge:
621 if munge:
622 m = munge(name)
622 m = munge(name)
623 else:
623 else:
624 m = name
624 m = name
625
625
626 if (self._tagscache.tagtypes and
626 if (self._tagscache.tagtypes and
627 name in self._tagscache.tagtypes):
627 name in self._tagscache.tagtypes):
628 old = self.tags().get(name, nullid)
628 old = self.tags().get(name, nullid)
629 fp.write('%s %s\n' % (hex(old), m))
629 fp.write('%s %s\n' % (hex(old), m))
630 fp.write('%s %s\n' % (hex(node), m))
630 fp.write('%s %s\n' % (hex(node), m))
631 fp.close()
631 fp.close()
632
632
633 prevtags = ''
633 prevtags = ''
634 if local:
634 if local:
635 try:
635 try:
636 fp = self.vfs('localtags', 'r+')
636 fp = self.vfs('localtags', 'r+')
637 except IOError:
637 except IOError:
638 fp = self.vfs('localtags', 'a')
638 fp = self.vfs('localtags', 'a')
639 else:
639 else:
640 prevtags = fp.read()
640 prevtags = fp.read()
641
641
642 # local tags are stored in the current charset
642 # local tags are stored in the current charset
643 writetags(fp, names, None, prevtags)
643 writetags(fp, names, None, prevtags)
644 for name in names:
644 for name in names:
645 self.hook('tag', node=hex(node), tag=name, local=local)
645 self.hook('tag', node=hex(node), tag=name, local=local)
646 return
646 return
647
647
648 try:
648 try:
649 fp = self.wfile('.hgtags', 'rb+')
649 fp = self.wfile('.hgtags', 'rb+')
650 except IOError as e:
650 except IOError as e:
651 if e.errno != errno.ENOENT:
651 if e.errno != errno.ENOENT:
652 raise
652 raise
653 fp = self.wfile('.hgtags', 'ab')
653 fp = self.wfile('.hgtags', 'ab')
654 else:
654 else:
655 prevtags = fp.read()
655 prevtags = fp.read()
656
656
657 # committed tags are stored in UTF-8
657 # committed tags are stored in UTF-8
658 writetags(fp, names, encoding.fromlocal, prevtags)
658 writetags(fp, names, encoding.fromlocal, prevtags)
659
659
660 fp.close()
660 fp.close()
661
661
662 self.invalidatecaches()
662 self.invalidatecaches()
663
663
664 if '.hgtags' not in self.dirstate:
664 if '.hgtags' not in self.dirstate:
665 self[None].add(['.hgtags'])
665 self[None].add(['.hgtags'])
666
666
667 m = matchmod.exact(self.root, '', ['.hgtags'])
667 m = matchmod.exact(self.root, '', ['.hgtags'])
668 tagnode = self.commit(message, user, date, extra=extra, match=m,
668 tagnode = self.commit(message, user, date, extra=extra, match=m,
669 editor=editor)
669 editor=editor)
670
670
671 for name in names:
671 for name in names:
672 self.hook('tag', node=hex(node), tag=name, local=local)
672 self.hook('tag', node=hex(node), tag=name, local=local)
673
673
674 return tagnode
674 return tagnode
675
675
676 def tag(self, names, node, message, local, user, date, editor=False):
676 def tag(self, names, node, message, local, user, date, editor=False):
677 '''tag a revision with one or more symbolic names.
677 '''tag a revision with one or more symbolic names.
678
678
679 names is a list of strings or, when adding a single tag, names may be a
679 names is a list of strings or, when adding a single tag, names may be a
680 string.
680 string.
681
681
682 if local is True, the tags are stored in a per-repository file.
682 if local is True, the tags are stored in a per-repository file.
683 otherwise, they are stored in the .hgtags file, and a new
683 otherwise, they are stored in the .hgtags file, and a new
684 changeset is committed with the change.
684 changeset is committed with the change.
685
685
686 keyword arguments:
686 keyword arguments:
687
687
688 local: whether to store tags in non-version-controlled file
688 local: whether to store tags in non-version-controlled file
689 (default False)
689 (default False)
690
690
691 message: commit message to use if committing
691 message: commit message to use if committing
692
692
693 user: name of user to use if committing
693 user: name of user to use if committing
694
694
695 date: date tuple to use if committing'''
695 date: date tuple to use if committing'''
696
696
697 if not local:
697 if not local:
698 m = matchmod.exact(self.root, '', ['.hgtags'])
698 m = matchmod.exact(self.root, '', ['.hgtags'])
699 if any(self.status(match=m, unknown=True, ignored=True)):
699 if any(self.status(match=m, unknown=True, ignored=True)):
700 raise error.Abort(_('working copy of .hgtags is changed'),
700 raise error.Abort(_('working copy of .hgtags is changed'),
701 hint=_('please commit .hgtags manually'))
701 hint=_('please commit .hgtags manually'))
702
702
703 self.tags() # instantiate the cache
703 self.tags() # instantiate the cache
704 self._tag(names, node, message, local, user, date, editor=editor)
704 self._tag(names, node, message, local, user, date, editor=editor)
705
705
706 @filteredpropertycache
706 @filteredpropertycache
707 def _tagscache(self):
707 def _tagscache(self):
708 '''Returns a tagscache object that contains various tags related
708 '''Returns a tagscache object that contains various tags related
709 caches.'''
709 caches.'''
710
710
711 # This simplifies its cache management by having one decorated
711 # This simplifies its cache management by having one decorated
712 # function (this one) and the rest simply fetch things from it.
712 # function (this one) and the rest simply fetch things from it.
713 class tagscache(object):
713 class tagscache(object):
714 def __init__(self):
714 def __init__(self):
715 # These two define the set of tags for this repository. tags
715 # These two define the set of tags for this repository. tags
716 # maps tag name to node; tagtypes maps tag name to 'global' or
716 # maps tag name to node; tagtypes maps tag name to 'global' or
717 # 'local'. (Global tags are defined by .hgtags across all
717 # 'local'. (Global tags are defined by .hgtags across all
718 # heads, and local tags are defined in .hg/localtags.)
718 # heads, and local tags are defined in .hg/localtags.)
719 # They constitute the in-memory cache of tags.
719 # They constitute the in-memory cache of tags.
720 self.tags = self.tagtypes = None
720 self.tags = self.tagtypes = None
721
721
722 self.nodetagscache = self.tagslist = None
722 self.nodetagscache = self.tagslist = None
723
723
724 cache = tagscache()
724 cache = tagscache()
725 cache.tags, cache.tagtypes = self._findtags()
725 cache.tags, cache.tagtypes = self._findtags()
726
726
727 return cache
727 return cache
728
728
729 def tags(self):
729 def tags(self):
730 '''return a mapping of tag to node'''
730 '''return a mapping of tag to node'''
731 t = {}
731 t = {}
732 if self.changelog.filteredrevs:
732 if self.changelog.filteredrevs:
733 tags, tt = self._findtags()
733 tags, tt = self._findtags()
734 else:
734 else:
735 tags = self._tagscache.tags
735 tags = self._tagscache.tags
736 for k, v in tags.iteritems():
736 for k, v in tags.iteritems():
737 try:
737 try:
738 # ignore tags to unknown nodes
738 # ignore tags to unknown nodes
739 self.changelog.rev(v)
739 self.changelog.rev(v)
740 t[k] = v
740 t[k] = v
741 except (error.LookupError, ValueError):
741 except (error.LookupError, ValueError):
742 pass
742 pass
743 return t
743 return t
744
744
745 def _findtags(self):
745 def _findtags(self):
746 '''Do the hard work of finding tags. Return a pair of dicts
746 '''Do the hard work of finding tags. Return a pair of dicts
747 (tags, tagtypes) where tags maps tag name to node, and tagtypes
747 (tags, tagtypes) where tags maps tag name to node, and tagtypes
748 maps tag name to a string like \'global\' or \'local\'.
748 maps tag name to a string like \'global\' or \'local\'.
749 Subclasses or extensions are free to add their own tags, but
749 Subclasses or extensions are free to add their own tags, but
750 should be aware that the returned dicts will be retained for the
750 should be aware that the returned dicts will be retained for the
751 duration of the localrepo object.'''
751 duration of the localrepo object.'''
752
752
753 # XXX what tagtype should subclasses/extensions use? Currently
753 # XXX what tagtype should subclasses/extensions use? Currently
754 # mq and bookmarks add tags, but do not set the tagtype at all.
754 # mq and bookmarks add tags, but do not set the tagtype at all.
755 # Should each extension invent its own tag type? Should there
755 # Should each extension invent its own tag type? Should there
756 # be one tagtype for all such "virtual" tags? Or is the status
756 # be one tagtype for all such "virtual" tags? Or is the status
757 # quo fine?
757 # quo fine?
758
758
759 alltags = {} # map tag name to (node, hist)
759 alltags = {} # map tag name to (node, hist)
760 tagtypes = {}
760 tagtypes = {}
761
761
762 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
762 tagsmod.findglobaltags(self.ui, self, alltags, tagtypes)
763 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
763 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
764
764
765 # Build the return dicts. Have to re-encode tag names because
765 # Build the return dicts. Have to re-encode tag names because
766 # the tags module always uses UTF-8 (in order not to lose info
766 # the tags module always uses UTF-8 (in order not to lose info
767 # writing to the cache), but the rest of Mercurial wants them in
767 # writing to the cache), but the rest of Mercurial wants them in
768 # local encoding.
768 # local encoding.
769 tags = {}
769 tags = {}
770 for (name, (node, hist)) in alltags.iteritems():
770 for (name, (node, hist)) in alltags.iteritems():
771 if node != nullid:
771 if node != nullid:
772 tags[encoding.tolocal(name)] = node
772 tags[encoding.tolocal(name)] = node
773 tags['tip'] = self.changelog.tip()
773 tags['tip'] = self.changelog.tip()
774 tagtypes = dict([(encoding.tolocal(name), value)
774 tagtypes = dict([(encoding.tolocal(name), value)
775 for (name, value) in tagtypes.iteritems()])
775 for (name, value) in tagtypes.iteritems()])
776 return (tags, tagtypes)
776 return (tags, tagtypes)
777
777
778 def tagtype(self, tagname):
778 def tagtype(self, tagname):
779 '''
779 '''
780 return the type of the given tag. result can be:
780 return the type of the given tag. result can be:
781
781
782 'local' : a local tag
782 'local' : a local tag
783 'global' : a global tag
783 'global' : a global tag
784 None : tag does not exist
784 None : tag does not exist
785 '''
785 '''
786
786
787 return self._tagscache.tagtypes.get(tagname)
787 return self._tagscache.tagtypes.get(tagname)
788
788
789 def tagslist(self):
789 def tagslist(self):
790 '''return a list of tags ordered by revision'''
790 '''return a list of tags ordered by revision'''
791 if not self._tagscache.tagslist:
791 if not self._tagscache.tagslist:
792 l = []
792 l = []
793 for t, n in self.tags().iteritems():
793 for t, n in self.tags().iteritems():
794 l.append((self.changelog.rev(n), t, n))
794 l.append((self.changelog.rev(n), t, n))
795 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
795 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
796
796
797 return self._tagscache.tagslist
797 return self._tagscache.tagslist
798
798
799 def nodetags(self, node):
799 def nodetags(self, node):
800 '''return the tags associated with a node'''
800 '''return the tags associated with a node'''
801 if not self._tagscache.nodetagscache:
801 if not self._tagscache.nodetagscache:
802 nodetagscache = {}
802 nodetagscache = {}
803 for t, n in self._tagscache.tags.iteritems():
803 for t, n in self._tagscache.tags.iteritems():
804 nodetagscache.setdefault(n, []).append(t)
804 nodetagscache.setdefault(n, []).append(t)
805 for tags in nodetagscache.itervalues():
805 for tags in nodetagscache.itervalues():
806 tags.sort()
806 tags.sort()
807 self._tagscache.nodetagscache = nodetagscache
807 self._tagscache.nodetagscache = nodetagscache
808 return self._tagscache.nodetagscache.get(node, [])
808 return self._tagscache.nodetagscache.get(node, [])
809
809
810 def nodebookmarks(self, node):
810 def nodebookmarks(self, node):
811 """return the list of bookmarks pointing to the specified node"""
811 """return the list of bookmarks pointing to the specified node"""
812 marks = []
812 marks = []
813 for bookmark, n in self._bookmarks.iteritems():
813 for bookmark, n in self._bookmarks.iteritems():
814 if n == node:
814 if n == node:
815 marks.append(bookmark)
815 marks.append(bookmark)
816 return sorted(marks)
816 return sorted(marks)
817
817
818 def branchmap(self):
818 def branchmap(self):
819 '''returns a dictionary {branch: [branchheads]} with branchheads
819 '''returns a dictionary {branch: [branchheads]} with branchheads
820 ordered by increasing revision number'''
820 ordered by increasing revision number'''
821 branchmap.updatecache(self)
821 branchmap.updatecache(self)
822 return self._branchcaches[self.filtername]
822 return self._branchcaches[self.filtername]
823
823
824 @unfilteredmethod
824 @unfilteredmethod
825 def revbranchcache(self):
825 def revbranchcache(self):
826 if not self._revbranchcache:
826 if not self._revbranchcache:
827 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
827 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
828 return self._revbranchcache
828 return self._revbranchcache
829
829
830 def branchtip(self, branch, ignoremissing=False):
830 def branchtip(self, branch, ignoremissing=False):
831 '''return the tip node for a given branch
831 '''return the tip node for a given branch
832
832
833 If ignoremissing is True, then this method will not raise an error.
833 If ignoremissing is True, then this method will not raise an error.
834 This is helpful for callers that only expect None for a missing branch
834 This is helpful for callers that only expect None for a missing branch
835 (e.g. namespace).
835 (e.g. namespace).
836
836
837 '''
837 '''
838 try:
838 try:
839 return self.branchmap().branchtip(branch)
839 return self.branchmap().branchtip(branch)
840 except KeyError:
840 except KeyError:
841 if not ignoremissing:
841 if not ignoremissing:
842 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
842 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
843 else:
843 else:
844 pass
844 pass
845
845
846 def lookup(self, key):
846 def lookup(self, key):
847 return self[key].node()
847 return self[key].node()
848
848
849 def lookupbranch(self, key, remote=None):
849 def lookupbranch(self, key, remote=None):
850 repo = remote or self
850 repo = remote or self
851 if key in repo.branchmap():
851 if key in repo.branchmap():
852 return key
852 return key
853
853
854 repo = (remote and remote.local()) and remote or self
854 repo = (remote and remote.local()) and remote or self
855 return repo[key].branch()
855 return repo[key].branch()
856
856
857 def known(self, nodes):
857 def known(self, nodes):
858 cl = self.changelog
858 cl = self.changelog
859 nm = cl.nodemap
859 nm = cl.nodemap
860 filtered = cl.filteredrevs
860 filtered = cl.filteredrevs
861 result = []
861 result = []
862 for n in nodes:
862 for n in nodes:
863 r = nm.get(n)
863 r = nm.get(n)
864 resp = not (r is None or r in filtered)
864 resp = not (r is None or r in filtered)
865 result.append(resp)
865 result.append(resp)
866 return result
866 return result
867
867
868 def local(self):
868 def local(self):
869 return self
869 return self
870
870
871 def publishing(self):
871 def publishing(self):
872 # it's safe (and desirable) to trust the publish flag unconditionally
872 # it's safe (and desirable) to trust the publish flag unconditionally
873 # so that we don't finalize changes shared between users via ssh or nfs
873 # so that we don't finalize changes shared between users via ssh or nfs
874 return self.ui.configbool('phases', 'publish', True, untrusted=True)
874 return self.ui.configbool('phases', 'publish', True, untrusted=True)
875
875
876 def cancopy(self):
876 def cancopy(self):
877 # so statichttprepo's override of local() works
877 # so statichttprepo's override of local() works
878 if not self.local():
878 if not self.local():
879 return False
879 return False
880 if not self.publishing():
880 if not self.publishing():
881 return True
881 return True
882 # if publishing we can't copy if there is filtered content
882 # if publishing we can't copy if there is filtered content
883 return not self.filtered('visible').changelog.filteredrevs
883 return not self.filtered('visible').changelog.filteredrevs
884
884
885 def shared(self):
885 def shared(self):
886 '''the type of shared repository (None if not shared)'''
886 '''the type of shared repository (None if not shared)'''
887 if self.sharedpath != self.path:
887 if self.sharedpath != self.path:
888 return 'store'
888 return 'store'
889 return None
889 return None
890
890
891 def join(self, f, *insidef):
891 def join(self, f, *insidef):
892 return self.vfs.join(os.path.join(f, *insidef))
892 return self.vfs.join(os.path.join(f, *insidef))
893
893
894 def wjoin(self, f, *insidef):
894 def wjoin(self, f, *insidef):
895 return self.vfs.reljoin(self.root, f, *insidef)
895 return self.vfs.reljoin(self.root, f, *insidef)
896
896
897 def file(self, f):
897 def file(self, f):
898 if f[0] == '/':
898 if f[0] == '/':
899 f = f[1:]
899 f = f[1:]
900 return filelog.filelog(self.svfs, f)
900 return filelog.filelog(self.svfs, f)
901
901
902 def changectx(self, changeid):
902 def changectx(self, changeid):
903 return self[changeid]
903 return self[changeid]
904
904
905 def setparents(self, p1, p2=nullid):
905 def setparents(self, p1, p2=nullid):
906 self.dirstate.beginparentchange()
906 self.dirstate.beginparentchange()
907 copies = self.dirstate.setparents(p1, p2)
907 copies = self.dirstate.setparents(p1, p2)
908 pctx = self[p1]
908 pctx = self[p1]
909 if copies:
909 if copies:
910 # Adjust copy records, the dirstate cannot do it, it
910 # Adjust copy records, the dirstate cannot do it, it
911 # requires access to parents manifests. Preserve them
911 # requires access to parents manifests. Preserve them
912 # only for entries added to first parent.
912 # only for entries added to first parent.
913 for f in copies:
913 for f in copies:
914 if f not in pctx and copies[f] in pctx:
914 if f not in pctx and copies[f] in pctx:
915 self.dirstate.copy(copies[f], f)
915 self.dirstate.copy(copies[f], f)
916 if p2 == nullid:
916 if p2 == nullid:
917 for f, s in sorted(self.dirstate.copies().items()):
917 for f, s in sorted(self.dirstate.copies().items()):
918 if f not in pctx and s not in pctx:
918 if f not in pctx and s not in pctx:
919 self.dirstate.copy(None, f)
919 self.dirstate.copy(None, f)
920 self.dirstate.endparentchange()
920 self.dirstate.endparentchange()
921
921
922 def filectx(self, path, changeid=None, fileid=None):
922 def filectx(self, path, changeid=None, fileid=None):
923 """changeid can be a changeset revision, node, or tag.
923 """changeid can be a changeset revision, node, or tag.
924 fileid can be a file revision or node."""
924 fileid can be a file revision or node."""
925 return context.filectx(self, path, changeid, fileid)
925 return context.filectx(self, path, changeid, fileid)
926
926
927 def getcwd(self):
927 def getcwd(self):
928 return self.dirstate.getcwd()
928 return self.dirstate.getcwd()
929
929
930 def pathto(self, f, cwd=None):
930 def pathto(self, f, cwd=None):
931 return self.dirstate.pathto(f, cwd)
931 return self.dirstate.pathto(f, cwd)
932
932
933 def wfile(self, f, mode='r'):
933 def wfile(self, f, mode='r'):
934 return self.wvfs(f, mode)
934 return self.wvfs(f, mode)
935
935
936 def _link(self, f):
936 def _link(self, f):
937 return self.wvfs.islink(f)
937 return self.wvfs.islink(f)
938
938
939 def _loadfilter(self, filter):
939 def _loadfilter(self, filter):
940 if filter not in self.filterpats:
940 if filter not in self.filterpats:
941 l = []
941 l = []
942 for pat, cmd in self.ui.configitems(filter):
942 for pat, cmd in self.ui.configitems(filter):
943 if cmd == '!':
943 if cmd == '!':
944 continue
944 continue
945 mf = matchmod.match(self.root, '', [pat])
945 mf = matchmod.match(self.root, '', [pat])
946 fn = None
946 fn = None
947 params = cmd
947 params = cmd
948 for name, filterfn in self._datafilters.iteritems():
948 for name, filterfn in self._datafilters.iteritems():
949 if cmd.startswith(name):
949 if cmd.startswith(name):
950 fn = filterfn
950 fn = filterfn
951 params = cmd[len(name):].lstrip()
951 params = cmd[len(name):].lstrip()
952 break
952 break
953 if not fn:
953 if not fn:
954 fn = lambda s, c, **kwargs: util.filter(s, c)
954 fn = lambda s, c, **kwargs: util.filter(s, c)
955 # Wrap old filters not supporting keyword arguments
955 # Wrap old filters not supporting keyword arguments
956 if not inspect.getargspec(fn)[2]:
956 if not inspect.getargspec(fn)[2]:
957 oldfn = fn
957 oldfn = fn
958 fn = lambda s, c, **kwargs: oldfn(s, c)
958 fn = lambda s, c, **kwargs: oldfn(s, c)
959 l.append((mf, fn, params))
959 l.append((mf, fn, params))
960 self.filterpats[filter] = l
960 self.filterpats[filter] = l
961 return self.filterpats[filter]
961 return self.filterpats[filter]
962
962
963 def _filter(self, filterpats, filename, data):
963 def _filter(self, filterpats, filename, data):
964 for mf, fn, cmd in filterpats:
964 for mf, fn, cmd in filterpats:
965 if mf(filename):
965 if mf(filename):
966 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
966 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
967 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
967 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
968 break
968 break
969
969
970 return data
970 return data
971
971
972 @unfilteredpropertycache
972 @unfilteredpropertycache
973 def _encodefilterpats(self):
973 def _encodefilterpats(self):
974 return self._loadfilter('encode')
974 return self._loadfilter('encode')
975
975
976 @unfilteredpropertycache
976 @unfilteredpropertycache
977 def _decodefilterpats(self):
977 def _decodefilterpats(self):
978 return self._loadfilter('decode')
978 return self._loadfilter('decode')
979
979
980 def adddatafilter(self, name, filter):
980 def adddatafilter(self, name, filter):
981 self._datafilters[name] = filter
981 self._datafilters[name] = filter
982
982
983 def wread(self, filename):
983 def wread(self, filename):
984 if self._link(filename):
984 if self._link(filename):
985 data = self.wvfs.readlink(filename)
985 data = self.wvfs.readlink(filename)
986 else:
986 else:
987 data = self.wvfs.read(filename)
987 data = self.wvfs.read(filename)
988 return self._filter(self._encodefilterpats, filename, data)
988 return self._filter(self._encodefilterpats, filename, data)
989
989
990 def wwrite(self, filename, data, flags, backgroundclose=False):
990 def wwrite(self, filename, data, flags, backgroundclose=False):
991 """write ``data`` into ``filename`` in the working directory
991 """write ``data`` into ``filename`` in the working directory
992
992
993 This returns length of written (maybe decoded) data.
993 This returns length of written (maybe decoded) data.
994 """
994 """
995 data = self._filter(self._decodefilterpats, filename, data)
995 data = self._filter(self._decodefilterpats, filename, data)
996 if 'l' in flags:
996 if 'l' in flags:
997 self.wvfs.symlink(data, filename)
997 self.wvfs.symlink(data, filename)
998 else:
998 else:
999 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
999 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
1000 if 'x' in flags:
1000 if 'x' in flags:
1001 self.wvfs.setflags(filename, False, True)
1001 self.wvfs.setflags(filename, False, True)
1002 return len(data)
1002 return len(data)
1003
1003
1004 def wwritedata(self, filename, data):
1004 def wwritedata(self, filename, data):
1005 return self._filter(self._decodefilterpats, filename, data)
1005 return self._filter(self._decodefilterpats, filename, data)
1006
1006
1007 def currenttransaction(self):
1007 def currenttransaction(self):
1008 """return the current transaction or None if non exists"""
1008 """return the current transaction or None if non exists"""
1009 if self._transref:
1009 if self._transref:
1010 tr = self._transref()
1010 tr = self._transref()
1011 else:
1011 else:
1012 tr = None
1012 tr = None
1013
1013
1014 if tr and tr.running():
1014 if tr and tr.running():
1015 return tr
1015 return tr
1016 return None
1016 return None
1017
1017
1018 def transaction(self, desc, report=None):
1018 def transaction(self, desc, report=None):
1019 if (self.ui.configbool('devel', 'all-warnings')
1019 if (self.ui.configbool('devel', 'all-warnings')
1020 or self.ui.configbool('devel', 'check-locks')):
1020 or self.ui.configbool('devel', 'check-locks')):
1021 if self._currentlock(self._lockref) is None:
1021 if self._currentlock(self._lockref) is None:
1022 raise RuntimeError('programming error: transaction requires '
1022 raise RuntimeError('programming error: transaction requires '
1023 'locking')
1023 'locking')
1024 tr = self.currenttransaction()
1024 tr = self.currenttransaction()
1025 if tr is not None:
1025 if tr is not None:
1026 return tr.nest()
1026 return tr.nest()
1027
1027
1028 # abort here if the journal already exists
1028 # abort here if the journal already exists
1029 if self.svfs.exists("journal"):
1029 if self.svfs.exists("journal"):
1030 raise error.RepoError(
1030 raise error.RepoError(
1031 _("abandoned transaction found"),
1031 _("abandoned transaction found"),
1032 hint=_("run 'hg recover' to clean up transaction"))
1032 hint=_("run 'hg recover' to clean up transaction"))
1033
1033
1034 idbase = "%.40f#%f" % (random.random(), time.time())
1034 idbase = "%.40f#%f" % (random.random(), time.time())
1035 txnid = 'TXN:' + hashlib.sha1(idbase).hexdigest()
1035 txnid = 'TXN:' + hashlib.sha1(idbase).hexdigest()
1036 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1036 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1037
1037
1038 self._writejournal(desc)
1038 self._writejournal(desc)
1039 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1039 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1040 if report:
1040 if report:
1041 rp = report
1041 rp = report
1042 else:
1042 else:
1043 rp = self.ui.warn
1043 rp = self.ui.warn
1044 vfsmap = {'plain': self.vfs} # root of .hg/
1044 vfsmap = {'plain': self.vfs} # root of .hg/
1045 # we must avoid cyclic reference between repo and transaction.
1045 # we must avoid cyclic reference between repo and transaction.
1046 reporef = weakref.ref(self)
1046 reporef = weakref.ref(self)
1047 def validate(tr):
1047 def validate(tr):
1048 """will run pre-closing hooks"""
1048 """will run pre-closing hooks"""
1049 reporef().hook('pretxnclose', throw=True,
1049 reporef().hook('pretxnclose', throw=True,
1050 txnname=desc, **tr.hookargs)
1050 txnname=desc, **tr.hookargs)
1051 def releasefn(tr, success):
1051 def releasefn(tr, success):
1052 repo = reporef()
1052 repo = reporef()
1053 if success:
1053 if success:
1054 # this should be explicitly invoked here, because
1054 # this should be explicitly invoked here, because
1055 # in-memory changes aren't written out at closing
1055 # in-memory changes aren't written out at closing
1056 # transaction, if tr.addfilegenerator (via
1056 # transaction, if tr.addfilegenerator (via
1057 # dirstate.write or so) isn't invoked while
1057 # dirstate.write or so) isn't invoked while
1058 # transaction running
1058 # transaction running
1059 repo.dirstate.write(None)
1059 repo.dirstate.write(None)
1060 else:
1060 else:
1061 # discard all changes (including ones already written
1061 # discard all changes (including ones already written
1062 # out) in this transaction
1062 # out) in this transaction
1063 repo.dirstate.restorebackup(None, prefix='journal.')
1063 repo.dirstate.restorebackup(None, prefix='journal.')
1064
1064
1065 repo.invalidate(clearfilecache=True)
1065 repo.invalidate(clearfilecache=True)
1066
1066
1067 tr = transaction.transaction(rp, self.svfs, vfsmap,
1067 tr = transaction.transaction(rp, self.svfs, vfsmap,
1068 "journal",
1068 "journal",
1069 "undo",
1069 "undo",
1070 aftertrans(renames),
1070 aftertrans(renames),
1071 self.store.createmode,
1071 self.store.createmode,
1072 validator=validate,
1072 validator=validate,
1073 releasefn=releasefn)
1073 releasefn=releasefn)
1074
1074
1075 tr.hookargs['txnid'] = txnid
1075 tr.hookargs['txnid'] = txnid
1076 # note: writing the fncache only during finalize mean that the file is
1076 # note: writing the fncache only during finalize mean that the file is
1077 # outdated when running hooks. As fncache is used for streaming clone,
1077 # outdated when running hooks. As fncache is used for streaming clone,
1078 # this is not expected to break anything that happen during the hooks.
1078 # this is not expected to break anything that happen during the hooks.
1079 tr.addfinalize('flush-fncache', self.store.write)
1079 tr.addfinalize('flush-fncache', self.store.write)
1080 def txnclosehook(tr2):
1080 def txnclosehook(tr2):
1081 """To be run if transaction is successful, will schedule a hook run
1081 """To be run if transaction is successful, will schedule a hook run
1082 """
1082 """
1083 # Don't reference tr2 in hook() so we don't hold a reference.
1083 # Don't reference tr2 in hook() so we don't hold a reference.
1084 # This reduces memory consumption when there are multiple
1084 # This reduces memory consumption when there are multiple
1085 # transactions per lock. This can likely go away if issue5045
1085 # transactions per lock. This can likely go away if issue5045
1086 # fixes the function accumulation.
1086 # fixes the function accumulation.
1087 hookargs = tr2.hookargs
1087 hookargs = tr2.hookargs
1088
1088
1089 def hook():
1089 def hook():
1090 reporef().hook('txnclose', throw=False, txnname=desc,
1090 reporef().hook('txnclose', throw=False, txnname=desc,
1091 **hookargs)
1091 **hookargs)
1092 reporef()._afterlock(hook)
1092 reporef()._afterlock(hook)
1093 tr.addfinalize('txnclose-hook', txnclosehook)
1093 tr.addfinalize('txnclose-hook', txnclosehook)
1094 def txnaborthook(tr2):
1094 def txnaborthook(tr2):
1095 """To be run if transaction is aborted
1095 """To be run if transaction is aborted
1096 """
1096 """
1097 reporef().hook('txnabort', throw=False, txnname=desc,
1097 reporef().hook('txnabort', throw=False, txnname=desc,
1098 **tr2.hookargs)
1098 **tr2.hookargs)
1099 tr.addabort('txnabort-hook', txnaborthook)
1099 tr.addabort('txnabort-hook', txnaborthook)
1100 # avoid eager cache invalidation. in-memory data should be identical
1100 # avoid eager cache invalidation. in-memory data should be identical
1101 # to stored data if transaction has no error.
1101 # to stored data if transaction has no error.
1102 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1102 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1103 self._transref = weakref.ref(tr)
1103 self._transref = weakref.ref(tr)
1104 return tr
1104 return tr
1105
1105
1106 def _journalfiles(self):
1106 def _journalfiles(self):
1107 return ((self.svfs, 'journal'),
1107 return ((self.svfs, 'journal'),
1108 (self.vfs, 'journal.dirstate'),
1108 (self.vfs, 'journal.dirstate'),
1109 (self.vfs, 'journal.branch'),
1109 (self.vfs, 'journal.branch'),
1110 (self.vfs, 'journal.desc'),
1110 (self.vfs, 'journal.desc'),
1111 (self.vfs, 'journal.bookmarks'),
1111 (self.vfs, 'journal.bookmarks'),
1112 (self.svfs, 'journal.phaseroots'))
1112 (self.svfs, 'journal.phaseroots'))
1113
1113
1114 def undofiles(self):
1114 def undofiles(self):
1115 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1115 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1116
1116
1117 def _writejournal(self, desc):
1117 def _writejournal(self, desc):
1118 self.dirstate.savebackup(None, prefix='journal.')
1118 self.dirstate.savebackup(None, prefix='journal.')
1119 self.vfs.write("journal.branch",
1119 self.vfs.write("journal.branch",
1120 encoding.fromlocal(self.dirstate.branch()))
1120 encoding.fromlocal(self.dirstate.branch()))
1121 self.vfs.write("journal.desc",
1121 self.vfs.write("journal.desc",
1122 "%d\n%s\n" % (len(self), desc))
1122 "%d\n%s\n" % (len(self), desc))
1123 self.vfs.write("journal.bookmarks",
1123 self.vfs.write("journal.bookmarks",
1124 self.vfs.tryread("bookmarks"))
1124 self.vfs.tryread("bookmarks"))
1125 self.svfs.write("journal.phaseroots",
1125 self.svfs.write("journal.phaseroots",
1126 self.svfs.tryread("phaseroots"))
1126 self.svfs.tryread("phaseroots"))
1127
1127
1128 def recover(self):
1128 def recover(self):
1129 with self.lock():
1129 with self.lock():
1130 if self.svfs.exists("journal"):
1130 if self.svfs.exists("journal"):
1131 self.ui.status(_("rolling back interrupted transaction\n"))
1131 self.ui.status(_("rolling back interrupted transaction\n"))
1132 vfsmap = {'': self.svfs,
1132 vfsmap = {'': self.svfs,
1133 'plain': self.vfs,}
1133 'plain': self.vfs,}
1134 transaction.rollback(self.svfs, vfsmap, "journal",
1134 transaction.rollback(self.svfs, vfsmap, "journal",
1135 self.ui.warn)
1135 self.ui.warn)
1136 self.invalidate()
1136 self.invalidate()
1137 return True
1137 return True
1138 else:
1138 else:
1139 self.ui.warn(_("no interrupted transaction available\n"))
1139 self.ui.warn(_("no interrupted transaction available\n"))
1140 return False
1140 return False
1141
1141
1142 def rollback(self, dryrun=False, force=False):
1142 def rollback(self, dryrun=False, force=False):
1143 wlock = lock = dsguard = None
1143 wlock = lock = dsguard = None
1144 try:
1144 try:
1145 wlock = self.wlock()
1145 wlock = self.wlock()
1146 lock = self.lock()
1146 lock = self.lock()
1147 if self.svfs.exists("undo"):
1147 if self.svfs.exists("undo"):
1148 dsguard = cmdutil.dirstateguard(self, 'rollback')
1148 dsguard = cmdutil.dirstateguard(self, 'rollback')
1149
1149
1150 return self._rollback(dryrun, force, dsguard)
1150 return self._rollback(dryrun, force, dsguard)
1151 else:
1151 else:
1152 self.ui.warn(_("no rollback information available\n"))
1152 self.ui.warn(_("no rollback information available\n"))
1153 return 1
1153 return 1
1154 finally:
1154 finally:
1155 release(dsguard, lock, wlock)
1155 release(dsguard, lock, wlock)
1156
1156
1157 @unfilteredmethod # Until we get smarter cache management
1157 @unfilteredmethod # Until we get smarter cache management
1158 def _rollback(self, dryrun, force, dsguard):
1158 def _rollback(self, dryrun, force, dsguard):
1159 ui = self.ui
1159 ui = self.ui
1160 try:
1160 try:
1161 args = self.vfs.read('undo.desc').splitlines()
1161 args = self.vfs.read('undo.desc').splitlines()
1162 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1162 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1163 if len(args) >= 3:
1163 if len(args) >= 3:
1164 detail = args[2]
1164 detail = args[2]
1165 oldtip = oldlen - 1
1165 oldtip = oldlen - 1
1166
1166
1167 if detail and ui.verbose:
1167 if detail and ui.verbose:
1168 msg = (_('repository tip rolled back to revision %s'
1168 msg = (_('repository tip rolled back to revision %s'
1169 ' (undo %s: %s)\n')
1169 ' (undo %s: %s)\n')
1170 % (oldtip, desc, detail))
1170 % (oldtip, desc, detail))
1171 else:
1171 else:
1172 msg = (_('repository tip rolled back to revision %s'
1172 msg = (_('repository tip rolled back to revision %s'
1173 ' (undo %s)\n')
1173 ' (undo %s)\n')
1174 % (oldtip, desc))
1174 % (oldtip, desc))
1175 except IOError:
1175 except IOError:
1176 msg = _('rolling back unknown transaction\n')
1176 msg = _('rolling back unknown transaction\n')
1177 desc = None
1177 desc = None
1178
1178
1179 if not force and self['.'] != self['tip'] and desc == 'commit':
1179 if not force and self['.'] != self['tip'] and desc == 'commit':
1180 raise error.Abort(
1180 raise error.Abort(
1181 _('rollback of last commit while not checked out '
1181 _('rollback of last commit while not checked out '
1182 'may lose data'), hint=_('use -f to force'))
1182 'may lose data'), hint=_('use -f to force'))
1183
1183
1184 ui.status(msg)
1184 ui.status(msg)
1185 if dryrun:
1185 if dryrun:
1186 return 0
1186 return 0
1187
1187
1188 parents = self.dirstate.parents()
1188 parents = self.dirstate.parents()
1189 self.destroying()
1189 self.destroying()
1190 vfsmap = {'plain': self.vfs, '': self.svfs}
1190 vfsmap = {'plain': self.vfs, '': self.svfs}
1191 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1191 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn)
1192 if self.vfs.exists('undo.bookmarks'):
1192 if self.vfs.exists('undo.bookmarks'):
1193 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1193 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1194 if self.svfs.exists('undo.phaseroots'):
1194 if self.svfs.exists('undo.phaseroots'):
1195 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1195 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1196 self.invalidate()
1196 self.invalidate()
1197
1197
1198 parentgone = (parents[0] not in self.changelog.nodemap or
1198 parentgone = (parents[0] not in self.changelog.nodemap or
1199 parents[1] not in self.changelog.nodemap)
1199 parents[1] not in self.changelog.nodemap)
1200 if parentgone:
1200 if parentgone:
1201 # prevent dirstateguard from overwriting already restored one
1201 # prevent dirstateguard from overwriting already restored one
1202 dsguard.close()
1202 dsguard.close()
1203
1203
1204 self.dirstate.restorebackup(None, prefix='undo.')
1204 self.dirstate.restorebackup(None, prefix='undo.')
1205 try:
1205 try:
1206 branch = self.vfs.read('undo.branch')
1206 branch = self.vfs.read('undo.branch')
1207 self.dirstate.setbranch(encoding.tolocal(branch))
1207 self.dirstate.setbranch(encoding.tolocal(branch))
1208 except IOError:
1208 except IOError:
1209 ui.warn(_('named branch could not be reset: '
1209 ui.warn(_('named branch could not be reset: '
1210 'current branch is still \'%s\'\n')
1210 'current branch is still \'%s\'\n')
1211 % self.dirstate.branch())
1211 % self.dirstate.branch())
1212
1212
1213 parents = tuple([p.rev() for p in self[None].parents()])
1213 parents = tuple([p.rev() for p in self[None].parents()])
1214 if len(parents) > 1:
1214 if len(parents) > 1:
1215 ui.status(_('working directory now based on '
1215 ui.status(_('working directory now based on '
1216 'revisions %d and %d\n') % parents)
1216 'revisions %d and %d\n') % parents)
1217 else:
1217 else:
1218 ui.status(_('working directory now based on '
1218 ui.status(_('working directory now based on '
1219 'revision %d\n') % parents)
1219 'revision %d\n') % parents)
1220 mergemod.mergestate.clean(self, self['.'].node())
1220 mergemod.mergestate.clean(self, self['.'].node())
1221
1221
1222 # TODO: if we know which new heads may result from this rollback, pass
1222 # TODO: if we know which new heads may result from this rollback, pass
1223 # them to destroy(), which will prevent the branchhead cache from being
1223 # them to destroy(), which will prevent the branchhead cache from being
1224 # invalidated.
1224 # invalidated.
1225 self.destroyed()
1225 self.destroyed()
1226 return 0
1226 return 0
1227
1227
1228 def invalidatecaches(self):
1228 def invalidatecaches(self):
1229
1229
1230 if '_tagscache' in vars(self):
1230 if '_tagscache' in vars(self):
1231 # can't use delattr on proxy
1231 # can't use delattr on proxy
1232 del self.__dict__['_tagscache']
1232 del self.__dict__['_tagscache']
1233
1233
1234 self.unfiltered()._branchcaches.clear()
1234 self.unfiltered()._branchcaches.clear()
1235 self.invalidatevolatilesets()
1235 self.invalidatevolatilesets()
1236
1236
1237 def invalidatevolatilesets(self):
1237 def invalidatevolatilesets(self):
1238 self.filteredrevcache.clear()
1238 self.filteredrevcache.clear()
1239 obsolete.clearobscaches(self)
1239 obsolete.clearobscaches(self)
1240
1240
1241 def invalidatedirstate(self):
1241 def invalidatedirstate(self):
1242 '''Invalidates the dirstate, causing the next call to dirstate
1242 '''Invalidates the dirstate, causing the next call to dirstate
1243 to check if it was modified since the last time it was read,
1243 to check if it was modified since the last time it was read,
1244 rereading it if it has.
1244 rereading it if it has.
1245
1245
1246 This is different to dirstate.invalidate() that it doesn't always
1246 This is different to dirstate.invalidate() that it doesn't always
1247 rereads the dirstate. Use dirstate.invalidate() if you want to
1247 rereads the dirstate. Use dirstate.invalidate() if you want to
1248 explicitly read the dirstate again (i.e. restoring it to a previous
1248 explicitly read the dirstate again (i.e. restoring it to a previous
1249 known good state).'''
1249 known good state).'''
1250 if hasunfilteredcache(self, 'dirstate'):
1250 if hasunfilteredcache(self, 'dirstate'):
1251 for k in self.dirstate._filecache:
1251 for k in self.dirstate._filecache:
1252 try:
1252 try:
1253 delattr(self.dirstate, k)
1253 delattr(self.dirstate, k)
1254 except AttributeError:
1254 except AttributeError:
1255 pass
1255 pass
1256 delattr(self.unfiltered(), 'dirstate')
1256 delattr(self.unfiltered(), 'dirstate')
1257
1257
1258 def invalidate(self, clearfilecache=False):
1258 def invalidate(self, clearfilecache=False):
1259 '''Invalidates both store and non-store parts other than dirstate
1259 '''Invalidates both store and non-store parts other than dirstate
1260
1260
1261 If a transaction is running, invalidation of store is omitted,
1261 If a transaction is running, invalidation of store is omitted,
1262 because discarding in-memory changes might cause inconsistency
1262 because discarding in-memory changes might cause inconsistency
1263 (e.g. incomplete fncache causes unintentional failure, but
1263 (e.g. incomplete fncache causes unintentional failure, but
1264 redundant one doesn't).
1264 redundant one doesn't).
1265 '''
1265 '''
1266 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1266 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1267 for k in self._filecache.keys():
1267 for k in self._filecache.keys():
1268 # dirstate is invalidated separately in invalidatedirstate()
1268 # dirstate is invalidated separately in invalidatedirstate()
1269 if k == 'dirstate':
1269 if k == 'dirstate':
1270 continue
1270 continue
1271
1271
1272 if clearfilecache:
1272 if clearfilecache:
1273 del self._filecache[k]
1273 del self._filecache[k]
1274 try:
1274 try:
1275 delattr(unfiltered, k)
1275 delattr(unfiltered, k)
1276 except AttributeError:
1276 except AttributeError:
1277 pass
1277 pass
1278 self.invalidatecaches()
1278 self.invalidatecaches()
1279 if not self.currenttransaction():
1279 if not self.currenttransaction():
1280 # TODO: Changing contents of store outside transaction
1280 # TODO: Changing contents of store outside transaction
1281 # causes inconsistency. We should make in-memory store
1281 # causes inconsistency. We should make in-memory store
1282 # changes detectable, and abort if changed.
1282 # changes detectable, and abort if changed.
1283 self.store.invalidatecaches()
1283 self.store.invalidatecaches()
1284
1284
1285 def invalidateall(self):
1285 def invalidateall(self):
1286 '''Fully invalidates both store and non-store parts, causing the
1286 '''Fully invalidates both store and non-store parts, causing the
1287 subsequent operation to reread any outside changes.'''
1287 subsequent operation to reread any outside changes.'''
1288 # extension should hook this to invalidate its caches
1288 # extension should hook this to invalidate its caches
1289 self.invalidate()
1289 self.invalidate()
1290 self.invalidatedirstate()
1290 self.invalidatedirstate()
1291
1291
1292 @unfilteredmethod
1292 @unfilteredmethod
1293 def _refreshfilecachestats(self, tr):
1293 def _refreshfilecachestats(self, tr):
1294 """Reload stats of cached files so that they are flagged as valid"""
1294 """Reload stats of cached files so that they are flagged as valid"""
1295 for k, ce in self._filecache.items():
1295 for k, ce in self._filecache.items():
1296 if k == 'dirstate' or k not in self.__dict__:
1296 if k == 'dirstate' or k not in self.__dict__:
1297 continue
1297 continue
1298 ce.refresh()
1298 ce.refresh()
1299
1299
1300 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1300 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1301 inheritchecker=None, parentenvvar=None):
1301 inheritchecker=None, parentenvvar=None):
1302 parentlock = None
1302 parentlock = None
1303 # the contents of parentenvvar are used by the underlying lock to
1303 # the contents of parentenvvar are used by the underlying lock to
1304 # determine whether it can be inherited
1304 # determine whether it can be inherited
1305 if parentenvvar is not None:
1305 if parentenvvar is not None:
1306 parentlock = os.environ.get(parentenvvar)
1306 parentlock = os.environ.get(parentenvvar)
1307 try:
1307 try:
1308 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1308 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1309 acquirefn=acquirefn, desc=desc,
1309 acquirefn=acquirefn, desc=desc,
1310 inheritchecker=inheritchecker,
1310 inheritchecker=inheritchecker,
1311 parentlock=parentlock)
1311 parentlock=parentlock)
1312 except error.LockHeld as inst:
1312 except error.LockHeld as inst:
1313 if not wait:
1313 if not wait:
1314 raise
1314 raise
1315 # show more details for new-style locks
1315 # show more details for new-style locks
1316 if ':' in inst.locker:
1316 if ':' in inst.locker:
1317 host, pid = inst.locker.split(":", 1)
1317 host, pid = inst.locker.split(":", 1)
1318 self.ui.warn(
1318 self.ui.warn(
1319 _("waiting for lock on %s held by process %r "
1319 _("waiting for lock on %s held by process %r "
1320 "on host %r\n") % (desc, pid, host))
1320 "on host %r\n") % (desc, pid, host))
1321 else:
1321 else:
1322 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1322 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1323 (desc, inst.locker))
1323 (desc, inst.locker))
1324 # default to 600 seconds timeout
1324 # default to 600 seconds timeout
1325 l = lockmod.lock(vfs, lockname,
1325 l = lockmod.lock(vfs, lockname,
1326 int(self.ui.config("ui", "timeout", "600")),
1326 int(self.ui.config("ui", "timeout", "600")),
1327 releasefn=releasefn, acquirefn=acquirefn,
1327 releasefn=releasefn, acquirefn=acquirefn,
1328 desc=desc)
1328 desc=desc)
1329 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1329 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1330 return l
1330 return l
1331
1331
1332 def _afterlock(self, callback):
1332 def _afterlock(self, callback):
1333 """add a callback to be run when the repository is fully unlocked
1333 """add a callback to be run when the repository is fully unlocked
1334
1334
1335 The callback will be executed when the outermost lock is released
1335 The callback will be executed when the outermost lock is released
1336 (with wlock being higher level than 'lock')."""
1336 (with wlock being higher level than 'lock')."""
1337 for ref in (self._wlockref, self._lockref):
1337 for ref in (self._wlockref, self._lockref):
1338 l = ref and ref()
1338 l = ref and ref()
1339 if l and l.held:
1339 if l and l.held:
1340 l.postrelease.append(callback)
1340 l.postrelease.append(callback)
1341 break
1341 break
1342 else: # no lock have been found.
1342 else: # no lock have been found.
1343 callback()
1343 callback()
1344
1344
1345 def lock(self, wait=True):
1345 def lock(self, wait=True):
1346 '''Lock the repository store (.hg/store) and return a weak reference
1346 '''Lock the repository store (.hg/store) and return a weak reference
1347 to the lock. Use this before modifying the store (e.g. committing or
1347 to the lock. Use this before modifying the store (e.g. committing or
1348 stripping). If you are opening a transaction, get a lock as well.)
1348 stripping). If you are opening a transaction, get a lock as well.)
1349
1349
1350 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1350 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1351 'wlock' first to avoid a dead-lock hazard.'''
1351 'wlock' first to avoid a dead-lock hazard.'''
1352 l = self._currentlock(self._lockref)
1352 l = self._currentlock(self._lockref)
1353 if l is not None:
1353 if l is not None:
1354 l.lock()
1354 l.lock()
1355 return l
1355 return l
1356
1356
1357 l = self._lock(self.svfs, "lock", wait, None,
1357 l = self._lock(self.svfs, "lock", wait, None,
1358 self.invalidate, _('repository %s') % self.origroot)
1358 self.invalidate, _('repository %s') % self.origroot)
1359 self._lockref = weakref.ref(l)
1359 self._lockref = weakref.ref(l)
1360 return l
1360 return l
1361
1361
1362 def _wlockchecktransaction(self):
1362 def _wlockchecktransaction(self):
1363 if self.currenttransaction() is not None:
1363 if self.currenttransaction() is not None:
1364 raise error.LockInheritanceContractViolation(
1364 raise error.LockInheritanceContractViolation(
1365 'wlock cannot be inherited in the middle of a transaction')
1365 'wlock cannot be inherited in the middle of a transaction')
1366
1366
1367 def wlock(self, wait=True):
1367 def wlock(self, wait=True):
1368 '''Lock the non-store parts of the repository (everything under
1368 '''Lock the non-store parts of the repository (everything under
1369 .hg except .hg/store) and return a weak reference to the lock.
1369 .hg except .hg/store) and return a weak reference to the lock.
1370
1370
1371 Use this before modifying files in .hg.
1371 Use this before modifying files in .hg.
1372
1372
1373 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1373 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1374 'wlock' first to avoid a dead-lock hazard.'''
1374 'wlock' first to avoid a dead-lock hazard.'''
1375 l = self._wlockref and self._wlockref()
1375 l = self._wlockref and self._wlockref()
1376 if l is not None and l.held:
1376 if l is not None and l.held:
1377 l.lock()
1377 l.lock()
1378 return l
1378 return l
1379
1379
1380 # We do not need to check for non-waiting lock acquisition. Such
1380 # We do not need to check for non-waiting lock acquisition. Such
1381 # acquisition would not cause dead-lock as they would just fail.
1381 # acquisition would not cause dead-lock as they would just fail.
1382 if wait and (self.ui.configbool('devel', 'all-warnings')
1382 if wait and (self.ui.configbool('devel', 'all-warnings')
1383 or self.ui.configbool('devel', 'check-locks')):
1383 or self.ui.configbool('devel', 'check-locks')):
1384 if self._currentlock(self._lockref) is not None:
1384 if self._currentlock(self._lockref) is not None:
1385 self.ui.develwarn('"wlock" acquired after "lock"')
1385 self.ui.develwarn('"wlock" acquired after "lock"')
1386
1386
1387 def unlock():
1387 def unlock():
1388 if self.dirstate.pendingparentchange():
1388 if self.dirstate.pendingparentchange():
1389 self.dirstate.invalidate()
1389 self.dirstate.invalidate()
1390 else:
1390 else:
1391 self.dirstate.write(None)
1391 self.dirstate.write(None)
1392
1392
1393 self._filecache['dirstate'].refresh()
1393 self._filecache['dirstate'].refresh()
1394
1394
1395 l = self._lock(self.vfs, "wlock", wait, unlock,
1395 l = self._lock(self.vfs, "wlock", wait, unlock,
1396 self.invalidatedirstate, _('working directory of %s') %
1396 self.invalidatedirstate, _('working directory of %s') %
1397 self.origroot,
1397 self.origroot,
1398 inheritchecker=self._wlockchecktransaction,
1398 inheritchecker=self._wlockchecktransaction,
1399 parentenvvar='HG_WLOCK_LOCKER')
1399 parentenvvar='HG_WLOCK_LOCKER')
1400 self._wlockref = weakref.ref(l)
1400 self._wlockref = weakref.ref(l)
1401 return l
1401 return l
1402
1402
1403 def _currentlock(self, lockref):
1403 def _currentlock(self, lockref):
1404 """Returns the lock if it's held, or None if it's not."""
1404 """Returns the lock if it's held, or None if it's not."""
1405 if lockref is None:
1405 if lockref is None:
1406 return None
1406 return None
1407 l = lockref()
1407 l = lockref()
1408 if l is None or not l.held:
1408 if l is None or not l.held:
1409 return None
1409 return None
1410 return l
1410 return l
1411
1411
1412 def currentwlock(self):
1412 def currentwlock(self):
1413 """Returns the wlock if it's held, or None if it's not."""
1413 """Returns the wlock if it's held, or None if it's not."""
1414 return self._currentlock(self._wlockref)
1414 return self._currentlock(self._wlockref)
1415
1415
1416 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1416 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1417 """
1417 """
1418 commit an individual file as part of a larger transaction
1418 commit an individual file as part of a larger transaction
1419 """
1419 """
1420
1420
1421 fname = fctx.path()
1421 fname = fctx.path()
1422 fparent1 = manifest1.get(fname, nullid)
1422 fparent1 = manifest1.get(fname, nullid)
1423 fparent2 = manifest2.get(fname, nullid)
1423 fparent2 = manifest2.get(fname, nullid)
1424 if isinstance(fctx, context.filectx):
1424 if isinstance(fctx, context.filectx):
1425 node = fctx.filenode()
1425 node = fctx.filenode()
1426 if node in [fparent1, fparent2]:
1426 if node in [fparent1, fparent2]:
1427 self.ui.debug('reusing %s filelog entry\n' % fname)
1427 self.ui.debug('reusing %s filelog entry\n' % fname)
1428 if manifest1.flags(fname) != fctx.flags():
1428 if manifest1.flags(fname) != fctx.flags():
1429 changelist.append(fname)
1429 changelist.append(fname)
1430 return node
1430 return node
1431
1431
1432 flog = self.file(fname)
1432 flog = self.file(fname)
1433 meta = {}
1433 meta = {}
1434 copy = fctx.renamed()
1434 copy = fctx.renamed()
1435 if copy and copy[0] != fname:
1435 if copy and copy[0] != fname:
1436 # Mark the new revision of this file as a copy of another
1436 # Mark the new revision of this file as a copy of another
1437 # file. This copy data will effectively act as a parent
1437 # file. This copy data will effectively act as a parent
1438 # of this new revision. If this is a merge, the first
1438 # of this new revision. If this is a merge, the first
1439 # parent will be the nullid (meaning "look up the copy data")
1439 # parent will be the nullid (meaning "look up the copy data")
1440 # and the second one will be the other parent. For example:
1440 # and the second one will be the other parent. For example:
1441 #
1441 #
1442 # 0 --- 1 --- 3 rev1 changes file foo
1442 # 0 --- 1 --- 3 rev1 changes file foo
1443 # \ / rev2 renames foo to bar and changes it
1443 # \ / rev2 renames foo to bar and changes it
1444 # \- 2 -/ rev3 should have bar with all changes and
1444 # \- 2 -/ rev3 should have bar with all changes and
1445 # should record that bar descends from
1445 # should record that bar descends from
1446 # bar in rev2 and foo in rev1
1446 # bar in rev2 and foo in rev1
1447 #
1447 #
1448 # this allows this merge to succeed:
1448 # this allows this merge to succeed:
1449 #
1449 #
1450 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1450 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1451 # \ / merging rev3 and rev4 should use bar@rev2
1451 # \ / merging rev3 and rev4 should use bar@rev2
1452 # \- 2 --- 4 as the merge base
1452 # \- 2 --- 4 as the merge base
1453 #
1453 #
1454
1454
1455 cfname = copy[0]
1455 cfname = copy[0]
1456 crev = manifest1.get(cfname)
1456 crev = manifest1.get(cfname)
1457 newfparent = fparent2
1457 newfparent = fparent2
1458
1458
1459 if manifest2: # branch merge
1459 if manifest2: # branch merge
1460 if fparent2 == nullid or crev is None: # copied on remote side
1460 if fparent2 == nullid or crev is None: # copied on remote side
1461 if cfname in manifest2:
1461 if cfname in manifest2:
1462 crev = manifest2[cfname]
1462 crev = manifest2[cfname]
1463 newfparent = fparent1
1463 newfparent = fparent1
1464
1464
1465 # Here, we used to search backwards through history to try to find
1465 # Here, we used to search backwards through history to try to find
1466 # where the file copy came from if the source of a copy was not in
1466 # where the file copy came from if the source of a copy was not in
1467 # the parent directory. However, this doesn't actually make sense to
1467 # the parent directory. However, this doesn't actually make sense to
1468 # do (what does a copy from something not in your working copy even
1468 # do (what does a copy from something not in your working copy even
1469 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1469 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1470 # the user that copy information was dropped, so if they didn't
1470 # the user that copy information was dropped, so if they didn't
1471 # expect this outcome it can be fixed, but this is the correct
1471 # expect this outcome it can be fixed, but this is the correct
1472 # behavior in this circumstance.
1472 # behavior in this circumstance.
1473
1473
1474 if crev:
1474 if crev:
1475 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1475 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1476 meta["copy"] = cfname
1476 meta["copy"] = cfname
1477 meta["copyrev"] = hex(crev)
1477 meta["copyrev"] = hex(crev)
1478 fparent1, fparent2 = nullid, newfparent
1478 fparent1, fparent2 = nullid, newfparent
1479 else:
1479 else:
1480 self.ui.warn(_("warning: can't find ancestor for '%s' "
1480 self.ui.warn(_("warning: can't find ancestor for '%s' "
1481 "copied from '%s'!\n") % (fname, cfname))
1481 "copied from '%s'!\n") % (fname, cfname))
1482
1482
1483 elif fparent1 == nullid:
1483 elif fparent1 == nullid:
1484 fparent1, fparent2 = fparent2, nullid
1484 fparent1, fparent2 = fparent2, nullid
1485 elif fparent2 != nullid:
1485 elif fparent2 != nullid:
1486 # is one parent an ancestor of the other?
1486 # is one parent an ancestor of the other?
1487 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1487 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1488 if fparent1 in fparentancestors:
1488 if fparent1 in fparentancestors:
1489 fparent1, fparent2 = fparent2, nullid
1489 fparent1, fparent2 = fparent2, nullid
1490 elif fparent2 in fparentancestors:
1490 elif fparent2 in fparentancestors:
1491 fparent2 = nullid
1491 fparent2 = nullid
1492
1492
1493 # is the file changed?
1493 # is the file changed?
1494 text = fctx.data()
1494 text = fctx.data()
1495 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1495 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1496 changelist.append(fname)
1496 changelist.append(fname)
1497 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1497 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1498 # are just the flags changed during merge?
1498 # are just the flags changed during merge?
1499 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1499 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1500 changelist.append(fname)
1500 changelist.append(fname)
1501
1501
1502 return fparent1
1502 return fparent1
1503
1503
1504 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1504 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1505 """check for commit arguments that aren't committable"""
1505 """check for commit arguments that aren't committable"""
1506 if match.isexact() or match.prefix():
1506 if match.isexact() or match.prefix():
1507 matched = set(status.modified + status.added + status.removed)
1507 matched = set(status.modified + status.added + status.removed)
1508
1508
1509 for f in match.files():
1509 for f in match.files():
1510 f = self.dirstate.normalize(f)
1510 f = self.dirstate.normalize(f)
1511 if f == '.' or f in matched or f in wctx.substate:
1511 if f == '.' or f in matched or f in wctx.substate:
1512 continue
1512 continue
1513 if f in status.deleted:
1513 if f in status.deleted:
1514 fail(f, _('file not found!'))
1514 fail(f, _('file not found!'))
1515 if f in vdirs: # visited directory
1515 if f in vdirs: # visited directory
1516 d = f + '/'
1516 d = f + '/'
1517 for mf in matched:
1517 for mf in matched:
1518 if mf.startswith(d):
1518 if mf.startswith(d):
1519 break
1519 break
1520 else:
1520 else:
1521 fail(f, _("no match under directory!"))
1521 fail(f, _("no match under directory!"))
1522 elif f not in self.dirstate:
1522 elif f not in self.dirstate:
1523 fail(f, _("file not tracked!"))
1523 fail(f, _("file not tracked!"))
1524
1524
1525 @unfilteredmethod
1525 @unfilteredmethod
1526 def commit(self, text="", user=None, date=None, match=None, force=False,
1526 def commit(self, text="", user=None, date=None, match=None, force=False,
1527 editor=False, extra=None):
1527 editor=False, extra=None):
1528 """Add a new revision to current repository.
1528 """Add a new revision to current repository.
1529
1529
1530 Revision information is gathered from the working directory,
1530 Revision information is gathered from the working directory,
1531 match can be used to filter the committed files. If editor is
1531 match can be used to filter the committed files. If editor is
1532 supplied, it is called to get a commit message.
1532 supplied, it is called to get a commit message.
1533 """
1533 """
1534 if extra is None:
1534 if extra is None:
1535 extra = {}
1535 extra = {}
1536
1536
1537 def fail(f, msg):
1537 def fail(f, msg):
1538 raise error.Abort('%s: %s' % (f, msg))
1538 raise error.Abort('%s: %s' % (f, msg))
1539
1539
1540 if not match:
1540 if not match:
1541 match = matchmod.always(self.root, '')
1541 match = matchmod.always(self.root, '')
1542
1542
1543 if not force:
1543 if not force:
1544 vdirs = []
1544 vdirs = []
1545 match.explicitdir = vdirs.append
1545 match.explicitdir = vdirs.append
1546 match.bad = fail
1546 match.bad = fail
1547
1547
1548 wlock = lock = tr = None
1548 wlock = lock = tr = None
1549 try:
1549 try:
1550 wlock = self.wlock()
1550 wlock = self.wlock()
1551 lock = self.lock() # for recent changelog (see issue4368)
1551 lock = self.lock() # for recent changelog (see issue4368)
1552
1552
1553 wctx = self[None]
1553 wctx = self[None]
1554 merge = len(wctx.parents()) > 1
1554 merge = len(wctx.parents()) > 1
1555
1555
1556 if not force and merge and match.ispartial():
1556 if not force and merge and match.ispartial():
1557 raise error.Abort(_('cannot partially commit a merge '
1557 raise error.Abort(_('cannot partially commit a merge '
1558 '(do not specify files or patterns)'))
1558 '(do not specify files or patterns)'))
1559
1559
1560 status = self.status(match=match, clean=force)
1560 status = self.status(match=match, clean=force)
1561 if force:
1561 if force:
1562 status.modified.extend(status.clean) # mq may commit clean files
1562 status.modified.extend(status.clean) # mq may commit clean files
1563
1563
1564 # check subrepos
1564 # check subrepos
1565 subs = []
1565 subs = []
1566 commitsubs = set()
1566 commitsubs = set()
1567 newstate = wctx.substate.copy()
1567 newstate = wctx.substate.copy()
1568 # only manage subrepos and .hgsubstate if .hgsub is present
1568 # only manage subrepos and .hgsubstate if .hgsub is present
1569 if '.hgsub' in wctx:
1569 if '.hgsub' in wctx:
1570 # we'll decide whether to track this ourselves, thanks
1570 # we'll decide whether to track this ourselves, thanks
1571 for c in status.modified, status.added, status.removed:
1571 for c in status.modified, status.added, status.removed:
1572 if '.hgsubstate' in c:
1572 if '.hgsubstate' in c:
1573 c.remove('.hgsubstate')
1573 c.remove('.hgsubstate')
1574
1574
1575 # compare current state to last committed state
1575 # compare current state to last committed state
1576 # build new substate based on last committed state
1576 # build new substate based on last committed state
1577 oldstate = wctx.p1().substate
1577 oldstate = wctx.p1().substate
1578 for s in sorted(newstate.keys()):
1578 for s in sorted(newstate.keys()):
1579 if not match(s):
1579 if not match(s):
1580 # ignore working copy, use old state if present
1580 # ignore working copy, use old state if present
1581 if s in oldstate:
1581 if s in oldstate:
1582 newstate[s] = oldstate[s]
1582 newstate[s] = oldstate[s]
1583 continue
1583 continue
1584 if not force:
1584 if not force:
1585 raise error.Abort(
1585 raise error.Abort(
1586 _("commit with new subrepo %s excluded") % s)
1586 _("commit with new subrepo %s excluded") % s)
1587 dirtyreason = wctx.sub(s).dirtyreason(True)
1587 dirtyreason = wctx.sub(s).dirtyreason(True)
1588 if dirtyreason:
1588 if dirtyreason:
1589 if not self.ui.configbool('ui', 'commitsubrepos'):
1589 if not self.ui.configbool('ui', 'commitsubrepos'):
1590 raise error.Abort(dirtyreason,
1590 raise error.Abort(dirtyreason,
1591 hint=_("use --subrepos for recursive commit"))
1591 hint=_("use --subrepos for recursive commit"))
1592 subs.append(s)
1592 subs.append(s)
1593 commitsubs.add(s)
1593 commitsubs.add(s)
1594 else:
1594 else:
1595 bs = wctx.sub(s).basestate()
1595 bs = wctx.sub(s).basestate()
1596 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1596 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1597 if oldstate.get(s, (None, None, None))[1] != bs:
1597 if oldstate.get(s, (None, None, None))[1] != bs:
1598 subs.append(s)
1598 subs.append(s)
1599
1599
1600 # check for removed subrepos
1600 # check for removed subrepos
1601 for p in wctx.parents():
1601 for p in wctx.parents():
1602 r = [s for s in p.substate if s not in newstate]
1602 r = [s for s in p.substate if s not in newstate]
1603 subs += [s for s in r if match(s)]
1603 subs += [s for s in r if match(s)]
1604 if subs:
1604 if subs:
1605 if (not match('.hgsub') and
1605 if (not match('.hgsub') and
1606 '.hgsub' in (wctx.modified() + wctx.added())):
1606 '.hgsub' in (wctx.modified() + wctx.added())):
1607 raise error.Abort(
1607 raise error.Abort(
1608 _("can't commit subrepos without .hgsub"))
1608 _("can't commit subrepos without .hgsub"))
1609 status.modified.insert(0, '.hgsubstate')
1609 status.modified.insert(0, '.hgsubstate')
1610
1610
1611 elif '.hgsub' in status.removed:
1611 elif '.hgsub' in status.removed:
1612 # clean up .hgsubstate when .hgsub is removed
1612 # clean up .hgsubstate when .hgsub is removed
1613 if ('.hgsubstate' in wctx and
1613 if ('.hgsubstate' in wctx and
1614 '.hgsubstate' not in (status.modified + status.added +
1614 '.hgsubstate' not in (status.modified + status.added +
1615 status.removed)):
1615 status.removed)):
1616 status.removed.insert(0, '.hgsubstate')
1616 status.removed.insert(0, '.hgsubstate')
1617
1617
1618 # make sure all explicit patterns are matched
1618 # make sure all explicit patterns are matched
1619 if not force:
1619 if not force:
1620 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1620 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1621
1621
1622 cctx = context.workingcommitctx(self, status,
1622 cctx = context.workingcommitctx(self, status,
1623 text, user, date, extra)
1623 text, user, date, extra)
1624
1624
1625 # internal config: ui.allowemptycommit
1625 # internal config: ui.allowemptycommit
1626 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1626 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1627 or extra.get('close') or merge or cctx.files()
1627 or extra.get('close') or merge or cctx.files()
1628 or self.ui.configbool('ui', 'allowemptycommit'))
1628 or self.ui.configbool('ui', 'allowemptycommit'))
1629 if not allowemptycommit:
1629 if not allowemptycommit:
1630 return None
1630 return None
1631
1631
1632 if merge and cctx.deleted():
1632 if merge and cctx.deleted():
1633 raise error.Abort(_("cannot commit merge with missing files"))
1633 raise error.Abort(_("cannot commit merge with missing files"))
1634
1634
1635 ms = mergemod.mergestate.read(self)
1635 ms = mergemod.mergestate.read(self)
1636 cmdutil.checkunresolved(ms)
1636 cmdutil.checkunresolved(ms)
1637
1637
1638 if editor:
1638 if editor:
1639 cctx._text = editor(self, cctx, subs)
1639 cctx._text = editor(self, cctx, subs)
1640 edited = (text != cctx._text)
1640 edited = (text != cctx._text)
1641
1641
1642 # Save commit message in case this transaction gets rolled back
1642 # Save commit message in case this transaction gets rolled back
1643 # (e.g. by a pretxncommit hook). Leave the content alone on
1643 # (e.g. by a pretxncommit hook). Leave the content alone on
1644 # the assumption that the user will use the same editor again.
1644 # the assumption that the user will use the same editor again.
1645 msgfn = self.savecommitmessage(cctx._text)
1645 msgfn = self.savecommitmessage(cctx._text)
1646
1646
1647 # commit subs and write new state
1647 # commit subs and write new state
1648 if subs:
1648 if subs:
1649 for s in sorted(commitsubs):
1649 for s in sorted(commitsubs):
1650 sub = wctx.sub(s)
1650 sub = wctx.sub(s)
1651 self.ui.status(_('committing subrepository %s\n') %
1651 self.ui.status(_('committing subrepository %s\n') %
1652 subrepo.subrelpath(sub))
1652 subrepo.subrelpath(sub))
1653 sr = sub.commit(cctx._text, user, date)
1653 sr = sub.commit(cctx._text, user, date)
1654 newstate[s] = (newstate[s][0], sr)
1654 newstate[s] = (newstate[s][0], sr)
1655 subrepo.writestate(self, newstate)
1655 subrepo.writestate(self, newstate)
1656
1656
1657 p1, p2 = self.dirstate.parents()
1657 p1, p2 = self.dirstate.parents()
1658 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1658 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1659 try:
1659 try:
1660 self.hook("precommit", throw=True, parent1=hookp1,
1660 self.hook("precommit", throw=True, parent1=hookp1,
1661 parent2=hookp2)
1661 parent2=hookp2)
1662 tr = self.transaction('commit')
1662 tr = self.transaction('commit')
1663 ret = self.commitctx(cctx, True)
1663 ret = self.commitctx(cctx, True)
1664 except: # re-raises
1664 except: # re-raises
1665 if edited:
1665 if edited:
1666 self.ui.write(
1666 self.ui.write(
1667 _('note: commit message saved in %s\n') % msgfn)
1667 _('note: commit message saved in %s\n') % msgfn)
1668 raise
1668 raise
1669 # update bookmarks, dirstate and mergestate
1669 # update bookmarks, dirstate and mergestate
1670 bookmarks.update(self, [p1, p2], ret)
1670 bookmarks.update(self, [p1, p2], ret)
1671 cctx.markcommitted(ret)
1671 cctx.markcommitted(ret)
1672 ms.reset()
1672 ms.reset()
1673 tr.close()
1673 tr.close()
1674
1674
1675 finally:
1675 finally:
1676 lockmod.release(tr, lock, wlock)
1676 lockmod.release(tr, lock, wlock)
1677
1677
1678 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1678 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1679 # hack for command that use a temporary commit (eg: histedit)
1679 # hack for command that use a temporary commit (eg: histedit)
1680 # temporary commit got stripped before hook release
1680 # temporary commit got stripped before hook release
1681 if self.changelog.hasnode(ret):
1681 if self.changelog.hasnode(ret):
1682 self.hook("commit", node=node, parent1=parent1,
1682 self.hook("commit", node=node, parent1=parent1,
1683 parent2=parent2)
1683 parent2=parent2)
1684 self._afterlock(commithook)
1684 self._afterlock(commithook)
1685 return ret
1685 return ret
1686
1686
1687 @unfilteredmethod
1687 @unfilteredmethod
1688 def commitctx(self, ctx, error=False):
1688 def commitctx(self, ctx, error=False):
1689 """Add a new revision to current repository.
1689 """Add a new revision to current repository.
1690 Revision information is passed via the context argument.
1690 Revision information is passed via the context argument.
1691 """
1691 """
1692
1692
1693 tr = None
1693 tr = None
1694 p1, p2 = ctx.p1(), ctx.p2()
1694 p1, p2 = ctx.p1(), ctx.p2()
1695 user = ctx.user()
1695 user = ctx.user()
1696
1696
1697 lock = self.lock()
1697 lock = self.lock()
1698 try:
1698 try:
1699 tr = self.transaction("commit")
1699 tr = self.transaction("commit")
1700 trp = weakref.proxy(tr)
1700 trp = weakref.proxy(tr)
1701
1701
1702 if ctx.files():
1702 if ctx.files():
1703 m1 = p1.manifest()
1703 m1ctx = p1.manifestctx()
1704 m2 = p2.manifest()
1704 m2ctx = p2.manifestctx()
1705 m = m1.copy()
1705 mctx = m1ctx.copy()
1706
1707 m = mctx.read()
1708 m1 = m1ctx.read()
1709 m2 = m2ctx.read()
1706
1710
1707 # check in files
1711 # check in files
1708 added = []
1712 added = []
1709 changed = []
1713 changed = []
1710 removed = list(ctx.removed())
1714 removed = list(ctx.removed())
1711 linkrev = len(self)
1715 linkrev = len(self)
1712 self.ui.note(_("committing files:\n"))
1716 self.ui.note(_("committing files:\n"))
1713 for f in sorted(ctx.modified() + ctx.added()):
1717 for f in sorted(ctx.modified() + ctx.added()):
1714 self.ui.note(f + "\n")
1718 self.ui.note(f + "\n")
1715 try:
1719 try:
1716 fctx = ctx[f]
1720 fctx = ctx[f]
1717 if fctx is None:
1721 if fctx is None:
1718 removed.append(f)
1722 removed.append(f)
1719 else:
1723 else:
1720 added.append(f)
1724 added.append(f)
1721 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1725 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1722 trp, changed)
1726 trp, changed)
1723 m.setflag(f, fctx.flags())
1727 m.setflag(f, fctx.flags())
1724 except OSError as inst:
1728 except OSError as inst:
1725 self.ui.warn(_("trouble committing %s!\n") % f)
1729 self.ui.warn(_("trouble committing %s!\n") % f)
1726 raise
1730 raise
1727 except IOError as inst:
1731 except IOError as inst:
1728 errcode = getattr(inst, 'errno', errno.ENOENT)
1732 errcode = getattr(inst, 'errno', errno.ENOENT)
1729 if error or errcode and errcode != errno.ENOENT:
1733 if error or errcode and errcode != errno.ENOENT:
1730 self.ui.warn(_("trouble committing %s!\n") % f)
1734 self.ui.warn(_("trouble committing %s!\n") % f)
1731 raise
1735 raise
1732
1736
1733 # update manifest
1737 # update manifest
1734 self.ui.note(_("committing manifest\n"))
1738 self.ui.note(_("committing manifest\n"))
1735 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1739 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1736 drop = [f for f in removed if f in m]
1740 drop = [f for f in removed if f in m]
1737 for f in drop:
1741 for f in drop:
1738 del m[f]
1742 del m[f]
1739 mn = self.manifestlog.add(m, trp, linkrev,
1743 mn = mctx.write(trp, linkrev,
1740 p1.manifestnode(), p2.manifestnode(),
1744 p1.manifestnode(), p2.manifestnode(),
1741 added, drop)
1745 added, drop)
1742 files = changed + removed
1746 files = changed + removed
1743 else:
1747 else:
1744 mn = p1.manifestnode()
1748 mn = p1.manifestnode()
1745 files = []
1749 files = []
1746
1750
1747 # update changelog
1751 # update changelog
1748 self.ui.note(_("committing changelog\n"))
1752 self.ui.note(_("committing changelog\n"))
1749 self.changelog.delayupdate(tr)
1753 self.changelog.delayupdate(tr)
1750 n = self.changelog.add(mn, files, ctx.description(),
1754 n = self.changelog.add(mn, files, ctx.description(),
1751 trp, p1.node(), p2.node(),
1755 trp, p1.node(), p2.node(),
1752 user, ctx.date(), ctx.extra().copy())
1756 user, ctx.date(), ctx.extra().copy())
1753 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1757 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1754 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1758 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1755 parent2=xp2)
1759 parent2=xp2)
1756 # set the new commit is proper phase
1760 # set the new commit is proper phase
1757 targetphase = subrepo.newcommitphase(self.ui, ctx)
1761 targetphase = subrepo.newcommitphase(self.ui, ctx)
1758 if targetphase:
1762 if targetphase:
1759 # retract boundary do not alter parent changeset.
1763 # retract boundary do not alter parent changeset.
1760 # if a parent have higher the resulting phase will
1764 # if a parent have higher the resulting phase will
1761 # be compliant anyway
1765 # be compliant anyway
1762 #
1766 #
1763 # if minimal phase was 0 we don't need to retract anything
1767 # if minimal phase was 0 we don't need to retract anything
1764 phases.retractboundary(self, tr, targetphase, [n])
1768 phases.retractboundary(self, tr, targetphase, [n])
1765 tr.close()
1769 tr.close()
1766 branchmap.updatecache(self.filtered('served'))
1770 branchmap.updatecache(self.filtered('served'))
1767 return n
1771 return n
1768 finally:
1772 finally:
1769 if tr:
1773 if tr:
1770 tr.release()
1774 tr.release()
1771 lock.release()
1775 lock.release()
1772
1776
1773 @unfilteredmethod
1777 @unfilteredmethod
1774 def destroying(self):
1778 def destroying(self):
1775 '''Inform the repository that nodes are about to be destroyed.
1779 '''Inform the repository that nodes are about to be destroyed.
1776 Intended for use by strip and rollback, so there's a common
1780 Intended for use by strip and rollback, so there's a common
1777 place for anything that has to be done before destroying history.
1781 place for anything that has to be done before destroying history.
1778
1782
1779 This is mostly useful for saving state that is in memory and waiting
1783 This is mostly useful for saving state that is in memory and waiting
1780 to be flushed when the current lock is released. Because a call to
1784 to be flushed when the current lock is released. Because a call to
1781 destroyed is imminent, the repo will be invalidated causing those
1785 destroyed is imminent, the repo will be invalidated causing those
1782 changes to stay in memory (waiting for the next unlock), or vanish
1786 changes to stay in memory (waiting for the next unlock), or vanish
1783 completely.
1787 completely.
1784 '''
1788 '''
1785 # When using the same lock to commit and strip, the phasecache is left
1789 # When using the same lock to commit and strip, the phasecache is left
1786 # dirty after committing. Then when we strip, the repo is invalidated,
1790 # dirty after committing. Then when we strip, the repo is invalidated,
1787 # causing those changes to disappear.
1791 # causing those changes to disappear.
1788 if '_phasecache' in vars(self):
1792 if '_phasecache' in vars(self):
1789 self._phasecache.write()
1793 self._phasecache.write()
1790
1794
1791 @unfilteredmethod
1795 @unfilteredmethod
1792 def destroyed(self):
1796 def destroyed(self):
1793 '''Inform the repository that nodes have been destroyed.
1797 '''Inform the repository that nodes have been destroyed.
1794 Intended for use by strip and rollback, so there's a common
1798 Intended for use by strip and rollback, so there's a common
1795 place for anything that has to be done after destroying history.
1799 place for anything that has to be done after destroying history.
1796 '''
1800 '''
1797 # When one tries to:
1801 # When one tries to:
1798 # 1) destroy nodes thus calling this method (e.g. strip)
1802 # 1) destroy nodes thus calling this method (e.g. strip)
1799 # 2) use phasecache somewhere (e.g. commit)
1803 # 2) use phasecache somewhere (e.g. commit)
1800 #
1804 #
1801 # then 2) will fail because the phasecache contains nodes that were
1805 # then 2) will fail because the phasecache contains nodes that were
1802 # removed. We can either remove phasecache from the filecache,
1806 # removed. We can either remove phasecache from the filecache,
1803 # causing it to reload next time it is accessed, or simply filter
1807 # causing it to reload next time it is accessed, or simply filter
1804 # the removed nodes now and write the updated cache.
1808 # the removed nodes now and write the updated cache.
1805 self._phasecache.filterunknown(self)
1809 self._phasecache.filterunknown(self)
1806 self._phasecache.write()
1810 self._phasecache.write()
1807
1811
1808 # update the 'served' branch cache to help read only server process
1812 # update the 'served' branch cache to help read only server process
1809 # Thanks to branchcache collaboration this is done from the nearest
1813 # Thanks to branchcache collaboration this is done from the nearest
1810 # filtered subset and it is expected to be fast.
1814 # filtered subset and it is expected to be fast.
1811 branchmap.updatecache(self.filtered('served'))
1815 branchmap.updatecache(self.filtered('served'))
1812
1816
1813 # Ensure the persistent tag cache is updated. Doing it now
1817 # Ensure the persistent tag cache is updated. Doing it now
1814 # means that the tag cache only has to worry about destroyed
1818 # means that the tag cache only has to worry about destroyed
1815 # heads immediately after a strip/rollback. That in turn
1819 # heads immediately after a strip/rollback. That in turn
1816 # guarantees that "cachetip == currenttip" (comparing both rev
1820 # guarantees that "cachetip == currenttip" (comparing both rev
1817 # and node) always means no nodes have been added or destroyed.
1821 # and node) always means no nodes have been added or destroyed.
1818
1822
1819 # XXX this is suboptimal when qrefresh'ing: we strip the current
1823 # XXX this is suboptimal when qrefresh'ing: we strip the current
1820 # head, refresh the tag cache, then immediately add a new head.
1824 # head, refresh the tag cache, then immediately add a new head.
1821 # But I think doing it this way is necessary for the "instant
1825 # But I think doing it this way is necessary for the "instant
1822 # tag cache retrieval" case to work.
1826 # tag cache retrieval" case to work.
1823 self.invalidate()
1827 self.invalidate()
1824
1828
1825 def walk(self, match, node=None):
1829 def walk(self, match, node=None):
1826 '''
1830 '''
1827 walk recursively through the directory tree or a given
1831 walk recursively through the directory tree or a given
1828 changeset, finding all files matched by the match
1832 changeset, finding all files matched by the match
1829 function
1833 function
1830 '''
1834 '''
1831 return self[node].walk(match)
1835 return self[node].walk(match)
1832
1836
1833 def status(self, node1='.', node2=None, match=None,
1837 def status(self, node1='.', node2=None, match=None,
1834 ignored=False, clean=False, unknown=False,
1838 ignored=False, clean=False, unknown=False,
1835 listsubrepos=False):
1839 listsubrepos=False):
1836 '''a convenience method that calls node1.status(node2)'''
1840 '''a convenience method that calls node1.status(node2)'''
1837 return self[node1].status(node2, match, ignored, clean, unknown,
1841 return self[node1].status(node2, match, ignored, clean, unknown,
1838 listsubrepos)
1842 listsubrepos)
1839
1843
1840 def heads(self, start=None):
1844 def heads(self, start=None):
1841 heads = self.changelog.heads(start)
1845 heads = self.changelog.heads(start)
1842 # sort the output in rev descending order
1846 # sort the output in rev descending order
1843 return sorted(heads, key=self.changelog.rev, reverse=True)
1847 return sorted(heads, key=self.changelog.rev, reverse=True)
1844
1848
1845 def branchheads(self, branch=None, start=None, closed=False):
1849 def branchheads(self, branch=None, start=None, closed=False):
1846 '''return a (possibly filtered) list of heads for the given branch
1850 '''return a (possibly filtered) list of heads for the given branch
1847
1851
1848 Heads are returned in topological order, from newest to oldest.
1852 Heads are returned in topological order, from newest to oldest.
1849 If branch is None, use the dirstate branch.
1853 If branch is None, use the dirstate branch.
1850 If start is not None, return only heads reachable from start.
1854 If start is not None, return only heads reachable from start.
1851 If closed is True, return heads that are marked as closed as well.
1855 If closed is True, return heads that are marked as closed as well.
1852 '''
1856 '''
1853 if branch is None:
1857 if branch is None:
1854 branch = self[None].branch()
1858 branch = self[None].branch()
1855 branches = self.branchmap()
1859 branches = self.branchmap()
1856 if branch not in branches:
1860 if branch not in branches:
1857 return []
1861 return []
1858 # the cache returns heads ordered lowest to highest
1862 # the cache returns heads ordered lowest to highest
1859 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1863 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
1860 if start is not None:
1864 if start is not None:
1861 # filter out the heads that cannot be reached from startrev
1865 # filter out the heads that cannot be reached from startrev
1862 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1866 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
1863 bheads = [h for h in bheads if h in fbheads]
1867 bheads = [h for h in bheads if h in fbheads]
1864 return bheads
1868 return bheads
1865
1869
1866 def branches(self, nodes):
1870 def branches(self, nodes):
1867 if not nodes:
1871 if not nodes:
1868 nodes = [self.changelog.tip()]
1872 nodes = [self.changelog.tip()]
1869 b = []
1873 b = []
1870 for n in nodes:
1874 for n in nodes:
1871 t = n
1875 t = n
1872 while True:
1876 while True:
1873 p = self.changelog.parents(n)
1877 p = self.changelog.parents(n)
1874 if p[1] != nullid or p[0] == nullid:
1878 if p[1] != nullid or p[0] == nullid:
1875 b.append((t, n, p[0], p[1]))
1879 b.append((t, n, p[0], p[1]))
1876 break
1880 break
1877 n = p[0]
1881 n = p[0]
1878 return b
1882 return b
1879
1883
1880 def between(self, pairs):
1884 def between(self, pairs):
1881 r = []
1885 r = []
1882
1886
1883 for top, bottom in pairs:
1887 for top, bottom in pairs:
1884 n, l, i = top, [], 0
1888 n, l, i = top, [], 0
1885 f = 1
1889 f = 1
1886
1890
1887 while n != bottom and n != nullid:
1891 while n != bottom and n != nullid:
1888 p = self.changelog.parents(n)[0]
1892 p = self.changelog.parents(n)[0]
1889 if i == f:
1893 if i == f:
1890 l.append(n)
1894 l.append(n)
1891 f = f * 2
1895 f = f * 2
1892 n = p
1896 n = p
1893 i += 1
1897 i += 1
1894
1898
1895 r.append(l)
1899 r.append(l)
1896
1900
1897 return r
1901 return r
1898
1902
1899 def checkpush(self, pushop):
1903 def checkpush(self, pushop):
1900 """Extensions can override this function if additional checks have
1904 """Extensions can override this function if additional checks have
1901 to be performed before pushing, or call it if they override push
1905 to be performed before pushing, or call it if they override push
1902 command.
1906 command.
1903 """
1907 """
1904 pass
1908 pass
1905
1909
1906 @unfilteredpropertycache
1910 @unfilteredpropertycache
1907 def prepushoutgoinghooks(self):
1911 def prepushoutgoinghooks(self):
1908 """Return util.hooks consists of a pushop with repo, remote, outgoing
1912 """Return util.hooks consists of a pushop with repo, remote, outgoing
1909 methods, which are called before pushing changesets.
1913 methods, which are called before pushing changesets.
1910 """
1914 """
1911 return util.hooks()
1915 return util.hooks()
1912
1916
1913 def pushkey(self, namespace, key, old, new):
1917 def pushkey(self, namespace, key, old, new):
1914 try:
1918 try:
1915 tr = self.currenttransaction()
1919 tr = self.currenttransaction()
1916 hookargs = {}
1920 hookargs = {}
1917 if tr is not None:
1921 if tr is not None:
1918 hookargs.update(tr.hookargs)
1922 hookargs.update(tr.hookargs)
1919 hookargs['namespace'] = namespace
1923 hookargs['namespace'] = namespace
1920 hookargs['key'] = key
1924 hookargs['key'] = key
1921 hookargs['old'] = old
1925 hookargs['old'] = old
1922 hookargs['new'] = new
1926 hookargs['new'] = new
1923 self.hook('prepushkey', throw=True, **hookargs)
1927 self.hook('prepushkey', throw=True, **hookargs)
1924 except error.HookAbort as exc:
1928 except error.HookAbort as exc:
1925 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1929 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
1926 if exc.hint:
1930 if exc.hint:
1927 self.ui.write_err(_("(%s)\n") % exc.hint)
1931 self.ui.write_err(_("(%s)\n") % exc.hint)
1928 return False
1932 return False
1929 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1933 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
1930 ret = pushkey.push(self, namespace, key, old, new)
1934 ret = pushkey.push(self, namespace, key, old, new)
1931 def runhook():
1935 def runhook():
1932 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1936 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
1933 ret=ret)
1937 ret=ret)
1934 self._afterlock(runhook)
1938 self._afterlock(runhook)
1935 return ret
1939 return ret
1936
1940
1937 def listkeys(self, namespace):
1941 def listkeys(self, namespace):
1938 self.hook('prelistkeys', throw=True, namespace=namespace)
1942 self.hook('prelistkeys', throw=True, namespace=namespace)
1939 self.ui.debug('listing keys for "%s"\n' % namespace)
1943 self.ui.debug('listing keys for "%s"\n' % namespace)
1940 values = pushkey.list(self, namespace)
1944 values = pushkey.list(self, namespace)
1941 self.hook('listkeys', namespace=namespace, values=values)
1945 self.hook('listkeys', namespace=namespace, values=values)
1942 return values
1946 return values
1943
1947
1944 def debugwireargs(self, one, two, three=None, four=None, five=None):
1948 def debugwireargs(self, one, two, three=None, four=None, five=None):
1945 '''used to test argument passing over the wire'''
1949 '''used to test argument passing over the wire'''
1946 return "%s %s %s %s %s" % (one, two, three, four, five)
1950 return "%s %s %s %s %s" % (one, two, three, four, five)
1947
1951
1948 def savecommitmessage(self, text):
1952 def savecommitmessage(self, text):
1949 fp = self.vfs('last-message.txt', 'wb')
1953 fp = self.vfs('last-message.txt', 'wb')
1950 try:
1954 try:
1951 fp.write(text)
1955 fp.write(text)
1952 finally:
1956 finally:
1953 fp.close()
1957 fp.close()
1954 return self.pathto(fp.name[len(self.root) + 1:])
1958 return self.pathto(fp.name[len(self.root) + 1:])
1955
1959
1956 # used to avoid circular references so destructors work
1960 # used to avoid circular references so destructors work
1957 def aftertrans(files):
1961 def aftertrans(files):
1958 renamefiles = [tuple(t) for t in files]
1962 renamefiles = [tuple(t) for t in files]
1959 def a():
1963 def a():
1960 for vfs, src, dest in renamefiles:
1964 for vfs, src, dest in renamefiles:
1961 try:
1965 try:
1962 vfs.rename(src, dest)
1966 vfs.rename(src, dest)
1963 except OSError: # journal file does not yet exist
1967 except OSError: # journal file does not yet exist
1964 pass
1968 pass
1965 return a
1969 return a
1966
1970
1967 def undoname(fn):
1971 def undoname(fn):
1968 base, name = os.path.split(fn)
1972 base, name = os.path.split(fn)
1969 assert name.startswith('journal')
1973 assert name.startswith('journal')
1970 return os.path.join(base, name.replace('journal', 'undo', 1))
1974 return os.path.join(base, name.replace('journal', 'undo', 1))
1971
1975
1972 def instance(ui, path, create):
1976 def instance(ui, path, create):
1973 return localrepository(ui, util.urllocalpath(path), create)
1977 return localrepository(ui, util.urllocalpath(path), create)
1974
1978
1975 def islocal(path):
1979 def islocal(path):
1976 return True
1980 return True
1977
1981
1978 def newreporequirements(repo):
1982 def newreporequirements(repo):
1979 """Determine the set of requirements for a new local repository.
1983 """Determine the set of requirements for a new local repository.
1980
1984
1981 Extensions can wrap this function to specify custom requirements for
1985 Extensions can wrap this function to specify custom requirements for
1982 new repositories.
1986 new repositories.
1983 """
1987 """
1984 ui = repo.ui
1988 ui = repo.ui
1985 requirements = set(['revlogv1'])
1989 requirements = set(['revlogv1'])
1986 if ui.configbool('format', 'usestore', True):
1990 if ui.configbool('format', 'usestore', True):
1987 requirements.add('store')
1991 requirements.add('store')
1988 if ui.configbool('format', 'usefncache', True):
1992 if ui.configbool('format', 'usefncache', True):
1989 requirements.add('fncache')
1993 requirements.add('fncache')
1990 if ui.configbool('format', 'dotencode', True):
1994 if ui.configbool('format', 'dotencode', True):
1991 requirements.add('dotencode')
1995 requirements.add('dotencode')
1992
1996
1993 if scmutil.gdinitconfig(ui):
1997 if scmutil.gdinitconfig(ui):
1994 requirements.add('generaldelta')
1998 requirements.add('generaldelta')
1995 if ui.configbool('experimental', 'treemanifest', False):
1999 if ui.configbool('experimental', 'treemanifest', False):
1996 requirements.add('treemanifest')
2000 requirements.add('treemanifest')
1997 if ui.configbool('experimental', 'manifestv2', False):
2001 if ui.configbool('experimental', 'manifestv2', False):
1998 requirements.add('manifestv2')
2002 requirements.add('manifestv2')
1999
2003
2000 return requirements
2004 return requirements
@@ -1,1611 +1,1622 b''
1 # manifest.py - manifest revision class for mercurial
1 # manifest.py - manifest revision class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import array
10 import array
11 import heapq
11 import heapq
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from . import (
16 from . import (
17 error,
17 error,
18 mdiff,
18 mdiff,
19 parsers,
19 parsers,
20 revlog,
20 revlog,
21 util,
21 util,
22 )
22 )
23
23
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def _parsev1(data):
26 def _parsev1(data):
27 # This method does a little bit of excessive-looking
27 # This method does a little bit of excessive-looking
28 # precondition checking. This is so that the behavior of this
28 # precondition checking. This is so that the behavior of this
29 # class exactly matches its C counterpart to try and help
29 # class exactly matches its C counterpart to try and help
30 # prevent surprise breakage for anyone that develops against
30 # prevent surprise breakage for anyone that develops against
31 # the pure version.
31 # the pure version.
32 if data and data[-1] != '\n':
32 if data and data[-1] != '\n':
33 raise ValueError('Manifest did not end in a newline.')
33 raise ValueError('Manifest did not end in a newline.')
34 prev = None
34 prev = None
35 for l in data.splitlines():
35 for l in data.splitlines():
36 if prev is not None and prev > l:
36 if prev is not None and prev > l:
37 raise ValueError('Manifest lines not in sorted order.')
37 raise ValueError('Manifest lines not in sorted order.')
38 prev = l
38 prev = l
39 f, n = l.split('\0')
39 f, n = l.split('\0')
40 if len(n) > 40:
40 if len(n) > 40:
41 yield f, revlog.bin(n[:40]), n[40:]
41 yield f, revlog.bin(n[:40]), n[40:]
42 else:
42 else:
43 yield f, revlog.bin(n), ''
43 yield f, revlog.bin(n), ''
44
44
45 def _parsev2(data):
45 def _parsev2(data):
46 metadataend = data.find('\n')
46 metadataend = data.find('\n')
47 # Just ignore metadata for now
47 # Just ignore metadata for now
48 pos = metadataend + 1
48 pos = metadataend + 1
49 prevf = ''
49 prevf = ''
50 while pos < len(data):
50 while pos < len(data):
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
51 end = data.find('\n', pos + 1) # +1 to skip stem length byte
52 if end == -1:
52 if end == -1:
53 raise ValueError('Manifest ended with incomplete file entry.')
53 raise ValueError('Manifest ended with incomplete file entry.')
54 stemlen = ord(data[pos])
54 stemlen = ord(data[pos])
55 items = data[pos + 1:end].split('\0')
55 items = data[pos + 1:end].split('\0')
56 f = prevf[:stemlen] + items[0]
56 f = prevf[:stemlen] + items[0]
57 if prevf > f:
57 if prevf > f:
58 raise ValueError('Manifest entries not in sorted order.')
58 raise ValueError('Manifest entries not in sorted order.')
59 fl = items[1]
59 fl = items[1]
60 # Just ignore metadata (items[2:] for now)
60 # Just ignore metadata (items[2:] for now)
61 n = data[end + 1:end + 21]
61 n = data[end + 1:end + 21]
62 yield f, n, fl
62 yield f, n, fl
63 pos = end + 22
63 pos = end + 22
64 prevf = f
64 prevf = f
65
65
66 def _parse(data):
66 def _parse(data):
67 """Generates (path, node, flags) tuples from a manifest text"""
67 """Generates (path, node, flags) tuples from a manifest text"""
68 if data.startswith('\0'):
68 if data.startswith('\0'):
69 return iter(_parsev2(data))
69 return iter(_parsev2(data))
70 else:
70 else:
71 return iter(_parsev1(data))
71 return iter(_parsev1(data))
72
72
73 def _text(it, usemanifestv2):
73 def _text(it, usemanifestv2):
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
74 """Given an iterator over (path, node, flags) tuples, returns a manifest
75 text"""
75 text"""
76 if usemanifestv2:
76 if usemanifestv2:
77 return _textv2(it)
77 return _textv2(it)
78 else:
78 else:
79 return _textv1(it)
79 return _textv1(it)
80
80
81 def _textv1(it):
81 def _textv1(it):
82 files = []
82 files = []
83 lines = []
83 lines = []
84 _hex = revlog.hex
84 _hex = revlog.hex
85 for f, n, fl in it:
85 for f, n, fl in it:
86 files.append(f)
86 files.append(f)
87 # if this is changed to support newlines in filenames,
87 # if this is changed to support newlines in filenames,
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
88 # be sure to check the templates/ dir again (especially *-raw.tmpl)
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
89 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
90
90
91 _checkforbidden(files)
91 _checkforbidden(files)
92 return ''.join(lines)
92 return ''.join(lines)
93
93
94 def _textv2(it):
94 def _textv2(it):
95 files = []
95 files = []
96 lines = ['\0\n']
96 lines = ['\0\n']
97 prevf = ''
97 prevf = ''
98 for f, n, fl in it:
98 for f, n, fl in it:
99 files.append(f)
99 files.append(f)
100 stem = os.path.commonprefix([prevf, f])
100 stem = os.path.commonprefix([prevf, f])
101 stemlen = min(len(stem), 255)
101 stemlen = min(len(stem), 255)
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
102 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
103 prevf = f
103 prevf = f
104 _checkforbidden(files)
104 _checkforbidden(files)
105 return ''.join(lines)
105 return ''.join(lines)
106
106
107 class lazymanifestiter(object):
107 class lazymanifestiter(object):
108 def __init__(self, lm):
108 def __init__(self, lm):
109 self.pos = 0
109 self.pos = 0
110 self.lm = lm
110 self.lm = lm
111
111
112 def __iter__(self):
112 def __iter__(self):
113 return self
113 return self
114
114
115 def next(self):
115 def next(self):
116 try:
116 try:
117 data, pos = self.lm._get(self.pos)
117 data, pos = self.lm._get(self.pos)
118 except IndexError:
118 except IndexError:
119 raise StopIteration
119 raise StopIteration
120 if pos == -1:
120 if pos == -1:
121 self.pos += 1
121 self.pos += 1
122 return data[0]
122 return data[0]
123 self.pos += 1
123 self.pos += 1
124 zeropos = data.find('\x00', pos)
124 zeropos = data.find('\x00', pos)
125 return data[pos:zeropos]
125 return data[pos:zeropos]
126
126
127 class lazymanifestiterentries(object):
127 class lazymanifestiterentries(object):
128 def __init__(self, lm):
128 def __init__(self, lm):
129 self.lm = lm
129 self.lm = lm
130 self.pos = 0
130 self.pos = 0
131
131
132 def __iter__(self):
132 def __iter__(self):
133 return self
133 return self
134
134
135 def next(self):
135 def next(self):
136 try:
136 try:
137 data, pos = self.lm._get(self.pos)
137 data, pos = self.lm._get(self.pos)
138 except IndexError:
138 except IndexError:
139 raise StopIteration
139 raise StopIteration
140 if pos == -1:
140 if pos == -1:
141 self.pos += 1
141 self.pos += 1
142 return data
142 return data
143 zeropos = data.find('\x00', pos)
143 zeropos = data.find('\x00', pos)
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
144 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
145 zeropos + 1, 40)
145 zeropos + 1, 40)
146 flags = self.lm._getflags(data, self.pos, zeropos)
146 flags = self.lm._getflags(data, self.pos, zeropos)
147 self.pos += 1
147 self.pos += 1
148 return (data[pos:zeropos], hashval, flags)
148 return (data[pos:zeropos], hashval, flags)
149
149
150 def unhexlify(data, extra, pos, length):
150 def unhexlify(data, extra, pos, length):
151 s = data[pos:pos + length].decode('hex')
151 s = data[pos:pos + length].decode('hex')
152 if extra:
152 if extra:
153 s += chr(extra & 0xff)
153 s += chr(extra & 0xff)
154 return s
154 return s
155
155
156 def _cmp(a, b):
156 def _cmp(a, b):
157 return (a > b) - (a < b)
157 return (a > b) - (a < b)
158
158
159 class _lazymanifest(object):
159 class _lazymanifest(object):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
160 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
161 if positions is None:
161 if positions is None:
162 self.positions = self.findlines(data)
162 self.positions = self.findlines(data)
163 self.extrainfo = [0] * len(self.positions)
163 self.extrainfo = [0] * len(self.positions)
164 self.data = data
164 self.data = data
165 self.extradata = []
165 self.extradata = []
166 else:
166 else:
167 self.positions = positions[:]
167 self.positions = positions[:]
168 self.extrainfo = extrainfo[:]
168 self.extrainfo = extrainfo[:]
169 self.extradata = extradata[:]
169 self.extradata = extradata[:]
170 self.data = data
170 self.data = data
171
171
172 def findlines(self, data):
172 def findlines(self, data):
173 if not data:
173 if not data:
174 return []
174 return []
175 pos = data.find("\n")
175 pos = data.find("\n")
176 if pos == -1 or data[-1] != '\n':
176 if pos == -1 or data[-1] != '\n':
177 raise ValueError("Manifest did not end in a newline.")
177 raise ValueError("Manifest did not end in a newline.")
178 positions = [0]
178 positions = [0]
179 prev = data[:data.find('\x00')]
179 prev = data[:data.find('\x00')]
180 while pos < len(data) - 1 and pos != -1:
180 while pos < len(data) - 1 and pos != -1:
181 positions.append(pos + 1)
181 positions.append(pos + 1)
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
182 nexts = data[pos + 1:data.find('\x00', pos + 1)]
183 if nexts < prev:
183 if nexts < prev:
184 raise ValueError("Manifest lines not in sorted order.")
184 raise ValueError("Manifest lines not in sorted order.")
185 prev = nexts
185 prev = nexts
186 pos = data.find("\n", pos + 1)
186 pos = data.find("\n", pos + 1)
187 return positions
187 return positions
188
188
189 def _get(self, index):
189 def _get(self, index):
190 # get the position encoded in pos:
190 # get the position encoded in pos:
191 # positive number is an index in 'data'
191 # positive number is an index in 'data'
192 # negative number is in extrapieces
192 # negative number is in extrapieces
193 pos = self.positions[index]
193 pos = self.positions[index]
194 if pos >= 0:
194 if pos >= 0:
195 return self.data, pos
195 return self.data, pos
196 return self.extradata[-pos - 1], -1
196 return self.extradata[-pos - 1], -1
197
197
198 def _getkey(self, pos):
198 def _getkey(self, pos):
199 if pos >= 0:
199 if pos >= 0:
200 return self.data[pos:self.data.find('\x00', pos + 1)]
200 return self.data[pos:self.data.find('\x00', pos + 1)]
201 return self.extradata[-pos - 1][0]
201 return self.extradata[-pos - 1][0]
202
202
203 def bsearch(self, key):
203 def bsearch(self, key):
204 first = 0
204 first = 0
205 last = len(self.positions) - 1
205 last = len(self.positions) - 1
206
206
207 while first <= last:
207 while first <= last:
208 midpoint = (first + last)//2
208 midpoint = (first + last)//2
209 nextpos = self.positions[midpoint]
209 nextpos = self.positions[midpoint]
210 candidate = self._getkey(nextpos)
210 candidate = self._getkey(nextpos)
211 r = _cmp(key, candidate)
211 r = _cmp(key, candidate)
212 if r == 0:
212 if r == 0:
213 return midpoint
213 return midpoint
214 else:
214 else:
215 if r < 0:
215 if r < 0:
216 last = midpoint - 1
216 last = midpoint - 1
217 else:
217 else:
218 first = midpoint + 1
218 first = midpoint + 1
219 return -1
219 return -1
220
220
221 def bsearch2(self, key):
221 def bsearch2(self, key):
222 # same as the above, but will always return the position
222 # same as the above, but will always return the position
223 # done for performance reasons
223 # done for performance reasons
224 first = 0
224 first = 0
225 last = len(self.positions) - 1
225 last = len(self.positions) - 1
226
226
227 while first <= last:
227 while first <= last:
228 midpoint = (first + last)//2
228 midpoint = (first + last)//2
229 nextpos = self.positions[midpoint]
229 nextpos = self.positions[midpoint]
230 candidate = self._getkey(nextpos)
230 candidate = self._getkey(nextpos)
231 r = _cmp(key, candidate)
231 r = _cmp(key, candidate)
232 if r == 0:
232 if r == 0:
233 return (midpoint, True)
233 return (midpoint, True)
234 else:
234 else:
235 if r < 0:
235 if r < 0:
236 last = midpoint - 1
236 last = midpoint - 1
237 else:
237 else:
238 first = midpoint + 1
238 first = midpoint + 1
239 return (first, False)
239 return (first, False)
240
240
241 def __contains__(self, key):
241 def __contains__(self, key):
242 return self.bsearch(key) != -1
242 return self.bsearch(key) != -1
243
243
244 def _getflags(self, data, needle, pos):
244 def _getflags(self, data, needle, pos):
245 start = pos + 41
245 start = pos + 41
246 end = data.find("\n", start)
246 end = data.find("\n", start)
247 if end == -1:
247 if end == -1:
248 end = len(data) - 1
248 end = len(data) - 1
249 if start == end:
249 if start == end:
250 return ''
250 return ''
251 return self.data[start:end]
251 return self.data[start:end]
252
252
253 def __getitem__(self, key):
253 def __getitem__(self, key):
254 if not isinstance(key, str):
254 if not isinstance(key, str):
255 raise TypeError("getitem: manifest keys must be a string.")
255 raise TypeError("getitem: manifest keys must be a string.")
256 needle = self.bsearch(key)
256 needle = self.bsearch(key)
257 if needle == -1:
257 if needle == -1:
258 raise KeyError
258 raise KeyError
259 data, pos = self._get(needle)
259 data, pos = self._get(needle)
260 if pos == -1:
260 if pos == -1:
261 return (data[1], data[2])
261 return (data[1], data[2])
262 zeropos = data.find('\x00', pos)
262 zeropos = data.find('\x00', pos)
263 assert 0 <= needle <= len(self.positions)
263 assert 0 <= needle <= len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
264 assert len(self.extrainfo) == len(self.positions)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
265 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
266 flags = self._getflags(data, needle, zeropos)
266 flags = self._getflags(data, needle, zeropos)
267 return (hashval, flags)
267 return (hashval, flags)
268
268
269 def __delitem__(self, key):
269 def __delitem__(self, key):
270 needle, found = self.bsearch2(key)
270 needle, found = self.bsearch2(key)
271 if not found:
271 if not found:
272 raise KeyError
272 raise KeyError
273 cur = self.positions[needle]
273 cur = self.positions[needle]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
274 self.positions = self.positions[:needle] + self.positions[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
275 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
276 if cur >= 0:
276 if cur >= 0:
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
277 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
278
278
279 def __setitem__(self, key, value):
279 def __setitem__(self, key, value):
280 if not isinstance(key, str):
280 if not isinstance(key, str):
281 raise TypeError("setitem: manifest keys must be a string.")
281 raise TypeError("setitem: manifest keys must be a string.")
282 if not isinstance(value, tuple) or len(value) != 2:
282 if not isinstance(value, tuple) or len(value) != 2:
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
283 raise TypeError("Manifest values must be a tuple of (node, flags).")
284 hashval = value[0]
284 hashval = value[0]
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
285 if not isinstance(hashval, str) or not 20 <= len(hashval) <= 22:
286 raise TypeError("node must be a 20-byte string")
286 raise TypeError("node must be a 20-byte string")
287 flags = value[1]
287 flags = value[1]
288 if len(hashval) == 22:
288 if len(hashval) == 22:
289 hashval = hashval[:-1]
289 hashval = hashval[:-1]
290 if not isinstance(flags, str) or len(flags) > 1:
290 if not isinstance(flags, str) or len(flags) > 1:
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
291 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
292 needle, found = self.bsearch2(key)
292 needle, found = self.bsearch2(key)
293 if found:
293 if found:
294 # put the item
294 # put the item
295 pos = self.positions[needle]
295 pos = self.positions[needle]
296 if pos < 0:
296 if pos < 0:
297 self.extradata[-pos - 1] = (key, hashval, value[1])
297 self.extradata[-pos - 1] = (key, hashval, value[1])
298 else:
298 else:
299 # just don't bother
299 # just don't bother
300 self.extradata.append((key, hashval, value[1]))
300 self.extradata.append((key, hashval, value[1]))
301 self.positions[needle] = -len(self.extradata)
301 self.positions[needle] = -len(self.extradata)
302 else:
302 else:
303 # not found, put it in with extra positions
303 # not found, put it in with extra positions
304 self.extradata.append((key, hashval, value[1]))
304 self.extradata.append((key, hashval, value[1]))
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
305 self.positions = (self.positions[:needle] + [-len(self.extradata)]
306 + self.positions[needle:])
306 + self.positions[needle:])
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
307 self.extrainfo = (self.extrainfo[:needle] + [0] +
308 self.extrainfo[needle:])
308 self.extrainfo[needle:])
309
309
310 def copy(self):
310 def copy(self):
311 # XXX call _compact like in C?
311 # XXX call _compact like in C?
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
312 return _lazymanifest(self.data, self.positions, self.extrainfo,
313 self.extradata)
313 self.extradata)
314
314
315 def _compact(self):
315 def _compact(self):
316 # hopefully not called TOO often
316 # hopefully not called TOO often
317 if len(self.extradata) == 0:
317 if len(self.extradata) == 0:
318 return
318 return
319 l = []
319 l = []
320 last_cut = 0
320 last_cut = 0
321 i = 0
321 i = 0
322 offset = 0
322 offset = 0
323 self.extrainfo = [0] * len(self.positions)
323 self.extrainfo = [0] * len(self.positions)
324 while i < len(self.positions):
324 while i < len(self.positions):
325 if self.positions[i] >= 0:
325 if self.positions[i] >= 0:
326 cur = self.positions[i]
326 cur = self.positions[i]
327 last_cut = cur
327 last_cut = cur
328 while True:
328 while True:
329 self.positions[i] = offset
329 self.positions[i] = offset
330 i += 1
330 i += 1
331 if i == len(self.positions) or self.positions[i] < 0:
331 if i == len(self.positions) or self.positions[i] < 0:
332 break
332 break
333 offset += self.positions[i] - cur
333 offset += self.positions[i] - cur
334 cur = self.positions[i]
334 cur = self.positions[i]
335 end_cut = self.data.find('\n', cur)
335 end_cut = self.data.find('\n', cur)
336 if end_cut != -1:
336 if end_cut != -1:
337 end_cut += 1
337 end_cut += 1
338 offset += end_cut - cur
338 offset += end_cut - cur
339 l.append(self.data[last_cut:end_cut])
339 l.append(self.data[last_cut:end_cut])
340 else:
340 else:
341 while i < len(self.positions) and self.positions[i] < 0:
341 while i < len(self.positions) and self.positions[i] < 0:
342 cur = self.positions[i]
342 cur = self.positions[i]
343 t = self.extradata[-cur - 1]
343 t = self.extradata[-cur - 1]
344 l.append(self._pack(t))
344 l.append(self._pack(t))
345 self.positions[i] = offset
345 self.positions[i] = offset
346 if len(t[1]) > 20:
346 if len(t[1]) > 20:
347 self.extrainfo[i] = ord(t[1][21])
347 self.extrainfo[i] = ord(t[1][21])
348 offset += len(l[-1])
348 offset += len(l[-1])
349 i += 1
349 i += 1
350 self.data = ''.join(l)
350 self.data = ''.join(l)
351 self.extradata = []
351 self.extradata = []
352
352
353 def _pack(self, d):
353 def _pack(self, d):
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
354 return d[0] + '\x00' + d[1][:20].encode('hex') + d[2] + '\n'
355
355
356 def text(self):
356 def text(self):
357 self._compact()
357 self._compact()
358 return self.data
358 return self.data
359
359
360 def diff(self, m2, clean=False):
360 def diff(self, m2, clean=False):
361 '''Finds changes between the current manifest and m2.'''
361 '''Finds changes between the current manifest and m2.'''
362 # XXX think whether efficiency matters here
362 # XXX think whether efficiency matters here
363 diff = {}
363 diff = {}
364
364
365 for fn, e1, flags in self.iterentries():
365 for fn, e1, flags in self.iterentries():
366 if fn not in m2:
366 if fn not in m2:
367 diff[fn] = (e1, flags), (None, '')
367 diff[fn] = (e1, flags), (None, '')
368 else:
368 else:
369 e2 = m2[fn]
369 e2 = m2[fn]
370 if (e1, flags) != e2:
370 if (e1, flags) != e2:
371 diff[fn] = (e1, flags), e2
371 diff[fn] = (e1, flags), e2
372 elif clean:
372 elif clean:
373 diff[fn] = None
373 diff[fn] = None
374
374
375 for fn, e2, flags in m2.iterentries():
375 for fn, e2, flags in m2.iterentries():
376 if fn not in self:
376 if fn not in self:
377 diff[fn] = (None, ''), (e2, flags)
377 diff[fn] = (None, ''), (e2, flags)
378
378
379 return diff
379 return diff
380
380
381 def iterentries(self):
381 def iterentries(self):
382 return lazymanifestiterentries(self)
382 return lazymanifestiterentries(self)
383
383
384 def iterkeys(self):
384 def iterkeys(self):
385 return lazymanifestiter(self)
385 return lazymanifestiter(self)
386
386
387 def __iter__(self):
387 def __iter__(self):
388 return lazymanifestiter(self)
388 return lazymanifestiter(self)
389
389
390 def __len__(self):
390 def __len__(self):
391 return len(self.positions)
391 return len(self.positions)
392
392
393 def filtercopy(self, filterfn):
393 def filtercopy(self, filterfn):
394 # XXX should be optimized
394 # XXX should be optimized
395 c = _lazymanifest('')
395 c = _lazymanifest('')
396 for f, n, fl in self.iterentries():
396 for f, n, fl in self.iterentries():
397 if filterfn(f):
397 if filterfn(f):
398 c[f] = n, fl
398 c[f] = n, fl
399 return c
399 return c
400
400
401 try:
401 try:
402 _lazymanifest = parsers.lazymanifest
402 _lazymanifest = parsers.lazymanifest
403 except AttributeError:
403 except AttributeError:
404 pass
404 pass
405
405
406 class manifestdict(object):
406 class manifestdict(object):
407 def __init__(self, data=''):
407 def __init__(self, data=''):
408 if data.startswith('\0'):
408 if data.startswith('\0'):
409 #_lazymanifest can not parse v2
409 #_lazymanifest can not parse v2
410 self._lm = _lazymanifest('')
410 self._lm = _lazymanifest('')
411 for f, n, fl in _parsev2(data):
411 for f, n, fl in _parsev2(data):
412 self._lm[f] = n, fl
412 self._lm[f] = n, fl
413 else:
413 else:
414 self._lm = _lazymanifest(data)
414 self._lm = _lazymanifest(data)
415
415
416 def __getitem__(self, key):
416 def __getitem__(self, key):
417 return self._lm[key][0]
417 return self._lm[key][0]
418
418
419 def find(self, key):
419 def find(self, key):
420 return self._lm[key]
420 return self._lm[key]
421
421
422 def __len__(self):
422 def __len__(self):
423 return len(self._lm)
423 return len(self._lm)
424
424
425 def __nonzero__(self):
425 def __nonzero__(self):
426 # nonzero is covered by the __len__ function, but implementing it here
426 # nonzero is covered by the __len__ function, but implementing it here
427 # makes it easier for extensions to override.
427 # makes it easier for extensions to override.
428 return len(self._lm) != 0
428 return len(self._lm) != 0
429
429
430 def __setitem__(self, key, node):
430 def __setitem__(self, key, node):
431 self._lm[key] = node, self.flags(key, '')
431 self._lm[key] = node, self.flags(key, '')
432
432
433 def __contains__(self, key):
433 def __contains__(self, key):
434 return key in self._lm
434 return key in self._lm
435
435
436 def __delitem__(self, key):
436 def __delitem__(self, key):
437 del self._lm[key]
437 del self._lm[key]
438
438
439 def __iter__(self):
439 def __iter__(self):
440 return self._lm.__iter__()
440 return self._lm.__iter__()
441
441
442 def iterkeys(self):
442 def iterkeys(self):
443 return self._lm.iterkeys()
443 return self._lm.iterkeys()
444
444
445 def keys(self):
445 def keys(self):
446 return list(self.iterkeys())
446 return list(self.iterkeys())
447
447
448 def filesnotin(self, m2):
448 def filesnotin(self, m2):
449 '''Set of files in this manifest that are not in the other'''
449 '''Set of files in this manifest that are not in the other'''
450 diff = self.diff(m2)
450 diff = self.diff(m2)
451 files = set(filepath
451 files = set(filepath
452 for filepath, hashflags in diff.iteritems()
452 for filepath, hashflags in diff.iteritems()
453 if hashflags[1][0] is None)
453 if hashflags[1][0] is None)
454 return files
454 return files
455
455
456 @propertycache
456 @propertycache
457 def _dirs(self):
457 def _dirs(self):
458 return util.dirs(self)
458 return util.dirs(self)
459
459
460 def dirs(self):
460 def dirs(self):
461 return self._dirs
461 return self._dirs
462
462
463 def hasdir(self, dir):
463 def hasdir(self, dir):
464 return dir in self._dirs
464 return dir in self._dirs
465
465
466 def _filesfastpath(self, match):
466 def _filesfastpath(self, match):
467 '''Checks whether we can correctly and quickly iterate over matcher
467 '''Checks whether we can correctly and quickly iterate over matcher
468 files instead of over manifest files.'''
468 files instead of over manifest files.'''
469 files = match.files()
469 files = match.files()
470 return (len(files) < 100 and (match.isexact() or
470 return (len(files) < 100 and (match.isexact() or
471 (match.prefix() and all(fn in self for fn in files))))
471 (match.prefix() and all(fn in self for fn in files))))
472
472
473 def walk(self, match):
473 def walk(self, match):
474 '''Generates matching file names.
474 '''Generates matching file names.
475
475
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
476 Equivalent to manifest.matches(match).iterkeys(), but without creating
477 an entirely new manifest.
477 an entirely new manifest.
478
478
479 It also reports nonexistent files by marking them bad with match.bad().
479 It also reports nonexistent files by marking them bad with match.bad().
480 '''
480 '''
481 if match.always():
481 if match.always():
482 for f in iter(self):
482 for f in iter(self):
483 yield f
483 yield f
484 return
484 return
485
485
486 fset = set(match.files())
486 fset = set(match.files())
487
487
488 # avoid the entire walk if we're only looking for specific files
488 # avoid the entire walk if we're only looking for specific files
489 if self._filesfastpath(match):
489 if self._filesfastpath(match):
490 for fn in sorted(fset):
490 for fn in sorted(fset):
491 yield fn
491 yield fn
492 return
492 return
493
493
494 for fn in self:
494 for fn in self:
495 if fn in fset:
495 if fn in fset:
496 # specified pattern is the exact name
496 # specified pattern is the exact name
497 fset.remove(fn)
497 fset.remove(fn)
498 if match(fn):
498 if match(fn):
499 yield fn
499 yield fn
500
500
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
501 # for dirstate.walk, files=['.'] means "walk the whole tree".
502 # follow that here, too
502 # follow that here, too
503 fset.discard('.')
503 fset.discard('.')
504
504
505 for fn in sorted(fset):
505 for fn in sorted(fset):
506 if not self.hasdir(fn):
506 if not self.hasdir(fn):
507 match.bad(fn, None)
507 match.bad(fn, None)
508
508
509 def matches(self, match):
509 def matches(self, match):
510 '''generate a new manifest filtered by the match argument'''
510 '''generate a new manifest filtered by the match argument'''
511 if match.always():
511 if match.always():
512 return self.copy()
512 return self.copy()
513
513
514 if self._filesfastpath(match):
514 if self._filesfastpath(match):
515 m = manifestdict()
515 m = manifestdict()
516 lm = self._lm
516 lm = self._lm
517 for fn in match.files():
517 for fn in match.files():
518 if fn in lm:
518 if fn in lm:
519 m._lm[fn] = lm[fn]
519 m._lm[fn] = lm[fn]
520 return m
520 return m
521
521
522 m = manifestdict()
522 m = manifestdict()
523 m._lm = self._lm.filtercopy(match)
523 m._lm = self._lm.filtercopy(match)
524 return m
524 return m
525
525
526 def diff(self, m2, clean=False):
526 def diff(self, m2, clean=False):
527 '''Finds changes between the current manifest and m2.
527 '''Finds changes between the current manifest and m2.
528
528
529 Args:
529 Args:
530 m2: the manifest to which this manifest should be compared.
530 m2: the manifest to which this manifest should be compared.
531 clean: if true, include files unchanged between these manifests
531 clean: if true, include files unchanged between these manifests
532 with a None value in the returned dictionary.
532 with a None value in the returned dictionary.
533
533
534 The result is returned as a dict with filename as key and
534 The result is returned as a dict with filename as key and
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
535 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
536 nodeid in the current/other manifest and fl1/fl2 is the flag
536 nodeid in the current/other manifest and fl1/fl2 is the flag
537 in the current/other manifest. Where the file does not exist,
537 in the current/other manifest. Where the file does not exist,
538 the nodeid will be None and the flags will be the empty
538 the nodeid will be None and the flags will be the empty
539 string.
539 string.
540 '''
540 '''
541 return self._lm.diff(m2._lm, clean)
541 return self._lm.diff(m2._lm, clean)
542
542
543 def setflag(self, key, flag):
543 def setflag(self, key, flag):
544 self._lm[key] = self[key], flag
544 self._lm[key] = self[key], flag
545
545
546 def get(self, key, default=None):
546 def get(self, key, default=None):
547 try:
547 try:
548 return self._lm[key][0]
548 return self._lm[key][0]
549 except KeyError:
549 except KeyError:
550 return default
550 return default
551
551
552 def flags(self, key, default=''):
552 def flags(self, key, default=''):
553 try:
553 try:
554 return self._lm[key][1]
554 return self._lm[key][1]
555 except KeyError:
555 except KeyError:
556 return default
556 return default
557
557
558 def copy(self):
558 def copy(self):
559 c = manifestdict()
559 c = manifestdict()
560 c._lm = self._lm.copy()
560 c._lm = self._lm.copy()
561 return c
561 return c
562
562
563 def iteritems(self):
563 def iteritems(self):
564 return (x[:2] for x in self._lm.iterentries())
564 return (x[:2] for x in self._lm.iterentries())
565
565
566 def iterentries(self):
566 def iterentries(self):
567 return self._lm.iterentries()
567 return self._lm.iterentries()
568
568
569 def text(self, usemanifestv2=False):
569 def text(self, usemanifestv2=False):
570 if usemanifestv2:
570 if usemanifestv2:
571 return _textv2(self._lm.iterentries())
571 return _textv2(self._lm.iterentries())
572 else:
572 else:
573 # use (probably) native version for v1
573 # use (probably) native version for v1
574 return self._lm.text()
574 return self._lm.text()
575
575
576 def fastdelta(self, base, changes):
576 def fastdelta(self, base, changes):
577 """Given a base manifest text as an array.array and a list of changes
577 """Given a base manifest text as an array.array and a list of changes
578 relative to that text, compute a delta that can be used by revlog.
578 relative to that text, compute a delta that can be used by revlog.
579 """
579 """
580 delta = []
580 delta = []
581 dstart = None
581 dstart = None
582 dend = None
582 dend = None
583 dline = [""]
583 dline = [""]
584 start = 0
584 start = 0
585 # zero copy representation of base as a buffer
585 # zero copy representation of base as a buffer
586 addbuf = util.buffer(base)
586 addbuf = util.buffer(base)
587
587
588 changes = list(changes)
588 changes = list(changes)
589 if len(changes) < 1000:
589 if len(changes) < 1000:
590 # start with a readonly loop that finds the offset of
590 # start with a readonly loop that finds the offset of
591 # each line and creates the deltas
591 # each line and creates the deltas
592 for f, todelete in changes:
592 for f, todelete in changes:
593 # bs will either be the index of the item or the insert point
593 # bs will either be the index of the item or the insert point
594 start, end = _msearch(addbuf, f, start)
594 start, end = _msearch(addbuf, f, start)
595 if not todelete:
595 if not todelete:
596 h, fl = self._lm[f]
596 h, fl = self._lm[f]
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
597 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
598 else:
598 else:
599 if start == end:
599 if start == end:
600 # item we want to delete was not found, error out
600 # item we want to delete was not found, error out
601 raise AssertionError(
601 raise AssertionError(
602 _("failed to remove %s from manifest") % f)
602 _("failed to remove %s from manifest") % f)
603 l = ""
603 l = ""
604 if dstart is not None and dstart <= start and dend >= start:
604 if dstart is not None and dstart <= start and dend >= start:
605 if dend < end:
605 if dend < end:
606 dend = end
606 dend = end
607 if l:
607 if l:
608 dline.append(l)
608 dline.append(l)
609 else:
609 else:
610 if dstart is not None:
610 if dstart is not None:
611 delta.append([dstart, dend, "".join(dline)])
611 delta.append([dstart, dend, "".join(dline)])
612 dstart = start
612 dstart = start
613 dend = end
613 dend = end
614 dline = [l]
614 dline = [l]
615
615
616 if dstart is not None:
616 if dstart is not None:
617 delta.append([dstart, dend, "".join(dline)])
617 delta.append([dstart, dend, "".join(dline)])
618 # apply the delta to the base, and get a delta for addrevision
618 # apply the delta to the base, and get a delta for addrevision
619 deltatext, arraytext = _addlistdelta(base, delta)
619 deltatext, arraytext = _addlistdelta(base, delta)
620 else:
620 else:
621 # For large changes, it's much cheaper to just build the text and
621 # For large changes, it's much cheaper to just build the text and
622 # diff it.
622 # diff it.
623 arraytext = array.array('c', self.text())
623 arraytext = array.array('c', self.text())
624 deltatext = mdiff.textdiff(base, arraytext)
624 deltatext = mdiff.textdiff(base, arraytext)
625
625
626 return arraytext, deltatext
626 return arraytext, deltatext
627
627
628 def _msearch(m, s, lo=0, hi=None):
628 def _msearch(m, s, lo=0, hi=None):
629 '''return a tuple (start, end) that says where to find s within m.
629 '''return a tuple (start, end) that says where to find s within m.
630
630
631 If the string is found m[start:end] are the line containing
631 If the string is found m[start:end] are the line containing
632 that string. If start == end the string was not found and
632 that string. If start == end the string was not found and
633 they indicate the proper sorted insertion point.
633 they indicate the proper sorted insertion point.
634
634
635 m should be a buffer or a string
635 m should be a buffer or a string
636 s is a string'''
636 s is a string'''
637 def advance(i, c):
637 def advance(i, c):
638 while i < lenm and m[i] != c:
638 while i < lenm and m[i] != c:
639 i += 1
639 i += 1
640 return i
640 return i
641 if not s:
641 if not s:
642 return (lo, lo)
642 return (lo, lo)
643 lenm = len(m)
643 lenm = len(m)
644 if not hi:
644 if not hi:
645 hi = lenm
645 hi = lenm
646 while lo < hi:
646 while lo < hi:
647 mid = (lo + hi) // 2
647 mid = (lo + hi) // 2
648 start = mid
648 start = mid
649 while start > 0 and m[start - 1] != '\n':
649 while start > 0 and m[start - 1] != '\n':
650 start -= 1
650 start -= 1
651 end = advance(start, '\0')
651 end = advance(start, '\0')
652 if m[start:end] < s:
652 if m[start:end] < s:
653 # we know that after the null there are 40 bytes of sha1
653 # we know that after the null there are 40 bytes of sha1
654 # this translates to the bisect lo = mid + 1
654 # this translates to the bisect lo = mid + 1
655 lo = advance(end + 40, '\n') + 1
655 lo = advance(end + 40, '\n') + 1
656 else:
656 else:
657 # this translates to the bisect hi = mid
657 # this translates to the bisect hi = mid
658 hi = start
658 hi = start
659 end = advance(lo, '\0')
659 end = advance(lo, '\0')
660 found = m[lo:end]
660 found = m[lo:end]
661 if s == found:
661 if s == found:
662 # we know that after the null there are 40 bytes of sha1
662 # we know that after the null there are 40 bytes of sha1
663 end = advance(end + 40, '\n')
663 end = advance(end + 40, '\n')
664 return (lo, end + 1)
664 return (lo, end + 1)
665 else:
665 else:
666 return (lo, lo)
666 return (lo, lo)
667
667
668 def _checkforbidden(l):
668 def _checkforbidden(l):
669 """Check filenames for illegal characters."""
669 """Check filenames for illegal characters."""
670 for f in l:
670 for f in l:
671 if '\n' in f or '\r' in f:
671 if '\n' in f or '\r' in f:
672 raise error.RevlogError(
672 raise error.RevlogError(
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
673 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
674
674
675
675
676 # apply the changes collected during the bisect loop to our addlist
676 # apply the changes collected during the bisect loop to our addlist
677 # return a delta suitable for addrevision
677 # return a delta suitable for addrevision
678 def _addlistdelta(addlist, x):
678 def _addlistdelta(addlist, x):
679 # for large addlist arrays, building a new array is cheaper
679 # for large addlist arrays, building a new array is cheaper
680 # than repeatedly modifying the existing one
680 # than repeatedly modifying the existing one
681 currentposition = 0
681 currentposition = 0
682 newaddlist = array.array('c')
682 newaddlist = array.array('c')
683
683
684 for start, end, content in x:
684 for start, end, content in x:
685 newaddlist += addlist[currentposition:start]
685 newaddlist += addlist[currentposition:start]
686 if content:
686 if content:
687 newaddlist += array.array('c', content)
687 newaddlist += array.array('c', content)
688
688
689 currentposition = end
689 currentposition = end
690
690
691 newaddlist += addlist[currentposition:]
691 newaddlist += addlist[currentposition:]
692
692
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
693 deltatext = "".join(struct.pack(">lll", start, end, len(content))
694 + content for start, end, content in x)
694 + content for start, end, content in x)
695 return deltatext, newaddlist
695 return deltatext, newaddlist
696
696
697 def _splittopdir(f):
697 def _splittopdir(f):
698 if '/' in f:
698 if '/' in f:
699 dir, subpath = f.split('/', 1)
699 dir, subpath = f.split('/', 1)
700 return dir + '/', subpath
700 return dir + '/', subpath
701 else:
701 else:
702 return '', f
702 return '', f
703
703
704 _noop = lambda s: None
704 _noop = lambda s: None
705
705
706 class treemanifest(object):
706 class treemanifest(object):
707 def __init__(self, dir='', text=''):
707 def __init__(self, dir='', text=''):
708 self._dir = dir
708 self._dir = dir
709 self._node = revlog.nullid
709 self._node = revlog.nullid
710 self._loadfunc = _noop
710 self._loadfunc = _noop
711 self._copyfunc = _noop
711 self._copyfunc = _noop
712 self._dirty = False
712 self._dirty = False
713 self._dirs = {}
713 self._dirs = {}
714 # Using _lazymanifest here is a little slower than plain old dicts
714 # Using _lazymanifest here is a little slower than plain old dicts
715 self._files = {}
715 self._files = {}
716 self._flags = {}
716 self._flags = {}
717 if text:
717 if text:
718 def readsubtree(subdir, subm):
718 def readsubtree(subdir, subm):
719 raise AssertionError('treemanifest constructor only accepts '
719 raise AssertionError('treemanifest constructor only accepts '
720 'flat manifests')
720 'flat manifests')
721 self.parse(text, readsubtree)
721 self.parse(text, readsubtree)
722 self._dirty = True # Mark flat manifest dirty after parsing
722 self._dirty = True # Mark flat manifest dirty after parsing
723
723
724 def _subpath(self, path):
724 def _subpath(self, path):
725 return self._dir + path
725 return self._dir + path
726
726
727 def __len__(self):
727 def __len__(self):
728 self._load()
728 self._load()
729 size = len(self._files)
729 size = len(self._files)
730 for m in self._dirs.values():
730 for m in self._dirs.values():
731 size += m.__len__()
731 size += m.__len__()
732 return size
732 return size
733
733
734 def _isempty(self):
734 def _isempty(self):
735 self._load() # for consistency; already loaded by all callers
735 self._load() # for consistency; already loaded by all callers
736 return (not self._files and (not self._dirs or
736 return (not self._files and (not self._dirs or
737 all(m._isempty() for m in self._dirs.values())))
737 all(m._isempty() for m in self._dirs.values())))
738
738
739 def __repr__(self):
739 def __repr__(self):
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
740 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
741 (self._dir, revlog.hex(self._node),
741 (self._dir, revlog.hex(self._node),
742 bool(self._loadfunc is _noop),
742 bool(self._loadfunc is _noop),
743 self._dirty, id(self)))
743 self._dirty, id(self)))
744
744
745 def dir(self):
745 def dir(self):
746 '''The directory that this tree manifest represents, including a
746 '''The directory that this tree manifest represents, including a
747 trailing '/'. Empty string for the repo root directory.'''
747 trailing '/'. Empty string for the repo root directory.'''
748 return self._dir
748 return self._dir
749
749
750 def node(self):
750 def node(self):
751 '''This node of this instance. nullid for unsaved instances. Should
751 '''This node of this instance. nullid for unsaved instances. Should
752 be updated when the instance is read or written from a revlog.
752 be updated when the instance is read or written from a revlog.
753 '''
753 '''
754 assert not self._dirty
754 assert not self._dirty
755 return self._node
755 return self._node
756
756
757 def setnode(self, node):
757 def setnode(self, node):
758 self._node = node
758 self._node = node
759 self._dirty = False
759 self._dirty = False
760
760
761 def iterentries(self):
761 def iterentries(self):
762 self._load()
762 self._load()
763 for p, n in sorted(self._dirs.items() + self._files.items()):
763 for p, n in sorted(self._dirs.items() + self._files.items()):
764 if p in self._files:
764 if p in self._files:
765 yield self._subpath(p), n, self._flags.get(p, '')
765 yield self._subpath(p), n, self._flags.get(p, '')
766 else:
766 else:
767 for x in n.iterentries():
767 for x in n.iterentries():
768 yield x
768 yield x
769
769
770 def iteritems(self):
770 def iteritems(self):
771 self._load()
771 self._load()
772 for p, n in sorted(self._dirs.items() + self._files.items()):
772 for p, n in sorted(self._dirs.items() + self._files.items()):
773 if p in self._files:
773 if p in self._files:
774 yield self._subpath(p), n
774 yield self._subpath(p), n
775 else:
775 else:
776 for f, sn in n.iteritems():
776 for f, sn in n.iteritems():
777 yield f, sn
777 yield f, sn
778
778
779 def iterkeys(self):
779 def iterkeys(self):
780 self._load()
780 self._load()
781 for p in sorted(self._dirs.keys() + self._files.keys()):
781 for p in sorted(self._dirs.keys() + self._files.keys()):
782 if p in self._files:
782 if p in self._files:
783 yield self._subpath(p)
783 yield self._subpath(p)
784 else:
784 else:
785 for f in self._dirs[p].iterkeys():
785 for f in self._dirs[p].iterkeys():
786 yield f
786 yield f
787
787
788 def keys(self):
788 def keys(self):
789 return list(self.iterkeys())
789 return list(self.iterkeys())
790
790
791 def __iter__(self):
791 def __iter__(self):
792 return self.iterkeys()
792 return self.iterkeys()
793
793
794 def __contains__(self, f):
794 def __contains__(self, f):
795 if f is None:
795 if f is None:
796 return False
796 return False
797 self._load()
797 self._load()
798 dir, subpath = _splittopdir(f)
798 dir, subpath = _splittopdir(f)
799 if dir:
799 if dir:
800 if dir not in self._dirs:
800 if dir not in self._dirs:
801 return False
801 return False
802 return self._dirs[dir].__contains__(subpath)
802 return self._dirs[dir].__contains__(subpath)
803 else:
803 else:
804 return f in self._files
804 return f in self._files
805
805
806 def get(self, f, default=None):
806 def get(self, f, default=None):
807 self._load()
807 self._load()
808 dir, subpath = _splittopdir(f)
808 dir, subpath = _splittopdir(f)
809 if dir:
809 if dir:
810 if dir not in self._dirs:
810 if dir not in self._dirs:
811 return default
811 return default
812 return self._dirs[dir].get(subpath, default)
812 return self._dirs[dir].get(subpath, default)
813 else:
813 else:
814 return self._files.get(f, default)
814 return self._files.get(f, default)
815
815
816 def __getitem__(self, f):
816 def __getitem__(self, f):
817 self._load()
817 self._load()
818 dir, subpath = _splittopdir(f)
818 dir, subpath = _splittopdir(f)
819 if dir:
819 if dir:
820 return self._dirs[dir].__getitem__(subpath)
820 return self._dirs[dir].__getitem__(subpath)
821 else:
821 else:
822 return self._files[f]
822 return self._files[f]
823
823
824 def flags(self, f):
824 def flags(self, f):
825 self._load()
825 self._load()
826 dir, subpath = _splittopdir(f)
826 dir, subpath = _splittopdir(f)
827 if dir:
827 if dir:
828 if dir not in self._dirs:
828 if dir not in self._dirs:
829 return ''
829 return ''
830 return self._dirs[dir].flags(subpath)
830 return self._dirs[dir].flags(subpath)
831 else:
831 else:
832 if f in self._dirs:
832 if f in self._dirs:
833 return ''
833 return ''
834 return self._flags.get(f, '')
834 return self._flags.get(f, '')
835
835
836 def find(self, f):
836 def find(self, f):
837 self._load()
837 self._load()
838 dir, subpath = _splittopdir(f)
838 dir, subpath = _splittopdir(f)
839 if dir:
839 if dir:
840 return self._dirs[dir].find(subpath)
840 return self._dirs[dir].find(subpath)
841 else:
841 else:
842 return self._files[f], self._flags.get(f, '')
842 return self._files[f], self._flags.get(f, '')
843
843
844 def __delitem__(self, f):
844 def __delitem__(self, f):
845 self._load()
845 self._load()
846 dir, subpath = _splittopdir(f)
846 dir, subpath = _splittopdir(f)
847 if dir:
847 if dir:
848 self._dirs[dir].__delitem__(subpath)
848 self._dirs[dir].__delitem__(subpath)
849 # If the directory is now empty, remove it
849 # If the directory is now empty, remove it
850 if self._dirs[dir]._isempty():
850 if self._dirs[dir]._isempty():
851 del self._dirs[dir]
851 del self._dirs[dir]
852 else:
852 else:
853 del self._files[f]
853 del self._files[f]
854 if f in self._flags:
854 if f in self._flags:
855 del self._flags[f]
855 del self._flags[f]
856 self._dirty = True
856 self._dirty = True
857
857
858 def __setitem__(self, f, n):
858 def __setitem__(self, f, n):
859 assert n is not None
859 assert n is not None
860 self._load()
860 self._load()
861 dir, subpath = _splittopdir(f)
861 dir, subpath = _splittopdir(f)
862 if dir:
862 if dir:
863 if dir not in self._dirs:
863 if dir not in self._dirs:
864 self._dirs[dir] = treemanifest(self._subpath(dir))
864 self._dirs[dir] = treemanifest(self._subpath(dir))
865 self._dirs[dir].__setitem__(subpath, n)
865 self._dirs[dir].__setitem__(subpath, n)
866 else:
866 else:
867 self._files[f] = n[:21] # to match manifestdict's behavior
867 self._files[f] = n[:21] # to match manifestdict's behavior
868 self._dirty = True
868 self._dirty = True
869
869
870 def _load(self):
870 def _load(self):
871 if self._loadfunc is not _noop:
871 if self._loadfunc is not _noop:
872 lf, self._loadfunc = self._loadfunc, _noop
872 lf, self._loadfunc = self._loadfunc, _noop
873 lf(self)
873 lf(self)
874 elif self._copyfunc is not _noop:
874 elif self._copyfunc is not _noop:
875 cf, self._copyfunc = self._copyfunc, _noop
875 cf, self._copyfunc = self._copyfunc, _noop
876 cf(self)
876 cf(self)
877
877
878 def setflag(self, f, flags):
878 def setflag(self, f, flags):
879 """Set the flags (symlink, executable) for path f."""
879 """Set the flags (symlink, executable) for path f."""
880 self._load()
880 self._load()
881 dir, subpath = _splittopdir(f)
881 dir, subpath = _splittopdir(f)
882 if dir:
882 if dir:
883 if dir not in self._dirs:
883 if dir not in self._dirs:
884 self._dirs[dir] = treemanifest(self._subpath(dir))
884 self._dirs[dir] = treemanifest(self._subpath(dir))
885 self._dirs[dir].setflag(subpath, flags)
885 self._dirs[dir].setflag(subpath, flags)
886 else:
886 else:
887 self._flags[f] = flags
887 self._flags[f] = flags
888 self._dirty = True
888 self._dirty = True
889
889
890 def copy(self):
890 def copy(self):
891 copy = treemanifest(self._dir)
891 copy = treemanifest(self._dir)
892 copy._node = self._node
892 copy._node = self._node
893 copy._dirty = self._dirty
893 copy._dirty = self._dirty
894 if self._copyfunc is _noop:
894 if self._copyfunc is _noop:
895 def _copyfunc(s):
895 def _copyfunc(s):
896 self._load()
896 self._load()
897 for d in self._dirs:
897 for d in self._dirs:
898 s._dirs[d] = self._dirs[d].copy()
898 s._dirs[d] = self._dirs[d].copy()
899 s._files = dict.copy(self._files)
899 s._files = dict.copy(self._files)
900 s._flags = dict.copy(self._flags)
900 s._flags = dict.copy(self._flags)
901 if self._loadfunc is _noop:
901 if self._loadfunc is _noop:
902 _copyfunc(copy)
902 _copyfunc(copy)
903 else:
903 else:
904 copy._copyfunc = _copyfunc
904 copy._copyfunc = _copyfunc
905 else:
905 else:
906 copy._copyfunc = self._copyfunc
906 copy._copyfunc = self._copyfunc
907 return copy
907 return copy
908
908
909 def filesnotin(self, m2):
909 def filesnotin(self, m2):
910 '''Set of files in this manifest that are not in the other'''
910 '''Set of files in this manifest that are not in the other'''
911 files = set()
911 files = set()
912 def _filesnotin(t1, t2):
912 def _filesnotin(t1, t2):
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
913 if t1._node == t2._node and not t1._dirty and not t2._dirty:
914 return
914 return
915 t1._load()
915 t1._load()
916 t2._load()
916 t2._load()
917 for d, m1 in t1._dirs.iteritems():
917 for d, m1 in t1._dirs.iteritems():
918 if d in t2._dirs:
918 if d in t2._dirs:
919 m2 = t2._dirs[d]
919 m2 = t2._dirs[d]
920 _filesnotin(m1, m2)
920 _filesnotin(m1, m2)
921 else:
921 else:
922 files.update(m1.iterkeys())
922 files.update(m1.iterkeys())
923
923
924 for fn in t1._files.iterkeys():
924 for fn in t1._files.iterkeys():
925 if fn not in t2._files:
925 if fn not in t2._files:
926 files.add(t1._subpath(fn))
926 files.add(t1._subpath(fn))
927
927
928 _filesnotin(self, m2)
928 _filesnotin(self, m2)
929 return files
929 return files
930
930
931 @propertycache
931 @propertycache
932 def _alldirs(self):
932 def _alldirs(self):
933 return util.dirs(self)
933 return util.dirs(self)
934
934
935 def dirs(self):
935 def dirs(self):
936 return self._alldirs
936 return self._alldirs
937
937
938 def hasdir(self, dir):
938 def hasdir(self, dir):
939 self._load()
939 self._load()
940 topdir, subdir = _splittopdir(dir)
940 topdir, subdir = _splittopdir(dir)
941 if topdir:
941 if topdir:
942 if topdir in self._dirs:
942 if topdir in self._dirs:
943 return self._dirs[topdir].hasdir(subdir)
943 return self._dirs[topdir].hasdir(subdir)
944 return False
944 return False
945 return (dir + '/') in self._dirs
945 return (dir + '/') in self._dirs
946
946
947 def walk(self, match):
947 def walk(self, match):
948 '''Generates matching file names.
948 '''Generates matching file names.
949
949
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
950 Equivalent to manifest.matches(match).iterkeys(), but without creating
951 an entirely new manifest.
951 an entirely new manifest.
952
952
953 It also reports nonexistent files by marking them bad with match.bad().
953 It also reports nonexistent files by marking them bad with match.bad().
954 '''
954 '''
955 if match.always():
955 if match.always():
956 for f in iter(self):
956 for f in iter(self):
957 yield f
957 yield f
958 return
958 return
959
959
960 fset = set(match.files())
960 fset = set(match.files())
961
961
962 for fn in self._walk(match):
962 for fn in self._walk(match):
963 if fn in fset:
963 if fn in fset:
964 # specified pattern is the exact name
964 # specified pattern is the exact name
965 fset.remove(fn)
965 fset.remove(fn)
966 yield fn
966 yield fn
967
967
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
968 # for dirstate.walk, files=['.'] means "walk the whole tree".
969 # follow that here, too
969 # follow that here, too
970 fset.discard('.')
970 fset.discard('.')
971
971
972 for fn in sorted(fset):
972 for fn in sorted(fset):
973 if not self.hasdir(fn):
973 if not self.hasdir(fn):
974 match.bad(fn, None)
974 match.bad(fn, None)
975
975
976 def _walk(self, match):
976 def _walk(self, match):
977 '''Recursively generates matching file names for walk().'''
977 '''Recursively generates matching file names for walk().'''
978 if not match.visitdir(self._dir[:-1] or '.'):
978 if not match.visitdir(self._dir[:-1] or '.'):
979 return
979 return
980
980
981 # yield this dir's files and walk its submanifests
981 # yield this dir's files and walk its submanifests
982 self._load()
982 self._load()
983 for p in sorted(self._dirs.keys() + self._files.keys()):
983 for p in sorted(self._dirs.keys() + self._files.keys()):
984 if p in self._files:
984 if p in self._files:
985 fullp = self._subpath(p)
985 fullp = self._subpath(p)
986 if match(fullp):
986 if match(fullp):
987 yield fullp
987 yield fullp
988 else:
988 else:
989 for f in self._dirs[p]._walk(match):
989 for f in self._dirs[p]._walk(match):
990 yield f
990 yield f
991
991
992 def matches(self, match):
992 def matches(self, match):
993 '''generate a new manifest filtered by the match argument'''
993 '''generate a new manifest filtered by the match argument'''
994 if match.always():
994 if match.always():
995 return self.copy()
995 return self.copy()
996
996
997 return self._matches(match)
997 return self._matches(match)
998
998
999 def _matches(self, match):
999 def _matches(self, match):
1000 '''recursively generate a new manifest filtered by the match argument.
1000 '''recursively generate a new manifest filtered by the match argument.
1001 '''
1001 '''
1002
1002
1003 visit = match.visitdir(self._dir[:-1] or '.')
1003 visit = match.visitdir(self._dir[:-1] or '.')
1004 if visit == 'all':
1004 if visit == 'all':
1005 return self.copy()
1005 return self.copy()
1006 ret = treemanifest(self._dir)
1006 ret = treemanifest(self._dir)
1007 if not visit:
1007 if not visit:
1008 return ret
1008 return ret
1009
1009
1010 self._load()
1010 self._load()
1011 for fn in self._files:
1011 for fn in self._files:
1012 fullp = self._subpath(fn)
1012 fullp = self._subpath(fn)
1013 if not match(fullp):
1013 if not match(fullp):
1014 continue
1014 continue
1015 ret._files[fn] = self._files[fn]
1015 ret._files[fn] = self._files[fn]
1016 if fn in self._flags:
1016 if fn in self._flags:
1017 ret._flags[fn] = self._flags[fn]
1017 ret._flags[fn] = self._flags[fn]
1018
1018
1019 for dir, subm in self._dirs.iteritems():
1019 for dir, subm in self._dirs.iteritems():
1020 m = subm._matches(match)
1020 m = subm._matches(match)
1021 if not m._isempty():
1021 if not m._isempty():
1022 ret._dirs[dir] = m
1022 ret._dirs[dir] = m
1023
1023
1024 if not ret._isempty():
1024 if not ret._isempty():
1025 ret._dirty = True
1025 ret._dirty = True
1026 return ret
1026 return ret
1027
1027
1028 def diff(self, m2, clean=False):
1028 def diff(self, m2, clean=False):
1029 '''Finds changes between the current manifest and m2.
1029 '''Finds changes between the current manifest and m2.
1030
1030
1031 Args:
1031 Args:
1032 m2: the manifest to which this manifest should be compared.
1032 m2: the manifest to which this manifest should be compared.
1033 clean: if true, include files unchanged between these manifests
1033 clean: if true, include files unchanged between these manifests
1034 with a None value in the returned dictionary.
1034 with a None value in the returned dictionary.
1035
1035
1036 The result is returned as a dict with filename as key and
1036 The result is returned as a dict with filename as key and
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1037 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1038 nodeid in the current/other manifest and fl1/fl2 is the flag
1039 in the current/other manifest. Where the file does not exist,
1039 in the current/other manifest. Where the file does not exist,
1040 the nodeid will be None and the flags will be the empty
1040 the nodeid will be None and the flags will be the empty
1041 string.
1041 string.
1042 '''
1042 '''
1043 result = {}
1043 result = {}
1044 emptytree = treemanifest()
1044 emptytree = treemanifest()
1045 def _diff(t1, t2):
1045 def _diff(t1, t2):
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1046 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1047 return
1047 return
1048 t1._load()
1048 t1._load()
1049 t2._load()
1049 t2._load()
1050 for d, m1 in t1._dirs.iteritems():
1050 for d, m1 in t1._dirs.iteritems():
1051 m2 = t2._dirs.get(d, emptytree)
1051 m2 = t2._dirs.get(d, emptytree)
1052 _diff(m1, m2)
1052 _diff(m1, m2)
1053
1053
1054 for d, m2 in t2._dirs.iteritems():
1054 for d, m2 in t2._dirs.iteritems():
1055 if d not in t1._dirs:
1055 if d not in t1._dirs:
1056 _diff(emptytree, m2)
1056 _diff(emptytree, m2)
1057
1057
1058 for fn, n1 in t1._files.iteritems():
1058 for fn, n1 in t1._files.iteritems():
1059 fl1 = t1._flags.get(fn, '')
1059 fl1 = t1._flags.get(fn, '')
1060 n2 = t2._files.get(fn, None)
1060 n2 = t2._files.get(fn, None)
1061 fl2 = t2._flags.get(fn, '')
1061 fl2 = t2._flags.get(fn, '')
1062 if n1 != n2 or fl1 != fl2:
1062 if n1 != n2 or fl1 != fl2:
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1063 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1064 elif clean:
1064 elif clean:
1065 result[t1._subpath(fn)] = None
1065 result[t1._subpath(fn)] = None
1066
1066
1067 for fn, n2 in t2._files.iteritems():
1067 for fn, n2 in t2._files.iteritems():
1068 if fn not in t1._files:
1068 if fn not in t1._files:
1069 fl2 = t2._flags.get(fn, '')
1069 fl2 = t2._flags.get(fn, '')
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1070 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1071
1071
1072 _diff(self, m2)
1072 _diff(self, m2)
1073 return result
1073 return result
1074
1074
1075 def unmodifiedsince(self, m2):
1075 def unmodifiedsince(self, m2):
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1076 return not self._dirty and not m2._dirty and self._node == m2._node
1077
1077
1078 def parse(self, text, readsubtree):
1078 def parse(self, text, readsubtree):
1079 for f, n, fl in _parse(text):
1079 for f, n, fl in _parse(text):
1080 if fl == 't':
1080 if fl == 't':
1081 f = f + '/'
1081 f = f + '/'
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1082 self._dirs[f] = readsubtree(self._subpath(f), n)
1083 elif '/' in f:
1083 elif '/' in f:
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1084 # This is a flat manifest, so use __setitem__ and setflag rather
1085 # than assigning directly to _files and _flags, so we can
1085 # than assigning directly to _files and _flags, so we can
1086 # assign a path in a subdirectory, and to mark dirty (compared
1086 # assign a path in a subdirectory, and to mark dirty (compared
1087 # to nullid).
1087 # to nullid).
1088 self[f] = n
1088 self[f] = n
1089 if fl:
1089 if fl:
1090 self.setflag(f, fl)
1090 self.setflag(f, fl)
1091 else:
1091 else:
1092 # Assigning to _files and _flags avoids marking as dirty,
1092 # Assigning to _files and _flags avoids marking as dirty,
1093 # and should be a little faster.
1093 # and should be a little faster.
1094 self._files[f] = n
1094 self._files[f] = n
1095 if fl:
1095 if fl:
1096 self._flags[f] = fl
1096 self._flags[f] = fl
1097
1097
1098 def text(self, usemanifestv2=False):
1098 def text(self, usemanifestv2=False):
1099 """Get the full data of this manifest as a bytestring."""
1099 """Get the full data of this manifest as a bytestring."""
1100 self._load()
1100 self._load()
1101 return _text(self.iterentries(), usemanifestv2)
1101 return _text(self.iterentries(), usemanifestv2)
1102
1102
1103 def dirtext(self, usemanifestv2=False):
1103 def dirtext(self, usemanifestv2=False):
1104 """Get the full data of this directory as a bytestring. Make sure that
1104 """Get the full data of this directory as a bytestring. Make sure that
1105 any submanifests have been written first, so their nodeids are correct.
1105 any submanifests have been written first, so their nodeids are correct.
1106 """
1106 """
1107 self._load()
1107 self._load()
1108 flags = self.flags
1108 flags = self.flags
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1109 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1110 files = [(f, self._files[f], flags(f)) for f in self._files]
1111 return _text(sorted(dirs + files), usemanifestv2)
1111 return _text(sorted(dirs + files), usemanifestv2)
1112
1112
1113 def read(self, gettext, readsubtree):
1113 def read(self, gettext, readsubtree):
1114 def _load_for_read(s):
1114 def _load_for_read(s):
1115 s.parse(gettext(), readsubtree)
1115 s.parse(gettext(), readsubtree)
1116 s._dirty = False
1116 s._dirty = False
1117 self._loadfunc = _load_for_read
1117 self._loadfunc = _load_for_read
1118
1118
1119 def writesubtrees(self, m1, m2, writesubtree):
1119 def writesubtrees(self, m1, m2, writesubtree):
1120 self._load() # for consistency; should never have any effect here
1120 self._load() # for consistency; should never have any effect here
1121 m1._load()
1121 m1._load()
1122 m2._load()
1122 m2._load()
1123 emptytree = treemanifest()
1123 emptytree = treemanifest()
1124 for d, subm in self._dirs.iteritems():
1124 for d, subm in self._dirs.iteritems():
1125 subp1 = m1._dirs.get(d, emptytree)._node
1125 subp1 = m1._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1126 subp2 = m2._dirs.get(d, emptytree)._node
1127 if subp1 == revlog.nullid:
1127 if subp1 == revlog.nullid:
1128 subp1, subp2 = subp2, subp1
1128 subp1, subp2 = subp2, subp1
1129 writesubtree(subm, subp1, subp2)
1129 writesubtree(subm, subp1, subp2)
1130
1130
1131 class manifestrevlog(revlog.revlog):
1131 class manifestrevlog(revlog.revlog):
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1132 '''A revlog that stores manifest texts. This is responsible for caching the
1133 full-text manifest contents.
1133 full-text manifest contents.
1134 '''
1134 '''
1135 def __init__(self, opener, dir='', dirlogcache=None):
1135 def __init__(self, opener, dir='', dirlogcache=None):
1136 # During normal operations, we expect to deal with not more than four
1136 # During normal operations, we expect to deal with not more than four
1137 # revs at a time (such as during commit --amend). When rebasing large
1137 # revs at a time (such as during commit --amend). When rebasing large
1138 # stacks of commits, the number can go up, hence the config knob below.
1138 # stacks of commits, the number can go up, hence the config knob below.
1139 cachesize = 4
1139 cachesize = 4
1140 usetreemanifest = False
1140 usetreemanifest = False
1141 usemanifestv2 = False
1141 usemanifestv2 = False
1142 opts = getattr(opener, 'options', None)
1142 opts = getattr(opener, 'options', None)
1143 if opts is not None:
1143 if opts is not None:
1144 cachesize = opts.get('manifestcachesize', cachesize)
1144 cachesize = opts.get('manifestcachesize', cachesize)
1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1145 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1146 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1147
1147
1148 self._treeondisk = usetreemanifest
1148 self._treeondisk = usetreemanifest
1149 self._usemanifestv2 = usemanifestv2
1149 self._usemanifestv2 = usemanifestv2
1150
1150
1151 self._fulltextcache = util.lrucachedict(cachesize)
1151 self._fulltextcache = util.lrucachedict(cachesize)
1152
1152
1153 indexfile = "00manifest.i"
1153 indexfile = "00manifest.i"
1154 if dir:
1154 if dir:
1155 assert self._treeondisk, 'opts is %r' % opts
1155 assert self._treeondisk, 'opts is %r' % opts
1156 if not dir.endswith('/'):
1156 if not dir.endswith('/'):
1157 dir = dir + '/'
1157 dir = dir + '/'
1158 indexfile = "meta/" + dir + "00manifest.i"
1158 indexfile = "meta/" + dir + "00manifest.i"
1159 self._dir = dir
1159 self._dir = dir
1160 # The dirlogcache is kept on the root manifest log
1160 # The dirlogcache is kept on the root manifest log
1161 if dir:
1161 if dir:
1162 self._dirlogcache = dirlogcache
1162 self._dirlogcache = dirlogcache
1163 else:
1163 else:
1164 self._dirlogcache = {'': self}
1164 self._dirlogcache = {'': self}
1165
1165
1166 super(manifestrevlog, self).__init__(opener, indexfile,
1166 super(manifestrevlog, self).__init__(opener, indexfile,
1167 checkambig=bool(dir))
1167 checkambig=bool(dir))
1168
1168
1169 @property
1169 @property
1170 def fulltextcache(self):
1170 def fulltextcache(self):
1171 return self._fulltextcache
1171 return self._fulltextcache
1172
1172
1173 def clearcaches(self):
1173 def clearcaches(self):
1174 super(manifestrevlog, self).clearcaches()
1174 super(manifestrevlog, self).clearcaches()
1175 self._fulltextcache.clear()
1175 self._fulltextcache.clear()
1176 self._dirlogcache = {'': self}
1176 self._dirlogcache = {'': self}
1177
1177
1178 def dirlog(self, dir):
1178 def dirlog(self, dir):
1179 if dir:
1179 if dir:
1180 assert self._treeondisk
1180 assert self._treeondisk
1181 if dir not in self._dirlogcache:
1181 if dir not in self._dirlogcache:
1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1182 self._dirlogcache[dir] = manifestrevlog(self.opener, dir,
1183 self._dirlogcache)
1183 self._dirlogcache)
1184 return self._dirlogcache[dir]
1184 return self._dirlogcache[dir]
1185
1185
1186 def add(self, m, transaction, link, p1, p2, added, removed):
1186 def add(self, m, transaction, link, p1, p2, added, removed):
1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1187 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1188 and not self._usemanifestv2):
1188 and not self._usemanifestv2):
1189 # If our first parent is in the manifest cache, we can
1189 # If our first parent is in the manifest cache, we can
1190 # compute a delta here using properties we know about the
1190 # compute a delta here using properties we know about the
1191 # manifest up-front, which may save time later for the
1191 # manifest up-front, which may save time later for the
1192 # revlog layer.
1192 # revlog layer.
1193
1193
1194 _checkforbidden(added)
1194 _checkforbidden(added)
1195 # combine the changed lists into one sorted iterator
1195 # combine the changed lists into one sorted iterator
1196 work = heapq.merge([(x, False) for x in added],
1196 work = heapq.merge([(x, False) for x in added],
1197 [(x, True) for x in removed])
1197 [(x, True) for x in removed])
1198
1198
1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1199 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1200 cachedelta = self.rev(p1), deltatext
1200 cachedelta = self.rev(p1), deltatext
1201 text = util.buffer(arraytext)
1201 text = util.buffer(arraytext)
1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1202 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1203 else:
1203 else:
1204 # The first parent manifest isn't already loaded, so we'll
1204 # The first parent manifest isn't already loaded, so we'll
1205 # just encode a fulltext of the manifest and pass that
1205 # just encode a fulltext of the manifest and pass that
1206 # through to the revlog layer, and let it handle the delta
1206 # through to the revlog layer, and let it handle the delta
1207 # process.
1207 # process.
1208 if self._treeondisk:
1208 if self._treeondisk:
1209 m1 = self.read(p1)
1209 m1 = self.read(p1)
1210 m2 = self.read(p2)
1210 m2 = self.read(p2)
1211 n = self._addtree(m, transaction, link, m1, m2)
1211 n = self._addtree(m, transaction, link, m1, m2)
1212 arraytext = None
1212 arraytext = None
1213 else:
1213 else:
1214 text = m.text(self._usemanifestv2)
1214 text = m.text(self._usemanifestv2)
1215 n = self.addrevision(text, transaction, link, p1, p2)
1215 n = self.addrevision(text, transaction, link, p1, p2)
1216 arraytext = array.array('c', text)
1216 arraytext = array.array('c', text)
1217
1217
1218 if arraytext is not None:
1218 if arraytext is not None:
1219 self.fulltextcache[n] = arraytext
1219 self.fulltextcache[n] = arraytext
1220
1220
1221 return n
1221 return n
1222
1222
1223 def _addtree(self, m, transaction, link, m1, m2):
1223 def _addtree(self, m, transaction, link, m1, m2):
1224 # If the manifest is unchanged compared to one parent,
1224 # If the manifest is unchanged compared to one parent,
1225 # don't write a new revision
1225 # don't write a new revision
1226 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1226 if m.unmodifiedsince(m1) or m.unmodifiedsince(m2):
1227 return m.node()
1227 return m.node()
1228 def writesubtree(subm, subp1, subp2):
1228 def writesubtree(subm, subp1, subp2):
1229 sublog = self.dirlog(subm.dir())
1229 sublog = self.dirlog(subm.dir())
1230 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1230 sublog.add(subm, transaction, link, subp1, subp2, None, None)
1231 m.writesubtrees(m1, m2, writesubtree)
1231 m.writesubtrees(m1, m2, writesubtree)
1232 text = m.dirtext(self._usemanifestv2)
1232 text = m.dirtext(self._usemanifestv2)
1233 # Double-check whether contents are unchanged to one parent
1233 # Double-check whether contents are unchanged to one parent
1234 if text == m1.dirtext(self._usemanifestv2):
1234 if text == m1.dirtext(self._usemanifestv2):
1235 n = m1.node()
1235 n = m1.node()
1236 elif text == m2.dirtext(self._usemanifestv2):
1236 elif text == m2.dirtext(self._usemanifestv2):
1237 n = m2.node()
1237 n = m2.node()
1238 else:
1238 else:
1239 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1239 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1240 # Save nodeid so parent manifest can calculate its nodeid
1240 # Save nodeid so parent manifest can calculate its nodeid
1241 m.setnode(n)
1241 m.setnode(n)
1242 return n
1242 return n
1243
1243
1244 class manifestlog(object):
1244 class manifestlog(object):
1245 """A collection class representing the collection of manifest snapshots
1245 """A collection class representing the collection of manifest snapshots
1246 referenced by commits in the repository.
1246 referenced by commits in the repository.
1247
1247
1248 In this situation, 'manifest' refers to the abstract concept of a snapshot
1248 In this situation, 'manifest' refers to the abstract concept of a snapshot
1249 of the list of files in the given commit. Consumers of the output of this
1249 of the list of files in the given commit. Consumers of the output of this
1250 class do not care about the implementation details of the actual manifests
1250 class do not care about the implementation details of the actual manifests
1251 they receive (i.e. tree or flat or lazily loaded, etc)."""
1251 they receive (i.e. tree or flat or lazily loaded, etc)."""
1252 def __init__(self, opener, repo):
1252 def __init__(self, opener, repo):
1253 self._repo = repo
1253 self._repo = repo
1254
1254
1255 usetreemanifest = False
1255 usetreemanifest = False
1256
1256
1257 opts = getattr(opener, 'options', None)
1257 opts = getattr(opener, 'options', None)
1258 if opts is not None:
1258 if opts is not None:
1259 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1259 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1260 self._treeinmem = usetreemanifest
1260 self._treeinmem = usetreemanifest
1261
1261
1262 self._oldmanifest = repo._constructmanifest()
1262 self._oldmanifest = repo._constructmanifest()
1263 self._revlog = self._oldmanifest
1263 self._revlog = self._oldmanifest
1264
1264
1265 # A cache of the manifestctx or treemanifestctx for each directory
1265 # A cache of the manifestctx or treemanifestctx for each directory
1266 self._dirmancache = {}
1266 self._dirmancache = {}
1267
1267
1268 # We'll separate this into it's own cache once oldmanifest is no longer
1268 # We'll separate this into it's own cache once oldmanifest is no longer
1269 # used
1269 # used
1270 self._mancache = self._oldmanifest._mancache
1270 self._mancache = self._oldmanifest._mancache
1271 self._dirmancache[''] = self._mancache
1271 self._dirmancache[''] = self._mancache
1272
1272
1273 # A future patch makes this use the same config value as the existing
1273 # A future patch makes this use the same config value as the existing
1274 # mancache
1274 # mancache
1275 self.cachesize = 4
1275 self.cachesize = 4
1276
1276
1277 def __getitem__(self, node):
1277 def __getitem__(self, node):
1278 """Retrieves the manifest instance for the given node. Throws a
1278 """Retrieves the manifest instance for the given node. Throws a
1279 LookupError if not found.
1279 LookupError if not found.
1280 """
1280 """
1281 return self.get('', node)
1281 return self.get('', node)
1282
1282
1283 def get(self, dir, node):
1283 def get(self, dir, node):
1284 """Retrieves the manifest instance for the given node. Throws a
1284 """Retrieves the manifest instance for the given node. Throws a
1285 LookupError if not found.
1285 LookupError if not found.
1286 """
1286 """
1287 if node in self._dirmancache.get(dir, ()):
1287 if node in self._dirmancache.get(dir, ()):
1288 cachemf = self._dirmancache[dir][node]
1288 cachemf = self._dirmancache[dir][node]
1289 # The old manifest may put non-ctx manifests in the cache, so
1289 # The old manifest may put non-ctx manifests in the cache, so
1290 # skip those since they don't implement the full api.
1290 # skip those since they don't implement the full api.
1291 if (isinstance(cachemf, manifestctx) or
1291 if (isinstance(cachemf, manifestctx) or
1292 isinstance(cachemf, treemanifestctx)):
1292 isinstance(cachemf, treemanifestctx)):
1293 return cachemf
1293 return cachemf
1294
1294
1295 if dir:
1295 if dir:
1296 if self._revlog._treeondisk:
1296 if self._revlog._treeondisk:
1297 dirlog = self._revlog.dirlog(dir)
1297 dirlog = self._revlog.dirlog(dir)
1298 if node not in dirlog.nodemap:
1298 if node not in dirlog.nodemap:
1299 raise LookupError(node, dirlog.indexfile,
1299 raise LookupError(node, dirlog.indexfile,
1300 _('no node'))
1300 _('no node'))
1301 m = treemanifestctx(self._repo, dir, node)
1301 m = treemanifestctx(self._repo, dir, node)
1302 else:
1302 else:
1303 raise error.Abort(
1303 raise error.Abort(
1304 _("cannot ask for manifest directory '%s' in a flat "
1304 _("cannot ask for manifest directory '%s' in a flat "
1305 "manifest") % dir)
1305 "manifest") % dir)
1306 else:
1306 else:
1307 if node not in self._revlog.nodemap:
1307 if node not in self._revlog.nodemap:
1308 raise LookupError(node, self._revlog.indexfile,
1308 raise LookupError(node, self._revlog.indexfile,
1309 _('no node'))
1309 _('no node'))
1310 if self._treeinmem:
1310 if self._treeinmem:
1311 m = treemanifestctx(self._repo, '', node)
1311 m = treemanifestctx(self._repo, '', node)
1312 else:
1312 else:
1313 m = manifestctx(self._repo, node)
1313 m = manifestctx(self._repo, node)
1314
1314
1315 if node != revlog.nullid:
1315 if node != revlog.nullid:
1316 mancache = self._dirmancache.get(dir)
1316 mancache = self._dirmancache.get(dir)
1317 if not mancache:
1317 if not mancache:
1318 mancache = util.lrucachedict(self.cachesize)
1318 mancache = util.lrucachedict(self.cachesize)
1319 self._dirmancache[dir] = mancache
1319 self._dirmancache[dir] = mancache
1320 mancache[node] = m
1320 mancache[node] = m
1321 return m
1321 return m
1322
1322
1323 def add(self, m, transaction, link, p1, p2, added, removed):
1324 return self._revlog.add(m, transaction, link, p1, p2, added, removed)
1325
1326 class memmanifestctx(object):
1323 class memmanifestctx(object):
1327 def __init__(self, repo):
1324 def __init__(self, repo):
1328 self._repo = repo
1325 self._repo = repo
1329 self._manifestdict = manifestdict()
1326 self._manifestdict = manifestdict()
1330
1327
1328 def _revlog(self):
1329 return self._repo.manifestlog._revlog
1330
1331 def new(self):
1331 def new(self):
1332 return memmanifestctx(self._repo)
1332 return memmanifestctx(self._repo)
1333
1333
1334 def copy(self):
1334 def copy(self):
1335 memmf = memmanifestctx(self._repo)
1335 memmf = memmanifestctx(self._repo)
1336 memmf._manifestdict = self.read().copy()
1336 memmf._manifestdict = self.read().copy()
1337 return memmf
1337 return memmf
1338
1338
1339 def read(self):
1339 def read(self):
1340 return self._manifestdict
1340 return self._manifestdict
1341
1341
1342 def write(self, transaction, link, p1, p2, added, removed):
1343 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1344 added, removed)
1345
1342 class manifestctx(object):
1346 class manifestctx(object):
1343 """A class representing a single revision of a manifest, including its
1347 """A class representing a single revision of a manifest, including its
1344 contents, its parent revs, and its linkrev.
1348 contents, its parent revs, and its linkrev.
1345 """
1349 """
1346 def __init__(self, repo, node):
1350 def __init__(self, repo, node):
1347 self._repo = repo
1351 self._repo = repo
1348 self._data = None
1352 self._data = None
1349
1353
1350 self._node = node
1354 self._node = node
1351
1355
1352 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1356 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1353 # but let's add it later when something needs it and we can load it
1357 # but let's add it later when something needs it and we can load it
1354 # lazily.
1358 # lazily.
1355 #self.p1, self.p2 = revlog.parents(node)
1359 #self.p1, self.p2 = revlog.parents(node)
1356 #rev = revlog.rev(node)
1360 #rev = revlog.rev(node)
1357 #self.linkrev = revlog.linkrev(rev)
1361 #self.linkrev = revlog.linkrev(rev)
1358
1362
1359 def _revlog(self):
1363 def _revlog(self):
1360 return self._repo.manifestlog._revlog
1364 return self._repo.manifestlog._revlog
1361
1365
1362 def node(self):
1366 def node(self):
1363 return self._node
1367 return self._node
1364
1368
1365 def new(self):
1369 def new(self):
1366 return memmanifestctx(self._repo)
1370 return memmanifestctx(self._repo)
1367
1371
1368 def copy(self):
1372 def copy(self):
1369 memmf = memmanifestctx(self._repo)
1373 memmf = memmanifestctx(self._repo)
1370 memmf._manifestdict = self.read().copy()
1374 memmf._manifestdict = self.read().copy()
1371 return memmf
1375 return memmf
1372
1376
1373 def read(self):
1377 def read(self):
1374 if not self._data:
1378 if not self._data:
1375 if self._node == revlog.nullid:
1379 if self._node == revlog.nullid:
1376 self._data = manifestdict()
1380 self._data = manifestdict()
1377 else:
1381 else:
1378 rl = self._revlog()
1382 rl = self._revlog()
1379 text = rl.revision(self._node)
1383 text = rl.revision(self._node)
1380 arraytext = array.array('c', text)
1384 arraytext = array.array('c', text)
1381 rl._fulltextcache[self._node] = arraytext
1385 rl._fulltextcache[self._node] = arraytext
1382 self._data = manifestdict(text)
1386 self._data = manifestdict(text)
1383 return self._data
1387 return self._data
1384
1388
1385 def readfast(self, shallow=False):
1389 def readfast(self, shallow=False):
1386 '''Calls either readdelta or read, based on which would be less work.
1390 '''Calls either readdelta or read, based on which would be less work.
1387 readdelta is called if the delta is against the p1, and therefore can be
1391 readdelta is called if the delta is against the p1, and therefore can be
1388 read quickly.
1392 read quickly.
1389
1393
1390 If `shallow` is True, nothing changes since this is a flat manifest.
1394 If `shallow` is True, nothing changes since this is a flat manifest.
1391 '''
1395 '''
1392 rl = self._revlog()
1396 rl = self._revlog()
1393 r = rl.rev(self._node)
1397 r = rl.rev(self._node)
1394 deltaparent = rl.deltaparent(r)
1398 deltaparent = rl.deltaparent(r)
1395 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1399 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1396 return self.readdelta()
1400 return self.readdelta()
1397 return self.read()
1401 return self.read()
1398
1402
1399 def readdelta(self, shallow=False):
1403 def readdelta(self, shallow=False):
1400 '''Returns a manifest containing just the entries that are present
1404 '''Returns a manifest containing just the entries that are present
1401 in this manifest, but not in its p1 manifest. This is efficient to read
1405 in this manifest, but not in its p1 manifest. This is efficient to read
1402 if the revlog delta is already p1.
1406 if the revlog delta is already p1.
1403
1407
1404 Changing the value of `shallow` has no effect on flat manifests.
1408 Changing the value of `shallow` has no effect on flat manifests.
1405 '''
1409 '''
1406 revlog = self._revlog()
1410 revlog = self._revlog()
1407 if revlog._usemanifestv2:
1411 if revlog._usemanifestv2:
1408 # Need to perform a slow delta
1412 # Need to perform a slow delta
1409 r0 = revlog.deltaparent(revlog.rev(self._node))
1413 r0 = revlog.deltaparent(revlog.rev(self._node))
1410 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1414 m0 = manifestctx(self._repo, revlog.node(r0)).read()
1411 m1 = self.read()
1415 m1 = self.read()
1412 md = manifestdict()
1416 md = manifestdict()
1413 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1417 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1414 if n1:
1418 if n1:
1415 md[f] = n1
1419 md[f] = n1
1416 if fl1:
1420 if fl1:
1417 md.setflag(f, fl1)
1421 md.setflag(f, fl1)
1418 return md
1422 return md
1419
1423
1420 r = revlog.rev(self._node)
1424 r = revlog.rev(self._node)
1421 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1425 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1422 return manifestdict(d)
1426 return manifestdict(d)
1423
1427
1424 def find(self, key):
1428 def find(self, key):
1425 return self.read().find(key)
1429 return self.read().find(key)
1426
1430
1427 class memtreemanifestctx(object):
1431 class memtreemanifestctx(object):
1428 def __init__(self, repo, dir=''):
1432 def __init__(self, repo, dir=''):
1429 self._repo = repo
1433 self._repo = repo
1430 self._dir = dir
1434 self._dir = dir
1431 self._treemanifest = treemanifest()
1435 self._treemanifest = treemanifest()
1432
1436
1437 def _revlog(self):
1438 return self._repo.manifestlog._revlog
1439
1433 def new(self, dir=''):
1440 def new(self, dir=''):
1434 return memtreemanifestctx(self._repo, dir=dir)
1441 return memtreemanifestctx(self._repo, dir=dir)
1435
1442
1436 def copy(self):
1443 def copy(self):
1437 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1444 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1438 memmf._treemanifest = self._treemanifest.copy()
1445 memmf._treemanifest = self._treemanifest.copy()
1439 return memmf
1446 return memmf
1440
1447
1441 def read(self):
1448 def read(self):
1442 return self._treemanifest
1449 return self._treemanifest
1443
1450
1451 def write(self, transaction, link, p1, p2, added, removed):
1452 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1453 added, removed)
1454
1444 class treemanifestctx(object):
1455 class treemanifestctx(object):
1445 def __init__(self, repo, dir, node):
1456 def __init__(self, repo, dir, node):
1446 self._repo = repo
1457 self._repo = repo
1447 self._dir = dir
1458 self._dir = dir
1448 self._data = None
1459 self._data = None
1449
1460
1450 self._node = node
1461 self._node = node
1451
1462
1452 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1463 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1453 # we can instantiate treemanifestctx objects for directories we don't
1464 # we can instantiate treemanifestctx objects for directories we don't
1454 # have on disk.
1465 # have on disk.
1455 #self.p1, self.p2 = revlog.parents(node)
1466 #self.p1, self.p2 = revlog.parents(node)
1456 #rev = revlog.rev(node)
1467 #rev = revlog.rev(node)
1457 #self.linkrev = revlog.linkrev(rev)
1468 #self.linkrev = revlog.linkrev(rev)
1458
1469
1459 def _revlog(self):
1470 def _revlog(self):
1460 return self._repo.manifestlog._revlog.dirlog(self._dir)
1471 return self._repo.manifestlog._revlog.dirlog(self._dir)
1461
1472
1462 def read(self):
1473 def read(self):
1463 if not self._data:
1474 if not self._data:
1464 rl = self._revlog()
1475 rl = self._revlog()
1465 if self._node == revlog.nullid:
1476 if self._node == revlog.nullid:
1466 self._data = treemanifest()
1477 self._data = treemanifest()
1467 elif rl._treeondisk:
1478 elif rl._treeondisk:
1468 m = treemanifest(dir=self._dir)
1479 m = treemanifest(dir=self._dir)
1469 def gettext():
1480 def gettext():
1470 return rl.revision(self._node)
1481 return rl.revision(self._node)
1471 def readsubtree(dir, subm):
1482 def readsubtree(dir, subm):
1472 return treemanifestctx(self._repo, dir, subm).read()
1483 return treemanifestctx(self._repo, dir, subm).read()
1473 m.read(gettext, readsubtree)
1484 m.read(gettext, readsubtree)
1474 m.setnode(self._node)
1485 m.setnode(self._node)
1475 self._data = m
1486 self._data = m
1476 else:
1487 else:
1477 text = rl.revision(self._node)
1488 text = rl.revision(self._node)
1478 arraytext = array.array('c', text)
1489 arraytext = array.array('c', text)
1479 rl.fulltextcache[self._node] = arraytext
1490 rl.fulltextcache[self._node] = arraytext
1480 self._data = treemanifest(dir=self._dir, text=text)
1491 self._data = treemanifest(dir=self._dir, text=text)
1481
1492
1482 return self._data
1493 return self._data
1483
1494
1484 def node(self):
1495 def node(self):
1485 return self._node
1496 return self._node
1486
1497
1487 def new(self, dir=''):
1498 def new(self, dir=''):
1488 return memtreemanifestctx(self._repo, dir=dir)
1499 return memtreemanifestctx(self._repo, dir=dir)
1489
1500
1490 def copy(self):
1501 def copy(self):
1491 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1502 memmf = memtreemanifestctx(self._repo, dir=self._dir)
1492 memmf._treemanifest = self.read().copy()
1503 memmf._treemanifest = self.read().copy()
1493 return memmf
1504 return memmf
1494
1505
1495 def readdelta(self, shallow=False):
1506 def readdelta(self, shallow=False):
1496 '''Returns a manifest containing just the entries that are present
1507 '''Returns a manifest containing just the entries that are present
1497 in this manifest, but not in its p1 manifest. This is efficient to read
1508 in this manifest, but not in its p1 manifest. This is efficient to read
1498 if the revlog delta is already p1.
1509 if the revlog delta is already p1.
1499
1510
1500 If `shallow` is True, this will read the delta for this directory,
1511 If `shallow` is True, this will read the delta for this directory,
1501 without recursively reading subdirectory manifests. Instead, any
1512 without recursively reading subdirectory manifests. Instead, any
1502 subdirectory entry will be reported as it appears in the manifest, i.e.
1513 subdirectory entry will be reported as it appears in the manifest, i.e.
1503 the subdirectory will be reported among files and distinguished only by
1514 the subdirectory will be reported among files and distinguished only by
1504 its 't' flag.
1515 its 't' flag.
1505 '''
1516 '''
1506 revlog = self._revlog()
1517 revlog = self._revlog()
1507 if shallow and not revlog._usemanifestv2:
1518 if shallow and not revlog._usemanifestv2:
1508 r = revlog.rev(self._node)
1519 r = revlog.rev(self._node)
1509 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1520 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1510 return manifestdict(d)
1521 return manifestdict(d)
1511 else:
1522 else:
1512 # Need to perform a slow delta
1523 # Need to perform a slow delta
1513 r0 = revlog.deltaparent(revlog.rev(self._node))
1524 r0 = revlog.deltaparent(revlog.rev(self._node))
1514 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1525 m0 = treemanifestctx(self._repo, self._dir, revlog.node(r0)).read()
1515 m1 = self.read()
1526 m1 = self.read()
1516 md = treemanifest(dir=self._dir)
1527 md = treemanifest(dir=self._dir)
1517 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1528 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1518 if n1:
1529 if n1:
1519 md[f] = n1
1530 md[f] = n1
1520 if fl1:
1531 if fl1:
1521 md.setflag(f, fl1)
1532 md.setflag(f, fl1)
1522 return md
1533 return md
1523
1534
1524 def readfast(self, shallow=False):
1535 def readfast(self, shallow=False):
1525 '''Calls either readdelta or read, based on which would be less work.
1536 '''Calls either readdelta or read, based on which would be less work.
1526 readdelta is called if the delta is against the p1, and therefore can be
1537 readdelta is called if the delta is against the p1, and therefore can be
1527 read quickly.
1538 read quickly.
1528
1539
1529 If `shallow` is True, it only returns the entries from this manifest,
1540 If `shallow` is True, it only returns the entries from this manifest,
1530 and not any submanifests.
1541 and not any submanifests.
1531 '''
1542 '''
1532 rl = self._revlog()
1543 rl = self._revlog()
1533 r = rl.rev(self._node)
1544 r = rl.rev(self._node)
1534 deltaparent = rl.deltaparent(r)
1545 deltaparent = rl.deltaparent(r)
1535 if (deltaparent != revlog.nullrev and
1546 if (deltaparent != revlog.nullrev and
1536 deltaparent in rl.parentrevs(r)):
1547 deltaparent in rl.parentrevs(r)):
1537 return self.readdelta(shallow=shallow)
1548 return self.readdelta(shallow=shallow)
1538
1549
1539 if shallow:
1550 if shallow:
1540 return manifestdict(rl.revision(self._node))
1551 return manifestdict(rl.revision(self._node))
1541 else:
1552 else:
1542 return self.read()
1553 return self.read()
1543
1554
1544 def find(self, key):
1555 def find(self, key):
1545 return self.read().find(key)
1556 return self.read().find(key)
1546
1557
1547 class manifest(manifestrevlog):
1558 class manifest(manifestrevlog):
1548 def __init__(self, opener, dir='', dirlogcache=None):
1559 def __init__(self, opener, dir='', dirlogcache=None):
1549 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1560 '''The 'dir' and 'dirlogcache' arguments are for internal use by
1550 manifest.manifest only. External users should create a root manifest
1561 manifest.manifest only. External users should create a root manifest
1551 log with manifest.manifest(opener) and call dirlog() on it.
1562 log with manifest.manifest(opener) and call dirlog() on it.
1552 '''
1563 '''
1553 # During normal operations, we expect to deal with not more than four
1564 # During normal operations, we expect to deal with not more than four
1554 # revs at a time (such as during commit --amend). When rebasing large
1565 # revs at a time (such as during commit --amend). When rebasing large
1555 # stacks of commits, the number can go up, hence the config knob below.
1566 # stacks of commits, the number can go up, hence the config knob below.
1556 cachesize = 4
1567 cachesize = 4
1557 usetreemanifest = False
1568 usetreemanifest = False
1558 opts = getattr(opener, 'options', None)
1569 opts = getattr(opener, 'options', None)
1559 if opts is not None:
1570 if opts is not None:
1560 cachesize = opts.get('manifestcachesize', cachesize)
1571 cachesize = opts.get('manifestcachesize', cachesize)
1561 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1572 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1562 self._mancache = util.lrucachedict(cachesize)
1573 self._mancache = util.lrucachedict(cachesize)
1563 self._treeinmem = usetreemanifest
1574 self._treeinmem = usetreemanifest
1564 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1575 super(manifest, self).__init__(opener, dir=dir, dirlogcache=dirlogcache)
1565
1576
1566 def _newmanifest(self, data=''):
1577 def _newmanifest(self, data=''):
1567 if self._treeinmem:
1578 if self._treeinmem:
1568 return treemanifest(self._dir, data)
1579 return treemanifest(self._dir, data)
1569 return manifestdict(data)
1580 return manifestdict(data)
1570
1581
1571 def dirlog(self, dir):
1582 def dirlog(self, dir):
1572 """This overrides the base revlog implementation to allow construction
1583 """This overrides the base revlog implementation to allow construction
1573 'manifest' types instead of manifestrevlog types. This is only needed
1584 'manifest' types instead of manifestrevlog types. This is only needed
1574 until we migrate off the 'manifest' type."""
1585 until we migrate off the 'manifest' type."""
1575 if dir:
1586 if dir:
1576 assert self._treeondisk
1587 assert self._treeondisk
1577 if dir not in self._dirlogcache:
1588 if dir not in self._dirlogcache:
1578 self._dirlogcache[dir] = manifest(self.opener, dir,
1589 self._dirlogcache[dir] = manifest(self.opener, dir,
1579 self._dirlogcache)
1590 self._dirlogcache)
1580 return self._dirlogcache[dir]
1591 return self._dirlogcache[dir]
1581
1592
1582 def read(self, node):
1593 def read(self, node):
1583 if node == revlog.nullid:
1594 if node == revlog.nullid:
1584 return self._newmanifest() # don't upset local cache
1595 return self._newmanifest() # don't upset local cache
1585 if node in self._mancache:
1596 if node in self._mancache:
1586 cached = self._mancache[node]
1597 cached = self._mancache[node]
1587 if (isinstance(cached, manifestctx) or
1598 if (isinstance(cached, manifestctx) or
1588 isinstance(cached, treemanifestctx)):
1599 isinstance(cached, treemanifestctx)):
1589 cached = cached.read()
1600 cached = cached.read()
1590 return cached
1601 return cached
1591 if self._treeondisk:
1602 if self._treeondisk:
1592 def gettext():
1603 def gettext():
1593 return self.revision(node)
1604 return self.revision(node)
1594 def readsubtree(dir, subm):
1605 def readsubtree(dir, subm):
1595 return self.dirlog(dir).read(subm)
1606 return self.dirlog(dir).read(subm)
1596 m = self._newmanifest()
1607 m = self._newmanifest()
1597 m.read(gettext, readsubtree)
1608 m.read(gettext, readsubtree)
1598 m.setnode(node)
1609 m.setnode(node)
1599 arraytext = None
1610 arraytext = None
1600 else:
1611 else:
1601 text = self.revision(node)
1612 text = self.revision(node)
1602 m = self._newmanifest(text)
1613 m = self._newmanifest(text)
1603 arraytext = array.array('c', text)
1614 arraytext = array.array('c', text)
1604 self._mancache[node] = m
1615 self._mancache[node] = m
1605 if arraytext is not None:
1616 if arraytext is not None:
1606 self.fulltextcache[node] = arraytext
1617 self.fulltextcache[node] = arraytext
1607 return m
1618 return m
1608
1619
1609 def clearcaches(self):
1620 def clearcaches(self):
1610 super(manifest, self).clearcaches()
1621 super(manifest, self).clearcaches()
1611 self._mancache.clear()
1622 self._mancache.clear()
General Comments 0
You need to be logged in to leave comments. Login now