##// END OF EJS Templates
transaction-summary: display the summary for all transactions...
Boris Feld -
r33541:b47fef6d default
parent child Browse files
Show More
@@ -1,1853 +1,1851 b''
1 # bundle2.py - generic container format to transmit arbitrary data.
1 # bundle2.py - generic container format to transmit arbitrary data.
2 #
2 #
3 # Copyright 2013 Facebook, Inc.
3 # Copyright 2013 Facebook, Inc.
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 """Handling of the new bundle2 format
7 """Handling of the new bundle2 format
8
8
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
9 The goal of bundle2 is to act as an atomically packet to transmit a set of
10 payloads in an application agnostic way. It consist in a sequence of "parts"
10 payloads in an application agnostic way. It consist in a sequence of "parts"
11 that will be handed to and processed by the application layer.
11 that will be handed to and processed by the application layer.
12
12
13
13
14 General format architecture
14 General format architecture
15 ===========================
15 ===========================
16
16
17 The format is architectured as follow
17 The format is architectured as follow
18
18
19 - magic string
19 - magic string
20 - stream level parameters
20 - stream level parameters
21 - payload parts (any number)
21 - payload parts (any number)
22 - end of stream marker.
22 - end of stream marker.
23
23
24 the Binary format
24 the Binary format
25 ============================
25 ============================
26
26
27 All numbers are unsigned and big-endian.
27 All numbers are unsigned and big-endian.
28
28
29 stream level parameters
29 stream level parameters
30 ------------------------
30 ------------------------
31
31
32 Binary format is as follow
32 Binary format is as follow
33
33
34 :params size: int32
34 :params size: int32
35
35
36 The total number of Bytes used by the parameters
36 The total number of Bytes used by the parameters
37
37
38 :params value: arbitrary number of Bytes
38 :params value: arbitrary number of Bytes
39
39
40 A blob of `params size` containing the serialized version of all stream level
40 A blob of `params size` containing the serialized version of all stream level
41 parameters.
41 parameters.
42
42
43 The blob contains a space separated list of parameters. Parameters with value
43 The blob contains a space separated list of parameters. Parameters with value
44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
44 are stored in the form `<name>=<value>`. Both name and value are urlquoted.
45
45
46 Empty name are obviously forbidden.
46 Empty name are obviously forbidden.
47
47
48 Name MUST start with a letter. If this first letter is lower case, the
48 Name MUST start with a letter. If this first letter is lower case, the
49 parameter is advisory and can be safely ignored. However when the first
49 parameter is advisory and can be safely ignored. However when the first
50 letter is capital, the parameter is mandatory and the bundling process MUST
50 letter is capital, the parameter is mandatory and the bundling process MUST
51 stop if he is not able to proceed it.
51 stop if he is not able to proceed it.
52
52
53 Stream parameters use a simple textual format for two main reasons:
53 Stream parameters use a simple textual format for two main reasons:
54
54
55 - Stream level parameters should remain simple and we want to discourage any
55 - Stream level parameters should remain simple and we want to discourage any
56 crazy usage.
56 crazy usage.
57 - Textual data allow easy human inspection of a bundle2 header in case of
57 - Textual data allow easy human inspection of a bundle2 header in case of
58 troubles.
58 troubles.
59
59
60 Any Applicative level options MUST go into a bundle2 part instead.
60 Any Applicative level options MUST go into a bundle2 part instead.
61
61
62 Payload part
62 Payload part
63 ------------------------
63 ------------------------
64
64
65 Binary format is as follow
65 Binary format is as follow
66
66
67 :header size: int32
67 :header size: int32
68
68
69 The total number of Bytes used by the part header. When the header is empty
69 The total number of Bytes used by the part header. When the header is empty
70 (size = 0) this is interpreted as the end of stream marker.
70 (size = 0) this is interpreted as the end of stream marker.
71
71
72 :header:
72 :header:
73
73
74 The header defines how to interpret the part. It contains two piece of
74 The header defines how to interpret the part. It contains two piece of
75 data: the part type, and the part parameters.
75 data: the part type, and the part parameters.
76
76
77 The part type is used to route an application level handler, that can
77 The part type is used to route an application level handler, that can
78 interpret payload.
78 interpret payload.
79
79
80 Part parameters are passed to the application level handler. They are
80 Part parameters are passed to the application level handler. They are
81 meant to convey information that will help the application level object to
81 meant to convey information that will help the application level object to
82 interpret the part payload.
82 interpret the part payload.
83
83
84 The binary format of the header is has follow
84 The binary format of the header is has follow
85
85
86 :typesize: (one byte)
86 :typesize: (one byte)
87
87
88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
88 :parttype: alphanumerical part name (restricted to [a-zA-Z0-9_:-]*)
89
89
90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
90 :partid: A 32bits integer (unique in the bundle) that can be used to refer
91 to this part.
91 to this part.
92
92
93 :parameters:
93 :parameters:
94
94
95 Part's parameter may have arbitrary content, the binary structure is::
95 Part's parameter may have arbitrary content, the binary structure is::
96
96
97 <mandatory-count><advisory-count><param-sizes><param-data>
97 <mandatory-count><advisory-count><param-sizes><param-data>
98
98
99 :mandatory-count: 1 byte, number of mandatory parameters
99 :mandatory-count: 1 byte, number of mandatory parameters
100
100
101 :advisory-count: 1 byte, number of advisory parameters
101 :advisory-count: 1 byte, number of advisory parameters
102
102
103 :param-sizes:
103 :param-sizes:
104
104
105 N couple of bytes, where N is the total number of parameters. Each
105 N couple of bytes, where N is the total number of parameters. Each
106 couple contains (<size-of-key>, <size-of-value) for one parameter.
106 couple contains (<size-of-key>, <size-of-value) for one parameter.
107
107
108 :param-data:
108 :param-data:
109
109
110 A blob of bytes from which each parameter key and value can be
110 A blob of bytes from which each parameter key and value can be
111 retrieved using the list of size couples stored in the previous
111 retrieved using the list of size couples stored in the previous
112 field.
112 field.
113
113
114 Mandatory parameters comes first, then the advisory ones.
114 Mandatory parameters comes first, then the advisory ones.
115
115
116 Each parameter's key MUST be unique within the part.
116 Each parameter's key MUST be unique within the part.
117
117
118 :payload:
118 :payload:
119
119
120 payload is a series of `<chunksize><chunkdata>`.
120 payload is a series of `<chunksize><chunkdata>`.
121
121
122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
122 `chunksize` is an int32, `chunkdata` are plain bytes (as much as
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
123 `chunksize` says)` The payload part is concluded by a zero size chunk.
124
124
125 The current implementation always produces either zero or one chunk.
125 The current implementation always produces either zero or one chunk.
126 This is an implementation limitation that will ultimately be lifted.
126 This is an implementation limitation that will ultimately be lifted.
127
127
128 `chunksize` can be negative to trigger special case processing. No such
128 `chunksize` can be negative to trigger special case processing. No such
129 processing is in place yet.
129 processing is in place yet.
130
130
131 Bundle processing
131 Bundle processing
132 ============================
132 ============================
133
133
134 Each part is processed in order using a "part handler". Handler are registered
134 Each part is processed in order using a "part handler". Handler are registered
135 for a certain part type.
135 for a certain part type.
136
136
137 The matching of a part to its handler is case insensitive. The case of the
137 The matching of a part to its handler is case insensitive. The case of the
138 part type is used to know if a part is mandatory or advisory. If the Part type
138 part type is used to know if a part is mandatory or advisory. If the Part type
139 contains any uppercase char it is considered mandatory. When no handler is
139 contains any uppercase char it is considered mandatory. When no handler is
140 known for a Mandatory part, the process is aborted and an exception is raised.
140 known for a Mandatory part, the process is aborted and an exception is raised.
141 If the part is advisory and no handler is known, the part is ignored. When the
141 If the part is advisory and no handler is known, the part is ignored. When the
142 process is aborted, the full bundle is still read from the stream to keep the
142 process is aborted, the full bundle is still read from the stream to keep the
143 channel usable. But none of the part read from an abort are processed. In the
143 channel usable. But none of the part read from an abort are processed. In the
144 future, dropping the stream may become an option for channel we do not care to
144 future, dropping the stream may become an option for channel we do not care to
145 preserve.
145 preserve.
146 """
146 """
147
147
148 from __future__ import absolute_import
148 from __future__ import absolute_import
149
149
150 import errno
150 import errno
151 import re
151 import re
152 import string
152 import string
153 import struct
153 import struct
154 import sys
154 import sys
155
155
156 from .i18n import _
156 from .i18n import _
157 from . import (
157 from . import (
158 changegroup,
158 changegroup,
159 error,
159 error,
160 obsolete,
160 obsolete,
161 phases,
161 phases,
162 pushkey,
162 pushkey,
163 pycompat,
163 pycompat,
164 scmutil,
165 tags,
164 tags,
166 url,
165 url,
167 util,
166 util,
168 )
167 )
169
168
170 urlerr = util.urlerr
169 urlerr = util.urlerr
171 urlreq = util.urlreq
170 urlreq = util.urlreq
172
171
173 _pack = struct.pack
172 _pack = struct.pack
174 _unpack = struct.unpack
173 _unpack = struct.unpack
175
174
176 _fstreamparamsize = '>i'
175 _fstreamparamsize = '>i'
177 _fpartheadersize = '>i'
176 _fpartheadersize = '>i'
178 _fparttypesize = '>B'
177 _fparttypesize = '>B'
179 _fpartid = '>I'
178 _fpartid = '>I'
180 _fpayloadsize = '>i'
179 _fpayloadsize = '>i'
181 _fpartparamcount = '>BB'
180 _fpartparamcount = '>BB'
182
181
183 _fphasesentry = '>i20s'
182 _fphasesentry = '>i20s'
184
183
185 preferedchunksize = 4096
184 preferedchunksize = 4096
186
185
187 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
186 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
188
187
189 def outdebug(ui, message):
188 def outdebug(ui, message):
190 """debug regarding output stream (bundling)"""
189 """debug regarding output stream (bundling)"""
191 if ui.configbool('devel', 'bundle2.debug'):
190 if ui.configbool('devel', 'bundle2.debug'):
192 ui.debug('bundle2-output: %s\n' % message)
191 ui.debug('bundle2-output: %s\n' % message)
193
192
194 def indebug(ui, message):
193 def indebug(ui, message):
195 """debug on input stream (unbundling)"""
194 """debug on input stream (unbundling)"""
196 if ui.configbool('devel', 'bundle2.debug'):
195 if ui.configbool('devel', 'bundle2.debug'):
197 ui.debug('bundle2-input: %s\n' % message)
196 ui.debug('bundle2-input: %s\n' % message)
198
197
199 def validateparttype(parttype):
198 def validateparttype(parttype):
200 """raise ValueError if a parttype contains invalid character"""
199 """raise ValueError if a parttype contains invalid character"""
201 if _parttypeforbidden.search(parttype):
200 if _parttypeforbidden.search(parttype):
202 raise ValueError(parttype)
201 raise ValueError(parttype)
203
202
204 def _makefpartparamsizes(nbparams):
203 def _makefpartparamsizes(nbparams):
205 """return a struct format to read part parameter sizes
204 """return a struct format to read part parameter sizes
206
205
207 The number parameters is variable so we need to build that format
206 The number parameters is variable so we need to build that format
208 dynamically.
207 dynamically.
209 """
208 """
210 return '>'+('BB'*nbparams)
209 return '>'+('BB'*nbparams)
211
210
212 parthandlermapping = {}
211 parthandlermapping = {}
213
212
214 def parthandler(parttype, params=()):
213 def parthandler(parttype, params=()):
215 """decorator that register a function as a bundle2 part handler
214 """decorator that register a function as a bundle2 part handler
216
215
217 eg::
216 eg::
218
217
219 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
218 @parthandler('myparttype', ('mandatory', 'param', 'handled'))
220 def myparttypehandler(...):
219 def myparttypehandler(...):
221 '''process a part of type "my part".'''
220 '''process a part of type "my part".'''
222 ...
221 ...
223 """
222 """
224 validateparttype(parttype)
223 validateparttype(parttype)
225 def _decorator(func):
224 def _decorator(func):
226 lparttype = parttype.lower() # enforce lower case matching.
225 lparttype = parttype.lower() # enforce lower case matching.
227 assert lparttype not in parthandlermapping
226 assert lparttype not in parthandlermapping
228 parthandlermapping[lparttype] = func
227 parthandlermapping[lparttype] = func
229 func.params = frozenset(params)
228 func.params = frozenset(params)
230 return func
229 return func
231 return _decorator
230 return _decorator
232
231
233 class unbundlerecords(object):
232 class unbundlerecords(object):
234 """keep record of what happens during and unbundle
233 """keep record of what happens during and unbundle
235
234
236 New records are added using `records.add('cat', obj)`. Where 'cat' is a
235 New records are added using `records.add('cat', obj)`. Where 'cat' is a
237 category of record and obj is an arbitrary object.
236 category of record and obj is an arbitrary object.
238
237
239 `records['cat']` will return all entries of this category 'cat'.
238 `records['cat']` will return all entries of this category 'cat'.
240
239
241 Iterating on the object itself will yield `('category', obj)` tuples
240 Iterating on the object itself will yield `('category', obj)` tuples
242 for all entries.
241 for all entries.
243
242
244 All iterations happens in chronological order.
243 All iterations happens in chronological order.
245 """
244 """
246
245
247 def __init__(self):
246 def __init__(self):
248 self._categories = {}
247 self._categories = {}
249 self._sequences = []
248 self._sequences = []
250 self._replies = {}
249 self._replies = {}
251
250
252 def add(self, category, entry, inreplyto=None):
251 def add(self, category, entry, inreplyto=None):
253 """add a new record of a given category.
252 """add a new record of a given category.
254
253
255 The entry can then be retrieved in the list returned by
254 The entry can then be retrieved in the list returned by
256 self['category']."""
255 self['category']."""
257 self._categories.setdefault(category, []).append(entry)
256 self._categories.setdefault(category, []).append(entry)
258 self._sequences.append((category, entry))
257 self._sequences.append((category, entry))
259 if inreplyto is not None:
258 if inreplyto is not None:
260 self.getreplies(inreplyto).add(category, entry)
259 self.getreplies(inreplyto).add(category, entry)
261
260
262 def getreplies(self, partid):
261 def getreplies(self, partid):
263 """get the records that are replies to a specific part"""
262 """get the records that are replies to a specific part"""
264 return self._replies.setdefault(partid, unbundlerecords())
263 return self._replies.setdefault(partid, unbundlerecords())
265
264
266 def __getitem__(self, cat):
265 def __getitem__(self, cat):
267 return tuple(self._categories.get(cat, ()))
266 return tuple(self._categories.get(cat, ()))
268
267
269 def __iter__(self):
268 def __iter__(self):
270 return iter(self._sequences)
269 return iter(self._sequences)
271
270
272 def __len__(self):
271 def __len__(self):
273 return len(self._sequences)
272 return len(self._sequences)
274
273
275 def __nonzero__(self):
274 def __nonzero__(self):
276 return bool(self._sequences)
275 return bool(self._sequences)
277
276
278 __bool__ = __nonzero__
277 __bool__ = __nonzero__
279
278
280 class bundleoperation(object):
279 class bundleoperation(object):
281 """an object that represents a single bundling process
280 """an object that represents a single bundling process
282
281
283 Its purpose is to carry unbundle-related objects and states.
282 Its purpose is to carry unbundle-related objects and states.
284
283
285 A new object should be created at the beginning of each bundle processing.
284 A new object should be created at the beginning of each bundle processing.
286 The object is to be returned by the processing function.
285 The object is to be returned by the processing function.
287
286
288 The object has very little content now it will ultimately contain:
287 The object has very little content now it will ultimately contain:
289 * an access to the repo the bundle is applied to,
288 * an access to the repo the bundle is applied to,
290 * a ui object,
289 * a ui object,
291 * a way to retrieve a transaction to add changes to the repo,
290 * a way to retrieve a transaction to add changes to the repo,
292 * a way to record the result of processing each part,
291 * a way to record the result of processing each part,
293 * a way to construct a bundle response when applicable.
292 * a way to construct a bundle response when applicable.
294 """
293 """
295
294
296 def __init__(self, repo, transactiongetter, captureoutput=True):
295 def __init__(self, repo, transactiongetter, captureoutput=True):
297 self.repo = repo
296 self.repo = repo
298 self.ui = repo.ui
297 self.ui = repo.ui
299 self.records = unbundlerecords()
298 self.records = unbundlerecords()
300 self.gettransaction = transactiongetter
299 self.gettransaction = transactiongetter
301 self.reply = None
300 self.reply = None
302 self.captureoutput = captureoutput
301 self.captureoutput = captureoutput
303
302
304 class TransactionUnavailable(RuntimeError):
303 class TransactionUnavailable(RuntimeError):
305 pass
304 pass
306
305
307 def _notransaction():
306 def _notransaction():
308 """default method to get a transaction while processing a bundle
307 """default method to get a transaction while processing a bundle
309
308
310 Raise an exception to highlight the fact that no transaction was expected
309 Raise an exception to highlight the fact that no transaction was expected
311 to be created"""
310 to be created"""
312 raise TransactionUnavailable()
311 raise TransactionUnavailable()
313
312
314 def applybundle(repo, unbundler, tr, source=None, url=None, **kwargs):
313 def applybundle(repo, unbundler, tr, source=None, url=None, **kwargs):
315 # transform me into unbundler.apply() as soon as the freeze is lifted
314 # transform me into unbundler.apply() as soon as the freeze is lifted
316 if isinstance(unbundler, unbundle20):
315 if isinstance(unbundler, unbundle20):
317 tr.hookargs['bundle2'] = '1'
316 tr.hookargs['bundle2'] = '1'
318 if source is not None and 'source' not in tr.hookargs:
317 if source is not None and 'source' not in tr.hookargs:
319 tr.hookargs['source'] = source
318 tr.hookargs['source'] = source
320 if url is not None and 'url' not in tr.hookargs:
319 if url is not None and 'url' not in tr.hookargs:
321 tr.hookargs['url'] = url
320 tr.hookargs['url'] = url
322 return processbundle(repo, unbundler, lambda: tr)
321 return processbundle(repo, unbundler, lambda: tr)
323 else:
322 else:
324 # the transactiongetter won't be used, but we might as well set it
323 # the transactiongetter won't be used, but we might as well set it
325 op = bundleoperation(repo, lambda: tr)
324 op = bundleoperation(repo, lambda: tr)
326 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
325 _processchangegroup(op, unbundler, tr, source, url, **kwargs)
327 return op
326 return op
328
327
329 def processbundle(repo, unbundler, transactiongetter=None, op=None):
328 def processbundle(repo, unbundler, transactiongetter=None, op=None):
330 """This function process a bundle, apply effect to/from a repo
329 """This function process a bundle, apply effect to/from a repo
331
330
332 It iterates over each part then searches for and uses the proper handling
331 It iterates over each part then searches for and uses the proper handling
333 code to process the part. Parts are processed in order.
332 code to process the part. Parts are processed in order.
334
333
335 Unknown Mandatory part will abort the process.
334 Unknown Mandatory part will abort the process.
336
335
337 It is temporarily possible to provide a prebuilt bundleoperation to the
336 It is temporarily possible to provide a prebuilt bundleoperation to the
338 function. This is used to ensure output is properly propagated in case of
337 function. This is used to ensure output is properly propagated in case of
339 an error during the unbundling. This output capturing part will likely be
338 an error during the unbundling. This output capturing part will likely be
340 reworked and this ability will probably go away in the process.
339 reworked and this ability will probably go away in the process.
341 """
340 """
342 if op is None:
341 if op is None:
343 if transactiongetter is None:
342 if transactiongetter is None:
344 transactiongetter = _notransaction
343 transactiongetter = _notransaction
345 op = bundleoperation(repo, transactiongetter)
344 op = bundleoperation(repo, transactiongetter)
346 # todo:
345 # todo:
347 # - replace this is a init function soon.
346 # - replace this is a init function soon.
348 # - exception catching
347 # - exception catching
349 unbundler.params
348 unbundler.params
350 if repo.ui.debugflag:
349 if repo.ui.debugflag:
351 msg = ['bundle2-input-bundle:']
350 msg = ['bundle2-input-bundle:']
352 if unbundler.params:
351 if unbundler.params:
353 msg.append(' %i params' % len(unbundler.params))
352 msg.append(' %i params' % len(unbundler.params))
354 if op.gettransaction is None or op.gettransaction is _notransaction:
353 if op.gettransaction is None or op.gettransaction is _notransaction:
355 msg.append(' no-transaction')
354 msg.append(' no-transaction')
356 else:
355 else:
357 msg.append(' with-transaction')
356 msg.append(' with-transaction')
358 msg.append('\n')
357 msg.append('\n')
359 repo.ui.debug(''.join(msg))
358 repo.ui.debug(''.join(msg))
360 iterparts = enumerate(unbundler.iterparts())
359 iterparts = enumerate(unbundler.iterparts())
361 part = None
360 part = None
362 nbpart = 0
361 nbpart = 0
363 try:
362 try:
364 for nbpart, part in iterparts:
363 for nbpart, part in iterparts:
365 _processpart(op, part)
364 _processpart(op, part)
366 except Exception as exc:
365 except Exception as exc:
367 # Any exceptions seeking to the end of the bundle at this point are
366 # Any exceptions seeking to the end of the bundle at this point are
368 # almost certainly related to the underlying stream being bad.
367 # almost certainly related to the underlying stream being bad.
369 # And, chances are that the exception we're handling is related to
368 # And, chances are that the exception we're handling is related to
370 # getting in that bad state. So, we swallow the seeking error and
369 # getting in that bad state. So, we swallow the seeking error and
371 # re-raise the original error.
370 # re-raise the original error.
372 seekerror = False
371 seekerror = False
373 try:
372 try:
374 for nbpart, part in iterparts:
373 for nbpart, part in iterparts:
375 # consume the bundle content
374 # consume the bundle content
376 part.seek(0, 2)
375 part.seek(0, 2)
377 except Exception:
376 except Exception:
378 seekerror = True
377 seekerror = True
379
378
380 # Small hack to let caller code distinguish exceptions from bundle2
379 # Small hack to let caller code distinguish exceptions from bundle2
381 # processing from processing the old format. This is mostly
380 # processing from processing the old format. This is mostly
382 # needed to handle different return codes to unbundle according to the
381 # needed to handle different return codes to unbundle according to the
383 # type of bundle. We should probably clean up or drop this return code
382 # type of bundle. We should probably clean up or drop this return code
384 # craziness in a future version.
383 # craziness in a future version.
385 exc.duringunbundle2 = True
384 exc.duringunbundle2 = True
386 salvaged = []
385 salvaged = []
387 replycaps = None
386 replycaps = None
388 if op.reply is not None:
387 if op.reply is not None:
389 salvaged = op.reply.salvageoutput()
388 salvaged = op.reply.salvageoutput()
390 replycaps = op.reply.capabilities
389 replycaps = op.reply.capabilities
391 exc._replycaps = replycaps
390 exc._replycaps = replycaps
392 exc._bundle2salvagedoutput = salvaged
391 exc._bundle2salvagedoutput = salvaged
393
392
394 # Re-raising from a variable loses the original stack. So only use
393 # Re-raising from a variable loses the original stack. So only use
395 # that form if we need to.
394 # that form if we need to.
396 if seekerror:
395 if seekerror:
397 raise exc
396 raise exc
398 else:
397 else:
399 raise
398 raise
400 finally:
399 finally:
401 repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
400 repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
402
401
403 return op
402 return op
404
403
405 def _processchangegroup(op, cg, tr, source, url, **kwargs):
404 def _processchangegroup(op, cg, tr, source, url, **kwargs):
406 ret = cg.apply(op.repo, tr, source, url, **kwargs)
405 ret = cg.apply(op.repo, tr, source, url, **kwargs)
407 op.records.add('changegroup', {
406 op.records.add('changegroup', {
408 'return': ret,
407 'return': ret,
409 })
408 })
410 return ret
409 return ret
411
410
412 def _processpart(op, part):
411 def _processpart(op, part):
413 """process a single part from a bundle
412 """process a single part from a bundle
414
413
415 The part is guaranteed to have been fully consumed when the function exits
414 The part is guaranteed to have been fully consumed when the function exits
416 (even if an exception is raised)."""
415 (even if an exception is raised)."""
417 status = 'unknown' # used by debug output
416 status = 'unknown' # used by debug output
418 hardabort = False
417 hardabort = False
419 try:
418 try:
420 try:
419 try:
421 handler = parthandlermapping.get(part.type)
420 handler = parthandlermapping.get(part.type)
422 if handler is None:
421 if handler is None:
423 status = 'unsupported-type'
422 status = 'unsupported-type'
424 raise error.BundleUnknownFeatureError(parttype=part.type)
423 raise error.BundleUnknownFeatureError(parttype=part.type)
425 indebug(op.ui, 'found a handler for part %r' % part.type)
424 indebug(op.ui, 'found a handler for part %r' % part.type)
426 unknownparams = part.mandatorykeys - handler.params
425 unknownparams = part.mandatorykeys - handler.params
427 if unknownparams:
426 if unknownparams:
428 unknownparams = list(unknownparams)
427 unknownparams = list(unknownparams)
429 unknownparams.sort()
428 unknownparams.sort()
430 status = 'unsupported-params (%s)' % unknownparams
429 status = 'unsupported-params (%s)' % unknownparams
431 raise error.BundleUnknownFeatureError(parttype=part.type,
430 raise error.BundleUnknownFeatureError(parttype=part.type,
432 params=unknownparams)
431 params=unknownparams)
433 status = 'supported'
432 status = 'supported'
434 except error.BundleUnknownFeatureError as exc:
433 except error.BundleUnknownFeatureError as exc:
435 if part.mandatory: # mandatory parts
434 if part.mandatory: # mandatory parts
436 raise
435 raise
437 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
436 indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
438 return # skip to part processing
437 return # skip to part processing
439 finally:
438 finally:
440 if op.ui.debugflag:
439 if op.ui.debugflag:
441 msg = ['bundle2-input-part: "%s"' % part.type]
440 msg = ['bundle2-input-part: "%s"' % part.type]
442 if not part.mandatory:
441 if not part.mandatory:
443 msg.append(' (advisory)')
442 msg.append(' (advisory)')
444 nbmp = len(part.mandatorykeys)
443 nbmp = len(part.mandatorykeys)
445 nbap = len(part.params) - nbmp
444 nbap = len(part.params) - nbmp
446 if nbmp or nbap:
445 if nbmp or nbap:
447 msg.append(' (params:')
446 msg.append(' (params:')
448 if nbmp:
447 if nbmp:
449 msg.append(' %i mandatory' % nbmp)
448 msg.append(' %i mandatory' % nbmp)
450 if nbap:
449 if nbap:
451 msg.append(' %i advisory' % nbmp)
450 msg.append(' %i advisory' % nbmp)
452 msg.append(')')
451 msg.append(')')
453 msg.append(' %s\n' % status)
452 msg.append(' %s\n' % status)
454 op.ui.debug(''.join(msg))
453 op.ui.debug(''.join(msg))
455
454
456 # handler is called outside the above try block so that we don't
455 # handler is called outside the above try block so that we don't
457 # risk catching KeyErrors from anything other than the
456 # risk catching KeyErrors from anything other than the
458 # parthandlermapping lookup (any KeyError raised by handler()
457 # parthandlermapping lookup (any KeyError raised by handler()
459 # itself represents a defect of a different variety).
458 # itself represents a defect of a different variety).
460 output = None
459 output = None
461 if op.captureoutput and op.reply is not None:
460 if op.captureoutput and op.reply is not None:
462 op.ui.pushbuffer(error=True, subproc=True)
461 op.ui.pushbuffer(error=True, subproc=True)
463 output = ''
462 output = ''
464 try:
463 try:
465 handler(op, part)
464 handler(op, part)
466 finally:
465 finally:
467 if output is not None:
466 if output is not None:
468 output = op.ui.popbuffer()
467 output = op.ui.popbuffer()
469 if output:
468 if output:
470 outpart = op.reply.newpart('output', data=output,
469 outpart = op.reply.newpart('output', data=output,
471 mandatory=False)
470 mandatory=False)
472 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
471 outpart.addparam('in-reply-to', str(part.id), mandatory=False)
473 # If exiting or interrupted, do not attempt to seek the stream in the
472 # If exiting or interrupted, do not attempt to seek the stream in the
474 # finally block below. This makes abort faster.
473 # finally block below. This makes abort faster.
475 except (SystemExit, KeyboardInterrupt):
474 except (SystemExit, KeyboardInterrupt):
476 hardabort = True
475 hardabort = True
477 raise
476 raise
478 finally:
477 finally:
479 # consume the part content to not corrupt the stream.
478 # consume the part content to not corrupt the stream.
480 if not hardabort:
479 if not hardabort:
481 part.seek(0, 2)
480 part.seek(0, 2)
482
481
483
482
484 def decodecaps(blob):
483 def decodecaps(blob):
485 """decode a bundle2 caps bytes blob into a dictionary
484 """decode a bundle2 caps bytes blob into a dictionary
486
485
487 The blob is a list of capabilities (one per line)
486 The blob is a list of capabilities (one per line)
488 Capabilities may have values using a line of the form::
487 Capabilities may have values using a line of the form::
489
488
490 capability=value1,value2,value3
489 capability=value1,value2,value3
491
490
492 The values are always a list."""
491 The values are always a list."""
493 caps = {}
492 caps = {}
494 for line in blob.splitlines():
493 for line in blob.splitlines():
495 if not line:
494 if not line:
496 continue
495 continue
497 if '=' not in line:
496 if '=' not in line:
498 key, vals = line, ()
497 key, vals = line, ()
499 else:
498 else:
500 key, vals = line.split('=', 1)
499 key, vals = line.split('=', 1)
501 vals = vals.split(',')
500 vals = vals.split(',')
502 key = urlreq.unquote(key)
501 key = urlreq.unquote(key)
503 vals = [urlreq.unquote(v) for v in vals]
502 vals = [urlreq.unquote(v) for v in vals]
504 caps[key] = vals
503 caps[key] = vals
505 return caps
504 return caps
506
505
507 def encodecaps(caps):
506 def encodecaps(caps):
508 """encode a bundle2 caps dictionary into a bytes blob"""
507 """encode a bundle2 caps dictionary into a bytes blob"""
509 chunks = []
508 chunks = []
510 for ca in sorted(caps):
509 for ca in sorted(caps):
511 vals = caps[ca]
510 vals = caps[ca]
512 ca = urlreq.quote(ca)
511 ca = urlreq.quote(ca)
513 vals = [urlreq.quote(v) for v in vals]
512 vals = [urlreq.quote(v) for v in vals]
514 if vals:
513 if vals:
515 ca = "%s=%s" % (ca, ','.join(vals))
514 ca = "%s=%s" % (ca, ','.join(vals))
516 chunks.append(ca)
515 chunks.append(ca)
517 return '\n'.join(chunks)
516 return '\n'.join(chunks)
518
517
519 bundletypes = {
518 bundletypes = {
520 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
519 "": ("", 'UN'), # only when using unbundle on ssh and old http servers
521 # since the unification ssh accepts a header but there
520 # since the unification ssh accepts a header but there
522 # is no capability signaling it.
521 # is no capability signaling it.
523 "HG20": (), # special-cased below
522 "HG20": (), # special-cased below
524 "HG10UN": ("HG10UN", 'UN'),
523 "HG10UN": ("HG10UN", 'UN'),
525 "HG10BZ": ("HG10", 'BZ'),
524 "HG10BZ": ("HG10", 'BZ'),
526 "HG10GZ": ("HG10GZ", 'GZ'),
525 "HG10GZ": ("HG10GZ", 'GZ'),
527 }
526 }
528
527
529 # hgweb uses this list to communicate its preferred type
528 # hgweb uses this list to communicate its preferred type
530 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
529 bundlepriority = ['HG10GZ', 'HG10BZ', 'HG10UN']
531
530
532 class bundle20(object):
531 class bundle20(object):
533 """represent an outgoing bundle2 container
532 """represent an outgoing bundle2 container
534
533
535 Use the `addparam` method to add stream level parameter. and `newpart` to
534 Use the `addparam` method to add stream level parameter. and `newpart` to
536 populate it. Then call `getchunks` to retrieve all the binary chunks of
535 populate it. Then call `getchunks` to retrieve all the binary chunks of
537 data that compose the bundle2 container."""
536 data that compose the bundle2 container."""
538
537
539 _magicstring = 'HG20'
538 _magicstring = 'HG20'
540
539
541 def __init__(self, ui, capabilities=()):
540 def __init__(self, ui, capabilities=()):
542 self.ui = ui
541 self.ui = ui
543 self._params = []
542 self._params = []
544 self._parts = []
543 self._parts = []
545 self.capabilities = dict(capabilities)
544 self.capabilities = dict(capabilities)
546 self._compengine = util.compengines.forbundletype('UN')
545 self._compengine = util.compengines.forbundletype('UN')
547 self._compopts = None
546 self._compopts = None
548
547
549 def setcompression(self, alg, compopts=None):
548 def setcompression(self, alg, compopts=None):
550 """setup core part compression to <alg>"""
549 """setup core part compression to <alg>"""
551 if alg in (None, 'UN'):
550 if alg in (None, 'UN'):
552 return
551 return
553 assert not any(n.lower() == 'compression' for n, v in self._params)
552 assert not any(n.lower() == 'compression' for n, v in self._params)
554 self.addparam('Compression', alg)
553 self.addparam('Compression', alg)
555 self._compengine = util.compengines.forbundletype(alg)
554 self._compengine = util.compengines.forbundletype(alg)
556 self._compopts = compopts
555 self._compopts = compopts
557
556
558 @property
557 @property
559 def nbparts(self):
558 def nbparts(self):
560 """total number of parts added to the bundler"""
559 """total number of parts added to the bundler"""
561 return len(self._parts)
560 return len(self._parts)
562
561
563 # methods used to defines the bundle2 content
562 # methods used to defines the bundle2 content
564 def addparam(self, name, value=None):
563 def addparam(self, name, value=None):
565 """add a stream level parameter"""
564 """add a stream level parameter"""
566 if not name:
565 if not name:
567 raise ValueError('empty parameter name')
566 raise ValueError('empty parameter name')
568 if name[0] not in string.letters:
567 if name[0] not in string.letters:
569 raise ValueError('non letter first character: %r' % name)
568 raise ValueError('non letter first character: %r' % name)
570 self._params.append((name, value))
569 self._params.append((name, value))
571
570
572 def addpart(self, part):
571 def addpart(self, part):
573 """add a new part to the bundle2 container
572 """add a new part to the bundle2 container
574
573
575 Parts contains the actual applicative payload."""
574 Parts contains the actual applicative payload."""
576 assert part.id is None
575 assert part.id is None
577 part.id = len(self._parts) # very cheap counter
576 part.id = len(self._parts) # very cheap counter
578 self._parts.append(part)
577 self._parts.append(part)
579
578
580 def newpart(self, typeid, *args, **kwargs):
579 def newpart(self, typeid, *args, **kwargs):
581 """create a new part and add it to the containers
580 """create a new part and add it to the containers
582
581
583 As the part is directly added to the containers. For now, this means
582 As the part is directly added to the containers. For now, this means
584 that any failure to properly initialize the part after calling
583 that any failure to properly initialize the part after calling
585 ``newpart`` should result in a failure of the whole bundling process.
584 ``newpart`` should result in a failure of the whole bundling process.
586
585
587 You can still fall back to manually create and add if you need better
586 You can still fall back to manually create and add if you need better
588 control."""
587 control."""
589 part = bundlepart(typeid, *args, **kwargs)
588 part = bundlepart(typeid, *args, **kwargs)
590 self.addpart(part)
589 self.addpart(part)
591 return part
590 return part
592
591
593 # methods used to generate the bundle2 stream
592 # methods used to generate the bundle2 stream
594 def getchunks(self):
593 def getchunks(self):
595 if self.ui.debugflag:
594 if self.ui.debugflag:
596 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
595 msg = ['bundle2-output-bundle: "%s",' % self._magicstring]
597 if self._params:
596 if self._params:
598 msg.append(' (%i params)' % len(self._params))
597 msg.append(' (%i params)' % len(self._params))
599 msg.append(' %i parts total\n' % len(self._parts))
598 msg.append(' %i parts total\n' % len(self._parts))
600 self.ui.debug(''.join(msg))
599 self.ui.debug(''.join(msg))
601 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
600 outdebug(self.ui, 'start emission of %s stream' % self._magicstring)
602 yield self._magicstring
601 yield self._magicstring
603 param = self._paramchunk()
602 param = self._paramchunk()
604 outdebug(self.ui, 'bundle parameter: %s' % param)
603 outdebug(self.ui, 'bundle parameter: %s' % param)
605 yield _pack(_fstreamparamsize, len(param))
604 yield _pack(_fstreamparamsize, len(param))
606 if param:
605 if param:
607 yield param
606 yield param
608 for chunk in self._compengine.compressstream(self._getcorechunk(),
607 for chunk in self._compengine.compressstream(self._getcorechunk(),
609 self._compopts):
608 self._compopts):
610 yield chunk
609 yield chunk
611
610
612 def _paramchunk(self):
611 def _paramchunk(self):
613 """return a encoded version of all stream parameters"""
612 """return a encoded version of all stream parameters"""
614 blocks = []
613 blocks = []
615 for par, value in self._params:
614 for par, value in self._params:
616 par = urlreq.quote(par)
615 par = urlreq.quote(par)
617 if value is not None:
616 if value is not None:
618 value = urlreq.quote(value)
617 value = urlreq.quote(value)
619 par = '%s=%s' % (par, value)
618 par = '%s=%s' % (par, value)
620 blocks.append(par)
619 blocks.append(par)
621 return ' '.join(blocks)
620 return ' '.join(blocks)
622
621
623 def _getcorechunk(self):
622 def _getcorechunk(self):
624 """yield chunk for the core part of the bundle
623 """yield chunk for the core part of the bundle
625
624
626 (all but headers and parameters)"""
625 (all but headers and parameters)"""
627 outdebug(self.ui, 'start of parts')
626 outdebug(self.ui, 'start of parts')
628 for part in self._parts:
627 for part in self._parts:
629 outdebug(self.ui, 'bundle part: "%s"' % part.type)
628 outdebug(self.ui, 'bundle part: "%s"' % part.type)
630 for chunk in part.getchunks(ui=self.ui):
629 for chunk in part.getchunks(ui=self.ui):
631 yield chunk
630 yield chunk
632 outdebug(self.ui, 'end of bundle')
631 outdebug(self.ui, 'end of bundle')
633 yield _pack(_fpartheadersize, 0)
632 yield _pack(_fpartheadersize, 0)
634
633
635
634
636 def salvageoutput(self):
635 def salvageoutput(self):
637 """return a list with a copy of all output parts in the bundle
636 """return a list with a copy of all output parts in the bundle
638
637
639 This is meant to be used during error handling to make sure we preserve
638 This is meant to be used during error handling to make sure we preserve
640 server output"""
639 server output"""
641 salvaged = []
640 salvaged = []
642 for part in self._parts:
641 for part in self._parts:
643 if part.type.startswith('output'):
642 if part.type.startswith('output'):
644 salvaged.append(part.copy())
643 salvaged.append(part.copy())
645 return salvaged
644 return salvaged
646
645
647
646
648 class unpackermixin(object):
647 class unpackermixin(object):
649 """A mixin to extract bytes and struct data from a stream"""
648 """A mixin to extract bytes and struct data from a stream"""
650
649
651 def __init__(self, fp):
650 def __init__(self, fp):
652 self._fp = fp
651 self._fp = fp
653
652
654 def _unpack(self, format):
653 def _unpack(self, format):
655 """unpack this struct format from the stream
654 """unpack this struct format from the stream
656
655
657 This method is meant for internal usage by the bundle2 protocol only.
656 This method is meant for internal usage by the bundle2 protocol only.
658 They directly manipulate the low level stream including bundle2 level
657 They directly manipulate the low level stream including bundle2 level
659 instruction.
658 instruction.
660
659
661 Do not use it to implement higher-level logic or methods."""
660 Do not use it to implement higher-level logic or methods."""
662 data = self._readexact(struct.calcsize(format))
661 data = self._readexact(struct.calcsize(format))
663 return _unpack(format, data)
662 return _unpack(format, data)
664
663
665 def _readexact(self, size):
664 def _readexact(self, size):
666 """read exactly <size> bytes from the stream
665 """read exactly <size> bytes from the stream
667
666
668 This method is meant for internal usage by the bundle2 protocol only.
667 This method is meant for internal usage by the bundle2 protocol only.
669 They directly manipulate the low level stream including bundle2 level
668 They directly manipulate the low level stream including bundle2 level
670 instruction.
669 instruction.
671
670
672 Do not use it to implement higher-level logic or methods."""
671 Do not use it to implement higher-level logic or methods."""
673 return changegroup.readexactly(self._fp, size)
672 return changegroup.readexactly(self._fp, size)
674
673
675 def getunbundler(ui, fp, magicstring=None):
674 def getunbundler(ui, fp, magicstring=None):
676 """return a valid unbundler object for a given magicstring"""
675 """return a valid unbundler object for a given magicstring"""
677 if magicstring is None:
676 if magicstring is None:
678 magicstring = changegroup.readexactly(fp, 4)
677 magicstring = changegroup.readexactly(fp, 4)
679 magic, version = magicstring[0:2], magicstring[2:4]
678 magic, version = magicstring[0:2], magicstring[2:4]
680 if magic != 'HG':
679 if magic != 'HG':
681 ui.debug(
680 ui.debug(
682 "error: invalid magic: %r (version %r), should be 'HG'\n"
681 "error: invalid magic: %r (version %r), should be 'HG'\n"
683 % (magic, version))
682 % (magic, version))
684 raise error.Abort(_('not a Mercurial bundle'))
683 raise error.Abort(_('not a Mercurial bundle'))
685 unbundlerclass = formatmap.get(version)
684 unbundlerclass = formatmap.get(version)
686 if unbundlerclass is None:
685 if unbundlerclass is None:
687 raise error.Abort(_('unknown bundle version %s') % version)
686 raise error.Abort(_('unknown bundle version %s') % version)
688 unbundler = unbundlerclass(ui, fp)
687 unbundler = unbundlerclass(ui, fp)
689 indebug(ui, 'start processing of %s stream' % magicstring)
688 indebug(ui, 'start processing of %s stream' % magicstring)
690 return unbundler
689 return unbundler
691
690
692 class unbundle20(unpackermixin):
691 class unbundle20(unpackermixin):
693 """interpret a bundle2 stream
692 """interpret a bundle2 stream
694
693
695 This class is fed with a binary stream and yields parts through its
694 This class is fed with a binary stream and yields parts through its
696 `iterparts` methods."""
695 `iterparts` methods."""
697
696
698 _magicstring = 'HG20'
697 _magicstring = 'HG20'
699
698
700 def __init__(self, ui, fp):
699 def __init__(self, ui, fp):
701 """If header is specified, we do not read it out of the stream."""
700 """If header is specified, we do not read it out of the stream."""
702 self.ui = ui
701 self.ui = ui
703 self._compengine = util.compengines.forbundletype('UN')
702 self._compengine = util.compengines.forbundletype('UN')
704 self._compressed = None
703 self._compressed = None
705 super(unbundle20, self).__init__(fp)
704 super(unbundle20, self).__init__(fp)
706
705
707 @util.propertycache
706 @util.propertycache
708 def params(self):
707 def params(self):
709 """dictionary of stream level parameters"""
708 """dictionary of stream level parameters"""
710 indebug(self.ui, 'reading bundle2 stream parameters')
709 indebug(self.ui, 'reading bundle2 stream parameters')
711 params = {}
710 params = {}
712 paramssize = self._unpack(_fstreamparamsize)[0]
711 paramssize = self._unpack(_fstreamparamsize)[0]
713 if paramssize < 0:
712 if paramssize < 0:
714 raise error.BundleValueError('negative bundle param size: %i'
713 raise error.BundleValueError('negative bundle param size: %i'
715 % paramssize)
714 % paramssize)
716 if paramssize:
715 if paramssize:
717 params = self._readexact(paramssize)
716 params = self._readexact(paramssize)
718 params = self._processallparams(params)
717 params = self._processallparams(params)
719 return params
718 return params
720
719
721 def _processallparams(self, paramsblock):
720 def _processallparams(self, paramsblock):
722 """"""
721 """"""
723 params = util.sortdict()
722 params = util.sortdict()
724 for p in paramsblock.split(' '):
723 for p in paramsblock.split(' '):
725 p = p.split('=', 1)
724 p = p.split('=', 1)
726 p = [urlreq.unquote(i) for i in p]
725 p = [urlreq.unquote(i) for i in p]
727 if len(p) < 2:
726 if len(p) < 2:
728 p.append(None)
727 p.append(None)
729 self._processparam(*p)
728 self._processparam(*p)
730 params[p[0]] = p[1]
729 params[p[0]] = p[1]
731 return params
730 return params
732
731
733
732
734 def _processparam(self, name, value):
733 def _processparam(self, name, value):
735 """process a parameter, applying its effect if needed
734 """process a parameter, applying its effect if needed
736
735
737 Parameter starting with a lower case letter are advisory and will be
736 Parameter starting with a lower case letter are advisory and will be
738 ignored when unknown. Those starting with an upper case letter are
737 ignored when unknown. Those starting with an upper case letter are
739 mandatory and will this function will raise a KeyError when unknown.
738 mandatory and will this function will raise a KeyError when unknown.
740
739
741 Note: no option are currently supported. Any input will be either
740 Note: no option are currently supported. Any input will be either
742 ignored or failing.
741 ignored or failing.
743 """
742 """
744 if not name:
743 if not name:
745 raise ValueError('empty parameter name')
744 raise ValueError('empty parameter name')
746 if name[0] not in string.letters:
745 if name[0] not in string.letters:
747 raise ValueError('non letter first character: %r' % name)
746 raise ValueError('non letter first character: %r' % name)
748 try:
747 try:
749 handler = b2streamparamsmap[name.lower()]
748 handler = b2streamparamsmap[name.lower()]
750 except KeyError:
749 except KeyError:
751 if name[0].islower():
750 if name[0].islower():
752 indebug(self.ui, "ignoring unknown parameter %r" % name)
751 indebug(self.ui, "ignoring unknown parameter %r" % name)
753 else:
752 else:
754 raise error.BundleUnknownFeatureError(params=(name,))
753 raise error.BundleUnknownFeatureError(params=(name,))
755 else:
754 else:
756 handler(self, name, value)
755 handler(self, name, value)
757
756
758 def _forwardchunks(self):
757 def _forwardchunks(self):
759 """utility to transfer a bundle2 as binary
758 """utility to transfer a bundle2 as binary
760
759
761 This is made necessary by the fact the 'getbundle' command over 'ssh'
760 This is made necessary by the fact the 'getbundle' command over 'ssh'
762 have no way to know then the reply end, relying on the bundle to be
761 have no way to know then the reply end, relying on the bundle to be
763 interpreted to know its end. This is terrible and we are sorry, but we
762 interpreted to know its end. This is terrible and we are sorry, but we
764 needed to move forward to get general delta enabled.
763 needed to move forward to get general delta enabled.
765 """
764 """
766 yield self._magicstring
765 yield self._magicstring
767 assert 'params' not in vars(self)
766 assert 'params' not in vars(self)
768 paramssize = self._unpack(_fstreamparamsize)[0]
767 paramssize = self._unpack(_fstreamparamsize)[0]
769 if paramssize < 0:
768 if paramssize < 0:
770 raise error.BundleValueError('negative bundle param size: %i'
769 raise error.BundleValueError('negative bundle param size: %i'
771 % paramssize)
770 % paramssize)
772 yield _pack(_fstreamparamsize, paramssize)
771 yield _pack(_fstreamparamsize, paramssize)
773 if paramssize:
772 if paramssize:
774 params = self._readexact(paramssize)
773 params = self._readexact(paramssize)
775 self._processallparams(params)
774 self._processallparams(params)
776 yield params
775 yield params
777 assert self._compengine.bundletype == 'UN'
776 assert self._compengine.bundletype == 'UN'
778 # From there, payload might need to be decompressed
777 # From there, payload might need to be decompressed
779 self._fp = self._compengine.decompressorreader(self._fp)
778 self._fp = self._compengine.decompressorreader(self._fp)
780 emptycount = 0
779 emptycount = 0
781 while emptycount < 2:
780 while emptycount < 2:
782 # so we can brainlessly loop
781 # so we can brainlessly loop
783 assert _fpartheadersize == _fpayloadsize
782 assert _fpartheadersize == _fpayloadsize
784 size = self._unpack(_fpartheadersize)[0]
783 size = self._unpack(_fpartheadersize)[0]
785 yield _pack(_fpartheadersize, size)
784 yield _pack(_fpartheadersize, size)
786 if size:
785 if size:
787 emptycount = 0
786 emptycount = 0
788 else:
787 else:
789 emptycount += 1
788 emptycount += 1
790 continue
789 continue
791 if size == flaginterrupt:
790 if size == flaginterrupt:
792 continue
791 continue
793 elif size < 0:
792 elif size < 0:
794 raise error.BundleValueError('negative chunk size: %i')
793 raise error.BundleValueError('negative chunk size: %i')
795 yield self._readexact(size)
794 yield self._readexact(size)
796
795
797
796
798 def iterparts(self):
797 def iterparts(self):
799 """yield all parts contained in the stream"""
798 """yield all parts contained in the stream"""
800 # make sure param have been loaded
799 # make sure param have been loaded
801 self.params
800 self.params
802 # From there, payload need to be decompressed
801 # From there, payload need to be decompressed
803 self._fp = self._compengine.decompressorreader(self._fp)
802 self._fp = self._compengine.decompressorreader(self._fp)
804 indebug(self.ui, 'start extraction of bundle2 parts')
803 indebug(self.ui, 'start extraction of bundle2 parts')
805 headerblock = self._readpartheader()
804 headerblock = self._readpartheader()
806 while headerblock is not None:
805 while headerblock is not None:
807 part = unbundlepart(self.ui, headerblock, self._fp)
806 part = unbundlepart(self.ui, headerblock, self._fp)
808 yield part
807 yield part
809 part.seek(0, 2)
808 part.seek(0, 2)
810 headerblock = self._readpartheader()
809 headerblock = self._readpartheader()
811 indebug(self.ui, 'end of bundle2 stream')
810 indebug(self.ui, 'end of bundle2 stream')
812
811
813 def _readpartheader(self):
812 def _readpartheader(self):
814 """reads a part header size and return the bytes blob
813 """reads a part header size and return the bytes blob
815
814
816 returns None if empty"""
815 returns None if empty"""
817 headersize = self._unpack(_fpartheadersize)[0]
816 headersize = self._unpack(_fpartheadersize)[0]
818 if headersize < 0:
817 if headersize < 0:
819 raise error.BundleValueError('negative part header size: %i'
818 raise error.BundleValueError('negative part header size: %i'
820 % headersize)
819 % headersize)
821 indebug(self.ui, 'part header size: %i' % headersize)
820 indebug(self.ui, 'part header size: %i' % headersize)
822 if headersize:
821 if headersize:
823 return self._readexact(headersize)
822 return self._readexact(headersize)
824 return None
823 return None
825
824
826 def compressed(self):
825 def compressed(self):
827 self.params # load params
826 self.params # load params
828 return self._compressed
827 return self._compressed
829
828
830 def close(self):
829 def close(self):
831 """close underlying file"""
830 """close underlying file"""
832 if util.safehasattr(self._fp, 'close'):
831 if util.safehasattr(self._fp, 'close'):
833 return self._fp.close()
832 return self._fp.close()
834
833
835 formatmap = {'20': unbundle20}
834 formatmap = {'20': unbundle20}
836
835
837 b2streamparamsmap = {}
836 b2streamparamsmap = {}
838
837
839 def b2streamparamhandler(name):
838 def b2streamparamhandler(name):
840 """register a handler for a stream level parameter"""
839 """register a handler for a stream level parameter"""
841 def decorator(func):
840 def decorator(func):
842 assert name not in formatmap
841 assert name not in formatmap
843 b2streamparamsmap[name] = func
842 b2streamparamsmap[name] = func
844 return func
843 return func
845 return decorator
844 return decorator
846
845
847 @b2streamparamhandler('compression')
846 @b2streamparamhandler('compression')
848 def processcompression(unbundler, param, value):
847 def processcompression(unbundler, param, value):
849 """read compression parameter and install payload decompression"""
848 """read compression parameter and install payload decompression"""
850 if value not in util.compengines.supportedbundletypes:
849 if value not in util.compengines.supportedbundletypes:
851 raise error.BundleUnknownFeatureError(params=(param,),
850 raise error.BundleUnknownFeatureError(params=(param,),
852 values=(value,))
851 values=(value,))
853 unbundler._compengine = util.compengines.forbundletype(value)
852 unbundler._compengine = util.compengines.forbundletype(value)
854 if value is not None:
853 if value is not None:
855 unbundler._compressed = True
854 unbundler._compressed = True
856
855
857 class bundlepart(object):
856 class bundlepart(object):
858 """A bundle2 part contains application level payload
857 """A bundle2 part contains application level payload
859
858
860 The part `type` is used to route the part to the application level
859 The part `type` is used to route the part to the application level
861 handler.
860 handler.
862
861
863 The part payload is contained in ``part.data``. It could be raw bytes or a
862 The part payload is contained in ``part.data``. It could be raw bytes or a
864 generator of byte chunks.
863 generator of byte chunks.
865
864
866 You can add parameters to the part using the ``addparam`` method.
865 You can add parameters to the part using the ``addparam`` method.
867 Parameters can be either mandatory (default) or advisory. Remote side
866 Parameters can be either mandatory (default) or advisory. Remote side
868 should be able to safely ignore the advisory ones.
867 should be able to safely ignore the advisory ones.
869
868
870 Both data and parameters cannot be modified after the generation has begun.
869 Both data and parameters cannot be modified after the generation has begun.
871 """
870 """
872
871
873 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
872 def __init__(self, parttype, mandatoryparams=(), advisoryparams=(),
874 data='', mandatory=True):
873 data='', mandatory=True):
875 validateparttype(parttype)
874 validateparttype(parttype)
876 self.id = None
875 self.id = None
877 self.type = parttype
876 self.type = parttype
878 self._data = data
877 self._data = data
879 self._mandatoryparams = list(mandatoryparams)
878 self._mandatoryparams = list(mandatoryparams)
880 self._advisoryparams = list(advisoryparams)
879 self._advisoryparams = list(advisoryparams)
881 # checking for duplicated entries
880 # checking for duplicated entries
882 self._seenparams = set()
881 self._seenparams = set()
883 for pname, __ in self._mandatoryparams + self._advisoryparams:
882 for pname, __ in self._mandatoryparams + self._advisoryparams:
884 if pname in self._seenparams:
883 if pname in self._seenparams:
885 raise error.ProgrammingError('duplicated params: %s' % pname)
884 raise error.ProgrammingError('duplicated params: %s' % pname)
886 self._seenparams.add(pname)
885 self._seenparams.add(pname)
887 # status of the part's generation:
886 # status of the part's generation:
888 # - None: not started,
887 # - None: not started,
889 # - False: currently generated,
888 # - False: currently generated,
890 # - True: generation done.
889 # - True: generation done.
891 self._generated = None
890 self._generated = None
892 self.mandatory = mandatory
891 self.mandatory = mandatory
893
892
894 def __repr__(self):
893 def __repr__(self):
895 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
894 cls = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
896 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
895 return ('<%s object at %x; id: %s; type: %s; mandatory: %s>'
897 % (cls, id(self), self.id, self.type, self.mandatory))
896 % (cls, id(self), self.id, self.type, self.mandatory))
898
897
899 def copy(self):
898 def copy(self):
900 """return a copy of the part
899 """return a copy of the part
901
900
902 The new part have the very same content but no partid assigned yet.
901 The new part have the very same content but no partid assigned yet.
903 Parts with generated data cannot be copied."""
902 Parts with generated data cannot be copied."""
904 assert not util.safehasattr(self.data, 'next')
903 assert not util.safehasattr(self.data, 'next')
905 return self.__class__(self.type, self._mandatoryparams,
904 return self.__class__(self.type, self._mandatoryparams,
906 self._advisoryparams, self._data, self.mandatory)
905 self._advisoryparams, self._data, self.mandatory)
907
906
908 # methods used to defines the part content
907 # methods used to defines the part content
909 @property
908 @property
910 def data(self):
909 def data(self):
911 return self._data
910 return self._data
912
911
913 @data.setter
912 @data.setter
914 def data(self, data):
913 def data(self, data):
915 if self._generated is not None:
914 if self._generated is not None:
916 raise error.ReadOnlyPartError('part is being generated')
915 raise error.ReadOnlyPartError('part is being generated')
917 self._data = data
916 self._data = data
918
917
919 @property
918 @property
920 def mandatoryparams(self):
919 def mandatoryparams(self):
921 # make it an immutable tuple to force people through ``addparam``
920 # make it an immutable tuple to force people through ``addparam``
922 return tuple(self._mandatoryparams)
921 return tuple(self._mandatoryparams)
923
922
924 @property
923 @property
925 def advisoryparams(self):
924 def advisoryparams(self):
926 # make it an immutable tuple to force people through ``addparam``
925 # make it an immutable tuple to force people through ``addparam``
927 return tuple(self._advisoryparams)
926 return tuple(self._advisoryparams)
928
927
929 def addparam(self, name, value='', mandatory=True):
928 def addparam(self, name, value='', mandatory=True):
930 """add a parameter to the part
929 """add a parameter to the part
931
930
932 If 'mandatory' is set to True, the remote handler must claim support
931 If 'mandatory' is set to True, the remote handler must claim support
933 for this parameter or the unbundling will be aborted.
932 for this parameter or the unbundling will be aborted.
934
933
935 The 'name' and 'value' cannot exceed 255 bytes each.
934 The 'name' and 'value' cannot exceed 255 bytes each.
936 """
935 """
937 if self._generated is not None:
936 if self._generated is not None:
938 raise error.ReadOnlyPartError('part is being generated')
937 raise error.ReadOnlyPartError('part is being generated')
939 if name in self._seenparams:
938 if name in self._seenparams:
940 raise ValueError('duplicated params: %s' % name)
939 raise ValueError('duplicated params: %s' % name)
941 self._seenparams.add(name)
940 self._seenparams.add(name)
942 params = self._advisoryparams
941 params = self._advisoryparams
943 if mandatory:
942 if mandatory:
944 params = self._mandatoryparams
943 params = self._mandatoryparams
945 params.append((name, value))
944 params.append((name, value))
946
945
947 # methods used to generates the bundle2 stream
946 # methods used to generates the bundle2 stream
948 def getchunks(self, ui):
947 def getchunks(self, ui):
949 if self._generated is not None:
948 if self._generated is not None:
950 raise error.ProgrammingError('part can only be consumed once')
949 raise error.ProgrammingError('part can only be consumed once')
951 self._generated = False
950 self._generated = False
952
951
953 if ui.debugflag:
952 if ui.debugflag:
954 msg = ['bundle2-output-part: "%s"' % self.type]
953 msg = ['bundle2-output-part: "%s"' % self.type]
955 if not self.mandatory:
954 if not self.mandatory:
956 msg.append(' (advisory)')
955 msg.append(' (advisory)')
957 nbmp = len(self.mandatoryparams)
956 nbmp = len(self.mandatoryparams)
958 nbap = len(self.advisoryparams)
957 nbap = len(self.advisoryparams)
959 if nbmp or nbap:
958 if nbmp or nbap:
960 msg.append(' (params:')
959 msg.append(' (params:')
961 if nbmp:
960 if nbmp:
962 msg.append(' %i mandatory' % nbmp)
961 msg.append(' %i mandatory' % nbmp)
963 if nbap:
962 if nbap:
964 msg.append(' %i advisory' % nbmp)
963 msg.append(' %i advisory' % nbmp)
965 msg.append(')')
964 msg.append(')')
966 if not self.data:
965 if not self.data:
967 msg.append(' empty payload')
966 msg.append(' empty payload')
968 elif util.safehasattr(self.data, 'next'):
967 elif util.safehasattr(self.data, 'next'):
969 msg.append(' streamed payload')
968 msg.append(' streamed payload')
970 else:
969 else:
971 msg.append(' %i bytes payload' % len(self.data))
970 msg.append(' %i bytes payload' % len(self.data))
972 msg.append('\n')
971 msg.append('\n')
973 ui.debug(''.join(msg))
972 ui.debug(''.join(msg))
974
973
975 #### header
974 #### header
976 if self.mandatory:
975 if self.mandatory:
977 parttype = self.type.upper()
976 parttype = self.type.upper()
978 else:
977 else:
979 parttype = self.type.lower()
978 parttype = self.type.lower()
980 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
979 outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
981 ## parttype
980 ## parttype
982 header = [_pack(_fparttypesize, len(parttype)),
981 header = [_pack(_fparttypesize, len(parttype)),
983 parttype, _pack(_fpartid, self.id),
982 parttype, _pack(_fpartid, self.id),
984 ]
983 ]
985 ## parameters
984 ## parameters
986 # count
985 # count
987 manpar = self.mandatoryparams
986 manpar = self.mandatoryparams
988 advpar = self.advisoryparams
987 advpar = self.advisoryparams
989 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
988 header.append(_pack(_fpartparamcount, len(manpar), len(advpar)))
990 # size
989 # size
991 parsizes = []
990 parsizes = []
992 for key, value in manpar:
991 for key, value in manpar:
993 parsizes.append(len(key))
992 parsizes.append(len(key))
994 parsizes.append(len(value))
993 parsizes.append(len(value))
995 for key, value in advpar:
994 for key, value in advpar:
996 parsizes.append(len(key))
995 parsizes.append(len(key))
997 parsizes.append(len(value))
996 parsizes.append(len(value))
998 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
997 paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
999 header.append(paramsizes)
998 header.append(paramsizes)
1000 # key, value
999 # key, value
1001 for key, value in manpar:
1000 for key, value in manpar:
1002 header.append(key)
1001 header.append(key)
1003 header.append(value)
1002 header.append(value)
1004 for key, value in advpar:
1003 for key, value in advpar:
1005 header.append(key)
1004 header.append(key)
1006 header.append(value)
1005 header.append(value)
1007 ## finalize header
1006 ## finalize header
1008 headerchunk = ''.join(header)
1007 headerchunk = ''.join(header)
1009 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
1008 outdebug(ui, 'header chunk size: %i' % len(headerchunk))
1010 yield _pack(_fpartheadersize, len(headerchunk))
1009 yield _pack(_fpartheadersize, len(headerchunk))
1011 yield headerchunk
1010 yield headerchunk
1012 ## payload
1011 ## payload
1013 try:
1012 try:
1014 for chunk in self._payloadchunks():
1013 for chunk in self._payloadchunks():
1015 outdebug(ui, 'payload chunk size: %i' % len(chunk))
1014 outdebug(ui, 'payload chunk size: %i' % len(chunk))
1016 yield _pack(_fpayloadsize, len(chunk))
1015 yield _pack(_fpayloadsize, len(chunk))
1017 yield chunk
1016 yield chunk
1018 except GeneratorExit:
1017 except GeneratorExit:
1019 # GeneratorExit means that nobody is listening for our
1018 # GeneratorExit means that nobody is listening for our
1020 # results anyway, so just bail quickly rather than trying
1019 # results anyway, so just bail quickly rather than trying
1021 # to produce an error part.
1020 # to produce an error part.
1022 ui.debug('bundle2-generatorexit\n')
1021 ui.debug('bundle2-generatorexit\n')
1023 raise
1022 raise
1024 except BaseException as exc:
1023 except BaseException as exc:
1025 # backup exception data for later
1024 # backup exception data for later
1026 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1025 ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
1027 % exc)
1026 % exc)
1028 tb = sys.exc_info()[2]
1027 tb = sys.exc_info()[2]
1029 msg = 'unexpected error: %s' % exc
1028 msg = 'unexpected error: %s' % exc
1030 interpart = bundlepart('error:abort', [('message', msg)],
1029 interpart = bundlepart('error:abort', [('message', msg)],
1031 mandatory=False)
1030 mandatory=False)
1032 interpart.id = 0
1031 interpart.id = 0
1033 yield _pack(_fpayloadsize, -1)
1032 yield _pack(_fpayloadsize, -1)
1034 for chunk in interpart.getchunks(ui=ui):
1033 for chunk in interpart.getchunks(ui=ui):
1035 yield chunk
1034 yield chunk
1036 outdebug(ui, 'closing payload chunk')
1035 outdebug(ui, 'closing payload chunk')
1037 # abort current part payload
1036 # abort current part payload
1038 yield _pack(_fpayloadsize, 0)
1037 yield _pack(_fpayloadsize, 0)
1039 pycompat.raisewithtb(exc, tb)
1038 pycompat.raisewithtb(exc, tb)
1040 # end of payload
1039 # end of payload
1041 outdebug(ui, 'closing payload chunk')
1040 outdebug(ui, 'closing payload chunk')
1042 yield _pack(_fpayloadsize, 0)
1041 yield _pack(_fpayloadsize, 0)
1043 self._generated = True
1042 self._generated = True
1044
1043
1045 def _payloadchunks(self):
1044 def _payloadchunks(self):
1046 """yield chunks of a the part payload
1045 """yield chunks of a the part payload
1047
1046
1048 Exists to handle the different methods to provide data to a part."""
1047 Exists to handle the different methods to provide data to a part."""
1049 # we only support fixed size data now.
1048 # we only support fixed size data now.
1050 # This will be improved in the future.
1049 # This will be improved in the future.
1051 if util.safehasattr(self.data, 'next'):
1050 if util.safehasattr(self.data, 'next'):
1052 buff = util.chunkbuffer(self.data)
1051 buff = util.chunkbuffer(self.data)
1053 chunk = buff.read(preferedchunksize)
1052 chunk = buff.read(preferedchunksize)
1054 while chunk:
1053 while chunk:
1055 yield chunk
1054 yield chunk
1056 chunk = buff.read(preferedchunksize)
1055 chunk = buff.read(preferedchunksize)
1057 elif len(self.data):
1056 elif len(self.data):
1058 yield self.data
1057 yield self.data
1059
1058
1060
1059
1061 flaginterrupt = -1
1060 flaginterrupt = -1
1062
1061
1063 class interrupthandler(unpackermixin):
1062 class interrupthandler(unpackermixin):
1064 """read one part and process it with restricted capability
1063 """read one part and process it with restricted capability
1065
1064
1066 This allows to transmit exception raised on the producer size during part
1065 This allows to transmit exception raised on the producer size during part
1067 iteration while the consumer is reading a part.
1066 iteration while the consumer is reading a part.
1068
1067
1069 Part processed in this manner only have access to a ui object,"""
1068 Part processed in this manner only have access to a ui object,"""
1070
1069
1071 def __init__(self, ui, fp):
1070 def __init__(self, ui, fp):
1072 super(interrupthandler, self).__init__(fp)
1071 super(interrupthandler, self).__init__(fp)
1073 self.ui = ui
1072 self.ui = ui
1074
1073
1075 def _readpartheader(self):
1074 def _readpartheader(self):
1076 """reads a part header size and return the bytes blob
1075 """reads a part header size and return the bytes blob
1077
1076
1078 returns None if empty"""
1077 returns None if empty"""
1079 headersize = self._unpack(_fpartheadersize)[0]
1078 headersize = self._unpack(_fpartheadersize)[0]
1080 if headersize < 0:
1079 if headersize < 0:
1081 raise error.BundleValueError('negative part header size: %i'
1080 raise error.BundleValueError('negative part header size: %i'
1082 % headersize)
1081 % headersize)
1083 indebug(self.ui, 'part header size: %i\n' % headersize)
1082 indebug(self.ui, 'part header size: %i\n' % headersize)
1084 if headersize:
1083 if headersize:
1085 return self._readexact(headersize)
1084 return self._readexact(headersize)
1086 return None
1085 return None
1087
1086
1088 def __call__(self):
1087 def __call__(self):
1089
1088
1090 self.ui.debug('bundle2-input-stream-interrupt:'
1089 self.ui.debug('bundle2-input-stream-interrupt:'
1091 ' opening out of band context\n')
1090 ' opening out of band context\n')
1092 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1091 indebug(self.ui, 'bundle2 stream interruption, looking for a part.')
1093 headerblock = self._readpartheader()
1092 headerblock = self._readpartheader()
1094 if headerblock is None:
1093 if headerblock is None:
1095 indebug(self.ui, 'no part found during interruption.')
1094 indebug(self.ui, 'no part found during interruption.')
1096 return
1095 return
1097 part = unbundlepart(self.ui, headerblock, self._fp)
1096 part = unbundlepart(self.ui, headerblock, self._fp)
1098 op = interruptoperation(self.ui)
1097 op = interruptoperation(self.ui)
1099 _processpart(op, part)
1098 _processpart(op, part)
1100 self.ui.debug('bundle2-input-stream-interrupt:'
1099 self.ui.debug('bundle2-input-stream-interrupt:'
1101 ' closing out of band context\n')
1100 ' closing out of band context\n')
1102
1101
1103 class interruptoperation(object):
1102 class interruptoperation(object):
1104 """A limited operation to be use by part handler during interruption
1103 """A limited operation to be use by part handler during interruption
1105
1104
1106 It only have access to an ui object.
1105 It only have access to an ui object.
1107 """
1106 """
1108
1107
1109 def __init__(self, ui):
1108 def __init__(self, ui):
1110 self.ui = ui
1109 self.ui = ui
1111 self.reply = None
1110 self.reply = None
1112 self.captureoutput = False
1111 self.captureoutput = False
1113
1112
1114 @property
1113 @property
1115 def repo(self):
1114 def repo(self):
1116 raise error.ProgrammingError('no repo access from stream interruption')
1115 raise error.ProgrammingError('no repo access from stream interruption')
1117
1116
1118 def gettransaction(self):
1117 def gettransaction(self):
1119 raise TransactionUnavailable('no repo access from stream interruption')
1118 raise TransactionUnavailable('no repo access from stream interruption')
1120
1119
1121 class unbundlepart(unpackermixin):
1120 class unbundlepart(unpackermixin):
1122 """a bundle part read from a bundle"""
1121 """a bundle part read from a bundle"""
1123
1122
1124 def __init__(self, ui, header, fp):
1123 def __init__(self, ui, header, fp):
1125 super(unbundlepart, self).__init__(fp)
1124 super(unbundlepart, self).__init__(fp)
1126 self._seekable = (util.safehasattr(fp, 'seek') and
1125 self._seekable = (util.safehasattr(fp, 'seek') and
1127 util.safehasattr(fp, 'tell'))
1126 util.safehasattr(fp, 'tell'))
1128 self.ui = ui
1127 self.ui = ui
1129 # unbundle state attr
1128 # unbundle state attr
1130 self._headerdata = header
1129 self._headerdata = header
1131 self._headeroffset = 0
1130 self._headeroffset = 0
1132 self._initialized = False
1131 self._initialized = False
1133 self.consumed = False
1132 self.consumed = False
1134 # part data
1133 # part data
1135 self.id = None
1134 self.id = None
1136 self.type = None
1135 self.type = None
1137 self.mandatoryparams = None
1136 self.mandatoryparams = None
1138 self.advisoryparams = None
1137 self.advisoryparams = None
1139 self.params = None
1138 self.params = None
1140 self.mandatorykeys = ()
1139 self.mandatorykeys = ()
1141 self._payloadstream = None
1140 self._payloadstream = None
1142 self._readheader()
1141 self._readheader()
1143 self._mandatory = None
1142 self._mandatory = None
1144 self._chunkindex = [] #(payload, file) position tuples for chunk starts
1143 self._chunkindex = [] #(payload, file) position tuples for chunk starts
1145 self._pos = 0
1144 self._pos = 0
1146
1145
1147 def _fromheader(self, size):
1146 def _fromheader(self, size):
1148 """return the next <size> byte from the header"""
1147 """return the next <size> byte from the header"""
1149 offset = self._headeroffset
1148 offset = self._headeroffset
1150 data = self._headerdata[offset:(offset + size)]
1149 data = self._headerdata[offset:(offset + size)]
1151 self._headeroffset = offset + size
1150 self._headeroffset = offset + size
1152 return data
1151 return data
1153
1152
1154 def _unpackheader(self, format):
1153 def _unpackheader(self, format):
1155 """read given format from header
1154 """read given format from header
1156
1155
1157 This automatically compute the size of the format to read."""
1156 This automatically compute the size of the format to read."""
1158 data = self._fromheader(struct.calcsize(format))
1157 data = self._fromheader(struct.calcsize(format))
1159 return _unpack(format, data)
1158 return _unpack(format, data)
1160
1159
1161 def _initparams(self, mandatoryparams, advisoryparams):
1160 def _initparams(self, mandatoryparams, advisoryparams):
1162 """internal function to setup all logic related parameters"""
1161 """internal function to setup all logic related parameters"""
1163 # make it read only to prevent people touching it by mistake.
1162 # make it read only to prevent people touching it by mistake.
1164 self.mandatoryparams = tuple(mandatoryparams)
1163 self.mandatoryparams = tuple(mandatoryparams)
1165 self.advisoryparams = tuple(advisoryparams)
1164 self.advisoryparams = tuple(advisoryparams)
1166 # user friendly UI
1165 # user friendly UI
1167 self.params = util.sortdict(self.mandatoryparams)
1166 self.params = util.sortdict(self.mandatoryparams)
1168 self.params.update(self.advisoryparams)
1167 self.params.update(self.advisoryparams)
1169 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1168 self.mandatorykeys = frozenset(p[0] for p in mandatoryparams)
1170
1169
1171 def _payloadchunks(self, chunknum=0):
1170 def _payloadchunks(self, chunknum=0):
1172 '''seek to specified chunk and start yielding data'''
1171 '''seek to specified chunk and start yielding data'''
1173 if len(self._chunkindex) == 0:
1172 if len(self._chunkindex) == 0:
1174 assert chunknum == 0, 'Must start with chunk 0'
1173 assert chunknum == 0, 'Must start with chunk 0'
1175 self._chunkindex.append((0, self._tellfp()))
1174 self._chunkindex.append((0, self._tellfp()))
1176 else:
1175 else:
1177 assert chunknum < len(self._chunkindex), \
1176 assert chunknum < len(self._chunkindex), \
1178 'Unknown chunk %d' % chunknum
1177 'Unknown chunk %d' % chunknum
1179 self._seekfp(self._chunkindex[chunknum][1])
1178 self._seekfp(self._chunkindex[chunknum][1])
1180
1179
1181 pos = self._chunkindex[chunknum][0]
1180 pos = self._chunkindex[chunknum][0]
1182 payloadsize = self._unpack(_fpayloadsize)[0]
1181 payloadsize = self._unpack(_fpayloadsize)[0]
1183 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1182 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1184 while payloadsize:
1183 while payloadsize:
1185 if payloadsize == flaginterrupt:
1184 if payloadsize == flaginterrupt:
1186 # interruption detection, the handler will now read a
1185 # interruption detection, the handler will now read a
1187 # single part and process it.
1186 # single part and process it.
1188 interrupthandler(self.ui, self._fp)()
1187 interrupthandler(self.ui, self._fp)()
1189 elif payloadsize < 0:
1188 elif payloadsize < 0:
1190 msg = 'negative payload chunk size: %i' % payloadsize
1189 msg = 'negative payload chunk size: %i' % payloadsize
1191 raise error.BundleValueError(msg)
1190 raise error.BundleValueError(msg)
1192 else:
1191 else:
1193 result = self._readexact(payloadsize)
1192 result = self._readexact(payloadsize)
1194 chunknum += 1
1193 chunknum += 1
1195 pos += payloadsize
1194 pos += payloadsize
1196 if chunknum == len(self._chunkindex):
1195 if chunknum == len(self._chunkindex):
1197 self._chunkindex.append((pos, self._tellfp()))
1196 self._chunkindex.append((pos, self._tellfp()))
1198 yield result
1197 yield result
1199 payloadsize = self._unpack(_fpayloadsize)[0]
1198 payloadsize = self._unpack(_fpayloadsize)[0]
1200 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1199 indebug(self.ui, 'payload chunk size: %i' % payloadsize)
1201
1200
1202 def _findchunk(self, pos):
1201 def _findchunk(self, pos):
1203 '''for a given payload position, return a chunk number and offset'''
1202 '''for a given payload position, return a chunk number and offset'''
1204 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1203 for chunk, (ppos, fpos) in enumerate(self._chunkindex):
1205 if ppos == pos:
1204 if ppos == pos:
1206 return chunk, 0
1205 return chunk, 0
1207 elif ppos > pos:
1206 elif ppos > pos:
1208 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1207 return chunk - 1, pos - self._chunkindex[chunk - 1][0]
1209 raise ValueError('Unknown chunk')
1208 raise ValueError('Unknown chunk')
1210
1209
1211 def _readheader(self):
1210 def _readheader(self):
1212 """read the header and setup the object"""
1211 """read the header and setup the object"""
1213 typesize = self._unpackheader(_fparttypesize)[0]
1212 typesize = self._unpackheader(_fparttypesize)[0]
1214 self.type = self._fromheader(typesize)
1213 self.type = self._fromheader(typesize)
1215 indebug(self.ui, 'part type: "%s"' % self.type)
1214 indebug(self.ui, 'part type: "%s"' % self.type)
1216 self.id = self._unpackheader(_fpartid)[0]
1215 self.id = self._unpackheader(_fpartid)[0]
1217 indebug(self.ui, 'part id: "%s"' % self.id)
1216 indebug(self.ui, 'part id: "%s"' % self.id)
1218 # extract mandatory bit from type
1217 # extract mandatory bit from type
1219 self.mandatory = (self.type != self.type.lower())
1218 self.mandatory = (self.type != self.type.lower())
1220 self.type = self.type.lower()
1219 self.type = self.type.lower()
1221 ## reading parameters
1220 ## reading parameters
1222 # param count
1221 # param count
1223 mancount, advcount = self._unpackheader(_fpartparamcount)
1222 mancount, advcount = self._unpackheader(_fpartparamcount)
1224 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1223 indebug(self.ui, 'part parameters: %i' % (mancount + advcount))
1225 # param size
1224 # param size
1226 fparamsizes = _makefpartparamsizes(mancount + advcount)
1225 fparamsizes = _makefpartparamsizes(mancount + advcount)
1227 paramsizes = self._unpackheader(fparamsizes)
1226 paramsizes = self._unpackheader(fparamsizes)
1228 # make it a list of couple again
1227 # make it a list of couple again
1229 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1228 paramsizes = zip(paramsizes[::2], paramsizes[1::2])
1230 # split mandatory from advisory
1229 # split mandatory from advisory
1231 mansizes = paramsizes[:mancount]
1230 mansizes = paramsizes[:mancount]
1232 advsizes = paramsizes[mancount:]
1231 advsizes = paramsizes[mancount:]
1233 # retrieve param value
1232 # retrieve param value
1234 manparams = []
1233 manparams = []
1235 for key, value in mansizes:
1234 for key, value in mansizes:
1236 manparams.append((self._fromheader(key), self._fromheader(value)))
1235 manparams.append((self._fromheader(key), self._fromheader(value)))
1237 advparams = []
1236 advparams = []
1238 for key, value in advsizes:
1237 for key, value in advsizes:
1239 advparams.append((self._fromheader(key), self._fromheader(value)))
1238 advparams.append((self._fromheader(key), self._fromheader(value)))
1240 self._initparams(manparams, advparams)
1239 self._initparams(manparams, advparams)
1241 ## part payload
1240 ## part payload
1242 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1241 self._payloadstream = util.chunkbuffer(self._payloadchunks())
1243 # we read the data, tell it
1242 # we read the data, tell it
1244 self._initialized = True
1243 self._initialized = True
1245
1244
1246 def read(self, size=None):
1245 def read(self, size=None):
1247 """read payload data"""
1246 """read payload data"""
1248 if not self._initialized:
1247 if not self._initialized:
1249 self._readheader()
1248 self._readheader()
1250 if size is None:
1249 if size is None:
1251 data = self._payloadstream.read()
1250 data = self._payloadstream.read()
1252 else:
1251 else:
1253 data = self._payloadstream.read(size)
1252 data = self._payloadstream.read(size)
1254 self._pos += len(data)
1253 self._pos += len(data)
1255 if size is None or len(data) < size:
1254 if size is None or len(data) < size:
1256 if not self.consumed and self._pos:
1255 if not self.consumed and self._pos:
1257 self.ui.debug('bundle2-input-part: total payload size %i\n'
1256 self.ui.debug('bundle2-input-part: total payload size %i\n'
1258 % self._pos)
1257 % self._pos)
1259 self.consumed = True
1258 self.consumed = True
1260 return data
1259 return data
1261
1260
1262 def tell(self):
1261 def tell(self):
1263 return self._pos
1262 return self._pos
1264
1263
1265 def seek(self, offset, whence=0):
1264 def seek(self, offset, whence=0):
1266 if whence == 0:
1265 if whence == 0:
1267 newpos = offset
1266 newpos = offset
1268 elif whence == 1:
1267 elif whence == 1:
1269 newpos = self._pos + offset
1268 newpos = self._pos + offset
1270 elif whence == 2:
1269 elif whence == 2:
1271 if not self.consumed:
1270 if not self.consumed:
1272 self.read()
1271 self.read()
1273 newpos = self._chunkindex[-1][0] - offset
1272 newpos = self._chunkindex[-1][0] - offset
1274 else:
1273 else:
1275 raise ValueError('Unknown whence value: %r' % (whence,))
1274 raise ValueError('Unknown whence value: %r' % (whence,))
1276
1275
1277 if newpos > self._chunkindex[-1][0] and not self.consumed:
1276 if newpos > self._chunkindex[-1][0] and not self.consumed:
1278 self.read()
1277 self.read()
1279 if not 0 <= newpos <= self._chunkindex[-1][0]:
1278 if not 0 <= newpos <= self._chunkindex[-1][0]:
1280 raise ValueError('Offset out of range')
1279 raise ValueError('Offset out of range')
1281
1280
1282 if self._pos != newpos:
1281 if self._pos != newpos:
1283 chunk, internaloffset = self._findchunk(newpos)
1282 chunk, internaloffset = self._findchunk(newpos)
1284 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1283 self._payloadstream = util.chunkbuffer(self._payloadchunks(chunk))
1285 adjust = self.read(internaloffset)
1284 adjust = self.read(internaloffset)
1286 if len(adjust) != internaloffset:
1285 if len(adjust) != internaloffset:
1287 raise error.Abort(_('Seek failed\n'))
1286 raise error.Abort(_('Seek failed\n'))
1288 self._pos = newpos
1287 self._pos = newpos
1289
1288
1290 def _seekfp(self, offset, whence=0):
1289 def _seekfp(self, offset, whence=0):
1291 """move the underlying file pointer
1290 """move the underlying file pointer
1292
1291
1293 This method is meant for internal usage by the bundle2 protocol only.
1292 This method is meant for internal usage by the bundle2 protocol only.
1294 They directly manipulate the low level stream including bundle2 level
1293 They directly manipulate the low level stream including bundle2 level
1295 instruction.
1294 instruction.
1296
1295
1297 Do not use it to implement higher-level logic or methods."""
1296 Do not use it to implement higher-level logic or methods."""
1298 if self._seekable:
1297 if self._seekable:
1299 return self._fp.seek(offset, whence)
1298 return self._fp.seek(offset, whence)
1300 else:
1299 else:
1301 raise NotImplementedError(_('File pointer is not seekable'))
1300 raise NotImplementedError(_('File pointer is not seekable'))
1302
1301
1303 def _tellfp(self):
1302 def _tellfp(self):
1304 """return the file offset, or None if file is not seekable
1303 """return the file offset, or None if file is not seekable
1305
1304
1306 This method is meant for internal usage by the bundle2 protocol only.
1305 This method is meant for internal usage by the bundle2 protocol only.
1307 They directly manipulate the low level stream including bundle2 level
1306 They directly manipulate the low level stream including bundle2 level
1308 instruction.
1307 instruction.
1309
1308
1310 Do not use it to implement higher-level logic or methods."""
1309 Do not use it to implement higher-level logic or methods."""
1311 if self._seekable:
1310 if self._seekable:
1312 try:
1311 try:
1313 return self._fp.tell()
1312 return self._fp.tell()
1314 except IOError as e:
1313 except IOError as e:
1315 if e.errno == errno.ESPIPE:
1314 if e.errno == errno.ESPIPE:
1316 self._seekable = False
1315 self._seekable = False
1317 else:
1316 else:
1318 raise
1317 raise
1319 return None
1318 return None
1320
1319
1321 # These are only the static capabilities.
1320 # These are only the static capabilities.
1322 # Check the 'getrepocaps' function for the rest.
1321 # Check the 'getrepocaps' function for the rest.
1323 capabilities = {'HG20': (),
1322 capabilities = {'HG20': (),
1324 'error': ('abort', 'unsupportedcontent', 'pushraced',
1323 'error': ('abort', 'unsupportedcontent', 'pushraced',
1325 'pushkey'),
1324 'pushkey'),
1326 'listkeys': (),
1325 'listkeys': (),
1327 'pushkey': (),
1326 'pushkey': (),
1328 'digests': tuple(sorted(util.DIGESTS.keys())),
1327 'digests': tuple(sorted(util.DIGESTS.keys())),
1329 'remote-changegroup': ('http', 'https'),
1328 'remote-changegroup': ('http', 'https'),
1330 'hgtagsfnodes': (),
1329 'hgtagsfnodes': (),
1331 }
1330 }
1332
1331
1333 def getrepocaps(repo, allowpushback=False):
1332 def getrepocaps(repo, allowpushback=False):
1334 """return the bundle2 capabilities for a given repo
1333 """return the bundle2 capabilities for a given repo
1335
1334
1336 Exists to allow extensions (like evolution) to mutate the capabilities.
1335 Exists to allow extensions (like evolution) to mutate the capabilities.
1337 """
1336 """
1338 caps = capabilities.copy()
1337 caps = capabilities.copy()
1339 caps['changegroup'] = tuple(sorted(
1338 caps['changegroup'] = tuple(sorted(
1340 changegroup.supportedincomingversions(repo)))
1339 changegroup.supportedincomingversions(repo)))
1341 if obsolete.isenabled(repo, obsolete.exchangeopt):
1340 if obsolete.isenabled(repo, obsolete.exchangeopt):
1342 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1341 supportedformat = tuple('V%i' % v for v in obsolete.formats)
1343 caps['obsmarkers'] = supportedformat
1342 caps['obsmarkers'] = supportedformat
1344 if allowpushback:
1343 if allowpushback:
1345 caps['pushback'] = ()
1344 caps['pushback'] = ()
1346 cpmode = repo.ui.config('server', 'concurrent-push-mode')
1345 cpmode = repo.ui.config('server', 'concurrent-push-mode')
1347 if cpmode == 'check-related':
1346 if cpmode == 'check-related':
1348 caps['checkheads'] = ('related',)
1347 caps['checkheads'] = ('related',)
1349 return caps
1348 return caps
1350
1349
1351 def bundle2caps(remote):
1350 def bundle2caps(remote):
1352 """return the bundle capabilities of a peer as dict"""
1351 """return the bundle capabilities of a peer as dict"""
1353 raw = remote.capable('bundle2')
1352 raw = remote.capable('bundle2')
1354 if not raw and raw != '':
1353 if not raw and raw != '':
1355 return {}
1354 return {}
1356 capsblob = urlreq.unquote(remote.capable('bundle2'))
1355 capsblob = urlreq.unquote(remote.capable('bundle2'))
1357 return decodecaps(capsblob)
1356 return decodecaps(capsblob)
1358
1357
1359 def obsmarkersversion(caps):
1358 def obsmarkersversion(caps):
1360 """extract the list of supported obsmarkers versions from a bundle2caps dict
1359 """extract the list of supported obsmarkers versions from a bundle2caps dict
1361 """
1360 """
1362 obscaps = caps.get('obsmarkers', ())
1361 obscaps = caps.get('obsmarkers', ())
1363 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1362 return [int(c[1:]) for c in obscaps if c.startswith('V')]
1364
1363
1365 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
1364 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
1366 vfs=None, compression=None, compopts=None):
1365 vfs=None, compression=None, compopts=None):
1367 if bundletype.startswith('HG10'):
1366 if bundletype.startswith('HG10'):
1368 cg = changegroup.getchangegroup(repo, source, outgoing, version='01')
1367 cg = changegroup.getchangegroup(repo, source, outgoing, version='01')
1369 return writebundle(ui, cg, filename, bundletype, vfs=vfs,
1368 return writebundle(ui, cg, filename, bundletype, vfs=vfs,
1370 compression=compression, compopts=compopts)
1369 compression=compression, compopts=compopts)
1371 elif not bundletype.startswith('HG20'):
1370 elif not bundletype.startswith('HG20'):
1372 raise error.ProgrammingError('unknown bundle type: %s' % bundletype)
1371 raise error.ProgrammingError('unknown bundle type: %s' % bundletype)
1373
1372
1374 caps = {}
1373 caps = {}
1375 if 'obsolescence' in opts:
1374 if 'obsolescence' in opts:
1376 caps['obsmarkers'] = ('V1',)
1375 caps['obsmarkers'] = ('V1',)
1377 bundle = bundle20(ui, caps)
1376 bundle = bundle20(ui, caps)
1378 bundle.setcompression(compression, compopts)
1377 bundle.setcompression(compression, compopts)
1379 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1378 _addpartsfromopts(ui, repo, bundle, source, outgoing, opts)
1380 chunkiter = bundle.getchunks()
1379 chunkiter = bundle.getchunks()
1381
1380
1382 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1381 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1383
1382
1384 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1383 def _addpartsfromopts(ui, repo, bundler, source, outgoing, opts):
1385 # We should eventually reconcile this logic with the one behind
1384 # We should eventually reconcile this logic with the one behind
1386 # 'exchange.getbundle2partsgenerator'.
1385 # 'exchange.getbundle2partsgenerator'.
1387 #
1386 #
1388 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1387 # The type of input from 'getbundle' and 'writenewbundle' are a bit
1389 # different right now. So we keep them separated for now for the sake of
1388 # different right now. So we keep them separated for now for the sake of
1390 # simplicity.
1389 # simplicity.
1391
1390
1392 # we always want a changegroup in such bundle
1391 # we always want a changegroup in such bundle
1393 cgversion = opts.get('cg.version')
1392 cgversion = opts.get('cg.version')
1394 if cgversion is None:
1393 if cgversion is None:
1395 cgversion = changegroup.safeversion(repo)
1394 cgversion = changegroup.safeversion(repo)
1396 cg = changegroup.getchangegroup(repo, source, outgoing,
1395 cg = changegroup.getchangegroup(repo, source, outgoing,
1397 version=cgversion)
1396 version=cgversion)
1398 part = bundler.newpart('changegroup', data=cg.getchunks())
1397 part = bundler.newpart('changegroup', data=cg.getchunks())
1399 part.addparam('version', cg.version)
1398 part.addparam('version', cg.version)
1400 if 'clcount' in cg.extras:
1399 if 'clcount' in cg.extras:
1401 part.addparam('nbchanges', str(cg.extras['clcount']),
1400 part.addparam('nbchanges', str(cg.extras['clcount']),
1402 mandatory=False)
1401 mandatory=False)
1403 if opts.get('phases') and repo.revs('%ln and secret()',
1402 if opts.get('phases') and repo.revs('%ln and secret()',
1404 outgoing.missingheads):
1403 outgoing.missingheads):
1405 part.addparam('targetphase', '%d' % phases.secret, mandatory=False)
1404 part.addparam('targetphase', '%d' % phases.secret, mandatory=False)
1406
1405
1407 addparttagsfnodescache(repo, bundler, outgoing)
1406 addparttagsfnodescache(repo, bundler, outgoing)
1408
1407
1409 if opts.get('obsolescence', False):
1408 if opts.get('obsolescence', False):
1410 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1409 obsmarkers = repo.obsstore.relevantmarkers(outgoing.missing)
1411 buildobsmarkerspart(bundler, obsmarkers)
1410 buildobsmarkerspart(bundler, obsmarkers)
1412
1411
1413 if opts.get('phases', False):
1412 if opts.get('phases', False):
1414 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1413 headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
1415 phasedata = []
1414 phasedata = []
1416 for phase in phases.allphases:
1415 for phase in phases.allphases:
1417 for head in headsbyphase[phase]:
1416 for head in headsbyphase[phase]:
1418 phasedata.append(_pack(_fphasesentry, phase, head))
1417 phasedata.append(_pack(_fphasesentry, phase, head))
1419 bundler.newpart('phase-heads', data=''.join(phasedata))
1418 bundler.newpart('phase-heads', data=''.join(phasedata))
1420
1419
1421 def addparttagsfnodescache(repo, bundler, outgoing):
1420 def addparttagsfnodescache(repo, bundler, outgoing):
1422 # we include the tags fnode cache for the bundle changeset
1421 # we include the tags fnode cache for the bundle changeset
1423 # (as an optional parts)
1422 # (as an optional parts)
1424 cache = tags.hgtagsfnodescache(repo.unfiltered())
1423 cache = tags.hgtagsfnodescache(repo.unfiltered())
1425 chunks = []
1424 chunks = []
1426
1425
1427 # .hgtags fnodes are only relevant for head changesets. While we could
1426 # .hgtags fnodes are only relevant for head changesets. While we could
1428 # transfer values for all known nodes, there will likely be little to
1427 # transfer values for all known nodes, there will likely be little to
1429 # no benefit.
1428 # no benefit.
1430 #
1429 #
1431 # We don't bother using a generator to produce output data because
1430 # We don't bother using a generator to produce output data because
1432 # a) we only have 40 bytes per head and even esoteric numbers of heads
1431 # a) we only have 40 bytes per head and even esoteric numbers of heads
1433 # consume little memory (1M heads is 40MB) b) we don't want to send the
1432 # consume little memory (1M heads is 40MB) b) we don't want to send the
1434 # part if we don't have entries and knowing if we have entries requires
1433 # part if we don't have entries and knowing if we have entries requires
1435 # cache lookups.
1434 # cache lookups.
1436 for node in outgoing.missingheads:
1435 for node in outgoing.missingheads:
1437 # Don't compute missing, as this may slow down serving.
1436 # Don't compute missing, as this may slow down serving.
1438 fnode = cache.getfnode(node, computemissing=False)
1437 fnode = cache.getfnode(node, computemissing=False)
1439 if fnode is not None:
1438 if fnode is not None:
1440 chunks.extend([node, fnode])
1439 chunks.extend([node, fnode])
1441
1440
1442 if chunks:
1441 if chunks:
1443 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1442 bundler.newpart('hgtagsfnodes', data=''.join(chunks))
1444
1443
1445 def buildobsmarkerspart(bundler, markers):
1444 def buildobsmarkerspart(bundler, markers):
1446 """add an obsmarker part to the bundler with <markers>
1445 """add an obsmarker part to the bundler with <markers>
1447
1446
1448 No part is created if markers is empty.
1447 No part is created if markers is empty.
1449 Raises ValueError if the bundler doesn't support any known obsmarker format.
1448 Raises ValueError if the bundler doesn't support any known obsmarker format.
1450 """
1449 """
1451 if not markers:
1450 if not markers:
1452 return None
1451 return None
1453
1452
1454 remoteversions = obsmarkersversion(bundler.capabilities)
1453 remoteversions = obsmarkersversion(bundler.capabilities)
1455 version = obsolete.commonversion(remoteversions)
1454 version = obsolete.commonversion(remoteversions)
1456 if version is None:
1455 if version is None:
1457 raise ValueError('bundler does not support common obsmarker format')
1456 raise ValueError('bundler does not support common obsmarker format')
1458 stream = obsolete.encodemarkers(markers, True, version=version)
1457 stream = obsolete.encodemarkers(markers, True, version=version)
1459 return bundler.newpart('obsmarkers', data=stream)
1458 return bundler.newpart('obsmarkers', data=stream)
1460
1459
1461 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1460 def writebundle(ui, cg, filename, bundletype, vfs=None, compression=None,
1462 compopts=None):
1461 compopts=None):
1463 """Write a bundle file and return its filename.
1462 """Write a bundle file and return its filename.
1464
1463
1465 Existing files will not be overwritten.
1464 Existing files will not be overwritten.
1466 If no filename is specified, a temporary file is created.
1465 If no filename is specified, a temporary file is created.
1467 bz2 compression can be turned off.
1466 bz2 compression can be turned off.
1468 The bundle file will be deleted in case of errors.
1467 The bundle file will be deleted in case of errors.
1469 """
1468 """
1470
1469
1471 if bundletype == "HG20":
1470 if bundletype == "HG20":
1472 bundle = bundle20(ui)
1471 bundle = bundle20(ui)
1473 bundle.setcompression(compression, compopts)
1472 bundle.setcompression(compression, compopts)
1474 part = bundle.newpart('changegroup', data=cg.getchunks())
1473 part = bundle.newpart('changegroup', data=cg.getchunks())
1475 part.addparam('version', cg.version)
1474 part.addparam('version', cg.version)
1476 if 'clcount' in cg.extras:
1475 if 'clcount' in cg.extras:
1477 part.addparam('nbchanges', str(cg.extras['clcount']),
1476 part.addparam('nbchanges', str(cg.extras['clcount']),
1478 mandatory=False)
1477 mandatory=False)
1479 chunkiter = bundle.getchunks()
1478 chunkiter = bundle.getchunks()
1480 else:
1479 else:
1481 # compression argument is only for the bundle2 case
1480 # compression argument is only for the bundle2 case
1482 assert compression is None
1481 assert compression is None
1483 if cg.version != '01':
1482 if cg.version != '01':
1484 raise error.Abort(_('old bundle types only supports v1 '
1483 raise error.Abort(_('old bundle types only supports v1 '
1485 'changegroups'))
1484 'changegroups'))
1486 header, comp = bundletypes[bundletype]
1485 header, comp = bundletypes[bundletype]
1487 if comp not in util.compengines.supportedbundletypes:
1486 if comp not in util.compengines.supportedbundletypes:
1488 raise error.Abort(_('unknown stream compression type: %s')
1487 raise error.Abort(_('unknown stream compression type: %s')
1489 % comp)
1488 % comp)
1490 compengine = util.compengines.forbundletype(comp)
1489 compengine = util.compengines.forbundletype(comp)
1491 def chunkiter():
1490 def chunkiter():
1492 yield header
1491 yield header
1493 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1492 for chunk in compengine.compressstream(cg.getchunks(), compopts):
1494 yield chunk
1493 yield chunk
1495 chunkiter = chunkiter()
1494 chunkiter = chunkiter()
1496
1495
1497 # parse the changegroup data, otherwise we will block
1496 # parse the changegroup data, otherwise we will block
1498 # in case of sshrepo because we don't know the end of the stream
1497 # in case of sshrepo because we don't know the end of the stream
1499 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1498 return changegroup.writechunks(ui, chunkiter, filename, vfs=vfs)
1500
1499
1501 def combinechangegroupresults(op):
1500 def combinechangegroupresults(op):
1502 """logic to combine 0 or more addchangegroup results into one"""
1501 """logic to combine 0 or more addchangegroup results into one"""
1503 results = [r.get('return', 0)
1502 results = [r.get('return', 0)
1504 for r in op.records['changegroup']]
1503 for r in op.records['changegroup']]
1505 changedheads = 0
1504 changedheads = 0
1506 result = 1
1505 result = 1
1507 for ret in results:
1506 for ret in results:
1508 # If any changegroup result is 0, return 0
1507 # If any changegroup result is 0, return 0
1509 if ret == 0:
1508 if ret == 0:
1510 result = 0
1509 result = 0
1511 break
1510 break
1512 if ret < -1:
1511 if ret < -1:
1513 changedheads += ret + 1
1512 changedheads += ret + 1
1514 elif ret > 1:
1513 elif ret > 1:
1515 changedheads += ret - 1
1514 changedheads += ret - 1
1516 if changedheads > 0:
1515 if changedheads > 0:
1517 result = 1 + changedheads
1516 result = 1 + changedheads
1518 elif changedheads < 0:
1517 elif changedheads < 0:
1519 result = -1 + changedheads
1518 result = -1 + changedheads
1520 return result
1519 return result
1521
1520
1522 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest',
1521 @parthandler('changegroup', ('version', 'nbchanges', 'treemanifest',
1523 'targetphase'))
1522 'targetphase'))
1524 def handlechangegroup(op, inpart):
1523 def handlechangegroup(op, inpart):
1525 """apply a changegroup part on the repo
1524 """apply a changegroup part on the repo
1526
1525
1527 This is a very early implementation that will massive rework before being
1526 This is a very early implementation that will massive rework before being
1528 inflicted to any end-user.
1527 inflicted to any end-user.
1529 """
1528 """
1530 tr = op.gettransaction()
1529 tr = op.gettransaction()
1531 unpackerversion = inpart.params.get('version', '01')
1530 unpackerversion = inpart.params.get('version', '01')
1532 # We should raise an appropriate exception here
1531 # We should raise an appropriate exception here
1533 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1532 cg = changegroup.getunbundler(unpackerversion, inpart, None)
1534 # the source and url passed here are overwritten by the one contained in
1533 # the source and url passed here are overwritten by the one contained in
1535 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1534 # the transaction.hookargs argument. So 'bundle2' is a placeholder
1536 nbchangesets = None
1535 nbchangesets = None
1537 if 'nbchanges' in inpart.params:
1536 if 'nbchanges' in inpart.params:
1538 nbchangesets = int(inpart.params.get('nbchanges'))
1537 nbchangesets = int(inpart.params.get('nbchanges'))
1539 if ('treemanifest' in inpart.params and
1538 if ('treemanifest' in inpart.params and
1540 'treemanifest' not in op.repo.requirements):
1539 'treemanifest' not in op.repo.requirements):
1541 if len(op.repo.changelog) != 0:
1540 if len(op.repo.changelog) != 0:
1542 raise error.Abort(_(
1541 raise error.Abort(_(
1543 "bundle contains tree manifests, but local repo is "
1542 "bundle contains tree manifests, but local repo is "
1544 "non-empty and does not use tree manifests"))
1543 "non-empty and does not use tree manifests"))
1545 op.repo.requirements.add('treemanifest')
1544 op.repo.requirements.add('treemanifest')
1546 op.repo._applyopenerreqs()
1545 op.repo._applyopenerreqs()
1547 op.repo._writerequirements()
1546 op.repo._writerequirements()
1548 extrakwargs = {}
1547 extrakwargs = {}
1549 targetphase = inpart.params.get('targetphase')
1548 targetphase = inpart.params.get('targetphase')
1550 if targetphase is not None:
1549 if targetphase is not None:
1551 extrakwargs['targetphase'] = int(targetphase)
1550 extrakwargs['targetphase'] = int(targetphase)
1552 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2',
1551 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2',
1553 expectedtotal=nbchangesets, **extrakwargs)
1552 expectedtotal=nbchangesets, **extrakwargs)
1554 if op.reply is not None:
1553 if op.reply is not None:
1555 # This is definitely not the final form of this
1554 # This is definitely not the final form of this
1556 # return. But one need to start somewhere.
1555 # return. But one need to start somewhere.
1557 part = op.reply.newpart('reply:changegroup', mandatory=False)
1556 part = op.reply.newpart('reply:changegroup', mandatory=False)
1558 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1557 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1559 part.addparam('return', '%i' % ret, mandatory=False)
1558 part.addparam('return', '%i' % ret, mandatory=False)
1560 assert not inpart.read()
1559 assert not inpart.read()
1561
1560
1562 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1561 _remotechangegroupparams = tuple(['url', 'size', 'digests'] +
1563 ['digest:%s' % k for k in util.DIGESTS.keys()])
1562 ['digest:%s' % k for k in util.DIGESTS.keys()])
1564 @parthandler('remote-changegroup', _remotechangegroupparams)
1563 @parthandler('remote-changegroup', _remotechangegroupparams)
1565 def handleremotechangegroup(op, inpart):
1564 def handleremotechangegroup(op, inpart):
1566 """apply a bundle10 on the repo, given an url and validation information
1565 """apply a bundle10 on the repo, given an url and validation information
1567
1566
1568 All the information about the remote bundle to import are given as
1567 All the information about the remote bundle to import are given as
1569 parameters. The parameters include:
1568 parameters. The parameters include:
1570 - url: the url to the bundle10.
1569 - url: the url to the bundle10.
1571 - size: the bundle10 file size. It is used to validate what was
1570 - size: the bundle10 file size. It is used to validate what was
1572 retrieved by the client matches the server knowledge about the bundle.
1571 retrieved by the client matches the server knowledge about the bundle.
1573 - digests: a space separated list of the digest types provided as
1572 - digests: a space separated list of the digest types provided as
1574 parameters.
1573 parameters.
1575 - digest:<digest-type>: the hexadecimal representation of the digest with
1574 - digest:<digest-type>: the hexadecimal representation of the digest with
1576 that name. Like the size, it is used to validate what was retrieved by
1575 that name. Like the size, it is used to validate what was retrieved by
1577 the client matches what the server knows about the bundle.
1576 the client matches what the server knows about the bundle.
1578
1577
1579 When multiple digest types are given, all of them are checked.
1578 When multiple digest types are given, all of them are checked.
1580 """
1579 """
1581 try:
1580 try:
1582 raw_url = inpart.params['url']
1581 raw_url = inpart.params['url']
1583 except KeyError:
1582 except KeyError:
1584 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1583 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'url')
1585 parsed_url = util.url(raw_url)
1584 parsed_url = util.url(raw_url)
1586 if parsed_url.scheme not in capabilities['remote-changegroup']:
1585 if parsed_url.scheme not in capabilities['remote-changegroup']:
1587 raise error.Abort(_('remote-changegroup does not support %s urls') %
1586 raise error.Abort(_('remote-changegroup does not support %s urls') %
1588 parsed_url.scheme)
1587 parsed_url.scheme)
1589
1588
1590 try:
1589 try:
1591 size = int(inpart.params['size'])
1590 size = int(inpart.params['size'])
1592 except ValueError:
1591 except ValueError:
1593 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1592 raise error.Abort(_('remote-changegroup: invalid value for param "%s"')
1594 % 'size')
1593 % 'size')
1595 except KeyError:
1594 except KeyError:
1596 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1595 raise error.Abort(_('remote-changegroup: missing "%s" param') % 'size')
1597
1596
1598 digests = {}
1597 digests = {}
1599 for typ in inpart.params.get('digests', '').split():
1598 for typ in inpart.params.get('digests', '').split():
1600 param = 'digest:%s' % typ
1599 param = 'digest:%s' % typ
1601 try:
1600 try:
1602 value = inpart.params[param]
1601 value = inpart.params[param]
1603 except KeyError:
1602 except KeyError:
1604 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1603 raise error.Abort(_('remote-changegroup: missing "%s" param') %
1605 param)
1604 param)
1606 digests[typ] = value
1605 digests[typ] = value
1607
1606
1608 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1607 real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
1609
1608
1610 tr = op.gettransaction()
1609 tr = op.gettransaction()
1611 from . import exchange
1610 from . import exchange
1612 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1611 cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
1613 if not isinstance(cg, changegroup.cg1unpacker):
1612 if not isinstance(cg, changegroup.cg1unpacker):
1614 raise error.Abort(_('%s: not a bundle version 1.0') %
1613 raise error.Abort(_('%s: not a bundle version 1.0') %
1615 util.hidepassword(raw_url))
1614 util.hidepassword(raw_url))
1616 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2')
1615 ret = _processchangegroup(op, cg, tr, 'bundle2', 'bundle2')
1617 if op.reply is not None:
1616 if op.reply is not None:
1618 # This is definitely not the final form of this
1617 # This is definitely not the final form of this
1619 # return. But one need to start somewhere.
1618 # return. But one need to start somewhere.
1620 part = op.reply.newpart('reply:changegroup')
1619 part = op.reply.newpart('reply:changegroup')
1621 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1620 part.addparam('in-reply-to', str(inpart.id), mandatory=False)
1622 part.addparam('return', '%i' % ret, mandatory=False)
1621 part.addparam('return', '%i' % ret, mandatory=False)
1623 try:
1622 try:
1624 real_part.validate()
1623 real_part.validate()
1625 except error.Abort as e:
1624 except error.Abort as e:
1626 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1625 raise error.Abort(_('bundle at %s is corrupted:\n%s') %
1627 (util.hidepassword(raw_url), str(e)))
1626 (util.hidepassword(raw_url), str(e)))
1628 assert not inpart.read()
1627 assert not inpart.read()
1629
1628
1630 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1629 @parthandler('reply:changegroup', ('return', 'in-reply-to'))
1631 def handlereplychangegroup(op, inpart):
1630 def handlereplychangegroup(op, inpart):
1632 ret = int(inpart.params['return'])
1631 ret = int(inpart.params['return'])
1633 replyto = int(inpart.params['in-reply-to'])
1632 replyto = int(inpart.params['in-reply-to'])
1634 op.records.add('changegroup', {'return': ret}, replyto)
1633 op.records.add('changegroup', {'return': ret}, replyto)
1635
1634
1636 @parthandler('check:heads')
1635 @parthandler('check:heads')
1637 def handlecheckheads(op, inpart):
1636 def handlecheckheads(op, inpart):
1638 """check that head of the repo did not change
1637 """check that head of the repo did not change
1639
1638
1640 This is used to detect a push race when using unbundle.
1639 This is used to detect a push race when using unbundle.
1641 This replaces the "heads" argument of unbundle."""
1640 This replaces the "heads" argument of unbundle."""
1642 h = inpart.read(20)
1641 h = inpart.read(20)
1643 heads = []
1642 heads = []
1644 while len(h) == 20:
1643 while len(h) == 20:
1645 heads.append(h)
1644 heads.append(h)
1646 h = inpart.read(20)
1645 h = inpart.read(20)
1647 assert not h
1646 assert not h
1648 # Trigger a transaction so that we are guaranteed to have the lock now.
1647 # Trigger a transaction so that we are guaranteed to have the lock now.
1649 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1648 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1650 op.gettransaction()
1649 op.gettransaction()
1651 if sorted(heads) != sorted(op.repo.heads()):
1650 if sorted(heads) != sorted(op.repo.heads()):
1652 raise error.PushRaced('repository changed while pushing - '
1651 raise error.PushRaced('repository changed while pushing - '
1653 'please try again')
1652 'please try again')
1654
1653
1655 @parthandler('check:updated-heads')
1654 @parthandler('check:updated-heads')
1656 def handlecheckupdatedheads(op, inpart):
1655 def handlecheckupdatedheads(op, inpart):
1657 """check for race on the heads touched by a push
1656 """check for race on the heads touched by a push
1658
1657
1659 This is similar to 'check:heads' but focus on the heads actually updated
1658 This is similar to 'check:heads' but focus on the heads actually updated
1660 during the push. If other activities happen on unrelated heads, it is
1659 during the push. If other activities happen on unrelated heads, it is
1661 ignored.
1660 ignored.
1662
1661
1663 This allow server with high traffic to avoid push contention as long as
1662 This allow server with high traffic to avoid push contention as long as
1664 unrelated parts of the graph are involved."""
1663 unrelated parts of the graph are involved."""
1665 h = inpart.read(20)
1664 h = inpart.read(20)
1666 heads = []
1665 heads = []
1667 while len(h) == 20:
1666 while len(h) == 20:
1668 heads.append(h)
1667 heads.append(h)
1669 h = inpart.read(20)
1668 h = inpart.read(20)
1670 assert not h
1669 assert not h
1671 # trigger a transaction so that we are guaranteed to have the lock now.
1670 # trigger a transaction so that we are guaranteed to have the lock now.
1672 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1671 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1673 op.gettransaction()
1672 op.gettransaction()
1674
1673
1675 currentheads = set()
1674 currentheads = set()
1676 for ls in op.repo.branchmap().itervalues():
1675 for ls in op.repo.branchmap().itervalues():
1677 currentheads.update(ls)
1676 currentheads.update(ls)
1678
1677
1679 for h in heads:
1678 for h in heads:
1680 if h not in currentheads:
1679 if h not in currentheads:
1681 raise error.PushRaced('repository changed while pushing - '
1680 raise error.PushRaced('repository changed while pushing - '
1682 'please try again')
1681 'please try again')
1683
1682
1684 @parthandler('output')
1683 @parthandler('output')
1685 def handleoutput(op, inpart):
1684 def handleoutput(op, inpart):
1686 """forward output captured on the server to the client"""
1685 """forward output captured on the server to the client"""
1687 for line in inpart.read().splitlines():
1686 for line in inpart.read().splitlines():
1688 op.ui.status(_('remote: %s\n') % line)
1687 op.ui.status(_('remote: %s\n') % line)
1689
1688
1690 @parthandler('replycaps')
1689 @parthandler('replycaps')
1691 def handlereplycaps(op, inpart):
1690 def handlereplycaps(op, inpart):
1692 """Notify that a reply bundle should be created
1691 """Notify that a reply bundle should be created
1693
1692
1694 The payload contains the capabilities information for the reply"""
1693 The payload contains the capabilities information for the reply"""
1695 caps = decodecaps(inpart.read())
1694 caps = decodecaps(inpart.read())
1696 if op.reply is None:
1695 if op.reply is None:
1697 op.reply = bundle20(op.ui, caps)
1696 op.reply = bundle20(op.ui, caps)
1698
1697
1699 class AbortFromPart(error.Abort):
1698 class AbortFromPart(error.Abort):
1700 """Sub-class of Abort that denotes an error from a bundle2 part."""
1699 """Sub-class of Abort that denotes an error from a bundle2 part."""
1701
1700
1702 @parthandler('error:abort', ('message', 'hint'))
1701 @parthandler('error:abort', ('message', 'hint'))
1703 def handleerrorabort(op, inpart):
1702 def handleerrorabort(op, inpart):
1704 """Used to transmit abort error over the wire"""
1703 """Used to transmit abort error over the wire"""
1705 raise AbortFromPart(inpart.params['message'],
1704 raise AbortFromPart(inpart.params['message'],
1706 hint=inpart.params.get('hint'))
1705 hint=inpart.params.get('hint'))
1707
1706
1708 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
1707 @parthandler('error:pushkey', ('namespace', 'key', 'new', 'old', 'ret',
1709 'in-reply-to'))
1708 'in-reply-to'))
1710 def handleerrorpushkey(op, inpart):
1709 def handleerrorpushkey(op, inpart):
1711 """Used to transmit failure of a mandatory pushkey over the wire"""
1710 """Used to transmit failure of a mandatory pushkey over the wire"""
1712 kwargs = {}
1711 kwargs = {}
1713 for name in ('namespace', 'key', 'new', 'old', 'ret'):
1712 for name in ('namespace', 'key', 'new', 'old', 'ret'):
1714 value = inpart.params.get(name)
1713 value = inpart.params.get(name)
1715 if value is not None:
1714 if value is not None:
1716 kwargs[name] = value
1715 kwargs[name] = value
1717 raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs)
1716 raise error.PushkeyFailed(inpart.params['in-reply-to'], **kwargs)
1718
1717
1719 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1718 @parthandler('error:unsupportedcontent', ('parttype', 'params'))
1720 def handleerrorunsupportedcontent(op, inpart):
1719 def handleerrorunsupportedcontent(op, inpart):
1721 """Used to transmit unknown content error over the wire"""
1720 """Used to transmit unknown content error over the wire"""
1722 kwargs = {}
1721 kwargs = {}
1723 parttype = inpart.params.get('parttype')
1722 parttype = inpart.params.get('parttype')
1724 if parttype is not None:
1723 if parttype is not None:
1725 kwargs['parttype'] = parttype
1724 kwargs['parttype'] = parttype
1726 params = inpart.params.get('params')
1725 params = inpart.params.get('params')
1727 if params is not None:
1726 if params is not None:
1728 kwargs['params'] = params.split('\0')
1727 kwargs['params'] = params.split('\0')
1729
1728
1730 raise error.BundleUnknownFeatureError(**kwargs)
1729 raise error.BundleUnknownFeatureError(**kwargs)
1731
1730
1732 @parthandler('error:pushraced', ('message',))
1731 @parthandler('error:pushraced', ('message',))
1733 def handleerrorpushraced(op, inpart):
1732 def handleerrorpushraced(op, inpart):
1734 """Used to transmit push race error over the wire"""
1733 """Used to transmit push race error over the wire"""
1735 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1734 raise error.ResponseError(_('push failed:'), inpart.params['message'])
1736
1735
1737 @parthandler('listkeys', ('namespace',))
1736 @parthandler('listkeys', ('namespace',))
1738 def handlelistkeys(op, inpart):
1737 def handlelistkeys(op, inpart):
1739 """retrieve pushkey namespace content stored in a bundle2"""
1738 """retrieve pushkey namespace content stored in a bundle2"""
1740 namespace = inpart.params['namespace']
1739 namespace = inpart.params['namespace']
1741 r = pushkey.decodekeys(inpart.read())
1740 r = pushkey.decodekeys(inpart.read())
1742 op.records.add('listkeys', (namespace, r))
1741 op.records.add('listkeys', (namespace, r))
1743
1742
1744 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1743 @parthandler('pushkey', ('namespace', 'key', 'old', 'new'))
1745 def handlepushkey(op, inpart):
1744 def handlepushkey(op, inpart):
1746 """process a pushkey request"""
1745 """process a pushkey request"""
1747 dec = pushkey.decode
1746 dec = pushkey.decode
1748 namespace = dec(inpart.params['namespace'])
1747 namespace = dec(inpart.params['namespace'])
1749 key = dec(inpart.params['key'])
1748 key = dec(inpart.params['key'])
1750 old = dec(inpart.params['old'])
1749 old = dec(inpart.params['old'])
1751 new = dec(inpart.params['new'])
1750 new = dec(inpart.params['new'])
1752 # Grab the transaction to ensure that we have the lock before performing the
1751 # Grab the transaction to ensure that we have the lock before performing the
1753 # pushkey.
1752 # pushkey.
1754 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1753 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1755 op.gettransaction()
1754 op.gettransaction()
1756 ret = op.repo.pushkey(namespace, key, old, new)
1755 ret = op.repo.pushkey(namespace, key, old, new)
1757 record = {'namespace': namespace,
1756 record = {'namespace': namespace,
1758 'key': key,
1757 'key': key,
1759 'old': old,
1758 'old': old,
1760 'new': new}
1759 'new': new}
1761 op.records.add('pushkey', record)
1760 op.records.add('pushkey', record)
1762 if op.reply is not None:
1761 if op.reply is not None:
1763 rpart = op.reply.newpart('reply:pushkey')
1762 rpart = op.reply.newpart('reply:pushkey')
1764 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1763 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1765 rpart.addparam('return', '%i' % ret, mandatory=False)
1764 rpart.addparam('return', '%i' % ret, mandatory=False)
1766 if inpart.mandatory and not ret:
1765 if inpart.mandatory and not ret:
1767 kwargs = {}
1766 kwargs = {}
1768 for key in ('namespace', 'key', 'new', 'old', 'ret'):
1767 for key in ('namespace', 'key', 'new', 'old', 'ret'):
1769 if key in inpart.params:
1768 if key in inpart.params:
1770 kwargs[key] = inpart.params[key]
1769 kwargs[key] = inpart.params[key]
1771 raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
1770 raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
1772
1771
1773 def _readphaseheads(inpart):
1772 def _readphaseheads(inpart):
1774 headsbyphase = [[] for i in phases.allphases]
1773 headsbyphase = [[] for i in phases.allphases]
1775 entrysize = struct.calcsize(_fphasesentry)
1774 entrysize = struct.calcsize(_fphasesentry)
1776 while True:
1775 while True:
1777 entry = inpart.read(entrysize)
1776 entry = inpart.read(entrysize)
1778 if len(entry) < entrysize:
1777 if len(entry) < entrysize:
1779 if entry:
1778 if entry:
1780 raise error.Abort(_('bad phase-heads bundle part'))
1779 raise error.Abort(_('bad phase-heads bundle part'))
1781 break
1780 break
1782 phase, node = struct.unpack(_fphasesentry, entry)
1781 phase, node = struct.unpack(_fphasesentry, entry)
1783 headsbyphase[phase].append(node)
1782 headsbyphase[phase].append(node)
1784 return headsbyphase
1783 return headsbyphase
1785
1784
1786 @parthandler('phase-heads')
1785 @parthandler('phase-heads')
1787 def handlephases(op, inpart):
1786 def handlephases(op, inpart):
1788 """apply phases from bundle part to repo"""
1787 """apply phases from bundle part to repo"""
1789 headsbyphase = _readphaseheads(inpart)
1788 headsbyphase = _readphaseheads(inpart)
1790 phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
1789 phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
1791
1790
1792 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1791 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
1793 def handlepushkeyreply(op, inpart):
1792 def handlepushkeyreply(op, inpart):
1794 """retrieve the result of a pushkey request"""
1793 """retrieve the result of a pushkey request"""
1795 ret = int(inpart.params['return'])
1794 ret = int(inpart.params['return'])
1796 partid = int(inpart.params['in-reply-to'])
1795 partid = int(inpart.params['in-reply-to'])
1797 op.records.add('pushkey', {'return': ret}, partid)
1796 op.records.add('pushkey', {'return': ret}, partid)
1798
1797
1799 @parthandler('obsmarkers')
1798 @parthandler('obsmarkers')
1800 def handleobsmarker(op, inpart):
1799 def handleobsmarker(op, inpart):
1801 """add a stream of obsmarkers to the repo"""
1800 """add a stream of obsmarkers to the repo"""
1802 tr = op.gettransaction()
1801 tr = op.gettransaction()
1803 markerdata = inpart.read()
1802 markerdata = inpart.read()
1804 if op.ui.config('experimental', 'obsmarkers-exchange-debug'):
1803 if op.ui.config('experimental', 'obsmarkers-exchange-debug'):
1805 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1804 op.ui.write(('obsmarker-exchange: %i bytes received\n')
1806 % len(markerdata))
1805 % len(markerdata))
1807 # The mergemarkers call will crash if marker creation is not enabled.
1806 # The mergemarkers call will crash if marker creation is not enabled.
1808 # we want to avoid this if the part is advisory.
1807 # we want to avoid this if the part is advisory.
1809 if not inpart.mandatory and op.repo.obsstore.readonly:
1808 if not inpart.mandatory and op.repo.obsstore.readonly:
1810 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled')
1809 op.repo.ui.debug('ignoring obsolescence markers, feature not enabled')
1811 return
1810 return
1812 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1811 new = op.repo.obsstore.mergemarkers(tr, markerdata)
1813 op.repo.invalidatevolatilesets()
1812 op.repo.invalidatevolatilesets()
1814 if new:
1813 if new:
1815 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1814 op.repo.ui.status(_('%i new obsolescence markers\n') % new)
1816 op.records.add('obsmarkers', {'new': new})
1815 op.records.add('obsmarkers', {'new': new})
1817 scmutil.registersummarycallback(op.repo, tr)
1818 if op.reply is not None:
1816 if op.reply is not None:
1819 rpart = op.reply.newpart('reply:obsmarkers')
1817 rpart = op.reply.newpart('reply:obsmarkers')
1820 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1818 rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
1821 rpart.addparam('new', '%i' % new, mandatory=False)
1819 rpart.addparam('new', '%i' % new, mandatory=False)
1822
1820
1823
1821
1824 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1822 @parthandler('reply:obsmarkers', ('new', 'in-reply-to'))
1825 def handleobsmarkerreply(op, inpart):
1823 def handleobsmarkerreply(op, inpart):
1826 """retrieve the result of a pushkey request"""
1824 """retrieve the result of a pushkey request"""
1827 ret = int(inpart.params['new'])
1825 ret = int(inpart.params['new'])
1828 partid = int(inpart.params['in-reply-to'])
1826 partid = int(inpart.params['in-reply-to'])
1829 op.records.add('obsmarkers', {'new': ret}, partid)
1827 op.records.add('obsmarkers', {'new': ret}, partid)
1830
1828
1831 @parthandler('hgtagsfnodes')
1829 @parthandler('hgtagsfnodes')
1832 def handlehgtagsfnodes(op, inpart):
1830 def handlehgtagsfnodes(op, inpart):
1833 """Applies .hgtags fnodes cache entries to the local repo.
1831 """Applies .hgtags fnodes cache entries to the local repo.
1834
1832
1835 Payload is pairs of 20 byte changeset nodes and filenodes.
1833 Payload is pairs of 20 byte changeset nodes and filenodes.
1836 """
1834 """
1837 # Grab the transaction so we ensure that we have the lock at this point.
1835 # Grab the transaction so we ensure that we have the lock at this point.
1838 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1836 if op.ui.configbool('experimental', 'bundle2lazylocking'):
1839 op.gettransaction()
1837 op.gettransaction()
1840 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
1838 cache = tags.hgtagsfnodescache(op.repo.unfiltered())
1841
1839
1842 count = 0
1840 count = 0
1843 while True:
1841 while True:
1844 node = inpart.read(20)
1842 node = inpart.read(20)
1845 fnode = inpart.read(20)
1843 fnode = inpart.read(20)
1846 if len(node) < 20 or len(fnode) < 20:
1844 if len(node) < 20 or len(fnode) < 20:
1847 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
1845 op.ui.debug('ignoring incomplete received .hgtags fnodes data\n')
1848 break
1846 break
1849 cache.setfnode(node, fnode)
1847 cache.setfnode(node, fnode)
1850 count += 1
1848 count += 1
1851
1849
1852 cache.write()
1850 cache.write()
1853 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
1851 op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
@@ -1,2254 +1,2256 b''
1 # localrepo.py - read/write repository class for mercurial
1 # localrepo.py - read/write repository class for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import inspect
12 import inspect
13 import os
13 import os
14 import random
14 import random
15 import time
15 import time
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 hex,
20 hex,
21 nullid,
21 nullid,
22 short,
22 short,
23 )
23 )
24 from . import (
24 from . import (
25 bookmarks,
25 bookmarks,
26 branchmap,
26 branchmap,
27 bundle2,
27 bundle2,
28 changegroup,
28 changegroup,
29 changelog,
29 changelog,
30 color,
30 color,
31 context,
31 context,
32 dirstate,
32 dirstate,
33 dirstateguard,
33 dirstateguard,
34 encoding,
34 encoding,
35 error,
35 error,
36 exchange,
36 exchange,
37 extensions,
37 extensions,
38 filelog,
38 filelog,
39 hook,
39 hook,
40 lock as lockmod,
40 lock as lockmod,
41 manifest,
41 manifest,
42 match as matchmod,
42 match as matchmod,
43 merge as mergemod,
43 merge as mergemod,
44 mergeutil,
44 mergeutil,
45 namespaces,
45 namespaces,
46 obsolete,
46 obsolete,
47 pathutil,
47 pathutil,
48 peer,
48 peer,
49 phases,
49 phases,
50 pushkey,
50 pushkey,
51 pycompat,
51 pycompat,
52 repoview,
52 repoview,
53 revset,
53 revset,
54 revsetlang,
54 revsetlang,
55 scmutil,
55 scmutil,
56 sparse,
56 sparse,
57 store,
57 store,
58 subrepo,
58 subrepo,
59 tags as tagsmod,
59 tags as tagsmod,
60 transaction,
60 transaction,
61 txnutil,
61 txnutil,
62 util,
62 util,
63 vfs as vfsmod,
63 vfs as vfsmod,
64 )
64 )
65
65
66 release = lockmod.release
66 release = lockmod.release
67 urlerr = util.urlerr
67 urlerr = util.urlerr
68 urlreq = util.urlreq
68 urlreq = util.urlreq
69
69
70 # set of (path, vfs-location) tuples. vfs-location is:
70 # set of (path, vfs-location) tuples. vfs-location is:
71 # - 'plain for vfs relative paths
71 # - 'plain for vfs relative paths
72 # - '' for svfs relative paths
72 # - '' for svfs relative paths
73 _cachedfiles = set()
73 _cachedfiles = set()
74
74
75 class _basefilecache(scmutil.filecache):
75 class _basefilecache(scmutil.filecache):
76 """All filecache usage on repo are done for logic that should be unfiltered
76 """All filecache usage on repo are done for logic that should be unfiltered
77 """
77 """
78 def __get__(self, repo, type=None):
78 def __get__(self, repo, type=None):
79 if repo is None:
79 if repo is None:
80 return self
80 return self
81 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
81 return super(_basefilecache, self).__get__(repo.unfiltered(), type)
82 def __set__(self, repo, value):
82 def __set__(self, repo, value):
83 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
83 return super(_basefilecache, self).__set__(repo.unfiltered(), value)
84 def __delete__(self, repo):
84 def __delete__(self, repo):
85 return super(_basefilecache, self).__delete__(repo.unfiltered())
85 return super(_basefilecache, self).__delete__(repo.unfiltered())
86
86
87 class repofilecache(_basefilecache):
87 class repofilecache(_basefilecache):
88 """filecache for files in .hg but outside of .hg/store"""
88 """filecache for files in .hg but outside of .hg/store"""
89 def __init__(self, *paths):
89 def __init__(self, *paths):
90 super(repofilecache, self).__init__(*paths)
90 super(repofilecache, self).__init__(*paths)
91 for path in paths:
91 for path in paths:
92 _cachedfiles.add((path, 'plain'))
92 _cachedfiles.add((path, 'plain'))
93
93
94 def join(self, obj, fname):
94 def join(self, obj, fname):
95 return obj.vfs.join(fname)
95 return obj.vfs.join(fname)
96
96
97 class storecache(_basefilecache):
97 class storecache(_basefilecache):
98 """filecache for files in the store"""
98 """filecache for files in the store"""
99 def __init__(self, *paths):
99 def __init__(self, *paths):
100 super(storecache, self).__init__(*paths)
100 super(storecache, self).__init__(*paths)
101 for path in paths:
101 for path in paths:
102 _cachedfiles.add((path, ''))
102 _cachedfiles.add((path, ''))
103
103
104 def join(self, obj, fname):
104 def join(self, obj, fname):
105 return obj.sjoin(fname)
105 return obj.sjoin(fname)
106
106
107 def isfilecached(repo, name):
107 def isfilecached(repo, name):
108 """check if a repo has already cached "name" filecache-ed property
108 """check if a repo has already cached "name" filecache-ed property
109
109
110 This returns (cachedobj-or-None, iscached) tuple.
110 This returns (cachedobj-or-None, iscached) tuple.
111 """
111 """
112 cacheentry = repo.unfiltered()._filecache.get(name, None)
112 cacheentry = repo.unfiltered()._filecache.get(name, None)
113 if not cacheentry:
113 if not cacheentry:
114 return None, False
114 return None, False
115 return cacheentry.obj, True
115 return cacheentry.obj, True
116
116
117 class unfilteredpropertycache(util.propertycache):
117 class unfilteredpropertycache(util.propertycache):
118 """propertycache that apply to unfiltered repo only"""
118 """propertycache that apply to unfiltered repo only"""
119
119
120 def __get__(self, repo, type=None):
120 def __get__(self, repo, type=None):
121 unfi = repo.unfiltered()
121 unfi = repo.unfiltered()
122 if unfi is repo:
122 if unfi is repo:
123 return super(unfilteredpropertycache, self).__get__(unfi)
123 return super(unfilteredpropertycache, self).__get__(unfi)
124 return getattr(unfi, self.name)
124 return getattr(unfi, self.name)
125
125
126 class filteredpropertycache(util.propertycache):
126 class filteredpropertycache(util.propertycache):
127 """propertycache that must take filtering in account"""
127 """propertycache that must take filtering in account"""
128
128
129 def cachevalue(self, obj, value):
129 def cachevalue(self, obj, value):
130 object.__setattr__(obj, self.name, value)
130 object.__setattr__(obj, self.name, value)
131
131
132
132
133 def hasunfilteredcache(repo, name):
133 def hasunfilteredcache(repo, name):
134 """check if a repo has an unfilteredpropertycache value for <name>"""
134 """check if a repo has an unfilteredpropertycache value for <name>"""
135 return name in vars(repo.unfiltered())
135 return name in vars(repo.unfiltered())
136
136
137 def unfilteredmethod(orig):
137 def unfilteredmethod(orig):
138 """decorate method that always need to be run on unfiltered version"""
138 """decorate method that always need to be run on unfiltered version"""
139 def wrapper(repo, *args, **kwargs):
139 def wrapper(repo, *args, **kwargs):
140 return orig(repo.unfiltered(), *args, **kwargs)
140 return orig(repo.unfiltered(), *args, **kwargs)
141 return wrapper
141 return wrapper
142
142
143 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
143 moderncaps = {'lookup', 'branchmap', 'pushkey', 'known', 'getbundle',
144 'unbundle'}
144 'unbundle'}
145 legacycaps = moderncaps.union({'changegroupsubset'})
145 legacycaps = moderncaps.union({'changegroupsubset'})
146
146
147 class localpeer(peer.peerrepository):
147 class localpeer(peer.peerrepository):
148 '''peer for a local repo; reflects only the most recent API'''
148 '''peer for a local repo; reflects only the most recent API'''
149
149
150 def __init__(self, repo, caps=None):
150 def __init__(self, repo, caps=None):
151 if caps is None:
151 if caps is None:
152 caps = moderncaps.copy()
152 caps = moderncaps.copy()
153 peer.peerrepository.__init__(self)
153 peer.peerrepository.__init__(self)
154 self._repo = repo.filtered('served')
154 self._repo = repo.filtered('served')
155 self.ui = repo.ui
155 self.ui = repo.ui
156 self._caps = repo._restrictcapabilities(caps)
156 self._caps = repo._restrictcapabilities(caps)
157 self.requirements = repo.requirements
157 self.requirements = repo.requirements
158 self.supportedformats = repo.supportedformats
158 self.supportedformats = repo.supportedformats
159
159
160 def close(self):
160 def close(self):
161 self._repo.close()
161 self._repo.close()
162
162
163 def _capabilities(self):
163 def _capabilities(self):
164 return self._caps
164 return self._caps
165
165
166 def local(self):
166 def local(self):
167 return self._repo
167 return self._repo
168
168
169 def canpush(self):
169 def canpush(self):
170 return True
170 return True
171
171
172 def url(self):
172 def url(self):
173 return self._repo.url()
173 return self._repo.url()
174
174
175 def lookup(self, key):
175 def lookup(self, key):
176 return self._repo.lookup(key)
176 return self._repo.lookup(key)
177
177
178 def branchmap(self):
178 def branchmap(self):
179 return self._repo.branchmap()
179 return self._repo.branchmap()
180
180
181 def heads(self):
181 def heads(self):
182 return self._repo.heads()
182 return self._repo.heads()
183
183
184 def known(self, nodes):
184 def known(self, nodes):
185 return self._repo.known(nodes)
185 return self._repo.known(nodes)
186
186
187 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
187 def getbundle(self, source, heads=None, common=None, bundlecaps=None,
188 **kwargs):
188 **kwargs):
189 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
189 chunks = exchange.getbundlechunks(self._repo, source, heads=heads,
190 common=common, bundlecaps=bundlecaps,
190 common=common, bundlecaps=bundlecaps,
191 **kwargs)
191 **kwargs)
192 cb = util.chunkbuffer(chunks)
192 cb = util.chunkbuffer(chunks)
193
193
194 if exchange.bundle2requested(bundlecaps):
194 if exchange.bundle2requested(bundlecaps):
195 # When requesting a bundle2, getbundle returns a stream to make the
195 # When requesting a bundle2, getbundle returns a stream to make the
196 # wire level function happier. We need to build a proper object
196 # wire level function happier. We need to build a proper object
197 # from it in local peer.
197 # from it in local peer.
198 return bundle2.getunbundler(self.ui, cb)
198 return bundle2.getunbundler(self.ui, cb)
199 else:
199 else:
200 return changegroup.getunbundler('01', cb, None)
200 return changegroup.getunbundler('01', cb, None)
201
201
202 # TODO We might want to move the next two calls into legacypeer and add
202 # TODO We might want to move the next two calls into legacypeer and add
203 # unbundle instead.
203 # unbundle instead.
204
204
205 def unbundle(self, cg, heads, url):
205 def unbundle(self, cg, heads, url):
206 """apply a bundle on a repo
206 """apply a bundle on a repo
207
207
208 This function handles the repo locking itself."""
208 This function handles the repo locking itself."""
209 try:
209 try:
210 try:
210 try:
211 cg = exchange.readbundle(self.ui, cg, None)
211 cg = exchange.readbundle(self.ui, cg, None)
212 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
212 ret = exchange.unbundle(self._repo, cg, heads, 'push', url)
213 if util.safehasattr(ret, 'getchunks'):
213 if util.safehasattr(ret, 'getchunks'):
214 # This is a bundle20 object, turn it into an unbundler.
214 # This is a bundle20 object, turn it into an unbundler.
215 # This little dance should be dropped eventually when the
215 # This little dance should be dropped eventually when the
216 # API is finally improved.
216 # API is finally improved.
217 stream = util.chunkbuffer(ret.getchunks())
217 stream = util.chunkbuffer(ret.getchunks())
218 ret = bundle2.getunbundler(self.ui, stream)
218 ret = bundle2.getunbundler(self.ui, stream)
219 return ret
219 return ret
220 except Exception as exc:
220 except Exception as exc:
221 # If the exception contains output salvaged from a bundle2
221 # If the exception contains output salvaged from a bundle2
222 # reply, we need to make sure it is printed before continuing
222 # reply, we need to make sure it is printed before continuing
223 # to fail. So we build a bundle2 with such output and consume
223 # to fail. So we build a bundle2 with such output and consume
224 # it directly.
224 # it directly.
225 #
225 #
226 # This is not very elegant but allows a "simple" solution for
226 # This is not very elegant but allows a "simple" solution for
227 # issue4594
227 # issue4594
228 output = getattr(exc, '_bundle2salvagedoutput', ())
228 output = getattr(exc, '_bundle2salvagedoutput', ())
229 if output:
229 if output:
230 bundler = bundle2.bundle20(self._repo.ui)
230 bundler = bundle2.bundle20(self._repo.ui)
231 for out in output:
231 for out in output:
232 bundler.addpart(out)
232 bundler.addpart(out)
233 stream = util.chunkbuffer(bundler.getchunks())
233 stream = util.chunkbuffer(bundler.getchunks())
234 b = bundle2.getunbundler(self.ui, stream)
234 b = bundle2.getunbundler(self.ui, stream)
235 bundle2.processbundle(self._repo, b)
235 bundle2.processbundle(self._repo, b)
236 raise
236 raise
237 except error.PushRaced as exc:
237 except error.PushRaced as exc:
238 raise error.ResponseError(_('push failed:'), str(exc))
238 raise error.ResponseError(_('push failed:'), str(exc))
239
239
240 def lock(self):
240 def lock(self):
241 return self._repo.lock()
241 return self._repo.lock()
242
242
243 def pushkey(self, namespace, key, old, new):
243 def pushkey(self, namespace, key, old, new):
244 return self._repo.pushkey(namespace, key, old, new)
244 return self._repo.pushkey(namespace, key, old, new)
245
245
246 def listkeys(self, namespace):
246 def listkeys(self, namespace):
247 return self._repo.listkeys(namespace)
247 return self._repo.listkeys(namespace)
248
248
249 def debugwireargs(self, one, two, three=None, four=None, five=None):
249 def debugwireargs(self, one, two, three=None, four=None, five=None):
250 '''used to test argument passing over the wire'''
250 '''used to test argument passing over the wire'''
251 return "%s %s %s %s %s" % (one, two, three, four, five)
251 return "%s %s %s %s %s" % (one, two, three, four, five)
252
252
253 class locallegacypeer(localpeer):
253 class locallegacypeer(localpeer):
254 '''peer extension which implements legacy methods too; used for tests with
254 '''peer extension which implements legacy methods too; used for tests with
255 restricted capabilities'''
255 restricted capabilities'''
256
256
257 def __init__(self, repo):
257 def __init__(self, repo):
258 localpeer.__init__(self, repo, caps=legacycaps)
258 localpeer.__init__(self, repo, caps=legacycaps)
259
259
260 def branches(self, nodes):
260 def branches(self, nodes):
261 return self._repo.branches(nodes)
261 return self._repo.branches(nodes)
262
262
263 def between(self, pairs):
263 def between(self, pairs):
264 return self._repo.between(pairs)
264 return self._repo.between(pairs)
265
265
266 def changegroup(self, basenodes, source):
266 def changegroup(self, basenodes, source):
267 return changegroup.changegroup(self._repo, basenodes, source)
267 return changegroup.changegroup(self._repo, basenodes, source)
268
268
269 def changegroupsubset(self, bases, heads, source):
269 def changegroupsubset(self, bases, heads, source):
270 return changegroup.changegroupsubset(self._repo, bases, heads, source)
270 return changegroup.changegroupsubset(self._repo, bases, heads, source)
271
271
272 # Increment the sub-version when the revlog v2 format changes to lock out old
272 # Increment the sub-version when the revlog v2 format changes to lock out old
273 # clients.
273 # clients.
274 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
274 REVLOGV2_REQUIREMENT = 'exp-revlogv2.0'
275
275
276 class localrepository(object):
276 class localrepository(object):
277
277
278 supportedformats = {
278 supportedformats = {
279 'revlogv1',
279 'revlogv1',
280 'generaldelta',
280 'generaldelta',
281 'treemanifest',
281 'treemanifest',
282 'manifestv2',
282 'manifestv2',
283 REVLOGV2_REQUIREMENT,
283 REVLOGV2_REQUIREMENT,
284 }
284 }
285 _basesupported = supportedformats | {
285 _basesupported = supportedformats | {
286 'store',
286 'store',
287 'fncache',
287 'fncache',
288 'shared',
288 'shared',
289 'relshared',
289 'relshared',
290 'dotencode',
290 'dotencode',
291 }
291 }
292 openerreqs = {
292 openerreqs = {
293 'revlogv1',
293 'revlogv1',
294 'generaldelta',
294 'generaldelta',
295 'treemanifest',
295 'treemanifest',
296 'manifestv2',
296 'manifestv2',
297 }
297 }
298
298
299 # a list of (ui, featureset) functions.
299 # a list of (ui, featureset) functions.
300 # only functions defined in module of enabled extensions are invoked
300 # only functions defined in module of enabled extensions are invoked
301 featuresetupfuncs = set()
301 featuresetupfuncs = set()
302
302
303 # list of prefix for file which can be written without 'wlock'
303 # list of prefix for file which can be written without 'wlock'
304 # Extensions should extend this list when needed
304 # Extensions should extend this list when needed
305 _wlockfreeprefix = {
305 _wlockfreeprefix = {
306 # We migh consider requiring 'wlock' for the next
306 # We migh consider requiring 'wlock' for the next
307 # two, but pretty much all the existing code assume
307 # two, but pretty much all the existing code assume
308 # wlock is not needed so we keep them excluded for
308 # wlock is not needed so we keep them excluded for
309 # now.
309 # now.
310 'hgrc',
310 'hgrc',
311 'requires',
311 'requires',
312 # XXX cache is a complicatged business someone
312 # XXX cache is a complicatged business someone
313 # should investigate this in depth at some point
313 # should investigate this in depth at some point
314 'cache/',
314 'cache/',
315 # XXX shouldn't be dirstate covered by the wlock?
315 # XXX shouldn't be dirstate covered by the wlock?
316 'dirstate',
316 'dirstate',
317 # XXX bisect was still a bit too messy at the time
317 # XXX bisect was still a bit too messy at the time
318 # this changeset was introduced. Someone should fix
318 # this changeset was introduced. Someone should fix
319 # the remainig bit and drop this line
319 # the remainig bit and drop this line
320 'bisect.state',
320 'bisect.state',
321 }
321 }
322
322
323 def __init__(self, baseui, path, create=False):
323 def __init__(self, baseui, path, create=False):
324 self.requirements = set()
324 self.requirements = set()
325 self.filtername = None
325 self.filtername = None
326 # wvfs: rooted at the repository root, used to access the working copy
326 # wvfs: rooted at the repository root, used to access the working copy
327 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
327 self.wvfs = vfsmod.vfs(path, expandpath=True, realpath=True)
328 # vfs: rooted at .hg, used to access repo files outside of .hg/store
328 # vfs: rooted at .hg, used to access repo files outside of .hg/store
329 self.vfs = None
329 self.vfs = None
330 # svfs: usually rooted at .hg/store, used to access repository history
330 # svfs: usually rooted at .hg/store, used to access repository history
331 # If this is a shared repository, this vfs may point to another
331 # If this is a shared repository, this vfs may point to another
332 # repository's .hg/store directory.
332 # repository's .hg/store directory.
333 self.svfs = None
333 self.svfs = None
334 self.root = self.wvfs.base
334 self.root = self.wvfs.base
335 self.path = self.wvfs.join(".hg")
335 self.path = self.wvfs.join(".hg")
336 self.origroot = path
336 self.origroot = path
337 # These auditor are not used by the vfs,
337 # These auditor are not used by the vfs,
338 # only used when writing this comment: basectx.match
338 # only used when writing this comment: basectx.match
339 self.auditor = pathutil.pathauditor(self.root, self._checknested)
339 self.auditor = pathutil.pathauditor(self.root, self._checknested)
340 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
340 self.nofsauditor = pathutil.pathauditor(self.root, self._checknested,
341 realfs=False)
341 realfs=False)
342 self.baseui = baseui
342 self.baseui = baseui
343 self.ui = baseui.copy()
343 self.ui = baseui.copy()
344 self.ui.copy = baseui.copy # prevent copying repo configuration
344 self.ui.copy = baseui.copy # prevent copying repo configuration
345 self.vfs = vfsmod.vfs(self.path)
345 self.vfs = vfsmod.vfs(self.path)
346 if (self.ui.configbool('devel', 'all-warnings') or
346 if (self.ui.configbool('devel', 'all-warnings') or
347 self.ui.configbool('devel', 'check-locks')):
347 self.ui.configbool('devel', 'check-locks')):
348 self.vfs.audit = self._getvfsward(self.vfs.audit)
348 self.vfs.audit = self._getvfsward(self.vfs.audit)
349 # A list of callback to shape the phase if no data were found.
349 # A list of callback to shape the phase if no data were found.
350 # Callback are in the form: func(repo, roots) --> processed root.
350 # Callback are in the form: func(repo, roots) --> processed root.
351 # This list it to be filled by extension during repo setup
351 # This list it to be filled by extension during repo setup
352 self._phasedefaults = []
352 self._phasedefaults = []
353 try:
353 try:
354 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
354 self.ui.readconfig(self.vfs.join("hgrc"), self.root)
355 self._loadextensions()
355 self._loadextensions()
356 except IOError:
356 except IOError:
357 pass
357 pass
358
358
359 if self.featuresetupfuncs:
359 if self.featuresetupfuncs:
360 self.supported = set(self._basesupported) # use private copy
360 self.supported = set(self._basesupported) # use private copy
361 extmods = set(m.__name__ for n, m
361 extmods = set(m.__name__ for n, m
362 in extensions.extensions(self.ui))
362 in extensions.extensions(self.ui))
363 for setupfunc in self.featuresetupfuncs:
363 for setupfunc in self.featuresetupfuncs:
364 if setupfunc.__module__ in extmods:
364 if setupfunc.__module__ in extmods:
365 setupfunc(self.ui, self.supported)
365 setupfunc(self.ui, self.supported)
366 else:
366 else:
367 self.supported = self._basesupported
367 self.supported = self._basesupported
368 color.setup(self.ui)
368 color.setup(self.ui)
369
369
370 # Add compression engines.
370 # Add compression engines.
371 for name in util.compengines:
371 for name in util.compengines:
372 engine = util.compengines[name]
372 engine = util.compengines[name]
373 if engine.revlogheader():
373 if engine.revlogheader():
374 self.supported.add('exp-compression-%s' % name)
374 self.supported.add('exp-compression-%s' % name)
375
375
376 if not self.vfs.isdir():
376 if not self.vfs.isdir():
377 if create:
377 if create:
378 self.requirements = newreporequirements(self)
378 self.requirements = newreporequirements(self)
379
379
380 if not self.wvfs.exists():
380 if not self.wvfs.exists():
381 self.wvfs.makedirs()
381 self.wvfs.makedirs()
382 self.vfs.makedir(notindexed=True)
382 self.vfs.makedir(notindexed=True)
383
383
384 if 'store' in self.requirements:
384 if 'store' in self.requirements:
385 self.vfs.mkdir("store")
385 self.vfs.mkdir("store")
386
386
387 # create an invalid changelog
387 # create an invalid changelog
388 self.vfs.append(
388 self.vfs.append(
389 "00changelog.i",
389 "00changelog.i",
390 '\0\0\0\2' # represents revlogv2
390 '\0\0\0\2' # represents revlogv2
391 ' dummy changelog to prevent using the old repo layout'
391 ' dummy changelog to prevent using the old repo layout'
392 )
392 )
393 else:
393 else:
394 raise error.RepoError(_("repository %s not found") % path)
394 raise error.RepoError(_("repository %s not found") % path)
395 elif create:
395 elif create:
396 raise error.RepoError(_("repository %s already exists") % path)
396 raise error.RepoError(_("repository %s already exists") % path)
397 else:
397 else:
398 try:
398 try:
399 self.requirements = scmutil.readrequires(
399 self.requirements = scmutil.readrequires(
400 self.vfs, self.supported)
400 self.vfs, self.supported)
401 except IOError as inst:
401 except IOError as inst:
402 if inst.errno != errno.ENOENT:
402 if inst.errno != errno.ENOENT:
403 raise
403 raise
404
404
405 cachepath = self.vfs.join('cache')
405 cachepath = self.vfs.join('cache')
406 self.sharedpath = self.path
406 self.sharedpath = self.path
407 try:
407 try:
408 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
408 sharedpath = self.vfs.read("sharedpath").rstrip('\n')
409 if 'relshared' in self.requirements:
409 if 'relshared' in self.requirements:
410 sharedpath = self.vfs.join(sharedpath)
410 sharedpath = self.vfs.join(sharedpath)
411 vfs = vfsmod.vfs(sharedpath, realpath=True)
411 vfs = vfsmod.vfs(sharedpath, realpath=True)
412 cachepath = vfs.join('cache')
412 cachepath = vfs.join('cache')
413 s = vfs.base
413 s = vfs.base
414 if not vfs.exists():
414 if not vfs.exists():
415 raise error.RepoError(
415 raise error.RepoError(
416 _('.hg/sharedpath points to nonexistent directory %s') % s)
416 _('.hg/sharedpath points to nonexistent directory %s') % s)
417 self.sharedpath = s
417 self.sharedpath = s
418 except IOError as inst:
418 except IOError as inst:
419 if inst.errno != errno.ENOENT:
419 if inst.errno != errno.ENOENT:
420 raise
420 raise
421
421
422 self.store = store.store(
422 self.store = store.store(
423 self.requirements, self.sharedpath, vfsmod.vfs)
423 self.requirements, self.sharedpath, vfsmod.vfs)
424 self.spath = self.store.path
424 self.spath = self.store.path
425 self.svfs = self.store.vfs
425 self.svfs = self.store.vfs
426 self.sjoin = self.store.join
426 self.sjoin = self.store.join
427 self.vfs.createmode = self.store.createmode
427 self.vfs.createmode = self.store.createmode
428 self.cachevfs = vfsmod.vfs(cachepath)
428 self.cachevfs = vfsmod.vfs(cachepath)
429 self.cachevfs.createmode = self.store.createmode
429 self.cachevfs.createmode = self.store.createmode
430 if (self.ui.configbool('devel', 'all-warnings') or
430 if (self.ui.configbool('devel', 'all-warnings') or
431 self.ui.configbool('devel', 'check-locks')):
431 self.ui.configbool('devel', 'check-locks')):
432 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
432 if util.safehasattr(self.svfs, 'vfs'): # this is filtervfs
433 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
433 self.svfs.vfs.audit = self._getsvfsward(self.svfs.vfs.audit)
434 else: # standard vfs
434 else: # standard vfs
435 self.svfs.audit = self._getsvfsward(self.svfs.audit)
435 self.svfs.audit = self._getsvfsward(self.svfs.audit)
436 self._applyopenerreqs()
436 self._applyopenerreqs()
437 if create:
437 if create:
438 self._writerequirements()
438 self._writerequirements()
439
439
440 self._dirstatevalidatewarned = False
440 self._dirstatevalidatewarned = False
441
441
442 self._branchcaches = {}
442 self._branchcaches = {}
443 self._revbranchcache = None
443 self._revbranchcache = None
444 self.filterpats = {}
444 self.filterpats = {}
445 self._datafilters = {}
445 self._datafilters = {}
446 self._transref = self._lockref = self._wlockref = None
446 self._transref = self._lockref = self._wlockref = None
447
447
448 # A cache for various files under .hg/ that tracks file changes,
448 # A cache for various files under .hg/ that tracks file changes,
449 # (used by the filecache decorator)
449 # (used by the filecache decorator)
450 #
450 #
451 # Maps a property name to its util.filecacheentry
451 # Maps a property name to its util.filecacheentry
452 self._filecache = {}
452 self._filecache = {}
453
453
454 # hold sets of revision to be filtered
454 # hold sets of revision to be filtered
455 # should be cleared when something might have changed the filter value:
455 # should be cleared when something might have changed the filter value:
456 # - new changesets,
456 # - new changesets,
457 # - phase change,
457 # - phase change,
458 # - new obsolescence marker,
458 # - new obsolescence marker,
459 # - working directory parent change,
459 # - working directory parent change,
460 # - bookmark changes
460 # - bookmark changes
461 self.filteredrevcache = {}
461 self.filteredrevcache = {}
462
462
463 # post-dirstate-status hooks
463 # post-dirstate-status hooks
464 self._postdsstatus = []
464 self._postdsstatus = []
465
465
466 # Cache of types representing filtered repos.
466 # Cache of types representing filtered repos.
467 self._filteredrepotypes = weakref.WeakKeyDictionary()
467 self._filteredrepotypes = weakref.WeakKeyDictionary()
468
468
469 # generic mapping between names and nodes
469 # generic mapping between names and nodes
470 self.names = namespaces.namespaces()
470 self.names = namespaces.namespaces()
471
471
472 # Key to signature value.
472 # Key to signature value.
473 self._sparsesignaturecache = {}
473 self._sparsesignaturecache = {}
474 # Signature to cached matcher instance.
474 # Signature to cached matcher instance.
475 self._sparsematchercache = {}
475 self._sparsematchercache = {}
476
476
477 def _getvfsward(self, origfunc):
477 def _getvfsward(self, origfunc):
478 """build a ward for self.vfs"""
478 """build a ward for self.vfs"""
479 rref = weakref.ref(self)
479 rref = weakref.ref(self)
480 def checkvfs(path, mode=None):
480 def checkvfs(path, mode=None):
481 ret = origfunc(path, mode=mode)
481 ret = origfunc(path, mode=mode)
482 repo = rref()
482 repo = rref()
483 if (repo is None
483 if (repo is None
484 or not util.safehasattr(repo, '_wlockref')
484 or not util.safehasattr(repo, '_wlockref')
485 or not util.safehasattr(repo, '_lockref')):
485 or not util.safehasattr(repo, '_lockref')):
486 return
486 return
487 if mode in (None, 'r', 'rb'):
487 if mode in (None, 'r', 'rb'):
488 return
488 return
489 if path.startswith(repo.path):
489 if path.startswith(repo.path):
490 # truncate name relative to the repository (.hg)
490 # truncate name relative to the repository (.hg)
491 path = path[len(repo.path) + 1:]
491 path = path[len(repo.path) + 1:]
492 if path.startswith('cache/'):
492 if path.startswith('cache/'):
493 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
493 msg = 'accessing cache with vfs instead of cachevfs: "%s"'
494 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
494 repo.ui.develwarn(msg % path, stacklevel=2, config="cache-vfs")
495 if path.startswith('journal.'):
495 if path.startswith('journal.'):
496 # journal is covered by 'lock'
496 # journal is covered by 'lock'
497 if repo._currentlock(repo._lockref) is None:
497 if repo._currentlock(repo._lockref) is None:
498 repo.ui.develwarn('write with no lock: "%s"' % path,
498 repo.ui.develwarn('write with no lock: "%s"' % path,
499 stacklevel=2, config='check-locks')
499 stacklevel=2, config='check-locks')
500 elif repo._currentlock(repo._wlockref) is None:
500 elif repo._currentlock(repo._wlockref) is None:
501 # rest of vfs files are covered by 'wlock'
501 # rest of vfs files are covered by 'wlock'
502 #
502 #
503 # exclude special files
503 # exclude special files
504 for prefix in self._wlockfreeprefix:
504 for prefix in self._wlockfreeprefix:
505 if path.startswith(prefix):
505 if path.startswith(prefix):
506 return
506 return
507 repo.ui.develwarn('write with no wlock: "%s"' % path,
507 repo.ui.develwarn('write with no wlock: "%s"' % path,
508 stacklevel=2, config='check-locks')
508 stacklevel=2, config='check-locks')
509 return ret
509 return ret
510 return checkvfs
510 return checkvfs
511
511
512 def _getsvfsward(self, origfunc):
512 def _getsvfsward(self, origfunc):
513 """build a ward for self.svfs"""
513 """build a ward for self.svfs"""
514 rref = weakref.ref(self)
514 rref = weakref.ref(self)
515 def checksvfs(path, mode=None):
515 def checksvfs(path, mode=None):
516 ret = origfunc(path, mode=mode)
516 ret = origfunc(path, mode=mode)
517 repo = rref()
517 repo = rref()
518 if repo is None or not util.safehasattr(repo, '_lockref'):
518 if repo is None or not util.safehasattr(repo, '_lockref'):
519 return
519 return
520 if mode in (None, 'r', 'rb'):
520 if mode in (None, 'r', 'rb'):
521 return
521 return
522 if path.startswith(repo.sharedpath):
522 if path.startswith(repo.sharedpath):
523 # truncate name relative to the repository (.hg)
523 # truncate name relative to the repository (.hg)
524 path = path[len(repo.sharedpath) + 1:]
524 path = path[len(repo.sharedpath) + 1:]
525 if repo._currentlock(repo._lockref) is None:
525 if repo._currentlock(repo._lockref) is None:
526 repo.ui.develwarn('write with no lock: "%s"' % path,
526 repo.ui.develwarn('write with no lock: "%s"' % path,
527 stacklevel=3)
527 stacklevel=3)
528 return ret
528 return ret
529 return checksvfs
529 return checksvfs
530
530
531 def close(self):
531 def close(self):
532 self._writecaches()
532 self._writecaches()
533
533
534 def _loadextensions(self):
534 def _loadextensions(self):
535 extensions.loadall(self.ui)
535 extensions.loadall(self.ui)
536
536
537 def _writecaches(self):
537 def _writecaches(self):
538 if self._revbranchcache:
538 if self._revbranchcache:
539 self._revbranchcache.write()
539 self._revbranchcache.write()
540
540
541 def _restrictcapabilities(self, caps):
541 def _restrictcapabilities(self, caps):
542 if self.ui.configbool('experimental', 'bundle2-advertise'):
542 if self.ui.configbool('experimental', 'bundle2-advertise'):
543 caps = set(caps)
543 caps = set(caps)
544 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
544 capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
545 caps.add('bundle2=' + urlreq.quote(capsblob))
545 caps.add('bundle2=' + urlreq.quote(capsblob))
546 return caps
546 return caps
547
547
548 def _applyopenerreqs(self):
548 def _applyopenerreqs(self):
549 self.svfs.options = dict((r, 1) for r in self.requirements
549 self.svfs.options = dict((r, 1) for r in self.requirements
550 if r in self.openerreqs)
550 if r in self.openerreqs)
551 # experimental config: format.chunkcachesize
551 # experimental config: format.chunkcachesize
552 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
552 chunkcachesize = self.ui.configint('format', 'chunkcachesize')
553 if chunkcachesize is not None:
553 if chunkcachesize is not None:
554 self.svfs.options['chunkcachesize'] = chunkcachesize
554 self.svfs.options['chunkcachesize'] = chunkcachesize
555 # experimental config: format.maxchainlen
555 # experimental config: format.maxchainlen
556 maxchainlen = self.ui.configint('format', 'maxchainlen')
556 maxchainlen = self.ui.configint('format', 'maxchainlen')
557 if maxchainlen is not None:
557 if maxchainlen is not None:
558 self.svfs.options['maxchainlen'] = maxchainlen
558 self.svfs.options['maxchainlen'] = maxchainlen
559 # experimental config: format.manifestcachesize
559 # experimental config: format.manifestcachesize
560 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
560 manifestcachesize = self.ui.configint('format', 'manifestcachesize')
561 if manifestcachesize is not None:
561 if manifestcachesize is not None:
562 self.svfs.options['manifestcachesize'] = manifestcachesize
562 self.svfs.options['manifestcachesize'] = manifestcachesize
563 # experimental config: format.aggressivemergedeltas
563 # experimental config: format.aggressivemergedeltas
564 aggressivemergedeltas = self.ui.configbool('format',
564 aggressivemergedeltas = self.ui.configbool('format',
565 'aggressivemergedeltas')
565 'aggressivemergedeltas')
566 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
566 self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
567 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
567 self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
568 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
568 chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
569 if 0 <= chainspan:
569 if 0 <= chainspan:
570 self.svfs.options['maxdeltachainspan'] = chainspan
570 self.svfs.options['maxdeltachainspan'] = chainspan
571
571
572 for r in self.requirements:
572 for r in self.requirements:
573 if r.startswith('exp-compression-'):
573 if r.startswith('exp-compression-'):
574 self.svfs.options['compengine'] = r[len('exp-compression-'):]
574 self.svfs.options['compengine'] = r[len('exp-compression-'):]
575
575
576 # TODO move "revlogv2" to openerreqs once finalized.
576 # TODO move "revlogv2" to openerreqs once finalized.
577 if REVLOGV2_REQUIREMENT in self.requirements:
577 if REVLOGV2_REQUIREMENT in self.requirements:
578 self.svfs.options['revlogv2'] = True
578 self.svfs.options['revlogv2'] = True
579
579
580 def _writerequirements(self):
580 def _writerequirements(self):
581 scmutil.writerequires(self.vfs, self.requirements)
581 scmutil.writerequires(self.vfs, self.requirements)
582
582
583 def _checknested(self, path):
583 def _checknested(self, path):
584 """Determine if path is a legal nested repository."""
584 """Determine if path is a legal nested repository."""
585 if not path.startswith(self.root):
585 if not path.startswith(self.root):
586 return False
586 return False
587 subpath = path[len(self.root) + 1:]
587 subpath = path[len(self.root) + 1:]
588 normsubpath = util.pconvert(subpath)
588 normsubpath = util.pconvert(subpath)
589
589
590 # XXX: Checking against the current working copy is wrong in
590 # XXX: Checking against the current working copy is wrong in
591 # the sense that it can reject things like
591 # the sense that it can reject things like
592 #
592 #
593 # $ hg cat -r 10 sub/x.txt
593 # $ hg cat -r 10 sub/x.txt
594 #
594 #
595 # if sub/ is no longer a subrepository in the working copy
595 # if sub/ is no longer a subrepository in the working copy
596 # parent revision.
596 # parent revision.
597 #
597 #
598 # However, it can of course also allow things that would have
598 # However, it can of course also allow things that would have
599 # been rejected before, such as the above cat command if sub/
599 # been rejected before, such as the above cat command if sub/
600 # is a subrepository now, but was a normal directory before.
600 # is a subrepository now, but was a normal directory before.
601 # The old path auditor would have rejected by mistake since it
601 # The old path auditor would have rejected by mistake since it
602 # panics when it sees sub/.hg/.
602 # panics when it sees sub/.hg/.
603 #
603 #
604 # All in all, checking against the working copy seems sensible
604 # All in all, checking against the working copy seems sensible
605 # since we want to prevent access to nested repositories on
605 # since we want to prevent access to nested repositories on
606 # the filesystem *now*.
606 # the filesystem *now*.
607 ctx = self[None]
607 ctx = self[None]
608 parts = util.splitpath(subpath)
608 parts = util.splitpath(subpath)
609 while parts:
609 while parts:
610 prefix = '/'.join(parts)
610 prefix = '/'.join(parts)
611 if prefix in ctx.substate:
611 if prefix in ctx.substate:
612 if prefix == normsubpath:
612 if prefix == normsubpath:
613 return True
613 return True
614 else:
614 else:
615 sub = ctx.sub(prefix)
615 sub = ctx.sub(prefix)
616 return sub.checknested(subpath[len(prefix) + 1:])
616 return sub.checknested(subpath[len(prefix) + 1:])
617 else:
617 else:
618 parts.pop()
618 parts.pop()
619 return False
619 return False
620
620
621 def peer(self):
621 def peer(self):
622 return localpeer(self) # not cached to avoid reference cycle
622 return localpeer(self) # not cached to avoid reference cycle
623
623
624 def unfiltered(self):
624 def unfiltered(self):
625 """Return unfiltered version of the repository
625 """Return unfiltered version of the repository
626
626
627 Intended to be overwritten by filtered repo."""
627 Intended to be overwritten by filtered repo."""
628 return self
628 return self
629
629
630 def filtered(self, name):
630 def filtered(self, name):
631 """Return a filtered version of a repository"""
631 """Return a filtered version of a repository"""
632 # Python <3.4 easily leaks types via __mro__. See
632 # Python <3.4 easily leaks types via __mro__. See
633 # https://bugs.python.org/issue17950. We cache dynamically
633 # https://bugs.python.org/issue17950. We cache dynamically
634 # created types so this method doesn't leak on every
634 # created types so this method doesn't leak on every
635 # invocation.
635 # invocation.
636
636
637 key = self.unfiltered().__class__
637 key = self.unfiltered().__class__
638 if key not in self._filteredrepotypes:
638 if key not in self._filteredrepotypes:
639 # Build a new type with the repoview mixin and the base
639 # Build a new type with the repoview mixin and the base
640 # class of this repo. Give it a name containing the
640 # class of this repo. Give it a name containing the
641 # filter name to aid debugging.
641 # filter name to aid debugging.
642 bases = (repoview.repoview, key)
642 bases = (repoview.repoview, key)
643 cls = type(r'%sfilteredrepo' % name, bases, {})
643 cls = type(r'%sfilteredrepo' % name, bases, {})
644 self._filteredrepotypes[key] = cls
644 self._filteredrepotypes[key] = cls
645
645
646 return self._filteredrepotypes[key](self, name)
646 return self._filteredrepotypes[key](self, name)
647
647
648 @repofilecache('bookmarks', 'bookmarks.current')
648 @repofilecache('bookmarks', 'bookmarks.current')
649 def _bookmarks(self):
649 def _bookmarks(self):
650 return bookmarks.bmstore(self)
650 return bookmarks.bmstore(self)
651
651
652 @property
652 @property
653 def _activebookmark(self):
653 def _activebookmark(self):
654 return self._bookmarks.active
654 return self._bookmarks.active
655
655
656 # _phaserevs and _phasesets depend on changelog. what we need is to
656 # _phaserevs and _phasesets depend on changelog. what we need is to
657 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
657 # call _phasecache.invalidate() if '00changelog.i' was changed, but it
658 # can't be easily expressed in filecache mechanism.
658 # can't be easily expressed in filecache mechanism.
659 @storecache('phaseroots', '00changelog.i')
659 @storecache('phaseroots', '00changelog.i')
660 def _phasecache(self):
660 def _phasecache(self):
661 return phases.phasecache(self, self._phasedefaults)
661 return phases.phasecache(self, self._phasedefaults)
662
662
663 @storecache('obsstore')
663 @storecache('obsstore')
664 def obsstore(self):
664 def obsstore(self):
665 return obsolete.makestore(self.ui, self)
665 return obsolete.makestore(self.ui, self)
666
666
667 @storecache('00changelog.i')
667 @storecache('00changelog.i')
668 def changelog(self):
668 def changelog(self):
669 return changelog.changelog(self.svfs,
669 return changelog.changelog(self.svfs,
670 trypending=txnutil.mayhavepending(self.root))
670 trypending=txnutil.mayhavepending(self.root))
671
671
672 def _constructmanifest(self):
672 def _constructmanifest(self):
673 # This is a temporary function while we migrate from manifest to
673 # This is a temporary function while we migrate from manifest to
674 # manifestlog. It allows bundlerepo and unionrepo to intercept the
674 # manifestlog. It allows bundlerepo and unionrepo to intercept the
675 # manifest creation.
675 # manifest creation.
676 return manifest.manifestrevlog(self.svfs)
676 return manifest.manifestrevlog(self.svfs)
677
677
678 @storecache('00manifest.i')
678 @storecache('00manifest.i')
679 def manifestlog(self):
679 def manifestlog(self):
680 return manifest.manifestlog(self.svfs, self)
680 return manifest.manifestlog(self.svfs, self)
681
681
682 @repofilecache('dirstate')
682 @repofilecache('dirstate')
683 def dirstate(self):
683 def dirstate(self):
684 sparsematchfn = lambda: sparse.matcher(self)
684 sparsematchfn = lambda: sparse.matcher(self)
685
685
686 return dirstate.dirstate(self.vfs, self.ui, self.root,
686 return dirstate.dirstate(self.vfs, self.ui, self.root,
687 self._dirstatevalidate, sparsematchfn)
687 self._dirstatevalidate, sparsematchfn)
688
688
689 def _dirstatevalidate(self, node):
689 def _dirstatevalidate(self, node):
690 try:
690 try:
691 self.changelog.rev(node)
691 self.changelog.rev(node)
692 return node
692 return node
693 except error.LookupError:
693 except error.LookupError:
694 if not self._dirstatevalidatewarned:
694 if not self._dirstatevalidatewarned:
695 self._dirstatevalidatewarned = True
695 self._dirstatevalidatewarned = True
696 self.ui.warn(_("warning: ignoring unknown"
696 self.ui.warn(_("warning: ignoring unknown"
697 " working parent %s!\n") % short(node))
697 " working parent %s!\n") % short(node))
698 return nullid
698 return nullid
699
699
700 def __getitem__(self, changeid):
700 def __getitem__(self, changeid):
701 if changeid is None:
701 if changeid is None:
702 return context.workingctx(self)
702 return context.workingctx(self)
703 if isinstance(changeid, slice):
703 if isinstance(changeid, slice):
704 # wdirrev isn't contiguous so the slice shouldn't include it
704 # wdirrev isn't contiguous so the slice shouldn't include it
705 return [context.changectx(self, i)
705 return [context.changectx(self, i)
706 for i in xrange(*changeid.indices(len(self)))
706 for i in xrange(*changeid.indices(len(self)))
707 if i not in self.changelog.filteredrevs]
707 if i not in self.changelog.filteredrevs]
708 try:
708 try:
709 return context.changectx(self, changeid)
709 return context.changectx(self, changeid)
710 except error.WdirUnsupported:
710 except error.WdirUnsupported:
711 return context.workingctx(self)
711 return context.workingctx(self)
712
712
713 def __contains__(self, changeid):
713 def __contains__(self, changeid):
714 """True if the given changeid exists
714 """True if the given changeid exists
715
715
716 error.LookupError is raised if an ambiguous node specified.
716 error.LookupError is raised if an ambiguous node specified.
717 """
717 """
718 try:
718 try:
719 self[changeid]
719 self[changeid]
720 return True
720 return True
721 except error.RepoLookupError:
721 except error.RepoLookupError:
722 return False
722 return False
723
723
724 def __nonzero__(self):
724 def __nonzero__(self):
725 return True
725 return True
726
726
727 __bool__ = __nonzero__
727 __bool__ = __nonzero__
728
728
729 def __len__(self):
729 def __len__(self):
730 return len(self.changelog)
730 return len(self.changelog)
731
731
732 def __iter__(self):
732 def __iter__(self):
733 return iter(self.changelog)
733 return iter(self.changelog)
734
734
735 def revs(self, expr, *args):
735 def revs(self, expr, *args):
736 '''Find revisions matching a revset.
736 '''Find revisions matching a revset.
737
737
738 The revset is specified as a string ``expr`` that may contain
738 The revset is specified as a string ``expr`` that may contain
739 %-formatting to escape certain types. See ``revsetlang.formatspec``.
739 %-formatting to escape certain types. See ``revsetlang.formatspec``.
740
740
741 Revset aliases from the configuration are not expanded. To expand
741 Revset aliases from the configuration are not expanded. To expand
742 user aliases, consider calling ``scmutil.revrange()`` or
742 user aliases, consider calling ``scmutil.revrange()`` or
743 ``repo.anyrevs([expr], user=True)``.
743 ``repo.anyrevs([expr], user=True)``.
744
744
745 Returns a revset.abstractsmartset, which is a list-like interface
745 Returns a revset.abstractsmartset, which is a list-like interface
746 that contains integer revisions.
746 that contains integer revisions.
747 '''
747 '''
748 expr = revsetlang.formatspec(expr, *args)
748 expr = revsetlang.formatspec(expr, *args)
749 m = revset.match(None, expr)
749 m = revset.match(None, expr)
750 return m(self)
750 return m(self)
751
751
752 def set(self, expr, *args):
752 def set(self, expr, *args):
753 '''Find revisions matching a revset and emit changectx instances.
753 '''Find revisions matching a revset and emit changectx instances.
754
754
755 This is a convenience wrapper around ``revs()`` that iterates the
755 This is a convenience wrapper around ``revs()`` that iterates the
756 result and is a generator of changectx instances.
756 result and is a generator of changectx instances.
757
757
758 Revset aliases from the configuration are not expanded. To expand
758 Revset aliases from the configuration are not expanded. To expand
759 user aliases, consider calling ``scmutil.revrange()``.
759 user aliases, consider calling ``scmutil.revrange()``.
760 '''
760 '''
761 for r in self.revs(expr, *args):
761 for r in self.revs(expr, *args):
762 yield self[r]
762 yield self[r]
763
763
764 def anyrevs(self, specs, user=False, localalias=None):
764 def anyrevs(self, specs, user=False, localalias=None):
765 '''Find revisions matching one of the given revsets.
765 '''Find revisions matching one of the given revsets.
766
766
767 Revset aliases from the configuration are not expanded by default. To
767 Revset aliases from the configuration are not expanded by default. To
768 expand user aliases, specify ``user=True``. To provide some local
768 expand user aliases, specify ``user=True``. To provide some local
769 definitions overriding user aliases, set ``localalias`` to
769 definitions overriding user aliases, set ``localalias`` to
770 ``{name: definitionstring}``.
770 ``{name: definitionstring}``.
771 '''
771 '''
772 if user:
772 if user:
773 m = revset.matchany(self.ui, specs, repo=self,
773 m = revset.matchany(self.ui, specs, repo=self,
774 localalias=localalias)
774 localalias=localalias)
775 else:
775 else:
776 m = revset.matchany(None, specs, localalias=localalias)
776 m = revset.matchany(None, specs, localalias=localalias)
777 return m(self)
777 return m(self)
778
778
779 def url(self):
779 def url(self):
780 return 'file:' + self.root
780 return 'file:' + self.root
781
781
782 def hook(self, name, throw=False, **args):
782 def hook(self, name, throw=False, **args):
783 """Call a hook, passing this repo instance.
783 """Call a hook, passing this repo instance.
784
784
785 This a convenience method to aid invoking hooks. Extensions likely
785 This a convenience method to aid invoking hooks. Extensions likely
786 won't call this unless they have registered a custom hook or are
786 won't call this unless they have registered a custom hook or are
787 replacing code that is expected to call a hook.
787 replacing code that is expected to call a hook.
788 """
788 """
789 return hook.hook(self.ui, self, name, throw, **args)
789 return hook.hook(self.ui, self, name, throw, **args)
790
790
791 @filteredpropertycache
791 @filteredpropertycache
792 def _tagscache(self):
792 def _tagscache(self):
793 '''Returns a tagscache object that contains various tags related
793 '''Returns a tagscache object that contains various tags related
794 caches.'''
794 caches.'''
795
795
796 # This simplifies its cache management by having one decorated
796 # This simplifies its cache management by having one decorated
797 # function (this one) and the rest simply fetch things from it.
797 # function (this one) and the rest simply fetch things from it.
798 class tagscache(object):
798 class tagscache(object):
799 def __init__(self):
799 def __init__(self):
800 # These two define the set of tags for this repository. tags
800 # These two define the set of tags for this repository. tags
801 # maps tag name to node; tagtypes maps tag name to 'global' or
801 # maps tag name to node; tagtypes maps tag name to 'global' or
802 # 'local'. (Global tags are defined by .hgtags across all
802 # 'local'. (Global tags are defined by .hgtags across all
803 # heads, and local tags are defined in .hg/localtags.)
803 # heads, and local tags are defined in .hg/localtags.)
804 # They constitute the in-memory cache of tags.
804 # They constitute the in-memory cache of tags.
805 self.tags = self.tagtypes = None
805 self.tags = self.tagtypes = None
806
806
807 self.nodetagscache = self.tagslist = None
807 self.nodetagscache = self.tagslist = None
808
808
809 cache = tagscache()
809 cache = tagscache()
810 cache.tags, cache.tagtypes = self._findtags()
810 cache.tags, cache.tagtypes = self._findtags()
811
811
812 return cache
812 return cache
813
813
814 def tags(self):
814 def tags(self):
815 '''return a mapping of tag to node'''
815 '''return a mapping of tag to node'''
816 t = {}
816 t = {}
817 if self.changelog.filteredrevs:
817 if self.changelog.filteredrevs:
818 tags, tt = self._findtags()
818 tags, tt = self._findtags()
819 else:
819 else:
820 tags = self._tagscache.tags
820 tags = self._tagscache.tags
821 for k, v in tags.iteritems():
821 for k, v in tags.iteritems():
822 try:
822 try:
823 # ignore tags to unknown nodes
823 # ignore tags to unknown nodes
824 self.changelog.rev(v)
824 self.changelog.rev(v)
825 t[k] = v
825 t[k] = v
826 except (error.LookupError, ValueError):
826 except (error.LookupError, ValueError):
827 pass
827 pass
828 return t
828 return t
829
829
830 def _findtags(self):
830 def _findtags(self):
831 '''Do the hard work of finding tags. Return a pair of dicts
831 '''Do the hard work of finding tags. Return a pair of dicts
832 (tags, tagtypes) where tags maps tag name to node, and tagtypes
832 (tags, tagtypes) where tags maps tag name to node, and tagtypes
833 maps tag name to a string like \'global\' or \'local\'.
833 maps tag name to a string like \'global\' or \'local\'.
834 Subclasses or extensions are free to add their own tags, but
834 Subclasses or extensions are free to add their own tags, but
835 should be aware that the returned dicts will be retained for the
835 should be aware that the returned dicts will be retained for the
836 duration of the localrepo object.'''
836 duration of the localrepo object.'''
837
837
838 # XXX what tagtype should subclasses/extensions use? Currently
838 # XXX what tagtype should subclasses/extensions use? Currently
839 # mq and bookmarks add tags, but do not set the tagtype at all.
839 # mq and bookmarks add tags, but do not set the tagtype at all.
840 # Should each extension invent its own tag type? Should there
840 # Should each extension invent its own tag type? Should there
841 # be one tagtype for all such "virtual" tags? Or is the status
841 # be one tagtype for all such "virtual" tags? Or is the status
842 # quo fine?
842 # quo fine?
843
843
844
844
845 # map tag name to (node, hist)
845 # map tag name to (node, hist)
846 alltags = tagsmod.findglobaltags(self.ui, self)
846 alltags = tagsmod.findglobaltags(self.ui, self)
847 # map tag name to tag type
847 # map tag name to tag type
848 tagtypes = dict((tag, 'global') for tag in alltags)
848 tagtypes = dict((tag, 'global') for tag in alltags)
849
849
850 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
850 tagsmod.readlocaltags(self.ui, self, alltags, tagtypes)
851
851
852 # Build the return dicts. Have to re-encode tag names because
852 # Build the return dicts. Have to re-encode tag names because
853 # the tags module always uses UTF-8 (in order not to lose info
853 # the tags module always uses UTF-8 (in order not to lose info
854 # writing to the cache), but the rest of Mercurial wants them in
854 # writing to the cache), but the rest of Mercurial wants them in
855 # local encoding.
855 # local encoding.
856 tags = {}
856 tags = {}
857 for (name, (node, hist)) in alltags.iteritems():
857 for (name, (node, hist)) in alltags.iteritems():
858 if node != nullid:
858 if node != nullid:
859 tags[encoding.tolocal(name)] = node
859 tags[encoding.tolocal(name)] = node
860 tags['tip'] = self.changelog.tip()
860 tags['tip'] = self.changelog.tip()
861 tagtypes = dict([(encoding.tolocal(name), value)
861 tagtypes = dict([(encoding.tolocal(name), value)
862 for (name, value) in tagtypes.iteritems()])
862 for (name, value) in tagtypes.iteritems()])
863 return (tags, tagtypes)
863 return (tags, tagtypes)
864
864
865 def tagtype(self, tagname):
865 def tagtype(self, tagname):
866 '''
866 '''
867 return the type of the given tag. result can be:
867 return the type of the given tag. result can be:
868
868
869 'local' : a local tag
869 'local' : a local tag
870 'global' : a global tag
870 'global' : a global tag
871 None : tag does not exist
871 None : tag does not exist
872 '''
872 '''
873
873
874 return self._tagscache.tagtypes.get(tagname)
874 return self._tagscache.tagtypes.get(tagname)
875
875
876 def tagslist(self):
876 def tagslist(self):
877 '''return a list of tags ordered by revision'''
877 '''return a list of tags ordered by revision'''
878 if not self._tagscache.tagslist:
878 if not self._tagscache.tagslist:
879 l = []
879 l = []
880 for t, n in self.tags().iteritems():
880 for t, n in self.tags().iteritems():
881 l.append((self.changelog.rev(n), t, n))
881 l.append((self.changelog.rev(n), t, n))
882 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
882 self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
883
883
884 return self._tagscache.tagslist
884 return self._tagscache.tagslist
885
885
886 def nodetags(self, node):
886 def nodetags(self, node):
887 '''return the tags associated with a node'''
887 '''return the tags associated with a node'''
888 if not self._tagscache.nodetagscache:
888 if not self._tagscache.nodetagscache:
889 nodetagscache = {}
889 nodetagscache = {}
890 for t, n in self._tagscache.tags.iteritems():
890 for t, n in self._tagscache.tags.iteritems():
891 nodetagscache.setdefault(n, []).append(t)
891 nodetagscache.setdefault(n, []).append(t)
892 for tags in nodetagscache.itervalues():
892 for tags in nodetagscache.itervalues():
893 tags.sort()
893 tags.sort()
894 self._tagscache.nodetagscache = nodetagscache
894 self._tagscache.nodetagscache = nodetagscache
895 return self._tagscache.nodetagscache.get(node, [])
895 return self._tagscache.nodetagscache.get(node, [])
896
896
897 def nodebookmarks(self, node):
897 def nodebookmarks(self, node):
898 """return the list of bookmarks pointing to the specified node"""
898 """return the list of bookmarks pointing to the specified node"""
899 marks = []
899 marks = []
900 for bookmark, n in self._bookmarks.iteritems():
900 for bookmark, n in self._bookmarks.iteritems():
901 if n == node:
901 if n == node:
902 marks.append(bookmark)
902 marks.append(bookmark)
903 return sorted(marks)
903 return sorted(marks)
904
904
905 def branchmap(self):
905 def branchmap(self):
906 '''returns a dictionary {branch: [branchheads]} with branchheads
906 '''returns a dictionary {branch: [branchheads]} with branchheads
907 ordered by increasing revision number'''
907 ordered by increasing revision number'''
908 branchmap.updatecache(self)
908 branchmap.updatecache(self)
909 return self._branchcaches[self.filtername]
909 return self._branchcaches[self.filtername]
910
910
911 @unfilteredmethod
911 @unfilteredmethod
912 def revbranchcache(self):
912 def revbranchcache(self):
913 if not self._revbranchcache:
913 if not self._revbranchcache:
914 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
914 self._revbranchcache = branchmap.revbranchcache(self.unfiltered())
915 return self._revbranchcache
915 return self._revbranchcache
916
916
917 def branchtip(self, branch, ignoremissing=False):
917 def branchtip(self, branch, ignoremissing=False):
918 '''return the tip node for a given branch
918 '''return the tip node for a given branch
919
919
920 If ignoremissing is True, then this method will not raise an error.
920 If ignoremissing is True, then this method will not raise an error.
921 This is helpful for callers that only expect None for a missing branch
921 This is helpful for callers that only expect None for a missing branch
922 (e.g. namespace).
922 (e.g. namespace).
923
923
924 '''
924 '''
925 try:
925 try:
926 return self.branchmap().branchtip(branch)
926 return self.branchmap().branchtip(branch)
927 except KeyError:
927 except KeyError:
928 if not ignoremissing:
928 if not ignoremissing:
929 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
929 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
930 else:
930 else:
931 pass
931 pass
932
932
933 def lookup(self, key):
933 def lookup(self, key):
934 return self[key].node()
934 return self[key].node()
935
935
936 def lookupbranch(self, key, remote=None):
936 def lookupbranch(self, key, remote=None):
937 repo = remote or self
937 repo = remote or self
938 if key in repo.branchmap():
938 if key in repo.branchmap():
939 return key
939 return key
940
940
941 repo = (remote and remote.local()) and remote or self
941 repo = (remote and remote.local()) and remote or self
942 return repo[key].branch()
942 return repo[key].branch()
943
943
944 def known(self, nodes):
944 def known(self, nodes):
945 cl = self.changelog
945 cl = self.changelog
946 nm = cl.nodemap
946 nm = cl.nodemap
947 filtered = cl.filteredrevs
947 filtered = cl.filteredrevs
948 result = []
948 result = []
949 for n in nodes:
949 for n in nodes:
950 r = nm.get(n)
950 r = nm.get(n)
951 resp = not (r is None or r in filtered)
951 resp = not (r is None or r in filtered)
952 result.append(resp)
952 result.append(resp)
953 return result
953 return result
954
954
955 def local(self):
955 def local(self):
956 return self
956 return self
957
957
958 def publishing(self):
958 def publishing(self):
959 # it's safe (and desirable) to trust the publish flag unconditionally
959 # it's safe (and desirable) to trust the publish flag unconditionally
960 # so that we don't finalize changes shared between users via ssh or nfs
960 # so that we don't finalize changes shared between users via ssh or nfs
961 return self.ui.configbool('phases', 'publish', untrusted=True)
961 return self.ui.configbool('phases', 'publish', untrusted=True)
962
962
963 def cancopy(self):
963 def cancopy(self):
964 # so statichttprepo's override of local() works
964 # so statichttprepo's override of local() works
965 if not self.local():
965 if not self.local():
966 return False
966 return False
967 if not self.publishing():
967 if not self.publishing():
968 return True
968 return True
969 # if publishing we can't copy if there is filtered content
969 # if publishing we can't copy if there is filtered content
970 return not self.filtered('visible').changelog.filteredrevs
970 return not self.filtered('visible').changelog.filteredrevs
971
971
972 def shared(self):
972 def shared(self):
973 '''the type of shared repository (None if not shared)'''
973 '''the type of shared repository (None if not shared)'''
974 if self.sharedpath != self.path:
974 if self.sharedpath != self.path:
975 return 'store'
975 return 'store'
976 return None
976 return None
977
977
978 def wjoin(self, f, *insidef):
978 def wjoin(self, f, *insidef):
979 return self.vfs.reljoin(self.root, f, *insidef)
979 return self.vfs.reljoin(self.root, f, *insidef)
980
980
981 def file(self, f):
981 def file(self, f):
982 if f[0] == '/':
982 if f[0] == '/':
983 f = f[1:]
983 f = f[1:]
984 return filelog.filelog(self.svfs, f)
984 return filelog.filelog(self.svfs, f)
985
985
986 def changectx(self, changeid):
986 def changectx(self, changeid):
987 return self[changeid]
987 return self[changeid]
988
988
989 def setparents(self, p1, p2=nullid):
989 def setparents(self, p1, p2=nullid):
990 with self.dirstate.parentchange():
990 with self.dirstate.parentchange():
991 copies = self.dirstate.setparents(p1, p2)
991 copies = self.dirstate.setparents(p1, p2)
992 pctx = self[p1]
992 pctx = self[p1]
993 if copies:
993 if copies:
994 # Adjust copy records, the dirstate cannot do it, it
994 # Adjust copy records, the dirstate cannot do it, it
995 # requires access to parents manifests. Preserve them
995 # requires access to parents manifests. Preserve them
996 # only for entries added to first parent.
996 # only for entries added to first parent.
997 for f in copies:
997 for f in copies:
998 if f not in pctx and copies[f] in pctx:
998 if f not in pctx and copies[f] in pctx:
999 self.dirstate.copy(copies[f], f)
999 self.dirstate.copy(copies[f], f)
1000 if p2 == nullid:
1000 if p2 == nullid:
1001 for f, s in sorted(self.dirstate.copies().items()):
1001 for f, s in sorted(self.dirstate.copies().items()):
1002 if f not in pctx and s not in pctx:
1002 if f not in pctx and s not in pctx:
1003 self.dirstate.copy(None, f)
1003 self.dirstate.copy(None, f)
1004
1004
1005 def filectx(self, path, changeid=None, fileid=None):
1005 def filectx(self, path, changeid=None, fileid=None):
1006 """changeid can be a changeset revision, node, or tag.
1006 """changeid can be a changeset revision, node, or tag.
1007 fileid can be a file revision or node."""
1007 fileid can be a file revision or node."""
1008 return context.filectx(self, path, changeid, fileid)
1008 return context.filectx(self, path, changeid, fileid)
1009
1009
1010 def getcwd(self):
1010 def getcwd(self):
1011 return self.dirstate.getcwd()
1011 return self.dirstate.getcwd()
1012
1012
1013 def pathto(self, f, cwd=None):
1013 def pathto(self, f, cwd=None):
1014 return self.dirstate.pathto(f, cwd)
1014 return self.dirstate.pathto(f, cwd)
1015
1015
1016 def _loadfilter(self, filter):
1016 def _loadfilter(self, filter):
1017 if filter not in self.filterpats:
1017 if filter not in self.filterpats:
1018 l = []
1018 l = []
1019 for pat, cmd in self.ui.configitems(filter):
1019 for pat, cmd in self.ui.configitems(filter):
1020 if cmd == '!':
1020 if cmd == '!':
1021 continue
1021 continue
1022 mf = matchmod.match(self.root, '', [pat])
1022 mf = matchmod.match(self.root, '', [pat])
1023 fn = None
1023 fn = None
1024 params = cmd
1024 params = cmd
1025 for name, filterfn in self._datafilters.iteritems():
1025 for name, filterfn in self._datafilters.iteritems():
1026 if cmd.startswith(name):
1026 if cmd.startswith(name):
1027 fn = filterfn
1027 fn = filterfn
1028 params = cmd[len(name):].lstrip()
1028 params = cmd[len(name):].lstrip()
1029 break
1029 break
1030 if not fn:
1030 if not fn:
1031 fn = lambda s, c, **kwargs: util.filter(s, c)
1031 fn = lambda s, c, **kwargs: util.filter(s, c)
1032 # Wrap old filters not supporting keyword arguments
1032 # Wrap old filters not supporting keyword arguments
1033 if not inspect.getargspec(fn)[2]:
1033 if not inspect.getargspec(fn)[2]:
1034 oldfn = fn
1034 oldfn = fn
1035 fn = lambda s, c, **kwargs: oldfn(s, c)
1035 fn = lambda s, c, **kwargs: oldfn(s, c)
1036 l.append((mf, fn, params))
1036 l.append((mf, fn, params))
1037 self.filterpats[filter] = l
1037 self.filterpats[filter] = l
1038 return self.filterpats[filter]
1038 return self.filterpats[filter]
1039
1039
1040 def _filter(self, filterpats, filename, data):
1040 def _filter(self, filterpats, filename, data):
1041 for mf, fn, cmd in filterpats:
1041 for mf, fn, cmd in filterpats:
1042 if mf(filename):
1042 if mf(filename):
1043 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1043 self.ui.debug("filtering %s through %s\n" % (filename, cmd))
1044 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1044 data = fn(data, cmd, ui=self.ui, repo=self, filename=filename)
1045 break
1045 break
1046
1046
1047 return data
1047 return data
1048
1048
1049 @unfilteredpropertycache
1049 @unfilteredpropertycache
1050 def _encodefilterpats(self):
1050 def _encodefilterpats(self):
1051 return self._loadfilter('encode')
1051 return self._loadfilter('encode')
1052
1052
1053 @unfilteredpropertycache
1053 @unfilteredpropertycache
1054 def _decodefilterpats(self):
1054 def _decodefilterpats(self):
1055 return self._loadfilter('decode')
1055 return self._loadfilter('decode')
1056
1056
1057 def adddatafilter(self, name, filter):
1057 def adddatafilter(self, name, filter):
1058 self._datafilters[name] = filter
1058 self._datafilters[name] = filter
1059
1059
1060 def wread(self, filename):
1060 def wread(self, filename):
1061 if self.wvfs.islink(filename):
1061 if self.wvfs.islink(filename):
1062 data = self.wvfs.readlink(filename)
1062 data = self.wvfs.readlink(filename)
1063 else:
1063 else:
1064 data = self.wvfs.read(filename)
1064 data = self.wvfs.read(filename)
1065 return self._filter(self._encodefilterpats, filename, data)
1065 return self._filter(self._encodefilterpats, filename, data)
1066
1066
1067 def wwrite(self, filename, data, flags, backgroundclose=False):
1067 def wwrite(self, filename, data, flags, backgroundclose=False):
1068 """write ``data`` into ``filename`` in the working directory
1068 """write ``data`` into ``filename`` in the working directory
1069
1069
1070 This returns length of written (maybe decoded) data.
1070 This returns length of written (maybe decoded) data.
1071 """
1071 """
1072 data = self._filter(self._decodefilterpats, filename, data)
1072 data = self._filter(self._decodefilterpats, filename, data)
1073 if 'l' in flags:
1073 if 'l' in flags:
1074 self.wvfs.symlink(data, filename)
1074 self.wvfs.symlink(data, filename)
1075 else:
1075 else:
1076 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
1076 self.wvfs.write(filename, data, backgroundclose=backgroundclose)
1077 if 'x' in flags:
1077 if 'x' in flags:
1078 self.wvfs.setflags(filename, False, True)
1078 self.wvfs.setflags(filename, False, True)
1079 return len(data)
1079 return len(data)
1080
1080
1081 def wwritedata(self, filename, data):
1081 def wwritedata(self, filename, data):
1082 return self._filter(self._decodefilterpats, filename, data)
1082 return self._filter(self._decodefilterpats, filename, data)
1083
1083
1084 def currenttransaction(self):
1084 def currenttransaction(self):
1085 """return the current transaction or None if non exists"""
1085 """return the current transaction or None if non exists"""
1086 if self._transref:
1086 if self._transref:
1087 tr = self._transref()
1087 tr = self._transref()
1088 else:
1088 else:
1089 tr = None
1089 tr = None
1090
1090
1091 if tr and tr.running():
1091 if tr and tr.running():
1092 return tr
1092 return tr
1093 return None
1093 return None
1094
1094
1095 def transaction(self, desc, report=None):
1095 def transaction(self, desc, report=None):
1096 if (self.ui.configbool('devel', 'all-warnings')
1096 if (self.ui.configbool('devel', 'all-warnings')
1097 or self.ui.configbool('devel', 'check-locks')):
1097 or self.ui.configbool('devel', 'check-locks')):
1098 if self._currentlock(self._lockref) is None:
1098 if self._currentlock(self._lockref) is None:
1099 raise error.ProgrammingError('transaction requires locking')
1099 raise error.ProgrammingError('transaction requires locking')
1100 tr = self.currenttransaction()
1100 tr = self.currenttransaction()
1101 if tr is not None:
1101 if tr is not None:
1102 scmutil.registersummarycallback(self, tr, desc)
1102 return tr.nest()
1103 return tr.nest()
1103
1104
1104 # abort here if the journal already exists
1105 # abort here if the journal already exists
1105 if self.svfs.exists("journal"):
1106 if self.svfs.exists("journal"):
1106 raise error.RepoError(
1107 raise error.RepoError(
1107 _("abandoned transaction found"),
1108 _("abandoned transaction found"),
1108 hint=_("run 'hg recover' to clean up transaction"))
1109 hint=_("run 'hg recover' to clean up transaction"))
1109
1110
1110 idbase = "%.40f#%f" % (random.random(), time.time())
1111 idbase = "%.40f#%f" % (random.random(), time.time())
1111 ha = hex(hashlib.sha1(idbase).digest())
1112 ha = hex(hashlib.sha1(idbase).digest())
1112 txnid = 'TXN:' + ha
1113 txnid = 'TXN:' + ha
1113 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1114 self.hook('pretxnopen', throw=True, txnname=desc, txnid=txnid)
1114
1115
1115 self._writejournal(desc)
1116 self._writejournal(desc)
1116 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1117 renames = [(vfs, x, undoname(x)) for vfs, x in self._journalfiles()]
1117 if report:
1118 if report:
1118 rp = report
1119 rp = report
1119 else:
1120 else:
1120 rp = self.ui.warn
1121 rp = self.ui.warn
1121 vfsmap = {'plain': self.vfs} # root of .hg/
1122 vfsmap = {'plain': self.vfs} # root of .hg/
1122 # we must avoid cyclic reference between repo and transaction.
1123 # we must avoid cyclic reference between repo and transaction.
1123 reporef = weakref.ref(self)
1124 reporef = weakref.ref(self)
1124 # Code to track tag movement
1125 # Code to track tag movement
1125 #
1126 #
1126 # Since tags are all handled as file content, it is actually quite hard
1127 # Since tags are all handled as file content, it is actually quite hard
1127 # to track these movement from a code perspective. So we fallback to a
1128 # to track these movement from a code perspective. So we fallback to a
1128 # tracking at the repository level. One could envision to track changes
1129 # tracking at the repository level. One could envision to track changes
1129 # to the '.hgtags' file through changegroup apply but that fails to
1130 # to the '.hgtags' file through changegroup apply but that fails to
1130 # cope with case where transaction expose new heads without changegroup
1131 # cope with case where transaction expose new heads without changegroup
1131 # being involved (eg: phase movement).
1132 # being involved (eg: phase movement).
1132 #
1133 #
1133 # For now, We gate the feature behind a flag since this likely comes
1134 # For now, We gate the feature behind a flag since this likely comes
1134 # with performance impacts. The current code run more often than needed
1135 # with performance impacts. The current code run more often than needed
1135 # and do not use caches as much as it could. The current focus is on
1136 # and do not use caches as much as it could. The current focus is on
1136 # the behavior of the feature so we disable it by default. The flag
1137 # the behavior of the feature so we disable it by default. The flag
1137 # will be removed when we are happy with the performance impact.
1138 # will be removed when we are happy with the performance impact.
1138 #
1139 #
1139 # Once this feature is no longer experimental move the following
1140 # Once this feature is no longer experimental move the following
1140 # documentation to the appropriate help section:
1141 # documentation to the appropriate help section:
1141 #
1142 #
1142 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1143 # The ``HG_TAG_MOVED`` variable will be set if the transaction touched
1143 # tags (new or changed or deleted tags). In addition the details of
1144 # tags (new or changed or deleted tags). In addition the details of
1144 # these changes are made available in a file at:
1145 # these changes are made available in a file at:
1145 # ``REPOROOT/.hg/changes/tags.changes``.
1146 # ``REPOROOT/.hg/changes/tags.changes``.
1146 # Make sure you check for HG_TAG_MOVED before reading that file as it
1147 # Make sure you check for HG_TAG_MOVED before reading that file as it
1147 # might exist from a previous transaction even if no tag were touched
1148 # might exist from a previous transaction even if no tag were touched
1148 # in this one. Changes are recorded in a line base format::
1149 # in this one. Changes are recorded in a line base format::
1149 #
1150 #
1150 # <action> <hex-node> <tag-name>\n
1151 # <action> <hex-node> <tag-name>\n
1151 #
1152 #
1152 # Actions are defined as follow:
1153 # Actions are defined as follow:
1153 # "-R": tag is removed,
1154 # "-R": tag is removed,
1154 # "+A": tag is added,
1155 # "+A": tag is added,
1155 # "-M": tag is moved (old value),
1156 # "-M": tag is moved (old value),
1156 # "+M": tag is moved (new value),
1157 # "+M": tag is moved (new value),
1157 tracktags = lambda x: None
1158 tracktags = lambda x: None
1158 # experimental config: experimental.hook-track-tags
1159 # experimental config: experimental.hook-track-tags
1159 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1160 shouldtracktags = self.ui.configbool('experimental', 'hook-track-tags')
1160 if desc != 'strip' and shouldtracktags:
1161 if desc != 'strip' and shouldtracktags:
1161 oldheads = self.changelog.headrevs()
1162 oldheads = self.changelog.headrevs()
1162 def tracktags(tr2):
1163 def tracktags(tr2):
1163 repo = reporef()
1164 repo = reporef()
1164 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1165 oldfnodes = tagsmod.fnoderevs(repo.ui, repo, oldheads)
1165 newheads = repo.changelog.headrevs()
1166 newheads = repo.changelog.headrevs()
1166 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1167 newfnodes = tagsmod.fnoderevs(repo.ui, repo, newheads)
1167 # notes: we compare lists here.
1168 # notes: we compare lists here.
1168 # As we do it only once buiding set would not be cheaper
1169 # As we do it only once buiding set would not be cheaper
1169 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1170 changes = tagsmod.difftags(repo.ui, repo, oldfnodes, newfnodes)
1170 if changes:
1171 if changes:
1171 tr2.hookargs['tag_moved'] = '1'
1172 tr2.hookargs['tag_moved'] = '1'
1172 with repo.vfs('changes/tags.changes', 'w',
1173 with repo.vfs('changes/tags.changes', 'w',
1173 atomictemp=True) as changesfile:
1174 atomictemp=True) as changesfile:
1174 # note: we do not register the file to the transaction
1175 # note: we do not register the file to the transaction
1175 # because we needs it to still exist on the transaction
1176 # because we needs it to still exist on the transaction
1176 # is close (for txnclose hooks)
1177 # is close (for txnclose hooks)
1177 tagsmod.writediff(changesfile, changes)
1178 tagsmod.writediff(changesfile, changes)
1178 def validate(tr2):
1179 def validate(tr2):
1179 """will run pre-closing hooks"""
1180 """will run pre-closing hooks"""
1180 # XXX the transaction API is a bit lacking here so we take a hacky
1181 # XXX the transaction API is a bit lacking here so we take a hacky
1181 # path for now
1182 # path for now
1182 #
1183 #
1183 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1184 # We cannot add this as a "pending" hooks since the 'tr.hookargs'
1184 # dict is copied before these run. In addition we needs the data
1185 # dict is copied before these run. In addition we needs the data
1185 # available to in memory hooks too.
1186 # available to in memory hooks too.
1186 #
1187 #
1187 # Moreover, we also need to make sure this runs before txnclose
1188 # Moreover, we also need to make sure this runs before txnclose
1188 # hooks and there is no "pending" mechanism that would execute
1189 # hooks and there is no "pending" mechanism that would execute
1189 # logic only if hooks are about to run.
1190 # logic only if hooks are about to run.
1190 #
1191 #
1191 # Fixing this limitation of the transaction is also needed to track
1192 # Fixing this limitation of the transaction is also needed to track
1192 # other families of changes (bookmarks, phases, obsolescence).
1193 # other families of changes (bookmarks, phases, obsolescence).
1193 #
1194 #
1194 # This will have to be fixed before we remove the experimental
1195 # This will have to be fixed before we remove the experimental
1195 # gating.
1196 # gating.
1196 tracktags(tr2)
1197 tracktags(tr2)
1197 reporef().hook('pretxnclose', throw=True,
1198 reporef().hook('pretxnclose', throw=True,
1198 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1199 txnname=desc, **pycompat.strkwargs(tr.hookargs))
1199 def releasefn(tr, success):
1200 def releasefn(tr, success):
1200 repo = reporef()
1201 repo = reporef()
1201 if success:
1202 if success:
1202 # this should be explicitly invoked here, because
1203 # this should be explicitly invoked here, because
1203 # in-memory changes aren't written out at closing
1204 # in-memory changes aren't written out at closing
1204 # transaction, if tr.addfilegenerator (via
1205 # transaction, if tr.addfilegenerator (via
1205 # dirstate.write or so) isn't invoked while
1206 # dirstate.write or so) isn't invoked while
1206 # transaction running
1207 # transaction running
1207 repo.dirstate.write(None)
1208 repo.dirstate.write(None)
1208 else:
1209 else:
1209 # discard all changes (including ones already written
1210 # discard all changes (including ones already written
1210 # out) in this transaction
1211 # out) in this transaction
1211 repo.dirstate.restorebackup(None, 'journal.dirstate')
1212 repo.dirstate.restorebackup(None, 'journal.dirstate')
1212
1213
1213 repo.invalidate(clearfilecache=True)
1214 repo.invalidate(clearfilecache=True)
1214
1215
1215 tr = transaction.transaction(rp, self.svfs, vfsmap,
1216 tr = transaction.transaction(rp, self.svfs, vfsmap,
1216 "journal",
1217 "journal",
1217 "undo",
1218 "undo",
1218 aftertrans(renames),
1219 aftertrans(renames),
1219 self.store.createmode,
1220 self.store.createmode,
1220 validator=validate,
1221 validator=validate,
1221 releasefn=releasefn,
1222 releasefn=releasefn,
1222 checkambigfiles=_cachedfiles)
1223 checkambigfiles=_cachedfiles)
1223 tr.changes['revs'] = set()
1224 tr.changes['revs'] = set()
1224 tr.changes['obsmarkers'] = set()
1225 tr.changes['obsmarkers'] = set()
1225 tr.changes['phases'] = {}
1226 tr.changes['phases'] = {}
1226 tr.changes['bookmarks'] = {}
1227 tr.changes['bookmarks'] = {}
1227
1228
1228 tr.hookargs['txnid'] = txnid
1229 tr.hookargs['txnid'] = txnid
1229 # note: writing the fncache only during finalize mean that the file is
1230 # note: writing the fncache only during finalize mean that the file is
1230 # outdated when running hooks. As fncache is used for streaming clone,
1231 # outdated when running hooks. As fncache is used for streaming clone,
1231 # this is not expected to break anything that happen during the hooks.
1232 # this is not expected to break anything that happen during the hooks.
1232 tr.addfinalize('flush-fncache', self.store.write)
1233 tr.addfinalize('flush-fncache', self.store.write)
1233 def txnclosehook(tr2):
1234 def txnclosehook(tr2):
1234 """To be run if transaction is successful, will schedule a hook run
1235 """To be run if transaction is successful, will schedule a hook run
1235 """
1236 """
1236 # Don't reference tr2 in hook() so we don't hold a reference.
1237 # Don't reference tr2 in hook() so we don't hold a reference.
1237 # This reduces memory consumption when there are multiple
1238 # This reduces memory consumption when there are multiple
1238 # transactions per lock. This can likely go away if issue5045
1239 # transactions per lock. This can likely go away if issue5045
1239 # fixes the function accumulation.
1240 # fixes the function accumulation.
1240 hookargs = tr2.hookargs
1241 hookargs = tr2.hookargs
1241
1242
1242 def hook():
1243 def hook():
1243 reporef().hook('txnclose', throw=False, txnname=desc,
1244 reporef().hook('txnclose', throw=False, txnname=desc,
1244 **pycompat.strkwargs(hookargs))
1245 **pycompat.strkwargs(hookargs))
1245 reporef()._afterlock(hook)
1246 reporef()._afterlock(hook)
1246 tr.addfinalize('txnclose-hook', txnclosehook)
1247 tr.addfinalize('txnclose-hook', txnclosehook)
1247 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1248 tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
1248 def txnaborthook(tr2):
1249 def txnaborthook(tr2):
1249 """To be run if transaction is aborted
1250 """To be run if transaction is aborted
1250 """
1251 """
1251 reporef().hook('txnabort', throw=False, txnname=desc,
1252 reporef().hook('txnabort', throw=False, txnname=desc,
1252 **tr2.hookargs)
1253 **tr2.hookargs)
1253 tr.addabort('txnabort-hook', txnaborthook)
1254 tr.addabort('txnabort-hook', txnaborthook)
1254 # avoid eager cache invalidation. in-memory data should be identical
1255 # avoid eager cache invalidation. in-memory data should be identical
1255 # to stored data if transaction has no error.
1256 # to stored data if transaction has no error.
1256 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1257 tr.addpostclose('refresh-filecachestats', self._refreshfilecachestats)
1257 self._transref = weakref.ref(tr)
1258 self._transref = weakref.ref(tr)
1259 scmutil.registersummarycallback(self, tr, desc)
1258 return tr
1260 return tr
1259
1261
1260 def _journalfiles(self):
1262 def _journalfiles(self):
1261 return ((self.svfs, 'journal'),
1263 return ((self.svfs, 'journal'),
1262 (self.vfs, 'journal.dirstate'),
1264 (self.vfs, 'journal.dirstate'),
1263 (self.vfs, 'journal.branch'),
1265 (self.vfs, 'journal.branch'),
1264 (self.vfs, 'journal.desc'),
1266 (self.vfs, 'journal.desc'),
1265 (self.vfs, 'journal.bookmarks'),
1267 (self.vfs, 'journal.bookmarks'),
1266 (self.svfs, 'journal.phaseroots'))
1268 (self.svfs, 'journal.phaseroots'))
1267
1269
1268 def undofiles(self):
1270 def undofiles(self):
1269 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1271 return [(vfs, undoname(x)) for vfs, x in self._journalfiles()]
1270
1272
1271 @unfilteredmethod
1273 @unfilteredmethod
1272 def _writejournal(self, desc):
1274 def _writejournal(self, desc):
1273 self.dirstate.savebackup(None, 'journal.dirstate')
1275 self.dirstate.savebackup(None, 'journal.dirstate')
1274 self.vfs.write("journal.branch",
1276 self.vfs.write("journal.branch",
1275 encoding.fromlocal(self.dirstate.branch()))
1277 encoding.fromlocal(self.dirstate.branch()))
1276 self.vfs.write("journal.desc",
1278 self.vfs.write("journal.desc",
1277 "%d\n%s\n" % (len(self), desc))
1279 "%d\n%s\n" % (len(self), desc))
1278 self.vfs.write("journal.bookmarks",
1280 self.vfs.write("journal.bookmarks",
1279 self.vfs.tryread("bookmarks"))
1281 self.vfs.tryread("bookmarks"))
1280 self.svfs.write("journal.phaseroots",
1282 self.svfs.write("journal.phaseroots",
1281 self.svfs.tryread("phaseroots"))
1283 self.svfs.tryread("phaseroots"))
1282
1284
1283 def recover(self):
1285 def recover(self):
1284 with self.lock():
1286 with self.lock():
1285 if self.svfs.exists("journal"):
1287 if self.svfs.exists("journal"):
1286 self.ui.status(_("rolling back interrupted transaction\n"))
1288 self.ui.status(_("rolling back interrupted transaction\n"))
1287 vfsmap = {'': self.svfs,
1289 vfsmap = {'': self.svfs,
1288 'plain': self.vfs,}
1290 'plain': self.vfs,}
1289 transaction.rollback(self.svfs, vfsmap, "journal",
1291 transaction.rollback(self.svfs, vfsmap, "journal",
1290 self.ui.warn,
1292 self.ui.warn,
1291 checkambigfiles=_cachedfiles)
1293 checkambigfiles=_cachedfiles)
1292 self.invalidate()
1294 self.invalidate()
1293 return True
1295 return True
1294 else:
1296 else:
1295 self.ui.warn(_("no interrupted transaction available\n"))
1297 self.ui.warn(_("no interrupted transaction available\n"))
1296 return False
1298 return False
1297
1299
1298 def rollback(self, dryrun=False, force=False):
1300 def rollback(self, dryrun=False, force=False):
1299 wlock = lock = dsguard = None
1301 wlock = lock = dsguard = None
1300 try:
1302 try:
1301 wlock = self.wlock()
1303 wlock = self.wlock()
1302 lock = self.lock()
1304 lock = self.lock()
1303 if self.svfs.exists("undo"):
1305 if self.svfs.exists("undo"):
1304 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1306 dsguard = dirstateguard.dirstateguard(self, 'rollback')
1305
1307
1306 return self._rollback(dryrun, force, dsguard)
1308 return self._rollback(dryrun, force, dsguard)
1307 else:
1309 else:
1308 self.ui.warn(_("no rollback information available\n"))
1310 self.ui.warn(_("no rollback information available\n"))
1309 return 1
1311 return 1
1310 finally:
1312 finally:
1311 release(dsguard, lock, wlock)
1313 release(dsguard, lock, wlock)
1312
1314
1313 @unfilteredmethod # Until we get smarter cache management
1315 @unfilteredmethod # Until we get smarter cache management
1314 def _rollback(self, dryrun, force, dsguard):
1316 def _rollback(self, dryrun, force, dsguard):
1315 ui = self.ui
1317 ui = self.ui
1316 try:
1318 try:
1317 args = self.vfs.read('undo.desc').splitlines()
1319 args = self.vfs.read('undo.desc').splitlines()
1318 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1320 (oldlen, desc, detail) = (int(args[0]), args[1], None)
1319 if len(args) >= 3:
1321 if len(args) >= 3:
1320 detail = args[2]
1322 detail = args[2]
1321 oldtip = oldlen - 1
1323 oldtip = oldlen - 1
1322
1324
1323 if detail and ui.verbose:
1325 if detail and ui.verbose:
1324 msg = (_('repository tip rolled back to revision %d'
1326 msg = (_('repository tip rolled back to revision %d'
1325 ' (undo %s: %s)\n')
1327 ' (undo %s: %s)\n')
1326 % (oldtip, desc, detail))
1328 % (oldtip, desc, detail))
1327 else:
1329 else:
1328 msg = (_('repository tip rolled back to revision %d'
1330 msg = (_('repository tip rolled back to revision %d'
1329 ' (undo %s)\n')
1331 ' (undo %s)\n')
1330 % (oldtip, desc))
1332 % (oldtip, desc))
1331 except IOError:
1333 except IOError:
1332 msg = _('rolling back unknown transaction\n')
1334 msg = _('rolling back unknown transaction\n')
1333 desc = None
1335 desc = None
1334
1336
1335 if not force and self['.'] != self['tip'] and desc == 'commit':
1337 if not force and self['.'] != self['tip'] and desc == 'commit':
1336 raise error.Abort(
1338 raise error.Abort(
1337 _('rollback of last commit while not checked out '
1339 _('rollback of last commit while not checked out '
1338 'may lose data'), hint=_('use -f to force'))
1340 'may lose data'), hint=_('use -f to force'))
1339
1341
1340 ui.status(msg)
1342 ui.status(msg)
1341 if dryrun:
1343 if dryrun:
1342 return 0
1344 return 0
1343
1345
1344 parents = self.dirstate.parents()
1346 parents = self.dirstate.parents()
1345 self.destroying()
1347 self.destroying()
1346 vfsmap = {'plain': self.vfs, '': self.svfs}
1348 vfsmap = {'plain': self.vfs, '': self.svfs}
1347 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1349 transaction.rollback(self.svfs, vfsmap, 'undo', ui.warn,
1348 checkambigfiles=_cachedfiles)
1350 checkambigfiles=_cachedfiles)
1349 if self.vfs.exists('undo.bookmarks'):
1351 if self.vfs.exists('undo.bookmarks'):
1350 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1352 self.vfs.rename('undo.bookmarks', 'bookmarks', checkambig=True)
1351 if self.svfs.exists('undo.phaseroots'):
1353 if self.svfs.exists('undo.phaseroots'):
1352 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1354 self.svfs.rename('undo.phaseroots', 'phaseroots', checkambig=True)
1353 self.invalidate()
1355 self.invalidate()
1354
1356
1355 parentgone = (parents[0] not in self.changelog.nodemap or
1357 parentgone = (parents[0] not in self.changelog.nodemap or
1356 parents[1] not in self.changelog.nodemap)
1358 parents[1] not in self.changelog.nodemap)
1357 if parentgone:
1359 if parentgone:
1358 # prevent dirstateguard from overwriting already restored one
1360 # prevent dirstateguard from overwriting already restored one
1359 dsguard.close()
1361 dsguard.close()
1360
1362
1361 self.dirstate.restorebackup(None, 'undo.dirstate')
1363 self.dirstate.restorebackup(None, 'undo.dirstate')
1362 try:
1364 try:
1363 branch = self.vfs.read('undo.branch')
1365 branch = self.vfs.read('undo.branch')
1364 self.dirstate.setbranch(encoding.tolocal(branch))
1366 self.dirstate.setbranch(encoding.tolocal(branch))
1365 except IOError:
1367 except IOError:
1366 ui.warn(_('named branch could not be reset: '
1368 ui.warn(_('named branch could not be reset: '
1367 'current branch is still \'%s\'\n')
1369 'current branch is still \'%s\'\n')
1368 % self.dirstate.branch())
1370 % self.dirstate.branch())
1369
1371
1370 parents = tuple([p.rev() for p in self[None].parents()])
1372 parents = tuple([p.rev() for p in self[None].parents()])
1371 if len(parents) > 1:
1373 if len(parents) > 1:
1372 ui.status(_('working directory now based on '
1374 ui.status(_('working directory now based on '
1373 'revisions %d and %d\n') % parents)
1375 'revisions %d and %d\n') % parents)
1374 else:
1376 else:
1375 ui.status(_('working directory now based on '
1377 ui.status(_('working directory now based on '
1376 'revision %d\n') % parents)
1378 'revision %d\n') % parents)
1377 mergemod.mergestate.clean(self, self['.'].node())
1379 mergemod.mergestate.clean(self, self['.'].node())
1378
1380
1379 # TODO: if we know which new heads may result from this rollback, pass
1381 # TODO: if we know which new heads may result from this rollback, pass
1380 # them to destroy(), which will prevent the branchhead cache from being
1382 # them to destroy(), which will prevent the branchhead cache from being
1381 # invalidated.
1383 # invalidated.
1382 self.destroyed()
1384 self.destroyed()
1383 return 0
1385 return 0
1384
1386
1385 def _buildcacheupdater(self, newtransaction):
1387 def _buildcacheupdater(self, newtransaction):
1386 """called during transaction to build the callback updating cache
1388 """called during transaction to build the callback updating cache
1387
1389
1388 Lives on the repository to help extension who might want to augment
1390 Lives on the repository to help extension who might want to augment
1389 this logic. For this purpose, the created transaction is passed to the
1391 this logic. For this purpose, the created transaction is passed to the
1390 method.
1392 method.
1391 """
1393 """
1392 # we must avoid cyclic reference between repo and transaction.
1394 # we must avoid cyclic reference between repo and transaction.
1393 reporef = weakref.ref(self)
1395 reporef = weakref.ref(self)
1394 def updater(tr):
1396 def updater(tr):
1395 repo = reporef()
1397 repo = reporef()
1396 repo.updatecaches(tr)
1398 repo.updatecaches(tr)
1397 return updater
1399 return updater
1398
1400
1399 @unfilteredmethod
1401 @unfilteredmethod
1400 def updatecaches(self, tr=None):
1402 def updatecaches(self, tr=None):
1401 """warm appropriate caches
1403 """warm appropriate caches
1402
1404
1403 If this function is called after a transaction closed. The transaction
1405 If this function is called after a transaction closed. The transaction
1404 will be available in the 'tr' argument. This can be used to selectively
1406 will be available in the 'tr' argument. This can be used to selectively
1405 update caches relevant to the changes in that transaction.
1407 update caches relevant to the changes in that transaction.
1406 """
1408 """
1407 if tr is not None and tr.hookargs.get('source') == 'strip':
1409 if tr is not None and tr.hookargs.get('source') == 'strip':
1408 # During strip, many caches are invalid but
1410 # During strip, many caches are invalid but
1409 # later call to `destroyed` will refresh them.
1411 # later call to `destroyed` will refresh them.
1410 return
1412 return
1411
1413
1412 if tr is None or tr.changes['revs']:
1414 if tr is None or tr.changes['revs']:
1413 # updating the unfiltered branchmap should refresh all the others,
1415 # updating the unfiltered branchmap should refresh all the others,
1414 self.ui.debug('updating the branch cache\n')
1416 self.ui.debug('updating the branch cache\n')
1415 branchmap.updatecache(self.filtered('served'))
1417 branchmap.updatecache(self.filtered('served'))
1416
1418
1417 def invalidatecaches(self):
1419 def invalidatecaches(self):
1418
1420
1419 if '_tagscache' in vars(self):
1421 if '_tagscache' in vars(self):
1420 # can't use delattr on proxy
1422 # can't use delattr on proxy
1421 del self.__dict__['_tagscache']
1423 del self.__dict__['_tagscache']
1422
1424
1423 self.unfiltered()._branchcaches.clear()
1425 self.unfiltered()._branchcaches.clear()
1424 self.invalidatevolatilesets()
1426 self.invalidatevolatilesets()
1425 self._sparsesignaturecache.clear()
1427 self._sparsesignaturecache.clear()
1426
1428
1427 def invalidatevolatilesets(self):
1429 def invalidatevolatilesets(self):
1428 self.filteredrevcache.clear()
1430 self.filteredrevcache.clear()
1429 obsolete.clearobscaches(self)
1431 obsolete.clearobscaches(self)
1430
1432
1431 def invalidatedirstate(self):
1433 def invalidatedirstate(self):
1432 '''Invalidates the dirstate, causing the next call to dirstate
1434 '''Invalidates the dirstate, causing the next call to dirstate
1433 to check if it was modified since the last time it was read,
1435 to check if it was modified since the last time it was read,
1434 rereading it if it has.
1436 rereading it if it has.
1435
1437
1436 This is different to dirstate.invalidate() that it doesn't always
1438 This is different to dirstate.invalidate() that it doesn't always
1437 rereads the dirstate. Use dirstate.invalidate() if you want to
1439 rereads the dirstate. Use dirstate.invalidate() if you want to
1438 explicitly read the dirstate again (i.e. restoring it to a previous
1440 explicitly read the dirstate again (i.e. restoring it to a previous
1439 known good state).'''
1441 known good state).'''
1440 if hasunfilteredcache(self, 'dirstate'):
1442 if hasunfilteredcache(self, 'dirstate'):
1441 for k in self.dirstate._filecache:
1443 for k in self.dirstate._filecache:
1442 try:
1444 try:
1443 delattr(self.dirstate, k)
1445 delattr(self.dirstate, k)
1444 except AttributeError:
1446 except AttributeError:
1445 pass
1447 pass
1446 delattr(self.unfiltered(), 'dirstate')
1448 delattr(self.unfiltered(), 'dirstate')
1447
1449
1448 def invalidate(self, clearfilecache=False):
1450 def invalidate(self, clearfilecache=False):
1449 '''Invalidates both store and non-store parts other than dirstate
1451 '''Invalidates both store and non-store parts other than dirstate
1450
1452
1451 If a transaction is running, invalidation of store is omitted,
1453 If a transaction is running, invalidation of store is omitted,
1452 because discarding in-memory changes might cause inconsistency
1454 because discarding in-memory changes might cause inconsistency
1453 (e.g. incomplete fncache causes unintentional failure, but
1455 (e.g. incomplete fncache causes unintentional failure, but
1454 redundant one doesn't).
1456 redundant one doesn't).
1455 '''
1457 '''
1456 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1458 unfiltered = self.unfiltered() # all file caches are stored unfiltered
1457 for k in list(self._filecache.keys()):
1459 for k in list(self._filecache.keys()):
1458 # dirstate is invalidated separately in invalidatedirstate()
1460 # dirstate is invalidated separately in invalidatedirstate()
1459 if k == 'dirstate':
1461 if k == 'dirstate':
1460 continue
1462 continue
1461
1463
1462 if clearfilecache:
1464 if clearfilecache:
1463 del self._filecache[k]
1465 del self._filecache[k]
1464 try:
1466 try:
1465 delattr(unfiltered, k)
1467 delattr(unfiltered, k)
1466 except AttributeError:
1468 except AttributeError:
1467 pass
1469 pass
1468 self.invalidatecaches()
1470 self.invalidatecaches()
1469 if not self.currenttransaction():
1471 if not self.currenttransaction():
1470 # TODO: Changing contents of store outside transaction
1472 # TODO: Changing contents of store outside transaction
1471 # causes inconsistency. We should make in-memory store
1473 # causes inconsistency. We should make in-memory store
1472 # changes detectable, and abort if changed.
1474 # changes detectable, and abort if changed.
1473 self.store.invalidatecaches()
1475 self.store.invalidatecaches()
1474
1476
1475 def invalidateall(self):
1477 def invalidateall(self):
1476 '''Fully invalidates both store and non-store parts, causing the
1478 '''Fully invalidates both store and non-store parts, causing the
1477 subsequent operation to reread any outside changes.'''
1479 subsequent operation to reread any outside changes.'''
1478 # extension should hook this to invalidate its caches
1480 # extension should hook this to invalidate its caches
1479 self.invalidate()
1481 self.invalidate()
1480 self.invalidatedirstate()
1482 self.invalidatedirstate()
1481
1483
1482 @unfilteredmethod
1484 @unfilteredmethod
1483 def _refreshfilecachestats(self, tr):
1485 def _refreshfilecachestats(self, tr):
1484 """Reload stats of cached files so that they are flagged as valid"""
1486 """Reload stats of cached files so that they are flagged as valid"""
1485 for k, ce in self._filecache.items():
1487 for k, ce in self._filecache.items():
1486 if k == 'dirstate' or k not in self.__dict__:
1488 if k == 'dirstate' or k not in self.__dict__:
1487 continue
1489 continue
1488 ce.refresh()
1490 ce.refresh()
1489
1491
1490 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1492 def _lock(self, vfs, lockname, wait, releasefn, acquirefn, desc,
1491 inheritchecker=None, parentenvvar=None):
1493 inheritchecker=None, parentenvvar=None):
1492 parentlock = None
1494 parentlock = None
1493 # the contents of parentenvvar are used by the underlying lock to
1495 # the contents of parentenvvar are used by the underlying lock to
1494 # determine whether it can be inherited
1496 # determine whether it can be inherited
1495 if parentenvvar is not None:
1497 if parentenvvar is not None:
1496 parentlock = encoding.environ.get(parentenvvar)
1498 parentlock = encoding.environ.get(parentenvvar)
1497 try:
1499 try:
1498 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1500 l = lockmod.lock(vfs, lockname, 0, releasefn=releasefn,
1499 acquirefn=acquirefn, desc=desc,
1501 acquirefn=acquirefn, desc=desc,
1500 inheritchecker=inheritchecker,
1502 inheritchecker=inheritchecker,
1501 parentlock=parentlock)
1503 parentlock=parentlock)
1502 except error.LockHeld as inst:
1504 except error.LockHeld as inst:
1503 if not wait:
1505 if not wait:
1504 raise
1506 raise
1505 # show more details for new-style locks
1507 # show more details for new-style locks
1506 if ':' in inst.locker:
1508 if ':' in inst.locker:
1507 host, pid = inst.locker.split(":", 1)
1509 host, pid = inst.locker.split(":", 1)
1508 self.ui.warn(
1510 self.ui.warn(
1509 _("waiting for lock on %s held by process %r "
1511 _("waiting for lock on %s held by process %r "
1510 "on host %r\n") % (desc, pid, host))
1512 "on host %r\n") % (desc, pid, host))
1511 else:
1513 else:
1512 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1514 self.ui.warn(_("waiting for lock on %s held by %r\n") %
1513 (desc, inst.locker))
1515 (desc, inst.locker))
1514 # default to 600 seconds timeout
1516 # default to 600 seconds timeout
1515 l = lockmod.lock(vfs, lockname,
1517 l = lockmod.lock(vfs, lockname,
1516 int(self.ui.config("ui", "timeout")),
1518 int(self.ui.config("ui", "timeout")),
1517 releasefn=releasefn, acquirefn=acquirefn,
1519 releasefn=releasefn, acquirefn=acquirefn,
1518 desc=desc)
1520 desc=desc)
1519 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1521 self.ui.warn(_("got lock after %s seconds\n") % l.delay)
1520 return l
1522 return l
1521
1523
1522 def _afterlock(self, callback):
1524 def _afterlock(self, callback):
1523 """add a callback to be run when the repository is fully unlocked
1525 """add a callback to be run when the repository is fully unlocked
1524
1526
1525 The callback will be executed when the outermost lock is released
1527 The callback will be executed when the outermost lock is released
1526 (with wlock being higher level than 'lock')."""
1528 (with wlock being higher level than 'lock')."""
1527 for ref in (self._wlockref, self._lockref):
1529 for ref in (self._wlockref, self._lockref):
1528 l = ref and ref()
1530 l = ref and ref()
1529 if l and l.held:
1531 if l and l.held:
1530 l.postrelease.append(callback)
1532 l.postrelease.append(callback)
1531 break
1533 break
1532 else: # no lock have been found.
1534 else: # no lock have been found.
1533 callback()
1535 callback()
1534
1536
1535 def lock(self, wait=True):
1537 def lock(self, wait=True):
1536 '''Lock the repository store (.hg/store) and return a weak reference
1538 '''Lock the repository store (.hg/store) and return a weak reference
1537 to the lock. Use this before modifying the store (e.g. committing or
1539 to the lock. Use this before modifying the store (e.g. committing or
1538 stripping). If you are opening a transaction, get a lock as well.)
1540 stripping). If you are opening a transaction, get a lock as well.)
1539
1541
1540 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1542 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1541 'wlock' first to avoid a dead-lock hazard.'''
1543 'wlock' first to avoid a dead-lock hazard.'''
1542 l = self._currentlock(self._lockref)
1544 l = self._currentlock(self._lockref)
1543 if l is not None:
1545 if l is not None:
1544 l.lock()
1546 l.lock()
1545 return l
1547 return l
1546
1548
1547 l = self._lock(self.svfs, "lock", wait, None,
1549 l = self._lock(self.svfs, "lock", wait, None,
1548 self.invalidate, _('repository %s') % self.origroot)
1550 self.invalidate, _('repository %s') % self.origroot)
1549 self._lockref = weakref.ref(l)
1551 self._lockref = weakref.ref(l)
1550 return l
1552 return l
1551
1553
1552 def _wlockchecktransaction(self):
1554 def _wlockchecktransaction(self):
1553 if self.currenttransaction() is not None:
1555 if self.currenttransaction() is not None:
1554 raise error.LockInheritanceContractViolation(
1556 raise error.LockInheritanceContractViolation(
1555 'wlock cannot be inherited in the middle of a transaction')
1557 'wlock cannot be inherited in the middle of a transaction')
1556
1558
1557 def wlock(self, wait=True):
1559 def wlock(self, wait=True):
1558 '''Lock the non-store parts of the repository (everything under
1560 '''Lock the non-store parts of the repository (everything under
1559 .hg except .hg/store) and return a weak reference to the lock.
1561 .hg except .hg/store) and return a weak reference to the lock.
1560
1562
1561 Use this before modifying files in .hg.
1563 Use this before modifying files in .hg.
1562
1564
1563 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1565 If both 'lock' and 'wlock' must be acquired, ensure you always acquires
1564 'wlock' first to avoid a dead-lock hazard.'''
1566 'wlock' first to avoid a dead-lock hazard.'''
1565 l = self._wlockref and self._wlockref()
1567 l = self._wlockref and self._wlockref()
1566 if l is not None and l.held:
1568 if l is not None and l.held:
1567 l.lock()
1569 l.lock()
1568 return l
1570 return l
1569
1571
1570 # We do not need to check for non-waiting lock acquisition. Such
1572 # We do not need to check for non-waiting lock acquisition. Such
1571 # acquisition would not cause dead-lock as they would just fail.
1573 # acquisition would not cause dead-lock as they would just fail.
1572 if wait and (self.ui.configbool('devel', 'all-warnings')
1574 if wait and (self.ui.configbool('devel', 'all-warnings')
1573 or self.ui.configbool('devel', 'check-locks')):
1575 or self.ui.configbool('devel', 'check-locks')):
1574 if self._currentlock(self._lockref) is not None:
1576 if self._currentlock(self._lockref) is not None:
1575 self.ui.develwarn('"wlock" acquired after "lock"')
1577 self.ui.develwarn('"wlock" acquired after "lock"')
1576
1578
1577 def unlock():
1579 def unlock():
1578 if self.dirstate.pendingparentchange():
1580 if self.dirstate.pendingparentchange():
1579 self.dirstate.invalidate()
1581 self.dirstate.invalidate()
1580 else:
1582 else:
1581 self.dirstate.write(None)
1583 self.dirstate.write(None)
1582
1584
1583 self._filecache['dirstate'].refresh()
1585 self._filecache['dirstate'].refresh()
1584
1586
1585 l = self._lock(self.vfs, "wlock", wait, unlock,
1587 l = self._lock(self.vfs, "wlock", wait, unlock,
1586 self.invalidatedirstate, _('working directory of %s') %
1588 self.invalidatedirstate, _('working directory of %s') %
1587 self.origroot,
1589 self.origroot,
1588 inheritchecker=self._wlockchecktransaction,
1590 inheritchecker=self._wlockchecktransaction,
1589 parentenvvar='HG_WLOCK_LOCKER')
1591 parentenvvar='HG_WLOCK_LOCKER')
1590 self._wlockref = weakref.ref(l)
1592 self._wlockref = weakref.ref(l)
1591 return l
1593 return l
1592
1594
1593 def _currentlock(self, lockref):
1595 def _currentlock(self, lockref):
1594 """Returns the lock if it's held, or None if it's not."""
1596 """Returns the lock if it's held, or None if it's not."""
1595 if lockref is None:
1597 if lockref is None:
1596 return None
1598 return None
1597 l = lockref()
1599 l = lockref()
1598 if l is None or not l.held:
1600 if l is None or not l.held:
1599 return None
1601 return None
1600 return l
1602 return l
1601
1603
1602 def currentwlock(self):
1604 def currentwlock(self):
1603 """Returns the wlock if it's held, or None if it's not."""
1605 """Returns the wlock if it's held, or None if it's not."""
1604 return self._currentlock(self._wlockref)
1606 return self._currentlock(self._wlockref)
1605
1607
1606 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1608 def _filecommit(self, fctx, manifest1, manifest2, linkrev, tr, changelist):
1607 """
1609 """
1608 commit an individual file as part of a larger transaction
1610 commit an individual file as part of a larger transaction
1609 """
1611 """
1610
1612
1611 fname = fctx.path()
1613 fname = fctx.path()
1612 fparent1 = manifest1.get(fname, nullid)
1614 fparent1 = manifest1.get(fname, nullid)
1613 fparent2 = manifest2.get(fname, nullid)
1615 fparent2 = manifest2.get(fname, nullid)
1614 if isinstance(fctx, context.filectx):
1616 if isinstance(fctx, context.filectx):
1615 node = fctx.filenode()
1617 node = fctx.filenode()
1616 if node in [fparent1, fparent2]:
1618 if node in [fparent1, fparent2]:
1617 self.ui.debug('reusing %s filelog entry\n' % fname)
1619 self.ui.debug('reusing %s filelog entry\n' % fname)
1618 if manifest1.flags(fname) != fctx.flags():
1620 if manifest1.flags(fname) != fctx.flags():
1619 changelist.append(fname)
1621 changelist.append(fname)
1620 return node
1622 return node
1621
1623
1622 flog = self.file(fname)
1624 flog = self.file(fname)
1623 meta = {}
1625 meta = {}
1624 copy = fctx.renamed()
1626 copy = fctx.renamed()
1625 if copy and copy[0] != fname:
1627 if copy and copy[0] != fname:
1626 # Mark the new revision of this file as a copy of another
1628 # Mark the new revision of this file as a copy of another
1627 # file. This copy data will effectively act as a parent
1629 # file. This copy data will effectively act as a parent
1628 # of this new revision. If this is a merge, the first
1630 # of this new revision. If this is a merge, the first
1629 # parent will be the nullid (meaning "look up the copy data")
1631 # parent will be the nullid (meaning "look up the copy data")
1630 # and the second one will be the other parent. For example:
1632 # and the second one will be the other parent. For example:
1631 #
1633 #
1632 # 0 --- 1 --- 3 rev1 changes file foo
1634 # 0 --- 1 --- 3 rev1 changes file foo
1633 # \ / rev2 renames foo to bar and changes it
1635 # \ / rev2 renames foo to bar and changes it
1634 # \- 2 -/ rev3 should have bar with all changes and
1636 # \- 2 -/ rev3 should have bar with all changes and
1635 # should record that bar descends from
1637 # should record that bar descends from
1636 # bar in rev2 and foo in rev1
1638 # bar in rev2 and foo in rev1
1637 #
1639 #
1638 # this allows this merge to succeed:
1640 # this allows this merge to succeed:
1639 #
1641 #
1640 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1642 # 0 --- 1 --- 3 rev4 reverts the content change from rev2
1641 # \ / merging rev3 and rev4 should use bar@rev2
1643 # \ / merging rev3 and rev4 should use bar@rev2
1642 # \- 2 --- 4 as the merge base
1644 # \- 2 --- 4 as the merge base
1643 #
1645 #
1644
1646
1645 cfname = copy[0]
1647 cfname = copy[0]
1646 crev = manifest1.get(cfname)
1648 crev = manifest1.get(cfname)
1647 newfparent = fparent2
1649 newfparent = fparent2
1648
1650
1649 if manifest2: # branch merge
1651 if manifest2: # branch merge
1650 if fparent2 == nullid or crev is None: # copied on remote side
1652 if fparent2 == nullid or crev is None: # copied on remote side
1651 if cfname in manifest2:
1653 if cfname in manifest2:
1652 crev = manifest2[cfname]
1654 crev = manifest2[cfname]
1653 newfparent = fparent1
1655 newfparent = fparent1
1654
1656
1655 # Here, we used to search backwards through history to try to find
1657 # Here, we used to search backwards through history to try to find
1656 # where the file copy came from if the source of a copy was not in
1658 # where the file copy came from if the source of a copy was not in
1657 # the parent directory. However, this doesn't actually make sense to
1659 # the parent directory. However, this doesn't actually make sense to
1658 # do (what does a copy from something not in your working copy even
1660 # do (what does a copy from something not in your working copy even
1659 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1661 # mean?) and it causes bugs (eg, issue4476). Instead, we will warn
1660 # the user that copy information was dropped, so if they didn't
1662 # the user that copy information was dropped, so if they didn't
1661 # expect this outcome it can be fixed, but this is the correct
1663 # expect this outcome it can be fixed, but this is the correct
1662 # behavior in this circumstance.
1664 # behavior in this circumstance.
1663
1665
1664 if crev:
1666 if crev:
1665 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1667 self.ui.debug(" %s: copy %s:%s\n" % (fname, cfname, hex(crev)))
1666 meta["copy"] = cfname
1668 meta["copy"] = cfname
1667 meta["copyrev"] = hex(crev)
1669 meta["copyrev"] = hex(crev)
1668 fparent1, fparent2 = nullid, newfparent
1670 fparent1, fparent2 = nullid, newfparent
1669 else:
1671 else:
1670 self.ui.warn(_("warning: can't find ancestor for '%s' "
1672 self.ui.warn(_("warning: can't find ancestor for '%s' "
1671 "copied from '%s'!\n") % (fname, cfname))
1673 "copied from '%s'!\n") % (fname, cfname))
1672
1674
1673 elif fparent1 == nullid:
1675 elif fparent1 == nullid:
1674 fparent1, fparent2 = fparent2, nullid
1676 fparent1, fparent2 = fparent2, nullid
1675 elif fparent2 != nullid:
1677 elif fparent2 != nullid:
1676 # is one parent an ancestor of the other?
1678 # is one parent an ancestor of the other?
1677 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1679 fparentancestors = flog.commonancestorsheads(fparent1, fparent2)
1678 if fparent1 in fparentancestors:
1680 if fparent1 in fparentancestors:
1679 fparent1, fparent2 = fparent2, nullid
1681 fparent1, fparent2 = fparent2, nullid
1680 elif fparent2 in fparentancestors:
1682 elif fparent2 in fparentancestors:
1681 fparent2 = nullid
1683 fparent2 = nullid
1682
1684
1683 # is the file changed?
1685 # is the file changed?
1684 text = fctx.data()
1686 text = fctx.data()
1685 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1687 if fparent2 != nullid or flog.cmp(fparent1, text) or meta:
1686 changelist.append(fname)
1688 changelist.append(fname)
1687 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1689 return flog.add(text, meta, tr, linkrev, fparent1, fparent2)
1688 # are just the flags changed during merge?
1690 # are just the flags changed during merge?
1689 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1691 elif fname in manifest1 and manifest1.flags(fname) != fctx.flags():
1690 changelist.append(fname)
1692 changelist.append(fname)
1691
1693
1692 return fparent1
1694 return fparent1
1693
1695
1694 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1696 def checkcommitpatterns(self, wctx, vdirs, match, status, fail):
1695 """check for commit arguments that aren't committable"""
1697 """check for commit arguments that aren't committable"""
1696 if match.isexact() or match.prefix():
1698 if match.isexact() or match.prefix():
1697 matched = set(status.modified + status.added + status.removed)
1699 matched = set(status.modified + status.added + status.removed)
1698
1700
1699 for f in match.files():
1701 for f in match.files():
1700 f = self.dirstate.normalize(f)
1702 f = self.dirstate.normalize(f)
1701 if f == '.' or f in matched or f in wctx.substate:
1703 if f == '.' or f in matched or f in wctx.substate:
1702 continue
1704 continue
1703 if f in status.deleted:
1705 if f in status.deleted:
1704 fail(f, _('file not found!'))
1706 fail(f, _('file not found!'))
1705 if f in vdirs: # visited directory
1707 if f in vdirs: # visited directory
1706 d = f + '/'
1708 d = f + '/'
1707 for mf in matched:
1709 for mf in matched:
1708 if mf.startswith(d):
1710 if mf.startswith(d):
1709 break
1711 break
1710 else:
1712 else:
1711 fail(f, _("no match under directory!"))
1713 fail(f, _("no match under directory!"))
1712 elif f not in self.dirstate:
1714 elif f not in self.dirstate:
1713 fail(f, _("file not tracked!"))
1715 fail(f, _("file not tracked!"))
1714
1716
1715 @unfilteredmethod
1717 @unfilteredmethod
1716 def commit(self, text="", user=None, date=None, match=None, force=False,
1718 def commit(self, text="", user=None, date=None, match=None, force=False,
1717 editor=False, extra=None):
1719 editor=False, extra=None):
1718 """Add a new revision to current repository.
1720 """Add a new revision to current repository.
1719
1721
1720 Revision information is gathered from the working directory,
1722 Revision information is gathered from the working directory,
1721 match can be used to filter the committed files. If editor is
1723 match can be used to filter the committed files. If editor is
1722 supplied, it is called to get a commit message.
1724 supplied, it is called to get a commit message.
1723 """
1725 """
1724 if extra is None:
1726 if extra is None:
1725 extra = {}
1727 extra = {}
1726
1728
1727 def fail(f, msg):
1729 def fail(f, msg):
1728 raise error.Abort('%s: %s' % (f, msg))
1730 raise error.Abort('%s: %s' % (f, msg))
1729
1731
1730 if not match:
1732 if not match:
1731 match = matchmod.always(self.root, '')
1733 match = matchmod.always(self.root, '')
1732
1734
1733 if not force:
1735 if not force:
1734 vdirs = []
1736 vdirs = []
1735 match.explicitdir = vdirs.append
1737 match.explicitdir = vdirs.append
1736 match.bad = fail
1738 match.bad = fail
1737
1739
1738 wlock = lock = tr = None
1740 wlock = lock = tr = None
1739 try:
1741 try:
1740 wlock = self.wlock()
1742 wlock = self.wlock()
1741 lock = self.lock() # for recent changelog (see issue4368)
1743 lock = self.lock() # for recent changelog (see issue4368)
1742
1744
1743 wctx = self[None]
1745 wctx = self[None]
1744 merge = len(wctx.parents()) > 1
1746 merge = len(wctx.parents()) > 1
1745
1747
1746 if not force and merge and not match.always():
1748 if not force and merge and not match.always():
1747 raise error.Abort(_('cannot partially commit a merge '
1749 raise error.Abort(_('cannot partially commit a merge '
1748 '(do not specify files or patterns)'))
1750 '(do not specify files or patterns)'))
1749
1751
1750 status = self.status(match=match, clean=force)
1752 status = self.status(match=match, clean=force)
1751 if force:
1753 if force:
1752 status.modified.extend(status.clean) # mq may commit clean files
1754 status.modified.extend(status.clean) # mq may commit clean files
1753
1755
1754 # check subrepos
1756 # check subrepos
1755 subs = []
1757 subs = []
1756 commitsubs = set()
1758 commitsubs = set()
1757 newstate = wctx.substate.copy()
1759 newstate = wctx.substate.copy()
1758 # only manage subrepos and .hgsubstate if .hgsub is present
1760 # only manage subrepos and .hgsubstate if .hgsub is present
1759 if '.hgsub' in wctx:
1761 if '.hgsub' in wctx:
1760 # we'll decide whether to track this ourselves, thanks
1762 # we'll decide whether to track this ourselves, thanks
1761 for c in status.modified, status.added, status.removed:
1763 for c in status.modified, status.added, status.removed:
1762 if '.hgsubstate' in c:
1764 if '.hgsubstate' in c:
1763 c.remove('.hgsubstate')
1765 c.remove('.hgsubstate')
1764
1766
1765 # compare current state to last committed state
1767 # compare current state to last committed state
1766 # build new substate based on last committed state
1768 # build new substate based on last committed state
1767 oldstate = wctx.p1().substate
1769 oldstate = wctx.p1().substate
1768 for s in sorted(newstate.keys()):
1770 for s in sorted(newstate.keys()):
1769 if not match(s):
1771 if not match(s):
1770 # ignore working copy, use old state if present
1772 # ignore working copy, use old state if present
1771 if s in oldstate:
1773 if s in oldstate:
1772 newstate[s] = oldstate[s]
1774 newstate[s] = oldstate[s]
1773 continue
1775 continue
1774 if not force:
1776 if not force:
1775 raise error.Abort(
1777 raise error.Abort(
1776 _("commit with new subrepo %s excluded") % s)
1778 _("commit with new subrepo %s excluded") % s)
1777 dirtyreason = wctx.sub(s).dirtyreason(True)
1779 dirtyreason = wctx.sub(s).dirtyreason(True)
1778 if dirtyreason:
1780 if dirtyreason:
1779 if not self.ui.configbool('ui', 'commitsubrepos'):
1781 if not self.ui.configbool('ui', 'commitsubrepos'):
1780 raise error.Abort(dirtyreason,
1782 raise error.Abort(dirtyreason,
1781 hint=_("use --subrepos for recursive commit"))
1783 hint=_("use --subrepos for recursive commit"))
1782 subs.append(s)
1784 subs.append(s)
1783 commitsubs.add(s)
1785 commitsubs.add(s)
1784 else:
1786 else:
1785 bs = wctx.sub(s).basestate()
1787 bs = wctx.sub(s).basestate()
1786 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1788 newstate[s] = (newstate[s][0], bs, newstate[s][2])
1787 if oldstate.get(s, (None, None, None))[1] != bs:
1789 if oldstate.get(s, (None, None, None))[1] != bs:
1788 subs.append(s)
1790 subs.append(s)
1789
1791
1790 # check for removed subrepos
1792 # check for removed subrepos
1791 for p in wctx.parents():
1793 for p in wctx.parents():
1792 r = [s for s in p.substate if s not in newstate]
1794 r = [s for s in p.substate if s not in newstate]
1793 subs += [s for s in r if match(s)]
1795 subs += [s for s in r if match(s)]
1794 if subs:
1796 if subs:
1795 if (not match('.hgsub') and
1797 if (not match('.hgsub') and
1796 '.hgsub' in (wctx.modified() + wctx.added())):
1798 '.hgsub' in (wctx.modified() + wctx.added())):
1797 raise error.Abort(
1799 raise error.Abort(
1798 _("can't commit subrepos without .hgsub"))
1800 _("can't commit subrepos without .hgsub"))
1799 status.modified.insert(0, '.hgsubstate')
1801 status.modified.insert(0, '.hgsubstate')
1800
1802
1801 elif '.hgsub' in status.removed:
1803 elif '.hgsub' in status.removed:
1802 # clean up .hgsubstate when .hgsub is removed
1804 # clean up .hgsubstate when .hgsub is removed
1803 if ('.hgsubstate' in wctx and
1805 if ('.hgsubstate' in wctx and
1804 '.hgsubstate' not in (status.modified + status.added +
1806 '.hgsubstate' not in (status.modified + status.added +
1805 status.removed)):
1807 status.removed)):
1806 status.removed.insert(0, '.hgsubstate')
1808 status.removed.insert(0, '.hgsubstate')
1807
1809
1808 # make sure all explicit patterns are matched
1810 # make sure all explicit patterns are matched
1809 if not force:
1811 if not force:
1810 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1812 self.checkcommitpatterns(wctx, vdirs, match, status, fail)
1811
1813
1812 cctx = context.workingcommitctx(self, status,
1814 cctx = context.workingcommitctx(self, status,
1813 text, user, date, extra)
1815 text, user, date, extra)
1814
1816
1815 # internal config: ui.allowemptycommit
1817 # internal config: ui.allowemptycommit
1816 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1818 allowemptycommit = (wctx.branch() != wctx.p1().branch()
1817 or extra.get('close') or merge or cctx.files()
1819 or extra.get('close') or merge or cctx.files()
1818 or self.ui.configbool('ui', 'allowemptycommit'))
1820 or self.ui.configbool('ui', 'allowemptycommit'))
1819 if not allowemptycommit:
1821 if not allowemptycommit:
1820 return None
1822 return None
1821
1823
1822 if merge and cctx.deleted():
1824 if merge and cctx.deleted():
1823 raise error.Abort(_("cannot commit merge with missing files"))
1825 raise error.Abort(_("cannot commit merge with missing files"))
1824
1826
1825 ms = mergemod.mergestate.read(self)
1827 ms = mergemod.mergestate.read(self)
1826 mergeutil.checkunresolved(ms)
1828 mergeutil.checkunresolved(ms)
1827
1829
1828 if editor:
1830 if editor:
1829 cctx._text = editor(self, cctx, subs)
1831 cctx._text = editor(self, cctx, subs)
1830 edited = (text != cctx._text)
1832 edited = (text != cctx._text)
1831
1833
1832 # Save commit message in case this transaction gets rolled back
1834 # Save commit message in case this transaction gets rolled back
1833 # (e.g. by a pretxncommit hook). Leave the content alone on
1835 # (e.g. by a pretxncommit hook). Leave the content alone on
1834 # the assumption that the user will use the same editor again.
1836 # the assumption that the user will use the same editor again.
1835 msgfn = self.savecommitmessage(cctx._text)
1837 msgfn = self.savecommitmessage(cctx._text)
1836
1838
1837 # commit subs and write new state
1839 # commit subs and write new state
1838 if subs:
1840 if subs:
1839 for s in sorted(commitsubs):
1841 for s in sorted(commitsubs):
1840 sub = wctx.sub(s)
1842 sub = wctx.sub(s)
1841 self.ui.status(_('committing subrepository %s\n') %
1843 self.ui.status(_('committing subrepository %s\n') %
1842 subrepo.subrelpath(sub))
1844 subrepo.subrelpath(sub))
1843 sr = sub.commit(cctx._text, user, date)
1845 sr = sub.commit(cctx._text, user, date)
1844 newstate[s] = (newstate[s][0], sr)
1846 newstate[s] = (newstate[s][0], sr)
1845 subrepo.writestate(self, newstate)
1847 subrepo.writestate(self, newstate)
1846
1848
1847 p1, p2 = self.dirstate.parents()
1849 p1, p2 = self.dirstate.parents()
1848 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1850 hookp1, hookp2 = hex(p1), (p2 != nullid and hex(p2) or '')
1849 try:
1851 try:
1850 self.hook("precommit", throw=True, parent1=hookp1,
1852 self.hook("precommit", throw=True, parent1=hookp1,
1851 parent2=hookp2)
1853 parent2=hookp2)
1852 tr = self.transaction('commit')
1854 tr = self.transaction('commit')
1853 ret = self.commitctx(cctx, True)
1855 ret = self.commitctx(cctx, True)
1854 except: # re-raises
1856 except: # re-raises
1855 if edited:
1857 if edited:
1856 self.ui.write(
1858 self.ui.write(
1857 _('note: commit message saved in %s\n') % msgfn)
1859 _('note: commit message saved in %s\n') % msgfn)
1858 raise
1860 raise
1859 # update bookmarks, dirstate and mergestate
1861 # update bookmarks, dirstate and mergestate
1860 bookmarks.update(self, [p1, p2], ret)
1862 bookmarks.update(self, [p1, p2], ret)
1861 cctx.markcommitted(ret)
1863 cctx.markcommitted(ret)
1862 ms.reset()
1864 ms.reset()
1863 tr.close()
1865 tr.close()
1864
1866
1865 finally:
1867 finally:
1866 lockmod.release(tr, lock, wlock)
1868 lockmod.release(tr, lock, wlock)
1867
1869
1868 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1870 def commithook(node=hex(ret), parent1=hookp1, parent2=hookp2):
1869 # hack for command that use a temporary commit (eg: histedit)
1871 # hack for command that use a temporary commit (eg: histedit)
1870 # temporary commit got stripped before hook release
1872 # temporary commit got stripped before hook release
1871 if self.changelog.hasnode(ret):
1873 if self.changelog.hasnode(ret):
1872 self.hook("commit", node=node, parent1=parent1,
1874 self.hook("commit", node=node, parent1=parent1,
1873 parent2=parent2)
1875 parent2=parent2)
1874 self._afterlock(commithook)
1876 self._afterlock(commithook)
1875 return ret
1877 return ret
1876
1878
1877 @unfilteredmethod
1879 @unfilteredmethod
1878 def commitctx(self, ctx, error=False):
1880 def commitctx(self, ctx, error=False):
1879 """Add a new revision to current repository.
1881 """Add a new revision to current repository.
1880 Revision information is passed via the context argument.
1882 Revision information is passed via the context argument.
1881 """
1883 """
1882
1884
1883 tr = None
1885 tr = None
1884 p1, p2 = ctx.p1(), ctx.p2()
1886 p1, p2 = ctx.p1(), ctx.p2()
1885 user = ctx.user()
1887 user = ctx.user()
1886
1888
1887 lock = self.lock()
1889 lock = self.lock()
1888 try:
1890 try:
1889 tr = self.transaction("commit")
1891 tr = self.transaction("commit")
1890 trp = weakref.proxy(tr)
1892 trp = weakref.proxy(tr)
1891
1893
1892 if ctx.manifestnode():
1894 if ctx.manifestnode():
1893 # reuse an existing manifest revision
1895 # reuse an existing manifest revision
1894 mn = ctx.manifestnode()
1896 mn = ctx.manifestnode()
1895 files = ctx.files()
1897 files = ctx.files()
1896 elif ctx.files():
1898 elif ctx.files():
1897 m1ctx = p1.manifestctx()
1899 m1ctx = p1.manifestctx()
1898 m2ctx = p2.manifestctx()
1900 m2ctx = p2.manifestctx()
1899 mctx = m1ctx.copy()
1901 mctx = m1ctx.copy()
1900
1902
1901 m = mctx.read()
1903 m = mctx.read()
1902 m1 = m1ctx.read()
1904 m1 = m1ctx.read()
1903 m2 = m2ctx.read()
1905 m2 = m2ctx.read()
1904
1906
1905 # check in files
1907 # check in files
1906 added = []
1908 added = []
1907 changed = []
1909 changed = []
1908 removed = list(ctx.removed())
1910 removed = list(ctx.removed())
1909 linkrev = len(self)
1911 linkrev = len(self)
1910 self.ui.note(_("committing files:\n"))
1912 self.ui.note(_("committing files:\n"))
1911 for f in sorted(ctx.modified() + ctx.added()):
1913 for f in sorted(ctx.modified() + ctx.added()):
1912 self.ui.note(f + "\n")
1914 self.ui.note(f + "\n")
1913 try:
1915 try:
1914 fctx = ctx[f]
1916 fctx = ctx[f]
1915 if fctx is None:
1917 if fctx is None:
1916 removed.append(f)
1918 removed.append(f)
1917 else:
1919 else:
1918 added.append(f)
1920 added.append(f)
1919 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1921 m[f] = self._filecommit(fctx, m1, m2, linkrev,
1920 trp, changed)
1922 trp, changed)
1921 m.setflag(f, fctx.flags())
1923 m.setflag(f, fctx.flags())
1922 except OSError as inst:
1924 except OSError as inst:
1923 self.ui.warn(_("trouble committing %s!\n") % f)
1925 self.ui.warn(_("trouble committing %s!\n") % f)
1924 raise
1926 raise
1925 except IOError as inst:
1927 except IOError as inst:
1926 errcode = getattr(inst, 'errno', errno.ENOENT)
1928 errcode = getattr(inst, 'errno', errno.ENOENT)
1927 if error or errcode and errcode != errno.ENOENT:
1929 if error or errcode and errcode != errno.ENOENT:
1928 self.ui.warn(_("trouble committing %s!\n") % f)
1930 self.ui.warn(_("trouble committing %s!\n") % f)
1929 raise
1931 raise
1930
1932
1931 # update manifest
1933 # update manifest
1932 self.ui.note(_("committing manifest\n"))
1934 self.ui.note(_("committing manifest\n"))
1933 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1935 removed = [f for f in sorted(removed) if f in m1 or f in m2]
1934 drop = [f for f in removed if f in m]
1936 drop = [f for f in removed if f in m]
1935 for f in drop:
1937 for f in drop:
1936 del m[f]
1938 del m[f]
1937 mn = mctx.write(trp, linkrev,
1939 mn = mctx.write(trp, linkrev,
1938 p1.manifestnode(), p2.manifestnode(),
1940 p1.manifestnode(), p2.manifestnode(),
1939 added, drop)
1941 added, drop)
1940 files = changed + removed
1942 files = changed + removed
1941 else:
1943 else:
1942 mn = p1.manifestnode()
1944 mn = p1.manifestnode()
1943 files = []
1945 files = []
1944
1946
1945 # update changelog
1947 # update changelog
1946 self.ui.note(_("committing changelog\n"))
1948 self.ui.note(_("committing changelog\n"))
1947 self.changelog.delayupdate(tr)
1949 self.changelog.delayupdate(tr)
1948 n = self.changelog.add(mn, files, ctx.description(),
1950 n = self.changelog.add(mn, files, ctx.description(),
1949 trp, p1.node(), p2.node(),
1951 trp, p1.node(), p2.node(),
1950 user, ctx.date(), ctx.extra().copy())
1952 user, ctx.date(), ctx.extra().copy())
1951 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1953 xp1, xp2 = p1.hex(), p2 and p2.hex() or ''
1952 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1954 self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1,
1953 parent2=xp2)
1955 parent2=xp2)
1954 # set the new commit is proper phase
1956 # set the new commit is proper phase
1955 targetphase = subrepo.newcommitphase(self.ui, ctx)
1957 targetphase = subrepo.newcommitphase(self.ui, ctx)
1956 if targetphase:
1958 if targetphase:
1957 # retract boundary do not alter parent changeset.
1959 # retract boundary do not alter parent changeset.
1958 # if a parent have higher the resulting phase will
1960 # if a parent have higher the resulting phase will
1959 # be compliant anyway
1961 # be compliant anyway
1960 #
1962 #
1961 # if minimal phase was 0 we don't need to retract anything
1963 # if minimal phase was 0 we don't need to retract anything
1962 phases.registernew(self, tr, targetphase, [n])
1964 phases.registernew(self, tr, targetphase, [n])
1963 tr.close()
1965 tr.close()
1964 return n
1966 return n
1965 finally:
1967 finally:
1966 if tr:
1968 if tr:
1967 tr.release()
1969 tr.release()
1968 lock.release()
1970 lock.release()
1969
1971
1970 @unfilteredmethod
1972 @unfilteredmethod
1971 def destroying(self):
1973 def destroying(self):
1972 '''Inform the repository that nodes are about to be destroyed.
1974 '''Inform the repository that nodes are about to be destroyed.
1973 Intended for use by strip and rollback, so there's a common
1975 Intended for use by strip and rollback, so there's a common
1974 place for anything that has to be done before destroying history.
1976 place for anything that has to be done before destroying history.
1975
1977
1976 This is mostly useful for saving state that is in memory and waiting
1978 This is mostly useful for saving state that is in memory and waiting
1977 to be flushed when the current lock is released. Because a call to
1979 to be flushed when the current lock is released. Because a call to
1978 destroyed is imminent, the repo will be invalidated causing those
1980 destroyed is imminent, the repo will be invalidated causing those
1979 changes to stay in memory (waiting for the next unlock), or vanish
1981 changes to stay in memory (waiting for the next unlock), or vanish
1980 completely.
1982 completely.
1981 '''
1983 '''
1982 # When using the same lock to commit and strip, the phasecache is left
1984 # When using the same lock to commit and strip, the phasecache is left
1983 # dirty after committing. Then when we strip, the repo is invalidated,
1985 # dirty after committing. Then when we strip, the repo is invalidated,
1984 # causing those changes to disappear.
1986 # causing those changes to disappear.
1985 if '_phasecache' in vars(self):
1987 if '_phasecache' in vars(self):
1986 self._phasecache.write()
1988 self._phasecache.write()
1987
1989
1988 @unfilteredmethod
1990 @unfilteredmethod
1989 def destroyed(self):
1991 def destroyed(self):
1990 '''Inform the repository that nodes have been destroyed.
1992 '''Inform the repository that nodes have been destroyed.
1991 Intended for use by strip and rollback, so there's a common
1993 Intended for use by strip and rollback, so there's a common
1992 place for anything that has to be done after destroying history.
1994 place for anything that has to be done after destroying history.
1993 '''
1995 '''
1994 # When one tries to:
1996 # When one tries to:
1995 # 1) destroy nodes thus calling this method (e.g. strip)
1997 # 1) destroy nodes thus calling this method (e.g. strip)
1996 # 2) use phasecache somewhere (e.g. commit)
1998 # 2) use phasecache somewhere (e.g. commit)
1997 #
1999 #
1998 # then 2) will fail because the phasecache contains nodes that were
2000 # then 2) will fail because the phasecache contains nodes that were
1999 # removed. We can either remove phasecache from the filecache,
2001 # removed. We can either remove phasecache from the filecache,
2000 # causing it to reload next time it is accessed, or simply filter
2002 # causing it to reload next time it is accessed, or simply filter
2001 # the removed nodes now and write the updated cache.
2003 # the removed nodes now and write the updated cache.
2002 self._phasecache.filterunknown(self)
2004 self._phasecache.filterunknown(self)
2003 self._phasecache.write()
2005 self._phasecache.write()
2004
2006
2005 # refresh all repository caches
2007 # refresh all repository caches
2006 self.updatecaches()
2008 self.updatecaches()
2007
2009
2008 # Ensure the persistent tag cache is updated. Doing it now
2010 # Ensure the persistent tag cache is updated. Doing it now
2009 # means that the tag cache only has to worry about destroyed
2011 # means that the tag cache only has to worry about destroyed
2010 # heads immediately after a strip/rollback. That in turn
2012 # heads immediately after a strip/rollback. That in turn
2011 # guarantees that "cachetip == currenttip" (comparing both rev
2013 # guarantees that "cachetip == currenttip" (comparing both rev
2012 # and node) always means no nodes have been added or destroyed.
2014 # and node) always means no nodes have been added or destroyed.
2013
2015
2014 # XXX this is suboptimal when qrefresh'ing: we strip the current
2016 # XXX this is suboptimal when qrefresh'ing: we strip the current
2015 # head, refresh the tag cache, then immediately add a new head.
2017 # head, refresh the tag cache, then immediately add a new head.
2016 # But I think doing it this way is necessary for the "instant
2018 # But I think doing it this way is necessary for the "instant
2017 # tag cache retrieval" case to work.
2019 # tag cache retrieval" case to work.
2018 self.invalidate()
2020 self.invalidate()
2019
2021
2020 def walk(self, match, node=None):
2022 def walk(self, match, node=None):
2021 '''
2023 '''
2022 walk recursively through the directory tree or a given
2024 walk recursively through the directory tree or a given
2023 changeset, finding all files matched by the match
2025 changeset, finding all files matched by the match
2024 function
2026 function
2025 '''
2027 '''
2026 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
2028 self.ui.deprecwarn('use repo[node].walk instead of repo.walk', '4.3')
2027 return self[node].walk(match)
2029 return self[node].walk(match)
2028
2030
2029 def status(self, node1='.', node2=None, match=None,
2031 def status(self, node1='.', node2=None, match=None,
2030 ignored=False, clean=False, unknown=False,
2032 ignored=False, clean=False, unknown=False,
2031 listsubrepos=False):
2033 listsubrepos=False):
2032 '''a convenience method that calls node1.status(node2)'''
2034 '''a convenience method that calls node1.status(node2)'''
2033 return self[node1].status(node2, match, ignored, clean, unknown,
2035 return self[node1].status(node2, match, ignored, clean, unknown,
2034 listsubrepos)
2036 listsubrepos)
2035
2037
2036 def addpostdsstatus(self, ps):
2038 def addpostdsstatus(self, ps):
2037 """Add a callback to run within the wlock, at the point at which status
2039 """Add a callback to run within the wlock, at the point at which status
2038 fixups happen.
2040 fixups happen.
2039
2041
2040 On status completion, callback(wctx, status) will be called with the
2042 On status completion, callback(wctx, status) will be called with the
2041 wlock held, unless the dirstate has changed from underneath or the wlock
2043 wlock held, unless the dirstate has changed from underneath or the wlock
2042 couldn't be grabbed.
2044 couldn't be grabbed.
2043
2045
2044 Callbacks should not capture and use a cached copy of the dirstate --
2046 Callbacks should not capture and use a cached copy of the dirstate --
2045 it might change in the meanwhile. Instead, they should access the
2047 it might change in the meanwhile. Instead, they should access the
2046 dirstate via wctx.repo().dirstate.
2048 dirstate via wctx.repo().dirstate.
2047
2049
2048 This list is emptied out after each status run -- extensions should
2050 This list is emptied out after each status run -- extensions should
2049 make sure it adds to this list each time dirstate.status is called.
2051 make sure it adds to this list each time dirstate.status is called.
2050 Extensions should also make sure they don't call this for statuses
2052 Extensions should also make sure they don't call this for statuses
2051 that don't involve the dirstate.
2053 that don't involve the dirstate.
2052 """
2054 """
2053
2055
2054 # The list is located here for uniqueness reasons -- it is actually
2056 # The list is located here for uniqueness reasons -- it is actually
2055 # managed by the workingctx, but that isn't unique per-repo.
2057 # managed by the workingctx, but that isn't unique per-repo.
2056 self._postdsstatus.append(ps)
2058 self._postdsstatus.append(ps)
2057
2059
2058 def postdsstatus(self):
2060 def postdsstatus(self):
2059 """Used by workingctx to get the list of post-dirstate-status hooks."""
2061 """Used by workingctx to get the list of post-dirstate-status hooks."""
2060 return self._postdsstatus
2062 return self._postdsstatus
2061
2063
2062 def clearpostdsstatus(self):
2064 def clearpostdsstatus(self):
2063 """Used by workingctx to clear post-dirstate-status hooks."""
2065 """Used by workingctx to clear post-dirstate-status hooks."""
2064 del self._postdsstatus[:]
2066 del self._postdsstatus[:]
2065
2067
2066 def heads(self, start=None):
2068 def heads(self, start=None):
2067 if start is None:
2069 if start is None:
2068 cl = self.changelog
2070 cl = self.changelog
2069 headrevs = reversed(cl.headrevs())
2071 headrevs = reversed(cl.headrevs())
2070 return [cl.node(rev) for rev in headrevs]
2072 return [cl.node(rev) for rev in headrevs]
2071
2073
2072 heads = self.changelog.heads(start)
2074 heads = self.changelog.heads(start)
2073 # sort the output in rev descending order
2075 # sort the output in rev descending order
2074 return sorted(heads, key=self.changelog.rev, reverse=True)
2076 return sorted(heads, key=self.changelog.rev, reverse=True)
2075
2077
2076 def branchheads(self, branch=None, start=None, closed=False):
2078 def branchheads(self, branch=None, start=None, closed=False):
2077 '''return a (possibly filtered) list of heads for the given branch
2079 '''return a (possibly filtered) list of heads for the given branch
2078
2080
2079 Heads are returned in topological order, from newest to oldest.
2081 Heads are returned in topological order, from newest to oldest.
2080 If branch is None, use the dirstate branch.
2082 If branch is None, use the dirstate branch.
2081 If start is not None, return only heads reachable from start.
2083 If start is not None, return only heads reachable from start.
2082 If closed is True, return heads that are marked as closed as well.
2084 If closed is True, return heads that are marked as closed as well.
2083 '''
2085 '''
2084 if branch is None:
2086 if branch is None:
2085 branch = self[None].branch()
2087 branch = self[None].branch()
2086 branches = self.branchmap()
2088 branches = self.branchmap()
2087 if branch not in branches:
2089 if branch not in branches:
2088 return []
2090 return []
2089 # the cache returns heads ordered lowest to highest
2091 # the cache returns heads ordered lowest to highest
2090 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2092 bheads = list(reversed(branches.branchheads(branch, closed=closed)))
2091 if start is not None:
2093 if start is not None:
2092 # filter out the heads that cannot be reached from startrev
2094 # filter out the heads that cannot be reached from startrev
2093 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2095 fbheads = set(self.changelog.nodesbetween([start], bheads)[2])
2094 bheads = [h for h in bheads if h in fbheads]
2096 bheads = [h for h in bheads if h in fbheads]
2095 return bheads
2097 return bheads
2096
2098
2097 def branches(self, nodes):
2099 def branches(self, nodes):
2098 if not nodes:
2100 if not nodes:
2099 nodes = [self.changelog.tip()]
2101 nodes = [self.changelog.tip()]
2100 b = []
2102 b = []
2101 for n in nodes:
2103 for n in nodes:
2102 t = n
2104 t = n
2103 while True:
2105 while True:
2104 p = self.changelog.parents(n)
2106 p = self.changelog.parents(n)
2105 if p[1] != nullid or p[0] == nullid:
2107 if p[1] != nullid or p[0] == nullid:
2106 b.append((t, n, p[0], p[1]))
2108 b.append((t, n, p[0], p[1]))
2107 break
2109 break
2108 n = p[0]
2110 n = p[0]
2109 return b
2111 return b
2110
2112
2111 def between(self, pairs):
2113 def between(self, pairs):
2112 r = []
2114 r = []
2113
2115
2114 for top, bottom in pairs:
2116 for top, bottom in pairs:
2115 n, l, i = top, [], 0
2117 n, l, i = top, [], 0
2116 f = 1
2118 f = 1
2117
2119
2118 while n != bottom and n != nullid:
2120 while n != bottom and n != nullid:
2119 p = self.changelog.parents(n)[0]
2121 p = self.changelog.parents(n)[0]
2120 if i == f:
2122 if i == f:
2121 l.append(n)
2123 l.append(n)
2122 f = f * 2
2124 f = f * 2
2123 n = p
2125 n = p
2124 i += 1
2126 i += 1
2125
2127
2126 r.append(l)
2128 r.append(l)
2127
2129
2128 return r
2130 return r
2129
2131
2130 def checkpush(self, pushop):
2132 def checkpush(self, pushop):
2131 """Extensions can override this function if additional checks have
2133 """Extensions can override this function if additional checks have
2132 to be performed before pushing, or call it if they override push
2134 to be performed before pushing, or call it if they override push
2133 command.
2135 command.
2134 """
2136 """
2135 pass
2137 pass
2136
2138
2137 @unfilteredpropertycache
2139 @unfilteredpropertycache
2138 def prepushoutgoinghooks(self):
2140 def prepushoutgoinghooks(self):
2139 """Return util.hooks consists of a pushop with repo, remote, outgoing
2141 """Return util.hooks consists of a pushop with repo, remote, outgoing
2140 methods, which are called before pushing changesets.
2142 methods, which are called before pushing changesets.
2141 """
2143 """
2142 return util.hooks()
2144 return util.hooks()
2143
2145
2144 def pushkey(self, namespace, key, old, new):
2146 def pushkey(self, namespace, key, old, new):
2145 try:
2147 try:
2146 tr = self.currenttransaction()
2148 tr = self.currenttransaction()
2147 hookargs = {}
2149 hookargs = {}
2148 if tr is not None:
2150 if tr is not None:
2149 hookargs.update(tr.hookargs)
2151 hookargs.update(tr.hookargs)
2150 hookargs['namespace'] = namespace
2152 hookargs['namespace'] = namespace
2151 hookargs['key'] = key
2153 hookargs['key'] = key
2152 hookargs['old'] = old
2154 hookargs['old'] = old
2153 hookargs['new'] = new
2155 hookargs['new'] = new
2154 self.hook('prepushkey', throw=True, **hookargs)
2156 self.hook('prepushkey', throw=True, **hookargs)
2155 except error.HookAbort as exc:
2157 except error.HookAbort as exc:
2156 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2158 self.ui.write_err(_("pushkey-abort: %s\n") % exc)
2157 if exc.hint:
2159 if exc.hint:
2158 self.ui.write_err(_("(%s)\n") % exc.hint)
2160 self.ui.write_err(_("(%s)\n") % exc.hint)
2159 return False
2161 return False
2160 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2162 self.ui.debug('pushing key for "%s:%s"\n' % (namespace, key))
2161 ret = pushkey.push(self, namespace, key, old, new)
2163 ret = pushkey.push(self, namespace, key, old, new)
2162 def runhook():
2164 def runhook():
2163 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2165 self.hook('pushkey', namespace=namespace, key=key, old=old, new=new,
2164 ret=ret)
2166 ret=ret)
2165 self._afterlock(runhook)
2167 self._afterlock(runhook)
2166 return ret
2168 return ret
2167
2169
2168 def listkeys(self, namespace):
2170 def listkeys(self, namespace):
2169 self.hook('prelistkeys', throw=True, namespace=namespace)
2171 self.hook('prelistkeys', throw=True, namespace=namespace)
2170 self.ui.debug('listing keys for "%s"\n' % namespace)
2172 self.ui.debug('listing keys for "%s"\n' % namespace)
2171 values = pushkey.list(self, namespace)
2173 values = pushkey.list(self, namespace)
2172 self.hook('listkeys', namespace=namespace, values=values)
2174 self.hook('listkeys', namespace=namespace, values=values)
2173 return values
2175 return values
2174
2176
2175 def debugwireargs(self, one, two, three=None, four=None, five=None):
2177 def debugwireargs(self, one, two, three=None, four=None, five=None):
2176 '''used to test argument passing over the wire'''
2178 '''used to test argument passing over the wire'''
2177 return "%s %s %s %s %s" % (one, two, three, four, five)
2179 return "%s %s %s %s %s" % (one, two, three, four, five)
2178
2180
2179 def savecommitmessage(self, text):
2181 def savecommitmessage(self, text):
2180 fp = self.vfs('last-message.txt', 'wb')
2182 fp = self.vfs('last-message.txt', 'wb')
2181 try:
2183 try:
2182 fp.write(text)
2184 fp.write(text)
2183 finally:
2185 finally:
2184 fp.close()
2186 fp.close()
2185 return self.pathto(fp.name[len(self.root) + 1:])
2187 return self.pathto(fp.name[len(self.root) + 1:])
2186
2188
2187 # used to avoid circular references so destructors work
2189 # used to avoid circular references so destructors work
2188 def aftertrans(files):
2190 def aftertrans(files):
2189 renamefiles = [tuple(t) for t in files]
2191 renamefiles = [tuple(t) for t in files]
2190 def a():
2192 def a():
2191 for vfs, src, dest in renamefiles:
2193 for vfs, src, dest in renamefiles:
2192 # if src and dest refer to a same file, vfs.rename is a no-op,
2194 # if src and dest refer to a same file, vfs.rename is a no-op,
2193 # leaving both src and dest on disk. delete dest to make sure
2195 # leaving both src and dest on disk. delete dest to make sure
2194 # the rename couldn't be such a no-op.
2196 # the rename couldn't be such a no-op.
2195 vfs.tryunlink(dest)
2197 vfs.tryunlink(dest)
2196 try:
2198 try:
2197 vfs.rename(src, dest)
2199 vfs.rename(src, dest)
2198 except OSError: # journal file does not yet exist
2200 except OSError: # journal file does not yet exist
2199 pass
2201 pass
2200 return a
2202 return a
2201
2203
2202 def undoname(fn):
2204 def undoname(fn):
2203 base, name = os.path.split(fn)
2205 base, name = os.path.split(fn)
2204 assert name.startswith('journal')
2206 assert name.startswith('journal')
2205 return os.path.join(base, name.replace('journal', 'undo', 1))
2207 return os.path.join(base, name.replace('journal', 'undo', 1))
2206
2208
2207 def instance(ui, path, create):
2209 def instance(ui, path, create):
2208 return localrepository(ui, util.urllocalpath(path), create)
2210 return localrepository(ui, util.urllocalpath(path), create)
2209
2211
2210 def islocal(path):
2212 def islocal(path):
2211 return True
2213 return True
2212
2214
2213 def newreporequirements(repo):
2215 def newreporequirements(repo):
2214 """Determine the set of requirements for a new local repository.
2216 """Determine the set of requirements for a new local repository.
2215
2217
2216 Extensions can wrap this function to specify custom requirements for
2218 Extensions can wrap this function to specify custom requirements for
2217 new repositories.
2219 new repositories.
2218 """
2220 """
2219 ui = repo.ui
2221 ui = repo.ui
2220 requirements = {'revlogv1'}
2222 requirements = {'revlogv1'}
2221 if ui.configbool('format', 'usestore'):
2223 if ui.configbool('format', 'usestore'):
2222 requirements.add('store')
2224 requirements.add('store')
2223 if ui.configbool('format', 'usefncache'):
2225 if ui.configbool('format', 'usefncache'):
2224 requirements.add('fncache')
2226 requirements.add('fncache')
2225 if ui.configbool('format', 'dotencode'):
2227 if ui.configbool('format', 'dotencode'):
2226 requirements.add('dotencode')
2228 requirements.add('dotencode')
2227
2229
2228 compengine = ui.config('experimental', 'format.compression')
2230 compengine = ui.config('experimental', 'format.compression')
2229 if compengine not in util.compengines:
2231 if compengine not in util.compengines:
2230 raise error.Abort(_('compression engine %s defined by '
2232 raise error.Abort(_('compression engine %s defined by '
2231 'experimental.format.compression not available') %
2233 'experimental.format.compression not available') %
2232 compengine,
2234 compengine,
2233 hint=_('run "hg debuginstall" to list available '
2235 hint=_('run "hg debuginstall" to list available '
2234 'compression engines'))
2236 'compression engines'))
2235
2237
2236 # zlib is the historical default and doesn't need an explicit requirement.
2238 # zlib is the historical default and doesn't need an explicit requirement.
2237 if compengine != 'zlib':
2239 if compengine != 'zlib':
2238 requirements.add('exp-compression-%s' % compengine)
2240 requirements.add('exp-compression-%s' % compengine)
2239
2241
2240 if scmutil.gdinitconfig(ui):
2242 if scmutil.gdinitconfig(ui):
2241 requirements.add('generaldelta')
2243 requirements.add('generaldelta')
2242 if ui.configbool('experimental', 'treemanifest'):
2244 if ui.configbool('experimental', 'treemanifest'):
2243 requirements.add('treemanifest')
2245 requirements.add('treemanifest')
2244 if ui.configbool('experimental', 'manifestv2'):
2246 if ui.configbool('experimental', 'manifestv2'):
2245 requirements.add('manifestv2')
2247 requirements.add('manifestv2')
2246
2248
2247 revlogv2 = ui.config('experimental', 'revlogv2')
2249 revlogv2 = ui.config('experimental', 'revlogv2')
2248 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2250 if revlogv2 == 'enable-unstable-format-and-corrupt-my-data':
2249 requirements.remove('revlogv1')
2251 requirements.remove('revlogv1')
2250 # generaldelta is implied by revlogv2.
2252 # generaldelta is implied by revlogv2.
2251 requirements.discard('generaldelta')
2253 requirements.discard('generaldelta')
2252 requirements.add(REVLOGV2_REQUIREMENT)
2254 requirements.add(REVLOGV2_REQUIREMENT)
2253
2255
2254 return requirements
2256 return requirements
@@ -1,1093 +1,1104 b''
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import glob
11 import glob
12 import hashlib
12 import hashlib
13 import os
13 import os
14 import re
14 import re
15 import socket
15 import socket
16 import weakref
16 import weakref
17
17
18 from .i18n import _
18 from .i18n import _
19 from .node import (
19 from .node import (
20 hex,
20 hex,
21 nullid,
21 nullid,
22 wdirid,
22 wdirid,
23 wdirrev,
23 wdirrev,
24 )
24 )
25
25
26 from . import (
26 from . import (
27 encoding,
27 encoding,
28 error,
28 error,
29 match as matchmod,
29 match as matchmod,
30 obsolete,
30 obsolete,
31 obsutil,
31 obsutil,
32 pathutil,
32 pathutil,
33 phases,
33 phases,
34 pycompat,
34 pycompat,
35 revsetlang,
35 revsetlang,
36 similar,
36 similar,
37 util,
37 util,
38 )
38 )
39
39
40 if pycompat.osname == 'nt':
40 if pycompat.osname == 'nt':
41 from . import scmwindows as scmplatform
41 from . import scmwindows as scmplatform
42 else:
42 else:
43 from . import scmposix as scmplatform
43 from . import scmposix as scmplatform
44
44
45 termsize = scmplatform.termsize
45 termsize = scmplatform.termsize
46
46
47 class status(tuple):
47 class status(tuple):
48 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
48 '''Named tuple with a list of files per status. The 'deleted', 'unknown'
49 and 'ignored' properties are only relevant to the working copy.
49 and 'ignored' properties are only relevant to the working copy.
50 '''
50 '''
51
51
52 __slots__ = ()
52 __slots__ = ()
53
53
54 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
54 def __new__(cls, modified, added, removed, deleted, unknown, ignored,
55 clean):
55 clean):
56 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
56 return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
57 ignored, clean))
57 ignored, clean))
58
58
59 @property
59 @property
60 def modified(self):
60 def modified(self):
61 '''files that have been modified'''
61 '''files that have been modified'''
62 return self[0]
62 return self[0]
63
63
64 @property
64 @property
65 def added(self):
65 def added(self):
66 '''files that have been added'''
66 '''files that have been added'''
67 return self[1]
67 return self[1]
68
68
69 @property
69 @property
70 def removed(self):
70 def removed(self):
71 '''files that have been removed'''
71 '''files that have been removed'''
72 return self[2]
72 return self[2]
73
73
74 @property
74 @property
75 def deleted(self):
75 def deleted(self):
76 '''files that are in the dirstate, but have been deleted from the
76 '''files that are in the dirstate, but have been deleted from the
77 working copy (aka "missing")
77 working copy (aka "missing")
78 '''
78 '''
79 return self[3]
79 return self[3]
80
80
81 @property
81 @property
82 def unknown(self):
82 def unknown(self):
83 '''files not in the dirstate that are not ignored'''
83 '''files not in the dirstate that are not ignored'''
84 return self[4]
84 return self[4]
85
85
86 @property
86 @property
87 def ignored(self):
87 def ignored(self):
88 '''files not in the dirstate that are ignored (by _dirignore())'''
88 '''files not in the dirstate that are ignored (by _dirignore())'''
89 return self[5]
89 return self[5]
90
90
91 @property
91 @property
92 def clean(self):
92 def clean(self):
93 '''files that have not been modified'''
93 '''files that have not been modified'''
94 return self[6]
94 return self[6]
95
95
96 def __repr__(self, *args, **kwargs):
96 def __repr__(self, *args, **kwargs):
97 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
97 return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
98 'unknown=%r, ignored=%r, clean=%r>') % self)
98 'unknown=%r, ignored=%r, clean=%r>') % self)
99
99
100 def itersubrepos(ctx1, ctx2):
100 def itersubrepos(ctx1, ctx2):
101 """find subrepos in ctx1 or ctx2"""
101 """find subrepos in ctx1 or ctx2"""
102 # Create a (subpath, ctx) mapping where we prefer subpaths from
102 # Create a (subpath, ctx) mapping where we prefer subpaths from
103 # ctx1. The subpaths from ctx2 are important when the .hgsub file
103 # ctx1. The subpaths from ctx2 are important when the .hgsub file
104 # has been modified (in ctx2) but not yet committed (in ctx1).
104 # has been modified (in ctx2) but not yet committed (in ctx1).
105 subpaths = dict.fromkeys(ctx2.substate, ctx2)
105 subpaths = dict.fromkeys(ctx2.substate, ctx2)
106 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
106 subpaths.update(dict.fromkeys(ctx1.substate, ctx1))
107
107
108 missing = set()
108 missing = set()
109
109
110 for subpath in ctx2.substate:
110 for subpath in ctx2.substate:
111 if subpath not in ctx1.substate:
111 if subpath not in ctx1.substate:
112 del subpaths[subpath]
112 del subpaths[subpath]
113 missing.add(subpath)
113 missing.add(subpath)
114
114
115 for subpath, ctx in sorted(subpaths.iteritems()):
115 for subpath, ctx in sorted(subpaths.iteritems()):
116 yield subpath, ctx.sub(subpath)
116 yield subpath, ctx.sub(subpath)
117
117
118 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
118 # Yield an empty subrepo based on ctx1 for anything only in ctx2. That way,
119 # status and diff will have an accurate result when it does
119 # status and diff will have an accurate result when it does
120 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
120 # 'sub.{status|diff}(rev2)'. Otherwise, the ctx2 subrepo is compared
121 # against itself.
121 # against itself.
122 for subpath in missing:
122 for subpath in missing:
123 yield subpath, ctx2.nullsub(subpath, ctx1)
123 yield subpath, ctx2.nullsub(subpath, ctx1)
124
124
125 def nochangesfound(ui, repo, excluded=None):
125 def nochangesfound(ui, repo, excluded=None):
126 '''Report no changes for push/pull, excluded is None or a list of
126 '''Report no changes for push/pull, excluded is None or a list of
127 nodes excluded from the push/pull.
127 nodes excluded from the push/pull.
128 '''
128 '''
129 secretlist = []
129 secretlist = []
130 if excluded:
130 if excluded:
131 for n in excluded:
131 for n in excluded:
132 ctx = repo[n]
132 ctx = repo[n]
133 if ctx.phase() >= phases.secret and not ctx.extinct():
133 if ctx.phase() >= phases.secret and not ctx.extinct():
134 secretlist.append(n)
134 secretlist.append(n)
135
135
136 if secretlist:
136 if secretlist:
137 ui.status(_("no changes found (ignored %d secret changesets)\n")
137 ui.status(_("no changes found (ignored %d secret changesets)\n")
138 % len(secretlist))
138 % len(secretlist))
139 else:
139 else:
140 ui.status(_("no changes found\n"))
140 ui.status(_("no changes found\n"))
141
141
142 def callcatch(ui, func):
142 def callcatch(ui, func):
143 """call func() with global exception handling
143 """call func() with global exception handling
144
144
145 return func() if no exception happens. otherwise do some error handling
145 return func() if no exception happens. otherwise do some error handling
146 and return an exit code accordingly. does not handle all exceptions.
146 and return an exit code accordingly. does not handle all exceptions.
147 """
147 """
148 try:
148 try:
149 try:
149 try:
150 return func()
150 return func()
151 except: # re-raises
151 except: # re-raises
152 ui.traceback()
152 ui.traceback()
153 raise
153 raise
154 # Global exception handling, alphabetically
154 # Global exception handling, alphabetically
155 # Mercurial-specific first, followed by built-in and library exceptions
155 # Mercurial-specific first, followed by built-in and library exceptions
156 except error.LockHeld as inst:
156 except error.LockHeld as inst:
157 if inst.errno == errno.ETIMEDOUT:
157 if inst.errno == errno.ETIMEDOUT:
158 reason = _('timed out waiting for lock held by %r') % inst.locker
158 reason = _('timed out waiting for lock held by %r') % inst.locker
159 else:
159 else:
160 reason = _('lock held by %r') % inst.locker
160 reason = _('lock held by %r') % inst.locker
161 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
161 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
162 if not inst.locker:
162 if not inst.locker:
163 ui.warn(_("(lock might be very busy)\n"))
163 ui.warn(_("(lock might be very busy)\n"))
164 except error.LockUnavailable as inst:
164 except error.LockUnavailable as inst:
165 ui.warn(_("abort: could not lock %s: %s\n") %
165 ui.warn(_("abort: could not lock %s: %s\n") %
166 (inst.desc or inst.filename, inst.strerror))
166 (inst.desc or inst.filename, inst.strerror))
167 except error.OutOfBandError as inst:
167 except error.OutOfBandError as inst:
168 if inst.args:
168 if inst.args:
169 msg = _("abort: remote error:\n")
169 msg = _("abort: remote error:\n")
170 else:
170 else:
171 msg = _("abort: remote error\n")
171 msg = _("abort: remote error\n")
172 ui.warn(msg)
172 ui.warn(msg)
173 if inst.args:
173 if inst.args:
174 ui.warn(''.join(inst.args))
174 ui.warn(''.join(inst.args))
175 if inst.hint:
175 if inst.hint:
176 ui.warn('(%s)\n' % inst.hint)
176 ui.warn('(%s)\n' % inst.hint)
177 except error.RepoError as inst:
177 except error.RepoError as inst:
178 ui.warn(_("abort: %s!\n") % inst)
178 ui.warn(_("abort: %s!\n") % inst)
179 if inst.hint:
179 if inst.hint:
180 ui.warn(_("(%s)\n") % inst.hint)
180 ui.warn(_("(%s)\n") % inst.hint)
181 except error.ResponseError as inst:
181 except error.ResponseError as inst:
182 ui.warn(_("abort: %s") % inst.args[0])
182 ui.warn(_("abort: %s") % inst.args[0])
183 if not isinstance(inst.args[1], basestring):
183 if not isinstance(inst.args[1], basestring):
184 ui.warn(" %r\n" % (inst.args[1],))
184 ui.warn(" %r\n" % (inst.args[1],))
185 elif not inst.args[1]:
185 elif not inst.args[1]:
186 ui.warn(_(" empty string\n"))
186 ui.warn(_(" empty string\n"))
187 else:
187 else:
188 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
188 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
189 except error.CensoredNodeError as inst:
189 except error.CensoredNodeError as inst:
190 ui.warn(_("abort: file censored %s!\n") % inst)
190 ui.warn(_("abort: file censored %s!\n") % inst)
191 except error.RevlogError as inst:
191 except error.RevlogError as inst:
192 ui.warn(_("abort: %s!\n") % inst)
192 ui.warn(_("abort: %s!\n") % inst)
193 except error.InterventionRequired as inst:
193 except error.InterventionRequired as inst:
194 ui.warn("%s\n" % inst)
194 ui.warn("%s\n" % inst)
195 if inst.hint:
195 if inst.hint:
196 ui.warn(_("(%s)\n") % inst.hint)
196 ui.warn(_("(%s)\n") % inst.hint)
197 return 1
197 return 1
198 except error.WdirUnsupported:
198 except error.WdirUnsupported:
199 ui.warn(_("abort: working directory revision cannot be specified\n"))
199 ui.warn(_("abort: working directory revision cannot be specified\n"))
200 except error.Abort as inst:
200 except error.Abort as inst:
201 ui.warn(_("abort: %s\n") % inst)
201 ui.warn(_("abort: %s\n") % inst)
202 if inst.hint:
202 if inst.hint:
203 ui.warn(_("(%s)\n") % inst.hint)
203 ui.warn(_("(%s)\n") % inst.hint)
204 except ImportError as inst:
204 except ImportError as inst:
205 ui.warn(_("abort: %s!\n") % inst)
205 ui.warn(_("abort: %s!\n") % inst)
206 m = str(inst).split()[-1]
206 m = str(inst).split()[-1]
207 if m in "mpatch bdiff".split():
207 if m in "mpatch bdiff".split():
208 ui.warn(_("(did you forget to compile extensions?)\n"))
208 ui.warn(_("(did you forget to compile extensions?)\n"))
209 elif m in "zlib".split():
209 elif m in "zlib".split():
210 ui.warn(_("(is your Python install correct?)\n"))
210 ui.warn(_("(is your Python install correct?)\n"))
211 except IOError as inst:
211 except IOError as inst:
212 if util.safehasattr(inst, "code"):
212 if util.safehasattr(inst, "code"):
213 ui.warn(_("abort: %s\n") % inst)
213 ui.warn(_("abort: %s\n") % inst)
214 elif util.safehasattr(inst, "reason"):
214 elif util.safehasattr(inst, "reason"):
215 try: # usually it is in the form (errno, strerror)
215 try: # usually it is in the form (errno, strerror)
216 reason = inst.reason.args[1]
216 reason = inst.reason.args[1]
217 except (AttributeError, IndexError):
217 except (AttributeError, IndexError):
218 # it might be anything, for example a string
218 # it might be anything, for example a string
219 reason = inst.reason
219 reason = inst.reason
220 if isinstance(reason, unicode):
220 if isinstance(reason, unicode):
221 # SSLError of Python 2.7.9 contains a unicode
221 # SSLError of Python 2.7.9 contains a unicode
222 reason = encoding.unitolocal(reason)
222 reason = encoding.unitolocal(reason)
223 ui.warn(_("abort: error: %s\n") % reason)
223 ui.warn(_("abort: error: %s\n") % reason)
224 elif (util.safehasattr(inst, "args")
224 elif (util.safehasattr(inst, "args")
225 and inst.args and inst.args[0] == errno.EPIPE):
225 and inst.args and inst.args[0] == errno.EPIPE):
226 pass
226 pass
227 elif getattr(inst, "strerror", None):
227 elif getattr(inst, "strerror", None):
228 if getattr(inst, "filename", None):
228 if getattr(inst, "filename", None):
229 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
229 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
230 else:
230 else:
231 ui.warn(_("abort: %s\n") % inst.strerror)
231 ui.warn(_("abort: %s\n") % inst.strerror)
232 else:
232 else:
233 raise
233 raise
234 except OSError as inst:
234 except OSError as inst:
235 if getattr(inst, "filename", None) is not None:
235 if getattr(inst, "filename", None) is not None:
236 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
236 ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
237 else:
237 else:
238 ui.warn(_("abort: %s\n") % inst.strerror)
238 ui.warn(_("abort: %s\n") % inst.strerror)
239 except MemoryError:
239 except MemoryError:
240 ui.warn(_("abort: out of memory\n"))
240 ui.warn(_("abort: out of memory\n"))
241 except SystemExit as inst:
241 except SystemExit as inst:
242 # Commands shouldn't sys.exit directly, but give a return code.
242 # Commands shouldn't sys.exit directly, but give a return code.
243 # Just in case catch this and and pass exit code to caller.
243 # Just in case catch this and and pass exit code to caller.
244 return inst.code
244 return inst.code
245 except socket.error as inst:
245 except socket.error as inst:
246 ui.warn(_("abort: %s\n") % inst.args[-1])
246 ui.warn(_("abort: %s\n") % inst.args[-1])
247
247
248 return -1
248 return -1
249
249
250 def checknewlabel(repo, lbl, kind):
250 def checknewlabel(repo, lbl, kind):
251 # Do not use the "kind" parameter in ui output.
251 # Do not use the "kind" parameter in ui output.
252 # It makes strings difficult to translate.
252 # It makes strings difficult to translate.
253 if lbl in ['tip', '.', 'null']:
253 if lbl in ['tip', '.', 'null']:
254 raise error.Abort(_("the name '%s' is reserved") % lbl)
254 raise error.Abort(_("the name '%s' is reserved") % lbl)
255 for c in (':', '\0', '\n', '\r'):
255 for c in (':', '\0', '\n', '\r'):
256 if c in lbl:
256 if c in lbl:
257 raise error.Abort(_("%r cannot be used in a name") % c)
257 raise error.Abort(_("%r cannot be used in a name") % c)
258 try:
258 try:
259 int(lbl)
259 int(lbl)
260 raise error.Abort(_("cannot use an integer as a name"))
260 raise error.Abort(_("cannot use an integer as a name"))
261 except ValueError:
261 except ValueError:
262 pass
262 pass
263
263
264 def checkfilename(f):
264 def checkfilename(f):
265 '''Check that the filename f is an acceptable filename for a tracked file'''
265 '''Check that the filename f is an acceptable filename for a tracked file'''
266 if '\r' in f or '\n' in f:
266 if '\r' in f or '\n' in f:
267 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
267 raise error.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
268
268
269 def checkportable(ui, f):
269 def checkportable(ui, f):
270 '''Check if filename f is portable and warn or abort depending on config'''
270 '''Check if filename f is portable and warn or abort depending on config'''
271 checkfilename(f)
271 checkfilename(f)
272 abort, warn = checkportabilityalert(ui)
272 abort, warn = checkportabilityalert(ui)
273 if abort or warn:
273 if abort or warn:
274 msg = util.checkwinfilename(f)
274 msg = util.checkwinfilename(f)
275 if msg:
275 if msg:
276 msg = "%s: %r" % (msg, f)
276 msg = "%s: %r" % (msg, f)
277 if abort:
277 if abort:
278 raise error.Abort(msg)
278 raise error.Abort(msg)
279 ui.warn(_("warning: %s\n") % msg)
279 ui.warn(_("warning: %s\n") % msg)
280
280
281 def checkportabilityalert(ui):
281 def checkportabilityalert(ui):
282 '''check if the user's config requests nothing, a warning, or abort for
282 '''check if the user's config requests nothing, a warning, or abort for
283 non-portable filenames'''
283 non-portable filenames'''
284 val = ui.config('ui', 'portablefilenames')
284 val = ui.config('ui', 'portablefilenames')
285 lval = val.lower()
285 lval = val.lower()
286 bval = util.parsebool(val)
286 bval = util.parsebool(val)
287 abort = pycompat.osname == 'nt' or lval == 'abort'
287 abort = pycompat.osname == 'nt' or lval == 'abort'
288 warn = bval or lval == 'warn'
288 warn = bval or lval == 'warn'
289 if bval is None and not (warn or abort or lval == 'ignore'):
289 if bval is None and not (warn or abort or lval == 'ignore'):
290 raise error.ConfigError(
290 raise error.ConfigError(
291 _("ui.portablefilenames value is invalid ('%s')") % val)
291 _("ui.portablefilenames value is invalid ('%s')") % val)
292 return abort, warn
292 return abort, warn
293
293
294 class casecollisionauditor(object):
294 class casecollisionauditor(object):
295 def __init__(self, ui, abort, dirstate):
295 def __init__(self, ui, abort, dirstate):
296 self._ui = ui
296 self._ui = ui
297 self._abort = abort
297 self._abort = abort
298 allfiles = '\0'.join(dirstate._map)
298 allfiles = '\0'.join(dirstate._map)
299 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
299 self._loweredfiles = set(encoding.lower(allfiles).split('\0'))
300 self._dirstate = dirstate
300 self._dirstate = dirstate
301 # The purpose of _newfiles is so that we don't complain about
301 # The purpose of _newfiles is so that we don't complain about
302 # case collisions if someone were to call this object with the
302 # case collisions if someone were to call this object with the
303 # same filename twice.
303 # same filename twice.
304 self._newfiles = set()
304 self._newfiles = set()
305
305
306 def __call__(self, f):
306 def __call__(self, f):
307 if f in self._newfiles:
307 if f in self._newfiles:
308 return
308 return
309 fl = encoding.lower(f)
309 fl = encoding.lower(f)
310 if fl in self._loweredfiles and f not in self._dirstate:
310 if fl in self._loweredfiles and f not in self._dirstate:
311 msg = _('possible case-folding collision for %s') % f
311 msg = _('possible case-folding collision for %s') % f
312 if self._abort:
312 if self._abort:
313 raise error.Abort(msg)
313 raise error.Abort(msg)
314 self._ui.warn(_("warning: %s\n") % msg)
314 self._ui.warn(_("warning: %s\n") % msg)
315 self._loweredfiles.add(fl)
315 self._loweredfiles.add(fl)
316 self._newfiles.add(f)
316 self._newfiles.add(f)
317
317
318 def filteredhash(repo, maxrev):
318 def filteredhash(repo, maxrev):
319 """build hash of filtered revisions in the current repoview.
319 """build hash of filtered revisions in the current repoview.
320
320
321 Multiple caches perform up-to-date validation by checking that the
321 Multiple caches perform up-to-date validation by checking that the
322 tiprev and tipnode stored in the cache file match the current repository.
322 tiprev and tipnode stored in the cache file match the current repository.
323 However, this is not sufficient for validating repoviews because the set
323 However, this is not sufficient for validating repoviews because the set
324 of revisions in the view may change without the repository tiprev and
324 of revisions in the view may change without the repository tiprev and
325 tipnode changing.
325 tipnode changing.
326
326
327 This function hashes all the revs filtered from the view and returns
327 This function hashes all the revs filtered from the view and returns
328 that SHA-1 digest.
328 that SHA-1 digest.
329 """
329 """
330 cl = repo.changelog
330 cl = repo.changelog
331 if not cl.filteredrevs:
331 if not cl.filteredrevs:
332 return None
332 return None
333 key = None
333 key = None
334 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
334 revs = sorted(r for r in cl.filteredrevs if r <= maxrev)
335 if revs:
335 if revs:
336 s = hashlib.sha1()
336 s = hashlib.sha1()
337 for rev in revs:
337 for rev in revs:
338 s.update('%d;' % rev)
338 s.update('%d;' % rev)
339 key = s.digest()
339 key = s.digest()
340 return key
340 return key
341
341
342 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
342 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
343 '''yield every hg repository under path, always recursively.
343 '''yield every hg repository under path, always recursively.
344 The recurse flag will only control recursion into repo working dirs'''
344 The recurse flag will only control recursion into repo working dirs'''
345 def errhandler(err):
345 def errhandler(err):
346 if err.filename == path:
346 if err.filename == path:
347 raise err
347 raise err
348 samestat = getattr(os.path, 'samestat', None)
348 samestat = getattr(os.path, 'samestat', None)
349 if followsym and samestat is not None:
349 if followsym and samestat is not None:
350 def adddir(dirlst, dirname):
350 def adddir(dirlst, dirname):
351 match = False
351 match = False
352 dirstat = os.stat(dirname)
352 dirstat = os.stat(dirname)
353 for lstdirstat in dirlst:
353 for lstdirstat in dirlst:
354 if samestat(dirstat, lstdirstat):
354 if samestat(dirstat, lstdirstat):
355 match = True
355 match = True
356 break
356 break
357 if not match:
357 if not match:
358 dirlst.append(dirstat)
358 dirlst.append(dirstat)
359 return not match
359 return not match
360 else:
360 else:
361 followsym = False
361 followsym = False
362
362
363 if (seen_dirs is None) and followsym:
363 if (seen_dirs is None) and followsym:
364 seen_dirs = []
364 seen_dirs = []
365 adddir(seen_dirs, path)
365 adddir(seen_dirs, path)
366 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
366 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
367 dirs.sort()
367 dirs.sort()
368 if '.hg' in dirs:
368 if '.hg' in dirs:
369 yield root # found a repository
369 yield root # found a repository
370 qroot = os.path.join(root, '.hg', 'patches')
370 qroot = os.path.join(root, '.hg', 'patches')
371 if os.path.isdir(os.path.join(qroot, '.hg')):
371 if os.path.isdir(os.path.join(qroot, '.hg')):
372 yield qroot # we have a patch queue repo here
372 yield qroot # we have a patch queue repo here
373 if recurse:
373 if recurse:
374 # avoid recursing inside the .hg directory
374 # avoid recursing inside the .hg directory
375 dirs.remove('.hg')
375 dirs.remove('.hg')
376 else:
376 else:
377 dirs[:] = [] # don't descend further
377 dirs[:] = [] # don't descend further
378 elif followsym:
378 elif followsym:
379 newdirs = []
379 newdirs = []
380 for d in dirs:
380 for d in dirs:
381 fname = os.path.join(root, d)
381 fname = os.path.join(root, d)
382 if adddir(seen_dirs, fname):
382 if adddir(seen_dirs, fname):
383 if os.path.islink(fname):
383 if os.path.islink(fname):
384 for hgname in walkrepos(fname, True, seen_dirs):
384 for hgname in walkrepos(fname, True, seen_dirs):
385 yield hgname
385 yield hgname
386 else:
386 else:
387 newdirs.append(d)
387 newdirs.append(d)
388 dirs[:] = newdirs
388 dirs[:] = newdirs
389
389
390 def binnode(ctx):
390 def binnode(ctx):
391 """Return binary node id for a given basectx"""
391 """Return binary node id for a given basectx"""
392 node = ctx.node()
392 node = ctx.node()
393 if node is None:
393 if node is None:
394 return wdirid
394 return wdirid
395 return node
395 return node
396
396
397 def intrev(ctx):
397 def intrev(ctx):
398 """Return integer for a given basectx that can be used in comparison or
398 """Return integer for a given basectx that can be used in comparison or
399 arithmetic operation"""
399 arithmetic operation"""
400 rev = ctx.rev()
400 rev = ctx.rev()
401 if rev is None:
401 if rev is None:
402 return wdirrev
402 return wdirrev
403 return rev
403 return rev
404
404
405 def revsingle(repo, revspec, default='.'):
405 def revsingle(repo, revspec, default='.'):
406 if not revspec and revspec != 0:
406 if not revspec and revspec != 0:
407 return repo[default]
407 return repo[default]
408
408
409 l = revrange(repo, [revspec])
409 l = revrange(repo, [revspec])
410 if not l:
410 if not l:
411 raise error.Abort(_('empty revision set'))
411 raise error.Abort(_('empty revision set'))
412 return repo[l.last()]
412 return repo[l.last()]
413
413
414 def _pairspec(revspec):
414 def _pairspec(revspec):
415 tree = revsetlang.parse(revspec)
415 tree = revsetlang.parse(revspec)
416 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
416 return tree and tree[0] in ('range', 'rangepre', 'rangepost', 'rangeall')
417
417
418 def revpair(repo, revs):
418 def revpair(repo, revs):
419 if not revs:
419 if not revs:
420 return repo.dirstate.p1(), None
420 return repo.dirstate.p1(), None
421
421
422 l = revrange(repo, revs)
422 l = revrange(repo, revs)
423
423
424 if not l:
424 if not l:
425 first = second = None
425 first = second = None
426 elif l.isascending():
426 elif l.isascending():
427 first = l.min()
427 first = l.min()
428 second = l.max()
428 second = l.max()
429 elif l.isdescending():
429 elif l.isdescending():
430 first = l.max()
430 first = l.max()
431 second = l.min()
431 second = l.min()
432 else:
432 else:
433 first = l.first()
433 first = l.first()
434 second = l.last()
434 second = l.last()
435
435
436 if first is None:
436 if first is None:
437 raise error.Abort(_('empty revision range'))
437 raise error.Abort(_('empty revision range'))
438 if (first == second and len(revs) >= 2
438 if (first == second and len(revs) >= 2
439 and not all(revrange(repo, [r]) for r in revs)):
439 and not all(revrange(repo, [r]) for r in revs)):
440 raise error.Abort(_('empty revision on one side of range'))
440 raise error.Abort(_('empty revision on one side of range'))
441
441
442 # if top-level is range expression, the result must always be a pair
442 # if top-level is range expression, the result must always be a pair
443 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
443 if first == second and len(revs) == 1 and not _pairspec(revs[0]):
444 return repo.lookup(first), None
444 return repo.lookup(first), None
445
445
446 return repo.lookup(first), repo.lookup(second)
446 return repo.lookup(first), repo.lookup(second)
447
447
448 def revrange(repo, specs):
448 def revrange(repo, specs):
449 """Execute 1 to many revsets and return the union.
449 """Execute 1 to many revsets and return the union.
450
450
451 This is the preferred mechanism for executing revsets using user-specified
451 This is the preferred mechanism for executing revsets using user-specified
452 config options, such as revset aliases.
452 config options, such as revset aliases.
453
453
454 The revsets specified by ``specs`` will be executed via a chained ``OR``
454 The revsets specified by ``specs`` will be executed via a chained ``OR``
455 expression. If ``specs`` is empty, an empty result is returned.
455 expression. If ``specs`` is empty, an empty result is returned.
456
456
457 ``specs`` can contain integers, in which case they are assumed to be
457 ``specs`` can contain integers, in which case they are assumed to be
458 revision numbers.
458 revision numbers.
459
459
460 It is assumed the revsets are already formatted. If you have arguments
460 It is assumed the revsets are already formatted. If you have arguments
461 that need to be expanded in the revset, call ``revsetlang.formatspec()``
461 that need to be expanded in the revset, call ``revsetlang.formatspec()``
462 and pass the result as an element of ``specs``.
462 and pass the result as an element of ``specs``.
463
463
464 Specifying a single revset is allowed.
464 Specifying a single revset is allowed.
465
465
466 Returns a ``revset.abstractsmartset`` which is a list-like interface over
466 Returns a ``revset.abstractsmartset`` which is a list-like interface over
467 integer revisions.
467 integer revisions.
468 """
468 """
469 allspecs = []
469 allspecs = []
470 for spec in specs:
470 for spec in specs:
471 if isinstance(spec, int):
471 if isinstance(spec, int):
472 spec = revsetlang.formatspec('rev(%d)', spec)
472 spec = revsetlang.formatspec('rev(%d)', spec)
473 allspecs.append(spec)
473 allspecs.append(spec)
474 return repo.anyrevs(allspecs, user=True)
474 return repo.anyrevs(allspecs, user=True)
475
475
476 def meaningfulparents(repo, ctx):
476 def meaningfulparents(repo, ctx):
477 """Return list of meaningful (or all if debug) parentrevs for rev.
477 """Return list of meaningful (or all if debug) parentrevs for rev.
478
478
479 For merges (two non-nullrev revisions) both parents are meaningful.
479 For merges (two non-nullrev revisions) both parents are meaningful.
480 Otherwise the first parent revision is considered meaningful if it
480 Otherwise the first parent revision is considered meaningful if it
481 is not the preceding revision.
481 is not the preceding revision.
482 """
482 """
483 parents = ctx.parents()
483 parents = ctx.parents()
484 if len(parents) > 1:
484 if len(parents) > 1:
485 return parents
485 return parents
486 if repo.ui.debugflag:
486 if repo.ui.debugflag:
487 return [parents[0], repo['null']]
487 return [parents[0], repo['null']]
488 if parents[0].rev() >= intrev(ctx) - 1:
488 if parents[0].rev() >= intrev(ctx) - 1:
489 return []
489 return []
490 return parents
490 return parents
491
491
492 def expandpats(pats):
492 def expandpats(pats):
493 '''Expand bare globs when running on windows.
493 '''Expand bare globs when running on windows.
494 On posix we assume it already has already been done by sh.'''
494 On posix we assume it already has already been done by sh.'''
495 if not util.expandglobs:
495 if not util.expandglobs:
496 return list(pats)
496 return list(pats)
497 ret = []
497 ret = []
498 for kindpat in pats:
498 for kindpat in pats:
499 kind, pat = matchmod._patsplit(kindpat, None)
499 kind, pat = matchmod._patsplit(kindpat, None)
500 if kind is None:
500 if kind is None:
501 try:
501 try:
502 globbed = glob.glob(pat)
502 globbed = glob.glob(pat)
503 except re.error:
503 except re.error:
504 globbed = [pat]
504 globbed = [pat]
505 if globbed:
505 if globbed:
506 ret.extend(globbed)
506 ret.extend(globbed)
507 continue
507 continue
508 ret.append(kindpat)
508 ret.append(kindpat)
509 return ret
509 return ret
510
510
511 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
511 def matchandpats(ctx, pats=(), opts=None, globbed=False, default='relpath',
512 badfn=None):
512 badfn=None):
513 '''Return a matcher and the patterns that were used.
513 '''Return a matcher and the patterns that were used.
514 The matcher will warn about bad matches, unless an alternate badfn callback
514 The matcher will warn about bad matches, unless an alternate badfn callback
515 is provided.'''
515 is provided.'''
516 if pats == ("",):
516 if pats == ("",):
517 pats = []
517 pats = []
518 if opts is None:
518 if opts is None:
519 opts = {}
519 opts = {}
520 if not globbed and default == 'relpath':
520 if not globbed and default == 'relpath':
521 pats = expandpats(pats or [])
521 pats = expandpats(pats or [])
522
522
523 def bad(f, msg):
523 def bad(f, msg):
524 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
524 ctx.repo().ui.warn("%s: %s\n" % (m.rel(f), msg))
525
525
526 if badfn is None:
526 if badfn is None:
527 badfn = bad
527 badfn = bad
528
528
529 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
529 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
530 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
530 default, listsubrepos=opts.get('subrepos'), badfn=badfn)
531
531
532 if m.always():
532 if m.always():
533 pats = []
533 pats = []
534 return m, pats
534 return m, pats
535
535
536 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
536 def match(ctx, pats=(), opts=None, globbed=False, default='relpath',
537 badfn=None):
537 badfn=None):
538 '''Return a matcher that will warn about bad matches.'''
538 '''Return a matcher that will warn about bad matches.'''
539 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
539 return matchandpats(ctx, pats, opts, globbed, default, badfn=badfn)[0]
540
540
541 def matchall(repo):
541 def matchall(repo):
542 '''Return a matcher that will efficiently match everything.'''
542 '''Return a matcher that will efficiently match everything.'''
543 return matchmod.always(repo.root, repo.getcwd())
543 return matchmod.always(repo.root, repo.getcwd())
544
544
545 def matchfiles(repo, files, badfn=None):
545 def matchfiles(repo, files, badfn=None):
546 '''Return a matcher that will efficiently match exactly these files.'''
546 '''Return a matcher that will efficiently match exactly these files.'''
547 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
547 return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
548
548
549 def origpath(ui, repo, filepath):
549 def origpath(ui, repo, filepath):
550 '''customize where .orig files are created
550 '''customize where .orig files are created
551
551
552 Fetch user defined path from config file: [ui] origbackuppath = <path>
552 Fetch user defined path from config file: [ui] origbackuppath = <path>
553 Fall back to default (filepath) if not specified
553 Fall back to default (filepath) if not specified
554 '''
554 '''
555 origbackuppath = ui.config('ui', 'origbackuppath')
555 origbackuppath = ui.config('ui', 'origbackuppath')
556 if origbackuppath is None:
556 if origbackuppath is None:
557 return filepath + ".orig"
557 return filepath + ".orig"
558
558
559 filepathfromroot = os.path.relpath(filepath, start=repo.root)
559 filepathfromroot = os.path.relpath(filepath, start=repo.root)
560 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
560 fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
561
561
562 origbackupdir = repo.vfs.dirname(fullorigpath)
562 origbackupdir = repo.vfs.dirname(fullorigpath)
563 if not repo.vfs.exists(origbackupdir):
563 if not repo.vfs.exists(origbackupdir):
564 ui.note(_('creating directory: %s\n') % origbackupdir)
564 ui.note(_('creating directory: %s\n') % origbackupdir)
565 util.makedirs(origbackupdir)
565 util.makedirs(origbackupdir)
566
566
567 return fullorigpath + ".orig"
567 return fullorigpath + ".orig"
568
568
569 class _containsnode(object):
569 class _containsnode(object):
570 """proxy __contains__(node) to container.__contains__ which accepts revs"""
570 """proxy __contains__(node) to container.__contains__ which accepts revs"""
571
571
572 def __init__(self, repo, revcontainer):
572 def __init__(self, repo, revcontainer):
573 self._torev = repo.changelog.rev
573 self._torev = repo.changelog.rev
574 self._revcontains = revcontainer.__contains__
574 self._revcontains = revcontainer.__contains__
575
575
576 def __contains__(self, node):
576 def __contains__(self, node):
577 return self._revcontains(self._torev(node))
577 return self._revcontains(self._torev(node))
578
578
579 def cleanupnodes(repo, mapping, operation):
579 def cleanupnodes(repo, mapping, operation):
580 """do common cleanups when old nodes are replaced by new nodes
580 """do common cleanups when old nodes are replaced by new nodes
581
581
582 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
582 That includes writing obsmarkers or stripping nodes, and moving bookmarks.
583 (we might also want to move working directory parent in the future)
583 (we might also want to move working directory parent in the future)
584
584
585 mapping is {oldnode: [newnode]} or a iterable of nodes if they do not have
585 mapping is {oldnode: [newnode]} or a iterable of nodes if they do not have
586 replacements. operation is a string, like "rebase".
586 replacements. operation is a string, like "rebase".
587 """
587 """
588 if not util.safehasattr(mapping, 'items'):
588 if not util.safehasattr(mapping, 'items'):
589 mapping = {n: () for n in mapping}
589 mapping = {n: () for n in mapping}
590
590
591 with repo.transaction('cleanup') as tr:
591 with repo.transaction('cleanup') as tr:
592 # Move bookmarks
592 # Move bookmarks
593 bmarks = repo._bookmarks
593 bmarks = repo._bookmarks
594 bmarkchanges = []
594 bmarkchanges = []
595 allnewnodes = [n for ns in mapping.values() for n in ns]
595 allnewnodes = [n for ns in mapping.values() for n in ns]
596 for oldnode, newnodes in mapping.items():
596 for oldnode, newnodes in mapping.items():
597 oldbmarks = repo.nodebookmarks(oldnode)
597 oldbmarks = repo.nodebookmarks(oldnode)
598 if not oldbmarks:
598 if not oldbmarks:
599 continue
599 continue
600 from . import bookmarks # avoid import cycle
600 from . import bookmarks # avoid import cycle
601 if len(newnodes) > 1:
601 if len(newnodes) > 1:
602 # usually a split, take the one with biggest rev number
602 # usually a split, take the one with biggest rev number
603 newnode = next(repo.set('max(%ln)', newnodes)).node()
603 newnode = next(repo.set('max(%ln)', newnodes)).node()
604 elif len(newnodes) == 0:
604 elif len(newnodes) == 0:
605 # move bookmark backwards
605 # move bookmark backwards
606 roots = list(repo.set('max((::%n) - %ln)', oldnode,
606 roots = list(repo.set('max((::%n) - %ln)', oldnode,
607 list(mapping)))
607 list(mapping)))
608 if roots:
608 if roots:
609 newnode = roots[0].node()
609 newnode = roots[0].node()
610 else:
610 else:
611 newnode = nullid
611 newnode = nullid
612 else:
612 else:
613 newnode = newnodes[0]
613 newnode = newnodes[0]
614 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
614 repo.ui.debug('moving bookmarks %r from %s to %s\n' %
615 (oldbmarks, hex(oldnode), hex(newnode)))
615 (oldbmarks, hex(oldnode), hex(newnode)))
616 # Delete divergent bookmarks being parents of related newnodes
616 # Delete divergent bookmarks being parents of related newnodes
617 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
617 deleterevs = repo.revs('parents(roots(%ln & (::%n))) - parents(%n)',
618 allnewnodes, newnode, oldnode)
618 allnewnodes, newnode, oldnode)
619 deletenodes = _containsnode(repo, deleterevs)
619 deletenodes = _containsnode(repo, deleterevs)
620 for name in oldbmarks:
620 for name in oldbmarks:
621 bmarkchanges.append((name, newnode))
621 bmarkchanges.append((name, newnode))
622 for b in bookmarks.divergent2delete(repo, deletenodes, name):
622 for b in bookmarks.divergent2delete(repo, deletenodes, name):
623 bmarkchanges.append((b, None))
623 bmarkchanges.append((b, None))
624
624
625 if bmarkchanges:
625 if bmarkchanges:
626 bmarks.applychanges(repo, tr, bmarkchanges)
626 bmarks.applychanges(repo, tr, bmarkchanges)
627
627
628 # Obsolete or strip nodes
628 # Obsolete or strip nodes
629 if obsolete.isenabled(repo, obsolete.createmarkersopt):
629 if obsolete.isenabled(repo, obsolete.createmarkersopt):
630 # If a node is already obsoleted, and we want to obsolete it
630 # If a node is already obsoleted, and we want to obsolete it
631 # without a successor, skip that obssolete request since it's
631 # without a successor, skip that obssolete request since it's
632 # unnecessary. That's the "if s or not isobs(n)" check below.
632 # unnecessary. That's the "if s or not isobs(n)" check below.
633 # Also sort the node in topology order, that might be useful for
633 # Also sort the node in topology order, that might be useful for
634 # some obsstore logic.
634 # some obsstore logic.
635 # NOTE: the filtering and sorting might belong to createmarkers.
635 # NOTE: the filtering and sorting might belong to createmarkers.
636 # Unfiltered repo is needed since nodes in mapping might be hidden.
636 # Unfiltered repo is needed since nodes in mapping might be hidden.
637 unfi = repo.unfiltered()
637 unfi = repo.unfiltered()
638 isobs = unfi.obsstore.successors.__contains__
638 isobs = unfi.obsstore.successors.__contains__
639 torev = unfi.changelog.rev
639 torev = unfi.changelog.rev
640 sortfunc = lambda ns: torev(ns[0])
640 sortfunc = lambda ns: torev(ns[0])
641 rels = [(unfi[n], tuple(unfi[m] for m in s))
641 rels = [(unfi[n], tuple(unfi[m] for m in s))
642 for n, s in sorted(mapping.items(), key=sortfunc)
642 for n, s in sorted(mapping.items(), key=sortfunc)
643 if s or not isobs(n)]
643 if s or not isobs(n)]
644 obsolete.createmarkers(repo, rels, operation=operation)
644 obsolete.createmarkers(repo, rels, operation=operation)
645 else:
645 else:
646 from . import repair # avoid import cycle
646 from . import repair # avoid import cycle
647 repair.delayedstrip(repo.ui, repo, list(mapping), operation)
647 repair.delayedstrip(repo.ui, repo, list(mapping), operation)
648
648
649 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
649 def addremove(repo, matcher, prefix, opts=None, dry_run=None, similarity=None):
650 if opts is None:
650 if opts is None:
651 opts = {}
651 opts = {}
652 m = matcher
652 m = matcher
653 if dry_run is None:
653 if dry_run is None:
654 dry_run = opts.get('dry_run')
654 dry_run = opts.get('dry_run')
655 if similarity is None:
655 if similarity is None:
656 similarity = float(opts.get('similarity') or 0)
656 similarity = float(opts.get('similarity') or 0)
657
657
658 ret = 0
658 ret = 0
659 join = lambda f: os.path.join(prefix, f)
659 join = lambda f: os.path.join(prefix, f)
660
660
661 wctx = repo[None]
661 wctx = repo[None]
662 for subpath in sorted(wctx.substate):
662 for subpath in sorted(wctx.substate):
663 submatch = matchmod.subdirmatcher(subpath, m)
663 submatch = matchmod.subdirmatcher(subpath, m)
664 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
664 if opts.get('subrepos') or m.exact(subpath) or any(submatch.files()):
665 sub = wctx.sub(subpath)
665 sub = wctx.sub(subpath)
666 try:
666 try:
667 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
667 if sub.addremove(submatch, prefix, opts, dry_run, similarity):
668 ret = 1
668 ret = 1
669 except error.LookupError:
669 except error.LookupError:
670 repo.ui.status(_("skipping missing subrepository: %s\n")
670 repo.ui.status(_("skipping missing subrepository: %s\n")
671 % join(subpath))
671 % join(subpath))
672
672
673 rejected = []
673 rejected = []
674 def badfn(f, msg):
674 def badfn(f, msg):
675 if f in m.files():
675 if f in m.files():
676 m.bad(f, msg)
676 m.bad(f, msg)
677 rejected.append(f)
677 rejected.append(f)
678
678
679 badmatch = matchmod.badmatch(m, badfn)
679 badmatch = matchmod.badmatch(m, badfn)
680 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
680 added, unknown, deleted, removed, forgotten = _interestingfiles(repo,
681 badmatch)
681 badmatch)
682
682
683 unknownset = set(unknown + forgotten)
683 unknownset = set(unknown + forgotten)
684 toprint = unknownset.copy()
684 toprint = unknownset.copy()
685 toprint.update(deleted)
685 toprint.update(deleted)
686 for abs in sorted(toprint):
686 for abs in sorted(toprint):
687 if repo.ui.verbose or not m.exact(abs):
687 if repo.ui.verbose or not m.exact(abs):
688 if abs in unknownset:
688 if abs in unknownset:
689 status = _('adding %s\n') % m.uipath(abs)
689 status = _('adding %s\n') % m.uipath(abs)
690 else:
690 else:
691 status = _('removing %s\n') % m.uipath(abs)
691 status = _('removing %s\n') % m.uipath(abs)
692 repo.ui.status(status)
692 repo.ui.status(status)
693
693
694 renames = _findrenames(repo, m, added + unknown, removed + deleted,
694 renames = _findrenames(repo, m, added + unknown, removed + deleted,
695 similarity)
695 similarity)
696
696
697 if not dry_run:
697 if not dry_run:
698 _markchanges(repo, unknown + forgotten, deleted, renames)
698 _markchanges(repo, unknown + forgotten, deleted, renames)
699
699
700 for f in rejected:
700 for f in rejected:
701 if f in m.files():
701 if f in m.files():
702 return 1
702 return 1
703 return ret
703 return ret
704
704
705 def marktouched(repo, files, similarity=0.0):
705 def marktouched(repo, files, similarity=0.0):
706 '''Assert that files have somehow been operated upon. files are relative to
706 '''Assert that files have somehow been operated upon. files are relative to
707 the repo root.'''
707 the repo root.'''
708 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
708 m = matchfiles(repo, files, badfn=lambda x, y: rejected.append(x))
709 rejected = []
709 rejected = []
710
710
711 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
711 added, unknown, deleted, removed, forgotten = _interestingfiles(repo, m)
712
712
713 if repo.ui.verbose:
713 if repo.ui.verbose:
714 unknownset = set(unknown + forgotten)
714 unknownset = set(unknown + forgotten)
715 toprint = unknownset.copy()
715 toprint = unknownset.copy()
716 toprint.update(deleted)
716 toprint.update(deleted)
717 for abs in sorted(toprint):
717 for abs in sorted(toprint):
718 if abs in unknownset:
718 if abs in unknownset:
719 status = _('adding %s\n') % abs
719 status = _('adding %s\n') % abs
720 else:
720 else:
721 status = _('removing %s\n') % abs
721 status = _('removing %s\n') % abs
722 repo.ui.status(status)
722 repo.ui.status(status)
723
723
724 renames = _findrenames(repo, m, added + unknown, removed + deleted,
724 renames = _findrenames(repo, m, added + unknown, removed + deleted,
725 similarity)
725 similarity)
726
726
727 _markchanges(repo, unknown + forgotten, deleted, renames)
727 _markchanges(repo, unknown + forgotten, deleted, renames)
728
728
729 for f in rejected:
729 for f in rejected:
730 if f in m.files():
730 if f in m.files():
731 return 1
731 return 1
732 return 0
732 return 0
733
733
734 def _interestingfiles(repo, matcher):
734 def _interestingfiles(repo, matcher):
735 '''Walk dirstate with matcher, looking for files that addremove would care
735 '''Walk dirstate with matcher, looking for files that addremove would care
736 about.
736 about.
737
737
738 This is different from dirstate.status because it doesn't care about
738 This is different from dirstate.status because it doesn't care about
739 whether files are modified or clean.'''
739 whether files are modified or clean.'''
740 added, unknown, deleted, removed, forgotten = [], [], [], [], []
740 added, unknown, deleted, removed, forgotten = [], [], [], [], []
741 audit_path = pathutil.pathauditor(repo.root)
741 audit_path = pathutil.pathauditor(repo.root)
742
742
743 ctx = repo[None]
743 ctx = repo[None]
744 dirstate = repo.dirstate
744 dirstate = repo.dirstate
745 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
745 walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
746 full=False)
746 full=False)
747 for abs, st in walkresults.iteritems():
747 for abs, st in walkresults.iteritems():
748 dstate = dirstate[abs]
748 dstate = dirstate[abs]
749 if dstate == '?' and audit_path.check(abs):
749 if dstate == '?' and audit_path.check(abs):
750 unknown.append(abs)
750 unknown.append(abs)
751 elif dstate != 'r' and not st:
751 elif dstate != 'r' and not st:
752 deleted.append(abs)
752 deleted.append(abs)
753 elif dstate == 'r' and st:
753 elif dstate == 'r' and st:
754 forgotten.append(abs)
754 forgotten.append(abs)
755 # for finding renames
755 # for finding renames
756 elif dstate == 'r' and not st:
756 elif dstate == 'r' and not st:
757 removed.append(abs)
757 removed.append(abs)
758 elif dstate == 'a':
758 elif dstate == 'a':
759 added.append(abs)
759 added.append(abs)
760
760
761 return added, unknown, deleted, removed, forgotten
761 return added, unknown, deleted, removed, forgotten
762
762
763 def _findrenames(repo, matcher, added, removed, similarity):
763 def _findrenames(repo, matcher, added, removed, similarity):
764 '''Find renames from removed files to added ones.'''
764 '''Find renames from removed files to added ones.'''
765 renames = {}
765 renames = {}
766 if similarity > 0:
766 if similarity > 0:
767 for old, new, score in similar.findrenames(repo, added, removed,
767 for old, new, score in similar.findrenames(repo, added, removed,
768 similarity):
768 similarity):
769 if (repo.ui.verbose or not matcher.exact(old)
769 if (repo.ui.verbose or not matcher.exact(old)
770 or not matcher.exact(new)):
770 or not matcher.exact(new)):
771 repo.ui.status(_('recording removal of %s as rename to %s '
771 repo.ui.status(_('recording removal of %s as rename to %s '
772 '(%d%% similar)\n') %
772 '(%d%% similar)\n') %
773 (matcher.rel(old), matcher.rel(new),
773 (matcher.rel(old), matcher.rel(new),
774 score * 100))
774 score * 100))
775 renames[new] = old
775 renames[new] = old
776 return renames
776 return renames
777
777
778 def _markchanges(repo, unknown, deleted, renames):
778 def _markchanges(repo, unknown, deleted, renames):
779 '''Marks the files in unknown as added, the files in deleted as removed,
779 '''Marks the files in unknown as added, the files in deleted as removed,
780 and the files in renames as copied.'''
780 and the files in renames as copied.'''
781 wctx = repo[None]
781 wctx = repo[None]
782 with repo.wlock():
782 with repo.wlock():
783 wctx.forget(deleted)
783 wctx.forget(deleted)
784 wctx.add(unknown)
784 wctx.add(unknown)
785 for new, old in renames.iteritems():
785 for new, old in renames.iteritems():
786 wctx.copy(old, new)
786 wctx.copy(old, new)
787
787
788 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
788 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
789 """Update the dirstate to reflect the intent of copying src to dst. For
789 """Update the dirstate to reflect the intent of copying src to dst. For
790 different reasons it might not end with dst being marked as copied from src.
790 different reasons it might not end with dst being marked as copied from src.
791 """
791 """
792 origsrc = repo.dirstate.copied(src) or src
792 origsrc = repo.dirstate.copied(src) or src
793 if dst == origsrc: # copying back a copy?
793 if dst == origsrc: # copying back a copy?
794 if repo.dirstate[dst] not in 'mn' and not dryrun:
794 if repo.dirstate[dst] not in 'mn' and not dryrun:
795 repo.dirstate.normallookup(dst)
795 repo.dirstate.normallookup(dst)
796 else:
796 else:
797 if repo.dirstate[origsrc] == 'a' and origsrc == src:
797 if repo.dirstate[origsrc] == 'a' and origsrc == src:
798 if not ui.quiet:
798 if not ui.quiet:
799 ui.warn(_("%s has not been committed yet, so no copy "
799 ui.warn(_("%s has not been committed yet, so no copy "
800 "data will be stored for %s.\n")
800 "data will be stored for %s.\n")
801 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
801 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
802 if repo.dirstate[dst] in '?r' and not dryrun:
802 if repo.dirstate[dst] in '?r' and not dryrun:
803 wctx.add([dst])
803 wctx.add([dst])
804 elif not dryrun:
804 elif not dryrun:
805 wctx.copy(origsrc, dst)
805 wctx.copy(origsrc, dst)
806
806
807 def readrequires(opener, supported):
807 def readrequires(opener, supported):
808 '''Reads and parses .hg/requires and checks if all entries found
808 '''Reads and parses .hg/requires and checks if all entries found
809 are in the list of supported features.'''
809 are in the list of supported features.'''
810 requirements = set(opener.read("requires").splitlines())
810 requirements = set(opener.read("requires").splitlines())
811 missings = []
811 missings = []
812 for r in requirements:
812 for r in requirements:
813 if r not in supported:
813 if r not in supported:
814 if not r or not r[0].isalnum():
814 if not r or not r[0].isalnum():
815 raise error.RequirementError(_(".hg/requires file is corrupt"))
815 raise error.RequirementError(_(".hg/requires file is corrupt"))
816 missings.append(r)
816 missings.append(r)
817 missings.sort()
817 missings.sort()
818 if missings:
818 if missings:
819 raise error.RequirementError(
819 raise error.RequirementError(
820 _("repository requires features unknown to this Mercurial: %s")
820 _("repository requires features unknown to this Mercurial: %s")
821 % " ".join(missings),
821 % " ".join(missings),
822 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
822 hint=_("see https://mercurial-scm.org/wiki/MissingRequirement"
823 " for more information"))
823 " for more information"))
824 return requirements
824 return requirements
825
825
826 def writerequires(opener, requirements):
826 def writerequires(opener, requirements):
827 with opener('requires', 'w') as fp:
827 with opener('requires', 'w') as fp:
828 for r in sorted(requirements):
828 for r in sorted(requirements):
829 fp.write("%s\n" % r)
829 fp.write("%s\n" % r)
830
830
831 class filecachesubentry(object):
831 class filecachesubentry(object):
832 def __init__(self, path, stat):
832 def __init__(self, path, stat):
833 self.path = path
833 self.path = path
834 self.cachestat = None
834 self.cachestat = None
835 self._cacheable = None
835 self._cacheable = None
836
836
837 if stat:
837 if stat:
838 self.cachestat = filecachesubentry.stat(self.path)
838 self.cachestat = filecachesubentry.stat(self.path)
839
839
840 if self.cachestat:
840 if self.cachestat:
841 self._cacheable = self.cachestat.cacheable()
841 self._cacheable = self.cachestat.cacheable()
842 else:
842 else:
843 # None means we don't know yet
843 # None means we don't know yet
844 self._cacheable = None
844 self._cacheable = None
845
845
846 def refresh(self):
846 def refresh(self):
847 if self.cacheable():
847 if self.cacheable():
848 self.cachestat = filecachesubentry.stat(self.path)
848 self.cachestat = filecachesubentry.stat(self.path)
849
849
850 def cacheable(self):
850 def cacheable(self):
851 if self._cacheable is not None:
851 if self._cacheable is not None:
852 return self._cacheable
852 return self._cacheable
853
853
854 # we don't know yet, assume it is for now
854 # we don't know yet, assume it is for now
855 return True
855 return True
856
856
857 def changed(self):
857 def changed(self):
858 # no point in going further if we can't cache it
858 # no point in going further if we can't cache it
859 if not self.cacheable():
859 if not self.cacheable():
860 return True
860 return True
861
861
862 newstat = filecachesubentry.stat(self.path)
862 newstat = filecachesubentry.stat(self.path)
863
863
864 # we may not know if it's cacheable yet, check again now
864 # we may not know if it's cacheable yet, check again now
865 if newstat and self._cacheable is None:
865 if newstat and self._cacheable is None:
866 self._cacheable = newstat.cacheable()
866 self._cacheable = newstat.cacheable()
867
867
868 # check again
868 # check again
869 if not self._cacheable:
869 if not self._cacheable:
870 return True
870 return True
871
871
872 if self.cachestat != newstat:
872 if self.cachestat != newstat:
873 self.cachestat = newstat
873 self.cachestat = newstat
874 return True
874 return True
875 else:
875 else:
876 return False
876 return False
877
877
878 @staticmethod
878 @staticmethod
879 def stat(path):
879 def stat(path):
880 try:
880 try:
881 return util.cachestat(path)
881 return util.cachestat(path)
882 except OSError as e:
882 except OSError as e:
883 if e.errno != errno.ENOENT:
883 if e.errno != errno.ENOENT:
884 raise
884 raise
885
885
886 class filecacheentry(object):
886 class filecacheentry(object):
887 def __init__(self, paths, stat=True):
887 def __init__(self, paths, stat=True):
888 self._entries = []
888 self._entries = []
889 for path in paths:
889 for path in paths:
890 self._entries.append(filecachesubentry(path, stat))
890 self._entries.append(filecachesubentry(path, stat))
891
891
892 def changed(self):
892 def changed(self):
893 '''true if any entry has changed'''
893 '''true if any entry has changed'''
894 for entry in self._entries:
894 for entry in self._entries:
895 if entry.changed():
895 if entry.changed():
896 return True
896 return True
897 return False
897 return False
898
898
899 def refresh(self):
899 def refresh(self):
900 for entry in self._entries:
900 for entry in self._entries:
901 entry.refresh()
901 entry.refresh()
902
902
903 class filecache(object):
903 class filecache(object):
904 '''A property like decorator that tracks files under .hg/ for updates.
904 '''A property like decorator that tracks files under .hg/ for updates.
905
905
906 Records stat info when called in _filecache.
906 Records stat info when called in _filecache.
907
907
908 On subsequent calls, compares old stat info with new info, and recreates the
908 On subsequent calls, compares old stat info with new info, and recreates the
909 object when any of the files changes, updating the new stat info in
909 object when any of the files changes, updating the new stat info in
910 _filecache.
910 _filecache.
911
911
912 Mercurial either atomic renames or appends for files under .hg,
912 Mercurial either atomic renames or appends for files under .hg,
913 so to ensure the cache is reliable we need the filesystem to be able
913 so to ensure the cache is reliable we need the filesystem to be able
914 to tell us if a file has been replaced. If it can't, we fallback to
914 to tell us if a file has been replaced. If it can't, we fallback to
915 recreating the object on every call (essentially the same behavior as
915 recreating the object on every call (essentially the same behavior as
916 propertycache).
916 propertycache).
917
917
918 '''
918 '''
919 def __init__(self, *paths):
919 def __init__(self, *paths):
920 self.paths = paths
920 self.paths = paths
921
921
922 def join(self, obj, fname):
922 def join(self, obj, fname):
923 """Used to compute the runtime path of a cached file.
923 """Used to compute the runtime path of a cached file.
924
924
925 Users should subclass filecache and provide their own version of this
925 Users should subclass filecache and provide their own version of this
926 function to call the appropriate join function on 'obj' (an instance
926 function to call the appropriate join function on 'obj' (an instance
927 of the class that its member function was decorated).
927 of the class that its member function was decorated).
928 """
928 """
929 raise NotImplementedError
929 raise NotImplementedError
930
930
931 def __call__(self, func):
931 def __call__(self, func):
932 self.func = func
932 self.func = func
933 self.name = func.__name__.encode('ascii')
933 self.name = func.__name__.encode('ascii')
934 return self
934 return self
935
935
936 def __get__(self, obj, type=None):
936 def __get__(self, obj, type=None):
937 # if accessed on the class, return the descriptor itself.
937 # if accessed on the class, return the descriptor itself.
938 if obj is None:
938 if obj is None:
939 return self
939 return self
940 # do we need to check if the file changed?
940 # do we need to check if the file changed?
941 if self.name in obj.__dict__:
941 if self.name in obj.__dict__:
942 assert self.name in obj._filecache, self.name
942 assert self.name in obj._filecache, self.name
943 return obj.__dict__[self.name]
943 return obj.__dict__[self.name]
944
944
945 entry = obj._filecache.get(self.name)
945 entry = obj._filecache.get(self.name)
946
946
947 if entry:
947 if entry:
948 if entry.changed():
948 if entry.changed():
949 entry.obj = self.func(obj)
949 entry.obj = self.func(obj)
950 else:
950 else:
951 paths = [self.join(obj, path) for path in self.paths]
951 paths = [self.join(obj, path) for path in self.paths]
952
952
953 # We stat -before- creating the object so our cache doesn't lie if
953 # We stat -before- creating the object so our cache doesn't lie if
954 # a writer modified between the time we read and stat
954 # a writer modified between the time we read and stat
955 entry = filecacheentry(paths, True)
955 entry = filecacheentry(paths, True)
956 entry.obj = self.func(obj)
956 entry.obj = self.func(obj)
957
957
958 obj._filecache[self.name] = entry
958 obj._filecache[self.name] = entry
959
959
960 obj.__dict__[self.name] = entry.obj
960 obj.__dict__[self.name] = entry.obj
961 return entry.obj
961 return entry.obj
962
962
963 def __set__(self, obj, value):
963 def __set__(self, obj, value):
964 if self.name not in obj._filecache:
964 if self.name not in obj._filecache:
965 # we add an entry for the missing value because X in __dict__
965 # we add an entry for the missing value because X in __dict__
966 # implies X in _filecache
966 # implies X in _filecache
967 paths = [self.join(obj, path) for path in self.paths]
967 paths = [self.join(obj, path) for path in self.paths]
968 ce = filecacheentry(paths, False)
968 ce = filecacheentry(paths, False)
969 obj._filecache[self.name] = ce
969 obj._filecache[self.name] = ce
970 else:
970 else:
971 ce = obj._filecache[self.name]
971 ce = obj._filecache[self.name]
972
972
973 ce.obj = value # update cached copy
973 ce.obj = value # update cached copy
974 obj.__dict__[self.name] = value # update copy returned by obj.x
974 obj.__dict__[self.name] = value # update copy returned by obj.x
975
975
976 def __delete__(self, obj):
976 def __delete__(self, obj):
977 try:
977 try:
978 del obj.__dict__[self.name]
978 del obj.__dict__[self.name]
979 except KeyError:
979 except KeyError:
980 raise AttributeError(self.name)
980 raise AttributeError(self.name)
981
981
982 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
982 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
983 if lock is None:
983 if lock is None:
984 raise error.LockInheritanceContractViolation(
984 raise error.LockInheritanceContractViolation(
985 'lock can only be inherited while held')
985 'lock can only be inherited while held')
986 if environ is None:
986 if environ is None:
987 environ = {}
987 environ = {}
988 with lock.inherit() as locker:
988 with lock.inherit() as locker:
989 environ[envvar] = locker
989 environ[envvar] = locker
990 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
990 return repo.ui.system(cmd, environ=environ, *args, **kwargs)
991
991
992 def wlocksub(repo, cmd, *args, **kwargs):
992 def wlocksub(repo, cmd, *args, **kwargs):
993 """run cmd as a subprocess that allows inheriting repo's wlock
993 """run cmd as a subprocess that allows inheriting repo's wlock
994
994
995 This can only be called while the wlock is held. This takes all the
995 This can only be called while the wlock is held. This takes all the
996 arguments that ui.system does, and returns the exit code of the
996 arguments that ui.system does, and returns the exit code of the
997 subprocess."""
997 subprocess."""
998 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
998 return _locksub(repo, repo.currentwlock(), 'HG_WLOCK_LOCKER', cmd, *args,
999 **kwargs)
999 **kwargs)
1000
1000
1001 def gdinitconfig(ui):
1001 def gdinitconfig(ui):
1002 """helper function to know if a repo should be created as general delta
1002 """helper function to know if a repo should be created as general delta
1003 """
1003 """
1004 # experimental config: format.generaldelta
1004 # experimental config: format.generaldelta
1005 return (ui.configbool('format', 'generaldelta')
1005 return (ui.configbool('format', 'generaldelta')
1006 or ui.configbool('format', 'usegeneraldelta'))
1006 or ui.configbool('format', 'usegeneraldelta'))
1007
1007
1008 def gddeltaconfig(ui):
1008 def gddeltaconfig(ui):
1009 """helper function to know if incoming delta should be optimised
1009 """helper function to know if incoming delta should be optimised
1010 """
1010 """
1011 # experimental config: format.generaldelta
1011 # experimental config: format.generaldelta
1012 return ui.configbool('format', 'generaldelta')
1012 return ui.configbool('format', 'generaldelta')
1013
1013
1014 class simplekeyvaluefile(object):
1014 class simplekeyvaluefile(object):
1015 """A simple file with key=value lines
1015 """A simple file with key=value lines
1016
1016
1017 Keys must be alphanumerics and start with a letter, values must not
1017 Keys must be alphanumerics and start with a letter, values must not
1018 contain '\n' characters"""
1018 contain '\n' characters"""
1019 firstlinekey = '__firstline'
1019 firstlinekey = '__firstline'
1020
1020
1021 def __init__(self, vfs, path, keys=None):
1021 def __init__(self, vfs, path, keys=None):
1022 self.vfs = vfs
1022 self.vfs = vfs
1023 self.path = path
1023 self.path = path
1024
1024
1025 def read(self, firstlinenonkeyval=False):
1025 def read(self, firstlinenonkeyval=False):
1026 """Read the contents of a simple key-value file
1026 """Read the contents of a simple key-value file
1027
1027
1028 'firstlinenonkeyval' indicates whether the first line of file should
1028 'firstlinenonkeyval' indicates whether the first line of file should
1029 be treated as a key-value pair or reuturned fully under the
1029 be treated as a key-value pair or reuturned fully under the
1030 __firstline key."""
1030 __firstline key."""
1031 lines = self.vfs.readlines(self.path)
1031 lines = self.vfs.readlines(self.path)
1032 d = {}
1032 d = {}
1033 if firstlinenonkeyval:
1033 if firstlinenonkeyval:
1034 if not lines:
1034 if not lines:
1035 e = _("empty simplekeyvalue file")
1035 e = _("empty simplekeyvalue file")
1036 raise error.CorruptedState(e)
1036 raise error.CorruptedState(e)
1037 # we don't want to include '\n' in the __firstline
1037 # we don't want to include '\n' in the __firstline
1038 d[self.firstlinekey] = lines[0][:-1]
1038 d[self.firstlinekey] = lines[0][:-1]
1039 del lines[0]
1039 del lines[0]
1040
1040
1041 try:
1041 try:
1042 # the 'if line.strip()' part prevents us from failing on empty
1042 # the 'if line.strip()' part prevents us from failing on empty
1043 # lines which only contain '\n' therefore are not skipped
1043 # lines which only contain '\n' therefore are not skipped
1044 # by 'if line'
1044 # by 'if line'
1045 updatedict = dict(line[:-1].split('=', 1) for line in lines
1045 updatedict = dict(line[:-1].split('=', 1) for line in lines
1046 if line.strip())
1046 if line.strip())
1047 if self.firstlinekey in updatedict:
1047 if self.firstlinekey in updatedict:
1048 e = _("%r can't be used as a key")
1048 e = _("%r can't be used as a key")
1049 raise error.CorruptedState(e % self.firstlinekey)
1049 raise error.CorruptedState(e % self.firstlinekey)
1050 d.update(updatedict)
1050 d.update(updatedict)
1051 except ValueError as e:
1051 except ValueError as e:
1052 raise error.CorruptedState(str(e))
1052 raise error.CorruptedState(str(e))
1053 return d
1053 return d
1054
1054
1055 def write(self, data, firstline=None):
1055 def write(self, data, firstline=None):
1056 """Write key=>value mapping to a file
1056 """Write key=>value mapping to a file
1057 data is a dict. Keys must be alphanumerical and start with a letter.
1057 data is a dict. Keys must be alphanumerical and start with a letter.
1058 Values must not contain newline characters.
1058 Values must not contain newline characters.
1059
1059
1060 If 'firstline' is not None, it is written to file before
1060 If 'firstline' is not None, it is written to file before
1061 everything else, as it is, not in a key=value form"""
1061 everything else, as it is, not in a key=value form"""
1062 lines = []
1062 lines = []
1063 if firstline is not None:
1063 if firstline is not None:
1064 lines.append('%s\n' % firstline)
1064 lines.append('%s\n' % firstline)
1065
1065
1066 for k, v in data.items():
1066 for k, v in data.items():
1067 if k == self.firstlinekey:
1067 if k == self.firstlinekey:
1068 e = "key name '%s' is reserved" % self.firstlinekey
1068 e = "key name '%s' is reserved" % self.firstlinekey
1069 raise error.ProgrammingError(e)
1069 raise error.ProgrammingError(e)
1070 if not k[0].isalpha():
1070 if not k[0].isalpha():
1071 e = "keys must start with a letter in a key-value file"
1071 e = "keys must start with a letter in a key-value file"
1072 raise error.ProgrammingError(e)
1072 raise error.ProgrammingError(e)
1073 if not k.isalnum():
1073 if not k.isalnum():
1074 e = "invalid key name in a simple key-value file"
1074 e = "invalid key name in a simple key-value file"
1075 raise error.ProgrammingError(e)
1075 raise error.ProgrammingError(e)
1076 if '\n' in v:
1076 if '\n' in v:
1077 e = "invalid value in a simple key-value file"
1077 e = "invalid value in a simple key-value file"
1078 raise error.ProgrammingError(e)
1078 raise error.ProgrammingError(e)
1079 lines.append("%s=%s\n" % (k, v))
1079 lines.append("%s=%s\n" % (k, v))
1080 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1080 with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
1081 fp.write(''.join(lines))
1081 fp.write(''.join(lines))
1082
1082
1083 def registersummarycallback(repo, otr):
1083 _reportobsoletedsource = [
1084 'pull',
1085 'push',
1086 'serve',
1087 'unbundle',
1088 ]
1089
1090 def registersummarycallback(repo, otr, txnname=''):
1084 """register a callback to issue a summary after the transaction is closed
1091 """register a callback to issue a summary after the transaction is closed
1085 """
1092 """
1086 reporef = weakref.ref(repo)
1093 for source in _reportobsoletedsource:
1087 def reportsummary(tr):
1094 if txnname.startswith(source):
1088 """the actual callback reporting the summary"""
1095 reporef = weakref.ref(repo)
1089 repo = reporef()
1096 def reportsummary(tr):
1090 obsoleted = obsutil.getobsoleted(repo, tr)
1097 """the actual callback reporting the summary"""
1091 if obsoleted:
1098 repo = reporef()
1092 repo.ui.status(_('obsoleted %i changesets\n') % len(obsoleted))
1099 obsoleted = obsutil.getobsoleted(repo, tr)
1093 otr.addpostclose('00-txnreport', reportsummary)
1100 if obsoleted:
1101 repo.ui.status(_('obsoleted %i changesets\n')
1102 % len(obsoleted))
1103 otr.addpostclose('00-txnreport', reportsummary)
1104 break
General Comments 0
You need to be logged in to leave comments. Login now