##// END OF EJS Templates
cleanup: rename all iteritems methods to items and add iteritems alias...
Augie Fackler -
r32550:b98199a5 default
parent child Browse files
Show More
@@ -1,1311 +1,1313 b''
1 # dirstate.py - working directory tracking for mercurial
1 # dirstate.py - working directory tracking 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 collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import os
13 import os
14 import stat
14 import stat
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import nullid
17 from .node import nullid
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 match as matchmod,
21 match as matchmod,
22 pathutil,
22 pathutil,
23 policy,
23 policy,
24 pycompat,
24 pycompat,
25 scmutil,
25 scmutil,
26 txnutil,
26 txnutil,
27 util,
27 util,
28 )
28 )
29
29
30 parsers = policy.importmod(r'parsers')
30 parsers = policy.importmod(r'parsers')
31
31
32 propertycache = util.propertycache
32 propertycache = util.propertycache
33 filecache = scmutil.filecache
33 filecache = scmutil.filecache
34 _rangemask = 0x7fffffff
34 _rangemask = 0x7fffffff
35
35
36 dirstatetuple = parsers.dirstatetuple
36 dirstatetuple = parsers.dirstatetuple
37
37
38 class repocache(filecache):
38 class repocache(filecache):
39 """filecache for files in .hg/"""
39 """filecache for files in .hg/"""
40 def join(self, obj, fname):
40 def join(self, obj, fname):
41 return obj._opener.join(fname)
41 return obj._opener.join(fname)
42
42
43 class rootcache(filecache):
43 class rootcache(filecache):
44 """filecache for files in the repository root"""
44 """filecache for files in the repository root"""
45 def join(self, obj, fname):
45 def join(self, obj, fname):
46 return obj._join(fname)
46 return obj._join(fname)
47
47
48 def _getfsnow(vfs):
48 def _getfsnow(vfs):
49 '''Get "now" timestamp on filesystem'''
49 '''Get "now" timestamp on filesystem'''
50 tmpfd, tmpname = vfs.mkstemp()
50 tmpfd, tmpname = vfs.mkstemp()
51 try:
51 try:
52 return os.fstat(tmpfd).st_mtime
52 return os.fstat(tmpfd).st_mtime
53 finally:
53 finally:
54 os.close(tmpfd)
54 os.close(tmpfd)
55 vfs.unlink(tmpname)
55 vfs.unlink(tmpname)
56
56
57 def nonnormalentries(dmap):
57 def nonnormalentries(dmap):
58 '''Compute the nonnormal dirstate entries from the dmap'''
58 '''Compute the nonnormal dirstate entries from the dmap'''
59 try:
59 try:
60 return parsers.nonnormalotherparententries(dmap)
60 return parsers.nonnormalotherparententries(dmap)
61 except AttributeError:
61 except AttributeError:
62 nonnorm = set()
62 nonnorm = set()
63 otherparent = set()
63 otherparent = set()
64 for fname, e in dmap.iteritems():
64 for fname, e in dmap.iteritems():
65 if e[0] != 'n' or e[3] == -1:
65 if e[0] != 'n' or e[3] == -1:
66 nonnorm.add(fname)
66 nonnorm.add(fname)
67 if e[0] == 'n' and e[2] == -2:
67 if e[0] == 'n' and e[2] == -2:
68 otherparent.add(fname)
68 otherparent.add(fname)
69 return nonnorm, otherparent
69 return nonnorm, otherparent
70
70
71 class dirstate(object):
71 class dirstate(object):
72
72
73 def __init__(self, opener, ui, root, validate):
73 def __init__(self, opener, ui, root, validate):
74 '''Create a new dirstate object.
74 '''Create a new dirstate object.
75
75
76 opener is an open()-like callable that can be used to open the
76 opener is an open()-like callable that can be used to open the
77 dirstate file; root is the root of the directory tracked by
77 dirstate file; root is the root of the directory tracked by
78 the dirstate.
78 the dirstate.
79 '''
79 '''
80 self._opener = opener
80 self._opener = opener
81 self._validate = validate
81 self._validate = validate
82 self._root = root
82 self._root = root
83 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
83 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
84 # UNC path pointing to root share (issue4557)
84 # UNC path pointing to root share (issue4557)
85 self._rootdir = pathutil.normasprefix(root)
85 self._rootdir = pathutil.normasprefix(root)
86 # internal config: ui.forcecwd
86 # internal config: ui.forcecwd
87 forcecwd = ui.config('ui', 'forcecwd')
87 forcecwd = ui.config('ui', 'forcecwd')
88 if forcecwd:
88 if forcecwd:
89 self._cwd = forcecwd
89 self._cwd = forcecwd
90 self._dirty = False
90 self._dirty = False
91 self._dirtypl = False
91 self._dirtypl = False
92 self._lastnormaltime = 0
92 self._lastnormaltime = 0
93 self._ui = ui
93 self._ui = ui
94 self._filecache = {}
94 self._filecache = {}
95 self._parentwriters = 0
95 self._parentwriters = 0
96 self._filename = 'dirstate'
96 self._filename = 'dirstate'
97 self._pendingfilename = '%s.pending' % self._filename
97 self._pendingfilename = '%s.pending' % self._filename
98 self._plchangecallbacks = {}
98 self._plchangecallbacks = {}
99 self._origpl = None
99 self._origpl = None
100 self._updatedfiles = set()
100 self._updatedfiles = set()
101
101
102 # for consistent view between _pl() and _read() invocations
102 # for consistent view between _pl() and _read() invocations
103 self._pendingmode = None
103 self._pendingmode = None
104
104
105 @contextlib.contextmanager
105 @contextlib.contextmanager
106 def parentchange(self):
106 def parentchange(self):
107 '''Context manager for handling dirstate parents.
107 '''Context manager for handling dirstate parents.
108
108
109 If an exception occurs in the scope of the context manager,
109 If an exception occurs in the scope of the context manager,
110 the incoherent dirstate won't be written when wlock is
110 the incoherent dirstate won't be written when wlock is
111 released.
111 released.
112 '''
112 '''
113 self._parentwriters += 1
113 self._parentwriters += 1
114 yield
114 yield
115 # Typically we want the "undo" step of a context manager in a
115 # Typically we want the "undo" step of a context manager in a
116 # finally block so it happens even when an exception
116 # finally block so it happens even when an exception
117 # occurs. In this case, however, we only want to decrement
117 # occurs. In this case, however, we only want to decrement
118 # parentwriters if the code in the with statement exits
118 # parentwriters if the code in the with statement exits
119 # normally, so we don't have a try/finally here on purpose.
119 # normally, so we don't have a try/finally here on purpose.
120 self._parentwriters -= 1
120 self._parentwriters -= 1
121
121
122 def beginparentchange(self):
122 def beginparentchange(self):
123 '''Marks the beginning of a set of changes that involve changing
123 '''Marks the beginning of a set of changes that involve changing
124 the dirstate parents. If there is an exception during this time,
124 the dirstate parents. If there is an exception during this time,
125 the dirstate will not be written when the wlock is released. This
125 the dirstate will not be written when the wlock is released. This
126 prevents writing an incoherent dirstate where the parent doesn't
126 prevents writing an incoherent dirstate where the parent doesn't
127 match the contents.
127 match the contents.
128 '''
128 '''
129 self._ui.deprecwarn('beginparentchange is obsoleted by the '
129 self._ui.deprecwarn('beginparentchange is obsoleted by the '
130 'parentchange context manager.', '4.3')
130 'parentchange context manager.', '4.3')
131 self._parentwriters += 1
131 self._parentwriters += 1
132
132
133 def endparentchange(self):
133 def endparentchange(self):
134 '''Marks the end of a set of changes that involve changing the
134 '''Marks the end of a set of changes that involve changing the
135 dirstate parents. Once all parent changes have been marked done,
135 dirstate parents. Once all parent changes have been marked done,
136 the wlock will be free to write the dirstate on release.
136 the wlock will be free to write the dirstate on release.
137 '''
137 '''
138 self._ui.deprecwarn('endparentchange is obsoleted by the '
138 self._ui.deprecwarn('endparentchange is obsoleted by the '
139 'parentchange context manager.', '4.3')
139 'parentchange context manager.', '4.3')
140 if self._parentwriters > 0:
140 if self._parentwriters > 0:
141 self._parentwriters -= 1
141 self._parentwriters -= 1
142
142
143 def pendingparentchange(self):
143 def pendingparentchange(self):
144 '''Returns true if the dirstate is in the middle of a set of changes
144 '''Returns true if the dirstate is in the middle of a set of changes
145 that modify the dirstate parent.
145 that modify the dirstate parent.
146 '''
146 '''
147 return self._parentwriters > 0
147 return self._parentwriters > 0
148
148
149 @propertycache
149 @propertycache
150 def _map(self):
150 def _map(self):
151 '''Return the dirstate contents as a map from filename to
151 '''Return the dirstate contents as a map from filename to
152 (state, mode, size, time).'''
152 (state, mode, size, time).'''
153 self._read()
153 self._read()
154 return self._map
154 return self._map
155
155
156 @propertycache
156 @propertycache
157 def _copymap(self):
157 def _copymap(self):
158 self._read()
158 self._read()
159 return self._copymap
159 return self._copymap
160
160
161 @propertycache
161 @propertycache
162 def _nonnormalset(self):
162 def _nonnormalset(self):
163 nonnorm, otherparents = nonnormalentries(self._map)
163 nonnorm, otherparents = nonnormalentries(self._map)
164 self._otherparentset = otherparents
164 self._otherparentset = otherparents
165 return nonnorm
165 return nonnorm
166
166
167 @propertycache
167 @propertycache
168 def _otherparentset(self):
168 def _otherparentset(self):
169 nonnorm, otherparents = nonnormalentries(self._map)
169 nonnorm, otherparents = nonnormalentries(self._map)
170 self._nonnormalset = nonnorm
170 self._nonnormalset = nonnorm
171 return otherparents
171 return otherparents
172
172
173 @propertycache
173 @propertycache
174 def _filefoldmap(self):
174 def _filefoldmap(self):
175 try:
175 try:
176 makefilefoldmap = parsers.make_file_foldmap
176 makefilefoldmap = parsers.make_file_foldmap
177 except AttributeError:
177 except AttributeError:
178 pass
178 pass
179 else:
179 else:
180 return makefilefoldmap(self._map, util.normcasespec,
180 return makefilefoldmap(self._map, util.normcasespec,
181 util.normcasefallback)
181 util.normcasefallback)
182
182
183 f = {}
183 f = {}
184 normcase = util.normcase
184 normcase = util.normcase
185 for name, s in self._map.iteritems():
185 for name, s in self._map.iteritems():
186 if s[0] != 'r':
186 if s[0] != 'r':
187 f[normcase(name)] = name
187 f[normcase(name)] = name
188 f['.'] = '.' # prevents useless util.fspath() invocation
188 f['.'] = '.' # prevents useless util.fspath() invocation
189 return f
189 return f
190
190
191 @propertycache
191 @propertycache
192 def _dirfoldmap(self):
192 def _dirfoldmap(self):
193 f = {}
193 f = {}
194 normcase = util.normcase
194 normcase = util.normcase
195 for name in self._dirs:
195 for name in self._dirs:
196 f[normcase(name)] = name
196 f[normcase(name)] = name
197 return f
197 return f
198
198
199 @repocache('branch')
199 @repocache('branch')
200 def _branch(self):
200 def _branch(self):
201 try:
201 try:
202 return self._opener.read("branch").strip() or "default"
202 return self._opener.read("branch").strip() or "default"
203 except IOError as inst:
203 except IOError as inst:
204 if inst.errno != errno.ENOENT:
204 if inst.errno != errno.ENOENT:
205 raise
205 raise
206 return "default"
206 return "default"
207
207
208 @propertycache
208 @propertycache
209 def _pl(self):
209 def _pl(self):
210 try:
210 try:
211 fp = self._opendirstatefile()
211 fp = self._opendirstatefile()
212 st = fp.read(40)
212 st = fp.read(40)
213 fp.close()
213 fp.close()
214 l = len(st)
214 l = len(st)
215 if l == 40:
215 if l == 40:
216 return st[:20], st[20:40]
216 return st[:20], st[20:40]
217 elif l > 0 and l < 40:
217 elif l > 0 and l < 40:
218 raise error.Abort(_('working directory state appears damaged!'))
218 raise error.Abort(_('working directory state appears damaged!'))
219 except IOError as err:
219 except IOError as err:
220 if err.errno != errno.ENOENT:
220 if err.errno != errno.ENOENT:
221 raise
221 raise
222 return [nullid, nullid]
222 return [nullid, nullid]
223
223
224 @propertycache
224 @propertycache
225 def _dirs(self):
225 def _dirs(self):
226 return util.dirs(self._map, 'r')
226 return util.dirs(self._map, 'r')
227
227
228 def dirs(self):
228 def dirs(self):
229 return self._dirs
229 return self._dirs
230
230
231 @rootcache('.hgignore')
231 @rootcache('.hgignore')
232 def _ignore(self):
232 def _ignore(self):
233 files = self._ignorefiles()
233 files = self._ignorefiles()
234 if not files:
234 if not files:
235 return util.never
235 return util.never
236
236
237 pats = ['include:%s' % f for f in files]
237 pats = ['include:%s' % f for f in files]
238 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
238 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
239
239
240 @propertycache
240 @propertycache
241 def _slash(self):
241 def _slash(self):
242 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
242 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
243
243
244 @propertycache
244 @propertycache
245 def _checklink(self):
245 def _checklink(self):
246 return util.checklink(self._root)
246 return util.checklink(self._root)
247
247
248 @propertycache
248 @propertycache
249 def _checkexec(self):
249 def _checkexec(self):
250 return util.checkexec(self._root)
250 return util.checkexec(self._root)
251
251
252 @propertycache
252 @propertycache
253 def _checkcase(self):
253 def _checkcase(self):
254 return not util.fscasesensitive(self._join('.hg'))
254 return not util.fscasesensitive(self._join('.hg'))
255
255
256 def _join(self, f):
256 def _join(self, f):
257 # much faster than os.path.join()
257 # much faster than os.path.join()
258 # it's safe because f is always a relative path
258 # it's safe because f is always a relative path
259 return self._rootdir + f
259 return self._rootdir + f
260
260
261 def flagfunc(self, buildfallback):
261 def flagfunc(self, buildfallback):
262 if self._checklink and self._checkexec:
262 if self._checklink and self._checkexec:
263 def f(x):
263 def f(x):
264 try:
264 try:
265 st = os.lstat(self._join(x))
265 st = os.lstat(self._join(x))
266 if util.statislink(st):
266 if util.statislink(st):
267 return 'l'
267 return 'l'
268 if util.statisexec(st):
268 if util.statisexec(st):
269 return 'x'
269 return 'x'
270 except OSError:
270 except OSError:
271 pass
271 pass
272 return ''
272 return ''
273 return f
273 return f
274
274
275 fallback = buildfallback()
275 fallback = buildfallback()
276 if self._checklink:
276 if self._checklink:
277 def f(x):
277 def f(x):
278 if os.path.islink(self._join(x)):
278 if os.path.islink(self._join(x)):
279 return 'l'
279 return 'l'
280 if 'x' in fallback(x):
280 if 'x' in fallback(x):
281 return 'x'
281 return 'x'
282 return ''
282 return ''
283 return f
283 return f
284 if self._checkexec:
284 if self._checkexec:
285 def f(x):
285 def f(x):
286 if 'l' in fallback(x):
286 if 'l' in fallback(x):
287 return 'l'
287 return 'l'
288 if util.isexec(self._join(x)):
288 if util.isexec(self._join(x)):
289 return 'x'
289 return 'x'
290 return ''
290 return ''
291 return f
291 return f
292 else:
292 else:
293 return fallback
293 return fallback
294
294
295 @propertycache
295 @propertycache
296 def _cwd(self):
296 def _cwd(self):
297 return pycompat.getcwd()
297 return pycompat.getcwd()
298
298
299 def getcwd(self):
299 def getcwd(self):
300 '''Return the path from which a canonical path is calculated.
300 '''Return the path from which a canonical path is calculated.
301
301
302 This path should be used to resolve file patterns or to convert
302 This path should be used to resolve file patterns or to convert
303 canonical paths back to file paths for display. It shouldn't be
303 canonical paths back to file paths for display. It shouldn't be
304 used to get real file paths. Use vfs functions instead.
304 used to get real file paths. Use vfs functions instead.
305 '''
305 '''
306 cwd = self._cwd
306 cwd = self._cwd
307 if cwd == self._root:
307 if cwd == self._root:
308 return ''
308 return ''
309 # self._root ends with a path separator if self._root is '/' or 'C:\'
309 # self._root ends with a path separator if self._root is '/' or 'C:\'
310 rootsep = self._root
310 rootsep = self._root
311 if not util.endswithsep(rootsep):
311 if not util.endswithsep(rootsep):
312 rootsep += pycompat.ossep
312 rootsep += pycompat.ossep
313 if cwd.startswith(rootsep):
313 if cwd.startswith(rootsep):
314 return cwd[len(rootsep):]
314 return cwd[len(rootsep):]
315 else:
315 else:
316 # we're outside the repo. return an absolute path.
316 # we're outside the repo. return an absolute path.
317 return cwd
317 return cwd
318
318
319 def pathto(self, f, cwd=None):
319 def pathto(self, f, cwd=None):
320 if cwd is None:
320 if cwd is None:
321 cwd = self.getcwd()
321 cwd = self.getcwd()
322 path = util.pathto(self._root, cwd, f)
322 path = util.pathto(self._root, cwd, f)
323 if self._slash:
323 if self._slash:
324 return util.pconvert(path)
324 return util.pconvert(path)
325 return path
325 return path
326
326
327 def __getitem__(self, key):
327 def __getitem__(self, key):
328 '''Return the current state of key (a filename) in the dirstate.
328 '''Return the current state of key (a filename) in the dirstate.
329
329
330 States are:
330 States are:
331 n normal
331 n normal
332 m needs merging
332 m needs merging
333 r marked for removal
333 r marked for removal
334 a marked for addition
334 a marked for addition
335 ? not tracked
335 ? not tracked
336 '''
336 '''
337 return self._map.get(key, ("?",))[0]
337 return self._map.get(key, ("?",))[0]
338
338
339 def __contains__(self, key):
339 def __contains__(self, key):
340 return key in self._map
340 return key in self._map
341
341
342 def __iter__(self):
342 def __iter__(self):
343 for x in sorted(self._map):
343 for x in sorted(self._map):
344 yield x
344 yield x
345
345
346 def iteritems(self):
346 def items(self):
347 return self._map.iteritems()
347 return self._map.iteritems()
348
348
349 iteritems = items
350
349 def parents(self):
351 def parents(self):
350 return [self._validate(p) for p in self._pl]
352 return [self._validate(p) for p in self._pl]
351
353
352 def p1(self):
354 def p1(self):
353 return self._validate(self._pl[0])
355 return self._validate(self._pl[0])
354
356
355 def p2(self):
357 def p2(self):
356 return self._validate(self._pl[1])
358 return self._validate(self._pl[1])
357
359
358 def branch(self):
360 def branch(self):
359 return encoding.tolocal(self._branch)
361 return encoding.tolocal(self._branch)
360
362
361 def setparents(self, p1, p2=nullid):
363 def setparents(self, p1, p2=nullid):
362 """Set dirstate parents to p1 and p2.
364 """Set dirstate parents to p1 and p2.
363
365
364 When moving from two parents to one, 'm' merged entries a
366 When moving from two parents to one, 'm' merged entries a
365 adjusted to normal and previous copy records discarded and
367 adjusted to normal and previous copy records discarded and
366 returned by the call.
368 returned by the call.
367
369
368 See localrepo.setparents()
370 See localrepo.setparents()
369 """
371 """
370 if self._parentwriters == 0:
372 if self._parentwriters == 0:
371 raise ValueError("cannot set dirstate parent without "
373 raise ValueError("cannot set dirstate parent without "
372 "calling dirstate.beginparentchange")
374 "calling dirstate.beginparentchange")
373
375
374 self._dirty = self._dirtypl = True
376 self._dirty = self._dirtypl = True
375 oldp2 = self._pl[1]
377 oldp2 = self._pl[1]
376 if self._origpl is None:
378 if self._origpl is None:
377 self._origpl = self._pl
379 self._origpl = self._pl
378 self._pl = p1, p2
380 self._pl = p1, p2
379 copies = {}
381 copies = {}
380 if oldp2 != nullid and p2 == nullid:
382 if oldp2 != nullid and p2 == nullid:
381 candidatefiles = self._nonnormalset.union(self._otherparentset)
383 candidatefiles = self._nonnormalset.union(self._otherparentset)
382 for f in candidatefiles:
384 for f in candidatefiles:
383 s = self._map.get(f)
385 s = self._map.get(f)
384 if s is None:
386 if s is None:
385 continue
387 continue
386
388
387 # Discard 'm' markers when moving away from a merge state
389 # Discard 'm' markers when moving away from a merge state
388 if s[0] == 'm':
390 if s[0] == 'm':
389 if f in self._copymap:
391 if f in self._copymap:
390 copies[f] = self._copymap[f]
392 copies[f] = self._copymap[f]
391 self.normallookup(f)
393 self.normallookup(f)
392 # Also fix up otherparent markers
394 # Also fix up otherparent markers
393 elif s[0] == 'n' and s[2] == -2:
395 elif s[0] == 'n' and s[2] == -2:
394 if f in self._copymap:
396 if f in self._copymap:
395 copies[f] = self._copymap[f]
397 copies[f] = self._copymap[f]
396 self.add(f)
398 self.add(f)
397 return copies
399 return copies
398
400
399 def setbranch(self, branch):
401 def setbranch(self, branch):
400 self._branch = encoding.fromlocal(branch)
402 self._branch = encoding.fromlocal(branch)
401 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
403 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
402 try:
404 try:
403 f.write(self._branch + '\n')
405 f.write(self._branch + '\n')
404 f.close()
406 f.close()
405
407
406 # make sure filecache has the correct stat info for _branch after
408 # make sure filecache has the correct stat info for _branch after
407 # replacing the underlying file
409 # replacing the underlying file
408 ce = self._filecache['_branch']
410 ce = self._filecache['_branch']
409 if ce:
411 if ce:
410 ce.refresh()
412 ce.refresh()
411 except: # re-raises
413 except: # re-raises
412 f.discard()
414 f.discard()
413 raise
415 raise
414
416
415 def _opendirstatefile(self):
417 def _opendirstatefile(self):
416 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
418 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
417 if self._pendingmode is not None and self._pendingmode != mode:
419 if self._pendingmode is not None and self._pendingmode != mode:
418 fp.close()
420 fp.close()
419 raise error.Abort(_('working directory state may be '
421 raise error.Abort(_('working directory state may be '
420 'changed parallelly'))
422 'changed parallelly'))
421 self._pendingmode = mode
423 self._pendingmode = mode
422 return fp
424 return fp
423
425
424 def _read(self):
426 def _read(self):
425 self._map = {}
427 self._map = {}
426 self._copymap = {}
428 self._copymap = {}
427 try:
429 try:
428 fp = self._opendirstatefile()
430 fp = self._opendirstatefile()
429 try:
431 try:
430 st = fp.read()
432 st = fp.read()
431 finally:
433 finally:
432 fp.close()
434 fp.close()
433 except IOError as err:
435 except IOError as err:
434 if err.errno != errno.ENOENT:
436 if err.errno != errno.ENOENT:
435 raise
437 raise
436 return
438 return
437 if not st:
439 if not st:
438 return
440 return
439
441
440 if util.safehasattr(parsers, 'dict_new_presized'):
442 if util.safehasattr(parsers, 'dict_new_presized'):
441 # Make an estimate of the number of files in the dirstate based on
443 # Make an estimate of the number of files in the dirstate based on
442 # its size. From a linear regression on a set of real-world repos,
444 # its size. From a linear regression on a set of real-world repos,
443 # all over 10,000 files, the size of a dirstate entry is 85
445 # all over 10,000 files, the size of a dirstate entry is 85
444 # bytes. The cost of resizing is significantly higher than the cost
446 # bytes. The cost of resizing is significantly higher than the cost
445 # of filling in a larger presized dict, so subtract 20% from the
447 # of filling in a larger presized dict, so subtract 20% from the
446 # size.
448 # size.
447 #
449 #
448 # This heuristic is imperfect in many ways, so in a future dirstate
450 # This heuristic is imperfect in many ways, so in a future dirstate
449 # format update it makes sense to just record the number of entries
451 # format update it makes sense to just record the number of entries
450 # on write.
452 # on write.
451 self._map = parsers.dict_new_presized(len(st) / 71)
453 self._map = parsers.dict_new_presized(len(st) / 71)
452
454
453 # Python's garbage collector triggers a GC each time a certain number
455 # Python's garbage collector triggers a GC each time a certain number
454 # of container objects (the number being defined by
456 # of container objects (the number being defined by
455 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
457 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
456 # for each file in the dirstate. The C version then immediately marks
458 # for each file in the dirstate. The C version then immediately marks
457 # them as not to be tracked by the collector. However, this has no
459 # them as not to be tracked by the collector. However, this has no
458 # effect on when GCs are triggered, only on what objects the GC looks
460 # effect on when GCs are triggered, only on what objects the GC looks
459 # into. This means that O(number of files) GCs are unavoidable.
461 # into. This means that O(number of files) GCs are unavoidable.
460 # Depending on when in the process's lifetime the dirstate is parsed,
462 # Depending on when in the process's lifetime the dirstate is parsed,
461 # this can get very expensive. As a workaround, disable GC while
463 # this can get very expensive. As a workaround, disable GC while
462 # parsing the dirstate.
464 # parsing the dirstate.
463 #
465 #
464 # (we cannot decorate the function directly since it is in a C module)
466 # (we cannot decorate the function directly since it is in a C module)
465 parse_dirstate = util.nogc(parsers.parse_dirstate)
467 parse_dirstate = util.nogc(parsers.parse_dirstate)
466 p = parse_dirstate(self._map, self._copymap, st)
468 p = parse_dirstate(self._map, self._copymap, st)
467 if not self._dirtypl:
469 if not self._dirtypl:
468 self._pl = p
470 self._pl = p
469
471
470 def invalidate(self):
472 def invalidate(self):
471 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
473 for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch",
472 "_pl", "_dirs", "_ignore", "_nonnormalset",
474 "_pl", "_dirs", "_ignore", "_nonnormalset",
473 "_otherparentset"):
475 "_otherparentset"):
474 if a in self.__dict__:
476 if a in self.__dict__:
475 delattr(self, a)
477 delattr(self, a)
476 self._lastnormaltime = 0
478 self._lastnormaltime = 0
477 self._dirty = False
479 self._dirty = False
478 self._updatedfiles.clear()
480 self._updatedfiles.clear()
479 self._parentwriters = 0
481 self._parentwriters = 0
480 self._origpl = None
482 self._origpl = None
481
483
482 def copy(self, source, dest):
484 def copy(self, source, dest):
483 """Mark dest as a copy of source. Unmark dest if source is None."""
485 """Mark dest as a copy of source. Unmark dest if source is None."""
484 if source == dest:
486 if source == dest:
485 return
487 return
486 self._dirty = True
488 self._dirty = True
487 if source is not None:
489 if source is not None:
488 self._copymap[dest] = source
490 self._copymap[dest] = source
489 self._updatedfiles.add(source)
491 self._updatedfiles.add(source)
490 self._updatedfiles.add(dest)
492 self._updatedfiles.add(dest)
491 elif dest in self._copymap:
493 elif dest in self._copymap:
492 del self._copymap[dest]
494 del self._copymap[dest]
493 self._updatedfiles.add(dest)
495 self._updatedfiles.add(dest)
494
496
495 def copied(self, file):
497 def copied(self, file):
496 return self._copymap.get(file, None)
498 return self._copymap.get(file, None)
497
499
498 def copies(self):
500 def copies(self):
499 return self._copymap
501 return self._copymap
500
502
501 def _droppath(self, f):
503 def _droppath(self, f):
502 if self[f] not in "?r" and "_dirs" in self.__dict__:
504 if self[f] not in "?r" and "_dirs" in self.__dict__:
503 self._dirs.delpath(f)
505 self._dirs.delpath(f)
504
506
505 if "_filefoldmap" in self.__dict__:
507 if "_filefoldmap" in self.__dict__:
506 normed = util.normcase(f)
508 normed = util.normcase(f)
507 if normed in self._filefoldmap:
509 if normed in self._filefoldmap:
508 del self._filefoldmap[normed]
510 del self._filefoldmap[normed]
509
511
510 self._updatedfiles.add(f)
512 self._updatedfiles.add(f)
511
513
512 def _addpath(self, f, state, mode, size, mtime):
514 def _addpath(self, f, state, mode, size, mtime):
513 oldstate = self[f]
515 oldstate = self[f]
514 if state == 'a' or oldstate == 'r':
516 if state == 'a' or oldstate == 'r':
515 scmutil.checkfilename(f)
517 scmutil.checkfilename(f)
516 if f in self._dirs:
518 if f in self._dirs:
517 raise error.Abort(_('directory %r already in dirstate') % f)
519 raise error.Abort(_('directory %r already in dirstate') % f)
518 # shadows
520 # shadows
519 for d in util.finddirs(f):
521 for d in util.finddirs(f):
520 if d in self._dirs:
522 if d in self._dirs:
521 break
523 break
522 if d in self._map and self[d] != 'r':
524 if d in self._map and self[d] != 'r':
523 raise error.Abort(
525 raise error.Abort(
524 _('file %r in dirstate clashes with %r') % (d, f))
526 _('file %r in dirstate clashes with %r') % (d, f))
525 if oldstate in "?r" and "_dirs" in self.__dict__:
527 if oldstate in "?r" and "_dirs" in self.__dict__:
526 self._dirs.addpath(f)
528 self._dirs.addpath(f)
527 self._dirty = True
529 self._dirty = True
528 self._updatedfiles.add(f)
530 self._updatedfiles.add(f)
529 self._map[f] = dirstatetuple(state, mode, size, mtime)
531 self._map[f] = dirstatetuple(state, mode, size, mtime)
530 if state != 'n' or mtime == -1:
532 if state != 'n' or mtime == -1:
531 self._nonnormalset.add(f)
533 self._nonnormalset.add(f)
532 if size == -2:
534 if size == -2:
533 self._otherparentset.add(f)
535 self._otherparentset.add(f)
534
536
535 def normal(self, f):
537 def normal(self, f):
536 '''Mark a file normal and clean.'''
538 '''Mark a file normal and clean.'''
537 s = os.lstat(self._join(f))
539 s = os.lstat(self._join(f))
538 mtime = s.st_mtime
540 mtime = s.st_mtime
539 self._addpath(f, 'n', s.st_mode,
541 self._addpath(f, 'n', s.st_mode,
540 s.st_size & _rangemask, mtime & _rangemask)
542 s.st_size & _rangemask, mtime & _rangemask)
541 if f in self._copymap:
543 if f in self._copymap:
542 del self._copymap[f]
544 del self._copymap[f]
543 if f in self._nonnormalset:
545 if f in self._nonnormalset:
544 self._nonnormalset.remove(f)
546 self._nonnormalset.remove(f)
545 if mtime > self._lastnormaltime:
547 if mtime > self._lastnormaltime:
546 # Remember the most recent modification timeslot for status(),
548 # Remember the most recent modification timeslot for status(),
547 # to make sure we won't miss future size-preserving file content
549 # to make sure we won't miss future size-preserving file content
548 # modifications that happen within the same timeslot.
550 # modifications that happen within the same timeslot.
549 self._lastnormaltime = mtime
551 self._lastnormaltime = mtime
550
552
551 def normallookup(self, f):
553 def normallookup(self, f):
552 '''Mark a file normal, but possibly dirty.'''
554 '''Mark a file normal, but possibly dirty.'''
553 if self._pl[1] != nullid and f in self._map:
555 if self._pl[1] != nullid and f in self._map:
554 # if there is a merge going on and the file was either
556 # if there is a merge going on and the file was either
555 # in state 'm' (-1) or coming from other parent (-2) before
557 # in state 'm' (-1) or coming from other parent (-2) before
556 # being removed, restore that state.
558 # being removed, restore that state.
557 entry = self._map[f]
559 entry = self._map[f]
558 if entry[0] == 'r' and entry[2] in (-1, -2):
560 if entry[0] == 'r' and entry[2] in (-1, -2):
559 source = self._copymap.get(f)
561 source = self._copymap.get(f)
560 if entry[2] == -1:
562 if entry[2] == -1:
561 self.merge(f)
563 self.merge(f)
562 elif entry[2] == -2:
564 elif entry[2] == -2:
563 self.otherparent(f)
565 self.otherparent(f)
564 if source:
566 if source:
565 self.copy(source, f)
567 self.copy(source, f)
566 return
568 return
567 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
569 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
568 return
570 return
569 self._addpath(f, 'n', 0, -1, -1)
571 self._addpath(f, 'n', 0, -1, -1)
570 if f in self._copymap:
572 if f in self._copymap:
571 del self._copymap[f]
573 del self._copymap[f]
572 if f in self._nonnormalset:
574 if f in self._nonnormalset:
573 self._nonnormalset.remove(f)
575 self._nonnormalset.remove(f)
574
576
575 def otherparent(self, f):
577 def otherparent(self, f):
576 '''Mark as coming from the other parent, always dirty.'''
578 '''Mark as coming from the other parent, always dirty.'''
577 if self._pl[1] == nullid:
579 if self._pl[1] == nullid:
578 raise error.Abort(_("setting %r to other parent "
580 raise error.Abort(_("setting %r to other parent "
579 "only allowed in merges") % f)
581 "only allowed in merges") % f)
580 if f in self and self[f] == 'n':
582 if f in self and self[f] == 'n':
581 # merge-like
583 # merge-like
582 self._addpath(f, 'm', 0, -2, -1)
584 self._addpath(f, 'm', 0, -2, -1)
583 else:
585 else:
584 # add-like
586 # add-like
585 self._addpath(f, 'n', 0, -2, -1)
587 self._addpath(f, 'n', 0, -2, -1)
586
588
587 if f in self._copymap:
589 if f in self._copymap:
588 del self._copymap[f]
590 del self._copymap[f]
589
591
590 def add(self, f):
592 def add(self, f):
591 '''Mark a file added.'''
593 '''Mark a file added.'''
592 self._addpath(f, 'a', 0, -1, -1)
594 self._addpath(f, 'a', 0, -1, -1)
593 if f in self._copymap:
595 if f in self._copymap:
594 del self._copymap[f]
596 del self._copymap[f]
595
597
596 def remove(self, f):
598 def remove(self, f):
597 '''Mark a file removed.'''
599 '''Mark a file removed.'''
598 self._dirty = True
600 self._dirty = True
599 self._droppath(f)
601 self._droppath(f)
600 size = 0
602 size = 0
601 if self._pl[1] != nullid and f in self._map:
603 if self._pl[1] != nullid and f in self._map:
602 # backup the previous state
604 # backup the previous state
603 entry = self._map[f]
605 entry = self._map[f]
604 if entry[0] == 'm': # merge
606 if entry[0] == 'm': # merge
605 size = -1
607 size = -1
606 elif entry[0] == 'n' and entry[2] == -2: # other parent
608 elif entry[0] == 'n' and entry[2] == -2: # other parent
607 size = -2
609 size = -2
608 self._otherparentset.add(f)
610 self._otherparentset.add(f)
609 self._map[f] = dirstatetuple('r', 0, size, 0)
611 self._map[f] = dirstatetuple('r', 0, size, 0)
610 self._nonnormalset.add(f)
612 self._nonnormalset.add(f)
611 if size == 0 and f in self._copymap:
613 if size == 0 and f in self._copymap:
612 del self._copymap[f]
614 del self._copymap[f]
613
615
614 def merge(self, f):
616 def merge(self, f):
615 '''Mark a file merged.'''
617 '''Mark a file merged.'''
616 if self._pl[1] == nullid:
618 if self._pl[1] == nullid:
617 return self.normallookup(f)
619 return self.normallookup(f)
618 return self.otherparent(f)
620 return self.otherparent(f)
619
621
620 def drop(self, f):
622 def drop(self, f):
621 '''Drop a file from the dirstate'''
623 '''Drop a file from the dirstate'''
622 if f in self._map:
624 if f in self._map:
623 self._dirty = True
625 self._dirty = True
624 self._droppath(f)
626 self._droppath(f)
625 del self._map[f]
627 del self._map[f]
626 if f in self._nonnormalset:
628 if f in self._nonnormalset:
627 self._nonnormalset.remove(f)
629 self._nonnormalset.remove(f)
628 if f in self._copymap:
630 if f in self._copymap:
629 del self._copymap[f]
631 del self._copymap[f]
630
632
631 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
633 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
632 if exists is None:
634 if exists is None:
633 exists = os.path.lexists(os.path.join(self._root, path))
635 exists = os.path.lexists(os.path.join(self._root, path))
634 if not exists:
636 if not exists:
635 # Maybe a path component exists
637 # Maybe a path component exists
636 if not ignoremissing and '/' in path:
638 if not ignoremissing and '/' in path:
637 d, f = path.rsplit('/', 1)
639 d, f = path.rsplit('/', 1)
638 d = self._normalize(d, False, ignoremissing, None)
640 d = self._normalize(d, False, ignoremissing, None)
639 folded = d + "/" + f
641 folded = d + "/" + f
640 else:
642 else:
641 # No path components, preserve original case
643 # No path components, preserve original case
642 folded = path
644 folded = path
643 else:
645 else:
644 # recursively normalize leading directory components
646 # recursively normalize leading directory components
645 # against dirstate
647 # against dirstate
646 if '/' in normed:
648 if '/' in normed:
647 d, f = normed.rsplit('/', 1)
649 d, f = normed.rsplit('/', 1)
648 d = self._normalize(d, False, ignoremissing, True)
650 d = self._normalize(d, False, ignoremissing, True)
649 r = self._root + "/" + d
651 r = self._root + "/" + d
650 folded = d + "/" + util.fspath(f, r)
652 folded = d + "/" + util.fspath(f, r)
651 else:
653 else:
652 folded = util.fspath(normed, self._root)
654 folded = util.fspath(normed, self._root)
653 storemap[normed] = folded
655 storemap[normed] = folded
654
656
655 return folded
657 return folded
656
658
657 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
659 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
658 normed = util.normcase(path)
660 normed = util.normcase(path)
659 folded = self._filefoldmap.get(normed, None)
661 folded = self._filefoldmap.get(normed, None)
660 if folded is None:
662 if folded is None:
661 if isknown:
663 if isknown:
662 folded = path
664 folded = path
663 else:
665 else:
664 folded = self._discoverpath(path, normed, ignoremissing, exists,
666 folded = self._discoverpath(path, normed, ignoremissing, exists,
665 self._filefoldmap)
667 self._filefoldmap)
666 return folded
668 return folded
667
669
668 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
670 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
669 normed = util.normcase(path)
671 normed = util.normcase(path)
670 folded = self._filefoldmap.get(normed, None)
672 folded = self._filefoldmap.get(normed, None)
671 if folded is None:
673 if folded is None:
672 folded = self._dirfoldmap.get(normed, None)
674 folded = self._dirfoldmap.get(normed, None)
673 if folded is None:
675 if folded is None:
674 if isknown:
676 if isknown:
675 folded = path
677 folded = path
676 else:
678 else:
677 # store discovered result in dirfoldmap so that future
679 # store discovered result in dirfoldmap so that future
678 # normalizefile calls don't start matching directories
680 # normalizefile calls don't start matching directories
679 folded = self._discoverpath(path, normed, ignoremissing, exists,
681 folded = self._discoverpath(path, normed, ignoremissing, exists,
680 self._dirfoldmap)
682 self._dirfoldmap)
681 return folded
683 return folded
682
684
683 def normalize(self, path, isknown=False, ignoremissing=False):
685 def normalize(self, path, isknown=False, ignoremissing=False):
684 '''
686 '''
685 normalize the case of a pathname when on a casefolding filesystem
687 normalize the case of a pathname when on a casefolding filesystem
686
688
687 isknown specifies whether the filename came from walking the
689 isknown specifies whether the filename came from walking the
688 disk, to avoid extra filesystem access.
690 disk, to avoid extra filesystem access.
689
691
690 If ignoremissing is True, missing path are returned
692 If ignoremissing is True, missing path are returned
691 unchanged. Otherwise, we try harder to normalize possibly
693 unchanged. Otherwise, we try harder to normalize possibly
692 existing path components.
694 existing path components.
693
695
694 The normalized case is determined based on the following precedence:
696 The normalized case is determined based on the following precedence:
695
697
696 - version of name already stored in the dirstate
698 - version of name already stored in the dirstate
697 - version of name stored on disk
699 - version of name stored on disk
698 - version provided via command arguments
700 - version provided via command arguments
699 '''
701 '''
700
702
701 if self._checkcase:
703 if self._checkcase:
702 return self._normalize(path, isknown, ignoremissing)
704 return self._normalize(path, isknown, ignoremissing)
703 return path
705 return path
704
706
705 def clear(self):
707 def clear(self):
706 self._map = {}
708 self._map = {}
707 self._nonnormalset = set()
709 self._nonnormalset = set()
708 self._otherparentset = set()
710 self._otherparentset = set()
709 if "_dirs" in self.__dict__:
711 if "_dirs" in self.__dict__:
710 delattr(self, "_dirs")
712 delattr(self, "_dirs")
711 self._copymap = {}
713 self._copymap = {}
712 self._pl = [nullid, nullid]
714 self._pl = [nullid, nullid]
713 self._lastnormaltime = 0
715 self._lastnormaltime = 0
714 self._updatedfiles.clear()
716 self._updatedfiles.clear()
715 self._dirty = True
717 self._dirty = True
716
718
717 def rebuild(self, parent, allfiles, changedfiles=None):
719 def rebuild(self, parent, allfiles, changedfiles=None):
718 if changedfiles is None:
720 if changedfiles is None:
719 # Rebuild entire dirstate
721 # Rebuild entire dirstate
720 changedfiles = allfiles
722 changedfiles = allfiles
721 lastnormaltime = self._lastnormaltime
723 lastnormaltime = self._lastnormaltime
722 self.clear()
724 self.clear()
723 self._lastnormaltime = lastnormaltime
725 self._lastnormaltime = lastnormaltime
724
726
725 if self._origpl is None:
727 if self._origpl is None:
726 self._origpl = self._pl
728 self._origpl = self._pl
727 self._pl = (parent, nullid)
729 self._pl = (parent, nullid)
728 for f in changedfiles:
730 for f in changedfiles:
729 if f in allfiles:
731 if f in allfiles:
730 self.normallookup(f)
732 self.normallookup(f)
731 else:
733 else:
732 self.drop(f)
734 self.drop(f)
733
735
734 self._dirty = True
736 self._dirty = True
735
737
736 def write(self, tr):
738 def write(self, tr):
737 if not self._dirty:
739 if not self._dirty:
738 return
740 return
739
741
740 filename = self._filename
742 filename = self._filename
741 if tr:
743 if tr:
742 # 'dirstate.write()' is not only for writing in-memory
744 # 'dirstate.write()' is not only for writing in-memory
743 # changes out, but also for dropping ambiguous timestamp.
745 # changes out, but also for dropping ambiguous timestamp.
744 # delayed writing re-raise "ambiguous timestamp issue".
746 # delayed writing re-raise "ambiguous timestamp issue".
745 # See also the wiki page below for detail:
747 # See also the wiki page below for detail:
746 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
748 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
747
749
748 # emulate dropping timestamp in 'parsers.pack_dirstate'
750 # emulate dropping timestamp in 'parsers.pack_dirstate'
749 now = _getfsnow(self._opener)
751 now = _getfsnow(self._opener)
750 dmap = self._map
752 dmap = self._map
751 for f in self._updatedfiles:
753 for f in self._updatedfiles:
752 e = dmap.get(f)
754 e = dmap.get(f)
753 if e is not None and e[0] == 'n' and e[3] == now:
755 if e is not None and e[0] == 'n' and e[3] == now:
754 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
756 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
755 self._nonnormalset.add(f)
757 self._nonnormalset.add(f)
756
758
757 # emulate that all 'dirstate.normal' results are written out
759 # emulate that all 'dirstate.normal' results are written out
758 self._lastnormaltime = 0
760 self._lastnormaltime = 0
759 self._updatedfiles.clear()
761 self._updatedfiles.clear()
760
762
761 # delay writing in-memory changes out
763 # delay writing in-memory changes out
762 tr.addfilegenerator('dirstate', (self._filename,),
764 tr.addfilegenerator('dirstate', (self._filename,),
763 self._writedirstate, location='plain')
765 self._writedirstate, location='plain')
764 return
766 return
765
767
766 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
768 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
767 self._writedirstate(st)
769 self._writedirstate(st)
768
770
769 def addparentchangecallback(self, category, callback):
771 def addparentchangecallback(self, category, callback):
770 """add a callback to be called when the wd parents are changed
772 """add a callback to be called when the wd parents are changed
771
773
772 Callback will be called with the following arguments:
774 Callback will be called with the following arguments:
773 dirstate, (oldp1, oldp2), (newp1, newp2)
775 dirstate, (oldp1, oldp2), (newp1, newp2)
774
776
775 Category is a unique identifier to allow overwriting an old callback
777 Category is a unique identifier to allow overwriting an old callback
776 with a newer callback.
778 with a newer callback.
777 """
779 """
778 self._plchangecallbacks[category] = callback
780 self._plchangecallbacks[category] = callback
779
781
780 def _writedirstate(self, st):
782 def _writedirstate(self, st):
781 # notify callbacks about parents change
783 # notify callbacks about parents change
782 if self._origpl is not None and self._origpl != self._pl:
784 if self._origpl is not None and self._origpl != self._pl:
783 for c, callback in sorted(self._plchangecallbacks.iteritems()):
785 for c, callback in sorted(self._plchangecallbacks.iteritems()):
784 callback(self, self._origpl, self._pl)
786 callback(self, self._origpl, self._pl)
785 self._origpl = None
787 self._origpl = None
786 # use the modification time of the newly created temporary file as the
788 # use the modification time of the newly created temporary file as the
787 # filesystem's notion of 'now'
789 # filesystem's notion of 'now'
788 now = util.fstat(st).st_mtime & _rangemask
790 now = util.fstat(st).st_mtime & _rangemask
789
791
790 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
792 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
791 # timestamp of each entries in dirstate, because of 'now > mtime'
793 # timestamp of each entries in dirstate, because of 'now > mtime'
792 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
794 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
793 if delaywrite > 0:
795 if delaywrite > 0:
794 # do we have any files to delay for?
796 # do we have any files to delay for?
795 for f, e in self._map.iteritems():
797 for f, e in self._map.iteritems():
796 if e[0] == 'n' and e[3] == now:
798 if e[0] == 'n' and e[3] == now:
797 import time # to avoid useless import
799 import time # to avoid useless import
798 # rather than sleep n seconds, sleep until the next
800 # rather than sleep n seconds, sleep until the next
799 # multiple of n seconds
801 # multiple of n seconds
800 clock = time.time()
802 clock = time.time()
801 start = int(clock) - (int(clock) % delaywrite)
803 start = int(clock) - (int(clock) % delaywrite)
802 end = start + delaywrite
804 end = start + delaywrite
803 time.sleep(end - clock)
805 time.sleep(end - clock)
804 now = end # trust our estimate that the end is near now
806 now = end # trust our estimate that the end is near now
805 break
807 break
806
808
807 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
809 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
808 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
810 self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
809 st.close()
811 st.close()
810 self._lastnormaltime = 0
812 self._lastnormaltime = 0
811 self._dirty = self._dirtypl = False
813 self._dirty = self._dirtypl = False
812
814
813 def _dirignore(self, f):
815 def _dirignore(self, f):
814 if f == '.':
816 if f == '.':
815 return False
817 return False
816 if self._ignore(f):
818 if self._ignore(f):
817 return True
819 return True
818 for p in util.finddirs(f):
820 for p in util.finddirs(f):
819 if self._ignore(p):
821 if self._ignore(p):
820 return True
822 return True
821 return False
823 return False
822
824
823 def _ignorefiles(self):
825 def _ignorefiles(self):
824 files = []
826 files = []
825 if os.path.exists(self._join('.hgignore')):
827 if os.path.exists(self._join('.hgignore')):
826 files.append(self._join('.hgignore'))
828 files.append(self._join('.hgignore'))
827 for name, path in self._ui.configitems("ui"):
829 for name, path in self._ui.configitems("ui"):
828 if name == 'ignore' or name.startswith('ignore.'):
830 if name == 'ignore' or name.startswith('ignore.'):
829 # we need to use os.path.join here rather than self._join
831 # we need to use os.path.join here rather than self._join
830 # because path is arbitrary and user-specified
832 # because path is arbitrary and user-specified
831 files.append(os.path.join(self._rootdir, util.expandpath(path)))
833 files.append(os.path.join(self._rootdir, util.expandpath(path)))
832 return files
834 return files
833
835
834 def _ignorefileandline(self, f):
836 def _ignorefileandline(self, f):
835 files = collections.deque(self._ignorefiles())
837 files = collections.deque(self._ignorefiles())
836 visited = set()
838 visited = set()
837 while files:
839 while files:
838 i = files.popleft()
840 i = files.popleft()
839 patterns = matchmod.readpatternfile(i, self._ui.warn,
841 patterns = matchmod.readpatternfile(i, self._ui.warn,
840 sourceinfo=True)
842 sourceinfo=True)
841 for pattern, lineno, line in patterns:
843 for pattern, lineno, line in patterns:
842 kind, p = matchmod._patsplit(pattern, 'glob')
844 kind, p = matchmod._patsplit(pattern, 'glob')
843 if kind == "subinclude":
845 if kind == "subinclude":
844 if p not in visited:
846 if p not in visited:
845 files.append(p)
847 files.append(p)
846 continue
848 continue
847 m = matchmod.match(self._root, '', [], [pattern],
849 m = matchmod.match(self._root, '', [], [pattern],
848 warn=self._ui.warn)
850 warn=self._ui.warn)
849 if m(f):
851 if m(f):
850 return (i, lineno, line)
852 return (i, lineno, line)
851 visited.add(i)
853 visited.add(i)
852 return (None, -1, "")
854 return (None, -1, "")
853
855
854 def _walkexplicit(self, match, subrepos):
856 def _walkexplicit(self, match, subrepos):
855 '''Get stat data about the files explicitly specified by match.
857 '''Get stat data about the files explicitly specified by match.
856
858
857 Return a triple (results, dirsfound, dirsnotfound).
859 Return a triple (results, dirsfound, dirsnotfound).
858 - results is a mapping from filename to stat result. It also contains
860 - results is a mapping from filename to stat result. It also contains
859 listings mapping subrepos and .hg to None.
861 listings mapping subrepos and .hg to None.
860 - dirsfound is a list of files found to be directories.
862 - dirsfound is a list of files found to be directories.
861 - dirsnotfound is a list of files that the dirstate thinks are
863 - dirsnotfound is a list of files that the dirstate thinks are
862 directories and that were not found.'''
864 directories and that were not found.'''
863
865
864 def badtype(mode):
866 def badtype(mode):
865 kind = _('unknown')
867 kind = _('unknown')
866 if stat.S_ISCHR(mode):
868 if stat.S_ISCHR(mode):
867 kind = _('character device')
869 kind = _('character device')
868 elif stat.S_ISBLK(mode):
870 elif stat.S_ISBLK(mode):
869 kind = _('block device')
871 kind = _('block device')
870 elif stat.S_ISFIFO(mode):
872 elif stat.S_ISFIFO(mode):
871 kind = _('fifo')
873 kind = _('fifo')
872 elif stat.S_ISSOCK(mode):
874 elif stat.S_ISSOCK(mode):
873 kind = _('socket')
875 kind = _('socket')
874 elif stat.S_ISDIR(mode):
876 elif stat.S_ISDIR(mode):
875 kind = _('directory')
877 kind = _('directory')
876 return _('unsupported file type (type is %s)') % kind
878 return _('unsupported file type (type is %s)') % kind
877
879
878 matchedir = match.explicitdir
880 matchedir = match.explicitdir
879 badfn = match.bad
881 badfn = match.bad
880 dmap = self._map
882 dmap = self._map
881 lstat = os.lstat
883 lstat = os.lstat
882 getkind = stat.S_IFMT
884 getkind = stat.S_IFMT
883 dirkind = stat.S_IFDIR
885 dirkind = stat.S_IFDIR
884 regkind = stat.S_IFREG
886 regkind = stat.S_IFREG
885 lnkkind = stat.S_IFLNK
887 lnkkind = stat.S_IFLNK
886 join = self._join
888 join = self._join
887 dirsfound = []
889 dirsfound = []
888 foundadd = dirsfound.append
890 foundadd = dirsfound.append
889 dirsnotfound = []
891 dirsnotfound = []
890 notfoundadd = dirsnotfound.append
892 notfoundadd = dirsnotfound.append
891
893
892 if not match.isexact() and self._checkcase:
894 if not match.isexact() and self._checkcase:
893 normalize = self._normalize
895 normalize = self._normalize
894 else:
896 else:
895 normalize = None
897 normalize = None
896
898
897 files = sorted(match.files())
899 files = sorted(match.files())
898 subrepos.sort()
900 subrepos.sort()
899 i, j = 0, 0
901 i, j = 0, 0
900 while i < len(files) and j < len(subrepos):
902 while i < len(files) and j < len(subrepos):
901 subpath = subrepos[j] + "/"
903 subpath = subrepos[j] + "/"
902 if files[i] < subpath:
904 if files[i] < subpath:
903 i += 1
905 i += 1
904 continue
906 continue
905 while i < len(files) and files[i].startswith(subpath):
907 while i < len(files) and files[i].startswith(subpath):
906 del files[i]
908 del files[i]
907 j += 1
909 j += 1
908
910
909 if not files or '.' in files:
911 if not files or '.' in files:
910 files = ['.']
912 files = ['.']
911 results = dict.fromkeys(subrepos)
913 results = dict.fromkeys(subrepos)
912 results['.hg'] = None
914 results['.hg'] = None
913
915
914 alldirs = None
916 alldirs = None
915 for ff in files:
917 for ff in files:
916 # constructing the foldmap is expensive, so don't do it for the
918 # constructing the foldmap is expensive, so don't do it for the
917 # common case where files is ['.']
919 # common case where files is ['.']
918 if normalize and ff != '.':
920 if normalize and ff != '.':
919 nf = normalize(ff, False, True)
921 nf = normalize(ff, False, True)
920 else:
922 else:
921 nf = ff
923 nf = ff
922 if nf in results:
924 if nf in results:
923 continue
925 continue
924
926
925 try:
927 try:
926 st = lstat(join(nf))
928 st = lstat(join(nf))
927 kind = getkind(st.st_mode)
929 kind = getkind(st.st_mode)
928 if kind == dirkind:
930 if kind == dirkind:
929 if nf in dmap:
931 if nf in dmap:
930 # file replaced by dir on disk but still in dirstate
932 # file replaced by dir on disk but still in dirstate
931 results[nf] = None
933 results[nf] = None
932 if matchedir:
934 if matchedir:
933 matchedir(nf)
935 matchedir(nf)
934 foundadd((nf, ff))
936 foundadd((nf, ff))
935 elif kind == regkind or kind == lnkkind:
937 elif kind == regkind or kind == lnkkind:
936 results[nf] = st
938 results[nf] = st
937 else:
939 else:
938 badfn(ff, badtype(kind))
940 badfn(ff, badtype(kind))
939 if nf in dmap:
941 if nf in dmap:
940 results[nf] = None
942 results[nf] = None
941 except OSError as inst: # nf not found on disk - it is dirstate only
943 except OSError as inst: # nf not found on disk - it is dirstate only
942 if nf in dmap: # does it exactly match a missing file?
944 if nf in dmap: # does it exactly match a missing file?
943 results[nf] = None
945 results[nf] = None
944 else: # does it match a missing directory?
946 else: # does it match a missing directory?
945 if alldirs is None:
947 if alldirs is None:
946 alldirs = util.dirs(dmap)
948 alldirs = util.dirs(dmap)
947 if nf in alldirs:
949 if nf in alldirs:
948 if matchedir:
950 if matchedir:
949 matchedir(nf)
951 matchedir(nf)
950 notfoundadd(nf)
952 notfoundadd(nf)
951 else:
953 else:
952 badfn(ff, inst.strerror)
954 badfn(ff, inst.strerror)
953
955
954 # Case insensitive filesystems cannot rely on lstat() failing to detect
956 # Case insensitive filesystems cannot rely on lstat() failing to detect
955 # a case-only rename. Prune the stat object for any file that does not
957 # a case-only rename. Prune the stat object for any file that does not
956 # match the case in the filesystem, if there are multiple files that
958 # match the case in the filesystem, if there are multiple files that
957 # normalize to the same path.
959 # normalize to the same path.
958 if match.isexact() and self._checkcase:
960 if match.isexact() and self._checkcase:
959 normed = {}
961 normed = {}
960
962
961 for f, st in results.iteritems():
963 for f, st in results.iteritems():
962 if st is None:
964 if st is None:
963 continue
965 continue
964
966
965 nc = util.normcase(f)
967 nc = util.normcase(f)
966 paths = normed.get(nc)
968 paths = normed.get(nc)
967
969
968 if paths is None:
970 if paths is None:
969 paths = set()
971 paths = set()
970 normed[nc] = paths
972 normed[nc] = paths
971
973
972 paths.add(f)
974 paths.add(f)
973
975
974 for norm, paths in normed.iteritems():
976 for norm, paths in normed.iteritems():
975 if len(paths) > 1:
977 if len(paths) > 1:
976 for path in paths:
978 for path in paths:
977 folded = self._discoverpath(path, norm, True, None,
979 folded = self._discoverpath(path, norm, True, None,
978 self._dirfoldmap)
980 self._dirfoldmap)
979 if path != folded:
981 if path != folded:
980 results[path] = None
982 results[path] = None
981
983
982 return results, dirsfound, dirsnotfound
984 return results, dirsfound, dirsnotfound
983
985
984 def walk(self, match, subrepos, unknown, ignored, full=True):
986 def walk(self, match, subrepos, unknown, ignored, full=True):
985 '''
987 '''
986 Walk recursively through the directory tree, finding all files
988 Walk recursively through the directory tree, finding all files
987 matched by match.
989 matched by match.
988
990
989 If full is False, maybe skip some known-clean files.
991 If full is False, maybe skip some known-clean files.
990
992
991 Return a dict mapping filename to stat-like object (either
993 Return a dict mapping filename to stat-like object (either
992 mercurial.osutil.stat instance or return value of os.stat()).
994 mercurial.osutil.stat instance or return value of os.stat()).
993
995
994 '''
996 '''
995 # full is a flag that extensions that hook into walk can use -- this
997 # full is a flag that extensions that hook into walk can use -- this
996 # implementation doesn't use it at all. This satisfies the contract
998 # implementation doesn't use it at all. This satisfies the contract
997 # because we only guarantee a "maybe".
999 # because we only guarantee a "maybe".
998
1000
999 if ignored:
1001 if ignored:
1000 ignore = util.never
1002 ignore = util.never
1001 dirignore = util.never
1003 dirignore = util.never
1002 elif unknown:
1004 elif unknown:
1003 ignore = self._ignore
1005 ignore = self._ignore
1004 dirignore = self._dirignore
1006 dirignore = self._dirignore
1005 else:
1007 else:
1006 # if not unknown and not ignored, drop dir recursion and step 2
1008 # if not unknown and not ignored, drop dir recursion and step 2
1007 ignore = util.always
1009 ignore = util.always
1008 dirignore = util.always
1010 dirignore = util.always
1009
1011
1010 matchfn = match.matchfn
1012 matchfn = match.matchfn
1011 matchalways = match.always()
1013 matchalways = match.always()
1012 matchtdir = match.traversedir
1014 matchtdir = match.traversedir
1013 dmap = self._map
1015 dmap = self._map
1014 listdir = util.listdir
1016 listdir = util.listdir
1015 lstat = os.lstat
1017 lstat = os.lstat
1016 dirkind = stat.S_IFDIR
1018 dirkind = stat.S_IFDIR
1017 regkind = stat.S_IFREG
1019 regkind = stat.S_IFREG
1018 lnkkind = stat.S_IFLNK
1020 lnkkind = stat.S_IFLNK
1019 join = self._join
1021 join = self._join
1020
1022
1021 exact = skipstep3 = False
1023 exact = skipstep3 = False
1022 if match.isexact(): # match.exact
1024 if match.isexact(): # match.exact
1023 exact = True
1025 exact = True
1024 dirignore = util.always # skip step 2
1026 dirignore = util.always # skip step 2
1025 elif match.prefix(): # match.match, no patterns
1027 elif match.prefix(): # match.match, no patterns
1026 skipstep3 = True
1028 skipstep3 = True
1027
1029
1028 if not exact and self._checkcase:
1030 if not exact and self._checkcase:
1029 normalize = self._normalize
1031 normalize = self._normalize
1030 normalizefile = self._normalizefile
1032 normalizefile = self._normalizefile
1031 skipstep3 = False
1033 skipstep3 = False
1032 else:
1034 else:
1033 normalize = self._normalize
1035 normalize = self._normalize
1034 normalizefile = None
1036 normalizefile = None
1035
1037
1036 # step 1: find all explicit files
1038 # step 1: find all explicit files
1037 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1039 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
1038
1040
1039 skipstep3 = skipstep3 and not (work or dirsnotfound)
1041 skipstep3 = skipstep3 and not (work or dirsnotfound)
1040 work = [d for d in work if not dirignore(d[0])]
1042 work = [d for d in work if not dirignore(d[0])]
1041
1043
1042 # step 2: visit subdirectories
1044 # step 2: visit subdirectories
1043 def traverse(work, alreadynormed):
1045 def traverse(work, alreadynormed):
1044 wadd = work.append
1046 wadd = work.append
1045 while work:
1047 while work:
1046 nd = work.pop()
1048 nd = work.pop()
1047 if not match.visitdir(nd):
1049 if not match.visitdir(nd):
1048 continue
1050 continue
1049 skip = None
1051 skip = None
1050 if nd == '.':
1052 if nd == '.':
1051 nd = ''
1053 nd = ''
1052 else:
1054 else:
1053 skip = '.hg'
1055 skip = '.hg'
1054 try:
1056 try:
1055 entries = listdir(join(nd), stat=True, skip=skip)
1057 entries = listdir(join(nd), stat=True, skip=skip)
1056 except OSError as inst:
1058 except OSError as inst:
1057 if inst.errno in (errno.EACCES, errno.ENOENT):
1059 if inst.errno in (errno.EACCES, errno.ENOENT):
1058 match.bad(self.pathto(nd), inst.strerror)
1060 match.bad(self.pathto(nd), inst.strerror)
1059 continue
1061 continue
1060 raise
1062 raise
1061 for f, kind, st in entries:
1063 for f, kind, st in entries:
1062 if normalizefile:
1064 if normalizefile:
1063 # even though f might be a directory, we're only
1065 # even though f might be a directory, we're only
1064 # interested in comparing it to files currently in the
1066 # interested in comparing it to files currently in the
1065 # dmap -- therefore normalizefile is enough
1067 # dmap -- therefore normalizefile is enough
1066 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1068 nf = normalizefile(nd and (nd + "/" + f) or f, True,
1067 True)
1069 True)
1068 else:
1070 else:
1069 nf = nd and (nd + "/" + f) or f
1071 nf = nd and (nd + "/" + f) or f
1070 if nf not in results:
1072 if nf not in results:
1071 if kind == dirkind:
1073 if kind == dirkind:
1072 if not ignore(nf):
1074 if not ignore(nf):
1073 if matchtdir:
1075 if matchtdir:
1074 matchtdir(nf)
1076 matchtdir(nf)
1075 wadd(nf)
1077 wadd(nf)
1076 if nf in dmap and (matchalways or matchfn(nf)):
1078 if nf in dmap and (matchalways or matchfn(nf)):
1077 results[nf] = None
1079 results[nf] = None
1078 elif kind == regkind or kind == lnkkind:
1080 elif kind == regkind or kind == lnkkind:
1079 if nf in dmap:
1081 if nf in dmap:
1080 if matchalways or matchfn(nf):
1082 if matchalways or matchfn(nf):
1081 results[nf] = st
1083 results[nf] = st
1082 elif ((matchalways or matchfn(nf))
1084 elif ((matchalways or matchfn(nf))
1083 and not ignore(nf)):
1085 and not ignore(nf)):
1084 # unknown file -- normalize if necessary
1086 # unknown file -- normalize if necessary
1085 if not alreadynormed:
1087 if not alreadynormed:
1086 nf = normalize(nf, False, True)
1088 nf = normalize(nf, False, True)
1087 results[nf] = st
1089 results[nf] = st
1088 elif nf in dmap and (matchalways or matchfn(nf)):
1090 elif nf in dmap and (matchalways or matchfn(nf)):
1089 results[nf] = None
1091 results[nf] = None
1090
1092
1091 for nd, d in work:
1093 for nd, d in work:
1092 # alreadynormed means that processwork doesn't have to do any
1094 # alreadynormed means that processwork doesn't have to do any
1093 # expensive directory normalization
1095 # expensive directory normalization
1094 alreadynormed = not normalize or nd == d
1096 alreadynormed = not normalize or nd == d
1095 traverse([d], alreadynormed)
1097 traverse([d], alreadynormed)
1096
1098
1097 for s in subrepos:
1099 for s in subrepos:
1098 del results[s]
1100 del results[s]
1099 del results['.hg']
1101 del results['.hg']
1100
1102
1101 # step 3: visit remaining files from dmap
1103 # step 3: visit remaining files from dmap
1102 if not skipstep3 and not exact:
1104 if not skipstep3 and not exact:
1103 # If a dmap file is not in results yet, it was either
1105 # If a dmap file is not in results yet, it was either
1104 # a) not matching matchfn b) ignored, c) missing, or d) under a
1106 # a) not matching matchfn b) ignored, c) missing, or d) under a
1105 # symlink directory.
1107 # symlink directory.
1106 if not results and matchalways:
1108 if not results and matchalways:
1107 visit = [f for f in dmap]
1109 visit = [f for f in dmap]
1108 else:
1110 else:
1109 visit = [f for f in dmap if f not in results and matchfn(f)]
1111 visit = [f for f in dmap if f not in results and matchfn(f)]
1110 visit.sort()
1112 visit.sort()
1111
1113
1112 if unknown:
1114 if unknown:
1113 # unknown == True means we walked all dirs under the roots
1115 # unknown == True means we walked all dirs under the roots
1114 # that wasn't ignored, and everything that matched was stat'ed
1116 # that wasn't ignored, and everything that matched was stat'ed
1115 # and is already in results.
1117 # and is already in results.
1116 # The rest must thus be ignored or under a symlink.
1118 # The rest must thus be ignored or under a symlink.
1117 audit_path = pathutil.pathauditor(self._root)
1119 audit_path = pathutil.pathauditor(self._root)
1118
1120
1119 for nf in iter(visit):
1121 for nf in iter(visit):
1120 # If a stat for the same file was already added with a
1122 # If a stat for the same file was already added with a
1121 # different case, don't add one for this, since that would
1123 # different case, don't add one for this, since that would
1122 # make it appear as if the file exists under both names
1124 # make it appear as if the file exists under both names
1123 # on disk.
1125 # on disk.
1124 if (normalizefile and
1126 if (normalizefile and
1125 normalizefile(nf, True, True) in results):
1127 normalizefile(nf, True, True) in results):
1126 results[nf] = None
1128 results[nf] = None
1127 # Report ignored items in the dmap as long as they are not
1129 # Report ignored items in the dmap as long as they are not
1128 # under a symlink directory.
1130 # under a symlink directory.
1129 elif audit_path.check(nf):
1131 elif audit_path.check(nf):
1130 try:
1132 try:
1131 results[nf] = lstat(join(nf))
1133 results[nf] = lstat(join(nf))
1132 # file was just ignored, no links, and exists
1134 # file was just ignored, no links, and exists
1133 except OSError:
1135 except OSError:
1134 # file doesn't exist
1136 # file doesn't exist
1135 results[nf] = None
1137 results[nf] = None
1136 else:
1138 else:
1137 # It's either missing or under a symlink directory
1139 # It's either missing or under a symlink directory
1138 # which we in this case report as missing
1140 # which we in this case report as missing
1139 results[nf] = None
1141 results[nf] = None
1140 else:
1142 else:
1141 # We may not have walked the full directory tree above,
1143 # We may not have walked the full directory tree above,
1142 # so stat and check everything we missed.
1144 # so stat and check everything we missed.
1143 iv = iter(visit)
1145 iv = iter(visit)
1144 for st in util.statfiles([join(i) for i in visit]):
1146 for st in util.statfiles([join(i) for i in visit]):
1145 results[next(iv)] = st
1147 results[next(iv)] = st
1146 return results
1148 return results
1147
1149
1148 def status(self, match, subrepos, ignored, clean, unknown):
1150 def status(self, match, subrepos, ignored, clean, unknown):
1149 '''Determine the status of the working copy relative to the
1151 '''Determine the status of the working copy relative to the
1150 dirstate and return a pair of (unsure, status), where status is of type
1152 dirstate and return a pair of (unsure, status), where status is of type
1151 scmutil.status and:
1153 scmutil.status and:
1152
1154
1153 unsure:
1155 unsure:
1154 files that might have been modified since the dirstate was
1156 files that might have been modified since the dirstate was
1155 written, but need to be read to be sure (size is the same
1157 written, but need to be read to be sure (size is the same
1156 but mtime differs)
1158 but mtime differs)
1157 status.modified:
1159 status.modified:
1158 files that have definitely been modified since the dirstate
1160 files that have definitely been modified since the dirstate
1159 was written (different size or mode)
1161 was written (different size or mode)
1160 status.clean:
1162 status.clean:
1161 files that have definitely not been modified since the
1163 files that have definitely not been modified since the
1162 dirstate was written
1164 dirstate was written
1163 '''
1165 '''
1164 listignored, listclean, listunknown = ignored, clean, unknown
1166 listignored, listclean, listunknown = ignored, clean, unknown
1165 lookup, modified, added, unknown, ignored = [], [], [], [], []
1167 lookup, modified, added, unknown, ignored = [], [], [], [], []
1166 removed, deleted, clean = [], [], []
1168 removed, deleted, clean = [], [], []
1167
1169
1168 dmap = self._map
1170 dmap = self._map
1169 ladd = lookup.append # aka "unsure"
1171 ladd = lookup.append # aka "unsure"
1170 madd = modified.append
1172 madd = modified.append
1171 aadd = added.append
1173 aadd = added.append
1172 uadd = unknown.append
1174 uadd = unknown.append
1173 iadd = ignored.append
1175 iadd = ignored.append
1174 radd = removed.append
1176 radd = removed.append
1175 dadd = deleted.append
1177 dadd = deleted.append
1176 cadd = clean.append
1178 cadd = clean.append
1177 mexact = match.exact
1179 mexact = match.exact
1178 dirignore = self._dirignore
1180 dirignore = self._dirignore
1179 checkexec = self._checkexec
1181 checkexec = self._checkexec
1180 copymap = self._copymap
1182 copymap = self._copymap
1181 lastnormaltime = self._lastnormaltime
1183 lastnormaltime = self._lastnormaltime
1182
1184
1183 # We need to do full walks when either
1185 # We need to do full walks when either
1184 # - we're listing all clean files, or
1186 # - we're listing all clean files, or
1185 # - match.traversedir does something, because match.traversedir should
1187 # - match.traversedir does something, because match.traversedir should
1186 # be called for every dir in the working dir
1188 # be called for every dir in the working dir
1187 full = listclean or match.traversedir is not None
1189 full = listclean or match.traversedir is not None
1188 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1190 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1189 full=full).iteritems():
1191 full=full).iteritems():
1190 if fn not in dmap:
1192 if fn not in dmap:
1191 if (listignored or mexact(fn)) and dirignore(fn):
1193 if (listignored or mexact(fn)) and dirignore(fn):
1192 if listignored:
1194 if listignored:
1193 iadd(fn)
1195 iadd(fn)
1194 else:
1196 else:
1195 uadd(fn)
1197 uadd(fn)
1196 continue
1198 continue
1197
1199
1198 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1200 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1199 # written like that for performance reasons. dmap[fn] is not a
1201 # written like that for performance reasons. dmap[fn] is not a
1200 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1202 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1201 # opcode has fast paths when the value to be unpacked is a tuple or
1203 # opcode has fast paths when the value to be unpacked is a tuple or
1202 # a list, but falls back to creating a full-fledged iterator in
1204 # a list, but falls back to creating a full-fledged iterator in
1203 # general. That is much slower than simply accessing and storing the
1205 # general. That is much slower than simply accessing and storing the
1204 # tuple members one by one.
1206 # tuple members one by one.
1205 t = dmap[fn]
1207 t = dmap[fn]
1206 state = t[0]
1208 state = t[0]
1207 mode = t[1]
1209 mode = t[1]
1208 size = t[2]
1210 size = t[2]
1209 time = t[3]
1211 time = t[3]
1210
1212
1211 if not st and state in "nma":
1213 if not st and state in "nma":
1212 dadd(fn)
1214 dadd(fn)
1213 elif state == 'n':
1215 elif state == 'n':
1214 if (size >= 0 and
1216 if (size >= 0 and
1215 ((size != st.st_size and size != st.st_size & _rangemask)
1217 ((size != st.st_size and size != st.st_size & _rangemask)
1216 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1218 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1217 or size == -2 # other parent
1219 or size == -2 # other parent
1218 or fn in copymap):
1220 or fn in copymap):
1219 madd(fn)
1221 madd(fn)
1220 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1222 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1221 ladd(fn)
1223 ladd(fn)
1222 elif st.st_mtime == lastnormaltime:
1224 elif st.st_mtime == lastnormaltime:
1223 # fn may have just been marked as normal and it may have
1225 # fn may have just been marked as normal and it may have
1224 # changed in the same second without changing its size.
1226 # changed in the same second without changing its size.
1225 # This can happen if we quickly do multiple commits.
1227 # This can happen if we quickly do multiple commits.
1226 # Force lookup, so we don't miss such a racy file change.
1228 # Force lookup, so we don't miss such a racy file change.
1227 ladd(fn)
1229 ladd(fn)
1228 elif listclean:
1230 elif listclean:
1229 cadd(fn)
1231 cadd(fn)
1230 elif state == 'm':
1232 elif state == 'm':
1231 madd(fn)
1233 madd(fn)
1232 elif state == 'a':
1234 elif state == 'a':
1233 aadd(fn)
1235 aadd(fn)
1234 elif state == 'r':
1236 elif state == 'r':
1235 radd(fn)
1237 radd(fn)
1236
1238
1237 return (lookup, scmutil.status(modified, added, removed, deleted,
1239 return (lookup, scmutil.status(modified, added, removed, deleted,
1238 unknown, ignored, clean))
1240 unknown, ignored, clean))
1239
1241
1240 def matches(self, match):
1242 def matches(self, match):
1241 '''
1243 '''
1242 return files in the dirstate (in whatever state) filtered by match
1244 return files in the dirstate (in whatever state) filtered by match
1243 '''
1245 '''
1244 dmap = self._map
1246 dmap = self._map
1245 if match.always():
1247 if match.always():
1246 return dmap.keys()
1248 return dmap.keys()
1247 files = match.files()
1249 files = match.files()
1248 if match.isexact():
1250 if match.isexact():
1249 # fast path -- filter the other way around, since typically files is
1251 # fast path -- filter the other way around, since typically files is
1250 # much smaller than dmap
1252 # much smaller than dmap
1251 return [f for f in files if f in dmap]
1253 return [f for f in files if f in dmap]
1252 if match.prefix() and all(fn in dmap for fn in files):
1254 if match.prefix() and all(fn in dmap for fn in files):
1253 # fast path -- all the values are known to be files, so just return
1255 # fast path -- all the values are known to be files, so just return
1254 # that
1256 # that
1255 return list(files)
1257 return list(files)
1256 return [f for f in dmap if match(f)]
1258 return [f for f in dmap if match(f)]
1257
1259
1258 def _actualfilename(self, tr):
1260 def _actualfilename(self, tr):
1259 if tr:
1261 if tr:
1260 return self._pendingfilename
1262 return self._pendingfilename
1261 else:
1263 else:
1262 return self._filename
1264 return self._filename
1263
1265
1264 def savebackup(self, tr, suffix='', prefix=''):
1266 def savebackup(self, tr, suffix='', prefix=''):
1265 '''Save current dirstate into backup file with suffix'''
1267 '''Save current dirstate into backup file with suffix'''
1266 assert len(suffix) > 0 or len(prefix) > 0
1268 assert len(suffix) > 0 or len(prefix) > 0
1267 filename = self._actualfilename(tr)
1269 filename = self._actualfilename(tr)
1268
1270
1269 # use '_writedirstate' instead of 'write' to write changes certainly,
1271 # use '_writedirstate' instead of 'write' to write changes certainly,
1270 # because the latter omits writing out if transaction is running.
1272 # because the latter omits writing out if transaction is running.
1271 # output file will be used to create backup of dirstate at this point.
1273 # output file will be used to create backup of dirstate at this point.
1272 if self._dirty or not self._opener.exists(filename):
1274 if self._dirty or not self._opener.exists(filename):
1273 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1275 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1274 checkambig=True))
1276 checkambig=True))
1275
1277
1276 if tr:
1278 if tr:
1277 # ensure that subsequent tr.writepending returns True for
1279 # ensure that subsequent tr.writepending returns True for
1278 # changes written out above, even if dirstate is never
1280 # changes written out above, even if dirstate is never
1279 # changed after this
1281 # changed after this
1280 tr.addfilegenerator('dirstate', (self._filename,),
1282 tr.addfilegenerator('dirstate', (self._filename,),
1281 self._writedirstate, location='plain')
1283 self._writedirstate, location='plain')
1282
1284
1283 # ensure that pending file written above is unlinked at
1285 # ensure that pending file written above is unlinked at
1284 # failure, even if tr.writepending isn't invoked until the
1286 # failure, even if tr.writepending isn't invoked until the
1285 # end of this transaction
1287 # end of this transaction
1286 tr.registertmp(filename, location='plain')
1288 tr.registertmp(filename, location='plain')
1287
1289
1288 backupname = prefix + self._filename + suffix
1290 backupname = prefix + self._filename + suffix
1289 assert backupname != filename
1291 assert backupname != filename
1290 self._opener.tryunlink(backupname)
1292 self._opener.tryunlink(backupname)
1291 # hardlink backup is okay because _writedirstate is always called
1293 # hardlink backup is okay because _writedirstate is always called
1292 # with an "atomictemp=True" file.
1294 # with an "atomictemp=True" file.
1293 util.copyfile(self._opener.join(filename),
1295 util.copyfile(self._opener.join(filename),
1294 self._opener.join(backupname), hardlink=True)
1296 self._opener.join(backupname), hardlink=True)
1295
1297
1296 def restorebackup(self, tr, suffix='', prefix=''):
1298 def restorebackup(self, tr, suffix='', prefix=''):
1297 '''Restore dirstate by backup file with suffix'''
1299 '''Restore dirstate by backup file with suffix'''
1298 assert len(suffix) > 0 or len(prefix) > 0
1300 assert len(suffix) > 0 or len(prefix) > 0
1299 # this "invalidate()" prevents "wlock.release()" from writing
1301 # this "invalidate()" prevents "wlock.release()" from writing
1300 # changes of dirstate out after restoring from backup file
1302 # changes of dirstate out after restoring from backup file
1301 self.invalidate()
1303 self.invalidate()
1302 filename = self._actualfilename(tr)
1304 filename = self._actualfilename(tr)
1303 # using self._filename to avoid having "pending" in the backup filename
1305 # using self._filename to avoid having "pending" in the backup filename
1304 self._opener.rename(prefix + self._filename + suffix, filename,
1306 self._opener.rename(prefix + self._filename + suffix, filename,
1305 checkambig=True)
1307 checkambig=True)
1306
1308
1307 def clearbackup(self, tr, suffix='', prefix=''):
1309 def clearbackup(self, tr, suffix='', prefix=''):
1308 '''Clear backup file with suffix'''
1310 '''Clear backup file with suffix'''
1309 assert len(suffix) > 0 or len(prefix) > 0
1311 assert len(suffix) > 0 or len(prefix) > 0
1310 # using self._filename to avoid having "pending" in the backup filename
1312 # using self._filename to avoid having "pending" in the backup filename
1311 self._opener.unlink(prefix + self._filename + suffix)
1313 self._opener.unlink(prefix + self._filename + suffix)
@@ -1,1635 +1,1639 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 heapq
10 import heapq
11 import itertools
11 import itertools
12 import os
12 import os
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 bin,
17 bin,
18 hex,
18 hex,
19 )
19 )
20 from . import (
20 from . import (
21 error,
21 error,
22 mdiff,
22 mdiff,
23 policy,
23 policy,
24 revlog,
24 revlog,
25 util,
25 util,
26 )
26 )
27
27
28 parsers = policy.importmod(r'parsers')
28 parsers = policy.importmod(r'parsers')
29 propertycache = util.propertycache
29 propertycache = util.propertycache
30
30
31 def _parsev1(data):
31 def _parsev1(data):
32 # This method does a little bit of excessive-looking
32 # This method does a little bit of excessive-looking
33 # precondition checking. This is so that the behavior of this
33 # precondition checking. This is so that the behavior of this
34 # class exactly matches its C counterpart to try and help
34 # class exactly matches its C counterpart to try and help
35 # prevent surprise breakage for anyone that develops against
35 # prevent surprise breakage for anyone that develops against
36 # the pure version.
36 # the pure version.
37 if data and data[-1:] != '\n':
37 if data and data[-1:] != '\n':
38 raise ValueError('Manifest did not end in a newline.')
38 raise ValueError('Manifest did not end in a newline.')
39 prev = None
39 prev = None
40 for l in data.splitlines():
40 for l in data.splitlines():
41 if prev is not None and prev > l:
41 if prev is not None and prev > l:
42 raise ValueError('Manifest lines not in sorted order.')
42 raise ValueError('Manifest lines not in sorted order.')
43 prev = l
43 prev = l
44 f, n = l.split('\0')
44 f, n = l.split('\0')
45 if len(n) > 40:
45 if len(n) > 40:
46 yield f, bin(n[:40]), n[40:]
46 yield f, bin(n[:40]), n[40:]
47 else:
47 else:
48 yield f, bin(n), ''
48 yield f, bin(n), ''
49
49
50 def _parsev2(data):
50 def _parsev2(data):
51 metadataend = data.find('\n')
51 metadataend = data.find('\n')
52 # Just ignore metadata for now
52 # Just ignore metadata for now
53 pos = metadataend + 1
53 pos = metadataend + 1
54 prevf = ''
54 prevf = ''
55 while pos < len(data):
55 while pos < len(data):
56 end = data.find('\n', pos + 1) # +1 to skip stem length byte
56 end = data.find('\n', pos + 1) # +1 to skip stem length byte
57 if end == -1:
57 if end == -1:
58 raise ValueError('Manifest ended with incomplete file entry.')
58 raise ValueError('Manifest ended with incomplete file entry.')
59 stemlen = ord(data[pos:pos + 1])
59 stemlen = ord(data[pos:pos + 1])
60 items = data[pos + 1:end].split('\0')
60 items = data[pos + 1:end].split('\0')
61 f = prevf[:stemlen] + items[0]
61 f = prevf[:stemlen] + items[0]
62 if prevf > f:
62 if prevf > f:
63 raise ValueError('Manifest entries not in sorted order.')
63 raise ValueError('Manifest entries not in sorted order.')
64 fl = items[1]
64 fl = items[1]
65 # Just ignore metadata (items[2:] for now)
65 # Just ignore metadata (items[2:] for now)
66 n = data[end + 1:end + 21]
66 n = data[end + 1:end + 21]
67 yield f, n, fl
67 yield f, n, fl
68 pos = end + 22
68 pos = end + 22
69 prevf = f
69 prevf = f
70
70
71 def _parse(data):
71 def _parse(data):
72 """Generates (path, node, flags) tuples from a manifest text"""
72 """Generates (path, node, flags) tuples from a manifest text"""
73 if data.startswith('\0'):
73 if data.startswith('\0'):
74 return iter(_parsev2(data))
74 return iter(_parsev2(data))
75 else:
75 else:
76 return iter(_parsev1(data))
76 return iter(_parsev1(data))
77
77
78 def _text(it, usemanifestv2):
78 def _text(it, usemanifestv2):
79 """Given an iterator over (path, node, flags) tuples, returns a manifest
79 """Given an iterator over (path, node, flags) tuples, returns a manifest
80 text"""
80 text"""
81 if usemanifestv2:
81 if usemanifestv2:
82 return _textv2(it)
82 return _textv2(it)
83 else:
83 else:
84 return _textv1(it)
84 return _textv1(it)
85
85
86 def _textv1(it):
86 def _textv1(it):
87 files = []
87 files = []
88 lines = []
88 lines = []
89 _hex = revlog.hex
89 _hex = revlog.hex
90 for f, n, fl in it:
90 for f, n, fl in it:
91 files.append(f)
91 files.append(f)
92 # if this is changed to support newlines in filenames,
92 # if this is changed to support newlines in filenames,
93 # be sure to check the templates/ dir again (especially *-raw.tmpl)
93 # be sure to check the templates/ dir again (especially *-raw.tmpl)
94 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
94 lines.append("%s\0%s%s\n" % (f, _hex(n), fl))
95
95
96 _checkforbidden(files)
96 _checkforbidden(files)
97 return ''.join(lines)
97 return ''.join(lines)
98
98
99 def _textv2(it):
99 def _textv2(it):
100 files = []
100 files = []
101 lines = ['\0\n']
101 lines = ['\0\n']
102 prevf = ''
102 prevf = ''
103 for f, n, fl in it:
103 for f, n, fl in it:
104 files.append(f)
104 files.append(f)
105 stem = os.path.commonprefix([prevf, f])
105 stem = os.path.commonprefix([prevf, f])
106 stemlen = min(len(stem), 255)
106 stemlen = min(len(stem), 255)
107 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
107 lines.append("%c%s\0%s\n%s\n" % (stemlen, f[stemlen:], fl, n))
108 prevf = f
108 prevf = f
109 _checkforbidden(files)
109 _checkforbidden(files)
110 return ''.join(lines)
110 return ''.join(lines)
111
111
112 class lazymanifestiter(object):
112 class lazymanifestiter(object):
113 def __init__(self, lm):
113 def __init__(self, lm):
114 self.pos = 0
114 self.pos = 0
115 self.lm = lm
115 self.lm = lm
116
116
117 def __iter__(self):
117 def __iter__(self):
118 return self
118 return self
119
119
120 def next(self):
120 def next(self):
121 try:
121 try:
122 data, pos = self.lm._get(self.pos)
122 data, pos = self.lm._get(self.pos)
123 except IndexError:
123 except IndexError:
124 raise StopIteration
124 raise StopIteration
125 if pos == -1:
125 if pos == -1:
126 self.pos += 1
126 self.pos += 1
127 return data[0]
127 return data[0]
128 self.pos += 1
128 self.pos += 1
129 zeropos = data.find('\x00', pos)
129 zeropos = data.find('\x00', pos)
130 return data[pos:zeropos]
130 return data[pos:zeropos]
131
131
132 __next__ = next
132 __next__ = next
133
133
134 class lazymanifestiterentries(object):
134 class lazymanifestiterentries(object):
135 def __init__(self, lm):
135 def __init__(self, lm):
136 self.lm = lm
136 self.lm = lm
137 self.pos = 0
137 self.pos = 0
138
138
139 def __iter__(self):
139 def __iter__(self):
140 return self
140 return self
141
141
142 def next(self):
142 def next(self):
143 try:
143 try:
144 data, pos = self.lm._get(self.pos)
144 data, pos = self.lm._get(self.pos)
145 except IndexError:
145 except IndexError:
146 raise StopIteration
146 raise StopIteration
147 if pos == -1:
147 if pos == -1:
148 self.pos += 1
148 self.pos += 1
149 return data
149 return data
150 zeropos = data.find('\x00', pos)
150 zeropos = data.find('\x00', pos)
151 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
151 hashval = unhexlify(data, self.lm.extrainfo[self.pos],
152 zeropos + 1, 40)
152 zeropos + 1, 40)
153 flags = self.lm._getflags(data, self.pos, zeropos)
153 flags = self.lm._getflags(data, self.pos, zeropos)
154 self.pos += 1
154 self.pos += 1
155 return (data[pos:zeropos], hashval, flags)
155 return (data[pos:zeropos], hashval, flags)
156
156
157 __next__ = next
157 __next__ = next
158
158
159 def unhexlify(data, extra, pos, length):
159 def unhexlify(data, extra, pos, length):
160 s = bin(data[pos:pos + length])
160 s = bin(data[pos:pos + length])
161 if extra:
161 if extra:
162 s += chr(extra & 0xff)
162 s += chr(extra & 0xff)
163 return s
163 return s
164
164
165 def _cmp(a, b):
165 def _cmp(a, b):
166 return (a > b) - (a < b)
166 return (a > b) - (a < b)
167
167
168 class _lazymanifest(object):
168 class _lazymanifest(object):
169 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
169 def __init__(self, data, positions=None, extrainfo=None, extradata=None):
170 if positions is None:
170 if positions is None:
171 self.positions = self.findlines(data)
171 self.positions = self.findlines(data)
172 self.extrainfo = [0] * len(self.positions)
172 self.extrainfo = [0] * len(self.positions)
173 self.data = data
173 self.data = data
174 self.extradata = []
174 self.extradata = []
175 else:
175 else:
176 self.positions = positions[:]
176 self.positions = positions[:]
177 self.extrainfo = extrainfo[:]
177 self.extrainfo = extrainfo[:]
178 self.extradata = extradata[:]
178 self.extradata = extradata[:]
179 self.data = data
179 self.data = data
180
180
181 def findlines(self, data):
181 def findlines(self, data):
182 if not data:
182 if not data:
183 return []
183 return []
184 pos = data.find("\n")
184 pos = data.find("\n")
185 if pos == -1 or data[-1:] != '\n':
185 if pos == -1 or data[-1:] != '\n':
186 raise ValueError("Manifest did not end in a newline.")
186 raise ValueError("Manifest did not end in a newline.")
187 positions = [0]
187 positions = [0]
188 prev = data[:data.find('\x00')]
188 prev = data[:data.find('\x00')]
189 while pos < len(data) - 1 and pos != -1:
189 while pos < len(data) - 1 and pos != -1:
190 positions.append(pos + 1)
190 positions.append(pos + 1)
191 nexts = data[pos + 1:data.find('\x00', pos + 1)]
191 nexts = data[pos + 1:data.find('\x00', pos + 1)]
192 if nexts < prev:
192 if nexts < prev:
193 raise ValueError("Manifest lines not in sorted order.")
193 raise ValueError("Manifest lines not in sorted order.")
194 prev = nexts
194 prev = nexts
195 pos = data.find("\n", pos + 1)
195 pos = data.find("\n", pos + 1)
196 return positions
196 return positions
197
197
198 def _get(self, index):
198 def _get(self, index):
199 # get the position encoded in pos:
199 # get the position encoded in pos:
200 # positive number is an index in 'data'
200 # positive number is an index in 'data'
201 # negative number is in extrapieces
201 # negative number is in extrapieces
202 pos = self.positions[index]
202 pos = self.positions[index]
203 if pos >= 0:
203 if pos >= 0:
204 return self.data, pos
204 return self.data, pos
205 return self.extradata[-pos - 1], -1
205 return self.extradata[-pos - 1], -1
206
206
207 def _getkey(self, pos):
207 def _getkey(self, pos):
208 if pos >= 0:
208 if pos >= 0:
209 return self.data[pos:self.data.find('\x00', pos + 1)]
209 return self.data[pos:self.data.find('\x00', pos + 1)]
210 return self.extradata[-pos - 1][0]
210 return self.extradata[-pos - 1][0]
211
211
212 def bsearch(self, key):
212 def bsearch(self, key):
213 first = 0
213 first = 0
214 last = len(self.positions) - 1
214 last = len(self.positions) - 1
215
215
216 while first <= last:
216 while first <= last:
217 midpoint = (first + last)//2
217 midpoint = (first + last)//2
218 nextpos = self.positions[midpoint]
218 nextpos = self.positions[midpoint]
219 candidate = self._getkey(nextpos)
219 candidate = self._getkey(nextpos)
220 r = _cmp(key, candidate)
220 r = _cmp(key, candidate)
221 if r == 0:
221 if r == 0:
222 return midpoint
222 return midpoint
223 else:
223 else:
224 if r < 0:
224 if r < 0:
225 last = midpoint - 1
225 last = midpoint - 1
226 else:
226 else:
227 first = midpoint + 1
227 first = midpoint + 1
228 return -1
228 return -1
229
229
230 def bsearch2(self, key):
230 def bsearch2(self, key):
231 # same as the above, but will always return the position
231 # same as the above, but will always return the position
232 # done for performance reasons
232 # done for performance reasons
233 first = 0
233 first = 0
234 last = len(self.positions) - 1
234 last = len(self.positions) - 1
235
235
236 while first <= last:
236 while first <= last:
237 midpoint = (first + last)//2
237 midpoint = (first + last)//2
238 nextpos = self.positions[midpoint]
238 nextpos = self.positions[midpoint]
239 candidate = self._getkey(nextpos)
239 candidate = self._getkey(nextpos)
240 r = _cmp(key, candidate)
240 r = _cmp(key, candidate)
241 if r == 0:
241 if r == 0:
242 return (midpoint, True)
242 return (midpoint, True)
243 else:
243 else:
244 if r < 0:
244 if r < 0:
245 last = midpoint - 1
245 last = midpoint - 1
246 else:
246 else:
247 first = midpoint + 1
247 first = midpoint + 1
248 return (first, False)
248 return (first, False)
249
249
250 def __contains__(self, key):
250 def __contains__(self, key):
251 return self.bsearch(key) != -1
251 return self.bsearch(key) != -1
252
252
253 def _getflags(self, data, needle, pos):
253 def _getflags(self, data, needle, pos):
254 start = pos + 41
254 start = pos + 41
255 end = data.find("\n", start)
255 end = data.find("\n", start)
256 if end == -1:
256 if end == -1:
257 end = len(data) - 1
257 end = len(data) - 1
258 if start == end:
258 if start == end:
259 return ''
259 return ''
260 return self.data[start:end]
260 return self.data[start:end]
261
261
262 def __getitem__(self, key):
262 def __getitem__(self, key):
263 if not isinstance(key, bytes):
263 if not isinstance(key, bytes):
264 raise TypeError("getitem: manifest keys must be a bytes.")
264 raise TypeError("getitem: manifest keys must be a bytes.")
265 needle = self.bsearch(key)
265 needle = self.bsearch(key)
266 if needle == -1:
266 if needle == -1:
267 raise KeyError
267 raise KeyError
268 data, pos = self._get(needle)
268 data, pos = self._get(needle)
269 if pos == -1:
269 if pos == -1:
270 return (data[1], data[2])
270 return (data[1], data[2])
271 zeropos = data.find('\x00', pos)
271 zeropos = data.find('\x00', pos)
272 assert 0 <= needle <= len(self.positions)
272 assert 0 <= needle <= len(self.positions)
273 assert len(self.extrainfo) == len(self.positions)
273 assert len(self.extrainfo) == len(self.positions)
274 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
274 hashval = unhexlify(data, self.extrainfo[needle], zeropos + 1, 40)
275 flags = self._getflags(data, needle, zeropos)
275 flags = self._getflags(data, needle, zeropos)
276 return (hashval, flags)
276 return (hashval, flags)
277
277
278 def __delitem__(self, key):
278 def __delitem__(self, key):
279 needle, found = self.bsearch2(key)
279 needle, found = self.bsearch2(key)
280 if not found:
280 if not found:
281 raise KeyError
281 raise KeyError
282 cur = self.positions[needle]
282 cur = self.positions[needle]
283 self.positions = self.positions[:needle] + self.positions[needle + 1:]
283 self.positions = self.positions[:needle] + self.positions[needle + 1:]
284 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
284 self.extrainfo = self.extrainfo[:needle] + self.extrainfo[needle + 1:]
285 if cur >= 0:
285 if cur >= 0:
286 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
286 self.data = self.data[:cur] + '\x00' + self.data[cur + 1:]
287
287
288 def __setitem__(self, key, value):
288 def __setitem__(self, key, value):
289 if not isinstance(key, bytes):
289 if not isinstance(key, bytes):
290 raise TypeError("setitem: manifest keys must be a byte string.")
290 raise TypeError("setitem: manifest keys must be a byte string.")
291 if not isinstance(value, tuple) or len(value) != 2:
291 if not isinstance(value, tuple) or len(value) != 2:
292 raise TypeError("Manifest values must be a tuple of (node, flags).")
292 raise TypeError("Manifest values must be a tuple of (node, flags).")
293 hashval = value[0]
293 hashval = value[0]
294 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
294 if not isinstance(hashval, bytes) or not 20 <= len(hashval) <= 22:
295 raise TypeError("node must be a 20-byte byte string")
295 raise TypeError("node must be a 20-byte byte string")
296 flags = value[1]
296 flags = value[1]
297 if len(hashval) == 22:
297 if len(hashval) == 22:
298 hashval = hashval[:-1]
298 hashval = hashval[:-1]
299 if not isinstance(flags, bytes) or len(flags) > 1:
299 if not isinstance(flags, bytes) or len(flags) > 1:
300 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
300 raise TypeError("flags must a 0 or 1 byte string, got %r", flags)
301 needle, found = self.bsearch2(key)
301 needle, found = self.bsearch2(key)
302 if found:
302 if found:
303 # put the item
303 # put the item
304 pos = self.positions[needle]
304 pos = self.positions[needle]
305 if pos < 0:
305 if pos < 0:
306 self.extradata[-pos - 1] = (key, hashval, value[1])
306 self.extradata[-pos - 1] = (key, hashval, value[1])
307 else:
307 else:
308 # just don't bother
308 # just don't bother
309 self.extradata.append((key, hashval, value[1]))
309 self.extradata.append((key, hashval, value[1]))
310 self.positions[needle] = -len(self.extradata)
310 self.positions[needle] = -len(self.extradata)
311 else:
311 else:
312 # not found, put it in with extra positions
312 # not found, put it in with extra positions
313 self.extradata.append((key, hashval, value[1]))
313 self.extradata.append((key, hashval, value[1]))
314 self.positions = (self.positions[:needle] + [-len(self.extradata)]
314 self.positions = (self.positions[:needle] + [-len(self.extradata)]
315 + self.positions[needle:])
315 + self.positions[needle:])
316 self.extrainfo = (self.extrainfo[:needle] + [0] +
316 self.extrainfo = (self.extrainfo[:needle] + [0] +
317 self.extrainfo[needle:])
317 self.extrainfo[needle:])
318
318
319 def copy(self):
319 def copy(self):
320 # XXX call _compact like in C?
320 # XXX call _compact like in C?
321 return _lazymanifest(self.data, self.positions, self.extrainfo,
321 return _lazymanifest(self.data, self.positions, self.extrainfo,
322 self.extradata)
322 self.extradata)
323
323
324 def _compact(self):
324 def _compact(self):
325 # hopefully not called TOO often
325 # hopefully not called TOO often
326 if len(self.extradata) == 0:
326 if len(self.extradata) == 0:
327 return
327 return
328 l = []
328 l = []
329 last_cut = 0
329 last_cut = 0
330 i = 0
330 i = 0
331 offset = 0
331 offset = 0
332 self.extrainfo = [0] * len(self.positions)
332 self.extrainfo = [0] * len(self.positions)
333 while i < len(self.positions):
333 while i < len(self.positions):
334 if self.positions[i] >= 0:
334 if self.positions[i] >= 0:
335 cur = self.positions[i]
335 cur = self.positions[i]
336 last_cut = cur
336 last_cut = cur
337 while True:
337 while True:
338 self.positions[i] = offset
338 self.positions[i] = offset
339 i += 1
339 i += 1
340 if i == len(self.positions) or self.positions[i] < 0:
340 if i == len(self.positions) or self.positions[i] < 0:
341 break
341 break
342 offset += self.positions[i] - cur
342 offset += self.positions[i] - cur
343 cur = self.positions[i]
343 cur = self.positions[i]
344 end_cut = self.data.find('\n', cur)
344 end_cut = self.data.find('\n', cur)
345 if end_cut != -1:
345 if end_cut != -1:
346 end_cut += 1
346 end_cut += 1
347 offset += end_cut - cur
347 offset += end_cut - cur
348 l.append(self.data[last_cut:end_cut])
348 l.append(self.data[last_cut:end_cut])
349 else:
349 else:
350 while i < len(self.positions) and self.positions[i] < 0:
350 while i < len(self.positions) and self.positions[i] < 0:
351 cur = self.positions[i]
351 cur = self.positions[i]
352 t = self.extradata[-cur - 1]
352 t = self.extradata[-cur - 1]
353 l.append(self._pack(t))
353 l.append(self._pack(t))
354 self.positions[i] = offset
354 self.positions[i] = offset
355 if len(t[1]) > 20:
355 if len(t[1]) > 20:
356 self.extrainfo[i] = ord(t[1][21])
356 self.extrainfo[i] = ord(t[1][21])
357 offset += len(l[-1])
357 offset += len(l[-1])
358 i += 1
358 i += 1
359 self.data = ''.join(l)
359 self.data = ''.join(l)
360 self.extradata = []
360 self.extradata = []
361
361
362 def _pack(self, d):
362 def _pack(self, d):
363 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
363 return d[0] + '\x00' + hex(d[1][:20]) + d[2] + '\n'
364
364
365 def text(self):
365 def text(self):
366 self._compact()
366 self._compact()
367 return self.data
367 return self.data
368
368
369 def diff(self, m2, clean=False):
369 def diff(self, m2, clean=False):
370 '''Finds changes between the current manifest and m2.'''
370 '''Finds changes between the current manifest and m2.'''
371 # XXX think whether efficiency matters here
371 # XXX think whether efficiency matters here
372 diff = {}
372 diff = {}
373
373
374 for fn, e1, flags in self.iterentries():
374 for fn, e1, flags in self.iterentries():
375 if fn not in m2:
375 if fn not in m2:
376 diff[fn] = (e1, flags), (None, '')
376 diff[fn] = (e1, flags), (None, '')
377 else:
377 else:
378 e2 = m2[fn]
378 e2 = m2[fn]
379 if (e1, flags) != e2:
379 if (e1, flags) != e2:
380 diff[fn] = (e1, flags), e2
380 diff[fn] = (e1, flags), e2
381 elif clean:
381 elif clean:
382 diff[fn] = None
382 diff[fn] = None
383
383
384 for fn, e2, flags in m2.iterentries():
384 for fn, e2, flags in m2.iterentries():
385 if fn not in self:
385 if fn not in self:
386 diff[fn] = (None, ''), (e2, flags)
386 diff[fn] = (None, ''), (e2, flags)
387
387
388 return diff
388 return diff
389
389
390 def iterentries(self):
390 def iterentries(self):
391 return lazymanifestiterentries(self)
391 return lazymanifestiterentries(self)
392
392
393 def iterkeys(self):
393 def iterkeys(self):
394 return lazymanifestiter(self)
394 return lazymanifestiter(self)
395
395
396 def __iter__(self):
396 def __iter__(self):
397 return lazymanifestiter(self)
397 return lazymanifestiter(self)
398
398
399 def __len__(self):
399 def __len__(self):
400 return len(self.positions)
400 return len(self.positions)
401
401
402 def filtercopy(self, filterfn):
402 def filtercopy(self, filterfn):
403 # XXX should be optimized
403 # XXX should be optimized
404 c = _lazymanifest('')
404 c = _lazymanifest('')
405 for f, n, fl in self.iterentries():
405 for f, n, fl in self.iterentries():
406 if filterfn(f):
406 if filterfn(f):
407 c[f] = n, fl
407 c[f] = n, fl
408 return c
408 return c
409
409
410 try:
410 try:
411 _lazymanifest = parsers.lazymanifest
411 _lazymanifest = parsers.lazymanifest
412 except AttributeError:
412 except AttributeError:
413 pass
413 pass
414
414
415 class manifestdict(object):
415 class manifestdict(object):
416 def __init__(self, data=''):
416 def __init__(self, data=''):
417 if data.startswith('\0'):
417 if data.startswith('\0'):
418 #_lazymanifest can not parse v2
418 #_lazymanifest can not parse v2
419 self._lm = _lazymanifest('')
419 self._lm = _lazymanifest('')
420 for f, n, fl in _parsev2(data):
420 for f, n, fl in _parsev2(data):
421 self._lm[f] = n, fl
421 self._lm[f] = n, fl
422 else:
422 else:
423 self._lm = _lazymanifest(data)
423 self._lm = _lazymanifest(data)
424
424
425 def __getitem__(self, key):
425 def __getitem__(self, key):
426 return self._lm[key][0]
426 return self._lm[key][0]
427
427
428 def find(self, key):
428 def find(self, key):
429 return self._lm[key]
429 return self._lm[key]
430
430
431 def __len__(self):
431 def __len__(self):
432 return len(self._lm)
432 return len(self._lm)
433
433
434 def __nonzero__(self):
434 def __nonzero__(self):
435 # nonzero is covered by the __len__ function, but implementing it here
435 # nonzero is covered by the __len__ function, but implementing it here
436 # makes it easier for extensions to override.
436 # makes it easier for extensions to override.
437 return len(self._lm) != 0
437 return len(self._lm) != 0
438
438
439 __bool__ = __nonzero__
439 __bool__ = __nonzero__
440
440
441 def __setitem__(self, key, node):
441 def __setitem__(self, key, node):
442 self._lm[key] = node, self.flags(key, '')
442 self._lm[key] = node, self.flags(key, '')
443
443
444 def __contains__(self, key):
444 def __contains__(self, key):
445 return key in self._lm
445 return key in self._lm
446
446
447 def __delitem__(self, key):
447 def __delitem__(self, key):
448 del self._lm[key]
448 del self._lm[key]
449
449
450 def __iter__(self):
450 def __iter__(self):
451 return self._lm.__iter__()
451 return self._lm.__iter__()
452
452
453 def iterkeys(self):
453 def iterkeys(self):
454 return self._lm.iterkeys()
454 return self._lm.iterkeys()
455
455
456 def keys(self):
456 def keys(self):
457 return list(self.iterkeys())
457 return list(self.iterkeys())
458
458
459 def filesnotin(self, m2, match=None):
459 def filesnotin(self, m2, match=None):
460 '''Set of files in this manifest that are not in the other'''
460 '''Set of files in this manifest that are not in the other'''
461 if match:
461 if match:
462 m1 = self.matches(match)
462 m1 = self.matches(match)
463 m2 = m2.matches(match)
463 m2 = m2.matches(match)
464 return m1.filesnotin(m2)
464 return m1.filesnotin(m2)
465 diff = self.diff(m2)
465 diff = self.diff(m2)
466 files = set(filepath
466 files = set(filepath
467 for filepath, hashflags in diff.iteritems()
467 for filepath, hashflags in diff.iteritems()
468 if hashflags[1][0] is None)
468 if hashflags[1][0] is None)
469 return files
469 return files
470
470
471 @propertycache
471 @propertycache
472 def _dirs(self):
472 def _dirs(self):
473 return util.dirs(self)
473 return util.dirs(self)
474
474
475 def dirs(self):
475 def dirs(self):
476 return self._dirs
476 return self._dirs
477
477
478 def hasdir(self, dir):
478 def hasdir(self, dir):
479 return dir in self._dirs
479 return dir in self._dirs
480
480
481 def _filesfastpath(self, match):
481 def _filesfastpath(self, match):
482 '''Checks whether we can correctly and quickly iterate over matcher
482 '''Checks whether we can correctly and quickly iterate over matcher
483 files instead of over manifest files.'''
483 files instead of over manifest files.'''
484 files = match.files()
484 files = match.files()
485 return (len(files) < 100 and (match.isexact() or
485 return (len(files) < 100 and (match.isexact() or
486 (match.prefix() and all(fn in self for fn in files))))
486 (match.prefix() and all(fn in self for fn in files))))
487
487
488 def walk(self, match):
488 def walk(self, match):
489 '''Generates matching file names.
489 '''Generates matching file names.
490
490
491 Equivalent to manifest.matches(match).iterkeys(), but without creating
491 Equivalent to manifest.matches(match).iterkeys(), but without creating
492 an entirely new manifest.
492 an entirely new manifest.
493
493
494 It also reports nonexistent files by marking them bad with match.bad().
494 It also reports nonexistent files by marking them bad with match.bad().
495 '''
495 '''
496 if match.always():
496 if match.always():
497 for f in iter(self):
497 for f in iter(self):
498 yield f
498 yield f
499 return
499 return
500
500
501 fset = set(match.files())
501 fset = set(match.files())
502
502
503 # avoid the entire walk if we're only looking for specific files
503 # avoid the entire walk if we're only looking for specific files
504 if self._filesfastpath(match):
504 if self._filesfastpath(match):
505 for fn in sorted(fset):
505 for fn in sorted(fset):
506 yield fn
506 yield fn
507 return
507 return
508
508
509 for fn in self:
509 for fn in self:
510 if fn in fset:
510 if fn in fset:
511 # specified pattern is the exact name
511 # specified pattern is the exact name
512 fset.remove(fn)
512 fset.remove(fn)
513 if match(fn):
513 if match(fn):
514 yield fn
514 yield fn
515
515
516 # for dirstate.walk, files=['.'] means "walk the whole tree".
516 # for dirstate.walk, files=['.'] means "walk the whole tree".
517 # follow that here, too
517 # follow that here, too
518 fset.discard('.')
518 fset.discard('.')
519
519
520 for fn in sorted(fset):
520 for fn in sorted(fset):
521 if not self.hasdir(fn):
521 if not self.hasdir(fn):
522 match.bad(fn, None)
522 match.bad(fn, None)
523
523
524 def matches(self, match):
524 def matches(self, match):
525 '''generate a new manifest filtered by the match argument'''
525 '''generate a new manifest filtered by the match argument'''
526 if match.always():
526 if match.always():
527 return self.copy()
527 return self.copy()
528
528
529 if self._filesfastpath(match):
529 if self._filesfastpath(match):
530 m = manifestdict()
530 m = manifestdict()
531 lm = self._lm
531 lm = self._lm
532 for fn in match.files():
532 for fn in match.files():
533 if fn in lm:
533 if fn in lm:
534 m._lm[fn] = lm[fn]
534 m._lm[fn] = lm[fn]
535 return m
535 return m
536
536
537 m = manifestdict()
537 m = manifestdict()
538 m._lm = self._lm.filtercopy(match)
538 m._lm = self._lm.filtercopy(match)
539 return m
539 return m
540
540
541 def diff(self, m2, match=None, clean=False):
541 def diff(self, m2, match=None, clean=False):
542 '''Finds changes between the current manifest and m2.
542 '''Finds changes between the current manifest and m2.
543
543
544 Args:
544 Args:
545 m2: the manifest to which this manifest should be compared.
545 m2: the manifest to which this manifest should be compared.
546 clean: if true, include files unchanged between these manifests
546 clean: if true, include files unchanged between these manifests
547 with a None value in the returned dictionary.
547 with a None value in the returned dictionary.
548
548
549 The result is returned as a dict with filename as key and
549 The result is returned as a dict with filename as key and
550 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
550 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
551 nodeid in the current/other manifest and fl1/fl2 is the flag
551 nodeid in the current/other manifest and fl1/fl2 is the flag
552 in the current/other manifest. Where the file does not exist,
552 in the current/other manifest. Where the file does not exist,
553 the nodeid will be None and the flags will be the empty
553 the nodeid will be None and the flags will be the empty
554 string.
554 string.
555 '''
555 '''
556 if match:
556 if match:
557 m1 = self.matches(match)
557 m1 = self.matches(match)
558 m2 = m2.matches(match)
558 m2 = m2.matches(match)
559 return m1.diff(m2, clean=clean)
559 return m1.diff(m2, clean=clean)
560 return self._lm.diff(m2._lm, clean)
560 return self._lm.diff(m2._lm, clean)
561
561
562 def setflag(self, key, flag):
562 def setflag(self, key, flag):
563 self._lm[key] = self[key], flag
563 self._lm[key] = self[key], flag
564
564
565 def get(self, key, default=None):
565 def get(self, key, default=None):
566 try:
566 try:
567 return self._lm[key][0]
567 return self._lm[key][0]
568 except KeyError:
568 except KeyError:
569 return default
569 return default
570
570
571 def flags(self, key, default=''):
571 def flags(self, key, default=''):
572 try:
572 try:
573 return self._lm[key][1]
573 return self._lm[key][1]
574 except KeyError:
574 except KeyError:
575 return default
575 return default
576
576
577 def copy(self):
577 def copy(self):
578 c = manifestdict()
578 c = manifestdict()
579 c._lm = self._lm.copy()
579 c._lm = self._lm.copy()
580 return c
580 return c
581
581
582 def iteritems(self):
582 def items(self):
583 return (x[:2] for x in self._lm.iterentries())
583 return (x[:2] for x in self._lm.iterentries())
584
584
585 iteritems = items
586
585 def iterentries(self):
587 def iterentries(self):
586 return self._lm.iterentries()
588 return self._lm.iterentries()
587
589
588 def text(self, usemanifestv2=False):
590 def text(self, usemanifestv2=False):
589 if usemanifestv2:
591 if usemanifestv2:
590 return _textv2(self._lm.iterentries())
592 return _textv2(self._lm.iterentries())
591 else:
593 else:
592 # use (probably) native version for v1
594 # use (probably) native version for v1
593 return self._lm.text()
595 return self._lm.text()
594
596
595 def fastdelta(self, base, changes):
597 def fastdelta(self, base, changes):
596 """Given a base manifest text as a bytearray and a list of changes
598 """Given a base manifest text as a bytearray and a list of changes
597 relative to that text, compute a delta that can be used by revlog.
599 relative to that text, compute a delta that can be used by revlog.
598 """
600 """
599 delta = []
601 delta = []
600 dstart = None
602 dstart = None
601 dend = None
603 dend = None
602 dline = [""]
604 dline = [""]
603 start = 0
605 start = 0
604 # zero copy representation of base as a buffer
606 # zero copy representation of base as a buffer
605 addbuf = util.buffer(base)
607 addbuf = util.buffer(base)
606
608
607 changes = list(changes)
609 changes = list(changes)
608 if len(changes) < 1000:
610 if len(changes) < 1000:
609 # start with a readonly loop that finds the offset of
611 # start with a readonly loop that finds the offset of
610 # each line and creates the deltas
612 # each line and creates the deltas
611 for f, todelete in changes:
613 for f, todelete in changes:
612 # bs will either be the index of the item or the insert point
614 # bs will either be the index of the item or the insert point
613 start, end = _msearch(addbuf, f, start)
615 start, end = _msearch(addbuf, f, start)
614 if not todelete:
616 if not todelete:
615 h, fl = self._lm[f]
617 h, fl = self._lm[f]
616 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
618 l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
617 else:
619 else:
618 if start == end:
620 if start == end:
619 # item we want to delete was not found, error out
621 # item we want to delete was not found, error out
620 raise AssertionError(
622 raise AssertionError(
621 _("failed to remove %s from manifest") % f)
623 _("failed to remove %s from manifest") % f)
622 l = ""
624 l = ""
623 if dstart is not None and dstart <= start and dend >= start:
625 if dstart is not None and dstart <= start and dend >= start:
624 if dend < end:
626 if dend < end:
625 dend = end
627 dend = end
626 if l:
628 if l:
627 dline.append(l)
629 dline.append(l)
628 else:
630 else:
629 if dstart is not None:
631 if dstart is not None:
630 delta.append([dstart, dend, "".join(dline)])
632 delta.append([dstart, dend, "".join(dline)])
631 dstart = start
633 dstart = start
632 dend = end
634 dend = end
633 dline = [l]
635 dline = [l]
634
636
635 if dstart is not None:
637 if dstart is not None:
636 delta.append([dstart, dend, "".join(dline)])
638 delta.append([dstart, dend, "".join(dline)])
637 # apply the delta to the base, and get a delta for addrevision
639 # apply the delta to the base, and get a delta for addrevision
638 deltatext, arraytext = _addlistdelta(base, delta)
640 deltatext, arraytext = _addlistdelta(base, delta)
639 else:
641 else:
640 # For large changes, it's much cheaper to just build the text and
642 # For large changes, it's much cheaper to just build the text and
641 # diff it.
643 # diff it.
642 arraytext = bytearray(self.text())
644 arraytext = bytearray(self.text())
643 deltatext = mdiff.textdiff(
645 deltatext = mdiff.textdiff(
644 util.buffer(base), util.buffer(arraytext))
646 util.buffer(base), util.buffer(arraytext))
645
647
646 return arraytext, deltatext
648 return arraytext, deltatext
647
649
648 def _msearch(m, s, lo=0, hi=None):
650 def _msearch(m, s, lo=0, hi=None):
649 '''return a tuple (start, end) that says where to find s within m.
651 '''return a tuple (start, end) that says where to find s within m.
650
652
651 If the string is found m[start:end] are the line containing
653 If the string is found m[start:end] are the line containing
652 that string. If start == end the string was not found and
654 that string. If start == end the string was not found and
653 they indicate the proper sorted insertion point.
655 they indicate the proper sorted insertion point.
654
656
655 m should be a buffer, a memoryview or a byte string.
657 m should be a buffer, a memoryview or a byte string.
656 s is a byte string'''
658 s is a byte string'''
657 def advance(i, c):
659 def advance(i, c):
658 while i < lenm and m[i:i + 1] != c:
660 while i < lenm and m[i:i + 1] != c:
659 i += 1
661 i += 1
660 return i
662 return i
661 if not s:
663 if not s:
662 return (lo, lo)
664 return (lo, lo)
663 lenm = len(m)
665 lenm = len(m)
664 if not hi:
666 if not hi:
665 hi = lenm
667 hi = lenm
666 while lo < hi:
668 while lo < hi:
667 mid = (lo + hi) // 2
669 mid = (lo + hi) // 2
668 start = mid
670 start = mid
669 while start > 0 and m[start - 1:start] != '\n':
671 while start > 0 and m[start - 1:start] != '\n':
670 start -= 1
672 start -= 1
671 end = advance(start, '\0')
673 end = advance(start, '\0')
672 if bytes(m[start:end]) < s:
674 if bytes(m[start:end]) < s:
673 # we know that after the null there are 40 bytes of sha1
675 # we know that after the null there are 40 bytes of sha1
674 # this translates to the bisect lo = mid + 1
676 # this translates to the bisect lo = mid + 1
675 lo = advance(end + 40, '\n') + 1
677 lo = advance(end + 40, '\n') + 1
676 else:
678 else:
677 # this translates to the bisect hi = mid
679 # this translates to the bisect hi = mid
678 hi = start
680 hi = start
679 end = advance(lo, '\0')
681 end = advance(lo, '\0')
680 found = m[lo:end]
682 found = m[lo:end]
681 if s == found:
683 if s == found:
682 # we know that after the null there are 40 bytes of sha1
684 # we know that after the null there are 40 bytes of sha1
683 end = advance(end + 40, '\n')
685 end = advance(end + 40, '\n')
684 return (lo, end + 1)
686 return (lo, end + 1)
685 else:
687 else:
686 return (lo, lo)
688 return (lo, lo)
687
689
688 def _checkforbidden(l):
690 def _checkforbidden(l):
689 """Check filenames for illegal characters."""
691 """Check filenames for illegal characters."""
690 for f in l:
692 for f in l:
691 if '\n' in f or '\r' in f:
693 if '\n' in f or '\r' in f:
692 raise error.RevlogError(
694 raise error.RevlogError(
693 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
695 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
694
696
695
697
696 # apply the changes collected during the bisect loop to our addlist
698 # apply the changes collected during the bisect loop to our addlist
697 # return a delta suitable for addrevision
699 # return a delta suitable for addrevision
698 def _addlistdelta(addlist, x):
700 def _addlistdelta(addlist, x):
699 # for large addlist arrays, building a new array is cheaper
701 # for large addlist arrays, building a new array is cheaper
700 # than repeatedly modifying the existing one
702 # than repeatedly modifying the existing one
701 currentposition = 0
703 currentposition = 0
702 newaddlist = bytearray()
704 newaddlist = bytearray()
703
705
704 for start, end, content in x:
706 for start, end, content in x:
705 newaddlist += addlist[currentposition:start]
707 newaddlist += addlist[currentposition:start]
706 if content:
708 if content:
707 newaddlist += bytearray(content)
709 newaddlist += bytearray(content)
708
710
709 currentposition = end
711 currentposition = end
710
712
711 newaddlist += addlist[currentposition:]
713 newaddlist += addlist[currentposition:]
712
714
713 deltatext = "".join(struct.pack(">lll", start, end, len(content))
715 deltatext = "".join(struct.pack(">lll", start, end, len(content))
714 + content for start, end, content in x)
716 + content for start, end, content in x)
715 return deltatext, newaddlist
717 return deltatext, newaddlist
716
718
717 def _splittopdir(f):
719 def _splittopdir(f):
718 if '/' in f:
720 if '/' in f:
719 dir, subpath = f.split('/', 1)
721 dir, subpath = f.split('/', 1)
720 return dir + '/', subpath
722 return dir + '/', subpath
721 else:
723 else:
722 return '', f
724 return '', f
723
725
724 _noop = lambda s: None
726 _noop = lambda s: None
725
727
726 class treemanifest(object):
728 class treemanifest(object):
727 def __init__(self, dir='', text=''):
729 def __init__(self, dir='', text=''):
728 self._dir = dir
730 self._dir = dir
729 self._node = revlog.nullid
731 self._node = revlog.nullid
730 self._loadfunc = _noop
732 self._loadfunc = _noop
731 self._copyfunc = _noop
733 self._copyfunc = _noop
732 self._dirty = False
734 self._dirty = False
733 self._dirs = {}
735 self._dirs = {}
734 # Using _lazymanifest here is a little slower than plain old dicts
736 # Using _lazymanifest here is a little slower than plain old dicts
735 self._files = {}
737 self._files = {}
736 self._flags = {}
738 self._flags = {}
737 if text:
739 if text:
738 def readsubtree(subdir, subm):
740 def readsubtree(subdir, subm):
739 raise AssertionError('treemanifest constructor only accepts '
741 raise AssertionError('treemanifest constructor only accepts '
740 'flat manifests')
742 'flat manifests')
741 self.parse(text, readsubtree)
743 self.parse(text, readsubtree)
742 self._dirty = True # Mark flat manifest dirty after parsing
744 self._dirty = True # Mark flat manifest dirty after parsing
743
745
744 def _subpath(self, path):
746 def _subpath(self, path):
745 return self._dir + path
747 return self._dir + path
746
748
747 def __len__(self):
749 def __len__(self):
748 self._load()
750 self._load()
749 size = len(self._files)
751 size = len(self._files)
750 for m in self._dirs.values():
752 for m in self._dirs.values():
751 size += m.__len__()
753 size += m.__len__()
752 return size
754 return size
753
755
754 def _isempty(self):
756 def _isempty(self):
755 self._load() # for consistency; already loaded by all callers
757 self._load() # for consistency; already loaded by all callers
756 return (not self._files and (not self._dirs or
758 return (not self._files and (not self._dirs or
757 all(m._isempty() for m in self._dirs.values())))
759 all(m._isempty() for m in self._dirs.values())))
758
760
759 def __repr__(self):
761 def __repr__(self):
760 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
762 return ('<treemanifest dir=%s, node=%s, loaded=%s, dirty=%s at 0x%x>' %
761 (self._dir, revlog.hex(self._node),
763 (self._dir, revlog.hex(self._node),
762 bool(self._loadfunc is _noop),
764 bool(self._loadfunc is _noop),
763 self._dirty, id(self)))
765 self._dirty, id(self)))
764
766
765 def dir(self):
767 def dir(self):
766 '''The directory that this tree manifest represents, including a
768 '''The directory that this tree manifest represents, including a
767 trailing '/'. Empty string for the repo root directory.'''
769 trailing '/'. Empty string for the repo root directory.'''
768 return self._dir
770 return self._dir
769
771
770 def node(self):
772 def node(self):
771 '''This node of this instance. nullid for unsaved instances. Should
773 '''This node of this instance. nullid for unsaved instances. Should
772 be updated when the instance is read or written from a revlog.
774 be updated when the instance is read or written from a revlog.
773 '''
775 '''
774 assert not self._dirty
776 assert not self._dirty
775 return self._node
777 return self._node
776
778
777 def setnode(self, node):
779 def setnode(self, node):
778 self._node = node
780 self._node = node
779 self._dirty = False
781 self._dirty = False
780
782
781 def iterentries(self):
783 def iterentries(self):
782 self._load()
784 self._load()
783 for p, n in sorted(itertools.chain(self._dirs.items(),
785 for p, n in sorted(itertools.chain(self._dirs.items(),
784 self._files.items())):
786 self._files.items())):
785 if p in self._files:
787 if p in self._files:
786 yield self._subpath(p), n, self._flags.get(p, '')
788 yield self._subpath(p), n, self._flags.get(p, '')
787 else:
789 else:
788 for x in n.iterentries():
790 for x in n.iterentries():
789 yield x
791 yield x
790
792
791 def iteritems(self):
793 def items(self):
792 self._load()
794 self._load()
793 for p, n in sorted(itertools.chain(self._dirs.items(),
795 for p, n in sorted(itertools.chain(self._dirs.items(),
794 self._files.items())):
796 self._files.items())):
795 if p in self._files:
797 if p in self._files:
796 yield self._subpath(p), n
798 yield self._subpath(p), n
797 else:
799 else:
798 for f, sn in n.iteritems():
800 for f, sn in n.iteritems():
799 yield f, sn
801 yield f, sn
800
802
803 iteritems = items
804
801 def iterkeys(self):
805 def iterkeys(self):
802 self._load()
806 self._load()
803 for p in sorted(itertools.chain(self._dirs, self._files)):
807 for p in sorted(itertools.chain(self._dirs, self._files)):
804 if p in self._files:
808 if p in self._files:
805 yield self._subpath(p)
809 yield self._subpath(p)
806 else:
810 else:
807 for f in self._dirs[p].iterkeys():
811 for f in self._dirs[p].iterkeys():
808 yield f
812 yield f
809
813
810 def keys(self):
814 def keys(self):
811 return list(self.iterkeys())
815 return list(self.iterkeys())
812
816
813 def __iter__(self):
817 def __iter__(self):
814 return self.iterkeys()
818 return self.iterkeys()
815
819
816 def __contains__(self, f):
820 def __contains__(self, f):
817 if f is None:
821 if f is None:
818 return False
822 return False
819 self._load()
823 self._load()
820 dir, subpath = _splittopdir(f)
824 dir, subpath = _splittopdir(f)
821 if dir:
825 if dir:
822 if dir not in self._dirs:
826 if dir not in self._dirs:
823 return False
827 return False
824 return self._dirs[dir].__contains__(subpath)
828 return self._dirs[dir].__contains__(subpath)
825 else:
829 else:
826 return f in self._files
830 return f in self._files
827
831
828 def get(self, f, default=None):
832 def get(self, f, default=None):
829 self._load()
833 self._load()
830 dir, subpath = _splittopdir(f)
834 dir, subpath = _splittopdir(f)
831 if dir:
835 if dir:
832 if dir not in self._dirs:
836 if dir not in self._dirs:
833 return default
837 return default
834 return self._dirs[dir].get(subpath, default)
838 return self._dirs[dir].get(subpath, default)
835 else:
839 else:
836 return self._files.get(f, default)
840 return self._files.get(f, default)
837
841
838 def __getitem__(self, f):
842 def __getitem__(self, f):
839 self._load()
843 self._load()
840 dir, subpath = _splittopdir(f)
844 dir, subpath = _splittopdir(f)
841 if dir:
845 if dir:
842 return self._dirs[dir].__getitem__(subpath)
846 return self._dirs[dir].__getitem__(subpath)
843 else:
847 else:
844 return self._files[f]
848 return self._files[f]
845
849
846 def flags(self, f):
850 def flags(self, f):
847 self._load()
851 self._load()
848 dir, subpath = _splittopdir(f)
852 dir, subpath = _splittopdir(f)
849 if dir:
853 if dir:
850 if dir not in self._dirs:
854 if dir not in self._dirs:
851 return ''
855 return ''
852 return self._dirs[dir].flags(subpath)
856 return self._dirs[dir].flags(subpath)
853 else:
857 else:
854 if f in self._dirs:
858 if f in self._dirs:
855 return ''
859 return ''
856 return self._flags.get(f, '')
860 return self._flags.get(f, '')
857
861
858 def find(self, f):
862 def find(self, f):
859 self._load()
863 self._load()
860 dir, subpath = _splittopdir(f)
864 dir, subpath = _splittopdir(f)
861 if dir:
865 if dir:
862 return self._dirs[dir].find(subpath)
866 return self._dirs[dir].find(subpath)
863 else:
867 else:
864 return self._files[f], self._flags.get(f, '')
868 return self._files[f], self._flags.get(f, '')
865
869
866 def __delitem__(self, f):
870 def __delitem__(self, f):
867 self._load()
871 self._load()
868 dir, subpath = _splittopdir(f)
872 dir, subpath = _splittopdir(f)
869 if dir:
873 if dir:
870 self._dirs[dir].__delitem__(subpath)
874 self._dirs[dir].__delitem__(subpath)
871 # If the directory is now empty, remove it
875 # If the directory is now empty, remove it
872 if self._dirs[dir]._isempty():
876 if self._dirs[dir]._isempty():
873 del self._dirs[dir]
877 del self._dirs[dir]
874 else:
878 else:
875 del self._files[f]
879 del self._files[f]
876 if f in self._flags:
880 if f in self._flags:
877 del self._flags[f]
881 del self._flags[f]
878 self._dirty = True
882 self._dirty = True
879
883
880 def __setitem__(self, f, n):
884 def __setitem__(self, f, n):
881 assert n is not None
885 assert n is not None
882 self._load()
886 self._load()
883 dir, subpath = _splittopdir(f)
887 dir, subpath = _splittopdir(f)
884 if dir:
888 if dir:
885 if dir not in self._dirs:
889 if dir not in self._dirs:
886 self._dirs[dir] = treemanifest(self._subpath(dir))
890 self._dirs[dir] = treemanifest(self._subpath(dir))
887 self._dirs[dir].__setitem__(subpath, n)
891 self._dirs[dir].__setitem__(subpath, n)
888 else:
892 else:
889 self._files[f] = n[:21] # to match manifestdict's behavior
893 self._files[f] = n[:21] # to match manifestdict's behavior
890 self._dirty = True
894 self._dirty = True
891
895
892 def _load(self):
896 def _load(self):
893 if self._loadfunc is not _noop:
897 if self._loadfunc is not _noop:
894 lf, self._loadfunc = self._loadfunc, _noop
898 lf, self._loadfunc = self._loadfunc, _noop
895 lf(self)
899 lf(self)
896 elif self._copyfunc is not _noop:
900 elif self._copyfunc is not _noop:
897 cf, self._copyfunc = self._copyfunc, _noop
901 cf, self._copyfunc = self._copyfunc, _noop
898 cf(self)
902 cf(self)
899
903
900 def setflag(self, f, flags):
904 def setflag(self, f, flags):
901 """Set the flags (symlink, executable) for path f."""
905 """Set the flags (symlink, executable) for path f."""
902 self._load()
906 self._load()
903 dir, subpath = _splittopdir(f)
907 dir, subpath = _splittopdir(f)
904 if dir:
908 if dir:
905 if dir not in self._dirs:
909 if dir not in self._dirs:
906 self._dirs[dir] = treemanifest(self._subpath(dir))
910 self._dirs[dir] = treemanifest(self._subpath(dir))
907 self._dirs[dir].setflag(subpath, flags)
911 self._dirs[dir].setflag(subpath, flags)
908 else:
912 else:
909 self._flags[f] = flags
913 self._flags[f] = flags
910 self._dirty = True
914 self._dirty = True
911
915
912 def copy(self):
916 def copy(self):
913 copy = treemanifest(self._dir)
917 copy = treemanifest(self._dir)
914 copy._node = self._node
918 copy._node = self._node
915 copy._dirty = self._dirty
919 copy._dirty = self._dirty
916 if self._copyfunc is _noop:
920 if self._copyfunc is _noop:
917 def _copyfunc(s):
921 def _copyfunc(s):
918 self._load()
922 self._load()
919 for d in self._dirs:
923 for d in self._dirs:
920 s._dirs[d] = self._dirs[d].copy()
924 s._dirs[d] = self._dirs[d].copy()
921 s._files = dict.copy(self._files)
925 s._files = dict.copy(self._files)
922 s._flags = dict.copy(self._flags)
926 s._flags = dict.copy(self._flags)
923 if self._loadfunc is _noop:
927 if self._loadfunc is _noop:
924 _copyfunc(copy)
928 _copyfunc(copy)
925 else:
929 else:
926 copy._copyfunc = _copyfunc
930 copy._copyfunc = _copyfunc
927 else:
931 else:
928 copy._copyfunc = self._copyfunc
932 copy._copyfunc = self._copyfunc
929 return copy
933 return copy
930
934
931 def filesnotin(self, m2, match=None):
935 def filesnotin(self, m2, match=None):
932 '''Set of files in this manifest that are not in the other'''
936 '''Set of files in this manifest that are not in the other'''
933 if match:
937 if match:
934 m1 = self.matches(match)
938 m1 = self.matches(match)
935 m2 = m2.matches(match)
939 m2 = m2.matches(match)
936 return m1.filesnotin(m2)
940 return m1.filesnotin(m2)
937
941
938 files = set()
942 files = set()
939 def _filesnotin(t1, t2):
943 def _filesnotin(t1, t2):
940 if t1._node == t2._node and not t1._dirty and not t2._dirty:
944 if t1._node == t2._node and not t1._dirty and not t2._dirty:
941 return
945 return
942 t1._load()
946 t1._load()
943 t2._load()
947 t2._load()
944 for d, m1 in t1._dirs.iteritems():
948 for d, m1 in t1._dirs.iteritems():
945 if d in t2._dirs:
949 if d in t2._dirs:
946 m2 = t2._dirs[d]
950 m2 = t2._dirs[d]
947 _filesnotin(m1, m2)
951 _filesnotin(m1, m2)
948 else:
952 else:
949 files.update(m1.iterkeys())
953 files.update(m1.iterkeys())
950
954
951 for fn in t1._files.iterkeys():
955 for fn in t1._files.iterkeys():
952 if fn not in t2._files:
956 if fn not in t2._files:
953 files.add(t1._subpath(fn))
957 files.add(t1._subpath(fn))
954
958
955 _filesnotin(self, m2)
959 _filesnotin(self, m2)
956 return files
960 return files
957
961
958 @propertycache
962 @propertycache
959 def _alldirs(self):
963 def _alldirs(self):
960 return util.dirs(self)
964 return util.dirs(self)
961
965
962 def dirs(self):
966 def dirs(self):
963 return self._alldirs
967 return self._alldirs
964
968
965 def hasdir(self, dir):
969 def hasdir(self, dir):
966 self._load()
970 self._load()
967 topdir, subdir = _splittopdir(dir)
971 topdir, subdir = _splittopdir(dir)
968 if topdir:
972 if topdir:
969 if topdir in self._dirs:
973 if topdir in self._dirs:
970 return self._dirs[topdir].hasdir(subdir)
974 return self._dirs[topdir].hasdir(subdir)
971 return False
975 return False
972 return (dir + '/') in self._dirs
976 return (dir + '/') in self._dirs
973
977
974 def walk(self, match):
978 def walk(self, match):
975 '''Generates matching file names.
979 '''Generates matching file names.
976
980
977 Equivalent to manifest.matches(match).iterkeys(), but without creating
981 Equivalent to manifest.matches(match).iterkeys(), but without creating
978 an entirely new manifest.
982 an entirely new manifest.
979
983
980 It also reports nonexistent files by marking them bad with match.bad().
984 It also reports nonexistent files by marking them bad with match.bad().
981 '''
985 '''
982 if match.always():
986 if match.always():
983 for f in iter(self):
987 for f in iter(self):
984 yield f
988 yield f
985 return
989 return
986
990
987 fset = set(match.files())
991 fset = set(match.files())
988
992
989 for fn in self._walk(match):
993 for fn in self._walk(match):
990 if fn in fset:
994 if fn in fset:
991 # specified pattern is the exact name
995 # specified pattern is the exact name
992 fset.remove(fn)
996 fset.remove(fn)
993 yield fn
997 yield fn
994
998
995 # for dirstate.walk, files=['.'] means "walk the whole tree".
999 # for dirstate.walk, files=['.'] means "walk the whole tree".
996 # follow that here, too
1000 # follow that here, too
997 fset.discard('.')
1001 fset.discard('.')
998
1002
999 for fn in sorted(fset):
1003 for fn in sorted(fset):
1000 if not self.hasdir(fn):
1004 if not self.hasdir(fn):
1001 match.bad(fn, None)
1005 match.bad(fn, None)
1002
1006
1003 def _walk(self, match):
1007 def _walk(self, match):
1004 '''Recursively generates matching file names for walk().'''
1008 '''Recursively generates matching file names for walk().'''
1005 if not match.visitdir(self._dir[:-1] or '.'):
1009 if not match.visitdir(self._dir[:-1] or '.'):
1006 return
1010 return
1007
1011
1008 # yield this dir's files and walk its submanifests
1012 # yield this dir's files and walk its submanifests
1009 self._load()
1013 self._load()
1010 for p in sorted(self._dirs.keys() + self._files.keys()):
1014 for p in sorted(self._dirs.keys() + self._files.keys()):
1011 if p in self._files:
1015 if p in self._files:
1012 fullp = self._subpath(p)
1016 fullp = self._subpath(p)
1013 if match(fullp):
1017 if match(fullp):
1014 yield fullp
1018 yield fullp
1015 else:
1019 else:
1016 for f in self._dirs[p]._walk(match):
1020 for f in self._dirs[p]._walk(match):
1017 yield f
1021 yield f
1018
1022
1019 def matches(self, match):
1023 def matches(self, match):
1020 '''generate a new manifest filtered by the match argument'''
1024 '''generate a new manifest filtered by the match argument'''
1021 if match.always():
1025 if match.always():
1022 return self.copy()
1026 return self.copy()
1023
1027
1024 return self._matches(match)
1028 return self._matches(match)
1025
1029
1026 def _matches(self, match):
1030 def _matches(self, match):
1027 '''recursively generate a new manifest filtered by the match argument.
1031 '''recursively generate a new manifest filtered by the match argument.
1028 '''
1032 '''
1029
1033
1030 visit = match.visitdir(self._dir[:-1] or '.')
1034 visit = match.visitdir(self._dir[:-1] or '.')
1031 if visit == 'all':
1035 if visit == 'all':
1032 return self.copy()
1036 return self.copy()
1033 ret = treemanifest(self._dir)
1037 ret = treemanifest(self._dir)
1034 if not visit:
1038 if not visit:
1035 return ret
1039 return ret
1036
1040
1037 self._load()
1041 self._load()
1038 for fn in self._files:
1042 for fn in self._files:
1039 fullp = self._subpath(fn)
1043 fullp = self._subpath(fn)
1040 if not match(fullp):
1044 if not match(fullp):
1041 continue
1045 continue
1042 ret._files[fn] = self._files[fn]
1046 ret._files[fn] = self._files[fn]
1043 if fn in self._flags:
1047 if fn in self._flags:
1044 ret._flags[fn] = self._flags[fn]
1048 ret._flags[fn] = self._flags[fn]
1045
1049
1046 for dir, subm in self._dirs.iteritems():
1050 for dir, subm in self._dirs.iteritems():
1047 m = subm._matches(match)
1051 m = subm._matches(match)
1048 if not m._isempty():
1052 if not m._isempty():
1049 ret._dirs[dir] = m
1053 ret._dirs[dir] = m
1050
1054
1051 if not ret._isempty():
1055 if not ret._isempty():
1052 ret._dirty = True
1056 ret._dirty = True
1053 return ret
1057 return ret
1054
1058
1055 def diff(self, m2, match=None, clean=False):
1059 def diff(self, m2, match=None, clean=False):
1056 '''Finds changes between the current manifest and m2.
1060 '''Finds changes between the current manifest and m2.
1057
1061
1058 Args:
1062 Args:
1059 m2: the manifest to which this manifest should be compared.
1063 m2: the manifest to which this manifest should be compared.
1060 clean: if true, include files unchanged between these manifests
1064 clean: if true, include files unchanged between these manifests
1061 with a None value in the returned dictionary.
1065 with a None value in the returned dictionary.
1062
1066
1063 The result is returned as a dict with filename as key and
1067 The result is returned as a dict with filename as key and
1064 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1068 values of the form ((n1,fl1),(n2,fl2)), where n1/n2 is the
1065 nodeid in the current/other manifest and fl1/fl2 is the flag
1069 nodeid in the current/other manifest and fl1/fl2 is the flag
1066 in the current/other manifest. Where the file does not exist,
1070 in the current/other manifest. Where the file does not exist,
1067 the nodeid will be None and the flags will be the empty
1071 the nodeid will be None and the flags will be the empty
1068 string.
1072 string.
1069 '''
1073 '''
1070 if match:
1074 if match:
1071 m1 = self.matches(match)
1075 m1 = self.matches(match)
1072 m2 = m2.matches(match)
1076 m2 = m2.matches(match)
1073 return m1.diff(m2, clean=clean)
1077 return m1.diff(m2, clean=clean)
1074 result = {}
1078 result = {}
1075 emptytree = treemanifest()
1079 emptytree = treemanifest()
1076 def _diff(t1, t2):
1080 def _diff(t1, t2):
1077 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1081 if t1._node == t2._node and not t1._dirty and not t2._dirty:
1078 return
1082 return
1079 t1._load()
1083 t1._load()
1080 t2._load()
1084 t2._load()
1081 for d, m1 in t1._dirs.iteritems():
1085 for d, m1 in t1._dirs.iteritems():
1082 m2 = t2._dirs.get(d, emptytree)
1086 m2 = t2._dirs.get(d, emptytree)
1083 _diff(m1, m2)
1087 _diff(m1, m2)
1084
1088
1085 for d, m2 in t2._dirs.iteritems():
1089 for d, m2 in t2._dirs.iteritems():
1086 if d not in t1._dirs:
1090 if d not in t1._dirs:
1087 _diff(emptytree, m2)
1091 _diff(emptytree, m2)
1088
1092
1089 for fn, n1 in t1._files.iteritems():
1093 for fn, n1 in t1._files.iteritems():
1090 fl1 = t1._flags.get(fn, '')
1094 fl1 = t1._flags.get(fn, '')
1091 n2 = t2._files.get(fn, None)
1095 n2 = t2._files.get(fn, None)
1092 fl2 = t2._flags.get(fn, '')
1096 fl2 = t2._flags.get(fn, '')
1093 if n1 != n2 or fl1 != fl2:
1097 if n1 != n2 or fl1 != fl2:
1094 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1098 result[t1._subpath(fn)] = ((n1, fl1), (n2, fl2))
1095 elif clean:
1099 elif clean:
1096 result[t1._subpath(fn)] = None
1100 result[t1._subpath(fn)] = None
1097
1101
1098 for fn, n2 in t2._files.iteritems():
1102 for fn, n2 in t2._files.iteritems():
1099 if fn not in t1._files:
1103 if fn not in t1._files:
1100 fl2 = t2._flags.get(fn, '')
1104 fl2 = t2._flags.get(fn, '')
1101 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1105 result[t2._subpath(fn)] = ((None, ''), (n2, fl2))
1102
1106
1103 _diff(self, m2)
1107 _diff(self, m2)
1104 return result
1108 return result
1105
1109
1106 def unmodifiedsince(self, m2):
1110 def unmodifiedsince(self, m2):
1107 return not self._dirty and not m2._dirty and self._node == m2._node
1111 return not self._dirty and not m2._dirty and self._node == m2._node
1108
1112
1109 def parse(self, text, readsubtree):
1113 def parse(self, text, readsubtree):
1110 for f, n, fl in _parse(text):
1114 for f, n, fl in _parse(text):
1111 if fl == 't':
1115 if fl == 't':
1112 f = f + '/'
1116 f = f + '/'
1113 self._dirs[f] = readsubtree(self._subpath(f), n)
1117 self._dirs[f] = readsubtree(self._subpath(f), n)
1114 elif '/' in f:
1118 elif '/' in f:
1115 # This is a flat manifest, so use __setitem__ and setflag rather
1119 # This is a flat manifest, so use __setitem__ and setflag rather
1116 # than assigning directly to _files and _flags, so we can
1120 # than assigning directly to _files and _flags, so we can
1117 # assign a path in a subdirectory, and to mark dirty (compared
1121 # assign a path in a subdirectory, and to mark dirty (compared
1118 # to nullid).
1122 # to nullid).
1119 self[f] = n
1123 self[f] = n
1120 if fl:
1124 if fl:
1121 self.setflag(f, fl)
1125 self.setflag(f, fl)
1122 else:
1126 else:
1123 # Assigning to _files and _flags avoids marking as dirty,
1127 # Assigning to _files and _flags avoids marking as dirty,
1124 # and should be a little faster.
1128 # and should be a little faster.
1125 self._files[f] = n
1129 self._files[f] = n
1126 if fl:
1130 if fl:
1127 self._flags[f] = fl
1131 self._flags[f] = fl
1128
1132
1129 def text(self, usemanifestv2=False):
1133 def text(self, usemanifestv2=False):
1130 """Get the full data of this manifest as a bytestring."""
1134 """Get the full data of this manifest as a bytestring."""
1131 self._load()
1135 self._load()
1132 return _text(self.iterentries(), usemanifestv2)
1136 return _text(self.iterentries(), usemanifestv2)
1133
1137
1134 def dirtext(self, usemanifestv2=False):
1138 def dirtext(self, usemanifestv2=False):
1135 """Get the full data of this directory as a bytestring. Make sure that
1139 """Get the full data of this directory as a bytestring. Make sure that
1136 any submanifests have been written first, so their nodeids are correct.
1140 any submanifests have been written first, so their nodeids are correct.
1137 """
1141 """
1138 self._load()
1142 self._load()
1139 flags = self.flags
1143 flags = self.flags
1140 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1144 dirs = [(d[:-1], self._dirs[d]._node, 't') for d in self._dirs]
1141 files = [(f, self._files[f], flags(f)) for f in self._files]
1145 files = [(f, self._files[f], flags(f)) for f in self._files]
1142 return _text(sorted(dirs + files), usemanifestv2)
1146 return _text(sorted(dirs + files), usemanifestv2)
1143
1147
1144 def read(self, gettext, readsubtree):
1148 def read(self, gettext, readsubtree):
1145 def _load_for_read(s):
1149 def _load_for_read(s):
1146 s.parse(gettext(), readsubtree)
1150 s.parse(gettext(), readsubtree)
1147 s._dirty = False
1151 s._dirty = False
1148 self._loadfunc = _load_for_read
1152 self._loadfunc = _load_for_read
1149
1153
1150 def writesubtrees(self, m1, m2, writesubtree):
1154 def writesubtrees(self, m1, m2, writesubtree):
1151 self._load() # for consistency; should never have any effect here
1155 self._load() # for consistency; should never have any effect here
1152 m1._load()
1156 m1._load()
1153 m2._load()
1157 m2._load()
1154 emptytree = treemanifest()
1158 emptytree = treemanifest()
1155 for d, subm in self._dirs.iteritems():
1159 for d, subm in self._dirs.iteritems():
1156 subp1 = m1._dirs.get(d, emptytree)._node
1160 subp1 = m1._dirs.get(d, emptytree)._node
1157 subp2 = m2._dirs.get(d, emptytree)._node
1161 subp2 = m2._dirs.get(d, emptytree)._node
1158 if subp1 == revlog.nullid:
1162 if subp1 == revlog.nullid:
1159 subp1, subp2 = subp2, subp1
1163 subp1, subp2 = subp2, subp1
1160 writesubtree(subm, subp1, subp2)
1164 writesubtree(subm, subp1, subp2)
1161
1165
1162 def walksubtrees(self, matcher=None):
1166 def walksubtrees(self, matcher=None):
1163 """Returns an iterator of the subtrees of this manifest, including this
1167 """Returns an iterator of the subtrees of this manifest, including this
1164 manifest itself.
1168 manifest itself.
1165
1169
1166 If `matcher` is provided, it only returns subtrees that match.
1170 If `matcher` is provided, it only returns subtrees that match.
1167 """
1171 """
1168 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1172 if matcher and not matcher.visitdir(self._dir[:-1] or '.'):
1169 return
1173 return
1170 if not matcher or matcher(self._dir[:-1]):
1174 if not matcher or matcher(self._dir[:-1]):
1171 yield self
1175 yield self
1172
1176
1173 self._load()
1177 self._load()
1174 for d, subm in self._dirs.iteritems():
1178 for d, subm in self._dirs.iteritems():
1175 for subtree in subm.walksubtrees(matcher=matcher):
1179 for subtree in subm.walksubtrees(matcher=matcher):
1176 yield subtree
1180 yield subtree
1177
1181
1178 class manifestrevlog(revlog.revlog):
1182 class manifestrevlog(revlog.revlog):
1179 '''A revlog that stores manifest texts. This is responsible for caching the
1183 '''A revlog that stores manifest texts. This is responsible for caching the
1180 full-text manifest contents.
1184 full-text manifest contents.
1181 '''
1185 '''
1182 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None,
1186 def __init__(self, opener, dir='', dirlogcache=None, indexfile=None,
1183 treemanifest=False):
1187 treemanifest=False):
1184 """Constructs a new manifest revlog
1188 """Constructs a new manifest revlog
1185
1189
1186 `indexfile` - used by extensions to have two manifests at once, like
1190 `indexfile` - used by extensions to have two manifests at once, like
1187 when transitioning between flatmanifeset and treemanifests.
1191 when transitioning between flatmanifeset and treemanifests.
1188
1192
1189 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1193 `treemanifest` - used to indicate this is a tree manifest revlog. Opener
1190 options can also be used to make this a tree manifest revlog. The opener
1194 options can also be used to make this a tree manifest revlog. The opener
1191 option takes precedence, so if it is set to True, we ignore whatever
1195 option takes precedence, so if it is set to True, we ignore whatever
1192 value is passed in to the constructor.
1196 value is passed in to the constructor.
1193 """
1197 """
1194 # During normal operations, we expect to deal with not more than four
1198 # During normal operations, we expect to deal with not more than four
1195 # revs at a time (such as during commit --amend). When rebasing large
1199 # revs at a time (such as during commit --amend). When rebasing large
1196 # stacks of commits, the number can go up, hence the config knob below.
1200 # stacks of commits, the number can go up, hence the config knob below.
1197 cachesize = 4
1201 cachesize = 4
1198 optiontreemanifest = False
1202 optiontreemanifest = False
1199 usemanifestv2 = False
1203 usemanifestv2 = False
1200 opts = getattr(opener, 'options', None)
1204 opts = getattr(opener, 'options', None)
1201 if opts is not None:
1205 if opts is not None:
1202 cachesize = opts.get('manifestcachesize', cachesize)
1206 cachesize = opts.get('manifestcachesize', cachesize)
1203 optiontreemanifest = opts.get('treemanifest', False)
1207 optiontreemanifest = opts.get('treemanifest', False)
1204 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1208 usemanifestv2 = opts.get('manifestv2', usemanifestv2)
1205
1209
1206 self._treeondisk = optiontreemanifest or treemanifest
1210 self._treeondisk = optiontreemanifest or treemanifest
1207 self._usemanifestv2 = usemanifestv2
1211 self._usemanifestv2 = usemanifestv2
1208
1212
1209 self._fulltextcache = util.lrucachedict(cachesize)
1213 self._fulltextcache = util.lrucachedict(cachesize)
1210
1214
1211 if dir:
1215 if dir:
1212 assert self._treeondisk, 'opts is %r' % opts
1216 assert self._treeondisk, 'opts is %r' % opts
1213 if not dir.endswith('/'):
1217 if not dir.endswith('/'):
1214 dir = dir + '/'
1218 dir = dir + '/'
1215
1219
1216 if indexfile is None:
1220 if indexfile is None:
1217 indexfile = '00manifest.i'
1221 indexfile = '00manifest.i'
1218 if dir:
1222 if dir:
1219 indexfile = "meta/" + dir + indexfile
1223 indexfile = "meta/" + dir + indexfile
1220
1224
1221 self._dir = dir
1225 self._dir = dir
1222 # The dirlogcache is kept on the root manifest log
1226 # The dirlogcache is kept on the root manifest log
1223 if dir:
1227 if dir:
1224 self._dirlogcache = dirlogcache
1228 self._dirlogcache = dirlogcache
1225 else:
1229 else:
1226 self._dirlogcache = {'': self}
1230 self._dirlogcache = {'': self}
1227
1231
1228 super(manifestrevlog, self).__init__(opener, indexfile,
1232 super(manifestrevlog, self).__init__(opener, indexfile,
1229 checkambig=bool(dir))
1233 checkambig=bool(dir))
1230
1234
1231 @property
1235 @property
1232 def fulltextcache(self):
1236 def fulltextcache(self):
1233 return self._fulltextcache
1237 return self._fulltextcache
1234
1238
1235 def clearcaches(self):
1239 def clearcaches(self):
1236 super(manifestrevlog, self).clearcaches()
1240 super(manifestrevlog, self).clearcaches()
1237 self._fulltextcache.clear()
1241 self._fulltextcache.clear()
1238 self._dirlogcache = {'': self}
1242 self._dirlogcache = {'': self}
1239
1243
1240 def dirlog(self, dir):
1244 def dirlog(self, dir):
1241 if dir:
1245 if dir:
1242 assert self._treeondisk
1246 assert self._treeondisk
1243 if dir not in self._dirlogcache:
1247 if dir not in self._dirlogcache:
1244 mfrevlog = manifestrevlog(self.opener, dir,
1248 mfrevlog = manifestrevlog(self.opener, dir,
1245 self._dirlogcache,
1249 self._dirlogcache,
1246 treemanifest=self._treeondisk)
1250 treemanifest=self._treeondisk)
1247 self._dirlogcache[dir] = mfrevlog
1251 self._dirlogcache[dir] = mfrevlog
1248 return self._dirlogcache[dir]
1252 return self._dirlogcache[dir]
1249
1253
1250 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1254 def add(self, m, transaction, link, p1, p2, added, removed, readtree=None):
1251 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1255 if (p1 in self.fulltextcache and util.safehasattr(m, 'fastdelta')
1252 and not self._usemanifestv2):
1256 and not self._usemanifestv2):
1253 # If our first parent is in the manifest cache, we can
1257 # If our first parent is in the manifest cache, we can
1254 # compute a delta here using properties we know about the
1258 # compute a delta here using properties we know about the
1255 # manifest up-front, which may save time later for the
1259 # manifest up-front, which may save time later for the
1256 # revlog layer.
1260 # revlog layer.
1257
1261
1258 _checkforbidden(added)
1262 _checkforbidden(added)
1259 # combine the changed lists into one sorted iterator
1263 # combine the changed lists into one sorted iterator
1260 work = heapq.merge([(x, False) for x in added],
1264 work = heapq.merge([(x, False) for x in added],
1261 [(x, True) for x in removed])
1265 [(x, True) for x in removed])
1262
1266
1263 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1267 arraytext, deltatext = m.fastdelta(self.fulltextcache[p1], work)
1264 cachedelta = self.rev(p1), deltatext
1268 cachedelta = self.rev(p1), deltatext
1265 text = util.buffer(arraytext)
1269 text = util.buffer(arraytext)
1266 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1270 n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
1267 else:
1271 else:
1268 # The first parent manifest isn't already loaded, so we'll
1272 # The first parent manifest isn't already loaded, so we'll
1269 # just encode a fulltext of the manifest and pass that
1273 # just encode a fulltext of the manifest and pass that
1270 # through to the revlog layer, and let it handle the delta
1274 # through to the revlog layer, and let it handle the delta
1271 # process.
1275 # process.
1272 if self._treeondisk:
1276 if self._treeondisk:
1273 assert readtree, "readtree must be set for treemanifest writes"
1277 assert readtree, "readtree must be set for treemanifest writes"
1274 m1 = readtree(self._dir, p1)
1278 m1 = readtree(self._dir, p1)
1275 m2 = readtree(self._dir, p2)
1279 m2 = readtree(self._dir, p2)
1276 n = self._addtree(m, transaction, link, m1, m2, readtree)
1280 n = self._addtree(m, transaction, link, m1, m2, readtree)
1277 arraytext = None
1281 arraytext = None
1278 else:
1282 else:
1279 text = m.text(self._usemanifestv2)
1283 text = m.text(self._usemanifestv2)
1280 n = self.addrevision(text, transaction, link, p1, p2)
1284 n = self.addrevision(text, transaction, link, p1, p2)
1281 arraytext = bytearray(text)
1285 arraytext = bytearray(text)
1282
1286
1283 if arraytext is not None:
1287 if arraytext is not None:
1284 self.fulltextcache[n] = arraytext
1288 self.fulltextcache[n] = arraytext
1285
1289
1286 return n
1290 return n
1287
1291
1288 def _addtree(self, m, transaction, link, m1, m2, readtree):
1292 def _addtree(self, m, transaction, link, m1, m2, readtree):
1289 # If the manifest is unchanged compared to one parent,
1293 # If the manifest is unchanged compared to one parent,
1290 # don't write a new revision
1294 # don't write a new revision
1291 if self._dir != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(m2)):
1295 if self._dir != '' and (m.unmodifiedsince(m1) or m.unmodifiedsince(m2)):
1292 return m.node()
1296 return m.node()
1293 def writesubtree(subm, subp1, subp2):
1297 def writesubtree(subm, subp1, subp2):
1294 sublog = self.dirlog(subm.dir())
1298 sublog = self.dirlog(subm.dir())
1295 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1299 sublog.add(subm, transaction, link, subp1, subp2, None, None,
1296 readtree=readtree)
1300 readtree=readtree)
1297 m.writesubtrees(m1, m2, writesubtree)
1301 m.writesubtrees(m1, m2, writesubtree)
1298 text = m.dirtext(self._usemanifestv2)
1302 text = m.dirtext(self._usemanifestv2)
1299 n = None
1303 n = None
1300 if self._dir != '':
1304 if self._dir != '':
1301 # Double-check whether contents are unchanged to one parent
1305 # Double-check whether contents are unchanged to one parent
1302 if text == m1.dirtext(self._usemanifestv2):
1306 if text == m1.dirtext(self._usemanifestv2):
1303 n = m1.node()
1307 n = m1.node()
1304 elif text == m2.dirtext(self._usemanifestv2):
1308 elif text == m2.dirtext(self._usemanifestv2):
1305 n = m2.node()
1309 n = m2.node()
1306
1310
1307 if not n:
1311 if not n:
1308 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1312 n = self.addrevision(text, transaction, link, m1.node(), m2.node())
1309
1313
1310 # Save nodeid so parent manifest can calculate its nodeid
1314 # Save nodeid so parent manifest can calculate its nodeid
1311 m.setnode(n)
1315 m.setnode(n)
1312 return n
1316 return n
1313
1317
1314 class manifestlog(object):
1318 class manifestlog(object):
1315 """A collection class representing the collection of manifest snapshots
1319 """A collection class representing the collection of manifest snapshots
1316 referenced by commits in the repository.
1320 referenced by commits in the repository.
1317
1321
1318 In this situation, 'manifest' refers to the abstract concept of a snapshot
1322 In this situation, 'manifest' refers to the abstract concept of a snapshot
1319 of the list of files in the given commit. Consumers of the output of this
1323 of the list of files in the given commit. Consumers of the output of this
1320 class do not care about the implementation details of the actual manifests
1324 class do not care about the implementation details of the actual manifests
1321 they receive (i.e. tree or flat or lazily loaded, etc)."""
1325 they receive (i.e. tree or flat or lazily loaded, etc)."""
1322 def __init__(self, opener, repo):
1326 def __init__(self, opener, repo):
1323 usetreemanifest = False
1327 usetreemanifest = False
1324 cachesize = 4
1328 cachesize = 4
1325
1329
1326 opts = getattr(opener, 'options', None)
1330 opts = getattr(opener, 'options', None)
1327 if opts is not None:
1331 if opts is not None:
1328 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1332 usetreemanifest = opts.get('treemanifest', usetreemanifest)
1329 cachesize = opts.get('manifestcachesize', cachesize)
1333 cachesize = opts.get('manifestcachesize', cachesize)
1330 self._treeinmem = usetreemanifest
1334 self._treeinmem = usetreemanifest
1331
1335
1332 self._revlog = repo._constructmanifest()
1336 self._revlog = repo._constructmanifest()
1333
1337
1334 # A cache of the manifestctx or treemanifestctx for each directory
1338 # A cache of the manifestctx or treemanifestctx for each directory
1335 self._dirmancache = {}
1339 self._dirmancache = {}
1336 self._dirmancache[''] = util.lrucachedict(cachesize)
1340 self._dirmancache[''] = util.lrucachedict(cachesize)
1337
1341
1338 self.cachesize = cachesize
1342 self.cachesize = cachesize
1339
1343
1340 def __getitem__(self, node):
1344 def __getitem__(self, node):
1341 """Retrieves the manifest instance for the given node. Throws a
1345 """Retrieves the manifest instance for the given node. Throws a
1342 LookupError if not found.
1346 LookupError if not found.
1343 """
1347 """
1344 return self.get('', node)
1348 return self.get('', node)
1345
1349
1346 def get(self, dir, node, verify=True):
1350 def get(self, dir, node, verify=True):
1347 """Retrieves the manifest instance for the given node. Throws a
1351 """Retrieves the manifest instance for the given node. Throws a
1348 LookupError if not found.
1352 LookupError if not found.
1349
1353
1350 `verify` - if True an exception will be thrown if the node is not in
1354 `verify` - if True an exception will be thrown if the node is not in
1351 the revlog
1355 the revlog
1352 """
1356 """
1353 if node in self._dirmancache.get(dir, ()):
1357 if node in self._dirmancache.get(dir, ()):
1354 return self._dirmancache[dir][node]
1358 return self._dirmancache[dir][node]
1355
1359
1356 if dir:
1360 if dir:
1357 if self._revlog._treeondisk:
1361 if self._revlog._treeondisk:
1358 if verify:
1362 if verify:
1359 dirlog = self._revlog.dirlog(dir)
1363 dirlog = self._revlog.dirlog(dir)
1360 if node not in dirlog.nodemap:
1364 if node not in dirlog.nodemap:
1361 raise LookupError(node, dirlog.indexfile,
1365 raise LookupError(node, dirlog.indexfile,
1362 _('no node'))
1366 _('no node'))
1363 m = treemanifestctx(self, dir, node)
1367 m = treemanifestctx(self, dir, node)
1364 else:
1368 else:
1365 raise error.Abort(
1369 raise error.Abort(
1366 _("cannot ask for manifest directory '%s' in a flat "
1370 _("cannot ask for manifest directory '%s' in a flat "
1367 "manifest") % dir)
1371 "manifest") % dir)
1368 else:
1372 else:
1369 if verify:
1373 if verify:
1370 if node not in self._revlog.nodemap:
1374 if node not in self._revlog.nodemap:
1371 raise LookupError(node, self._revlog.indexfile,
1375 raise LookupError(node, self._revlog.indexfile,
1372 _('no node'))
1376 _('no node'))
1373 if self._treeinmem:
1377 if self._treeinmem:
1374 m = treemanifestctx(self, '', node)
1378 m = treemanifestctx(self, '', node)
1375 else:
1379 else:
1376 m = manifestctx(self, node)
1380 m = manifestctx(self, node)
1377
1381
1378 if node != revlog.nullid:
1382 if node != revlog.nullid:
1379 mancache = self._dirmancache.get(dir)
1383 mancache = self._dirmancache.get(dir)
1380 if not mancache:
1384 if not mancache:
1381 mancache = util.lrucachedict(self.cachesize)
1385 mancache = util.lrucachedict(self.cachesize)
1382 self._dirmancache[dir] = mancache
1386 self._dirmancache[dir] = mancache
1383 mancache[node] = m
1387 mancache[node] = m
1384 return m
1388 return m
1385
1389
1386 def clearcaches(self):
1390 def clearcaches(self):
1387 self._dirmancache.clear()
1391 self._dirmancache.clear()
1388 self._revlog.clearcaches()
1392 self._revlog.clearcaches()
1389
1393
1390 class memmanifestctx(object):
1394 class memmanifestctx(object):
1391 def __init__(self, manifestlog):
1395 def __init__(self, manifestlog):
1392 self._manifestlog = manifestlog
1396 self._manifestlog = manifestlog
1393 self._manifestdict = manifestdict()
1397 self._manifestdict = manifestdict()
1394
1398
1395 def _revlog(self):
1399 def _revlog(self):
1396 return self._manifestlog._revlog
1400 return self._manifestlog._revlog
1397
1401
1398 def new(self):
1402 def new(self):
1399 return memmanifestctx(self._manifestlog)
1403 return memmanifestctx(self._manifestlog)
1400
1404
1401 def copy(self):
1405 def copy(self):
1402 memmf = memmanifestctx(self._manifestlog)
1406 memmf = memmanifestctx(self._manifestlog)
1403 memmf._manifestdict = self.read().copy()
1407 memmf._manifestdict = self.read().copy()
1404 return memmf
1408 return memmf
1405
1409
1406 def read(self):
1410 def read(self):
1407 return self._manifestdict
1411 return self._manifestdict
1408
1412
1409 def write(self, transaction, link, p1, p2, added, removed):
1413 def write(self, transaction, link, p1, p2, added, removed):
1410 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1414 return self._revlog().add(self._manifestdict, transaction, link, p1, p2,
1411 added, removed)
1415 added, removed)
1412
1416
1413 class manifestctx(object):
1417 class manifestctx(object):
1414 """A class representing a single revision of a manifest, including its
1418 """A class representing a single revision of a manifest, including its
1415 contents, its parent revs, and its linkrev.
1419 contents, its parent revs, and its linkrev.
1416 """
1420 """
1417 def __init__(self, manifestlog, node):
1421 def __init__(self, manifestlog, node):
1418 self._manifestlog = manifestlog
1422 self._manifestlog = manifestlog
1419 self._data = None
1423 self._data = None
1420
1424
1421 self._node = node
1425 self._node = node
1422
1426
1423 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1427 # TODO: We eventually want p1, p2, and linkrev exposed on this class,
1424 # but let's add it later when something needs it and we can load it
1428 # but let's add it later when something needs it and we can load it
1425 # lazily.
1429 # lazily.
1426 #self.p1, self.p2 = revlog.parents(node)
1430 #self.p1, self.p2 = revlog.parents(node)
1427 #rev = revlog.rev(node)
1431 #rev = revlog.rev(node)
1428 #self.linkrev = revlog.linkrev(rev)
1432 #self.linkrev = revlog.linkrev(rev)
1429
1433
1430 def _revlog(self):
1434 def _revlog(self):
1431 return self._manifestlog._revlog
1435 return self._manifestlog._revlog
1432
1436
1433 def node(self):
1437 def node(self):
1434 return self._node
1438 return self._node
1435
1439
1436 def new(self):
1440 def new(self):
1437 return memmanifestctx(self._manifestlog)
1441 return memmanifestctx(self._manifestlog)
1438
1442
1439 def copy(self):
1443 def copy(self):
1440 memmf = memmanifestctx(self._manifestlog)
1444 memmf = memmanifestctx(self._manifestlog)
1441 memmf._manifestdict = self.read().copy()
1445 memmf._manifestdict = self.read().copy()
1442 return memmf
1446 return memmf
1443
1447
1444 @propertycache
1448 @propertycache
1445 def parents(self):
1449 def parents(self):
1446 return self._revlog().parents(self._node)
1450 return self._revlog().parents(self._node)
1447
1451
1448 def read(self):
1452 def read(self):
1449 if self._data is None:
1453 if self._data is None:
1450 if self._node == revlog.nullid:
1454 if self._node == revlog.nullid:
1451 self._data = manifestdict()
1455 self._data = manifestdict()
1452 else:
1456 else:
1453 rl = self._revlog()
1457 rl = self._revlog()
1454 text = rl.revision(self._node)
1458 text = rl.revision(self._node)
1455 arraytext = bytearray(text)
1459 arraytext = bytearray(text)
1456 rl._fulltextcache[self._node] = arraytext
1460 rl._fulltextcache[self._node] = arraytext
1457 self._data = manifestdict(text)
1461 self._data = manifestdict(text)
1458 return self._data
1462 return self._data
1459
1463
1460 def readfast(self, shallow=False):
1464 def readfast(self, shallow=False):
1461 '''Calls either readdelta or read, based on which would be less work.
1465 '''Calls either readdelta or read, based on which would be less work.
1462 readdelta is called if the delta is against the p1, and therefore can be
1466 readdelta is called if the delta is against the p1, and therefore can be
1463 read quickly.
1467 read quickly.
1464
1468
1465 If `shallow` is True, nothing changes since this is a flat manifest.
1469 If `shallow` is True, nothing changes since this is a flat manifest.
1466 '''
1470 '''
1467 rl = self._revlog()
1471 rl = self._revlog()
1468 r = rl.rev(self._node)
1472 r = rl.rev(self._node)
1469 deltaparent = rl.deltaparent(r)
1473 deltaparent = rl.deltaparent(r)
1470 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1474 if deltaparent != revlog.nullrev and deltaparent in rl.parentrevs(r):
1471 return self.readdelta()
1475 return self.readdelta()
1472 return self.read()
1476 return self.read()
1473
1477
1474 def readdelta(self, shallow=False):
1478 def readdelta(self, shallow=False):
1475 '''Returns a manifest containing just the entries that are present
1479 '''Returns a manifest containing just the entries that are present
1476 in this manifest, but not in its p1 manifest. This is efficient to read
1480 in this manifest, but not in its p1 manifest. This is efficient to read
1477 if the revlog delta is already p1.
1481 if the revlog delta is already p1.
1478
1482
1479 Changing the value of `shallow` has no effect on flat manifests.
1483 Changing the value of `shallow` has no effect on flat manifests.
1480 '''
1484 '''
1481 revlog = self._revlog()
1485 revlog = self._revlog()
1482 if revlog._usemanifestv2:
1486 if revlog._usemanifestv2:
1483 # Need to perform a slow delta
1487 # Need to perform a slow delta
1484 r0 = revlog.deltaparent(revlog.rev(self._node))
1488 r0 = revlog.deltaparent(revlog.rev(self._node))
1485 m0 = self._manifestlog[revlog.node(r0)].read()
1489 m0 = self._manifestlog[revlog.node(r0)].read()
1486 m1 = self.read()
1490 m1 = self.read()
1487 md = manifestdict()
1491 md = manifestdict()
1488 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1492 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1489 if n1:
1493 if n1:
1490 md[f] = n1
1494 md[f] = n1
1491 if fl1:
1495 if fl1:
1492 md.setflag(f, fl1)
1496 md.setflag(f, fl1)
1493 return md
1497 return md
1494
1498
1495 r = revlog.rev(self._node)
1499 r = revlog.rev(self._node)
1496 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1500 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1497 return manifestdict(d)
1501 return manifestdict(d)
1498
1502
1499 def find(self, key):
1503 def find(self, key):
1500 return self.read().find(key)
1504 return self.read().find(key)
1501
1505
1502 class memtreemanifestctx(object):
1506 class memtreemanifestctx(object):
1503 def __init__(self, manifestlog, dir=''):
1507 def __init__(self, manifestlog, dir=''):
1504 self._manifestlog = manifestlog
1508 self._manifestlog = manifestlog
1505 self._dir = dir
1509 self._dir = dir
1506 self._treemanifest = treemanifest()
1510 self._treemanifest = treemanifest()
1507
1511
1508 def _revlog(self):
1512 def _revlog(self):
1509 return self._manifestlog._revlog
1513 return self._manifestlog._revlog
1510
1514
1511 def new(self, dir=''):
1515 def new(self, dir=''):
1512 return memtreemanifestctx(self._manifestlog, dir=dir)
1516 return memtreemanifestctx(self._manifestlog, dir=dir)
1513
1517
1514 def copy(self):
1518 def copy(self):
1515 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1519 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1516 memmf._treemanifest = self._treemanifest.copy()
1520 memmf._treemanifest = self._treemanifest.copy()
1517 return memmf
1521 return memmf
1518
1522
1519 def read(self):
1523 def read(self):
1520 return self._treemanifest
1524 return self._treemanifest
1521
1525
1522 def write(self, transaction, link, p1, p2, added, removed):
1526 def write(self, transaction, link, p1, p2, added, removed):
1523 def readtree(dir, node):
1527 def readtree(dir, node):
1524 return self._manifestlog.get(dir, node).read()
1528 return self._manifestlog.get(dir, node).read()
1525 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1529 return self._revlog().add(self._treemanifest, transaction, link, p1, p2,
1526 added, removed, readtree=readtree)
1530 added, removed, readtree=readtree)
1527
1531
1528 class treemanifestctx(object):
1532 class treemanifestctx(object):
1529 def __init__(self, manifestlog, dir, node):
1533 def __init__(self, manifestlog, dir, node):
1530 self._manifestlog = manifestlog
1534 self._manifestlog = manifestlog
1531 self._dir = dir
1535 self._dir = dir
1532 self._data = None
1536 self._data = None
1533
1537
1534 self._node = node
1538 self._node = node
1535
1539
1536 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1540 # TODO: Load p1/p2/linkrev lazily. They need to be lazily loaded so that
1537 # we can instantiate treemanifestctx objects for directories we don't
1541 # we can instantiate treemanifestctx objects for directories we don't
1538 # have on disk.
1542 # have on disk.
1539 #self.p1, self.p2 = revlog.parents(node)
1543 #self.p1, self.p2 = revlog.parents(node)
1540 #rev = revlog.rev(node)
1544 #rev = revlog.rev(node)
1541 #self.linkrev = revlog.linkrev(rev)
1545 #self.linkrev = revlog.linkrev(rev)
1542
1546
1543 def _revlog(self):
1547 def _revlog(self):
1544 return self._manifestlog._revlog.dirlog(self._dir)
1548 return self._manifestlog._revlog.dirlog(self._dir)
1545
1549
1546 def read(self):
1550 def read(self):
1547 if self._data is None:
1551 if self._data is None:
1548 rl = self._revlog()
1552 rl = self._revlog()
1549 if self._node == revlog.nullid:
1553 if self._node == revlog.nullid:
1550 self._data = treemanifest()
1554 self._data = treemanifest()
1551 elif rl._treeondisk:
1555 elif rl._treeondisk:
1552 m = treemanifest(dir=self._dir)
1556 m = treemanifest(dir=self._dir)
1553 def gettext():
1557 def gettext():
1554 return rl.revision(self._node)
1558 return rl.revision(self._node)
1555 def readsubtree(dir, subm):
1559 def readsubtree(dir, subm):
1556 # Set verify to False since we need to be able to create
1560 # Set verify to False since we need to be able to create
1557 # subtrees for trees that don't exist on disk.
1561 # subtrees for trees that don't exist on disk.
1558 return self._manifestlog.get(dir, subm, verify=False).read()
1562 return self._manifestlog.get(dir, subm, verify=False).read()
1559 m.read(gettext, readsubtree)
1563 m.read(gettext, readsubtree)
1560 m.setnode(self._node)
1564 m.setnode(self._node)
1561 self._data = m
1565 self._data = m
1562 else:
1566 else:
1563 text = rl.revision(self._node)
1567 text = rl.revision(self._node)
1564 arraytext = bytearray(text)
1568 arraytext = bytearray(text)
1565 rl.fulltextcache[self._node] = arraytext
1569 rl.fulltextcache[self._node] = arraytext
1566 self._data = treemanifest(dir=self._dir, text=text)
1570 self._data = treemanifest(dir=self._dir, text=text)
1567
1571
1568 return self._data
1572 return self._data
1569
1573
1570 def node(self):
1574 def node(self):
1571 return self._node
1575 return self._node
1572
1576
1573 def new(self, dir=''):
1577 def new(self, dir=''):
1574 return memtreemanifestctx(self._manifestlog, dir=dir)
1578 return memtreemanifestctx(self._manifestlog, dir=dir)
1575
1579
1576 def copy(self):
1580 def copy(self):
1577 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1581 memmf = memtreemanifestctx(self._manifestlog, dir=self._dir)
1578 memmf._treemanifest = self.read().copy()
1582 memmf._treemanifest = self.read().copy()
1579 return memmf
1583 return memmf
1580
1584
1581 @propertycache
1585 @propertycache
1582 def parents(self):
1586 def parents(self):
1583 return self._revlog().parents(self._node)
1587 return self._revlog().parents(self._node)
1584
1588
1585 def readdelta(self, shallow=False):
1589 def readdelta(self, shallow=False):
1586 '''Returns a manifest containing just the entries that are present
1590 '''Returns a manifest containing just the entries that are present
1587 in this manifest, but not in its p1 manifest. This is efficient to read
1591 in this manifest, but not in its p1 manifest. This is efficient to read
1588 if the revlog delta is already p1.
1592 if the revlog delta is already p1.
1589
1593
1590 If `shallow` is True, this will read the delta for this directory,
1594 If `shallow` is True, this will read the delta for this directory,
1591 without recursively reading subdirectory manifests. Instead, any
1595 without recursively reading subdirectory manifests. Instead, any
1592 subdirectory entry will be reported as it appears in the manifest, i.e.
1596 subdirectory entry will be reported as it appears in the manifest, i.e.
1593 the subdirectory will be reported among files and distinguished only by
1597 the subdirectory will be reported among files and distinguished only by
1594 its 't' flag.
1598 its 't' flag.
1595 '''
1599 '''
1596 revlog = self._revlog()
1600 revlog = self._revlog()
1597 if shallow and not revlog._usemanifestv2:
1601 if shallow and not revlog._usemanifestv2:
1598 r = revlog.rev(self._node)
1602 r = revlog.rev(self._node)
1599 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1603 d = mdiff.patchtext(revlog.revdiff(revlog.deltaparent(r), r))
1600 return manifestdict(d)
1604 return manifestdict(d)
1601 else:
1605 else:
1602 # Need to perform a slow delta
1606 # Need to perform a slow delta
1603 r0 = revlog.deltaparent(revlog.rev(self._node))
1607 r0 = revlog.deltaparent(revlog.rev(self._node))
1604 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1608 m0 = self._manifestlog.get(self._dir, revlog.node(r0)).read()
1605 m1 = self.read()
1609 m1 = self.read()
1606 md = treemanifest(dir=self._dir)
1610 md = treemanifest(dir=self._dir)
1607 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1611 for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).iteritems():
1608 if n1:
1612 if n1:
1609 md[f] = n1
1613 md[f] = n1
1610 if fl1:
1614 if fl1:
1611 md.setflag(f, fl1)
1615 md.setflag(f, fl1)
1612 return md
1616 return md
1613
1617
1614 def readfast(self, shallow=False):
1618 def readfast(self, shallow=False):
1615 '''Calls either readdelta or read, based on which would be less work.
1619 '''Calls either readdelta or read, based on which would be less work.
1616 readdelta is called if the delta is against the p1, and therefore can be
1620 readdelta is called if the delta is against the p1, and therefore can be
1617 read quickly.
1621 read quickly.
1618
1622
1619 If `shallow` is True, it only returns the entries from this manifest,
1623 If `shallow` is True, it only returns the entries from this manifest,
1620 and not any submanifests.
1624 and not any submanifests.
1621 '''
1625 '''
1622 rl = self._revlog()
1626 rl = self._revlog()
1623 r = rl.rev(self._node)
1627 r = rl.rev(self._node)
1624 deltaparent = rl.deltaparent(r)
1628 deltaparent = rl.deltaparent(r)
1625 if (deltaparent != revlog.nullrev and
1629 if (deltaparent != revlog.nullrev and
1626 deltaparent in rl.parentrevs(r)):
1630 deltaparent in rl.parentrevs(r)):
1627 return self.readdelta(shallow=shallow)
1631 return self.readdelta(shallow=shallow)
1628
1632
1629 if shallow:
1633 if shallow:
1630 return manifestdict(rl.revision(self._node))
1634 return manifestdict(rl.revision(self._node))
1631 else:
1635 else:
1632 return self.read()
1636 return self.read()
1633
1637
1634 def find(self, key):
1638 def find(self, key):
1635 return self.read().find(key)
1639 return self.read().find(key)
@@ -1,194 +1,196 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 from .i18n import _
3 from .i18n import _
4 from . import (
4 from . import (
5 templatekw,
5 templatekw,
6 util,
6 util,
7 )
7 )
8
8
9 def tolist(val):
9 def tolist(val):
10 """
10 """
11 a convenience method to return an empty list instead of None
11 a convenience method to return an empty list instead of None
12 """
12 """
13 if val is None:
13 if val is None:
14 return []
14 return []
15 else:
15 else:
16 return [val]
16 return [val]
17
17
18 class namespaces(object):
18 class namespaces(object):
19 """provides an interface to register and operate on multiple namespaces. See
19 """provides an interface to register and operate on multiple namespaces. See
20 the namespace class below for details on the namespace object.
20 the namespace class below for details on the namespace object.
21
21
22 """
22 """
23
23
24 _names_version = 0
24 _names_version = 0
25
25
26 def __init__(self):
26 def __init__(self):
27 self._names = util.sortdict()
27 self._names = util.sortdict()
28
28
29 # we need current mercurial named objects (bookmarks, tags, and
29 # we need current mercurial named objects (bookmarks, tags, and
30 # branches) to be initialized somewhere, so that place is here
30 # branches) to be initialized somewhere, so that place is here
31 bmknames = lambda repo: repo._bookmarks.keys()
31 bmknames = lambda repo: repo._bookmarks.keys()
32 bmknamemap = lambda repo, name: tolist(repo._bookmarks.get(name))
32 bmknamemap = lambda repo, name: tolist(repo._bookmarks.get(name))
33 bmknodemap = lambda repo, node: repo.nodebookmarks(node)
33 bmknodemap = lambda repo, node: repo.nodebookmarks(node)
34 n = namespace("bookmarks", templatename="bookmark",
34 n = namespace("bookmarks", templatename="bookmark",
35 # i18n: column positioning for "hg log"
35 # i18n: column positioning for "hg log"
36 logfmt=_("bookmark: %s\n"),
36 logfmt=_("bookmark: %s\n"),
37 listnames=bmknames,
37 listnames=bmknames,
38 namemap=bmknamemap, nodemap=bmknodemap)
38 namemap=bmknamemap, nodemap=bmknodemap)
39 self.addnamespace(n)
39 self.addnamespace(n)
40
40
41 tagnames = lambda repo: [t for t, n in repo.tagslist()]
41 tagnames = lambda repo: [t for t, n in repo.tagslist()]
42 tagnamemap = lambda repo, name: tolist(repo._tagscache.tags.get(name))
42 tagnamemap = lambda repo, name: tolist(repo._tagscache.tags.get(name))
43 tagnodemap = lambda repo, node: repo.nodetags(node)
43 tagnodemap = lambda repo, node: repo.nodetags(node)
44 n = namespace("tags", templatename="tag",
44 n = namespace("tags", templatename="tag",
45 # i18n: column positioning for "hg log"
45 # i18n: column positioning for "hg log"
46 logfmt=_("tag: %s\n"),
46 logfmt=_("tag: %s\n"),
47 listnames=tagnames,
47 listnames=tagnames,
48 namemap=tagnamemap, nodemap=tagnodemap,
48 namemap=tagnamemap, nodemap=tagnodemap,
49 deprecated={'tip'})
49 deprecated={'tip'})
50 self.addnamespace(n)
50 self.addnamespace(n)
51
51
52 bnames = lambda repo: repo.branchmap().keys()
52 bnames = lambda repo: repo.branchmap().keys()
53 bnamemap = lambda repo, name: tolist(repo.branchtip(name, True))
53 bnamemap = lambda repo, name: tolist(repo.branchtip(name, True))
54 bnodemap = lambda repo, node: [repo[node].branch()]
54 bnodemap = lambda repo, node: [repo[node].branch()]
55 n = namespace("branches", templatename="branch",
55 n = namespace("branches", templatename="branch",
56 # i18n: column positioning for "hg log"
56 # i18n: column positioning for "hg log"
57 logfmt=_("branch: %s\n"),
57 logfmt=_("branch: %s\n"),
58 listnames=bnames,
58 listnames=bnames,
59 namemap=bnamemap, nodemap=bnodemap)
59 namemap=bnamemap, nodemap=bnodemap)
60 self.addnamespace(n)
60 self.addnamespace(n)
61
61
62 def __getitem__(self, namespace):
62 def __getitem__(self, namespace):
63 """returns the namespace object"""
63 """returns the namespace object"""
64 return self._names[namespace]
64 return self._names[namespace]
65
65
66 def __iter__(self):
66 def __iter__(self):
67 return self._names.__iter__()
67 return self._names.__iter__()
68
68
69 def iteritems(self):
69 def items(self):
70 return self._names.iteritems()
70 return self._names.iteritems()
71
71
72 iteritems = items
73
72 def addnamespace(self, namespace, order=None):
74 def addnamespace(self, namespace, order=None):
73 """register a namespace
75 """register a namespace
74
76
75 namespace: the name to be registered (in plural form)
77 namespace: the name to be registered (in plural form)
76 order: optional argument to specify the order of namespaces
78 order: optional argument to specify the order of namespaces
77 (e.g. 'branches' should be listed before 'bookmarks')
79 (e.g. 'branches' should be listed before 'bookmarks')
78
80
79 """
81 """
80 if order is not None:
82 if order is not None:
81 self._names.insert(order, namespace.name, namespace)
83 self._names.insert(order, namespace.name, namespace)
82 else:
84 else:
83 self._names[namespace.name] = namespace
85 self._names[namespace.name] = namespace
84
86
85 # we only generate a template keyword if one does not already exist
87 # we only generate a template keyword if one does not already exist
86 if namespace.name not in templatekw.keywords:
88 if namespace.name not in templatekw.keywords:
87 def generatekw(**args):
89 def generatekw(**args):
88 return templatekw.shownames(namespace.name, **args)
90 return templatekw.shownames(namespace.name, **args)
89
91
90 templatekw.keywords[namespace.name] = generatekw
92 templatekw.keywords[namespace.name] = generatekw
91
93
92 def singlenode(self, repo, name):
94 def singlenode(self, repo, name):
93 """
95 """
94 Return the 'best' node for the given name. Best means the first node
96 Return the 'best' node for the given name. Best means the first node
95 in the first nonempty list returned by a name-to-nodes mapping function
97 in the first nonempty list returned by a name-to-nodes mapping function
96 in the defined precedence order.
98 in the defined precedence order.
97
99
98 Raises a KeyError if there is no such node.
100 Raises a KeyError if there is no such node.
99 """
101 """
100 for ns, v in self._names.iteritems():
102 for ns, v in self._names.iteritems():
101 n = v.namemap(repo, name)
103 n = v.namemap(repo, name)
102 if n:
104 if n:
103 # return max revision number
105 # return max revision number
104 if len(n) > 1:
106 if len(n) > 1:
105 cl = repo.changelog
107 cl = repo.changelog
106 maxrev = max(cl.rev(node) for node in n)
108 maxrev = max(cl.rev(node) for node in n)
107 return cl.node(maxrev)
109 return cl.node(maxrev)
108 return n[0]
110 return n[0]
109 raise KeyError(_('no such name: %s') % name)
111 raise KeyError(_('no such name: %s') % name)
110
112
111 class namespace(object):
113 class namespace(object):
112 """provides an interface to a namespace
114 """provides an interface to a namespace
113
115
114 Namespaces are basically generic many-to-many mapping between some
116 Namespaces are basically generic many-to-many mapping between some
115 (namespaced) names and nodes. The goal here is to control the pollution of
117 (namespaced) names and nodes. The goal here is to control the pollution of
116 jamming things into tags or bookmarks (in extension-land) and to simplify
118 jamming things into tags or bookmarks (in extension-land) and to simplify
117 internal bits of mercurial: log output, tab completion, etc.
119 internal bits of mercurial: log output, tab completion, etc.
118
120
119 More precisely, we define a mapping of names to nodes, and a mapping from
121 More precisely, we define a mapping of names to nodes, and a mapping from
120 nodes to names. Each mapping returns a list.
122 nodes to names. Each mapping returns a list.
121
123
122 Furthermore, each name mapping will be passed a name to lookup which might
124 Furthermore, each name mapping will be passed a name to lookup which might
123 not be in its domain. In this case, each method should return an empty list
125 not be in its domain. In this case, each method should return an empty list
124 and not raise an error.
126 and not raise an error.
125
127
126 This namespace object will define the properties we need:
128 This namespace object will define the properties we need:
127 'name': the namespace (plural form)
129 'name': the namespace (plural form)
128 'templatename': name to use for templating (usually the singular form
130 'templatename': name to use for templating (usually the singular form
129 of the plural namespace name)
131 of the plural namespace name)
130 'listnames': list of all names in the namespace (usually the keys of a
132 'listnames': list of all names in the namespace (usually the keys of a
131 dictionary)
133 dictionary)
132 'namemap': function that takes a name and returns a list of nodes
134 'namemap': function that takes a name and returns a list of nodes
133 'nodemap': function that takes a node and returns a list of names
135 'nodemap': function that takes a node and returns a list of names
134 'deprecated': set of names to be masked for ordinary use
136 'deprecated': set of names to be masked for ordinary use
135
137
136 """
138 """
137
139
138 def __init__(self, name, templatename=None, logname=None, colorname=None,
140 def __init__(self, name, templatename=None, logname=None, colorname=None,
139 logfmt=None, listnames=None, namemap=None, nodemap=None,
141 logfmt=None, listnames=None, namemap=None, nodemap=None,
140 deprecated=None):
142 deprecated=None):
141 """create a namespace
143 """create a namespace
142
144
143 name: the namespace to be registered (in plural form)
145 name: the namespace to be registered (in plural form)
144 templatename: the name to use for templating
146 templatename: the name to use for templating
145 logname: the name to use for log output; if not specified templatename
147 logname: the name to use for log output; if not specified templatename
146 is used
148 is used
147 colorname: the name to use for colored log output; if not specified
149 colorname: the name to use for colored log output; if not specified
148 logname is used
150 logname is used
149 logfmt: the format to use for (i18n-ed) log output; if not specified
151 logfmt: the format to use for (i18n-ed) log output; if not specified
150 it is composed from logname
152 it is composed from logname
151 listnames: function to list all names
153 listnames: function to list all names
152 namemap: function that inputs a name, output node(s)
154 namemap: function that inputs a name, output node(s)
153 nodemap: function that inputs a node, output name(s)
155 nodemap: function that inputs a node, output name(s)
154 deprecated: set of names to be masked for ordinary use
156 deprecated: set of names to be masked for ordinary use
155
157
156 """
158 """
157 self.name = name
159 self.name = name
158 self.templatename = templatename
160 self.templatename = templatename
159 self.logname = logname
161 self.logname = logname
160 self.colorname = colorname
162 self.colorname = colorname
161 self.logfmt = logfmt
163 self.logfmt = logfmt
162 self.listnames = listnames
164 self.listnames = listnames
163 self.namemap = namemap
165 self.namemap = namemap
164 self.nodemap = nodemap
166 self.nodemap = nodemap
165
167
166 # if logname is not specified, use the template name as backup
168 # if logname is not specified, use the template name as backup
167 if self.logname is None:
169 if self.logname is None:
168 self.logname = self.templatename
170 self.logname = self.templatename
169
171
170 # if colorname is not specified, just use the logname as a backup
172 # if colorname is not specified, just use the logname as a backup
171 if self.colorname is None:
173 if self.colorname is None:
172 self.colorname = self.logname
174 self.colorname = self.logname
173
175
174 # if logfmt is not specified, compose it from logname as backup
176 # if logfmt is not specified, compose it from logname as backup
175 if self.logfmt is None:
177 if self.logfmt is None:
176 # i18n: column positioning for "hg log"
178 # i18n: column positioning for "hg log"
177 self.logfmt = ("%s:" % self.logname).ljust(13) + "%s\n"
179 self.logfmt = ("%s:" % self.logname).ljust(13) + "%s\n"
178
180
179 if deprecated is None:
181 if deprecated is None:
180 self.deprecated = set()
182 self.deprecated = set()
181 else:
183 else:
182 self.deprecated = deprecated
184 self.deprecated = deprecated
183
185
184 def names(self, repo, node):
186 def names(self, repo, node):
185 """method that returns a (sorted) list of names in a namespace that
187 """method that returns a (sorted) list of names in a namespace that
186 match a given node"""
188 match a given node"""
187 return sorted(self.nodemap(repo, node))
189 return sorted(self.nodemap(repo, node))
188
190
189 def nodes(self, repo, name):
191 def nodes(self, repo, name):
190 """method that returns a list of nodes in a namespace that
192 """method that returns a list of nodes in a namespace that
191 match a given name.
193 match a given name.
192
194
193 """
195 """
194 return sorted(self.namemap(repo, name))
196 return sorted(self.namemap(repo, name))
General Comments 0
You need to be logged in to leave comments. Login now