##// END OF EJS Templates
merge: stable into default
Raphaël Gomès -
r49869:12adf8c6 merge default
parent child Browse files
Show More
@@ -1,1372 +1,1372 b''
1 # dispatch.py - command dispatching for mercurial
1 # dispatch.py - command dispatching for mercurial
2 #
2 #
3 # Copyright 2005-2007 Olivia Mackall <olivia@selenic.com>
3 # Copyright 2005-2007 Olivia Mackall <olivia@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
8
9 import errno
9 import errno
10 import getopt
10 import getopt
11 import io
11 import io
12 import os
12 import os
13 import pdb
13 import pdb
14 import re
14 import re
15 import signal
15 import signal
16 import sys
16 import sys
17 import traceback
17 import traceback
18
18
19
19
20 from .i18n import _
20 from .i18n import _
21 from .pycompat import getattr
21 from .pycompat import getattr
22
22
23 from hgdemandimport import tracing
23 from hgdemandimport import tracing
24
24
25 from . import (
25 from . import (
26 cmdutil,
26 cmdutil,
27 color,
27 color,
28 commands,
28 commands,
29 demandimport,
29 demandimport,
30 encoding,
30 encoding,
31 error,
31 error,
32 extensions,
32 extensions,
33 fancyopts,
33 fancyopts,
34 help,
34 help,
35 hg,
35 hg,
36 hook,
36 hook,
37 localrepo,
37 localrepo,
38 profiling,
38 profiling,
39 pycompat,
39 pycompat,
40 rcutil,
40 rcutil,
41 registrar,
41 registrar,
42 requirements as requirementsmod,
42 requirements as requirementsmod,
43 scmutil,
43 scmutil,
44 ui as uimod,
44 ui as uimod,
45 util,
45 util,
46 vfs,
46 vfs,
47 )
47 )
48
48
49 from .utils import (
49 from .utils import (
50 procutil,
50 procutil,
51 stringutil,
51 stringutil,
52 urlutil,
52 urlutil,
53 )
53 )
54
54
55
55
56 class request:
56 class request:
57 def __init__(
57 def __init__(
58 self,
58 self,
59 args,
59 args,
60 ui=None,
60 ui=None,
61 repo=None,
61 repo=None,
62 fin=None,
62 fin=None,
63 fout=None,
63 fout=None,
64 ferr=None,
64 ferr=None,
65 fmsg=None,
65 fmsg=None,
66 prereposetups=None,
66 prereposetups=None,
67 ):
67 ):
68 self.args = args
68 self.args = args
69 self.ui = ui
69 self.ui = ui
70 self.repo = repo
70 self.repo = repo
71
71
72 # input/output/error streams
72 # input/output/error streams
73 self.fin = fin
73 self.fin = fin
74 self.fout = fout
74 self.fout = fout
75 self.ferr = ferr
75 self.ferr = ferr
76 # separate stream for status/error messages
76 # separate stream for status/error messages
77 self.fmsg = fmsg
77 self.fmsg = fmsg
78
78
79 # remember options pre-parsed by _earlyparseopts()
79 # remember options pre-parsed by _earlyparseopts()
80 self.earlyoptions = {}
80 self.earlyoptions = {}
81
81
82 # reposetups which run before extensions, useful for chg to pre-fill
82 # reposetups which run before extensions, useful for chg to pre-fill
83 # low-level repo state (for example, changelog) before extensions.
83 # low-level repo state (for example, changelog) before extensions.
84 self.prereposetups = prereposetups or []
84 self.prereposetups = prereposetups or []
85
85
86 # store the parsed and canonical command
86 # store the parsed and canonical command
87 self.canonical_command = None
87 self.canonical_command = None
88
88
89 def _runexithandlers(self):
89 def _runexithandlers(self):
90 exc = None
90 exc = None
91 handlers = self.ui._exithandlers
91 handlers = self.ui._exithandlers
92 try:
92 try:
93 while handlers:
93 while handlers:
94 func, args, kwargs = handlers.pop()
94 func, args, kwargs = handlers.pop()
95 try:
95 try:
96 func(*args, **kwargs)
96 func(*args, **kwargs)
97 except: # re-raises below
97 except: # re-raises below
98 if exc is None:
98 if exc is None:
99 exc = sys.exc_info()[1]
99 exc = sys.exc_info()[1]
100 self.ui.warnnoi18n(b'error in exit handlers:\n')
100 self.ui.warnnoi18n(b'error in exit handlers:\n')
101 self.ui.traceback(force=True)
101 self.ui.traceback(force=True)
102 finally:
102 finally:
103 if exc is not None:
103 if exc is not None:
104 raise exc
104 raise exc
105
105
106
106
107 def _flushstdio(ui, err):
107 def _flushstdio(ui, err):
108 status = None
108 status = None
109 # In all cases we try to flush stdio streams.
109 # In all cases we try to flush stdio streams.
110 if util.safehasattr(ui, b'fout'):
110 if util.safehasattr(ui, b'fout'):
111 assert ui is not None # help pytype
111 assert ui is not None # help pytype
112 assert ui.fout is not None # help pytype
112 assert ui.fout is not None # help pytype
113 try:
113 try:
114 ui.fout.flush()
114 ui.fout.flush()
115 except IOError as e:
115 except IOError as e:
116 err = e
116 err = e
117 status = -1
117 status = -1
118
118
119 if util.safehasattr(ui, b'ferr'):
119 if util.safehasattr(ui, b'ferr'):
120 assert ui is not None # help pytype
120 assert ui is not None # help pytype
121 assert ui.ferr is not None # help pytype
121 assert ui.ferr is not None # help pytype
122 try:
122 try:
123 if err is not None and err.errno != errno.EPIPE:
123 if err is not None and err.errno != errno.EPIPE:
124 ui.ferr.write(
124 ui.ferr.write(
125 b'abort: %s\n' % encoding.strtolocal(err.strerror)
125 b'abort: %s\n' % encoding.strtolocal(err.strerror)
126 )
126 )
127 ui.ferr.flush()
127 ui.ferr.flush()
128 # There's not much we can do about an I/O error here. So (possibly)
128 # There's not much we can do about an I/O error here. So (possibly)
129 # change the status code and move on.
129 # change the status code and move on.
130 except IOError:
130 except IOError:
131 status = -1
131 status = -1
132
132
133 return status
133 return status
134
134
135
135
136 def run():
136 def run():
137 """run the command in sys.argv"""
137 """run the command in sys.argv"""
138 try:
138 try:
139 initstdio()
139 initstdio()
140 with tracing.log('parse args into request'):
140 with tracing.log('parse args into request'):
141 req = request(pycompat.sysargv[1:])
141 req = request(pycompat.sysargv[1:])
142
142
143 status = dispatch(req)
143 status = dispatch(req)
144 _silencestdio()
144 _silencestdio()
145 except KeyboardInterrupt:
145 except KeyboardInterrupt:
146 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
146 # Catch early/late KeyboardInterrupt as last ditch. Here nothing will
147 # be printed to console to avoid another IOError/KeyboardInterrupt.
147 # be printed to console to avoid another IOError/KeyboardInterrupt.
148 status = -1
148 status = -1
149 sys.exit(status & 255)
149 sys.exit(status & 255)
150
150
151
151
152 def initstdio():
152 def initstdio():
153 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
153 # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
154 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
154 # buffer. These streams will normalize \n to \r\n by default. Mercurial's
155 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
155 # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
156 # instances, which write to the underlying stdio file descriptor in binary
156 # instances, which write to the underlying stdio file descriptor in binary
157 # mode. ui.write() uses \n for line endings and no line ending normalization
157 # mode. ui.write() uses \n for line endings and no line ending normalization
158 # is attempted through this interface. This "just works," even if the system
158 # is attempted through this interface. This "just works," even if the system
159 # preferred line ending is not \n.
159 # preferred line ending is not \n.
160 #
160 #
161 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
161 # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
162 # and sys.stderr. They will inherit the line ending normalization settings,
162 # and sys.stderr. They will inherit the line ending normalization settings,
163 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
163 # potentially causing e.g. \r\n to be emitted. Since emitting \n should
164 # "just work," here we change the sys.* streams to disable line ending
164 # "just work," here we change the sys.* streams to disable line ending
165 # normalization, ensuring compatibility with our ui type.
165 # normalization, ensuring compatibility with our ui type.
166
166
167 if sys.stdout is not None:
167 if sys.stdout is not None:
168 # write_through is new in Python 3.7.
168 # write_through is new in Python 3.7.
169 kwargs = {
169 kwargs = {
170 "newline": "\n",
170 "newline": "\n",
171 "line_buffering": sys.stdout.line_buffering,
171 "line_buffering": sys.stdout.line_buffering,
172 }
172 }
173 if util.safehasattr(sys.stdout, "write_through"):
173 if util.safehasattr(sys.stdout, "write_through"):
174 # pytype: disable=attribute-error
174 # pytype: disable=attribute-error
175 kwargs["write_through"] = sys.stdout.write_through
175 kwargs["write_through"] = sys.stdout.write_through
176 # pytype: enable=attribute-error
176 # pytype: enable=attribute-error
177 sys.stdout = io.TextIOWrapper(
177 sys.stdout = io.TextIOWrapper(
178 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
178 sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
179 )
179 )
180
180
181 if sys.stderr is not None:
181 if sys.stderr is not None:
182 kwargs = {
182 kwargs = {
183 "newline": "\n",
183 "newline": "\n",
184 "line_buffering": sys.stderr.line_buffering,
184 "line_buffering": sys.stderr.line_buffering,
185 }
185 }
186 if util.safehasattr(sys.stderr, "write_through"):
186 if util.safehasattr(sys.stderr, "write_through"):
187 # pytype: disable=attribute-error
187 # pytype: disable=attribute-error
188 kwargs["write_through"] = sys.stderr.write_through
188 kwargs["write_through"] = sys.stderr.write_through
189 # pytype: enable=attribute-error
189 # pytype: enable=attribute-error
190 sys.stderr = io.TextIOWrapper(
190 sys.stderr = io.TextIOWrapper(
191 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
191 sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
192 )
192 )
193
193
194 if sys.stdin is not None:
194 if sys.stdin is not None:
195 # No write_through on read-only stream.
195 # No write_through on read-only stream.
196 sys.stdin = io.TextIOWrapper(
196 sys.stdin = io.TextIOWrapper(
197 sys.stdin.buffer,
197 sys.stdin.buffer,
198 sys.stdin.encoding,
198 sys.stdin.encoding,
199 sys.stdin.errors,
199 sys.stdin.errors,
200 # None is universal newlines mode.
200 # None is universal newlines mode.
201 newline=None,
201 newline=None,
202 line_buffering=sys.stdin.line_buffering,
202 line_buffering=sys.stdin.line_buffering,
203 )
203 )
204
204
205
205
206 def _silencestdio():
206 def _silencestdio():
207 for fp in (sys.stdout, sys.stderr):
207 for fp in (sys.stdout, sys.stderr):
208 if fp is None:
208 if fp is None:
209 continue
209 continue
210 # Check if the file is okay
210 # Check if the file is okay
211 try:
211 try:
212 fp.flush()
212 fp.flush()
213 continue
213 continue
214 except IOError:
214 except IOError:
215 pass
215 pass
216 # Otherwise mark it as closed to silence "Exception ignored in"
216 # Otherwise mark it as closed to silence "Exception ignored in"
217 # message emitted by the interpreter finalizer.
217 # message emitted by the interpreter finalizer.
218 try:
218 try:
219 fp.close()
219 fp.close()
220 except IOError:
220 except IOError:
221 pass
221 pass
222
222
223
223
224 def _formatargs(args):
224 def _formatargs(args):
225 return b' '.join(procutil.shellquote(a) for a in args)
225 return b' '.join(procutil.shellquote(a) for a in args)
226
226
227
227
228 def dispatch(req):
228 def dispatch(req):
229 """run the command specified in req.args; returns an integer status code"""
229 """run the command specified in req.args; returns an integer status code"""
230 err = None
230 err = None
231 try:
231 try:
232 status = _rundispatch(req)
232 status = _rundispatch(req)
233 except error.StdioError as e:
233 except error.StdioError as e:
234 err = e
234 err = e
235 status = -1
235 status = -1
236
236
237 ret = _flushstdio(req.ui, err)
237 ret = _flushstdio(req.ui, err)
238 if ret and not status:
238 if ret and not status:
239 status = ret
239 status = ret
240 return status
240 return status
241
241
242
242
243 def _rundispatch(req):
243 def _rundispatch(req):
244 with tracing.log('dispatch._rundispatch'):
244 with tracing.log('dispatch._rundispatch'):
245 if req.ferr:
245 if req.ferr:
246 ferr = req.ferr
246 ferr = req.ferr
247 elif req.ui:
247 elif req.ui:
248 ferr = req.ui.ferr
248 ferr = req.ui.ferr
249 else:
249 else:
250 ferr = procutil.stderr
250 ferr = procutil.stderr
251
251
252 try:
252 try:
253 if not req.ui:
253 if not req.ui:
254 req.ui = uimod.ui.load()
254 req.ui = uimod.ui.load()
255 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
255 req.earlyoptions.update(_earlyparseopts(req.ui, req.args))
256 if req.earlyoptions[b'traceback']:
256 if req.earlyoptions[b'traceback']:
257 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
257 req.ui.setconfig(b'ui', b'traceback', b'on', b'--traceback')
258
258
259 # set ui streams from the request
259 # set ui streams from the request
260 if req.fin:
260 if req.fin:
261 req.ui.fin = req.fin
261 req.ui.fin = req.fin
262 if req.fout:
262 if req.fout:
263 req.ui.fout = req.fout
263 req.ui.fout = req.fout
264 if req.ferr:
264 if req.ferr:
265 req.ui.ferr = req.ferr
265 req.ui.ferr = req.ferr
266 if req.fmsg:
266 if req.fmsg:
267 req.ui.fmsg = req.fmsg
267 req.ui.fmsg = req.fmsg
268 except error.Abort as inst:
268 except error.Abort as inst:
269 ferr.write(inst.format())
269 ferr.write(inst.format())
270 return -1
270 return -1
271
271
272 msg = _formatargs(req.args)
272 formattedargs = _formatargs(req.args)
273 starttime = util.timer()
273 starttime = util.timer()
274 ret = 1 # default of Python exit code on unhandled exception
274 ret = 1 # default of Python exit code on unhandled exception
275 try:
275 try:
276 ret = _runcatch(req) or 0
276 ret = _runcatch(req) or 0
277 except error.ProgrammingError as inst:
277 except error.ProgrammingError as inst:
278 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
278 req.ui.error(_(b'** ProgrammingError: %s\n') % inst)
279 if inst.hint:
279 if inst.hint:
280 req.ui.error(_(b'** (%s)\n') % inst.hint)
280 req.ui.error(_(b'** (%s)\n') % inst.hint)
281 raise
281 raise
282 except KeyboardInterrupt as inst:
282 except KeyboardInterrupt as inst:
283 try:
283 try:
284 if isinstance(inst, error.SignalInterrupt):
284 if isinstance(inst, error.SignalInterrupt):
285 msg = _(b"killed!\n")
285 msg = _(b"killed!\n")
286 else:
286 else:
287 msg = _(b"interrupted!\n")
287 msg = _(b"interrupted!\n")
288 req.ui.error(msg)
288 req.ui.error(msg)
289 except error.SignalInterrupt:
289 except error.SignalInterrupt:
290 # maybe pager would quit without consuming all the output, and
290 # maybe pager would quit without consuming all the output, and
291 # SIGPIPE was raised. we cannot print anything in this case.
291 # SIGPIPE was raised. we cannot print anything in this case.
292 pass
292 pass
293 except IOError as inst:
293 except IOError as inst:
294 if inst.errno != errno.EPIPE:
294 if inst.errno != errno.EPIPE:
295 raise
295 raise
296 ret = -1
296 ret = -1
297 finally:
297 finally:
298 duration = util.timer() - starttime
298 duration = util.timer() - starttime
299 req.ui.flush() # record blocked times
299 req.ui.flush() # record blocked times
300 if req.ui.logblockedtimes:
300 if req.ui.logblockedtimes:
301 req.ui._blockedtimes[b'command_duration'] = duration * 1000
301 req.ui._blockedtimes[b'command_duration'] = duration * 1000
302 req.ui.log(
302 req.ui.log(
303 b'uiblocked',
303 b'uiblocked',
304 b'ui blocked ms\n',
304 b'ui blocked ms\n',
305 **pycompat.strkwargs(req.ui._blockedtimes)
305 **pycompat.strkwargs(req.ui._blockedtimes)
306 )
306 )
307 return_code = ret & 255
307 return_code = ret & 255
308 req.ui.log(
308 req.ui.log(
309 b"commandfinish",
309 b"commandfinish",
310 b"%s exited %d after %0.2f seconds\n",
310 b"%s exited %d after %0.2f seconds\n",
311 msg,
311 formattedargs,
312 return_code,
312 return_code,
313 duration,
313 duration,
314 return_code=return_code,
314 return_code=return_code,
315 duration=duration,
315 duration=duration,
316 canonical_command=req.canonical_command,
316 canonical_command=req.canonical_command,
317 )
317 )
318 try:
318 try:
319 req._runexithandlers()
319 req._runexithandlers()
320 except: # exiting, so no re-raises
320 except: # exiting, so no re-raises
321 ret = ret or -1
321 ret = ret or -1
322 # do flush again since ui.log() and exit handlers may write to ui
322 # do flush again since ui.log() and exit handlers may write to ui
323 req.ui.flush()
323 req.ui.flush()
324 return ret
324 return ret
325
325
326
326
327 def _runcatch(req):
327 def _runcatch(req):
328 with tracing.log('dispatch._runcatch'):
328 with tracing.log('dispatch._runcatch'):
329
329
330 def catchterm(*args):
330 def catchterm(*args):
331 raise error.SignalInterrupt
331 raise error.SignalInterrupt
332
332
333 ui = req.ui
333 ui = req.ui
334 try:
334 try:
335 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
335 for name in b'SIGBREAK', b'SIGHUP', b'SIGTERM':
336 num = getattr(signal, name, None)
336 num = getattr(signal, name, None)
337 if num:
337 if num:
338 signal.signal(num, catchterm)
338 signal.signal(num, catchterm)
339 except ValueError:
339 except ValueError:
340 pass # happens if called in a thread
340 pass # happens if called in a thread
341
341
342 def _runcatchfunc():
342 def _runcatchfunc():
343 realcmd = None
343 realcmd = None
344 try:
344 try:
345 cmdargs = fancyopts.fancyopts(
345 cmdargs = fancyopts.fancyopts(
346 req.args[:], commands.globalopts, {}
346 req.args[:], commands.globalopts, {}
347 )
347 )
348 cmd = cmdargs[0]
348 cmd = cmdargs[0]
349 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
349 aliases, entry = cmdutil.findcmd(cmd, commands.table, False)
350 realcmd = aliases[0]
350 realcmd = aliases[0]
351 except (
351 except (
352 error.UnknownCommand,
352 error.UnknownCommand,
353 error.AmbiguousCommand,
353 error.AmbiguousCommand,
354 IndexError,
354 IndexError,
355 getopt.GetoptError,
355 getopt.GetoptError,
356 ):
356 ):
357 # Don't handle this here. We know the command is
357 # Don't handle this here. We know the command is
358 # invalid, but all we're worried about for now is that
358 # invalid, but all we're worried about for now is that
359 # it's not a command that server operators expect to
359 # it's not a command that server operators expect to
360 # be safe to offer to users in a sandbox.
360 # be safe to offer to users in a sandbox.
361 pass
361 pass
362 if realcmd == b'serve' and b'--stdio' in cmdargs:
362 if realcmd == b'serve' and b'--stdio' in cmdargs:
363 # We want to constrain 'hg serve --stdio' instances pretty
363 # We want to constrain 'hg serve --stdio' instances pretty
364 # closely, as many shared-ssh access tools want to grant
364 # closely, as many shared-ssh access tools want to grant
365 # access to run *only* 'hg -R $repo serve --stdio'. We
365 # access to run *only* 'hg -R $repo serve --stdio'. We
366 # restrict to exactly that set of arguments, and prohibit
366 # restrict to exactly that set of arguments, and prohibit
367 # any repo name that starts with '--' to prevent
367 # any repo name that starts with '--' to prevent
368 # shenanigans wherein a user does something like pass
368 # shenanigans wherein a user does something like pass
369 # --debugger or --config=ui.debugger=1 as a repo
369 # --debugger or --config=ui.debugger=1 as a repo
370 # name. This used to actually run the debugger.
370 # name. This used to actually run the debugger.
371 if (
371 if (
372 len(req.args) != 4
372 len(req.args) != 4
373 or req.args[0] != b'-R'
373 or req.args[0] != b'-R'
374 or req.args[1].startswith(b'--')
374 or req.args[1].startswith(b'--')
375 or req.args[2] != b'serve'
375 or req.args[2] != b'serve'
376 or req.args[3] != b'--stdio'
376 or req.args[3] != b'--stdio'
377 ):
377 ):
378 raise error.Abort(
378 raise error.Abort(
379 _(b'potentially unsafe serve --stdio invocation: %s')
379 _(b'potentially unsafe serve --stdio invocation: %s')
380 % (stringutil.pprint(req.args),)
380 % (stringutil.pprint(req.args),)
381 )
381 )
382
382
383 try:
383 try:
384 debugger = b'pdb'
384 debugger = b'pdb'
385 debugtrace = {b'pdb': pdb.set_trace}
385 debugtrace = {b'pdb': pdb.set_trace}
386 debugmortem = {b'pdb': pdb.post_mortem}
386 debugmortem = {b'pdb': pdb.post_mortem}
387
387
388 # read --config before doing anything else
388 # read --config before doing anything else
389 # (e.g. to change trust settings for reading .hg/hgrc)
389 # (e.g. to change trust settings for reading .hg/hgrc)
390 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
390 cfgs = _parseconfig(req.ui, req.earlyoptions[b'config'])
391
391
392 if req.repo:
392 if req.repo:
393 # copy configs that were passed on the cmdline (--config) to
393 # copy configs that were passed on the cmdline (--config) to
394 # the repo ui
394 # the repo ui
395 for sec, name, val in cfgs:
395 for sec, name, val in cfgs:
396 req.repo.ui.setconfig(
396 req.repo.ui.setconfig(
397 sec, name, val, source=b'--config'
397 sec, name, val, source=b'--config'
398 )
398 )
399
399
400 # developer config: ui.debugger
400 # developer config: ui.debugger
401 debugger = ui.config(b"ui", b"debugger")
401 debugger = ui.config(b"ui", b"debugger")
402 debugmod = pdb
402 debugmod = pdb
403 if not debugger or ui.plain():
403 if not debugger or ui.plain():
404 # if we are in HGPLAIN mode, then disable custom debugging
404 # if we are in HGPLAIN mode, then disable custom debugging
405 debugger = b'pdb'
405 debugger = b'pdb'
406 elif req.earlyoptions[b'debugger']:
406 elif req.earlyoptions[b'debugger']:
407 # This import can be slow for fancy debuggers, so only
407 # This import can be slow for fancy debuggers, so only
408 # do it when absolutely necessary, i.e. when actual
408 # do it when absolutely necessary, i.e. when actual
409 # debugging has been requested
409 # debugging has been requested
410 with demandimport.deactivated():
410 with demandimport.deactivated():
411 try:
411 try:
412 debugmod = __import__(debugger)
412 debugmod = __import__(debugger)
413 except ImportError:
413 except ImportError:
414 pass # Leave debugmod = pdb
414 pass # Leave debugmod = pdb
415
415
416 debugtrace[debugger] = debugmod.set_trace
416 debugtrace[debugger] = debugmod.set_trace
417 debugmortem[debugger] = debugmod.post_mortem
417 debugmortem[debugger] = debugmod.post_mortem
418
418
419 # enter the debugger before command execution
419 # enter the debugger before command execution
420 if req.earlyoptions[b'debugger']:
420 if req.earlyoptions[b'debugger']:
421 ui.warn(
421 ui.warn(
422 _(
422 _(
423 b"entering debugger - "
423 b"entering debugger - "
424 b"type c to continue starting hg or h for help\n"
424 b"type c to continue starting hg or h for help\n"
425 )
425 )
426 )
426 )
427
427
428 if (
428 if (
429 debugger != b'pdb'
429 debugger != b'pdb'
430 and debugtrace[debugger] == debugtrace[b'pdb']
430 and debugtrace[debugger] == debugtrace[b'pdb']
431 ):
431 ):
432 ui.warn(
432 ui.warn(
433 _(
433 _(
434 b"%s debugger specified "
434 b"%s debugger specified "
435 b"but its module was not found\n"
435 b"but its module was not found\n"
436 )
436 )
437 % debugger
437 % debugger
438 )
438 )
439 with demandimport.deactivated():
439 with demandimport.deactivated():
440 debugtrace[debugger]()
440 debugtrace[debugger]()
441 try:
441 try:
442 return _dispatch(req)
442 return _dispatch(req)
443 finally:
443 finally:
444 ui.flush()
444 ui.flush()
445 except: # re-raises
445 except: # re-raises
446 # enter the debugger when we hit an exception
446 # enter the debugger when we hit an exception
447 if req.earlyoptions[b'debugger']:
447 if req.earlyoptions[b'debugger']:
448 traceback.print_exc()
448 traceback.print_exc()
449 debugmortem[debugger](sys.exc_info()[2])
449 debugmortem[debugger](sys.exc_info()[2])
450 raise
450 raise
451
451
452 return _callcatch(ui, _runcatchfunc)
452 return _callcatch(ui, _runcatchfunc)
453
453
454
454
455 def _callcatch(ui, func):
455 def _callcatch(ui, func):
456 """like scmutil.callcatch but handles more high-level exceptions about
456 """like scmutil.callcatch but handles more high-level exceptions about
457 config parsing and commands. besides, use handlecommandexception to handle
457 config parsing and commands. besides, use handlecommandexception to handle
458 uncaught exceptions.
458 uncaught exceptions.
459 """
459 """
460 detailed_exit_code = -1
460 detailed_exit_code = -1
461 try:
461 try:
462 return scmutil.callcatch(ui, func)
462 return scmutil.callcatch(ui, func)
463 except error.AmbiguousCommand as inst:
463 except error.AmbiguousCommand as inst:
464 detailed_exit_code = 10
464 detailed_exit_code = 10
465 ui.warn(
465 ui.warn(
466 _(b"hg: command '%s' is ambiguous:\n %s\n")
466 _(b"hg: command '%s' is ambiguous:\n %s\n")
467 % (inst.prefix, b" ".join(inst.matches))
467 % (inst.prefix, b" ".join(inst.matches))
468 )
468 )
469 except error.CommandError as inst:
469 except error.CommandError as inst:
470 detailed_exit_code = 10
470 detailed_exit_code = 10
471 if inst.command:
471 if inst.command:
472 ui.pager(b'help')
472 ui.pager(b'help')
473 msgbytes = pycompat.bytestr(inst.message)
473 msgbytes = pycompat.bytestr(inst.message)
474 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
474 ui.warn(_(b"hg %s: %s\n") % (inst.command, msgbytes))
475 commands.help_(ui, inst.command, full=False, command=True)
475 commands.help_(ui, inst.command, full=False, command=True)
476 else:
476 else:
477 ui.warn(_(b"hg: %s\n") % inst.message)
477 ui.warn(_(b"hg: %s\n") % inst.message)
478 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
478 ui.warn(_(b"(use 'hg help -v' for a list of global options)\n"))
479 except error.UnknownCommand as inst:
479 except error.UnknownCommand as inst:
480 detailed_exit_code = 10
480 detailed_exit_code = 10
481 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
481 nocmdmsg = _(b"hg: unknown command '%s'\n") % inst.command
482 try:
482 try:
483 # check if the command is in a disabled extension
483 # check if the command is in a disabled extension
484 # (but don't check for extensions themselves)
484 # (but don't check for extensions themselves)
485 formatted = help.formattedhelp(
485 formatted = help.formattedhelp(
486 ui, commands, inst.command, unknowncmd=True
486 ui, commands, inst.command, unknowncmd=True
487 )
487 )
488 ui.warn(nocmdmsg)
488 ui.warn(nocmdmsg)
489 ui.write(formatted)
489 ui.write(formatted)
490 except (error.UnknownCommand, error.Abort):
490 except (error.UnknownCommand, error.Abort):
491 suggested = False
491 suggested = False
492 if inst.all_commands:
492 if inst.all_commands:
493 sim = error.getsimilar(inst.all_commands, inst.command)
493 sim = error.getsimilar(inst.all_commands, inst.command)
494 if sim:
494 if sim:
495 ui.warn(nocmdmsg)
495 ui.warn(nocmdmsg)
496 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
496 ui.warn(b"(%s)\n" % error.similarity_hint(sim))
497 suggested = True
497 suggested = True
498 if not suggested:
498 if not suggested:
499 ui.warn(nocmdmsg)
499 ui.warn(nocmdmsg)
500 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
500 ui.warn(_(b"(use 'hg help' for a list of commands)\n"))
501 except IOError:
501 except IOError:
502 raise
502 raise
503 except KeyboardInterrupt:
503 except KeyboardInterrupt:
504 raise
504 raise
505 except: # probably re-raises
505 except: # probably re-raises
506 if not handlecommandexception(ui):
506 if not handlecommandexception(ui):
507 raise
507 raise
508
508
509 if ui.configbool(b'ui', b'detailed-exit-code'):
509 if ui.configbool(b'ui', b'detailed-exit-code'):
510 return detailed_exit_code
510 return detailed_exit_code
511 else:
511 else:
512 return -1
512 return -1
513
513
514
514
515 def aliasargs(fn, givenargs):
515 def aliasargs(fn, givenargs):
516 args = []
516 args = []
517 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
517 # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
518 if not util.safehasattr(fn, b'_origfunc'):
518 if not util.safehasattr(fn, b'_origfunc'):
519 args = getattr(fn, 'args', args)
519 args = getattr(fn, 'args', args)
520 if args:
520 if args:
521 cmd = b' '.join(map(procutil.shellquote, args))
521 cmd = b' '.join(map(procutil.shellquote, args))
522
522
523 nums = []
523 nums = []
524
524
525 def replacer(m):
525 def replacer(m):
526 num = int(m.group(1)) - 1
526 num = int(m.group(1)) - 1
527 nums.append(num)
527 nums.append(num)
528 if num < len(givenargs):
528 if num < len(givenargs):
529 return givenargs[num]
529 return givenargs[num]
530 raise error.InputError(_(b'too few arguments for command alias'))
530 raise error.InputError(_(b'too few arguments for command alias'))
531
531
532 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
532 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
533 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
533 givenargs = [x for i, x in enumerate(givenargs) if i not in nums]
534 args = pycompat.shlexsplit(cmd)
534 args = pycompat.shlexsplit(cmd)
535 return args + givenargs
535 return args + givenargs
536
536
537
537
538 def aliasinterpolate(name, args, cmd):
538 def aliasinterpolate(name, args, cmd):
539 """interpolate args into cmd for shell aliases
539 """interpolate args into cmd for shell aliases
540
540
541 This also handles $0, $@ and "$@".
541 This also handles $0, $@ and "$@".
542 """
542 """
543 # util.interpolate can't deal with "$@" (with quotes) because it's only
543 # util.interpolate can't deal with "$@" (with quotes) because it's only
544 # built to match prefix + patterns.
544 # built to match prefix + patterns.
545 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
545 replacemap = {b'$%d' % (i + 1): arg for i, arg in enumerate(args)}
546 replacemap[b'$0'] = name
546 replacemap[b'$0'] = name
547 replacemap[b'$$'] = b'$'
547 replacemap[b'$$'] = b'$'
548 replacemap[b'$@'] = b' '.join(args)
548 replacemap[b'$@'] = b' '.join(args)
549 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
549 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
550 # parameters, separated out into words. Emulate the same behavior here by
550 # parameters, separated out into words. Emulate the same behavior here by
551 # quoting the arguments individually. POSIX shells will then typically
551 # quoting the arguments individually. POSIX shells will then typically
552 # tokenize each argument into exactly one word.
552 # tokenize each argument into exactly one word.
553 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
553 replacemap[b'"$@"'] = b' '.join(procutil.shellquote(arg) for arg in args)
554 # escape '\$' for regex
554 # escape '\$' for regex
555 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
555 regex = b'|'.join(replacemap.keys()).replace(b'$', br'\$')
556 r = re.compile(regex)
556 r = re.compile(regex)
557 return r.sub(lambda x: replacemap[x.group()], cmd)
557 return r.sub(lambda x: replacemap[x.group()], cmd)
558
558
559
559
560 class cmdalias:
560 class cmdalias:
561 def __init__(self, ui, name, definition, cmdtable, source):
561 def __init__(self, ui, name, definition, cmdtable, source):
562 self.name = self.cmd = name
562 self.name = self.cmd = name
563 self.cmdname = b''
563 self.cmdname = b''
564 self.definition = definition
564 self.definition = definition
565 self.fn = None
565 self.fn = None
566 self.givenargs = []
566 self.givenargs = []
567 self.opts = []
567 self.opts = []
568 self.help = b''
568 self.help = b''
569 self.badalias = None
569 self.badalias = None
570 self.unknowncmd = False
570 self.unknowncmd = False
571 self.source = source
571 self.source = source
572
572
573 try:
573 try:
574 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
574 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
575 for alias, e in cmdtable.items():
575 for alias, e in cmdtable.items():
576 if e is entry:
576 if e is entry:
577 self.cmd = alias
577 self.cmd = alias
578 break
578 break
579 self.shadows = True
579 self.shadows = True
580 except error.UnknownCommand:
580 except error.UnknownCommand:
581 self.shadows = False
581 self.shadows = False
582
582
583 if not self.definition:
583 if not self.definition:
584 self.badalias = _(b"no definition for alias '%s'") % self.name
584 self.badalias = _(b"no definition for alias '%s'") % self.name
585 return
585 return
586
586
587 if self.definition.startswith(b'!'):
587 if self.definition.startswith(b'!'):
588 shdef = self.definition[1:]
588 shdef = self.definition[1:]
589 self.shell = True
589 self.shell = True
590
590
591 def fn(ui, *args):
591 def fn(ui, *args):
592 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
592 env = {b'HG_ARGS': b' '.join((self.name,) + args)}
593
593
594 def _checkvar(m):
594 def _checkvar(m):
595 if m.groups()[0] == b'$':
595 if m.groups()[0] == b'$':
596 return m.group()
596 return m.group()
597 elif int(m.groups()[0]) <= len(args):
597 elif int(m.groups()[0]) <= len(args):
598 return m.group()
598 return m.group()
599 else:
599 else:
600 ui.debug(
600 ui.debug(
601 b"No argument found for substitution "
601 b"No argument found for substitution "
602 b"of %i variable in alias '%s' definition.\n"
602 b"of %i variable in alias '%s' definition.\n"
603 % (int(m.groups()[0]), self.name)
603 % (int(m.groups()[0]), self.name)
604 )
604 )
605 return b''
605 return b''
606
606
607 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
607 cmd = re.sub(br'\$(\d+|\$)', _checkvar, shdef)
608 cmd = aliasinterpolate(self.name, args, cmd)
608 cmd = aliasinterpolate(self.name, args, cmd)
609 return ui.system(
609 return ui.system(
610 cmd, environ=env, blockedtag=b'alias_%s' % self.name
610 cmd, environ=env, blockedtag=b'alias_%s' % self.name
611 )
611 )
612
612
613 self.fn = fn
613 self.fn = fn
614 self.alias = True
614 self.alias = True
615 self._populatehelp(ui, name, shdef, self.fn)
615 self._populatehelp(ui, name, shdef, self.fn)
616 return
616 return
617
617
618 try:
618 try:
619 args = pycompat.shlexsplit(self.definition)
619 args = pycompat.shlexsplit(self.definition)
620 except ValueError as inst:
620 except ValueError as inst:
621 self.badalias = _(b"error in definition for alias '%s': %s") % (
621 self.badalias = _(b"error in definition for alias '%s': %s") % (
622 self.name,
622 self.name,
623 stringutil.forcebytestr(inst),
623 stringutil.forcebytestr(inst),
624 )
624 )
625 return
625 return
626 earlyopts, args = _earlysplitopts(args)
626 earlyopts, args = _earlysplitopts(args)
627 if earlyopts:
627 if earlyopts:
628 self.badalias = _(
628 self.badalias = _(
629 b"error in definition for alias '%s': %s may "
629 b"error in definition for alias '%s': %s may "
630 b"only be given on the command line"
630 b"only be given on the command line"
631 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
631 ) % (self.name, b'/'.join(pycompat.ziplist(*earlyopts)[0]))
632 return
632 return
633 self.cmdname = cmd = args.pop(0)
633 self.cmdname = cmd = args.pop(0)
634 self.givenargs = args
634 self.givenargs = args
635
635
636 try:
636 try:
637 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
637 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
638 if len(tableentry) > 2:
638 if len(tableentry) > 2:
639 self.fn, self.opts, cmdhelp = tableentry
639 self.fn, self.opts, cmdhelp = tableentry
640 else:
640 else:
641 self.fn, self.opts = tableentry
641 self.fn, self.opts = tableentry
642 cmdhelp = None
642 cmdhelp = None
643
643
644 self.alias = True
644 self.alias = True
645 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
645 self._populatehelp(ui, name, cmd, self.fn, cmdhelp)
646
646
647 except error.UnknownCommand:
647 except error.UnknownCommand:
648 self.badalias = _(
648 self.badalias = _(
649 b"alias '%s' resolves to unknown command '%s'"
649 b"alias '%s' resolves to unknown command '%s'"
650 ) % (
650 ) % (
651 self.name,
651 self.name,
652 cmd,
652 cmd,
653 )
653 )
654 self.unknowncmd = True
654 self.unknowncmd = True
655 except error.AmbiguousCommand:
655 except error.AmbiguousCommand:
656 self.badalias = _(
656 self.badalias = _(
657 b"alias '%s' resolves to ambiguous command '%s'"
657 b"alias '%s' resolves to ambiguous command '%s'"
658 ) % (
658 ) % (
659 self.name,
659 self.name,
660 cmd,
660 cmd,
661 )
661 )
662
662
663 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
663 def _populatehelp(self, ui, name, cmd, fn, defaulthelp=None):
664 # confine strings to be passed to i18n.gettext()
664 # confine strings to be passed to i18n.gettext()
665 cfg = {}
665 cfg = {}
666 for k in (b'doc', b'help', b'category'):
666 for k in (b'doc', b'help', b'category'):
667 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
667 v = ui.config(b'alias', b'%s:%s' % (name, k), None)
668 if v is None:
668 if v is None:
669 continue
669 continue
670 if not encoding.isasciistr(v):
670 if not encoding.isasciistr(v):
671 self.badalias = _(
671 self.badalias = _(
672 b"non-ASCII character in alias definition '%s:%s'"
672 b"non-ASCII character in alias definition '%s:%s'"
673 ) % (name, k)
673 ) % (name, k)
674 return
674 return
675 cfg[k] = v
675 cfg[k] = v
676
676
677 self.help = cfg.get(b'help', defaulthelp or b'')
677 self.help = cfg.get(b'help', defaulthelp or b'')
678 if self.help and self.help.startswith(b"hg " + cmd):
678 if self.help and self.help.startswith(b"hg " + cmd):
679 # drop prefix in old-style help lines so hg shows the alias
679 # drop prefix in old-style help lines so hg shows the alias
680 self.help = self.help[4 + len(cmd) :]
680 self.help = self.help[4 + len(cmd) :]
681
681
682 self.owndoc = b'doc' in cfg
682 self.owndoc = b'doc' in cfg
683 doc = cfg.get(b'doc', pycompat.getdoc(fn))
683 doc = cfg.get(b'doc', pycompat.getdoc(fn))
684 if doc is not None:
684 if doc is not None:
685 doc = pycompat.sysstr(doc)
685 doc = pycompat.sysstr(doc)
686 self.__doc__ = doc
686 self.__doc__ = doc
687
687
688 self.helpcategory = cfg.get(
688 self.helpcategory = cfg.get(
689 b'category', registrar.command.CATEGORY_NONE
689 b'category', registrar.command.CATEGORY_NONE
690 )
690 )
691
691
692 @property
692 @property
693 def args(self):
693 def args(self):
694 args = pycompat.maplist(util.expandpath, self.givenargs)
694 args = pycompat.maplist(util.expandpath, self.givenargs)
695 return aliasargs(self.fn, args)
695 return aliasargs(self.fn, args)
696
696
697 def __getattr__(self, name):
697 def __getattr__(self, name):
698 adefaults = {
698 adefaults = {
699 'norepo': True,
699 'norepo': True,
700 'intents': set(),
700 'intents': set(),
701 'optionalrepo': False,
701 'optionalrepo': False,
702 'inferrepo': False,
702 'inferrepo': False,
703 }
703 }
704 if name not in adefaults:
704 if name not in adefaults:
705 raise AttributeError(name)
705 raise AttributeError(name)
706 if self.badalias or util.safehasattr(self, b'shell'):
706 if self.badalias or util.safehasattr(self, b'shell'):
707 return adefaults[name]
707 return adefaults[name]
708 return getattr(self.fn, name)
708 return getattr(self.fn, name)
709
709
710 def __call__(self, ui, *args, **opts):
710 def __call__(self, ui, *args, **opts):
711 if self.badalias:
711 if self.badalias:
712 hint = None
712 hint = None
713 if self.unknowncmd:
713 if self.unknowncmd:
714 try:
714 try:
715 # check if the command is in a disabled extension
715 # check if the command is in a disabled extension
716 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
716 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
717 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
717 hint = _(b"'%s' is provided by '%s' extension") % (cmd, ext)
718 except error.UnknownCommand:
718 except error.UnknownCommand:
719 pass
719 pass
720 raise error.ConfigError(self.badalias, hint=hint)
720 raise error.ConfigError(self.badalias, hint=hint)
721 if self.shadows:
721 if self.shadows:
722 ui.debug(
722 ui.debug(
723 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
723 b"alias '%s' shadows command '%s'\n" % (self.name, self.cmdname)
724 )
724 )
725
725
726 ui.log(
726 ui.log(
727 b'commandalias',
727 b'commandalias',
728 b"alias '%s' expands to '%s'\n",
728 b"alias '%s' expands to '%s'\n",
729 self.name,
729 self.name,
730 self.definition,
730 self.definition,
731 )
731 )
732 if util.safehasattr(self, b'shell'):
732 if util.safehasattr(self, b'shell'):
733 return self.fn(ui, *args, **opts)
733 return self.fn(ui, *args, **opts)
734 else:
734 else:
735 try:
735 try:
736 return util.checksignature(self.fn)(ui, *args, **opts)
736 return util.checksignature(self.fn)(ui, *args, **opts)
737 except error.SignatureError:
737 except error.SignatureError:
738 args = b' '.join([self.cmdname] + self.args)
738 args = b' '.join([self.cmdname] + self.args)
739 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
739 ui.debug(b"alias '%s' expands to '%s'\n" % (self.name, args))
740 raise
740 raise
741
741
742
742
743 class lazyaliasentry:
743 class lazyaliasentry:
744 """like a typical command entry (func, opts, help), but is lazy"""
744 """like a typical command entry (func, opts, help), but is lazy"""
745
745
746 def __init__(self, ui, name, definition, cmdtable, source):
746 def __init__(self, ui, name, definition, cmdtable, source):
747 self.ui = ui
747 self.ui = ui
748 self.name = name
748 self.name = name
749 self.definition = definition
749 self.definition = definition
750 self.cmdtable = cmdtable.copy()
750 self.cmdtable = cmdtable.copy()
751 self.source = source
751 self.source = source
752 self.alias = True
752 self.alias = True
753
753
754 @util.propertycache
754 @util.propertycache
755 def _aliasdef(self):
755 def _aliasdef(self):
756 return cmdalias(
756 return cmdalias(
757 self.ui, self.name, self.definition, self.cmdtable, self.source
757 self.ui, self.name, self.definition, self.cmdtable, self.source
758 )
758 )
759
759
760 def __getitem__(self, n):
760 def __getitem__(self, n):
761 aliasdef = self._aliasdef
761 aliasdef = self._aliasdef
762 if n == 0:
762 if n == 0:
763 return aliasdef
763 return aliasdef
764 elif n == 1:
764 elif n == 1:
765 return aliasdef.opts
765 return aliasdef.opts
766 elif n == 2:
766 elif n == 2:
767 return aliasdef.help
767 return aliasdef.help
768 else:
768 else:
769 raise IndexError
769 raise IndexError
770
770
771 def __iter__(self):
771 def __iter__(self):
772 for i in range(3):
772 for i in range(3):
773 yield self[i]
773 yield self[i]
774
774
775 def __len__(self):
775 def __len__(self):
776 return 3
776 return 3
777
777
778
778
779 def addaliases(ui, cmdtable):
779 def addaliases(ui, cmdtable):
780 # aliases are processed after extensions have been loaded, so they
780 # aliases are processed after extensions have been loaded, so they
781 # may use extension commands. Aliases can also use other alias definitions,
781 # may use extension commands. Aliases can also use other alias definitions,
782 # but only if they have been defined prior to the current definition.
782 # but only if they have been defined prior to the current definition.
783 for alias, definition in ui.configitems(b'alias', ignoresub=True):
783 for alias, definition in ui.configitems(b'alias', ignoresub=True):
784 try:
784 try:
785 if cmdtable[alias].definition == definition:
785 if cmdtable[alias].definition == definition:
786 continue
786 continue
787 except (KeyError, AttributeError):
787 except (KeyError, AttributeError):
788 # definition might not exist or it might not be a cmdalias
788 # definition might not exist or it might not be a cmdalias
789 pass
789 pass
790
790
791 source = ui.configsource(b'alias', alias)
791 source = ui.configsource(b'alias', alias)
792 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
792 entry = lazyaliasentry(ui, alias, definition, cmdtable, source)
793 cmdtable[alias] = entry
793 cmdtable[alias] = entry
794
794
795
795
796 def _parse(ui, args):
796 def _parse(ui, args):
797 options = {}
797 options = {}
798 cmdoptions = {}
798 cmdoptions = {}
799
799
800 try:
800 try:
801 args = fancyopts.fancyopts(args, commands.globalopts, options)
801 args = fancyopts.fancyopts(args, commands.globalopts, options)
802 except getopt.GetoptError as inst:
802 except getopt.GetoptError as inst:
803 raise error.CommandError(None, stringutil.forcebytestr(inst))
803 raise error.CommandError(None, stringutil.forcebytestr(inst))
804
804
805 if args:
805 if args:
806 cmd, args = args[0], args[1:]
806 cmd, args = args[0], args[1:]
807 aliases, entry = cmdutil.findcmd(
807 aliases, entry = cmdutil.findcmd(
808 cmd, commands.table, ui.configbool(b"ui", b"strict")
808 cmd, commands.table, ui.configbool(b"ui", b"strict")
809 )
809 )
810 cmd = aliases[0]
810 cmd = aliases[0]
811 args = aliasargs(entry[0], args)
811 args = aliasargs(entry[0], args)
812 defaults = ui.config(b"defaults", cmd)
812 defaults = ui.config(b"defaults", cmd)
813 if defaults:
813 if defaults:
814 args = (
814 args = (
815 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
815 pycompat.maplist(util.expandpath, pycompat.shlexsplit(defaults))
816 + args
816 + args
817 )
817 )
818 c = list(entry[1])
818 c = list(entry[1])
819 else:
819 else:
820 cmd = None
820 cmd = None
821 c = []
821 c = []
822
822
823 # combine global options into local
823 # combine global options into local
824 for o in commands.globalopts:
824 for o in commands.globalopts:
825 c.append((o[0], o[1], options[o[1]], o[3]))
825 c.append((o[0], o[1], options[o[1]], o[3]))
826
826
827 try:
827 try:
828 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
828 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
829 except getopt.GetoptError as inst:
829 except getopt.GetoptError as inst:
830 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
830 raise error.CommandError(cmd, stringutil.forcebytestr(inst))
831
831
832 # separate global options back out
832 # separate global options back out
833 for o in commands.globalopts:
833 for o in commands.globalopts:
834 n = o[1]
834 n = o[1]
835 options[n] = cmdoptions[n]
835 options[n] = cmdoptions[n]
836 del cmdoptions[n]
836 del cmdoptions[n]
837
837
838 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
838 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
839
839
840
840
841 def _parseconfig(ui, config):
841 def _parseconfig(ui, config):
842 """parse the --config options from the command line"""
842 """parse the --config options from the command line"""
843 configs = []
843 configs = []
844
844
845 for cfg in config:
845 for cfg in config:
846 try:
846 try:
847 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
847 name, value = [cfgelem.strip() for cfgelem in cfg.split(b'=', 1)]
848 section, name = name.split(b'.', 1)
848 section, name = name.split(b'.', 1)
849 if not section or not name:
849 if not section or not name:
850 raise IndexError
850 raise IndexError
851 ui.setconfig(section, name, value, b'--config')
851 ui.setconfig(section, name, value, b'--config')
852 configs.append((section, name, value))
852 configs.append((section, name, value))
853 except (IndexError, ValueError):
853 except (IndexError, ValueError):
854 raise error.InputError(
854 raise error.InputError(
855 _(
855 _(
856 b'malformed --config option: %r '
856 b'malformed --config option: %r '
857 b'(use --config section.name=value)'
857 b'(use --config section.name=value)'
858 )
858 )
859 % pycompat.bytestr(cfg)
859 % pycompat.bytestr(cfg)
860 )
860 )
861
861
862 return configs
862 return configs
863
863
864
864
865 def _earlyparseopts(ui, args):
865 def _earlyparseopts(ui, args):
866 options = {}
866 options = {}
867 fancyopts.fancyopts(
867 fancyopts.fancyopts(
868 args,
868 args,
869 commands.globalopts,
869 commands.globalopts,
870 options,
870 options,
871 gnu=not ui.plain(b'strictflags'),
871 gnu=not ui.plain(b'strictflags'),
872 early=True,
872 early=True,
873 optaliases={b'repository': [b'repo']},
873 optaliases={b'repository': [b'repo']},
874 )
874 )
875 return options
875 return options
876
876
877
877
878 def _earlysplitopts(args):
878 def _earlysplitopts(args):
879 """Split args into a list of possible early options and remainder args"""
879 """Split args into a list of possible early options and remainder args"""
880 shortoptions = b'R:'
880 shortoptions = b'R:'
881 # TODO: perhaps 'debugger' should be included
881 # TODO: perhaps 'debugger' should be included
882 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
882 longoptions = [b'cwd=', b'repository=', b'repo=', b'config=']
883 return fancyopts.earlygetopt(
883 return fancyopts.earlygetopt(
884 args, shortoptions, longoptions, gnu=True, keepsep=True
884 args, shortoptions, longoptions, gnu=True, keepsep=True
885 )
885 )
886
886
887
887
888 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
888 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
889 # run pre-hook, and abort if it fails
889 # run pre-hook, and abort if it fails
890 hook.hook(
890 hook.hook(
891 lui,
891 lui,
892 repo,
892 repo,
893 b"pre-%s" % cmd,
893 b"pre-%s" % cmd,
894 True,
894 True,
895 args=b" ".join(fullargs),
895 args=b" ".join(fullargs),
896 pats=cmdpats,
896 pats=cmdpats,
897 opts=cmdoptions,
897 opts=cmdoptions,
898 )
898 )
899 try:
899 try:
900 ret = _runcommand(ui, options, cmd, d)
900 ret = _runcommand(ui, options, cmd, d)
901 # run post-hook, passing command result
901 # run post-hook, passing command result
902 hook.hook(
902 hook.hook(
903 lui,
903 lui,
904 repo,
904 repo,
905 b"post-%s" % cmd,
905 b"post-%s" % cmd,
906 False,
906 False,
907 args=b" ".join(fullargs),
907 args=b" ".join(fullargs),
908 result=ret,
908 result=ret,
909 pats=cmdpats,
909 pats=cmdpats,
910 opts=cmdoptions,
910 opts=cmdoptions,
911 )
911 )
912 except Exception:
912 except Exception:
913 # run failure hook and re-raise
913 # run failure hook and re-raise
914 hook.hook(
914 hook.hook(
915 lui,
915 lui,
916 repo,
916 repo,
917 b"fail-%s" % cmd,
917 b"fail-%s" % cmd,
918 False,
918 False,
919 args=b" ".join(fullargs),
919 args=b" ".join(fullargs),
920 pats=cmdpats,
920 pats=cmdpats,
921 opts=cmdoptions,
921 opts=cmdoptions,
922 )
922 )
923 raise
923 raise
924 return ret
924 return ret
925
925
926
926
927 def _readsharedsourceconfig(ui, path):
927 def _readsharedsourceconfig(ui, path):
928 """if the current repository is shared one, this tries to read
928 """if the current repository is shared one, this tries to read
929 .hg/hgrc of shared source if we are in share-safe mode
929 .hg/hgrc of shared source if we are in share-safe mode
930
930
931 Config read is loaded into the ui object passed
931 Config read is loaded into the ui object passed
932
932
933 This should be called before reading .hg/hgrc or the main repo
933 This should be called before reading .hg/hgrc or the main repo
934 as that overrides config set in shared source"""
934 as that overrides config set in shared source"""
935 try:
935 try:
936 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
936 with open(os.path.join(path, b".hg", b"requires"), "rb") as fp:
937 requirements = set(fp.read().splitlines())
937 requirements = set(fp.read().splitlines())
938 if not (
938 if not (
939 requirementsmod.SHARESAFE_REQUIREMENT in requirements
939 requirementsmod.SHARESAFE_REQUIREMENT in requirements
940 and requirementsmod.SHARED_REQUIREMENT in requirements
940 and requirementsmod.SHARED_REQUIREMENT in requirements
941 ):
941 ):
942 return
942 return
943 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
943 hgvfs = vfs.vfs(os.path.join(path, b".hg"))
944 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
944 sharedvfs = localrepo._getsharedvfs(hgvfs, requirements)
945 root = sharedvfs.base
945 root = sharedvfs.base
946 ui.readconfig(sharedvfs.join(b"hgrc"), root)
946 ui.readconfig(sharedvfs.join(b"hgrc"), root)
947 except IOError:
947 except IOError:
948 pass
948 pass
949
949
950
950
951 def _getlocal(ui, rpath, wd=None):
951 def _getlocal(ui, rpath, wd=None):
952 """Return (path, local ui object) for the given target path.
952 """Return (path, local ui object) for the given target path.
953
953
954 Takes paths in [cwd]/.hg/hgrc into account."
954 Takes paths in [cwd]/.hg/hgrc into account."
955 """
955 """
956 if wd is None:
956 if wd is None:
957 try:
957 try:
958 wd = encoding.getcwd()
958 wd = encoding.getcwd()
959 except OSError as e:
959 except OSError as e:
960 raise error.Abort(
960 raise error.Abort(
961 _(b"error getting current working directory: %s")
961 _(b"error getting current working directory: %s")
962 % encoding.strtolocal(e.strerror)
962 % encoding.strtolocal(e.strerror)
963 )
963 )
964
964
965 path = cmdutil.findrepo(wd) or b""
965 path = cmdutil.findrepo(wd) or b""
966 if not path:
966 if not path:
967 lui = ui
967 lui = ui
968 else:
968 else:
969 lui = ui.copy()
969 lui = ui.copy()
970 if rcutil.use_repo_hgrc():
970 if rcutil.use_repo_hgrc():
971 _readsharedsourceconfig(lui, path)
971 _readsharedsourceconfig(lui, path)
972 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
972 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
973 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
973 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
974
974
975 if rpath:
975 if rpath:
976 path = urlutil.get_clone_path(lui, rpath)[0]
976 path = urlutil.get_clone_path(lui, rpath)[0]
977 lui = ui.copy()
977 lui = ui.copy()
978 if rcutil.use_repo_hgrc():
978 if rcutil.use_repo_hgrc():
979 _readsharedsourceconfig(lui, path)
979 _readsharedsourceconfig(lui, path)
980 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
980 lui.readconfig(os.path.join(path, b".hg", b"hgrc"), path)
981 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
981 lui.readconfig(os.path.join(path, b".hg", b"hgrc-not-shared"), path)
982
982
983 return path, lui
983 return path, lui
984
984
985
985
986 def _checkshellalias(lui, ui, args):
986 def _checkshellalias(lui, ui, args):
987 """Return the function to run the shell alias, if it is required"""
987 """Return the function to run the shell alias, if it is required"""
988 options = {}
988 options = {}
989
989
990 try:
990 try:
991 args = fancyopts.fancyopts(args, commands.globalopts, options)
991 args = fancyopts.fancyopts(args, commands.globalopts, options)
992 except getopt.GetoptError:
992 except getopt.GetoptError:
993 return
993 return
994
994
995 if not args:
995 if not args:
996 return
996 return
997
997
998 cmdtable = commands.table
998 cmdtable = commands.table
999
999
1000 cmd = args[0]
1000 cmd = args[0]
1001 try:
1001 try:
1002 strict = ui.configbool(b"ui", b"strict")
1002 strict = ui.configbool(b"ui", b"strict")
1003 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1003 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
1004 except (error.AmbiguousCommand, error.UnknownCommand):
1004 except (error.AmbiguousCommand, error.UnknownCommand):
1005 return
1005 return
1006
1006
1007 cmd = aliases[0]
1007 cmd = aliases[0]
1008 fn = entry[0]
1008 fn = entry[0]
1009
1009
1010 if cmd and util.safehasattr(fn, b'shell'):
1010 if cmd and util.safehasattr(fn, b'shell'):
1011 # shell alias shouldn't receive early options which are consumed by hg
1011 # shell alias shouldn't receive early options which are consumed by hg
1012 _earlyopts, args = _earlysplitopts(args)
1012 _earlyopts, args = _earlysplitopts(args)
1013 d = lambda: fn(ui, *args[1:])
1013 d = lambda: fn(ui, *args[1:])
1014 return lambda: runcommand(
1014 return lambda: runcommand(
1015 lui, None, cmd, args[:1], ui, options, d, [], {}
1015 lui, None, cmd, args[:1], ui, options, d, [], {}
1016 )
1016 )
1017
1017
1018
1018
1019 def _dispatch(req):
1019 def _dispatch(req):
1020 args = req.args
1020 args = req.args
1021 ui = req.ui
1021 ui = req.ui
1022
1022
1023 # check for cwd
1023 # check for cwd
1024 cwd = req.earlyoptions[b'cwd']
1024 cwd = req.earlyoptions[b'cwd']
1025 if cwd:
1025 if cwd:
1026 os.chdir(cwd)
1026 os.chdir(cwd)
1027
1027
1028 rpath = req.earlyoptions[b'repository']
1028 rpath = req.earlyoptions[b'repository']
1029 path, lui = _getlocal(ui, rpath)
1029 path, lui = _getlocal(ui, rpath)
1030
1030
1031 uis = {ui, lui}
1031 uis = {ui, lui}
1032
1032
1033 if req.repo:
1033 if req.repo:
1034 uis.add(req.repo.ui)
1034 uis.add(req.repo.ui)
1035
1035
1036 if (
1036 if (
1037 req.earlyoptions[b'verbose']
1037 req.earlyoptions[b'verbose']
1038 or req.earlyoptions[b'debug']
1038 or req.earlyoptions[b'debug']
1039 or req.earlyoptions[b'quiet']
1039 or req.earlyoptions[b'quiet']
1040 ):
1040 ):
1041 for opt in (b'verbose', b'debug', b'quiet'):
1041 for opt in (b'verbose', b'debug', b'quiet'):
1042 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1042 val = pycompat.bytestr(bool(req.earlyoptions[opt]))
1043 for ui_ in uis:
1043 for ui_ in uis:
1044 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1044 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1045
1045
1046 if req.earlyoptions[b'profile']:
1046 if req.earlyoptions[b'profile']:
1047 for ui_ in uis:
1047 for ui_ in uis:
1048 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1048 ui_.setconfig(b'profiling', b'enabled', b'true', b'--profile')
1049 elif req.earlyoptions[b'profile'] is False:
1049 elif req.earlyoptions[b'profile'] is False:
1050 # Check for it being set already, so that we don't pollute the config
1050 # Check for it being set already, so that we don't pollute the config
1051 # with this when using chg in the very common case that it's not
1051 # with this when using chg in the very common case that it's not
1052 # enabled.
1052 # enabled.
1053 if lui.configbool(b'profiling', b'enabled'):
1053 if lui.configbool(b'profiling', b'enabled'):
1054 # Only do this on lui so that `chg foo` with a user config setting
1054 # Only do this on lui so that `chg foo` with a user config setting
1055 # profiling.enabled=1 still shows profiling information (chg will
1055 # profiling.enabled=1 still shows profiling information (chg will
1056 # specify `--no-profile` when `hg serve` is starting up, we don't
1056 # specify `--no-profile` when `hg serve` is starting up, we don't
1057 # want that to propagate to every later invocation).
1057 # want that to propagate to every later invocation).
1058 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1058 lui.setconfig(b'profiling', b'enabled', b'false', b'--no-profile')
1059
1059
1060 profile = lui.configbool(b'profiling', b'enabled')
1060 profile = lui.configbool(b'profiling', b'enabled')
1061 with profiling.profile(lui, enabled=profile) as profiler:
1061 with profiling.profile(lui, enabled=profile) as profiler:
1062 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1062 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
1063 # reposetup
1063 # reposetup
1064 extensions.loadall(lui)
1064 extensions.loadall(lui)
1065 # Propagate any changes to lui.__class__ by extensions
1065 # Propagate any changes to lui.__class__ by extensions
1066 ui.__class__ = lui.__class__
1066 ui.__class__ = lui.__class__
1067
1067
1068 # (uisetup and extsetup are handled in extensions.loadall)
1068 # (uisetup and extsetup are handled in extensions.loadall)
1069
1069
1070 # (reposetup is handled in hg.repository)
1070 # (reposetup is handled in hg.repository)
1071
1071
1072 addaliases(lui, commands.table)
1072 addaliases(lui, commands.table)
1073
1073
1074 # All aliases and commands are completely defined, now.
1074 # All aliases and commands are completely defined, now.
1075 # Check abbreviation/ambiguity of shell alias.
1075 # Check abbreviation/ambiguity of shell alias.
1076 shellaliasfn = _checkshellalias(lui, ui, args)
1076 shellaliasfn = _checkshellalias(lui, ui, args)
1077 if shellaliasfn:
1077 if shellaliasfn:
1078 # no additional configs will be set, set up the ui instances
1078 # no additional configs will be set, set up the ui instances
1079 for ui_ in uis:
1079 for ui_ in uis:
1080 extensions.populateui(ui_)
1080 extensions.populateui(ui_)
1081 return shellaliasfn()
1081 return shellaliasfn()
1082
1082
1083 # check for fallback encoding
1083 # check for fallback encoding
1084 fallback = lui.config(b'ui', b'fallbackencoding')
1084 fallback = lui.config(b'ui', b'fallbackencoding')
1085 if fallback:
1085 if fallback:
1086 encoding.fallbackencoding = fallback
1086 encoding.fallbackencoding = fallback
1087
1087
1088 fullargs = args
1088 fullargs = args
1089 cmd, func, args, options, cmdoptions = _parse(lui, args)
1089 cmd, func, args, options, cmdoptions = _parse(lui, args)
1090
1090
1091 # store the canonical command name in request object for later access
1091 # store the canonical command name in request object for later access
1092 req.canonical_command = cmd
1092 req.canonical_command = cmd
1093
1093
1094 if options[b"config"] != req.earlyoptions[b"config"]:
1094 if options[b"config"] != req.earlyoptions[b"config"]:
1095 raise error.InputError(_(b"option --config may not be abbreviated"))
1095 raise error.InputError(_(b"option --config may not be abbreviated"))
1096 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1096 if options[b"cwd"] != req.earlyoptions[b"cwd"]:
1097 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1097 raise error.InputError(_(b"option --cwd may not be abbreviated"))
1098 if options[b"repository"] != req.earlyoptions[b"repository"]:
1098 if options[b"repository"] != req.earlyoptions[b"repository"]:
1099 raise error.InputError(
1099 raise error.InputError(
1100 _(
1100 _(
1101 b"option -R has to be separated from other options (e.g. not "
1101 b"option -R has to be separated from other options (e.g. not "
1102 b"-qR) and --repository may only be abbreviated as --repo"
1102 b"-qR) and --repository may only be abbreviated as --repo"
1103 )
1103 )
1104 )
1104 )
1105 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1105 if options[b"debugger"] != req.earlyoptions[b"debugger"]:
1106 raise error.InputError(
1106 raise error.InputError(
1107 _(b"option --debugger may not be abbreviated")
1107 _(b"option --debugger may not be abbreviated")
1108 )
1108 )
1109 # don't validate --profile/--traceback, which can be enabled from now
1109 # don't validate --profile/--traceback, which can be enabled from now
1110
1110
1111 if options[b"encoding"]:
1111 if options[b"encoding"]:
1112 encoding.encoding = options[b"encoding"]
1112 encoding.encoding = options[b"encoding"]
1113 if options[b"encodingmode"]:
1113 if options[b"encodingmode"]:
1114 encoding.encodingmode = options[b"encodingmode"]
1114 encoding.encodingmode = options[b"encodingmode"]
1115 if options[b"time"]:
1115 if options[b"time"]:
1116
1116
1117 def get_times():
1117 def get_times():
1118 t = os.times()
1118 t = os.times()
1119 if t[4] == 0.0:
1119 if t[4] == 0.0:
1120 # Windows leaves this as zero, so use time.perf_counter()
1120 # Windows leaves this as zero, so use time.perf_counter()
1121 t = (t[0], t[1], t[2], t[3], util.timer())
1121 t = (t[0], t[1], t[2], t[3], util.timer())
1122 return t
1122 return t
1123
1123
1124 s = get_times()
1124 s = get_times()
1125
1125
1126 def print_time():
1126 def print_time():
1127 t = get_times()
1127 t = get_times()
1128 ui.warn(
1128 ui.warn(
1129 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1129 _(b"time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n")
1130 % (
1130 % (
1131 t[4] - s[4],
1131 t[4] - s[4],
1132 t[0] - s[0],
1132 t[0] - s[0],
1133 t[2] - s[2],
1133 t[2] - s[2],
1134 t[1] - s[1],
1134 t[1] - s[1],
1135 t[3] - s[3],
1135 t[3] - s[3],
1136 )
1136 )
1137 )
1137 )
1138
1138
1139 ui.atexit(print_time)
1139 ui.atexit(print_time)
1140 if options[b"profile"]:
1140 if options[b"profile"]:
1141 profiler.start()
1141 profiler.start()
1142
1142
1143 # if abbreviated version of this were used, take them in account, now
1143 # if abbreviated version of this were used, take them in account, now
1144 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1144 if options[b'verbose'] or options[b'debug'] or options[b'quiet']:
1145 for opt in (b'verbose', b'debug', b'quiet'):
1145 for opt in (b'verbose', b'debug', b'quiet'):
1146 if options[opt] == req.earlyoptions[opt]:
1146 if options[opt] == req.earlyoptions[opt]:
1147 continue
1147 continue
1148 val = pycompat.bytestr(bool(options[opt]))
1148 val = pycompat.bytestr(bool(options[opt]))
1149 for ui_ in uis:
1149 for ui_ in uis:
1150 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1150 ui_.setconfig(b'ui', opt, val, b'--' + opt)
1151
1151
1152 if options[b'traceback']:
1152 if options[b'traceback']:
1153 for ui_ in uis:
1153 for ui_ in uis:
1154 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1154 ui_.setconfig(b'ui', b'traceback', b'on', b'--traceback')
1155
1155
1156 if options[b'noninteractive']:
1156 if options[b'noninteractive']:
1157 for ui_ in uis:
1157 for ui_ in uis:
1158 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1158 ui_.setconfig(b'ui', b'interactive', b'off', b'-y')
1159
1159
1160 if cmdoptions.get(b'insecure', False):
1160 if cmdoptions.get(b'insecure', False):
1161 for ui_ in uis:
1161 for ui_ in uis:
1162 ui_.insecureconnections = True
1162 ui_.insecureconnections = True
1163
1163
1164 # setup color handling before pager, because setting up pager
1164 # setup color handling before pager, because setting up pager
1165 # might cause incorrect console information
1165 # might cause incorrect console information
1166 coloropt = options[b'color']
1166 coloropt = options[b'color']
1167 for ui_ in uis:
1167 for ui_ in uis:
1168 if coloropt:
1168 if coloropt:
1169 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1169 ui_.setconfig(b'ui', b'color', coloropt, b'--color')
1170 color.setup(ui_)
1170 color.setup(ui_)
1171
1171
1172 if stringutil.parsebool(options[b'pager']):
1172 if stringutil.parsebool(options[b'pager']):
1173 # ui.pager() expects 'internal-always-' prefix in this case
1173 # ui.pager() expects 'internal-always-' prefix in this case
1174 ui.pager(b'internal-always-' + cmd)
1174 ui.pager(b'internal-always-' + cmd)
1175 elif options[b'pager'] != b'auto':
1175 elif options[b'pager'] != b'auto':
1176 for ui_ in uis:
1176 for ui_ in uis:
1177 ui_.disablepager()
1177 ui_.disablepager()
1178
1178
1179 # configs are fully loaded, set up the ui instances
1179 # configs are fully loaded, set up the ui instances
1180 for ui_ in uis:
1180 for ui_ in uis:
1181 extensions.populateui(ui_)
1181 extensions.populateui(ui_)
1182
1182
1183 if options[b'version']:
1183 if options[b'version']:
1184 return commands.version_(ui)
1184 return commands.version_(ui)
1185 if options[b'help']:
1185 if options[b'help']:
1186 return commands.help_(ui, cmd, command=cmd is not None)
1186 return commands.help_(ui, cmd, command=cmd is not None)
1187 elif not cmd:
1187 elif not cmd:
1188 return commands.help_(ui, b'shortlist')
1188 return commands.help_(ui, b'shortlist')
1189
1189
1190 repo = None
1190 repo = None
1191 cmdpats = args[:]
1191 cmdpats = args[:]
1192 assert func is not None # help out pytype
1192 assert func is not None # help out pytype
1193 if not func.norepo:
1193 if not func.norepo:
1194 # use the repo from the request only if we don't have -R
1194 # use the repo from the request only if we don't have -R
1195 if not rpath and not cwd:
1195 if not rpath and not cwd:
1196 repo = req.repo
1196 repo = req.repo
1197
1197
1198 if repo:
1198 if repo:
1199 # set the descriptors of the repo ui to those of ui
1199 # set the descriptors of the repo ui to those of ui
1200 repo.ui.fin = ui.fin
1200 repo.ui.fin = ui.fin
1201 repo.ui.fout = ui.fout
1201 repo.ui.fout = ui.fout
1202 repo.ui.ferr = ui.ferr
1202 repo.ui.ferr = ui.ferr
1203 repo.ui.fmsg = ui.fmsg
1203 repo.ui.fmsg = ui.fmsg
1204 else:
1204 else:
1205 try:
1205 try:
1206 repo = hg.repository(
1206 repo = hg.repository(
1207 ui,
1207 ui,
1208 path=path,
1208 path=path,
1209 presetupfuncs=req.prereposetups,
1209 presetupfuncs=req.prereposetups,
1210 intents=func.intents,
1210 intents=func.intents,
1211 )
1211 )
1212 if not repo.local():
1212 if not repo.local():
1213 raise error.InputError(
1213 raise error.InputError(
1214 _(b"repository '%s' is not local") % path
1214 _(b"repository '%s' is not local") % path
1215 )
1215 )
1216 repo.ui.setconfig(
1216 repo.ui.setconfig(
1217 b"bundle", b"mainreporoot", repo.root, b'repo'
1217 b"bundle", b"mainreporoot", repo.root, b'repo'
1218 )
1218 )
1219 except error.RequirementError:
1219 except error.RequirementError:
1220 raise
1220 raise
1221 except error.RepoError:
1221 except error.RepoError:
1222 if rpath: # invalid -R path
1222 if rpath: # invalid -R path
1223 raise
1223 raise
1224 if not func.optionalrepo:
1224 if not func.optionalrepo:
1225 if func.inferrepo and args and not path:
1225 if func.inferrepo and args and not path:
1226 # try to infer -R from command args
1226 # try to infer -R from command args
1227 repos = pycompat.maplist(cmdutil.findrepo, args)
1227 repos = pycompat.maplist(cmdutil.findrepo, args)
1228 guess = repos[0]
1228 guess = repos[0]
1229 if guess and repos.count(guess) == len(repos):
1229 if guess and repos.count(guess) == len(repos):
1230 req.args = [b'--repository', guess] + fullargs
1230 req.args = [b'--repository', guess] + fullargs
1231 req.earlyoptions[b'repository'] = guess
1231 req.earlyoptions[b'repository'] = guess
1232 return _dispatch(req)
1232 return _dispatch(req)
1233 if not path:
1233 if not path:
1234 raise error.InputError(
1234 raise error.InputError(
1235 _(
1235 _(
1236 b"no repository found in"
1236 b"no repository found in"
1237 b" '%s' (.hg not found)"
1237 b" '%s' (.hg not found)"
1238 )
1238 )
1239 % encoding.getcwd()
1239 % encoding.getcwd()
1240 )
1240 )
1241 raise
1241 raise
1242 if repo:
1242 if repo:
1243 ui = repo.ui
1243 ui = repo.ui
1244 if options[b'hidden']:
1244 if options[b'hidden']:
1245 repo = repo.unfiltered()
1245 repo = repo.unfiltered()
1246 args.insert(0, repo)
1246 args.insert(0, repo)
1247 elif rpath:
1247 elif rpath:
1248 ui.warn(_(b"warning: --repository ignored\n"))
1248 ui.warn(_(b"warning: --repository ignored\n"))
1249
1249
1250 msg = _formatargs(fullargs)
1250 msg = _formatargs(fullargs)
1251 ui.log(b"command", b'%s\n', msg)
1251 ui.log(b"command", b'%s\n', msg)
1252 strcmdopt = pycompat.strkwargs(cmdoptions)
1252 strcmdopt = pycompat.strkwargs(cmdoptions)
1253 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1253 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
1254 try:
1254 try:
1255 return runcommand(
1255 return runcommand(
1256 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1256 lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions
1257 )
1257 )
1258 finally:
1258 finally:
1259 if repo and repo != req.repo:
1259 if repo and repo != req.repo:
1260 repo.close()
1260 repo.close()
1261
1261
1262
1262
1263 def _runcommand(ui, options, cmd, cmdfunc):
1263 def _runcommand(ui, options, cmd, cmdfunc):
1264 """Run a command function, possibly with profiling enabled."""
1264 """Run a command function, possibly with profiling enabled."""
1265 try:
1265 try:
1266 with tracing.log("Running %s command" % cmd):
1266 with tracing.log("Running %s command" % cmd):
1267 return cmdfunc()
1267 return cmdfunc()
1268 except error.SignatureError:
1268 except error.SignatureError:
1269 raise error.CommandError(cmd, _(b'invalid arguments'))
1269 raise error.CommandError(cmd, _(b'invalid arguments'))
1270
1270
1271
1271
1272 def _exceptionwarning(ui):
1272 def _exceptionwarning(ui):
1273 """Produce a warning message for the current active exception"""
1273 """Produce a warning message for the current active exception"""
1274
1274
1275 # For compatibility checking, we discard the portion of the hg
1275 # For compatibility checking, we discard the portion of the hg
1276 # version after the + on the assumption that if a "normal
1276 # version after the + on the assumption that if a "normal
1277 # user" is running a build with a + in it the packager
1277 # user" is running a build with a + in it the packager
1278 # probably built from fairly close to a tag and anyone with a
1278 # probably built from fairly close to a tag and anyone with a
1279 # 'make local' copy of hg (where the version number can be out
1279 # 'make local' copy of hg (where the version number can be out
1280 # of date) will be clueful enough to notice the implausible
1280 # of date) will be clueful enough to notice the implausible
1281 # version number and try updating.
1281 # version number and try updating.
1282 ct = util.versiontuple(n=2)
1282 ct = util.versiontuple(n=2)
1283 worst = None, ct, b'', b''
1283 worst = None, ct, b'', b''
1284 if ui.config(b'ui', b'supportcontact') is None:
1284 if ui.config(b'ui', b'supportcontact') is None:
1285 for name, mod in extensions.extensions():
1285 for name, mod in extensions.extensions():
1286 # 'testedwith' should be bytes, but not all extensions are ported
1286 # 'testedwith' should be bytes, but not all extensions are ported
1287 # to py3 and we don't want UnicodeException because of that.
1287 # to py3 and we don't want UnicodeException because of that.
1288 testedwith = stringutil.forcebytestr(
1288 testedwith = stringutil.forcebytestr(
1289 getattr(mod, 'testedwith', b'')
1289 getattr(mod, 'testedwith', b'')
1290 )
1290 )
1291 version = extensions.moduleversion(mod)
1291 version = extensions.moduleversion(mod)
1292 report = getattr(mod, 'buglink', _(b'the extension author.'))
1292 report = getattr(mod, 'buglink', _(b'the extension author.'))
1293 if not testedwith.strip():
1293 if not testedwith.strip():
1294 # We found an untested extension. It's likely the culprit.
1294 # We found an untested extension. It's likely the culprit.
1295 worst = name, b'unknown', report, version
1295 worst = name, b'unknown', report, version
1296 break
1296 break
1297
1297
1298 # Never blame on extensions bundled with Mercurial.
1298 # Never blame on extensions bundled with Mercurial.
1299 if extensions.ismoduleinternal(mod):
1299 if extensions.ismoduleinternal(mod):
1300 continue
1300 continue
1301
1301
1302 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1302 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
1303 if ct in tested:
1303 if ct in tested:
1304 continue
1304 continue
1305
1305
1306 lower = [t for t in tested if t < ct]
1306 lower = [t for t in tested if t < ct]
1307 nearest = max(lower or tested)
1307 nearest = max(lower or tested)
1308 if worst[0] is None or nearest < worst[1]:
1308 if worst[0] is None or nearest < worst[1]:
1309 worst = name, nearest, report, version
1309 worst = name, nearest, report, version
1310 if worst[0] is not None:
1310 if worst[0] is not None:
1311 name, testedwith, report, version = worst
1311 name, testedwith, report, version = worst
1312 if not isinstance(testedwith, (bytes, str)):
1312 if not isinstance(testedwith, (bytes, str)):
1313 testedwith = b'.'.join(
1313 testedwith = b'.'.join(
1314 [stringutil.forcebytestr(c) for c in testedwith]
1314 [stringutil.forcebytestr(c) for c in testedwith]
1315 )
1315 )
1316 extver = version or _(b"(version N/A)")
1316 extver = version or _(b"(version N/A)")
1317 warning = _(
1317 warning = _(
1318 b'** Unknown exception encountered with '
1318 b'** Unknown exception encountered with '
1319 b'possibly-broken third-party extension "%s" %s\n'
1319 b'possibly-broken third-party extension "%s" %s\n'
1320 b'** which supports versions %s of Mercurial.\n'
1320 b'** which supports versions %s of Mercurial.\n'
1321 b'** Please disable "%s" and try your action again.\n'
1321 b'** Please disable "%s" and try your action again.\n'
1322 b'** If that fixes the bug please report it to %s\n'
1322 b'** If that fixes the bug please report it to %s\n'
1323 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1323 ) % (name, extver, testedwith, name, stringutil.forcebytestr(report))
1324 else:
1324 else:
1325 bugtracker = ui.config(b'ui', b'supportcontact')
1325 bugtracker = ui.config(b'ui', b'supportcontact')
1326 if bugtracker is None:
1326 if bugtracker is None:
1327 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1327 bugtracker = _(b"https://mercurial-scm.org/wiki/BugTracker")
1328 warning = (
1328 warning = (
1329 _(
1329 _(
1330 b"** unknown exception encountered, "
1330 b"** unknown exception encountered, "
1331 b"please report by visiting\n** "
1331 b"please report by visiting\n** "
1332 )
1332 )
1333 + bugtracker
1333 + bugtracker
1334 + b'\n'
1334 + b'\n'
1335 )
1335 )
1336 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1336 sysversion = pycompat.sysbytes(sys.version).replace(b'\n', b'')
1337
1337
1338 def ext_with_ver(x):
1338 def ext_with_ver(x):
1339 ext = x[0]
1339 ext = x[0]
1340 ver = extensions.moduleversion(x[1])
1340 ver = extensions.moduleversion(x[1])
1341 if ver:
1341 if ver:
1342 ext += b' ' + ver
1342 ext += b' ' + ver
1343 return ext
1343 return ext
1344
1344
1345 warning += (
1345 warning += (
1346 (_(b"** Python %s\n") % sysversion)
1346 (_(b"** Python %s\n") % sysversion)
1347 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1347 + (_(b"** Mercurial Distributed SCM (version %s)\n") % util.version())
1348 + (
1348 + (
1349 _(b"** Extensions loaded: %s\n")
1349 _(b"** Extensions loaded: %s\n")
1350 % b", ".join(
1350 % b", ".join(
1351 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1351 [ext_with_ver(x) for x in sorted(extensions.extensions())]
1352 )
1352 )
1353 )
1353 )
1354 )
1354 )
1355 return warning
1355 return warning
1356
1356
1357
1357
1358 def handlecommandexception(ui):
1358 def handlecommandexception(ui):
1359 """Produce a warning message for broken commands
1359 """Produce a warning message for broken commands
1360
1360
1361 Called when handling an exception; the exception is reraised if
1361 Called when handling an exception; the exception is reraised if
1362 this function returns False, ignored otherwise.
1362 this function returns False, ignored otherwise.
1363 """
1363 """
1364 warning = _exceptionwarning(ui)
1364 warning = _exceptionwarning(ui)
1365 ui.log(
1365 ui.log(
1366 b"commandexception",
1366 b"commandexception",
1367 b"%s\n%s\n",
1367 b"%s\n%s\n",
1368 warning,
1368 warning,
1369 pycompat.sysbytes(traceback.format_exc()),
1369 pycompat.sysbytes(traceback.format_exc()),
1370 )
1370 )
1371 ui.warn(warning)
1371 ui.warn(warning)
1372 return False # re-raise the exception
1372 return False # re-raise the exception
@@ -1,1204 +1,1313 b''
1 # This file is automatically @generated by Cargo.
1 # This file is automatically @generated by Cargo.
2 # It is not intended for manual editing.
2 # It is not intended for manual editing.
3 version = 3
3 version = 3
4
4
5 [[package]]
5 [[package]]
6 name = "Inflector"
7 version = "0.11.4"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
10
11 [[package]]
6 name = "adler"
12 name = "adler"
7 version = "0.2.3"
13 version = "0.2.3"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
15 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
10
16
11 [[package]]
17 [[package]]
12 name = "ahash"
18 name = "ahash"
13 version = "0.4.7"
19 version = "0.4.7"
14 source = "registry+https://github.com/rust-lang/crates.io-index"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
15 checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
21 checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
16
22
17 [[package]]
23 [[package]]
18 name = "aho-corasick"
24 name = "aho-corasick"
19 version = "0.7.18"
25 version = "0.7.18"
20 source = "registry+https://github.com/rust-lang/crates.io-index"
26 source = "registry+https://github.com/rust-lang/crates.io-index"
21 checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
27 checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
22 dependencies = [
28 dependencies = [
23 "memchr",
29 "memchr",
24 ]
30 ]
25
31
26 [[package]]
32 [[package]]
33 name = "aliasable"
34 version = "0.1.3"
35 source = "registry+https://github.com/rust-lang/crates.io-index"
36 checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd"
37
38 [[package]]
27 name = "ansi_term"
39 name = "ansi_term"
28 version = "0.12.1"
40 version = "0.12.1"
29 source = "registry+https://github.com/rust-lang/crates.io-index"
41 source = "registry+https://github.com/rust-lang/crates.io-index"
30 checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
42 checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
31 dependencies = [
43 dependencies = [
32 "winapi",
44 "winapi",
33 ]
45 ]
34
46
35 [[package]]
47 [[package]]
36 name = "atty"
48 name = "atty"
37 version = "0.2.14"
49 version = "0.2.14"
38 source = "registry+https://github.com/rust-lang/crates.io-index"
50 source = "registry+https://github.com/rust-lang/crates.io-index"
39 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
51 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
40 dependencies = [
52 dependencies = [
41 "hermit-abi",
53 "hermit-abi",
42 "libc",
54 "libc",
43 "winapi",
55 "winapi",
44 ]
56 ]
45
57
46 [[package]]
58 [[package]]
47 name = "autocfg"
59 name = "autocfg"
48 version = "1.0.1"
60 version = "1.0.1"
49 source = "registry+https://github.com/rust-lang/crates.io-index"
61 source = "registry+https://github.com/rust-lang/crates.io-index"
50 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
62 checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
51
63
52 [[package]]
64 [[package]]
53 name = "bitflags"
65 name = "bitflags"
54 version = "1.3.2"
66 version = "1.3.2"
55 source = "registry+https://github.com/rust-lang/crates.io-index"
67 source = "registry+https://github.com/rust-lang/crates.io-index"
56 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
68 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
57
69
58 [[package]]
70 [[package]]
59 name = "bitmaps"
71 name = "bitmaps"
60 version = "2.1.0"
72 version = "2.1.0"
61 source = "registry+https://github.com/rust-lang/crates.io-index"
73 source = "registry+https://github.com/rust-lang/crates.io-index"
62 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
74 checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2"
63 dependencies = [
75 dependencies = [
64 "typenum",
76 "typenum",
65 ]
77 ]
66
78
67 [[package]]
79 [[package]]
68 name = "block-buffer"
80 name = "block-buffer"
69 version = "0.9.0"
81 version = "0.9.0"
70 source = "registry+https://github.com/rust-lang/crates.io-index"
82 source = "registry+https://github.com/rust-lang/crates.io-index"
71 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
83 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
72 dependencies = [
84 dependencies = [
73 "generic-array",
85 "generic-array",
74 ]
86 ]
75
87
76 [[package]]
88 [[package]]
77 name = "block-buffer"
89 name = "block-buffer"
78 version = "0.10.2"
90 version = "0.10.2"
79 source = "registry+https://github.com/rust-lang/crates.io-index"
91 source = "registry+https://github.com/rust-lang/crates.io-index"
80 checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
92 checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
81 dependencies = [
93 dependencies = [
82 "generic-array",
94 "generic-array",
83 ]
95 ]
84
96
85 [[package]]
97 [[package]]
86 name = "byteorder"
98 name = "byteorder"
87 version = "1.4.3"
99 version = "1.4.3"
88 source = "registry+https://github.com/rust-lang/crates.io-index"
100 source = "registry+https://github.com/rust-lang/crates.io-index"
89 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
101 checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
90
102
91 [[package]]
103 [[package]]
92 name = "bytes-cast"
104 name = "bytes-cast"
93 version = "0.2.0"
105 version = "0.2.0"
94 source = "registry+https://github.com/rust-lang/crates.io-index"
106 source = "registry+https://github.com/rust-lang/crates.io-index"
95 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
107 checksum = "0d434f9a4ecbe987e7ccfda7274b6f82ea52c9b63742565a65cb5e8ba0f2c452"
96 dependencies = [
108 dependencies = [
97 "bytes-cast-derive",
109 "bytes-cast-derive",
98 ]
110 ]
99
111
100 [[package]]
112 [[package]]
101 name = "bytes-cast-derive"
113 name = "bytes-cast-derive"
102 version = "0.1.0"
114 version = "0.1.0"
103 source = "registry+https://github.com/rust-lang/crates.io-index"
115 source = "registry+https://github.com/rust-lang/crates.io-index"
104 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
116 checksum = "cb936af9de38476664d6b58e529aff30d482e4ce1c5e150293d00730b0d81fdb"
105 dependencies = [
117 dependencies = [
106 "proc-macro2",
118 "proc-macro2",
107 "quote",
119 "quote",
108 "syn",
120 "syn",
109 ]
121 ]
110
122
111 [[package]]
123 [[package]]
112 name = "cc"
124 name = "cc"
113 version = "1.0.66"
125 version = "1.0.66"
114 source = "registry+https://github.com/rust-lang/crates.io-index"
126 source = "registry+https://github.com/rust-lang/crates.io-index"
115 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
127 checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
116 dependencies = [
128 dependencies = [
117 "jobserver",
129 "jobserver",
118 ]
130 ]
119
131
120 [[package]]
132 [[package]]
121 name = "cfg-if"
133 name = "cfg-if"
122 version = "0.1.10"
134 version = "0.1.10"
123 source = "registry+https://github.com/rust-lang/crates.io-index"
135 source = "registry+https://github.com/rust-lang/crates.io-index"
124 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
136 checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
125
137
126 [[package]]
138 [[package]]
127 name = "cfg-if"
139 name = "cfg-if"
128 version = "1.0.0"
140 version = "1.0.0"
129 source = "registry+https://github.com/rust-lang/crates.io-index"
141 source = "registry+https://github.com/rust-lang/crates.io-index"
130 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
142 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
131
143
132 [[package]]
144 [[package]]
133 name = "chrono"
145 name = "chrono"
134 version = "0.4.19"
146 version = "0.4.19"
135 source = "registry+https://github.com/rust-lang/crates.io-index"
147 source = "registry+https://github.com/rust-lang/crates.io-index"
136 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
148 checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
137 dependencies = [
149 dependencies = [
138 "libc",
150 "libc",
139 "num-integer",
151 "num-integer",
140 "num-traits",
152 "num-traits",
141 "time",
153 "time",
142 "winapi",
154 "winapi",
143 ]
155 ]
144
156
145 [[package]]
157 [[package]]
146 name = "clap"
158 name = "clap"
147 version = "2.34.0"
159 version = "2.34.0"
148 source = "registry+https://github.com/rust-lang/crates.io-index"
160 source = "registry+https://github.com/rust-lang/crates.io-index"
149 checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
161 checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
150 dependencies = [
162 dependencies = [
151 "ansi_term",
163 "ansi_term",
152 "atty",
164 "atty",
153 "bitflags",
165 "bitflags",
154 "strsim",
166 "strsim",
155 "textwrap",
167 "textwrap",
156 "unicode-width",
168 "unicode-width",
157 "vec_map",
169 "vec_map",
158 ]
170 ]
159
171
160 [[package]]
172 [[package]]
161 name = "const_fn"
173 name = "const_fn"
162 version = "0.4.4"
174 version = "0.4.4"
163 source = "registry+https://github.com/rust-lang/crates.io-index"
175 source = "registry+https://github.com/rust-lang/crates.io-index"
164 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
176 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
165
177
166 [[package]]
178 [[package]]
167 name = "convert_case"
179 name = "convert_case"
168 version = "0.4.0"
180 version = "0.4.0"
169 source = "registry+https://github.com/rust-lang/crates.io-index"
181 source = "registry+https://github.com/rust-lang/crates.io-index"
170 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
182 checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
171
183
172 [[package]]
184 [[package]]
173 name = "cpufeatures"
185 name = "cpufeatures"
174 version = "0.1.4"
186 version = "0.1.4"
175 source = "registry+https://github.com/rust-lang/crates.io-index"
187 source = "registry+https://github.com/rust-lang/crates.io-index"
176 checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
188 checksum = "ed00c67cb5d0a7d64a44f6ad2668db7e7530311dd53ea79bcd4fb022c64911c8"
177 dependencies = [
189 dependencies = [
178 "libc",
190 "libc",
179 ]
191 ]
180
192
181 [[package]]
193 [[package]]
182 name = "cpufeatures"
194 name = "cpufeatures"
183 version = "0.2.1"
195 version = "0.2.1"
184 source = "registry+https://github.com/rust-lang/crates.io-index"
196 source = "registry+https://github.com/rust-lang/crates.io-index"
185 checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
197 checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
186 dependencies = [
198 dependencies = [
187 "libc",
199 "libc",
188 ]
200 ]
189
201
190 [[package]]
202 [[package]]
191 name = "cpython"
203 name = "cpython"
192 version = "0.7.0"
204 version = "0.7.0"
193 source = "registry+https://github.com/rust-lang/crates.io-index"
205 source = "registry+https://github.com/rust-lang/crates.io-index"
194 checksum = "b7d46ba8ace7f3a1d204ac5060a706d0a68de6b42eafb6a586cc08bebcffe664"
206 checksum = "b7d46ba8ace7f3a1d204ac5060a706d0a68de6b42eafb6a586cc08bebcffe664"
195 dependencies = [
207 dependencies = [
196 "libc",
208 "libc",
197 "num-traits",
209 "num-traits",
198 "paste",
210 "paste",
199 "python3-sys",
211 "python3-sys",
200 ]
212 ]
201
213
202 [[package]]
214 [[package]]
203 name = "crc32fast"
215 name = "crc32fast"
204 version = "1.2.1"
216 version = "1.2.1"
205 source = "registry+https://github.com/rust-lang/crates.io-index"
217 source = "registry+https://github.com/rust-lang/crates.io-index"
206 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
218 checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
207 dependencies = [
219 dependencies = [
208 "cfg-if 1.0.0",
220 "cfg-if 1.0.0",
209 ]
221 ]
210
222
211 [[package]]
223 [[package]]
212 name = "crossbeam-channel"
224 name = "crossbeam-channel"
225 version = "0.4.4"
226 source = "registry+https://github.com/rust-lang/crates.io-index"
227 checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87"
228 dependencies = [
229 "crossbeam-utils 0.7.2",
230 "maybe-uninit",
231 ]
232
233 [[package]]
234 name = "crossbeam-channel"
213 version = "0.5.2"
235 version = "0.5.2"
214 source = "registry+https://github.com/rust-lang/crates.io-index"
236 source = "registry+https://github.com/rust-lang/crates.io-index"
215 checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
237 checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
216 dependencies = [
238 dependencies = [
217 "cfg-if 1.0.0",
239 "cfg-if 1.0.0",
218 "crossbeam-utils",
240 "crossbeam-utils 0.8.1",
219 ]
241 ]
220
242
221 [[package]]
243 [[package]]
222 name = "crossbeam-deque"
244 name = "crossbeam-deque"
223 version = "0.8.0"
245 version = "0.8.0"
224 source = "registry+https://github.com/rust-lang/crates.io-index"
246 source = "registry+https://github.com/rust-lang/crates.io-index"
225 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
247 checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
226 dependencies = [
248 dependencies = [
227 "cfg-if 1.0.0",
249 "cfg-if 1.0.0",
228 "crossbeam-epoch",
250 "crossbeam-epoch",
229 "crossbeam-utils",
251 "crossbeam-utils 0.8.1",
230 ]
252 ]
231
253
232 [[package]]
254 [[package]]
233 name = "crossbeam-epoch"
255 name = "crossbeam-epoch"
234 version = "0.9.1"
256 version = "0.9.1"
235 source = "registry+https://github.com/rust-lang/crates.io-index"
257 source = "registry+https://github.com/rust-lang/crates.io-index"
236 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
258 checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
237 dependencies = [
259 dependencies = [
238 "cfg-if 1.0.0",
260 "cfg-if 1.0.0",
239 "const_fn",
261 "const_fn",
240 "crossbeam-utils",
262 "crossbeam-utils 0.8.1",
241 "lazy_static",
263 "lazy_static",
242 "memoffset",
264 "memoffset",
243 "scopeguard",
265 "scopeguard",
244 ]
266 ]
245
267
246 [[package]]
268 [[package]]
247 name = "crossbeam-utils"
269 name = "crossbeam-utils"
270 version = "0.7.2"
271 source = "registry+https://github.com/rust-lang/crates.io-index"
272 checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
273 dependencies = [
274 "autocfg",
275 "cfg-if 0.1.10",
276 "lazy_static",
277 ]
278
279 [[package]]
280 name = "crossbeam-utils"
248 version = "0.8.1"
281 version = "0.8.1"
249 source = "registry+https://github.com/rust-lang/crates.io-index"
282 source = "registry+https://github.com/rust-lang/crates.io-index"
250 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
283 checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
251 dependencies = [
284 dependencies = [
252 "autocfg",
285 "autocfg",
253 "cfg-if 1.0.0",
286 "cfg-if 1.0.0",
254 "lazy_static",
287 "lazy_static",
255 ]
288 ]
256
289
257 [[package]]
290 [[package]]
258 name = "crypto-common"
291 name = "crypto-common"
259 version = "0.1.2"
292 version = "0.1.2"
260 source = "registry+https://github.com/rust-lang/crates.io-index"
293 source = "registry+https://github.com/rust-lang/crates.io-index"
261 checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
294 checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
262 dependencies = [
295 dependencies = [
263 "generic-array",
296 "generic-array",
264 ]
297 ]
265
298
266 [[package]]
299 [[package]]
267 name = "ctor"
300 name = "ctor"
268 version = "0.1.16"
301 version = "0.1.16"
269 source = "registry+https://github.com/rust-lang/crates.io-index"
302 source = "registry+https://github.com/rust-lang/crates.io-index"
270 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
303 checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484"
271 dependencies = [
304 dependencies = [
272 "quote",
305 "quote",
273 "syn",
306 "syn",
274 ]
307 ]
275
308
276 [[package]]
309 [[package]]
277 name = "derive_more"
310 name = "derive_more"
278 version = "0.99.17"
311 version = "0.99.17"
279 source = "registry+https://github.com/rust-lang/crates.io-index"
312 source = "registry+https://github.com/rust-lang/crates.io-index"
280 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
313 checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
281 dependencies = [
314 dependencies = [
282 "convert_case",
315 "convert_case",
283 "proc-macro2",
316 "proc-macro2",
284 "quote",
317 "quote",
285 "rustc_version",
318 "rustc_version",
286 "syn",
319 "syn",
287 ]
320 ]
288
321
289 [[package]]
322 [[package]]
290 name = "diff"
323 name = "diff"
291 version = "0.1.12"
324 version = "0.1.12"
292 source = "registry+https://github.com/rust-lang/crates.io-index"
325 source = "registry+https://github.com/rust-lang/crates.io-index"
293 checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
326 checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
294
327
295 [[package]]
328 [[package]]
296 name = "digest"
329 name = "digest"
297 version = "0.9.0"
330 version = "0.9.0"
298 source = "registry+https://github.com/rust-lang/crates.io-index"
331 source = "registry+https://github.com/rust-lang/crates.io-index"
299 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
332 checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
300 dependencies = [
333 dependencies = [
301 "generic-array",
334 "generic-array",
302 ]
335 ]
303
336
304 [[package]]
337 [[package]]
305 name = "digest"
338 name = "digest"
306 version = "0.10.2"
339 version = "0.10.2"
307 source = "registry+https://github.com/rust-lang/crates.io-index"
340 source = "registry+https://github.com/rust-lang/crates.io-index"
308 checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
341 checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
309 dependencies = [
342 dependencies = [
310 "block-buffer 0.10.2",
343 "block-buffer 0.10.2",
311 "crypto-common",
344 "crypto-common",
312 ]
345 ]
313
346
314 [[package]]
347 [[package]]
315 name = "either"
348 name = "either"
316 version = "1.6.1"
349 version = "1.6.1"
317 source = "registry+https://github.com/rust-lang/crates.io-index"
350 source = "registry+https://github.com/rust-lang/crates.io-index"
318 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
351 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
319
352
320 [[package]]
353 [[package]]
321 name = "env_logger"
354 name = "env_logger"
322 version = "0.9.0"
355 version = "0.9.0"
323 source = "registry+https://github.com/rust-lang/crates.io-index"
356 source = "registry+https://github.com/rust-lang/crates.io-index"
324 checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
357 checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
325 dependencies = [
358 dependencies = [
326 "atty",
359 "atty",
327 "humantime",
360 "humantime",
328 "log",
361 "log",
329 "regex",
362 "regex",
330 "termcolor",
363 "termcolor",
331 ]
364 ]
332
365
333 [[package]]
366 [[package]]
334 name = "fastrand"
367 name = "fastrand"
335 version = "1.7.0"
368 version = "1.7.0"
336 source = "registry+https://github.com/rust-lang/crates.io-index"
369 source = "registry+https://github.com/rust-lang/crates.io-index"
337 checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
370 checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
338 dependencies = [
371 dependencies = [
339 "instant",
372 "instant",
340 ]
373 ]
341
374
342 [[package]]
375 [[package]]
343 name = "flate2"
376 name = "flate2"
344 version = "1.0.22"
377 version = "1.0.22"
345 source = "registry+https://github.com/rust-lang/crates.io-index"
378 source = "registry+https://github.com/rust-lang/crates.io-index"
346 checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
379 checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
347 dependencies = [
380 dependencies = [
348 "cfg-if 1.0.0",
381 "cfg-if 1.0.0",
349 "crc32fast",
382 "crc32fast",
350 "libc",
383 "libc",
351 "libz-sys",
384 "libz-sys",
352 "miniz_oxide",
385 "miniz_oxide",
353 ]
386 ]
354
387
355 [[package]]
388 [[package]]
356 name = "format-bytes"
389 name = "format-bytes"
357 version = "0.3.0"
390 version = "0.3.0"
358 source = "registry+https://github.com/rust-lang/crates.io-index"
391 source = "registry+https://github.com/rust-lang/crates.io-index"
359 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
392 checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
360 dependencies = [
393 dependencies = [
361 "format-bytes-macros",
394 "format-bytes-macros",
362 ]
395 ]
363
396
364 [[package]]
397 [[package]]
365 name = "format-bytes-macros"
398 name = "format-bytes-macros"
366 version = "0.4.0"
399 version = "0.4.0"
367 source = "registry+https://github.com/rust-lang/crates.io-index"
400 source = "registry+https://github.com/rust-lang/crates.io-index"
368 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
401 checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
369 dependencies = [
402 dependencies = [
370 "proc-macro2",
403 "proc-macro2",
371 "quote",
404 "quote",
372 "syn",
405 "syn",
373 ]
406 ]
374
407
375 [[package]]
408 [[package]]
376 name = "generic-array"
409 name = "generic-array"
377 version = "0.14.4"
410 version = "0.14.4"
378 source = "registry+https://github.com/rust-lang/crates.io-index"
411 source = "registry+https://github.com/rust-lang/crates.io-index"
379 checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
412 checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
380 dependencies = [
413 dependencies = [
381 "typenum",
414 "typenum",
382 "version_check",
415 "version_check",
383 ]
416 ]
384
417
385 [[package]]
418 [[package]]
386 name = "getrandom"
419 name = "getrandom"
387 version = "0.1.15"
420 version = "0.1.15"
388 source = "registry+https://github.com/rust-lang/crates.io-index"
421 source = "registry+https://github.com/rust-lang/crates.io-index"
389 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
422 checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
390 dependencies = [
423 dependencies = [
391 "cfg-if 0.1.10",
424 "cfg-if 0.1.10",
392 "libc",
425 "libc",
393 "wasi 0.9.0+wasi-snapshot-preview1",
426 "wasi 0.9.0+wasi-snapshot-preview1",
394 ]
427 ]
395
428
396 [[package]]
429 [[package]]
397 name = "getrandom"
430 name = "getrandom"
398 version = "0.2.4"
431 version = "0.2.4"
399 source = "registry+https://github.com/rust-lang/crates.io-index"
432 source = "registry+https://github.com/rust-lang/crates.io-index"
400 checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
433 checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c"
401 dependencies = [
434 dependencies = [
402 "cfg-if 1.0.0",
435 "cfg-if 1.0.0",
403 "libc",
436 "libc",
404 "wasi 0.10.0+wasi-snapshot-preview1",
437 "wasi 0.10.0+wasi-snapshot-preview1",
405 ]
438 ]
406
439
407 [[package]]
440 [[package]]
408 name = "glob"
441 name = "glob"
409 version = "0.3.0"
442 version = "0.3.0"
410 source = "registry+https://github.com/rust-lang/crates.io-index"
443 source = "registry+https://github.com/rust-lang/crates.io-index"
411 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
444 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
412
445
413 [[package]]
446 [[package]]
414 name = "hashbrown"
447 name = "hashbrown"
415 version = "0.9.1"
448 version = "0.9.1"
416 source = "registry+https://github.com/rust-lang/crates.io-index"
449 source = "registry+https://github.com/rust-lang/crates.io-index"
417 checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
450 checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
418 dependencies = [
451 dependencies = [
419 "ahash",
452 "ahash",
420 "rayon",
453 "rayon",
421 ]
454 ]
422
455
423 [[package]]
456 [[package]]
424 name = "hermit-abi"
457 name = "hermit-abi"
425 version = "0.1.17"
458 version = "0.1.17"
426 source = "registry+https://github.com/rust-lang/crates.io-index"
459 source = "registry+https://github.com/rust-lang/crates.io-index"
427 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
460 checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
428 dependencies = [
461 dependencies = [
429 "libc",
462 "libc",
430 ]
463 ]
431
464
432 [[package]]
465 [[package]]
433 name = "hex"
466 name = "hex"
434 version = "0.4.3"
467 version = "0.4.3"
435 source = "registry+https://github.com/rust-lang/crates.io-index"
468 source = "registry+https://github.com/rust-lang/crates.io-index"
436 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
469 checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
437
470
438 [[package]]
471 [[package]]
439 name = "hg-core"
472 name = "hg-core"
440 version = "0.1.0"
473 version = "0.1.0"
441 dependencies = [
474 dependencies = [
442 "bitflags",
475 "bitflags",
443 "byteorder",
476 "byteorder",
444 "bytes-cast",
477 "bytes-cast",
445 "clap",
478 "clap",
446 "crossbeam-channel",
479 "crossbeam-channel 0.4.4",
447 "derive_more",
480 "derive_more",
448 "flate2",
481 "flate2",
449 "format-bytes",
482 "format-bytes",
450 "hashbrown",
483 "hashbrown",
451 "home",
484 "home",
452 "im-rc",
485 "im-rc",
453 "itertools 0.10.3",
486 "itertools 0.10.3",
454 "lazy_static",
487 "lazy_static",
455 "libc",
488 "libc",
456 "log",
489 "log",
457 "memmap2",
490 "memmap2",
458 "micro-timer",
491 "micro-timer 0.3.1",
492 "ouroboros",
459 "pretty_assertions",
493 "pretty_assertions",
460 "rand 0.8.5",
494 "rand 0.8.5",
461 "rand_distr",
495 "rand_distr",
462 "rand_pcg",
496 "rand_pcg",
463 "rayon",
497 "rayon",
464 "regex",
498 "regex",
465 "same-file",
499 "same-file",
466 "sha-1 0.10.0",
500 "sha-1 0.10.0",
467 "stable_deref_trait",
468 "tempfile",
501 "tempfile",
469 "twox-hash",
502 "twox-hash",
470 "zstd",
503 "zstd",
471 ]
504 ]
472
505
473 [[package]]
506 [[package]]
474 name = "hg-cpython"
507 name = "hg-cpython"
475 version = "0.1.0"
508 version = "0.1.0"
476 dependencies = [
509 dependencies = [
477 "cpython",
510 "cpython",
478 "crossbeam-channel",
511 "crossbeam-channel 0.5.2",
479 "env_logger",
512 "env_logger",
480 "hg-core",
513 "hg-core",
481 "libc",
514 "libc",
482 "log",
515 "log",
483 "stable_deref_trait",
516 "stable_deref_trait",
484 "vcsgraph",
517 "vcsgraph",
485 ]
518 ]
486
519
487 [[package]]
520 [[package]]
488 name = "home"
521 name = "home"
489 version = "0.5.3"
522 version = "0.5.3"
490 source = "registry+https://github.com/rust-lang/crates.io-index"
523 source = "registry+https://github.com/rust-lang/crates.io-index"
491 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
524 checksum = "2456aef2e6b6a9784192ae780c0f15bc57df0e918585282325e8c8ac27737654"
492 dependencies = [
525 dependencies = [
493 "winapi",
526 "winapi",
494 ]
527 ]
495
528
496 [[package]]
529 [[package]]
497 name = "humantime"
530 name = "humantime"
498 version = "2.1.0"
531 version = "2.1.0"
499 source = "registry+https://github.com/rust-lang/crates.io-index"
532 source = "registry+https://github.com/rust-lang/crates.io-index"
500 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
533 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
501
534
502 [[package]]
535 [[package]]
503 name = "im-rc"
536 name = "im-rc"
504 version = "15.0.0"
537 version = "15.0.0"
505 source = "registry+https://github.com/rust-lang/crates.io-index"
538 source = "registry+https://github.com/rust-lang/crates.io-index"
506 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
539 checksum = "3ca8957e71f04a205cb162508f9326aea04676c8dfd0711220190d6b83664f3f"
507 dependencies = [
540 dependencies = [
508 "bitmaps",
541 "bitmaps",
509 "rand_core 0.5.1",
542 "rand_core 0.5.1",
510 "rand_xoshiro",
543 "rand_xoshiro",
511 "sized-chunks",
544 "sized-chunks",
512 "typenum",
545 "typenum",
513 "version_check",
546 "version_check",
514 ]
547 ]
515
548
516 [[package]]
549 [[package]]
517 name = "instant"
550 name = "instant"
518 version = "0.1.12"
551 version = "0.1.12"
519 source = "registry+https://github.com/rust-lang/crates.io-index"
552 source = "registry+https://github.com/rust-lang/crates.io-index"
520 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
553 checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
521 dependencies = [
554 dependencies = [
522 "cfg-if 1.0.0",
555 "cfg-if 1.0.0",
523 ]
556 ]
524
557
525 [[package]]
558 [[package]]
526 name = "itertools"
559 name = "itertools"
527 version = "0.9.0"
560 version = "0.9.0"
528 source = "registry+https://github.com/rust-lang/crates.io-index"
561 source = "registry+https://github.com/rust-lang/crates.io-index"
529 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
562 checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
530 dependencies = [
563 dependencies = [
531 "either",
564 "either",
532 ]
565 ]
533
566
534 [[package]]
567 [[package]]
535 name = "itertools"
568 name = "itertools"
536 version = "0.10.3"
569 version = "0.10.3"
537 source = "registry+https://github.com/rust-lang/crates.io-index"
570 source = "registry+https://github.com/rust-lang/crates.io-index"
538 checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
571 checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
539 dependencies = [
572 dependencies = [
540 "either",
573 "either",
541 ]
574 ]
542
575
543 [[package]]
576 [[package]]
544 name = "jobserver"
577 name = "jobserver"
545 version = "0.1.21"
578 version = "0.1.21"
546 source = "registry+https://github.com/rust-lang/crates.io-index"
579 source = "registry+https://github.com/rust-lang/crates.io-index"
547 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
580 checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
548 dependencies = [
581 dependencies = [
549 "libc",
582 "libc",
550 ]
583 ]
551
584
552 [[package]]
585 [[package]]
553 name = "lazy_static"
586 name = "lazy_static"
554 version = "1.4.0"
587 version = "1.4.0"
555 source = "registry+https://github.com/rust-lang/crates.io-index"
588 source = "registry+https://github.com/rust-lang/crates.io-index"
556 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
589 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
557
590
558 [[package]]
591 [[package]]
559 name = "libc"
592 name = "libc"
560 version = "0.2.119"
593 version = "0.2.119"
561 source = "registry+https://github.com/rust-lang/crates.io-index"
594 source = "registry+https://github.com/rust-lang/crates.io-index"
562 checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
595 checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
563
596
564 [[package]]
597 [[package]]
565 name = "libm"
598 name = "libm"
566 version = "0.2.1"
599 version = "0.2.1"
567 source = "registry+https://github.com/rust-lang/crates.io-index"
600 source = "registry+https://github.com/rust-lang/crates.io-index"
568 checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
601 checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
569
602
570 [[package]]
603 [[package]]
571 name = "libz-sys"
604 name = "libz-sys"
572 version = "1.1.2"
605 version = "1.1.2"
573 source = "registry+https://github.com/rust-lang/crates.io-index"
606 source = "registry+https://github.com/rust-lang/crates.io-index"
574 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
607 checksum = "602113192b08db8f38796c4e85c39e960c145965140e918018bcde1952429655"
575 dependencies = [
608 dependencies = [
576 "cc",
609 "cc",
577 "pkg-config",
610 "pkg-config",
578 "vcpkg",
611 "vcpkg",
579 ]
612 ]
580
613
581 [[package]]
614 [[package]]
582 name = "log"
615 name = "log"
583 version = "0.4.14"
616 version = "0.4.14"
584 source = "registry+https://github.com/rust-lang/crates.io-index"
617 source = "registry+https://github.com/rust-lang/crates.io-index"
585 checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
618 checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
586 dependencies = [
619 dependencies = [
587 "cfg-if 1.0.0",
620 "cfg-if 1.0.0",
588 ]
621 ]
589
622
590 [[package]]
623 [[package]]
624 name = "maybe-uninit"
625 version = "2.0.0"
626 source = "registry+https://github.com/rust-lang/crates.io-index"
627 checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
628
629 [[package]]
591 name = "memchr"
630 name = "memchr"
592 version = "2.4.1"
631 version = "2.4.1"
593 source = "registry+https://github.com/rust-lang/crates.io-index"
632 source = "registry+https://github.com/rust-lang/crates.io-index"
594 checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
633 checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
595
634
596 [[package]]
635 [[package]]
597 name = "memmap2"
636 name = "memmap2"
598 version = "0.5.3"
637 version = "0.4.0"
599 source = "registry+https://github.com/rust-lang/crates.io-index"
638 source = "registry+https://github.com/rust-lang/crates.io-index"
600 checksum = "057a3db23999c867821a7a59feb06a578fcb03685e983dff90daf9e7d24ac08f"
639 checksum = "de5d3112c080d58ce560081baeaab7e1e864ca21795ddbf533d5b1842bb1ecf8"
601 dependencies = [
640 dependencies = [
602 "libc",
641 "libc",
603 "stable_deref_trait",
642 "stable_deref_trait",
604 ]
643 ]
605
644
606 [[package]]
645 [[package]]
607 name = "memoffset"
646 name = "memoffset"
608 version = "0.6.1"
647 version = "0.6.1"
609 source = "registry+https://github.com/rust-lang/crates.io-index"
648 source = "registry+https://github.com/rust-lang/crates.io-index"
610 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
649 checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
611 dependencies = [
650 dependencies = [
612 "autocfg",
651 "autocfg",
613 ]
652 ]
614
653
615 [[package]]
654 [[package]]
616 name = "micro-timer"
655 name = "micro-timer"
656 version = "0.3.1"
657 source = "registry+https://github.com/rust-lang/crates.io-index"
658 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
659 dependencies = [
660 "micro-timer-macros 0.3.1",
661 "scopeguard",
662 ]
663
664 [[package]]
665 name = "micro-timer"
617 version = "0.4.0"
666 version = "0.4.0"
618 source = "registry+https://github.com/rust-lang/crates.io-index"
667 source = "registry+https://github.com/rust-lang/crates.io-index"
619 checksum = "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405"
668 checksum = "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405"
620 dependencies = [
669 dependencies = [
621 "micro-timer-macros",
670 "micro-timer-macros 0.4.0",
622 "scopeguard",
671 "scopeguard",
623 ]
672 ]
624
673
625 [[package]]
674 [[package]]
626 name = "micro-timer-macros"
675 name = "micro-timer-macros"
676 version = "0.3.1"
677 source = "registry+https://github.com/rust-lang/crates.io-index"
678 checksum = "e28a3473e6abd6e9aab36aaeef32ad22ae0bd34e79f376643594c2b152ec1c5d"
679 dependencies = [
680 "proc-macro2",
681 "quote",
682 "scopeguard",
683 "syn",
684 ]
685
686 [[package]]
687 name = "micro-timer-macros"
627 version = "0.4.0"
688 version = "0.4.0"
628 source = "registry+https://github.com/rust-lang/crates.io-index"
689 source = "registry+https://github.com/rust-lang/crates.io-index"
629 checksum = "cee948b94700125b52dfb68dd17c19f6326696c1df57f92c05ee857463c93ba1"
690 checksum = "cee948b94700125b52dfb68dd17c19f6326696c1df57f92c05ee857463c93ba1"
630 dependencies = [
691 dependencies = [
631 "proc-macro2",
692 "proc-macro2",
632 "quote",
693 "quote",
633 "scopeguard",
694 "scopeguard",
634 "syn",
695 "syn",
635 ]
696 ]
636
697
637 [[package]]
698 [[package]]
638 name = "miniz_oxide"
699 name = "miniz_oxide"
639 version = "0.4.3"
700 version = "0.4.3"
640 source = "registry+https://github.com/rust-lang/crates.io-index"
701 source = "registry+https://github.com/rust-lang/crates.io-index"
641 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
702 checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
642 dependencies = [
703 dependencies = [
643 "adler",
704 "adler",
644 "autocfg",
705 "autocfg",
645 ]
706 ]
646
707
647 [[package]]
708 [[package]]
648 name = "num-integer"
709 name = "num-integer"
649 version = "0.1.44"
710 version = "0.1.44"
650 source = "registry+https://github.com/rust-lang/crates.io-index"
711 source = "registry+https://github.com/rust-lang/crates.io-index"
651 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
712 checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
652 dependencies = [
713 dependencies = [
653 "autocfg",
714 "autocfg",
654 "num-traits",
715 "num-traits",
655 ]
716 ]
656
717
657 [[package]]
718 [[package]]
658 name = "num-traits"
719 name = "num-traits"
659 version = "0.2.14"
720 version = "0.2.14"
660 source = "registry+https://github.com/rust-lang/crates.io-index"
721 source = "registry+https://github.com/rust-lang/crates.io-index"
661 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
722 checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
662 dependencies = [
723 dependencies = [
663 "autocfg",
724 "autocfg",
664 "libm",
725 "libm",
665 ]
726 ]
666
727
667 [[package]]
728 [[package]]
668 name = "num_cpus"
729 name = "num_cpus"
669 version = "1.13.0"
730 version = "1.13.0"
670 source = "registry+https://github.com/rust-lang/crates.io-index"
731 source = "registry+https://github.com/rust-lang/crates.io-index"
671 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
732 checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
672 dependencies = [
733 dependencies = [
673 "hermit-abi",
734 "hermit-abi",
674 "libc",
735 "libc",
675 ]
736 ]
676
737
677 [[package]]
738 [[package]]
678 name = "opaque-debug"
739 name = "opaque-debug"
679 version = "0.3.0"
740 version = "0.3.0"
680 source = "registry+https://github.com/rust-lang/crates.io-index"
741 source = "registry+https://github.com/rust-lang/crates.io-index"
681 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
742 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
682
743
683 [[package]]
744 [[package]]
745 name = "ouroboros"
746 version = "0.15.0"
747 source = "registry+https://github.com/rust-lang/crates.io-index"
748 checksum = "9f31a3b678685b150cba82b702dcdc5e155893f63610cf388d30cd988d4ca2bf"
749 dependencies = [
750 "aliasable",
751 "ouroboros_macro",
752 "stable_deref_trait",
753 ]
754
755 [[package]]
756 name = "ouroboros_macro"
757 version = "0.15.0"
758 source = "registry+https://github.com/rust-lang/crates.io-index"
759 checksum = "084fd65d5dd8b3772edccb5ffd1e4b7eba43897ecd0f9401e330e8c542959408"
760 dependencies = [
761 "Inflector",
762 "proc-macro-error",
763 "proc-macro2",
764 "quote",
765 "syn",
766 ]
767
768 [[package]]
684 name = "output_vt100"
769 name = "output_vt100"
685 version = "0.1.2"
770 version = "0.1.2"
686 source = "registry+https://github.com/rust-lang/crates.io-index"
771 source = "registry+https://github.com/rust-lang/crates.io-index"
687 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
772 checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9"
688 dependencies = [
773 dependencies = [
689 "winapi",
774 "winapi",
690 ]
775 ]
691
776
692 [[package]]
777 [[package]]
693 name = "paste"
778 name = "paste"
694 version = "1.0.5"
779 version = "1.0.5"
695 source = "registry+https://github.com/rust-lang/crates.io-index"
780 source = "registry+https://github.com/rust-lang/crates.io-index"
696 checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
781 checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58"
697
782
698 [[package]]
783 [[package]]
699 name = "pkg-config"
784 name = "pkg-config"
700 version = "0.3.19"
785 version = "0.3.19"
701 source = "registry+https://github.com/rust-lang/crates.io-index"
786 source = "registry+https://github.com/rust-lang/crates.io-index"
702 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
787 checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
703
788
704 [[package]]
789 [[package]]
705 name = "ppv-lite86"
790 name = "ppv-lite86"
706 version = "0.2.10"
791 version = "0.2.10"
707 source = "registry+https://github.com/rust-lang/crates.io-index"
792 source = "registry+https://github.com/rust-lang/crates.io-index"
708 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
793 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
709
794
710 [[package]]
795 [[package]]
711 name = "pretty_assertions"
796 name = "pretty_assertions"
712 version = "1.1.0"
797 version = "1.1.0"
713 source = "registry+https://github.com/rust-lang/crates.io-index"
798 source = "registry+https://github.com/rust-lang/crates.io-index"
714 checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
799 checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
715 dependencies = [
800 dependencies = [
716 "ansi_term",
801 "ansi_term",
717 "ctor",
802 "ctor",
718 "diff",
803 "diff",
719 "output_vt100",
804 "output_vt100",
720 ]
805 ]
721
806
722 [[package]]
807 [[package]]
808 name = "proc-macro-error"
809 version = "1.0.4"
810 source = "registry+https://github.com/rust-lang/crates.io-index"
811 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
812 dependencies = [
813 "proc-macro-error-attr",
814 "proc-macro2",
815 "quote",
816 "syn",
817 "version_check",
818 ]
819
820 [[package]]
821 name = "proc-macro-error-attr"
822 version = "1.0.4"
823 source = "registry+https://github.com/rust-lang/crates.io-index"
824 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
825 dependencies = [
826 "proc-macro2",
827 "quote",
828 "version_check",
829 ]
830
831 [[package]]
723 name = "proc-macro2"
832 name = "proc-macro2"
724 version = "1.0.24"
833 version = "1.0.24"
725 source = "registry+https://github.com/rust-lang/crates.io-index"
834 source = "registry+https://github.com/rust-lang/crates.io-index"
726 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
835 checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
727 dependencies = [
836 dependencies = [
728 "unicode-xid",
837 "unicode-xid",
729 ]
838 ]
730
839
731 [[package]]
840 [[package]]
732 name = "python3-sys"
841 name = "python3-sys"
733 version = "0.7.0"
842 version = "0.7.0"
734 source = "registry+https://github.com/rust-lang/crates.io-index"
843 source = "registry+https://github.com/rust-lang/crates.io-index"
735 checksum = "b18b32e64c103d5045f44644d7ddddd65336f7a0521f6fde673240a9ecceb77e"
844 checksum = "b18b32e64c103d5045f44644d7ddddd65336f7a0521f6fde673240a9ecceb77e"
736 dependencies = [
845 dependencies = [
737 "libc",
846 "libc",
738 "regex",
847 "regex",
739 ]
848 ]
740
849
741 [[package]]
850 [[package]]
742 name = "quote"
851 name = "quote"
743 version = "1.0.7"
852 version = "1.0.7"
744 source = "registry+https://github.com/rust-lang/crates.io-index"
853 source = "registry+https://github.com/rust-lang/crates.io-index"
745 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
854 checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
746 dependencies = [
855 dependencies = [
747 "proc-macro2",
856 "proc-macro2",
748 ]
857 ]
749
858
750 [[package]]
859 [[package]]
751 name = "rand"
860 name = "rand"
752 version = "0.7.3"
861 version = "0.7.3"
753 source = "registry+https://github.com/rust-lang/crates.io-index"
862 source = "registry+https://github.com/rust-lang/crates.io-index"
754 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
863 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
755 dependencies = [
864 dependencies = [
756 "getrandom 0.1.15",
865 "getrandom 0.1.15",
757 "libc",
866 "libc",
758 "rand_chacha 0.2.2",
867 "rand_chacha 0.2.2",
759 "rand_core 0.5.1",
868 "rand_core 0.5.1",
760 "rand_hc",
869 "rand_hc",
761 ]
870 ]
762
871
763 [[package]]
872 [[package]]
764 name = "rand"
873 name = "rand"
765 version = "0.8.5"
874 version = "0.8.5"
766 source = "registry+https://github.com/rust-lang/crates.io-index"
875 source = "registry+https://github.com/rust-lang/crates.io-index"
767 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
876 checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
768 dependencies = [
877 dependencies = [
769 "libc",
878 "libc",
770 "rand_chacha 0.3.1",
879 "rand_chacha 0.3.1",
771 "rand_core 0.6.3",
880 "rand_core 0.6.3",
772 ]
881 ]
773
882
774 [[package]]
883 [[package]]
775 name = "rand_chacha"
884 name = "rand_chacha"
776 version = "0.2.2"
885 version = "0.2.2"
777 source = "registry+https://github.com/rust-lang/crates.io-index"
886 source = "registry+https://github.com/rust-lang/crates.io-index"
778 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
887 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
779 dependencies = [
888 dependencies = [
780 "ppv-lite86",
889 "ppv-lite86",
781 "rand_core 0.5.1",
890 "rand_core 0.5.1",
782 ]
891 ]
783
892
784 [[package]]
893 [[package]]
785 name = "rand_chacha"
894 name = "rand_chacha"
786 version = "0.3.1"
895 version = "0.3.1"
787 source = "registry+https://github.com/rust-lang/crates.io-index"
896 source = "registry+https://github.com/rust-lang/crates.io-index"
788 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
897 checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
789 dependencies = [
898 dependencies = [
790 "ppv-lite86",
899 "ppv-lite86",
791 "rand_core 0.6.3",
900 "rand_core 0.6.3",
792 ]
901 ]
793
902
794 [[package]]
903 [[package]]
795 name = "rand_core"
904 name = "rand_core"
796 version = "0.5.1"
905 version = "0.5.1"
797 source = "registry+https://github.com/rust-lang/crates.io-index"
906 source = "registry+https://github.com/rust-lang/crates.io-index"
798 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
907 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
799 dependencies = [
908 dependencies = [
800 "getrandom 0.1.15",
909 "getrandom 0.1.15",
801 ]
910 ]
802
911
803 [[package]]
912 [[package]]
804 name = "rand_core"
913 name = "rand_core"
805 version = "0.6.3"
914 version = "0.6.3"
806 source = "registry+https://github.com/rust-lang/crates.io-index"
915 source = "registry+https://github.com/rust-lang/crates.io-index"
807 checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
916 checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
808 dependencies = [
917 dependencies = [
809 "getrandom 0.2.4",
918 "getrandom 0.2.4",
810 ]
919 ]
811
920
812 [[package]]
921 [[package]]
813 name = "rand_distr"
922 name = "rand_distr"
814 version = "0.4.3"
923 version = "0.4.3"
815 source = "registry+https://github.com/rust-lang/crates.io-index"
924 source = "registry+https://github.com/rust-lang/crates.io-index"
816 checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
925 checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
817 dependencies = [
926 dependencies = [
818 "num-traits",
927 "num-traits",
819 "rand 0.8.5",
928 "rand 0.8.5",
820 ]
929 ]
821
930
822 [[package]]
931 [[package]]
823 name = "rand_hc"
932 name = "rand_hc"
824 version = "0.2.0"
933 version = "0.2.0"
825 source = "registry+https://github.com/rust-lang/crates.io-index"
934 source = "registry+https://github.com/rust-lang/crates.io-index"
826 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
935 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
827 dependencies = [
936 dependencies = [
828 "rand_core 0.5.1",
937 "rand_core 0.5.1",
829 ]
938 ]
830
939
831 [[package]]
940 [[package]]
832 name = "rand_pcg"
941 name = "rand_pcg"
833 version = "0.3.1"
942 version = "0.3.1"
834 source = "registry+https://github.com/rust-lang/crates.io-index"
943 source = "registry+https://github.com/rust-lang/crates.io-index"
835 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
944 checksum = "59cad018caf63deb318e5a4586d99a24424a364f40f1e5778c29aca23f4fc73e"
836 dependencies = [
945 dependencies = [
837 "rand_core 0.6.3",
946 "rand_core 0.6.3",
838 ]
947 ]
839
948
840 [[package]]
949 [[package]]
841 name = "rand_xoshiro"
950 name = "rand_xoshiro"
842 version = "0.4.0"
951 version = "0.4.0"
843 source = "registry+https://github.com/rust-lang/crates.io-index"
952 source = "registry+https://github.com/rust-lang/crates.io-index"
844 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
953 checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
845 dependencies = [
954 dependencies = [
846 "rand_core 0.5.1",
955 "rand_core 0.5.1",
847 ]
956 ]
848
957
849 [[package]]
958 [[package]]
850 name = "rayon"
959 name = "rayon"
851 version = "1.5.1"
960 version = "1.5.1"
852 source = "registry+https://github.com/rust-lang/crates.io-index"
961 source = "registry+https://github.com/rust-lang/crates.io-index"
853 checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
962 checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
854 dependencies = [
963 dependencies = [
855 "autocfg",
964 "autocfg",
856 "crossbeam-deque",
965 "crossbeam-deque",
857 "either",
966 "either",
858 "rayon-core",
967 "rayon-core",
859 ]
968 ]
860
969
861 [[package]]
970 [[package]]
862 name = "rayon-core"
971 name = "rayon-core"
863 version = "1.9.1"
972 version = "1.9.1"
864 source = "registry+https://github.com/rust-lang/crates.io-index"
973 source = "registry+https://github.com/rust-lang/crates.io-index"
865 checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
974 checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
866 dependencies = [
975 dependencies = [
867 "crossbeam-channel",
976 "crossbeam-channel 0.5.2",
868 "crossbeam-deque",
977 "crossbeam-deque",
869 "crossbeam-utils",
978 "crossbeam-utils 0.8.1",
870 "lazy_static",
979 "lazy_static",
871 "num_cpus",
980 "num_cpus",
872 ]
981 ]
873
982
874 [[package]]
983 [[package]]
875 name = "redox_syscall"
984 name = "redox_syscall"
876 version = "0.2.11"
985 version = "0.2.11"
877 source = "registry+https://github.com/rust-lang/crates.io-index"
986 source = "registry+https://github.com/rust-lang/crates.io-index"
878 checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
987 checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
879 dependencies = [
988 dependencies = [
880 "bitflags",
989 "bitflags",
881 ]
990 ]
882
991
883 [[package]]
992 [[package]]
884 name = "regex"
993 name = "regex"
885 version = "1.5.5"
994 version = "1.5.5"
886 source = "registry+https://github.com/rust-lang/crates.io-index"
995 source = "registry+https://github.com/rust-lang/crates.io-index"
887 checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
996 checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
888 dependencies = [
997 dependencies = [
889 "aho-corasick",
998 "aho-corasick",
890 "memchr",
999 "memchr",
891 "regex-syntax",
1000 "regex-syntax",
892 ]
1001 ]
893
1002
894 [[package]]
1003 [[package]]
895 name = "regex-syntax"
1004 name = "regex-syntax"
896 version = "0.6.25"
1005 version = "0.6.25"
897 source = "registry+https://github.com/rust-lang/crates.io-index"
1006 source = "registry+https://github.com/rust-lang/crates.io-index"
898 checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
1007 checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
899
1008
900 [[package]]
1009 [[package]]
901 name = "remove_dir_all"
1010 name = "remove_dir_all"
902 version = "0.5.3"
1011 version = "0.5.3"
903 source = "registry+https://github.com/rust-lang/crates.io-index"
1012 source = "registry+https://github.com/rust-lang/crates.io-index"
904 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
1013 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
905 dependencies = [
1014 dependencies = [
906 "winapi",
1015 "winapi",
907 ]
1016 ]
908
1017
909 [[package]]
1018 [[package]]
910 name = "rhg"
1019 name = "rhg"
911 version = "0.1.0"
1020 version = "0.1.0"
912 dependencies = [
1021 dependencies = [
913 "atty",
1022 "atty",
914 "chrono",
1023 "chrono",
915 "clap",
1024 "clap",
916 "derive_more",
1025 "derive_more",
917 "env_logger",
1026 "env_logger",
918 "format-bytes",
1027 "format-bytes",
919 "hg-core",
1028 "hg-core",
920 "home",
1029 "home",
921 "lazy_static",
1030 "lazy_static",
922 "log",
1031 "log",
923 "micro-timer",
1032 "micro-timer 0.4.0",
924 "regex",
1033 "regex",
925 "users",
1034 "users",
926 ]
1035 ]
927
1036
928 [[package]]
1037 [[package]]
929 name = "rustc_version"
1038 name = "rustc_version"
930 version = "0.4.0"
1039 version = "0.4.0"
931 source = "registry+https://github.com/rust-lang/crates.io-index"
1040 source = "registry+https://github.com/rust-lang/crates.io-index"
932 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
1041 checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
933 dependencies = [
1042 dependencies = [
934 "semver",
1043 "semver",
935 ]
1044 ]
936
1045
937 [[package]]
1046 [[package]]
938 name = "same-file"
1047 name = "same-file"
939 version = "1.0.6"
1048 version = "1.0.6"
940 source = "registry+https://github.com/rust-lang/crates.io-index"
1049 source = "registry+https://github.com/rust-lang/crates.io-index"
941 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
1050 checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
942 dependencies = [
1051 dependencies = [
943 "winapi-util",
1052 "winapi-util",
944 ]
1053 ]
945
1054
946 [[package]]
1055 [[package]]
947 name = "scopeguard"
1056 name = "scopeguard"
948 version = "1.1.0"
1057 version = "1.1.0"
949 source = "registry+https://github.com/rust-lang/crates.io-index"
1058 source = "registry+https://github.com/rust-lang/crates.io-index"
950 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
1059 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
951
1060
952 [[package]]
1061 [[package]]
953 name = "semver"
1062 name = "semver"
954 version = "1.0.6"
1063 version = "1.0.6"
955 source = "registry+https://github.com/rust-lang/crates.io-index"
1064 source = "registry+https://github.com/rust-lang/crates.io-index"
956 checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
1065 checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
957
1066
958 [[package]]
1067 [[package]]
959 name = "sha-1"
1068 name = "sha-1"
960 version = "0.9.6"
1069 version = "0.9.6"
961 source = "registry+https://github.com/rust-lang/crates.io-index"
1070 source = "registry+https://github.com/rust-lang/crates.io-index"
962 checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
1071 checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
963 dependencies = [
1072 dependencies = [
964 "block-buffer 0.9.0",
1073 "block-buffer 0.9.0",
965 "cfg-if 1.0.0",
1074 "cfg-if 1.0.0",
966 "cpufeatures 0.1.4",
1075 "cpufeatures 0.1.4",
967 "digest 0.9.0",
1076 "digest 0.9.0",
968 "opaque-debug",
1077 "opaque-debug",
969 ]
1078 ]
970
1079
971 [[package]]
1080 [[package]]
972 name = "sha-1"
1081 name = "sha-1"
973 version = "0.10.0"
1082 version = "0.10.0"
974 source = "registry+https://github.com/rust-lang/crates.io-index"
1083 source = "registry+https://github.com/rust-lang/crates.io-index"
975 checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
1084 checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
976 dependencies = [
1085 dependencies = [
977 "cfg-if 1.0.0",
1086 "cfg-if 1.0.0",
978 "cpufeatures 0.2.1",
1087 "cpufeatures 0.2.1",
979 "digest 0.10.2",
1088 "digest 0.10.2",
980 ]
1089 ]
981
1090
982 [[package]]
1091 [[package]]
983 name = "sized-chunks"
1092 name = "sized-chunks"
984 version = "0.6.2"
1093 version = "0.6.2"
985 source = "registry+https://github.com/rust-lang/crates.io-index"
1094 source = "registry+https://github.com/rust-lang/crates.io-index"
986 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
1095 checksum = "1ec31ceca5644fa6d444cc77548b88b67f46db6f7c71683b0f9336e671830d2f"
987 dependencies = [
1096 dependencies = [
988 "bitmaps",
1097 "bitmaps",
989 "typenum",
1098 "typenum",
990 ]
1099 ]
991
1100
992 [[package]]
1101 [[package]]
993 name = "stable_deref_trait"
1102 name = "stable_deref_trait"
994 version = "1.2.0"
1103 version = "1.2.0"
995 source = "registry+https://github.com/rust-lang/crates.io-index"
1104 source = "registry+https://github.com/rust-lang/crates.io-index"
996 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
1105 checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
997
1106
998 [[package]]
1107 [[package]]
999 name = "static_assertions"
1108 name = "static_assertions"
1000 version = "1.1.0"
1109 version = "1.1.0"
1001 source = "registry+https://github.com/rust-lang/crates.io-index"
1110 source = "registry+https://github.com/rust-lang/crates.io-index"
1002 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1111 checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1003
1112
1004 [[package]]
1113 [[package]]
1005 name = "strsim"
1114 name = "strsim"
1006 version = "0.8.0"
1115 version = "0.8.0"
1007 source = "registry+https://github.com/rust-lang/crates.io-index"
1116 source = "registry+https://github.com/rust-lang/crates.io-index"
1008 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
1117 checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
1009
1118
1010 [[package]]
1119 [[package]]
1011 name = "syn"
1120 name = "syn"
1012 version = "1.0.54"
1121 version = "1.0.54"
1013 source = "registry+https://github.com/rust-lang/crates.io-index"
1122 source = "registry+https://github.com/rust-lang/crates.io-index"
1014 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
1123 checksum = "9a2af957a63d6bd42255c359c93d9bfdb97076bd3b820897ce55ffbfbf107f44"
1015 dependencies = [
1124 dependencies = [
1016 "proc-macro2",
1125 "proc-macro2",
1017 "quote",
1126 "quote",
1018 "unicode-xid",
1127 "unicode-xid",
1019 ]
1128 ]
1020
1129
1021 [[package]]
1130 [[package]]
1022 name = "tempfile"
1131 name = "tempfile"
1023 version = "3.3.0"
1132 version = "3.3.0"
1024 source = "registry+https://github.com/rust-lang/crates.io-index"
1133 source = "registry+https://github.com/rust-lang/crates.io-index"
1025 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1134 checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
1026 dependencies = [
1135 dependencies = [
1027 "cfg-if 1.0.0",
1136 "cfg-if 1.0.0",
1028 "fastrand",
1137 "fastrand",
1029 "libc",
1138 "libc",
1030 "redox_syscall",
1139 "redox_syscall",
1031 "remove_dir_all",
1140 "remove_dir_all",
1032 "winapi",
1141 "winapi",
1033 ]
1142 ]
1034
1143
1035 [[package]]
1144 [[package]]
1036 name = "termcolor"
1145 name = "termcolor"
1037 version = "1.1.2"
1146 version = "1.1.2"
1038 source = "registry+https://github.com/rust-lang/crates.io-index"
1147 source = "registry+https://github.com/rust-lang/crates.io-index"
1039 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
1148 checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
1040 dependencies = [
1149 dependencies = [
1041 "winapi-util",
1150 "winapi-util",
1042 ]
1151 ]
1043
1152
1044 [[package]]
1153 [[package]]
1045 name = "textwrap"
1154 name = "textwrap"
1046 version = "0.11.0"
1155 version = "0.11.0"
1047 source = "registry+https://github.com/rust-lang/crates.io-index"
1156 source = "registry+https://github.com/rust-lang/crates.io-index"
1048 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1157 checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
1049 dependencies = [
1158 dependencies = [
1050 "unicode-width",
1159 "unicode-width",
1051 ]
1160 ]
1052
1161
1053 [[package]]
1162 [[package]]
1054 name = "time"
1163 name = "time"
1055 version = "0.1.44"
1164 version = "0.1.44"
1056 source = "registry+https://github.com/rust-lang/crates.io-index"
1165 source = "registry+https://github.com/rust-lang/crates.io-index"
1057 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1166 checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
1058 dependencies = [
1167 dependencies = [
1059 "libc",
1168 "libc",
1060 "wasi 0.10.0+wasi-snapshot-preview1",
1169 "wasi 0.10.0+wasi-snapshot-preview1",
1061 "winapi",
1170 "winapi",
1062 ]
1171 ]
1063
1172
1064 [[package]]
1173 [[package]]
1065 name = "twox-hash"
1174 name = "twox-hash"
1066 version = "1.6.2"
1175 version = "1.6.2"
1067 source = "registry+https://github.com/rust-lang/crates.io-index"
1176 source = "registry+https://github.com/rust-lang/crates.io-index"
1068 checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0"
1177 checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0"
1069 dependencies = [
1178 dependencies = [
1070 "cfg-if 1.0.0",
1179 "cfg-if 1.0.0",
1071 "rand 0.8.5",
1180 "rand 0.8.5",
1072 "static_assertions",
1181 "static_assertions",
1073 ]
1182 ]
1074
1183
1075 [[package]]
1184 [[package]]
1076 name = "typenum"
1185 name = "typenum"
1077 version = "1.12.0"
1186 version = "1.12.0"
1078 source = "registry+https://github.com/rust-lang/crates.io-index"
1187 source = "registry+https://github.com/rust-lang/crates.io-index"
1079 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1188 checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
1080
1189
1081 [[package]]
1190 [[package]]
1082 name = "unicode-width"
1191 name = "unicode-width"
1083 version = "0.1.9"
1192 version = "0.1.9"
1084 source = "registry+https://github.com/rust-lang/crates.io-index"
1193 source = "registry+https://github.com/rust-lang/crates.io-index"
1085 checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
1194 checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
1086
1195
1087 [[package]]
1196 [[package]]
1088 name = "unicode-xid"
1197 name = "unicode-xid"
1089 version = "0.2.1"
1198 version = "0.2.1"
1090 source = "registry+https://github.com/rust-lang/crates.io-index"
1199 source = "registry+https://github.com/rust-lang/crates.io-index"
1091 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1200 checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
1092
1201
1093 [[package]]
1202 [[package]]
1094 name = "users"
1203 name = "users"
1095 version = "0.11.0"
1204 version = "0.11.0"
1096 source = "registry+https://github.com/rust-lang/crates.io-index"
1205 source = "registry+https://github.com/rust-lang/crates.io-index"
1097 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1206 checksum = "24cc0f6d6f267b73e5a2cadf007ba8f9bc39c6a6f9666f8cf25ea809a153b032"
1098 dependencies = [
1207 dependencies = [
1099 "libc",
1208 "libc",
1100 "log",
1209 "log",
1101 ]
1210 ]
1102
1211
1103 [[package]]
1212 [[package]]
1104 name = "vcpkg"
1213 name = "vcpkg"
1105 version = "0.2.11"
1214 version = "0.2.11"
1106 source = "registry+https://github.com/rust-lang/crates.io-index"
1215 source = "registry+https://github.com/rust-lang/crates.io-index"
1107 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1216 checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
1108
1217
1109 [[package]]
1218 [[package]]
1110 name = "vcsgraph"
1219 name = "vcsgraph"
1111 version = "0.2.0"
1220 version = "0.2.0"
1112 source = "registry+https://github.com/rust-lang/crates.io-index"
1221 source = "registry+https://github.com/rust-lang/crates.io-index"
1113 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1222 checksum = "4cb68c231e2575f7503a7c19213875f9d4ec2e84e963a56ce3de4b6bee351ef7"
1114 dependencies = [
1223 dependencies = [
1115 "hex",
1224 "hex",
1116 "rand 0.7.3",
1225 "rand 0.7.3",
1117 "sha-1 0.9.6",
1226 "sha-1 0.9.6",
1118 ]
1227 ]
1119
1228
1120 [[package]]
1229 [[package]]
1121 name = "vec_map"
1230 name = "vec_map"
1122 version = "0.8.2"
1231 version = "0.8.2"
1123 source = "registry+https://github.com/rust-lang/crates.io-index"
1232 source = "registry+https://github.com/rust-lang/crates.io-index"
1124 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1233 checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
1125
1234
1126 [[package]]
1235 [[package]]
1127 name = "version_check"
1236 name = "version_check"
1128 version = "0.9.2"
1237 version = "0.9.2"
1129 source = "registry+https://github.com/rust-lang/crates.io-index"
1238 source = "registry+https://github.com/rust-lang/crates.io-index"
1130 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1239 checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
1131
1240
1132 [[package]]
1241 [[package]]
1133 name = "wasi"
1242 name = "wasi"
1134 version = "0.9.0+wasi-snapshot-preview1"
1243 version = "0.9.0+wasi-snapshot-preview1"
1135 source = "registry+https://github.com/rust-lang/crates.io-index"
1244 source = "registry+https://github.com/rust-lang/crates.io-index"
1136 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1245 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
1137
1246
1138 [[package]]
1247 [[package]]
1139 name = "wasi"
1248 name = "wasi"
1140 version = "0.10.0+wasi-snapshot-preview1"
1249 version = "0.10.0+wasi-snapshot-preview1"
1141 source = "registry+https://github.com/rust-lang/crates.io-index"
1250 source = "registry+https://github.com/rust-lang/crates.io-index"
1142 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1251 checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
1143
1252
1144 [[package]]
1253 [[package]]
1145 name = "winapi"
1254 name = "winapi"
1146 version = "0.3.9"
1255 version = "0.3.9"
1147 source = "registry+https://github.com/rust-lang/crates.io-index"
1256 source = "registry+https://github.com/rust-lang/crates.io-index"
1148 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1257 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
1149 dependencies = [
1258 dependencies = [
1150 "winapi-i686-pc-windows-gnu",
1259 "winapi-i686-pc-windows-gnu",
1151 "winapi-x86_64-pc-windows-gnu",
1260 "winapi-x86_64-pc-windows-gnu",
1152 ]
1261 ]
1153
1262
1154 [[package]]
1263 [[package]]
1155 name = "winapi-i686-pc-windows-gnu"
1264 name = "winapi-i686-pc-windows-gnu"
1156 version = "0.4.0"
1265 version = "0.4.0"
1157 source = "registry+https://github.com/rust-lang/crates.io-index"
1266 source = "registry+https://github.com/rust-lang/crates.io-index"
1158 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1267 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
1159
1268
1160 [[package]]
1269 [[package]]
1161 name = "winapi-util"
1270 name = "winapi-util"
1162 version = "0.1.5"
1271 version = "0.1.5"
1163 source = "registry+https://github.com/rust-lang/crates.io-index"
1272 source = "registry+https://github.com/rust-lang/crates.io-index"
1164 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1273 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
1165 dependencies = [
1274 dependencies = [
1166 "winapi",
1275 "winapi",
1167 ]
1276 ]
1168
1277
1169 [[package]]
1278 [[package]]
1170 name = "winapi-x86_64-pc-windows-gnu"
1279 name = "winapi-x86_64-pc-windows-gnu"
1171 version = "0.4.0"
1280 version = "0.4.0"
1172 source = "registry+https://github.com/rust-lang/crates.io-index"
1281 source = "registry+https://github.com/rust-lang/crates.io-index"
1173 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1282 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
1174
1283
1175 [[package]]
1284 [[package]]
1176 name = "zstd"
1285 name = "zstd"
1177 version = "0.5.4+zstd.1.4.7"
1286 version = "0.5.4+zstd.1.4.7"
1178 source = "registry+https://github.com/rust-lang/crates.io-index"
1287 source = "registry+https://github.com/rust-lang/crates.io-index"
1179 checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
1288 checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
1180 dependencies = [
1289 dependencies = [
1181 "zstd-safe",
1290 "zstd-safe",
1182 ]
1291 ]
1183
1292
1184 [[package]]
1293 [[package]]
1185 name = "zstd-safe"
1294 name = "zstd-safe"
1186 version = "2.0.6+zstd.1.4.7"
1295 version = "2.0.6+zstd.1.4.7"
1187 source = "registry+https://github.com/rust-lang/crates.io-index"
1296 source = "registry+https://github.com/rust-lang/crates.io-index"
1188 checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
1297 checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
1189 dependencies = [
1298 dependencies = [
1190 "libc",
1299 "libc",
1191 "zstd-sys",
1300 "zstd-sys",
1192 ]
1301 ]
1193
1302
1194 [[package]]
1303 [[package]]
1195 name = "zstd-sys"
1304 name = "zstd-sys"
1196 version = "1.4.18+zstd.1.4.7"
1305 version = "1.4.18+zstd.1.4.7"
1197 source = "registry+https://github.com/rust-lang/crates.io-index"
1306 source = "registry+https://github.com/rust-lang/crates.io-index"
1198 checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
1307 checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
1199 dependencies = [
1308 dependencies = [
1200 "cc",
1309 "cc",
1201 "glob",
1310 "glob",
1202 "itertools 0.9.0",
1311 "itertools 0.9.0",
1203 "libc",
1312 "libc",
1204 ]
1313 ]
@@ -1,48 +1,48 b''
1 [package]
1 [package]
2 name = "hg-core"
2 name = "hg-core"
3 version = "0.1.0"
3 version = "0.1.0"
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
4 authors = ["Georges Racinet <gracinet@anybox.fr>"]
5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
5 description = "Mercurial pure Rust core library, with no assumption on Python bindings (FFI)"
6 edition = "2018"
6 edition = "2018"
7
7
8 [lib]
8 [lib]
9 name = "hg"
9 name = "hg"
10
10
11 [dependencies]
11 [dependencies]
12 bitflags = "1.3.2"
12 bitflags = "1.3.2"
13 bytes-cast = "0.2.0"
13 bytes-cast = "0.2.0"
14 byteorder = "1.4.3"
14 byteorder = "1.4.3"
15 derive_more = "0.99.17"
15 derive_more = "0.99.17"
16 hashbrown = { version = "0.9.1", features = ["rayon"] }
16 hashbrown = { version = "0.9.1", features = ["rayon"] }
17 home = "0.5.3"
17 home = "0.5.3"
18 im-rc = "15.0.0"
18 im-rc = "15.0.0"
19 itertools = "0.10.3"
19 itertools = "0.10.3"
20 lazy_static = "1.4.0"
20 lazy_static = "1.4.0"
21 libc = "0.2.119"
21 libc = "0.2"
22 rand = "0.8.5"
22 ouroboros = "0.15.0"
23 rand = "0.8.4"
23 rand_pcg = "0.3.1"
24 rand_pcg = "0.3.1"
24 rand_distr = "0.4.3"
25 rand_distr = "0.4.3"
25 rayon = "1.5.1"
26 rayon = "1.5.1"
26 regex = "1.5.5"
27 regex = "1.5.5"
27 sha-1 = "0.10.0"
28 sha-1 = "0.10.0"
28 twox-hash = "1.6.2"
29 twox-hash = "1.6.2"
29 same-file = "1.0.6"
30 same-file = "1.0.6"
30 stable_deref_trait = "1.2.0"
31 tempfile = "3.1.0"
31 tempfile = "3.3.0"
32 crossbeam-channel = "0.4"
32 crossbeam-channel = "0.5.2"
33 micro-timer = "0.3.0"
33 micro-timer = "0.4.0"
34 log = "0.4.8"
34 log = "0.4.14"
35 memmap2 = {version = "0.4", features = ["stable_deref_trait"]}
35 memmap2 = { version = "0.5.3", features = ["stable_deref_trait"] }
36 zstd = "0.5.3"
36 zstd = "0.5.3"
37 format-bytes = "0.3.0"
37 format-bytes = "0.3.0"
38
38
39 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
39 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
40 # we have a clearer view of which backend is the fastest.
40 # we have a clearer view of which backend is the fastest.
41 [dependencies.flate2]
41 [dependencies.flate2]
42 version = "1.0.22"
42 version = "1.0.22"
43 features = ["zlib"]
43 features = ["zlib"]
44 default-features = false
44 default-features = false
45
45
46 [dev-dependencies]
46 [dev-dependencies]
47 clap = "2.34.0"
47 clap = "2.34.0"
48 pretty_assertions = "1.1.0"
48 pretty_assertions = "1.1.0"
@@ -1,151 +1,149 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
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 //! Rust implementation of dirstate.status (dirstate.py).
8 //! Rust implementation of dirstate.status (dirstate.py).
9 //! It is currently missing a lot of functionality compared to the Python one
9 //! It is currently missing a lot of functionality compared to the Python one
10 //! and will only be triggered in narrow cases.
10 //! and will only be triggered in narrow cases.
11
11
12 use crate::dirstate::entry::TruncatedTimestamp;
12 use crate::dirstate::entry::TruncatedTimestamp;
13 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
13 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
14 use crate::{
14 use crate::{
15 utils::hg_path::{HgPath, HgPathError},
15 utils::hg_path::{HgPath, HgPathError},
16 PatternError,
16 PatternError,
17 };
17 };
18
18
19 use std::{borrow::Cow, fmt};
19 use std::{borrow::Cow, fmt};
20
20
21 /// Wrong type of file from a `BadMatch`
21 /// Wrong type of file from a `BadMatch`
22 /// Note: a lot of those don't exist on all platforms.
22 /// Note: a lot of those don't exist on all platforms.
23 #[derive(Debug, Copy, Clone)]
23 #[derive(Debug, Copy, Clone)]
24 pub enum BadType {
24 pub enum BadType {
25 CharacterDevice,
25 CharacterDevice,
26 BlockDevice,
26 BlockDevice,
27 FIFO,
27 FIFO,
28 Socket,
28 Socket,
29 Directory,
29 Directory,
30 Unknown,
30 Unknown,
31 }
31 }
32
32
33 impl fmt::Display for BadType {
33 impl fmt::Display for BadType {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 f.write_str(match self {
35 f.write_str(match self {
36 BadType::CharacterDevice => "character device",
36 BadType::CharacterDevice => "character device",
37 BadType::BlockDevice => "block device",
37 BadType::BlockDevice => "block device",
38 BadType::FIFO => "fifo",
38 BadType::FIFO => "fifo",
39 BadType::Socket => "socket",
39 BadType::Socket => "socket",
40 BadType::Directory => "directory",
40 BadType::Directory => "directory",
41 BadType::Unknown => "unknown",
41 BadType::Unknown => "unknown",
42 })
42 })
43 }
43 }
44 }
44 }
45
45
46 /// Was explicitly matched but cannot be found/accessed
46 /// Was explicitly matched but cannot be found/accessed
47 #[derive(Debug, Copy, Clone)]
47 #[derive(Debug, Copy, Clone)]
48 pub enum BadMatch {
48 pub enum BadMatch {
49 OsError(i32),
49 OsError(i32),
50 BadType(BadType),
50 BadType(BadType),
51 }
51 }
52
52
53 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
53 /// `Box<dyn Trait>` is syntactic sugar for `Box<dyn Trait + 'static>`, so add
54 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
54 /// an explicit lifetime here to not fight `'static` bounds "out of nowhere".
55 pub type IgnoreFnType<'a> =
55 pub type IgnoreFnType<'a> =
56 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
56 Box<dyn for<'r> Fn(&'r HgPath) -> bool + Sync + 'a>;
57
57
58 /// We have a good mix of owned (from directory traversal) and borrowed (from
58 /// We have a good mix of owned (from directory traversal) and borrowed (from
59 /// the dirstate/explicit) paths, this comes up a lot.
59 /// the dirstate/explicit) paths, this comes up a lot.
60 pub type HgPathCow<'a> = Cow<'a, HgPath>;
60 pub type HgPathCow<'a> = Cow<'a, HgPath>;
61
61
62 #[derive(Debug, Copy, Clone)]
62 #[derive(Debug, Copy, Clone)]
63 pub struct StatusOptions {
63 pub struct StatusOptions {
64 /// Whether we are on a filesystem with UNIX-like exec flags
64 /// Whether we are on a filesystem with UNIX-like exec flags
65 pub check_exec: bool,
65 pub check_exec: bool,
66 pub list_clean: bool,
66 pub list_clean: bool,
67 pub list_unknown: bool,
67 pub list_unknown: bool,
68 pub list_ignored: bool,
68 pub list_ignored: bool,
69 /// Whether to populate `StatusPath::copy_source`
69 /// Whether to populate `StatusPath::copy_source`
70 pub list_copies: bool,
70 pub list_copies: bool,
71 /// Whether to collect traversed dirs for applying a callback later.
71 /// Whether to collect traversed dirs for applying a callback later.
72 /// Used by `hg purge` for example.
72 /// Used by `hg purge` for example.
73 pub collect_traversed_dirs: bool,
73 pub collect_traversed_dirs: bool,
74 }
74 }
75
75
76 #[derive(Default)]
76 #[derive(Default)]
77 pub struct DirstateStatus<'a> {
77 pub struct DirstateStatus<'a> {
78 /// The current time at the start of the `status()` algorithm, as measured
78 /// The current time at the start of the `status()` algorithm, as measured
79 /// and possibly truncated by the filesystem.
79 /// and possibly truncated by the filesystem.
80 pub filesystem_time_at_status_start: Option<TruncatedTimestamp>,
80 pub filesystem_time_at_status_start: Option<TruncatedTimestamp>,
81
81
82 /// Tracked files whose contents have changed since the parent revision
82 /// Tracked files whose contents have changed since the parent revision
83 pub modified: Vec<StatusPath<'a>>,
83 pub modified: Vec<StatusPath<'a>>,
84
84
85 /// Newly-tracked files that were not present in the parent
85 /// Newly-tracked files that were not present in the parent
86 pub added: Vec<StatusPath<'a>>,
86 pub added: Vec<StatusPath<'a>>,
87
87
88 /// Previously-tracked files that have been (re)moved with an hg command
88 /// Previously-tracked files that have been (re)moved with an hg command
89 pub removed: Vec<StatusPath<'a>>,
89 pub removed: Vec<StatusPath<'a>>,
90
90
91 /// (Still) tracked files that are missing, (re)moved with an non-hg
91 /// (Still) tracked files that are missing, (re)moved with an non-hg
92 /// command
92 /// command
93 pub deleted: Vec<StatusPath<'a>>,
93 pub deleted: Vec<StatusPath<'a>>,
94
94
95 /// Tracked files that are up to date with the parent.
95 /// Tracked files that are up to date with the parent.
96 /// Only pupulated if `StatusOptions::list_clean` is true.
96 /// Only pupulated if `StatusOptions::list_clean` is true.
97 pub clean: Vec<StatusPath<'a>>,
97 pub clean: Vec<StatusPath<'a>>,
98
98
99 /// Files in the working directory that are ignored with `.hgignore`.
99 /// Files in the working directory that are ignored with `.hgignore`.
100 /// Only pupulated if `StatusOptions::list_ignored` is true.
100 /// Only pupulated if `StatusOptions::list_ignored` is true.
101 pub ignored: Vec<StatusPath<'a>>,
101 pub ignored: Vec<StatusPath<'a>>,
102
102
103 /// Files in the working directory that are neither tracked nor ignored.
103 /// Files in the working directory that are neither tracked nor ignored.
104 /// Only pupulated if `StatusOptions::list_unknown` is true.
104 /// Only pupulated if `StatusOptions::list_unknown` is true.
105 pub unknown: Vec<StatusPath<'a>>,
105 pub unknown: Vec<StatusPath<'a>>,
106
106
107 /// Was explicitly matched but cannot be found/accessed
107 /// Was explicitly matched but cannot be found/accessed
108 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
108 pub bad: Vec<(HgPathCow<'a>, BadMatch)>,
109
109
110 /// Either clean or modified, but we can’t tell from filesystem metadata
110 /// Either clean or modified, but we can’t tell from filesystem metadata
111 /// alone. The file contents need to be read and compared with that in
111 /// alone. The file contents need to be read and compared with that in
112 /// the parent.
112 /// the parent.
113 pub unsure: Vec<StatusPath<'a>>,
113 pub unsure: Vec<StatusPath<'a>>,
114
114
115 /// Only filled if `collect_traversed_dirs` is `true`
115 /// Only filled if `collect_traversed_dirs` is `true`
116 pub traversed: Vec<HgPathCow<'a>>,
116 pub traversed: Vec<HgPathCow<'a>>,
117
117
118 /// Whether `status()` made changed to the `DirstateMap` that should be
118 /// Whether `status()` made changed to the `DirstateMap` that should be
119 /// written back to disk
119 /// written back to disk
120 pub dirty: bool,
120 pub dirty: bool,
121 }
121 }
122
122
123 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
123 #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
124 pub struct StatusPath<'a> {
124 pub struct StatusPath<'a> {
125 pub path: HgPathCow<'a>,
125 pub path: HgPathCow<'a>,
126 pub copy_source: Option<HgPathCow<'a>>,
126 pub copy_source: Option<HgPathCow<'a>>,
127 }
127 }
128
128
129 #[derive(Debug, derive_more::From)]
129 #[derive(Debug, derive_more::From)]
130 pub enum StatusError {
130 pub enum StatusError {
131 /// An invalid path that cannot be represented in Mercurial was found
131 /// An invalid path that cannot be represented in Mercurial was found
132 Path(HgPathError),
132 Path(HgPathError),
133 /// An invalid "ignore" pattern was found
133 /// An invalid "ignore" pattern was found
134 Pattern(PatternError),
134 Pattern(PatternError),
135 /// Corrupted dirstate
135 /// Corrupted dirstate
136 DirstateV2ParseError(DirstateV2ParseError),
136 DirstateV2ParseError(DirstateV2ParseError),
137 }
137 }
138
138
139 pub type StatusResult<T> = Result<T, StatusError>;
140
141 impl fmt::Display for StatusError {
139 impl fmt::Display for StatusError {
142 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
143 match self {
141 match self {
144 StatusError::Path(error) => error.fmt(f),
142 StatusError::Path(error) => error.fmt(f),
145 StatusError::Pattern(error) => error.fmt(f),
143 StatusError::Pattern(error) => error.fmt(f),
146 StatusError::DirstateV2ParseError(_) => {
144 StatusError::DirstateV2ParseError(_) => {
147 f.write_str("dirstate-v2 parse error")
145 f.write_str("dirstate-v2 parse error")
148 }
146 }
149 }
147 }
150 }
148 }
151 }
149 }
@@ -1,1156 +1,1195 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::path::PathBuf;
4 use std::path::PathBuf;
5
5
6 use super::on_disk;
6 use super::on_disk;
7 use super::on_disk::DirstateV2ParseError;
7 use super::on_disk::DirstateV2ParseError;
8 use super::owning::OwningDirstateMap;
8 use super::owning::OwningDirstateMap;
9 use super::path_with_basename::WithBasename;
9 use super::path_with_basename::WithBasename;
10 use crate::dirstate::parsers::pack_entry;
10 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::packed_entry_size;
11 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::parse_dirstate_entries;
12 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::CopyMapIter;
13 use crate::dirstate::CopyMapIter;
14 use crate::dirstate::StateMapIter;
14 use crate::dirstate::StateMapIter;
15 use crate::dirstate::TruncatedTimestamp;
15 use crate::dirstate::TruncatedTimestamp;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
16 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 use crate::dirstate::SIZE_NON_NORMAL;
17 use crate::dirstate::SIZE_NON_NORMAL;
18 use crate::matchers::Matcher;
18 use crate::matchers::Matcher;
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
19 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::DirstateEntry;
20 use crate::DirstateEntry;
21 use crate::DirstateError;
21 use crate::DirstateError;
22 use crate::DirstateParents;
22 use crate::DirstateParents;
23 use crate::DirstateStatus;
23 use crate::DirstateStatus;
24 use crate::EntryState;
24 use crate::EntryState;
25 use crate::FastHashbrownMap as FastHashMap;
25 use crate::FastHashbrownMap as FastHashMap;
26 use crate::PatternFileWarning;
26 use crate::PatternFileWarning;
27 use crate::StatusError;
27 use crate::StatusError;
28 use crate::StatusOptions;
28 use crate::StatusOptions;
29
29
30 /// Append to an existing data file if the amount of unreachable data (not used
30 /// Append to an existing data file if the amount of unreachable data (not used
31 /// anymore) is less than this fraction of the total amount of existing data.
31 /// anymore) is less than this fraction of the total amount of existing data.
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
32 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33
33
34 pub struct DirstateMap<'on_disk> {
34 pub struct DirstateMap<'on_disk> {
35 /// Contents of the `.hg/dirstate` file
35 /// Contents of the `.hg/dirstate` file
36 pub(super) on_disk: &'on_disk [u8],
36 pub(super) on_disk: &'on_disk [u8],
37
37
38 pub(super) root: ChildNodes<'on_disk>,
38 pub(super) root: ChildNodes<'on_disk>,
39
39
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
40 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 pub(super) nodes_with_entry_count: u32,
41 pub(super) nodes_with_entry_count: u32,
42
42
43 /// Number of nodes anywhere in the tree that have
43 /// Number of nodes anywhere in the tree that have
44 /// `.copy_source.is_some()`.
44 /// `.copy_source.is_some()`.
45 pub(super) nodes_with_copy_source_count: u32,
45 pub(super) nodes_with_copy_source_count: u32,
46
46
47 /// See on_disk::Header
47 /// See on_disk::Header
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
48 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49
49
50 /// How many bytes of `on_disk` are not used anymore
50 /// How many bytes of `on_disk` are not used anymore
51 pub(super) unreachable_bytes: u32,
51 pub(super) unreachable_bytes: u32,
52 }
52 }
53
53
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
54 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// map key would also work: all paths in a given map have the same parent
55 /// map key would also work: all paths in a given map have the same parent
56 /// path, so comparing full paths gives the same result as comparing base
56 /// path, so comparing full paths gives the same result as comparing base
57 /// names. However `HashMap` would waste time always re-hashing the same
57 /// names. However `HashMap` would waste time always re-hashing the same
58 /// string prefix.
58 /// string prefix.
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
59 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60
60
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
61 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
62 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
63 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 InMemory(&'tree HgPathBuf),
64 InMemory(&'tree HgPathBuf),
65 OnDisk(&'on_disk HgPath),
65 OnDisk(&'on_disk HgPath),
66 }
66 }
67
67
68 pub(super) enum ChildNodes<'on_disk> {
68 pub(super) enum ChildNodes<'on_disk> {
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
69 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 OnDisk(&'on_disk [on_disk::Node]),
70 OnDisk(&'on_disk [on_disk::Node]),
71 }
71 }
72
72
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
73 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
74 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 OnDisk(&'on_disk [on_disk::Node]),
75 OnDisk(&'on_disk [on_disk::Node]),
76 }
76 }
77
77
78 pub(super) enum NodeRef<'tree, 'on_disk> {
78 pub(super) enum NodeRef<'tree, 'on_disk> {
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
79 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 OnDisk(&'on_disk on_disk::Node),
80 OnDisk(&'on_disk on_disk::Node),
81 }
81 }
82
82
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
83 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
84 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 match *self {
85 match *self {
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
86 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
87 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 }
88 }
89 }
89 }
90 }
90 }
91
91
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
92 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 type Target = HgPath;
93 type Target = HgPath;
94
94
95 fn deref(&self) -> &HgPath {
95 fn deref(&self) -> &HgPath {
96 match *self {
96 match *self {
97 BorrowedPath::InMemory(in_memory) => in_memory,
97 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
98 BorrowedPath::OnDisk(on_disk) => on_disk,
99 }
99 }
100 }
100 }
101 }
101 }
102
102
103 impl Default for ChildNodes<'_> {
103 impl Default for ChildNodes<'_> {
104 fn default() -> Self {
104 fn default() -> Self {
105 ChildNodes::InMemory(Default::default())
105 ChildNodes::InMemory(Default::default())
106 }
106 }
107 }
107 }
108
108
109 impl<'on_disk> ChildNodes<'on_disk> {
109 impl<'on_disk> ChildNodes<'on_disk> {
110 pub(super) fn as_ref<'tree>(
110 pub(super) fn as_ref<'tree>(
111 &'tree self,
111 &'tree self,
112 ) -> ChildNodesRef<'tree, 'on_disk> {
112 ) -> ChildNodesRef<'tree, 'on_disk> {
113 match self {
113 match self {
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
114 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
115 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 }
116 }
117 }
117 }
118
118
119 pub(super) fn is_empty(&self) -> bool {
119 pub(super) fn is_empty(&self) -> bool {
120 match self {
120 match self {
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
121 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
122 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 }
123 }
124 }
124 }
125
125
126 fn make_mut(
126 fn make_mut(
127 &mut self,
127 &mut self,
128 on_disk: &'on_disk [u8],
128 on_disk: &'on_disk [u8],
129 unreachable_bytes: &mut u32,
129 unreachable_bytes: &mut u32,
130 ) -> Result<
130 ) -> Result<
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
131 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 DirstateV2ParseError,
132 DirstateV2ParseError,
133 > {
133 > {
134 match self {
134 match self {
135 ChildNodes::InMemory(nodes) => Ok(nodes),
135 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::OnDisk(nodes) => {
136 ChildNodes::OnDisk(nodes) => {
137 *unreachable_bytes +=
137 *unreachable_bytes +=
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
138 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 let nodes = nodes
139 let nodes = nodes
140 .iter()
140 .iter()
141 .map(|node| {
141 .map(|node| {
142 Ok((
142 Ok((
143 node.path(on_disk)?,
143 node.path(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
144 node.to_in_memory_node(on_disk)?,
145 ))
145 ))
146 })
146 })
147 .collect::<Result<_, _>>()?;
147 .collect::<Result<_, _>>()?;
148 *self = ChildNodes::InMemory(nodes);
148 *self = ChildNodes::InMemory(nodes);
149 match self {
149 match self {
150 ChildNodes::InMemory(nodes) => Ok(nodes),
150 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::OnDisk(_) => unreachable!(),
151 ChildNodes::OnDisk(_) => unreachable!(),
152 }
152 }
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157
157
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
158 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 pub(super) fn get(
159 pub(super) fn get(
160 &self,
160 &self,
161 base_name: &HgPath,
161 base_name: &HgPath,
162 on_disk: &'on_disk [u8],
162 on_disk: &'on_disk [u8],
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
163 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 match self {
164 match self {
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
165 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 .get_key_value(base_name)
166 .get_key_value(base_name)
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
167 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 ChildNodesRef::OnDisk(nodes) => {
168 ChildNodesRef::OnDisk(nodes) => {
169 let mut parse_result = Ok(());
169 let mut parse_result = Ok(());
170 let search_result = nodes.binary_search_by(|node| {
170 let search_result = nodes.binary_search_by(|node| {
171 match node.base_name(on_disk) {
171 match node.base_name(on_disk) {
172 Ok(node_base_name) => node_base_name.cmp(base_name),
172 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Err(e) => {
173 Err(e) => {
174 parse_result = Err(e);
174 parse_result = Err(e);
175 // Dummy comparison result, `search_result` won’t
175 // Dummy comparison result, `search_result` won’t
176 // be used since `parse_result` is an error
176 // be used since `parse_result` is an error
177 std::cmp::Ordering::Equal
177 std::cmp::Ordering::Equal
178 }
178 }
179 }
179 }
180 });
180 });
181 parse_result.map(|()| {
181 parse_result.map(|()| {
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
182 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 })
183 })
184 }
184 }
185 }
185 }
186 }
186 }
187
187
188 /// Iterate in undefined order
188 /// Iterate in undefined order
189 pub(super) fn iter(
189 pub(super) fn iter(
190 &self,
190 &self,
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
191 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 match self {
192 match self {
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
193 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
194 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 ),
195 ),
196 ChildNodesRef::OnDisk(nodes) => {
196 ChildNodesRef::OnDisk(nodes) => {
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
197 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 }
198 }
199 }
199 }
200 }
200 }
201
201
202 /// Iterate in parallel in undefined order
202 /// Iterate in parallel in undefined order
203 pub(super) fn par_iter(
203 pub(super) fn par_iter(
204 &self,
204 &self,
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
205 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 {
206 {
207 use rayon::prelude::*;
207 use rayon::prelude::*;
208 match self {
208 match self {
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
209 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
210 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 ),
211 ),
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
212 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 nodes.par_iter().map(NodeRef::OnDisk),
213 nodes.par_iter().map(NodeRef::OnDisk),
214 ),
214 ),
215 }
215 }
216 }
216 }
217
217
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
218 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 match self {
219 match self {
220 ChildNodesRef::InMemory(nodes) => {
220 ChildNodesRef::InMemory(nodes) => {
221 let mut vec: Vec<_> = nodes
221 let mut vec: Vec<_> = nodes
222 .iter()
222 .iter()
223 .map(|(k, v)| NodeRef::InMemory(k, v))
223 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .collect();
224 .collect();
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
225 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 match node {
226 match node {
227 NodeRef::InMemory(path, _node) => path.base_name(),
227 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::OnDisk(_) => unreachable!(),
228 NodeRef::OnDisk(_) => unreachable!(),
229 }
229 }
230 }
230 }
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
231 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // value: https://github.com/rust-lang/rust/issues/34162
232 // value: https://github.com/rust-lang/rust/issues/34162
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
233 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec
234 vec
235 }
235 }
236 ChildNodesRef::OnDisk(nodes) => {
236 ChildNodesRef::OnDisk(nodes) => {
237 // Nodes on disk are already sorted
237 // Nodes on disk are already sorted
238 nodes.iter().map(NodeRef::OnDisk).collect()
238 nodes.iter().map(NodeRef::OnDisk).collect()
239 }
239 }
240 }
240 }
241 }
241 }
242 }
242 }
243
243
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
244 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 pub(super) fn full_path(
245 pub(super) fn full_path(
246 &self,
246 &self,
247 on_disk: &'on_disk [u8],
247 on_disk: &'on_disk [u8],
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
248 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 match self {
249 match self {
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
250 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
251 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 }
252 }
253 }
253 }
254
254
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
255 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// HgPath>` detached from `'tree`
256 /// HgPath>` detached from `'tree`
257 pub(super) fn full_path_borrowed(
257 pub(super) fn full_path_borrowed(
258 &self,
258 &self,
259 on_disk: &'on_disk [u8],
259 on_disk: &'on_disk [u8],
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
260 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 match self {
261 match self {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
262 NodeRef::InMemory(path, _node) => match path.full_path() {
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
263 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
264 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 },
265 },
266 NodeRef::OnDisk(node) => {
266 NodeRef::OnDisk(node) => {
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
267 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 }
268 }
269 }
269 }
270 }
270 }
271
271
272 pub(super) fn base_name(
272 pub(super) fn base_name(
273 &self,
273 &self,
274 on_disk: &'on_disk [u8],
274 on_disk: &'on_disk [u8],
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
275 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 match self {
276 match self {
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
277 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
278 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 }
279 }
280 }
280 }
281
281
282 pub(super) fn children(
282 pub(super) fn children(
283 &self,
283 &self,
284 on_disk: &'on_disk [u8],
284 on_disk: &'on_disk [u8],
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
285 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 match self {
286 match self {
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
287 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::OnDisk(node) => {
288 NodeRef::OnDisk(node) => {
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
289 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 }
290 }
291 }
291 }
292 }
292 }
293
293
294 pub(super) fn has_copy_source(&self) -> bool {
294 pub(super) fn has_copy_source(&self) -> bool {
295 match self {
295 match self {
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
296 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
297 NodeRef::OnDisk(node) => node.has_copy_source(),
298 }
298 }
299 }
299 }
300
300
301 pub(super) fn copy_source(
301 pub(super) fn copy_source(
302 &self,
302 &self,
303 on_disk: &'on_disk [u8],
303 on_disk: &'on_disk [u8],
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
304 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 match self {
305 match self {
306 NodeRef::InMemory(_path, node) => {
306 NodeRef::InMemory(_path, node) => {
307 Ok(node.copy_source.as_ref().map(|s| &**s))
307 Ok(node.copy_source.as_ref().map(|s| &**s))
308 }
308 }
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
309 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 }
310 }
311 }
311 }
312 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
312 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
313 /// HgPath>` detached from `'tree`
313 /// HgPath>` detached from `'tree`
314 pub(super) fn copy_source_borrowed(
314 pub(super) fn copy_source_borrowed(
315 &self,
315 &self,
316 on_disk: &'on_disk [u8],
316 on_disk: &'on_disk [u8],
317 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
317 ) -> Result<Option<BorrowedPath<'tree, 'on_disk>>, DirstateV2ParseError>
318 {
318 {
319 Ok(match self {
319 Ok(match self {
320 NodeRef::InMemory(_path, node) => {
320 NodeRef::InMemory(_path, node) => {
321 node.copy_source.as_ref().map(|source| match source {
321 node.copy_source.as_ref().map(|source| match source {
322 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
322 Cow::Borrowed(on_disk) => BorrowedPath::OnDisk(on_disk),
323 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
323 Cow::Owned(in_memory) => BorrowedPath::InMemory(in_memory),
324 })
324 })
325 }
325 }
326 NodeRef::OnDisk(node) => node
326 NodeRef::OnDisk(node) => node
327 .copy_source(on_disk)?
327 .copy_source(on_disk)?
328 .map(|source| BorrowedPath::OnDisk(source)),
328 .map(|source| BorrowedPath::OnDisk(source)),
329 })
329 })
330 }
330 }
331
331
332 pub(super) fn entry(
332 pub(super) fn entry(
333 &self,
333 &self,
334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
334 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
335 match self {
335 match self {
336 NodeRef::InMemory(_path, node) => {
336 NodeRef::InMemory(_path, node) => {
337 Ok(node.data.as_entry().copied())
337 Ok(node.data.as_entry().copied())
338 }
338 }
339 NodeRef::OnDisk(node) => node.entry(),
339 NodeRef::OnDisk(node) => node.entry(),
340 }
340 }
341 }
341 }
342
342
343 pub(super) fn state(
343 pub(super) fn state(
344 &self,
344 &self,
345 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
345 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
346 Ok(self.entry()?.map(|e| e.state()))
346 Ok(self.entry()?.map(|e| e.state()))
347 }
347 }
348
348
349 pub(super) fn cached_directory_mtime(
349 pub(super) fn cached_directory_mtime(
350 &self,
350 &self,
351 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
351 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
352 match self {
352 match self {
353 NodeRef::InMemory(_path, node) => Ok(match node.data {
353 NodeRef::InMemory(_path, node) => Ok(match node.data {
354 NodeData::CachedDirectory { mtime } => Some(mtime),
354 NodeData::CachedDirectory { mtime } => Some(mtime),
355 _ => None,
355 _ => None,
356 }),
356 }),
357 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
357 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
358 }
358 }
359 }
359 }
360
360
361 pub(super) fn descendants_with_entry_count(&self) -> u32 {
361 pub(super) fn descendants_with_entry_count(&self) -> u32 {
362 match self {
362 match self {
363 NodeRef::InMemory(_path, node) => {
363 NodeRef::InMemory(_path, node) => {
364 node.descendants_with_entry_count
364 node.descendants_with_entry_count
365 }
365 }
366 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
366 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
367 }
367 }
368 }
368 }
369
369
370 pub(super) fn tracked_descendants_count(&self) -> u32 {
370 pub(super) fn tracked_descendants_count(&self) -> u32 {
371 match self {
371 match self {
372 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
372 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
373 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
373 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
374 }
374 }
375 }
375 }
376 }
376 }
377
377
378 /// Represents a file or a directory
378 /// Represents a file or a directory
379 #[derive(Default)]
379 #[derive(Default)]
380 pub(super) struct Node<'on_disk> {
380 pub(super) struct Node<'on_disk> {
381 pub(super) data: NodeData,
381 pub(super) data: NodeData,
382
382
383 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
383 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
384
384
385 pub(super) children: ChildNodes<'on_disk>,
385 pub(super) children: ChildNodes<'on_disk>,
386
386
387 /// How many (non-inclusive) descendants of this node have an entry.
387 /// How many (non-inclusive) descendants of this node have an entry.
388 pub(super) descendants_with_entry_count: u32,
388 pub(super) descendants_with_entry_count: u32,
389
389
390 /// How many (non-inclusive) descendants of this node have an entry whose
390 /// How many (non-inclusive) descendants of this node have an entry whose
391 /// state is "tracked".
391 /// state is "tracked".
392 pub(super) tracked_descendants_count: u32,
392 pub(super) tracked_descendants_count: u32,
393 }
393 }
394
394
395 pub(super) enum NodeData {
395 pub(super) enum NodeData {
396 Entry(DirstateEntry),
396 Entry(DirstateEntry),
397 CachedDirectory { mtime: TruncatedTimestamp },
397 CachedDirectory { mtime: TruncatedTimestamp },
398 None,
398 None,
399 }
399 }
400
400
401 impl Default for NodeData {
401 impl Default for NodeData {
402 fn default() -> Self {
402 fn default() -> Self {
403 NodeData::None
403 NodeData::None
404 }
404 }
405 }
405 }
406
406
407 impl NodeData {
407 impl NodeData {
408 fn has_entry(&self) -> bool {
408 fn has_entry(&self) -> bool {
409 match self {
409 match self {
410 NodeData::Entry(_) => true,
410 NodeData::Entry(_) => true,
411 _ => false,
411 _ => false,
412 }
412 }
413 }
413 }
414
414
415 fn as_entry(&self) -> Option<&DirstateEntry> {
415 fn as_entry(&self) -> Option<&DirstateEntry> {
416 match self {
416 match self {
417 NodeData::Entry(entry) => Some(entry),
417 NodeData::Entry(entry) => Some(entry),
418 _ => None,
418 _ => None,
419 }
419 }
420 }
420 }
421 }
421 }
422
422
423 impl<'on_disk> DirstateMap<'on_disk> {
423 impl<'on_disk> DirstateMap<'on_disk> {
424 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
424 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
425 Self {
425 Self {
426 on_disk,
426 on_disk,
427 root: ChildNodes::default(),
427 root: ChildNodes::default(),
428 nodes_with_entry_count: 0,
428 nodes_with_entry_count: 0,
429 nodes_with_copy_source_count: 0,
429 nodes_with_copy_source_count: 0,
430 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
430 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
431 unreachable_bytes: 0,
431 unreachable_bytes: 0,
432 }
432 }
433 }
433 }
434
434
435 #[timed]
435 #[timed]
436 pub fn new_v2(
436 pub fn new_v2(
437 on_disk: &'on_disk [u8],
437 on_disk: &'on_disk [u8],
438 data_size: usize,
438 data_size: usize,
439 metadata: &[u8],
439 metadata: &[u8],
440 ) -> Result<Self, DirstateError> {
440 ) -> Result<Self, DirstateError> {
441 if let Some(data) = on_disk.get(..data_size) {
441 if let Some(data) = on_disk.get(..data_size) {
442 Ok(on_disk::read(data, metadata)?)
442 Ok(on_disk::read(data, metadata)?)
443 } else {
443 } else {
444 Err(DirstateV2ParseError.into())
444 Err(DirstateV2ParseError.into())
445 }
445 }
446 }
446 }
447
447
448 #[timed]
448 #[timed]
449 pub fn new_v1(
449 pub fn new_v1(
450 on_disk: &'on_disk [u8],
450 on_disk: &'on_disk [u8],
451 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
451 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
452 let mut map = Self::empty(on_disk);
452 let mut map = Self::empty(on_disk);
453 if map.on_disk.is_empty() {
453 if map.on_disk.is_empty() {
454 return Ok((map, None));
454 return Ok((map, None));
455 }
455 }
456
456
457 let parents = parse_dirstate_entries(
457 let parents = parse_dirstate_entries(
458 map.on_disk,
458 map.on_disk,
459 |path, entry, copy_source| {
459 |path, entry, copy_source| {
460 let tracked = entry.state().is_tracked();
460 let tracked = entry.state().is_tracked();
461 let node = Self::get_or_insert_node(
461 let node = Self::get_or_insert_node(
462 map.on_disk,
462 map.on_disk,
463 &mut map.unreachable_bytes,
463 &mut map.unreachable_bytes,
464 &mut map.root,
464 &mut map.root,
465 path,
465 path,
466 WithBasename::to_cow_borrowed,
466 WithBasename::to_cow_borrowed,
467 |ancestor| {
467 |ancestor| {
468 if tracked {
468 if tracked {
469 ancestor.tracked_descendants_count += 1
469 ancestor.tracked_descendants_count += 1
470 }
470 }
471 ancestor.descendants_with_entry_count += 1
471 ancestor.descendants_with_entry_count += 1
472 },
472 },
473 )?;
473 )?;
474 assert!(
474 assert!(
475 !node.data.has_entry(),
475 !node.data.has_entry(),
476 "duplicate dirstate entry in read"
476 "duplicate dirstate entry in read"
477 );
477 );
478 assert!(
478 assert!(
479 node.copy_source.is_none(),
479 node.copy_source.is_none(),
480 "duplicate dirstate entry in read"
480 "duplicate dirstate entry in read"
481 );
481 );
482 node.data = NodeData::Entry(*entry);
482 node.data = NodeData::Entry(*entry);
483 node.copy_source = copy_source.map(Cow::Borrowed);
483 node.copy_source = copy_source.map(Cow::Borrowed);
484 map.nodes_with_entry_count += 1;
484 map.nodes_with_entry_count += 1;
485 if copy_source.is_some() {
485 if copy_source.is_some() {
486 map.nodes_with_copy_source_count += 1
486 map.nodes_with_copy_source_count += 1
487 }
487 }
488 Ok(())
488 Ok(())
489 },
489 },
490 )?;
490 )?;
491 let parents = Some(parents.clone());
491 let parents = Some(parents.clone());
492
492
493 Ok((map, parents))
493 Ok((map, parents))
494 }
494 }
495
495
496 /// Assuming dirstate-v2 format, returns whether the next write should
496 /// Assuming dirstate-v2 format, returns whether the next write should
497 /// append to the existing data file that contains `self.on_disk` (true),
497 /// append to the existing data file that contains `self.on_disk` (true),
498 /// or create a new data file from scratch (false).
498 /// or create a new data file from scratch (false).
499 pub(super) fn write_should_append(&self) -> bool {
499 pub(super) fn write_should_append(&self) -> bool {
500 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
500 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
501 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
501 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
502 }
502 }
503
503
504 fn get_node<'tree>(
504 fn get_node<'tree>(
505 &'tree self,
505 &'tree self,
506 path: &HgPath,
506 path: &HgPath,
507 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
507 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
508 let mut children = self.root.as_ref();
508 let mut children = self.root.as_ref();
509 let mut components = path.components();
509 let mut components = path.components();
510 let mut component =
510 let mut component =
511 components.next().expect("expected at least one components");
511 components.next().expect("expected at least one components");
512 loop {
512 loop {
513 if let Some(child) = children.get(component, self.on_disk)? {
513 if let Some(child) = children.get(component, self.on_disk)? {
514 if let Some(next_component) = components.next() {
514 if let Some(next_component) = components.next() {
515 component = next_component;
515 component = next_component;
516 children = child.children(self.on_disk)?;
516 children = child.children(self.on_disk)?;
517 } else {
517 } else {
518 return Ok(Some(child));
518 return Ok(Some(child));
519 }
519 }
520 } else {
520 } else {
521 return Ok(None);
521 return Ok(None);
522 }
522 }
523 }
523 }
524 }
524 }
525
525
526 /// Returns a mutable reference to the node at `path` if it exists
526 /// Returns a mutable reference to the node at `path` if it exists
527 ///
527 ///
528 /// This takes `root` instead of `&mut self` so that callers can mutate
528 /// This takes `root` instead of `&mut self` so that callers can mutate
529 /// other fields while the returned borrow is still valid
529 /// other fields while the returned borrow is still valid
530 fn get_node_mut<'tree>(
530 fn get_node_mut<'tree>(
531 on_disk: &'on_disk [u8],
531 on_disk: &'on_disk [u8],
532 unreachable_bytes: &mut u32,
532 unreachable_bytes: &mut u32,
533 root: &'tree mut ChildNodes<'on_disk>,
533 root: &'tree mut ChildNodes<'on_disk>,
534 path: &HgPath,
534 path: &HgPath,
535 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
535 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
536 let mut children = root;
536 let mut children = root;
537 let mut components = path.components();
537 let mut components = path.components();
538 let mut component =
538 let mut component =
539 components.next().expect("expected at least one components");
539 components.next().expect("expected at least one components");
540 loop {
540 loop {
541 if let Some(child) = children
541 if let Some(child) = children
542 .make_mut(on_disk, unreachable_bytes)?
542 .make_mut(on_disk, unreachable_bytes)?
543 .get_mut(component)
543 .get_mut(component)
544 {
544 {
545 if let Some(next_component) = components.next() {
545 if let Some(next_component) = components.next() {
546 component = next_component;
546 component = next_component;
547 children = &mut child.children;
547 children = &mut child.children;
548 } else {
548 } else {
549 return Ok(Some(child));
549 return Ok(Some(child));
550 }
550 }
551 } else {
551 } else {
552 return Ok(None);
552 return Ok(None);
553 }
553 }
554 }
554 }
555 }
555 }
556
556
557 pub(super) fn get_or_insert<'tree, 'path>(
557 pub(super) fn get_or_insert<'tree, 'path>(
558 &'tree mut self,
558 &'tree mut self,
559 path: &HgPath,
559 path: &HgPath,
560 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
560 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
561 Self::get_or_insert_node(
561 Self::get_or_insert_node(
562 self.on_disk,
562 self.on_disk,
563 &mut self.unreachable_bytes,
563 &mut self.unreachable_bytes,
564 &mut self.root,
564 &mut self.root,
565 path,
565 path,
566 WithBasename::to_cow_owned,
566 WithBasename::to_cow_owned,
567 |_| {},
567 |_| {},
568 )
568 )
569 }
569 }
570
570
571 fn get_or_insert_node<'tree, 'path>(
571 fn get_or_insert_node<'tree, 'path>(
572 on_disk: &'on_disk [u8],
572 on_disk: &'on_disk [u8],
573 unreachable_bytes: &mut u32,
573 unreachable_bytes: &mut u32,
574 root: &'tree mut ChildNodes<'on_disk>,
574 root: &'tree mut ChildNodes<'on_disk>,
575 path: &'path HgPath,
575 path: &'path HgPath,
576 to_cow: impl Fn(
576 to_cow: impl Fn(
577 WithBasename<&'path HgPath>,
577 WithBasename<&'path HgPath>,
578 ) -> WithBasename<Cow<'on_disk, HgPath>>,
578 ) -> WithBasename<Cow<'on_disk, HgPath>>,
579 mut each_ancestor: impl FnMut(&mut Node),
579 mut each_ancestor: impl FnMut(&mut Node),
580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
580 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
581 let mut child_nodes = root;
581 let mut child_nodes = root;
582 let mut inclusive_ancestor_paths =
582 let mut inclusive_ancestor_paths =
583 WithBasename::inclusive_ancestors_of(path);
583 WithBasename::inclusive_ancestors_of(path);
584 let mut ancestor_path = inclusive_ancestor_paths
584 let mut ancestor_path = inclusive_ancestor_paths
585 .next()
585 .next()
586 .expect("expected at least one inclusive ancestor");
586 .expect("expected at least one inclusive ancestor");
587 loop {
587 loop {
588 let (_, child_node) = child_nodes
588 let (_, child_node) = child_nodes
589 .make_mut(on_disk, unreachable_bytes)?
589 .make_mut(on_disk, unreachable_bytes)?
590 .raw_entry_mut()
590 .raw_entry_mut()
591 .from_key(ancestor_path.base_name())
591 .from_key(ancestor_path.base_name())
592 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
592 .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
593 if let Some(next) = inclusive_ancestor_paths.next() {
593 if let Some(next) = inclusive_ancestor_paths.next() {
594 each_ancestor(child_node);
594 each_ancestor(child_node);
595 ancestor_path = next;
595 ancestor_path = next;
596 child_nodes = &mut child_node.children;
596 child_nodes = &mut child_node.children;
597 } else {
597 } else {
598 return Ok(child_node);
598 return Ok(child_node);
599 }
599 }
600 }
600 }
601 }
601 }
602
602
603 fn add_or_remove_file(
603 fn add_or_remove_file(
604 &mut self,
604 &mut self,
605 path: &HgPath,
605 path: &HgPath,
606 old_state: Option<EntryState>,
606 old_state: Option<EntryState>,
607 new_entry: DirstateEntry,
607 new_entry: DirstateEntry,
608 ) -> Result<(), DirstateV2ParseError> {
608 ) -> Result<(), DirstateV2ParseError> {
609 let had_entry = old_state.is_some();
609 let had_entry = old_state.is_some();
610 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
610 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
611 let tracked_count_increment =
611 let tracked_count_increment =
612 match (was_tracked, new_entry.state().is_tracked()) {
612 match (was_tracked, new_entry.state().is_tracked()) {
613 (false, true) => 1,
613 (false, true) => 1,
614 (true, false) => -1,
614 (true, false) => -1,
615 _ => 0,
615 _ => 0,
616 };
616 };
617
617
618 let node = Self::get_or_insert_node(
618 let node = Self::get_or_insert_node(
619 self.on_disk,
619 self.on_disk,
620 &mut self.unreachable_bytes,
620 &mut self.unreachable_bytes,
621 &mut self.root,
621 &mut self.root,
622 path,
622 path,
623 WithBasename::to_cow_owned,
623 WithBasename::to_cow_owned,
624 |ancestor| {
624 |ancestor| {
625 if !had_entry {
625 if !had_entry {
626 ancestor.descendants_with_entry_count += 1;
626 ancestor.descendants_with_entry_count += 1;
627 }
627 }
628
628
629 // We can’t use `+= increment` because the counter is unsigned,
629 // We can’t use `+= increment` because the counter is unsigned,
630 // and we want debug builds to detect accidental underflow
630 // and we want debug builds to detect accidental underflow
631 // through zero
631 // through zero
632 match tracked_count_increment {
632 match tracked_count_increment {
633 1 => ancestor.tracked_descendants_count += 1,
633 1 => ancestor.tracked_descendants_count += 1,
634 -1 => ancestor.tracked_descendants_count -= 1,
634 -1 => ancestor.tracked_descendants_count -= 1,
635 _ => {}
635 _ => {}
636 }
636 }
637 },
637 },
638 )?;
638 )?;
639 if !had_entry {
639 if !had_entry {
640 self.nodes_with_entry_count += 1
640 self.nodes_with_entry_count += 1
641 }
641 }
642 node.data = NodeData::Entry(new_entry);
642 node.data = NodeData::Entry(new_entry);
643 Ok(())
643 Ok(())
644 }
644 }
645
645
646 fn iter_nodes<'tree>(
646 fn iter_nodes<'tree>(
647 &'tree self,
647 &'tree self,
648 ) -> impl Iterator<
648 ) -> impl Iterator<
649 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
649 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
650 > + 'tree {
650 > + 'tree {
651 // Depth first tree traversal.
651 // Depth first tree traversal.
652 //
652 //
653 // If we could afford internal iteration and recursion,
653 // If we could afford internal iteration and recursion,
654 // this would look like:
654 // this would look like:
655 //
655 //
656 // ```
656 // ```
657 // fn traverse_children(
657 // fn traverse_children(
658 // children: &ChildNodes,
658 // children: &ChildNodes,
659 // each: &mut impl FnMut(&Node),
659 // each: &mut impl FnMut(&Node),
660 // ) {
660 // ) {
661 // for child in children.values() {
661 // for child in children.values() {
662 // traverse_children(&child.children, each);
662 // traverse_children(&child.children, each);
663 // each(child);
663 // each(child);
664 // }
664 // }
665 // }
665 // }
666 // ```
666 // ```
667 //
667 //
668 // However we want an external iterator and therefore can’t use the
668 // However we want an external iterator and therefore can’t use the
669 // call stack. Use an explicit stack instead:
669 // call stack. Use an explicit stack instead:
670 let mut stack = Vec::new();
670 let mut stack = Vec::new();
671 let mut iter = self.root.as_ref().iter();
671 let mut iter = self.root.as_ref().iter();
672 std::iter::from_fn(move || {
672 std::iter::from_fn(move || {
673 while let Some(child_node) = iter.next() {
673 while let Some(child_node) = iter.next() {
674 let children = match child_node.children(self.on_disk) {
674 let children = match child_node.children(self.on_disk) {
675 Ok(children) => children,
675 Ok(children) => children,
676 Err(error) => return Some(Err(error)),
676 Err(error) => return Some(Err(error)),
677 };
677 };
678 // Pseudo-recursion
678 // Pseudo-recursion
679 let new_iter = children.iter();
679 let new_iter = children.iter();
680 let old_iter = std::mem::replace(&mut iter, new_iter);
680 let old_iter = std::mem::replace(&mut iter, new_iter);
681 stack.push((child_node, old_iter));
681 stack.push((child_node, old_iter));
682 }
682 }
683 // Found the end of a `children.iter()` iterator.
683 // Found the end of a `children.iter()` iterator.
684 if let Some((child_node, next_iter)) = stack.pop() {
684 if let Some((child_node, next_iter)) = stack.pop() {
685 // "Return" from pseudo-recursion by restoring state from the
685 // "Return" from pseudo-recursion by restoring state from the
686 // explicit stack
686 // explicit stack
687 iter = next_iter;
687 iter = next_iter;
688
688
689 Some(Ok(child_node))
689 Some(Ok(child_node))
690 } else {
690 } else {
691 // Reached the bottom of the stack, we’re done
691 // Reached the bottom of the stack, we’re done
692 None
692 None
693 }
693 }
694 })
694 })
695 }
695 }
696
696
697 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
697 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
698 if let Cow::Borrowed(path) = path {
698 if let Cow::Borrowed(path) = path {
699 *unreachable_bytes += path.len() as u32
699 *unreachable_bytes += path.len() as u32
700 }
700 }
701 }
701 }
702 }
702 }
703
703
704 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
704 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
705 ///
705 ///
706 /// The callback is only called for incoming `Ok` values. Errors are passed
706 /// The callback is only called for incoming `Ok` values. Errors are passed
707 /// through as-is. In order to let it use the `?` operator the callback is
707 /// through as-is. In order to let it use the `?` operator the callback is
708 /// expected to return a `Result` of `Option`, instead of an `Option` of
708 /// expected to return a `Result` of `Option`, instead of an `Option` of
709 /// `Result`.
709 /// `Result`.
710 fn filter_map_results<'a, I, F, A, B, E>(
710 fn filter_map_results<'a, I, F, A, B, E>(
711 iter: I,
711 iter: I,
712 f: F,
712 f: F,
713 ) -> impl Iterator<Item = Result<B, E>> + 'a
713 ) -> impl Iterator<Item = Result<B, E>> + 'a
714 where
714 where
715 I: Iterator<Item = Result<A, E>> + 'a,
715 I: Iterator<Item = Result<A, E>> + 'a,
716 F: Fn(A) -> Result<Option<B>, E> + 'a,
716 F: Fn(A) -> Result<Option<B>, E> + 'a,
717 {
717 {
718 iter.filter_map(move |result| match result {
718 iter.filter_map(move |result| match result {
719 Ok(node) => f(node).transpose(),
719 Ok(node) => f(node).transpose(),
720 Err(e) => Some(Err(e)),
720 Err(e) => Some(Err(e)),
721 })
721 })
722 }
722 }
723
723
724 impl OwningDirstateMap {
724 impl OwningDirstateMap {
725 pub fn clear(&mut self) {
725 pub fn clear(&mut self) {
726 let map = self.get_map_mut();
726 self.with_dmap_mut(|map| {
727 map.root = Default::default();
727 map.root = Default::default();
728 map.nodes_with_entry_count = 0;
728 map.nodes_with_entry_count = 0;
729 map.nodes_with_copy_source_count = 0;
729 map.nodes_with_copy_source_count = 0;
730 });
730 }
731 }
731
732
732 pub fn set_entry(
733 pub fn set_entry(
733 &mut self,
734 &mut self,
734 filename: &HgPath,
735 filename: &HgPath,
735 entry: DirstateEntry,
736 entry: DirstateEntry,
736 ) -> Result<(), DirstateV2ParseError> {
737 ) -> Result<(), DirstateV2ParseError> {
737 let map = self.get_map_mut();
738 self.with_dmap_mut(|map| {
738 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
739 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
739 Ok(())
740 Ok(())
741 })
740 }
742 }
741
743
742 pub fn add_file(
744 pub fn add_file(
743 &mut self,
745 &mut self,
744 filename: &HgPath,
746 filename: &HgPath,
745 entry: DirstateEntry,
747 entry: DirstateEntry,
746 ) -> Result<(), DirstateError> {
748 ) -> Result<(), DirstateError> {
747 let old_state = self.get(filename)?.map(|e| e.state());
749 let old_state = self.get(filename)?.map(|e| e.state());
748 let map = self.get_map_mut();
750 self.with_dmap_mut(|map| {
749 Ok(map.add_or_remove_file(filename, old_state, entry)?)
751 Ok(map.add_or_remove_file(filename, old_state, entry)?)
752 })
750 }
753 }
751
754
752 pub fn remove_file(
755 pub fn remove_file(
753 &mut self,
756 &mut self,
754 filename: &HgPath,
757 filename: &HgPath,
755 in_merge: bool,
758 in_merge: bool,
756 ) -> Result<(), DirstateError> {
759 ) -> Result<(), DirstateError> {
757 let old_entry_opt = self.get(filename)?;
760 let old_entry_opt = self.get(filename)?;
758 let old_state = old_entry_opt.map(|e| e.state());
761 let old_state = old_entry_opt.map(|e| e.state());
759 let mut size = 0;
762 let mut size = 0;
760 if in_merge {
763 if in_merge {
761 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
764 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
762 // during a merge. So I (marmoute) am not sure we need the
765 // during a merge. So I (marmoute) am not sure we need the
763 // conditionnal at all. Adding double checking this with assert
766 // conditionnal at all. Adding double checking this with assert
764 // would be nice.
767 // would be nice.
765 if let Some(old_entry) = old_entry_opt {
768 if let Some(old_entry) = old_entry_opt {
766 // backup the previous state
769 // backup the previous state
767 if old_entry.state() == EntryState::Merged {
770 if old_entry.state() == EntryState::Merged {
768 size = SIZE_NON_NORMAL;
771 size = SIZE_NON_NORMAL;
769 } else if old_entry.state() == EntryState::Normal
772 } else if old_entry.state() == EntryState::Normal
770 && old_entry.size() == SIZE_FROM_OTHER_PARENT
773 && old_entry.size() == SIZE_FROM_OTHER_PARENT
771 {
774 {
772 // other parent
775 // other parent
773 size = SIZE_FROM_OTHER_PARENT;
776 size = SIZE_FROM_OTHER_PARENT;
774 }
777 }
775 }
778 }
776 }
779 }
777 if size == 0 {
780 if size == 0 {
778 self.copy_map_remove(filename)?;
781 self.copy_map_remove(filename)?;
779 }
782 }
780 let map = self.get_map_mut();
783 self.with_dmap_mut(|map| {
781 let entry = DirstateEntry::new_removed(size);
784 let entry = DirstateEntry::new_removed(size);
782 Ok(map.add_or_remove_file(filename, old_state, entry)?)
785 Ok(map.add_or_remove_file(filename, old_state, entry)?)
786 })
783 }
787 }
784
788
785 pub fn drop_entry_and_copy_source(
789 pub fn drop_entry_and_copy_source(
786 &mut self,
790 &mut self,
787 filename: &HgPath,
791 filename: &HgPath,
788 ) -> Result<(), DirstateError> {
792 ) -> Result<(), DirstateError> {
789 let was_tracked = self
793 let was_tracked = self
790 .get(filename)?
794 .get(filename)?
791 .map_or(false, |e| e.state().is_tracked());
795 .map_or(false, |e| e.state().is_tracked());
792 let map = self.get_map_mut();
793 struct Dropped {
796 struct Dropped {
794 was_tracked: bool,
797 was_tracked: bool,
795 had_entry: bool,
798 had_entry: bool,
796 had_copy_source: bool,
799 had_copy_source: bool,
797 }
800 }
798
801
799 /// If this returns `Ok(Some((dropped, removed)))`, then
802 /// If this returns `Ok(Some((dropped, removed)))`, then
800 ///
803 ///
801 /// * `dropped` is about the leaf node that was at `filename`
804 /// * `dropped` is about the leaf node that was at `filename`
802 /// * `removed` is whether this particular level of recursion just
805 /// * `removed` is whether this particular level of recursion just
803 /// removed a node in `nodes`.
806 /// removed a node in `nodes`.
804 fn recur<'on_disk>(
807 fn recur<'on_disk>(
805 on_disk: &'on_disk [u8],
808 on_disk: &'on_disk [u8],
806 unreachable_bytes: &mut u32,
809 unreachable_bytes: &mut u32,
807 nodes: &mut ChildNodes<'on_disk>,
810 nodes: &mut ChildNodes<'on_disk>,
808 path: &HgPath,
811 path: &HgPath,
809 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
812 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
810 let (first_path_component, rest_of_path) =
813 let (first_path_component, rest_of_path) =
811 path.split_first_component();
814 path.split_first_component();
812 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
815 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
813 let node = if let Some(node) = nodes.get_mut(first_path_component)
816 let node = if let Some(node) = nodes.get_mut(first_path_component)
814 {
817 {
815 node
818 node
816 } else {
819 } else {
817 return Ok(None);
820 return Ok(None);
818 };
821 };
819 let dropped;
822 let dropped;
820 if let Some(rest) = rest_of_path {
823 if let Some(rest) = rest_of_path {
821 if let Some((d, removed)) = recur(
824 if let Some((d, removed)) = recur(
822 on_disk,
825 on_disk,
823 unreachable_bytes,
826 unreachable_bytes,
824 &mut node.children,
827 &mut node.children,
825 rest,
828 rest,
826 )? {
829 )? {
827 dropped = d;
830 dropped = d;
828 if dropped.had_entry {
831 if dropped.had_entry {
829 node.descendants_with_entry_count -= 1;
832 node.descendants_with_entry_count = node
833 .descendants_with_entry_count
834 .checked_sub(1)
835 .expect(
836 "descendants_with_entry_count should be >= 0",
837 );
830 }
838 }
831 if dropped.was_tracked {
839 if dropped.was_tracked {
832 node.tracked_descendants_count -= 1;
840 node.tracked_descendants_count = node
841 .tracked_descendants_count
842 .checked_sub(1)
843 .expect(
844 "tracked_descendants_count should be >= 0",
845 );
833 }
846 }
834
847
835 // Directory caches must be invalidated when removing a
848 // Directory caches must be invalidated when removing a
836 // child node
849 // child node
837 if removed {
850 if removed {
838 if let NodeData::CachedDirectory { .. } = &node.data {
851 if let NodeData::CachedDirectory { .. } = &node.data {
839 node.data = NodeData::None
852 node.data = NodeData::None
840 }
853 }
841 }
854 }
842 } else {
855 } else {
843 return Ok(None);
856 return Ok(None);
844 }
857 }
845 } else {
858 } else {
846 let had_entry = node.data.has_entry();
859 let entry = node.data.as_entry();
860 let was_tracked = entry.map_or(false, |entry| entry.tracked());
861 let had_entry = entry.is_some();
847 if had_entry {
862 if had_entry {
848 node.data = NodeData::None
863 node.data = NodeData::None
849 }
864 }
865 let mut had_copy_source = false;
850 if let Some(source) = &node.copy_source {
866 if let Some(source) = &node.copy_source {
851 DirstateMap::count_dropped_path(unreachable_bytes, source);
867 DirstateMap::count_dropped_path(unreachable_bytes, source);
868 had_copy_source = true;
852 node.copy_source = None
869 node.copy_source = None
853 }
870 }
854 dropped = Dropped {
871 dropped = Dropped {
855 was_tracked: node
872 was_tracked,
856 .data
857 .as_entry()
858 .map_or(false, |entry| entry.state().is_tracked()),
859 had_entry,
873 had_entry,
860 had_copy_source: node.copy_source.take().is_some(),
874 had_copy_source,
861 };
875 };
862 }
876 }
863 // After recursion, for both leaf (rest_of_path is None) nodes and
877 // After recursion, for both leaf (rest_of_path is None) nodes and
864 // parent nodes, remove a node if it just became empty.
878 // parent nodes, remove a node if it just became empty.
865 let remove = !node.data.has_entry()
879 let remove = !node.data.has_entry()
866 && node.copy_source.is_none()
880 && node.copy_source.is_none()
867 && node.children.is_empty();
881 && node.children.is_empty();
868 if remove {
882 if remove {
869 let (key, _) =
883 let (key, _) =
870 nodes.remove_entry(first_path_component).unwrap();
884 nodes.remove_entry(first_path_component).unwrap();
871 DirstateMap::count_dropped_path(
885 DirstateMap::count_dropped_path(
872 unreachable_bytes,
886 unreachable_bytes,
873 key.full_path(),
887 key.full_path(),
874 )
888 )
875 }
889 }
876 Ok(Some((dropped, remove)))
890 Ok(Some((dropped, remove)))
877 }
891 }
878
892
879 if let Some((dropped, _removed)) = recur(
893 self.with_dmap_mut(|map| {
880 map.on_disk,
894 if let Some((dropped, _removed)) = recur(
881 &mut map.unreachable_bytes,
895 map.on_disk,
882 &mut map.root,
896 &mut map.unreachable_bytes,
883 filename,
897 &mut map.root,
884 )? {
898 filename,
885 if dropped.had_entry {
899 )? {
886 map.nodes_with_entry_count -= 1
900 if dropped.had_entry {
901 map.nodes_with_entry_count = map
902 .nodes_with_entry_count
903 .checked_sub(1)
904 .expect("nodes_with_entry_count should be >= 0");
905 }
906 if dropped.had_copy_source {
907 map.nodes_with_copy_source_count = map
908 .nodes_with_copy_source_count
909 .checked_sub(1)
910 .expect("nodes_with_copy_source_count should be >= 0");
911 }
912 } else {
913 debug_assert!(!was_tracked);
887 }
914 }
888 if dropped.had_copy_source {
915 Ok(())
889 map.nodes_with_copy_source_count -= 1
916 })
890 }
891 } else {
892 debug_assert!(!was_tracked);
893 }
894 Ok(())
895 }
917 }
896
918
897 pub fn has_tracked_dir(
919 pub fn has_tracked_dir(
898 &mut self,
920 &mut self,
899 directory: &HgPath,
921 directory: &HgPath,
900 ) -> Result<bool, DirstateError> {
922 ) -> Result<bool, DirstateError> {
901 let map = self.get_map_mut();
923 self.with_dmap_mut(|map| {
902 if let Some(node) = map.get_node(directory)? {
924 if let Some(node) = map.get_node(directory)? {
903 // A node without a `DirstateEntry` was created to hold child
925 // A node without a `DirstateEntry` was created to hold child
904 // nodes, and is therefore a directory.
926 // nodes, and is therefore a directory.
905 let state = node.state()?;
927 let state = node.state()?;
906 Ok(state.is_none() && node.tracked_descendants_count() > 0)
928 Ok(state.is_none() && node.tracked_descendants_count() > 0)
907 } else {
929 } else {
908 Ok(false)
930 Ok(false)
909 }
931 }
932 })
910 }
933 }
911
934
912 pub fn has_dir(
935 pub fn has_dir(
913 &mut self,
936 &mut self,
914 directory: &HgPath,
937 directory: &HgPath,
915 ) -> Result<bool, DirstateError> {
938 ) -> Result<bool, DirstateError> {
916 let map = self.get_map_mut();
939 self.with_dmap_mut(|map| {
917 if let Some(node) = map.get_node(directory)? {
940 if let Some(node) = map.get_node(directory)? {
918 // A node without a `DirstateEntry` was created to hold child
941 // A node without a `DirstateEntry` was created to hold child
919 // nodes, and is therefore a directory.
942 // nodes, and is therefore a directory.
920 let state = node.state()?;
943 let state = node.state()?;
921 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
944 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
922 } else {
945 } else {
923 Ok(false)
946 Ok(false)
924 }
947 }
948 })
925 }
949 }
926
950
927 #[timed]
951 #[timed]
928 pub fn pack_v1(
952 pub fn pack_v1(
929 &self,
953 &self,
930 parents: DirstateParents,
954 parents: DirstateParents,
931 ) -> Result<Vec<u8>, DirstateError> {
955 ) -> Result<Vec<u8>, DirstateError> {
932 let map = self.get_map();
956 let map = self.get_map();
933 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
957 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
934 // reallocations
958 // reallocations
935 let mut size = parents.as_bytes().len();
959 let mut size = parents.as_bytes().len();
936 for node in map.iter_nodes() {
960 for node in map.iter_nodes() {
937 let node = node?;
961 let node = node?;
938 if node.entry()?.is_some() {
962 if node.entry()?.is_some() {
939 size += packed_entry_size(
963 size += packed_entry_size(
940 node.full_path(map.on_disk)?,
964 node.full_path(map.on_disk)?,
941 node.copy_source(map.on_disk)?,
965 node.copy_source(map.on_disk)?,
942 );
966 );
943 }
967 }
944 }
968 }
945
969
946 let mut packed = Vec::with_capacity(size);
970 let mut packed = Vec::with_capacity(size);
947 packed.extend(parents.as_bytes());
971 packed.extend(parents.as_bytes());
948
972
949 for node in map.iter_nodes() {
973 for node in map.iter_nodes() {
950 let node = node?;
974 let node = node?;
951 if let Some(entry) = node.entry()? {
975 if let Some(entry) = node.entry()? {
952 pack_entry(
976 pack_entry(
953 node.full_path(map.on_disk)?,
977 node.full_path(map.on_disk)?,
954 &entry,
978 &entry,
955 node.copy_source(map.on_disk)?,
979 node.copy_source(map.on_disk)?,
956 &mut packed,
980 &mut packed,
957 );
981 );
958 }
982 }
959 }
983 }
960 Ok(packed)
984 Ok(packed)
961 }
985 }
962
986
963 /// Returns new data and metadata together with whether that data should be
987 /// Returns new data and metadata together with whether that data should be
964 /// appended to the existing data file whose content is at
988 /// appended to the existing data file whose content is at
965 /// `map.on_disk` (true), instead of written to a new data file
989 /// `map.on_disk` (true), instead of written to a new data file
966 /// (false).
990 /// (false).
967 #[timed]
991 #[timed]
968 pub fn pack_v2(
992 pub fn pack_v2(
969 &self,
993 &self,
970 can_append: bool,
994 can_append: bool,
971 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
995 ) -> Result<(Vec<u8>, on_disk::TreeMetadata, bool), DirstateError> {
972 let map = self.get_map();
996 let map = self.get_map();
973 on_disk::write(map, can_append)
997 on_disk::write(map, can_append)
974 }
998 }
975
999
976 pub fn status<'a>(
1000 /// `callback` allows the caller to process and do something with the
977 &'a mut self,
1001 /// results of the status. This is needed to do so efficiently (i.e.
978 matcher: &'a (dyn Matcher + Sync),
1002 /// without cloning the `DirstateStatus` object with its paths) because
1003 /// we need to borrow from `Self`.
1004 pub fn with_status<R>(
1005 &mut self,
1006 matcher: &(dyn Matcher + Sync),
979 root_dir: PathBuf,
1007 root_dir: PathBuf,
980 ignore_files: Vec<PathBuf>,
1008 ignore_files: Vec<PathBuf>,
981 options: StatusOptions,
1009 options: StatusOptions,
982 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1010 callback: impl for<'r> FnOnce(
983 {
1011 Result<(DirstateStatus<'r>, Vec<PatternFileWarning>), StatusError>,
984 let map = self.get_map_mut();
1012 ) -> R,
985 super::status::status(map, matcher, root_dir, ignore_files, options)
1013 ) -> R {
1014 self.with_dmap_mut(|map| {
1015 callback(super::status::status(
1016 map,
1017 matcher,
1018 root_dir,
1019 ignore_files,
1020 options,
1021 ))
1022 })
986 }
1023 }
987
1024
988 pub fn copy_map_len(&self) -> usize {
1025 pub fn copy_map_len(&self) -> usize {
989 let map = self.get_map();
1026 let map = self.get_map();
990 map.nodes_with_copy_source_count as usize
1027 map.nodes_with_copy_source_count as usize
991 }
1028 }
992
1029
993 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1030 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
994 let map = self.get_map();
1031 let map = self.get_map();
995 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1032 Box::new(filter_map_results(map.iter_nodes(), move |node| {
996 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1033 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
997 Some((node.full_path(map.on_disk)?, source))
1034 Some((node.full_path(map.on_disk)?, source))
998 } else {
1035 } else {
999 None
1036 None
1000 })
1037 })
1001 }))
1038 }))
1002 }
1039 }
1003
1040
1004 pub fn copy_map_contains_key(
1041 pub fn copy_map_contains_key(
1005 &self,
1042 &self,
1006 key: &HgPath,
1043 key: &HgPath,
1007 ) -> Result<bool, DirstateV2ParseError> {
1044 ) -> Result<bool, DirstateV2ParseError> {
1008 let map = self.get_map();
1045 let map = self.get_map();
1009 Ok(if let Some(node) = map.get_node(key)? {
1046 Ok(if let Some(node) = map.get_node(key)? {
1010 node.has_copy_source()
1047 node.has_copy_source()
1011 } else {
1048 } else {
1012 false
1049 false
1013 })
1050 })
1014 }
1051 }
1015
1052
1016 pub fn copy_map_get(
1053 pub fn copy_map_get(
1017 &self,
1054 &self,
1018 key: &HgPath,
1055 key: &HgPath,
1019 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1056 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1020 let map = self.get_map();
1057 let map = self.get_map();
1021 if let Some(node) = map.get_node(key)? {
1058 if let Some(node) = map.get_node(key)? {
1022 if let Some(source) = node.copy_source(map.on_disk)? {
1059 if let Some(source) = node.copy_source(map.on_disk)? {
1023 return Ok(Some(source));
1060 return Ok(Some(source));
1024 }
1061 }
1025 }
1062 }
1026 Ok(None)
1063 Ok(None)
1027 }
1064 }
1028
1065
1029 pub fn copy_map_remove(
1066 pub fn copy_map_remove(
1030 &mut self,
1067 &mut self,
1031 key: &HgPath,
1068 key: &HgPath,
1032 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1069 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1033 let map = self.get_map_mut();
1070 self.with_dmap_mut(|map| {
1034 let count = &mut map.nodes_with_copy_source_count;
1071 let count = &mut map.nodes_with_copy_source_count;
1035 let unreachable_bytes = &mut map.unreachable_bytes;
1072 let unreachable_bytes = &mut map.unreachable_bytes;
1036 Ok(DirstateMap::get_node_mut(
1073 Ok(DirstateMap::get_node_mut(
1037 map.on_disk,
1074 map.on_disk,
1038 unreachable_bytes,
1075 unreachable_bytes,
1039 &mut map.root,
1076 &mut map.root,
1040 key,
1077 key,
1041 )?
1078 )?
1042 .and_then(|node| {
1079 .and_then(|node| {
1043 if let Some(source) = &node.copy_source {
1080 if let Some(source) = &node.copy_source {
1044 *count -= 1;
1081 *count -= 1;
1045 DirstateMap::count_dropped_path(unreachable_bytes, source);
1082 DirstateMap::count_dropped_path(unreachable_bytes, source);
1046 }
1083 }
1047 node.copy_source.take().map(Cow::into_owned)
1084 node.copy_source.take().map(Cow::into_owned)
1048 }))
1085 }))
1086 })
1049 }
1087 }
1050
1088
1051 pub fn copy_map_insert(
1089 pub fn copy_map_insert(
1052 &mut self,
1090 &mut self,
1053 key: HgPathBuf,
1091 key: HgPathBuf,
1054 value: HgPathBuf,
1092 value: HgPathBuf,
1055 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1093 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1056 let map = self.get_map_mut();
1094 self.with_dmap_mut(|map| {
1057 let node = DirstateMap::get_or_insert_node(
1095 let node = DirstateMap::get_or_insert_node(
1058 map.on_disk,
1096 map.on_disk,
1059 &mut map.unreachable_bytes,
1097 &mut map.unreachable_bytes,
1060 &mut map.root,
1098 &mut map.root,
1061 &key,
1099 &key,
1062 WithBasename::to_cow_owned,
1100 WithBasename::to_cow_owned,
1063 |_ancestor| {},
1101 |_ancestor| {},
1064 )?;
1102 )?;
1065 if node.copy_source.is_none() {
1103 if node.copy_source.is_none() {
1066 map.nodes_with_copy_source_count += 1
1104 map.nodes_with_copy_source_count += 1
1067 }
1105 }
1068 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1106 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1107 })
1069 }
1108 }
1070
1109
1071 pub fn len(&self) -> usize {
1110 pub fn len(&self) -> usize {
1072 let map = self.get_map();
1111 let map = self.get_map();
1073 map.nodes_with_entry_count as usize
1112 map.nodes_with_entry_count as usize
1074 }
1113 }
1075
1114
1076 pub fn contains_key(
1115 pub fn contains_key(
1077 &self,
1116 &self,
1078 key: &HgPath,
1117 key: &HgPath,
1079 ) -> Result<bool, DirstateV2ParseError> {
1118 ) -> Result<bool, DirstateV2ParseError> {
1080 Ok(self.get(key)?.is_some())
1119 Ok(self.get(key)?.is_some())
1081 }
1120 }
1082
1121
1083 pub fn get(
1122 pub fn get(
1084 &self,
1123 &self,
1085 key: &HgPath,
1124 key: &HgPath,
1086 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1125 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1087 let map = self.get_map();
1126 let map = self.get_map();
1088 Ok(if let Some(node) = map.get_node(key)? {
1127 Ok(if let Some(node) = map.get_node(key)? {
1089 node.entry()?
1128 node.entry()?
1090 } else {
1129 } else {
1091 None
1130 None
1092 })
1131 })
1093 }
1132 }
1094
1133
1095 pub fn iter(&self) -> StateMapIter<'_> {
1134 pub fn iter(&self) -> StateMapIter<'_> {
1096 let map = self.get_map();
1135 let map = self.get_map();
1097 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1136 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1098 Ok(if let Some(entry) = node.entry()? {
1137 Ok(if let Some(entry) = node.entry()? {
1099 Some((node.full_path(map.on_disk)?, entry))
1138 Some((node.full_path(map.on_disk)?, entry))
1100 } else {
1139 } else {
1101 None
1140 None
1102 })
1141 })
1103 }))
1142 }))
1104 }
1143 }
1105
1144
1106 pub fn iter_tracked_dirs(
1145 pub fn iter_tracked_dirs(
1107 &mut self,
1146 &mut self,
1108 ) -> Result<
1147 ) -> Result<
1109 Box<
1148 Box<
1110 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1149 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1111 + Send
1150 + Send
1112 + '_,
1151 + '_,
1113 >,
1152 >,
1114 DirstateError,
1153 DirstateError,
1115 > {
1154 > {
1116 let map = self.get_map_mut();
1155 let map = self.get_map();
1117 let on_disk = map.on_disk;
1156 let on_disk = map.on_disk;
1118 Ok(Box::new(filter_map_results(
1157 Ok(Box::new(filter_map_results(
1119 map.iter_nodes(),
1158 map.iter_nodes(),
1120 move |node| {
1159 move |node| {
1121 Ok(if node.tracked_descendants_count() > 0 {
1160 Ok(if node.tracked_descendants_count() > 0 {
1122 Some(node.full_path(on_disk)?)
1161 Some(node.full_path(on_disk)?)
1123 } else {
1162 } else {
1124 None
1163 None
1125 })
1164 })
1126 },
1165 },
1127 )))
1166 )))
1128 }
1167 }
1129
1168
1130 pub fn debug_iter(
1169 pub fn debug_iter(
1131 &self,
1170 &self,
1132 all: bool,
1171 all: bool,
1133 ) -> Box<
1172 ) -> Box<
1134 dyn Iterator<
1173 dyn Iterator<
1135 Item = Result<
1174 Item = Result<
1136 (&HgPath, (u8, i32, i32, i32)),
1175 (&HgPath, (u8, i32, i32, i32)),
1137 DirstateV2ParseError,
1176 DirstateV2ParseError,
1138 >,
1177 >,
1139 > + Send
1178 > + Send
1140 + '_,
1179 + '_,
1141 > {
1180 > {
1142 let map = self.get_map();
1181 let map = self.get_map();
1143 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1182 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1144 let debug_tuple = if let Some(entry) = node.entry()? {
1183 let debug_tuple = if let Some(entry) = node.entry()? {
1145 entry.debug_tuple()
1184 entry.debug_tuple()
1146 } else if !all {
1185 } else if !all {
1147 return Ok(None);
1186 return Ok(None);
1148 } else if let Some(mtime) = node.cached_directory_mtime()? {
1187 } else if let Some(mtime) = node.cached_directory_mtime()? {
1149 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1188 (b' ', 0, -1, mtime.truncated_seconds() as i32)
1150 } else {
1189 } else {
1151 (b' ', 0, -1, -1)
1190 (b' ', 0, -1, -1)
1152 };
1191 };
1153 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1192 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1154 }))
1193 }))
1155 }
1194 }
1156 }
1195 }
@@ -1,105 +1,89 b''
1 use crate::{DirstateError, DirstateParents};
2
1 use super::dirstate_map::DirstateMap;
3 use super::dirstate_map::DirstateMap;
2 use stable_deref_trait::StableDeref;
3 use std::ops::Deref;
4 use std::ops::Deref;
4
5
6 use ouroboros::self_referencing;
7
5 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
8 /// Keep a `DirstateMap<'on_disk>` next to the `on_disk` buffer that it
6 /// borrows.
9 /// borrows.
7 ///
10 #[self_referencing]
8 /// This is similar to [`OwningRef`] which is more limited because it
9 /// represents exactly one `&T` reference next to the value it borrows, as
10 /// opposed to a struct that may contain an arbitrary number of references in
11 /// arbitrarily-nested data structures.
12 ///
13 /// [`OwningRef`]: https://docs.rs/owning_ref/0.4.1/owning_ref/struct.OwningRef.html
14 pub struct OwningDirstateMap {
11 pub struct OwningDirstateMap {
15 /// Owned handle to a bytes buffer with a stable address.
16 ///
17 /// See <https://docs.rs/owning_ref/0.4.1/owning_ref/trait.StableAddress.html>.
18 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
12 on_disk: Box<dyn Deref<Target = [u8]> + Send>,
19
13 #[borrows(on_disk)]
20 /// Pointer for `Box<DirstateMap<'on_disk>>`, typed-erased because the
14 #[covariant]
21 /// language cannot represent a lifetime referencing a sibling field.
15 map: DirstateMap<'this>,
22 /// This is not quite a self-referencial struct (moving this struct is not
23 /// a problem as it doesn’t change the address of the bytes buffer owned
24 /// by `on_disk`) but touches similar borrow-checker limitations.
25 ptr: *mut (),
26 }
16 }
27
17
28 impl OwningDirstateMap {
18 impl OwningDirstateMap {
29 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
19 pub fn new_empty<OnDisk>(on_disk: OnDisk) -> Self
30 where
20 where
31 OnDisk: Deref<Target = [u8]> + StableDeref + Send + 'static,
21 OnDisk: Deref<Target = [u8]> + Send + 'static,
32 {
22 {
33 let on_disk = Box::new(on_disk);
23 let on_disk = Box::new(on_disk);
34 let bytes: &'_ [u8] = &on_disk;
35 let map = DirstateMap::empty(bytes);
36
24
37 // Like in `bytes` above, this `'_` lifetime parameter borrows from
25 OwningDirstateMapBuilder {
38 // the bytes buffer owned by `on_disk`.
26 on_disk,
39 let ptr: *mut DirstateMap<'_> = Box::into_raw(Box::new(map));
27 map_builder: |bytes| DirstateMap::empty(&bytes),
40
28 }
41 // Erase the pointed type entirely in order to erase the lifetime.
29 .build()
42 let ptr: *mut () = ptr.cast();
43
44 Self { on_disk, ptr }
45 }
30 }
46
31
47 pub fn get_pair_mut<'a>(
32 pub fn new_v1<OnDisk>(
48 &'a mut self,
33 on_disk: OnDisk,
49 ) -> (&'a [u8], &'a mut DirstateMap<'a>) {
34 ) -> Result<(Self, DirstateParents), DirstateError>
50 // SAFETY: We cast the type-erased pointer back to the same type it had
35 where
51 // in `new`, except with a different lifetime parameter. This time we
36 OnDisk: Deref<Target = [u8]> + Send + 'static,
52 // connect the lifetime to that of `self`. This cast is valid because
37 {
53 // `self` owns the same `on_disk` whose buffer `DirstateMap`
38 let on_disk = Box::new(on_disk);
54 // references. That buffer has a stable memory address because our
39 let mut parents = DirstateParents::NULL;
55 // `Self::new_empty` counstructor requires `StableDeref`.
56 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
57 // SAFETY: we dereference that pointer, connecting the lifetime of the
58 // new `&mut` to that of `self`. This is valid because the
59 // raw pointer is to a boxed value, and `self` owns that box.
60 (&self.on_disk, unsafe { &mut *ptr })
61 }
62
40
63 pub fn get_map_mut<'a>(&'a mut self) -> &'a mut DirstateMap<'a> {
41 Ok((
64 self.get_pair_mut().1
42 OwningDirstateMapTryBuilder {
43 on_disk,
44 map_builder: |bytes| {
45 DirstateMap::new_v1(&bytes).map(|(dmap, p)| {
46 parents = p.unwrap_or(DirstateParents::NULL);
47 dmap
48 })
49 },
50 }
51 .try_build()?,
52 parents,
53 ))
65 }
54 }
66
55
67 pub fn get_map<'a>(&'a self) -> &'a DirstateMap<'a> {
56 pub fn new_v2<OnDisk>(
68 // SAFETY: same reasoning as in `get_pair_mut` above.
57 on_disk: OnDisk,
69 let ptr: *mut DirstateMap<'a> = self.ptr.cast();
58 data_size: usize,
70 unsafe { &*ptr }
59 metadata: &[u8],
60 ) -> Result<Self, DirstateError>
61 where
62 OnDisk: Deref<Target = [u8]> + Send + 'static,
63 {
64 let on_disk = Box::new(on_disk);
65
66 OwningDirstateMapTryBuilder {
67 on_disk,
68 map_builder: |bytes| {
69 DirstateMap::new_v2(&bytes, data_size, metadata)
70 },
71 }
72 .try_build()
71 }
73 }
72
74
73 pub fn on_disk<'a>(&'a self) -> &'a [u8] {
75 pub fn with_dmap_mut<R>(
74 &self.on_disk
76 &mut self,
77 f: impl FnOnce(&mut DirstateMap) -> R,
78 ) -> R {
79 self.with_map_mut(f)
80 }
81
82 pub fn get_map(&self) -> &DirstateMap {
83 self.borrow_map()
84 }
85
86 pub fn on_disk(&self) -> &[u8] {
87 self.borrow_on_disk()
75 }
88 }
76 }
89 }
77
78 impl Drop for OwningDirstateMap {
79 fn drop(&mut self) {
80 // Silence a "field is never read" warning, and demonstrate that this
81 // value is still alive.
82 let _: &Box<dyn Deref<Target = [u8]> + Send> = &self.on_disk;
83 // SAFETY: this cast is the same as in `get_mut`, and is valid for the
84 // same reason. `self.on_disk` still exists at this point, drop glue
85 // will drop it implicitly after this `drop` method returns.
86 let ptr: *mut DirstateMap<'_> = self.ptr.cast();
87 // SAFETY: `Box::from_raw` takes ownership of the box away from `self`.
88 // This is fine because drop glue does nothing for `*mut ()` and we’re
89 // in `drop`, so `get` and `get_mut` cannot be called again.
90 unsafe { drop(Box::from_raw(ptr)) }
91 }
92 }
93
94 fn _static_assert_is_send<T: Send>() {}
95
96 fn _static_assert_fields_are_send() {
97 _static_assert_is_send::<Box<DirstateMap<'_>>>();
98 }
99
100 // SAFETY: we don’t get this impl implicitly because `*mut (): !Send` because
101 // thread-safety of raw pointers is unknown in the general case. However this
102 // particular raw pointer represents a `Box<DirstateMap<'on_disk>>` that we
103 // own. Since that `Box` is `Send` as shown in above, it is sound to mark
104 // this struct as `Send` too.
105 unsafe impl Send for OwningDirstateMap {}
@@ -1,848 +1,849 b''
1 use crate::dirstate::entry::TruncatedTimestamp;
1 use crate::dirstate::entry::TruncatedTimestamp;
2 use crate::dirstate::status::IgnoreFnType;
2 use crate::dirstate::status::IgnoreFnType;
3 use crate::dirstate::status::StatusPath;
3 use crate::dirstate::status::StatusPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
4 use crate::dirstate_tree::dirstate_map::BorrowedPath;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
5 use crate::dirstate_tree::dirstate_map::ChildNodesRef;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
6 use crate::dirstate_tree::dirstate_map::DirstateMap;
7 use crate::dirstate_tree::dirstate_map::NodeData;
7 use crate::dirstate_tree::dirstate_map::NodeData;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
8 use crate::dirstate_tree::dirstate_map::NodeRef;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
9 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
10 use crate::matchers::get_ignore_function;
10 use crate::matchers::get_ignore_function;
11 use crate::matchers::Matcher;
11 use crate::matchers::Matcher;
12 use crate::utils::files::get_bytes_from_os_string;
12 use crate::utils::files::get_bytes_from_os_string;
13 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::BadMatch;
15 use crate::BadMatch;
16 use crate::DirstateStatus;
16 use crate::DirstateStatus;
17 use crate::EntryState;
17 use crate::EntryState;
18 use crate::HgPathBuf;
18 use crate::HgPathBuf;
19 use crate::HgPathCow;
19 use crate::HgPathCow;
20 use crate::PatternFileWarning;
20 use crate::PatternFileWarning;
21 use crate::StatusError;
21 use crate::StatusError;
22 use crate::StatusOptions;
22 use crate::StatusOptions;
23 use micro_timer::timed;
23 use micro_timer::timed;
24 use rayon::prelude::*;
24 use rayon::prelude::*;
25 use sha1::{Digest, Sha1};
25 use sha1::{Digest, Sha1};
26 use std::borrow::Cow;
26 use std::borrow::Cow;
27 use std::io;
27 use std::io;
28 use std::path::Path;
28 use std::path::Path;
29 use std::path::PathBuf;
29 use std::path::PathBuf;
30 use std::sync::Mutex;
30 use std::sync::Mutex;
31 use std::time::SystemTime;
31 use std::time::SystemTime;
32
32
33 /// Returns the status of the working directory compared to its parent
33 /// Returns the status of the working directory compared to its parent
34 /// changeset.
34 /// changeset.
35 ///
35 ///
36 /// This algorithm is based on traversing the filesystem tree (`fs` in function
36 /// This algorithm is based on traversing the filesystem tree (`fs` in function
37 /// and variable names) and dirstate tree at the same time. The core of this
37 /// and variable names) and dirstate tree at the same time. The core of this
38 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
38 /// traversal is the recursive `traverse_fs_directory_and_dirstate` function
39 /// and its use of `itertools::merge_join_by`. When reaching a path that only
39 /// and its use of `itertools::merge_join_by`. When reaching a path that only
40 /// exists in one of the two trees, depending on information requested by
40 /// exists in one of the two trees, depending on information requested by
41 /// `options` we may need to traverse the remaining subtree.
41 /// `options` we may need to traverse the remaining subtree.
42 #[timed]
42 #[timed]
43 pub fn status<'tree, 'on_disk: 'tree>(
43 pub fn status<'dirstate>(
44 dmap: &'tree mut DirstateMap<'on_disk>,
44 dmap: &'dirstate mut DirstateMap,
45 matcher: &(dyn Matcher + Sync),
45 matcher: &(dyn Matcher + Sync),
46 root_dir: PathBuf,
46 root_dir: PathBuf,
47 ignore_files: Vec<PathBuf>,
47 ignore_files: Vec<PathBuf>,
48 options: StatusOptions,
48 options: StatusOptions,
49 ) -> Result<(DirstateStatus<'on_disk>, Vec<PatternFileWarning>), StatusError> {
49 ) -> Result<(DirstateStatus<'dirstate>, Vec<PatternFileWarning>), StatusError>
50 {
50 // Force the global rayon threadpool to not exceed 16 concurrent threads.
51 // Force the global rayon threadpool to not exceed 16 concurrent threads.
51 // This is a stop-gap measure until we figure out why using more than 16
52 // This is a stop-gap measure until we figure out why using more than 16
52 // threads makes `status` slower for each additional thread.
53 // threads makes `status` slower for each additional thread.
53 // We use `ok()` in case the global threadpool has already been
54 // We use `ok()` in case the global threadpool has already been
54 // instantiated in `rhg` or some other caller.
55 // instantiated in `rhg` or some other caller.
55 // TODO find the underlying cause and fix it, then remove this.
56 // TODO find the underlying cause and fix it, then remove this.
56 rayon::ThreadPoolBuilder::new()
57 rayon::ThreadPoolBuilder::new()
57 .num_threads(16)
58 .num_threads(16)
58 .build_global()
59 .build_global()
59 .ok();
60 .ok();
60
61
61 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
62 let (ignore_fn, warnings, patterns_changed): (IgnoreFnType, _, _) =
62 if options.list_ignored || options.list_unknown {
63 if options.list_ignored || options.list_unknown {
63 let mut hasher = Sha1::new();
64 let mut hasher = Sha1::new();
64 let (ignore_fn, warnings) = get_ignore_function(
65 let (ignore_fn, warnings) = get_ignore_function(
65 ignore_files,
66 ignore_files,
66 &root_dir,
67 &root_dir,
67 &mut |pattern_bytes| hasher.update(pattern_bytes),
68 &mut |pattern_bytes| hasher.update(pattern_bytes),
68 )?;
69 )?;
69 let new_hash = *hasher.finalize().as_ref();
70 let new_hash = *hasher.finalize().as_ref();
70 let changed = new_hash != dmap.ignore_patterns_hash;
71 let changed = new_hash != dmap.ignore_patterns_hash;
71 dmap.ignore_patterns_hash = new_hash;
72 dmap.ignore_patterns_hash = new_hash;
72 (ignore_fn, warnings, Some(changed))
73 (ignore_fn, warnings, Some(changed))
73 } else {
74 } else {
74 (Box::new(|&_| true), vec![], None)
75 (Box::new(|&_| true), vec![], None)
75 };
76 };
76
77
77 let filesystem_time_at_status_start =
78 let filesystem_time_at_status_start =
78 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
79 filesystem_now(&root_dir).ok().map(TruncatedTimestamp::from);
79
80
80 // If the repository is under the current directory, prefer using a
81 // If the repository is under the current directory, prefer using a
81 // relative path, so the kernel needs to traverse fewer directory in every
82 // relative path, so the kernel needs to traverse fewer directory in every
82 // call to `read_dir` or `symlink_metadata`.
83 // call to `read_dir` or `symlink_metadata`.
83 // This is effective in the common case where the current directory is the
84 // This is effective in the common case where the current directory is the
84 // repository root.
85 // repository root.
85
86
86 // TODO: Better yet would be to use libc functions like `openat` and
87 // TODO: Better yet would be to use libc functions like `openat` and
87 // `fstatat` to remove such repeated traversals entirely, but the standard
88 // `fstatat` to remove such repeated traversals entirely, but the standard
88 // library does not provide APIs based on those.
89 // library does not provide APIs based on those.
89 // Maybe with a crate like https://crates.io/crates/openat instead?
90 // Maybe with a crate like https://crates.io/crates/openat instead?
90 let root_dir = if let Some(relative) = std::env::current_dir()
91 let root_dir = if let Some(relative) = std::env::current_dir()
91 .ok()
92 .ok()
92 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
93 .and_then(|cwd| root_dir.strip_prefix(cwd).ok())
93 {
94 {
94 relative
95 relative
95 } else {
96 } else {
96 &root_dir
97 &root_dir
97 };
98 };
98
99
99 let outcome = DirstateStatus {
100 let outcome = DirstateStatus {
100 filesystem_time_at_status_start,
101 filesystem_time_at_status_start,
101 ..Default::default()
102 ..Default::default()
102 };
103 };
103 let common = StatusCommon {
104 let common = StatusCommon {
104 dmap,
105 dmap,
105 options,
106 options,
106 matcher,
107 matcher,
107 ignore_fn,
108 ignore_fn,
108 outcome: Mutex::new(outcome),
109 outcome: Mutex::new(outcome),
109 ignore_patterns_have_changed: patterns_changed,
110 ignore_patterns_have_changed: patterns_changed,
110 new_cachable_directories: Default::default(),
111 new_cachable_directories: Default::default(),
111 outated_cached_directories: Default::default(),
112 outated_cached_directories: Default::default(),
112 filesystem_time_at_status_start,
113 filesystem_time_at_status_start,
113 };
114 };
114 let is_at_repo_root = true;
115 let is_at_repo_root = true;
115 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
116 let hg_path = &BorrowedPath::OnDisk(HgPath::new(""));
116 let has_ignored_ancestor = false;
117 let has_ignored_ancestor = false;
117 let root_cached_mtime = None;
118 let root_cached_mtime = None;
118 let root_dir_metadata = None;
119 let root_dir_metadata = None;
119 // If the path we have for the repository root is a symlink, do follow it.
120 // If the path we have for the repository root is a symlink, do follow it.
120 // (As opposed to symlinks within the working directory which are not
121 // (As opposed to symlinks within the working directory which are not
121 // followed, using `std::fs::symlink_metadata`.)
122 // followed, using `std::fs::symlink_metadata`.)
122 common.traverse_fs_directory_and_dirstate(
123 common.traverse_fs_directory_and_dirstate(
123 has_ignored_ancestor,
124 has_ignored_ancestor,
124 dmap.root.as_ref(),
125 dmap.root.as_ref(),
125 hg_path,
126 hg_path,
126 &root_dir,
127 &root_dir,
127 root_dir_metadata,
128 root_dir_metadata,
128 root_cached_mtime,
129 root_cached_mtime,
129 is_at_repo_root,
130 is_at_repo_root,
130 )?;
131 )?;
131 let mut outcome = common.outcome.into_inner().unwrap();
132 let mut outcome = common.outcome.into_inner().unwrap();
132 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
133 let new_cachable = common.new_cachable_directories.into_inner().unwrap();
133 let outdated = common.outated_cached_directories.into_inner().unwrap();
134 let outdated = common.outated_cached_directories.into_inner().unwrap();
134
135
135 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
136 outcome.dirty = common.ignore_patterns_have_changed == Some(true)
136 || !outdated.is_empty()
137 || !outdated.is_empty()
137 || !new_cachable.is_empty();
138 || !new_cachable.is_empty();
138
139
139 // Remove outdated mtimes before adding new mtimes, in case a given
140 // Remove outdated mtimes before adding new mtimes, in case a given
140 // directory is both
141 // directory is both
141 for path in &outdated {
142 for path in &outdated {
142 let node = dmap.get_or_insert(path)?;
143 let node = dmap.get_or_insert(path)?;
143 if let NodeData::CachedDirectory { .. } = &node.data {
144 if let NodeData::CachedDirectory { .. } = &node.data {
144 node.data = NodeData::None
145 node.data = NodeData::None
145 }
146 }
146 }
147 }
147 for (path, mtime) in &new_cachable {
148 for (path, mtime) in &new_cachable {
148 let node = dmap.get_or_insert(path)?;
149 let node = dmap.get_or_insert(path)?;
149 match &node.data {
150 match &node.data {
150 NodeData::Entry(_) => {} // Don’t overwrite an entry
151 NodeData::Entry(_) => {} // Don’t overwrite an entry
151 NodeData::CachedDirectory { .. } | NodeData::None => {
152 NodeData::CachedDirectory { .. } | NodeData::None => {
152 node.data = NodeData::CachedDirectory { mtime: *mtime }
153 node.data = NodeData::CachedDirectory { mtime: *mtime }
153 }
154 }
154 }
155 }
155 }
156 }
156
157
157 Ok((outcome, warnings))
158 Ok((outcome, warnings))
158 }
159 }
159
160
160 /// Bag of random things needed by various parts of the algorithm. Reduces the
161 /// Bag of random things needed by various parts of the algorithm. Reduces the
161 /// number of parameters passed to functions.
162 /// number of parameters passed to functions.
162 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
163 struct StatusCommon<'a, 'tree, 'on_disk: 'tree> {
163 dmap: &'tree DirstateMap<'on_disk>,
164 dmap: &'tree DirstateMap<'on_disk>,
164 options: StatusOptions,
165 options: StatusOptions,
165 matcher: &'a (dyn Matcher + Sync),
166 matcher: &'a (dyn Matcher + Sync),
166 ignore_fn: IgnoreFnType<'a>,
167 ignore_fn: IgnoreFnType<'a>,
167 outcome: Mutex<DirstateStatus<'on_disk>>,
168 outcome: Mutex<DirstateStatus<'on_disk>>,
168 new_cachable_directories:
169 new_cachable_directories:
169 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
170 Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
170 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
171 outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
171
172
172 /// Whether ignore files like `.hgignore` have changed since the previous
173 /// Whether ignore files like `.hgignore` have changed since the previous
173 /// time a `status()` call wrote their hash to the dirstate. `None` means
174 /// time a `status()` call wrote their hash to the dirstate. `None` means
174 /// we don’t know as this run doesn’t list either ignored or uknown files
175 /// we don’t know as this run doesn’t list either ignored or uknown files
175 /// and therefore isn’t reading `.hgignore`.
176 /// and therefore isn’t reading `.hgignore`.
176 ignore_patterns_have_changed: Option<bool>,
177 ignore_patterns_have_changed: Option<bool>,
177
178
178 /// The current time at the start of the `status()` algorithm, as measured
179 /// The current time at the start of the `status()` algorithm, as measured
179 /// and possibly truncated by the filesystem.
180 /// and possibly truncated by the filesystem.
180 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
181 filesystem_time_at_status_start: Option<TruncatedTimestamp>,
181 }
182 }
182
183
183 enum Outcome {
184 enum Outcome {
184 Modified,
185 Modified,
185 Added,
186 Added,
186 Removed,
187 Removed,
187 Deleted,
188 Deleted,
188 Clean,
189 Clean,
189 Ignored,
190 Ignored,
190 Unknown,
191 Unknown,
191 Unsure,
192 Unsure,
192 }
193 }
193
194
194 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
195 impl<'a, 'tree, 'on_disk> StatusCommon<'a, 'tree, 'on_disk> {
195 fn push_outcome(
196 fn push_outcome(
196 &self,
197 &self,
197 which: Outcome,
198 which: Outcome,
198 dirstate_node: &NodeRef<'tree, 'on_disk>,
199 dirstate_node: &NodeRef<'tree, 'on_disk>,
199 ) -> Result<(), DirstateV2ParseError> {
200 ) -> Result<(), DirstateV2ParseError> {
200 let path = dirstate_node
201 let path = dirstate_node
201 .full_path_borrowed(self.dmap.on_disk)?
202 .full_path_borrowed(self.dmap.on_disk)?
202 .detach_from_tree();
203 .detach_from_tree();
203 let copy_source = if self.options.list_copies {
204 let copy_source = if self.options.list_copies {
204 dirstate_node
205 dirstate_node
205 .copy_source_borrowed(self.dmap.on_disk)?
206 .copy_source_borrowed(self.dmap.on_disk)?
206 .map(|source| source.detach_from_tree())
207 .map(|source| source.detach_from_tree())
207 } else {
208 } else {
208 None
209 None
209 };
210 };
210 self.push_outcome_common(which, path, copy_source);
211 self.push_outcome_common(which, path, copy_source);
211 Ok(())
212 Ok(())
212 }
213 }
213
214
214 fn push_outcome_without_copy_source(
215 fn push_outcome_without_copy_source(
215 &self,
216 &self,
216 which: Outcome,
217 which: Outcome,
217 path: &BorrowedPath<'_, 'on_disk>,
218 path: &BorrowedPath<'_, 'on_disk>,
218 ) {
219 ) {
219 self.push_outcome_common(which, path.detach_from_tree(), None)
220 self.push_outcome_common(which, path.detach_from_tree(), None)
220 }
221 }
221
222
222 fn push_outcome_common(
223 fn push_outcome_common(
223 &self,
224 &self,
224 which: Outcome,
225 which: Outcome,
225 path: HgPathCow<'on_disk>,
226 path: HgPathCow<'on_disk>,
226 copy_source: Option<HgPathCow<'on_disk>>,
227 copy_source: Option<HgPathCow<'on_disk>>,
227 ) {
228 ) {
228 let mut outcome = self.outcome.lock().unwrap();
229 let mut outcome = self.outcome.lock().unwrap();
229 let vec = match which {
230 let vec = match which {
230 Outcome::Modified => &mut outcome.modified,
231 Outcome::Modified => &mut outcome.modified,
231 Outcome::Added => &mut outcome.added,
232 Outcome::Added => &mut outcome.added,
232 Outcome::Removed => &mut outcome.removed,
233 Outcome::Removed => &mut outcome.removed,
233 Outcome::Deleted => &mut outcome.deleted,
234 Outcome::Deleted => &mut outcome.deleted,
234 Outcome::Clean => &mut outcome.clean,
235 Outcome::Clean => &mut outcome.clean,
235 Outcome::Ignored => &mut outcome.ignored,
236 Outcome::Ignored => &mut outcome.ignored,
236 Outcome::Unknown => &mut outcome.unknown,
237 Outcome::Unknown => &mut outcome.unknown,
237 Outcome::Unsure => &mut outcome.unsure,
238 Outcome::Unsure => &mut outcome.unsure,
238 };
239 };
239 vec.push(StatusPath { path, copy_source });
240 vec.push(StatusPath { path, copy_source });
240 }
241 }
241
242
242 fn read_dir(
243 fn read_dir(
243 &self,
244 &self,
244 hg_path: &HgPath,
245 hg_path: &HgPath,
245 fs_path: &Path,
246 fs_path: &Path,
246 is_at_repo_root: bool,
247 is_at_repo_root: bool,
247 ) -> Result<Vec<DirEntry>, ()> {
248 ) -> Result<Vec<DirEntry>, ()> {
248 DirEntry::read_dir(fs_path, is_at_repo_root)
249 DirEntry::read_dir(fs_path, is_at_repo_root)
249 .map_err(|error| self.io_error(error, hg_path))
250 .map_err(|error| self.io_error(error, hg_path))
250 }
251 }
251
252
252 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
253 fn io_error(&self, error: std::io::Error, hg_path: &HgPath) {
253 let errno = error.raw_os_error().expect("expected real OS error");
254 let errno = error.raw_os_error().expect("expected real OS error");
254 self.outcome
255 self.outcome
255 .lock()
256 .lock()
256 .unwrap()
257 .unwrap()
257 .bad
258 .bad
258 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
259 .push((hg_path.to_owned().into(), BadMatch::OsError(errno)))
259 }
260 }
260
261
261 fn check_for_outdated_directory_cache(
262 fn check_for_outdated_directory_cache(
262 &self,
263 &self,
263 dirstate_node: &NodeRef<'tree, 'on_disk>,
264 dirstate_node: &NodeRef<'tree, 'on_disk>,
264 ) -> Result<(), DirstateV2ParseError> {
265 ) -> Result<(), DirstateV2ParseError> {
265 if self.ignore_patterns_have_changed == Some(true)
266 if self.ignore_patterns_have_changed == Some(true)
266 && dirstate_node.cached_directory_mtime()?.is_some()
267 && dirstate_node.cached_directory_mtime()?.is_some()
267 {
268 {
268 self.outated_cached_directories.lock().unwrap().push(
269 self.outated_cached_directories.lock().unwrap().push(
269 dirstate_node
270 dirstate_node
270 .full_path_borrowed(self.dmap.on_disk)?
271 .full_path_borrowed(self.dmap.on_disk)?
271 .detach_from_tree(),
272 .detach_from_tree(),
272 )
273 )
273 }
274 }
274 Ok(())
275 Ok(())
275 }
276 }
276
277
277 /// If this returns true, we can get accurate results by only using
278 /// If this returns true, we can get accurate results by only using
278 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
279 /// `symlink_metadata` for child nodes that exist in the dirstate and don’t
279 /// need to call `read_dir`.
280 /// need to call `read_dir`.
280 fn can_skip_fs_readdir(
281 fn can_skip_fs_readdir(
281 &self,
282 &self,
282 directory_metadata: Option<&std::fs::Metadata>,
283 directory_metadata: Option<&std::fs::Metadata>,
283 cached_directory_mtime: Option<TruncatedTimestamp>,
284 cached_directory_mtime: Option<TruncatedTimestamp>,
284 ) -> bool {
285 ) -> bool {
285 if !self.options.list_unknown && !self.options.list_ignored {
286 if !self.options.list_unknown && !self.options.list_ignored {
286 // All states that we care about listing have corresponding
287 // All states that we care about listing have corresponding
287 // dirstate entries.
288 // dirstate entries.
288 // This happens for example with `hg status -mard`.
289 // This happens for example with `hg status -mard`.
289 return true;
290 return true;
290 }
291 }
291 if !self.options.list_ignored
292 if !self.options.list_ignored
292 && self.ignore_patterns_have_changed == Some(false)
293 && self.ignore_patterns_have_changed == Some(false)
293 {
294 {
294 if let Some(cached_mtime) = cached_directory_mtime {
295 if let Some(cached_mtime) = cached_directory_mtime {
295 // The dirstate contains a cached mtime for this directory, set
296 // The dirstate contains a cached mtime for this directory, set
296 // by a previous run of the `status` algorithm which found this
297 // by a previous run of the `status` algorithm which found this
297 // directory eligible for `read_dir` caching.
298 // directory eligible for `read_dir` caching.
298 if let Some(meta) = directory_metadata {
299 if let Some(meta) = directory_metadata {
299 if cached_mtime
300 if cached_mtime
300 .likely_equal_to_mtime_of(meta)
301 .likely_equal_to_mtime_of(meta)
301 .unwrap_or(false)
302 .unwrap_or(false)
302 {
303 {
303 // The mtime of that directory has not changed
304 // The mtime of that directory has not changed
304 // since then, which means that the results of
305 // since then, which means that the results of
305 // `read_dir` should also be unchanged.
306 // `read_dir` should also be unchanged.
306 return true;
307 return true;
307 }
308 }
308 }
309 }
309 }
310 }
310 }
311 }
311 false
312 false
312 }
313 }
313
314
314 /// Returns whether all child entries of the filesystem directory have a
315 /// Returns whether all child entries of the filesystem directory have a
315 /// corresponding dirstate node or are ignored.
316 /// corresponding dirstate node or are ignored.
316 fn traverse_fs_directory_and_dirstate(
317 fn traverse_fs_directory_and_dirstate(
317 &self,
318 &self,
318 has_ignored_ancestor: bool,
319 has_ignored_ancestor: bool,
319 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
320 dirstate_nodes: ChildNodesRef<'tree, 'on_disk>,
320 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
321 directory_hg_path: &BorrowedPath<'tree, 'on_disk>,
321 directory_fs_path: &Path,
322 directory_fs_path: &Path,
322 directory_metadata: Option<&std::fs::Metadata>,
323 directory_metadata: Option<&std::fs::Metadata>,
323 cached_directory_mtime: Option<TruncatedTimestamp>,
324 cached_directory_mtime: Option<TruncatedTimestamp>,
324 is_at_repo_root: bool,
325 is_at_repo_root: bool,
325 ) -> Result<bool, DirstateV2ParseError> {
326 ) -> Result<bool, DirstateV2ParseError> {
326 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
327 if self.can_skip_fs_readdir(directory_metadata, cached_directory_mtime)
327 {
328 {
328 dirstate_nodes
329 dirstate_nodes
329 .par_iter()
330 .par_iter()
330 .map(|dirstate_node| {
331 .map(|dirstate_node| {
331 let fs_path = directory_fs_path.join(get_path_from_bytes(
332 let fs_path = directory_fs_path.join(get_path_from_bytes(
332 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
333 dirstate_node.base_name(self.dmap.on_disk)?.as_bytes(),
333 ));
334 ));
334 match std::fs::symlink_metadata(&fs_path) {
335 match std::fs::symlink_metadata(&fs_path) {
335 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
336 Ok(fs_metadata) => self.traverse_fs_and_dirstate(
336 &fs_path,
337 &fs_path,
337 &fs_metadata,
338 &fs_metadata,
338 dirstate_node,
339 dirstate_node,
339 has_ignored_ancestor,
340 has_ignored_ancestor,
340 ),
341 ),
341 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
342 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
342 self.traverse_dirstate_only(dirstate_node)
343 self.traverse_dirstate_only(dirstate_node)
343 }
344 }
344 Err(error) => {
345 Err(error) => {
345 let hg_path =
346 let hg_path =
346 dirstate_node.full_path(self.dmap.on_disk)?;
347 dirstate_node.full_path(self.dmap.on_disk)?;
347 Ok(self.io_error(error, hg_path))
348 Ok(self.io_error(error, hg_path))
348 }
349 }
349 }
350 }
350 })
351 })
351 .collect::<Result<_, _>>()?;
352 .collect::<Result<_, _>>()?;
352
353
353 // We don’t know, so conservatively say this isn’t the case
354 // We don’t know, so conservatively say this isn’t the case
354 let children_all_have_dirstate_node_or_are_ignored = false;
355 let children_all_have_dirstate_node_or_are_ignored = false;
355
356
356 return Ok(children_all_have_dirstate_node_or_are_ignored);
357 return Ok(children_all_have_dirstate_node_or_are_ignored);
357 }
358 }
358
359
359 let mut fs_entries = if let Ok(entries) = self.read_dir(
360 let mut fs_entries = if let Ok(entries) = self.read_dir(
360 directory_hg_path,
361 directory_hg_path,
361 directory_fs_path,
362 directory_fs_path,
362 is_at_repo_root,
363 is_at_repo_root,
363 ) {
364 ) {
364 entries
365 entries
365 } else {
366 } else {
366 // Treat an unreadable directory (typically because of insufficient
367 // Treat an unreadable directory (typically because of insufficient
367 // permissions) like an empty directory. `self.read_dir` has
368 // permissions) like an empty directory. `self.read_dir` has
368 // already called `self.io_error` so a warning will be emitted.
369 // already called `self.io_error` so a warning will be emitted.
369 Vec::new()
370 Vec::new()
370 };
371 };
371
372
372 // `merge_join_by` requires both its input iterators to be sorted:
373 // `merge_join_by` requires both its input iterators to be sorted:
373
374
374 let dirstate_nodes = dirstate_nodes.sorted();
375 let dirstate_nodes = dirstate_nodes.sorted();
375 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
376 // `sort_unstable_by_key` doesn’t allow keys borrowing from the value:
376 // https://github.com/rust-lang/rust/issues/34162
377 // https://github.com/rust-lang/rust/issues/34162
377 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
378 fs_entries.sort_unstable_by(|e1, e2| e1.base_name.cmp(&e2.base_name));
378
379
379 // Propagate here any error that would happen inside the comparison
380 // Propagate here any error that would happen inside the comparison
380 // callback below
381 // callback below
381 for dirstate_node in &dirstate_nodes {
382 for dirstate_node in &dirstate_nodes {
382 dirstate_node.base_name(self.dmap.on_disk)?;
383 dirstate_node.base_name(self.dmap.on_disk)?;
383 }
384 }
384 itertools::merge_join_by(
385 itertools::merge_join_by(
385 dirstate_nodes,
386 dirstate_nodes,
386 &fs_entries,
387 &fs_entries,
387 |dirstate_node, fs_entry| {
388 |dirstate_node, fs_entry| {
388 // This `unwrap` never panics because we already propagated
389 // This `unwrap` never panics because we already propagated
389 // those errors above
390 // those errors above
390 dirstate_node
391 dirstate_node
391 .base_name(self.dmap.on_disk)
392 .base_name(self.dmap.on_disk)
392 .unwrap()
393 .unwrap()
393 .cmp(&fs_entry.base_name)
394 .cmp(&fs_entry.base_name)
394 },
395 },
395 )
396 )
396 .par_bridge()
397 .par_bridge()
397 .map(|pair| {
398 .map(|pair| {
398 use itertools::EitherOrBoth::*;
399 use itertools::EitherOrBoth::*;
399 let has_dirstate_node_or_is_ignored;
400 let has_dirstate_node_or_is_ignored;
400 match pair {
401 match pair {
401 Both(dirstate_node, fs_entry) => {
402 Both(dirstate_node, fs_entry) => {
402 self.traverse_fs_and_dirstate(
403 self.traverse_fs_and_dirstate(
403 &fs_entry.full_path,
404 &fs_entry.full_path,
404 &fs_entry.metadata,
405 &fs_entry.metadata,
405 dirstate_node,
406 dirstate_node,
406 has_ignored_ancestor,
407 has_ignored_ancestor,
407 )?;
408 )?;
408 has_dirstate_node_or_is_ignored = true
409 has_dirstate_node_or_is_ignored = true
409 }
410 }
410 Left(dirstate_node) => {
411 Left(dirstate_node) => {
411 self.traverse_dirstate_only(dirstate_node)?;
412 self.traverse_dirstate_only(dirstate_node)?;
412 has_dirstate_node_or_is_ignored = true;
413 has_dirstate_node_or_is_ignored = true;
413 }
414 }
414 Right(fs_entry) => {
415 Right(fs_entry) => {
415 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
416 has_dirstate_node_or_is_ignored = self.traverse_fs_only(
416 has_ignored_ancestor,
417 has_ignored_ancestor,
417 directory_hg_path,
418 directory_hg_path,
418 fs_entry,
419 fs_entry,
419 )
420 )
420 }
421 }
421 }
422 }
422 Ok(has_dirstate_node_or_is_ignored)
423 Ok(has_dirstate_node_or_is_ignored)
423 })
424 })
424 .try_reduce(|| true, |a, b| Ok(a && b))
425 .try_reduce(|| true, |a, b| Ok(a && b))
425 }
426 }
426
427
427 fn traverse_fs_and_dirstate(
428 fn traverse_fs_and_dirstate(
428 &self,
429 &self,
429 fs_path: &Path,
430 fs_path: &Path,
430 fs_metadata: &std::fs::Metadata,
431 fs_metadata: &std::fs::Metadata,
431 dirstate_node: NodeRef<'tree, 'on_disk>,
432 dirstate_node: NodeRef<'tree, 'on_disk>,
432 has_ignored_ancestor: bool,
433 has_ignored_ancestor: bool,
433 ) -> Result<(), DirstateV2ParseError> {
434 ) -> Result<(), DirstateV2ParseError> {
434 self.check_for_outdated_directory_cache(&dirstate_node)?;
435 self.check_for_outdated_directory_cache(&dirstate_node)?;
435 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
436 let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
436 let file_type = fs_metadata.file_type();
437 let file_type = fs_metadata.file_type();
437 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
438 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
438 if !file_or_symlink {
439 if !file_or_symlink {
439 // If we previously had a file here, it was removed (with
440 // If we previously had a file here, it was removed (with
440 // `hg rm` or similar) or deleted before it could be
441 // `hg rm` or similar) or deleted before it could be
441 // replaced by a directory or something else.
442 // replaced by a directory or something else.
442 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
443 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
443 }
444 }
444 if file_type.is_dir() {
445 if file_type.is_dir() {
445 if self.options.collect_traversed_dirs {
446 if self.options.collect_traversed_dirs {
446 self.outcome
447 self.outcome
447 .lock()
448 .lock()
448 .unwrap()
449 .unwrap()
449 .traversed
450 .traversed
450 .push(hg_path.detach_from_tree())
451 .push(hg_path.detach_from_tree())
451 }
452 }
452 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
453 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(hg_path);
453 let is_at_repo_root = false;
454 let is_at_repo_root = false;
454 let children_all_have_dirstate_node_or_are_ignored = self
455 let children_all_have_dirstate_node_or_are_ignored = self
455 .traverse_fs_directory_and_dirstate(
456 .traverse_fs_directory_and_dirstate(
456 is_ignored,
457 is_ignored,
457 dirstate_node.children(self.dmap.on_disk)?,
458 dirstate_node.children(self.dmap.on_disk)?,
458 hg_path,
459 hg_path,
459 fs_path,
460 fs_path,
460 Some(fs_metadata),
461 Some(fs_metadata),
461 dirstate_node.cached_directory_mtime()?,
462 dirstate_node.cached_directory_mtime()?,
462 is_at_repo_root,
463 is_at_repo_root,
463 )?;
464 )?;
464 self.maybe_save_directory_mtime(
465 self.maybe_save_directory_mtime(
465 children_all_have_dirstate_node_or_are_ignored,
466 children_all_have_dirstate_node_or_are_ignored,
466 fs_metadata,
467 fs_metadata,
467 dirstate_node,
468 dirstate_node,
468 )?
469 )?
469 } else {
470 } else {
470 if file_or_symlink && self.matcher.matches(hg_path) {
471 if file_or_symlink && self.matcher.matches(hg_path) {
471 if let Some(state) = dirstate_node.state()? {
472 if let Some(state) = dirstate_node.state()? {
472 match state {
473 match state {
473 EntryState::Added => {
474 EntryState::Added => {
474 self.push_outcome(Outcome::Added, &dirstate_node)?
475 self.push_outcome(Outcome::Added, &dirstate_node)?
475 }
476 }
476 EntryState::Removed => self
477 EntryState::Removed => self
477 .push_outcome(Outcome::Removed, &dirstate_node)?,
478 .push_outcome(Outcome::Removed, &dirstate_node)?,
478 EntryState::Merged => self
479 EntryState::Merged => self
479 .push_outcome(Outcome::Modified, &dirstate_node)?,
480 .push_outcome(Outcome::Modified, &dirstate_node)?,
480 EntryState::Normal => self
481 EntryState::Normal => self
481 .handle_normal_file(&dirstate_node, fs_metadata)?,
482 .handle_normal_file(&dirstate_node, fs_metadata)?,
482 }
483 }
483 } else {
484 } else {
484 // `node.entry.is_none()` indicates a "directory"
485 // `node.entry.is_none()` indicates a "directory"
485 // node, but the filesystem has a file
486 // node, but the filesystem has a file
486 self.mark_unknown_or_ignored(
487 self.mark_unknown_or_ignored(
487 has_ignored_ancestor,
488 has_ignored_ancestor,
488 hg_path,
489 hg_path,
489 );
490 );
490 }
491 }
491 }
492 }
492
493
493 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
494 for child_node in dirstate_node.children(self.dmap.on_disk)?.iter()
494 {
495 {
495 self.traverse_dirstate_only(child_node)?
496 self.traverse_dirstate_only(child_node)?
496 }
497 }
497 }
498 }
498 Ok(())
499 Ok(())
499 }
500 }
500
501
501 fn maybe_save_directory_mtime(
502 fn maybe_save_directory_mtime(
502 &self,
503 &self,
503 children_all_have_dirstate_node_or_are_ignored: bool,
504 children_all_have_dirstate_node_or_are_ignored: bool,
504 directory_metadata: &std::fs::Metadata,
505 directory_metadata: &std::fs::Metadata,
505 dirstate_node: NodeRef<'tree, 'on_disk>,
506 dirstate_node: NodeRef<'tree, 'on_disk>,
506 ) -> Result<(), DirstateV2ParseError> {
507 ) -> Result<(), DirstateV2ParseError> {
507 if !children_all_have_dirstate_node_or_are_ignored {
508 if !children_all_have_dirstate_node_or_are_ignored {
508 return Ok(());
509 return Ok(());
509 }
510 }
510 // All filesystem directory entries from `read_dir` have a
511 // All filesystem directory entries from `read_dir` have a
511 // corresponding node in the dirstate, so we can reconstitute the
512 // corresponding node in the dirstate, so we can reconstitute the
512 // names of those entries without calling `read_dir` again.
513 // names of those entries without calling `read_dir` again.
513
514
514 // TODO: use let-else here and below when available:
515 // TODO: use let-else here and below when available:
515 // https://github.com/rust-lang/rust/issues/87335
516 // https://github.com/rust-lang/rust/issues/87335
516 let status_start = if let Some(status_start) =
517 let status_start = if let Some(status_start) =
517 &self.filesystem_time_at_status_start
518 &self.filesystem_time_at_status_start
518 {
519 {
519 status_start
520 status_start
520 } else {
521 } else {
521 return Ok(());
522 return Ok(());
522 };
523 };
523
524
524 // Although the Rust standard library’s `SystemTime` type
525 // Although the Rust standard library’s `SystemTime` type
525 // has nanosecond precision, the times reported for a
526 // has nanosecond precision, the times reported for a
526 // directory’s (or file’s) modified time may have lower
527 // directory’s (or file’s) modified time may have lower
527 // resolution based on the filesystem (for example ext3
528 // resolution based on the filesystem (for example ext3
528 // only stores integer seconds), kernel (see
529 // only stores integer seconds), kernel (see
529 // https://stackoverflow.com/a/14393315/1162888), etc.
530 // https://stackoverflow.com/a/14393315/1162888), etc.
530 let directory_mtime = if let Ok(option) =
531 let directory_mtime = if let Ok(option) =
531 TruncatedTimestamp::for_reliable_mtime_of(
532 TruncatedTimestamp::for_reliable_mtime_of(
532 directory_metadata,
533 directory_metadata,
533 status_start,
534 status_start,
534 ) {
535 ) {
535 if let Some(directory_mtime) = option {
536 if let Some(directory_mtime) = option {
536 directory_mtime
537 directory_mtime
537 } else {
538 } else {
538 // The directory was modified too recently,
539 // The directory was modified too recently,
539 // don’t cache its `read_dir` results.
540 // don’t cache its `read_dir` results.
540 //
541 //
541 // 1. A change to this directory (direct child was
542 // 1. A change to this directory (direct child was
542 // added or removed) cause its mtime to be set
543 // added or removed) cause its mtime to be set
543 // (possibly truncated) to `directory_mtime`
544 // (possibly truncated) to `directory_mtime`
544 // 2. This `status` algorithm calls `read_dir`
545 // 2. This `status` algorithm calls `read_dir`
545 // 3. An other change is made to the same directory is
546 // 3. An other change is made to the same directory is
546 // made so that calling `read_dir` agin would give
547 // made so that calling `read_dir` agin would give
547 // different results, but soon enough after 1. that
548 // different results, but soon enough after 1. that
548 // the mtime stays the same
549 // the mtime stays the same
549 //
550 //
550 // On a system where the time resolution poor, this
551 // On a system where the time resolution poor, this
551 // scenario is not unlikely if all three steps are caused
552 // scenario is not unlikely if all three steps are caused
552 // by the same script.
553 // by the same script.
553 return Ok(());
554 return Ok(());
554 }
555 }
555 } else {
556 } else {
556 // OS/libc does not support mtime?
557 // OS/libc does not support mtime?
557 return Ok(());
558 return Ok(());
558 };
559 };
559 // We’ve observed (through `status_start`) that time has
560 // We’ve observed (through `status_start`) that time has
560 // “progressed” since `directory_mtime`, so any further
561 // “progressed” since `directory_mtime`, so any further
561 // change to this directory is extremely likely to cause a
562 // change to this directory is extremely likely to cause a
562 // different mtime.
563 // different mtime.
563 //
564 //
564 // Having the same mtime again is not entirely impossible
565 // Having the same mtime again is not entirely impossible
565 // since the system clock is not monotonous. It could jump
566 // since the system clock is not monotonous. It could jump
566 // backward to some point before `directory_mtime`, then a
567 // backward to some point before `directory_mtime`, then a
567 // directory change could potentially happen during exactly
568 // directory change could potentially happen during exactly
568 // the wrong tick.
569 // the wrong tick.
569 //
570 //
570 // We deem this scenario (unlike the previous one) to be
571 // We deem this scenario (unlike the previous one) to be
571 // unlikely enough in practice.
572 // unlikely enough in practice.
572
573
573 let is_up_to_date =
574 let is_up_to_date =
574 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
575 if let Some(cached) = dirstate_node.cached_directory_mtime()? {
575 cached.likely_equal(directory_mtime)
576 cached.likely_equal(directory_mtime)
576 } else {
577 } else {
577 false
578 false
578 };
579 };
579 if !is_up_to_date {
580 if !is_up_to_date {
580 let hg_path = dirstate_node
581 let hg_path = dirstate_node
581 .full_path_borrowed(self.dmap.on_disk)?
582 .full_path_borrowed(self.dmap.on_disk)?
582 .detach_from_tree();
583 .detach_from_tree();
583 self.new_cachable_directories
584 self.new_cachable_directories
584 .lock()
585 .lock()
585 .unwrap()
586 .unwrap()
586 .push((hg_path, directory_mtime))
587 .push((hg_path, directory_mtime))
587 }
588 }
588 Ok(())
589 Ok(())
589 }
590 }
590
591
591 /// A file with `EntryState::Normal` in the dirstate was found in the
592 /// A file with `EntryState::Normal` in the dirstate was found in the
592 /// filesystem
593 /// filesystem
593 fn handle_normal_file(
594 fn handle_normal_file(
594 &self,
595 &self,
595 dirstate_node: &NodeRef<'tree, 'on_disk>,
596 dirstate_node: &NodeRef<'tree, 'on_disk>,
596 fs_metadata: &std::fs::Metadata,
597 fs_metadata: &std::fs::Metadata,
597 ) -> Result<(), DirstateV2ParseError> {
598 ) -> Result<(), DirstateV2ParseError> {
598 // Keep the low 31 bits
599 // Keep the low 31 bits
599 fn truncate_u64(value: u64) -> i32 {
600 fn truncate_u64(value: u64) -> i32 {
600 (value & 0x7FFF_FFFF) as i32
601 (value & 0x7FFF_FFFF) as i32
601 }
602 }
602
603
603 let entry = dirstate_node
604 let entry = dirstate_node
604 .entry()?
605 .entry()?
605 .expect("handle_normal_file called with entry-less node");
606 .expect("handle_normal_file called with entry-less node");
606 let mode_changed =
607 let mode_changed =
607 || self.options.check_exec && entry.mode_changed(fs_metadata);
608 || self.options.check_exec && entry.mode_changed(fs_metadata);
608 let size = entry.size();
609 let size = entry.size();
609 let size_changed = size != truncate_u64(fs_metadata.len());
610 let size_changed = size != truncate_u64(fs_metadata.len());
610 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
611 if size >= 0 && size_changed && fs_metadata.file_type().is_symlink() {
611 // issue6456: Size returned may be longer due to encryption
612 // issue6456: Size returned may be longer due to encryption
612 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
613 // on EXT-4 fscrypt. TODO maybe only do it on EXT4?
613 self.push_outcome(Outcome::Unsure, dirstate_node)?
614 self.push_outcome(Outcome::Unsure, dirstate_node)?
614 } else if dirstate_node.has_copy_source()
615 } else if dirstate_node.has_copy_source()
615 || entry.is_from_other_parent()
616 || entry.is_from_other_parent()
616 || (size >= 0 && (size_changed || mode_changed()))
617 || (size >= 0 && (size_changed || mode_changed()))
617 {
618 {
618 self.push_outcome(Outcome::Modified, dirstate_node)?
619 self.push_outcome(Outcome::Modified, dirstate_node)?
619 } else {
620 } else {
620 let mtime_looks_clean;
621 let mtime_looks_clean;
621 if let Some(dirstate_mtime) = entry.truncated_mtime() {
622 if let Some(dirstate_mtime) = entry.truncated_mtime() {
622 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata)
623 let fs_mtime = TruncatedTimestamp::for_mtime_of(fs_metadata)
623 .expect("OS/libc does not support mtime?");
624 .expect("OS/libc does not support mtime?");
624 // There might be a change in the future if for example the
625 // There might be a change in the future if for example the
625 // internal clock become off while process run, but this is a
626 // internal clock become off while process run, but this is a
626 // case where the issues the user would face
627 // case where the issues the user would face
627 // would be a lot worse and there is nothing we
628 // would be a lot worse and there is nothing we
628 // can really do.
629 // can really do.
629 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
630 mtime_looks_clean = fs_mtime.likely_equal(dirstate_mtime)
630 } else {
631 } else {
631 // No mtime in the dirstate entry
632 // No mtime in the dirstate entry
632 mtime_looks_clean = false
633 mtime_looks_clean = false
633 };
634 };
634 if !mtime_looks_clean {
635 if !mtime_looks_clean {
635 self.push_outcome(Outcome::Unsure, dirstate_node)?
636 self.push_outcome(Outcome::Unsure, dirstate_node)?
636 } else if self.options.list_clean {
637 } else if self.options.list_clean {
637 self.push_outcome(Outcome::Clean, dirstate_node)?
638 self.push_outcome(Outcome::Clean, dirstate_node)?
638 }
639 }
639 }
640 }
640 Ok(())
641 Ok(())
641 }
642 }
642
643
643 /// A node in the dirstate tree has no corresponding filesystem entry
644 /// A node in the dirstate tree has no corresponding filesystem entry
644 fn traverse_dirstate_only(
645 fn traverse_dirstate_only(
645 &self,
646 &self,
646 dirstate_node: NodeRef<'tree, 'on_disk>,
647 dirstate_node: NodeRef<'tree, 'on_disk>,
647 ) -> Result<(), DirstateV2ParseError> {
648 ) -> Result<(), DirstateV2ParseError> {
648 self.check_for_outdated_directory_cache(&dirstate_node)?;
649 self.check_for_outdated_directory_cache(&dirstate_node)?;
649 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
650 self.mark_removed_or_deleted_if_file(&dirstate_node)?;
650 dirstate_node
651 dirstate_node
651 .children(self.dmap.on_disk)?
652 .children(self.dmap.on_disk)?
652 .par_iter()
653 .par_iter()
653 .map(|child_node| self.traverse_dirstate_only(child_node))
654 .map(|child_node| self.traverse_dirstate_only(child_node))
654 .collect()
655 .collect()
655 }
656 }
656
657
657 /// A node in the dirstate tree has no corresponding *file* on the
658 /// A node in the dirstate tree has no corresponding *file* on the
658 /// filesystem
659 /// filesystem
659 ///
660 ///
660 /// Does nothing on a "directory" node
661 /// Does nothing on a "directory" node
661 fn mark_removed_or_deleted_if_file(
662 fn mark_removed_or_deleted_if_file(
662 &self,
663 &self,
663 dirstate_node: &NodeRef<'tree, 'on_disk>,
664 dirstate_node: &NodeRef<'tree, 'on_disk>,
664 ) -> Result<(), DirstateV2ParseError> {
665 ) -> Result<(), DirstateV2ParseError> {
665 if let Some(state) = dirstate_node.state()? {
666 if let Some(state) = dirstate_node.state()? {
666 let path = dirstate_node.full_path(self.dmap.on_disk)?;
667 let path = dirstate_node.full_path(self.dmap.on_disk)?;
667 if self.matcher.matches(path) {
668 if self.matcher.matches(path) {
668 if let EntryState::Removed = state {
669 if let EntryState::Removed = state {
669 self.push_outcome(Outcome::Removed, dirstate_node)?
670 self.push_outcome(Outcome::Removed, dirstate_node)?
670 } else {
671 } else {
671 self.push_outcome(Outcome::Deleted, &dirstate_node)?
672 self.push_outcome(Outcome::Deleted, &dirstate_node)?
672 }
673 }
673 }
674 }
674 }
675 }
675 Ok(())
676 Ok(())
676 }
677 }
677
678
678 /// Something in the filesystem has no corresponding dirstate node
679 /// Something in the filesystem has no corresponding dirstate node
679 ///
680 ///
680 /// Returns whether that path is ignored
681 /// Returns whether that path is ignored
681 fn traverse_fs_only(
682 fn traverse_fs_only(
682 &self,
683 &self,
683 has_ignored_ancestor: bool,
684 has_ignored_ancestor: bool,
684 directory_hg_path: &HgPath,
685 directory_hg_path: &HgPath,
685 fs_entry: &DirEntry,
686 fs_entry: &DirEntry,
686 ) -> bool {
687 ) -> bool {
687 let hg_path = directory_hg_path.join(&fs_entry.base_name);
688 let hg_path = directory_hg_path.join(&fs_entry.base_name);
688 let file_type = fs_entry.metadata.file_type();
689 let file_type = fs_entry.metadata.file_type();
689 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
690 let file_or_symlink = file_type.is_file() || file_type.is_symlink();
690 if file_type.is_dir() {
691 if file_type.is_dir() {
691 let is_ignored =
692 let is_ignored =
692 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
693 has_ignored_ancestor || (self.ignore_fn)(&hg_path);
693 let traverse_children = if is_ignored {
694 let traverse_children = if is_ignored {
694 // Descendants of an ignored directory are all ignored
695 // Descendants of an ignored directory are all ignored
695 self.options.list_ignored
696 self.options.list_ignored
696 } else {
697 } else {
697 // Descendants of an unknown directory may be either unknown or
698 // Descendants of an unknown directory may be either unknown or
698 // ignored
699 // ignored
699 self.options.list_unknown || self.options.list_ignored
700 self.options.list_unknown || self.options.list_ignored
700 };
701 };
701 if traverse_children {
702 if traverse_children {
702 let is_at_repo_root = false;
703 let is_at_repo_root = false;
703 if let Ok(children_fs_entries) = self.read_dir(
704 if let Ok(children_fs_entries) = self.read_dir(
704 &hg_path,
705 &hg_path,
705 &fs_entry.full_path,
706 &fs_entry.full_path,
706 is_at_repo_root,
707 is_at_repo_root,
707 ) {
708 ) {
708 children_fs_entries.par_iter().for_each(|child_fs_entry| {
709 children_fs_entries.par_iter().for_each(|child_fs_entry| {
709 self.traverse_fs_only(
710 self.traverse_fs_only(
710 is_ignored,
711 is_ignored,
711 &hg_path,
712 &hg_path,
712 child_fs_entry,
713 child_fs_entry,
713 );
714 );
714 })
715 })
715 }
716 }
716 }
717 }
717 if self.options.collect_traversed_dirs {
718 if self.options.collect_traversed_dirs {
718 self.outcome.lock().unwrap().traversed.push(hg_path.into())
719 self.outcome.lock().unwrap().traversed.push(hg_path.into())
719 }
720 }
720 is_ignored
721 is_ignored
721 } else {
722 } else {
722 if file_or_symlink {
723 if file_or_symlink {
723 if self.matcher.matches(&hg_path) {
724 if self.matcher.matches(&hg_path) {
724 self.mark_unknown_or_ignored(
725 self.mark_unknown_or_ignored(
725 has_ignored_ancestor,
726 has_ignored_ancestor,
726 &BorrowedPath::InMemory(&hg_path),
727 &BorrowedPath::InMemory(&hg_path),
727 )
728 )
728 } else {
729 } else {
729 // We haven’t computed whether this path is ignored. It
730 // We haven’t computed whether this path is ignored. It
730 // might not be, and a future run of status might have a
731 // might not be, and a future run of status might have a
731 // different matcher that matches it. So treat it as not
732 // different matcher that matches it. So treat it as not
732 // ignored. That is, inhibit readdir caching of the parent
733 // ignored. That is, inhibit readdir caching of the parent
733 // directory.
734 // directory.
734 false
735 false
735 }
736 }
736 } else {
737 } else {
737 // This is neither a directory, a plain file, or a symlink.
738 // This is neither a directory, a plain file, or a symlink.
738 // Treat it like an ignored file.
739 // Treat it like an ignored file.
739 true
740 true
740 }
741 }
741 }
742 }
742 }
743 }
743
744
744 /// Returns whether that path is ignored
745 /// Returns whether that path is ignored
745 fn mark_unknown_or_ignored(
746 fn mark_unknown_or_ignored(
746 &self,
747 &self,
747 has_ignored_ancestor: bool,
748 has_ignored_ancestor: bool,
748 hg_path: &BorrowedPath<'_, 'on_disk>,
749 hg_path: &BorrowedPath<'_, 'on_disk>,
749 ) -> bool {
750 ) -> bool {
750 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
751 let is_ignored = has_ignored_ancestor || (self.ignore_fn)(&hg_path);
751 if is_ignored {
752 if is_ignored {
752 if self.options.list_ignored {
753 if self.options.list_ignored {
753 self.push_outcome_without_copy_source(
754 self.push_outcome_without_copy_source(
754 Outcome::Ignored,
755 Outcome::Ignored,
755 hg_path,
756 hg_path,
756 )
757 )
757 }
758 }
758 } else {
759 } else {
759 if self.options.list_unknown {
760 if self.options.list_unknown {
760 self.push_outcome_without_copy_source(
761 self.push_outcome_without_copy_source(
761 Outcome::Unknown,
762 Outcome::Unknown,
762 hg_path,
763 hg_path,
763 )
764 )
764 }
765 }
765 }
766 }
766 is_ignored
767 is_ignored
767 }
768 }
768 }
769 }
769
770
770 struct DirEntry {
771 struct DirEntry {
771 base_name: HgPathBuf,
772 base_name: HgPathBuf,
772 full_path: PathBuf,
773 full_path: PathBuf,
773 metadata: std::fs::Metadata,
774 metadata: std::fs::Metadata,
774 }
775 }
775
776
776 impl DirEntry {
777 impl DirEntry {
777 /// Returns **unsorted** entries in the given directory, with name and
778 /// Returns **unsorted** entries in the given directory, with name and
778 /// metadata.
779 /// metadata.
779 ///
780 ///
780 /// If a `.hg` sub-directory is encountered:
781 /// If a `.hg` sub-directory is encountered:
781 ///
782 ///
782 /// * At the repository root, ignore that sub-directory
783 /// * At the repository root, ignore that sub-directory
783 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
784 /// * Elsewhere, we’re listing the content of a sub-repo. Return an empty
784 /// list instead.
785 /// list instead.
785 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
786 fn read_dir(path: &Path, is_at_repo_root: bool) -> io::Result<Vec<Self>> {
786 // `read_dir` returns a "not found" error for the empty path
787 // `read_dir` returns a "not found" error for the empty path
787 let at_cwd = path == Path::new("");
788 let at_cwd = path == Path::new("");
788 let read_dir_path = if at_cwd { Path::new(".") } else { path };
789 let read_dir_path = if at_cwd { Path::new(".") } else { path };
789 let mut results = Vec::new();
790 let mut results = Vec::new();
790 for entry in read_dir_path.read_dir()? {
791 for entry in read_dir_path.read_dir()? {
791 let entry = entry?;
792 let entry = entry?;
792 let metadata = match entry.metadata() {
793 let metadata = match entry.metadata() {
793 Ok(v) => v,
794 Ok(v) => v,
794 Err(e) => {
795 Err(e) => {
795 // race with file deletion?
796 // race with file deletion?
796 if e.kind() == std::io::ErrorKind::NotFound {
797 if e.kind() == std::io::ErrorKind::NotFound {
797 continue;
798 continue;
798 } else {
799 } else {
799 return Err(e);
800 return Err(e);
800 }
801 }
801 }
802 }
802 };
803 };
803 let file_name = entry.file_name();
804 let file_name = entry.file_name();
804 // FIXME don't do this when cached
805 // FIXME don't do this when cached
805 if file_name == ".hg" {
806 if file_name == ".hg" {
806 if is_at_repo_root {
807 if is_at_repo_root {
807 // Skip the repo’s own .hg (might be a symlink)
808 // Skip the repo’s own .hg (might be a symlink)
808 continue;
809 continue;
809 } else if metadata.is_dir() {
810 } else if metadata.is_dir() {
810 // A .hg sub-directory at another location means a subrepo,
811 // A .hg sub-directory at another location means a subrepo,
811 // skip it entirely.
812 // skip it entirely.
812 return Ok(Vec::new());
813 return Ok(Vec::new());
813 }
814 }
814 }
815 }
815 let full_path = if at_cwd {
816 let full_path = if at_cwd {
816 file_name.clone().into()
817 file_name.clone().into()
817 } else {
818 } else {
818 entry.path()
819 entry.path()
819 };
820 };
820 let base_name = get_bytes_from_os_string(file_name).into();
821 let base_name = get_bytes_from_os_string(file_name).into();
821 results.push(DirEntry {
822 results.push(DirEntry {
822 base_name,
823 base_name,
823 full_path,
824 full_path,
824 metadata,
825 metadata,
825 })
826 })
826 }
827 }
827 Ok(results)
828 Ok(results)
828 }
829 }
829 }
830 }
830
831
831 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
832 /// Return the `mtime` of a temporary file newly-created in the `.hg` directory
832 /// of the give repository.
833 /// of the give repository.
833 ///
834 ///
834 /// This is similar to `SystemTime::now()`, with the result truncated to the
835 /// This is similar to `SystemTime::now()`, with the result truncated to the
835 /// same time resolution as other files’ modification times. Using `.hg`
836 /// same time resolution as other files’ modification times. Using `.hg`
836 /// instead of the system’s default temporary directory (such as `/tmp`) makes
837 /// instead of the system’s default temporary directory (such as `/tmp`) makes
837 /// it more likely the temporary file is in the same disk partition as contents
838 /// it more likely the temporary file is in the same disk partition as contents
838 /// of the working directory, which can matter since different filesystems may
839 /// of the working directory, which can matter since different filesystems may
839 /// store timestamps with different resolutions.
840 /// store timestamps with different resolutions.
840 ///
841 ///
841 /// This may fail, typically if we lack write permissions. In that case we
842 /// This may fail, typically if we lack write permissions. In that case we
842 /// should continue the `status()` algoritm anyway and consider the current
843 /// should continue the `status()` algoritm anyway and consider the current
843 /// date/time to be unknown.
844 /// date/time to be unknown.
844 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
845 fn filesystem_now(repo_root: &Path) -> Result<SystemTime, io::Error> {
845 tempfile::tempfile_in(repo_root.join(".hg"))?
846 tempfile::tempfile_in(repo_root.join(".hg"))?
846 .metadata()?
847 .metadata()?
847 .modified()
848 .modified()
848 }
849 }
@@ -1,516 +1,509 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
4 use crate::dirstate_tree::on_disk::Docket as DirstateDocket;
6 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
7 use crate::errors::HgResultExt;
6 use crate::errors::HgResultExt;
8 use crate::errors::{HgError, IoResultExt};
7 use crate::errors::{HgError, IoResultExt};
9 use crate::lock::{try_with_lock_no_wait, LockError};
8 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::manifest::{Manifest, Manifestlog};
9 use crate::manifest::{Manifest, Manifestlog};
11 use crate::revlog::filelog::Filelog;
10 use crate::revlog::filelog::Filelog;
12 use crate::revlog::revlog::RevlogError;
11 use crate::revlog::revlog::RevlogError;
13 use crate::utils::files::get_path_from_bytes;
12 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
13 use crate::utils::hg_path::HgPath;
15 use crate::utils::SliceExt;
14 use crate::utils::SliceExt;
16 use crate::vfs::{is_dir, is_file, Vfs};
15 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::{requirements, NodePrefix};
16 use crate::{requirements, NodePrefix};
18 use crate::{DirstateError, Revision};
17 use crate::{DirstateError, Revision};
19 use std::cell::{Ref, RefCell, RefMut};
18 use std::cell::{Ref, RefCell, RefMut};
20 use std::collections::HashSet;
19 use std::collections::HashSet;
21 use std::io::Seek;
20 use std::io::Seek;
22 use std::io::SeekFrom;
21 use std::io::SeekFrom;
23 use std::io::Write as IoWrite;
22 use std::io::Write as IoWrite;
24 use std::path::{Path, PathBuf};
23 use std::path::{Path, PathBuf};
25
24
26 /// A repository on disk
25 /// A repository on disk
27 pub struct Repo {
26 pub struct Repo {
28 working_directory: PathBuf,
27 working_directory: PathBuf,
29 dot_hg: PathBuf,
28 dot_hg: PathBuf,
30 store: PathBuf,
29 store: PathBuf,
31 requirements: HashSet<String>,
30 requirements: HashSet<String>,
32 config: Config,
31 config: Config,
33 dirstate_parents: LazyCell<DirstateParents, HgError>,
32 dirstate_parents: LazyCell<DirstateParents, HgError>,
34 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
33 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
35 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
34 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
36 changelog: LazyCell<Changelog, HgError>,
35 changelog: LazyCell<Changelog, HgError>,
37 manifestlog: LazyCell<Manifestlog, HgError>,
36 manifestlog: LazyCell<Manifestlog, HgError>,
38 }
37 }
39
38
40 #[derive(Debug, derive_more::From)]
39 #[derive(Debug, derive_more::From)]
41 pub enum RepoError {
40 pub enum RepoError {
42 NotFound {
41 NotFound {
43 at: PathBuf,
42 at: PathBuf,
44 },
43 },
45 #[from]
44 #[from]
46 ConfigParseError(ConfigParseError),
45 ConfigParseError(ConfigParseError),
47 #[from]
46 #[from]
48 Other(HgError),
47 Other(HgError),
49 }
48 }
50
49
51 impl From<ConfigError> for RepoError {
50 impl From<ConfigError> for RepoError {
52 fn from(error: ConfigError) -> Self {
51 fn from(error: ConfigError) -> Self {
53 match error {
52 match error {
54 ConfigError::Parse(error) => error.into(),
53 ConfigError::Parse(error) => error.into(),
55 ConfigError::Other(error) => error.into(),
54 ConfigError::Other(error) => error.into(),
56 }
55 }
57 }
56 }
58 }
57 }
59
58
60 impl Repo {
59 impl Repo {
61 /// tries to find nearest repository root in current working directory or
60 /// tries to find nearest repository root in current working directory or
62 /// its ancestors
61 /// its ancestors
63 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
62 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
64 let current_directory = crate::utils::current_dir()?;
63 let current_directory = crate::utils::current_dir()?;
65 // ancestors() is inclusive: it first yields `current_directory`
64 // ancestors() is inclusive: it first yields `current_directory`
66 // as-is.
65 // as-is.
67 for ancestor in current_directory.ancestors() {
66 for ancestor in current_directory.ancestors() {
68 if is_dir(ancestor.join(".hg"))? {
67 if is_dir(ancestor.join(".hg"))? {
69 return Ok(ancestor.to_path_buf());
68 return Ok(ancestor.to_path_buf());
70 }
69 }
71 }
70 }
72 return Err(RepoError::NotFound {
71 return Err(RepoError::NotFound {
73 at: current_directory,
72 at: current_directory,
74 });
73 });
75 }
74 }
76
75
77 /// Find a repository, either at the given path (which must contain a `.hg`
76 /// Find a repository, either at the given path (which must contain a `.hg`
78 /// sub-directory) or by searching the current directory and its
77 /// sub-directory) or by searching the current directory and its
79 /// ancestors.
78 /// ancestors.
80 ///
79 ///
81 /// A method with two very different "modes" like this usually a code smell
80 /// A method with two very different "modes" like this usually a code smell
82 /// to make two methods instead, but in this case an `Option` is what rhg
81 /// to make two methods instead, but in this case an `Option` is what rhg
83 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
82 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
84 /// Having two methods would just move that `if` to almost all callers.
83 /// Having two methods would just move that `if` to almost all callers.
85 pub fn find(
84 pub fn find(
86 config: &Config,
85 config: &Config,
87 explicit_path: Option<PathBuf>,
86 explicit_path: Option<PathBuf>,
88 ) -> Result<Self, RepoError> {
87 ) -> Result<Self, RepoError> {
89 if let Some(root) = explicit_path {
88 if let Some(root) = explicit_path {
90 if is_dir(root.join(".hg"))? {
89 if is_dir(root.join(".hg"))? {
91 Self::new_at_path(root.to_owned(), config)
90 Self::new_at_path(root.to_owned(), config)
92 } else if is_file(&root)? {
91 } else if is_file(&root)? {
93 Err(HgError::unsupported("bundle repository").into())
92 Err(HgError::unsupported("bundle repository").into())
94 } else {
93 } else {
95 Err(RepoError::NotFound {
94 Err(RepoError::NotFound {
96 at: root.to_owned(),
95 at: root.to_owned(),
97 })
96 })
98 }
97 }
99 } else {
98 } else {
100 let root = Self::find_repo_root()?;
99 let root = Self::find_repo_root()?;
101 Self::new_at_path(root, config)
100 Self::new_at_path(root, config)
102 }
101 }
103 }
102 }
104
103
105 /// To be called after checking that `.hg` is a sub-directory
104 /// To be called after checking that `.hg` is a sub-directory
106 fn new_at_path(
105 fn new_at_path(
107 working_directory: PathBuf,
106 working_directory: PathBuf,
108 config: &Config,
107 config: &Config,
109 ) -> Result<Self, RepoError> {
108 ) -> Result<Self, RepoError> {
110 let dot_hg = working_directory.join(".hg");
109 let dot_hg = working_directory.join(".hg");
111
110
112 let mut repo_config_files = Vec::new();
111 let mut repo_config_files = Vec::new();
113 repo_config_files.push(dot_hg.join("hgrc"));
112 repo_config_files.push(dot_hg.join("hgrc"));
114 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
113 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
115
114
116 let hg_vfs = Vfs { base: &dot_hg };
115 let hg_vfs = Vfs { base: &dot_hg };
117 let mut reqs = requirements::load_if_exists(hg_vfs)?;
116 let mut reqs = requirements::load_if_exists(hg_vfs)?;
118 let relative =
117 let relative =
119 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
118 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
120 let shared =
119 let shared =
121 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
120 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
122
121
123 // From `mercurial/localrepo.py`:
122 // From `mercurial/localrepo.py`:
124 //
123 //
125 // if .hg/requires contains the sharesafe requirement, it means
124 // if .hg/requires contains the sharesafe requirement, it means
126 // there exists a `.hg/store/requires` too and we should read it
125 // there exists a `.hg/store/requires` too and we should read it
127 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
126 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
128 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
127 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
129 // is not present, refer checkrequirementscompat() for that
128 // is not present, refer checkrequirementscompat() for that
130 //
129 //
131 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
130 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
132 // repository was shared the old way. We check the share source
131 // repository was shared the old way. We check the share source
133 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
132 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
134 // current repository needs to be reshared
133 // current repository needs to be reshared
135 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
134 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
136
135
137 let store_path;
136 let store_path;
138 if !shared {
137 if !shared {
139 store_path = dot_hg.join("store");
138 store_path = dot_hg.join("store");
140 } else {
139 } else {
141 let bytes = hg_vfs.read("sharedpath")?;
140 let bytes = hg_vfs.read("sharedpath")?;
142 let mut shared_path =
141 let mut shared_path =
143 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
142 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
144 .to_owned();
143 .to_owned();
145 if relative {
144 if relative {
146 shared_path = dot_hg.join(shared_path)
145 shared_path = dot_hg.join(shared_path)
147 }
146 }
148 if !is_dir(&shared_path)? {
147 if !is_dir(&shared_path)? {
149 return Err(HgError::corrupted(format!(
148 return Err(HgError::corrupted(format!(
150 ".hg/sharedpath points to nonexistent directory {}",
149 ".hg/sharedpath points to nonexistent directory {}",
151 shared_path.display()
150 shared_path.display()
152 ))
151 ))
153 .into());
152 .into());
154 }
153 }
155
154
156 store_path = shared_path.join("store");
155 store_path = shared_path.join("store");
157
156
158 let source_is_share_safe =
157 let source_is_share_safe =
159 requirements::load(Vfs { base: &shared_path })?
158 requirements::load(Vfs { base: &shared_path })?
160 .contains(requirements::SHARESAFE_REQUIREMENT);
159 .contains(requirements::SHARESAFE_REQUIREMENT);
161
160
162 if share_safe != source_is_share_safe {
161 if share_safe != source_is_share_safe {
163 return Err(HgError::unsupported("share-safe mismatch").into());
162 return Err(HgError::unsupported("share-safe mismatch").into());
164 }
163 }
165
164
166 if share_safe {
165 if share_safe {
167 repo_config_files.insert(0, shared_path.join("hgrc"))
166 repo_config_files.insert(0, shared_path.join("hgrc"))
168 }
167 }
169 }
168 }
170 if share_safe {
169 if share_safe {
171 reqs.extend(requirements::load(Vfs { base: &store_path })?);
170 reqs.extend(requirements::load(Vfs { base: &store_path })?);
172 }
171 }
173
172
174 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
173 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
175 config.combine_with_repo(&repo_config_files)?
174 config.combine_with_repo(&repo_config_files)?
176 } else {
175 } else {
177 config.clone()
176 config.clone()
178 };
177 };
179
178
180 let repo = Self {
179 let repo = Self {
181 requirements: reqs,
180 requirements: reqs,
182 working_directory,
181 working_directory,
183 store: store_path,
182 store: store_path,
184 dot_hg,
183 dot_hg,
185 config: repo_config,
184 config: repo_config,
186 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
185 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
187 dirstate_data_file_uuid: LazyCell::new(
186 dirstate_data_file_uuid: LazyCell::new(
188 Self::read_dirstate_data_file_uuid,
187 Self::read_dirstate_data_file_uuid,
189 ),
188 ),
190 dirstate_map: LazyCell::new(Self::new_dirstate_map),
189 dirstate_map: LazyCell::new(Self::new_dirstate_map),
191 changelog: LazyCell::new(Changelog::open),
190 changelog: LazyCell::new(Changelog::open),
192 manifestlog: LazyCell::new(Manifestlog::open),
191 manifestlog: LazyCell::new(Manifestlog::open),
193 };
192 };
194
193
195 requirements::check(&repo)?;
194 requirements::check(&repo)?;
196
195
197 Ok(repo)
196 Ok(repo)
198 }
197 }
199
198
200 pub fn working_directory_path(&self) -> &Path {
199 pub fn working_directory_path(&self) -> &Path {
201 &self.working_directory
200 &self.working_directory
202 }
201 }
203
202
204 pub fn requirements(&self) -> &HashSet<String> {
203 pub fn requirements(&self) -> &HashSet<String> {
205 &self.requirements
204 &self.requirements
206 }
205 }
207
206
208 pub fn config(&self) -> &Config {
207 pub fn config(&self) -> &Config {
209 &self.config
208 &self.config
210 }
209 }
211
210
212 /// For accessing repository files (in `.hg`), except for the store
211 /// For accessing repository files (in `.hg`), except for the store
213 /// (`.hg/store`).
212 /// (`.hg/store`).
214 pub fn hg_vfs(&self) -> Vfs<'_> {
213 pub fn hg_vfs(&self) -> Vfs<'_> {
215 Vfs { base: &self.dot_hg }
214 Vfs { base: &self.dot_hg }
216 }
215 }
217
216
218 /// For accessing repository store files (in `.hg/store`)
217 /// For accessing repository store files (in `.hg/store`)
219 pub fn store_vfs(&self) -> Vfs<'_> {
218 pub fn store_vfs(&self) -> Vfs<'_> {
220 Vfs { base: &self.store }
219 Vfs { base: &self.store }
221 }
220 }
222
221
223 /// For accessing the working copy
222 /// For accessing the working copy
224 pub fn working_directory_vfs(&self) -> Vfs<'_> {
223 pub fn working_directory_vfs(&self) -> Vfs<'_> {
225 Vfs {
224 Vfs {
226 base: &self.working_directory,
225 base: &self.working_directory,
227 }
226 }
228 }
227 }
229
228
230 pub fn try_with_wlock_no_wait<R>(
229 pub fn try_with_wlock_no_wait<R>(
231 &self,
230 &self,
232 f: impl FnOnce() -> R,
231 f: impl FnOnce() -> R,
233 ) -> Result<R, LockError> {
232 ) -> Result<R, LockError> {
234 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
233 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
235 }
234 }
236
235
237 pub fn has_dirstate_v2(&self) -> bool {
236 pub fn has_dirstate_v2(&self) -> bool {
238 self.requirements
237 self.requirements
239 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
238 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
240 }
239 }
241
240
242 pub fn has_sparse(&self) -> bool {
241 pub fn has_sparse(&self) -> bool {
243 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
242 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
244 }
243 }
245
244
246 pub fn has_narrow(&self) -> bool {
245 pub fn has_narrow(&self) -> bool {
247 self.requirements.contains(requirements::NARROW_REQUIREMENT)
246 self.requirements.contains(requirements::NARROW_REQUIREMENT)
248 }
247 }
249
248
250 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
249 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
251 Ok(self
250 Ok(self
252 .hg_vfs()
251 .hg_vfs()
253 .read("dirstate")
252 .read("dirstate")
254 .io_not_found_as_none()?
253 .io_not_found_as_none()?
255 .unwrap_or(Vec::new()))
254 .unwrap_or(Vec::new()))
256 }
255 }
257
256
258 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
257 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
259 Ok(*self.dirstate_parents.get_or_init(self)?)
258 Ok(*self.dirstate_parents.get_or_init(self)?)
260 }
259 }
261
260
262 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
261 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
263 let dirstate = self.dirstate_file_contents()?;
262 let dirstate = self.dirstate_file_contents()?;
264 let parents = if dirstate.is_empty() {
263 let parents = if dirstate.is_empty() {
265 if self.has_dirstate_v2() {
264 if self.has_dirstate_v2() {
266 self.dirstate_data_file_uuid.set(None);
265 self.dirstate_data_file_uuid.set(None);
267 }
266 }
268 DirstateParents::NULL
267 DirstateParents::NULL
269 } else if self.has_dirstate_v2() {
268 } else if self.has_dirstate_v2() {
270 let docket =
269 let docket =
271 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
270 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
272 self.dirstate_data_file_uuid
271 self.dirstate_data_file_uuid
273 .set(Some(docket.uuid.to_owned()));
272 .set(Some(docket.uuid.to_owned()));
274 docket.parents()
273 docket.parents()
275 } else {
274 } else {
276 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
275 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
277 .clone()
276 .clone()
278 };
277 };
279 self.dirstate_parents.set(parents);
278 self.dirstate_parents.set(parents);
280 Ok(parents)
279 Ok(parents)
281 }
280 }
282
281
283 fn read_dirstate_data_file_uuid(
282 fn read_dirstate_data_file_uuid(
284 &self,
283 &self,
285 ) -> Result<Option<Vec<u8>>, HgError> {
284 ) -> Result<Option<Vec<u8>>, HgError> {
286 assert!(
285 assert!(
287 self.has_dirstate_v2(),
286 self.has_dirstate_v2(),
288 "accessing dirstate data file ID without dirstate-v2"
287 "accessing dirstate data file ID without dirstate-v2"
289 );
288 );
290 let dirstate = self.dirstate_file_contents()?;
289 let dirstate = self.dirstate_file_contents()?;
291 if dirstate.is_empty() {
290 if dirstate.is_empty() {
292 self.dirstate_parents.set(DirstateParents::NULL);
291 self.dirstate_parents.set(DirstateParents::NULL);
293 Ok(None)
292 Ok(None)
294 } else {
293 } else {
295 let docket =
294 let docket =
296 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
295 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
297 self.dirstate_parents.set(docket.parents());
296 self.dirstate_parents.set(docket.parents());
298 Ok(Some(docket.uuid.to_owned()))
297 Ok(Some(docket.uuid.to_owned()))
299 }
298 }
300 }
299 }
301
300
302 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
301 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
303 let dirstate_file_contents = self.dirstate_file_contents()?;
302 let dirstate_file_contents = self.dirstate_file_contents()?;
304 if dirstate_file_contents.is_empty() {
303 if dirstate_file_contents.is_empty() {
305 self.dirstate_parents.set(DirstateParents::NULL);
304 self.dirstate_parents.set(DirstateParents::NULL);
306 if self.has_dirstate_v2() {
305 if self.has_dirstate_v2() {
307 self.dirstate_data_file_uuid.set(None);
306 self.dirstate_data_file_uuid.set(None);
308 }
307 }
309 Ok(OwningDirstateMap::new_empty(Vec::new()))
308 Ok(OwningDirstateMap::new_empty(Vec::new()))
310 } else if self.has_dirstate_v2() {
309 } else if self.has_dirstate_v2() {
311 let docket = crate::dirstate_tree::on_disk::read_docket(
310 let docket = crate::dirstate_tree::on_disk::read_docket(
312 &dirstate_file_contents,
311 &dirstate_file_contents,
313 )?;
312 )?;
314 self.dirstate_parents.set(docket.parents());
313 self.dirstate_parents.set(docket.parents());
315 self.dirstate_data_file_uuid
314 self.dirstate_data_file_uuid
316 .set(Some(docket.uuid.to_owned()));
315 .set(Some(docket.uuid.to_owned()));
317 let data_size = docket.data_size();
316 let data_size = docket.data_size();
318 let metadata = docket.tree_metadata();
317 let metadata = docket.tree_metadata();
319 let mut map = if let Some(data_mmap) = self
318 if let Some(data_mmap) = self
320 .hg_vfs()
319 .hg_vfs()
321 .mmap_open(docket.data_filename())
320 .mmap_open(docket.data_filename())
322 .io_not_found_as_none()?
321 .io_not_found_as_none()?
323 {
322 {
324 OwningDirstateMap::new_empty(data_mmap)
323 OwningDirstateMap::new_v2(data_mmap, data_size, metadata)
325 } else {
324 } else {
326 OwningDirstateMap::new_empty(Vec::new())
325 OwningDirstateMap::new_v2(Vec::new(), data_size, metadata)
327 };
326 }
328 let (on_disk, placeholder) = map.get_pair_mut();
329 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
330 Ok(map)
331 } else {
327 } else {
332 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
328 let (map, parents) =
333 let (on_disk, placeholder) = map.get_pair_mut();
329 OwningDirstateMap::new_v1(dirstate_file_contents)?;
334 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
330 self.dirstate_parents.set(parents);
335 self.dirstate_parents
336 .set(parents.unwrap_or(DirstateParents::NULL));
337 *placeholder = inner;
338 Ok(map)
331 Ok(map)
339 }
332 }
340 }
333 }
341
334
342 pub fn dirstate_map(
335 pub fn dirstate_map(
343 &self,
336 &self,
344 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
337 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
345 self.dirstate_map.get_or_init(self)
338 self.dirstate_map.get_or_init(self)
346 }
339 }
347
340
348 pub fn dirstate_map_mut(
341 pub fn dirstate_map_mut(
349 &self,
342 &self,
350 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
343 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
351 self.dirstate_map.get_mut_or_init(self)
344 self.dirstate_map.get_mut_or_init(self)
352 }
345 }
353
346
354 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
347 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
355 self.changelog.get_or_init(self)
348 self.changelog.get_or_init(self)
356 }
349 }
357
350
358 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
351 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
359 self.changelog.get_mut_or_init(self)
352 self.changelog.get_mut_or_init(self)
360 }
353 }
361
354
362 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
355 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
363 self.manifestlog.get_or_init(self)
356 self.manifestlog.get_or_init(self)
364 }
357 }
365
358
366 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
359 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
367 self.manifestlog.get_mut_or_init(self)
360 self.manifestlog.get_mut_or_init(self)
368 }
361 }
369
362
370 /// Returns the manifest of the *changeset* with the given node ID
363 /// Returns the manifest of the *changeset* with the given node ID
371 pub fn manifest_for_node(
364 pub fn manifest_for_node(
372 &self,
365 &self,
373 node: impl Into<NodePrefix>,
366 node: impl Into<NodePrefix>,
374 ) -> Result<Manifest, RevlogError> {
367 ) -> Result<Manifest, RevlogError> {
375 self.manifestlog()?.data_for_node(
368 self.manifestlog()?.data_for_node(
376 self.changelog()?
369 self.changelog()?
377 .data_for_node(node.into())?
370 .data_for_node(node.into())?
378 .manifest_node()?
371 .manifest_node()?
379 .into(),
372 .into(),
380 )
373 )
381 }
374 }
382
375
383 /// Returns the manifest of the *changeset* with the given revision number
376 /// Returns the manifest of the *changeset* with the given revision number
384 pub fn manifest_for_rev(
377 pub fn manifest_for_rev(
385 &self,
378 &self,
386 revision: Revision,
379 revision: Revision,
387 ) -> Result<Manifest, RevlogError> {
380 ) -> Result<Manifest, RevlogError> {
388 self.manifestlog()?.data_for_node(
381 self.manifestlog()?.data_for_node(
389 self.changelog()?
382 self.changelog()?
390 .data_for_rev(revision)?
383 .data_for_rev(revision)?
391 .manifest_node()?
384 .manifest_node()?
392 .into(),
385 .into(),
393 )
386 )
394 }
387 }
395
388
396 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
389 pub fn has_subrepos(&self) -> Result<bool, DirstateError> {
397 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
390 if let Some(entry) = self.dirstate_map()?.get(HgPath::new(".hgsub"))? {
398 Ok(entry.state().is_tracked())
391 Ok(entry.state().is_tracked())
399 } else {
392 } else {
400 Ok(false)
393 Ok(false)
401 }
394 }
402 }
395 }
403
396
404 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
397 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
405 Filelog::open(self, path)
398 Filelog::open(self, path)
406 }
399 }
407
400
408 /// Write to disk any updates that were made through `dirstate_map_mut`.
401 /// Write to disk any updates that were made through `dirstate_map_mut`.
409 ///
402 ///
410 /// The "wlock" must be held while calling this.
403 /// The "wlock" must be held while calling this.
411 /// See for example `try_with_wlock_no_wait`.
404 /// See for example `try_with_wlock_no_wait`.
412 ///
405 ///
413 /// TODO: have a `WritableRepo` type only accessible while holding the
406 /// TODO: have a `WritableRepo` type only accessible while holding the
414 /// lock?
407 /// lock?
415 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
408 pub fn write_dirstate(&self) -> Result<(), DirstateError> {
416 let map = self.dirstate_map()?;
409 let map = self.dirstate_map()?;
417 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
410 // TODO: Maintain a `DirstateMap::dirty` flag, and return early here if
418 // it’s unset
411 // it’s unset
419 let parents = self.dirstate_parents()?;
412 let parents = self.dirstate_parents()?;
420 let packed_dirstate = if self.has_dirstate_v2() {
413 let packed_dirstate = if self.has_dirstate_v2() {
421 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
414 let uuid = self.dirstate_data_file_uuid.get_or_init(self)?;
422 let mut uuid = uuid.as_ref();
415 let mut uuid = uuid.as_ref();
423 let can_append = uuid.is_some();
416 let can_append = uuid.is_some();
424 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
417 let (data, tree_metadata, append) = map.pack_v2(can_append)?;
425 if !append {
418 if !append {
426 uuid = None
419 uuid = None
427 }
420 }
428 let uuid = if let Some(uuid) = uuid {
421 let uuid = if let Some(uuid) = uuid {
429 std::str::from_utf8(uuid)
422 std::str::from_utf8(uuid)
430 .map_err(|_| {
423 .map_err(|_| {
431 HgError::corrupted("non-UTF-8 dirstate data file ID")
424 HgError::corrupted("non-UTF-8 dirstate data file ID")
432 })?
425 })?
433 .to_owned()
426 .to_owned()
434 } else {
427 } else {
435 DirstateDocket::new_uid()
428 DirstateDocket::new_uid()
436 };
429 };
437 let data_filename = format!("dirstate.{}", uuid);
430 let data_filename = format!("dirstate.{}", uuid);
438 let data_filename = self.hg_vfs().join(data_filename);
431 let data_filename = self.hg_vfs().join(data_filename);
439 let mut options = std::fs::OpenOptions::new();
432 let mut options = std::fs::OpenOptions::new();
440 if append {
433 if append {
441 options.append(true);
434 options.append(true);
442 } else {
435 } else {
443 options.write(true).create_new(true);
436 options.write(true).create_new(true);
444 }
437 }
445 let data_size = (|| {
438 let data_size = (|| {
446 // TODO: loop and try another random ID if !append and this
439 // TODO: loop and try another random ID if !append and this
447 // returns `ErrorKind::AlreadyExists`? Collision chance of two
440 // returns `ErrorKind::AlreadyExists`? Collision chance of two
448 // random IDs is one in 2**32
441 // random IDs is one in 2**32
449 let mut file = options.open(&data_filename)?;
442 let mut file = options.open(&data_filename)?;
450 file.write_all(&data)?;
443 file.write_all(&data)?;
451 file.flush()?;
444 file.flush()?;
452 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
445 // TODO: use https://doc.rust-lang.org/std/io/trait.Seek.html#method.stream_position when we require Rust 1.51+
453 file.seek(SeekFrom::Current(0))
446 file.seek(SeekFrom::Current(0))
454 })()
447 })()
455 .when_writing_file(&data_filename)?;
448 .when_writing_file(&data_filename)?;
456 DirstateDocket::serialize(
449 DirstateDocket::serialize(
457 parents,
450 parents,
458 tree_metadata,
451 tree_metadata,
459 data_size,
452 data_size,
460 uuid.as_bytes(),
453 uuid.as_bytes(),
461 )
454 )
462 .map_err(|_: std::num::TryFromIntError| {
455 .map_err(|_: std::num::TryFromIntError| {
463 HgError::corrupted("overflow in dirstate docket serialization")
456 HgError::corrupted("overflow in dirstate docket serialization")
464 })?
457 })?
465 } else {
458 } else {
466 map.pack_v1(parents)?
459 map.pack_v1(parents)?
467 };
460 };
468 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
461 self.hg_vfs().atomic_write("dirstate", &packed_dirstate)?;
469 Ok(())
462 Ok(())
470 }
463 }
471 }
464 }
472
465
473 /// Lazily-initialized component of `Repo` with interior mutability
466 /// Lazily-initialized component of `Repo` with interior mutability
474 ///
467 ///
475 /// This differs from `OnceCell` in that the value can still be "deinitialized"
468 /// This differs from `OnceCell` in that the value can still be "deinitialized"
476 /// later by setting its inner `Option` to `None`.
469 /// later by setting its inner `Option` to `None`.
477 struct LazyCell<T, E> {
470 struct LazyCell<T, E> {
478 value: RefCell<Option<T>>,
471 value: RefCell<Option<T>>,
479 // `Fn`s that don’t capture environment are zero-size, so this box does
472 // `Fn`s that don’t capture environment are zero-size, so this box does
480 // not allocate:
473 // not allocate:
481 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
474 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
482 }
475 }
483
476
484 impl<T, E> LazyCell<T, E> {
477 impl<T, E> LazyCell<T, E> {
485 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
478 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
486 Self {
479 Self {
487 value: RefCell::new(None),
480 value: RefCell::new(None),
488 init: Box::new(init),
481 init: Box::new(init),
489 }
482 }
490 }
483 }
491
484
492 fn set(&self, value: T) {
485 fn set(&self, value: T) {
493 *self.value.borrow_mut() = Some(value)
486 *self.value.borrow_mut() = Some(value)
494 }
487 }
495
488
496 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
489 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
497 let mut borrowed = self.value.borrow();
490 let mut borrowed = self.value.borrow();
498 if borrowed.is_none() {
491 if borrowed.is_none() {
499 drop(borrowed);
492 drop(borrowed);
500 // Only use `borrow_mut` if it is really needed to avoid panic in
493 // Only use `borrow_mut` if it is really needed to avoid panic in
501 // case there is another outstanding borrow but mutation is not
494 // case there is another outstanding borrow but mutation is not
502 // needed.
495 // needed.
503 *self.value.borrow_mut() = Some((self.init)(repo)?);
496 *self.value.borrow_mut() = Some((self.init)(repo)?);
504 borrowed = self.value.borrow()
497 borrowed = self.value.borrow()
505 }
498 }
506 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
499 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
507 }
500 }
508
501
509 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
502 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
510 let mut borrowed = self.value.borrow_mut();
503 let mut borrowed = self.value.borrow_mut();
511 if borrowed.is_none() {
504 if borrowed.is_none() {
512 *borrowed = Some((self.init)(repo)?);
505 *borrowed = Some((self.init)(repo)?);
513 }
506 }
514 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
507 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
515 }
508 }
516 }
509 }
@@ -1,820 +1,813 b''
1 // hg_path.rs
1 // hg_path.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
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 use crate::utils::SliceExt;
8 use crate::utils::SliceExt;
9 use std::borrow::Borrow;
9 use std::borrow::Borrow;
10 use std::borrow::Cow;
10 use std::borrow::Cow;
11 use std::convert::TryFrom;
11 use std::convert::TryFrom;
12 use std::ffi::{OsStr, OsString};
12 use std::ffi::{OsStr, OsString};
13 use std::fmt;
13 use std::fmt;
14 use std::ops::Deref;
14 use std::ops::Deref;
15 use std::path::{Path, PathBuf};
15 use std::path::{Path, PathBuf};
16
16
17 #[derive(Debug, Eq, PartialEq)]
17 #[derive(Debug, Eq, PartialEq)]
18 pub enum HgPathError {
18 pub enum HgPathError {
19 /// Bytes from the invalid `HgPath`
19 /// Bytes from the invalid `HgPath`
20 LeadingSlash(Vec<u8>),
20 LeadingSlash(Vec<u8>),
21 ConsecutiveSlashes {
21 ConsecutiveSlashes {
22 bytes: Vec<u8>,
22 bytes: Vec<u8>,
23 second_slash_index: usize,
23 second_slash_index: usize,
24 },
24 },
25 ContainsNullByte {
25 ContainsNullByte {
26 bytes: Vec<u8>,
26 bytes: Vec<u8>,
27 null_byte_index: usize,
27 null_byte_index: usize,
28 },
28 },
29 /// Bytes
29 /// Bytes
30 DecodeError(Vec<u8>),
30 DecodeError(Vec<u8>),
31 /// The rest come from audit errors
31 /// The rest come from audit errors
32 EndsWithSlash(HgPathBuf),
32 EndsWithSlash(HgPathBuf),
33 ContainsIllegalComponent(HgPathBuf),
33 ContainsIllegalComponent(HgPathBuf),
34 /// Path is inside the `.hg` folder
34 /// Path is inside the `.hg` folder
35 InsideDotHg(HgPathBuf),
35 InsideDotHg(HgPathBuf),
36 IsInsideNestedRepo {
36 IsInsideNestedRepo {
37 path: HgPathBuf,
37 path: HgPathBuf,
38 nested_repo: HgPathBuf,
38 nested_repo: HgPathBuf,
39 },
39 },
40 TraversesSymbolicLink {
40 TraversesSymbolicLink {
41 path: HgPathBuf,
41 path: HgPathBuf,
42 symlink: HgPathBuf,
42 symlink: HgPathBuf,
43 },
43 },
44 NotFsCompliant(HgPathBuf),
44 NotFsCompliant(HgPathBuf),
45 /// `path` is the smallest invalid path
45 /// `path` is the smallest invalid path
46 NotUnderRoot {
46 NotUnderRoot {
47 path: PathBuf,
47 path: PathBuf,
48 root: PathBuf,
48 root: PathBuf,
49 },
49 },
50 }
50 }
51
51
52 impl fmt::Display for HgPathError {
52 impl fmt::Display for HgPathError {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
53 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54 match self {
54 match self {
55 HgPathError::LeadingSlash(bytes) => {
55 HgPathError::LeadingSlash(bytes) => {
56 write!(f, "Invalid HgPath '{:?}': has a leading slash.", bytes)
56 write!(f, "Invalid HgPath '{:?}': has a leading slash.", bytes)
57 }
57 }
58 HgPathError::ConsecutiveSlashes {
58 HgPathError::ConsecutiveSlashes {
59 bytes,
59 bytes,
60 second_slash_index: pos,
60 second_slash_index: pos,
61 } => write!(
61 } => write!(
62 f,
62 f,
63 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
63 "Invalid HgPath '{:?}': consecutive slashes at pos {}.",
64 bytes, pos
64 bytes, pos
65 ),
65 ),
66 HgPathError::ContainsNullByte {
66 HgPathError::ContainsNullByte {
67 bytes,
67 bytes,
68 null_byte_index: pos,
68 null_byte_index: pos,
69 } => write!(
69 } => write!(
70 f,
70 f,
71 "Invalid HgPath '{:?}': contains null byte at pos {}.",
71 "Invalid HgPath '{:?}': contains null byte at pos {}.",
72 bytes, pos
72 bytes, pos
73 ),
73 ),
74 HgPathError::DecodeError(bytes) => write!(
74 HgPathError::DecodeError(bytes) => write!(
75 f,
75 f,
76 "Invalid HgPath '{:?}': could not be decoded.",
76 "Invalid HgPath '{:?}': could not be decoded.",
77 bytes
77 bytes
78 ),
78 ),
79 HgPathError::EndsWithSlash(path) => {
79 HgPathError::EndsWithSlash(path) => {
80 write!(f, "Audit failed for '{}': ends with a slash.", path)
80 write!(f, "Audit failed for '{}': ends with a slash.", path)
81 }
81 }
82 HgPathError::ContainsIllegalComponent(path) => write!(
82 HgPathError::ContainsIllegalComponent(path) => write!(
83 f,
83 f,
84 "Audit failed for '{}': contains an illegal component.",
84 "Audit failed for '{}': contains an illegal component.",
85 path
85 path
86 ),
86 ),
87 HgPathError::InsideDotHg(path) => write!(
87 HgPathError::InsideDotHg(path) => write!(
88 f,
88 f,
89 "Audit failed for '{}': is inside the '.hg' folder.",
89 "Audit failed for '{}': is inside the '.hg' folder.",
90 path
90 path
91 ),
91 ),
92 HgPathError::IsInsideNestedRepo {
92 HgPathError::IsInsideNestedRepo {
93 path,
93 path,
94 nested_repo: nested,
94 nested_repo: nested,
95 } => {
95 } => {
96 write!(f,
96 write!(f,
97 "Audit failed for '{}': is inside a nested repository '{}'.",
97 "Audit failed for '{}': is inside a nested repository '{}'.",
98 path, nested
98 path, nested
99 )
99 )
100 }
100 }
101 HgPathError::TraversesSymbolicLink { path, symlink } => write!(
101 HgPathError::TraversesSymbolicLink { path, symlink } => write!(
102 f,
102 f,
103 "Audit failed for '{}': traverses symbolic link '{}'.",
103 "Audit failed for '{}': traverses symbolic link '{}'.",
104 path, symlink
104 path, symlink
105 ),
105 ),
106 HgPathError::NotFsCompliant(path) => write!(
106 HgPathError::NotFsCompliant(path) => write!(
107 f,
107 f,
108 "Audit failed for '{}': cannot be turned into a \
108 "Audit failed for '{}': cannot be turned into a \
109 filesystem path.",
109 filesystem path.",
110 path
110 path
111 ),
111 ),
112 HgPathError::NotUnderRoot { path, root } => write!(
112 HgPathError::NotUnderRoot { path, root } => write!(
113 f,
113 f,
114 "Audit failed for '{}': not under root {}.",
114 "Audit failed for '{}': not under root {}.",
115 path.display(),
115 path.display(),
116 root.display()
116 root.display()
117 ),
117 ),
118 }
118 }
119 }
119 }
120 }
120 }
121
121
122 impl From<HgPathError> for std::io::Error {
122 impl From<HgPathError> for std::io::Error {
123 fn from(e: HgPathError) -> Self {
123 fn from(e: HgPathError) -> Self {
124 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
124 std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string())
125 }
125 }
126 }
126 }
127
127
128 /// This is a repository-relative path (or canonical path):
128 /// This is a repository-relative path (or canonical path):
129 /// - no null characters
129 /// - no null characters
130 /// - `/` separates directories
130 /// - `/` separates directories
131 /// - no consecutive slashes
131 /// - no consecutive slashes
132 /// - no leading slash,
132 /// - no leading slash,
133 /// - no `.` nor `..` of special meaning
133 /// - no `.` nor `..` of special meaning
134 /// - stored in repository and shared across platforms
134 /// - stored in repository and shared across platforms
135 ///
135 ///
136 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
136 /// Note: there is no guarantee of any `HgPath` being well-formed at any point
137 /// in its lifetime for performance reasons and to ease ergonomics. It is
137 /// in its lifetime for performance reasons and to ease ergonomics. It is
138 /// however checked using the `check_state` method before any file-system
138 /// however checked using the `check_state` method before any file-system
139 /// operation.
139 /// operation.
140 ///
140 ///
141 /// This allows us to be encoding-transparent as much as possible, until really
141 /// This allows us to be encoding-transparent as much as possible, until really
142 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
142 /// needed; `HgPath` can be transformed into a platform-specific path (`OsStr`
143 /// or `Path`) whenever more complex operations are needed:
143 /// or `Path`) whenever more complex operations are needed:
144 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
144 /// On Unix, it's just byte-to-byte conversion. On Windows, it has to be
145 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
145 /// decoded from MBCS to WTF-8. If WindowsUTF8Plan is implemented, the source
146 /// character encoding will be determined on a per-repository basis.
146 /// character encoding will be determined on a per-repository basis.
147 //
148 // FIXME: (adapted from a comment in the stdlib)
149 // `HgPath::new()` current implementation relies on `Slice` being
150 // layout-compatible with `[u8]`.
151 // When attribute privacy is implemented, `Slice` should be annotated as
152 // `#[repr(transparent)]`.
153 // Anyway, `Slice` representation and layout are considered implementation
154 // detail, are not documented and must not be relied upon.
155 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
147 #[derive(Eq, Ord, PartialEq, PartialOrd, Hash)]
148 #[repr(transparent)]
156 pub struct HgPath {
149 pub struct HgPath {
157 inner: [u8],
150 inner: [u8],
158 }
151 }
159
152
160 impl HgPath {
153 impl HgPath {
161 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
154 pub fn new<S: AsRef<[u8]> + ?Sized>(s: &S) -> &Self {
162 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
155 unsafe { &*(s.as_ref() as *const [u8] as *const Self) }
163 }
156 }
164 pub fn is_empty(&self) -> bool {
157 pub fn is_empty(&self) -> bool {
165 self.inner.is_empty()
158 self.inner.is_empty()
166 }
159 }
167 pub fn len(&self) -> usize {
160 pub fn len(&self) -> usize {
168 self.inner.len()
161 self.inner.len()
169 }
162 }
170 fn to_hg_path_buf(&self) -> HgPathBuf {
163 fn to_hg_path_buf(&self) -> HgPathBuf {
171 HgPathBuf {
164 HgPathBuf {
172 inner: self.inner.to_owned(),
165 inner: self.inner.to_owned(),
173 }
166 }
174 }
167 }
175 pub fn bytes(&self) -> std::slice::Iter<u8> {
168 pub fn bytes(&self) -> std::slice::Iter<u8> {
176 self.inner.iter()
169 self.inner.iter()
177 }
170 }
178 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
171 pub fn to_ascii_uppercase(&self) -> HgPathBuf {
179 HgPathBuf::from(self.inner.to_ascii_uppercase())
172 HgPathBuf::from(self.inner.to_ascii_uppercase())
180 }
173 }
181 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
174 pub fn to_ascii_lowercase(&self) -> HgPathBuf {
182 HgPathBuf::from(self.inner.to_ascii_lowercase())
175 HgPathBuf::from(self.inner.to_ascii_lowercase())
183 }
176 }
184 pub fn as_bytes(&self) -> &[u8] {
177 pub fn as_bytes(&self) -> &[u8] {
185 &self.inner
178 &self.inner
186 }
179 }
187 pub fn contains(&self, other: u8) -> bool {
180 pub fn contains(&self, other: u8) -> bool {
188 self.inner.contains(&other)
181 self.inner.contains(&other)
189 }
182 }
190 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
183 pub fn starts_with(&self, needle: impl AsRef<Self>) -> bool {
191 self.inner.starts_with(needle.as_ref().as_bytes())
184 self.inner.starts_with(needle.as_ref().as_bytes())
192 }
185 }
193 pub fn trim_trailing_slash(&self) -> &Self {
186 pub fn trim_trailing_slash(&self) -> &Self {
194 Self::new(if self.inner.last() == Some(&b'/') {
187 Self::new(if self.inner.last() == Some(&b'/') {
195 &self.inner[..self.inner.len() - 1]
188 &self.inner[..self.inner.len() - 1]
196 } else {
189 } else {
197 &self.inner[..]
190 &self.inner[..]
198 })
191 })
199 }
192 }
200 /// Returns a tuple of slices `(base, filename)` resulting from the split
193 /// Returns a tuple of slices `(base, filename)` resulting from the split
201 /// at the rightmost `/`, if any.
194 /// at the rightmost `/`, if any.
202 ///
195 ///
203 /// # Examples:
196 /// # Examples:
204 ///
197 ///
205 /// ```
198 /// ```
206 /// use hg::utils::hg_path::HgPath;
199 /// use hg::utils::hg_path::HgPath;
207 ///
200 ///
208 /// let path = HgPath::new(b"cool/hg/path").split_filename();
201 /// let path = HgPath::new(b"cool/hg/path").split_filename();
209 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
202 /// assert_eq!(path, (HgPath::new(b"cool/hg"), HgPath::new(b"path")));
210 ///
203 ///
211 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
204 /// let path = HgPath::new(b"pathwithoutsep").split_filename();
212 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
205 /// assert_eq!(path, (HgPath::new(b""), HgPath::new(b"pathwithoutsep")));
213 /// ```
206 /// ```
214 pub fn split_filename(&self) -> (&Self, &Self) {
207 pub fn split_filename(&self) -> (&Self, &Self) {
215 match &self.inner.iter().rposition(|c| *c == b'/') {
208 match &self.inner.iter().rposition(|c| *c == b'/') {
216 None => (HgPath::new(""), &self),
209 None => (HgPath::new(""), &self),
217 Some(size) => (
210 Some(size) => (
218 HgPath::new(&self.inner[..*size]),
211 HgPath::new(&self.inner[..*size]),
219 HgPath::new(&self.inner[*size + 1..]),
212 HgPath::new(&self.inner[*size + 1..]),
220 ),
213 ),
221 }
214 }
222 }
215 }
223
216
224 pub fn join(&self, path: &HgPath) -> HgPathBuf {
217 pub fn join(&self, path: &HgPath) -> HgPathBuf {
225 let mut buf = self.to_owned();
218 let mut buf = self.to_owned();
226 buf.push(path);
219 buf.push(path);
227 buf
220 buf
228 }
221 }
229
222
230 pub fn components(&self) -> impl Iterator<Item = &HgPath> {
223 pub fn components(&self) -> impl Iterator<Item = &HgPath> {
231 self.inner.split(|&byte| byte == b'/').map(HgPath::new)
224 self.inner.split(|&byte| byte == b'/').map(HgPath::new)
232 }
225 }
233
226
234 /// Returns the first (that is "root-most") slash-separated component of
227 /// Returns the first (that is "root-most") slash-separated component of
235 /// the path, and the rest after the first slash if there is one.
228 /// the path, and the rest after the first slash if there is one.
236 pub fn split_first_component(&self) -> (&HgPath, Option<&HgPath>) {
229 pub fn split_first_component(&self) -> (&HgPath, Option<&HgPath>) {
237 match self.inner.split_2(b'/') {
230 match self.inner.split_2(b'/') {
238 Some((a, b)) => (HgPath::new(a), Some(HgPath::new(b))),
231 Some((a, b)) => (HgPath::new(a), Some(HgPath::new(b))),
239 None => (self, None),
232 None => (self, None),
240 }
233 }
241 }
234 }
242
235
243 pub fn parent(&self) -> &Self {
236 pub fn parent(&self) -> &Self {
244 let inner = self.as_bytes();
237 let inner = self.as_bytes();
245 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
238 HgPath::new(match inner.iter().rposition(|b| *b == b'/') {
246 Some(pos) => &inner[..pos],
239 Some(pos) => &inner[..pos],
247 None => &[],
240 None => &[],
248 })
241 })
249 }
242 }
250 /// Given a base directory, returns the slice of `self` relative to the
243 /// Given a base directory, returns the slice of `self` relative to the
251 /// base directory. If `base` is not a directory (does not end with a
244 /// base directory. If `base` is not a directory (does not end with a
252 /// `b'/'`), returns `None`.
245 /// `b'/'`), returns `None`.
253 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
246 pub fn relative_to(&self, base: impl AsRef<Self>) -> Option<&Self> {
254 let base = base.as_ref();
247 let base = base.as_ref();
255 if base.is_empty() {
248 if base.is_empty() {
256 return Some(self);
249 return Some(self);
257 }
250 }
258 let is_dir = base.as_bytes().ends_with(b"/");
251 let is_dir = base.as_bytes().ends_with(b"/");
259 if is_dir && self.starts_with(base) {
252 if is_dir && self.starts_with(base) {
260 Some(Self::new(&self.inner[base.len()..]))
253 Some(Self::new(&self.inner[base.len()..]))
261 } else {
254 } else {
262 None
255 None
263 }
256 }
264 }
257 }
265
258
266 #[cfg(windows)]
259 #[cfg(windows)]
267 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
260 /// Copied from the Python stdlib's `os.path.splitdrive` implementation.
268 ///
261 ///
269 /// Split a pathname into drive/UNC sharepoint and relative path
262 /// Split a pathname into drive/UNC sharepoint and relative path
270 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
263 /// specifiers. Returns a 2-tuple (drive_or_unc, path); either part may
271 /// be empty.
264 /// be empty.
272 ///
265 ///
273 /// If you assign
266 /// If you assign
274 /// result = split_drive(p)
267 /// result = split_drive(p)
275 /// It is always true that:
268 /// It is always true that:
276 /// result[0] + result[1] == p
269 /// result[0] + result[1] == p
277 ///
270 ///
278 /// If the path contained a drive letter, drive_or_unc will contain
271 /// If the path contained a drive letter, drive_or_unc will contain
279 /// everything up to and including the colon.
272 /// everything up to and including the colon.
280 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
273 /// e.g. split_drive("c:/dir") returns ("c:", "/dir")
281 ///
274 ///
282 /// If the path contained a UNC path, the drive_or_unc will contain the
275 /// If the path contained a UNC path, the drive_or_unc will contain the
283 /// host name and share up to but not including the fourth directory
276 /// host name and share up to but not including the fourth directory
284 /// separator character.
277 /// separator character.
285 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
278 /// e.g. split_drive("//host/computer/dir") returns ("//host/computer",
286 /// "/dir")
279 /// "/dir")
287 ///
280 ///
288 /// Paths cannot contain both a drive letter and a UNC path.
281 /// Paths cannot contain both a drive letter and a UNC path.
289 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
282 pub fn split_drive<'a>(&self) -> (&HgPath, &HgPath) {
290 let bytes = self.as_bytes();
283 let bytes = self.as_bytes();
291 let is_sep = |b| std::path::is_separator(b as char);
284 let is_sep = |b| std::path::is_separator(b as char);
292
285
293 if self.len() < 2 {
286 if self.len() < 2 {
294 (HgPath::new(b""), &self)
287 (HgPath::new(b""), &self)
295 } else if is_sep(bytes[0])
288 } else if is_sep(bytes[0])
296 && is_sep(bytes[1])
289 && is_sep(bytes[1])
297 && (self.len() == 2 || !is_sep(bytes[2]))
290 && (self.len() == 2 || !is_sep(bytes[2]))
298 {
291 {
299 // Is a UNC path:
292 // Is a UNC path:
300 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
293 // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
301 // \\machine\mountpoint\directory\etc\...
294 // \\machine\mountpoint\directory\etc\...
302 // directory ^^^^^^^^^^^^^^^
295 // directory ^^^^^^^^^^^^^^^
303
296
304 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
297 let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
305 let mountpoint_start_index = if let Some(i) = machine_end_index {
298 let mountpoint_start_index = if let Some(i) = machine_end_index {
306 i + 2
299 i + 2
307 } else {
300 } else {
308 return (HgPath::new(b""), &self);
301 return (HgPath::new(b""), &self);
309 };
302 };
310
303
311 match bytes[mountpoint_start_index + 1..]
304 match bytes[mountpoint_start_index + 1..]
312 .iter()
305 .iter()
313 .position(|b| is_sep(*b))
306 .position(|b| is_sep(*b))
314 {
307 {
315 // A UNC path can't have two slashes in a row
308 // A UNC path can't have two slashes in a row
316 // (after the initial two)
309 // (after the initial two)
317 Some(0) => (HgPath::new(b""), &self),
310 Some(0) => (HgPath::new(b""), &self),
318 Some(i) => {
311 Some(i) => {
319 let (a, b) =
312 let (a, b) =
320 bytes.split_at(mountpoint_start_index + 1 + i);
313 bytes.split_at(mountpoint_start_index + 1 + i);
321 (HgPath::new(a), HgPath::new(b))
314 (HgPath::new(a), HgPath::new(b))
322 }
315 }
323 None => (&self, HgPath::new(b"")),
316 None => (&self, HgPath::new(b"")),
324 }
317 }
325 } else if bytes[1] == b':' {
318 } else if bytes[1] == b':' {
326 // Drive path c:\directory
319 // Drive path c:\directory
327 let (a, b) = bytes.split_at(2);
320 let (a, b) = bytes.split_at(2);
328 (HgPath::new(a), HgPath::new(b))
321 (HgPath::new(a), HgPath::new(b))
329 } else {
322 } else {
330 (HgPath::new(b""), &self)
323 (HgPath::new(b""), &self)
331 }
324 }
332 }
325 }
333
326
334 #[cfg(unix)]
327 #[cfg(unix)]
335 /// Split a pathname into drive and path. On Posix, drive is always empty.
328 /// Split a pathname into drive and path. On Posix, drive is always empty.
336 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
329 pub fn split_drive(&self) -> (&HgPath, &HgPath) {
337 (HgPath::new(b""), &self)
330 (HgPath::new(b""), &self)
338 }
331 }
339
332
340 /// Checks for errors in the path, short-circuiting at the first one.
333 /// Checks for errors in the path, short-circuiting at the first one.
341 /// This generates fine-grained errors useful for debugging.
334 /// This generates fine-grained errors useful for debugging.
342 /// To simply check if the path is valid during tests, use `is_valid`.
335 /// To simply check if the path is valid during tests, use `is_valid`.
343 pub fn check_state(&self) -> Result<(), HgPathError> {
336 pub fn check_state(&self) -> Result<(), HgPathError> {
344 if self.is_empty() {
337 if self.is_empty() {
345 return Ok(());
338 return Ok(());
346 }
339 }
347 let bytes = self.as_bytes();
340 let bytes = self.as_bytes();
348 let mut previous_byte = None;
341 let mut previous_byte = None;
349
342
350 if bytes[0] == b'/' {
343 if bytes[0] == b'/' {
351 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
344 return Err(HgPathError::LeadingSlash(bytes.to_vec()));
352 }
345 }
353 for (index, byte) in bytes.iter().enumerate() {
346 for (index, byte) in bytes.iter().enumerate() {
354 match byte {
347 match byte {
355 0 => {
348 0 => {
356 return Err(HgPathError::ContainsNullByte {
349 return Err(HgPathError::ContainsNullByte {
357 bytes: bytes.to_vec(),
350 bytes: bytes.to_vec(),
358 null_byte_index: index,
351 null_byte_index: index,
359 })
352 })
360 }
353 }
361 b'/' => {
354 b'/' => {
362 if previous_byte.is_some() && previous_byte == Some(b'/') {
355 if previous_byte.is_some() && previous_byte == Some(b'/') {
363 return Err(HgPathError::ConsecutiveSlashes {
356 return Err(HgPathError::ConsecutiveSlashes {
364 bytes: bytes.to_vec(),
357 bytes: bytes.to_vec(),
365 second_slash_index: index,
358 second_slash_index: index,
366 });
359 });
367 }
360 }
368 }
361 }
369 _ => (),
362 _ => (),
370 };
363 };
371 previous_byte = Some(*byte);
364 previous_byte = Some(*byte);
372 }
365 }
373 Ok(())
366 Ok(())
374 }
367 }
375
368
376 #[cfg(test)]
369 #[cfg(test)]
377 /// Only usable during tests to force developers to handle invalid states
370 /// Only usable during tests to force developers to handle invalid states
378 fn is_valid(&self) -> bool {
371 fn is_valid(&self) -> bool {
379 self.check_state().is_ok()
372 self.check_state().is_ok()
380 }
373 }
381 }
374 }
382
375
383 impl fmt::Debug for HgPath {
376 impl fmt::Debug for HgPath {
384 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
385 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
378 write!(f, "HgPath({:?})", String::from_utf8_lossy(&self.inner))
386 }
379 }
387 }
380 }
388
381
389 impl fmt::Display for HgPath {
382 impl fmt::Display for HgPath {
390 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
391 write!(f, "{}", String::from_utf8_lossy(&self.inner))
384 write!(f, "{}", String::from_utf8_lossy(&self.inner))
392 }
385 }
393 }
386 }
394
387
395 #[derive(
388 #[derive(
396 Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash, derive_more::From,
389 Default, Eq, Ord, Clone, PartialEq, PartialOrd, Hash, derive_more::From,
397 )]
390 )]
398 pub struct HgPathBuf {
391 pub struct HgPathBuf {
399 inner: Vec<u8>,
392 inner: Vec<u8>,
400 }
393 }
401
394
402 impl HgPathBuf {
395 impl HgPathBuf {
403 pub fn new() -> Self {
396 pub fn new() -> Self {
404 Default::default()
397 Default::default()
405 }
398 }
406
399
407 pub fn push<T: ?Sized + AsRef<HgPath>>(&mut self, other: &T) -> () {
400 pub fn push<T: ?Sized + AsRef<HgPath>>(&mut self, other: &T) -> () {
408 if !self.inner.is_empty() && self.inner.last() != Some(&b'/') {
401 if !self.inner.is_empty() && self.inner.last() != Some(&b'/') {
409 self.inner.push(b'/');
402 self.inner.push(b'/');
410 }
403 }
411 self.inner.extend(other.as_ref().bytes())
404 self.inner.extend(other.as_ref().bytes())
412 }
405 }
413
406
414 pub fn push_byte(&mut self, byte: u8) {
407 pub fn push_byte(&mut self, byte: u8) {
415 self.inner.push(byte);
408 self.inner.push(byte);
416 }
409 }
417 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
410 pub fn from_bytes(s: &[u8]) -> HgPathBuf {
418 HgPath::new(s).to_owned()
411 HgPath::new(s).to_owned()
419 }
412 }
420 pub fn into_vec(self) -> Vec<u8> {
413 pub fn into_vec(self) -> Vec<u8> {
421 self.inner
414 self.inner
422 }
415 }
423 }
416 }
424
417
425 impl fmt::Debug for HgPathBuf {
418 impl fmt::Debug for HgPathBuf {
426 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
427 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
420 write!(f, "HgPathBuf({:?})", String::from_utf8_lossy(&self.inner))
428 }
421 }
429 }
422 }
430
423
431 impl fmt::Display for HgPathBuf {
424 impl fmt::Display for HgPathBuf {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 write!(f, "{}", String::from_utf8_lossy(&self.inner))
426 write!(f, "{}", String::from_utf8_lossy(&self.inner))
434 }
427 }
435 }
428 }
436
429
437 impl Deref for HgPathBuf {
430 impl Deref for HgPathBuf {
438 type Target = HgPath;
431 type Target = HgPath;
439
432
440 #[inline]
433 #[inline]
441 fn deref(&self) -> &HgPath {
434 fn deref(&self) -> &HgPath {
442 &HgPath::new(&self.inner)
435 &HgPath::new(&self.inner)
443 }
436 }
444 }
437 }
445
438
446 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
439 impl<T: ?Sized + AsRef<HgPath>> From<&T> for HgPathBuf {
447 fn from(s: &T) -> HgPathBuf {
440 fn from(s: &T) -> HgPathBuf {
448 s.as_ref().to_owned()
441 s.as_ref().to_owned()
449 }
442 }
450 }
443 }
451
444
452 impl Into<Vec<u8>> for HgPathBuf {
445 impl Into<Vec<u8>> for HgPathBuf {
453 fn into(self) -> Vec<u8> {
446 fn into(self) -> Vec<u8> {
454 self.inner
447 self.inner
455 }
448 }
456 }
449 }
457
450
458 impl Borrow<HgPath> for HgPathBuf {
451 impl Borrow<HgPath> for HgPathBuf {
459 fn borrow(&self) -> &HgPath {
452 fn borrow(&self) -> &HgPath {
460 &HgPath::new(self.as_bytes())
453 &HgPath::new(self.as_bytes())
461 }
454 }
462 }
455 }
463
456
464 impl ToOwned for HgPath {
457 impl ToOwned for HgPath {
465 type Owned = HgPathBuf;
458 type Owned = HgPathBuf;
466
459
467 fn to_owned(&self) -> HgPathBuf {
460 fn to_owned(&self) -> HgPathBuf {
468 self.to_hg_path_buf()
461 self.to_hg_path_buf()
469 }
462 }
470 }
463 }
471
464
472 impl AsRef<HgPath> for HgPath {
465 impl AsRef<HgPath> for HgPath {
473 fn as_ref(&self) -> &HgPath {
466 fn as_ref(&self) -> &HgPath {
474 self
467 self
475 }
468 }
476 }
469 }
477
470
478 impl AsRef<HgPath> for HgPathBuf {
471 impl AsRef<HgPath> for HgPathBuf {
479 fn as_ref(&self) -> &HgPath {
472 fn as_ref(&self) -> &HgPath {
480 self
473 self
481 }
474 }
482 }
475 }
483
476
484 impl Extend<u8> for HgPathBuf {
477 impl Extend<u8> for HgPathBuf {
485 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
478 fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
486 self.inner.extend(iter);
479 self.inner.extend(iter);
487 }
480 }
488 }
481 }
489
482
490 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
483 /// TODO: Once https://www.mercurial-scm.org/wiki/WindowsUTF8Plan is
491 /// implemented, these conversion utils will have to work differently depending
484 /// implemented, these conversion utils will have to work differently depending
492 /// on the repository encoding: either `UTF-8` or `MBCS`.
485 /// on the repository encoding: either `UTF-8` or `MBCS`.
493
486
494 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
487 pub fn hg_path_to_os_string<P: AsRef<HgPath>>(
495 hg_path: P,
488 hg_path: P,
496 ) -> Result<OsString, HgPathError> {
489 ) -> Result<OsString, HgPathError> {
497 hg_path.as_ref().check_state()?;
490 hg_path.as_ref().check_state()?;
498 let os_str;
491 let os_str;
499 #[cfg(unix)]
492 #[cfg(unix)]
500 {
493 {
501 use std::os::unix::ffi::OsStrExt;
494 use std::os::unix::ffi::OsStrExt;
502 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
495 os_str = std::ffi::OsStr::from_bytes(&hg_path.as_ref().as_bytes());
503 }
496 }
504 // TODO Handle other platforms
497 // TODO Handle other platforms
505 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
498 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
506 Ok(os_str.to_os_string())
499 Ok(os_str.to_os_string())
507 }
500 }
508
501
509 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
502 pub fn hg_path_to_path_buf<P: AsRef<HgPath>>(
510 hg_path: P,
503 hg_path: P,
511 ) -> Result<PathBuf, HgPathError> {
504 ) -> Result<PathBuf, HgPathError> {
512 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
505 Ok(Path::new(&hg_path_to_os_string(hg_path)?).to_path_buf())
513 }
506 }
514
507
515 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
508 pub fn os_string_to_hg_path_buf<S: AsRef<OsStr>>(
516 os_string: S,
509 os_string: S,
517 ) -> Result<HgPathBuf, HgPathError> {
510 ) -> Result<HgPathBuf, HgPathError> {
518 let buf;
511 let buf;
519 #[cfg(unix)]
512 #[cfg(unix)]
520 {
513 {
521 use std::os::unix::ffi::OsStrExt;
514 use std::os::unix::ffi::OsStrExt;
522 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
515 buf = HgPathBuf::from_bytes(&os_string.as_ref().as_bytes());
523 }
516 }
524 // TODO Handle other platforms
517 // TODO Handle other platforms
525 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
518 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
526
519
527 buf.check_state()?;
520 buf.check_state()?;
528 Ok(buf)
521 Ok(buf)
529 }
522 }
530
523
531 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
524 pub fn path_to_hg_path_buf<P: AsRef<Path>>(
532 path: P,
525 path: P,
533 ) -> Result<HgPathBuf, HgPathError> {
526 ) -> Result<HgPathBuf, HgPathError> {
534 let buf;
527 let buf;
535 let os_str = path.as_ref().as_os_str();
528 let os_str = path.as_ref().as_os_str();
536 #[cfg(unix)]
529 #[cfg(unix)]
537 {
530 {
538 use std::os::unix::ffi::OsStrExt;
531 use std::os::unix::ffi::OsStrExt;
539 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
532 buf = HgPathBuf::from_bytes(&os_str.as_bytes());
540 }
533 }
541 // TODO Handle other platforms
534 // TODO Handle other platforms
542 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
535 // TODO: convert from WTF8 to Windows MBCS (ANSI encoding).
543
536
544 buf.check_state()?;
537 buf.check_state()?;
545 Ok(buf)
538 Ok(buf)
546 }
539 }
547
540
548 impl TryFrom<PathBuf> for HgPathBuf {
541 impl TryFrom<PathBuf> for HgPathBuf {
549 type Error = HgPathError;
542 type Error = HgPathError;
550 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
543 fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
551 path_to_hg_path_buf(path)
544 path_to_hg_path_buf(path)
552 }
545 }
553 }
546 }
554
547
555 impl From<HgPathBuf> for Cow<'_, HgPath> {
548 impl From<HgPathBuf> for Cow<'_, HgPath> {
556 fn from(path: HgPathBuf) -> Self {
549 fn from(path: HgPathBuf) -> Self {
557 Cow::Owned(path)
550 Cow::Owned(path)
558 }
551 }
559 }
552 }
560
553
561 impl<'a> From<&'a HgPath> for Cow<'a, HgPath> {
554 impl<'a> From<&'a HgPath> for Cow<'a, HgPath> {
562 fn from(path: &'a HgPath) -> Self {
555 fn from(path: &'a HgPath) -> Self {
563 Cow::Borrowed(path)
556 Cow::Borrowed(path)
564 }
557 }
565 }
558 }
566
559
567 impl<'a> From<&'a HgPathBuf> for Cow<'a, HgPath> {
560 impl<'a> From<&'a HgPathBuf> for Cow<'a, HgPath> {
568 fn from(path: &'a HgPathBuf) -> Self {
561 fn from(path: &'a HgPathBuf) -> Self {
569 Cow::Borrowed(&**path)
562 Cow::Borrowed(&**path)
570 }
563 }
571 }
564 }
572
565
573 #[cfg(test)]
566 #[cfg(test)]
574 mod tests {
567 mod tests {
575 use super::*;
568 use super::*;
576 use pretty_assertions::assert_eq;
569 use pretty_assertions::assert_eq;
577
570
578 #[test]
571 #[test]
579 fn test_path_states() {
572 fn test_path_states() {
580 assert_eq!(
573 assert_eq!(
581 Err(HgPathError::LeadingSlash(b"/".to_vec())),
574 Err(HgPathError::LeadingSlash(b"/".to_vec())),
582 HgPath::new(b"/").check_state()
575 HgPath::new(b"/").check_state()
583 );
576 );
584 assert_eq!(
577 assert_eq!(
585 Err(HgPathError::ConsecutiveSlashes {
578 Err(HgPathError::ConsecutiveSlashes {
586 bytes: b"a/b//c".to_vec(),
579 bytes: b"a/b//c".to_vec(),
587 second_slash_index: 4
580 second_slash_index: 4
588 }),
581 }),
589 HgPath::new(b"a/b//c").check_state()
582 HgPath::new(b"a/b//c").check_state()
590 );
583 );
591 assert_eq!(
584 assert_eq!(
592 Err(HgPathError::ContainsNullByte {
585 Err(HgPathError::ContainsNullByte {
593 bytes: b"a/b/\0c".to_vec(),
586 bytes: b"a/b/\0c".to_vec(),
594 null_byte_index: 4
587 null_byte_index: 4
595 }),
588 }),
596 HgPath::new(b"a/b/\0c").check_state()
589 HgPath::new(b"a/b/\0c").check_state()
597 );
590 );
598 // TODO test HgPathError::DecodeError for the Windows implementation.
591 // TODO test HgPathError::DecodeError for the Windows implementation.
599 assert_eq!(true, HgPath::new(b"").is_valid());
592 assert_eq!(true, HgPath::new(b"").is_valid());
600 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
593 assert_eq!(true, HgPath::new(b"a/b/c").is_valid());
601 // Backslashes in paths are not significant, but allowed
594 // Backslashes in paths are not significant, but allowed
602 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
595 assert_eq!(true, HgPath::new(br"a\b/c").is_valid());
603 // Dots in paths are not significant, but allowed
596 // Dots in paths are not significant, but allowed
604 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
597 assert_eq!(true, HgPath::new(b"a/b/../c/").is_valid());
605 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
598 assert_eq!(true, HgPath::new(b"./a/b/../c/").is_valid());
606 }
599 }
607
600
608 #[test]
601 #[test]
609 fn test_iter() {
602 fn test_iter() {
610 let path = HgPath::new(b"a");
603 let path = HgPath::new(b"a");
611 let mut iter = path.bytes();
604 let mut iter = path.bytes();
612 assert_eq!(Some(&b'a'), iter.next());
605 assert_eq!(Some(&b'a'), iter.next());
613 assert_eq!(None, iter.next_back());
606 assert_eq!(None, iter.next_back());
614 assert_eq!(None, iter.next());
607 assert_eq!(None, iter.next());
615
608
616 let path = HgPath::new(b"a");
609 let path = HgPath::new(b"a");
617 let mut iter = path.bytes();
610 let mut iter = path.bytes();
618 assert_eq!(Some(&b'a'), iter.next_back());
611 assert_eq!(Some(&b'a'), iter.next_back());
619 assert_eq!(None, iter.next_back());
612 assert_eq!(None, iter.next_back());
620 assert_eq!(None, iter.next());
613 assert_eq!(None, iter.next());
621
614
622 let path = HgPath::new(b"abc");
615 let path = HgPath::new(b"abc");
623 let mut iter = path.bytes();
616 let mut iter = path.bytes();
624 assert_eq!(Some(&b'a'), iter.next());
617 assert_eq!(Some(&b'a'), iter.next());
625 assert_eq!(Some(&b'c'), iter.next_back());
618 assert_eq!(Some(&b'c'), iter.next_back());
626 assert_eq!(Some(&b'b'), iter.next_back());
619 assert_eq!(Some(&b'b'), iter.next_back());
627 assert_eq!(None, iter.next_back());
620 assert_eq!(None, iter.next_back());
628 assert_eq!(None, iter.next());
621 assert_eq!(None, iter.next());
629
622
630 let path = HgPath::new(b"abc");
623 let path = HgPath::new(b"abc");
631 let mut iter = path.bytes();
624 let mut iter = path.bytes();
632 assert_eq!(Some(&b'a'), iter.next());
625 assert_eq!(Some(&b'a'), iter.next());
633 assert_eq!(Some(&b'b'), iter.next());
626 assert_eq!(Some(&b'b'), iter.next());
634 assert_eq!(Some(&b'c'), iter.next());
627 assert_eq!(Some(&b'c'), iter.next());
635 assert_eq!(None, iter.next_back());
628 assert_eq!(None, iter.next_back());
636 assert_eq!(None, iter.next());
629 assert_eq!(None, iter.next());
637
630
638 let path = HgPath::new(b"abc");
631 let path = HgPath::new(b"abc");
639 let iter = path.bytes();
632 let iter = path.bytes();
640 let mut vec = Vec::new();
633 let mut vec = Vec::new();
641 vec.extend(iter);
634 vec.extend(iter);
642 assert_eq!(vec![b'a', b'b', b'c'], vec);
635 assert_eq!(vec![b'a', b'b', b'c'], vec);
643
636
644 let path = HgPath::new(b"abc");
637 let path = HgPath::new(b"abc");
645 let mut iter = path.bytes();
638 let mut iter = path.bytes();
646 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
639 assert_eq!(Some(2), iter.rposition(|c| *c == b'c'));
647
640
648 let path = HgPath::new(b"abc");
641 let path = HgPath::new(b"abc");
649 let mut iter = path.bytes();
642 let mut iter = path.bytes();
650 assert_eq!(None, iter.rposition(|c| *c == b'd'));
643 assert_eq!(None, iter.rposition(|c| *c == b'd'));
651 }
644 }
652
645
653 #[test]
646 #[test]
654 fn test_join() {
647 fn test_join() {
655 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
648 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"b"));
656 assert_eq!(b"a/b", path.as_bytes());
649 assert_eq!(b"a/b", path.as_bytes());
657
650
658 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
651 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"b/c"));
659 assert_eq!(b"a/b/c", path.as_bytes());
652 assert_eq!(b"a/b/c", path.as_bytes());
660
653
661 // No leading slash if empty before join
654 // No leading slash if empty before join
662 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
655 let path = HgPathBuf::new().join(HgPath::new(b"b/c"));
663 assert_eq!(b"b/c", path.as_bytes());
656 assert_eq!(b"b/c", path.as_bytes());
664
657
665 // The leading slash is an invalid representation of an `HgPath`, but
658 // The leading slash is an invalid representation of an `HgPath`, but
666 // it can happen. This creates another invalid representation of
659 // it can happen. This creates another invalid representation of
667 // consecutive bytes.
660 // consecutive bytes.
668 // TODO What should be done in this case? Should we silently remove
661 // TODO What should be done in this case? Should we silently remove
669 // the extra slash? Should we change the signature to a problematic
662 // the extra slash? Should we change the signature to a problematic
670 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
663 // `Result<HgPathBuf, HgPathError>`, or should we just keep it so and
671 // let the error happen upon filesystem interaction?
664 // let the error happen upon filesystem interaction?
672 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
665 let path = HgPathBuf::from_bytes(b"a/").join(HgPath::new(b"/b"));
673 assert_eq!(b"a//b", path.as_bytes());
666 assert_eq!(b"a//b", path.as_bytes());
674 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
667 let path = HgPathBuf::from_bytes(b"a").join(HgPath::new(b"/b"));
675 assert_eq!(b"a//b", path.as_bytes());
668 assert_eq!(b"a//b", path.as_bytes());
676 }
669 }
677
670
678 #[test]
671 #[test]
679 fn test_relative_to() {
672 fn test_relative_to() {
680 let path = HgPath::new(b"");
673 let path = HgPath::new(b"");
681 let base = HgPath::new(b"");
674 let base = HgPath::new(b"");
682 assert_eq!(Some(path), path.relative_to(base));
675 assert_eq!(Some(path), path.relative_to(base));
683
676
684 let path = HgPath::new(b"path");
677 let path = HgPath::new(b"path");
685 let base = HgPath::new(b"");
678 let base = HgPath::new(b"");
686 assert_eq!(Some(path), path.relative_to(base));
679 assert_eq!(Some(path), path.relative_to(base));
687
680
688 let path = HgPath::new(b"a");
681 let path = HgPath::new(b"a");
689 let base = HgPath::new(b"b");
682 let base = HgPath::new(b"b");
690 assert_eq!(None, path.relative_to(base));
683 assert_eq!(None, path.relative_to(base));
691
684
692 let path = HgPath::new(b"a/b");
685 let path = HgPath::new(b"a/b");
693 let base = HgPath::new(b"a");
686 let base = HgPath::new(b"a");
694 assert_eq!(None, path.relative_to(base));
687 assert_eq!(None, path.relative_to(base));
695
688
696 let path = HgPath::new(b"a/b");
689 let path = HgPath::new(b"a/b");
697 let base = HgPath::new(b"a/");
690 let base = HgPath::new(b"a/");
698 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
691 assert_eq!(Some(HgPath::new(b"b")), path.relative_to(base));
699
692
700 let path = HgPath::new(b"nested/path/to/b");
693 let path = HgPath::new(b"nested/path/to/b");
701 let base = HgPath::new(b"nested/path/");
694 let base = HgPath::new(b"nested/path/");
702 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
695 assert_eq!(Some(HgPath::new(b"to/b")), path.relative_to(base));
703
696
704 let path = HgPath::new(b"ends/with/dir/");
697 let path = HgPath::new(b"ends/with/dir/");
705 let base = HgPath::new(b"ends/");
698 let base = HgPath::new(b"ends/");
706 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
699 assert_eq!(Some(HgPath::new(b"with/dir/")), path.relative_to(base));
707 }
700 }
708
701
709 #[test]
702 #[test]
710 #[cfg(unix)]
703 #[cfg(unix)]
711 fn test_split_drive() {
704 fn test_split_drive() {
712 // Taken from the Python stdlib's tests
705 // Taken from the Python stdlib's tests
713 assert_eq!(
706 assert_eq!(
714 HgPath::new(br"/foo/bar").split_drive(),
707 HgPath::new(br"/foo/bar").split_drive(),
715 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
708 (HgPath::new(b""), HgPath::new(br"/foo/bar"))
716 );
709 );
717 assert_eq!(
710 assert_eq!(
718 HgPath::new(br"foo:bar").split_drive(),
711 HgPath::new(br"foo:bar").split_drive(),
719 (HgPath::new(b""), HgPath::new(br"foo:bar"))
712 (HgPath::new(b""), HgPath::new(br"foo:bar"))
720 );
713 );
721 assert_eq!(
714 assert_eq!(
722 HgPath::new(br":foo:bar").split_drive(),
715 HgPath::new(br":foo:bar").split_drive(),
723 (HgPath::new(b""), HgPath::new(br":foo:bar"))
716 (HgPath::new(b""), HgPath::new(br":foo:bar"))
724 );
717 );
725 // Also try NT paths; should not split them
718 // Also try NT paths; should not split them
726 assert_eq!(
719 assert_eq!(
727 HgPath::new(br"c:\foo\bar").split_drive(),
720 HgPath::new(br"c:\foo\bar").split_drive(),
728 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
721 (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
729 );
722 );
730 assert_eq!(
723 assert_eq!(
731 HgPath::new(b"c:/foo/bar").split_drive(),
724 HgPath::new(b"c:/foo/bar").split_drive(),
732 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
725 (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
733 );
726 );
734 assert_eq!(
727 assert_eq!(
735 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
728 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
736 (
729 (
737 HgPath::new(b""),
730 HgPath::new(b""),
738 HgPath::new(br"\\conky\mountpoint\foo\bar")
731 HgPath::new(br"\\conky\mountpoint\foo\bar")
739 )
732 )
740 );
733 );
741 }
734 }
742
735
743 #[test]
736 #[test]
744 #[cfg(windows)]
737 #[cfg(windows)]
745 fn test_split_drive() {
738 fn test_split_drive() {
746 assert_eq!(
739 assert_eq!(
747 HgPath::new(br"c:\foo\bar").split_drive(),
740 HgPath::new(br"c:\foo\bar").split_drive(),
748 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
741 (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
749 );
742 );
750 assert_eq!(
743 assert_eq!(
751 HgPath::new(b"c:/foo/bar").split_drive(),
744 HgPath::new(b"c:/foo/bar").split_drive(),
752 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
745 (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
753 );
746 );
754 assert_eq!(
747 assert_eq!(
755 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
748 HgPath::new(br"\\conky\mountpoint\foo\bar").split_drive(),
756 (
749 (
757 HgPath::new(br"\\conky\mountpoint"),
750 HgPath::new(br"\\conky\mountpoint"),
758 HgPath::new(br"\foo\bar")
751 HgPath::new(br"\foo\bar")
759 )
752 )
760 );
753 );
761 assert_eq!(
754 assert_eq!(
762 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
755 HgPath::new(br"//conky/mountpoint/foo/bar").split_drive(),
763 (
756 (
764 HgPath::new(br"//conky/mountpoint"),
757 HgPath::new(br"//conky/mountpoint"),
765 HgPath::new(br"/foo/bar")
758 HgPath::new(br"/foo/bar")
766 )
759 )
767 );
760 );
768 assert_eq!(
761 assert_eq!(
769 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
762 HgPath::new(br"\\\conky\mountpoint\foo\bar").split_drive(),
770 (
763 (
771 HgPath::new(br""),
764 HgPath::new(br""),
772 HgPath::new(br"\\\conky\mountpoint\foo\bar")
765 HgPath::new(br"\\\conky\mountpoint\foo\bar")
773 )
766 )
774 );
767 );
775 assert_eq!(
768 assert_eq!(
776 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
769 HgPath::new(br"///conky/mountpoint/foo/bar").split_drive(),
777 (
770 (
778 HgPath::new(br""),
771 HgPath::new(br""),
779 HgPath::new(br"///conky/mountpoint/foo/bar")
772 HgPath::new(br"///conky/mountpoint/foo/bar")
780 )
773 )
781 );
774 );
782 assert_eq!(
775 assert_eq!(
783 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
776 HgPath::new(br"\\conky\\mountpoint\foo\bar").split_drive(),
784 (
777 (
785 HgPath::new(br""),
778 HgPath::new(br""),
786 HgPath::new(br"\\conky\\mountpoint\foo\bar")
779 HgPath::new(br"\\conky\\mountpoint\foo\bar")
787 )
780 )
788 );
781 );
789 assert_eq!(
782 assert_eq!(
790 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
783 HgPath::new(br"//conky//mountpoint/foo/bar").split_drive(),
791 (
784 (
792 HgPath::new(br""),
785 HgPath::new(br""),
793 HgPath::new(br"//conky//mountpoint/foo/bar")
786 HgPath::new(br"//conky//mountpoint/foo/bar")
794 )
787 )
795 );
788 );
796 // UNC part containing U+0130
789 // UNC part containing U+0130
797 assert_eq!(
790 assert_eq!(
798 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
791 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar").split_drive(),
799 (
792 (
800 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
793 HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
801 HgPath::new(br"/foo/bar")
794 HgPath::new(br"/foo/bar")
802 )
795 )
803 );
796 );
804 }
797 }
805
798
806 #[test]
799 #[test]
807 fn test_parent() {
800 fn test_parent() {
808 let path = HgPath::new(b"");
801 let path = HgPath::new(b"");
809 assert_eq!(path.parent(), path);
802 assert_eq!(path.parent(), path);
810
803
811 let path = HgPath::new(b"a");
804 let path = HgPath::new(b"a");
812 assert_eq!(path.parent(), HgPath::new(b""));
805 assert_eq!(path.parent(), HgPath::new(b""));
813
806
814 let path = HgPath::new(b"a/b");
807 let path = HgPath::new(b"a/b");
815 assert_eq!(path.parent(), HgPath::new(b"a"));
808 assert_eq!(path.parent(), HgPath::new(b"a"));
816
809
817 let path = HgPath::new(b"a/other/b");
810 let path = HgPath::new(b"a/other/b");
818 assert_eq!(path.parent(), HgPath::new(b"a/other"));
811 assert_eq!(path.parent(), HgPath::new(b"a/other"));
819 }
812 }
820 }
813 }
@@ -1,499 +1,490 b''
1 // dirstate_map.rs
1 // dirstate_map.rs
2 //
2 //
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019 Raphaël Gomès <rgomes@octobus.net>
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 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
8 //! Bindings for the `hg::dirstate::dirstate_map` file provided by the
9 //! `hg-core` package.
9 //! `hg-core` package.
10
10
11 use std::cell::{RefCell, RefMut};
11 use std::cell::{RefCell, RefMut};
12 use std::convert::TryInto;
12 use std::convert::TryInto;
13
13
14 use cpython::{
14 use cpython::{
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
15 exc, PyBool, PyBytes, PyClone, PyDict, PyErr, PyList, PyNone, PyObject,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
16 PyResult, Python, PythonObject, ToPyObject, UnsafePyLeaked,
17 };
17 };
18
18
19 use crate::{
19 use crate::{
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
20 dirstate::copymap::{CopyMap, CopyMapItemsIterator, CopyMapKeysIterator},
21 dirstate::item::DirstateItem,
21 dirstate::item::DirstateItem,
22 pybytes_deref::PyBytesDeref,
22 pybytes_deref::PyBytesDeref,
23 };
23 };
24 use hg::{
24 use hg::{
25 dirstate::StateMapIter,
25 dirstate::StateMapIter,
26 dirstate_tree::dirstate_map::DirstateMap as TreeDirstateMap,
27 dirstate_tree::on_disk::DirstateV2ParseError,
26 dirstate_tree::on_disk::DirstateV2ParseError,
28 dirstate_tree::owning::OwningDirstateMap,
27 dirstate_tree::owning::OwningDirstateMap,
29 revlog::Node,
28 revlog::Node,
30 utils::files::normalize_case,
29 utils::files::normalize_case,
31 utils::hg_path::{HgPath, HgPathBuf},
30 utils::hg_path::{HgPath, HgPathBuf},
32 DirstateEntry, DirstateError, DirstateParents, EntryState,
31 DirstateEntry, DirstateError, DirstateParents, EntryState,
33 };
32 };
34
33
35 // TODO
34 // TODO
36 // This object needs to share references to multiple members of its Rust
35 // This object needs to share references to multiple members of its Rust
37 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
36 // inner struct, namely `copy_map`, `dirs` and `all_dirs`.
38 // Right now `CopyMap` is done, but it needs to have an explicit reference
37 // Right now `CopyMap` is done, but it needs to have an explicit reference
39 // to `RustDirstateMap` which itself needs to have an encapsulation for
38 // to `RustDirstateMap` which itself needs to have an encapsulation for
40 // every method in `CopyMap` (copymapcopy, etc.).
39 // every method in `CopyMap` (copymapcopy, etc.).
41 // This is ugly and hard to maintain.
40 // This is ugly and hard to maintain.
42 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
41 // The same logic applies to `dirs` and `all_dirs`, however the `Dirs`
43 // `py_class!` is already implemented and does not mention
42 // `py_class!` is already implemented and does not mention
44 // `RustDirstateMap`, rightfully so.
43 // `RustDirstateMap`, rightfully so.
45 // All attributes also have to have a separate refcount data attribute for
44 // All attributes also have to have a separate refcount data attribute for
46 // leaks, with all methods that go along for reference sharing.
45 // leaks, with all methods that go along for reference sharing.
47 py_class!(pub class DirstateMap |py| {
46 py_class!(pub class DirstateMap |py| {
48 @shared data inner: OwningDirstateMap;
47 @shared data inner: OwningDirstateMap;
49
48
50 /// Returns a `(dirstate_map, parents)` tuple
49 /// Returns a `(dirstate_map, parents)` tuple
51 @staticmethod
50 @staticmethod
52 def new_v1(
51 def new_v1(
53 on_disk: PyBytes,
52 on_disk: PyBytes,
54 ) -> PyResult<PyObject> {
53 ) -> PyResult<PyObject> {
55 let on_disk = PyBytesDeref::new(py, on_disk);
54 let on_disk = PyBytesDeref::new(py, on_disk);
56 let mut map = OwningDirstateMap::new_empty(on_disk);
55 let (map, parents) = OwningDirstateMap::new_v1(on_disk)
57 let (on_disk, map_placeholder) = map.get_pair_mut();
58
59 let (actual_map, parents) = TreeDirstateMap::new_v1(on_disk)
60 .map_err(|e| dirstate_error(py, e))?;
56 .map_err(|e| dirstate_error(py, e))?;
61 *map_placeholder = actual_map;
62 let map = Self::create_instance(py, map)?;
57 let map = Self::create_instance(py, map)?;
63 let parents = parents.map(|p| {
58 let p1 = PyBytes::new(py, parents.p1.as_bytes());
64 let p1 = PyBytes::new(py, p.p1.as_bytes());
59 let p2 = PyBytes::new(py, parents.p2.as_bytes());
65 let p2 = PyBytes::new(py, p.p2.as_bytes());
60 let parents = (p1, p2);
66 (p1, p2)
67 });
68 Ok((map, parents).to_py_object(py).into_object())
61 Ok((map, parents).to_py_object(py).into_object())
69 }
62 }
70
63
71 /// Returns a DirstateMap
64 /// Returns a DirstateMap
72 @staticmethod
65 @staticmethod
73 def new_v2(
66 def new_v2(
74 on_disk: PyBytes,
67 on_disk: PyBytes,
75 data_size: usize,
68 data_size: usize,
76 tree_metadata: PyBytes,
69 tree_metadata: PyBytes,
77 ) -> PyResult<PyObject> {
70 ) -> PyResult<PyObject> {
78 let dirstate_error = |e: DirstateError| {
71 let dirstate_error = |e: DirstateError| {
79 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
72 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
80 };
73 };
81 let on_disk = PyBytesDeref::new(py, on_disk);
74 let on_disk = PyBytesDeref::new(py, on_disk);
82 let mut map = OwningDirstateMap::new_empty(on_disk);
75 let map = OwningDirstateMap::new_v2(
83 let (on_disk, map_placeholder) = map.get_pair_mut();
84 *map_placeholder = TreeDirstateMap::new_v2(
85 on_disk, data_size, tree_metadata.data(py),
76 on_disk, data_size, tree_metadata.data(py),
86 ).map_err(dirstate_error)?;
77 ).map_err(dirstate_error)?;
87 let map = Self::create_instance(py, map)?;
78 let map = Self::create_instance(py, map)?;
88 Ok(map.into_object())
79 Ok(map.into_object())
89 }
80 }
90
81
91 def clear(&self) -> PyResult<PyObject> {
82 def clear(&self) -> PyResult<PyObject> {
92 self.inner(py).borrow_mut().clear();
83 self.inner(py).borrow_mut().clear();
93 Ok(py.None())
84 Ok(py.None())
94 }
85 }
95
86
96 def get(
87 def get(
97 &self,
88 &self,
98 key: PyObject,
89 key: PyObject,
99 default: Option<PyObject> = None
90 default: Option<PyObject> = None
100 ) -> PyResult<Option<PyObject>> {
91 ) -> PyResult<Option<PyObject>> {
101 let key = key.extract::<PyBytes>(py)?;
92 let key = key.extract::<PyBytes>(py)?;
102 match self
93 match self
103 .inner(py)
94 .inner(py)
104 .borrow()
95 .borrow()
105 .get(HgPath::new(key.data(py)))
96 .get(HgPath::new(key.data(py)))
106 .map_err(|e| v2_error(py, e))?
97 .map_err(|e| v2_error(py, e))?
107 {
98 {
108 Some(entry) => {
99 Some(entry) => {
109 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
100 Ok(Some(DirstateItem::new_as_pyobject(py, entry)?))
110 },
101 },
111 None => Ok(default)
102 None => Ok(default)
112 }
103 }
113 }
104 }
114
105
115 def set_dirstate_item(
106 def set_dirstate_item(
116 &self,
107 &self,
117 path: PyObject,
108 path: PyObject,
118 item: DirstateItem
109 item: DirstateItem
119 ) -> PyResult<PyObject> {
110 ) -> PyResult<PyObject> {
120 let f = path.extract::<PyBytes>(py)?;
111 let f = path.extract::<PyBytes>(py)?;
121 let filename = HgPath::new(f.data(py));
112 let filename = HgPath::new(f.data(py));
122 self.inner(py)
113 self.inner(py)
123 .borrow_mut()
114 .borrow_mut()
124 .set_entry(filename, item.get_entry(py))
115 .set_entry(filename, item.get_entry(py))
125 .map_err(|e| v2_error(py, e))?;
116 .map_err(|e| v2_error(py, e))?;
126 Ok(py.None())
117 Ok(py.None())
127 }
118 }
128
119
129 def addfile(
120 def addfile(
130 &self,
121 &self,
131 f: PyBytes,
122 f: PyBytes,
132 item: DirstateItem,
123 item: DirstateItem,
133 ) -> PyResult<PyNone> {
124 ) -> PyResult<PyNone> {
134 let filename = HgPath::new(f.data(py));
125 let filename = HgPath::new(f.data(py));
135 let entry = item.get_entry(py);
126 let entry = item.get_entry(py);
136 self.inner(py)
127 self.inner(py)
137 .borrow_mut()
128 .borrow_mut()
138 .add_file(filename, entry)
129 .add_file(filename, entry)
139 .map_err(|e |dirstate_error(py, e))?;
130 .map_err(|e |dirstate_error(py, e))?;
140 Ok(PyNone)
131 Ok(PyNone)
141 }
132 }
142
133
143 def removefile(
134 def removefile(
144 &self,
135 &self,
145 f: PyObject,
136 f: PyObject,
146 in_merge: PyObject
137 in_merge: PyObject
147 ) -> PyResult<PyObject> {
138 ) -> PyResult<PyObject> {
148 self.inner(py).borrow_mut()
139 self.inner(py).borrow_mut()
149 .remove_file(
140 .remove_file(
150 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
141 HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
151 in_merge.extract::<PyBool>(py)?.is_true(),
142 in_merge.extract::<PyBool>(py)?.is_true(),
152 )
143 )
153 .or_else(|_| {
144 .or_else(|_| {
154 Err(PyErr::new::<exc::OSError, _>(
145 Err(PyErr::new::<exc::OSError, _>(
155 py,
146 py,
156 "Dirstate error".to_string(),
147 "Dirstate error".to_string(),
157 ))
148 ))
158 })?;
149 })?;
159 Ok(py.None())
150 Ok(py.None())
160 }
151 }
161
152
162 def drop_item_and_copy_source(
153 def drop_item_and_copy_source(
163 &self,
154 &self,
164 f: PyBytes,
155 f: PyBytes,
165 ) -> PyResult<PyNone> {
156 ) -> PyResult<PyNone> {
166 self.inner(py)
157 self.inner(py)
167 .borrow_mut()
158 .borrow_mut()
168 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
159 .drop_entry_and_copy_source(HgPath::new(f.data(py)))
169 .map_err(|e |dirstate_error(py, e))?;
160 .map_err(|e |dirstate_error(py, e))?;
170 Ok(PyNone)
161 Ok(PyNone)
171 }
162 }
172
163
173 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
164 def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
174 let d = d.extract::<PyBytes>(py)?;
165 let d = d.extract::<PyBytes>(py)?;
175 Ok(self.inner(py).borrow_mut()
166 Ok(self.inner(py).borrow_mut()
176 .has_tracked_dir(HgPath::new(d.data(py)))
167 .has_tracked_dir(HgPath::new(d.data(py)))
177 .map_err(|e| {
168 .map_err(|e| {
178 PyErr::new::<exc::ValueError, _>(py, e.to_string())
169 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 })?
170 })?
180 .to_py_object(py))
171 .to_py_object(py))
181 }
172 }
182
173
183 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
174 def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
184 let d = d.extract::<PyBytes>(py)?;
175 let d = d.extract::<PyBytes>(py)?;
185 Ok(self.inner(py).borrow_mut()
176 Ok(self.inner(py).borrow_mut()
186 .has_dir(HgPath::new(d.data(py)))
177 .has_dir(HgPath::new(d.data(py)))
187 .map_err(|e| {
178 .map_err(|e| {
188 PyErr::new::<exc::ValueError, _>(py, e.to_string())
179 PyErr::new::<exc::ValueError, _>(py, e.to_string())
189 })?
180 })?
190 .to_py_object(py))
181 .to_py_object(py))
191 }
182 }
192
183
193 def write_v1(
184 def write_v1(
194 &self,
185 &self,
195 p1: PyObject,
186 p1: PyObject,
196 p2: PyObject,
187 p2: PyObject,
197 ) -> PyResult<PyBytes> {
188 ) -> PyResult<PyBytes> {
198 let inner = self.inner(py).borrow();
189 let inner = self.inner(py).borrow();
199 let parents = DirstateParents {
190 let parents = DirstateParents {
200 p1: extract_node_id(py, &p1)?,
191 p1: extract_node_id(py, &p1)?,
201 p2: extract_node_id(py, &p2)?,
192 p2: extract_node_id(py, &p2)?,
202 };
193 };
203 let result = inner.pack_v1(parents);
194 let result = inner.pack_v1(parents);
204 match result {
195 match result {
205 Ok(packed) => Ok(PyBytes::new(py, &packed)),
196 Ok(packed) => Ok(PyBytes::new(py, &packed)),
206 Err(_) => Err(PyErr::new::<exc::OSError, _>(
197 Err(_) => Err(PyErr::new::<exc::OSError, _>(
207 py,
198 py,
208 "Dirstate error".to_string(),
199 "Dirstate error".to_string(),
209 )),
200 )),
210 }
201 }
211 }
202 }
212
203
213 /// Returns new data together with whether that data should be appended to
204 /// Returns new data together with whether that data should be appended to
214 /// the existing data file whose content is at `self.on_disk` (True),
205 /// the existing data file whose content is at `self.on_disk` (True),
215 /// instead of written to a new data file (False).
206 /// instead of written to a new data file (False).
216 def write_v2(
207 def write_v2(
217 &self,
208 &self,
218 can_append: bool,
209 can_append: bool,
219 ) -> PyResult<PyObject> {
210 ) -> PyResult<PyObject> {
220 let inner = self.inner(py).borrow();
211 let inner = self.inner(py).borrow();
221 let result = inner.pack_v2(can_append);
212 let result = inner.pack_v2(can_append);
222 match result {
213 match result {
223 Ok((packed, tree_metadata, append)) => {
214 Ok((packed, tree_metadata, append)) => {
224 let packed = PyBytes::new(py, &packed);
215 let packed = PyBytes::new(py, &packed);
225 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
216 let tree_metadata = PyBytes::new(py, tree_metadata.as_bytes());
226 let tuple = (packed, tree_metadata, append);
217 let tuple = (packed, tree_metadata, append);
227 Ok(tuple.to_py_object(py).into_object())
218 Ok(tuple.to_py_object(py).into_object())
228 },
219 },
229 Err(_) => Err(PyErr::new::<exc::OSError, _>(
220 Err(_) => Err(PyErr::new::<exc::OSError, _>(
230 py,
221 py,
231 "Dirstate error".to_string(),
222 "Dirstate error".to_string(),
232 )),
223 )),
233 }
224 }
234 }
225 }
235
226
236 def filefoldmapasdict(&self) -> PyResult<PyDict> {
227 def filefoldmapasdict(&self) -> PyResult<PyDict> {
237 let dict = PyDict::new(py);
228 let dict = PyDict::new(py);
238 for item in self.inner(py).borrow_mut().iter() {
229 for item in self.inner(py).borrow_mut().iter() {
239 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
230 let (path, entry) = item.map_err(|e| v2_error(py, e))?;
240 if entry.state() != EntryState::Removed {
231 if entry.state() != EntryState::Removed {
241 let key = normalize_case(path);
232 let key = normalize_case(path);
242 let value = path;
233 let value = path;
243 dict.set_item(
234 dict.set_item(
244 py,
235 py,
245 PyBytes::new(py, key.as_bytes()).into_object(),
236 PyBytes::new(py, key.as_bytes()).into_object(),
246 PyBytes::new(py, value.as_bytes()).into_object(),
237 PyBytes::new(py, value.as_bytes()).into_object(),
247 )?;
238 )?;
248 }
239 }
249 }
240 }
250 Ok(dict)
241 Ok(dict)
251 }
242 }
252
243
253 def __len__(&self) -> PyResult<usize> {
244 def __len__(&self) -> PyResult<usize> {
254 Ok(self.inner(py).borrow().len())
245 Ok(self.inner(py).borrow().len())
255 }
246 }
256
247
257 def __contains__(&self, key: PyObject) -> PyResult<bool> {
248 def __contains__(&self, key: PyObject) -> PyResult<bool> {
258 let key = key.extract::<PyBytes>(py)?;
249 let key = key.extract::<PyBytes>(py)?;
259 self.inner(py)
250 self.inner(py)
260 .borrow()
251 .borrow()
261 .contains_key(HgPath::new(key.data(py)))
252 .contains_key(HgPath::new(key.data(py)))
262 .map_err(|e| v2_error(py, e))
253 .map_err(|e| v2_error(py, e))
263 }
254 }
264
255
265 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
256 def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
266 let key = key.extract::<PyBytes>(py)?;
257 let key = key.extract::<PyBytes>(py)?;
267 let key = HgPath::new(key.data(py));
258 let key = HgPath::new(key.data(py));
268 match self
259 match self
269 .inner(py)
260 .inner(py)
270 .borrow()
261 .borrow()
271 .get(key)
262 .get(key)
272 .map_err(|e| v2_error(py, e))?
263 .map_err(|e| v2_error(py, e))?
273 {
264 {
274 Some(entry) => {
265 Some(entry) => {
275 Ok(DirstateItem::new_as_pyobject(py, entry)?)
266 Ok(DirstateItem::new_as_pyobject(py, entry)?)
276 },
267 },
277 None => Err(PyErr::new::<exc::KeyError, _>(
268 None => Err(PyErr::new::<exc::KeyError, _>(
278 py,
269 py,
279 String::from_utf8_lossy(key.as_bytes()),
270 String::from_utf8_lossy(key.as_bytes()),
280 )),
271 )),
281 }
272 }
282 }
273 }
283
274
284 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
275 def keys(&self) -> PyResult<DirstateMapKeysIterator> {
285 let leaked_ref = self.inner(py).leak_immutable();
276 let leaked_ref = self.inner(py).leak_immutable();
286 DirstateMapKeysIterator::from_inner(
277 DirstateMapKeysIterator::from_inner(
287 py,
278 py,
288 unsafe { leaked_ref.map(py, |o| o.iter()) },
279 unsafe { leaked_ref.map(py, |o| o.iter()) },
289 )
280 )
290 }
281 }
291
282
292 def items(&self) -> PyResult<DirstateMapItemsIterator> {
283 def items(&self) -> PyResult<DirstateMapItemsIterator> {
293 let leaked_ref = self.inner(py).leak_immutable();
284 let leaked_ref = self.inner(py).leak_immutable();
294 DirstateMapItemsIterator::from_inner(
285 DirstateMapItemsIterator::from_inner(
295 py,
286 py,
296 unsafe { leaked_ref.map(py, |o| o.iter()) },
287 unsafe { leaked_ref.map(py, |o| o.iter()) },
297 )
288 )
298 }
289 }
299
290
300 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
291 def __iter__(&self) -> PyResult<DirstateMapKeysIterator> {
301 let leaked_ref = self.inner(py).leak_immutable();
292 let leaked_ref = self.inner(py).leak_immutable();
302 DirstateMapKeysIterator::from_inner(
293 DirstateMapKeysIterator::from_inner(
303 py,
294 py,
304 unsafe { leaked_ref.map(py, |o| o.iter()) },
295 unsafe { leaked_ref.map(py, |o| o.iter()) },
305 )
296 )
306 }
297 }
307
298
308 // TODO all copymap* methods, see docstring above
299 // TODO all copymap* methods, see docstring above
309 def copymapcopy(&self) -> PyResult<PyDict> {
300 def copymapcopy(&self) -> PyResult<PyDict> {
310 let dict = PyDict::new(py);
301 let dict = PyDict::new(py);
311 for item in self.inner(py).borrow().copy_map_iter() {
302 for item in self.inner(py).borrow().copy_map_iter() {
312 let (key, value) = item.map_err(|e| v2_error(py, e))?;
303 let (key, value) = item.map_err(|e| v2_error(py, e))?;
313 dict.set_item(
304 dict.set_item(
314 py,
305 py,
315 PyBytes::new(py, key.as_bytes()),
306 PyBytes::new(py, key.as_bytes()),
316 PyBytes::new(py, value.as_bytes()),
307 PyBytes::new(py, value.as_bytes()),
317 )?;
308 )?;
318 }
309 }
319 Ok(dict)
310 Ok(dict)
320 }
311 }
321
312
322 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
313 def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
323 let key = key.extract::<PyBytes>(py)?;
314 let key = key.extract::<PyBytes>(py)?;
324 match self
315 match self
325 .inner(py)
316 .inner(py)
326 .borrow()
317 .borrow()
327 .copy_map_get(HgPath::new(key.data(py)))
318 .copy_map_get(HgPath::new(key.data(py)))
328 .map_err(|e| v2_error(py, e))?
319 .map_err(|e| v2_error(py, e))?
329 {
320 {
330 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
321 Some(copy) => Ok(PyBytes::new(py, copy.as_bytes())),
331 None => Err(PyErr::new::<exc::KeyError, _>(
322 None => Err(PyErr::new::<exc::KeyError, _>(
332 py,
323 py,
333 String::from_utf8_lossy(key.data(py)),
324 String::from_utf8_lossy(key.data(py)),
334 )),
325 )),
335 }
326 }
336 }
327 }
337 def copymap(&self) -> PyResult<CopyMap> {
328 def copymap(&self) -> PyResult<CopyMap> {
338 CopyMap::from_inner(py, self.clone_ref(py))
329 CopyMap::from_inner(py, self.clone_ref(py))
339 }
330 }
340
331
341 def copymaplen(&self) -> PyResult<usize> {
332 def copymaplen(&self) -> PyResult<usize> {
342 Ok(self.inner(py).borrow().copy_map_len())
333 Ok(self.inner(py).borrow().copy_map_len())
343 }
334 }
344 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
335 def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
345 let key = key.extract::<PyBytes>(py)?;
336 let key = key.extract::<PyBytes>(py)?;
346 self.inner(py)
337 self.inner(py)
347 .borrow()
338 .borrow()
348 .copy_map_contains_key(HgPath::new(key.data(py)))
339 .copy_map_contains_key(HgPath::new(key.data(py)))
349 .map_err(|e| v2_error(py, e))
340 .map_err(|e| v2_error(py, e))
350 }
341 }
351 def copymapget(
342 def copymapget(
352 &self,
343 &self,
353 key: PyObject,
344 key: PyObject,
354 default: Option<PyObject>
345 default: Option<PyObject>
355 ) -> PyResult<Option<PyObject>> {
346 ) -> PyResult<Option<PyObject>> {
356 let key = key.extract::<PyBytes>(py)?;
347 let key = key.extract::<PyBytes>(py)?;
357 match self
348 match self
358 .inner(py)
349 .inner(py)
359 .borrow()
350 .borrow()
360 .copy_map_get(HgPath::new(key.data(py)))
351 .copy_map_get(HgPath::new(key.data(py)))
361 .map_err(|e| v2_error(py, e))?
352 .map_err(|e| v2_error(py, e))?
362 {
353 {
363 Some(copy) => Ok(Some(
354 Some(copy) => Ok(Some(
364 PyBytes::new(py, copy.as_bytes()).into_object(),
355 PyBytes::new(py, copy.as_bytes()).into_object(),
365 )),
356 )),
366 None => Ok(default),
357 None => Ok(default),
367 }
358 }
368 }
359 }
369 def copymapsetitem(
360 def copymapsetitem(
370 &self,
361 &self,
371 key: PyObject,
362 key: PyObject,
372 value: PyObject
363 value: PyObject
373 ) -> PyResult<PyObject> {
364 ) -> PyResult<PyObject> {
374 let key = key.extract::<PyBytes>(py)?;
365 let key = key.extract::<PyBytes>(py)?;
375 let value = value.extract::<PyBytes>(py)?;
366 let value = value.extract::<PyBytes>(py)?;
376 self.inner(py)
367 self.inner(py)
377 .borrow_mut()
368 .borrow_mut()
378 .copy_map_insert(
369 .copy_map_insert(
379 HgPathBuf::from_bytes(key.data(py)),
370 HgPathBuf::from_bytes(key.data(py)),
380 HgPathBuf::from_bytes(value.data(py)),
371 HgPathBuf::from_bytes(value.data(py)),
381 )
372 )
382 .map_err(|e| v2_error(py, e))?;
373 .map_err(|e| v2_error(py, e))?;
383 Ok(py.None())
374 Ok(py.None())
384 }
375 }
385 def copymappop(
376 def copymappop(
386 &self,
377 &self,
387 key: PyObject,
378 key: PyObject,
388 default: Option<PyObject>
379 default: Option<PyObject>
389 ) -> PyResult<Option<PyObject>> {
380 ) -> PyResult<Option<PyObject>> {
390 let key = key.extract::<PyBytes>(py)?;
381 let key = key.extract::<PyBytes>(py)?;
391 match self
382 match self
392 .inner(py)
383 .inner(py)
393 .borrow_mut()
384 .borrow_mut()
394 .copy_map_remove(HgPath::new(key.data(py)))
385 .copy_map_remove(HgPath::new(key.data(py)))
395 .map_err(|e| v2_error(py, e))?
386 .map_err(|e| v2_error(py, e))?
396 {
387 {
397 Some(copy) => Ok(Some(
388 Some(copy) => Ok(Some(
398 PyBytes::new(py, copy.as_bytes()).into_object(),
389 PyBytes::new(py, copy.as_bytes()).into_object(),
399 )),
390 )),
400 None => Ok(default),
391 None => Ok(default),
401 }
392 }
402 }
393 }
403
394
404 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
395 def copymapiter(&self) -> PyResult<CopyMapKeysIterator> {
405 let leaked_ref = self.inner(py).leak_immutable();
396 let leaked_ref = self.inner(py).leak_immutable();
406 CopyMapKeysIterator::from_inner(
397 CopyMapKeysIterator::from_inner(
407 py,
398 py,
408 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
399 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
409 )
400 )
410 }
401 }
411
402
412 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
403 def copymapitemsiter(&self) -> PyResult<CopyMapItemsIterator> {
413 let leaked_ref = self.inner(py).leak_immutable();
404 let leaked_ref = self.inner(py).leak_immutable();
414 CopyMapItemsIterator::from_inner(
405 CopyMapItemsIterator::from_inner(
415 py,
406 py,
416 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
407 unsafe { leaked_ref.map(py, |o| o.copy_map_iter()) },
417 )
408 )
418 }
409 }
419
410
420 def tracked_dirs(&self) -> PyResult<PyList> {
411 def tracked_dirs(&self) -> PyResult<PyList> {
421 let dirs = PyList::new(py, &[]);
412 let dirs = PyList::new(py, &[]);
422 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
413 for path in self.inner(py).borrow_mut().iter_tracked_dirs()
423 .map_err(|e |dirstate_error(py, e))?
414 .map_err(|e |dirstate_error(py, e))?
424 {
415 {
425 let path = path.map_err(|e| v2_error(py, e))?;
416 let path = path.map_err(|e| v2_error(py, e))?;
426 let path = PyBytes::new(py, path.as_bytes());
417 let path = PyBytes::new(py, path.as_bytes());
427 dirs.append(py, path.into_object())
418 dirs.append(py, path.into_object())
428 }
419 }
429 Ok(dirs)
420 Ok(dirs)
430 }
421 }
431
422
432 def debug_iter(&self, all: bool) -> PyResult<PyList> {
423 def debug_iter(&self, all: bool) -> PyResult<PyList> {
433 let dirs = PyList::new(py, &[]);
424 let dirs = PyList::new(py, &[]);
434 for item in self.inner(py).borrow().debug_iter(all) {
425 for item in self.inner(py).borrow().debug_iter(all) {
435 let (path, (state, mode, size, mtime)) =
426 let (path, (state, mode, size, mtime)) =
436 item.map_err(|e| v2_error(py, e))?;
427 item.map_err(|e| v2_error(py, e))?;
437 let path = PyBytes::new(py, path.as_bytes());
428 let path = PyBytes::new(py, path.as_bytes());
438 let item = (path, state, mode, size, mtime);
429 let item = (path, state, mode, size, mtime);
439 dirs.append(py, item.to_py_object(py).into_object())
430 dirs.append(py, item.to_py_object(py).into_object())
440 }
431 }
441 Ok(dirs)
432 Ok(dirs)
442 }
433 }
443 });
434 });
444
435
445 impl DirstateMap {
436 impl DirstateMap {
446 pub fn get_inner_mut<'a>(
437 pub fn get_inner_mut<'a>(
447 &'a self,
438 &'a self,
448 py: Python<'a>,
439 py: Python<'a>,
449 ) -> RefMut<'a, OwningDirstateMap> {
440 ) -> RefMut<'a, OwningDirstateMap> {
450 self.inner(py).borrow_mut()
441 self.inner(py).borrow_mut()
451 }
442 }
452 fn translate_key(
443 fn translate_key(
453 py: Python,
444 py: Python,
454 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
445 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
455 ) -> PyResult<Option<PyBytes>> {
446 ) -> PyResult<Option<PyBytes>> {
456 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
447 let (f, _entry) = res.map_err(|e| v2_error(py, e))?;
457 Ok(Some(PyBytes::new(py, f.as_bytes())))
448 Ok(Some(PyBytes::new(py, f.as_bytes())))
458 }
449 }
459 fn translate_key_value(
450 fn translate_key_value(
460 py: Python,
451 py: Python,
461 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
452 res: Result<(&HgPath, DirstateEntry), DirstateV2ParseError>,
462 ) -> PyResult<Option<(PyBytes, PyObject)>> {
453 ) -> PyResult<Option<(PyBytes, PyObject)>> {
463 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
454 let (f, entry) = res.map_err(|e| v2_error(py, e))?;
464 Ok(Some((
455 Ok(Some((
465 PyBytes::new(py, f.as_bytes()),
456 PyBytes::new(py, f.as_bytes()),
466 DirstateItem::new_as_pyobject(py, entry)?,
457 DirstateItem::new_as_pyobject(py, entry)?,
467 )))
458 )))
468 }
459 }
469 }
460 }
470
461
471 py_shared_iterator!(
462 py_shared_iterator!(
472 DirstateMapKeysIterator,
463 DirstateMapKeysIterator,
473 UnsafePyLeaked<StateMapIter<'static>>,
464 UnsafePyLeaked<StateMapIter<'static>>,
474 DirstateMap::translate_key,
465 DirstateMap::translate_key,
475 Option<PyBytes>
466 Option<PyBytes>
476 );
467 );
477
468
478 py_shared_iterator!(
469 py_shared_iterator!(
479 DirstateMapItemsIterator,
470 DirstateMapItemsIterator,
480 UnsafePyLeaked<StateMapIter<'static>>,
471 UnsafePyLeaked<StateMapIter<'static>>,
481 DirstateMap::translate_key_value,
472 DirstateMap::translate_key_value,
482 Option<(PyBytes, PyObject)>
473 Option<(PyBytes, PyObject)>
483 );
474 );
484
475
485 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
476 fn extract_node_id(py: Python, obj: &PyObject) -> PyResult<Node> {
486 let bytes = obj.extract::<PyBytes>(py)?;
477 let bytes = obj.extract::<PyBytes>(py)?;
487 match bytes.data(py).try_into() {
478 match bytes.data(py).try_into() {
488 Ok(s) => Ok(s),
479 Ok(s) => Ok(s),
489 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
480 Err(e) => Err(PyErr::new::<exc::ValueError, _>(py, e.to_string())),
490 }
481 }
491 }
482 }
492
483
493 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
484 pub(super) fn v2_error(py: Python<'_>, _: DirstateV2ParseError) -> PyErr {
494 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
485 PyErr::new::<exc::ValueError, _>(py, "corrupted dirstate-v2")
495 }
486 }
496
487
497 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
488 fn dirstate_error(py: Python<'_>, e: DirstateError) -> PyErr {
498 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
489 PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e))
499 }
490 }
@@ -1,303 +1,302 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
3 // Copyright 2019, Raphaël Gomès <rgomes@octobus.net>
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 //! Bindings for the `hg::status` module provided by the
8 //! Bindings for the `hg::status` module provided by the
9 //! `hg-core` crate. From Python, this will be seen as
9 //! `hg-core` crate. From Python, this will be seen as
10 //! `rustext.dirstate.status`.
10 //! `rustext.dirstate.status`.
11
11
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
12 use crate::{dirstate::DirstateMap, exceptions::FallbackError};
13 use cpython::{
13 use cpython::{
14 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
14 exc::ValueError, ObjectProtocol, PyBytes, PyErr, PyList, PyObject,
15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
15 PyResult, PyTuple, Python, PythonObject, ToPyObject,
16 };
16 };
17 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::status::StatusPath;
18 use hg::{
18 use hg::{
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
19 matchers::{AlwaysMatcher, FileMatcher, IncludeMatcher},
20 parse_pattern_syntax,
20 parse_pattern_syntax,
21 utils::{
21 utils::{
22 files::{get_bytes_from_path, get_path_from_bytes},
22 files::{get_bytes_from_path, get_path_from_bytes},
23 hg_path::{HgPath, HgPathBuf},
23 hg_path::{HgPath, HgPathBuf},
24 },
24 },
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
25 BadMatch, DirstateStatus, IgnorePattern, PatternFileWarning, StatusError,
26 StatusOptions,
26 StatusOptions,
27 };
27 };
28 use std::borrow::Borrow;
28 use std::borrow::Borrow;
29
29
30 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
30 fn collect_status_path_list(py: Python, paths: &[StatusPath<'_>]) -> PyList {
31 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
31 collect_pybytes_list(py, paths.iter().map(|item| &*item.path))
32 }
32 }
33
33
34 /// This will be useless once trait impls for collection are added to `PyBytes`
34 /// This will be useless once trait impls for collection are added to `PyBytes`
35 /// upstream.
35 /// upstream.
36 fn collect_pybytes_list(
36 fn collect_pybytes_list(
37 py: Python,
37 py: Python,
38 iter: impl Iterator<Item = impl AsRef<HgPath>>,
38 iter: impl Iterator<Item = impl AsRef<HgPath>>,
39 ) -> PyList {
39 ) -> PyList {
40 let list = PyList::new(py, &[]);
40 let list = PyList::new(py, &[]);
41
41
42 for path in iter {
42 for path in iter {
43 list.append(
43 list.append(
44 py,
44 py,
45 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
45 PyBytes::new(py, path.as_ref().as_bytes()).into_object(),
46 )
46 )
47 }
47 }
48
48
49 list
49 list
50 }
50 }
51
51
52 fn collect_bad_matches(
52 fn collect_bad_matches(
53 py: Python,
53 py: Python,
54 collection: &[(impl AsRef<HgPath>, BadMatch)],
54 collection: &[(impl AsRef<HgPath>, BadMatch)],
55 ) -> PyResult<PyList> {
55 ) -> PyResult<PyList> {
56 let list = PyList::new(py, &[]);
56 let list = PyList::new(py, &[]);
57
57
58 let os = py.import("os")?;
58 let os = py.import("os")?;
59 let get_error_message = |code: i32| -> PyResult<_> {
59 let get_error_message = |code: i32| -> PyResult<_> {
60 os.call(
60 os.call(
61 py,
61 py,
62 "strerror",
62 "strerror",
63 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
63 PyTuple::new(py, &[code.to_py_object(py).into_object()]),
64 None,
64 None,
65 )
65 )
66 };
66 };
67
67
68 for (path, bad_match) in collection.iter() {
68 for (path, bad_match) in collection.iter() {
69 let message = match bad_match {
69 let message = match bad_match {
70 BadMatch::OsError(code) => get_error_message(*code)?,
70 BadMatch::OsError(code) => get_error_message(*code)?,
71 BadMatch::BadType(bad_type) => format!(
71 BadMatch::BadType(bad_type) => format!(
72 "unsupported file type (type is {})",
72 "unsupported file type (type is {})",
73 bad_type.to_string()
73 bad_type.to_string()
74 )
74 )
75 .to_py_object(py)
75 .to_py_object(py)
76 .into_object(),
76 .into_object(),
77 };
77 };
78 list.append(
78 list.append(
79 py,
79 py,
80 (PyBytes::new(py, path.as_ref().as_bytes()), message)
80 (PyBytes::new(py, path.as_ref().as_bytes()), message)
81 .to_py_object(py)
81 .to_py_object(py)
82 .into_object(),
82 .into_object(),
83 )
83 )
84 }
84 }
85
85
86 Ok(list)
86 Ok(list)
87 }
87 }
88
88
89 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
89 fn handle_fallback(py: Python, err: StatusError) -> PyErr {
90 match err {
90 match err {
91 StatusError::Pattern(e) => {
91 StatusError::Pattern(e) => {
92 let as_string = e.to_string();
92 let as_string = e.to_string();
93 log::trace!("Rust status fallback: `{}`", &as_string);
93 log::trace!("Rust status fallback: `{}`", &as_string);
94
94
95 PyErr::new::<FallbackError, _>(py, &as_string)
95 PyErr::new::<FallbackError, _>(py, &as_string)
96 }
96 }
97 e => PyErr::new::<ValueError, _>(py, e.to_string()),
97 e => PyErr::new::<ValueError, _>(py, e.to_string()),
98 }
98 }
99 }
99 }
100
100
101 pub fn status_wrapper(
101 pub fn status_wrapper(
102 py: Python,
102 py: Python,
103 dmap: DirstateMap,
103 dmap: DirstateMap,
104 matcher: PyObject,
104 matcher: PyObject,
105 root_dir: PyObject,
105 root_dir: PyObject,
106 ignore_files: PyList,
106 ignore_files: PyList,
107 check_exec: bool,
107 check_exec: bool,
108 list_clean: bool,
108 list_clean: bool,
109 list_ignored: bool,
109 list_ignored: bool,
110 list_unknown: bool,
110 list_unknown: bool,
111 collect_traversed_dirs: bool,
111 collect_traversed_dirs: bool,
112 ) -> PyResult<PyTuple> {
112 ) -> PyResult<PyTuple> {
113 let bytes = root_dir.extract::<PyBytes>(py)?;
113 let bytes = root_dir.extract::<PyBytes>(py)?;
114 let root_dir = get_path_from_bytes(bytes.data(py));
114 let root_dir = get_path_from_bytes(bytes.data(py));
115
115
116 let dmap: DirstateMap = dmap.to_py_object(py);
116 let dmap: DirstateMap = dmap.to_py_object(py);
117 let mut dmap = dmap.get_inner_mut(py);
117 let mut dmap = dmap.get_inner_mut(py);
118
118
119 let ignore_files: PyResult<Vec<_>> = ignore_files
119 let ignore_files: PyResult<Vec<_>> = ignore_files
120 .iter(py)
120 .iter(py)
121 .map(|b| {
121 .map(|b| {
122 let file = b.extract::<PyBytes>(py)?;
122 let file = b.extract::<PyBytes>(py)?;
123 Ok(get_path_from_bytes(file.data(py)).to_owned())
123 Ok(get_path_from_bytes(file.data(py)).to_owned())
124 })
124 })
125 .collect();
125 .collect();
126 let ignore_files = ignore_files?;
126 let ignore_files = ignore_files?;
127 // The caller may call `copymap.items()` separately
127 // The caller may call `copymap.items()` separately
128 let list_copies = false;
128 let list_copies = false;
129
129
130 let after_status = |res: Result<(DirstateStatus<'_>, _), StatusError>| {
131 let (status_res, warnings) =
132 res.map_err(|e| handle_fallback(py, e))?;
133 build_response(py, status_res, warnings)
134 };
135
130 match matcher.get_type(py).name(py).borrow() {
136 match matcher.get_type(py).name(py).borrow() {
131 "alwaysmatcher" => {
137 "alwaysmatcher" => {
132 let matcher = AlwaysMatcher;
138 let matcher = AlwaysMatcher;
133 let (status_res, warnings) = dmap
139 dmap.with_status(
134 .status(
140 &matcher,
135 &matcher,
141 root_dir.to_path_buf(),
136 root_dir.to_path_buf(),
142 ignore_files,
137 ignore_files,
143 StatusOptions {
138 StatusOptions {
144 check_exec,
139 check_exec,
145 list_clean,
140 list_clean,
146 list_ignored,
141 list_ignored,
147 list_unknown,
142 list_unknown,
148 list_copies,
143 list_copies,
149 collect_traversed_dirs,
144 collect_traversed_dirs,
150 },
145 },
151 after_status,
146 )
152 )
147 .map_err(|e| handle_fallback(py, e))?;
148 build_response(py, status_res, warnings)
149 }
153 }
150 "exactmatcher" => {
154 "exactmatcher" => {
151 let files = matcher.call_method(
155 let files = matcher.call_method(
152 py,
156 py,
153 "files",
157 "files",
154 PyTuple::new(py, &[]),
158 PyTuple::new(py, &[]),
155 None,
159 None,
156 )?;
160 )?;
157 let files: PyList = files.cast_into(py)?;
161 let files: PyList = files.cast_into(py)?;
158 let files: PyResult<Vec<HgPathBuf>> = files
162 let files: PyResult<Vec<HgPathBuf>> = files
159 .iter(py)
163 .iter(py)
160 .map(|f| {
164 .map(|f| {
161 Ok(HgPathBuf::from_bytes(
165 Ok(HgPathBuf::from_bytes(
162 f.extract::<PyBytes>(py)?.data(py),
166 f.extract::<PyBytes>(py)?.data(py),
163 ))
167 ))
164 })
168 })
165 .collect();
169 .collect();
166
170
167 let files = files?;
171 let files = files?;
168 let matcher = FileMatcher::new(files.as_ref())
172 let matcher = FileMatcher::new(files.as_ref())
169 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
173 .map_err(|e| PyErr::new::<ValueError, _>(py, e.to_string()))?;
170 let (status_res, warnings) = dmap
174 dmap.with_status(
171 .status(
175 &matcher,
172 &matcher,
176 root_dir.to_path_buf(),
173 root_dir.to_path_buf(),
177 ignore_files,
174 ignore_files,
178 StatusOptions {
175 StatusOptions {
179 check_exec,
176 check_exec,
180 list_clean,
177 list_clean,
181 list_ignored,
178 list_ignored,
182 list_unknown,
179 list_unknown,
183 list_copies,
180 list_copies,
184 collect_traversed_dirs,
181 collect_traversed_dirs,
185 },
182 },
186 after_status,
183 )
187 )
184 .map_err(|e| handle_fallback(py, e))?;
185 build_response(py, status_res, warnings)
186 }
188 }
187 "includematcher" => {
189 "includematcher" => {
188 // Get the patterns from Python even though most of them are
190 // Get the patterns from Python even though most of them are
189 // redundant with those we will parse later on, as they include
191 // redundant with those we will parse later on, as they include
190 // those passed from the command line.
192 // those passed from the command line.
191 let ignore_patterns: PyResult<Vec<_>> = matcher
193 let ignore_patterns: PyResult<Vec<_>> = matcher
192 .getattr(py, "_kindpats")?
194 .getattr(py, "_kindpats")?
193 .iter(py)?
195 .iter(py)?
194 .map(|k| {
196 .map(|k| {
195 let k = k?;
197 let k = k?;
196 let syntax = parse_pattern_syntax(
198 let syntax = parse_pattern_syntax(
197 &[
199 &[
198 k.get_item(py, 0)?
200 k.get_item(py, 0)?
199 .extract::<PyBytes>(py)?
201 .extract::<PyBytes>(py)?
200 .data(py),
202 .data(py),
201 &b":"[..],
203 &b":"[..],
202 ]
204 ]
203 .concat(),
205 .concat(),
204 )
206 )
205 .map_err(|e| {
207 .map_err(|e| {
206 handle_fallback(py, StatusError::Pattern(e))
208 handle_fallback(py, StatusError::Pattern(e))
207 })?;
209 })?;
208 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
210 let pattern = k.get_item(py, 1)?.extract::<PyBytes>(py)?;
209 let pattern = pattern.data(py);
211 let pattern = pattern.data(py);
210 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
212 let source = k.get_item(py, 2)?.extract::<PyBytes>(py)?;
211 let source = get_path_from_bytes(source.data(py));
213 let source = get_path_from_bytes(source.data(py));
212 let new = IgnorePattern::new(syntax, pattern, source);
214 let new = IgnorePattern::new(syntax, pattern, source);
213 Ok(new)
215 Ok(new)
214 })
216 })
215 .collect();
217 .collect();
216
218
217 let ignore_patterns = ignore_patterns?;
219 let ignore_patterns = ignore_patterns?;
218
220
219 let matcher = IncludeMatcher::new(ignore_patterns)
221 let matcher = IncludeMatcher::new(ignore_patterns)
220 .map_err(|e| handle_fallback(py, e.into()))?;
222 .map_err(|e| handle_fallback(py, e.into()))?;
221
223
222 let (status_res, warnings) = dmap
224 dmap.with_status(
223 .status(
225 &matcher,
224 &matcher,
226 root_dir.to_path_buf(),
225 root_dir.to_path_buf(),
227 ignore_files,
226 ignore_files,
228 StatusOptions {
227 StatusOptions {
229 check_exec,
228 check_exec,
230 list_clean,
229 list_clean,
231 list_ignored,
230 list_ignored,
232 list_unknown,
231 list_unknown,
233 list_copies,
232 list_copies,
234 collect_traversed_dirs,
233 collect_traversed_dirs,
235 },
234 },
236 after_status,
235 )
237 )
236 .map_err(|e| handle_fallback(py, e))?;
237
238 build_response(py, status_res, warnings)
239 }
238 }
240 e => Err(PyErr::new::<ValueError, _>(
239 e => Err(PyErr::new::<ValueError, _>(
241 py,
240 py,
242 format!("Unsupported matcher {}", e),
241 format!("Unsupported matcher {}", e),
243 )),
242 )),
244 }
243 }
245 }
244 }
246
245
247 fn build_response(
246 fn build_response(
248 py: Python,
247 py: Python,
249 status_res: DirstateStatus,
248 status_res: DirstateStatus,
250 warnings: Vec<PatternFileWarning>,
249 warnings: Vec<PatternFileWarning>,
251 ) -> PyResult<PyTuple> {
250 ) -> PyResult<PyTuple> {
252 let modified = collect_status_path_list(py, &status_res.modified);
251 let modified = collect_status_path_list(py, &status_res.modified);
253 let added = collect_status_path_list(py, &status_res.added);
252 let added = collect_status_path_list(py, &status_res.added);
254 let removed = collect_status_path_list(py, &status_res.removed);
253 let removed = collect_status_path_list(py, &status_res.removed);
255 let deleted = collect_status_path_list(py, &status_res.deleted);
254 let deleted = collect_status_path_list(py, &status_res.deleted);
256 let clean = collect_status_path_list(py, &status_res.clean);
255 let clean = collect_status_path_list(py, &status_res.clean);
257 let ignored = collect_status_path_list(py, &status_res.ignored);
256 let ignored = collect_status_path_list(py, &status_res.ignored);
258 let unknown = collect_status_path_list(py, &status_res.unknown);
257 let unknown = collect_status_path_list(py, &status_res.unknown);
259 let unsure = collect_status_path_list(py, &status_res.unsure);
258 let unsure = collect_status_path_list(py, &status_res.unsure);
260 let bad = collect_bad_matches(py, &status_res.bad)?;
259 let bad = collect_bad_matches(py, &status_res.bad)?;
261 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
260 let traversed = collect_pybytes_list(py, status_res.traversed.iter());
262 let dirty = status_res.dirty.to_py_object(py);
261 let dirty = status_res.dirty.to_py_object(py);
263 let py_warnings = PyList::new(py, &[]);
262 let py_warnings = PyList::new(py, &[]);
264 for warning in warnings.iter() {
263 for warning in warnings.iter() {
265 // We use duck-typing on the Python side for dispatch, good enough for
264 // We use duck-typing on the Python side for dispatch, good enough for
266 // now.
265 // now.
267 match warning {
266 match warning {
268 PatternFileWarning::InvalidSyntax(file, syn) => {
267 PatternFileWarning::InvalidSyntax(file, syn) => {
269 py_warnings.append(
268 py_warnings.append(
270 py,
269 py,
271 (
270 (
272 PyBytes::new(py, &get_bytes_from_path(&file)),
271 PyBytes::new(py, &get_bytes_from_path(&file)),
273 PyBytes::new(py, syn),
272 PyBytes::new(py, syn),
274 )
273 )
275 .to_py_object(py)
274 .to_py_object(py)
276 .into_object(),
275 .into_object(),
277 );
276 );
278 }
277 }
279 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
278 PatternFileWarning::NoSuchFile(file) => py_warnings.append(
280 py,
279 py,
281 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
280 PyBytes::new(py, &get_bytes_from_path(&file)).into_object(),
282 ),
281 ),
283 }
282 }
284 }
283 }
285
284
286 Ok(PyTuple::new(
285 Ok(PyTuple::new(
287 py,
286 py,
288 &[
287 &[
289 unsure.into_object(),
288 unsure.into_object(),
290 modified.into_object(),
289 modified.into_object(),
291 added.into_object(),
290 added.into_object(),
292 removed.into_object(),
291 removed.into_object(),
293 deleted.into_object(),
292 deleted.into_object(),
294 clean.into_object(),
293 clean.into_object(),
295 ignored.into_object(),
294 ignored.into_object(),
296 unknown.into_object(),
295 unknown.into_object(),
297 py_warnings.into_object(),
296 py_warnings.into_object(),
298 bad.into_object(),
297 bad.into_object(),
299 traversed.into_object(),
298 traversed.into_object(),
300 dirty.into_object(),
299 dirty.into_object(),
301 ][..],
300 ][..],
302 ))
301 ))
303 }
302 }
@@ -1,531 +1,549 b''
1 // status.rs
1 // status.rs
2 //
2 //
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
3 // Copyright 2020, Georges Racinet <georges.racinets@octobus.net>
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 use crate::error::CommandError;
8 use crate::error::CommandError;
9 use crate::ui::Ui;
9 use crate::ui::Ui;
10 use crate::utils::path_utils::RelativizePaths;
10 use crate::utils::path_utils::RelativizePaths;
11 use clap::{Arg, SubCommand};
11 use clap::{Arg, SubCommand};
12 use format_bytes::format_bytes;
12 use format_bytes::format_bytes;
13 use hg;
13 use hg;
14 use hg::config::Config;
14 use hg::config::Config;
15 use hg::dirstate::has_exec_bit;
15 use hg::dirstate::has_exec_bit;
16 use hg::dirstate::status::StatusPath;
16 use hg::dirstate::status::StatusPath;
17 use hg::dirstate::TruncatedTimestamp;
17 use hg::dirstate::TruncatedTimestamp;
18 use hg::dirstate::RANGE_MASK_31BIT;
18 use hg::dirstate::RANGE_MASK_31BIT;
19 use hg::errors::{HgError, IoResultExt};
19 use hg::errors::{HgError, IoResultExt};
20 use hg::lock::LockError;
20 use hg::lock::LockError;
21 use hg::manifest::Manifest;
21 use hg::manifest::Manifest;
22 use hg::matchers::AlwaysMatcher;
22 use hg::matchers::AlwaysMatcher;
23 use hg::repo::Repo;
23 use hg::repo::Repo;
24 use hg::utils::files::get_bytes_from_os_string;
24 use hg::utils::files::get_bytes_from_os_string;
25 use hg::utils::files::get_bytes_from_path;
25 use hg::utils::files::get_bytes_from_path;
26 use hg::utils::files::get_path_from_bytes;
26 use hg::utils::files::get_path_from_bytes;
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
27 use hg::utils::hg_path::{hg_path_to_path_buf, HgPath};
28 use hg::DirstateStatus;
29 use hg::PatternFileWarning;
30 use hg::StatusError;
28 use hg::StatusOptions;
31 use hg::StatusOptions;
29 use log::info;
32 use log::info;
30 use std::io;
33 use std::io;
31 use std::path::PathBuf;
34 use std::path::PathBuf;
32
35
33 pub const HELP_TEXT: &str = "
36 pub const HELP_TEXT: &str = "
34 Show changed files in the working directory
37 Show changed files in the working directory
35
38
36 This is a pure Rust version of `hg status`.
39 This is a pure Rust version of `hg status`.
37
40
38 Some options might be missing, check the list below.
41 Some options might be missing, check the list below.
39 ";
42 ";
40
43
41 pub fn args() -> clap::App<'static, 'static> {
44 pub fn args() -> clap::App<'static, 'static> {
42 SubCommand::with_name("status")
45 SubCommand::with_name("status")
43 .alias("st")
46 .alias("st")
44 .about(HELP_TEXT)
47 .about(HELP_TEXT)
45 .arg(
48 .arg(
46 Arg::with_name("all")
49 Arg::with_name("all")
47 .help("show status of all files")
50 .help("show status of all files")
48 .short("-A")
51 .short("-A")
49 .long("--all"),
52 .long("--all"),
50 )
53 )
51 .arg(
54 .arg(
52 Arg::with_name("modified")
55 Arg::with_name("modified")
53 .help("show only modified files")
56 .help("show only modified files")
54 .short("-m")
57 .short("-m")
55 .long("--modified"),
58 .long("--modified"),
56 )
59 )
57 .arg(
60 .arg(
58 Arg::with_name("added")
61 Arg::with_name("added")
59 .help("show only added files")
62 .help("show only added files")
60 .short("-a")
63 .short("-a")
61 .long("--added"),
64 .long("--added"),
62 )
65 )
63 .arg(
66 .arg(
64 Arg::with_name("removed")
67 Arg::with_name("removed")
65 .help("show only removed files")
68 .help("show only removed files")
66 .short("-r")
69 .short("-r")
67 .long("--removed"),
70 .long("--removed"),
68 )
71 )
69 .arg(
72 .arg(
70 Arg::with_name("clean")
73 Arg::with_name("clean")
71 .help("show only clean files")
74 .help("show only clean files")
72 .short("-c")
75 .short("-c")
73 .long("--clean"),
76 .long("--clean"),
74 )
77 )
75 .arg(
78 .arg(
76 Arg::with_name("deleted")
79 Arg::with_name("deleted")
77 .help("show only deleted files")
80 .help("show only deleted files")
78 .short("-d")
81 .short("-d")
79 .long("--deleted"),
82 .long("--deleted"),
80 )
83 )
81 .arg(
84 .arg(
82 Arg::with_name("unknown")
85 Arg::with_name("unknown")
83 .help("show only unknown (not tracked) files")
86 .help("show only unknown (not tracked) files")
84 .short("-u")
87 .short("-u")
85 .long("--unknown"),
88 .long("--unknown"),
86 )
89 )
87 .arg(
90 .arg(
88 Arg::with_name("ignored")
91 Arg::with_name("ignored")
89 .help("show only ignored files")
92 .help("show only ignored files")
90 .short("-i")
93 .short("-i")
91 .long("--ignored"),
94 .long("--ignored"),
92 )
95 )
93 .arg(
96 .arg(
94 Arg::with_name("copies")
97 Arg::with_name("copies")
95 .help("show source of copied files (DEFAULT: ui.statuscopies)")
98 .help("show source of copied files (DEFAULT: ui.statuscopies)")
96 .short("-C")
99 .short("-C")
97 .long("--copies"),
100 .long("--copies"),
98 )
101 )
99 .arg(
102 .arg(
100 Arg::with_name("no-status")
103 Arg::with_name("no-status")
101 .help("hide status prefix")
104 .help("hide status prefix")
102 .short("-n")
105 .short("-n")
103 .long("--no-status"),
106 .long("--no-status"),
104 )
107 )
105 }
108 }
106
109
107 /// Pure data type allowing the caller to specify file states to display
110 /// Pure data type allowing the caller to specify file states to display
108 #[derive(Copy, Clone, Debug)]
111 #[derive(Copy, Clone, Debug)]
109 pub struct DisplayStates {
112 pub struct DisplayStates {
110 pub modified: bool,
113 pub modified: bool,
111 pub added: bool,
114 pub added: bool,
112 pub removed: bool,
115 pub removed: bool,
113 pub clean: bool,
116 pub clean: bool,
114 pub deleted: bool,
117 pub deleted: bool,
115 pub unknown: bool,
118 pub unknown: bool,
116 pub ignored: bool,
119 pub ignored: bool,
117 }
120 }
118
121
119 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
122 pub const DEFAULT_DISPLAY_STATES: DisplayStates = DisplayStates {
120 modified: true,
123 modified: true,
121 added: true,
124 added: true,
122 removed: true,
125 removed: true,
123 clean: false,
126 clean: false,
124 deleted: true,
127 deleted: true,
125 unknown: true,
128 unknown: true,
126 ignored: false,
129 ignored: false,
127 };
130 };
128
131
129 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
132 pub const ALL_DISPLAY_STATES: DisplayStates = DisplayStates {
130 modified: true,
133 modified: true,
131 added: true,
134 added: true,
132 removed: true,
135 removed: true,
133 clean: true,
136 clean: true,
134 deleted: true,
137 deleted: true,
135 unknown: true,
138 unknown: true,
136 ignored: true,
139 ignored: true,
137 };
140 };
138
141
139 impl DisplayStates {
142 impl DisplayStates {
140 pub fn is_empty(&self) -> bool {
143 pub fn is_empty(&self) -> bool {
141 !(self.modified
144 !(self.modified
142 || self.added
145 || self.added
143 || self.removed
146 || self.removed
144 || self.clean
147 || self.clean
145 || self.deleted
148 || self.deleted
146 || self.unknown
149 || self.unknown
147 || self.ignored)
150 || self.ignored)
148 }
151 }
149 }
152 }
150
153
151 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
154 pub fn run(invocation: &crate::CliInvocation) -> Result<(), CommandError> {
152 // TODO: lift these limitations
155 // TODO: lift these limitations
153 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
156 if invocation.config.get_bool(b"ui", b"tweakdefaults")? {
154 return Err(CommandError::unsupported(
157 return Err(CommandError::unsupported(
155 "ui.tweakdefaults is not yet supported with rhg status",
158 "ui.tweakdefaults is not yet supported with rhg status",
156 ));
159 ));
157 }
160 }
158 if invocation.config.get_bool(b"ui", b"statuscopies")? {
161 if invocation.config.get_bool(b"ui", b"statuscopies")? {
159 return Err(CommandError::unsupported(
162 return Err(CommandError::unsupported(
160 "ui.statuscopies is not yet supported with rhg status",
163 "ui.statuscopies is not yet supported with rhg status",
161 ));
164 ));
162 }
165 }
163 if invocation
166 if invocation
164 .config
167 .config
165 .get(b"commands", b"status.terse")
168 .get(b"commands", b"status.terse")
166 .is_some()
169 .is_some()
167 {
170 {
168 return Err(CommandError::unsupported(
171 return Err(CommandError::unsupported(
169 "status.terse is not yet supported with rhg status",
172 "status.terse is not yet supported with rhg status",
170 ));
173 ));
171 }
174 }
172
175
173 let ui = invocation.ui;
176 let ui = invocation.ui;
174 let config = invocation.config;
177 let config = invocation.config;
175 let args = invocation.subcommand_args;
178 let args = invocation.subcommand_args;
176
179
177 let verbose = !ui.plain(None)
180 let verbose = !ui.plain(None)
178 && !args.is_present("print0")
181 && !args.is_present("print0")
179 && (config.get_bool(b"ui", b"verbose")?
182 && (config.get_bool(b"ui", b"verbose")?
180 || config.get_bool(b"commands", b"status.verbose")?);
183 || config.get_bool(b"commands", b"status.verbose")?);
181 if verbose {
184 if verbose {
182 return Err(CommandError::unsupported(
185 return Err(CommandError::unsupported(
183 "verbose status is not supported yet",
186 "verbose status is not supported yet",
184 ));
187 ));
185 }
188 }
186
189
187 let all = args.is_present("all");
190 let all = args.is_present("all");
188 let display_states = if all {
191 let display_states = if all {
189 // TODO when implementing `--quiet`: it excludes clean files
192 // TODO when implementing `--quiet`: it excludes clean files
190 // from `--all`
193 // from `--all`
191 ALL_DISPLAY_STATES
194 ALL_DISPLAY_STATES
192 } else {
195 } else {
193 let requested = DisplayStates {
196 let requested = DisplayStates {
194 modified: args.is_present("modified"),
197 modified: args.is_present("modified"),
195 added: args.is_present("added"),
198 added: args.is_present("added"),
196 removed: args.is_present("removed"),
199 removed: args.is_present("removed"),
197 clean: args.is_present("clean"),
200 clean: args.is_present("clean"),
198 deleted: args.is_present("deleted"),
201 deleted: args.is_present("deleted"),
199 unknown: args.is_present("unknown"),
202 unknown: args.is_present("unknown"),
200 ignored: args.is_present("ignored"),
203 ignored: args.is_present("ignored"),
201 };
204 };
202 if requested.is_empty() {
205 if requested.is_empty() {
203 DEFAULT_DISPLAY_STATES
206 DEFAULT_DISPLAY_STATES
204 } else {
207 } else {
205 requested
208 requested
206 }
209 }
207 };
210 };
208 let no_status = args.is_present("no-status");
211 let no_status = args.is_present("no-status");
209 let list_copies = all
212 let list_copies = all
210 || args.is_present("copies")
213 || args.is_present("copies")
211 || config.get_bool(b"ui", b"statuscopies")?;
214 || config.get_bool(b"ui", b"statuscopies")?;
212
215
213 let repo = invocation.repo?;
216 let repo = invocation.repo?;
214
217
215 if repo.has_sparse() || repo.has_narrow() {
218 if repo.has_sparse() || repo.has_narrow() {
216 return Err(CommandError::unsupported(
219 return Err(CommandError::unsupported(
217 "rhg status is not supported for sparse checkouts or narrow clones yet"
220 "rhg status is not supported for sparse checkouts or narrow clones yet"
218 ));
221 ));
219 }
222 }
220
223
221 let mut dmap = repo.dirstate_map_mut()?;
224 let mut dmap = repo.dirstate_map_mut()?;
222
225
223 let options = StatusOptions {
226 let options = StatusOptions {
224 // we're currently supporting file systems with exec flags only
227 // we're currently supporting file systems with exec flags only
225 // anyway
228 // anyway
226 check_exec: true,
229 check_exec: true,
227 list_clean: display_states.clean,
230 list_clean: display_states.clean,
228 list_unknown: display_states.unknown,
231 list_unknown: display_states.unknown,
229 list_ignored: display_states.ignored,
232 list_ignored: display_states.ignored,
230 list_copies,
233 list_copies,
231 collect_traversed_dirs: false,
234 collect_traversed_dirs: false,
232 };
235 };
233 let (mut ds_status, pattern_warnings) = dmap.status(
236
234 &AlwaysMatcher,
237 type StatusResult<'a> =
235 repo.working_directory_path().to_owned(),
238 Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>;
236 ignore_files(repo, config),
239
237 options,
240 let after_status = |res: StatusResult| -> Result<_, CommandError> {
238 )?;
241 let (mut ds_status, pattern_warnings) = res?;
239 for warning in pattern_warnings {
242 for warning in pattern_warnings {
240 match warning {
243 match warning {
241 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
244 hg::PatternFileWarning::InvalidSyntax(path, syntax) => ui
242 .write_stderr(&format_bytes!(
245 .write_stderr(&format_bytes!(
243 b"{}: ignoring invalid syntax '{}'\n",
246 b"{}: ignoring invalid syntax '{}'\n",
244 get_bytes_from_path(path),
247 get_bytes_from_path(path),
245 &*syntax
248 &*syntax
246 ))?,
249 ))?,
247 hg::PatternFileWarning::NoSuchFile(path) => {
250 hg::PatternFileWarning::NoSuchFile(path) => {
248 let path = if let Ok(relative) =
251 let path = if let Ok(relative) =
249 path.strip_prefix(repo.working_directory_path())
252 path.strip_prefix(repo.working_directory_path())
250 {
253 {
251 relative
254 relative
252 } else {
255 } else {
253 &*path
256 &*path
254 };
257 };
255 ui.write_stderr(&format_bytes!(
258 ui.write_stderr(&format_bytes!(
256 b"skipping unreadable pattern file '{}': \
259 b"skipping unreadable pattern file '{}': \
257 No such file or directory\n",
260 No such file or directory\n",
258 get_bytes_from_path(path),
261 get_bytes_from_path(path),
259 ))?
262 ))?
263 }
260 }
264 }
261 }
265 }
262 }
263
266
264 for (path, error) in ds_status.bad {
267 for (path, error) in ds_status.bad {
265 let error = match error {
268 let error = match error {
266 hg::BadMatch::OsError(code) => {
269 hg::BadMatch::OsError(code) => {
267 std::io::Error::from_raw_os_error(code).to_string()
270 std::io::Error::from_raw_os_error(code).to_string()
268 }
271 }
269 hg::BadMatch::BadType(ty) => {
272 hg::BadMatch::BadType(ty) => {
270 format!("unsupported file type (type is {})", ty)
273 format!("unsupported file type (type is {})", ty)
271 }
272 };
273 ui.write_stderr(&format_bytes!(
274 b"{}: {}\n",
275 path.as_bytes(),
276 error.as_bytes()
277 ))?
278 }
279 if !ds_status.unsure.is_empty() {
280 info!(
281 "Files to be rechecked by retrieval from filelog: {:?}",
282 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
283 );
284 }
285 let mut fixup = Vec::new();
286 if !ds_status.unsure.is_empty()
287 && (display_states.modified || display_states.clean)
288 {
289 let p1 = repo.dirstate_parents()?.p1;
290 let manifest = repo.manifest_for_node(p1).map_err(|e| {
291 CommandError::from((e, &*format!("{:x}", p1.short())))
292 })?;
293 for to_check in ds_status.unsure {
294 if unsure_is_modified(repo, &manifest, &to_check.path)? {
295 if display_states.modified {
296 ds_status.modified.push(to_check);
297 }
274 }
298 } else {
275 };
299 if display_states.clean {
276 ui.write_stderr(&format_bytes!(
300 ds_status.clean.push(to_check.clone());
277 b"{}: {}\n",
278 path.as_bytes(),
279 error.as_bytes()
280 ))?
281 }
282 if !ds_status.unsure.is_empty() {
283 info!(
284 "Files to be rechecked by retrieval from filelog: {:?}",
285 ds_status.unsure.iter().map(|s| &s.path).collect::<Vec<_>>()
286 );
287 }
288 let mut fixup = Vec::new();
289 if !ds_status.unsure.is_empty()
290 && (display_states.modified || display_states.clean)
291 {
292 let p1 = repo.dirstate_parents()?.p1;
293 let manifest = repo.manifest_for_node(p1).map_err(|e| {
294 CommandError::from((e, &*format!("{:x}", p1.short())))
295 })?;
296 for to_check in ds_status.unsure {
297 if unsure_is_modified(repo, &manifest, &to_check.path)? {
298 if display_states.modified {
299 ds_status.modified.push(to_check);
300 }
301 } else {
302 if display_states.clean {
303 ds_status.clean.push(to_check.clone());
304 }
305 fixup.push(to_check.path.into_owned())
301 }
306 }
302 fixup.push(to_check.path.into_owned())
303 }
307 }
304 }
308 }
305 }
309 let relative_paths = (!ui.plain(None))
306 let relative_paths = (!ui.plain(None))
310 && config
307 && config
311 .get_option(b"commands", b"status.relative")?
308 .get_option(b"commands", b"status.relative")?
312 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
309 .unwrap_or(config.get_bool(b"ui", b"relative-paths")?);
313 let output = DisplayStatusPaths {
310 let output = DisplayStatusPaths {
314 ui,
311 ui,
315 no_status,
312 no_status,
316 relativize: if relative_paths {
313 relativize: if relative_paths {
317 Some(RelativizePaths::new(repo)?)
314 Some(RelativizePaths::new(repo)?)
318 } else {
315 } else {
319 None
316 None
320 },
317 },
321 };
322 if display_states.modified {
323 output.display(b"M ", "status.modified", ds_status.modified)?;
324 }
325 if display_states.added {
326 output.display(b"A ", "status.added", ds_status.added)?;
327 }
328 if display_states.removed {
329 output.display(b"R ", "status.removed", ds_status.removed)?;
330 }
331 if display_states.deleted {
332 output.display(b"! ", "status.deleted", ds_status.deleted)?;
333 }
334 if display_states.unknown {
335 output.display(b"? ", "status.unknown", ds_status.unknown)?;
336 }
337 if display_states.ignored {
338 output.display(b"I ", "status.ignored", ds_status.ignored)?;
339 }
340 if display_states.clean {
341 output.display(b"C ", "status.clean", ds_status.clean)?;
342 }
343
344 let dirstate_write_needed = ds_status.dirty;
345 let filesystem_time_at_status_start =
346 ds_status.filesystem_time_at_status_start;
347
348 Ok((
349 fixup,
350 dirstate_write_needed,
351 filesystem_time_at_status_start,
352 ))
318 };
353 };
319 if display_states.modified {
354 let (fixup, mut dirstate_write_needed, filesystem_time_at_status_start) =
320 output.display(b"M ", "status.modified", ds_status.modified)?;
355 dmap.with_status(
321 }
356 &AlwaysMatcher,
322 if display_states.added {
357 repo.working_directory_path().to_owned(),
323 output.display(b"A ", "status.added", ds_status.added)?;
358 ignore_files(repo, config),
324 }
359 options,
325 if display_states.removed {
360 after_status,
326 output.display(b"R ", "status.removed", ds_status.removed)?;
361 )?;
327 }
328 if display_states.deleted {
329 output.display(b"! ", "status.deleted", ds_status.deleted)?;
330 }
331 if display_states.unknown {
332 output.display(b"? ", "status.unknown", ds_status.unknown)?;
333 }
334 if display_states.ignored {
335 output.display(b"I ", "status.ignored", ds_status.ignored)?;
336 }
337 if display_states.clean {
338 output.display(b"C ", "status.clean", ds_status.clean)?;
339 }
340
341 let mut dirstate_write_needed = ds_status.dirty;
342 let filesystem_time_at_status_start =
343 ds_status.filesystem_time_at_status_start;
344
362
345 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
363 if (fixup.is_empty() || filesystem_time_at_status_start.is_none())
346 && !dirstate_write_needed
364 && !dirstate_write_needed
347 {
365 {
348 // Nothing to update
366 // Nothing to update
349 return Ok(());
367 return Ok(());
350 }
368 }
351
369
352 // Update the dirstate on disk if we can
370 // Update the dirstate on disk if we can
353 let with_lock_result =
371 let with_lock_result =
354 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
372 repo.try_with_wlock_no_wait(|| -> Result<(), CommandError> {
355 if let Some(mtime_boundary) = filesystem_time_at_status_start {
373 if let Some(mtime_boundary) = filesystem_time_at_status_start {
356 for hg_path in fixup {
374 for hg_path in fixup {
357 use std::os::unix::fs::MetadataExt;
375 use std::os::unix::fs::MetadataExt;
358 let fs_path = hg_path_to_path_buf(&hg_path)
376 let fs_path = hg_path_to_path_buf(&hg_path)
359 .expect("HgPath conversion");
377 .expect("HgPath conversion");
360 // Specifically do not reuse `fs_metadata` from
378 // Specifically do not reuse `fs_metadata` from
361 // `unsure_is_clean` which was needed before reading
379 // `unsure_is_clean` which was needed before reading
362 // contents. Here we access metadata again after reading
380 // contents. Here we access metadata again after reading
363 // content, in case it changed in the meantime.
381 // content, in case it changed in the meantime.
364 let fs_metadata = repo
382 let fs_metadata = repo
365 .working_directory_vfs()
383 .working_directory_vfs()
366 .symlink_metadata(&fs_path)?;
384 .symlink_metadata(&fs_path)?;
367 if let Some(mtime) =
385 if let Some(mtime) =
368 TruncatedTimestamp::for_reliable_mtime_of(
386 TruncatedTimestamp::for_reliable_mtime_of(
369 &fs_metadata,
387 &fs_metadata,
370 &mtime_boundary,
388 &mtime_boundary,
371 )
389 )
372 .when_reading_file(&fs_path)?
390 .when_reading_file(&fs_path)?
373 {
391 {
374 let mode = fs_metadata.mode();
392 let mode = fs_metadata.mode();
375 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
393 let size = fs_metadata.len() as u32 & RANGE_MASK_31BIT;
376 let mut entry = dmap
394 let mut entry = dmap
377 .get(&hg_path)?
395 .get(&hg_path)?
378 .expect("ambiguous file not in dirstate");
396 .expect("ambiguous file not in dirstate");
379 entry.set_clean(mode, size, mtime);
397 entry.set_clean(mode, size, mtime);
380 dmap.add_file(&hg_path, entry)?;
398 dmap.add_file(&hg_path, entry)?;
381 dirstate_write_needed = true
399 dirstate_write_needed = true
382 }
400 }
383 }
401 }
384 }
402 }
385 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
403 drop(dmap); // Avoid "already mutably borrowed" RefCell panics
386 if dirstate_write_needed {
404 if dirstate_write_needed {
387 repo.write_dirstate()?
405 repo.write_dirstate()?
388 }
406 }
389 Ok(())
407 Ok(())
390 });
408 });
391 match with_lock_result {
409 match with_lock_result {
392 Ok(closure_result) => closure_result?,
410 Ok(closure_result) => closure_result?,
393 Err(LockError::AlreadyHeld) => {
411 Err(LockError::AlreadyHeld) => {
394 // Not updating the dirstate is not ideal but not critical:
412 // Not updating the dirstate is not ideal but not critical:
395 // don’t keep our caller waiting until some other Mercurial
413 // don’t keep our caller waiting until some other Mercurial
396 // process releases the lock.
414 // process releases the lock.
397 }
415 }
398 Err(LockError::Other(HgError::IoError { error, .. }))
416 Err(LockError::Other(HgError::IoError { error, .. }))
399 if error.kind() == io::ErrorKind::PermissionDenied =>
417 if error.kind() == io::ErrorKind::PermissionDenied =>
400 {
418 {
401 // `hg status` on a read-only repository is fine
419 // `hg status` on a read-only repository is fine
402 }
420 }
403 Err(LockError::Other(error)) => {
421 Err(LockError::Other(error)) => {
404 // Report other I/O errors
422 // Report other I/O errors
405 Err(error)?
423 Err(error)?
406 }
424 }
407 }
425 }
408 Ok(())
426 Ok(())
409 }
427 }
410
428
411 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
429 fn ignore_files(repo: &Repo, config: &Config) -> Vec<PathBuf> {
412 let mut ignore_files = Vec::new();
430 let mut ignore_files = Vec::new();
413 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
431 let repo_ignore = repo.working_directory_vfs().join(".hgignore");
414 if repo_ignore.exists() {
432 if repo_ignore.exists() {
415 ignore_files.push(repo_ignore)
433 ignore_files.push(repo_ignore)
416 }
434 }
417 for (key, value) in config.iter_section(b"ui") {
435 for (key, value) in config.iter_section(b"ui") {
418 if key == b"ignore" || key.starts_with(b"ignore.") {
436 if key == b"ignore" || key.starts_with(b"ignore.") {
419 let path = get_path_from_bytes(value);
437 let path = get_path_from_bytes(value);
420 // TODO: expand "~/" and environment variable here, like Python
438 // TODO: expand "~/" and environment variable here, like Python
421 // does with `os.path.expanduser` and `os.path.expandvars`
439 // does with `os.path.expanduser` and `os.path.expandvars`
422
440
423 let joined = repo.working_directory_path().join(path);
441 let joined = repo.working_directory_path().join(path);
424 ignore_files.push(joined);
442 ignore_files.push(joined);
425 }
443 }
426 }
444 }
427 ignore_files
445 ignore_files
428 }
446 }
429
447
430 struct DisplayStatusPaths<'a> {
448 struct DisplayStatusPaths<'a> {
431 ui: &'a Ui,
449 ui: &'a Ui,
432 no_status: bool,
450 no_status: bool,
433 relativize: Option<RelativizePaths>,
451 relativize: Option<RelativizePaths>,
434 }
452 }
435
453
436 impl DisplayStatusPaths<'_> {
454 impl DisplayStatusPaths<'_> {
437 // Probably more elegant to use a Deref or Borrow trait rather than
455 // Probably more elegant to use a Deref or Borrow trait rather than
438 // harcode HgPathBuf, but probably not really useful at this point
456 // harcode HgPathBuf, but probably not really useful at this point
439 fn display(
457 fn display(
440 &self,
458 &self,
441 status_prefix: &[u8],
459 status_prefix: &[u8],
442 label: &'static str,
460 label: &'static str,
443 mut paths: Vec<StatusPath<'_>>,
461 mut paths: Vec<StatusPath<'_>>,
444 ) -> Result<(), CommandError> {
462 ) -> Result<(), CommandError> {
445 paths.sort_unstable();
463 paths.sort_unstable();
446 // TODO: get the stdout lock once for the whole loop
464 // TODO: get the stdout lock once for the whole loop
447 // instead of in each write
465 // instead of in each write
448 for StatusPath { path, copy_source } in paths {
466 for StatusPath { path, copy_source } in paths {
449 let relative;
467 let relative;
450 let path = if let Some(relativize) = &self.relativize {
468 let path = if let Some(relativize) = &self.relativize {
451 relative = relativize.relativize(&path);
469 relative = relativize.relativize(&path);
452 &*relative
470 &*relative
453 } else {
471 } else {
454 path.as_bytes()
472 path.as_bytes()
455 };
473 };
456 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
474 // TODO: Add a way to use `write_bytes!` instead of `format_bytes!`
457 // in order to stream to stdout instead of allocating an
475 // in order to stream to stdout instead of allocating an
458 // itermediate `Vec<u8>`.
476 // itermediate `Vec<u8>`.
459 if !self.no_status {
477 if !self.no_status {
460 self.ui.write_stdout_labelled(status_prefix, label)?
478 self.ui.write_stdout_labelled(status_prefix, label)?
461 }
479 }
462 self.ui
480 self.ui
463 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
481 .write_stdout_labelled(&format_bytes!(b"{}\n", path), label)?;
464 if let Some(source) = copy_source {
482 if let Some(source) = copy_source {
465 let label = "status.copied";
483 let label = "status.copied";
466 self.ui.write_stdout_labelled(
484 self.ui.write_stdout_labelled(
467 &format_bytes!(b" {}\n", source.as_bytes()),
485 &format_bytes!(b" {}\n", source.as_bytes()),
468 label,
486 label,
469 )?
487 )?
470 }
488 }
471 }
489 }
472 Ok(())
490 Ok(())
473 }
491 }
474 }
492 }
475
493
476 /// Check if a file is modified by comparing actual repo store and file system.
494 /// Check if a file is modified by comparing actual repo store and file system.
477 ///
495 ///
478 /// This meant to be used for those that the dirstate cannot resolve, due
496 /// This meant to be used for those that the dirstate cannot resolve, due
479 /// to time resolution limits.
497 /// to time resolution limits.
480 fn unsure_is_modified(
498 fn unsure_is_modified(
481 repo: &Repo,
499 repo: &Repo,
482 manifest: &Manifest,
500 manifest: &Manifest,
483 hg_path: &HgPath,
501 hg_path: &HgPath,
484 ) -> Result<bool, HgError> {
502 ) -> Result<bool, HgError> {
485 let vfs = repo.working_directory_vfs();
503 let vfs = repo.working_directory_vfs();
486 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
504 let fs_path = hg_path_to_path_buf(hg_path).expect("HgPath conversion");
487 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
505 let fs_metadata = vfs.symlink_metadata(&fs_path)?;
488 let is_symlink = fs_metadata.file_type().is_symlink();
506 let is_symlink = fs_metadata.file_type().is_symlink();
489 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
507 // TODO: Also account for `FALLBACK_SYMLINK` and `FALLBACK_EXEC` from the
490 // dirstate
508 // dirstate
491 let fs_flags = if is_symlink {
509 let fs_flags = if is_symlink {
492 Some(b'l')
510 Some(b'l')
493 } else if has_exec_bit(&fs_metadata) {
511 } else if has_exec_bit(&fs_metadata) {
494 Some(b'x')
512 Some(b'x')
495 } else {
513 } else {
496 None
514 None
497 };
515 };
498
516
499 let entry = manifest
517 let entry = manifest
500 .find_by_path(hg_path)?
518 .find_by_path(hg_path)?
501 .expect("ambgious file not in p1");
519 .expect("ambgious file not in p1");
502 if entry.flags != fs_flags {
520 if entry.flags != fs_flags {
503 return Ok(true);
521 return Ok(true);
504 }
522 }
505 let filelog = repo.filelog(hg_path)?;
523 let filelog = repo.filelog(hg_path)?;
506 let fs_len = fs_metadata.len();
524 let fs_len = fs_metadata.len();
507 let filelog_entry =
525 let filelog_entry =
508 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
526 filelog.entry_for_node(entry.node_id()?).map_err(|_| {
509 HgError::corrupted("filelog missing node from manifest")
527 HgError::corrupted("filelog missing node from manifest")
510 })?;
528 })?;
511 if filelog_entry.file_data_len_not_equal_to(fs_len) {
529 if filelog_entry.file_data_len_not_equal_to(fs_len) {
512 // No need to read file contents:
530 // No need to read file contents:
513 // it cannot be equal if it has a different length.
531 // it cannot be equal if it has a different length.
514 return Ok(true);
532 return Ok(true);
515 }
533 }
516
534
517 let p1_filelog_data = filelog_entry.data()?;
535 let p1_filelog_data = filelog_entry.data()?;
518 let p1_contents = p1_filelog_data.file_data()?;
536 let p1_contents = p1_filelog_data.file_data()?;
519 if p1_contents.len() as u64 != fs_len {
537 if p1_contents.len() as u64 != fs_len {
520 // No need to read file contents:
538 // No need to read file contents:
521 // it cannot be equal if it has a different length.
539 // it cannot be equal if it has a different length.
522 return Ok(true);
540 return Ok(true);
523 }
541 }
524
542
525 let fs_contents = if is_symlink {
543 let fs_contents = if is_symlink {
526 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
544 get_bytes_from_os_string(vfs.read_link(fs_path)?.into_os_string())
527 } else {
545 } else {
528 vfs.read(fs_path)?
546 vfs.read(fs_path)?
529 };
547 };
530 Ok(p1_contents != &*fs_contents)
548 Ok(p1_contents != &*fs_contents)
531 }
549 }
General Comments 0
You need to be logged in to leave comments. Login now