status: prefer relative paths in Rust code
… when the repository root is under the current directory,
so the kernel needs to traverse fewer directory in every call
to `read_dir` or `symlink_metadata`.
Better yet would be to use libc functions like `openat` and `fstatat`
to remove such repeated traversals entirely, but the standard library
does not provide APIs based on those.
Maybe with a crate like https://crates.io/crates/openat instead?
Benchmarks of `rhg status` show that this patch is neutral in some configurations,
and makes the command up to ~20% faster in others.
Below is semi-arbitrary subset of results. The four numeric columns are:
time (in seconds) with this changeset’s parent, time with this changeset,
time difference (negative is better), time ratio (less than 1 is better).
```
mercurial-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.0061 -> 0.0059: -0.0002 (0.97)
mercurial-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0029 -> 0.0028: -0.0001 (0.97)
mozilla-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.2110 -> 0.2102: -0.0007 (1.00)
mozilla-dirstate-v2 | default-copies-clean.ignored.pbr | 0.0489 -> 0.0401: -0.0088 (0.82)
mozilla-dirstate-v2 | default-copies-clean.no-iu.pbr | 0.0479 -> 0.0393: -0.0085 (0.82)
mozilla-dirstate-v2 | default-copies-large.all.pbr | 0.1262 -> 0.1210: -0.0051 (0.96)
mozilla-dirstate-v2 | default-copies-small.ignored-unknown.pbr | 0.1262 -> 0.1200: -0.0062 (0.95)
mozilla-dirstate-v2 | default-copies-small.ignored.pbr | 0.0536 -> 0.0417: -0.0119 (0.78)
mozilla-dirstate-v2 | default-copies-small.no-iu.pbr | 0.0482 -> 0.0393: -0.0089 (0.81)
mozilla-dirstate-v2 | default-plain-clean.ignored.pbr | 0.0518 -> 0.0402: -0.0116 (0.78)
mozilla-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0481 -> 0.0392: -0.0088 (0.82)
mozilla-dirstate-v2 | default-plain-large.all.pbr | 0.1271 -> 0.1218: -0.0052 (0.96)
mozilla-dirstate-v2 | default-plain-small.ignored-unknown.pbr | 0.1225 -> 0.1202: -0.0022 (0.98)
mozilla-dirstate-v2 | default-plain-small.ignored.pbr | 0.0510 -> 0.0418: -0.0092 (0.82)
mozilla-dirstate-v2 | default-plain-small.no-iu.pbr | 0.0480 -> 0.0394: -0.0086 (0.82)
netbeans-dirstate-v1 | default-plain-clean.no-iu.pbr | 0.1442 -> 0.1422: -0.0020 (0.99)
netbeans-dirstate-v2 | default-plain-clean.no-iu.pbr | 0.0325 -> 0.0282: -0.0043 (0.87)
```
Differential Revision: https://phab.mercurial-scm.org/D12175
#require py37
$ byteify_strings () {
> "$PYTHON" "$TESTDIR/../contrib/byteify-strings.py" "$@"
> }
Test version
$ byteify_strings --version
Byteify strings * (glob)
Test in-place
$ cat > testfile.py <<EOF
> obj['test'] = b"1234"
> mydict.iteritems()
> EOF
$ byteify_strings testfile.py -i
$ cat testfile.py
obj[b'test'] = b"1234"
mydict.iteritems()
Test with dictiter
$ cat > testfile.py <<EOF
> obj['test'] = b"1234"
> mydict.iteritems()
> EOF
$ byteify_strings testfile.py --dictiter
obj[b'test'] = b"1234"
mydict.items()
Test kwargs-like objects
$ cat > testfile.py <<EOF
> kwargs['test'] = "123"
> kwargs[test['testing']]
> kwargs[test[[['testing']]]]
> kwargs[kwargs['testing']]
> kwargs.get('test')
> kwargs.pop('test')
> kwargs.get('test', 'testing')
> kwargs.pop('test', 'testing')
> kwargs.setdefault('test', 'testing')
>
> opts['test'] = "123"
> opts[test['testing']]
> opts[test[[['testing']]]]
> opts[opts['testing']]
> opts.get('test')
> opts.pop('test')
> opts.get('test', 'testing')
> opts.pop('test', 'testing')
> opts.setdefault('test', 'testing')
>
> commitopts['test'] = "123"
> commitopts[test['testing']]
> commitopts[test[[['testing']]]]
> commitopts[commitopts['testing']]
> commitopts.get('test')
> commitopts.pop('test')
> commitopts.get('test', 'testing')
> commitopts.pop('test', 'testing')
> commitopts.setdefault('test', 'testing')
> EOF
$ byteify_strings testfile.py --treat-as-kwargs kwargs opts commitopts
kwargs['test'] = b"123"
kwargs[test[b'testing']]
kwargs[test[[[b'testing']]]]
kwargs[kwargs['testing']]
kwargs.get('test')
kwargs.pop('test')
kwargs.get('test', b'testing')
kwargs.pop('test', b'testing')
kwargs.setdefault('test', b'testing')
opts['test'] = b"123"
opts[test[b'testing']]
opts[test[[[b'testing']]]]
opts[opts['testing']]
opts.get('test')
opts.pop('test')
opts.get('test', b'testing')
opts.pop('test', b'testing')
opts.setdefault('test', b'testing')
commitopts['test'] = b"123"
commitopts[test[b'testing']]
commitopts[test[[[b'testing']]]]
commitopts[commitopts['testing']]
commitopts.get('test')
commitopts.pop('test')
commitopts.get('test', b'testing')
commitopts.pop('test', b'testing')
commitopts.setdefault('test', b'testing')
Test attr*() as methods
$ cat > testfile.py <<EOF
> setattr(o, 'a', 1)
> util.setattr(o, 'ae', 1)
> util.getattr(o, 'alksjdf', 'default')
> util.addattr(o, 'asdf')
> util.hasattr(o, 'lksjdf', 'default')
> util.safehasattr(o, 'lksjdf', 'default')
> @eh.wrapfunction(func, 'lksjdf')
> def f():
> pass
> @eh.wrapclass(klass, 'lksjdf')
> def f():
> pass
> EOF
$ byteify_strings testfile.py --allow-attr-methods
setattr(o, 'a', 1)
util.setattr(o, 'ae', 1)
util.getattr(o, 'alksjdf', b'default')
util.addattr(o, 'asdf')
util.hasattr(o, 'lksjdf', b'default')
util.safehasattr(o, 'lksjdf', b'default')
@eh.wrapfunction(func, 'lksjdf')
def f():
pass
@eh.wrapclass(klass, 'lksjdf')
def f():
pass
Test without attr*() as methods
$ cat > testfile.py <<EOF
> setattr(o, 'a', 1)
> util.setattr(o, 'ae', 1)
> util.getattr(o, 'alksjdf', 'default')
> util.addattr(o, 'asdf')
> util.hasattr(o, 'lksjdf', 'default')
> util.safehasattr(o, 'lksjdf', 'default')
> @eh.wrapfunction(func, 'lksjdf')
> def f():
> pass
> @eh.wrapclass(klass, 'lksjdf')
> def f():
> pass
> EOF
$ byteify_strings testfile.py
setattr(o, 'a', 1)
util.setattr(o, b'ae', 1)
util.getattr(o, b'alksjdf', b'default')
util.addattr(o, b'asdf')
util.hasattr(o, b'lksjdf', b'default')
util.safehasattr(o, b'lksjdf', b'default')
@eh.wrapfunction(func, b'lksjdf')
def f():
pass
@eh.wrapclass(klass, b'lksjdf')
def f():
pass
Test ignore comments
$ cat > testfile.py <<EOF
> # py3-transform: off
> "none"
> "of"
> 'these'
> s = """should"""
> d = '''be'''
> # py3-transform: on
> "this should"
> 'and this also'
>
> # no-py3-transform
> l = "this should be ignored"
> l2 = "this shouldn't"
>
> EOF
$ byteify_strings testfile.py
# py3-transform: off
"none"
"of"
'these'
s = """should"""
d = '''be'''
# py3-transform: on
b"this should"
b'and this also'
# no-py3-transform
l = "this should be ignored"
l2 = b"this shouldn't"
Test triple-quoted strings
$ cat > testfile.py <<EOF
> """This is ignored
> """
>
> line = """
> This should not be
> """
> line = '''
> Neither should this
> '''
> EOF
$ byteify_strings testfile.py
"""This is ignored
"""
line = b"""
This should not be
"""
line = b'''
Neither should this
'''
Test prefixed strings
$ cat > testfile.py <<EOF
> obj['test'] = b"1234"
> obj[r'test'] = u"1234"
> EOF
$ byteify_strings testfile.py
obj[b'test'] = b"1234"
obj[r'test'] = u"1234"
Test multi-line alignment
$ cat > testfile.py <<'EOF'
> def foo():
> error.Abort(_("foo"
> "bar"
> "%s")
> % parameter)
> {
> 'test': dict,
> 'test2': dict,
> }
> [
> "thing",
> "thing2"
> ]
> (
> "tuple",
> "tuple2",
> )
> {"thing",
> }
> EOF
$ byteify_strings testfile.py
def foo():
error.Abort(_(b"foo"
b"bar"
b"%s")
% parameter)
{
b'test': dict,
b'test2': dict,
}
[
b"thing",
b"thing2"
]
(
b"tuple",
b"tuple2",
)
{b"thing",
}