##// END OF EJS Templates
revlog: use specialized exception for ambiguous prefix lookup...
Martin von Zweigbergk -
r38877:df0873ab default
parent child Browse files
Show More
@@ -1,307 +1,310 b''
1 # error.py - Mercurial exceptions
1 # error.py - Mercurial exceptions
2 #
2 #
3 # Copyright 2005-2008 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2008 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 """Mercurial exceptions.
8 """Mercurial exceptions.
9
9
10 This allows us to catch exceptions at higher levels without forcing
10 This allows us to catch exceptions at higher levels without forcing
11 imports.
11 imports.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 # Do not import anything but pycompat here, please
16 # Do not import anything but pycompat here, please
17 from . import pycompat
17 from . import pycompat
18
18
19 def _tobytes(exc):
19 def _tobytes(exc):
20 """Byte-stringify exception in the same way as BaseException_str()"""
20 """Byte-stringify exception in the same way as BaseException_str()"""
21 if not exc.args:
21 if not exc.args:
22 return b''
22 return b''
23 if len(exc.args) == 1:
23 if len(exc.args) == 1:
24 return pycompat.bytestr(exc.args[0])
24 return pycompat.bytestr(exc.args[0])
25 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
25 return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
26
26
27 class Hint(object):
27 class Hint(object):
28 """Mix-in to provide a hint of an error
28 """Mix-in to provide a hint of an error
29
29
30 This should come first in the inheritance list to consume a hint and
30 This should come first in the inheritance list to consume a hint and
31 pass remaining arguments to the exception class.
31 pass remaining arguments to the exception class.
32 """
32 """
33 def __init__(self, *args, **kw):
33 def __init__(self, *args, **kw):
34 self.hint = kw.pop(r'hint', None)
34 self.hint = kw.pop(r'hint', None)
35 super(Hint, self).__init__(*args, **kw)
35 super(Hint, self).__init__(*args, **kw)
36
36
37 class RevlogError(Hint, Exception):
37 class RevlogError(Hint, Exception):
38 __bytes__ = _tobytes
38 __bytes__ = _tobytes
39
39
40 class FilteredIndexError(IndexError):
40 class FilteredIndexError(IndexError):
41 __bytes__ = _tobytes
41 __bytes__ = _tobytes
42
42
43 class LookupError(RevlogError, KeyError):
43 class LookupError(RevlogError, KeyError):
44 def __init__(self, name, index, message):
44 def __init__(self, name, index, message):
45 self.name = name
45 self.name = name
46 self.index = index
46 self.index = index
47 # this can't be called 'message' because at least some installs of
47 # this can't be called 'message' because at least some installs of
48 # Python 2.6+ complain about the 'message' property being deprecated
48 # Python 2.6+ complain about the 'message' property being deprecated
49 self.lookupmessage = message
49 self.lookupmessage = message
50 if isinstance(name, bytes) and len(name) == 20:
50 if isinstance(name, bytes) and len(name) == 20:
51 from .node import short
51 from .node import short
52 name = short(name)
52 name = short(name)
53 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
53 RevlogError.__init__(self, '%s@%s: %s' % (index, name, message))
54
54
55 def __bytes__(self):
55 def __bytes__(self):
56 return RevlogError.__bytes__(self)
56 return RevlogError.__bytes__(self)
57
57
58 def __str__(self):
58 def __str__(self):
59 return RevlogError.__str__(self)
59 return RevlogError.__str__(self)
60
60
61 class AmbiguousPrefixLookupError(LookupError):
62 pass
63
61 class FilteredLookupError(LookupError):
64 class FilteredLookupError(LookupError):
62 pass
65 pass
63
66
64 class ManifestLookupError(LookupError):
67 class ManifestLookupError(LookupError):
65 pass
68 pass
66
69
67 class CommandError(Exception):
70 class CommandError(Exception):
68 """Exception raised on errors in parsing the command line."""
71 """Exception raised on errors in parsing the command line."""
69 __bytes__ = _tobytes
72 __bytes__ = _tobytes
70
73
71 class InterventionRequired(Hint, Exception):
74 class InterventionRequired(Hint, Exception):
72 """Exception raised when a command requires human intervention."""
75 """Exception raised when a command requires human intervention."""
73 __bytes__ = _tobytes
76 __bytes__ = _tobytes
74
77
75 class Abort(Hint, Exception):
78 class Abort(Hint, Exception):
76 """Raised if a command needs to print an error and exit."""
79 """Raised if a command needs to print an error and exit."""
77 __bytes__ = _tobytes
80 __bytes__ = _tobytes
78
81
79 class HookLoadError(Abort):
82 class HookLoadError(Abort):
80 """raised when loading a hook fails, aborting an operation
83 """raised when loading a hook fails, aborting an operation
81
84
82 Exists to allow more specialized catching."""
85 Exists to allow more specialized catching."""
83
86
84 class HookAbort(Abort):
87 class HookAbort(Abort):
85 """raised when a validation hook fails, aborting an operation
88 """raised when a validation hook fails, aborting an operation
86
89
87 Exists to allow more specialized catching."""
90 Exists to allow more specialized catching."""
88
91
89 class ConfigError(Abort):
92 class ConfigError(Abort):
90 """Exception raised when parsing config files"""
93 """Exception raised when parsing config files"""
91
94
92 class UpdateAbort(Abort):
95 class UpdateAbort(Abort):
93 """Raised when an update is aborted for destination issue"""
96 """Raised when an update is aborted for destination issue"""
94
97
95 class MergeDestAbort(Abort):
98 class MergeDestAbort(Abort):
96 """Raised when an update is aborted for destination issues"""
99 """Raised when an update is aborted for destination issues"""
97
100
98 class NoMergeDestAbort(MergeDestAbort):
101 class NoMergeDestAbort(MergeDestAbort):
99 """Raised when an update is aborted because there is nothing to merge"""
102 """Raised when an update is aborted because there is nothing to merge"""
100
103
101 class ManyMergeDestAbort(MergeDestAbort):
104 class ManyMergeDestAbort(MergeDestAbort):
102 """Raised when an update is aborted because destination is ambiguous"""
105 """Raised when an update is aborted because destination is ambiguous"""
103
106
104 class ResponseExpected(Abort):
107 class ResponseExpected(Abort):
105 """Raised when an EOF is received for a prompt"""
108 """Raised when an EOF is received for a prompt"""
106 def __init__(self):
109 def __init__(self):
107 from .i18n import _
110 from .i18n import _
108 Abort.__init__(self, _('response expected'))
111 Abort.__init__(self, _('response expected'))
109
112
110 class OutOfBandError(Hint, Exception):
113 class OutOfBandError(Hint, Exception):
111 """Exception raised when a remote repo reports failure"""
114 """Exception raised when a remote repo reports failure"""
112 __bytes__ = _tobytes
115 __bytes__ = _tobytes
113
116
114 class ParseError(Hint, Exception):
117 class ParseError(Hint, Exception):
115 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
118 """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
116 __bytes__ = _tobytes
119 __bytes__ = _tobytes
117
120
118 class PatchError(Exception):
121 class PatchError(Exception):
119 __bytes__ = _tobytes
122 __bytes__ = _tobytes
120
123
121 class UnknownIdentifier(ParseError):
124 class UnknownIdentifier(ParseError):
122 """Exception raised when a {rev,file}set references an unknown identifier"""
125 """Exception raised when a {rev,file}set references an unknown identifier"""
123
126
124 def __init__(self, function, symbols):
127 def __init__(self, function, symbols):
125 from .i18n import _
128 from .i18n import _
126 ParseError.__init__(self, _("unknown identifier: %s") % function)
129 ParseError.__init__(self, _("unknown identifier: %s") % function)
127 self.function = function
130 self.function = function
128 self.symbols = symbols
131 self.symbols = symbols
129
132
130 class RepoError(Hint, Exception):
133 class RepoError(Hint, Exception):
131 __bytes__ = _tobytes
134 __bytes__ = _tobytes
132
135
133 class RepoLookupError(RepoError):
136 class RepoLookupError(RepoError):
134 pass
137 pass
135
138
136 class FilteredRepoLookupError(RepoLookupError):
139 class FilteredRepoLookupError(RepoLookupError):
137 pass
140 pass
138
141
139 class CapabilityError(RepoError):
142 class CapabilityError(RepoError):
140 pass
143 pass
141
144
142 class RequirementError(RepoError):
145 class RequirementError(RepoError):
143 """Exception raised if .hg/requires has an unknown entry."""
146 """Exception raised if .hg/requires has an unknown entry."""
144
147
145 class StdioError(IOError):
148 class StdioError(IOError):
146 """Raised if I/O to stdout or stderr fails"""
149 """Raised if I/O to stdout or stderr fails"""
147
150
148 def __init__(self, err):
151 def __init__(self, err):
149 IOError.__init__(self, err.errno, err.strerror)
152 IOError.__init__(self, err.errno, err.strerror)
150
153
151 # no __bytes__() because error message is derived from the standard IOError
154 # no __bytes__() because error message is derived from the standard IOError
152
155
153 class UnsupportedMergeRecords(Abort):
156 class UnsupportedMergeRecords(Abort):
154 def __init__(self, recordtypes):
157 def __init__(self, recordtypes):
155 from .i18n import _
158 from .i18n import _
156 self.recordtypes = sorted(recordtypes)
159 self.recordtypes = sorted(recordtypes)
157 s = ' '.join(self.recordtypes)
160 s = ' '.join(self.recordtypes)
158 Abort.__init__(
161 Abort.__init__(
159 self, _('unsupported merge state records: %s') % s,
162 self, _('unsupported merge state records: %s') % s,
160 hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for '
163 hint=_('see https://mercurial-scm.org/wiki/MergeStateRecords for '
161 'more information'))
164 'more information'))
162
165
163 class UnknownVersion(Abort):
166 class UnknownVersion(Abort):
164 """generic exception for aborting from an encounter with an unknown version
167 """generic exception for aborting from an encounter with an unknown version
165 """
168 """
166
169
167 def __init__(self, msg, hint=None, version=None):
170 def __init__(self, msg, hint=None, version=None):
168 self.version = version
171 self.version = version
169 super(UnknownVersion, self).__init__(msg, hint=hint)
172 super(UnknownVersion, self).__init__(msg, hint=hint)
170
173
171 class LockError(IOError):
174 class LockError(IOError):
172 def __init__(self, errno, strerror, filename, desc):
175 def __init__(self, errno, strerror, filename, desc):
173 IOError.__init__(self, errno, strerror, filename)
176 IOError.__init__(self, errno, strerror, filename)
174 self.desc = desc
177 self.desc = desc
175
178
176 # no __bytes__() because error message is derived from the standard IOError
179 # no __bytes__() because error message is derived from the standard IOError
177
180
178 class LockHeld(LockError):
181 class LockHeld(LockError):
179 def __init__(self, errno, filename, desc, locker):
182 def __init__(self, errno, filename, desc, locker):
180 LockError.__init__(self, errno, 'Lock held', filename, desc)
183 LockError.__init__(self, errno, 'Lock held', filename, desc)
181 self.locker = locker
184 self.locker = locker
182
185
183 class LockUnavailable(LockError):
186 class LockUnavailable(LockError):
184 pass
187 pass
185
188
186 # LockError is for errors while acquiring the lock -- this is unrelated
189 # LockError is for errors while acquiring the lock -- this is unrelated
187 class LockInheritanceContractViolation(RuntimeError):
190 class LockInheritanceContractViolation(RuntimeError):
188 __bytes__ = _tobytes
191 __bytes__ = _tobytes
189
192
190 class ResponseError(Exception):
193 class ResponseError(Exception):
191 """Raised to print an error with part of output and exit."""
194 """Raised to print an error with part of output and exit."""
192 __bytes__ = _tobytes
195 __bytes__ = _tobytes
193
196
194 class UnknownCommand(Exception):
197 class UnknownCommand(Exception):
195 """Exception raised if command is not in the command table."""
198 """Exception raised if command is not in the command table."""
196 __bytes__ = _tobytes
199 __bytes__ = _tobytes
197
200
198 class AmbiguousCommand(Exception):
201 class AmbiguousCommand(Exception):
199 """Exception raised if command shortcut matches more than one command."""
202 """Exception raised if command shortcut matches more than one command."""
200 __bytes__ = _tobytes
203 __bytes__ = _tobytes
201
204
202 # derived from KeyboardInterrupt to simplify some breakout code
205 # derived from KeyboardInterrupt to simplify some breakout code
203 class SignalInterrupt(KeyboardInterrupt):
206 class SignalInterrupt(KeyboardInterrupt):
204 """Exception raised on SIGTERM and SIGHUP."""
207 """Exception raised on SIGTERM and SIGHUP."""
205
208
206 class SignatureError(Exception):
209 class SignatureError(Exception):
207 __bytes__ = _tobytes
210 __bytes__ = _tobytes
208
211
209 class PushRaced(RuntimeError):
212 class PushRaced(RuntimeError):
210 """An exception raised during unbundling that indicate a push race"""
213 """An exception raised during unbundling that indicate a push race"""
211 __bytes__ = _tobytes
214 __bytes__ = _tobytes
212
215
213 class ProgrammingError(Hint, RuntimeError):
216 class ProgrammingError(Hint, RuntimeError):
214 """Raised if a mercurial (core or extension) developer made a mistake"""
217 """Raised if a mercurial (core or extension) developer made a mistake"""
215 __bytes__ = _tobytes
218 __bytes__ = _tobytes
216
219
217 class WdirUnsupported(Exception):
220 class WdirUnsupported(Exception):
218 """An exception which is raised when 'wdir()' is not supported"""
221 """An exception which is raised when 'wdir()' is not supported"""
219 __bytes__ = _tobytes
222 __bytes__ = _tobytes
220
223
221 # bundle2 related errors
224 # bundle2 related errors
222 class BundleValueError(ValueError):
225 class BundleValueError(ValueError):
223 """error raised when bundle2 cannot be processed"""
226 """error raised when bundle2 cannot be processed"""
224 __bytes__ = _tobytes
227 __bytes__ = _tobytes
225
228
226 class BundleUnknownFeatureError(BundleValueError):
229 class BundleUnknownFeatureError(BundleValueError):
227 def __init__(self, parttype=None, params=(), values=()):
230 def __init__(self, parttype=None, params=(), values=()):
228 self.parttype = parttype
231 self.parttype = parttype
229 self.params = params
232 self.params = params
230 self.values = values
233 self.values = values
231 if self.parttype is None:
234 if self.parttype is None:
232 msg = 'Stream Parameter'
235 msg = 'Stream Parameter'
233 else:
236 else:
234 msg = parttype
237 msg = parttype
235 entries = self.params
238 entries = self.params
236 if self.params and self.values:
239 if self.params and self.values:
237 assert len(self.params) == len(self.values)
240 assert len(self.params) == len(self.values)
238 entries = []
241 entries = []
239 for idx, par in enumerate(self.params):
242 for idx, par in enumerate(self.params):
240 val = self.values[idx]
243 val = self.values[idx]
241 if val is None:
244 if val is None:
242 entries.append(val)
245 entries.append(val)
243 else:
246 else:
244 entries.append("%s=%r" % (par, pycompat.maybebytestr(val)))
247 entries.append("%s=%r" % (par, pycompat.maybebytestr(val)))
245 if entries:
248 if entries:
246 msg = '%s - %s' % (msg, ', '.join(entries))
249 msg = '%s - %s' % (msg, ', '.join(entries))
247 ValueError.__init__(self, msg)
250 ValueError.__init__(self, msg)
248
251
249 class ReadOnlyPartError(RuntimeError):
252 class ReadOnlyPartError(RuntimeError):
250 """error raised when code tries to alter a part being generated"""
253 """error raised when code tries to alter a part being generated"""
251 __bytes__ = _tobytes
254 __bytes__ = _tobytes
252
255
253 class PushkeyFailed(Abort):
256 class PushkeyFailed(Abort):
254 """error raised when a pushkey part failed to update a value"""
257 """error raised when a pushkey part failed to update a value"""
255
258
256 def __init__(self, partid, namespace=None, key=None, new=None, old=None,
259 def __init__(self, partid, namespace=None, key=None, new=None, old=None,
257 ret=None):
260 ret=None):
258 self.partid = partid
261 self.partid = partid
259 self.namespace = namespace
262 self.namespace = namespace
260 self.key = key
263 self.key = key
261 self.new = new
264 self.new = new
262 self.old = old
265 self.old = old
263 self.ret = ret
266 self.ret = ret
264 # no i18n expected to be processed into a better message
267 # no i18n expected to be processed into a better message
265 Abort.__init__(self, 'failed to update value for "%s/%s"'
268 Abort.__init__(self, 'failed to update value for "%s/%s"'
266 % (namespace, key))
269 % (namespace, key))
267
270
268 class CensoredNodeError(RevlogError):
271 class CensoredNodeError(RevlogError):
269 """error raised when content verification fails on a censored node
272 """error raised when content verification fails on a censored node
270
273
271 Also contains the tombstone data substituted for the uncensored data.
274 Also contains the tombstone data substituted for the uncensored data.
272 """
275 """
273
276
274 def __init__(self, filename, node, tombstone):
277 def __init__(self, filename, node, tombstone):
275 from .node import short
278 from .node import short
276 RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
279 RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
277 self.tombstone = tombstone
280 self.tombstone = tombstone
278
281
279 class CensoredBaseError(RevlogError):
282 class CensoredBaseError(RevlogError):
280 """error raised when a delta is rejected because its base is censored
283 """error raised when a delta is rejected because its base is censored
281
284
282 A delta based on a censored revision must be formed as single patch
285 A delta based on a censored revision must be formed as single patch
283 operation which replaces the entire base with new content. This ensures
286 operation which replaces the entire base with new content. This ensures
284 the delta may be applied by clones which have not censored the base.
287 the delta may be applied by clones which have not censored the base.
285 """
288 """
286
289
287 class InvalidBundleSpecification(Exception):
290 class InvalidBundleSpecification(Exception):
288 """error raised when a bundle specification is invalid.
291 """error raised when a bundle specification is invalid.
289
292
290 This is used for syntax errors as opposed to support errors.
293 This is used for syntax errors as opposed to support errors.
291 """
294 """
292 __bytes__ = _tobytes
295 __bytes__ = _tobytes
293
296
294 class UnsupportedBundleSpecification(Exception):
297 class UnsupportedBundleSpecification(Exception):
295 """error raised when a bundle specification is not supported."""
298 """error raised when a bundle specification is not supported."""
296 __bytes__ = _tobytes
299 __bytes__ = _tobytes
297
300
298 class CorruptedState(Exception):
301 class CorruptedState(Exception):
299 """error raised when a command is not able to read its state from file"""
302 """error raised when a command is not able to read its state from file"""
300 __bytes__ = _tobytes
303 __bytes__ = _tobytes
301
304
302 class PeerTransportError(Abort):
305 class PeerTransportError(Abort):
303 """Transport-level I/O error when communicating with a peer repo."""
306 """Transport-level I/O error when communicating with a peer repo."""
304
307
305 class InMemoryMergeConflictsError(Exception):
308 class InMemoryMergeConflictsError(Exception):
306 """Exception raised when merge conflicts arose during an in-memory merge."""
309 """Exception raised when merge conflicts arose during an in-memory merge."""
307 __bytes__ = _tobytes
310 __bytes__ = _tobytes
@@ -1,2401 +1,2402 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 hex,
20 hex,
21 nullid,
21 nullid,
22 short,
22 short,
23 )
23 )
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 branchmap,
26 branchmap,
27 bundle2,
27 bundle2,
28 changegroup,
28 changegroup,
29 changelog,
29 changelog,
30 color,
30 color,
31 context,
31 context,
32 dirstate,
32 dirstate,
33 dirstateguard,
33 dirstateguard,
34 discovery,
34 discovery,
35 encoding,
35 encoding,
36 error,
36 error,
37 exchange,
37 exchange,
38 extensions,
38 extensions,
39 filelog,
39 filelog,
40 hook,
40 hook,
41 lock as lockmod,
41 lock as lockmod,
42 manifest,
42 manifest,
43 match as matchmod,
43 match as matchmod,
44 merge as mergemod,
44 merge as mergemod,
45 mergeutil,
45 mergeutil,
46 namespaces,
46 namespaces,
47 narrowspec,
47 narrowspec,
48 obsolete,
48 obsolete,
49 pathutil,
49 pathutil,
50 phases,
50 phases,
51 pushkey,
51 pushkey,
52 pycompat,
52 pycompat,
53 repository,
53 repository,
54 repoview,
54 repoview,
55 revset,
55 revset,
56 revsetlang,
56 revsetlang,
57 scmutil,
57 scmutil,
58 sparse,
58 sparse,
59 store,
59 store,
60 subrepoutil,
60 subrepoutil,
61 tags as tagsmod,
61 tags as tagsmod,
62 transaction,
62 transaction,
63 txnutil,
63 txnutil,
64 util,
64 util,
65 vfs as vfsmod,
65 vfs as vfsmod,
66 )
66 )
67 from .utils import (
67 from .utils import (
68 interfaceutil,
68 interfaceutil,
69 procutil,
69 procutil,
70 stringutil,
70 stringutil,
71 )
71 )
72
72
73 release = lockmod.release
73 release = lockmod.release
74 urlerr = util.urlerr
74 urlerr = util.urlerr
75 urlreq = util.urlreq
75 urlreq = util.urlreq
76
76
77 # set of (path, vfs-location) tuples. vfs-location is:
77 # set of (path, vfs-location) tuples. vfs-location is:
78 # - 'plain for vfs relative paths
78 # - 'plain for vfs relative paths
79 # - '' for svfs relative paths
79 # - '' for svfs relative paths
80 _cachedfiles = set()
80 _cachedfiles = set()
81
81
82 class _basefilecache(scmutil.filecache):
82 class _basefilecache(scmutil.filecache):
83 """All filecache usage on repo are done for logic that should be unfiltered
83 """All filecache usage on repo are done for logic that should be unfiltered
84 """
84 """
85 def __get__(self, repo, type=None):
85 def __get__(self, repo, type=None):
86 if repo is None:
86 if repo is None:
87 return self
87 return self
88 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
88 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
89 def __set__(self, repo, value):
89 def __set__(self, repo, value):
90 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
90 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
91 def __delete__(self, repo):
91 def __delete__(self, repo):
92 return super(_basefilecache, self).__delete__(repo.unfiltered())
92 return super(_basefilecache, self).__delete__(repo.unfiltered())
93
93
94 class repofilecache(_basefilecache):
94 class repofilecache(_basefilecache):
95 """filecache for files in .hg but outside of .hg/store"""
95 """filecache for files in .hg but outside of .hg/store"""
96 def __init__(self, *paths):
96 def __init__(self, *paths):
97 super(repofilecache, self).__init__(*paths)
97 super(repofilecache, self).__init__(*paths)
98 for path in paths:
98 for path in paths:
99 _cachedfiles.add((path, 'plain'))
99 _cachedfiles.add((path, 'plain'))
100
100
101 def join(self, obj, fname):
101 def join(self, obj, fname):
102 return obj.vfs.join(fname)
102 return obj.vfs.join(fname)
103
103
104 class storecache(_basefilecache):
104 class storecache(_basefilecache):
105 """filecache for files in the store"""
105 """filecache for files in the store"""
106 def __init__(self, *paths):
106 def __init__(self, *paths):
107 super(storecache, self).__init__(*paths)
107 super(storecache, self).__init__(*paths)
108 for path in paths:
108 for path in paths:
109 _cachedfiles.add((path, ''))
109 _cachedfiles.add((path, ''))
110
110
111 def join(self, obj, fname):
111 def join(self, obj, fname):
112 return obj.sjoin(fname)
112 return obj.sjoin(fname)
113
113
114 def isfilecached(repo, name):
114 def isfilecached(repo, name):
115 """check if a repo has already cached "name" filecache-ed property
115 """check if a repo has already cached "name" filecache-ed property
116
116
117 This returns (cachedobj-or-None, iscached) tuple.
117 This returns (cachedobj-or-None, iscached) tuple.
118 """
118 """
119 cacheentry = repo.unfiltered()._filecache.get(name, None)
119 cacheentry = repo.unfiltered()._filecache.get(name, None)
120 if not cacheentry:
120 if not cacheentry:
121 return None, False
121 return None, False
122 return cacheentry.obj, True
122 return cacheentry.obj, True
123
123
124 class unfilteredpropertycache(util.propertycache):
124 class unfilteredpropertycache(util.propertycache):
125 """propertycache that apply to unfiltered repo only"""
125 """propertycache that apply to unfiltered repo only"""
126
126
127 def __get__(self, repo, type=None):
127 def __get__(self, repo, type=None):
128 unfi = repo.unfiltered()
128 unfi = repo.unfiltered()
129 if unfi is repo:
129 if unfi is repo:
130 return super(unfilteredpropertycache, self).__get__(unfi)
130 return super(unfilteredpropertycache, self).__get__(unfi)
131 return getattr(unfi, self.name)
131 return getattr(unfi, self.name)
132
132
133 class filteredpropertycache(util.propertycache):
133 class filteredpropertycache(util.propertycache):
134 """propertycache that must take filtering in account"""
134 """propertycache that must take filtering in account"""
135
135
136 def cachevalue(self, obj, value):
136 def cachevalue(self, obj, value):
137 object.__setattr__(obj, self.name, value)
137 object.__setattr__(obj, self.name, value)
138
138
139
139
140 def hasunfilteredcache(repo, name):
140 def hasunfilteredcache(repo, name):
141 """check if a repo has an unfilteredpropertycache value for <name>"""
141 """check if a repo has an unfilteredpropertycache value for <name>"""
142 return name in vars(repo.unfiltered())
142 return name in vars(repo.unfiltered())
143
143
144 def unfilteredmethod(orig):
144 def unfilteredmethod(orig):
145 """decorate method that always need to be run on unfiltered version"""
145 """decorate method that always need to be run on unfiltered version"""
146 def wrapper(repo, *args, **kwargs):
146 def wrapper(repo, *args, **kwargs):
147 return orig(repo.unfiltered(), *args, **kwargs)
147 return orig(repo.unfiltered(), *args, **kwargs)
148 return wrapper
148 return wrapper
149
149
150 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
150 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
151 'unbundle'}
151 'unbundle'}
152 legacycaps = moderncaps.union({'changegroupsubset'})
152 legacycaps = moderncaps.union({'changegroupsubset'})
153
153
154 @interfaceutil.implementer(repository.ipeercommandexecutor)
154 @interfaceutil.implementer(repository.ipeercommandexecutor)
155 class localcommandexecutor(object):
155 class localcommandexecutor(object):
156 def __init__(self, peer):
156 def __init__(self, peer):
157 self._peer = peer
157 self._peer = peer
158 self._sent = False
158 self._sent = False
159 self._closed = False
159 self._closed = False
160
160
161 def __enter__(self):
161 def __enter__(self):
162 return self
162 return self
163
163
164 def __exit__(self, exctype, excvalue, exctb):
164 def __exit__(self, exctype, excvalue, exctb):
165 self.close()
165 self.close()
166
166
167 def callcommand(self, command, args):
167 def callcommand(self, command, args):
168 if self._sent:
168 if self._sent:
169 raise error.ProgrammingError('callcommand() cannot be used after '
169 raise error.ProgrammingError('callcommand() cannot be used after '
170 'sendcommands()')
170 'sendcommands()')
171
171
172 if self._closed:
172 if self._closed:
173 raise error.ProgrammingError('callcommand() cannot be used after '
173 raise error.ProgrammingError('callcommand() cannot be used after '
174 'close()')
174 'close()')
175
175
176 # We don't need to support anything fancy. Just call the named
176 # We don't need to support anything fancy. Just call the named
177 # method on the peer and return a resolved future.
177 # method on the peer and return a resolved future.
178 fn = getattr(self._peer, pycompat.sysstr(command))
178 fn = getattr(self._peer, pycompat.sysstr(command))
179
179
180 f = pycompat.futures.Future()
180 f = pycompat.futures.Future()
181
181
182 try:
182 try:
183 result = fn(**pycompat.strkwargs(args))
183 result = fn(**pycompat.strkwargs(args))
184 except Exception:
184 except Exception:
185 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
185 pycompat.future_set_exception_info(f, sys.exc_info()[1:])
186 else:
186 else:
187 f.set_result(result)
187 f.set_result(result)
188
188
189 return f
189 return f
190
190
191 def sendcommands(self):
191 def sendcommands(self):
192 self._sent = True
192 self._sent = True
193
193
194 def close(self):
194 def close(self):
195 self._closed = True
195 self._closed = True
196
196
197 @interfaceutil.implementer(repository.ipeercommands)
197 @interfaceutil.implementer(repository.ipeercommands)
198 class localpeer(repository.peer):
198 class localpeer(repository.peer):
199 '''peer for a local repo; reflects only the most recent API'''
199 '''peer for a local repo; reflects only the most recent API'''
200
200
201 def __init__(self, repo, caps=None):
201 def __init__(self, repo, caps=None):
202 super(localpeer, self).__init__()
202 super(localpeer, self).__init__()
203
203
204 if caps is None:
204 if caps is None:
205 caps = moderncaps.copy()
205 caps = moderncaps.copy()
206 self._repo = repo.filtered('served')
206 self._repo = repo.filtered('served')
207 self.ui = repo.ui
207 self.ui = repo.ui
208 self._caps = repo._restrictcapabilities(caps)
208 self._caps = repo._restrictcapabilities(caps)
209
209
210 # Begin of _basepeer interface.
210 # Begin of _basepeer interface.
211
211
212 def url(self):
212 def url(self):
213 return self._repo.url()
213 return self._repo.url()
214
214
215 def local(self):
215 def local(self):
216 return self._repo
216 return self._repo
217
217
218 def peer(self):
218 def peer(self):
219 return self
219 return self
220
220
221 def canpush(self):
221 def canpush(self):
222 return True
222 return True
223
223
224 def close(self):
224 def close(self):
225 self._repo.close()
225 self._repo.close()
226
226
227 # End of _basepeer interface.
227 # End of _basepeer interface.
228
228
229 # Begin of _basewirecommands interface.
229 # Begin of _basewirecommands interface.
230
230
231 def branchmap(self):
231 def branchmap(self):
232 return self._repo.branchmap()
232 return self._repo.branchmap()
233
233
234 def capabilities(self):
234 def capabilities(self):
235 return self._caps
235 return self._caps
236
236
237 def clonebundles(self):
237 def clonebundles(self):
238 return self._repo.tryread('clonebundles.manifest')
238 return self._repo.tryread('clonebundles.manifest')
239
239
240 def debugwireargs(self, one, two, three=None, four=None, five=None):
240 def debugwireargs(self, one, two, three=None, four=None, five=None):
241 """Used to test argument passing over the wire"""
241 """Used to test argument passing over the wire"""
242 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
242 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
243 pycompat.bytestr(four),
243 pycompat.bytestr(four),
244 pycompat.bytestr(five))
244 pycompat.bytestr(five))
245
245
246 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
246 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
247 **kwargs):
247 **kwargs):
248 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
248 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
249 common=common, bundlecaps=bundlecaps,
249 common=common, bundlecaps=bundlecaps,
250 **kwargs)[1]
250 **kwargs)[1]
251 cb = util.chunkbuffer(chunks)
251 cb = util.chunkbuffer(chunks)
252
252
253 if exchange.bundle2requested(bundlecaps):
253 if exchange.bundle2requested(bundlecaps):
254 # When requesting a bundle2, getbundle returns a stream to make the
254 # When requesting a bundle2, getbundle returns a stream to make the
255 # wire level function happier. We need to build a proper object
255 # wire level function happier. We need to build a proper object
256 # from it in local peer.
256 # from it in local peer.
257 return bundle2.getunbundler(self.ui, cb)
257 return bundle2.getunbundler(self.ui, cb)
258 else:
258 else:
259 return changegroup.getunbundler('01', cb, None)
259 return changegroup.getunbundler('01', cb, None)
260
260
261 def heads(self):
261 def heads(self):
262 return self._repo.heads()
262 return self._repo.heads()
263
263
264 def known(self, nodes):
264 def known(self, nodes):
265 return self._repo.known(nodes)
265 return self._repo.known(nodes)
266
266
267 def listkeys(self, namespace):
267 def listkeys(self, namespace):
268 return self._repo.listkeys(namespace)
268 return self._repo.listkeys(namespace)
269
269
270 def lookup(self, key):
270 def lookup(self, key):
271 return self._repo.lookup(key)
271 return self._repo.lookup(key)
272
272
273 def pushkey(self, namespace, key, old, new):
273 def pushkey(self, namespace, key, old, new):
274 return self._repo.pushkey(namespace, key, old, new)
274 return self._repo.pushkey(namespace, key, old, new)
275
275
276 def stream_out(self):
276 def stream_out(self):
277 raise error.Abort(_('cannot perform stream clone against local '
277 raise error.Abort(_('cannot perform stream clone against local '
278 'peer'))
278 'peer'))
279
279
280 def unbundle(self, bundle, heads, url):
280 def unbundle(self, bundle, heads, url):
281 """apply a bundle on a repo
281 """apply a bundle on a repo
282
282
283 This function handles the repo locking itself."""
283 This function handles the repo locking itself."""
284 try:
284 try:
285 try:
285 try:
286 bundle = exchange.readbundle(self.ui, bundle, None)
286 bundle = exchange.readbundle(self.ui, bundle, None)
287 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
287 ret = exchange.unbundle(self._repo, bundle, heads, 'push', url)
288 if util.safehasattr(ret, 'getchunks'):
288 if util.safehasattr(ret, 'getchunks'):
289 # This is a bundle20 object, turn it into an unbundler.
289 # This is a bundle20 object, turn it into an unbundler.
290 # This little dance should be dropped eventually when the
290 # This little dance should be dropped eventually when the
291 # API is finally improved.
291 # API is finally improved.
292 stream = util.chunkbuffer(ret.getchunks())
292 stream = util.chunkbuffer(ret.getchunks())
293 ret = bundle2.getunbundler(self.ui, stream)
293 ret = bundle2.getunbundler(self.ui, stream)
294 return ret
294 return ret
295 except Exception as exc:
295 except Exception as exc:
296 # If the exception contains output salvaged from a bundle2
296 # If the exception contains output salvaged from a bundle2
297 # reply, we need to make sure it is printed before continuing
297 # reply, we need to make sure it is printed before continuing
298 # to fail. So we build a bundle2 with such output and consume
298 # to fail. So we build a bundle2 with such output and consume
299 # it directly.
299 # it directly.
300 #
300 #
301 # This is not very elegant but allows a "simple" solution for
301 # This is not very elegant but allows a "simple" solution for
302 # issue4594
302 # issue4594
303 output = getattr(exc, '_bundle2salvagedoutput', ())
303 output = getattr(exc, '_bundle2salvagedoutput', ())
304 if output:
304 if output:
305 bundler = bundle2.bundle20(self._repo.ui)
305 bundler = bundle2.bundle20(self._repo.ui)
306 for out in output:
306 for out in output:
307 bundler.addpart(out)
307 bundler.addpart(out)
308 stream = util.chunkbuffer(bundler.getchunks())
308 stream = util.chunkbuffer(bundler.getchunks())
309 b = bundle2.getunbundler(self.ui, stream)
309 b = bundle2.getunbundler(self.ui, stream)
310 bundle2.processbundle(self._repo, b)
310 bundle2.processbundle(self._repo, b)
311 raise
311 raise
312 except error.PushRaced as exc:
312 except error.PushRaced as exc:
313 raise error.ResponseError(_('push failed:'),
313 raise error.ResponseError(_('push failed:'),
314 stringutil.forcebytestr(exc))
314 stringutil.forcebytestr(exc))
315
315
316 # End of _basewirecommands interface.
316 # End of _basewirecommands interface.
317
317
318 # Begin of peer interface.
318 # Begin of peer interface.
319
319
320 def commandexecutor(self):
320 def commandexecutor(self):
321 return localcommandexecutor(self)
321 return localcommandexecutor(self)
322
322
323 # End of peer interface.
323 # End of peer interface.
324
324
325 @interfaceutil.implementer(repository.ipeerlegacycommands)
325 @interfaceutil.implementer(repository.ipeerlegacycommands)
326 class locallegacypeer(localpeer):
326 class locallegacypeer(localpeer):
327 '''peer extension which implements legacy methods too; used for tests with
327 '''peer extension which implements legacy methods too; used for tests with
328 restricted capabilities'''
328 restricted capabilities'''
329
329
330 def __init__(self, repo):
330 def __init__(self, repo):
331 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
331 super(locallegacypeer, self).__init__(repo, caps=legacycaps)
332
332
333 # Begin of baselegacywirecommands interface.
333 # Begin of baselegacywirecommands interface.
334
334
335 def between(self, pairs):
335 def between(self, pairs):
336 return self._repo.between(pairs)
336 return self._repo.between(pairs)
337
337
338 def branches(self, nodes):
338 def branches(self, nodes):
339 return self._repo.branches(nodes)
339 return self._repo.branches(nodes)
340
340
341 def changegroup(self, nodes, source):
341 def changegroup(self, nodes, source):
342 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
342 outgoing = discovery.outgoing(self._repo, missingroots=nodes,
343 missingheads=self._repo.heads())
343 missingheads=self._repo.heads())
344 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
344 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
345
345
346 def changegroupsubset(self, bases, heads, source):
346 def changegroupsubset(self, bases, heads, source):
347 outgoing = discovery.outgoing(self._repo, missingroots=bases,
347 outgoing = discovery.outgoing(self._repo, missingroots=bases,
348 missingheads=heads)
348 missingheads=heads)
349 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
349 return changegroup.makechangegroup(self._repo, outgoing, '01', source)
350
350
351 # End of baselegacywirecommands interface.
351 # End of baselegacywirecommands interface.
352
352
353 # Increment the sub-version when the revlog v2 format changes to lock out old
353 # Increment the sub-version when the revlog v2 format changes to lock out old
354 # clients.
354 # clients.
355 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
355 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
356
356
357 # A repository with the sparserevlog feature will have delta chains that
357 # A repository with the sparserevlog feature will have delta chains that
358 # can spread over a larger span. Sparse reading cuts these large spans into
358 # can spread over a larger span. Sparse reading cuts these large spans into
359 # pieces, so that each piece isn't too big.
359 # pieces, so that each piece isn't too big.
360 # Without the sparserevlog capability, reading from the repository could use
360 # Without the sparserevlog capability, reading from the repository could use
361 # huge amounts of memory, because the whole span would be read at once,
361 # huge amounts of memory, because the whole span would be read at once,
362 # including all the intermediate revisions that aren't pertinent for the chain.
362 # including all the intermediate revisions that aren't pertinent for the chain.
363 # This is why once a repository has enabled sparse-read, it becomes required.
363 # This is why once a repository has enabled sparse-read, it becomes required.
364 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
364 SPARSEREVLOG_REQUIREMENT = 'sparserevlog'
365
365
366 # Functions receiving (ui, features) that extensions can register to impact
366 # Functions receiving (ui, features) that extensions can register to impact
367 # the ability to load repositories with custom requirements. Only
367 # the ability to load repositories with custom requirements. Only
368 # functions defined in loaded extensions are called.
368 # functions defined in loaded extensions are called.
369 #
369 #
370 # The function receives a set of requirement strings that the repository
370 # The function receives a set of requirement strings that the repository
371 # is capable of opening. Functions will typically add elements to the
371 # is capable of opening. Functions will typically add elements to the
372 # set to reflect that the extension knows how to handle that requirements.
372 # set to reflect that the extension knows how to handle that requirements.
373 featuresetupfuncs = set()
373 featuresetupfuncs = set()
374
374
375 @interfaceutil.implementer(repository.completelocalrepository)
375 @interfaceutil.implementer(repository.completelocalrepository)
376 class localrepository(object):
376 class localrepository(object):
377
377
378 # obsolete experimental requirements:
378 # obsolete experimental requirements:
379 # - manifestv2: An experimental new manifest format that allowed
379 # - manifestv2: An experimental new manifest format that allowed
380 # for stem compression of long paths. Experiment ended up not
380 # for stem compression of long paths. Experiment ended up not
381 # being successful (repository sizes went up due to worse delta
381 # being successful (repository sizes went up due to worse delta
382 # chains), and the code was deleted in 4.6.
382 # chains), and the code was deleted in 4.6.
383 supportedformats = {
383 supportedformats = {
384 'revlogv1',
384 'revlogv1',
385 'generaldelta',
385 'generaldelta',
386 'treemanifest',
386 'treemanifest',
387 REVLOGV2_REQUIREMENT,
387 REVLOGV2_REQUIREMENT,
388 SPARSEREVLOG_REQUIREMENT,
388 SPARSEREVLOG_REQUIREMENT,
389 }
389 }
390 _basesupported = supportedformats | {
390 _basesupported = supportedformats | {
391 'store',
391 'store',
392 'fncache',
392 'fncache',
393 'shared',
393 'shared',
394 'relshared',
394 'relshared',
395 'dotencode',
395 'dotencode',
396 'exp-sparse',
396 'exp-sparse',
397 }
397 }
398 openerreqs = {
398 openerreqs = {
399 'revlogv1',
399 'revlogv1',
400 'generaldelta',
400 'generaldelta',
401 'treemanifest',
401 'treemanifest',
402 }
402 }
403
403
404 # list of prefix for file which can be written without 'wlock'
404 # list of prefix for file which can be written without 'wlock'
405 # Extensions should extend this list when needed
405 # Extensions should extend this list when needed
406 _wlockfreeprefix = {
406 _wlockfreeprefix = {
407 # We migh consider requiring 'wlock' for the next
407 # We migh consider requiring 'wlock' for the next
408 # two, but pretty much all the existing code assume
408 # two, but pretty much all the existing code assume
409 # wlock is not needed so we keep them excluded for
409 # wlock is not needed so we keep them excluded for
410 # now.
410 # now.
411 'hgrc',
411 'hgrc',
412 'requires',
412 'requires',
413 # XXX cache is a complicatged business someone
413 # XXX cache is a complicatged business someone
414 # should investigate this in depth at some point
414 # should investigate this in depth at some point
415 'cache/',
415 'cache/',
416 # XXX shouldn't be dirstate covered by the wlock?
416 # XXX shouldn't be dirstate covered by the wlock?
417 'dirstate',
417 'dirstate',
418 # XXX bisect was still a bit too messy at the time
418 # XXX bisect was still a bit too messy at the time
419 # this changeset was introduced. Someone should fix
419 # this changeset was introduced. Someone should fix
420 # the remainig bit and drop this line
420 # the remainig bit and drop this line
421 'bisect.state',
421 'bisect.state',
422 }
422 }
423
423
424 def __init__(self, baseui, path, create=False, intents=None):
424 def __init__(self, baseui, path, create=False, intents=None):
425 self.requirements = set()
425 self.requirements = set()
426 self.filtername = None
426 self.filtername = None
427 # wvfs: rooted at the repository root, used to access the working copy
427 # wvfs: rooted at the repository root, used to access the working copy
428 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
428 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
429 # vfs: rooted at .hg, used to access repo files outside of .hg/store
429 # vfs: rooted at .hg, used to access repo files outside of .hg/store
430 self.vfs = None
430 self.vfs = None
431 # svfs: usually rooted at .hg/store, used to access repository history
431 # svfs: usually rooted at .hg/store, used to access repository history
432 # If this is a shared repository, this vfs may point to another
432 # If this is a shared repository, this vfs may point to another
433 # repository's .hg/store directory.
433 # repository's .hg/store directory.
434 self.svfs = None
434 self.svfs = None
435 self.root = self.wvfs.base
435 self.root = self.wvfs.base
436 self.path = self.wvfs.join(".hg")
436 self.path = self.wvfs.join(".hg")
437 self.origroot = path
437 self.origroot = path
438 # This is only used by context.workingctx.match in order to
438 # This is only used by context.workingctx.match in order to
439 # detect files in subrepos.
439 # detect files in subrepos.
440 self.auditor = pathutil.pathauditor(
440 self.auditor = pathutil.pathauditor(
441 self.root, callback=self._checknested)
441 self.root, callback=self._checknested)
442 # This is only used by context.basectx.match in order to detect
442 # This is only used by context.basectx.match in order to detect
443 # files in subrepos.
443 # files in subrepos.
444 self.nofsauditor = pathutil.pathauditor(
444 self.nofsauditor = pathutil.pathauditor(
445 self.root, callback=self._checknested, realfs=False, cached=True)
445 self.root, callback=self._checknested, realfs=False, cached=True)
446 self.baseui = baseui
446 self.baseui = baseui
447 self.ui = baseui.copy()
447 self.ui = baseui.copy()
448 self.ui.copy = baseui.copy # prevent copying repo configuration
448 self.ui.copy = baseui.copy # prevent copying repo configuration
449 self.vfs = vfsmod.vfs(self.path, cacheaudited=True)
449 self.vfs = vfsmod.vfs(self.path, cacheaudited=True)
450 if (self.ui.configbool('devel', 'all-warnings') or
450 if (self.ui.configbool('devel', 'all-warnings') or
451 self.ui.configbool('devel', 'check-locks')):
451 self.ui.configbool('devel', 'check-locks')):
452 self.vfs.audit = self._getvfsward(self.vfs.audit)
452 self.vfs.audit = self._getvfsward(self.vfs.audit)
453 # A list of callback to shape the phase if no data were found.
453 # A list of callback to shape the phase if no data were found.
454 # Callback are in the form: func(repo, roots) --> processed root.
454 # Callback are in the form: func(repo, roots) --> processed root.
455 # This list it to be filled by extension during repo setup
455 # This list it to be filled by extension during repo setup
456 self._phasedefaults = []
456 self._phasedefaults = []
457 try:
457 try:
458 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
458 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
459 self._loadextensions()
459 self._loadextensions()
460 except IOError:
460 except IOError:
461 pass
461 pass
462
462
463 if featuresetupfuncs:
463 if featuresetupfuncs:
464 self.supported = set(self._basesupported) # use private copy
464 self.supported = set(self._basesupported) # use private copy
465 extmods = set(m.__name__ for n, m
465 extmods = set(m.__name__ for n, m
466 in extensions.extensions(self.ui))
466 in extensions.extensions(self.ui))
467 for setupfunc in featuresetupfuncs:
467 for setupfunc in featuresetupfuncs:
468 if setupfunc.__module__ in extmods:
468 if setupfunc.__module__ in extmods:
469 setupfunc(self.ui, self.supported)
469 setupfunc(self.ui, self.supported)
470 else:
470 else:
471 self.supported = self._basesupported
471 self.supported = self._basesupported
472 color.setup(self.ui)
472 color.setup(self.ui)
473
473
474 # Add compression engines.
474 # Add compression engines.
475 for name in util.compengines:
475 for name in util.compengines:
476 engine = util.compengines[name]
476 engine = util.compengines[name]
477 if engine.revlogheader():
477 if engine.revlogheader():
478 self.supported.add('exp-compression-%s' % name)
478 self.supported.add('exp-compression-%s' % name)
479
479
480 if not self.vfs.isdir():
480 if not self.vfs.isdir():
481 if create:
481 if create:
482 self.requirements = newreporequirements(self)
482 self.requirements = newreporequirements(self)
483
483
484 if not self.wvfs.exists():
484 if not self.wvfs.exists():
485 self.wvfs.makedirs()
485 self.wvfs.makedirs()
486 self.vfs.makedir(notindexed=True)
486 self.vfs.makedir(notindexed=True)
487
487
488 if 'store' in self.requirements:
488 if 'store' in self.requirements:
489 self.vfs.mkdir("store")
489 self.vfs.mkdir("store")
490
490
491 # create an invalid changelog
491 # create an invalid changelog
492 self.vfs.append(
492 self.vfs.append(
493 "00changelog.i",
493 "00changelog.i",
494 '\0\0\0\2' # represents revlogv2
494 '\0\0\0\2' # represents revlogv2
495 ' dummy changelog to prevent using the old repo layout'
495 ' dummy changelog to prevent using the old repo layout'
496 )
496 )
497 else:
497 else:
498 raise error.RepoError(_("repository %s not found") % path)
498 raise error.RepoError(_("repository %s not found") % path)
499 elif create:
499 elif create:
500 raise error.RepoError(_("repository %s already exists") % path)
500 raise error.RepoError(_("repository %s already exists") % path)
501 else:
501 else:
502 try:
502 try:
503 self.requirements = scmutil.readrequires(
503 self.requirements = scmutil.readrequires(
504 self.vfs, self.supported)
504 self.vfs, self.supported)
505 except IOError as inst:
505 except IOError as inst:
506 if inst.errno != errno.ENOENT:
506 if inst.errno != errno.ENOENT:
507 raise
507 raise
508
508
509 cachepath = self.vfs.join('cache')
509 cachepath = self.vfs.join('cache')
510 self.sharedpath = self.path
510 self.sharedpath = self.path
511 try:
511 try:
512 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
512 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
513 if 'relshared' in self.requirements:
513 if 'relshared' in self.requirements:
514 sharedpath = self.vfs.join(sharedpath)
514 sharedpath = self.vfs.join(sharedpath)
515 vfs = vfsmod.vfs(sharedpath, realpath=True)
515 vfs = vfsmod.vfs(sharedpath, realpath=True)
516 cachepath = vfs.join('cache')
516 cachepath = vfs.join('cache')
517 s = vfs.base
517 s = vfs.base
518 if not vfs.exists():
518 if not vfs.exists():
519 raise error.RepoError(
519 raise error.RepoError(
520 _('.hg/sharedpath points to nonexistent directory %s') % s)
520 _('.hg/sharedpath points to nonexistent directory %s') % s)
521 self.sharedpath = s
521 self.sharedpath = s
522 except IOError as inst:
522 except IOError as inst:
523 if inst.errno != errno.ENOENT:
523 if inst.errno != errno.ENOENT:
524 raise
524 raise
525
525
526 if 'exp-sparse' in self.requirements and not sparse.enabled:
526 if 'exp-sparse' in self.requirements and not sparse.enabled:
527 raise error.RepoError(_('repository is using sparse feature but '
527 raise error.RepoError(_('repository is using sparse feature but '
528 'sparse is not enabled; enable the '
528 'sparse is not enabled; enable the '
529 '"sparse" extensions to access'))
529 '"sparse" extensions to access'))
530
530
531 self.store = store.store(
531 self.store = store.store(
532 self.requirements, self.sharedpath,
532 self.requirements, self.sharedpath,
533 lambda base: vfsmod.vfs(base, cacheaudited=True))
533 lambda base: vfsmod.vfs(base, cacheaudited=True))
534 self.spath = self.store.path
534 self.spath = self.store.path
535 self.svfs = self.store.vfs
535 self.svfs = self.store.vfs
536 self.sjoin = self.store.join
536 self.sjoin = self.store.join
537 self.vfs.createmode = self.store.createmode
537 self.vfs.createmode = self.store.createmode
538 self.cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
538 self.cachevfs = vfsmod.vfs(cachepath, cacheaudited=True)
539 self.cachevfs.createmode = self.store.createmode
539 self.cachevfs.createmode = self.store.createmode
540 if (self.ui.configbool('devel', 'all-warnings') or
540 if (self.ui.configbool('devel', 'all-warnings') or
541 self.ui.configbool('devel', 'check-locks')):
541 self.ui.configbool('devel', 'check-locks')):
542 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
542 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
543 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
543 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
544 else: # standard vfs
544 else: # standard vfs
545 self.svfs.audit = self._getsvfsward(self.svfs.audit)
545 self.svfs.audit = self._getsvfsward(self.svfs.audit)
546 self._applyopenerreqs()
546 self._applyopenerreqs()
547 if create:
547 if create:
548 self._writerequirements()
548 self._writerequirements()
549
549
550 self._dirstatevalidatewarned = False
550 self._dirstatevalidatewarned = False
551
551
552 self._branchcaches = {}
552 self._branchcaches = {}
553 self._revbranchcache = None
553 self._revbranchcache = None
554 self._filterpats = {}
554 self._filterpats = {}
555 self._datafilters = {}
555 self._datafilters = {}
556 self._transref = self._lockref = self._wlockref = None
556 self._transref = self._lockref = self._wlockref = None
557
557
558 # A cache for various files under .hg/ that tracks file changes,
558 # A cache for various files under .hg/ that tracks file changes,
559 # (used by the filecache decorator)
559 # (used by the filecache decorator)
560 #
560 #
561 # Maps a property name to its util.filecacheentry
561 # Maps a property name to its util.filecacheentry
562 self._filecache = {}
562 self._filecache = {}
563
563
564 # hold sets of revision to be filtered
564 # hold sets of revision to be filtered
565 # should be cleared when something might have changed the filter value:
565 # should be cleared when something might have changed the filter value:
566 # - new changesets,
566 # - new changesets,
567 # - phase change,
567 # - phase change,
568 # - new obsolescence marker,
568 # - new obsolescence marker,
569 # - working directory parent change,
569 # - working directory parent change,
570 # - bookmark changes
570 # - bookmark changes
571 self.filteredrevcache = {}
571 self.filteredrevcache = {}
572
572
573 # post-dirstate-status hooks
573 # post-dirstate-status hooks
574 self._postdsstatus = []
574 self._postdsstatus = []
575
575
576 # generic mapping between names and nodes
576 # generic mapping between names and nodes
577 self.names = namespaces.namespaces()
577 self.names = namespaces.namespaces()
578
578
579 # Key to signature value.
579 # Key to signature value.
580 self._sparsesignaturecache = {}
580 self._sparsesignaturecache = {}
581 # Signature to cached matcher instance.
581 # Signature to cached matcher instance.
582 self._sparsematchercache = {}
582 self._sparsematchercache = {}
583
583
584 def _getvfsward(self, origfunc):
584 def _getvfsward(self, origfunc):
585 """build a ward for self.vfs"""
585 """build a ward for self.vfs"""
586 rref = weakref.ref(self)
586 rref = weakref.ref(self)
587 def checkvfs(path, mode=None):
587 def checkvfs(path, mode=None):
588 ret = origfunc(path, mode=mode)
588 ret = origfunc(path, mode=mode)
589 repo = rref()
589 repo = rref()
590 if (repo is None
590 if (repo is None
591 or not util.safehasattr(repo, '_wlockref')
591 or not util.safehasattr(repo, '_wlockref')
592 or not util.safehasattr(repo, '_lockref')):
592 or not util.safehasattr(repo, '_lockref')):
593 return
593 return
594 if mode in (None, 'r', 'rb'):
594 if mode in (None, 'r', 'rb'):
595 return
595 return
596 if path.startswith(repo.path):
596 if path.startswith(repo.path):
597 # truncate name relative to the repository (.hg)
597 # truncate name relative to the repository (.hg)
598 path = path[len(repo.path) + 1:]
598 path = path[len(repo.path) + 1:]
599 if path.startswith('cache/'):
599 if path.startswith('cache/'):
600 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
600 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
601 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
601 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
602 if path.startswith('journal.'):
602 if path.startswith('journal.'):
603 # journal is covered by 'lock'
603 # journal is covered by 'lock'
604 if repo._currentlock(repo._lockref) is None:
604 if repo._currentlock(repo._lockref) is None:
605 repo.ui.develwarn('write with no lock: "%s"' % path,
605 repo.ui.develwarn('write with no lock: "%s"' % path,
606 stacklevel=2, config='check-locks')
606 stacklevel=2, config='check-locks')
607 elif repo._currentlock(repo._wlockref) is None:
607 elif repo._currentlock(repo._wlockref) is None:
608 # rest of vfs files are covered by 'wlock'
608 # rest of vfs files are covered by 'wlock'
609 #
609 #
610 # exclude special files
610 # exclude special files
611 for prefix in self._wlockfreeprefix:
611 for prefix in self._wlockfreeprefix:
612 if path.startswith(prefix):
612 if path.startswith(prefix):
613 return
613 return
614 repo.ui.develwarn('write with no wlock: "%s"' % path,
614 repo.ui.develwarn('write with no wlock: "%s"' % path,
615 stacklevel=2, config='check-locks')
615 stacklevel=2, config='check-locks')
616 return ret
616 return ret
617 return checkvfs
617 return checkvfs
618
618
619 def _getsvfsward(self, origfunc):
619 def _getsvfsward(self, origfunc):
620 """build a ward for self.svfs"""
620 """build a ward for self.svfs"""
621 rref = weakref.ref(self)
621 rref = weakref.ref(self)
622 def checksvfs(path, mode=None):
622 def checksvfs(path, mode=None):
623 ret = origfunc(path, mode=mode)
623 ret = origfunc(path, mode=mode)
624 repo = rref()
624 repo = rref()
625 if repo is None or not util.safehasattr(repo, '_lockref'):
625 if repo is None or not util.safehasattr(repo, '_lockref'):
626 return
626 return
627 if mode in (None, 'r', 'rb'):
627 if mode in (None, 'r', 'rb'):
628 return
628 return
629 if path.startswith(repo.sharedpath):
629 if path.startswith(repo.sharedpath):
630 # truncate name relative to the repository (.hg)
630 # truncate name relative to the repository (.hg)
631 path = path[len(repo.sharedpath) + 1:]
631 path = path[len(repo.sharedpath) + 1:]
632 if repo._currentlock(repo._lockref) is None:
632 if repo._currentlock(repo._lockref) is None:
633 repo.ui.develwarn('write with no lock: "%s"' % path,
633 repo.ui.develwarn('write with no lock: "%s"' % path,
634 stacklevel=3)
634 stacklevel=3)
635 return ret
635 return ret
636 return checksvfs
636 return checksvfs
637
637
638 def close(self):
638 def close(self):
639 self._writecaches()
639 self._writecaches()
640
640
641 def _loadextensions(self):
641 def _loadextensions(self):
642 extensions.loadall(self.ui)
642 extensions.loadall(self.ui)
643
643
644 def _writecaches(self):
644 def _writecaches(self):
645 if self._revbranchcache:
645 if self._revbranchcache:
646 self._revbranchcache.write()
646 self._revbranchcache.write()
647
647
648 def _restrictcapabilities(self, caps):
648 def _restrictcapabilities(self, caps):
649 if self.ui.configbool('experimental', 'bundle2-advertise'):
649 if self.ui.configbool('experimental', 'bundle2-advertise'):
650 caps = set(caps)
650 caps = set(caps)
651 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
651 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self,
652 role='client'))
652 role='client'))
653 caps.add('bundle2=' + urlreq.quote(capsblob))
653 caps.add('bundle2=' + urlreq.quote(capsblob))
654 return caps
654 return caps
655
655
656 def _applyopenerreqs(self):
656 def _applyopenerreqs(self):
657 self.svfs.options = dict((r, 1) for r in self.requirements
657 self.svfs.options = dict((r, 1) for r in self.requirements
658 if r in self.openerreqs)
658 if r in self.openerreqs)
659 # experimental config: format.chunkcachesize
659 # experimental config: format.chunkcachesize
660 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
660 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
661 if chunkcachesize is not None:
661 if chunkcachesize is not None:
662 self.svfs.options['chunkcachesize'] = chunkcachesize
662 self.svfs.options['chunkcachesize'] = chunkcachesize
663 # experimental config: format.maxchainlen
663 # experimental config: format.maxchainlen
664 maxchainlen = self.ui.configint('format', 'maxchainlen')
664 maxchainlen = self.ui.configint('format', 'maxchainlen')
665 if maxchainlen is not None:
665 if maxchainlen is not None:
666 self.svfs.options['maxchainlen'] = maxchainlen
666 self.svfs.options['maxchainlen'] = maxchainlen
667 # experimental config: format.manifestcachesize
667 # experimental config: format.manifestcachesize
668 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
668 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
669 if manifestcachesize is not None:
669 if manifestcachesize is not None:
670 self.svfs.options['manifestcachesize'] = manifestcachesize
670 self.svfs.options['manifestcachesize'] = manifestcachesize
671 deltabothparents = self.ui.configbool('storage',
671 deltabothparents = self.ui.configbool('storage',
672 'revlog.optimize-delta-parent-choice')
672 'revlog.optimize-delta-parent-choice')
673 self.svfs.options['deltabothparents'] = deltabothparents
673 self.svfs.options['deltabothparents'] = deltabothparents
674 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
674 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
675 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
675 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
676 if 0 <= chainspan:
676 if 0 <= chainspan:
677 self.svfs.options['maxdeltachainspan'] = chainspan
677 self.svfs.options['maxdeltachainspan'] = chainspan
678 mmapindexthreshold = self.ui.configbytes('experimental',
678 mmapindexthreshold = self.ui.configbytes('experimental',
679 'mmapindexthreshold')
679 'mmapindexthreshold')
680 if mmapindexthreshold is not None:
680 if mmapindexthreshold is not None:
681 self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
681 self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
682 withsparseread = self.ui.configbool('experimental', 'sparse-read')
682 withsparseread = self.ui.configbool('experimental', 'sparse-read')
683 srdensitythres = float(self.ui.config('experimental',
683 srdensitythres = float(self.ui.config('experimental',
684 'sparse-read.density-threshold'))
684 'sparse-read.density-threshold'))
685 srmingapsize = self.ui.configbytes('experimental',
685 srmingapsize = self.ui.configbytes('experimental',
686 'sparse-read.min-gap-size')
686 'sparse-read.min-gap-size')
687 self.svfs.options['with-sparse-read'] = withsparseread
687 self.svfs.options['with-sparse-read'] = withsparseread
688 self.svfs.options['sparse-read-density-threshold'] = srdensitythres
688 self.svfs.options['sparse-read-density-threshold'] = srdensitythres
689 self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
689 self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
690 sparserevlog = SPARSEREVLOG_REQUIREMENT in self.requirements
690 sparserevlog = SPARSEREVLOG_REQUIREMENT in self.requirements
691 self.svfs.options['sparse-revlog'] = sparserevlog
691 self.svfs.options['sparse-revlog'] = sparserevlog
692 if sparserevlog:
692 if sparserevlog:
693 self.svfs.options['generaldelta'] = True
693 self.svfs.options['generaldelta'] = True
694
694
695 for r in self.requirements:
695 for r in self.requirements:
696 if r.startswith('exp-compression-'):
696 if r.startswith('exp-compression-'):
697 self.svfs.options['compengine'] = r[len('exp-compression-'):]
697 self.svfs.options['compengine'] = r[len('exp-compression-'):]
698
698
699 # TODO move "revlogv2" to openerreqs once finalized.
699 # TODO move "revlogv2" to openerreqs once finalized.
700 if REVLOGV2_REQUIREMENT in self.requirements:
700 if REVLOGV2_REQUIREMENT in self.requirements:
701 self.svfs.options['revlogv2'] = True
701 self.svfs.options['revlogv2'] = True
702
702
703 def _writerequirements(self):
703 def _writerequirements(self):
704 scmutil.writerequires(self.vfs, self.requirements)
704 scmutil.writerequires(self.vfs, self.requirements)
705
705
706 def _checknested(self, path):
706 def _checknested(self, path):
707 """Determine if path is a legal nested repository."""
707 """Determine if path is a legal nested repository."""
708 if not path.startswith(self.root):
708 if not path.startswith(self.root):
709 return False
709 return False
710 subpath = path[len(self.root) + 1:]
710 subpath = path[len(self.root) + 1:]
711 normsubpath = util.pconvert(subpath)
711 normsubpath = util.pconvert(subpath)
712
712
713 # XXX: Checking against the current working copy is wrong in
713 # XXX: Checking against the current working copy is wrong in
714 # the sense that it can reject things like
714 # the sense that it can reject things like
715 #
715 #
716 # $ hg cat -r 10 sub/x.txt
716 # $ hg cat -r 10 sub/x.txt
717 #
717 #
718 # if sub/ is no longer a subrepository in the working copy
718 # if sub/ is no longer a subrepository in the working copy
719 # parent revision.
719 # parent revision.
720 #
720 #
721 # However, it can of course also allow things that would have
721 # However, it can of course also allow things that would have
722 # been rejected before, such as the above cat command if sub/
722 # been rejected before, such as the above cat command if sub/
723 # is a subrepository now, but was a normal directory before.
723 # is a subrepository now, but was a normal directory before.
724 # The old path auditor would have rejected by mistake since it
724 # The old path auditor would have rejected by mistake since it
725 # panics when it sees sub/.hg/.
725 # panics when it sees sub/.hg/.
726 #
726 #
727 # All in all, checking against the working copy seems sensible
727 # All in all, checking against the working copy seems sensible
728 # since we want to prevent access to nested repositories on
728 # since we want to prevent access to nested repositories on
729 # the filesystem *now*.
729 # the filesystem *now*.
730 ctx = self[None]
730 ctx = self[None]
731 parts = util.splitpath(subpath)
731 parts = util.splitpath(subpath)
732 while parts:
732 while parts:
733 prefix = '/'.join(parts)
733 prefix = '/'.join(parts)
734 if prefix in ctx.substate:
734 if prefix in ctx.substate:
735 if prefix == normsubpath:
735 if prefix == normsubpath:
736 return True
736 return True
737 else:
737 else:
738 sub = ctx.sub(prefix)
738 sub = ctx.sub(prefix)
739 return sub.checknested(subpath[len(prefix) + 1:])
739 return sub.checknested(subpath[len(prefix) + 1:])
740 else:
740 else:
741 parts.pop()
741 parts.pop()
742 return False
742 return False
743
743
744 def peer(self):
744 def peer(self):
745 return localpeer(self) # not cached to avoid reference cycle
745 return localpeer(self) # not cached to avoid reference cycle
746
746
747 def unfiltered(self):
747 def unfiltered(self):
748 """Return unfiltered version of the repository
748 """Return unfiltered version of the repository
749
749
750 Intended to be overwritten by filtered repo."""
750 Intended to be overwritten by filtered repo."""
751 return self
751 return self
752
752
753 def filtered(self, name, visibilityexceptions=None):
753 def filtered(self, name, visibilityexceptions=None):
754 """Return a filtered version of a repository"""
754 """Return a filtered version of a repository"""
755 cls = repoview.newtype(self.unfiltered().__class__)
755 cls = repoview.newtype(self.unfiltered().__class__)
756 return cls(self, name, visibilityexceptions)
756 return cls(self, name, visibilityexceptions)
757
757
758 @repofilecache('bookmarks', 'bookmarks.current')
758 @repofilecache('bookmarks', 'bookmarks.current')
759 def _bookmarks(self):
759 def _bookmarks(self):
760 return bookmarks.bmstore(self)
760 return bookmarks.bmstore(self)
761
761
762 @property
762 @property
763 def _activebookmark(self):
763 def _activebookmark(self):
764 return self._bookmarks.active
764 return self._bookmarks.active
765
765
766 # _phasesets depend on changelog. what we need is to call
766 # _phasesets depend on changelog. what we need is to call
767 # _phasecache.invalidate() if '00changelog.i' was changed, but it
767 # _phasecache.invalidate() if '00changelog.i' was changed, but it
768 # can't be easily expressed in filecache mechanism.
768 # can't be easily expressed in filecache mechanism.
769 @storecache('phaseroots', '00changelog.i')
769 @storecache('phaseroots', '00changelog.i')
770 def _phasecache(self):
770 def _phasecache(self):
771 return phases.phasecache(self, self._phasedefaults)
771 return phases.phasecache(self, self._phasedefaults)
772
772
773 @storecache('obsstore')
773 @storecache('obsstore')
774 def obsstore(self):
774 def obsstore(self):
775 return obsolete.makestore(self.ui, self)
775 return obsolete.makestore(self.ui, self)
776
776
777 @storecache('00changelog.i')
777 @storecache('00changelog.i')
778 def changelog(self):
778 def changelog(self):
779 return changelog.changelog(self.svfs,
779 return changelog.changelog(self.svfs,
780 trypending=txnutil.mayhavepending(self.root))
780 trypending=txnutil.mayhavepending(self.root))
781
781
782 def _constructmanifest(self):
782 def _constructmanifest(self):
783 # This is a temporary function while we migrate from manifest to
783 # This is a temporary function while we migrate from manifest to
784 # manifestlog. It allows bundlerepo and unionrepo to intercept the
784 # manifestlog. It allows bundlerepo and unionrepo to intercept the
785 # manifest creation.
785 # manifest creation.
786 return manifest.manifestrevlog(self.svfs)
786 return manifest.manifestrevlog(self.svfs)
787
787
788 @storecache('00manifest.i')
788 @storecache('00manifest.i')
789 def manifestlog(self):
789 def manifestlog(self):
790 return manifest.manifestlog(self.svfs, self)
790 return manifest.manifestlog(self.svfs, self)
791
791
792 @repofilecache('dirstate')
792 @repofilecache('dirstate')
793 def dirstate(self):
793 def dirstate(self):
794 return self._makedirstate()
794 return self._makedirstate()
795
795
796 def _makedirstate(self):
796 def _makedirstate(self):
797 """Extension point for wrapping the dirstate per-repo."""
797 """Extension point for wrapping the dirstate per-repo."""
798 sparsematchfn = lambda: sparse.matcher(self)
798 sparsematchfn = lambda: sparse.matcher(self)
799
799
800 return dirstate.dirstate(self.vfs, self.ui, self.root,
800 return dirstate.dirstate(self.vfs, self.ui, self.root,
801 self._dirstatevalidate, sparsematchfn)
801 self._dirstatevalidate, sparsematchfn)
802
802
803 def _dirstatevalidate(self, node):
803 def _dirstatevalidate(self, node):
804 try:
804 try:
805 self.changelog.rev(node)
805 self.changelog.rev(node)
806 return node
806 return node
807 except error.LookupError:
807 except error.LookupError:
808 if not self._dirstatevalidatewarned:
808 if not self._dirstatevalidatewarned:
809 self._dirstatevalidatewarned = True
809 self._dirstatevalidatewarned = True
810 self.ui.warn(_("warning: ignoring unknown"
810 self.ui.warn(_("warning: ignoring unknown"
811 " working parent %s!\n") % short(node))
811 " working parent %s!\n") % short(node))
812 return nullid
812 return nullid
813
813
814 @repofilecache(narrowspec.FILENAME)
814 @repofilecache(narrowspec.FILENAME)
815 def narrowpats(self):
815 def narrowpats(self):
816 """matcher patterns for this repository's narrowspec
816 """matcher patterns for this repository's narrowspec
817
817
818 A tuple of (includes, excludes).
818 A tuple of (includes, excludes).
819 """
819 """
820 source = self
820 source = self
821 if self.shared():
821 if self.shared():
822 from . import hg
822 from . import hg
823 source = hg.sharedreposource(self)
823 source = hg.sharedreposource(self)
824 return narrowspec.load(source)
824 return narrowspec.load(source)
825
825
826 @repofilecache(narrowspec.FILENAME)
826 @repofilecache(narrowspec.FILENAME)
827 def _narrowmatch(self):
827 def _narrowmatch(self):
828 if repository.NARROW_REQUIREMENT not in self.requirements:
828 if repository.NARROW_REQUIREMENT not in self.requirements:
829 return matchmod.always(self.root, '')
829 return matchmod.always(self.root, '')
830 include, exclude = self.narrowpats
830 include, exclude = self.narrowpats
831 return narrowspec.match(self.root, include=include, exclude=exclude)
831 return narrowspec.match(self.root, include=include, exclude=exclude)
832
832
833 # TODO(martinvonz): make this property-like instead?
833 # TODO(martinvonz): make this property-like instead?
834 def narrowmatch(self):
834 def narrowmatch(self):
835 return self._narrowmatch
835 return self._narrowmatch
836
836
837 def setnarrowpats(self, newincludes, newexcludes):
837 def setnarrowpats(self, newincludes, newexcludes):
838 target = self
838 target = self
839 if self.shared():
839 if self.shared():
840 from . import hg
840 from . import hg
841 target = hg.sharedreposource(self)
841 target = hg.sharedreposource(self)
842 narrowspec.save(target, newincludes, newexcludes)
842 narrowspec.save(target, newincludes, newexcludes)
843 self.invalidate(clearfilecache=True)
843 self.invalidate(clearfilecache=True)
844
844
845 def __getitem__(self, changeid):
845 def __getitem__(self, changeid):
846 if changeid is None:
846 if changeid is None:
847 return context.workingctx(self)
847 return context.workingctx(self)
848 if isinstance(changeid, context.basectx):
848 if isinstance(changeid, context.basectx):
849 return changeid
849 return changeid
850 if isinstance(changeid, slice):
850 if isinstance(changeid, slice):
851 # wdirrev isn't contiguous so the slice shouldn't include it
851 # wdirrev isn't contiguous so the slice shouldn't include it
852 return [context.changectx(self, i)
852 return [context.changectx(self, i)
853 for i in pycompat.xrange(*changeid.indices(len(self)))
853 for i in pycompat.xrange(*changeid.indices(len(self)))
854 if i not in self.changelog.filteredrevs]
854 if i not in self.changelog.filteredrevs]
855 try:
855 try:
856 return context.changectx(self, changeid)
856 return context.changectx(self, changeid)
857 except error.WdirUnsupported:
857 except error.WdirUnsupported:
858 return context.workingctx(self)
858 return context.workingctx(self)
859
859
860 def __contains__(self, changeid):
860 def __contains__(self, changeid):
861 """True if the given changeid exists
861 """True if the given changeid exists
862
862
863 error.LookupError is raised if an ambiguous node specified.
863 error.AmbiguousPrefixLookupError is raised if an ambiguous node
864 specified.
864 """
865 """
865 try:
866 try:
866 self[changeid]
867 self[changeid]
867 return True
868 return True
868 except error.RepoLookupError:
869 except error.RepoLookupError:
869 return False
870 return False
870
871
871 def __nonzero__(self):
872 def __nonzero__(self):
872 return True
873 return True
873
874
874 __bool__ = __nonzero__
875 __bool__ = __nonzero__
875
876
876 def __len__(self):
877 def __len__(self):
877 # no need to pay the cost of repoview.changelog
878 # no need to pay the cost of repoview.changelog
878 unfi = self.unfiltered()
879 unfi = self.unfiltered()
879 return len(unfi.changelog)
880 return len(unfi.changelog)
880
881
881 def __iter__(self):
882 def __iter__(self):
882 return iter(self.changelog)
883 return iter(self.changelog)
883
884
884 def revs(self, expr, *args):
885 def revs(self, expr, *args):
885 '''Find revisions matching a revset.
886 '''Find revisions matching a revset.
886
887
887 The revset is specified as a string ``expr`` that may contain
888 The revset is specified as a string ``expr`` that may contain
888 %-formatting to escape certain types. See ``revsetlang.formatspec``.
889 %-formatting to escape certain types. See ``revsetlang.formatspec``.
889
890
890 Revset aliases from the configuration are not expanded. To expand
891 Revset aliases from the configuration are not expanded. To expand
891 user aliases, consider calling ``scmutil.revrange()`` or
892 user aliases, consider calling ``scmutil.revrange()`` or
892 ``repo.anyrevs([expr], user=True)``.
893 ``repo.anyrevs([expr], user=True)``.
893
894
894 Returns a revset.abstractsmartset, which is a list-like interface
895 Returns a revset.abstractsmartset, which is a list-like interface
895 that contains integer revisions.
896 that contains integer revisions.
896 '''
897 '''
897 expr = revsetlang.formatspec(expr, *args)
898 expr = revsetlang.formatspec(expr, *args)
898 m = revset.match(None, expr)
899 m = revset.match(None, expr)
899 return m(self)
900 return m(self)
900
901
901 def set(self, expr, *args):
902 def set(self, expr, *args):
902 '''Find revisions matching a revset and emit changectx instances.
903 '''Find revisions matching a revset and emit changectx instances.
903
904
904 This is a convenience wrapper around ``revs()`` that iterates the
905 This is a convenience wrapper around ``revs()`` that iterates the
905 result and is a generator of changectx instances.
906 result and is a generator of changectx instances.
906
907
907 Revset aliases from the configuration are not expanded. To expand
908 Revset aliases from the configuration are not expanded. To expand
908 user aliases, consider calling ``scmutil.revrange()``.
909 user aliases, consider calling ``scmutil.revrange()``.
909 '''
910 '''
910 for r in self.revs(expr, *args):
911 for r in self.revs(expr, *args):
911 yield self[r]
912 yield self[r]
912
913
913 def anyrevs(self, specs, user=False, localalias=None):
914 def anyrevs(self, specs, user=False, localalias=None):
914 '''Find revisions matching one of the given revsets.
915 '''Find revisions matching one of the given revsets.
915
916
916 Revset aliases from the configuration are not expanded by default. To
917 Revset aliases from the configuration are not expanded by default. To
917 expand user aliases, specify ``user=True``. To provide some local
918 expand user aliases, specify ``user=True``. To provide some local
918 definitions overriding user aliases, set ``localalias`` to
919 definitions overriding user aliases, set ``localalias`` to
919 ``{name: definitionstring}``.
920 ``{name: definitionstring}``.
920 '''
921 '''
921 if user:
922 if user:
922 m = revset.matchany(self.ui, specs,
923 m = revset.matchany(self.ui, specs,
923 lookup=revset.lookupfn(self),
924 lookup=revset.lookupfn(self),
924 localalias=localalias)
925 localalias=localalias)
925 else:
926 else:
926 m = revset.matchany(None, specs, localalias=localalias)
927 m = revset.matchany(None, specs, localalias=localalias)
927 return m(self)
928 return m(self)
928
929
929 def url(self):
930 def url(self):
930 return 'file:' + self.root
931 return 'file:' + self.root
931
932
932 def hook(self, name, throw=False, **args):
933 def hook(self, name, throw=False, **args):
933 """Call a hook, passing this repo instance.
934 """Call a hook, passing this repo instance.
934
935
935 This a convenience method to aid invoking hooks. Extensions likely
936 This a convenience method to aid invoking hooks. Extensions likely
936 won't call this unless they have registered a custom hook or are
937 won't call this unless they have registered a custom hook or are
937 replacing code that is expected to call a hook.
938 replacing code that is expected to call a hook.
938 """
939 """
939 return hook.hook(self.ui, self, name, throw, **args)
940 return hook.hook(self.ui, self, name, throw, **args)
940
941
941 @filteredpropertycache
942 @filteredpropertycache
942 def _tagscache(self):
943 def _tagscache(self):
943 '''Returns a tagscache object that contains various tags related
944 '''Returns a tagscache object that contains various tags related
944 caches.'''
945 caches.'''
945
946
946 # This simplifies its cache management by having one decorated
947 # This simplifies its cache management by having one decorated
947 # function (this one) and the rest simply fetch things from it.
948 # function (this one) and the rest simply fetch things from it.
948 class tagscache(object):
949 class tagscache(object):
949 def __init__(self):
950 def __init__(self):
950 # These two define the set of tags for this repository. tags
951 # These two define the set of tags for this repository. tags
951 # maps tag name to node; tagtypes maps tag name to 'global' or
952 # maps tag name to node; tagtypes maps tag name to 'global' or
952 # 'local'. (Global tags are defined by .hgtags across all
953 # 'local'. (Global tags are defined by .hgtags across all
953 # heads, and local tags are defined in .hg/localtags.)
954 # heads, and local tags are defined in .hg/localtags.)
954 # They constitute the in-memory cache of tags.
955 # They constitute the in-memory cache of tags.
955 self.tags = self.tagtypes = None
956 self.tags = self.tagtypes = None
956
957
957 self.nodetagscache = self.tagslist = None
958 self.nodetagscache = self.tagslist = None
958
959
959 cache = tagscache()
960 cache = tagscache()
960 cache.tags, cache.tagtypes = self._findtags()
961 cache.tags, cache.tagtypes = self._findtags()
961
962
962 return cache
963 return cache
963
964
964 def tags(self):
965 def tags(self):
965 '''return a mapping of tag to node'''
966 '''return a mapping of tag to node'''
966 t = {}
967 t = {}
967 if self.changelog.filteredrevs:
968 if self.changelog.filteredrevs:
968 tags, tt = self._findtags()
969 tags, tt = self._findtags()
969 else:
970 else:
970 tags = self._tagscache.tags
971 tags = self._tagscache.tags
971 for k, v in tags.iteritems():
972 for k, v in tags.iteritems():
972 try:
973 try:
973 # ignore tags to unknown nodes
974 # ignore tags to unknown nodes
974 self.changelog.rev(v)
975 self.changelog.rev(v)
975 t[k] = v
976 t[k] = v
976 except (error.LookupError, ValueError):
977 except (error.LookupError, ValueError):
977 pass
978 pass
978 return t
979 return t
979
980
980 def _findtags(self):
981 def _findtags(self):
981 '''Do the hard work of finding tags. Return a pair of dicts
982 '''Do the hard work of finding tags. Return a pair of dicts
982 (tags, tagtypes) where tags maps tag name to node, and tagtypes
983 (tags, tagtypes) where tags maps tag name to node, and tagtypes
983 maps tag name to a string like \'global\' or \'local\'.
984 maps tag name to a string like \'global\' or \'local\'.
984 Subclasses or extensions are free to add their own tags, but
985 Subclasses or extensions are free to add their own tags, but
985 should be aware that the returned dicts will be retained for the
986 should be aware that the returned dicts will be retained for the
986 duration of the localrepo object.'''
987 duration of the localrepo object.'''
987
988
988 # XXX what tagtype should subclasses/extensions use? Currently
989 # XXX what tagtype should subclasses/extensions use? Currently
989 # mq and bookmarks add tags, but do not set the tagtype at all.
990 # mq and bookmarks add tags, but do not set the tagtype at all.
990 # Should each extension invent its own tag type? Should there
991 # Should each extension invent its own tag type? Should there
991 # be one tagtype for all such "virtual" tags? Or is the status
992 # be one tagtype for all such "virtual" tags? Or is the status
992 # quo fine?
993 # quo fine?
993
994
994
995
995 # map tag name to (node, hist)
996 # map tag name to (node, hist)
996 alltags = tagsmod.findglobaltags(self.ui, self)
997 alltags = tagsmod.findglobaltags(self.ui, self)
997 # map tag name to tag type
998 # map tag name to tag type
998 tagtypes = dict((tag, 'global') for tag in alltags)
999 tagtypes = dict((tag, 'global') for tag in alltags)
999
1000
1000 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1001 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
1001
1002
1002 # Build the return dicts. Have to re-encode tag names because
1003 # Build the return dicts. Have to re-encode tag names because
1003 # the tags module always uses UTF-8 (in order not to lose info
1004 # the tags module always uses UTF-8 (in order not to lose info
1004 # writing to the cache), but the rest of Mercurial wants them in
1005 # writing to the cache), but the rest of Mercurial wants them in
1005 # local encoding.
1006 # local encoding.
1006 tags = {}
1007 tags = {}
1007 for (name, (node, hist)) in alltags.iteritems():
1008 for (name, (node, hist)) in alltags.iteritems():
1008 if node != nullid:
1009 if node != nullid:
1009 tags[encoding.tolocal(name)] = node
1010 tags[encoding.tolocal(name)] = node
1010 tags['tip'] = self.changelog.tip()
1011 tags['tip'] = self.changelog.tip()
1011 tagtypes = dict([(encoding.tolocal(name), value)
1012 tagtypes = dict([(encoding.tolocal(name), value)
1012 for (name, value) in tagtypes.iteritems()])
1013 for (name, value) in tagtypes.iteritems()])
1013 return (tags, tagtypes)
1014 return (tags, tagtypes)
1014
1015
1015 def tagtype(self, tagname):
1016 def tagtype(self, tagname):
1016 '''
1017 '''
1017 return the type of the given tag. result can be:
1018 return the type of the given tag. result can be:
1018
1019
1019 'local' : a local tag
1020 'local' : a local tag
1020 'global' : a global tag
1021 'global' : a global tag
1021 None : tag does not exist
1022 None : tag does not exist
1022 '''
1023 '''
1023
1024
1024 return self._tagscache.tagtypes.get(tagname)
1025 return self._tagscache.tagtypes.get(tagname)
1025
1026
1026 def tagslist(self):
1027 def tagslist(self):
1027 '''return a list of tags ordered by revision'''
1028 '''return a list of tags ordered by revision'''
1028 if not self._tagscache.tagslist:
1029 if not self._tagscache.tagslist:
1029 l = []
1030 l = []
1030 for t, n in self.tags().iteritems():
1031 for t, n in self.tags().iteritems():
1031 l.append((self.changelog.rev(n), t, n))
1032 l.append((self.changelog.rev(n), t, n))
1032 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1033 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
1033
1034
1034 return self._tagscache.tagslist
1035 return self._tagscache.tagslist
1035
1036
1036 def nodetags(self, node):
1037 def nodetags(self, node):
1037 '''return the tags associated with a node'''
1038 '''return the tags associated with a node'''
1038 if not self._tagscache.nodetagscache:
1039 if not self._tagscache.nodetagscache:
1039 nodetagscache = {}
1040 nodetagscache = {}
1040 for t, n in self._tagscache.tags.iteritems():
1041 for t, n in self._tagscache.tags.iteritems():
1041 nodetagscache.setdefault(n, []).append(t)
1042 nodetagscache.setdefault(n, []).append(t)
1042 for tags in nodetagscache.itervalues():
1043 for tags in nodetagscache.itervalues():
1043 tags.sort()
1044 tags.sort()
1044 self._tagscache.nodetagscache = nodetagscache
1045 self._tagscache.nodetagscache = nodetagscache
1045 return self._tagscache.nodetagscache.get(node, [])
1046 return self._tagscache.nodetagscache.get(node, [])
1046
1047
1047 def nodebookmarks(self, node):
1048 def nodebookmarks(self, node):
1048 """return the list of bookmarks pointing to the specified node"""
1049 """return the list of bookmarks pointing to the specified node"""
1049 return self._bookmarks.names(node)
1050 return self._bookmarks.names(node)
1050
1051
1051 def branchmap(self):
1052 def branchmap(self):
1052 '''returns a dictionary {branch: [branchheads]} with branchheads
1053 '''returns a dictionary {branch: [branchheads]} with branchheads
1053 ordered by increasing revision number'''
1054 ordered by increasing revision number'''
1054 branchmap.updatecache(self)
1055 branchmap.updatecache(self)
1055 return self._branchcaches[self.filtername]
1056 return self._branchcaches[self.filtername]
1056
1057
1057 @unfilteredmethod
1058 @unfilteredmethod
1058 def revbranchcache(self):
1059 def revbranchcache(self):
1059 if not self._revbranchcache:
1060 if not self._revbranchcache:
1060 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1061 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
1061 return self._revbranchcache
1062 return self._revbranchcache
1062
1063
1063 def branchtip(self, branch, ignoremissing=False):
1064 def branchtip(self, branch, ignoremissing=False):
1064 '''return the tip node for a given branch
1065 '''return the tip node for a given branch
1065
1066
1066 If ignoremissing is True, then this method will not raise an error.
1067 If ignoremissing is True, then this method will not raise an error.
1067 This is helpful for callers that only expect None for a missing branch
1068 This is helpful for callers that only expect None for a missing branch
1068 (e.g. namespace).
1069 (e.g. namespace).
1069
1070
1070 '''
1071 '''
1071 try:
1072 try:
1072 return self.branchmap().branchtip(branch)
1073 return self.branchmap().branchtip(branch)
1073 except KeyError:
1074 except KeyError:
1074 if not ignoremissing:
1075 if not ignoremissing:
1075 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1076 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
1076 else:
1077 else:
1077 pass
1078 pass
1078
1079
1079 def lookup(self, key):
1080 def lookup(self, key):
1080 return scmutil.revsymbol(self, key).node()
1081 return scmutil.revsymbol(self, key).node()
1081
1082
1082 def lookupbranch(self, key):
1083 def lookupbranch(self, key):
1083 if key in self.branchmap():
1084 if key in self.branchmap():
1084 return key
1085 return key
1085
1086
1086 return scmutil.revsymbol(self, key).branch()
1087 return scmutil.revsymbol(self, key).branch()
1087
1088
1088 def known(self, nodes):
1089 def known(self, nodes):
1089 cl = self.changelog
1090 cl = self.changelog
1090 nm = cl.nodemap
1091 nm = cl.nodemap
1091 filtered = cl.filteredrevs
1092 filtered = cl.filteredrevs
1092 result = []
1093 result = []
1093 for n in nodes:
1094 for n in nodes:
1094 r = nm.get(n)
1095 r = nm.get(n)
1095 resp = not (r is None or r in filtered)
1096 resp = not (r is None or r in filtered)
1096 result.append(resp)
1097 result.append(resp)
1097 return result
1098 return result
1098
1099
1099 def local(self):
1100 def local(self):
1100 return self
1101 return self
1101
1102
1102 def publishing(self):
1103 def publishing(self):
1103 # it's safe (and desirable) to trust the publish flag unconditionally
1104 # it's safe (and desirable) to trust the publish flag unconditionally
1104 # so that we don't finalize changes shared between users via ssh or nfs
1105 # so that we don't finalize changes shared between users via ssh or nfs
1105 return self.ui.configbool('phases', 'publish', untrusted=True)
1106 return self.ui.configbool('phases', 'publish', untrusted=True)
1106
1107
1107 def cancopy(self):
1108 def cancopy(self):
1108 # so statichttprepo's override of local() works
1109 # so statichttprepo's override of local() works
1109 if not self.local():
1110 if not self.local():
1110 return False
1111 return False
1111 if not self.publishing():
1112 if not self.publishing():
1112 return True
1113 return True
1113 # if publishing we can't copy if there is filtered content
1114 # if publishing we can't copy if there is filtered content
1114 return not self.filtered('visible').changelog.filteredrevs
1115 return not self.filtered('visible').changelog.filteredrevs
1115
1116
1116 def shared(self):
1117 def shared(self):
1117 '''the type of shared repository (None if not shared)'''
1118 '''the type of shared repository (None if not shared)'''
1118 if self.sharedpath != self.path:
1119 if self.sharedpath != self.path:
1119 return 'store'
1120 return 'store'
1120 return None
1121 return None
1121
1122
1122 def wjoin(self, f, *insidef):
1123 def wjoin(self, f, *insidef):
1123 return self.vfs.reljoin(self.root, f, *insidef)
1124 return self.vfs.reljoin(self.root, f, *insidef)
1124
1125
1125 def file(self, f):
1126 def file(self, f):
1126 if f[0] == '/':
1127 if f[0] == '/':
1127 f = f[1:]
1128 f = f[1:]
1128 return filelog.filelog(self.svfs, f)
1129 return filelog.filelog(self.svfs, f)
1129
1130
1130 def setparents(self, p1, p2=nullid):
1131 def setparents(self, p1, p2=nullid):
1131 with self.dirstate.parentchange():
1132 with self.dirstate.parentchange():
1132 copies = self.dirstate.setparents(p1, p2)
1133 copies = self.dirstate.setparents(p1, p2)
1133 pctx = self[p1]
1134 pctx = self[p1]
1134 if copies:
1135 if copies:
1135 # Adjust copy records, the dirstate cannot do it, it
1136 # Adjust copy records, the dirstate cannot do it, it
1136 # requires access to parents manifests. Preserve them
1137 # requires access to parents manifests. Preserve them
1137 # only for entries added to first parent.
1138 # only for entries added to first parent.
1138 for f in copies:
1139 for f in copies:
1139 if f not in pctx and copies[f] in pctx:
1140 if f not in pctx and copies[f] in pctx:
1140 self.dirstate.copy(copies[f], f)
1141 self.dirstate.copy(copies[f], f)
1141 if p2 == nullid:
1142 if p2 == nullid:
1142 for f, s in sorted(self.dirstate.copies().items()):
1143 for f, s in sorted(self.dirstate.copies().items()):
1143 if f not in pctx and s not in pctx:
1144 if f not in pctx and s not in pctx:
1144 self.dirstate.copy(None, f)
1145 self.dirstate.copy(None, f)
1145
1146
1146 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1147 def filectx(self, path, changeid=None, fileid=None, changectx=None):
1147 """changeid can be a changeset revision, node, or tag.
1148 """changeid can be a changeset revision, node, or tag.
1148 fileid can be a file revision or node."""
1149 fileid can be a file revision or node."""
1149 return context.filectx(self, path, changeid, fileid,
1150 return context.filectx(self, path, changeid, fileid,
1150 changectx=changectx)
1151 changectx=changectx)
1151
1152
1152 def getcwd(self):
1153 def getcwd(self):
1153 return self.dirstate.getcwd()
1154 return self.dirstate.getcwd()
1154
1155
1155 def pathto(self, f, cwd=None):
1156 def pathto(self, f, cwd=None):
1156 return self.dirstate.pathto(f, cwd)
1157 return self.dirstate.pathto(f, cwd)
1157
1158
1158 def _loadfilter(self, filter):
1159 def _loadfilter(self, filter):
1159 if filter not in self._filterpats:
1160 if filter not in self._filterpats:
1160 l = []
1161 l = []
1161 for pat, cmd in self.ui.configitems(filter):
1162 for pat, cmd in self.ui.configitems(filter):
1162 if cmd == '!':
1163 if cmd == '!':
1163 continue
1164 continue
1164 mf = matchmod.match(self.root, '', [pat])
1165 mf = matchmod.match(self.root, '', [pat])
1165 fn = None
1166 fn = None
1166 params = cmd
1167 params = cmd
1167 for name, filterfn in self._datafilters.iteritems():
1168 for name, filterfn in self._datafilters.iteritems():
1168 if cmd.startswith(name):
1169 if cmd.startswith(name):
1169 fn = filterfn
1170 fn = filterfn
1170 params = cmd[len(name):].lstrip()
1171 params = cmd[len(name):].lstrip()
1171 break
1172 break
1172 if not fn:
1173 if not fn:
1173 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1174 fn = lambda s, c, **kwargs: procutil.filter(s, c)
1174 # Wrap old filters not supporting keyword arguments
1175 # Wrap old filters not supporting keyword arguments
1175 if not pycompat.getargspec(fn)[2]:
1176 if not pycompat.getargspec(fn)[2]:
1176 oldfn = fn
1177 oldfn = fn
1177 fn = lambda s, c, **kwargs: oldfn(s, c)
1178 fn = lambda s, c, **kwargs: oldfn(s, c)
1178 l.append((mf, fn, params))
1179 l.append((mf, fn, params))
1179 self._filterpats[filter] = l
1180 self._filterpats[filter] = l
1180 return self._filterpats[filter]
1181 return self._filterpats[filter]
1181
1182
1182 def _filter(self, filterpats, filename, data):
1183 def _filter(self, filterpats, filename, data):
1183 for mf, fn, cmd in filterpats:
1184 for mf, fn, cmd in filterpats:
1184 if mf(filename):
1185 if mf(filename):
1185 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1186 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1186 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1187 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1187 break
1188 break
1188
1189
1189 return data
1190 return data
1190
1191
1191 @unfilteredpropertycache
1192 @unfilteredpropertycache
1192 def _encodefilterpats(self):
1193 def _encodefilterpats(self):
1193 return self._loadfilter('encode')
1194 return self._loadfilter('encode')
1194
1195
1195 @unfilteredpropertycache
1196 @unfilteredpropertycache
1196 def _decodefilterpats(self):
1197 def _decodefilterpats(self):
1197 return self._loadfilter('decode')
1198 return self._loadfilter('decode')
1198
1199
1199 def adddatafilter(self, name, filter):
1200 def adddatafilter(self, name, filter):
1200 self._datafilters[name] = filter
1201 self._datafilters[name] = filter
1201
1202
1202 def wread(self, filename):
1203 def wread(self, filename):
1203 if self.wvfs.islink(filename):
1204 if self.wvfs.islink(filename):
1204 data = self.wvfs.readlink(filename)
1205 data = self.wvfs.readlink(filename)
1205 else:
1206 else:
1206 data = self.wvfs.read(filename)
1207 data = self.wvfs.read(filename)
1207 return self._filter(self._encodefilterpats, filename, data)
1208 return self._filter(self._encodefilterpats, filename, data)
1208
1209
1209 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1210 def wwrite(self, filename, data, flags, backgroundclose=False, **kwargs):
1210 """write ``data`` into ``filename`` in the working directory
1211 """write ``data`` into ``filename`` in the working directory
1211
1212
1212 This returns length of written (maybe decoded) data.
1213 This returns length of written (maybe decoded) data.
1213 """
1214 """
1214 data = self._filter(self._decodefilterpats, filename, data)
1215 data = self._filter(self._decodefilterpats, filename, data)
1215 if 'l' in flags:
1216 if 'l' in flags:
1216 self.wvfs.symlink(data, filename)
1217 self.wvfs.symlink(data, filename)
1217 else:
1218 else:
1218 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1219 self.wvfs.write(filename, data, backgroundclose=backgroundclose,
1219 **kwargs)
1220 **kwargs)
1220 if 'x' in flags:
1221 if 'x' in flags:
1221 self.wvfs.setflags(filename, False, True)
1222 self.wvfs.setflags(filename, False, True)
1222 else:
1223 else:
1223 self.wvfs.setflags(filename, False, False)
1224 self.wvfs.setflags(filename, False, False)
1224 return len(data)
1225 return len(data)
1225
1226
1226 def wwritedata(self, filename, data):
1227 def wwritedata(self, filename, data):
1227 return self._filter(self._decodefilterpats, filename, data)
1228 return self._filter(self._decodefilterpats, filename, data)
1228
1229
1229 def currenttransaction(self):
1230 def currenttransaction(self):
1230 """return the current transaction or None if non exists"""
1231 """return the current transaction or None if non exists"""
1231 if self._transref:
1232 if self._transref:
1232 tr = self._transref()
1233 tr = self._transref()
1233 else:
1234 else:
1234 tr = None
1235 tr = None
1235
1236
1236 if tr and tr.running():
1237 if tr and tr.running():
1237 return tr
1238 return tr
1238 return None
1239 return None
1239
1240
1240 def transaction(self, desc, report=None):
1241 def transaction(self, desc, report=None):
1241 if (self.ui.configbool('devel', 'all-warnings')
1242 if (self.ui.configbool('devel', 'all-warnings')
1242 or self.ui.configbool('devel', 'check-locks')):
1243 or self.ui.configbool('devel', 'check-locks')):
1243 if self._currentlock(self._lockref) is None:
1244 if self._currentlock(self._lockref) is None:
1244 raise error.ProgrammingError('transaction requires locking')
1245 raise error.ProgrammingError('transaction requires locking')
1245 tr = self.currenttransaction()
1246 tr = self.currenttransaction()
1246 if tr is not None:
1247 if tr is not None:
1247 return tr.nest(name=desc)
1248 return tr.nest(name=desc)
1248
1249
1249 # abort here if the journal already exists
1250 # abort here if the journal already exists
1250 if self.svfs.exists("journal"):
1251 if self.svfs.exists("journal"):
1251 raise error.RepoError(
1252 raise error.RepoError(
1252 _("abandoned transaction found"),
1253 _("abandoned transaction found"),
1253 hint=_("run 'hg recover' to clean up transaction"))
1254 hint=_("run 'hg recover' to clean up transaction"))
1254
1255
1255 idbase = "%.40f#%f" % (random.random(), time.time())
1256 idbase = "%.40f#%f" % (random.random(), time.time())
1256 ha = hex(hashlib.sha1(idbase).digest())
1257 ha = hex(hashlib.sha1(idbase).digest())
1257 txnid = 'TXN:' + ha
1258 txnid = 'TXN:' + ha
1258 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1259 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1259
1260
1260 self._writejournal(desc)
1261 self._writejournal(desc)
1261 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1262 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1262 if report:
1263 if report:
1263 rp = report
1264 rp = report
1264 else:
1265 else:
1265 rp = self.ui.warn
1266 rp = self.ui.warn
1266 vfsmap = {'plain': self.vfs} # root of .hg/
1267 vfsmap = {'plain': self.vfs} # root of .hg/
1267 # we must avoid cyclic reference between repo and transaction.
1268 # we must avoid cyclic reference between repo and transaction.
1268 reporef = weakref.ref(self)
1269 reporef = weakref.ref(self)
1269 # Code to track tag movement
1270 # Code to track tag movement
1270 #
1271 #
1271 # Since tags are all handled as file content, it is actually quite hard
1272 # Since tags are all handled as file content, it is actually quite hard
1272 # to track these movement from a code perspective. So we fallback to a
1273 # to track these movement from a code perspective. So we fallback to a
1273 # tracking at the repository level. One could envision to track changes
1274 # tracking at the repository level. One could envision to track changes
1274 # to the '.hgtags' file through changegroup apply but that fails to
1275 # to the '.hgtags' file through changegroup apply but that fails to
1275 # cope with case where transaction expose new heads without changegroup
1276 # cope with case where transaction expose new heads without changegroup
1276 # being involved (eg: phase movement).
1277 # being involved (eg: phase movement).
1277 #
1278 #
1278 # For now, We gate the feature behind a flag since this likely comes
1279 # For now, We gate the feature behind a flag since this likely comes
1279 # with performance impacts. The current code run more often than needed
1280 # with performance impacts. The current code run more often than needed
1280 # and do not use caches as much as it could. The current focus is on
1281 # and do not use caches as much as it could. The current focus is on
1281 # the behavior of the feature so we disable it by default. The flag
1282 # the behavior of the feature so we disable it by default. The flag
1282 # will be removed when we are happy with the performance impact.
1283 # will be removed when we are happy with the performance impact.
1283 #
1284 #
1284 # Once this feature is no longer experimental move the following
1285 # Once this feature is no longer experimental move the following
1285 # documentation to the appropriate help section:
1286 # documentation to the appropriate help section:
1286 #
1287 #
1287 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1288 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1288 # tags (new or changed or deleted tags). In addition the details of
1289 # tags (new or changed or deleted tags). In addition the details of
1289 # these changes are made available in a file at:
1290 # these changes are made available in a file at:
1290 # ``REPOROOT/.hg/changes/tags.changes``.
1291 # ``REPOROOT/.hg/changes/tags.changes``.
1291 # Make sure you check for HG_TAG_MOVED before reading that file as it
1292 # Make sure you check for HG_TAG_MOVED before reading that file as it
1292 # might exist from a previous transaction even if no tag were touched
1293 # might exist from a previous transaction even if no tag were touched
1293 # in this one. Changes are recorded in a line base format::
1294 # in this one. Changes are recorded in a line base format::
1294 #
1295 #
1295 # <action> <hex-node> <tag-name>\n
1296 # <action> <hex-node> <tag-name>\n
1296 #
1297 #
1297 # Actions are defined as follow:
1298 # Actions are defined as follow:
1298 # "-R": tag is removed,
1299 # "-R": tag is removed,
1299 # "+A": tag is added,
1300 # "+A": tag is added,
1300 # "-M": tag is moved (old value),
1301 # "-M": tag is moved (old value),
1301 # "+M": tag is moved (new value),
1302 # "+M": tag is moved (new value),
1302 tracktags = lambda x: None
1303 tracktags = lambda x: None
1303 # experimental config: experimental.hook-track-tags
1304 # experimental config: experimental.hook-track-tags
1304 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1305 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1305 if desc != 'strip' and shouldtracktags:
1306 if desc != 'strip' and shouldtracktags:
1306 oldheads = self.changelog.headrevs()
1307 oldheads = self.changelog.headrevs()
1307 def tracktags(tr2):
1308 def tracktags(tr2):
1308 repo = reporef()
1309 repo = reporef()
1309 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1310 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1310 newheads = repo.changelog.headrevs()
1311 newheads = repo.changelog.headrevs()
1311 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1312 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1312 # notes: we compare lists here.
1313 # notes: we compare lists here.
1313 # As we do it only once buiding set would not be cheaper
1314 # As we do it only once buiding set would not be cheaper
1314 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1315 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1315 if changes:
1316 if changes:
1316 tr2.hookargs['tag_moved'] = '1'
1317 tr2.hookargs['tag_moved'] = '1'
1317 with repo.vfs('changes/tags.changes', 'w',
1318 with repo.vfs('changes/tags.changes', 'w',
1318 atomictemp=True) as changesfile:
1319 atomictemp=True) as changesfile:
1319 # note: we do not register the file to the transaction
1320 # note: we do not register the file to the transaction
1320 # because we needs it to still exist on the transaction
1321 # because we needs it to still exist on the transaction
1321 # is close (for txnclose hooks)
1322 # is close (for txnclose hooks)
1322 tagsmod.writediff(changesfile, changes)
1323 tagsmod.writediff(changesfile, changes)
1323 def validate(tr2):
1324 def validate(tr2):
1324 """will run pre-closing hooks"""
1325 """will run pre-closing hooks"""
1325 # XXX the transaction API is a bit lacking here so we take a hacky
1326 # XXX the transaction API is a bit lacking here so we take a hacky
1326 # path for now
1327 # path for now
1327 #
1328 #
1328 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1329 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1329 # dict is copied before these run. In addition we needs the data
1330 # dict is copied before these run. In addition we needs the data
1330 # available to in memory hooks too.
1331 # available to in memory hooks too.
1331 #
1332 #
1332 # Moreover, we also need to make sure this runs before txnclose
1333 # Moreover, we also need to make sure this runs before txnclose
1333 # hooks and there is no "pending" mechanism that would execute
1334 # hooks and there is no "pending" mechanism that would execute
1334 # logic only if hooks are about to run.
1335 # logic only if hooks are about to run.
1335 #
1336 #
1336 # Fixing this limitation of the transaction is also needed to track
1337 # Fixing this limitation of the transaction is also needed to track
1337 # other families of changes (bookmarks, phases, obsolescence).
1338 # other families of changes (bookmarks, phases, obsolescence).
1338 #
1339 #
1339 # This will have to be fixed before we remove the experimental
1340 # This will have to be fixed before we remove the experimental
1340 # gating.
1341 # gating.
1341 tracktags(tr2)
1342 tracktags(tr2)
1342 repo = reporef()
1343 repo = reporef()
1343 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1344 if repo.ui.configbool('experimental', 'single-head-per-branch'):
1344 scmutil.enforcesinglehead(repo, tr2, desc)
1345 scmutil.enforcesinglehead(repo, tr2, desc)
1345 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1346 if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
1346 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1347 for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
1347 args = tr.hookargs.copy()
1348 args = tr.hookargs.copy()
1348 args.update(bookmarks.preparehookargs(name, old, new))
1349 args.update(bookmarks.preparehookargs(name, old, new))
1349 repo.hook('pretxnclose-bookmark', throw=True,
1350 repo.hook('pretxnclose-bookmark', throw=True,
1350 txnname=desc,
1351 txnname=desc,
1351 **pycompat.strkwargs(args))
1352 **pycompat.strkwargs(args))
1352 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1353 if hook.hashook(repo.ui, 'pretxnclose-phase'):
1353 cl = repo.unfiltered().changelog
1354 cl = repo.unfiltered().changelog
1354 for rev, (old, new) in tr.changes['phases'].items():
1355 for rev, (old, new) in tr.changes['phases'].items():
1355 args = tr.hookargs.copy()
1356 args = tr.hookargs.copy()
1356 node = hex(cl.node(rev))
1357 node = hex(cl.node(rev))
1357 args.update(phases.preparehookargs(node, old, new))
1358 args.update(phases.preparehookargs(node, old, new))
1358 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1359 repo.hook('pretxnclose-phase', throw=True, txnname=desc,
1359 **pycompat.strkwargs(args))
1360 **pycompat.strkwargs(args))
1360
1361
1361 repo.hook('pretxnclose', throw=True,
1362 repo.hook('pretxnclose', throw=True,
1362 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1363 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1363 def releasefn(tr, success):
1364 def releasefn(tr, success):
1364 repo = reporef()
1365 repo = reporef()
1365 if success:
1366 if success:
1366 # this should be explicitly invoked here, because
1367 # this should be explicitly invoked here, because
1367 # in-memory changes aren't written out at closing
1368 # in-memory changes aren't written out at closing
1368 # transaction, if tr.addfilegenerator (via
1369 # transaction, if tr.addfilegenerator (via
1369 # dirstate.write or so) isn't invoked while
1370 # dirstate.write or so) isn't invoked while
1370 # transaction running
1371 # transaction running
1371 repo.dirstate.write(None)
1372 repo.dirstate.write(None)
1372 else:
1373 else:
1373 # discard all changes (including ones already written
1374 # discard all changes (including ones already written
1374 # out) in this transaction
1375 # out) in this transaction
1375 repo.dirstate.restorebackup(None, 'journal.dirstate')
1376 repo.dirstate.restorebackup(None, 'journal.dirstate')
1376
1377
1377 repo.invalidate(clearfilecache=True)
1378 repo.invalidate(clearfilecache=True)
1378
1379
1379 tr = transaction.transaction(rp, self.svfs, vfsmap,
1380 tr = transaction.transaction(rp, self.svfs, vfsmap,
1380 "journal",
1381 "journal",
1381 "undo",
1382 "undo",
1382 aftertrans(renames),
1383 aftertrans(renames),
1383 self.store.createmode,
1384 self.store.createmode,
1384 validator=validate,
1385 validator=validate,
1385 releasefn=releasefn,
1386 releasefn=releasefn,
1386 checkambigfiles=_cachedfiles,
1387 checkambigfiles=_cachedfiles,
1387 name=desc)
1388 name=desc)
1388 tr.changes['revs'] = pycompat.xrange(0, 0)
1389 tr.changes['revs'] = pycompat.xrange(0, 0)
1389 tr.changes['obsmarkers'] = set()
1390 tr.changes['obsmarkers'] = set()
1390 tr.changes['phases'] = {}
1391 tr.changes['phases'] = {}
1391 tr.changes['bookmarks'] = {}
1392 tr.changes['bookmarks'] = {}
1392
1393
1393 tr.hookargs['txnid'] = txnid
1394 tr.hookargs['txnid'] = txnid
1394 # note: writing the fncache only during finalize mean that the file is
1395 # note: writing the fncache only during finalize mean that the file is
1395 # outdated when running hooks. As fncache is used for streaming clone,
1396 # outdated when running hooks. As fncache is used for streaming clone,
1396 # this is not expected to break anything that happen during the hooks.
1397 # this is not expected to break anything that happen during the hooks.
1397 tr.addfinalize('flush-fncache', self.store.write)
1398 tr.addfinalize('flush-fncache', self.store.write)
1398 def txnclosehook(tr2):
1399 def txnclosehook(tr2):
1399 """To be run if transaction is successful, will schedule a hook run
1400 """To be run if transaction is successful, will schedule a hook run
1400 """
1401 """
1401 # Don't reference tr2 in hook() so we don't hold a reference.
1402 # Don't reference tr2 in hook() so we don't hold a reference.
1402 # This reduces memory consumption when there are multiple
1403 # This reduces memory consumption when there are multiple
1403 # transactions per lock. This can likely go away if issue5045
1404 # transactions per lock. This can likely go away if issue5045
1404 # fixes the function accumulation.
1405 # fixes the function accumulation.
1405 hookargs = tr2.hookargs
1406 hookargs = tr2.hookargs
1406
1407
1407 def hookfunc():
1408 def hookfunc():
1408 repo = reporef()
1409 repo = reporef()
1409 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1410 if hook.hashook(repo.ui, 'txnclose-bookmark'):
1410 bmchanges = sorted(tr.changes['bookmarks'].items())
1411 bmchanges = sorted(tr.changes['bookmarks'].items())
1411 for name, (old, new) in bmchanges:
1412 for name, (old, new) in bmchanges:
1412 args = tr.hookargs.copy()
1413 args = tr.hookargs.copy()
1413 args.update(bookmarks.preparehookargs(name, old, new))
1414 args.update(bookmarks.preparehookargs(name, old, new))
1414 repo.hook('txnclose-bookmark', throw=False,
1415 repo.hook('txnclose-bookmark', throw=False,
1415 txnname=desc, **pycompat.strkwargs(args))
1416 txnname=desc, **pycompat.strkwargs(args))
1416
1417
1417 if hook.hashook(repo.ui, 'txnclose-phase'):
1418 if hook.hashook(repo.ui, 'txnclose-phase'):
1418 cl = repo.unfiltered().changelog
1419 cl = repo.unfiltered().changelog
1419 phasemv = sorted(tr.changes['phases'].items())
1420 phasemv = sorted(tr.changes['phases'].items())
1420 for rev, (old, new) in phasemv:
1421 for rev, (old, new) in phasemv:
1421 args = tr.hookargs.copy()
1422 args = tr.hookargs.copy()
1422 node = hex(cl.node(rev))
1423 node = hex(cl.node(rev))
1423 args.update(phases.preparehookargs(node, old, new))
1424 args.update(phases.preparehookargs(node, old, new))
1424 repo.hook('txnclose-phase', throw=False, txnname=desc,
1425 repo.hook('txnclose-phase', throw=False, txnname=desc,
1425 **pycompat.strkwargs(args))
1426 **pycompat.strkwargs(args))
1426
1427
1427 repo.hook('txnclose', throw=False, txnname=desc,
1428 repo.hook('txnclose', throw=False, txnname=desc,
1428 **pycompat.strkwargs(hookargs))
1429 **pycompat.strkwargs(hookargs))
1429 reporef()._afterlock(hookfunc)
1430 reporef()._afterlock(hookfunc)
1430 tr.addfinalize('txnclose-hook', txnclosehook)
1431 tr.addfinalize('txnclose-hook', txnclosehook)
1431 # Include a leading "-" to make it happen before the transaction summary
1432 # Include a leading "-" to make it happen before the transaction summary
1432 # reports registered via scmutil.registersummarycallback() whose names
1433 # reports registered via scmutil.registersummarycallback() whose names
1433 # are 00-txnreport etc. That way, the caches will be warm when the
1434 # are 00-txnreport etc. That way, the caches will be warm when the
1434 # callbacks run.
1435 # callbacks run.
1435 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1436 tr.addpostclose('-warm-cache', self._buildcacheupdater(tr))
1436 def txnaborthook(tr2):
1437 def txnaborthook(tr2):
1437 """To be run if transaction is aborted
1438 """To be run if transaction is aborted
1438 """
1439 """
1439 reporef().hook('txnabort', throw=False, txnname=desc,
1440 reporef().hook('txnabort', throw=False, txnname=desc,
1440 **pycompat.strkwargs(tr2.hookargs))
1441 **pycompat.strkwargs(tr2.hookargs))
1441 tr.addabort('txnabort-hook', txnaborthook)
1442 tr.addabort('txnabort-hook', txnaborthook)
1442 # avoid eager cache invalidation. in-memory data should be identical
1443 # avoid eager cache invalidation. in-memory data should be identical
1443 # to stored data if transaction has no error.
1444 # to stored data if transaction has no error.
1444 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1445 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1445 self._transref = weakref.ref(tr)
1446 self._transref = weakref.ref(tr)
1446 scmutil.registersummarycallback(self, tr, desc)
1447 scmutil.registersummarycallback(self, tr, desc)
1447 return tr
1448 return tr
1448
1449
1449 def _journalfiles(self):
1450 def _journalfiles(self):
1450 return ((self.svfs, 'journal'),
1451 return ((self.svfs, 'journal'),
1451 (self.vfs, 'journal.dirstate'),
1452 (self.vfs, 'journal.dirstate'),
1452 (self.vfs, 'journal.branch'),
1453 (self.vfs, 'journal.branch'),
1453 (self.vfs, 'journal.desc'),
1454 (self.vfs, 'journal.desc'),
1454 (self.vfs, 'journal.bookmarks'),
1455 (self.vfs, 'journal.bookmarks'),
1455 (self.svfs, 'journal.phaseroots'))
1456 (self.svfs, 'journal.phaseroots'))
1456
1457
1457 def undofiles(self):
1458 def undofiles(self):
1458 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1459 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1459
1460
1460 @unfilteredmethod
1461 @unfilteredmethod
1461 def _writejournal(self, desc):
1462 def _writejournal(self, desc):
1462 self.dirstate.savebackup(None, 'journal.dirstate')
1463 self.dirstate.savebackup(None, 'journal.dirstate')
1463 self.vfs.write("journal.branch",
1464 self.vfs.write("journal.branch",
1464 encoding.fromlocal(self.dirstate.branch()))
1465 encoding.fromlocal(self.dirstate.branch()))
1465 self.vfs.write("journal.desc",
1466 self.vfs.write("journal.desc",
1466 "%d\n%s\n" % (len(self), desc))
1467 "%d\n%s\n" % (len(self), desc))
1467 self.vfs.write("journal.bookmarks",
1468 self.vfs.write("journal.bookmarks",
1468 self.vfs.tryread("bookmarks"))
1469 self.vfs.tryread("bookmarks"))
1469 self.svfs.write("journal.phaseroots",
1470 self.svfs.write("journal.phaseroots",
1470 self.svfs.tryread("phaseroots"))
1471 self.svfs.tryread("phaseroots"))
1471
1472
1472 def recover(self):
1473 def recover(self):
1473 with self.lock():
1474 with self.lock():
1474 if self.svfs.exists("journal"):
1475 if self.svfs.exists("journal"):
1475 self.ui.status(_("rolling back interrupted transaction\n"))
1476 self.ui.status(_("rolling back interrupted transaction\n"))
1476 vfsmap = {'': self.svfs,
1477 vfsmap = {'': self.svfs,
1477 'plain': self.vfs,}
1478 'plain': self.vfs,}
1478 transaction.rollback(self.svfs, vfsmap, "journal",
1479 transaction.rollback(self.svfs, vfsmap, "journal",
1479 self.ui.warn,
1480 self.ui.warn,
1480 checkambigfiles=_cachedfiles)
1481 checkambigfiles=_cachedfiles)
1481 self.invalidate()
1482 self.invalidate()
1482 return True
1483 return True
1483 else:
1484 else:
1484 self.ui.warn(_("no interrupted transaction available\n"))
1485 self.ui.warn(_("no interrupted transaction available\n"))
1485 return False
1486 return False
1486
1487
1487 def rollback(self, dryrun=False, force=False):
1488 def rollback(self, dryrun=False, force=False):
1488 wlock = lock = dsguard = None
1489 wlock = lock = dsguard = None
1489 try:
1490 try:
1490 wlock = self.wlock()
1491 wlock = self.wlock()
1491 lock = self.lock()
1492 lock = self.lock()
1492 if self.svfs.exists("undo"):
1493 if self.svfs.exists("undo"):
1493 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1494 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1494
1495
1495 return self._rollback(dryrun, force, dsguard)
1496 return self._rollback(dryrun, force, dsguard)
1496 else:
1497 else:
1497 self.ui.warn(_("no rollback information available\n"))
1498 self.ui.warn(_("no rollback information available\n"))
1498 return 1
1499 return 1
1499 finally:
1500 finally:
1500 release(dsguard, lock, wlock)
1501 release(dsguard, lock, wlock)
1501
1502
1502 @unfilteredmethod # Until we get smarter cache management
1503 @unfilteredmethod # Until we get smarter cache management
1503 def _rollback(self, dryrun, force, dsguard):
1504 def _rollback(self, dryrun, force, dsguard):
1504 ui = self.ui
1505 ui = self.ui
1505 try:
1506 try:
1506 args = self.vfs.read('undo.desc').splitlines()
1507 args = self.vfs.read('undo.desc').splitlines()
1507 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1508 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1508 if len(args) >= 3:
1509 if len(args) >= 3:
1509 detail = args[2]
1510 detail = args[2]
1510 oldtip = oldlen - 1
1511 oldtip = oldlen - 1
1511
1512
1512 if detail and ui.verbose:
1513 if detail and ui.verbose:
1513 msg = (_('repository tip rolled back to revision %d'
1514 msg = (_('repository tip rolled back to revision %d'
1514 ' (undo %s: %s)\n')
1515 ' (undo %s: %s)\n')
1515 % (oldtip, desc, detail))
1516 % (oldtip, desc, detail))
1516 else:
1517 else:
1517 msg = (_('repository tip rolled back to revision %d'
1518 msg = (_('repository tip rolled back to revision %d'
1518 ' (undo %s)\n')
1519 ' (undo %s)\n')
1519 % (oldtip, desc))
1520 % (oldtip, desc))
1520 except IOError:
1521 except IOError:
1521 msg = _('rolling back unknown transaction\n')
1522 msg = _('rolling back unknown transaction\n')
1522 desc = None
1523 desc = None
1523
1524
1524 if not force and self['.'] != self['tip'] and desc == 'commit':
1525 if not force and self['.'] != self['tip'] and desc == 'commit':
1525 raise error.Abort(
1526 raise error.Abort(
1526 _('rollback of last commit while not checked out '
1527 _('rollback of last commit while not checked out '
1527 'may lose data'), hint=_('use -f to force'))
1528 'may lose data'), hint=_('use -f to force'))
1528
1529
1529 ui.status(msg)
1530 ui.status(msg)
1530 if dryrun:
1531 if dryrun:
1531 return 0
1532 return 0
1532
1533
1533 parents = self.dirstate.parents()
1534 parents = self.dirstate.parents()
1534 self.destroying()
1535 self.destroying()
1535 vfsmap = {'plain': self.vfs, '': self.svfs}
1536 vfsmap = {'plain': self.vfs, '': self.svfs}
1536 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1537 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1537 checkambigfiles=_cachedfiles)
1538 checkambigfiles=_cachedfiles)
1538 if self.vfs.exists('undo.bookmarks'):
1539 if self.vfs.exists('undo.bookmarks'):
1539 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1540 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1540 if self.svfs.exists('undo.phaseroots'):
1541 if self.svfs.exists('undo.phaseroots'):
1541 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1542 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1542 self.invalidate()
1543 self.invalidate()
1543
1544
1544 parentgone = (parents[0] not in self.changelog.nodemap or
1545 parentgone = (parents[0] not in self.changelog.nodemap or
1545 parents[1] not in self.changelog.nodemap)
1546 parents[1] not in self.changelog.nodemap)
1546 if parentgone:
1547 if parentgone:
1547 # prevent dirstateguard from overwriting already restored one
1548 # prevent dirstateguard from overwriting already restored one
1548 dsguard.close()
1549 dsguard.close()
1549
1550
1550 self.dirstate.restorebackup(None, 'undo.dirstate')
1551 self.dirstate.restorebackup(None, 'undo.dirstate')
1551 try:
1552 try:
1552 branch = self.vfs.read('undo.branch')
1553 branch = self.vfs.read('undo.branch')
1553 self.dirstate.setbranch(encoding.tolocal(branch))
1554 self.dirstate.setbranch(encoding.tolocal(branch))
1554 except IOError:
1555 except IOError:
1555 ui.warn(_('named branch could not be reset: '
1556 ui.warn(_('named branch could not be reset: '
1556 'current branch is still \'%s\'\n')
1557 'current branch is still \'%s\'\n')
1557 % self.dirstate.branch())
1558 % self.dirstate.branch())
1558
1559
1559 parents = tuple([p.rev() for p in self[None].parents()])
1560 parents = tuple([p.rev() for p in self[None].parents()])
1560 if len(parents) > 1:
1561 if len(parents) > 1:
1561 ui.status(_('working directory now based on '
1562 ui.status(_('working directory now based on '
1562 'revisions %d and %d\n') % parents)
1563 'revisions %d and %d\n') % parents)
1563 else:
1564 else:
1564 ui.status(_('working directory now based on '
1565 ui.status(_('working directory now based on '
1565 'revision %d\n') % parents)
1566 'revision %d\n') % parents)
1566 mergemod.mergestate.clean(self, self['.'].node())
1567 mergemod.mergestate.clean(self, self['.'].node())
1567
1568
1568 # TODO: if we know which new heads may result from this rollback, pass
1569 # TODO: if we know which new heads may result from this rollback, pass
1569 # them to destroy(), which will prevent the branchhead cache from being
1570 # them to destroy(), which will prevent the branchhead cache from being
1570 # invalidated.
1571 # invalidated.
1571 self.destroyed()
1572 self.destroyed()
1572 return 0
1573 return 0
1573
1574
1574 def _buildcacheupdater(self, newtransaction):
1575 def _buildcacheupdater(self, newtransaction):
1575 """called during transaction to build the callback updating cache
1576 """called during transaction to build the callback updating cache
1576
1577
1577 Lives on the repository to help extension who might want to augment
1578 Lives on the repository to help extension who might want to augment
1578 this logic. For this purpose, the created transaction is passed to the
1579 this logic. For this purpose, the created transaction is passed to the
1579 method.
1580 method.
1580 """
1581 """
1581 # we must avoid cyclic reference between repo and transaction.
1582 # we must avoid cyclic reference between repo and transaction.
1582 reporef = weakref.ref(self)
1583 reporef = weakref.ref(self)
1583 def updater(tr):
1584 def updater(tr):
1584 repo = reporef()
1585 repo = reporef()
1585 repo.updatecaches(tr)
1586 repo.updatecaches(tr)
1586 return updater
1587 return updater
1587
1588
1588 @unfilteredmethod
1589 @unfilteredmethod
1589 def updatecaches(self, tr=None, full=False):
1590 def updatecaches(self, tr=None, full=False):
1590 """warm appropriate caches
1591 """warm appropriate caches
1591
1592
1592 If this function is called after a transaction closed. The transaction
1593 If this function is called after a transaction closed. The transaction
1593 will be available in the 'tr' argument. This can be used to selectively
1594 will be available in the 'tr' argument. This can be used to selectively
1594 update caches relevant to the changes in that transaction.
1595 update caches relevant to the changes in that transaction.
1595
1596
1596 If 'full' is set, make sure all caches the function knows about have
1597 If 'full' is set, make sure all caches the function knows about have
1597 up-to-date data. Even the ones usually loaded more lazily.
1598 up-to-date data. Even the ones usually loaded more lazily.
1598 """
1599 """
1599 if tr is not None and tr.hookargs.get('source') == 'strip':
1600 if tr is not None and tr.hookargs.get('source') == 'strip':
1600 # During strip, many caches are invalid but
1601 # During strip, many caches are invalid but
1601 # later call to `destroyed` will refresh them.
1602 # later call to `destroyed` will refresh them.
1602 return
1603 return
1603
1604
1604 if tr is None or tr.changes['revs']:
1605 if tr is None or tr.changes['revs']:
1605 # updating the unfiltered branchmap should refresh all the others,
1606 # updating the unfiltered branchmap should refresh all the others,
1606 self.ui.debug('updating the branch cache\n')
1607 self.ui.debug('updating the branch cache\n')
1607 branchmap.updatecache(self.filtered('served'))
1608 branchmap.updatecache(self.filtered('served'))
1608
1609
1609 if full:
1610 if full:
1610 rbc = self.revbranchcache()
1611 rbc = self.revbranchcache()
1611 for r in self.changelog:
1612 for r in self.changelog:
1612 rbc.branchinfo(r)
1613 rbc.branchinfo(r)
1613 rbc.write()
1614 rbc.write()
1614
1615
1615 # ensure the working copy parents are in the manifestfulltextcache
1616 # ensure the working copy parents are in the manifestfulltextcache
1616 for ctx in self['.'].parents():
1617 for ctx in self['.'].parents():
1617 ctx.manifest() # accessing the manifest is enough
1618 ctx.manifest() # accessing the manifest is enough
1618
1619
1619 def invalidatecaches(self):
1620 def invalidatecaches(self):
1620
1621
1621 if '_tagscache' in vars(self):
1622 if '_tagscache' in vars(self):
1622 # can't use delattr on proxy
1623 # can't use delattr on proxy
1623 del self.__dict__['_tagscache']
1624 del self.__dict__['_tagscache']
1624
1625
1625 self.unfiltered()._branchcaches.clear()
1626 self.unfiltered()._branchcaches.clear()
1626 self.invalidatevolatilesets()
1627 self.invalidatevolatilesets()
1627 self._sparsesignaturecache.clear()
1628 self._sparsesignaturecache.clear()
1628
1629
1629 def invalidatevolatilesets(self):
1630 def invalidatevolatilesets(self):
1630 self.filteredrevcache.clear()
1631 self.filteredrevcache.clear()
1631 obsolete.clearobscaches(self)
1632 obsolete.clearobscaches(self)
1632
1633
1633 def invalidatedirstate(self):
1634 def invalidatedirstate(self):
1634 '''Invalidates the dirstate, causing the next call to dirstate
1635 '''Invalidates the dirstate, causing the next call to dirstate
1635 to check if it was modified since the last time it was read,
1636 to check if it was modified since the last time it was read,
1636 rereading it if it has.
1637 rereading it if it has.
1637
1638
1638 This is different to dirstate.invalidate() that it doesn't always
1639 This is different to dirstate.invalidate() that it doesn't always
1639 rereads the dirstate. Use dirstate.invalidate() if you want to
1640 rereads the dirstate. Use dirstate.invalidate() if you want to
1640 explicitly read the dirstate again (i.e. restoring it to a previous
1641 explicitly read the dirstate again (i.e. restoring it to a previous
1641 known good state).'''
1642 known good state).'''
1642 if hasunfilteredcache(self, 'dirstate'):
1643 if hasunfilteredcache(self, 'dirstate'):
1643 for k in self.dirstate._filecache:
1644 for k in self.dirstate._filecache:
1644 try:
1645 try:
1645 delattr(self.dirstate, k)
1646 delattr(self.dirstate, k)
1646 except AttributeError:
1647 except AttributeError:
1647 pass
1648 pass
1648 delattr(self.unfiltered(), 'dirstate')
1649 delattr(self.unfiltered(), 'dirstate')
1649
1650
1650 def invalidate(self, clearfilecache=False):
1651 def invalidate(self, clearfilecache=False):
1651 '''Invalidates both store and non-store parts other than dirstate
1652 '''Invalidates both store and non-store parts other than dirstate
1652
1653
1653 If a transaction is running, invalidation of store is omitted,
1654 If a transaction is running, invalidation of store is omitted,
1654 because discarding in-memory changes might cause inconsistency
1655 because discarding in-memory changes might cause inconsistency
1655 (e.g. incomplete fncache causes unintentional failure, but
1656 (e.g. incomplete fncache causes unintentional failure, but
1656 redundant one doesn't).
1657 redundant one doesn't).
1657 '''
1658 '''
1658 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1659 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1659 for k in list(self._filecache.keys()):
1660 for k in list(self._filecache.keys()):
1660 # dirstate is invalidated separately in invalidatedirstate()
1661 # dirstate is invalidated separately in invalidatedirstate()
1661 if k == 'dirstate':
1662 if k == 'dirstate':
1662 continue
1663 continue
1663 if (k == 'changelog' and
1664 if (k == 'changelog' and
1664 self.currenttransaction() and
1665 self.currenttransaction() and
1665 self.changelog._delayed):
1666 self.changelog._delayed):
1666 # The changelog object may store unwritten revisions. We don't
1667 # The changelog object may store unwritten revisions. We don't
1667 # want to lose them.
1668 # want to lose them.
1668 # TODO: Solve the problem instead of working around it.
1669 # TODO: Solve the problem instead of working around it.
1669 continue
1670 continue
1670
1671
1671 if clearfilecache:
1672 if clearfilecache:
1672 del self._filecache[k]
1673 del self._filecache[k]
1673 try:
1674 try:
1674 delattr(unfiltered, k)
1675 delattr(unfiltered, k)
1675 except AttributeError:
1676 except AttributeError:
1676 pass
1677 pass
1677 self.invalidatecaches()
1678 self.invalidatecaches()
1678 if not self.currenttransaction():
1679 if not self.currenttransaction():
1679 # TODO: Changing contents of store outside transaction
1680 # TODO: Changing contents of store outside transaction
1680 # causes inconsistency. We should make in-memory store
1681 # causes inconsistency. We should make in-memory store
1681 # changes detectable, and abort if changed.
1682 # changes detectable, and abort if changed.
1682 self.store.invalidatecaches()
1683 self.store.invalidatecaches()
1683
1684
1684 def invalidateall(self):
1685 def invalidateall(self):
1685 '''Fully invalidates both store and non-store parts, causing the
1686 '''Fully invalidates both store and non-store parts, causing the
1686 subsequent operation to reread any outside changes.'''
1687 subsequent operation to reread any outside changes.'''
1687 # extension should hook this to invalidate its caches
1688 # extension should hook this to invalidate its caches
1688 self.invalidate()
1689 self.invalidate()
1689 self.invalidatedirstate()
1690 self.invalidatedirstate()
1690
1691
1691 @unfilteredmethod
1692 @unfilteredmethod
1692 def _refreshfilecachestats(self, tr):
1693 def _refreshfilecachestats(self, tr):
1693 """Reload stats of cached files so that they are flagged as valid"""
1694 """Reload stats of cached files so that they are flagged as valid"""
1694 for k, ce in self._filecache.items():
1695 for k, ce in self._filecache.items():
1695 k = pycompat.sysstr(k)
1696 k = pycompat.sysstr(k)
1696 if k == r'dirstate' or k not in self.__dict__:
1697 if k == r'dirstate' or k not in self.__dict__:
1697 continue
1698 continue
1698 ce.refresh()
1699 ce.refresh()
1699
1700
1700 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1701 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1701 inheritchecker=None, parentenvvar=None):
1702 inheritchecker=None, parentenvvar=None):
1702 parentlock = None
1703 parentlock = None
1703 # the contents of parentenvvar are used by the underlying lock to
1704 # the contents of parentenvvar are used by the underlying lock to
1704 # determine whether it can be inherited
1705 # determine whether it can be inherited
1705 if parentenvvar is not None:
1706 if parentenvvar is not None:
1706 parentlock = encoding.environ.get(parentenvvar)
1707 parentlock = encoding.environ.get(parentenvvar)
1707
1708
1708 timeout = 0
1709 timeout = 0
1709 warntimeout = 0
1710 warntimeout = 0
1710 if wait:
1711 if wait:
1711 timeout = self.ui.configint("ui", "timeout")
1712 timeout = self.ui.configint("ui", "timeout")
1712 warntimeout = self.ui.configint("ui", "timeout.warn")
1713 warntimeout = self.ui.configint("ui", "timeout.warn")
1713 # internal config: ui.signal-safe-lock
1714 # internal config: ui.signal-safe-lock
1714 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
1715 signalsafe = self.ui.configbool('ui', 'signal-safe-lock')
1715
1716
1716 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
1717 l = lockmod.trylock(self.ui, vfs, lockname, timeout, warntimeout,
1717 releasefn=releasefn,
1718 releasefn=releasefn,
1718 acquirefn=acquirefn, desc=desc,
1719 acquirefn=acquirefn, desc=desc,
1719 inheritchecker=inheritchecker,
1720 inheritchecker=inheritchecker,
1720 parentlock=parentlock,
1721 parentlock=parentlock,
1721 signalsafe=signalsafe)
1722 signalsafe=signalsafe)
1722 return l
1723 return l
1723
1724
1724 def _afterlock(self, callback):
1725 def _afterlock(self, callback):
1725 """add a callback to be run when the repository is fully unlocked
1726 """add a callback to be run when the repository is fully unlocked
1726
1727
1727 The callback will be executed when the outermost lock is released
1728 The callback will be executed when the outermost lock is released
1728 (with wlock being higher level than 'lock')."""
1729 (with wlock being higher level than 'lock')."""
1729 for ref in (self._wlockref, self._lockref):
1730 for ref in (self._wlockref, self._lockref):
1730 l = ref and ref()
1731 l = ref and ref()
1731 if l and l.held:
1732 if l and l.held:
1732 l.postrelease.append(callback)
1733 l.postrelease.append(callback)
1733 break
1734 break
1734 else: # no lock have been found.
1735 else: # no lock have been found.
1735 callback()
1736 callback()
1736
1737
1737 def lock(self, wait=True):
1738 def lock(self, wait=True):
1738 '''Lock the repository store (.hg/store) and return a weak reference
1739 '''Lock the repository store (.hg/store) and return a weak reference
1739 to the lock. Use this before modifying the store (e.g. committing or
1740 to the lock. Use this before modifying the store (e.g. committing or
1740 stripping). If you are opening a transaction, get a lock as well.)
1741 stripping). If you are opening a transaction, get a lock as well.)
1741
1742
1742 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1743 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1743 'wlock' first to avoid a dead-lock hazard.'''
1744 'wlock' first to avoid a dead-lock hazard.'''
1744 l = self._currentlock(self._lockref)
1745 l = self._currentlock(self._lockref)
1745 if l is not None:
1746 if l is not None:
1746 l.lock()
1747 l.lock()
1747 return l
1748 return l
1748
1749
1749 l = self._lock(self.svfs, "lock", wait, None,
1750 l = self._lock(self.svfs, "lock", wait, None,
1750 self.invalidate, _('repository %s') % self.origroot)
1751 self.invalidate, _('repository %s') % self.origroot)
1751 self._lockref = weakref.ref(l)
1752 self._lockref = weakref.ref(l)
1752 return l
1753 return l
1753
1754
1754 def _wlockchecktransaction(self):
1755 def _wlockchecktransaction(self):
1755 if self.currenttransaction() is not None:
1756 if self.currenttransaction() is not None:
1756 raise error.LockInheritanceContractViolation(
1757 raise error.LockInheritanceContractViolation(
1757 'wlock cannot be inherited in the middle of a transaction')
1758 'wlock cannot be inherited in the middle of a transaction')
1758
1759
1759 def wlock(self, wait=True):
1760 def wlock(self, wait=True):
1760 '''Lock the non-store parts of the repository (everything under
1761 '''Lock the non-store parts of the repository (everything under
1761 .hg except .hg/store) and return a weak reference to the lock.
1762 .hg except .hg/store) and return a weak reference to the lock.
1762
1763
1763 Use this before modifying files in .hg.
1764 Use this before modifying files in .hg.
1764
1765
1765 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1766 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1766 'wlock' first to avoid a dead-lock hazard.'''
1767 'wlock' first to avoid a dead-lock hazard.'''
1767 l = self._wlockref and self._wlockref()
1768 l = self._wlockref and self._wlockref()
1768 if l is not None and l.held:
1769 if l is not None and l.held:
1769 l.lock()
1770 l.lock()
1770 return l
1771 return l
1771
1772
1772 # We do not need to check for non-waiting lock acquisition. Such
1773 # We do not need to check for non-waiting lock acquisition. Such
1773 # acquisition would not cause dead-lock as they would just fail.
1774 # acquisition would not cause dead-lock as they would just fail.
1774 if wait and (self.ui.configbool('devel', 'all-warnings')
1775 if wait and (self.ui.configbool('devel', 'all-warnings')
1775 or self.ui.configbool('devel', 'check-locks')):
1776 or self.ui.configbool('devel', 'check-locks')):
1776 if self._currentlock(self._lockref) is not None:
1777 if self._currentlock(self._lockref) is not None:
1777 self.ui.develwarn('"wlock" acquired after "lock"')
1778 self.ui.develwarn('"wlock" acquired after "lock"')
1778
1779
1779 def unlock():
1780 def unlock():
1780 if self.dirstate.pendingparentchange():
1781 if self.dirstate.pendingparentchange():
1781 self.dirstate.invalidate()
1782 self.dirstate.invalidate()
1782 else:
1783 else:
1783 self.dirstate.write(None)
1784 self.dirstate.write(None)
1784
1785
1785 self._filecache['dirstate'].refresh()
1786 self._filecache['dirstate'].refresh()
1786
1787
1787 l = self._lock(self.vfs, "wlock", wait, unlock,
1788 l = self._lock(self.vfs, "wlock", wait, unlock,
1788 self.invalidatedirstate, _('working directory of %s') %
1789 self.invalidatedirstate, _('working directory of %s') %
1789 self.origroot,
1790 self.origroot,
1790 inheritchecker=self._wlockchecktransaction,
1791 inheritchecker=self._wlockchecktransaction,
1791 parentenvvar='HG_WLOCK_LOCKER')
1792 parentenvvar='HG_WLOCK_LOCKER')
1792 self._wlockref = weakref.ref(l)
1793 self._wlockref = weakref.ref(l)
1793 return l
1794 return l
1794
1795
1795 def _currentlock(self, lockref):
1796 def _currentlock(self, lockref):
1796 """Returns the lock if it's held, or None if it's not."""
1797 """Returns the lock if it's held, or None if it's not."""
1797 if lockref is None:
1798 if lockref is None:
1798 return None
1799 return None
1799 l = lockref()
1800 l = lockref()
1800 if l is None or not l.held:
1801 if l is None or not l.held:
1801 return None
1802 return None
1802 return l
1803 return l
1803
1804
1804 def currentwlock(self):
1805 def currentwlock(self):
1805 """Returns the wlock if it's held, or None if it's not."""
1806 """Returns the wlock if it's held, or None if it's not."""
1806 return self._currentlock(self._wlockref)
1807 return self._currentlock(self._wlockref)
1807
1808
1808 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1809 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1809 """
1810 """
1810 commit an individual file as part of a larger transaction
1811 commit an individual file as part of a larger transaction
1811 """
1812 """
1812
1813
1813 fname = fctx.path()
1814 fname = fctx.path()
1814 fparent1 = manifest1.get(fname, nullid)
1815 fparent1 = manifest1.get(fname, nullid)
1815 fparent2 = manifest2.get(fname, nullid)
1816 fparent2 = manifest2.get(fname, nullid)
1816 if isinstance(fctx, context.filectx):
1817 if isinstance(fctx, context.filectx):
1817 node = fctx.filenode()
1818 node = fctx.filenode()
1818 if node in [fparent1, fparent2]:
1819 if node in [fparent1, fparent2]:
1819 self.ui.debug('reusing %s filelog entry\n' % fname)
1820 self.ui.debug('reusing %s filelog entry\n' % fname)
1820 if manifest1.flags(fname) != fctx.flags():
1821 if manifest1.flags(fname) != fctx.flags():
1821 changelist.append(fname)
1822 changelist.append(fname)
1822 return node
1823 return node
1823
1824
1824 flog = self.file(fname)
1825 flog = self.file(fname)
1825 meta = {}
1826 meta = {}
1826 copy = fctx.renamed()
1827 copy = fctx.renamed()
1827 if copy and copy[0] != fname:
1828 if copy and copy[0] != fname:
1828 # Mark the new revision of this file as a copy of another
1829 # Mark the new revision of this file as a copy of another
1829 # file. This copy data will effectively act as a parent
1830 # file. This copy data will effectively act as a parent
1830 # of this new revision. If this is a merge, the first
1831 # of this new revision. If this is a merge, the first
1831 # parent will be the nullid (meaning "look up the copy data")
1832 # parent will be the nullid (meaning "look up the copy data")
1832 # and the second one will be the other parent. For example:
1833 # and the second one will be the other parent. For example:
1833 #
1834 #
1834 # 0 --- 1 --- 3 rev1 changes file foo
1835 # 0 --- 1 --- 3 rev1 changes file foo
1835 # \ / rev2 renames foo to bar and changes it
1836 # \ / rev2 renames foo to bar and changes it
1836 # \- 2 -/ rev3 should have bar with all changes and
1837 # \- 2 -/ rev3 should have bar with all changes and
1837 # should record that bar descends from
1838 # should record that bar descends from
1838 # bar in rev2 and foo in rev1
1839 # bar in rev2 and foo in rev1
1839 #
1840 #
1840 # this allows this merge to succeed:
1841 # this allows this merge to succeed:
1841 #
1842 #
1842 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1843 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1843 # \ / merging rev3 and rev4 should use bar@rev2
1844 # \ / merging rev3 and rev4 should use bar@rev2
1844 # \- 2 --- 4 as the merge base
1845 # \- 2 --- 4 as the merge base
1845 #
1846 #
1846
1847
1847 cfname = copy[0]
1848 cfname = copy[0]
1848 crev = manifest1.get(cfname)
1849 crev = manifest1.get(cfname)
1849 newfparent = fparent2
1850 newfparent = fparent2
1850
1851
1851 if manifest2: # branch merge
1852 if manifest2: # branch merge
1852 if fparent2 == nullid or crev is None: # copied on remote side
1853 if fparent2 == nullid or crev is None: # copied on remote side
1853 if cfname in manifest2:
1854 if cfname in manifest2:
1854 crev = manifest2[cfname]
1855 crev = manifest2[cfname]
1855 newfparent = fparent1
1856 newfparent = fparent1
1856
1857
1857 # Here, we used to search backwards through history to try to find
1858 # Here, we used to search backwards through history to try to find
1858 # where the file copy came from if the source of a copy was not in
1859 # where the file copy came from if the source of a copy was not in
1859 # the parent directory. However, this doesn't actually make sense to
1860 # the parent directory. However, this doesn't actually make sense to
1860 # do (what does a copy from something not in your working copy even
1861 # do (what does a copy from something not in your working copy even
1861 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1862 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1862 # the user that copy information was dropped, so if they didn't
1863 # the user that copy information was dropped, so if they didn't
1863 # expect this outcome it can be fixed, but this is the correct
1864 # expect this outcome it can be fixed, but this is the correct
1864 # behavior in this circumstance.
1865 # behavior in this circumstance.
1865
1866
1866 if crev:
1867 if crev:
1867 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1868 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1868 meta["copy"] = cfname
1869 meta["copy"] = cfname
1869 meta["copyrev"] = hex(crev)
1870 meta["copyrev"] = hex(crev)
1870 fparent1, fparent2 = nullid, newfparent
1871 fparent1, fparent2 = nullid, newfparent
1871 else:
1872 else:
1872 self.ui.warn(_("warning: can't find ancestor for '%s' "
1873 self.ui.warn(_("warning: can't find ancestor for '%s' "
1873 "copied from '%s'!\n") % (fname, cfname))
1874 "copied from '%s'!\n") % (fname, cfname))
1874
1875
1875 elif fparent1 == nullid:
1876 elif fparent1 == nullid:
1876 fparent1, fparent2 = fparent2, nullid
1877 fparent1, fparent2 = fparent2, nullid
1877 elif fparent2 != nullid:
1878 elif fparent2 != nullid:
1878 # is one parent an ancestor of the other?
1879 # is one parent an ancestor of the other?
1879 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1880 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1880 if fparent1 in fparentancestors:
1881 if fparent1 in fparentancestors:
1881 fparent1, fparent2 = fparent2, nullid
1882 fparent1, fparent2 = fparent2, nullid
1882 elif fparent2 in fparentancestors:
1883 elif fparent2 in fparentancestors:
1883 fparent2 = nullid
1884 fparent2 = nullid
1884
1885
1885 # is the file changed?
1886 # is the file changed?
1886 text = fctx.data()
1887 text = fctx.data()
1887 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1888 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1888 changelist.append(fname)
1889 changelist.append(fname)
1889 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1890 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1890 # are just the flags changed during merge?
1891 # are just the flags changed during merge?
1891 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1892 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1892 changelist.append(fname)
1893 changelist.append(fname)
1893
1894
1894 return fparent1
1895 return fparent1
1895
1896
1896 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1897 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1897 """check for commit arguments that aren't committable"""
1898 """check for commit arguments that aren't committable"""
1898 if match.isexact() or match.prefix():
1899 if match.isexact() or match.prefix():
1899 matched = set(status.modified + status.added + status.removed)
1900 matched = set(status.modified + status.added + status.removed)
1900
1901
1901 for f in match.files():
1902 for f in match.files():
1902 f = self.dirstate.normalize(f)
1903 f = self.dirstate.normalize(f)
1903 if f == '.' or f in matched or f in wctx.substate:
1904 if f == '.' or f in matched or f in wctx.substate:
1904 continue
1905 continue
1905 if f in status.deleted:
1906 if f in status.deleted:
1906 fail(f, _('file not found!'))
1907 fail(f, _('file not found!'))
1907 if f in vdirs: # visited directory
1908 if f in vdirs: # visited directory
1908 d = f + '/'
1909 d = f + '/'
1909 for mf in matched:
1910 for mf in matched:
1910 if mf.startswith(d):
1911 if mf.startswith(d):
1911 break
1912 break
1912 else:
1913 else:
1913 fail(f, _("no match under directory!"))
1914 fail(f, _("no match under directory!"))
1914 elif f not in self.dirstate:
1915 elif f not in self.dirstate:
1915 fail(f, _("file not tracked!"))
1916 fail(f, _("file not tracked!"))
1916
1917
1917 @unfilteredmethod
1918 @unfilteredmethod
1918 def commit(self, text="", user=None, date=None, match=None, force=False,
1919 def commit(self, text="", user=None, date=None, match=None, force=False,
1919 editor=False, extra=None):
1920 editor=False, extra=None):
1920 """Add a new revision to current repository.
1921 """Add a new revision to current repository.
1921
1922
1922 Revision information is gathered from the working directory,
1923 Revision information is gathered from the working directory,
1923 match can be used to filter the committed files. If editor is
1924 match can be used to filter the committed files. If editor is
1924 supplied, it is called to get a commit message.
1925 supplied, it is called to get a commit message.
1925 """
1926 """
1926 if extra is None:
1927 if extra is None:
1927 extra = {}
1928 extra = {}
1928
1929
1929 def fail(f, msg):
1930 def fail(f, msg):
1930 raise error.Abort('%s: %s' % (f, msg))
1931 raise error.Abort('%s: %s' % (f, msg))
1931
1932
1932 if not match:
1933 if not match:
1933 match = matchmod.always(self.root, '')
1934 match = matchmod.always(self.root, '')
1934
1935
1935 if not force:
1936 if not force:
1936 vdirs = []
1937 vdirs = []
1937 match.explicitdir = vdirs.append
1938 match.explicitdir = vdirs.append
1938 match.bad = fail
1939 match.bad = fail
1939
1940
1940 wlock = lock = tr = None
1941 wlock = lock = tr = None
1941 try:
1942 try:
1942 wlock = self.wlock()
1943 wlock = self.wlock()
1943 lock = self.lock() # for recent changelog (see issue4368)
1944 lock = self.lock() # for recent changelog (see issue4368)
1944
1945
1945 wctx = self[None]
1946 wctx = self[None]
1946 merge = len(wctx.parents()) > 1
1947 merge = len(wctx.parents()) > 1
1947
1948
1948 if not force and merge and not match.always():
1949 if not force and merge and not match.always():
1949 raise error.Abort(_('cannot partially commit a merge '
1950 raise error.Abort(_('cannot partially commit a merge '
1950 '(do not specify files or patterns)'))
1951 '(do not specify files or patterns)'))
1951
1952
1952 status = self.status(match=match, clean=force)
1953 status = self.status(match=match, clean=force)
1953 if force:
1954 if force:
1954 status.modified.extend(status.clean) # mq may commit clean files
1955 status.modified.extend(status.clean) # mq may commit clean files
1955
1956
1956 # check subrepos
1957 # check subrepos
1957 subs, commitsubs, newstate = subrepoutil.precommit(
1958 subs, commitsubs, newstate = subrepoutil.precommit(
1958 self.ui, wctx, status, match, force=force)
1959 self.ui, wctx, status, match, force=force)
1959
1960
1960 # make sure all explicit patterns are matched
1961 # make sure all explicit patterns are matched
1961 if not force:
1962 if not force:
1962 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1963 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1963
1964
1964 cctx = context.workingcommitctx(self, status,
1965 cctx = context.workingcommitctx(self, status,
1965 text, user, date, extra)
1966 text, user, date, extra)
1966
1967
1967 # internal config: ui.allowemptycommit
1968 # internal config: ui.allowemptycommit
1968 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1969 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1969 or extra.get('close') or merge or cctx.files()
1970 or extra.get('close') or merge or cctx.files()
1970 or self.ui.configbool('ui', 'allowemptycommit'))
1971 or self.ui.configbool('ui', 'allowemptycommit'))
1971 if not allowemptycommit:
1972 if not allowemptycommit:
1972 return None
1973 return None
1973
1974
1974 if merge and cctx.deleted():
1975 if merge and cctx.deleted():
1975 raise error.Abort(_("cannot commit merge with missing files"))
1976 raise error.Abort(_("cannot commit merge with missing files"))
1976
1977
1977 ms = mergemod.mergestate.read(self)
1978 ms = mergemod.mergestate.read(self)
1978 mergeutil.checkunresolved(ms)
1979 mergeutil.checkunresolved(ms)
1979
1980
1980 if editor:
1981 if editor:
1981 cctx._text = editor(self, cctx, subs)
1982 cctx._text = editor(self, cctx, subs)
1982 edited = (text != cctx._text)
1983 edited = (text != cctx._text)
1983
1984
1984 # Save commit message in case this transaction gets rolled back
1985 # Save commit message in case this transaction gets rolled back
1985 # (e.g. by a pretxncommit hook). Leave the content alone on
1986 # (e.g. by a pretxncommit hook). Leave the content alone on
1986 # the assumption that the user will use the same editor again.
1987 # the assumption that the user will use the same editor again.
1987 msgfn = self.savecommitmessage(cctx._text)
1988 msgfn = self.savecommitmessage(cctx._text)
1988
1989
1989 # commit subs and write new state
1990 # commit subs and write new state
1990 if subs:
1991 if subs:
1991 for s in sorted(commitsubs):
1992 for s in sorted(commitsubs):
1992 sub = wctx.sub(s)
1993 sub = wctx.sub(s)
1993 self.ui.status(_('committing subrepository %s\n') %
1994 self.ui.status(_('committing subrepository %s\n') %
1994 subrepoutil.subrelpath(sub))
1995 subrepoutil.subrelpath(sub))
1995 sr = sub.commit(cctx._text, user, date)
1996 sr = sub.commit(cctx._text, user, date)
1996 newstate[s] = (newstate[s][0], sr)
1997 newstate[s] = (newstate[s][0], sr)
1997 subrepoutil.writestate(self, newstate)
1998 subrepoutil.writestate(self, newstate)
1998
1999
1999 p1, p2 = self.dirstate.parents()
2000 p1, p2 = self.dirstate.parents()
2000 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2001 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
2001 try:
2002 try:
2002 self.hook("precommit", throw=True, parent1=hookp1,
2003 self.hook("precommit", throw=True, parent1=hookp1,
2003 parent2=hookp2)
2004 parent2=hookp2)
2004 tr = self.transaction('commit')
2005 tr = self.transaction('commit')
2005 ret = self.commitctx(cctx, True)
2006 ret = self.commitctx(cctx, True)
2006 except: # re-raises
2007 except: # re-raises
2007 if edited:
2008 if edited:
2008 self.ui.write(
2009 self.ui.write(
2009 _('note: commit message saved in %s\n') % msgfn)
2010 _('note: commit message saved in %s\n') % msgfn)
2010 raise
2011 raise
2011 # update bookmarks, dirstate and mergestate
2012 # update bookmarks, dirstate and mergestate
2012 bookmarks.update(self, [p1, p2], ret)
2013 bookmarks.update(self, [p1, p2], ret)
2013 cctx.markcommitted(ret)
2014 cctx.markcommitted(ret)
2014 ms.reset()
2015 ms.reset()
2015 tr.close()
2016 tr.close()
2016
2017
2017 finally:
2018 finally:
2018 lockmod.release(tr, lock, wlock)
2019 lockmod.release(tr, lock, wlock)
2019
2020
2020 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2021 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
2021 # hack for command that use a temporary commit (eg: histedit)
2022 # hack for command that use a temporary commit (eg: histedit)
2022 # temporary commit got stripped before hook release
2023 # temporary commit got stripped before hook release
2023 if self.changelog.hasnode(ret):
2024 if self.changelog.hasnode(ret):
2024 self.hook("commit", node=node, parent1=parent1,
2025 self.hook("commit", node=node, parent1=parent1,
2025 parent2=parent2)
2026 parent2=parent2)
2026 self._afterlock(commithook)
2027 self._afterlock(commithook)
2027 return ret
2028 return ret
2028
2029
2029 @unfilteredmethod
2030 @unfilteredmethod
2030 def commitctx(self, ctx, error=False):
2031 def commitctx(self, ctx, error=False):
2031 """Add a new revision to current repository.
2032 """Add a new revision to current repository.
2032 Revision information is passed via the context argument.
2033 Revision information is passed via the context argument.
2033 """
2034 """
2034
2035
2035 tr = None
2036 tr = None
2036 p1, p2 = ctx.p1(), ctx.p2()
2037 p1, p2 = ctx.p1(), ctx.p2()
2037 user = ctx.user()
2038 user = ctx.user()
2038
2039
2039 lock = self.lock()
2040 lock = self.lock()
2040 try:
2041 try:
2041 tr = self.transaction("commit")
2042 tr = self.transaction("commit")
2042 trp = weakref.proxy(tr)
2043 trp = weakref.proxy(tr)
2043
2044
2044 if ctx.manifestnode():
2045 if ctx.manifestnode():
2045 # reuse an existing manifest revision
2046 # reuse an existing manifest revision
2046 mn = ctx.manifestnode()
2047 mn = ctx.manifestnode()
2047 files = ctx.files()
2048 files = ctx.files()
2048 elif ctx.files():
2049 elif ctx.files():
2049 m1ctx = p1.manifestctx()
2050 m1ctx = p1.manifestctx()
2050 m2ctx = p2.manifestctx()
2051 m2ctx = p2.manifestctx()
2051 mctx = m1ctx.copy()
2052 mctx = m1ctx.copy()
2052
2053
2053 m = mctx.read()
2054 m = mctx.read()
2054 m1 = m1ctx.read()
2055 m1 = m1ctx.read()
2055 m2 = m2ctx.read()
2056 m2 = m2ctx.read()
2056
2057
2057 # check in files
2058 # check in files
2058 added = []
2059 added = []
2059 changed = []
2060 changed = []
2060 removed = list(ctx.removed())
2061 removed = list(ctx.removed())
2061 linkrev = len(self)
2062 linkrev = len(self)
2062 self.ui.note(_("committing files:\n"))
2063 self.ui.note(_("committing files:\n"))
2063 for f in sorted(ctx.modified() + ctx.added()):
2064 for f in sorted(ctx.modified() + ctx.added()):
2064 self.ui.note(f + "\n")
2065 self.ui.note(f + "\n")
2065 try:
2066 try:
2066 fctx = ctx[f]
2067 fctx = ctx[f]
2067 if fctx is None:
2068 if fctx is None:
2068 removed.append(f)
2069 removed.append(f)
2069 else:
2070 else:
2070 added.append(f)
2071 added.append(f)
2071 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2072 m[f] = self._filecommit(fctx, m1, m2, linkrev,
2072 trp, changed)
2073 trp, changed)
2073 m.setflag(f, fctx.flags())
2074 m.setflag(f, fctx.flags())
2074 except OSError as inst:
2075 except OSError as inst:
2075 self.ui.warn(_("trouble committing %s!\n") % f)
2076 self.ui.warn(_("trouble committing %s!\n") % f)
2076 raise
2077 raise
2077 except IOError as inst:
2078 except IOError as inst:
2078 errcode = getattr(inst, 'errno', errno.ENOENT)
2079 errcode = getattr(inst, 'errno', errno.ENOENT)
2079 if error or errcode and errcode != errno.ENOENT:
2080 if error or errcode and errcode != errno.ENOENT:
2080 self.ui.warn(_("trouble committing %s!\n") % f)
2081 self.ui.warn(_("trouble committing %s!\n") % f)
2081 raise
2082 raise
2082
2083
2083 # update manifest
2084 # update manifest
2084 self.ui.note(_("committing manifest\n"))
2085 self.ui.note(_("committing manifest\n"))
2085 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2086 removed = [f for f in sorted(removed) if f in m1 or f in m2]
2086 drop = [f for f in removed if f in m]
2087 drop = [f for f in removed if f in m]
2087 for f in drop:
2088 for f in drop:
2088 del m[f]
2089 del m[f]
2089 mn = mctx.write(trp, linkrev,
2090 mn = mctx.write(trp, linkrev,
2090 p1.manifestnode(), p2.manifestnode(),
2091 p1.manifestnode(), p2.manifestnode(),
2091 added, drop)
2092 added, drop)
2092 files = changed + removed
2093 files = changed + removed
2093 else:
2094 else:
2094 mn = p1.manifestnode()
2095 mn = p1.manifestnode()
2095 files = []
2096 files = []
2096
2097
2097 # update changelog
2098 # update changelog
2098 self.ui.note(_("committing changelog\n"))
2099 self.ui.note(_("committing changelog\n"))
2099 self.changelog.delayupdate(tr)
2100 self.changelog.delayupdate(tr)
2100 n = self.changelog.add(mn, files, ctx.description(),
2101 n = self.changelog.add(mn, files, ctx.description(),
2101 trp, p1.node(), p2.node(),
2102 trp, p1.node(), p2.node(),
2102 user, ctx.date(), ctx.extra().copy())
2103 user, ctx.date(), ctx.extra().copy())
2103 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2104 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
2104 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2105 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
2105 parent2=xp2)
2106 parent2=xp2)
2106 # set the new commit is proper phase
2107 # set the new commit is proper phase
2107 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2108 targetphase = subrepoutil.newcommitphase(self.ui, ctx)
2108 if targetphase:
2109 if targetphase:
2109 # retract boundary do not alter parent changeset.
2110 # retract boundary do not alter parent changeset.
2110 # if a parent have higher the resulting phase will
2111 # if a parent have higher the resulting phase will
2111 # be compliant anyway
2112 # be compliant anyway
2112 #
2113 #
2113 # if minimal phase was 0 we don't need to retract anything
2114 # if minimal phase was 0 we don't need to retract anything
2114 phases.registernew(self, tr, targetphase, [n])
2115 phases.registernew(self, tr, targetphase, [n])
2115 tr.close()
2116 tr.close()
2116 return n
2117 return n
2117 finally:
2118 finally:
2118 if tr:
2119 if tr:
2119 tr.release()
2120 tr.release()
2120 lock.release()
2121 lock.release()
2121
2122
2122 @unfilteredmethod
2123 @unfilteredmethod
2123 def destroying(self):
2124 def destroying(self):
2124 '''Inform the repository that nodes are about to be destroyed.
2125 '''Inform the repository that nodes are about to be destroyed.
2125 Intended for use by strip and rollback, so there's a common
2126 Intended for use by strip and rollback, so there's a common
2126 place for anything that has to be done before destroying history.
2127 place for anything that has to be done before destroying history.
2127
2128
2128 This is mostly useful for saving state that is in memory and waiting
2129 This is mostly useful for saving state that is in memory and waiting
2129 to be flushed when the current lock is released. Because a call to
2130 to be flushed when the current lock is released. Because a call to
2130 destroyed is imminent, the repo will be invalidated causing those
2131 destroyed is imminent, the repo will be invalidated causing those
2131 changes to stay in memory (waiting for the next unlock), or vanish
2132 changes to stay in memory (waiting for the next unlock), or vanish
2132 completely.
2133 completely.
2133 '''
2134 '''
2134 # When using the same lock to commit and strip, the phasecache is left
2135 # When using the same lock to commit and strip, the phasecache is left
2135 # dirty after committing. Then when we strip, the repo is invalidated,
2136 # dirty after committing. Then when we strip, the repo is invalidated,
2136 # causing those changes to disappear.
2137 # causing those changes to disappear.
2137 if '_phasecache' in vars(self):
2138 if '_phasecache' in vars(self):
2138 self._phasecache.write()
2139 self._phasecache.write()
2139
2140
2140 @unfilteredmethod
2141 @unfilteredmethod
2141 def destroyed(self):
2142 def destroyed(self):
2142 '''Inform the repository that nodes have been destroyed.
2143 '''Inform the repository that nodes have been destroyed.
2143 Intended for use by strip and rollback, so there's a common
2144 Intended for use by strip and rollback, so there's a common
2144 place for anything that has to be done after destroying history.
2145 place for anything that has to be done after destroying history.
2145 '''
2146 '''
2146 # When one tries to:
2147 # When one tries to:
2147 # 1) destroy nodes thus calling this method (e.g. strip)
2148 # 1) destroy nodes thus calling this method (e.g. strip)
2148 # 2) use phasecache somewhere (e.g. commit)
2149 # 2) use phasecache somewhere (e.g. commit)
2149 #
2150 #
2150 # then 2) will fail because the phasecache contains nodes that were
2151 # then 2) will fail because the phasecache contains nodes that were
2151 # removed. We can either remove phasecache from the filecache,
2152 # removed. We can either remove phasecache from the filecache,
2152 # causing it to reload next time it is accessed, or simply filter
2153 # causing it to reload next time it is accessed, or simply filter
2153 # the removed nodes now and write the updated cache.
2154 # the removed nodes now and write the updated cache.
2154 self._phasecache.filterunknown(self)
2155 self._phasecache.filterunknown(self)
2155 self._phasecache.write()
2156 self._phasecache.write()
2156
2157
2157 # refresh all repository caches
2158 # refresh all repository caches
2158 self.updatecaches()
2159 self.updatecaches()
2159
2160
2160 # Ensure the persistent tag cache is updated. Doing it now
2161 # Ensure the persistent tag cache is updated. Doing it now
2161 # means that the tag cache only has to worry about destroyed
2162 # means that the tag cache only has to worry about destroyed
2162 # heads immediately after a strip/rollback. That in turn
2163 # heads immediately after a strip/rollback. That in turn
2163 # guarantees that "cachetip == currenttip" (comparing both rev
2164 # guarantees that "cachetip == currenttip" (comparing both rev
2164 # and node) always means no nodes have been added or destroyed.
2165 # and node) always means no nodes have been added or destroyed.
2165
2166
2166 # XXX this is suboptimal when qrefresh'ing: we strip the current
2167 # XXX this is suboptimal when qrefresh'ing: we strip the current
2167 # head, refresh the tag cache, then immediately add a new head.
2168 # head, refresh the tag cache, then immediately add a new head.
2168 # But I think doing it this way is necessary for the "instant
2169 # But I think doing it this way is necessary for the "instant
2169 # tag cache retrieval" case to work.
2170 # tag cache retrieval" case to work.
2170 self.invalidate()
2171 self.invalidate()
2171
2172
2172 def status(self, node1='.', node2=None, match=None,
2173 def status(self, node1='.', node2=None, match=None,
2173 ignored=False, clean=False, unknown=False,
2174 ignored=False, clean=False, unknown=False,
2174 listsubrepos=False):
2175 listsubrepos=False):
2175 '''a convenience method that calls node1.status(node2)'''
2176 '''a convenience method that calls node1.status(node2)'''
2176 return self[node1].status(node2, match, ignored, clean, unknown,
2177 return self[node1].status(node2, match, ignored, clean, unknown,
2177 listsubrepos)
2178 listsubrepos)
2178
2179
2179 def addpostdsstatus(self, ps):
2180 def addpostdsstatus(self, ps):
2180 """Add a callback to run within the wlock, at the point at which status
2181 """Add a callback to run within the wlock, at the point at which status
2181 fixups happen.
2182 fixups happen.
2182
2183
2183 On status completion, callback(wctx, status) will be called with the
2184 On status completion, callback(wctx, status) will be called with the
2184 wlock held, unless the dirstate has changed from underneath or the wlock
2185 wlock held, unless the dirstate has changed from underneath or the wlock
2185 couldn't be grabbed.
2186 couldn't be grabbed.
2186
2187
2187 Callbacks should not capture and use a cached copy of the dirstate --
2188 Callbacks should not capture and use a cached copy of the dirstate --
2188 it might change in the meanwhile. Instead, they should access the
2189 it might change in the meanwhile. Instead, they should access the
2189 dirstate via wctx.repo().dirstate.
2190 dirstate via wctx.repo().dirstate.
2190
2191
2191 This list is emptied out after each status run -- extensions should
2192 This list is emptied out after each status run -- extensions should
2192 make sure it adds to this list each time dirstate.status is called.
2193 make sure it adds to this list each time dirstate.status is called.
2193 Extensions should also make sure they don't call this for statuses
2194 Extensions should also make sure they don't call this for statuses
2194 that don't involve the dirstate.
2195 that don't involve the dirstate.
2195 """
2196 """
2196
2197
2197 # The list is located here for uniqueness reasons -- it is actually
2198 # The list is located here for uniqueness reasons -- it is actually
2198 # managed by the workingctx, but that isn't unique per-repo.
2199 # managed by the workingctx, but that isn't unique per-repo.
2199 self._postdsstatus.append(ps)
2200 self._postdsstatus.append(ps)
2200
2201
2201 def postdsstatus(self):
2202 def postdsstatus(self):
2202 """Used by workingctx to get the list of post-dirstate-status hooks."""
2203 """Used by workingctx to get the list of post-dirstate-status hooks."""
2203 return self._postdsstatus
2204 return self._postdsstatus
2204
2205
2205 def clearpostdsstatus(self):
2206 def clearpostdsstatus(self):
2206 """Used by workingctx to clear post-dirstate-status hooks."""
2207 """Used by workingctx to clear post-dirstate-status hooks."""
2207 del self._postdsstatus[:]
2208 del self._postdsstatus[:]
2208
2209
2209 def heads(self, start=None):
2210 def heads(self, start=None):
2210 if start is None:
2211 if start is None:
2211 cl = self.changelog
2212 cl = self.changelog
2212 headrevs = reversed(cl.headrevs())
2213 headrevs = reversed(cl.headrevs())
2213 return [cl.node(rev) for rev in headrevs]
2214 return [cl.node(rev) for rev in headrevs]
2214
2215
2215 heads = self.changelog.heads(start)
2216 heads = self.changelog.heads(start)
2216 # sort the output in rev descending order
2217 # sort the output in rev descending order
2217 return sorted(heads, key=self.changelog.rev, reverse=True)
2218 return sorted(heads, key=self.changelog.rev, reverse=True)
2218
2219
2219 def branchheads(self, branch=None, start=None, closed=False):
2220 def branchheads(self, branch=None, start=None, closed=False):
2220 '''return a (possibly filtered) list of heads for the given branch
2221 '''return a (possibly filtered) list of heads for the given branch
2221
2222
2222 Heads are returned in topological order, from newest to oldest.
2223 Heads are returned in topological order, from newest to oldest.
2223 If branch is None, use the dirstate branch.
2224 If branch is None, use the dirstate branch.
2224 If start is not None, return only heads reachable from start.
2225 If start is not None, return only heads reachable from start.
2225 If closed is True, return heads that are marked as closed as well.
2226 If closed is True, return heads that are marked as closed as well.
2226 '''
2227 '''
2227 if branch is None:
2228 if branch is None:
2228 branch = self[None].branch()
2229 branch = self[None].branch()
2229 branches = self.branchmap()
2230 branches = self.branchmap()
2230 if branch not in branches:
2231 if branch not in branches:
2231 return []
2232 return []
2232 # the cache returns heads ordered lowest to highest
2233 # the cache returns heads ordered lowest to highest
2233 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2234 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2234 if start is not None:
2235 if start is not None:
2235 # filter out the heads that cannot be reached from startrev
2236 # filter out the heads that cannot be reached from startrev
2236 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2237 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2237 bheads = [h for h in bheads if h in fbheads]
2238 bheads = [h for h in bheads if h in fbheads]
2238 return bheads
2239 return bheads
2239
2240
2240 def branches(self, nodes):
2241 def branches(self, nodes):
2241 if not nodes:
2242 if not nodes:
2242 nodes = [self.changelog.tip()]
2243 nodes = [self.changelog.tip()]
2243 b = []
2244 b = []
2244 for n in nodes:
2245 for n in nodes:
2245 t = n
2246 t = n
2246 while True:
2247 while True:
2247 p = self.changelog.parents(n)
2248 p = self.changelog.parents(n)
2248 if p[1] != nullid or p[0] == nullid:
2249 if p[1] != nullid or p[0] == nullid:
2249 b.append((t, n, p[0], p[1]))
2250 b.append((t, n, p[0], p[1]))
2250 break
2251 break
2251 n = p[0]
2252 n = p[0]
2252 return b
2253 return b
2253
2254
2254 def between(self, pairs):
2255 def between(self, pairs):
2255 r = []
2256 r = []
2256
2257
2257 for top, bottom in pairs:
2258 for top, bottom in pairs:
2258 n, l, i = top, [], 0
2259 n, l, i = top, [], 0
2259 f = 1
2260 f = 1
2260
2261
2261 while n != bottom and n != nullid:
2262 while n != bottom and n != nullid:
2262 p = self.changelog.parents(n)[0]
2263 p = self.changelog.parents(n)[0]
2263 if i == f:
2264 if i == f:
2264 l.append(n)
2265 l.append(n)
2265 f = f * 2
2266 f = f * 2
2266 n = p
2267 n = p
2267 i += 1
2268 i += 1
2268
2269
2269 r.append(l)
2270 r.append(l)
2270
2271
2271 return r
2272 return r
2272
2273
2273 def checkpush(self, pushop):
2274 def checkpush(self, pushop):
2274 """Extensions can override this function if additional checks have
2275 """Extensions can override this function if additional checks have
2275 to be performed before pushing, or call it if they override push
2276 to be performed before pushing, or call it if they override push
2276 command.
2277 command.
2277 """
2278 """
2278
2279
2279 @unfilteredpropertycache
2280 @unfilteredpropertycache
2280 def prepushoutgoinghooks(self):
2281 def prepushoutgoinghooks(self):
2281 """Return util.hooks consists of a pushop with repo, remote, outgoing
2282 """Return util.hooks consists of a pushop with repo, remote, outgoing
2282 methods, which are called before pushing changesets.
2283 methods, which are called before pushing changesets.
2283 """
2284 """
2284 return util.hooks()
2285 return util.hooks()
2285
2286
2286 def pushkey(self, namespace, key, old, new):
2287 def pushkey(self, namespace, key, old, new):
2287 try:
2288 try:
2288 tr = self.currenttransaction()
2289 tr = self.currenttransaction()
2289 hookargs = {}
2290 hookargs = {}
2290 if tr is not None:
2291 if tr is not None:
2291 hookargs.update(tr.hookargs)
2292 hookargs.update(tr.hookargs)
2292 hookargs = pycompat.strkwargs(hookargs)
2293 hookargs = pycompat.strkwargs(hookargs)
2293 hookargs[r'namespace'] = namespace
2294 hookargs[r'namespace'] = namespace
2294 hookargs[r'key'] = key
2295 hookargs[r'key'] = key
2295 hookargs[r'old'] = old
2296 hookargs[r'old'] = old
2296 hookargs[r'new'] = new
2297 hookargs[r'new'] = new
2297 self.hook('prepushkey', throw=True, **hookargs)
2298 self.hook('prepushkey', throw=True, **hookargs)
2298 except error.HookAbort as exc:
2299 except error.HookAbort as exc:
2299 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2300 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2300 if exc.hint:
2301 if exc.hint:
2301 self.ui.write_err(_("(%s)\n") % exc.hint)
2302 self.ui.write_err(_("(%s)\n") % exc.hint)
2302 return False
2303 return False
2303 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2304 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2304 ret = pushkey.push(self, namespace, key, old, new)
2305 ret = pushkey.push(self, namespace, key, old, new)
2305 def runhook():
2306 def runhook():
2306 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2307 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2307 ret=ret)
2308 ret=ret)
2308 self._afterlock(runhook)
2309 self._afterlock(runhook)
2309 return ret
2310 return ret
2310
2311
2311 def listkeys(self, namespace):
2312 def listkeys(self, namespace):
2312 self.hook('prelistkeys', throw=True, namespace=namespace)
2313 self.hook('prelistkeys', throw=True, namespace=namespace)
2313 self.ui.debug('listing keys for "%s"\n' % namespace)
2314 self.ui.debug('listing keys for "%s"\n' % namespace)
2314 values = pushkey.list(self, namespace)
2315 values = pushkey.list(self, namespace)
2315 self.hook('listkeys', namespace=namespace, values=values)
2316 self.hook('listkeys', namespace=namespace, values=values)
2316 return values
2317 return values
2317
2318
2318 def debugwireargs(self, one, two, three=None, four=None, five=None):
2319 def debugwireargs(self, one, two, three=None, four=None, five=None):
2319 '''used to test argument passing over the wire'''
2320 '''used to test argument passing over the wire'''
2320 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2321 return "%s %s %s %s %s" % (one, two, pycompat.bytestr(three),
2321 pycompat.bytestr(four),
2322 pycompat.bytestr(four),
2322 pycompat.bytestr(five))
2323 pycompat.bytestr(five))
2323
2324
2324 def savecommitmessage(self, text):
2325 def savecommitmessage(self, text):
2325 fp = self.vfs('last-message.txt', 'wb')
2326 fp = self.vfs('last-message.txt', 'wb')
2326 try:
2327 try:
2327 fp.write(text)
2328 fp.write(text)
2328 finally:
2329 finally:
2329 fp.close()
2330 fp.close()
2330 return self.pathto(fp.name[len(self.root) + 1:])
2331 return self.pathto(fp.name[len(self.root) + 1:])
2331
2332
2332 # used to avoid circular references so destructors work
2333 # used to avoid circular references so destructors work
2333 def aftertrans(files):
2334 def aftertrans(files):
2334 renamefiles = [tuple(t) for t in files]
2335 renamefiles = [tuple(t) for t in files]
2335 def a():
2336 def a():
2336 for vfs, src, dest in renamefiles:
2337 for vfs, src, dest in renamefiles:
2337 # if src and dest refer to a same file, vfs.rename is a no-op,
2338 # if src and dest refer to a same file, vfs.rename is a no-op,
2338 # leaving both src and dest on disk. delete dest to make sure
2339 # leaving both src and dest on disk. delete dest to make sure
2339 # the rename couldn't be such a no-op.
2340 # the rename couldn't be such a no-op.
2340 vfs.tryunlink(dest)
2341 vfs.tryunlink(dest)
2341 try:
2342 try:
2342 vfs.rename(src, dest)
2343 vfs.rename(src, dest)
2343 except OSError: # journal file does not yet exist
2344 except OSError: # journal file does not yet exist
2344 pass
2345 pass
2345 return a
2346 return a
2346
2347
2347 def undoname(fn):
2348 def undoname(fn):
2348 base, name = os.path.split(fn)
2349 base, name = os.path.split(fn)
2349 assert name.startswith('journal')
2350 assert name.startswith('journal')
2350 return os.path.join(base, name.replace('journal', 'undo', 1))
2351 return os.path.join(base, name.replace('journal', 'undo', 1))
2351
2352
2352 def instance(ui, path, create, intents=None):
2353 def instance(ui, path, create, intents=None):
2353 return localrepository(ui, util.urllocalpath(path), create,
2354 return localrepository(ui, util.urllocalpath(path), create,
2354 intents=intents)
2355 intents=intents)
2355
2356
2356 def islocal(path):
2357 def islocal(path):
2357 return True
2358 return True
2358
2359
2359 def newreporequirements(repo):
2360 def newreporequirements(repo):
2360 """Determine the set of requirements for a new local repository.
2361 """Determine the set of requirements for a new local repository.
2361
2362
2362 Extensions can wrap this function to specify custom requirements for
2363 Extensions can wrap this function to specify custom requirements for
2363 new repositories.
2364 new repositories.
2364 """
2365 """
2365 ui = repo.ui
2366 ui = repo.ui
2366 requirements = {'revlogv1'}
2367 requirements = {'revlogv1'}
2367 if ui.configbool('format', 'usestore'):
2368 if ui.configbool('format', 'usestore'):
2368 requirements.add('store')
2369 requirements.add('store')
2369 if ui.configbool('format', 'usefncache'):
2370 if ui.configbool('format', 'usefncache'):
2370 requirements.add('fncache')
2371 requirements.add('fncache')
2371 if ui.configbool('format', 'dotencode'):
2372 if ui.configbool('format', 'dotencode'):
2372 requirements.add('dotencode')
2373 requirements.add('dotencode')
2373
2374
2374 compengine = ui.config('experimental', 'format.compression')
2375 compengine = ui.config('experimental', 'format.compression')
2375 if compengine not in util.compengines:
2376 if compengine not in util.compengines:
2376 raise error.Abort(_('compression engine %s defined by '
2377 raise error.Abort(_('compression engine %s defined by '
2377 'experimental.format.compression not available') %
2378 'experimental.format.compression not available') %
2378 compengine,
2379 compengine,
2379 hint=_('run "hg debuginstall" to list available '
2380 hint=_('run "hg debuginstall" to list available '
2380 'compression engines'))
2381 'compression engines'))
2381
2382
2382 # zlib is the historical default and doesn't need an explicit requirement.
2383 # zlib is the historical default and doesn't need an explicit requirement.
2383 if compengine != 'zlib':
2384 if compengine != 'zlib':
2384 requirements.add('exp-compression-%s' % compengine)
2385 requirements.add('exp-compression-%s' % compengine)
2385
2386
2386 if scmutil.gdinitconfig(ui):
2387 if scmutil.gdinitconfig(ui):
2387 requirements.add('generaldelta')
2388 requirements.add('generaldelta')
2388 if ui.configbool('experimental', 'treemanifest'):
2389 if ui.configbool('experimental', 'treemanifest'):
2389 requirements.add('treemanifest')
2390 requirements.add('treemanifest')
2390 # experimental config: format.sparse-revlog
2391 # experimental config: format.sparse-revlog
2391 if ui.configbool('format', 'sparse-revlog'):
2392 if ui.configbool('format', 'sparse-revlog'):
2392 requirements.add(SPARSEREVLOG_REQUIREMENT)
2393 requirements.add(SPARSEREVLOG_REQUIREMENT)
2393
2394
2394 revlogv2 = ui.config('experimental', 'revlogv2')
2395 revlogv2 = ui.config('experimental', 'revlogv2')
2395 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2396 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2396 requirements.remove('revlogv1')
2397 requirements.remove('revlogv1')
2397 # generaldelta is implied by revlogv2.
2398 # generaldelta is implied by revlogv2.
2398 requirements.discard('generaldelta')
2399 requirements.discard('generaldelta')
2399 requirements.add(REVLOGV2_REQUIREMENT)
2400 requirements.add(REVLOGV2_REQUIREMENT)
2400
2401
2401 return requirements
2402 return requirements
@@ -1,2963 +1,2964 b''
1 # revlog.py - storage back-end for mercurial
1 # revlog.py - storage back-end for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Storage back-end for Mercurial.
8 """Storage back-end for Mercurial.
9
9
10 This provides efficient delta storage with O(1) retrieve and append
10 This provides efficient delta storage with O(1) retrieve and append
11 and O(changes) merge between branches.
11 and O(changes) merge between branches.
12 """
12 """
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import collections
16 import collections
17 import contextlib
17 import contextlib
18 import errno
18 import errno
19 import hashlib
19 import hashlib
20 import heapq
20 import heapq
21 import os
21 import os
22 import re
22 import re
23 import struct
23 import struct
24 import zlib
24 import zlib
25
25
26 # import stuff from node for others to import from revlog
26 # import stuff from node for others to import from revlog
27 from .node import (
27 from .node import (
28 bin,
28 bin,
29 hex,
29 hex,
30 nullid,
30 nullid,
31 nullrev,
31 nullrev,
32 wdirfilenodeids,
32 wdirfilenodeids,
33 wdirhex,
33 wdirhex,
34 wdirid,
34 wdirid,
35 wdirrev,
35 wdirrev,
36 )
36 )
37 from .i18n import _
37 from .i18n import _
38 from .thirdparty import (
38 from .thirdparty import (
39 attr,
39 attr,
40 )
40 )
41 from . import (
41 from . import (
42 ancestor,
42 ancestor,
43 error,
43 error,
44 mdiff,
44 mdiff,
45 policy,
45 policy,
46 pycompat,
46 pycompat,
47 templatefilters,
47 templatefilters,
48 util,
48 util,
49 )
49 )
50 from .utils import (
50 from .utils import (
51 stringutil,
51 stringutil,
52 )
52 )
53
53
54 parsers = policy.importmod(r'parsers')
54 parsers = policy.importmod(r'parsers')
55
55
56 # Aliased for performance.
56 # Aliased for performance.
57 _zlibdecompress = zlib.decompress
57 _zlibdecompress = zlib.decompress
58
58
59 # revlog header flags
59 # revlog header flags
60 REVLOGV0 = 0
60 REVLOGV0 = 0
61 REVLOGV1 = 1
61 REVLOGV1 = 1
62 # Dummy value until file format is finalized.
62 # Dummy value until file format is finalized.
63 # Reminder: change the bounds check in revlog.__init__ when this is changed.
63 # Reminder: change the bounds check in revlog.__init__ when this is changed.
64 REVLOGV2 = 0xDEAD
64 REVLOGV2 = 0xDEAD
65 FLAG_INLINE_DATA = (1 << 16)
65 FLAG_INLINE_DATA = (1 << 16)
66 FLAG_GENERALDELTA = (1 << 17)
66 FLAG_GENERALDELTA = (1 << 17)
67 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
67 REVLOG_DEFAULT_FLAGS = FLAG_INLINE_DATA
68 REVLOG_DEFAULT_FORMAT = REVLOGV1
68 REVLOG_DEFAULT_FORMAT = REVLOGV1
69 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
69 REVLOG_DEFAULT_VERSION = REVLOG_DEFAULT_FORMAT | REVLOG_DEFAULT_FLAGS
70 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
70 REVLOGV1_FLAGS = FLAG_INLINE_DATA | FLAG_GENERALDELTA
71 REVLOGV2_FLAGS = REVLOGV1_FLAGS
71 REVLOGV2_FLAGS = REVLOGV1_FLAGS
72
72
73 # revlog index flags
73 # revlog index flags
74 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
74 REVIDX_ISCENSORED = (1 << 15) # revision has censor metadata, must be verified
75 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
75 REVIDX_ELLIPSIS = (1 << 14) # revision hash does not match data (narrowhg)
76 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
76 REVIDX_EXTSTORED = (1 << 13) # revision data is stored externally
77 REVIDX_DEFAULT_FLAGS = 0
77 REVIDX_DEFAULT_FLAGS = 0
78 # stable order in which flags need to be processed and their processors applied
78 # stable order in which flags need to be processed and their processors applied
79 REVIDX_FLAGS_ORDER = [
79 REVIDX_FLAGS_ORDER = [
80 REVIDX_ISCENSORED,
80 REVIDX_ISCENSORED,
81 REVIDX_ELLIPSIS,
81 REVIDX_ELLIPSIS,
82 REVIDX_EXTSTORED,
82 REVIDX_EXTSTORED,
83 ]
83 ]
84 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
84 REVIDX_KNOWN_FLAGS = util.bitsfrom(REVIDX_FLAGS_ORDER)
85 # bitmark for flags that could cause rawdata content change
85 # bitmark for flags that could cause rawdata content change
86 REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
86 REVIDX_RAWTEXT_CHANGING_FLAGS = REVIDX_ISCENSORED | REVIDX_EXTSTORED
87
87
88 # max size of revlog with inline data
88 # max size of revlog with inline data
89 _maxinline = 131072
89 _maxinline = 131072
90 _chunksize = 1048576
90 _chunksize = 1048576
91
91
92 RevlogError = error.RevlogError
92 RevlogError = error.RevlogError
93 LookupError = error.LookupError
93 LookupError = error.LookupError
94 AmbiguousPrefixLookupError = error.AmbiguousPrefixLookupError
94 CensoredNodeError = error.CensoredNodeError
95 CensoredNodeError = error.CensoredNodeError
95 ProgrammingError = error.ProgrammingError
96 ProgrammingError = error.ProgrammingError
96
97
97 # Store flag processors (cf. 'addflagprocessor()' to register)
98 # Store flag processors (cf. 'addflagprocessor()' to register)
98 _flagprocessors = {
99 _flagprocessors = {
99 REVIDX_ISCENSORED: None,
100 REVIDX_ISCENSORED: None,
100 }
101 }
101
102
102 _mdre = re.compile('\1\n')
103 _mdre = re.compile('\1\n')
103 def parsemeta(text):
104 def parsemeta(text):
104 """return (metadatadict, metadatasize)"""
105 """return (metadatadict, metadatasize)"""
105 # text can be buffer, so we can't use .startswith or .index
106 # text can be buffer, so we can't use .startswith or .index
106 if text[:2] != '\1\n':
107 if text[:2] != '\1\n':
107 return None, None
108 return None, None
108 s = _mdre.search(text, 2).start()
109 s = _mdre.search(text, 2).start()
109 mtext = text[2:s]
110 mtext = text[2:s]
110 meta = {}
111 meta = {}
111 for l in mtext.splitlines():
112 for l in mtext.splitlines():
112 k, v = l.split(": ", 1)
113 k, v = l.split(": ", 1)
113 meta[k] = v
114 meta[k] = v
114 return meta, (s + 2)
115 return meta, (s + 2)
115
116
116 def packmeta(meta, text):
117 def packmeta(meta, text):
117 keys = sorted(meta)
118 keys = sorted(meta)
118 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
119 metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
119 return "\1\n%s\1\n%s" % (metatext, text)
120 return "\1\n%s\1\n%s" % (metatext, text)
120
121
121 def _censoredtext(text):
122 def _censoredtext(text):
122 m, offs = parsemeta(text)
123 m, offs = parsemeta(text)
123 return m and "censored" in m
124 return m and "censored" in m
124
125
125 def addflagprocessor(flag, processor):
126 def addflagprocessor(flag, processor):
126 """Register a flag processor on a revision data flag.
127 """Register a flag processor on a revision data flag.
127
128
128 Invariant:
129 Invariant:
129 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
130 - Flags need to be defined in REVIDX_KNOWN_FLAGS and REVIDX_FLAGS_ORDER,
130 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
131 and REVIDX_RAWTEXT_CHANGING_FLAGS if they can alter rawtext.
131 - Only one flag processor can be registered on a specific flag.
132 - Only one flag processor can be registered on a specific flag.
132 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
133 - flagprocessors must be 3-tuples of functions (read, write, raw) with the
133 following signatures:
134 following signatures:
134 - (read) f(self, rawtext) -> text, bool
135 - (read) f(self, rawtext) -> text, bool
135 - (write) f(self, text) -> rawtext, bool
136 - (write) f(self, text) -> rawtext, bool
136 - (raw) f(self, rawtext) -> bool
137 - (raw) f(self, rawtext) -> bool
137 "text" is presented to the user. "rawtext" is stored in revlog data, not
138 "text" is presented to the user. "rawtext" is stored in revlog data, not
138 directly visible to the user.
139 directly visible to the user.
139 The boolean returned by these transforms is used to determine whether
140 The boolean returned by these transforms is used to determine whether
140 the returned text can be used for hash integrity checking. For example,
141 the returned text can be used for hash integrity checking. For example,
141 if "write" returns False, then "text" is used to generate hash. If
142 if "write" returns False, then "text" is used to generate hash. If
142 "write" returns True, that basically means "rawtext" returned by "write"
143 "write" returns True, that basically means "rawtext" returned by "write"
143 should be used to generate hash. Usually, "write" and "read" return
144 should be used to generate hash. Usually, "write" and "read" return
144 different booleans. And "raw" returns a same boolean as "write".
145 different booleans. And "raw" returns a same boolean as "write".
145
146
146 Note: The 'raw' transform is used for changegroup generation and in some
147 Note: The 'raw' transform is used for changegroup generation and in some
147 debug commands. In this case the transform only indicates whether the
148 debug commands. In this case the transform only indicates whether the
148 contents can be used for hash integrity checks.
149 contents can be used for hash integrity checks.
149 """
150 """
150 if not flag & REVIDX_KNOWN_FLAGS:
151 if not flag & REVIDX_KNOWN_FLAGS:
151 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
152 msg = _("cannot register processor on unknown flag '%#x'.") % (flag)
152 raise ProgrammingError(msg)
153 raise ProgrammingError(msg)
153 if flag not in REVIDX_FLAGS_ORDER:
154 if flag not in REVIDX_FLAGS_ORDER:
154 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
155 msg = _("flag '%#x' undefined in REVIDX_FLAGS_ORDER.") % (flag)
155 raise ProgrammingError(msg)
156 raise ProgrammingError(msg)
156 if flag in _flagprocessors:
157 if flag in _flagprocessors:
157 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
158 msg = _("cannot register multiple processors on flag '%#x'.") % (flag)
158 raise error.Abort(msg)
159 raise error.Abort(msg)
159 _flagprocessors[flag] = processor
160 _flagprocessors[flag] = processor
160
161
161 def getoffset(q):
162 def getoffset(q):
162 return int(q >> 16)
163 return int(q >> 16)
163
164
164 def gettype(q):
165 def gettype(q):
165 return int(q & 0xFFFF)
166 return int(q & 0xFFFF)
166
167
167 def offset_type(offset, type):
168 def offset_type(offset, type):
168 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
169 if (type & ~REVIDX_KNOWN_FLAGS) != 0:
169 raise ValueError('unknown revlog index flags')
170 raise ValueError('unknown revlog index flags')
170 return int(int(offset) << 16 | type)
171 return int(int(offset) << 16 | type)
171
172
172 _nullhash = hashlib.sha1(nullid)
173 _nullhash = hashlib.sha1(nullid)
173
174
174 def hash(text, p1, p2):
175 def hash(text, p1, p2):
175 """generate a hash from the given text and its parent hashes
176 """generate a hash from the given text and its parent hashes
176
177
177 This hash combines both the current file contents and its history
178 This hash combines both the current file contents and its history
178 in a manner that makes it easy to distinguish nodes with the same
179 in a manner that makes it easy to distinguish nodes with the same
179 content in the revision graph.
180 content in the revision graph.
180 """
181 """
181 # As of now, if one of the parent node is null, p2 is null
182 # As of now, if one of the parent node is null, p2 is null
182 if p2 == nullid:
183 if p2 == nullid:
183 # deep copy of a hash is faster than creating one
184 # deep copy of a hash is faster than creating one
184 s = _nullhash.copy()
185 s = _nullhash.copy()
185 s.update(p1)
186 s.update(p1)
186 else:
187 else:
187 # none of the parent nodes are nullid
188 # none of the parent nodes are nullid
188 if p1 < p2:
189 if p1 < p2:
189 a = p1
190 a = p1
190 b = p2
191 b = p2
191 else:
192 else:
192 a = p2
193 a = p2
193 b = p1
194 b = p1
194 s = hashlib.sha1(a)
195 s = hashlib.sha1(a)
195 s.update(b)
196 s.update(b)
196 s.update(text)
197 s.update(text)
197 return s.digest()
198 return s.digest()
198
199
199 class _testrevlog(object):
200 class _testrevlog(object):
200 """minimalist fake revlog to use in doctests"""
201 """minimalist fake revlog to use in doctests"""
201
202
202 def __init__(self, data, density=0.5, mingap=0):
203 def __init__(self, data, density=0.5, mingap=0):
203 """data is an list of revision payload boundaries"""
204 """data is an list of revision payload boundaries"""
204 self._data = data
205 self._data = data
205 self._srdensitythreshold = density
206 self._srdensitythreshold = density
206 self._srmingapsize = mingap
207 self._srmingapsize = mingap
207
208
208 def start(self, rev):
209 def start(self, rev):
209 if rev == 0:
210 if rev == 0:
210 return 0
211 return 0
211 return self._data[rev - 1]
212 return self._data[rev - 1]
212
213
213 def end(self, rev):
214 def end(self, rev):
214 return self._data[rev]
215 return self._data[rev]
215
216
216 def length(self, rev):
217 def length(self, rev):
217 return self.end(rev) - self.start(rev)
218 return self.end(rev) - self.start(rev)
218
219
219 def __len__(self):
220 def __len__(self):
220 return len(self._data)
221 return len(self._data)
221
222
222 def _trimchunk(revlog, revs, startidx, endidx=None):
223 def _trimchunk(revlog, revs, startidx, endidx=None):
223 """returns revs[startidx:endidx] without empty trailing revs
224 """returns revs[startidx:endidx] without empty trailing revs
224
225
225 Doctest Setup
226 Doctest Setup
226 >>> revlog = _testrevlog([
227 >>> revlog = _testrevlog([
227 ... 5, #0
228 ... 5, #0
228 ... 10, #1
229 ... 10, #1
229 ... 12, #2
230 ... 12, #2
230 ... 12, #3 (empty)
231 ... 12, #3 (empty)
231 ... 17, #4
232 ... 17, #4
232 ... 21, #5
233 ... 21, #5
233 ... 21, #6 (empty)
234 ... 21, #6 (empty)
234 ... ])
235 ... ])
235
236
236 Contiguous cases:
237 Contiguous cases:
237 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
238 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0)
238 [0, 1, 2, 3, 4, 5]
239 [0, 1, 2, 3, 4, 5]
239 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
240 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 5)
240 [0, 1, 2, 3, 4]
241 [0, 1, 2, 3, 4]
241 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
242 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 0, 4)
242 [0, 1, 2]
243 [0, 1, 2]
243 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
244 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 2, 4)
244 [2]
245 [2]
245 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
246 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3)
246 [3, 4, 5]
247 [3, 4, 5]
247 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
248 >>> _trimchunk(revlog, [0, 1, 2, 3, 4, 5, 6], 3, 5)
248 [3, 4]
249 [3, 4]
249
250
250 Discontiguous cases:
251 Discontiguous cases:
251 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
252 >>> _trimchunk(revlog, [1, 3, 5, 6], 0)
252 [1, 3, 5]
253 [1, 3, 5]
253 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
254 >>> _trimchunk(revlog, [1, 3, 5, 6], 0, 2)
254 [1]
255 [1]
255 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
256 >>> _trimchunk(revlog, [1, 3, 5, 6], 1, 3)
256 [3, 5]
257 [3, 5]
257 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
258 >>> _trimchunk(revlog, [1, 3, 5, 6], 1)
258 [3, 5]
259 [3, 5]
259 """
260 """
260 length = revlog.length
261 length = revlog.length
261
262
262 if endidx is None:
263 if endidx is None:
263 endidx = len(revs)
264 endidx = len(revs)
264
265
265 # Trim empty revs at the end, but never the very first revision of a chain
266 # Trim empty revs at the end, but never the very first revision of a chain
266 while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
267 while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
267 endidx -= 1
268 endidx -= 1
268
269
269 return revs[startidx:endidx]
270 return revs[startidx:endidx]
270
271
271 def _segmentspan(revlog, revs):
272 def _segmentspan(revlog, revs):
272 """Get the byte span of a segment of revisions
273 """Get the byte span of a segment of revisions
273
274
274 revs is a sorted array of revision numbers
275 revs is a sorted array of revision numbers
275
276
276 >>> revlog = _testrevlog([
277 >>> revlog = _testrevlog([
277 ... 5, #0
278 ... 5, #0
278 ... 10, #1
279 ... 10, #1
279 ... 12, #2
280 ... 12, #2
280 ... 12, #3 (empty)
281 ... 12, #3 (empty)
281 ... 17, #4
282 ... 17, #4
282 ... ])
283 ... ])
283
284
284 >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
285 >>> _segmentspan(revlog, [0, 1, 2, 3, 4])
285 17
286 17
286 >>> _segmentspan(revlog, [0, 4])
287 >>> _segmentspan(revlog, [0, 4])
287 17
288 17
288 >>> _segmentspan(revlog, [3, 4])
289 >>> _segmentspan(revlog, [3, 4])
289 5
290 5
290 >>> _segmentspan(revlog, [1, 2, 3,])
291 >>> _segmentspan(revlog, [1, 2, 3,])
291 7
292 7
292 >>> _segmentspan(revlog, [1, 3])
293 >>> _segmentspan(revlog, [1, 3])
293 7
294 7
294 """
295 """
295 if not revs:
296 if not revs:
296 return 0
297 return 0
297 return revlog.end(revs[-1]) - revlog.start(revs[0])
298 return revlog.end(revs[-1]) - revlog.start(revs[0])
298
299
299 def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
300 def _slicechunk(revlog, revs, deltainfo=None, targetsize=None):
300 """slice revs to reduce the amount of unrelated data to be read from disk.
301 """slice revs to reduce the amount of unrelated data to be read from disk.
301
302
302 ``revs`` is sliced into groups that should be read in one time.
303 ``revs`` is sliced into groups that should be read in one time.
303 Assume that revs are sorted.
304 Assume that revs are sorted.
304
305
305 The initial chunk is sliced until the overall density (payload/chunks-span
306 The initial chunk is sliced until the overall density (payload/chunks-span
306 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
307 ratio) is above `revlog._srdensitythreshold`. No gap smaller than
307 `revlog._srmingapsize` is skipped.
308 `revlog._srmingapsize` is skipped.
308
309
309 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
310 If `targetsize` is set, no chunk larger than `targetsize` will be yield.
310 For consistency with other slicing choice, this limit won't go lower than
311 For consistency with other slicing choice, this limit won't go lower than
311 `revlog._srmingapsize`.
312 `revlog._srmingapsize`.
312
313
313 If individual revisions chunk are larger than this limit, they will still
314 If individual revisions chunk are larger than this limit, they will still
314 be raised individually.
315 be raised individually.
315
316
316 >>> revlog = _testrevlog([
317 >>> revlog = _testrevlog([
317 ... 5, #00 (5)
318 ... 5, #00 (5)
318 ... 10, #01 (5)
319 ... 10, #01 (5)
319 ... 12, #02 (2)
320 ... 12, #02 (2)
320 ... 12, #03 (empty)
321 ... 12, #03 (empty)
321 ... 27, #04 (15)
322 ... 27, #04 (15)
322 ... 31, #05 (4)
323 ... 31, #05 (4)
323 ... 31, #06 (empty)
324 ... 31, #06 (empty)
324 ... 42, #07 (11)
325 ... 42, #07 (11)
325 ... 47, #08 (5)
326 ... 47, #08 (5)
326 ... 47, #09 (empty)
327 ... 47, #09 (empty)
327 ... 48, #10 (1)
328 ... 48, #10 (1)
328 ... 51, #11 (3)
329 ... 51, #11 (3)
329 ... 74, #12 (23)
330 ... 74, #12 (23)
330 ... 85, #13 (11)
331 ... 85, #13 (11)
331 ... 86, #14 (1)
332 ... 86, #14 (1)
332 ... 91, #15 (5)
333 ... 91, #15 (5)
333 ... ])
334 ... ])
334
335
335 >>> list(_slicechunk(revlog, list(range(16))))
336 >>> list(_slicechunk(revlog, list(range(16))))
336 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
337 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
337 >>> list(_slicechunk(revlog, [0, 15]))
338 >>> list(_slicechunk(revlog, [0, 15]))
338 [[0], [15]]
339 [[0], [15]]
339 >>> list(_slicechunk(revlog, [0, 11, 15]))
340 >>> list(_slicechunk(revlog, [0, 11, 15]))
340 [[0], [11], [15]]
341 [[0], [11], [15]]
341 >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
342 >>> list(_slicechunk(revlog, [0, 11, 13, 15]))
342 [[0], [11, 13, 15]]
343 [[0], [11, 13, 15]]
343 >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
344 >>> list(_slicechunk(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
344 [[1, 2], [5, 8, 10, 11], [14]]
345 [[1, 2], [5, 8, 10, 11], [14]]
345
346
346 Slicing with a maximum chunk size
347 Slicing with a maximum chunk size
347 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
348 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=15))
348 [[0], [11], [13], [15]]
349 [[0], [11], [13], [15]]
349 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
350 >>> list(_slicechunk(revlog, [0, 11, 13, 15], targetsize=20))
350 [[0], [11], [13, 15]]
351 [[0], [11], [13, 15]]
351 """
352 """
352 if targetsize is not None:
353 if targetsize is not None:
353 targetsize = max(targetsize, revlog._srmingapsize)
354 targetsize = max(targetsize, revlog._srmingapsize)
354 # targetsize should not be specified when evaluating delta candidates:
355 # targetsize should not be specified when evaluating delta candidates:
355 # * targetsize is used to ensure we stay within specification when reading,
356 # * targetsize is used to ensure we stay within specification when reading,
356 # * deltainfo is used to pick are good delta chain when writing.
357 # * deltainfo is used to pick are good delta chain when writing.
357 if not (deltainfo is None or targetsize is None):
358 if not (deltainfo is None or targetsize is None):
358 msg = 'cannot use `targetsize` with a `deltainfo`'
359 msg = 'cannot use `targetsize` with a `deltainfo`'
359 raise error.ProgrammingError(msg)
360 raise error.ProgrammingError(msg)
360 for chunk in _slicechunktodensity(revlog, revs,
361 for chunk in _slicechunktodensity(revlog, revs,
361 deltainfo,
362 deltainfo,
362 revlog._srdensitythreshold,
363 revlog._srdensitythreshold,
363 revlog._srmingapsize):
364 revlog._srmingapsize):
364 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
365 for subchunk in _slicechunktosize(revlog, chunk, targetsize):
365 yield subchunk
366 yield subchunk
366
367
367 def _slicechunktosize(revlog, revs, targetsize=None):
368 def _slicechunktosize(revlog, revs, targetsize=None):
368 """slice revs to match the target size
369 """slice revs to match the target size
369
370
370 This is intended to be used on chunk that density slicing selected by that
371 This is intended to be used on chunk that density slicing selected by that
371 are still too large compared to the read garantee of revlog. This might
372 are still too large compared to the read garantee of revlog. This might
372 happens when "minimal gap size" interrupted the slicing or when chain are
373 happens when "minimal gap size" interrupted the slicing or when chain are
373 built in a way that create large blocks next to each other.
374 built in a way that create large blocks next to each other.
374
375
375 >>> revlog = _testrevlog([
376 >>> revlog = _testrevlog([
376 ... 3, #0 (3)
377 ... 3, #0 (3)
377 ... 5, #1 (2)
378 ... 5, #1 (2)
378 ... 6, #2 (1)
379 ... 6, #2 (1)
379 ... 8, #3 (2)
380 ... 8, #3 (2)
380 ... 8, #4 (empty)
381 ... 8, #4 (empty)
381 ... 11, #5 (3)
382 ... 11, #5 (3)
382 ... 12, #6 (1)
383 ... 12, #6 (1)
383 ... 13, #7 (1)
384 ... 13, #7 (1)
384 ... 14, #8 (1)
385 ... 14, #8 (1)
385 ... ])
386 ... ])
386
387
387 Cases where chunk is already small enough
388 Cases where chunk is already small enough
388 >>> list(_slicechunktosize(revlog, [0], 3))
389 >>> list(_slicechunktosize(revlog, [0], 3))
389 [[0]]
390 [[0]]
390 >>> list(_slicechunktosize(revlog, [6, 7], 3))
391 >>> list(_slicechunktosize(revlog, [6, 7], 3))
391 [[6, 7]]
392 [[6, 7]]
392 >>> list(_slicechunktosize(revlog, [0], None))
393 >>> list(_slicechunktosize(revlog, [0], None))
393 [[0]]
394 [[0]]
394 >>> list(_slicechunktosize(revlog, [6, 7], None))
395 >>> list(_slicechunktosize(revlog, [6, 7], None))
395 [[6, 7]]
396 [[6, 7]]
396
397
397 cases where we need actual slicing
398 cases where we need actual slicing
398 >>> list(_slicechunktosize(revlog, [0, 1], 3))
399 >>> list(_slicechunktosize(revlog, [0, 1], 3))
399 [[0], [1]]
400 [[0], [1]]
400 >>> list(_slicechunktosize(revlog, [1, 3], 3))
401 >>> list(_slicechunktosize(revlog, [1, 3], 3))
401 [[1], [3]]
402 [[1], [3]]
402 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
403 >>> list(_slicechunktosize(revlog, [1, 2, 3], 3))
403 [[1, 2], [3]]
404 [[1, 2], [3]]
404 >>> list(_slicechunktosize(revlog, [3, 5], 3))
405 >>> list(_slicechunktosize(revlog, [3, 5], 3))
405 [[3], [5]]
406 [[3], [5]]
406 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
407 >>> list(_slicechunktosize(revlog, [3, 4, 5], 3))
407 [[3], [5]]
408 [[3], [5]]
408 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
409 >>> list(_slicechunktosize(revlog, [5, 6, 7, 8], 3))
409 [[5], [6, 7, 8]]
410 [[5], [6, 7, 8]]
410 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
411 >>> list(_slicechunktosize(revlog, [0, 1, 2, 3, 4, 5, 6, 7, 8], 3))
411 [[0], [1, 2], [3], [5], [6, 7, 8]]
412 [[0], [1, 2], [3], [5], [6, 7, 8]]
412
413
413 Case with too large individual chunk (must return valid chunk)
414 Case with too large individual chunk (must return valid chunk)
414 >>> list(_slicechunktosize(revlog, [0, 1], 2))
415 >>> list(_slicechunktosize(revlog, [0, 1], 2))
415 [[0], [1]]
416 [[0], [1]]
416 >>> list(_slicechunktosize(revlog, [1, 3], 1))
417 >>> list(_slicechunktosize(revlog, [1, 3], 1))
417 [[1], [3]]
418 [[1], [3]]
418 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
419 >>> list(_slicechunktosize(revlog, [3, 4, 5], 2))
419 [[3], [5]]
420 [[3], [5]]
420 """
421 """
421 assert targetsize is None or 0 <= targetsize
422 assert targetsize is None or 0 <= targetsize
422 if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
423 if targetsize is None or _segmentspan(revlog, revs) <= targetsize:
423 yield revs
424 yield revs
424 return
425 return
425
426
426 startrevidx = 0
427 startrevidx = 0
427 startdata = revlog.start(revs[0])
428 startdata = revlog.start(revs[0])
428 endrevidx = 0
429 endrevidx = 0
429 iterrevs = enumerate(revs)
430 iterrevs = enumerate(revs)
430 next(iterrevs) # skip first rev.
431 next(iterrevs) # skip first rev.
431 for idx, r in iterrevs:
432 for idx, r in iterrevs:
432 span = revlog.end(r) - startdata
433 span = revlog.end(r) - startdata
433 if span <= targetsize:
434 if span <= targetsize:
434 endrevidx = idx
435 endrevidx = idx
435 else:
436 else:
436 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
437 chunk = _trimchunk(revlog, revs, startrevidx, endrevidx + 1)
437 if chunk:
438 if chunk:
438 yield chunk
439 yield chunk
439 startrevidx = idx
440 startrevidx = idx
440 startdata = revlog.start(r)
441 startdata = revlog.start(r)
441 endrevidx = idx
442 endrevidx = idx
442 yield _trimchunk(revlog, revs, startrevidx)
443 yield _trimchunk(revlog, revs, startrevidx)
443
444
444 def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
445 def _slicechunktodensity(revlog, revs, deltainfo=None, targetdensity=0.5,
445 mingapsize=0):
446 mingapsize=0):
446 """slice revs to reduce the amount of unrelated data to be read from disk.
447 """slice revs to reduce the amount of unrelated data to be read from disk.
447
448
448 ``revs`` is sliced into groups that should be read in one time.
449 ``revs`` is sliced into groups that should be read in one time.
449 Assume that revs are sorted.
450 Assume that revs are sorted.
450
451
451 ``deltainfo`` is a _deltainfo instance of a revision that we would append
452 ``deltainfo`` is a _deltainfo instance of a revision that we would append
452 to the top of the revlog.
453 to the top of the revlog.
453
454
454 The initial chunk is sliced until the overall density (payload/chunks-span
455 The initial chunk is sliced until the overall density (payload/chunks-span
455 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
456 ratio) is above `targetdensity`. No gap smaller than `mingapsize` is
456 skipped.
457 skipped.
457
458
458 >>> revlog = _testrevlog([
459 >>> revlog = _testrevlog([
459 ... 5, #00 (5)
460 ... 5, #00 (5)
460 ... 10, #01 (5)
461 ... 10, #01 (5)
461 ... 12, #02 (2)
462 ... 12, #02 (2)
462 ... 12, #03 (empty)
463 ... 12, #03 (empty)
463 ... 27, #04 (15)
464 ... 27, #04 (15)
464 ... 31, #05 (4)
465 ... 31, #05 (4)
465 ... 31, #06 (empty)
466 ... 31, #06 (empty)
466 ... 42, #07 (11)
467 ... 42, #07 (11)
467 ... 47, #08 (5)
468 ... 47, #08 (5)
468 ... 47, #09 (empty)
469 ... 47, #09 (empty)
469 ... 48, #10 (1)
470 ... 48, #10 (1)
470 ... 51, #11 (3)
471 ... 51, #11 (3)
471 ... 74, #12 (23)
472 ... 74, #12 (23)
472 ... 85, #13 (11)
473 ... 85, #13 (11)
473 ... 86, #14 (1)
474 ... 86, #14 (1)
474 ... 91, #15 (5)
475 ... 91, #15 (5)
475 ... ])
476 ... ])
476
477
477 >>> list(_slicechunktodensity(revlog, list(range(16))))
478 >>> list(_slicechunktodensity(revlog, list(range(16))))
478 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
479 [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]]
479 >>> list(_slicechunktodensity(revlog, [0, 15]))
480 >>> list(_slicechunktodensity(revlog, [0, 15]))
480 [[0], [15]]
481 [[0], [15]]
481 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
482 >>> list(_slicechunktodensity(revlog, [0, 11, 15]))
482 [[0], [11], [15]]
483 [[0], [11], [15]]
483 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
484 >>> list(_slicechunktodensity(revlog, [0, 11, 13, 15]))
484 [[0], [11, 13, 15]]
485 [[0], [11, 13, 15]]
485 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
486 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14]))
486 [[1, 2], [5, 8, 10, 11], [14]]
487 [[1, 2], [5, 8, 10, 11], [14]]
487 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
488 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
488 ... mingapsize=20))
489 ... mingapsize=20))
489 [[1, 2, 3, 5, 8, 10, 11], [14]]
490 [[1, 2, 3, 5, 8, 10, 11], [14]]
490 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
491 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
491 ... targetdensity=0.95))
492 ... targetdensity=0.95))
492 [[1, 2], [5], [8, 10, 11], [14]]
493 [[1, 2], [5], [8, 10, 11], [14]]
493 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
494 >>> list(_slicechunktodensity(revlog, [1, 2, 3, 5, 8, 10, 11, 14],
494 ... targetdensity=0.95, mingapsize=12))
495 ... targetdensity=0.95, mingapsize=12))
495 [[1, 2], [5, 8, 10, 11], [14]]
496 [[1, 2], [5, 8, 10, 11], [14]]
496 """
497 """
497 start = revlog.start
498 start = revlog.start
498 length = revlog.length
499 length = revlog.length
499
500
500 if len(revs) <= 1:
501 if len(revs) <= 1:
501 yield revs
502 yield revs
502 return
503 return
503
504
504 nextrev = len(revlog)
505 nextrev = len(revlog)
505 nextoffset = revlog.end(nextrev - 1)
506 nextoffset = revlog.end(nextrev - 1)
506
507
507 if deltainfo is None:
508 if deltainfo is None:
508 deltachainspan = _segmentspan(revlog, revs)
509 deltachainspan = _segmentspan(revlog, revs)
509 chainpayload = sum(length(r) for r in revs)
510 chainpayload = sum(length(r) for r in revs)
510 else:
511 else:
511 deltachainspan = deltainfo.distance
512 deltachainspan = deltainfo.distance
512 chainpayload = deltainfo.compresseddeltalen
513 chainpayload = deltainfo.compresseddeltalen
513
514
514 if deltachainspan < mingapsize:
515 if deltachainspan < mingapsize:
515 yield revs
516 yield revs
516 return
517 return
517
518
518 readdata = deltachainspan
519 readdata = deltachainspan
519
520
520 if deltachainspan:
521 if deltachainspan:
521 density = chainpayload / float(deltachainspan)
522 density = chainpayload / float(deltachainspan)
522 else:
523 else:
523 density = 1.0
524 density = 1.0
524
525
525 if density >= targetdensity:
526 if density >= targetdensity:
526 yield revs
527 yield revs
527 return
528 return
528
529
529 if deltainfo is not None:
530 if deltainfo is not None:
530 revs = list(revs)
531 revs = list(revs)
531 revs.append(nextrev)
532 revs.append(nextrev)
532
533
533 # Store the gaps in a heap to have them sorted by decreasing size
534 # Store the gaps in a heap to have them sorted by decreasing size
534 gapsheap = []
535 gapsheap = []
535 heapq.heapify(gapsheap)
536 heapq.heapify(gapsheap)
536 prevend = None
537 prevend = None
537 for i, rev in enumerate(revs):
538 for i, rev in enumerate(revs):
538 if rev < nextrev:
539 if rev < nextrev:
539 revstart = start(rev)
540 revstart = start(rev)
540 revlen = length(rev)
541 revlen = length(rev)
541 else:
542 else:
542 revstart = nextoffset
543 revstart = nextoffset
543 revlen = deltainfo.deltalen
544 revlen = deltainfo.deltalen
544
545
545 # Skip empty revisions to form larger holes
546 # Skip empty revisions to form larger holes
546 if revlen == 0:
547 if revlen == 0:
547 continue
548 continue
548
549
549 if prevend is not None:
550 if prevend is not None:
550 gapsize = revstart - prevend
551 gapsize = revstart - prevend
551 # only consider holes that are large enough
552 # only consider holes that are large enough
552 if gapsize > mingapsize:
553 if gapsize > mingapsize:
553 heapq.heappush(gapsheap, (-gapsize, i))
554 heapq.heappush(gapsheap, (-gapsize, i))
554
555
555 prevend = revstart + revlen
556 prevend = revstart + revlen
556
557
557 # Collect the indices of the largest holes until the density is acceptable
558 # Collect the indices of the largest holes until the density is acceptable
558 indicesheap = []
559 indicesheap = []
559 heapq.heapify(indicesheap)
560 heapq.heapify(indicesheap)
560 while gapsheap and density < targetdensity:
561 while gapsheap and density < targetdensity:
561 oppgapsize, gapidx = heapq.heappop(gapsheap)
562 oppgapsize, gapidx = heapq.heappop(gapsheap)
562
563
563 heapq.heappush(indicesheap, gapidx)
564 heapq.heappush(indicesheap, gapidx)
564
565
565 # the gap sizes are stored as negatives to be sorted decreasingly
566 # the gap sizes are stored as negatives to be sorted decreasingly
566 # by the heap
567 # by the heap
567 readdata -= (-oppgapsize)
568 readdata -= (-oppgapsize)
568 if readdata > 0:
569 if readdata > 0:
569 density = chainpayload / float(readdata)
570 density = chainpayload / float(readdata)
570 else:
571 else:
571 density = 1.0
572 density = 1.0
572
573
573 # Cut the revs at collected indices
574 # Cut the revs at collected indices
574 previdx = 0
575 previdx = 0
575 while indicesheap:
576 while indicesheap:
576 idx = heapq.heappop(indicesheap)
577 idx = heapq.heappop(indicesheap)
577
578
578 chunk = _trimchunk(revlog, revs, previdx, idx)
579 chunk = _trimchunk(revlog, revs, previdx, idx)
579 if chunk:
580 if chunk:
580 yield chunk
581 yield chunk
581
582
582 previdx = idx
583 previdx = idx
583
584
584 chunk = _trimchunk(revlog, revs, previdx)
585 chunk = _trimchunk(revlog, revs, previdx)
585 if chunk:
586 if chunk:
586 yield chunk
587 yield chunk
587
588
588 @attr.s(slots=True, frozen=True)
589 @attr.s(slots=True, frozen=True)
589 class _deltainfo(object):
590 class _deltainfo(object):
590 distance = attr.ib()
591 distance = attr.ib()
591 deltalen = attr.ib()
592 deltalen = attr.ib()
592 data = attr.ib()
593 data = attr.ib()
593 base = attr.ib()
594 base = attr.ib()
594 chainbase = attr.ib()
595 chainbase = attr.ib()
595 chainlen = attr.ib()
596 chainlen = attr.ib()
596 compresseddeltalen = attr.ib()
597 compresseddeltalen = attr.ib()
597
598
598 class _deltacomputer(object):
599 class _deltacomputer(object):
599 def __init__(self, revlog):
600 def __init__(self, revlog):
600 self.revlog = revlog
601 self.revlog = revlog
601
602
602 def _getcandidaterevs(self, p1, p2, cachedelta):
603 def _getcandidaterevs(self, p1, p2, cachedelta):
603 """
604 """
604 Provides revisions that present an interest to be diffed against,
605 Provides revisions that present an interest to be diffed against,
605 grouped by level of easiness.
606 grouped by level of easiness.
606 """
607 """
607 revlog = self.revlog
608 revlog = self.revlog
608 gdelta = revlog._generaldelta
609 gdelta = revlog._generaldelta
609 curr = len(revlog)
610 curr = len(revlog)
610 prev = curr - 1
611 prev = curr - 1
611 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
612 p1r, p2r = revlog.rev(p1), revlog.rev(p2)
612
613
613 # should we try to build a delta?
614 # should we try to build a delta?
614 if prev != nullrev and revlog.storedeltachains:
615 if prev != nullrev and revlog.storedeltachains:
615 tested = set()
616 tested = set()
616 # This condition is true most of the time when processing
617 # This condition is true most of the time when processing
617 # changegroup data into a generaldelta repo. The only time it
618 # changegroup data into a generaldelta repo. The only time it
618 # isn't true is if this is the first revision in a delta chain
619 # isn't true is if this is the first revision in a delta chain
619 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
620 # or if ``format.generaldelta=true`` disabled ``lazydeltabase``.
620 if cachedelta and gdelta and revlog._lazydeltabase:
621 if cachedelta and gdelta and revlog._lazydeltabase:
621 # Assume what we received from the server is a good choice
622 # Assume what we received from the server is a good choice
622 # build delta will reuse the cache
623 # build delta will reuse the cache
623 yield (cachedelta[0],)
624 yield (cachedelta[0],)
624 tested.add(cachedelta[0])
625 tested.add(cachedelta[0])
625
626
626 if gdelta:
627 if gdelta:
627 # exclude already lazy tested base if any
628 # exclude already lazy tested base if any
628 parents = [p for p in (p1r, p2r)
629 parents = [p for p in (p1r, p2r)
629 if p != nullrev and p not in tested]
630 if p != nullrev and p not in tested]
630
631
631 if not revlog._deltabothparents and len(parents) == 2:
632 if not revlog._deltabothparents and len(parents) == 2:
632 parents.sort()
633 parents.sort()
633 # To minimize the chance of having to build a fulltext,
634 # To minimize the chance of having to build a fulltext,
634 # pick first whichever parent is closest to us (max rev)
635 # pick first whichever parent is closest to us (max rev)
635 yield (parents[1],)
636 yield (parents[1],)
636 # then the other one (min rev) if the first did not fit
637 # then the other one (min rev) if the first did not fit
637 yield (parents[0],)
638 yield (parents[0],)
638 tested.update(parents)
639 tested.update(parents)
639 elif len(parents) > 0:
640 elif len(parents) > 0:
640 # Test all parents (1 or 2), and keep the best candidate
641 # Test all parents (1 or 2), and keep the best candidate
641 yield parents
642 yield parents
642 tested.update(parents)
643 tested.update(parents)
643
644
644 if prev not in tested:
645 if prev not in tested:
645 # other approach failed try against prev to hopefully save us a
646 # other approach failed try against prev to hopefully save us a
646 # fulltext.
647 # fulltext.
647 yield (prev,)
648 yield (prev,)
648 tested.add(prev)
649 tested.add(prev)
649
650
650 def buildtext(self, revinfo, fh):
651 def buildtext(self, revinfo, fh):
651 """Builds a fulltext version of a revision
652 """Builds a fulltext version of a revision
652
653
653 revinfo: _revisioninfo instance that contains all needed info
654 revinfo: _revisioninfo instance that contains all needed info
654 fh: file handle to either the .i or the .d revlog file,
655 fh: file handle to either the .i or the .d revlog file,
655 depending on whether it is inlined or not
656 depending on whether it is inlined or not
656 """
657 """
657 btext = revinfo.btext
658 btext = revinfo.btext
658 if btext[0] is not None:
659 if btext[0] is not None:
659 return btext[0]
660 return btext[0]
660
661
661 revlog = self.revlog
662 revlog = self.revlog
662 cachedelta = revinfo.cachedelta
663 cachedelta = revinfo.cachedelta
663 flags = revinfo.flags
664 flags = revinfo.flags
664 node = revinfo.node
665 node = revinfo.node
665
666
666 baserev = cachedelta[0]
667 baserev = cachedelta[0]
667 delta = cachedelta[1]
668 delta = cachedelta[1]
668 # special case deltas which replace entire base; no need to decode
669 # special case deltas which replace entire base; no need to decode
669 # base revision. this neatly avoids censored bases, which throw when
670 # base revision. this neatly avoids censored bases, which throw when
670 # they're decoded.
671 # they're decoded.
671 hlen = struct.calcsize(">lll")
672 hlen = struct.calcsize(">lll")
672 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
673 if delta[:hlen] == mdiff.replacediffheader(revlog.rawsize(baserev),
673 len(delta) - hlen):
674 len(delta) - hlen):
674 btext[0] = delta[hlen:]
675 btext[0] = delta[hlen:]
675 else:
676 else:
676 # deltabase is rawtext before changed by flag processors, which is
677 # deltabase is rawtext before changed by flag processors, which is
677 # equivalent to non-raw text
678 # equivalent to non-raw text
678 basetext = revlog.revision(baserev, _df=fh, raw=False)
679 basetext = revlog.revision(baserev, _df=fh, raw=False)
679 btext[0] = mdiff.patch(basetext, delta)
680 btext[0] = mdiff.patch(basetext, delta)
680
681
681 try:
682 try:
682 res = revlog._processflags(btext[0], flags, 'read', raw=True)
683 res = revlog._processflags(btext[0], flags, 'read', raw=True)
683 btext[0], validatehash = res
684 btext[0], validatehash = res
684 if validatehash:
685 if validatehash:
685 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
686 revlog.checkhash(btext[0], node, p1=revinfo.p1, p2=revinfo.p2)
686 if flags & REVIDX_ISCENSORED:
687 if flags & REVIDX_ISCENSORED:
687 raise RevlogError(_('node %s is not censored') % node)
688 raise RevlogError(_('node %s is not censored') % node)
688 except CensoredNodeError:
689 except CensoredNodeError:
689 # must pass the censored index flag to add censored revisions
690 # must pass the censored index flag to add censored revisions
690 if not flags & REVIDX_ISCENSORED:
691 if not flags & REVIDX_ISCENSORED:
691 raise
692 raise
692 return btext[0]
693 return btext[0]
693
694
694 def _builddeltadiff(self, base, revinfo, fh):
695 def _builddeltadiff(self, base, revinfo, fh):
695 revlog = self.revlog
696 revlog = self.revlog
696 t = self.buildtext(revinfo, fh)
697 t = self.buildtext(revinfo, fh)
697 if revlog.iscensored(base):
698 if revlog.iscensored(base):
698 # deltas based on a censored revision must replace the
699 # deltas based on a censored revision must replace the
699 # full content in one patch, so delta works everywhere
700 # full content in one patch, so delta works everywhere
700 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
701 header = mdiff.replacediffheader(revlog.rawsize(base), len(t))
701 delta = header + t
702 delta = header + t
702 else:
703 else:
703 ptext = revlog.revision(base, _df=fh, raw=True)
704 ptext = revlog.revision(base, _df=fh, raw=True)
704 delta = mdiff.textdiff(ptext, t)
705 delta = mdiff.textdiff(ptext, t)
705
706
706 return delta
707 return delta
707
708
708 def _builddeltainfo(self, revinfo, base, fh):
709 def _builddeltainfo(self, revinfo, base, fh):
709 # can we use the cached delta?
710 # can we use the cached delta?
710 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
711 if revinfo.cachedelta and revinfo.cachedelta[0] == base:
711 delta = revinfo.cachedelta[1]
712 delta = revinfo.cachedelta[1]
712 else:
713 else:
713 delta = self._builddeltadiff(base, revinfo, fh)
714 delta = self._builddeltadiff(base, revinfo, fh)
714 revlog = self.revlog
715 revlog = self.revlog
715 header, data = revlog.compress(delta)
716 header, data = revlog.compress(delta)
716 deltalen = len(header) + len(data)
717 deltalen = len(header) + len(data)
717 chainbase = revlog.chainbase(base)
718 chainbase = revlog.chainbase(base)
718 offset = revlog.end(len(revlog) - 1)
719 offset = revlog.end(len(revlog) - 1)
719 dist = deltalen + offset - revlog.start(chainbase)
720 dist = deltalen + offset - revlog.start(chainbase)
720 if revlog._generaldelta:
721 if revlog._generaldelta:
721 deltabase = base
722 deltabase = base
722 else:
723 else:
723 deltabase = chainbase
724 deltabase = chainbase
724 chainlen, compresseddeltalen = revlog._chaininfo(base)
725 chainlen, compresseddeltalen = revlog._chaininfo(base)
725 chainlen += 1
726 chainlen += 1
726 compresseddeltalen += deltalen
727 compresseddeltalen += deltalen
727 return _deltainfo(dist, deltalen, (header, data), deltabase,
728 return _deltainfo(dist, deltalen, (header, data), deltabase,
728 chainbase, chainlen, compresseddeltalen)
729 chainbase, chainlen, compresseddeltalen)
729
730
730 def finddeltainfo(self, revinfo, fh):
731 def finddeltainfo(self, revinfo, fh):
731 """Find an acceptable delta against a candidate revision
732 """Find an acceptable delta against a candidate revision
732
733
733 revinfo: information about the revision (instance of _revisioninfo)
734 revinfo: information about the revision (instance of _revisioninfo)
734 fh: file handle to either the .i or the .d revlog file,
735 fh: file handle to either the .i or the .d revlog file,
735 depending on whether it is inlined or not
736 depending on whether it is inlined or not
736
737
737 Returns the first acceptable candidate revision, as ordered by
738 Returns the first acceptable candidate revision, as ordered by
738 _getcandidaterevs
739 _getcandidaterevs
739 """
740 """
740 cachedelta = revinfo.cachedelta
741 cachedelta = revinfo.cachedelta
741 p1 = revinfo.p1
742 p1 = revinfo.p1
742 p2 = revinfo.p2
743 p2 = revinfo.p2
743 revlog = self.revlog
744 revlog = self.revlog
744
745
745 deltainfo = None
746 deltainfo = None
746 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
747 for candidaterevs in self._getcandidaterevs(p1, p2, cachedelta):
747 nominateddeltas = []
748 nominateddeltas = []
748 for candidaterev in candidaterevs:
749 for candidaterev in candidaterevs:
749 # no delta for rawtext-changing revs (see "candelta" for why)
750 # no delta for rawtext-changing revs (see "candelta" for why)
750 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
751 if revlog.flags(candidaterev) & REVIDX_RAWTEXT_CHANGING_FLAGS:
751 continue
752 continue
752 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
753 candidatedelta = self._builddeltainfo(revinfo, candidaterev, fh)
753 if revlog._isgooddeltainfo(candidatedelta, revinfo):
754 if revlog._isgooddeltainfo(candidatedelta, revinfo):
754 nominateddeltas.append(candidatedelta)
755 nominateddeltas.append(candidatedelta)
755 if nominateddeltas:
756 if nominateddeltas:
756 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
757 deltainfo = min(nominateddeltas, key=lambda x: x.deltalen)
757 break
758 break
758
759
759 return deltainfo
760 return deltainfo
760
761
761 @attr.s(slots=True, frozen=True)
762 @attr.s(slots=True, frozen=True)
762 class _revisioninfo(object):
763 class _revisioninfo(object):
763 """Information about a revision that allows building its fulltext
764 """Information about a revision that allows building its fulltext
764 node: expected hash of the revision
765 node: expected hash of the revision
765 p1, p2: parent revs of the revision
766 p1, p2: parent revs of the revision
766 btext: built text cache consisting of a one-element list
767 btext: built text cache consisting of a one-element list
767 cachedelta: (baserev, uncompressed_delta) or None
768 cachedelta: (baserev, uncompressed_delta) or None
768 flags: flags associated to the revision storage
769 flags: flags associated to the revision storage
769
770
770 One of btext[0] or cachedelta must be set.
771 One of btext[0] or cachedelta must be set.
771 """
772 """
772 node = attr.ib()
773 node = attr.ib()
773 p1 = attr.ib()
774 p1 = attr.ib()
774 p2 = attr.ib()
775 p2 = attr.ib()
775 btext = attr.ib()
776 btext = attr.ib()
776 textlen = attr.ib()
777 textlen = attr.ib()
777 cachedelta = attr.ib()
778 cachedelta = attr.ib()
778 flags = attr.ib()
779 flags = attr.ib()
779
780
780 # index v0:
781 # index v0:
781 # 4 bytes: offset
782 # 4 bytes: offset
782 # 4 bytes: compressed length
783 # 4 bytes: compressed length
783 # 4 bytes: base rev
784 # 4 bytes: base rev
784 # 4 bytes: link rev
785 # 4 bytes: link rev
785 # 20 bytes: parent 1 nodeid
786 # 20 bytes: parent 1 nodeid
786 # 20 bytes: parent 2 nodeid
787 # 20 bytes: parent 2 nodeid
787 # 20 bytes: nodeid
788 # 20 bytes: nodeid
788 indexformatv0 = struct.Struct(">4l20s20s20s")
789 indexformatv0 = struct.Struct(">4l20s20s20s")
789 indexformatv0_pack = indexformatv0.pack
790 indexformatv0_pack = indexformatv0.pack
790 indexformatv0_unpack = indexformatv0.unpack
791 indexformatv0_unpack = indexformatv0.unpack
791
792
792 class revlogoldio(object):
793 class revlogoldio(object):
793 def __init__(self):
794 def __init__(self):
794 self.size = indexformatv0.size
795 self.size = indexformatv0.size
795
796
796 def parseindex(self, data, inline):
797 def parseindex(self, data, inline):
797 s = self.size
798 s = self.size
798 index = []
799 index = []
799 nodemap = {nullid: nullrev}
800 nodemap = {nullid: nullrev}
800 n = off = 0
801 n = off = 0
801 l = len(data)
802 l = len(data)
802 while off + s <= l:
803 while off + s <= l:
803 cur = data[off:off + s]
804 cur = data[off:off + s]
804 off += s
805 off += s
805 e = indexformatv0_unpack(cur)
806 e = indexformatv0_unpack(cur)
806 # transform to revlogv1 format
807 # transform to revlogv1 format
807 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
808 e2 = (offset_type(e[0], 0), e[1], -1, e[2], e[3],
808 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
809 nodemap.get(e[4], nullrev), nodemap.get(e[5], nullrev), e[6])
809 index.append(e2)
810 index.append(e2)
810 nodemap[e[6]] = n
811 nodemap[e[6]] = n
811 n += 1
812 n += 1
812
813
813 # add the magic null revision at -1
814 # add the magic null revision at -1
814 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
815 index.append((0, 0, 0, -1, -1, -1, -1, nullid))
815
816
816 return index, nodemap, None
817 return index, nodemap, None
817
818
818 def packentry(self, entry, node, version, rev):
819 def packentry(self, entry, node, version, rev):
819 if gettype(entry[0]):
820 if gettype(entry[0]):
820 raise RevlogError(_('index entry flags need revlog version 1'))
821 raise RevlogError(_('index entry flags need revlog version 1'))
821 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
822 e2 = (getoffset(entry[0]), entry[1], entry[3], entry[4],
822 node(entry[5]), node(entry[6]), entry[7])
823 node(entry[5]), node(entry[6]), entry[7])
823 return indexformatv0_pack(*e2)
824 return indexformatv0_pack(*e2)
824
825
825 # index ng:
826 # index ng:
826 # 6 bytes: offset
827 # 6 bytes: offset
827 # 2 bytes: flags
828 # 2 bytes: flags
828 # 4 bytes: compressed length
829 # 4 bytes: compressed length
829 # 4 bytes: uncompressed length
830 # 4 bytes: uncompressed length
830 # 4 bytes: base rev
831 # 4 bytes: base rev
831 # 4 bytes: link rev
832 # 4 bytes: link rev
832 # 4 bytes: parent 1 rev
833 # 4 bytes: parent 1 rev
833 # 4 bytes: parent 2 rev
834 # 4 bytes: parent 2 rev
834 # 32 bytes: nodeid
835 # 32 bytes: nodeid
835 indexformatng = struct.Struct(">Qiiiiii20s12x")
836 indexformatng = struct.Struct(">Qiiiiii20s12x")
836 indexformatng_pack = indexformatng.pack
837 indexformatng_pack = indexformatng.pack
837 versionformat = struct.Struct(">I")
838 versionformat = struct.Struct(">I")
838 versionformat_pack = versionformat.pack
839 versionformat_pack = versionformat.pack
839 versionformat_unpack = versionformat.unpack
840 versionformat_unpack = versionformat.unpack
840
841
841 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
842 # corresponds to uncompressed length of indexformatng (2 gigs, 4-byte
842 # signed integer)
843 # signed integer)
843 _maxentrysize = 0x7fffffff
844 _maxentrysize = 0x7fffffff
844
845
845 class revlogio(object):
846 class revlogio(object):
846 def __init__(self):
847 def __init__(self):
847 self.size = indexformatng.size
848 self.size = indexformatng.size
848
849
849 def parseindex(self, data, inline):
850 def parseindex(self, data, inline):
850 # call the C implementation to parse the index data
851 # call the C implementation to parse the index data
851 index, cache = parsers.parse_index2(data, inline)
852 index, cache = parsers.parse_index2(data, inline)
852 return index, getattr(index, 'nodemap', None), cache
853 return index, getattr(index, 'nodemap', None), cache
853
854
854 def packentry(self, entry, node, version, rev):
855 def packentry(self, entry, node, version, rev):
855 p = indexformatng_pack(*entry)
856 p = indexformatng_pack(*entry)
856 if rev == 0:
857 if rev == 0:
857 p = versionformat_pack(version) + p[4:]
858 p = versionformat_pack(version) + p[4:]
858 return p
859 return p
859
860
860 class revlog(object):
861 class revlog(object):
861 """
862 """
862 the underlying revision storage object
863 the underlying revision storage object
863
864
864 A revlog consists of two parts, an index and the revision data.
865 A revlog consists of two parts, an index and the revision data.
865
866
866 The index is a file with a fixed record size containing
867 The index is a file with a fixed record size containing
867 information on each revision, including its nodeid (hash), the
868 information on each revision, including its nodeid (hash), the
868 nodeids of its parents, the position and offset of its data within
869 nodeids of its parents, the position and offset of its data within
869 the data file, and the revision it's based on. Finally, each entry
870 the data file, and the revision it's based on. Finally, each entry
870 contains a linkrev entry that can serve as a pointer to external
871 contains a linkrev entry that can serve as a pointer to external
871 data.
872 data.
872
873
873 The revision data itself is a linear collection of data chunks.
874 The revision data itself is a linear collection of data chunks.
874 Each chunk represents a revision and is usually represented as a
875 Each chunk represents a revision and is usually represented as a
875 delta against the previous chunk. To bound lookup time, runs of
876 delta against the previous chunk. To bound lookup time, runs of
876 deltas are limited to about 2 times the length of the original
877 deltas are limited to about 2 times the length of the original
877 version data. This makes retrieval of a version proportional to
878 version data. This makes retrieval of a version proportional to
878 its size, or O(1) relative to the number of revisions.
879 its size, or O(1) relative to the number of revisions.
879
880
880 Both pieces of the revlog are written to in an append-only
881 Both pieces of the revlog are written to in an append-only
881 fashion, which means we never need to rewrite a file to insert or
882 fashion, which means we never need to rewrite a file to insert or
882 remove data, and can use some simple techniques to avoid the need
883 remove data, and can use some simple techniques to avoid the need
883 for locking while reading.
884 for locking while reading.
884
885
885 If checkambig, indexfile is opened with checkambig=True at
886 If checkambig, indexfile is opened with checkambig=True at
886 writing, to avoid file stat ambiguity.
887 writing, to avoid file stat ambiguity.
887
888
888 If mmaplargeindex is True, and an mmapindexthreshold is set, the
889 If mmaplargeindex is True, and an mmapindexthreshold is set, the
889 index will be mmapped rather than read if it is larger than the
890 index will be mmapped rather than read if it is larger than the
890 configured threshold.
891 configured threshold.
891
892
892 If censorable is True, the revlog can have censored revisions.
893 If censorable is True, the revlog can have censored revisions.
893 """
894 """
894 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
895 def __init__(self, opener, indexfile, datafile=None, checkambig=False,
895 mmaplargeindex=False, censorable=False):
896 mmaplargeindex=False, censorable=False):
896 """
897 """
897 create a revlog object
898 create a revlog object
898
899
899 opener is a function that abstracts the file opening operation
900 opener is a function that abstracts the file opening operation
900 and can be used to implement COW semantics or the like.
901 and can be used to implement COW semantics or the like.
901 """
902 """
902 self.indexfile = indexfile
903 self.indexfile = indexfile
903 self.datafile = datafile or (indexfile[:-2] + ".d")
904 self.datafile = datafile or (indexfile[:-2] + ".d")
904 self.opener = opener
905 self.opener = opener
905 # When True, indexfile is opened with checkambig=True at writing, to
906 # When True, indexfile is opened with checkambig=True at writing, to
906 # avoid file stat ambiguity.
907 # avoid file stat ambiguity.
907 self._checkambig = checkambig
908 self._checkambig = checkambig
908 self._censorable = censorable
909 self._censorable = censorable
909 # 3-tuple of (node, rev, text) for a raw revision.
910 # 3-tuple of (node, rev, text) for a raw revision.
910 self._cache = None
911 self._cache = None
911 # Maps rev to chain base rev.
912 # Maps rev to chain base rev.
912 self._chainbasecache = util.lrucachedict(100)
913 self._chainbasecache = util.lrucachedict(100)
913 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
914 # 2-tuple of (offset, data) of raw data from the revlog at an offset.
914 self._chunkcache = (0, '')
915 self._chunkcache = (0, '')
915 # How much data to read and cache into the raw revlog data cache.
916 # How much data to read and cache into the raw revlog data cache.
916 self._chunkcachesize = 65536
917 self._chunkcachesize = 65536
917 self._maxchainlen = None
918 self._maxchainlen = None
918 self._deltabothparents = True
919 self._deltabothparents = True
919 self.index = []
920 self.index = []
920 # Mapping of partial identifiers to full nodes.
921 # Mapping of partial identifiers to full nodes.
921 self._pcache = {}
922 self._pcache = {}
922 # Mapping of revision integer to full node.
923 # Mapping of revision integer to full node.
923 self._nodecache = {nullid: nullrev}
924 self._nodecache = {nullid: nullrev}
924 self._nodepos = None
925 self._nodepos = None
925 self._compengine = 'zlib'
926 self._compengine = 'zlib'
926 self._maxdeltachainspan = -1
927 self._maxdeltachainspan = -1
927 self._withsparseread = False
928 self._withsparseread = False
928 self._sparserevlog = False
929 self._sparserevlog = False
929 self._srdensitythreshold = 0.50
930 self._srdensitythreshold = 0.50
930 self._srmingapsize = 262144
931 self._srmingapsize = 262144
931
932
932 mmapindexthreshold = None
933 mmapindexthreshold = None
933 v = REVLOG_DEFAULT_VERSION
934 v = REVLOG_DEFAULT_VERSION
934 opts = getattr(opener, 'options', None)
935 opts = getattr(opener, 'options', None)
935 if opts is not None:
936 if opts is not None:
936 if 'revlogv2' in opts:
937 if 'revlogv2' in opts:
937 # version 2 revlogs always use generaldelta.
938 # version 2 revlogs always use generaldelta.
938 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
939 v = REVLOGV2 | FLAG_GENERALDELTA | FLAG_INLINE_DATA
939 elif 'revlogv1' in opts:
940 elif 'revlogv1' in opts:
940 if 'generaldelta' in opts:
941 if 'generaldelta' in opts:
941 v |= FLAG_GENERALDELTA
942 v |= FLAG_GENERALDELTA
942 else:
943 else:
943 v = 0
944 v = 0
944 if 'chunkcachesize' in opts:
945 if 'chunkcachesize' in opts:
945 self._chunkcachesize = opts['chunkcachesize']
946 self._chunkcachesize = opts['chunkcachesize']
946 if 'maxchainlen' in opts:
947 if 'maxchainlen' in opts:
947 self._maxchainlen = opts['maxchainlen']
948 self._maxchainlen = opts['maxchainlen']
948 if 'deltabothparents' in opts:
949 if 'deltabothparents' in opts:
949 self._deltabothparents = opts['deltabothparents']
950 self._deltabothparents = opts['deltabothparents']
950 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
951 self._lazydeltabase = bool(opts.get('lazydeltabase', False))
951 if 'compengine' in opts:
952 if 'compengine' in opts:
952 self._compengine = opts['compengine']
953 self._compengine = opts['compengine']
953 if 'maxdeltachainspan' in opts:
954 if 'maxdeltachainspan' in opts:
954 self._maxdeltachainspan = opts['maxdeltachainspan']
955 self._maxdeltachainspan = opts['maxdeltachainspan']
955 if mmaplargeindex and 'mmapindexthreshold' in opts:
956 if mmaplargeindex and 'mmapindexthreshold' in opts:
956 mmapindexthreshold = opts['mmapindexthreshold']
957 mmapindexthreshold = opts['mmapindexthreshold']
957 self._sparserevlog = bool(opts.get('sparse-revlog', False))
958 self._sparserevlog = bool(opts.get('sparse-revlog', False))
958 withsparseread = bool(opts.get('with-sparse-read', False))
959 withsparseread = bool(opts.get('with-sparse-read', False))
959 # sparse-revlog forces sparse-read
960 # sparse-revlog forces sparse-read
960 self._withsparseread = self._sparserevlog or withsparseread
961 self._withsparseread = self._sparserevlog or withsparseread
961 if 'sparse-read-density-threshold' in opts:
962 if 'sparse-read-density-threshold' in opts:
962 self._srdensitythreshold = opts['sparse-read-density-threshold']
963 self._srdensitythreshold = opts['sparse-read-density-threshold']
963 if 'sparse-read-min-gap-size' in opts:
964 if 'sparse-read-min-gap-size' in opts:
964 self._srmingapsize = opts['sparse-read-min-gap-size']
965 self._srmingapsize = opts['sparse-read-min-gap-size']
965
966
966 if self._chunkcachesize <= 0:
967 if self._chunkcachesize <= 0:
967 raise RevlogError(_('revlog chunk cache size %r is not greater '
968 raise RevlogError(_('revlog chunk cache size %r is not greater '
968 'than 0') % self._chunkcachesize)
969 'than 0') % self._chunkcachesize)
969 elif self._chunkcachesize & (self._chunkcachesize - 1):
970 elif self._chunkcachesize & (self._chunkcachesize - 1):
970 raise RevlogError(_('revlog chunk cache size %r is not a power '
971 raise RevlogError(_('revlog chunk cache size %r is not a power '
971 'of 2') % self._chunkcachesize)
972 'of 2') % self._chunkcachesize)
972
973
973 indexdata = ''
974 indexdata = ''
974 self._initempty = True
975 self._initempty = True
975 try:
976 try:
976 with self._indexfp() as f:
977 with self._indexfp() as f:
977 if (mmapindexthreshold is not None and
978 if (mmapindexthreshold is not None and
978 self.opener.fstat(f).st_size >= mmapindexthreshold):
979 self.opener.fstat(f).st_size >= mmapindexthreshold):
979 indexdata = util.buffer(util.mmapread(f))
980 indexdata = util.buffer(util.mmapread(f))
980 else:
981 else:
981 indexdata = f.read()
982 indexdata = f.read()
982 if len(indexdata) > 0:
983 if len(indexdata) > 0:
983 v = versionformat_unpack(indexdata[:4])[0]
984 v = versionformat_unpack(indexdata[:4])[0]
984 self._initempty = False
985 self._initempty = False
985 except IOError as inst:
986 except IOError as inst:
986 if inst.errno != errno.ENOENT:
987 if inst.errno != errno.ENOENT:
987 raise
988 raise
988
989
989 self.version = v
990 self.version = v
990 self._inline = v & FLAG_INLINE_DATA
991 self._inline = v & FLAG_INLINE_DATA
991 self._generaldelta = v & FLAG_GENERALDELTA
992 self._generaldelta = v & FLAG_GENERALDELTA
992 flags = v & ~0xFFFF
993 flags = v & ~0xFFFF
993 fmt = v & 0xFFFF
994 fmt = v & 0xFFFF
994 if fmt == REVLOGV0:
995 if fmt == REVLOGV0:
995 if flags:
996 if flags:
996 raise RevlogError(_('unknown flags (%#04x) in version %d '
997 raise RevlogError(_('unknown flags (%#04x) in version %d '
997 'revlog %s') %
998 'revlog %s') %
998 (flags >> 16, fmt, self.indexfile))
999 (flags >> 16, fmt, self.indexfile))
999 elif fmt == REVLOGV1:
1000 elif fmt == REVLOGV1:
1000 if flags & ~REVLOGV1_FLAGS:
1001 if flags & ~REVLOGV1_FLAGS:
1001 raise RevlogError(_('unknown flags (%#04x) in version %d '
1002 raise RevlogError(_('unknown flags (%#04x) in version %d '
1002 'revlog %s') %
1003 'revlog %s') %
1003 (flags >> 16, fmt, self.indexfile))
1004 (flags >> 16, fmt, self.indexfile))
1004 elif fmt == REVLOGV2:
1005 elif fmt == REVLOGV2:
1005 if flags & ~REVLOGV2_FLAGS:
1006 if flags & ~REVLOGV2_FLAGS:
1006 raise RevlogError(_('unknown flags (%#04x) in version %d '
1007 raise RevlogError(_('unknown flags (%#04x) in version %d '
1007 'revlog %s') %
1008 'revlog %s') %
1008 (flags >> 16, fmt, self.indexfile))
1009 (flags >> 16, fmt, self.indexfile))
1009 else:
1010 else:
1010 raise RevlogError(_('unknown version (%d) in revlog %s') %
1011 raise RevlogError(_('unknown version (%d) in revlog %s') %
1011 (fmt, self.indexfile))
1012 (fmt, self.indexfile))
1012
1013
1013 self.storedeltachains = True
1014 self.storedeltachains = True
1014
1015
1015 self._io = revlogio()
1016 self._io = revlogio()
1016 if self.version == REVLOGV0:
1017 if self.version == REVLOGV0:
1017 self._io = revlogoldio()
1018 self._io = revlogoldio()
1018 try:
1019 try:
1019 d = self._io.parseindex(indexdata, self._inline)
1020 d = self._io.parseindex(indexdata, self._inline)
1020 except (ValueError, IndexError):
1021 except (ValueError, IndexError):
1021 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
1022 raise RevlogError(_("index %s is corrupted") % (self.indexfile))
1022 self.index, nodemap, self._chunkcache = d
1023 self.index, nodemap, self._chunkcache = d
1023 if nodemap is not None:
1024 if nodemap is not None:
1024 self.nodemap = self._nodecache = nodemap
1025 self.nodemap = self._nodecache = nodemap
1025 if not self._chunkcache:
1026 if not self._chunkcache:
1026 self._chunkclear()
1027 self._chunkclear()
1027 # revnum -> (chain-length, sum-delta-length)
1028 # revnum -> (chain-length, sum-delta-length)
1028 self._chaininfocache = {}
1029 self._chaininfocache = {}
1029 # revlog header -> revlog compressor
1030 # revlog header -> revlog compressor
1030 self._decompressors = {}
1031 self._decompressors = {}
1031
1032
1032 @util.propertycache
1033 @util.propertycache
1033 def _compressor(self):
1034 def _compressor(self):
1034 return util.compengines[self._compengine].revlogcompressor()
1035 return util.compengines[self._compengine].revlogcompressor()
1035
1036
1036 def _indexfp(self, mode='r'):
1037 def _indexfp(self, mode='r'):
1037 """file object for the revlog's index file"""
1038 """file object for the revlog's index file"""
1038 args = {r'mode': mode}
1039 args = {r'mode': mode}
1039 if mode != 'r':
1040 if mode != 'r':
1040 args[r'checkambig'] = self._checkambig
1041 args[r'checkambig'] = self._checkambig
1041 if mode == 'w':
1042 if mode == 'w':
1042 args[r'atomictemp'] = True
1043 args[r'atomictemp'] = True
1043 return self.opener(self.indexfile, **args)
1044 return self.opener(self.indexfile, **args)
1044
1045
1045 def _datafp(self, mode='r'):
1046 def _datafp(self, mode='r'):
1046 """file object for the revlog's data file"""
1047 """file object for the revlog's data file"""
1047 return self.opener(self.datafile, mode=mode)
1048 return self.opener(self.datafile, mode=mode)
1048
1049
1049 @contextlib.contextmanager
1050 @contextlib.contextmanager
1050 def _datareadfp(self, existingfp=None):
1051 def _datareadfp(self, existingfp=None):
1051 """file object suitable to read data"""
1052 """file object suitable to read data"""
1052 if existingfp is not None:
1053 if existingfp is not None:
1053 yield existingfp
1054 yield existingfp
1054 else:
1055 else:
1055 if self._inline:
1056 if self._inline:
1056 func = self._indexfp
1057 func = self._indexfp
1057 else:
1058 else:
1058 func = self._datafp
1059 func = self._datafp
1059 with func() as fp:
1060 with func() as fp:
1060 yield fp
1061 yield fp
1061
1062
1062 def tip(self):
1063 def tip(self):
1063 return self.node(len(self.index) - 2)
1064 return self.node(len(self.index) - 2)
1064 def __contains__(self, rev):
1065 def __contains__(self, rev):
1065 return 0 <= rev < len(self)
1066 return 0 <= rev < len(self)
1066 def __len__(self):
1067 def __len__(self):
1067 return len(self.index) - 1
1068 return len(self.index) - 1
1068 def __iter__(self):
1069 def __iter__(self):
1069 return iter(pycompat.xrange(len(self)))
1070 return iter(pycompat.xrange(len(self)))
1070 def revs(self, start=0, stop=None):
1071 def revs(self, start=0, stop=None):
1071 """iterate over all rev in this revlog (from start to stop)"""
1072 """iterate over all rev in this revlog (from start to stop)"""
1072 step = 1
1073 step = 1
1073 length = len(self)
1074 length = len(self)
1074 if stop is not None:
1075 if stop is not None:
1075 if start > stop:
1076 if start > stop:
1076 step = -1
1077 step = -1
1077 stop += step
1078 stop += step
1078 if stop > length:
1079 if stop > length:
1079 stop = length
1080 stop = length
1080 else:
1081 else:
1081 stop = length
1082 stop = length
1082 return pycompat.xrange(start, stop, step)
1083 return pycompat.xrange(start, stop, step)
1083
1084
1084 @util.propertycache
1085 @util.propertycache
1085 def nodemap(self):
1086 def nodemap(self):
1086 self.rev(self.node(0))
1087 self.rev(self.node(0))
1087 return self._nodecache
1088 return self._nodecache
1088
1089
1089 def hasnode(self, node):
1090 def hasnode(self, node):
1090 try:
1091 try:
1091 self.rev(node)
1092 self.rev(node)
1092 return True
1093 return True
1093 except KeyError:
1094 except KeyError:
1094 return False
1095 return False
1095
1096
1096 def candelta(self, baserev, rev):
1097 def candelta(self, baserev, rev):
1097 """whether two revisions (baserev, rev) can be delta-ed or not"""
1098 """whether two revisions (baserev, rev) can be delta-ed or not"""
1098 # Disable delta if either rev requires a content-changing flag
1099 # Disable delta if either rev requires a content-changing flag
1099 # processor (ex. LFS). This is because such flag processor can alter
1100 # processor (ex. LFS). This is because such flag processor can alter
1100 # the rawtext content that the delta will be based on, and two clients
1101 # the rawtext content that the delta will be based on, and two clients
1101 # could have a same revlog node with different flags (i.e. different
1102 # could have a same revlog node with different flags (i.e. different
1102 # rawtext contents) and the delta could be incompatible.
1103 # rawtext contents) and the delta could be incompatible.
1103 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
1104 if ((self.flags(baserev) & REVIDX_RAWTEXT_CHANGING_FLAGS)
1104 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
1105 or (self.flags(rev) & REVIDX_RAWTEXT_CHANGING_FLAGS)):
1105 return False
1106 return False
1106 return True
1107 return True
1107
1108
1108 def clearcaches(self):
1109 def clearcaches(self):
1109 self._cache = None
1110 self._cache = None
1110 self._chainbasecache.clear()
1111 self._chainbasecache.clear()
1111 self._chunkcache = (0, '')
1112 self._chunkcache = (0, '')
1112 self._pcache = {}
1113 self._pcache = {}
1113
1114
1114 try:
1115 try:
1115 self._nodecache.clearcaches()
1116 self._nodecache.clearcaches()
1116 except AttributeError:
1117 except AttributeError:
1117 self._nodecache = {nullid: nullrev}
1118 self._nodecache = {nullid: nullrev}
1118 self._nodepos = None
1119 self._nodepos = None
1119
1120
1120 def rev(self, node):
1121 def rev(self, node):
1121 try:
1122 try:
1122 return self._nodecache[node]
1123 return self._nodecache[node]
1123 except TypeError:
1124 except TypeError:
1124 raise
1125 raise
1125 except RevlogError:
1126 except RevlogError:
1126 # parsers.c radix tree lookup failed
1127 # parsers.c radix tree lookup failed
1127 if node == wdirid or node in wdirfilenodeids:
1128 if node == wdirid or node in wdirfilenodeids:
1128 raise error.WdirUnsupported
1129 raise error.WdirUnsupported
1129 raise LookupError(node, self.indexfile, _('no node'))
1130 raise LookupError(node, self.indexfile, _('no node'))
1130 except KeyError:
1131 except KeyError:
1131 # pure python cache lookup failed
1132 # pure python cache lookup failed
1132 n = self._nodecache
1133 n = self._nodecache
1133 i = self.index
1134 i = self.index
1134 p = self._nodepos
1135 p = self._nodepos
1135 if p is None:
1136 if p is None:
1136 p = len(i) - 2
1137 p = len(i) - 2
1137 else:
1138 else:
1138 assert p < len(i)
1139 assert p < len(i)
1139 for r in pycompat.xrange(p, -1, -1):
1140 for r in pycompat.xrange(p, -1, -1):
1140 v = i[r][7]
1141 v = i[r][7]
1141 n[v] = r
1142 n[v] = r
1142 if v == node:
1143 if v == node:
1143 self._nodepos = r - 1
1144 self._nodepos = r - 1
1144 return r
1145 return r
1145 if node == wdirid or node in wdirfilenodeids:
1146 if node == wdirid or node in wdirfilenodeids:
1146 raise error.WdirUnsupported
1147 raise error.WdirUnsupported
1147 raise LookupError(node, self.indexfile, _('no node'))
1148 raise LookupError(node, self.indexfile, _('no node'))
1148
1149
1149 # Accessors for index entries.
1150 # Accessors for index entries.
1150
1151
1151 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1152 # First tuple entry is 8 bytes. First 6 bytes are offset. Last 2 bytes
1152 # are flags.
1153 # are flags.
1153 def start(self, rev):
1154 def start(self, rev):
1154 return int(self.index[rev][0] >> 16)
1155 return int(self.index[rev][0] >> 16)
1155
1156
1156 def flags(self, rev):
1157 def flags(self, rev):
1157 return self.index[rev][0] & 0xFFFF
1158 return self.index[rev][0] & 0xFFFF
1158
1159
1159 def length(self, rev):
1160 def length(self, rev):
1160 return self.index[rev][1]
1161 return self.index[rev][1]
1161
1162
1162 def rawsize(self, rev):
1163 def rawsize(self, rev):
1163 """return the length of the uncompressed text for a given revision"""
1164 """return the length of the uncompressed text for a given revision"""
1164 l = self.index[rev][2]
1165 l = self.index[rev][2]
1165 if l >= 0:
1166 if l >= 0:
1166 return l
1167 return l
1167
1168
1168 t = self.revision(rev, raw=True)
1169 t = self.revision(rev, raw=True)
1169 return len(t)
1170 return len(t)
1170
1171
1171 def size(self, rev):
1172 def size(self, rev):
1172 """length of non-raw text (processed by a "read" flag processor)"""
1173 """length of non-raw text (processed by a "read" flag processor)"""
1173 # fast path: if no "read" flag processor could change the content,
1174 # fast path: if no "read" flag processor could change the content,
1174 # size is rawsize. note: ELLIPSIS is known to not change the content.
1175 # size is rawsize. note: ELLIPSIS is known to not change the content.
1175 flags = self.flags(rev)
1176 flags = self.flags(rev)
1176 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1177 if flags & (REVIDX_KNOWN_FLAGS ^ REVIDX_ELLIPSIS) == 0:
1177 return self.rawsize(rev)
1178 return self.rawsize(rev)
1178
1179
1179 return len(self.revision(rev, raw=False))
1180 return len(self.revision(rev, raw=False))
1180
1181
1181 def chainbase(self, rev):
1182 def chainbase(self, rev):
1182 base = self._chainbasecache.get(rev)
1183 base = self._chainbasecache.get(rev)
1183 if base is not None:
1184 if base is not None:
1184 return base
1185 return base
1185
1186
1186 index = self.index
1187 index = self.index
1187 iterrev = rev
1188 iterrev = rev
1188 base = index[iterrev][3]
1189 base = index[iterrev][3]
1189 while base != iterrev:
1190 while base != iterrev:
1190 iterrev = base
1191 iterrev = base
1191 base = index[iterrev][3]
1192 base = index[iterrev][3]
1192
1193
1193 self._chainbasecache[rev] = base
1194 self._chainbasecache[rev] = base
1194 return base
1195 return base
1195
1196
1196 def linkrev(self, rev):
1197 def linkrev(self, rev):
1197 return self.index[rev][4]
1198 return self.index[rev][4]
1198
1199
1199 def parentrevs(self, rev):
1200 def parentrevs(self, rev):
1200 try:
1201 try:
1201 entry = self.index[rev]
1202 entry = self.index[rev]
1202 except IndexError:
1203 except IndexError:
1203 if rev == wdirrev:
1204 if rev == wdirrev:
1204 raise error.WdirUnsupported
1205 raise error.WdirUnsupported
1205 raise
1206 raise
1206
1207
1207 return entry[5], entry[6]
1208 return entry[5], entry[6]
1208
1209
1209 def node(self, rev):
1210 def node(self, rev):
1210 try:
1211 try:
1211 return self.index[rev][7]
1212 return self.index[rev][7]
1212 except IndexError:
1213 except IndexError:
1213 if rev == wdirrev:
1214 if rev == wdirrev:
1214 raise error.WdirUnsupported
1215 raise error.WdirUnsupported
1215 raise
1216 raise
1216
1217
1217 # Derived from index values.
1218 # Derived from index values.
1218
1219
1219 def end(self, rev):
1220 def end(self, rev):
1220 return self.start(rev) + self.length(rev)
1221 return self.start(rev) + self.length(rev)
1221
1222
1222 def parents(self, node):
1223 def parents(self, node):
1223 i = self.index
1224 i = self.index
1224 d = i[self.rev(node)]
1225 d = i[self.rev(node)]
1225 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
1226 return i[d[5]][7], i[d[6]][7] # map revisions to nodes inline
1226
1227
1227 def chainlen(self, rev):
1228 def chainlen(self, rev):
1228 return self._chaininfo(rev)[0]
1229 return self._chaininfo(rev)[0]
1229
1230
1230 def _chaininfo(self, rev):
1231 def _chaininfo(self, rev):
1231 chaininfocache = self._chaininfocache
1232 chaininfocache = self._chaininfocache
1232 if rev in chaininfocache:
1233 if rev in chaininfocache:
1233 return chaininfocache[rev]
1234 return chaininfocache[rev]
1234 index = self.index
1235 index = self.index
1235 generaldelta = self._generaldelta
1236 generaldelta = self._generaldelta
1236 iterrev = rev
1237 iterrev = rev
1237 e = index[iterrev]
1238 e = index[iterrev]
1238 clen = 0
1239 clen = 0
1239 compresseddeltalen = 0
1240 compresseddeltalen = 0
1240 while iterrev != e[3]:
1241 while iterrev != e[3]:
1241 clen += 1
1242 clen += 1
1242 compresseddeltalen += e[1]
1243 compresseddeltalen += e[1]
1243 if generaldelta:
1244 if generaldelta:
1244 iterrev = e[3]
1245 iterrev = e[3]
1245 else:
1246 else:
1246 iterrev -= 1
1247 iterrev -= 1
1247 if iterrev in chaininfocache:
1248 if iterrev in chaininfocache:
1248 t = chaininfocache[iterrev]
1249 t = chaininfocache[iterrev]
1249 clen += t[0]
1250 clen += t[0]
1250 compresseddeltalen += t[1]
1251 compresseddeltalen += t[1]
1251 break
1252 break
1252 e = index[iterrev]
1253 e = index[iterrev]
1253 else:
1254 else:
1254 # Add text length of base since decompressing that also takes
1255 # Add text length of base since decompressing that also takes
1255 # work. For cache hits the length is already included.
1256 # work. For cache hits the length is already included.
1256 compresseddeltalen += e[1]
1257 compresseddeltalen += e[1]
1257 r = (clen, compresseddeltalen)
1258 r = (clen, compresseddeltalen)
1258 chaininfocache[rev] = r
1259 chaininfocache[rev] = r
1259 return r
1260 return r
1260
1261
1261 def _deltachain(self, rev, stoprev=None):
1262 def _deltachain(self, rev, stoprev=None):
1262 """Obtain the delta chain for a revision.
1263 """Obtain the delta chain for a revision.
1263
1264
1264 ``stoprev`` specifies a revision to stop at. If not specified, we
1265 ``stoprev`` specifies a revision to stop at. If not specified, we
1265 stop at the base of the chain.
1266 stop at the base of the chain.
1266
1267
1267 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1268 Returns a 2-tuple of (chain, stopped) where ``chain`` is a list of
1268 revs in ascending order and ``stopped`` is a bool indicating whether
1269 revs in ascending order and ``stopped`` is a bool indicating whether
1269 ``stoprev`` was hit.
1270 ``stoprev`` was hit.
1270 """
1271 """
1271 # Try C implementation.
1272 # Try C implementation.
1272 try:
1273 try:
1273 return self.index.deltachain(rev, stoprev, self._generaldelta)
1274 return self.index.deltachain(rev, stoprev, self._generaldelta)
1274 except AttributeError:
1275 except AttributeError:
1275 pass
1276 pass
1276
1277
1277 chain = []
1278 chain = []
1278
1279
1279 # Alias to prevent attribute lookup in tight loop.
1280 # Alias to prevent attribute lookup in tight loop.
1280 index = self.index
1281 index = self.index
1281 generaldelta = self._generaldelta
1282 generaldelta = self._generaldelta
1282
1283
1283 iterrev = rev
1284 iterrev = rev
1284 e = index[iterrev]
1285 e = index[iterrev]
1285 while iterrev != e[3] and iterrev != stoprev:
1286 while iterrev != e[3] and iterrev != stoprev:
1286 chain.append(iterrev)
1287 chain.append(iterrev)
1287 if generaldelta:
1288 if generaldelta:
1288 iterrev = e[3]
1289 iterrev = e[3]
1289 else:
1290 else:
1290 iterrev -= 1
1291 iterrev -= 1
1291 e = index[iterrev]
1292 e = index[iterrev]
1292
1293
1293 if iterrev == stoprev:
1294 if iterrev == stoprev:
1294 stopped = True
1295 stopped = True
1295 else:
1296 else:
1296 chain.append(iterrev)
1297 chain.append(iterrev)
1297 stopped = False
1298 stopped = False
1298
1299
1299 chain.reverse()
1300 chain.reverse()
1300 return chain, stopped
1301 return chain, stopped
1301
1302
1302 def ancestors(self, revs, stoprev=0, inclusive=False):
1303 def ancestors(self, revs, stoprev=0, inclusive=False):
1303 """Generate the ancestors of 'revs' in reverse topological order.
1304 """Generate the ancestors of 'revs' in reverse topological order.
1304 Does not generate revs lower than stoprev.
1305 Does not generate revs lower than stoprev.
1305
1306
1306 See the documentation for ancestor.lazyancestors for more details."""
1307 See the documentation for ancestor.lazyancestors for more details."""
1307
1308
1308 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
1309 return ancestor.lazyancestors(self.parentrevs, revs, stoprev=stoprev,
1309 inclusive=inclusive)
1310 inclusive=inclusive)
1310
1311
1311 def descendants(self, revs):
1312 def descendants(self, revs):
1312 """Generate the descendants of 'revs' in revision order.
1313 """Generate the descendants of 'revs' in revision order.
1313
1314
1314 Yield a sequence of revision numbers starting with a child of
1315 Yield a sequence of revision numbers starting with a child of
1315 some rev in revs, i.e., each revision is *not* considered a
1316 some rev in revs, i.e., each revision is *not* considered a
1316 descendant of itself. Results are ordered by revision number (a
1317 descendant of itself. Results are ordered by revision number (a
1317 topological sort)."""
1318 topological sort)."""
1318 first = min(revs)
1319 first = min(revs)
1319 if first == nullrev:
1320 if first == nullrev:
1320 for i in self:
1321 for i in self:
1321 yield i
1322 yield i
1322 return
1323 return
1323
1324
1324 seen = set(revs)
1325 seen = set(revs)
1325 for i in self.revs(start=first + 1):
1326 for i in self.revs(start=first + 1):
1326 for x in self.parentrevs(i):
1327 for x in self.parentrevs(i):
1327 if x != nullrev and x in seen:
1328 if x != nullrev and x in seen:
1328 seen.add(i)
1329 seen.add(i)
1329 yield i
1330 yield i
1330 break
1331 break
1331
1332
1332 def findcommonmissing(self, common=None, heads=None):
1333 def findcommonmissing(self, common=None, heads=None):
1333 """Return a tuple of the ancestors of common and the ancestors of heads
1334 """Return a tuple of the ancestors of common and the ancestors of heads
1334 that are not ancestors of common. In revset terminology, we return the
1335 that are not ancestors of common. In revset terminology, we return the
1335 tuple:
1336 tuple:
1336
1337
1337 ::common, (::heads) - (::common)
1338 ::common, (::heads) - (::common)
1338
1339
1339 The list is sorted by revision number, meaning it is
1340 The list is sorted by revision number, meaning it is
1340 topologically sorted.
1341 topologically sorted.
1341
1342
1342 'heads' and 'common' are both lists of node IDs. If heads is
1343 'heads' and 'common' are both lists of node IDs. If heads is
1343 not supplied, uses all of the revlog's heads. If common is not
1344 not supplied, uses all of the revlog's heads. If common is not
1344 supplied, uses nullid."""
1345 supplied, uses nullid."""
1345 if common is None:
1346 if common is None:
1346 common = [nullid]
1347 common = [nullid]
1347 if heads is None:
1348 if heads is None:
1348 heads = self.heads()
1349 heads = self.heads()
1349
1350
1350 common = [self.rev(n) for n in common]
1351 common = [self.rev(n) for n in common]
1351 heads = [self.rev(n) for n in heads]
1352 heads = [self.rev(n) for n in heads]
1352
1353
1353 # we want the ancestors, but inclusive
1354 # we want the ancestors, but inclusive
1354 class lazyset(object):
1355 class lazyset(object):
1355 def __init__(self, lazyvalues):
1356 def __init__(self, lazyvalues):
1356 self.addedvalues = set()
1357 self.addedvalues = set()
1357 self.lazyvalues = lazyvalues
1358 self.lazyvalues = lazyvalues
1358
1359
1359 def __contains__(self, value):
1360 def __contains__(self, value):
1360 return value in self.addedvalues or value in self.lazyvalues
1361 return value in self.addedvalues or value in self.lazyvalues
1361
1362
1362 def __iter__(self):
1363 def __iter__(self):
1363 added = self.addedvalues
1364 added = self.addedvalues
1364 for r in added:
1365 for r in added:
1365 yield r
1366 yield r
1366 for r in self.lazyvalues:
1367 for r in self.lazyvalues:
1367 if not r in added:
1368 if not r in added:
1368 yield r
1369 yield r
1369
1370
1370 def add(self, value):
1371 def add(self, value):
1371 self.addedvalues.add(value)
1372 self.addedvalues.add(value)
1372
1373
1373 def update(self, values):
1374 def update(self, values):
1374 self.addedvalues.update(values)
1375 self.addedvalues.update(values)
1375
1376
1376 has = lazyset(self.ancestors(common))
1377 has = lazyset(self.ancestors(common))
1377 has.add(nullrev)
1378 has.add(nullrev)
1378 has.update(common)
1379 has.update(common)
1379
1380
1380 # take all ancestors from heads that aren't in has
1381 # take all ancestors from heads that aren't in has
1381 missing = set()
1382 missing = set()
1382 visit = collections.deque(r for r in heads if r not in has)
1383 visit = collections.deque(r for r in heads if r not in has)
1383 while visit:
1384 while visit:
1384 r = visit.popleft()
1385 r = visit.popleft()
1385 if r in missing:
1386 if r in missing:
1386 continue
1387 continue
1387 else:
1388 else:
1388 missing.add(r)
1389 missing.add(r)
1389 for p in self.parentrevs(r):
1390 for p in self.parentrevs(r):
1390 if p not in has:
1391 if p not in has:
1391 visit.append(p)
1392 visit.append(p)
1392 missing = list(missing)
1393 missing = list(missing)
1393 missing.sort()
1394 missing.sort()
1394 return has, [self.node(miss) for miss in missing]
1395 return has, [self.node(miss) for miss in missing]
1395
1396
1396 def incrementalmissingrevs(self, common=None):
1397 def incrementalmissingrevs(self, common=None):
1397 """Return an object that can be used to incrementally compute the
1398 """Return an object that can be used to incrementally compute the
1398 revision numbers of the ancestors of arbitrary sets that are not
1399 revision numbers of the ancestors of arbitrary sets that are not
1399 ancestors of common. This is an ancestor.incrementalmissingancestors
1400 ancestors of common. This is an ancestor.incrementalmissingancestors
1400 object.
1401 object.
1401
1402
1402 'common' is a list of revision numbers. If common is not supplied, uses
1403 'common' is a list of revision numbers. If common is not supplied, uses
1403 nullrev.
1404 nullrev.
1404 """
1405 """
1405 if common is None:
1406 if common is None:
1406 common = [nullrev]
1407 common = [nullrev]
1407
1408
1408 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1409 return ancestor.incrementalmissingancestors(self.parentrevs, common)
1409
1410
1410 def findmissingrevs(self, common=None, heads=None):
1411 def findmissingrevs(self, common=None, heads=None):
1411 """Return the revision numbers of the ancestors of heads that
1412 """Return the revision numbers of the ancestors of heads that
1412 are not ancestors of common.
1413 are not ancestors of common.
1413
1414
1414 More specifically, return a list of revision numbers corresponding to
1415 More specifically, return a list of revision numbers corresponding to
1415 nodes N such that every N satisfies the following constraints:
1416 nodes N such that every N satisfies the following constraints:
1416
1417
1417 1. N is an ancestor of some node in 'heads'
1418 1. N is an ancestor of some node in 'heads'
1418 2. N is not an ancestor of any node in 'common'
1419 2. N is not an ancestor of any node in 'common'
1419
1420
1420 The list is sorted by revision number, meaning it is
1421 The list is sorted by revision number, meaning it is
1421 topologically sorted.
1422 topologically sorted.
1422
1423
1423 'heads' and 'common' are both lists of revision numbers. If heads is
1424 'heads' and 'common' are both lists of revision numbers. If heads is
1424 not supplied, uses all of the revlog's heads. If common is not
1425 not supplied, uses all of the revlog's heads. If common is not
1425 supplied, uses nullid."""
1426 supplied, uses nullid."""
1426 if common is None:
1427 if common is None:
1427 common = [nullrev]
1428 common = [nullrev]
1428 if heads is None:
1429 if heads is None:
1429 heads = self.headrevs()
1430 heads = self.headrevs()
1430
1431
1431 inc = self.incrementalmissingrevs(common=common)
1432 inc = self.incrementalmissingrevs(common=common)
1432 return inc.missingancestors(heads)
1433 return inc.missingancestors(heads)
1433
1434
1434 def findmissing(self, common=None, heads=None):
1435 def findmissing(self, common=None, heads=None):
1435 """Return the ancestors of heads that are not ancestors of common.
1436 """Return the ancestors of heads that are not ancestors of common.
1436
1437
1437 More specifically, return a list of nodes N such that every N
1438 More specifically, return a list of nodes N such that every N
1438 satisfies the following constraints:
1439 satisfies the following constraints:
1439
1440
1440 1. N is an ancestor of some node in 'heads'
1441 1. N is an ancestor of some node in 'heads'
1441 2. N is not an ancestor of any node in 'common'
1442 2. N is not an ancestor of any node in 'common'
1442
1443
1443 The list is sorted by revision number, meaning it is
1444 The list is sorted by revision number, meaning it is
1444 topologically sorted.
1445 topologically sorted.
1445
1446
1446 'heads' and 'common' are both lists of node IDs. If heads is
1447 'heads' and 'common' are both lists of node IDs. If heads is
1447 not supplied, uses all of the revlog's heads. If common is not
1448 not supplied, uses all of the revlog's heads. If common is not
1448 supplied, uses nullid."""
1449 supplied, uses nullid."""
1449 if common is None:
1450 if common is None:
1450 common = [nullid]
1451 common = [nullid]
1451 if heads is None:
1452 if heads is None:
1452 heads = self.heads()
1453 heads = self.heads()
1453
1454
1454 common = [self.rev(n) for n in common]
1455 common = [self.rev(n) for n in common]
1455 heads = [self.rev(n) for n in heads]
1456 heads = [self.rev(n) for n in heads]
1456
1457
1457 inc = self.incrementalmissingrevs(common=common)
1458 inc = self.incrementalmissingrevs(common=common)
1458 return [self.node(r) for r in inc.missingancestors(heads)]
1459 return [self.node(r) for r in inc.missingancestors(heads)]
1459
1460
1460 def nodesbetween(self, roots=None, heads=None):
1461 def nodesbetween(self, roots=None, heads=None):
1461 """Return a topological path from 'roots' to 'heads'.
1462 """Return a topological path from 'roots' to 'heads'.
1462
1463
1463 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1464 Return a tuple (nodes, outroots, outheads) where 'nodes' is a
1464 topologically sorted list of all nodes N that satisfy both of
1465 topologically sorted list of all nodes N that satisfy both of
1465 these constraints:
1466 these constraints:
1466
1467
1467 1. N is a descendant of some node in 'roots'
1468 1. N is a descendant of some node in 'roots'
1468 2. N is an ancestor of some node in 'heads'
1469 2. N is an ancestor of some node in 'heads'
1469
1470
1470 Every node is considered to be both a descendant and an ancestor
1471 Every node is considered to be both a descendant and an ancestor
1471 of itself, so every reachable node in 'roots' and 'heads' will be
1472 of itself, so every reachable node in 'roots' and 'heads' will be
1472 included in 'nodes'.
1473 included in 'nodes'.
1473
1474
1474 'outroots' is the list of reachable nodes in 'roots', i.e., the
1475 'outroots' is the list of reachable nodes in 'roots', i.e., the
1475 subset of 'roots' that is returned in 'nodes'. Likewise,
1476 subset of 'roots' that is returned in 'nodes'. Likewise,
1476 'outheads' is the subset of 'heads' that is also in 'nodes'.
1477 'outheads' is the subset of 'heads' that is also in 'nodes'.
1477
1478
1478 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1479 'roots' and 'heads' are both lists of node IDs. If 'roots' is
1479 unspecified, uses nullid as the only root. If 'heads' is
1480 unspecified, uses nullid as the only root. If 'heads' is
1480 unspecified, uses list of all of the revlog's heads."""
1481 unspecified, uses list of all of the revlog's heads."""
1481 nonodes = ([], [], [])
1482 nonodes = ([], [], [])
1482 if roots is not None:
1483 if roots is not None:
1483 roots = list(roots)
1484 roots = list(roots)
1484 if not roots:
1485 if not roots:
1485 return nonodes
1486 return nonodes
1486 lowestrev = min([self.rev(n) for n in roots])
1487 lowestrev = min([self.rev(n) for n in roots])
1487 else:
1488 else:
1488 roots = [nullid] # Everybody's a descendant of nullid
1489 roots = [nullid] # Everybody's a descendant of nullid
1489 lowestrev = nullrev
1490 lowestrev = nullrev
1490 if (lowestrev == nullrev) and (heads is None):
1491 if (lowestrev == nullrev) and (heads is None):
1491 # We want _all_ the nodes!
1492 # We want _all_ the nodes!
1492 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1493 return ([self.node(r) for r in self], [nullid], list(self.heads()))
1493 if heads is None:
1494 if heads is None:
1494 # All nodes are ancestors, so the latest ancestor is the last
1495 # All nodes are ancestors, so the latest ancestor is the last
1495 # node.
1496 # node.
1496 highestrev = len(self) - 1
1497 highestrev = len(self) - 1
1497 # Set ancestors to None to signal that every node is an ancestor.
1498 # Set ancestors to None to signal that every node is an ancestor.
1498 ancestors = None
1499 ancestors = None
1499 # Set heads to an empty dictionary for later discovery of heads
1500 # Set heads to an empty dictionary for later discovery of heads
1500 heads = {}
1501 heads = {}
1501 else:
1502 else:
1502 heads = list(heads)
1503 heads = list(heads)
1503 if not heads:
1504 if not heads:
1504 return nonodes
1505 return nonodes
1505 ancestors = set()
1506 ancestors = set()
1506 # Turn heads into a dictionary so we can remove 'fake' heads.
1507 # Turn heads into a dictionary so we can remove 'fake' heads.
1507 # Also, later we will be using it to filter out the heads we can't
1508 # Also, later we will be using it to filter out the heads we can't
1508 # find from roots.
1509 # find from roots.
1509 heads = dict.fromkeys(heads, False)
1510 heads = dict.fromkeys(heads, False)
1510 # Start at the top and keep marking parents until we're done.
1511 # Start at the top and keep marking parents until we're done.
1511 nodestotag = set(heads)
1512 nodestotag = set(heads)
1512 # Remember where the top was so we can use it as a limit later.
1513 # Remember where the top was so we can use it as a limit later.
1513 highestrev = max([self.rev(n) for n in nodestotag])
1514 highestrev = max([self.rev(n) for n in nodestotag])
1514 while nodestotag:
1515 while nodestotag:
1515 # grab a node to tag
1516 # grab a node to tag
1516 n = nodestotag.pop()
1517 n = nodestotag.pop()
1517 # Never tag nullid
1518 # Never tag nullid
1518 if n == nullid:
1519 if n == nullid:
1519 continue
1520 continue
1520 # A node's revision number represents its place in a
1521 # A node's revision number represents its place in a
1521 # topologically sorted list of nodes.
1522 # topologically sorted list of nodes.
1522 r = self.rev(n)
1523 r = self.rev(n)
1523 if r >= lowestrev:
1524 if r >= lowestrev:
1524 if n not in ancestors:
1525 if n not in ancestors:
1525 # If we are possibly a descendant of one of the roots
1526 # If we are possibly a descendant of one of the roots
1526 # and we haven't already been marked as an ancestor
1527 # and we haven't already been marked as an ancestor
1527 ancestors.add(n) # Mark as ancestor
1528 ancestors.add(n) # Mark as ancestor
1528 # Add non-nullid parents to list of nodes to tag.
1529 # Add non-nullid parents to list of nodes to tag.
1529 nodestotag.update([p for p in self.parents(n) if
1530 nodestotag.update([p for p in self.parents(n) if
1530 p != nullid])
1531 p != nullid])
1531 elif n in heads: # We've seen it before, is it a fake head?
1532 elif n in heads: # We've seen it before, is it a fake head?
1532 # So it is, real heads should not be the ancestors of
1533 # So it is, real heads should not be the ancestors of
1533 # any other heads.
1534 # any other heads.
1534 heads.pop(n)
1535 heads.pop(n)
1535 if not ancestors:
1536 if not ancestors:
1536 return nonodes
1537 return nonodes
1537 # Now that we have our set of ancestors, we want to remove any
1538 # Now that we have our set of ancestors, we want to remove any
1538 # roots that are not ancestors.
1539 # roots that are not ancestors.
1539
1540
1540 # If one of the roots was nullid, everything is included anyway.
1541 # If one of the roots was nullid, everything is included anyway.
1541 if lowestrev > nullrev:
1542 if lowestrev > nullrev:
1542 # But, since we weren't, let's recompute the lowest rev to not
1543 # But, since we weren't, let's recompute the lowest rev to not
1543 # include roots that aren't ancestors.
1544 # include roots that aren't ancestors.
1544
1545
1545 # Filter out roots that aren't ancestors of heads
1546 # Filter out roots that aren't ancestors of heads
1546 roots = [root for root in roots if root in ancestors]
1547 roots = [root for root in roots if root in ancestors]
1547 # Recompute the lowest revision
1548 # Recompute the lowest revision
1548 if roots:
1549 if roots:
1549 lowestrev = min([self.rev(root) for root in roots])
1550 lowestrev = min([self.rev(root) for root in roots])
1550 else:
1551 else:
1551 # No more roots? Return empty list
1552 # No more roots? Return empty list
1552 return nonodes
1553 return nonodes
1553 else:
1554 else:
1554 # We are descending from nullid, and don't need to care about
1555 # We are descending from nullid, and don't need to care about
1555 # any other roots.
1556 # any other roots.
1556 lowestrev = nullrev
1557 lowestrev = nullrev
1557 roots = [nullid]
1558 roots = [nullid]
1558 # Transform our roots list into a set.
1559 # Transform our roots list into a set.
1559 descendants = set(roots)
1560 descendants = set(roots)
1560 # Also, keep the original roots so we can filter out roots that aren't
1561 # Also, keep the original roots so we can filter out roots that aren't
1561 # 'real' roots (i.e. are descended from other roots).
1562 # 'real' roots (i.e. are descended from other roots).
1562 roots = descendants.copy()
1563 roots = descendants.copy()
1563 # Our topologically sorted list of output nodes.
1564 # Our topologically sorted list of output nodes.
1564 orderedout = []
1565 orderedout = []
1565 # Don't start at nullid since we don't want nullid in our output list,
1566 # Don't start at nullid since we don't want nullid in our output list,
1566 # and if nullid shows up in descendants, empty parents will look like
1567 # and if nullid shows up in descendants, empty parents will look like
1567 # they're descendants.
1568 # they're descendants.
1568 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1569 for r in self.revs(start=max(lowestrev, 0), stop=highestrev + 1):
1569 n = self.node(r)
1570 n = self.node(r)
1570 isdescendant = False
1571 isdescendant = False
1571 if lowestrev == nullrev: # Everybody is a descendant of nullid
1572 if lowestrev == nullrev: # Everybody is a descendant of nullid
1572 isdescendant = True
1573 isdescendant = True
1573 elif n in descendants:
1574 elif n in descendants:
1574 # n is already a descendant
1575 # n is already a descendant
1575 isdescendant = True
1576 isdescendant = True
1576 # This check only needs to be done here because all the roots
1577 # This check only needs to be done here because all the roots
1577 # will start being marked is descendants before the loop.
1578 # will start being marked is descendants before the loop.
1578 if n in roots:
1579 if n in roots:
1579 # If n was a root, check if it's a 'real' root.
1580 # If n was a root, check if it's a 'real' root.
1580 p = tuple(self.parents(n))
1581 p = tuple(self.parents(n))
1581 # If any of its parents are descendants, it's not a root.
1582 # If any of its parents are descendants, it's not a root.
1582 if (p[0] in descendants) or (p[1] in descendants):
1583 if (p[0] in descendants) or (p[1] in descendants):
1583 roots.remove(n)
1584 roots.remove(n)
1584 else:
1585 else:
1585 p = tuple(self.parents(n))
1586 p = tuple(self.parents(n))
1586 # A node is a descendant if either of its parents are
1587 # A node is a descendant if either of its parents are
1587 # descendants. (We seeded the dependents list with the roots
1588 # descendants. (We seeded the dependents list with the roots
1588 # up there, remember?)
1589 # up there, remember?)
1589 if (p[0] in descendants) or (p[1] in descendants):
1590 if (p[0] in descendants) or (p[1] in descendants):
1590 descendants.add(n)
1591 descendants.add(n)
1591 isdescendant = True
1592 isdescendant = True
1592 if isdescendant and ((ancestors is None) or (n in ancestors)):
1593 if isdescendant and ((ancestors is None) or (n in ancestors)):
1593 # Only include nodes that are both descendants and ancestors.
1594 # Only include nodes that are both descendants and ancestors.
1594 orderedout.append(n)
1595 orderedout.append(n)
1595 if (ancestors is not None) and (n in heads):
1596 if (ancestors is not None) and (n in heads):
1596 # We're trying to figure out which heads are reachable
1597 # We're trying to figure out which heads are reachable
1597 # from roots.
1598 # from roots.
1598 # Mark this head as having been reached
1599 # Mark this head as having been reached
1599 heads[n] = True
1600 heads[n] = True
1600 elif ancestors is None:
1601 elif ancestors is None:
1601 # Otherwise, we're trying to discover the heads.
1602 # Otherwise, we're trying to discover the heads.
1602 # Assume this is a head because if it isn't, the next step
1603 # Assume this is a head because if it isn't, the next step
1603 # will eventually remove it.
1604 # will eventually remove it.
1604 heads[n] = True
1605 heads[n] = True
1605 # But, obviously its parents aren't.
1606 # But, obviously its parents aren't.
1606 for p in self.parents(n):
1607 for p in self.parents(n):
1607 heads.pop(p, None)
1608 heads.pop(p, None)
1608 heads = [head for head, flag in heads.iteritems() if flag]
1609 heads = [head for head, flag in heads.iteritems() if flag]
1609 roots = list(roots)
1610 roots = list(roots)
1610 assert orderedout
1611 assert orderedout
1611 assert roots
1612 assert roots
1612 assert heads
1613 assert heads
1613 return (orderedout, roots, heads)
1614 return (orderedout, roots, heads)
1614
1615
1615 def headrevs(self):
1616 def headrevs(self):
1616 try:
1617 try:
1617 return self.index.headrevs()
1618 return self.index.headrevs()
1618 except AttributeError:
1619 except AttributeError:
1619 return self._headrevs()
1620 return self._headrevs()
1620
1621
1621 def computephases(self, roots):
1622 def computephases(self, roots):
1622 return self.index.computephasesmapsets(roots)
1623 return self.index.computephasesmapsets(roots)
1623
1624
1624 def _headrevs(self):
1625 def _headrevs(self):
1625 count = len(self)
1626 count = len(self)
1626 if not count:
1627 if not count:
1627 return [nullrev]
1628 return [nullrev]
1628 # we won't iter over filtered rev so nobody is a head at start
1629 # we won't iter over filtered rev so nobody is a head at start
1629 ishead = [0] * (count + 1)
1630 ishead = [0] * (count + 1)
1630 index = self.index
1631 index = self.index
1631 for r in self:
1632 for r in self:
1632 ishead[r] = 1 # I may be an head
1633 ishead[r] = 1 # I may be an head
1633 e = index[r]
1634 e = index[r]
1634 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1635 ishead[e[5]] = ishead[e[6]] = 0 # my parent are not
1635 return [r for r, val in enumerate(ishead) if val]
1636 return [r for r, val in enumerate(ishead) if val]
1636
1637
1637 def heads(self, start=None, stop=None):
1638 def heads(self, start=None, stop=None):
1638 """return the list of all nodes that have no children
1639 """return the list of all nodes that have no children
1639
1640
1640 if start is specified, only heads that are descendants of
1641 if start is specified, only heads that are descendants of
1641 start will be returned
1642 start will be returned
1642 if stop is specified, it will consider all the revs from stop
1643 if stop is specified, it will consider all the revs from stop
1643 as if they had no children
1644 as if they had no children
1644 """
1645 """
1645 if start is None and stop is None:
1646 if start is None and stop is None:
1646 if not len(self):
1647 if not len(self):
1647 return [nullid]
1648 return [nullid]
1648 return [self.node(r) for r in self.headrevs()]
1649 return [self.node(r) for r in self.headrevs()]
1649
1650
1650 if start is None:
1651 if start is None:
1651 start = nullid
1652 start = nullid
1652 if stop is None:
1653 if stop is None:
1653 stop = []
1654 stop = []
1654 stoprevs = set([self.rev(n) for n in stop])
1655 stoprevs = set([self.rev(n) for n in stop])
1655 startrev = self.rev(start)
1656 startrev = self.rev(start)
1656 reachable = {startrev}
1657 reachable = {startrev}
1657 heads = {startrev}
1658 heads = {startrev}
1658
1659
1659 parentrevs = self.parentrevs
1660 parentrevs = self.parentrevs
1660 for r in self.revs(start=startrev + 1):
1661 for r in self.revs(start=startrev + 1):
1661 for p in parentrevs(r):
1662 for p in parentrevs(r):
1662 if p in reachable:
1663 if p in reachable:
1663 if r not in stoprevs:
1664 if r not in stoprevs:
1664 reachable.add(r)
1665 reachable.add(r)
1665 heads.add(r)
1666 heads.add(r)
1666 if p in heads and p not in stoprevs:
1667 if p in heads and p not in stoprevs:
1667 heads.remove(p)
1668 heads.remove(p)
1668
1669
1669 return [self.node(r) for r in heads]
1670 return [self.node(r) for r in heads]
1670
1671
1671 def children(self, node):
1672 def children(self, node):
1672 """find the children of a given node"""
1673 """find the children of a given node"""
1673 c = []
1674 c = []
1674 p = self.rev(node)
1675 p = self.rev(node)
1675 for r in self.revs(start=p + 1):
1676 for r in self.revs(start=p + 1):
1676 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1677 prevs = [pr for pr in self.parentrevs(r) if pr != nullrev]
1677 if prevs:
1678 if prevs:
1678 for pr in prevs:
1679 for pr in prevs:
1679 if pr == p:
1680 if pr == p:
1680 c.append(self.node(r))
1681 c.append(self.node(r))
1681 elif p == nullrev:
1682 elif p == nullrev:
1682 c.append(self.node(r))
1683 c.append(self.node(r))
1683 return c
1684 return c
1684
1685
1685 def commonancestorsheads(self, a, b):
1686 def commonancestorsheads(self, a, b):
1686 """calculate all the heads of the common ancestors of nodes a and b"""
1687 """calculate all the heads of the common ancestors of nodes a and b"""
1687 a, b = self.rev(a), self.rev(b)
1688 a, b = self.rev(a), self.rev(b)
1688 ancs = self._commonancestorsheads(a, b)
1689 ancs = self._commonancestorsheads(a, b)
1689 return pycompat.maplist(self.node, ancs)
1690 return pycompat.maplist(self.node, ancs)
1690
1691
1691 def _commonancestorsheads(self, *revs):
1692 def _commonancestorsheads(self, *revs):
1692 """calculate all the heads of the common ancestors of revs"""
1693 """calculate all the heads of the common ancestors of revs"""
1693 try:
1694 try:
1694 ancs = self.index.commonancestorsheads(*revs)
1695 ancs = self.index.commonancestorsheads(*revs)
1695 except (AttributeError, OverflowError): # C implementation failed
1696 except (AttributeError, OverflowError): # C implementation failed
1696 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1697 ancs = ancestor.commonancestorsheads(self.parentrevs, *revs)
1697 return ancs
1698 return ancs
1698
1699
1699 def isancestor(self, a, b):
1700 def isancestor(self, a, b):
1700 """return True if node a is an ancestor of node b
1701 """return True if node a is an ancestor of node b
1701
1702
1702 A revision is considered an ancestor of itself."""
1703 A revision is considered an ancestor of itself."""
1703 a, b = self.rev(a), self.rev(b)
1704 a, b = self.rev(a), self.rev(b)
1704 return self.isancestorrev(a, b)
1705 return self.isancestorrev(a, b)
1705
1706
1706 def descendant(self, a, b):
1707 def descendant(self, a, b):
1707 msg = 'revlog.descendant is deprecated, use revlog.isancestorrev'
1708 msg = 'revlog.descendant is deprecated, use revlog.isancestorrev'
1708 util.nouideprecwarn(msg, '4.7')
1709 util.nouideprecwarn(msg, '4.7')
1709 return self.isancestorrev(a, b)
1710 return self.isancestorrev(a, b)
1710
1711
1711 def isancestorrev(self, a, b):
1712 def isancestorrev(self, a, b):
1712 """return True if revision a is an ancestor of revision b
1713 """return True if revision a is an ancestor of revision b
1713
1714
1714 A revision is considered an ancestor of itself.
1715 A revision is considered an ancestor of itself.
1715
1716
1716 The implementation of this is trivial but the use of
1717 The implementation of this is trivial but the use of
1717 commonancestorsheads is not."""
1718 commonancestorsheads is not."""
1718 if a == nullrev:
1719 if a == nullrev:
1719 return True
1720 return True
1720 elif a == b:
1721 elif a == b:
1721 return True
1722 return True
1722 elif a > b:
1723 elif a > b:
1723 return False
1724 return False
1724 return a in self._commonancestorsheads(a, b)
1725 return a in self._commonancestorsheads(a, b)
1725
1726
1726 def ancestor(self, a, b):
1727 def ancestor(self, a, b):
1727 """calculate the "best" common ancestor of nodes a and b"""
1728 """calculate the "best" common ancestor of nodes a and b"""
1728
1729
1729 a, b = self.rev(a), self.rev(b)
1730 a, b = self.rev(a), self.rev(b)
1730 try:
1731 try:
1731 ancs = self.index.ancestors(a, b)
1732 ancs = self.index.ancestors(a, b)
1732 except (AttributeError, OverflowError):
1733 except (AttributeError, OverflowError):
1733 ancs = ancestor.ancestors(self.parentrevs, a, b)
1734 ancs = ancestor.ancestors(self.parentrevs, a, b)
1734 if ancs:
1735 if ancs:
1735 # choose a consistent winner when there's a tie
1736 # choose a consistent winner when there's a tie
1736 return min(map(self.node, ancs))
1737 return min(map(self.node, ancs))
1737 return nullid
1738 return nullid
1738
1739
1739 def _match(self, id):
1740 def _match(self, id):
1740 if isinstance(id, int):
1741 if isinstance(id, int):
1741 # rev
1742 # rev
1742 return self.node(id)
1743 return self.node(id)
1743 if len(id) == 20:
1744 if len(id) == 20:
1744 # possibly a binary node
1745 # possibly a binary node
1745 # odds of a binary node being all hex in ASCII are 1 in 10**25
1746 # odds of a binary node being all hex in ASCII are 1 in 10**25
1746 try:
1747 try:
1747 node = id
1748 node = id
1748 self.rev(node) # quick search the index
1749 self.rev(node) # quick search the index
1749 return node
1750 return node
1750 except LookupError:
1751 except LookupError:
1751 pass # may be partial hex id
1752 pass # may be partial hex id
1752 try:
1753 try:
1753 # str(rev)
1754 # str(rev)
1754 rev = int(id)
1755 rev = int(id)
1755 if "%d" % rev != id:
1756 if "%d" % rev != id:
1756 raise ValueError
1757 raise ValueError
1757 if rev < 0:
1758 if rev < 0:
1758 rev = len(self) + rev
1759 rev = len(self) + rev
1759 if rev < 0 or rev >= len(self):
1760 if rev < 0 or rev >= len(self):
1760 raise ValueError
1761 raise ValueError
1761 return self.node(rev)
1762 return self.node(rev)
1762 except (ValueError, OverflowError):
1763 except (ValueError, OverflowError):
1763 pass
1764 pass
1764 if len(id) == 40:
1765 if len(id) == 40:
1765 try:
1766 try:
1766 # a full hex nodeid?
1767 # a full hex nodeid?
1767 node = bin(id)
1768 node = bin(id)
1768 self.rev(node)
1769 self.rev(node)
1769 return node
1770 return node
1770 except (TypeError, LookupError):
1771 except (TypeError, LookupError):
1771 pass
1772 pass
1772
1773
1773 def _partialmatch(self, id):
1774 def _partialmatch(self, id):
1774 # we don't care wdirfilenodeids as they should be always full hash
1775 # we don't care wdirfilenodeids as they should be always full hash
1775 maybewdir = wdirhex.startswith(id)
1776 maybewdir = wdirhex.startswith(id)
1776 try:
1777 try:
1777 partial = self.index.partialmatch(id)
1778 partial = self.index.partialmatch(id)
1778 if partial and self.hasnode(partial):
1779 if partial and self.hasnode(partial):
1779 if maybewdir:
1780 if maybewdir:
1780 # single 'ff...' match in radix tree, ambiguous with wdir
1781 # single 'ff...' match in radix tree, ambiguous with wdir
1781 raise RevlogError
1782 raise RevlogError
1782 return partial
1783 return partial
1783 if maybewdir:
1784 if maybewdir:
1784 # no 'ff...' match in radix tree, wdir identified
1785 # no 'ff...' match in radix tree, wdir identified
1785 raise error.WdirUnsupported
1786 raise error.WdirUnsupported
1786 return None
1787 return None
1787 except RevlogError:
1788 except RevlogError:
1788 # parsers.c radix tree lookup gave multiple matches
1789 # parsers.c radix tree lookup gave multiple matches
1789 # fast path: for unfiltered changelog, radix tree is accurate
1790 # fast path: for unfiltered changelog, radix tree is accurate
1790 if not getattr(self, 'filteredrevs', None):
1791 if not getattr(self, 'filteredrevs', None):
1791 raise LookupError(id, self.indexfile,
1792 raise AmbiguousPrefixLookupError(id, self.indexfile,
1792 _('ambiguous identifier'))
1793 _('ambiguous identifier'))
1793 # fall through to slow path that filters hidden revisions
1794 # fall through to slow path that filters hidden revisions
1794 except (AttributeError, ValueError):
1795 except (AttributeError, ValueError):
1795 # we are pure python, or key was too short to search radix tree
1796 # we are pure python, or key was too short to search radix tree
1796 pass
1797 pass
1797
1798
1798 if id in self._pcache:
1799 if id in self._pcache:
1799 return self._pcache[id]
1800 return self._pcache[id]
1800
1801
1801 if len(id) <= 40:
1802 if len(id) <= 40:
1802 try:
1803 try:
1803 # hex(node)[:...]
1804 # hex(node)[:...]
1804 l = len(id) // 2 # grab an even number of digits
1805 l = len(id) // 2 # grab an even number of digits
1805 prefix = bin(id[:l * 2])
1806 prefix = bin(id[:l * 2])
1806 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1807 nl = [e[7] for e in self.index if e[7].startswith(prefix)]
1807 nl = [n for n in nl if hex(n).startswith(id) and
1808 nl = [n for n in nl if hex(n).startswith(id) and
1808 self.hasnode(n)]
1809 self.hasnode(n)]
1809 if len(nl) > 0:
1810 if len(nl) > 0:
1810 if len(nl) == 1 and not maybewdir:
1811 if len(nl) == 1 and not maybewdir:
1811 self._pcache[id] = nl[0]
1812 self._pcache[id] = nl[0]
1812 return nl[0]
1813 return nl[0]
1813 raise LookupError(id, self.indexfile,
1814 raise AmbiguousPrefixLookupError(id, self.indexfile,
1814 _('ambiguous identifier'))
1815 _('ambiguous identifier'))
1815 if maybewdir:
1816 if maybewdir:
1816 raise error.WdirUnsupported
1817 raise error.WdirUnsupported
1817 return None
1818 return None
1818 except TypeError:
1819 except TypeError:
1819 pass
1820 pass
1820
1821
1821 def lookup(self, id):
1822 def lookup(self, id):
1822 """locate a node based on:
1823 """locate a node based on:
1823 - revision number or str(revision number)
1824 - revision number or str(revision number)
1824 - nodeid or subset of hex nodeid
1825 - nodeid or subset of hex nodeid
1825 """
1826 """
1826 n = self._match(id)
1827 n = self._match(id)
1827 if n is not None:
1828 if n is not None:
1828 return n
1829 return n
1829 n = self._partialmatch(id)
1830 n = self._partialmatch(id)
1830 if n:
1831 if n:
1831 return n
1832 return n
1832
1833
1833 raise LookupError(id, self.indexfile, _('no match found'))
1834 raise LookupError(id, self.indexfile, _('no match found'))
1834
1835
1835 def shortest(self, node, minlength=1):
1836 def shortest(self, node, minlength=1):
1836 """Find the shortest unambiguous prefix that matches node."""
1837 """Find the shortest unambiguous prefix that matches node."""
1837 def isvalid(prefix):
1838 def isvalid(prefix):
1838 try:
1839 try:
1839 node = self._partialmatch(prefix)
1840 node = self._partialmatch(prefix)
1840 except error.RevlogError:
1841 except error.RevlogError:
1841 return False
1842 return False
1842 except error.WdirUnsupported:
1843 except error.WdirUnsupported:
1843 # single 'ff...' match
1844 # single 'ff...' match
1844 return True
1845 return True
1845 if node is None:
1846 if node is None:
1846 raise LookupError(node, self.indexfile, _('no node'))
1847 raise LookupError(node, self.indexfile, _('no node'))
1847 return True
1848 return True
1848
1849
1849 def maybewdir(prefix):
1850 def maybewdir(prefix):
1850 return all(c == 'f' for c in prefix)
1851 return all(c == 'f' for c in prefix)
1851
1852
1852 hexnode = hex(node)
1853 hexnode = hex(node)
1853
1854
1854 def disambiguate(hexnode, minlength):
1855 def disambiguate(hexnode, minlength):
1855 """Disambiguate against wdirid."""
1856 """Disambiguate against wdirid."""
1856 for length in range(minlength, 41):
1857 for length in range(minlength, 41):
1857 prefix = hexnode[:length]
1858 prefix = hexnode[:length]
1858 if not maybewdir(prefix):
1859 if not maybewdir(prefix):
1859 return prefix
1860 return prefix
1860
1861
1861 if not getattr(self, 'filteredrevs', None):
1862 if not getattr(self, 'filteredrevs', None):
1862 try:
1863 try:
1863 length = max(self.index.shortest(node), minlength)
1864 length = max(self.index.shortest(node), minlength)
1864 return disambiguate(hexnode, length)
1865 return disambiguate(hexnode, length)
1865 except RevlogError:
1866 except RevlogError:
1866 if node != wdirid:
1867 if node != wdirid:
1867 raise LookupError(node, self.indexfile, _('no node'))
1868 raise LookupError(node, self.indexfile, _('no node'))
1868 except AttributeError:
1869 except AttributeError:
1869 # Fall through to pure code
1870 # Fall through to pure code
1870 pass
1871 pass
1871
1872
1872 if node == wdirid:
1873 if node == wdirid:
1873 for length in range(minlength, 41):
1874 for length in range(minlength, 41):
1874 prefix = hexnode[:length]
1875 prefix = hexnode[:length]
1875 if isvalid(prefix):
1876 if isvalid(prefix):
1876 return prefix
1877 return prefix
1877
1878
1878 for length in range(minlength, 41):
1879 for length in range(minlength, 41):
1879 prefix = hexnode[:length]
1880 prefix = hexnode[:length]
1880 if isvalid(prefix):
1881 if isvalid(prefix):
1881 return disambiguate(hexnode, length)
1882 return disambiguate(hexnode, length)
1882
1883
1883 def cmp(self, node, text):
1884 def cmp(self, node, text):
1884 """compare text with a given file revision
1885 """compare text with a given file revision
1885
1886
1886 returns True if text is different than what is stored.
1887 returns True if text is different than what is stored.
1887 """
1888 """
1888 p1, p2 = self.parents(node)
1889 p1, p2 = self.parents(node)
1889 return hash(text, p1, p2) != node
1890 return hash(text, p1, p2) != node
1890
1891
1891 def _cachesegment(self, offset, data):
1892 def _cachesegment(self, offset, data):
1892 """Add a segment to the revlog cache.
1893 """Add a segment to the revlog cache.
1893
1894
1894 Accepts an absolute offset and the data that is at that location.
1895 Accepts an absolute offset and the data that is at that location.
1895 """
1896 """
1896 o, d = self._chunkcache
1897 o, d = self._chunkcache
1897 # try to add to existing cache
1898 # try to add to existing cache
1898 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1899 if o + len(d) == offset and len(d) + len(data) < _chunksize:
1899 self._chunkcache = o, d + data
1900 self._chunkcache = o, d + data
1900 else:
1901 else:
1901 self._chunkcache = offset, data
1902 self._chunkcache = offset, data
1902
1903
1903 def _readsegment(self, offset, length, df=None):
1904 def _readsegment(self, offset, length, df=None):
1904 """Load a segment of raw data from the revlog.
1905 """Load a segment of raw data from the revlog.
1905
1906
1906 Accepts an absolute offset, length to read, and an optional existing
1907 Accepts an absolute offset, length to read, and an optional existing
1907 file handle to read from.
1908 file handle to read from.
1908
1909
1909 If an existing file handle is passed, it will be seeked and the
1910 If an existing file handle is passed, it will be seeked and the
1910 original seek position will NOT be restored.
1911 original seek position will NOT be restored.
1911
1912
1912 Returns a str or buffer of raw byte data.
1913 Returns a str or buffer of raw byte data.
1913 """
1914 """
1914 # Cache data both forward and backward around the requested
1915 # Cache data both forward and backward around the requested
1915 # data, in a fixed size window. This helps speed up operations
1916 # data, in a fixed size window. This helps speed up operations
1916 # involving reading the revlog backwards.
1917 # involving reading the revlog backwards.
1917 cachesize = self._chunkcachesize
1918 cachesize = self._chunkcachesize
1918 realoffset = offset & ~(cachesize - 1)
1919 realoffset = offset & ~(cachesize - 1)
1919 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1920 reallength = (((offset + length + cachesize) & ~(cachesize - 1))
1920 - realoffset)
1921 - realoffset)
1921 with self._datareadfp(df) as df:
1922 with self._datareadfp(df) as df:
1922 df.seek(realoffset)
1923 df.seek(realoffset)
1923 d = df.read(reallength)
1924 d = df.read(reallength)
1924 self._cachesegment(realoffset, d)
1925 self._cachesegment(realoffset, d)
1925 if offset != realoffset or reallength != length:
1926 if offset != realoffset or reallength != length:
1926 return util.buffer(d, offset - realoffset, length)
1927 return util.buffer(d, offset - realoffset, length)
1927 return d
1928 return d
1928
1929
1929 def _getsegment(self, offset, length, df=None):
1930 def _getsegment(self, offset, length, df=None):
1930 """Obtain a segment of raw data from the revlog.
1931 """Obtain a segment of raw data from the revlog.
1931
1932
1932 Accepts an absolute offset, length of bytes to obtain, and an
1933 Accepts an absolute offset, length of bytes to obtain, and an
1933 optional file handle to the already-opened revlog. If the file
1934 optional file handle to the already-opened revlog. If the file
1934 handle is used, it's original seek position will not be preserved.
1935 handle is used, it's original seek position will not be preserved.
1935
1936
1936 Requests for data may be returned from a cache.
1937 Requests for data may be returned from a cache.
1937
1938
1938 Returns a str or a buffer instance of raw byte data.
1939 Returns a str or a buffer instance of raw byte data.
1939 """
1940 """
1940 o, d = self._chunkcache
1941 o, d = self._chunkcache
1941 l = len(d)
1942 l = len(d)
1942
1943
1943 # is it in the cache?
1944 # is it in the cache?
1944 cachestart = offset - o
1945 cachestart = offset - o
1945 cacheend = cachestart + length
1946 cacheend = cachestart + length
1946 if cachestart >= 0 and cacheend <= l:
1947 if cachestart >= 0 and cacheend <= l:
1947 if cachestart == 0 and cacheend == l:
1948 if cachestart == 0 and cacheend == l:
1948 return d # avoid a copy
1949 return d # avoid a copy
1949 return util.buffer(d, cachestart, cacheend - cachestart)
1950 return util.buffer(d, cachestart, cacheend - cachestart)
1950
1951
1951 return self._readsegment(offset, length, df=df)
1952 return self._readsegment(offset, length, df=df)
1952
1953
1953 def _getsegmentforrevs(self, startrev, endrev, df=None):
1954 def _getsegmentforrevs(self, startrev, endrev, df=None):
1954 """Obtain a segment of raw data corresponding to a range of revisions.
1955 """Obtain a segment of raw data corresponding to a range of revisions.
1955
1956
1956 Accepts the start and end revisions and an optional already-open
1957 Accepts the start and end revisions and an optional already-open
1957 file handle to be used for reading. If the file handle is read, its
1958 file handle to be used for reading. If the file handle is read, its
1958 seek position will not be preserved.
1959 seek position will not be preserved.
1959
1960
1960 Requests for data may be satisfied by a cache.
1961 Requests for data may be satisfied by a cache.
1961
1962
1962 Returns a 2-tuple of (offset, data) for the requested range of
1963 Returns a 2-tuple of (offset, data) for the requested range of
1963 revisions. Offset is the integer offset from the beginning of the
1964 revisions. Offset is the integer offset from the beginning of the
1964 revlog and data is a str or buffer of the raw byte data.
1965 revlog and data is a str or buffer of the raw byte data.
1965
1966
1966 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1967 Callers will need to call ``self.start(rev)`` and ``self.length(rev)``
1967 to determine where each revision's data begins and ends.
1968 to determine where each revision's data begins and ends.
1968 """
1969 """
1969 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1970 # Inlined self.start(startrev) & self.end(endrev) for perf reasons
1970 # (functions are expensive).
1971 # (functions are expensive).
1971 index = self.index
1972 index = self.index
1972 istart = index[startrev]
1973 istart = index[startrev]
1973 start = int(istart[0] >> 16)
1974 start = int(istart[0] >> 16)
1974 if startrev == endrev:
1975 if startrev == endrev:
1975 end = start + istart[1]
1976 end = start + istart[1]
1976 else:
1977 else:
1977 iend = index[endrev]
1978 iend = index[endrev]
1978 end = int(iend[0] >> 16) + iend[1]
1979 end = int(iend[0] >> 16) + iend[1]
1979
1980
1980 if self._inline:
1981 if self._inline:
1981 start += (startrev + 1) * self._io.size
1982 start += (startrev + 1) * self._io.size
1982 end += (endrev + 1) * self._io.size
1983 end += (endrev + 1) * self._io.size
1983 length = end - start
1984 length = end - start
1984
1985
1985 return start, self._getsegment(start, length, df=df)
1986 return start, self._getsegment(start, length, df=df)
1986
1987
1987 def _chunk(self, rev, df=None):
1988 def _chunk(self, rev, df=None):
1988 """Obtain a single decompressed chunk for a revision.
1989 """Obtain a single decompressed chunk for a revision.
1989
1990
1990 Accepts an integer revision and an optional already-open file handle
1991 Accepts an integer revision and an optional already-open file handle
1991 to be used for reading. If used, the seek position of the file will not
1992 to be used for reading. If used, the seek position of the file will not
1992 be preserved.
1993 be preserved.
1993
1994
1994 Returns a str holding uncompressed data for the requested revision.
1995 Returns a str holding uncompressed data for the requested revision.
1995 """
1996 """
1996 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1997 return self.decompress(self._getsegmentforrevs(rev, rev, df=df)[1])
1997
1998
1998 def _chunks(self, revs, df=None, targetsize=None):
1999 def _chunks(self, revs, df=None, targetsize=None):
1999 """Obtain decompressed chunks for the specified revisions.
2000 """Obtain decompressed chunks for the specified revisions.
2000
2001
2001 Accepts an iterable of numeric revisions that are assumed to be in
2002 Accepts an iterable of numeric revisions that are assumed to be in
2002 ascending order. Also accepts an optional already-open file handle
2003 ascending order. Also accepts an optional already-open file handle
2003 to be used for reading. If used, the seek position of the file will
2004 to be used for reading. If used, the seek position of the file will
2004 not be preserved.
2005 not be preserved.
2005
2006
2006 This function is similar to calling ``self._chunk()`` multiple times,
2007 This function is similar to calling ``self._chunk()`` multiple times,
2007 but is faster.
2008 but is faster.
2008
2009
2009 Returns a list with decompressed data for each requested revision.
2010 Returns a list with decompressed data for each requested revision.
2010 """
2011 """
2011 if not revs:
2012 if not revs:
2012 return []
2013 return []
2013 start = self.start
2014 start = self.start
2014 length = self.length
2015 length = self.length
2015 inline = self._inline
2016 inline = self._inline
2016 iosize = self._io.size
2017 iosize = self._io.size
2017 buffer = util.buffer
2018 buffer = util.buffer
2018
2019
2019 l = []
2020 l = []
2020 ladd = l.append
2021 ladd = l.append
2021
2022
2022 if not self._withsparseread:
2023 if not self._withsparseread:
2023 slicedchunks = (revs,)
2024 slicedchunks = (revs,)
2024 else:
2025 else:
2025 slicedchunks = _slicechunk(self, revs, targetsize=targetsize)
2026 slicedchunks = _slicechunk(self, revs, targetsize=targetsize)
2026
2027
2027 for revschunk in slicedchunks:
2028 for revschunk in slicedchunks:
2028 firstrev = revschunk[0]
2029 firstrev = revschunk[0]
2029 # Skip trailing revisions with empty diff
2030 # Skip trailing revisions with empty diff
2030 for lastrev in revschunk[::-1]:
2031 for lastrev in revschunk[::-1]:
2031 if length(lastrev) != 0:
2032 if length(lastrev) != 0:
2032 break
2033 break
2033
2034
2034 try:
2035 try:
2035 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
2036 offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
2036 except OverflowError:
2037 except OverflowError:
2037 # issue4215 - we can't cache a run of chunks greater than
2038 # issue4215 - we can't cache a run of chunks greater than
2038 # 2G on Windows
2039 # 2G on Windows
2039 return [self._chunk(rev, df=df) for rev in revschunk]
2040 return [self._chunk(rev, df=df) for rev in revschunk]
2040
2041
2041 decomp = self.decompress
2042 decomp = self.decompress
2042 for rev in revschunk:
2043 for rev in revschunk:
2043 chunkstart = start(rev)
2044 chunkstart = start(rev)
2044 if inline:
2045 if inline:
2045 chunkstart += (rev + 1) * iosize
2046 chunkstart += (rev + 1) * iosize
2046 chunklength = length(rev)
2047 chunklength = length(rev)
2047 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
2048 ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
2048
2049
2049 return l
2050 return l
2050
2051
2051 def _chunkclear(self):
2052 def _chunkclear(self):
2052 """Clear the raw chunk cache."""
2053 """Clear the raw chunk cache."""
2053 self._chunkcache = (0, '')
2054 self._chunkcache = (0, '')
2054
2055
2055 def deltaparent(self, rev):
2056 def deltaparent(self, rev):
2056 """return deltaparent of the given revision"""
2057 """return deltaparent of the given revision"""
2057 base = self.index[rev][3]
2058 base = self.index[rev][3]
2058 if base == rev:
2059 if base == rev:
2059 return nullrev
2060 return nullrev
2060 elif self._generaldelta:
2061 elif self._generaldelta:
2061 return base
2062 return base
2062 else:
2063 else:
2063 return rev - 1
2064 return rev - 1
2064
2065
2065 def revdiff(self, rev1, rev2):
2066 def revdiff(self, rev1, rev2):
2066 """return or calculate a delta between two revisions
2067 """return or calculate a delta between two revisions
2067
2068
2068 The delta calculated is in binary form and is intended to be written to
2069 The delta calculated is in binary form and is intended to be written to
2069 revlog data directly. So this function needs raw revision data.
2070 revlog data directly. So this function needs raw revision data.
2070 """
2071 """
2071 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2072 if rev1 != nullrev and self.deltaparent(rev2) == rev1:
2072 return bytes(self._chunk(rev2))
2073 return bytes(self._chunk(rev2))
2073
2074
2074 return mdiff.textdiff(self.revision(rev1, raw=True),
2075 return mdiff.textdiff(self.revision(rev1, raw=True),
2075 self.revision(rev2, raw=True))
2076 self.revision(rev2, raw=True))
2076
2077
2077 def revision(self, nodeorrev, _df=None, raw=False):
2078 def revision(self, nodeorrev, _df=None, raw=False):
2078 """return an uncompressed revision of a given node or revision
2079 """return an uncompressed revision of a given node or revision
2079 number.
2080 number.
2080
2081
2081 _df - an existing file handle to read from. (internal-only)
2082 _df - an existing file handle to read from. (internal-only)
2082 raw - an optional argument specifying if the revision data is to be
2083 raw - an optional argument specifying if the revision data is to be
2083 treated as raw data when applying flag transforms. 'raw' should be set
2084 treated as raw data when applying flag transforms. 'raw' should be set
2084 to True when generating changegroups or in debug commands.
2085 to True when generating changegroups or in debug commands.
2085 """
2086 """
2086 if isinstance(nodeorrev, int):
2087 if isinstance(nodeorrev, int):
2087 rev = nodeorrev
2088 rev = nodeorrev
2088 node = self.node(rev)
2089 node = self.node(rev)
2089 else:
2090 else:
2090 node = nodeorrev
2091 node = nodeorrev
2091 rev = None
2092 rev = None
2092
2093
2093 cachedrev = None
2094 cachedrev = None
2094 flags = None
2095 flags = None
2095 rawtext = None
2096 rawtext = None
2096 if node == nullid:
2097 if node == nullid:
2097 return ""
2098 return ""
2098 if self._cache:
2099 if self._cache:
2099 if self._cache[0] == node:
2100 if self._cache[0] == node:
2100 # _cache only stores rawtext
2101 # _cache only stores rawtext
2101 if raw:
2102 if raw:
2102 return self._cache[2]
2103 return self._cache[2]
2103 # duplicated, but good for perf
2104 # duplicated, but good for perf
2104 if rev is None:
2105 if rev is None:
2105 rev = self.rev(node)
2106 rev = self.rev(node)
2106 if flags is None:
2107 if flags is None:
2107 flags = self.flags(rev)
2108 flags = self.flags(rev)
2108 # no extra flags set, no flag processor runs, text = rawtext
2109 # no extra flags set, no flag processor runs, text = rawtext
2109 if flags == REVIDX_DEFAULT_FLAGS:
2110 if flags == REVIDX_DEFAULT_FLAGS:
2110 return self._cache[2]
2111 return self._cache[2]
2111 # rawtext is reusable. need to run flag processor
2112 # rawtext is reusable. need to run flag processor
2112 rawtext = self._cache[2]
2113 rawtext = self._cache[2]
2113
2114
2114 cachedrev = self._cache[1]
2115 cachedrev = self._cache[1]
2115
2116
2116 # look up what we need to read
2117 # look up what we need to read
2117 if rawtext is None:
2118 if rawtext is None:
2118 if rev is None:
2119 if rev is None:
2119 rev = self.rev(node)
2120 rev = self.rev(node)
2120
2121
2121 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2122 chain, stopped = self._deltachain(rev, stoprev=cachedrev)
2122 if stopped:
2123 if stopped:
2123 rawtext = self._cache[2]
2124 rawtext = self._cache[2]
2124
2125
2125 # drop cache to save memory
2126 # drop cache to save memory
2126 self._cache = None
2127 self._cache = None
2127
2128
2128 targetsize = None
2129 targetsize = None
2129 rawsize = self.index[rev][2]
2130 rawsize = self.index[rev][2]
2130 if 0 <= rawsize:
2131 if 0 <= rawsize:
2131 targetsize = 4 * rawsize
2132 targetsize = 4 * rawsize
2132
2133
2133 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2134 bins = self._chunks(chain, df=_df, targetsize=targetsize)
2134 if rawtext is None:
2135 if rawtext is None:
2135 rawtext = bytes(bins[0])
2136 rawtext = bytes(bins[0])
2136 bins = bins[1:]
2137 bins = bins[1:]
2137
2138
2138 rawtext = mdiff.patches(rawtext, bins)
2139 rawtext = mdiff.patches(rawtext, bins)
2139 self._cache = (node, rev, rawtext)
2140 self._cache = (node, rev, rawtext)
2140
2141
2141 if flags is None:
2142 if flags is None:
2142 if rev is None:
2143 if rev is None:
2143 rev = self.rev(node)
2144 rev = self.rev(node)
2144 flags = self.flags(rev)
2145 flags = self.flags(rev)
2145
2146
2146 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
2147 text, validatehash = self._processflags(rawtext, flags, 'read', raw=raw)
2147 if validatehash:
2148 if validatehash:
2148 self.checkhash(text, node, rev=rev)
2149 self.checkhash(text, node, rev=rev)
2149
2150
2150 return text
2151 return text
2151
2152
2152 def hash(self, text, p1, p2):
2153 def hash(self, text, p1, p2):
2153 """Compute a node hash.
2154 """Compute a node hash.
2154
2155
2155 Available as a function so that subclasses can replace the hash
2156 Available as a function so that subclasses can replace the hash
2156 as needed.
2157 as needed.
2157 """
2158 """
2158 return hash(text, p1, p2)
2159 return hash(text, p1, p2)
2159
2160
2160 def _processflags(self, text, flags, operation, raw=False):
2161 def _processflags(self, text, flags, operation, raw=False):
2161 """Inspect revision data flags and applies transforms defined by
2162 """Inspect revision data flags and applies transforms defined by
2162 registered flag processors.
2163 registered flag processors.
2163
2164
2164 ``text`` - the revision data to process
2165 ``text`` - the revision data to process
2165 ``flags`` - the revision flags
2166 ``flags`` - the revision flags
2166 ``operation`` - the operation being performed (read or write)
2167 ``operation`` - the operation being performed (read or write)
2167 ``raw`` - an optional argument describing if the raw transform should be
2168 ``raw`` - an optional argument describing if the raw transform should be
2168 applied.
2169 applied.
2169
2170
2170 This method processes the flags in the order (or reverse order if
2171 This method processes the flags in the order (or reverse order if
2171 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
2172 ``operation`` is 'write') defined by REVIDX_FLAGS_ORDER, applying the
2172 flag processors registered for present flags. The order of flags defined
2173 flag processors registered for present flags. The order of flags defined
2173 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
2174 in REVIDX_FLAGS_ORDER needs to be stable to allow non-commutativity.
2174
2175
2175 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
2176 Returns a 2-tuple of ``(text, validatehash)`` where ``text`` is the
2176 processed text and ``validatehash`` is a bool indicating whether the
2177 processed text and ``validatehash`` is a bool indicating whether the
2177 returned text should be checked for hash integrity.
2178 returned text should be checked for hash integrity.
2178
2179
2179 Note: If the ``raw`` argument is set, it has precedence over the
2180 Note: If the ``raw`` argument is set, it has precedence over the
2180 operation and will only update the value of ``validatehash``.
2181 operation and will only update the value of ``validatehash``.
2181 """
2182 """
2182 # fast path: no flag processors will run
2183 # fast path: no flag processors will run
2183 if flags == 0:
2184 if flags == 0:
2184 return text, True
2185 return text, True
2185 if not operation in ('read', 'write'):
2186 if not operation in ('read', 'write'):
2186 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
2187 raise ProgrammingError(_("invalid '%s' operation ") % (operation))
2187 # Check all flags are known.
2188 # Check all flags are known.
2188 if flags & ~REVIDX_KNOWN_FLAGS:
2189 if flags & ~REVIDX_KNOWN_FLAGS:
2189 raise RevlogError(_("incompatible revision flag '%#x'") %
2190 raise RevlogError(_("incompatible revision flag '%#x'") %
2190 (flags & ~REVIDX_KNOWN_FLAGS))
2191 (flags & ~REVIDX_KNOWN_FLAGS))
2191 validatehash = True
2192 validatehash = True
2192 # Depending on the operation (read or write), the order might be
2193 # Depending on the operation (read or write), the order might be
2193 # reversed due to non-commutative transforms.
2194 # reversed due to non-commutative transforms.
2194 orderedflags = REVIDX_FLAGS_ORDER
2195 orderedflags = REVIDX_FLAGS_ORDER
2195 if operation == 'write':
2196 if operation == 'write':
2196 orderedflags = reversed(orderedflags)
2197 orderedflags = reversed(orderedflags)
2197
2198
2198 for flag in orderedflags:
2199 for flag in orderedflags:
2199 # If a flagprocessor has been registered for a known flag, apply the
2200 # If a flagprocessor has been registered for a known flag, apply the
2200 # related operation transform and update result tuple.
2201 # related operation transform and update result tuple.
2201 if flag & flags:
2202 if flag & flags:
2202 vhash = True
2203 vhash = True
2203
2204
2204 if flag not in _flagprocessors:
2205 if flag not in _flagprocessors:
2205 message = _("missing processor for flag '%#x'") % (flag)
2206 message = _("missing processor for flag '%#x'") % (flag)
2206 raise RevlogError(message)
2207 raise RevlogError(message)
2207
2208
2208 processor = _flagprocessors[flag]
2209 processor = _flagprocessors[flag]
2209 if processor is not None:
2210 if processor is not None:
2210 readtransform, writetransform, rawtransform = processor
2211 readtransform, writetransform, rawtransform = processor
2211
2212
2212 if raw:
2213 if raw:
2213 vhash = rawtransform(self, text)
2214 vhash = rawtransform(self, text)
2214 elif operation == 'read':
2215 elif operation == 'read':
2215 text, vhash = readtransform(self, text)
2216 text, vhash = readtransform(self, text)
2216 else: # write operation
2217 else: # write operation
2217 text, vhash = writetransform(self, text)
2218 text, vhash = writetransform(self, text)
2218 validatehash = validatehash and vhash
2219 validatehash = validatehash and vhash
2219
2220
2220 return text, validatehash
2221 return text, validatehash
2221
2222
2222 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2223 def checkhash(self, text, node, p1=None, p2=None, rev=None):
2223 """Check node hash integrity.
2224 """Check node hash integrity.
2224
2225
2225 Available as a function so that subclasses can extend hash mismatch
2226 Available as a function so that subclasses can extend hash mismatch
2226 behaviors as needed.
2227 behaviors as needed.
2227 """
2228 """
2228 try:
2229 try:
2229 if p1 is None and p2 is None:
2230 if p1 is None and p2 is None:
2230 p1, p2 = self.parents(node)
2231 p1, p2 = self.parents(node)
2231 if node != self.hash(text, p1, p2):
2232 if node != self.hash(text, p1, p2):
2232 revornode = rev
2233 revornode = rev
2233 if revornode is None:
2234 if revornode is None:
2234 revornode = templatefilters.short(hex(node))
2235 revornode = templatefilters.short(hex(node))
2235 raise RevlogError(_("integrity check failed on %s:%s")
2236 raise RevlogError(_("integrity check failed on %s:%s")
2236 % (self.indexfile, pycompat.bytestr(revornode)))
2237 % (self.indexfile, pycompat.bytestr(revornode)))
2237 except RevlogError:
2238 except RevlogError:
2238 if self._censorable and _censoredtext(text):
2239 if self._censorable and _censoredtext(text):
2239 raise error.CensoredNodeError(self.indexfile, node, text)
2240 raise error.CensoredNodeError(self.indexfile, node, text)
2240 raise
2241 raise
2241
2242
2242 def _enforceinlinesize(self, tr, fp=None):
2243 def _enforceinlinesize(self, tr, fp=None):
2243 """Check if the revlog is too big for inline and convert if so.
2244 """Check if the revlog is too big for inline and convert if so.
2244
2245
2245 This should be called after revisions are added to the revlog. If the
2246 This should be called after revisions are added to the revlog. If the
2246 revlog has grown too large to be an inline revlog, it will convert it
2247 revlog has grown too large to be an inline revlog, it will convert it
2247 to use multiple index and data files.
2248 to use multiple index and data files.
2248 """
2249 """
2249 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
2250 if not self._inline or (self.start(-2) + self.length(-2)) < _maxinline:
2250 return
2251 return
2251
2252
2252 trinfo = tr.find(self.indexfile)
2253 trinfo = tr.find(self.indexfile)
2253 if trinfo is None:
2254 if trinfo is None:
2254 raise RevlogError(_("%s not found in the transaction")
2255 raise RevlogError(_("%s not found in the transaction")
2255 % self.indexfile)
2256 % self.indexfile)
2256
2257
2257 trindex = trinfo[2]
2258 trindex = trinfo[2]
2258 if trindex is not None:
2259 if trindex is not None:
2259 dataoff = self.start(trindex)
2260 dataoff = self.start(trindex)
2260 else:
2261 else:
2261 # revlog was stripped at start of transaction, use all leftover data
2262 # revlog was stripped at start of transaction, use all leftover data
2262 trindex = len(self) - 1
2263 trindex = len(self) - 1
2263 dataoff = self.end(-2)
2264 dataoff = self.end(-2)
2264
2265
2265 tr.add(self.datafile, dataoff)
2266 tr.add(self.datafile, dataoff)
2266
2267
2267 if fp:
2268 if fp:
2268 fp.flush()
2269 fp.flush()
2269 fp.close()
2270 fp.close()
2270
2271
2271 with self._datafp('w') as df:
2272 with self._datafp('w') as df:
2272 for r in self:
2273 for r in self:
2273 df.write(self._getsegmentforrevs(r, r)[1])
2274 df.write(self._getsegmentforrevs(r, r)[1])
2274
2275
2275 with self._indexfp('w') as fp:
2276 with self._indexfp('w') as fp:
2276 self.version &= ~FLAG_INLINE_DATA
2277 self.version &= ~FLAG_INLINE_DATA
2277 self._inline = False
2278 self._inline = False
2278 io = self._io
2279 io = self._io
2279 for i in self:
2280 for i in self:
2280 e = io.packentry(self.index[i], self.node, self.version, i)
2281 e = io.packentry(self.index[i], self.node, self.version, i)
2281 fp.write(e)
2282 fp.write(e)
2282
2283
2283 # the temp file replace the real index when we exit the context
2284 # the temp file replace the real index when we exit the context
2284 # manager
2285 # manager
2285
2286
2286 tr.replace(self.indexfile, trindex * self._io.size)
2287 tr.replace(self.indexfile, trindex * self._io.size)
2287 self._chunkclear()
2288 self._chunkclear()
2288
2289
2289 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
2290 def addrevision(self, text, transaction, link, p1, p2, cachedelta=None,
2290 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
2291 node=None, flags=REVIDX_DEFAULT_FLAGS, deltacomputer=None):
2291 """add a revision to the log
2292 """add a revision to the log
2292
2293
2293 text - the revision data to add
2294 text - the revision data to add
2294 transaction - the transaction object used for rollback
2295 transaction - the transaction object used for rollback
2295 link - the linkrev data to add
2296 link - the linkrev data to add
2296 p1, p2 - the parent nodeids of the revision
2297 p1, p2 - the parent nodeids of the revision
2297 cachedelta - an optional precomputed delta
2298 cachedelta - an optional precomputed delta
2298 node - nodeid of revision; typically node is not specified, and it is
2299 node - nodeid of revision; typically node is not specified, and it is
2299 computed by default as hash(text, p1, p2), however subclasses might
2300 computed by default as hash(text, p1, p2), however subclasses might
2300 use different hashing method (and override checkhash() in such case)
2301 use different hashing method (and override checkhash() in such case)
2301 flags - the known flags to set on the revision
2302 flags - the known flags to set on the revision
2302 deltacomputer - an optional _deltacomputer instance shared between
2303 deltacomputer - an optional _deltacomputer instance shared between
2303 multiple calls
2304 multiple calls
2304 """
2305 """
2305 if link == nullrev:
2306 if link == nullrev:
2306 raise RevlogError(_("attempted to add linkrev -1 to %s")
2307 raise RevlogError(_("attempted to add linkrev -1 to %s")
2307 % self.indexfile)
2308 % self.indexfile)
2308
2309
2309 if flags:
2310 if flags:
2310 node = node or self.hash(text, p1, p2)
2311 node = node or self.hash(text, p1, p2)
2311
2312
2312 rawtext, validatehash = self._processflags(text, flags, 'write')
2313 rawtext, validatehash = self._processflags(text, flags, 'write')
2313
2314
2314 # If the flag processor modifies the revision data, ignore any provided
2315 # If the flag processor modifies the revision data, ignore any provided
2315 # cachedelta.
2316 # cachedelta.
2316 if rawtext != text:
2317 if rawtext != text:
2317 cachedelta = None
2318 cachedelta = None
2318
2319
2319 if len(rawtext) > _maxentrysize:
2320 if len(rawtext) > _maxentrysize:
2320 raise RevlogError(
2321 raise RevlogError(
2321 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
2322 _("%s: size of %d bytes exceeds maximum revlog storage of 2GiB")
2322 % (self.indexfile, len(rawtext)))
2323 % (self.indexfile, len(rawtext)))
2323
2324
2324 node = node or self.hash(rawtext, p1, p2)
2325 node = node or self.hash(rawtext, p1, p2)
2325 if node in self.nodemap:
2326 if node in self.nodemap:
2326 return node
2327 return node
2327
2328
2328 if validatehash:
2329 if validatehash:
2329 self.checkhash(rawtext, node, p1=p1, p2=p2)
2330 self.checkhash(rawtext, node, p1=p1, p2=p2)
2330
2331
2331 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
2332 return self.addrawrevision(rawtext, transaction, link, p1, p2, node,
2332 flags, cachedelta=cachedelta,
2333 flags, cachedelta=cachedelta,
2333 deltacomputer=deltacomputer)
2334 deltacomputer=deltacomputer)
2334
2335
2335 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
2336 def addrawrevision(self, rawtext, transaction, link, p1, p2, node, flags,
2336 cachedelta=None, deltacomputer=None):
2337 cachedelta=None, deltacomputer=None):
2337 """add a raw revision with known flags, node and parents
2338 """add a raw revision with known flags, node and parents
2338 useful when reusing a revision not stored in this revlog (ex: received
2339 useful when reusing a revision not stored in this revlog (ex: received
2339 over wire, or read from an external bundle).
2340 over wire, or read from an external bundle).
2340 """
2341 """
2341 dfh = None
2342 dfh = None
2342 if not self._inline:
2343 if not self._inline:
2343 dfh = self._datafp("a+")
2344 dfh = self._datafp("a+")
2344 ifh = self._indexfp("a+")
2345 ifh = self._indexfp("a+")
2345 try:
2346 try:
2346 return self._addrevision(node, rawtext, transaction, link, p1, p2,
2347 return self._addrevision(node, rawtext, transaction, link, p1, p2,
2347 flags, cachedelta, ifh, dfh,
2348 flags, cachedelta, ifh, dfh,
2348 deltacomputer=deltacomputer)
2349 deltacomputer=deltacomputer)
2349 finally:
2350 finally:
2350 if dfh:
2351 if dfh:
2351 dfh.close()
2352 dfh.close()
2352 ifh.close()
2353 ifh.close()
2353
2354
2354 def compress(self, data):
2355 def compress(self, data):
2355 """Generate a possibly-compressed representation of data."""
2356 """Generate a possibly-compressed representation of data."""
2356 if not data:
2357 if not data:
2357 return '', data
2358 return '', data
2358
2359
2359 compressed = self._compressor.compress(data)
2360 compressed = self._compressor.compress(data)
2360
2361
2361 if compressed:
2362 if compressed:
2362 # The revlog compressor added the header in the returned data.
2363 # The revlog compressor added the header in the returned data.
2363 return '', compressed
2364 return '', compressed
2364
2365
2365 if data[0:1] == '\0':
2366 if data[0:1] == '\0':
2366 return '', data
2367 return '', data
2367 return 'u', data
2368 return 'u', data
2368
2369
2369 def decompress(self, data):
2370 def decompress(self, data):
2370 """Decompress a revlog chunk.
2371 """Decompress a revlog chunk.
2371
2372
2372 The chunk is expected to begin with a header identifying the
2373 The chunk is expected to begin with a header identifying the
2373 format type so it can be routed to an appropriate decompressor.
2374 format type so it can be routed to an appropriate decompressor.
2374 """
2375 """
2375 if not data:
2376 if not data:
2376 return data
2377 return data
2377
2378
2378 # Revlogs are read much more frequently than they are written and many
2379 # Revlogs are read much more frequently than they are written and many
2379 # chunks only take microseconds to decompress, so performance is
2380 # chunks only take microseconds to decompress, so performance is
2380 # important here.
2381 # important here.
2381 #
2382 #
2382 # We can make a few assumptions about revlogs:
2383 # We can make a few assumptions about revlogs:
2383 #
2384 #
2384 # 1) the majority of chunks will be compressed (as opposed to inline
2385 # 1) the majority of chunks will be compressed (as opposed to inline
2385 # raw data).
2386 # raw data).
2386 # 2) decompressing *any* data will likely by at least 10x slower than
2387 # 2) decompressing *any* data will likely by at least 10x slower than
2387 # returning raw inline data.
2388 # returning raw inline data.
2388 # 3) we want to prioritize common and officially supported compression
2389 # 3) we want to prioritize common and officially supported compression
2389 # engines
2390 # engines
2390 #
2391 #
2391 # It follows that we want to optimize for "decompress compressed data
2392 # It follows that we want to optimize for "decompress compressed data
2392 # when encoded with common and officially supported compression engines"
2393 # when encoded with common and officially supported compression engines"
2393 # case over "raw data" and "data encoded by less common or non-official
2394 # case over "raw data" and "data encoded by less common or non-official
2394 # compression engines." That is why we have the inline lookup first
2395 # compression engines." That is why we have the inline lookup first
2395 # followed by the compengines lookup.
2396 # followed by the compengines lookup.
2396 #
2397 #
2397 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2398 # According to `hg perfrevlogchunks`, this is ~0.5% faster for zlib
2398 # compressed chunks. And this matters for changelog and manifest reads.
2399 # compressed chunks. And this matters for changelog and manifest reads.
2399 t = data[0:1]
2400 t = data[0:1]
2400
2401
2401 if t == 'x':
2402 if t == 'x':
2402 try:
2403 try:
2403 return _zlibdecompress(data)
2404 return _zlibdecompress(data)
2404 except zlib.error as e:
2405 except zlib.error as e:
2405 raise RevlogError(_('revlog decompress error: %s') %
2406 raise RevlogError(_('revlog decompress error: %s') %
2406 stringutil.forcebytestr(e))
2407 stringutil.forcebytestr(e))
2407 # '\0' is more common than 'u' so it goes first.
2408 # '\0' is more common than 'u' so it goes first.
2408 elif t == '\0':
2409 elif t == '\0':
2409 return data
2410 return data
2410 elif t == 'u':
2411 elif t == 'u':
2411 return util.buffer(data, 1)
2412 return util.buffer(data, 1)
2412
2413
2413 try:
2414 try:
2414 compressor = self._decompressors[t]
2415 compressor = self._decompressors[t]
2415 except KeyError:
2416 except KeyError:
2416 try:
2417 try:
2417 engine = util.compengines.forrevlogheader(t)
2418 engine = util.compengines.forrevlogheader(t)
2418 compressor = engine.revlogcompressor()
2419 compressor = engine.revlogcompressor()
2419 self._decompressors[t] = compressor
2420 self._decompressors[t] = compressor
2420 except KeyError:
2421 except KeyError:
2421 raise RevlogError(_('unknown compression type %r') % t)
2422 raise RevlogError(_('unknown compression type %r') % t)
2422
2423
2423 return compressor.decompress(data)
2424 return compressor.decompress(data)
2424
2425
2425 def _isgooddeltainfo(self, deltainfo, revinfo):
2426 def _isgooddeltainfo(self, deltainfo, revinfo):
2426 """Returns True if the given delta is good. Good means that it is within
2427 """Returns True if the given delta is good. Good means that it is within
2427 the disk span, disk size, and chain length bounds that we know to be
2428 the disk span, disk size, and chain length bounds that we know to be
2428 performant."""
2429 performant."""
2429 if deltainfo is None:
2430 if deltainfo is None:
2430 return False
2431 return False
2431
2432
2432 # - 'deltainfo.distance' is the distance from the base revision --
2433 # - 'deltainfo.distance' is the distance from the base revision --
2433 # bounding it limits the amount of I/O we need to do.
2434 # bounding it limits the amount of I/O we need to do.
2434 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
2435 # - 'deltainfo.compresseddeltalen' is the sum of the total size of
2435 # deltas we need to apply -- bounding it limits the amount of CPU
2436 # deltas we need to apply -- bounding it limits the amount of CPU
2436 # we consume.
2437 # we consume.
2437
2438
2438 if self._sparserevlog:
2439 if self._sparserevlog:
2439 # As sparse-read will be used, we can consider that the distance,
2440 # As sparse-read will be used, we can consider that the distance,
2440 # instead of being the span of the whole chunk,
2441 # instead of being the span of the whole chunk,
2441 # is the span of the largest read chunk
2442 # is the span of the largest read chunk
2442 base = deltainfo.base
2443 base = deltainfo.base
2443
2444
2444 if base != nullrev:
2445 if base != nullrev:
2445 deltachain = self._deltachain(base)[0]
2446 deltachain = self._deltachain(base)[0]
2446 else:
2447 else:
2447 deltachain = []
2448 deltachain = []
2448
2449
2449 chunks = _slicechunk(self, deltachain, deltainfo)
2450 chunks = _slicechunk(self, deltachain, deltainfo)
2450 distance = max(map(lambda revs:_segmentspan(self, revs), chunks))
2451 distance = max(map(lambda revs:_segmentspan(self, revs), chunks))
2451 else:
2452 else:
2452 distance = deltainfo.distance
2453 distance = deltainfo.distance
2453
2454
2454 textlen = revinfo.textlen
2455 textlen = revinfo.textlen
2455 defaultmax = textlen * 4
2456 defaultmax = textlen * 4
2456 maxdist = self._maxdeltachainspan
2457 maxdist = self._maxdeltachainspan
2457 if not maxdist:
2458 if not maxdist:
2458 maxdist = distance # ensure the conditional pass
2459 maxdist = distance # ensure the conditional pass
2459 maxdist = max(maxdist, defaultmax)
2460 maxdist = max(maxdist, defaultmax)
2460 if self._sparserevlog and maxdist < self._srmingapsize:
2461 if self._sparserevlog and maxdist < self._srmingapsize:
2461 # In multiple place, we are ignoring irrelevant data range below a
2462 # In multiple place, we are ignoring irrelevant data range below a
2462 # certain size. Be also apply this tradeoff here and relax span
2463 # certain size. Be also apply this tradeoff here and relax span
2463 # constraint for small enought content.
2464 # constraint for small enought content.
2464 maxdist = self._srmingapsize
2465 maxdist = self._srmingapsize
2465 if (distance > maxdist or deltainfo.deltalen > textlen or
2466 if (distance > maxdist or deltainfo.deltalen > textlen or
2466 deltainfo.compresseddeltalen > textlen * 2 or
2467 deltainfo.compresseddeltalen > textlen * 2 or
2467 (self._maxchainlen and deltainfo.chainlen > self._maxchainlen)):
2468 (self._maxchainlen and deltainfo.chainlen > self._maxchainlen)):
2468 return False
2469 return False
2469
2470
2470 return True
2471 return True
2471
2472
2472 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2473 def _addrevision(self, node, rawtext, transaction, link, p1, p2, flags,
2473 cachedelta, ifh, dfh, alwayscache=False,
2474 cachedelta, ifh, dfh, alwayscache=False,
2474 deltacomputer=None):
2475 deltacomputer=None):
2475 """internal function to add revisions to the log
2476 """internal function to add revisions to the log
2476
2477
2477 see addrevision for argument descriptions.
2478 see addrevision for argument descriptions.
2478
2479
2479 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2480 note: "addrevision" takes non-raw text, "_addrevision" takes raw text.
2480
2481
2481 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2482 if "deltacomputer" is not provided or None, a defaultdeltacomputer will
2482 be used.
2483 be used.
2483
2484
2484 invariants:
2485 invariants:
2485 - rawtext is optional (can be None); if not set, cachedelta must be set.
2486 - rawtext is optional (can be None); if not set, cachedelta must be set.
2486 if both are set, they must correspond to each other.
2487 if both are set, they must correspond to each other.
2487 """
2488 """
2488 if node == nullid:
2489 if node == nullid:
2489 raise RevlogError(_("%s: attempt to add null revision") %
2490 raise RevlogError(_("%s: attempt to add null revision") %
2490 (self.indexfile))
2491 (self.indexfile))
2491 if node == wdirid or node in wdirfilenodeids:
2492 if node == wdirid or node in wdirfilenodeids:
2492 raise RevlogError(_("%s: attempt to add wdir revision") %
2493 raise RevlogError(_("%s: attempt to add wdir revision") %
2493 (self.indexfile))
2494 (self.indexfile))
2494
2495
2495 if self._inline:
2496 if self._inline:
2496 fh = ifh
2497 fh = ifh
2497 else:
2498 else:
2498 fh = dfh
2499 fh = dfh
2499
2500
2500 btext = [rawtext]
2501 btext = [rawtext]
2501
2502
2502 curr = len(self)
2503 curr = len(self)
2503 prev = curr - 1
2504 prev = curr - 1
2504 offset = self.end(prev)
2505 offset = self.end(prev)
2505 p1r, p2r = self.rev(p1), self.rev(p2)
2506 p1r, p2r = self.rev(p1), self.rev(p2)
2506
2507
2507 # full versions are inserted when the needed deltas
2508 # full versions are inserted when the needed deltas
2508 # become comparable to the uncompressed text
2509 # become comparable to the uncompressed text
2509 if rawtext is None:
2510 if rawtext is None:
2510 # need rawtext size, before changed by flag processors, which is
2511 # need rawtext size, before changed by flag processors, which is
2511 # the non-raw size. use revlog explicitly to avoid filelog's extra
2512 # the non-raw size. use revlog explicitly to avoid filelog's extra
2512 # logic that might remove metadata size.
2513 # logic that might remove metadata size.
2513 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2514 textlen = mdiff.patchedsize(revlog.size(self, cachedelta[0]),
2514 cachedelta[1])
2515 cachedelta[1])
2515 else:
2516 else:
2516 textlen = len(rawtext)
2517 textlen = len(rawtext)
2517
2518
2518 if deltacomputer is None:
2519 if deltacomputer is None:
2519 deltacomputer = _deltacomputer(self)
2520 deltacomputer = _deltacomputer(self)
2520
2521
2521 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2522 revinfo = _revisioninfo(node, p1, p2, btext, textlen, cachedelta, flags)
2522
2523
2523 # no delta for flag processor revision (see "candelta" for why)
2524 # no delta for flag processor revision (see "candelta" for why)
2524 # not calling candelta since only one revision needs test, also to
2525 # not calling candelta since only one revision needs test, also to
2525 # avoid overhead fetching flags again.
2526 # avoid overhead fetching flags again.
2526 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
2527 if flags & REVIDX_RAWTEXT_CHANGING_FLAGS:
2527 deltainfo = None
2528 deltainfo = None
2528 else:
2529 else:
2529 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2530 deltainfo = deltacomputer.finddeltainfo(revinfo, fh)
2530
2531
2531 if deltainfo is not None:
2532 if deltainfo is not None:
2532 base = deltainfo.base
2533 base = deltainfo.base
2533 chainbase = deltainfo.chainbase
2534 chainbase = deltainfo.chainbase
2534 data = deltainfo.data
2535 data = deltainfo.data
2535 l = deltainfo.deltalen
2536 l = deltainfo.deltalen
2536 else:
2537 else:
2537 rawtext = deltacomputer.buildtext(revinfo, fh)
2538 rawtext = deltacomputer.buildtext(revinfo, fh)
2538 data = self.compress(rawtext)
2539 data = self.compress(rawtext)
2539 l = len(data[1]) + len(data[0])
2540 l = len(data[1]) + len(data[0])
2540 base = chainbase = curr
2541 base = chainbase = curr
2541
2542
2542 e = (offset_type(offset, flags), l, textlen,
2543 e = (offset_type(offset, flags), l, textlen,
2543 base, link, p1r, p2r, node)
2544 base, link, p1r, p2r, node)
2544 self.index.insert(-1, e)
2545 self.index.insert(-1, e)
2545 self.nodemap[node] = curr
2546 self.nodemap[node] = curr
2546
2547
2547 entry = self._io.packentry(e, self.node, self.version, curr)
2548 entry = self._io.packentry(e, self.node, self.version, curr)
2548 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2549 self._writeentry(transaction, ifh, dfh, entry, data, link, offset)
2549
2550
2550 if alwayscache and rawtext is None:
2551 if alwayscache and rawtext is None:
2551 rawtext = deltacomputer._buildtext(revinfo, fh)
2552 rawtext = deltacomputer._buildtext(revinfo, fh)
2552
2553
2553 if type(rawtext) == bytes: # only accept immutable objects
2554 if type(rawtext) == bytes: # only accept immutable objects
2554 self._cache = (node, curr, rawtext)
2555 self._cache = (node, curr, rawtext)
2555 self._chainbasecache[curr] = chainbase
2556 self._chainbasecache[curr] = chainbase
2556 return node
2557 return node
2557
2558
2558 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2559 def _writeentry(self, transaction, ifh, dfh, entry, data, link, offset):
2559 # Files opened in a+ mode have inconsistent behavior on various
2560 # Files opened in a+ mode have inconsistent behavior on various
2560 # platforms. Windows requires that a file positioning call be made
2561 # platforms. Windows requires that a file positioning call be made
2561 # when the file handle transitions between reads and writes. See
2562 # when the file handle transitions between reads and writes. See
2562 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2563 # 3686fa2b8eee and the mixedfilemodewrapper in windows.py. On other
2563 # platforms, Python or the platform itself can be buggy. Some versions
2564 # platforms, Python or the platform itself can be buggy. Some versions
2564 # of Solaris have been observed to not append at the end of the file
2565 # of Solaris have been observed to not append at the end of the file
2565 # if the file was seeked to before the end. See issue4943 for more.
2566 # if the file was seeked to before the end. See issue4943 for more.
2566 #
2567 #
2567 # We work around this issue by inserting a seek() before writing.
2568 # We work around this issue by inserting a seek() before writing.
2568 # Note: This is likely not necessary on Python 3.
2569 # Note: This is likely not necessary on Python 3.
2569 ifh.seek(0, os.SEEK_END)
2570 ifh.seek(0, os.SEEK_END)
2570 if dfh:
2571 if dfh:
2571 dfh.seek(0, os.SEEK_END)
2572 dfh.seek(0, os.SEEK_END)
2572
2573
2573 curr = len(self) - 1
2574 curr = len(self) - 1
2574 if not self._inline:
2575 if not self._inline:
2575 transaction.add(self.datafile, offset)
2576 transaction.add(self.datafile, offset)
2576 transaction.add(self.indexfile, curr * len(entry))
2577 transaction.add(self.indexfile, curr * len(entry))
2577 if data[0]:
2578 if data[0]:
2578 dfh.write(data[0])
2579 dfh.write(data[0])
2579 dfh.write(data[1])
2580 dfh.write(data[1])
2580 ifh.write(entry)
2581 ifh.write(entry)
2581 else:
2582 else:
2582 offset += curr * self._io.size
2583 offset += curr * self._io.size
2583 transaction.add(self.indexfile, offset, curr)
2584 transaction.add(self.indexfile, offset, curr)
2584 ifh.write(entry)
2585 ifh.write(entry)
2585 ifh.write(data[0])
2586 ifh.write(data[0])
2586 ifh.write(data[1])
2587 ifh.write(data[1])
2587 self._enforceinlinesize(transaction, ifh)
2588 self._enforceinlinesize(transaction, ifh)
2588
2589
2589 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2590 def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
2590 """
2591 """
2591 add a delta group
2592 add a delta group
2592
2593
2593 given a set of deltas, add them to the revision log. the
2594 given a set of deltas, add them to the revision log. the
2594 first delta is against its parent, which should be in our
2595 first delta is against its parent, which should be in our
2595 log, the rest are against the previous delta.
2596 log, the rest are against the previous delta.
2596
2597
2597 If ``addrevisioncb`` is defined, it will be called with arguments of
2598 If ``addrevisioncb`` is defined, it will be called with arguments of
2598 this revlog and the node that was added.
2599 this revlog and the node that was added.
2599 """
2600 """
2600
2601
2601 nodes = []
2602 nodes = []
2602
2603
2603 r = len(self)
2604 r = len(self)
2604 end = 0
2605 end = 0
2605 if r:
2606 if r:
2606 end = self.end(r - 1)
2607 end = self.end(r - 1)
2607 ifh = self._indexfp("a+")
2608 ifh = self._indexfp("a+")
2608 isize = r * self._io.size
2609 isize = r * self._io.size
2609 if self._inline:
2610 if self._inline:
2610 transaction.add(self.indexfile, end + isize, r)
2611 transaction.add(self.indexfile, end + isize, r)
2611 dfh = None
2612 dfh = None
2612 else:
2613 else:
2613 transaction.add(self.indexfile, isize, r)
2614 transaction.add(self.indexfile, isize, r)
2614 transaction.add(self.datafile, end)
2615 transaction.add(self.datafile, end)
2615 dfh = self._datafp("a+")
2616 dfh = self._datafp("a+")
2616 def flush():
2617 def flush():
2617 if dfh:
2618 if dfh:
2618 dfh.flush()
2619 dfh.flush()
2619 ifh.flush()
2620 ifh.flush()
2620 try:
2621 try:
2621 deltacomputer = _deltacomputer(self)
2622 deltacomputer = _deltacomputer(self)
2622 # loop through our set of deltas
2623 # loop through our set of deltas
2623 for data in deltas:
2624 for data in deltas:
2624 node, p1, p2, linknode, deltabase, delta, flags = data
2625 node, p1, p2, linknode, deltabase, delta, flags = data
2625 link = linkmapper(linknode)
2626 link = linkmapper(linknode)
2626 flags = flags or REVIDX_DEFAULT_FLAGS
2627 flags = flags or REVIDX_DEFAULT_FLAGS
2627
2628
2628 nodes.append(node)
2629 nodes.append(node)
2629
2630
2630 if node in self.nodemap:
2631 if node in self.nodemap:
2631 # this can happen if two branches make the same change
2632 # this can happen if two branches make the same change
2632 continue
2633 continue
2633
2634
2634 for p in (p1, p2):
2635 for p in (p1, p2):
2635 if p not in self.nodemap:
2636 if p not in self.nodemap:
2636 raise LookupError(p, self.indexfile,
2637 raise LookupError(p, self.indexfile,
2637 _('unknown parent'))
2638 _('unknown parent'))
2638
2639
2639 if deltabase not in self.nodemap:
2640 if deltabase not in self.nodemap:
2640 raise LookupError(deltabase, self.indexfile,
2641 raise LookupError(deltabase, self.indexfile,
2641 _('unknown delta base'))
2642 _('unknown delta base'))
2642
2643
2643 baserev = self.rev(deltabase)
2644 baserev = self.rev(deltabase)
2644
2645
2645 if baserev != nullrev and self.iscensored(baserev):
2646 if baserev != nullrev and self.iscensored(baserev):
2646 # if base is censored, delta must be full replacement in a
2647 # if base is censored, delta must be full replacement in a
2647 # single patch operation
2648 # single patch operation
2648 hlen = struct.calcsize(">lll")
2649 hlen = struct.calcsize(">lll")
2649 oldlen = self.rawsize(baserev)
2650 oldlen = self.rawsize(baserev)
2650 newlen = len(delta) - hlen
2651 newlen = len(delta) - hlen
2651 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2652 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2652 raise error.CensoredBaseError(self.indexfile,
2653 raise error.CensoredBaseError(self.indexfile,
2653 self.node(baserev))
2654 self.node(baserev))
2654
2655
2655 if not flags and self._peek_iscensored(baserev, delta, flush):
2656 if not flags and self._peek_iscensored(baserev, delta, flush):
2656 flags |= REVIDX_ISCENSORED
2657 flags |= REVIDX_ISCENSORED
2657
2658
2658 # We assume consumers of addrevisioncb will want to retrieve
2659 # We assume consumers of addrevisioncb will want to retrieve
2659 # the added revision, which will require a call to
2660 # the added revision, which will require a call to
2660 # revision(). revision() will fast path if there is a cache
2661 # revision(). revision() will fast path if there is a cache
2661 # hit. So, we tell _addrevision() to always cache in this case.
2662 # hit. So, we tell _addrevision() to always cache in this case.
2662 # We're only using addgroup() in the context of changegroup
2663 # We're only using addgroup() in the context of changegroup
2663 # generation so the revision data can always be handled as raw
2664 # generation so the revision data can always be handled as raw
2664 # by the flagprocessor.
2665 # by the flagprocessor.
2665 self._addrevision(node, None, transaction, link,
2666 self._addrevision(node, None, transaction, link,
2666 p1, p2, flags, (baserev, delta),
2667 p1, p2, flags, (baserev, delta),
2667 ifh, dfh,
2668 ifh, dfh,
2668 alwayscache=bool(addrevisioncb),
2669 alwayscache=bool(addrevisioncb),
2669 deltacomputer=deltacomputer)
2670 deltacomputer=deltacomputer)
2670
2671
2671 if addrevisioncb:
2672 if addrevisioncb:
2672 addrevisioncb(self, node)
2673 addrevisioncb(self, node)
2673
2674
2674 if not dfh and not self._inline:
2675 if not dfh and not self._inline:
2675 # addrevision switched from inline to conventional
2676 # addrevision switched from inline to conventional
2676 # reopen the index
2677 # reopen the index
2677 ifh.close()
2678 ifh.close()
2678 dfh = self._datafp("a+")
2679 dfh = self._datafp("a+")
2679 ifh = self._indexfp("a+")
2680 ifh = self._indexfp("a+")
2680 finally:
2681 finally:
2681 if dfh:
2682 if dfh:
2682 dfh.close()
2683 dfh.close()
2683 ifh.close()
2684 ifh.close()
2684
2685
2685 return nodes
2686 return nodes
2686
2687
2687 def iscensored(self, rev):
2688 def iscensored(self, rev):
2688 """Check if a file revision is censored."""
2689 """Check if a file revision is censored."""
2689 if not self._censorable:
2690 if not self._censorable:
2690 return False
2691 return False
2691
2692
2692 return self.flags(rev) & REVIDX_ISCENSORED
2693 return self.flags(rev) & REVIDX_ISCENSORED
2693
2694
2694 def _peek_iscensored(self, baserev, delta, flush):
2695 def _peek_iscensored(self, baserev, delta, flush):
2695 """Quickly check if a delta produces a censored revision."""
2696 """Quickly check if a delta produces a censored revision."""
2696 if not self._censorable:
2697 if not self._censorable:
2697 return False
2698 return False
2698
2699
2699 # Fragile heuristic: unless new file meta keys are added alphabetically
2700 # Fragile heuristic: unless new file meta keys are added alphabetically
2700 # preceding "censored", all censored revisions are prefixed by
2701 # preceding "censored", all censored revisions are prefixed by
2701 # "\1\ncensored:". A delta producing such a censored revision must be a
2702 # "\1\ncensored:". A delta producing such a censored revision must be a
2702 # full-replacement delta, so we inspect the first and only patch in the
2703 # full-replacement delta, so we inspect the first and only patch in the
2703 # delta for this prefix.
2704 # delta for this prefix.
2704 hlen = struct.calcsize(">lll")
2705 hlen = struct.calcsize(">lll")
2705 if len(delta) <= hlen:
2706 if len(delta) <= hlen:
2706 return False
2707 return False
2707
2708
2708 oldlen = self.rawsize(baserev)
2709 oldlen = self.rawsize(baserev)
2709 newlen = len(delta) - hlen
2710 newlen = len(delta) - hlen
2710 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2711 if delta[:hlen] != mdiff.replacediffheader(oldlen, newlen):
2711 return False
2712 return False
2712
2713
2713 add = "\1\ncensored:"
2714 add = "\1\ncensored:"
2714 addlen = len(add)
2715 addlen = len(add)
2715 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2716 return newlen >= addlen and delta[hlen:hlen + addlen] == add
2716
2717
2717 def getstrippoint(self, minlink):
2718 def getstrippoint(self, minlink):
2718 """find the minimum rev that must be stripped to strip the linkrev
2719 """find the minimum rev that must be stripped to strip the linkrev
2719
2720
2720 Returns a tuple containing the minimum rev and a set of all revs that
2721 Returns a tuple containing the minimum rev and a set of all revs that
2721 have linkrevs that will be broken by this strip.
2722 have linkrevs that will be broken by this strip.
2722 """
2723 """
2723 brokenrevs = set()
2724 brokenrevs = set()
2724 strippoint = len(self)
2725 strippoint = len(self)
2725
2726
2726 heads = {}
2727 heads = {}
2727 futurelargelinkrevs = set()
2728 futurelargelinkrevs = set()
2728 for head in self.headrevs():
2729 for head in self.headrevs():
2729 headlinkrev = self.linkrev(head)
2730 headlinkrev = self.linkrev(head)
2730 heads[head] = headlinkrev
2731 heads[head] = headlinkrev
2731 if headlinkrev >= minlink:
2732 if headlinkrev >= minlink:
2732 futurelargelinkrevs.add(headlinkrev)
2733 futurelargelinkrevs.add(headlinkrev)
2733
2734
2734 # This algorithm involves walking down the rev graph, starting at the
2735 # This algorithm involves walking down the rev graph, starting at the
2735 # heads. Since the revs are topologically sorted according to linkrev,
2736 # heads. Since the revs are topologically sorted according to linkrev,
2736 # once all head linkrevs are below the minlink, we know there are
2737 # once all head linkrevs are below the minlink, we know there are
2737 # no more revs that could have a linkrev greater than minlink.
2738 # no more revs that could have a linkrev greater than minlink.
2738 # So we can stop walking.
2739 # So we can stop walking.
2739 while futurelargelinkrevs:
2740 while futurelargelinkrevs:
2740 strippoint -= 1
2741 strippoint -= 1
2741 linkrev = heads.pop(strippoint)
2742 linkrev = heads.pop(strippoint)
2742
2743
2743 if linkrev < minlink:
2744 if linkrev < minlink:
2744 brokenrevs.add(strippoint)
2745 brokenrevs.add(strippoint)
2745 else:
2746 else:
2746 futurelargelinkrevs.remove(linkrev)
2747 futurelargelinkrevs.remove(linkrev)
2747
2748
2748 for p in self.parentrevs(strippoint):
2749 for p in self.parentrevs(strippoint):
2749 if p != nullrev:
2750 if p != nullrev:
2750 plinkrev = self.linkrev(p)
2751 plinkrev = self.linkrev(p)
2751 heads[p] = plinkrev
2752 heads[p] = plinkrev
2752 if plinkrev >= minlink:
2753 if plinkrev >= minlink:
2753 futurelargelinkrevs.add(plinkrev)
2754 futurelargelinkrevs.add(plinkrev)
2754
2755
2755 return strippoint, brokenrevs
2756 return strippoint, brokenrevs
2756
2757
2757 def strip(self, minlink, transaction):
2758 def strip(self, minlink, transaction):
2758 """truncate the revlog on the first revision with a linkrev >= minlink
2759 """truncate the revlog on the first revision with a linkrev >= minlink
2759
2760
2760 This function is called when we're stripping revision minlink and
2761 This function is called when we're stripping revision minlink and
2761 its descendants from the repository.
2762 its descendants from the repository.
2762
2763
2763 We have to remove all revisions with linkrev >= minlink, because
2764 We have to remove all revisions with linkrev >= minlink, because
2764 the equivalent changelog revisions will be renumbered after the
2765 the equivalent changelog revisions will be renumbered after the
2765 strip.
2766 strip.
2766
2767
2767 So we truncate the revlog on the first of these revisions, and
2768 So we truncate the revlog on the first of these revisions, and
2768 trust that the caller has saved the revisions that shouldn't be
2769 trust that the caller has saved the revisions that shouldn't be
2769 removed and that it'll re-add them after this truncation.
2770 removed and that it'll re-add them after this truncation.
2770 """
2771 """
2771 if len(self) == 0:
2772 if len(self) == 0:
2772 return
2773 return
2773
2774
2774 rev, _ = self.getstrippoint(minlink)
2775 rev, _ = self.getstrippoint(minlink)
2775 if rev == len(self):
2776 if rev == len(self):
2776 return
2777 return
2777
2778
2778 # first truncate the files on disk
2779 # first truncate the files on disk
2779 end = self.start(rev)
2780 end = self.start(rev)
2780 if not self._inline:
2781 if not self._inline:
2781 transaction.add(self.datafile, end)
2782 transaction.add(self.datafile, end)
2782 end = rev * self._io.size
2783 end = rev * self._io.size
2783 else:
2784 else:
2784 end += rev * self._io.size
2785 end += rev * self._io.size
2785
2786
2786 transaction.add(self.indexfile, end)
2787 transaction.add(self.indexfile, end)
2787
2788
2788 # then reset internal state in memory to forget those revisions
2789 # then reset internal state in memory to forget those revisions
2789 self._cache = None
2790 self._cache = None
2790 self._chaininfocache = {}
2791 self._chaininfocache = {}
2791 self._chunkclear()
2792 self._chunkclear()
2792 for x in pycompat.xrange(rev, len(self)):
2793 for x in pycompat.xrange(rev, len(self)):
2793 del self.nodemap[self.node(x)]
2794 del self.nodemap[self.node(x)]
2794
2795
2795 del self.index[rev:-1]
2796 del self.index[rev:-1]
2796 self._nodepos = None
2797 self._nodepos = None
2797
2798
2798 def checksize(self):
2799 def checksize(self):
2799 expected = 0
2800 expected = 0
2800 if len(self):
2801 if len(self):
2801 expected = max(0, self.end(len(self) - 1))
2802 expected = max(0, self.end(len(self) - 1))
2802
2803
2803 try:
2804 try:
2804 with self._datafp() as f:
2805 with self._datafp() as f:
2805 f.seek(0, 2)
2806 f.seek(0, 2)
2806 actual = f.tell()
2807 actual = f.tell()
2807 dd = actual - expected
2808 dd = actual - expected
2808 except IOError as inst:
2809 except IOError as inst:
2809 if inst.errno != errno.ENOENT:
2810 if inst.errno != errno.ENOENT:
2810 raise
2811 raise
2811 dd = 0
2812 dd = 0
2812
2813
2813 try:
2814 try:
2814 f = self.opener(self.indexfile)
2815 f = self.opener(self.indexfile)
2815 f.seek(0, 2)
2816 f.seek(0, 2)
2816 actual = f.tell()
2817 actual = f.tell()
2817 f.close()
2818 f.close()
2818 s = self._io.size
2819 s = self._io.size
2819 i = max(0, actual // s)
2820 i = max(0, actual // s)
2820 di = actual - (i * s)
2821 di = actual - (i * s)
2821 if self._inline:
2822 if self._inline:
2822 databytes = 0
2823 databytes = 0
2823 for r in self:
2824 for r in self:
2824 databytes += max(0, self.length(r))
2825 databytes += max(0, self.length(r))
2825 dd = 0
2826 dd = 0
2826 di = actual - len(self) * s - databytes
2827 di = actual - len(self) * s - databytes
2827 except IOError as inst:
2828 except IOError as inst:
2828 if inst.errno != errno.ENOENT:
2829 if inst.errno != errno.ENOENT:
2829 raise
2830 raise
2830 di = 0
2831 di = 0
2831
2832
2832 return (dd, di)
2833 return (dd, di)
2833
2834
2834 def files(self):
2835 def files(self):
2835 res = [self.indexfile]
2836 res = [self.indexfile]
2836 if not self._inline:
2837 if not self._inline:
2837 res.append(self.datafile)
2838 res.append(self.datafile)
2838 return res
2839 return res
2839
2840
2840 DELTAREUSEALWAYS = 'always'
2841 DELTAREUSEALWAYS = 'always'
2841 DELTAREUSESAMEREVS = 'samerevs'
2842 DELTAREUSESAMEREVS = 'samerevs'
2842 DELTAREUSENEVER = 'never'
2843 DELTAREUSENEVER = 'never'
2843
2844
2844 DELTAREUSEFULLADD = 'fulladd'
2845 DELTAREUSEFULLADD = 'fulladd'
2845
2846
2846 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2847 DELTAREUSEALL = {'always', 'samerevs', 'never', 'fulladd'}
2847
2848
2848 def clone(self, tr, destrevlog, addrevisioncb=None,
2849 def clone(self, tr, destrevlog, addrevisioncb=None,
2849 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2850 deltareuse=DELTAREUSESAMEREVS, deltabothparents=None):
2850 """Copy this revlog to another, possibly with format changes.
2851 """Copy this revlog to another, possibly with format changes.
2851
2852
2852 The destination revlog will contain the same revisions and nodes.
2853 The destination revlog will contain the same revisions and nodes.
2853 However, it may not be bit-for-bit identical due to e.g. delta encoding
2854 However, it may not be bit-for-bit identical due to e.g. delta encoding
2854 differences.
2855 differences.
2855
2856
2856 The ``deltareuse`` argument control how deltas from the existing revlog
2857 The ``deltareuse`` argument control how deltas from the existing revlog
2857 are preserved in the destination revlog. The argument can have the
2858 are preserved in the destination revlog. The argument can have the
2858 following values:
2859 following values:
2859
2860
2860 DELTAREUSEALWAYS
2861 DELTAREUSEALWAYS
2861 Deltas will always be reused (if possible), even if the destination
2862 Deltas will always be reused (if possible), even if the destination
2862 revlog would not select the same revisions for the delta. This is the
2863 revlog would not select the same revisions for the delta. This is the
2863 fastest mode of operation.
2864 fastest mode of operation.
2864 DELTAREUSESAMEREVS
2865 DELTAREUSESAMEREVS
2865 Deltas will be reused if the destination revlog would pick the same
2866 Deltas will be reused if the destination revlog would pick the same
2866 revisions for the delta. This mode strikes a balance between speed
2867 revisions for the delta. This mode strikes a balance between speed
2867 and optimization.
2868 and optimization.
2868 DELTAREUSENEVER
2869 DELTAREUSENEVER
2869 Deltas will never be reused. This is the slowest mode of execution.
2870 Deltas will never be reused. This is the slowest mode of execution.
2870 This mode can be used to recompute deltas (e.g. if the diff/delta
2871 This mode can be used to recompute deltas (e.g. if the diff/delta
2871 algorithm changes).
2872 algorithm changes).
2872
2873
2873 Delta computation can be slow, so the choice of delta reuse policy can
2874 Delta computation can be slow, so the choice of delta reuse policy can
2874 significantly affect run time.
2875 significantly affect run time.
2875
2876
2876 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2877 The default policy (``DELTAREUSESAMEREVS``) strikes a balance between
2877 two extremes. Deltas will be reused if they are appropriate. But if the
2878 two extremes. Deltas will be reused if they are appropriate. But if the
2878 delta could choose a better revision, it will do so. This means if you
2879 delta could choose a better revision, it will do so. This means if you
2879 are converting a non-generaldelta revlog to a generaldelta revlog,
2880 are converting a non-generaldelta revlog to a generaldelta revlog,
2880 deltas will be recomputed if the delta's parent isn't a parent of the
2881 deltas will be recomputed if the delta's parent isn't a parent of the
2881 revision.
2882 revision.
2882
2883
2883 In addition to the delta policy, the ``deltabothparents`` argument
2884 In addition to the delta policy, the ``deltabothparents`` argument
2884 controls whether to compute deltas against both parents for merges.
2885 controls whether to compute deltas against both parents for merges.
2885 By default, the current default is used.
2886 By default, the current default is used.
2886 """
2887 """
2887 if deltareuse not in self.DELTAREUSEALL:
2888 if deltareuse not in self.DELTAREUSEALL:
2888 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2889 raise ValueError(_('value for deltareuse invalid: %s') % deltareuse)
2889
2890
2890 if len(destrevlog):
2891 if len(destrevlog):
2891 raise ValueError(_('destination revlog is not empty'))
2892 raise ValueError(_('destination revlog is not empty'))
2892
2893
2893 if getattr(self, 'filteredrevs', None):
2894 if getattr(self, 'filteredrevs', None):
2894 raise ValueError(_('source revlog has filtered revisions'))
2895 raise ValueError(_('source revlog has filtered revisions'))
2895 if getattr(destrevlog, 'filteredrevs', None):
2896 if getattr(destrevlog, 'filteredrevs', None):
2896 raise ValueError(_('destination revlog has filtered revisions'))
2897 raise ValueError(_('destination revlog has filtered revisions'))
2897
2898
2898 # lazydeltabase controls whether to reuse a cached delta, if possible.
2899 # lazydeltabase controls whether to reuse a cached delta, if possible.
2899 oldlazydeltabase = destrevlog._lazydeltabase
2900 oldlazydeltabase = destrevlog._lazydeltabase
2900 oldamd = destrevlog._deltabothparents
2901 oldamd = destrevlog._deltabothparents
2901
2902
2902 try:
2903 try:
2903 if deltareuse == self.DELTAREUSEALWAYS:
2904 if deltareuse == self.DELTAREUSEALWAYS:
2904 destrevlog._lazydeltabase = True
2905 destrevlog._lazydeltabase = True
2905 elif deltareuse == self.DELTAREUSESAMEREVS:
2906 elif deltareuse == self.DELTAREUSESAMEREVS:
2906 destrevlog._lazydeltabase = False
2907 destrevlog._lazydeltabase = False
2907
2908
2908 destrevlog._deltabothparents = deltabothparents or oldamd
2909 destrevlog._deltabothparents = deltabothparents or oldamd
2909
2910
2910 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2911 populatecachedelta = deltareuse in (self.DELTAREUSEALWAYS,
2911 self.DELTAREUSESAMEREVS)
2912 self.DELTAREUSESAMEREVS)
2912
2913
2913 deltacomputer = _deltacomputer(destrevlog)
2914 deltacomputer = _deltacomputer(destrevlog)
2914 index = self.index
2915 index = self.index
2915 for rev in self:
2916 for rev in self:
2916 entry = index[rev]
2917 entry = index[rev]
2917
2918
2918 # Some classes override linkrev to take filtered revs into
2919 # Some classes override linkrev to take filtered revs into
2919 # account. Use raw entry from index.
2920 # account. Use raw entry from index.
2920 flags = entry[0] & 0xffff
2921 flags = entry[0] & 0xffff
2921 linkrev = entry[4]
2922 linkrev = entry[4]
2922 p1 = index[entry[5]][7]
2923 p1 = index[entry[5]][7]
2923 p2 = index[entry[6]][7]
2924 p2 = index[entry[6]][7]
2924 node = entry[7]
2925 node = entry[7]
2925
2926
2926 # (Possibly) reuse the delta from the revlog if allowed and
2927 # (Possibly) reuse the delta from the revlog if allowed and
2927 # the revlog chunk is a delta.
2928 # the revlog chunk is a delta.
2928 cachedelta = None
2929 cachedelta = None
2929 rawtext = None
2930 rawtext = None
2930 if populatecachedelta:
2931 if populatecachedelta:
2931 dp = self.deltaparent(rev)
2932 dp = self.deltaparent(rev)
2932 if dp != nullrev:
2933 if dp != nullrev:
2933 cachedelta = (dp, bytes(self._chunk(rev)))
2934 cachedelta = (dp, bytes(self._chunk(rev)))
2934
2935
2935 if not cachedelta:
2936 if not cachedelta:
2936 rawtext = self.revision(rev, raw=True)
2937 rawtext = self.revision(rev, raw=True)
2937
2938
2938
2939
2939 if deltareuse == self.DELTAREUSEFULLADD:
2940 if deltareuse == self.DELTAREUSEFULLADD:
2940 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2941 destrevlog.addrevision(rawtext, tr, linkrev, p1, p2,
2941 cachedelta=cachedelta,
2942 cachedelta=cachedelta,
2942 node=node, flags=flags,
2943 node=node, flags=flags,
2943 deltacomputer=deltacomputer)
2944 deltacomputer=deltacomputer)
2944 else:
2945 else:
2945 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2946 ifh = destrevlog.opener(destrevlog.indexfile, 'a+',
2946 checkambig=False)
2947 checkambig=False)
2947 dfh = None
2948 dfh = None
2948 if not destrevlog._inline:
2949 if not destrevlog._inline:
2949 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2950 dfh = destrevlog.opener(destrevlog.datafile, 'a+')
2950 try:
2951 try:
2951 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2952 destrevlog._addrevision(node, rawtext, tr, linkrev, p1,
2952 p2, flags, cachedelta, ifh, dfh,
2953 p2, flags, cachedelta, ifh, dfh,
2953 deltacomputer=deltacomputer)
2954 deltacomputer=deltacomputer)
2954 finally:
2955 finally:
2955 if dfh:
2956 if dfh:
2956 dfh.close()
2957 dfh.close()
2957 ifh.close()
2958 ifh.close()
2958
2959
2959 if addrevisioncb:
2960 if addrevisioncb:
2960 addrevisioncb(self, rev, node)
2961 addrevisioncb(self, rev, node)
2961 finally:
2962 finally:
2962 destrevlog._lazydeltabase = oldlazydeltabase
2963 destrevlog._lazydeltabase = oldlazydeltabase
2963 destrevlog._deltabothparents = oldamd
2964 destrevlog._deltabothparents = oldamd
@@ -1,1701 +1,1701 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 short,
24 short,
25 wdirid,
25 wdirid,
26 wdirrev,
26 wdirrev,
27 )
27 )
28
28
29 from . import (
29 from . import (
30 encoding,
30 encoding,
31 error,
31 error,
32 match as matchmod,
32 match as matchmod,
33 obsolete,
33 obsolete,
34 obsutil,
34 obsutil,
35 pathutil,
35 pathutil,
36 phases,
36 phases,
37 pycompat,
37 pycompat,
38 revsetlang,
38 revsetlang,
39 similar,
39 similar,
40 url,
40 url,
41 util,
41 util,
42 vfs,
42 vfs,
43 )
43 )
44
44
45 from .utils import (
45 from .utils import (
46 procutil,
46 procutil,
47 stringutil,
47 stringutil,
48 )
48 )
49
49
50 if pycompat.iswindows:
50 if pycompat.iswindows:
51 from . import scmwindows as scmplatform
51 from . import scmwindows as scmplatform
52 else:
52 else:
53 from . import scmposix as scmplatform
53 from . import scmposix as scmplatform
54
54
55 termsize = scmplatform.termsize
55 termsize = scmplatform.termsize
56
56
57 class status(tuple):
57 class status(tuple):
58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
58 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
59 and 'ignored' properties are only relevant to the working copy.
59 and 'ignored' properties are only relevant to the working copy.
60 '''
60 '''
61
61
62 __slots__ = ()
62 __slots__ = ()
63
63
64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
64 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
65 clean):
65 clean):
66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
66 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
67 ignored, clean))
67 ignored, clean))
68
68
69 @property
69 @property
70 def modified(self):
70 def modified(self):
71 '''files that have been modified'''
71 '''files that have been modified'''
72 return self[0]
72 return self[0]
73
73
74 @property
74 @property
75 def added(self):
75 def added(self):
76 '''files that have been added'''
76 '''files that have been added'''
77 return self[1]
77 return self[1]
78
78
79 @property
79 @property
80 def removed(self):
80 def removed(self):
81 '''files that have been removed'''
81 '''files that have been removed'''
82 return self[2]
82 return self[2]
83
83
84 @property
84 @property
85 def deleted(self):
85 def deleted(self):
86 '''files that are in the dirstate, but have been deleted from the
86 '''files that are in the dirstate, but have been deleted from the
87 working copy (aka "missing")
87 working copy (aka "missing")
88 '''
88 '''
89 return self[3]
89 return self[3]
90
90
91 @property
91 @property
92 def unknown(self):
92 def unknown(self):
93 '''files not in the dirstate that are not ignored'''
93 '''files not in the dirstate that are not ignored'''
94 return self[4]
94 return self[4]
95
95
96 @property
96 @property
97 def ignored(self):
97 def ignored(self):
98 '''files not in the dirstate that are ignored (by _dirignore())'''
98 '''files not in the dirstate that are ignored (by _dirignore())'''
99 return self[5]
99 return self[5]
100
100
101 @property
101 @property
102 def clean(self):
102 def clean(self):
103 '''files that have not been modified'''
103 '''files that have not been modified'''
104 return self[6]
104 return self[6]
105
105
106 def __repr__(self, *args, **kwargs):
106 def __repr__(self, *args, **kwargs):
107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
107 return ((r'<status modified=%s, added=%s, removed=%s, deleted=%s, '
108 r'unknown=%s, ignored=%s, clean=%s>') %
108 r'unknown=%s, ignored=%s, clean=%s>') %
109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
109 tuple(pycompat.sysstr(stringutil.pprint(v)) for v in self))
110
110
111 def itersubrepos(ctx1, ctx2):
111 def itersubrepos(ctx1, ctx2):
112 """find subrepos in ctx1 or ctx2"""
112 """find subrepos in ctx1 or ctx2"""
113 # Create a (subpath, ctx) mapping where we prefer subpaths from
113 # Create a (subpath, ctx) mapping where we prefer subpaths from
114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
114 # ctx1. The subpaths from ctx2 are important when the .hgsub file
115 # has been modified (in ctx2) but not yet committed (in ctx1).
115 # has been modified (in ctx2) but not yet committed (in ctx1).
116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
116 subpaths = dict.fromkeys(ctx2.substate, ctx2)
117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
117 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
118
118
119 missing = set()
119 missing = set()
120
120
121 for subpath in ctx2.substate:
121 for subpath in ctx2.substate:
122 if subpath not in ctx1.substate:
122 if subpath not in ctx1.substate:
123 del subpaths[subpath]
123 del subpaths[subpath]
124 missing.add(subpath)
124 missing.add(subpath)
125
125
126 for subpath, ctx in sorted(subpaths.iteritems()):
126 for subpath, ctx in sorted(subpaths.iteritems()):
127 yield subpath, ctx.sub(subpath)
127 yield subpath, ctx.sub(subpath)
128
128
129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
129 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
130 # status and diff will have an accurate result when it does
130 # status and diff will have an accurate result when it does
131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
131 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
132 # against itself.
132 # against itself.
133 for subpath in missing:
133 for subpath in missing:
134 yield subpath, ctx2.nullsub(subpath, ctx1)
134 yield subpath, ctx2.nullsub(subpath, ctx1)
135
135
136 def nochangesfound(ui, repo, excluded=None):
136 def nochangesfound(ui, repo, excluded=None):
137 '''Report no changes for push/pull, excluded is None or a list of
137 '''Report no changes for push/pull, excluded is None or a list of
138 nodes excluded from the push/pull.
138 nodes excluded from the push/pull.
139 '''
139 '''
140 secretlist = []
140 secretlist = []
141 if excluded:
141 if excluded:
142 for n in excluded:
142 for n in excluded:
143 ctx = repo[n]
143 ctx = repo[n]
144 if ctx.phase() >= phases.secret and not ctx.extinct():
144 if ctx.phase() >= phases.secret and not ctx.extinct():
145 secretlist.append(n)
145 secretlist.append(n)
146
146
147 if secretlist:
147 if secretlist:
148 ui.status(_("no changes found (ignored %d secret changesets)\n")
148 ui.status(_("no changes found (ignored %d secret changesets)\n")
149 % len(secretlist))
149 % len(secretlist))
150 else:
150 else:
151 ui.status(_("no changes found\n"))
151 ui.status(_("no changes found\n"))
152
152
153 def callcatch(ui, func):
153 def callcatch(ui, func):
154 """call func() with global exception handling
154 """call func() with global exception handling
155
155
156 return func() if no exception happens. otherwise do some error handling
156 return func() if no exception happens. otherwise do some error handling
157 and return an exit code accordingly. does not handle all exceptions.
157 and return an exit code accordingly. does not handle all exceptions.
158 """
158 """
159 try:
159 try:
160 try:
160 try:
161 return func()
161 return func()
162 except: # re-raises
162 except: # re-raises
163 ui.traceback()
163 ui.traceback()
164 raise
164 raise
165 # Global exception handling, alphabetically
165 # Global exception handling, alphabetically
166 # Mercurial-specific first, followed by built-in and library exceptions
166 # Mercurial-specific first, followed by built-in and library exceptions
167 except error.LockHeld as inst:
167 except error.LockHeld as inst:
168 if inst.errno == errno.ETIMEDOUT:
168 if inst.errno == errno.ETIMEDOUT:
169 reason = _('timed out waiting for lock held by %r') % inst.locker
169 reason = _('timed out waiting for lock held by %r') % inst.locker
170 else:
170 else:
171 reason = _('lock held by %r') % inst.locker
171 reason = _('lock held by %r') % inst.locker
172 ui.error(_("abort: %s: %s\n") % (
172 ui.error(_("abort: %s: %s\n") % (
173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
173 inst.desc or stringutil.forcebytestr(inst.filename), reason))
174 if not inst.locker:
174 if not inst.locker:
175 ui.error(_("(lock might be very busy)\n"))
175 ui.error(_("(lock might be very busy)\n"))
176 except error.LockUnavailable as inst:
176 except error.LockUnavailable as inst:
177 ui.error(_("abort: could not lock %s: %s\n") %
177 ui.error(_("abort: could not lock %s: %s\n") %
178 (inst.desc or stringutil.forcebytestr(inst.filename),
178 (inst.desc or stringutil.forcebytestr(inst.filename),
179 encoding.strtolocal(inst.strerror)))
179 encoding.strtolocal(inst.strerror)))
180 except error.OutOfBandError as inst:
180 except error.OutOfBandError as inst:
181 if inst.args:
181 if inst.args:
182 msg = _("abort: remote error:\n")
182 msg = _("abort: remote error:\n")
183 else:
183 else:
184 msg = _("abort: remote error\n")
184 msg = _("abort: remote error\n")
185 ui.error(msg)
185 ui.error(msg)
186 if inst.args:
186 if inst.args:
187 ui.error(''.join(inst.args))
187 ui.error(''.join(inst.args))
188 if inst.hint:
188 if inst.hint:
189 ui.error('(%s)\n' % inst.hint)
189 ui.error('(%s)\n' % inst.hint)
190 except error.RepoError as inst:
190 except error.RepoError as inst:
191 ui.error(_("abort: %s!\n") % inst)
191 ui.error(_("abort: %s!\n") % inst)
192 if inst.hint:
192 if inst.hint:
193 ui.error(_("(%s)\n") % inst.hint)
193 ui.error(_("(%s)\n") % inst.hint)
194 except error.ResponseError as inst:
194 except error.ResponseError as inst:
195 ui.error(_("abort: %s") % inst.args[0])
195 ui.error(_("abort: %s") % inst.args[0])
196 msg = inst.args[1]
196 msg = inst.args[1]
197 if isinstance(msg, type(u'')):
197 if isinstance(msg, type(u'')):
198 msg = pycompat.sysbytes(msg)
198 msg = pycompat.sysbytes(msg)
199 if not isinstance(msg, bytes):
199 if not isinstance(msg, bytes):
200 ui.error(" %r\n" % (msg,))
200 ui.error(" %r\n" % (msg,))
201 elif not msg:
201 elif not msg:
202 ui.error(_(" empty string\n"))
202 ui.error(_(" empty string\n"))
203 else:
203 else:
204 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
204 ui.error("\n%r\n" % pycompat.bytestr(stringutil.ellipsis(msg)))
205 except error.CensoredNodeError as inst:
205 except error.CensoredNodeError as inst:
206 ui.error(_("abort: file censored %s!\n") % inst)
206 ui.error(_("abort: file censored %s!\n") % inst)
207 except error.RevlogError as inst:
207 except error.RevlogError as inst:
208 ui.error(_("abort: %s!\n") % inst)
208 ui.error(_("abort: %s!\n") % inst)
209 except error.InterventionRequired as inst:
209 except error.InterventionRequired as inst:
210 ui.error("%s\n" % inst)
210 ui.error("%s\n" % inst)
211 if inst.hint:
211 if inst.hint:
212 ui.error(_("(%s)\n") % inst.hint)
212 ui.error(_("(%s)\n") % inst.hint)
213 return 1
213 return 1
214 except error.WdirUnsupported:
214 except error.WdirUnsupported:
215 ui.error(_("abort: working directory revision cannot be specified\n"))
215 ui.error(_("abort: working directory revision cannot be specified\n"))
216 except error.Abort as inst:
216 except error.Abort as inst:
217 ui.error(_("abort: %s\n") % inst)
217 ui.error(_("abort: %s\n") % inst)
218 if inst.hint:
218 if inst.hint:
219 ui.error(_("(%s)\n") % inst.hint)
219 ui.error(_("(%s)\n") % inst.hint)
220 except ImportError as inst:
220 except ImportError as inst:
221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
221 ui.error(_("abort: %s!\n") % stringutil.forcebytestr(inst))
222 m = stringutil.forcebytestr(inst).split()[-1]
222 m = stringutil.forcebytestr(inst).split()[-1]
223 if m in "mpatch bdiff".split():
223 if m in "mpatch bdiff".split():
224 ui.error(_("(did you forget to compile extensions?)\n"))
224 ui.error(_("(did you forget to compile extensions?)\n"))
225 elif m in "zlib".split():
225 elif m in "zlib".split():
226 ui.error(_("(is your Python install correct?)\n"))
226 ui.error(_("(is your Python install correct?)\n"))
227 except IOError as inst:
227 except IOError as inst:
228 if util.safehasattr(inst, "code"):
228 if util.safehasattr(inst, "code"):
229 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
229 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst))
230 elif util.safehasattr(inst, "reason"):
230 elif util.safehasattr(inst, "reason"):
231 try: # usually it is in the form (errno, strerror)
231 try: # usually it is in the form (errno, strerror)
232 reason = inst.reason.args[1]
232 reason = inst.reason.args[1]
233 except (AttributeError, IndexError):
233 except (AttributeError, IndexError):
234 # it might be anything, for example a string
234 # it might be anything, for example a string
235 reason = inst.reason
235 reason = inst.reason
236 if isinstance(reason, pycompat.unicode):
236 if isinstance(reason, pycompat.unicode):
237 # SSLError of Python 2.7.9 contains a unicode
237 # SSLError of Python 2.7.9 contains a unicode
238 reason = encoding.unitolocal(reason)
238 reason = encoding.unitolocal(reason)
239 ui.error(_("abort: error: %s\n") % reason)
239 ui.error(_("abort: error: %s\n") % reason)
240 elif (util.safehasattr(inst, "args")
240 elif (util.safehasattr(inst, "args")
241 and inst.args and inst.args[0] == errno.EPIPE):
241 and inst.args and inst.args[0] == errno.EPIPE):
242 pass
242 pass
243 elif getattr(inst, "strerror", None):
243 elif getattr(inst, "strerror", None):
244 if getattr(inst, "filename", None):
244 if getattr(inst, "filename", None):
245 ui.error(_("abort: %s: %s\n") % (
245 ui.error(_("abort: %s: %s\n") % (
246 encoding.strtolocal(inst.strerror),
246 encoding.strtolocal(inst.strerror),
247 stringutil.forcebytestr(inst.filename)))
247 stringutil.forcebytestr(inst.filename)))
248 else:
248 else:
249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
249 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
250 else:
250 else:
251 raise
251 raise
252 except OSError as inst:
252 except OSError as inst:
253 if getattr(inst, "filename", None) is not None:
253 if getattr(inst, "filename", None) is not None:
254 ui.error(_("abort: %s: '%s'\n") % (
254 ui.error(_("abort: %s: '%s'\n") % (
255 encoding.strtolocal(inst.strerror),
255 encoding.strtolocal(inst.strerror),
256 stringutil.forcebytestr(inst.filename)))
256 stringutil.forcebytestr(inst.filename)))
257 else:
257 else:
258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
258 ui.error(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
259 except MemoryError:
259 except MemoryError:
260 ui.error(_("abort: out of memory\n"))
260 ui.error(_("abort: out of memory\n"))
261 except SystemExit as inst:
261 except SystemExit as inst:
262 # Commands shouldn't sys.exit directly, but give a return code.
262 # Commands shouldn't sys.exit directly, but give a return code.
263 # Just in case catch this and and pass exit code to caller.
263 # Just in case catch this and and pass exit code to caller.
264 return inst.code
264 return inst.code
265 except socket.error as inst:
265 except socket.error as inst:
266 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
266 ui.error(_("abort: %s\n") % stringutil.forcebytestr(inst.args[-1]))
267
267
268 return -1
268 return -1
269
269
270 def checknewlabel(repo, lbl, kind):
270 def checknewlabel(repo, lbl, kind):
271 # Do not use the "kind" parameter in ui output.
271 # Do not use the "kind" parameter in ui output.
272 # It makes strings difficult to translate.
272 # It makes strings difficult to translate.
273 if lbl in ['tip', '.', 'null']:
273 if lbl in ['tip', '.', 'null']:
274 raise error.Abort(_("the name '%s' is reserved") % lbl)
274 raise error.Abort(_("the name '%s' is reserved") % lbl)
275 for c in (':', '\0', '\n', '\r'):
275 for c in (':', '\0', '\n', '\r'):
276 if c in lbl:
276 if c in lbl:
277 raise error.Abort(
277 raise error.Abort(
278 _("%r cannot be used in a name") % pycompat.bytestr(c))
278 _("%r cannot be used in a name") % pycompat.bytestr(c))
279 try:
279 try:
280 int(lbl)
280 int(lbl)
281 raise error.Abort(_("cannot use an integer as a name"))
281 raise error.Abort(_("cannot use an integer as a name"))
282 except ValueError:
282 except ValueError:
283 pass
283 pass
284 if lbl.strip() != lbl:
284 if lbl.strip() != lbl:
285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
285 raise error.Abort(_("leading or trailing whitespace in name %r") % lbl)
286
286
287 def checkfilename(f):
287 def checkfilename(f):
288 '''Check that the filename f is an acceptable filename for a tracked file'''
288 '''Check that the filename f is an acceptable filename for a tracked file'''
289 if '\r' in f or '\n' in f:
289 if '\r' in f or '\n' in f:
290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
290 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r")
291 % pycompat.bytestr(f))
291 % pycompat.bytestr(f))
292
292
293 def checkportable(ui, f):
293 def checkportable(ui, f):
294 '''Check if filename f is portable and warn or abort depending on config'''
294 '''Check if filename f is portable and warn or abort depending on config'''
295 checkfilename(f)
295 checkfilename(f)
296 abort, warn = checkportabilityalert(ui)
296 abort, warn = checkportabilityalert(ui)
297 if abort or warn:
297 if abort or warn:
298 msg = util.checkwinfilename(f)
298 msg = util.checkwinfilename(f)
299 if msg:
299 if msg:
300 msg = "%s: %s" % (msg, procutil.shellquote(f))
300 msg = "%s: %s" % (msg, procutil.shellquote(f))
301 if abort:
301 if abort:
302 raise error.Abort(msg)
302 raise error.Abort(msg)
303 ui.warn(_("warning: %s\n") % msg)
303 ui.warn(_("warning: %s\n") % msg)
304
304
305 def checkportabilityalert(ui):
305 def checkportabilityalert(ui):
306 '''check if the user's config requests nothing, a warning, or abort for
306 '''check if the user's config requests nothing, a warning, or abort for
307 non-portable filenames'''
307 non-portable filenames'''
308 val = ui.config('ui', 'portablefilenames')
308 val = ui.config('ui', 'portablefilenames')
309 lval = val.lower()
309 lval = val.lower()
310 bval = stringutil.parsebool(val)
310 bval = stringutil.parsebool(val)
311 abort = pycompat.iswindows or lval == 'abort'
311 abort = pycompat.iswindows or lval == 'abort'
312 warn = bval or lval == 'warn'
312 warn = bval or lval == 'warn'
313 if bval is None and not (warn or abort or lval == 'ignore'):
313 if bval is None and not (warn or abort or lval == 'ignore'):
314 raise error.ConfigError(
314 raise error.ConfigError(
315 _("ui.portablefilenames value is invalid ('%s')") % val)
315 _("ui.portablefilenames value is invalid ('%s')") % val)
316 return abort, warn
316 return abort, warn
317
317
318 class casecollisionauditor(object):
318 class casecollisionauditor(object):
319 def __init__(self, ui, abort, dirstate):
319 def __init__(self, ui, abort, dirstate):
320 self._ui = ui
320 self._ui = ui
321 self._abort = abort
321 self._abort = abort
322 allfiles = '\0'.join(dirstate._map)
322 allfiles = '\0'.join(dirstate._map)
323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
323 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
324 self._dirstate = dirstate
324 self._dirstate = dirstate
325 # The purpose of _newfiles is so that we don't complain about
325 # The purpose of _newfiles is so that we don't complain about
326 # case collisions if someone were to call this object with the
326 # case collisions if someone were to call this object with the
327 # same filename twice.
327 # same filename twice.
328 self._newfiles = set()
328 self._newfiles = set()
329
329
330 def __call__(self, f):
330 def __call__(self, f):
331 if f in self._newfiles:
331 if f in self._newfiles:
332 return
332 return
333 fl = encoding.lower(f)
333 fl = encoding.lower(f)
334 if fl in self._loweredfiles and f not in self._dirstate:
334 if fl in self._loweredfiles and f not in self._dirstate:
335 msg = _('possible case-folding collision for %s') % f
335 msg = _('possible case-folding collision for %s') % f
336 if self._abort:
336 if self._abort:
337 raise error.Abort(msg)
337 raise error.Abort(msg)
338 self._ui.warn(_("warning: %s\n") % msg)
338 self._ui.warn(_("warning: %s\n") % msg)
339 self._loweredfiles.add(fl)
339 self._loweredfiles.add(fl)
340 self._newfiles.add(f)
340 self._newfiles.add(f)
341
341
342 def filteredhash(repo, maxrev):
342 def filteredhash(repo, maxrev):
343 """build hash of filtered revisions in the current repoview.
343 """build hash of filtered revisions in the current repoview.
344
344
345 Multiple caches perform up-to-date validation by checking that the
345 Multiple caches perform up-to-date validation by checking that the
346 tiprev and tipnode stored in the cache file match the current repository.
346 tiprev and tipnode stored in the cache file match the current repository.
347 However, this is not sufficient for validating repoviews because the set
347 However, this is not sufficient for validating repoviews because the set
348 of revisions in the view may change without the repository tiprev and
348 of revisions in the view may change without the repository tiprev and
349 tipnode changing.
349 tipnode changing.
350
350
351 This function hashes all the revs filtered from the view and returns
351 This function hashes all the revs filtered from the view and returns
352 that SHA-1 digest.
352 that SHA-1 digest.
353 """
353 """
354 cl = repo.changelog
354 cl = repo.changelog
355 if not cl.filteredrevs:
355 if not cl.filteredrevs:
356 return None
356 return None
357 key = None
357 key = None
358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
358 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
359 if revs:
359 if revs:
360 s = hashlib.sha1()
360 s = hashlib.sha1()
361 for rev in revs:
361 for rev in revs:
362 s.update('%d;' % rev)
362 s.update('%d;' % rev)
363 key = s.digest()
363 key = s.digest()
364 return key
364 return key
365
365
366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
366 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
367 '''yield every hg repository under path, always recursively.
367 '''yield every hg repository under path, always recursively.
368 The recurse flag will only control recursion into repo working dirs'''
368 The recurse flag will only control recursion into repo working dirs'''
369 def errhandler(err):
369 def errhandler(err):
370 if err.filename == path:
370 if err.filename == path:
371 raise err
371 raise err
372 samestat = getattr(os.path, 'samestat', None)
372 samestat = getattr(os.path, 'samestat', None)
373 if followsym and samestat is not None:
373 if followsym and samestat is not None:
374 def adddir(dirlst, dirname):
374 def adddir(dirlst, dirname):
375 dirstat = os.stat(dirname)
375 dirstat = os.stat(dirname)
376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
376 match = any(samestat(dirstat, lstdirstat) for lstdirstat in dirlst)
377 if not match:
377 if not match:
378 dirlst.append(dirstat)
378 dirlst.append(dirstat)
379 return not match
379 return not match
380 else:
380 else:
381 followsym = False
381 followsym = False
382
382
383 if (seen_dirs is None) and followsym:
383 if (seen_dirs is None) and followsym:
384 seen_dirs = []
384 seen_dirs = []
385 adddir(seen_dirs, path)
385 adddir(seen_dirs, path)
386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
386 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
387 dirs.sort()
387 dirs.sort()
388 if '.hg' in dirs:
388 if '.hg' in dirs:
389 yield root # found a repository
389 yield root # found a repository
390 qroot = os.path.join(root, '.hg', 'patches')
390 qroot = os.path.join(root, '.hg', 'patches')
391 if os.path.isdir(os.path.join(qroot, '.hg')):
391 if os.path.isdir(os.path.join(qroot, '.hg')):
392 yield qroot # we have a patch queue repo here
392 yield qroot # we have a patch queue repo here
393 if recurse:
393 if recurse:
394 # avoid recursing inside the .hg directory
394 # avoid recursing inside the .hg directory
395 dirs.remove('.hg')
395 dirs.remove('.hg')
396 else:
396 else:
397 dirs[:] = [] # don't descend further
397 dirs[:] = [] # don't descend further
398 elif followsym:
398 elif followsym:
399 newdirs = []
399 newdirs = []
400 for d in dirs:
400 for d in dirs:
401 fname = os.path.join(root, d)
401 fname = os.path.join(root, d)
402 if adddir(seen_dirs, fname):
402 if adddir(seen_dirs, fname):
403 if os.path.islink(fname):
403 if os.path.islink(fname):
404 for hgname in walkrepos(fname, True, seen_dirs):
404 for hgname in walkrepos(fname, True, seen_dirs):
405 yield hgname
405 yield hgname
406 else:
406 else:
407 newdirs.append(d)
407 newdirs.append(d)
408 dirs[:] = newdirs
408 dirs[:] = newdirs
409
409
410 def binnode(ctx):
410 def binnode(ctx):
411 """Return binary node id for a given basectx"""
411 """Return binary node id for a given basectx"""
412 node = ctx.node()
412 node = ctx.node()
413 if node is None:
413 if node is None:
414 return wdirid
414 return wdirid
415 return node
415 return node
416
416
417 def intrev(ctx):
417 def intrev(ctx):
418 """Return integer for a given basectx that can be used in comparison or
418 """Return integer for a given basectx that can be used in comparison or
419 arithmetic operation"""
419 arithmetic operation"""
420 rev = ctx.rev()
420 rev = ctx.rev()
421 if rev is None:
421 if rev is None:
422 return wdirrev
422 return wdirrev
423 return rev
423 return rev
424
424
425 def formatchangeid(ctx):
425 def formatchangeid(ctx):
426 """Format changectx as '{rev}:{node|formatnode}', which is the default
426 """Format changectx as '{rev}:{node|formatnode}', which is the default
427 template provided by logcmdutil.changesettemplater"""
427 template provided by logcmdutil.changesettemplater"""
428 repo = ctx.repo()
428 repo = ctx.repo()
429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
429 return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
430
430
431 def formatrevnode(ui, rev, node):
431 def formatrevnode(ui, rev, node):
432 """Format given revision and node depending on the current verbosity"""
432 """Format given revision and node depending on the current verbosity"""
433 if ui.debugflag:
433 if ui.debugflag:
434 hexfunc = hex
434 hexfunc = hex
435 else:
435 else:
436 hexfunc = short
436 hexfunc = short
437 return '%d:%s' % (rev, hexfunc(node))
437 return '%d:%s' % (rev, hexfunc(node))
438
438
439 def resolvehexnodeidprefix(repo, prefix):
439 def resolvehexnodeidprefix(repo, prefix):
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
440 # Uses unfiltered repo because it's faster when prefix is ambiguous/
441 # This matches the shortesthexnodeidprefix() function below.
441 # This matches the shortesthexnodeidprefix() function below.
442 node = repo.unfiltered().changelog._partialmatch(prefix)
442 node = repo.unfiltered().changelog._partialmatch(prefix)
443 if node is None:
443 if node is None:
444 return
444 return
445 repo.changelog.rev(node) # make sure node isn't filtered
445 repo.changelog.rev(node) # make sure node isn't filtered
446 return node
446 return node
447
447
448 def shortesthexnodeidprefix(repo, node, minlength=1):
448 def shortesthexnodeidprefix(repo, node, minlength=1):
449 """Find the shortest unambiguous prefix that matches hexnode."""
449 """Find the shortest unambiguous prefix that matches hexnode."""
450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
450 # _partialmatch() of filtered changelog could take O(len(repo)) time,
451 # which would be unacceptably slow. so we look for hash collision in
451 # which would be unacceptably slow. so we look for hash collision in
452 # unfiltered space, which means some hashes may be slightly longer.
452 # unfiltered space, which means some hashes may be slightly longer.
453 cl = repo.unfiltered().changelog
453 cl = repo.unfiltered().changelog
454
454
455 def isrev(prefix):
455 def isrev(prefix):
456 try:
456 try:
457 i = int(prefix)
457 i = int(prefix)
458 # if we are a pure int, then starting with zero will not be
458 # if we are a pure int, then starting with zero will not be
459 # confused as a rev; or, obviously, if the int is larger
459 # confused as a rev; or, obviously, if the int is larger
460 # than the value of the tip rev
460 # than the value of the tip rev
461 if prefix[0:1] == b'0' or i > len(cl):
461 if prefix[0:1] == b'0' or i > len(cl):
462 return False
462 return False
463 return True
463 return True
464 except ValueError:
464 except ValueError:
465 return False
465 return False
466
466
467 def disambiguate(prefix):
467 def disambiguate(prefix):
468 """Disambiguate against revnums."""
468 """Disambiguate against revnums."""
469 hexnode = hex(node)
469 hexnode = hex(node)
470 for length in range(len(prefix), len(hexnode) + 1):
470 for length in range(len(prefix), len(hexnode) + 1):
471 prefix = hexnode[:length]
471 prefix = hexnode[:length]
472 if not isrev(prefix):
472 if not isrev(prefix):
473 return prefix
473 return prefix
474
474
475 try:
475 try:
476 return disambiguate(cl.shortest(node, minlength))
476 return disambiguate(cl.shortest(node, minlength))
477 except error.LookupError:
477 except error.LookupError:
478 raise error.RepoLookupError()
478 raise error.RepoLookupError()
479
479
480 def isrevsymbol(repo, symbol):
480 def isrevsymbol(repo, symbol):
481 """Checks if a symbol exists in the repo.
481 """Checks if a symbol exists in the repo.
482
482
483 See revsymbol() for details. Raises error.LookupError if the symbol is an
483 See revsymbol() for details. Raises error.AmbiguousPrefixLookupError if the
484 ambiguous nodeid prefix.
484 symbol is an ambiguous nodeid prefix.
485 """
485 """
486 try:
486 try:
487 revsymbol(repo, symbol)
487 revsymbol(repo, symbol)
488 return True
488 return True
489 except error.RepoLookupError:
489 except error.RepoLookupError:
490 return False
490 return False
491
491
492 def revsymbol(repo, symbol):
492 def revsymbol(repo, symbol):
493 """Returns a context given a single revision symbol (as string).
493 """Returns a context given a single revision symbol (as string).
494
494
495 This is similar to revsingle(), but accepts only a single revision symbol,
495 This is similar to revsingle(), but accepts only a single revision symbol,
496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
496 i.e. things like ".", "tip", "1234", "deadbeef", "my-bookmark" work, but
497 not "max(public())".
497 not "max(public())".
498 """
498 """
499 if not isinstance(symbol, bytes):
499 if not isinstance(symbol, bytes):
500 msg = ("symbol (%s of type %s) was not a string, did you mean "
500 msg = ("symbol (%s of type %s) was not a string, did you mean "
501 "repo[symbol]?" % (symbol, type(symbol)))
501 "repo[symbol]?" % (symbol, type(symbol)))
502 raise error.ProgrammingError(msg)
502 raise error.ProgrammingError(msg)
503 try:
503 try:
504 if symbol in ('.', 'tip', 'null'):
504 if symbol in ('.', 'tip', 'null'):
505 return repo[symbol]
505 return repo[symbol]
506
506
507 try:
507 try:
508 r = int(symbol)
508 r = int(symbol)
509 if '%d' % r != symbol:
509 if '%d' % r != symbol:
510 raise ValueError
510 raise ValueError
511 l = len(repo.changelog)
511 l = len(repo.changelog)
512 if r < 0:
512 if r < 0:
513 r += l
513 r += l
514 if r < 0 or r >= l and r != wdirrev:
514 if r < 0 or r >= l and r != wdirrev:
515 raise ValueError
515 raise ValueError
516 return repo[r]
516 return repo[r]
517 except error.FilteredIndexError:
517 except error.FilteredIndexError:
518 raise
518 raise
519 except (ValueError, OverflowError, IndexError):
519 except (ValueError, OverflowError, IndexError):
520 pass
520 pass
521
521
522 if len(symbol) == 40:
522 if len(symbol) == 40:
523 try:
523 try:
524 node = bin(symbol)
524 node = bin(symbol)
525 rev = repo.changelog.rev(node)
525 rev = repo.changelog.rev(node)
526 return repo[rev]
526 return repo[rev]
527 except error.FilteredLookupError:
527 except error.FilteredLookupError:
528 raise
528 raise
529 except (TypeError, LookupError):
529 except (TypeError, LookupError):
530 pass
530 pass
531
531
532 # look up bookmarks through the name interface
532 # look up bookmarks through the name interface
533 try:
533 try:
534 node = repo.names.singlenode(repo, symbol)
534 node = repo.names.singlenode(repo, symbol)
535 rev = repo.changelog.rev(node)
535 rev = repo.changelog.rev(node)
536 return repo[rev]
536 return repo[rev]
537 except KeyError:
537 except KeyError:
538 pass
538 pass
539
539
540 node = resolvehexnodeidprefix(repo, symbol)
540 node = resolvehexnodeidprefix(repo, symbol)
541 if node is not None:
541 if node is not None:
542 rev = repo.changelog.rev(node)
542 rev = repo.changelog.rev(node)
543 return repo[rev]
543 return repo[rev]
544
544
545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
545 raise error.RepoLookupError(_("unknown revision '%s'") % symbol)
546
546
547 except error.WdirUnsupported:
547 except error.WdirUnsupported:
548 return repo[None]
548 return repo[None]
549 except (error.FilteredIndexError, error.FilteredLookupError,
549 except (error.FilteredIndexError, error.FilteredLookupError,
550 error.FilteredRepoLookupError):
550 error.FilteredRepoLookupError):
551 raise _filterederror(repo, symbol)
551 raise _filterederror(repo, symbol)
552
552
553 def _filterederror(repo, changeid):
553 def _filterederror(repo, changeid):
554 """build an exception to be raised about a filtered changeid
554 """build an exception to be raised about a filtered changeid
555
555
556 This is extracted in a function to help extensions (eg: evolve) to
556 This is extracted in a function to help extensions (eg: evolve) to
557 experiment with various message variants."""
557 experiment with various message variants."""
558 if repo.filtername.startswith('visible'):
558 if repo.filtername.startswith('visible'):
559
559
560 # Check if the changeset is obsolete
560 # Check if the changeset is obsolete
561 unfilteredrepo = repo.unfiltered()
561 unfilteredrepo = repo.unfiltered()
562 ctx = revsymbol(unfilteredrepo, changeid)
562 ctx = revsymbol(unfilteredrepo, changeid)
563
563
564 # If the changeset is obsolete, enrich the message with the reason
564 # If the changeset is obsolete, enrich the message with the reason
565 # that made this changeset not visible
565 # that made this changeset not visible
566 if ctx.obsolete():
566 if ctx.obsolete():
567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
567 msg = obsutil._getfilteredreason(repo, changeid, ctx)
568 else:
568 else:
569 msg = _("hidden revision '%s'") % changeid
569 msg = _("hidden revision '%s'") % changeid
570
570
571 hint = _('use --hidden to access hidden revisions')
571 hint = _('use --hidden to access hidden revisions')
572
572
573 return error.FilteredRepoLookupError(msg, hint=hint)
573 return error.FilteredRepoLookupError(msg, hint=hint)
574 msg = _("filtered revision '%s' (not in '%s' subset)")
574 msg = _("filtered revision '%s' (not in '%s' subset)")
575 msg %= (changeid, repo.filtername)
575 msg %= (changeid, repo.filtername)
576 return error.FilteredRepoLookupError(msg)
576 return error.FilteredRepoLookupError(msg)
577
577
578 def revsingle(repo, revspec, default='.', localalias=None):
578 def revsingle(repo, revspec, default='.', localalias=None):
579 if not revspec and revspec != 0:
579 if not revspec and revspec != 0:
580 return repo[default]
580 return repo[default]
581
581
582 l = revrange(repo, [revspec], localalias=localalias)
582 l = revrange(repo, [revspec], localalias=localalias)
583 if not l:
583 if not l:
584 raise error.Abort(_('empty revision set'))
584 raise error.Abort(_('empty revision set'))
585 return repo[l.last()]
585 return repo[l.last()]
586
586
587 def _pairspec(revspec):
587 def _pairspec(revspec):
588 tree = revsetlang.parse(revspec)
588 tree = revsetlang.parse(revspec)
589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
589 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
590
590
591 def revpair(repo, revs):
591 def revpair(repo, revs):
592 if not revs:
592 if not revs:
593 return repo['.'], repo[None]
593 return repo['.'], repo[None]
594
594
595 l = revrange(repo, revs)
595 l = revrange(repo, revs)
596
596
597 if not l:
597 if not l:
598 first = second = None
598 first = second = None
599 elif l.isascending():
599 elif l.isascending():
600 first = l.min()
600 first = l.min()
601 second = l.max()
601 second = l.max()
602 elif l.isdescending():
602 elif l.isdescending():
603 first = l.max()
603 first = l.max()
604 second = l.min()
604 second = l.min()
605 else:
605 else:
606 first = l.first()
606 first = l.first()
607 second = l.last()
607 second = l.last()
608
608
609 if first is None:
609 if first is None:
610 raise error.Abort(_('empty revision range'))
610 raise error.Abort(_('empty revision range'))
611 if (first == second and len(revs) >= 2
611 if (first == second and len(revs) >= 2
612 and not all(revrange(repo, [r]) for r in revs)):
612 and not all(revrange(repo, [r]) for r in revs)):
613 raise error.Abort(_('empty revision on one side of range'))
613 raise error.Abort(_('empty revision on one side of range'))
614
614
615 # if top-level is range expression, the result must always be a pair
615 # if top-level is range expression, the result must always be a pair
616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
616 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
617 return repo[first], repo[None]
617 return repo[first], repo[None]
618
618
619 return repo[first], repo[second]
619 return repo[first], repo[second]
620
620
621 def revrange(repo, specs, localalias=None):
621 def revrange(repo, specs, localalias=None):
622 """Execute 1 to many revsets and return the union.
622 """Execute 1 to many revsets and return the union.
623
623
624 This is the preferred mechanism for executing revsets using user-specified
624 This is the preferred mechanism for executing revsets using user-specified
625 config options, such as revset aliases.
625 config options, such as revset aliases.
626
626
627 The revsets specified by ``specs`` will be executed via a chained ``OR``
627 The revsets specified by ``specs`` will be executed via a chained ``OR``
628 expression. If ``specs`` is empty, an empty result is returned.
628 expression. If ``specs`` is empty, an empty result is returned.
629
629
630 ``specs`` can contain integers, in which case they are assumed to be
630 ``specs`` can contain integers, in which case they are assumed to be
631 revision numbers.
631 revision numbers.
632
632
633 It is assumed the revsets are already formatted. If you have arguments
633 It is assumed the revsets are already formatted. If you have arguments
634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
634 that need to be expanded in the revset, call ``revsetlang.formatspec()``
635 and pass the result as an element of ``specs``.
635 and pass the result as an element of ``specs``.
636
636
637 Specifying a single revset is allowed.
637 Specifying a single revset is allowed.
638
638
639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
639 Returns a ``revset.abstractsmartset`` which is a list-like interface over
640 integer revisions.
640 integer revisions.
641 """
641 """
642 allspecs = []
642 allspecs = []
643 for spec in specs:
643 for spec in specs:
644 if isinstance(spec, int):
644 if isinstance(spec, int):
645 spec = revsetlang.formatspec('rev(%d)', spec)
645 spec = revsetlang.formatspec('rev(%d)', spec)
646 allspecs.append(spec)
646 allspecs.append(spec)
647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
647 return repo.anyrevs(allspecs, user=True, localalias=localalias)
648
648
649 def meaningfulparents(repo, ctx):
649 def meaningfulparents(repo, ctx):
650 """Return list of meaningful (or all if debug) parentrevs for rev.
650 """Return list of meaningful (or all if debug) parentrevs for rev.
651
651
652 For merges (two non-nullrev revisions) both parents are meaningful.
652 For merges (two non-nullrev revisions) both parents are meaningful.
653 Otherwise the first parent revision is considered meaningful if it
653 Otherwise the first parent revision is considered meaningful if it
654 is not the preceding revision.
654 is not the preceding revision.
655 """
655 """
656 parents = ctx.parents()
656 parents = ctx.parents()
657 if len(parents) > 1:
657 if len(parents) > 1:
658 return parents
658 return parents
659 if repo.ui.debugflag:
659 if repo.ui.debugflag:
660 return [parents[0], repo['null']]
660 return [parents[0], repo['null']]
661 if parents[0].rev() >= intrev(ctx) - 1:
661 if parents[0].rev() >= intrev(ctx) - 1:
662 return []
662 return []
663 return parents
663 return parents
664
664
665 def expandpats(pats):
665 def expandpats(pats):
666 '''Expand bare globs when running on windows.
666 '''Expand bare globs when running on windows.
667 On posix we assume it already has already been done by sh.'''
667 On posix we assume it already has already been done by sh.'''
668 if not util.expandglobs:
668 if not util.expandglobs:
669 return list(pats)
669 return list(pats)
670 ret = []
670 ret = []
671 for kindpat in pats:
671 for kindpat in pats:
672 kind, pat = matchmod._patsplit(kindpat, None)
672 kind, pat = matchmod._patsplit(kindpat, None)
673 if kind is None:
673 if kind is None:
674 try:
674 try:
675 globbed = glob.glob(pat)
675 globbed = glob.glob(pat)
676 except re.error:
676 except re.error:
677 globbed = [pat]
677 globbed = [pat]
678 if globbed:
678 if globbed:
679 ret.extend(globbed)
679 ret.extend(globbed)
680 continue
680 continue
681 ret.append(kindpat)
681 ret.append(kindpat)
682 return ret
682 return ret
683
683
684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
684 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
685 badfn=None):
685 badfn=None):
686 '''Return a matcher and the patterns that were used.
686 '''Return a matcher and the patterns that were used.
687 The matcher will warn about bad matches, unless an alternate badfn callback
687 The matcher will warn about bad matches, unless an alternate badfn callback
688 is provided.'''
688 is provided.'''
689 if pats == ("",):
689 if pats == ("",):
690 pats = []
690 pats = []
691 if opts is None:
691 if opts is None:
692 opts = {}
692 opts = {}
693 if not globbed and default == 'relpath':
693 if not globbed and default == 'relpath':
694 pats = expandpats(pats or [])
694 pats = expandpats(pats or [])
695
695
696 def bad(f, msg):
696 def bad(f, msg):
697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
697 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
698
698
699 if badfn is None:
699 if badfn is None:
700 badfn = bad
700 badfn = bad
701
701
702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
702 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
703 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
704
704
705 if m.always():
705 if m.always():
706 pats = []
706 pats = []
707 return m, pats
707 return m, pats
708
708
709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
709 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
710 badfn=None):
710 badfn=None):
711 '''Return a matcher that will warn about bad matches.'''
711 '''Return a matcher that will warn about bad matches.'''
712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
712 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
713
713
714 def matchall(repo):
714 def matchall(repo):
715 '''Return a matcher that will efficiently match everything.'''
715 '''Return a matcher that will efficiently match everything.'''
716 return matchmod.always(repo.root, repo.getcwd())
716 return matchmod.always(repo.root, repo.getcwd())
717
717
718 def matchfiles(repo, files, badfn=None):
718 def matchfiles(repo, files, badfn=None):
719 '''Return a matcher that will efficiently match exactly these files.'''
719 '''Return a matcher that will efficiently match exactly these files.'''
720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
720 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
721
721
722 def parsefollowlinespattern(repo, rev, pat, msg):
722 def parsefollowlinespattern(repo, rev, pat, msg):
723 """Return a file name from `pat` pattern suitable for usage in followlines
723 """Return a file name from `pat` pattern suitable for usage in followlines
724 logic.
724 logic.
725 """
725 """
726 if not matchmod.patkind(pat):
726 if not matchmod.patkind(pat):
727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
727 return pathutil.canonpath(repo.root, repo.getcwd(), pat)
728 else:
728 else:
729 ctx = repo[rev]
729 ctx = repo[rev]
730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
730 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
731 files = [f for f in ctx if m(f)]
731 files = [f for f in ctx if m(f)]
732 if len(files) != 1:
732 if len(files) != 1:
733 raise error.ParseError(msg)
733 raise error.ParseError(msg)
734 return files[0]
734 return files[0]
735
735
736 def origpath(ui, repo, filepath):
736 def origpath(ui, repo, filepath):
737 '''customize where .orig files are created
737 '''customize where .orig files are created
738
738
739 Fetch user defined path from config file: [ui] origbackuppath = <path>
739 Fetch user defined path from config file: [ui] origbackuppath = <path>
740 Fall back to default (filepath with .orig suffix) if not specified
740 Fall back to default (filepath with .orig suffix) if not specified
741 '''
741 '''
742 origbackuppath = ui.config('ui', 'origbackuppath')
742 origbackuppath = ui.config('ui', 'origbackuppath')
743 if not origbackuppath:
743 if not origbackuppath:
744 return filepath + ".orig"
744 return filepath + ".orig"
745
745
746 # Convert filepath from an absolute path into a path inside the repo.
746 # Convert filepath from an absolute path into a path inside the repo.
747 filepathfromroot = util.normpath(os.path.relpath(filepath,
747 filepathfromroot = util.normpath(os.path.relpath(filepath,
748 start=repo.root))
748 start=repo.root))
749
749
750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
750 origvfs = vfs.vfs(repo.wjoin(origbackuppath))
751 origbackupdir = origvfs.dirname(filepathfromroot)
751 origbackupdir = origvfs.dirname(filepathfromroot)
752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
752 if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
753 ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
754
754
755 # Remove any files that conflict with the backup file's path
755 # Remove any files that conflict with the backup file's path
756 for f in reversed(list(util.finddirs(filepathfromroot))):
756 for f in reversed(list(util.finddirs(filepathfromroot))):
757 if origvfs.isfileorlink(f):
757 if origvfs.isfileorlink(f):
758 ui.note(_('removing conflicting file: %s\n')
758 ui.note(_('removing conflicting file: %s\n')
759 % origvfs.join(f))
759 % origvfs.join(f))
760 origvfs.unlink(f)
760 origvfs.unlink(f)
761 break
761 break
762
762
763 origvfs.makedirs(origbackupdir)
763 origvfs.makedirs(origbackupdir)
764
764
765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
765 if origvfs.isdir(filepathfromroot) and not origvfs.islink(filepathfromroot):
766 ui.note(_('removing conflicting directory: %s\n')
766 ui.note(_('removing conflicting directory: %s\n')
767 % origvfs.join(filepathfromroot))
767 % origvfs.join(filepathfromroot))
768 origvfs.rmtree(filepathfromroot, forcibly=True)
768 origvfs.rmtree(filepathfromroot, forcibly=True)
769
769
770 return origvfs.join(filepathfromroot)
770 return origvfs.join(filepathfromroot)
771
771
772 class _containsnode(object):
772 class _containsnode(object):
773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
773 """proxy __contains__(node) to container.__contains__ which accepts revs"""
774
774
775 def __init__(self, repo, revcontainer):
775 def __init__(self, repo, revcontainer):
776 self._torev = repo.changelog.rev
776 self._torev = repo.changelog.rev
777 self._revcontains = revcontainer.__contains__
777 self._revcontains = revcontainer.__contains__
778
778
779 def __contains__(self, node):
779 def __contains__(self, node):
780 return self._revcontains(self._torev(node))
780 return self._revcontains(self._torev(node))
781
781
782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
782 def cleanupnodes(repo, replacements, operation, moves=None, metadata=None,
783 fixphase=False, targetphase=None, backup=True):
783 fixphase=False, targetphase=None, backup=True):
784 """do common cleanups when old nodes are replaced by new nodes
784 """do common cleanups when old nodes are replaced by new nodes
785
785
786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
786 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
787 (we might also want to move working directory parent in the future)
787 (we might also want to move working directory parent in the future)
788
788
789 By default, bookmark moves are calculated automatically from 'replacements',
789 By default, bookmark moves are calculated automatically from 'replacements',
790 but 'moves' can be used to override that. Also, 'moves' may include
790 but 'moves' can be used to override that. Also, 'moves' may include
791 additional bookmark moves that should not have associated obsmarkers.
791 additional bookmark moves that should not have associated obsmarkers.
792
792
793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
793 replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
794 have replacements. operation is a string, like "rebase".
794 have replacements. operation is a string, like "rebase".
795
795
796 metadata is dictionary containing metadata to be stored in obsmarker if
796 metadata is dictionary containing metadata to be stored in obsmarker if
797 obsolescence is enabled.
797 obsolescence is enabled.
798 """
798 """
799 assert fixphase or targetphase is None
799 assert fixphase or targetphase is None
800 if not replacements and not moves:
800 if not replacements and not moves:
801 return
801 return
802
802
803 # translate mapping's other forms
803 # translate mapping's other forms
804 if not util.safehasattr(replacements, 'items'):
804 if not util.safehasattr(replacements, 'items'):
805 replacements = {n: () for n in replacements}
805 replacements = {n: () for n in replacements}
806
806
807 # Calculate bookmark movements
807 # Calculate bookmark movements
808 if moves is None:
808 if moves is None:
809 moves = {}
809 moves = {}
810 # Unfiltered repo is needed since nodes in replacements might be hidden.
810 # Unfiltered repo is needed since nodes in replacements might be hidden.
811 unfi = repo.unfiltered()
811 unfi = repo.unfiltered()
812 for oldnode, newnodes in replacements.items():
812 for oldnode, newnodes in replacements.items():
813 if oldnode in moves:
813 if oldnode in moves:
814 continue
814 continue
815 if len(newnodes) > 1:
815 if len(newnodes) > 1:
816 # usually a split, take the one with biggest rev number
816 # usually a split, take the one with biggest rev number
817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
817 newnode = next(unfi.set('max(%ln)', newnodes)).node()
818 elif len(newnodes) == 0:
818 elif len(newnodes) == 0:
819 # move bookmark backwards
819 # move bookmark backwards
820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
820 roots = list(unfi.set('max((::%n) - %ln)', oldnode,
821 list(replacements)))
821 list(replacements)))
822 if roots:
822 if roots:
823 newnode = roots[0].node()
823 newnode = roots[0].node()
824 else:
824 else:
825 newnode = nullid
825 newnode = nullid
826 else:
826 else:
827 newnode = newnodes[0]
827 newnode = newnodes[0]
828 moves[oldnode] = newnode
828 moves[oldnode] = newnode
829
829
830 allnewnodes = [n for ns in replacements.values() for n in ns]
830 allnewnodes = [n for ns in replacements.values() for n in ns]
831 toretract = {}
831 toretract = {}
832 toadvance = {}
832 toadvance = {}
833 if fixphase:
833 if fixphase:
834 precursors = {}
834 precursors = {}
835 for oldnode, newnodes in replacements.items():
835 for oldnode, newnodes in replacements.items():
836 for newnode in newnodes:
836 for newnode in newnodes:
837 precursors.setdefault(newnode, []).append(oldnode)
837 precursors.setdefault(newnode, []).append(oldnode)
838
838
839 allnewnodes.sort(key=lambda n: unfi[n].rev())
839 allnewnodes.sort(key=lambda n: unfi[n].rev())
840 newphases = {}
840 newphases = {}
841 def phase(ctx):
841 def phase(ctx):
842 return newphases.get(ctx.node(), ctx.phase())
842 return newphases.get(ctx.node(), ctx.phase())
843 for newnode in allnewnodes:
843 for newnode in allnewnodes:
844 ctx = unfi[newnode]
844 ctx = unfi[newnode]
845 parentphase = max(phase(p) for p in ctx.parents())
845 parentphase = max(phase(p) for p in ctx.parents())
846 if targetphase is None:
846 if targetphase is None:
847 oldphase = max(unfi[oldnode].phase()
847 oldphase = max(unfi[oldnode].phase()
848 for oldnode in precursors[newnode])
848 for oldnode in precursors[newnode])
849 newphase = max(oldphase, parentphase)
849 newphase = max(oldphase, parentphase)
850 else:
850 else:
851 newphase = max(targetphase, parentphase)
851 newphase = max(targetphase, parentphase)
852 newphases[newnode] = newphase
852 newphases[newnode] = newphase
853 if newphase > ctx.phase():
853 if newphase > ctx.phase():
854 toretract.setdefault(newphase, []).append(newnode)
854 toretract.setdefault(newphase, []).append(newnode)
855 elif newphase < ctx.phase():
855 elif newphase < ctx.phase():
856 toadvance.setdefault(newphase, []).append(newnode)
856 toadvance.setdefault(newphase, []).append(newnode)
857
857
858 with repo.transaction('cleanup') as tr:
858 with repo.transaction('cleanup') as tr:
859 # Move bookmarks
859 # Move bookmarks
860 bmarks = repo._bookmarks
860 bmarks = repo._bookmarks
861 bmarkchanges = []
861 bmarkchanges = []
862 for oldnode, newnode in moves.items():
862 for oldnode, newnode in moves.items():
863 oldbmarks = repo.nodebookmarks(oldnode)
863 oldbmarks = repo.nodebookmarks(oldnode)
864 if not oldbmarks:
864 if not oldbmarks:
865 continue
865 continue
866 from . import bookmarks # avoid import cycle
866 from . import bookmarks # avoid import cycle
867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
867 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
868 (pycompat.rapply(pycompat.maybebytestr, oldbmarks),
869 hex(oldnode), hex(newnode)))
869 hex(oldnode), hex(newnode)))
870 # Delete divergent bookmarks being parents of related newnodes
870 # Delete divergent bookmarks being parents of related newnodes
871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
871 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
872 allnewnodes, newnode, oldnode)
872 allnewnodes, newnode, oldnode)
873 deletenodes = _containsnode(repo, deleterevs)
873 deletenodes = _containsnode(repo, deleterevs)
874 for name in oldbmarks:
874 for name in oldbmarks:
875 bmarkchanges.append((name, newnode))
875 bmarkchanges.append((name, newnode))
876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
876 for b in bookmarks.divergent2delete(repo, deletenodes, name):
877 bmarkchanges.append((b, None))
877 bmarkchanges.append((b, None))
878
878
879 if bmarkchanges:
879 if bmarkchanges:
880 bmarks.applychanges(repo, tr, bmarkchanges)
880 bmarks.applychanges(repo, tr, bmarkchanges)
881
881
882 for phase, nodes in toretract.items():
882 for phase, nodes in toretract.items():
883 phases.retractboundary(repo, tr, phase, nodes)
883 phases.retractboundary(repo, tr, phase, nodes)
884 for phase, nodes in toadvance.items():
884 for phase, nodes in toadvance.items():
885 phases.advanceboundary(repo, tr, phase, nodes)
885 phases.advanceboundary(repo, tr, phase, nodes)
886
886
887 # Obsolete or strip nodes
887 # Obsolete or strip nodes
888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
888 if obsolete.isenabled(repo, obsolete.createmarkersopt):
889 # If a node is already obsoleted, and we want to obsolete it
889 # If a node is already obsoleted, and we want to obsolete it
890 # without a successor, skip that obssolete request since it's
890 # without a successor, skip that obssolete request since it's
891 # unnecessary. That's the "if s or not isobs(n)" check below.
891 # unnecessary. That's the "if s or not isobs(n)" check below.
892 # Also sort the node in topology order, that might be useful for
892 # Also sort the node in topology order, that might be useful for
893 # some obsstore logic.
893 # some obsstore logic.
894 # NOTE: the filtering and sorting might belong to createmarkers.
894 # NOTE: the filtering and sorting might belong to createmarkers.
895 isobs = unfi.obsstore.successors.__contains__
895 isobs = unfi.obsstore.successors.__contains__
896 torev = unfi.changelog.rev
896 torev = unfi.changelog.rev
897 sortfunc = lambda ns: torev(ns[0])
897 sortfunc = lambda ns: torev(ns[0])
898 rels = [(unfi[n], tuple(unfi[m] for m in s))
898 rels = [(unfi[n], tuple(unfi[m] for m in s))
899 for n, s in sorted(replacements.items(), key=sortfunc)
899 for n, s in sorted(replacements.items(), key=sortfunc)
900 if s or not isobs(n)]
900 if s or not isobs(n)]
901 if rels:
901 if rels:
902 obsolete.createmarkers(repo, rels, operation=operation,
902 obsolete.createmarkers(repo, rels, operation=operation,
903 metadata=metadata)
903 metadata=metadata)
904 else:
904 else:
905 from . import repair # avoid import cycle
905 from . import repair # avoid import cycle
906 tostrip = list(replacements)
906 tostrip = list(replacements)
907 if tostrip:
907 if tostrip:
908 repair.delayedstrip(repo.ui, repo, tostrip, operation,
908 repair.delayedstrip(repo.ui, repo, tostrip, operation,
909 backup=backup)
909 backup=backup)
910
910
911 def addremove(repo, matcher, prefix, opts=None):
911 def addremove(repo, matcher, prefix, opts=None):
912 if opts is None:
912 if opts is None:
913 opts = {}
913 opts = {}
914 m = matcher
914 m = matcher
915 dry_run = opts.get('dry_run')
915 dry_run = opts.get('dry_run')
916 try:
916 try:
917 similarity = float(opts.get('similarity') or 0)
917 similarity = float(opts.get('similarity') or 0)
918 except ValueError:
918 except ValueError:
919 raise error.Abort(_('similarity must be a number'))
919 raise error.Abort(_('similarity must be a number'))
920 if similarity < 0 or similarity > 100:
920 if similarity < 0 or similarity > 100:
921 raise error.Abort(_('similarity must be between 0 and 100'))
921 raise error.Abort(_('similarity must be between 0 and 100'))
922 similarity /= 100.0
922 similarity /= 100.0
923
923
924 ret = 0
924 ret = 0
925 join = lambda f: os.path.join(prefix, f)
925 join = lambda f: os.path.join(prefix, f)
926
926
927 wctx = repo[None]
927 wctx = repo[None]
928 for subpath in sorted(wctx.substate):
928 for subpath in sorted(wctx.substate):
929 submatch = matchmod.subdirmatcher(subpath, m)
929 submatch = matchmod.subdirmatcher(subpath, m)
930 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
930 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
931 sub = wctx.sub(subpath)
931 sub = wctx.sub(subpath)
932 try:
932 try:
933 if sub.addremove(submatch, prefix, opts):
933 if sub.addremove(submatch, prefix, opts):
934 ret = 1
934 ret = 1
935 except error.LookupError:
935 except error.LookupError:
936 repo.ui.status(_("skipping missing subrepository: %s\n")
936 repo.ui.status(_("skipping missing subrepository: %s\n")
937 % join(subpath))
937 % join(subpath))
938
938
939 rejected = []
939 rejected = []
940 def badfn(f, msg):
940 def badfn(f, msg):
941 if f in m.files():
941 if f in m.files():
942 m.bad(f, msg)
942 m.bad(f, msg)
943 rejected.append(f)
943 rejected.append(f)
944
944
945 badmatch = matchmod.badmatch(m, badfn)
945 badmatch = matchmod.badmatch(m, badfn)
946 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
946 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
947 badmatch)
947 badmatch)
948
948
949 unknownset = set(unknown + forgotten)
949 unknownset = set(unknown + forgotten)
950 toprint = unknownset.copy()
950 toprint = unknownset.copy()
951 toprint.update(deleted)
951 toprint.update(deleted)
952 for abs in sorted(toprint):
952 for abs in sorted(toprint):
953 if repo.ui.verbose or not m.exact(abs):
953 if repo.ui.verbose or not m.exact(abs):
954 if abs in unknownset:
954 if abs in unknownset:
955 status = _('adding %s\n') % m.uipath(abs)
955 status = _('adding %s\n') % m.uipath(abs)
956 else:
956 else:
957 status = _('removing %s\n') % m.uipath(abs)
957 status = _('removing %s\n') % m.uipath(abs)
958 repo.ui.status(status)
958 repo.ui.status(status)
959
959
960 renames = _findrenames(repo, m, added + unknown, removed + deleted,
960 renames = _findrenames(repo, m, added + unknown, removed + deleted,
961 similarity)
961 similarity)
962
962
963 if not dry_run:
963 if not dry_run:
964 _markchanges(repo, unknown + forgotten, deleted, renames)
964 _markchanges(repo, unknown + forgotten, deleted, renames)
965
965
966 for f in rejected:
966 for f in rejected:
967 if f in m.files():
967 if f in m.files():
968 return 1
968 return 1
969 return ret
969 return ret
970
970
971 def marktouched(repo, files, similarity=0.0):
971 def marktouched(repo, files, similarity=0.0):
972 '''Assert that files have somehow been operated upon. files are relative to
972 '''Assert that files have somehow been operated upon. files are relative to
973 the repo root.'''
973 the repo root.'''
974 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
974 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
975 rejected = []
975 rejected = []
976
976
977 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
977 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
978
978
979 if repo.ui.verbose:
979 if repo.ui.verbose:
980 unknownset = set(unknown + forgotten)
980 unknownset = set(unknown + forgotten)
981 toprint = unknownset.copy()
981 toprint = unknownset.copy()
982 toprint.update(deleted)
982 toprint.update(deleted)
983 for abs in sorted(toprint):
983 for abs in sorted(toprint):
984 if abs in unknownset:
984 if abs in unknownset:
985 status = _('adding %s\n') % abs
985 status = _('adding %s\n') % abs
986 else:
986 else:
987 status = _('removing %s\n') % abs
987 status = _('removing %s\n') % abs
988 repo.ui.status(status)
988 repo.ui.status(status)
989
989
990 renames = _findrenames(repo, m, added + unknown, removed + deleted,
990 renames = _findrenames(repo, m, added + unknown, removed + deleted,
991 similarity)
991 similarity)
992
992
993 _markchanges(repo, unknown + forgotten, deleted, renames)
993 _markchanges(repo, unknown + forgotten, deleted, renames)
994
994
995 for f in rejected:
995 for f in rejected:
996 if f in m.files():
996 if f in m.files():
997 return 1
997 return 1
998 return 0
998 return 0
999
999
1000 def _interestingfiles(repo, matcher):
1000 def _interestingfiles(repo, matcher):
1001 '''Walk dirstate with matcher, looking for files that addremove would care
1001 '''Walk dirstate with matcher, looking for files that addremove would care
1002 about.
1002 about.
1003
1003
1004 This is different from dirstate.status because it doesn't care about
1004 This is different from dirstate.status because it doesn't care about
1005 whether files are modified or clean.'''
1005 whether files are modified or clean.'''
1006 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1006 added, unknown, deleted, removed, forgotten = [], [], [], [], []
1007 audit_path = pathutil.pathauditor(repo.root, cached=True)
1007 audit_path = pathutil.pathauditor(repo.root, cached=True)
1008
1008
1009 ctx = repo[None]
1009 ctx = repo[None]
1010 dirstate = repo.dirstate
1010 dirstate = repo.dirstate
1011 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1011 walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
1012 unknown=True, ignored=False, full=False)
1012 unknown=True, ignored=False, full=False)
1013 for abs, st in walkresults.iteritems():
1013 for abs, st in walkresults.iteritems():
1014 dstate = dirstate[abs]
1014 dstate = dirstate[abs]
1015 if dstate == '?' and audit_path.check(abs):
1015 if dstate == '?' and audit_path.check(abs):
1016 unknown.append(abs)
1016 unknown.append(abs)
1017 elif dstate != 'r' and not st:
1017 elif dstate != 'r' and not st:
1018 deleted.append(abs)
1018 deleted.append(abs)
1019 elif dstate == 'r' and st:
1019 elif dstate == 'r' and st:
1020 forgotten.append(abs)
1020 forgotten.append(abs)
1021 # for finding renames
1021 # for finding renames
1022 elif dstate == 'r' and not st:
1022 elif dstate == 'r' and not st:
1023 removed.append(abs)
1023 removed.append(abs)
1024 elif dstate == 'a':
1024 elif dstate == 'a':
1025 added.append(abs)
1025 added.append(abs)
1026
1026
1027 return added, unknown, deleted, removed, forgotten
1027 return added, unknown, deleted, removed, forgotten
1028
1028
1029 def _findrenames(repo, matcher, added, removed, similarity):
1029 def _findrenames(repo, matcher, added, removed, similarity):
1030 '''Find renames from removed files to added ones.'''
1030 '''Find renames from removed files to added ones.'''
1031 renames = {}
1031 renames = {}
1032 if similarity > 0:
1032 if similarity > 0:
1033 for old, new, score in similar.findrenames(repo, added, removed,
1033 for old, new, score in similar.findrenames(repo, added, removed,
1034 similarity):
1034 similarity):
1035 if (repo.ui.verbose or not matcher.exact(old)
1035 if (repo.ui.verbose or not matcher.exact(old)
1036 or not matcher.exact(new)):
1036 or not matcher.exact(new)):
1037 repo.ui.status(_('recording removal of %s as rename to %s '
1037 repo.ui.status(_('recording removal of %s as rename to %s '
1038 '(%d%% similar)\n') %
1038 '(%d%% similar)\n') %
1039 (matcher.rel(old), matcher.rel(new),
1039 (matcher.rel(old), matcher.rel(new),
1040 score * 100))
1040 score * 100))
1041 renames[new] = old
1041 renames[new] = old
1042 return renames
1042 return renames
1043
1043
1044 def _markchanges(repo, unknown, deleted, renames):
1044 def _markchanges(repo, unknown, deleted, renames):
1045 '''Marks the files in unknown as added, the files in deleted as removed,
1045 '''Marks the files in unknown as added, the files in deleted as removed,
1046 and the files in renames as copied.'''
1046 and the files in renames as copied.'''
1047 wctx = repo[None]
1047 wctx = repo[None]
1048 with repo.wlock():
1048 with repo.wlock():
1049 wctx.forget(deleted)
1049 wctx.forget(deleted)
1050 wctx.add(unknown)
1050 wctx.add(unknown)
1051 for new, old in renames.iteritems():
1051 for new, old in renames.iteritems():
1052 wctx.copy(old, new)
1052 wctx.copy(old, new)
1053
1053
1054 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1054 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
1055 """Update the dirstate to reflect the intent of copying src to dst. For
1055 """Update the dirstate to reflect the intent of copying src to dst. For
1056 different reasons it might not end with dst being marked as copied from src.
1056 different reasons it might not end with dst being marked as copied from src.
1057 """
1057 """
1058 origsrc = repo.dirstate.copied(src) or src
1058 origsrc = repo.dirstate.copied(src) or src
1059 if dst == origsrc: # copying back a copy?
1059 if dst == origsrc: # copying back a copy?
1060 if repo.dirstate[dst] not in 'mn' and not dryrun:
1060 if repo.dirstate[dst] not in 'mn' and not dryrun:
1061 repo.dirstate.normallookup(dst)
1061 repo.dirstate.normallookup(dst)
1062 else:
1062 else:
1063 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1063 if repo.dirstate[origsrc] == 'a' and origsrc == src:
1064 if not ui.quiet:
1064 if not ui.quiet:
1065 ui.warn(_("%s has not been committed yet, so no copy "
1065 ui.warn(_("%s has not been committed yet, so no copy "
1066 "data will be stored for %s.\n")
1066 "data will be stored for %s.\n")
1067 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1067 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
1068 if repo.dirstate[dst] in '?r' and not dryrun:
1068 if repo.dirstate[dst] in '?r' and not dryrun:
1069 wctx.add([dst])
1069 wctx.add([dst])
1070 elif not dryrun:
1070 elif not dryrun:
1071 wctx.copy(origsrc, dst)
1071 wctx.copy(origsrc, dst)
1072
1072
1073 def readrequires(opener, supported):
1073 def readrequires(opener, supported):
1074 '''Reads and parses .hg/requires and checks if all entries found
1074 '''Reads and parses .hg/requires and checks if all entries found
1075 are in the list of supported features.'''
1075 are in the list of supported features.'''
1076 requirements = set(opener.read("requires").splitlines())
1076 requirements = set(opener.read("requires").splitlines())
1077 missings = []
1077 missings = []
1078 for r in requirements:
1078 for r in requirements:
1079 if r not in supported:
1079 if r not in supported:
1080 if not r or not r[0:1].isalnum():
1080 if not r or not r[0:1].isalnum():
1081 raise error.RequirementError(_(".hg/requires file is corrupt"))
1081 raise error.RequirementError(_(".hg/requires file is corrupt"))
1082 missings.append(r)
1082 missings.append(r)
1083 missings.sort()
1083 missings.sort()
1084 if missings:
1084 if missings:
1085 raise error.RequirementError(
1085 raise error.RequirementError(
1086 _("repository requires features unknown to this Mercurial: %s")
1086 _("repository requires features unknown to this Mercurial: %s")
1087 % " ".join(missings),
1087 % " ".join(missings),
1088 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1088 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
1089 " for more information"))
1089 " for more information"))
1090 return requirements
1090 return requirements
1091
1091
1092 def writerequires(opener, requirements):
1092 def writerequires(opener, requirements):
1093 with opener('requires', 'w') as fp:
1093 with opener('requires', 'w') as fp:
1094 for r in sorted(requirements):
1094 for r in sorted(requirements):
1095 fp.write("%s\n" % r)
1095 fp.write("%s\n" % r)
1096
1096
1097 class filecachesubentry(object):
1097 class filecachesubentry(object):
1098 def __init__(self, path, stat):
1098 def __init__(self, path, stat):
1099 self.path = path
1099 self.path = path
1100 self.cachestat = None
1100 self.cachestat = None
1101 self._cacheable = None
1101 self._cacheable = None
1102
1102
1103 if stat:
1103 if stat:
1104 self.cachestat = filecachesubentry.stat(self.path)
1104 self.cachestat = filecachesubentry.stat(self.path)
1105
1105
1106 if self.cachestat:
1106 if self.cachestat:
1107 self._cacheable = self.cachestat.cacheable()
1107 self._cacheable = self.cachestat.cacheable()
1108 else:
1108 else:
1109 # None means we don't know yet
1109 # None means we don't know yet
1110 self._cacheable = None
1110 self._cacheable = None
1111
1111
1112 def refresh(self):
1112 def refresh(self):
1113 if self.cacheable():
1113 if self.cacheable():
1114 self.cachestat = filecachesubentry.stat(self.path)
1114 self.cachestat = filecachesubentry.stat(self.path)
1115
1115
1116 def cacheable(self):
1116 def cacheable(self):
1117 if self._cacheable is not None:
1117 if self._cacheable is not None:
1118 return self._cacheable
1118 return self._cacheable
1119
1119
1120 # we don't know yet, assume it is for now
1120 # we don't know yet, assume it is for now
1121 return True
1121 return True
1122
1122
1123 def changed(self):
1123 def changed(self):
1124 # no point in going further if we can't cache it
1124 # no point in going further if we can't cache it
1125 if not self.cacheable():
1125 if not self.cacheable():
1126 return True
1126 return True
1127
1127
1128 newstat = filecachesubentry.stat(self.path)
1128 newstat = filecachesubentry.stat(self.path)
1129
1129
1130 # we may not know if it's cacheable yet, check again now
1130 # we may not know if it's cacheable yet, check again now
1131 if newstat and self._cacheable is None:
1131 if newstat and self._cacheable is None:
1132 self._cacheable = newstat.cacheable()
1132 self._cacheable = newstat.cacheable()
1133
1133
1134 # check again
1134 # check again
1135 if not self._cacheable:
1135 if not self._cacheable:
1136 return True
1136 return True
1137
1137
1138 if self.cachestat != newstat:
1138 if self.cachestat != newstat:
1139 self.cachestat = newstat
1139 self.cachestat = newstat
1140 return True
1140 return True
1141 else:
1141 else:
1142 return False
1142 return False
1143
1143
1144 @staticmethod
1144 @staticmethod
1145 def stat(path):
1145 def stat(path):
1146 try:
1146 try:
1147 return util.cachestat(path)
1147 return util.cachestat(path)
1148 except OSError as e:
1148 except OSError as e:
1149 if e.errno != errno.ENOENT:
1149 if e.errno != errno.ENOENT:
1150 raise
1150 raise
1151
1151
1152 class filecacheentry(object):
1152 class filecacheentry(object):
1153 def __init__(self, paths, stat=True):
1153 def __init__(self, paths, stat=True):
1154 self._entries = []
1154 self._entries = []
1155 for path in paths:
1155 for path in paths:
1156 self._entries.append(filecachesubentry(path, stat))
1156 self._entries.append(filecachesubentry(path, stat))
1157
1157
1158 def changed(self):
1158 def changed(self):
1159 '''true if any entry has changed'''
1159 '''true if any entry has changed'''
1160 for entry in self._entries:
1160 for entry in self._entries:
1161 if entry.changed():
1161 if entry.changed():
1162 return True
1162 return True
1163 return False
1163 return False
1164
1164
1165 def refresh(self):
1165 def refresh(self):
1166 for entry in self._entries:
1166 for entry in self._entries:
1167 entry.refresh()
1167 entry.refresh()
1168
1168
1169 class filecache(object):
1169 class filecache(object):
1170 """A property like decorator that tracks files under .hg/ for updates.
1170 """A property like decorator that tracks files under .hg/ for updates.
1171
1171
1172 On first access, the files defined as arguments are stat()ed and the
1172 On first access, the files defined as arguments are stat()ed and the
1173 results cached. The decorated function is called. The results are stashed
1173 results cached. The decorated function is called. The results are stashed
1174 away in a ``_filecache`` dict on the object whose method is decorated.
1174 away in a ``_filecache`` dict on the object whose method is decorated.
1175
1175
1176 On subsequent access, the cached result is returned.
1176 On subsequent access, the cached result is returned.
1177
1177
1178 On external property set operations, stat() calls are performed and the new
1178 On external property set operations, stat() calls are performed and the new
1179 value is cached.
1179 value is cached.
1180
1180
1181 On property delete operations, cached data is removed.
1181 On property delete operations, cached data is removed.
1182
1182
1183 When using the property API, cached data is always returned, if available:
1183 When using the property API, cached data is always returned, if available:
1184 no stat() is performed to check if the file has changed and if the function
1184 no stat() is performed to check if the file has changed and if the function
1185 needs to be called to reflect file changes.
1185 needs to be called to reflect file changes.
1186
1186
1187 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1187 Others can muck about with the state of the ``_filecache`` dict. e.g. they
1188 can populate an entry before the property's getter is called. In this case,
1188 can populate an entry before the property's getter is called. In this case,
1189 entries in ``_filecache`` will be used during property operations,
1189 entries in ``_filecache`` will be used during property operations,
1190 if available. If the underlying file changes, it is up to external callers
1190 if available. If the underlying file changes, it is up to external callers
1191 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1191 to reflect this by e.g. calling ``delattr(obj, attr)`` to remove the cached
1192 method result as well as possibly calling ``del obj._filecache[attr]`` to
1192 method result as well as possibly calling ``del obj._filecache[attr]`` to
1193 remove the ``filecacheentry``.
1193 remove the ``filecacheentry``.
1194 """
1194 """
1195
1195
1196 def __init__(self, *paths):
1196 def __init__(self, *paths):
1197 self.paths = paths
1197 self.paths = paths
1198
1198
1199 def join(self, obj, fname):
1199 def join(self, obj, fname):
1200 """Used to compute the runtime path of a cached file.
1200 """Used to compute the runtime path of a cached file.
1201
1201
1202 Users should subclass filecache and provide their own version of this
1202 Users should subclass filecache and provide their own version of this
1203 function to call the appropriate join function on 'obj' (an instance
1203 function to call the appropriate join function on 'obj' (an instance
1204 of the class that its member function was decorated).
1204 of the class that its member function was decorated).
1205 """
1205 """
1206 raise NotImplementedError
1206 raise NotImplementedError
1207
1207
1208 def __call__(self, func):
1208 def __call__(self, func):
1209 self.func = func
1209 self.func = func
1210 self.sname = func.__name__
1210 self.sname = func.__name__
1211 self.name = pycompat.sysbytes(self.sname)
1211 self.name = pycompat.sysbytes(self.sname)
1212 return self
1212 return self
1213
1213
1214 def __get__(self, obj, type=None):
1214 def __get__(self, obj, type=None):
1215 # if accessed on the class, return the descriptor itself.
1215 # if accessed on the class, return the descriptor itself.
1216 if obj is None:
1216 if obj is None:
1217 return self
1217 return self
1218 # do we need to check if the file changed?
1218 # do we need to check if the file changed?
1219 if self.sname in obj.__dict__:
1219 if self.sname in obj.__dict__:
1220 assert self.name in obj._filecache, self.name
1220 assert self.name in obj._filecache, self.name
1221 return obj.__dict__[self.sname]
1221 return obj.__dict__[self.sname]
1222
1222
1223 entry = obj._filecache.get(self.name)
1223 entry = obj._filecache.get(self.name)
1224
1224
1225 if entry:
1225 if entry:
1226 if entry.changed():
1226 if entry.changed():
1227 entry.obj = self.func(obj)
1227 entry.obj = self.func(obj)
1228 else:
1228 else:
1229 paths = [self.join(obj, path) for path in self.paths]
1229 paths = [self.join(obj, path) for path in self.paths]
1230
1230
1231 # We stat -before- creating the object so our cache doesn't lie if
1231 # We stat -before- creating the object so our cache doesn't lie if
1232 # a writer modified between the time we read and stat
1232 # a writer modified between the time we read and stat
1233 entry = filecacheentry(paths, True)
1233 entry = filecacheentry(paths, True)
1234 entry.obj = self.func(obj)
1234 entry.obj = self.func(obj)
1235
1235
1236 obj._filecache[self.name] = entry
1236 obj._filecache[self.name] = entry
1237
1237
1238 obj.__dict__[self.sname] = entry.obj
1238 obj.__dict__[self.sname] = entry.obj
1239 return entry.obj
1239 return entry.obj
1240
1240
1241 def __set__(self, obj, value):
1241 def __set__(self, obj, value):
1242 if self.name not in obj._filecache:
1242 if self.name not in obj._filecache:
1243 # we add an entry for the missing value because X in __dict__
1243 # we add an entry for the missing value because X in __dict__
1244 # implies X in _filecache
1244 # implies X in _filecache
1245 paths = [self.join(obj, path) for path in self.paths]
1245 paths = [self.join(obj, path) for path in self.paths]
1246 ce = filecacheentry(paths, False)
1246 ce = filecacheentry(paths, False)
1247 obj._filecache[self.name] = ce
1247 obj._filecache[self.name] = ce
1248 else:
1248 else:
1249 ce = obj._filecache[self.name]
1249 ce = obj._filecache[self.name]
1250
1250
1251 ce.obj = value # update cached copy
1251 ce.obj = value # update cached copy
1252 obj.__dict__[self.sname] = value # update copy returned by obj.x
1252 obj.__dict__[self.sname] = value # update copy returned by obj.x
1253
1253
1254 def __delete__(self, obj):
1254 def __delete__(self, obj):
1255 try:
1255 try:
1256 del obj.__dict__[self.sname]
1256 del obj.__dict__[self.sname]
1257 except KeyError:
1257 except KeyError:
1258 raise AttributeError(self.sname)
1258 raise AttributeError(self.sname)
1259
1259
1260 def extdatasource(repo, source):
1260 def extdatasource(repo, source):
1261 """Gather a map of rev -> value dict from the specified source
1261 """Gather a map of rev -> value dict from the specified source
1262
1262
1263 A source spec is treated as a URL, with a special case shell: type
1263 A source spec is treated as a URL, with a special case shell: type
1264 for parsing the output from a shell command.
1264 for parsing the output from a shell command.
1265
1265
1266 The data is parsed as a series of newline-separated records where
1266 The data is parsed as a series of newline-separated records where
1267 each record is a revision specifier optionally followed by a space
1267 each record is a revision specifier optionally followed by a space
1268 and a freeform string value. If the revision is known locally, it
1268 and a freeform string value. If the revision is known locally, it
1269 is converted to a rev, otherwise the record is skipped.
1269 is converted to a rev, otherwise the record is skipped.
1270
1270
1271 Note that both key and value are treated as UTF-8 and converted to
1271 Note that both key and value are treated as UTF-8 and converted to
1272 the local encoding. This allows uniformity between local and
1272 the local encoding. This allows uniformity between local and
1273 remote data sources.
1273 remote data sources.
1274 """
1274 """
1275
1275
1276 spec = repo.ui.config("extdata", source)
1276 spec = repo.ui.config("extdata", source)
1277 if not spec:
1277 if not spec:
1278 raise error.Abort(_("unknown extdata source '%s'") % source)
1278 raise error.Abort(_("unknown extdata source '%s'") % source)
1279
1279
1280 data = {}
1280 data = {}
1281 src = proc = None
1281 src = proc = None
1282 try:
1282 try:
1283 if spec.startswith("shell:"):
1283 if spec.startswith("shell:"):
1284 # external commands should be run relative to the repo root
1284 # external commands should be run relative to the repo root
1285 cmd = spec[6:]
1285 cmd = spec[6:]
1286 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1286 proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
1287 close_fds=procutil.closefds,
1287 close_fds=procutil.closefds,
1288 stdout=subprocess.PIPE, cwd=repo.root)
1288 stdout=subprocess.PIPE, cwd=repo.root)
1289 src = proc.stdout
1289 src = proc.stdout
1290 else:
1290 else:
1291 # treat as a URL or file
1291 # treat as a URL or file
1292 src = url.open(repo.ui, spec)
1292 src = url.open(repo.ui, spec)
1293 for l in src:
1293 for l in src:
1294 if " " in l:
1294 if " " in l:
1295 k, v = l.strip().split(" ", 1)
1295 k, v = l.strip().split(" ", 1)
1296 else:
1296 else:
1297 k, v = l.strip(), ""
1297 k, v = l.strip(), ""
1298
1298
1299 k = encoding.tolocal(k)
1299 k = encoding.tolocal(k)
1300 try:
1300 try:
1301 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1301 data[revsingle(repo, k).rev()] = encoding.tolocal(v)
1302 except (error.LookupError, error.RepoLookupError):
1302 except (error.LookupError, error.RepoLookupError):
1303 pass # we ignore data for nodes that don't exist locally
1303 pass # we ignore data for nodes that don't exist locally
1304 finally:
1304 finally:
1305 if proc:
1305 if proc:
1306 proc.communicate()
1306 proc.communicate()
1307 if src:
1307 if src:
1308 src.close()
1308 src.close()
1309 if proc and proc.returncode != 0:
1309 if proc and proc.returncode != 0:
1310 raise error.Abort(_("extdata command '%s' failed: %s")
1310 raise error.Abort(_("extdata command '%s' failed: %s")
1311 % (cmd, procutil.explainexit(proc.returncode)))
1311 % (cmd, procutil.explainexit(proc.returncode)))
1312
1312
1313 return data
1313 return data
1314
1314
1315 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1315 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
1316 if lock is None:
1316 if lock is None:
1317 raise error.LockInheritanceContractViolation(
1317 raise error.LockInheritanceContractViolation(
1318 'lock can only be inherited while held')
1318 'lock can only be inherited while held')
1319 if environ is None:
1319 if environ is None:
1320 environ = {}
1320 environ = {}
1321 with lock.inherit() as locker:
1321 with lock.inherit() as locker:
1322 environ[envvar] = locker
1322 environ[envvar] = locker
1323 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1323 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
1324
1324
1325 def wlocksub(repo, cmd, *args, **kwargs):
1325 def wlocksub(repo, cmd, *args, **kwargs):
1326 """run cmd as a subprocess that allows inheriting repo's wlock
1326 """run cmd as a subprocess that allows inheriting repo's wlock
1327
1327
1328 This can only be called while the wlock is held. This takes all the
1328 This can only be called while the wlock is held. This takes all the
1329 arguments that ui.system does, and returns the exit code of the
1329 arguments that ui.system does, and returns the exit code of the
1330 subprocess."""
1330 subprocess."""
1331 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1331 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
1332 **kwargs)
1332 **kwargs)
1333
1333
1334 class progress(object):
1334 class progress(object):
1335 def __init__(self, ui, topic, unit="", total=None):
1335 def __init__(self, ui, topic, unit="", total=None):
1336 self.ui = ui
1336 self.ui = ui
1337 self.pos = 0
1337 self.pos = 0
1338 self.topic = topic
1338 self.topic = topic
1339 self.unit = unit
1339 self.unit = unit
1340 self.total = total
1340 self.total = total
1341
1341
1342 def __enter__(self):
1342 def __enter__(self):
1343 return self
1343 return self
1344
1344
1345 def __exit__(self, exc_type, exc_value, exc_tb):
1345 def __exit__(self, exc_type, exc_value, exc_tb):
1346 self.complete()
1346 self.complete()
1347
1347
1348 def update(self, pos, item="", total=None):
1348 def update(self, pos, item="", total=None):
1349 assert pos is not None
1349 assert pos is not None
1350 if total:
1350 if total:
1351 self.total = total
1351 self.total = total
1352 self.pos = pos
1352 self.pos = pos
1353 self._print(item)
1353 self._print(item)
1354
1354
1355 def increment(self, step=1, item="", total=None):
1355 def increment(self, step=1, item="", total=None):
1356 self.update(self.pos + step, item, total)
1356 self.update(self.pos + step, item, total)
1357
1357
1358 def complete(self):
1358 def complete(self):
1359 self.ui.progress(self.topic, None)
1359 self.ui.progress(self.topic, None)
1360
1360
1361 def _print(self, item):
1361 def _print(self, item):
1362 self.ui.progress(self.topic, self.pos, item, self.unit,
1362 self.ui.progress(self.topic, self.pos, item, self.unit,
1363 self.total)
1363 self.total)
1364
1364
1365 def gdinitconfig(ui):
1365 def gdinitconfig(ui):
1366 """helper function to know if a repo should be created as general delta
1366 """helper function to know if a repo should be created as general delta
1367 """
1367 """
1368 # experimental config: format.generaldelta
1368 # experimental config: format.generaldelta
1369 return (ui.configbool('format', 'generaldelta')
1369 return (ui.configbool('format', 'generaldelta')
1370 or ui.configbool('format', 'usegeneraldelta')
1370 or ui.configbool('format', 'usegeneraldelta')
1371 or ui.configbool('format', 'sparse-revlog'))
1371 or ui.configbool('format', 'sparse-revlog'))
1372
1372
1373 def gddeltaconfig(ui):
1373 def gddeltaconfig(ui):
1374 """helper function to know if incoming delta should be optimised
1374 """helper function to know if incoming delta should be optimised
1375 """
1375 """
1376 # experimental config: format.generaldelta
1376 # experimental config: format.generaldelta
1377 return ui.configbool('format', 'generaldelta')
1377 return ui.configbool('format', 'generaldelta')
1378
1378
1379 class simplekeyvaluefile(object):
1379 class simplekeyvaluefile(object):
1380 """A simple file with key=value lines
1380 """A simple file with key=value lines
1381
1381
1382 Keys must be alphanumerics and start with a letter, values must not
1382 Keys must be alphanumerics and start with a letter, values must not
1383 contain '\n' characters"""
1383 contain '\n' characters"""
1384 firstlinekey = '__firstline'
1384 firstlinekey = '__firstline'
1385
1385
1386 def __init__(self, vfs, path, keys=None):
1386 def __init__(self, vfs, path, keys=None):
1387 self.vfs = vfs
1387 self.vfs = vfs
1388 self.path = path
1388 self.path = path
1389
1389
1390 def read(self, firstlinenonkeyval=False):
1390 def read(self, firstlinenonkeyval=False):
1391 """Read the contents of a simple key-value file
1391 """Read the contents of a simple key-value file
1392
1392
1393 'firstlinenonkeyval' indicates whether the first line of file should
1393 'firstlinenonkeyval' indicates whether the first line of file should
1394 be treated as a key-value pair or reuturned fully under the
1394 be treated as a key-value pair or reuturned fully under the
1395 __firstline key."""
1395 __firstline key."""
1396 lines = self.vfs.readlines(self.path)
1396 lines = self.vfs.readlines(self.path)
1397 d = {}
1397 d = {}
1398 if firstlinenonkeyval:
1398 if firstlinenonkeyval:
1399 if not lines:
1399 if not lines:
1400 e = _("empty simplekeyvalue file")
1400 e = _("empty simplekeyvalue file")
1401 raise error.CorruptedState(e)
1401 raise error.CorruptedState(e)
1402 # we don't want to include '\n' in the __firstline
1402 # we don't want to include '\n' in the __firstline
1403 d[self.firstlinekey] = lines[0][:-1]
1403 d[self.firstlinekey] = lines[0][:-1]
1404 del lines[0]
1404 del lines[0]
1405
1405
1406 try:
1406 try:
1407 # the 'if line.strip()' part prevents us from failing on empty
1407 # the 'if line.strip()' part prevents us from failing on empty
1408 # lines which only contain '\n' therefore are not skipped
1408 # lines which only contain '\n' therefore are not skipped
1409 # by 'if line'
1409 # by 'if line'
1410 updatedict = dict(line[:-1].split('=', 1) for line in lines
1410 updatedict = dict(line[:-1].split('=', 1) for line in lines
1411 if line.strip())
1411 if line.strip())
1412 if self.firstlinekey in updatedict:
1412 if self.firstlinekey in updatedict:
1413 e = _("%r can't be used as a key")
1413 e = _("%r can't be used as a key")
1414 raise error.CorruptedState(e % self.firstlinekey)
1414 raise error.CorruptedState(e % self.firstlinekey)
1415 d.update(updatedict)
1415 d.update(updatedict)
1416 except ValueError as e:
1416 except ValueError as e:
1417 raise error.CorruptedState(str(e))
1417 raise error.CorruptedState(str(e))
1418 return d
1418 return d
1419
1419
1420 def write(self, data, firstline=None):
1420 def write(self, data, firstline=None):
1421 """Write key=>value mapping to a file
1421 """Write key=>value mapping to a file
1422 data is a dict. Keys must be alphanumerical and start with a letter.
1422 data is a dict. Keys must be alphanumerical and start with a letter.
1423 Values must not contain newline characters.
1423 Values must not contain newline characters.
1424
1424
1425 If 'firstline' is not None, it is written to file before
1425 If 'firstline' is not None, it is written to file before
1426 everything else, as it is, not in a key=value form"""
1426 everything else, as it is, not in a key=value form"""
1427 lines = []
1427 lines = []
1428 if firstline is not None:
1428 if firstline is not None:
1429 lines.append('%s\n' % firstline)
1429 lines.append('%s\n' % firstline)
1430
1430
1431 for k, v in data.items():
1431 for k, v in data.items():
1432 if k == self.firstlinekey:
1432 if k == self.firstlinekey:
1433 e = "key name '%s' is reserved" % self.firstlinekey
1433 e = "key name '%s' is reserved" % self.firstlinekey
1434 raise error.ProgrammingError(e)
1434 raise error.ProgrammingError(e)
1435 if not k[0:1].isalpha():
1435 if not k[0:1].isalpha():
1436 e = "keys must start with a letter in a key-value file"
1436 e = "keys must start with a letter in a key-value file"
1437 raise error.ProgrammingError(e)
1437 raise error.ProgrammingError(e)
1438 if not k.isalnum():
1438 if not k.isalnum():
1439 e = "invalid key name in a simple key-value file"
1439 e = "invalid key name in a simple key-value file"
1440 raise error.ProgrammingError(e)
1440 raise error.ProgrammingError(e)
1441 if '\n' in v:
1441 if '\n' in v:
1442 e = "invalid value in a simple key-value file"
1442 e = "invalid value in a simple key-value file"
1443 raise error.ProgrammingError(e)
1443 raise error.ProgrammingError(e)
1444 lines.append("%s=%s\n" % (k, v))
1444 lines.append("%s=%s\n" % (k, v))
1445 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1445 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1446 fp.write(''.join(lines))
1446 fp.write(''.join(lines))
1447
1447
1448 _reportobsoletedsource = [
1448 _reportobsoletedsource = [
1449 'debugobsolete',
1449 'debugobsolete',
1450 'pull',
1450 'pull',
1451 'push',
1451 'push',
1452 'serve',
1452 'serve',
1453 'unbundle',
1453 'unbundle',
1454 ]
1454 ]
1455
1455
1456 _reportnewcssource = [
1456 _reportnewcssource = [
1457 'pull',
1457 'pull',
1458 'unbundle',
1458 'unbundle',
1459 ]
1459 ]
1460
1460
1461 def prefetchfiles(repo, revs, match):
1461 def prefetchfiles(repo, revs, match):
1462 """Invokes the registered file prefetch functions, allowing extensions to
1462 """Invokes the registered file prefetch functions, allowing extensions to
1463 ensure the corresponding files are available locally, before the command
1463 ensure the corresponding files are available locally, before the command
1464 uses them."""
1464 uses them."""
1465 if match:
1465 if match:
1466 # The command itself will complain about files that don't exist, so
1466 # The command itself will complain about files that don't exist, so
1467 # don't duplicate the message.
1467 # don't duplicate the message.
1468 match = matchmod.badmatch(match, lambda fn, msg: None)
1468 match = matchmod.badmatch(match, lambda fn, msg: None)
1469 else:
1469 else:
1470 match = matchall(repo)
1470 match = matchall(repo)
1471
1471
1472 fileprefetchhooks(repo, revs, match)
1472 fileprefetchhooks(repo, revs, match)
1473
1473
1474 # a list of (repo, revs, match) prefetch functions
1474 # a list of (repo, revs, match) prefetch functions
1475 fileprefetchhooks = util.hooks()
1475 fileprefetchhooks = util.hooks()
1476
1476
1477 # A marker that tells the evolve extension to suppress its own reporting
1477 # A marker that tells the evolve extension to suppress its own reporting
1478 _reportstroubledchangesets = True
1478 _reportstroubledchangesets = True
1479
1479
1480 def registersummarycallback(repo, otr, txnname=''):
1480 def registersummarycallback(repo, otr, txnname=''):
1481 """register a callback to issue a summary after the transaction is closed
1481 """register a callback to issue a summary after the transaction is closed
1482 """
1482 """
1483 def txmatch(sources):
1483 def txmatch(sources):
1484 return any(txnname.startswith(source) for source in sources)
1484 return any(txnname.startswith(source) for source in sources)
1485
1485
1486 categories = []
1486 categories = []
1487
1487
1488 def reportsummary(func):
1488 def reportsummary(func):
1489 """decorator for report callbacks."""
1489 """decorator for report callbacks."""
1490 # The repoview life cycle is shorter than the one of the actual
1490 # The repoview life cycle is shorter than the one of the actual
1491 # underlying repository. So the filtered object can die before the
1491 # underlying repository. So the filtered object can die before the
1492 # weakref is used leading to troubles. We keep a reference to the
1492 # weakref is used leading to troubles. We keep a reference to the
1493 # unfiltered object and restore the filtering when retrieving the
1493 # unfiltered object and restore the filtering when retrieving the
1494 # repository through the weakref.
1494 # repository through the weakref.
1495 filtername = repo.filtername
1495 filtername = repo.filtername
1496 reporef = weakref.ref(repo.unfiltered())
1496 reporef = weakref.ref(repo.unfiltered())
1497 def wrapped(tr):
1497 def wrapped(tr):
1498 repo = reporef()
1498 repo = reporef()
1499 if filtername:
1499 if filtername:
1500 repo = repo.filtered(filtername)
1500 repo = repo.filtered(filtername)
1501 func(repo, tr)
1501 func(repo, tr)
1502 newcat = '%02i-txnreport' % len(categories)
1502 newcat = '%02i-txnreport' % len(categories)
1503 otr.addpostclose(newcat, wrapped)
1503 otr.addpostclose(newcat, wrapped)
1504 categories.append(newcat)
1504 categories.append(newcat)
1505 return wrapped
1505 return wrapped
1506
1506
1507 if txmatch(_reportobsoletedsource):
1507 if txmatch(_reportobsoletedsource):
1508 @reportsummary
1508 @reportsummary
1509 def reportobsoleted(repo, tr):
1509 def reportobsoleted(repo, tr):
1510 obsoleted = obsutil.getobsoleted(repo, tr)
1510 obsoleted = obsutil.getobsoleted(repo, tr)
1511 if obsoleted:
1511 if obsoleted:
1512 repo.ui.status(_('obsoleted %i changesets\n')
1512 repo.ui.status(_('obsoleted %i changesets\n')
1513 % len(obsoleted))
1513 % len(obsoleted))
1514
1514
1515 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1515 if (obsolete.isenabled(repo, obsolete.createmarkersopt) and
1516 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1516 repo.ui.configbool('experimental', 'evolution.report-instabilities')):
1517 instabilitytypes = [
1517 instabilitytypes = [
1518 ('orphan', 'orphan'),
1518 ('orphan', 'orphan'),
1519 ('phase-divergent', 'phasedivergent'),
1519 ('phase-divergent', 'phasedivergent'),
1520 ('content-divergent', 'contentdivergent'),
1520 ('content-divergent', 'contentdivergent'),
1521 ]
1521 ]
1522
1522
1523 def getinstabilitycounts(repo):
1523 def getinstabilitycounts(repo):
1524 filtered = repo.changelog.filteredrevs
1524 filtered = repo.changelog.filteredrevs
1525 counts = {}
1525 counts = {}
1526 for instability, revset in instabilitytypes:
1526 for instability, revset in instabilitytypes:
1527 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1527 counts[instability] = len(set(obsolete.getrevs(repo, revset)) -
1528 filtered)
1528 filtered)
1529 return counts
1529 return counts
1530
1530
1531 oldinstabilitycounts = getinstabilitycounts(repo)
1531 oldinstabilitycounts = getinstabilitycounts(repo)
1532 @reportsummary
1532 @reportsummary
1533 def reportnewinstabilities(repo, tr):
1533 def reportnewinstabilities(repo, tr):
1534 newinstabilitycounts = getinstabilitycounts(repo)
1534 newinstabilitycounts = getinstabilitycounts(repo)
1535 for instability, revset in instabilitytypes:
1535 for instability, revset in instabilitytypes:
1536 delta = (newinstabilitycounts[instability] -
1536 delta = (newinstabilitycounts[instability] -
1537 oldinstabilitycounts[instability])
1537 oldinstabilitycounts[instability])
1538 msg = getinstabilitymessage(delta, instability)
1538 msg = getinstabilitymessage(delta, instability)
1539 if msg:
1539 if msg:
1540 repo.ui.warn(msg)
1540 repo.ui.warn(msg)
1541
1541
1542 if txmatch(_reportnewcssource):
1542 if txmatch(_reportnewcssource):
1543 @reportsummary
1543 @reportsummary
1544 def reportnewcs(repo, tr):
1544 def reportnewcs(repo, tr):
1545 """Report the range of new revisions pulled/unbundled."""
1545 """Report the range of new revisions pulled/unbundled."""
1546 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1546 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1547 if not newrevs:
1547 if not newrevs:
1548 return
1548 return
1549
1549
1550 # Compute the bounds of new revisions' range, excluding obsoletes.
1550 # Compute the bounds of new revisions' range, excluding obsoletes.
1551 unfi = repo.unfiltered()
1551 unfi = repo.unfiltered()
1552 revs = unfi.revs('%ld and not obsolete()', newrevs)
1552 revs = unfi.revs('%ld and not obsolete()', newrevs)
1553 if not revs:
1553 if not revs:
1554 # Got only obsoletes.
1554 # Got only obsoletes.
1555 return
1555 return
1556 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1556 minrev, maxrev = repo[revs.min()], repo[revs.max()]
1557
1557
1558 if minrev == maxrev:
1558 if minrev == maxrev:
1559 revrange = minrev
1559 revrange = minrev
1560 else:
1560 else:
1561 revrange = '%s:%s' % (minrev, maxrev)
1561 revrange = '%s:%s' % (minrev, maxrev)
1562 repo.ui.status(_('new changesets %s\n') % revrange)
1562 repo.ui.status(_('new changesets %s\n') % revrange)
1563
1563
1564 @reportsummary
1564 @reportsummary
1565 def reportphasechanges(repo, tr):
1565 def reportphasechanges(repo, tr):
1566 """Report statistics of phase changes for changesets pre-existing
1566 """Report statistics of phase changes for changesets pre-existing
1567 pull/unbundle.
1567 pull/unbundle.
1568 """
1568 """
1569 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1569 newrevs = tr.changes.get('revs', pycompat.xrange(0, 0))
1570 phasetracking = tr.changes.get('phases', {})
1570 phasetracking = tr.changes.get('phases', {})
1571 if not phasetracking:
1571 if not phasetracking:
1572 return
1572 return
1573 published = [
1573 published = [
1574 rev for rev, (old, new) in phasetracking.iteritems()
1574 rev for rev, (old, new) in phasetracking.iteritems()
1575 if new == phases.public and rev not in newrevs
1575 if new == phases.public and rev not in newrevs
1576 ]
1576 ]
1577 if not published:
1577 if not published:
1578 return
1578 return
1579 repo.ui.status(_('%d local changesets published\n')
1579 repo.ui.status(_('%d local changesets published\n')
1580 % len(published))
1580 % len(published))
1581
1581
1582 def getinstabilitymessage(delta, instability):
1582 def getinstabilitymessage(delta, instability):
1583 """function to return the message to show warning about new instabilities
1583 """function to return the message to show warning about new instabilities
1584
1584
1585 exists as a separate function so that extension can wrap to show more
1585 exists as a separate function so that extension can wrap to show more
1586 information like how to fix instabilities"""
1586 information like how to fix instabilities"""
1587 if delta > 0:
1587 if delta > 0:
1588 return _('%i new %s changesets\n') % (delta, instability)
1588 return _('%i new %s changesets\n') % (delta, instability)
1589
1589
1590 def nodesummaries(repo, nodes, maxnumnodes=4):
1590 def nodesummaries(repo, nodes, maxnumnodes=4):
1591 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1591 if len(nodes) <= maxnumnodes or repo.ui.verbose:
1592 return ' '.join(short(h) for h in nodes)
1592 return ' '.join(short(h) for h in nodes)
1593 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1593 first = ' '.join(short(h) for h in nodes[:maxnumnodes])
1594 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1594 return _("%s and %d others") % (first, len(nodes) - maxnumnodes)
1595
1595
1596 def enforcesinglehead(repo, tr, desc):
1596 def enforcesinglehead(repo, tr, desc):
1597 """check that no named branch has multiple heads"""
1597 """check that no named branch has multiple heads"""
1598 if desc in ('strip', 'repair'):
1598 if desc in ('strip', 'repair'):
1599 # skip the logic during strip
1599 # skip the logic during strip
1600 return
1600 return
1601 visible = repo.filtered('visible')
1601 visible = repo.filtered('visible')
1602 # possible improvement: we could restrict the check to affected branch
1602 # possible improvement: we could restrict the check to affected branch
1603 for name, heads in visible.branchmap().iteritems():
1603 for name, heads in visible.branchmap().iteritems():
1604 if len(heads) > 1:
1604 if len(heads) > 1:
1605 msg = _('rejecting multiple heads on branch "%s"')
1605 msg = _('rejecting multiple heads on branch "%s"')
1606 msg %= name
1606 msg %= name
1607 hint = _('%d heads: %s')
1607 hint = _('%d heads: %s')
1608 hint %= (len(heads), nodesummaries(repo, heads))
1608 hint %= (len(heads), nodesummaries(repo, heads))
1609 raise error.Abort(msg, hint=hint)
1609 raise error.Abort(msg, hint=hint)
1610
1610
1611 def wrapconvertsink(sink):
1611 def wrapconvertsink(sink):
1612 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1612 """Allow extensions to wrap the sink returned by convcmd.convertsink()
1613 before it is used, whether or not the convert extension was formally loaded.
1613 before it is used, whether or not the convert extension was formally loaded.
1614 """
1614 """
1615 return sink
1615 return sink
1616
1616
1617 def unhidehashlikerevs(repo, specs, hiddentype):
1617 def unhidehashlikerevs(repo, specs, hiddentype):
1618 """parse the user specs and unhide changesets whose hash or revision number
1618 """parse the user specs and unhide changesets whose hash or revision number
1619 is passed.
1619 is passed.
1620
1620
1621 hiddentype can be: 1) 'warn': warn while unhiding changesets
1621 hiddentype can be: 1) 'warn': warn while unhiding changesets
1622 2) 'nowarn': don't warn while unhiding changesets
1622 2) 'nowarn': don't warn while unhiding changesets
1623
1623
1624 returns a repo object with the required changesets unhidden
1624 returns a repo object with the required changesets unhidden
1625 """
1625 """
1626 if not repo.filtername or not repo.ui.configbool('experimental',
1626 if not repo.filtername or not repo.ui.configbool('experimental',
1627 'directaccess'):
1627 'directaccess'):
1628 return repo
1628 return repo
1629
1629
1630 if repo.filtername not in ('visible', 'visible-hidden'):
1630 if repo.filtername not in ('visible', 'visible-hidden'):
1631 return repo
1631 return repo
1632
1632
1633 symbols = set()
1633 symbols = set()
1634 for spec in specs:
1634 for spec in specs:
1635 try:
1635 try:
1636 tree = revsetlang.parse(spec)
1636 tree = revsetlang.parse(spec)
1637 except error.ParseError: # will be reported by scmutil.revrange()
1637 except error.ParseError: # will be reported by scmutil.revrange()
1638 continue
1638 continue
1639
1639
1640 symbols.update(revsetlang.gethashlikesymbols(tree))
1640 symbols.update(revsetlang.gethashlikesymbols(tree))
1641
1641
1642 if not symbols:
1642 if not symbols:
1643 return repo
1643 return repo
1644
1644
1645 revs = _getrevsfromsymbols(repo, symbols)
1645 revs = _getrevsfromsymbols(repo, symbols)
1646
1646
1647 if not revs:
1647 if not revs:
1648 return repo
1648 return repo
1649
1649
1650 if hiddentype == 'warn':
1650 if hiddentype == 'warn':
1651 unfi = repo.unfiltered()
1651 unfi = repo.unfiltered()
1652 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1652 revstr = ", ".join([pycompat.bytestr(unfi[l]) for l in revs])
1653 repo.ui.warn(_("warning: accessing hidden changesets for write "
1653 repo.ui.warn(_("warning: accessing hidden changesets for write "
1654 "operation: %s\n") % revstr)
1654 "operation: %s\n") % revstr)
1655
1655
1656 # we have to use new filtername to separate branch/tags cache until we can
1656 # we have to use new filtername to separate branch/tags cache until we can
1657 # disbale these cache when revisions are dynamically pinned.
1657 # disbale these cache when revisions are dynamically pinned.
1658 return repo.filtered('visible-hidden', revs)
1658 return repo.filtered('visible-hidden', revs)
1659
1659
1660 def _getrevsfromsymbols(repo, symbols):
1660 def _getrevsfromsymbols(repo, symbols):
1661 """parse the list of symbols and returns a set of revision numbers of hidden
1661 """parse the list of symbols and returns a set of revision numbers of hidden
1662 changesets present in symbols"""
1662 changesets present in symbols"""
1663 revs = set()
1663 revs = set()
1664 unfi = repo.unfiltered()
1664 unfi = repo.unfiltered()
1665 unficl = unfi.changelog
1665 unficl = unfi.changelog
1666 cl = repo.changelog
1666 cl = repo.changelog
1667 tiprev = len(unficl)
1667 tiprev = len(unficl)
1668 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1668 allowrevnums = repo.ui.configbool('experimental', 'directaccess.revnums')
1669 for s in symbols:
1669 for s in symbols:
1670 try:
1670 try:
1671 n = int(s)
1671 n = int(s)
1672 if n <= tiprev:
1672 if n <= tiprev:
1673 if not allowrevnums:
1673 if not allowrevnums:
1674 continue
1674 continue
1675 else:
1675 else:
1676 if n not in cl:
1676 if n not in cl:
1677 revs.add(n)
1677 revs.add(n)
1678 continue
1678 continue
1679 except ValueError:
1679 except ValueError:
1680 pass
1680 pass
1681
1681
1682 try:
1682 try:
1683 s = resolvehexnodeidprefix(unfi, s)
1683 s = resolvehexnodeidprefix(unfi, s)
1684 except (error.LookupError, error.WdirUnsupported):
1684 except (error.LookupError, error.WdirUnsupported):
1685 s = None
1685 s = None
1686
1686
1687 if s is not None:
1687 if s is not None:
1688 rev = unficl.rev(s)
1688 rev = unficl.rev(s)
1689 if rev not in cl:
1689 if rev not in cl:
1690 revs.add(rev)
1690 revs.add(rev)
1691
1691
1692 return revs
1692 return revs
1693
1693
1694 def bookmarkrevs(repo, mark):
1694 def bookmarkrevs(repo, mark):
1695 """
1695 """
1696 Select revisions reachable by a given bookmark
1696 Select revisions reachable by a given bookmark
1697 """
1697 """
1698 return repo.revs("ancestors(bookmark(%s)) - "
1698 return repo.revs("ancestors(bookmark(%s)) - "
1699 "ancestors(head() and not bookmark(%s)) - "
1699 "ancestors(head() and not bookmark(%s)) - "
1700 "ancestors(bookmark() and not bookmark(%s))",
1700 "ancestors(bookmark() and not bookmark(%s))",
1701 mark, mark, mark)
1701 mark, mark, mark)
General Comments 0
You need to be logged in to leave comments. Login now