##// END OF EJS Templates
filecache: unimplement __set__() and __delete__() (API)...
Yuya Nishihara -
r40454:7caf632e default
parent child Browse files
Show More
@@ -1,1504 +1,1504 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)[stat.ST_MTIME]
52 return os.fstat(tmpfd)[stat.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 class dirstate(object):
57 class dirstate(object):
58
58
59 def __init__(self, opener, ui, root, validate, sparsematchfn):
59 def __init__(self, opener, ui, root, validate, sparsematchfn):
60 '''Create a new dirstate object.
60 '''Create a new dirstate object.
61
61
62 opener is an open()-like callable that can be used to open the
62 opener is an open()-like callable that can be used to open the
63 dirstate file; root is the root of the directory tracked by
63 dirstate file; root is the root of the directory tracked by
64 the dirstate.
64 the dirstate.
65 '''
65 '''
66 self._opener = opener
66 self._opener = opener
67 self._validate = validate
67 self._validate = validate
68 self._root = root
68 self._root = root
69 self._sparsematchfn = sparsematchfn
69 self._sparsematchfn = sparsematchfn
70 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
70 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
71 # UNC path pointing to root share (issue4557)
71 # UNC path pointing to root share (issue4557)
72 self._rootdir = pathutil.normasprefix(root)
72 self._rootdir = pathutil.normasprefix(root)
73 self._dirty = False
73 self._dirty = False
74 self._lastnormaltime = 0
74 self._lastnormaltime = 0
75 self._ui = ui
75 self._ui = ui
76 self._filecache = {}
76 self._filecache = {}
77 self._parentwriters = 0
77 self._parentwriters = 0
78 self._filename = 'dirstate'
78 self._filename = 'dirstate'
79 self._pendingfilename = '%s.pending' % self._filename
79 self._pendingfilename = '%s.pending' % self._filename
80 self._plchangecallbacks = {}
80 self._plchangecallbacks = {}
81 self._origpl = None
81 self._origpl = None
82 self._updatedfiles = set()
82 self._updatedfiles = set()
83 self._mapcls = dirstatemap
83 self._mapcls = dirstatemap
84
84
85 @contextlib.contextmanager
85 @contextlib.contextmanager
86 def parentchange(self):
86 def parentchange(self):
87 '''Context manager for handling dirstate parents.
87 '''Context manager for handling dirstate parents.
88
88
89 If an exception occurs in the scope of the context manager,
89 If an exception occurs in the scope of the context manager,
90 the incoherent dirstate won't be written when wlock is
90 the incoherent dirstate won't be written when wlock is
91 released.
91 released.
92 '''
92 '''
93 self._parentwriters += 1
93 self._parentwriters += 1
94 yield
94 yield
95 # Typically we want the "undo" step of a context manager in a
95 # Typically we want the "undo" step of a context manager in a
96 # finally block so it happens even when an exception
96 # finally block so it happens even when an exception
97 # occurs. In this case, however, we only want to decrement
97 # occurs. In this case, however, we only want to decrement
98 # parentwriters if the code in the with statement exits
98 # parentwriters if the code in the with statement exits
99 # normally, so we don't have a try/finally here on purpose.
99 # normally, so we don't have a try/finally here on purpose.
100 self._parentwriters -= 1
100 self._parentwriters -= 1
101
101
102 def pendingparentchange(self):
102 def pendingparentchange(self):
103 '''Returns true if the dirstate is in the middle of a set of changes
103 '''Returns true if the dirstate is in the middle of a set of changes
104 that modify the dirstate parent.
104 that modify the dirstate parent.
105 '''
105 '''
106 return self._parentwriters > 0
106 return self._parentwriters > 0
107
107
108 @propertycache
108 @propertycache
109 def _map(self):
109 def _map(self):
110 """Return the dirstate contents (see documentation for dirstatemap)."""
110 """Return the dirstate contents (see documentation for dirstatemap)."""
111 self._map = self._mapcls(self._ui, self._opener, self._root)
111 self._map = self._mapcls(self._ui, self._opener, self._root)
112 return self._map
112 return self._map
113
113
114 @property
114 @property
115 def _sparsematcher(self):
115 def _sparsematcher(self):
116 """The matcher for the sparse checkout.
116 """The matcher for the sparse checkout.
117
117
118 The working directory may not include every file from a manifest. The
118 The working directory may not include every file from a manifest. The
119 matcher obtained by this property will match a path if it is to be
119 matcher obtained by this property will match a path if it is to be
120 included in the working directory.
120 included in the working directory.
121 """
121 """
122 # TODO there is potential to cache this property. For now, the matcher
122 # TODO there is potential to cache this property. For now, the matcher
123 # is resolved on every access. (But the called function does use a
123 # is resolved on every access. (But the called function does use a
124 # cache to keep the lookup fast.)
124 # cache to keep the lookup fast.)
125 return self._sparsematchfn()
125 return self._sparsematchfn()
126
126
127 @repocache('branch')
127 @repocache('branch')
128 def _branch(self):
128 def _branch(self):
129 try:
129 try:
130 return self._opener.read("branch").strip() or "default"
130 return self._opener.read("branch").strip() or "default"
131 except IOError as inst:
131 except IOError as inst:
132 if inst.errno != errno.ENOENT:
132 if inst.errno != errno.ENOENT:
133 raise
133 raise
134 return "default"
134 return "default"
135
135
136 @property
136 @property
137 def _pl(self):
137 def _pl(self):
138 return self._map.parents()
138 return self._map.parents()
139
139
140 def hasdir(self, d):
140 def hasdir(self, d):
141 return self._map.hastrackeddir(d)
141 return self._map.hastrackeddir(d)
142
142
143 @rootcache('.hgignore')
143 @rootcache('.hgignore')
144 def _ignore(self):
144 def _ignore(self):
145 files = self._ignorefiles()
145 files = self._ignorefiles()
146 if not files:
146 if not files:
147 return matchmod.never(self._root, '')
147 return matchmod.never(self._root, '')
148
148
149 pats = ['include:%s' % f for f in files]
149 pats = ['include:%s' % f for f in files]
150 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
150 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
151
151
152 @propertycache
152 @propertycache
153 def _slash(self):
153 def _slash(self):
154 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
154 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
155
155
156 @propertycache
156 @propertycache
157 def _checklink(self):
157 def _checklink(self):
158 return util.checklink(self._root)
158 return util.checklink(self._root)
159
159
160 @propertycache
160 @propertycache
161 def _checkexec(self):
161 def _checkexec(self):
162 return util.checkexec(self._root)
162 return util.checkexec(self._root)
163
163
164 @propertycache
164 @propertycache
165 def _checkcase(self):
165 def _checkcase(self):
166 return not util.fscasesensitive(self._join('.hg'))
166 return not util.fscasesensitive(self._join('.hg'))
167
167
168 def _join(self, f):
168 def _join(self, f):
169 # much faster than os.path.join()
169 # much faster than os.path.join()
170 # it's safe because f is always a relative path
170 # it's safe because f is always a relative path
171 return self._rootdir + f
171 return self._rootdir + f
172
172
173 def flagfunc(self, buildfallback):
173 def flagfunc(self, buildfallback):
174 if self._checklink and self._checkexec:
174 if self._checklink and self._checkexec:
175 def f(x):
175 def f(x):
176 try:
176 try:
177 st = os.lstat(self._join(x))
177 st = os.lstat(self._join(x))
178 if util.statislink(st):
178 if util.statislink(st):
179 return 'l'
179 return 'l'
180 if util.statisexec(st):
180 if util.statisexec(st):
181 return 'x'
181 return 'x'
182 except OSError:
182 except OSError:
183 pass
183 pass
184 return ''
184 return ''
185 return f
185 return f
186
186
187 fallback = buildfallback()
187 fallback = buildfallback()
188 if self._checklink:
188 if self._checklink:
189 def f(x):
189 def f(x):
190 if os.path.islink(self._join(x)):
190 if os.path.islink(self._join(x)):
191 return 'l'
191 return 'l'
192 if 'x' in fallback(x):
192 if 'x' in fallback(x):
193 return 'x'
193 return 'x'
194 return ''
194 return ''
195 return f
195 return f
196 if self._checkexec:
196 if self._checkexec:
197 def f(x):
197 def f(x):
198 if 'l' in fallback(x):
198 if 'l' in fallback(x):
199 return 'l'
199 return 'l'
200 if util.isexec(self._join(x)):
200 if util.isexec(self._join(x)):
201 return 'x'
201 return 'x'
202 return ''
202 return ''
203 return f
203 return f
204 else:
204 else:
205 return fallback
205 return fallback
206
206
207 @propertycache
207 @propertycache
208 def _cwd(self):
208 def _cwd(self):
209 # internal config: ui.forcecwd
209 # internal config: ui.forcecwd
210 forcecwd = self._ui.config('ui', 'forcecwd')
210 forcecwd = self._ui.config('ui', 'forcecwd')
211 if forcecwd:
211 if forcecwd:
212 return forcecwd
212 return forcecwd
213 return encoding.getcwd()
213 return encoding.getcwd()
214
214
215 def getcwd(self):
215 def getcwd(self):
216 '''Return the path from which a canonical path is calculated.
216 '''Return the path from which a canonical path is calculated.
217
217
218 This path should be used to resolve file patterns or to convert
218 This path should be used to resolve file patterns or to convert
219 canonical paths back to file paths for display. It shouldn't be
219 canonical paths back to file paths for display. It shouldn't be
220 used to get real file paths. Use vfs functions instead.
220 used to get real file paths. Use vfs functions instead.
221 '''
221 '''
222 cwd = self._cwd
222 cwd = self._cwd
223 if cwd == self._root:
223 if cwd == self._root:
224 return ''
224 return ''
225 # self._root ends with a path separator if self._root is '/' or 'C:\'
225 # self._root ends with a path separator if self._root is '/' or 'C:\'
226 rootsep = self._root
226 rootsep = self._root
227 if not util.endswithsep(rootsep):
227 if not util.endswithsep(rootsep):
228 rootsep += pycompat.ossep
228 rootsep += pycompat.ossep
229 if cwd.startswith(rootsep):
229 if cwd.startswith(rootsep):
230 return cwd[len(rootsep):]
230 return cwd[len(rootsep):]
231 else:
231 else:
232 # we're outside the repo. return an absolute path.
232 # we're outside the repo. return an absolute path.
233 return cwd
233 return cwd
234
234
235 def pathto(self, f, cwd=None):
235 def pathto(self, f, cwd=None):
236 if cwd is None:
236 if cwd is None:
237 cwd = self.getcwd()
237 cwd = self.getcwd()
238 path = util.pathto(self._root, cwd, f)
238 path = util.pathto(self._root, cwd, f)
239 if self._slash:
239 if self._slash:
240 return util.pconvert(path)
240 return util.pconvert(path)
241 return path
241 return path
242
242
243 def __getitem__(self, key):
243 def __getitem__(self, key):
244 '''Return the current state of key (a filename) in the dirstate.
244 '''Return the current state of key (a filename) in the dirstate.
245
245
246 States are:
246 States are:
247 n normal
247 n normal
248 m needs merging
248 m needs merging
249 r marked for removal
249 r marked for removal
250 a marked for addition
250 a marked for addition
251 ? not tracked
251 ? not tracked
252 '''
252 '''
253 return self._map.get(key, ("?",))[0]
253 return self._map.get(key, ("?",))[0]
254
254
255 def __contains__(self, key):
255 def __contains__(self, key):
256 return key in self._map
256 return key in self._map
257
257
258 def __iter__(self):
258 def __iter__(self):
259 return iter(sorted(self._map))
259 return iter(sorted(self._map))
260
260
261 def items(self):
261 def items(self):
262 return self._map.iteritems()
262 return self._map.iteritems()
263
263
264 iteritems = items
264 iteritems = items
265
265
266 def parents(self):
266 def parents(self):
267 return [self._validate(p) for p in self._pl]
267 return [self._validate(p) for p in self._pl]
268
268
269 def p1(self):
269 def p1(self):
270 return self._validate(self._pl[0])
270 return self._validate(self._pl[0])
271
271
272 def p2(self):
272 def p2(self):
273 return self._validate(self._pl[1])
273 return self._validate(self._pl[1])
274
274
275 def branch(self):
275 def branch(self):
276 return encoding.tolocal(self._branch)
276 return encoding.tolocal(self._branch)
277
277
278 def setparents(self, p1, p2=nullid):
278 def setparents(self, p1, p2=nullid):
279 """Set dirstate parents to p1 and p2.
279 """Set dirstate parents to p1 and p2.
280
280
281 When moving from two parents to one, 'm' merged entries a
281 When moving from two parents to one, 'm' merged entries a
282 adjusted to normal and previous copy records discarded and
282 adjusted to normal and previous copy records discarded and
283 returned by the call.
283 returned by the call.
284
284
285 See localrepo.setparents()
285 See localrepo.setparents()
286 """
286 """
287 if self._parentwriters == 0:
287 if self._parentwriters == 0:
288 raise ValueError("cannot set dirstate parent without "
288 raise ValueError("cannot set dirstate parent without "
289 "calling dirstate.beginparentchange")
289 "calling dirstate.beginparentchange")
290
290
291 self._dirty = True
291 self._dirty = True
292 oldp2 = self._pl[1]
292 oldp2 = self._pl[1]
293 if self._origpl is None:
293 if self._origpl is None:
294 self._origpl = self._pl
294 self._origpl = self._pl
295 self._map.setparents(p1, p2)
295 self._map.setparents(p1, p2)
296 copies = {}
296 copies = {}
297 if oldp2 != nullid and p2 == nullid:
297 if oldp2 != nullid and p2 == nullid:
298 candidatefiles = self._map.nonnormalset.union(
298 candidatefiles = self._map.nonnormalset.union(
299 self._map.otherparentset)
299 self._map.otherparentset)
300 for f in candidatefiles:
300 for f in candidatefiles:
301 s = self._map.get(f)
301 s = self._map.get(f)
302 if s is None:
302 if s is None:
303 continue
303 continue
304
304
305 # Discard 'm' markers when moving away from a merge state
305 # Discard 'm' markers when moving away from a merge state
306 if s[0] == 'm':
306 if s[0] == 'm':
307 source = self._map.copymap.get(f)
307 source = self._map.copymap.get(f)
308 if source:
308 if source:
309 copies[f] = source
309 copies[f] = source
310 self.normallookup(f)
310 self.normallookup(f)
311 # Also fix up otherparent markers
311 # Also fix up otherparent markers
312 elif s[0] == 'n' and s[2] == -2:
312 elif s[0] == 'n' and s[2] == -2:
313 source = self._map.copymap.get(f)
313 source = self._map.copymap.get(f)
314 if source:
314 if source:
315 copies[f] = source
315 copies[f] = source
316 self.add(f)
316 self.add(f)
317 return copies
317 return copies
318
318
319 def setbranch(self, branch):
319 def setbranch(self, branch):
320 self._branch = encoding.fromlocal(branch)
320 self.__class__._branch.set(self, encoding.fromlocal(branch))
321 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
321 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
322 try:
322 try:
323 f.write(self._branch + '\n')
323 f.write(self._branch + '\n')
324 f.close()
324 f.close()
325
325
326 # make sure filecache has the correct stat info for _branch after
326 # make sure filecache has the correct stat info for _branch after
327 # replacing the underlying file
327 # replacing the underlying file
328 ce = self._filecache['_branch']
328 ce = self._filecache['_branch']
329 if ce:
329 if ce:
330 ce.refresh()
330 ce.refresh()
331 except: # re-raises
331 except: # re-raises
332 f.discard()
332 f.discard()
333 raise
333 raise
334
334
335 def invalidate(self):
335 def invalidate(self):
336 '''Causes the next access to reread the dirstate.
336 '''Causes the next access to reread the dirstate.
337
337
338 This is different from localrepo.invalidatedirstate() because it always
338 This is different from localrepo.invalidatedirstate() because it always
339 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
339 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
340 check whether the dirstate has changed before rereading it.'''
340 check whether the dirstate has changed before rereading it.'''
341
341
342 for a in (r"_map", r"_branch", r"_ignore"):
342 for a in (r"_map", r"_branch", r"_ignore"):
343 if a in self.__dict__:
343 if a in self.__dict__:
344 delattr(self, a)
344 delattr(self, a)
345 self._lastnormaltime = 0
345 self._lastnormaltime = 0
346 self._dirty = False
346 self._dirty = False
347 self._updatedfiles.clear()
347 self._updatedfiles.clear()
348 self._parentwriters = 0
348 self._parentwriters = 0
349 self._origpl = None
349 self._origpl = None
350
350
351 def copy(self, source, dest):
351 def copy(self, source, dest):
352 """Mark dest as a copy of source. Unmark dest if source is None."""
352 """Mark dest as a copy of source. Unmark dest if source is None."""
353 if source == dest:
353 if source == dest:
354 return
354 return
355 self._dirty = True
355 self._dirty = True
356 if source is not None:
356 if source is not None:
357 self._map.copymap[dest] = source
357 self._map.copymap[dest] = source
358 self._updatedfiles.add(source)
358 self._updatedfiles.add(source)
359 self._updatedfiles.add(dest)
359 self._updatedfiles.add(dest)
360 elif self._map.copymap.pop(dest, None):
360 elif self._map.copymap.pop(dest, None):
361 self._updatedfiles.add(dest)
361 self._updatedfiles.add(dest)
362
362
363 def copied(self, file):
363 def copied(self, file):
364 return self._map.copymap.get(file, None)
364 return self._map.copymap.get(file, None)
365
365
366 def copies(self):
366 def copies(self):
367 return self._map.copymap
367 return self._map.copymap
368
368
369 def _addpath(self, f, state, mode, size, mtime):
369 def _addpath(self, f, state, mode, size, mtime):
370 oldstate = self[f]
370 oldstate = self[f]
371 if state == 'a' or oldstate == 'r':
371 if state == 'a' or oldstate == 'r':
372 scmutil.checkfilename(f)
372 scmutil.checkfilename(f)
373 if self._map.hastrackeddir(f):
373 if self._map.hastrackeddir(f):
374 raise error.Abort(_('directory %r already in dirstate') %
374 raise error.Abort(_('directory %r already in dirstate') %
375 pycompat.bytestr(f))
375 pycompat.bytestr(f))
376 # shadows
376 # shadows
377 for d in util.finddirs(f):
377 for d in util.finddirs(f):
378 if self._map.hastrackeddir(d):
378 if self._map.hastrackeddir(d):
379 break
379 break
380 entry = self._map.get(d)
380 entry = self._map.get(d)
381 if entry is not None and entry[0] != 'r':
381 if entry is not None and entry[0] != 'r':
382 raise error.Abort(
382 raise error.Abort(
383 _('file %r in dirstate clashes with %r') %
383 _('file %r in dirstate clashes with %r') %
384 (pycompat.bytestr(d), pycompat.bytestr(f)))
384 (pycompat.bytestr(d), pycompat.bytestr(f)))
385 self._dirty = True
385 self._dirty = True
386 self._updatedfiles.add(f)
386 self._updatedfiles.add(f)
387 self._map.addfile(f, oldstate, state, mode, size, mtime)
387 self._map.addfile(f, oldstate, state, mode, size, mtime)
388
388
389 def normal(self, f):
389 def normal(self, f):
390 '''Mark a file normal and clean.'''
390 '''Mark a file normal and clean.'''
391 s = os.lstat(self._join(f))
391 s = os.lstat(self._join(f))
392 mtime = s[stat.ST_MTIME]
392 mtime = s[stat.ST_MTIME]
393 self._addpath(f, 'n', s.st_mode,
393 self._addpath(f, 'n', s.st_mode,
394 s.st_size & _rangemask, mtime & _rangemask)
394 s.st_size & _rangemask, mtime & _rangemask)
395 self._map.copymap.pop(f, None)
395 self._map.copymap.pop(f, None)
396 if f in self._map.nonnormalset:
396 if f in self._map.nonnormalset:
397 self._map.nonnormalset.remove(f)
397 self._map.nonnormalset.remove(f)
398 if mtime > self._lastnormaltime:
398 if mtime > self._lastnormaltime:
399 # Remember the most recent modification timeslot for status(),
399 # Remember the most recent modification timeslot for status(),
400 # to make sure we won't miss future size-preserving file content
400 # to make sure we won't miss future size-preserving file content
401 # modifications that happen within the same timeslot.
401 # modifications that happen within the same timeslot.
402 self._lastnormaltime = mtime
402 self._lastnormaltime = mtime
403
403
404 def normallookup(self, f):
404 def normallookup(self, f):
405 '''Mark a file normal, but possibly dirty.'''
405 '''Mark a file normal, but possibly dirty.'''
406 if self._pl[1] != nullid:
406 if self._pl[1] != nullid:
407 # if there is a merge going on and the file was either
407 # if there is a merge going on and the file was either
408 # in state 'm' (-1) or coming from other parent (-2) before
408 # in state 'm' (-1) or coming from other parent (-2) before
409 # being removed, restore that state.
409 # being removed, restore that state.
410 entry = self._map.get(f)
410 entry = self._map.get(f)
411 if entry is not None:
411 if entry is not None:
412 if entry[0] == 'r' and entry[2] in (-1, -2):
412 if entry[0] == 'r' and entry[2] in (-1, -2):
413 source = self._map.copymap.get(f)
413 source = self._map.copymap.get(f)
414 if entry[2] == -1:
414 if entry[2] == -1:
415 self.merge(f)
415 self.merge(f)
416 elif entry[2] == -2:
416 elif entry[2] == -2:
417 self.otherparent(f)
417 self.otherparent(f)
418 if source:
418 if source:
419 self.copy(source, f)
419 self.copy(source, f)
420 return
420 return
421 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
421 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
422 return
422 return
423 self._addpath(f, 'n', 0, -1, -1)
423 self._addpath(f, 'n', 0, -1, -1)
424 self._map.copymap.pop(f, None)
424 self._map.copymap.pop(f, None)
425
425
426 def otherparent(self, f):
426 def otherparent(self, f):
427 '''Mark as coming from the other parent, always dirty.'''
427 '''Mark as coming from the other parent, always dirty.'''
428 if self._pl[1] == nullid:
428 if self._pl[1] == nullid:
429 raise error.Abort(_("setting %r to other parent "
429 raise error.Abort(_("setting %r to other parent "
430 "only allowed in merges") % f)
430 "only allowed in merges") % f)
431 if f in self and self[f] == 'n':
431 if f in self and self[f] == 'n':
432 # merge-like
432 # merge-like
433 self._addpath(f, 'm', 0, -2, -1)
433 self._addpath(f, 'm', 0, -2, -1)
434 else:
434 else:
435 # add-like
435 # add-like
436 self._addpath(f, 'n', 0, -2, -1)
436 self._addpath(f, 'n', 0, -2, -1)
437 self._map.copymap.pop(f, None)
437 self._map.copymap.pop(f, None)
438
438
439 def add(self, f):
439 def add(self, f):
440 '''Mark a file added.'''
440 '''Mark a file added.'''
441 self._addpath(f, 'a', 0, -1, -1)
441 self._addpath(f, 'a', 0, -1, -1)
442 self._map.copymap.pop(f, None)
442 self._map.copymap.pop(f, None)
443
443
444 def remove(self, f):
444 def remove(self, f):
445 '''Mark a file removed.'''
445 '''Mark a file removed.'''
446 self._dirty = True
446 self._dirty = True
447 oldstate = self[f]
447 oldstate = self[f]
448 size = 0
448 size = 0
449 if self._pl[1] != nullid:
449 if self._pl[1] != nullid:
450 entry = self._map.get(f)
450 entry = self._map.get(f)
451 if entry is not None:
451 if entry is not None:
452 # backup the previous state
452 # backup the previous state
453 if entry[0] == 'm': # merge
453 if entry[0] == 'm': # merge
454 size = -1
454 size = -1
455 elif entry[0] == 'n' and entry[2] == -2: # other parent
455 elif entry[0] == 'n' and entry[2] == -2: # other parent
456 size = -2
456 size = -2
457 self._map.otherparentset.add(f)
457 self._map.otherparentset.add(f)
458 self._updatedfiles.add(f)
458 self._updatedfiles.add(f)
459 self._map.removefile(f, oldstate, size)
459 self._map.removefile(f, oldstate, size)
460 if size == 0:
460 if size == 0:
461 self._map.copymap.pop(f, None)
461 self._map.copymap.pop(f, None)
462
462
463 def merge(self, f):
463 def merge(self, f):
464 '''Mark a file merged.'''
464 '''Mark a file merged.'''
465 if self._pl[1] == nullid:
465 if self._pl[1] == nullid:
466 return self.normallookup(f)
466 return self.normallookup(f)
467 return self.otherparent(f)
467 return self.otherparent(f)
468
468
469 def drop(self, f):
469 def drop(self, f):
470 '''Drop a file from the dirstate'''
470 '''Drop a file from the dirstate'''
471 oldstate = self[f]
471 oldstate = self[f]
472 if self._map.dropfile(f, oldstate):
472 if self._map.dropfile(f, oldstate):
473 self._dirty = True
473 self._dirty = True
474 self._updatedfiles.add(f)
474 self._updatedfiles.add(f)
475 self._map.copymap.pop(f, None)
475 self._map.copymap.pop(f, None)
476
476
477 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
477 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
478 if exists is None:
478 if exists is None:
479 exists = os.path.lexists(os.path.join(self._root, path))
479 exists = os.path.lexists(os.path.join(self._root, path))
480 if not exists:
480 if not exists:
481 # Maybe a path component exists
481 # Maybe a path component exists
482 if not ignoremissing and '/' in path:
482 if not ignoremissing and '/' in path:
483 d, f = path.rsplit('/', 1)
483 d, f = path.rsplit('/', 1)
484 d = self._normalize(d, False, ignoremissing, None)
484 d = self._normalize(d, False, ignoremissing, None)
485 folded = d + "/" + f
485 folded = d + "/" + f
486 else:
486 else:
487 # No path components, preserve original case
487 # No path components, preserve original case
488 folded = path
488 folded = path
489 else:
489 else:
490 # recursively normalize leading directory components
490 # recursively normalize leading directory components
491 # against dirstate
491 # against dirstate
492 if '/' in normed:
492 if '/' in normed:
493 d, f = normed.rsplit('/', 1)
493 d, f = normed.rsplit('/', 1)
494 d = self._normalize(d, False, ignoremissing, True)
494 d = self._normalize(d, False, ignoremissing, True)
495 r = self._root + "/" + d
495 r = self._root + "/" + d
496 folded = d + "/" + util.fspath(f, r)
496 folded = d + "/" + util.fspath(f, r)
497 else:
497 else:
498 folded = util.fspath(normed, self._root)
498 folded = util.fspath(normed, self._root)
499 storemap[normed] = folded
499 storemap[normed] = folded
500
500
501 return folded
501 return folded
502
502
503 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
503 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
504 normed = util.normcase(path)
504 normed = util.normcase(path)
505 folded = self._map.filefoldmap.get(normed, None)
505 folded = self._map.filefoldmap.get(normed, None)
506 if folded is None:
506 if folded is None:
507 if isknown:
507 if isknown:
508 folded = path
508 folded = path
509 else:
509 else:
510 folded = self._discoverpath(path, normed, ignoremissing, exists,
510 folded = self._discoverpath(path, normed, ignoremissing, exists,
511 self._map.filefoldmap)
511 self._map.filefoldmap)
512 return folded
512 return folded
513
513
514 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
514 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
515 normed = util.normcase(path)
515 normed = util.normcase(path)
516 folded = self._map.filefoldmap.get(normed, None)
516 folded = self._map.filefoldmap.get(normed, None)
517 if folded is None:
517 if folded is None:
518 folded = self._map.dirfoldmap.get(normed, None)
518 folded = self._map.dirfoldmap.get(normed, None)
519 if folded is None:
519 if folded is None:
520 if isknown:
520 if isknown:
521 folded = path
521 folded = path
522 else:
522 else:
523 # store discovered result in dirfoldmap so that future
523 # store discovered result in dirfoldmap so that future
524 # normalizefile calls don't start matching directories
524 # normalizefile calls don't start matching directories
525 folded = self._discoverpath(path, normed, ignoremissing, exists,
525 folded = self._discoverpath(path, normed, ignoremissing, exists,
526 self._map.dirfoldmap)
526 self._map.dirfoldmap)
527 return folded
527 return folded
528
528
529 def normalize(self, path, isknown=False, ignoremissing=False):
529 def normalize(self, path, isknown=False, ignoremissing=False):
530 '''
530 '''
531 normalize the case of a pathname when on a casefolding filesystem
531 normalize the case of a pathname when on a casefolding filesystem
532
532
533 isknown specifies whether the filename came from walking the
533 isknown specifies whether the filename came from walking the
534 disk, to avoid extra filesystem access.
534 disk, to avoid extra filesystem access.
535
535
536 If ignoremissing is True, missing path are returned
536 If ignoremissing is True, missing path are returned
537 unchanged. Otherwise, we try harder to normalize possibly
537 unchanged. Otherwise, we try harder to normalize possibly
538 existing path components.
538 existing path components.
539
539
540 The normalized case is determined based on the following precedence:
540 The normalized case is determined based on the following precedence:
541
541
542 - version of name already stored in the dirstate
542 - version of name already stored in the dirstate
543 - version of name stored on disk
543 - version of name stored on disk
544 - version provided via command arguments
544 - version provided via command arguments
545 '''
545 '''
546
546
547 if self._checkcase:
547 if self._checkcase:
548 return self._normalize(path, isknown, ignoremissing)
548 return self._normalize(path, isknown, ignoremissing)
549 return path
549 return path
550
550
551 def clear(self):
551 def clear(self):
552 self._map.clear()
552 self._map.clear()
553 self._lastnormaltime = 0
553 self._lastnormaltime = 0
554 self._updatedfiles.clear()
554 self._updatedfiles.clear()
555 self._dirty = True
555 self._dirty = True
556
556
557 def rebuild(self, parent, allfiles, changedfiles=None):
557 def rebuild(self, parent, allfiles, changedfiles=None):
558 if changedfiles is None:
558 if changedfiles is None:
559 # Rebuild entire dirstate
559 # Rebuild entire dirstate
560 changedfiles = allfiles
560 changedfiles = allfiles
561 lastnormaltime = self._lastnormaltime
561 lastnormaltime = self._lastnormaltime
562 self.clear()
562 self.clear()
563 self._lastnormaltime = lastnormaltime
563 self._lastnormaltime = lastnormaltime
564
564
565 if self._origpl is None:
565 if self._origpl is None:
566 self._origpl = self._pl
566 self._origpl = self._pl
567 self._map.setparents(parent, nullid)
567 self._map.setparents(parent, nullid)
568 for f in changedfiles:
568 for f in changedfiles:
569 if f in allfiles:
569 if f in allfiles:
570 self.normallookup(f)
570 self.normallookup(f)
571 else:
571 else:
572 self.drop(f)
572 self.drop(f)
573
573
574 self._dirty = True
574 self._dirty = True
575
575
576 def identity(self):
576 def identity(self):
577 '''Return identity of dirstate itself to detect changing in storage
577 '''Return identity of dirstate itself to detect changing in storage
578
578
579 If identity of previous dirstate is equal to this, writing
579 If identity of previous dirstate is equal to this, writing
580 changes based on the former dirstate out can keep consistency.
580 changes based on the former dirstate out can keep consistency.
581 '''
581 '''
582 return self._map.identity
582 return self._map.identity
583
583
584 def write(self, tr):
584 def write(self, tr):
585 if not self._dirty:
585 if not self._dirty:
586 return
586 return
587
587
588 filename = self._filename
588 filename = self._filename
589 if tr:
589 if tr:
590 # 'dirstate.write()' is not only for writing in-memory
590 # 'dirstate.write()' is not only for writing in-memory
591 # changes out, but also for dropping ambiguous timestamp.
591 # changes out, but also for dropping ambiguous timestamp.
592 # delayed writing re-raise "ambiguous timestamp issue".
592 # delayed writing re-raise "ambiguous timestamp issue".
593 # See also the wiki page below for detail:
593 # See also the wiki page below for detail:
594 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
594 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
595
595
596 # emulate dropping timestamp in 'parsers.pack_dirstate'
596 # emulate dropping timestamp in 'parsers.pack_dirstate'
597 now = _getfsnow(self._opener)
597 now = _getfsnow(self._opener)
598 self._map.clearambiguoustimes(self._updatedfiles, now)
598 self._map.clearambiguoustimes(self._updatedfiles, now)
599
599
600 # emulate that all 'dirstate.normal' results are written out
600 # emulate that all 'dirstate.normal' results are written out
601 self._lastnormaltime = 0
601 self._lastnormaltime = 0
602 self._updatedfiles.clear()
602 self._updatedfiles.clear()
603
603
604 # delay writing in-memory changes out
604 # delay writing in-memory changes out
605 tr.addfilegenerator('dirstate', (self._filename,),
605 tr.addfilegenerator('dirstate', (self._filename,),
606 self._writedirstate, location='plain')
606 self._writedirstate, location='plain')
607 return
607 return
608
608
609 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
609 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
610 self._writedirstate(st)
610 self._writedirstate(st)
611
611
612 def addparentchangecallback(self, category, callback):
612 def addparentchangecallback(self, category, callback):
613 """add a callback to be called when the wd parents are changed
613 """add a callback to be called when the wd parents are changed
614
614
615 Callback will be called with the following arguments:
615 Callback will be called with the following arguments:
616 dirstate, (oldp1, oldp2), (newp1, newp2)
616 dirstate, (oldp1, oldp2), (newp1, newp2)
617
617
618 Category is a unique identifier to allow overwriting an old callback
618 Category is a unique identifier to allow overwriting an old callback
619 with a newer callback.
619 with a newer callback.
620 """
620 """
621 self._plchangecallbacks[category] = callback
621 self._plchangecallbacks[category] = callback
622
622
623 def _writedirstate(self, st):
623 def _writedirstate(self, st):
624 # notify callbacks about parents change
624 # notify callbacks about parents change
625 if self._origpl is not None and self._origpl != self._pl:
625 if self._origpl is not None and self._origpl != self._pl:
626 for c, callback in sorted(self._plchangecallbacks.iteritems()):
626 for c, callback in sorted(self._plchangecallbacks.iteritems()):
627 callback(self, self._origpl, self._pl)
627 callback(self, self._origpl, self._pl)
628 self._origpl = None
628 self._origpl = None
629 # use the modification time of the newly created temporary file as the
629 # use the modification time of the newly created temporary file as the
630 # filesystem's notion of 'now'
630 # filesystem's notion of 'now'
631 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
631 now = util.fstat(st)[stat.ST_MTIME] & _rangemask
632
632
633 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
633 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
634 # timestamp of each entries in dirstate, because of 'now > mtime'
634 # timestamp of each entries in dirstate, because of 'now > mtime'
635 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
635 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
636 if delaywrite > 0:
636 if delaywrite > 0:
637 # do we have any files to delay for?
637 # do we have any files to delay for?
638 for f, e in self._map.iteritems():
638 for f, e in self._map.iteritems():
639 if e[0] == 'n' and e[3] == now:
639 if e[0] == 'n' and e[3] == now:
640 import time # to avoid useless import
640 import time # to avoid useless import
641 # rather than sleep n seconds, sleep until the next
641 # rather than sleep n seconds, sleep until the next
642 # multiple of n seconds
642 # multiple of n seconds
643 clock = time.time()
643 clock = time.time()
644 start = int(clock) - (int(clock) % delaywrite)
644 start = int(clock) - (int(clock) % delaywrite)
645 end = start + delaywrite
645 end = start + delaywrite
646 time.sleep(end - clock)
646 time.sleep(end - clock)
647 now = end # trust our estimate that the end is near now
647 now = end # trust our estimate that the end is near now
648 break
648 break
649
649
650 self._map.write(st, now)
650 self._map.write(st, now)
651 self._lastnormaltime = 0
651 self._lastnormaltime = 0
652 self._dirty = False
652 self._dirty = False
653
653
654 def _dirignore(self, f):
654 def _dirignore(self, f):
655 if f == '.':
655 if f == '.':
656 return False
656 return False
657 if self._ignore(f):
657 if self._ignore(f):
658 return True
658 return True
659 for p in util.finddirs(f):
659 for p in util.finddirs(f):
660 if self._ignore(p):
660 if self._ignore(p):
661 return True
661 return True
662 return False
662 return False
663
663
664 def _ignorefiles(self):
664 def _ignorefiles(self):
665 files = []
665 files = []
666 if os.path.exists(self._join('.hgignore')):
666 if os.path.exists(self._join('.hgignore')):
667 files.append(self._join('.hgignore'))
667 files.append(self._join('.hgignore'))
668 for name, path in self._ui.configitems("ui"):
668 for name, path in self._ui.configitems("ui"):
669 if name == 'ignore' or name.startswith('ignore.'):
669 if name == 'ignore' or name.startswith('ignore.'):
670 # we need to use os.path.join here rather than self._join
670 # we need to use os.path.join here rather than self._join
671 # because path is arbitrary and user-specified
671 # because path is arbitrary and user-specified
672 files.append(os.path.join(self._rootdir, util.expandpath(path)))
672 files.append(os.path.join(self._rootdir, util.expandpath(path)))
673 return files
673 return files
674
674
675 def _ignorefileandline(self, f):
675 def _ignorefileandline(self, f):
676 files = collections.deque(self._ignorefiles())
676 files = collections.deque(self._ignorefiles())
677 visited = set()
677 visited = set()
678 while files:
678 while files:
679 i = files.popleft()
679 i = files.popleft()
680 patterns = matchmod.readpatternfile(i, self._ui.warn,
680 patterns = matchmod.readpatternfile(i, self._ui.warn,
681 sourceinfo=True)
681 sourceinfo=True)
682 for pattern, lineno, line in patterns:
682 for pattern, lineno, line in patterns:
683 kind, p = matchmod._patsplit(pattern, 'glob')
683 kind, p = matchmod._patsplit(pattern, 'glob')
684 if kind == "subinclude":
684 if kind == "subinclude":
685 if p not in visited:
685 if p not in visited:
686 files.append(p)
686 files.append(p)
687 continue
687 continue
688 m = matchmod.match(self._root, '', [], [pattern],
688 m = matchmod.match(self._root, '', [], [pattern],
689 warn=self._ui.warn)
689 warn=self._ui.warn)
690 if m(f):
690 if m(f):
691 return (i, lineno, line)
691 return (i, lineno, line)
692 visited.add(i)
692 visited.add(i)
693 return (None, -1, "")
693 return (None, -1, "")
694
694
695 def _walkexplicit(self, match, subrepos):
695 def _walkexplicit(self, match, subrepos):
696 '''Get stat data about the files explicitly specified by match.
696 '''Get stat data about the files explicitly specified by match.
697
697
698 Return a triple (results, dirsfound, dirsnotfound).
698 Return a triple (results, dirsfound, dirsnotfound).
699 - results is a mapping from filename to stat result. It also contains
699 - results is a mapping from filename to stat result. It also contains
700 listings mapping subrepos and .hg to None.
700 listings mapping subrepos and .hg to None.
701 - dirsfound is a list of files found to be directories.
701 - dirsfound is a list of files found to be directories.
702 - dirsnotfound is a list of files that the dirstate thinks are
702 - dirsnotfound is a list of files that the dirstate thinks are
703 directories and that were not found.'''
703 directories and that were not found.'''
704
704
705 def badtype(mode):
705 def badtype(mode):
706 kind = _('unknown')
706 kind = _('unknown')
707 if stat.S_ISCHR(mode):
707 if stat.S_ISCHR(mode):
708 kind = _('character device')
708 kind = _('character device')
709 elif stat.S_ISBLK(mode):
709 elif stat.S_ISBLK(mode):
710 kind = _('block device')
710 kind = _('block device')
711 elif stat.S_ISFIFO(mode):
711 elif stat.S_ISFIFO(mode):
712 kind = _('fifo')
712 kind = _('fifo')
713 elif stat.S_ISSOCK(mode):
713 elif stat.S_ISSOCK(mode):
714 kind = _('socket')
714 kind = _('socket')
715 elif stat.S_ISDIR(mode):
715 elif stat.S_ISDIR(mode):
716 kind = _('directory')
716 kind = _('directory')
717 return _('unsupported file type (type is %s)') % kind
717 return _('unsupported file type (type is %s)') % kind
718
718
719 matchedir = match.explicitdir
719 matchedir = match.explicitdir
720 badfn = match.bad
720 badfn = match.bad
721 dmap = self._map
721 dmap = self._map
722 lstat = os.lstat
722 lstat = os.lstat
723 getkind = stat.S_IFMT
723 getkind = stat.S_IFMT
724 dirkind = stat.S_IFDIR
724 dirkind = stat.S_IFDIR
725 regkind = stat.S_IFREG
725 regkind = stat.S_IFREG
726 lnkkind = stat.S_IFLNK
726 lnkkind = stat.S_IFLNK
727 join = self._join
727 join = self._join
728 dirsfound = []
728 dirsfound = []
729 foundadd = dirsfound.append
729 foundadd = dirsfound.append
730 dirsnotfound = []
730 dirsnotfound = []
731 notfoundadd = dirsnotfound.append
731 notfoundadd = dirsnotfound.append
732
732
733 if not match.isexact() and self._checkcase:
733 if not match.isexact() and self._checkcase:
734 normalize = self._normalize
734 normalize = self._normalize
735 else:
735 else:
736 normalize = None
736 normalize = None
737
737
738 files = sorted(match.files())
738 files = sorted(match.files())
739 subrepos.sort()
739 subrepos.sort()
740 i, j = 0, 0
740 i, j = 0, 0
741 while i < len(files) and j < len(subrepos):
741 while i < len(files) and j < len(subrepos):
742 subpath = subrepos[j] + "/"
742 subpath = subrepos[j] + "/"
743 if files[i] < subpath:
743 if files[i] < subpath:
744 i += 1
744 i += 1
745 continue
745 continue
746 while i < len(files) and files[i].startswith(subpath):
746 while i < len(files) and files[i].startswith(subpath):
747 del files[i]
747 del files[i]
748 j += 1
748 j += 1
749
749
750 if not files or '.' in files:
750 if not files or '.' in files:
751 files = ['.']
751 files = ['.']
752 results = dict.fromkeys(subrepos)
752 results = dict.fromkeys(subrepos)
753 results['.hg'] = None
753 results['.hg'] = None
754
754
755 for ff in files:
755 for ff in files:
756 # constructing the foldmap is expensive, so don't do it for the
756 # constructing the foldmap is expensive, so don't do it for the
757 # common case where files is ['.']
757 # common case where files is ['.']
758 if normalize and ff != '.':
758 if normalize and ff != '.':
759 nf = normalize(ff, False, True)
759 nf = normalize(ff, False, True)
760 else:
760 else:
761 nf = ff
761 nf = ff
762 if nf in results:
762 if nf in results:
763 continue
763 continue
764
764
765 try:
765 try:
766 st = lstat(join(nf))
766 st = lstat(join(nf))
767 kind = getkind(st.st_mode)
767 kind = getkind(st.st_mode)
768 if kind == dirkind:
768 if kind == dirkind:
769 if nf in dmap:
769 if nf in dmap:
770 # file replaced by dir on disk but still in dirstate
770 # file replaced by dir on disk but still in dirstate
771 results[nf] = None
771 results[nf] = None
772 if matchedir:
772 if matchedir:
773 matchedir(nf)
773 matchedir(nf)
774 foundadd((nf, ff))
774 foundadd((nf, ff))
775 elif kind == regkind or kind == lnkkind:
775 elif kind == regkind or kind == lnkkind:
776 results[nf] = st
776 results[nf] = st
777 else:
777 else:
778 badfn(ff, badtype(kind))
778 badfn(ff, badtype(kind))
779 if nf in dmap:
779 if nf in dmap:
780 results[nf] = None
780 results[nf] = None
781 except OSError as inst: # nf not found on disk - it is dirstate only
781 except OSError as inst: # nf not found on disk - it is dirstate only
782 if nf in dmap: # does it exactly match a missing file?
782 if nf in dmap: # does it exactly match a missing file?
783 results[nf] = None
783 results[nf] = None
784 else: # does it match a missing directory?
784 else: # does it match a missing directory?
785 if self._map.hasdir(nf):
785 if self._map.hasdir(nf):
786 if matchedir:
786 if matchedir:
787 matchedir(nf)
787 matchedir(nf)
788 notfoundadd(nf)
788 notfoundadd(nf)
789 else:
789 else:
790 badfn(ff, encoding.strtolocal(inst.strerror))
790 badfn(ff, encoding.strtolocal(inst.strerror))
791
791
792 # match.files() may contain explicitly-specified paths that shouldn't
792 # match.files() may contain explicitly-specified paths that shouldn't
793 # be taken; drop them from the list of files found. dirsfound/notfound
793 # be taken; drop them from the list of files found. dirsfound/notfound
794 # aren't filtered here because they will be tested later.
794 # aren't filtered here because they will be tested later.
795 if match.anypats():
795 if match.anypats():
796 for f in list(results):
796 for f in list(results):
797 if f == '.hg' or f in subrepos:
797 if f == '.hg' or f in subrepos:
798 # keep sentinel to disable further out-of-repo walks
798 # keep sentinel to disable further out-of-repo walks
799 continue
799 continue
800 if not match(f):
800 if not match(f):
801 del results[f]
801 del results[f]
802
802
803 # Case insensitive filesystems cannot rely on lstat() failing to detect
803 # Case insensitive filesystems cannot rely on lstat() failing to detect
804 # a case-only rename. Prune the stat object for any file that does not
804 # a case-only rename. Prune the stat object for any file that does not
805 # match the case in the filesystem, if there are multiple files that
805 # match the case in the filesystem, if there are multiple files that
806 # normalize to the same path.
806 # normalize to the same path.
807 if match.isexact() and self._checkcase:
807 if match.isexact() and self._checkcase:
808 normed = {}
808 normed = {}
809
809
810 for f, st in results.iteritems():
810 for f, st in results.iteritems():
811 if st is None:
811 if st is None:
812 continue
812 continue
813
813
814 nc = util.normcase(f)
814 nc = util.normcase(f)
815 paths = normed.get(nc)
815 paths = normed.get(nc)
816
816
817 if paths is None:
817 if paths is None:
818 paths = set()
818 paths = set()
819 normed[nc] = paths
819 normed[nc] = paths
820
820
821 paths.add(f)
821 paths.add(f)
822
822
823 for norm, paths in normed.iteritems():
823 for norm, paths in normed.iteritems():
824 if len(paths) > 1:
824 if len(paths) > 1:
825 for path in paths:
825 for path in paths:
826 folded = self._discoverpath(path, norm, True, None,
826 folded = self._discoverpath(path, norm, True, None,
827 self._map.dirfoldmap)
827 self._map.dirfoldmap)
828 if path != folded:
828 if path != folded:
829 results[path] = None
829 results[path] = None
830
830
831 return results, dirsfound, dirsnotfound
831 return results, dirsfound, dirsnotfound
832
832
833 def walk(self, match, subrepos, unknown, ignored, full=True):
833 def walk(self, match, subrepos, unknown, ignored, full=True):
834 '''
834 '''
835 Walk recursively through the directory tree, finding all files
835 Walk recursively through the directory tree, finding all files
836 matched by match.
836 matched by match.
837
837
838 If full is False, maybe skip some known-clean files.
838 If full is False, maybe skip some known-clean files.
839
839
840 Return a dict mapping filename to stat-like object (either
840 Return a dict mapping filename to stat-like object (either
841 mercurial.osutil.stat instance or return value of os.stat()).
841 mercurial.osutil.stat instance or return value of os.stat()).
842
842
843 '''
843 '''
844 # full is a flag that extensions that hook into walk can use -- this
844 # full is a flag that extensions that hook into walk can use -- this
845 # implementation doesn't use it at all. This satisfies the contract
845 # implementation doesn't use it at all. This satisfies the contract
846 # because we only guarantee a "maybe".
846 # because we only guarantee a "maybe".
847
847
848 if ignored:
848 if ignored:
849 ignore = util.never
849 ignore = util.never
850 dirignore = util.never
850 dirignore = util.never
851 elif unknown:
851 elif unknown:
852 ignore = self._ignore
852 ignore = self._ignore
853 dirignore = self._dirignore
853 dirignore = self._dirignore
854 else:
854 else:
855 # if not unknown and not ignored, drop dir recursion and step 2
855 # if not unknown and not ignored, drop dir recursion and step 2
856 ignore = util.always
856 ignore = util.always
857 dirignore = util.always
857 dirignore = util.always
858
858
859 matchfn = match.matchfn
859 matchfn = match.matchfn
860 matchalways = match.always()
860 matchalways = match.always()
861 matchtdir = match.traversedir
861 matchtdir = match.traversedir
862 dmap = self._map
862 dmap = self._map
863 listdir = util.listdir
863 listdir = util.listdir
864 lstat = os.lstat
864 lstat = os.lstat
865 dirkind = stat.S_IFDIR
865 dirkind = stat.S_IFDIR
866 regkind = stat.S_IFREG
866 regkind = stat.S_IFREG
867 lnkkind = stat.S_IFLNK
867 lnkkind = stat.S_IFLNK
868 join = self._join
868 join = self._join
869
869
870 exact = skipstep3 = False
870 exact = skipstep3 = False
871 if match.isexact(): # match.exact
871 if match.isexact(): # match.exact
872 exact = True
872 exact = True
873 dirignore = util.always # skip step 2
873 dirignore = util.always # skip step 2
874 elif match.prefix(): # match.match, no patterns
874 elif match.prefix(): # match.match, no patterns
875 skipstep3 = True
875 skipstep3 = True
876
876
877 if not exact and self._checkcase:
877 if not exact and self._checkcase:
878 normalize = self._normalize
878 normalize = self._normalize
879 normalizefile = self._normalizefile
879 normalizefile = self._normalizefile
880 skipstep3 = False
880 skipstep3 = False
881 else:
881 else:
882 normalize = self._normalize
882 normalize = self._normalize
883 normalizefile = None
883 normalizefile = None
884
884
885 # step 1: find all explicit files
885 # step 1: find all explicit files
886 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
886 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
887
887
888 skipstep3 = skipstep3 and not (work or dirsnotfound)
888 skipstep3 = skipstep3 and not (work or dirsnotfound)
889 work = [d for d in work if not dirignore(d[0])]
889 work = [d for d in work if not dirignore(d[0])]
890
890
891 # step 2: visit subdirectories
891 # step 2: visit subdirectories
892 def traverse(work, alreadynormed):
892 def traverse(work, alreadynormed):
893 wadd = work.append
893 wadd = work.append
894 while work:
894 while work:
895 nd = work.pop()
895 nd = work.pop()
896 visitentries = match.visitchildrenset(nd)
896 visitentries = match.visitchildrenset(nd)
897 if not visitentries:
897 if not visitentries:
898 continue
898 continue
899 if visitentries == 'this' or visitentries == 'all':
899 if visitentries == 'this' or visitentries == 'all':
900 visitentries = None
900 visitentries = None
901 skip = None
901 skip = None
902 if nd == '.':
902 if nd == '.':
903 nd = ''
903 nd = ''
904 else:
904 else:
905 skip = '.hg'
905 skip = '.hg'
906 try:
906 try:
907 entries = listdir(join(nd), stat=True, skip=skip)
907 entries = listdir(join(nd), stat=True, skip=skip)
908 except OSError as inst:
908 except OSError as inst:
909 if inst.errno in (errno.EACCES, errno.ENOENT):
909 if inst.errno in (errno.EACCES, errno.ENOENT):
910 match.bad(self.pathto(nd),
910 match.bad(self.pathto(nd),
911 encoding.strtolocal(inst.strerror))
911 encoding.strtolocal(inst.strerror))
912 continue
912 continue
913 raise
913 raise
914 for f, kind, st in entries:
914 for f, kind, st in entries:
915 # Some matchers may return files in the visitentries set,
915 # Some matchers may return files in the visitentries set,
916 # instead of 'this', if the matcher explicitly mentions them
916 # instead of 'this', if the matcher explicitly mentions them
917 # and is not an exactmatcher. This is acceptable; we do not
917 # and is not an exactmatcher. This is acceptable; we do not
918 # make any hard assumptions about file-or-directory below
918 # make any hard assumptions about file-or-directory below
919 # based on the presence of `f` in visitentries. If
919 # based on the presence of `f` in visitentries. If
920 # visitchildrenset returned a set, we can always skip the
920 # visitchildrenset returned a set, we can always skip the
921 # entries *not* in the set it provided regardless of whether
921 # entries *not* in the set it provided regardless of whether
922 # they're actually a file or a directory.
922 # they're actually a file or a directory.
923 if visitentries and f not in visitentries:
923 if visitentries and f not in visitentries:
924 continue
924 continue
925 if normalizefile:
925 if normalizefile:
926 # even though f might be a directory, we're only
926 # even though f might be a directory, we're only
927 # interested in comparing it to files currently in the
927 # interested in comparing it to files currently in the
928 # dmap -- therefore normalizefile is enough
928 # dmap -- therefore normalizefile is enough
929 nf = normalizefile(nd and (nd + "/" + f) or f, True,
929 nf = normalizefile(nd and (nd + "/" + f) or f, True,
930 True)
930 True)
931 else:
931 else:
932 nf = nd and (nd + "/" + f) or f
932 nf = nd and (nd + "/" + f) or f
933 if nf not in results:
933 if nf not in results:
934 if kind == dirkind:
934 if kind == dirkind:
935 if not ignore(nf):
935 if not ignore(nf):
936 if matchtdir:
936 if matchtdir:
937 matchtdir(nf)
937 matchtdir(nf)
938 wadd(nf)
938 wadd(nf)
939 if nf in dmap and (matchalways or matchfn(nf)):
939 if nf in dmap and (matchalways or matchfn(nf)):
940 results[nf] = None
940 results[nf] = None
941 elif kind == regkind or kind == lnkkind:
941 elif kind == regkind or kind == lnkkind:
942 if nf in dmap:
942 if nf in dmap:
943 if matchalways or matchfn(nf):
943 if matchalways or matchfn(nf):
944 results[nf] = st
944 results[nf] = st
945 elif ((matchalways or matchfn(nf))
945 elif ((matchalways or matchfn(nf))
946 and not ignore(nf)):
946 and not ignore(nf)):
947 # unknown file -- normalize if necessary
947 # unknown file -- normalize if necessary
948 if not alreadynormed:
948 if not alreadynormed:
949 nf = normalize(nf, False, True)
949 nf = normalize(nf, False, True)
950 results[nf] = st
950 results[nf] = st
951 elif nf in dmap and (matchalways or matchfn(nf)):
951 elif nf in dmap and (matchalways or matchfn(nf)):
952 results[nf] = None
952 results[nf] = None
953
953
954 for nd, d in work:
954 for nd, d in work:
955 # alreadynormed means that processwork doesn't have to do any
955 # alreadynormed means that processwork doesn't have to do any
956 # expensive directory normalization
956 # expensive directory normalization
957 alreadynormed = not normalize or nd == d
957 alreadynormed = not normalize or nd == d
958 traverse([d], alreadynormed)
958 traverse([d], alreadynormed)
959
959
960 for s in subrepos:
960 for s in subrepos:
961 del results[s]
961 del results[s]
962 del results['.hg']
962 del results['.hg']
963
963
964 # step 3: visit remaining files from dmap
964 # step 3: visit remaining files from dmap
965 if not skipstep3 and not exact:
965 if not skipstep3 and not exact:
966 # If a dmap file is not in results yet, it was either
966 # If a dmap file is not in results yet, it was either
967 # a) not matching matchfn b) ignored, c) missing, or d) under a
967 # a) not matching matchfn b) ignored, c) missing, or d) under a
968 # symlink directory.
968 # symlink directory.
969 if not results and matchalways:
969 if not results and matchalways:
970 visit = [f for f in dmap]
970 visit = [f for f in dmap]
971 else:
971 else:
972 visit = [f for f in dmap if f not in results and matchfn(f)]
972 visit = [f for f in dmap if f not in results and matchfn(f)]
973 visit.sort()
973 visit.sort()
974
974
975 if unknown:
975 if unknown:
976 # unknown == True means we walked all dirs under the roots
976 # unknown == True means we walked all dirs under the roots
977 # that wasn't ignored, and everything that matched was stat'ed
977 # that wasn't ignored, and everything that matched was stat'ed
978 # and is already in results.
978 # and is already in results.
979 # The rest must thus be ignored or under a symlink.
979 # The rest must thus be ignored or under a symlink.
980 audit_path = pathutil.pathauditor(self._root, cached=True)
980 audit_path = pathutil.pathauditor(self._root, cached=True)
981
981
982 for nf in iter(visit):
982 for nf in iter(visit):
983 # If a stat for the same file was already added with a
983 # If a stat for the same file was already added with a
984 # different case, don't add one for this, since that would
984 # different case, don't add one for this, since that would
985 # make it appear as if the file exists under both names
985 # make it appear as if the file exists under both names
986 # on disk.
986 # on disk.
987 if (normalizefile and
987 if (normalizefile and
988 normalizefile(nf, True, True) in results):
988 normalizefile(nf, True, True) in results):
989 results[nf] = None
989 results[nf] = None
990 # Report ignored items in the dmap as long as they are not
990 # Report ignored items in the dmap as long as they are not
991 # under a symlink directory.
991 # under a symlink directory.
992 elif audit_path.check(nf):
992 elif audit_path.check(nf):
993 try:
993 try:
994 results[nf] = lstat(join(nf))
994 results[nf] = lstat(join(nf))
995 # file was just ignored, no links, and exists
995 # file was just ignored, no links, and exists
996 except OSError:
996 except OSError:
997 # file doesn't exist
997 # file doesn't exist
998 results[nf] = None
998 results[nf] = None
999 else:
999 else:
1000 # It's either missing or under a symlink directory
1000 # It's either missing or under a symlink directory
1001 # which we in this case report as missing
1001 # which we in this case report as missing
1002 results[nf] = None
1002 results[nf] = None
1003 else:
1003 else:
1004 # We may not have walked the full directory tree above,
1004 # We may not have walked the full directory tree above,
1005 # so stat and check everything we missed.
1005 # so stat and check everything we missed.
1006 iv = iter(visit)
1006 iv = iter(visit)
1007 for st in util.statfiles([join(i) for i in visit]):
1007 for st in util.statfiles([join(i) for i in visit]):
1008 results[next(iv)] = st
1008 results[next(iv)] = st
1009 return results
1009 return results
1010
1010
1011 def status(self, match, subrepos, ignored, clean, unknown):
1011 def status(self, match, subrepos, ignored, clean, unknown):
1012 '''Determine the status of the working copy relative to the
1012 '''Determine the status of the working copy relative to the
1013 dirstate and return a pair of (unsure, status), where status is of type
1013 dirstate and return a pair of (unsure, status), where status is of type
1014 scmutil.status and:
1014 scmutil.status and:
1015
1015
1016 unsure:
1016 unsure:
1017 files that might have been modified since the dirstate was
1017 files that might have been modified since the dirstate was
1018 written, but need to be read to be sure (size is the same
1018 written, but need to be read to be sure (size is the same
1019 but mtime differs)
1019 but mtime differs)
1020 status.modified:
1020 status.modified:
1021 files that have definitely been modified since the dirstate
1021 files that have definitely been modified since the dirstate
1022 was written (different size or mode)
1022 was written (different size or mode)
1023 status.clean:
1023 status.clean:
1024 files that have definitely not been modified since the
1024 files that have definitely not been modified since the
1025 dirstate was written
1025 dirstate was written
1026 '''
1026 '''
1027 listignored, listclean, listunknown = ignored, clean, unknown
1027 listignored, listclean, listunknown = ignored, clean, unknown
1028 lookup, modified, added, unknown, ignored = [], [], [], [], []
1028 lookup, modified, added, unknown, ignored = [], [], [], [], []
1029 removed, deleted, clean = [], [], []
1029 removed, deleted, clean = [], [], []
1030
1030
1031 dmap = self._map
1031 dmap = self._map
1032 dmap.preload()
1032 dmap.preload()
1033 dcontains = dmap.__contains__
1033 dcontains = dmap.__contains__
1034 dget = dmap.__getitem__
1034 dget = dmap.__getitem__
1035 ladd = lookup.append # aka "unsure"
1035 ladd = lookup.append # aka "unsure"
1036 madd = modified.append
1036 madd = modified.append
1037 aadd = added.append
1037 aadd = added.append
1038 uadd = unknown.append
1038 uadd = unknown.append
1039 iadd = ignored.append
1039 iadd = ignored.append
1040 radd = removed.append
1040 radd = removed.append
1041 dadd = deleted.append
1041 dadd = deleted.append
1042 cadd = clean.append
1042 cadd = clean.append
1043 mexact = match.exact
1043 mexact = match.exact
1044 dirignore = self._dirignore
1044 dirignore = self._dirignore
1045 checkexec = self._checkexec
1045 checkexec = self._checkexec
1046 copymap = self._map.copymap
1046 copymap = self._map.copymap
1047 lastnormaltime = self._lastnormaltime
1047 lastnormaltime = self._lastnormaltime
1048
1048
1049 # We need to do full walks when either
1049 # We need to do full walks when either
1050 # - we're listing all clean files, or
1050 # - we're listing all clean files, or
1051 # - match.traversedir does something, because match.traversedir should
1051 # - match.traversedir does something, because match.traversedir should
1052 # be called for every dir in the working dir
1052 # be called for every dir in the working dir
1053 full = listclean or match.traversedir is not None
1053 full = listclean or match.traversedir is not None
1054 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1054 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1055 full=full).iteritems():
1055 full=full).iteritems():
1056 if not dcontains(fn):
1056 if not dcontains(fn):
1057 if (listignored or mexact(fn)) and dirignore(fn):
1057 if (listignored or mexact(fn)) and dirignore(fn):
1058 if listignored:
1058 if listignored:
1059 iadd(fn)
1059 iadd(fn)
1060 else:
1060 else:
1061 uadd(fn)
1061 uadd(fn)
1062 continue
1062 continue
1063
1063
1064 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1064 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1065 # written like that for performance reasons. dmap[fn] is not a
1065 # written like that for performance reasons. dmap[fn] is not a
1066 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1066 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1067 # opcode has fast paths when the value to be unpacked is a tuple or
1067 # opcode has fast paths when the value to be unpacked is a tuple or
1068 # a list, but falls back to creating a full-fledged iterator in
1068 # a list, but falls back to creating a full-fledged iterator in
1069 # general. That is much slower than simply accessing and storing the
1069 # general. That is much slower than simply accessing and storing the
1070 # tuple members one by one.
1070 # tuple members one by one.
1071 t = dget(fn)
1071 t = dget(fn)
1072 state = t[0]
1072 state = t[0]
1073 mode = t[1]
1073 mode = t[1]
1074 size = t[2]
1074 size = t[2]
1075 time = t[3]
1075 time = t[3]
1076
1076
1077 if not st and state in "nma":
1077 if not st and state in "nma":
1078 dadd(fn)
1078 dadd(fn)
1079 elif state == 'n':
1079 elif state == 'n':
1080 if (size >= 0 and
1080 if (size >= 0 and
1081 ((size != st.st_size and size != st.st_size & _rangemask)
1081 ((size != st.st_size and size != st.st_size & _rangemask)
1082 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1082 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1083 or size == -2 # other parent
1083 or size == -2 # other parent
1084 or fn in copymap):
1084 or fn in copymap):
1085 madd(fn)
1085 madd(fn)
1086 elif (time != st[stat.ST_MTIME]
1086 elif (time != st[stat.ST_MTIME]
1087 and time != st[stat.ST_MTIME] & _rangemask):
1087 and time != st[stat.ST_MTIME] & _rangemask):
1088 ladd(fn)
1088 ladd(fn)
1089 elif st[stat.ST_MTIME] == lastnormaltime:
1089 elif st[stat.ST_MTIME] == lastnormaltime:
1090 # fn may have just been marked as normal and it may have
1090 # fn may have just been marked as normal and it may have
1091 # changed in the same second without changing its size.
1091 # changed in the same second without changing its size.
1092 # This can happen if we quickly do multiple commits.
1092 # This can happen if we quickly do multiple commits.
1093 # Force lookup, so we don't miss such a racy file change.
1093 # Force lookup, so we don't miss such a racy file change.
1094 ladd(fn)
1094 ladd(fn)
1095 elif listclean:
1095 elif listclean:
1096 cadd(fn)
1096 cadd(fn)
1097 elif state == 'm':
1097 elif state == 'm':
1098 madd(fn)
1098 madd(fn)
1099 elif state == 'a':
1099 elif state == 'a':
1100 aadd(fn)
1100 aadd(fn)
1101 elif state == 'r':
1101 elif state == 'r':
1102 radd(fn)
1102 radd(fn)
1103
1103
1104 return (lookup, scmutil.status(modified, added, removed, deleted,
1104 return (lookup, scmutil.status(modified, added, removed, deleted,
1105 unknown, ignored, clean))
1105 unknown, ignored, clean))
1106
1106
1107 def matches(self, match):
1107 def matches(self, match):
1108 '''
1108 '''
1109 return files in the dirstate (in whatever state) filtered by match
1109 return files in the dirstate (in whatever state) filtered by match
1110 '''
1110 '''
1111 dmap = self._map
1111 dmap = self._map
1112 if match.always():
1112 if match.always():
1113 return dmap.keys()
1113 return dmap.keys()
1114 files = match.files()
1114 files = match.files()
1115 if match.isexact():
1115 if match.isexact():
1116 # fast path -- filter the other way around, since typically files is
1116 # fast path -- filter the other way around, since typically files is
1117 # much smaller than dmap
1117 # much smaller than dmap
1118 return [f for f in files if f in dmap]
1118 return [f for f in files if f in dmap]
1119 if match.prefix() and all(fn in dmap for fn in files):
1119 if match.prefix() and all(fn in dmap for fn in files):
1120 # fast path -- all the values are known to be files, so just return
1120 # fast path -- all the values are known to be files, so just return
1121 # that
1121 # that
1122 return list(files)
1122 return list(files)
1123 return [f for f in dmap if match(f)]
1123 return [f for f in dmap if match(f)]
1124
1124
1125 def _actualfilename(self, tr):
1125 def _actualfilename(self, tr):
1126 if tr:
1126 if tr:
1127 return self._pendingfilename
1127 return self._pendingfilename
1128 else:
1128 else:
1129 return self._filename
1129 return self._filename
1130
1130
1131 def savebackup(self, tr, backupname):
1131 def savebackup(self, tr, backupname):
1132 '''Save current dirstate into backup file'''
1132 '''Save current dirstate into backup file'''
1133 filename = self._actualfilename(tr)
1133 filename = self._actualfilename(tr)
1134 assert backupname != filename
1134 assert backupname != filename
1135
1135
1136 # use '_writedirstate' instead of 'write' to write changes certainly,
1136 # use '_writedirstate' instead of 'write' to write changes certainly,
1137 # because the latter omits writing out if transaction is running.
1137 # because the latter omits writing out if transaction is running.
1138 # output file will be used to create backup of dirstate at this point.
1138 # output file will be used to create backup of dirstate at this point.
1139 if self._dirty or not self._opener.exists(filename):
1139 if self._dirty or not self._opener.exists(filename):
1140 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1140 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1141 checkambig=True))
1141 checkambig=True))
1142
1142
1143 if tr:
1143 if tr:
1144 # ensure that subsequent tr.writepending returns True for
1144 # ensure that subsequent tr.writepending returns True for
1145 # changes written out above, even if dirstate is never
1145 # changes written out above, even if dirstate is never
1146 # changed after this
1146 # changed after this
1147 tr.addfilegenerator('dirstate', (self._filename,),
1147 tr.addfilegenerator('dirstate', (self._filename,),
1148 self._writedirstate, location='plain')
1148 self._writedirstate, location='plain')
1149
1149
1150 # ensure that pending file written above is unlinked at
1150 # ensure that pending file written above is unlinked at
1151 # failure, even if tr.writepending isn't invoked until the
1151 # failure, even if tr.writepending isn't invoked until the
1152 # end of this transaction
1152 # end of this transaction
1153 tr.registertmp(filename, location='plain')
1153 tr.registertmp(filename, location='plain')
1154
1154
1155 self._opener.tryunlink(backupname)
1155 self._opener.tryunlink(backupname)
1156 # hardlink backup is okay because _writedirstate is always called
1156 # hardlink backup is okay because _writedirstate is always called
1157 # with an "atomictemp=True" file.
1157 # with an "atomictemp=True" file.
1158 util.copyfile(self._opener.join(filename),
1158 util.copyfile(self._opener.join(filename),
1159 self._opener.join(backupname), hardlink=True)
1159 self._opener.join(backupname), hardlink=True)
1160
1160
1161 def restorebackup(self, tr, backupname):
1161 def restorebackup(self, tr, backupname):
1162 '''Restore dirstate by backup file'''
1162 '''Restore dirstate by backup file'''
1163 # this "invalidate()" prevents "wlock.release()" from writing
1163 # this "invalidate()" prevents "wlock.release()" from writing
1164 # changes of dirstate out after restoring from backup file
1164 # changes of dirstate out after restoring from backup file
1165 self.invalidate()
1165 self.invalidate()
1166 filename = self._actualfilename(tr)
1166 filename = self._actualfilename(tr)
1167 o = self._opener
1167 o = self._opener
1168 if util.samefile(o.join(backupname), o.join(filename)):
1168 if util.samefile(o.join(backupname), o.join(filename)):
1169 o.unlink(backupname)
1169 o.unlink(backupname)
1170 else:
1170 else:
1171 o.rename(backupname, filename, checkambig=True)
1171 o.rename(backupname, filename, checkambig=True)
1172
1172
1173 def clearbackup(self, tr, backupname):
1173 def clearbackup(self, tr, backupname):
1174 '''Clear backup file'''
1174 '''Clear backup file'''
1175 self._opener.unlink(backupname)
1175 self._opener.unlink(backupname)
1176
1176
1177 class dirstatemap(object):
1177 class dirstatemap(object):
1178 """Map encapsulating the dirstate's contents.
1178 """Map encapsulating the dirstate's contents.
1179
1179
1180 The dirstate contains the following state:
1180 The dirstate contains the following state:
1181
1181
1182 - `identity` is the identity of the dirstate file, which can be used to
1182 - `identity` is the identity of the dirstate file, which can be used to
1183 detect when changes have occurred to the dirstate file.
1183 detect when changes have occurred to the dirstate file.
1184
1184
1185 - `parents` is a pair containing the parents of the working copy. The
1185 - `parents` is a pair containing the parents of the working copy. The
1186 parents are updated by calling `setparents`.
1186 parents are updated by calling `setparents`.
1187
1187
1188 - the state map maps filenames to tuples of (state, mode, size, mtime),
1188 - the state map maps filenames to tuples of (state, mode, size, mtime),
1189 where state is a single character representing 'normal', 'added',
1189 where state is a single character representing 'normal', 'added',
1190 'removed', or 'merged'. It is read by treating the dirstate as a
1190 'removed', or 'merged'. It is read by treating the dirstate as a
1191 dict. File state is updated by calling the `addfile`, `removefile` and
1191 dict. File state is updated by calling the `addfile`, `removefile` and
1192 `dropfile` methods.
1192 `dropfile` methods.
1193
1193
1194 - `copymap` maps destination filenames to their source filename.
1194 - `copymap` maps destination filenames to their source filename.
1195
1195
1196 The dirstate also provides the following views onto the state:
1196 The dirstate also provides the following views onto the state:
1197
1197
1198 - `nonnormalset` is a set of the filenames that have state other
1198 - `nonnormalset` is a set of the filenames that have state other
1199 than 'normal', or are normal but have an mtime of -1 ('normallookup').
1199 than 'normal', or are normal but have an mtime of -1 ('normallookup').
1200
1200
1201 - `otherparentset` is a set of the filenames that are marked as coming
1201 - `otherparentset` is a set of the filenames that are marked as coming
1202 from the second parent when the dirstate is currently being merged.
1202 from the second parent when the dirstate is currently being merged.
1203
1203
1204 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
1204 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
1205 form that they appear as in the dirstate.
1205 form that they appear as in the dirstate.
1206
1206
1207 - `dirfoldmap` is a dict mapping normalized directory names to the
1207 - `dirfoldmap` is a dict mapping normalized directory names to the
1208 denormalized form that they appear as in the dirstate.
1208 denormalized form that they appear as in the dirstate.
1209 """
1209 """
1210
1210
1211 def __init__(self, ui, opener, root):
1211 def __init__(self, ui, opener, root):
1212 self._ui = ui
1212 self._ui = ui
1213 self._opener = opener
1213 self._opener = opener
1214 self._root = root
1214 self._root = root
1215 self._filename = 'dirstate'
1215 self._filename = 'dirstate'
1216
1216
1217 self._parents = None
1217 self._parents = None
1218 self._dirtyparents = False
1218 self._dirtyparents = False
1219
1219
1220 # for consistent view between _pl() and _read() invocations
1220 # for consistent view between _pl() and _read() invocations
1221 self._pendingmode = None
1221 self._pendingmode = None
1222
1222
1223 @propertycache
1223 @propertycache
1224 def _map(self):
1224 def _map(self):
1225 self._map = {}
1225 self._map = {}
1226 self.read()
1226 self.read()
1227 return self._map
1227 return self._map
1228
1228
1229 @propertycache
1229 @propertycache
1230 def copymap(self):
1230 def copymap(self):
1231 self.copymap = {}
1231 self.copymap = {}
1232 self._map
1232 self._map
1233 return self.copymap
1233 return self.copymap
1234
1234
1235 def clear(self):
1235 def clear(self):
1236 self._map.clear()
1236 self._map.clear()
1237 self.copymap.clear()
1237 self.copymap.clear()
1238 self.setparents(nullid, nullid)
1238 self.setparents(nullid, nullid)
1239 util.clearcachedproperty(self, "_dirs")
1239 util.clearcachedproperty(self, "_dirs")
1240 util.clearcachedproperty(self, "_alldirs")
1240 util.clearcachedproperty(self, "_alldirs")
1241 util.clearcachedproperty(self, "filefoldmap")
1241 util.clearcachedproperty(self, "filefoldmap")
1242 util.clearcachedproperty(self, "dirfoldmap")
1242 util.clearcachedproperty(self, "dirfoldmap")
1243 util.clearcachedproperty(self, "nonnormalset")
1243 util.clearcachedproperty(self, "nonnormalset")
1244 util.clearcachedproperty(self, "otherparentset")
1244 util.clearcachedproperty(self, "otherparentset")
1245
1245
1246 def items(self):
1246 def items(self):
1247 return self._map.iteritems()
1247 return self._map.iteritems()
1248
1248
1249 # forward for python2,3 compat
1249 # forward for python2,3 compat
1250 iteritems = items
1250 iteritems = items
1251
1251
1252 def __len__(self):
1252 def __len__(self):
1253 return len(self._map)
1253 return len(self._map)
1254
1254
1255 def __iter__(self):
1255 def __iter__(self):
1256 return iter(self._map)
1256 return iter(self._map)
1257
1257
1258 def get(self, key, default=None):
1258 def get(self, key, default=None):
1259 return self._map.get(key, default)
1259 return self._map.get(key, default)
1260
1260
1261 def __contains__(self, key):
1261 def __contains__(self, key):
1262 return key in self._map
1262 return key in self._map
1263
1263
1264 def __getitem__(self, key):
1264 def __getitem__(self, key):
1265 return self._map[key]
1265 return self._map[key]
1266
1266
1267 def keys(self):
1267 def keys(self):
1268 return self._map.keys()
1268 return self._map.keys()
1269
1269
1270 def preload(self):
1270 def preload(self):
1271 """Loads the underlying data, if it's not already loaded"""
1271 """Loads the underlying data, if it's not already loaded"""
1272 self._map
1272 self._map
1273
1273
1274 def addfile(self, f, oldstate, state, mode, size, mtime):
1274 def addfile(self, f, oldstate, state, mode, size, mtime):
1275 """Add a tracked file to the dirstate."""
1275 """Add a tracked file to the dirstate."""
1276 if oldstate in "?r" and r"_dirs" in self.__dict__:
1276 if oldstate in "?r" and r"_dirs" in self.__dict__:
1277 self._dirs.addpath(f)
1277 self._dirs.addpath(f)
1278 if oldstate == "?" and r"_alldirs" in self.__dict__:
1278 if oldstate == "?" and r"_alldirs" in self.__dict__:
1279 self._alldirs.addpath(f)
1279 self._alldirs.addpath(f)
1280 self._map[f] = dirstatetuple(state, mode, size, mtime)
1280 self._map[f] = dirstatetuple(state, mode, size, mtime)
1281 if state != 'n' or mtime == -1:
1281 if state != 'n' or mtime == -1:
1282 self.nonnormalset.add(f)
1282 self.nonnormalset.add(f)
1283 if size == -2:
1283 if size == -2:
1284 self.otherparentset.add(f)
1284 self.otherparentset.add(f)
1285
1285
1286 def removefile(self, f, oldstate, size):
1286 def removefile(self, f, oldstate, size):
1287 """
1287 """
1288 Mark a file as removed in the dirstate.
1288 Mark a file as removed in the dirstate.
1289
1289
1290 The `size` parameter is used to store sentinel values that indicate
1290 The `size` parameter is used to store sentinel values that indicate
1291 the file's previous state. In the future, we should refactor this
1291 the file's previous state. In the future, we should refactor this
1292 to be more explicit about what that state is.
1292 to be more explicit about what that state is.
1293 """
1293 """
1294 if oldstate not in "?r" and r"_dirs" in self.__dict__:
1294 if oldstate not in "?r" and r"_dirs" in self.__dict__:
1295 self._dirs.delpath(f)
1295 self._dirs.delpath(f)
1296 if oldstate == "?" and r"_alldirs" in self.__dict__:
1296 if oldstate == "?" and r"_alldirs" in self.__dict__:
1297 self._alldirs.addpath(f)
1297 self._alldirs.addpath(f)
1298 if r"filefoldmap" in self.__dict__:
1298 if r"filefoldmap" in self.__dict__:
1299 normed = util.normcase(f)
1299 normed = util.normcase(f)
1300 self.filefoldmap.pop(normed, None)
1300 self.filefoldmap.pop(normed, None)
1301 self._map[f] = dirstatetuple('r', 0, size, 0)
1301 self._map[f] = dirstatetuple('r', 0, size, 0)
1302 self.nonnormalset.add(f)
1302 self.nonnormalset.add(f)
1303
1303
1304 def dropfile(self, f, oldstate):
1304 def dropfile(self, f, oldstate):
1305 """
1305 """
1306 Remove a file from the dirstate. Returns True if the file was
1306 Remove a file from the dirstate. Returns True if the file was
1307 previously recorded.
1307 previously recorded.
1308 """
1308 """
1309 exists = self._map.pop(f, None) is not None
1309 exists = self._map.pop(f, None) is not None
1310 if exists:
1310 if exists:
1311 if oldstate != "r" and r"_dirs" in self.__dict__:
1311 if oldstate != "r" and r"_dirs" in self.__dict__:
1312 self._dirs.delpath(f)
1312 self._dirs.delpath(f)
1313 if r"_alldirs" in self.__dict__:
1313 if r"_alldirs" in self.__dict__:
1314 self._alldirs.delpath(f)
1314 self._alldirs.delpath(f)
1315 if r"filefoldmap" in self.__dict__:
1315 if r"filefoldmap" in self.__dict__:
1316 normed = util.normcase(f)
1316 normed = util.normcase(f)
1317 self.filefoldmap.pop(normed, None)
1317 self.filefoldmap.pop(normed, None)
1318 self.nonnormalset.discard(f)
1318 self.nonnormalset.discard(f)
1319 return exists
1319 return exists
1320
1320
1321 def clearambiguoustimes(self, files, now):
1321 def clearambiguoustimes(self, files, now):
1322 for f in files:
1322 for f in files:
1323 e = self.get(f)
1323 e = self.get(f)
1324 if e is not None and e[0] == 'n' and e[3] == now:
1324 if e is not None and e[0] == 'n' and e[3] == now:
1325 self._map[f] = dirstatetuple(e[0], e[1], e[2], -1)
1325 self._map[f] = dirstatetuple(e[0], e[1], e[2], -1)
1326 self.nonnormalset.add(f)
1326 self.nonnormalset.add(f)
1327
1327
1328 def nonnormalentries(self):
1328 def nonnormalentries(self):
1329 '''Compute the nonnormal dirstate entries from the dmap'''
1329 '''Compute the nonnormal dirstate entries from the dmap'''
1330 try:
1330 try:
1331 return parsers.nonnormalotherparententries(self._map)
1331 return parsers.nonnormalotherparententries(self._map)
1332 except AttributeError:
1332 except AttributeError:
1333 nonnorm = set()
1333 nonnorm = set()
1334 otherparent = set()
1334 otherparent = set()
1335 for fname, e in self._map.iteritems():
1335 for fname, e in self._map.iteritems():
1336 if e[0] != 'n' or e[3] == -1:
1336 if e[0] != 'n' or e[3] == -1:
1337 nonnorm.add(fname)
1337 nonnorm.add(fname)
1338 if e[0] == 'n' and e[2] == -2:
1338 if e[0] == 'n' and e[2] == -2:
1339 otherparent.add(fname)
1339 otherparent.add(fname)
1340 return nonnorm, otherparent
1340 return nonnorm, otherparent
1341
1341
1342 @propertycache
1342 @propertycache
1343 def filefoldmap(self):
1343 def filefoldmap(self):
1344 """Returns a dictionary mapping normalized case paths to their
1344 """Returns a dictionary mapping normalized case paths to their
1345 non-normalized versions.
1345 non-normalized versions.
1346 """
1346 """
1347 try:
1347 try:
1348 makefilefoldmap = parsers.make_file_foldmap
1348 makefilefoldmap = parsers.make_file_foldmap
1349 except AttributeError:
1349 except AttributeError:
1350 pass
1350 pass
1351 else:
1351 else:
1352 return makefilefoldmap(self._map, util.normcasespec,
1352 return makefilefoldmap(self._map, util.normcasespec,
1353 util.normcasefallback)
1353 util.normcasefallback)
1354
1354
1355 f = {}
1355 f = {}
1356 normcase = util.normcase
1356 normcase = util.normcase
1357 for name, s in self._map.iteritems():
1357 for name, s in self._map.iteritems():
1358 if s[0] != 'r':
1358 if s[0] != 'r':
1359 f[normcase(name)] = name
1359 f[normcase(name)] = name
1360 f['.'] = '.' # prevents useless util.fspath() invocation
1360 f['.'] = '.' # prevents useless util.fspath() invocation
1361 return f
1361 return f
1362
1362
1363 def hastrackeddir(self, d):
1363 def hastrackeddir(self, d):
1364 """
1364 """
1365 Returns True if the dirstate contains a tracked (not removed) file
1365 Returns True if the dirstate contains a tracked (not removed) file
1366 in this directory.
1366 in this directory.
1367 """
1367 """
1368 return d in self._dirs
1368 return d in self._dirs
1369
1369
1370 def hasdir(self, d):
1370 def hasdir(self, d):
1371 """
1371 """
1372 Returns True if the dirstate contains a file (tracked or removed)
1372 Returns True if the dirstate contains a file (tracked or removed)
1373 in this directory.
1373 in this directory.
1374 """
1374 """
1375 return d in self._alldirs
1375 return d in self._alldirs
1376
1376
1377 @propertycache
1377 @propertycache
1378 def _dirs(self):
1378 def _dirs(self):
1379 return util.dirs(self._map, 'r')
1379 return util.dirs(self._map, 'r')
1380
1380
1381 @propertycache
1381 @propertycache
1382 def _alldirs(self):
1382 def _alldirs(self):
1383 return util.dirs(self._map)
1383 return util.dirs(self._map)
1384
1384
1385 def _opendirstatefile(self):
1385 def _opendirstatefile(self):
1386 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1386 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1387 if self._pendingmode is not None and self._pendingmode != mode:
1387 if self._pendingmode is not None and self._pendingmode != mode:
1388 fp.close()
1388 fp.close()
1389 raise error.Abort(_('working directory state may be '
1389 raise error.Abort(_('working directory state may be '
1390 'changed parallelly'))
1390 'changed parallelly'))
1391 self._pendingmode = mode
1391 self._pendingmode = mode
1392 return fp
1392 return fp
1393
1393
1394 def parents(self):
1394 def parents(self):
1395 if not self._parents:
1395 if not self._parents:
1396 try:
1396 try:
1397 fp = self._opendirstatefile()
1397 fp = self._opendirstatefile()
1398 st = fp.read(40)
1398 st = fp.read(40)
1399 fp.close()
1399 fp.close()
1400 except IOError as err:
1400 except IOError as err:
1401 if err.errno != errno.ENOENT:
1401 if err.errno != errno.ENOENT:
1402 raise
1402 raise
1403 # File doesn't exist, so the current state is empty
1403 # File doesn't exist, so the current state is empty
1404 st = ''
1404 st = ''
1405
1405
1406 l = len(st)
1406 l = len(st)
1407 if l == 40:
1407 if l == 40:
1408 self._parents = (st[:20], st[20:40])
1408 self._parents = (st[:20], st[20:40])
1409 elif l == 0:
1409 elif l == 0:
1410 self._parents = (nullid, nullid)
1410 self._parents = (nullid, nullid)
1411 else:
1411 else:
1412 raise error.Abort(_('working directory state appears '
1412 raise error.Abort(_('working directory state appears '
1413 'damaged!'))
1413 'damaged!'))
1414
1414
1415 return self._parents
1415 return self._parents
1416
1416
1417 def setparents(self, p1, p2):
1417 def setparents(self, p1, p2):
1418 self._parents = (p1, p2)
1418 self._parents = (p1, p2)
1419 self._dirtyparents = True
1419 self._dirtyparents = True
1420
1420
1421 def read(self):
1421 def read(self):
1422 # ignore HG_PENDING because identity is used only for writing
1422 # ignore HG_PENDING because identity is used only for writing
1423 self.identity = util.filestat.frompath(
1423 self.identity = util.filestat.frompath(
1424 self._opener.join(self._filename))
1424 self._opener.join(self._filename))
1425
1425
1426 try:
1426 try:
1427 fp = self._opendirstatefile()
1427 fp = self._opendirstatefile()
1428 try:
1428 try:
1429 st = fp.read()
1429 st = fp.read()
1430 finally:
1430 finally:
1431 fp.close()
1431 fp.close()
1432 except IOError as err:
1432 except IOError as err:
1433 if err.errno != errno.ENOENT:
1433 if err.errno != errno.ENOENT:
1434 raise
1434 raise
1435 return
1435 return
1436 if not st:
1436 if not st:
1437 return
1437 return
1438
1438
1439 if util.safehasattr(parsers, 'dict_new_presized'):
1439 if util.safehasattr(parsers, 'dict_new_presized'):
1440 # Make an estimate of the number of files in the dirstate based on
1440 # Make an estimate of the number of files in the dirstate based on
1441 # its size. From a linear regression on a set of real-world repos,
1441 # its size. From a linear regression on a set of real-world repos,
1442 # all over 10,000 files, the size of a dirstate entry is 85
1442 # all over 10,000 files, the size of a dirstate entry is 85
1443 # bytes. The cost of resizing is significantly higher than the cost
1443 # bytes. The cost of resizing is significantly higher than the cost
1444 # of filling in a larger presized dict, so subtract 20% from the
1444 # of filling in a larger presized dict, so subtract 20% from the
1445 # size.
1445 # size.
1446 #
1446 #
1447 # This heuristic is imperfect in many ways, so in a future dirstate
1447 # This heuristic is imperfect in many ways, so in a future dirstate
1448 # format update it makes sense to just record the number of entries
1448 # format update it makes sense to just record the number of entries
1449 # on write.
1449 # on write.
1450 self._map = parsers.dict_new_presized(len(st) // 71)
1450 self._map = parsers.dict_new_presized(len(st) // 71)
1451
1451
1452 # Python's garbage collector triggers a GC each time a certain number
1452 # Python's garbage collector triggers a GC each time a certain number
1453 # of container objects (the number being defined by
1453 # of container objects (the number being defined by
1454 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1454 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1455 # for each file in the dirstate. The C version then immediately marks
1455 # for each file in the dirstate. The C version then immediately marks
1456 # them as not to be tracked by the collector. However, this has no
1456 # them as not to be tracked by the collector. However, this has no
1457 # effect on when GCs are triggered, only on what objects the GC looks
1457 # effect on when GCs are triggered, only on what objects the GC looks
1458 # into. This means that O(number of files) GCs are unavoidable.
1458 # into. This means that O(number of files) GCs are unavoidable.
1459 # Depending on when in the process's lifetime the dirstate is parsed,
1459 # Depending on when in the process's lifetime the dirstate is parsed,
1460 # this can get very expensive. As a workaround, disable GC while
1460 # this can get very expensive. As a workaround, disable GC while
1461 # parsing the dirstate.
1461 # parsing the dirstate.
1462 #
1462 #
1463 # (we cannot decorate the function directly since it is in a C module)
1463 # (we cannot decorate the function directly since it is in a C module)
1464 parse_dirstate = util.nogc(parsers.parse_dirstate)
1464 parse_dirstate = util.nogc(parsers.parse_dirstate)
1465 p = parse_dirstate(self._map, self.copymap, st)
1465 p = parse_dirstate(self._map, self.copymap, st)
1466 if not self._dirtyparents:
1466 if not self._dirtyparents:
1467 self.setparents(*p)
1467 self.setparents(*p)
1468
1468
1469 # Avoid excess attribute lookups by fast pathing certain checks
1469 # Avoid excess attribute lookups by fast pathing certain checks
1470 self.__contains__ = self._map.__contains__
1470 self.__contains__ = self._map.__contains__
1471 self.__getitem__ = self._map.__getitem__
1471 self.__getitem__ = self._map.__getitem__
1472 self.get = self._map.get
1472 self.get = self._map.get
1473
1473
1474 def write(self, st, now):
1474 def write(self, st, now):
1475 st.write(parsers.pack_dirstate(self._map, self.copymap,
1475 st.write(parsers.pack_dirstate(self._map, self.copymap,
1476 self.parents(), now))
1476 self.parents(), now))
1477 st.close()
1477 st.close()
1478 self._dirtyparents = False
1478 self._dirtyparents = False
1479 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1479 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1480
1480
1481 @propertycache
1481 @propertycache
1482 def nonnormalset(self):
1482 def nonnormalset(self):
1483 nonnorm, otherparents = self.nonnormalentries()
1483 nonnorm, otherparents = self.nonnormalentries()
1484 self.otherparentset = otherparents
1484 self.otherparentset = otherparents
1485 return nonnorm
1485 return nonnorm
1486
1486
1487 @propertycache
1487 @propertycache
1488 def otherparentset(self):
1488 def otherparentset(self):
1489 nonnorm, otherparents = self.nonnormalentries()
1489 nonnorm, otherparents = self.nonnormalentries()
1490 self.nonnormalset = nonnorm
1490 self.nonnormalset = nonnorm
1491 return otherparents
1491 return otherparents
1492
1492
1493 @propertycache
1493 @propertycache
1494 def identity(self):
1494 def identity(self):
1495 self._map
1495 self._map
1496 return self.identity
1496 return self.identity
1497
1497
1498 @propertycache
1498 @propertycache
1499 def dirfoldmap(self):
1499 def dirfoldmap(self):
1500 f = {}
1500 f = {}
1501 normcase = util.normcase
1501 normcase = util.normcase
1502 for name in self._dirs:
1502 for name in self._dirs:
1503 f[normcase(name)] = name
1503 f[normcase(name)] = name
1504 return f
1504 return f
@@ -1,3050 +1,3049 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import os
12 import os
13 import random
13 import random
14 import sys
14 import sys
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 bin,
20 bin,
21 hex,
21 hex,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 short,
24 short,
25 )
25 )
26 from . import (
26 from . import (
27 bookmarks,
27 bookmarks,
28 branchmap,
28 branchmap,
29 bundle2,
29 bundle2,
30 changegroup,
30 changegroup,
31 changelog,
31 changelog,
32 color,
32 color,
33 context,
33 context,
34 dirstate,
34 dirstate,
35 dirstateguard,
35 dirstateguard,
36 discovery,
36 discovery,
37 encoding,
37 encoding,
38 error,
38 error,
39 exchange,
39 exchange,
40 extensions,
40 extensions,
41 filelog,
41 filelog,
42 hook,
42 hook,
43 lock as lockmod,
43 lock as lockmod,
44 manifest,
44 manifest,
45 match as matchmod,
45 match as matchmod,
46 merge as mergemod,
46 merge as mergemod,
47 mergeutil,
47 mergeutil,
48 namespaces,
48 namespaces,
49 narrowspec,
49 narrowspec,
50 obsolete,
50 obsolete,
51 pathutil,
51 pathutil,
52 phases,
52 phases,
53 pushkey,
53 pushkey,
54 pycompat,
54 pycompat,
55 repository,
55 repository,
56 repoview,
56 repoview,
57 revset,
57 revset,
58 revsetlang,
58 revsetlang,
59 scmutil,
59 scmutil,
60 sparse,
60 sparse,
61 store as storemod,
61 store as storemod,
62 subrepoutil,
62 subrepoutil,
63 tags as tagsmod,
63 tags as tagsmod,
64 transaction,
64 transaction,
65 txnutil,
65 txnutil,
66 util,
66 util,
67 vfs as vfsmod,
67 vfs as vfsmod,
68 )
68 )
69 from .utils import (
69 from .utils import (
70 interfaceutil,
70 interfaceutil,
71 procutil,
71 procutil,
72 stringutil,
72 stringutil,
73 )
73 )
74
74
75 from .revlogutils import (
75 from .revlogutils import (
76 constants as revlogconst,
76 constants as revlogconst,
77 )
77 )
78
78
79 release = lockmod.release
79 release = lockmod.release
80 urlerr = util.urlerr
80 urlerr = util.urlerr
81 urlreq = util.urlreq
81 urlreq = util.urlreq
82
82
83 # set of (path, vfs-location) tuples. vfs-location is:
83 # set of (path, vfs-location) tuples. vfs-location is:
84 # - 'plain for vfs relative paths
84 # - 'plain for vfs relative paths
85 # - '' for svfs relative paths
85 # - '' for svfs relative paths
86 _cachedfiles = set()
86 _cachedfiles = set()
87
87
88 class _basefilecache(scmutil.filecache):
88 class _basefilecache(scmutil.filecache):
89 """All filecache usage on repo are done for logic that should be unfiltered
89 """All filecache usage on repo are done for logic that should be unfiltered
90 """
90 """
91 def __get__(self, repo, type=None):
91 def __get__(self, repo, type=None):
92 if repo is None:
92 if repo is None:
93 return self
93 return self
94 # inlined the fast path as the cost of function call matters
94 # proxy to unfiltered __dict__ since filtered repo has no entry
95 unfi = repo.unfiltered()
95 unfi = repo.unfiltered()
96 try:
96 try:
97 return unfi.__dict__[self.sname]
97 return unfi.__dict__[self.sname]
98 except KeyError:
98 except KeyError:
99 pass
99 pass
100 return super(_basefilecache, self).__get__(unfi, type)
100 return super(_basefilecache, self).__get__(unfi, type)
101 def __set__(self, repo, value):
101
102 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
102 def set(self, repo, value):
103 def __delete__(self, repo):
103 return super(_basefilecache, self).set(repo.unfiltered(), value)
104 return super(_basefilecache, self).__delete__(repo.unfiltered())
105
104
106 class repofilecache(_basefilecache):
105 class repofilecache(_basefilecache):
107 """filecache for files in .hg but outside of .hg/store"""
106 """filecache for files in .hg but outside of .hg/store"""
108 def __init__(self, *paths):
107 def __init__(self, *paths):
109 super(repofilecache, self).__init__(*paths)
108 super(repofilecache, self).__init__(*paths)
110 for path in paths:
109 for path in paths:
111 _cachedfiles.add((path, 'plain'))
110 _cachedfiles.add((path, 'plain'))
112
111
113 def join(self, obj, fname):
112 def join(self, obj, fname):
114 return obj.vfs.join(fname)
113 return obj.vfs.join(fname)
115
114
116 class storecache(_basefilecache):
115 class storecache(_basefilecache):
117 """filecache for files in the store"""
116 """filecache for files in the store"""
118 def __init__(self, *paths):
117 def __init__(self, *paths):
119 super(storecache, self).__init__(*paths)
118 super(storecache, self).__init__(*paths)
120 for path in paths:
119 for path in paths:
121 _cachedfiles.add((path, ''))
120 _cachedfiles.add((path, ''))
122
121
123 def join(self, obj, fname):
122 def join(self, obj, fname):
124 return obj.sjoin(fname)
123 return obj.sjoin(fname)
125
124
126 def isfilecached(repo, name):
125 def isfilecached(repo, name):
127 """check if a repo has already cached "name" filecache-ed property
126 """check if a repo has already cached "name" filecache-ed property
128
127
129 This returns (cachedobj-or-None, iscached) tuple.
128 This returns (cachedobj-or-None, iscached) tuple.
130 """
129 """
131 cacheentry = repo.unfiltered()._filecache.get(name, None)
130 cacheentry = repo.unfiltered()._filecache.get(name, None)
132 if not cacheentry:
131 if not cacheentry:
133 return None, False
132 return None, False
134 return cacheentry.obj, True
133 return cacheentry.obj, True
135
134
136 class unfilteredpropertycache(util.propertycache):
135 class unfilteredpropertycache(util.propertycache):
137 """propertycache that apply to unfiltered repo only"""
136 """propertycache that apply to unfiltered repo only"""
138
137
139 def __get__(self, repo, type=None):
138 def __get__(self, repo, type=None):
140 unfi = repo.unfiltered()
139 unfi = repo.unfiltered()
141 if unfi is repo:
140 if unfi is repo:
142 return super(unfilteredpropertycache, self).__get__(unfi)
141 return super(unfilteredpropertycache, self).__get__(unfi)
143 return getattr(unfi, self.name)
142 return getattr(unfi, self.name)
144
143
145 class filteredpropertycache(util.propertycache):
144 class filteredpropertycache(util.propertycache):
146 """propertycache that must take filtering in account"""
145 """propertycache that must take filtering in account"""
147
146
148 def cachevalue(self, obj, value):
147 def cachevalue(self, obj, value):
149 object.__setattr__(obj, self.name, value)
148 object.__setattr__(obj, self.name, value)
150
149
151
150
152 def hasunfilteredcache(repo, name):
151 def hasunfilteredcache(repo, name):
153 """check if a repo has an unfilteredpropertycache value for <name>"""
152 """check if a repo has an unfilteredpropertycache value for <name>"""
154 return name in vars(repo.unfiltered())
153 return name in vars(repo.unfiltered())
155
154
156 def unfilteredmethod(orig):
155 def unfilteredmethod(orig):
157 """decorate method that always need to be run on unfiltered version"""
156 """decorate method that always need to be run on unfiltered version"""
158 def wrapper(repo, *args, **kwargs):
157 def wrapper(repo, *args, **kwargs):
159 return orig(repo.unfiltered(), *args, **kwargs)
158 return orig(repo.unfiltered(), *args, **kwargs)
160 return wrapper
159 return wrapper
161
160
162 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
161 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
163 'unbundle'}
162 'unbundle'}
164 legacycaps = moderncaps.union({'changegroupsubset'})
163 legacycaps = moderncaps.union({'changegroupsubset'})
165
164
166 @interfaceutil.implementer(repository.ipeercommandexecutor)
165 @interfaceutil.implementer(repository.ipeercommandexecutor)
167 class localcommandexecutor(object):
166 class localcommandexecutor(object):
168 def __init__(self, peer):
167 def __init__(self, peer):
169 self._peer = peer
168 self._peer = peer
170 self._sent = False
169 self._sent = False
171 self._closed = False
170 self._closed = False
172
171
173 def __enter__(self):
172 def __enter__(self):
174 return self
173 return self
175
174
176 def __exit__(self, exctype, excvalue, exctb):
175 def __exit__(self, exctype, excvalue, exctb):
177 self.close()
176 self.close()
178
177
179 def callcommand(self, command, args):
178 def callcommand(self, command, args):
180 if self._sent:
179 if self._sent:
181 raise error.ProgrammingError('callcommand() cannot be used after '
180 raise error.ProgrammingError('callcommand() cannot be used after '
182 'sendcommands()')
181 'sendcommands()')
183
182
184 if self._closed:
183 if self._closed:
185 raise error.ProgrammingError('callcommand() cannot be used after '
184 raise error.ProgrammingError('callcommand() cannot be used after '
186 'close()')
185 'close()')
187
186
188 # We don't need to support anything fancy. Just call the named
187 # We don't need to support anything fancy. Just call the named
189 # method on the peer and return a resolved future.
188 # method on the peer and return a resolved future.
190 fn = getattr(self._peer, pycompat.sysstr(command))
189 fn = getattr(self._peer, pycompat.sysstr(command))
191
190
192 f = pycompat.futures.Future()
191 f = pycompat.futures.Future()
193
192
194 try:
193 try:
195 result = fn(**pycompat.strkwargs(args))
194 result = fn(**pycompat.strkwargs(args))
196 except Exception:
195 except Exception:
197 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
196 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
198 else:
197 else:
199 f.set_result(result)
198 f.set_result(result)
200
199
201 return f
200 return f
202
201
203 def sendcommands(self):
202 def sendcommands(self):
204 self._sent = True
203 self._sent = True
205
204
206 def close(self):
205 def close(self):
207 self._closed = True
206 self._closed = True
208
207
209 @interfaceutil.implementer(repository.ipeercommands)
208 @interfaceutil.implementer(repository.ipeercommands)
210 class localpeer(repository.peer):
209 class localpeer(repository.peer):
211 '''peer for a local repo; reflects only the most recent API'''
210 '''peer for a local repo; reflects only the most recent API'''
212
211
213 def __init__(self, repo, caps=None):
212 def __init__(self, repo, caps=None):
214 super(localpeer, self).__init__()
213 super(localpeer, self).__init__()
215
214
216 if caps is None:
215 if caps is None:
217 caps = moderncaps.copy()
216 caps = moderncaps.copy()
218 self._repo = repo.filtered('served')
217 self._repo = repo.filtered('served')
219 self.ui = repo.ui
218 self.ui = repo.ui
220 self._caps = repo._restrictcapabilities(caps)
219 self._caps = repo._restrictcapabilities(caps)
221
220
222 # Begin of _basepeer interface.
221 # Begin of _basepeer interface.
223
222
224 def url(self):
223 def url(self):
225 return self._repo.url()
224 return self._repo.url()
226
225
227 def local(self):
226 def local(self):
228 return self._repo
227 return self._repo
229
228
230 def peer(self):
229 def peer(self):
231 return self
230 return self
232
231
233 def canpush(self):
232 def canpush(self):
234 return True
233 return True
235
234
236 def close(self):
235 def close(self):
237 self._repo.close()
236 self._repo.close()
238
237
239 # End of _basepeer interface.
238 # End of _basepeer interface.
240
239
241 # Begin of _basewirecommands interface.
240 # Begin of _basewirecommands interface.
242
241
243 def branchmap(self):
242 def branchmap(self):
244 return self._repo.branchmap()
243 return self._repo.branchmap()
245
244
246 def capabilities(self):
245 def capabilities(self):
247 return self._caps
246 return self._caps
248
247
249 def clonebundles(self):
248 def clonebundles(self):
250 return self._repo.tryread('clonebundles.manifest')
249 return self._repo.tryread('clonebundles.manifest')
251
250
252 def debugwireargs(self, one, two, three=None, four=None, five=None):
251 def debugwireargs(self, one, two, three=None, four=None, five=None):
253 """Used to test argument passing over the wire"""
252 """Used to test argument passing over the wire"""
254 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
253 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
255 pycompat.bytestr(four),
254 pycompat.bytestr(four),
256 pycompat.bytestr(five))
255 pycompat.bytestr(five))
257
256
258 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
257 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
259 **kwargs):
258 **kwargs):
260 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
259 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
261 common=common, bundlecaps=bundlecaps,
260 common=common, bundlecaps=bundlecaps,
262 **kwargs)[1]
261 **kwargs)[1]
263 cb = util.chunkbuffer(chunks)
262 cb = util.chunkbuffer(chunks)
264
263
265 if exchange.bundle2requested(bundlecaps):
264 if exchange.bundle2requested(bundlecaps):
266 # When requesting a bundle2, getbundle returns a stream to make the
265 # When requesting a bundle2, getbundle returns a stream to make the
267 # wire level function happier. We need to build a proper object
266 # wire level function happier. We need to build a proper object
268 # from it in local peer.
267 # from it in local peer.
269 return bundle2.getunbundler(self.ui, cb)
268 return bundle2.getunbundler(self.ui, cb)
270 else:
269 else:
271 return changegroup.getunbundler('01', cb, None)
270 return changegroup.getunbundler('01', cb, None)
272
271
273 def heads(self):
272 def heads(self):
274 return self._repo.heads()
273 return self._repo.heads()
275
274
276 def known(self, nodes):
275 def known(self, nodes):
277 return self._repo.known(nodes)
276 return self._repo.known(nodes)
278
277
279 def listkeys(self, namespace):
278 def listkeys(self, namespace):
280 return self._repo.listkeys(namespace)
279 return self._repo.listkeys(namespace)
281
280
282 def lookup(self, key):
281 def lookup(self, key):
283 return self._repo.lookup(key)
282 return self._repo.lookup(key)
284
283
285 def pushkey(self, namespace, key, old, new):
284 def pushkey(self, namespace, key, old, new):
286 return self._repo.pushkey(namespace, key, old, new)
285 return self._repo.pushkey(namespace, key, old, new)
287
286
288 def stream_out(self):
287 def stream_out(self):
289 raise error.Abort(_('cannot perform stream clone against local '
288 raise error.Abort(_('cannot perform stream clone against local '
290 'peer'))
289 'peer'))
291
290
292 def unbundle(self, bundle, heads, url):
291 def unbundle(self, bundle, heads, url):
293 """apply a bundle on a repo
292 """apply a bundle on a repo
294
293
295 This function handles the repo locking itself."""
294 This function handles the repo locking itself."""
296 try:
295 try:
297 try:
296 try:
298 bundle = exchange.readbundle(self.ui, bundle, None)
297 bundle = exchange.readbundle(self.ui, bundle, None)
299 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
298 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
300 if util.safehasattr(ret, 'getchunks'):
299 if util.safehasattr(ret, 'getchunks'):
301 # This is a bundle20 object, turn it into an unbundler.
300 # This is a bundle20 object, turn it into an unbundler.
302 # This little dance should be dropped eventually when the
301 # This little dance should be dropped eventually when the
303 # API is finally improved.
302 # API is finally improved.
304 stream = util.chunkbuffer(ret.getchunks())
303 stream = util.chunkbuffer(ret.getchunks())
305 ret = bundle2.getunbundler(self.ui, stream)
304 ret = bundle2.getunbundler(self.ui, stream)
306 return ret
305 return ret
307 except Exception as exc:
306 except Exception as exc:
308 # If the exception contains output salvaged from a bundle2
307 # If the exception contains output salvaged from a bundle2
309 # reply, we need to make sure it is printed before continuing
308 # reply, we need to make sure it is printed before continuing
310 # to fail. So we build a bundle2 with such output and consume
309 # to fail. So we build a bundle2 with such output and consume
311 # it directly.
310 # it directly.
312 #
311 #
313 # This is not very elegant but allows a "simple" solution for
312 # This is not very elegant but allows a "simple" solution for
314 # issue4594
313 # issue4594
315 output = getattr(exc, '_bundle2salvagedoutput', ())
314 output = getattr(exc, '_bundle2salvagedoutput', ())
316 if output:
315 if output:
317 bundler = bundle2.bundle20(self._repo.ui)
316 bundler = bundle2.bundle20(self._repo.ui)
318 for out in output:
317 for out in output:
319 bundler.addpart(out)
318 bundler.addpart(out)
320 stream = util.chunkbuffer(bundler.getchunks())
319 stream = util.chunkbuffer(bundler.getchunks())
321 b = bundle2.getunbundler(self.ui, stream)
320 b = bundle2.getunbundler(self.ui, stream)
322 bundle2.processbundle(self._repo, b)
321 bundle2.processbundle(self._repo, b)
323 raise
322 raise
324 except error.PushRaced as exc:
323 except error.PushRaced as exc:
325 raise error.ResponseError(_('push failed:'),
324 raise error.ResponseError(_('push failed:'),
326 stringutil.forcebytestr(exc))
325 stringutil.forcebytestr(exc))
327
326
328 # End of _basewirecommands interface.
327 # End of _basewirecommands interface.
329
328
330 # Begin of peer interface.
329 # Begin of peer interface.
331
330
332 def commandexecutor(self):
331 def commandexecutor(self):
333 return localcommandexecutor(self)
332 return localcommandexecutor(self)
334
333
335 # End of peer interface.
334 # End of peer interface.
336
335
337 @interfaceutil.implementer(repository.ipeerlegacycommands)
336 @interfaceutil.implementer(repository.ipeerlegacycommands)
338 class locallegacypeer(localpeer):
337 class locallegacypeer(localpeer):
339 '''peer extension which implements legacy methods too; used for tests with
338 '''peer extension which implements legacy methods too; used for tests with
340 restricted capabilities'''
339 restricted capabilities'''
341
340
342 def __init__(self, repo):
341 def __init__(self, repo):
343 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
342 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
344
343
345 # Begin of baselegacywirecommands interface.
344 # Begin of baselegacywirecommands interface.
346
345
347 def between(self, pairs):
346 def between(self, pairs):
348 return self._repo.between(pairs)
347 return self._repo.between(pairs)
349
348
350 def branches(self, nodes):
349 def branches(self, nodes):
351 return self._repo.branches(nodes)
350 return self._repo.branches(nodes)
352
351
353 def changegroup(self, nodes, source):
352 def changegroup(self, nodes, source):
354 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
353 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
355 missingheads=self._repo.heads())
354 missingheads=self._repo.heads())
356 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
355 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
357
356
358 def changegroupsubset(self, bases, heads, source):
357 def changegroupsubset(self, bases, heads, source):
359 outgoing = discovery.outgoing(self._repo, missingroots=bases,
358 outgoing = discovery.outgoing(self._repo, missingroots=bases,
360 missingheads=heads)
359 missingheads=heads)
361 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
360 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
362
361
363 # End of baselegacywirecommands interface.
362 # End of baselegacywirecommands interface.
364
363
365 # Increment the sub-version when the revlog v2 format changes to lock out old
364 # Increment the sub-version when the revlog v2 format changes to lock out old
366 # clients.
365 # clients.
367 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
366 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
368
367
369 # A repository with the sparserevlog feature will have delta chains that
368 # A repository with the sparserevlog feature will have delta chains that
370 # can spread over a larger span. Sparse reading cuts these large spans into
369 # can spread over a larger span. Sparse reading cuts these large spans into
371 # pieces, so that each piece isn't too big.
370 # pieces, so that each piece isn't too big.
372 # Without the sparserevlog capability, reading from the repository could use
371 # Without the sparserevlog capability, reading from the repository could use
373 # huge amounts of memory, because the whole span would be read at once,
372 # huge amounts of memory, because the whole span would be read at once,
374 # including all the intermediate revisions that aren't pertinent for the chain.
373 # including all the intermediate revisions that aren't pertinent for the chain.
375 # This is why once a repository has enabled sparse-read, it becomes required.
374 # This is why once a repository has enabled sparse-read, it becomes required.
376 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
375 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
377
376
378 # Functions receiving (ui, features) that extensions can register to impact
377 # Functions receiving (ui, features) that extensions can register to impact
379 # the ability to load repositories with custom requirements. Only
378 # the ability to load repositories with custom requirements. Only
380 # functions defined in loaded extensions are called.
379 # functions defined in loaded extensions are called.
381 #
380 #
382 # The function receives a set of requirement strings that the repository
381 # The function receives a set of requirement strings that the repository
383 # is capable of opening. Functions will typically add elements to the
382 # is capable of opening. Functions will typically add elements to the
384 # set to reflect that the extension knows how to handle that requirements.
383 # set to reflect that the extension knows how to handle that requirements.
385 featuresetupfuncs = set()
384 featuresetupfuncs = set()
386
385
387 def makelocalrepository(baseui, path, intents=None):
386 def makelocalrepository(baseui, path, intents=None):
388 """Create a local repository object.
387 """Create a local repository object.
389
388
390 Given arguments needed to construct a local repository, this function
389 Given arguments needed to construct a local repository, this function
391 performs various early repository loading functionality (such as
390 performs various early repository loading functionality (such as
392 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
391 reading the ``.hg/requires`` and ``.hg/hgrc`` files), validates that
393 the repository can be opened, derives a type suitable for representing
392 the repository can be opened, derives a type suitable for representing
394 that repository, and returns an instance of it.
393 that repository, and returns an instance of it.
395
394
396 The returned object conforms to the ``repository.completelocalrepository``
395 The returned object conforms to the ``repository.completelocalrepository``
397 interface.
396 interface.
398
397
399 The repository type is derived by calling a series of factory functions
398 The repository type is derived by calling a series of factory functions
400 for each aspect/interface of the final repository. These are defined by
399 for each aspect/interface of the final repository. These are defined by
401 ``REPO_INTERFACES``.
400 ``REPO_INTERFACES``.
402
401
403 Each factory function is called to produce a type implementing a specific
402 Each factory function is called to produce a type implementing a specific
404 interface. The cumulative list of returned types will be combined into a
403 interface. The cumulative list of returned types will be combined into a
405 new type and that type will be instantiated to represent the local
404 new type and that type will be instantiated to represent the local
406 repository.
405 repository.
407
406
408 The factory functions each receive various state that may be consulted
407 The factory functions each receive various state that may be consulted
409 as part of deriving a type.
408 as part of deriving a type.
410
409
411 Extensions should wrap these factory functions to customize repository type
410 Extensions should wrap these factory functions to customize repository type
412 creation. Note that an extension's wrapped function may be called even if
411 creation. Note that an extension's wrapped function may be called even if
413 that extension is not loaded for the repo being constructed. Extensions
412 that extension is not loaded for the repo being constructed. Extensions
414 should check if their ``__name__`` appears in the
413 should check if their ``__name__`` appears in the
415 ``extensionmodulenames`` set passed to the factory function and no-op if
414 ``extensionmodulenames`` set passed to the factory function and no-op if
416 not.
415 not.
417 """
416 """
418 ui = baseui.copy()
417 ui = baseui.copy()
419 # Prevent copying repo configuration.
418 # Prevent copying repo configuration.
420 ui.copy = baseui.copy
419 ui.copy = baseui.copy
421
420
422 # Working directory VFS rooted at repository root.
421 # Working directory VFS rooted at repository root.
423 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
422 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
424
423
425 # Main VFS for .hg/ directory.
424 # Main VFS for .hg/ directory.
426 hgpath = wdirvfs.join(b'.hg')
425 hgpath = wdirvfs.join(b'.hg')
427 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
426 hgvfs = vfsmod.vfs(hgpath, cacheaudited=True)
428
427
429 # The .hg/ path should exist and should be a directory. All other
428 # The .hg/ path should exist and should be a directory. All other
430 # cases are errors.
429 # cases are errors.
431 if not hgvfs.isdir():
430 if not hgvfs.isdir():
432 try:
431 try:
433 hgvfs.stat()
432 hgvfs.stat()
434 except OSError as e:
433 except OSError as e:
435 if e.errno != errno.ENOENT:
434 if e.errno != errno.ENOENT:
436 raise
435 raise
437
436
438 raise error.RepoError(_(b'repository %s not found') % path)
437 raise error.RepoError(_(b'repository %s not found') % path)
439
438
440 # .hg/requires file contains a newline-delimited list of
439 # .hg/requires file contains a newline-delimited list of
441 # features/capabilities the opener (us) must have in order to use
440 # features/capabilities the opener (us) must have in order to use
442 # the repository. This file was introduced in Mercurial 0.9.2,
441 # the repository. This file was introduced in Mercurial 0.9.2,
443 # which means very old repositories may not have one. We assume
442 # which means very old repositories may not have one. We assume
444 # a missing file translates to no requirements.
443 # a missing file translates to no requirements.
445 try:
444 try:
446 requirements = set(hgvfs.read(b'requires').splitlines())
445 requirements = set(hgvfs.read(b'requires').splitlines())
447 except IOError as e:
446 except IOError as e:
448 if e.errno != errno.ENOENT:
447 if e.errno != errno.ENOENT:
449 raise
448 raise
450 requirements = set()
449 requirements = set()
451
450
452 # The .hg/hgrc file may load extensions or contain config options
451 # The .hg/hgrc file may load extensions or contain config options
453 # that influence repository construction. Attempt to load it and
452 # that influence repository construction. Attempt to load it and
454 # process any new extensions that it may have pulled in.
453 # process any new extensions that it may have pulled in.
455 try:
454 try:
456 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
455 ui.readconfig(hgvfs.join(b'hgrc'), root=wdirvfs.base)
457 # Run this before extensions.loadall() so extensions can be
456 # Run this before extensions.loadall() so extensions can be
458 # automatically enabled.
457 # automatically enabled.
459 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
458 afterhgrcload(ui, wdirvfs, hgvfs, requirements)
460 except IOError:
459 except IOError:
461 pass
460 pass
462 else:
461 else:
463 extensions.loadall(ui)
462 extensions.loadall(ui)
464
463
465 # Set of module names of extensions loaded for this repository.
464 # Set of module names of extensions loaded for this repository.
466 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
465 extensionmodulenames = {m.__name__ for n, m in extensions.extensions(ui)}
467
466
468 supportedrequirements = gathersupportedrequirements(ui)
467 supportedrequirements = gathersupportedrequirements(ui)
469
468
470 # We first validate the requirements are known.
469 # We first validate the requirements are known.
471 ensurerequirementsrecognized(requirements, supportedrequirements)
470 ensurerequirementsrecognized(requirements, supportedrequirements)
472
471
473 # Then we validate that the known set is reasonable to use together.
472 # Then we validate that the known set is reasonable to use together.
474 ensurerequirementscompatible(ui, requirements)
473 ensurerequirementscompatible(ui, requirements)
475
474
476 # TODO there are unhandled edge cases related to opening repositories with
475 # TODO there are unhandled edge cases related to opening repositories with
477 # shared storage. If storage is shared, we should also test for requirements
476 # shared storage. If storage is shared, we should also test for requirements
478 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
477 # compatibility in the pointed-to repo. This entails loading the .hg/hgrc in
479 # that repo, as that repo may load extensions needed to open it. This is a
478 # that repo, as that repo may load extensions needed to open it. This is a
480 # bit complicated because we don't want the other hgrc to overwrite settings
479 # bit complicated because we don't want the other hgrc to overwrite settings
481 # in this hgrc.
480 # in this hgrc.
482 #
481 #
483 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
482 # This bug is somewhat mitigated by the fact that we copy the .hg/requires
484 # file when sharing repos. But if a requirement is added after the share is
483 # file when sharing repos. But if a requirement is added after the share is
485 # performed, thereby introducing a new requirement for the opener, we may
484 # performed, thereby introducing a new requirement for the opener, we may
486 # will not see that and could encounter a run-time error interacting with
485 # will not see that and could encounter a run-time error interacting with
487 # that shared store since it has an unknown-to-us requirement.
486 # that shared store since it has an unknown-to-us requirement.
488
487
489 # At this point, we know we should be capable of opening the repository.
488 # At this point, we know we should be capable of opening the repository.
490 # Now get on with doing that.
489 # Now get on with doing that.
491
490
492 features = set()
491 features = set()
493
492
494 # The "store" part of the repository holds versioned data. How it is
493 # The "store" part of the repository holds versioned data. How it is
495 # accessed is determined by various requirements. The ``shared`` or
494 # accessed is determined by various requirements. The ``shared`` or
496 # ``relshared`` requirements indicate the store lives in the path contained
495 # ``relshared`` requirements indicate the store lives in the path contained
497 # in the ``.hg/sharedpath`` file. This is an absolute path for
496 # in the ``.hg/sharedpath`` file. This is an absolute path for
498 # ``shared`` and relative to ``.hg/`` for ``relshared``.
497 # ``shared`` and relative to ``.hg/`` for ``relshared``.
499 if b'shared' in requirements or b'relshared' in requirements:
498 if b'shared' in requirements or b'relshared' in requirements:
500 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
499 sharedpath = hgvfs.read(b'sharedpath').rstrip(b'\n')
501 if b'relshared' in requirements:
500 if b'relshared' in requirements:
502 sharedpath = hgvfs.join(sharedpath)
501 sharedpath = hgvfs.join(sharedpath)
503
502
504 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
503 sharedvfs = vfsmod.vfs(sharedpath, realpath=True)
505
504
506 if not sharedvfs.exists():
505 if not sharedvfs.exists():
507 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
506 raise error.RepoError(_(b'.hg/sharedpath points to nonexistent '
508 b'directory %s') % sharedvfs.base)
507 b'directory %s') % sharedvfs.base)
509
508
510 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
509 features.add(repository.REPO_FEATURE_SHARED_STORAGE)
511
510
512 storebasepath = sharedvfs.base
511 storebasepath = sharedvfs.base
513 cachepath = sharedvfs.join(b'cache')
512 cachepath = sharedvfs.join(b'cache')
514 else:
513 else:
515 storebasepath = hgvfs.base
514 storebasepath = hgvfs.base
516 cachepath = hgvfs.join(b'cache')
515 cachepath = hgvfs.join(b'cache')
517
516
518 # The store has changed over time and the exact layout is dictated by
517 # The store has changed over time and the exact layout is dictated by
519 # requirements. The store interface abstracts differences across all
518 # requirements. The store interface abstracts differences across all
520 # of them.
519 # of them.
521 store = makestore(requirements, storebasepath,
520 store = makestore(requirements, storebasepath,
522 lambda base: vfsmod.vfs(base, cacheaudited=True))
521 lambda base: vfsmod.vfs(base, cacheaudited=True))
523 hgvfs.createmode = store.createmode
522 hgvfs.createmode = store.createmode
524
523
525 storevfs = store.vfs
524 storevfs = store.vfs
526 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
525 storevfs.options = resolvestorevfsoptions(ui, requirements, features)
527
526
528 # The cache vfs is used to manage cache files.
527 # The cache vfs is used to manage cache files.
529 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
528 cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
530 cachevfs.createmode = store.createmode
529 cachevfs.createmode = store.createmode
531
530
532 # Now resolve the type for the repository object. We do this by repeatedly
531 # Now resolve the type for the repository object. We do this by repeatedly
533 # calling a factory function to produces types for specific aspects of the
532 # calling a factory function to produces types for specific aspects of the
534 # repo's operation. The aggregate returned types are used as base classes
533 # repo's operation. The aggregate returned types are used as base classes
535 # for a dynamically-derived type, which will represent our new repository.
534 # for a dynamically-derived type, which will represent our new repository.
536
535
537 bases = []
536 bases = []
538 extrastate = {}
537 extrastate = {}
539
538
540 for iface, fn in REPO_INTERFACES:
539 for iface, fn in REPO_INTERFACES:
541 # We pass all potentially useful state to give extensions tons of
540 # We pass all potentially useful state to give extensions tons of
542 # flexibility.
541 # flexibility.
543 typ = fn()(ui=ui,
542 typ = fn()(ui=ui,
544 intents=intents,
543 intents=intents,
545 requirements=requirements,
544 requirements=requirements,
546 features=features,
545 features=features,
547 wdirvfs=wdirvfs,
546 wdirvfs=wdirvfs,
548 hgvfs=hgvfs,
547 hgvfs=hgvfs,
549 store=store,
548 store=store,
550 storevfs=storevfs,
549 storevfs=storevfs,
551 storeoptions=storevfs.options,
550 storeoptions=storevfs.options,
552 cachevfs=cachevfs,
551 cachevfs=cachevfs,
553 extensionmodulenames=extensionmodulenames,
552 extensionmodulenames=extensionmodulenames,
554 extrastate=extrastate,
553 extrastate=extrastate,
555 baseclasses=bases)
554 baseclasses=bases)
556
555
557 if not isinstance(typ, type):
556 if not isinstance(typ, type):
558 raise error.ProgrammingError('unable to construct type for %s' %
557 raise error.ProgrammingError('unable to construct type for %s' %
559 iface)
558 iface)
560
559
561 bases.append(typ)
560 bases.append(typ)
562
561
563 # type() allows you to use characters in type names that wouldn't be
562 # type() allows you to use characters in type names that wouldn't be
564 # recognized as Python symbols in source code. We abuse that to add
563 # recognized as Python symbols in source code. We abuse that to add
565 # rich information about our constructed repo.
564 # rich information about our constructed repo.
566 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
565 name = pycompat.sysstr(b'derivedrepo:%s<%s>' % (
567 wdirvfs.base,
566 wdirvfs.base,
568 b','.join(sorted(requirements))))
567 b','.join(sorted(requirements))))
569
568
570 cls = type(name, tuple(bases), {})
569 cls = type(name, tuple(bases), {})
571
570
572 return cls(
571 return cls(
573 baseui=baseui,
572 baseui=baseui,
574 ui=ui,
573 ui=ui,
575 origroot=path,
574 origroot=path,
576 wdirvfs=wdirvfs,
575 wdirvfs=wdirvfs,
577 hgvfs=hgvfs,
576 hgvfs=hgvfs,
578 requirements=requirements,
577 requirements=requirements,
579 supportedrequirements=supportedrequirements,
578 supportedrequirements=supportedrequirements,
580 sharedpath=storebasepath,
579 sharedpath=storebasepath,
581 store=store,
580 store=store,
582 cachevfs=cachevfs,
581 cachevfs=cachevfs,
583 features=features,
582 features=features,
584 intents=intents)
583 intents=intents)
585
584
586 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
585 def afterhgrcload(ui, wdirvfs, hgvfs, requirements):
587 """Perform additional actions after .hg/hgrc is loaded.
586 """Perform additional actions after .hg/hgrc is loaded.
588
587
589 This function is called during repository loading immediately after
588 This function is called during repository loading immediately after
590 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
589 the .hg/hgrc file is loaded and before per-repo extensions are loaded.
591
590
592 The function can be used to validate configs, automatically add
591 The function can be used to validate configs, automatically add
593 options (including extensions) based on requirements, etc.
592 options (including extensions) based on requirements, etc.
594 """
593 """
595
594
596 # Map of requirements to list of extensions to load automatically when
595 # Map of requirements to list of extensions to load automatically when
597 # requirement is present.
596 # requirement is present.
598 autoextensions = {
597 autoextensions = {
599 b'largefiles': [b'largefiles'],
598 b'largefiles': [b'largefiles'],
600 b'lfs': [b'lfs'],
599 b'lfs': [b'lfs'],
601 }
600 }
602
601
603 for requirement, names in sorted(autoextensions.items()):
602 for requirement, names in sorted(autoextensions.items()):
604 if requirement not in requirements:
603 if requirement not in requirements:
605 continue
604 continue
606
605
607 for name in names:
606 for name in names:
608 if not ui.hasconfig(b'extensions', name):
607 if not ui.hasconfig(b'extensions', name):
609 ui.setconfig(b'extensions', name, b'', source='autoload')
608 ui.setconfig(b'extensions', name, b'', source='autoload')
610
609
611 def gathersupportedrequirements(ui):
610 def gathersupportedrequirements(ui):
612 """Determine the complete set of recognized requirements."""
611 """Determine the complete set of recognized requirements."""
613 # Start with all requirements supported by this file.
612 # Start with all requirements supported by this file.
614 supported = set(localrepository._basesupported)
613 supported = set(localrepository._basesupported)
615
614
616 # Execute ``featuresetupfuncs`` entries if they belong to an extension
615 # Execute ``featuresetupfuncs`` entries if they belong to an extension
617 # relevant to this ui instance.
616 # relevant to this ui instance.
618 modules = {m.__name__ for n, m in extensions.extensions(ui)}
617 modules = {m.__name__ for n, m in extensions.extensions(ui)}
619
618
620 for fn in featuresetupfuncs:
619 for fn in featuresetupfuncs:
621 if fn.__module__ in modules:
620 if fn.__module__ in modules:
622 fn(ui, supported)
621 fn(ui, supported)
623
622
624 # Add derived requirements from registered compression engines.
623 # Add derived requirements from registered compression engines.
625 for name in util.compengines:
624 for name in util.compengines:
626 engine = util.compengines[name]
625 engine = util.compengines[name]
627 if engine.revlogheader():
626 if engine.revlogheader():
628 supported.add(b'exp-compression-%s' % name)
627 supported.add(b'exp-compression-%s' % name)
629
628
630 return supported
629 return supported
631
630
632 def ensurerequirementsrecognized(requirements, supported):
631 def ensurerequirementsrecognized(requirements, supported):
633 """Validate that a set of local requirements is recognized.
632 """Validate that a set of local requirements is recognized.
634
633
635 Receives a set of requirements. Raises an ``error.RepoError`` if there
634 Receives a set of requirements. Raises an ``error.RepoError`` if there
636 exists any requirement in that set that currently loaded code doesn't
635 exists any requirement in that set that currently loaded code doesn't
637 recognize.
636 recognize.
638
637
639 Returns a set of supported requirements.
638 Returns a set of supported requirements.
640 """
639 """
641 missing = set()
640 missing = set()
642
641
643 for requirement in requirements:
642 for requirement in requirements:
644 if requirement in supported:
643 if requirement in supported:
645 continue
644 continue
646
645
647 if not requirement or not requirement[0:1].isalnum():
646 if not requirement or not requirement[0:1].isalnum():
648 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
647 raise error.RequirementError(_(b'.hg/requires file is corrupt'))
649
648
650 missing.add(requirement)
649 missing.add(requirement)
651
650
652 if missing:
651 if missing:
653 raise error.RequirementError(
652 raise error.RequirementError(
654 _(b'repository requires features unknown to this Mercurial: %s') %
653 _(b'repository requires features unknown to this Mercurial: %s') %
655 b' '.join(sorted(missing)),
654 b' '.join(sorted(missing)),
656 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
655 hint=_(b'see https://mercurial-scm.org/wiki/MissingRequirement '
657 b'for more information'))
656 b'for more information'))
658
657
659 def ensurerequirementscompatible(ui, requirements):
658 def ensurerequirementscompatible(ui, requirements):
660 """Validates that a set of recognized requirements is mutually compatible.
659 """Validates that a set of recognized requirements is mutually compatible.
661
660
662 Some requirements may not be compatible with others or require
661 Some requirements may not be compatible with others or require
663 config options that aren't enabled. This function is called during
662 config options that aren't enabled. This function is called during
664 repository opening to ensure that the set of requirements needed
663 repository opening to ensure that the set of requirements needed
665 to open a repository is sane and compatible with config options.
664 to open a repository is sane and compatible with config options.
666
665
667 Extensions can monkeypatch this function to perform additional
666 Extensions can monkeypatch this function to perform additional
668 checking.
667 checking.
669
668
670 ``error.RepoError`` should be raised on failure.
669 ``error.RepoError`` should be raised on failure.
671 """
670 """
672 if b'exp-sparse' in requirements and not sparse.enabled:
671 if b'exp-sparse' in requirements and not sparse.enabled:
673 raise error.RepoError(_(b'repository is using sparse feature but '
672 raise error.RepoError(_(b'repository is using sparse feature but '
674 b'sparse is not enabled; enable the '
673 b'sparse is not enabled; enable the '
675 b'"sparse" extensions to access'))
674 b'"sparse" extensions to access'))
676
675
677 def makestore(requirements, path, vfstype):
676 def makestore(requirements, path, vfstype):
678 """Construct a storage object for a repository."""
677 """Construct a storage object for a repository."""
679 if b'store' in requirements:
678 if b'store' in requirements:
680 if b'fncache' in requirements:
679 if b'fncache' in requirements:
681 return storemod.fncachestore(path, vfstype,
680 return storemod.fncachestore(path, vfstype,
682 b'dotencode' in requirements)
681 b'dotencode' in requirements)
683
682
684 return storemod.encodedstore(path, vfstype)
683 return storemod.encodedstore(path, vfstype)
685
684
686 return storemod.basicstore(path, vfstype)
685 return storemod.basicstore(path, vfstype)
687
686
688 def resolvestorevfsoptions(ui, requirements, features):
687 def resolvestorevfsoptions(ui, requirements, features):
689 """Resolve the options to pass to the store vfs opener.
688 """Resolve the options to pass to the store vfs opener.
690
689
691 The returned dict is used to influence behavior of the storage layer.
690 The returned dict is used to influence behavior of the storage layer.
692 """
691 """
693 options = {}
692 options = {}
694
693
695 if b'treemanifest' in requirements:
694 if b'treemanifest' in requirements:
696 options[b'treemanifest'] = True
695 options[b'treemanifest'] = True
697
696
698 # experimental config: format.manifestcachesize
697 # experimental config: format.manifestcachesize
699 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
698 manifestcachesize = ui.configint(b'format', b'manifestcachesize')
700 if manifestcachesize is not None:
699 if manifestcachesize is not None:
701 options[b'manifestcachesize'] = manifestcachesize
700 options[b'manifestcachesize'] = manifestcachesize
702
701
703 # In the absence of another requirement superseding a revlog-related
702 # In the absence of another requirement superseding a revlog-related
704 # requirement, we have to assume the repo is using revlog version 0.
703 # requirement, we have to assume the repo is using revlog version 0.
705 # This revlog format is super old and we don't bother trying to parse
704 # This revlog format is super old and we don't bother trying to parse
706 # opener options for it because those options wouldn't do anything
705 # opener options for it because those options wouldn't do anything
707 # meaningful on such old repos.
706 # meaningful on such old repos.
708 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
707 if b'revlogv1' in requirements or REVLOGV2_REQUIREMENT in requirements:
709 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
708 options.update(resolverevlogstorevfsoptions(ui, requirements, features))
710
709
711 return options
710 return options
712
711
713 def resolverevlogstorevfsoptions(ui, requirements, features):
712 def resolverevlogstorevfsoptions(ui, requirements, features):
714 """Resolve opener options specific to revlogs."""
713 """Resolve opener options specific to revlogs."""
715
714
716 options = {}
715 options = {}
717 options[b'flagprocessors'] = {}
716 options[b'flagprocessors'] = {}
718
717
719 if b'revlogv1' in requirements:
718 if b'revlogv1' in requirements:
720 options[b'revlogv1'] = True
719 options[b'revlogv1'] = True
721 if REVLOGV2_REQUIREMENT in requirements:
720 if REVLOGV2_REQUIREMENT in requirements:
722 options[b'revlogv2'] = True
721 options[b'revlogv2'] = True
723
722
724 if b'generaldelta' in requirements:
723 if b'generaldelta' in requirements:
725 options[b'generaldelta'] = True
724 options[b'generaldelta'] = True
726
725
727 # experimental config: format.chunkcachesize
726 # experimental config: format.chunkcachesize
728 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
727 chunkcachesize = ui.configint(b'format', b'chunkcachesize')
729 if chunkcachesize is not None:
728 if chunkcachesize is not None:
730 options[b'chunkcachesize'] = chunkcachesize
729 options[b'chunkcachesize'] = chunkcachesize
731
730
732 deltabothparents = ui.configbool(b'storage',
731 deltabothparents = ui.configbool(b'storage',
733 b'revlog.optimize-delta-parent-choice')
732 b'revlog.optimize-delta-parent-choice')
734 options[b'deltabothparents'] = deltabothparents
733 options[b'deltabothparents'] = deltabothparents
735
734
736 options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
735 options[b'lazydeltabase'] = not scmutil.gddeltaconfig(ui)
737
736
738 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
737 chainspan = ui.configbytes(b'experimental', b'maxdeltachainspan')
739 if 0 <= chainspan:
738 if 0 <= chainspan:
740 options[b'maxdeltachainspan'] = chainspan
739 options[b'maxdeltachainspan'] = chainspan
741
740
742 mmapindexthreshold = ui.configbytes(b'experimental',
741 mmapindexthreshold = ui.configbytes(b'experimental',
743 b'mmapindexthreshold')
742 b'mmapindexthreshold')
744 if mmapindexthreshold is not None:
743 if mmapindexthreshold is not None:
745 options[b'mmapindexthreshold'] = mmapindexthreshold
744 options[b'mmapindexthreshold'] = mmapindexthreshold
746
745
747 withsparseread = ui.configbool(b'experimental', b'sparse-read')
746 withsparseread = ui.configbool(b'experimental', b'sparse-read')
748 srdensitythres = float(ui.config(b'experimental',
747 srdensitythres = float(ui.config(b'experimental',
749 b'sparse-read.density-threshold'))
748 b'sparse-read.density-threshold'))
750 srmingapsize = ui.configbytes(b'experimental',
749 srmingapsize = ui.configbytes(b'experimental',
751 b'sparse-read.min-gap-size')
750 b'sparse-read.min-gap-size')
752 options[b'with-sparse-read'] = withsparseread
751 options[b'with-sparse-read'] = withsparseread
753 options[b'sparse-read-density-threshold'] = srdensitythres
752 options[b'sparse-read-density-threshold'] = srdensitythres
754 options[b'sparse-read-min-gap-size'] = srmingapsize
753 options[b'sparse-read-min-gap-size'] = srmingapsize
755
754
756 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
755 sparserevlog = SPARSEREVLOG_REQUIREMENT in requirements
757 options[b'sparse-revlog'] = sparserevlog
756 options[b'sparse-revlog'] = sparserevlog
758 if sparserevlog:
757 if sparserevlog:
759 options[b'generaldelta'] = True
758 options[b'generaldelta'] = True
760
759
761 maxchainlen = None
760 maxchainlen = None
762 if sparserevlog:
761 if sparserevlog:
763 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
762 maxchainlen = revlogconst.SPARSE_REVLOG_MAX_CHAIN_LENGTH
764 # experimental config: format.maxchainlen
763 # experimental config: format.maxchainlen
765 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
764 maxchainlen = ui.configint(b'format', b'maxchainlen', maxchainlen)
766 if maxchainlen is not None:
765 if maxchainlen is not None:
767 options[b'maxchainlen'] = maxchainlen
766 options[b'maxchainlen'] = maxchainlen
768
767
769 for r in requirements:
768 for r in requirements:
770 if r.startswith(b'exp-compression-'):
769 if r.startswith(b'exp-compression-'):
771 options[b'compengine'] = r[len(b'exp-compression-'):]
770 options[b'compengine'] = r[len(b'exp-compression-'):]
772
771
773 if repository.NARROW_REQUIREMENT in requirements:
772 if repository.NARROW_REQUIREMENT in requirements:
774 options[b'enableellipsis'] = True
773 options[b'enableellipsis'] = True
775
774
776 return options
775 return options
777
776
778 def makemain(**kwargs):
777 def makemain(**kwargs):
779 """Produce a type conforming to ``ilocalrepositorymain``."""
778 """Produce a type conforming to ``ilocalrepositorymain``."""
780 return localrepository
779 return localrepository
781
780
782 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
781 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
783 class revlogfilestorage(object):
782 class revlogfilestorage(object):
784 """File storage when using revlogs."""
783 """File storage when using revlogs."""
785
784
786 def file(self, path):
785 def file(self, path):
787 if path[0] == b'/':
786 if path[0] == b'/':
788 path = path[1:]
787 path = path[1:]
789
788
790 return filelog.filelog(self.svfs, path)
789 return filelog.filelog(self.svfs, path)
791
790
792 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
791 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
793 class revlognarrowfilestorage(object):
792 class revlognarrowfilestorage(object):
794 """File storage when using revlogs and narrow files."""
793 """File storage when using revlogs and narrow files."""
795
794
796 def file(self, path):
795 def file(self, path):
797 if path[0] == b'/':
796 if path[0] == b'/':
798 path = path[1:]
797 path = path[1:]
799
798
800 return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
799 return filelog.narrowfilelog(self.svfs, path, self.narrowmatch())
801
800
802 def makefilestorage(requirements, features, **kwargs):
801 def makefilestorage(requirements, features, **kwargs):
803 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
802 """Produce a type conforming to ``ilocalrepositoryfilestorage``."""
804 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
803 features.add(repository.REPO_FEATURE_REVLOG_FILE_STORAGE)
805 features.add(repository.REPO_FEATURE_STREAM_CLONE)
804 features.add(repository.REPO_FEATURE_STREAM_CLONE)
806
805
807 if repository.NARROW_REQUIREMENT in requirements:
806 if repository.NARROW_REQUIREMENT in requirements:
808 return revlognarrowfilestorage
807 return revlognarrowfilestorage
809 else:
808 else:
810 return revlogfilestorage
809 return revlogfilestorage
811
810
812 # List of repository interfaces and factory functions for them. Each
811 # List of repository interfaces and factory functions for them. Each
813 # will be called in order during ``makelocalrepository()`` to iteratively
812 # will be called in order during ``makelocalrepository()`` to iteratively
814 # derive the final type for a local repository instance. We capture the
813 # derive the final type for a local repository instance. We capture the
815 # function as a lambda so we don't hold a reference and the module-level
814 # function as a lambda so we don't hold a reference and the module-level
816 # functions can be wrapped.
815 # functions can be wrapped.
817 REPO_INTERFACES = [
816 REPO_INTERFACES = [
818 (repository.ilocalrepositorymain, lambda: makemain),
817 (repository.ilocalrepositorymain, lambda: makemain),
819 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
818 (repository.ilocalrepositoryfilestorage, lambda: makefilestorage),
820 ]
819 ]
821
820
822 @interfaceutil.implementer(repository.ilocalrepositorymain)
821 @interfaceutil.implementer(repository.ilocalrepositorymain)
823 class localrepository(object):
822 class localrepository(object):
824 """Main class for representing local repositories.
823 """Main class for representing local repositories.
825
824
826 All local repositories are instances of this class.
825 All local repositories are instances of this class.
827
826
828 Constructed on its own, instances of this class are not usable as
827 Constructed on its own, instances of this class are not usable as
829 repository objects. To obtain a usable repository object, call
828 repository objects. To obtain a usable repository object, call
830 ``hg.repository()``, ``localrepo.instance()``, or
829 ``hg.repository()``, ``localrepo.instance()``, or
831 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
830 ``localrepo.makelocalrepository()``. The latter is the lowest-level.
832 ``instance()`` adds support for creating new repositories.
831 ``instance()`` adds support for creating new repositories.
833 ``hg.repository()`` adds more extension integration, including calling
832 ``hg.repository()`` adds more extension integration, including calling
834 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
833 ``reposetup()``. Generally speaking, ``hg.repository()`` should be
835 used.
834 used.
836 """
835 """
837
836
838 # obsolete experimental requirements:
837 # obsolete experimental requirements:
839 # - manifestv2: An experimental new manifest format that allowed
838 # - manifestv2: An experimental new manifest format that allowed
840 # for stem compression of long paths. Experiment ended up not
839 # for stem compression of long paths. Experiment ended up not
841 # being successful (repository sizes went up due to worse delta
840 # being successful (repository sizes went up due to worse delta
842 # chains), and the code was deleted in 4.6.
841 # chains), and the code was deleted in 4.6.
843 supportedformats = {
842 supportedformats = {
844 'revlogv1',
843 'revlogv1',
845 'generaldelta',
844 'generaldelta',
846 'treemanifest',
845 'treemanifest',
847 REVLOGV2_REQUIREMENT,
846 REVLOGV2_REQUIREMENT,
848 SPARSEREVLOG_REQUIREMENT,
847 SPARSEREVLOG_REQUIREMENT,
849 }
848 }
850 _basesupported = supportedformats | {
849 _basesupported = supportedformats | {
851 'store',
850 'store',
852 'fncache',
851 'fncache',
853 'shared',
852 'shared',
854 'relshared',
853 'relshared',
855 'dotencode',
854 'dotencode',
856 'exp-sparse',
855 'exp-sparse',
857 'internal-phase'
856 'internal-phase'
858 }
857 }
859
858
860 # list of prefix for file which can be written without 'wlock'
859 # list of prefix for file which can be written without 'wlock'
861 # Extensions should extend this list when needed
860 # Extensions should extend this list when needed
862 _wlockfreeprefix = {
861 _wlockfreeprefix = {
863 # We migh consider requiring 'wlock' for the next
862 # We migh consider requiring 'wlock' for the next
864 # two, but pretty much all the existing code assume
863 # two, but pretty much all the existing code assume
865 # wlock is not needed so we keep them excluded for
864 # wlock is not needed so we keep them excluded for
866 # now.
865 # now.
867 'hgrc',
866 'hgrc',
868 'requires',
867 'requires',
869 # XXX cache is a complicatged business someone
868 # XXX cache is a complicatged business someone
870 # should investigate this in depth at some point
869 # should investigate this in depth at some point
871 'cache/',
870 'cache/',
872 # XXX shouldn't be dirstate covered by the wlock?
871 # XXX shouldn't be dirstate covered by the wlock?
873 'dirstate',
872 'dirstate',
874 # XXX bisect was still a bit too messy at the time
873 # XXX bisect was still a bit too messy at the time
875 # this changeset was introduced. Someone should fix
874 # this changeset was introduced. Someone should fix
876 # the remainig bit and drop this line
875 # the remainig bit and drop this line
877 'bisect.state',
876 'bisect.state',
878 }
877 }
879
878
880 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
879 def __init__(self, baseui, ui, origroot, wdirvfs, hgvfs, requirements,
881 supportedrequirements, sharedpath, store, cachevfs,
880 supportedrequirements, sharedpath, store, cachevfs,
882 features, intents=None):
881 features, intents=None):
883 """Create a new local repository instance.
882 """Create a new local repository instance.
884
883
885 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
884 Most callers should use ``hg.repository()``, ``localrepo.instance()``,
886 or ``localrepo.makelocalrepository()`` for obtaining a new repository
885 or ``localrepo.makelocalrepository()`` for obtaining a new repository
887 object.
886 object.
888
887
889 Arguments:
888 Arguments:
890
889
891 baseui
890 baseui
892 ``ui.ui`` instance that ``ui`` argument was based off of.
891 ``ui.ui`` instance that ``ui`` argument was based off of.
893
892
894 ui
893 ui
895 ``ui.ui`` instance for use by the repository.
894 ``ui.ui`` instance for use by the repository.
896
895
897 origroot
896 origroot
898 ``bytes`` path to working directory root of this repository.
897 ``bytes`` path to working directory root of this repository.
899
898
900 wdirvfs
899 wdirvfs
901 ``vfs.vfs`` rooted at the working directory.
900 ``vfs.vfs`` rooted at the working directory.
902
901
903 hgvfs
902 hgvfs
904 ``vfs.vfs`` rooted at .hg/
903 ``vfs.vfs`` rooted at .hg/
905
904
906 requirements
905 requirements
907 ``set`` of bytestrings representing repository opening requirements.
906 ``set`` of bytestrings representing repository opening requirements.
908
907
909 supportedrequirements
908 supportedrequirements
910 ``set`` of bytestrings representing repository requirements that we
909 ``set`` of bytestrings representing repository requirements that we
911 know how to open. May be a supetset of ``requirements``.
910 know how to open. May be a supetset of ``requirements``.
912
911
913 sharedpath
912 sharedpath
914 ``bytes`` Defining path to storage base directory. Points to a
913 ``bytes`` Defining path to storage base directory. Points to a
915 ``.hg/`` directory somewhere.
914 ``.hg/`` directory somewhere.
916
915
917 store
916 store
918 ``store.basicstore`` (or derived) instance providing access to
917 ``store.basicstore`` (or derived) instance providing access to
919 versioned storage.
918 versioned storage.
920
919
921 cachevfs
920 cachevfs
922 ``vfs.vfs`` used for cache files.
921 ``vfs.vfs`` used for cache files.
923
922
924 features
923 features
925 ``set`` of bytestrings defining features/capabilities of this
924 ``set`` of bytestrings defining features/capabilities of this
926 instance.
925 instance.
927
926
928 intents
927 intents
929 ``set`` of system strings indicating what this repo will be used
928 ``set`` of system strings indicating what this repo will be used
930 for.
929 for.
931 """
930 """
932 self.baseui = baseui
931 self.baseui = baseui
933 self.ui = ui
932 self.ui = ui
934 self.origroot = origroot
933 self.origroot = origroot
935 # vfs rooted at working directory.
934 # vfs rooted at working directory.
936 self.wvfs = wdirvfs
935 self.wvfs = wdirvfs
937 self.root = wdirvfs.base
936 self.root = wdirvfs.base
938 # vfs rooted at .hg/. Used to access most non-store paths.
937 # vfs rooted at .hg/. Used to access most non-store paths.
939 self.vfs = hgvfs
938 self.vfs = hgvfs
940 self.path = hgvfs.base
939 self.path = hgvfs.base
941 self.requirements = requirements
940 self.requirements = requirements
942 self.supported = supportedrequirements
941 self.supported = supportedrequirements
943 self.sharedpath = sharedpath
942 self.sharedpath = sharedpath
944 self.store = store
943 self.store = store
945 self.cachevfs = cachevfs
944 self.cachevfs = cachevfs
946 self.features = features
945 self.features = features
947
946
948 self.filtername = None
947 self.filtername = None
949
948
950 if (self.ui.configbool('devel', 'all-warnings') or
949 if (self.ui.configbool('devel', 'all-warnings') or
951 self.ui.configbool('devel', 'check-locks')):
950 self.ui.configbool('devel', 'check-locks')):
952 self.vfs.audit = self._getvfsward(self.vfs.audit)
951 self.vfs.audit = self._getvfsward(self.vfs.audit)
953 # A list of callback to shape the phase if no data were found.
952 # A list of callback to shape the phase if no data were found.
954 # Callback are in the form: func(repo, roots) --> processed root.
953 # Callback are in the form: func(repo, roots) --> processed root.
955 # This list it to be filled by extension during repo setup
954 # This list it to be filled by extension during repo setup
956 self._phasedefaults = []
955 self._phasedefaults = []
957
956
958 color.setup(self.ui)
957 color.setup(self.ui)
959
958
960 self.spath = self.store.path
959 self.spath = self.store.path
961 self.svfs = self.store.vfs
960 self.svfs = self.store.vfs
962 self.sjoin = self.store.join
961 self.sjoin = self.store.join
963 if (self.ui.configbool('devel', 'all-warnings') or
962 if (self.ui.configbool('devel', 'all-warnings') or
964 self.ui.configbool('devel', 'check-locks')):
963 self.ui.configbool('devel', 'check-locks')):
965 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
964 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
966 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
965 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
967 else: # standard vfs
966 else: # standard vfs
968 self.svfs.audit = self._getsvfsward(self.svfs.audit)
967 self.svfs.audit = self._getsvfsward(self.svfs.audit)
969
968
970 self._dirstatevalidatewarned = False
969 self._dirstatevalidatewarned = False
971
970
972 self._branchcaches = {}
971 self._branchcaches = {}
973 self._revbranchcache = None
972 self._revbranchcache = None
974 self._filterpats = {}
973 self._filterpats = {}
975 self._datafilters = {}
974 self._datafilters = {}
976 self._transref = self._lockref = self._wlockref = None
975 self._transref = self._lockref = self._wlockref = None
977
976
978 # A cache for various files under .hg/ that tracks file changes,
977 # A cache for various files under .hg/ that tracks file changes,
979 # (used by the filecache decorator)
978 # (used by the filecache decorator)
980 #
979 #
981 # Maps a property name to its util.filecacheentry
980 # Maps a property name to its util.filecacheentry
982 self._filecache = {}
981 self._filecache = {}
983
982
984 # hold sets of revision to be filtered
983 # hold sets of revision to be filtered
985 # should be cleared when something might have changed the filter value:
984 # should be cleared when something might have changed the filter value:
986 # - new changesets,
985 # - new changesets,
987 # - phase change,
986 # - phase change,
988 # - new obsolescence marker,
987 # - new obsolescence marker,
989 # - working directory parent change,
988 # - working directory parent change,
990 # - bookmark changes
989 # - bookmark changes
991 self.filteredrevcache = {}
990 self.filteredrevcache = {}
992
991
993 # post-dirstate-status hooks
992 # post-dirstate-status hooks
994 self._postdsstatus = []
993 self._postdsstatus = []
995
994
996 # generic mapping between names and nodes
995 # generic mapping between names and nodes
997 self.names = namespaces.namespaces()
996 self.names = namespaces.namespaces()
998
997
999 # Key to signature value.
998 # Key to signature value.
1000 self._sparsesignaturecache = {}
999 self._sparsesignaturecache = {}
1001 # Signature to cached matcher instance.
1000 # Signature to cached matcher instance.
1002 self._sparsematchercache = {}
1001 self._sparsematchercache = {}
1003
1002
1004 def _getvfsward(self, origfunc):
1003 def _getvfsward(self, origfunc):
1005 """build a ward for self.vfs"""
1004 """build a ward for self.vfs"""
1006 rref = weakref.ref(self)
1005 rref = weakref.ref(self)
1007 def checkvfs(path, mode=None):
1006 def checkvfs(path, mode=None):
1008 ret = origfunc(path, mode=mode)
1007 ret = origfunc(path, mode=mode)
1009 repo = rref()
1008 repo = rref()
1010 if (repo is None
1009 if (repo is None
1011 or not util.safehasattr(repo, '_wlockref')
1010 or not util.safehasattr(repo, '_wlockref')
1012 or not util.safehasattr(repo, '_lockref')):
1011 or not util.safehasattr(repo, '_lockref')):
1013 return
1012 return
1014 if mode in (None, 'r', 'rb'):
1013 if mode in (None, 'r', 'rb'):
1015 return
1014 return
1016 if path.startswith(repo.path):
1015 if path.startswith(repo.path):
1017 # truncate name relative to the repository (.hg)
1016 # truncate name relative to the repository (.hg)
1018 path = path[len(repo.path) + 1:]
1017 path = path[len(repo.path) + 1:]
1019 if path.startswith('cache/'):
1018 if path.startswith('cache/'):
1020 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1019 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
1021 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
1020 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
1022 if path.startswith('journal.'):
1021 if path.startswith('journal.'):
1023 # journal is covered by 'lock'
1022 # journal is covered by 'lock'
1024 if repo._currentlock(repo._lockref) is None:
1023 if repo._currentlock(repo._lockref) is None:
1025 repo.ui.develwarn('write with no lock: "%s"' % path,
1024 repo.ui.develwarn('write with no lock: "%s"' % path,
1026 stacklevel=2, config='check-locks')
1025 stacklevel=2, config='check-locks')
1027 elif repo._currentlock(repo._wlockref) is None:
1026 elif repo._currentlock(repo._wlockref) is None:
1028 # rest of vfs files are covered by 'wlock'
1027 # rest of vfs files are covered by 'wlock'
1029 #
1028 #
1030 # exclude special files
1029 # exclude special files
1031 for prefix in self._wlockfreeprefix:
1030 for prefix in self._wlockfreeprefix:
1032 if path.startswith(prefix):
1031 if path.startswith(prefix):
1033 return
1032 return
1034 repo.ui.develwarn('write with no wlock: "%s"' % path,
1033 repo.ui.develwarn('write with no wlock: "%s"' % path,
1035 stacklevel=2, config='check-locks')
1034 stacklevel=2, config='check-locks')
1036 return ret
1035 return ret
1037 return checkvfs
1036 return checkvfs
1038
1037
1039 def _getsvfsward(self, origfunc):
1038 def _getsvfsward(self, origfunc):
1040 """build a ward for self.svfs"""
1039 """build a ward for self.svfs"""
1041 rref = weakref.ref(self)
1040 rref = weakref.ref(self)
1042 def checksvfs(path, mode=None):
1041 def checksvfs(path, mode=None):
1043 ret = origfunc(path, mode=mode)
1042 ret = origfunc(path, mode=mode)
1044 repo = rref()
1043 repo = rref()
1045 if repo is None or not util.safehasattr(repo, '_lockref'):
1044 if repo is None or not util.safehasattr(repo, '_lockref'):
1046 return
1045 return
1047 if mode in (None, 'r', 'rb'):
1046 if mode in (None, 'r', 'rb'):
1048 return
1047 return
1049 if path.startswith(repo.sharedpath):
1048 if path.startswith(repo.sharedpath):
1050 # truncate name relative to the repository (.hg)
1049 # truncate name relative to the repository (.hg)
1051 path = path[len(repo.sharedpath) + 1:]
1050 path = path[len(repo.sharedpath) + 1:]
1052 if repo._currentlock(repo._lockref) is None:
1051 if repo._currentlock(repo._lockref) is None:
1053 repo.ui.develwarn('write with no lock: "%s"' % path,
1052 repo.ui.develwarn('write with no lock: "%s"' % path,
1054 stacklevel=3)
1053 stacklevel=3)
1055 return ret
1054 return ret
1056 return checksvfs
1055 return checksvfs
1057
1056
1058 def close(self):
1057 def close(self):
1059 self._writecaches()
1058 self._writecaches()
1060
1059
1061 def _writecaches(self):
1060 def _writecaches(self):
1062 if self._revbranchcache:
1061 if self._revbranchcache:
1063 self._revbranchcache.write()
1062 self._revbranchcache.write()
1064
1063
1065 def _restrictcapabilities(self, caps):
1064 def _restrictcapabilities(self, caps):
1066 if self.ui.configbool('experimental', 'bundle2-advertise'):
1065 if self.ui.configbool('experimental', 'bundle2-advertise'):
1067 caps = set(caps)
1066 caps = set(caps)
1068 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1067 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
1069 role='client'))
1068 role='client'))
1070 caps.add('bundle2=' + urlreq.quote(capsblob))
1069 caps.add('bundle2=' + urlreq.quote(capsblob))
1071 return caps
1070 return caps
1072
1071
1073 def _writerequirements(self):
1072 def _writerequirements(self):
1074 scmutil.writerequires(self.vfs, self.requirements)
1073 scmutil.writerequires(self.vfs, self.requirements)
1075
1074
1076 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1075 # Don't cache auditor/nofsauditor, or you'll end up with reference cycle:
1077 # self -> auditor -> self._checknested -> self
1076 # self -> auditor -> self._checknested -> self
1078
1077
1079 @property
1078 @property
1080 def auditor(self):
1079 def auditor(self):
1081 # This is only used by context.workingctx.match in order to
1080 # This is only used by context.workingctx.match in order to
1082 # detect files in subrepos.
1081 # detect files in subrepos.
1083 return pathutil.pathauditor(self.root, callback=self._checknested)
1082 return pathutil.pathauditor(self.root, callback=self._checknested)
1084
1083
1085 @property
1084 @property
1086 def nofsauditor(self):
1085 def nofsauditor(self):
1087 # This is only used by context.basectx.match in order to detect
1086 # This is only used by context.basectx.match in order to detect
1088 # files in subrepos.
1087 # files in subrepos.
1089 return pathutil.pathauditor(self.root, callback=self._checknested,
1088 return pathutil.pathauditor(self.root, callback=self._checknested,
1090 realfs=False, cached=True)
1089 realfs=False, cached=True)
1091
1090
1092 def _checknested(self, path):
1091 def _checknested(self, path):
1093 """Determine if path is a legal nested repository."""
1092 """Determine if path is a legal nested repository."""
1094 if not path.startswith(self.root):
1093 if not path.startswith(self.root):
1095 return False
1094 return False
1096 subpath = path[len(self.root) + 1:]
1095 subpath = path[len(self.root) + 1:]
1097 normsubpath = util.pconvert(subpath)
1096 normsubpath = util.pconvert(subpath)
1098
1097
1099 # XXX: Checking against the current working copy is wrong in
1098 # XXX: Checking against the current working copy is wrong in
1100 # the sense that it can reject things like
1099 # the sense that it can reject things like
1101 #
1100 #
1102 # $ hg cat -r 10 sub/x.txt
1101 # $ hg cat -r 10 sub/x.txt
1103 #
1102 #
1104 # if sub/ is no longer a subrepository in the working copy
1103 # if sub/ is no longer a subrepository in the working copy
1105 # parent revision.
1104 # parent revision.
1106 #
1105 #
1107 # However, it can of course also allow things that would have
1106 # However, it can of course also allow things that would have
1108 # been rejected before, such as the above cat command if sub/
1107 # been rejected before, such as the above cat command if sub/
1109 # is a subrepository now, but was a normal directory before.
1108 # is a subrepository now, but was a normal directory before.
1110 # The old path auditor would have rejected by mistake since it
1109 # The old path auditor would have rejected by mistake since it
1111 # panics when it sees sub/.hg/.
1110 # panics when it sees sub/.hg/.
1112 #
1111 #
1113 # All in all, checking against the working copy seems sensible
1112 # All in all, checking against the working copy seems sensible
1114 # since we want to prevent access to nested repositories on
1113 # since we want to prevent access to nested repositories on
1115 # the filesystem *now*.
1114 # the filesystem *now*.
1116 ctx = self[None]
1115 ctx = self[None]
1117 parts = util.splitpath(subpath)
1116 parts = util.splitpath(subpath)
1118 while parts:
1117 while parts:
1119 prefix = '/'.join(parts)
1118 prefix = '/'.join(parts)
1120 if prefix in ctx.substate:
1119 if prefix in ctx.substate:
1121 if prefix == normsubpath:
1120 if prefix == normsubpath:
1122 return True
1121 return True
1123 else:
1122 else:
1124 sub = ctx.sub(prefix)
1123 sub = ctx.sub(prefix)
1125 return sub.checknested(subpath[len(prefix) + 1:])
1124 return sub.checknested(subpath[len(prefix) + 1:])
1126 else:
1125 else:
1127 parts.pop()
1126 parts.pop()
1128 return False
1127 return False
1129
1128
1130 def peer(self):
1129 def peer(self):
1131 return localpeer(self) # not cached to avoid reference cycle
1130 return localpeer(self) # not cached to avoid reference cycle
1132
1131
1133 def unfiltered(self):
1132 def unfiltered(self):
1134 """Return unfiltered version of the repository
1133 """Return unfiltered version of the repository
1135
1134
1136 Intended to be overwritten by filtered repo."""
1135 Intended to be overwritten by filtered repo."""
1137 return self
1136 return self
1138
1137
1139 def filtered(self, name, visibilityexceptions=None):
1138 def filtered(self, name, visibilityexceptions=None):
1140 """Return a filtered version of a repository"""
1139 """Return a filtered version of a repository"""
1141 cls = repoview.newtype(self.unfiltered().__class__)
1140 cls = repoview.newtype(self.unfiltered().__class__)
1142 return cls(self, name, visibilityexceptions)
1141 return cls(self, name, visibilityexceptions)
1143
1142
1144 @repofilecache('bookmarks', 'bookmarks.current')
1143 @repofilecache('bookmarks', 'bookmarks.current')
1145 def _bookmarks(self):
1144 def _bookmarks(self):
1146 return bookmarks.bmstore(self)
1145 return bookmarks.bmstore(self)
1147
1146
1148 @property
1147 @property
1149 def _activebookmark(self):
1148 def _activebookmark(self):
1150 return self._bookmarks.active
1149 return self._bookmarks.active
1151
1150
1152 # _phasesets depend on changelog. what we need is to call
1151 # _phasesets depend on changelog. what we need is to call
1153 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1152 # _phasecache.invalidate() if '00changelog.i' was changed, but it
1154 # can't be easily expressed in filecache mechanism.
1153 # can't be easily expressed in filecache mechanism.
1155 @storecache('phaseroots', '00changelog.i')
1154 @storecache('phaseroots', '00changelog.i')
1156 def _phasecache(self):
1155 def _phasecache(self):
1157 return phases.phasecache(self, self._phasedefaults)
1156 return phases.phasecache(self, self._phasedefaults)
1158
1157
1159 @storecache('obsstore')
1158 @storecache('obsstore')
1160 def obsstore(self):
1159 def obsstore(self):
1161 return obsolete.makestore(self.ui, self)
1160 return obsolete.makestore(self.ui, self)
1162
1161
1163 @storecache('00changelog.i')
1162 @storecache('00changelog.i')
1164 def changelog(self):
1163 def changelog(self):
1165 return changelog.changelog(self.svfs,
1164 return changelog.changelog(self.svfs,
1166 trypending=txnutil.mayhavepending(self.root))
1165 trypending=txnutil.mayhavepending(self.root))
1167
1166
1168 @storecache('00manifest.i')
1167 @storecache('00manifest.i')
1169 def manifestlog(self):
1168 def manifestlog(self):
1170 rootstore = manifest.manifestrevlog(self.svfs)
1169 rootstore = manifest.manifestrevlog(self.svfs)
1171 return manifest.manifestlog(self.svfs, self, rootstore)
1170 return manifest.manifestlog(self.svfs, self, rootstore)
1172
1171
1173 @repofilecache('dirstate')
1172 @repofilecache('dirstate')
1174 def dirstate(self):
1173 def dirstate(self):
1175 return self._makedirstate()
1174 return self._makedirstate()
1176
1175
1177 def _makedirstate(self):
1176 def _makedirstate(self):
1178 """Extension point for wrapping the dirstate per-repo."""
1177 """Extension point for wrapping the dirstate per-repo."""
1179 sparsematchfn = lambda: sparse.matcher(self)
1178 sparsematchfn = lambda: sparse.matcher(self)
1180
1179
1181 return dirstate.dirstate(self.vfs, self.ui, self.root,
1180 return dirstate.dirstate(self.vfs, self.ui, self.root,
1182 self._dirstatevalidate, sparsematchfn)
1181 self._dirstatevalidate, sparsematchfn)
1183
1182
1184 def _dirstatevalidate(self, node):
1183 def _dirstatevalidate(self, node):
1185 try:
1184 try:
1186 self.changelog.rev(node)
1185 self.changelog.rev(node)
1187 return node
1186 return node
1188 except error.LookupError:
1187 except error.LookupError:
1189 if not self._dirstatevalidatewarned:
1188 if not self._dirstatevalidatewarned:
1190 self._dirstatevalidatewarned = True
1189 self._dirstatevalidatewarned = True
1191 self.ui.warn(_("warning: ignoring unknown"
1190 self.ui.warn(_("warning: ignoring unknown"
1192 " working parent %s!\n") % short(node))
1191 " working parent %s!\n") % short(node))
1193 return nullid
1192 return nullid
1194
1193
1195 @storecache(narrowspec.FILENAME)
1194 @storecache(narrowspec.FILENAME)
1196 def narrowpats(self):
1195 def narrowpats(self):
1197 """matcher patterns for this repository's narrowspec
1196 """matcher patterns for this repository's narrowspec
1198
1197
1199 A tuple of (includes, excludes).
1198 A tuple of (includes, excludes).
1200 """
1199 """
1201 return narrowspec.load(self)
1200 return narrowspec.load(self)
1202
1201
1203 @storecache(narrowspec.FILENAME)
1202 @storecache(narrowspec.FILENAME)
1204 def _narrowmatch(self):
1203 def _narrowmatch(self):
1205 if repository.NARROW_REQUIREMENT not in self.requirements:
1204 if repository.NARROW_REQUIREMENT not in self.requirements:
1206 return matchmod.always(self.root, '')
1205 return matchmod.always(self.root, '')
1207 include, exclude = self.narrowpats
1206 include, exclude = self.narrowpats
1208 return narrowspec.match(self.root, include=include, exclude=exclude)
1207 return narrowspec.match(self.root, include=include, exclude=exclude)
1209
1208
1210 def narrowmatch(self, match=None, includeexact=False):
1209 def narrowmatch(self, match=None, includeexact=False):
1211 """matcher corresponding the the repo's narrowspec
1210 """matcher corresponding the the repo's narrowspec
1212
1211
1213 If `match` is given, then that will be intersected with the narrow
1212 If `match` is given, then that will be intersected with the narrow
1214 matcher.
1213 matcher.
1215
1214
1216 If `includeexact` is True, then any exact matches from `match` will
1215 If `includeexact` is True, then any exact matches from `match` will
1217 be included even if they're outside the narrowspec.
1216 be included even if they're outside the narrowspec.
1218 """
1217 """
1219 if match:
1218 if match:
1220 if includeexact and not self._narrowmatch.always():
1219 if includeexact and not self._narrowmatch.always():
1221 # do not exclude explicitly-specified paths so that they can
1220 # do not exclude explicitly-specified paths so that they can
1222 # be warned later on
1221 # be warned later on
1223 em = matchmod.exact(match._root, match._cwd, match.files())
1222 em = matchmod.exact(match._root, match._cwd, match.files())
1224 nm = matchmod.unionmatcher([self._narrowmatch, em])
1223 nm = matchmod.unionmatcher([self._narrowmatch, em])
1225 return matchmod.intersectmatchers(match, nm)
1224 return matchmod.intersectmatchers(match, nm)
1226 return matchmod.intersectmatchers(match, self._narrowmatch)
1225 return matchmod.intersectmatchers(match, self._narrowmatch)
1227 return self._narrowmatch
1226 return self._narrowmatch
1228
1227
1229 def setnarrowpats(self, newincludes, newexcludes):
1228 def setnarrowpats(self, newincludes, newexcludes):
1230 narrowspec.save(self, newincludes, newexcludes)
1229 narrowspec.save(self, newincludes, newexcludes)
1231 self.invalidate(clearfilecache=True)
1230 self.invalidate(clearfilecache=True)
1232
1231
1233 def __getitem__(self, changeid):
1232 def __getitem__(self, changeid):
1234 if changeid is None:
1233 if changeid is None:
1235 return context.workingctx(self)
1234 return context.workingctx(self)
1236 if isinstance(changeid, context.basectx):
1235 if isinstance(changeid, context.basectx):
1237 return changeid
1236 return changeid
1238 if isinstance(changeid, slice):
1237 if isinstance(changeid, slice):
1239 # wdirrev isn't contiguous so the slice shouldn't include it
1238 # wdirrev isn't contiguous so the slice shouldn't include it
1240 return [self[i]
1239 return [self[i]
1241 for i in pycompat.xrange(*changeid.indices(len(self)))
1240 for i in pycompat.xrange(*changeid.indices(len(self)))
1242 if i not in self.changelog.filteredrevs]
1241 if i not in self.changelog.filteredrevs]
1243 try:
1242 try:
1244 if isinstance(changeid, int):
1243 if isinstance(changeid, int):
1245 node = self.changelog.node(changeid)
1244 node = self.changelog.node(changeid)
1246 rev = changeid
1245 rev = changeid
1247 elif changeid == 'null':
1246 elif changeid == 'null':
1248 node = nullid
1247 node = nullid
1249 rev = nullrev
1248 rev = nullrev
1250 elif changeid == 'tip':
1249 elif changeid == 'tip':
1251 node = self.changelog.tip()
1250 node = self.changelog.tip()
1252 rev = self.changelog.rev(node)
1251 rev = self.changelog.rev(node)
1253 elif changeid == '.':
1252 elif changeid == '.':
1254 # this is a hack to delay/avoid loading obsmarkers
1253 # this is a hack to delay/avoid loading obsmarkers
1255 # when we know that '.' won't be hidden
1254 # when we know that '.' won't be hidden
1256 node = self.dirstate.p1()
1255 node = self.dirstate.p1()
1257 rev = self.unfiltered().changelog.rev(node)
1256 rev = self.unfiltered().changelog.rev(node)
1258 elif len(changeid) == 20:
1257 elif len(changeid) == 20:
1259 try:
1258 try:
1260 node = changeid
1259 node = changeid
1261 rev = self.changelog.rev(changeid)
1260 rev = self.changelog.rev(changeid)
1262 except error.FilteredLookupError:
1261 except error.FilteredLookupError:
1263 changeid = hex(changeid) # for the error message
1262 changeid = hex(changeid) # for the error message
1264 raise
1263 raise
1265 except LookupError:
1264 except LookupError:
1266 # check if it might have come from damaged dirstate
1265 # check if it might have come from damaged dirstate
1267 #
1266 #
1268 # XXX we could avoid the unfiltered if we had a recognizable
1267 # XXX we could avoid the unfiltered if we had a recognizable
1269 # exception for filtered changeset access
1268 # exception for filtered changeset access
1270 if (self.local()
1269 if (self.local()
1271 and changeid in self.unfiltered().dirstate.parents()):
1270 and changeid in self.unfiltered().dirstate.parents()):
1272 msg = _("working directory has unknown parent '%s'!")
1271 msg = _("working directory has unknown parent '%s'!")
1273 raise error.Abort(msg % short(changeid))
1272 raise error.Abort(msg % short(changeid))
1274 changeid = hex(changeid) # for the error message
1273 changeid = hex(changeid) # for the error message
1275 raise
1274 raise
1276
1275
1277 elif len(changeid) == 40:
1276 elif len(changeid) == 40:
1278 node = bin(changeid)
1277 node = bin(changeid)
1279 rev = self.changelog.rev(node)
1278 rev = self.changelog.rev(node)
1280 else:
1279 else:
1281 raise error.ProgrammingError(
1280 raise error.ProgrammingError(
1282 "unsupported changeid '%s' of type %s" %
1281 "unsupported changeid '%s' of type %s" %
1283 (changeid, type(changeid)))
1282 (changeid, type(changeid)))
1284
1283
1285 return context.changectx(self, rev, node)
1284 return context.changectx(self, rev, node)
1286
1285
1287 except (error.FilteredIndexError, error.FilteredLookupError):
1286 except (error.FilteredIndexError, error.FilteredLookupError):
1288 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1287 raise error.FilteredRepoLookupError(_("filtered revision '%s'")
1289 % pycompat.bytestr(changeid))
1288 % pycompat.bytestr(changeid))
1290 except (IndexError, LookupError):
1289 except (IndexError, LookupError):
1291 raise error.RepoLookupError(
1290 raise error.RepoLookupError(
1292 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1291 _("unknown revision '%s'") % pycompat.bytestr(changeid))
1293 except error.WdirUnsupported:
1292 except error.WdirUnsupported:
1294 return context.workingctx(self)
1293 return context.workingctx(self)
1295
1294
1296 def __contains__(self, changeid):
1295 def __contains__(self, changeid):
1297 """True if the given changeid exists
1296 """True if the given changeid exists
1298
1297
1299 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1298 error.AmbiguousPrefixLookupError is raised if an ambiguous node
1300 specified.
1299 specified.
1301 """
1300 """
1302 try:
1301 try:
1303 self[changeid]
1302 self[changeid]
1304 return True
1303 return True
1305 except error.RepoLookupError:
1304 except error.RepoLookupError:
1306 return False
1305 return False
1307
1306
1308 def __nonzero__(self):
1307 def __nonzero__(self):
1309 return True
1308 return True
1310
1309
1311 __bool__ = __nonzero__
1310 __bool__ = __nonzero__
1312
1311
1313 def __len__(self):
1312 def __len__(self):
1314 # no need to pay the cost of repoview.changelog
1313 # no need to pay the cost of repoview.changelog
1315 unfi = self.unfiltered()
1314 unfi = self.unfiltered()
1316 return len(unfi.changelog)
1315 return len(unfi.changelog)
1317
1316
1318 def __iter__(self):
1317 def __iter__(self):
1319 return iter(self.changelog)
1318 return iter(self.changelog)
1320
1319
1321 def revs(self, expr, *args):
1320 def revs(self, expr, *args):
1322 '''Find revisions matching a revset.
1321 '''Find revisions matching a revset.
1323
1322
1324 The revset is specified as a string ``expr`` that may contain
1323 The revset is specified as a string ``expr`` that may contain
1325 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1324 %-formatting to escape certain types. See ``revsetlang.formatspec``.
1326
1325
1327 Revset aliases from the configuration are not expanded. To expand
1326 Revset aliases from the configuration are not expanded. To expand
1328 user aliases, consider calling ``scmutil.revrange()`` or
1327 user aliases, consider calling ``scmutil.revrange()`` or
1329 ``repo.anyrevs([expr], user=True)``.
1328 ``repo.anyrevs([expr], user=True)``.
1330
1329
1331 Returns a revset.abstractsmartset, which is a list-like interface
1330 Returns a revset.abstractsmartset, which is a list-like interface
1332 that contains integer revisions.
1331 that contains integer revisions.
1333 '''
1332 '''
1334 expr = revsetlang.formatspec(expr, *args)
1333 expr = revsetlang.formatspec(expr, *args)
1335 m = revset.match(None, expr)
1334 m = revset.match(None, expr)
1336 return m(self)
1335 return m(self)
1337
1336
1338 def set(self, expr, *args):
1337 def set(self, expr, *args):
1339 '''Find revisions matching a revset and emit changectx instances.
1338 '''Find revisions matching a revset and emit changectx instances.
1340
1339
1341 This is a convenience wrapper around ``revs()`` that iterates the
1340 This is a convenience wrapper around ``revs()`` that iterates the
1342 result and is a generator of changectx instances.
1341 result and is a generator of changectx instances.
1343
1342
1344 Revset aliases from the configuration are not expanded. To expand
1343 Revset aliases from the configuration are not expanded. To expand
1345 user aliases, consider calling ``scmutil.revrange()``.
1344 user aliases, consider calling ``scmutil.revrange()``.
1346 '''
1345 '''
1347 for r in self.revs(expr, *args):
1346 for r in self.revs(expr, *args):
1348 yield self[r]
1347 yield self[r]
1349
1348
1350 def anyrevs(self, specs, user=False, localalias=None):
1349 def anyrevs(self, specs, user=False, localalias=None):
1351 '''Find revisions matching one of the given revsets.
1350 '''Find revisions matching one of the given revsets.
1352
1351
1353 Revset aliases from the configuration are not expanded by default. To
1352 Revset aliases from the configuration are not expanded by default. To
1354 expand user aliases, specify ``user=True``. To provide some local
1353 expand user aliases, specify ``user=True``. To provide some local
1355 definitions overriding user aliases, set ``localalias`` to
1354 definitions overriding user aliases, set ``localalias`` to
1356 ``{name: definitionstring}``.
1355 ``{name: definitionstring}``.
1357 '''
1356 '''
1358 if user:
1357 if user:
1359 m = revset.matchany(self.ui, specs,
1358 m = revset.matchany(self.ui, specs,
1360 lookup=revset.lookupfn(self),
1359 lookup=revset.lookupfn(self),
1361 localalias=localalias)
1360 localalias=localalias)
1362 else:
1361 else:
1363 m = revset.matchany(None, specs, localalias=localalias)
1362 m = revset.matchany(None, specs, localalias=localalias)
1364 return m(self)
1363 return m(self)
1365
1364
1366 def url(self):
1365 def url(self):
1367 return 'file:' + self.root
1366 return 'file:' + self.root
1368
1367
1369 def hook(self, name, throw=False, **args):
1368 def hook(self, name, throw=False, **args):
1370 """Call a hook, passing this repo instance.
1369 """Call a hook, passing this repo instance.
1371
1370
1372 This a convenience method to aid invoking hooks. Extensions likely
1371 This a convenience method to aid invoking hooks. Extensions likely
1373 won't call this unless they have registered a custom hook or are
1372 won't call this unless they have registered a custom hook or are
1374 replacing code that is expected to call a hook.
1373 replacing code that is expected to call a hook.
1375 """
1374 """
1376 return hook.hook(self.ui, self, name, throw, **args)
1375 return hook.hook(self.ui, self, name, throw, **args)
1377
1376
1378 @filteredpropertycache
1377 @filteredpropertycache
1379 def _tagscache(self):
1378 def _tagscache(self):
1380 '''Returns a tagscache object that contains various tags related
1379 '''Returns a tagscache object that contains various tags related
1381 caches.'''
1380 caches.'''
1382
1381
1383 # This simplifies its cache management by having one decorated
1382 # This simplifies its cache management by having one decorated
1384 # function (this one) and the rest simply fetch things from it.
1383 # function (this one) and the rest simply fetch things from it.
1385 class tagscache(object):
1384 class tagscache(object):
1386 def __init__(self):
1385 def __init__(self):
1387 # These two define the set of tags for this repository. tags
1386 # These two define the set of tags for this repository. tags
1388 # maps tag name to node; tagtypes maps tag name to 'global' or
1387 # maps tag name to node; tagtypes maps tag name to 'global' or
1389 # 'local'. (Global tags are defined by .hgtags across all
1388 # 'local'. (Global tags are defined by .hgtags across all
1390 # heads, and local tags are defined in .hg/localtags.)
1389 # heads, and local tags are defined in .hg/localtags.)
1391 # They constitute the in-memory cache of tags.
1390 # They constitute the in-memory cache of tags.
1392 self.tags = self.tagtypes = None
1391 self.tags = self.tagtypes = None
1393
1392
1394 self.nodetagscache = self.tagslist = None
1393 self.nodetagscache = self.tagslist = None
1395
1394
1396 cache = tagscache()
1395 cache = tagscache()
1397 cache.tags, cache.tagtypes = self._findtags()
1396 cache.tags, cache.tagtypes = self._findtags()
1398
1397
1399 return cache
1398 return cache
1400
1399
1401 def tags(self):
1400 def tags(self):
1402 '''return a mapping of tag to node'''
1401 '''return a mapping of tag to node'''
1403 t = {}
1402 t = {}
1404 if self.changelog.filteredrevs:
1403 if self.changelog.filteredrevs:
1405 tags, tt = self._findtags()
1404 tags, tt = self._findtags()
1406 else:
1405 else:
1407 tags = self._tagscache.tags
1406 tags = self._tagscache.tags
1408 for k, v in tags.iteritems():
1407 for k, v in tags.iteritems():
1409 try:
1408 try:
1410 # ignore tags to unknown nodes
1409 # ignore tags to unknown nodes
1411 self.changelog.rev(v)
1410 self.changelog.rev(v)
1412 t[k] = v
1411 t[k] = v
1413 except (error.LookupError, ValueError):
1412 except (error.LookupError, ValueError):
1414 pass
1413 pass
1415 return t
1414 return t
1416
1415
1417 def _findtags(self):
1416 def _findtags(self):
1418 '''Do the hard work of finding tags. Return a pair of dicts
1417 '''Do the hard work of finding tags. Return a pair of dicts
1419 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1418 (tags, tagtypes) where tags maps tag name to node, and tagtypes
1420 maps tag name to a string like \'global\' or \'local\'.
1419 maps tag name to a string like \'global\' or \'local\'.
1421 Subclasses or extensions are free to add their own tags, but
1420 Subclasses or extensions are free to add their own tags, but
1422 should be aware that the returned dicts will be retained for the
1421 should be aware that the returned dicts will be retained for the
1423 duration of the localrepo object.'''
1422 duration of the localrepo object.'''
1424
1423
1425 # XXX what tagtype should subclasses/extensions use? Currently
1424 # XXX what tagtype should subclasses/extensions use? Currently
1426 # mq and bookmarks add tags, but do not set the tagtype at all.
1425 # mq and bookmarks add tags, but do not set the tagtype at all.
1427 # Should each extension invent its own tag type? Should there
1426 # Should each extension invent its own tag type? Should there
1428 # be one tagtype for all such "virtual" tags? Or is the status
1427 # be one tagtype for all such "virtual" tags? Or is the status
1429 # quo fine?
1428 # quo fine?
1430
1429
1431
1430
1432 # map tag name to (node, hist)
1431 # map tag name to (node, hist)
1433 alltags = tagsmod.findglobaltags(self.ui, self)
1432 alltags = tagsmod.findglobaltags(self.ui, self)
1434 # map tag name to tag type
1433 # map tag name to tag type
1435 tagtypes = dict((tag, 'global') for tag in alltags)
1434 tagtypes = dict((tag, 'global') for tag in alltags)
1436
1435
1437 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1436 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1438
1437
1439 # Build the return dicts. Have to re-encode tag names because
1438 # Build the return dicts. Have to re-encode tag names because
1440 # the tags module always uses UTF-8 (in order not to lose info
1439 # the tags module always uses UTF-8 (in order not to lose info
1441 # writing to the cache), but the rest of Mercurial wants them in
1440 # writing to the cache), but the rest of Mercurial wants them in
1442 # local encoding.
1441 # local encoding.
1443 tags = {}
1442 tags = {}
1444 for (name, (node, hist)) in alltags.iteritems():
1443 for (name, (node, hist)) in alltags.iteritems():
1445 if node != nullid:
1444 if node != nullid:
1446 tags[encoding.tolocal(name)] = node
1445 tags[encoding.tolocal(name)] = node
1447 tags['tip'] = self.changelog.tip()
1446 tags['tip'] = self.changelog.tip()
1448 tagtypes = dict([(encoding.tolocal(name), value)
1447 tagtypes = dict([(encoding.tolocal(name), value)
1449 for (name, value) in tagtypes.iteritems()])
1448 for (name, value) in tagtypes.iteritems()])
1450 return (tags, tagtypes)
1449 return (tags, tagtypes)
1451
1450
1452 def tagtype(self, tagname):
1451 def tagtype(self, tagname):
1453 '''
1452 '''
1454 return the type of the given tag. result can be:
1453 return the type of the given tag. result can be:
1455
1454
1456 'local' : a local tag
1455 'local' : a local tag
1457 'global' : a global tag
1456 'global' : a global tag
1458 None : tag does not exist
1457 None : tag does not exist
1459 '''
1458 '''
1460
1459
1461 return self._tagscache.tagtypes.get(tagname)
1460 return self._tagscache.tagtypes.get(tagname)
1462
1461
1463 def tagslist(self):
1462 def tagslist(self):
1464 '''return a list of tags ordered by revision'''
1463 '''return a list of tags ordered by revision'''
1465 if not self._tagscache.tagslist:
1464 if not self._tagscache.tagslist:
1466 l = []
1465 l = []
1467 for t, n in self.tags().iteritems():
1466 for t, n in self.tags().iteritems():
1468 l.append((self.changelog.rev(n), t, n))
1467 l.append((self.changelog.rev(n), t, n))
1469 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1468 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1470
1469
1471 return self._tagscache.tagslist
1470 return self._tagscache.tagslist
1472
1471
1473 def nodetags(self, node):
1472 def nodetags(self, node):
1474 '''return the tags associated with a node'''
1473 '''return the tags associated with a node'''
1475 if not self._tagscache.nodetagscache:
1474 if not self._tagscache.nodetagscache:
1476 nodetagscache = {}
1475 nodetagscache = {}
1477 for t, n in self._tagscache.tags.iteritems():
1476 for t, n in self._tagscache.tags.iteritems():
1478 nodetagscache.setdefault(n, []).append(t)
1477 nodetagscache.setdefault(n, []).append(t)
1479 for tags in nodetagscache.itervalues():
1478 for tags in nodetagscache.itervalues():
1480 tags.sort()
1479 tags.sort()
1481 self._tagscache.nodetagscache = nodetagscache
1480 self._tagscache.nodetagscache = nodetagscache
1482 return self._tagscache.nodetagscache.get(node, [])
1481 return self._tagscache.nodetagscache.get(node, [])
1483
1482
1484 def nodebookmarks(self, node):
1483 def nodebookmarks(self, node):
1485 """return the list of bookmarks pointing to the specified node"""
1484 """return the list of bookmarks pointing to the specified node"""
1486 return self._bookmarks.names(node)
1485 return self._bookmarks.names(node)
1487
1486
1488 def branchmap(self):
1487 def branchmap(self):
1489 '''returns a dictionary {branch: [branchheads]} with branchheads
1488 '''returns a dictionary {branch: [branchheads]} with branchheads
1490 ordered by increasing revision number'''
1489 ordered by increasing revision number'''
1491 branchmap.updatecache(self)
1490 branchmap.updatecache(self)
1492 return self._branchcaches[self.filtername]
1491 return self._branchcaches[self.filtername]
1493
1492
1494 @unfilteredmethod
1493 @unfilteredmethod
1495 def revbranchcache(self):
1494 def revbranchcache(self):
1496 if not self._revbranchcache:
1495 if not self._revbranchcache:
1497 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1496 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1498 return self._revbranchcache
1497 return self._revbranchcache
1499
1498
1500 def branchtip(self, branch, ignoremissing=False):
1499 def branchtip(self, branch, ignoremissing=False):
1501 '''return the tip node for a given branch
1500 '''return the tip node for a given branch
1502
1501
1503 If ignoremissing is True, then this method will not raise an error.
1502 If ignoremissing is True, then this method will not raise an error.
1504 This is helpful for callers that only expect None for a missing branch
1503 This is helpful for callers that only expect None for a missing branch
1505 (e.g. namespace).
1504 (e.g. namespace).
1506
1505
1507 '''
1506 '''
1508 try:
1507 try:
1509 return self.branchmap().branchtip(branch)
1508 return self.branchmap().branchtip(branch)
1510 except KeyError:
1509 except KeyError:
1511 if not ignoremissing:
1510 if not ignoremissing:
1512 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1511 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1513 else:
1512 else:
1514 pass
1513 pass
1515
1514
1516 def lookup(self, key):
1515 def lookup(self, key):
1517 return scmutil.revsymbol(self, key).node()
1516 return scmutil.revsymbol(self, key).node()
1518
1517
1519 def lookupbranch(self, key):
1518 def lookupbranch(self, key):
1520 if key in self.branchmap():
1519 if key in self.branchmap():
1521 return key
1520 return key
1522
1521
1523 return scmutil.revsymbol(self, key).branch()
1522 return scmutil.revsymbol(self, key).branch()
1524
1523
1525 def known(self, nodes):
1524 def known(self, nodes):
1526 cl = self.changelog
1525 cl = self.changelog
1527 nm = cl.nodemap
1526 nm = cl.nodemap
1528 filtered = cl.filteredrevs
1527 filtered = cl.filteredrevs
1529 result = []
1528 result = []
1530 for n in nodes:
1529 for n in nodes:
1531 r = nm.get(n)
1530 r = nm.get(n)
1532 resp = not (r is None or r in filtered)
1531 resp = not (r is None or r in filtered)
1533 result.append(resp)
1532 result.append(resp)
1534 return result
1533 return result
1535
1534
1536 def local(self):
1535 def local(self):
1537 return self
1536 return self
1538
1537
1539 def publishing(self):
1538 def publishing(self):
1540 # it's safe (and desirable) to trust the publish flag unconditionally
1539 # it's safe (and desirable) to trust the publish flag unconditionally
1541 # so that we don't finalize changes shared between users via ssh or nfs
1540 # so that we don't finalize changes shared between users via ssh or nfs
1542 return self.ui.configbool('phases', 'publish', untrusted=True)
1541 return self.ui.configbool('phases', 'publish', untrusted=True)
1543
1542
1544 def cancopy(self):
1543 def cancopy(self):
1545 # so statichttprepo's override of local() works
1544 # so statichttprepo's override of local() works
1546 if not self.local():
1545 if not self.local():
1547 return False
1546 return False
1548 if not self.publishing():
1547 if not self.publishing():
1549 return True
1548 return True
1550 # if publishing we can't copy if there is filtered content
1549 # if publishing we can't copy if there is filtered content
1551 return not self.filtered('visible').changelog.filteredrevs
1550 return not self.filtered('visible').changelog.filteredrevs
1552
1551
1553 def shared(self):
1552 def shared(self):
1554 '''the type of shared repository (None if not shared)'''
1553 '''the type of shared repository (None if not shared)'''
1555 if self.sharedpath != self.path:
1554 if self.sharedpath != self.path:
1556 return 'store'
1555 return 'store'
1557 return None
1556 return None
1558
1557
1559 def wjoin(self, f, *insidef):
1558 def wjoin(self, f, *insidef):
1560 return self.vfs.reljoin(self.root, f, *insidef)
1559 return self.vfs.reljoin(self.root, f, *insidef)
1561
1560
1562 def setparents(self, p1, p2=nullid):
1561 def setparents(self, p1, p2=nullid):
1563 with self.dirstate.parentchange():
1562 with self.dirstate.parentchange():
1564 copies = self.dirstate.setparents(p1, p2)
1563 copies = self.dirstate.setparents(p1, p2)
1565 pctx = self[p1]
1564 pctx = self[p1]
1566 if copies:
1565 if copies:
1567 # Adjust copy records, the dirstate cannot do it, it
1566 # Adjust copy records, the dirstate cannot do it, it
1568 # requires access to parents manifests. Preserve them
1567 # requires access to parents manifests. Preserve them
1569 # only for entries added to first parent.
1568 # only for entries added to first parent.
1570 for f in copies:
1569 for f in copies:
1571 if f not in pctx and copies[f] in pctx:
1570 if f not in pctx and copies[f] in pctx:
1572 self.dirstate.copy(copies[f], f)
1571 self.dirstate.copy(copies[f], f)
1573 if p2 == nullid:
1572 if p2 == nullid:
1574 for f, s in sorted(self.dirstate.copies().items()):
1573 for f, s in sorted(self.dirstate.copies().items()):
1575 if f not in pctx and s not in pctx:
1574 if f not in pctx and s not in pctx:
1576 self.dirstate.copy(None, f)
1575 self.dirstate.copy(None, f)
1577
1576
1578 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1577 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1579 """changeid can be a changeset revision, node, or tag.
1578 """changeid can be a changeset revision, node, or tag.
1580 fileid can be a file revision or node."""
1579 fileid can be a file revision or node."""
1581 return context.filectx(self, path, changeid, fileid,
1580 return context.filectx(self, path, changeid, fileid,
1582 changectx=changectx)
1581 changectx=changectx)
1583
1582
1584 def getcwd(self):
1583 def getcwd(self):
1585 return self.dirstate.getcwd()
1584 return self.dirstate.getcwd()
1586
1585
1587 def pathto(self, f, cwd=None):
1586 def pathto(self, f, cwd=None):
1588 return self.dirstate.pathto(f, cwd)
1587 return self.dirstate.pathto(f, cwd)
1589
1588
1590 def _loadfilter(self, filter):
1589 def _loadfilter(self, filter):
1591 if filter not in self._filterpats:
1590 if filter not in self._filterpats:
1592 l = []
1591 l = []
1593 for pat, cmd in self.ui.configitems(filter):
1592 for pat, cmd in self.ui.configitems(filter):
1594 if cmd == '!':
1593 if cmd == '!':
1595 continue
1594 continue
1596 mf = matchmod.match(self.root, '', [pat])
1595 mf = matchmod.match(self.root, '', [pat])
1597 fn = None
1596 fn = None
1598 params = cmd
1597 params = cmd
1599 for name, filterfn in self._datafilters.iteritems():
1598 for name, filterfn in self._datafilters.iteritems():
1600 if cmd.startswith(name):
1599 if cmd.startswith(name):
1601 fn = filterfn
1600 fn = filterfn
1602 params = cmd[len(name):].lstrip()
1601 params = cmd[len(name):].lstrip()
1603 break
1602 break
1604 if not fn:
1603 if not fn:
1605 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1604 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1606 # Wrap old filters not supporting keyword arguments
1605 # Wrap old filters not supporting keyword arguments
1607 if not pycompat.getargspec(fn)[2]:
1606 if not pycompat.getargspec(fn)[2]:
1608 oldfn = fn
1607 oldfn = fn
1609 fn = lambda s, c, **kwargs: oldfn(s, c)
1608 fn = lambda s, c, **kwargs: oldfn(s, c)
1610 l.append((mf, fn, params))
1609 l.append((mf, fn, params))
1611 self._filterpats[filter] = l
1610 self._filterpats[filter] = l
1612 return self._filterpats[filter]
1611 return self._filterpats[filter]
1613
1612
1614 def _filter(self, filterpats, filename, data):
1613 def _filter(self, filterpats, filename, data):
1615 for mf, fn, cmd in filterpats:
1614 for mf, fn, cmd in filterpats:
1616 if mf(filename):
1615 if mf(filename):
1617 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1616 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1618 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1617 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1619 break
1618 break
1620
1619
1621 return data
1620 return data
1622
1621
1623 @unfilteredpropertycache
1622 @unfilteredpropertycache
1624 def _encodefilterpats(self):
1623 def _encodefilterpats(self):
1625 return self._loadfilter('encode')
1624 return self._loadfilter('encode')
1626
1625
1627 @unfilteredpropertycache
1626 @unfilteredpropertycache
1628 def _decodefilterpats(self):
1627 def _decodefilterpats(self):
1629 return self._loadfilter('decode')
1628 return self._loadfilter('decode')
1630
1629
1631 def adddatafilter(self, name, filter):
1630 def adddatafilter(self, name, filter):
1632 self._datafilters[name] = filter
1631 self._datafilters[name] = filter
1633
1632
1634 def wread(self, filename):
1633 def wread(self, filename):
1635 if self.wvfs.islink(filename):
1634 if self.wvfs.islink(filename):
1636 data = self.wvfs.readlink(filename)
1635 data = self.wvfs.readlink(filename)
1637 else:
1636 else:
1638 data = self.wvfs.read(filename)
1637 data = self.wvfs.read(filename)
1639 return self._filter(self._encodefilterpats, filename, data)
1638 return self._filter(self._encodefilterpats, filename, data)
1640
1639
1641 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1640 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1642 """write ``data`` into ``filename`` in the working directory
1641 """write ``data`` into ``filename`` in the working directory
1643
1642
1644 This returns length of written (maybe decoded) data.
1643 This returns length of written (maybe decoded) data.
1645 """
1644 """
1646 data = self._filter(self._decodefilterpats, filename, data)
1645 data = self._filter(self._decodefilterpats, filename, data)
1647 if 'l' in flags:
1646 if 'l' in flags:
1648 self.wvfs.symlink(data, filename)
1647 self.wvfs.symlink(data, filename)
1649 else:
1648 else:
1650 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1649 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1651 **kwargs)
1650 **kwargs)
1652 if 'x' in flags:
1651 if 'x' in flags:
1653 self.wvfs.setflags(filename, False, True)
1652 self.wvfs.setflags(filename, False, True)
1654 else:
1653 else:
1655 self.wvfs.setflags(filename, False, False)
1654 self.wvfs.setflags(filename, False, False)
1656 return len(data)
1655 return len(data)
1657
1656
1658 def wwritedata(self, filename, data):
1657 def wwritedata(self, filename, data):
1659 return self._filter(self._decodefilterpats, filename, data)
1658 return self._filter(self._decodefilterpats, filename, data)
1660
1659
1661 def currenttransaction(self):
1660 def currenttransaction(self):
1662 """return the current transaction or None if non exists"""
1661 """return the current transaction or None if non exists"""
1663 if self._transref:
1662 if self._transref:
1664 tr = self._transref()
1663 tr = self._transref()
1665 else:
1664 else:
1666 tr = None
1665 tr = None
1667
1666
1668 if tr and tr.running():
1667 if tr and tr.running():
1669 return tr
1668 return tr
1670 return None
1669 return None
1671
1670
1672 def transaction(self, desc, report=None):
1671 def transaction(self, desc, report=None):
1673 if (self.ui.configbool('devel', 'all-warnings')
1672 if (self.ui.configbool('devel', 'all-warnings')
1674 or self.ui.configbool('devel', 'check-locks')):
1673 or self.ui.configbool('devel', 'check-locks')):
1675 if self._currentlock(self._lockref) is None:
1674 if self._currentlock(self._lockref) is None:
1676 raise error.ProgrammingError('transaction requires locking')
1675 raise error.ProgrammingError('transaction requires locking')
1677 tr = self.currenttransaction()
1676 tr = self.currenttransaction()
1678 if tr is not None:
1677 if tr is not None:
1679 return tr.nest(name=desc)
1678 return tr.nest(name=desc)
1680
1679
1681 # abort here if the journal already exists
1680 # abort here if the journal already exists
1682 if self.svfs.exists("journal"):
1681 if self.svfs.exists("journal"):
1683 raise error.RepoError(
1682 raise error.RepoError(
1684 _("abandoned transaction found"),
1683 _("abandoned transaction found"),
1685 hint=_("run 'hg recover' to clean up transaction"))
1684 hint=_("run 'hg recover' to clean up transaction"))
1686
1685
1687 idbase = "%.40f#%f" % (random.random(), time.time())
1686 idbase = "%.40f#%f" % (random.random(), time.time())
1688 ha = hex(hashlib.sha1(idbase).digest())
1687 ha = hex(hashlib.sha1(idbase).digest())
1689 txnid = 'TXN:' + ha
1688 txnid = 'TXN:' + ha
1690 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1689 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1691
1690
1692 self._writejournal(desc)
1691 self._writejournal(desc)
1693 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1692 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1694 if report:
1693 if report:
1695 rp = report
1694 rp = report
1696 else:
1695 else:
1697 rp = self.ui.warn
1696 rp = self.ui.warn
1698 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1697 vfsmap = {'plain': self.vfs, 'store': self.svfs} # root of .hg/
1699 # we must avoid cyclic reference between repo and transaction.
1698 # we must avoid cyclic reference between repo and transaction.
1700 reporef = weakref.ref(self)
1699 reporef = weakref.ref(self)
1701 # Code to track tag movement
1700 # Code to track tag movement
1702 #
1701 #
1703 # Since tags are all handled as file content, it is actually quite hard
1702 # Since tags are all handled as file content, it is actually quite hard
1704 # to track these movement from a code perspective. So we fallback to a
1703 # to track these movement from a code perspective. So we fallback to a
1705 # tracking at the repository level. One could envision to track changes
1704 # tracking at the repository level. One could envision to track changes
1706 # to the '.hgtags' file through changegroup apply but that fails to
1705 # to the '.hgtags' file through changegroup apply but that fails to
1707 # cope with case where transaction expose new heads without changegroup
1706 # cope with case where transaction expose new heads without changegroup
1708 # being involved (eg: phase movement).
1707 # being involved (eg: phase movement).
1709 #
1708 #
1710 # For now, We gate the feature behind a flag since this likely comes
1709 # For now, We gate the feature behind a flag since this likely comes
1711 # with performance impacts. The current code run more often than needed
1710 # with performance impacts. The current code run more often than needed
1712 # and do not use caches as much as it could. The current focus is on
1711 # and do not use caches as much as it could. The current focus is on
1713 # the behavior of the feature so we disable it by default. The flag
1712 # the behavior of the feature so we disable it by default. The flag
1714 # will be removed when we are happy with the performance impact.
1713 # will be removed when we are happy with the performance impact.
1715 #
1714 #
1716 # Once this feature is no longer experimental move the following
1715 # Once this feature is no longer experimental move the following
1717 # documentation to the appropriate help section:
1716 # documentation to the appropriate help section:
1718 #
1717 #
1719 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1718 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1720 # tags (new or changed or deleted tags). In addition the details of
1719 # tags (new or changed or deleted tags). In addition the details of
1721 # these changes are made available in a file at:
1720 # these changes are made available in a file at:
1722 # ``REPOROOT/.hg/changes/tags.changes``.
1721 # ``REPOROOT/.hg/changes/tags.changes``.
1723 # Make sure you check for HG_TAG_MOVED before reading that file as it
1722 # Make sure you check for HG_TAG_MOVED before reading that file as it
1724 # might exist from a previous transaction even if no tag were touched
1723 # might exist from a previous transaction even if no tag were touched
1725 # in this one. Changes are recorded in a line base format::
1724 # in this one. Changes are recorded in a line base format::
1726 #
1725 #
1727 # <action> <hex-node> <tag-name>\n
1726 # <action> <hex-node> <tag-name>\n
1728 #
1727 #
1729 # Actions are defined as follow:
1728 # Actions are defined as follow:
1730 # "-R": tag is removed,
1729 # "-R": tag is removed,
1731 # "+A": tag is added,
1730 # "+A": tag is added,
1732 # "-M": tag is moved (old value),
1731 # "-M": tag is moved (old value),
1733 # "+M": tag is moved (new value),
1732 # "+M": tag is moved (new value),
1734 tracktags = lambda x: None
1733 tracktags = lambda x: None
1735 # experimental config: experimental.hook-track-tags
1734 # experimental config: experimental.hook-track-tags
1736 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1735 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1737 if desc != 'strip' and shouldtracktags:
1736 if desc != 'strip' and shouldtracktags:
1738 oldheads = self.changelog.headrevs()
1737 oldheads = self.changelog.headrevs()
1739 def tracktags(tr2):
1738 def tracktags(tr2):
1740 repo = reporef()
1739 repo = reporef()
1741 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1740 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1742 newheads = repo.changelog.headrevs()
1741 newheads = repo.changelog.headrevs()
1743 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1742 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1744 # notes: we compare lists here.
1743 # notes: we compare lists here.
1745 # As we do it only once buiding set would not be cheaper
1744 # As we do it only once buiding set would not be cheaper
1746 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1745 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1747 if changes:
1746 if changes:
1748 tr2.hookargs['tag_moved'] = '1'
1747 tr2.hookargs['tag_moved'] = '1'
1749 with repo.vfs('changes/tags.changes', 'w',
1748 with repo.vfs('changes/tags.changes', 'w',
1750 atomictemp=True) as changesfile:
1749 atomictemp=True) as changesfile:
1751 # note: we do not register the file to the transaction
1750 # note: we do not register the file to the transaction
1752 # because we needs it to still exist on the transaction
1751 # because we needs it to still exist on the transaction
1753 # is close (for txnclose hooks)
1752 # is close (for txnclose hooks)
1754 tagsmod.writediff(changesfile, changes)
1753 tagsmod.writediff(changesfile, changes)
1755 def validate(tr2):
1754 def validate(tr2):
1756 """will run pre-closing hooks"""
1755 """will run pre-closing hooks"""
1757 # XXX the transaction API is a bit lacking here so we take a hacky
1756 # XXX the transaction API is a bit lacking here so we take a hacky
1758 # path for now
1757 # path for now
1759 #
1758 #
1760 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1759 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1761 # dict is copied before these run. In addition we needs the data
1760 # dict is copied before these run. In addition we needs the data
1762 # available to in memory hooks too.
1761 # available to in memory hooks too.
1763 #
1762 #
1764 # Moreover, we also need to make sure this runs before txnclose
1763 # Moreover, we also need to make sure this runs before txnclose
1765 # hooks and there is no "pending" mechanism that would execute
1764 # hooks and there is no "pending" mechanism that would execute
1766 # logic only if hooks are about to run.
1765 # logic only if hooks are about to run.
1767 #
1766 #
1768 # Fixing this limitation of the transaction is also needed to track
1767 # Fixing this limitation of the transaction is also needed to track
1769 # other families of changes (bookmarks, phases, obsolescence).
1768 # other families of changes (bookmarks, phases, obsolescence).
1770 #
1769 #
1771 # This will have to be fixed before we remove the experimental
1770 # This will have to be fixed before we remove the experimental
1772 # gating.
1771 # gating.
1773 tracktags(tr2)
1772 tracktags(tr2)
1774 repo = reporef()
1773 repo = reporef()
1775 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1774 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1776 scmutil.enforcesinglehead(repo, tr2, desc)
1775 scmutil.enforcesinglehead(repo, tr2, desc)
1777 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1776 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1778 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1777 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1779 args = tr.hookargs.copy()
1778 args = tr.hookargs.copy()
1780 args.update(bookmarks.preparehookargs(name, old, new))
1779 args.update(bookmarks.preparehookargs(name, old, new))
1781 repo.hook('pretxnclose-bookmark', throw=True,
1780 repo.hook('pretxnclose-bookmark', throw=True,
1782 txnname=desc,
1781 txnname=desc,
1783 **pycompat.strkwargs(args))
1782 **pycompat.strkwargs(args))
1784 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1783 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1785 cl = repo.unfiltered().changelog
1784 cl = repo.unfiltered().changelog
1786 for rev, (old, new) in tr.changes['phases'].items():
1785 for rev, (old, new) in tr.changes['phases'].items():
1787 args = tr.hookargs.copy()
1786 args = tr.hookargs.copy()
1788 node = hex(cl.node(rev))
1787 node = hex(cl.node(rev))
1789 args.update(phases.preparehookargs(node, old, new))
1788 args.update(phases.preparehookargs(node, old, new))
1790 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1789 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1791 **pycompat.strkwargs(args))
1790 **pycompat.strkwargs(args))
1792
1791
1793 repo.hook('pretxnclose', throw=True,
1792 repo.hook('pretxnclose', throw=True,
1794 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1793 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1795 def releasefn(tr, success):
1794 def releasefn(tr, success):
1796 repo = reporef()
1795 repo = reporef()
1797 if success:
1796 if success:
1798 # this should be explicitly invoked here, because
1797 # this should be explicitly invoked here, because
1799 # in-memory changes aren't written out at closing
1798 # in-memory changes aren't written out at closing
1800 # transaction, if tr.addfilegenerator (via
1799 # transaction, if tr.addfilegenerator (via
1801 # dirstate.write or so) isn't invoked while
1800 # dirstate.write or so) isn't invoked while
1802 # transaction running
1801 # transaction running
1803 repo.dirstate.write(None)
1802 repo.dirstate.write(None)
1804 else:
1803 else:
1805 # discard all changes (including ones already written
1804 # discard all changes (including ones already written
1806 # out) in this transaction
1805 # out) in this transaction
1807 narrowspec.restorebackup(self, 'journal.narrowspec')
1806 narrowspec.restorebackup(self, 'journal.narrowspec')
1808 repo.dirstate.restorebackup(None, 'journal.dirstate')
1807 repo.dirstate.restorebackup(None, 'journal.dirstate')
1809
1808
1810 repo.invalidate(clearfilecache=True)
1809 repo.invalidate(clearfilecache=True)
1811
1810
1812 tr = transaction.transaction(rp, self.svfs, vfsmap,
1811 tr = transaction.transaction(rp, self.svfs, vfsmap,
1813 "journal",
1812 "journal",
1814 "undo",
1813 "undo",
1815 aftertrans(renames),
1814 aftertrans(renames),
1816 self.store.createmode,
1815 self.store.createmode,
1817 validator=validate,
1816 validator=validate,
1818 releasefn=releasefn,
1817 releasefn=releasefn,
1819 checkambigfiles=_cachedfiles,
1818 checkambigfiles=_cachedfiles,
1820 name=desc)
1819 name=desc)
1821 tr.changes['origrepolen'] = len(self)
1820 tr.changes['origrepolen'] = len(self)
1822 tr.changes['obsmarkers'] = set()
1821 tr.changes['obsmarkers'] = set()
1823 tr.changes['phases'] = {}
1822 tr.changes['phases'] = {}
1824 tr.changes['bookmarks'] = {}
1823 tr.changes['bookmarks'] = {}
1825
1824
1826 tr.hookargs['txnid'] = txnid
1825 tr.hookargs['txnid'] = txnid
1827 # note: writing the fncache only during finalize mean that the file is
1826 # note: writing the fncache only during finalize mean that the file is
1828 # outdated when running hooks. As fncache is used for streaming clone,
1827 # outdated when running hooks. As fncache is used for streaming clone,
1829 # this is not expected to break anything that happen during the hooks.
1828 # this is not expected to break anything that happen during the hooks.
1830 tr.addfinalize('flush-fncache', self.store.write)
1829 tr.addfinalize('flush-fncache', self.store.write)
1831 def txnclosehook(tr2):
1830 def txnclosehook(tr2):
1832 """To be run if transaction is successful, will schedule a hook run
1831 """To be run if transaction is successful, will schedule a hook run
1833 """
1832 """
1834 # Don't reference tr2 in hook() so we don't hold a reference.
1833 # Don't reference tr2 in hook() so we don't hold a reference.
1835 # This reduces memory consumption when there are multiple
1834 # This reduces memory consumption when there are multiple
1836 # transactions per lock. This can likely go away if issue5045
1835 # transactions per lock. This can likely go away if issue5045
1837 # fixes the function accumulation.
1836 # fixes the function accumulation.
1838 hookargs = tr2.hookargs
1837 hookargs = tr2.hookargs
1839
1838
1840 def hookfunc():
1839 def hookfunc():
1841 repo = reporef()
1840 repo = reporef()
1842 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1841 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1843 bmchanges = sorted(tr.changes['bookmarks'].items())
1842 bmchanges = sorted(tr.changes['bookmarks'].items())
1844 for name, (old, new) in bmchanges:
1843 for name, (old, new) in bmchanges:
1845 args = tr.hookargs.copy()
1844 args = tr.hookargs.copy()
1846 args.update(bookmarks.preparehookargs(name, old, new))
1845 args.update(bookmarks.preparehookargs(name, old, new))
1847 repo.hook('txnclose-bookmark', throw=False,
1846 repo.hook('txnclose-bookmark', throw=False,
1848 txnname=desc, **pycompat.strkwargs(args))
1847 txnname=desc, **pycompat.strkwargs(args))
1849
1848
1850 if hook.hashook(repo.ui, 'txnclose-phase'):
1849 if hook.hashook(repo.ui, 'txnclose-phase'):
1851 cl = repo.unfiltered().changelog
1850 cl = repo.unfiltered().changelog
1852 phasemv = sorted(tr.changes['phases'].items())
1851 phasemv = sorted(tr.changes['phases'].items())
1853 for rev, (old, new) in phasemv:
1852 for rev, (old, new) in phasemv:
1854 args = tr.hookargs.copy()
1853 args = tr.hookargs.copy()
1855 node = hex(cl.node(rev))
1854 node = hex(cl.node(rev))
1856 args.update(phases.preparehookargs(node, old, new))
1855 args.update(phases.preparehookargs(node, old, new))
1857 repo.hook('txnclose-phase', throw=False, txnname=desc,
1856 repo.hook('txnclose-phase', throw=False, txnname=desc,
1858 **pycompat.strkwargs(args))
1857 **pycompat.strkwargs(args))
1859
1858
1860 repo.hook('txnclose', throw=False, txnname=desc,
1859 repo.hook('txnclose', throw=False, txnname=desc,
1861 **pycompat.strkwargs(hookargs))
1860 **pycompat.strkwargs(hookargs))
1862 reporef()._afterlock(hookfunc)
1861 reporef()._afterlock(hookfunc)
1863 tr.addfinalize('txnclose-hook', txnclosehook)
1862 tr.addfinalize('txnclose-hook', txnclosehook)
1864 # Include a leading "-" to make it happen before the transaction summary
1863 # Include a leading "-" to make it happen before the transaction summary
1865 # reports registered via scmutil.registersummarycallback() whose names
1864 # reports registered via scmutil.registersummarycallback() whose names
1866 # are 00-txnreport etc. That way, the caches will be warm when the
1865 # are 00-txnreport etc. That way, the caches will be warm when the
1867 # callbacks run.
1866 # callbacks run.
1868 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1867 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1869 def txnaborthook(tr2):
1868 def txnaborthook(tr2):
1870 """To be run if transaction is aborted
1869 """To be run if transaction is aborted
1871 """
1870 """
1872 reporef().hook('txnabort', throw=False, txnname=desc,
1871 reporef().hook('txnabort', throw=False, txnname=desc,
1873 **pycompat.strkwargs(tr2.hookargs))
1872 **pycompat.strkwargs(tr2.hookargs))
1874 tr.addabort('txnabort-hook', txnaborthook)
1873 tr.addabort('txnabort-hook', txnaborthook)
1875 # avoid eager cache invalidation. in-memory data should be identical
1874 # avoid eager cache invalidation. in-memory data should be identical
1876 # to stored data if transaction has no error.
1875 # to stored data if transaction has no error.
1877 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1876 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1878 self._transref = weakref.ref(tr)
1877 self._transref = weakref.ref(tr)
1879 scmutil.registersummarycallback(self, tr, desc)
1878 scmutil.registersummarycallback(self, tr, desc)
1880 return tr
1879 return tr
1881
1880
1882 def _journalfiles(self):
1881 def _journalfiles(self):
1883 return ((self.svfs, 'journal'),
1882 return ((self.svfs, 'journal'),
1884 (self.vfs, 'journal.dirstate'),
1883 (self.vfs, 'journal.dirstate'),
1885 (self.vfs, 'journal.branch'),
1884 (self.vfs, 'journal.branch'),
1886 (self.vfs, 'journal.desc'),
1885 (self.vfs, 'journal.desc'),
1887 (self.vfs, 'journal.bookmarks'),
1886 (self.vfs, 'journal.bookmarks'),
1888 (self.svfs, 'journal.phaseroots'))
1887 (self.svfs, 'journal.phaseroots'))
1889
1888
1890 def undofiles(self):
1889 def undofiles(self):
1891 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1890 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1892
1891
1893 @unfilteredmethod
1892 @unfilteredmethod
1894 def _writejournal(self, desc):
1893 def _writejournal(self, desc):
1895 self.dirstate.savebackup(None, 'journal.dirstate')
1894 self.dirstate.savebackup(None, 'journal.dirstate')
1896 narrowspec.savebackup(self, 'journal.narrowspec')
1895 narrowspec.savebackup(self, 'journal.narrowspec')
1897 self.vfs.write("journal.branch",
1896 self.vfs.write("journal.branch",
1898 encoding.fromlocal(self.dirstate.branch()))
1897 encoding.fromlocal(self.dirstate.branch()))
1899 self.vfs.write("journal.desc",
1898 self.vfs.write("journal.desc",
1900 "%d\n%s\n" % (len(self), desc))
1899 "%d\n%s\n" % (len(self), desc))
1901 self.vfs.write("journal.bookmarks",
1900 self.vfs.write("journal.bookmarks",
1902 self.vfs.tryread("bookmarks"))
1901 self.vfs.tryread("bookmarks"))
1903 self.svfs.write("journal.phaseroots",
1902 self.svfs.write("journal.phaseroots",
1904 self.svfs.tryread("phaseroots"))
1903 self.svfs.tryread("phaseroots"))
1905
1904
1906 def recover(self):
1905 def recover(self):
1907 with self.lock():
1906 with self.lock():
1908 if self.svfs.exists("journal"):
1907 if self.svfs.exists("journal"):
1909 self.ui.status(_("rolling back interrupted transaction\n"))
1908 self.ui.status(_("rolling back interrupted transaction\n"))
1910 vfsmap = {'': self.svfs,
1909 vfsmap = {'': self.svfs,
1911 'plain': self.vfs,}
1910 'plain': self.vfs,}
1912 transaction.rollback(self.svfs, vfsmap, "journal",
1911 transaction.rollback(self.svfs, vfsmap, "journal",
1913 self.ui.warn,
1912 self.ui.warn,
1914 checkambigfiles=_cachedfiles)
1913 checkambigfiles=_cachedfiles)
1915 self.invalidate()
1914 self.invalidate()
1916 return True
1915 return True
1917 else:
1916 else:
1918 self.ui.warn(_("no interrupted transaction available\n"))
1917 self.ui.warn(_("no interrupted transaction available\n"))
1919 return False
1918 return False
1920
1919
1921 def rollback(self, dryrun=False, force=False):
1920 def rollback(self, dryrun=False, force=False):
1922 wlock = lock = dsguard = None
1921 wlock = lock = dsguard = None
1923 try:
1922 try:
1924 wlock = self.wlock()
1923 wlock = self.wlock()
1925 lock = self.lock()
1924 lock = self.lock()
1926 if self.svfs.exists("undo"):
1925 if self.svfs.exists("undo"):
1927 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1926 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1928
1927
1929 return self._rollback(dryrun, force, dsguard)
1928 return self._rollback(dryrun, force, dsguard)
1930 else:
1929 else:
1931 self.ui.warn(_("no rollback information available\n"))
1930 self.ui.warn(_("no rollback information available\n"))
1932 return 1
1931 return 1
1933 finally:
1932 finally:
1934 release(dsguard, lock, wlock)
1933 release(dsguard, lock, wlock)
1935
1934
1936 @unfilteredmethod # Until we get smarter cache management
1935 @unfilteredmethod # Until we get smarter cache management
1937 def _rollback(self, dryrun, force, dsguard):
1936 def _rollback(self, dryrun, force, dsguard):
1938 ui = self.ui
1937 ui = self.ui
1939 try:
1938 try:
1940 args = self.vfs.read('undo.desc').splitlines()
1939 args = self.vfs.read('undo.desc').splitlines()
1941 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1940 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1942 if len(args) >= 3:
1941 if len(args) >= 3:
1943 detail = args[2]
1942 detail = args[2]
1944 oldtip = oldlen - 1
1943 oldtip = oldlen - 1
1945
1944
1946 if detail and ui.verbose:
1945 if detail and ui.verbose:
1947 msg = (_('repository tip rolled back to revision %d'
1946 msg = (_('repository tip rolled back to revision %d'
1948 ' (undo %s: %s)\n')
1947 ' (undo %s: %s)\n')
1949 % (oldtip, desc, detail))
1948 % (oldtip, desc, detail))
1950 else:
1949 else:
1951 msg = (_('repository tip rolled back to revision %d'
1950 msg = (_('repository tip rolled back to revision %d'
1952 ' (undo %s)\n')
1951 ' (undo %s)\n')
1953 % (oldtip, desc))
1952 % (oldtip, desc))
1954 except IOError:
1953 except IOError:
1955 msg = _('rolling back unknown transaction\n')
1954 msg = _('rolling back unknown transaction\n')
1956 desc = None
1955 desc = None
1957
1956
1958 if not force and self['.'] != self['tip'] and desc == 'commit':
1957 if not force and self['.'] != self['tip'] and desc == 'commit':
1959 raise error.Abort(
1958 raise error.Abort(
1960 _('rollback of last commit while not checked out '
1959 _('rollback of last commit while not checked out '
1961 'may lose data'), hint=_('use -f to force'))
1960 'may lose data'), hint=_('use -f to force'))
1962
1961
1963 ui.status(msg)
1962 ui.status(msg)
1964 if dryrun:
1963 if dryrun:
1965 return 0
1964 return 0
1966
1965
1967 parents = self.dirstate.parents()
1966 parents = self.dirstate.parents()
1968 self.destroying()
1967 self.destroying()
1969 vfsmap = {'plain': self.vfs, '': self.svfs}
1968 vfsmap = {'plain': self.vfs, '': self.svfs}
1970 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1969 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1971 checkambigfiles=_cachedfiles)
1970 checkambigfiles=_cachedfiles)
1972 if self.vfs.exists('undo.bookmarks'):
1971 if self.vfs.exists('undo.bookmarks'):
1973 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1972 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1974 if self.svfs.exists('undo.phaseroots'):
1973 if self.svfs.exists('undo.phaseroots'):
1975 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1974 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1976 self.invalidate()
1975 self.invalidate()
1977
1976
1978 parentgone = (parents[0] not in self.changelog.nodemap or
1977 parentgone = (parents[0] not in self.changelog.nodemap or
1979 parents[1] not in self.changelog.nodemap)
1978 parents[1] not in self.changelog.nodemap)
1980 if parentgone:
1979 if parentgone:
1981 # prevent dirstateguard from overwriting already restored one
1980 # prevent dirstateguard from overwriting already restored one
1982 dsguard.close()
1981 dsguard.close()
1983
1982
1984 narrowspec.restorebackup(self, 'undo.narrowspec')
1983 narrowspec.restorebackup(self, 'undo.narrowspec')
1985 self.dirstate.restorebackup(None, 'undo.dirstate')
1984 self.dirstate.restorebackup(None, 'undo.dirstate')
1986 try:
1985 try:
1987 branch = self.vfs.read('undo.branch')
1986 branch = self.vfs.read('undo.branch')
1988 self.dirstate.setbranch(encoding.tolocal(branch))
1987 self.dirstate.setbranch(encoding.tolocal(branch))
1989 except IOError:
1988 except IOError:
1990 ui.warn(_('named branch could not be reset: '
1989 ui.warn(_('named branch could not be reset: '
1991 'current branch is still \'%s\'\n')
1990 'current branch is still \'%s\'\n')
1992 % self.dirstate.branch())
1991 % self.dirstate.branch())
1993
1992
1994 parents = tuple([p.rev() for p in self[None].parents()])
1993 parents = tuple([p.rev() for p in self[None].parents()])
1995 if len(parents) > 1:
1994 if len(parents) > 1:
1996 ui.status(_('working directory now based on '
1995 ui.status(_('working directory now based on '
1997 'revisions %d and %d\n') % parents)
1996 'revisions %d and %d\n') % parents)
1998 else:
1997 else:
1999 ui.status(_('working directory now based on '
1998 ui.status(_('working directory now based on '
2000 'revision %d\n') % parents)
1999 'revision %d\n') % parents)
2001 mergemod.mergestate.clean(self, self['.'].node())
2000 mergemod.mergestate.clean(self, self['.'].node())
2002
2001
2003 # TODO: if we know which new heads may result from this rollback, pass
2002 # TODO: if we know which new heads may result from this rollback, pass
2004 # them to destroy(), which will prevent the branchhead cache from being
2003 # them to destroy(), which will prevent the branchhead cache from being
2005 # invalidated.
2004 # invalidated.
2006 self.destroyed()
2005 self.destroyed()
2007 return 0
2006 return 0
2008
2007
2009 def _buildcacheupdater(self, newtransaction):
2008 def _buildcacheupdater(self, newtransaction):
2010 """called during transaction to build the callback updating cache
2009 """called during transaction to build the callback updating cache
2011
2010
2012 Lives on the repository to help extension who might want to augment
2011 Lives on the repository to help extension who might want to augment
2013 this logic. For this purpose, the created transaction is passed to the
2012 this logic. For this purpose, the created transaction is passed to the
2014 method.
2013 method.
2015 """
2014 """
2016 # we must avoid cyclic reference between repo and transaction.
2015 # we must avoid cyclic reference between repo and transaction.
2017 reporef = weakref.ref(self)
2016 reporef = weakref.ref(self)
2018 def updater(tr):
2017 def updater(tr):
2019 repo = reporef()
2018 repo = reporef()
2020 repo.updatecaches(tr)
2019 repo.updatecaches(tr)
2021 return updater
2020 return updater
2022
2021
2023 @unfilteredmethod
2022 @unfilteredmethod
2024 def updatecaches(self, tr=None, full=False):
2023 def updatecaches(self, tr=None, full=False):
2025 """warm appropriate caches
2024 """warm appropriate caches
2026
2025
2027 If this function is called after a transaction closed. The transaction
2026 If this function is called after a transaction closed. The transaction
2028 will be available in the 'tr' argument. This can be used to selectively
2027 will be available in the 'tr' argument. This can be used to selectively
2029 update caches relevant to the changes in that transaction.
2028 update caches relevant to the changes in that transaction.
2030
2029
2031 If 'full' is set, make sure all caches the function knows about have
2030 If 'full' is set, make sure all caches the function knows about have
2032 up-to-date data. Even the ones usually loaded more lazily.
2031 up-to-date data. Even the ones usually loaded more lazily.
2033 """
2032 """
2034 if tr is not None and tr.hookargs.get('source') == 'strip':
2033 if tr is not None and tr.hookargs.get('source') == 'strip':
2035 # During strip, many caches are invalid but
2034 # During strip, many caches are invalid but
2036 # later call to `destroyed` will refresh them.
2035 # later call to `destroyed` will refresh them.
2037 return
2036 return
2038
2037
2039 if tr is None or tr.changes['origrepolen'] < len(self):
2038 if tr is None or tr.changes['origrepolen'] < len(self):
2040 # updating the unfiltered branchmap should refresh all the others,
2039 # updating the unfiltered branchmap should refresh all the others,
2041 self.ui.debug('updating the branch cache\n')
2040 self.ui.debug('updating the branch cache\n')
2042 branchmap.updatecache(self.filtered('served'))
2041 branchmap.updatecache(self.filtered('served'))
2043
2042
2044 if full:
2043 if full:
2045 rbc = self.revbranchcache()
2044 rbc = self.revbranchcache()
2046 for r in self.changelog:
2045 for r in self.changelog:
2047 rbc.branchinfo(r)
2046 rbc.branchinfo(r)
2048 rbc.write()
2047 rbc.write()
2049
2048
2050 # ensure the working copy parents are in the manifestfulltextcache
2049 # ensure the working copy parents are in the manifestfulltextcache
2051 for ctx in self['.'].parents():
2050 for ctx in self['.'].parents():
2052 ctx.manifest() # accessing the manifest is enough
2051 ctx.manifest() # accessing the manifest is enough
2053
2052
2054 def invalidatecaches(self):
2053 def invalidatecaches(self):
2055
2054
2056 if r'_tagscache' in vars(self):
2055 if r'_tagscache' in vars(self):
2057 # can't use delattr on proxy
2056 # can't use delattr on proxy
2058 del self.__dict__[r'_tagscache']
2057 del self.__dict__[r'_tagscache']
2059
2058
2060 self.unfiltered()._branchcaches.clear()
2059 self.unfiltered()._branchcaches.clear()
2061 self.invalidatevolatilesets()
2060 self.invalidatevolatilesets()
2062 self._sparsesignaturecache.clear()
2061 self._sparsesignaturecache.clear()
2063
2062
2064 def invalidatevolatilesets(self):
2063 def invalidatevolatilesets(self):
2065 self.filteredrevcache.clear()
2064 self.filteredrevcache.clear()
2066 obsolete.clearobscaches(self)
2065 obsolete.clearobscaches(self)
2067
2066
2068 def invalidatedirstate(self):
2067 def invalidatedirstate(self):
2069 '''Invalidates the dirstate, causing the next call to dirstate
2068 '''Invalidates the dirstate, causing the next call to dirstate
2070 to check if it was modified since the last time it was read,
2069 to check if it was modified since the last time it was read,
2071 rereading it if it has.
2070 rereading it if it has.
2072
2071
2073 This is different to dirstate.invalidate() that it doesn't always
2072 This is different to dirstate.invalidate() that it doesn't always
2074 rereads the dirstate. Use dirstate.invalidate() if you want to
2073 rereads the dirstate. Use dirstate.invalidate() if you want to
2075 explicitly read the dirstate again (i.e. restoring it to a previous
2074 explicitly read the dirstate again (i.e. restoring it to a previous
2076 known good state).'''
2075 known good state).'''
2077 if hasunfilteredcache(self, r'dirstate'):
2076 if hasunfilteredcache(self, r'dirstate'):
2078 for k in self.dirstate._filecache:
2077 for k in self.dirstate._filecache:
2079 try:
2078 try:
2080 delattr(self.dirstate, k)
2079 delattr(self.dirstate, k)
2081 except AttributeError:
2080 except AttributeError:
2082 pass
2081 pass
2083 delattr(self.unfiltered(), r'dirstate')
2082 delattr(self.unfiltered(), r'dirstate')
2084
2083
2085 def invalidate(self, clearfilecache=False):
2084 def invalidate(self, clearfilecache=False):
2086 '''Invalidates both store and non-store parts other than dirstate
2085 '''Invalidates both store and non-store parts other than dirstate
2087
2086
2088 If a transaction is running, invalidation of store is omitted,
2087 If a transaction is running, invalidation of store is omitted,
2089 because discarding in-memory changes might cause inconsistency
2088 because discarding in-memory changes might cause inconsistency
2090 (e.g. incomplete fncache causes unintentional failure, but
2089 (e.g. incomplete fncache causes unintentional failure, but
2091 redundant one doesn't).
2090 redundant one doesn't).
2092 '''
2091 '''
2093 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2092 unfiltered = self.unfiltered() # all file caches are stored unfiltered
2094 for k in list(self._filecache.keys()):
2093 for k in list(self._filecache.keys()):
2095 # dirstate is invalidated separately in invalidatedirstate()
2094 # dirstate is invalidated separately in invalidatedirstate()
2096 if k == 'dirstate':
2095 if k == 'dirstate':
2097 continue
2096 continue
2098 if (k == 'changelog' and
2097 if (k == 'changelog' and
2099 self.currenttransaction() and
2098 self.currenttransaction() and
2100 self.changelog._delayed):
2099 self.changelog._delayed):
2101 # The changelog object may store unwritten revisions. We don't
2100 # The changelog object may store unwritten revisions. We don't
2102 # want to lose them.
2101 # want to lose them.
2103 # TODO: Solve the problem instead of working around it.
2102 # TODO: Solve the problem instead of working around it.
2104 continue
2103 continue
2105
2104
2106 if clearfilecache:
2105 if clearfilecache:
2107 del self._filecache[k]
2106 del self._filecache[k]
2108 try:
2107 try:
2109 delattr(unfiltered, k)
2108 delattr(unfiltered, k)
2110 except AttributeError:
2109 except AttributeError:
2111 pass
2110 pass
2112 self.invalidatecaches()
2111 self.invalidatecaches()
2113 if not self.currenttransaction():
2112 if not self.currenttransaction():
2114 # TODO: Changing contents of store outside transaction
2113 # TODO: Changing contents of store outside transaction
2115 # causes inconsistency. We should make in-memory store
2114 # causes inconsistency. We should make in-memory store
2116 # changes detectable, and abort if changed.
2115 # changes detectable, and abort if changed.
2117 self.store.invalidatecaches()
2116 self.store.invalidatecaches()
2118
2117
2119 def invalidateall(self):
2118 def invalidateall(self):
2120 '''Fully invalidates both store and non-store parts, causing the
2119 '''Fully invalidates both store and non-store parts, causing the
2121 subsequent operation to reread any outside changes.'''
2120 subsequent operation to reread any outside changes.'''
2122 # extension should hook this to invalidate its caches
2121 # extension should hook this to invalidate its caches
2123 self.invalidate()
2122 self.invalidate()
2124 self.invalidatedirstate()
2123 self.invalidatedirstate()
2125
2124
2126 @unfilteredmethod
2125 @unfilteredmethod
2127 def _refreshfilecachestats(self, tr):
2126 def _refreshfilecachestats(self, tr):
2128 """Reload stats of cached files so that they are flagged as valid"""
2127 """Reload stats of cached files so that they are flagged as valid"""
2129 for k, ce in self._filecache.items():
2128 for k, ce in self._filecache.items():
2130 k = pycompat.sysstr(k)
2129 k = pycompat.sysstr(k)
2131 if k == r'dirstate' or k not in self.__dict__:
2130 if k == r'dirstate' or k not in self.__dict__:
2132 continue
2131 continue
2133 ce.refresh()
2132 ce.refresh()
2134
2133
2135 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2134 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
2136 inheritchecker=None, parentenvvar=None):
2135 inheritchecker=None, parentenvvar=None):
2137 parentlock = None
2136 parentlock = None
2138 # the contents of parentenvvar are used by the underlying lock to
2137 # the contents of parentenvvar are used by the underlying lock to
2139 # determine whether it can be inherited
2138 # determine whether it can be inherited
2140 if parentenvvar is not None:
2139 if parentenvvar is not None:
2141 parentlock = encoding.environ.get(parentenvvar)
2140 parentlock = encoding.environ.get(parentenvvar)
2142
2141
2143 timeout = 0
2142 timeout = 0
2144 warntimeout = 0
2143 warntimeout = 0
2145 if wait:
2144 if wait:
2146 timeout = self.ui.configint("ui", "timeout")
2145 timeout = self.ui.configint("ui", "timeout")
2147 warntimeout = self.ui.configint("ui", "timeout.warn")
2146 warntimeout = self.ui.configint("ui", "timeout.warn")
2148 # internal config: ui.signal-safe-lock
2147 # internal config: ui.signal-safe-lock
2149 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2148 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
2150
2149
2151 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2150 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
2152 releasefn=releasefn,
2151 releasefn=releasefn,
2153 acquirefn=acquirefn, desc=desc,
2152 acquirefn=acquirefn, desc=desc,
2154 inheritchecker=inheritchecker,
2153 inheritchecker=inheritchecker,
2155 parentlock=parentlock,
2154 parentlock=parentlock,
2156 signalsafe=signalsafe)
2155 signalsafe=signalsafe)
2157 return l
2156 return l
2158
2157
2159 def _afterlock(self, callback):
2158 def _afterlock(self, callback):
2160 """add a callback to be run when the repository is fully unlocked
2159 """add a callback to be run when the repository is fully unlocked
2161
2160
2162 The callback will be executed when the outermost lock is released
2161 The callback will be executed when the outermost lock is released
2163 (with wlock being higher level than 'lock')."""
2162 (with wlock being higher level than 'lock')."""
2164 for ref in (self._wlockref, self._lockref):
2163 for ref in (self._wlockref, self._lockref):
2165 l = ref and ref()
2164 l = ref and ref()
2166 if l and l.held:
2165 if l and l.held:
2167 l.postrelease.append(callback)
2166 l.postrelease.append(callback)
2168 break
2167 break
2169 else: # no lock have been found.
2168 else: # no lock have been found.
2170 callback()
2169 callback()
2171
2170
2172 def lock(self, wait=True):
2171 def lock(self, wait=True):
2173 '''Lock the repository store (.hg/store) and return a weak reference
2172 '''Lock the repository store (.hg/store) and return a weak reference
2174 to the lock. Use this before modifying the store (e.g. committing or
2173 to the lock. Use this before modifying the store (e.g. committing or
2175 stripping). If you are opening a transaction, get a lock as well.)
2174 stripping). If you are opening a transaction, get a lock as well.)
2176
2175
2177 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2176 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2178 'wlock' first to avoid a dead-lock hazard.'''
2177 'wlock' first to avoid a dead-lock hazard.'''
2179 l = self._currentlock(self._lockref)
2178 l = self._currentlock(self._lockref)
2180 if l is not None:
2179 if l is not None:
2181 l.lock()
2180 l.lock()
2182 return l
2181 return l
2183
2182
2184 l = self._lock(self.svfs, "lock", wait, None,
2183 l = self._lock(self.svfs, "lock", wait, None,
2185 self.invalidate, _('repository %s') % self.origroot)
2184 self.invalidate, _('repository %s') % self.origroot)
2186 self._lockref = weakref.ref(l)
2185 self._lockref = weakref.ref(l)
2187 return l
2186 return l
2188
2187
2189 def _wlockchecktransaction(self):
2188 def _wlockchecktransaction(self):
2190 if self.currenttransaction() is not None:
2189 if self.currenttransaction() is not None:
2191 raise error.LockInheritanceContractViolation(
2190 raise error.LockInheritanceContractViolation(
2192 'wlock cannot be inherited in the middle of a transaction')
2191 'wlock cannot be inherited in the middle of a transaction')
2193
2192
2194 def wlock(self, wait=True):
2193 def wlock(self, wait=True):
2195 '''Lock the non-store parts of the repository (everything under
2194 '''Lock the non-store parts of the repository (everything under
2196 .hg except .hg/store) and return a weak reference to the lock.
2195 .hg except .hg/store) and return a weak reference to the lock.
2197
2196
2198 Use this before modifying files in .hg.
2197 Use this before modifying files in .hg.
2199
2198
2200 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2199 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
2201 'wlock' first to avoid a dead-lock hazard.'''
2200 'wlock' first to avoid a dead-lock hazard.'''
2202 l = self._wlockref and self._wlockref()
2201 l = self._wlockref and self._wlockref()
2203 if l is not None and l.held:
2202 if l is not None and l.held:
2204 l.lock()
2203 l.lock()
2205 return l
2204 return l
2206
2205
2207 # We do not need to check for non-waiting lock acquisition. Such
2206 # We do not need to check for non-waiting lock acquisition. Such
2208 # acquisition would not cause dead-lock as they would just fail.
2207 # acquisition would not cause dead-lock as they would just fail.
2209 if wait and (self.ui.configbool('devel', 'all-warnings')
2208 if wait and (self.ui.configbool('devel', 'all-warnings')
2210 or self.ui.configbool('devel', 'check-locks')):
2209 or self.ui.configbool('devel', 'check-locks')):
2211 if self._currentlock(self._lockref) is not None:
2210 if self._currentlock(self._lockref) is not None:
2212 self.ui.develwarn('"wlock" acquired after "lock"')
2211 self.ui.develwarn('"wlock" acquired after "lock"')
2213
2212
2214 def unlock():
2213 def unlock():
2215 if self.dirstate.pendingparentchange():
2214 if self.dirstate.pendingparentchange():
2216 self.dirstate.invalidate()
2215 self.dirstate.invalidate()
2217 else:
2216 else:
2218 self.dirstate.write(None)
2217 self.dirstate.write(None)
2219
2218
2220 self._filecache['dirstate'].refresh()
2219 self._filecache['dirstate'].refresh()
2221
2220
2222 l = self._lock(self.vfs, "wlock", wait, unlock,
2221 l = self._lock(self.vfs, "wlock", wait, unlock,
2223 self.invalidatedirstate, _('working directory of %s') %
2222 self.invalidatedirstate, _('working directory of %s') %
2224 self.origroot,
2223 self.origroot,
2225 inheritchecker=self._wlockchecktransaction,
2224 inheritchecker=self._wlockchecktransaction,
2226 parentenvvar='HG_WLOCK_LOCKER')
2225 parentenvvar='HG_WLOCK_LOCKER')
2227 self._wlockref = weakref.ref(l)
2226 self._wlockref = weakref.ref(l)
2228 return l
2227 return l
2229
2228
2230 def _currentlock(self, lockref):
2229 def _currentlock(self, lockref):
2231 """Returns the lock if it's held, or None if it's not."""
2230 """Returns the lock if it's held, or None if it's not."""
2232 if lockref is None:
2231 if lockref is None:
2233 return None
2232 return None
2234 l = lockref()
2233 l = lockref()
2235 if l is None or not l.held:
2234 if l is None or not l.held:
2236 return None
2235 return None
2237 return l
2236 return l
2238
2237
2239 def currentwlock(self):
2238 def currentwlock(self):
2240 """Returns the wlock if it's held, or None if it's not."""
2239 """Returns the wlock if it's held, or None if it's not."""
2241 return self._currentlock(self._wlockref)
2240 return self._currentlock(self._wlockref)
2242
2241
2243 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
2242 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
2244 """
2243 """
2245 commit an individual file as part of a larger transaction
2244 commit an individual file as part of a larger transaction
2246 """
2245 """
2247
2246
2248 fname = fctx.path()
2247 fname = fctx.path()
2249 fparent1 = manifest1.get(fname, nullid)
2248 fparent1 = manifest1.get(fname, nullid)
2250 fparent2 = manifest2.get(fname, nullid)
2249 fparent2 = manifest2.get(fname, nullid)
2251 if isinstance(fctx, context.filectx):
2250 if isinstance(fctx, context.filectx):
2252 node = fctx.filenode()
2251 node = fctx.filenode()
2253 if node in [fparent1, fparent2]:
2252 if node in [fparent1, fparent2]:
2254 self.ui.debug('reusing %s filelog entry\n' % fname)
2253 self.ui.debug('reusing %s filelog entry\n' % fname)
2255 if manifest1.flags(fname) != fctx.flags():
2254 if manifest1.flags(fname) != fctx.flags():
2256 changelist.append(fname)
2255 changelist.append(fname)
2257 return node
2256 return node
2258
2257
2259 flog = self.file(fname)
2258 flog = self.file(fname)
2260 meta = {}
2259 meta = {}
2261 copy = fctx.renamed()
2260 copy = fctx.renamed()
2262 if copy and copy[0] != fname:
2261 if copy and copy[0] != fname:
2263 # Mark the new revision of this file as a copy of another
2262 # Mark the new revision of this file as a copy of another
2264 # file. This copy data will effectively act as a parent
2263 # file. This copy data will effectively act as a parent
2265 # of this new revision. If this is a merge, the first
2264 # of this new revision. If this is a merge, the first
2266 # parent will be the nullid (meaning "look up the copy data")
2265 # parent will be the nullid (meaning "look up the copy data")
2267 # and the second one will be the other parent. For example:
2266 # and the second one will be the other parent. For example:
2268 #
2267 #
2269 # 0 --- 1 --- 3 rev1 changes file foo
2268 # 0 --- 1 --- 3 rev1 changes file foo
2270 # \ / rev2 renames foo to bar and changes it
2269 # \ / rev2 renames foo to bar and changes it
2271 # \- 2 -/ rev3 should have bar with all changes and
2270 # \- 2 -/ rev3 should have bar with all changes and
2272 # should record that bar descends from
2271 # should record that bar descends from
2273 # bar in rev2 and foo in rev1
2272 # bar in rev2 and foo in rev1
2274 #
2273 #
2275 # this allows this merge to succeed:
2274 # this allows this merge to succeed:
2276 #
2275 #
2277 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2276 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
2278 # \ / merging rev3 and rev4 should use bar@rev2
2277 # \ / merging rev3 and rev4 should use bar@rev2
2279 # \- 2 --- 4 as the merge base
2278 # \- 2 --- 4 as the merge base
2280 #
2279 #
2281
2280
2282 cfname = copy[0]
2281 cfname = copy[0]
2283 crev = manifest1.get(cfname)
2282 crev = manifest1.get(cfname)
2284 newfparent = fparent2
2283 newfparent = fparent2
2285
2284
2286 if manifest2: # branch merge
2285 if manifest2: # branch merge
2287 if fparent2 == nullid or crev is None: # copied on remote side
2286 if fparent2 == nullid or crev is None: # copied on remote side
2288 if cfname in manifest2:
2287 if cfname in manifest2:
2289 crev = manifest2[cfname]
2288 crev = manifest2[cfname]
2290 newfparent = fparent1
2289 newfparent = fparent1
2291
2290
2292 # Here, we used to search backwards through history to try to find
2291 # Here, we used to search backwards through history to try to find
2293 # where the file copy came from if the source of a copy was not in
2292 # where the file copy came from if the source of a copy was not in
2294 # the parent directory. However, this doesn't actually make sense to
2293 # the parent directory. However, this doesn't actually make sense to
2295 # do (what does a copy from something not in your working copy even
2294 # do (what does a copy from something not in your working copy even
2296 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2295 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
2297 # the user that copy information was dropped, so if they didn't
2296 # the user that copy information was dropped, so if they didn't
2298 # expect this outcome it can be fixed, but this is the correct
2297 # expect this outcome it can be fixed, but this is the correct
2299 # behavior in this circumstance.
2298 # behavior in this circumstance.
2300
2299
2301 if crev:
2300 if crev:
2302 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
2301 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
2303 meta["copy"] = cfname
2302 meta["copy"] = cfname
2304 meta["copyrev"] = hex(crev)
2303 meta["copyrev"] = hex(crev)
2305 fparent1, fparent2 = nullid, newfparent
2304 fparent1, fparent2 = nullid, newfparent
2306 else:
2305 else:
2307 self.ui.warn(_("warning: can't find ancestor for '%s' "
2306 self.ui.warn(_("warning: can't find ancestor for '%s' "
2308 "copied from '%s'!\n") % (fname, cfname))
2307 "copied from '%s'!\n") % (fname, cfname))
2309
2308
2310 elif fparent1 == nullid:
2309 elif fparent1 == nullid:
2311 fparent1, fparent2 = fparent2, nullid
2310 fparent1, fparent2 = fparent2, nullid
2312 elif fparent2 != nullid:
2311 elif fparent2 != nullid:
2313 # is one parent an ancestor of the other?
2312 # is one parent an ancestor of the other?
2314 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2313 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
2315 if fparent1 in fparentancestors:
2314 if fparent1 in fparentancestors:
2316 fparent1, fparent2 = fparent2, nullid
2315 fparent1, fparent2 = fparent2, nullid
2317 elif fparent2 in fparentancestors:
2316 elif fparent2 in fparentancestors:
2318 fparent2 = nullid
2317 fparent2 = nullid
2319
2318
2320 # is the file changed?
2319 # is the file changed?
2321 text = fctx.data()
2320 text = fctx.data()
2322 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2321 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
2323 changelist.append(fname)
2322 changelist.append(fname)
2324 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2323 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
2325 # are just the flags changed during merge?
2324 # are just the flags changed during merge?
2326 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2325 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
2327 changelist.append(fname)
2326 changelist.append(fname)
2328
2327
2329 return fparent1
2328 return fparent1
2330
2329
2331 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2330 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
2332 """check for commit arguments that aren't committable"""
2331 """check for commit arguments that aren't committable"""
2333 if match.isexact() or match.prefix():
2332 if match.isexact() or match.prefix():
2334 matched = set(status.modified + status.added + status.removed)
2333 matched = set(status.modified + status.added + status.removed)
2335
2334
2336 for f in match.files():
2335 for f in match.files():
2337 f = self.dirstate.normalize(f)
2336 f = self.dirstate.normalize(f)
2338 if f == '.' or f in matched or f in wctx.substate:
2337 if f == '.' or f in matched or f in wctx.substate:
2339 continue
2338 continue
2340 if f in status.deleted:
2339 if f in status.deleted:
2341 fail(f, _('file not found!'))
2340 fail(f, _('file not found!'))
2342 if f in vdirs: # visited directory
2341 if f in vdirs: # visited directory
2343 d = f + '/'
2342 d = f + '/'
2344 for mf in matched:
2343 for mf in matched:
2345 if mf.startswith(d):
2344 if mf.startswith(d):
2346 break
2345 break
2347 else:
2346 else:
2348 fail(f, _("no match under directory!"))
2347 fail(f, _("no match under directory!"))
2349 elif f not in self.dirstate:
2348 elif f not in self.dirstate:
2350 fail(f, _("file not tracked!"))
2349 fail(f, _("file not tracked!"))
2351
2350
2352 @unfilteredmethod
2351 @unfilteredmethod
2353 def commit(self, text="", user=None, date=None, match=None, force=False,
2352 def commit(self, text="", user=None, date=None, match=None, force=False,
2354 editor=False, extra=None):
2353 editor=False, extra=None):
2355 """Add a new revision to current repository.
2354 """Add a new revision to current repository.
2356
2355
2357 Revision information is gathered from the working directory,
2356 Revision information is gathered from the working directory,
2358 match can be used to filter the committed files. If editor is
2357 match can be used to filter the committed files. If editor is
2359 supplied, it is called to get a commit message.
2358 supplied, it is called to get a commit message.
2360 """
2359 """
2361 if extra is None:
2360 if extra is None:
2362 extra = {}
2361 extra = {}
2363
2362
2364 def fail(f, msg):
2363 def fail(f, msg):
2365 raise error.Abort('%s: %s' % (f, msg))
2364 raise error.Abort('%s: %s' % (f, msg))
2366
2365
2367 if not match:
2366 if not match:
2368 match = matchmod.always(self.root, '')
2367 match = matchmod.always(self.root, '')
2369
2368
2370 if not force:
2369 if not force:
2371 vdirs = []
2370 vdirs = []
2372 match.explicitdir = vdirs.append
2371 match.explicitdir = vdirs.append
2373 match.bad = fail
2372 match.bad = fail
2374
2373
2375 wlock = lock = tr = None
2374 wlock = lock = tr = None
2376 try:
2375 try:
2377 wlock = self.wlock()
2376 wlock = self.wlock()
2378 lock = self.lock() # for recent changelog (see issue4368)
2377 lock = self.lock() # for recent changelog (see issue4368)
2379
2378
2380 wctx = self[None]
2379 wctx = self[None]
2381 merge = len(wctx.parents()) > 1
2380 merge = len(wctx.parents()) > 1
2382
2381
2383 if not force and merge and not match.always():
2382 if not force and merge and not match.always():
2384 raise error.Abort(_('cannot partially commit a merge '
2383 raise error.Abort(_('cannot partially commit a merge '
2385 '(do not specify files or patterns)'))
2384 '(do not specify files or patterns)'))
2386
2385
2387 status = self.status(match=match, clean=force)
2386 status = self.status(match=match, clean=force)
2388 if force:
2387 if force:
2389 status.modified.extend(status.clean) # mq may commit clean files
2388 status.modified.extend(status.clean) # mq may commit clean files
2390
2389
2391 # check subrepos
2390 # check subrepos
2392 subs, commitsubs, newstate = subrepoutil.precommit(
2391 subs, commitsubs, newstate = subrepoutil.precommit(
2393 self.ui, wctx, status, match, force=force)
2392 self.ui, wctx, status, match, force=force)
2394
2393
2395 # make sure all explicit patterns are matched
2394 # make sure all explicit patterns are matched
2396 if not force:
2395 if not force:
2397 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2396 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
2398
2397
2399 cctx = context.workingcommitctx(self, status,
2398 cctx = context.workingcommitctx(self, status,
2400 text, user, date, extra)
2399 text, user, date, extra)
2401
2400
2402 # internal config: ui.allowemptycommit
2401 # internal config: ui.allowemptycommit
2403 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2402 allowemptycommit = (wctx.branch() != wctx.p1().branch()
2404 or extra.get('close') or merge or cctx.files()
2403 or extra.get('close') or merge or cctx.files()
2405 or self.ui.configbool('ui', 'allowemptycommit'))
2404 or self.ui.configbool('ui', 'allowemptycommit'))
2406 if not allowemptycommit:
2405 if not allowemptycommit:
2407 return None
2406 return None
2408
2407
2409 if merge and cctx.deleted():
2408 if merge and cctx.deleted():
2410 raise error.Abort(_("cannot commit merge with missing files"))
2409 raise error.Abort(_("cannot commit merge with missing files"))
2411
2410
2412 ms = mergemod.mergestate.read(self)
2411 ms = mergemod.mergestate.read(self)
2413 mergeutil.checkunresolved(ms)
2412 mergeutil.checkunresolved(ms)
2414
2413
2415 if editor:
2414 if editor:
2416 cctx._text = editor(self, cctx, subs)
2415 cctx._text = editor(self, cctx, subs)
2417 edited = (text != cctx._text)
2416 edited = (text != cctx._text)
2418
2417
2419 # Save commit message in case this transaction gets rolled back
2418 # Save commit message in case this transaction gets rolled back
2420 # (e.g. by a pretxncommit hook). Leave the content alone on
2419 # (e.g. by a pretxncommit hook). Leave the content alone on
2421 # the assumption that the user will use the same editor again.
2420 # the assumption that the user will use the same editor again.
2422 msgfn = self.savecommitmessage(cctx._text)
2421 msgfn = self.savecommitmessage(cctx._text)
2423
2422
2424 # commit subs and write new state
2423 # commit subs and write new state
2425 if subs:
2424 if subs:
2426 for s in sorted(commitsubs):
2425 for s in sorted(commitsubs):
2427 sub = wctx.sub(s)
2426 sub = wctx.sub(s)
2428 self.ui.status(_('committing subrepository %s\n') %
2427 self.ui.status(_('committing subrepository %s\n') %
2429 subrepoutil.subrelpath(sub))
2428 subrepoutil.subrelpath(sub))
2430 sr = sub.commit(cctx._text, user, date)
2429 sr = sub.commit(cctx._text, user, date)
2431 newstate[s] = (newstate[s][0], sr)
2430 newstate[s] = (newstate[s][0], sr)
2432 subrepoutil.writestate(self, newstate)
2431 subrepoutil.writestate(self, newstate)
2433
2432
2434 p1, p2 = self.dirstate.parents()
2433 p1, p2 = self.dirstate.parents()
2435 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2434 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2436 try:
2435 try:
2437 self.hook("precommit", throw=True, parent1=hookp1,
2436 self.hook("precommit", throw=True, parent1=hookp1,
2438 parent2=hookp2)
2437 parent2=hookp2)
2439 tr = self.transaction('commit')
2438 tr = self.transaction('commit')
2440 ret = self.commitctx(cctx, True)
2439 ret = self.commitctx(cctx, True)
2441 except: # re-raises
2440 except: # re-raises
2442 if edited:
2441 if edited:
2443 self.ui.write(
2442 self.ui.write(
2444 _('note: commit message saved in %s\n') % msgfn)
2443 _('note: commit message saved in %s\n') % msgfn)
2445 raise
2444 raise
2446 # update bookmarks, dirstate and mergestate
2445 # update bookmarks, dirstate and mergestate
2447 bookmarks.update(self, [p1, p2], ret)
2446 bookmarks.update(self, [p1, p2], ret)
2448 cctx.markcommitted(ret)
2447 cctx.markcommitted(ret)
2449 ms.reset()
2448 ms.reset()
2450 tr.close()
2449 tr.close()
2451
2450
2452 finally:
2451 finally:
2453 lockmod.release(tr, lock, wlock)
2452 lockmod.release(tr, lock, wlock)
2454
2453
2455 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2454 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2456 # hack for command that use a temporary commit (eg: histedit)
2455 # hack for command that use a temporary commit (eg: histedit)
2457 # temporary commit got stripped before hook release
2456 # temporary commit got stripped before hook release
2458 if self.changelog.hasnode(ret):
2457 if self.changelog.hasnode(ret):
2459 self.hook("commit", node=node, parent1=parent1,
2458 self.hook("commit", node=node, parent1=parent1,
2460 parent2=parent2)
2459 parent2=parent2)
2461 self._afterlock(commithook)
2460 self._afterlock(commithook)
2462 return ret
2461 return ret
2463
2462
2464 @unfilteredmethod
2463 @unfilteredmethod
2465 def commitctx(self, ctx, error=False):
2464 def commitctx(self, ctx, error=False):
2466 """Add a new revision to current repository.
2465 """Add a new revision to current repository.
2467 Revision information is passed via the context argument.
2466 Revision information is passed via the context argument.
2468
2467
2469 ctx.files() should list all files involved in this commit, i.e.
2468 ctx.files() should list all files involved in this commit, i.e.
2470 modified/added/removed files. On merge, it may be wider than the
2469 modified/added/removed files. On merge, it may be wider than the
2471 ctx.files() to be committed, since any file nodes derived directly
2470 ctx.files() to be committed, since any file nodes derived directly
2472 from p1 or p2 are excluded from the committed ctx.files().
2471 from p1 or p2 are excluded from the committed ctx.files().
2473 """
2472 """
2474
2473
2475 tr = None
2474 tr = None
2476 p1, p2 = ctx.p1(), ctx.p2()
2475 p1, p2 = ctx.p1(), ctx.p2()
2477 user = ctx.user()
2476 user = ctx.user()
2478
2477
2479 lock = self.lock()
2478 lock = self.lock()
2480 try:
2479 try:
2481 tr = self.transaction("commit")
2480 tr = self.transaction("commit")
2482 trp = weakref.proxy(tr)
2481 trp = weakref.proxy(tr)
2483
2482
2484 if ctx.manifestnode():
2483 if ctx.manifestnode():
2485 # reuse an existing manifest revision
2484 # reuse an existing manifest revision
2486 self.ui.debug('reusing known manifest\n')
2485 self.ui.debug('reusing known manifest\n')
2487 mn = ctx.manifestnode()
2486 mn = ctx.manifestnode()
2488 files = ctx.files()
2487 files = ctx.files()
2489 elif ctx.files():
2488 elif ctx.files():
2490 m1ctx = p1.manifestctx()
2489 m1ctx = p1.manifestctx()
2491 m2ctx = p2.manifestctx()
2490 m2ctx = p2.manifestctx()
2492 mctx = m1ctx.copy()
2491 mctx = m1ctx.copy()
2493
2492
2494 m = mctx.read()
2493 m = mctx.read()
2495 m1 = m1ctx.read()
2494 m1 = m1ctx.read()
2496 m2 = m2ctx.read()
2495 m2 = m2ctx.read()
2497
2496
2498 # check in files
2497 # check in files
2499 added = []
2498 added = []
2500 changed = []
2499 changed = []
2501 removed = list(ctx.removed())
2500 removed = list(ctx.removed())
2502 linkrev = len(self)
2501 linkrev = len(self)
2503 self.ui.note(_("committing files:\n"))
2502 self.ui.note(_("committing files:\n"))
2504 for f in sorted(ctx.modified() + ctx.added()):
2503 for f in sorted(ctx.modified() + ctx.added()):
2505 self.ui.note(f + "\n")
2504 self.ui.note(f + "\n")
2506 try:
2505 try:
2507 fctx = ctx[f]
2506 fctx = ctx[f]
2508 if fctx is None:
2507 if fctx is None:
2509 removed.append(f)
2508 removed.append(f)
2510 else:
2509 else:
2511 added.append(f)
2510 added.append(f)
2512 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2511 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2513 trp, changed)
2512 trp, changed)
2514 m.setflag(f, fctx.flags())
2513 m.setflag(f, fctx.flags())
2515 except OSError as inst:
2514 except OSError as inst:
2516 self.ui.warn(_("trouble committing %s!\n") % f)
2515 self.ui.warn(_("trouble committing %s!\n") % f)
2517 raise
2516 raise
2518 except IOError as inst:
2517 except IOError as inst:
2519 errcode = getattr(inst, 'errno', errno.ENOENT)
2518 errcode = getattr(inst, 'errno', errno.ENOENT)
2520 if error or errcode and errcode != errno.ENOENT:
2519 if error or errcode and errcode != errno.ENOENT:
2521 self.ui.warn(_("trouble committing %s!\n") % f)
2520 self.ui.warn(_("trouble committing %s!\n") % f)
2522 raise
2521 raise
2523
2522
2524 # update manifest
2523 # update manifest
2525 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2524 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2526 drop = [f for f in removed if f in m]
2525 drop = [f for f in removed if f in m]
2527 for f in drop:
2526 for f in drop:
2528 del m[f]
2527 del m[f]
2529 files = changed + removed
2528 files = changed + removed
2530 md = None
2529 md = None
2531 if not files:
2530 if not files:
2532 # if no "files" actually changed in terms of the changelog,
2531 # if no "files" actually changed in terms of the changelog,
2533 # try hard to detect unmodified manifest entry so that the
2532 # try hard to detect unmodified manifest entry so that the
2534 # exact same commit can be reproduced later on convert.
2533 # exact same commit can be reproduced later on convert.
2535 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2534 md = m1.diff(m, scmutil.matchfiles(self, ctx.files()))
2536 if not files and md:
2535 if not files and md:
2537 self.ui.debug('not reusing manifest (no file change in '
2536 self.ui.debug('not reusing manifest (no file change in '
2538 'changelog, but manifest differs)\n')
2537 'changelog, but manifest differs)\n')
2539 if files or md:
2538 if files or md:
2540 self.ui.note(_("committing manifest\n"))
2539 self.ui.note(_("committing manifest\n"))
2541 # we're using narrowmatch here since it's already applied at
2540 # we're using narrowmatch here since it's already applied at
2542 # other stages (such as dirstate.walk), so we're already
2541 # other stages (such as dirstate.walk), so we're already
2543 # ignoring things outside of narrowspec in most cases. The
2542 # ignoring things outside of narrowspec in most cases. The
2544 # one case where we might have files outside the narrowspec
2543 # one case where we might have files outside the narrowspec
2545 # at this point is merges, and we already error out in the
2544 # at this point is merges, and we already error out in the
2546 # case where the merge has files outside of the narrowspec,
2545 # case where the merge has files outside of the narrowspec,
2547 # so this is safe.
2546 # so this is safe.
2548 mn = mctx.write(trp, linkrev,
2547 mn = mctx.write(trp, linkrev,
2549 p1.manifestnode(), p2.manifestnode(),
2548 p1.manifestnode(), p2.manifestnode(),
2550 added, drop, match=self.narrowmatch())
2549 added, drop, match=self.narrowmatch())
2551 else:
2550 else:
2552 self.ui.debug('reusing manifest form p1 (listed files '
2551 self.ui.debug('reusing manifest form p1 (listed files '
2553 'actually unchanged)\n')
2552 'actually unchanged)\n')
2554 mn = p1.manifestnode()
2553 mn = p1.manifestnode()
2555 else:
2554 else:
2556 self.ui.debug('reusing manifest from p1 (no file change)\n')
2555 self.ui.debug('reusing manifest from p1 (no file change)\n')
2557 mn = p1.manifestnode()
2556 mn = p1.manifestnode()
2558 files = []
2557 files = []
2559
2558
2560 # update changelog
2559 # update changelog
2561 self.ui.note(_("committing changelog\n"))
2560 self.ui.note(_("committing changelog\n"))
2562 self.changelog.delayupdate(tr)
2561 self.changelog.delayupdate(tr)
2563 n = self.changelog.add(mn, files, ctx.description(),
2562 n = self.changelog.add(mn, files, ctx.description(),
2564 trp, p1.node(), p2.node(),
2563 trp, p1.node(), p2.node(),
2565 user, ctx.date(), ctx.extra().copy())
2564 user, ctx.date(), ctx.extra().copy())
2566 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2565 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2567 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2566 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2568 parent2=xp2)
2567 parent2=xp2)
2569 # set the new commit is proper phase
2568 # set the new commit is proper phase
2570 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2569 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2571 if targetphase:
2570 if targetphase:
2572 # retract boundary do not alter parent changeset.
2571 # retract boundary do not alter parent changeset.
2573 # if a parent have higher the resulting phase will
2572 # if a parent have higher the resulting phase will
2574 # be compliant anyway
2573 # be compliant anyway
2575 #
2574 #
2576 # if minimal phase was 0 we don't need to retract anything
2575 # if minimal phase was 0 we don't need to retract anything
2577 phases.registernew(self, tr, targetphase, [n])
2576 phases.registernew(self, tr, targetphase, [n])
2578 tr.close()
2577 tr.close()
2579 return n
2578 return n
2580 finally:
2579 finally:
2581 if tr:
2580 if tr:
2582 tr.release()
2581 tr.release()
2583 lock.release()
2582 lock.release()
2584
2583
2585 @unfilteredmethod
2584 @unfilteredmethod
2586 def destroying(self):
2585 def destroying(self):
2587 '''Inform the repository that nodes are about to be destroyed.
2586 '''Inform the repository that nodes are about to be destroyed.
2588 Intended for use by strip and rollback, so there's a common
2587 Intended for use by strip and rollback, so there's a common
2589 place for anything that has to be done before destroying history.
2588 place for anything that has to be done before destroying history.
2590
2589
2591 This is mostly useful for saving state that is in memory and waiting
2590 This is mostly useful for saving state that is in memory and waiting
2592 to be flushed when the current lock is released. Because a call to
2591 to be flushed when the current lock is released. Because a call to
2593 destroyed is imminent, the repo will be invalidated causing those
2592 destroyed is imminent, the repo will be invalidated causing those
2594 changes to stay in memory (waiting for the next unlock), or vanish
2593 changes to stay in memory (waiting for the next unlock), or vanish
2595 completely.
2594 completely.
2596 '''
2595 '''
2597 # When using the same lock to commit and strip, the phasecache is left
2596 # When using the same lock to commit and strip, the phasecache is left
2598 # dirty after committing. Then when we strip, the repo is invalidated,
2597 # dirty after committing. Then when we strip, the repo is invalidated,
2599 # causing those changes to disappear.
2598 # causing those changes to disappear.
2600 if '_phasecache' in vars(self):
2599 if '_phasecache' in vars(self):
2601 self._phasecache.write()
2600 self._phasecache.write()
2602
2601
2603 @unfilteredmethod
2602 @unfilteredmethod
2604 def destroyed(self):
2603 def destroyed(self):
2605 '''Inform the repository that nodes have been destroyed.
2604 '''Inform the repository that nodes have been destroyed.
2606 Intended for use by strip and rollback, so there's a common
2605 Intended for use by strip and rollback, so there's a common
2607 place for anything that has to be done after destroying history.
2606 place for anything that has to be done after destroying history.
2608 '''
2607 '''
2609 # When one tries to:
2608 # When one tries to:
2610 # 1) destroy nodes thus calling this method (e.g. strip)
2609 # 1) destroy nodes thus calling this method (e.g. strip)
2611 # 2) use phasecache somewhere (e.g. commit)
2610 # 2) use phasecache somewhere (e.g. commit)
2612 #
2611 #
2613 # then 2) will fail because the phasecache contains nodes that were
2612 # then 2) will fail because the phasecache contains nodes that were
2614 # removed. We can either remove phasecache from the filecache,
2613 # removed. We can either remove phasecache from the filecache,
2615 # causing it to reload next time it is accessed, or simply filter
2614 # causing it to reload next time it is accessed, or simply filter
2616 # the removed nodes now and write the updated cache.
2615 # the removed nodes now and write the updated cache.
2617 self._phasecache.filterunknown(self)
2616 self._phasecache.filterunknown(self)
2618 self._phasecache.write()
2617 self._phasecache.write()
2619
2618
2620 # refresh all repository caches
2619 # refresh all repository caches
2621 self.updatecaches()
2620 self.updatecaches()
2622
2621
2623 # Ensure the persistent tag cache is updated. Doing it now
2622 # Ensure the persistent tag cache is updated. Doing it now
2624 # means that the tag cache only has to worry about destroyed
2623 # means that the tag cache only has to worry about destroyed
2625 # heads immediately after a strip/rollback. That in turn
2624 # heads immediately after a strip/rollback. That in turn
2626 # guarantees that "cachetip == currenttip" (comparing both rev
2625 # guarantees that "cachetip == currenttip" (comparing both rev
2627 # and node) always means no nodes have been added or destroyed.
2626 # and node) always means no nodes have been added or destroyed.
2628
2627
2629 # XXX this is suboptimal when qrefresh'ing: we strip the current
2628 # XXX this is suboptimal when qrefresh'ing: we strip the current
2630 # head, refresh the tag cache, then immediately add a new head.
2629 # head, refresh the tag cache, then immediately add a new head.
2631 # But I think doing it this way is necessary for the "instant
2630 # But I think doing it this way is necessary for the "instant
2632 # tag cache retrieval" case to work.
2631 # tag cache retrieval" case to work.
2633 self.invalidate()
2632 self.invalidate()
2634
2633
2635 def status(self, node1='.', node2=None, match=None,
2634 def status(self, node1='.', node2=None, match=None,
2636 ignored=False, clean=False, unknown=False,
2635 ignored=False, clean=False, unknown=False,
2637 listsubrepos=False):
2636 listsubrepos=False):
2638 '''a convenience method that calls node1.status(node2)'''
2637 '''a convenience method that calls node1.status(node2)'''
2639 return self[node1].status(node2, match, ignored, clean, unknown,
2638 return self[node1].status(node2, match, ignored, clean, unknown,
2640 listsubrepos)
2639 listsubrepos)
2641
2640
2642 def addpostdsstatus(self, ps):
2641 def addpostdsstatus(self, ps):
2643 """Add a callback to run within the wlock, at the point at which status
2642 """Add a callback to run within the wlock, at the point at which status
2644 fixups happen.
2643 fixups happen.
2645
2644
2646 On status completion, callback(wctx, status) will be called with the
2645 On status completion, callback(wctx, status) will be called with the
2647 wlock held, unless the dirstate has changed from underneath or the wlock
2646 wlock held, unless the dirstate has changed from underneath or the wlock
2648 couldn't be grabbed.
2647 couldn't be grabbed.
2649
2648
2650 Callbacks should not capture and use a cached copy of the dirstate --
2649 Callbacks should not capture and use a cached copy of the dirstate --
2651 it might change in the meanwhile. Instead, they should access the
2650 it might change in the meanwhile. Instead, they should access the
2652 dirstate via wctx.repo().dirstate.
2651 dirstate via wctx.repo().dirstate.
2653
2652
2654 This list is emptied out after each status run -- extensions should
2653 This list is emptied out after each status run -- extensions should
2655 make sure it adds to this list each time dirstate.status is called.
2654 make sure it adds to this list each time dirstate.status is called.
2656 Extensions should also make sure they don't call this for statuses
2655 Extensions should also make sure they don't call this for statuses
2657 that don't involve the dirstate.
2656 that don't involve the dirstate.
2658 """
2657 """
2659
2658
2660 # The list is located here for uniqueness reasons -- it is actually
2659 # The list is located here for uniqueness reasons -- it is actually
2661 # managed by the workingctx, but that isn't unique per-repo.
2660 # managed by the workingctx, but that isn't unique per-repo.
2662 self._postdsstatus.append(ps)
2661 self._postdsstatus.append(ps)
2663
2662
2664 def postdsstatus(self):
2663 def postdsstatus(self):
2665 """Used by workingctx to get the list of post-dirstate-status hooks."""
2664 """Used by workingctx to get the list of post-dirstate-status hooks."""
2666 return self._postdsstatus
2665 return self._postdsstatus
2667
2666
2668 def clearpostdsstatus(self):
2667 def clearpostdsstatus(self):
2669 """Used by workingctx to clear post-dirstate-status hooks."""
2668 """Used by workingctx to clear post-dirstate-status hooks."""
2670 del self._postdsstatus[:]
2669 del self._postdsstatus[:]
2671
2670
2672 def heads(self, start=None):
2671 def heads(self, start=None):
2673 if start is None:
2672 if start is None:
2674 cl = self.changelog
2673 cl = self.changelog
2675 headrevs = reversed(cl.headrevs())
2674 headrevs = reversed(cl.headrevs())
2676 return [cl.node(rev) for rev in headrevs]
2675 return [cl.node(rev) for rev in headrevs]
2677
2676
2678 heads = self.changelog.heads(start)
2677 heads = self.changelog.heads(start)
2679 # sort the output in rev descending order
2678 # sort the output in rev descending order
2680 return sorted(heads, key=self.changelog.rev, reverse=True)
2679 return sorted(heads, key=self.changelog.rev, reverse=True)
2681
2680
2682 def branchheads(self, branch=None, start=None, closed=False):
2681 def branchheads(self, branch=None, start=None, closed=False):
2683 '''return a (possibly filtered) list of heads for the given branch
2682 '''return a (possibly filtered) list of heads for the given branch
2684
2683
2685 Heads are returned in topological order, from newest to oldest.
2684 Heads are returned in topological order, from newest to oldest.
2686 If branch is None, use the dirstate branch.
2685 If branch is None, use the dirstate branch.
2687 If start is not None, return only heads reachable from start.
2686 If start is not None, return only heads reachable from start.
2688 If closed is True, return heads that are marked as closed as well.
2687 If closed is True, return heads that are marked as closed as well.
2689 '''
2688 '''
2690 if branch is None:
2689 if branch is None:
2691 branch = self[None].branch()
2690 branch = self[None].branch()
2692 branches = self.branchmap()
2691 branches = self.branchmap()
2693 if branch not in branches:
2692 if branch not in branches:
2694 return []
2693 return []
2695 # the cache returns heads ordered lowest to highest
2694 # the cache returns heads ordered lowest to highest
2696 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2695 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2697 if start is not None:
2696 if start is not None:
2698 # filter out the heads that cannot be reached from startrev
2697 # filter out the heads that cannot be reached from startrev
2699 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2698 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2700 bheads = [h for h in bheads if h in fbheads]
2699 bheads = [h for h in bheads if h in fbheads]
2701 return bheads
2700 return bheads
2702
2701
2703 def branches(self, nodes):
2702 def branches(self, nodes):
2704 if not nodes:
2703 if not nodes:
2705 nodes = [self.changelog.tip()]
2704 nodes = [self.changelog.tip()]
2706 b = []
2705 b = []
2707 for n in nodes:
2706 for n in nodes:
2708 t = n
2707 t = n
2709 while True:
2708 while True:
2710 p = self.changelog.parents(n)
2709 p = self.changelog.parents(n)
2711 if p[1] != nullid or p[0] == nullid:
2710 if p[1] != nullid or p[0] == nullid:
2712 b.append((t, n, p[0], p[1]))
2711 b.append((t, n, p[0], p[1]))
2713 break
2712 break
2714 n = p[0]
2713 n = p[0]
2715 return b
2714 return b
2716
2715
2717 def between(self, pairs):
2716 def between(self, pairs):
2718 r = []
2717 r = []
2719
2718
2720 for top, bottom in pairs:
2719 for top, bottom in pairs:
2721 n, l, i = top, [], 0
2720 n, l, i = top, [], 0
2722 f = 1
2721 f = 1
2723
2722
2724 while n != bottom and n != nullid:
2723 while n != bottom and n != nullid:
2725 p = self.changelog.parents(n)[0]
2724 p = self.changelog.parents(n)[0]
2726 if i == f:
2725 if i == f:
2727 l.append(n)
2726 l.append(n)
2728 f = f * 2
2727 f = f * 2
2729 n = p
2728 n = p
2730 i += 1
2729 i += 1
2731
2730
2732 r.append(l)
2731 r.append(l)
2733
2732
2734 return r
2733 return r
2735
2734
2736 def checkpush(self, pushop):
2735 def checkpush(self, pushop):
2737 """Extensions can override this function if additional checks have
2736 """Extensions can override this function if additional checks have
2738 to be performed before pushing, or call it if they override push
2737 to be performed before pushing, or call it if they override push
2739 command.
2738 command.
2740 """
2739 """
2741
2740
2742 @unfilteredpropertycache
2741 @unfilteredpropertycache
2743 def prepushoutgoinghooks(self):
2742 def prepushoutgoinghooks(self):
2744 """Return util.hooks consists of a pushop with repo, remote, outgoing
2743 """Return util.hooks consists of a pushop with repo, remote, outgoing
2745 methods, which are called before pushing changesets.
2744 methods, which are called before pushing changesets.
2746 """
2745 """
2747 return util.hooks()
2746 return util.hooks()
2748
2747
2749 def pushkey(self, namespace, key, old, new):
2748 def pushkey(self, namespace, key, old, new):
2750 try:
2749 try:
2751 tr = self.currenttransaction()
2750 tr = self.currenttransaction()
2752 hookargs = {}
2751 hookargs = {}
2753 if tr is not None:
2752 if tr is not None:
2754 hookargs.update(tr.hookargs)
2753 hookargs.update(tr.hookargs)
2755 hookargs = pycompat.strkwargs(hookargs)
2754 hookargs = pycompat.strkwargs(hookargs)
2756 hookargs[r'namespace'] = namespace
2755 hookargs[r'namespace'] = namespace
2757 hookargs[r'key'] = key
2756 hookargs[r'key'] = key
2758 hookargs[r'old'] = old
2757 hookargs[r'old'] = old
2759 hookargs[r'new'] = new
2758 hookargs[r'new'] = new
2760 self.hook('prepushkey', throw=True, **hookargs)
2759 self.hook('prepushkey', throw=True, **hookargs)
2761 except error.HookAbort as exc:
2760 except error.HookAbort as exc:
2762 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2761 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2763 if exc.hint:
2762 if exc.hint:
2764 self.ui.write_err(_("(%s)\n") % exc.hint)
2763 self.ui.write_err(_("(%s)\n") % exc.hint)
2765 return False
2764 return False
2766 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2765 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2767 ret = pushkey.push(self, namespace, key, old, new)
2766 ret = pushkey.push(self, namespace, key, old, new)
2768 def runhook():
2767 def runhook():
2769 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2768 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2770 ret=ret)
2769 ret=ret)
2771 self._afterlock(runhook)
2770 self._afterlock(runhook)
2772 return ret
2771 return ret
2773
2772
2774 def listkeys(self, namespace):
2773 def listkeys(self, namespace):
2775 self.hook('prelistkeys', throw=True, namespace=namespace)
2774 self.hook('prelistkeys', throw=True, namespace=namespace)
2776 self.ui.debug('listing keys for "%s"\n' % namespace)
2775 self.ui.debug('listing keys for "%s"\n' % namespace)
2777 values = pushkey.list(self, namespace)
2776 values = pushkey.list(self, namespace)
2778 self.hook('listkeys', namespace=namespace, values=values)
2777 self.hook('listkeys', namespace=namespace, values=values)
2779 return values
2778 return values
2780
2779
2781 def debugwireargs(self, one, two, three=None, four=None, five=None):
2780 def debugwireargs(self, one, two, three=None, four=None, five=None):
2782 '''used to test argument passing over the wire'''
2781 '''used to test argument passing over the wire'''
2783 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2782 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2784 pycompat.bytestr(four),
2783 pycompat.bytestr(four),
2785 pycompat.bytestr(five))
2784 pycompat.bytestr(five))
2786
2785
2787 def savecommitmessage(self, text):
2786 def savecommitmessage(self, text):
2788 fp = self.vfs('last-message.txt', 'wb')
2787 fp = self.vfs('last-message.txt', 'wb')
2789 try:
2788 try:
2790 fp.write(text)
2789 fp.write(text)
2791 finally:
2790 finally:
2792 fp.close()
2791 fp.close()
2793 return self.pathto(fp.name[len(self.root) + 1:])
2792 return self.pathto(fp.name[len(self.root) + 1:])
2794
2793
2795 # used to avoid circular references so destructors work
2794 # used to avoid circular references so destructors work
2796 def aftertrans(files):
2795 def aftertrans(files):
2797 renamefiles = [tuple(t) for t in files]
2796 renamefiles = [tuple(t) for t in files]
2798 def a():
2797 def a():
2799 for vfs, src, dest in renamefiles:
2798 for vfs, src, dest in renamefiles:
2800 # if src and dest refer to a same file, vfs.rename is a no-op,
2799 # if src and dest refer to a same file, vfs.rename is a no-op,
2801 # leaving both src and dest on disk. delete dest to make sure
2800 # leaving both src and dest on disk. delete dest to make sure
2802 # the rename couldn't be such a no-op.
2801 # the rename couldn't be such a no-op.
2803 vfs.tryunlink(dest)
2802 vfs.tryunlink(dest)
2804 try:
2803 try:
2805 vfs.rename(src, dest)
2804 vfs.rename(src, dest)
2806 except OSError: # journal file does not yet exist
2805 except OSError: # journal file does not yet exist
2807 pass
2806 pass
2808 return a
2807 return a
2809
2808
2810 def undoname(fn):
2809 def undoname(fn):
2811 base, name = os.path.split(fn)
2810 base, name = os.path.split(fn)
2812 assert name.startswith('journal')
2811 assert name.startswith('journal')
2813 return os.path.join(base, name.replace('journal', 'undo', 1))
2812 return os.path.join(base, name.replace('journal', 'undo', 1))
2814
2813
2815 def instance(ui, path, create, intents=None, createopts=None):
2814 def instance(ui, path, create, intents=None, createopts=None):
2816 localpath = util.urllocalpath(path)
2815 localpath = util.urllocalpath(path)
2817 if create:
2816 if create:
2818 createrepository(ui, localpath, createopts=createopts)
2817 createrepository(ui, localpath, createopts=createopts)
2819
2818
2820 return makelocalrepository(ui, localpath, intents=intents)
2819 return makelocalrepository(ui, localpath, intents=intents)
2821
2820
2822 def islocal(path):
2821 def islocal(path):
2823 return True
2822 return True
2824
2823
2825 def defaultcreateopts(ui, createopts=None):
2824 def defaultcreateopts(ui, createopts=None):
2826 """Populate the default creation options for a repository.
2825 """Populate the default creation options for a repository.
2827
2826
2828 A dictionary of explicitly requested creation options can be passed
2827 A dictionary of explicitly requested creation options can be passed
2829 in. Missing keys will be populated.
2828 in. Missing keys will be populated.
2830 """
2829 """
2831 createopts = dict(createopts or {})
2830 createopts = dict(createopts or {})
2832
2831
2833 if 'backend' not in createopts:
2832 if 'backend' not in createopts:
2834 # experimental config: storage.new-repo-backend
2833 # experimental config: storage.new-repo-backend
2835 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2834 createopts['backend'] = ui.config('storage', 'new-repo-backend')
2836
2835
2837 return createopts
2836 return createopts
2838
2837
2839 def newreporequirements(ui, createopts):
2838 def newreporequirements(ui, createopts):
2840 """Determine the set of requirements for a new local repository.
2839 """Determine the set of requirements for a new local repository.
2841
2840
2842 Extensions can wrap this function to specify custom requirements for
2841 Extensions can wrap this function to specify custom requirements for
2843 new repositories.
2842 new repositories.
2844 """
2843 """
2845 # If the repo is being created from a shared repository, we copy
2844 # If the repo is being created from a shared repository, we copy
2846 # its requirements.
2845 # its requirements.
2847 if 'sharedrepo' in createopts:
2846 if 'sharedrepo' in createopts:
2848 requirements = set(createopts['sharedrepo'].requirements)
2847 requirements = set(createopts['sharedrepo'].requirements)
2849 if createopts.get('sharedrelative'):
2848 if createopts.get('sharedrelative'):
2850 requirements.add('relshared')
2849 requirements.add('relshared')
2851 else:
2850 else:
2852 requirements.add('shared')
2851 requirements.add('shared')
2853
2852
2854 return requirements
2853 return requirements
2855
2854
2856 if 'backend' not in createopts:
2855 if 'backend' not in createopts:
2857 raise error.ProgrammingError('backend key not present in createopts; '
2856 raise error.ProgrammingError('backend key not present in createopts; '
2858 'was defaultcreateopts() called?')
2857 'was defaultcreateopts() called?')
2859
2858
2860 if createopts['backend'] != 'revlogv1':
2859 if createopts['backend'] != 'revlogv1':
2861 raise error.Abort(_('unable to determine repository requirements for '
2860 raise error.Abort(_('unable to determine repository requirements for '
2862 'storage backend: %s') % createopts['backend'])
2861 'storage backend: %s') % createopts['backend'])
2863
2862
2864 requirements = {'revlogv1'}
2863 requirements = {'revlogv1'}
2865 if ui.configbool('format', 'usestore'):
2864 if ui.configbool('format', 'usestore'):
2866 requirements.add('store')
2865 requirements.add('store')
2867 if ui.configbool('format', 'usefncache'):
2866 if ui.configbool('format', 'usefncache'):
2868 requirements.add('fncache')
2867 requirements.add('fncache')
2869 if ui.configbool('format', 'dotencode'):
2868 if ui.configbool('format', 'dotencode'):
2870 requirements.add('dotencode')
2869 requirements.add('dotencode')
2871
2870
2872 compengine = ui.config('experimental', 'format.compression')
2871 compengine = ui.config('experimental', 'format.compression')
2873 if compengine not in util.compengines:
2872 if compengine not in util.compengines:
2874 raise error.Abort(_('compression engine %s defined by '
2873 raise error.Abort(_('compression engine %s defined by '
2875 'experimental.format.compression not available') %
2874 'experimental.format.compression not available') %
2876 compengine,
2875 compengine,
2877 hint=_('run "hg debuginstall" to list available '
2876 hint=_('run "hg debuginstall" to list available '
2878 'compression engines'))
2877 'compression engines'))
2879
2878
2880 # zlib is the historical default and doesn't need an explicit requirement.
2879 # zlib is the historical default and doesn't need an explicit requirement.
2881 if compengine != 'zlib':
2880 if compengine != 'zlib':
2882 requirements.add('exp-compression-%s' % compengine)
2881 requirements.add('exp-compression-%s' % compengine)
2883
2882
2884 if scmutil.gdinitconfig(ui):
2883 if scmutil.gdinitconfig(ui):
2885 requirements.add('generaldelta')
2884 requirements.add('generaldelta')
2886 if ui.configbool('experimental', 'treemanifest'):
2885 if ui.configbool('experimental', 'treemanifest'):
2887 requirements.add('treemanifest')
2886 requirements.add('treemanifest')
2888 # experimental config: format.sparse-revlog
2887 # experimental config: format.sparse-revlog
2889 if ui.configbool('format', 'sparse-revlog'):
2888 if ui.configbool('format', 'sparse-revlog'):
2890 requirements.add(SPARSEREVLOG_REQUIREMENT)
2889 requirements.add(SPARSEREVLOG_REQUIREMENT)
2891
2890
2892 revlogv2 = ui.config('experimental', 'revlogv2')
2891 revlogv2 = ui.config('experimental', 'revlogv2')
2893 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2892 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2894 requirements.remove('revlogv1')
2893 requirements.remove('revlogv1')
2895 # generaldelta is implied by revlogv2.
2894 # generaldelta is implied by revlogv2.
2896 requirements.discard('generaldelta')
2895 requirements.discard('generaldelta')
2897 requirements.add(REVLOGV2_REQUIREMENT)
2896 requirements.add(REVLOGV2_REQUIREMENT)
2898 # experimental config: format.internal-phase
2897 # experimental config: format.internal-phase
2899 if ui.configbool('format', 'internal-phase'):
2898 if ui.configbool('format', 'internal-phase'):
2900 requirements.add('internal-phase')
2899 requirements.add('internal-phase')
2901
2900
2902 if createopts.get('narrowfiles'):
2901 if createopts.get('narrowfiles'):
2903 requirements.add(repository.NARROW_REQUIREMENT)
2902 requirements.add(repository.NARROW_REQUIREMENT)
2904
2903
2905 if createopts.get('lfs'):
2904 if createopts.get('lfs'):
2906 requirements.add('lfs')
2905 requirements.add('lfs')
2907
2906
2908 return requirements
2907 return requirements
2909
2908
2910 def filterknowncreateopts(ui, createopts):
2909 def filterknowncreateopts(ui, createopts):
2911 """Filters a dict of repo creation options against options that are known.
2910 """Filters a dict of repo creation options against options that are known.
2912
2911
2913 Receives a dict of repo creation options and returns a dict of those
2912 Receives a dict of repo creation options and returns a dict of those
2914 options that we don't know how to handle.
2913 options that we don't know how to handle.
2915
2914
2916 This function is called as part of repository creation. If the
2915 This function is called as part of repository creation. If the
2917 returned dict contains any items, repository creation will not
2916 returned dict contains any items, repository creation will not
2918 be allowed, as it means there was a request to create a repository
2917 be allowed, as it means there was a request to create a repository
2919 with options not recognized by loaded code.
2918 with options not recognized by loaded code.
2920
2919
2921 Extensions can wrap this function to filter out creation options
2920 Extensions can wrap this function to filter out creation options
2922 they know how to handle.
2921 they know how to handle.
2923 """
2922 """
2924 known = {
2923 known = {
2925 'backend',
2924 'backend',
2926 'lfs',
2925 'lfs',
2927 'narrowfiles',
2926 'narrowfiles',
2928 'sharedrepo',
2927 'sharedrepo',
2929 'sharedrelative',
2928 'sharedrelative',
2930 'shareditems',
2929 'shareditems',
2931 'shallowfilestore',
2930 'shallowfilestore',
2932 }
2931 }
2933
2932
2934 return {k: v for k, v in createopts.items() if k not in known}
2933 return {k: v for k, v in createopts.items() if k not in known}
2935
2934
2936 def createrepository(ui, path, createopts=None):
2935 def createrepository(ui, path, createopts=None):
2937 """Create a new repository in a vfs.
2936 """Create a new repository in a vfs.
2938
2937
2939 ``path`` path to the new repo's working directory.
2938 ``path`` path to the new repo's working directory.
2940 ``createopts`` options for the new repository.
2939 ``createopts`` options for the new repository.
2941
2940
2942 The following keys for ``createopts`` are recognized:
2941 The following keys for ``createopts`` are recognized:
2943
2942
2944 backend
2943 backend
2945 The storage backend to use.
2944 The storage backend to use.
2946 lfs
2945 lfs
2947 Repository will be created with ``lfs`` requirement. The lfs extension
2946 Repository will be created with ``lfs`` requirement. The lfs extension
2948 will automatically be loaded when the repository is accessed.
2947 will automatically be loaded when the repository is accessed.
2949 narrowfiles
2948 narrowfiles
2950 Set up repository to support narrow file storage.
2949 Set up repository to support narrow file storage.
2951 sharedrepo
2950 sharedrepo
2952 Repository object from which storage should be shared.
2951 Repository object from which storage should be shared.
2953 sharedrelative
2952 sharedrelative
2954 Boolean indicating if the path to the shared repo should be
2953 Boolean indicating if the path to the shared repo should be
2955 stored as relative. By default, the pointer to the "parent" repo
2954 stored as relative. By default, the pointer to the "parent" repo
2956 is stored as an absolute path.
2955 is stored as an absolute path.
2957 shareditems
2956 shareditems
2958 Set of items to share to the new repository (in addition to storage).
2957 Set of items to share to the new repository (in addition to storage).
2959 shallowfilestore
2958 shallowfilestore
2960 Indicates that storage for files should be shallow (not all ancestor
2959 Indicates that storage for files should be shallow (not all ancestor
2961 revisions are known).
2960 revisions are known).
2962 """
2961 """
2963 createopts = defaultcreateopts(ui, createopts=createopts)
2962 createopts = defaultcreateopts(ui, createopts=createopts)
2964
2963
2965 unknownopts = filterknowncreateopts(ui, createopts)
2964 unknownopts = filterknowncreateopts(ui, createopts)
2966
2965
2967 if not isinstance(unknownopts, dict):
2966 if not isinstance(unknownopts, dict):
2968 raise error.ProgrammingError('filterknowncreateopts() did not return '
2967 raise error.ProgrammingError('filterknowncreateopts() did not return '
2969 'a dict')
2968 'a dict')
2970
2969
2971 if unknownopts:
2970 if unknownopts:
2972 raise error.Abort(_('unable to create repository because of unknown '
2971 raise error.Abort(_('unable to create repository because of unknown '
2973 'creation option: %s') %
2972 'creation option: %s') %
2974 ', '.join(sorted(unknownopts)),
2973 ', '.join(sorted(unknownopts)),
2975 hint=_('is a required extension not loaded?'))
2974 hint=_('is a required extension not loaded?'))
2976
2975
2977 requirements = newreporequirements(ui, createopts=createopts)
2976 requirements = newreporequirements(ui, createopts=createopts)
2978
2977
2979 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
2978 wdirvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
2980
2979
2981 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
2980 hgvfs = vfsmod.vfs(wdirvfs.join(b'.hg'))
2982 if hgvfs.exists():
2981 if hgvfs.exists():
2983 raise error.RepoError(_('repository %s already exists') % path)
2982 raise error.RepoError(_('repository %s already exists') % path)
2984
2983
2985 if 'sharedrepo' in createopts:
2984 if 'sharedrepo' in createopts:
2986 sharedpath = createopts['sharedrepo'].sharedpath
2985 sharedpath = createopts['sharedrepo'].sharedpath
2987
2986
2988 if createopts.get('sharedrelative'):
2987 if createopts.get('sharedrelative'):
2989 try:
2988 try:
2990 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
2989 sharedpath = os.path.relpath(sharedpath, hgvfs.base)
2991 except (IOError, ValueError) as e:
2990 except (IOError, ValueError) as e:
2992 # ValueError is raised on Windows if the drive letters differ
2991 # ValueError is raised on Windows if the drive letters differ
2993 # on each path.
2992 # on each path.
2994 raise error.Abort(_('cannot calculate relative path'),
2993 raise error.Abort(_('cannot calculate relative path'),
2995 hint=stringutil.forcebytestr(e))
2994 hint=stringutil.forcebytestr(e))
2996
2995
2997 if not wdirvfs.exists():
2996 if not wdirvfs.exists():
2998 wdirvfs.makedirs()
2997 wdirvfs.makedirs()
2999
2998
3000 hgvfs.makedir(notindexed=True)
2999 hgvfs.makedir(notindexed=True)
3001
3000
3002 if b'store' in requirements and 'sharedrepo' not in createopts:
3001 if b'store' in requirements and 'sharedrepo' not in createopts:
3003 hgvfs.mkdir(b'store')
3002 hgvfs.mkdir(b'store')
3004
3003
3005 # We create an invalid changelog outside the store so very old
3004 # We create an invalid changelog outside the store so very old
3006 # Mercurial versions (which didn't know about the requirements
3005 # Mercurial versions (which didn't know about the requirements
3007 # file) encounter an error on reading the changelog. This
3006 # file) encounter an error on reading the changelog. This
3008 # effectively locks out old clients and prevents them from
3007 # effectively locks out old clients and prevents them from
3009 # mucking with a repo in an unknown format.
3008 # mucking with a repo in an unknown format.
3010 #
3009 #
3011 # The revlog header has version 2, which won't be recognized by
3010 # The revlog header has version 2, which won't be recognized by
3012 # such old clients.
3011 # such old clients.
3013 hgvfs.append(b'00changelog.i',
3012 hgvfs.append(b'00changelog.i',
3014 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3013 b'\0\0\0\2 dummy changelog to prevent using the old repo '
3015 b'layout')
3014 b'layout')
3016
3015
3017 scmutil.writerequires(hgvfs, requirements)
3016 scmutil.writerequires(hgvfs, requirements)
3018
3017
3019 # Write out file telling readers where to find the shared store.
3018 # Write out file telling readers where to find the shared store.
3020 if 'sharedrepo' in createopts:
3019 if 'sharedrepo' in createopts:
3021 hgvfs.write(b'sharedpath', sharedpath)
3020 hgvfs.write(b'sharedpath', sharedpath)
3022
3021
3023 if createopts.get('shareditems'):
3022 if createopts.get('shareditems'):
3024 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3023 shared = b'\n'.join(sorted(createopts['shareditems'])) + b'\n'
3025 hgvfs.write(b'shared', shared)
3024 hgvfs.write(b'shared', shared)
3026
3025
3027 def poisonrepository(repo):
3026 def poisonrepository(repo):
3028 """Poison a repository instance so it can no longer be used."""
3027 """Poison a repository instance so it can no longer be used."""
3029 # Perform any cleanup on the instance.
3028 # Perform any cleanup on the instance.
3030 repo.close()
3029 repo.close()
3031
3030
3032 # Our strategy is to replace the type of the object with one that
3031 # Our strategy is to replace the type of the object with one that
3033 # has all attribute lookups result in error.
3032 # has all attribute lookups result in error.
3034 #
3033 #
3035 # But we have to allow the close() method because some constructors
3034 # But we have to allow the close() method because some constructors
3036 # of repos call close() on repo references.
3035 # of repos call close() on repo references.
3037 class poisonedrepository(object):
3036 class poisonedrepository(object):
3038 def __getattribute__(self, item):
3037 def __getattribute__(self, item):
3039 if item == r'close':
3038 if item == r'close':
3040 return object.__getattribute__(self, item)
3039 return object.__getattribute__(self, item)
3041
3040
3042 raise error.ProgrammingError('repo instances should not be used '
3041 raise error.ProgrammingError('repo instances should not be used '
3043 'after unshare')
3042 'after unshare')
3044
3043
3045 def close(self):
3044 def close(self):
3046 pass
3045 pass
3047
3046
3048 # We may have a repoview, which intercepts __setattr__. So be sure
3047 # We may have a repoview, which intercepts __setattr__. So be sure
3049 # we operate at the lowest level possible.
3048 # we operate at the lowest level possible.
3050 object.__setattr__(repo, r'__class__', poisonedrepository)
3049 object.__setattr__(repo, r'__class__', poisonedrepository)
@@ -1,1807 +1,1800 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16 import subprocess
16 import subprocess
17 import weakref
17 import weakref
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import (
20 from .node import (
21 bin,
21 bin,
22 hex,
22 hex,
23 nullid,
23 nullid,
24 nullrev,
24 nullrev,
25 short,
25 short,
26 wdirid,
26 wdirid,
27 wdirrev,
27 wdirrev,
28 )
28 )
29
29
30 from . import (
30 from . import (
31 encoding,
31 encoding,
32 error,
32 error,
33 match as matchmod,
33 match as matchmod,
34 obsolete,
34 obsolete,
35 obsutil,
35 obsutil,
36 pathutil,
36 pathutil,
37 phases,
37 phases,
38 policy,
38 policy,
39 pycompat,
39 pycompat,
40 revsetlang,
40 revsetlang,
41 similar,
41 similar,
42 smartset,
42 smartset,
43 url,
43 url,
44 util,
44 util,
45 vfs,
45 vfs,
46 )
46 )
47
47
48 from .utils import (
48 from .utils import (
49 procutil,
49 procutil,
50 stringutil,
50 stringutil,
51 )
51 )
52
52
53 if pycompat.iswindows:
53 if pycompat.iswindows:
54 from . import scmwindows as scmplatform
54 from . import scmwindows as scmplatform
55 else:
55 else:
56 from . import scmposix as scmplatform
56 from . import scmposix as scmplatform
57
57
58 parsers = policy.importmod(r'parsers')
58 parsers = policy.importmod(r'parsers')
59
59
60 termsize = scmplatform.termsize
60 termsize = scmplatform.termsize
61
61
62 class status(tuple):
62 class status(tuple):
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
63 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
64 and 'ignored' properties are only relevant to the working copy.
64 and 'ignored' properties are only relevant to the working copy.
65 '''
65 '''
66
66
67 __slots__ = ()
67 __slots__ = ()
68
68
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
69 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
70 clean):
70 clean):
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
71 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
72 ignored, clean))
72 ignored, clean))
73
73
74 @property
74 @property
75 def modified(self):
75 def modified(self):
76 '''files that have been modified'''
76 '''files that have been modified'''
77 return self[0]
77 return self[0]
78
78
79 @property
79 @property
80 def added(self):
80 def added(self):
81 '''files that have been added'''
81 '''files that have been added'''
82 return self[1]
82 return self[1]
83
83
84 @property
84 @property
85 def removed(self):
85 def removed(self):
86 '''files that have been removed'''
86 '''files that have been removed'''
87 return self[2]
87 return self[2]
88
88
89 @property
89 @property
90 def deleted(self):
90 def deleted(self):
91 '''files that are in the dirstate, but have been deleted from the
91 '''files that are in the dirstate, but have been deleted from the
92 working copy (aka "missing")
92 working copy (aka "missing")
93 '''
93 '''
94 return self[3]
94 return self[3]
95
95
96 @property
96 @property
97 def unknown(self):
97 def unknown(self):
98 '''files not in the dirstate that are not ignored'''
98 '''files not in the dirstate that are not ignored'''
99 return self[4]
99 return self[4]
100
100
101 @property
101 @property
102 def ignored(self):
102 def ignored(self):
103 '''files not in the dirstate that are ignored (by _dirignore())'''
103 '''files not in the dirstate that are ignored (by _dirignore())'''
104 return self[5]
104 return self[5]
105
105
106 @property
106 @property
107 def clean(self):
107 def clean(self):
108 '''files that have not been modified'''
108 '''files that have not been modified'''
109 return self[6]
109 return self[6]
110
110
111 def __repr__(self, *args, **kwargs):
111 def __repr__(self, *args, **kwargs):
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
112 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
113 r'unknown=%s, ignored=%s, clean=%s>') %
113 r'unknown=%s, ignored=%s, clean=%s>') %
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
114 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
115
115
116 def itersubrepos(ctx1, ctx2):
116 def itersubrepos(ctx1, ctx2):
117 """find subrepos in ctx1 or ctx2"""
117 """find subrepos in ctx1 or ctx2"""
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
118 # Create a (subpath, ctx) mapping where we prefer subpaths from
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
119 # ctx1. The subpaths from ctx2 are important when the .hgsub file
120 # has been modified (in ctx2) but not yet committed (in ctx1).
120 # has been modified (in ctx2) but not yet committed (in ctx1).
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
121 subpaths = dict.fromkeys(ctx2.substate, ctx2)
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
122 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
123
123
124 missing = set()
124 missing = set()
125
125
126 for subpath in ctx2.substate:
126 for subpath in ctx2.substate:
127 if subpath not in ctx1.substate:
127 if subpath not in ctx1.substate:
128 del subpaths[subpath]
128 del subpaths[subpath]
129 missing.add(subpath)
129 missing.add(subpath)
130
130
131 for subpath, ctx in sorted(subpaths.iteritems()):
131 for subpath, ctx in sorted(subpaths.iteritems()):
132 yield subpath, ctx.sub(subpath)
132 yield subpath, ctx.sub(subpath)
133
133
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
134 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
135 # status and diff will have an accurate result when it does
135 # status and diff will have an accurate result when it does
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
136 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
137 # against itself.
137 # against itself.
138 for subpath in missing:
138 for subpath in missing:
139 yield subpath, ctx2.nullsub(subpath, ctx1)
139 yield subpath, ctx2.nullsub(subpath, ctx1)
140
140
141 def nochangesfound(ui, repo, excluded=None):
141 def nochangesfound(ui, repo, excluded=None):
142 '''Report no changes for push/pull, excluded is None or a list of
142 '''Report no changes for push/pull, excluded is None or a list of
143 nodes excluded from the push/pull.
143 nodes excluded from the push/pull.
144 '''
144 '''
145 secretlist = []
145 secretlist = []
146 if excluded:
146 if excluded:
147 for n in excluded:
147 for n in excluded:
148 ctx = repo[n]
148 ctx = repo[n]
149 if ctx.phase() >= phases.secret and not ctx.extinct():
149 if ctx.phase() >= phases.secret and not ctx.extinct():
150 secretlist.append(n)
150 secretlist.append(n)
151
151
152 if secretlist:
152 if secretlist:
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
153 ui.status(_("no changes found (ignored %d secret changesets)\n")
154 % len(secretlist))
154 % len(secretlist))
155 else:
155 else:
156 ui.status(_("no changes found\n"))
156 ui.status(_("no changes found\n"))
157
157
158 def callcatch(ui, func):
158 def callcatch(ui, func):
159 """call func() with global exception handling
159 """call func() with global exception handling
160
160
161 return func() if no exception happens. otherwise do some error handling
161 return func() if no exception happens. otherwise do some error handling
162 and return an exit code accordingly. does not handle all exceptions.
162 and return an exit code accordingly. does not handle all exceptions.
163 """
163 """
164 try:
164 try:
165 try:
165 try:
166 return func()
166 return func()
167 except: # re-raises
167 except: # re-raises
168 ui.traceback()
168 ui.traceback()
169 raise
169 raise
170 # Global exception handling, alphabetically
170 # Global exception handling, alphabetically
171 # Mercurial-specific first, followed by built-in and library exceptions
171 # Mercurial-specific first, followed by built-in and library exceptions
172 except error.LockHeld as inst:
172 except error.LockHeld as inst:
173 if inst.errno == errno.ETIMEDOUT:
173 if inst.errno == errno.ETIMEDOUT:
174 reason = _('timed out waiting for lock held by %r') % (
174 reason = _('timed out waiting for lock held by %r') % (
175 pycompat.bytestr(inst.locker))
175 pycompat.bytestr(inst.locker))
176 else:
176 else:
177 reason = _('lock held by %r') % inst.locker
177 reason = _('lock held by %r') % inst.locker
178 ui.error(_("abort: %s: %s\n") % (
178 ui.error(_("abort: %s: %s\n") % (
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
179 inst.desc or stringutil.forcebytestr(inst.filename), reason))
180 if not inst.locker:
180 if not inst.locker:
181 ui.error(_("(lock might be very busy)\n"))
181 ui.error(_("(lock might be very busy)\n"))
182 except error.LockUnavailable as inst:
182 except error.LockUnavailable as inst:
183 ui.error(_("abort: could not lock %s: %s\n") %
183 ui.error(_("abort: could not lock %s: %s\n") %
184 (inst.desc or stringutil.forcebytestr(inst.filename),
184 (inst.desc or stringutil.forcebytestr(inst.filename),
185 encoding.strtolocal(inst.strerror)))
185 encoding.strtolocal(inst.strerror)))
186 except error.OutOfBandError as inst:
186 except error.OutOfBandError as inst:
187 if inst.args:
187 if inst.args:
188 msg = _("abort: remote error:\n")
188 msg = _("abort: remote error:\n")
189 else:
189 else:
190 msg = _("abort: remote error\n")
190 msg = _("abort: remote error\n")
191 ui.error(msg)
191 ui.error(msg)
192 if inst.args:
192 if inst.args:
193 ui.error(''.join(inst.args))
193 ui.error(''.join(inst.args))
194 if inst.hint:
194 if inst.hint:
195 ui.error('(%s)\n' % inst.hint)
195 ui.error('(%s)\n' % inst.hint)
196 except error.RepoError as inst:
196 except error.RepoError as inst:
197 ui.error(_("abort: %s!\n") % inst)
197 ui.error(_("abort: %s!\n") % inst)
198 if inst.hint:
198 if inst.hint:
199 ui.error(_("(%s)\n") % inst.hint)
199 ui.error(_("(%s)\n") % inst.hint)
200 except error.ResponseError as inst:
200 except error.ResponseError as inst:
201 ui.error(_("abort: %s") % inst.args[0])
201 ui.error(_("abort: %s") % inst.args[0])
202 msg = inst.args[1]
202 msg = inst.args[1]
203 if isinstance(msg, type(u'')):
203 if isinstance(msg, type(u'')):
204 msg = pycompat.sysbytes(msg)
204 msg = pycompat.sysbytes(msg)
205 if not isinstance(msg, bytes):
205 if not isinstance(msg, bytes):
206 ui.error(" %r\n" % (msg,))
206 ui.error(" %r\n" % (msg,))
207 elif not msg:
207 elif not msg:
208 ui.error(_(" empty string\n"))
208 ui.error(_(" empty string\n"))
209 else:
209 else:
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
210 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
211 except error.CensoredNodeError as inst:
211 except error.CensoredNodeError as inst:
212 ui.error(_("abort: file censored %s!\n") % inst)
212 ui.error(_("abort: file censored %s!\n") % inst)
213 except error.StorageError as inst:
213 except error.StorageError as inst:
214 ui.error(_("abort: %s!\n") % inst)
214 ui.error(_("abort: %s!\n") % inst)
215 except error.InterventionRequired as inst:
215 except error.InterventionRequired as inst:
216 ui.error("%s\n" % inst)
216 ui.error("%s\n" % inst)
217 if inst.hint:
217 if inst.hint:
218 ui.error(_("(%s)\n") % inst.hint)
218 ui.error(_("(%s)\n") % inst.hint)
219 return 1
219 return 1
220 except error.WdirUnsupported:
220 except error.WdirUnsupported:
221 ui.error(_("abort: working directory revision cannot be specified\n"))
221 ui.error(_("abort: working directory revision cannot be specified\n"))
222 except error.Abort as inst:
222 except error.Abort as inst:
223 ui.error(_("abort: %s\n") % inst)
223 ui.error(_("abort: %s\n") % inst)
224 if inst.hint:
224 if inst.hint:
225 ui.error(_("(%s)\n") % inst.hint)
225 ui.error(_("(%s)\n") % inst.hint)
226 except ImportError as inst:
226 except ImportError as inst:
227 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
227 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
228 m = stringutil.forcebytestr(inst).split()[-1]
228 m = stringutil.forcebytestr(inst).split()[-1]
229 if m in "mpatch bdiff".split():
229 if m in "mpatch bdiff".split():
230 ui.error(_("(did you forget to compile extensions?)\n"))
230 ui.error(_("(did you forget to compile extensions?)\n"))
231 elif m in "zlib".split():
231 elif m in "zlib".split():
232 ui.error(_("(is your Python install correct?)\n"))
232 ui.error(_("(is your Python install correct?)\n"))
233 except IOError as inst:
233 except IOError as inst:
234 if util.safehasattr(inst, "code"):
234 if util.safehasattr(inst, "code"):
235 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
235 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
236 elif util.safehasattr(inst, "reason"):
236 elif util.safehasattr(inst, "reason"):
237 try: # usually it is in the form (errno, strerror)
237 try: # usually it is in the form (errno, strerror)
238 reason = inst.reason.args[1]
238 reason = inst.reason.args[1]
239 except (AttributeError, IndexError):
239 except (AttributeError, IndexError):
240 # it might be anything, for example a string
240 # it might be anything, for example a string
241 reason = inst.reason
241 reason = inst.reason
242 if isinstance(reason, pycompat.unicode):
242 if isinstance(reason, pycompat.unicode):
243 # SSLError of Python 2.7.9 contains a unicode
243 # SSLError of Python 2.7.9 contains a unicode
244 reason = encoding.unitolocal(reason)
244 reason = encoding.unitolocal(reason)
245 ui.error(_("abort: error: %s\n") % reason)
245 ui.error(_("abort: error: %s\n") % reason)
246 elif (util.safehasattr(inst, "args")
246 elif (util.safehasattr(inst, "args")
247 and inst.args and inst.args[0] == errno.EPIPE):
247 and inst.args and inst.args[0] == errno.EPIPE):
248 pass
248 pass
249 elif getattr(inst, "strerror", None):
249 elif getattr(inst, "strerror", None):
250 if getattr(inst, "filename", None):
250 if getattr(inst, "filename", None):
251 ui.error(_("abort: %s: %s\n") % (
251 ui.error(_("abort: %s: %s\n") % (
252 encoding.strtolocal(inst.strerror),
252 encoding.strtolocal(inst.strerror),
253 stringutil.forcebytestr(inst.filename)))
253 stringutil.forcebytestr(inst.filename)))
254 else:
254 else:
255 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
255 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
256 else:
256 else:
257 raise
257 raise
258 except OSError as inst:
258 except OSError as inst:
259 if getattr(inst, "filename", None) is not None:
259 if getattr(inst, "filename", None) is not None:
260 ui.error(_("abort: %s: '%s'\n") % (
260 ui.error(_("abort: %s: '%s'\n") % (
261 encoding.strtolocal(inst.strerror),
261 encoding.strtolocal(inst.strerror),
262 stringutil.forcebytestr(inst.filename)))
262 stringutil.forcebytestr(inst.filename)))
263 else:
263 else:
264 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
264 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
265 except MemoryError:
265 except MemoryError:
266 ui.error(_("abort: out of memory\n"))
266 ui.error(_("abort: out of memory\n"))
267 except SystemExit as inst:
267 except SystemExit as inst:
268 # Commands shouldn't sys.exit directly, but give a return code.
268 # Commands shouldn't sys.exit directly, but give a return code.
269 # Just in case catch this and and pass exit code to caller.
269 # Just in case catch this and and pass exit code to caller.
270 return inst.code
270 return inst.code
271 except socket.error as inst:
271 except socket.error as inst:
272 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
272 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
273
273
274 return -1
274 return -1
275
275
276 def checknewlabel(repo, lbl, kind):
276 def checknewlabel(repo, lbl, kind):
277 # Do not use the "kind" parameter in ui output.
277 # Do not use the "kind" parameter in ui output.
278 # It makes strings difficult to translate.
278 # It makes strings difficult to translate.
279 if lbl in ['tip', '.', 'null']:
279 if lbl in ['tip', '.', 'null']:
280 raise error.Abort(_("the name '%s' is reserved") % lbl)
280 raise error.Abort(_("the name '%s' is reserved") % lbl)
281 for c in (':', '\0', '\n', '\r'):
281 for c in (':', '\0', '\n', '\r'):
282 if c in lbl:
282 if c in lbl:
283 raise error.Abort(
283 raise error.Abort(
284 _("%r cannot be used in a name") % pycompat.bytestr(c))
284 _("%r cannot be used in a name") % pycompat.bytestr(c))
285 try:
285 try:
286 int(lbl)
286 int(lbl)
287 raise error.Abort(_("cannot use an integer as a name"))
287 raise error.Abort(_("cannot use an integer as a name"))
288 except ValueError:
288 except ValueError:
289 pass
289 pass
290 if lbl.strip() != lbl:
290 if lbl.strip() != lbl:
291 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
291 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
292
292
293 def checkfilename(f):
293 def checkfilename(f):
294 '''Check that the filename f is an acceptable filename for a tracked file'''
294 '''Check that the filename f is an acceptable filename for a tracked file'''
295 if '\r' in f or '\n' in f:
295 if '\r' in f or '\n' in f:
296 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
296 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
297 % pycompat.bytestr(f))
297 % pycompat.bytestr(f))
298
298
299 def checkportable(ui, f):
299 def checkportable(ui, f):
300 '''Check if filename f is portable and warn or abort depending on config'''
300 '''Check if filename f is portable and warn or abort depending on config'''
301 checkfilename(f)
301 checkfilename(f)
302 abort, warn = checkportabilityalert(ui)
302 abort, warn = checkportabilityalert(ui)
303 if abort or warn:
303 if abort or warn:
304 msg = util.checkwinfilename(f)
304 msg = util.checkwinfilename(f)
305 if msg:
305 if msg:
306 msg = "%s: %s" % (msg, procutil.shellquote(f))
306 msg = "%s: %s" % (msg, procutil.shellquote(f))
307 if abort:
307 if abort:
308 raise error.Abort(msg)
308 raise error.Abort(msg)
309 ui.warn(_("warning: %s\n") % msg)
309 ui.warn(_("warning: %s\n") % msg)
310
310
311 def checkportabilityalert(ui):
311 def checkportabilityalert(ui):
312 '''check if the user's config requests nothing, a warning, or abort for
312 '''check if the user's config requests nothing, a warning, or abort for
313 non-portable filenames'''
313 non-portable filenames'''
314 val = ui.config('ui', 'portablefilenames')
314 val = ui.config('ui', 'portablefilenames')
315 lval = val.lower()
315 lval = val.lower()
316 bval = stringutil.parsebool(val)
316 bval = stringutil.parsebool(val)
317 abort = pycompat.iswindows or lval == 'abort'
317 abort = pycompat.iswindows or lval == 'abort'
318 warn = bval or lval == 'warn'
318 warn = bval or lval == 'warn'
319 if bval is None and not (warn or abort or lval == 'ignore'):
319 if bval is None and not (warn or abort or lval == 'ignore'):
320 raise error.ConfigError(
320 raise error.ConfigError(
321 _("ui.portablefilenames value is invalid ('%s')") % val)
321 _("ui.portablefilenames value is invalid ('%s')") % val)
322 return abort, warn
322 return abort, warn
323
323
324 class casecollisionauditor(object):
324 class casecollisionauditor(object):
325 def __init__(self, ui, abort, dirstate):
325 def __init__(self, ui, abort, dirstate):
326 self._ui = ui
326 self._ui = ui
327 self._abort = abort
327 self._abort = abort
328 allfiles = '\0'.join(dirstate._map)
328 allfiles = '\0'.join(dirstate._map)
329 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
329 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
330 self._dirstate = dirstate
330 self._dirstate = dirstate
331 # The purpose of _newfiles is so that we don't complain about
331 # The purpose of _newfiles is so that we don't complain about
332 # case collisions if someone were to call this object with the
332 # case collisions if someone were to call this object with the
333 # same filename twice.
333 # same filename twice.
334 self._newfiles = set()
334 self._newfiles = set()
335
335
336 def __call__(self, f):
336 def __call__(self, f):
337 if f in self._newfiles:
337 if f in self._newfiles:
338 return
338 return
339 fl = encoding.lower(f)
339 fl = encoding.lower(f)
340 if fl in self._loweredfiles and f not in self._dirstate:
340 if fl in self._loweredfiles and f not in self._dirstate:
341 msg = _('possible case-folding collision for %s') % f
341 msg = _('possible case-folding collision for %s') % f
342 if self._abort:
342 if self._abort:
343 raise error.Abort(msg)
343 raise error.Abort(msg)
344 self._ui.warn(_("warning: %s\n") % msg)
344 self._ui.warn(_("warning: %s\n") % msg)
345 self._loweredfiles.add(fl)
345 self._loweredfiles.add(fl)
346 self._newfiles.add(f)
346 self._newfiles.add(f)
347
347
348 def filteredhash(repo, maxrev):
348 def filteredhash(repo, maxrev):
349 """build hash of filtered revisions in the current repoview.
349 """build hash of filtered revisions in the current repoview.
350
350
351 Multiple caches perform up-to-date validation by checking that the
351 Multiple caches perform up-to-date validation by checking that the
352 tiprev and tipnode stored in the cache file match the current repository.
352 tiprev and tipnode stored in the cache file match the current repository.
353 However, this is not sufficient for validating repoviews because the set
353 However, this is not sufficient for validating repoviews because the set
354 of revisions in the view may change without the repository tiprev and
354 of revisions in the view may change without the repository tiprev and
355 tipnode changing.
355 tipnode changing.
356
356
357 This function hashes all the revs filtered from the view and returns
357 This function hashes all the revs filtered from the view and returns
358 that SHA-1 digest.
358 that SHA-1 digest.
359 """
359 """
360 cl = repo.changelog
360 cl = repo.changelog
361 if not cl.filteredrevs:
361 if not cl.filteredrevs:
362 return None
362 return None
363 key = None
363 key = None
364 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
364 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
365 if revs:
365 if revs:
366 s = hashlib.sha1()
366 s = hashlib.sha1()
367 for rev in revs:
367 for rev in revs:
368 s.update('%d;' % rev)
368 s.update('%d;' % rev)
369 key = s.digest()
369 key = s.digest()
370 return key
370 return key
371
371
372 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
372 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
373 '''yield every hg repository under path, always recursively.
373 '''yield every hg repository under path, always recursively.
374 The recurse flag will only control recursion into repo working dirs'''
374 The recurse flag will only control recursion into repo working dirs'''
375 def errhandler(err):
375 def errhandler(err):
376 if err.filename == path:
376 if err.filename == path:
377 raise err
377 raise err
378 samestat = getattr(os.path, 'samestat', None)
378 samestat = getattr(os.path, 'samestat', None)
379 if followsym and samestat is not None:
379 if followsym and samestat is not None:
380 def adddir(dirlst, dirname):
380 def adddir(dirlst, dirname):
381 dirstat = os.stat(dirname)
381 dirstat = os.stat(dirname)
382 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
382 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
383 if not match:
383 if not match:
384 dirlst.append(dirstat)
384 dirlst.append(dirstat)
385 return not match
385 return not match
386 else:
386 else:
387 followsym = False
387 followsym = False
388
388
389 if (seen_dirs is None) and followsym:
389 if (seen_dirs is None) and followsym:
390 seen_dirs = []
390 seen_dirs = []
391 adddir(seen_dirs, path)
391 adddir(seen_dirs, path)
392 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
392 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
393 dirs.sort()
393 dirs.sort()
394 if '.hg' in dirs:
394 if '.hg' in dirs:
395 yield root # found a repository
395 yield root # found a repository
396 qroot = os.path.join(root, '.hg', 'patches')
396 qroot = os.path.join(root, '.hg', 'patches')
397 if os.path.isdir(os.path.join(qroot, '.hg')):
397 if os.path.isdir(os.path.join(qroot, '.hg')):
398 yield qroot # we have a patch queue repo here
398 yield qroot # we have a patch queue repo here
399 if recurse:
399 if recurse:
400 # avoid recursing inside the .hg directory
400 # avoid recursing inside the .hg directory
401 dirs.remove('.hg')
401 dirs.remove('.hg')
402 else:
402 else:
403 dirs[:] = [] # don't descend further
403 dirs[:] = [] # don't descend further
404 elif followsym:
404 elif followsym:
405 newdirs = []
405 newdirs = []
406 for d in dirs:
406 for d in dirs:
407 fname = os.path.join(root, d)
407 fname = os.path.join(root, d)
408 if adddir(seen_dirs, fname):
408 if adddir(seen_dirs, fname):
409 if os.path.islink(fname):
409 if os.path.islink(fname):
410 for hgname in walkrepos(fname, True, seen_dirs):
410 for hgname in walkrepos(fname, True, seen_dirs):
411 yield hgname
411 yield hgname
412 else:
412 else:
413 newdirs.append(d)
413 newdirs.append(d)
414 dirs[:] = newdirs
414 dirs[:] = newdirs
415
415
416 def binnode(ctx):
416 def binnode(ctx):
417 """Return binary node id for a given basectx"""
417 """Return binary node id for a given basectx"""
418 node = ctx.node()
418 node = ctx.node()
419 if node is None:
419 if node is None:
420 return wdirid
420 return wdirid
421 return node
421 return node
422
422
423 def intrev(ctx):
423 def intrev(ctx):
424 """Return integer for a given basectx that can be used in comparison or
424 """Return integer for a given basectx that can be used in comparison or
425 arithmetic operation"""
425 arithmetic operation"""
426 rev = ctx.rev()
426 rev = ctx.rev()
427 if rev is None:
427 if rev is None:
428 return wdirrev
428 return wdirrev
429 return rev
429 return rev
430
430
431 def formatchangeid(ctx):
431 def formatchangeid(ctx):
432 """Format changectx as '{rev}:{node|formatnode}', which is the default
432 """Format changectx as '{rev}:{node|formatnode}', which is the default
433 template provided by logcmdutil.changesettemplater"""
433 template provided by logcmdutil.changesettemplater"""
434 repo = ctx.repo()
434 repo = ctx.repo()
435 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
435 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
436
436
437 def formatrevnode(ui, rev, node):
437 def formatrevnode(ui, rev, node):
438 """Format given revision and node depending on the current verbosity"""
438 """Format given revision and node depending on the current verbosity"""
439 if ui.debugflag:
439 if ui.debugflag:
440 hexfunc = hex
440 hexfunc = hex
441 else:
441 else:
442 hexfunc = short
442 hexfunc = short
443 return '%d:%s' % (rev, hexfunc(node))
443 return '%d:%s' % (rev, hexfunc(node))
444
444
445 def resolvehexnodeidprefix(repo, prefix):
445 def resolvehexnodeidprefix(repo, prefix):
446 if (prefix.startswith('x') and
446 if (prefix.startswith('x') and
447 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
447 repo.ui.configbool('experimental', 'revisions.prefixhexnode')):
448 prefix = prefix[1:]
448 prefix = prefix[1:]
449 try:
449 try:
450 # Uses unfiltered repo because it's faster when prefix is ambiguous/
450 # Uses unfiltered repo because it's faster when prefix is ambiguous/
451 # This matches the shortesthexnodeidprefix() function below.
451 # This matches the shortesthexnodeidprefix() function below.
452 node = repo.unfiltered().changelog._partialmatch(prefix)
452 node = repo.unfiltered().changelog._partialmatch(prefix)
453 except error.AmbiguousPrefixLookupError:
453 except error.AmbiguousPrefixLookupError:
454 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
454 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
455 if revset:
455 if revset:
456 # Clear config to avoid infinite recursion
456 # Clear config to avoid infinite recursion
457 configoverrides = {('experimental',
457 configoverrides = {('experimental',
458 'revisions.disambiguatewithin'): None}
458 'revisions.disambiguatewithin'): None}
459 with repo.ui.configoverride(configoverrides):
459 with repo.ui.configoverride(configoverrides):
460 revs = repo.anyrevs([revset], user=True)
460 revs = repo.anyrevs([revset], user=True)
461 matches = []
461 matches = []
462 for rev in revs:
462 for rev in revs:
463 node = repo.changelog.node(rev)
463 node = repo.changelog.node(rev)
464 if hex(node).startswith(prefix):
464 if hex(node).startswith(prefix):
465 matches.append(node)
465 matches.append(node)
466 if len(matches) == 1:
466 if len(matches) == 1:
467 return matches[0]
467 return matches[0]
468 raise
468 raise
469 if node is None:
469 if node is None:
470 return
470 return
471 repo.changelog.rev(node) # make sure node isn't filtered
471 repo.changelog.rev(node) # make sure node isn't filtered
472 return node
472 return node
473
473
474 def mayberevnum(repo, prefix):
474 def mayberevnum(repo, prefix):
475 """Checks if the given prefix may be mistaken for a revision number"""
475 """Checks if the given prefix may be mistaken for a revision number"""
476 try:
476 try:
477 i = int(prefix)
477 i = int(prefix)
478 # if we are a pure int, then starting with zero will not be
478 # if we are a pure int, then starting with zero will not be
479 # confused as a rev; or, obviously, if the int is larger
479 # confused as a rev; or, obviously, if the int is larger
480 # than the value of the tip rev. We still need to disambiguate if
480 # than the value of the tip rev. We still need to disambiguate if
481 # prefix == '0', since that *is* a valid revnum.
481 # prefix == '0', since that *is* a valid revnum.
482 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
482 if (prefix != b'0' and prefix[0:1] == b'0') or i >= len(repo):
483 return False
483 return False
484 return True
484 return True
485 except ValueError:
485 except ValueError:
486 return False
486 return False
487
487
488 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
488 def shortesthexnodeidprefix(repo, node, minlength=1, cache=None):
489 """Find the shortest unambiguous prefix that matches hexnode.
489 """Find the shortest unambiguous prefix that matches hexnode.
490
490
491 If "cache" is not None, it must be a dictionary that can be used for
491 If "cache" is not None, it must be a dictionary that can be used for
492 caching between calls to this method.
492 caching between calls to this method.
493 """
493 """
494 # _partialmatch() of filtered changelog could take O(len(repo)) time,
494 # _partialmatch() of filtered changelog could take O(len(repo)) time,
495 # which would be unacceptably slow. so we look for hash collision in
495 # which would be unacceptably slow. so we look for hash collision in
496 # unfiltered space, which means some hashes may be slightly longer.
496 # unfiltered space, which means some hashes may be slightly longer.
497
497
498 minlength=max(minlength, 1)
498 minlength=max(minlength, 1)
499
499
500 def disambiguate(prefix):
500 def disambiguate(prefix):
501 """Disambiguate against revnums."""
501 """Disambiguate against revnums."""
502 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
502 if repo.ui.configbool('experimental', 'revisions.prefixhexnode'):
503 if mayberevnum(repo, prefix):
503 if mayberevnum(repo, prefix):
504 return 'x' + prefix
504 return 'x' + prefix
505 else:
505 else:
506 return prefix
506 return prefix
507
507
508 hexnode = hex(node)
508 hexnode = hex(node)
509 for length in range(len(prefix), len(hexnode) + 1):
509 for length in range(len(prefix), len(hexnode) + 1):
510 prefix = hexnode[:length]
510 prefix = hexnode[:length]
511 if not mayberevnum(repo, prefix):
511 if not mayberevnum(repo, prefix):
512 return prefix
512 return prefix
513
513
514 cl = repo.unfiltered().changelog
514 cl = repo.unfiltered().changelog
515 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
515 revset = repo.ui.config('experimental', 'revisions.disambiguatewithin')
516 if revset:
516 if revset:
517 revs = None
517 revs = None
518 if cache is not None:
518 if cache is not None:
519 revs = cache.get('disambiguationrevset')
519 revs = cache.get('disambiguationrevset')
520 if revs is None:
520 if revs is None:
521 revs = repo.anyrevs([revset], user=True)
521 revs = repo.anyrevs([revset], user=True)
522 if cache is not None:
522 if cache is not None:
523 cache['disambiguationrevset'] = revs
523 cache['disambiguationrevset'] = revs
524 if cl.rev(node) in revs:
524 if cl.rev(node) in revs:
525 hexnode = hex(node)
525 hexnode = hex(node)
526 nodetree = None
526 nodetree = None
527 if cache is not None:
527 if cache is not None:
528 nodetree = cache.get('disambiguationnodetree')
528 nodetree = cache.get('disambiguationnodetree')
529 if not nodetree:
529 if not nodetree:
530 try:
530 try:
531 nodetree = parsers.nodetree(cl.index, len(revs))
531 nodetree = parsers.nodetree(cl.index, len(revs))
532 except AttributeError:
532 except AttributeError:
533 # no native nodetree
533 # no native nodetree
534 pass
534 pass
535 else:
535 else:
536 for r in revs:
536 for r in revs:
537 nodetree.insert(r)
537 nodetree.insert(r)
538 if cache is not None:
538 if cache is not None:
539 cache['disambiguationnodetree'] = nodetree
539 cache['disambiguationnodetree'] = nodetree
540 if nodetree is not None:
540 if nodetree is not None:
541 length = max(nodetree.shortest(node), minlength)
541 length = max(nodetree.shortest(node), minlength)
542 prefix = hexnode[:length]
542 prefix = hexnode[:length]
543 return disambiguate(prefix)
543 return disambiguate(prefix)
544 for length in range(minlength, len(hexnode) + 1):
544 for length in range(minlength, len(hexnode) + 1):
545 matches = []
545 matches = []
546 prefix = hexnode[:length]
546 prefix = hexnode[:length]
547 for rev in revs:
547 for rev in revs:
548 otherhexnode = repo[rev].hex()
548 otherhexnode = repo[rev].hex()
549 if prefix == otherhexnode[:length]:
549 if prefix == otherhexnode[:length]:
550 matches.append(otherhexnode)
550 matches.append(otherhexnode)
551 if len(matches) == 1:
551 if len(matches) == 1:
552 return disambiguate(prefix)
552 return disambiguate(prefix)
553
553
554 try:
554 try:
555 return disambiguate(cl.shortest(node, minlength))
555 return disambiguate(cl.shortest(node, minlength))
556 except error.LookupError:
556 except error.LookupError:
557 raise error.RepoLookupError()
557 raise error.RepoLookupError()
558
558
559 def isrevsymbol(repo, symbol):
559 def isrevsymbol(repo, symbol):
560 """Checks if a symbol exists in the repo.
560 """Checks if a symbol exists in the repo.
561
561
562 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
562 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
563 symbol is an ambiguous nodeid prefix.
563 symbol is an ambiguous nodeid prefix.
564 """
564 """
565 try:
565 try:
566 revsymbol(repo, symbol)
566 revsymbol(repo, symbol)
567 return True
567 return True
568 except error.RepoLookupError:
568 except error.RepoLookupError:
569 return False
569 return False
570
570
571 def revsymbol(repo, symbol):
571 def revsymbol(repo, symbol):
572 """Returns a context given a single revision symbol (as string).
572 """Returns a context given a single revision symbol (as string).
573
573
574 This is similar to revsingle(), but accepts only a single revision symbol,
574 This is similar to revsingle(), but accepts only a single revision symbol,
575 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
575 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
576 not "max(public())".
576 not "max(public())".
577 """
577 """
578 if not isinstance(symbol, bytes):
578 if not isinstance(symbol, bytes):
579 msg = ("symbol (%s of type %s) was not a string, did you mean "
579 msg = ("symbol (%s of type %s) was not a string, did you mean "
580 "repo[symbol]?" % (symbol, type(symbol)))
580 "repo[symbol]?" % (symbol, type(symbol)))
581 raise error.ProgrammingError(msg)
581 raise error.ProgrammingError(msg)
582 try:
582 try:
583 if symbol in ('.', 'tip', 'null'):
583 if symbol in ('.', 'tip', 'null'):
584 return repo[symbol]
584 return repo[symbol]
585
585
586 try:
586 try:
587 r = int(symbol)
587 r = int(symbol)
588 if '%d' % r != symbol:
588 if '%d' % r != symbol:
589 raise ValueError
589 raise ValueError
590 l = len(repo.changelog)
590 l = len(repo.changelog)
591 if r < 0:
591 if r < 0:
592 r += l
592 r += l
593 if r < 0 or r >= l and r != wdirrev:
593 if r < 0 or r >= l and r != wdirrev:
594 raise ValueError
594 raise ValueError
595 return repo[r]
595 return repo[r]
596 except error.FilteredIndexError:
596 except error.FilteredIndexError:
597 raise
597 raise
598 except (ValueError, OverflowError, IndexError):
598 except (ValueError, OverflowError, IndexError):
599 pass
599 pass
600
600
601 if len(symbol) == 40:
601 if len(symbol) == 40:
602 try:
602 try:
603 node = bin(symbol)
603 node = bin(symbol)
604 rev = repo.changelog.rev(node)
604 rev = repo.changelog.rev(node)
605 return repo[rev]
605 return repo[rev]
606 except error.FilteredLookupError:
606 except error.FilteredLookupError:
607 raise
607 raise
608 except (TypeError, LookupError):
608 except (TypeError, LookupError):
609 pass
609 pass
610
610
611 # look up bookmarks through the name interface
611 # look up bookmarks through the name interface
612 try:
612 try:
613 node = repo.names.singlenode(repo, symbol)
613 node = repo.names.singlenode(repo, symbol)
614 rev = repo.changelog.rev(node)
614 rev = repo.changelog.rev(node)
615 return repo[rev]
615 return repo[rev]
616 except KeyError:
616 except KeyError:
617 pass
617 pass
618
618
619 node = resolvehexnodeidprefix(repo, symbol)
619 node = resolvehexnodeidprefix(repo, symbol)
620 if node is not None:
620 if node is not None:
621 rev = repo.changelog.rev(node)
621 rev = repo.changelog.rev(node)
622 return repo[rev]
622 return repo[rev]
623
623
624 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
624 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
625
625
626 except error.WdirUnsupported:
626 except error.WdirUnsupported:
627 return repo[None]
627 return repo[None]
628 except (error.FilteredIndexError, error.FilteredLookupError,
628 except (error.FilteredIndexError, error.FilteredLookupError,
629 error.FilteredRepoLookupError):
629 error.FilteredRepoLookupError):
630 raise _filterederror(repo, symbol)
630 raise _filterederror(repo, symbol)
631
631
632 def _filterederror(repo, changeid):
632 def _filterederror(repo, changeid):
633 """build an exception to be raised about a filtered changeid
633 """build an exception to be raised about a filtered changeid
634
634
635 This is extracted in a function to help extensions (eg: evolve) to
635 This is extracted in a function to help extensions (eg: evolve) to
636 experiment with various message variants."""
636 experiment with various message variants."""
637 if repo.filtername.startswith('visible'):
637 if repo.filtername.startswith('visible'):
638
638
639 # Check if the changeset is obsolete
639 # Check if the changeset is obsolete
640 unfilteredrepo = repo.unfiltered()
640 unfilteredrepo = repo.unfiltered()
641 ctx = revsymbol(unfilteredrepo, changeid)
641 ctx = revsymbol(unfilteredrepo, changeid)
642
642
643 # If the changeset is obsolete, enrich the message with the reason
643 # If the changeset is obsolete, enrich the message with the reason
644 # that made this changeset not visible
644 # that made this changeset not visible
645 if ctx.obsolete():
645 if ctx.obsolete():
646 msg = obsutil._getfilteredreason(repo, changeid, ctx)
646 msg = obsutil._getfilteredreason(repo, changeid, ctx)
647 else:
647 else:
648 msg = _("hidden revision '%s'") % changeid
648 msg = _("hidden revision '%s'") % changeid
649
649
650 hint = _('use --hidden to access hidden revisions')
650 hint = _('use --hidden to access hidden revisions')
651
651
652 return error.FilteredRepoLookupError(msg, hint=hint)
652 return error.FilteredRepoLookupError(msg, hint=hint)
653 msg = _("filtered revision '%s' (not in '%s' subset)")
653 msg = _("filtered revision '%s' (not in '%s' subset)")
654 msg %= (changeid, repo.filtername)
654 msg %= (changeid, repo.filtername)
655 return error.FilteredRepoLookupError(msg)
655 return error.FilteredRepoLookupError(msg)
656
656
657 def revsingle(repo, revspec, default='.', localalias=None):
657 def revsingle(repo, revspec, default='.', localalias=None):
658 if not revspec and revspec != 0:
658 if not revspec and revspec != 0:
659 return repo[default]
659 return repo[default]
660
660
661 l = revrange(repo, [revspec], localalias=localalias)
661 l = revrange(repo, [revspec], localalias=localalias)
662 if not l:
662 if not l:
663 raise error.Abort(_('empty revision set'))
663 raise error.Abort(_('empty revision set'))
664 return repo[l.last()]
664 return repo[l.last()]
665
665
666 def _pairspec(revspec):
666 def _pairspec(revspec):
667 tree = revsetlang.parse(revspec)
667 tree = revsetlang.parse(revspec)
668 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
668 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
669
669
670 def revpair(repo, revs):
670 def revpair(repo, revs):
671 if not revs:
671 if not revs:
672 return repo['.'], repo[None]
672 return repo['.'], repo[None]
673
673
674 l = revrange(repo, revs)
674 l = revrange(repo, revs)
675
675
676 if not l:
676 if not l:
677 first = second = None
677 first = second = None
678 elif l.isascending():
678 elif l.isascending():
679 first = l.min()
679 first = l.min()
680 second = l.max()
680 second = l.max()
681 elif l.isdescending():
681 elif l.isdescending():
682 first = l.max()
682 first = l.max()
683 second = l.min()
683 second = l.min()
684 else:
684 else:
685 first = l.first()
685 first = l.first()
686 second = l.last()
686 second = l.last()
687
687
688 if first is None:
688 if first is None:
689 raise error.Abort(_('empty revision range'))
689 raise error.Abort(_('empty revision range'))
690 if (first == second and len(revs) >= 2
690 if (first == second and len(revs) >= 2
691 and not all(revrange(repo, [r]) for r in revs)):
691 and not all(revrange(repo, [r]) for r in revs)):
692 raise error.Abort(_('empty revision on one side of range'))
692 raise error.Abort(_('empty revision on one side of range'))
693
693
694 # if top-level is range expression, the result must always be a pair
694 # if top-level is range expression, the result must always be a pair
695 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
695 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
696 return repo[first], repo[None]
696 return repo[first], repo[None]
697
697
698 return repo[first], repo[second]
698 return repo[first], repo[second]
699
699
700 def revrange(repo, specs, localalias=None):
700 def revrange(repo, specs, localalias=None):
701 """Execute 1 to many revsets and return the union.
701 """Execute 1 to many revsets and return the union.
702
702
703 This is the preferred mechanism for executing revsets using user-specified
703 This is the preferred mechanism for executing revsets using user-specified
704 config options, such as revset aliases.
704 config options, such as revset aliases.
705
705
706 The revsets specified by ``specs`` will be executed via a chained ``OR``
706 The revsets specified by ``specs`` will be executed via a chained ``OR``
707 expression. If ``specs`` is empty, an empty result is returned.
707 expression. If ``specs`` is empty, an empty result is returned.
708
708
709 ``specs`` can contain integers, in which case they are assumed to be
709 ``specs`` can contain integers, in which case they are assumed to be
710 revision numbers.
710 revision numbers.
711
711
712 It is assumed the revsets are already formatted. If you have arguments
712 It is assumed the revsets are already formatted. If you have arguments
713 that need to be expanded in the revset, call ``revsetlang.formatspec()``
713 that need to be expanded in the revset, call ``revsetlang.formatspec()``
714 and pass the result as an element of ``specs``.
714 and pass the result as an element of ``specs``.
715
715
716 Specifying a single revset is allowed.
716 Specifying a single revset is allowed.
717
717
718 Returns a ``revset.abstractsmartset`` which is a list-like interface over
718 Returns a ``revset.abstractsmartset`` which is a list-like interface over
719 integer revisions.
719 integer revisions.
720 """
720 """
721 allspecs = []
721 allspecs = []
722 for spec in specs:
722 for spec in specs:
723 if isinstance(spec, int):
723 if isinstance(spec, int):
724 spec = revsetlang.formatspec('rev(%d)', spec)
724 spec = revsetlang.formatspec('rev(%d)', spec)
725 allspecs.append(spec)
725 allspecs.append(spec)
726 return repo.anyrevs(allspecs, user=True, localalias=localalias)
726 return repo.anyrevs(allspecs, user=True, localalias=localalias)
727
727
728 def meaningfulparents(repo, ctx):
728 def meaningfulparents(repo, ctx):
729 """Return list of meaningful (or all if debug) parentrevs for rev.
729 """Return list of meaningful (or all if debug) parentrevs for rev.
730
730
731 For merges (two non-nullrev revisions) both parents are meaningful.
731 For merges (two non-nullrev revisions) both parents are meaningful.
732 Otherwise the first parent revision is considered meaningful if it
732 Otherwise the first parent revision is considered meaningful if it
733 is not the preceding revision.
733 is not the preceding revision.
734 """
734 """
735 parents = ctx.parents()
735 parents = ctx.parents()
736 if len(parents) > 1:
736 if len(parents) > 1:
737 return parents
737 return parents
738 if repo.ui.debugflag:
738 if repo.ui.debugflag:
739 return [parents[0], repo[nullrev]]
739 return [parents[0], repo[nullrev]]
740 if parents[0].rev() >= intrev(ctx) - 1:
740 if parents[0].rev() >= intrev(ctx) - 1:
741 return []
741 return []
742 return parents
742 return parents
743
743
744 def expandpats(pats):
744 def expandpats(pats):
745 '''Expand bare globs when running on windows.
745 '''Expand bare globs when running on windows.
746 On posix we assume it already has already been done by sh.'''
746 On posix we assume it already has already been done by sh.'''
747 if not util.expandglobs:
747 if not util.expandglobs:
748 return list(pats)
748 return list(pats)
749 ret = []
749 ret = []
750 for kindpat in pats:
750 for kindpat in pats:
751 kind, pat = matchmod._patsplit(kindpat, None)
751 kind, pat = matchmod._patsplit(kindpat, None)
752 if kind is None:
752 if kind is None:
753 try:
753 try:
754 globbed = glob.glob(pat)
754 globbed = glob.glob(pat)
755 except re.error:
755 except re.error:
756 globbed = [pat]
756 globbed = [pat]
757 if globbed:
757 if globbed:
758 ret.extend(globbed)
758 ret.extend(globbed)
759 continue
759 continue
760 ret.append(kindpat)
760 ret.append(kindpat)
761 return ret
761 return ret
762
762
763 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
763 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
764 badfn=None):
764 badfn=None):
765 '''Return a matcher and the patterns that were used.
765 '''Return a matcher and the patterns that were used.
766 The matcher will warn about bad matches, unless an alternate badfn callback
766 The matcher will warn about bad matches, unless an alternate badfn callback
767 is provided.'''
767 is provided.'''
768 if pats == ("",):
768 if pats == ("",):
769 pats = []
769 pats = []
770 if opts is None:
770 if opts is None:
771 opts = {}
771 opts = {}
772 if not globbed and default == 'relpath':
772 if not globbed and default == 'relpath':
773 pats = expandpats(pats or [])
773 pats = expandpats(pats or [])
774
774
775 def bad(f, msg):
775 def bad(f, msg):
776 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
776 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
777
777
778 if badfn is None:
778 if badfn is None:
779 badfn = bad
779 badfn = bad
780
780
781 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
781 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
782 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
782 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
783
783
784 if m.always():
784 if m.always():
785 pats = []
785 pats = []
786 return m, pats
786 return m, pats
787
787
788 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
788 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
789 badfn=None):
789 badfn=None):
790 '''Return a matcher that will warn about bad matches.'''
790 '''Return a matcher that will warn about bad matches.'''
791 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
791 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
792
792
793 def matchall(repo):
793 def matchall(repo):
794 '''Return a matcher that will efficiently match everything.'''
794 '''Return a matcher that will efficiently match everything.'''
795 return matchmod.always(repo.root, repo.getcwd())
795 return matchmod.always(repo.root, repo.getcwd())
796
796
797 def matchfiles(repo, files, badfn=None):
797 def matchfiles(repo, files, badfn=None):
798 '''Return a matcher that will efficiently match exactly these files.'''
798 '''Return a matcher that will efficiently match exactly these files.'''
799 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
799 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
800
800
801 def parsefollowlinespattern(repo, rev, pat, msg):
801 def parsefollowlinespattern(repo, rev, pat, msg):
802 """Return a file name from `pat` pattern suitable for usage in followlines
802 """Return a file name from `pat` pattern suitable for usage in followlines
803 logic.
803 logic.
804 """
804 """
805 if not matchmod.patkind(pat):
805 if not matchmod.patkind(pat):
806 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
806 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
807 else:
807 else:
808 ctx = repo[rev]
808 ctx = repo[rev]
809 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
809 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
810 files = [f for f in ctx if m(f)]
810 files = [f for f in ctx if m(f)]
811 if len(files) != 1:
811 if len(files) != 1:
812 raise error.ParseError(msg)
812 raise error.ParseError(msg)
813 return files[0]
813 return files[0]
814
814
815 def origpath(ui, repo, filepath):
815 def origpath(ui, repo, filepath):
816 '''customize where .orig files are created
816 '''customize where .orig files are created
817
817
818 Fetch user defined path from config file: [ui] origbackuppath = <path>
818 Fetch user defined path from config file: [ui] origbackuppath = <path>
819 Fall back to default (filepath with .orig suffix) if not specified
819 Fall back to default (filepath with .orig suffix) if not specified
820 '''
820 '''
821 origbackuppath = ui.config('ui', 'origbackuppath')
821 origbackuppath = ui.config('ui', 'origbackuppath')
822 if not origbackuppath:
822 if not origbackuppath:
823 return filepath + ".orig"
823 return filepath + ".orig"
824
824
825 # Convert filepath from an absolute path into a path inside the repo.
825 # Convert filepath from an absolute path into a path inside the repo.
826 filepathfromroot = util.normpath(os.path.relpath(filepath,
826 filepathfromroot = util.normpath(os.path.relpath(filepath,
827 start=repo.root))
827 start=repo.root))
828
828
829 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
829 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
830 origbackupdir = origvfs.dirname(filepathfromroot)
830 origbackupdir = origvfs.dirname(filepathfromroot)
831 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
831 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
832 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
832 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
833
833
834 # Remove any files that conflict with the backup file's path
834 # Remove any files that conflict with the backup file's path
835 for f in reversed(list(util.finddirs(filepathfromroot))):
835 for f in reversed(list(util.finddirs(filepathfromroot))):
836 if origvfs.isfileorlink(f):
836 if origvfs.isfileorlink(f):
837 ui.note(_('removing conflicting file: %s\n')
837 ui.note(_('removing conflicting file: %s\n')
838 % origvfs.join(f))
838 % origvfs.join(f))
839 origvfs.unlink(f)
839 origvfs.unlink(f)
840 break
840 break
841
841
842 origvfs.makedirs(origbackupdir)
842 origvfs.makedirs(origbackupdir)
843
843
844 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
844 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
845 ui.note(_('removing conflicting directory: %s\n')
845 ui.note(_('removing conflicting directory: %s\n')
846 % origvfs.join(filepathfromroot))
846 % origvfs.join(filepathfromroot))
847 origvfs.rmtree(filepathfromroot, forcibly=True)
847 origvfs.rmtree(filepathfromroot, forcibly=True)
848
848
849 return origvfs.join(filepathfromroot)
849 return origvfs.join(filepathfromroot)
850
850
851 class _containsnode(object):
851 class _containsnode(object):
852 """proxy __contains__(node) to container.__contains__ which accepts revs"""
852 """proxy __contains__(node) to container.__contains__ which accepts revs"""
853
853
854 def __init__(self, repo, revcontainer):
854 def __init__(self, repo, revcontainer):
855 self._torev = repo.changelog.rev
855 self._torev = repo.changelog.rev
856 self._revcontains = revcontainer.__contains__
856 self._revcontains = revcontainer.__contains__
857
857
858 def __contains__(self, node):
858 def __contains__(self, node):
859 return self._revcontains(self._torev(node))
859 return self._revcontains(self._torev(node))
860
860
861 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
861 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
862 fixphase=False, targetphase=None, backup=True):
862 fixphase=False, targetphase=None, backup=True):
863 """do common cleanups when old nodes are replaced by new nodes
863 """do common cleanups when old nodes are replaced by new nodes
864
864
865 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
865 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
866 (we might also want to move working directory parent in the future)
866 (we might also want to move working directory parent in the future)
867
867
868 By default, bookmark moves are calculated automatically from 'replacements',
868 By default, bookmark moves are calculated automatically from 'replacements',
869 but 'moves' can be used to override that. Also, 'moves' may include
869 but 'moves' can be used to override that. Also, 'moves' may include
870 additional bookmark moves that should not have associated obsmarkers.
870 additional bookmark moves that should not have associated obsmarkers.
871
871
872 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
872 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
873 have replacements. operation is a string, like "rebase".
873 have replacements. operation is a string, like "rebase".
874
874
875 metadata is dictionary containing metadata to be stored in obsmarker if
875 metadata is dictionary containing metadata to be stored in obsmarker if
876 obsolescence is enabled.
876 obsolescence is enabled.
877 """
877 """
878 assert fixphase or targetphase is None
878 assert fixphase or targetphase is None
879 if not replacements and not moves:
879 if not replacements and not moves:
880 return
880 return
881
881
882 # translate mapping's other forms
882 # translate mapping's other forms
883 if not util.safehasattr(replacements, 'items'):
883 if not util.safehasattr(replacements, 'items'):
884 replacements = {(n,): () for n in replacements}
884 replacements = {(n,): () for n in replacements}
885 else:
885 else:
886 # upgrading non tuple "source" to tuple ones for BC
886 # upgrading non tuple "source" to tuple ones for BC
887 repls = {}
887 repls = {}
888 for key, value in replacements.items():
888 for key, value in replacements.items():
889 if not isinstance(key, tuple):
889 if not isinstance(key, tuple):
890 key = (key,)
890 key = (key,)
891 repls[key] = value
891 repls[key] = value
892 replacements = repls
892 replacements = repls
893
893
894 # Calculate bookmark movements
894 # Calculate bookmark movements
895 if moves is None:
895 if moves is None:
896 moves = {}
896 moves = {}
897 # Unfiltered repo is needed since nodes in replacements might be hidden.
897 # Unfiltered repo is needed since nodes in replacements might be hidden.
898 unfi = repo.unfiltered()
898 unfi = repo.unfiltered()
899 for oldnodes, newnodes in replacements.items():
899 for oldnodes, newnodes in replacements.items():
900 for oldnode in oldnodes:
900 for oldnode in oldnodes:
901 if oldnode in moves:
901 if oldnode in moves:
902 continue
902 continue
903 if len(newnodes) > 1:
903 if len(newnodes) > 1:
904 # usually a split, take the one with biggest rev number
904 # usually a split, take the one with biggest rev number
905 newnode = next(unfi.set('max(%ln)', newnodes)).node()
905 newnode = next(unfi.set('max(%ln)', newnodes)).node()
906 elif len(newnodes) == 0:
906 elif len(newnodes) == 0:
907 # move bookmark backwards
907 # move bookmark backwards
908 allreplaced = []
908 allreplaced = []
909 for rep in replacements:
909 for rep in replacements:
910 allreplaced.extend(rep)
910 allreplaced.extend(rep)
911 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
911 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
912 allreplaced))
912 allreplaced))
913 if roots:
913 if roots:
914 newnode = roots[0].node()
914 newnode = roots[0].node()
915 else:
915 else:
916 newnode = nullid
916 newnode = nullid
917 else:
917 else:
918 newnode = newnodes[0]
918 newnode = newnodes[0]
919 moves[oldnode] = newnode
919 moves[oldnode] = newnode
920
920
921 allnewnodes = [n for ns in replacements.values() for n in ns]
921 allnewnodes = [n for ns in replacements.values() for n in ns]
922 toretract = {}
922 toretract = {}
923 toadvance = {}
923 toadvance = {}
924 if fixphase:
924 if fixphase:
925 precursors = {}
925 precursors = {}
926 for oldnodes, newnodes in replacements.items():
926 for oldnodes, newnodes in replacements.items():
927 for oldnode in oldnodes:
927 for oldnode in oldnodes:
928 for newnode in newnodes:
928 for newnode in newnodes:
929 precursors.setdefault(newnode, []).append(oldnode)
929 precursors.setdefault(newnode, []).append(oldnode)
930
930
931 allnewnodes.sort(key=lambda n: unfi[n].rev())
931 allnewnodes.sort(key=lambda n: unfi[n].rev())
932 newphases = {}
932 newphases = {}
933 def phase(ctx):
933 def phase(ctx):
934 return newphases.get(ctx.node(), ctx.phase())
934 return newphases.get(ctx.node(), ctx.phase())
935 for newnode in allnewnodes:
935 for newnode in allnewnodes:
936 ctx = unfi[newnode]
936 ctx = unfi[newnode]
937 parentphase = max(phase(p) for p in ctx.parents())
937 parentphase = max(phase(p) for p in ctx.parents())
938 if targetphase is None:
938 if targetphase is None:
939 oldphase = max(unfi[oldnode].phase()
939 oldphase = max(unfi[oldnode].phase()
940 for oldnode in precursors[newnode])
940 for oldnode in precursors[newnode])
941 newphase = max(oldphase, parentphase)
941 newphase = max(oldphase, parentphase)
942 else:
942 else:
943 newphase = max(targetphase, parentphase)
943 newphase = max(targetphase, parentphase)
944 newphases[newnode] = newphase
944 newphases[newnode] = newphase
945 if newphase > ctx.phase():
945 if newphase > ctx.phase():
946 toretract.setdefault(newphase, []).append(newnode)
946 toretract.setdefault(newphase, []).append(newnode)
947 elif newphase < ctx.phase():
947 elif newphase < ctx.phase():
948 toadvance.setdefault(newphase, []).append(newnode)
948 toadvance.setdefault(newphase, []).append(newnode)
949
949
950 with repo.transaction('cleanup') as tr:
950 with repo.transaction('cleanup') as tr:
951 # Move bookmarks
951 # Move bookmarks
952 bmarks = repo._bookmarks
952 bmarks = repo._bookmarks
953 bmarkchanges = []
953 bmarkchanges = []
954 for oldnode, newnode in moves.items():
954 for oldnode, newnode in moves.items():
955 oldbmarks = repo.nodebookmarks(oldnode)
955 oldbmarks = repo.nodebookmarks(oldnode)
956 if not oldbmarks:
956 if not oldbmarks:
957 continue
957 continue
958 from . import bookmarks # avoid import cycle
958 from . import bookmarks # avoid import cycle
959 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
959 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
960 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
960 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
961 hex(oldnode), hex(newnode)))
961 hex(oldnode), hex(newnode)))
962 # Delete divergent bookmarks being parents of related newnodes
962 # Delete divergent bookmarks being parents of related newnodes
963 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
963 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
964 allnewnodes, newnode, oldnode)
964 allnewnodes, newnode, oldnode)
965 deletenodes = _containsnode(repo, deleterevs)
965 deletenodes = _containsnode(repo, deleterevs)
966 for name in oldbmarks:
966 for name in oldbmarks:
967 bmarkchanges.append((name, newnode))
967 bmarkchanges.append((name, newnode))
968 for b in bookmarks.divergent2delete(repo, deletenodes, name):
968 for b in bookmarks.divergent2delete(repo, deletenodes, name):
969 bmarkchanges.append((b, None))
969 bmarkchanges.append((b, None))
970
970
971 if bmarkchanges:
971 if bmarkchanges:
972 bmarks.applychanges(repo, tr, bmarkchanges)
972 bmarks.applychanges(repo, tr, bmarkchanges)
973
973
974 for phase, nodes in toretract.items():
974 for phase, nodes in toretract.items():
975 phases.retractboundary(repo, tr, phase, nodes)
975 phases.retractboundary(repo, tr, phase, nodes)
976 for phase, nodes in toadvance.items():
976 for phase, nodes in toadvance.items():
977 phases.advanceboundary(repo, tr, phase, nodes)
977 phases.advanceboundary(repo, tr, phase, nodes)
978
978
979 # Obsolete or strip nodes
979 # Obsolete or strip nodes
980 if obsolete.isenabled(repo, obsolete.createmarkersopt):
980 if obsolete.isenabled(repo, obsolete.createmarkersopt):
981 # If a node is already obsoleted, and we want to obsolete it
981 # If a node is already obsoleted, and we want to obsolete it
982 # without a successor, skip that obssolete request since it's
982 # without a successor, skip that obssolete request since it's
983 # unnecessary. That's the "if s or not isobs(n)" check below.
983 # unnecessary. That's the "if s or not isobs(n)" check below.
984 # Also sort the node in topology order, that might be useful for
984 # Also sort the node in topology order, that might be useful for
985 # some obsstore logic.
985 # some obsstore logic.
986 # NOTE: the sorting might belong to createmarkers.
986 # NOTE: the sorting might belong to createmarkers.
987 torev = unfi.changelog.rev
987 torev = unfi.changelog.rev
988 sortfunc = lambda ns: torev(ns[0][0])
988 sortfunc = lambda ns: torev(ns[0][0])
989 rels = []
989 rels = []
990 for ns, s in sorted(replacements.items(), key=sortfunc):
990 for ns, s in sorted(replacements.items(), key=sortfunc):
991 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
991 rel = (tuple(unfi[n] for n in ns), tuple(unfi[m] for m in s))
992 rels.append(rel)
992 rels.append(rel)
993 if rels:
993 if rels:
994 obsolete.createmarkers(repo, rels, operation=operation,
994 obsolete.createmarkers(repo, rels, operation=operation,
995 metadata=metadata)
995 metadata=metadata)
996 else:
996 else:
997 from . import repair # avoid import cycle
997 from . import repair # avoid import cycle
998 tostrip = list(n for ns in replacements for n in ns)
998 tostrip = list(n for ns in replacements for n in ns)
999 if tostrip:
999 if tostrip:
1000 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1000 repair.delayedstrip(repo.ui, repo, tostrip, operation,
1001 backup=backup)
1001 backup=backup)
1002
1002
1003 def addremove(repo, matcher, prefix, opts=None):
1003 def addremove(repo, matcher, prefix, opts=None):
1004 if opts is None:
1004 if opts is None:
1005 opts = {}
1005 opts = {}
1006 m = matcher
1006 m = matcher
1007 dry_run = opts.get('dry_run')
1007 dry_run = opts.get('dry_run')
1008 try:
1008 try:
1009 similarity = float(opts.get('similarity') or 0)
1009 similarity = float(opts.get('similarity') or 0)
1010 except ValueError:
1010 except ValueError:
1011 raise error.Abort(_('similarity must be a number'))
1011 raise error.Abort(_('similarity must be a number'))
1012 if similarity < 0 or similarity > 100:
1012 if similarity < 0 or similarity > 100:
1013 raise error.Abort(_('similarity must be between 0 and 100'))
1013 raise error.Abort(_('similarity must be between 0 and 100'))
1014 similarity /= 100.0
1014 similarity /= 100.0
1015
1015
1016 ret = 0
1016 ret = 0
1017 join = lambda f: os.path.join(prefix, f)
1017 join = lambda f: os.path.join(prefix, f)
1018
1018
1019 wctx = repo[None]
1019 wctx = repo[None]
1020 for subpath in sorted(wctx.substate):
1020 for subpath in sorted(wctx.substate):
1021 submatch = matchmod.subdirmatcher(subpath, m)
1021 submatch = matchmod.subdirmatcher(subpath, m)
1022 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1022 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
1023 sub = wctx.sub(subpath)
1023 sub = wctx.sub(subpath)
1024 try:
1024 try:
1025 if sub.addremove(submatch, prefix, opts):
1025 if sub.addremove(submatch, prefix, opts):
1026 ret = 1
1026 ret = 1
1027 except error.LookupError:
1027 except error.LookupError:
1028 repo.ui.status(_("skipping missing subrepository: %s\n")
1028 repo.ui.status(_("skipping missing subrepository: %s\n")
1029 % join(subpath))
1029 % join(subpath))
1030
1030
1031 rejected = []
1031 rejected = []
1032 def badfn(f, msg):
1032 def badfn(f, msg):
1033 if f in m.files():
1033 if f in m.files():
1034 m.bad(f, msg)
1034 m.bad(f, msg)
1035 rejected.append(f)
1035 rejected.append(f)
1036
1036
1037 badmatch = matchmod.badmatch(m, badfn)
1037 badmatch = matchmod.badmatch(m, badfn)
1038 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1038 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
1039 badmatch)
1039 badmatch)
1040
1040
1041 unknownset = set(unknown + forgotten)
1041 unknownset = set(unknown + forgotten)
1042 toprint = unknownset.copy()
1042 toprint = unknownset.copy()
1043 toprint.update(deleted)
1043 toprint.update(deleted)
1044 for abs in sorted(toprint):
1044 for abs in sorted(toprint):
1045 if repo.ui.verbose or not m.exact(abs):
1045 if repo.ui.verbose or not m.exact(abs):
1046 if abs in unknownset:
1046 if abs in unknownset:
1047 status = _('adding %s\n') % m.uipath(abs)
1047 status = _('adding %s\n') % m.uipath(abs)
1048 label = 'ui.addremove.added'
1048 label = 'ui.addremove.added'
1049 else:
1049 else:
1050 status = _('removing %s\n') % m.uipath(abs)
1050 status = _('removing %s\n') % m.uipath(abs)
1051 label = 'ui.addremove.removed'
1051 label = 'ui.addremove.removed'
1052 repo.ui.status(status, label=label)
1052 repo.ui.status(status, label=label)
1053
1053
1054 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1054 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1055 similarity)
1055 similarity)
1056
1056
1057 if not dry_run:
1057 if not dry_run:
1058 _markchanges(repo, unknown + forgotten, deleted, renames)
1058 _markchanges(repo, unknown + forgotten, deleted, renames)
1059
1059
1060 for f in rejected:
1060 for f in rejected:
1061 if f in m.files():
1061 if f in m.files():
1062 return 1
1062 return 1
1063 return ret
1063 return ret
1064
1064
1065 def marktouched(repo, files, similarity=0.0):
1065 def marktouched(repo, files, similarity=0.0):
1066 '''Assert that files have somehow been operated upon. files are relative to
1066 '''Assert that files have somehow been operated upon. files are relative to
1067 the repo root.'''
1067 the repo root.'''
1068 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1068 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
1069 rejected = []
1069 rejected = []
1070
1070
1071 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1071 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
1072
1072
1073 if repo.ui.verbose:
1073 if repo.ui.verbose:
1074 unknownset = set(unknown + forgotten)
1074 unknownset = set(unknown + forgotten)
1075 toprint = unknownset.copy()
1075 toprint = unknownset.copy()
1076 toprint.update(deleted)
1076 toprint.update(deleted)
1077 for abs in sorted(toprint):
1077 for abs in sorted(toprint):
1078 if abs in unknownset:
1078 if abs in unknownset:
1079 status = _('adding %s\n') % abs
1079 status = _('adding %s\n') % abs
1080 else:
1080 else:
1081 status = _('removing %s\n') % abs
1081 status = _('removing %s\n') % abs
1082 repo.ui.status(status)
1082 repo.ui.status(status)
1083
1083
1084 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1084 renames = _findrenames(repo, m, added + unknown, removed + deleted,
1085 similarity)
1085 similarity)
1086
1086
1087 _markchanges(repo, unknown + forgotten, deleted, renames)
1087 _markchanges(repo, unknown + forgotten, deleted, renames)
1088
1088
1089 for f in rejected:
1089 for f in rejected:
1090 if f in m.files():
1090 if f in m.files():
1091 return 1
1091 return 1
1092 return 0
1092 return 0
1093
1093
1094 def _interestingfiles(repo, matcher):
1094 def _interestingfiles(repo, matcher):
1095 '''Walk dirstate with matcher, looking for files that addremove would care
1095 '''Walk dirstate with matcher, looking for files that addremove would care
1096 about.
1096 about.
1097
1097
1098 This is different from dirstate.status because it doesn't care about
1098 This is different from dirstate.status because it doesn't care about
1099 whether files are modified or clean.'''
1099 whether files are modified or clean.'''
1100 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1100 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1101 audit_path = pathutil.pathauditor(repo.root, cached=True)
1101 audit_path = pathutil.pathauditor(repo.root, cached=True)
1102
1102
1103 ctx = repo[None]
1103 ctx = repo[None]
1104 dirstate = repo.dirstate
1104 dirstate = repo.dirstate
1105 matcher = repo.narrowmatch(matcher, includeexact=True)
1105 matcher = repo.narrowmatch(matcher, includeexact=True)
1106 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1106 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1107 unknown=True, ignored=False, full=False)
1107 unknown=True, ignored=False, full=False)
1108 for abs, st in walkresults.iteritems():
1108 for abs, st in walkresults.iteritems():
1109 dstate = dirstate[abs]
1109 dstate = dirstate[abs]
1110 if dstate == '?' and audit_path.check(abs):
1110 if dstate == '?' and audit_path.check(abs):
1111 unknown.append(abs)
1111 unknown.append(abs)
1112 elif dstate != 'r' and not st:
1112 elif dstate != 'r' and not st:
1113 deleted.append(abs)
1113 deleted.append(abs)
1114 elif dstate == 'r' and st:
1114 elif dstate == 'r' and st:
1115 forgotten.append(abs)
1115 forgotten.append(abs)
1116 # for finding renames
1116 # for finding renames
1117 elif dstate == 'r' and not st:
1117 elif dstate == 'r' and not st:
1118 removed.append(abs)
1118 removed.append(abs)
1119 elif dstate == 'a':
1119 elif dstate == 'a':
1120 added.append(abs)
1120 added.append(abs)
1121
1121
1122 return added, unknown, deleted, removed, forgotten
1122 return added, unknown, deleted, removed, forgotten
1123
1123
1124 def _findrenames(repo, matcher, added, removed, similarity):
1124 def _findrenames(repo, matcher, added, removed, similarity):
1125 '''Find renames from removed files to added ones.'''
1125 '''Find renames from removed files to added ones.'''
1126 renames = {}
1126 renames = {}
1127 if similarity > 0:
1127 if similarity > 0:
1128 for old, new, score in similar.findrenames(repo, added, removed,
1128 for old, new, score in similar.findrenames(repo, added, removed,
1129 similarity):
1129 similarity):
1130 if (repo.ui.verbose or not matcher.exact(old)
1130 if (repo.ui.verbose or not matcher.exact(old)
1131 or not matcher.exact(new)):
1131 or not matcher.exact(new)):
1132 repo.ui.status(_('recording removal of %s as rename to %s '
1132 repo.ui.status(_('recording removal of %s as rename to %s '
1133 '(%d%% similar)\n') %
1133 '(%d%% similar)\n') %
1134 (matcher.rel(old), matcher.rel(new),
1134 (matcher.rel(old), matcher.rel(new),
1135 score * 100))
1135 score * 100))
1136 renames[new] = old
1136 renames[new] = old
1137 return renames
1137 return renames
1138
1138
1139 def _markchanges(repo, unknown, deleted, renames):
1139 def _markchanges(repo, unknown, deleted, renames):
1140 '''Marks the files in unknown as added, the files in deleted as removed,
1140 '''Marks the files in unknown as added, the files in deleted as removed,
1141 and the files in renames as copied.'''
1141 and the files in renames as copied.'''
1142 wctx = repo[None]
1142 wctx = repo[None]
1143 with repo.wlock():
1143 with repo.wlock():
1144 wctx.forget(deleted)
1144 wctx.forget(deleted)
1145 wctx.add(unknown)
1145 wctx.add(unknown)
1146 for new, old in renames.iteritems():
1146 for new, old in renames.iteritems():
1147 wctx.copy(old, new)
1147 wctx.copy(old, new)
1148
1148
1149 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1149 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1150 """Update the dirstate to reflect the intent of copying src to dst. For
1150 """Update the dirstate to reflect the intent of copying src to dst. For
1151 different reasons it might not end with dst being marked as copied from src.
1151 different reasons it might not end with dst being marked as copied from src.
1152 """
1152 """
1153 origsrc = repo.dirstate.copied(src) or src
1153 origsrc = repo.dirstate.copied(src) or src
1154 if dst == origsrc: # copying back a copy?
1154 if dst == origsrc: # copying back a copy?
1155 if repo.dirstate[dst] not in 'mn' and not dryrun:
1155 if repo.dirstate[dst] not in 'mn' and not dryrun:
1156 repo.dirstate.normallookup(dst)
1156 repo.dirstate.normallookup(dst)
1157 else:
1157 else:
1158 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1158 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1159 if not ui.quiet:
1159 if not ui.quiet:
1160 ui.warn(_("%s has not been committed yet, so no copy "
1160 ui.warn(_("%s has not been committed yet, so no copy "
1161 "data will be stored for %s.\n")
1161 "data will be stored for %s.\n")
1162 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1162 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1163 if repo.dirstate[dst] in '?r' and not dryrun:
1163 if repo.dirstate[dst] in '?r' and not dryrun:
1164 wctx.add([dst])
1164 wctx.add([dst])
1165 elif not dryrun:
1165 elif not dryrun:
1166 wctx.copy(origsrc, dst)
1166 wctx.copy(origsrc, dst)
1167
1167
1168 def writerequires(opener, requirements):
1168 def writerequires(opener, requirements):
1169 with opener('requires', 'w') as fp:
1169 with opener('requires', 'w') as fp:
1170 for r in sorted(requirements):
1170 for r in sorted(requirements):
1171 fp.write("%s\n" % r)
1171 fp.write("%s\n" % r)
1172
1172
1173 class filecachesubentry(object):
1173 class filecachesubentry(object):
1174 def __init__(self, path, stat):
1174 def __init__(self, path, stat):
1175 self.path = path
1175 self.path = path
1176 self.cachestat = None
1176 self.cachestat = None
1177 self._cacheable = None
1177 self._cacheable = None
1178
1178
1179 if stat:
1179 if stat:
1180 self.cachestat = filecachesubentry.stat(self.path)
1180 self.cachestat = filecachesubentry.stat(self.path)
1181
1181
1182 if self.cachestat:
1182 if self.cachestat:
1183 self._cacheable = self.cachestat.cacheable()
1183 self._cacheable = self.cachestat.cacheable()
1184 else:
1184 else:
1185 # None means we don't know yet
1185 # None means we don't know yet
1186 self._cacheable = None
1186 self._cacheable = None
1187
1187
1188 def refresh(self):
1188 def refresh(self):
1189 if self.cacheable():
1189 if self.cacheable():
1190 self.cachestat = filecachesubentry.stat(self.path)
1190 self.cachestat = filecachesubentry.stat(self.path)
1191
1191
1192 def cacheable(self):
1192 def cacheable(self):
1193 if self._cacheable is not None:
1193 if self._cacheable is not None:
1194 return self._cacheable
1194 return self._cacheable
1195
1195
1196 # we don't know yet, assume it is for now
1196 # we don't know yet, assume it is for now
1197 return True
1197 return True
1198
1198
1199 def changed(self):
1199 def changed(self):
1200 # no point in going further if we can't cache it
1200 # no point in going further if we can't cache it
1201 if not self.cacheable():
1201 if not self.cacheable():
1202 return True
1202 return True
1203
1203
1204 newstat = filecachesubentry.stat(self.path)
1204 newstat = filecachesubentry.stat(self.path)
1205
1205
1206 # we may not know if it's cacheable yet, check again now
1206 # we may not know if it's cacheable yet, check again now
1207 if newstat and self._cacheable is None:
1207 if newstat and self._cacheable is None:
1208 self._cacheable = newstat.cacheable()
1208 self._cacheable = newstat.cacheable()
1209
1209
1210 # check again
1210 # check again
1211 if not self._cacheable:
1211 if not self._cacheable:
1212 return True
1212 return True
1213
1213
1214 if self.cachestat != newstat:
1214 if self.cachestat != newstat:
1215 self.cachestat = newstat
1215 self.cachestat = newstat
1216 return True
1216 return True
1217 else:
1217 else:
1218 return False
1218 return False
1219
1219
1220 @staticmethod
1220 @staticmethod
1221 def stat(path):
1221 def stat(path):
1222 try:
1222 try:
1223 return util.cachestat(path)
1223 return util.cachestat(path)
1224 except OSError as e:
1224 except OSError as e:
1225 if e.errno != errno.ENOENT:
1225 if e.errno != errno.ENOENT:
1226 raise
1226 raise
1227
1227
1228 class filecacheentry(object):
1228 class filecacheentry(object):
1229 def __init__(self, paths, stat=True):
1229 def __init__(self, paths, stat=True):
1230 self._entries = []
1230 self._entries = []
1231 for path in paths:
1231 for path in paths:
1232 self._entries.append(filecachesubentry(path, stat))
1232 self._entries.append(filecachesubentry(path, stat))
1233
1233
1234 def changed(self):
1234 def changed(self):
1235 '''true if any entry has changed'''
1235 '''true if any entry has changed'''
1236 for entry in self._entries:
1236 for entry in self._entries:
1237 if entry.changed():
1237 if entry.changed():
1238 return True
1238 return True
1239 return False
1239 return False
1240
1240
1241 def refresh(self):
1241 def refresh(self):
1242 for entry in self._entries:
1242 for entry in self._entries:
1243 entry.refresh()
1243 entry.refresh()
1244
1244
1245 class filecache(object):
1245 class filecache(object):
1246 """A property like decorator that tracks files under .hg/ for updates.
1246 """A property like decorator that tracks files under .hg/ for updates.
1247
1247
1248 On first access, the files defined as arguments are stat()ed and the
1248 On first access, the files defined as arguments are stat()ed and the
1249 results cached. The decorated function is called. The results are stashed
1249 results cached. The decorated function is called. The results are stashed
1250 away in a ``_filecache`` dict on the object whose method is decorated.
1250 away in a ``_filecache`` dict on the object whose method is decorated.
1251
1251
1252 On subsequent access, the cached result is returned.
1252 On subsequent access, the cached result is used as it is set to the
1253
1253 instance dictionary.
1254 On external property set operations, stat() calls are performed and the new
1255 value is cached.
1256
1254
1257 On property delete operations, cached data is removed.
1255 On external property set/delete operations, the caller must update the
1256 corresponding _filecache entry appropriately. Use __class__.<attr>.set()
1257 instead of directly setting <attr>.
1258
1258
1259 When using the property API, cached data is always returned, if available:
1259 When using the property API, the cached data is always used if available.
1260 no stat() is performed to check if the file has changed and if the function
1260 No stat() is performed to check if the file has changed.
1261 needs to be called to reflect file changes.
1262
1261
1263 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1262 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1264 can populate an entry before the property's getter is called. In this case,
1263 can populate an entry before the property's getter is called. In this case,
1265 entries in ``_filecache`` will be used during property operations,
1264 entries in ``_filecache`` will be used during property operations,
1266 if available. If the underlying file changes, it is up to external callers
1265 if available. If the underlying file changes, it is up to external callers
1267 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1266 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1268 method result as well as possibly calling ``del obj._filecache[attr]`` to
1267 method result as well as possibly calling ``del obj._filecache[attr]`` to
1269 remove the ``filecacheentry``.
1268 remove the ``filecacheentry``.
1270 """
1269 """
1271
1270
1272 def __init__(self, *paths):
1271 def __init__(self, *paths):
1273 self.paths = paths
1272 self.paths = paths
1274
1273
1275 def join(self, obj, fname):
1274 def join(self, obj, fname):
1276 """Used to compute the runtime path of a cached file.
1275 """Used to compute the runtime path of a cached file.
1277
1276
1278 Users should subclass filecache and provide their own version of this
1277 Users should subclass filecache and provide their own version of this
1279 function to call the appropriate join function on 'obj' (an instance
1278 function to call the appropriate join function on 'obj' (an instance
1280 of the class that its member function was decorated).
1279 of the class that its member function was decorated).
1281 """
1280 """
1282 raise NotImplementedError
1281 raise NotImplementedError
1283
1282
1284 def __call__(self, func):
1283 def __call__(self, func):
1285 self.func = func
1284 self.func = func
1286 self.sname = func.__name__
1285 self.sname = func.__name__
1287 self.name = pycompat.sysbytes(self.sname)
1286 self.name = pycompat.sysbytes(self.sname)
1288 return self
1287 return self
1289
1288
1290 def __get__(self, obj, type=None):
1289 def __get__(self, obj, type=None):
1291 # if accessed on the class, return the descriptor itself.
1290 # if accessed on the class, return the descriptor itself.
1292 if obj is None:
1291 if obj is None:
1293 return self
1292 return self
1294 # do we need to check if the file changed?
1293
1295 try:
1294 assert self.sname not in obj.__dict__
1296 return obj.__dict__[self.sname]
1297 except KeyError:
1298 pass
1299
1295
1300 entry = obj._filecache.get(self.name)
1296 entry = obj._filecache.get(self.name)
1301
1297
1302 if entry:
1298 if entry:
1303 if entry.changed():
1299 if entry.changed():
1304 entry.obj = self.func(obj)
1300 entry.obj = self.func(obj)
1305 else:
1301 else:
1306 paths = [self.join(obj, path) for path in self.paths]
1302 paths = [self.join(obj, path) for path in self.paths]
1307
1303
1308 # We stat -before- creating the object so our cache doesn't lie if
1304 # We stat -before- creating the object so our cache doesn't lie if
1309 # a writer modified between the time we read and stat
1305 # a writer modified between the time we read and stat
1310 entry = filecacheentry(paths, True)
1306 entry = filecacheentry(paths, True)
1311 entry.obj = self.func(obj)
1307 entry.obj = self.func(obj)
1312
1308
1313 obj._filecache[self.name] = entry
1309 obj._filecache[self.name] = entry
1314
1310
1315 obj.__dict__[self.sname] = entry.obj
1311 obj.__dict__[self.sname] = entry.obj
1316 return entry.obj
1312 return entry.obj
1317
1313
1318 def __set__(self, obj, value):
1314 # don't implement __set__(), which would make __dict__ lookup as slow as
1315 # function call.
1316
1317 def set(self, obj, value):
1319 if self.name not in obj._filecache:
1318 if self.name not in obj._filecache:
1320 # we add an entry for the missing value because X in __dict__
1319 # we add an entry for the missing value because X in __dict__
1321 # implies X in _filecache
1320 # implies X in _filecache
1322 paths = [self.join(obj, path) for path in self.paths]
1321 paths = [self.join(obj, path) for path in self.paths]
1323 ce = filecacheentry(paths, False)
1322 ce = filecacheentry(paths, False)
1324 obj._filecache[self.name] = ce
1323 obj._filecache[self.name] = ce
1325 else:
1324 else:
1326 ce = obj._filecache[self.name]
1325 ce = obj._filecache[self.name]
1327
1326
1328 ce.obj = value # update cached copy
1327 ce.obj = value # update cached copy
1329 obj.__dict__[self.sname] = value # update copy returned by obj.x
1328 obj.__dict__[self.sname] = value # update copy returned by obj.x
1330
1329
1331 def __delete__(self, obj):
1332 try:
1333 del obj.__dict__[self.sname]
1334 except KeyError:
1335 raise AttributeError(self.sname)
1336
1337 def extdatasource(repo, source):
1330 def extdatasource(repo, source):
1338 """Gather a map of rev -> value dict from the specified source
1331 """Gather a map of rev -> value dict from the specified source
1339
1332
1340 A source spec is treated as a URL, with a special case shell: type
1333 A source spec is treated as a URL, with a special case shell: type
1341 for parsing the output from a shell command.
1334 for parsing the output from a shell command.
1342
1335
1343 The data is parsed as a series of newline-separated records where
1336 The data is parsed as a series of newline-separated records where
1344 each record is a revision specifier optionally followed by a space
1337 each record is a revision specifier optionally followed by a space
1345 and a freeform string value. If the revision is known locally, it
1338 and a freeform string value. If the revision is known locally, it
1346 is converted to a rev, otherwise the record is skipped.
1339 is converted to a rev, otherwise the record is skipped.
1347
1340
1348 Note that both key and value are treated as UTF-8 and converted to
1341 Note that both key and value are treated as UTF-8 and converted to
1349 the local encoding. This allows uniformity between local and
1342 the local encoding. This allows uniformity between local and
1350 remote data sources.
1343 remote data sources.
1351 """
1344 """
1352
1345
1353 spec = repo.ui.config("extdata", source)
1346 spec = repo.ui.config("extdata", source)
1354 if not spec:
1347 if not spec:
1355 raise error.Abort(_("unknown extdata source '%s'") % source)
1348 raise error.Abort(_("unknown extdata source '%s'") % source)
1356
1349
1357 data = {}
1350 data = {}
1358 src = proc = None
1351 src = proc = None
1359 try:
1352 try:
1360 if spec.startswith("shell:"):
1353 if spec.startswith("shell:"):
1361 # external commands should be run relative to the repo root
1354 # external commands should be run relative to the repo root
1362 cmd = spec[6:]
1355 cmd = spec[6:]
1363 proc = subprocess.Popen(procutil.tonativestr(cmd),
1356 proc = subprocess.Popen(procutil.tonativestr(cmd),
1364 shell=True, bufsize=-1,
1357 shell=True, bufsize=-1,
1365 close_fds=procutil.closefds,
1358 close_fds=procutil.closefds,
1366 stdout=subprocess.PIPE,
1359 stdout=subprocess.PIPE,
1367 cwd=procutil.tonativestr(repo.root))
1360 cwd=procutil.tonativestr(repo.root))
1368 src = proc.stdout
1361 src = proc.stdout
1369 else:
1362 else:
1370 # treat as a URL or file
1363 # treat as a URL or file
1371 src = url.open(repo.ui, spec)
1364 src = url.open(repo.ui, spec)
1372 for l in src:
1365 for l in src:
1373 if " " in l:
1366 if " " in l:
1374 k, v = l.strip().split(" ", 1)
1367 k, v = l.strip().split(" ", 1)
1375 else:
1368 else:
1376 k, v = l.strip(), ""
1369 k, v = l.strip(), ""
1377
1370
1378 k = encoding.tolocal(k)
1371 k = encoding.tolocal(k)
1379 try:
1372 try:
1380 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1373 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1381 except (error.LookupError, error.RepoLookupError):
1374 except (error.LookupError, error.RepoLookupError):
1382 pass # we ignore data for nodes that don't exist locally
1375 pass # we ignore data for nodes that don't exist locally
1383 finally:
1376 finally:
1384 if proc:
1377 if proc:
1385 proc.communicate()
1378 proc.communicate()
1386 if src:
1379 if src:
1387 src.close()
1380 src.close()
1388 if proc and proc.returncode != 0:
1381 if proc and proc.returncode != 0:
1389 raise error.Abort(_("extdata command '%s' failed: %s")
1382 raise error.Abort(_("extdata command '%s' failed: %s")
1390 % (cmd, procutil.explainexit(proc.returncode)))
1383 % (cmd, procutil.explainexit(proc.returncode)))
1391
1384
1392 return data
1385 return data
1393
1386
1394 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1387 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1395 if lock is None:
1388 if lock is None:
1396 raise error.LockInheritanceContractViolation(
1389 raise error.LockInheritanceContractViolation(
1397 'lock can only be inherited while held')
1390 'lock can only be inherited while held')
1398 if environ is None:
1391 if environ is None:
1399 environ = {}
1392 environ = {}
1400 with lock.inherit() as locker:
1393 with lock.inherit() as locker:
1401 environ[envvar] = locker
1394 environ[envvar] = locker
1402 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1395 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1403
1396
1404 def wlocksub(repo, cmd, *args, **kwargs):
1397 def wlocksub(repo, cmd, *args, **kwargs):
1405 """run cmd as a subprocess that allows inheriting repo's wlock
1398 """run cmd as a subprocess that allows inheriting repo's wlock
1406
1399
1407 This can only be called while the wlock is held. This takes all the
1400 This can only be called while the wlock is held. This takes all the
1408 arguments that ui.system does, and returns the exit code of the
1401 arguments that ui.system does, and returns the exit code of the
1409 subprocess."""
1402 subprocess."""
1410 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1403 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1411 **kwargs)
1404 **kwargs)
1412
1405
1413 class progress(object):
1406 class progress(object):
1414 def __init__(self, ui, topic, unit="", total=None):
1407 def __init__(self, ui, topic, unit="", total=None):
1415 self.ui = ui
1408 self.ui = ui
1416 self.pos = 0
1409 self.pos = 0
1417 self.topic = topic
1410 self.topic = topic
1418 self.unit = unit
1411 self.unit = unit
1419 self.total = total
1412 self.total = total
1420
1413
1421 def __enter__(self):
1414 def __enter__(self):
1422 return self
1415 return self
1423
1416
1424 def __exit__(self, exc_type, exc_value, exc_tb):
1417 def __exit__(self, exc_type, exc_value, exc_tb):
1425 self.complete()
1418 self.complete()
1426
1419
1427 def update(self, pos, item="", total=None):
1420 def update(self, pos, item="", total=None):
1428 assert pos is not None
1421 assert pos is not None
1429 if total:
1422 if total:
1430 self.total = total
1423 self.total = total
1431 self.pos = pos
1424 self.pos = pos
1432 self._print(item)
1425 self._print(item)
1433
1426
1434 def increment(self, step=1, item="", total=None):
1427 def increment(self, step=1, item="", total=None):
1435 self.update(self.pos + step, item, total)
1428 self.update(self.pos + step, item, total)
1436
1429
1437 def complete(self):
1430 def complete(self):
1438 self.ui.progress(self.topic, None)
1431 self.ui.progress(self.topic, None)
1439
1432
1440 def _print(self, item):
1433 def _print(self, item):
1441 self.ui.progress(self.topic, self.pos, item, self.unit,
1434 self.ui.progress(self.topic, self.pos, item, self.unit,
1442 self.total)
1435 self.total)
1443
1436
1444 def gdinitconfig(ui):
1437 def gdinitconfig(ui):
1445 """helper function to know if a repo should be created as general delta
1438 """helper function to know if a repo should be created as general delta
1446 """
1439 """
1447 # experimental config: format.generaldelta
1440 # experimental config: format.generaldelta
1448 return (ui.configbool('format', 'generaldelta')
1441 return (ui.configbool('format', 'generaldelta')
1449 or ui.configbool('format', 'usegeneraldelta')
1442 or ui.configbool('format', 'usegeneraldelta')
1450 or ui.configbool('format', 'sparse-revlog'))
1443 or ui.configbool('format', 'sparse-revlog'))
1451
1444
1452 def gddeltaconfig(ui):
1445 def gddeltaconfig(ui):
1453 """helper function to know if incoming delta should be optimised
1446 """helper function to know if incoming delta should be optimised
1454 """
1447 """
1455 # experimental config: format.generaldelta
1448 # experimental config: format.generaldelta
1456 return ui.configbool('format', 'generaldelta')
1449 return ui.configbool('format', 'generaldelta')
1457
1450
1458 class simplekeyvaluefile(object):
1451 class simplekeyvaluefile(object):
1459 """A simple file with key=value lines
1452 """A simple file with key=value lines
1460
1453
1461 Keys must be alphanumerics and start with a letter, values must not
1454 Keys must be alphanumerics and start with a letter, values must not
1462 contain '\n' characters"""
1455 contain '\n' characters"""
1463 firstlinekey = '__firstline'
1456 firstlinekey = '__firstline'
1464
1457
1465 def __init__(self, vfs, path, keys=None):
1458 def __init__(self, vfs, path, keys=None):
1466 self.vfs = vfs
1459 self.vfs = vfs
1467 self.path = path
1460 self.path = path
1468
1461
1469 def read(self, firstlinenonkeyval=False):
1462 def read(self, firstlinenonkeyval=False):
1470 """Read the contents of a simple key-value file
1463 """Read the contents of a simple key-value file
1471
1464
1472 'firstlinenonkeyval' indicates whether the first line of file should
1465 'firstlinenonkeyval' indicates whether the first line of file should
1473 be treated as a key-value pair or reuturned fully under the
1466 be treated as a key-value pair or reuturned fully under the
1474 __firstline key."""
1467 __firstline key."""
1475 lines = self.vfs.readlines(self.path)
1468 lines = self.vfs.readlines(self.path)
1476 d = {}
1469 d = {}
1477 if firstlinenonkeyval:
1470 if firstlinenonkeyval:
1478 if not lines:
1471 if not lines:
1479 e = _("empty simplekeyvalue file")
1472 e = _("empty simplekeyvalue file")
1480 raise error.CorruptedState(e)
1473 raise error.CorruptedState(e)
1481 # we don't want to include '\n' in the __firstline
1474 # we don't want to include '\n' in the __firstline
1482 d[self.firstlinekey] = lines[0][:-1]
1475 d[self.firstlinekey] = lines[0][:-1]
1483 del lines[0]
1476 del lines[0]
1484
1477
1485 try:
1478 try:
1486 # the 'if line.strip()' part prevents us from failing on empty
1479 # the 'if line.strip()' part prevents us from failing on empty
1487 # lines which only contain '\n' therefore are not skipped
1480 # lines which only contain '\n' therefore are not skipped
1488 # by 'if line'
1481 # by 'if line'
1489 updatedict = dict(line[:-1].split('=', 1) for line in lines
1482 updatedict = dict(line[:-1].split('=', 1) for line in lines
1490 if line.strip())
1483 if line.strip())
1491 if self.firstlinekey in updatedict:
1484 if self.firstlinekey in updatedict:
1492 e = _("%r can't be used as a key")
1485 e = _("%r can't be used as a key")
1493 raise error.CorruptedState(e % self.firstlinekey)
1486 raise error.CorruptedState(e % self.firstlinekey)
1494 d.update(updatedict)
1487 d.update(updatedict)
1495 except ValueError as e:
1488 except ValueError as e:
1496 raise error.CorruptedState(str(e))
1489 raise error.CorruptedState(str(e))
1497 return d
1490 return d
1498
1491
1499 def write(self, data, firstline=None):
1492 def write(self, data, firstline=None):
1500 """Write key=>value mapping to a file
1493 """Write key=>value mapping to a file
1501 data is a dict. Keys must be alphanumerical and start with a letter.
1494 data is a dict. Keys must be alphanumerical and start with a letter.
1502 Values must not contain newline characters.
1495 Values must not contain newline characters.
1503
1496
1504 If 'firstline' is not None, it is written to file before
1497 If 'firstline' is not None, it is written to file before
1505 everything else, as it is, not in a key=value form"""
1498 everything else, as it is, not in a key=value form"""
1506 lines = []
1499 lines = []
1507 if firstline is not None:
1500 if firstline is not None:
1508 lines.append('%s\n' % firstline)
1501 lines.append('%s\n' % firstline)
1509
1502
1510 for k, v in data.items():
1503 for k, v in data.items():
1511 if k == self.firstlinekey:
1504 if k == self.firstlinekey:
1512 e = "key name '%s' is reserved" % self.firstlinekey
1505 e = "key name '%s' is reserved" % self.firstlinekey
1513 raise error.ProgrammingError(e)
1506 raise error.ProgrammingError(e)
1514 if not k[0:1].isalpha():
1507 if not k[0:1].isalpha():
1515 e = "keys must start with a letter in a key-value file"
1508 e = "keys must start with a letter in a key-value file"
1516 raise error.ProgrammingError(e)
1509 raise error.ProgrammingError(e)
1517 if not k.isalnum():
1510 if not k.isalnum():
1518 e = "invalid key name in a simple key-value file"
1511 e = "invalid key name in a simple key-value file"
1519 raise error.ProgrammingError(e)
1512 raise error.ProgrammingError(e)
1520 if '\n' in v:
1513 if '\n' in v:
1521 e = "invalid value in a simple key-value file"
1514 e = "invalid value in a simple key-value file"
1522 raise error.ProgrammingError(e)
1515 raise error.ProgrammingError(e)
1523 lines.append("%s=%s\n" % (k, v))
1516 lines.append("%s=%s\n" % (k, v))
1524 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1517 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1525 fp.write(''.join(lines))
1518 fp.write(''.join(lines))
1526
1519
1527 _reportobsoletedsource = [
1520 _reportobsoletedsource = [
1528 'debugobsolete',
1521 'debugobsolete',
1529 'pull',
1522 'pull',
1530 'push',
1523 'push',
1531 'serve',
1524 'serve',
1532 'unbundle',
1525 'unbundle',
1533 ]
1526 ]
1534
1527
1535 _reportnewcssource = [
1528 _reportnewcssource = [
1536 'pull',
1529 'pull',
1537 'unbundle',
1530 'unbundle',
1538 ]
1531 ]
1539
1532
1540 def prefetchfiles(repo, revs, match):
1533 def prefetchfiles(repo, revs, match):
1541 """Invokes the registered file prefetch functions, allowing extensions to
1534 """Invokes the registered file prefetch functions, allowing extensions to
1542 ensure the corresponding files are available locally, before the command
1535 ensure the corresponding files are available locally, before the command
1543 uses them."""
1536 uses them."""
1544 if match:
1537 if match:
1545 # The command itself will complain about files that don't exist, so
1538 # The command itself will complain about files that don't exist, so
1546 # don't duplicate the message.
1539 # don't duplicate the message.
1547 match = matchmod.badmatch(match, lambda fn, msg: None)
1540 match = matchmod.badmatch(match, lambda fn, msg: None)
1548 else:
1541 else:
1549 match = matchall(repo)
1542 match = matchall(repo)
1550
1543
1551 fileprefetchhooks(repo, revs, match)
1544 fileprefetchhooks(repo, revs, match)
1552
1545
1553 # a list of (repo, revs, match) prefetch functions
1546 # a list of (repo, revs, match) prefetch functions
1554 fileprefetchhooks = util.hooks()
1547 fileprefetchhooks = util.hooks()
1555
1548
1556 # A marker that tells the evolve extension to suppress its own reporting
1549 # A marker that tells the evolve extension to suppress its own reporting
1557 _reportstroubledchangesets = True
1550 _reportstroubledchangesets = True
1558
1551
1559 def registersummarycallback(repo, otr, txnname=''):
1552 def registersummarycallback(repo, otr, txnname=''):
1560 """register a callback to issue a summary after the transaction is closed
1553 """register a callback to issue a summary after the transaction is closed
1561 """
1554 """
1562 def txmatch(sources):
1555 def txmatch(sources):
1563 return any(txnname.startswith(source) for source in sources)
1556 return any(txnname.startswith(source) for source in sources)
1564
1557
1565 categories = []
1558 categories = []
1566
1559
1567 def reportsummary(func):
1560 def reportsummary(func):
1568 """decorator for report callbacks."""
1561 """decorator for report callbacks."""
1569 # The repoview life cycle is shorter than the one of the actual
1562 # The repoview life cycle is shorter than the one of the actual
1570 # underlying repository. So the filtered object can die before the
1563 # underlying repository. So the filtered object can die before the
1571 # weakref is used leading to troubles. We keep a reference to the
1564 # weakref is used leading to troubles. We keep a reference to the
1572 # unfiltered object and restore the filtering when retrieving the
1565 # unfiltered object and restore the filtering when retrieving the
1573 # repository through the weakref.
1566 # repository through the weakref.
1574 filtername = repo.filtername
1567 filtername = repo.filtername
1575 reporef = weakref.ref(repo.unfiltered())
1568 reporef = weakref.ref(repo.unfiltered())
1576 def wrapped(tr):
1569 def wrapped(tr):
1577 repo = reporef()
1570 repo = reporef()
1578 if filtername:
1571 if filtername:
1579 repo = repo.filtered(filtername)
1572 repo = repo.filtered(filtername)
1580 func(repo, tr)
1573 func(repo, tr)
1581 newcat = '%02i-txnreport' % len(categories)
1574 newcat = '%02i-txnreport' % len(categories)
1582 otr.addpostclose(newcat, wrapped)
1575 otr.addpostclose(newcat, wrapped)
1583 categories.append(newcat)
1576 categories.append(newcat)
1584 return wrapped
1577 return wrapped
1585
1578
1586 if txmatch(_reportobsoletedsource):
1579 if txmatch(_reportobsoletedsource):
1587 @reportsummary
1580 @reportsummary
1588 def reportobsoleted(repo, tr):
1581 def reportobsoleted(repo, tr):
1589 obsoleted = obsutil.getobsoleted(repo, tr)
1582 obsoleted = obsutil.getobsoleted(repo, tr)
1590 if obsoleted:
1583 if obsoleted:
1591 repo.ui.status(_('obsoleted %i changesets\n')
1584 repo.ui.status(_('obsoleted %i changesets\n')
1592 % len(obsoleted))
1585 % len(obsoleted))
1593
1586
1594 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1587 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1595 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1588 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1596 instabilitytypes = [
1589 instabilitytypes = [
1597 ('orphan', 'orphan'),
1590 ('orphan', 'orphan'),
1598 ('phase-divergent', 'phasedivergent'),
1591 ('phase-divergent', 'phasedivergent'),
1599 ('content-divergent', 'contentdivergent'),
1592 ('content-divergent', 'contentdivergent'),
1600 ]
1593 ]
1601
1594
1602 def getinstabilitycounts(repo):
1595 def getinstabilitycounts(repo):
1603 filtered = repo.changelog.filteredrevs
1596 filtered = repo.changelog.filteredrevs
1604 counts = {}
1597 counts = {}
1605 for instability, revset in instabilitytypes:
1598 for instability, revset in instabilitytypes:
1606 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1599 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1607 filtered)
1600 filtered)
1608 return counts
1601 return counts
1609
1602
1610 oldinstabilitycounts = getinstabilitycounts(repo)
1603 oldinstabilitycounts = getinstabilitycounts(repo)
1611 @reportsummary
1604 @reportsummary
1612 def reportnewinstabilities(repo, tr):
1605 def reportnewinstabilities(repo, tr):
1613 newinstabilitycounts = getinstabilitycounts(repo)
1606 newinstabilitycounts = getinstabilitycounts(repo)
1614 for instability, revset in instabilitytypes:
1607 for instability, revset in instabilitytypes:
1615 delta = (newinstabilitycounts[instability] -
1608 delta = (newinstabilitycounts[instability] -
1616 oldinstabilitycounts[instability])
1609 oldinstabilitycounts[instability])
1617 msg = getinstabilitymessage(delta, instability)
1610 msg = getinstabilitymessage(delta, instability)
1618 if msg:
1611 if msg:
1619 repo.ui.warn(msg)
1612 repo.ui.warn(msg)
1620
1613
1621 if txmatch(_reportnewcssource):
1614 if txmatch(_reportnewcssource):
1622 @reportsummary
1615 @reportsummary
1623 def reportnewcs(repo, tr):
1616 def reportnewcs(repo, tr):
1624 """Report the range of new revisions pulled/unbundled."""
1617 """Report the range of new revisions pulled/unbundled."""
1625 origrepolen = tr.changes.get('origrepolen', len(repo))
1618 origrepolen = tr.changes.get('origrepolen', len(repo))
1626 unfi = repo.unfiltered()
1619 unfi = repo.unfiltered()
1627 if origrepolen >= len(unfi):
1620 if origrepolen >= len(unfi):
1628 return
1621 return
1629
1622
1630 # Compute the bounds of new visible revisions' range.
1623 # Compute the bounds of new visible revisions' range.
1631 revs = smartset.spanset(repo, start=origrepolen)
1624 revs = smartset.spanset(repo, start=origrepolen)
1632 if revs:
1625 if revs:
1633 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1626 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1634
1627
1635 if minrev == maxrev:
1628 if minrev == maxrev:
1636 revrange = minrev
1629 revrange = minrev
1637 else:
1630 else:
1638 revrange = '%s:%s' % (minrev, maxrev)
1631 revrange = '%s:%s' % (minrev, maxrev)
1639 draft = len(repo.revs('%ld and draft()', revs))
1632 draft = len(repo.revs('%ld and draft()', revs))
1640 secret = len(repo.revs('%ld and secret()', revs))
1633 secret = len(repo.revs('%ld and secret()', revs))
1641 if not (draft or secret):
1634 if not (draft or secret):
1642 msg = _('new changesets %s\n') % revrange
1635 msg = _('new changesets %s\n') % revrange
1643 elif draft and secret:
1636 elif draft and secret:
1644 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1637 msg = _('new changesets %s (%d drafts, %d secrets)\n')
1645 msg %= (revrange, draft, secret)
1638 msg %= (revrange, draft, secret)
1646 elif draft:
1639 elif draft:
1647 msg = _('new changesets %s (%d drafts)\n')
1640 msg = _('new changesets %s (%d drafts)\n')
1648 msg %= (revrange, draft)
1641 msg %= (revrange, draft)
1649 elif secret:
1642 elif secret:
1650 msg = _('new changesets %s (%d secrets)\n')
1643 msg = _('new changesets %s (%d secrets)\n')
1651 msg %= (revrange, secret)
1644 msg %= (revrange, secret)
1652 else:
1645 else:
1653 errormsg = 'entered unreachable condition'
1646 errormsg = 'entered unreachable condition'
1654 raise error.ProgrammingError(errormsg)
1647 raise error.ProgrammingError(errormsg)
1655 repo.ui.status(msg)
1648 repo.ui.status(msg)
1656
1649
1657 # search new changesets directly pulled as obsolete
1650 # search new changesets directly pulled as obsolete
1658 duplicates = tr.changes.get('revduplicates', ())
1651 duplicates = tr.changes.get('revduplicates', ())
1659 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1652 obsadded = unfi.revs('(%d: + %ld) and obsolete()',
1660 origrepolen, duplicates)
1653 origrepolen, duplicates)
1661 cl = repo.changelog
1654 cl = repo.changelog
1662 extinctadded = [r for r in obsadded if r not in cl]
1655 extinctadded = [r for r in obsadded if r not in cl]
1663 if extinctadded:
1656 if extinctadded:
1664 # They are not just obsolete, but obsolete and invisible
1657 # They are not just obsolete, but obsolete and invisible
1665 # we call them "extinct" internally but the terms have not been
1658 # we call them "extinct" internally but the terms have not been
1666 # exposed to users.
1659 # exposed to users.
1667 msg = '(%d other changesets obsolete on arrival)\n'
1660 msg = '(%d other changesets obsolete on arrival)\n'
1668 repo.ui.status(msg % len(extinctadded))
1661 repo.ui.status(msg % len(extinctadded))
1669
1662
1670 @reportsummary
1663 @reportsummary
1671 def reportphasechanges(repo, tr):
1664 def reportphasechanges(repo, tr):
1672 """Report statistics of phase changes for changesets pre-existing
1665 """Report statistics of phase changes for changesets pre-existing
1673 pull/unbundle.
1666 pull/unbundle.
1674 """
1667 """
1675 origrepolen = tr.changes.get('origrepolen', len(repo))
1668 origrepolen = tr.changes.get('origrepolen', len(repo))
1676 phasetracking = tr.changes.get('phases', {})
1669 phasetracking = tr.changes.get('phases', {})
1677 if not phasetracking:
1670 if not phasetracking:
1678 return
1671 return
1679 published = [
1672 published = [
1680 rev for rev, (old, new) in phasetracking.iteritems()
1673 rev for rev, (old, new) in phasetracking.iteritems()
1681 if new == phases.public and rev < origrepolen
1674 if new == phases.public and rev < origrepolen
1682 ]
1675 ]
1683 if not published:
1676 if not published:
1684 return
1677 return
1685 repo.ui.status(_('%d local changesets published\n')
1678 repo.ui.status(_('%d local changesets published\n')
1686 % len(published))
1679 % len(published))
1687
1680
1688 def getinstabilitymessage(delta, instability):
1681 def getinstabilitymessage(delta, instability):
1689 """function to return the message to show warning about new instabilities
1682 """function to return the message to show warning about new instabilities
1690
1683
1691 exists as a separate function so that extension can wrap to show more
1684 exists as a separate function so that extension can wrap to show more
1692 information like how to fix instabilities"""
1685 information like how to fix instabilities"""
1693 if delta > 0:
1686 if delta > 0:
1694 return _('%i new %s changesets\n') % (delta, instability)
1687 return _('%i new %s changesets\n') % (delta, instability)
1695
1688
1696 def nodesummaries(repo, nodes, maxnumnodes=4):
1689 def nodesummaries(repo, nodes, maxnumnodes=4):
1697 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1690 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1698 return ' '.join(short(h) for h in nodes)
1691 return ' '.join(short(h) for h in nodes)
1699 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1692 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1700 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1693 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1701
1694
1702 def enforcesinglehead(repo, tr, desc):
1695 def enforcesinglehead(repo, tr, desc):
1703 """check that no named branch has multiple heads"""
1696 """check that no named branch has multiple heads"""
1704 if desc in ('strip', 'repair'):
1697 if desc in ('strip', 'repair'):
1705 # skip the logic during strip
1698 # skip the logic during strip
1706 return
1699 return
1707 visible = repo.filtered('visible')
1700 visible = repo.filtered('visible')
1708 # possible improvement: we could restrict the check to affected branch
1701 # possible improvement: we could restrict the check to affected branch
1709 for name, heads in visible.branchmap().iteritems():
1702 for name, heads in visible.branchmap().iteritems():
1710 if len(heads) > 1:
1703 if len(heads) > 1:
1711 msg = _('rejecting multiple heads on branch "%s"')
1704 msg = _('rejecting multiple heads on branch "%s"')
1712 msg %= name
1705 msg %= name
1713 hint = _('%d heads: %s')
1706 hint = _('%d heads: %s')
1714 hint %= (len(heads), nodesummaries(repo, heads))
1707 hint %= (len(heads), nodesummaries(repo, heads))
1715 raise error.Abort(msg, hint=hint)
1708 raise error.Abort(msg, hint=hint)
1716
1709
1717 def wrapconvertsink(sink):
1710 def wrapconvertsink(sink):
1718 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1711 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1719 before it is used, whether or not the convert extension was formally loaded.
1712 before it is used, whether or not the convert extension was formally loaded.
1720 """
1713 """
1721 return sink
1714 return sink
1722
1715
1723 def unhidehashlikerevs(repo, specs, hiddentype):
1716 def unhidehashlikerevs(repo, specs, hiddentype):
1724 """parse the user specs and unhide changesets whose hash or revision number
1717 """parse the user specs and unhide changesets whose hash or revision number
1725 is passed.
1718 is passed.
1726
1719
1727 hiddentype can be: 1) 'warn': warn while unhiding changesets
1720 hiddentype can be: 1) 'warn': warn while unhiding changesets
1728 2) 'nowarn': don't warn while unhiding changesets
1721 2) 'nowarn': don't warn while unhiding changesets
1729
1722
1730 returns a repo object with the required changesets unhidden
1723 returns a repo object with the required changesets unhidden
1731 """
1724 """
1732 if not repo.filtername or not repo.ui.configbool('experimental',
1725 if not repo.filtername or not repo.ui.configbool('experimental',
1733 'directaccess'):
1726 'directaccess'):
1734 return repo
1727 return repo
1735
1728
1736 if repo.filtername not in ('visible', 'visible-hidden'):
1729 if repo.filtername not in ('visible', 'visible-hidden'):
1737 return repo
1730 return repo
1738
1731
1739 symbols = set()
1732 symbols = set()
1740 for spec in specs:
1733 for spec in specs:
1741 try:
1734 try:
1742 tree = revsetlang.parse(spec)
1735 tree = revsetlang.parse(spec)
1743 except error.ParseError: # will be reported by scmutil.revrange()
1736 except error.ParseError: # will be reported by scmutil.revrange()
1744 continue
1737 continue
1745
1738
1746 symbols.update(revsetlang.gethashlikesymbols(tree))
1739 symbols.update(revsetlang.gethashlikesymbols(tree))
1747
1740
1748 if not symbols:
1741 if not symbols:
1749 return repo
1742 return repo
1750
1743
1751 revs = _getrevsfromsymbols(repo, symbols)
1744 revs = _getrevsfromsymbols(repo, symbols)
1752
1745
1753 if not revs:
1746 if not revs:
1754 return repo
1747 return repo
1755
1748
1756 if hiddentype == 'warn':
1749 if hiddentype == 'warn':
1757 unfi = repo.unfiltered()
1750 unfi = repo.unfiltered()
1758 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1751 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1759 repo.ui.warn(_("warning: accessing hidden changesets for write "
1752 repo.ui.warn(_("warning: accessing hidden changesets for write "
1760 "operation: %s\n") % revstr)
1753 "operation: %s\n") % revstr)
1761
1754
1762 # we have to use new filtername to separate branch/tags cache until we can
1755 # we have to use new filtername to separate branch/tags cache until we can
1763 # disbale these cache when revisions are dynamically pinned.
1756 # disbale these cache when revisions are dynamically pinned.
1764 return repo.filtered('visible-hidden', revs)
1757 return repo.filtered('visible-hidden', revs)
1765
1758
1766 def _getrevsfromsymbols(repo, symbols):
1759 def _getrevsfromsymbols(repo, symbols):
1767 """parse the list of symbols and returns a set of revision numbers of hidden
1760 """parse the list of symbols and returns a set of revision numbers of hidden
1768 changesets present in symbols"""
1761 changesets present in symbols"""
1769 revs = set()
1762 revs = set()
1770 unfi = repo.unfiltered()
1763 unfi = repo.unfiltered()
1771 unficl = unfi.changelog
1764 unficl = unfi.changelog
1772 cl = repo.changelog
1765 cl = repo.changelog
1773 tiprev = len(unficl)
1766 tiprev = len(unficl)
1774 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1767 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1775 for s in symbols:
1768 for s in symbols:
1776 try:
1769 try:
1777 n = int(s)
1770 n = int(s)
1778 if n <= tiprev:
1771 if n <= tiprev:
1779 if not allowrevnums:
1772 if not allowrevnums:
1780 continue
1773 continue
1781 else:
1774 else:
1782 if n not in cl:
1775 if n not in cl:
1783 revs.add(n)
1776 revs.add(n)
1784 continue
1777 continue
1785 except ValueError:
1778 except ValueError:
1786 pass
1779 pass
1787
1780
1788 try:
1781 try:
1789 s = resolvehexnodeidprefix(unfi, s)
1782 s = resolvehexnodeidprefix(unfi, s)
1790 except (error.LookupError, error.WdirUnsupported):
1783 except (error.LookupError, error.WdirUnsupported):
1791 s = None
1784 s = None
1792
1785
1793 if s is not None:
1786 if s is not None:
1794 rev = unficl.rev(s)
1787 rev = unficl.rev(s)
1795 if rev not in cl:
1788 if rev not in cl:
1796 revs.add(rev)
1789 revs.add(rev)
1797
1790
1798 return revs
1791 return revs
1799
1792
1800 def bookmarkrevs(repo, mark):
1793 def bookmarkrevs(repo, mark):
1801 """
1794 """
1802 Select revisions reachable by a given bookmark
1795 Select revisions reachable by a given bookmark
1803 """
1796 """
1804 return repo.revs("ancestors(bookmark(%s)) - "
1797 return repo.revs("ancestors(bookmark(%s)) - "
1805 "ancestors(head() and not bookmark(%s)) - "
1798 "ancestors(head() and not bookmark(%s)) - "
1806 "ancestors(bookmark() and not bookmark(%s))",
1799 "ancestors(bookmark() and not bookmark(%s))",
1807 mark, mark, mark)
1800 mark, mark, mark)
@@ -1,269 +1,269 b''
1 from __future__ import absolute_import, print_function
1 from __future__ import absolute_import, print_function
2 import os
2 import os
3 import stat
3 import stat
4 import subprocess
4 import subprocess
5 import sys
5 import sys
6
6
7 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
7 if subprocess.call(['python', '%s/hghave' % os.environ['TESTDIR'],
8 'cacheable']):
8 'cacheable']):
9 sys.exit(80)
9 sys.exit(80)
10
10
11 print_ = print
11 print_ = print
12 def print(*args, **kwargs):
12 def print(*args, **kwargs):
13 """print() wrapper that flushes stdout buffers to avoid py3 buffer issues
13 """print() wrapper that flushes stdout buffers to avoid py3 buffer issues
14
14
15 We could also just write directly to sys.stdout.buffer the way the
15 We could also just write directly to sys.stdout.buffer the way the
16 ui object will, but this was easier for porting the test.
16 ui object will, but this was easier for porting the test.
17 """
17 """
18 print_(*args, **kwargs)
18 print_(*args, **kwargs)
19 sys.stdout.flush()
19 sys.stdout.flush()
20
20
21 from mercurial import (
21 from mercurial import (
22 extensions,
22 extensions,
23 hg,
23 hg,
24 localrepo,
24 localrepo,
25 pycompat,
25 pycompat,
26 ui as uimod,
26 ui as uimod,
27 util,
27 util,
28 vfs as vfsmod,
28 vfs as vfsmod,
29 )
29 )
30
30
31 if pycompat.ispy3:
31 if pycompat.ispy3:
32 xrange = range
32 xrange = range
33
33
34 class fakerepo(object):
34 class fakerepo(object):
35 def __init__(self):
35 def __init__(self):
36 self._filecache = {}
36 self._filecache = {}
37
37
38 class fakevfs(object):
38 class fakevfs(object):
39
39
40 def join(self, p):
40 def join(self, p):
41 return p
41 return p
42
42
43 vfs = fakevfs()
43 vfs = fakevfs()
44
44
45 def unfiltered(self):
45 def unfiltered(self):
46 return self
46 return self
47
47
48 def sjoin(self, p):
48 def sjoin(self, p):
49 return p
49 return p
50
50
51 @localrepo.repofilecache('x', 'y')
51 @localrepo.repofilecache('x', 'y')
52 def cached(self):
52 def cached(self):
53 print('creating')
53 print('creating')
54 return 'string from function'
54 return 'string from function'
55
55
56 def invalidate(self):
56 def invalidate(self):
57 for k in self._filecache:
57 for k in self._filecache:
58 try:
58 try:
59 delattr(self, pycompat.sysstr(k))
59 delattr(self, pycompat.sysstr(k))
60 except AttributeError:
60 except AttributeError:
61 pass
61 pass
62
62
63 def basic(repo):
63 def basic(repo):
64 print("* neither file exists")
64 print("* neither file exists")
65 # calls function
65 # calls function
66 repo.cached
66 repo.cached
67
67
68 repo.invalidate()
68 repo.invalidate()
69 print("* neither file still exists")
69 print("* neither file still exists")
70 # uses cache
70 # uses cache
71 repo.cached
71 repo.cached
72
72
73 # create empty file
73 # create empty file
74 f = open('x', 'w')
74 f = open('x', 'w')
75 f.close()
75 f.close()
76 repo.invalidate()
76 repo.invalidate()
77 print("* empty file x created")
77 print("* empty file x created")
78 # should recreate the object
78 # should recreate the object
79 repo.cached
79 repo.cached
80
80
81 f = open('x', 'w')
81 f = open('x', 'w')
82 f.write('a')
82 f.write('a')
83 f.close()
83 f.close()
84 repo.invalidate()
84 repo.invalidate()
85 print("* file x changed size")
85 print("* file x changed size")
86 # should recreate the object
86 # should recreate the object
87 repo.cached
87 repo.cached
88
88
89 repo.invalidate()
89 repo.invalidate()
90 print("* nothing changed with either file")
90 print("* nothing changed with either file")
91 # stats file again, reuses object
91 # stats file again, reuses object
92 repo.cached
92 repo.cached
93
93
94 # atomic replace file, size doesn't change
94 # atomic replace file, size doesn't change
95 # hopefully st_mtime doesn't change as well so this doesn't use the cache
95 # hopefully st_mtime doesn't change as well so this doesn't use the cache
96 # because of inode change
96 # because of inode change
97 f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
97 f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
98 f.write(b'b')
98 f.write(b'b')
99 f.close()
99 f.close()
100
100
101 repo.invalidate()
101 repo.invalidate()
102 print("* file x changed inode")
102 print("* file x changed inode")
103 repo.cached
103 repo.cached
104
104
105 # create empty file y
105 # create empty file y
106 f = open('y', 'w')
106 f = open('y', 'w')
107 f.close()
107 f.close()
108 repo.invalidate()
108 repo.invalidate()
109 print("* empty file y created")
109 print("* empty file y created")
110 # should recreate the object
110 # should recreate the object
111 repo.cached
111 repo.cached
112
112
113 f = open('y', 'w')
113 f = open('y', 'w')
114 f.write('A')
114 f.write('A')
115 f.close()
115 f.close()
116 repo.invalidate()
116 repo.invalidate()
117 print("* file y changed size")
117 print("* file y changed size")
118 # should recreate the object
118 # should recreate the object
119 repo.cached
119 repo.cached
120
120
121 f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
121 f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
122 f.write(b'B')
122 f.write(b'B')
123 f.close()
123 f.close()
124
124
125 repo.invalidate()
125 repo.invalidate()
126 print("* file y changed inode")
126 print("* file y changed inode")
127 repo.cached
127 repo.cached
128
128
129 f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
129 f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
130 f.write(b'c')
130 f.write(b'c')
131 f.close()
131 f.close()
132 f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
132 f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
133 f.write(b'C')
133 f.write(b'C')
134 f.close()
134 f.close()
135
135
136 repo.invalidate()
136 repo.invalidate()
137 print("* both files changed inode")
137 print("* both files changed inode")
138 repo.cached
138 repo.cached
139
139
140 def fakeuncacheable():
140 def fakeuncacheable():
141 def wrapcacheable(orig, *args, **kwargs):
141 def wrapcacheable(orig, *args, **kwargs):
142 return False
142 return False
143
143
144 def wrapinit(orig, *args, **kwargs):
144 def wrapinit(orig, *args, **kwargs):
145 pass
145 pass
146
146
147 originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit)
147 originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit)
148 origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable',
148 origcacheable = extensions.wrapfunction(util.cachestat, 'cacheable',
149 wrapcacheable)
149 wrapcacheable)
150
150
151 for fn in ['x', 'y']:
151 for fn in ['x', 'y']:
152 try:
152 try:
153 os.remove(fn)
153 os.remove(fn)
154 except OSError:
154 except OSError:
155 pass
155 pass
156
156
157 basic(fakerepo())
157 basic(fakerepo())
158
158
159 util.cachestat.cacheable = origcacheable
159 util.cachestat.cacheable = origcacheable
160 util.cachestat.__init__ = originit
160 util.cachestat.__init__ = originit
161
161
162 def test_filecache_synced():
162 def test_filecache_synced():
163 # test old behavior that caused filecached properties to go out of sync
163 # test old behavior that caused filecached properties to go out of sync
164 os.system('hg init && echo a >> a && hg ci -qAm.')
164 os.system('hg init && echo a >> a && hg ci -qAm.')
165 repo = hg.repository(uimod.ui.load())
165 repo = hg.repository(uimod.ui.load())
166 # first rollback clears the filecache, but changelog to stays in __dict__
166 # first rollback clears the filecache, but changelog to stays in __dict__
167 repo.rollback()
167 repo.rollback()
168 repo.commit(b'.')
168 repo.commit(b'.')
169 # second rollback comes along and touches the changelog externally
169 # second rollback comes along and touches the changelog externally
170 # (file is moved)
170 # (file is moved)
171 repo.rollback()
171 repo.rollback()
172 # but since changelog isn't under the filecache control anymore, we don't
172 # but since changelog isn't under the filecache control anymore, we don't
173 # see that it changed, and return the old changelog without reconstructing
173 # see that it changed, and return the old changelog without reconstructing
174 # it
174 # it
175 repo.commit(b'.')
175 repo.commit(b'.')
176
176
177 def setbeforeget(repo):
177 def setbeforeget(repo):
178 os.remove('x')
178 os.remove('x')
179 os.remove('y')
179 os.remove('y')
180 repo.cached = 'string set externally'
180 repo.__class__.cached.set(repo, 'string set externally')
181 repo.invalidate()
181 repo.invalidate()
182 print("* neither file exists")
182 print("* neither file exists")
183 print(repo.cached)
183 print(repo.cached)
184 repo.invalidate()
184 repo.invalidate()
185 f = open('x', 'w')
185 f = open('x', 'w')
186 f.write('a')
186 f.write('a')
187 f.close()
187 f.close()
188 print("* file x created")
188 print("* file x created")
189 print(repo.cached)
189 print(repo.cached)
190
190
191 repo.cached = 'string 2 set externally'
191 repo.__class__.cached.set(repo, 'string 2 set externally')
192 repo.invalidate()
192 repo.invalidate()
193 print("* string set externally again")
193 print("* string set externally again")
194 print(repo.cached)
194 print(repo.cached)
195
195
196 repo.invalidate()
196 repo.invalidate()
197 f = open('y', 'w')
197 f = open('y', 'w')
198 f.write('b')
198 f.write('b')
199 f.close()
199 f.close()
200 print("* file y created")
200 print("* file y created")
201 print(repo.cached)
201 print(repo.cached)
202
202
203 def antiambiguity():
203 def antiambiguity():
204 filename = 'ambigcheck'
204 filename = 'ambigcheck'
205
205
206 # try some times, because reproduction of ambiguity depends on
206 # try some times, because reproduction of ambiguity depends on
207 # "filesystem time"
207 # "filesystem time"
208 for i in xrange(5):
208 for i in xrange(5):
209 fp = open(filename, 'w')
209 fp = open(filename, 'w')
210 fp.write('FOO')
210 fp.write('FOO')
211 fp.close()
211 fp.close()
212
212
213 oldstat = os.stat(filename)
213 oldstat = os.stat(filename)
214 if oldstat[stat.ST_CTIME] != oldstat[stat.ST_MTIME]:
214 if oldstat[stat.ST_CTIME] != oldstat[stat.ST_MTIME]:
215 # subsequent changing never causes ambiguity
215 # subsequent changing never causes ambiguity
216 continue
216 continue
217
217
218 repetition = 3
218 repetition = 3
219
219
220 # repeat changing via checkambigatclosing, to examine whether
220 # repeat changing via checkambigatclosing, to examine whether
221 # st_mtime is advanced multiple times as expected
221 # st_mtime is advanced multiple times as expected
222 for i in xrange(repetition):
222 for i in xrange(repetition):
223 # explicit closing
223 # explicit closing
224 fp = vfsmod.checkambigatclosing(open(filename, 'a'))
224 fp = vfsmod.checkambigatclosing(open(filename, 'a'))
225 fp.write('FOO')
225 fp.write('FOO')
226 fp.close()
226 fp.close()
227
227
228 # implicit closing by "with" statement
228 # implicit closing by "with" statement
229 with vfsmod.checkambigatclosing(open(filename, 'a')) as fp:
229 with vfsmod.checkambigatclosing(open(filename, 'a')) as fp:
230 fp.write('BAR')
230 fp.write('BAR')
231
231
232 newstat = os.stat(filename)
232 newstat = os.stat(filename)
233 if oldstat[stat.ST_CTIME] != newstat[stat.ST_CTIME]:
233 if oldstat[stat.ST_CTIME] != newstat[stat.ST_CTIME]:
234 # timestamp ambiguity was naturally avoided while repetition
234 # timestamp ambiguity was naturally avoided while repetition
235 continue
235 continue
236
236
237 # st_mtime should be advanced "repetition * 2" times, because
237 # st_mtime should be advanced "repetition * 2" times, because
238 # all changes occurred at same time (in sec)
238 # all changes occurred at same time (in sec)
239 expected = (oldstat[stat.ST_MTIME] + repetition * 2) & 0x7fffffff
239 expected = (oldstat[stat.ST_MTIME] + repetition * 2) & 0x7fffffff
240 if newstat[stat.ST_MTIME] != expected:
240 if newstat[stat.ST_MTIME] != expected:
241 print("'newstat[stat.ST_MTIME] %s is not %s (as %s + %s * 2)" %
241 print("'newstat[stat.ST_MTIME] %s is not %s (as %s + %s * 2)" %
242 (newstat[stat.ST_MTIME], expected,
242 (newstat[stat.ST_MTIME], expected,
243 oldstat[stat.ST_MTIME], repetition))
243 oldstat[stat.ST_MTIME], repetition))
244
244
245 # no more examination is needed regardless of result
245 # no more examination is needed regardless of result
246 break
246 break
247 else:
247 else:
248 # This platform seems too slow to examine anti-ambiguity
248 # This platform seems too slow to examine anti-ambiguity
249 # of file timestamp (or test happened to be executed at
249 # of file timestamp (or test happened to be executed at
250 # bad timing). Exit silently in this case, because running
250 # bad timing). Exit silently in this case, because running
251 # on other faster platforms can detect problems
251 # on other faster platforms can detect problems
252 pass
252 pass
253
253
254 print('basic:')
254 print('basic:')
255 print()
255 print()
256 basic(fakerepo())
256 basic(fakerepo())
257 print()
257 print()
258 print('fakeuncacheable:')
258 print('fakeuncacheable:')
259 print()
259 print()
260 fakeuncacheable()
260 fakeuncacheable()
261 test_filecache_synced()
261 test_filecache_synced()
262 print()
262 print()
263 print('setbeforeget:')
263 print('setbeforeget:')
264 print()
264 print()
265 setbeforeget(fakerepo())
265 setbeforeget(fakerepo())
266 print()
266 print()
267 print('antiambiguity:')
267 print('antiambiguity:')
268 print()
268 print()
269 antiambiguity()
269 antiambiguity()
General Comments 0
You need to be logged in to leave comments. Login now